From df4148a5a3cf18ee6e13e262079130b169ee51ac Mon Sep 17 00:00:00 2001 From: Mingyou Chen Date: Fri, 13 Feb 2026 13:59:02 +0800 Subject: [PATCH 001/359] ASoC: Add DMIC support for the AMD RPL platform Add RPL rev 0x62 check and reserve ACP CONFIG PIN 10 to 14 for RPL platform Signed-off-by: Mingyou Chen Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260213055904.110256-2-qby140326@gmail.com Reviewed-by: Vijendar Mukunda Signed-off-by: Mark Brown --- sound/soc/amd/yc/pci-acp6x.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/soc/amd/yc/pci-acp6x.c b/sound/soc/amd/yc/pci-acp6x.c index 1140ed1cbb3d..671443204e04 100644 --- a/sound/soc/amd/yc/pci-acp6x.c +++ b/sound/soc/amd/yc/pci-acp6x.c @@ -163,6 +163,7 @@ static int snd_acp6x_probe(struct pci_dev *pci, switch (pci->revision) { case 0x60: case 0x6f: + case 0x62: /* RPL */ break; default: dev_dbg(&pci->dev, "acp6x pci device not found\n"); @@ -208,6 +209,17 @@ static int snd_acp6x_probe(struct pci_dev *pci, case ACP_CONFIG_15: dev_info(&pci->dev, "Audio Mode %d\n", val); break; + case ACP_CONFIG_10: + case ACP_CONFIG_11: + case ACP_CONFIG_12: + case ACP_CONFIG_13: + case ACP_CONFIG_14: + /* PIN 10 to 14 is reserve for RPL */ + if (pci->revision == 0x62) { + dev_info(&pci->dev, "RPL Audio Mode %d\n", val); + break; + } + fallthrough; default: adata->res = devm_kzalloc(&pci->dev, sizeof(struct resource), From d3fbf61317f6ec99398661b95d1046008ee61282 Mon Sep 17 00:00:00 2001 From: Mingyou Chen Date: Fri, 13 Feb 2026 13:59:03 +0800 Subject: [PATCH 002/359] ASoC: Drop RPL driver The RPL driver is no longer needed because its functionality has been superseded by the YC driver Signed-off-by: Mingyou Chen Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260213055904.110256-3-qby140326@gmail.com Reviewed-by: Vijendar Mukunda Signed-off-by: Mark Brown --- sound/soc/amd/Kconfig | 10 - sound/soc/amd/Makefile | 1 - sound/soc/amd/rpl/Makefile | 5 - sound/soc/amd/rpl/rpl-pci-acp6x.c | 227 ------------------ sound/soc/amd/rpl/rpl_acp6x.h | 36 --- .../soc/amd/rpl/rpl_acp6x_chip_offset_byte.h | 30 --- 6 files changed, 309 deletions(-) delete mode 100644 sound/soc/amd/rpl/Makefile delete mode 100644 sound/soc/amd/rpl/rpl-pci-acp6x.c delete mode 100644 sound/soc/amd/rpl/rpl_acp6x.h delete mode 100644 sound/soc/amd/rpl/rpl_acp6x_chip_offset_byte.h diff --git a/sound/soc/amd/Kconfig b/sound/soc/amd/Kconfig index f7c3010624df..53139766fafb 100644 --- a/sound/soc/amd/Kconfig +++ b/sound/soc/amd/Kconfig @@ -122,16 +122,6 @@ config SND_AMD_ACP_CONFIG source "sound/soc/amd/acp/Kconfig" -config SND_SOC_AMD_RPL_ACP6x - tristate "AMD Audio Coprocessor-v6.2 RPL support" - depends on X86 && PCI - help - This option enables Audio Coprocessor i.e. ACP v6.2 support on - AMD RPL platform. By enabling this flag build will be - triggered for ACP PCI driver. - Say m if you have such a device. - If unsure select "N". - config SND_SOC_AMD_ACP63_TOPLEVEL tristate "support for AMD platforms with ACP version >= 6.3" default SND_AMD_ACP_CONFIG diff --git a/sound/soc/amd/Makefile b/sound/soc/amd/Makefile index 4f89d962cce2..23b25ff0d800 100644 --- a/sound/soc/amd/Makefile +++ b/sound/soc/amd/Makefile @@ -17,5 +17,4 @@ obj-$(CONFIG_SND_SOC_AMD_ACP5x) += vangogh/ obj-$(CONFIG_SND_SOC_AMD_ACP6x) += yc/ obj-$(CONFIG_SND_AMD_ACP_CONFIG) += acp/ obj-$(CONFIG_SND_AMD_ACP_CONFIG) += snd-acp-config.o -obj-$(CONFIG_SND_SOC_AMD_RPL_ACP6x) += rpl/ obj-$(CONFIG_SND_SOC_AMD_PS) += ps/ diff --git a/sound/soc/amd/rpl/Makefile b/sound/soc/amd/rpl/Makefile deleted file mode 100644 index a3825c5be4e7..000000000000 --- a/sound/soc/amd/rpl/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0+ -# RPL platform Support -snd-rpl-pci-acp6x-y := rpl-pci-acp6x.o - -obj-$(CONFIG_SND_SOC_AMD_RPL_ACP6x) += snd-rpl-pci-acp6x.o diff --git a/sound/soc/amd/rpl/rpl-pci-acp6x.c b/sound/soc/amd/rpl/rpl-pci-acp6x.c deleted file mode 100644 index e3afe9172bdf..000000000000 --- a/sound/soc/amd/rpl/rpl-pci-acp6x.c +++ /dev/null @@ -1,227 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * AMD RPL ACP PCI Driver - * - * Copyright 2022 Advanced Micro Devices, Inc. - */ - -#include -#include -#include -#include -#include -#include - -#include "rpl_acp6x.h" - -struct rpl_dev_data { - void __iomem *acp6x_base; -}; - -static int rpl_power_on(void __iomem *acp_base) -{ - u32 val; - int timeout; - - val = rpl_acp_readl(acp_base + ACP_PGFSM_STATUS); - - if (!val) - return val; - - if ((val & ACP_PGFSM_STATUS_MASK) != ACP_POWER_ON_IN_PROGRESS) - rpl_acp_writel(ACP_PGFSM_CNTL_POWER_ON_MASK, acp_base + ACP_PGFSM_CONTROL); - timeout = 0; - while (++timeout < 500) { - val = rpl_acp_readl(acp_base + ACP_PGFSM_STATUS); - if (!val) - return 0; - udelay(1); - } - return -ETIMEDOUT; -} - -static int rpl_reset(void __iomem *acp_base) -{ - u32 val; - int timeout; - - rpl_acp_writel(1, acp_base + ACP_SOFT_RESET); - timeout = 0; - while (++timeout < 500) { - val = rpl_acp_readl(acp_base + ACP_SOFT_RESET); - if (val & ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK) - break; - cpu_relax(); - } - rpl_acp_writel(0, acp_base + ACP_SOFT_RESET); - timeout = 0; - while (++timeout < 500) { - val = rpl_acp_readl(acp_base + ACP_SOFT_RESET); - if (!val) - return 0; - cpu_relax(); - } - return -ETIMEDOUT; -} - -static int rpl_init(void __iomem *acp_base) -{ - int ret; - - /* power on */ - ret = rpl_power_on(acp_base); - if (ret) { - pr_err("ACP power on failed\n"); - return ret; - } - rpl_acp_writel(0x01, acp_base + ACP_CONTROL); - /* Reset */ - ret = rpl_reset(acp_base); - if (ret) { - pr_err("ACP reset failed\n"); - return ret; - } - rpl_acp_writel(0x03, acp_base + ACP_CLKMUX_SEL); - return 0; -} - -static int rpl_deinit(void __iomem *acp_base) -{ - int ret; - - /* Reset */ - ret = rpl_reset(acp_base); - if (ret) { - pr_err("ACP reset failed\n"); - return ret; - } - rpl_acp_writel(0x00, acp_base + ACP_CLKMUX_SEL); - rpl_acp_writel(0x00, acp_base + ACP_CONTROL); - return 0; -} - -static int snd_rpl_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - struct rpl_dev_data *adata; - u32 addr; - int ret; - - /* RPL device check */ - switch (pci->revision) { - case 0x62: - break; - default: - dev_dbg(&pci->dev, "acp6x pci device not found\n"); - return -ENODEV; - } - if (pci_enable_device(pci)) { - dev_err(&pci->dev, "pci_enable_device failed\n"); - return -ENODEV; - } - - ret = pci_request_regions(pci, "AMD ACP6x audio"); - if (ret < 0) { - dev_err(&pci->dev, "pci_request_regions failed\n"); - goto disable_pci; - } - - adata = devm_kzalloc(&pci->dev, sizeof(struct rpl_dev_data), - GFP_KERNEL); - if (!adata) { - ret = -ENOMEM; - goto release_regions; - } - - addr = pci_resource_start(pci, 0); - adata->acp6x_base = devm_ioremap(&pci->dev, addr, - pci_resource_len(pci, 0)); - if (!adata->acp6x_base) { - ret = -ENOMEM; - goto release_regions; - } - pci_set_master(pci); - pci_set_drvdata(pci, adata); - ret = rpl_init(adata->acp6x_base); - if (ret) - goto release_regions; - pm_runtime_set_autosuspend_delay(&pci->dev, ACP_SUSPEND_DELAY_MS); - pm_runtime_use_autosuspend(&pci->dev); - pm_runtime_put_noidle(&pci->dev); - pm_runtime_allow(&pci->dev); - - return 0; -release_regions: - pci_release_regions(pci); -disable_pci: - pci_disable_device(pci); - - return ret; -} - -static int snd_rpl_suspend(struct device *dev) -{ - struct rpl_dev_data *adata; - int ret; - - adata = dev_get_drvdata(dev); - ret = rpl_deinit(adata->acp6x_base); - if (ret) - dev_err(dev, "ACP de-init failed\n"); - return ret; -} - -static int snd_rpl_resume(struct device *dev) -{ - struct rpl_dev_data *adata; - int ret; - - adata = dev_get_drvdata(dev); - ret = rpl_init(adata->acp6x_base); - if (ret) - dev_err(dev, "ACP init failed\n"); - return ret; -} - -static const struct dev_pm_ops rpl_pm = { - RUNTIME_PM_OPS(snd_rpl_suspend, snd_rpl_resume, NULL) - SYSTEM_SLEEP_PM_OPS(snd_rpl_suspend, snd_rpl_resume) -}; - -static void snd_rpl_remove(struct pci_dev *pci) -{ - struct rpl_dev_data *adata; - int ret; - - adata = pci_get_drvdata(pci); - ret = rpl_deinit(adata->acp6x_base); - if (ret) - dev_err(&pci->dev, "ACP de-init failed\n"); - pm_runtime_forbid(&pci->dev); - pm_runtime_get_noresume(&pci->dev); - pci_release_regions(pci); - pci_disable_device(pci); -} - -static const struct pci_device_id snd_rpl_ids[] = { - { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_DEVICE_ID), - .class = PCI_CLASS_MULTIMEDIA_OTHER << 8, - .class_mask = 0xffffff }, - { 0, }, -}; -MODULE_DEVICE_TABLE(pci, snd_rpl_ids); - -static struct pci_driver rpl_acp6x_driver = { - .name = KBUILD_MODNAME, - .id_table = snd_rpl_ids, - .probe = snd_rpl_probe, - .remove = snd_rpl_remove, - .driver = { - .pm = pm_ptr(&rpl_pm), - } -}; - -module_pci_driver(rpl_acp6x_driver); - -MODULE_DESCRIPTION("AMD ACP RPL PCI driver"); -MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/amd/rpl/rpl_acp6x.h b/sound/soc/amd/rpl/rpl_acp6x.h deleted file mode 100644 index f5816a33632e..000000000000 --- a/sound/soc/amd/rpl/rpl_acp6x.h +++ /dev/null @@ -1,36 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * AMD ACP Driver - * - * Copyright (C) 2021 Advanced Micro Devices, Inc. All rights reserved. - */ - -#include "rpl_acp6x_chip_offset_byte.h" - -#define ACP_DEVICE_ID 0x15E2 -#define ACP6x_PHY_BASE_ADDRESS 0x1240000 - -#define ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK 0x00010001 -#define ACP_PGFSM_CNTL_POWER_ON_MASK 1 -#define ACP_PGFSM_CNTL_POWER_OFF_MASK 0 -#define ACP_PGFSM_STATUS_MASK 3 -#define ACP_POWERED_ON 0 -#define ACP_POWER_ON_IN_PROGRESS 1 -#define ACP_POWERED_OFF 2 -#define ACP_POWER_OFF_IN_PROGRESS 3 - -#define DELAY_US 5 -#define ACP_COUNTER 20000 - -/* time in ms for runtime suspend delay */ -#define ACP_SUSPEND_DELAY_MS 2000 - -static inline u32 rpl_acp_readl(void __iomem *base_addr) -{ - return readl(base_addr - ACP6x_PHY_BASE_ADDRESS); -} - -static inline void rpl_acp_writel(u32 val, void __iomem *base_addr) -{ - writel(val, base_addr - ACP6x_PHY_BASE_ADDRESS); -} diff --git a/sound/soc/amd/rpl/rpl_acp6x_chip_offset_byte.h b/sound/soc/amd/rpl/rpl_acp6x_chip_offset_byte.h deleted file mode 100644 index 456498f5396d..000000000000 --- a/sound/soc/amd/rpl/rpl_acp6x_chip_offset_byte.h +++ /dev/null @@ -1,30 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * AMD ACP 6.2 Register Documentation - * - * Copyright 2022 Advanced Micro Devices, Inc. - */ - -#ifndef _rpl_acp6x_OFFSET_HEADER -#define _rpl_acp6x_OFFSET_HEADER - -/* Registers from ACP_CLKRST block */ -#define ACP_SOFT_RESET 0x1241000 -#define ACP_CONTROL 0x1241004 -#define ACP_STATUS 0x1241008 -#define ACP_DYNAMIC_CG_MASTER_CONTROL 0x1241010 -#define ACP_PGFSM_CONTROL 0x124101C -#define ACP_PGFSM_STATUS 0x1241020 -#define ACP_CLKMUX_SEL 0x1241024 - -/* Registers from ACP_AON block */ -#define ACP_PME_EN 0x1241400 -#define ACP_DEVICE_STATE 0x1241404 -#define AZ_DEVICE_STATE 0x1241408 -#define ACP_PIN_CONFIG 0x1241440 -#define ACP_PAD_PULLUP_CTRL 0x1241444 -#define ACP_PAD_PULLDOWN_CTRL 0x1241448 -#define ACP_PAD_DRIVE_STRENGTH_CTRL 0x124144C -#define ACP_PAD_SCHMEN_CTRL 0x1241450 - -#endif From 78dfbd4ad0be9f51de7b9a19388809254aeccd26 Mon Sep 17 00:00:00 2001 From: Mingyou Chen Date: Fri, 13 Feb 2026 13:59:04 +0800 Subject: [PATCH 003/359] ASoC: Add quirk for Lecoo Bellator N176 The Lecoo Bellator N176 requires a specific quirk to enable the DMIC support. Signed-off-by: Mingyou Chen Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260213055904.110256-4-qby140326@gmail.com Reviewed-by: Vijendar Mukunda 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 f1a63475100d..9ddfd4e8360f 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -45,6 +45,13 @@ static struct snd_soc_card acp6x_card = { }; static const struct dmi_system_id yc_acp_quirk_table[] = { + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Lecoo"), + DMI_MATCH(DMI_PRODUCT_NAME, "Bellator N176"), + } + }, { .driver_data = &acp6x_card, .matches = { From c5ad3f623103c5b606043517a63f202a22283d32 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Wed, 11 Feb 2026 11:27:33 +0100 Subject: [PATCH 004/359] ASoC: dt-bindings: awinic,aw88395: Document firmware-name property Since the aw88261_acf.bin file appears to be device-specific, add a firmware-name property so that devicetree users can point to a device-specific path to not conflict with other devices that might also ship a aw88261_acf.bin. Signed-off-by: Luca Weiss Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260211-aw88261-fwname-v1-2-e24e833a019d@fairphone.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/awinic,aw88395.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/awinic,aw88395.yaml b/Documentation/devicetree/bindings/sound/awinic,aw88395.yaml index 994d68c074a9..b9abb10942ba 100644 --- a/Documentation/devicetree/bindings/sound/awinic,aw88395.yaml +++ b/Documentation/devicetree/bindings/sound/awinic,aw88395.yaml @@ -35,6 +35,10 @@ properties: dvdd-supply: true + firmware-name: + maxItems: 1 + description: Name of the *_acf.bin file used for amplifier initialization + awinic,audio-channel: description: It is used to distinguish multiple PA devices, so that different From 171b3663f33e1efdc97f5112f49be10b47b20fa8 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Wed, 11 Feb 2026 11:27:34 +0100 Subject: [PATCH 005/359] ASoC: codecs: aw88261: Add firmware-name support Since the aw88261_acf.bin file appears to be device-specific, add firmware-name support so that devicetree users can point to a device-specific path to not conflict with other devices that might also ship a aw88261_acf.bin. Read the "firmware-name" property if available, and otherwise fall back to the generic name. Signed-off-by: Luca Weiss Link: https://patch.msgid.link/20260211-aw88261-fwname-v1-3-e24e833a019d@fairphone.com Signed-off-by: Mark Brown --- sound/soc/codecs/aw88261.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/aw88261.c b/sound/soc/codecs/aw88261.c index 43c03d3cb252..02f8c2bb2569 100644 --- a/sound/soc/codecs/aw88261.c +++ b/sound/soc/codecs/aw88261.c @@ -1094,17 +1094,22 @@ static int aw88261_dev_init(struct aw88261 *aw88261, struct aw_container *aw_cfg static int aw88261_request_firmware_file(struct aw88261 *aw88261) { const struct firmware *cont = NULL; + const char *fw_name; int ret; aw88261->aw_pa->fw_status = AW88261_DEV_FW_FAILED; - ret = request_firmware(&cont, AW88261_ACF_FILE, aw88261->aw_pa->dev); + ret = device_property_read_string(aw88261->aw_pa->dev, "firmware-name", &fw_name); + if (ret) + fw_name = AW88261_ACF_FILE; + + ret = request_firmware(&cont, fw_name, aw88261->aw_pa->dev); if (ret) return dev_err_probe(aw88261->aw_pa->dev, ret, - "load [%s] failed!", AW88261_ACF_FILE); + "load [%s] failed!", fw_name); dev_info(aw88261->aw_pa->dev, "loaded %s - size: %zu\n", - AW88261_ACF_FILE, cont ? cont->size : 0); + fw_name, cont ? cont->size : 0); aw88261->aw_cfg = devm_kzalloc(aw88261->aw_pa->dev, cont->size + sizeof(int), GFP_KERNEL); if (!aw88261->aw_cfg) { @@ -1117,7 +1122,7 @@ static int aw88261_request_firmware_file(struct aw88261 *aw88261) ret = aw88395_dev_load_acf_check(aw88261->aw_pa, aw88261->aw_cfg); if (ret) { - dev_err(aw88261->aw_pa->dev, "load [%s] failed !", AW88261_ACF_FILE); + dev_err(aw88261->aw_pa->dev, "load [%s] failed !", fw_name); return ret; } From f43aed8dfecb10e95b8235d002990739ebe72c5e Mon Sep 17 00:00:00 2001 From: Yauhen Kharuzhy Date: Wed, 11 Feb 2026 02:18:33 +0200 Subject: [PATCH 006/359] ASoC: rt5677: Add 10EC5677 ACPI ID as supported Add 10EC5677 ACPI ID to the match table. It is used in the Lenovo Yoga Book YB1-X91 tablet at least. Signed-off-by: Yauhen Kharuzhy Link: https://patch.msgid.link/20260211002255.4090440-2-jekhor@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5677.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index d46385249867..ccbd32cf8298 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -5204,6 +5204,7 @@ MODULE_DEVICE_TABLE(of, rt5677_of_match); static const struct acpi_device_id rt5677_acpi_match[] = { { "10EC5677", RT5677 }, { "RT5677CE", RT5677 }, + { "10EC5677", RT5677 }, { } }; MODULE_DEVICE_TABLE(acpi, rt5677_acpi_match); From 9f56f0bd2e49876ceeaf9036887fe9b50dfa6b6b Mon Sep 17 00:00:00 2001 From: Yauhen Kharuzhy Date: Wed, 11 Feb 2026 02:18:34 +0200 Subject: [PATCH 007/359] ASoC: rt5677: Allow it to be defined as an I2C board Some devices (e.g., Lenovo Yoga Book YB1-X90) define the codec as i2c_board, so add an I2C ID table to support this. Signed-off-by: Yauhen Kharuzhy Link: https://patch.msgid.link/20260211002255.4090440-3-jekhor@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5677.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index ccbd32cf8298..6628c7ca5a8a 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -5209,6 +5209,12 @@ static const struct acpi_device_id rt5677_acpi_match[] = { }; MODULE_DEVICE_TABLE(acpi, rt5677_acpi_match); +static const struct i2c_device_id rt5677_i2c_id[] = { + { "rt5677", RT5677 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rt5677_i2c_id); + static void rt5677_read_device_properties(struct rt5677_priv *rt5677, struct device *dev) { @@ -5532,7 +5538,6 @@ static int rt5677_init_irq(struct i2c_client *i2c) static int rt5677_i2c_probe(struct i2c_client *i2c) { - struct device *dev = &i2c->dev; struct rt5677_priv *rt5677; int ret; unsigned int val; @@ -5547,7 +5552,7 @@ static int rt5677_i2c_probe(struct i2c_client *i2c) INIT_DELAYED_WORK(&rt5677->dsp_work, rt5677_dsp_work); i2c_set_clientdata(i2c, rt5677); - rt5677->type = (enum rt5677_type)(uintptr_t)device_get_match_data(dev); + rt5677->type = (enum rt5677_type)(uintptr_t)i2c_get_match_data(i2c); if (rt5677->type == 0) return -EINVAL; @@ -5666,6 +5671,7 @@ static struct i2c_driver rt5677_i2c_driver = { .of_match_table = rt5677_of_match, .acpi_match_table = rt5677_acpi_match, }, + .id_table = rt5677_i2c_id, .probe = rt5677_i2c_probe, .remove = rt5677_i2c_remove, }; From 687f2679e8bf65bca247ca7f4013130d609e1e56 Mon Sep 17 00:00:00 2001 From: Yauhen Kharuzhy Date: Wed, 11 Feb 2026 02:18:35 +0200 Subject: [PATCH 008/359] ASoC: rt5677: Fix typo in the header file Fix a typo in the DAC1 Digital Volume register description comment (it was mistakenly named as DAC3). Signed-off-by: Yauhen Kharuzhy Link: https://patch.msgid.link/20260211002255.4090440-4-jekhor@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5677.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt5677.h b/sound/soc/codecs/rt5677.h index d67ebae067d9..876f7674015c 100644 --- a/sound/soc/codecs/rt5677.h +++ b/sound/soc/codecs/rt5677.h @@ -421,7 +421,7 @@ #define RT5677_DAC3_R_VOL_MASK (0xff) #define RT5677_DAC3_R_VOL_SFT 0 -/* DAC3 Digital Volume (0x19) */ +/* DAC1 Digital Volume (0x19) */ #define RT5677_DAC1_L_VOL_MASK (0xff << 8) #define RT5677_DAC1_L_VOL_SFT 8 #define RT5677_DAC1_R_VOL_MASK (0xff) From 14848e6a8058916e9b008734aa36da989725b6df Mon Sep 17 00:00:00 2001 From: Yauhen Kharuzhy Date: Wed, 11 Feb 2026 02:18:36 +0200 Subject: [PATCH 009/359] ASoC: rt5677: Report error if the ID register cannot be read To make error messages clearer, report if the ID register failed to be read via I2C during probing. Signed-off-by: Yauhen Kharuzhy Link: https://patch.msgid.link/20260211002255.4090440-5-jekhor@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5677.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 6628c7ca5a8a..845385c1fb88 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -5602,7 +5602,13 @@ static int rt5677_i2c_probe(struct i2c_client *i2c) return ret; } - regmap_read(rt5677->regmap, RT5677_VENDOR_ID2, &val); + ret = regmap_read(rt5677->regmap, RT5677_VENDOR_ID2, &val); + if (ret) { + dev_err(&i2c->dev, + "Failed to read ID register: %d\n", ret); + return -ENODEV; + } + if (val != RT5677_DEVICE_ID) { dev_err(&i2c->dev, "Device with ID register %#x is not rt5677\n", val); From 0cc00de3dd5b66c80e6ec1705ec929f4a45ffa1c Mon Sep 17 00:00:00 2001 From: Yauhen Kharuzhy Date: Wed, 11 Feb 2026 02:18:37 +0200 Subject: [PATCH 010/359] ASoC: rt5677: Set up ACPI GPIO pins mapping table To get GPIO configuration from ACPI, define an ACPI GPIO mapping table, linking GPIO names with CRS entries. The only known device that has an ACPI entry for the RT5677 codec is the Lenovo Yoga Book YB1-X91, and it uses this entry to describe the entire sound system configuration: codec connections and jack detection chip connections. This ACPI definition looks YogaBook-specific, but codec GPIOs may be recognized as generic, so add this lookup table for all devices. Details about the YogaBook RT5677 ACPI entry are provided below. CRS resources: I2C devices: 0: rt5677 codec 1: ts3a227e jack detection IC GPIOs: 0: rt5677 codec reset 1: rt5677 codec pow-ldo2 2: speaker enable INTs: 0: rt5677 codec 1: ts3a227e jack detection IC SPI device: 0: rt5677 codec SPI connection ACPI decompiled dump fragment: Device (RTEK) { Name (_ADR, Zero) // _ADR: Address Name (_HID, "10EC5677") // _HID: Hardware ID Name (_CID, "10EC5677") // _CID: Compatible ID Name (_DDN, "Realtek IIS Audio Codec") // _DDN: DOS Device Name Name (_SUB, "17AA7005") // _SUB: Subsystem ID Name (_UID, One) // _UID: Unique ID Name (_PR0, Package (0x01) // _PR0: Power Resources for D0 { CLK3 }) Name (CHAN, Package (0x02) { One, 0x0124F800 }) Method (_CRS, 0, NotSerialized) // _CRS: Current Resource Settings { Name (SBUF, ResourceTemplate () { I2cSerialBusV2 (0x002C, ControllerInitiated, 0x000186A0, AddressingMode7Bit, "\\_SB.PCI0.I2C1", 0x00, ResourceConsumer, , Exclusive, ) I2cSerialBusV2 (0x003B, ControllerInitiated, 0x000186A0, AddressingMode7Bit, "\\_SB.PCI0.I2C1", 0x00, ResourceConsumer, , Exclusive, ) GpioIo (Exclusive, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly, "\\_SB.GPO3", 0x00, ResourceConsumer, , ) { // Pin list 0x0019 } GpioIo (Exclusive, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly, "\\_SB.GPO3", 0x00, ResourceConsumer, , ) { // Pin list 0x0012 } GpioIo (Exclusive, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly, "\\_SB.GPO3", 0x00, ResourceConsumer, , ) { // Pin list 0x0030 } GpioInt (Edge, ActiveLow, Exclusive, PullNone, 0x0000, "\\_SB.GPO0", 0x00, ResourceConsumer, , ) { // Pin list 0x005B } GpioInt (Edge, ActiveLow, Exclusive, PullNone, 0x0000, "\\_SB.GPO0", 0x00, ResourceConsumer, , ) { // Pin list 0x004D } SpiSerialBusV2 (0x0001, PolarityLow, FourWireMode, 0x08, ControllerInitiated, 0x003D0900, ClockPolarityHigh, ClockPhaseSecond, "\\_SB.PCI0.SPI1", 0x00, ResourceConsumer, , Exclusive, ) }) Return (SBUF) /* \_SB_.PCI0.I2C1.RTEK._CRS.SBUF */ } } Signed-off-by: Yauhen Kharuzhy Link: https://patch.msgid.link/20260211002255.4090440-6-jekhor@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5677.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 845385c1fb88..f56ac267f53b 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -5536,6 +5536,15 @@ static int rt5677_init_irq(struct i2c_client *i2c) return ret; } +static const struct acpi_gpio_params rt5677_acpi_reset_gpios = {0, 0, true}; +static const struct acpi_gpio_params rt5677_acpi_ldo2_gpios = {1, 0, false}; + +static const struct acpi_gpio_mapping rt5677_acpi_gpios[] = { + { "realtek,reset-gpios", &rt5677_acpi_reset_gpios, 1 }, + { "realtek,pow-ldo2-gpios", &rt5677_acpi_ldo2_gpios, 1 }, + {}, +}; + static int rt5677_i2c_probe(struct i2c_client *i2c) { struct rt5677_priv *rt5677; @@ -5556,6 +5565,9 @@ static int rt5677_i2c_probe(struct i2c_client *i2c) if (rt5677->type == 0) return -EINVAL; + if (devm_acpi_dev_add_driver_gpios(rt5677->dev, rt5677_acpi_gpios)) + dev_warn(rt5677->dev, "Unable to add GPIO mapping table\n"); + rt5677_read_device_properties(rt5677, &i2c->dev); /* pow-ldo2 and reset are optional. The codec pins may be statically From 904e71d42505ca2af19b0031ffffefc8c1fefb48 Mon Sep 17 00:00:00 2001 From: Yauhen Kharuzhy Date: Wed, 11 Feb 2026 02:18:38 +0200 Subject: [PATCH 011/359] ASoC: rt5677: Reset the codec during probing To ensure that the codec is in a known state, reset it during driver initialization. Signed-off-by: Yauhen Kharuzhy Link: https://patch.msgid.link/20260211002255.4090440-7-jekhor@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5677.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index f56ac267f53b..60a93c3fe2e7 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -5581,14 +5581,21 @@ static int rt5677_i2c_probe(struct i2c_client *i2c) dev_err(&i2c->dev, "Failed to request POW_LDO2: %d\n", ret); return ret; } + rt5677->reset_pin = devm_gpiod_get_optional(&i2c->dev, - "realtek,reset", GPIOD_OUT_LOW); + "realtek,reset", GPIOD_OUT_HIGH); + if (IS_ERR(rt5677->reset_pin)) { ret = PTR_ERR(rt5677->reset_pin); dev_err(&i2c->dev, "Failed to request RESET: %d\n", ret); return ret; } + if (rt5677->reset_pin) { + msleep(1); + gpiod_set_value_cansleep(rt5677->reset_pin, 0); + } + if (rt5677->pow_ldo2 || rt5677->reset_pin) { /* Wait a while until I2C bus becomes available. The datasheet * does not specify the exact we should wait but startup From bf122191473e26a8f195308b1ba924c98424c8e1 Mon Sep 17 00:00:00 2001 From: Yauhen Kharuzhy Date: Wed, 11 Feb 2026 02:18:39 +0200 Subject: [PATCH 012/359] ASoC: rt5677-spi: Add SPI device ID matching table To allow defining a device in board-specific drivers, add 'rt5677' as a supported SPI device ID. Signed-off-by: Yauhen Kharuzhy Link: https://patch.msgid.link/20260211002255.4090440-8-jekhor@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5677-spi.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c index 885edcf0a3a5..548180ec5762 100644 --- a/sound/soc/codecs/rt5677-spi.c +++ b/sound/soc/codecs/rt5677-spi.c @@ -624,12 +624,19 @@ static const struct acpi_device_id rt5677_spi_acpi_id[] = { MODULE_DEVICE_TABLE(acpi, rt5677_spi_acpi_id); #endif +static const struct spi_device_id rt5677_spi_ids[] = { + { "rt5677", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(spi, rt5677_spi_ids); + static struct spi_driver rt5677_spi_driver = { .driver = { .name = DRV_NAME, .acpi_match_table = ACPI_PTR(rt5677_spi_acpi_id), }, .probe = rt5677_spi_probe, + .id_table = rt5677_spi_ids, }; module_spi_driver(rt5677_spi_driver); From f817c6a21235b503e68c50d0900d66cb1e1cf7bc Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Fri, 20 Feb 2026 10:56:40 +0800 Subject: [PATCH 013/359] ASoC: rt5575: Remove redundant assignment to .owner The coccicheck tool reports the following warning for this driver: ./rt5575.c:342:3-8: No need to set .owner here. The core will do it. The manual assignment of .owner = THIS_MODULE; in the rt5575_i2c_driver struct is redundant. The i2c_add_driver() function, which is called to register the driver, automatically sets the driver's owner to THIS_MODULE. The driver core handles this assignment internally, making the explicit initialization in the struct definition unnecessary. Remove the unnecessary line. Signed-off-by: Pei Xiao Link: https://patch.msgid.link/tencent_D7DDB8B646B471AF50B541DA7AC99FBCCD0A@qq.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5575.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/codecs/rt5575.c b/sound/soc/codecs/rt5575.c index c5525ad195ee..24e41af29689 100644 --- a/sound/soc/codecs/rt5575.c +++ b/sound/soc/codecs/rt5575.c @@ -339,7 +339,6 @@ MODULE_DEVICE_TABLE(of, rt5575_of_match); static struct i2c_driver rt5575_i2c_driver = { .driver = { .name = "rt5575", - .owner = THIS_MODULE, .of_match_table = rt5575_of_match, }, .probe = rt5575_i2c_probe, From 0fa3df83d5537c5cc3eacb9896c457f0c22791f8 Mon Sep 17 00:00:00 2001 From: Tim Bird Date: Tue, 17 Feb 2026 12:08:37 -0700 Subject: [PATCH 014/359] ASoC: Add SPDX ids to many soc files Add appropriate SPDX-License-Identifier lines to the the remaining files that are missing such lines in the sound/soc directory, and in a few other miscelaneous files. Remove boilerplate license text. Signed-off-by: Tim Bird Link: https://patch.msgid.link/20260217190837.165504-1-tim.bird@sony.com Signed-off-by: Mark Brown --- sound/hda/codecs/side-codecs/Kconfig | 1 + sound/soc/amd/acp-rt5645.c | 21 +-------------------- sound/soc/amd/include/acp_2_2_d.h | 18 +----------------- sound/soc/amd/include/acp_2_2_enum.h | 18 +----------------- sound/soc/amd/include/acp_2_2_sh_mask.h | 18 +----------------- sound/soc/apple/Kconfig | 1 + sound/soc/apple/Makefile | 1 + sound/soc/codecs/cs4270.c | 6 ++---- sound/soc/codecs/cs42xx8-i2c.c | 5 +---- sound/soc/codecs/cs42xx8.c | 5 +---- sound/soc/codecs/cs42xx8.h | 5 +---- sound/soc/codecs/sta529.c | 5 +---- sound/soc/dwc/dwc-i2s.c | 5 +---- sound/soc/dwc/dwc-pcm.c | 5 +---- sound/soc/dwc/local.h | 5 +---- sound/soc/fsl/efika-audio-fabric.c | 5 +---- sound/soc/intel/catpt/Makefile | 1 + sound/soc/intel/keembay/Makefile | 1 + sound/soc/spear/spdif_in.c | 5 +---- sound/soc/spear/spdif_out.c | 5 +---- sound/soc/spear/spear_pcm.c | 5 +---- sound/soc/starfive/Makefile | 1 + sound/usb/qcom/Makefile | 1 + sound/x86/intel_hdmi_audio.h | 22 +--------------------- 24 files changed, 25 insertions(+), 140 deletions(-) diff --git a/sound/hda/codecs/side-codecs/Kconfig b/sound/hda/codecs/side-codecs/Kconfig index f674e9a9c7d7..fc5651e555e3 100644 --- a/sound/hda/codecs/side-codecs/Kconfig +++ b/sound/hda/codecs/side-codecs/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only config SND_HDA_CIRRUS_SCODEC tristate diff --git a/sound/soc/amd/acp-rt5645.c b/sound/soc/amd/acp-rt5645.c index 11d373169380..d545516760b9 100644 --- a/sound/soc/amd/acp-rt5645.c +++ b/sound/soc/amd/acp-rt5645.c @@ -1,29 +1,10 @@ +// SPDX-License-Identifier: MIT /* * Machine driver for AMD ACP Audio engine using Realtek RT5645 codec * * Copyright 2017 Advanced Micro Devices, Inc. * * This file is modified from rt288 machine driver - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR - * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - * - * */ #include diff --git a/sound/soc/amd/include/acp_2_2_d.h b/sound/soc/amd/include/acp_2_2_d.h index 0118fe9e6a87..d07ec8646ee8 100644 --- a/sound/soc/amd/include/acp_2_2_d.h +++ b/sound/soc/amd/include/acp_2_2_d.h @@ -1,24 +1,8 @@ +/* SPDX-License-Identifier: MIT */ /* * ACP_2_2 Register documentation * * Copyright (C) 2014 Advanced Micro Devices, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef ACP_2_2_D_H diff --git a/sound/soc/amd/include/acp_2_2_enum.h b/sound/soc/amd/include/acp_2_2_enum.h index f3577c851086..26c7993ac395 100644 --- a/sound/soc/amd/include/acp_2_2_enum.h +++ b/sound/soc/amd/include/acp_2_2_enum.h @@ -1,24 +1,8 @@ +/* SPDX-License-Identifier: MIT */ /* * ACP_2_2 Register documentation * * Copyright (C) 2014 Advanced Micro Devices, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef ACP_2_2_ENUM_H diff --git a/sound/soc/amd/include/acp_2_2_sh_mask.h b/sound/soc/amd/include/acp_2_2_sh_mask.h index 32d2d4104309..9749c11e733f 100644 --- a/sound/soc/amd/include/acp_2_2_sh_mask.h +++ b/sound/soc/amd/include/acp_2_2_sh_mask.h @@ -1,24 +1,8 @@ +/* SPDX-License-Identifier: MIT */ /* * ACP_2_2 Register documentation * * Copyright (C) 2014 Advanced Micro Devices, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef ACP_2_2_SH_MASK_H diff --git a/sound/soc/apple/Kconfig b/sound/soc/apple/Kconfig index d8dc2f1ccc83..f1d071a99fbf 100644 --- a/sound/soc/apple/Kconfig +++ b/sound/soc/apple/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only menu "Apple" config SND_SOC_APPLE_MCA diff --git a/sound/soc/apple/Makefile b/sound/soc/apple/Makefile index 1eb8fbef60c6..252ba5fc18cb 100644 --- a/sound/soc/apple/Makefile +++ b/sound/soc/apple/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only snd-soc-apple-mca-y := mca.o obj-$(CONFIG_SND_SOC_APPLE_MCA) += snd-soc-apple-mca.o diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index 3139f03cd42b..a48980e746ff 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -1,12 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* * CS4270 ALSA SoC (ASoC) codec driver * * Author: Timur Tabi * - * Copyright 2007-2009 Freescale Semiconductor, Inc. This file is licensed - * under the terms of the GNU General Public License version 2. This - * program is licensed "as is" without any warranty of any kind, whether - * express or implied. + * Copyright 2007-2009 Freescale Semiconductor, Inc. * * This is an ASoC device driver for the Cirrus Logic CS4270 codec. * diff --git a/sound/soc/codecs/cs42xx8-i2c.c b/sound/soc/codecs/cs42xx8-i2c.c index 039b3ecb3b9b..0faca384073a 100644 --- a/sound/soc/codecs/cs42xx8-i2c.c +++ b/sound/soc/codecs/cs42xx8-i2c.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Cirrus Logic CS42448/CS42888 Audio CODEC DAI I2C driver * * Copyright (C) 2014 Freescale Semiconductor, Inc. * * Author: Nicolin Chen - * - * This file is licensed under the terms of the GNU General Public License - * version 2. This program is licensed "as is" without any warranty of any - * kind, whether express or implied. */ #include diff --git a/sound/soc/codecs/cs42xx8.c b/sound/soc/codecs/cs42xx8.c index d14538c49b97..f6b8fe7d53e2 100644 --- a/sound/soc/codecs/cs42xx8.c +++ b/sound/soc/codecs/cs42xx8.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Cirrus Logic CS42448/CS42888 Audio CODEC Digital Audio Interface (DAI) driver * * Copyright (C) 2014 Freescale Semiconductor, Inc. * * Author: Nicolin Chen - * - * This file is licensed under the terms of the GNU General Public License - * version 2. This program is licensed "as is" without any warranty of any - * kind, whether express or implied. */ #include diff --git a/sound/soc/codecs/cs42xx8.h b/sound/soc/codecs/cs42xx8.h index 342389e8b1a8..4087c91471d1 100644 --- a/sound/soc/codecs/cs42xx8.h +++ b/sound/soc/codecs/cs42xx8.h @@ -1,13 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * cs42xx8.h - Cirrus Logic CS42448/CS42888 Audio CODEC driver header file * * Copyright (C) 2014 Freescale Semiconductor, Inc. * * Author: Nicolin Chen - * - * This file is licensed under the terms of the GNU General Public License - * version 2. This program is licensed "as is" without any warranty of any - * kind, whether express or implied. */ #ifndef _CS42XX8_H diff --git a/sound/soc/codecs/sta529.c b/sound/soc/codecs/sta529.c index 8c3b2652b02e..946aa6a4e57c 100644 --- a/sound/soc/codecs/sta529.c +++ b/sound/soc/codecs/sta529.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * ASoC codec driver for spear platform * @@ -5,10 +6,6 @@ * * Copyright (C) 2012 ST Microelectronics * Rajeev Kumar - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. */ #include diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c index 28001e9857d9..74dfd39fd604 100644 --- a/sound/soc/dwc/dwc-i2s.c +++ b/sound/soc/dwc/dwc-i2s.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * ALSA SoC Synopsys I2S Audio Layer * @@ -5,10 +6,6 @@ * * Copyright (C) 2010 ST Microelectronics * Rajeev Kumar - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. */ #include diff --git a/sound/soc/dwc/dwc-pcm.c b/sound/soc/dwc/dwc-pcm.c index a418265c030a..15ff39b1a509 100644 --- a/sound/soc/dwc/dwc-pcm.c +++ b/sound/soc/dwc/dwc-pcm.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * ALSA SoC Synopsys PIO PCM for I2S driver * @@ -5,10 +6,6 @@ * * Copyright (C) 2016 Synopsys * Jose Abreu - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. */ #include diff --git a/sound/soc/dwc/local.h b/sound/soc/dwc/local.h index dce88c9ad5f3..6510b3496371 100644 --- a/sound/soc/dwc/local.h +++ b/sound/soc/dwc/local.h @@ -1,9 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (ST) 2012 Rajeev Kumar (rajeevkumar.linux@gmail.com) - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. */ #ifndef __DESIGNWARE_LOCAL_H diff --git a/sound/soc/fsl/efika-audio-fabric.c b/sound/soc/fsl/efika-audio-fabric.c index de17b103a4cf..4d3266d9cbb7 100644 --- a/sound/soc/fsl/efika-audio-fabric.c +++ b/sound/soc/fsl/efika-audio-fabric.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Efika driver for the PSC of the Freescale MPC52xx * configured as AC97 interface * * Copyright 2008 Jon Smirl, Digispeaker * Author: Jon Smirl - * - * This file is licensed under the terms of the GNU General Public License - * version 2. This program is licensed "as is" without any warranty of any - * kind, whether express or implied. */ #include diff --git a/sound/soc/intel/catpt/Makefile b/sound/soc/intel/catpt/Makefile index f5f6a7e956ce..e8316e33b820 100644 --- a/sound/soc/intel/catpt/Makefile +++ b/sound/soc/intel/catpt/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 snd-soc-catpt-y := device.o dsp.o loader.o ipc.o messages.o pcm.o sysfs.o # tell define_trace.h where to find the trace header diff --git a/sound/soc/intel/keembay/Makefile b/sound/soc/intel/keembay/Makefile index 3da9a6f9ba2a..fe3d761743d9 100644 --- a/sound/soc/intel/keembay/Makefile +++ b/sound/soc/intel/keembay/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 snd-soc-kmb_platform-y := \ kmb_platform.o diff --git a/sound/soc/spear/spdif_in.c b/sound/soc/spear/spdif_in.c index 4ad8b1fc713a..b31b120a85de 100644 --- a/sound/soc/spear/spdif_in.c +++ b/sound/soc/spear/spdif_in.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * ALSA SoC SPDIF In Audio Layer for spear processors * * Copyright (C) 2012 ST Microelectronics * Vipin Kumar - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. */ #include diff --git a/sound/soc/spear/spdif_out.c b/sound/soc/spear/spdif_out.c index 469373d1bb41..c06f09c646a8 100644 --- a/sound/soc/spear/spdif_out.c +++ b/sound/soc/spear/spdif_out.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * ALSA SoC SPDIF Out Audio Layer for spear processors * * Copyright (C) 2012 ST Microelectronics * Vipin Kumar - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. */ #include diff --git a/sound/soc/spear/spear_pcm.c b/sound/soc/spear/spear_pcm.c index e8476da157cd..f222956e857c 100644 --- a/sound/soc/spear/spear_pcm.c +++ b/sound/soc/spear/spear_pcm.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * ALSA PCM interface for ST SPEAr Processors * @@ -5,10 +6,6 @@ * * Copyright (C) 2012 ST Microelectronics * Rajeev Kumar - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. */ #include diff --git a/sound/soc/starfive/Makefile b/sound/soc/starfive/Makefile index 9e958f70ef51..cfe03c83d5f7 100644 --- a/sound/soc/starfive/Makefile +++ b/sound/soc/starfive/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 # StarFive Platform Support obj-$(CONFIG_SND_SOC_JH7110_PWMDAC) += jh7110_pwmdac.o obj-$(CONFIG_SND_SOC_JH7110_TDM) += jh7110_tdm.o diff --git a/sound/usb/qcom/Makefile b/sound/usb/qcom/Makefile index 6567727b66f0..aa8fcf8d2458 100644 --- a/sound/usb/qcom/Makefile +++ b/sound/usb/qcom/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 snd-usb-audio-qmi-y := usb_audio_qmi_v01.o qc_audio_offload.o snd-usb-audio-qmi-y += mixer_usb_offload.o obj-$(CONFIG_SND_USB_AUDIO_QMI) += snd-usb-audio-qmi.o diff --git a/sound/x86/intel_hdmi_audio.h b/sound/x86/intel_hdmi_audio.h index 7ce8c2a7d714..88bf9ef2c491 100644 --- a/sound/x86/intel_hdmi_audio.h +++ b/sound/x86/intel_hdmi_audio.h @@ -1,30 +1,10 @@ +/* SPDX-License-Identifier: MIT */ /* * Copyright (C) 2016 Intel Corporation * Authors: Sailaja Bandarupalli * Ramesh Babu K V * Vaibhav Agarwal * Jerome Anand - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files - * (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, - * and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. */ #ifndef _INTEL_HDMI_AUDIO_H_ From 07c774dd64ba0c605dbf844132122e3edbdbea93 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 19 Feb 2026 04:53:52 +0000 Subject: [PATCH 015/359] ASoC: soc-compress: use function to clear symmetric params Current soc-compress.c clears symmetric_rate, but it clears rate only, not clear other symmetric_channels/sample_bits. static int soc_compr_clean(...) { ... if (!snd_soc_dai_active(cpu_dai)) => cpu_dai->symmetric_rate = 0; if (!snd_soc_dai_active(codec_dai)) => codec_dai->symmetric_rate = 0; ... }; This feature was added when v3.7 kernel [1], and there was only symmetric_rate, no symmetric_channels/sample_bits in that timing. symmetric_channels/sample_bits were added in v3.14 [2], but I guess it didn't notice that soc-compress.c is updating symmetric_xxx. We are clearing symmetry_xxx by soc_pcm_set_dai_params(), but is soc-pcm.c local function. Makes it global function and clear symmetry_xxx by it. [1] commit 1245b7005de02 ("ASoC: add compress stream support") [2] commit 3635bf09a89cf ("ASoC: soc-pcm: add symmetry for channels and sample bits") Fixes: 3635bf09a89c ("ASoC: soc-pcm: add symmetry for channels and sample bits") Cc: Nicolin Chen Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ms15e3kv.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc.h | 3 +++ sound/soc/soc-compress.c | 4 ++-- sound/soc/soc-pcm.c | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index 7d8376c8e1be..1e0b7cd8d956 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1412,6 +1412,9 @@ 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 7b81dffc6a93..b8402802ae78 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -69,10 +69,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)) - cpu_dai->symmetric_rate = 0; + soc_pcm_set_dai_params(cpu_dai, NULL); if (!snd_soc_dai_active(codec_dai)) - codec_dai->symmetric_rate = 0; + soc_pcm_set_dai_params(codec_dai, NULL); snd_soc_link_compr_shutdown(cstream, rollback); diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index afa9fad4457f..9b12eedb77c3 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -423,8 +423,8 @@ void dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir, int event) snd_soc_dapm_stream_event(fe, dir, event); } -static void soc_pcm_set_dai_params(struct snd_soc_dai *dai, - struct snd_pcm_hw_params *params) +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); From 5879521cb558871472b97c4744dbe634a4286f0e Mon Sep 17 00:00:00 2001 From: Sen Wang Date: Wed, 11 Feb 2026 16:10:01 -0600 Subject: [PATCH 016/359] ASoC: ti: davinci-mcasp: Add system suspend/resume support The McASP driver supports runtime PM callbacks for register save/restore during device idle, but doesn't provide system suspend/resume callbacks. This causes audio to fail to resume after system suspend. Since the driver already handles runtime suspend & resume, we can reuse existing runtime PM logics. Signed-off-by: Sen Wang Link: https://patch.msgid.link/20260211221001.155843-1-sen@ti.com Signed-off-by: Mark Brown --- sound/soc/ti/davinci-mcasp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/ti/davinci-mcasp.c b/sound/soc/ti/davinci-mcasp.c index 2d260fbc9b83..14267be4a288 100644 --- a/sound/soc/ti/davinci-mcasp.c +++ b/sound/soc/ti/davinci-mcasp.c @@ -2823,6 +2823,8 @@ static int davinci_mcasp_runtime_resume(struct device *dev) #endif static const struct dev_pm_ops davinci_mcasp_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) SET_RUNTIME_PM_OPS(davinci_mcasp_runtime_suspend, davinci_mcasp_runtime_resume, NULL) From d075cef4af6327a5de4bee7bf77591e3201e54f4 Mon Sep 17 00:00:00 2001 From: Stefano Radaelli Date: Fri, 13 Feb 2026 16:03:55 +0100 Subject: [PATCH 017/359] ASoC: simple-card-utils: add sysclk ordering support When simple-audio-card programs sysclk for CPU and codec DAIs during hw_params, the ordering of these calls may matter on some platforms. Some CPU DAIs finalize or adjust the MCLK rate as part of their set_sysclk() callback (for example by calling clk_set_rate()). If the codec sysclk is configured before the CPU DAI applies the final MCLK rate, the codec may configure its internal clocking based on a non-final MCLK value. Such situations can arise depending on the clock provider/consumer relationship between the CPU DAI and the codec. Introduce an explicit sysclk ordering enum in simple-card-utils and use it to control the order of snd_soc_dai_set_sysclk() calls in the mclk-fs handling path. The default behaviour remains unchanged (codec-first) to avoid regressions. Signed-off-by: Stefano Radaelli Acked-by: Kuninori Morimoto Link: https://patch.msgid.link/20260213150355.442609-1-stefano.r@variscite.com Signed-off-by: Mark Brown --- include/sound/simple_card_utils.h | 6 ++++ sound/soc/generic/simple-card-utils.c | 41 ++++++++++++++++++++------- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h index 69a9c9c4d0e9..915e6ae5f68d 100644 --- a/include/sound/simple_card_utils.h +++ b/include/sound/simple_card_utils.h @@ -54,6 +54,11 @@ struct prop_nums { int platforms; }; +enum simple_util_sysclk_order { + SIMPLE_SYSCLK_ORDER_CODEC_FIRST = 0, + SIMPLE_SYSCLK_ORDER_CPU_FIRST, +}; + struct simple_util_priv { struct snd_soc_card snd_card; struct simple_dai_props { @@ -63,6 +68,7 @@ struct simple_util_priv { struct snd_soc_codec_conf *codec_conf; struct prop_nums num; unsigned int mclk_fs; + enum simple_util_sysclk_order sysclk_order; } *dai_props; struct simple_util_jack hp_jack; struct simple_util_jack mic_jack; diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index bdc02e85b089..fdd8b76f2914 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -468,6 +468,7 @@ int simple_util_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *sdai; struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card); struct simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd); + enum simple_util_sysclk_order order = props->sysclk_order; unsigned int mclk, mclk_fs = 0; int i, ret; @@ -501,18 +502,36 @@ int simple_util_hw_params(struct snd_pcm_substream *substream, goto end; } - for_each_rtd_codec_dais(rtd, i, sdai) { - pdai = simple_props_to_dai_codec(props, i); - ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction); - if (ret && ret != -ENOTSUPP) - goto end; - } + if (order == SIMPLE_SYSCLK_ORDER_CPU_FIRST) { + /* CPU first */ + for_each_rtd_cpu_dais(rtd, i, sdai) { + pdai = simple_props_to_dai_cpu(props, i); + ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction); + if (ret && ret != -ENOTSUPP) + goto end; + } - for_each_rtd_cpu_dais(rtd, i, sdai) { - pdai = simple_props_to_dai_cpu(props, i); - ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction); - if (ret && ret != -ENOTSUPP) - goto end; + for_each_rtd_codec_dais(rtd, i, sdai) { + pdai = simple_props_to_dai_codec(props, i); + ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction); + if (ret && ret != -ENOTSUPP) + goto end; + } + } else { + /* default: codec first */ + for_each_rtd_codec_dais(rtd, i, sdai) { + pdai = simple_props_to_dai_codec(props, i); + ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction); + if (ret && ret != -ENOTSUPP) + goto end; + } + + for_each_rtd_cpu_dais(rtd, i, sdai) { + pdai = simple_props_to_dai_cpu(props, i); + ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction); + if (ret && ret != -ENOTSUPP) + goto end; + } } } From 5ebc20921b7fff9feb44de465448e17a382c9965 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 20 Feb 2026 21:22:19 +0100 Subject: [PATCH 018/359] ASoC: tas2552: Allow audio enable GPIO to sleep The audio enable GPIO is not toggled in any critical section where it could not sleep, allow the audio enable GPIO to sleep. This allows the driver to operate the audio enable GPIO connected to I2C GPIO expander. Signed-off-by: Marek Vasut Link: https://patch.msgid.link/20260220202332.241035-1-marex@nabladev.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2552.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/tas2552.c b/sound/soc/codecs/tas2552.c index 43449d7c2584..80206c2e0946 100644 --- a/sound/soc/codecs/tas2552.c +++ b/sound/soc/codecs/tas2552.c @@ -487,7 +487,7 @@ static int tas2552_runtime_suspend(struct device *dev) regcache_cache_only(tas2552->regmap, true); regcache_mark_dirty(tas2552->regmap); - gpiod_set_value(tas2552->enable_gpio, 0); + gpiod_set_value_cansleep(tas2552->enable_gpio, 0); return 0; } @@ -496,7 +496,7 @@ static int tas2552_runtime_resume(struct device *dev) { struct tas2552_data *tas2552 = dev_get_drvdata(dev); - gpiod_set_value(tas2552->enable_gpio, 1); + gpiod_set_value_cansleep(tas2552->enable_gpio, 1); tas2552_sw_shutdown(tas2552, 0); @@ -583,7 +583,7 @@ static int tas2552_component_probe(struct snd_soc_component *component) return ret; } - gpiod_set_value(tas2552->enable_gpio, 1); + gpiod_set_value_cansleep(tas2552->enable_gpio, 1); ret = pm_runtime_resume_and_get(component->dev); if (ret < 0) { @@ -608,7 +608,7 @@ static int tas2552_component_probe(struct snd_soc_component *component) probe_fail: pm_runtime_put_noidle(component->dev); - gpiod_set_value(tas2552->enable_gpio, 0); + gpiod_set_value_cansleep(tas2552->enable_gpio, 0); regulator_bulk_disable(ARRAY_SIZE(tas2552->supplies), tas2552->supplies); @@ -621,7 +621,7 @@ static void tas2552_component_remove(struct snd_soc_component *component) pm_runtime_put(component->dev); - gpiod_set_value(tas2552->enable_gpio, 0); + gpiod_set_value_cansleep(tas2552->enable_gpio, 0); }; #ifdef CONFIG_PM From 80930d81c4b0753ba2ca750708e4d2fcc0627dc8 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 23 Feb 2026 15:02:55 +0000 Subject: [PATCH 019/359] ASoC: soc_sdw_utils: Add device info for CS47L47 Add a device info entry for the Cirrus Logic CS47L47. CS47L47 has UAJ (headset speaker + mic + jack detect) and DMICs. The audio ports are similar to the CS42L45 so can be based on the CS42L45 code. Signed-off-by: Richard Fitzgerald Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20260223150256.326143-3-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/soc_sdw_utils.h | 2 + sound/soc/sdw_utils/Makefile | 1 + sound/soc/sdw_utils/soc_sdw_cs47l47.c | 80 +++++++++++++++++++++++++++ sound/soc/sdw_utils/soc_sdw_utils.c | 36 ++++++++++++ 4 files changed, 119 insertions(+) create mode 100644 sound/soc/sdw_utils/soc_sdw_cs47l47.c diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h index 227347c8f0b3..98531e500cbb 100644 --- a/include/sound/soc_sdw_utils.h +++ b/include/sound/soc_sdw_utils.h @@ -259,6 +259,8 @@ int asoc_sdw_cs42l43_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_so int asoc_sdw_cs42l43_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai); int asoc_sdw_cs42l45_hs_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai); int asoc_sdw_cs42l45_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai); +int asoc_sdw_cs47l47_hs_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai); +int asoc_sdw_cs47l47_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai); int asoc_sdw_cs_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai); int asoc_sdw_maxim_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai); /* TI */ diff --git a/sound/soc/sdw_utils/Makefile b/sound/soc/sdw_utils/Makefile index e8bd5ffb1a6a..a8d091fd374b 100644 --- a/sound/soc/sdw_utils/Makefile +++ b/sound/soc/sdw_utils/Makefile @@ -6,6 +6,7 @@ snd-soc-sdw-utils-y := soc_sdw_utils.o soc_sdw_dmic.o soc_sdw_rt_dmic.o \ soc_sdw_bridge_cs35l56.o \ soc_sdw_cs42l42.o soc_sdw_cs42l43.o \ soc_sdw_cs42l45.o \ + soc_sdw_cs47l47.o \ soc_sdw_cs_amp.o \ soc_sdw_maxim.o \ soc_sdw_ti_amp.o diff --git a/sound/soc/sdw_utils/soc_sdw_cs47l47.c b/sound/soc/sdw_utils/soc_sdw_cs47l47.c new file mode 100644 index 000000000000..259ecf1e0a71 --- /dev/null +++ b/sound/soc/sdw_utils/soc_sdw_cs47l47.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Based on sof_sdw_cs42l45.c +// This file incorporates work covered by the following copyright notice: +// Copyright (c) 2023 Intel Corporation +// Copyright (c) 2024 Advanced Micro Devices, Inc. + +/* + * soc_sdw_cs47l47 - Helpers to handle CS47L47 from generic machine driver + */ +#include +#include +#include +#include +#include +#include +#include +#include + +static struct snd_soc_jack_pin soc_jack_pins[] = { + { + .pin = "cs47l47 OT 43 Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "cs47l47 OT 45 Headset", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "cs47l47 IT 31 Microphone", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "cs47l47 IT 33 Headset", + .mask = SND_JACK_MICROPHONE, + }, +}; + +int asoc_sdw_cs47l47_hs_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_jack *jack = &ctx->sdw_headset; + int ret; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, "%s hs:cs47l47", + card->components); + if (!card->components) + return -ENOMEM; + + ret = snd_soc_card_jack_new_pins(card, "Jack", SND_JACK_MECHANICAL | + SND_JACK_HEADSET | SND_JACK_LINEOUT, jack, + soc_jack_pins, ARRAY_SIZE(soc_jack_pins)); + if (ret) { + dev_err(card->dev, "Failed to create jack: %d\n", ret); + return ret; + } + + ret = snd_soc_component_set_jack(component, jack, NULL); + if (ret) { + dev_err(card->dev, "Failed to register jack: %d\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS(asoc_sdw_cs47l47_hs_rtd_init, "SND_SOC_SDW_UTILS"); + +int asoc_sdw_cs47l47_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = rtd->card; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, "%s mic:cs47l47-dmic", + card->components); + if (!card->components) + return -ENOMEM; + + return 0; +} +EXPORT_SYMBOL_NS(asoc_sdw_cs47l47_dmic_rtd_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 0e67d9f34cba..eeeb91dded9d 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -759,6 +759,42 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, .aux_num = 1, }, + { + .part_id = 0x4747, + .name_prefix = "cs47l47", + .dais = { + { + .direction = {true, false}, + .codec_name = "snd_soc_sdca.UAJ.1", + .dai_name = "IT 41", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .rtd_init = asoc_sdw_cs47l47_hs_rtd_init, + }, + { + .direction = {false, true}, + .codec_name = "snd_soc_sdca.SmartMic.0", + .dai_name = "OT 113", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_cs47l47_dmic_rtd_init, + }, + { + .direction = {false, true}, + .codec_name = "snd_soc_sdca.UAJ.1", + .dai_name = "OT 36", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + }, + }, + .dai_num = 3, + .auxs = { + { + .codec_name = "snd_soc_sdca.HID.2", + }, + }, + .aux_num = 1, + }, { .part_id = 0xaaaa, /* generic codec mockup */ .name_prefix = "sdw_mockup_mmulti-function", From ada32396f90951e12465224c04742607ca56a982 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 23 Feb 2026 15:02:56 +0000 Subject: [PATCH 020/359] ASoC: SDCA: Add CS47L47 to class driver Add the SoundWire ID for CS47L47 to the class driver. Signed-off-by: Richard Fitzgerald Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20260223150256.326143-4-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_class.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c index 918b638acb57..55c33ff63ca2 100644 --- a/sound/soc/sdca/sdca_class.c +++ b/sound/soc/sdca/sdca_class.c @@ -317,6 +317,7 @@ static const struct dev_pm_ops class_pm_ops = { static const struct sdw_device_id class_sdw_id[] = { SDW_SLAVE_ENTRY(0x01FA, 0x4245, 0), + SDW_SLAVE_ENTRY(0x01FA, 0x4747, 0), {} }; MODULE_DEVICE_TABLE(sdw, class_sdw_id); From 260c3fff1fefc570d8f23e87953e181d7d248861 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 25 Feb 2026 11:23:39 +0000 Subject: [PATCH 021/359] ASoC: cs-amp-lib-test: Stop including platform_device.h commit d1965f008f22 ("ASoC: cs-amp-lib-test: Use faux bus instead of creating a dummy platform device") replaced all use of platform device with faux_bus but forgot to remove the header include. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260225112339.1179585-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs-amp-lib-test.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/codecs/cs-amp-lib-test.c b/sound/soc/codecs/cs-amp-lib-test.c index 2b1529343f11..636d0f3198b8 100644 --- a/sound/soc/codecs/cs-amp-lib-test.c +++ b/sound/soc/codecs/cs-amp-lib-test.c @@ -17,7 +17,6 @@ #include #include #include -#include #include #include From 5c74a008ffc62fc57a041602b4517519c8bf9436 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 25 Feb 2026 12:56:44 +0000 Subject: [PATCH 022/359] firmware: cs_dsp: Mark KUnit test suites KUNIT_SPEED_SLOW Most of the test suites perform a large number of test cases, so mark the suites as KUNIT_SPEED_SLOW for anyone who finds that useful. The callback test suites haven't been marked because they don't perform many test cases, and the tests are quick. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260225125644.1248826-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- drivers/firmware/cirrus/test/cs_dsp_test_bin.c | 4 ++++ drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c | 3 +++ drivers/firmware/cirrus/test/cs_dsp_test_control_cache.c | 5 +++++ drivers/firmware/cirrus/test/cs_dsp_test_control_parse.c | 5 +++++ drivers/firmware/cirrus/test/cs_dsp_test_control_rw.c | 5 +++++ drivers/firmware/cirrus/test/cs_dsp_test_wmfw.c | 7 +++++++ drivers/firmware/cirrus/test/cs_dsp_test_wmfw_error.c | 7 +++++++ 7 files changed, 36 insertions(+) diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_bin.c b/drivers/firmware/cirrus/test/cs_dsp_test_bin.c index 66140caeebb5..61078da57462 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_bin.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_bin.c @@ -2835,12 +2835,14 @@ static struct kunit_suite cs_dsp_bin_test_halo = { .init = cs_dsp_bin_test_halo_init, .exit = cs_dsp_bin_test_exit, .test_cases = cs_dsp_bin_test_cases_halo, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_bin_test_halo_wmdr3 = { .name = "cs_dsp_bin_halo_wmdr_v3", .init = cs_dsp_bin_test_halo_wmdr3_init, .test_cases = cs_dsp_bin_test_cases_halo_wmdr3, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_bin_test_adsp2_32bit = { @@ -2848,6 +2850,7 @@ static struct kunit_suite cs_dsp_bin_test_adsp2_32bit = { .init = cs_dsp_bin_test_adsp2_32bit_init, .exit = cs_dsp_bin_test_exit, .test_cases = cs_dsp_bin_test_cases_adsp2, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_bin_test_adsp2_16bit = { @@ -2855,6 +2858,7 @@ static struct kunit_suite cs_dsp_bin_test_adsp2_16bit = { .init = cs_dsp_bin_test_adsp2_16bit_init, .exit = cs_dsp_bin_test_exit, .test_cases = cs_dsp_bin_test_cases_adsp2, + .attr.speed = KUNIT_SPEED_SLOW, }; kunit_test_suites(&cs_dsp_bin_test_halo, diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c b/drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c index 9b2763b36970..888e277a265c 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c @@ -586,6 +586,7 @@ static struct kunit_suite cs_dsp_bin_err_test_halo = { .init = cs_dsp_bin_err_test_halo_init, .exit = cs_dsp_bin_err_test_exit, .test_cases = cs_dsp_bin_err_test_cases, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_bin_err_test_adsp2_32bit = { @@ -593,6 +594,7 @@ static struct kunit_suite cs_dsp_bin_err_test_adsp2_32bit = { .init = cs_dsp_bin_err_test_adsp2_32bit_init, .exit = cs_dsp_bin_err_test_exit, .test_cases = cs_dsp_bin_err_test_cases, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_bin_err_test_adsp2_16bit = { @@ -600,6 +602,7 @@ static struct kunit_suite cs_dsp_bin_err_test_adsp2_16bit = { .init = cs_dsp_bin_err_test_adsp2_16bit_init, .exit = cs_dsp_bin_err_test_exit, .test_cases = cs_dsp_bin_err_test_cases, + .attr.speed = KUNIT_SPEED_SLOW, }; kunit_test_suites(&cs_dsp_bin_err_test_halo, diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_control_cache.c b/drivers/firmware/cirrus/test/cs_dsp_test_control_cache.c index ebca3a4ab0f1..b03cd7c37f6e 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_control_cache.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_control_cache.c @@ -3248,30 +3248,35 @@ static struct kunit_suite cs_dsp_ctl_cache_test_halo = { .name = "cs_dsp_ctl_cache_wmfwV3_halo", .init = cs_dsp_ctl_cache_test_halo_init, .test_cases = cs_dsp_ctl_cache_test_cases_v3, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_ctl_cache_test_adsp2_32bit_wmfw1 = { .name = "cs_dsp_ctl_cache_wmfwV1_adsp2_32bit", .init = cs_dsp_ctl_cache_test_adsp2_32bit_wmfw1_init, .test_cases = cs_dsp_ctl_cache_test_cases_v1, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_ctl_cache_test_adsp2_32bit_wmfw2 = { .name = "cs_dsp_ctl_cache_wmfwV2_adsp2_32bit", .init = cs_dsp_ctl_cache_test_adsp2_32bit_wmfw2_init, .test_cases = cs_dsp_ctl_cache_test_cases_v2, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_ctl_cache_test_adsp2_16bit_wmfw1 = { .name = "cs_dsp_ctl_cache_wmfwV1_adsp2_16bit", .init = cs_dsp_ctl_cache_test_adsp2_16bit_wmfw1_init, .test_cases = cs_dsp_ctl_cache_test_cases_v1, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_ctl_cache_test_adsp2_16bit_wmfw2 = { .name = "cs_dsp_ctl_cache_wmfwV2_adsp2_16bit", .init = cs_dsp_ctl_cache_test_adsp2_16bit_wmfw2_init, .test_cases = cs_dsp_ctl_cache_test_cases_v2, + .attr.speed = KUNIT_SPEED_SLOW, }; kunit_test_suites(&cs_dsp_ctl_cache_test_halo, diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_control_parse.c b/drivers/firmware/cirrus/test/cs_dsp_test_control_parse.c index 942ba1af5e7c..368703767644 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_control_parse.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_control_parse.c @@ -1805,30 +1805,35 @@ static struct kunit_suite cs_dsp_ctl_parse_test_halo = { .name = "cs_dsp_ctl_parse_wmfwV3_halo", .init = cs_dsp_ctl_parse_test_halo_init, .test_cases = cs_dsp_ctl_parse_test_cases_v2_v3, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_ctl_parse_test_adsp2_32bit_wmfw1 = { .name = "cs_dsp_ctl_parse_wmfwV1_adsp2_32bit", .init = cs_dsp_ctl_parse_test_adsp2_32bit_wmfw1_init, .test_cases = cs_dsp_ctl_parse_test_cases_v1, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_ctl_parse_test_adsp2_32bit_wmfw2 = { .name = "cs_dsp_ctl_parse_wmfwV2_adsp2_32bit", .init = cs_dsp_ctl_parse_test_adsp2_32bit_wmfw2_init, .test_cases = cs_dsp_ctl_parse_test_cases_v2_v3, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_ctl_parse_test_adsp2_16bit_wmfw1 = { .name = "cs_dsp_ctl_parse_wmfwV1_adsp2_16bit", .init = cs_dsp_ctl_parse_test_adsp2_16bit_wmfw1_init, .test_cases = cs_dsp_ctl_parse_test_cases_v1, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_ctl_parse_test_adsp2_16bit_wmfw2 = { .name = "cs_dsp_ctl_parse_wmfwV2_adsp2_16bit", .init = cs_dsp_ctl_parse_test_adsp2_16bit_wmfw2_init, .test_cases = cs_dsp_ctl_parse_test_cases_v2_v3, + .attr.speed = KUNIT_SPEED_SLOW, }; kunit_test_suites(&cs_dsp_ctl_parse_test_halo, diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_control_rw.c b/drivers/firmware/cirrus/test/cs_dsp_test_control_rw.c index bda00a95d4f9..d800f163b099 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_control_rw.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_control_rw.c @@ -2636,30 +2636,35 @@ static struct kunit_suite cs_dsp_ctl_rw_test_halo = { .name = "cs_dsp_ctl_rw_wmfwV3_halo", .init = cs_dsp_ctl_rw_test_halo_init, .test_cases = cs_dsp_ctl_rw_test_cases_halo, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_ctl_rw_test_adsp2_32bit_wmfw1 = { .name = "cs_dsp_ctl_rw_wmfwV1_adsp2_32bit", .init = cs_dsp_ctl_rw_test_adsp2_32bit_wmfw1_init, .test_cases = cs_dsp_ctl_rw_test_cases_adsp, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_ctl_rw_test_adsp2_32bit_wmfw2 = { .name = "cs_dsp_ctl_rw_wmfwV2_adsp2_32bit", .init = cs_dsp_ctl_rw_test_adsp2_32bit_wmfw2_init, .test_cases = cs_dsp_ctl_rw_test_cases_adsp, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_ctl_rw_test_adsp2_16bit_wmfw1 = { .name = "cs_dsp_ctl_rw_wmfwV1_adsp2_16bit", .init = cs_dsp_ctl_rw_test_adsp2_16bit_wmfw1_init, .test_cases = cs_dsp_ctl_rw_test_cases_adsp, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_ctl_rw_test_adsp2_16bit_wmfw2 = { .name = "cs_dsp_ctl_rw_wmfwV2_adsp2_16bit", .init = cs_dsp_ctl_rw_test_adsp2_16bit_wmfw2_init, .test_cases = cs_dsp_ctl_rw_test_cases_adsp, + .attr.speed = KUNIT_SPEED_SLOW, }; kunit_test_suites(&cs_dsp_ctl_rw_test_halo, diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_wmfw.c b/drivers/firmware/cirrus/test/cs_dsp_test_wmfw.c index f02cb6cf7638..788458d5a287 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_wmfw.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_wmfw.c @@ -2182,6 +2182,7 @@ static struct kunit_suite cs_dsp_wmfw_test_halo = { .init = cs_dsp_wmfw_test_halo_init, .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_halo, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_wmfw_test_adsp2_32bit_wmfw0 = { @@ -2189,6 +2190,7 @@ static struct kunit_suite cs_dsp_wmfw_test_adsp2_32bit_wmfw0 = { .init = cs_dsp_wmfw_test_adsp2_32bit_wmfw0_init, .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_wmfw_test_adsp2_32bit_wmfw1 = { @@ -2196,6 +2198,7 @@ static struct kunit_suite cs_dsp_wmfw_test_adsp2_32bit_wmfw1 = { .init = cs_dsp_wmfw_test_adsp2_32bit_wmfw1_init, .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_wmfw_test_adsp2_32bit_wmfw2 = { @@ -2203,6 +2206,7 @@ static struct kunit_suite cs_dsp_wmfw_test_adsp2_32bit_wmfw2 = { .init = cs_dsp_wmfw_test_adsp2_32bit_wmfw2_init, .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_wmfw_test_adsp2_16bit_wmfw0 = { @@ -2210,6 +2214,7 @@ static struct kunit_suite cs_dsp_wmfw_test_adsp2_16bit_wmfw0 = { .init = cs_dsp_wmfw_test_adsp2_16bit_wmfw0_init, .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_wmfw_test_adsp2_16bit_wmfw1 = { @@ -2217,6 +2222,7 @@ static struct kunit_suite cs_dsp_wmfw_test_adsp2_16bit_wmfw1 = { .init = cs_dsp_wmfw_test_adsp2_16bit_wmfw1_init, .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_wmfw_test_adsp2_16bit_wmfw2 = { @@ -2224,6 +2230,7 @@ static struct kunit_suite cs_dsp_wmfw_test_adsp2_16bit_wmfw2 = { .init = cs_dsp_wmfw_test_adsp2_16bit_wmfw2_init, .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, + .attr.speed = KUNIT_SPEED_SLOW, }; kunit_test_suites(&cs_dsp_wmfw_test_halo, diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_wmfw_error.c b/drivers/firmware/cirrus/test/cs_dsp_test_wmfw_error.c index 37162d12e2fa..79eee89a7fd5 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_wmfw_error.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_wmfw_error.c @@ -1306,6 +1306,7 @@ static struct kunit_suite cs_dsp_wmfw_err_test_halo = { .init = cs_dsp_wmfw_err_test_halo_init, .exit = cs_dsp_wmfw_err_test_exit, .test_cases = cs_dsp_wmfw_err_test_cases_v3, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_32bit_wmfw0 = { @@ -1313,6 +1314,7 @@ static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_32bit_wmfw0 = { .init = cs_dsp_wmfw_err_test_adsp2_32bit_wmfw0_init, .exit = cs_dsp_wmfw_err_test_exit, .test_cases = cs_dsp_wmfw_err_test_cases_v0, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_32bit_wmfw1 = { @@ -1320,6 +1322,7 @@ static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_32bit_wmfw1 = { .init = cs_dsp_wmfw_err_test_adsp2_32bit_wmfw1_init, .exit = cs_dsp_wmfw_err_test_exit, .test_cases = cs_dsp_wmfw_err_test_cases_v1, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_32bit_wmfw2 = { @@ -1327,6 +1330,7 @@ static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_32bit_wmfw2 = { .init = cs_dsp_wmfw_err_test_adsp2_32bit_wmfw2_init, .exit = cs_dsp_wmfw_err_test_exit, .test_cases = cs_dsp_wmfw_err_test_cases_v2, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_16bit_wmfw0 = { @@ -1334,6 +1338,7 @@ static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_16bit_wmfw0 = { .init = cs_dsp_wmfw_err_test_adsp2_16bit_wmfw0_init, .exit = cs_dsp_wmfw_err_test_exit, .test_cases = cs_dsp_wmfw_err_test_cases_v0, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_16bit_wmfw1 = { @@ -1341,6 +1346,7 @@ static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_16bit_wmfw1 = { .init = cs_dsp_wmfw_err_test_adsp2_16bit_wmfw1_init, .exit = cs_dsp_wmfw_err_test_exit, .test_cases = cs_dsp_wmfw_err_test_cases_v1, + .attr.speed = KUNIT_SPEED_SLOW, }; static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_16bit_wmfw2 = { @@ -1348,6 +1354,7 @@ static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_16bit_wmfw2 = { .init = cs_dsp_wmfw_err_test_adsp2_16bit_wmfw2_init, .exit = cs_dsp_wmfw_err_test_exit, .test_cases = cs_dsp_wmfw_err_test_cases_v2, + .attr.speed = KUNIT_SPEED_SLOW, }; kunit_test_suites(&cs_dsp_wmfw_err_test_halo, From 1bbbda5b178a1399339139eb3c326300008b72d6 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 25 Feb 2026 14:01:15 +0000 Subject: [PATCH 023/359] ASoC: SDCA: Add default value for mipi-sdca-function-reset-max-delay Add a default value for the function reset timeout since version 1.0 of the SDCA specification doesn't actually include this property, it was added later. Fixes: 7b6be935e7ef ("ASoC: SDCA: Parse Function Reset max delay") Reviewed-by: Pierre-Louis Bossart Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260225140118.402695-2-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_fdl.c | 5 ----- sound/soc/sdca/sdca_functions.c | 6 +++++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c index 07892bc3a44e..994821a6df61 100644 --- a/sound/soc/sdca/sdca_fdl.c +++ b/sound/soc/sdca/sdca_fdl.c @@ -46,11 +46,6 @@ int sdca_reset_function(struct device *dev, struct sdca_function_data *function, if (ret) // Allowed for function reset to not be implemented return 0; - if (!function->reset_max_delay) { - dev_err(dev, "No reset delay specified in DisCo\n"); - return -EINVAL; - } - /* * Poll up to 16 times but no more than once per ms, these are just * arbitrarily selected values, so may be fine tuned in future. diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c index 95b67bb904c3..a0aed95c7634 100644 --- a/sound/soc/sdca/sdca_functions.c +++ b/sound/soc/sdca/sdca_functions.c @@ -2167,8 +2167,12 @@ int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, ret = fwnode_property_read_u32(function_desc->node, "mipi-sdca-function-reset-max-delay", &tmp); - if (!ret) + if (ret || tmp == 0) { + dev_dbg(dev, "reset delay missing, defaulting to 100mS\n"); + function->reset_max_delay = 100000; + } else { function->reset_max_delay = tmp; + } dev_dbg(dev, "%pfwP: name %s busy delay %dus reset delay %dus\n", function->desc->node, function->desc->name, From 1fb720d33eecdb9a90ee340b3000ba378d49f5ca Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 25 Feb 2026 14:01:16 +0000 Subject: [PATCH 024/359] ASoC: SDCA: Update counting of SU/GE DAPM routes Device Layer Selector Unit's are controlled by a Group Entity control rather than by the host directly. For the purposes of the ASoC class driver the number of input routes to the SU is controlled by the number of options within the Group Entity Selected Mode Control. ie. One valid DAPM route for each valid route defined in the Group Entity. Currently the code assumes that a Device Layer SU will have a number of routes equal to the number of potential sources for the SU. ie. it counts the routes using the SU, but then creates the routes using the GE. However, this isn't actually true, it is perfectly allowed for the GE to only define options for some of the potential sources of the SU.o In such a case the number of routes return will not match those created, leading to either an overflow of the routes array or undefined routes to be past to the ASoC core, both of which generally lead to the sound card failing to probe. Update the handling for the counting of routes to count the connected routes on the GE itself and then ignore the source routes on the SU. This makes it match the logic generating the routes and ensuring that both remain in sync. Fixes: 2c8b3a8e6aa8 ("ASoC: SDCA: Create DAPM widgets and routes from DisCo") Reviewed-by: Pierre-Louis Bossart Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260225140118.402695-3-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_asoc.c | 41 +++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c index a0191e5a5a7d..b6536eeecf58 100644 --- a/sound/soc/sdca/sdca_asoc.c +++ b/sound/soc/sdca/sdca_asoc.c @@ -51,6 +51,25 @@ static bool readonly_control(struct sdca_control *control) return control->has_fixed || control->mode == SDCA_ACCESS_MODE_RO; } +static int ge_count_routes(struct sdca_entity *entity) +{ + int count = 0; + int i, j; + + for (i = 0; i < entity->ge.num_modes; i++) { + struct sdca_ge_mode *mode = &entity->ge.modes[i]; + + for (j = 0; j < mode->num_controls; j++) { + struct sdca_ge_control *affected = &mode->controls[j]; + + if (affected->sel != SDCA_CTL_SU_SELECTOR || affected->val) + count++; + } + } + + return count; +} + /** * sdca_asoc_count_component - count the various component parts * @dev: Pointer to the device against which allocations will be done. @@ -74,6 +93,7 @@ int sdca_asoc_count_component(struct device *dev, struct sdca_function_data *fun int *num_widgets, int *num_routes, int *num_controls, int *num_dais) { + struct sdca_control *control; int i, j; *num_widgets = function->num_entities - 1; @@ -83,6 +103,7 @@ int sdca_asoc_count_component(struct device *dev, struct sdca_function_data *fun for (i = 0; i < function->num_entities - 1; i++) { struct sdca_entity *entity = &function->entities[i]; + bool skip_primary_routes = false; /* Add supply/DAI widget connections */ switch (entity->type) { @@ -96,6 +117,17 @@ int sdca_asoc_count_component(struct device *dev, struct sdca_function_data *fun case SDCA_ENTITY_TYPE_PDE: *num_routes += entity->pde.num_managed; break; + case SDCA_ENTITY_TYPE_GE: + *num_routes += ge_count_routes(entity); + skip_primary_routes = true; + break; + case SDCA_ENTITY_TYPE_SU: + control = sdca_selector_find_control(dev, entity, SDCA_CTL_SU_SELECTOR); + if (!control) + return -EINVAL; + + skip_primary_routes = (control->layers == SDCA_ACCESS_LAYER_DEVICE); + break; default: break; } @@ -104,7 +136,8 @@ int sdca_asoc_count_component(struct device *dev, struct sdca_function_data *fun (*num_routes)++; /* Add primary entity connections from DisCo */ - *num_routes += entity->num_sources; + if (!skip_primary_routes) + *num_routes += entity->num_sources; for (j = 0; j < entity->num_controls; j++) { if (exported_control(entity, &entity->controls[j])) @@ -442,7 +475,6 @@ static int entity_parse_su_device(struct device *dev, struct snd_soc_dapm_route **route) { struct sdca_control_range *range; - int num_routes = 0; int i, j; if (!entity->group) { @@ -478,11 +510,6 @@ static int entity_parse_su_device(struct device *dev, return -EINVAL; } - if (++num_routes > entity->num_sources) { - dev_err(dev, "%s: too many input routes\n", entity->label); - return -EINVAL; - } - term = sdca_range_search(range, SDCA_SELECTED_MODE_INDEX, mode->val, SDCA_SELECTED_MODE_TERM_TYPE); if (!term) { From d4f7d5a9a0f963dc895c18084425ce332a80d3a8 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 25 Feb 2026 14:01:17 +0000 Subject: [PATCH 025/359] ASoC: SDCA: Improve mapping of Q7.8 SDCA volumes SDCA measures volumes in 256ths of a dB, whereas ALSA measures volumes in 100ths of a dB. Currently the SDCA volume controls are mapped to ALSA controls by mapping the step size and working out the number of steps for this mapped step size. Due to quantization of the step size this means the number of steps in the ALSA control will rarely match the number of steps in the SDCA control, leading to skipped values and multiple values that map to the same volume. This is not a huge problem, the volume is still increasing and the differences will be small but it is not really desirable. It is simpler and more accurate to count the number of steps based on the SDCA volume levels. This gives a 1-to-1 mapping between control values and register volumes. The TLV is based on a minimum and maximum volume so still accurately specifies the volume range. Tested-by: Richard Fitzgerald Reviewed-by: Pierre-Louis Bossart Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260225140118.402695-4-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_asoc.c | 6 ++---- sound/soc/soc-ops.c | 12 ++---------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c index b6536eeecf58..e6f7c2778bec 100644 --- a/sound/soc/sdca/sdca_asoc.c +++ b/sound/soc/sdca/sdca_asoc.c @@ -841,10 +841,8 @@ static int control_limit_kctl(struct device *dev, tlv[2] = (min * 100) >> 8; tlv[3] = (max * 100) >> 8; - step = (step * 100) >> 8; - - mc->min = ((int)tlv[2] / step); - mc->max = ((int)tlv[3] / step); + mc->min = min / step; + mc->max = max / step; mc->shift = step; mc->sign_bit = 15; mc->sdca_q78 = 1; diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c index f966d4e13c7f..8ae6609ca961 100644 --- a/sound/soc/soc-ops.c +++ b/sound/soc/soc-ops.c @@ -120,25 +120,17 @@ static int sdca_soc_q78_reg_to_ctl(struct soc_mixer_control *mc, unsigned int re return -EINVAL; val = sign_extend32(val, mc->sign_bit); - val = (((val * 100) >> 8) / (int)mc->shift); - val -= mc->min; - return val & mask; + return ((val / mc->shift) - mc->min) & mask; } static unsigned int sdca_soc_q78_ctl_to_reg(struct soc_mixer_control *mc, int val, unsigned int mask, unsigned int shift, int max) { - unsigned int ret_val; - int reg_val; - if (WARN_ON(!mc->shift)) return -EINVAL; - reg_val = val + mc->min; - ret_val = (int)((reg_val * mc->shift) << 8) / 100; - - return ret_val & mask; + return ((val + mc->min) * mc->shift) & mask; } static int soc_mixer_reg_to_ctl(struct soc_mixer_control *mc, unsigned int reg_val, From 501efdcb3b3ab099fc0ce2f6e668b1c4095dd476 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 25 Feb 2026 14:01:18 +0000 Subject: [PATCH 026/359] ASoC: SDCA: Pull the Q7.8 volume helpers out of soc-ops It is cleaner to keep the SDCA code contained and not update the core code for things that are unlikely to see reuse outside of SDCA. Move the Q7.8 volume helpers back into the SDCA core code. Reviewed-by: Pierre-Louis Bossart Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260225140118.402695-5-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/soc.h | 1 - sound/soc/sdca/sdca_asoc.c | 67 +++++++++++++++++++++++++++++++++++++- sound/soc/soc-ops.c | 56 +++++++------------------------ 3 files changed, 77 insertions(+), 47 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index 7d8376c8e1be..172bd68e1315 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1239,7 +1239,6 @@ struct soc_mixer_control { unsigned int sign_bit; unsigned int invert:1; unsigned int autodisable:1; - unsigned int sdca_q78:1; #ifdef CONFIG_SND_SOC_TOPOLOGY struct snd_soc_dobj dobj; #endif diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c index e6f7c2778bec..a342a4e56717 100644 --- a/sound/soc/sdca/sdca_asoc.c +++ b/sound/soc/sdca/sdca_asoc.c @@ -805,6 +805,70 @@ int sdca_asoc_populate_dapm(struct device *dev, struct sdca_function_data *funct } EXPORT_SYMBOL_NS(sdca_asoc_populate_dapm, "SND_SOC_SDCA"); +static int q78_write(struct snd_soc_component *component, + struct soc_mixer_control *mc, + unsigned int reg, const int val) +{ + unsigned int mask = GENMASK(mc->sign_bit, 0); + unsigned int reg_val; + + if (val < 0 || val > mc->max - mc->min) + return -EINVAL; + + reg_val = (val + mc->min) * mc->shift; + + return snd_soc_component_update_bits(component, reg, mask, reg_val); +} + +static int q78_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + int ret; + + ret = q78_write(component, mc, mc->reg, ucontrol->value.integer.value[0]); + if (ret < 0) + return ret; + + if (snd_soc_volsw_is_stereo(mc)) { + int err; /* Don't drop change flag */ + + err = q78_write(component, mc, mc->rreg, ucontrol->value.integer.value[1]); + if (err) + return err; + } + + return ret; +} + +static int q78_read(struct snd_soc_component *component, + struct soc_mixer_control *mc, unsigned int reg) +{ + unsigned int reg_val; + int val; + + reg_val = snd_soc_component_read(component, reg); + + val = (sign_extend32(reg_val, mc->sign_bit) / mc->shift) - mc->min; + + return val & GENMASK(mc->sign_bit, 0); +} + +static int q78_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = q78_read(component, mc, mc->reg); + + if (snd_soc_volsw_is_stereo(mc)) + ucontrol->value.integer.value[1] = q78_read(component, mc, mc->rreg); + + return 0; +} + static int control_limit_kctl(struct device *dev, struct sdca_entity *entity, struct sdca_control *control, @@ -845,10 +909,11 @@ static int control_limit_kctl(struct device *dev, mc->max = max / step; mc->shift = step; mc->sign_bit = 15; - mc->sdca_q78 = 1; kctl->tlv.p = tlv; kctl->access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + kctl->get = q78_get_volsw; + kctl->put = q78_put_volsw; return 0; } diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c index 8ae6609ca961..0d633f38cfdc 100644 --- a/sound/soc/soc-ops.c +++ b/sound/soc/soc-ops.c @@ -110,29 +110,6 @@ int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol, } EXPORT_SYMBOL_GPL(snd_soc_put_enum_double); -static int sdca_soc_q78_reg_to_ctl(struct soc_mixer_control *mc, unsigned int reg_val, - unsigned int mask, unsigned int shift, int max, - bool sx) -{ - int val = reg_val; - - if (WARN_ON(!mc->shift)) - return -EINVAL; - - val = sign_extend32(val, mc->sign_bit); - - return ((val / mc->shift) - mc->min) & mask; -} - -static unsigned int sdca_soc_q78_ctl_to_reg(struct soc_mixer_control *mc, int val, - unsigned int mask, unsigned int shift, int max) -{ - if (WARN_ON(!mc->shift)) - return -EINVAL; - - return ((val + mc->min) * mc->shift) & mask; -} - static int soc_mixer_reg_to_ctl(struct soc_mixer_control *mc, unsigned int reg_val, unsigned int mask, unsigned int shift, int max, bool sx) @@ -226,27 +203,19 @@ static int soc_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol, struct soc_mixer_control *mc, int mask, int max) { - unsigned int (*ctl_to_reg)(struct soc_mixer_control *, int, unsigned int, unsigned int, int); struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); unsigned int val1, val_mask; unsigned int val2 = 0; bool double_r = false; int ret; - if (mc->sdca_q78) { - ctl_to_reg = sdca_soc_q78_ctl_to_reg; - val_mask = mask; - } else { - ctl_to_reg = soc_mixer_ctl_to_reg; - val_mask = mask << mc->shift; - } - ret = soc_mixer_valid_ctl(mc, ucontrol->value.integer.value[0], max); if (ret) return ret; - val1 = ctl_to_reg(mc, ucontrol->value.integer.value[0], + val1 = soc_mixer_ctl_to_reg(mc, ucontrol->value.integer.value[0], mask, mc->shift, max); + val_mask = mask << mc->shift; if (snd_soc_volsw_is_stereo(mc)) { ret = soc_mixer_valid_ctl(mc, ucontrol->value.integer.value[1], max); @@ -254,10 +223,14 @@ static int soc_put_volsw(struct snd_kcontrol *kcontrol, return ret; if (mc->reg == mc->rreg) { - val1 |= ctl_to_reg(mc, ucontrol->value.integer.value[1], mask, mc->rshift, max); + val1 |= soc_mixer_ctl_to_reg(mc, + ucontrol->value.integer.value[1], + mask, mc->rshift, max); val_mask |= mask << mc->rshift; } else { - val2 = ctl_to_reg(mc, ucontrol->value.integer.value[1], mask, mc->shift, max); + val2 = soc_mixer_ctl_to_reg(mc, + ucontrol->value.integer.value[1], + mask, mc->shift, max); double_r = true; } } @@ -281,28 +254,21 @@ static int soc_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol, struct soc_mixer_control *mc, int mask, int max, bool sx) { - int (*reg_to_ctl)(struct soc_mixer_control *, unsigned int, unsigned int, - unsigned int, int, bool); struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); unsigned int reg_val; int val; - if (mc->sdca_q78) - reg_to_ctl = sdca_soc_q78_reg_to_ctl; - else - reg_to_ctl = soc_mixer_reg_to_ctl; - reg_val = snd_soc_component_read(component, mc->reg); - val = reg_to_ctl(mc, reg_val, mask, mc->shift, max, sx); + val = soc_mixer_reg_to_ctl(mc, reg_val, mask, mc->shift, max, sx); ucontrol->value.integer.value[0] = val; if (snd_soc_volsw_is_stereo(mc)) { if (mc->reg == mc->rreg) { - val = reg_to_ctl(mc, reg_val, mask, mc->rshift, max, sx); + val = soc_mixer_reg_to_ctl(mc, reg_val, mask, mc->rshift, max, sx); } else { reg_val = snd_soc_component_read(component, mc->rreg); - val = reg_to_ctl(mc, reg_val, mask, mc->shift, max, sx); + val = soc_mixer_reg_to_ctl(mc, reg_val, mask, mc->shift, max, sx); } ucontrol->value.integer.value[1] = val; From 2974aa42e6696a1d95b727d677dc01a71af5b998 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 27 Feb 2026 01:17:19 +0000 Subject: [PATCH 027/359] ASoC: remove snd_soc_pcm_subclass enum snd_soc_pcm_subclass has added at v3.1 commit b8c0dab9bf337 ("ASoC: core - PCM mutex per rtd"), but has never been used during this 15 years. Let's remove it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/878qcfyogw.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc.h | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index 7bf7ce085516..b1c5dad26edb 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -422,11 +422,6 @@ struct snd_soc_jack_pin; #include #include -enum snd_soc_pcm_subclass { - SND_SOC_PCM_CLASS_PCM = 0, - SND_SOC_PCM_CLASS_BE = 1, -}; - int snd_soc_register_card(struct snd_soc_card *card); void snd_soc_unregister_card(struct snd_soc_card *card); int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card); @@ -999,7 +994,6 @@ struct snd_soc_card { /* Mutex for PCM operations */ struct mutex pcm_mutex; - enum snd_soc_pcm_subclass pcm_subclass; int (*probe)(struct snd_soc_card *card); int (*late_probe)(struct snd_soc_card *card); @@ -1519,7 +1513,7 @@ static inline void _snd_soc_dapm_mutex_assert_held_d(struct snd_soc_dapm_context */ static inline void _snd_soc_dpcm_mutex_lock_c(struct snd_soc_card *card) { - mutex_lock_nested(&card->pcm_mutex, card->pcm_subclass); + mutex_lock(&card->pcm_mutex); } static inline void _snd_soc_dpcm_mutex_unlock_c(struct snd_soc_card *card) From 84446536f63d471ab16b2faa25eeab1df21ace0a Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Tue, 24 Feb 2026 21:56:19 +0100 Subject: [PATCH 028/359] ALSA: control: Verify put() result when in debug mode The put() operation is expected to return: 1) 0 on success if no changes were made 2) 1 on success if changes were made 3) error code otherwise Currently 2) is usually ignored when writing control-operations. While forcing compliance is not an option right now, make it easier for developers to adhere to the expectations and notice problems by logging them when CONFIG_SND_CTL_DEBUG is enabled. Due to large size of struct snd_ctl_elem_value, 'value_buf' is provided as a reusable buffer for kctl->put() verification. This prevents exhausting the stack when verifying the operation. >From user perspective, patch introduces a new trace/events category 'snd_ctl' containing a single 'snd_ctl_put' event type. Log sample: amixer-1086 [003] ..... 8.035939: snd_ctl_put: success: expected=0, actual=0 for ctl numid=1, iface=MIXER, name='Master Playback Volume', index=0, device=0, subdevice=0, card=0 amixer-1087 [003] ..... 8.938721: snd_ctl_put: success: expected=1, actual=1 for ctl numid=1, iface=MIXER, name='Master Playback Volume', index=0, device=0, subdevice=0, card=0 amixer-1088 [003] ..... 9.631470: snd_ctl_put: success: expected=1, actual=1 for ctl numid=1, iface=MIXER, name='Master Playback Volume', index=0, device=0, subdevice=0, card=0 amixer-1089 [000] ..... 9.636786: snd_ctl_put: fail: expected=1, actual=0 for ctl numid=5, iface=MIXER, name='Loopback Mute', index=0, device=0, subdevice=0, card=0 Signed-off-by: Cezary Rojewski Reviewed-by: Mark Brown Reviewed-by: Jaroslav Kysela Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260224205619.584795-1-cezary.rojewski@intel.com --- include/sound/core.h | 3 ++ sound/core/Makefile | 1 + sound/core/control.c | 76 +++++++++++++++++++++++++++++++++++++- sound/core/control_trace.h | 55 +++++++++++++++++++++++++++ sound/core/init.c | 8 ++++ 5 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 sound/core/control_trace.h diff --git a/include/sound/core.h b/include/sound/core.h index 64327e971122..4093ec82a0a1 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -133,6 +133,9 @@ struct snd_card { #ifdef CONFIG_SND_DEBUG struct dentry *debugfs_root; /* debugfs root for card */ #endif +#ifdef CONFIG_SND_CTL_DEBUG + struct snd_ctl_elem_value *value_buf; /* buffer for kctl->put() verification */ +#endif #ifdef CONFIG_PM unsigned int power_state; /* power state */ diff --git a/sound/core/Makefile b/sound/core/Makefile index 31a0623cc89d..fdd3bb6e81a9 100644 --- a/sound/core/Makefile +++ b/sound/core/Makefile @@ -23,6 +23,7 @@ snd-pcm-$(CONFIG_SND_PCM_IEC958) += pcm_iec958.o # for trace-points CFLAGS_pcm_lib.o := -I$(src) CFLAGS_pcm_native.o := -I$(src) +CFLAGS_control.o := -I$(src) snd-pcm-dmaengine-y := pcm_dmaengine.o diff --git a/sound/core/control.c b/sound/core/control.c index 934e84e93838..374e703d15a9 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -19,6 +19,13 @@ #include #include +#ifdef CONFIG_SND_CTL_DEBUG +#define CREATE_TRACE_POINTS +#include "control_trace.h" +#else +#define trace_snd_ctl_put(card, kctl, iname, expected, actual) +#endif + // Max allocation size for user controls. static int max_user_ctl_alloc_size = 8 * 1024 * 1024; module_param_named(max_user_ctl_alloc_size, max_user_ctl_alloc_size, int, 0444); @@ -1264,6 +1271,72 @@ static int snd_ctl_elem_read_user(struct snd_card *card, return result; } +#if IS_ENABLED(CONFIG_SND_CTL_DEBUG) + +static const char *const snd_ctl_elem_iface_names[] = { + [SNDRV_CTL_ELEM_IFACE_CARD] = "CARD", + [SNDRV_CTL_ELEM_IFACE_HWDEP] = "HWDEP", + [SNDRV_CTL_ELEM_IFACE_MIXER] = "MIXER", + [SNDRV_CTL_ELEM_IFACE_PCM] = "PCM", + [SNDRV_CTL_ELEM_IFACE_RAWMIDI] = "RAWMIDI", + [SNDRV_CTL_ELEM_IFACE_TIMER] = "TIMER", + [SNDRV_CTL_ELEM_IFACE_SEQUENCER] = "SEQUENCER", +}; + +static int snd_ctl_put_verify(struct snd_card *card, struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *control) +{ + struct snd_ctl_elem_value *original = card->value_buf; + struct snd_ctl_elem_info info; + const char *iname; + int ret, retcmp; + + memset(original, 0, sizeof(*original)); + memset(&info, 0, sizeof(info)); + + ret = kctl->info(kctl, &info); + if (ret) + return ret; + + ret = kctl->get(kctl, original); + if (ret) + return ret; + + ret = kctl->put(kctl, control); + if (ret < 0) + return ret; + + /* Sanitize the new value (control->value) before comparing. */ + fill_remaining_elem_value(control, &info, 0); + + /* With known state for both new and original, do the comparison. */ + retcmp = memcmp(&original->value, &control->value, sizeof(original->value)); + if (retcmp) + retcmp = 1; + + iname = snd_ctl_elem_iface_names[kctl->id.iface]; + trace_snd_ctl_put(&kctl->id, iname, card->number, ret, retcmp); + + return ret; +} + +static int snd_ctl_put(struct snd_card *card, struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *control, unsigned int access) +{ + if ((access & SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK) || + (access & SNDRV_CTL_ELEM_ACCESS_VOLATILE)) + return kctl->put(kctl, control); + + return snd_ctl_put_verify(card, kctl, control); +} +#else +static inline int snd_ctl_put(struct snd_card *card, struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *control, unsigned int access) +{ + return kctl->put(kctl, control); +} +#endif + static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file, struct snd_ctl_elem_value *control) { @@ -1300,7 +1373,8 @@ static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file, false); } if (!result) - result = kctl->put(kctl, control); + result = snd_ctl_put(card, kctl, control, vd->access); + if (result < 0) { up_write(&card->controls_rwsem); return result; diff --git a/sound/core/control_trace.h b/sound/core/control_trace.h new file mode 100644 index 000000000000..d30e654b0860 --- /dev/null +++ b/sound/core/control_trace.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM snd_ctl + +#if !defined(_TRACE_SND_CTL_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_SND_CTL_H + +#include +#include + +TRACE_EVENT(snd_ctl_put, + + TP_PROTO(struct snd_ctl_elem_id *id, const char *iname, unsigned int card, + int expected, int actual), + + TP_ARGS(id, iname, card, expected, actual), + + TP_STRUCT__entry( + __field(unsigned int, numid) + __string(iname, iname) + __string(kname, id->name) + __field(unsigned int, index) + __field(unsigned int, device) + __field(unsigned int, subdevice) + __field(unsigned int, card) + __field(int, expected) + __field(int, actual) + ), + + TP_fast_assign( + __entry->numid = id->numid; + __assign_str(iname); + __assign_str(kname); + __entry->index = id->index; + __entry->device = id->device; + __entry->subdevice = id->subdevice; + __entry->card = card; + __entry->expected = expected; + __entry->actual = actual; + ), + + TP_printk("%s: expected=%d, actual=%d for ctl numid=%d, iface=%s, name='%s', index=%d, device=%d, subdevice=%d, card=%d\n", + __entry->expected == __entry->actual ? "success" : "fail", + __entry->expected, __entry->actual, __entry->numid, + __get_str(iname), __get_str(kname), __entry->index, + __entry->device, __entry->subdevice, __entry->card) +); + +#endif /* _TRACE_SND_CTL_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#define TRACE_INCLUDE_FILE control_trace +#include diff --git a/sound/core/init.c b/sound/core/init.c index 2f1bd9cbdbed..0c316189e947 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -362,6 +362,11 @@ static int snd_card_init(struct snd_card *card, struct device *parent, #ifdef CONFIG_SND_DEBUG card->debugfs_root = debugfs_create_dir(dev_name(&card->card_dev), sound_debugfs_root); +#endif +#ifdef CONFIG_SND_CTL_DEBUG + card->value_buf = kmalloc(sizeof(*card->value_buf), GFP_KERNEL); + if (!card->value_buf) + return -ENOMEM; #endif return 0; @@ -587,6 +592,9 @@ static int snd_card_do_free(struct snd_card *card) snd_device_free_all(card); if (card->private_free) card->private_free(card); +#ifdef CONFIG_SND_CTL_DEBUG + kfree(card->value_buf); +#endif if (snd_info_card_free(card) < 0) { dev_warn(card->dev, "unable to free card info\n"); /* Not fatal error */ From a69f67702091429316bf7b8dd2c7a405e8c26a65 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Thu, 26 Feb 2026 21:44:10 +0100 Subject: [PATCH 029/359] ALSA: aoa: Constify struct codec_connection 'struct codec_connection' are not modified in this driver. Constifying these structures moves some data to a read-only section, so increases overall security. On a x86_64, with allmodconfig: Before: ====== text data bss dec hex filename 10034 3392 12 13438 347e sound/aoa/fabrics/layout.o After: ===== text data bss dec hex filename 10370 3040 12 13422 346e sound/aoa/fabrics/layout.o Signed-off-by: Christophe JAILLET Link: https://patch.msgid.link/4009c337cc1a1a57795562279270c03687973b3b.1772138640.git.christophe.jaillet@wanadoo.fr Signed-off-by: Takashi Iwai --- sound/aoa/aoa.h | 2 +- sound/aoa/fabrics/layout.c | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/sound/aoa/aoa.h b/sound/aoa/aoa.h index badff9f7cd54..b92593f170ca 100644 --- a/sound/aoa/aoa.h +++ b/sound/aoa/aoa.h @@ -48,7 +48,7 @@ struct aoa_codec { u32 connected; /* data the fabric can associate with this structure */ - void *fabric_data; + const void *fabric_data; /* private! */ struct list_head list; diff --git a/sound/aoa/fabrics/layout.c b/sound/aoa/fabrics/layout.c index c18b55305294..c3ebb6de4789 100644 --- a/sound/aoa/fabrics/layout.c +++ b/sound/aoa/fabrics/layout.c @@ -55,7 +55,7 @@ struct codec_connection { struct codec_connect_info { char *name; - struct codec_connection *connections; + const struct codec_connection *connections; }; #define LAYOUT_FLAG_COMBO_LINEOUT_SPDIF (1<<0) @@ -116,7 +116,7 @@ MODULE_ALIAS("aoa-device-id-35"); MODULE_ALIAS("aoa-device-id-44"); /* onyx with all but microphone connected */ -static struct codec_connection onyx_connections_nomic[] = { +static const struct codec_connection onyx_connections_nomic[] = { { .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT, .codec_bit = 0, @@ -133,7 +133,7 @@ static struct codec_connection onyx_connections_nomic[] = { }; /* onyx on machines without headphone */ -static struct codec_connection onyx_connections_noheadphones[] = { +static const struct codec_connection onyx_connections_noheadphones[] = { { .connected = CC_SPEAKERS | CC_LINEOUT | CC_LINEOUT_LABELLED_HEADPHONE, @@ -157,7 +157,7 @@ static struct codec_connection onyx_connections_noheadphones[] = { }; /* onyx on machines with real line-out */ -static struct codec_connection onyx_connections_reallineout[] = { +static const struct codec_connection onyx_connections_reallineout[] = { { .connected = CC_SPEAKERS | CC_LINEOUT | CC_HEADPHONE, .codec_bit = 0, @@ -174,7 +174,7 @@ static struct codec_connection onyx_connections_reallineout[] = { }; /* tas on machines without line out */ -static struct codec_connection tas_connections_nolineout[] = { +static const struct codec_connection tas_connections_nolineout[] = { { .connected = CC_SPEAKERS | CC_HEADPHONE, .codec_bit = 0, @@ -191,7 +191,7 @@ static struct codec_connection tas_connections_nolineout[] = { }; /* tas on machines with neither line out nor line in */ -static struct codec_connection tas_connections_noline[] = { +static const struct codec_connection tas_connections_noline[] = { { .connected = CC_SPEAKERS | CC_HEADPHONE, .codec_bit = 0, @@ -204,7 +204,7 @@ static struct codec_connection tas_connections_noline[] = { }; /* tas on machines without microphone */ -static struct codec_connection tas_connections_nomic[] = { +static const struct codec_connection tas_connections_nomic[] = { { .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT, .codec_bit = 0, @@ -217,7 +217,7 @@ static struct codec_connection tas_connections_nomic[] = { }; /* tas on machines with everything connected */ -static struct codec_connection tas_connections_all[] = { +static const struct codec_connection tas_connections_all[] = { { .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT, .codec_bit = 0, @@ -233,7 +233,7 @@ static struct codec_connection tas_connections_all[] = { {} /* terminate array by .connected == 0 */ }; -static struct codec_connection toonie_connections[] = { +static const struct codec_connection toonie_connections[] = { { .connected = CC_SPEAKERS | CC_HEADPHONE, .codec_bit = 0, @@ -241,7 +241,7 @@ static struct codec_connection toonie_connections[] = { {} /* terminate array by .connected == 0 */ }; -static struct codec_connection topaz_input[] = { +static const struct codec_connection topaz_input[] = { { .connected = CC_DIGITALIN, .codec_bit = 0, @@ -249,7 +249,7 @@ static struct codec_connection topaz_input[] = { {} /* terminate array by .connected == 0 */ }; -static struct codec_connection topaz_output[] = { +static const struct codec_connection topaz_output[] = { { .connected = CC_DIGITALOUT, .codec_bit = 1, @@ -257,7 +257,7 @@ static struct codec_connection topaz_output[] = { {} /* terminate array by .connected == 0 */ }; -static struct codec_connection topaz_inout[] = { +static const struct codec_connection topaz_inout[] = { { .connected = CC_DIGITALIN, .codec_bit = 0, @@ -772,7 +772,7 @@ static int check_codec(struct aoa_codec *codec, { const u32 *ref; char propname[32]; - struct codec_connection *cc; + const struct codec_connection *cc; /* if the codec has a 'codec' node, we require a reference */ if (of_node_name_eq(codec->node, "codec")) { @@ -895,7 +895,7 @@ static void layout_notify(void *data) static void layout_attached_codec(struct aoa_codec *codec) { - struct codec_connection *cc; + const struct codec_connection *cc; struct snd_kcontrol *ctl; int headphones, lineout; struct layout_dev *ldev = layout_device; From da37bfe76b5b4ccc01ed8132215098e20d78e5f3 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 25 Feb 2026 18:09:07 +0800 Subject: [PATCH 030/359] ASoC: cs42xx8: add error checks for constraints in TDM mode In the TDM format the ADC and DAC serial ports will only operate as a slave, the sysclk should not be less than 256FS and Quad-Speed Mode (100 to 200 kHz sample rates) is not supported by ADC. So add error checks for these constraints. Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260225100907.686470-1-shengjiu.wang@nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs42xx8.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/sound/soc/codecs/cs42xx8.c b/sound/soc/codecs/cs42xx8.c index f6b8fe7d53e2..12fe9b3e2525 100644 --- a/sound/soc/codecs/cs42xx8.c +++ b/sound/soc/codecs/cs42xx8.c @@ -40,6 +40,7 @@ struct cs42xx8_priv { struct clk *clk; bool slave_mode; + bool is_tdm_mode; unsigned long sysclk; u32 tx_channels; struct gpio_desc *gpiod_reset; @@ -214,6 +215,8 @@ static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai, struct cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component); u32 val; + cs42xx8->is_tdm_mode = false; + /* Set DAI format */ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_LEFT_J: @@ -227,6 +230,7 @@ static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai, break; case SND_SOC_DAIFMT_DSP_A: val = CS42XX8_INTF_DAC_DIF_TDM | CS42XX8_INTF_ADC_DIF_TDM; + cs42xx8->is_tdm_mode = true; break; default: dev_err(component->dev, "unsupported dai format\n"); @@ -250,6 +254,11 @@ static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai, return -EINVAL; } + if (cs42xx8->is_tdm_mode && !cs42xx8->slave_mode) { + dev_err(component->dev, "TDM mode is supported only in slave mode\n"); + return -EINVAL; + } + return 0; } @@ -332,6 +341,19 @@ static int cs42xx8_hw_params(struct snd_pcm_substream *substream, cs42xx8->rate[tx] = params_rate(params); + if (cs42xx8->is_tdm_mode) { + if (cs42xx8->sysclk < 256 * cs42xx8->rate[tx]) { + dev_err(component->dev, "Unsupported sysclk in TDM mode\n"); + return -EINVAL; + } + + if (!tx && cs42xx8->rate[tx] > 100000) { + dev_err(component->dev, + "ADC does not support Quad-Speed Mode in TDM mode\n"); + return -EINVAL; + } + } + mask = CS42XX8_FUNCMOD_MFREQ_MASK; val = cs42xx8_ratios[i].mfreq; From 272aabef50bc3fe58edd26de000f4cdd41bdbe60 Mon Sep 17 00:00:00 2001 From: Sander Vanheule Date: Fri, 20 Feb 2026 16:26:33 +0100 Subject: [PATCH 031/359] ASoC: sti: Return errors from regmap_field_alloc() When regmap_field_alloc() fails, it can return an error. Specifically, it will return PTR_ERR(-ENOMEM) when the allocation returns a NULL pointer. The code then uses these allocations with a simple NULL check: if (player->clk_sel) { // May dereference invalid pointer (-ENOMEM) err = regmap_field_write(player->clk_sel, ...); } Ensure initialization fails by forwarding the errors from regmap_field_alloc(), thus avoiding the use of the invalid pointers. Fixes: 76c2145ded6b ("ASoC: sti: Add CPU DAI driver for playback") Signed-off-by: Sander Vanheule Link: https://patch.msgid.link/20260220152634.480766-2-sander@svanheule.net Signed-off-by: Mark Brown --- sound/soc/sti/uniperif_player.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c index 6d1ce030963c..f1b7e76f97b5 100644 --- a/sound/soc/sti/uniperif_player.c +++ b/sound/soc/sti/uniperif_player.c @@ -1029,7 +1029,12 @@ static int uni_player_parse_dt_audio_glue(struct platform_device *pdev, } player->clk_sel = regmap_field_alloc(regmap, regfield[0]); + if (IS_ERR(player->clk_sel)) + return PTR_ERR(player->clk_sel); + player->valid_sel = regmap_field_alloc(regmap, regfield[1]); + if (IS_ERR(player->valid_sel)) + return PTR_ERR(player->valid_sel); return 0; } From 1696fad8b259a2d46e51cd6e17e4bcdbe02279fa Mon Sep 17 00:00:00 2001 From: Sander Vanheule Date: Fri, 20 Feb 2026 16:26:34 +0100 Subject: [PATCH 032/359] ASoC: sti: use managed regmap_field allocations The regmap_field objects allocated at player init are never freed and may leak resources if the driver is removed. Switch to devm_regmap_field_alloc() to automatically limit the lifetime of the allocations the lifetime of the device. Fixes: 76c2145ded6b ("ASoC: sti: Add CPU DAI driver for playback") Signed-off-by: Sander Vanheule Link: https://patch.msgid.link/20260220152634.480766-3-sander@svanheule.net Signed-off-by: Mark Brown --- sound/soc/sti/uniperif_player.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c index f1b7e76f97b5..45d35b887e4e 100644 --- a/sound/soc/sti/uniperif_player.c +++ b/sound/soc/sti/uniperif_player.c @@ -1028,11 +1028,11 @@ static int uni_player_parse_dt_audio_glue(struct platform_device *pdev, return PTR_ERR(regmap); } - player->clk_sel = regmap_field_alloc(regmap, regfield[0]); + player->clk_sel = devm_regmap_field_alloc(&pdev->dev, regmap, regfield[0]); if (IS_ERR(player->clk_sel)) return PTR_ERR(player->clk_sel); - player->valid_sel = regmap_field_alloc(regmap, regfield[1]); + player->valid_sel = devm_regmap_field_alloc(&pdev->dev, regmap, regfield[1]); if (IS_ERR(player->valid_sel)) return PTR_ERR(player->valid_sel); From d6b634cbc2eed73497877c95a7807d0f34fa4459 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Mon, 23 Feb 2026 08:50:47 +0200 Subject: [PATCH 033/359] ASoC: dt-bindings: nvidia,tegra-audio: document CPCAP CODEC Add dt-binding for CPCAP CODEC. Signed-off-by: Svyatoslav Ryhel Link: https://patch.msgid.link/20260223065051.13070-2-clamor95@gmail.com Signed-off-by: Mark Brown --- .../sound/nvidia,tegra-audio-cpcap.yaml | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-cpcap.yaml diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-cpcap.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-cpcap.yaml new file mode 100644 index 000000000000..69af2022d0fa --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-cpcap.yaml @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-cpcap.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NVIDIA Tegra audio complex with CPCAP CODEC + +maintainers: + - Svyatoslav Ryhel + +allOf: + - $ref: nvidia,tegra-audio-common.yaml# + +properties: + compatible: + items: + - pattern: '^motorola,tegra-audio-cpcap(-[a-z0-9]+)+$' + - const: nvidia,tegra-audio-cpcap + + 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 + - Speakers + - Int Spk + - Earpiece + - Int Mic + - Headset Mic + - Internal Mic 1 + - Internal Mic 2 + - Headphone + - Headphones + - Headphone Jack + - Mic Jack + + # CODEC Pins + - MICR + - HSMIC + - EMUMIC + - MICL + - EXTR + - EXTL + - EP + - SPKR + - SPKL + - LINER + - LINEL + - HSR + - HSL + - EMUR + - EMUL + +unevaluatedProperties: false + +examples: + - | + #include + #include + sound { + compatible = "motorola,tegra-audio-cpcap-olympus", + "nvidia,tegra-audio-cpcap"; + nvidia,model = "Motorola Atrix 4G (MB860) CPCAP"; + + nvidia,audio-routing = + "Headphones", "HSR", + "Headphones", "HSL", + "Int Spk", "SPKR", + "Int Spk", "SPKL", + "Earpiece", "EP", + "HSMIC", "Mic Jack", + "MICR", "Internal Mic 1", + "MICL", "Internal Mic 2"; + + nvidia,i2s-controller = <&tegra_i2s1>; + nvidia,audio-codec = <&cpcap_audio>; + + 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 a34547b389a91bf0709f0901c3752ccaf63730cd Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Mon, 23 Feb 2026 08:50:48 +0200 Subject: [PATCH 034/359] ASoC: tegra: Support CPCAP by machine driver Add CPCAP codec support to the Tegra ASoC machine driver. This codec is found in Motorola T20 devices like Atrix 4G and Droid X2. Signed-off-by: Svyatoslav Ryhel Link: https://patch.msgid.link/20260223065051.13070-3-clamor95@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/Kconfig | 9 +++++ sound/soc/tegra/tegra_asoc_machine.c | 52 ++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig index 9dbd589879fb..dd58525b7079 100644 --- a/sound/soc/tegra/Kconfig +++ b/sound/soc/tegra/Kconfig @@ -294,6 +294,15 @@ config SND_SOC_TEGRA_SGTL5000 boards using the SGTL5000 codec, such as Apalis T30, Apalis TK1 or Colibri T30. +config SND_SOC_TEGRA_CPCAP + tristate "SoC Audio support for Tegra boards using a CPCAP codec" + depends on I2C && GPIOLIB && MFD_CPCAP + select SND_SOC_TEGRA_MACHINE_DRV + select SND_SOC_CPCAP + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the CPCAP codec, such as Motorola Atrix 4G or Droid X2. + endif endmenu diff --git a/sound/soc/tegra/tegra_asoc_machine.c b/sound/soc/tegra/tegra_asoc_machine.c index d48463ac16fc..10834f9c3422 100644 --- a/sound/soc/tegra/tegra_asoc_machine.c +++ b/sound/soc/tegra/tegra_asoc_machine.c @@ -287,6 +287,25 @@ static unsigned int tegra_machine_mclk_rate_6mhz(unsigned int srate) return mclk; } +static unsigned int tegra_machine_mclk_rate_cpcap(unsigned int srate) +{ + unsigned int mclk; + + switch (srate) { + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 26000000; + break; + default: + mclk = 256 * srate; + break; + } + + return mclk; +} + static int tegra_machine_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { @@ -985,6 +1004,38 @@ static const struct tegra_asoc_data tegra_rt5631_data = { .add_hp_jack = true, }; +/* CPCAP machine */ + +SND_SOC_DAILINK_DEFS(cpcap_hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "cpcap-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link tegra_cpcap_dai = { + .name = "CPCAP", + .stream_name = "CPCAP PCM", + .init = tegra_asoc_machine_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBP_CFP, + SND_SOC_DAILINK_REG(cpcap_hifi), +}; + +static struct snd_soc_card snd_soc_tegra_cpcap = { + .components = "codec:cpcap", + .dai_link = &tegra_cpcap_dai, + .num_links = 1, + .fully_routed = true, +}; + +static const struct tegra_asoc_data tegra_cpcap_data = { + .mclk_rate = tegra_machine_mclk_rate_cpcap, + .card = &snd_soc_tegra_cpcap, + .add_common_dapm_widgets = true, + .add_common_controls = true, + .add_common_snd_ops = true, +}; + static const struct of_device_id tegra_machine_of_match[] = { { .compatible = "nvidia,tegra-audio-trimslice", .data = &tegra_trimslice_data }, { .compatible = "nvidia,tegra-audio-max98090", .data = &tegra_max98090_data }, @@ -997,6 +1048,7 @@ static const struct of_device_id tegra_machine_of_match[] = { { .compatible = "nvidia,tegra-audio-rt5640", .data = &tegra_rt5640_data }, { .compatible = "nvidia,tegra-audio-alc5632", .data = &tegra_rt5632_data }, { .compatible = "nvidia,tegra-audio-rt5631", .data = &tegra_rt5631_data }, + { .compatible = "nvidia,tegra-audio-cpcap", .data = &tegra_cpcap_data }, {}, }; MODULE_DEVICE_TABLE(of, tegra_machine_of_match); From 79d73f5a0b7a702a19189c1a237ae3728d88de59 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Mon, 23 Feb 2026 08:50:49 +0200 Subject: [PATCH 035/359] ASoC: dt-bindings: nvidia,tegra-audio: document WM8962 CODEC Add dt-binding for WM8962 CODEC. Signed-off-by: Svyatoslav Ryhel Link: https://patch.msgid.link/20260223065051.13070-4-clamor95@gmail.com Signed-off-by: Mark Brown --- .../sound/nvidia,tegra-audio-wm8962.yaml | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8962.yaml diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8962.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8962.yaml new file mode 100644 index 000000000000..2c3bf5a02a34 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8962.yaml @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-wm8962.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NVIDIA Tegra audio complex with WM8962 CODEC + +maintainers: + - Svyatoslav Ryhel + +allOf: + - $ref: nvidia,tegra-audio-common.yaml# + +properties: + compatible: + items: + - pattern: '^[a-z0-9]+,tegra-audio-wm8962(-[a-z0-9]+)+$' + - const: nvidia,tegra-audio-wm8962 + + 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 + - Speakers + - Int Spk + - Earpiece + - Int Mic + - Headset Mic + - Internal Mic 1 + - Internal Mic 2 + - Headphone + - Headphones + - Headphone Jack + - Mic Jack + + # CODEC Pins + - IN1L + - IN1R + - IN2L + - IN2R + - IN3L + - IN3R + - IN4L + - IN4R + - DMICDAT + - HPOUTL + - HPOUTR + - SPKOUT + - SPKOUTL + - SPKOUTR + +required: + - nvidia,i2s-controller + +unevaluatedProperties: false + +examples: + - | + #include + #include + sound { + compatible = "microsoft,tegra-audio-wm8962-surface-rt", + "nvidia,tegra-audio-wm8962"; + nvidia,model = "Microsoft Surface RT WM8962"; + + nvidia,audio-routing = + "Headphone Jack", "HPOUTR", + "Headphone Jack", "HPOUTL", + "Int Spk", "SPKOUTR", + "Int Spk", "SPKOUTL"; + + nvidia,i2s-controller = <&tegra_i2s1>; + nvidia,audio-codec = <&wm8962>; + + clocks = <&tegra_car TEGRA30_CLK_PLL_A>, + <&tegra_car TEGRA30_CLK_PLL_A_OUT0>, + <&tegra_pmc TEGRA_PMC_CLK_OUT_1>; + clock-names = "pll_a", "pll_a_out0", "mclk"; + }; From 536ffa105e16ca3f6e7de53f9d6059f53d0207f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Schw=C3=B6bel?= Date: Mon, 23 Feb 2026 08:50:50 +0200 Subject: [PATCH 036/359] ASoC: tegra: Support WM8962 by machine driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Wolfson Microelectronics WM8962 codec support to the Tegra ASoC machine driver. The WM8962 codec is found on devices like Microsoft Surface RT and Microsoft Surface 2 Windows RT based tablets. Co-developed-by: Svyatoslav Ryhel Signed-off-by: Svyatoslav Ryhel Signed-off-by: Jonas Schwöbel Link: https://patch.msgid.link/20260223065051.13070-5-clamor95@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/Kconfig | 10 ++ sound/soc/tegra/Makefile | 2 + sound/soc/tegra/tegra_wm8962.c | 165 +++++++++++++++++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 sound/soc/tegra/tegra_wm8962.c diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig index dd58525b7079..fdc954028d62 100644 --- a/sound/soc/tegra/Kconfig +++ b/sound/soc/tegra/Kconfig @@ -229,6 +229,16 @@ config SND_SOC_TEGRA_WM8903 boards using the WM8093 codec. Currently, the supported boards are Harmony, Ventana, Seaboard, Kaen, and Aebl. +config SND_SOC_TEGRA_WM8962 + tristate "SoC Audio support for Tegra boards using a WM8962 codec" + depends on I2C && INPUT && GPIOLIB + select SND_SOC_TEGRA_MACHINE_DRV + select SND_SOC_WM8962 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the WM8962 codec. Currently, the supported boards are + Microsoft Surface RT. + 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 defea7f53f11..3f396c87802e 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -43,9 +43,11 @@ 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-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_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_wm8962.c b/sound/soc/tegra/tegra_wm8962.c new file mode 100644 index 000000000000..31f9d9181595 --- /dev/null +++ b/sound/soc/tegra/tegra_wm8962.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tegra_wm8962.c - Tegra machine ASoC driver for boards using WM8962 codec. + * + * Copyright (C) 2021-2024 Jonas Schwöbel + * Svyatoslav Ryhel + * + * Based on tegra_wm8903 code copyright/by: + * + * Author: Stephen Warren + * Copyright (C) 2010-2012 - 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/wm8962.h" + +#include "tegra_asoc_machine.h" + +static struct snd_soc_jack_pin tegra_wm8962_mic_jack_pins[] = { + { .pin = "Mic Jack", .mask = SND_JACK_MICROPHONE }, +}; + +static unsigned int tegra_wm8962_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 int tegra_wm8962_init(struct snd_soc_pcm_runtime *rtd) +{ + struct tegra_machine *machine = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_card *card = rtd->card; + struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(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_wm8962_mic_jack_pins, + ARRAY_SIZE(tegra_wm8962_mic_jack_pins)); + if (err) { + dev_err(rtd->dev, "Mic Jack creation failed: %d\n", err); + return err; + } + + wm8962_mic_detect(component, machine->mic_jack); + } + + snd_soc_dapm_force_enable_pin(dapm, "MICBIAS"); + + return 0; +} + +static int tegra_wm8962_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 snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + + wm8962_mic_detect(component, NULL); + + return 0; +} + +SND_SOC_DAILINK_DEFS(wm8962_hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8962")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link tegra_wm8962_dai = { + .name = "WM8962", + .stream_name = "WM8962 PCM", + .init = tegra_wm8962_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(wm8962_hifi), +}; + +static struct snd_soc_card snd_soc_tegra_wm8962 = { + .components = "codec:wm8962", + .owner = THIS_MODULE, + .dai_link = &tegra_wm8962_dai, + .num_links = 1, + .remove = tegra_wm8962_remove, + .fully_routed = true, +}; + +static const struct tegra_asoc_data tegra_wm8962_data = { + .mclk_rate = tegra_wm8962_mclk_rate, + .card = &snd_soc_tegra_wm8962, + .add_common_dapm_widgets = true, + .add_common_controls = true, + .add_common_snd_ops = true, + .add_mic_jack = true, + .add_hp_jack = true, +}; + +static const struct of_device_id tegra_wm8962_of_match[] = { + { .compatible = "nvidia,tegra-audio-wm8962", .data = &tegra_wm8962_data }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra_wm8962_of_match); + +static struct platform_driver tegra_wm8962_driver = { + .driver = { + .name = "tegra-wm8962", + .of_match_table = tegra_wm8962_of_match, + .pm = &snd_soc_pm_ops, + }, + .probe = tegra_asoc_machine_probe, +}; +module_platform_driver(tegra_wm8962_driver); + +MODULE_AUTHOR("Jonas Schwöbel "); +MODULE_AUTHOR("Svyatoslav Ryhel "); +MODULE_DESCRIPTION("Tegra+WM8962 machine ASoC driver"); +MODULE_LICENSE("GPL"); From 06dba254de95b16e7793224d29daa5195de2e581 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Mon, 23 Feb 2026 08:50:51 +0200 Subject: [PATCH 037/359] ASoC: dt-bindings: nvidia,tegra-audio-max9808x: document additional board pins Document additional board pins that may be used in routing. Signed-off-by: Svyatoslav Ryhel Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260223065051.13070-6-clamor95@gmail.com Signed-off-by: Mark Brown --- .../bindings/sound/nvidia,tegra-audio-max9808x.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max9808x.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max9808x.yaml index 241d20f3aad0..4957645a8e03 100644 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max9808x.yaml +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max9808x.yaml @@ -35,10 +35,15 @@ properties: items: enum: # Board Connectors + - Speakers - Int Spk + - Headphone + - Headphones - Headphone Jack - Earpiece - Headset Mic + - Mic Jack + - Int Mic - Internal Mic 1 - Internal Mic 2 From 73942a6ea26bd7e02b7c260b8b7aa942397be894 Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Tue, 24 Feb 2026 16:18:05 +0000 Subject: [PATCH 038/359] firmware: cs_dsp: Add API to hibernate the DSP For some parts, the DSP is kept running when in low power mode (hibernation), leaving the firmware ALSA controls enabled, but the registers are inaccessible. Attempts to access volatile firmware controls whilst in this state would produce errors in the kernel log due to a regmap_raw_read() into DSP registers whilst the regmap is in cache_only. To remove this error log, add a hibernating flag to indicate that the controls are inaccessible, so we no longer try to read or write to the registers whilst the regmap is in cache_only. This would still produce an error when trying to read or write to these controls, but this would be a different error (-EPERM instead of -EBUSY), and would not produce a spurious error log in the kernel. Upon wake from hibernation, the control caches are re-synced to the hardware, if the DSP is running. Signed-off-by: Stefan Binding Link: https://patch.msgid.link/20260224161821.93365-2-sbinding@opensource.cirrus.com Reviewed-by: Richard Fitzgerald Signed-off-by: Mark Brown --- drivers/firmware/cirrus/cs_dsp.c | 49 +++++++++++++++++++++++--- include/linux/firmware/cirrus/cs_dsp.h | 3 ++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c index b4f1c01e3b5b..f9d8a883900d 100644 --- a/drivers/firmware/cirrus/cs_dsp.c +++ b/drivers/firmware/cirrus/cs_dsp.c @@ -515,6 +515,7 @@ void cs_dsp_init_debugfs(struct cs_dsp *dsp, struct dentry *debugfs_root) debugfs_create_bool("booted", 0444, root, &dsp->booted); debugfs_create_bool("running", 0444, root, &dsp->running); + debugfs_create_bool("hibernating", 0444, root, &dsp->hibernating); debugfs_create_x32("fw_id", 0444, root, &dsp->fw_id); debugfs_create_x32("fw_version", 0444, root, &dsp->fw_id_version); @@ -703,7 +704,7 @@ int cs_dsp_coeff_write_acked_control(struct cs_dsp_coeff_ctl *ctl, unsigned int lockdep_assert_held(&dsp->pwr_lock); - if (!dsp->running) + if (!dsp->running || dsp->hibernating) return -EPERM; ret = cs_dsp_coeff_base_reg(ctl, ®, 0); @@ -827,7 +828,7 @@ int cs_dsp_coeff_write_ctrl(struct cs_dsp_coeff_ctl *ctl, } ctl->set = 1; - if (ctl->enabled && ctl->dsp->running) + if (ctl->enabled && ctl->dsp->running && !ctl->dsp->hibernating) ret = cs_dsp_coeff_write_ctrl_raw(ctl, off, buf, len); if (ret < 0) @@ -920,12 +921,12 @@ int cs_dsp_coeff_read_ctrl(struct cs_dsp_coeff_ctl *ctl, return -EINVAL; if (ctl->flags & WMFW_CTL_FLAG_VOLATILE) { - if (ctl->enabled && ctl->dsp->running) + if (ctl->enabled && ctl->dsp->running && !ctl->dsp->hibernating) return cs_dsp_coeff_read_ctrl_raw(ctl, off, buf, len); else return -EPERM; } else { - if (!ctl->flags && ctl->enabled && ctl->dsp->running) + if (!ctl->flags && ctl->enabled && ctl->dsp->running && !ctl->dsp->hibernating) ret = cs_dsp_coeff_read_ctrl_raw(ctl, 0, ctl->cache, ctl->len); if (buf != ctl->cache) @@ -1108,6 +1109,44 @@ err_ctl: return ret; } + +/** + * cs_dsp_hibernate() - Disable or enable all controls for a DSP + * @dsp: pointer to DSP structure + * @hibernate: whether to set controls to cache only mode + * + * When @hibernate is true, the DSP is entering hibernation mode where the + * regmap is inaccessible, and all controls become cache only. + * When @hibernate is false, the DSP has exited hibernation mode. If the DSP + * is running, all controls are re-synced to the DSP. + * + */ +void cs_dsp_hibernate(struct cs_dsp *dsp, bool hibernate) +{ + mutex_lock(&dsp->pwr_lock); + + if (!dsp->running) { + cs_dsp_dbg(dsp, "Cannot hibernate, DSP not running\n"); + goto out; + } + + if (dsp->hibernating == hibernate) + goto out; + + cs_dsp_dbg(dsp, "Set hibernating to %d\n", hibernate); + dsp->hibernating = hibernate; + + if (!dsp->hibernating && dsp->running) { + int ret = cs_dsp_coeff_sync_controls(dsp); + + if (ret) + cs_dsp_err(dsp, "Error syncing controls: %d\n", ret); + } +out: + mutex_unlock(&dsp->pwr_lock); +} +EXPORT_SYMBOL_NS_GPL(cs_dsp_hibernate, "FW_CS_DSP"); + struct cs_dsp_coeff_parsed_alg { int id; const u8 *name; @@ -2498,6 +2537,7 @@ int cs_dsp_adsp1_power_up(struct cs_dsp *dsp, goto err_ena; dsp->booted = true; + dsp->hibernating = false; /* Start the core running */ regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, @@ -2776,6 +2816,7 @@ int cs_dsp_power_up(struct cs_dsp *dsp, dsp->ops->disable_core(dsp); dsp->booted = true; + dsp->hibernating = false; mutex_unlock(&dsp->pwr_lock); diff --git a/include/linux/firmware/cirrus/cs_dsp.h b/include/linux/firmware/cirrus/cs_dsp.h index 0ec1cdc5585d..4e3baa557068 100644 --- a/include/linux/firmware/cirrus/cs_dsp.h +++ b/include/linux/firmware/cirrus/cs_dsp.h @@ -179,6 +179,7 @@ struct cs_dsp { bool booted; bool running; + bool hibernating; struct list_head ctl_list; @@ -354,4 +355,6 @@ int cs_dsp_chunk_write(struct cs_dsp_chunk *ch, int nbits, u32 val); int cs_dsp_chunk_flush(struct cs_dsp_chunk *ch); int cs_dsp_chunk_read(struct cs_dsp_chunk *ch, int nbits); +void cs_dsp_hibernate(struct cs_dsp *dsp, bool hibernating); + #endif From 6394a52c90c4abd10c8265983ec1f53207cd283e Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Tue, 24 Feb 2026 16:18:06 +0000 Subject: [PATCH 039/359] ASoC: codecs: wm_adsp: Allow wm_adsp to hibernate without stopping DSP Some parts do not stop the DSP core before runtime suspend, however, this leaves the DSP controls enabled and accessible whilst the part is suspended and the regmap is cache_only. Add new APIs to allow for the DSP to hibernate when the part is suspended, and the DSP is not shut down. Signed-off-by: Stefan Binding Reviewed-by: Richard Fitzgerald Link: https://patch.msgid.link/20260224161821.93365-3-sbinding@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm_adsp.c | 6 ++++++ sound/soc/codecs/wm_adsp.h | 1 + 2 files changed, 7 insertions(+) diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index 2e23848e1dce..d95b54275b5e 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -1100,6 +1100,12 @@ void wm_adsp_stop(struct wm_adsp *dsp) } EXPORT_SYMBOL_GPL(wm_adsp_stop); +void wm_adsp_hibernate(struct wm_adsp *dsp, bool hibernate) +{ + cs_dsp_hibernate(&dsp->cs_dsp, hibernate); +} +EXPORT_SYMBOL_GPL(wm_adsp_hibernate); + int wm_adsp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h index 8035fda71f8d..a9118be793d7 100644 --- a/sound/soc/codecs/wm_adsp.h +++ b/sound/soc/codecs/wm_adsp.h @@ -103,6 +103,7 @@ irqreturn_t wm_halo_wdt_expire(int irq, void *data); int wm_adsp_run(struct wm_adsp *dsp); void wm_adsp_stop(struct wm_adsp *dsp); +void wm_adsp_hibernate(struct wm_adsp *dsp, bool hibernate); int wm_adsp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event); From 4d80c0dbcda551b8b86ff14c6ae93026993970b2 Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Tue, 24 Feb 2026 16:18:07 +0000 Subject: [PATCH 040/359] ASoC: cs35l41: Hibernate wm_adsp on runtime suspend When the CS35L41 driver suspends, it is put into hibernation, and the regmap goes into cache_only, but the firmware is still running, and wm_adsp is not stopped. If userspace attempts to read a firmware control, it will perform a regmap_raw_read() and this will produce an error in the kernel log. To prevent spurious errors, put the DSP into hibernation which prevents access to the hardware for the ALSA controls. Signed-off-by: Stefan Binding Link: https://patch.msgid.link/20260224161821.93365-4-sbinding@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l41.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c index ee56dfceedeb..b2a076706c79 100644 --- a/sound/soc/codecs/cs35l41.c +++ b/sound/soc/codecs/cs35l41.c @@ -1404,6 +1404,7 @@ static int cs35l41_runtime_suspend(struct device *dev) if (!cs35l41->dsp.preloaded || !cs35l41->dsp.cs_dsp.running) return 0; + wm_adsp_hibernate(&cs35l41->dsp, true); cs35l41_enter_hibernate(dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type); regcache_cache_only(cs35l41->regmap, true); @@ -1432,10 +1433,14 @@ static int cs35l41_runtime_resume(struct device *dev) cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap); ret = regcache_sync(cs35l41->regmap); cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap); + + wm_adsp_hibernate(&cs35l41->dsp, false); + if (ret) { dev_err(cs35l41->dev, "Failed to restore register cache: %d\n", ret); return ret; } + cs35l41_init_boost(cs35l41->dev, cs35l41->regmap, &cs35l41->hw_cfg); return 0; From 17c6bf433742e0c1ff5ce175145877c0194e4a7a Mon Sep 17 00:00:00 2001 From: Ricardo Rivera-Matos Date: Tue, 24 Feb 2026 16:18:08 +0000 Subject: [PATCH 041/359] ASoC: cs35l45: Hibernate wm_adsp on runtime suspend When the CS35L45 driver suspends, it is put into hibernation, and the regmap goes into cache_only, but the firmware is still running, and wm_adsp is not stopped. If userspace attempts to read a firmware control, it will perform a regmap_raw_read() and this will produce an error in the kernel log. To prevent spurious errors, put the DSP into hibernation which prevents access to the hardware for the ALSA controls. Signed-off-by: Ricardo Rivera-Matos Signed-off-by: Stefan Binding Link: https://patch.msgid.link/20260224161821.93365-5-sbinding@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l45.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c index 7aa558d6362f..a032bb23b4ac 100644 --- a/sound/soc/codecs/cs35l45.c +++ b/sound/soc/codecs/cs35l45.c @@ -984,6 +984,7 @@ static int cs35l45_runtime_suspend(struct device *dev) if (!cs35l45->dsp.preloaded || !cs35l45->dsp.cs_dsp.running) return 0; + wm_adsp_hibernate(&cs35l45->dsp, true); cs35l45_enter_hibernate(cs35l45); regcache_cache_only(cs35l45->regmap, true); @@ -1014,6 +1015,8 @@ static int cs35l45_runtime_resume(struct device *dev) if (ret != 0) dev_warn(cs35l45->dev, "regcache_sync failed: %d\n", ret); + wm_adsp_hibernate(&cs35l45->dsp, false); + /* Clear global error status */ regmap_clear_bits(cs35l45->regmap, CS35L45_ERROR_RELEASE, CS35L45_GLOBAL_ERR_RLS_MASK); regmap_set_bits(cs35l45->regmap, CS35L45_ERROR_RELEASE, CS35L45_GLOBAL_ERR_RLS_MASK); From 98eb42c7de6b0185c914df4cca61b49ff76821ee Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Wed, 25 Feb 2026 21:50:02 +0800 Subject: [PATCH 042/359] ASoC: add snd_soc_lookup_component_by_name helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a helper to help user to get the component by name. Signed-off-by: Bard Liao Reviewed-by: Charles Keepax Reviewed-by: Péter Ujfalusi Link: https://patch.msgid.link/20260225135004.2322987-2-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- include/sound/soc.h | 1 + sound/soc/soc-core.c | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/include/sound/soc.h b/include/sound/soc.h index 7d8376c8e1be..1a486153dc76 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -465,6 +465,7 @@ 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-core.c b/sound/soc/soc-core.c index d0fffef65daf..f41ca5109a6a 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -404,6 +404,19 @@ struct snd_soc_component *snd_soc_lookup_component(struct device *dev, } 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 c5ae3d8bc968a28aaeefbb772ec42e50cf3a15f0 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Wed, 25 Feb 2026 21:50:03 +0800 Subject: [PATCH 043/359] ASoC: soc_sdw_utils: partial match the codec name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, we can set codec name in the dai info which will be set as the codec component name in a DAI link. However, the codec name may not be fixed. For example, there is an index in a SDCA codec name and that is not fixed. Lookup the fixed codec name string from the component list to get the right component name to ensure the DAI link will bind to the right codec component. Signed-off-by: Bard Liao Reviewed-by: Charles Keepax Reviewed-by: Péter Ujfalusi Link: https://patch.msgid.link/20260225135004.2322987-3-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_utils.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 0e67d9f34cba..821eb9720f6f 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -1215,8 +1215,18 @@ const char *asoc_sdw_get_codec_name(struct device *dev, const struct snd_soc_acpi_link_adr *adr_link, int adr_index) { - if (dai_info->codec_name) - return devm_kstrdup(dev, dai_info->codec_name, GFP_KERNEL); + if (dai_info->codec_name) { + struct snd_soc_component *component; + + component = snd_soc_lookup_component_by_name(dai_info->codec_name); + if (component) { + dev_dbg(dev, "%s found component %s for codec_name %s\n", + __func__, component->name, dai_info->codec_name); + return devm_kstrdup(dev, component->name, GFP_KERNEL); + } else { + return devm_kstrdup(dev, dai_info->codec_name, GFP_KERNEL); + } + } return _asoc_sdw_get_codec_name(dev, adr_link, adr_index); } @@ -1528,7 +1538,17 @@ int asoc_sdw_parse_sdw_endpoints(struct snd_soc_card *card, return -EINVAL; for (j = 0; j < codec_info->aux_num; j++) { - soc_aux->dlc.name = codec_info->auxs[j].codec_name; + struct snd_soc_component *component; + + component = snd_soc_lookup_component_by_name(codec_info->auxs[j].codec_name); + if (component) { + dev_dbg(dev, "%s found component %s for aux name %s\n", + __func__, component->name, + codec_info->auxs[j].codec_name); + soc_aux->dlc.name = component->name; + } else { + soc_aux->dlc.name = codec_info->auxs[j].codec_name; + } soc_aux++; } From 34b4fc44e4f904fbb81335d53163ffdcb0180000 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Wed, 25 Feb 2026 21:50:04 +0800 Subject: [PATCH 044/359] ASoC: soc_sdw_utils: remove index from sdca codec name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The index is not fixed and it will lead to the DAI link can't bind the codec component with the name when the index is different from the predefined one. Signed-off-by: Bard Liao Reviewed-by: Charles Keepax Reviewed-by: Péter Ujfalusi Link: https://patch.msgid.link/20260225135004.2322987-4-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_utils.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 821eb9720f6f..e624988958e8 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -729,7 +729,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dais = { { .direction = {true, false}, - .codec_name = "snd_soc_sdca.UAJ.1", + .codec_name = "snd_soc_sdca.UAJ", .dai_name = "IT 41", .dai_type = SOC_SDW_DAI_TYPE_JACK, .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, @@ -745,7 +745,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, { .direction = {false, true}, - .codec_name = "snd_soc_sdca.UAJ.1", + .codec_name = "snd_soc_sdca.UAJ", .dai_name = "OT 36", .dai_type = SOC_SDW_DAI_TYPE_JACK, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, @@ -754,7 +754,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 3, .auxs = { { - .codec_name = "snd_soc_sdca.HID.2", + .codec_name = "snd_soc_sdca.HID", }, }, .aux_num = 1, From f48e7a246a567e3764112e2463274c479d95cd96 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 3 Mar 2026 02:20:33 +0000 Subject: [PATCH 045/359] ASoC: soc-core: Use guard()/scoped_guard() for mutex lock Replace the manual mutex lock/unlock pairs with guard()/scoped_guard(). Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/875x7dac26.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 68 ++++++++++++++++---------------------------- 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index f41ca5109a6a..e70bf22db5c9 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -167,15 +167,12 @@ static int dai_list_show(struct seq_file *m, void *v) { struct snd_soc_component *component; struct snd_soc_dai *dai; - - mutex_lock(&client_mutex); + guard(mutex)(&client_mutex); for_each_component(component) for_each_component_dais(component, dai) seq_printf(m, "%s\n", dai->name); - mutex_unlock(&client_mutex); - return 0; } DEFINE_SHOW_ATTRIBUTE(dai_list); @@ -183,14 +180,11 @@ DEFINE_SHOW_ATTRIBUTE(dai_list); static int component_list_show(struct seq_file *m, void *v) { struct snd_soc_component *component; - - mutex_lock(&client_mutex); + guard(mutex)(&client_mutex); for_each_component(component) seq_printf(m, "%s\n", component->name); - mutex_unlock(&client_mutex); - return 0; } DEFINE_SHOW_ATTRIBUTE(component_list); @@ -394,13 +388,9 @@ EXPORT_SYMBOL_GPL(snd_soc_lookup_component_nolocked); struct snd_soc_component *snd_soc_lookup_component(struct device *dev, const char *driver_name) { - struct snd_soc_component *component; + guard(mutex)(&client_mutex); - mutex_lock(&client_mutex); - component = snd_soc_lookup_component_nolocked(dev, driver_name); - mutex_unlock(&client_mutex); - - return component; + return snd_soc_lookup_component_nolocked(dev, driver_name); } EXPORT_SYMBOL_GPL(snd_soc_lookup_component); @@ -950,13 +940,9 @@ EXPORT_SYMBOL_GPL(snd_soc_find_dai); struct snd_soc_dai *snd_soc_find_dai_with_mutex( const struct snd_soc_dai_link_component *dlc) { - struct snd_soc_dai *dai; + guard(mutex)(&client_mutex); - mutex_lock(&client_mutex); - dai = snd_soc_find_dai(dlc); - mutex_unlock(&client_mutex); - - return dai; + return snd_soc_find_dai(dlc); } EXPORT_SYMBOL_GPL(snd_soc_find_dai_with_mutex); @@ -2590,7 +2576,7 @@ int snd_soc_register_card(struct snd_soc_card *card) mutex_init(&card->dapm_mutex); mutex_init(&card->pcm_mutex); - mutex_lock(&client_mutex); + guard(mutex)(&client_mutex); if (card->devres_dev) { ret = devm_snd_soc_bind_card(card->devres_dev, card); @@ -2602,8 +2588,6 @@ int snd_soc_register_card(struct snd_soc_card *card) ret = snd_soc_bind_card(card); } - mutex_unlock(&client_mutex); - return ret; } EXPORT_SYMBOL_GPL(snd_soc_register_card); @@ -2616,10 +2600,11 @@ EXPORT_SYMBOL_GPL(snd_soc_register_card); */ void snd_soc_unregister_card(struct snd_soc_card *card) { - mutex_lock(&client_mutex); + guard(mutex)(&client_mutex); + snd_soc_unbind_card(card); list_del(&card->list); - mutex_unlock(&client_mutex); + dev_dbg(card->dev, "ASoC: Unregistered card '%s'\n", card->name); } EXPORT_SYMBOL_GPL(snd_soc_unregister_card); @@ -2896,8 +2881,7 @@ int snd_soc_add_component(struct snd_soc_component *component, struct snd_soc_card *card, *c; int ret; int i; - - mutex_lock(&client_mutex); + guard(mutex)(&client_mutex); if (component->driver->endianness) { for (i = 0; i < num_dai; i++) { @@ -2931,7 +2915,6 @@ err_cleanup: if (ret < 0) snd_soc_del_component_unlocked(component); - mutex_unlock(&client_mutex); return ret; } EXPORT_SYMBOL_GPL(snd_soc_add_component); @@ -2971,7 +2954,8 @@ void snd_soc_unregister_component_by_driver(struct device *dev, if (component_driver) driver_name = component_driver->name; - mutex_lock(&client_mutex); + guard(mutex)(&client_mutex); + while (1) { struct snd_soc_component *component = snd_soc_lookup_component_nolocked(dev, driver_name); @@ -2980,7 +2964,6 @@ void snd_soc_unregister_component_by_driver(struct device *dev, snd_soc_del_component_unlocked(component); } - mutex_unlock(&client_mutex); } EXPORT_SYMBOL_GPL(snd_soc_unregister_component_by_driver); @@ -3516,7 +3499,6 @@ EXPORT_SYMBOL_GPL(snd_soc_get_stream_cpu); int snd_soc_get_dai_id(struct device_node *ep) { - struct snd_soc_component *component; struct snd_soc_dai_link_component dlc = { .of_node = of_graph_get_port_parent(ep), }; @@ -3530,11 +3512,13 @@ int snd_soc_get_dai_id(struct device_node *ep) * Then, it should have .of_xlate_dai_id */ ret = -ENOTSUPP; - mutex_lock(&client_mutex); - component = soc_find_component(&dlc); - if (component) - ret = snd_soc_component_of_xlate_dai_id(component, ep); - mutex_unlock(&client_mutex); + + scoped_guard(mutex, &client_mutex) { + struct snd_soc_component *component = soc_find_component(&dlc); + + if (component) + ret = snd_soc_component_of_xlate_dai_id(component, ep); + } of_node_put(dlc.of_node); @@ -3546,8 +3530,8 @@ int snd_soc_get_dlc(const struct of_phandle_args *args, struct snd_soc_dai_link_ { struct snd_soc_component *pos; int ret = -EPROBE_DEFER; + guard(mutex)(&client_mutex); - mutex_lock(&client_mutex); for_each_component(pos) { struct device_node *component_of_node = soc_component_to_node(pos); @@ -3602,7 +3586,6 @@ int snd_soc_get_dlc(const struct of_phandle_args *args, struct snd_soc_dai_link_ if (ret == 0) dlc->of_node = args->np; - mutex_unlock(&client_mutex); return ret; } EXPORT_SYMBOL_GPL(snd_soc_get_dlc); @@ -3657,17 +3640,14 @@ struct snd_soc_dai *snd_soc_get_dai_via_args(const struct of_phandle_args *dai_a { struct snd_soc_dai *dai; struct snd_soc_component *component; + guard(mutex)(&client_mutex); - mutex_lock(&client_mutex); for_each_component(component) { for_each_component_dais(component, dai) if (snd_soc_is_match_dai_args(dai->driver->dai_args, dai_args)) - goto found; + return dai; } - dai = NULL; -found: - mutex_unlock(&client_mutex); - return dai; + return NULL; } EXPORT_SYMBOL_GPL(snd_soc_get_dai_via_args); From 2adac914c72b6cb5aba2612f49050c82aecd498e Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 3 Mar 2026 13:14:40 +0000 Subject: [PATCH 046/359] ASoC: cs35l56-test: Add test cases without onchip pulls defined Add parameters to cs35l56_test_onchip_spkid_cases[] for the ACPI defining onchip GPIOs but not defining any pull settings. Pull settings are optional because the pull should normally be provided by an external resistor. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260303131440.574690-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56-test.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/soc/codecs/cs35l56-test.c b/sound/soc/codecs/cs35l56-test.c index b6c8c08e3ade..decedf76847d 100644 --- a/sound/soc/codecs/cs35l56-test.c +++ b/sound/soc/codecs/cs35l56-test.c @@ -541,18 +541,28 @@ static void cs35l56_test_gpio_param_desc(const struct cs35l56_test_param *param, } static const struct cs35l56_test_param cs35l56_test_onchip_spkid_cases[] = { + { .spkid_gpios = { 1, -1 }, .spkid_pulls = { -1, -1 }, }, + { .spkid_gpios = { 1, -1 }, .spkid_pulls = { -1, -1 }, }, { .spkid_gpios = { 1, -1 }, .spkid_pulls = { 1, -1 }, }, { .spkid_gpios = { 1, -1 }, .spkid_pulls = { 2, -1 }, }, + { .spkid_gpios = { 7, -1 }, .spkid_pulls = { -1, -1 }, }, + { .spkid_gpios = { 7, -1 }, .spkid_pulls = { -1, -1 }, }, { .spkid_gpios = { 7, -1 }, .spkid_pulls = { 1, -1 }, }, { .spkid_gpios = { 7, -1 }, .spkid_pulls = { 2, -1 }, }, + { .spkid_gpios = { 1, 7, -1 }, .spkid_pulls = { -1, -1, -1 }, }, + { .spkid_gpios = { 1, 7, -1 }, .spkid_pulls = { -1, -1, -1 }, }, { .spkid_gpios = { 1, 7, -1 }, .spkid_pulls = { 1, 1, -1 }, }, { .spkid_gpios = { 1, 7, -1 }, .spkid_pulls = { 2, 2, -1 }, }, + { .spkid_gpios = { 7, 1, -1 }, .spkid_pulls = { -1, -1, -1 }, }, + { .spkid_gpios = { 7, 1, -1 }, .spkid_pulls = { -1, -1, -1 }, }, { .spkid_gpios = { 7, 1, -1 }, .spkid_pulls = { 1, 1, -1 }, }, { .spkid_gpios = { 7, 1, -1 }, .spkid_pulls = { 2, 2, -1 }, }, + { .spkid_gpios = { 3, 7, 1, -1 }, .spkid_pulls = { -1, -1, -1, -1 }, }, + { .spkid_gpios = { 3, 7, 1, -1 }, .spkid_pulls = { -1, -1, -1, -1 }, }, { .spkid_gpios = { 3, 7, 1, -1 }, .spkid_pulls = { 1, 1, 1, -1 }, }, { .spkid_gpios = { 3, 7, 1, -1 }, .spkid_pulls = { 2, 2, 2, -1 }, }, }; From 41d78cb724f4b40b7548af420ccfe524b14023bb Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 4 Mar 2026 03:47:56 +0800 Subject: [PATCH 047/359] Revert "ALSA: usb: Increase volume range that triggers a warning" UAC uses 2 bytes to store volume values, so the maximum volume range is 0xFFFF (65535, val = -32768/32767/1). The reverted commit bumpped the range of triggering the warning to > 65535, effectively making the range check a no-op. It didn't fix anything but covered any potential problems and deviated from the original intention of the range check. This reverts commit 6b971191fcfc9e3c2c0143eea22534f1f48dbb62. Fixes: 6b971191fcfc ("ALSA: usb: Increase volume range that triggers a warning") Cc: stable@vger.kernel.org Signed-off-by: Rong Zhang Acked-by: Arun Raghavan Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260303194805.266158-2-i@rong.moe --- sound/usb/mixer.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index ac8c71ba9483..df0d3df9c7ec 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1813,10 +1813,11 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, range = (cval->max - cval->min) / cval->res; /* - * There are definitely devices with a range of ~20,000, so let's be - * conservative and allow for a bit more. + * Are there devices with volume range more than 255? I use a bit more + * to be sure. 384 is a resolution magic number found on Logitech + * devices. It will definitively catch all buggy Logitech devices. */ - if (range > 65535) { + if (range > 384) { usb_audio_warn(mixer->chip, "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.", range); From 1060dbbbb2f260e4755dbd8d2f53f5a1894397d8 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 4 Mar 2026 03:47:57 +0800 Subject: [PATCH 048/359] ALSA: usb-audio: Add helper function for volume range checks When a potentially insane volume range is found, the volume control parameters will be printed in WARN level instead of DEBUG level. Currently, it's done by emitting a open-coded usb_audio_warn() in the corresponding check. The following changes are about to add more checks against volumen ranges. As the first step, extract the current check logic into a helper function to improve readability. No functional change intended. Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260303194805.266158-3-i@rong.moe --- sound/usb/mixer.c | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index df0d3df9c7ec..f52ca0d7e665 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1660,6 +1660,27 @@ static const struct usb_feature_control_info *get_feature_control_info(int contr return NULL; } +static bool check_insane_volume_range(struct usb_mixer_interface *mixer, + struct snd_kcontrol *kctl, + struct usb_mixer_elem_info *cval) +{ + int range = (cval->max - cval->min) / cval->res; + + /* + * Are there devices with volume range more than 255? I use a bit more + * to be sure. 384 is a resolution magic number found on Logitech + * devices. It will definitively catch all buggy Logitech devices. + */ + if (range > 384) { + usb_audio_warn(mixer->chip, + "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.", + range); + return true; + } + + return false; +} + static void __build_feature_ctl(struct usb_mixer_interface *mixer, const struct usbmix_name_map *imap, unsigned int ctl_mask, int control, @@ -1673,7 +1694,6 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, struct snd_kcontrol *kctl; struct usb_mixer_elem_info *cval; const struct usbmix_name_map *map; - unsigned int range; if (control == UAC_FU_GRAPHIC_EQUALIZER) { /* FIXME: not supported yet */ @@ -1811,25 +1831,16 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, snd_usb_mixer_fu_apply_quirk(mixer, cval, unitid, kctl); - range = (cval->max - cval->min) / cval->res; - /* - * Are there devices with volume range more than 255? I use a bit more - * to be sure. 384 is a resolution magic number found on Logitech - * devices. It will definitively catch all buggy Logitech devices. - */ - if (range > 384) { - usb_audio_warn(mixer->chip, - "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.", - range); - usb_audio_warn(mixer->chip, - "[%d] FU [%s] ch = %d, val = %d/%d/%d", + if (check_insane_volume_range(mixer, kctl, cval)) { + usb_audio_warn(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", cval->head.id, kctl->id.name, cval->channels, cval->min, cval->max, cval->res); + } else { + usb_audio_dbg(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", + cval->head.id, kctl->id.name, cval->channels, + cval->min, cval->max, cval->res); } - usb_audio_dbg(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", - cval->head.id, kctl->id.name, cval->channels, - cval->min, cval->max, cval->res); snd_usb_mixer_add_control(&cval->head, kctl); } From 52dc4b190a31d5a5f43aadc51f5297048bfb6d52 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 4 Mar 2026 03:47:58 +0800 Subject: [PATCH 049/359] ALSA: usb-audio: Improve volume range checks Currently the volume range check is only meant to discover quirky microphone on webcam devices and facing these issues: - The check is only meaningful for dB volume, but it doesn't check if the TLV callback is the corresponding one - A common quirky pattern "val = 0/100/1" doesn't trigger any warning - Some modern devices trigger the check, but they are legit - The warning message doesn't apply to some quirky messages with linear volume - The term "range" in the warning message is confusing. At readers' first glance it should be (max - min), but it turns out to be ((max - min) / res) Solve these issues by improving the checking logic to: - Ignore mixers with non-dB TLV - Warn on unlikely small volume ranges (max - min < 256) - Add some heuristics to determine if the volume range is unlikely big - Rephrase the warning message to mention linear volume - Rephrase the warning message in correct wording Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260303194805.266158-4-i@rong.moe --- sound/usb/mixer.c | 54 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index f52ca0d7e665..7007e0c9489b 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1664,20 +1664,62 @@ static bool check_insane_volume_range(struct usb_mixer_interface *mixer, struct snd_kcontrol *kctl, struct usb_mixer_elem_info *cval) { - int range = (cval->max - cval->min) / cval->res; + int range, steps, threshold; /* - * Are there devices with volume range more than 255? I use a bit more - * to be sure. 384 is a resolution magic number found on Logitech - * devices. It will definitively catch all buggy Logitech devices. + * If a device quirk has overrode our TLV callback, no warning should + * be generated since our checks are only meaningful for dB volume. */ - if (range > 384) { + if (!(kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) || + kctl->tlv.c != snd_usb_mixer_vol_tlv) + return false; + + /* + * Meaningless volume control capability (<1dB). This should cover + * devices mapping their volume to val = 0/100/1, which are very likely + * to be quirky. + */ + range = cval->max - cval->min; + if (range < 256) { usb_audio_warn(mixer->chip, - "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.", + "Warning! Unlikely small volume range (=%u), linear volume or custom curve?", range); return true; } + steps = range / cval->res; + + /* + * There are definitely devices with ~20,000 ranges (e.g., HyperX Cloud + * III with val = -18944/0/1), so we use some heuristics here: + * + * min < 0 < max: Attenuator + amplifier? Likely to be sane + * + * min < 0 = max: DSP? Voltage attenuator with FW conversion to dB? + * Likely to be sane + * + * min < max < 0: Measured values? Neutral + * + * min = 0 < max: Oversimplified FW conversion? Linear volume? Likely to + * be quirky (e.g., MV-SILICON) + * + * 0 < min < max: Amplifier with fixed gains? Likely to be quirky + * (e.g., Logitech webcam) + */ + if (cval->min < 0 && 0 <= cval->max) + threshold = 24576; /* 65535 * (3 / 8) */ + else if (cval->min < cval->max && cval->max < 0) + threshold = 1024; + else + threshold = 384; + + if (steps > threshold) { + usb_audio_warn(mixer->chip, + "Warning! Unlikely big volume step count (=%u), linear volume or wrong cval->res?", + steps); + return true; + } + return false; } From 30f68d090c0ee55a7c9b422e65d43b9ff68434c8 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 4 Mar 2026 03:47:59 +0800 Subject: [PATCH 050/359] ALSA: usb-audio: Support string-descriptor-based quirk table entry Some quirky devices do not have a unique VID/PID. Matching them using DEVICE_FLG() or VENDOR_FLG() may result in conflicts. Add two new macros DEVICE_STRING_FLG() and VENDOR_STRING_FLG() to match USB string descriptors (manufacturer and/or product) in addition to VID and/or PID, so that we can deconflict these devices safely. No functional change intended. Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260303194805.266158-5-i@rong.moe --- sound/usb/quirks.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index d54a1a44a69b..d365eb41910a 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2,8 +2,11 @@ /* */ +#include +#include #include #include +#include #include #include #include @@ -2135,16 +2138,39 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip, /* * driver behavior quirk flags */ +struct usb_string_match { + const char *manufacturer; + const char *product; +}; + struct usb_audio_quirk_flags_table { u32 id; u32 flags; + const struct usb_string_match *usb_string_match; }; #define DEVICE_FLG(vid, pid, _flags) \ { .id = USB_ID(vid, pid), .flags = (_flags) } #define VENDOR_FLG(vid, _flags) DEVICE_FLG(vid, 0, _flags) +/* Use as a last resort if using DEVICE_FLG() is prone to VID/PID conflicts. */ +#define DEVICE_STRING_FLG(vid, pid, _manufacturer, _product, _flags) \ +{ \ + .id = USB_ID(vid, pid), \ + .usb_string_match = &(const struct usb_string_match) { \ + .manufacturer = _manufacturer, \ + .product = _product, \ + }, \ + .flags = (_flags), \ +} + +/* Use as a last resort if using VENDOR_FLG() is prone to VID conflicts. */ +#define VENDOR_STRING_FLG(vid, _manufacturer, _flags) \ + DEVICE_STRING_FLG(vid, 0, _manufacturer, NULL, _flags) + static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + /* Device and string descriptor matches */ + /* Device matches */ DEVICE_FLG(0x001f, 0x0b21, /* AB13X USB Audio */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), @@ -2416,6 +2442,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */ QUIRK_FLAG_ALIGN_TRANSFER), + /* Vendor and string descriptor matches */ + /* Vendor matches */ VENDOR_FLG(0x045e, /* MS Lifecam */ QUIRK_FLAG_GET_SAMPLE_RATE), @@ -2560,14 +2588,64 @@ void snd_usb_apply_flag_dbg(const char *reason, } } +#define USB_STRING_SIZE 128 + +static char *snd_usb_get_string(struct snd_usb_audio *chip, int id) +{ + char *buf; + int ret; + + /* + * Devices without the corresponding string descriptor. + * This is non-fatal as *_STRING_FLG have nothing to do in this case. + */ + if (id == 0) + return ERR_PTR(-ENODATA); + + buf = kmalloc(USB_STRING_SIZE, GFP_KERNEL); + if (buf == NULL) + return ERR_PTR(-ENOMEM); + + ret = usb_string(chip->dev, id, buf, USB_STRING_SIZE); + if (ret < 0) { + usb_audio_warn(chip, "failed to get string for id%d: %d\n", id, ret); + kfree(buf); + return ERR_PTR(ret); + } + + return buf; +} + void snd_usb_init_quirk_flags_table(struct snd_usb_audio *chip) { const struct usb_audio_quirk_flags_table *p; + char *manufacturer __free(kfree) = NULL; + char *product __free(kfree) = NULL; for (p = quirk_flags_table; p->id; p++) { if (chip->usb_id == p->id || (!USB_ID_PRODUCT(p->id) && USB_ID_VENDOR(chip->usb_id) == USB_ID_VENDOR(p->id))) { + /* Handle DEVICE_STRING_FLG/VENDOR_STRING_FLG. */ + if (p->usb_string_match && p->usb_string_match->manufacturer) { + if (!manufacturer) { + manufacturer = snd_usb_get_string(chip, + chip->dev->descriptor.iManufacturer); + } + if (IS_ERR_OR_NULL(manufacturer) || + strcmp(p->usb_string_match->manufacturer, manufacturer)) + continue; + } + if (p->usb_string_match && p->usb_string_match->product) { + if (!product) { + product = snd_usb_get_string(chip, + chip->dev->descriptor.iProduct); + } + if (IS_ERR_OR_NULL(product) || + strcmp(p->usb_string_match->product, product)) + continue; + } + snd_usb_apply_flag_dbg("builtin table", chip, p->flags); chip->quirk_flags |= p->flags; return; From b13031ca112a922352e585ab7220c0a99979f328 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 4 Mar 2026 03:48:00 +0800 Subject: [PATCH 051/359] ALSA: usb-audio: Deconflict VID between Focusrite Novation & MV-SILICON MV-SILICON is a SoC manufacturer producing multifunctional audio SoCs. Many budget-oriented OEM devices are built on top of them. However, some of them are just too budget-constrained that their manufacturers didn't even have a USB VID and simply picked a random VID. Some OEMs unfortunately picked the VID of Focusrite Novation (0x1235), resulting in VID conflicts as we had defined a VENDOR_FLG() for the latter. Add a VENDOR_STRING_FLG() for MV-SILICON to stop the matching procedure for these quirky devices, so that quirk flags for Focusrite Novation won't be accidentally applied on them. Quirky device samples: usb 7-1: New USB device found, idVendor=1235, idProduct=0003, bcdDevice= 1.00 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: G1 usb 7-1: Manufacturer: MV-SILICON usb 7-1: SerialNumber: 20190808 usb 7-1: New USB device found, idVendor=1235, idProduct=0003, bcdDevice= 1.00 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: mvsilicon B1 usb audio usb 7-1: Manufacturer: MV-SILICON usb 7-1: SerialNumber: 20190808 usb 1-1.2: New USB device found, idVendor=1235, idProduct=0002, bcdDevice= 1.00 usb 1-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=... usb 1-1.2: Product: V8 usb 1-1.2: Manufacturer: MV-SILICON usb 1-1.2: SerialNumber: ... * https://github.com/linuxhw/Dmesg/blob/main/Desktop/Others/Intel/Intel%20X79/96ED1CC44499/LINUXMINT-19.3/5.0.0-32-GENERIC/X86_64/5BE1E4C74C#L1122 usb 2-1.6: New USB device found, idVendor=1235, idProduct=0002, bcdDevice= 1.00 usb 2-1.6: New USB device strings: Mfr=1, Product=2, SerialNumber=... usb 2-1.6: Product: V9 usb 2-1.6: Manufacturer: MV-SILICON usb 2-1.6: SerialNumber: ... * https://github.com/linuxhw/Dmesg/blob/main/Desktop/Hewlett-Packard/ProLiant/ProLiant%20ML110%20G6/79B1D707316A/KUBUNTU-21.04/5.11.0-33-GENERIC/X86_64/A43F59C4AB#L1009 Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260303194805.266158-6-i@rong.moe --- sound/usb/quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index d365eb41910a..00d1a7c2260e 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2443,6 +2443,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_ALIGN_TRANSFER), /* Vendor and string descriptor matches */ + VENDOR_STRING_FLG(0x1235, /* Conflict with Focusrite Novation */ + "MV-SILICON", + 0), /* Stop matching */ /* Vendor matches */ VENDOR_FLG(0x045e, /* MS Lifecam */ From 3787a6a10e707fca1a61d364a7c256c59823ed59 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 4 Mar 2026 03:48:01 +0800 Subject: [PATCH 052/359] ALSA: usb-audio: Add QUIRK_FLAG_MIXER_{PLAYBACK,CAPTURE}_LINEAR_VOL Some quirky devices tune their volume by linearly tuning the voltage level (linear volume). In other words, such devices has a linear TLV mapping of DECLARE_TLV_DB_LINEAR(scale, TLV_DB_GAIN_MUTE, 0). Add quirk flags MIXER_PLAYBACK_LINEAR_VOL and MIXER_CAPTURE_LINEAR_VOL to represent this case respectively for playback and capture mixers. No functional change intended. Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260303194805.266158-7-i@rong.moe --- Documentation/sound/alsa-configuration.rst | 7 +++++ sound/usb/mixer_quirks.c | 34 ++++++++++++++++++++++ sound/usb/quirks.c | 2 ++ sound/usb/usbaudio.h | 12 ++++++++ 4 files changed, 55 insertions(+) diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst index 55b845d38236..f75f08763941 100644 --- a/Documentation/sound/alsa-configuration.rst +++ b/Documentation/sound/alsa-configuration.rst @@ -2376,6 +2376,13 @@ quirk_flags Skip the probe-time interface setup (usb_set_interface, init_pitch, init_sample_rate); redundant with snd_usb_endpoint_prepare() at stream-open time + * bit 27: ``mixer_playback_linear_vol`` + Set linear volume mapping for devices where the playback volume + control value is mapped to voltage (instead of dB) level linearly. + In short: ``x(raw) = (raw - raw_min) / (raw_max - raw_min)``; + ``V(x) = k * x``; ``dB(x) = 20 * log10(x)``. Overrides bit 24 + * bit 28: ``mixer_capture_linear_vol`` + Similar to bit 27 but for capture streams. Overrides bit 25 This module supports multiple devices, autoprobe and hotplugging. diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 11e205da7964..539044c0c644 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -4634,6 +4634,25 @@ triggered: usb_audio_dbg(chip, "something wrong in kctl name %s\n", id->name); } +static void snd_usb_mixer_fu_quirk_linear_scale(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, + struct snd_kcontrol *kctl) +{ + static const DECLARE_TLV_DB_LINEAR(scale, TLV_DB_GAIN_MUTE, 0); + + if (cval->min_mute) { + /* + * We are clearing SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, + * resulting in min_mute being a no-op. + */ + usb_audio_warn(mixer->chip, "LINEAR_VOL overrides MIN_MUTE\n"); + } + + kctl->tlv.p = scale; + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; +} + void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, struct usb_mixer_elem_info *cval, int unitid, struct snd_kcontrol *kctl) @@ -4660,6 +4679,21 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, "applying capture min mute quirk\n"); cval->min_mute = 1; } + + if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL) + if (cval->control == UAC_FU_VOLUME && strstr(kctl->id.name, "Playback")) { + usb_audio_info(mixer->chip, + "applying playback linear volume quirk\n"); + snd_usb_mixer_fu_quirk_linear_scale(mixer, cval, kctl); + } + + if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL) + if (cval->control == UAC_FU_VOLUME && strstr(kctl->id.name, "Capture")) { + usb_audio_info(mixer->chip, + "applying capture linear volume quirk\n"); + snd_usb_mixer_fu_quirk_linear_scale(mixer, cval, kctl); + } + /* ALSA-ify some Plantronics headset control names */ if (USB_ID_VENDOR(mixer->chip->usb_id) == 0x047f && (cval->control == UAC_FU_MUTE || cval->control == UAC_FU_VOLUME)) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 00d1a7c2260e..7a5cec9cc4bd 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2543,6 +2543,8 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { QUIRK_STRING_ENTRY(MIXER_PLAYBACK_MIN_MUTE), QUIRK_STRING_ENTRY(MIXER_CAPTURE_MIN_MUTE), QUIRK_STRING_ENTRY(SKIP_IFACE_SETUP), + QUIRK_STRING_ENTRY(MIXER_PLAYBACK_LINEAR_VOL), + QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), NULL }; diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 085530cf62d9..58fd07f8c3c9 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -228,6 +228,14 @@ extern bool snd_usb_skip_validation; * Skip the probe-time interface setup (usb_set_interface, * init_pitch, init_sample_rate); redundant with * snd_usb_endpoint_prepare() at stream-open time + * QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL + * Set linear volume mapping for devices where the playback volume control + * value is mapped to voltage (instead of dB) level linearly. In short: + * x(raw) = (raw - raw_min) / (raw_max - raw_min); V(x) = k * x; + * dB(x) = 20 * log10(x). Overrides QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE + * QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL + * Similar to QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL, but for capture streams. + * Overrides QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE */ enum { @@ -258,6 +266,8 @@ enum { QUIRK_TYPE_MIXER_PLAYBACK_MIN_MUTE = 24, QUIRK_TYPE_MIXER_CAPTURE_MIN_MUTE = 25, QUIRK_TYPE_SKIP_IFACE_SETUP = 26, + QUIRK_TYPE_MIXER_PLAYBACK_LINEAR_VOL = 27, + QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, /* Please also edit snd_usb_audio_quirk_flag_names */ }; @@ -290,5 +300,7 @@ enum { #define QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE QUIRK_FLAG(MIXER_PLAYBACK_MIN_MUTE) #define QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE QUIRK_FLAG(MIXER_CAPTURE_MIN_MUTE) #define QUIRK_FLAG_SKIP_IFACE_SETUP QUIRK_FLAG(SKIP_IFACE_SETUP) +#define QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL QUIRK_FLAG(MIXER_PLAYBACK_LINEAR_VOL) +#define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) #endif /* __USBAUDIO_H */ From f510f3bacc2f0f862bb4b878e80e5519cec3419e Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 4 Mar 2026 03:48:02 +0800 Subject: [PATCH 053/359] ALSA: usb-audio: Add linear volume quirk for Hotone Audio Pulze Mini Hotone Audio Pulze Mini is a modeling amplifier with UAC interface. Its Playback and Capture mixers use linear volume with val = 0/100/1. Add a quirk table entry matching VID/PID=0x84ef/0x0082 and applying linear volume quirk flags, so that it can work properly. Quirky device sample: usb 7-1: New USB device found, idVendor=84ef, idProduct=0082, bcdDevice= 1.03 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: Pulze Mini usb 7-1: Manufacturer: Hotone Audio usb 7-1: SerialNumber: 20240807 Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260303194805.266158-8-i@rong.moe --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 7a5cec9cc4bd..17f6be4d2350 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2441,6 +2441,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_ALIGN_TRANSFER), DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */ QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x84ef, 0x0082, /* Hotone Audio Pulze Mini */ + QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL | QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL), /* Vendor and string descriptor matches */ VENDOR_STRING_FLG(0x1235, /* Conflict with Focusrite Novation */ From dfd4b0d46e774d7fbd23a438ead45de08bde783e Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 4 Mar 2026 03:48:03 +0800 Subject: [PATCH 054/359] ALSA: usb-audio: Apply linear volume quirk on MV-SILICON devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MV-SILICON is a SoC manufacturer producing multifunctional audio SoCs. Many devices built on top of their SDK share a common quirk that the Playback and Capture mixers use linear volume with val = 0/4096/1. The SDK seems to always report "MV-SILICON" for manufacturer string. Hence, match it so that we don't need to define quirk table entries separately for each devices. The "val = 0/4096/1" pattern is also checked against before applying the quirk, in order that the quirk won't accidentally break unseen variants. Quirky device samples: usb 7-1: New USB device found, idVendor=1235, idProduct=0003, bcdDevice= 1.00 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: G1 usb 7-1: Manufacturer: MV-SILICON usb 7-1: SerialNumber: 20190808 usb 7-1: New USB device found, idVendor=1235, idProduct=0003, bcdDevice= 1.00 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: mvsilicon B1 usb audio usb 7-1: Manufacturer: MV-SILICON usb 7-1: SerialNumber: 20190808 usb 5-1.4: New USB device found, idVendor=8888, idProduct=1719, bcdDevice= 1.00 usb 5-1.4: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 5-1.4: Product: HF310 USB Audio usb 5-1.4: Manufacturer: MV-SILICON usb 5-1.4: SerialNumber: 20190808 usb 7-1: New USB device found, idVendor=2717, idProduct=5086, bcdDevice= 1.00 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: Redmi 电脑音箱 usb 7-1: Manufacturer: MV-SILICON usb 7-1: SerialNumber: 20190808 usb 2-1.2: New USB device found, idVendor=3142, idProduct=a601, bcdDevice= 1.00 usb 2-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 2-1.2: Product: fifine Microphone usb 2-1.2: Manufacturer: MV-SILICON usb 2-1.2: SerialNumber: 20190808 * https://forum.ubuntu-it.org/viewtopic.php?t=659345 Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260303194805.266158-9-i@rong.moe --- sound/usb/mixer_quirks.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 539044c0c644..e97814dc9025 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -4588,6 +4588,24 @@ static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer, } } +static void snd_usb_mv_silicon_quirks(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, + struct snd_kcontrol *kctl) +{ + if (cval->min == 0 && cval->max == 4096 && cval->res == 1) { + /* The final effects will be printed later. */ + usb_audio_info(mixer->chip, "applying MV-SILICON quirks (0/4096/1 variant)\n"); + + /* Respect MIN_MUTE set by module parameters. */ + if (!(mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE)) + mixer->chip->quirk_flags |= QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL; + if (!(mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE)) + mixer->chip->quirk_flags |= QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL; + } else { + usb_audio_dbg(mixer->chip, "not applying MV-SILICON quirks on unknown variant"); + } +} + /* * Some Plantronics headsets have control names that don't meet ALSA naming * standards. This function fixes nonstandard source names. By the time @@ -4664,6 +4682,10 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, break; } + if (cval->control == UAC_FU_VOLUME && + !strncmp(mixer->chip->card->longname, "MV-SILICON", 10)) + snd_usb_mv_silicon_quirks(mixer, cval, kctl); + /* lowest playback value is muted on some devices */ if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE) if (strstr(kctl->id.name, "Playback")) { From 27b9bcad2bf77e12c08e36d8d72bd6ce0db46041 Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Wed, 4 Mar 2026 15:02:19 +0800 Subject: [PATCH 055/359] ALSA: hda/senary: Add hardware init verbs and fixup framework Port the essential hardware initialization logic from the vendor driver and introduce the standard HDA fixup framework to handle different machine configurations. Key changes: 1. Add hardware init verbs: - Implement `senary_init_verb` to send the vendor-specific initialization sequence required by the SN6186 chip. - Override pin capabilities for Node 0x19 to ensure proper headset microphone support. 2. Introduce fixup framework: - Define a default pin configuration table (`senary_pincfg_default`) to provide a fallback for devices with invalid BIOS configurations. - Establish a quirk table structure for future machine-specific fixes. - Since the standard quirk matching relies on Subsystem IDs, we manually apply the default fixup if `snd_hda_pick_fixup` does not find a specific match. This ensures the chip is correctly initialized during probe and resume, and provides a scalable mechanism for supporting specific hardware quirks. Signed-off-by: wangdicheng Link: https://patch.msgid.link/20260304070219.450083-1-wangdich9700@163.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/senarytech.c | 63 +++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/sound/hda/codecs/senarytech.c b/sound/hda/codecs/senarytech.c index 6239a25bb8f3..f9a389df3a17 100644 --- a/sound/hda/codecs/senarytech.c +++ b/sound/hda/codecs/senarytech.c @@ -36,6 +36,32 @@ struct senary_spec { unsigned int gpio_mic_led_mask; }; +enum { + SENARY_FIXUP_PINCFG_DEFAULT, +}; + +static const struct hda_pintbl senary_pincfg_default[] = { + { 0x16, 0x02211020 }, /* Headphone */ + { 0x17, 0x40f001f0 }, /* Not used */ + { 0x18, 0x05a1904d }, /* Mic */ + { 0x19, 0x02a1104e }, /* Headset Mic */ + { 0x1a, 0x01819030 }, /* Line-in */ + { 0x1d, 0x01014010 }, /* Line-out */ + {} +}; + +static const struct hda_fixup senary_fixups[] = { + [SENARY_FIXUP_PINCFG_DEFAULT] = { + .type = HDA_FIXUP_PINS, + .v.pins = senary_pincfg_default, + }, +}; + +/* Quirk table for specific machines can be added here */ +static const struct hda_quirk sn6186_fixups[] = { + {} +}; + #ifdef CONFIG_SND_HDA_INPUT_BEEP /* additional beep mixers; private_value will be overwritten */ static const struct snd_kcontrol_new senary_beep_mixer[] = { @@ -93,6 +119,19 @@ static void senary_auto_parse_eapd(struct hda_codec *codec) } } +/* Hardware specific initialization verbs */ +static void senary_init_verb(struct hda_codec *codec) +{ + /* Vendor specific init sequence */ + snd_hda_codec_write(codec, 0x1b, 0x0, 0x05a, 0xaa); + snd_hda_codec_write(codec, 0x1b, 0x0, 0x059, 0x48); + snd_hda_codec_write(codec, 0x1b, 0x0, 0x01b, 0x00); + snd_hda_codec_write(codec, 0x1b, 0x0, 0x01c, 0x00); + + /* Override pin caps for headset mic */ + snd_hda_override_pin_caps(codec, 0x19, 0x2124); +} + static void senary_auto_turn_eapd(struct hda_codec *codec, int num_pins, const hda_nid_t *pins, bool on) { @@ -136,6 +175,7 @@ static int senary_init(struct hda_codec *codec) snd_hda_gen_init(codec); senary_init_gpio_led(codec); + senary_init_verb(codec); if (!spec->dynamic_eapd) senary_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, true); snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT); @@ -181,11 +221,30 @@ static int senary_probe(struct hda_codec *codec, const struct hda_device_id *id) senary_auto_parse_eapd(codec); spec->gen.own_eapd_ctl = 1; - if (!spec->gen.vmaster_mute.hook) - spec->gen.vmaster_mute.hook = senary_auto_vmaster_hook; + /* Setup fixups based on codec vendor ID */ + switch (codec->core.vendor_id) { + case 0x1fa86186: + codec->pin_amp_workaround = 1; + spec->gen.mixer_nid = 0x15; + snd_hda_pick_fixup(codec, NULL, sn6186_fixups, senary_fixups); + + /* If no specific quirk found, apply the default pin configuration */ + if (codec->fixup_id == HDA_FIXUP_ID_NOT_SET) + codec->fixup_id = SENARY_FIXUP_PINCFG_DEFAULT; + break; + default: + snd_hda_pick_fixup(codec, NULL, sn6186_fixups, senary_fixups); + break; + } snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + /* Run hardware init verbs once during probe */ + senary_init_verb(codec); + + if (!spec->gen.vmaster_mute.hook) + spec->gen.vmaster_mute.hook = senary_auto_vmaster_hook; + err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, spec->parse_flags); if (err < 0) From b364a0d23cae157691cde2c0137998d66b45b703 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 5 Mar 2026 14:04:25 +0100 Subject: [PATCH 056/359] ALSA: usb-audio: Use strings in struct usb_dev for manufacturer & co The USB core already prepares the strings for manufacturer, product and serial number, and we don't have to extract the string at each time. Replace the manual usb_string() calls with the corresponding pointers in struct usb_dev as a code cleanup. Link: https://patch.msgid.link/20260305130426.975604-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/card.c | 20 ++++++++------------ sound/usb/midi2.c | 6 ++---- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/sound/usb/card.c b/sound/usb/card.c index 270dad84d825..fd81f32a66fb 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -631,9 +631,9 @@ static void usb_audio_make_shortname(struct usb_device *dev, } /* retrieve the device string as shortname */ - if (!dev->descriptor.iProduct || - usb_string(dev, dev->descriptor.iProduct, - card->shortname, sizeof(card->shortname)) <= 0) { + if (dev->product && *dev->product) { + strscpy(card->shortname, dev->product); + } else { /* no name available from anywhere, so use ID */ scnprintf(card->shortname, sizeof(card->shortname), "USB Device %#04x:%#04x", @@ -668,15 +668,11 @@ static void usb_audio_make_longname(struct usb_device *dev, else if (quirk && quirk->vendor_name) s = quirk->vendor_name; *card->longname = 0; - if (s && *s) { - strscpy(card->longname, s, sizeof(card->longname)); - } else { - /* retrieve the vendor and device strings as longname */ - if (dev->descriptor.iManufacturer) - usb_string(dev, dev->descriptor.iManufacturer, - card->longname, sizeof(card->longname)); - /* we don't really care if there isn't any vendor string */ - } + if (s && *s) + strscpy(card->longname, s); + else if (dev->manufacturer && *dev->manufacturer) + strscpy(card->longname, dev->manufacturer); + if (*card->longname) { strim(card->longname); if (*card->longname) diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index ef602e81576d..3546ba926cb3 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -1057,10 +1057,8 @@ static void set_fallback_rawmidi_names(struct snd_usb_midi2_interface *umidi) strscpy(ump->core.name, ump->info.name, sizeof(ump->core.name)); /* use serial number string as unique UMP product id */ - if (!*ump->info.product_id && dev->descriptor.iSerialNumber) - usb_string(dev, dev->descriptor.iSerialNumber, - ump->info.product_id, - sizeof(ump->info.product_id)); + if (!*ump->info.product_id && dev->serial && *dev->serial) + strscpy(ump->info.product_id, dev->serial); } } From bae6668c526018af45b70e7825b61e6edb528f41 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 4 Mar 2026 16:23:59 +0000 Subject: [PATCH 057/359] ASoC: cs35l56: KUnit tests for setting dsp.system_name Add KUnit tests for setting the dsp.system_name string. There are two sources of the string: 1. PCI SSID from struct snd_soc_card. 2. "cirrus,firmware-uid" property. Either of these can then be qualified by a speaker ID integer. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260304162402.1714759-2-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56-test.c | 80 +++++++++++++++++++++++++++++++++ sound/soc/codecs/cs35l56.c | 3 +- sound/soc/codecs/cs35l56.h | 1 + 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/cs35l56-test.c b/sound/soc/codecs/cs35l56-test.c index b6c8c08e3ade..2732b65fc372 100644 --- a/sound/soc/codecs/cs35l56-test.c +++ b/sound/soc/codecs/cs35l56-test.c @@ -74,6 +74,78 @@ static const char *cs35l56_test_devm_get_vendor_specific_variant_id_none(struct return ERR_PTR(-ENOENT); } +static void cs35l56_test_system_name_from_ssid(struct kunit *test) +{ + struct cs35l56_test_priv *priv = test->priv; + struct cs35l56_private *cs35l56 = priv->cs35l56_priv; + + cs35l56->speaker_id = -1; + snd_soc_card_set_pci_ssid(cs35l56->component->card, 0x12b4, 0xa7c8); + + KUNIT_EXPECT_EQ(test, cs35l56_get_firmware_uid(cs35l56), 0); + KUNIT_EXPECT_EQ(test, cs35l56_set_fw_name(cs35l56->component), 0); + KUNIT_EXPECT_STREQ(test, cs35l56->dsp.system_name, "12b4a7c8"); +} + +static void cs35l56_test_system_name_from_ssid_and_spkid(struct kunit *test) +{ + struct cs35l56_test_priv *priv = test->priv; + struct cs35l56_private *cs35l56 = priv->cs35l56_priv; + + cs35l56->speaker_id = 1; + snd_soc_card_set_pci_ssid(cs35l56->component->card, 0x12b4, 0xa7c8); + + KUNIT_EXPECT_EQ(test, cs35l56_get_firmware_uid(cs35l56), 0); + KUNIT_EXPECT_EQ(test, cs35l56_set_fw_name(cs35l56->component), 0); + KUNIT_EXPECT_STREQ(test, cs35l56->dsp.system_name, "12b4a7c8-spkid1"); +} + +static void cs35l56_test_system_name_from_property(struct kunit *test) +{ + struct cs35l56_test_priv *priv = test->priv; + struct cs35l56_private *cs35l56 = priv->cs35l56_priv; + const struct property_entry dev_props[] = { + PROPERTY_ENTRY_STRING("cirrus,firmware-uid", "acme"), + { } + }; + const struct software_node dev_node = SOFTWARE_NODE("SPK1", dev_props, NULL); + + cs35l56->speaker_id = -1; + + KUNIT_ASSERT_EQ(test, device_add_software_node(cs35l56->base.dev, &dev_node), 0); + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, + device_remove_software_node_wrapper, + cs35l56->base.dev)); + + KUNIT_EXPECT_EQ(test, cs35l56_get_firmware_uid(cs35l56), 0); + KUNIT_EXPECT_EQ(test, cs35l56_set_fw_name(cs35l56->component), 0); + KUNIT_EXPECT_STREQ(test, cs35l56->dsp.system_name, "acme"); +} + +static void cs35l56_test_system_name_from_property_and_spkid(struct kunit *test) +{ + struct cs35l56_test_priv *priv = test->priv; + struct cs35l56_private *cs35l56 = priv->cs35l56_priv; + const struct property_entry dev_props[] = { + PROPERTY_ENTRY_STRING("cirrus,firmware-uid", "acme"), + { } + }; + const struct software_node dev_node = SOFTWARE_NODE("SPK1", dev_props, NULL); + + cs35l56->speaker_id = 1; + + KUNIT_ASSERT_EQ(test, device_add_software_node(cs35l56->base.dev, &dev_node), 0); + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, + device_remove_software_node_wrapper, + cs35l56->base.dev)); + + KUNIT_EXPECT_EQ(test, cs35l56_get_firmware_uid(cs35l56), 0); + KUNIT_EXPECT_EQ(test, cs35l56_set_fw_name(cs35l56->component), 0); + KUNIT_EXPECT_STREQ(test, cs35l56->dsp.system_name, "acme-spkid1"); +} + static void cs35l56_test_l56_b0_suffix_sdw(struct kunit *test) { struct cs35l56_test_priv *priv = test->priv; @@ -586,6 +658,10 @@ KUNIT_ARRAY_PARAM(cs35l56_test_type_rev_all, cs35l56_test_type_rev_all_param_cas cs35l56_test_type_rev_param_desc); static struct kunit_case cs35l56_test_cases_soundwire[] = { + KUNIT_CASE(cs35l56_test_system_name_from_ssid), + KUNIT_CASE(cs35l56_test_system_name_from_ssid_and_spkid), + KUNIT_CASE(cs35l56_test_system_name_from_property), + KUNIT_CASE(cs35l56_test_system_name_from_property_and_spkid), KUNIT_CASE(cs35l56_test_l56_b0_suffix_sdw), KUNIT_CASE_PARAM(cs35l56_test_suffix_sdw, cs35l56_test_type_rev_ex_b0_gen_params), KUNIT_CASE_PARAM(cs35l56_test_ssidexv2_suffix_sdw, @@ -603,6 +679,10 @@ static struct kunit_case cs35l56_test_cases_soundwire[] = { }; static struct kunit_case cs35l56_test_cases_not_soundwire[] = { + KUNIT_CASE(cs35l56_test_system_name_from_ssid), + KUNIT_CASE(cs35l56_test_system_name_from_ssid_and_spkid), + KUNIT_CASE(cs35l56_test_system_name_from_property), + KUNIT_CASE(cs35l56_test_system_name_from_property_and_spkid), KUNIT_CASE_PARAM(cs35l56_test_suffix_i2cspi, cs35l56_test_type_rev_all_gen_params), KUNIT_CASE_PARAM(cs35l56_test_ssidexv2_suffix_i2cspi, cs35l56_test_type_rev_all_gen_params), diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index 2ff8b172b76e..ffaa5fca8d50 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -1661,7 +1661,7 @@ VISIBLE_IF_KUNIT int cs35l56_process_xu_properties(struct cs35l56_private *cs35l } EXPORT_SYMBOL_IF_KUNIT(cs35l56_process_xu_properties); -static int cs35l56_get_firmware_uid(struct cs35l56_private *cs35l56) +VISIBLE_IF_KUNIT int cs35l56_get_firmware_uid(struct cs35l56_private *cs35l56) { struct device *dev = cs35l56->base.dev; const char *prop; @@ -1686,6 +1686,7 @@ static int cs35l56_get_firmware_uid(struct cs35l56_private *cs35l56) return 0; } +EXPORT_SYMBOL_IF_KUNIT(cs35l56_get_firmware_uid); /* * Some SoundWire laptops have a spk-id-gpios property but it points to diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h index 691f857d0bd8..747529be3e2e 100644 --- a/sound/soc/codecs/cs35l56.h +++ b/sound/soc/codecs/cs35l56.h @@ -78,6 +78,7 @@ void cs35l56_remove(struct cs35l56_private *cs35l56); int cs35l56_set_fw_suffix(struct cs35l56_private *cs35l56); int cs35l56_set_fw_name(struct snd_soc_component *component); int cs35l56_process_xu_properties(struct cs35l56_private *cs35l56); +int cs35l56_get_firmware_uid(struct cs35l56_private *cs35l56); #endif #endif /* ifndef CS35L56_H */ From 72e1c4704844766c46725d6b043ba04559054d02 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 4 Mar 2026 16:24:00 +0000 Subject: [PATCH 058/359] ASoC: cs35l56: Some KUnit testing of cs35l56_get_speaker_id() Add some KUnit tests for cs35l56_get_speaker_id(). These only test the simpler cases of reading the speaker ID from cs_amp_get_vendor_spkid() or the "cirrus,speaker-id" property. The untested case is reading it from GPIOs, which would require a dummy implementation of a GPIO driver. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260304162402.1714759-3-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs-amp-lib.c | 2 ++ sound/soc/codecs/cs35l56-shared-test.c | 43 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c index 8b131975143d..b34b1f5f121f 100644 --- a/sound/soc/codecs/cs-amp-lib.c +++ b/sound/soc/codecs/cs-amp-lib.c @@ -716,6 +716,8 @@ int cs_amp_get_vendor_spkid(struct device *dev) { int i, ret; + KUNIT_STATIC_STUB_REDIRECT(cs_amp_get_vendor_spkid, dev); + if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE) && !IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST_HOOKS)) return -ENOENT; diff --git a/sound/soc/codecs/cs35l56-shared-test.c b/sound/soc/codecs/cs35l56-shared-test.c index 94db02aef7dc..d510893dcd44 100644 --- a/sound/soc/codecs/cs35l56-shared-test.c +++ b/sound/soc/codecs/cs35l56-shared-test.c @@ -37,6 +37,10 @@ KUNIT_DEFINE_ACTION_WRAPPER(faux_device_destroy_wrapper, faux_device_destroy, KUNIT_DEFINE_ACTION_WRAPPER(regmap_exit_wrapper, regmap_exit, struct regmap *) +KUNIT_DEFINE_ACTION_WRAPPER(device_remove_software_node_wrapper, + device_remove_software_node, + struct device *) + static const struct regmap_config cs35l56_shared_test_mock_registers_regmap = { .reg_bits = 32, .val_bits = 32, @@ -410,6 +414,41 @@ static void cs35l56_shared_test_onchip_speaker_id_not_defined(struct kunit *test KUNIT_EXPECT_EQ(test, cs35l56_read_onchip_spkid(cs35l56_base), -ENOENT); } +/* simulate cs_amp_get_vendor_spkid() reading a vendor-specific ID of 1 */ +static int cs35l56_shared_test_get_vendor_spkid_1(struct device *dev) +{ + return 1; +} + +static void cs35l56_shared_test_get_speaker_id_vendor(struct kunit *test) +{ + struct cs35l56_shared_test_priv *priv = test->priv; + + /* Hook cs_amp_get_vendor_spkid() to return an ID of 1 */ + kunit_activate_static_stub(test, cs_amp_get_vendor_spkid, + cs35l56_shared_test_get_vendor_spkid_1); + + KUNIT_EXPECT_EQ(test, cs35l56_get_speaker_id(priv->cs35l56_base), 1); +} + +static void cs35l56_shared_test_get_speaker_id_property(struct kunit *test) +{ + struct cs35l56_shared_test_priv *priv = test->priv; + const struct property_entry dev_props[] = { + PROPERTY_ENTRY_U32("cirrus,speaker-id", 2), + { } + }; + const struct software_node dev_node = SOFTWARE_NODE("SPK1", dev_props, NULL); + + KUNIT_ASSERT_EQ(test, device_add_software_node(priv->cs35l56_base->dev, &dev_node), 0); + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, + device_remove_software_node_wrapper, + priv->cs35l56_base->dev)); + + KUNIT_EXPECT_EQ(test, cs35l56_get_speaker_id(priv->cs35l56_base), 2); +} + static int cs35l56_shared_test_case_regmap_init(struct kunit *test, const struct regmap_config *regmap_config) { @@ -616,6 +655,10 @@ static struct kunit_case cs35l56_shared_test_cases[] = { cs35l56_shared_test_onchip_spkid_pull_gen_params), KUNIT_CASE(cs35l56_shared_test_stash_onchip_spkid_pins_reject_invalid), KUNIT_CASE(cs35l56_shared_test_onchip_speaker_id_not_defined), + + KUNIT_CASE(cs35l56_shared_test_get_speaker_id_vendor), + KUNIT_CASE(cs35l56_shared_test_get_speaker_id_property), + { } }; From ef0b4783afc211a4b120e72b5a57f3d0340a9981 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 4 Mar 2026 16:24:01 +0000 Subject: [PATCH 059/359] ASoC: cs35l56: KUnit tests for reading speaker ID from host GPIOs Add test cases for the spk-id-gpios property used to read an integer speaker ID value from a set of GPIO inputs. There is a single parameterized test function, cs35l56_shared_test_get_speaker_id_from_host_gpio() that does the following: - Create a mock GPIO driver to simulate real GPIO inputs. - Create an array of struct software_node_ref_args to provide the content of the spk-id-gpios property. Each entry is a reference to a GPIO on the mock GPIO driver. The GPIO indexes are taken from test case parameterization. - Create a software node containing the spk-id-gpios property and set this as the node of the pseudo codec driver device. - Call cs35l56_get_speaker_id() and assert that the returned value matches the expected value defined in the test case parameterization. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260304162402.1714759-4-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56-shared-test.c | 197 +++++++++++++++++++++++++ 1 file changed, 197 insertions(+) diff --git a/sound/soc/codecs/cs35l56-shared-test.c b/sound/soc/codecs/cs35l56-shared-test.c index d510893dcd44..cfe7938065f9 100644 --- a/sound/soc/codecs/cs35l56-shared-test.c +++ b/sound/soc/codecs/cs35l56-shared-test.c @@ -11,15 +11,23 @@ #include #include #include +#include #include #include #include #include #include +struct cs35l56_shared_test_mock_gpio { + unsigned int pin_state; + struct gpio_chip chip; +}; + struct cs35l56_shared_test_priv { struct kunit *test; struct faux_device *amp_dev; + struct faux_device *gpio_dev; + struct cs35l56_shared_test_mock_gpio *gpio_priv; struct regmap *registers; struct cs35l56_base *cs35l56_base; u8 applied_pad_pull_state[CS35L56_MAX_GPIO]; @@ -41,6 +49,90 @@ KUNIT_DEFINE_ACTION_WRAPPER(device_remove_software_node_wrapper, device_remove_software_node, struct device *) +static int cs35l56_shared_test_mock_gpio_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + return GPIO_LINE_DIRECTION_IN; +} + +static int cs35l56_shared_test_mock_gpio_direction_in(struct gpio_chip *chip, + unsigned int offset) +{ + return 0; +} + +static int cs35l56_shared_test_mock_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct cs35l56_shared_test_mock_gpio *gpio_priv = gpiochip_get_data(chip); + + return !!(gpio_priv->pin_state & BIT(offset)); +} + +static const struct gpio_chip cs35l56_shared_test_mock_gpio_chip = { + .label = "cs35l56_shared_test_mock_gpio", + .owner = THIS_MODULE, + .get_direction = cs35l56_shared_test_mock_gpio_get_direction, + .direction_input = cs35l56_shared_test_mock_gpio_direction_in, + .get = cs35l56_shared_test_mock_gpio_get, + .base = -1, + .ngpio = 32, +}; + +/* software_node referencing the gpio driver */ +static const struct software_node cs35l56_shared_test_mock_gpio_swnode = { + .name = "cs35l56_shared_test_mock_gpio", +}; + +static int cs35l56_shared_test_mock_gpio_probe(struct faux_device *fdev) +{ + struct cs35l56_shared_test_mock_gpio *gpio_priv; + struct device *dev = &fdev->dev; + int ret; + + gpio_priv = devm_kzalloc(dev, sizeof(*gpio_priv), GFP_KERNEL); + if (!gpio_priv) + return -ENOMEM; + + ret = device_add_software_node(dev, &cs35l56_shared_test_mock_gpio_swnode); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, device_remove_software_node_wrapper, dev); + if (ret) + return ret; + + /* GPIO core modifies our struct gpio_chip so use a copy */ + gpio_priv->chip = cs35l56_shared_test_mock_gpio_chip; + gpio_priv->chip.parent = dev; + ret = devm_gpiochip_add_data(dev, &gpio_priv->chip, gpio_priv); + if (ret) + return dev_err_probe(dev, ret, "Failed to add gpiochip\n"); + + dev_set_drvdata(dev, gpio_priv); + + return 0; +} + +static struct faux_device_ops cs35l56_shared_test_mock_gpio_drv = { + .probe = cs35l56_shared_test_mock_gpio_probe, +}; + +static void _cs35l56_shared_test_create_dummy_gpio(struct kunit *test) +{ + struct cs35l56_shared_test_priv *priv = test->priv; + + priv->gpio_dev = faux_device_create("cs35l56_shared_test_mock_gpio", NULL, + &cs35l56_shared_test_mock_gpio_drv); + KUNIT_ASSERT_NOT_NULL(test, priv->gpio_dev); + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, + faux_device_destroy_wrapper, + priv->gpio_dev)); + + priv->gpio_priv = dev_get_drvdata(&priv->gpio_dev->dev); + KUNIT_ASSERT_NOT_NULL(test, priv->gpio_priv); +} + static const struct regmap_config cs35l56_shared_test_mock_registers_regmap = { .reg_bits = 32, .val_bits = 32, @@ -449,6 +541,74 @@ static void cs35l56_shared_test_get_speaker_id_property(struct kunit *test) KUNIT_EXPECT_EQ(test, cs35l56_get_speaker_id(priv->cs35l56_base), 2); } +/* + * Create software nodes equivalent to ACPI structure + * + * Device(GSPK) { + * Name(_DSD, ...) { + * Package() { + * cs-gpios { + * GPIO, n, 0, + * ... + * } + * } + */ +static void _cs35l56_shared_test_create_spkid_swnode(struct kunit *test, + struct device *dev, + const struct software_node_ref_args *args, + int num_args) +{ + struct cs35l56_shared_test_priv *priv = test->priv; + const struct property_entry props_template[] = { + PROPERTY_ENTRY_REF_ARRAY_LEN("spk-id-gpios", args, num_args), + { } + }; + struct property_entry *props; + struct software_node *node; + + props = kunit_kzalloc(test, sizeof(props_template), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, props); + memcpy(props, props_template, sizeof(props_template)); + + node = kunit_kzalloc(test, sizeof(*node), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, node); + *node = SOFTWARE_NODE("GSPK", props, NULL); + + KUNIT_ASSERT_EQ(test, device_add_software_node(dev, node), 0); + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, + device_remove_software_node_wrapper, + priv->cs35l56_base->dev)); +} + +static void cs35l56_shared_test_get_speaker_id_from_host_gpio(struct kunit *test) +{ + const struct cs35l56_shared_test_param *param = test->param_value; + struct cs35l56_shared_test_priv *priv = test->priv; + struct cs35l56_base *cs35l56_base = priv->cs35l56_base; + struct software_node_ref_args *ref; + int i; + + if (!IS_REACHABLE(CONFIG_GPIOLIB)) { + kunit_skip(test, "Requires CONFIG_GPIOLIB"); + return; + } + + _cs35l56_shared_test_create_dummy_gpio(test); + + ref = kunit_kcalloc(test, ARRAY_SIZE(param->spkid_gpios), sizeof(*ref), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ref); + + for (i = 0; param->spkid_gpios[i] >= 0; i++) { + ref[i] = SOFTWARE_NODE_REFERENCE(&cs35l56_shared_test_mock_gpio_swnode, + param->spkid_gpios[i], 0); + } + _cs35l56_shared_test_create_spkid_swnode(test, cs35l56_base->dev, ref, i); + + priv->gpio_priv->pin_state = param->gpio_status; + KUNIT_EXPECT_EQ(test, cs35l56_get_speaker_id(priv->cs35l56_base), param->spkid); +} + static int cs35l56_shared_test_case_regmap_init(struct kunit *test, const struct regmap_config *regmap_config) { @@ -641,6 +801,40 @@ KUNIT_ARRAY_PARAM(cs35l56_shared_test_onchip_spkid_pull, cs35l56_shared_test_onchip_spkid_pull_cases, cs35l56_shared_test_gpio_param_desc); +/* Note: spk-id-gpios property bit order is LSbit...MSbit */ +static const struct cs35l56_shared_test_param cs35l56_shared_test_host_gpio_spkid_cases[] = { + { .spkid_gpios = { 0, -1 }, .gpio_status = 0, .spkid = 0 }, + { .spkid_gpios = { 0, -1 }, .gpio_status = ~BIT(0), .spkid = 0 }, + { .spkid_gpios = { 0, -1 }, .gpio_status = BIT(0), .spkid = 1 }, + + { .spkid_gpios = { 6, -1 }, .gpio_status = 0, .spkid = 0 }, + { .spkid_gpios = { 6, -1 }, .gpio_status = ~BIT(6), .spkid = 0 }, + { .spkid_gpios = { 6, -1 }, .gpio_status = BIT(6), .spkid = 1 }, + + { .spkid_gpios = { 6, 0, -1 }, .gpio_status = 0, .spkid = 0 }, + { .spkid_gpios = { 6, 0, -1 }, .gpio_status = ~(BIT(0) | BIT(6)), .spkid = 0 }, + { .spkid_gpios = { 6, 0, -1 }, .gpio_status = BIT(6), .spkid = 1 }, + { .spkid_gpios = { 6, 0, -1 }, .gpio_status = BIT(0), .spkid = 2 }, + { .spkid_gpios = { 6, 0, -1 }, .gpio_status = BIT(6) | BIT(0), .spkid = 3 }, + + { .spkid_gpios = { 0, 6, -1 }, .gpio_status = 0, .spkid = 0 }, + { .spkid_gpios = { 0, 6, -1 }, .gpio_status = ~(BIT(6) | BIT(0)), .spkid = 0 }, + { .spkid_gpios = { 0, 6, -1 }, .gpio_status = BIT(0), .spkid = 1 }, + { .spkid_gpios = { 0, 6, -1 }, .gpio_status = BIT(6), .spkid = 2 }, + { .spkid_gpios = { 0, 6, -1 }, .gpio_status = BIT(6) | BIT(0), .spkid = 3 }, + + { .spkid_gpios = { 0, 6, 2, -1 }, .gpio_status = 0, .spkid = 0 }, + { .spkid_gpios = { 0, 6, 2, -1 }, .gpio_status = BIT(0), .spkid = 1 }, + { .spkid_gpios = { 0, 6, 2, -1 }, .gpio_status = BIT(6), .spkid = 2 }, + { .spkid_gpios = { 0, 6, 2, -1 }, .gpio_status = BIT(6) | BIT(0), .spkid = 3 }, + { .spkid_gpios = { 0, 6, 2, -1 }, .gpio_status = BIT(2), .spkid = 4 }, + { .spkid_gpios = { 0, 6, 2, -1 }, .gpio_status = BIT(2) | BIT(0), .spkid = 5 }, + { .spkid_gpios = { 0, 6, 2, -1 }, .gpio_status = BIT(2) | BIT(6), .spkid = 6 }, + { .spkid_gpios = { 0, 6, 2, -1 }, .gpio_status = BIT(2) | BIT(6) | BIT(0), .spkid = 7 }, +}; +KUNIT_ARRAY_PARAM(cs35l56_shared_test_host_gpio_spkid, cs35l56_shared_test_host_gpio_spkid_cases, + cs35l56_shared_test_gpio_param_desc); + static struct kunit_case cs35l56_shared_test_cases[] = { /* Tests for speaker id */ KUNIT_CASE_PARAM(cs35l56_shared_test_mock_gpio_status_selftest, @@ -658,6 +852,9 @@ static struct kunit_case cs35l56_shared_test_cases[] = { KUNIT_CASE(cs35l56_shared_test_get_speaker_id_vendor), KUNIT_CASE(cs35l56_shared_test_get_speaker_id_property), + KUNIT_CASE_PARAM_ATTR(cs35l56_shared_test_get_speaker_id_from_host_gpio, + cs35l56_shared_test_host_gpio_spkid_gen_params, + { KUNIT_SPEED_SLOW }), { } }; From 9558a2cbec2eb034ae7d570ff56ad74f9a918177 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Fri, 6 Mar 2026 01:46:39 +0800 Subject: [PATCH 060/359] ALSA: usb-audio: Refine string-descriptor-based quirk matching Remove snd_usb_get_string() and use the manufacturer and product strings stored in struct usb_device directly to match quirk table entries. Their NULLity can be checked to determine if the device has no these strings. This simplifies the code a lot. Meanwhile, allow quirk table entries to match "no string" explicitly, and add appropriate comments to show the expected usages of DEVICE_STRING_FLG() and VENDOR_STRING_FLG(). These changes are tiny and doesn't form another separate patch, so that back-and-forth changes can be avoided. Suggested-by: Terry Junge Link: https://lore.kernel.org/r/b59da54a-9c80-4212-a337-c5ea98da52d1@cosmicgizmosystems.com Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20260305174711.1106324-1-i@rong.moe Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 90 +++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 50 deletions(-) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 17f6be4d2350..ab67f7826e9b 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2153,7 +2153,28 @@ struct usb_audio_quirk_flags_table { { .id = USB_ID(vid, pid), .flags = (_flags) } #define VENDOR_FLG(vid, _flags) DEVICE_FLG(vid, 0, _flags) -/* Use as a last resort if using DEVICE_FLG() is prone to VID/PID conflicts. */ +/* + * Use as a last resort if using DEVICE_FLG() is prone to VID/PID conflicts. + * + * Usage: + * // match vid, pid, "manufacturer", and "product" + * DEVICE_STRING_FLG(vid, pid, "manufacturer", "product", flags) + * + * // match vid, pid, "manufacturer", and any product string + * DEVICE_STRING_FLG(vid, pid, "manufacturer", NULL, flags) + * + * // match vid, pid, "manufacturer", and device must have no product string + * DEVICE_STRING_FLG(vid, pid, "manufacturer", "", flags) + * + * // match vid, pid, any manufacturer string, and "product" + * DEVICE_STRING_FLG(vid, pid, NULL, "product", flags) + * + * // match vid, pid, no manufacturer string, and "product" + * DEVICE_STRING_FLG(vid, pid, "", "product", flags) + * + * // match vid, pid, no manufacturer string, and no product string + * DEVICE_STRING_FLG(vid, pid, "", "", flags) + */ #define DEVICE_STRING_FLG(vid, pid, _manufacturer, _product, _flags) \ { \ .id = USB_ID(vid, pid), \ @@ -2164,7 +2185,16 @@ struct usb_audio_quirk_flags_table { .flags = (_flags), \ } -/* Use as a last resort if using VENDOR_FLG() is prone to VID conflicts. */ +/* + * Use as a last resort if using VENDOR_FLG() is prone to VID conflicts. + * + * Usage: + * // match vid, and "manufacturer" + * VENDOR_STRING_FLG(vid, "manufacturer", flags) + * + * // match vid, and device must have no manufacturer string + * VENDOR_STRING_FLG(vid, "", flags) + */ #define VENDOR_STRING_FLG(vid, _manufacturer, _flags) \ DEVICE_STRING_FLG(vid, 0, _manufacturer, NULL, _flags) @@ -2595,63 +2625,23 @@ void snd_usb_apply_flag_dbg(const char *reason, } } -#define USB_STRING_SIZE 128 - -static char *snd_usb_get_string(struct snd_usb_audio *chip, int id) -{ - char *buf; - int ret; - - /* - * Devices without the corresponding string descriptor. - * This is non-fatal as *_STRING_FLG have nothing to do in this case. - */ - if (id == 0) - return ERR_PTR(-ENODATA); - - buf = kmalloc(USB_STRING_SIZE, GFP_KERNEL); - if (buf == NULL) - return ERR_PTR(-ENOMEM); - - ret = usb_string(chip->dev, id, buf, USB_STRING_SIZE); - if (ret < 0) { - usb_audio_warn(chip, "failed to get string for id%d: %d\n", id, ret); - kfree(buf); - return ERR_PTR(ret); - } - - return buf; -} - void snd_usb_init_quirk_flags_table(struct snd_usb_audio *chip) { const struct usb_audio_quirk_flags_table *p; - char *manufacturer __free(kfree) = NULL; - char *product __free(kfree) = NULL; for (p = quirk_flags_table; p->id; p++) { if (chip->usb_id == p->id || (!USB_ID_PRODUCT(p->id) && USB_ID_VENDOR(chip->usb_id) == USB_ID_VENDOR(p->id))) { /* Handle DEVICE_STRING_FLG/VENDOR_STRING_FLG. */ - if (p->usb_string_match && p->usb_string_match->manufacturer) { - if (!manufacturer) { - manufacturer = snd_usb_get_string(chip, - chip->dev->descriptor.iManufacturer); - } - if (IS_ERR_OR_NULL(manufacturer) || - strcmp(p->usb_string_match->manufacturer, manufacturer)) - continue; - } - if (p->usb_string_match && p->usb_string_match->product) { - if (!product) { - product = snd_usb_get_string(chip, - chip->dev->descriptor.iProduct); - } - if (IS_ERR_OR_NULL(product) || - strcmp(p->usb_string_match->product, product)) - continue; - } + if (p->usb_string_match && p->usb_string_match->manufacturer && + strcmp(p->usb_string_match->manufacturer, + chip->dev->manufacturer ? chip->dev->manufacturer : "")) + continue; + if (p->usb_string_match && p->usb_string_match->product && + strcmp(p->usb_string_match->product, + chip->dev->product ? chip->dev->product : "")) + continue; snd_usb_apply_flag_dbg("builtin table", chip, p->flags); chip->quirk_flags |= p->flags; From 5bebbfd64b879d1a7220233767be3274e7d442b8 Mon Sep 17 00:00:00 2001 From: Sen Wang Date: Thu, 5 Mar 2026 13:58:24 -0600 Subject: [PATCH 061/359] ASoC: ti: davinci-mcasp: extract mcasp_is_auxclk_enabled() helper Move the AUXCLK-enabled check out of davinci_mcasp_calc_clk_div() into a reusable helper. No functional change. Signed-off-by: Sen Wang Link: https://patch.msgid.link/20260305195825.9998-2-sen@ti.com Signed-off-by: Mark Brown --- sound/soc/ti/davinci-mcasp.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sound/soc/ti/davinci-mcasp.c b/sound/soc/ti/davinci-mcasp.c index 2d260fbc9b83..6d0310f09b12 100644 --- a/sound/soc/ti/davinci-mcasp.c +++ b/sound/soc/ti/davinci-mcasp.c @@ -274,6 +274,14 @@ static inline unsigned int mcasp_get_auxclk_fs_ratio(struct davinci_mcasp *mcasp mcasp->auxclk_fs_ratio_tx : mcasp->auxclk_fs_ratio_rx; } +static inline bool mcasp_is_auxclk_enabled(struct davinci_mcasp *mcasp, int stream) +{ + if (mcasp->async_mode && stream == SNDRV_PCM_STREAM_CAPTURE) + return mcasp_get_reg(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG) & AHCLKRE; + + return mcasp_get_reg(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG) & AHCLKXE; +} + static void mcasp_start_rx(struct davinci_mcasp *mcasp) { if (mcasp->rxnumevt) { /* enable FIFO */ @@ -1337,16 +1345,15 @@ static int davinci_mcasp_calc_clk_div(struct davinci_mcasp *mcasp, int bclk_div_id, auxclk_div_id; bool auxclk_enabled; + auxclk_enabled = mcasp_is_auxclk_enabled(mcasp, stream); + if (mcasp->async_mode && stream == SNDRV_PCM_STREAM_CAPTURE) { - auxclk_enabled = mcasp_get_reg(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG) & AHCLKRE; bclk_div_id = MCASP_CLKDIV_BCLK_RXONLY; auxclk_div_id = MCASP_CLKDIV_AUXCLK_RXONLY; } else if (mcasp->async_mode && stream == SNDRV_PCM_STREAM_PLAYBACK) { - auxclk_enabled = mcasp_get_reg(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG) & AHCLKXE; bclk_div_id = MCASP_CLKDIV_BCLK_TXONLY; auxclk_div_id = MCASP_CLKDIV_AUXCLK_TXONLY; } else { - auxclk_enabled = mcasp_get_reg(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG) & AHCLKXE; bclk_div_id = MCASP_CLKDIV_BCLK; auxclk_div_id = MCASP_CLKDIV_AUXCLK; } From a8075ada4a341ce58ebf8bef0188cefe6c2f6487 Mon Sep 17 00:00:00 2001 From: Sen Wang Date: Thu, 5 Mar 2026 13:58:25 -0600 Subject: [PATCH 062/359] ASoC: ti: davinci-mcasp: improve aux_div selection for mid-range dividers When the ideal total divider (sysclk/bclk) is between 33 and 4096 and AUXCLK is enabled, the driver computes aux_div as ceil(div/32) and then recomputes bclk_div from the truncated sysclk. This two-step integer division loses precision due to truncation and can sometimes produce PPM errors large enough for ALSA's hw_rule_format to reject otherwise valid sample formats. For example, on AM62D-EVM (auxclk-fs-ratio=2177, tdm-slots=2, fck=96 MHz), playing S16_LE at 44100 Hz gives BCLK = 1,411,200 Hz and an ideal total divider of 68. The old code picks aux_div = ceil(68/32) = 3, then bclk_div = (96005700/3) / 1411200 = 22, for a total of 3 x 22 = 66 -- two steps from ideal. The resulting error exceeds the PPM threshold and causes S16_LE, S24_LE to be rejected. Therefore when the total divider fits in the AHCLKXDIV register alone (<=4096), use it directly as aux_div with bclk_div=1, and compare floor and ceil to pick the closer match, to ensure the best ideal total dividers. Dividers at or below 32 never enter this path, and dividers above 4096 still fall through to the existing DIV_ROUND_UP path, so previously working configurations remains unaffected. Signed-off-by: Sen Wang Link: https://patch.msgid.link/20260305195825.9998-3-sen@ti.com Signed-off-by: Mark Brown --- sound/soc/ti/davinci-mcasp.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/sound/soc/ti/davinci-mcasp.c b/sound/soc/ti/davinci-mcasp.c index 6d0310f09b12..800c383ed2e1 100644 --- a/sound/soc/ti/davinci-mcasp.c +++ b/sound/soc/ti/davinci-mcasp.c @@ -1358,19 +1358,29 @@ static int davinci_mcasp_calc_clk_div(struct davinci_mcasp *mcasp, auxclk_div_id = MCASP_CLKDIV_AUXCLK; } - if (div > (ACLKXDIV_MASK + 1)) { - if (auxclk_enabled) { - aux_div = div / (ACLKXDIV_MASK + 1); - if (div % (ACLKXDIV_MASK + 1)) - aux_div++; + if (div > (ACLKXDIV_MASK + 1) && auxclk_enabled) { + if (div <= (AHCLKXDIV_MASK + 1)) { + /* aux_div absorbs entire division; bclk_div = 1 */ + aux_div = div; + if ((div + 1) <= (AHCLKXDIV_MASK + 1)) { + unsigned int err_lo = sysclk_freq / div - + bclk_freq; + unsigned int err_hi = bclk_freq - + sysclk_freq / (div + 1); - sysclk_freq /= aux_div; - div = sysclk_freq / bclk_freq; - rem = sysclk_freq % bclk_freq; - } else if (set) { - dev_warn(mcasp->dev, "Too fast reference clock (%u)\n", - sysclk_freq); + if (err_hi < err_lo) + aux_div = div + 1; + } + } else { + aux_div = DIV_ROUND_UP(div, ACLKXDIV_MASK + 1); } + + sysclk_freq /= aux_div; + div = sysclk_freq / bclk_freq; + rem = sysclk_freq % bclk_freq; + } else if (div > (ACLKXDIV_MASK + 1) && set) { + dev_warn(mcasp->dev, "Too fast reference clock (%u)\n", + sysclk_freq); } if (rem != 0) { From 3e314fde2304b328929c471a70906bc5968f9dcf Mon Sep 17 00:00:00 2001 From: Maciej Strozek Date: Fri, 6 Mar 2026 15:28:08 +0000 Subject: [PATCH 063/359] ASoC: sdw_utils: Add CS42L43B codec info Add codec_info for a new variant of CS42L43. It can resue existing info but needs a new part_id. Signed-off-by: Maciej Strozek Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260306152829.3130530-2-mstrozek@opensource.cirrus.com Tested-by: Charles Keepax Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_utils.c | 54 +++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 424c8a2171f1..becc22328230 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -723,6 +723,60 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, .dai_num = 4, }, + { + .part_id = 0x2A3B, + .name_prefix = "cs42l43", + .count_sidecar = asoc_sdw_bridge_cs35l56_count_sidecar, + .add_sidecar = asoc_sdw_bridge_cs35l56_add_sidecar, + .dais = { + { + .direction = {true, false}, + .codec_name = "cs42l43-codec", + .dai_name = "cs42l43-dp5", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .rtd_init = asoc_sdw_cs42l43_hs_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 = {false, true}, + .codec_name = "cs42l43-codec", + .dai_name = "cs42l43-dp1", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_cs42l43_dmic_rtd_init, + .widgets = generic_dmic_widgets, + .num_widgets = ARRAY_SIZE(generic_dmic_widgets), + .quirk = SOC_SDW_CODEC_MIC, + .quirk_exclude = true, + }, + { + .direction = {false, true}, + .codec_name = "cs42l43-codec", + .dai_name = "cs42l43-dp2", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + }, + { + .direction = {true, false}, + .codec_name = "cs42l43-codec", + .dai_name = "cs42l43-dp6", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_cs42l43_spk_init, + .rtd_init = asoc_sdw_cs42l43_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 | SOC_SDW_SIDECAR_AMPS, + }, + }, + .dai_num = 4, + }, { .part_id = 0x4245, .name_prefix = "cs42l45", From 301db523173236829974c7add540baa470ac8761 Mon Sep 17 00:00:00 2001 From: Maciej Strozek Date: Fri, 6 Mar 2026 15:28:09 +0000 Subject: [PATCH 064/359] ASoC: dt-bindings: cirrus,cs42l43: Add CS42L43B variant CS42L43B variant adds dedicated PDM interface, SoundWire Clock Gearing support and more decimators to ISRCs. Acked-by: Krzysztof Kozlowski Signed-off-by: Maciej Strozek Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260306152829.3130530-3-mstrozek@opensource.cirrus.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/cirrus,cs42l43.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs42l43.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs42l43.yaml index 99a536601cc7..376928d1f64b 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs42l43.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs42l43.yaml @@ -16,6 +16,8 @@ description: | DAC for headphone output, two integrated Class D amplifiers for loudspeakers, and two ADCs for wired headset microphone input or stereo line input. PDM inputs are provided for digital microphones. + CS42L43B variant adds dedicated PDM interface, SoundWire Clock Gearing + support and more decimators to ISRCs. allOf: - $ref: dai-common.yaml# @@ -24,6 +26,7 @@ properties: compatible: enum: - cirrus,cs42l43 + - cirrus,cs42l43b reg: maxItems: 1 From a6fe20d67dc7d512f9b5dc11c5777fb1e1ff70ce Mon Sep 17 00:00:00 2001 From: Maciej Strozek Date: Fri, 6 Mar 2026 15:28:10 +0000 Subject: [PATCH 065/359] mfd: cs42l43: Add support for the B variant Introducing CS42L43B codec, a variant of CS42L43 which can be driven by the same driver. Changes in CS42L43 driver specific for CS42L43B: - Decimator 1 and 2 are dedicated to ADC, can't be selected for PDM - Decimators 3 and 4 are connected to PDM1 - Added Decimator 5 and 6 for PDM2 - Supports SoundWire Clock Gearing - Updated ROM requiring no patching - Reduced RAM space - Each ISRC has 4 decimators now Signed-off-by: Maciej Strozek Acked-by: Lee Jones Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260306152829.3130530-4-mstrozek@opensource.cirrus.com Signed-off-by: Mark Brown --- drivers/mfd/cs42l43-i2c.c | 7 ++- drivers/mfd/cs42l43-sdw.c | 4 +- drivers/mfd/cs42l43.c | 93 +++++++++++++++++++++++++++----- drivers/mfd/cs42l43.h | 2 +- include/linux/mfd/cs42l43-regs.h | 76 ++++++++++++++++++++++++++ include/linux/mfd/cs42l43.h | 1 + 6 files changed, 166 insertions(+), 17 deletions(-) diff --git a/drivers/mfd/cs42l43-i2c.c b/drivers/mfd/cs42l43-i2c.c index a2ab001a600a..0a0ab5e549a5 100644 --- a/drivers/mfd/cs42l43-i2c.c +++ b/drivers/mfd/cs42l43-i2c.c @@ -47,6 +47,7 @@ static int cs42l43_i2c_probe(struct i2c_client *i2c) cs42l43->irq = i2c->irq; /* A device on an I2C is always attached by definition. */ cs42l43->attached = true; + cs42l43->variant_id = (long)device_get_match_data(cs42l43->dev); cs42l43->regmap = devm_regmap_init_i2c(i2c, &cs42l43_i2c_regmap); if (IS_ERR(cs42l43->regmap)) @@ -58,7 +59,8 @@ static int cs42l43_i2c_probe(struct i2c_client *i2c) #if IS_ENABLED(CONFIG_OF) static const struct of_device_id cs42l43_of_match[] = { - { .compatible = "cirrus,cs42l43", }, + { .compatible = "cirrus,cs42l43", .data = (void *)CS42L43_DEVID_VAL }, + { .compatible = "cirrus,cs42l43b", .data = (void *)CS42L43B_DEVID_VAL }, {} }; MODULE_DEVICE_TABLE(of, cs42l43_of_match); @@ -66,7 +68,8 @@ MODULE_DEVICE_TABLE(of, cs42l43_of_match); #if IS_ENABLED(CONFIG_ACPI) static const struct acpi_device_id cs42l43_acpi_match[] = { - { "CSC4243", 0 }, + { "CSC4243", CS42L43_DEVID_VAL }, + { "CSC2A3B", CS42L43B_DEVID_VAL }, {} }; MODULE_DEVICE_TABLE(acpi, cs42l43_acpi_match); diff --git a/drivers/mfd/cs42l43-sdw.c b/drivers/mfd/cs42l43-sdw.c index 023f7e1a30f8..794c98378175 100644 --- a/drivers/mfd/cs42l43-sdw.c +++ b/drivers/mfd/cs42l43-sdw.c @@ -178,6 +178,7 @@ static int cs42l43_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id * cs42l43->dev = dev; cs42l43->sdw = sdw; + cs42l43->variant_id = (long)id->driver_data; cs42l43->regmap = devm_regmap_init_sdw(sdw, &cs42l43_sdw_regmap); if (IS_ERR(cs42l43->regmap)) @@ -188,7 +189,8 @@ static int cs42l43_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id * } static const struct sdw_device_id cs42l43_sdw_id[] = { - SDW_SLAVE_ENTRY(0x01FA, 0x4243, 0), + SDW_SLAVE_ENTRY(0x01FA, 0x4243, (void *) CS42L43_DEVID_VAL), + SDW_SLAVE_ENTRY(0x01FA, 0x2A3B, (void *) CS42L43B_DEVID_VAL), {} }; MODULE_DEVICE_TABLE(sdw, cs42l43_sdw_id); diff --git a/drivers/mfd/cs42l43.c b/drivers/mfd/cs42l43.c index 107cfb983fec..166881751e69 100644 --- a/drivers/mfd/cs42l43.c +++ b/drivers/mfd/cs42l43.c @@ -115,9 +115,14 @@ const struct reg_default cs42l43_reg_default[CS42L43_N_DEFAULTS] = { { CS42L43_DECIM_HPF_WNF_CTRL2, 0x00000001 }, { CS42L43_DECIM_HPF_WNF_CTRL3, 0x00000001 }, { CS42L43_DECIM_HPF_WNF_CTRL4, 0x00000001 }, + { CS42L43B_DECIM_HPF_WNF_CTRL5, 0x00000001 }, + { CS42L43B_DECIM_HPF_WNF_CTRL6, 0x00000001 }, { CS42L43_DMIC_PDM_CTRL, 0x00000000 }, { CS42L43_DECIM_VOL_CTRL_CH1_CH2, 0x20122012 }, { CS42L43_DECIM_VOL_CTRL_CH3_CH4, 0x20122012 }, + { CS42L43B_DECIM_VOL_CTRL_CH1_CH2, 0x20122012 }, + { CS42L43B_DECIM_VOL_CTRL_CH3_CH4, 0x20122012 }, + { CS42L43B_DECIM_VOL_CTRL_CH5_CH6, 0x20122012 }, { CS42L43_INTP_VOLUME_CTRL1, 0x00000180 }, { CS42L43_INTP_VOLUME_CTRL2, 0x00000180 }, { CS42L43_AMP1_2_VOL_RAMP, 0x00000022 }, @@ -155,8 +160,12 @@ const struct reg_default cs42l43_reg_default[CS42L43_N_DEFAULTS] = { { CS42L43_SWIRE_DP2_CH2_INPUT, 0x00000000 }, { CS42L43_SWIRE_DP3_CH1_INPUT, 0x00000000 }, { CS42L43_SWIRE_DP3_CH2_INPUT, 0x00000000 }, + { CS42L43B_SWIRE_DP3_CH3_INPUT, 0x00000000 }, + { CS42L43B_SWIRE_DP3_CH4_INPUT, 0x00000000 }, { CS42L43_SWIRE_DP4_CH1_INPUT, 0x00000000 }, { CS42L43_SWIRE_DP4_CH2_INPUT, 0x00000000 }, + { CS42L43B_SWIRE_DP4_CH3_INPUT, 0x00000000 }, + { CS42L43B_SWIRE_DP4_CH4_INPUT, 0x00000000 }, { CS42L43_ASRC_INT1_INPUT1, 0x00000000 }, { CS42L43_ASRC_INT2_INPUT1, 0x00000000 }, { CS42L43_ASRC_INT3_INPUT1, 0x00000000 }, @@ -169,10 +178,14 @@ const struct reg_default cs42l43_reg_default[CS42L43_N_DEFAULTS] = { { CS42L43_ISRC1INT2_INPUT1, 0x00000000 }, { CS42L43_ISRC1DEC1_INPUT1, 0x00000000 }, { CS42L43_ISRC1DEC2_INPUT1, 0x00000000 }, + { CS42L43B_ISRC1DEC3_INPUT1, 0x00000000 }, + { CS42L43B_ISRC1DEC4_INPUT1, 0x00000000 }, { CS42L43_ISRC2INT1_INPUT1, 0x00000000 }, { CS42L43_ISRC2INT2_INPUT1, 0x00000000 }, { CS42L43_ISRC2DEC1_INPUT1, 0x00000000 }, { CS42L43_ISRC2DEC2_INPUT1, 0x00000000 }, + { CS42L43B_ISRC2DEC3_INPUT1, 0x00000000 }, + { CS42L43B_ISRC2DEC4_INPUT1, 0x00000000 }, { CS42L43_EQ1MIX_INPUT1, 0x00800000 }, { CS42L43_EQ1MIX_INPUT2, 0x00800000 }, { CS42L43_EQ1MIX_INPUT3, 0x00800000 }, @@ -269,6 +282,8 @@ EXPORT_SYMBOL_NS_GPL(cs42l43_reg_default, "MFD_CS42L43"); bool cs42l43_readable_register(struct device *dev, unsigned int reg) { + struct cs42l43 *cs42l43 = dev_get_drvdata(dev); + switch (reg) { case CS42L43_DEVID: case CS42L43_REVID: @@ -292,7 +307,6 @@ bool cs42l43_readable_register(struct device *dev, unsigned int reg) case CS42L43_ADC_B_CTRL1 ... CS42L43_ADC_B_CTRL2: case CS42L43_DECIM_HPF_WNF_CTRL1 ... CS42L43_DECIM_HPF_WNF_CTRL4: case CS42L43_DMIC_PDM_CTRL: - case CS42L43_DECIM_VOL_CTRL_CH1_CH2 ... CS42L43_DECIM_VOL_CTRL_CH3_CH4: case CS42L43_INTP_VOLUME_CTRL1 ... CS42L43_INTP_VOLUME_CTRL2: case CS42L43_AMP1_2_VOL_RAMP: case CS42L43_ASP_CTRL: @@ -387,8 +401,16 @@ bool cs42l43_readable_register(struct device *dev, unsigned int reg) case CS42L43_BOOT_CONTROL: case CS42L43_BLOCK_EN: case CS42L43_SHUTTER_CONTROL: - case CS42L43_MCU_SW_REV ... CS42L43_MCU_RAM_MAX: - return true; + case CS42L43B_MCU_SW_REV ... CS42L43B_MCU_RAM_MAX: + return true; // registers present on all variants + case CS42L43_MCU_SW_REV ... CS42L43B_MCU_SW_REV - 1: + case CS42L43B_MCU_RAM_MAX + 1 ... CS42L43_MCU_RAM_MAX: + case CS42L43_DECIM_VOL_CTRL_CH1_CH2 ... CS42L43_DECIM_VOL_CTRL_CH3_CH4: + return cs42l43->variant_id == CS42L43_DEVID_VAL; // regs only in CS42L43 variant + case CS42L43B_DECIM_VOL_CTRL_CH1_CH2 ... CS42L43B_DECIM_HPF_WNF_CTRL6: + case CS42L43B_SWIRE_DP3_CH3_INPUT ... CS42L43B_SWIRE_DP4_CH4_INPUT: + case CS42L43B_ISRC1DEC3_INPUT1 ... CS42L43B_ISRC2DEC4_INPUT1: + return cs42l43->variant_id == CS42L43B_DEVID_VAL; // regs only in CS42L43B variant default: return false; } @@ -597,15 +619,27 @@ static int cs42l43_wait_for_attach(struct cs42l43 *cs42l43) static int cs42l43_mcu_stage_2_3(struct cs42l43 *cs42l43, bool shadow) { unsigned int need_reg = CS42L43_NEED_CONFIGS; + unsigned int boot_reg; unsigned int val; int ret; - if (shadow) - need_reg = CS42L43_FW_SH_BOOT_CFG_NEED_CONFIGS; + switch (cs42l43->variant_id) { + case CS42L43_DEVID_VAL: + if (shadow) + need_reg = CS42L43_FW_SH_BOOT_CFG_NEED_CONFIGS; + boot_reg = CS42L43_BOOT_STATUS; + break; + case CS42L43B_DEVID_VAL: + need_reg = CS42L43B_NEED_CONFIGS; + boot_reg = CS42L43B_BOOT_STATUS; + break; + default: + return -EINVAL; + } regmap_write(cs42l43->regmap, need_reg, 0); - ret = regmap_read_poll_timeout(cs42l43->regmap, CS42L43_BOOT_STATUS, + ret = regmap_read_poll_timeout(cs42l43->regmap, boot_reg, val, (val == CS42L43_MCU_BOOT_STAGE3), CS42L43_MCU_POLL_US, CS42L43_MCU_CMD_TIMEOUT_US); if (ret) { @@ -644,13 +678,25 @@ static int cs42l43_mcu_stage_3_2(struct cs42l43 *cs42l43) */ static int cs42l43_mcu_disable(struct cs42l43 *cs42l43) { - unsigned int val; + unsigned int val, cfg_reg, ctrl_reg; int ret; - regmap_write(cs42l43->regmap, CS42L43_FW_MISSION_CTRL_MM_MCU_CFG_REG, - CS42L43_FW_MISSION_CTRL_MM_MCU_CFG_DISABLE_VAL); - regmap_write(cs42l43->regmap, CS42L43_FW_MISSION_CTRL_MM_CTRL_SELECTION, - CS42L43_FW_MM_CTRL_MCU_SEL_MASK); + switch (cs42l43->variant_id) { + case CS42L43_DEVID_VAL: + cfg_reg = CS42L43_FW_MISSION_CTRL_MM_MCU_CFG_REG; + ctrl_reg = CS42L43_FW_MISSION_CTRL_MM_CTRL_SELECTION; + break; + case CS42L43B_DEVID_VAL: + cfg_reg = CS42L43B_FW_MISSION_CTRL_MM_MCU_CFG_REG; + ctrl_reg = CS42L43B_FW_MISSION_CTRL_MM_CTRL_SELECTION; + break; + default: + return -EINVAL; + } + + regmap_write(cs42l43->regmap, cfg_reg, CS42L43_FW_MISSION_CTRL_MM_MCU_CFG_DISABLE_VAL); + regmap_write(cs42l43->regmap, ctrl_reg, CS42L43_FW_MM_CTRL_MCU_SEL_MASK); + regmap_write(cs42l43->regmap, CS42L43_MCU_SW_INTERRUPT, CS42L43_CONTROL_IND_MASK); regmap_write(cs42l43->regmap, CS42L43_MCU_SW_INTERRUPT, 0); @@ -740,18 +786,32 @@ static int cs42l43_mcu_update_step(struct cs42l43 *cs42l43) { unsigned int mcu_rev, bios_rev, boot_status, secure_cfg; bool patched, shadow; + int boot_status_reg, mcu_sw_rev_reg; int ret; + switch (cs42l43->variant_id) { + case CS42L43_DEVID_VAL: + boot_status_reg = CS42L43_BOOT_STATUS; + mcu_sw_rev_reg = CS42L43_MCU_SW_REV; + break; + case CS42L43B_DEVID_VAL: + boot_status_reg = CS42L43B_BOOT_STATUS; + mcu_sw_rev_reg = CS42L43B_MCU_SW_REV; + break; + default: + return -EINVAL; + } + /* Clear any stale software interrupt bits. */ regmap_read(cs42l43->regmap, CS42L43_SOFT_INT, &mcu_rev); - ret = regmap_read(cs42l43->regmap, CS42L43_BOOT_STATUS, &boot_status); + ret = regmap_read(cs42l43->regmap, boot_status_reg, &boot_status); if (ret) { dev_err(cs42l43->dev, "Failed to read boot status: %d\n", ret); return ret; } - ret = regmap_read(cs42l43->regmap, CS42L43_MCU_SW_REV, &mcu_rev); + ret = regmap_read(cs42l43->regmap, mcu_sw_rev_reg, &mcu_rev); if (ret) { dev_err(cs42l43->dev, "Failed to read firmware revision: %d\n", ret); return ret; @@ -918,6 +978,13 @@ static void cs42l43_boot_work(struct work_struct *work) switch (devid) { case CS42L43_DEVID_VAL: + case CS42L43B_DEVID_VAL: + if (devid != cs42l43->variant_id) { + dev_err(cs42l43->dev, + "Device ID (0x%06x) does not match variant ID (0x%06lx)\n", + devid, cs42l43->variant_id); + goto err; + } break; default: dev_err(cs42l43->dev, "Unrecognised devid: 0x%06x\n", devid); diff --git a/drivers/mfd/cs42l43.h b/drivers/mfd/cs42l43.h index f3da783930f5..a0068f6572e2 100644 --- a/drivers/mfd/cs42l43.h +++ b/drivers/mfd/cs42l43.h @@ -9,7 +9,7 @@ #ifndef CS42L43_CORE_INT_H #define CS42L43_CORE_INT_H -#define CS42L43_N_DEFAULTS 176 +#define CS42L43_N_DEFAULTS 189 struct dev_pm_ops; struct device; diff --git a/include/linux/mfd/cs42l43-regs.h b/include/linux/mfd/cs42l43-regs.h index c39a49269cb7..68831f113589 100644 --- a/include/linux/mfd/cs42l43-regs.h +++ b/include/linux/mfd/cs42l43-regs.h @@ -1181,4 +1181,80 @@ /* CS42L43_FW_MISSION_CTRL_MM_MCU_CFG_REG */ #define CS42L43_FW_MISSION_CTRL_MM_MCU_CFG_DISABLE_VAL 0xF05AA50F +/* CS42L43B VARIANT REGISTERS */ +#define CS42L43B_DEVID_VAL 0x0042A43B + +#define CS42L43B_DECIM_VOL_CTRL_CH1_CH2 0x00008280 +#define CS42L43B_DECIM_VOL_CTRL_CH3_CH4 0x00008284 + +#define CS42L43B_DECIM_VOL_CTRL_CH5_CH6 0x00008290 +#define CS42L43B_DECIM_VOL_CTRL_UPDATE 0x0000829C + +#define CS42L43B_DECIM_HPF_WNF_CTRL5 0x000082A0 +#define CS42L43B_DECIM_HPF_WNF_CTRL6 0x000082A4 + +#define CS42L43B_SWIRE_DP3_CH3_INPUT 0x0000C320 +#define CS42L43B_SWIRE_DP3_CH4_INPUT 0x0000C330 +#define CS42L43B_SWIRE_DP4_CH3_INPUT 0x0000C340 +#define CS42L43B_SWIRE_DP4_CH4_INPUT 0x0000C350 + +#define CS42L43B_ISRC1DEC3_INPUT1 0x0000C780 +#define CS42L43B_ISRC1DEC4_INPUT1 0x0000C790 +#define CS42L43B_ISRC2DEC3_INPUT1 0x0000C7A0 +#define CS42L43B_ISRC2DEC4_INPUT1 0x0000C7B0 + +#define CS42L43B_FW_MISSION_CTRL_NEED_CONFIGS 0x00117E00 +#define CS42L43B_FW_MISSION_CTRL_HAVE_CONFIGS 0x00117E04 +#define CS42L43B_FW_MISSION_CTRL_PATCH_START_ADDR_REG 0x00117E08 +#define CS42L43B_FW_MISSION_CTRL_MM_CTRL_SELECTION 0x00117E0C +#define CS42L43B_FW_MISSION_CTRL_MM_MCU_CFG_REG 0x00117E10 + +#define CS42L43B_MCU_SW_REV 0x00117314 +#define CS42L43B_PATCH_START_ADDR 0x00117318 +#define CS42L43B_CONFIG_SELECTION 0x0011731C +#define CS42L43B_NEED_CONFIGS 0x00117320 +#define CS42L43B_BOOT_STATUS 0x00117330 + +#define CS42L43B_FW_MISSION_CTRL_NEED_CONFIGS 0x00117E00 +#define CS42L43B_FW_MISSION_CTRL_HAVE_CONFIGS 0x00117E04 +#define CS42L43B_FW_MISSION_CTRL_PATCH_START_ADDR_REG 0x00117E08 +#define CS42L43B_FW_MISSION_CTRL_MM_CTRL_SELECTION 0x00117E0C +#define CS42L43B_FW_MISSION_CTRL_MM_MCU_CFG_REG 0x00117E10 + +#define CS42L43B_MCU_RAM_MAX 0x00117FFF + +/* CS42L43B_DECIM_DECIM_VOL_CTRL_CH5_CH6 */ +#define CS42L43B_DECIM6_MUTE_MASK 0x80000000 +#define CS42L43B_DECIM6_MUTE_SHIFT 31 +#define CS42L43B_DECIM6_VOL_MASK 0x3FC00000 +#define CS42L43B_DECIM6_VOL_SHIFT 22 +#define CS42L43B_DECIM6_PATH1_VOL_FALL_RATE_MASK 0x00380000 +#define CS42L43B_DECIM6_PATH1_VOL_FALL_RATE_SHIFT 19 +#define CS42L43B_DECIM6_PATH1_VOL_RISE_RATE_MASK 0x00070000 +#define CS42L43B_DECIM6_PATH1_VOL_RISE_RATE_SHIFT 16 +#define CS42L43B_DECIM5_MUTE_MASK 0x00008000 +#define CS42L43B_DECIM5_MUTE_SHIFT 15 +#define CS42L43B_DECIM5_VOL_MASK 0x00003FC0 +#define CS42L43B_DECIM5_VOL_SHIFT 6 +#define CS42L43B_DECIM5_PATH1_VOL_FALL_RATE_MASK 0x00000038 +#define CS42L43B_DECIM5_PATH1_VOL_FALL_RATE_SHIFT 3 +#define CS42L43B_DECIM5_PATH1_VOL_RISE_RATE_MASK 0x00000007 +#define CS42L43B_DECIM5_PATH1_VOL_RISE_RATE_SHIFT 0 + +/* CS42L43B_DECIM_VOL_CTRL_UPDATE */ +#define CS42L43B_DECIM6_PATH1_VOL_TRIG_MASK 0x00000800 +#define CS42L43B_DECIM6_PATH1_VOL_TRIG_SHIFT 11 +#define CS42L43B_DECIM5_PATH1_VOL_TRIG_MASK 0x00000100 +#define CS42L43B_DECIM5_PATH1_VOL_TRIG_SHIFT 8 +#define CS42L43B_DECIM4_VOL_UPDATE_MASK 0x00000020 +#define CS42L43B_DECIM4_VOL_UPDATE_SHIFT 5 + +/* CS42L43_ISRC1_CTRL..CS42L43_ISRC2_CTRL */ +#define CS42L43B_ISRC_DEC4_EN_MASK 0x00000008 +#define CS42L43B_ISRC_DEC4_EN_SHIFT 3 +#define CS42L43B_ISRC_DEC4_EN_WIDTH 1 +#define CS42L43B_ISRC_DEC3_EN_MASK 0x00000004 +#define CS42L43B_ISRC_DEC3_EN_SHIFT 2 +#define CS42L43B_ISRC_DEC3_EN_WIDTH 1 + #endif /* CS42L43_CORE_REGS_H */ diff --git a/include/linux/mfd/cs42l43.h b/include/linux/mfd/cs42l43.h index 2239d8585e78..ff0f7e365a19 100644 --- a/include/linux/mfd/cs42l43.h +++ b/include/linux/mfd/cs42l43.h @@ -98,6 +98,7 @@ struct cs42l43 { bool sdw_pll_active; bool attached; bool hw_lock; + long variant_id; }; #endif /* CS42L43_CORE_EXT_H */ From 0a208adefecb287d22321054470d4619cb303839 Mon Sep 17 00:00:00 2001 From: Maciej Strozek Date: Fri, 6 Mar 2026 15:28:11 +0000 Subject: [PATCH 066/359] ASoC: cs42l43: Add support for the B variant Introducing CS42L43B codec, a variant of CS42L43 which can be driven by the same driver. Changes in CS42L43 driver specific for CS42L43B: - Decimator 1 and 2 are dedicated to ADC, can't be selected for PDM - Decimators 3 and 4 are connected to PDM1 - Added Decimator 5 and 6 for PDM2 - Supports SoundWire Clock Gearing - Updated ROM requiring no patching - Reduced RAM space - Each ISRC has 4 decimators now Signed-off-by: Maciej Strozek Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260306152829.3130530-5-mstrozek@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs42l43.c | 756 ++++++++++++++++++++++++++++++------- sound/soc/codecs/cs42l43.h | 4 +- 2 files changed, 625 insertions(+), 135 deletions(-) diff --git a/sound/soc/codecs/cs42l43.c b/sound/soc/codecs/cs42l43.c index fd02d8a57e0f..f0d6ff0b2976 100644 --- a/sound/soc/codecs/cs42l43.c +++ b/sound/soc/codecs/cs42l43.c @@ -45,12 +45,25 @@ static SOC_VALUE_ENUM_SINGLE_DECL(cs42l43_##name##_enum, reg, \ static const struct snd_kcontrol_new cs42l43_##name##_mux = \ SOC_DAPM_ENUM("Route", cs42l43_##name##_enum) +#define CS42L43B_DECL_MUX(name, reg) \ +static SOC_VALUE_ENUM_SINGLE_DECL(cs42l43_##name##_enum, reg, \ + 0, CS42L43_MIXER_SRC_MASK, \ + cs42l43b_mixer_texts, cs42l43b_mixer_values); \ +static const struct snd_kcontrol_new cs42l43_##name##_mux = \ + SOC_DAPM_ENUM("Route", cs42l43_##name##_enum) + #define CS42L43_DECL_MIXER(name, reg) \ CS42L43_DECL_MUX(name##_in1, reg); \ CS42L43_DECL_MUX(name##_in2, reg + 0x4); \ CS42L43_DECL_MUX(name##_in3, reg + 0x8); \ CS42L43_DECL_MUX(name##_in4, reg + 0xC) +#define CS42L43B_DECL_MIXER(name, reg) \ + CS42L43B_DECL_MUX(name##_in1, reg); \ + CS42L43B_DECL_MUX(name##_in2, reg + 0x4); \ + CS42L43B_DECL_MUX(name##_in3, reg + 0x8); \ + CS42L43B_DECL_MUX(name##_in4, reg + 0xC) + #define CS42L43_DAPM_MUX(name_str, name) \ SND_SOC_DAPM_MUX(name_str " Input", SND_SOC_NOPM, 0, 0, &cs42l43_##name##_mux) @@ -99,11 +112,23 @@ static const struct snd_kcontrol_new cs42l43_##name##_mux = \ { name_str, "EQ1", "EQ" }, \ { name_str, "EQ2", "EQ" } +#define CS42L43B_BASE_ROUTES(name_str) \ + { name_str, "Decimator 5", "Decimator 5" }, \ + { name_str, "Decimator 6", "Decimator 6" }, \ + { name_str, "ISRC1 DEC3", "ISRC1DEC3" }, \ + { name_str, "ISRC1 DEC4", "ISRC1DEC4" }, \ + { name_str, "ISRC2 DEC3", "ISRC2DEC3" }, \ + { name_str, "ISRC2 DEC4", "ISRC2DEC4" } + #define CS42L43_MUX_ROUTES(name_str, widget) \ { widget, NULL, name_str " Input" }, \ { name_str " Input", NULL, "Mixer Core" }, \ CS42L43_BASE_ROUTES(name_str " Input") +#define CS42L43B_MUX_ROUTES(name_str, widget) \ + CS42L43_MUX_ROUTES(name_str, widget), \ + CS42L43B_BASE_ROUTES(name_str " Input") + #define CS42L43_MIXER_ROUTES(name_str, widget) \ { name_str " Mixer", NULL, name_str " Input 1" }, \ { name_str " Mixer", NULL, name_str " Input 2" }, \ @@ -116,6 +141,13 @@ static const struct snd_kcontrol_new cs42l43_##name##_mux = \ CS42L43_BASE_ROUTES(name_str " Input 3"), \ CS42L43_BASE_ROUTES(name_str " Input 4") +#define CS42L43B_MIXER_ROUTES(name_str, widget) \ + CS42L43_MIXER_ROUTES(name_str, widget), \ + CS42L43B_BASE_ROUTES(name_str " Input 1"), \ + CS42L43B_BASE_ROUTES(name_str " Input 2"), \ + CS42L43B_BASE_ROUTES(name_str " Input 3"), \ + CS42L43B_BASE_ROUTES(name_str " Input 4") + #define CS42L43_MIXER_VOLUMES(name_str, base) \ SOC_SINGLE_RANGE_TLV(name_str " Input 1 Volume", base, \ CS42L43_MIXER_VOL_SHIFT, 0x20, 0x50, 0, \ @@ -300,6 +332,7 @@ static int cs42l43_startup(struct snd_pcm_substream *substream, struct snd_soc_d struct snd_soc_component *component = dai->component; struct cs42l43_codec *priv = snd_soc_component_get_drvdata(component); struct cs42l43 *cs42l43 = priv->core; + int ret; int provider = !dai->id || !!regmap_test_bits(cs42l43->regmap, CS42L43_ASP_CLK_CONFIG2, CS42L43_ASP_MASTER_MODE_MASK); @@ -309,6 +342,14 @@ static int cs42l43_startup(struct snd_pcm_substream *substream, struct snd_soc_d else priv->constraint.mask = CS42L43_CONSUMER_RATE_MASK; + if (cs42l43->variant_id == CS42L43_DEVID_VAL && (dai->id == 3 || dai->id == 4)) { + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 1, 2); + if (ret < 0) + return ret; + } + return snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &priv->constraint); @@ -590,12 +631,25 @@ static int cs42l43_dai_probe(struct snd_soc_dai *dai) "Decimator 2 Switch", "Decimator 3 Switch", "Decimator 4 Switch", + "Decimator 5 Switch", + "Decimator 6 Switch", }; - int i; + int control_size, i; static_assert(ARRAY_SIZE(controls) == ARRAY_SIZE(priv->kctl)); - for (i = 0; i < ARRAY_SIZE(controls); i++) { + switch (priv->core->variant_id) { + case CS42L43_DEVID_VAL: + control_size = ARRAY_SIZE(controls) - 2; // ignore Decimator 5 and 6 + break; + case CS42L43B_DEVID_VAL: + control_size = ARRAY_SIZE(controls); + break; + default: + return -EINVAL; + } + + for (i = 0; i < control_size; i++) { if (priv->kctl[i]) continue; @@ -703,7 +757,7 @@ static struct snd_soc_dai_driver cs42l43_dais[] = { .capture = { .stream_name = "DP3 Capture", .channels_min = 1, - .channels_max = 2, + .channels_max = 4, .rates = SNDRV_PCM_RATE_KNOT, .formats = CS42L43_SDW_FORMATS, }, @@ -715,7 +769,7 @@ static struct snd_soc_dai_driver cs42l43_dais[] = { .capture = { .stream_name = "DP4 Capture", .channels_min = 1, - .channels_max = 2, + .channels_max = 4, .rates = SNDRV_PCM_RATE_KNOT, .formats = CS42L43_SDW_FORMATS, }, @@ -808,6 +862,10 @@ static SOC_ENUM_SINGLE_DECL(cs42l43_dec3_wnf_corner, CS42L43_DECIM_HPF_WNF_CTRL3 CS42L43_DECIM_WNF_CF_SHIFT, cs42l43_wnf_corner_text); static SOC_ENUM_SINGLE_DECL(cs42l43_dec4_wnf_corner, CS42L43_DECIM_HPF_WNF_CTRL4, CS42L43_DECIM_WNF_CF_SHIFT, cs42l43_wnf_corner_text); +static SOC_ENUM_SINGLE_DECL(cs42l43b_dec5_wnf_corner, CS42L43B_DECIM_HPF_WNF_CTRL5, + CS42L43_DECIM_WNF_CF_SHIFT, cs42l43_wnf_corner_text); +static SOC_ENUM_SINGLE_DECL(cs42l43b_dec6_wnf_corner, CS42L43B_DECIM_HPF_WNF_CTRL6, + CS42L43_DECIM_WNF_CF_SHIFT, cs42l43_wnf_corner_text); static const char * const cs42l43_hpf_corner_text[] = { "3Hz", "12Hz", "48Hz", "96Hz", @@ -821,6 +879,10 @@ static SOC_ENUM_SINGLE_DECL(cs42l43_dec3_hpf_corner, CS42L43_DECIM_HPF_WNF_CTRL3 CS42L43_DECIM_HPF_CF_SHIFT, cs42l43_hpf_corner_text); static SOC_ENUM_SINGLE_DECL(cs42l43_dec4_hpf_corner, CS42L43_DECIM_HPF_WNF_CTRL4, CS42L43_DECIM_HPF_CF_SHIFT, cs42l43_hpf_corner_text); +static SOC_ENUM_SINGLE_DECL(cs42l43b_dec5_hpf_corner, CS42L43B_DECIM_HPF_WNF_CTRL5, + CS42L43_DECIM_HPF_CF_SHIFT, cs42l43_hpf_corner_text); +static SOC_ENUM_SINGLE_DECL(cs42l43b_dec6_hpf_corner, CS42L43B_DECIM_HPF_WNF_CTRL6, + CS42L43_DECIM_HPF_CF_SHIFT, cs42l43_hpf_corner_text); static SOC_ENUM_SINGLE_DECL(cs42l43_dec1_ramp_up, CS42L43_DECIM_VOL_CTRL_CH1_CH2, CS42L43_DECIM1_VI_RAMP_SHIFT, cs42l43_ramp_text); @@ -839,6 +901,31 @@ static SOC_ENUM_SINGLE_DECL(cs42l43_dec4_ramp_up, CS42L43_DECIM_VOL_CTRL_CH3_CH4 static SOC_ENUM_SINGLE_DECL(cs42l43_dec4_ramp_down, CS42L43_DECIM_VOL_CTRL_CH3_CH4, CS42L43_DECIM4_VD_RAMP_SHIFT, cs42l43_ramp_text); +static SOC_ENUM_SINGLE_DECL(cs42l43b_dec1_ramp_up, CS42L43B_DECIM_VOL_CTRL_CH1_CH2, + CS42L43_DECIM1_VI_RAMP_SHIFT, cs42l43_ramp_text); +static SOC_ENUM_SINGLE_DECL(cs42l43b_dec1_ramp_down, CS42L43B_DECIM_VOL_CTRL_CH1_CH2, + CS42L43_DECIM1_VD_RAMP_SHIFT, cs42l43_ramp_text); +static SOC_ENUM_SINGLE_DECL(cs42l43b_dec2_ramp_up, CS42L43B_DECIM_VOL_CTRL_CH1_CH2, + CS42L43_DECIM2_VI_RAMP_SHIFT, cs42l43_ramp_text); +static SOC_ENUM_SINGLE_DECL(cs42l43b_dec2_ramp_down, CS42L43B_DECIM_VOL_CTRL_CH1_CH2, + CS42L43_DECIM2_VD_RAMP_SHIFT, cs42l43_ramp_text); +static SOC_ENUM_SINGLE_DECL(cs42l43b_dec3_ramp_up, CS42L43B_DECIM_VOL_CTRL_CH3_CH4, + CS42L43_DECIM3_VI_RAMP_SHIFT, cs42l43_ramp_text); +static SOC_ENUM_SINGLE_DECL(cs42l43b_dec3_ramp_down, CS42L43B_DECIM_VOL_CTRL_CH3_CH4, + CS42L43_DECIM3_VD_RAMP_SHIFT, cs42l43_ramp_text); +static SOC_ENUM_SINGLE_DECL(cs42l43b_dec4_ramp_up, CS42L43B_DECIM_VOL_CTRL_CH3_CH4, + CS42L43_DECIM4_VI_RAMP_SHIFT, cs42l43_ramp_text); +static SOC_ENUM_SINGLE_DECL(cs42l43b_dec4_ramp_down, CS42L43B_DECIM_VOL_CTRL_CH3_CH4, + CS42L43_DECIM4_VD_RAMP_SHIFT, cs42l43_ramp_text); +static SOC_ENUM_SINGLE_DECL(cs42l43b_dec5_ramp_up, CS42L43B_DECIM_VOL_CTRL_CH5_CH6, + CS42L43B_DECIM5_PATH1_VOL_RISE_RATE_SHIFT, cs42l43_ramp_text); +static SOC_ENUM_SINGLE_DECL(cs42l43b_dec5_ramp_down, CS42L43B_DECIM_VOL_CTRL_CH5_CH6, + CS42L43B_DECIM5_PATH1_VOL_FALL_RATE_SHIFT, cs42l43_ramp_text); +static SOC_ENUM_SINGLE_DECL(cs42l43b_dec6_ramp_up, CS42L43B_DECIM_VOL_CTRL_CH5_CH6, + CS42L43B_DECIM6_PATH1_VOL_RISE_RATE_SHIFT, cs42l43_ramp_text); +static SOC_ENUM_SINGLE_DECL(cs42l43b_dec6_ramp_down, CS42L43B_DECIM_VOL_CTRL_CH5_CH6, + CS42L43B_DECIM6_PATH1_VOL_FALL_RATE_SHIFT, cs42l43_ramp_text); + static DECLARE_TLV_DB_SCALE(cs42l43_speaker_tlv, -6400, 50, 0); static SOC_ENUM_SINGLE_DECL(cs42l43_speaker_ramp_up, CS42L43_AMP1_2_VOL_RAMP, @@ -898,6 +985,37 @@ static const unsigned int cs42l43_mixer_values[] = { 0x58, 0x59, // EQ1, 2 }; +static const char * const cs42l43b_mixer_texts[] = { + "None", + "Tone Generator 1", "Tone Generator 2", + "Decimator 1", "Decimator 2", "Decimator 3", "Decimator 4", "Decimator 5", "Decimator 6", + "ASPRX1", "ASPRX2", "ASPRX3", "ASPRX4", "ASPRX5", "ASPRX6", + "DP5RX1", "DP5RX2", "DP6RX1", "DP6RX2", "DP7RX1", "DP7RX2", + "ASRC INT1", "ASRC INT2", "ASRC INT3", "ASRC INT4", + "ASRC DEC1", "ASRC DEC2", "ASRC DEC3", "ASRC DEC4", + "ISRC1 INT1", "ISRC1 INT2", + "ISRC1 DEC1", "ISRC1 DEC2", "ISRC1 DEC3", "ISRC1 DEC4", + "ISRC2 INT1", "ISRC2 INT2", + "ISRC2 DEC1", "ISRC2 DEC2", "ISRC2 DEC3", "ISRC2 DEC4", + "EQ1", "EQ2", +}; + +static const unsigned int cs42l43b_mixer_values[] = { + 0x00, // None + 0x04, 0x05, // Tone Generator 1, 2 + 0x10, 0x11, 0x80, 0x81, 0x12, 0x13, // Decimator 1, 2, 3, 4, 5, 6 + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, // ASPRX1,2,3,4,5,6 + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, // DP5, 6, 7RX1, 2 + 0x40, 0x41, 0x42, 0x43, // ASRC INT1, 2, 3, 4 + 0x44, 0x45, 0x46, 0x47, // ASRC DEC1, 2, 3, 4 + 0x50, 0x51, // ISRC1 INT1, 2 + 0x52, 0x53, 0x78, 0x79, // ISRC1 DEC1, 2, 3, 4 + 0x54, 0x55, // ISRC2 INT1, 2 + 0x56, 0x57, 0x7A, 0x7B, // ISRC2 DEC1, 2, 3, 4 + 0x58, 0x59, // EQ1, 2 +}; + +/* A variant */ CS42L43_DECL_MUX(asptx1, CS42L43_ASPTX1_INPUT); CS42L43_DECL_MUX(asptx2, CS42L43_ASPTX2_INPUT); CS42L43_DECL_MUX(asptx3, CS42L43_ASPTX3_INPUT); @@ -946,6 +1064,63 @@ CS42L43_DECL_MIXER(amp2, CS42L43_AMP2MIX_INPUT1); CS42L43_DECL_MIXER(amp3, CS42L43_AMP3MIX_INPUT1); CS42L43_DECL_MIXER(amp4, CS42L43_AMP4MIX_INPUT1); +/* B variant */ +CS42L43B_DECL_MUX(b_asptx1, CS42L43_ASPTX1_INPUT); +CS42L43B_DECL_MUX(b_asptx2, CS42L43_ASPTX2_INPUT); +CS42L43B_DECL_MUX(b_asptx3, CS42L43_ASPTX3_INPUT); +CS42L43B_DECL_MUX(b_asptx4, CS42L43_ASPTX4_INPUT); +CS42L43B_DECL_MUX(b_asptx5, CS42L43_ASPTX5_INPUT); +CS42L43B_DECL_MUX(b_asptx6, CS42L43_ASPTX6_INPUT); + +CS42L43B_DECL_MUX(b_dp1tx1, CS42L43_SWIRE_DP1_CH1_INPUT); +CS42L43B_DECL_MUX(b_dp1tx2, CS42L43_SWIRE_DP1_CH2_INPUT); +CS42L43B_DECL_MUX(b_dp1tx3, CS42L43_SWIRE_DP1_CH3_INPUT); +CS42L43B_DECL_MUX(b_dp1tx4, CS42L43_SWIRE_DP1_CH4_INPUT); +CS42L43B_DECL_MUX(b_dp2tx1, CS42L43_SWIRE_DP2_CH1_INPUT); +CS42L43B_DECL_MUX(b_dp2tx2, CS42L43_SWIRE_DP2_CH2_INPUT); +CS42L43B_DECL_MUX(b_dp3tx1, CS42L43_SWIRE_DP3_CH1_INPUT); +CS42L43B_DECL_MUX(b_dp3tx2, CS42L43_SWIRE_DP3_CH2_INPUT); +CS42L43B_DECL_MUX(b_dp3tx3, CS42L43B_SWIRE_DP3_CH3_INPUT); +CS42L43B_DECL_MUX(b_dp3tx4, CS42L43B_SWIRE_DP3_CH4_INPUT); +CS42L43B_DECL_MUX(b_dp4tx1, CS42L43_SWIRE_DP4_CH1_INPUT); +CS42L43B_DECL_MUX(b_dp4tx2, CS42L43_SWIRE_DP4_CH2_INPUT); +CS42L43B_DECL_MUX(b_dp4tx3, CS42L43B_SWIRE_DP4_CH3_INPUT); +CS42L43B_DECL_MUX(b_dp4tx4, CS42L43B_SWIRE_DP4_CH4_INPUT); + +CS42L43B_DECL_MUX(b_asrcint1, CS42L43_ASRC_INT1_INPUT1); +CS42L43B_DECL_MUX(b_asrcint2, CS42L43_ASRC_INT2_INPUT1); +CS42L43B_DECL_MUX(b_asrcint3, CS42L43_ASRC_INT3_INPUT1); +CS42L43B_DECL_MUX(b_asrcint4, CS42L43_ASRC_INT4_INPUT1); +CS42L43B_DECL_MUX(b_asrcdec1, CS42L43_ASRC_DEC1_INPUT1); +CS42L43B_DECL_MUX(b_asrcdec2, CS42L43_ASRC_DEC2_INPUT1); +CS42L43B_DECL_MUX(b_asrcdec3, CS42L43_ASRC_DEC3_INPUT1); +CS42L43B_DECL_MUX(b_asrcdec4, CS42L43_ASRC_DEC4_INPUT1); + +CS42L43B_DECL_MUX(b_isrc1int1, CS42L43_ISRC1INT1_INPUT1); +CS42L43B_DECL_MUX(b_isrc1int2, CS42L43_ISRC1INT2_INPUT1); +CS42L43B_DECL_MUX(b_isrc1dec1, CS42L43_ISRC1DEC1_INPUT1); +CS42L43B_DECL_MUX(b_isrc1dec2, CS42L43_ISRC1DEC2_INPUT1); +CS42L43B_DECL_MUX(b_isrc1dec3, CS42L43B_ISRC1DEC3_INPUT1); +CS42L43B_DECL_MUX(b_isrc1dec4, CS42L43B_ISRC1DEC4_INPUT1); +CS42L43B_DECL_MUX(b_isrc2int1, CS42L43_ISRC2INT1_INPUT1); +CS42L43B_DECL_MUX(b_isrc2int2, CS42L43_ISRC2INT2_INPUT1); +CS42L43B_DECL_MUX(b_isrc2dec1, CS42L43_ISRC2DEC1_INPUT1); +CS42L43B_DECL_MUX(b_isrc2dec2, CS42L43_ISRC2DEC2_INPUT1); +CS42L43B_DECL_MUX(b_isrc2dec3, CS42L43B_ISRC2DEC3_INPUT1); +CS42L43B_DECL_MUX(b_isrc2dec4, CS42L43B_ISRC2DEC4_INPUT1); + +CS42L43B_DECL_MUX(b_spdif1, CS42L43_SPDIF1_INPUT1); +CS42L43B_DECL_MUX(b_spdif2, CS42L43_SPDIF2_INPUT1); + +CS42L43B_DECL_MIXER(b_eq1, CS42L43_EQ1MIX_INPUT1); +CS42L43B_DECL_MIXER(b_eq2, CS42L43_EQ2MIX_INPUT1); + +CS42L43B_DECL_MIXER(b_amp1, CS42L43_AMP1MIX_INPUT1); +CS42L43B_DECL_MIXER(b_amp2, CS42L43_AMP2MIX_INPUT1); + +CS42L43B_DECL_MIXER(b_amp3, CS42L43_AMP3MIX_INPUT1); +CS42L43B_DECL_MIXER(b_amp4, CS42L43_AMP4MIX_INPUT1); + static int cs42l43_dapm_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -1174,44 +1349,6 @@ static const struct snd_kcontrol_new cs42l43_controls[] = { SOC_ENUM("Decimator 3 HPF Corner Frequency", cs42l43_dec3_hpf_corner), SOC_ENUM("Decimator 4 HPF Corner Frequency", cs42l43_dec4_hpf_corner), - SOC_SINGLE_TLV("Decimator 1 Volume", CS42L43_DECIM_VOL_CTRL_CH1_CH2, - CS42L43_DECIM1_VOL_SHIFT, 0xBF, 0, cs42l43_dec_tlv), - SOC_SINGLE_EXT("Decimator 1 Switch", CS42L43_DECIM_VOL_CTRL_CH1_CH2, - CS42L43_DECIM1_MUTE_SHIFT, 1, 1, - cs42l43_decim_get, cs42l43_dapm_put_volsw), - SOC_SINGLE_TLV("Decimator 2 Volume", CS42L43_DECIM_VOL_CTRL_CH1_CH2, - CS42L43_DECIM2_VOL_SHIFT, 0xBF, 0, cs42l43_dec_tlv), - SOC_SINGLE_EXT("Decimator 2 Switch", CS42L43_DECIM_VOL_CTRL_CH1_CH2, - CS42L43_DECIM2_MUTE_SHIFT, 1, 1, - cs42l43_decim_get, cs42l43_dapm_put_volsw), - SOC_SINGLE_TLV("Decimator 3 Volume", CS42L43_DECIM_VOL_CTRL_CH3_CH4, - CS42L43_DECIM3_VOL_SHIFT, 0xBF, 0, cs42l43_dec_tlv), - SOC_SINGLE_EXT("Decimator 3 Switch", CS42L43_DECIM_VOL_CTRL_CH3_CH4, - CS42L43_DECIM3_MUTE_SHIFT, 1, 1, - cs42l43_decim_get, cs42l43_dapm_put_volsw), - SOC_SINGLE_TLV("Decimator 4 Volume", CS42L43_DECIM_VOL_CTRL_CH3_CH4, - CS42L43_DECIM4_VOL_SHIFT, 0xBF, 0, cs42l43_dec_tlv), - SOC_SINGLE_EXT("Decimator 4 Switch", CS42L43_DECIM_VOL_CTRL_CH3_CH4, - CS42L43_DECIM4_MUTE_SHIFT, 1, 1, - cs42l43_decim_get, cs42l43_dapm_put_volsw), - - SOC_ENUM_EXT("Decimator 1 Ramp Up", cs42l43_dec1_ramp_up, - cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), - SOC_ENUM_EXT("Decimator 1 Ramp Down", cs42l43_dec1_ramp_down, - cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), - SOC_ENUM_EXT("Decimator 2 Ramp Up", cs42l43_dec2_ramp_up, - cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), - SOC_ENUM_EXT("Decimator 2 Ramp Down", cs42l43_dec2_ramp_down, - cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), - SOC_ENUM_EXT("Decimator 3 Ramp Up", cs42l43_dec3_ramp_up, - cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), - SOC_ENUM_EXT("Decimator 3 Ramp Down", cs42l43_dec3_ramp_down, - cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), - SOC_ENUM_EXT("Decimator 4 Ramp Up", cs42l43_dec4_ramp_up, - cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), - SOC_ENUM_EXT("Decimator 4 Ramp Down", cs42l43_dec4_ramp_down, - cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), - SOC_DOUBLE_R_EXT("Speaker Digital Switch", CS42L43_INTP_VOLUME_CTRL1, CS42L43_INTP_VOLUME_CTRL2, CS42L43_AMP_MUTE_SHIFT, 1, 1, @@ -1601,35 +1738,81 @@ static int cs42l43_mic_ev(struct snd_soc_dapm_widget *w, unsigned int *val; int ret; - switch (w->shift) { - case CS42L43_ADC1_EN_SHIFT: - case CS42L43_PDM1_DIN_L_EN_SHIFT: - reg = CS42L43_DECIM_VOL_CTRL_CH1_CH2; - ramp = CS42L43_DECIM1_VD_RAMP_MASK; - mute = CS42L43_DECIM1_MUTE_MASK; - val = &priv->decim_cache[0]; - break; - case CS42L43_ADC2_EN_SHIFT: - case CS42L43_PDM1_DIN_R_EN_SHIFT: - reg = CS42L43_DECIM_VOL_CTRL_CH1_CH2; - ramp = CS42L43_DECIM2_VD_RAMP_MASK; - mute = CS42L43_DECIM2_MUTE_MASK; - val = &priv->decim_cache[1]; - break; - case CS42L43_PDM2_DIN_L_EN_SHIFT: - reg = CS42L43_DECIM_VOL_CTRL_CH3_CH4; - ramp = CS42L43_DECIM3_VD_RAMP_MASK; - mute = CS42L43_DECIM3_MUTE_MASK; - val = &priv->decim_cache[2]; - break; - case CS42L43_PDM2_DIN_R_EN_SHIFT: - reg = CS42L43_DECIM_VOL_CTRL_CH3_CH4; - ramp = CS42L43_DECIM4_VD_RAMP_MASK; - mute = CS42L43_DECIM4_MUTE_MASK; - val = &priv->decim_cache[3]; - break; - default: - dev_err(priv->dev, "Invalid microphone shift: %d\n", w->shift); + if (cs42l43->variant_id == CS42L43_DEVID_VAL) { + switch (w->shift) { + case CS42L43_ADC1_EN_SHIFT: + case CS42L43_PDM1_DIN_L_EN_SHIFT: + reg = CS42L43_DECIM_VOL_CTRL_CH1_CH2; + ramp = CS42L43_DECIM1_VD_RAMP_MASK; + mute = CS42L43_DECIM1_MUTE_MASK; + val = &priv->decim_cache[0]; + break; + case CS42L43_ADC2_EN_SHIFT: + case CS42L43_PDM1_DIN_R_EN_SHIFT: + reg = CS42L43_DECIM_VOL_CTRL_CH1_CH2; + ramp = CS42L43_DECIM2_VD_RAMP_MASK; + mute = CS42L43_DECIM2_MUTE_MASK; + val = &priv->decim_cache[1]; + break; + case CS42L43_PDM2_DIN_L_EN_SHIFT: + reg = CS42L43_DECIM_VOL_CTRL_CH3_CH4; + ramp = CS42L43_DECIM3_VD_RAMP_MASK; + mute = CS42L43_DECIM3_MUTE_MASK; + val = &priv->decim_cache[2]; + break; + case CS42L43_PDM2_DIN_R_EN_SHIFT: + reg = CS42L43_DECIM_VOL_CTRL_CH3_CH4; + ramp = CS42L43_DECIM4_VD_RAMP_MASK; + mute = CS42L43_DECIM4_MUTE_MASK; + val = &priv->decim_cache[3]; + break; + default: + dev_err(priv->dev, "Invalid microphone shift: %d\n", w->shift); + return -EINVAL; + } + } else if (cs42l43->variant_id == CS42L43B_DEVID_VAL) { + switch (w->shift) { + case CS42L43_ADC1_EN_SHIFT: + reg = CS42L43B_DECIM_VOL_CTRL_CH1_CH2; + ramp = CS42L43_DECIM1_VD_RAMP_MASK; + mute = CS42L43_DECIM1_MUTE_MASK; + val = &priv->decim_cache[0]; + break; + case CS42L43_ADC2_EN_SHIFT: + reg = CS42L43B_DECIM_VOL_CTRL_CH1_CH2; + ramp = CS42L43_DECIM2_VD_RAMP_MASK; + mute = CS42L43_DECIM2_MUTE_MASK; + val = &priv->decim_cache[1]; + break; + case CS42L43_PDM1_DIN_L_EN_SHIFT: + reg = CS42L43B_DECIM_VOL_CTRL_CH3_CH4; + ramp = CS42L43_DECIM3_VD_RAMP_MASK; + mute = CS42L43_DECIM3_MUTE_MASK; + val = &priv->decim_cache[2]; + break; + case CS42L43_PDM1_DIN_R_EN_SHIFT: + reg = CS42L43B_DECIM_VOL_CTRL_CH3_CH4; + ramp = CS42L43_DECIM4_VD_RAMP_MASK; + mute = CS42L43_DECIM4_MUTE_MASK; + val = &priv->decim_cache[3]; + break; + case CS42L43_PDM2_DIN_L_EN_SHIFT: + reg = CS42L43B_DECIM_VOL_CTRL_CH5_CH6; + ramp = CS42L43B_DECIM5_PATH1_VOL_FALL_RATE_MASK; + mute = CS42L43B_DECIM5_MUTE_MASK; + val = &priv->decim_cache[4]; + break; + case CS42L43_PDM2_DIN_R_EN_SHIFT: + reg = CS42L43B_DECIM_VOL_CTRL_CH5_CH6; + ramp = CS42L43B_DECIM6_PATH1_VOL_FALL_RATE_MASK; + mute = CS42L43B_DECIM6_MUTE_MASK; + val = &priv->decim_cache[5]; + break; + default: + dev_err(priv->dev, "Invalid microphone shift: %d\n", w->shift); + return -EINVAL; + } + } else { return -EINVAL; } @@ -1722,11 +1905,6 @@ static const struct snd_soc_dapm_widget cs42l43_widgets[] = { 0, NULL, 0, cs42l43_mic_ev, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), - SND_SOC_DAPM_MUX("Decimator 1 Mode", SND_SOC_NOPM, 0, 0, - &cs42l43_dec_mode_ctl[0]), - SND_SOC_DAPM_MUX("Decimator 2 Mode", SND_SOC_NOPM, 0, 0, - &cs42l43_dec_mode_ctl[1]), - SND_SOC_DAPM_PGA("Decimator 1", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("Decimator 2", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("Decimator 3", SND_SOC_NOPM, 0, 0, NULL, 0), @@ -1871,53 +2049,6 @@ static const struct snd_soc_dapm_widget cs42l43_widgets[] = { SND_SOC_DAPM_SUPPLY("Mixer Core", CS42L43_BLOCK_EN6, CS42L43_MIXER_EN_SHIFT, 0, NULL, 0), - CS42L43_DAPM_MUX("ASPTX1", asptx1), - CS42L43_DAPM_MUX("ASPTX2", asptx2), - CS42L43_DAPM_MUX("ASPTX3", asptx3), - CS42L43_DAPM_MUX("ASPTX4", asptx4), - CS42L43_DAPM_MUX("ASPTX5", asptx5), - CS42L43_DAPM_MUX("ASPTX6", asptx6), - - CS42L43_DAPM_MUX("DP1TX1", dp1tx1), - CS42L43_DAPM_MUX("DP1TX2", dp1tx2), - CS42L43_DAPM_MUX("DP1TX3", dp1tx3), - CS42L43_DAPM_MUX("DP1TX4", dp1tx4), - CS42L43_DAPM_MUX("DP2TX1", dp2tx1), - CS42L43_DAPM_MUX("DP2TX2", dp2tx2), - CS42L43_DAPM_MUX("DP3TX1", dp3tx1), - CS42L43_DAPM_MUX("DP3TX2", dp3tx2), - CS42L43_DAPM_MUX("DP4TX1", dp4tx1), - CS42L43_DAPM_MUX("DP4TX2", dp4tx2), - - CS42L43_DAPM_MUX("ASRC INT1", asrcint1), - CS42L43_DAPM_MUX("ASRC INT2", asrcint2), - CS42L43_DAPM_MUX("ASRC INT3", asrcint3), - CS42L43_DAPM_MUX("ASRC INT4", asrcint4), - CS42L43_DAPM_MUX("ASRC DEC1", asrcdec1), - CS42L43_DAPM_MUX("ASRC DEC2", asrcdec2), - CS42L43_DAPM_MUX("ASRC DEC3", asrcdec3), - CS42L43_DAPM_MUX("ASRC DEC4", asrcdec4), - - CS42L43_DAPM_MUX("ISRC1INT1", isrc1int1), - CS42L43_DAPM_MUX("ISRC1INT2", isrc1int2), - CS42L43_DAPM_MUX("ISRC1DEC1", isrc1dec1), - CS42L43_DAPM_MUX("ISRC1DEC2", isrc1dec2), - CS42L43_DAPM_MUX("ISRC2INT1", isrc2int1), - CS42L43_DAPM_MUX("ISRC2INT2", isrc2int2), - CS42L43_DAPM_MUX("ISRC2DEC1", isrc2dec1), - CS42L43_DAPM_MUX("ISRC2DEC2", isrc2dec2), - - CS42L43_DAPM_MUX("SPDIF1", spdif1), - CS42L43_DAPM_MUX("SPDIF2", spdif2), - - CS42L43_DAPM_MIXER("EQ1", eq1), - CS42L43_DAPM_MIXER("EQ2", eq2), - - CS42L43_DAPM_MIXER("Speaker L", amp1), - CS42L43_DAPM_MIXER("Speaker R", amp2), - - CS42L43_DAPM_MIXER("Headphone L", amp3), - CS42L43_DAPM_MIXER("Headphone R", amp4), }; static const struct snd_soc_dapm_route cs42l43_routes[] = { @@ -1963,16 +2094,6 @@ static const struct snd_soc_dapm_route cs42l43_routes[] = { { "PDM2L", NULL, "PDM2_DIN" }, { "PDM2R", NULL, "PDM2_DIN" }, - { "Decimator 1 Mode", "PDM", "PDM1L" }, - { "Decimator 1 Mode", "ADC", "ADC1" }, - { "Decimator 2 Mode", "PDM", "PDM1R" }, - { "Decimator 2 Mode", "ADC", "ADC2" }, - - { "Decimator 1", NULL, "Decimator 1 Mode" }, - { "Decimator 2", NULL, "Decimator 2 Mode" }, - { "Decimator 3", NULL, "PDM2L" }, - { "Decimator 4", NULL, "PDM2R" }, - { "ASP Capture", NULL, "ASPTX1" }, { "ASP Capture", NULL, "ASPTX2" }, { "ASP Capture", NULL, "ASPTX3" }, @@ -2060,6 +2181,261 @@ static const struct snd_soc_dapm_route cs42l43_routes[] = { { "ASRC_DEC4", NULL, "ASRC_DEC" }, { "EQ", NULL, "EQ Clock" }, +}; + +static const struct snd_kcontrol_new cs42l43_a_controls[] = { + SOC_ENUM_EXT("Decimator 1 Ramp Up", cs42l43_dec1_ramp_up, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 1 Ramp Down", cs42l43_dec1_ramp_down, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 2 Ramp Up", cs42l43_dec2_ramp_up, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 2 Ramp Down", cs42l43_dec2_ramp_down, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 3 Ramp Up", cs42l43_dec3_ramp_up, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 3 Ramp Down", cs42l43_dec3_ramp_down, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 4 Ramp Up", cs42l43_dec4_ramp_up, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 4 Ramp Down", cs42l43_dec4_ramp_down, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + + SOC_SINGLE_TLV("Decimator 1 Volume", CS42L43_DECIM_VOL_CTRL_CH1_CH2, + CS42L43_DECIM1_VOL_SHIFT, 0xBF, 0, cs42l43_dec_tlv), + SOC_SINGLE_EXT("Decimator 1 Switch", CS42L43_DECIM_VOL_CTRL_CH1_CH2, + CS42L43_DECIM1_MUTE_SHIFT, 1, 1, + cs42l43_decim_get, cs42l43_dapm_put_volsw), + SOC_SINGLE_TLV("Decimator 2 Volume", CS42L43_DECIM_VOL_CTRL_CH1_CH2, + CS42L43_DECIM2_VOL_SHIFT, 0xBF, 0, cs42l43_dec_tlv), + SOC_SINGLE_EXT("Decimator 2 Switch", CS42L43_DECIM_VOL_CTRL_CH1_CH2, + CS42L43_DECIM2_MUTE_SHIFT, 1, 1, + cs42l43_decim_get, cs42l43_dapm_put_volsw), + SOC_SINGLE_TLV("Decimator 3 Volume", CS42L43_DECIM_VOL_CTRL_CH3_CH4, + CS42L43_DECIM3_VOL_SHIFT, 0xBF, 0, cs42l43_dec_tlv), + SOC_SINGLE_EXT("Decimator 3 Switch", CS42L43_DECIM_VOL_CTRL_CH3_CH4, + CS42L43_DECIM3_MUTE_SHIFT, 1, 1, + cs42l43_decim_get, cs42l43_dapm_put_volsw), + SOC_SINGLE_TLV("Decimator 4 Volume", CS42L43_DECIM_VOL_CTRL_CH3_CH4, + CS42L43_DECIM4_VOL_SHIFT, 0xBF, 0, cs42l43_dec_tlv), + SOC_SINGLE_EXT("Decimator 4 Switch", CS42L43_DECIM_VOL_CTRL_CH3_CH4, + CS42L43_DECIM4_MUTE_SHIFT, 1, 1, + cs42l43_decim_get, cs42l43_dapm_put_volsw), +}; + +static const struct snd_kcontrol_new cs42l43_b_controls[] = { + SOC_SINGLE_TLV("Decimator 1 Volume", CS42L43B_DECIM_VOL_CTRL_CH1_CH2, + CS42L43_DECIM1_VOL_SHIFT, 0xBF, 0, cs42l43_dec_tlv), + SOC_SINGLE_EXT("Decimator 1 Switch", CS42L43B_DECIM_VOL_CTRL_CH1_CH2, + CS42L43_DECIM1_MUTE_SHIFT, 1, 1, + cs42l43_decim_get, cs42l43_dapm_put_volsw), + SOC_SINGLE_TLV("Decimator 2 Volume", CS42L43B_DECIM_VOL_CTRL_CH1_CH2, + CS42L43_DECIM2_VOL_SHIFT, 0xBF, 0, cs42l43_dec_tlv), + SOC_SINGLE_EXT("Decimator 2 Switch", CS42L43B_DECIM_VOL_CTRL_CH1_CH2, + CS42L43_DECIM2_MUTE_SHIFT, 1, 1, + cs42l43_decim_get, cs42l43_dapm_put_volsw), + SOC_SINGLE_TLV("Decimator 3 Volume", CS42L43B_DECIM_VOL_CTRL_CH3_CH4, + CS42L43_DECIM3_VOL_SHIFT, 0xBF, 0, cs42l43_dec_tlv), + SOC_SINGLE_EXT("Decimator 3 Switch", CS42L43B_DECIM_VOL_CTRL_CH3_CH4, + CS42L43_DECIM3_MUTE_SHIFT, 1, 1, + cs42l43_decim_get, cs42l43_dapm_put_volsw), + SOC_SINGLE_TLV("Decimator 4 Volume", CS42L43B_DECIM_VOL_CTRL_CH3_CH4, + CS42L43_DECIM4_VOL_SHIFT, 0xBF, 0, cs42l43_dec_tlv), + SOC_SINGLE_EXT("Decimator 4 Switch", CS42L43B_DECIM_VOL_CTRL_CH3_CH4, + CS42L43_DECIM4_MUTE_SHIFT, 1, 1, + cs42l43_decim_get, cs42l43_dapm_put_volsw), + SOC_SINGLE_TLV("Decimator 5 Volume", CS42L43B_DECIM_VOL_CTRL_CH5_CH6, + CS42L43B_DECIM5_VOL_SHIFT, 0xBF, 0, cs42l43_dec_tlv), + SOC_SINGLE_EXT("Decimator 5 Switch", CS42L43B_DECIM_VOL_CTRL_CH5_CH6, + CS42L43B_DECIM5_MUTE_SHIFT, 1, 1, + cs42l43_decim_get, cs42l43_dapm_put_volsw), + SOC_SINGLE_TLV("Decimator 6 Volume", CS42L43B_DECIM_VOL_CTRL_CH5_CH6, + CS42L43B_DECIM6_VOL_SHIFT, 0xBF, 0, cs42l43_dec_tlv), + SOC_SINGLE_EXT("Decimator 6 Switch", CS42L43B_DECIM_VOL_CTRL_CH5_CH6, + CS42L43B_DECIM6_MUTE_SHIFT, 1, 1, + cs42l43_decim_get, cs42l43_dapm_put_volsw), + + SOC_ENUM_EXT("Decimator 1 Ramp Up", cs42l43b_dec1_ramp_up, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 1 Ramp Down", cs42l43b_dec1_ramp_down, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 2 Ramp Up", cs42l43b_dec2_ramp_up, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 2 Ramp Down", cs42l43b_dec2_ramp_down, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 3 Ramp Up", cs42l43b_dec3_ramp_up, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 3 Ramp Down", cs42l43b_dec3_ramp_down, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 4 Ramp Up", cs42l43b_dec4_ramp_up, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 4 Ramp Down", cs42l43b_dec4_ramp_down, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 5 Ramp Up", cs42l43b_dec5_ramp_up, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 5 Ramp Down", cs42l43b_dec5_ramp_down, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 6 Ramp Up", cs42l43b_dec6_ramp_up, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + SOC_ENUM_EXT("Decimator 6 Ramp Down", cs42l43b_dec6_ramp_down, + cs42l43_dapm_get_enum, cs42l43_dapm_put_enum), + + SOC_SINGLE("Decimator 5 WNF Switch", CS42L43B_DECIM_HPF_WNF_CTRL5, + CS42L43_DECIM_WNF_EN_SHIFT, 1, 0), + SOC_SINGLE("Decimator 6 WNF Switch", CS42L43B_DECIM_HPF_WNF_CTRL6, + CS42L43_DECIM_WNF_EN_SHIFT, 1, 0), + + SOC_ENUM("Decimator 5 WNF Corner Frequency", cs42l43b_dec5_wnf_corner), + SOC_ENUM("Decimator 6 WNF Corner Frequency", cs42l43b_dec6_wnf_corner), + + SOC_SINGLE("Decimator 5 HPF Switch", CS42L43B_DECIM_HPF_WNF_CTRL5, + CS42L43_DECIM_HPF_EN_SHIFT, 1, 0), + SOC_SINGLE("Decimator 6 HPF Switch", CS42L43B_DECIM_HPF_WNF_CTRL6, + CS42L43_DECIM_HPF_EN_SHIFT, 1, 0), + + SOC_ENUM("Decimator 5 HPF Corner Frequency", cs42l43b_dec5_hpf_corner), + SOC_ENUM("Decimator 6 HPF Corner Frequency", cs42l43b_dec6_hpf_corner), +}; + +static const struct snd_soc_dapm_widget cs42l43_a_widgets[] = { + SND_SOC_DAPM_MUX("Decimator 1 Mode", SND_SOC_NOPM, 0, 0, + &cs42l43_dec_mode_ctl[0]), + SND_SOC_DAPM_MUX("Decimator 2 Mode", SND_SOC_NOPM, 0, 0, + &cs42l43_dec_mode_ctl[1]), + CS42L43_DAPM_MUX("ASPTX1", asptx1), + CS42L43_DAPM_MUX("ASPTX2", asptx2), + CS42L43_DAPM_MUX("ASPTX3", asptx3), + CS42L43_DAPM_MUX("ASPTX4", asptx4), + CS42L43_DAPM_MUX("ASPTX5", asptx5), + CS42L43_DAPM_MUX("ASPTX6", asptx6), + + CS42L43_DAPM_MUX("DP1TX1", dp1tx1), + CS42L43_DAPM_MUX("DP1TX2", dp1tx2), + CS42L43_DAPM_MUX("DP1TX3", dp1tx3), + CS42L43_DAPM_MUX("DP1TX4", dp1tx4), + CS42L43_DAPM_MUX("DP2TX1", dp2tx1), + CS42L43_DAPM_MUX("DP2TX2", dp2tx2), + CS42L43_DAPM_MUX("DP3TX1", dp3tx1), + CS42L43_DAPM_MUX("DP3TX2", dp3tx2), + CS42L43_DAPM_MUX("DP4TX1", dp4tx1), + CS42L43_DAPM_MUX("DP4TX2", dp4tx2), + + CS42L43_DAPM_MUX("ASRC INT1", asrcint1), + CS42L43_DAPM_MUX("ASRC INT2", asrcint2), + CS42L43_DAPM_MUX("ASRC INT3", asrcint3), + CS42L43_DAPM_MUX("ASRC INT4", asrcint4), + CS42L43_DAPM_MUX("ASRC DEC1", asrcdec1), + CS42L43_DAPM_MUX("ASRC DEC2", asrcdec2), + CS42L43_DAPM_MUX("ASRC DEC3", asrcdec3), + CS42L43_DAPM_MUX("ASRC DEC4", asrcdec4), + + CS42L43_DAPM_MUX("ISRC1INT1", isrc1int1), + CS42L43_DAPM_MUX("ISRC1INT2", isrc1int2), + CS42L43_DAPM_MUX("ISRC1DEC1", isrc1dec1), + CS42L43_DAPM_MUX("ISRC1DEC2", isrc1dec2), + CS42L43_DAPM_MUX("ISRC2INT1", isrc2int1), + CS42L43_DAPM_MUX("ISRC2INT2", isrc2int2), + CS42L43_DAPM_MUX("ISRC2DEC1", isrc2dec1), + CS42L43_DAPM_MUX("ISRC2DEC2", isrc2dec2), + + CS42L43_DAPM_MUX("SPDIF1", spdif1), + CS42L43_DAPM_MUX("SPDIF2", spdif2), + + CS42L43_DAPM_MIXER("EQ1", eq1), + CS42L43_DAPM_MIXER("EQ2", eq2), + + CS42L43_DAPM_MIXER("Speaker L", amp1), + CS42L43_DAPM_MIXER("Speaker R", amp2), + + CS42L43_DAPM_MIXER("Headphone L", amp3), + CS42L43_DAPM_MIXER("Headphone R", amp4), +}; + +static const struct snd_soc_dapm_widget cs42l43_b_widgets[] = { + SND_SOC_DAPM_AIF_OUT("DP3TX3", NULL, 2, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DP3TX4", NULL, 3, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DP4TX3", NULL, 2, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DP4TX4", NULL, 3, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_PGA("Decimator 5", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Decimator 6", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_PGA("ISRC1DEC3", CS42L43_ISRC1_CTRL, + CS42L43B_ISRC_DEC3_EN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_PGA("ISRC1DEC4", CS42L43_ISRC1_CTRL, + CS42L43B_ISRC_DEC4_EN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_PGA("ISRC2DEC3", CS42L43_ISRC2_CTRL, + CS42L43B_ISRC_DEC3_EN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_PGA("ISRC2DEC4", CS42L43_ISRC2_CTRL, + CS42L43B_ISRC_DEC4_EN_SHIFT, 0, NULL, 0), + + CS42L43_DAPM_MUX("ASPTX1", b_asptx1), + CS42L43_DAPM_MUX("ASPTX2", b_asptx2), + CS42L43_DAPM_MUX("ASPTX3", b_asptx3), + CS42L43_DAPM_MUX("ASPTX4", b_asptx4), + CS42L43_DAPM_MUX("ASPTX5", b_asptx5), + CS42L43_DAPM_MUX("ASPTX6", b_asptx6), + + CS42L43_DAPM_MUX("DP1TX1", b_dp1tx1), + CS42L43_DAPM_MUX("DP1TX2", b_dp1tx2), + CS42L43_DAPM_MUX("DP1TX3", b_dp1tx3), + CS42L43_DAPM_MUX("DP1TX4", b_dp1tx4), + CS42L43_DAPM_MUX("DP2TX1", b_dp2tx1), + CS42L43_DAPM_MUX("DP2TX2", b_dp2tx2), + CS42L43_DAPM_MUX("DP3TX1", b_dp3tx1), + CS42L43_DAPM_MUX("DP3TX2", b_dp3tx2), + CS42L43_DAPM_MUX("DP3TX3", b_dp3tx3), + CS42L43_DAPM_MUX("DP3TX4", b_dp3tx4), + CS42L43_DAPM_MUX("DP4TX1", b_dp4tx1), + CS42L43_DAPM_MUX("DP4TX2", b_dp4tx2), + CS42L43_DAPM_MUX("DP4TX3", b_dp4tx3), + CS42L43_DAPM_MUX("DP4TX4", b_dp4tx4), + + CS42L43_DAPM_MUX("ASRC INT1", b_asrcint1), + CS42L43_DAPM_MUX("ASRC INT2", b_asrcint2), + CS42L43_DAPM_MUX("ASRC INT3", b_asrcint3), + CS42L43_DAPM_MUX("ASRC INT4", b_asrcint4), + CS42L43_DAPM_MUX("ASRC DEC1", b_asrcdec1), + CS42L43_DAPM_MUX("ASRC DEC2", b_asrcdec2), + CS42L43_DAPM_MUX("ASRC DEC3", b_asrcdec3), + CS42L43_DAPM_MUX("ASRC DEC4", b_asrcdec4), + + CS42L43_DAPM_MUX("ISRC1INT1", b_isrc1int1), + CS42L43_DAPM_MUX("ISRC1INT2", b_isrc1int2), + CS42L43_DAPM_MUX("ISRC1DEC1", b_isrc1dec1), + CS42L43_DAPM_MUX("ISRC1DEC2", b_isrc1dec2), + CS42L43_DAPM_MUX("ISRC1DEC3", b_isrc1dec3), + CS42L43_DAPM_MUX("ISRC1DEC4", b_isrc1dec4), + CS42L43_DAPM_MUX("ISRC2INT1", b_isrc2int1), + CS42L43_DAPM_MUX("ISRC2INT2", b_isrc2int2), + CS42L43_DAPM_MUX("ISRC2DEC1", b_isrc2dec1), + CS42L43_DAPM_MUX("ISRC2DEC2", b_isrc2dec2), + CS42L43_DAPM_MUX("ISRC2DEC3", b_isrc2dec3), + CS42L43_DAPM_MUX("ISRC2DEC4", b_isrc2dec4), + + CS42L43_DAPM_MUX("SPDIF1", b_spdif1), + CS42L43_DAPM_MUX("SPDIF2", b_spdif2), + + CS42L43_DAPM_MIXER("EQ1", b_eq1), + CS42L43_DAPM_MIXER("EQ2", b_eq2), + + CS42L43_DAPM_MIXER("Speaker L", b_amp1), + CS42L43_DAPM_MIXER("Speaker R", b_amp2), + + CS42L43_DAPM_MIXER("Headphone L", b_amp3), + CS42L43_DAPM_MIXER("Headphone R", b_amp4), +}; + +static const struct snd_soc_dapm_route cs42l43_a_routes[] = { + { "Decimator 1 Mode", "PDM", "PDM1L" }, + { "Decimator 1 Mode", "ADC", "ADC1" }, + { "Decimator 2 Mode", "PDM", "PDM1R" }, + { "Decimator 2 Mode", "ADC", "ADC2" }, + + { "Decimator 1", NULL, "Decimator 1 Mode" }, + { "Decimator 2", NULL, "Decimator 2 Mode" }, + { "Decimator 3", NULL, "PDM2L" }, + { "Decimator 4", NULL, "PDM2R" }, CS42L43_MUX_ROUTES("ASPTX1", "ASPTX1"), CS42L43_MUX_ROUTES("ASPTX2", "ASPTX2"), @@ -2110,6 +2486,81 @@ static const struct snd_soc_dapm_route cs42l43_routes[] = { CS42L43_MIXER_ROUTES("Headphone R", "HP"), }; +static const struct snd_soc_dapm_route cs42l43_b_routes[] = { + { "Decimator 1", NULL, "ADC1" }, + { "Decimator 2", NULL, "ADC2" }, + { "Decimator 3", NULL, "PDM1L" }, + { "Decimator 4", NULL, "PDM1R" }, + { "Decimator 5", NULL, "PDM2L" }, + { "Decimator 6", NULL, "PDM2R" }, + + { "DP3 Capture", NULL, "DP3TX3" }, + { "DP3 Capture", NULL, "DP3TX4" }, + { "DP4 Capture", NULL, "DP4TX3" }, + { "DP4 Capture", NULL, "DP4TX4" }, + + { "ISRC1DEC3", NULL, "ISRC1" }, + { "ISRC1DEC4", NULL, "ISRC1" }, + { "ISRC2DEC3", NULL, "ISRC2" }, + { "ISRC2DEC4", NULL, "ISRC2" }, + + CS42L43B_MUX_ROUTES("ASPTX1", "ASPTX1"), + CS42L43B_MUX_ROUTES("ASPTX2", "ASPTX2"), + CS42L43B_MUX_ROUTES("ASPTX3", "ASPTX3"), + CS42L43B_MUX_ROUTES("ASPTX4", "ASPTX4"), + CS42L43B_MUX_ROUTES("ASPTX5", "ASPTX5"), + CS42L43B_MUX_ROUTES("ASPTX6", "ASPTX6"), + + CS42L43B_MUX_ROUTES("DP1TX1", "DP1TX1"), + CS42L43B_MUX_ROUTES("DP1TX2", "DP1TX2"), + CS42L43B_MUX_ROUTES("DP1TX3", "DP1TX3"), + CS42L43B_MUX_ROUTES("DP1TX4", "DP1TX4"), + CS42L43B_MUX_ROUTES("DP2TX1", "DP2TX1"), + CS42L43B_MUX_ROUTES("DP2TX2", "DP2TX2"), + CS42L43B_MUX_ROUTES("DP3TX1", "DP3TX1"), + CS42L43B_MUX_ROUTES("DP3TX2", "DP3TX2"), + CS42L43B_MUX_ROUTES("DP3TX3", "DP3TX3"), + CS42L43B_MUX_ROUTES("DP3TX4", "DP3TX4"), + CS42L43B_MUX_ROUTES("DP4TX1", "DP4TX1"), + CS42L43B_MUX_ROUTES("DP4TX2", "DP4TX2"), + CS42L43B_MUX_ROUTES("DP4TX3", "DP4TX3"), + CS42L43B_MUX_ROUTES("DP4TX4", "DP4TX4"), + + CS42L43B_MUX_ROUTES("ASRC INT1", "ASRC_INT1"), + CS42L43B_MUX_ROUTES("ASRC INT2", "ASRC_INT2"), + CS42L43B_MUX_ROUTES("ASRC INT3", "ASRC_INT3"), + CS42L43B_MUX_ROUTES("ASRC INT4", "ASRC_INT4"), + CS42L43B_MUX_ROUTES("ASRC DEC1", "ASRC_DEC1"), + CS42L43B_MUX_ROUTES("ASRC DEC2", "ASRC_DEC2"), + CS42L43B_MUX_ROUTES("ASRC DEC3", "ASRC_DEC3"), + CS42L43B_MUX_ROUTES("ASRC DEC4", "ASRC_DEC4"), + + CS42L43B_MUX_ROUTES("ISRC1INT1", "ISRC1INT1"), + CS42L43B_MUX_ROUTES("ISRC1INT2", "ISRC1INT2"), + CS42L43B_MUX_ROUTES("ISRC1DEC1", "ISRC1DEC1"), + CS42L43B_MUX_ROUTES("ISRC1DEC2", "ISRC1DEC2"), + CS42L43B_MUX_ROUTES("ISRC1DEC3", "ISRC1DEC3"), + CS42L43B_MUX_ROUTES("ISRC1DEC4", "ISRC1DEC4"), + CS42L43B_MUX_ROUTES("ISRC2INT1", "ISRC2INT1"), + CS42L43B_MUX_ROUTES("ISRC2INT2", "ISRC2INT2"), + CS42L43B_MUX_ROUTES("ISRC2DEC1", "ISRC2DEC1"), + CS42L43B_MUX_ROUTES("ISRC2DEC2", "ISRC2DEC2"), + CS42L43B_MUX_ROUTES("ISRC2DEC3", "ISRC2DEC3"), + CS42L43B_MUX_ROUTES("ISRC2DEC4", "ISRC2DEC4"), + + CS42L43B_MUX_ROUTES("SPDIF1", "SPDIF"), + CS42L43B_MUX_ROUTES("SPDIF2", "SPDIF"), + + CS42L43B_MIXER_ROUTES("EQ1", "EQ"), + CS42L43B_MIXER_ROUTES("EQ2", "EQ"), + + CS42L43B_MIXER_ROUTES("Speaker L", "AMP1"), + CS42L43B_MIXER_ROUTES("Speaker R", "AMP2"), + + CS42L43B_MIXER_ROUTES("Headphone L", "HP"), + CS42L43B_MIXER_ROUTES("Headphone R", "HP"), +}; + static int cs42l43_set_sysclk(struct snd_soc_component *component, int clk_id, int src, unsigned int freq, int dir) { @@ -2126,8 +2577,14 @@ static int cs42l43_set_sysclk(struct snd_soc_component *component, int clk_id, static int cs42l43_component_probe(struct snd_soc_component *component) { + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); struct cs42l43_codec *priv = snd_soc_component_get_drvdata(component); + unsigned int num_controls, num_widgets, num_routes; + const struct snd_soc_dapm_widget *widgets; + const struct snd_kcontrol_new *controls; + const struct snd_soc_dapm_route *routes; struct cs42l43 *cs42l43 = priv->core; + int ret; snd_soc_component_init_regmap(component, cs42l43->regmap); @@ -2139,6 +2596,39 @@ static int cs42l43_component_probe(struct snd_soc_component *component) priv->component = component; priv->constraint = cs42l43_constraint; + switch (cs42l43->variant_id) { + case CS42L43_DEVID_VAL: + controls = cs42l43_a_controls; + num_controls = ARRAY_SIZE(cs42l43_a_controls); + widgets = cs42l43_a_widgets; + num_widgets = ARRAY_SIZE(cs42l43_a_widgets); + routes = cs42l43_a_routes; + num_routes = ARRAY_SIZE(cs42l43_a_routes); + break; + case CS42L43B_DEVID_VAL: + controls = cs42l43_b_controls; + num_controls = ARRAY_SIZE(cs42l43_b_controls); + widgets = cs42l43_b_widgets; + num_widgets = ARRAY_SIZE(cs42l43_b_widgets); + routes = cs42l43_b_routes; + num_routes = ARRAY_SIZE(cs42l43_b_routes); + break; + default: + return -EINVAL; + } + + ret = snd_soc_add_component_controls(component, controls, num_controls); + if (ret) + return ret; + + ret = snd_soc_dapm_new_controls(dapm, widgets, num_widgets); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, routes, num_routes); + if (ret) + return ret; + return 0; } diff --git a/sound/soc/codecs/cs42l43.h b/sound/soc/codecs/cs42l43.h index b2fa2cd1d99f..fd50feb26ae9 100644 --- a/sound/soc/codecs/cs42l43.h +++ b/sound/soc/codecs/cs42l43.h @@ -61,7 +61,7 @@ struct cs42l43_codec { unsigned int refclk_freq; struct completion pll_ready; - unsigned int decim_cache[4]; + unsigned int decim_cache[6]; unsigned int adc_ena; unsigned int hp_ena; @@ -103,7 +103,7 @@ struct cs42l43_codec { bool hp_ilimited; int hp_ilimit_count; - struct snd_kcontrol *kctl[5]; + struct snd_kcontrol *kctl[7]; }; #if IS_REACHABLE(CONFIG_SND_SOC_CS42L43_SDW) From c4791ce96b88a444b04c7089ae2827a3b3ae1877 Mon Sep 17 00:00:00 2001 From: Frederic Popp Date: Sun, 8 Mar 2026 16:22:16 +0100 Subject: [PATCH 067/359] ALSA: usb-audio: add Studio 1824 support Adapt the already implemented support for the Studio 1824c audio interface to the predecessor Studio 1824. Basically just a change adding the different hardware ID in the relevant places. Tested as much as possible. All implemented functionality seemingly works. Signed-off-by: Frederic Popp Link: https://patch.msgid.link/20260308153334.50433-2-frederic.l.popp@t-online.de Signed-off-by: Takashi Iwai --- sound/usb/format.c | 4 ++++ sound/usb/mixer_quirks.c | 3 +++ sound/usb/mixer_s1810c.c | 2 ++ 3 files changed, 9 insertions(+) diff --git a/sound/usb/format.c b/sound/usb/format.c index 1207c507882a..030b4307927a 100644 --- a/sound/usb/format.c +++ b/sound/usb/format.c @@ -455,6 +455,10 @@ static int parse_uac2_sample_rate_range(struct snd_usb_audio *chip, if (chip->usb_id == USB_ID(0x194f, 0x010d) && !s1810c_valid_sample_rate(fp, rate)) goto skip_rate; + /* Filter out invalid rates on Presonus Studio 1824 */ + if (chip->usb_id == USB_ID(0x194f, 0x0107) && + !s1810c_valid_sample_rate(fp, rate)) + goto skip_rate; /* Filter out invalid rates on Focusrite devices */ if (USB_ID_VENDOR(chip->usb_id) == 0x1235 && diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index e97814dc9025..a01510a855c2 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -4477,6 +4477,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) case USB_ID(0x194f, 0x010d): /* Presonus Studio 1824c */ err = snd_sc1810_init_mixer(mixer); break; + case USB_ID(0x194f, 0x0107): /* Presonus Studio 1824 */ + err = snd_sc1810_init_mixer(mixer); + break; case USB_ID(0x2a39, 0x3fb0): /* RME Babyface Pro FS */ err = snd_bbfpro_controls_create(mixer); break; diff --git a/sound/usb/mixer_s1810c.c b/sound/usb/mixer_s1810c.c index 7eac7d1bce64..2e5a8d37ec57 100644 --- a/sound/usb/mixer_s1810c.c +++ b/sound/usb/mixer_s1810c.c @@ -362,6 +362,7 @@ static int snd_s1810c_init_mixer_maps(struct snd_usb_audio *chip) snd_s1810c_send_ctl_packet(dev, a, 3, 0, 1, MIXER_LEVEL_0DB); break; + case USB_ID(0x194f, 0x0107): /* 1824 */ case USB_ID(0x194f, 0x010d): /* 1824c */ /* Set all output faders to unity gain */ a = SC1810C_SEL_OUTPUT; @@ -685,6 +686,7 @@ int snd_sc1810_init_mixer(struct usb_mixer_interface *mixer) return ret; break; + case USB_ID(0x194f, 0x0107): /* Presonus Studio 1824 */ case USB_ID(0x194f, 0x010d): /* Presonus Studio 1824c */ ret = snd_s1810c_switch_init(mixer, &snd_s1824c_mono_sw); if (ret < 0) From 66f71ec3539e0e724f5099e6d4bbc81db4d9954a Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 9 Mar 2026 11:56:51 +0000 Subject: [PATCH 068/359] ASoC: cs35l56-test: Remove pointless duplicate loop counters In cs35l56_test_parse_xu_onchip_spkid() the first two loops used local i to index an array and num_gpios/num_pulls to count how many entries it had seen. But both i and num_* started at 0 and incremented on each loop so were identical. Remove i from these loops. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260309115651.1090368-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56-test.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/cs35l56-test.c b/sound/soc/codecs/cs35l56-test.c index ac3f34bf8adc..124fe5e75500 100644 --- a/sound/soc/codecs/cs35l56-test.c +++ b/sound/soc/codecs/cs35l56-test.c @@ -364,18 +364,17 @@ static void cs35l56_test_parse_xu_onchip_spkid(struct kunit *test) struct cs35l56_test_priv *priv = test->priv; struct cs35l56_private *cs35l56 = priv->cs35l56_priv; struct software_node *ext0_node; - int num_gpios = 0; - int num_pulls = 0; + int num_gpios, num_pulls; int i; - for (i = 0; i < ARRAY_SIZE(param->spkid_gpios); i++, num_gpios++) { - if (param->spkid_gpios[i] < 0) + for (num_gpios = 0; num_gpios < ARRAY_SIZE(param->spkid_gpios); num_gpios++) { + if (param->spkid_gpios[num_gpios] < 0) break; } KUNIT_ASSERT_LE(test, num_gpios, ARRAY_SIZE(cs35l56->base.onchip_spkid_gpios)); - for (i = 0; i < ARRAY_SIZE(param->spkid_pulls); i++, num_pulls++) { - if (param->spkid_pulls[i] < 0) + for (num_pulls = 0; num_pulls < ARRAY_SIZE(param->spkid_pulls); num_pulls++) { + if (param->spkid_pulls[num_pulls] < 0) break; } KUNIT_ASSERT_LE(test, num_pulls, ARRAY_SIZE(cs35l56->base.onchip_spkid_pulls)); From eae0946b2485109a9f24f46853497f5b1983ab79 Mon Sep 17 00:00:00 2001 From: "Christophe Leroy (CS GROUP)" Date: Mon, 9 Mar 2026 12:46:49 +0100 Subject: [PATCH 069/359] ASoC: codecs: peb2466: Change the +/- 6dB switch to a volume In addition to the digital volume adjustment, infineon peb2466 codec has an analog adjustment which can be set to either 0dB or +/-6dB. At the time being it is defined as a switch and appears in 'alsamixer' like a 'mute'. This adjustment isn't a mute, change it to a 'volume' with an associated dB range. With this change, this adjustment now appears in 'alsamixer' as a standard volume slider which can have two positions. Signed-off-by: Christophe Leroy (CS GROUP) Acked-by: Herve Codina Link: https://patch.msgid.link/418fe76d1c9efc8182ab57b6b426a8c723e025fe.1773056714.git.chleroy@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/peb2466.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/sound/soc/codecs/peb2466.c b/sound/soc/codecs/peb2466.c index f1ee42af264b..2d5163c15d0d 100644 --- a/sound/soc/codecs/peb2466.c +++ b/sound/soc/codecs/peb2466.c @@ -517,18 +517,21 @@ static const struct snd_kcontrol_new peb2466_ch3_out_mix_controls[] = { SOC_DAPM_SINGLE("Voice Switch", PEB2466_CR2(3), 0, 1, 0) }; +static const SNDRV_CTL_TLVD_DECLARE_DB_MINMAX(peb2466_gain_p_tlv, -600, 0); +static const SNDRV_CTL_TLVD_DECLARE_DB_MINMAX(peb2466_gain_c_tlv, 0, 600); + static const struct snd_kcontrol_new peb2466_controls[] = { /* Attenuators */ - SOC_SINGLE("DAC0 -6dB Playback Switch", PEB2466_CR3(0), 2, 1, 0), - SOC_SINGLE("DAC1 -6dB Playback Switch", PEB2466_CR3(1), 2, 1, 0), - SOC_SINGLE("DAC2 -6dB Playback Switch", PEB2466_CR3(2), 2, 1, 0), - SOC_SINGLE("DAC3 -6dB Playback Switch", PEB2466_CR3(3), 2, 1, 0), + SOC_SINGLE_TLV("DAC0 -6dB Playback Volume", PEB2466_CR3(0), 2, 1, 1, peb2466_gain_p_tlv), + SOC_SINGLE_TLV("DAC1 -6dB Playback Volume", PEB2466_CR3(1), 2, 1, 1, peb2466_gain_p_tlv), + SOC_SINGLE_TLV("DAC2 -6dB Playback Volume", PEB2466_CR3(2), 2, 1, 1, peb2466_gain_p_tlv), + SOC_SINGLE_TLV("DAC3 -6dB Playback Volume", PEB2466_CR3(3), 2, 1, 1, peb2466_gain_p_tlv), /* Amplifiers */ - SOC_SINGLE("ADC0 +6dB Capture Switch", PEB2466_CR3(0), 3, 1, 0), - SOC_SINGLE("ADC1 +6dB Capture Switch", PEB2466_CR3(1), 3, 1, 0), - SOC_SINGLE("ADC2 +6dB Capture Switch", PEB2466_CR3(2), 3, 1, 0), - SOC_SINGLE("ADC3 +6dB Capture Switch", PEB2466_CR3(3), 3, 1, 0), + SOC_SINGLE_TLV("ADC0 +6dB Capture Volume", PEB2466_CR3(0), 3, 1, 0, peb2466_gain_c_tlv), + SOC_SINGLE_TLV("ADC1 +6dB Capture Volume", PEB2466_CR3(1), 3, 1, 0, peb2466_gain_c_tlv), + SOC_SINGLE_TLV("ADC2 +6dB Capture Volume", PEB2466_CR3(2), 3, 1, 0, peb2466_gain_c_tlv), + SOC_SINGLE_TLV("ADC3 +6dB Capture Volume", PEB2466_CR3(3), 3, 1, 0, peb2466_gain_c_tlv), /* Tone generators */ SOC_ENUM_EXT("DAC0 TG1 Freq", peb2466_tg_freq[0][0], From 97af961568c8682c44506c9ad4b26c8a5455ec1d Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 9 Mar 2026 12:45:43 +0000 Subject: [PATCH 070/359] ASoC: cs35l56: Put OTP register defines in correct address order Move the defines for the OTP registers to keep the register defines in order of increasing address. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260309124543.1135247-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/cs35l56.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h index ae1e1489b671..51b153bb8598 100644 --- a/include/sound/cs35l56.h +++ b/include/sound/cs35l56.h @@ -32,9 +32,6 @@ struct snd_ctl_elem_value; #define CS35L56_UPDATE_REGS 0x0002A0C #define CS35L56_REFCLK_INPUT 0x0002C04 #define CS35L56_GLOBAL_SAMPLE_RATE 0x0002C0C -#define CS35L56_OTP_MEM_53 0x00300D4 -#define CS35L56_OTP_MEM_54 0x00300D8 -#define CS35L56_OTP_MEM_55 0x00300DC #define CS35L56_ASP1_ENABLES1 0x0004800 #define CS35L56_ASP1_CONTROL1 0x0004804 #define CS35L56_ASP1_CONTROL2 0x0004808 @@ -86,6 +83,9 @@ struct snd_ctl_elem_value; #define CS35L56_DIE_STS1 0x0017040 #define CS35L56_DIE_STS2 0x0017044 #define CS35L56_DSP_RESTRICT_STS1 0x00190F0 +#define CS35L56_OTP_MEM_53 0x00300D4 +#define CS35L56_OTP_MEM_54 0x00300D8 +#define CS35L56_OTP_MEM_55 0x00300DC #define CS35L56_DSP1_XMEM_PACKED_0 0x2000000 #define CS35L56_DSP1_XMEM_PACKED_6143 0x2005FFC #define CS35L56_DSP1_XMEM_UNPACKED32_0 0x2400000 From 87e49eb0918154f64f736d5271e9e3fe566f82b1 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 9 Mar 2026 14:53:10 +0000 Subject: [PATCH 071/359] ASoC: wm_adsp: Make wm_adsp_fw_text[] const Add the extra const to wm_adsp_fw_text[] to make the array data const. This array should have been const data but was missing the second const that is needed when declaring a const array of const pointers. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260309145310.1199583-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm_adsp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index d95b54275b5e..f0aa6e3a1cfa 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -72,7 +72,7 @@ static const struct cs_dsp_client_ops wm_adsp2_client_ops; #define WM_ADSP_NUM_FW 13 -static const char *wm_adsp_fw_text[WM_ADSP_NUM_FW] = { +static const char * const wm_adsp_fw_text[WM_ADSP_NUM_FW] = { [WM_ADSP_FW_MBC_VSS] = "MBC/VSS", [WM_ADSP_FW_HIFI] = "MasterHiFi", [WM_ADSP_FW_TX] = "Tx", From ed0313223ce6514dbd39c049e25f702980d7e3cc Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 9 Mar 2026 09:03:06 -0700 Subject: [PATCH 072/359] ASoC: codecs: wcd9335: Remove potential undefined behavior in wcd9335_slimbus_irq() If 'port_id' is negative, the shift counts in wcd9335_slimbus_irq() also become negative, resulting in undefined behavior due to shift out of bounds. That appears to be not possible, but with UBSAN enabled, Clang's range analysis isn't always able to determine that and generates undefined behavior. As a result the code generation isn't optimal, and undefined behavior should be avoided regardless. Improve code generation and remove the undefined behavior by converting the signed variables to unsigned. Fixes the following warning: sound/soc/codecs/wcd9335.o: warning: objtool: wcd9335_slimbus_irq() falls through to next function __cfi_wcd9335_set_channel_map() This is very similar to a previous fix to wcd934x with commit 060aed9c0093 ("objtool, ASoC: codecs: wcd934x: Remove potential undefined behavior in wcd934x_slim_irq_handler()"). Cc: Srinivas Kandagatla Cc: Liam Girdwood Cc: Mark Brown Reported-by: Arnd Bergmann Closes: https://lore.kernel.org/a426d669-58bb-4be1-9eaa-6f3d83109e2d@app.fastmail.com Signed-off-by: Josh Poimboeuf Link: https://patch.msgid.link/08dbb63519ef31a5457e07673b7b256fecb5989b.1773071992.git.jpoimboe@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/wcd9335.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c index 640e43ee1975..e3ca5ca6de3d 100644 --- a/sound/soc/codecs/wcd9335.c +++ b/sound/soc/codecs/wcd9335.c @@ -3907,7 +3907,7 @@ static irqreturn_t wcd9335_slimbus_irq(int irq, void *data) { struct wcd9335_codec *wcd = data; unsigned long status = 0; - int i, j, port_id; + unsigned int i, j, port_id; unsigned int val, int_val = 0; irqreturn_t ret = IRQ_NONE; bool tx; From edf04f1af05d714c7aba0cf008ded1245365fcd7 Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Tue, 10 Mar 2026 10:36:49 +0800 Subject: [PATCH 073/359] ALSA: hda/senary: Fix beep error handling and optimize EAPD switching This patch addresses a potential state inconsistency bug and optimizes runtime performance: 1. Fix error handling in set_beep_amp(): Previously, beep_nid was assigned before adding kcontrols. If kcontrol creation failed, the function returned error but left beep_nid set, causing inconsistent driver state. Moved the assignment to the end of the function. 2. Optimize senary_auto_turn_eapd(): Removed the redundant snd_hda_query_pin_caps() check inside the loop. The target pins are sourced from spec->eapds, which is strictly filtered during the initial parse phase. Checking capabilities again during every mute/unmute hook is unnecessary overhead. Signed-off-by: wangdicheng Link: https://patch.msgid.link/20260310023649.155858-1-wangdich9700@163.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/senarytech.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/hda/codecs/senarytech.c b/sound/hda/codecs/senarytech.c index f9a389df3a17..29b554cdd81d 100644 --- a/sound/hda/codecs/senarytech.c +++ b/sound/hda/codecs/senarytech.c @@ -76,7 +76,6 @@ static int set_beep_amp(struct senary_spec *spec, hda_nid_t nid, unsigned int beep_amp = HDA_COMPOSE_AMP_VAL(nid, 1, idx, dir); int i; - spec->gen.beep_nid = nid; for (i = 0; i < ARRAY_SIZE(senary_beep_mixer); i++) { knew = snd_hda_gen_add_kctl(&spec->gen, NULL, &senary_beep_mixer[i]); @@ -84,6 +83,8 @@ static int set_beep_amp(struct senary_spec *spec, hda_nid_t nid, return -ENOMEM; knew->private_value = beep_amp; } + + spec->gen.beep_nid = nid; return 0; } @@ -138,10 +139,9 @@ static void senary_auto_turn_eapd(struct hda_codec *codec, int num_pins, int i; for (i = 0; i < num_pins; i++) { - if (snd_hda_query_pin_caps(codec, pins[i]) & AC_PINCAP_EAPD) - snd_hda_codec_write(codec, pins[i], 0, - AC_VERB_SET_EAPD_BTLENABLE, - on ? 0x02 : 0); + snd_hda_codec_write(codec, pins[i], 0, + AC_VERB_SET_EAPD_BTLENABLE, + on ? 0x02 : 0); } } From 819cf1dc01ce66b6906d403a6925c4bd754a7a1d Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Tue, 10 Mar 2026 18:42:33 +0800 Subject: [PATCH 074/359] ASoC: fsl_utils: Add snd_kcontrol functions for specific cases There are some registers which are volatile, at pm runtime suspend state, the regmap cache only is enabled, regmap will return -EBUSY when trying to access these registers. static int _regmap_read(struct regmap *map, unsigned int reg, unsigned int *val) { int ret; void *context = _regmap_map_get_context(map); if (!map->cache_bypass) { ret = regcache_read(map, reg, val); if (ret == 0) return 0; } if (map->cache_only) return -EBUSY; if (!regmap_readable(map, reg)) return -EIO; When exporting these registers by amixer interface to user space, there will be -EBUSY errors in mixer-test when the cpu dai is in idle. In order to avoid such error, needs to define FSL own functions to take a pm runtime reference before calling snd_soc_get_xr_sx(), snd_soc_get_enum_double(), snd_soc_get_volsw(), and so on. Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260310104235.1234569-2-shengjiu.wang@nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_utils.c | 131 ++++++++++++++++++++++++++++++++++++++ sound/soc/fsl/fsl_utils.h | 48 ++++++++++++++ 2 files changed, 179 insertions(+) diff --git a/sound/soc/fsl/fsl_utils.c b/sound/soc/fsl/fsl_utils.c index d69a6b9795bf..7651c64bc837 100644 --- a/sound/soc/fsl/fsl_utils.c +++ b/sound/soc/fsl/fsl_utils.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "fsl_utils.h" @@ -197,6 +198,136 @@ void fsl_asoc_constrain_rates(struct snd_pcm_hw_constraint_list *target_constr, } EXPORT_SYMBOL(fsl_asoc_constrain_rates); +/* + * Below functions are used by mixer interface to avoid accessing registers + * which are volatile at pm runtime suspend state (cache_only is enabled). + */ +int fsl_asoc_get_xr_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + int ret = 0; + + ret = pm_runtime_resume_and_get(component->dev); + if (ret) + return ret; + + ret = snd_soc_get_xr_sx(kcontrol, ucontrol); + + pm_runtime_put_autosuspend(component->dev); + + return ret; +} +EXPORT_SYMBOL_GPL(fsl_asoc_get_xr_sx); + +int fsl_asoc_put_xr_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + int ret = 0; + + ret = pm_runtime_resume_and_get(component->dev); + if (ret) + return ret; + + ret = snd_soc_put_xr_sx(kcontrol, ucontrol); + /* + * As this function only used by the SNDRV_CTL_ELEM_ACCESS_VOLATILE + * case. return 0 to avoid control event notification. + */ + if (ret > 0) + ret = 0; + + pm_runtime_put_autosuspend(component->dev); + + return ret; +} +EXPORT_SYMBOL_GPL(fsl_asoc_put_xr_sx); + +int fsl_asoc_get_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + int ret = 0; + + ret = pm_runtime_resume_and_get(component->dev); + if (ret) + return ret; + + ret = snd_soc_get_enum_double(kcontrol, ucontrol); + + pm_runtime_put_autosuspend(component->dev); + + return ret; +} +EXPORT_SYMBOL_GPL(fsl_asoc_get_enum_double); + +int fsl_asoc_put_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + int ret = 0; + + ret = pm_runtime_resume_and_get(component->dev); + if (ret) + return ret; + + ret = snd_soc_put_enum_double(kcontrol, ucontrol); + /* + * As this function only used by the SNDRV_CTL_ELEM_ACCESS_VOLATILE + * case. return 0 to avoid control event notification. + */ + if (ret > 0) + ret = 0; + + pm_runtime_put_autosuspend(component->dev); + + return ret; +} +EXPORT_SYMBOL_GPL(fsl_asoc_put_enum_double); + +int fsl_asoc_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + int ret = 0; + + ret = pm_runtime_resume_and_get(component->dev); + if (ret) + return ret; + + ret = snd_soc_get_volsw(kcontrol, ucontrol); + + pm_runtime_put_autosuspend(component->dev); + + return ret; +} +EXPORT_SYMBOL_GPL(fsl_asoc_get_volsw); + +int fsl_asoc_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + int ret = 0; + + ret = pm_runtime_resume_and_get(component->dev); + if (ret) + return ret; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + /* + * As this function only used by the SNDRV_CTL_ELEM_ACCESS_VOLATILE + * case. return 0 to avoid control event notification. + */ + if (ret > 0) + ret = 0; + + pm_runtime_put_autosuspend(component->dev); + + return ret; +} +EXPORT_SYMBOL_GPL(fsl_asoc_put_volsw); + MODULE_AUTHOR("Timur Tabi "); MODULE_DESCRIPTION("Freescale ASoC utility code"); MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_utils.h b/sound/soc/fsl/fsl_utils.h index 21b25a11ecda..1aab0c1cee62 100644 --- a/sound/soc/fsl/fsl_utils.h +++ b/sound/soc/fsl/fsl_utils.h @@ -31,4 +31,52 @@ void fsl_asoc_constrain_rates(struct snd_pcm_hw_constraint_list *target_constr, const struct snd_pcm_hw_constraint_list *original_constr, struct clk *pll8k_clk, struct clk *pll11k_clk, struct clk *ext_clk, int *target_rates); + +/* Similar to SOC_SINGLE_XR_SX, but it is for read only registers. */ +#define FSL_ASOC_SINGLE_XR_SX_EXT_RO(xname, xregbase, xregcount, xnbits, \ + xmin, xmax, xinvert, xhandler_get) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | \ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = snd_soc_info_xr_sx, .get = xhandler_get, \ + .private_value = (unsigned long)&(struct soc_mreg_control) \ + {.regbase = xregbase, .regcount = xregcount, .nbits = xnbits, \ + .invert = xinvert, .min = xmin, .max = xmax} } + +/* Similar to SOC_SINGLE_EXT, but it is for volatile register. */ +#define FSL_ASOC_SINGLE_EXT(xname, xreg, xshift, xmax, xinvert,\ + xhandler_get, xhandler_put) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_VOLATILE | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = snd_soc_info_volsw, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = SOC_SINGLE_VALUE(xreg, xshift, 0, xmax, xinvert, 0) } + +#define FSL_ASOC_ENUM_EXT(xname, xenum, xhandler_get, xhandler_put) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_VOLATILE | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = snd_soc_info_enum_double, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = (unsigned long)&xenum } + +int fsl_asoc_get_xr_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +int fsl_asoc_put_xr_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +int fsl_asoc_get_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +int fsl_asoc_put_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +int fsl_asoc_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +int fsl_asoc_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + #endif /* _FSL_UTILS_H */ From 8e27987a208029c39da7a787bd9f1217d42011a5 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Tue, 10 Mar 2026 18:42:34 +0800 Subject: [PATCH 075/359] ASoC: fsl_sai: add bitcount and timestamp controls The transmitter and receiver implement separate timestamp counters and bit counters. The bit counter increments at the end of each bit in a frame whenever the transmitter or receiver is enabled. The bit counter can be reset by software. The timestamp counter increments on the bus interface clock whenever it is enabled. The current value of the timestamp counter is latched whenever the bit counter increments. Reading the bit counter register will cause the latched timestamp value to be saved in the bit counter timestamp register. The timestamp counter can be reset by software, this also resets the latched timestamp value and the bit counter timestamp register. The timestamp counter and bit counter can be used by software to track the progress of the transmitter and receiver. It can also be used to calculate the relative frequency of the bit clock against the bus interface clock. These bitcount and timestamp registers are volatile, and supported when the module has timestamp features. Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260310104235.1234569-3-shengjiu.wang@nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_sai.c | 66 +++++++++++++++++++++++++++++++++++++++++ sound/soc/fsl/fsl_sai.h | 4 +++ 2 files changed, 70 insertions(+) diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index 148e09e58dfa..bd336d2e4cb3 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -41,6 +41,52 @@ static const struct snd_pcm_hw_constraint_list fsl_sai_rate_constraints = { .list = fsl_sai_rates, }; +static const char * const inc_mode[] = { + "On enabled and bitcount increment", "On enabled" +}; + +static SOC_ENUM_SINGLE_DECL(transmit_tstmp_enum, + FSL_SAI_TTCTL, FSL_SAI_xTCTL_TSINC_SHIFT, inc_mode); +static SOC_ENUM_SINGLE_DECL(receive_tstmp_enum, + FSL_SAI_RTCTL, FSL_SAI_xTCTL_TSINC_SHIFT, inc_mode); + +static const struct snd_kcontrol_new fsl_sai_timestamp_ctrls[] = { + FSL_ASOC_SINGLE_EXT("Transmit Timestamp Control Switch", FSL_SAI_TTCTL, + FSL_SAI_xTCTL_TSEN_SHIFT, 1, 0, + fsl_asoc_get_volsw, fsl_asoc_put_volsw), + FSL_ASOC_ENUM_EXT("Transmit Timestamp Increment", transmit_tstmp_enum, + fsl_asoc_get_enum_double, fsl_asoc_put_enum_double), + FSL_ASOC_SINGLE_EXT("Transmit Timestamp Reset Switch", FSL_SAI_TTCTL, + FSL_SAI_xTCTL_RTSC_SHIFT, 1, 0, + fsl_asoc_get_volsw, fsl_asoc_put_volsw), + FSL_ASOC_SINGLE_EXT("Transmit Bit Counter Reset Switch", FSL_SAI_TTCTL, + FSL_SAI_xTCTL_RBC_SHIFT, 1, 0, + fsl_asoc_get_volsw, fsl_asoc_put_volsw), + FSL_ASOC_SINGLE_XR_SX_EXT_RO("Transmit Timestamp Counter", FSL_SAI_TTCTN, + 1, 32, 0, 0xffffffff, 0, fsl_asoc_get_xr_sx), + FSL_ASOC_SINGLE_XR_SX_EXT_RO("Transmit Bit Counter", FSL_SAI_TBCTN, + 1, 32, 0, 0xffffffff, 0, fsl_asoc_get_xr_sx), + FSL_ASOC_SINGLE_XR_SX_EXT_RO("Transmit Latched Timestamp Counter", FSL_SAI_TTCAP, + 1, 32, 0, 0xffffffff, 0, fsl_asoc_get_xr_sx), + FSL_ASOC_SINGLE_EXT("Receive Timestamp Control Switch", FSL_SAI_RTCTL, + FSL_SAI_xTCTL_TSEN_SHIFT, 1, 0, + fsl_asoc_get_volsw, fsl_asoc_put_volsw), + FSL_ASOC_ENUM_EXT("Receive Timestamp Increment", receive_tstmp_enum, + fsl_asoc_get_enum_double, fsl_asoc_put_enum_double), + FSL_ASOC_SINGLE_EXT("Receive Timestamp Reset Switch", FSL_SAI_RTCTL, + FSL_SAI_xTCTL_RTSC_SHIFT, 1, 0, + fsl_asoc_get_volsw, fsl_asoc_put_volsw), + FSL_ASOC_SINGLE_EXT("Receive Bit Counter Reset Switch", FSL_SAI_RTCTL, + FSL_SAI_xTCTL_RBC_SHIFT, 1, 0, + fsl_asoc_get_volsw, fsl_asoc_put_volsw), + FSL_ASOC_SINGLE_XR_SX_EXT_RO("Receive Timestamp Counter", FSL_SAI_RTCTN, + 1, 32, 0, 0xffffffff, 0, fsl_asoc_get_xr_sx), + FSL_ASOC_SINGLE_XR_SX_EXT_RO("Receive Bit Counter", FSL_SAI_RBCTN, + 1, 32, 0, 0xffffffff, 0, fsl_asoc_get_xr_sx), + FSL_ASOC_SINGLE_XR_SX_EXT_RO("Receive Latched Timestamp Counter", FSL_SAI_RTCAP, + 1, 32, 0, 0xffffffff, 0, fsl_asoc_get_xr_sx), +}; + /** * fsl_sai_dir_is_synced - Check if stream is synced by the opposite stream * @@ -1010,6 +1056,17 @@ static int fsl_sai_dai_resume(struct snd_soc_component *component) return 0; } +static int fsl_sai_component_probe(struct snd_soc_component *component) +{ + struct fsl_sai *sai = snd_soc_component_get_drvdata(component); + + if (sai->verid.feature & FSL_SAI_VERID_TSTMP_EN) + snd_soc_add_component_controls(component, fsl_sai_timestamp_ctrls, + ARRAY_SIZE(fsl_sai_timestamp_ctrls)); + + return 0; +} + static struct snd_soc_dai_driver fsl_sai_dai_template[] = { { .name = "sai-tx-rx", @@ -1063,6 +1120,7 @@ static struct snd_soc_dai_driver fsl_sai_dai_template[] = { static const struct snd_soc_component_driver fsl_component = { .name = "fsl-sai", + .probe = fsl_sai_component_probe, .resume = fsl_sai_dai_resume, .legacy_dai_naming = 1, }; @@ -1211,6 +1269,14 @@ static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg) case FSL_SAI_RDR5: case FSL_SAI_RDR6: case FSL_SAI_RDR7: + case FSL_SAI_TTCTN: + case FSL_SAI_RTCTN: + case FSL_SAI_TTCTL: + case FSL_SAI_TBCTN: + case FSL_SAI_TTCAP: + case FSL_SAI_RTCTL: + case FSL_SAI_RBCTN: + case FSL_SAI_RTCAP: return true; default: return false; diff --git a/sound/soc/fsl/fsl_sai.h b/sound/soc/fsl/fsl_sai.h index 7605cbaca3d8..af967833b6ed 100644 --- a/sound/soc/fsl/fsl_sai.h +++ b/sound/soc/fsl/fsl_sai.h @@ -196,9 +196,13 @@ #define FSL_SAI_MDIV_MASK 0xFFFFF /* SAI timestamp and bitcounter */ +#define FSL_SAI_xTCTL_TSEN_SHIFT 0 #define FSL_SAI_xTCTL_TSEN BIT(0) +#define FSL_SAI_xTCTL_TSINC_SHIFT 1 #define FSL_SAI_xTCTL_TSINC BIT(1) +#define FSL_SAI_xTCTL_RTSC_SHIFT 8 #define FSL_SAI_xTCTL_RTSC BIT(8) +#define FSL_SAI_xTCTL_RBC_SHIFT 9 #define FSL_SAI_xTCTL_RBC BIT(9) /* SAI type */ From 7b3f8db159f710d432c4edc024fcefa9e62e8b4b Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Tue, 10 Mar 2026 18:42:35 +0800 Subject: [PATCH 076/359] ASoC: fsl_xcvr: add bitcount and timestamp controls The transmitter and receiver implement separate timestamp counters and bit counters. The bit counter increments at the end of each bit in a frame whenever the transmitter or receiver is enabled. The bit counter can be reset by software. The timestamp counter increments on the bus interface clock whenever it is enabled. The current value of the timestamp counter is latched whenever the bit counter increments. Reading the bit counter register will cause the latched timestamp value to be saved in the bit counter timestamp register. The timestamp counter can be reset by software, this also resets the latched timestamp value and the bit counter timestamp register. The timestamp counter and bit counter can be used by software to track the progress of the transmitter and receiver. It can also be used to calculate the relative frequency of the bit clock against the bus interface clock. As there are three regmap handlers defined in this driver, explicitly call the snd_soc_component_init_regmap() to init regmap handler for the component. Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260310104235.1234569-4-shengjiu.wang@nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_xcvr.c | 64 ++++++++++++++++++++++++++++++++++++++++ sound/soc/fsl/fsl_xcvr.h | 18 +++++++++++ 2 files changed, 82 insertions(+) diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c index a268fb81a2f8..de25d9e667ee 100644 --- a/sound/soc/fsl/fsl_xcvr.c +++ b/sound/soc/fsl/fsl_xcvr.c @@ -62,6 +62,58 @@ struct fsl_xcvr { u32 spdif_constr_rates_list[SPDIF_NUM_RATES]; }; +static const char * const inc_mode[] = { + "On enabled and bitcount increment", "On enabled" +}; + +static SOC_ENUM_SINGLE_DECL(transmit_tstmp_enum, + FSL_XCVR_TX_DPTH_CNTR_CTRL, + FSL_XCVR_TX_DPTH_CNTR_CTRL_TSINC_SHIFT, inc_mode); +static SOC_ENUM_SINGLE_DECL(receive_tstmp_enum, + FSL_XCVR_RX_DPTH_CNTR_CTRL, + FSL_XCVR_RX_DPTH_CNTR_CTRL_TSINC_SHIFT, inc_mode); + +static const struct snd_kcontrol_new fsl_xcvr_timestamp_ctrls[] = { + FSL_ASOC_SINGLE_EXT("Transmit Timestamp Control Switch", FSL_XCVR_TX_DPTH_CNTR_CTRL, + FSL_XCVR_TX_DPTH_CNTR_CTRL_TSEN_SHIFT, 1, 0, + fsl_asoc_get_volsw, fsl_asoc_put_volsw), + FSL_ASOC_ENUM_EXT("Transmit Timestamp Increment", transmit_tstmp_enum, + fsl_asoc_get_enum_double, fsl_asoc_put_enum_double), + FSL_ASOC_SINGLE_EXT("Transmit Timestamp Reset Switch", FSL_XCVR_TX_DPTH_CNTR_CTRL, + FSL_XCVR_TX_DPTH_CNTR_CTRL_RTSC_SHIFT, 1, 0, + fsl_asoc_get_volsw, fsl_asoc_put_volsw), + FSL_ASOC_SINGLE_EXT("Transmit Bit Counter Reset Switch", FSL_XCVR_TX_DPTH_CNTR_CTRL, + FSL_XCVR_TX_DPTH_CNTR_CTRL_RBC_SHIFT, 1, 0, + fsl_asoc_get_volsw, fsl_asoc_put_volsw), + FSL_ASOC_SINGLE_XR_SX_EXT_RO("Transmit Timestamp Counter", FSL_XCVR_TX_DPTH_TSCR, + 1, 32, 0, 0xffffffff, 0, fsl_asoc_get_xr_sx), + FSL_ASOC_SINGLE_XR_SX_EXT_RO("Transmit Bit Counter", FSL_XCVR_TX_DPTH_BCR, + 1, 32, 0, 0xffffffff, 0, fsl_asoc_get_xr_sx), + FSL_ASOC_SINGLE_XR_SX_EXT_RO("Transmit Bit Count Timestamp", FSL_XCVR_TX_DPTH_BCTR, + 1, 32, 0, 0xffffffff, 0, fsl_asoc_get_xr_sx), + FSL_ASOC_SINGLE_XR_SX_EXT_RO("Transmit Latched Timestamp Counter", FSL_XCVR_TX_DPTH_BCRR, + 1, 32, 0, 0xffffffff, 0, fsl_asoc_get_xr_sx), + FSL_ASOC_SINGLE_EXT("Receive Timestamp Control Switch", FSL_XCVR_RX_DPTH_CNTR_CTRL, + FSL_XCVR_RX_DPTH_CNTR_CTRL_TSEN_SHIFT, 1, 0, + fsl_asoc_get_volsw, fsl_asoc_put_volsw), + FSL_ASOC_ENUM_EXT("Receive Timestamp Increment", receive_tstmp_enum, + fsl_asoc_get_enum_double, fsl_asoc_put_enum_double), + FSL_ASOC_SINGLE_EXT("Receive Timestamp Reset Switch", FSL_XCVR_RX_DPTH_CNTR_CTRL, + FSL_XCVR_RX_DPTH_CNTR_CTRL_RTSC_SHIFT, 1, 0, + fsl_asoc_get_volsw, fsl_asoc_put_volsw), + FSL_ASOC_SINGLE_EXT("Receive Bit Counter Reset Switch", FSL_XCVR_RX_DPTH_CNTR_CTRL, + FSL_XCVR_RX_DPTH_CNTR_CTRL_RBC_SHIFT, 1, 0, + fsl_asoc_get_volsw, fsl_asoc_put_volsw), + FSL_ASOC_SINGLE_XR_SX_EXT_RO("Receive Timestamp Counter", FSL_XCVR_RX_DPTH_TSCR, + 1, 32, 0, 0xffffffff, 0, fsl_asoc_get_xr_sx), + FSL_ASOC_SINGLE_XR_SX_EXT_RO("Receive Bit Counter", FSL_XCVR_RX_DPTH_BCR, + 1, 32, 0, 0xffffffff, 0, fsl_asoc_get_xr_sx), + FSL_ASOC_SINGLE_XR_SX_EXT_RO("Receive Bit Count Timestamp", FSL_XCVR_RX_DPTH_BCTR, + 1, 32, 0, 0xffffffff, 0, fsl_asoc_get_xr_sx), + FSL_ASOC_SINGLE_XR_SX_EXT_RO("Receive Latched Timestamp Counter", FSL_XCVR_RX_DPTH_BCRR, + 1, 32, 0, 0xffffffff, 0, fsl_asoc_get_xr_sx), +}; + static const struct fsl_xcvr_pll_conf { u8 mfi; /* min=0x18, max=0x38 */ u32 mfn; /* signed int, 2's compl., min=0x3FFF0000, max=0x00010000 */ @@ -1070,8 +1122,20 @@ static struct snd_soc_dai_driver fsl_xcvr_dai = { }, }; +static int fsl_xcvr_component_probe(struct snd_soc_component *component) +{ + struct fsl_xcvr *xcvr = snd_soc_component_get_drvdata(component); + + snd_soc_component_init_regmap(component, xcvr->regmap); + + return 0; +} + static const struct snd_soc_component_driver fsl_xcvr_comp = { .name = "fsl-xcvr-dai", + .probe = fsl_xcvr_component_probe, + .controls = fsl_xcvr_timestamp_ctrls, + .num_controls = ARRAY_SIZE(fsl_xcvr_timestamp_ctrls), .legacy_dai_naming = 1, }; diff --git a/sound/soc/fsl/fsl_xcvr.h b/sound/soc/fsl/fsl_xcvr.h index dade3945cc0c..0cc7945b1d9f 100644 --- a/sound/soc/fsl/fsl_xcvr.h +++ b/sound/soc/fsl/fsl_xcvr.h @@ -233,6 +233,24 @@ #define FSL_XCVR_TX_DPTH_CTRL_CLK_RATIO BIT(29) #define FSL_XCVR_TX_DPTH_CTRL_TM_NO_PRE_BME GENMASK(31, 30) +#define FSL_XCVR_RX_DPTH_CNTR_CTRL_TSEN_SHIFT 0 +#define FSL_XCVR_RX_DPTH_CNTR_CTRL_TSEN BIT(0) +#define FSL_XCVR_RX_DPTH_CNTR_CTRL_TSINC_SHIFT 1 +#define FSL_XCVR_RX_DPTH_CNTR_CTRL_TSINC BIT(1) +#define FSL_XCVR_RX_DPTH_CNTR_CTRL_RBC_SHIFT 8 +#define FSL_XCVR_RX_DPTH_CNTR_CTRL_RBC BIT(8) +#define FSL_XCVR_RX_DPTH_CNTR_CTRL_RTSC_SHIFT 9 +#define FSL_XCVR_RX_DPTH_CNTR_CTRL_RTSC BIT(9) + +#define FSL_XCVR_TX_DPTH_CNTR_CTRL_TSEN_SHIFT 0 +#define FSL_XCVR_TX_DPTH_CNTR_CTRL_TSEN BIT(0) +#define FSL_XCVR_TX_DPTH_CNTR_CTRL_TSINC_SHIFT 1 +#define FSL_XCVR_TX_DPTH_CNTR_CTRL_TSINC BIT(1) +#define FSL_XCVR_TX_DPTH_CNTR_CTRL_RBC_SHIFT 8 +#define FSL_XCVR_TX_DPTH_CNTR_CTRL_RBC BIT(8) +#define FSL_XCVR_TX_DPTH_CNTR_CTRL_RTSC_SHIFT 9 +#define FSL_XCVR_TX_DPTH_CNTR_CTRL_RTSC BIT(9) + #define FSL_XCVR_PHY_AI_CTRL_AI_RESETN BIT(15) #define FSL_XCVR_PHY_AI_CTRL_AI_RWB BIT(31) From 1eadb7791ee5699ca58920ef705ce0934c814ece Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 10 Mar 2026 14:18:08 +0000 Subject: [PATCH 077/359] ASoC: wm_adsp: Remove unused argument to wm_adsp_release_firmware_files() The dsp argument to wm_adsp_release_firmware_files() isn't used so remove it. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260310141817.1871794-2-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm_adsp.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index 2e23848e1dce..b3cead2cd758 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -704,8 +704,7 @@ int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name, int type, } EXPORT_SYMBOL_GPL(wm_adsp_read_ctl); -static void wm_adsp_release_firmware_files(struct wm_adsp *dsp, - const struct firmware *wmfw_firmware, +static void wm_adsp_release_firmware_files(const struct firmware *wmfw_firmware, char *wmfw_filename, const struct firmware *coeff_firmware, char *coeff_filename) @@ -908,8 +907,7 @@ int wm_adsp1_event(struct snd_soc_dapm_widget *w, coeff_firmware, coeff_filename, wm_adsp_fw_text[dsp->fw]); - wm_adsp_release_firmware_files(dsp, - wmfw_firmware, wmfw_filename, + wm_adsp_release_firmware_files(wmfw_firmware, wmfw_filename, coeff_firmware, coeff_filename); break; case SND_SOC_DAPM_PRE_PMD: @@ -1011,8 +1009,7 @@ int wm_adsp_power_up(struct wm_adsp *dsp, bool load_firmware) wm_adsp_fw_text[dsp->fw]); err: - wm_adsp_release_firmware_files(dsp, - wmfw_firmware, wmfw_filename, + wm_adsp_release_firmware_files(wmfw_firmware, wmfw_filename, coeff_firmware, coeff_filename); return ret; From 70057cfe492d600c2b83598e8b8b64780b2ca147 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 10 Mar 2026 14:18:09 +0000 Subject: [PATCH 078/359] ASoC: wm_adsp: Add KUnit redirection stubs for firmware file search Make some of the firmware file search functions redirectable for KUnit testing. - The call to firmware_request_nowarn() is factored out into a wrapper function so that it can be redirected. - wm_adsp_request_firmware_files() and wm_adsp_release_firmware_files() are made visible and exported if KUNIT is enabled. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260310141817.1871794-3-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm_adsp.c | 38 ++++++++++++++++++++++++++++---------- sound/soc/codecs/wm_adsp.h | 15 +++++++++++++++ 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index b3cead2cd758..e32da6949d1f 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -7,6 +7,8 @@ * Author: Mark Brown */ +#include +#include #include #include #include @@ -704,17 +706,32 @@ int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name, int type, } EXPORT_SYMBOL_GPL(wm_adsp_read_ctl); -static void wm_adsp_release_firmware_files(const struct firmware *wmfw_firmware, - char *wmfw_filename, - const struct firmware *coeff_firmware, - char *coeff_filename) +VISIBLE_IF_KUNIT void wm_adsp_release_firmware_files(const struct firmware *wmfw_firmware, + char *wmfw_filename, + const struct firmware *coeff_firmware, + char *coeff_filename) { + KUNIT_STATIC_STUB_REDIRECT(wm_adsp_release_firmware_files, + wmfw_firmware, wmfw_filename, + coeff_firmware, coeff_filename); + release_firmware(wmfw_firmware); kfree(wmfw_filename); release_firmware(coeff_firmware); kfree(coeff_filename); } +EXPORT_SYMBOL_IF_KUNIT(wm_adsp_release_firmware_files); + +VISIBLE_IF_KUNIT int wm_adsp_firmware_request(const struct firmware **firmware, + const char *filename, + struct device *dev) +{ + KUNIT_STATIC_STUB_REDIRECT(wm_adsp_firmware_request, firmware, filename, dev); + + return firmware_request_nowarn(firmware, filename, dev); +} +EXPORT_SYMBOL_IF_KUNIT(wm_adsp_firmware_request); static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, const struct firmware **firmware, char **filename, @@ -762,7 +779,7 @@ static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, s++; } - ret = firmware_request_nowarn(firmware, *filename, cs_dsp->dev); + ret = wm_adsp_firmware_request(firmware, *filename, cs_dsp->dev); if (ret != 0) { adsp_dbg(dsp, "Failed to request '%s'\n", *filename); kfree(*filename); @@ -775,11 +792,11 @@ static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, } static const char * const cirrus_dir = "cirrus/"; -static int wm_adsp_request_firmware_files(struct wm_adsp *dsp, - const struct firmware **wmfw_firmware, - char **wmfw_filename, - const struct firmware **coeff_firmware, - char **coeff_filename) +VISIBLE_IF_KUNIT int wm_adsp_request_firmware_files(struct wm_adsp *dsp, + const struct firmware **wmfw_firmware, + char **wmfw_filename, + const struct firmware **coeff_firmware, + char **coeff_filename) { const char *system_name = dsp->system_name; const char *suffix = dsp->component->name_prefix; @@ -856,6 +873,7 @@ static int wm_adsp_request_firmware_files(struct wm_adsp *dsp, return -ENOENT; } +EXPORT_SYMBOL_IF_KUNIT(wm_adsp_request_firmware_files); static int wm_adsp_common_init(struct wm_adsp *dsp) { diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h index 8035fda71f8d..7c667123758b 100644 --- a/sound/soc/codecs/wm_adsp.h +++ b/sound/soc/codecs/wm_adsp.h @@ -142,4 +142,19 @@ int wm_adsp_write_ctl(struct wm_adsp *dsp, const char *name, int type, int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name, int type, unsigned int alg, void *buf, size_t len); +#if IS_ENABLED(CONFIG_KUNIT) +void wm_adsp_release_firmware_files(const struct firmware *wmfw_firmware, + char *wmfw_filename, + const struct firmware *coeff_firmware, + char *coeff_filename); +int wm_adsp_firmware_request(const struct firmware **firmware, + const char *filename, + struct device *dev); +int wm_adsp_request_firmware_files(struct wm_adsp *dsp, + const struct firmware **wmfw_firmware, + char **wmfw_filename, + const struct firmware **coeff_firmware, + char **coeff_filename); +#endif + #endif From b4e6b01191afb9516570437a7aff61019134fd59 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 10 Mar 2026 14:18:10 +0000 Subject: [PATCH 079/359] ASoC: wm_adsp: Export function for KUnit test to get firmware filenames Export a function that KUnit tests can use to get the firmware filenames from the wm_adsp_fw[] array. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260310141817.1871794-4-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm_adsp.c | 11 +++++++++++ sound/soc/codecs/wm_adsp.h | 1 + 2 files changed, 12 insertions(+) diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index e32da6949d1f..346ede149a9e 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -318,6 +318,17 @@ struct wm_coeff_ctl { struct work_struct work; }; +#if IS_ENABLED(CONFIG_KUNIT) +const char *wm_adsp_get_fwf_name_by_index(int index) +{ + if (index < ARRAY_SIZE(wm_adsp_fw)) + return wm_adsp_fw[index].file; + + return NULL; +} +EXPORT_SYMBOL_IF_KUNIT(wm_adsp_get_fwf_name_by_index); +#endif + int wm_adsp_fw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h index 7c667123758b..6560dfc8c08d 100644 --- a/sound/soc/codecs/wm_adsp.h +++ b/sound/soc/codecs/wm_adsp.h @@ -143,6 +143,7 @@ int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name, int type, unsigned int alg, void *buf, size_t len); #if IS_ENABLED(CONFIG_KUNIT) +const char *wm_adsp_get_fwf_name_by_index(int index); void wm_adsp_release_firmware_files(const struct firmware *wmfw_firmware, char *wmfw_filename, const struct firmware *coeff_firmware, From bf2d44d07de726b0393439cb4d4defc5cf89a4fc Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 10 Mar 2026 14:18:11 +0000 Subject: [PATCH 080/359] ASoC: wm_adsp: Add kunit test for firmware file search Add KUnit testing of the wm_adsp code that searches for firmware files. Also make the SND_SOC_WM_ADSP Kconfig symbol visible if KUNIT is enabled so that wm_adsp can be manually included for KUnit testing. The firmware filename is composed of several fields, some of which are optional, and there is a search algorithm to fallback from specific to generic versions of firmware for a device. This KUnit test verifies that wm_adsp is searching for the correct sequence of filenames. The are two ways of testing this, and both are used in this KUnit test. 1. Trap the calls to firmware_request_nowarn() and test that the sequence of filenames request is correct. This is the most thorough test because it proves that exactly the expected filenames are requested, in the correct order. But it doesn't fully cover regression testing. If a change to the search algorithm changes the expected sequence of requested files, the test must also be changed to expect that new sequence. If the expectation is wrong, the tests can pass (because the search order is as expected) while picking a different file in some cases from what it did before the change. 2. Test which file is picked from a simulated directory of files. This is better for regression testing because it is independent of the search algorithm. It does not need to change if the search algorithm changes. It is not testing exactly which files the algorithm searches for, only which file it eventually picks from a given set of available files. In other words, the regression test is: does it still pick the same file from the same directory of files? But it is impractical for thorough testing. It doesn't prove that exactly the correct files were searched for, unless it was to test with every possible combination of file names and directory content that could ever exist. Clearly this is impossible to implement, since the number of combations of possible valid filenames in a directory and number of files in a directory is astronomically large. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260310141817.1871794-5-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 14 +- sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm_adsp_fw_find_test.c | 1223 +++++++++++++++++++++++ 3 files changed, 1238 insertions(+), 1 deletion(-) create mode 100644 sound/soc/codecs/wm_adsp_fw_find_test.c diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index adb3fb923be3..f9e6a83e55c6 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -398,7 +398,7 @@ config SND_SOC_WM_HUBS default m if SND_SOC_WM8993=m || SND_SOC_WM8994=m config SND_SOC_WM_ADSP - tristate + tristate "Cirrus Logic wm_adsp driver" if KUNIT select FW_CS_DSP select SND_SOC_COMPRESS default y if SND_SOC_MADERA=y @@ -424,6 +424,18 @@ config SND_SOC_WM_ADSP default m if SND_SOC_CS35L56=m default m if SND_SOC_CS48L32=m +config SND_SOC_WM_ADSP_TEST + tristate "KUnit tests for Cirrus Logic wm_adsp" if !KUNIT_ALL_TESTS + depends on KUNIT + depends on SND_SOC_WM_ADSP + default KUNIT_ALL_TESTS + help + This builds KUnit tests for the Cirrus Logic wm_adsp library. + For more information on KUnit and unit tests in general, + please refer to the KUnit documentation in + Documentation/dev-tools/kunit/. + If in doubt, say "N". + config SND_SOC_AB8500_CODEC tristate depends on ABX500_CORE diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 3ddee5298721..172861d17cfd 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -361,6 +361,7 @@ snd-soc-wcd938x-sdw-y := wcd938x-sdw.o snd-soc-wcd939x-y := wcd939x.o snd-soc-wcd939x-sdw-y := wcd939x-sdw.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 snd-soc-wm1250-ev1-y := wm1250-ev1.o snd-soc-wm2000-y := wm2000.o @@ -862,6 +863,7 @@ obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o +obj-$(CONFIG_SND_SOC_WM_ADSP_TEST) += snd-soc-wm-adsp-test.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o obj-$(CONFIG_SND_SOC_WSA881X) += snd-soc-wsa881x.o obj-$(CONFIG_SND_SOC_WSA883X) += snd-soc-wsa883x.o diff --git a/sound/soc/codecs/wm_adsp_fw_find_test.c b/sound/soc/codecs/wm_adsp_fw_find_test.c new file mode 100644 index 000000000000..556221d38a50 --- /dev/null +++ b/sound/soc/codecs/wm_adsp_fw_find_test.c @@ -0,0 +1,1223 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Test cases for wm_adsp library. +// +// Copyright (C) 2025 Cirrus Logic, Inc. and +// Cirrus Logic International Semiconductor Ltd. + +#include +#include +#include +#include +#include "wm_adsp.h" + +KUNIT_DEFINE_ACTION_WRAPPER(_put_device_wrapper, put_device, struct device *); + +struct wm_adsp_fw_find_test { + struct wm_adsp dsp; + + const struct firmware *found_wmfw_firmware; + const struct firmware *found_bin_firmware; + char *found_wmfw_filename; + char *found_bin_filename; + char searched_fw_files[768]; +}; + +struct wm_adsp_fw_find_test_params { + const char *part; + const char *dsp_name; + const char *fwf_name; + const char *system_name; + const char *alsa_name; + bool wmfw_optional; + bool bin_mandatory; + + /* If non-NULL this file should be returned as "found" */ + const char *expect_wmfw; + + /* If non-NULL this file should be returned as "found" */ + const char *expect_bin; + + /* Space-separated list of filenames in expected order of searching */ + const char *expected_searches; + + /* NULL-terminated array of pointers to filenames to simulate directory content */ + const char * const *dir_files; +}; + +/* Dummy struct firmware to return from wm_adsp_request_firmware_files */ +static const struct firmware wm_adsp_find_test_dummy_firmware; + +/* Simple lookup of a filename in a list of names */ +static int wm_adsp_fw_find_test_firmware_request_simple_stub(const struct firmware **firmware, + const char *filename, + struct device *dev) +{ + struct kunit *test = kunit_get_current_test(); + const struct wm_adsp_fw_find_test_params *params = test->param_value; + int i; + + /* Non-parameterized test? */ + if (!params) + return -ENOENT; + + if (!params->dir_files) + return -ENOENT; + + for (i = 0; params->dir_files[i]; i++) { + if (strcmp(params->dir_files[i], filename) == 0) { + *firmware = &wm_adsp_find_test_dummy_firmware; + return 0; + } + } + + return -ENOENT; +} + +static void wm_adsp_fw_find_test_pick_file(struct kunit *test) +{ + struct wm_adsp_fw_find_test *priv = test->priv; + const struct wm_adsp_fw_find_test_params *params = test->param_value; + struct wm_adsp *dsp = &priv->dsp; + int i, ret; + + /* Concatenate string of dir content for error messages */ + for (i = 0; params->dir_files[i]; i++) { + strlcat(priv->searched_fw_files, params->dir_files[i], + sizeof(priv->searched_fw_files)); + strlcat(priv->searched_fw_files, ";", + sizeof(priv->searched_fw_files)); + } + + dsp->cs_dsp.name = params->dsp_name; + dsp->part = params->part; + dsp->fwf_name = params->fwf_name; + dsp->system_name = params->system_name; + dsp->component->name_prefix = params->alsa_name; + dsp->wmfw_optional = params->wmfw_optional; + dsp->bin_mandatory = params->bin_mandatory; + + kunit_activate_static_stub(test, + wm_adsp_firmware_request, + wm_adsp_fw_find_test_firmware_request_simple_stub); + + ret = wm_adsp_request_firmware_files(dsp, + &priv->found_wmfw_firmware, + &priv->found_wmfw_filename, + &priv->found_bin_firmware, + &priv->found_bin_filename); + kunit_deactivate_static_stub(test, wm_adsp_firmware_request); + KUNIT_EXPECT_EQ_MSG(test, ret, + (params->expect_wmfw || params->expect_bin) ? 0 : -ENOENT, + "%s\n", priv->searched_fw_files); + + KUNIT_EXPECT_EQ_MSG(test, !!priv->found_wmfw_filename, !!params->expect_wmfw, + "%s\n", priv->searched_fw_files); + KUNIT_EXPECT_EQ_MSG(test, !!priv->found_bin_filename, !!params->expect_bin, + "%s\n", priv->searched_fw_files); + + if (params->expect_wmfw) { + KUNIT_EXPECT_STREQ_MSG(test, priv->found_wmfw_filename, params->expect_wmfw, + "%s\n", priv->searched_fw_files); + } + + if (params->expect_bin) { + KUNIT_EXPECT_STREQ_MSG(test, priv->found_bin_filename, params->expect_bin, + "%s\n", priv->searched_fw_files); + } +} + +static int wm_adsp_fw_find_test_firmware_request_stub(const struct firmware **firmware, + const char *filename, + struct device *dev) +{ + struct kunit *test = kunit_get_current_test(); + const struct wm_adsp_fw_find_test_params *params = test->param_value; + struct wm_adsp_fw_find_test *priv = test->priv; + + /* + * Searches are accumulated as a single string of space-separated names. + * The list of expected searches are stored the same way in + * struct wm_adsp_fw_find_test_params. This allows for comparision using + * a simple KUNIT_EXPECT_STREQ(), which avoids the risk of bugs in a + * more complex custom comparison. + */ + if (priv->searched_fw_files[0] != '\0') + strlcat(priv->searched_fw_files, " ", sizeof(priv->searched_fw_files)); + + strlcat(priv->searched_fw_files, filename, sizeof(priv->searched_fw_files)); + + /* Non-parameterized test? */ + if (!params) + return -ENOENT; + + if (params->expect_wmfw && (strcmp(filename, params->expect_wmfw) == 0)) { + *firmware = &wm_adsp_find_test_dummy_firmware; + return 0; + } + + if (params->expect_bin && (strcmp(filename, params->expect_bin) == 0)) { + *firmware = &wm_adsp_find_test_dummy_firmware; + return 0; + } + + return -ENOENT; +} + +static void wm_adsp_fw_find_test_search_order(struct kunit *test) +{ + struct wm_adsp_fw_find_test *priv = test->priv; + const struct wm_adsp_fw_find_test_params *params = test->param_value; + struct wm_adsp *dsp = &priv->dsp; + + dsp->cs_dsp.name = params->dsp_name; + dsp->part = params->part; + dsp->fwf_name = params->fwf_name; + dsp->system_name = params->system_name; + dsp->component->name_prefix = params->alsa_name; + dsp->wmfw_optional = params->wmfw_optional; + + kunit_activate_static_stub(test, + wm_adsp_firmware_request, + wm_adsp_fw_find_test_firmware_request_stub); + + wm_adsp_request_firmware_files(dsp, + &priv->found_wmfw_firmware, + &priv->found_wmfw_filename, + &priv->found_bin_firmware, + &priv->found_bin_filename); + + kunit_deactivate_static_stub(test, wm_adsp_firmware_request); + + KUNIT_EXPECT_STREQ(test, priv->searched_fw_files, params->expected_searches); + + KUNIT_EXPECT_EQ(test, !!priv->found_wmfw_filename, !!params->expect_wmfw); + if (params->expect_wmfw) + KUNIT_EXPECT_STREQ(test, priv->found_wmfw_filename, params->expect_wmfw); + + KUNIT_EXPECT_EQ(test, !!priv->found_bin_filename, !!params->expect_bin); + if (params->expect_bin) + KUNIT_EXPECT_STREQ(test, priv->found_bin_filename, params->expect_bin); + + /* Either we get a filename and firmware, or neither */ + KUNIT_EXPECT_EQ(test, !!priv->found_wmfw_filename, !!priv->found_wmfw_firmware); + KUNIT_EXPECT_EQ(test, !!priv->found_bin_filename, !!priv->found_bin_firmware); +} + +static void wm_adsp_fw_find_test_find_firmware_byindex(struct kunit *test) +{ + struct wm_adsp_fw_find_test *priv = test->priv; + struct wm_adsp *dsp = &priv->dsp; + const char *fw_name; + + dsp->cs_dsp.name = "cs1234"; + dsp->part = "dsp1"; + for (dsp->fw = 0;; dsp->fw++) { + fw_name = wm_adsp_get_fwf_name_by_index(dsp->fw); + if (!fw_name) + break; + + kunit_activate_static_stub(test, + wm_adsp_firmware_request, + wm_adsp_fw_find_test_firmware_request_stub); + + wm_adsp_request_firmware_files(dsp, + &priv->found_wmfw_firmware, + &priv->found_wmfw_filename, + &priv->found_bin_firmware, + &priv->found_bin_filename); + + kunit_deactivate_static_stub(test, wm_adsp_firmware_request); + + KUNIT_EXPECT_NOT_NULL_MSG(test, + strstr(priv->searched_fw_files, fw_name), + "fw#%d Did not find '%s' in '%s'\n", + dsp->fw, fw_name, priv->searched_fw_files); + } +} + +static int wm_adsp_fw_find_test_case_init(struct kunit *test) +{ + struct wm_adsp_fw_find_test *priv; + struct device *test_dev; + int ret; + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* Require dummy struct snd_soc_component for the alsa name prefix string */ + priv->dsp.component = kunit_kzalloc(test, sizeof(*priv->dsp.component), GFP_KERNEL); + if (!priv->dsp.component) + return -ENOMEM; + + test->priv = priv; + + /* Create dummy amp device */ + test_dev = kunit_device_register(test, "wm_adsp_test_drv"); + if (IS_ERR(test_dev)) + return PTR_ERR(test_dev); + + priv->dsp.cs_dsp.dev = get_device(test_dev); + if (!priv->dsp.cs_dsp.dev) + return -ENODEV; + + ret = kunit_add_action_or_reset(test, _put_device_wrapper, priv->dsp.cs_dsp.dev); + if (ret) + return ret; + + return 0; +} + +static void wm_adsp_fw_find_test_case_exit(struct kunit *test) +{ + struct wm_adsp_fw_find_test *priv = test->priv; + + /* + * priv->found_wmfw_firmware and priv->found_bin_firmware are + * dummies not allocated by the real request_firmware() call they + * must not be passed to release_firmware(). + */ + wm_adsp_release_firmware_files(NULL, priv->found_wmfw_filename, + NULL, priv->found_bin_filename); +} + +static void wm_adsp_fw_find_test_param_desc(const struct wm_adsp_fw_find_test_params *param, + char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, + "%s %s fwf_name:%s system:%s alsa_name:%s %s expects:(%s %s)", + param->part, param->dsp_name, + param->fwf_name ? param->fwf_name : "", + param->system_name ? param->system_name : "", + param->alsa_name ? param->alsa_name : "", + param->wmfw_optional ? "wmfw_optional" : "", + param->expect_wmfw ? param->expect_wmfw : "", + param->expect_bin ? param->expect_bin : ""); +} + +/* Cases where firmware file not found. Tests full search sequence. */ +static const struct wm_adsp_fw_find_test_params wm_adsp_fw_find_full_search_cases[] = { + { /* system name and alsa prefix, wmfw mandatory. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw", + }, + { /* system name and alsa prefix, wmfw optional. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", .wmfw_optional = true, + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin " + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* system name only, wmfw mandatory. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw", + }, + { /* system name only, wmfw optional. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .wmfw_optional = true, + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin " + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + + /* + * TODO: Is this a bug? Device-specific bin is only allowed when there + * is a system_name. But if there isn't any meaningful system name on + * a product, why can't it load firmware files qualified by alsa prefix? + */ + + { /* Alsa prefix, wmfw mandatory. No system name so generic files only. */ + .part = "cs1234", .dsp_name = "dsp1", .alsa_name = "amp1", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw", + }, + { /* Alsa prefix, wmfw optional. No system name so generic files only. */ + .part = "cs1234", .dsp_name = "dsp1", .alsa_name = "amp1", + .wmfw_optional = true, + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + + { /* fwf_name, system name and alsa prefix, wmfw mandatory. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", .fwf_name = "ao", + .expected_searches = + "cirrus/cs1234-ao-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-ao-mbc-vss-abc123.wmfw " + "cs1234-ao-mbc-vss.wmfw " + "cirrus/cs1234-ao-mbc-vss.wmfw", + }, + { /* fwf_name, system name and alsa prefix, wmfw optional. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", .fwf_name = "ao", + .wmfw_optional = true, + .expected_searches = + "cirrus/cs1234-ao-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-ao-mbc-vss-abc123.wmfw " + "cirrus/cs1234-ao-mbc-vss-abc123-amp1.bin " + "cirrus/cs1234-ao-mbc-vss-abc123.bin " + "cs1234-ao-mbc-vss.wmfw " + "cirrus/cs1234-ao-mbc-vss.wmfw " + "cirrus/cs1234-ao-mbc-vss.bin", + }, +}; +KUNIT_ARRAY_PARAM(wm_adsp_fw_find_full_search, + wm_adsp_fw_find_full_search_cases, + wm_adsp_fw_find_test_param_desc); + +/* Cases with system name and alsa prefix both given. */ +static const struct wm_adsp_fw_find_test_params wm_adsp_fw_find_system_alsaname_cases[] = { + { /* Fully-qualified wmfw exists. No bin */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin", + }, + { /* Optional fully-qualified wmfw exists. No bin */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin", + }, + { /* Fully-qualified wmfw and bin exist. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin", + }, + { /* Optional fully-qualified wmfw and fully-qualified bin exist. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin", + }, + { /* wmfw matches system name only. No bin */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin", + }, + { /* Optional wmfw matches system name only. No bin */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin", + }, + { /* wmfw matches system name only. Fully-qualified bin. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin", + }, + { /* Optional wmfw matches system name only. Fully-qualified bin. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin", + }, + { /* wmfw and bin match system name only. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-abc123.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin", + }, + { /* Optional wmfw and bin match system name only. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-abc123.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin", + }, + { /* Optional wmfw not found. bin matches fully-qualified name. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", .wmfw_optional = true, + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin", + }, + { /* Optional wmfw not found. bin matches system name only. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", .wmfw_optional = true, + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-abc123.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin", + }, + { /* No qualified wmfw. Legacy generic wmfw found. No bin. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", + .expect_wmfw = "cs1234-dsp1-mbc-vss.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cs1234-dsp1-mbc-vss.wmfw " + "cs1234-dsp1-mbc-vss.bin", + }, + { /* No qualified optional wmfw. Legacy generic wmfw found. No bin. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", .wmfw_optional = true, + .expect_wmfw = "cs1234-dsp1-mbc-vss.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin " + "cs1234-dsp1-mbc-vss.wmfw " + "cs1234-dsp1-mbc-vss.bin", + }, + { /* No qualified wmfw. Legacy generic wmfw and bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", + .expect_wmfw = "cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cs1234-dsp1-mbc-vss.wmfw " + "cs1234-dsp1-mbc-vss.bin", + }, + { /* No qualified optional wmfw. Legacy generic wmfw and bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", .wmfw_optional = true, + .expect_wmfw = "cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin " + "cs1234-dsp1-mbc-vss.wmfw " + "cs1234-dsp1-mbc-vss.bin", + }, + { /* No qualified or legacy wmfw. Generic wmfw found. No bin. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* No optional qualified or legacy wmfw. Generic wmfw found. No bin. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin " + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* No qualified or legacy wmfw. Generic wmfw and bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* No optional qualified or legacy wmfw. Generic wmfw and bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin " + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* No optional qualified or generic wmfw. Generic bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .alsa_name = "amp1", .wmfw_optional = true, + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123-amp1.bin " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin " + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, +}; +KUNIT_ARRAY_PARAM(wm_adsp_fw_find_system_alsaname, + wm_adsp_fw_find_system_alsaname_cases, + wm_adsp_fw_find_test_param_desc); + +/* Cases with system name but without alsa name prefix. */ +static const struct wm_adsp_fw_find_test_params wm_adsp_fw_find_system_cases[] = { + { /* Qualified wmfw found. No bin */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin", + }, + { /* Optional qualified wmfw found. No bin */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin", + }, + { /* Qualified wmfw found. Qualified bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-abc123.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin", + }, + { /* Optional qualified wmfw found. Qualified bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-abc123.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin", + }, + { /* Optional wmfw not found. Qualified bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .wmfw_optional = true, + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-abc123.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin", + }, + { /* No qualified wmfw. Legacy generic wmfw found. No bin. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .expect_wmfw = "cs1234-dsp1-mbc-vss.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cs1234-dsp1-mbc-vss.wmfw " + "cs1234-dsp1-mbc-vss.bin", + }, + { /* No qualified optional wmfw. Legacy generic wmfw found. No bin. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .wmfw_optional = true, + .expect_wmfw = "cs1234-dsp1-mbc-vss.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin " + "cs1234-dsp1-mbc-vss.wmfw " + "cs1234-dsp1-mbc-vss.bin", + }, + { /* No qualified wmfw. Legacy generic wmfw and bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .expect_wmfw = "cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cs1234-dsp1-mbc-vss.wmfw " + "cs1234-dsp1-mbc-vss.bin", + }, + { /* No qualified optional wmfw. Legacy generic wmfw and bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .wmfw_optional = true, + .expect_wmfw = "cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin " + "cs1234-dsp1-mbc-vss.wmfw " + "cs1234-dsp1-mbc-vss.bin", + }, + { /* No qualified or legacy wmfw. Generic wmfw found. No bin. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* No optional qualified or legacy wmfw. Generic wmfw found. No bin. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin " + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* No qualified or legacy wmfw. Generic wmfw and bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* No optional qualified or legacy wmfw. Generic wmfw and bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin " + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* No optional qualified or generic wmfw. Generic bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "ABC123", + .wmfw_optional = true, + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc123.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc123.bin " + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, +}; +KUNIT_ARRAY_PARAM(wm_adsp_fw_find_system, + wm_adsp_fw_find_system_cases, + wm_adsp_fw_find_test_param_desc); + +/* Cases without system name but with alsa name prefix. */ +static const struct wm_adsp_fw_find_test_params wm_adsp_fw_find_alsaname_cases[] = { + { /* Legacy generic wmfw found. No bin. */ + .part = "cs1234", .dsp_name = "dsp1", .alsa_name = "amp1", + .expect_wmfw = "cs1234-dsp1-mbc-vss.wmfw", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cs1234-dsp1-mbc-vss.bin", + }, + { /* wmfw optional. Legacy generic wmfw found. No bin. */ + .part = "cs1234", .dsp_name = "dsp1", .alsa_name = "amp1", + .wmfw_optional = true, + .expect_wmfw = "cs1234-dsp1-mbc-vss.wmfw", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cs1234-dsp1-mbc-vss.bin", + }, + { /* Legacy generic wmfw and bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .alsa_name = "amp1", + .expect_wmfw = "cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cs1234-dsp1-mbc-vss.bin", + }, + { /* wmfw optional. Legacy generic wmfw and bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .alsa_name = "amp1", + .wmfw_optional = true, + .expect_wmfw = "cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cs1234-dsp1-mbc-vss.bin", + }, + { /* No legacy wmfw. Generic wmfw found. No bin. */ + .part = "cs1234", .dsp_name = "dsp1", .alsa_name = "amp1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* No legacy wmfw. Optional generic wmfw found. No bin. */ + .part = "cs1234", .dsp_name = "dsp1", .alsa_name = "amp1", + .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* No legacy wmfw. Generic wmfw and bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .alsa_name = "amp1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* No legacy wmfw. Optional generic wmfw and bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .alsa_name = "amp1", + .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* No legacy or generic wmfw. Generic bin found. */ + .part = "cs1234", .dsp_name = "dsp1", .alsa_name = "amp1", + .wmfw_optional = true, + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, +}; +KUNIT_ARRAY_PARAM(wm_adsp_fw_find_alsaname, + wm_adsp_fw_find_alsaname_cases, + wm_adsp_fw_find_test_param_desc); + +/* Cases without system name or alsa name prefix. */ +static const struct wm_adsp_fw_find_test_params wm_adsp_fw_find_noqual_cases[] = { + { /* Legacy generic wmfw found. No bin. */ + .part = "cs1234", .dsp_name = "dsp1", + .expect_wmfw = "cs1234-dsp1-mbc-vss.wmfw", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cs1234-dsp1-mbc-vss.bin", + }, + { /* wmfw optional. Legacy generic wmfw found. No bin. */ + .part = "cs1234", .dsp_name = "dsp1", + .wmfw_optional = true, + .expect_wmfw = "cs1234-dsp1-mbc-vss.wmfw", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cs1234-dsp1-mbc-vss.bin", + }, + { /* Legacy generic wmfw and bin found. */ + .part = "cs1234", .dsp_name = "dsp1", + .expect_wmfw = "cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cs1234-dsp1-mbc-vss.bin", + }, + { /* wmfw optional. Legacy generic wmfw and bin found. */ + .part = "cs1234", .dsp_name = "dsp1", + .wmfw_optional = true, + .expect_wmfw = "cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cs1234-dsp1-mbc-vss.bin", + }, + { /* No legacy wmfw. Generic wmfw found. No bin. */ + .part = "cs1234", .dsp_name = "dsp1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* No legacy wmfw. Optional generic wmfw found. No bin. */ + .part = "cs1234", .dsp_name = "dsp1", + .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* No legacy wmfw. Generic wmfw and bin found. */ + .part = "cs1234", .dsp_name = "dsp1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* No legacy wmfw. Optional generic wmfw and bin found. */ + .part = "cs1234", .dsp_name = "dsp1", + .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, + { /* No legacy or generic wmfw. Generic bin found. */ + .part = "cs1234", .dsp_name = "dsp1", + .wmfw_optional = true, + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss.bin", + .expected_searches = + "cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.wmfw " + "cirrus/cs1234-dsp1-mbc-vss.bin", + }, +}; +KUNIT_ARRAY_PARAM(wm_adsp_fw_find_noqual, + wm_adsp_fw_find_noqual_cases, + wm_adsp_fw_find_test_param_desc); + +/* + * Tests for filename normalization. The system name and alsa prefix strings + * should be converted to lower-case and delimiters are converted to '-', except + * for '.' which is preserved. + */ +static const struct wm_adsp_fw_find_test_params wm_adsp_fw_find_normalization_cases[] = { + { + .part = "cs1234", .dsp_name = "dsp1", .system_name = "Vendor", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-vendor.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-vendor.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-vendor.bin", + }, + { + .part = "cs1234", .dsp_name = "dsp1", .system_name = "Vendor Device", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-vendor-device.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-vendor-device.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-vendor-device.bin", + }, + { + .part = "cs1234", .dsp_name = "dsp1", .system_name = "Vendor_Device", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-vendor-device.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-vendor-device.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-vendor-device.bin", + }, + { + .part = "cs1234", .dsp_name = "dsp1", .system_name = "1234:56AB", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-1234-56ab.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-1234-56ab.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-1234-56ab.bin", + }, + + { + .part = "cs1234", .dsp_name = "dsp1", .system_name = "abc", + .alsa_name = "LEFT", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc-left.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc-left.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc-left.bin", + }, + { + .part = "cs1234", .dsp_name = "dsp1", .system_name = "abc", + .alsa_name = "LEFT AMP", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc-left-amp.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc-left-amp.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc-left-amp.bin", + }, + { + .part = "cs1234", .dsp_name = "dsp1", .system_name = "abc", + .alsa_name = "Left Amp", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc-left-amp.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc-left-amp.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc-left-amp.bin", + }, + { + .part = "cs1234", .dsp_name = "dsp1", .system_name = "abc", + .alsa_name = "Amp_1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc-amp-1.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc-amp-1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc-amp-1.bin", + }, + { + .part = "cs1234", .dsp_name = "dsp1", .system_name = "abc", + .alsa_name = "cs1234.1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc-cs1234.1.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc-cs1234.1.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc-cs1234.1.bin", + }, +}; +KUNIT_ARRAY_PARAM(wm_adsp_fw_find_normalization, + wm_adsp_fw_find_normalization_cases, + wm_adsp_fw_find_test_param_desc); + +/* + * Dummy directory content for regression tests. + * DSP part name and system name are used to select different available + * files. + * + * System: + * WFBF1111 = wmfw and bin fully-qualified + * WSBF1111 = wmfw system-qualified, bin fully-qualified + * WSBS1111 = wmfw and bin system-qualified + * WFXX1111 = wmfw fully-qualified, bin not present + * XXBF1111 = wmfw not present, bin fully-qualified + * + * Part: + * cs1234 = for testing fully-qualified configurations + * cs1234nobin = generic wmfw without a bin available + * wm1234 = legacy wmfw and bin + * wm1234nobin = legacy wmfw without bin + */ +static const char * const wm_adsp_fw_find_test_dir_all_files[] = { + "cirrus/cs1234-dsp1-mbc-vss-wfbf1111-amp1.wmfw", + "cirrus/cs1234-dsp1-mbc-vss-wfbf1111-l1u2.wmfw", + "cirrus/cs1234-dsp1-mbc-vss-wfbf1111.wmfw", + "cirrus/cs1234-dsp1-mbc-vss-wsbf1111.wmfw", + "cirrus/cs1234-dsp1-mbc-vss-wsbs1111.wmfw", + "cirrus/cs1234-dsp1-mbc-vss-wfxx1111.wmfw", + "cirrus/cs1234-dsp1-mbc-vss.wmfw", + "cirrus/cs1234nobin-dsp1-mbc-vss.wmfw", + "cirrus/wm1234-dsp1-mbc-vss.wmfw", + "cirrus/wm1234nobin-dsp1-mbc-vss.wmfw", + "wm1234-dsp1-mbc-vss.wmfw", + "wm1234nobin-dsp1-mbc-vss.wmfw", + "cirrus/cs1234-dsp1-mbc-vss-wfbf1111-amp1.bin", + "cirrus/cs1234-dsp1-mbc-vss-wfbf1111-l1u2.bin", + "cirrus/cs1234-dsp1-mbc-vss-wsbf1111-amp1.bin", + "cirrus/cs1234-dsp1-mbc-vss-wsbf1111-l1u2.bin", + "cirrus/cs1234-dsp1-mbc-vss-wsbs1111.bin", + "cirrus/cs1234-dsp1-mbc-vss-xxbf1111-amp1.bin", + "cirrus/cs1234-dsp1-mbc-vss.bin", + "cirrus/wm1234-dsp1-mbc-vss.bin", + "wm1234-dsp1-mbc-vss.bin", +}; + +/* + * Regression testing that a change in the search algorithm doesn't change + * which file is picked. This doesn't cover every possible combination, only + * those that are already in use and typical cases. + * + * It wouldn't be efficent to fully prove the algorithm this way (too many + * directory content combinations would be needed, and it only infers what the + * algorithm searched for, it doesn't prove exactly what searches were made). + * So the main testing is done by checking for the expected file searches. + * This regression test is independent of the search algorithm. + * + * The main tests already prove that the algorithm only searches for files + * with the correct qualifiers so we can assume that files with the wrong + * qualifiers would not be picked and there's no need to test for that here. + */ +static const struct wm_adsp_fw_find_test_params wm_adsp_fw_find_pick_cases[] = { + /* + * Amps + */ + { /* Full info, wmfw and bin fully-qualified */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "WFBF1111", + .alsa_name = "amp1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-wfbf1111-amp1.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-wfbf1111-amp1.bin", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, + { /* Full info, wmfw and bin fully-qualified */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "WFBF1111", + .alsa_name = "l1u2", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-wfbf1111-l1u2.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-wfbf1111-l1u2.bin", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, + { /* Full info, wmfw only system-qualified, bin fully-qualified */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "WSBF1111", + .alsa_name = "amp1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-wsbf1111.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-wsbf1111-amp1.bin", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, + { /* Full info, wmfw only system-qualified, bin fully-qualified */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "WSBF1111", + .alsa_name = "l1u2", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-wsbf1111.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-wsbf1111-l1u2.bin", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, + { /* Full info, wmfw optional but present, and bin fully-qualified */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "WFBF1111", + .alsa_name = "amp1", .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-wfbf1111-amp1.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-wfbf1111-amp1.bin", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, + { /* Full info, wmfw and bin only system-qualified */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "WSBS1111", + .alsa_name = "amp1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-wsbs1111.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-wsbs1111.bin", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, + { /* Full info, wmfw optional but system-qualified wmfm present, bin fully-qualified */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "WSBF1111", + .alsa_name = "amp1", .wmfw_optional = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-wsbf1111.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-wsbf1111-amp1.bin", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, + { /* Full info, wmfw optional not present, and bin fully-qualified */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "XXBF1111", + .alsa_name = "amp1", .wmfw_optional = true, + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-xxbf1111-amp1.bin", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, + { /* Full info, wmfw and bin fully-qualified, bin mandatory and present */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "WFBF1111", + .alsa_name = "amp1", .bin_mandatory = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-wfbf1111-amp1.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss-wfbf1111-amp1.bin", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, + { /* Full info, wmfw and bin fully-qualified, bin mandatory but not present */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "WFXX1111", + .alsa_name = "amp1", .bin_mandatory = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-wfxx1111.wmfw", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, + { /* Full info, wmfw optional but present, bin mandatory but not present */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "WFXX1111", + .alsa_name = "amp1", .wmfw_optional = true, .bin_mandatory = true, + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-wfxx1111.wmfw", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, + { /* Full info, wmfw and bin not present, generic fallbacks are present */ + .part = "cs1234", .dsp_name = "dsp1", .system_name = "XXXX1111", + .alsa_name = "amp1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss.bin", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, + { /* Full info, wmfw and bin not present, generic wmfw present */ + .part = "cs1234nobin", .dsp_name = "dsp1", .system_name = "XXXX1111", + .alsa_name = "amp1", + .expect_wmfw = "cirrus/cs1234nobin-dsp1-mbc-vss.wmfw", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, + + /* + * Codecs + */ + { /* No qualifiers. Generic wmfws exist, legacy should be chosen. */ + .part = "wm1234nobin", .dsp_name = "dsp1", + .expect_wmfw = "wm1234nobin-dsp1-mbc-vss.wmfw", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, + { /* No qualifiers. Generic wmfw and bin exist, legacy should be chosen */ + .part = "wm1234", .dsp_name = "dsp1", + .expect_wmfw = "wm1234-dsp1-mbc-vss.wmfw", + .expect_bin = "wm1234-dsp1-mbc-vss.bin", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, + { /* No qualifiers. New generic wmfw exists, no legacy files. */ + .part = "cs1234nobin", .dsp_name = "dsp1", + .expect_wmfw = "cirrus/cs1234nobin-dsp1-mbc-vss.wmfw", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, + { /* No qualifiers. New generic wmfw and bin exist, no legacy files. */ + .part = "cs1234", .dsp_name = "dsp1", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss.wmfw", + .expect_bin = "cirrus/cs1234-dsp1-mbc-vss.bin", + .dir_files = wm_adsp_fw_find_test_dir_all_files, + }, +}; +KUNIT_ARRAY_PARAM(wm_adsp_fw_find_pick, + wm_adsp_fw_find_pick_cases, + wm_adsp_fw_find_test_param_desc); + +static struct kunit_case wm_adsp_fw_find_test_cases[] = { + KUNIT_CASE_PARAM(wm_adsp_fw_find_test_search_order, + wm_adsp_fw_find_full_search_gen_params), + + KUNIT_CASE_PARAM(wm_adsp_fw_find_test_search_order, + wm_adsp_fw_find_system_alsaname_gen_params), + + KUNIT_CASE_PARAM(wm_adsp_fw_find_test_search_order, + wm_adsp_fw_find_system_gen_params), + + KUNIT_CASE_PARAM(wm_adsp_fw_find_test_search_order, + wm_adsp_fw_find_alsaname_gen_params), + + KUNIT_CASE_PARAM(wm_adsp_fw_find_test_search_order, + wm_adsp_fw_find_noqual_gen_params), + + KUNIT_CASE_PARAM(wm_adsp_fw_find_test_search_order, + wm_adsp_fw_find_normalization_gen_params), + + KUNIT_CASE_PARAM(wm_adsp_fw_find_test_pick_file, + wm_adsp_fw_find_pick_gen_params), + + KUNIT_CASE(wm_adsp_fw_find_test_find_firmware_byindex), + + { } /* terminator */ +}; + +static struct kunit_suite wm_adsp_fw_find_test_suite = { + .name = "wm-adsp-fw-find", + .init = wm_adsp_fw_find_test_case_init, + .exit = wm_adsp_fw_find_test_case_exit, + .test_cases = wm_adsp_fw_find_test_cases, +}; + +kunit_test_suite(wm_adsp_fw_find_test_suite); + +MODULE_DESCRIPTION("KUnit test for Cirrus Logic wm_adsp driver"); +MODULE_AUTHOR("Richard Fitzgerald "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); From 2c7c27025374abbdeda201ad103ddf27e8079aec Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 10 Mar 2026 14:18:12 +0000 Subject: [PATCH 081/359] ASoC: wm_adsp: Remove duplicated code to find firmware file The 3rd search case in wm_adsp_request_firmware_files() does exactly the same bin file searches as the case immediately above it. Merge the conditional from the 3rd case into the second case so the duplicated code can be removed. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260310141817.1871794-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm_adsp.c | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index 346ede149a9e..a3f297510526 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -828,9 +828,10 @@ VISIBLE_IF_KUNIT int wm_adsp_request_firmware_files(struct wm_adsp *dsp, } if (system_name) { - if (!wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename, - cirrus_dir, system_name, - NULL, "wmfw")) { + ret = wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename, + cirrus_dir, system_name, + NULL, "wmfw"); + if (!ret || dsp->wmfw_optional) { if (suffix) wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename, cirrus_dir, system_name, @@ -840,26 +841,12 @@ VISIBLE_IF_KUNIT int wm_adsp_request_firmware_files(struct wm_adsp *dsp, wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename, cirrus_dir, system_name, NULL, "bin"); - return 0; + + if (*wmfw_firmware || (dsp->wmfw_optional && *coeff_firmware)) + return 0; } } - /* Check system-specific bin without wmfw before falling back to generic */ - if (dsp->wmfw_optional && system_name) { - if (suffix) - wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename, - cirrus_dir, system_name, - suffix, "bin"); - - if (!*coeff_firmware) - wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename, - cirrus_dir, system_name, - NULL, "bin"); - - if (*coeff_firmware) - return 0; - } - /* Check legacy location */ if (!wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename, "", NULL, NULL, "wmfw")) { From f8f0c68c75214e326c0d4cbcab8ecab882201f48 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 10 Mar 2026 14:18:13 +0000 Subject: [PATCH 082/359] ASoC: wm_adsp: Use consistent error checks in wm_adsp_request_firmware_files() Use a consistent pattern of error checking in wm_adsp_request_firmware_files(). - The integer return value of wm_adsp_request_firmware_file() reports unrecoverable errors, for example -ENOMEM. - A NULL struct firmware pointer is a valid result. This not an error, not all DSPs require both files, some may not require any files. Previously wm_adsp_request_firmware_files() was using a mix of checking the return value and checking the struct firmware pointer to determine whether a file was found. It wasn't checking for unrecoverable errors. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260310141817.1871794-7-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm_adsp.c | 92 +++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 27 deletions(-) diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index a3f297510526..9f39db680e07 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -753,7 +753,7 @@ static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, struct cs_dsp *cs_dsp = &dsp->cs_dsp; const char *fwf; char *s, c; - int ret = 0; + int ret; if (dsp->fwf_name) fwf = dsp->fwf_name; @@ -791,15 +791,17 @@ static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, } ret = wm_adsp_firmware_request(firmware, *filename, cs_dsp->dev); - if (ret != 0) { - adsp_dbg(dsp, "Failed to request '%s'\n", *filename); + if (ret < 0) { + adsp_dbg(dsp, "Failed to request '%s': %d\n", *filename, ret); kfree(*filename); *filename = NULL; + if (ret != -ENOENT) + return ret; } else { adsp_dbg(dsp, "Found '%s'\n", *filename); } - return ret; + return 0; } static const char * const cirrus_dir = "cirrus/"; @@ -817,12 +819,19 @@ VISIBLE_IF_KUNIT int wm_adsp_request_firmware_files(struct wm_adsp *dsp, suffix = dsp->fwf_suffix; if (system_name && suffix) { - if (!wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename, - cirrus_dir, system_name, - suffix, "wmfw")) { - wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename, - cirrus_dir, system_name, - suffix, "bin"); + ret = wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename, + cirrus_dir, system_name, + suffix, "wmfw"); + if (ret < 0) + goto err; + + if (*wmfw_firmware) { + ret = wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename, + cirrus_dir, system_name, + suffix, "bin"); + if (ret < 0) + goto err; + return 0; } } @@ -831,16 +840,27 @@ VISIBLE_IF_KUNIT int wm_adsp_request_firmware_files(struct wm_adsp *dsp, ret = wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename, cirrus_dir, system_name, NULL, "wmfw"); - if (!ret || dsp->wmfw_optional) { - if (suffix) - wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename, - cirrus_dir, system_name, - suffix, "bin"); + if (ret < 0) + goto err; - if (!*coeff_firmware) - wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename, - cirrus_dir, system_name, - NULL, "bin"); + if (*wmfw_firmware || dsp->wmfw_optional) { + if (suffix) { + ret = wm_adsp_request_firmware_file(dsp, + coeff_firmware, coeff_filename, + cirrus_dir, system_name, + suffix, "bin"); + if (ret < 0) + goto err; + } + + if (!*coeff_firmware) { + ret = wm_adsp_request_firmware_file(dsp, + coeff_firmware, coeff_filename, + cirrus_dir, system_name, + NULL, "bin"); + if (ret < 0) + goto err; + } if (*wmfw_firmware || (dsp->wmfw_optional && *coeff_firmware)) return 0; @@ -848,19 +868,32 @@ VISIBLE_IF_KUNIT int wm_adsp_request_firmware_files(struct wm_adsp *dsp, } /* Check legacy location */ - if (!wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename, - "", NULL, NULL, "wmfw")) { - wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename, - "", NULL, NULL, "bin"); + ret = wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename, + "", NULL, NULL, "wmfw"); + if (ret < 0) + goto err; + + if (*wmfw_firmware) { + ret = wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename, + "", NULL, NULL, "bin"); + if (ret < 0) + goto err; + return 0; } /* Fall back to generic wmfw and optional matching bin */ ret = wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename, cirrus_dir, NULL, NULL, "wmfw"); - if (!ret || dsp->wmfw_optional) { - wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename, - cirrus_dir, NULL, NULL, "bin"); + if (ret < 0) + goto err; + + if (*wmfw_firmware || dsp->wmfw_optional) { + ret = wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename, + cirrus_dir, NULL, NULL, "bin"); + if (ret < 0) + goto err; + return 0; } @@ -869,7 +902,12 @@ VISIBLE_IF_KUNIT int wm_adsp_request_firmware_files(struct wm_adsp *dsp, dsp->fwf_name ? dsp->fwf_name : dsp->cs_dsp.name, wm_adsp_fw[dsp->fw].file, system_name, suffix); - return -ENOENT; + ret = -ENOENT; +err: + wm_adsp_release_firmware_files(*wmfw_firmware, *wmfw_filename, + *coeff_firmware, *coeff_filename); + + return ret; } EXPORT_SYMBOL_IF_KUNIT(wm_adsp_request_firmware_files); From 66170cc7ed59fb7e1e192e53f1d690bd04e8c720 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 10 Mar 2026 14:18:14 +0000 Subject: [PATCH 083/359] ASoC: wm_adsp: Convert '/' to '-' when normalizing firmware filenames Don't preserve '/' in firmware filename fields - convert it to '-' like other punctuation characters. The code originally normalized the entire string, including the directory prefix. To prevent breaking the directory it had to preserve '/' characters in the name, but this meant that the system name and ALSA prefix must not contain those characters. It's trivial to skip the directory name prefix and start the normalization after it, and that means the normalization does not need to make a special case for '/'. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260310141817.1871794-8-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm_adsp.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index 9f39db680e07..74c503fc3088 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -776,16 +776,15 @@ static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, return -ENOMEM; /* - * Make sure that filename is lower-case and any non alpha-numeric - * characters except full stop and forward slash are replaced with - * hyphens. + * Make sure that filename after dir is lower-case and any non-alpha-numeric + * characters except full-stop are replaced with hyphens. */ - s = *filename; + s = *filename + strlen(dir); while (*s) { c = *s; if (isalnum(c)) *s = tolower(c); - else if ((c != '.') && (c != '/')) + else if (c != '.') *s = '-'; s++; } From d8a4c96082e6f5c7aaf6f3e101effe7ff0ea4d6e Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 10 Mar 2026 14:18:15 +0000 Subject: [PATCH 084/359] ASoC: wm_adsp: Add KUnit test cases for '/' in firmware filenames Add test cases that '/' in the system name or ALSA prefix are converted to '-' in the firmware filename. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260310141817.1871794-9-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm_adsp_fw_find_test.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sound/soc/codecs/wm_adsp_fw_find_test.c b/sound/soc/codecs/wm_adsp_fw_find_test.c index 556221d38a50..11047851fd80 100644 --- a/sound/soc/codecs/wm_adsp_fw_find_test.c +++ b/sound/soc/codecs/wm_adsp_fw_find_test.c @@ -949,6 +949,13 @@ static const struct wm_adsp_fw_find_test_params wm_adsp_fw_find_normalization_ca "cirrus/cs1234-dsp1-mbc-vss-vendor-device.wmfw " "cirrus/cs1234-dsp1-mbc-vss-vendor-device.bin", }, + { + .part = "cs1234", .dsp_name = "dsp1", .system_name = "Vendor/Device", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-vendor-device.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-vendor-device.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-vendor-device.bin", + }, { .part = "cs1234", .dsp_name = "dsp1", .system_name = "1234:56AB", .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-1234-56ab.wmfw", @@ -997,6 +1004,14 @@ static const struct wm_adsp_fw_find_test_params wm_adsp_fw_find_normalization_ca "cirrus/cs1234-dsp1-mbc-vss-abc-cs1234.1.wmfw " "cirrus/cs1234-dsp1-mbc-vss-abc-cs1234.1.bin", }, + { + .part = "cs1234", .dsp_name = "dsp1", .system_name = "abc", + .alsa_name = "Spk/Jack", + .expect_wmfw = "cirrus/cs1234-dsp1-mbc-vss-abc-spk-jack.wmfw", + .expected_searches = + "cirrus/cs1234-dsp1-mbc-vss-abc-spk-jack.wmfw " + "cirrus/cs1234-dsp1-mbc-vss-abc-spk-jack.bin", + }, }; KUNIT_ARRAY_PARAM(wm_adsp_fw_find_normalization, wm_adsp_fw_find_normalization_cases, From 7bca3ca55ef53ca66fdf6e663290d0596a8f520d Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 10 Mar 2026 14:18:16 +0000 Subject: [PATCH 085/359] ASoC: wm_adsp: Use a struct to pass around firmware struct and filename Bundle the pointers to struct firmware and its filename into a new struct wm_adsp_fw_files. This simplifies passing these pointers around. Changes are also needed to the test cases in wm_adsp_fw_find_test.c that use this API. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260310141817.1871794-10-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm_adsp.c | 123 ++++++++++-------------- sound/soc/codecs/wm_adsp.h | 21 ++-- sound/soc/codecs/wm_adsp_fw_find_test.c | 47 +++------ 3 files changed, 77 insertions(+), 114 deletions(-) diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index 74c503fc3088..bcc77797c09a 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -717,20 +717,15 @@ int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name, int type, } EXPORT_SYMBOL_GPL(wm_adsp_read_ctl); -VISIBLE_IF_KUNIT void wm_adsp_release_firmware_files(const struct firmware *wmfw_firmware, - char *wmfw_filename, - const struct firmware *coeff_firmware, - char *coeff_filename) +VISIBLE_IF_KUNIT void wm_adsp_release_firmware_files(struct wm_adsp_fw_files *fw) { - KUNIT_STATIC_STUB_REDIRECT(wm_adsp_release_firmware_files, - wmfw_firmware, wmfw_filename, - coeff_firmware, coeff_filename); + KUNIT_STATIC_STUB_REDIRECT(wm_adsp_release_firmware_files, fw); - release_firmware(wmfw_firmware); - kfree(wmfw_filename); + release_firmware(fw->wmfw.firmware); + kfree(fw->wmfw.filename); - release_firmware(coeff_firmware); - kfree(coeff_filename); + release_firmware(fw->coeff.firmware); + kfree(fw->coeff.filename); } EXPORT_SYMBOL_IF_KUNIT(wm_adsp_release_firmware_files); @@ -745,7 +740,7 @@ VISIBLE_IF_KUNIT int wm_adsp_firmware_request(const struct firmware **firmware, EXPORT_SYMBOL_IF_KUNIT(wm_adsp_firmware_request); static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, - const struct firmware **firmware, char **filename, + struct wm_adsp_fw_file *fw, const char *dir, const char *system_name, const char *asoc_component_prefix, const char *filetype) @@ -761,25 +756,25 @@ static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, fwf = dsp->cs_dsp.name; if (system_name && asoc_component_prefix) - *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s-%s.%s", dir, dsp->part, - fwf, wm_adsp_fw[dsp->fw].file, system_name, - asoc_component_prefix, filetype); + fw->filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s-%s.%s", dir, dsp->part, + fwf, wm_adsp_fw[dsp->fw].file, system_name, + asoc_component_prefix, filetype); else if (system_name) - *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s.%s", dir, dsp->part, - fwf, wm_adsp_fw[dsp->fw].file, system_name, - filetype); + fw->filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s.%s", dir, dsp->part, + fwf, wm_adsp_fw[dsp->fw].file, system_name, + filetype); else - *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s.%s", dir, dsp->part, fwf, - wm_adsp_fw[dsp->fw].file, filetype); + fw->filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s.%s", dir, dsp->part, fwf, + wm_adsp_fw[dsp->fw].file, filetype); - if (*filename == NULL) + if (!fw->filename) return -ENOMEM; /* * Make sure that filename after dir is lower-case and any non-alpha-numeric * characters except full-stop are replaced with hyphens. */ - s = *filename + strlen(dir); + s = fw->filename + strlen(dir); while (*s) { c = *s; if (isalnum(c)) @@ -789,15 +784,15 @@ static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, s++; } - ret = wm_adsp_firmware_request(firmware, *filename, cs_dsp->dev); + ret = wm_adsp_firmware_request(&fw->firmware, fw->filename, cs_dsp->dev); if (ret < 0) { - adsp_dbg(dsp, "Failed to request '%s': %d\n", *filename, ret); - kfree(*filename); - *filename = NULL; + adsp_dbg(dsp, "Failed to request '%s': %d\n", fw->filename, ret); + kfree(fw->filename); + fw->filename = NULL; if (ret != -ENOENT) return ret; } else { - adsp_dbg(dsp, "Found '%s'\n", *filename); + adsp_dbg(dsp, "Found '%s'\n", fw->filename); } return 0; @@ -805,10 +800,7 @@ static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, static const char * const cirrus_dir = "cirrus/"; VISIBLE_IF_KUNIT int wm_adsp_request_firmware_files(struct wm_adsp *dsp, - const struct firmware **wmfw_firmware, - char **wmfw_filename, - const struct firmware **coeff_firmware, - char **coeff_filename) + struct wm_adsp_fw_files *fw) { const char *system_name = dsp->system_name; const char *suffix = dsp->component->name_prefix; @@ -818,14 +810,14 @@ VISIBLE_IF_KUNIT int wm_adsp_request_firmware_files(struct wm_adsp *dsp, suffix = dsp->fwf_suffix; if (system_name && suffix) { - ret = wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename, + ret = wm_adsp_request_firmware_file(dsp, &fw->wmfw, cirrus_dir, system_name, suffix, "wmfw"); if (ret < 0) goto err; - if (*wmfw_firmware) { - ret = wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename, + if (fw->wmfw.firmware) { + ret = wm_adsp_request_firmware_file(dsp, &fw->coeff, cirrus_dir, system_name, suffix, "bin"); if (ret < 0) @@ -836,45 +828,43 @@ VISIBLE_IF_KUNIT int wm_adsp_request_firmware_files(struct wm_adsp *dsp, } if (system_name) { - ret = wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename, + ret = wm_adsp_request_firmware_file(dsp, &fw->wmfw, cirrus_dir, system_name, NULL, "wmfw"); if (ret < 0) goto err; - if (*wmfw_firmware || dsp->wmfw_optional) { + if (fw->wmfw.firmware || dsp->wmfw_optional) { if (suffix) { ret = wm_adsp_request_firmware_file(dsp, - coeff_firmware, coeff_filename, + &fw->coeff, cirrus_dir, system_name, suffix, "bin"); if (ret < 0) goto err; } - if (!*coeff_firmware) { + if (!fw->coeff.firmware) { ret = wm_adsp_request_firmware_file(dsp, - coeff_firmware, coeff_filename, + &fw->coeff, cirrus_dir, system_name, NULL, "bin"); if (ret < 0) goto err; } - if (*wmfw_firmware || (dsp->wmfw_optional && *coeff_firmware)) + if (fw->wmfw.firmware || (dsp->wmfw_optional && fw->coeff.firmware)) return 0; } } /* Check legacy location */ - ret = wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename, - "", NULL, NULL, "wmfw"); + ret = wm_adsp_request_firmware_file(dsp, &fw->wmfw, "", NULL, NULL, "wmfw"); if (ret < 0) goto err; - if (*wmfw_firmware) { - ret = wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename, - "", NULL, NULL, "bin"); + if (fw->wmfw.firmware) { + ret = wm_adsp_request_firmware_file(dsp, &fw->coeff, "", NULL, NULL, "bin"); if (ret < 0) goto err; @@ -882,13 +872,13 @@ VISIBLE_IF_KUNIT int wm_adsp_request_firmware_files(struct wm_adsp *dsp, } /* Fall back to generic wmfw and optional matching bin */ - ret = wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename, + ret = wm_adsp_request_firmware_file(dsp, &fw->wmfw, cirrus_dir, NULL, NULL, "wmfw"); if (ret < 0) goto err; - if (*wmfw_firmware || dsp->wmfw_optional) { - ret = wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename, + if (fw->wmfw.firmware || dsp->wmfw_optional) { + ret = wm_adsp_request_firmware_file(dsp, &fw->coeff, cirrus_dir, NULL, NULL, "bin"); if (ret < 0) goto err; @@ -903,8 +893,7 @@ VISIBLE_IF_KUNIT int wm_adsp_request_firmware_files(struct wm_adsp *dsp, ret = -ENOENT; err: - wm_adsp_release_firmware_files(*wmfw_firmware, *wmfw_filename, - *coeff_firmware, *coeff_filename); + wm_adsp_release_firmware_files(fw); return ret; } @@ -939,29 +928,23 @@ int wm_adsp1_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); struct wm_adsp *dsp = &dsps[w->shift]; + struct wm_adsp_fw_files fw = { 0 }; int ret = 0; - char *wmfw_filename = NULL; - const struct firmware *wmfw_firmware = NULL; - char *coeff_filename = NULL; - const struct firmware *coeff_firmware = NULL; dsp->component = component; switch (event) { case SND_SOC_DAPM_POST_PMU: - ret = wm_adsp_request_firmware_files(dsp, - &wmfw_firmware, &wmfw_filename, - &coeff_firmware, &coeff_filename); + ret = wm_adsp_request_firmware_files(dsp, &fw); if (ret) break; ret = cs_dsp_adsp1_power_up(&dsp->cs_dsp, - wmfw_firmware, wmfw_filename, - coeff_firmware, coeff_filename, + fw.wmfw.firmware, fw.wmfw.filename, + fw.coeff.firmware, fw.coeff.filename, wm_adsp_fw_text[dsp->fw]); - wm_adsp_release_firmware_files(wmfw_firmware, wmfw_filename, - coeff_firmware, coeff_filename); + wm_adsp_release_firmware_files(&fw); break; case SND_SOC_DAPM_PRE_PMD: cs_dsp_adsp1_power_down(&dsp->cs_dsp); @@ -1037,33 +1020,27 @@ EXPORT_SYMBOL_GPL(wm_adsp2_preloader_put); int wm_adsp_power_up(struct wm_adsp *dsp, bool load_firmware) { + struct wm_adsp_fw_files fw = { 0 }; int ret = 0; - char *wmfw_filename = NULL; - const struct firmware *wmfw_firmware = NULL; - char *coeff_filename = NULL; - const struct firmware *coeff_firmware = NULL; if (load_firmware) { - ret = wm_adsp_request_firmware_files(dsp, - &wmfw_firmware, &wmfw_filename, - &coeff_firmware, &coeff_filename); + ret = wm_adsp_request_firmware_files(dsp, &fw); if (ret) return ret; } - if (dsp->bin_mandatory && !coeff_firmware) { + if (dsp->bin_mandatory && !fw.coeff.firmware) { ret = -ENOENT; goto err; } ret = cs_dsp_power_up(&dsp->cs_dsp, - wmfw_firmware, wmfw_filename, - coeff_firmware, coeff_filename, + fw.wmfw.firmware, fw.wmfw.filename, + fw.coeff.firmware, fw.coeff.filename, wm_adsp_fw_text[dsp->fw]); err: - wm_adsp_release_firmware_files(wmfw_firmware, wmfw_filename, - coeff_firmware, coeff_filename); + wm_adsp_release_firmware_files(&fw); return ret; } diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h index 6560dfc8c08d..e314c2299196 100644 --- a/sound/soc/codecs/wm_adsp.h +++ b/sound/soc/codecs/wm_adsp.h @@ -79,6 +79,16 @@ struct wm_adsp { SOC_ENUM_EXT(dspname " Firmware", wm_adsp_fw_enum[num], \ wm_adsp_fw_get, wm_adsp_fw_put) +struct wm_adsp_fw_file { + const struct firmware *firmware; + char *filename; +}; + +struct wm_adsp_fw_files { + struct wm_adsp_fw_file wmfw; + struct wm_adsp_fw_file coeff; +}; + extern const struct soc_enum wm_adsp_fw_enum[]; int wm_adsp1_init(struct wm_adsp *dsp); @@ -144,18 +154,11 @@ int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name, int type, #if IS_ENABLED(CONFIG_KUNIT) const char *wm_adsp_get_fwf_name_by_index(int index); -void wm_adsp_release_firmware_files(const struct firmware *wmfw_firmware, - char *wmfw_filename, - const struct firmware *coeff_firmware, - char *coeff_filename); +void wm_adsp_release_firmware_files(struct wm_adsp_fw_files *fw); int wm_adsp_firmware_request(const struct firmware **firmware, const char *filename, struct device *dev); -int wm_adsp_request_firmware_files(struct wm_adsp *dsp, - const struct firmware **wmfw_firmware, - char **wmfw_filename, - const struct firmware **coeff_firmware, - char **coeff_filename); +int wm_adsp_request_firmware_files(struct wm_adsp *dsp, struct wm_adsp_fw_files *fw); #endif #endif diff --git a/sound/soc/codecs/wm_adsp_fw_find_test.c b/sound/soc/codecs/wm_adsp_fw_find_test.c index 11047851fd80..44c26e991b35 100644 --- a/sound/soc/codecs/wm_adsp_fw_find_test.c +++ b/sound/soc/codecs/wm_adsp_fw_find_test.c @@ -16,10 +16,7 @@ KUNIT_DEFINE_ACTION_WRAPPER(_put_device_wrapper, put_device, struct device *); struct wm_adsp_fw_find_test { struct wm_adsp dsp; - const struct firmware *found_wmfw_firmware; - const struct firmware *found_bin_firmware; - char *found_wmfw_filename; - char *found_bin_filename; + struct wm_adsp_fw_files found_fw; char searched_fw_files[768]; }; @@ -101,28 +98,24 @@ static void wm_adsp_fw_find_test_pick_file(struct kunit *test) wm_adsp_firmware_request, wm_adsp_fw_find_test_firmware_request_simple_stub); - ret = wm_adsp_request_firmware_files(dsp, - &priv->found_wmfw_firmware, - &priv->found_wmfw_filename, - &priv->found_bin_firmware, - &priv->found_bin_filename); + ret = wm_adsp_request_firmware_files(dsp, &priv->found_fw); kunit_deactivate_static_stub(test, wm_adsp_firmware_request); KUNIT_EXPECT_EQ_MSG(test, ret, (params->expect_wmfw || params->expect_bin) ? 0 : -ENOENT, "%s\n", priv->searched_fw_files); - KUNIT_EXPECT_EQ_MSG(test, !!priv->found_wmfw_filename, !!params->expect_wmfw, + KUNIT_EXPECT_EQ_MSG(test, !!priv->found_fw.wmfw.filename, !!params->expect_wmfw, "%s\n", priv->searched_fw_files); - KUNIT_EXPECT_EQ_MSG(test, !!priv->found_bin_filename, !!params->expect_bin, + KUNIT_EXPECT_EQ_MSG(test, !!priv->found_fw.coeff.filename, !!params->expect_bin, "%s\n", priv->searched_fw_files); if (params->expect_wmfw) { - KUNIT_EXPECT_STREQ_MSG(test, priv->found_wmfw_filename, params->expect_wmfw, + KUNIT_EXPECT_STREQ_MSG(test, priv->found_fw.wmfw.filename, params->expect_wmfw, "%s\n", priv->searched_fw_files); } if (params->expect_bin) { - KUNIT_EXPECT_STREQ_MSG(test, priv->found_bin_filename, params->expect_bin, + KUNIT_EXPECT_STREQ_MSG(test, priv->found_fw.coeff.filename, params->expect_bin, "%s\n", priv->searched_fw_files); } } @@ -181,27 +174,23 @@ static void wm_adsp_fw_find_test_search_order(struct kunit *test) wm_adsp_firmware_request, wm_adsp_fw_find_test_firmware_request_stub); - wm_adsp_request_firmware_files(dsp, - &priv->found_wmfw_firmware, - &priv->found_wmfw_filename, - &priv->found_bin_firmware, - &priv->found_bin_filename); + wm_adsp_request_firmware_files(dsp, &priv->found_fw); kunit_deactivate_static_stub(test, wm_adsp_firmware_request); KUNIT_EXPECT_STREQ(test, priv->searched_fw_files, params->expected_searches); - KUNIT_EXPECT_EQ(test, !!priv->found_wmfw_filename, !!params->expect_wmfw); + KUNIT_EXPECT_EQ(test, !!priv->found_fw.wmfw.filename, !!params->expect_wmfw); if (params->expect_wmfw) - KUNIT_EXPECT_STREQ(test, priv->found_wmfw_filename, params->expect_wmfw); + KUNIT_EXPECT_STREQ(test, priv->found_fw.wmfw.filename, params->expect_wmfw); - KUNIT_EXPECT_EQ(test, !!priv->found_bin_filename, !!params->expect_bin); + KUNIT_EXPECT_EQ(test, !!priv->found_fw.coeff.filename, !!params->expect_bin); if (params->expect_bin) - KUNIT_EXPECT_STREQ(test, priv->found_bin_filename, params->expect_bin); + KUNIT_EXPECT_STREQ(test, priv->found_fw.coeff.filename, params->expect_bin); /* Either we get a filename and firmware, or neither */ - KUNIT_EXPECT_EQ(test, !!priv->found_wmfw_filename, !!priv->found_wmfw_firmware); - KUNIT_EXPECT_EQ(test, !!priv->found_bin_filename, !!priv->found_bin_firmware); + KUNIT_EXPECT_EQ(test, !!priv->found_fw.wmfw.filename, !!priv->found_fw.wmfw.firmware); + KUNIT_EXPECT_EQ(test, !!priv->found_fw.coeff.filename, !!priv->found_fw.coeff.firmware); } static void wm_adsp_fw_find_test_find_firmware_byindex(struct kunit *test) @@ -221,12 +210,7 @@ static void wm_adsp_fw_find_test_find_firmware_byindex(struct kunit *test) wm_adsp_firmware_request, wm_adsp_fw_find_test_firmware_request_stub); - wm_adsp_request_firmware_files(dsp, - &priv->found_wmfw_firmware, - &priv->found_wmfw_filename, - &priv->found_bin_firmware, - &priv->found_bin_filename); - + wm_adsp_request_firmware_files(dsp, &priv->found_fw); kunit_deactivate_static_stub(test, wm_adsp_firmware_request); KUNIT_EXPECT_NOT_NULL_MSG(test, @@ -278,8 +262,7 @@ static void wm_adsp_fw_find_test_case_exit(struct kunit *test) * dummies not allocated by the real request_firmware() call they * must not be passed to release_firmware(). */ - wm_adsp_release_firmware_files(NULL, priv->found_wmfw_filename, - NULL, priv->found_bin_filename); + wm_adsp_release_firmware_files(&priv->found_fw); } static void wm_adsp_fw_find_test_param_desc(const struct wm_adsp_fw_find_test_params *param, From 8fc5c7895185d1119ae76b509892a1d14e0bd483 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 10 Mar 2026 14:18:17 +0000 Subject: [PATCH 086/359] ASoC: wm_adsp: Combine some similar code in firmware file search In wm_adsp_request_firmware_files() squash the if (system_name && suffix) and the following if (system_name) blocks together. This removes some duplicated code. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260310141817.1871794-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm_adsp.c | 49 +++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index bcc77797c09a..a5fa0db250f2 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -804,47 +804,42 @@ VISIBLE_IF_KUNIT int wm_adsp_request_firmware_files(struct wm_adsp *dsp, { const char *system_name = dsp->system_name; const char *suffix = dsp->component->name_prefix; + bool require_bin_suffix = false; int ret = 0; if (dsp->fwf_suffix) suffix = dsp->fwf_suffix; - if (system_name && suffix) { + if (system_name) { ret = wm_adsp_request_firmware_file(dsp, &fw->wmfw, cirrus_dir, system_name, suffix, "wmfw"); if (ret < 0) goto err; - if (fw->wmfw.firmware) { + if (suffix) { + if (fw->wmfw.firmware) { + require_bin_suffix = true; + } else { + /* Fallback to name without suffix */ + ret = wm_adsp_request_firmware_file(dsp, &fw->wmfw, + cirrus_dir, system_name, + NULL, "wmfw"); + if (ret < 0) + goto err; + } + } + + /* Look for matching .bin file */ + if (fw->wmfw.firmware || dsp->wmfw_optional) { ret = wm_adsp_request_firmware_file(dsp, &fw->coeff, cirrus_dir, system_name, suffix, "bin"); if (ret < 0) goto err; - return 0; - } - } - - if (system_name) { - ret = wm_adsp_request_firmware_file(dsp, &fw->wmfw, - cirrus_dir, system_name, - NULL, "wmfw"); - if (ret < 0) - goto err; - - if (fw->wmfw.firmware || dsp->wmfw_optional) { - if (suffix) { - ret = wm_adsp_request_firmware_file(dsp, - &fw->coeff, - cirrus_dir, system_name, - suffix, "bin"); - if (ret < 0) - goto err; - } - - if (!fw->coeff.firmware) { + if (suffix && !fw->coeff.firmware && !require_bin_suffix) { + /* Fallback to name without suffix */ ret = wm_adsp_request_firmware_file(dsp, &fw->coeff, cirrus_dir, system_name, @@ -852,10 +847,10 @@ VISIBLE_IF_KUNIT int wm_adsp_request_firmware_files(struct wm_adsp *dsp, if (ret < 0) goto err; } - - if (fw->wmfw.firmware || (dsp->wmfw_optional && fw->coeff.firmware)) - return 0; } + + if (fw->wmfw.firmware || (dsp->wmfw_optional && fw->coeff.firmware)) + return 0; } /* Check legacy location */ From 207fd1f4d84b7f073eaab556623bf4ad3a161f3f Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Tue, 10 Mar 2026 12:51:06 +0000 Subject: [PATCH 087/359] ASoC: SOF: sof-audio: pcm_id is __le32 The pcm_id value is __le32 so convert it before passing to the dev_dbg function to be printed. Also fixup some other uses of __le32 data and a couple of places where %u should have been used instead of %d Picked up by sparse prototype for variadic and printf function checking. Fixes a large number of sparse warnings, such as: sound/soc/sof/pcm.c:84:25: warning: incorrect type in argument 4 (different base types) sound/soc/sof/pcm.c:84:25: expected unsigned int sound/soc/sof/pcm.c:84:25: got restricted __le32 [usertype] pcm_id Signed-off-by: Ben Dooks Acked-by: Peter Ujfalusi Link: https://patch.msgid.link/20260310125106.178944-1-ben.dooks@codethink.co.uk Signed-off-by: Mark Brown --- sound/soc/sof/compress.c | 2 +- sound/soc/sof/ipc4-topology.c | 13 +++++++------ sound/soc/sof/pcm.c | 4 ++-- sound/soc/sof/sof-audio.h | 11 +++++++---- sound/soc/sof/topology.c | 10 ++++++---- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/compress.c index 96570121aae0..c6c19df29035 100644 --- a/sound/soc/sof/compress.c +++ b/sound/soc/sof/compress.c @@ -248,7 +248,7 @@ static int sof_compr_set_params(struct snd_soc_component *component, ipc_params_reply.posn_offset); if (ret < 0) { dev_err(component->dev, "Invalid stream data offset for Compr %d\n", - spcm->pcm.pcm_id); + le32_to_cpu(spcm->pcm.pcm_id)); goto out; } diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index db077e9d5644..82f1e44a145e 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -581,6 +581,7 @@ sof_ipc4_update_card_components_string(struct snd_sof_widget *swidget, struct snd_soc_component *scomp = spcm->scomp; struct snd_soc_card *card = scomp->card; const char *pt_marker = "iec61937-pcm"; + unsigned pcm_id = le32_to_cpu(spcm->pcm.pcm_id); /* * Update the card's components list with iec61937-pcm and a list of PCM @@ -595,21 +596,21 @@ sof_ipc4_update_card_components_string(struct snd_sof_widget *swidget, if (strstr(card->components, pt_marker)) card->components = devm_kasprintf(card->dev, GFP_KERNEL, - "%s,%d", + "%s,%u", card->components, - spcm->pcm.pcm_id); + pcm_id); else card->components = devm_kasprintf(card->dev, GFP_KERNEL, - "%s %s:%d", + "%s %s:%u", card->components, pt_marker, - spcm->pcm.pcm_id); + pcm_id); devm_kfree(card->dev, tmp); } else { card->components = devm_kasprintf(card->dev, GFP_KERNEL, - "%s:%d", pt_marker, - spcm->pcm.pcm_id); + "%s:%u", pt_marker, + pcm_id); } if (!card->components) diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 5b598d0940eb..16c194b4851b 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -361,7 +361,7 @@ static int sof_pcm_prepare(struct snd_soc_component *component, ret = sof_widget_list_setup(sdev, spcm, params, platform_params, dir); if (ret < 0) { dev_err(sdev->dev, "failed widget list set up for pcm %d dir %d\n", - spcm->pcm.pcm_id, dir); + le32_to_cpu(spcm->pcm.pcm_id), dir); spcm->stream[dir].list = NULL; snd_soc_dapm_dai_free_widgets(&list); return ret; @@ -652,7 +652,7 @@ static int sof_pcm_new(struct snd_soc_component *component, } dev_dbg(spcm->scomp->dev, "pcm%u (%s): Entry: pcm_construct\n", - spcm->pcm.pcm_id, spcm->pcm.pcm_name); + le32_to_cpu(spcm->pcm.pcm_id), spcm->pcm.pcm_name); /* do we need to pre-allocate playback audio buffer pages */ if (!spcm->pcm.playback) diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 36082e764bf9..80b11625915d 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -641,17 +641,20 @@ void snd_sof_pcm_init_elapsed_work(struct work_struct *work); */ #define spcm_dbg(__spcm, __dir, __fmt, ...) \ dev_dbg((__spcm)->scomp->dev, "pcm%u (%s), dir %d: " __fmt, \ - (__spcm)->pcm.pcm_id, (__spcm)->pcm.pcm_name, __dir, \ + le32_to_cpu((__spcm)->pcm.pcm_id), \ + (__spcm)->pcm.pcm_name, __dir, \ ##__VA_ARGS__) #define spcm_dbg_ratelimited(__spcm, __dir, __fmt, ...) \ dev_dbg_ratelimited((__spcm)->scomp->dev, "pcm%u (%s), dir %d: " __fmt, \ - (__spcm)->pcm.pcm_id, (__spcm)->pcm.pcm_name, __dir, \ - ##__VA_ARGS__) + le32_to_cpu((__spcm)->pcm.pcm_id), \ + (__spcm)->pcm.pcm_name, __dir, \ + ##__VA_ARGS__) #define spcm_err(__spcm, __dir, __fmt, ...) \ dev_err((__spcm)->scomp->dev, "%s: pcm%u (%s), dir %d: " __fmt, \ - __func__, (__spcm)->pcm.pcm_id, (__spcm)->pcm.pcm_name, __dir, \ + __func__, le32_to_cpu((__spcm)->pcm.pcm_id), \ + (__spcm)->pcm.pcm_name, __dir, \ ##__VA_ARGS__) #if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 18e2401152c8..2740f24cd1d3 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -776,7 +776,7 @@ static int sof_parse_token_sets(struct snd_soc_component *scomp, break; default: dev_err(scomp->dev, "error: unknown token type %d\n", - array->type); + le32_to_cpu(array->type)); return -EINVAL; } @@ -971,7 +971,7 @@ static int sof_control_load(struct snd_soc_component *scomp, int index, int ret; dev_dbg(scomp->dev, "tplg: load control type %d name : %s\n", - hdr->type, hdr->name); + le32_to_cpu(hdr->type), hdr->name); scontrol = kzalloc_obj(*scontrol); if (!scontrol) @@ -1016,7 +1016,9 @@ static int sof_control_load(struct snd_soc_component *scomp, int index, case SND_SOC_TPLG_DAPM_CTL_PIN: default: dev_warn(scomp->dev, "control type not supported %d:%d:%d\n", - hdr->ops.get, hdr->ops.put, hdr->ops.info); + le32_to_cpu(hdr->ops.get), + le32_to_cpu(hdr->ops.put), + le32_to_cpu(hdr->ops.info)); kfree(scontrol->name); kfree(scontrol); return 0; @@ -1524,7 +1526,7 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, case snd_soc_dapm_pga: if (!le32_to_cpu(tw->num_kcontrols)) { dev_err(scomp->dev, "invalid kcontrol count %d for volume\n", - tw->num_kcontrols); + le32_to_cpu(tw->num_kcontrols)); ret = -EINVAL; break; } From 9be71d462c33b1a00acfa4ab8f0f5332ed592817 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 10 Mar 2026 13:03:43 +0000 Subject: [PATCH 088/359] firmware: cs_dsp: Simplify suppressing log messages during KUnit testing Rework the way that kernel log messages are rate-limited or suppressed while running the cs_dsp KUnit tests. Under normal conditions cs_dsp doesn't produce an unreasonable number of log messages, and state changes are relatively infrequent. But the KUnit tests run through a very large number of test cases, especially error cases, and this produces an unusually large amount of log output from cs_dsp. The original fix for this in commit 10db9f6899dd ("firmware: cs_dsp: rate-limit log messages in KUnit builds") was effective but not pretty. It involved different definitions of the log macros for KUnit and not-KUnit builds, and exported variables for the KUnit tests to disable log messages. I would have preferred to turn the log macros into real functions that can contain a KUNIT_STATIC_STUB_REDIRECT(), but the dev_xxx() macros don't have a version that take va_args, so they can't be wrapped by a function. This patch enables the use of a KUNIT_STATIC_STUB_REDIRECT() instead of exported variables, and avoids the need for different definitions of the debug macros in KUnit and not-KUnit builds. - A new function cs_dsp_can_emit_message() returns true if the messages can be emitted to the kernel log. In a normal not-KUnit build this function collapses to simply returning true. In KUnit builds it will rate-limit output, and this uses a single static rate limiter so it limits the overall rate across all cs_dsp log messages. The KUnit test can redirect it to change the suppression behavior. - The cs_dsp debug message macros are changed to only call the dev_xxx() if cs_dsp_can_emit_message() returns true. These are still macros so there is no problem wrapping the dev_xxx(). For a normal not-KUnit build cs_dsp_can_emit_message() always returns true so these macros simplify down to being identical to calling dev_xxx() directly. - The KUnit tests that cause a lot of cs_dsp messages now redirect cs_dsp_can_emit_message() to a local function. This returns false to suppress cs_dsp messages, unless DEBUG is defined for that test. I have checked that for a x86_64 production (non-KUnit) build the disassembled cs_dsp.o is identical to what was generated from the original code. So the complier is correctly simplifying the cs_dsp_can_emit_message() and macros down to only the call to dev_xxx(). Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260310130343.1791951-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- drivers/firmware/cirrus/cs_dsp.c | 68 ++++++++++--------- drivers/firmware/cirrus/cs_dsp.h | 4 +- .../firmware/cirrus/test/cs_dsp_test_bin.c | 21 +++--- .../cirrus/test/cs_dsp_test_bin_error.c | 19 +++--- .../firmware/cirrus/test/cs_dsp_test_wmfw.c | 25 ++++--- .../cirrus/test/cs_dsp_test_wmfw_error.c | 23 +++---- 6 files changed, 79 insertions(+), 81 deletions(-) diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c index f9d8a883900d..39e7e1db8eed 100644 --- a/drivers/firmware/cirrus/cs_dsp.c +++ b/drivers/firmware/cirrus/cs_dsp.c @@ -9,6 +9,7 @@ * Cirrus Logic International Semiconductor Ltd. */ +#include #include #include #include @@ -18,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -30,45 +32,47 @@ /* * When the KUnit test is running the error-case tests will cause a lot * of messages. Rate-limit to prevent overflowing the kernel log buffer - * during KUnit test runs. + * during KUnit test runs and allow the test to redirect this function. + * In normal (not KUnit) builds this collapses to only return true. */ -#if IS_ENABLED(CONFIG_FW_CS_DSP_KUNIT_TEST) -bool cs_dsp_suppress_err_messages; -EXPORT_SYMBOL_IF_KUNIT(cs_dsp_suppress_err_messages); +VISIBLE_IF_KUNIT bool cs_dsp_can_emit_message(void) +{ + KUNIT_STATIC_STUB_REDIRECT(cs_dsp_can_emit_message); -bool cs_dsp_suppress_warn_messages; -EXPORT_SYMBOL_IF_KUNIT(cs_dsp_suppress_warn_messages); + if (IS_ENABLED(CONFIG_FW_CS_DSP_KUNIT_TEST)) { + static DEFINE_RATELIMIT_STATE(_rs, + DEFAULT_RATELIMIT_INTERVAL, + DEFAULT_RATELIMIT_BURST); + return __ratelimit(&_rs); + } -bool cs_dsp_suppress_info_messages; -EXPORT_SYMBOL_IF_KUNIT(cs_dsp_suppress_info_messages); + return true; +} +EXPORT_SYMBOL_IF_KUNIT(cs_dsp_can_emit_message); -#define cs_dsp_err(_dsp, fmt, ...) \ - do { \ - if (!cs_dsp_suppress_err_messages) \ - dev_err_ratelimited(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__); \ +#define cs_dsp_err(_dsp, fmt, ...) \ + do { \ + if (cs_dsp_can_emit_message()) \ + dev_err(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__); \ } while (false) -#define cs_dsp_warn(_dsp, fmt, ...) \ - do { \ - if (!cs_dsp_suppress_warn_messages) \ - dev_warn_ratelimited(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__); \ + +#define cs_dsp_warn(_dsp, fmt, ...) \ + do { \ + if (cs_dsp_can_emit_message()) \ + dev_warn(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__); \ } while (false) -#define cs_dsp_info(_dsp, fmt, ...) \ - do { \ - if (!cs_dsp_suppress_info_messages) \ - dev_info_ratelimited(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__); \ + +#define cs_dsp_info(_dsp, fmt, ...) \ + do { \ + if (cs_dsp_can_emit_message()) \ + dev_info(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__); \ + } while (false) + +#define cs_dsp_dbg(_dsp, fmt, ...) \ + do { \ + if (cs_dsp_can_emit_message()) \ + dev_dbg(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__); \ } while (false) -#define cs_dsp_dbg(_dsp, fmt, ...) \ - dev_dbg_ratelimited(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) -#else -#define cs_dsp_err(_dsp, fmt, ...) \ - dev_err(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) -#define cs_dsp_warn(_dsp, fmt, ...) \ - dev_warn(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) -#define cs_dsp_info(_dsp, fmt, ...) \ - dev_info(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) -#define cs_dsp_dbg(_dsp, fmt, ...) \ - dev_dbg(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) -#endif #define ADSP1_CONTROL_1 0x00 #define ADSP1_CONTROL_2 0x02 diff --git a/drivers/firmware/cirrus/cs_dsp.h b/drivers/firmware/cirrus/cs_dsp.h index adf543004aea..04d768d08d03 100644 --- a/drivers/firmware/cirrus/cs_dsp.h +++ b/drivers/firmware/cirrus/cs_dsp.h @@ -10,9 +10,7 @@ #define FW_CS_DSP_H #if IS_ENABLED(CONFIG_KUNIT) -extern bool cs_dsp_suppress_err_messages; -extern bool cs_dsp_suppress_warn_messages; -extern bool cs_dsp_suppress_info_messages; +bool cs_dsp_can_emit_message(void); #endif #endif /* ifndef FW_CS_DSP_H */ diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_bin.c b/drivers/firmware/cirrus/test/cs_dsp_test_bin.c index 61078da57462..63416838f865 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_bin.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_bin.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -2155,6 +2156,15 @@ static void bin_patch_name_and_info(struct kunit *test) KUNIT_EXPECT_EQ(test, reg_val, payload_data); } +static bool cs_dsp_bin_test_can_emit_message_hook(void) +{ +#if defined(DEBUG) + return true; +#else + return false; +#endif +} + static int cs_dsp_bin_test_common_init(struct kunit *test, struct cs_dsp *dsp, int wmdr_ver) { @@ -2239,16 +2249,12 @@ static int cs_dsp_bin_test_common_init(struct kunit *test, struct cs_dsp *dsp, * The large number of test cases will cause an unusually large amount * of dev_info() messages from cs_dsp, so suppress these. */ - cs_dsp_suppress_info_messages = true; + kunit_activate_static_stub(test, cs_dsp_can_emit_message, + cs_dsp_bin_test_can_emit_message_hook); return 0; } -static void cs_dsp_bin_test_exit(struct kunit *test) -{ - cs_dsp_suppress_info_messages = false; -} - static int cs_dsp_bin_test_halo_init_common(struct kunit *test, int wmdr_ver) { struct cs_dsp *dsp; @@ -2833,7 +2839,6 @@ static struct kunit_case cs_dsp_bin_test_cases_adsp2[] = { static struct kunit_suite cs_dsp_bin_test_halo = { .name = "cs_dsp_bin_halo", .init = cs_dsp_bin_test_halo_init, - .exit = cs_dsp_bin_test_exit, .test_cases = cs_dsp_bin_test_cases_halo, .attr.speed = KUNIT_SPEED_SLOW, }; @@ -2848,7 +2853,6 @@ static struct kunit_suite cs_dsp_bin_test_halo_wmdr3 = { static struct kunit_suite cs_dsp_bin_test_adsp2_32bit = { .name = "cs_dsp_bin_adsp2_32bit", .init = cs_dsp_bin_test_adsp2_32bit_init, - .exit = cs_dsp_bin_test_exit, .test_cases = cs_dsp_bin_test_cases_adsp2, .attr.speed = KUNIT_SPEED_SLOW, }; @@ -2856,7 +2860,6 @@ static struct kunit_suite cs_dsp_bin_test_adsp2_32bit = { static struct kunit_suite cs_dsp_bin_test_adsp2_16bit = { .name = "cs_dsp_bin_adsp2_16bit", .init = cs_dsp_bin_test_adsp2_16bit_init, - .exit = cs_dsp_bin_test_exit, .test_cases = cs_dsp_bin_test_cases_adsp2, .attr.speed = KUNIT_SPEED_SLOW, }; diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c b/drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c index 888e277a265c..f879c5467c98 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -380,11 +381,13 @@ static void bin_block_payload_len_garbage(struct kunit *test) 0); } -static void cs_dsp_bin_err_test_exit(struct kunit *test) +static bool cs_dsp_bin_err_test_can_emit_message_hook(void) { - cs_dsp_suppress_err_messages = false; - cs_dsp_suppress_warn_messages = false; - cs_dsp_suppress_info_messages = false; +#if defined(DEBUG) + return true; +#else + return false; +#endif } static int cs_dsp_bin_err_test_common_init(struct kunit *test, struct cs_dsp *dsp, @@ -482,9 +485,8 @@ static int cs_dsp_bin_err_test_common_init(struct kunit *test, struct cs_dsp *ds * Testing error conditions can produce a lot of log output * from cs_dsp error messages, so suppress messages. */ - cs_dsp_suppress_err_messages = true; - cs_dsp_suppress_warn_messages = true; - cs_dsp_suppress_info_messages = true; + kunit_activate_static_stub(test, cs_dsp_can_emit_message, + cs_dsp_bin_err_test_can_emit_message_hook); return 0; } @@ -584,7 +586,6 @@ static struct kunit_case cs_dsp_bin_err_test_cases[] = { static struct kunit_suite cs_dsp_bin_err_test_halo = { .name = "cs_dsp_bin_err_halo", .init = cs_dsp_bin_err_test_halo_init, - .exit = cs_dsp_bin_err_test_exit, .test_cases = cs_dsp_bin_err_test_cases, .attr.speed = KUNIT_SPEED_SLOW, }; @@ -592,7 +593,6 @@ static struct kunit_suite cs_dsp_bin_err_test_halo = { static struct kunit_suite cs_dsp_bin_err_test_adsp2_32bit = { .name = "cs_dsp_bin_err_adsp2_32bit", .init = cs_dsp_bin_err_test_adsp2_32bit_init, - .exit = cs_dsp_bin_err_test_exit, .test_cases = cs_dsp_bin_err_test_cases, .attr.speed = KUNIT_SPEED_SLOW, }; @@ -600,7 +600,6 @@ static struct kunit_suite cs_dsp_bin_err_test_adsp2_32bit = { static struct kunit_suite cs_dsp_bin_err_test_adsp2_16bit = { .name = "cs_dsp_bin_err_adsp2_16bit", .init = cs_dsp_bin_err_test_adsp2_16bit_init, - .exit = cs_dsp_bin_err_test_exit, .test_cases = cs_dsp_bin_err_test_cases, .attr.speed = KUNIT_SPEED_SLOW, }; diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_wmfw.c b/drivers/firmware/cirrus/test/cs_dsp_test_wmfw.c index 788458d5a287..1de70e81a868 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_wmfw.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_wmfw.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -1775,6 +1776,15 @@ static void wmfw_load_with_info(struct kunit *test) KUNIT_EXPECT_MEMEQ(test, readback, payload_data, payload_size_bytes); } +static bool cs_dsp_wmfw_test_can_emit_message_hook(void) +{ +#if defined(DEBUG) + return true; +#else + return false; +#endif +} + static int cs_dsp_wmfw_test_common_init(struct kunit *test, struct cs_dsp *dsp, int wmfw_version) { @@ -1863,16 +1873,12 @@ static int cs_dsp_wmfw_test_common_init(struct kunit *test, struct cs_dsp *dsp, * The large number of test cases will cause an unusually large amount * of dev_info() messages from cs_dsp, so suppress these. */ - cs_dsp_suppress_info_messages = true; + kunit_activate_static_stub(test, cs_dsp_can_emit_message, + cs_dsp_wmfw_test_can_emit_message_hook); return 0; } -static void cs_dsp_wmfw_test_exit(struct kunit *test) -{ - cs_dsp_suppress_info_messages = false; -} - static int cs_dsp_wmfw_test_halo_init(struct kunit *test) { struct cs_dsp *dsp; @@ -2180,7 +2186,6 @@ static struct kunit_case cs_dsp_wmfw_test_cases_adsp2[] = { static struct kunit_suite cs_dsp_wmfw_test_halo = { .name = "cs_dsp_wmfwV3_halo", .init = cs_dsp_wmfw_test_halo_init, - .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_halo, .attr.speed = KUNIT_SPEED_SLOW, }; @@ -2188,7 +2193,6 @@ static struct kunit_suite cs_dsp_wmfw_test_halo = { static struct kunit_suite cs_dsp_wmfw_test_adsp2_32bit_wmfw0 = { .name = "cs_dsp_wmfwV0_adsp2_32bit", .init = cs_dsp_wmfw_test_adsp2_32bit_wmfw0_init, - .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, .attr.speed = KUNIT_SPEED_SLOW, }; @@ -2196,7 +2200,6 @@ static struct kunit_suite cs_dsp_wmfw_test_adsp2_32bit_wmfw0 = { static struct kunit_suite cs_dsp_wmfw_test_adsp2_32bit_wmfw1 = { .name = "cs_dsp_wmfwV1_adsp2_32bit", .init = cs_dsp_wmfw_test_adsp2_32bit_wmfw1_init, - .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, .attr.speed = KUNIT_SPEED_SLOW, }; @@ -2204,7 +2207,6 @@ static struct kunit_suite cs_dsp_wmfw_test_adsp2_32bit_wmfw1 = { static struct kunit_suite cs_dsp_wmfw_test_adsp2_32bit_wmfw2 = { .name = "cs_dsp_wmfwV2_adsp2_32bit", .init = cs_dsp_wmfw_test_adsp2_32bit_wmfw2_init, - .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, .attr.speed = KUNIT_SPEED_SLOW, }; @@ -2212,7 +2214,6 @@ static struct kunit_suite cs_dsp_wmfw_test_adsp2_32bit_wmfw2 = { static struct kunit_suite cs_dsp_wmfw_test_adsp2_16bit_wmfw0 = { .name = "cs_dsp_wmfwV0_adsp2_16bit", .init = cs_dsp_wmfw_test_adsp2_16bit_wmfw0_init, - .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, .attr.speed = KUNIT_SPEED_SLOW, }; @@ -2220,7 +2221,6 @@ static struct kunit_suite cs_dsp_wmfw_test_adsp2_16bit_wmfw0 = { static struct kunit_suite cs_dsp_wmfw_test_adsp2_16bit_wmfw1 = { .name = "cs_dsp_wmfwV1_adsp2_16bit", .init = cs_dsp_wmfw_test_adsp2_16bit_wmfw1_init, - .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, .attr.speed = KUNIT_SPEED_SLOW, }; @@ -2228,7 +2228,6 @@ static struct kunit_suite cs_dsp_wmfw_test_adsp2_16bit_wmfw1 = { static struct kunit_suite cs_dsp_wmfw_test_adsp2_16bit_wmfw2 = { .name = "cs_dsp_wmfwV2_adsp2_16bit", .init = cs_dsp_wmfw_test_adsp2_16bit_wmfw2_init, - .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, .attr.speed = KUNIT_SPEED_SLOW, }; diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_wmfw_error.c b/drivers/firmware/cirrus/test/cs_dsp_test_wmfw_error.c index 79eee89a7fd5..e7bf5dc474f5 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_wmfw_error.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_wmfw_error.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -989,11 +990,13 @@ static void wmfw_v2_coeff_description_exceeds_block(struct kunit *test) -EOVERFLOW); } -static void cs_dsp_wmfw_err_test_exit(struct kunit *test) +static bool cs_dsp_wmfw_err_test_can_emit_message_hook(void) { - cs_dsp_suppress_err_messages = false; - cs_dsp_suppress_warn_messages = false; - cs_dsp_suppress_info_messages = false; +#if defined(DEBUG) + return true; +#else + return false; +#endif } static int cs_dsp_wmfw_err_test_common_init(struct kunit *test, struct cs_dsp *dsp, @@ -1080,9 +1083,8 @@ static int cs_dsp_wmfw_err_test_common_init(struct kunit *test, struct cs_dsp *d * Testing error conditions can produce a lot of log output * from cs_dsp error messages, so suppress messages. */ - cs_dsp_suppress_err_messages = true; - cs_dsp_suppress_warn_messages = true; - cs_dsp_suppress_info_messages = true; + kunit_activate_static_stub(test, cs_dsp_can_emit_message, + cs_dsp_wmfw_err_test_can_emit_message_hook); return 0; } @@ -1304,7 +1306,6 @@ static struct kunit_case cs_dsp_wmfw_err_test_cases_v3[] = { static struct kunit_suite cs_dsp_wmfw_err_test_halo = { .name = "cs_dsp_wmfwV3_err_halo", .init = cs_dsp_wmfw_err_test_halo_init, - .exit = cs_dsp_wmfw_err_test_exit, .test_cases = cs_dsp_wmfw_err_test_cases_v3, .attr.speed = KUNIT_SPEED_SLOW, }; @@ -1312,7 +1313,6 @@ static struct kunit_suite cs_dsp_wmfw_err_test_halo = { static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_32bit_wmfw0 = { .name = "cs_dsp_wmfwV0_err_adsp2_32bit", .init = cs_dsp_wmfw_err_test_adsp2_32bit_wmfw0_init, - .exit = cs_dsp_wmfw_err_test_exit, .test_cases = cs_dsp_wmfw_err_test_cases_v0, .attr.speed = KUNIT_SPEED_SLOW, }; @@ -1320,7 +1320,6 @@ static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_32bit_wmfw0 = { static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_32bit_wmfw1 = { .name = "cs_dsp_wmfwV1_err_adsp2_32bit", .init = cs_dsp_wmfw_err_test_adsp2_32bit_wmfw1_init, - .exit = cs_dsp_wmfw_err_test_exit, .test_cases = cs_dsp_wmfw_err_test_cases_v1, .attr.speed = KUNIT_SPEED_SLOW, }; @@ -1328,7 +1327,6 @@ static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_32bit_wmfw1 = { static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_32bit_wmfw2 = { .name = "cs_dsp_wmfwV2_err_adsp2_32bit", .init = cs_dsp_wmfw_err_test_adsp2_32bit_wmfw2_init, - .exit = cs_dsp_wmfw_err_test_exit, .test_cases = cs_dsp_wmfw_err_test_cases_v2, .attr.speed = KUNIT_SPEED_SLOW, }; @@ -1336,7 +1334,6 @@ static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_32bit_wmfw2 = { static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_16bit_wmfw0 = { .name = "cs_dsp_wmfwV0_err_adsp2_16bit", .init = cs_dsp_wmfw_err_test_adsp2_16bit_wmfw0_init, - .exit = cs_dsp_wmfw_err_test_exit, .test_cases = cs_dsp_wmfw_err_test_cases_v0, .attr.speed = KUNIT_SPEED_SLOW, }; @@ -1344,7 +1341,6 @@ static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_16bit_wmfw0 = { static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_16bit_wmfw1 = { .name = "cs_dsp_wmfwV1_err_adsp2_16bit", .init = cs_dsp_wmfw_err_test_adsp2_16bit_wmfw1_init, - .exit = cs_dsp_wmfw_err_test_exit, .test_cases = cs_dsp_wmfw_err_test_cases_v1, .attr.speed = KUNIT_SPEED_SLOW, }; @@ -1352,7 +1348,6 @@ static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_16bit_wmfw1 = { static struct kunit_suite cs_dsp_wmfw_err_test_adsp2_16bit_wmfw2 = { .name = "cs_dsp_wmfwV2_err_adsp2_16bit", .init = cs_dsp_wmfw_err_test_adsp2_16bit_wmfw2_init, - .exit = cs_dsp_wmfw_err_test_exit, .test_cases = cs_dsp_wmfw_err_test_cases_v2, .attr.speed = KUNIT_SPEED_SLOW, }; From fd7df93013c5118812e63a52635dc6c3a805a1de Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 10 Mar 2026 11:29:20 +0100 Subject: [PATCH 089/359] ALSA: aoa: Skip devices with no codecs in i2sbus_resume() In i2sbus_resume(), skip devices with an empty codec list, which avoids using an uninitialized 'sysclock_factor' in the 32-bit format path in i2sbus_pcm_prepare(). In i2sbus_pcm_prepare(), replace two list_for_each_entry() loops with a single list_first_entry() now that the codec list is guaranteed to be non-empty by all callers. Fixes: f3d9478b2ce4 ("[ALSA] snd-aoa: add snd-aoa") Cc: stable@vger.kernel.org Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260310102921.210109-3-thorsten.blum@linux.dev Signed-off-by: Takashi Iwai --- sound/aoa/soundbus/i2sbus/core.c | 3 +++ sound/aoa/soundbus/i2sbus/pcm.c | 16 +++++----------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/sound/aoa/soundbus/i2sbus/core.c b/sound/aoa/soundbus/i2sbus/core.c index f974b96e98cd..22c956267f4e 100644 --- a/sound/aoa/soundbus/i2sbus/core.c +++ b/sound/aoa/soundbus/i2sbus/core.c @@ -405,6 +405,9 @@ static int i2sbus_resume(struct macio_dev* dev) int err, ret = 0; list_for_each_entry(i2sdev, &control->list, item) { + if (list_empty(&i2sdev->sound.codec_list)) + continue; + /* reset i2s bus format etc. */ i2sbus_pcm_prepare_both(i2sdev); diff --git a/sound/aoa/soundbus/i2sbus/pcm.c b/sound/aoa/soundbus/i2sbus/pcm.c index aff99003d833..97c807e67d56 100644 --- a/sound/aoa/soundbus/i2sbus/pcm.c +++ b/sound/aoa/soundbus/i2sbus/pcm.c @@ -383,6 +383,9 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in) /* set stop command */ command->command = cpu_to_le16(DBDMA_STOP); + cii = list_first_entry(&i2sdev->sound.codec_list, + struct codec_info_item, list); + /* ok, let's set the serial format and stuff */ switch (runtime->format) { /* 16 bit formats */ @@ -390,13 +393,7 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in) case SNDRV_PCM_FORMAT_U16_BE: /* FIXME: if we add different bus factors we need to * do more here!! */ - bi.bus_factor = 0; - list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { - bi.bus_factor = cii->codec->bus_factor; - break; - } - if (!bi.bus_factor) - return -ENODEV; + bi.bus_factor = cii->codec->bus_factor; input_16bit = 1; break; case SNDRV_PCM_FORMAT_S32_BE: @@ -410,10 +407,7 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in) return -EINVAL; } /* we assume all sysclocks are the same! */ - list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { - bi.sysclock_factor = cii->codec->sysclock_factor; - break; - } + bi.sysclock_factor = cii->codec->sysclock_factor; if (clock_and_divisors(bi.sysclock_factor, bi.bus_factor, From 4ba5c63778e5cca15b1408f012d00b441f808f3d Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Tue, 10 Mar 2026 08:23:48 +0000 Subject: [PATCH 090/359] ASoC: da7219: scope AAD suspend and resume helpers to CONFIG_PM In current linux.git, da7219_aad_suspend() and da7219_aad_resume() are defined and declared unconditionally. However, their only in-tree callers are the component PM callbacks in da7219.c, which are already guarded by #ifdef CONFIG_PM. This inconsistency leads to unnecessary code inclusion and potential compiler warnings when CONFIG_PM is disabled. Match the helper declarations and definitions to the callers' scope and provide no-op !CONFIG_PM stubs in the header. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260310082348.743716-1-pengpeng.hou@isrc.iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/da7219-aad.c | 3 ++- sound/soc/codecs/da7219-aad.h | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/da7219-aad.c b/sound/soc/codecs/da7219-aad.c index d9d932a78b71..0b3b108aaeee 100644 --- a/sound/soc/codecs/da7219-aad.c +++ b/sound/soc/codecs/da7219-aad.c @@ -924,6 +924,7 @@ static void da7219_aad_handle_gnd_switch_time(struct snd_soc_component *componen * Suspend/Resume */ +#ifdef CONFIG_PM void da7219_aad_suspend(struct snd_soc_component *component) { struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); @@ -981,7 +982,7 @@ void da7219_aad_resume(struct snd_soc_component *component) enable_irq(da7219_aad->irq); } - +#endif /* * Init/Exit diff --git a/sound/soc/codecs/da7219-aad.h b/sound/soc/codecs/da7219-aad.h index fbfbf3e67918..ea5b81819247 100644 --- a/sound/soc/codecs/da7219-aad.h +++ b/sound/soc/codecs/da7219-aad.h @@ -209,8 +209,13 @@ struct da7219_aad_priv { void da7219_aad_jack_det(struct snd_soc_component *component, struct snd_soc_jack *jack); /* Suspend/Resume */ +#ifdef CONFIG_PM void da7219_aad_suspend(struct snd_soc_component *component); void da7219_aad_resume(struct snd_soc_component *component); +#else +static inline void da7219_aad_suspend(struct snd_soc_component *component) { } +static inline void da7219_aad_resume(struct snd_soc_component *component) { } +#endif /* Init/Exit */ int da7219_aad_init(struct snd_soc_component *component); From b0b49c77bddac75db79f7c2c6ec0b07d61864f2f Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Mon, 9 Mar 2026 10:16:01 +0100 Subject: [PATCH 091/359] ASoC: Intel: catpt: Synchronize stream access Streams may have individual controls assigned to them e.g.: volume control in case of offload streams. If such a stream is running and simultaneously its controls are being manipulated, both processes are touching the exact same descriptors - access to these must be synchronized. Replace spinlock with mutex as IPCs are non-atomic operations and add proper locking for all ->stream_list users. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260309091605.896307-2-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/catpt/core.h | 2 +- sound/soc/intel/catpt/device.c | 2 +- sound/soc/intel/catpt/ipc.c | 3 +++ sound/soc/intel/catpt/loader.c | 2 ++ sound/soc/intel/catpt/pcm.c | 25 +++++++++++++++---------- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/sound/soc/intel/catpt/core.h b/sound/soc/intel/catpt/core.h index df8a5fd95e13..7e479ef89ad0 100644 --- a/sound/soc/intel/catpt/core.h +++ b/sound/soc/intel/catpt/core.h @@ -96,7 +96,7 @@ struct catpt_dev { struct catpt_module_type modules[CATPT_MODULE_COUNT]; struct catpt_ssp_device_format devfmt[CATPT_SSP_COUNT]; struct list_head stream_list; - spinlock_t list_lock; + struct mutex stream_mutex; struct mutex clk_mutex; struct catpt_dx_context dx_ctx; diff --git a/sound/soc/intel/catpt/device.c b/sound/soc/intel/catpt/device.c index 0638aecba40d..b5f4361d4465 100644 --- a/sound/soc/intel/catpt/device.c +++ b/sound/soc/intel/catpt/device.c @@ -226,7 +226,7 @@ static void catpt_dev_init(struct catpt_dev *cdev, struct device *dev, cdev->spec = spec; init_completion(&cdev->fw_ready); INIT_LIST_HEAD(&cdev->stream_list); - spin_lock_init(&cdev->list_lock); + mutex_init(&cdev->stream_mutex); mutex_init(&cdev->clk_mutex); /* diff --git a/sound/soc/intel/catpt/ipc.c b/sound/soc/intel/catpt/ipc.c index 5a01a9afb26e..2e3b7a5cbb9b 100644 --- a/sound/soc/intel/catpt/ipc.c +++ b/sound/soc/intel/catpt/ipc.c @@ -5,6 +5,7 @@ // Author: Cezary Rojewski // +#include #include #include "core.h" #include "messages.h" @@ -151,6 +152,8 @@ catpt_dsp_notify_stream(struct catpt_dev *cdev, union catpt_notify_msg msg) struct catpt_notify_position pos; struct catpt_notify_glitch glitch; + guard(mutex)(&cdev->stream_mutex); + stream = catpt_stream_find(cdev, msg.stream_hw_id); if (!stream) { dev_warn(cdev->dev, "notify %d for non-existent stream %d\n", diff --git a/sound/soc/intel/catpt/loader.c b/sound/soc/intel/catpt/loader.c index dc7afe587e6f..432cb1f0ab4e 100644 --- a/sound/soc/intel/catpt/loader.c +++ b/sound/soc/intel/catpt/loader.c @@ -90,6 +90,7 @@ int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) { struct catpt_stream_runtime *stream; + /* Lockless as no streams can be added or removed during D3 -> D0 transition. */ list_for_each_entry(stream, &cdev->stream_list, node) { u32 off, size; int ret; @@ -180,6 +181,7 @@ catpt_restore_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) { struct catpt_stream_runtime *stream; + /* Lockless as no streams can be added or removed during D3 -> D0 transition. */ list_for_each_entry(stream, &cdev->stream_list, node) { u32 off, size; int ret; diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c index 2c5ea4e0ff3d..fbe4821755bd 100644 --- a/sound/soc/intel/catpt/pcm.c +++ b/sound/soc/intel/catpt/pcm.c @@ -5,6 +5,7 @@ // Author: Cezary Rojewski // +#include #include #include #include @@ -97,12 +98,12 @@ catpt_get_stream_template(struct snd_pcm_substream *substream) return catpt_topology[type]; } +/* Caller responsible for holding ->stream_mutex. */ struct catpt_stream_runtime * catpt_stream_find(struct catpt_dev *cdev, u8 stream_hw_id) { struct catpt_stream_runtime *pos, *result = NULL; - spin_lock(&cdev->list_lock); list_for_each_entry(pos, &cdev->stream_list, node) { if (pos->info.stream_hw_id == stream_hw_id) { result = pos; @@ -110,7 +111,6 @@ catpt_stream_find(struct catpt_dev *cdev, u8 stream_hw_id) } } - spin_unlock(&cdev->list_lock); return result; } @@ -286,10 +286,6 @@ static int catpt_dai_startup(struct snd_pcm_substream *substream, INIT_LIST_HEAD(&stream->node); snd_soc_dai_set_dma_data(dai, substream, stream); - spin_lock(&cdev->list_lock); - list_add_tail(&stream->node, &cdev->stream_list); - spin_unlock(&cdev->list_lock); - return 0; err_request: @@ -307,10 +303,6 @@ static void catpt_dai_shutdown(struct snd_pcm_substream *substream, stream = snd_soc_dai_get_dma_data(dai, substream); - spin_lock(&cdev->list_lock); - list_del(&stream->node); - spin_unlock(&cdev->list_lock); - release_resource(stream->persistent); kfree(stream->persistent); catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask); @@ -410,12 +402,15 @@ static int catpt_dai_hw_params(struct snd_pcm_substream *substream, if (ret) return CATPT_IPC_RET(ret); + guard(mutex)(&cdev->stream_mutex); + ret = catpt_dai_apply_usettings(dai, stream); if (ret) { catpt_ipc_free_stream(cdev, stream->info.stream_hw_id); return ret; } + list_add_tail(&stream->node, &cdev->stream_list); stream->allocated = true; return 0; } @@ -430,6 +425,10 @@ static int catpt_dai_hw_free(struct snd_pcm_substream *substream, if (!stream->allocated) return 0; + mutex_lock(&cdev->stream_mutex); + list_del(&stream->node); + mutex_unlock(&cdev->stream_mutex); + catpt_ipc_reset_stream(cdev, stream->info.stream_hw_id); catpt_ipc_free_stream(cdev, stream->info.stream_hw_id); @@ -910,6 +909,8 @@ static int catpt_stream_volume_get(struct snd_kcontrol *kcontrol, int ret; int i; + guard(mutex)(&cdev->stream_mutex); + stream = catpt_stream_find(cdev, pin_id); if (!stream) { for (i = 0; i < CATPT_CHANNELS_MAX; i++) @@ -941,6 +942,8 @@ static int catpt_stream_volume_put(struct snd_kcontrol *kcontrol, long *ctlvol = (long *)kcontrol->private_value; int ret, i; + guard(mutex)(&cdev->stream_mutex); + stream = catpt_stream_find(cdev, pin_id); if (!stream) { for (i = 0; i < CATPT_CHANNELS_MAX; i++) @@ -1017,6 +1020,8 @@ static int catpt_loopback_switch_put(struct snd_kcontrol *kcontrol, bool mute; int ret; + guard(mutex)(&cdev->stream_mutex); + mute = (bool)ucontrol->value.integer.value[0]; stream = catpt_stream_find(cdev, CATPT_PIN_ID_REFERENCE); if (!stream) { From d16b942aa7fa6135a44d156246d98e50b5a0aad3 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Mon, 9 Mar 2026 10:16:02 +0100 Subject: [PATCH 092/359] ASoC: Intel: catpt: New volume and mute control operations The catpt-driver's volume and mute control operations always return '0' regardless if a change occurred or not. To conform to ALSA's interface, value '1' shall be returned when a change occurred. The second major point is power consumption. Existing control operations always wake the DSP even if no streams are running. In such case waking the DSP just for the sake of updating the volume (or mute) settings on the firmware side is a waste of power. The provided implementation caches the values and updates the settings only when streams are being opened for streaming or are already running. As changing existing code is non-trivial, provide new operations instead. The put() operation, which interests us the most, takes the following shape: // two values provided to put(): // pin_id - which stream given control relates to // value_to_apply - the value from user if (control->existing_val == value_to_apply) return 0; runtime_stream = get_running_stream(pin_id); if (runtime_stream != NULL) { ret = send_ipc(); if (ret) return ret; } control->existing_val = value_to_apply; return 1; Adheres to ALSA's expectation and avoids sending IPCs if there is no change to be made. Two helpers which are part of the patch, catpt_stream_hw_id() and catpt_stream_volume_regs(), help differentiate between individual streams and the general MIXER stream. Translates to one pair of get()/put() instead of two pairs as done currently. PIN_ID_INVALID is returned if given stream is not currently running - the constant is part of the firmware's API but remained unused by the driver. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260309091605.896307-3-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/catpt/messages.h | 3 + sound/soc/intel/catpt/pcm.c | 149 +++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) diff --git a/sound/soc/intel/catpt/messages.h b/sound/soc/intel/catpt/messages.h index a634943eb669..fcc9f1b46bc2 100644 --- a/sound/soc/intel/catpt/messages.h +++ b/sound/soc/intel/catpt/messages.h @@ -69,6 +69,7 @@ struct catpt_fw_version { int catpt_ipc_get_fw_version(struct catpt_dev *cdev, struct catpt_fw_version *version); +/* PIN_IDs represent both, individual streams and the general mixer. */ enum catpt_pin_id { CATPT_PIN_ID_SYSTEM = 0, CATPT_PIN_ID_REFERENCE = 1, @@ -79,6 +80,8 @@ enum catpt_pin_id { CATPT_PIN_ID_MIXER = 7, CATPT_PIN_ID_BLUETOOTH_CAPTURE = 8, CATPT_PIN_ID_BLUETOOTH_RENDER = 9, + /* 10 is reserved */ + CATPT_PIN_ID_INVALID = 11, }; enum catpt_path_id { diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c index fbe4821755bd..08f1e36a136f 100644 --- a/sound/soc/intel/catpt/pcm.c +++ b/sound/soc/intel/catpt/pcm.c @@ -114,6 +114,46 @@ catpt_stream_find(struct catpt_dev *cdev, u8 stream_hw_id) return result; } +/* Caller responsible for holding ->stream_mutex. */ +static u8 catpt_stream_hw_id(struct catpt_dev *cdev, enum catpt_pin_id pin_id) +{ + struct catpt_stream_runtime *stream; + + switch (pin_id) { + default: + stream = catpt_stream_find(cdev, pin_id); + if (stream) + return stream->info.stream_hw_id; + break; + case CATPT_PIN_ID_MIXER: + if (!list_empty(&cdev->stream_list)) + return cdev->mixer.mixer_hw_id; + break; + } + + return CATPT_PIN_ID_INVALID; +} + +/* Caller responsible for holding ->stream_mutex. */ +static u32 *catpt_stream_volume_regs(struct catpt_dev *cdev, enum catpt_pin_id pin_id) +{ + struct catpt_stream_runtime *stream; + + switch (pin_id) { + case CATPT_PIN_ID_MIXER: + if (!list_empty(&cdev->stream_list)) + return &cdev->mixer.volume_regaddr[0]; + break; + default: + stream = catpt_stream_find(cdev, pin_id); + if (stream) + return &stream->info.volume_regaddr[0]; + break; + } + + return NULL; +} + static void catpt_stream_read_position(struct catpt_dev *cdev, struct catpt_stream_runtime *stream, u32 *pos) { @@ -314,6 +354,11 @@ static void catpt_dai_shutdown(struct snd_pcm_substream *substream, static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol); +struct catpt_control_data { + enum catpt_pin_id pin_id; + long volumes[CATPT_CHANNELS_MAX]; +}; + static int catpt_dai_apply_usettings(struct snd_soc_dai *dai, struct catpt_stream_runtime *stream) { @@ -855,6 +900,97 @@ static int catpt_volume_info(struct snd_kcontrol *kcontrol, return 0; } +__maybe_unused +static int catpt_volume_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kctl); + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + struct catpt_control_data *data; + u32 dspvol, *regs; + long *uvolumes; + int i; + + data = (struct catpt_control_data *)kctl->private_value; + uvolumes = &uctl->value.integer.value[0]; + + guard(mutex)(&cdev->stream_mutex); + + regs = catpt_stream_volume_regs(cdev, data->pin_id); + if (regs) { + for (i = 0; i < CATPT_CHANNELS_MAX; i++) { + dspvol = readl(cdev->lpe_ba + regs[i]); + data->volumes[i] = dspvol_to_ctlvol(dspvol); + } + } + + memcpy(uvolumes, data->volumes, sizeof(data->volumes)); + return 0; +} + +__maybe_unused +static int catpt_volume_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kctl); + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + struct catpt_control_data *data; + u8 stream_hw_id; + long *uvolumes; + int ret; + + data = (struct catpt_control_data *)kctl->private_value; + uvolumes = &uctl->value.integer.value[0]; + + if (!memcmp(data->volumes, uvolumes, sizeof(data->volumes))) + return 0; + + guard(mutex)(&cdev->stream_mutex); + + stream_hw_id = catpt_stream_hw_id(cdev, data->pin_id); + if (stream_hw_id != CATPT_PIN_ID_INVALID) { + ret = catpt_set_dspvol(cdev, stream_hw_id, uvolumes); + if (ret) + return ret; + } + + memcpy(data->volumes, uvolumes, sizeof(data->volumes)); + return 1; +} + +__maybe_unused +static int catpt_loopback_mute_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl) +{ + uctl->value.integer.value[0] = *(bool *)kctl->private_value; + return 0; +} + +__maybe_unused +static int catpt_loopback_mute_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kctl); + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + bool *kmute, cmute; + u8 stream_hw_id; + int ret; + + kmute = (bool *)kctl->private_value; + cmute = (bool)uctl->value.integer.value[0]; + + if (*kmute == cmute) + return 0; + + guard(mutex)(&cdev->stream_mutex); + + stream_hw_id = catpt_stream_hw_id(cdev, CATPT_PIN_ID_REFERENCE); + if (stream_hw_id != CATPT_PIN_ID_INVALID) { + ret = catpt_ipc_mute_loopback(cdev, stream_hw_id, cmute); + if (ret) + return CATPT_IPC_RET(ret); + } + + *kmute = cmute; + return 1; +} + static int catpt_mixer_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -1072,6 +1208,19 @@ static int catpt_waves_param_put(struct snd_kcontrol *kcontrol, static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(catpt_volume_tlv, -9000, 300, 1); +#define CATPT_VOLUME_CTL2(kname, pname) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = kname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .info = catpt_volume_info, \ + .get = catpt_volume_get, \ + .put = catpt_volume_put, \ + .tlv.p = catpt_volume_tlv, \ + .private_value = (unsigned long) \ + &(struct catpt_control_data) { CATPT_PIN_ID_##pname } \ +} + #define CATPT_VOLUME_CTL(kname, sname) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ .name = (kname), \ From 2464febd81e4c98f78466462da938ed2f8c37e17 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Mon, 9 Mar 2026 10:16:03 +0100 Subject: [PATCH 093/359] ASoC: Intel: catpt: Simplify procedure of applying user settings Existing catpt_dai_apply_usettings() applies all the individual control settings but why-what is covered behind if-statements. Refactor the operation into: catpt_apply_controls() |__ catpt_apply_volume() |__ catpt_apply_mute() to make it easy to understand why and what is going on. The update also enlists snd_ctl_find_id_mixer() for the query purpose, removing code duplication. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260309091605.896307-4-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/catpt/pcm.c | 66 +++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c index 08f1e36a136f..3074b6c958fa 100644 --- a/sound/soc/intel/catpt/pcm.c +++ b/sound/soc/intel/catpt/pcm.c @@ -359,46 +359,48 @@ struct catpt_control_data { long volumes[CATPT_CHANNELS_MAX]; }; -static int catpt_dai_apply_usettings(struct snd_soc_dai *dai, - struct catpt_stream_runtime *stream) +static int catpt_apply_volume(struct catpt_dev *cdev, struct snd_soc_card *card, const char *name) { - struct snd_soc_component *component = dai->component; - struct snd_kcontrol *pos; - struct catpt_dev *cdev = dev_get_drvdata(dai->dev); - const char *name; - int ret; - u32 id = stream->info.stream_hw_id; + struct snd_kcontrol *kctl = snd_ctl_find_id_mixer(card->snd_card, name); + struct catpt_control_data *data; - /* only selected streams have individual controls */ - switch (id) { + if (!kctl) + return -ENOENT; + data = (struct catpt_control_data *)kctl->private_value; + + return catpt_set_dspvol(cdev, data->pin_id, data->volumes); +} + +static int catpt_apply_mute(struct catpt_dev *cdev, struct snd_soc_card *card) +{ + struct snd_kcontrol *kctl = snd_ctl_find_id_mixer(card->snd_card, "Loopback Mute"); + bool mute; + int ret; + + if (!kctl) + return -ENOENT; + mute = *(bool *)kctl->private_value; + + ret = catpt_ipc_mute_loopback(cdev, CATPT_PIN_ID_REFERENCE, mute); + return CATPT_IPC_RET(ret); +} + +static int catpt_apply_controls(struct catpt_dev *cdev, struct snd_soc_card *card, + struct catpt_stream_runtime *stream) +{ + /* Only selected streams have individual controls. */ + switch (stream->info.stream_hw_id) { case CATPT_PIN_ID_OFFLOAD1: - name = "Media0 Playback Volume"; - break; + return catpt_apply_volume(cdev, card, "Media0 Playback Volume"); case CATPT_PIN_ID_OFFLOAD2: - name = "Media1 Playback Volume"; - break; + return catpt_apply_volume(cdev, card, "Media1 Playback Volume"); case CATPT_PIN_ID_CAPTURE1: - name = "Mic Capture Volume"; - break; + return catpt_apply_volume(cdev, card, "Mic Capture Volume"); case CATPT_PIN_ID_REFERENCE: - name = "Loopback Mute"; - break; + return catpt_apply_mute(cdev, card); default: return 0; } - - list_for_each_entry(pos, &component->card->snd_card->controls, list) { - if (pos->private_data == component && - !strncmp(name, pos->id.name, sizeof(pos->id.name))) - break; - } - if (list_entry_is_head(pos, &component->card->snd_card->controls, list)) - return -ENOENT; - - if (stream->template->type != CATPT_STRM_TYPE_LOOPBACK) - return catpt_set_dspvol(cdev, id, (long *)pos->private_value); - ret = catpt_ipc_mute_loopback(cdev, id, *(bool *)pos->private_value); - return CATPT_IPC_RET(ret); } static int catpt_dai_hw_params(struct snd_pcm_substream *substream, @@ -449,7 +451,7 @@ static int catpt_dai_hw_params(struct snd_pcm_substream *substream, guard(mutex)(&cdev->stream_mutex); - ret = catpt_dai_apply_usettings(dai, stream); + ret = catpt_apply_controls(cdev, dai->component->card, stream); if (ret) { catpt_ipc_free_stream(cdev, stream->info.stream_hw_id); return ret; From 150badf73e9a10cf646d07353b41117ea90f67ae Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Mon, 9 Mar 2026 10:16:04 +0100 Subject: [PATCH 094/359] ASoC: Intel: catpt: Do not wake DSP just for volume setup With the new control-operations in place, the controls no longer wake the DSP just for the sake of updating volume (or mute) settings on the firmware side. The values are cached and actual update occurs only when streams are being opened for streaming or are already running. In those cases the DSP must already be woken up and we avoid unnecessary power consumption. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260309091605.896307-5-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/catpt/pcm.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c index 3074b6c958fa..2ed9df7bc434 100644 --- a/sound/soc/intel/catpt/pcm.c +++ b/sound/soc/intel/catpt/pcm.c @@ -388,6 +388,15 @@ static int catpt_apply_mute(struct catpt_dev *cdev, struct snd_soc_card *card) static int catpt_apply_controls(struct catpt_dev *cdev, struct snd_soc_card *card, struct catpt_stream_runtime *stream) { + int ret; + + /* Update the master volume when the first stream is opened. */ + if (list_empty(&cdev->stream_list)) { + ret = catpt_apply_volume(cdev, card, "Master Playback Volume"); + if (ret) + return ret; + } + /* Only selected streams have individual controls. */ switch (stream->info.stream_hw_id) { case CATPT_PIN_ID_OFFLOAD1: From 8a99ccb032d670eae2b1ea89174e06a60d2d3fc2 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Mon, 9 Mar 2026 10:16:05 +0100 Subject: [PATCH 095/359] ASoC: Intel: catpt: Migrate to the new control operations Switch to the new implementation and remove all unused code. The change effectively causes the control put() operations to return '1' if a change occurred, '0' if no change was made or error otherwise. The second effect of the update is reducing the power consumption. With the new control-operations in place, the controls no longer wake the DSP just for the sake of updating volume (or mute) settings on the firmware side. The values are cached and actual update occurs only when streams are being opened for streaming or are already running. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260309091605.896307-6-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/catpt/pcm.c | 243 +----------------------------------- 1 file changed, 6 insertions(+), 237 deletions(-) diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c index 2ed9df7bc434..8356dbdb2809 100644 --- a/sound/soc/intel/catpt/pcm.c +++ b/sound/soc/intel/catpt/pcm.c @@ -160,32 +160,6 @@ static void catpt_stream_read_position(struct catpt_dev *cdev, memcpy_fromio(pos, cdev->lpe_ba + stream->info.read_pos_regaddr, sizeof(*pos)); } -static u32 catpt_stream_volume(struct catpt_dev *cdev, - struct catpt_stream_runtime *stream, u32 channel) -{ - u32 volume, offset; - - if (channel >= CATPT_CHANNELS_MAX) - channel = 0; - - offset = stream->info.volume_regaddr[channel]; - memcpy_fromio(&volume, cdev->lpe_ba + offset, sizeof(volume)); - return volume; -} - -static u32 catpt_mixer_volume(struct catpt_dev *cdev, - struct catpt_mixer_stream_info *info, u32 channel) -{ - u32 volume, offset; - - if (channel >= CATPT_CHANNELS_MAX) - channel = 0; - - offset = info->volume_regaddr[channel]; - memcpy_fromio(&volume, cdev->lpe_ba + offset, sizeof(volume)); - return volume; -} - static void catpt_arrange_page_table(struct snd_pcm_substream *substream, struct snd_dma_buffer *pgtbl) { @@ -911,7 +885,6 @@ static int catpt_volume_info(struct snd_kcontrol *kcontrol, return 0; } -__maybe_unused static int catpt_volume_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl) { struct snd_soc_component *component = snd_kcontrol_chip(kctl); @@ -938,7 +911,6 @@ static int catpt_volume_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value return 0; } -__maybe_unused static int catpt_volume_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl) { struct snd_soc_component *component = snd_kcontrol_chip(kctl); @@ -967,14 +939,12 @@ static int catpt_volume_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value return 1; } -__maybe_unused static int catpt_loopback_mute_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl) { uctl->value.integer.value[0] = *(bool *)kctl->private_value; return 0; } -__maybe_unused static int catpt_loopback_mute_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl) { struct snd_soc_component *component = snd_kcontrol_chip(kctl); @@ -1002,195 +972,6 @@ static int catpt_loopback_mute_put(struct snd_kcontrol *kctl, struct snd_ctl_ele return 1; } -static int catpt_mixer_volume_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct catpt_dev *cdev = dev_get_drvdata(component->dev); - u32 dspvol; - int ret; - int i; - - ret = pm_runtime_resume_and_get(cdev->dev); - if (ret) - return ret; - - for (i = 0; i < CATPT_CHANNELS_MAX; i++) { - dspvol = catpt_mixer_volume(cdev, &cdev->mixer, i); - ucontrol->value.integer.value[i] = dspvol_to_ctlvol(dspvol); - } - - pm_runtime_put_autosuspend(cdev->dev); - - return 0; -} - -static int catpt_mixer_volume_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct catpt_dev *cdev = dev_get_drvdata(component->dev); - int ret; - - ret = pm_runtime_resume_and_get(cdev->dev); - if (ret) - return ret; - - ret = catpt_set_dspvol(cdev, cdev->mixer.mixer_hw_id, - ucontrol->value.integer.value); - - pm_runtime_put_autosuspend(cdev->dev); - - return ret; -} - -static int catpt_stream_volume_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol, - enum catpt_pin_id pin_id) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct catpt_stream_runtime *stream; - struct catpt_dev *cdev = dev_get_drvdata(component->dev); - long *ctlvol = (long *)kcontrol->private_value; - u32 dspvol; - int ret; - int i; - - guard(mutex)(&cdev->stream_mutex); - - stream = catpt_stream_find(cdev, pin_id); - if (!stream) { - for (i = 0; i < CATPT_CHANNELS_MAX; i++) - ucontrol->value.integer.value[i] = ctlvol[i]; - return 0; - } - - ret = pm_runtime_resume_and_get(cdev->dev); - if (ret) - return ret; - - for (i = 0; i < CATPT_CHANNELS_MAX; i++) { - dspvol = catpt_stream_volume(cdev, stream, i); - ucontrol->value.integer.value[i] = dspvol_to_ctlvol(dspvol); - } - - pm_runtime_put_autosuspend(cdev->dev); - - return 0; -} - -static int catpt_stream_volume_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol, - enum catpt_pin_id pin_id) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct catpt_stream_runtime *stream; - struct catpt_dev *cdev = dev_get_drvdata(component->dev); - long *ctlvol = (long *)kcontrol->private_value; - int ret, i; - - guard(mutex)(&cdev->stream_mutex); - - stream = catpt_stream_find(cdev, pin_id); - if (!stream) { - for (i = 0; i < CATPT_CHANNELS_MAX; i++) - ctlvol[i] = ucontrol->value.integer.value[i]; - return 0; - } - - ret = pm_runtime_resume_and_get(cdev->dev); - if (ret) - return ret; - - ret = catpt_set_dspvol(cdev, stream->info.stream_hw_id, - ucontrol->value.integer.value); - - pm_runtime_put_autosuspend(cdev->dev); - - if (ret) - return ret; - - for (i = 0; i < CATPT_CHANNELS_MAX; i++) - ctlvol[i] = ucontrol->value.integer.value[i]; - return 0; -} - -static int catpt_offload1_volume_get(struct snd_kcontrol *kctl, - struct snd_ctl_elem_value *uctl) -{ - return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_OFFLOAD1); -} - -static int catpt_offload1_volume_put(struct snd_kcontrol *kctl, - struct snd_ctl_elem_value *uctl) -{ - return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_OFFLOAD1); -} - -static int catpt_offload2_volume_get(struct snd_kcontrol *kctl, - struct snd_ctl_elem_value *uctl) -{ - return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_OFFLOAD2); -} - -static int catpt_offload2_volume_put(struct snd_kcontrol *kctl, - struct snd_ctl_elem_value *uctl) -{ - return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_OFFLOAD2); -} - -static int catpt_capture_volume_get(struct snd_kcontrol *kctl, - struct snd_ctl_elem_value *uctl) -{ - return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_CAPTURE1); -} - -static int catpt_capture_volume_put(struct snd_kcontrol *kctl, - struct snd_ctl_elem_value *uctl) -{ - return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_CAPTURE1); -} - -static int catpt_loopback_switch_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - ucontrol->value.integer.value[0] = *(bool *)kcontrol->private_value; - return 0; -} - -static int catpt_loopback_switch_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct catpt_stream_runtime *stream; - struct catpt_dev *cdev = dev_get_drvdata(component->dev); - bool mute; - int ret; - - guard(mutex)(&cdev->stream_mutex); - - mute = (bool)ucontrol->value.integer.value[0]; - stream = catpt_stream_find(cdev, CATPT_PIN_ID_REFERENCE); - if (!stream) { - *(bool *)kcontrol->private_value = mute; - return 0; - } - - ret = pm_runtime_resume_and_get(cdev->dev); - if (ret) - return ret; - - ret = catpt_ipc_mute_loopback(cdev, stream->info.stream_hw_id, mute); - - pm_runtime_put_autosuspend(cdev->dev); - - if (ret) - return CATPT_IPC_RET(ret); - - *(bool *)kcontrol->private_value = mute; - return 0; -} - static int catpt_waves_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -1219,7 +1000,7 @@ static int catpt_waves_param_put(struct snd_kcontrol *kcontrol, static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(catpt_volume_tlv, -9000, 300, 1); -#define CATPT_VOLUME_CTL2(kname, pname) { \ +#define CATPT_VOLUME_CTL(kname, pname) { \ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ .name = kname, \ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ @@ -1232,27 +1013,15 @@ static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(catpt_volume_tlv, -9000, 300, 1); &(struct catpt_control_data) { CATPT_PIN_ID_##pname } \ } -#define CATPT_VOLUME_CTL(kname, sname) \ -{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ - .name = (kname), \ - .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ - SNDRV_CTL_ELEM_ACCESS_READWRITE, \ - .info = catpt_volume_info, \ - .get = catpt_##sname##_volume_get, \ - .put = catpt_##sname##_volume_put, \ - .tlv.p = catpt_volume_tlv, \ - .private_value = (unsigned long) \ - &(long[CATPT_CHANNELS_MAX]) {0} } - static const struct snd_kcontrol_new component_kcontrols[] = { /* Master volume (mixer stream) */ -CATPT_VOLUME_CTL("Master Playback Volume", mixer), +CATPT_VOLUME_CTL("Master Playback Volume", MIXER), /* Individual volume controls for offload and capture */ -CATPT_VOLUME_CTL("Media0 Playback Volume", offload1), -CATPT_VOLUME_CTL("Media1 Playback Volume", offload2), -CATPT_VOLUME_CTL("Mic Capture Volume", capture), +CATPT_VOLUME_CTL("Media0 Playback Volume", OFFLOAD1), +CATPT_VOLUME_CTL("Media1 Playback Volume", OFFLOAD2), +CATPT_VOLUME_CTL("Mic Capture Volume", CAPTURE1), SOC_SINGLE_BOOL_EXT("Loopback Mute", (unsigned long)&(bool[1]) {0}, - catpt_loopback_switch_get, catpt_loopback_switch_put), + catpt_loopback_mute_get, catpt_loopback_mute_put), /* Enable or disable WAVES module */ SOC_SINGLE_BOOL_EXT("Waves Switch", 0, catpt_waves_switch_get, catpt_waves_switch_put), From 48863104d2e1ea70a68cbd8d87c0d75270f3c6e5 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 11 Mar 2026 14:21:53 +0000 Subject: [PATCH 096/359] ASoC: cs35l56: Support clock stop mode 1 if enabled in ACPI Report the ability to support SoundWire clock-stop mode 1 if this is enabled in ACPI. Mode 1 allows the device to lose state, so it can reduce power consumption in clock-stop. Also add the necessary handling to wait for re-enumeration on resume. This does not use sdw_slave_read_prop(), because that also fills in other properties from ACPI that were not previously set by the driver and this has been observed to break some systems. Instead, the "mipi-sdw-clock-stop-mode1-supported" property is checked directly. When a SoundWire peripheral has been put into clock-stop mode 1 it must be re-enumerated after the clock is restarted. A new flag sdw_in_clock_stop_1 is set to true in cs35l56_sdw_clk_stop() if the SoundWire core notifies that it is entering clock-stop 1. cs35l56_sdw_handle_unattach() will wait for re-enumeration if sdw_in_clock_stop_1 is true. sdw_in_clock_stop_1 will be reset to false when an ATTACH notification is received in cs35l56_sdw_update_status(). Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260311142153.2201761-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56-sdw.c | 34 +++++++++++++++++++++++++++++----- sound/soc/codecs/cs35l56.h | 1 + 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c index 30b3192d6ce9..9dc47fec1ea0 100644 --- a/sound/soc/codecs/cs35l56-sdw.c +++ b/sound/soc/codecs/cs35l56-sdw.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -340,6 +341,14 @@ static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); struct sdw_slave_prop *prop = &peripheral->prop; struct sdw_dpn_prop *ports; + u8 clock_stop_1 = false; + int ret; + + ret = fwnode_property_read_u8(dev_fwnode(cs35l56->base.dev), + "mipi-sdw-clock-stop-mode1-supported", + &clock_stop_1); + if (ret == 0) + prop->clk_stop_mode1 = !!clock_stop_1; ports = devm_kcalloc(cs35l56->base.dev, 2, sizeof(*ports), GFP_KERNEL); if (!ports) @@ -363,6 +372,9 @@ static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) ports[1].ch_prep_timeout = 10; prop->src_dpn_prop = &ports[1]; + dev_dbg(&peripheral->dev, "clock stop mode 1 supported: %s\n", + str_yes_no(prop->clk_stop_mode1)); + return 0; } @@ -374,6 +386,7 @@ static int cs35l56_sdw_update_status(struct sdw_slave *peripheral, switch (status) { case SDW_SLAVE_ATTACHED: dev_dbg(cs35l56->base.dev, "%s: ATTACHED\n", __func__); + cs35l56->sdw_in_clock_stop_1 = false; if (cs35l56->sdw_attached) break; @@ -399,25 +412,35 @@ static int __maybe_unused cs35l56_sdw_clk_stop(struct sdw_slave *peripheral, { struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); - dev_dbg(cs35l56->base.dev, "%s: mode:%d type:%d\n", __func__, mode, type); + dev_dbg(cs35l56->base.dev, "%s: clock_stop_mode%d stage:%d\n", __func__, mode, type); - return 0; + switch (type) { + case SDW_CLK_POST_PREPARE: + if (mode == SDW_CLK_STOP_MODE1) + cs35l56->sdw_in_clock_stop_1 = true; + else + cs35l56->sdw_in_clock_stop_1 = false; + return 0; + default: + return 0; + } } static const struct sdw_slave_ops cs35l56_sdw_ops = { .read_prop = cs35l56_sdw_read_prop, .interrupt_callback = cs35l56_sdw_interrupt, .update_status = cs35l56_sdw_update_status, -#ifdef DEBUG .clk_stop = cs35l56_sdw_clk_stop, -#endif }; static int __maybe_unused cs35l56_sdw_handle_unattach(struct cs35l56_private *cs35l56) { struct sdw_slave *peripheral = cs35l56->sdw_peripheral; - if (peripheral->unattach_request) { + dev_dbg(cs35l56->base.dev, "attached:%u unattach_request:%u in_clock_stop_1:%u\n", + cs35l56->sdw_attached, peripheral->unattach_request, cs35l56->sdw_in_clock_stop_1); + + if (cs35l56->sdw_in_clock_stop_1 || peripheral->unattach_request) { /* Cannot access registers until bus is re-initialized. */ dev_dbg(cs35l56->base.dev, "Wait for initialization_complete\n"); if (!wait_for_completion_timeout(&peripheral->initialization_complete, @@ -427,6 +450,7 @@ static int __maybe_unused cs35l56_sdw_handle_unattach(struct cs35l56_private *cs } peripheral->unattach_request = 0; + cs35l56->sdw_in_clock_stop_1 = false; /* * Don't call regcache_mark_dirty(), we can't be sure that the diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h index 747529be3e2e..36d239d571cd 100644 --- a/sound/soc/codecs/cs35l56.h +++ b/sound/soc/codecs/cs35l56.h @@ -42,6 +42,7 @@ struct cs35l56_private { bool sdw_irq_no_unmask; bool soft_resetting; bool sdw_attached; + bool sdw_in_clock_stop_1; struct completion init_completion; int speaker_id; From 46b87c37ca4f54a630d988cb24a1aa9a2e5ba1dc Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 11 Mar 2026 16:35:49 +0100 Subject: [PATCH 097/359] ASoC: dt-bindings: ti,tas2770: Switch to undeprecated reset-gpios GPIOs with "gpio" suffix were long time ago deprecated, thus "reset-gpios" should be used. Linux kernel supports both, thus this only documents desired choice of ABI. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260311153548.94265-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/ti,tas2770.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/ti,tas2770.yaml b/Documentation/devicetree/bindings/sound/ti,tas2770.yaml index 8eab98a0f7a2..8d49fbcf0b9b 100644 --- a/Documentation/devicetree/bindings/sound/ti,tas2770.yaml +++ b/Documentation/devicetree/bindings/sound/ti,tas2770.yaml @@ -30,7 +30,7 @@ properties: description: | I2C address of the device can be between 0x41 to 0x48. - reset-gpio: + reset-gpios: maxItems: 1 description: GPIO used to reset the device. @@ -82,7 +82,7 @@ examples: #sound-dai-cells = <0>; interrupt-parent = <&gpio1>; interrupts = <14>; - reset-gpio = <&gpio1 15 GPIO_ACTIVE_HIGH>; + reset-gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>; shutdown-gpios = <&gpio1 14 GPIO_ACTIVE_HIGH>; ti,imon-slot-no = <0>; ti,vmon-slot-no = <2>; From 103a7b97c63905ab4d1463bf040e27f668e6a340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Wed, 11 Mar 2026 17:00:10 -0300 Subject: [PATCH 098/359] ALSA: usb-audio: map UAC3 front wide channels in convert_chmap_v3() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit convert_chmap_v3() translates UAC3 channel relationship values into ALSA channel-map positions. UAC3_CH_FRONT_WIDE_LEFT and UAC3_CH_FRONT_WIDE_RIGHT currently fall back to SNDRV_CHMAP_UNKNOWN, although ALSA already provides matching channel-map positions via SNDRV_CHMAP_FLW and SNDRV_CHMAP_FRW. Map these two UAC3 positions to their ALSA equivalents and update the comment to clarify that unsupported UAC3 channel relationships remain reported as SNDRV_CHMAP_UNKNOWN. No functional change for other channel relationships. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260311200010.103710-1-cassiogabrielcontato@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/stream.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/usb/stream.c b/sound/usb/stream.c index d38c39e28f38..2532bf97e05e 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -366,6 +366,8 @@ snd_pcm_chmap_elem *convert_chmap_v3(struct uac3_cluster_header_descriptor /* * TODO: this conversion is not complete, update it * after adding UAC3 values to asound.h + * NOTE: not all UAC3 channel relationship have a + * direct ALSA chmap equivalent. */ switch (is->bChRelationship) { case UAC3_CH_MONO: @@ -390,6 +392,12 @@ snd_pcm_chmap_elem *convert_chmap_v3(struct uac3_cluster_header_descriptor case UAC3_CH_FRONT_RIGHT_OF_CENTER: map = SNDRV_CHMAP_FRC; break; + case UAC3_CH_FRONT_WIDE_LEFT: + map = SNDRV_CHMAP_FLW; + break; + case UAC3_CH_FRONT_WIDE_RIGHT: + map = SNDRV_CHMAP_FRW; + break; case UAC3_CH_SIDE_LEFT: map = SNDRV_CHMAP_SL; break; From 7699b12a32b99437dc270cc7ee603b608860679f Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 13 Mar 2026 16:08:57 +0000 Subject: [PATCH 099/359] ASoC: SDCA: Add support for the Cirrus Logic cs42l49 The cs42l49 is an audio codec with integrated MIPI SoundWire interface (Version 1.2.1 compliant) with SDCA v1.0 support, I2C, and I2S/TDM interfaces designed for portable applications. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260313160858.2022849-2-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_class.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c index 55c33ff63ca2..7af4e5d1b347 100644 --- a/sound/soc/sdca/sdca_class.c +++ b/sound/soc/sdca/sdca_class.c @@ -317,6 +317,7 @@ static const struct dev_pm_ops class_pm_ops = { static const struct sdw_device_id class_sdw_id[] = { SDW_SLAVE_ENTRY(0x01FA, 0x4245, 0), + SDW_SLAVE_ENTRY(0x01FA, 0x4249, 0), SDW_SLAVE_ENTRY(0x01FA, 0x4747, 0), {} }; From 5e2f19ce90d5601f4250b510ed2f41160e5692e9 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 13 Mar 2026 16:08:58 +0000 Subject: [PATCH 100/359] ASoC: sdw_utils: Add codec_info for cs42l49 The cs42l49 is an audio codec with integrated MIPI SoundWire interface (Version 1.2.1 compliant) with SDCA v1.0 support, I2C, and I2S/TDM interfaces designed for portable applications. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260313160858.2022849-3-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_utils.c | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index becc22328230..d1e98f95735b 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -813,6 +813,42 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, .aux_num = 1, }, + { + .part_id = 0x4249, + .name_prefix = "cs42l45", /* Use same user-space as cs42l45 */ + .dais = { + { + .direction = {true, false}, + .codec_name = "snd_soc_sdca.UAJ", + .dai_name = "IT 41", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .rtd_init = asoc_sdw_cs42l45_hs_rtd_init, + }, + { + .direction = {false, true}, + .codec_name = "snd_soc_sdca.SmartMic", + .dai_name = "OT 113", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_cs42l45_dmic_rtd_init, + }, + { + .direction = {false, true}, + .codec_name = "snd_soc_sdca.UAJ", + .dai_name = "OT 36", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + }, + }, + .dai_num = 3, + .auxs = { + { + .codec_name = "snd_soc_sdca.HID", + }, + }, + .aux_num = 1, + }, { .part_id = 0x4747, .name_prefix = "cs47l47", From 6d841b46ec05e31d21827821ce748ff168cbd4ee Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 14 Mar 2026 21:06:20 +0100 Subject: [PATCH 101/359] ASoC: amd: acp-da7291-max98357a: Drop unused include This driver includes the legacy header but does not use any symbols from it. Drop the inclusion. Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260314-asoc-amd-v1-1-31afed06e022@kernel.org Signed-off-by: Mark Brown --- sound/soc/amd/acp-da7219-max98357a.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/amd/acp-da7219-max98357a.c b/sound/soc/amd/acp-da7219-max98357a.c index 42aa009c4e13..3c08e6e17e41 100644 --- a/sound/soc/amd/acp-da7219-max98357a.c +++ b/sound/soc/amd/acp-da7219-max98357a.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include From 0a80eff3f53ce6fb0c75187b34a0c145c4c7167d Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 14 Mar 2026 21:06:21 +0100 Subject: [PATCH 102/359] ASoC: amd: acp-es8336: Drop unused include This driver includes the legacy header but does not use any symbols from it. Drop the inclusion. Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260314-asoc-amd-v1-2-31afed06e022@kernel.org Signed-off-by: Mark Brown --- sound/soc/amd/acp-es8336.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/amd/acp-es8336.c b/sound/soc/amd/acp-es8336.c index b16dde0e2987..9f3f11256788 100644 --- a/sound/soc/amd/acp-es8336.c +++ b/sound/soc/amd/acp-es8336.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include From d63b722361b8dff3d8bbc0f5f324367420f19f0b Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 14 Mar 2026 21:06:22 +0100 Subject: [PATCH 103/359] ASoC: amd: acp-rt5645: Drop unused include This driver includes the legacy header but does not use any symbols from it. Drop the inclusion. Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260314-asoc-amd-v1-3-31afed06e022@kernel.org Signed-off-by: Mark Brown --- sound/soc/amd/acp-rt5645.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/amd/acp-rt5645.c b/sound/soc/amd/acp-rt5645.c index 11d373169380..894f530434da 100644 --- a/sound/soc/amd/acp-rt5645.c +++ b/sound/soc/amd/acp-rt5645.c @@ -32,7 +32,6 @@ #include #include #include -#include #include #include #include From 70f0a90e718d6163543a3f7eb4f7cb582f85ae47 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 14 Mar 2026 21:06:23 +0100 Subject: [PATCH 104/359] ASoC: amd: acp3x-es83x: Drop unused include This driver includes the legacy header but does not use any symbols from it. Drop the inclusion. Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260314-asoc-amd-v1-4-31afed06e022@kernel.org Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c | 1 - 1 file changed, 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 141ea4787d99..46fff565d732 100644 --- a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c +++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include From c43988dfe25ba358b1df72201327ca719e8a369d Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 14 Mar 2026 21:06:24 +0100 Subject: [PATCH 105/359] ASoC: amd: acp3x-rt5682-max9836: Drop unused include This driver includes the legacy header but does not use any symbols from it. Drop the inclusion. Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260314-asoc-amd-v1-5-31afed06e022@kernel.org Signed-off-by: Mark Brown --- sound/soc/amd/acp3x-rt5682-max9836.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/amd/acp3x-rt5682-max9836.c b/sound/soc/amd/acp3x-rt5682-max9836.c index d1eb6f12a183..4affb00bdc95 100644 --- a/sound/soc/amd/acp3x-rt5682-max9836.c +++ b/sound/soc/amd/acp3x-rt5682-max9836.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include From 5273cb8197d8eea1ab83ae3227a8c2db94177195 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sun, 1 Mar 2026 18:05:20 +1000 Subject: [PATCH 106/359] ASoC: dt-bindings: convert tdm-slot to YAML This schema was still in plaintext form. Convert to YAML format. Signed-off-by: James Calligeros Link: https://patch.msgid.link/20260301-tdm-idle-slots-v3-1-c6ac5351489a@gmail.com Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/tdm-slot.txt | 29 ---------------- .../devicetree/bindings/sound/tdm-slot.yaml | 33 +++++++++++++++++++ 2 files changed, 33 insertions(+), 29 deletions(-) delete mode 100644 Documentation/devicetree/bindings/sound/tdm-slot.txt create mode 100644 Documentation/devicetree/bindings/sound/tdm-slot.yaml diff --git a/Documentation/devicetree/bindings/sound/tdm-slot.txt b/Documentation/devicetree/bindings/sound/tdm-slot.txt deleted file mode 100644 index 4bb513ae62fc..000000000000 --- a/Documentation/devicetree/bindings/sound/tdm-slot.txt +++ /dev/null @@ -1,29 +0,0 @@ -TDM slot: - -This specifies audio DAI's TDM slot. - -TDM slot properties: -dai-tdm-slot-num : Number of slots in use. -dai-tdm-slot-width : Width in bits for each slot. -dai-tdm-slot-tx-mask : Transmit direction slot mask, optional -dai-tdm-slot-rx-mask : Receive direction slot mask, optional - -For instance: - dai-tdm-slot-num = <2>; - dai-tdm-slot-width = <8>; - dai-tdm-slot-tx-mask = <0 1>; - dai-tdm-slot-rx-mask = <1 0>; - -And for each specified driver, there could be one .of_xlate_tdm_slot_mask() -to specify an explicit mapping of the channels and the slots. If it's absent -the default snd_soc_of_xlate_tdm_slot_mask() will be used to generating the -tx and rx masks. - -For snd_soc_of_xlate_tdm_slot_mask(), the tx and rx masks will use a 1 bit -for an active slot as default, and the default active bits are at the LSB of -the masks. - -The explicit masks are given as array of integers, where the first -number presents bit-0 (LSB), second presents bit-1, etc. Any non zero -number is considered 1 and 0 is 0. snd_soc_of_xlate_tdm_slot_mask() -does not do anything, if either mask is set non zero value. diff --git a/Documentation/devicetree/bindings/sound/tdm-slot.yaml b/Documentation/devicetree/bindings/sound/tdm-slot.yaml new file mode 100644 index 000000000000..ca71e3660f62 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/tdm-slot.yaml @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/tdm-slot.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Time Division Multiplexing (TDM) Slot Parameters + +maintainers: + - Liam Girdwood + +select: false + +properties: + dai-tdm-slot-num: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Number of slots in use + + dai-tdm-slot-width: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Width, in bits, of each slot + +patternProperties: + '^dai-tdm-slot-[rt]x-mask$': + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Slot mask for active TDM slots. Optional. Drivers may + specify .xlate_tdm_slot_mask() to generate a slot mask dynamically. If + neither this property nor a driver-specific function are specified, the + default snd_soc_xlate_tdm_slot_mask() function will be used to generate + a mask. The first element of the array is slot 0 (LSB). Any nonzero + value will be treated as 1. + +additionalProperties: true From cc74890fb484a73420e46fc7be7bff8d02a82699 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sun, 1 Mar 2026 18:05:21 +1000 Subject: [PATCH 107/359] ASoC: dt-bindings: update tdm-slot.txt references to tdm-slot.yaml Ensure that all references to tdm-slot.txt have been updated to tdm-slot.yaml, and are schema-compliant. Signed-off-by: James Calligeros Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260301-tdm-idle-slots-v3-2-c6ac5351489a@gmail.com Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/imx-audio-card.yaml | 9 +++------ .../devicetree/bindings/sound/simple-card.yaml | 14 ++------------ 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/imx-audio-card.yaml b/Documentation/devicetree/bindings/sound/imx-audio-card.yaml index 3c75c8c78987..5424d4f16f52 100644 --- a/Documentation/devicetree/bindings/sound/imx-audio-card.yaml +++ b/Documentation/devicetree/bindings/sound/imx-audio-card.yaml @@ -24,6 +24,7 @@ patternProperties: cpu/codec dais. type: object + $ref: tdm-slot.yaml# properties: link-name: @@ -38,13 +39,9 @@ patternProperties: - i2s - dsp_b - dai-tdm-slot-num: - description: see tdm-slot.txt. - $ref: /schemas/types.yaml#/definitions/uint32 + dai-tdm-slot-num: true - dai-tdm-slot-width: - description: see tdm-slot.txt. - $ref: /schemas/types.yaml#/definitions/uint32 + dai-tdm-slot-width: true playback-only: description: link is used only for playback diff --git a/Documentation/devicetree/bindings/sound/simple-card.yaml b/Documentation/devicetree/bindings/sound/simple-card.yaml index 533d0a1da56e..a14716b2732f 100644 --- a/Documentation/devicetree/bindings/sound/simple-card.yaml +++ b/Documentation/devicetree/bindings/sound/simple-card.yaml @@ -27,14 +27,6 @@ definitions: description: dai-link uses bit clock inversion $ref: /schemas/types.yaml#/definitions/flag - dai-tdm-slot-num: - description: see tdm-slot.txt. - $ref: /schemas/types.yaml#/definitions/uint32 - - dai-tdm-slot-width: - description: see tdm-slot.txt. - $ref: /schemas/types.yaml#/definitions/uint32 - system-clock-frequency: description: | If a clock is specified and a multiplication factor is given with @@ -115,6 +107,8 @@ definitions: dai: type: object + $ref: tdm-slot.yaml# + properties: sound-dai: maxItems: 1 @@ -133,10 +127,6 @@ definitions: bitclock-master: $ref: /schemas/types.yaml#/definitions/flag - dai-tdm-slot-num: - $ref: "#/definitions/dai-tdm-slot-num" - dai-tdm-slot-width: - $ref: "#/definitions/dai-tdm-slot-width" clocks: maxItems: 1 system-clock-frequency: From 938c1ed56ab888b0715a8c7070dbb4e276c2d3fe Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sun, 1 Mar 2026 18:05:22 +1000 Subject: [PATCH 108/359] ASoC: dt-bindings: add TDM slot idle mode properties Add properties to describe TDM slot idle behaviour. These properties can be used to describe how a DAI is supposed to behave when not active. For example, Apple Silicon laptops split a single I2S bus between two physical lines which are combined at an OR gate in front of the receiving port. One codec on each line is expected to transmit zeroes during the active TDM slots of the opposite line, or we will have corrupted data at the receiver. Signed-off-by: James Calligeros Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260301-tdm-idle-slots-v3-3-c6ac5351489a@gmail.com Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/tdm-slot.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/tdm-slot.yaml b/Documentation/devicetree/bindings/sound/tdm-slot.yaml index ca71e3660f62..457a899e8872 100644 --- a/Documentation/devicetree/bindings/sound/tdm-slot.yaml +++ b/Documentation/devicetree/bindings/sound/tdm-slot.yaml @@ -20,6 +20,20 @@ properties: $ref: /schemas/types.yaml#/definitions/uint32 description: Width, in bits, of each slot + dai-tdm-idle-mode: + $ref: /schemas/types.yaml#/definitions/string + enum: + - none + - off + - zero + - pulldown + - hiz + - pullup + - drivehigh + description: Drive mode for inactive/idle TDM slots. For hardware that + implements .set_tdm_idle(). Optional. "None" represents undefined + behaviour and is the same as not setting this property. + patternProperties: '^dai-tdm-slot-[rt]x-mask$': $ref: /schemas/types.yaml#/definitions/uint32-array @@ -30,4 +44,9 @@ patternProperties: a mask. The first element of the array is slot 0 (LSB). Any nonzero value will be treated as 1. + '^dai-tdm-slot-[rt]x-idle-mask$': + $ref: /schemas/types.yaml#/definitions/uint32 + description: Idle slot mask. Optional. A bit being set to 1 indicates + that the corresponding TDM slot is inactive/idle. + additionalProperties: true From 7d8632f1ef6c8ed0b53771c16f130f18d636931e Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sun, 1 Mar 2026 18:05:23 +1000 Subject: [PATCH 109/359] ASoC: soc-dai: define possible idle TDM slot modes Some audio devices, such as certain Texas Instruments codecs, include configurable bus keepers. We currently don't have a standardised way to configure such hardware, and instead rely on the hardware initialising setting itself up into a sane state. There are situations where this is insufficient, however, and some platforms require more concrete guarantees as to the state of the bus, and being able to explicitly configure bus keepers enables this. For example, some Apple Silicon machines have an odd bus topology where the SDOUT pins of all codecs are split across two data lines, which are summed via an OR gate in front of the receiving port on the SoC's I2S peripheral. Each line must transmit 0 while a codec on the other line is actively transmitting data, or the SoC will receive garbage data. To do this, one codec on each line must be configured to transmit zeroes during the other line's active TDM slots. Thus, we define seven possible bus-keeping modes that a device can be in: NONE (UB/as initialised), OFF (explicitly disabled), ZERO (actively transmit a 0), PULLDOWN, HIZ (floating), PULLUP, and DRIVE_HIGH. These will be consumed by CODEC/CPU drivers via a common DAI op, enabling the explicit configuration of bus keepers where required. Signed-off-by: James Calligeros Link: https://patch.msgid.link/20260301-tdm-idle-slots-v3-4-c6ac5351489a@gmail.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 224396927aef..44dd06add52e 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -52,6 +52,21 @@ struct snd_compr_stream; #define SND_SOC_POSSIBLE_DAIFMT_AC97 (1 << SND_SOC_DAI_FORMAT_AC97) #define SND_SOC_POSSIBLE_DAIFMT_PDM (1 << SND_SOC_DAI_FORMAT_PDM) +/* + * DAI TDM slot idle modes + * + * Describes a CODEC/CPU's behaviour when not actively receiving or + * transmitting on a given TDM slot. NONE is undefined behaviour. + * Add new modes to the end. + */ +#define SND_SOC_DAI_TDM_IDLE_NONE 0 +#define SND_SOC_DAI_TDM_IDLE_OFF 1 +#define SND_SOC_DAI_TDM_IDLE_ZERO 2 +#define SND_SOC_DAI_TDM_IDLE_PULLDOWN 3 +#define SND_SOC_DAI_TDM_IDLE_HIZ 4 +#define SND_SOC_DAI_TDM_IDLE_PULLUP 5 +#define SND_SOC_DAI_TDM_IDLE_DRIVE_HIGH 6 + /* * DAI Clock gating. * From b758d3574e88537f9089bd757a51b35cf9675179 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sun, 1 Mar 2026 18:05:24 +1000 Subject: [PATCH 110/359] ASoC: soc-dai: add common operation to set TDM idle mode Some audio devices, like certain Texas Instruments codecs, integrate configurable bus keepers that dictate the codec's behaviour during idle TDM slots. Now that we have definitions for various idle modes, add a snd_soc_dai_set_tdm_idle() operation to control this in a standardised way. This is useful on Apple Silicon laptops, where a single I2S bus is comprised of two physical lines which are ORed just before the receiving port. When a codec on one line is transmitting, we must guarantee that the other line is low. We can achieve this by configuring one codec on each line to use its bus keeper to fill its line with zeroes during the active slots of the other line. Signed-off-by: James Calligeros Link: https://patch.msgid.link/20260301-tdm-idle-slots-v3-5-c6ac5351489a@gmail.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 7 +++++++ sound/soc/soc-dai.c | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 44dd06add52e..6a42812bba8c 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -196,6 +196,10 @@ int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt); int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width); +int snd_soc_dai_set_tdm_idle(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int tx_mode, int rx_mode); + int snd_soc_dai_set_channel_map(struct snd_soc_dai *dai, unsigned int tx_num, const unsigned int *tx_slot, unsigned int rx_num, const unsigned int *rx_slot); @@ -312,6 +316,9 @@ struct snd_soc_dai_ops { int (*set_tdm_slot)(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width); + int (*set_tdm_idle)(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int tx_mode, int rx_mode); int (*set_channel_map)(struct snd_soc_dai *dai, unsigned int tx_num, const unsigned int *tx_slot, unsigned int rx_num, const unsigned int *rx_slot); diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index a1e05307067d..2f370fda1266 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -282,6 +282,46 @@ err: } EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot); +/** + * snd_soc_dai_set_tdm_idle() - Configure a DAI's TDM idle mode + * @dai: The DAI to configure + * @tx_mask: bitmask representing idle TX slots. + * @rx_mask: bitmask representing idle RX slots. + * @tx_mode: idle mode to set for TX slots. + * @rx_mode: idle mode to set for RX slots. + * + * This function configures the DAI to handle idle TDM slots in the + * specified manner. @tx_mode and @rx_mode can be one of + * SND_SOC_DAI_TDM_IDLE_NONE, SND_SOC_DAI_TDM_IDLE_ZERO, + * SND_SOC_DAI_TDM_IDLE_PULLDOWN, or SND_SOC_DAI_TDM_IDLE_HIZ. + * SND_SOC_TDM_IDLE_NONE represents the DAI's default/unset idle slot + * handling state and could be any of the other modes depending on the + * hardware behind the DAI. It is therefore undefined behaviour when set + * explicitly. + * + * Mode and mask can be set independently for both the TX and RX direction. + * Some hardware may ignore both TX and RX masks depending on its + * capabilities. + */ +int snd_soc_dai_set_tdm_idle(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int tx_mode, int rx_mode) +{ + int ret = -EOPNOTSUPP; + + /* You can't write to the RX line */ + if (rx_mode == SND_SOC_DAI_TDM_IDLE_ZERO) + return soc_dai_ret(dai, -EINVAL); + + if (dai->driver->ops && + dai->driver->ops->set_tdm_idle) + ret = dai->driver->ops->set_tdm_idle(dai, tx_mask, rx_mask, + tx_mode, rx_mode); + + return soc_dai_ret(dai, ret); +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_idle); + /** * snd_soc_dai_set_channel_map - configure DAI audio channel map * @dai: DAI From 45573ee1f2badd1886eb6f4a736e60e3f76effe6 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sun, 1 Mar 2026 18:05:25 +1000 Subject: [PATCH 111/359] ASoC: tas2764: expose SDOUT bus keeper via set_tdm_idle operation TAS2764, and the Apple-exclusive variant SN012776, include bus keepers on the SDOUT pin that can be configured to alter the behaviour of the SDOUT pin during specified TDM slots. The chip can either leave the bus floating (default/uninitialised behaviour) or fill the specified slots with zeroes. Expose the SDOUT bus keeper and allow it to be configured using the set_tdm_idle DAI op. The mask must be cropped to only cover slots valid for the configured BCLK ratio, so introduce a set_bclk_ratio op that properly configures this. Signed-off-by: James Calligeros Link: https://patch.msgid.link/20260301-tdm-idle-slots-v3-6-c6ac5351489a@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2764.c | 95 ++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/tas2764.h | 11 +++++ 2 files changed, 106 insertions(+) diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c index 36e25e48b354..423b7073b302 100644 --- a/sound/soc/codecs/tas2764.c +++ b/sound/soc/codecs/tas2764.c @@ -44,6 +44,11 @@ struct tas2764_priv { bool dac_powered; bool unmuted; + + struct { + int tx_mode; + unsigned int tx_mask; + } idle_slot_config; }; #include "tas2764-quirks.h" @@ -509,11 +514,101 @@ static int tas2764_set_dai_tdm_slot(struct snd_soc_dai *dai, return 0; } +static int tas2764_write_sdout_idle_mask(struct tas2764_priv *tas2764, u32 mask) +{ + struct snd_soc_component *component = tas2764->component; + int i, ret; + + /* Hardware supports up to 64 slots, but we don't */ + for (i = 0; i < 4; i++) { + ret = snd_soc_component_write(component, + TAS2764_SDOUT_HIZ_1 + i, + (mask >> (i * 8)) & 0xff); + if (ret < 0) + return ret; + } + + return 0; +} + +static int tas2764_set_dai_tdm_idle(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int tx_mode, int rx_mode) +{ + struct snd_soc_component *component = dai->component; + struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component); + int ret; + + /* We don't support setting anything on SDIN */ + if (rx_mode) + return -EOPNOTSUPP; + + if (tas2764->idle_slot_config.tx_mask == tx_mask && + tas2764->idle_slot_config.tx_mode == tx_mode) + return 0; + + switch (tx_mode) { + case SND_SOC_DAI_TDM_IDLE_ZERO: + if (!tx_mask) + return -EINVAL; + + ret = tas2764_write_sdout_idle_mask(tas2764, tx_mask); + if (ret < 0) + return ret; + + ret = snd_soc_component_update_bits(component, + TAS2764_SDOUT_HIZ_9, + TAS2764_SDOUT_HIZ_9_FORCE_0_EN, + TAS2764_SDOUT_HIZ_9_FORCE_0_EN); + if (ret < 0) + return ret; + + tas2764->idle_slot_config.tx_mask = tx_mask; + tas2764->idle_slot_config.tx_mode = tx_mode; + break; + case SND_SOC_DAI_TDM_IDLE_HIZ: + case SND_SOC_DAI_TDM_IDLE_OFF: + /* HiZ mode does not support a slot mask */ + ret = tas2764_write_sdout_idle_mask(tas2764, 0); + if (ret < 0) + return ret; + + ret = snd_soc_component_update_bits(component, + TAS2764_SDOUT_HIZ_9, + TAS2764_SDOUT_HIZ_9_FORCE_0_EN, 0); + if (ret < 0) + return ret; + + tas2764->idle_slot_config.tx_mask = 0; + tas2764->idle_slot_config.tx_mode = tx_mode; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +/* The SDOUT idle slot mask must be cropped based on the BCLK ratio */ +static int tas2764_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(dai->component); + + if (!tas2764->idle_slot_config.tx_mask) + return 0; + + tas2764->idle_slot_config.tx_mask &= GENMASK((ratio / 8) - 1, 0); + + return tas2764_write_sdout_idle_mask(tas2764, tas2764->idle_slot_config.tx_mask); +} + static const struct snd_soc_dai_ops tas2764_dai_ops = { .mute_stream = tas2764_mute, .hw_params = tas2764_hw_params, .set_fmt = tas2764_set_fmt, + .set_bclk_ratio = tas2764_set_bclk_ratio, .set_tdm_slot = tas2764_set_dai_tdm_slot, + .set_tdm_idle = tas2764_set_dai_tdm_idle, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tas2764.h b/sound/soc/codecs/tas2764.h index 538290ed3d92..4494bc4889dc 100644 --- a/sound/soc/codecs/tas2764.h +++ b/sound/soc/codecs/tas2764.h @@ -126,4 +126,15 @@ #define TAS2764_BOP_CFG0 TAS2764_REG(0X0, 0x1d) +#define TAS2764_SDOUT_HIZ_1 TAS2764_REG(0x1, 0x3d) +#define TAS2764_SDOUT_HIZ_2 TAS2764_REG(0x1, 0x3e) +#define TAS2764_SDOUT_HIZ_3 TAS2764_REG(0x1, 0x3f) +#define TAS2764_SDOUT_HIZ_4 TAS2764_REG(0x1, 0x40) +#define TAS2764_SDOUT_HIZ_5 TAS2764_REG(0x1, 0x41) +#define TAS2764_SDOUT_HIZ_6 TAS2764_REG(0x1, 0x42) +#define TAS2764_SDOUT_HIZ_7 TAS2764_REG(0x1, 0x43) +#define TAS2764_SDOUT_HIZ_8 TAS2764_REG(0x1, 0x44) +#define TAS2764_SDOUT_HIZ_9 TAS2764_REG(0x1, 0x45) +#define TAS2764_SDOUT_HIZ_9_FORCE_0_EN BIT(7) + #endif /* __TAS2764__ */ From af176d0787d219d9e07272988079ebb9be8efe6a Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sun, 1 Mar 2026 18:05:26 +1000 Subject: [PATCH 112/359] ASoC: tas2770: expose SDOUT bus keeper via set_tdm_idle TAS2770 includes a bus keeper which can be used to control the behaviour of the SDOUT pin during specified TDM slots. The chip can either pull the pin to ground, actively transmit zeroes, or keep the pin floating (default/uninitialised behaviour). Expose the bus keeper via the set_tdm_idle DAI op so that it can be configured by consumers. Signed-off-by: James Calligeros Link: https://patch.msgid.link/20260301-tdm-idle-slots-v3-7-c6ac5351489a@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2770.c | 75 ++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/tas2770.h | 12 ++++++ 2 files changed, 87 insertions(+) diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c index 6f878b01716f..d4d7d056141b 100644 --- a/sound/soc/codecs/tas2770.c +++ b/sound/soc/codecs/tas2770.c @@ -492,11 +492,86 @@ static int tas2770_set_dai_tdm_slot(struct snd_soc_dai *dai, return 0; } +static int tas2770_set_dai_tdm_idle(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int tx_mode, int rx_mode) +{ + struct snd_soc_component *component = dai->component; + struct tas2770_priv *tas2770 = snd_soc_component_get_drvdata(component); + int ret; + + /* We don't support setting anything for SDIN */ + if (rx_mode) + return -EOPNOTSUPP; + + if (tas2770->idle_tx_mode == tx_mode) + return 0; + + switch (tx_mode) { + case SND_SOC_DAI_TDM_IDLE_PULLDOWN: + ret = snd_soc_component_update_bits(component, TAS2770_DIN_PD, + TAS2770_DIN_PD_SDOUT, + TAS2770_DIN_PD_SDOUT); + if (ret) + return ret; + + break; + case SND_SOC_DAI_TDM_IDLE_ZERO: + ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG4, + TAS2770_TDM_CFG_REG4_TX_KEEPER, + TAS2770_TDM_CFG_REG4_TX_KEEPER); + if (ret) + return ret; + + ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG4, + TAS2770_TDM_CFG_REG4_TX_FILL, 0); + if (ret) + return ret; + + break; + case SND_SOC_DAI_TDM_IDLE_HIZ: + ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG4, + TAS2770_TDM_CFG_REG4_TX_KEEPER, + TAS2770_TDM_CFG_REG4_TX_KEEPER); + if (ret) + return ret; + + ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG4, + TAS2770_TDM_CFG_REG4_TX_FILL, + TAS2770_TDM_CFG_REG4_TX_FILL); + if (ret) + return ret; + + break; + case SND_SOC_DAI_TDM_IDLE_OFF: + ret = snd_soc_component_update_bits(component, TAS2770_DIN_PD, + TAS2770_DIN_PD_SDOUT, 0); + if (ret) + return ret; + + ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG4, + TAS2770_TDM_CFG_REG4_TX_KEEPER, 0); + if (ret) + return ret; + + break; + + default: + return -EOPNOTSUPP; + } + + tas2770->idle_tx_mode = tx_mode; + + return 0; +} + 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, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tas2770.h b/sound/soc/codecs/tas2770.h index 3fd2e7003c50..102040b6bdf8 100644 --- a/sound/soc/codecs/tas2770.h +++ b/sound/soc/codecs/tas2770.h @@ -67,6 +67,14 @@ #define TAS2770_TDM_CFG_REG3_RXS_SHIFT 0x4 #define TAS2770_TDM_CFG_REG3_30_MASK GENMASK(3, 0) #define TAS2770_TDM_CFG_REG3_30_SHIFT 0 + /* TDM Configuration Reg4 */ +#define TAS2770_TDM_CFG_REG4 TAS2770_REG(0X0, 0x0E) +#define TAS2770_TDM_CFG_REG4_TX_LSB_CFG BIT(7) +#define TAS2770_TDM_CFG_REG4_TX_KEEPER_CFG BIT(6) +#define TAS2770_TDM_CFG_REG4_TX_KEEPER BIT(5) +#define TAS2770_TDM_CFG_REG4_TX_FILL BIT(4) +#define TAS2770_TDM_CFG_REG4_TX_OFFSET_MASK GENMASK(3, 1) +#define TAS2770_TDM_CFG_REG4_TX_EDGE_FALLING BIT(0) /* TDM Configuration Reg5 */ #define TAS2770_TDM_CFG_REG5 TAS2770_REG(0X0, 0x0F) #define TAS2770_TDM_CFG_REG5_VSNS_MASK BIT(6) @@ -115,6 +123,9 @@ #define TAS2770_TEMP_LSB TAS2770_REG(0X0, 0x2A) /* Interrupt Configuration */ #define TAS2770_INT_CFG TAS2770_REG(0X0, 0x30) + /* Data In Pull-Down */ +#define TAS2770_DIN_PD TAS2770_REG(0X0, 0x31) +#define TAS2770_DIN_PD_SDOUT BIT(7) /* Misc IRQ */ #define TAS2770_MISC_IRQ TAS2770_REG(0X0, 0x32) /* Clock Configuration */ @@ -146,6 +157,7 @@ struct tas2770_priv { int pdm_slot; bool dac_powered; bool unmuted; + int idle_tx_mode; }; #endif /* __TAS2770__ */ From fc1fbafc18a0352f1f23cdff3533fbd0276dd58e Mon Sep 17 00:00:00 2001 From: Teguh Sobirin Date: Wed, 11 Mar 2026 12:41:20 -0500 Subject: [PATCH 113/359] ASoC: codecs: aw88166: Support device specific firmware This driver currently loads firmware from a hardcoded path. Support loading device specific firmware when provided by the boot firmware. Signed-off-by: Teguh Sobirin Co-developed-by: Aaron Kling Signed-off-by: Aaron Kling Link: https://patch.msgid.link/20260311-aw88166-fw-v2-1-8ef30dae3657@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/aw88166.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/aw88166.c b/sound/soc/codecs/aw88166.c index daee4de9e3b0..ea277a940c44 100644 --- a/sound/soc/codecs/aw88166.c +++ b/sound/soc/codecs/aw88166.c @@ -1574,18 +1574,22 @@ static int aw88166_dev_init(struct aw88166 *aw88166, struct aw_container *aw_cfg static int aw88166_request_firmware_file(struct aw88166 *aw88166) { const struct firmware *cont = NULL; + const char *fw_name; int ret; aw88166->aw_pa->fw_status = AW88166_DEV_FW_FAILED; - ret = request_firmware(&cont, AW88166_ACF_FILE, aw88166->aw_pa->dev); + if (device_property_read_string(aw88166->aw_pa->dev, "firmware-name", &fw_name) < 0) + fw_name = AW88166_ACF_FILE; + + ret = request_firmware(&cont, fw_name, aw88166->aw_pa->dev); if (ret) { - dev_err(aw88166->aw_pa->dev, "request [%s] failed!\n", AW88166_ACF_FILE); + dev_err(aw88166->aw_pa->dev, "request [%s] failed!\n", fw_name); return ret; } dev_dbg(aw88166->aw_pa->dev, "loaded %s - size: %zu\n", - AW88166_ACF_FILE, cont ? cont->size : 0); + fw_name, cont ? cont->size : 0); aw88166->aw_cfg = devm_kzalloc(aw88166->aw_pa->dev, struct_size(aw88166->aw_cfg, data, cont->size), GFP_KERNEL); @@ -1599,7 +1603,7 @@ static int aw88166_request_firmware_file(struct aw88166 *aw88166) ret = aw88395_dev_load_acf_check(aw88166->aw_pa, aw88166->aw_cfg); if (ret) { - dev_err(aw88166->aw_pa->dev, "load [%s] failed!\n", AW88166_ACF_FILE); + dev_err(aw88166->aw_pa->dev, "load [%s] failed!\n", fw_name); return ret; } From cb15d8e6cbe8d085ac585016deb2e1e0107b99e5 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 14 Mar 2026 23:56:49 +0100 Subject: [PATCH 114/359] ASoC: codec: arizona: Convert to use GPIO descriptors This converts the Arizona driver to use GPIO descriptors exclusively, deletes the legacy code path an updates the in-tree user of legacy GPIO. The GPIO lines for mic detect polarity and headphone ID detection are made exclusively descriptor-oriented. The headphone ID detection could actually only be used by the legacy GPIO code, but I converted it to use a descriptor if someone would actually need it so we don't just drop useful code. The compatible "wlf,hpdet-id-gpio" is not in the device tree bindings and only intended to be used by software nodes if any. If someone insists I can try to add a binding for it, but I doubt there is any real user so it seems pointless. Signed-off-by: Linus Walleij Reviewed-by: Charles Keepax Reviewed-by: Bartosz Golaszewski Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260314-asoc-arizona-v1-1-ecc9a165307c@kernel.org Signed-off-by: Mark Brown --- arch/arm/mach-s3c/mach-crag6410-module.c | 6 +- include/linux/mfd/arizona/pdata.h | 10 --- sound/soc/codecs/arizona-jack.c | 95 ++++++++---------------- sound/soc/codecs/arizona.h | 1 + 4 files changed, 34 insertions(+), 78 deletions(-) diff --git a/arch/arm/mach-s3c/mach-crag6410-module.c b/arch/arm/mach-s3c/mach-crag6410-module.c index 4ffcf024b09d..14b0f9cc103e 100644 --- a/arch/arm/mach-s3c/mach-crag6410-module.c +++ b/arch/arm/mach-s3c/mach-crag6410-module.c @@ -239,7 +239,6 @@ static struct gpiod_lookup_table wm8994_gpiod_table = { static struct arizona_pdata wm5102_reva_pdata = { .gpio_base = CODEC_GPIO_BASE, .irq_flags = IRQF_TRIGGER_HIGH, - .micd_pol_gpio = CODEC_GPIO_BASE + 4, .micd_rate = 6, .gpio_defaults = { [2] = 0x10000, /* AIF3TXLRCLK */ @@ -265,6 +264,8 @@ static struct gpiod_lookup_table wm5102_reva_gpiod_table = { .table = { GPIO_LOOKUP("GPION", 7, "wlf,ldoena", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("arizona", 4, + "wlf,micd-pol", GPIO_ACTIVE_HIGH), { }, }, }; @@ -272,7 +273,6 @@ static struct gpiod_lookup_table wm5102_reva_gpiod_table = { static struct arizona_pdata wm5102_pdata = { .gpio_base = CODEC_GPIO_BASE, .irq_flags = IRQF_TRIGGER_HIGH, - .micd_pol_gpio = CODEC_GPIO_BASE + 2, .gpio_defaults = { [2] = 0x10000, /* AIF3TXLRCLK */ [3] = 0x4, /* OPCLK */ @@ -297,6 +297,8 @@ static struct gpiod_lookup_table wm5102_gpiod_table = { .table = { GPIO_LOOKUP("GPION", 7, "wlf,ldo1ena", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("arizona", 2, + "wlf,micd-pol", GPIO_ACTIVE_HIGH), { }, }, }; diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index f72e6d4b14a7..d465dcd8c90a 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -117,11 +117,6 @@ struct arizona_pdata { /** Check for line output with HPDET method */ bool hpdet_acc_id_line; -#ifdef CONFIG_GPIOLIB_LEGACY - /** GPIO used for mic isolation with HPDET */ - int hpdet_id_gpio; -#endif - /** Channel to use for headphone detection */ unsigned int hpdet_channel; @@ -131,11 +126,6 @@ struct arizona_pdata { /** Extra debounce timeout used during initial mic detection (ms) */ unsigned int micd_detect_debounce; -#ifdef CONFIG_GPIOLIB_LEGACY - /** GPIO for mic detection polarity */ - int micd_pol_gpio; -#endif - /** Mic detect ramp rate */ unsigned int micd_bias_start_time; diff --git a/sound/soc/codecs/arizona-jack.c b/sound/soc/codecs/arizona-jack.c index 303c1d44ebd8..a9063bac2752 100644 --- a/sound/soc/codecs/arizona-jack.c +++ b/sound/soc/codecs/arizona-jack.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -459,11 +458,6 @@ static int arizona_hpdet_do_id(struct arizona_priv *info, int *reading, bool *mic) { struct arizona *arizona = info->arizona; -#ifdef CONFIG_GPIOLIB_LEGACY - int id_gpio = arizona->pdata.hpdet_id_gpio; -#else - int id_gpio = 0; -#endif if (!arizona->pdata.hpdet_acc_id) return 0; @@ -474,9 +468,8 @@ static int arizona_hpdet_do_id(struct arizona_priv *info, int *reading, */ info->hpdet_res[info->num_hpdet_res++] = *reading; -#ifdef CONFIG_GPIOLIB_LEGACY /* Only check the mic directly if we didn't already ID it */ - if (id_gpio && info->num_hpdet_res == 1) { + if (info->hpdet_id_gpio && info->num_hpdet_res == 1) { dev_dbg(arizona->dev, "Measuring mic\n"); regmap_update_bits(arizona->regmap, @@ -486,13 +479,12 @@ static int arizona_hpdet_do_id(struct arizona_priv *info, int *reading, ARIZONA_ACCDET_MODE_HPR | info->micd_modes[0].src); - gpio_set_value_cansleep(id_gpio, 1); + gpiod_set_value_cansleep(info->hpdet_id_gpio, 1); regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, ARIZONA_HP_POLL, ARIZONA_HP_POLL); return -EAGAIN; } -#endif /* OK, got both. Now, compare... */ dev_dbg(arizona->dev, "HPDET measured %d %d\n", @@ -514,7 +506,7 @@ static int arizona_hpdet_do_id(struct arizona_priv *info, int *reading, /* * If we measure the mic as high impedance */ - if (!id_gpio || info->hpdet_res[1] > 50) { + if (!info->hpdet_id_gpio || info->hpdet_res[1] > 50) { dev_dbg(arizona->dev, "Detected mic\n"); *mic = true; info->detecting = true; @@ -533,9 +525,6 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) { struct arizona_priv *info = data; struct arizona *arizona = info->arizona; -#ifdef CONFIG_GPIOLIB_LEGACY - int id_gpio = arizona->pdata.hpdet_id_gpio; -#endif int ret, reading, state, report; bool mic = false; @@ -591,10 +580,8 @@ done: arizona_extcon_hp_clamp(info, false); -#ifdef CONFIG_GPIOLIB_LEGACY - if (id_gpio) - gpio_set_value_cansleep(id_gpio, 0); -#endif + if (info->hpdet_id_gpio) + gpiod_set_value_cansleep(info->hpdet_id_gpio, 0); /* If we have a mic then reenable MICDET */ if (state && (mic || info->mic)) @@ -1325,58 +1312,33 @@ int arizona_jack_codec_dev_probe(struct arizona_priv *info, struct device *dev) regmap_update_bits(arizona->regmap, ARIZONA_GP_SWITCH_1, ARIZONA_SW1_MODE_MASK, arizona->pdata.gpsw); -#ifdef CONFIG_GPIOLIB_LEGACY - if (pdata->micd_pol_gpio > 0) { - if (info->micd_modes[0].gpio) - mode = GPIOF_OUT_INIT_HIGH; - else - mode = GPIOF_OUT_INIT_LOW; + if (info->micd_modes[0].gpio) + mode = GPIOD_OUT_HIGH; + else + mode = GPIOD_OUT_LOW; - ret = devm_gpio_request_one(dev, pdata->micd_pol_gpio, - mode, "MICD polarity"); - if (ret != 0) { - dev_err(arizona->dev, "Failed to request GPIO%d: %d\n", - pdata->micd_pol_gpio, ret); - return ret; - } - - info->micd_pol_gpio = gpio_to_desc(pdata->micd_pol_gpio); - } else -#endif - { - if (info->micd_modes[0].gpio) - mode = GPIOD_OUT_HIGH; - else - mode = GPIOD_OUT_LOW; - - /* We can't use devm here because we need to do the get - * against the MFD device, as that is where the of_node - * will reside, but if we devm against that the GPIO - * will not be freed if the extcon driver is unloaded. - */ - info->micd_pol_gpio = gpiod_get_optional(arizona->dev, - "wlf,micd-pol", - mode); - if (IS_ERR(info->micd_pol_gpio)) { - ret = PTR_ERR(info->micd_pol_gpio); - dev_err_probe(arizona->dev, ret, "getting microphone polarity GPIO\n"); - return ret; - } + /* We can't use devm here because we need to do the get + * against the MFD device, as that is where the of_node + * will reside, but if we devm against that the GPIO + * will not be freed if the extcon driver is unloaded. + */ + info->micd_pol_gpio = gpiod_get_optional(arizona->dev, + "wlf,micd-pol", + mode); + if (IS_ERR(info->micd_pol_gpio)) { + ret = PTR_ERR(info->micd_pol_gpio); + dev_err_probe(arizona->dev, ret, "getting microphone polarity GPIO\n"); + return ret; } -#ifdef CONFIG_GPIOLIB_LEGACY - if (arizona->pdata.hpdet_id_gpio > 0) { - ret = devm_gpio_request_one(dev, arizona->pdata.hpdet_id_gpio, - GPIOF_OUT_INIT_LOW, - "HPDET"); - if (ret != 0) { - dev_err(arizona->dev, "Failed to request GPIO%d: %d\n", - arizona->pdata.hpdet_id_gpio, ret); - gpiod_put(info->micd_pol_gpio); - return ret; - } + info->hpdet_id_gpio = gpiod_get_optional(arizona->dev, + "wlf,hpdet-id-gpio", + mode); + if (IS_ERR(info->hpdet_id_gpio)) { + ret = PTR_ERR(info->hpdet_id_gpio); + dev_err_probe(arizona->dev, ret, "getting headphone detect ID GPIO\n"); + return ret; } -#endif return 0; } @@ -1385,6 +1347,7 @@ EXPORT_SYMBOL_GPL(arizona_jack_codec_dev_probe); int arizona_jack_codec_dev_remove(struct arizona_priv *info) { gpiod_put(info->micd_pol_gpio); + gpiod_put(info->hpdet_id_gpio); return 0; } EXPORT_SYMBOL_GPL(arizona_jack_codec_dev_remove); diff --git a/sound/soc/codecs/arizona.h b/sound/soc/codecs/arizona.h index ecd8890eefc1..0703182d87b3 100644 --- a/sound/soc/codecs/arizona.h +++ b/sound/soc/codecs/arizona.h @@ -100,6 +100,7 @@ struct arizona_priv { struct snd_soc_jack *jack; struct regulator *micvdd; struct gpio_desc *micd_pol_gpio; + struct gpio_desc *hpdet_id_gpio; u16 last_jackdet; From 68130eef1e0d3c1770952e738f7f8d9f340bd42d Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:24:43 +0000 Subject: [PATCH 115/359] ASoC: soc-component: re-add pcm_new()/pcm_free() Because old pcm_new()/pcm_free() didn't care about parameter component, to avoid name collisions, we have added pcm_construct()/pcm_destruct() by commit c64bfc9066007 ("ASoC: soc-core: add new pcm_construct/pcm_destruct") Because all driver switch to new pcm_construct()/pcm_destruct(), old pcm_new()/pcm_free() were remoted by commit e9067bb502787 ("ASoC: soc-component: remove snd_pcm_ops from component driver") But naming of pcm_construct()/pcm_destruct() are not goot. re-add pcm_new()/pcm_free(), and switch to use it, again. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87a4w8lde4.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 4 ++++ sound/soc/generic/audio-graph-card.c | 1 + sound/soc/soc-component.c | 10 +++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 2a2b74b24a60..0435ba376369 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -90,6 +90,10 @@ struct snd_soc_component_driver { struct snd_soc_pcm_runtime *rtd); void (*pcm_destruct)(struct snd_soc_component *component, struct snd_pcm *pcm); + int (*pcm_new)(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd); + void (*pcm_free)(struct snd_soc_component *component, + struct snd_pcm *pcm); /* component wide operations */ int (*set_sysclk)(struct snd_soc_component *component, diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c index 8a5f41704739..74e8f2ab7ffc 100644 --- a/sound/soc/generic/audio-graph-card.c +++ b/sound/soc/generic/audio-graph-card.c @@ -77,6 +77,7 @@ static bool soc_component_is_pcm(struct snd_soc_dai_link_component *dlc) struct snd_soc_dai *dai = snd_soc_find_dai_with_mutex(dlc); if (dai && (dai->component->driver->pcm_construct || + dai->component->driver->pcm_new || (dai->driver->ops && dai->driver->ops->pcm_new))) return true; diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 89f236ab3034..77ad33383974 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -1042,6 +1042,11 @@ int snd_soc_pcm_component_new(struct snd_soc_pcm_runtime *rtd) if (ret < 0) return soc_component_ret(component, ret); } + if (component->driver->pcm_new) { + ret = component->driver->pcm_new(component, rtd); + if (ret < 0) + return soc_component_ret(component, ret); + } } return 0; @@ -1055,9 +1060,12 @@ void snd_soc_pcm_component_free(struct snd_soc_pcm_runtime *rtd) if (!rtd->pcm) return; - for_each_rtd_components(rtd, i, component) + for_each_rtd_components(rtd, i, component) { if (component->driver->pcm_destruct) component->driver->pcm_destruct(component, rtd->pcm); + if (component->driver->pcm_free) + component->driver->pcm_free(component, rtd->pcm); + } } int snd_soc_pcm_component_prepare(struct snd_pcm_substream *substream) From fe33a69681e343999e18893f97bb6cd99b883992 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:24:51 +0000 Subject: [PATCH 116/359] ASoC: amd: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcm_destruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops fromcomponent driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/878qbslddx.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/amd/acp-pcm-dma.c | 2 +- sound/soc/amd/acp/acp-platform.c | 2 +- sound/soc/amd/ps/ps-pdm-dma.c | 2 +- sound/soc/amd/ps/ps-sdw-dma.c | 2 +- sound/soc/amd/raven/acp3x-pcm-dma.c | 2 +- sound/soc/amd/renoir/acp3x-pdm-dma.c | 2 +- sound/soc/amd/vangogh/acp5x-pcm-dma.c | 2 +- sound/soc/amd/yc/acp6x-pdm-dma.c | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c index c76a4bcc9645..6ad70aa0ea83 100644 --- a/sound/soc/amd/acp-pcm-dma.c +++ b/sound/soc/amd/acp-pcm-dma.c @@ -1252,7 +1252,7 @@ static const struct snd_soc_component_driver acp_asoc_platform = { .pointer = acp_dma_pointer, .delay = acp_dma_delay, .prepare = acp_dma_prepare, - .pcm_construct = acp_dma_new, + .pcm_new = acp_dma_new, }; static int acp_audio_probe(struct platform_device *pdev) diff --git a/sound/soc/amd/acp/acp-platform.c b/sound/soc/amd/acp/acp-platform.c index 88613569fd64..6b1e18b31c1c 100644 --- a/sound/soc/amd/acp/acp-platform.c +++ b/sound/soc/amd/acp/acp-platform.c @@ -321,7 +321,7 @@ static const struct snd_soc_component_driver acp_pcm_component = { .close = acp_dma_close, .hw_params = acp_dma_hw_params, .pointer = acp_dma_pointer, - .pcm_construct = acp_dma_new, + .pcm_new = acp_dma_new, .legacy_dai_naming = 1, }; diff --git a/sound/soc/amd/ps/ps-pdm-dma.c b/sound/soc/amd/ps/ps-pdm-dma.c index 7c529fc6ba99..c6cd844d458c 100644 --- a/sound/soc/amd/ps/ps-pdm-dma.c +++ b/sound/soc/amd/ps/ps-pdm-dma.c @@ -351,7 +351,7 @@ static const struct snd_soc_component_driver acp63_pdm_component = { .close = acp63_pdm_dma_close, .hw_params = acp63_pdm_dma_hw_params, .pointer = acp63_pdm_dma_pointer, - .pcm_construct = acp63_pdm_dma_new, + .pcm_new = acp63_pdm_dma_new, }; static int acp63_pdm_audio_probe(struct platform_device *pdev) diff --git a/sound/soc/amd/ps/ps-sdw-dma.c b/sound/soc/amd/ps/ps-sdw-dma.c index 366d7c4bb07e..f27ebbd21379 100644 --- a/sound/soc/amd/ps/ps-sdw-dma.c +++ b/sound/soc/amd/ps/ps-sdw-dma.c @@ -634,7 +634,7 @@ static const struct snd_soc_component_driver acp63_sdw_component = { .hw_params = acp63_sdw_dma_hw_params, .trigger = acp63_sdw_dma_trigger, .pointer = acp63_sdw_dma_pointer, - .pcm_construct = acp63_sdw_dma_new, + .pcm_new = acp63_sdw_dma_new, .use_dai_pcm_id = true, }; diff --git a/sound/soc/amd/raven/acp3x-pcm-dma.c b/sound/soc/amd/raven/acp3x-pcm-dma.c index 4529404ebd93..37ea5c572eb9 100644 --- a/sound/soc/amd/raven/acp3x-pcm-dma.c +++ b/sound/soc/amd/raven/acp3x-pcm-dma.c @@ -363,7 +363,7 @@ static const struct snd_soc_component_driver acp3x_i2s_component = { .close = acp3x_dma_close, .hw_params = acp3x_dma_hw_params, .pointer = acp3x_dma_pointer, - .pcm_construct = acp3x_dma_new, + .pcm_new = acp3x_dma_new, }; static int acp3x_audio_probe(struct platform_device *pdev) diff --git a/sound/soc/amd/renoir/acp3x-pdm-dma.c b/sound/soc/amd/renoir/acp3x-pdm-dma.c index e832c7c4b96f..e60e3821703c 100644 --- a/sound/soc/amd/renoir/acp3x-pdm-dma.c +++ b/sound/soc/amd/renoir/acp3x-pdm-dma.c @@ -376,7 +376,7 @@ static const struct snd_soc_component_driver acp_pdm_component = { .close = acp_pdm_dma_close, .hw_params = acp_pdm_dma_hw_params, .pointer = acp_pdm_dma_pointer, - .pcm_construct = acp_pdm_dma_new, + .pcm_new = acp_pdm_dma_new, .legacy_dai_naming = 1, }; diff --git a/sound/soc/amd/vangogh/acp5x-pcm-dma.c b/sound/soc/amd/vangogh/acp5x-pcm-dma.c index 6ce82cd8859b..831e30e9b042 100644 --- a/sound/soc/amd/vangogh/acp5x-pcm-dma.c +++ b/sound/soc/amd/vangogh/acp5x-pcm-dma.c @@ -357,7 +357,7 @@ static const struct snd_soc_component_driver acp5x_i2s_component = { .close = acp5x_dma_close, .hw_params = acp5x_dma_hw_params, .pointer = acp5x_dma_pointer, - .pcm_construct = acp5x_dma_new, + .pcm_new = acp5x_dma_new, }; static int acp5x_audio_probe(struct platform_device *pdev) diff --git a/sound/soc/amd/yc/acp6x-pdm-dma.c b/sound/soc/amd/yc/acp6x-pdm-dma.c index 1c8aad849916..710db721ffa4 100644 --- a/sound/soc/amd/yc/acp6x-pdm-dma.c +++ b/sound/soc/amd/yc/acp6x-pdm-dma.c @@ -346,7 +346,7 @@ static const struct snd_soc_component_driver acp6x_pdm_component = { .close = acp6x_pdm_dma_close, .hw_params = acp6x_pdm_dma_hw_params, .pointer = acp6x_pdm_dma_pointer, - .pcm_construct = acp6x_pdm_dma_new, + .pcm_new = acp6x_pdm_dma_new, .legacy_dai_naming = 1, }; From d28e193504903cb0735ff595a689ef25d2134a9a Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:25:01 +0000 Subject: [PATCH 117/359] ASoC: apple: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/877brclddm.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/apple/mca.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index c4dcb2b54591..39269cc7def6 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -1002,8 +1002,8 @@ static const struct snd_soc_component_driver mca_component = { .hw_params = mca_hw_params, .trigger = mca_trigger, .pointer = mca_pointer, - .pcm_construct = mca_pcm_new, - .pcm_destruct = mca_pcm_free, + .pcm_new = mca_pcm_new, + .pcm_free = mca_pcm_free, }; static void apple_mca_release(struct mca_data *mca) From 83cce46863b06feb83ead3742fb3d1501673c37e Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:25:10 +0000 Subject: [PATCH 118/359] ASoC: atmel: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/875x6wldde.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/atmel/atmel-pcm-pdc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/atmel/atmel-pcm-pdc.c b/sound/soc/atmel/atmel-pcm-pdc.c index 1a0c584801f0..374f93fa6e0b 100644 --- a/sound/soc/atmel/atmel-pcm-pdc.c +++ b/sound/soc/atmel/atmel-pcm-pdc.c @@ -316,7 +316,7 @@ static const struct snd_soc_component_driver atmel_soc_platform = { .prepare = atmel_pcm_prepare, .trigger = atmel_pcm_trigger, .pointer = atmel_pcm_pointer, - .pcm_construct = atmel_pcm_new, + .pcm_new = atmel_pcm_new, }; int atmel_pcm_pdc_platform_register(struct device *dev) From 8ec624ef881ce0099053363ebc8eecb4652cbdd3 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:25:18 +0000 Subject: [PATCH 119/359] ASoC: au1x: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/874imgldd6.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/au1x/dbdma2.c | 2 +- sound/soc/au1x/dma.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/au1x/dbdma2.c b/sound/soc/au1x/dbdma2.c index 3392693faeb9..3d10d6460472 100644 --- a/sound/soc/au1x/dbdma2.c +++ b/sound/soc/au1x/dbdma2.c @@ -319,7 +319,7 @@ static const struct snd_soc_component_driver au1xpsc_soc_component = { .prepare = au1xpsc_pcm_prepare, .trigger = au1xpsc_pcm_trigger, .pointer = au1xpsc_pcm_pointer, - .pcm_construct = au1xpsc_pcm_new, + .pcm_new = au1xpsc_pcm_new, }; static int au1xpsc_pcm_drvprobe(struct platform_device *pdev) diff --git a/sound/soc/au1x/dma.c b/sound/soc/au1x/dma.c index 8d91a0e0f112..026d9f36db10 100644 --- a/sound/soc/au1x/dma.c +++ b/sound/soc/au1x/dma.c @@ -296,7 +296,7 @@ static const struct snd_soc_component_driver alchemy_pcm_soc_component = { .hw_free = alchemy_pcm_hw_free, .trigger = alchemy_pcm_trigger, .pointer = alchemy_pcm_pointer, - .pcm_construct = alchemy_pcm_new, + .pcm_new = alchemy_pcm_new, }; static int alchemy_pcm_drvprobe(struct platform_device *pdev) From 3859e15cc9d88c0e7b6f9a2cefdc4cbf3d6298e5 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:25:26 +0000 Subject: [PATCH 120/359] ASoC: bcm: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/873420ldcy.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/bcm/bcm63xx-pcm-whistler.c | 2 +- sound/soc/bcm/cygnus-pcm.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/bcm/bcm63xx-pcm-whistler.c b/sound/soc/bcm/bcm63xx-pcm-whistler.c index 6a3fd0d89365..a40f6bed69c6 100644 --- a/sound/soc/bcm/bcm63xx-pcm-whistler.c +++ b/sound/soc/bcm/bcm63xx-pcm-whistler.c @@ -386,7 +386,7 @@ static const struct snd_soc_component_driver bcm63xx_soc_platform = { .prepare = bcm63xx_pcm_prepare, .trigger = bcm63xx_pcm_trigger, .pointer = bcm63xx_pcm_pointer, - .pcm_construct = bcm63xx_soc_pcm_new, + .pcm_new = bcm63xx_soc_pcm_new, }; int bcm63xx_soc_platform_probe(struct platform_device *pdev, diff --git a/sound/soc/bcm/cygnus-pcm.c b/sound/soc/bcm/cygnus-pcm.c index 4cb2fe10bcdc..dc243f2dfd32 100644 --- a/sound/soc/bcm/cygnus-pcm.c +++ b/sound/soc/bcm/cygnus-pcm.c @@ -713,7 +713,7 @@ static const struct snd_soc_component_driver cygnus_soc_platform = { .prepare = cygnus_pcm_prepare, .trigger = cygnus_pcm_trigger, .pointer = cygnus_pcm_pointer, - .pcm_construct = cygnus_dma_new, + .pcm_new = cygnus_dma_new, }; int cygnus_soc_platform_register(struct device *dev, From 2a97dfda9e53ab57f1ec1e8062ce55b29e8ae02b Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:25:33 +0000 Subject: [PATCH 121/359] ASoC: codecs: cros_ec_codec: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/871phkldcq.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/cros_ec_codec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/cros_ec_codec.c b/sound/soc/codecs/cros_ec_codec.c index f78a85b89d95..7dc5a7c3ca96 100644 --- a/sound/soc/codecs/cros_ec_codec.c +++ b/sound/soc/codecs/cros_ec_codec.c @@ -948,7 +948,7 @@ static const struct snd_soc_component_driver wov_component_driver = { .hw_params = wov_pcm_hw_params, .hw_free = wov_pcm_hw_free, .pointer = wov_pcm_pointer, - .pcm_construct = wov_pcm_new, + .pcm_new = wov_pcm_new, }; static int cros_ec_codec_platform_probe(struct platform_device *pdev) From a1f956d8519964c92a66d73ad86149ed3f0d514c Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:25:40 +0000 Subject: [PATCH 122/359] ASoC: codecs: rt5xxx-spi: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87zf48jys3.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5514-spi.c | 2 +- sound/soc/codecs/rt5677-spi.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt5514-spi.c b/sound/soc/codecs/rt5514-spi.c index 54d84581ec47..199507d12841 100644 --- a/sound/soc/codecs/rt5514-spi.c +++ b/sound/soc/codecs/rt5514-spi.c @@ -303,7 +303,7 @@ static const struct snd_soc_component_driver rt5514_spi_component = { .hw_params = rt5514_spi_hw_params, .hw_free = rt5514_spi_hw_free, .pointer = rt5514_spi_pcm_pointer, - .pcm_construct = rt5514_spi_pcm_new, + .pcm_new = rt5514_spi_pcm_new, .legacy_dai_naming = 1, }; diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c index 548180ec5762..1bcafd5f4468 100644 --- a/sound/soc/codecs/rt5677-spi.c +++ b/sound/soc/codecs/rt5677-spi.c @@ -404,7 +404,7 @@ static const struct snd_soc_component_driver rt5677_spi_dai_component = { .hw_free = rt5677_spi_hw_free, .prepare = rt5677_spi_prepare, .pointer = rt5677_spi_pcm_pointer, - .pcm_construct = rt5677_spi_pcm_new, + .pcm_new = rt5677_spi_pcm_new, .legacy_dai_naming = 1, }; From c42b65583773251ebd5a71ee38f8a74f3e5dbb13 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:25:47 +0000 Subject: [PATCH 123/359] ASoC: dwc: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87y0jsjyrw.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/dwc/dwc-pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/dwc/dwc-pcm.c b/sound/soc/dwc/dwc-pcm.c index 15ff39b1a509..bbc4ea88edc4 100644 --- a/sound/soc/dwc/dwc-pcm.c +++ b/sound/soc/dwc/dwc-pcm.c @@ -253,7 +253,7 @@ static const struct snd_soc_component_driver dw_pcm_component = { .hw_params = dw_pcm_hw_params, .trigger = dw_pcm_trigger, .pointer = dw_pcm_pointer, - .pcm_construct = dw_pcm_new, + .pcm_new = dw_pcm_new, }; int dw_pcm_register(struct platform_device *pdev) From fe8112d6d2a62eac923a6fdde274b848021e94b6 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:25:56 +0000 Subject: [PATCH 124/359] ASoC: fsl: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87wlzcjyrn.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_asrc_dma.c | 2 +- sound/soc/fsl/fsl_dma.c | 2 +- sound/soc/fsl/fsl_qmc_audio.c | 6 +++--- sound/soc/fsl/imx-pcm-fiq.c | 4 ++-- sound/soc/fsl/imx-pcm-rpmsg.c | 2 +- sound/soc/fsl/mpc5200_dma.c | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index 348b0aabfa68..5aa96af994c4 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -505,7 +505,7 @@ struct snd_soc_component_driver fsl_asrc_component = { .open = fsl_asrc_dma_startup, .close = fsl_asrc_dma_shutdown, .pointer = fsl_asrc_dma_pcm_pointer, - .pcm_construct = fsl_asrc_dma_pcm_new, + .pcm_new = fsl_asrc_dma_pcm_new, .legacy_dai_naming = 1, #ifdef CONFIG_DEBUG_FS .debugfs_prefix = "asrc", diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c index 26ddbe867b58..b120b3514fbe 100644 --- a/sound/soc/fsl/fsl_dma.c +++ b/sound/soc/fsl/fsl_dma.c @@ -860,7 +860,7 @@ static int fsl_soc_dma_probe(struct platform_device *pdev) dma->dai.hw_params = fsl_dma_hw_params; dma->dai.hw_free = fsl_dma_hw_free; dma->dai.pointer = fsl_dma_pointer; - dma->dai.pcm_construct = fsl_dma_new; + dma->dai.pcm_new = fsl_dma_new; /* Store the SSI-specific information that we need */ dma->ssi_stx_phys = res.start + REG_SSI_STX0; diff --git a/sound/soc/fsl/fsl_qmc_audio.c b/sound/soc/fsl/fsl_qmc_audio.c index 43d401ae2d03..76e014dfb6d7 100644 --- a/sound/soc/fsl/fsl_qmc_audio.c +++ b/sound/soc/fsl/fsl_qmc_audio.c @@ -54,8 +54,8 @@ struct qmc_dai_prtd { struct snd_pcm_substream *substream; }; -static int qmc_audio_pcm_construct(struct snd_soc_component *component, - struct snd_soc_pcm_runtime *rtd) +static int qmc_audio_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) { struct snd_card *card = rtd->card->snd_card; int ret; @@ -340,7 +340,7 @@ static const struct snd_soc_component_driver qmc_audio_soc_platform = { .hw_params = qmc_audio_pcm_hw_params, .trigger = qmc_audio_pcm_trigger, .pointer = qmc_audio_pcm_pointer, - .pcm_construct = qmc_audio_pcm_construct, + .pcm_new = qmc_audio_pcm_new, .of_xlate_dai_name = qmc_audio_of_xlate_dai_name, }; diff --git a/sound/soc/fsl/imx-pcm-fiq.c b/sound/soc/fsl/imx-pcm-fiq.c index d51a3de493cb..e0fb4f8fd522 100644 --- a/sound/soc/fsl/imx-pcm-fiq.c +++ b/sound/soc/fsl/imx-pcm-fiq.c @@ -273,8 +273,8 @@ static const struct snd_soc_component_driver imx_soc_component_fiq = { .prepare = snd_imx_pcm_prepare, .trigger = snd_imx_pcm_trigger, .pointer = snd_imx_pcm_pointer, - .pcm_construct = snd_imx_pcm_new, - .pcm_destruct = snd_imx_pcm_free, + .pcm_new = snd_imx_pcm_new, + .pcm_free = snd_imx_pcm_free, }; int imx_pcm_fiq_init(struct platform_device *pdev, diff --git a/sound/soc/fsl/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c index edab68ae8366..031e5272215d 100644 --- a/sound/soc/fsl/imx-pcm-rpmsg.c +++ b/sound/soc/fsl/imx-pcm-rpmsg.c @@ -624,7 +624,7 @@ static int imx_rpmsg_pcm_new(struct snd_soc_component *component, static const struct snd_soc_component_driver imx_rpmsg_soc_component = { .name = IMX_PCM_DRV_NAME, - .pcm_construct = imx_rpmsg_pcm_new, + .pcm_new = imx_rpmsg_pcm_new, .open = imx_rpmsg_pcm_open, .close = imx_rpmsg_pcm_close, .hw_params = imx_rpmsg_pcm_hw_params, diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c index a593a95aa532..ee4fb5c932ca 100644 --- a/sound/soc/fsl/mpc5200_dma.c +++ b/sound/soc/fsl/mpc5200_dma.c @@ -307,7 +307,7 @@ static const struct snd_soc_component_driver mpc5200_audio_dma_component = { .close = psc_dma_close, .pointer = psc_dma_pointer, .trigger = psc_dma_trigger, - .pcm_construct = psc_dma_new, + .pcm_new = psc_dma_new, }; int mpc5200_audio_dma_create(struct platform_device *op) From 2905b2266ac6a6bb99c9dcdb122fcf2b89b829dc Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:26:04 +0000 Subject: [PATCH 125/359] ASoC: generic: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87v7ewjyrf.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/generic/test-component.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/soc/generic/test-component.c b/sound/soc/generic/test-component.c index 2e49066dedd4..fc40d024152e 100644 --- a/sound/soc/generic/test-component.c +++ b/sound/soc/generic/test-component.c @@ -273,8 +273,8 @@ static int test_component_resume(struct snd_soc_component *component) } #define PREALLOC_BUFFER (32 * 1024) -static int test_component_pcm_construct(struct snd_soc_component *component, - struct snd_soc_pcm_runtime *rtd) +static int test_component_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) { mile_stone(component); @@ -287,8 +287,8 @@ static int test_component_pcm_construct(struct snd_soc_component *component, return 0; } -static void test_component_pcm_destruct(struct snd_soc_component *component, - struct snd_pcm *pcm) +static void test_component_pcm_free(struct snd_soc_component *component, + struct snd_pcm *pcm) { mile_stone(component); } @@ -562,7 +562,7 @@ static int test_driver_probe(struct platform_device *pdev) if (adata->is_cpu) { cdriv->name = "test_cpu"; - cdriv->pcm_construct = test_component_pcm_construct; + cdriv->pcm_new = test_component_pcm_new; cdriv->pointer = test_component_pointer; cdriv->trigger = test_component_trigger; cdriv->legacy_dai_naming = 1; @@ -597,7 +597,7 @@ static int test_driver_probe(struct platform_device *pdev) cdriv->be_hw_params_fixup = test_component_be_hw_params_fixup; if (adata->is_cpu) - cdriv->pcm_destruct = test_component_pcm_destruct; + cdriv->pcm_free = test_component_pcm_free; } i = 0; From 71a5590610063a3bff8acffa2677a00f42828343 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:26:11 +0000 Subject: [PATCH 126/359] ASoC: google: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87tsugjyr8.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/google/chv3-i2s.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/google/chv3-i2s.c b/sound/soc/google/chv3-i2s.c index 0ff24653d49f..fd12cedfe1c9 100644 --- a/sound/soc/google/chv3-i2s.c +++ b/sound/soc/google/chv3-i2s.c @@ -163,8 +163,8 @@ static int chv3_dma_close(struct snd_soc_component *component, return 0; } -static int chv3_dma_pcm_construct(struct snd_soc_component *component, - struct snd_soc_pcm_runtime *rtd) +static int chv3_dma_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) { struct chv3_i2s_dev *i2s = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)); struct snd_pcm_substream *substream; @@ -271,7 +271,7 @@ static const struct snd_soc_component_driver chv3_i2s_comp = { .name = "chv3-i2s-comp", .open = chv3_dma_open, .close = chv3_dma_close, - .pcm_construct = chv3_dma_pcm_construct, + .pcm_new = chv3_dma_pcm_new, .hw_params = chv3_dma_hw_params, .prepare = chv3_dma_prepare, .pointer = chv3_dma_pointer, From 6f2b7bd598824a4f4bccb052daa91c44e7001010 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:26:19 +0000 Subject: [PATCH 127/359] ASoC: intel: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87sea0jyr1.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/intel/atom/sst-mfld-platform-pcm.c | 2 +- sound/soc/intel/avs/pcm.c | 8 ++++---- sound/soc/intel/catpt/pcm.c | 6 +++--- sound/soc/intel/keembay/kmb_platform.c | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/sound/soc/intel/atom/sst-mfld-platform-pcm.c b/sound/soc/intel/atom/sst-mfld-platform-pcm.c index 67caea39b557..f074af2499c8 100644 --- a/sound/soc/intel/atom/sst-mfld-platform-pcm.c +++ b/sound/soc/intel/atom/sst-mfld-platform-pcm.c @@ -707,7 +707,7 @@ static const struct snd_soc_component_driver sst_soc_platform_drv = { .pointer = sst_soc_pointer, .delay = sst_soc_delay, .compress_ops = &sst_platform_compress_ops, - .pcm_construct = sst_soc_pcm_new, + .pcm_new = sst_soc_pcm_new, }; static int sst_platform_probe(struct platform_device *pdev) diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c index d53c2f76fcd4..797b9c9163b4 100644 --- a/sound/soc/intel/avs/pcm.c +++ b/sound/soc/intel/avs/pcm.c @@ -1349,8 +1349,8 @@ static int avs_component_mmap(struct snd_soc_component *component, #define MAX_PREALLOC_SIZE (32 * 1024 * 1024) -static int avs_component_construct(struct snd_soc_component *component, - struct snd_soc_pcm_runtime *rtd) +static int avs_component_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai *dai = snd_soc_rtd_to_cpu(rtd, 0); struct snd_pcm *pcm = rtd->pcm; @@ -1377,7 +1377,7 @@ static struct snd_soc_component_driver avs_component_driver = { .open = avs_component_open, .pointer = avs_component_pointer, .mmap = avs_component_mmap, - .pcm_construct = avs_component_construct, + .pcm_new = avs_component_new, .module_get_upon_open = 1, /* increment refcount when a pcm is opened */ .topology_name_prefix = "intel/avs", }; @@ -1755,7 +1755,7 @@ static struct snd_soc_component_driver avs_hda_component_driver = { .open = avs_component_hda_open, .pointer = avs_component_pointer, .mmap = avs_component_mmap, - .pcm_construct = avs_component_construct, + .pcm_new = avs_component_new, /* * hda platform component's probe() is dependent on * codec->pcm_list_head, it needs to be initialized after codec diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c index 8356dbdb2809..7b2bab12c707 100644 --- a/sound/soc/intel/catpt/pcm.c +++ b/sound/soc/intel/catpt/pcm.c @@ -603,8 +603,8 @@ static const struct snd_pcm_hardware catpt_pcm_hardware = { .buffer_bytes_max = CATPT_BUFFER_MAX_SIZE, }; -static int catpt_component_pcm_construct(struct snd_soc_component *component, - struct snd_soc_pcm_runtime *rtm) +static int catpt_component_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtm) { struct catpt_dev *cdev = dev_get_drvdata(component->dev); @@ -1056,7 +1056,7 @@ static const struct snd_soc_dapm_route component_routes[] = { static const struct snd_soc_component_driver catpt_comp_driver = { .name = "catpt-platform", - .pcm_construct = catpt_component_pcm_construct, + .pcm_new = catpt_component_pcm_new, .open = catpt_component_open, .pointer = catpt_component_pointer, diff --git a/sound/soc/intel/keembay/kmb_platform.c b/sound/soc/intel/keembay/kmb_platform.c index 4ed71d11ad77..6659e8060ef3 100644 --- a/sound/soc/intel/keembay/kmb_platform.c +++ b/sound/soc/intel/keembay/kmb_platform.c @@ -388,7 +388,7 @@ static snd_pcm_uframes_t kmb_pcm_pointer(struct snd_soc_component *component, static const struct snd_soc_component_driver kmb_component = { .name = "kmb", - .pcm_construct = kmb_platform_pcm_new, + .pcm_new = kmb_platform_pcm_new, .open = kmb_pcm_open, .trigger = kmb_pcm_trigger, .pointer = kmb_pcm_pointer, From 31447bd2149eda1032f7bb050045008ed241f166 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:26:26 +0000 Subject: [PATCH 128/359] ASoC: kirkwood: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87qzpkjyqt.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/kirkwood/kirkwood-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/kirkwood/kirkwood-dma.c b/sound/soc/kirkwood/kirkwood-dma.c index 036b42058272..bd09cb163e65 100644 --- a/sound/soc/kirkwood/kirkwood-dma.c +++ b/sound/soc/kirkwood/kirkwood-dma.c @@ -261,5 +261,5 @@ const struct snd_soc_component_driver kirkwood_soc_component = { .hw_params = kirkwood_dma_hw_params, .prepare = kirkwood_dma_prepare, .pointer = kirkwood_dma_pointer, - .pcm_construct = kirkwood_dma_new, + .pcm_new = kirkwood_dma_new, }; From 7fce3691e13140c26c50f91f205a6388de34355a Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:26:33 +0000 Subject: [PATCH 129/359] ASoC: loongson: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87pl54jyqm.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/loongson/loongson_dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/loongson/loongson_dma.c b/sound/soc/loongson/loongson_dma.c index f26b2951bc9c..a149b643175c 100644 --- a/sound/soc/loongson/loongson_dma.c +++ b/sound/soc/loongson/loongson_dma.c @@ -341,5 +341,5 @@ const struct snd_soc_component_driver loongson_i2s_component = { .trigger = loongson_pcm_trigger, .pointer = loongson_pcm_pointer, .mmap = loongson_pcm_mmap, - .pcm_construct = loongson_pcm_new, + .pcm_new = loongson_pcm_new, }; From 6f3658d6ee738bc7e8289e73f9f65aed5399dc27 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:26:40 +0000 Subject: [PATCH 130/359] ASoC: mediatek: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87o6kojyqf.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/mediatek/common/mtk-afe-platform-driver.c | 2 +- sound/soc/mediatek/mt8186/mt8186-afe-pcm.c | 2 +- sound/soc/mediatek/mt8189/mt8189-afe-pcm.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/mediatek/common/mtk-afe-platform-driver.c b/sound/soc/mediatek/common/mtk-afe-platform-driver.c index 938dd3d46b00..f2b39fc9ec81 100644 --- a/sound/soc/mediatek/common/mtk-afe-platform-driver.c +++ b/sound/soc/mediatek/common/mtk-afe-platform-driver.c @@ -149,7 +149,7 @@ static int mtk_afe_component_probe(struct snd_soc_component *component) const struct snd_soc_component_driver mtk_afe_pcm_platform = { .name = AFE_PCM_NAME, .pointer = mtk_afe_pcm_pointer, - .pcm_construct = mtk_afe_pcm_new, + .pcm_new = mtk_afe_pcm_new, .probe = mtk_afe_component_probe, }; EXPORT_SYMBOL_GPL(mtk_afe_pcm_platform); diff --git a/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c b/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c index 085e993c650d..44a521c3a610 100644 --- a/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c +++ b/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c @@ -2773,7 +2773,7 @@ static int mt8186_afe_component_probe(struct snd_soc_component *component) static const struct snd_soc_component_driver mt8186_afe_component = { .name = AFE_PCM_NAME, - .pcm_construct = mtk_afe_pcm_new, + .pcm_new = mtk_afe_pcm_new, .pointer = mtk_afe_pcm_pointer, .probe = mt8186_afe_component_probe, }; diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c index 166ece74270e..24b0c78815f6 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c @@ -2378,8 +2378,8 @@ static void mt8189_afe_pcm_free(struct snd_soc_component *component, static const struct snd_soc_component_driver mt8189_afe_component = { .name = AFE_PCM_NAME, .probe = mt8189_afe_component_probe, - .pcm_construct = mtk_afe_pcm_new, - .pcm_destruct = mt8189_afe_pcm_free, + .pcm_new = mtk_afe_pcm_new, + .pcm_free = mt8189_afe_pcm_free, .open = mt8189_afe_pcm_open, .pointer = mtk_afe_pcm_pointer, }; From 0828e050415f73f7904521629449ed313a617da8 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:26:47 +0000 Subject: [PATCH 131/359] ASoC: pxa: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ms08jyq8.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/pxa/pxa-ssp.c | 2 +- sound/soc/pxa/pxa2xx-ac97.c | 2 +- sound/soc/pxa/pxa2xx-i2s.c | 2 +- sound/soc/pxa/pxa2xx-pcm.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index c34bfa27a446..37bd8dbd541f 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -843,7 +843,7 @@ static struct snd_soc_dai_driver pxa_ssp_dai = { static const struct snd_soc_component_driver pxa_ssp_component = { .name = "pxa-ssp", - .pcm_construct = pxa2xx_soc_pcm_new, + .pcm_new = pxa2xx_soc_pcm_new, .open = pxa2xx_soc_pcm_open, .close = pxa2xx_soc_pcm_close, .hw_params = pxa2xx_soc_pcm_hw_params, diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c index 109a4958d9c0..a0c672602918 100644 --- a/sound/soc/pxa/pxa2xx-ac97.c +++ b/sound/soc/pxa/pxa2xx-ac97.c @@ -198,7 +198,7 @@ static struct snd_soc_dai_driver pxa_ac97_dai_driver[] = { static const struct snd_soc_component_driver pxa_ac97_component = { .name = "pxa-ac97", - .pcm_construct = pxa2xx_soc_pcm_new, + .pcm_new = pxa2xx_soc_pcm_new, .open = pxa2xx_soc_pcm_open, .close = pxa2xx_soc_pcm_close, .hw_params = pxa2xx_soc_pcm_hw_params, diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index 849fbf176a70..f6ada6cffc88 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -356,7 +356,7 @@ static struct snd_soc_dai_driver pxa_i2s_dai = { static const struct snd_soc_component_driver pxa_i2s_component = { .name = "pxa-i2s", - .pcm_construct = pxa2xx_soc_pcm_new, + .pcm_new = pxa2xx_soc_pcm_new, .open = pxa2xx_soc_pcm_open, .close = pxa2xx_soc_pcm_close, .hw_params = pxa2xx_soc_pcm_hw_params, diff --git a/sound/soc/pxa/pxa2xx-pcm.c b/sound/soc/pxa/pxa2xx-pcm.c index 9d6c41f775e5..ff0fbb61dccd 100644 --- a/sound/soc/pxa/pxa2xx-pcm.c +++ b/sound/soc/pxa/pxa2xx-pcm.c @@ -18,7 +18,7 @@ #include static const struct snd_soc_component_driver pxa2xx_soc_platform = { - .pcm_construct = pxa2xx_soc_pcm_new, + .pcm_new = pxa2xx_soc_pcm_new, .open = pxa2xx_soc_pcm_open, .close = pxa2xx_soc_pcm_close, .hw_params = pxa2xx_soc_pcm_hw_params, From df202b5e6d3e9a56a9b9c21666105ec869fa8093 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:26:54 +0000 Subject: [PATCH 132/359] ASoC: qcom: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ldfsjyq1.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/qcom/lpass-platform.c | 2 +- sound/soc/qcom/qdsp6/q6apm-dai.c | 2 +- sound/soc/qcom/qdsp6/q6asm-dai.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c index ce6896cc015d..e162627d6f86 100644 --- a/sound/soc/qcom/lpass-platform.c +++ b/sound/soc/qcom/lpass-platform.c @@ -1268,7 +1268,7 @@ static const struct snd_soc_component_driver lpass_component_driver = { .trigger = lpass_platform_pcmops_trigger, .pointer = lpass_platform_pcmops_pointer, .mmap = lpass_platform_pcmops_mmap, - .pcm_construct = lpass_platform_pcm_new, + .pcm_new = lpass_platform_pcm_new, .suspend = lpass_platform_pcmops_suspend, .resume = lpass_platform_pcmops_resume, .copy = lpass_platform_copy, diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index 168c166c960d..306e928e7b49 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -831,7 +831,7 @@ static const struct snd_soc_component_driver q6apm_fe_dai_component = { .open = q6apm_dai_open, .close = q6apm_dai_close, .prepare = q6apm_dai_prepare, - .pcm_construct = q6apm_dai_pcm_new, + .pcm_new = q6apm_dai_pcm_new, .hw_params = q6apm_dai_hw_params, .pointer = q6apm_dai_pointer, .trigger = q6apm_dai_trigger, diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c index 9e3d176f50c2..4f8f7db6c3d3 100644 --- a/sound/soc/qcom/qdsp6/q6asm-dai.c +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -1224,7 +1224,7 @@ static const struct snd_soc_component_driver q6asm_fe_dai_component = { .trigger = q6asm_dai_trigger, .ack = q6asm_dai_ack, .pointer = q6asm_dai_pointer, - .pcm_construct = q6asm_dai_pcm_new, + .pcm_new = q6asm_dai_pcm_new, .compress_ops = &q6asm_dai_compress_ops, .dapm_widgets = q6asm_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(q6asm_dapm_widgets), From 2840266185be2c4c350c930b7f48da962ee16ede Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:26:59 +0000 Subject: [PATCH 133/359] ASoC: renesas: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87jyvcjypw.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/renesas/dma-sh7760.c | 2 +- sound/soc/renesas/fsi.c | 2 +- sound/soc/renesas/rcar/msiof.c | 2 +- sound/soc/renesas/rz-ssi.c | 2 +- sound/soc/renesas/siu_pcm.c | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/soc/renesas/dma-sh7760.c b/sound/soc/renesas/dma-sh7760.c index c53539482c20..ca128ea903da 100644 --- a/sound/soc/renesas/dma-sh7760.c +++ b/sound/soc/renesas/dma-sh7760.c @@ -310,7 +310,7 @@ static const struct snd_soc_component_driver sh7760_soc_component = { .prepare = camelot_prepare, .trigger = camelot_trigger, .pointer = camelot_pos, - .pcm_construct = camelot_pcm_new, + .pcm_new = camelot_pcm_new, }; static int sh7760_soc_platform_probe(struct platform_device *pdev) diff --git a/sound/soc/renesas/fsi.c b/sound/soc/renesas/fsi.c index 1491c2f2cc96..8cbd7acc26f4 100644 --- a/sound/soc/renesas/fsi.c +++ b/sound/soc/renesas/fsi.c @@ -1817,7 +1817,7 @@ static const struct snd_soc_component_driver fsi_soc_component = { .name = "fsi", .open = fsi_pcm_open, .pointer = fsi_pointer, - .pcm_construct = fsi_pcm_new, + .pcm_new = fsi_pcm_new, }; /* diff --git a/sound/soc/renesas/rcar/msiof.c b/sound/soc/renesas/rcar/msiof.c index f2addfbac923..2671abc028cc 100644 --- a/sound/soc/renesas/rcar/msiof.c +++ b/sound/soc/renesas/rcar/msiof.c @@ -514,7 +514,7 @@ static const struct snd_soc_component_driver msiof_component_driver = { .open = msiof_open, .close = msiof_close, .pointer = msiof_pointer, - .pcm_construct = msiof_new, + .pcm_new = msiof_new, .trigger = msiof_trigger, .hw_params = msiof_hw_params, }; diff --git a/sound/soc/renesas/rz-ssi.c b/sound/soc/renesas/rz-ssi.c index 74e078c04150..71e434cfe07b 100644 --- a/sound/soc/renesas/rz-ssi.c +++ b/sound/soc/renesas/rz-ssi.c @@ -1117,7 +1117,7 @@ static const struct snd_soc_component_driver rz_ssi_soc_component = { .name = "rz-ssi", .open = rz_ssi_pcm_open, .pointer = rz_ssi_pcm_pointer, - .pcm_construct = rz_ssi_pcm_new, + .pcm_new = rz_ssi_pcm_new, .legacy_dai_naming = 1, }; diff --git a/sound/soc/renesas/siu_pcm.c b/sound/soc/renesas/siu_pcm.c index f15ff36e7934..ae5c0c40299d 100644 --- a/sound/soc/renesas/siu_pcm.c +++ b/sound/soc/renesas/siu_pcm.c @@ -546,8 +546,8 @@ const struct snd_soc_component_driver siu_component = { .prepare = siu_pcm_prepare, .trigger = siu_pcm_trigger, .pointer = siu_pcm_pointer_dma, - .pcm_construct = siu_pcm_new, - .pcm_destruct = siu_pcm_free, + .pcm_new = siu_pcm_new, + .pcm_free = siu_pcm_free, .legacy_dai_naming = 1, }; EXPORT_SYMBOL_GPL(siu_component); From 7ef8b7a10c4b20dd84db7d3a066d90949eb65882 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:27:05 +0000 Subject: [PATCH 134/359] ASoC: samsung: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ikawjypq.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/samsung/idma.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/samsung/idma.c b/sound/soc/samsung/idma.c index beb7e09e9fac..cb455ddce253 100644 --- a/sound/soc/samsung/idma.c +++ b/sound/soc/samsung/idma.c @@ -399,8 +399,8 @@ static const struct snd_soc_component_driver asoc_idma_platform = { .hw_params = idma_hw_params, .hw_free = idma_hw_free, .prepare = idma_prepare, - .pcm_construct = idma_new, - .pcm_destruct = idma_free, + .pcm_new = idma_new, + .pcm_free = idma_free, }; static int asoc_idma_platform_probe(struct platform_device *pdev) From 7d803acf6181e2ec98b0c48710c800b3167f00e0 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:27:11 +0000 Subject: [PATCH 135/359] ASoC: soc-generic-dmaengine-pcm: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87h5qgjypk.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-generic-dmaengine-pcm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index dbec46703b35..6b8c65763c82 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -332,7 +332,7 @@ static const struct snd_soc_component_driver dmaengine_pcm_component = { .hw_params = dmaengine_pcm_hw_params, .trigger = dmaengine_pcm_trigger, .pointer = dmaengine_pcm_pointer, - .pcm_construct = dmaengine_pcm_new, + .pcm_new = dmaengine_pcm_new, .sync_stop = dmaengine_pcm_sync_stop, }; @@ -345,7 +345,7 @@ static const struct snd_soc_component_driver dmaengine_pcm_component_process = { .trigger = dmaengine_pcm_trigger, .pointer = dmaengine_pcm_pointer, .copy = dmaengine_copy, - .pcm_construct = dmaengine_pcm_new, + .pcm_new = dmaengine_pcm_new, .sync_stop = dmaengine_pcm_sync_stop, }; From e56decec6baea73a5871229889a08b64783fa0d9 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:27:16 +0000 Subject: [PATCH 136/359] ASoC: sof: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87fr60jypf.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/sof/pcm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 16c194b4851b..d3af30398305 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -651,7 +651,7 @@ static int sof_pcm_new(struct snd_soc_component *component, return 0; } - dev_dbg(spcm->scomp->dev, "pcm%u (%s): Entry: pcm_construct\n", + dev_dbg(spcm->scomp->dev, "pcm%u (%s): Entry: pcm_new\n", le32_to_cpu(spcm->pcm.pcm_id), spcm->pcm.pcm_name); /* do we need to pre-allocate playback audio buffer pages */ @@ -850,7 +850,7 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) pd->compress_ops = &sof_compressed_ops; #endif - pd->pcm_construct = sof_pcm_new; + pd->pcm_new = sof_pcm_new; pd->ignore_machine = drv_name; pd->be_pcm_base = SOF_BE_PCM_BASE; pd->use_dai_pcm_id = true; From 90a2dbecc20fc20f365d3e080cbcf251a8a33873 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:27:22 +0000 Subject: [PATCH 137/359] ASoC: sprd: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87eclkjyp9.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/sprd/sprd-pcm-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/sprd/sprd-pcm-dma.c b/sound/soc/sprd/sprd-pcm-dma.c index d6b96cc2f708..cbf5bf82d96e 100644 --- a/sound/soc/sprd/sprd-pcm-dma.c +++ b/sound/soc/sprd/sprd-pcm-dma.c @@ -453,7 +453,7 @@ static const struct snd_soc_component_driver sprd_soc_component = { .hw_free = sprd_pcm_hw_free, .trigger = sprd_pcm_trigger, .pointer = sprd_pcm_pointer, - .pcm_construct = sprd_pcm_new, + .pcm_new = sprd_pcm_new, .compress_ops = &sprd_platform_compress_ops, }; From 4e2f7ecb5db3223c9dcdb8d433038b89163123aa Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:27:27 +0000 Subject: [PATCH 138/359] ASoC: stm: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87cy14jyp4.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/stm/stm32_adfsdm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/stm/stm32_adfsdm.c b/sound/soc/stm/stm32_adfsdm.c index 1797a91fea7a..0f6d32814c22 100644 --- a/sound/soc/stm/stm32_adfsdm.c +++ b/sound/soc/stm/stm32_adfsdm.c @@ -315,7 +315,7 @@ static const struct snd_soc_component_driver stm32_adfsdm_soc_platform = { .hw_params = stm32_adfsdm_pcm_hw_params, .trigger = stm32_adfsdm_trigger, .pointer = stm32_adfsdm_pcm_pointer, - .pcm_construct = stm32_adfsdm_pcm_new, + .pcm_new = stm32_adfsdm_pcm_new, }; static const struct of_device_id stm32_adfsdm_of_match[] = { From 1f7fc5f1f084af6fcb4c42ab8bcc9d46ef5d1f36 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:27:33 +0000 Subject: [PATCH 139/359] ASoC: tegra: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87bjgojyoy.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_admaif.c | 6 +++--- sound/soc/tegra/tegra_pcm.c | 6 +++--- sound/soc/tegra/tegra_pcm.h | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sound/soc/tegra/tegra210_admaif.c b/sound/soc/tegra/tegra210_admaif.c index 0976779d29f2..ecb095526662 100644 --- a/sound/soc/tegra/tegra210_admaif.c +++ b/sound/soc/tegra/tegra210_admaif.c @@ -839,7 +839,7 @@ static struct snd_kcontrol_new tegra264_admaif_controls[] = { static const struct snd_soc_component_driver tegra210_admaif_cmpnt = { .controls = tegra210_admaif_controls, .num_controls = ARRAY_SIZE(tegra210_admaif_controls), - .pcm_construct = tegra_pcm_construct, + .pcm_new = tegra_pcm_new, .open = tegra_pcm_open, .close = tegra_pcm_close, .hw_params = tegra_pcm_hw_params, @@ -849,7 +849,7 @@ static const struct snd_soc_component_driver tegra210_admaif_cmpnt = { static const struct snd_soc_component_driver tegra186_admaif_cmpnt = { .controls = tegra186_admaif_controls, .num_controls = ARRAY_SIZE(tegra186_admaif_controls), - .pcm_construct = tegra_pcm_construct, + .pcm_new = tegra_pcm_new, .open = tegra_pcm_open, .close = tegra_pcm_close, .hw_params = tegra_pcm_hw_params, @@ -859,7 +859,7 @@ static const struct snd_soc_component_driver tegra186_admaif_cmpnt = { static const struct snd_soc_component_driver tegra264_admaif_cmpnt = { .controls = tegra264_admaif_controls, .num_controls = ARRAY_SIZE(tegra264_admaif_controls), - .pcm_construct = tegra_pcm_construct, + .pcm_new = tegra_pcm_new, .open = tegra_pcm_open, .close = tegra_pcm_close, .hw_params = tegra_pcm_hw_params, diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c index 05d59e03b1c5..c490a9e66858 100644 --- a/sound/soc/tegra/tegra_pcm.c +++ b/sound/soc/tegra/tegra_pcm.c @@ -204,8 +204,8 @@ static int tegra_pcm_dma_allocate(struct device *dev, struct snd_soc_pcm_runtime return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_WC, dev, size); } -int tegra_pcm_construct(struct snd_soc_component *component, - struct snd_soc_pcm_runtime *rtd) +int tegra_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) { struct device *dev = component->dev; @@ -218,7 +218,7 @@ int tegra_pcm_construct(struct snd_soc_component *component, return tegra_pcm_dma_allocate(dev, rtd, tegra_pcm_hardware.buffer_bytes_max); } -EXPORT_SYMBOL_GPL(tegra_pcm_construct); +EXPORT_SYMBOL_GPL(tegra_pcm_new); MODULE_AUTHOR("Stephen Warren "); MODULE_DESCRIPTION("Tegra PCM ASoC driver"); diff --git a/sound/soc/tegra/tegra_pcm.h b/sound/soc/tegra/tegra_pcm.h index 2a36eea1740d..ad69ca9233da 100644 --- a/sound/soc/tegra/tegra_pcm.h +++ b/sound/soc/tegra/tegra_pcm.h @@ -20,8 +20,8 @@ #include #include -int tegra_pcm_construct(struct snd_soc_component *component, - struct snd_soc_pcm_runtime *rtd); +int tegra_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd); int tegra_pcm_open(struct snd_soc_component *component, struct snd_pcm_substream *substream); int tegra_pcm_close(struct snd_soc_component *component, From 88e277ef789a67a3ee5fb3de8757609a08e85404 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:27:38 +0000 Subject: [PATCH 140/359] ASoC: uniphier: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87a4w8jyot.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/uniphier/aio-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/uniphier/aio-dma.c b/sound/soc/uniphier/aio-dma.c index 265d61723e99..c1ca55997103 100644 --- a/sound/soc/uniphier/aio-dma.c +++ b/sound/soc/uniphier/aio-dma.c @@ -226,7 +226,7 @@ static const struct snd_soc_component_driver uniphier_soc_platform = { .trigger = uniphier_aiodma_trigger, .pointer = uniphier_aiodma_pointer, .mmap = uniphier_aiodma_mmap, - .pcm_construct = uniphier_aiodma_new, + .pcm_new = uniphier_aiodma_new, .compress_ops = &uniphier_aio_compress_ops, }; From 7649b2f1f8b207716e42e4446b88de3691cfe9aa Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:27:44 +0000 Subject: [PATCH 141/359] ASoC: xilinx: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/878qbsjyon.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/xilinx/xlnx_formatter_pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/xilinx/xlnx_formatter_pcm.c b/sound/soc/xilinx/xlnx_formatter_pcm.c index 04a4eae1bc92..8f7a76758535 100644 --- a/sound/soc/xilinx/xlnx_formatter_pcm.c +++ b/sound/soc/xilinx/xlnx_formatter_pcm.c @@ -582,7 +582,7 @@ static const struct snd_soc_component_driver xlnx_asoc_component = { .hw_params = xlnx_formatter_pcm_hw_params, .trigger = xlnx_formatter_pcm_trigger, .pointer = xlnx_formatter_pcm_pointer, - .pcm_construct = xlnx_formatter_pcm_new, + .pcm_new = xlnx_formatter_pcm_new, }; static int xlnx_formatter_pcm_probe(struct platform_device *pdev) From 667fb65f5164e190b3c30c76be113cceb7260bf6 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:27:50 +0000 Subject: [PATCH 142/359] ASoC: xtensa: name back to pcm_new()/pcm_free() We have been used pcm_new()/pcm_free(), but switched to pcm_construct()/pcm_destruct() to use extra parameters [1]. pcm_new()/free() had been removed [2], but each drivers are still using such function naming. Let's name back to pcm_new()/pcm_free() again. [1] commit c64bfc906600 ("ASoC: soc-core: add new pcm_construct/pcmdestruct") [2] commit e9067bb50278 ("ASoC: soc-component: remove snd_pcm_ops from component driver") Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/877brcjyoh.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/xtensa/xtfpga-i2s.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/xtensa/xtfpga-i2s.c b/sound/soc/xtensa/xtfpga-i2s.c index 678ded059b95..9ad86c54e3ea 100644 --- a/sound/soc/xtensa/xtfpga-i2s.c +++ b/sound/soc/xtensa/xtfpga-i2s.c @@ -481,7 +481,7 @@ static const struct snd_soc_component_driver xtfpga_i2s_component = { .hw_params = xtfpga_pcm_hw_params, .trigger = xtfpga_pcm_trigger, .pointer = xtfpga_pcm_pointer, - .pcm_construct = xtfpga_pcm_new, + .pcm_new = xtfpga_pcm_new, .legacy_dai_naming = 1, }; From 175f733325ac2ce875cafd051980be2d2c06dec9 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 16 Mar 2026 02:27:57 +0000 Subject: [PATCH 143/359] ASoC: soc-component: remove pcm_construct()/pcm_destruct() All driver have switched to use pcm_new()/pcm_free(), let's remove pcm_construct()/pcm_destruct(). Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/875x6wjyoa.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 4 ---- sound/soc/generic/audio-graph-card.c | 3 +-- sound/soc/soc-component.c | 10 +--------- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 0435ba376369..60f73c4b0bbb 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -86,10 +86,6 @@ struct snd_soc_component_driver { unsigned int reg, unsigned int val); /* pcm creation and destruction */ - int (*pcm_construct)(struct snd_soc_component *component, - struct snd_soc_pcm_runtime *rtd); - void (*pcm_destruct)(struct snd_soc_component *component, - struct snd_pcm *pcm); int (*pcm_new)(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd); void (*pcm_free)(struct snd_soc_component *component, diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c index 74e8f2ab7ffc..18ce4ee06350 100644 --- a/sound/soc/generic/audio-graph-card.c +++ b/sound/soc/generic/audio-graph-card.c @@ -76,8 +76,7 @@ static bool soc_component_is_pcm(struct snd_soc_dai_link_component *dlc) { struct snd_soc_dai *dai = snd_soc_find_dai_with_mutex(dlc); - if (dai && (dai->component->driver->pcm_construct || - dai->component->driver->pcm_new || + if (dai && (dai->component->driver->pcm_new || (dai->driver->ops && dai->driver->ops->pcm_new))) return true; diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 77ad33383974..0f5e120d32b7 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -1037,11 +1037,6 @@ int snd_soc_pcm_component_new(struct snd_soc_pcm_runtime *rtd) int i; for_each_rtd_components(rtd, i, component) { - if (component->driver->pcm_construct) { - ret = component->driver->pcm_construct(component, rtd); - if (ret < 0) - return soc_component_ret(component, ret); - } if (component->driver->pcm_new) { ret = component->driver->pcm_new(component, rtd); if (ret < 0) @@ -1060,12 +1055,9 @@ void snd_soc_pcm_component_free(struct snd_soc_pcm_runtime *rtd) if (!rtd->pcm) return; - for_each_rtd_components(rtd, i, component) { - if (component->driver->pcm_destruct) - component->driver->pcm_destruct(component, rtd->pcm); + for_each_rtd_components(rtd, i, component) if (component->driver->pcm_free) component->driver->pcm_free(component, rtd->pcm); - } } int snd_soc_pcm_component_prepare(struct snd_pcm_substream *substream) From 032322b44c02f5e8a127d1dca6798f91cc72eb1d Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Mon, 16 Mar 2026 16:50:47 +0800 Subject: [PATCH 144/359] ALSA: pcm: oss: use proper stream lock for runtime->state access __snd_pcm_set_state() writes runtime->state under the PCM stream lock. However, the OSS I/O functions snd_pcm_oss_write3(), snd_pcm_oss_read3(), snd_pcm_oss_writev3() and snd_pcm_oss_readv3() read runtime->state without holding the stream lock, only holding oss.params_lock (a different mutex that does not synchronize with the stream lock). Since __snd_pcm_set_state() is called from IRQ context (e.g., snd_pcm_period_elapsed -> snd_pcm_update_state -> __snd_pcm_xrun -> snd_pcm_stop -> snd_pcm_post_stop) while the OSS read/write paths run in process context, these are concurrent accesses that constitute a data race. Rather than using READ_ONCE()/WRITE_ONCE() barriers, introduce a snd_pcm_get_state() helper that reads runtime->state under the stream lock, matching the locking discipline used elsewhere in the PCM layer. Also export snd_pcm_set_state() for completeness. Use snd_pcm_get_state() in all four OSS I/O functions, caching the result in a local variable where the same snapshot is used for multiple comparisons to avoid taking the lock repeatedly. Signed-off-by: Cen Zhang Link: https://patch.msgid.link/20260316085047.2876451-1-zzzccc427@gmail.com Signed-off-by: Takashi Iwai --- include/sound/pcm.h | 4 ++++ sound/core/oss/pcm_oss.c | 44 +++++++++++++++++++++++----------------- sound/core/pcm_native.c | 23 +++++++++++++++++++-- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/include/sound/pcm.h b/include/sound/pcm.h index a7860c047503..76fc33dce537 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -729,6 +729,10 @@ static inline void __snd_pcm_set_state(struct snd_pcm_runtime *runtime, runtime->status->state = state; /* copy for mmap */ } +void snd_pcm_set_state(struct snd_pcm_substream *substream, + snd_pcm_state_t state); +snd_pcm_state_t snd_pcm_get_state(struct snd_pcm_substream *substream); + /** * bytes_to_samples - Unit conversion of the size from bytes to samples * @runtime: PCM runtime instance diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index d4fd4dfc7fc3..a140a0d9abb8 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -1227,14 +1227,16 @@ static int snd_pcm_oss_capture_position_fixup(struct snd_pcm_substream *substrea snd_pcm_sframes_t snd_pcm_oss_write3(struct snd_pcm_substream *substream, const char *ptr, snd_pcm_uframes_t frames, int in_kernel) { struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_state_t state; int ret; while (1) { - if (runtime->state == SNDRV_PCM_STATE_XRUN || - runtime->state == SNDRV_PCM_STATE_SUSPENDED) { + state = snd_pcm_get_state(substream); + if (state == SNDRV_PCM_STATE_XRUN || + state == SNDRV_PCM_STATE_SUSPENDED) { #ifdef OSS_DEBUG pcm_dbg(substream->pcm, "pcm_oss: write: recovering from %s\n", - runtime->state == SNDRV_PCM_STATE_XRUN ? + state == SNDRV_PCM_STATE_XRUN ? "XRUN" : "SUSPEND"); #endif ret = snd_pcm_oss_prepare(substream); @@ -1249,7 +1251,7 @@ snd_pcm_sframes_t snd_pcm_oss_write3(struct snd_pcm_substream *substream, const break; /* test, if we can't store new data, because the stream */ /* has not been started */ - if (runtime->state == SNDRV_PCM_STATE_PREPARED) + if (snd_pcm_get_state(substream) == SNDRV_PCM_STATE_PREPARED) return -EAGAIN; } return ret; @@ -1259,20 +1261,22 @@ snd_pcm_sframes_t snd_pcm_oss_read3(struct snd_pcm_substream *substream, char *p { struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_sframes_t delay; + snd_pcm_state_t state; int ret; while (1) { - if (runtime->state == SNDRV_PCM_STATE_XRUN || - runtime->state == SNDRV_PCM_STATE_SUSPENDED) { + state = snd_pcm_get_state(substream); + if (state == SNDRV_PCM_STATE_XRUN || + state == SNDRV_PCM_STATE_SUSPENDED) { #ifdef OSS_DEBUG pcm_dbg(substream->pcm, "pcm_oss: read: recovering from %s\n", - runtime->state == SNDRV_PCM_STATE_XRUN ? + state == SNDRV_PCM_STATE_XRUN ? "XRUN" : "SUSPEND"); #endif ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL); if (ret < 0) break; - } else if (runtime->state == SNDRV_PCM_STATE_SETUP) { + } else if (state == SNDRV_PCM_STATE_SETUP) { ret = snd_pcm_oss_prepare(substream); if (ret < 0) break; @@ -1285,7 +1289,7 @@ snd_pcm_sframes_t snd_pcm_oss_read3(struct snd_pcm_substream *substream, char *p frames, in_kernel); mutex_lock(&runtime->oss.params_lock); if (ret == -EPIPE) { - if (runtime->state == SNDRV_PCM_STATE_DRAINING) { + if (snd_pcm_get_state(substream) == SNDRV_PCM_STATE_DRAINING) { ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); if (ret < 0) break; @@ -1301,15 +1305,16 @@ snd_pcm_sframes_t snd_pcm_oss_read3(struct snd_pcm_substream *substream, char *p #ifdef CONFIG_SND_PCM_OSS_PLUGINS snd_pcm_sframes_t snd_pcm_oss_writev3(struct snd_pcm_substream *substream, void **bufs, snd_pcm_uframes_t frames) { - struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_state_t state; int ret; while (1) { - if (runtime->state == SNDRV_PCM_STATE_XRUN || - runtime->state == SNDRV_PCM_STATE_SUSPENDED) { + state = snd_pcm_get_state(substream); + if (state == SNDRV_PCM_STATE_XRUN || + state == SNDRV_PCM_STATE_SUSPENDED) { #ifdef OSS_DEBUG pcm_dbg(substream->pcm, "pcm_oss: writev: recovering from %s\n", - runtime->state == SNDRV_PCM_STATE_XRUN ? + state == SNDRV_PCM_STATE_XRUN ? "XRUN" : "SUSPEND"); #endif ret = snd_pcm_oss_prepare(substream); @@ -1322,7 +1327,7 @@ snd_pcm_sframes_t snd_pcm_oss_writev3(struct snd_pcm_substream *substream, void /* test, if we can't store new data, because the stream */ /* has not been started */ - if (runtime->state == SNDRV_PCM_STATE_PREPARED) + if (snd_pcm_get_state(substream) == SNDRV_PCM_STATE_PREPARED) return -EAGAIN; } return ret; @@ -1330,21 +1335,22 @@ snd_pcm_sframes_t snd_pcm_oss_writev3(struct snd_pcm_substream *substream, void snd_pcm_sframes_t snd_pcm_oss_readv3(struct snd_pcm_substream *substream, void **bufs, snd_pcm_uframes_t frames) { - struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_state_t state; int ret; while (1) { - if (runtime->state == SNDRV_PCM_STATE_XRUN || - runtime->state == SNDRV_PCM_STATE_SUSPENDED) { + state = snd_pcm_get_state(substream); + if (state == SNDRV_PCM_STATE_XRUN || + state == SNDRV_PCM_STATE_SUSPENDED) { #ifdef OSS_DEBUG pcm_dbg(substream->pcm, "pcm_oss: readv: recovering from %s\n", - runtime->state == SNDRV_PCM_STATE_XRUN ? + state == SNDRV_PCM_STATE_XRUN ? "XRUN" : "SUSPEND"); #endif ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL); if (ret < 0) break; - } else if (runtime->state == SNDRV_PCM_STATE_SETUP) { + } else if (state == SNDRV_PCM_STATE_SETUP) { ret = snd_pcm_oss_prepare(substream); if (ret < 0) break; diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 67cf6a0e17ba..394f86bc4d29 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -618,13 +618,32 @@ static int period_to_usecs(struct snd_pcm_runtime *runtime) return usecs; } -static void snd_pcm_set_state(struct snd_pcm_substream *substream, - snd_pcm_state_t state) +/** + * snd_pcm_set_state - Set the PCM runtime state with stream lock + * @substream: PCM substream + * @state: state to set + */ +void snd_pcm_set_state(struct snd_pcm_substream *substream, + snd_pcm_state_t state) { guard(pcm_stream_lock_irq)(substream); if (substream->runtime->state != SNDRV_PCM_STATE_DISCONNECTED) __snd_pcm_set_state(substream->runtime, state); } +EXPORT_SYMBOL_GPL(snd_pcm_set_state); + +/** + * snd_pcm_get_state - Read the PCM runtime state with stream lock + * @substream: PCM substream + * + * Return: the current PCM state + */ +snd_pcm_state_t snd_pcm_get_state(struct snd_pcm_substream *substream) +{ + guard(pcm_stream_lock_irqsave)(substream); + return substream->runtime->state; +} +EXPORT_SYMBOL_GPL(snd_pcm_get_state); static inline void snd_pcm_timer_notify(struct snd_pcm_substream *substream, int event) From 1029df4de882bdfb4698bee6ac2b0c3d98d0e52d Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Mon, 16 Mar 2026 09:03:07 +0800 Subject: [PATCH 145/359] ASoC: soc_sdw_utils: remove cs42l45 SmartMic codec name index Currently the index is 0. In theory, it will not change even if any other endpoint is not present. But to be consistent and avoid surprise, remove the index and let the machine driver match the codec name partially. Signed-off-by: Bard Liao Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260316010308.285622-2-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index d1e98f95735b..fe9b79ace387 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -791,7 +791,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, { .direction = {false, true}, - .codec_name = "snd_soc_sdca.SmartMic.0", + .codec_name = "snd_soc_sdca.SmartMic", .dai_name = "OT 113", .dai_type = SOC_SDW_DAI_TYPE_MIC, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, From ac5f5cfc98b5285c9278a74733a306e77816b822 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Mon, 16 Mar 2026 09:03:08 +0800 Subject: [PATCH 146/359] ASoC: soc_sdw_utils: remove codec name index of snd_soc_sdca codecs The index could change if any endpoint is not present. Remove the index and let the machine driver get the right codec name from the registered components. Signed-off-by: Bard Liao Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260316010308.285622-3-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_utils.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index fe9b79ace387..75658148df57 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -855,7 +855,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dais = { { .direction = {true, false}, - .codec_name = "snd_soc_sdca.UAJ.1", + .codec_name = "snd_soc_sdca.UAJ", .dai_name = "IT 41", .dai_type = SOC_SDW_DAI_TYPE_JACK, .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, @@ -863,7 +863,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, { .direction = {false, true}, - .codec_name = "snd_soc_sdca.SmartMic.0", + .codec_name = "snd_soc_sdca.SmartMic", .dai_name = "OT 113", .dai_type = SOC_SDW_DAI_TYPE_MIC, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, @@ -871,7 +871,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, { .direction = {false, true}, - .codec_name = "snd_soc_sdca.UAJ.1", + .codec_name = "snd_soc_sdca.UAJ", .dai_name = "OT 36", .dai_type = SOC_SDW_DAI_TYPE_JACK, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, @@ -880,7 +880,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 3, .auxs = { { - .codec_name = "snd_soc_sdca.HID.2", + .codec_name = "snd_soc_sdca.HID", }, }, .aux_num = 1, From 804dce6c73fdfa44184ee4e8b09abad7f5da408f Mon Sep 17 00:00:00 2001 From: Joseph Salisbury Date: Mon, 16 Mar 2026 14:05:45 -0400 Subject: [PATCH 147/359] ASoC: fsl_easrc: fix comment typo The file contains a spelling error in a source comment (funciton). Typos in comments reduce readability and make text searches less reliable for developers and maintainers. Replace 'funciton' with 'function' in the affected comment. This is a comment-only cleanup and does not change behavior. Fixes: 955ac624058f ("ASoC: fsl_easrc: Add EASRC ASoC CPU DAI drivers") Cc: stable@vger.kernel.org Signed-off-by: Joseph Salisbury Link: https://patch.msgid.link/20260316180545.144032-1-joseph.salisbury@oracle.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_easrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c index 6c56134c60cc..599e439b359a 100644 --- a/sound/soc/fsl/fsl_easrc.c +++ b/sound/soc/fsl/fsl_easrc.c @@ -1286,7 +1286,7 @@ static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx) /* * Release the context * - * This funciton is mainly doing the revert thing in request context + * This function is mainly doing the revert thing in request context */ static void fsl_easrc_release_context(struct fsl_asrc_pair *ctx) { From f8d51e903a6c97d8d298f14d9f8b4fff808670e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Bellegarde?= Date: Wed, 4 Mar 2026 15:10:06 +0100 Subject: [PATCH 148/359] ASoC: codecs: wcd-clsh: Always update buck/flyback on transitions on transitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WCD934x audio outputs (earpiece, headphone, speaker) share two power supply converters, a buck and a flyback, managed by reference counters (buck_users, flyback_users) in the Class-H controller. The early return in wcd_clsh_ctrl_set_state() when nstate == ctrl->state prevented _wcd_clsh_ctrl_set_state() from being called when switching between outputs sharing the same state value. As a result, the buck and flyback reference counters were never decremented on disable, leaving the converters active and their counters out of sync with the actual hardware state. This caused audible distortion on the earpiece output and spurious MBHC over-current protection interrupts on HPHL/HPHR during output switching. Remove the early return so that CLSH_REQ_ENABLE and CLSH_REQ_DISABLE are always dispatched, keeping the buck and flyback reference counters consistent on every state transition. Signed-off-by: Cédric Bellegarde Link: https://patch.msgid.link/20260304141006.280894-1-cedric.bellegarde@adishatz.org Signed-off-by: Mark Brown --- sound/soc/codecs/wcd-clsh-v2.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/sound/soc/codecs/wcd-clsh-v2.c b/sound/soc/codecs/wcd-clsh-v2.c index 13d07296916f..62ca22ea0f3b 100644 --- a/sound/soc/codecs/wcd-clsh-v2.c +++ b/sound/soc/codecs/wcd-clsh-v2.c @@ -848,9 +848,6 @@ int wcd_clsh_ctrl_set_state(struct wcd_clsh_ctrl *ctrl, { struct snd_soc_component *comp = ctrl->comp; - if (nstate == ctrl->state) - return 0; - if (!wcd_clsh_is_state_valid(nstate)) { dev_err(comp->dev, "Class-H not a valid new state:\n"); return -EINVAL; From e29d097ead33d0172f028b5b23f10812fe8e8335 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 3 Mar 2026 15:53:07 +0000 Subject: [PATCH 149/359] ASoC: dapm: Add a named controls variant of a mux widget There is already a version of the mixer widget that forces use of the specified control name, rather than factoring in the widget name. Add the same feature for mux widgets. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260303155308.138989-2-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/soc-dapm.h | 1 + sound/soc/soc-dapm.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 49f0fe05db01..4f8fb7622a13 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -424,6 +424,7 @@ enum snd_soc_dapm_type { snd_soc_dapm_input = 0, /* input pin */ snd_soc_dapm_output, /* output pin */ snd_soc_dapm_mux, /* selects 1 analog signal from many inputs */ + snd_soc_dapm_mux_named_ctl, /* mux with named controls */ snd_soc_dapm_demux, /* connects the input to one of multiple outputs */ snd_soc_dapm_mixer, /* mixes several analog signals together */ snd_soc_dapm_mixer_named_ctl, /* mixer with named controls */ diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 2768ba5bfc9f..d6192204e613 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -89,6 +89,7 @@ static int dapm_up_seq[] = { [snd_soc_dapm_input] = 6, [snd_soc_dapm_output] = 6, [snd_soc_dapm_mux] = 7, + [snd_soc_dapm_mux_named_ctl] = 7, [snd_soc_dapm_demux] = 7, [snd_soc_dapm_dac] = 8, [snd_soc_dapm_switch] = 9, @@ -140,6 +141,7 @@ static int dapm_down_seq[] = { [snd_soc_dapm_micbias] = 10, [snd_soc_dapm_vmid] = 10, [snd_soc_dapm_mux] = 11, + [snd_soc_dapm_mux_named_ctl] = 11, [snd_soc_dapm_demux] = 11, [snd_soc_dapm_aif_in] = 12, [snd_soc_dapm_aif_out] = 12, @@ -577,6 +579,7 @@ static int dapm_check_dynamic_path( switch (sink->id) { case snd_soc_dapm_mux: + case snd_soc_dapm_mux_named_ctl: case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: @@ -668,6 +671,7 @@ static int dapm_add_path( switch (wsink->id) { case snd_soc_dapm_mux: + case snd_soc_dapm_mux_named_ctl: ret = dapm_connect_mux(dapm, path, control, wsink); if (ret != 0) goto err; @@ -766,6 +770,7 @@ static int dapm_kcontrol_data_alloc(struct snd_soc_dapm_widget *widget, break; case snd_soc_dapm_demux: case snd_soc_dapm_mux: + case snd_soc_dapm_mux_named_ctl: e = (struct soc_enum *)kcontrol->private_value; if (e->autodisable) { @@ -915,6 +920,7 @@ static bool dapm_kcontrol_set_value(const struct snd_kcontrol *kcontrol, break; case snd_soc_dapm_demux: case snd_soc_dapm_mux: + case snd_soc_dapm_mux_named_ctl: data->widget->on_val = value >> data->widget->shift; break; default: @@ -1198,6 +1204,7 @@ static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w, wname_in_long_name = true; kcname_in_long_name = true; break; + case snd_soc_dapm_mux_named_ctl: case snd_soc_dapm_mixer_named_ctl: wname_in_long_name = false; kcname_in_long_name = true; @@ -1317,6 +1324,7 @@ static int dapm_new_mux(struct snd_soc_dapm_widget *w) switch (w->id) { case snd_soc_dapm_mux: + case snd_soc_dapm_mux_named_ctl: dir = SND_SOC_DAPM_DIR_OUT; type = "mux"; break; @@ -2399,6 +2407,7 @@ static const char * const dapm_type_name[] = { [snd_soc_dapm_input] = "input", [snd_soc_dapm_output] = "output", [snd_soc_dapm_mux] = "mux", + [snd_soc_dapm_mux_named_ctl] = "mux_named_ctl", [snd_soc_dapm_demux] = "demux", [snd_soc_dapm_mixer] = "mixer", [snd_soc_dapm_mixer_named_ctl] = "mixer_named_ctl", @@ -3347,6 +3356,7 @@ int snd_soc_dapm_new_widgets(struct snd_soc_card *card) dapm_new_mixer(w); break; case snd_soc_dapm_mux: + case snd_soc_dapm_mux_named_ctl: case snd_soc_dapm_demux: dapm_new_mux(w); break; @@ -3834,6 +3844,7 @@ snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm, break; case snd_soc_dapm_mux: + case snd_soc_dapm_mux_named_ctl: case snd_soc_dapm_demux: case snd_soc_dapm_switch: case snd_soc_dapm_mixer: From b6a6cd3f6b5b0de65b398383ba12e72eb7322c82 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 3 Mar 2026 15:53:08 +0000 Subject: [PATCH 150/359] ASoC: SDCA: Use named control mux for GE/SU controls Most of the time a GE Entity will control many SU Entities and thus the generated control will use the control name, since the control is shared. However, in rare cases the GE Entity will only control a single SU Entity. In this case the control is only linked to a single SU widget and will thus inherit the name from that. This leads to the naming for the Selected Mode control to be unpredictable. Update to use the new named control muxes to always force the use of the control name. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260303155308.138989-3-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_asoc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c index a0191e5a5a7d..69b4fe967922 100644 --- a/sound/soc/sdca/sdca_asoc.c +++ b/sound/soc/sdca/sdca_asoc.c @@ -455,7 +455,7 @@ static int entity_parse_su_device(struct device *dev, if (!range) return -EINVAL; - (*widget)->id = snd_soc_dapm_mux; + (*widget)->id = snd_soc_dapm_mux_named_ctl; (*widget)->kcontrol_news = entity->group->ge.kctl; (*widget)->num_kcontrols = 1; (*widget)++; From 785639b5bf2a87eaf0cda14baaa068b3728c7be2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Mon, 16 Mar 2026 10:39:38 -0300 Subject: [PATCH 151/359] ALSA: timer: keep a list of open masters for slave lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_timer_check_slave() still walks all registered timers and all open timer instances to find a matching master for a newly opened slave. Maintain a global list of open master instances that can accept slave links and use it for the slave lookup path instead. This keeps the existing matching semantics while avoiding the nested walk over snd_timer_list and each timer open_list_head. The reverse path in snd_timer_check_master() already scans only the pending slave list, so this makes both lookup paths closer in shape without changing the master/slave linking logic. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260316-alsa-timer-master-list-v1-1-fb95e547110a@gmail.com Signed-off-by: Takashi Iwai --- include/sound/timer.h | 1 + sound/core/timer.c | 29 ++++++++++++++++++++--------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/include/sound/timer.h b/include/sound/timer.h index 760e132cc0cd..83bafe70cf33 100644 --- a/include/sound/timer.h +++ b/include/sound/timer.h @@ -102,6 +102,7 @@ struct snd_timer_instance { unsigned int slave_id; struct list_head open_list; struct list_head active_list; + struct list_head master_list; struct list_head ack_list; struct list_head slave_list_head; struct list_head slave_active_head; diff --git a/sound/core/timer.c b/sound/core/timer.c index 6a70df7ae019..820901d503af 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -129,6 +129,9 @@ static LIST_HEAD(snd_timer_list); /* list of slave instances */ static LIST_HEAD(snd_timer_slave_list); +/* list of open master instances that can accept slave links */ +static LIST_HEAD(snd_timer_master_list); + /* lock for slave active lists */ static DEFINE_SPINLOCK(slave_active_lock); @@ -161,6 +164,7 @@ struct snd_timer_instance *snd_timer_instance_new(const char *owner) } INIT_LIST_HEAD(&timeri->open_list); INIT_LIST_HEAD(&timeri->active_list); + INIT_LIST_HEAD(&timeri->master_list); INIT_LIST_HEAD(&timeri->ack_list); INIT_LIST_HEAD(&timeri->slave_list_head); INIT_LIST_HEAD(&timeri->slave_active_head); @@ -245,6 +249,12 @@ static int check_matching_master_slave(struct snd_timer_instance *master, return 1; } +static bool snd_timer_has_slave_key(const struct snd_timer_instance *timeri) +{ + return !(timeri->flags & SNDRV_TIMER_IFLG_SLAVE) && + timeri->slave_class > SNDRV_TIMER_SCLASS_NONE; +} + /* * look for a master instance matching with the slave id of the given slave. * when found, relink the open_link of the slave. @@ -253,19 +263,15 @@ static int check_matching_master_slave(struct snd_timer_instance *master, */ static int snd_timer_check_slave(struct snd_timer_instance *slave) { - struct snd_timer *timer; struct snd_timer_instance *master; int err = 0; - /* FIXME: it's really dumb to look up all entries.. */ - list_for_each_entry(timer, &snd_timer_list, device_list) { - list_for_each_entry(master, &timer->open_list_head, open_list) { - err = check_matching_master_slave(master, slave); - if (err != 0) /* match found or error */ - goto out; - } + list_for_each_entry(master, &snd_timer_master_list, master_list) { + err = check_matching_master_slave(master, slave); + if (err != 0) /* match found or error */ + goto out; } - out: +out: return err < 0 ? err : 0; } @@ -377,6 +383,8 @@ int snd_timer_open(struct snd_timer_instance *timeri, timeri->slave_id = slave_id; list_add_tail(&timeri->open_list, &timer->open_list_head); + if (snd_timer_has_slave_key(timeri)) + list_add_tail(&timeri->master_list, &snd_timer_master_list); timer->num_instances++; err = snd_timer_check_master(timeri); list_added: @@ -431,6 +439,9 @@ static void snd_timer_close_locked(struct snd_timer_instance *timeri, num_slaves--; } + if (!list_empty(&timeri->master_list)) + list_del_init(&timeri->master_list); + /* force to stop the timer */ snd_timer_stop(timeri); From 4ebaf9d999327ce8e2ea5847ac96fe53fde3fe9a Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sat, 14 Mar 2026 10:27:11 +1000 Subject: [PATCH 152/359] ASoC: codecs: cs42l84: set up PLL for more sample rates Previously, this driver only advertised support for 48 kHz and 96 kHz sample rates, as there was no PLL configuration data specified for any other sample rate/BCLK. The CS42L84 is an Apple-specific variant of CS42L42. The PLL configuration parameters for a variety of common BCLKs are available in the latter's datasheet. What happens if we just use those? As it turns out, they work just fine. Fill out more PLL config parameters in the PLL config lookup table, and advertise the corresponding sample rates to userspace. This enables 44.1, 88.2, 176.4 and 192 kHz output and input. Signed-off-by: James Calligeros Link: https://patch.msgid.link/20260314-cs42l84-rates-v2-1-ea8a5af52542@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs42l84.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c index 1e1307a16f81..e590a43559e4 100644 --- a/sound/soc/codecs/cs42l84.c +++ b/sound/soc/codecs/cs42l84.c @@ -357,8 +357,11 @@ struct cs42l84_pll_params { * Common PLL Settings for given BCLK */ static const struct cs42l84_pll_params pll_ratio_table[] = { + { 2822400, 1, 0, 0x40, 0x000000, 0x03, 0x10, 11289600}, { 3072000, 1, 0, 0x40, 0x000000, 0x03, 0x10, 12288000}, + { 5644800, 1, 0, 0x40, 0x000000, 0x03, 0x10, 11289600}, { 6144000, 1, 1, 0x40, 0x000000, 0x03, 0x10, 12288000}, + { 11289600, 0, 0, 0, 0, 0, 0, 11289600}, { 12288000, 0, 0, 0, 0, 0, 0, 12288000}, { 24576000, 1, 3, 0x40, 0x000000, 0x03, 0x10, 12288000}, }; @@ -408,11 +411,18 @@ static int cs42l84_pll_config(struct snd_soc_component *component) CS42L84_ASP_FSYNC_CTL3_BCLK_PERIOD_HI, FIELD_PREP(CS42L84_ASP_FSYNC_CTL3_BCLK_PERIOD_HI, fsync >> 7)); - /* Save what the MCLK will be */ + /* + * MCLK values are binned into 12 or 24 MHz regions. If MCLK is exactly + * 12 or 24 MHz, the high bit of CCM_CTL1_MCLK_F is set. If MCLK + * is in the region of 24 MHz, the low bit is set. This seemingly + * corresponds to CS42L42's documented INTERNAL_FS and MCLKDIV + * behaviour respectively. + */ switch (pll_ratio_table[i].mclk_int) { case 12000000: cs42l84->pll_mclk_f = CS42L84_CCM_CTL1_MCLK_F_12MHZ; break; + case 11289600: case 12288000: cs42l84->pll_mclk_f = CS42L84_CCM_CTL1_MCLK_F_12_288KHZ; break; @@ -670,14 +680,18 @@ static struct snd_soc_dai_driver cs42l84_dai = { .stream_name = "Playback", .channels_min = 1, .channels_max = 2, - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000, .formats = CS42L84_FORMATS, }, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 1, - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000, .formats = CS42L84_FORMATS, }, .symmetric_rate = 1, From 6017671da9d0a11056bf37b4b54903e57dbc9cd1 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 18 Mar 2026 10:38:13 +0000 Subject: [PATCH 153/359] ASoC: wm_adsp: Fix crash in kunit tests on arm64 Several of the wm_adsp kunit tests failed by only on arm64. This turns out to be passing things to release_firmware which were not allocated with request_firmware. This does actually drop some errors on other platforms but somehow doesn't cause the tests to fail, and kunit hides a lot of the output for passing tests. There is a comment in the code regarding this exact thing in wm_adsp_fw_find_test_case_exit(), but then it goes on and does it anyway. I suspect maybe there was a plan to override wm_adsp_release_firmware_files() that got forgotten, as the function appears to have hooks for kunit that aren't used. Anyway for now implement a simple fix of freeing the filenames locally. It is likely a more detailed fix will be implemented in the near future but this should get things moving again. Fixes: bf2d44d07de7 ("ASoC: wm_adsp: Add kunit test for firmware file search") Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260318103813.249190-1-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm_adsp_fw_find_test.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm_adsp_fw_find_test.c b/sound/soc/codecs/wm_adsp_fw_find_test.c index 44c26e991b35..fb886dc61c27 100644 --- a/sound/soc/codecs/wm_adsp_fw_find_test.c +++ b/sound/soc/codecs/wm_adsp_fw_find_test.c @@ -262,7 +262,8 @@ static void wm_adsp_fw_find_test_case_exit(struct kunit *test) * dummies not allocated by the real request_firmware() call they * must not be passed to release_firmware(). */ - wm_adsp_release_firmware_files(&priv->found_fw); + kfree(priv->found_fw.wmfw.filename); + kfree(priv->found_fw.coeff.filename); } static void wm_adsp_fw_find_test_param_desc(const struct wm_adsp_fw_find_test_params *param, From a809ff6469c53d69db5f30251bcf206d618bcccb Mon Sep 17 00:00:00 2001 From: Piyush Patle Date: Wed, 18 Mar 2026 18:37:33 +0530 Subject: [PATCH 154/359] ASoC: dt-bindings: adi,ssm2305: Convert to DT schema Convert the SSM2305 speaker amplifier binding from text format to DT schema to enable dtbs_check validation. Signed-off-by: Piyush Patle Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260318130733.52477-1-piyushpatle228@gmail.com Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/adi,ssm2305.txt | 14 ------ .../bindings/sound/adi,ssm2305.yaml | 46 +++++++++++++++++++ 2 files changed, 46 insertions(+), 14 deletions(-) delete mode 100644 Documentation/devicetree/bindings/sound/adi,ssm2305.txt create mode 100644 Documentation/devicetree/bindings/sound/adi,ssm2305.yaml diff --git a/Documentation/devicetree/bindings/sound/adi,ssm2305.txt b/Documentation/devicetree/bindings/sound/adi,ssm2305.txt deleted file mode 100644 index a9c9d83c8a30..000000000000 --- a/Documentation/devicetree/bindings/sound/adi,ssm2305.txt +++ /dev/null @@ -1,14 +0,0 @@ -Analog Devices SSM2305 Speaker Amplifier -======================================== - -Required properties: - - compatible : "adi,ssm2305" - - shutdown-gpios : The gpio connected to the shutdown pin. - The gpio signal is ACTIVE_LOW. - -Example: - -ssm2305: analog-amplifier { - compatible = "adi,ssm2305"; - shutdown-gpios = <&gpio3 20 GPIO_ACTIVE_LOW>; -}; diff --git a/Documentation/devicetree/bindings/sound/adi,ssm2305.yaml b/Documentation/devicetree/bindings/sound/adi,ssm2305.yaml new file mode 100644 index 000000000000..b841da2dc284 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/adi,ssm2305.yaml @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/adi,ssm2305.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices SSM2305 Class-D Speaker Amplifier + +maintainers: + - Lars-Peter Clausen + +description: + The SSM2305 is a filterless, high efficiency, mono 2.8 W Class-D + audio amplifier with a micropower shutdown mode controlled via a + dedicated active-low GPIO pin. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: adi,ssm2305 + + shutdown-gpios: + maxItems: 1 + description: + GPIO connected to the shutdown pin (SD) of the SSM2305. + The pin is active-low; asserting it puts the device into + micropower shutdown mode. + +required: + - compatible + - shutdown-gpios + +unevaluatedProperties: false + +examples: + - | + #include + + analog-amplifier { + compatible = "adi,ssm2305"; + shutdown-gpios = <&gpio3 20 GPIO_ACTIVE_LOW>; + }; + +... From df3eec203b940bad98a7c0b7ec0edaaaa8cd0247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Wed, 18 Mar 2026 11:08:46 -0300 Subject: [PATCH 155/359] ALSA: usb-audio: validate full match when resolving quirk aliases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit get_alias_quirk() resolves a quirk for an aliased USB ID by scanning usb_audio_ids[], but it currently checks only the vendor/product pair. This is weak for quirk table entries that also depend on additional USB_DEVICE_ID match fields, such as device or interface class, subclass, protocol, interface number, or bcdDevice range. Keep the aliased vid:pid as the lookup key, then validate only the remaining match criteria of each candidate entry against the real device/interface descriptors by clearing USB_DEVICE_ID_MATCH_DEVICE from a temporary copy and passing it to usb_match_one_id(). Suggested-by: Takashi Iwai Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260318-alsa-usb-fix-quirk-alias-v3-1-bd3b17a32939@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/card.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/sound/usb/card.c b/sound/usb/card.c index fd81f32a66fb..f42d72cd0378 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -866,19 +866,25 @@ static void find_last_interface(struct snd_usb_audio *chip) /* look for the corresponding quirk */ static const struct snd_usb_audio_quirk * -get_alias_quirk(struct usb_device *dev, unsigned int id) +get_alias_quirk(struct usb_interface *intf, unsigned int id) { const struct usb_device_id *p; + struct usb_device_id match_id; for (p = usb_audio_ids; p->match_flags; p++) { - /* FIXME: this checks only vendor:product pair in the list */ - if ((p->match_flags & USB_DEVICE_ID_MATCH_DEVICE) == - USB_DEVICE_ID_MATCH_DEVICE && - p->idVendor == USB_ID_VENDOR(id) && - p->idProduct == USB_ID_PRODUCT(id)) - return (const struct snd_usb_audio_quirk *)p->driver_info; - } + if ((p->match_flags & USB_DEVICE_ID_MATCH_DEVICE) != + USB_DEVICE_ID_MATCH_DEVICE) + continue; + if (p->idVendor != USB_ID_VENDOR(id) || + p->idProduct != USB_ID_PRODUCT(id)) + continue; + match_id = *p; + match_id.match_flags &= ~USB_DEVICE_ID_MATCH_DEVICE; + if (!match_id.match_flags || usb_match_one_id(intf, &match_id)) + return (const struct snd_usb_audio_quirk *) + p->driver_info; + } return NULL; } @@ -927,7 +933,7 @@ static int usb_audio_probe(struct usb_interface *intf, id = USB_ID(le16_to_cpu(dev->descriptor.idVendor), le16_to_cpu(dev->descriptor.idProduct)); if (get_alias_id(dev, &id)) - quirk = get_alias_quirk(dev, id); + quirk = get_alias_quirk(intf, id); if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum) return -ENXIO; if (quirk && quirk->ifnum == QUIRK_NODEV_INTERFACE) From c2da4813882b8037198cd8e67182293e17b44573 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 12 Mar 2026 00:15:03 +0000 Subject: [PATCH 156/359] ASoC: soc-component: add snd_soc_component_regmap_val_bytes() component has component->val_bytes which is set via snd_soc_component_setup_regmap(). But it can be calculated via component->regmap. No need to keep it as component->val_bytes. Add snd_soc_component_regmap_val_bytes() for it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/878qbxzywo.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 1 + sound/soc/soc-component.c | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 2a2b74b24a60..c08922290cf1 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -327,6 +327,7 @@ int snd_soc_component_stream_event(struct snd_soc_component *component, 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); void snd_soc_component_setup_regmap(struct snd_soc_component *component); #ifdef CONFIG_REGMAP void snd_soc_component_init_regmap(struct snd_soc_component *component, diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 89f236ab3034..9dc2cbdc2dda 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -351,6 +351,23 @@ void snd_soc_component_setup_regmap(struct snd_soc_component *component) component->val_bytes = val_bytes; } +int snd_soc_component_regmap_val_bytes(struct snd_soc_component *component) +{ + int val_bytes; + + /* Errors are legitimate for non-integer byte multiples */ + + if (!component->regmap) + return 0; + + val_bytes = regmap_get_val_bytes(component->regmap); + if (val_bytes < 0) + return 0; + + return val_bytes; +} +EXPORT_SYMBOL_GPL(snd_soc_component_regmap_val_bytes); + #ifdef CONFIG_REGMAP /** From 72660d1ac9f1d4eb535e502b404c5cb4f15ada1a Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 12 Mar 2026 00:15:13 +0000 Subject: [PATCH 157/359] ASoC: tegra: use snd_soc_component_regmap_val_bytes() component has component->val_bytes which is set via snd_soc_component_setup_regmap(). But it can be calculated via component->regmap. No need to keep it as component->val_bytes. Use snd_soc_component_regmap_val_bytes(). Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/877brhzywd.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_ahub.c | 8 +++++--- sound/soc/tegra/tegra210_mbdrc.c | 18 ++++++++++++------ sound/soc/tegra/tegra210_peq.c | 6 ++++-- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/sound/soc/tegra/tegra210_ahub.c b/sound/soc/tegra/tegra210_ahub.c index 43a45f785d5b..a4ed02617472 100644 --- a/sound/soc/tegra/tegra210_ahub.c +++ b/sound/soc/tegra/tegra210_ahub.c @@ -20,6 +20,7 @@ static int tegra_ahub_get_value_enum(struct snd_kcontrol *kctl, struct snd_soc_component *cmpnt = snd_soc_dapm_kcontrol_to_component(kctl); struct tegra_ahub *ahub = snd_soc_component_get_drvdata(cmpnt); struct soc_enum *e = (struct soc_enum *)kctl->private_value; + int val_bytes = snd_soc_component_regmap_val_bytes(cmpnt); unsigned int reg, i, bit_pos = 0; /* @@ -35,7 +36,7 @@ static int tegra_ahub_get_value_enum(struct snd_kcontrol *kctl, if (reg_val) { bit_pos = ffs(reg_val) + - (8 * cmpnt->val_bytes * i); + (8 * val_bytes * i); break; } } @@ -59,6 +60,7 @@ static int tegra_ahub_put_value_enum(struct snd_kcontrol *kctl, struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kctl); struct soc_enum *e = (struct soc_enum *)kctl->private_value; struct snd_soc_dapm_update update[TEGRA_XBAR_UPDATE_MAX_REG] = { }; + int val_bytes = snd_soc_component_regmap_val_bytes(cmpnt); unsigned int *item = uctl->value.enumerated.item; unsigned int value = e->values[item[0]]; unsigned int i, bit_pos, reg_idx = 0, reg_val = 0; @@ -69,8 +71,8 @@ static int tegra_ahub_put_value_enum(struct snd_kcontrol *kctl, if (value) { /* Get the register index and value to set */ - reg_idx = (value - 1) / (8 * cmpnt->val_bytes); - bit_pos = (value - 1) % (8 * cmpnt->val_bytes); + reg_idx = (value - 1) / (8 * val_bytes); + bit_pos = (value - 1) % (8 * val_bytes); reg_val = BIT(bit_pos); } diff --git a/sound/soc/tegra/tegra210_mbdrc.c b/sound/soc/tegra/tegra210_mbdrc.c index 6a268dbb7197..64f1c91646ce 100644 --- a/sound/soc/tegra/tegra210_mbdrc.c +++ b/sound/soc/tegra/tegra210_mbdrc.c @@ -307,13 +307,14 @@ static int tegra210_mbdrc_band_params_get(struct snd_kcontrol *kcontrol, struct tegra_soc_bytes *params = (void *)kcontrol->private_value; struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol); struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + int val_bytes = snd_soc_component_regmap_val_bytes(cmpnt); u32 *data = (u32 *)ucontrol->value.bytes.data; u32 regs = params->soc.base; u32 mask = params->soc.mask; u32 shift = params->shift; unsigned int i; - for (i = 0; i < params->soc.num_regs; i++, regs += cmpnt->val_bytes) { + for (i = 0; i < params->soc.num_regs; i++, regs += val_bytes) { regmap_read(ope->mbdrc_regmap, regs, &data[i]); data[i] = ((data[i] & mask) >> shift); @@ -328,6 +329,7 @@ static int tegra210_mbdrc_band_params_put(struct snd_kcontrol *kcontrol, struct tegra_soc_bytes *params = (void *)kcontrol->private_value; struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol); struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + int val_bytes = snd_soc_component_regmap_val_bytes(cmpnt); u32 *data = (u32 *)ucontrol->value.bytes.data; u32 regs = params->soc.base; u32 mask = params->soc.mask; @@ -335,7 +337,7 @@ static int tegra210_mbdrc_band_params_put(struct snd_kcontrol *kcontrol, bool change = false; unsigned int i; - for (i = 0; i < params->soc.num_regs; i++, regs += cmpnt->val_bytes) { + for (i = 0; i < params->soc.num_regs; i++, regs += val_bytes) { bool update = false; regmap_update_bits_check(ope->mbdrc_regmap, regs, mask, @@ -353,13 +355,14 @@ static int tegra210_mbdrc_threshold_get(struct snd_kcontrol *kcontrol, struct tegra_soc_bytes *params = (void *)kcontrol->private_value; struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol); struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + int val_bytes = snd_soc_component_regmap_val_bytes(cmpnt); u32 *data = (u32 *)ucontrol->value.bytes.data; u32 regs = params->soc.base; u32 num_regs = params->soc.num_regs; u32 val; unsigned int i; - for (i = 0; i < num_regs; i += 4, regs += cmpnt->val_bytes) { + for (i = 0; i < num_regs; i += 4, regs += val_bytes) { regmap_read(ope->mbdrc_regmap, regs, &val); data[i] = (val & TEGRA210_MBDRC_THRESH_1ST_MASK) >> @@ -381,13 +384,14 @@ static int tegra210_mbdrc_threshold_put(struct snd_kcontrol *kcontrol, struct tegra_soc_bytes *params = (void *)kcontrol->private_value; struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol); struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + int val_bytes = snd_soc_component_regmap_val_bytes(cmpnt); u32 *data = (u32 *)ucontrol->value.bytes.data; u32 regs = params->soc.base; u32 num_regs = params->soc.num_regs; bool change = false; unsigned int i; - for (i = 0; i < num_regs; i += 4, regs += cmpnt->val_bytes) { + for (i = 0; i < num_regs; i += 4, regs += val_bytes) { bool update = false; data[i] = (((data[i] >> TEGRA210_MBDRC_THRESH_1ST_SHIFT) & @@ -413,9 +417,10 @@ static int tegra210_mbdrc_biquad_coeffs_get(struct snd_kcontrol *kcontrol, { struct tegra_soc_bytes *params = (void *)kcontrol->private_value; struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol); + int val_bytes = snd_soc_component_regmap_val_bytes(cmpnt); u32 *data = (u32 *)ucontrol->value.bytes.data; - memset(data, 0, params->soc.num_regs * cmpnt->val_bytes); + memset(data, 0, params->soc.num_regs * val_bytes); return 0; } @@ -426,8 +431,9 @@ static int tegra210_mbdrc_biquad_coeffs_put(struct snd_kcontrol *kcontrol, struct tegra_soc_bytes *params = (void *)kcontrol->private_value; struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol); struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + int val_bytes = snd_soc_component_regmap_val_bytes(cmpnt); u32 reg_ctrl = params->soc.base; - u32 reg_data = reg_ctrl + cmpnt->val_bytes; + u32 reg_data = reg_ctrl + val_bytes; u32 *data = (u32 *)ucontrol->value.bytes.data; tegra210_mbdrc_write_ram(ope->mbdrc_regmap, reg_ctrl, reg_data, diff --git a/sound/soc/tegra/tegra210_peq.c b/sound/soc/tegra/tegra210_peq.c index 2f72e9d541dc..022a20f1ef7d 100644 --- a/sound/soc/tegra/tegra210_peq.c +++ b/sound/soc/tegra/tegra210_peq.c @@ -148,8 +148,9 @@ static int tegra210_peq_ram_get(struct snd_kcontrol *kcontrol, struct tegra_soc_bytes *params = (void *)kcontrol->private_value; struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol); struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + int val_bytes = snd_soc_component_regmap_val_bytes(cmpnt); u32 i, reg_ctrl = params->soc.base; - u32 reg_data = reg_ctrl + cmpnt->val_bytes; + u32 reg_data = reg_ctrl + val_bytes; s32 *data = (s32 *)biquad_coeff_buffer; pm_runtime_get_sync(cmpnt->dev); @@ -171,8 +172,9 @@ static int tegra210_peq_ram_put(struct snd_kcontrol *kcontrol, struct tegra_soc_bytes *params = (void *)kcontrol->private_value; struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol); struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + int val_bytes = snd_soc_component_regmap_val_bytes(cmpnt); u32 i, reg_ctrl = params->soc.base; - u32 reg_data = reg_ctrl + cmpnt->val_bytes; + u32 reg_data = reg_ctrl + val_bytes; s32 *data = (s32 *)biquad_coeff_buffer; for (i = 0; i < params->soc.num_regs; i++) From 7a478db6980f88969590d41b8b4f5a4b06a60881 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 12 Mar 2026 00:15:21 +0000 Subject: [PATCH 158/359] ASoC: soc-ops: use snd_soc_component_regmap_val_bytes() component has component->val_bytes which is set via snd_soc_component_setup_regmap(). But it can be calculated via component->regmap. No need to keep it as component->val_bytes. Use snd_soc_component_regmap_val_bytes(). Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/875x71zyw5.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-ops.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c index f966d4e13c7f..01d20a71ed77 100644 --- a/sound/soc/soc-ops.c +++ b/sound/soc/soc-ops.c @@ -514,9 +514,10 @@ int snd_soc_bytes_info(struct snd_kcontrol *kcontrol, { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct soc_bytes *params = (void *)kcontrol->private_value; + int val_bytes = snd_soc_component_regmap_val_bytes(component); uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; - uinfo->count = params->num_regs * component->val_bytes; + uinfo->count = params->num_regs * val_bytes; return 0; } @@ -527,18 +528,19 @@ int snd_soc_bytes_get(struct snd_kcontrol *kcontrol, { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct soc_bytes *params = (void *)kcontrol->private_value; + int val_bytes = snd_soc_component_regmap_val_bytes(component); int ret; if (component->regmap) ret = regmap_raw_read(component->regmap, params->base, ucontrol->value.bytes.data, - params->num_regs * component->val_bytes); + params->num_regs * val_bytes); else ret = -EINVAL; /* Hide any masked bytes to ensure consistent data reporting */ if (ret == 0 && params->mask) { - switch (component->val_bytes) { + switch (val_bytes) { case 1: ucontrol->value.bytes.data[0] &= ~params->mask; break; @@ -564,13 +566,14 @@ int snd_soc_bytes_put(struct snd_kcontrol *kcontrol, { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct soc_bytes *params = (void *)kcontrol->private_value; + int val_bytes = snd_soc_component_regmap_val_bytes(component); unsigned int val, mask; int ret, len; if (!component->regmap || !params->num_regs) return -EINVAL; - len = params->num_regs * component->val_bytes; + len = params->num_regs * val_bytes; void *data __free(kfree) = kmemdup(ucontrol->value.bytes.data, len, GFP_KERNEL | GFP_DMA); @@ -589,7 +592,7 @@ int snd_soc_bytes_put(struct snd_kcontrol *kcontrol, val &= params->mask; - switch (component->val_bytes) { + switch (val_bytes) { case 1: ((u8 *)data)[0] &= ~params->mask; ((u8 *)data)[0] |= val; @@ -712,9 +715,10 @@ int snd_soc_get_xr_sx(struct snd_kcontrol *kcontrol, struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct soc_mreg_control *mc = (struct soc_mreg_control *)kcontrol->private_value; + int val_bytes = snd_soc_component_regmap_val_bytes(component); unsigned int regbase = mc->regbase; unsigned int regcount = mc->regcount; - unsigned int regwshift = component->val_bytes * BITS_PER_BYTE; + unsigned int regwshift = val_bytes * BITS_PER_BYTE; unsigned int regwmask = GENMASK(regwshift - 1, 0); unsigned long mask = GENMASK(mc->nbits - 1, 0); long val = 0; @@ -756,9 +760,10 @@ int snd_soc_put_xr_sx(struct snd_kcontrol *kcontrol, struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct soc_mreg_control *mc = (struct soc_mreg_control *)kcontrol->private_value; + int val_bytes = snd_soc_component_regmap_val_bytes(component); unsigned int regbase = mc->regbase; unsigned int regcount = mc->regcount; - unsigned int regwshift = component->val_bytes * BITS_PER_BYTE; + unsigned int regwshift = val_bytes * BITS_PER_BYTE; unsigned int regwmask = GENMASK(regwshift - 1, 0); unsigned long mask = GENMASK(mc->nbits - 1, 0); long val = ucontrol->value.integer.value[0]; From b84d27531744e046a72120882f513f42e361269d Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 12 Mar 2026 00:15:55 +0000 Subject: [PATCH 159/359] ASoC: soc-component: remove component->val_bytes component has component->val_bytes which is set via snd_soc_component_setup_regmap(). But it can be calculated via component->regmap. No need to keep it as component->val_bytes. No one is using component->val_bytes. Remove it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/874imlzyv8.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 2 -- sound/soc/soc-component.c | 10 ---------- sound/soc/soc-core.c | 2 -- 3 files changed, 14 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index c08922290cf1..65dc540246aa 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -224,7 +224,6 @@ struct snd_soc_component { int num_dai; struct regmap *regmap; - int val_bytes; struct mutex io_mutex; @@ -328,7 +327,6 @@ 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); -void snd_soc_component_setup_regmap(struct snd_soc_component *component); #ifdef CONFIG_REGMAP void snd_soc_component_init_regmap(struct snd_soc_component *component, struct regmap *regmap); diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 9dc2cbdc2dda..2eaad5db2130 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -342,15 +342,6 @@ int snd_soc_component_of_xlate_dai_name(struct snd_soc_component *component, return -ENOTSUPP; } -void snd_soc_component_setup_regmap(struct snd_soc_component *component) -{ - int val_bytes = regmap_get_val_bytes(component->regmap); - - /* Errors are legitimate for non-integer byte multiples */ - if (val_bytes > 0) - component->val_bytes = val_bytes; -} - int snd_soc_component_regmap_val_bytes(struct snd_soc_component *component) { int val_bytes; @@ -385,7 +376,6 @@ void snd_soc_component_init_regmap(struct snd_soc_component *component, struct regmap *regmap) { component->regmap = regmap; - snd_soc_component_setup_regmap(component); } EXPORT_SYMBOL_GPL(snd_soc_component_init_regmap); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 573693e21780..112857a5300b 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2909,8 +2909,6 @@ int snd_soc_add_component(struct snd_soc_component *component, if (!component->regmap) component->regmap = dev_get_regmap(component->dev, NULL); - if (component->regmap) - snd_soc_component_setup_regmap(component); } /* see for_each_component */ From e3f1ce073a0dd2b319bd1541c461f153306ca163 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 20 Mar 2026 16:17:31 +0100 Subject: [PATCH 160/359] ASoC: wm_adsp: select CONFIG_SND_SOC_WM_ADSP from all users The addition of the kunit test made it possible to enable the WM_ADSP driver even when there are no users. However, an unintended side-effect was that it is also possible to turn it off when it is actually required, leading to build failures: ERROR: modpost: "wm_halo_init" [sound/soc/codecs/snd-soc-cs35l45.ko] undefined! ERROR: modpost: "wm_adsp2_remove" [sound/soc/codecs/snd-soc-cs35l45.ko] undefined! ERROR: modpost: "wm_adsp_hibernate" [sound/soc/codecs/snd-soc-cs35l45.ko] undefined! ERROR: modpost: "wm_adsp2_component_probe" [sound/soc/codecs/snd-soc-cs35l45.ko] undefined! Reverse the logic to replace the ununual list of 'default y if ....' with the regular 'select' that do the same thing but prevent it from being disabled if that would break the build. Fixes: bf2d44d07de7 ("ASoC: wm_adsp: Add kunit test for firmware file search") Signed-off-by: Arnd Bergmann Reviewed-by: Richard Fitzgerald Link: https://patch.msgid.link/20260320151752.3439218-1-arnd@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index f9e6a83e55c6..d6104796db4f 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -401,28 +401,6 @@ config SND_SOC_WM_ADSP tristate "Cirrus Logic wm_adsp driver" if KUNIT select FW_CS_DSP select SND_SOC_COMPRESS - default y if SND_SOC_MADERA=y - default y if SND_SOC_CS47L24=y - default y if SND_SOC_WM5102=y - default y if SND_SOC_WM5110=y - default y if SND_SOC_WM2200=y - default y if SND_SOC_CS35L41_SPI=y - default y if SND_SOC_CS35L41_I2C=y - default y if SND_SOC_CS35L45_SPI=y - default y if SND_SOC_CS35L45_I2C=y - default y if SND_SOC_CS35L56=y - default y if SND_SOC_CS48L32=y - default m if SND_SOC_MADERA=m - default m if SND_SOC_CS47L24=m - default m if SND_SOC_WM5102=m - default m if SND_SOC_WM5110=m - default m if SND_SOC_WM2200=m - default m if SND_SOC_CS35L41_SPI=m - default m if SND_SOC_CS35L41_I2C=m - default m if SND_SOC_CS35L45_SPI=m - default m if SND_SOC_CS35L45_I2C=m - default m if SND_SOC_CS35L56=m - default m if SND_SOC_CS48L32=m config SND_SOC_WM_ADSP_TEST tristate "KUnit tests for Cirrus Logic wm_adsp" if !KUNIT_ALL_TESTS @@ -834,6 +812,7 @@ config SND_SOC_CS35L41_LIB config SND_SOC_CS35L41 tristate + select SND_SOC_WM_ADSP config SND_SOC_CS35L41_SPI tristate "Cirrus Logic CS35L41 CODEC (SPI)" @@ -852,6 +831,7 @@ config SND_SOC_CS35L41_I2C config SND_SOC_CS35L45 tristate select REGMAP_IRQ + select SND_SOC_WM_ADSP config SND_SOC_CS35L45_SPI tristate "Cirrus Logic CS35L45 CODEC (SPI)" @@ -875,6 +855,7 @@ config SND_SOC_CS35L45_I2C config SND_SOC_CS35L56 tristate + select SND_SOC_WM_ADSP config SND_SOC_CS35L56_SHARED select SND_SOC_CS_AMP_LIB @@ -1112,6 +1093,7 @@ config SND_SOC_CS47L15 config SND_SOC_CS47L24 tristate depends on MFD_CS47L24 && MFD_ARIZONA + select SND_SOC_WM_ADSP config SND_SOC_CS47L35 tristate @@ -1133,6 +1115,7 @@ config SND_SOC_CS48L32 tristate "Cirrus Logic CS48L32 audio DSP" depends on SPI_MASTER select REGMAP_SPI + select SND_SOC_WM_ADSP help Build the codec driver for the Cirrus Logic CS48L32 audio DSP. @@ -1391,6 +1374,7 @@ config SND_SOC_LOCHNAGAR_SC config SND_SOC_MADERA tristate + select SND_SOC_WM_ADSP default y if SND_SOC_CS47L15=y default y if SND_SOC_CS47L35=y default y if SND_SOC_CS47L85=y @@ -2489,6 +2473,7 @@ config SND_SOC_WM2000 config SND_SOC_WM2200 tristate depends on I2C + select SND_SOC_WM_ADSP config SND_SOC_WM5100 tristate @@ -2497,10 +2482,12 @@ config SND_SOC_WM5100 config SND_SOC_WM5102 tristate depends on MFD_WM5102 && MFD_ARIZONA + select SND_SOC_WM_ADSP config SND_SOC_WM5110 tristate depends on MFD_WM5110 && MFD_ARIZONA + select SND_SOC_WM_ADSP config SND_SOC_WM8350 tristate From d57fe7b02072ad04a41517e40c737ed7c11f4520 Mon Sep 17 00:00:00 2001 From: Kexin Sun Date: Sat, 21 Mar 2026 19:50:18 +0800 Subject: [PATCH 161/359] ASoC: generic: update outdated comment for removed soc_bind_dai_link() The function soc_bind_dai_link() was first merged into snd_soc_add_dai_link() by commit 63dc47da1f39 ("ASoC: soc-core: merge snd_soc_add_dai_link() and soc_bind_dai_link()"), and later renamed to snd_soc_add_pcm_runtime() by commit 0c04800424c4 ("ASoC: soc-core: rename snd_soc_add_dai_link() to snd_soc_add_pcm_runtime()"). In simple-card.c, also adjust the wording since snd_soc_add_pcm_runtime() no longer uses "xxx_of_node" fields but matches components by of_node through snd_soc_find_dai() and snd_soc_is_matching_component(). In simple-card-utils.c, simply update the function name to its successor snd_soc_add_pcm_runtime(). Assisted-by: unnamed:deepseek-v3.2 coccinelle Signed-off-by: Kexin Sun Acked-by: Kuninori Morimoto Link: https://patch.msgid.link/20260321115018.9481-1-kexinsun@smail.nju.edu.cn Signed-off-by: Mark Brown --- sound/soc/generic/simple-card-utils.c | 2 +- sound/soc/generic/simple-card.c | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 3115e1f37c0c..2a072515a853 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -718,7 +718,7 @@ void simple_util_canonicalize_cpu(struct snd_soc_dai_link_component *cpus, int is_single_links) { /* - * In soc_bind_dai_link() will check cpu name after + * In snd_soc_add_pcm_runtime() will check cpu name after * of_node matching if dai_link has cpu_dai_name. * but, it will never match if name was created by * fmt_single_name() remove cpu_dai_name if cpu_args diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index 06638f9a74b8..9937a991846e 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -48,7 +48,8 @@ static int simple_parse_platform(struct simple_util_priv *priv, /* * Get node via "sound-dai = <&phandle port>" - * it will be used as xxx_of_node on soc_bind_dai_link() + * It will be used as the of_node for component matching during + * snd_soc_add_pcm_runtime(). */ ret = of_parse_phandle_with_args(node, DAI, CELL, 0, &args); if (ret) @@ -76,7 +77,8 @@ static int simple_parse_dai(struct simple_util_priv *priv, /* * Get node via "sound-dai = <&phandle port>" - * it will be used as xxx_of_node on soc_bind_dai_link() + * It will be used as the of_node for component matching during + * snd_soc_add_pcm_runtime(). */ ret = of_parse_phandle_with_args(node, DAI, CELL, 0, &args); if (ret) From 1001b4735137894a6bd7b5bf73375cfa27b6125e Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Mon, 23 Mar 2026 17:56:04 +0000 Subject: [PATCH 162/359] ASoC: soc-topology: fix __le32 conversion in printed values A number of dev_dbg() and dev_err() calls get passed values that are of __le32 type which does not get noticed by sparse until my variadic checking patches. There are a number of these, and we should probably fix these up. The sparse warnings are numerous so the first few are listed here that this patch fixes: sound/soc/soc-topology.c:226:9: warning: incorrect type in argument 4 (different base types) sound/soc/soc-topology.c:226:9: expected int sound/soc/soc-topology.c:226:9: got restricted __le32 [usertype] get sound/soc/soc-topology.c:226:9: warning: incorrect type in argument 5 (different base types) sound/soc/soc-topology.c:226:9: expected int sound/soc/soc-topology.c:226:9: got restricted __le32 [usertype] put sound/soc/soc-topology.c:226:9: warning: incorrect type in argument 6 (different base types) sound/soc/soc-topology.c:226:9: expected int sound/soc/soc-topology.c:226:9: got restricted __le32 [usertype] info sound/soc/soc-topology.c:226:9: warning: incorrect type in argument 4 (different base types) sound/soc/soc-topology.c:226:9: expected int sound/soc/soc-topology.c:226:9: got restricted __le32 [usertype] get sound/soc/soc-topology.c:226:9: warning: incorrect type in argument 5 (different base types) sound/soc/soc-topology.c:226:9: expected int sound/soc/soc-topology.c:226:9: got restricted __le32 [usertype] put sound/soc/soc-topology.c:226:9: warning: incorrect type in argument 6 (different base types) sound/soc/soc-topology.c:226:9: expected int sound/soc/soc-topology.c:226:9: got restricted __le32 [usertype] info sound/soc/soc-topology.c:226:9: warning: incorrect type in argument 4 (different base types) sound/soc/soc-topology.c:226:9: expected int sound/soc/soc-topology.c:226:9: got restricted __le32 [usertype] get sound/soc/soc-topology.c:226:9: warning: incorrect type in argument 5 (different base types) sound/soc/soc-topology.c:226:9: expected int sound/soc/soc-topology.c:226:9: got restricted __le32 [usertype] put sound/soc/soc-topology.c:226:9: warning: incorrect type in argument 6 (different base types) sound/soc/soc-topology.c:226:9: expected int sound/soc/soc-topology.c:226:9: got restricted __le32 [usertype] info Signed-off-by: Ben Dooks Link: https://patch.msgid.link/20260323175604.19315-1-ben.dooks@codethink.co.uk Signed-off-by: Mark Brown --- sound/soc/soc-topology.c | 73 ++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 064b8d76b955..85679c8e0229 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -224,8 +224,11 @@ static inline void soc_control_err(struct soc_tplg *tplg, struct snd_soc_tplg_ctl_hdr *hdr, const char *name) { dev_err(tplg->dev, - "ASoC: no complete control IO handler for %s type (g,p,i) %d:%d:%d at 0x%lx\n", - name, hdr->ops.get, hdr->ops.put, hdr->ops.info, + "ASoC: no complete control IO handler for %s type (g,p,i) %u:%u:%u at 0x%lx\n", + name, + le32_to_cpu(hdr->ops.get), + le32_to_cpu(hdr->ops.put), + le32_to_cpu(hdr->ops.info), soc_tplg_get_offset(tplg)); } @@ -238,17 +241,18 @@ static int soc_tplg_vendor_load(struct soc_tplg *tplg, if (tplg->ops && tplg->ops->vendor_load) ret = tplg->ops->vendor_load(tplg->comp, tplg->index, hdr); else { - dev_err(tplg->dev, "ASoC: no vendor load callback for ID %d\n", - hdr->vendor_type); + dev_err(tplg->dev, "ASoC: no vendor load callback for ID %u\n", + le32_to_cpu(hdr->vendor_type)); return -EINVAL; } if (ret < 0) dev_err(tplg->dev, - "ASoC: vendor load failed at hdr offset %ld/0x%lx for type %d:%d\n", + "ASoC: vendor load failed at hdr offset %ld/0x%lx for type %u:%u\n", soc_tplg_get_hdr_offset(tplg), soc_tplg_get_hdr_offset(tplg), - hdr->type, hdr->vendor_type); + le32_to_cpu(hdr->type), + le32_to_cpu(hdr->vendor_type)); return ret; } @@ -625,8 +629,8 @@ static int soc_tplg_create_tlv(struct soc_tplg *tplg, /* TODO: add support for other TLV types */ default: - dev_dbg(tplg->dev, "Unsupported TLV type %d\n", - tplg_tlv->type); + dev_dbg(tplg->dev, "Unsupported TLV type %u\n", + le32_to_cpu(tplg_tlv->type)); return -EINVAL; } } @@ -653,7 +657,7 @@ static int soc_tplg_control_dmixer_create(struct soc_tplg *tplg, struct snd_kcon tplg->pos += sizeof(struct snd_soc_tplg_mixer_control) + le32_to_cpu(mc->priv.size); dev_dbg(tplg->dev, "ASoC: adding mixer kcontrol %s with access 0x%x\n", - mc->hdr.name, mc->hdr.access); + mc->hdr.name, le32_to_cpu(mc->hdr.access)); kc->name = devm_kstrdup(tplg->dev, mc->hdr.name, GFP_KERNEL); if (!kc->name) @@ -776,7 +780,7 @@ static int soc_tplg_control_denum_create(struct soc_tplg *tplg, struct snd_kcont tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + le32_to_cpu(ec->priv.size)); - dev_dbg(tplg->dev, "ASoC: adding enum kcontrol %s size %d\n", ec->hdr.name, ec->items); + dev_dbg(tplg->dev, "ASoC: adding enum kcontrol %s size %u\n", ec->hdr.name, le32_to_cpu(ec->items)); kc->name = devm_kstrdup(tplg->dev, ec->hdr.name, GFP_KERNEL); if (!kc->name) @@ -811,8 +815,8 @@ static int soc_tplg_control_denum_create(struct soc_tplg *tplg, struct snd_kcont } break; default: - dev_err(tplg->dev, "ASoC: invalid enum control type %d for %s\n", - ec->hdr.ops.info, ec->hdr.name); + dev_err(tplg->dev, "ASoC: invalid enum control type %u for %s\n", + le32_to_cpu(ec->hdr.ops.info), ec->hdr.name); return -EINVAL; } @@ -846,7 +850,7 @@ static int soc_tplg_control_dbytes_create(struct soc_tplg *tplg, struct snd_kcon tplg->pos += (sizeof(struct snd_soc_tplg_bytes_control) + le32_to_cpu(be->priv.size)); dev_dbg(tplg->dev, "ASoC: adding bytes kcontrol %s with access 0x%x\n", - be->hdr.name, be->hdr.access); + be->hdr.name, le32_to_cpu(be->hdr.access)); kc->name = devm_kstrdup(tplg->dev, be->hdr.name, GFP_KERNEL); if (!kc->name) @@ -976,7 +980,7 @@ static int soc_tplg_kcontrol_elems_load(struct soc_tplg *tplg, int ret; int i; - dev_dbg(tplg->dev, "ASoC: adding %d kcontrols at 0x%lx\n", hdr->count, + dev_dbg(tplg->dev, "ASoC: adding %u kcontrols at 0x%lx\n", le32_to_cpu(hdr->count), soc_tplg_get_offset(tplg)); for (i = 0; i < le32_to_cpu(hdr->count); i++) { @@ -1003,8 +1007,8 @@ static int soc_tplg_kcontrol_elems_load(struct soc_tplg *tplg, } if (ret < 0) { - dev_err(tplg->dev, "ASoC: invalid control type: %d, index: %d at 0x%lx\n", - control_hdr->type, i, soc_tplg_get_offset(tplg)); + dev_err(tplg->dev, "ASoC: invalid control type: %u, index: %d at 0x%lx\n", + le32_to_cpu(control_hdr->type), i, soc_tplg_get_offset(tplg)); return ret; } } @@ -1040,8 +1044,8 @@ static int soc_tplg_dapm_graph_elems_load(struct soc_tplg *tplg, count, le32_to_cpu(hdr->payload_size), "graph")) return -EINVAL; - dev_dbg(tplg->dev, "ASoC: adding %d DAPM routes for index %d\n", count, - hdr->index); + dev_dbg(tplg->dev, "ASoC: adding %d DAPM routes for index %u\n", count, + le32_to_cpu(hdr->index)); for (i = 0; i < count; i++) { route = devm_kzalloc(tplg->dev, sizeof(*route), GFP_KERNEL); @@ -1116,8 +1120,8 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) return -EINVAL; - dev_dbg(tplg->dev, "ASoC: creating DAPM widget %s id %d\n", - w->name, w->id); + dev_dbg(tplg->dev, "ASoC: creating DAPM widget %s id %u\n", + w->name, le32_to_cpu(w->id)); memset(&template, 0, sizeof(template)); @@ -1200,8 +1204,9 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, goto hdr_err; break; default: - dev_err(tplg->dev, "ASoC: invalid widget control type %d:%d:%d\n", - control_hdr->ops.get, control_hdr->ops.put, + dev_err(tplg->dev, "ASoC: invalid widget control type %u:%u:%u\n", + le32_to_cpu(control_hdr->ops.get), + le32_to_cpu(control_hdr->ops.put), le32_to_cpu(control_hdr->ops.info)); ret = -EINVAL; goto hdr_err; @@ -1745,8 +1750,8 @@ static int soc_tplg_link_config(struct soc_tplg *tplg, link = snd_soc_find_dai_link(tplg->comp->card, le32_to_cpu(cfg->id), name, stream_name); if (!link) { - dev_err(tplg->dev, "ASoC: physical link %s (id %d) not exist\n", - name, cfg->id); + dev_err(tplg->dev, "ASoC: physical link %s (id %u) not exist\n", + name, le32_to_cpu(cfg->id)); return -EINVAL; } @@ -1949,7 +1954,7 @@ static int soc_tplg_valid_header(struct soc_tplg *tplg, { if (le32_to_cpu(hdr->size) != sizeof(*hdr)) { dev_err(tplg->dev, - "ASoC: invalid header size for type %d at offset 0x%lx size 0x%zx.\n", + "ASoC: invalid header size for type %u at offset 0x%lx size 0x%zx.\n", le32_to_cpu(hdr->type), soc_tplg_get_hdr_offset(tplg), tplg->fw->size); return -EINVAL; @@ -1957,9 +1962,9 @@ static int soc_tplg_valid_header(struct soc_tplg *tplg, if (soc_tplg_get_hdr_offset(tplg) + le32_to_cpu(hdr->payload_size) >= tplg->fw->size) { dev_err(tplg->dev, - "ASoC: invalid header of type %d at offset %ld payload_size %d\n", + "ASoC: invalid header of type %u at offset %ld payload_size %u\n", le32_to_cpu(hdr->type), soc_tplg_get_hdr_offset(tplg), - hdr->payload_size); + le32_to_cpu(hdr->payload_size)); return -EINVAL; } @@ -1967,7 +1972,7 @@ static int soc_tplg_valid_header(struct soc_tplg *tplg, if (le32_to_cpu(hdr->magic) == SOC_TPLG_MAGIC_BIG_ENDIAN) { dev_err(tplg->dev, "ASoC: pass %d big endian not supported header got %x at offset 0x%lx size 0x%zx.\n", - tplg->pass, hdr->magic, + tplg->pass, le32_to_cpu(hdr->magic), soc_tplg_get_hdr_offset(tplg), tplg->fw->size); return -EINVAL; } @@ -1975,7 +1980,7 @@ static int soc_tplg_valid_header(struct soc_tplg *tplg, if (le32_to_cpu(hdr->magic) != SND_SOC_TPLG_MAGIC) { dev_err(tplg->dev, "ASoC: pass %d does not have a valid header got %x at offset 0x%lx size 0x%zx.\n", - tplg->pass, hdr->magic, + tplg->pass, le32_to_cpu(hdr->magic), soc_tplg_get_hdr_offset(tplg), tplg->fw->size); return -EINVAL; } @@ -1985,7 +1990,7 @@ static int soc_tplg_valid_header(struct soc_tplg *tplg, le32_to_cpu(hdr->abi) < SND_SOC_TPLG_ABI_VERSION_MIN) { dev_err(tplg->dev, "ASoC: pass %d invalid ABI version got 0x%x need 0x%x at offset 0x%lx size 0x%zx.\n", - tplg->pass, hdr->abi, + tplg->pass, le32_to_cpu(hdr->abi), SND_SOC_TPLG_ABI_VERSION, soc_tplg_get_hdr_offset(tplg), tplg->fw->size); return -EINVAL; @@ -2054,9 +2059,11 @@ static int soc_tplg_load_header(struct soc_tplg *tplg, if (tplg->pass == hdr_pass) { dev_dbg(tplg->dev, - "ASoC: Got 0x%x bytes of type %d version %d vendor %d at pass %d\n", - hdr->payload_size, hdr->type, hdr->version, - hdr->vendor_type, tplg->pass); + "ASoC: Got 0x%x bytes of type %u version %u vendor %u at pass %d\n", + le32_to_cpu(hdr->payload_size), + le32_to_cpu(hdr->type), + le32_to_cpu(hdr->version), + le32_to_cpu(hdr->vendor_type), tplg->pass); return elem_load(tplg, hdr); } From 51b0909d0bc479b37db5f26674a7c41676aa8f80 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 24 Mar 2026 01:24:48 +0800 Subject: [PATCH 163/359] ASoC: sun4i-spdif: Make reset control non-optional and check for all errors The logic around the reset control is weird. The driver already has quirks describing which compatible strings require a reset control, but the reset control is acquired using the "optional" API. Then any errors other than deferred probe are ignored. Instead, switch to the non-optional variant. Also use the deassered variant to simplify the code. This also fixes a missing reset assert upon removal. Last, clean up the error message printing with dev_err_probe(). Signed-off-by: Chen-Yu Tsai Reviewed-by: Jernej Skrabec Link: https://patch.msgid.link/20260323172450.1259111-1-wens@kernel.org Signed-off-by: Mark Brown --- sound/soc/sunxi/sun4i-spdif.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c index 65de03ca3ad2..c2ec19437cd7 100644 --- a/sound/soc/sunxi/sun4i-spdif.c +++ b/sound/soc/sunxi/sun4i-spdif.c @@ -712,15 +712,10 @@ static int sun4i_spdif_probe(struct platform_device *pdev) platform_set_drvdata(pdev, host); if (quirks->has_reset) { - host->rst = devm_reset_control_get_optional_exclusive(&pdev->dev, - NULL); - if (PTR_ERR(host->rst) == -EPROBE_DEFER) { - ret = -EPROBE_DEFER; - dev_err(&pdev->dev, "Failed to get reset: %d\n", ret); - return ret; - } - if (!IS_ERR(host->rst)) - reset_control_deassert(host->rst); + host->rst = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); + if (IS_ERR(host->rst)) + return dev_err_probe(&pdev->dev, PTR_ERR(host->rst), + "Failed to get reset\n"); } ret = devm_snd_soc_register_component(&pdev->dev, From d12dc8c558b955e74bf3dc9c01926c3c109f2d69 Mon Sep 17 00:00:00 2001 From: Niranjan H Y Date: Tue, 24 Mar 2026 09:42:59 +0530 Subject: [PATCH 164/359] ASoC: sdw_utils: add vendor_id to asoc_sdw_codec_info struct asoc_sdw_codec_info has part_id which is not sufficient to uniquely identify devices. This change adds the vendor_id field and updates the codec_info list with the corresponding vendor id as per the Manufacturer's id in https://mid.mipi.org/ Signed-off-by: Niranjan H Y Reviewed-by: Bard Liao Reviewed-by: Pierre-Louis Bossart Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260324041300.784-1-niranjan.hy@ti.com Signed-off-by: Mark Brown --- include/sound/soc_sdw_utils.h | 1 + sound/soc/sdw_utils/soc_sdw_utils.c | 48 ++++++++++++++++++++++++++--- sound/soc/sof/intel/hda.c | 7 +++-- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h index 98531e500cbb..48f516ba682f 100644 --- a/include/sound/soc_sdw_utils.h +++ b/include/sound/soc_sdw_utils.h @@ -71,6 +71,7 @@ struct asoc_sdw_aux_info { }; struct asoc_sdw_codec_info { + const int vendor_id; const int part_id; const int version_id; const char *name_prefix; diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 75658148df57..637c640001d6 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -73,6 +73,7 @@ static const struct snd_kcontrol_new rt700_controls[] = { struct asoc_sdw_codec_info codec_info_list[] = { { + .vendor_id = 0x0102, .part_id = 0x0000, /* TAS2783A */ .name_prefix = "tas2783", .dais = { @@ -92,6 +93,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x025d, .part_id = 0x700, .name_prefix = "rt700", .dais = { @@ -110,6 +112,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x025d, .part_id = 0x711, .name_prefix = "rt711", .version_id = 3, @@ -131,6 +134,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x025d, .part_id = 0x711, .name_prefix = "rt711", .version_id = 2, @@ -152,6 +156,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x025d, .part_id = 0x712, .name_prefix = "rt712", .version_id = 3, @@ -194,6 +199,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 3, }, { + .vendor_id = 0x025d, .part_id = 0x1712, .name_prefix = "rt712-dmic", .version_id = 3, @@ -209,6 +215,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x025d, .part_id = 0x713, .name_prefix = "rt713", .version_id = 3, @@ -237,6 +244,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 2, }, { + .vendor_id = 0x025d, .part_id = 0x1713, .name_prefix = "rt713-dmic", .version_id = 3, @@ -252,6 +260,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x025d, .part_id = 0x1308, .name_prefix = "rt1308", .acpi_id = "10EC1308", @@ -275,6 +284,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .ops = &soc_sdw_rt1308_i2s_ops, }, { + .vendor_id = 0x025d, .part_id = 0x1316, .name_prefix = "rt1316", .dais = { @@ -296,6 +306,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x025d, .part_id = 0x1318, .name_prefix = "rt1318", .dais = { @@ -317,6 +328,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x025d, .part_id = 0x1320, .name_prefix = "rt1320", .dais = { @@ -338,6 +350,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x025d, .part_id = 0x1321, .name_prefix = "rt1320", .dais = { @@ -359,6 +372,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x025d, .part_id = 0x714, .name_prefix = "rt714", .version_id = 3, @@ -375,6 +389,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x025d, .part_id = 0x715, .name_prefix = "rt715", .version_id = 3, @@ -391,6 +406,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x025d, .part_id = 0x714, .name_prefix = "rt714", .version_id = 2, @@ -407,6 +423,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x025d, .part_id = 0x715, .name_prefix = "rt715", .version_id = 2, @@ -423,6 +440,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x025d, .part_id = 0x721, .name_prefix = "rt721", .version_id = 3, @@ -466,6 +484,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 3, }, { + .vendor_id = 0x025d, .part_id = 0x722, .name_prefix = "rt722", .version_id = 3, @@ -513,6 +532,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 3, }, { + .vendor_id = 0x019f, .part_id = 0x8373, .name_prefix = "Left", .dais = { @@ -533,6 +553,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x019f, .part_id = 0x8363, .name_prefix = "Left", .dais = { @@ -553,6 +574,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x025d, .part_id = 0x5682, .name_prefix = "rt5682", .dais = { @@ -571,6 +593,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x01fa, .part_id = 0x3556, .name_prefix = "AMP", .dais = { @@ -598,6 +621,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 2, }, { + .vendor_id = 0x01fa, .part_id = 0x3557, .name_prefix = "AMP", .dais = { @@ -625,6 +649,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 2, }, { + .vendor_id = 0x01fa, .part_id = 0x3563, .name_prefix = "AMP", .dais = { @@ -652,6 +677,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 2, }, { + .vendor_id = 0x01fa, .part_id = 0x4242, .name_prefix = "cs42l42", .dais = { @@ -670,6 +696,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x01fa, .part_id = 0x4243, .name_prefix = "cs42l43", .count_sidecar = asoc_sdw_bridge_cs35l56_count_sidecar, @@ -724,6 +751,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 4, }, { + .vendor_id = 0x01fa, .part_id = 0x2A3B, .name_prefix = "cs42l43", .count_sidecar = asoc_sdw_bridge_cs35l56_count_sidecar, @@ -778,6 +806,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 4, }, { + .vendor_id = 0x01fa, .part_id = 0x4245, .name_prefix = "cs42l45", .dais = { @@ -814,6 +843,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .aux_num = 1, }, { + .vendor_id = 0x01fa, .part_id = 0x4249, .name_prefix = "cs42l45", /* Use same user-space as cs42l45 */ .dais = { @@ -850,6 +880,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .aux_num = 1, }, { + .vendor_id = 0x01fa, .part_id = 0x4747, .name_prefix = "cs47l47", .dais = { @@ -886,6 +917,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .aux_num = 1, }, { + .vendor_id = 0x0105, .part_id = 0xaaaa, /* generic codec mockup */ .name_prefix = "sdw_mockup_mmulti-function", .version_id = 0, @@ -912,6 +944,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 3, }, { + .vendor_id = 0x0105, .part_id = 0xaa55, /* headset codec mockup */ .name_prefix = "sdw_mockup_headset0", .version_id = 0, @@ -926,6 +959,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x0105, .part_id = 0x55aa, /* amplifier mockup */ .name_prefix = "sdw_mockup_amp1", .version_id = 0, @@ -940,6 +974,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_num = 1, }, { + .vendor_id = 0x0105, .part_id = 0x5555, .name_prefix = "sdw_mockup_mic0", .version_id = 0, @@ -964,9 +999,10 @@ EXPORT_SYMBOL_NS(asoc_sdw_get_codec_info_list_count, "SND_SOC_SDW_UTILS"); struct asoc_sdw_codec_info *asoc_sdw_find_codec_info_part(const u64 adr) { - unsigned int part_id, sdw_version; + unsigned int vendor_id, part_id, sdw_version; int i; + vendor_id = SDW_MFG_ID(adr); part_id = SDW_PART_ID(adr); sdw_version = SDW_VERSION(adr); for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) @@ -975,6 +1011,7 @@ struct asoc_sdw_codec_info *asoc_sdw_find_codec_info_part(const u64 adr) * version_id is not specified in the codec info. */ if (part_id == codec_info_list[i].part_id && + vendor_id == codec_info_list[i].vendor_id && (!codec_info_list[i].version_id || sdw_version == codec_info_list[i].version_id)) return &codec_info_list[i]; @@ -989,6 +1026,7 @@ static struct asoc_sdw_codec_info *asoc_sdw_find_codec_info_sdw_id(const struct for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) if (id->part_id == codec_info_list[i].part_id && + id->mfg_id == codec_info_list[i].vendor_id && (!codec_info_list[i].version_id || id->sdw_version == codec_info_list[i].version_id)) return &codec_info_list[i]; @@ -1093,8 +1131,8 @@ int asoc_sdw_rtd_init(struct snd_soc_pcm_runtime *rtd) ret = snd_soc_add_card_controls(card, codec_info->dais[dai_index].controls, codec_info->dais[dai_index].num_controls); if (ret) { - dev_err(card->dev, "%#x controls addition failed: %d\n", - codec_info->part_id, ret); + dev_err(card->dev, "%#x-%#x controls addition failed: %d\n", + codec_info->vendor_id, codec_info->part_id, ret); return ret; } } @@ -1103,8 +1141,8 @@ int asoc_sdw_rtd_init(struct snd_soc_pcm_runtime *rtd) codec_info->dais[dai_index].widgets, codec_info->dais[dai_index].num_widgets); if (ret) { - dev_err(card->dev, "%#x widgets addition failed: %d\n", - codec_info->part_id, ret); + dev_err(card->dev, "%#x-%#x widgets addition failed: %d\n", + codec_info->vendor_id, codec_info->part_id, ret); return ret; } } diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index c0cc7d3ce526..9ec33147d9af 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -1179,6 +1179,9 @@ static struct snd_soc_acpi_adr_device *find_acpi_adr_device(struct device *dev, struct snd_soc_acpi_endpoint *endpoints; int amp_group_id = 1; + if (sdw_device->id.mfg_id != codec_info_list[i].vendor_id) + continue; + if (sdw_device->id.part_id != codec_info_list[i].part_id) continue; @@ -1193,8 +1196,8 @@ static struct snd_soc_acpi_adr_device *find_acpi_adr_device(struct device *dev, * dereference */ if (!name_prefix) { - dev_err(dev, "codec_info_list name_prefix of part id %#x is missing\n", - codec_info_list[i].part_id); + dev_err(dev, "codec_info_list name_prefix of part id %#x-%#x is missing\n", + codec_info_list[i].vendor_id, codec_info_list[i].part_id); return NULL; } for (j = 0; j < codec_info_list[i].dai_num; j++) { From 141efa53b9875c059d0edec5212fbcd311ec4896 Mon Sep 17 00:00:00 2001 From: DanielePioGenovese Date: Fri, 20 Mar 2026 16:58:42 +0100 Subject: [PATCH 165/359] ASoC: amd: yc: Add MSI Thin A15 B7VF to quirk table Add the MSI Thin A15 B7VF to the quirk table to enable the internal microphone, which otherwise does not work out of the box. Signed-off-by: DanielePioGenovese Link: https://patch.msgid.link/20260320155842.55304-1-delitedaniele@gmail.com 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 1c73d2123b5a..2b37330d4b3c 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -724,6 +724,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_BOARD_NAME, "PM1503CDA"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_PRODUCT_NAME, "Thin A15 B7VF"), + } + }, {} }; From 4478886310e5a2cadb3d07ce2d8564d1fd3e206a Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Tue, 24 Mar 2026 13:34:03 +0000 Subject: [PATCH 166/359] ASoC: SOF: sof-audio: pcm_id is __le32 The pcm_id value is __le32 so convert it before passing to the dev_dbg function to be printed. Also fixup some other uses of __le32 data and a couple of places where %u should have been used instead of %d Picked up by sparse prototype for variadic and printf function checking. Fixes a large number of sparse warnings, such as: sound/soc/sof/pcm.c:84:25: warning: incorrect type in argument 4 (different base types) sound/soc/sof/pcm.c:84:25: expected unsigned int sound/soc/sof/pcm.c:84:25: got restricted __le32 [usertype] pcm_id Signed-off-by: Ben Dooks Acked-by: Peter Ujfalusi Link: https://patch.msgid.link/20260324133403.107708-1-ben.dooks@codethink.co.uk Signed-off-by: Mark Brown --- sound/soc/sof/compress.c | 2 +- sound/soc/sof/pcm.c | 2 +- sound/soc/sof/topology.c | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/compress.c index c6c19df29035..3dbc05bf3b79 100644 --- a/sound/soc/sof/compress.c +++ b/sound/soc/sof/compress.c @@ -247,7 +247,7 @@ static int sof_compr_set_params(struct snd_soc_component *component, ret = snd_sof_set_stream_data_offset(sdev, &spcm->stream[cstream->direction], ipc_params_reply.posn_offset); if (ret < 0) { - dev_err(component->dev, "Invalid stream data offset for Compr %d\n", + dev_err(component->dev, "Invalid stream data offset for Compr %u\n", le32_to_cpu(spcm->pcm.pcm_id)); goto out; } diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index d3af30398305..b2071edeaea6 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -360,7 +360,7 @@ static int sof_pcm_prepare(struct snd_soc_component *component, platform_params = &spcm->platform_params[substream->stream]; ret = sof_widget_list_setup(sdev, spcm, params, platform_params, dir); if (ret < 0) { - dev_err(sdev->dev, "failed widget list set up for pcm %d dir %d\n", + dev_err(sdev->dev, "failed widget list set up for pcm %d dir %u\n", le32_to_cpu(spcm->pcm.pcm_id), dir); spcm->stream[dir].list = NULL; snd_soc_dapm_dai_free_widgets(&list); diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 2740f24cd1d3..73532fad2cb9 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -775,7 +775,7 @@ static int sof_parse_token_sets(struct snd_soc_component *scomp, array); break; default: - dev_err(scomp->dev, "error: unknown token type %d\n", + dev_err(scomp->dev, "error: unknown token type %u\n", le32_to_cpu(array->type)); return -EINVAL; } @@ -880,7 +880,7 @@ skip: ARRAY_SIZE(led_tokens), mc->priv.array, le32_to_cpu(mc->priv.size)); if (ret != 0) { - dev_err(scomp->dev, "error: parse led tokens failed %d\n", + dev_err(scomp->dev, "error: parse led tokens failed %u\n", le32_to_cpu(mc->priv.size)); goto err; } @@ -970,7 +970,7 @@ static int sof_control_load(struct snd_soc_component *scomp, int index, struct snd_sof_control *scontrol; int ret; - dev_dbg(scomp->dev, "tplg: load control type %d name : %s\n", + dev_dbg(scomp->dev, "tplg: load control type %u name : %s\n", le32_to_cpu(hdr->type), hdr->name); scontrol = kzalloc_obj(*scontrol); @@ -1015,7 +1015,7 @@ static int sof_control_load(struct snd_soc_component *scomp, int index, case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: case SND_SOC_TPLG_DAPM_CTL_PIN: default: - dev_warn(scomp->dev, "control type not supported %d:%d:%d\n", + dev_warn(scomp->dev, "control type not supported %u:%u:%u\n", le32_to_cpu(hdr->ops.get), le32_to_cpu(hdr->ops.put), le32_to_cpu(hdr->ops.info)); @@ -1525,7 +1525,7 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, break; case snd_soc_dapm_pga: if (!le32_to_cpu(tw->num_kcontrols)) { - dev_err(scomp->dev, "invalid kcontrol count %d for volume\n", + dev_err(scomp->dev, "invalid kcontrol count %u for volume\n", le32_to_cpu(tw->num_kcontrols)); ret = -EINVAL; break; @@ -1774,7 +1774,7 @@ static int sof_dai_load(struct snd_soc_component *scomp, int index, ARRAY_SIZE(stream_tokens), private->array, le32_to_cpu(private->size)); if (ret) { - dev_err(scomp->dev, "error: parse stream tokens failed %d\n", + dev_err(scomp->dev, "error: parse stream tokens failed %u\n", le32_to_cpu(private->size)); return ret; } From 071efde6b7568fc3ce3b76d9e8c01e7b172539ad Mon Sep 17 00:00:00 2001 From: Wenyuan Li <2063309626@qq.com> Date: Tue, 24 Mar 2026 16:36:05 +0800 Subject: [PATCH 167/359] ASoC: uda1380: fix missing return value checks for I2C operations The driver currently ignores the return values of several I2C operations during register writes, which could lead to silent failures and inconsistent device state. Fix this by: - Moving variable declarations to the beginning of the function (C90). - Checking the return value of every i2c_master_send() and recv() call. - Returning the actual error code if it's negative, or -EIO if the transfer was incomplete. Signed-off-by: Wenyuan Li <2063309626@qq.com> Link: https://patch.msgid.link/tencent_579D057AC557914CF739A2D9EAD045CE7306@qq.com Signed-off-by: Mark Brown --- sound/soc/codecs/uda1380.c | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index 9e9c540a45ca..55b03d1ac8d2 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -95,6 +95,8 @@ static int uda1380_write(struct snd_soc_component *component, unsigned int reg, { struct uda1380_priv *uda1380 = snd_soc_component_get_drvdata(component); u8 data[3]; + unsigned int val; + int ret; /* data is * data[0] is register offset @@ -113,21 +115,27 @@ static int uda1380_write(struct snd_soc_component *component, unsigned int reg, if (!snd_soc_component_active(component) && (reg >= UDA1380_MVOL)) return 0; pr_debug("uda1380: hw write %x val %x\n", reg, value); - if (i2c_master_send(uda1380->i2c, data, 3) == 3) { - unsigned int val; - i2c_master_send(uda1380->i2c, data, 1); - i2c_master_recv(uda1380->i2c, data, 2); - val = (data[0]<<8) | data[1]; - if (val != value) { - pr_debug("uda1380: READ BACK VAL %x\n", - (data[0]<<8) | data[1]); - return -EIO; - } - if (reg >= 0x10) - clear_bit(reg - 0x10, &uda1380_cache_dirty); - return 0; - } else + + ret = i2c_master_send(uda1380->i2c, data, 3); + if (ret != 3) + return ret < 0 ? ret : -EIO; + + ret = i2c_master_send(uda1380->i2c, data, 1); + if (ret != 1) + return ret < 0 ? ret : -EIO; + + ret = i2c_master_recv(uda1380->i2c, data, 2); + if (ret != 2) + return ret < 0 ? ret : -EIO; + + val = (data[0] << 8) | data[1]; + if (val != value) return -EIO; + + if (reg >= 0x10) + clear_bit(reg - 0x10, &uda1380_cache_dirty); + + return 0; } static void uda1380_sync_cache(struct snd_soc_component *component) From e5d5cc44791f7275fd1b5329a9dc374d6ad1bf6c Mon Sep 17 00:00:00 2001 From: Wenyuan Li <2063309626@qq.com> Date: Tue, 24 Mar 2026 16:36:06 +0800 Subject: [PATCH 168/359] ASoC: uda1380: use dev_err() and %pe for better error reporting Convert existing debug and error prints to dev_err() to provide better diagnostic information. Use the %pe format specifier to print human-readable error strings for I2C failures. Signed-off-by: Wenyuan Li <2063309626@qq.com> Link: https://patch.msgid.link/tencent_A26B55F062F2B9C282A142BFC8BCDB1B9105@qq.com Signed-off-by: Mark Brown --- sound/soc/codecs/uda1380.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index 55b03d1ac8d2..63c3ea878fcf 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -117,20 +117,31 @@ static int uda1380_write(struct snd_soc_component *component, unsigned int reg, pr_debug("uda1380: hw write %x val %x\n", reg, value); ret = i2c_master_send(uda1380->i2c, data, 3); - if (ret != 3) - return ret < 0 ? ret : -EIO; + if (ret != 3) { + int err = ret < 0 ? ret : -EIO; + dev_err(component->dev, "write failed: %pe\n", ERR_PTR(err)); + return err; + } ret = i2c_master_send(uda1380->i2c, data, 1); - if (ret != 1) - return ret < 0 ? ret : -EIO; + if (ret != 1) { + int err = ret < 0 ? ret : -EIO; + dev_err(component->dev, "send address failed: %pe\n", ERR_PTR(err)); + return err; +} ret = i2c_master_recv(uda1380->i2c, data, 2); - if (ret != 2) - return ret < 0 ? ret : -EIO; + if (ret != 2) { + int err = ret < 0 ? ret : -EIO; + dev_err(component->dev, "read failed: %pe\n", ERR_PTR(err)); + return err; + } val = (data[0] << 8) | data[1]; - if (val != value) + if (val != value) { + dev_err(component->dev, "read back val %x (expected %x)\n", val, value); return -EIO; + } if (reg >= 0x10) clear_bit(reg - 0x10, &uda1380_cache_dirty); From 3e839947b842b81e3d940044425853477c5a4d7e Mon Sep 17 00:00:00 2001 From: Kexin Sun Date: Tue, 24 Mar 2026 12:14:00 +0800 Subject: [PATCH 169/359] ASoC: update outdated comments for removed snd_soc_new_pcms() The function snd_soc_new_pcms() was removed during the multi-component refactoring in commit f0fba2ad1b6b ("ASoC: multi-component - ASoC Multi-Component Support"). Its PCM creation role is now handled by soc_new_pcm(), which was later moved to sound/soc/soc-pcm.c by commit ddee627cf6bb ("ASoC: core - Separate out PCM operations into new file."). In fsl_dma.c, update the comment to reference soc_new_pcm(). Also remove the stale paragraph about snd_dma_alloc_pages() always allocating in lowmem, since commit e159704f7920 ("ASoC: fsl_dma: Use managed buffer allocation") replaced that call with snd_pcm_set_fixed_buffer_all(). In siu_pcm.c, remove the stale comment referencing snd_soc_new_pcms() and the no-longer-existing socdev structure. Assisted-by: unnamed:deepseek-v3.2 coccinelle Signed-off-by: Kexin Sun Link: https://patch.msgid.link/20260324041400.16217-1-kexinsun@smail.nju.edu.cn Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_dma.c | 10 ++-------- sound/soc/renesas/siu_pcm.c | 1 - 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c index b120b3514fbe..b12474880185 100644 --- a/sound/soc/fsl/fsl_dma.c +++ b/sound/soc/fsl/fsl_dma.c @@ -267,14 +267,8 @@ static irqreturn_t fsl_dma_isr(int irq, void *dev_id) /** * fsl_dma_new: initialize this PCM driver. * - * This function is called when the codec driver calls snd_soc_new_pcms(), - * once for each .dai_link in the machine driver's snd_soc_card - * structure. - * - * snd_dma_alloc_pages() is just a front-end to dma_alloc_coherent(), which - * (currently) always allocates the DMA buffer in lowmem, even if GFP_HIGHMEM - * is specified. Therefore, any DMA buffers we allocate will always be in low - * memory, but we support for 36-bit physical addresses anyway. + * This function is called by soc_new_pcm(), once for each DAI link + * in the machine driver's snd_soc_card structure. * * Regardless of where the memory is actually allocated, since the device can * technically DMA to any 36-bit address, we do need to set the DMA mask to 36. diff --git a/sound/soc/renesas/siu_pcm.c b/sound/soc/renesas/siu_pcm.c index ae5c0c40299d..fd5b20b469a4 100644 --- a/sound/soc/renesas/siu_pcm.c +++ b/sound/soc/renesas/siu_pcm.c @@ -483,7 +483,6 @@ siu_pcm_pointer_dma(struct snd_soc_component *component, static int siu_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd) { - /* card->dev == socdev->dev, see snd_soc_new_pcms() */ struct snd_card *card = rtd->card->snd_card; struct snd_pcm *pcm = rtd->pcm; struct siu_info *info = siu_i2s_data; From e0390dc08f1fca9bfd25b7567e794f7a332703bc Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 25 Mar 2026 12:08:50 +0100 Subject: [PATCH 170/359] ASoC: dt-bindings: mediatek,mt2701-wm8960: Correctly use additionalProperties The binding does not reference any other schema, thus should use "additionalProperties: false" to disallow any undocumented properties. Correct the code and place this after "required:" block to match convention expressed in example-schema.yaml. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260325110849.127051-3-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/mediatek,mt2701-wm8960.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt2701-wm8960.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt2701-wm8960.yaml index cf985461a995..bb6a405b263e 100644 --- a/Documentation/devicetree/bindings/sound/mediatek,mt2701-wm8960.yaml +++ b/Documentation/devicetree/bindings/sound/mediatek,mt2701-wm8960.yaml @@ -28,8 +28,6 @@ properties: $ref: /schemas/types.yaml#/definitions/phandle description: The phandle of the WM8960 audio codec. -unevaluatedProperties: false - required: - compatible - mediatek,platform @@ -38,6 +36,8 @@ required: - pinctrl-names - pinctrl-0 +additionalProperties: false + examples: - | sound { From 6256628ee819ef28c811e9e86ada2988f5681f95 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 25 Mar 2026 12:08:51 +0100 Subject: [PATCH 171/359] ASoC: dt-bindings: mediatek: Adjust style of blocks placement Convention expressed in example-schema.yaml is to place "unevaluatedProperties" part just before example. No functional change. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260325110849.127051-4-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/mediatek,mt7986-wm8960.yaml | 4 ++-- .../devicetree/bindings/sound/mediatek,mt8188-mt6359.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt7986-wm8960.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt7986-wm8960.yaml index 09247ceea3f7..f21cad4bae15 100644 --- a/Documentation/devicetree/bindings/sound/mediatek,mt7986-wm8960.yaml +++ b/Documentation/devicetree/bindings/sound/mediatek,mt7986-wm8960.yaml @@ -36,14 +36,14 @@ properties: required: - sound-dai -unevaluatedProperties: false - required: - compatible - audio-routing - platform - codec +unevaluatedProperties: false + examples: - | sound { diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt8188-mt6359.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt8188-mt6359.yaml index 7ba2ea2dfa0b..539de75eb20d 100644 --- a/Documentation/devicetree/bindings/sound/mediatek,mt8188-mt6359.yaml +++ b/Documentation/devicetree/bindings/sound/mediatek,mt8188-mt6359.yaml @@ -105,12 +105,12 @@ patternProperties: required: - link-name -unevaluatedProperties: false - required: - compatible - mediatek,platform +unevaluatedProperties: false + examples: - | sound { From 884f3101d1edb2bff463a1a4bae162ab011fe73f Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 25 Mar 2026 10:14:24 +0000 Subject: [PATCH 172/359] ASoC: tegra: Use dev_err_probe() in tegra186_asrc probe Log errors in the Tegra186 ASRC probe path using dev_err_probe(). Signed-off-by: Sheetal Link: https://patch.msgid.link/20260325101437.3059693-2-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra186_asrc.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sound/soc/tegra/tegra186_asrc.c b/sound/soc/tegra/tegra186_asrc.c index d2a5ec7c54cc..778a6ef83c4b 100644 --- a/sound/soc/tegra/tegra186_asrc.c +++ b/sound/soc/tegra/tegra186_asrc.c @@ -1016,10 +1016,9 @@ static int tegra186_asrc_platform_probe(struct platform_device *pdev) err = devm_snd_soc_register_component(dev, &tegra186_asrc_cmpnt, tegra186_asrc_dais, ARRAY_SIZE(tegra186_asrc_dais)); - if (err) { - dev_err(dev, "can't register ASRC component, err: %d\n", err); - return err; - } + if (err) + return dev_err_probe(dev, err, + "can't register ASRC component\n"); pm_runtime_enable(dev); From 6205ca05227f26721b8d5920d976ecae6f8ce83e Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 25 Mar 2026 10:14:25 +0000 Subject: [PATCH 173/359] ASoC: tegra: Use dev_err_probe() in tegra186_dspk probe Log errors in the Tegra186 DSPK probe path using dev_err_probe(). Signed-off-by: Sheetal Link: https://patch.msgid.link/20260325101437.3059693-3-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra186_dspk.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/sound/soc/tegra/tegra186_dspk.c b/sound/soc/tegra/tegra186_dspk.c index 8816e4967331..3ec99b946dff 100644 --- a/sound/soc/tegra/tegra186_dspk.c +++ b/sound/soc/tegra/tegra186_dspk.c @@ -496,10 +496,9 @@ static int tegra186_dspk_platform_probe(struct platform_device *pdev) dev_set_drvdata(dev, dspk); dspk->clk_dspk = devm_clk_get(dev, "dspk"); - if (IS_ERR(dspk->clk_dspk)) { - dev_err(dev, "can't retrieve DSPK clock\n"); - return PTR_ERR(dspk->clk_dspk); - } + if (IS_ERR(dspk->clk_dspk)) + return dev_err_probe(dev, PTR_ERR(dspk->clk_dspk), + "can't retrieve DSPK clock\n"); regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(regs)) @@ -516,11 +515,9 @@ static int tegra186_dspk_platform_probe(struct platform_device *pdev) err = devm_snd_soc_register_component(dev, &tegra186_dspk_cmpnt, tegra186_dspk_dais, ARRAY_SIZE(tegra186_dspk_dais)); - if (err) { - dev_err(dev, "can't register DSPK component, err: %d\n", - err); - return err; - } + if (err) + return dev_err_probe(dev, err, + "can't register DSPK component\n"); pm_runtime_enable(dev); From 50e51b84a4b38f2dbffe48e16aef5e0568cc14e9 Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 25 Mar 2026 10:14:27 +0000 Subject: [PATCH 174/359] ASoC: tegra: Add error logging in tegra210_adx driver Log errors in the Tegra210 ADX probe and set_audio_cif paths. Signed-off-by: Sheetal Link: https://patch.msgid.link/20260325101437.3059693-5-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_adx.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/sound/soc/tegra/tegra210_adx.c b/sound/soc/tegra/tegra210_adx.c index 95875c75ddf8..6a5f93f76984 100644 --- a/sound/soc/tegra/tegra210_adx.c +++ b/sound/soc/tegra/tegra210_adx.c @@ -134,8 +134,11 @@ static int tegra210_adx_set_audio_cif(struct snd_soc_dai *dai, memset(&cif_conf, 0, sizeof(struct tegra_cif_conf)); - if (channels < 1 || channels > adx->soc_data->max_ch) + if (channels < 1 || channels > adx->soc_data->max_ch) { + dev_err(dai->dev, "invalid channels: %u (max %u)\n", + channels, adx->soc_data->max_ch); return -EINVAL; + } switch (format) { case SNDRV_PCM_FORMAT_S8: @@ -149,6 +152,7 @@ static int tegra210_adx_set_audio_cif(struct snd_soc_dai *dai, audio_bits = TEGRA_ACIF_BITS_32; break; default: + dev_err(dai->dev, "unsupported format: %d\n", format); return -EINVAL; } @@ -717,10 +721,9 @@ static int tegra210_adx_platform_probe(struct platform_device *pdev) err = devm_snd_soc_register_component(dev, &tegra210_adx_cmpnt, tegra210_adx_dais, ARRAY_SIZE(tegra210_adx_dais)); - if (err) { - dev_err(dev, "can't register ADX component, err: %d\n", err); - return err; - } + if (err) + return dev_err_probe(dev, err, + "can't register ADX component\n"); pm_runtime_enable(dev); From 802d0d6c25b3aecc51c336ba784aedf75c592512 Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 25 Mar 2026 10:14:28 +0000 Subject: [PATCH 175/359] ASoC: tegra: Use dev_err_probe() in tegra210_ahub probe Log errors in the Tegra210 AHUB probe path using dev_err_probe(). Signed-off-by: Sheetal Link: https://patch.msgid.link/20260325101437.3059693-6-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_ahub.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/sound/soc/tegra/tegra210_ahub.c b/sound/soc/tegra/tegra210_ahub.c index a4ed02617472..ccf99c2ac287 100644 --- a/sound/soc/tegra/tegra210_ahub.c +++ b/sound/soc/tegra/tegra210_ahub.c @@ -2267,10 +2267,9 @@ static int tegra_ahub_probe(struct platform_device *pdev) platform_set_drvdata(pdev, ahub); ahub->clk = devm_clk_get(&pdev->dev, "ahub"); - if (IS_ERR(ahub->clk)) { - dev_err(&pdev->dev, "can't retrieve AHUB clock\n"); - return PTR_ERR(ahub->clk); - } + if (IS_ERR(ahub->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(ahub->clk), + "can't retrieve AHUB clock\n"); regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(regs)) @@ -2289,18 +2288,17 @@ static int tegra_ahub_probe(struct platform_device *pdev) ahub->soc_data->cmpnt_drv, ahub->soc_data->dai_drv, ahub->soc_data->num_dais); - if (err) { - dev_err(&pdev->dev, "can't register AHUB component, err: %d\n", - err); - return err; - } + if (err) + return dev_err_probe(&pdev->dev, err, + "can't register AHUB component\n"); pm_runtime_enable(&pdev->dev); err = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); if (err) { pm_runtime_disable(&pdev->dev); - return err; + return dev_err_probe(&pdev->dev, err, + "failed to populate child nodes\n"); } return 0; From d310c08db2d80488435e6cef2b42d09bc63f3f43 Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 25 Mar 2026 10:14:29 +0000 Subject: [PATCH 176/359] ASoC: tegra: Add error logging in tegra210_amx driver Log errors in the Tegra210 AMX probe and set_audio_cif paths. Signed-off-by: Sheetal Link: https://patch.msgid.link/20260325101437.3059693-7-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_amx.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sound/soc/tegra/tegra210_amx.c b/sound/soc/tegra/tegra210_amx.c index bfda82505298..fadfcdf03198 100644 --- a/sound/soc/tegra/tegra210_amx.c +++ b/sound/soc/tegra/tegra210_amx.c @@ -163,6 +163,8 @@ static int tegra210_amx_set_audio_cif(struct snd_soc_dai *dai, audio_bits = TEGRA_ACIF_BITS_32; break; default: + dev_err(dai->dev, "unsupported format: %d\n", + params_format(params)); return -EINVAL; } @@ -767,10 +769,9 @@ static int tegra210_amx_platform_probe(struct platform_device *pdev) err = devm_snd_soc_register_component(dev, &tegra210_amx_cmpnt, tegra210_amx_dais, ARRAY_SIZE(tegra210_amx_dais)); - if (err) { - dev_err(dev, "can't register AMX component, err: %d\n", err); - return err; - } + if (err) + return dev_err_probe(dev, err, + "can't register AMX component\n"); pm_runtime_enable(dev); From ca069c3403ec4ffd1cfbc9a5b274bd9e799a0281 Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 25 Mar 2026 10:14:30 +0000 Subject: [PATCH 177/359] ASoC: tegra: Use dev_err_probe() in tegra210_dmic probe Log errors in the Tegra210 DMIC probe path using dev_err_probe(). Signed-off-by: Sheetal Link: https://patch.msgid.link/20260325101437.3059693-8-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_dmic.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/sound/soc/tegra/tegra210_dmic.c b/sound/soc/tegra/tegra210_dmic.c index 93def7ac4fde..eba88443b7ec 100644 --- a/sound/soc/tegra/tegra210_dmic.c +++ b/sound/soc/tegra/tegra210_dmic.c @@ -507,10 +507,9 @@ static int tegra210_dmic_probe(struct platform_device *pdev) dev_set_drvdata(dev, dmic); dmic->clk_dmic = devm_clk_get(dev, "dmic"); - if (IS_ERR(dmic->clk_dmic)) { - dev_err(dev, "can't retrieve DMIC clock\n"); - return PTR_ERR(dmic->clk_dmic); - } + if (IS_ERR(dmic->clk_dmic)) + return dev_err_probe(dev, PTR_ERR(dmic->clk_dmic), + "can't retrieve DMIC clock\n"); regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(regs)) @@ -528,10 +527,9 @@ static int tegra210_dmic_probe(struct platform_device *pdev) err = devm_snd_soc_register_component(dev, &tegra210_dmic_compnt, tegra210_dmic_dais, ARRAY_SIZE(tegra210_dmic_dais)); - if (err) { - dev_err(dev, "can't register DMIC component, err: %d\n", err); - return err; - } + if (err) + return dev_err_probe(dev, err, + "can't register DMIC component\n"); pm_runtime_enable(dev); From 67b7bcdd9798536ffe2f5d77783d83f40d7e6c27 Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 25 Mar 2026 10:14:31 +0000 Subject: [PATCH 178/359] ASoC: tegra: Add error logging in tegra210_i2s driver Log errors in the Tegra210 I2S probe and runtime callback paths. Signed-off-by: Sheetal Link: https://patch.msgid.link/20260325101437.3059693-9-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_i2s.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/sound/soc/tegra/tegra210_i2s.c b/sound/soc/tegra/tegra210_i2s.c index d8e02f0a3025..d525da498b62 100644 --- a/sound/soc/tegra/tegra210_i2s.c +++ b/sound/soc/tegra/tegra210_i2s.c @@ -161,6 +161,7 @@ static int tegra210_i2s_init(struct snd_soc_dapm_widget *w, stream = SNDRV_PCM_STREAM_CAPTURE; status_reg = TEGRA210_I2S_TX_STATUS + i2s->soc_data->tx_offset; } else { + dev_err(dev, "invalid I2S direction register 0x%x\n", w->reg); return -EINVAL; } @@ -235,6 +236,7 @@ static int tegra210_i2s_set_fmt(struct snd_soc_dai *dai, val = I2S_CTRL_MASTER_EN; break; default: + dev_err(dai->dev, "invalid clock provider format 0x%x\n", fmt); return -EINVAL; } @@ -270,6 +272,7 @@ static int tegra210_i2s_set_fmt(struct snd_soc_dai *dai, tegra210_i2s_set_data_offset(i2s, 0); break; default: + dev_err(dai->dev, "invalid I2S frame format 0x%x\n", fmt); return -EINVAL; } @@ -290,6 +293,7 @@ static int tegra210_i2s_set_fmt(struct snd_soc_dai *dai, val ^= I2S_CTRL_LRCK_POL_MASK; break; default: + dev_err(dai->dev, "invalid I2S clock inversion 0x%x\n", fmt); return -EINVAL; } @@ -1070,10 +1074,9 @@ static int tegra210_i2s_probe(struct platform_device *pdev) dev_set_drvdata(dev, i2s); i2s->clk_i2s = devm_clk_get(dev, "i2s"); - if (IS_ERR(i2s->clk_i2s)) { - dev_err(dev, "can't retrieve I2S bit clock\n"); - return PTR_ERR(i2s->clk_i2s); - } + if (IS_ERR(i2s->clk_i2s)) + return dev_err_probe(dev, PTR_ERR(i2s->clk_i2s), + "can't retrieve I2S bit clock\n"); /* * Not an error, as this clock is needed only when some other I/O @@ -1108,10 +1111,9 @@ static int tegra210_i2s_probe(struct platform_device *pdev) err = devm_snd_soc_register_component(dev, i2s->soc_data->i2s_cmpnt, tegra210_i2s_dais, ARRAY_SIZE(tegra210_i2s_dais)); - if (err) { - dev_err(dev, "can't register I2S component, err: %d\n", err); - return err; - } + if (err) + return dev_err_probe(dev, err, + "can't register I2S component\n"); pm_runtime_enable(dev); From 3d027d4b93b9423d106d85cc82a084032fd2635d Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 25 Mar 2026 10:14:33 +0000 Subject: [PATCH 179/359] ASoC: tegra: Use dev_err_probe() in tegra210_mixer probe Log errors in the Tegra210 Mixer probe path using dev_err_probe(). Signed-off-by: Sheetal Link: https://patch.msgid.link/20260325101437.3059693-11-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_mixer.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sound/soc/tegra/tegra210_mixer.c b/sound/soc/tegra/tegra210_mixer.c index 6d3a2b76fd61..2c0e9c1957c3 100644 --- a/sound/soc/tegra/tegra210_mixer.c +++ b/sound/soc/tegra/tegra210_mixer.c @@ -651,10 +651,9 @@ static int tegra210_mixer_platform_probe(struct platform_device *pdev) err = devm_snd_soc_register_component(dev, &tegra210_mixer_cmpnt, tegra210_mixer_dais, ARRAY_SIZE(tegra210_mixer_dais)); - if (err) { - dev_err(dev, "can't register MIXER component, err: %d\n", err); - return err; - } + if (err) + return dev_err_probe(dev, err, + "can't register MIXER component\n"); pm_runtime_enable(dev); From f2067c1dba07e3816c635601a4b21fbc4a44921c Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 25 Mar 2026 10:14:34 +0000 Subject: [PATCH 180/359] ASoC: tegra: Use dev_err_probe() in tegra210_mvc probe Log errors in the Tegra210 MVC probe path using dev_err_probe(). Signed-off-by: Sheetal Link: https://patch.msgid.link/20260325101437.3059693-12-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_mvc.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sound/soc/tegra/tegra210_mvc.c b/sound/soc/tegra/tegra210_mvc.c index 6cdc5e1f5507..5ab7c07a0ba3 100644 --- a/sound/soc/tegra/tegra210_mvc.c +++ b/sound/soc/tegra/tegra210_mvc.c @@ -741,10 +741,9 @@ static int tegra210_mvc_platform_probe(struct platform_device *pdev) err = devm_snd_soc_register_component(dev, &tegra210_mvc_cmpnt, tegra210_mvc_dais, ARRAY_SIZE(tegra210_mvc_dais)); - if (err) { - dev_err(dev, "can't register MVC component, err: %d\n", err); - return err; - } + if (err) + return dev_err_probe(dev, err, + "can't register MVC component\n"); pm_runtime_enable(dev); From 856ffd8f4aae227dd008e718dbb1cf7d3474a626 Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 25 Mar 2026 10:14:35 +0000 Subject: [PATCH 181/359] ASoC: tegra: Use dev_err_probe() in tegra210_sfc probe Log errors in the Tegra210 SFC probe path using dev_err_probe(). Signed-off-by: Sheetal Link: https://patch.msgid.link/20260325101437.3059693-13-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_sfc.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sound/soc/tegra/tegra210_sfc.c b/sound/soc/tegra/tegra210_sfc.c index b298bf0421b1..09e3c718d29f 100644 --- a/sound/soc/tegra/tegra210_sfc.c +++ b/sound/soc/tegra/tegra210_sfc.c @@ -3608,10 +3608,9 @@ static int tegra210_sfc_platform_probe(struct platform_device *pdev) err = devm_snd_soc_register_component(dev, &tegra210_sfc_cmpnt, tegra210_sfc_dais, ARRAY_SIZE(tegra210_sfc_dais)); - if (err) { - dev_err(dev, "can't register SFC component, err: %d\n", err); - return err; - } + if (err) + return dev_err_probe(dev, err, + "can't register SFC component\n"); pm_runtime_enable(&pdev->dev); From fa11e1cb2b77946c56e690e431193e64a1e06bda Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 25 Mar 2026 10:14:36 +0000 Subject: [PATCH 182/359] ASoC: tegra: Use dev_err_probe() in tegra_asoc_machine probe Log errors in the Tegra ASoC machine driver probe path using dev_err_probe(). Signed-off-by: Sheetal Link: https://patch.msgid.link/20260325101437.3059693-14-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra_asoc_machine.c | 70 ++++++++++++++-------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/sound/soc/tegra/tegra_asoc_machine.c b/sound/soc/tegra/tegra_asoc_machine.c index 10834f9c3422..fcf5f43aed1d 100644 --- a/sound/soc/tegra/tegra_asoc_machine.c +++ b/sound/soc/tegra/tegra_asoc_machine.c @@ -432,7 +432,8 @@ static int tegra_machine_register_codec(struct device *dev, const char *name) pdev = platform_device_register_simple(name, -1, NULL, 0); if (IS_ERR(pdev)) - return PTR_ERR(pdev); + return dev_err_probe(dev, PTR_ERR(pdev), + "failed to register codec %s\n", name); err = devm_add_action_or_reset(dev, tegra_machine_unregister_codec, pdev); @@ -468,32 +469,38 @@ int tegra_asoc_machine_probe(struct platform_device *pdev) gpiod = devm_gpiod_get_optional(dev, "nvidia,hp-mute", GPIOD_OUT_HIGH); machine->gpiod_hp_mute = gpiod; if (IS_ERR(gpiod)) - return PTR_ERR(gpiod); + return dev_err_probe(dev, PTR_ERR(gpiod), + "failed to get hp-mute GPIO\n"); gpiod = devm_gpiod_get_optional(dev, "nvidia,hp-det", GPIOD_IN); machine->gpiod_hp_det = gpiod; if (IS_ERR(gpiod)) - return PTR_ERR(gpiod); + return dev_err_probe(dev, PTR_ERR(gpiod), + "failed to get hp-det GPIO\n"); gpiod = devm_gpiod_get_optional(dev, "nvidia,mic-det", GPIOD_IN); machine->gpiod_mic_det = gpiod; if (IS_ERR(gpiod)) - return PTR_ERR(gpiod); + return dev_err_probe(dev, PTR_ERR(gpiod), + "failed to get mic-det GPIO\n"); gpiod = devm_gpiod_get_optional(dev, "nvidia,spkr-en", GPIOD_OUT_LOW); machine->gpiod_spkr_en = gpiod; if (IS_ERR(gpiod)) - return PTR_ERR(gpiod); + return dev_err_probe(dev, PTR_ERR(gpiod), + "failed to get spkr-en GPIO\n"); gpiod = devm_gpiod_get_optional(dev, "nvidia,int-mic-en", GPIOD_OUT_LOW); machine->gpiod_int_mic_en = gpiod; if (IS_ERR(gpiod)) - return PTR_ERR(gpiod); + return dev_err_probe(dev, PTR_ERR(gpiod), + "failed to get int-mic-en GPIO\n"); gpiod = devm_gpiod_get_optional(dev, "nvidia,ext-mic-en", GPIOD_OUT_LOW); machine->gpiod_ext_mic_en = gpiod; if (IS_ERR(gpiod)) - return PTR_ERR(gpiod); + return dev_err_probe(dev, PTR_ERR(gpiod), + "failed to get ext-mic-en GPIO\n"); err = snd_soc_of_parse_card_name(card, "nvidia,model"); if (err) @@ -549,22 +556,19 @@ int tegra_asoc_machine_probe(struct platform_device *pdev) card->driver_name = "tegra"; machine->clk_pll_a = devm_clk_get(dev, "pll_a"); - if (IS_ERR(machine->clk_pll_a)) { - dev_err(dev, "Can't retrieve clk pll_a\n"); - return PTR_ERR(machine->clk_pll_a); - } + if (IS_ERR(machine->clk_pll_a)) + return dev_err_probe(dev, PTR_ERR(machine->clk_pll_a), + "can't retrieve clk pll_a\n"); machine->clk_pll_a_out0 = devm_clk_get(dev, "pll_a_out0"); - if (IS_ERR(machine->clk_pll_a_out0)) { - dev_err(dev, "Can't retrieve clk pll_a_out0\n"); - return PTR_ERR(machine->clk_pll_a_out0); - } + if (IS_ERR(machine->clk_pll_a_out0)) + return dev_err_probe(dev, PTR_ERR(machine->clk_pll_a_out0), + "can't retrieve clk pll_a_out0\n"); machine->clk_cdev1 = devm_clk_get(dev, "mclk"); - if (IS_ERR(machine->clk_cdev1)) { - dev_err(dev, "Can't retrieve clk cdev1\n"); - return PTR_ERR(machine->clk_cdev1); - } + if (IS_ERR(machine->clk_cdev1)) + return dev_err_probe(dev, PTR_ERR(machine->clk_cdev1), + "can't retrieve clk cdev1\n"); /* * If clock parents are not set in DT, configure here to use clk_out_1 @@ -578,28 +582,24 @@ int tegra_asoc_machine_probe(struct platform_device *pdev) dev_warn(dev, "Please update DT to use assigned-clock-parents\n"); clk_extern1 = devm_clk_get(dev, "extern1"); - if (IS_ERR(clk_extern1)) { - dev_err(dev, "Can't retrieve clk extern1\n"); - return PTR_ERR(clk_extern1); - } + if (IS_ERR(clk_extern1)) + return dev_err_probe(dev, PTR_ERR(clk_extern1), + "can't retrieve clk extern1\n"); err = clk_set_parent(clk_extern1, machine->clk_pll_a_out0); - if (err < 0) { - dev_err(dev, "Set parent failed for clk extern1\n"); - return err; - } + if (err < 0) + return dev_err_probe(dev, err, + "set parent failed for clk extern1\n"); clk_out_1 = devm_clk_get(dev, "pmc_clk_out_1"); - if (IS_ERR(clk_out_1)) { - dev_err(dev, "Can't retrieve pmc_clk_out_1\n"); - return PTR_ERR(clk_out_1); - } + if (IS_ERR(clk_out_1)) + return dev_err_probe(dev, PTR_ERR(clk_out_1), + "can't retrieve pmc_clk_out_1\n"); err = clk_set_parent(clk_out_1, clk_extern1); - if (err < 0) { - dev_err(dev, "Set parent failed for pmc_clk_out_1\n"); - return err; - } + if (err < 0) + return dev_err_probe(dev, err, + "set parent failed for pmc_clk_out_1\n"); machine->clk_cdev1 = clk_out_1; } From f7d9eb0291ef00ebfb4ff4f0bec63536c15a0071 Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 25 Mar 2026 10:14:37 +0000 Subject: [PATCH 183/359] ASoC: tegra: Use dev_err_probe() in tegra_audio_graph_card probe Log errors in the Tegra audio graph card probe path using dev_err_probe(). Signed-off-by: Sheetal Link: https://patch.msgid.link/20260325101437.3059693-15-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra_audio_graph_card.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/sound/soc/tegra/tegra_audio_graph_card.c b/sound/soc/tegra/tegra_audio_graph_card.c index ea10e6e8a9fe..b93a61db9ed0 100644 --- a/sound/soc/tegra/tegra_audio_graph_card.c +++ b/sound/soc/tegra/tegra_audio_graph_card.c @@ -174,20 +174,23 @@ static int tegra_audio_graph_card_probe(struct snd_soc_card *card) { struct simple_util_priv *simple = snd_soc_card_get_drvdata(card); struct tegra_audio_priv *priv = simple_to_tegra_priv(simple); + int ret; priv->clk_plla = devm_clk_get(card->dev, "pll_a"); - if (IS_ERR(priv->clk_plla)) { - dev_err(card->dev, "Can't retrieve clk pll_a\n"); - return PTR_ERR(priv->clk_plla); - } + if (IS_ERR(priv->clk_plla)) + return dev_err_probe(card->dev, PTR_ERR(priv->clk_plla), + "can't retrieve clk pll_a\n"); priv->clk_plla_out0 = devm_clk_get(card->dev, "plla_out0"); - if (IS_ERR(priv->clk_plla_out0)) { - dev_err(card->dev, "Can't retrieve clk plla_out0\n"); - return PTR_ERR(priv->clk_plla_out0); - } + if (IS_ERR(priv->clk_plla_out0)) + return dev_err_probe(card->dev, PTR_ERR(priv->clk_plla_out0), + "can't retrieve clk plla_out0\n"); - return graph_util_card_probe(card); + ret = graph_util_card_probe(card); + if (ret < 0) + return dev_err_probe(card->dev, ret, "graph_util_card_probe failed\n"); + + return ret; } static int tegra_audio_graph_probe(struct platform_device *pdev) From c9b48a451ca8fc36c382e1e05a86e477803e32a5 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Tue, 24 Mar 2026 10:55:08 -0300 Subject: [PATCH 184/359] ASoC: dt-bindings: rockchip: Convert rockchip-max98090.txt to yaml Convert rockchip-max98090.txt to yaml to allow dt-schema validation. Signed-off-by: Fabio Estevam Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260324135508.839142-1-festevam@gmail.com Signed-off-by: Mark Brown --- .../rockchip,rockchip-audio-max98090.yaml | 59 +++++++++++++++++++ .../bindings/sound/rockchip-max98090.txt | 42 ------------- 2 files changed, 59 insertions(+), 42 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/rockchip,rockchip-audio-max98090.yaml delete mode 100644 Documentation/devicetree/bindings/sound/rockchip-max98090.txt diff --git a/Documentation/devicetree/bindings/sound/rockchip,rockchip-audio-max98090.yaml b/Documentation/devicetree/bindings/sound/rockchip,rockchip-audio-max98090.yaml new file mode 100644 index 000000000000..5351d5f02edf --- /dev/null +++ b/Documentation/devicetree/bindings/sound/rockchip,rockchip-audio-max98090.yaml @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/rockchip,rockchip-audio-max98090.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip audio complex with MAX98090 codec + +maintainers: + - Fabio Estevam + +properties: + compatible: + const: rockchip,rockchip-audio-max98090 + + rockchip,model: + $ref: /schemas/types.yaml#/definitions/string + description: The user-visible name of this sound complex. + + rockchip,i2s-controller: + $ref: /schemas/types.yaml#/definitions/phandle + description: Phandle to the Rockchip I2S controller. + + rockchip,audio-codec: + $ref: /schemas/types.yaml#/definitions/phandle + description: Phandle to the MAX98090 audio codec. + + rockchip,headset-codec: + $ref: /schemas/types.yaml#/definitions/phandle + description: Phandle to the external chip for jack detection. + + rockchip,hdmi-codec: + $ref: /schemas/types.yaml#/definitions/phandle + description: Phandle to the HDMI device for HDMI codec. + +required: + - compatible + - rockchip,model + - rockchip,i2s-controller + +allOf: + - if: + required: + - rockchip,audio-codec + then: + required: + - rockchip,headset-codec + +unevaluatedProperties: false + +examples: + - | + sound { + compatible = "rockchip,rockchip-audio-max98090"; + rockchip,model = "ROCKCHIP-I2S"; + rockchip,i2s-controller = <&i2s>; + rockchip,audio-codec = <&max98090>; + rockchip,headset-codec = <&headsetcodec>; + }; diff --git a/Documentation/devicetree/bindings/sound/rockchip-max98090.txt b/Documentation/devicetree/bindings/sound/rockchip-max98090.txt deleted file mode 100644 index e9c58b204399..000000000000 --- a/Documentation/devicetree/bindings/sound/rockchip-max98090.txt +++ /dev/null @@ -1,42 +0,0 @@ -ROCKCHIP with MAX98090 CODEC - -Required properties: -- compatible: "rockchip,rockchip-audio-max98090" -- rockchip,model: The user-visible name of this sound complex -- rockchip,i2s-controller: The phandle of the Rockchip I2S controller that's - connected to the CODEC - -Optional properties: -- rockchip,audio-codec: The phandle of the MAX98090 audio codec. -- rockchip,headset-codec: The phandle of Ext chip for jack detection. This is - required if there is rockchip,audio-codec. -- rockchip,hdmi-codec: The phandle of HDMI device for HDMI codec. - -Example: - -/* For max98090-only board. */ -sound { - compatible = "rockchip,rockchip-audio-max98090"; - rockchip,model = "ROCKCHIP-I2S"; - rockchip,i2s-controller = <&i2s>; - rockchip,audio-codec = <&max98090>; - rockchip,headset-codec = <&headsetcodec>; -}; - -/* For HDMI-only board. */ -sound { - compatible = "rockchip,rockchip-audio-max98090"; - rockchip,model = "ROCKCHIP-I2S"; - rockchip,i2s-controller = <&i2s>; - rockchip,hdmi-codec = <&hdmi>; -}; - -/* For max98090 plus HDMI board. */ -sound { - compatible = "rockchip,rockchip-audio-max98090"; - rockchip,model = "ROCKCHIP-I2S"; - rockchip,i2s-controller = <&i2s>; - rockchip,audio-codec = <&max98090>; - rockchip,headset-codec = <&headsetcodec>; - rockchip,hdmi-codec = <&hdmi>; -}; From 8a6391ec669366cbe7bde92b468c561e8b309fd6 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 18 Mar 2026 18:50:25 +0400 Subject: [PATCH 185/359] ASoC: rockchip: rockchip_sai: Set slot width for non-TDM mode Currently the slot width in non-TDM mode is always kept at the POR value of 32 bits, regardless of the sample width, which doesn't work well for some codecs such as NAU8822. Set the slot width according to the sample width in non-TDM mode, which is what other CPU DAI drivers do. Tested on the following RK3576 configurations: - SAI2 + NAU8822 (codec as the clock master), custom board - SAI1 + ES8388 (codec as the clock master), RK3576 EVB1 - SAI2 + RT5616 (SAI as the clock master), FriendlyElec NanoPi M5 NAU8822 didn't work prior to this patch but works after the patch. Other two configurations work both before and after the patch. Fixes: cc78d1eaabad ("ASoC: rockchip: add Serial Audio Interface (SAI) driver") Signed-off-by: Alexey Charkov Tested-by: Nicolas Frattaroli Link: https://patch.msgid.link/20260318-sai-slot-width-v1-1-1f68186f71e3@flipper.net Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_sai.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/soc/rockchip/rockchip_sai.c b/sound/soc/rockchip/rockchip_sai.c index 1bf614dbdf4d..ed393e5034a4 100644 --- a/sound/soc/rockchip/rockchip_sai.c +++ b/sound/soc/rockchip/rockchip_sai.c @@ -628,6 +628,10 @@ static int rockchip_sai_hw_params(struct snd_pcm_substream *substream, regmap_update_bits(sai->regmap, reg, SAI_XCR_VDW_MASK | SAI_XCR_CSR_MASK, val); + if (!sai->is_tdm) + regmap_update_bits(sai->regmap, reg, SAI_XCR_SBW_MASK, + SAI_XCR_SBW(params_physical_width(params))); + regmap_read(sai->regmap, reg, &val); slot_width = SAI_XCR_SBW_V(val); From 37c277f050e8d24cb3db6d090d4e9cdd263ba1a3 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 25 Mar 2026 17:08:40 +0000 Subject: [PATCH 186/359] ASoC: soc.h: Add SOC_SINGLE_BOOL_EXT_ACC() to allow setting access flags Add a macro SOC_SINGLE_BOOL_EXT_ACC() to allow the access permission flags to be set. This is the same as SOC_SINGLE_BOOL_EXT() but with an extra argument for the access flags. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260325170841.1405368-2-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/soc.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/sound/soc.h b/include/sound/soc.h index 7d8376c8e1be..8055def75195 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -311,6 +311,12 @@ struct platform_device; .info = snd_soc_info_bool_ext, \ .get = xhandler_get, .put = xhandler_put, \ .private_value = xdata } +#define SOC_SINGLE_BOOL_EXT_ACC(xname, xdata, xhandler_get, xhandler_put, xaccess) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = xaccess, \ + .info = snd_soc_info_bool_ext, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = xdata } #define SOC_ENUM_EXT(xname, xenum, xhandler_get, xhandler_put) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_enum_double, \ From ee7d655dbaf5e57145c73fd3925b5f44f7a1a5cc Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 25 Mar 2026 17:08:41 +0000 Subject: [PATCH 187/359] ASoC: cs35l56: Allow factory calibration through ALSA controls Add support for using ALSA controls to trigger a factory calibration. This is protected by a new Kconfig option so that it is only available if explicitly enabled in the kernel. By default it is not enabled. Factory calibration is normally done through debugfs files. Google have requested that factory calibration can be performed by repair shops. These repair shops only have access to the standard "user" kernel, which does not include debugfs. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260325170841.1405368-3-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/cs35l56.h | 1 + sound/soc/codecs/Kconfig | 13 +++++ sound/soc/codecs/cs35l56-shared.c | 9 +++ sound/soc/codecs/cs35l56.c | 96 +++++++++++++++++++++++++++++++ sound/soc/codecs/cs35l56.h | 2 + 5 files changed, 121 insertions(+) diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h index 28f9f5940ab6..4c1969cd84d8 100644 --- a/include/sound/cs35l56.h +++ b/include/sound/cs35l56.h @@ -435,6 +435,7 @@ ssize_t cs35l56_cal_data_debugfs_read(struct cs35l56_base *cs35l56_base, ssize_t cs35l56_cal_data_debugfs_write(struct cs35l56_base *cs35l56_base, const char __user *from, size_t count, loff_t *ppos); +int cs35l56_factory_calibrate(struct cs35l56_base *cs35l56_base); void cs35l56_create_cal_debugfs(struct cs35l56_base *cs35l56_base, const struct cs35l56_cal_debugfs_fops *fops); void cs35l56_remove_cal_debugfs(struct cs35l56_base *cs35l56_base); diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index adb3fb923be3..8bcffb812828 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -928,6 +928,19 @@ config SND_SOC_CS35L56_CAL_SET_CTRL If unsure select "N". +config SND_SOC_CS35L56_CAL_PERFORM_CTRL + bool "CS35L56 ALSA control to perform factory calibration" + default N + select SND_SOC_CS35L56_CAL_DEBUGFS_COMMON + help + Allow performing factory calibration data through an ALSA + control. It is recommended to use the debugfs method instead + because debugfs has restricted access permissions. + + On most platforms this is not needed. + + If unsure select "N". + config SND_SOC_CS35L56_TEST tristate "KUnit test for Cirrus Logic cs35l56 driver" if !KUNIT_ALL_TESTS depends on SND_SOC_CS35L56 && KUNIT diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c index af87ebae98cb..e05d975ba794 100644 --- a/sound/soc/codecs/cs35l56-shared.c +++ b/sound/soc/codecs/cs35l56-shared.c @@ -1185,6 +1185,15 @@ ssize_t cs35l56_calibrate_debugfs_write(struct cs35l56_base *cs35l56_base, } EXPORT_SYMBOL_NS_GPL(cs35l56_calibrate_debugfs_write, "SND_SOC_CS35L56_SHARED"); +int cs35l56_factory_calibrate(struct cs35l56_base *cs35l56_base) +{ + if (!IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_PERFORM_CTRL)) + return -ENXIO; + + return cs35l56_perform_calibration(cs35l56_base); +} +EXPORT_SYMBOL_NS_GPL(cs35l56_factory_calibrate, "SND_SOC_CS35L56_SHARED"); + ssize_t cs35l56_cal_ambient_debugfs_write(struct cs35l56_base *cs35l56_base, const char __user *from, size_t count, loff_t *ppos) diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index 37909a319f88..f03a2b47dc6c 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -1109,6 +1109,88 @@ static int cs35l56_cal_data_ctl_set(struct snd_kcontrol *kcontrol, return 1; } +static int cs35l56_cal_ambient_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = cs35l56->ambient_ctl_value; + + return 0; +} + +static int cs35l56_cal_ambient_ctl_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm; + int temperature = ucontrol->value.integer.value[0]; + int ret; + + if (temperature == cs35l56->ambient_ctl_value) + return 0; + + if ((temperature < 0) || (temperature > 40)) + return -EINVAL; + + dapm = cs35l56_power_up_for_cal(cs35l56); + if (IS_ERR(dapm)) + return PTR_ERR(dapm); + + ret = cs_amp_write_ambient_temp(&cs35l56->dsp.cs_dsp, + cs35l56->base.calibration_controls, + temperature); + cs35l56_power_down_after_cal(cs35l56); + + if (ret) + return ret; + + cs35l56->ambient_ctl_value = temperature; + + return 1; +} + +static int cs35l56_calibrate_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* + * Allow reading because of user-side libraries that assume all + * controls are readable. But always return false to prevent dumb + * save-restore tools like alsactl accidentically triggering a + * factory calibration when they restore. + */ + ucontrol->value.integer.value[0] = 0; + + return 0; +} + +static int cs35l56_calibrate_ctl_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm; + int ret; + + if (ucontrol->value.integer.value[0] == 0) + return 0; + + dapm = cs35l56_power_up_for_cal(cs35l56); + if (IS_ERR(dapm)) + return PTR_ERR(dapm); + + snd_soc_dapm_mutex_lock(dapm); + ret = cs35l56_factory_calibrate(&cs35l56->base); + snd_soc_dapm_mutex_unlock(dapm); + cs35l56_power_down_after_cal(cs35l56); + if (ret < 0) + return ret; + + return 1; +} + static const struct snd_kcontrol_new cs35l56_cal_data_restore_controls[] = { SND_SOC_BYTES_E("CAL_DATA", 0, sizeof(struct cirrus_amp_cal_data) / sizeof(u32), cs35l56_cal_data_ctl_get, cs35l56_cal_data_ctl_set), @@ -1117,6 +1199,14 @@ static const struct snd_kcontrol_new cs35l56_cal_data_restore_controls[] = { SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE), }; +static const struct snd_kcontrol_new cs35l56_cal_perform_controls[] = { + SOC_SINGLE_EXT("CAL_AMBIENT", SND_SOC_NOPM, 0, 40, 0, + cs35l56_cal_ambient_ctl_get, cs35l56_cal_ambient_ctl_set), + SOC_SINGLE_BOOL_EXT_ACC("Calibrate Switch", 0, + cs35l56_calibrate_ctl_get, cs35l56_calibrate_ctl_set, + SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE), +}; + VISIBLE_IF_KUNIT int cs35l56_set_fw_suffix(struct cs35l56_private *cs35l56) { unsigned short vendor, device; @@ -1290,6 +1380,12 @@ static int cs35l56_component_probe(struct snd_soc_component *component) ARRAY_SIZE(cs35l56_cal_data_restore_controls)); } + if (!ret && IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_PERFORM_CTRL)) { + ret = snd_soc_add_component_controls(component, + cs35l56_cal_perform_controls, + ARRAY_SIZE(cs35l56_cal_perform_controls)); + } + if (ret) return dev_err_probe(cs35l56->base.dev, ret, "unable to add controls\n"); diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h index 691f857d0bd8..9aaff2140bbb 100644 --- a/sound/soc/codecs/cs35l56.h +++ b/sound/soc/codecs/cs35l56.h @@ -53,6 +53,8 @@ struct cs35l56_private { bool sysclk_set; u8 sdw_link_num; u8 sdw_unique_id; + + u8 ambient_ctl_value; }; static inline struct cs35l56_private *cs35l56_private_from_base(struct cs35l56_base *cs35l56_base) From 951a4e8589b3ee6e889ed97fb9896df197dd88e7 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 26 Mar 2026 10:08:53 +0000 Subject: [PATCH 188/359] ASoC: wm_adsp_fw_find_test: Fix missing NULL terminator on file name list The array wm_adsp_fw_find_test_dir_all_files[] needs a NULL terminator. It was documented at line 41 that dir_files points to a NULL-terminated array. /* NULL-terminated array of pointers to filenames ... */ const char * const *dir_files; This fixes the error reported by KASAN: [17:46:43] BUG: KASAN: global-out-of-bounds in wm_adsp_fw_find_test_pick_file+0x8cc/0xc80 [17:46:43] Read of size 8 at addr ffff800081e5ffe8 by task kunit_try_catch/18349 Signed-off-by: Richard Fitzgerald Fixes: bf2d44d07de7 ("ASoC: wm_adsp: Add kunit test for firmware file search") Link: https://patch.msgid.link/20260326100853.1582886-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm_adsp_fw_find_test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/wm_adsp_fw_find_test.c b/sound/soc/codecs/wm_adsp_fw_find_test.c index fb886dc61c27..d0c7fb30a95d 100644 --- a/sound/soc/codecs/wm_adsp_fw_find_test.c +++ b/sound/soc/codecs/wm_adsp_fw_find_test.c @@ -1041,6 +1041,7 @@ static const char * const wm_adsp_fw_find_test_dir_all_files[] = { "cirrus/cs1234-dsp1-mbc-vss.bin", "cirrus/wm1234-dsp1-mbc-vss.bin", "wm1234-dsp1-mbc-vss.bin", + NULL /* terminator */ }; /* From e113ed3e73b91194a39bf6a9207a3598827f0033 Mon Sep 17 00:00:00 2001 From: songxiebing Date: Wed, 25 Mar 2026 10:28:04 +0800 Subject: [PATCH 189/359] ASoC: renesas: Fix non-static global variable When using global variables in a .c file only,it is necessary to add the keyword "static", so here fix the warning. sparse warnings: (new ones prefixed by >>) >> sound/soc/renesas/dma-sh7760.c:62:3: sparse: sparse: symbol 'cam_pcm_data' was not declared. Should it be static? Signed-off-by: songxiebing Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202412171210.7a4vH3Ew-lkp@intel.com/ Link: https://patch.msgid.link/20260325022804.253353-1-songxiebing@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/renesas/dma-sh7760.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/renesas/dma-sh7760.c b/sound/soc/renesas/dma-sh7760.c index ca128ea903da..810f44cd6523 100644 --- a/sound/soc/renesas/dma-sh7760.c +++ b/sound/soc/renesas/dma-sh7760.c @@ -58,8 +58,9 @@ struct camelot_pcm { struct snd_pcm_substream *rx_ss; unsigned long rx_period_size; unsigned int rx_period; +}; -} cam_pcm_data[2] = { +static struct camelot_pcm cam_pcm_data[2] = { { .mmio = 0xFE3C0040, .txid = DMABRGIRQ_A0TXF, From 3b6f4cfc7152feb5641678ea254988fdce70a91b Mon Sep 17 00:00:00 2001 From: Jihed Chaibi Date: Tue, 24 Mar 2026 23:45:30 +0100 Subject: [PATCH 190/359] ASoC: fsl: mpc5200_dma: Convert to devm_ioremap() Replace ioremap() with devm_ioremap() so the mapping is released automatically when the device is unbound. Remove the corresponding iounmap() calls from the error path in mpc5200_audio_dma_create() and from mpc5200_audio_dma_destroy(). Since devm_ioremap() failure already returns directly and no other cleanup is needed at that point, simplify the kzalloc error path to return -ENOMEM directly instead of jumping to the now-removed out_unmap label. Signed-off-by: Jihed Chaibi Link: https://patch.msgid.link/20260324224530.102164-1-jihed.chaibi.dev@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/mpc5200_dma.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c index ee4fb5c932ca..56e2cf2f727b 100644 --- a/sound/soc/fsl/mpc5200_dma.c +++ b/sound/soc/fsl/mpc5200_dma.c @@ -326,7 +326,7 @@ int mpc5200_audio_dma_create(struct platform_device *op) dev_err(&op->dev, "Missing reg property\n"); return -ENODEV; } - regs = ioremap(res.start, resource_size(&res)); + regs = devm_ioremap(&op->dev, res.start, resource_size(&res)); if (!regs) { dev_err(&op->dev, "Could not map registers\n"); return -ENODEV; @@ -334,10 +334,8 @@ int mpc5200_audio_dma_create(struct platform_device *op) /* Allocate and initialize the driver private data */ psc_dma = kzalloc_obj(*psc_dma); - if (!psc_dma) { - ret = -ENOMEM; - goto out_unmap; - } + if (!psc_dma) + return -ENOMEM; /* Get the PSC ID */ prop = of_get_property(op->dev.of_node, "cell-index", &size); @@ -424,8 +422,6 @@ out_irq: free_irq(psc_dma->playback.irq, &psc_dma->playback); out_free: kfree(psc_dma); -out_unmap: - iounmap(regs); return ret; } EXPORT_SYMBOL_GPL(mpc5200_audio_dma_create); @@ -444,7 +440,6 @@ int mpc5200_audio_dma_destroy(struct platform_device *op) free_irq(psc_dma->capture.irq, &psc_dma->capture); free_irq(psc_dma->playback.irq, &psc_dma->playback); - iounmap(psc_dma->psc_regs); kfree(psc_dma); dev_set_drvdata(&op->dev, NULL); From 9033f7b7f27d09d468c98ed2b811d0f2495c70c1 Mon Sep 17 00:00:00 2001 From: Mac Chiang Date: Thu, 26 Mar 2026 15:53:00 +0800 Subject: [PATCH 191/359] ASoC: Intel: soc-acpi-intel-ptl-match: drop rt722 monolithic match tables Use functional topologies to support RT722 topologies with/without amplifiers, e.g. sof-ptl-rt722.tplg, sof-ptl-rt722-rt1320.tplg... If these entries are not removed, they will find the first same link of sof-ptl-rt722.tplg. Signed-off-by: Mac Chiang Co-developed-by: Derek Fang Signed-off-by: Derek Fang Signed-off-by: Bard Liao Link: https://patch.msgid.link/20260326075303.1083567-2-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- .../intel/common/soc-acpi-intel-ptl-match.c | 100 ------------------ 1 file changed, 100 deletions(-) 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 ddd919847c1f..3b7818355ff6 100644 --- a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c @@ -134,31 +134,6 @@ static const struct snd_soc_acpi_endpoint spk_6_endpoint = { .group_id = 1, }; -/* - * Multi-function codecs with three endpoints created for - * headset, amp and dmic functions. - */ -static const struct snd_soc_acpi_endpoint rt_mf_endpoints[] = { - { - .num = 0, - .aggregated = 0, - .group_position = 0, - .group_id = 0, - }, - { - .num = 1, - .aggregated = 0, - .group_position = 0, - .group_id = 0, - }, - { - .num = 2, - .aggregated = 0, - .group_position = 0, - .group_id = 0, - }, -}; - static const struct snd_soc_acpi_endpoint jack_dmic_endpoints[] = { /* Jack Endpoint */ { @@ -365,33 +340,6 @@ static const struct snd_soc_acpi_adr_device rt722_0_agg_adr[] = { } }; -static const struct snd_soc_acpi_adr_device rt722_0_single_adr[] = { - { - .adr = 0x000030025d072201ull, - .num_endpoints = ARRAY_SIZE(rt_mf_endpoints), - .endpoints = rt_mf_endpoints, - .name_prefix = "rt722" - } -}; - -static const struct snd_soc_acpi_adr_device rt722_1_single_adr[] = { - { - .adr = 0x000130025d072201ull, - .num_endpoints = ARRAY_SIZE(rt_mf_endpoints), - .endpoints = rt_mf_endpoints, - .name_prefix = "rt722" - } -}; - -static const struct snd_soc_acpi_adr_device rt722_3_single_adr[] = { - { - .adr = 0x000330025d072201ull, - .num_endpoints = ARRAY_SIZE(rt_mf_endpoints), - .endpoints = rt_mf_endpoints, - .name_prefix = "rt722" - } -}; - static const struct snd_soc_acpi_adr_device rt1320_1_group1_adr[] = { { .adr = 0x000130025D132001ull, @@ -479,33 +427,6 @@ static const struct snd_soc_acpi_link_adr ptl_cs42l43_l2_cs35l56x6_l13[] = { {} }; -static const struct snd_soc_acpi_link_adr ptl_rt722_only[] = { - { - .mask = BIT(0), - .num_adr = ARRAY_SIZE(rt722_0_single_adr), - .adr_d = rt722_0_single_adr, - }, - {} -}; - -static const struct snd_soc_acpi_link_adr ptl_rt722_l1[] = { - { - .mask = BIT(1), - .num_adr = ARRAY_SIZE(rt722_1_single_adr), - .adr_d = rt722_1_single_adr, - }, - {} -}; - -static const struct snd_soc_acpi_link_adr ptl_rt722_l3[] = { - { - .mask = BIT(3), - .num_adr = ARRAY_SIZE(rt722_3_single_adr), - .adr_d = rt722_3_single_adr, - }, - {} -}; - static const struct snd_soc_acpi_link_adr ptl_rt722_l0_rt1320_l23[] = { { .mask = BIT(0), @@ -698,20 +619,6 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = { .drv_name = "sof_sdw", .sof_tplg_filename = "sof-ptl-rt711.tplg", }, - { - .link_mask = BIT(0), - .links = ptl_rt722_only, - .drv_name = "sof_sdw", - .sof_tplg_filename = "sof-ptl-rt722.tplg", - .get_function_tplg_files = sof_sdw_get_tplg_files, - }, - { - .link_mask = BIT(1), - .links = ptl_rt722_l1, - .drv_name = "sof_sdw", - .sof_tplg_filename = "sof-ptl-rt722.tplg", - .get_function_tplg_files = sof_sdw_get_tplg_files, - }, { .link_mask = BIT(3), .links = ptl_sdw_rt712_vb_l3_rt1320_l3, @@ -720,13 +627,6 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = { .sof_tplg_filename = "sof-ptl-rt712-l3-rt1320-l3.tplg", .get_function_tplg_files = sof_sdw_get_tplg_files, }, - { - .link_mask = BIT(3), - .links = ptl_rt722_l3, - .drv_name = "sof_sdw", - .sof_tplg_filename = "sof-ptl-rt722.tplg", - .get_function_tplg_files = sof_sdw_get_tplg_files, - }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_ptl_sdw_machines); From dd4a1963ddf0d0f5e129efec03f34ea37109b4b7 Mon Sep 17 00:00:00 2001 From: Derek Fang Date: Thu, 26 Mar 2026 15:53:01 +0800 Subject: [PATCH 192/359] ASoC: SOF: Intel: Add a is_amp flag to fix the wrong name prefix According to the Intel sof design, it will create the name prefix appended with amp index for the amp codec only, such as: rt1318-1, rt1318-2, etc... But the rt1320 is a codec with amp and mic codec functions, it doesn't have the amp index in its name prefix as above. And then it will be hard to identify the codec if in multi-rt1320 case. So we add a flag to force the amp index to be appended. Signed-off-by: Derek Fang Signed-off-by: Bard Liao Link: https://patch.msgid.link/20260326075303.1083567-3-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- include/sound/soc_sdw_utils.h | 2 ++ sound/soc/sof/intel/hda.c | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h index 48f516ba682f..489083183673 100644 --- a/include/sound/soc_sdw_utils.h +++ b/include/sound/soc_sdw_utils.h @@ -83,6 +83,8 @@ struct asoc_sdw_codec_info { const int dai_num; struct asoc_sdw_aux_info auxs[SOC_SDW_MAX_AUX_NUM]; const int aux_num; + /* Force AMP-style name_prefix handling (append AMP index) even if MIC/Jack DAIs exist */ + const bool is_amp; int (*codec_card_late_probe)(struct snd_soc_card *card); diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index 9ec33147d9af..edb80c2fa770 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -1232,6 +1232,16 @@ static struct snd_soc_acpi_adr_device *find_acpi_adr_device(struct device *dev, return NULL; } + /* + * codec_info_list[].is_amp is a codec-level override: for multi-function + * codecs we must treat the whole codec as an AMP when it is described as + * such in the codec info table, even if some endpoints were detected as + * non-AMP above. Callers/UCM rely on this to keep name_prefix and AMP + * indexing stable and backwards compatible. + */ + if (codec_info_list[i].is_amp) + is_amp = true; + adr_dev[index].adr = ((u64)sdw_device->id.class_id & 0xFF) | ((u64)sdw_device->id.part_id & 0xFFFF) << 8 | ((u64)sdw_device->id.mfg_id & 0xFFFF) << 24 | From ae2cb3384337a556ce02e557fecd39db78c36e7d Mon Sep 17 00:00:00 2001 From: Derek Fang Date: Thu, 26 Mar 2026 15:53:02 +0800 Subject: [PATCH 193/359] ASoC: sdw_utils: add rt1320 and rt1321 dmic dai in codec_info_list Add 'rt1320-aif2' dai infos for rt1320 and rt1321 dmic function. Signed-off-by: Derek Fang Signed-off-by: Bard Liao Link: https://patch.msgid.link/20260326075303.1083567-4-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_rt_dmic.c | 46 +++++++++++++++++++++++++-- sound/soc/sdw_utils/soc_sdw_utils.c | 26 +++++++++++++-- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/sound/soc/sdw_utils/soc_sdw_rt_dmic.c b/sound/soc/sdw_utils/soc_sdw_rt_dmic.c index 97be110a59b6..fed85bad9e88 100644 --- a/sound/soc/sdw_utils/soc_sdw_rt_dmic.c +++ b/sound/soc/sdw_utils/soc_sdw_rt_dmic.c @@ -9,15 +9,20 @@ #include #include +#include +#include #include #include #include +#include int asoc_sdw_rt_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai) { struct snd_soc_card *card = rtd->card; struct snd_soc_component *component; + struct sdw_slave *sdw_peripheral = NULL; char *mic_name; + int rt1320_dmic_num = 0, part_id, i; component = dai->component; @@ -32,9 +37,44 @@ int asoc_sdw_rt_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_da if (!mic_name) return -ENOMEM; - card->components = devm_kasprintf(card->dev, GFP_KERNEL, - "%s mic:%s", card->components, - mic_name); + /* + * If there is any rt1320/rt1321 DMIC belonging to this card, try to count the `cfg-mics` + * to be used in card->components. + * Note: The rt1320 drivers register the peripheral dev to component->dev, so get the + * sdw_peripheral from component->dev. + */ + if (is_sdw_slave(component->dev)) + sdw_peripheral = dev_to_sdw_dev(component->dev); + if (sdw_peripheral && + (sdw_peripheral->id.part_id == 0x1320 || sdw_peripheral->id.part_id == 0x1321)) { + part_id = sdw_peripheral->id.part_id; + /* + * This rtd init callback is called once, so count the rt1320/rt1321 with SDCA + * function SmartMic type in this card. + */ + for_each_card_components(card, component) { + if (!is_sdw_slave(component->dev)) + continue; + sdw_peripheral = dev_to_sdw_dev(component->dev); + if (sdw_peripheral->id.part_id != part_id) + continue; + for (i = 0; i < sdw_peripheral->sdca_data.num_functions; i++) { + if (sdw_peripheral->sdca_data.function[i].type == + SDCA_FUNCTION_TYPE_SMART_MIC) { + rt1320_dmic_num++; + break; + } + } + } + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s mic:%s cfg-mics:%d", card->components, + mic_name, rt1320_dmic_num); + } else { + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s mic:%s", card->components, + mic_name); + } + if (!card->components) return -ENOMEM; diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 637c640001d6..2807f536eef0 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -331,6 +331,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { .vendor_id = 0x025d, .part_id = 0x1320, .name_prefix = "rt1320", + .is_amp = true, .dais = { { .direction = {true, false}, @@ -346,13 +347,24 @@ struct asoc_sdw_codec_info codec_info_list[] = { .widgets = generic_spk_widgets, .num_widgets = ARRAY_SIZE(generic_spk_widgets), }, + { + .direction = {false, true}, + .dai_name = "rt1320-aif2", + .component_name = "rt1320", + .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, + .widgets = generic_dmic_widgets, + .num_widgets = ARRAY_SIZE(generic_dmic_widgets), + }, }, - .dai_num = 1, + .dai_num = 2, }, { .vendor_id = 0x025d, .part_id = 0x1321, .name_prefix = "rt1320", + .is_amp = true, .dais = { { .direction = {true, false}, @@ -368,8 +380,18 @@ struct asoc_sdw_codec_info codec_info_list[] = { .widgets = generic_spk_widgets, .num_widgets = ARRAY_SIZE(generic_spk_widgets), }, + { + .direction = {false, true}, + .dai_name = "rt1320-aif2", + .component_name = "rt1320", + .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, + .widgets = generic_dmic_widgets, + .num_widgets = ARRAY_SIZE(generic_dmic_widgets), + }, }, - .dai_num = 1, + .dai_num = 2, }, { .vendor_id = 0x025d, From 4b097a7b25a01a3732f0e7569921efc9ad22bc81 Mon Sep 17 00:00:00 2001 From: Ben Copeland Date: Thu, 19 Mar 2026 12:45:21 +0000 Subject: [PATCH 194/359] selftests: ALSA: Skip utimer test when CONFIG_SND_UTIMER is not enabled The timer_f.utimer test hard-fails with ASSERT_EQ when SNDRV_TIMER_IOCTL_CREATE returns -1 on kernels without CONFIG_SND_UTIMER. This causes the entire alsa kselftest suite to report a failure rather than skipping the unsupported test. When CONFIG_SND_UTIMER is not enabled, the ioctl is not recognised and the kernel returns -ENOTTY. If the timer device or subdevice does not exist, -ENXIO is returned. Skip the test in both cases, but still fail on any other unexpected error. Suggested-by: Mark Brown Link: https://lore.kernel.org/linux-kselftest/0e9c25d3-efbd-433b-9fb1-0923010101b9@stanley.mountain/ Signed-off-by: Ben Copeland Reviewed-by: Mark Brown Link: https://patch.msgid.link/20260319124521.191491-1-ben.copeland@linaro.org Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/utimer-test.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/alsa/utimer-test.c b/tools/testing/selftests/alsa/utimer-test.c index d221972cd8fb..1a9ff010cb11 100644 --- a/tools/testing/selftests/alsa/utimer-test.c +++ b/tools/testing/selftests/alsa/utimer-test.c @@ -15,6 +15,7 @@ #include #include #include +#include #define FRAME_RATE 8000 #define PERIOD_SIZE 4410 @@ -52,7 +53,14 @@ FIXTURE_SETUP(timer_f) { timer_dev_fd = open("/dev/snd/timer", O_RDONLY); ASSERT_GE(timer_dev_fd, 0); - ASSERT_EQ(ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, self->utimer_info), 0); + if (ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, self->utimer_info) < 0) { + int err = errno; + + close(timer_dev_fd); + if (err == ENOTTY || err == ENXIO) + SKIP(return, "CONFIG_SND_UTIMER not enabled"); + ASSERT_EQ(err, 0); + } ASSERT_GE(self->utimer_info->fd, 0); close(timer_dev_fd); From 1e512ac1254c8e370dd18efe9da4dfc92492cdc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Sat, 21 Mar 2026 20:02:21 -0300 Subject: [PATCH 195/359] ALSA: pcm: Use pcm_lib_apply_appl_ptr() in x32 sync_ptr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_pcm_ioctl_sync_ptr_x32() still handles incoming appl_ptr updates differently from the other SYNC_PTR paths. The native handler and the 32-bit compat handler both pass appl_ptr through pcm_lib_apply_appl_ptr(), but the x32 handler still writes control->appl_ptr directly. That direct assignment skips the common appl_ptr validation against runtime->boundary and also bypasses the substream ack() callback. This makes the x32 ioctl path behave differently from the native and compat32 cases, and it can miss the driver notification that explicit appl_ptr synchronization relies on. Use pcm_lib_apply_appl_ptr() for x32 too, so appl_ptr updates are validated consistently and drivers relying on ack() notifications see the same behavior. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260321-alsa-pcm-x32-sync-ptr-v1-1-02ce655657c6@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm_compat.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c index e71f393d3b01..5313f50f17da 100644 --- a/sound/core/pcm_compat.c +++ b/sound/core/pcm_compat.c @@ -430,11 +430,13 @@ static int snd_pcm_ioctl_sync_ptr_x32(struct snd_pcm_substream *substream, if (!boundary) boundary = 0x7fffffff; scoped_guard(pcm_stream_lock_irq, substream) { - /* FIXME: we should consider the boundary for the sync from app */ - if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL)) - control->appl_ptr = scontrol.appl_ptr; - else + if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL)) { + err = pcm_lib_apply_appl_ptr(substream, scontrol.appl_ptr); + if (err < 0) + return err; + } else { scontrol.appl_ptr = control->appl_ptr % boundary; + } if (!(sflags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN)) control->avail_min = scontrol.avail_min; else From 32f35f9d8e457f5b2ee1df3f7a45af42965bedfe Mon Sep 17 00:00:00 2001 From: songxiebing Date: Wed, 25 Mar 2026 09:51:19 +0800 Subject: [PATCH 196/359] ALSA: core/seq: Optimize the return logic in cc_ev_to_ump_midi2 There are multiple early return branches within the func, and compiler optimizations(such as -O2/-O3)lead to abnormal stack frame analysis - objtool cannot comfirm that the stack frames of all branches can be correctly restored, thus generating false warnings. Below: >> sound/core/seq/seq_ump_convert.o: warning: objtool: cc_ev_to_ump_midi2+0x589: return with modified stack frame So we modify it by uniformly returning at the and of the function. Signed-off-by: songxiebing Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202503200535.J3hAvcjw-lkp@intel.com/ Link: https://patch.msgid.link/20260325015119.175835-1-songxiebing@kylinos.cn Signed-off-by: Takashi Iwai --- sound/core/seq/seq_ump_convert.c | 35 +++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c index db2f169cae11..ff4ee26adad1 100644 --- a/sound/core/seq/seq_ump_convert.c +++ b/sound/core/seq/seq_ump_convert.c @@ -841,7 +841,7 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event, unsigned char index = event->data.control.param & 0x7f; unsigned char val = event->data.control.value & 0x7f; struct ump_cvt_to_ump_bank *cc = &dest_port->midi2_bank[channel]; - int ret; + int ret = 0; /* process special CC's (bank/rpn/nrpn) */ switch (index) { @@ -851,47 +851,54 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event, cc->cc_rpn_msb = val; if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f) reset_rpn(cc); - return ret; + break; case UMP_CC_RPN_LSB: ret = fill_rpn(cc, data, channel, true); cc->rpn_set = 1; cc->cc_rpn_lsb = val; if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f) reset_rpn(cc); - return ret; + break; case UMP_CC_NRPN_MSB: ret = fill_rpn(cc, data, channel, true); cc->nrpn_set = 1; cc->cc_nrpn_msb = val; - return ret; + break; case UMP_CC_NRPN_LSB: ret = fill_rpn(cc, data, channel, true); cc->nrpn_set = 1; cc->cc_nrpn_lsb = val; - return ret; + break; case UMP_CC_DATA: cc->cc_data_msb_set = 1; cc->cc_data_msb = val; - return fill_rpn(cc, data, channel, false); + ret = fill_rpn(cc, data, channel, false); + break; case UMP_CC_BANK_SELECT: cc->bank_set = 1; cc->cc_bank_msb = val; - return 0; // skip + ret = 0; // skip + break; case UMP_CC_BANK_SELECT_LSB: cc->bank_set = 1; cc->cc_bank_lsb = val; - return 0; // skip + ret = 0; // skip + break; case UMP_CC_DATA_LSB: cc->cc_data_lsb_set = 1; cc->cc_data_lsb = val; - return fill_rpn(cc, data, channel, false); + ret = fill_rpn(cc, data, channel, false); + break; + default: + data->cc.status = status; + data->cc.channel = channel; + data->cc.index = index; + data->cc.data = upscale_7_to_32bit(event->data.control.value & 0x7f); + ret = 1; + break; } - data->cc.status = status; - data->cc.channel = channel; - data->cc.index = index; - data->cc.data = upscale_7_to_32bit(event->data.control.value & 0x7f); - return 1; + return ret; } /* convert one-parameter control event to MIDI 2.0 UMP */ From a213b6b019519063ce10569b19da20eac6ab884f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Mon, 23 Mar 2026 10:46:24 -0300 Subject: [PATCH 197/359] ALSA: usb-audio: rotate standard MIDI output port scan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_usbmidi_standard_output() iterates output ports in ascending order and drains each active port until the URB is full. On interfaces where multiple USB-MIDI cables share one endpoint, sustained traffic on a lower-numbered port can consume every refill before higher-numbered ports are even examined. That behavior dates back to the original implementation and still applies with the current multi-URB output path. snd_usbmidi_do_output() can refill several idle URBs in one pass, but each refill restarts the scan at port 0, so a busy lower-numbered port can keep higher-numbered ports from making progress at all. Use ep->current_port as the starting point of the scan and advance it after each URB fill. This keeps the existing packet formatting and per-port state handling intact while preventing persistent starvation of higher-numbered ports. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260323-usbmidi-port-fairness-v1-1-2d68e97592a1@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/midi.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sound/usb/midi.c b/sound/usb/midi.c index a8bddc90c0ed..0a5b8941ebda 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -699,15 +699,18 @@ static void snd_usbmidi_transmit_byte(struct usbmidi_out_port *port, static void snd_usbmidi_standard_output(struct snd_usb_midi_out_endpoint *ep, struct urb *urb) { - int p; + int port0 = ep->current_port; + int i; + + for (i = 0; i < 0x10; ++i) { + int portnum = (port0 + i) & 15; + struct usbmidi_out_port *port = &ep->ports[portnum]; - /* FIXME: lower-numbered ports can starve higher-numbered ports */ - for (p = 0; p < 0x10; ++p) { - struct usbmidi_out_port *port = &ep->ports[p]; if (!port->active) continue; while (urb->transfer_buffer_length + 3 < ep->max_transfer) { uint8_t b; + if (snd_rawmidi_transmit(port->substream, &b, 1) != 1) { port->active = 0; break; @@ -715,6 +718,7 @@ static void snd_usbmidi_standard_output(struct snd_usb_midi_out_endpoint *ep, snd_usbmidi_transmit_byte(port, b, urb); } } + ep->current_port = (port0 + 1) & 15; } static const struct usb_protocol_ops snd_usbmidi_standard_ops = { From bbc6c0dda54fc0ad8f8aed0b796c23e186e1a188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Tue, 24 Mar 2026 16:59:41 -0300 Subject: [PATCH 198/359] ALSA: seq_oss: return full count for successful SEQ_FULLSIZE writes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_seq_oss_write() currently returns the raw load_patch() callback result for SEQ_FULLSIZE events. That callback is documented as returning 0 on success and -errno on failure, but snd_seq_oss_write() is the file write path and should report the number of user bytes consumed on success. Some in-tree backends also return backend-specific positive values, which can still be shorter than the original write size. Return the full byte count for successful SEQ_FULLSIZE writes. Preserve negative errors and convert any nonnegative completion to the original count. Cc: stable@vger.kernel.org Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260324-alsa-seq-oss-fullsize-write-return-v1-1-66d448510538@gmail.com Signed-off-by: Takashi Iwai --- sound/core/seq/oss/seq_oss_rw.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/core/seq/oss/seq_oss_rw.c b/sound/core/seq/oss/seq_oss_rw.c index 8a142fd54a19..307ef98c44c7 100644 --- a/sound/core/seq/oss/seq_oss_rw.c +++ b/sound/core/seq/oss/seq_oss_rw.c @@ -101,9 +101,9 @@ snd_seq_oss_write(struct seq_oss_devinfo *dp, const char __user *buf, int count, break; } fmt = (*(unsigned short *)rec.c) & 0xffff; - /* FIXME the return value isn't correct */ - return snd_seq_oss_synth_load_patch(dp, rec.s.dev, - fmt, buf, 0, count); + err = snd_seq_oss_synth_load_patch(dp, rec.s.dev, + fmt, buf, 0, count); + return err < 0 ? err : count; } if (ev_is_long(&rec)) { /* extended code */ From 1a56641b7ae4f19216774a59d68024be3e6197d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Fri, 27 Mar 2026 10:59:45 -0300 Subject: [PATCH 199/359] ALSA: pcm: Serialize snd_pcm_suspend_all() with open_mutex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_pcm_suspend_all() walks all PCM substreams and uses a lockless runtime check to skip closed streams. It then calls snd_pcm_suspend() for each remaining substream and finally runs snd_pcm_sync_stop() in a second pass. The runtime lifetime is still controlled by pcm->open_mutex in the open/release path. That means a concurrent close can clear or free substream->runtime after the initial check in snd_pcm_suspend_all(), leaving the later suspend or sync-stop path to dereference a stale or NULL runtime pointer. Serialize snd_pcm_suspend_all() with pcm->open_mutex so the runtime pointer stays stable across both loops. This matches the existing PCM runtime lifetime rule already used by other core paths that access substream->runtime outside the stream lock. Suggested-by: Takashi Iwai Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260327-alsa-pcm-suspend-open-close-lock-v2-1-cc4baca4dcd6@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 394f86bc4d29..aefb861ab873 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -1780,6 +1780,9 @@ static int snd_pcm_suspend(struct snd_pcm_substream *substream) * snd_pcm_suspend_all - trigger SUSPEND to all substreams in the given pcm * @pcm: the PCM instance * + * Takes and releases pcm->open_mutex to serialize against + * concurrent open/close while walking the substreams. + * * After this call, all streams are changed to SUSPENDED state. * * Return: Zero if successful (or @pcm is %NULL), or a negative error code. @@ -1792,8 +1795,9 @@ int snd_pcm_suspend_all(struct snd_pcm *pcm) if (! pcm) return 0; + guard(mutex)(&pcm->open_mutex); + for_each_pcm_substream(pcm, stream, substream) { - /* FIXME: the open/close code should lock this as well */ if (!substream->runtime) continue; From ec9a788620be1c11535fe99e9b2779f9eef2b099 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 27 Mar 2026 16:30:53 +0100 Subject: [PATCH 200/359] ALSA: usb-audio: Replace hard-coded number with MAX_CHANNELS One place in mixer.c still used a hard-coded number 16 instead of MAX_CHANNELS. Replace with it, so that we can extend the max number of channels gracefully. Link: https://lore.kernel.org/F1B104A5-CD6A-4A26-AB46-14BF233C0579@getmailspring.com Tested-by: Phil Willoughby Link: https://patch.msgid.link/20260327153056.691575-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 7007e0c9489b..e764757979e0 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1769,7 +1769,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, cval->master_readonly = readonly_mask; } else { int i, c = 0; - for (i = 0; i < 16; i++) + for (i = 0; i < MAX_CHANNELS; i++) if (ctl_mask & BIT(i)) c++; cval->channels = c; From 16ee07bfa935f4f36deba896956f57d388221944 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 27 Mar 2026 16:30:54 +0100 Subject: [PATCH 201/359] ALSA: usb-audio: Extend max number of channels to 64 The current limitation of 16 as MAX_CHANNELS is rather historical at the time of UAC1 definition. As there seem already devices with a higher number of mixer channels, we should extend it too. As an ad hoc update, let's raise it to 64 so that it can still fit in a single long-long integer. Link: https://lore.kernel.org/F1B104A5-CD6A-4A26-AB46-14BF233C0579@getmailspring.com Tested-by: Phil Willoughby Link: https://patch.msgid.link/20260327153056.691575-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 14 +++++++------- sound/usb/mixer.h | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index e764757979e0..69026cf54979 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1725,7 +1725,7 @@ static bool check_insane_volume_range(struct usb_mixer_interface *mixer, static void __build_feature_ctl(struct usb_mixer_interface *mixer, const struct usbmix_name_map *imap, - unsigned int ctl_mask, int control, + u64 ctl_mask, int control, struct usb_audio_term *iterm, struct usb_audio_term *oterm, int unitid, int nameid, int readonly_mask) @@ -1887,7 +1887,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, } static void build_feature_ctl(struct mixer_build *state, void *raw_desc, - unsigned int ctl_mask, int control, + u64 ctl_mask, int control, struct usb_audio_term *iterm, int unitid, int readonly_mask) { @@ -1899,7 +1899,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, } static void build_feature_ctl_badd(struct usb_mixer_interface *mixer, - unsigned int ctl_mask, int control, int unitid, + u64 ctl_mask, int control, int unitid, const struct usbmix_name_map *badd_map) { __build_feature_ctl(mixer, badd_map, ctl_mask, control, @@ -2075,7 +2075,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, bmaControls = ftr->bmaControls; } - if (channels > 32) { + if (channels > MAX_CHANNELS) { usb_audio_info(state->chip, "usbmixer: too many channels (%d) in unit %d\n", channels, unitid); @@ -2113,7 +2113,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, if (state->mixer->protocol == UAC_VERSION_1) { /* check all control types */ for (i = 0; i < 10; i++) { - unsigned int ch_bits = 0; + u64 ch_bits = 0; int control = audio_feature_info[i].control; for (j = 0; j < channels; j++) { @@ -2139,7 +2139,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, } } else { /* UAC_VERSION_2/3 */ for (i = 0; i < ARRAY_SIZE(audio_feature_info); i++) { - unsigned int ch_bits = 0; + u64 ch_bits = 0; unsigned int ch_read_only = 0; int control = audio_feature_info[i].control; @@ -3452,7 +3452,7 @@ static void snd_usb_mixer_dump_cval(struct snd_info_buffer *buffer, [USB_MIXER_U32] = "U32", [USB_MIXER_BESPOKEN] = "BESPOKEN", }; - snd_iprintf(buffer, " Info: id=%i, control=%i, cmask=0x%x, " + snd_iprintf(buffer, " Info: id=%i, control=%i, cmask=0x%llx, " "channels=%i, type=\"%s\"\n", cval->head.id, cval->control, cval->cmask, cval->channels, val_types[cval->val_type]); diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 167fbfcf01ac..afbb3dd9f177 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -44,7 +44,7 @@ struct usb_mixer_interface { void (*private_suspend)(struct usb_mixer_interface *mixer); }; -#define MAX_CHANNELS 16 /* max logical channels */ +#define MAX_CHANNELS 64 /* max logical channels */ enum { USB_MIXER_BOOLEAN, @@ -81,7 +81,7 @@ struct usb_mixer_elem_list { struct usb_mixer_elem_info { struct usb_mixer_elem_list head; unsigned int control; /* CS or ICN (high byte) */ - unsigned int cmask; /* channel mask bitmap: 0 = master */ + u64 cmask; /* channel mask bitmap: 0 = master */ unsigned int idx_off; /* Control index offset */ unsigned int ch_readonly; unsigned int master_readonly; From 472d77bdc511d96434b3679ad022bfa35d3861c1 Mon Sep 17 00:00:00 2001 From: Khushal Chitturi Date: Fri, 27 Mar 2026 19:16:49 +0530 Subject: [PATCH 202/359] ASoC: dt-bindings: mediatek,mt8173-rt5650-rt5514: convert to DT schema Convert the Mediatek MT8173 with RT5650 and RT5514 sound card bindings to DT schema. Signed-off-by: Khushal Chitturi Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260327134649.31376-1-khushalchitturi@gmail.com Signed-off-by: Mark Brown --- .../sound/mediatek,mt8173-rt5650-rt5514.yaml | 41 +++++++++++++++++++ .../bindings/sound/mt8173-rt5650-rt5514.txt | 15 ------- 2 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/mediatek,mt8173-rt5650-rt5514.yaml delete mode 100644 Documentation/devicetree/bindings/sound/mt8173-rt5650-rt5514.txt diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt8173-rt5650-rt5514.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt8173-rt5650-rt5514.yaml new file mode 100644 index 000000000000..ed698c9ff42b --- /dev/null +++ b/Documentation/devicetree/bindings/sound/mediatek,mt8173-rt5650-rt5514.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/mediatek,mt8173-rt5650-rt5514.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Mediatek MT8173 with RT5650 and RT5514 audio codecs + +maintainers: + - Koro Chen + +properties: + compatible: + const: mediatek,mt8173-rt5650-rt5514 + + mediatek,audio-codec: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: Phandles of rt5650 and rt5514 codecs + items: + - description: phandle of rt5650 codec + - description: phandle of rt5514 codec + + mediatek,platform: + $ref: /schemas/types.yaml#/definitions/phandle + description: The phandle of MT8173 ASoC platform. + +required: + - compatible + - mediatek,audio-codec + - mediatek,platform + +additionalProperties: false + +examples: + - | + sound { + compatible = "mediatek,mt8173-rt5650-rt5514"; + mediatek,audio-codec = <&rt5650>, <&rt5514>; + mediatek,platform = <&afe>; + }; +... diff --git a/Documentation/devicetree/bindings/sound/mt8173-rt5650-rt5514.txt b/Documentation/devicetree/bindings/sound/mt8173-rt5650-rt5514.txt deleted file mode 100644 index e8b3c80c6fff..000000000000 --- a/Documentation/devicetree/bindings/sound/mt8173-rt5650-rt5514.txt +++ /dev/null @@ -1,15 +0,0 @@ -MT8173 with RT5650 RT5514 CODECS - -Required properties: -- compatible : "mediatek,mt8173-rt5650-rt5514" -- mediatek,audio-codec: the phandles of rt5650 and rt5514 codecs -- mediatek,platform: the phandle of MT8173 ASoC platform - -Example: - - sound { - compatible = "mediatek,mt8173-rt5650-rt5514"; - mediatek,audio-codec = <&rt5650 &rt5514>; - mediatek,platform = <&afe>; - }; - From 486c06451590fde1d4285fef744d419e46dba407 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 27 Mar 2026 00:26:38 +0100 Subject: [PATCH 203/359] ASoC: rt1318: Drop unused include The driver includes the legacy GPIO header but does not use any symbols from it so drop the include. Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260327-asoc-rt1318-v1-1-9fcecf868fda@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt1318.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/codecs/rt1318.c b/sound/soc/codecs/rt1318.c index 01c58b15fd91..a80643099644 100644 --- a/sound/soc/codecs/rt1318.c +++ b/sound/soc/codecs/rt1318.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include From 175b2d025874d2798d34f670319e63fb5570cb2c Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 27 Mar 2026 00:26:39 +0100 Subject: [PATCH 204/359] ASoC: nau8315: Drop unused include The driver includes the legacy GPIO header but does not use any symbols from it so drop the include. (It is already using the consumer header as is proper.) Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260327-asoc-rt1318-v1-2-9fcecf868fda@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/nau8315.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/codecs/nau8315.c b/sound/soc/codecs/nau8315.c index 125742601f88..9ac2048ff0b0 100644 --- a/sound/soc/codecs/nau8315.c +++ b/sound/soc/codecs/nau8315.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include From 00e981c7c0a1e7672a33fbe8c0b91e2fc6676782 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 27 Mar 2026 00:26:40 +0100 Subject: [PATCH 205/359] ASoC: ts3a227e: Drop unused include The driver includes the legacy GPIO header but does not use any symbols from it so drop the include. Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260327-asoc-rt1318-v1-3-9fcecf868fda@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/ts3a227e.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/codecs/ts3a227e.c b/sound/soc/codecs/ts3a227e.c index 5ce0db9326fd..5a7beeadb009 100644 --- a/sound/soc/codecs/ts3a227e.c +++ b/sound/soc/codecs/ts3a227e.c @@ -5,7 +5,6 @@ * Copyright (C) 2014 Google, Inc. */ -#include #include #include #include From 7caae0aed04137545e9f8c146d8d1dbb7a8e9865 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 27 Mar 2026 02:43:31 +0000 Subject: [PATCH 206/359] ASoC: soc-core: remove unused dobj_list commit 8a9782346dccd ("ASoC: topology: Add topology core") added dobj_list to Component and Card, but Card side has never been used. Remove it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/874im2xa98.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc.h | 3 --- sound/soc/soc-core.c | 1 - 2 files changed, 4 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index fd6c1c8055d2..d66164fd83e5 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1073,9 +1073,6 @@ struct snd_soc_card { struct list_head dapm_list; struct list_head dapm_dirty; - /* attached dynamic objects */ - struct list_head dobj_list; - /* Generic DAPM context for the card */ struct snd_soc_dapm_context *dapm; struct snd_soc_dapm_stats dapm_stats; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index f80f1a149ad1..67d9ea319c84 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2574,7 +2574,6 @@ int snd_soc_register_card(struct snd_soc_card *card) INIT_LIST_HEAD(&card->list); INIT_LIST_HEAD(&card->rtd_list); INIT_LIST_HEAD(&card->dapm_dirty); - INIT_LIST_HEAD(&card->dobj_list); card->instantiated = 0; mutex_init(&card->mutex); From ae00200acb870ac00551350f26f03ced188bad6f Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Fri, 27 Mar 2026 16:23:31 +0800 Subject: [PATCH 207/359] ASoC: SDCA: fix the register to ctl value conversion for Q7.8 format The division calculation should be implemented using signed integer format. This patch changes mc->shift from an unsigned type to a signed integer during the calculation. Fixes: 501efdcb3b3a ("ASoC: SDCA: Pull the Q7.8 volume helpers out of soc-ops") Signed-off-by: Shuming Fan Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260327082331.2277498-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_asoc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c index 733c9808891a..7709a4ce26e0 100644 --- a/sound/soc/sdca/sdca_asoc.c +++ b/sound/soc/sdca/sdca_asoc.c @@ -850,7 +850,7 @@ static int q78_read(struct snd_soc_component *component, reg_val = snd_soc_component_read(component, reg); - val = (sign_extend32(reg_val, mc->sign_bit) / mc->shift) - mc->min; + val = (sign_extend32(reg_val, mc->sign_bit) / (int)mc->shift) - mc->min; return val & GENMASK(mc->sign_bit, 0); } From ebbe5d957efa09b6636065eeb984078d8e302e5f Mon Sep 17 00:00:00 2001 From: Sachin Mokashi Date: Tue, 24 Mar 2026 12:34:00 -0400 Subject: [PATCH 208/359] ASoC: Intel: ehl_rt5660: remove unused macro definitions DUAL_CHANNEL and NAME_SIZE macros are not being used (anymore) but the macros are still defined. Remove them to clean up dead code. Signed-off-by: Sachin Mokashi Link: https://patch.msgid.link/20260324163400.1276247-1-sachin.mokashi@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/ehl_rt5660.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/sound/soc/intel/boards/ehl_rt5660.c b/sound/soc/intel/boards/ehl_rt5660.c index 5c7b218f22b7..ec4825064436 100644 --- a/sound/soc/intel/boards/ehl_rt5660.c +++ b/sound/soc/intel/boards/ehl_rt5660.c @@ -23,10 +23,8 @@ #include "hda_dsp_common.h" #include "../../codecs/rt5660.h" -#define DUAL_CHANNEL 2 #define HDMI_LINK_START 3 #define HDMI_LINE_END 6 -#define NAME_SIZE 32 #define IDISP_CODEC_MASK 0x4 struct sof_card_private { From 796e119e9b14763be905ad0d023c71a14bc2e931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Wed, 25 Mar 2026 02:24:04 -0300 Subject: [PATCH 209/359] ALSA: core: Validate compress device numbers without dynamic minors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without CONFIG_SND_DYNAMIC_MINORS, ALSA reserves only two fixed minors for compress devices on each card: comprD0 and comprD1. snd_find_free_minor() currently computes the compress minor as type + dev without validating dev first, so device numbers greater than 1 spill into the HWDEP minor range instead of failing registration. ASoC passes rtd->id to snd_compress_new(), so this can happen on real non-dynamic-minor builds. Add a dedicated fixed-minor check for SNDRV_DEVICE_TYPE_COMPRESS in snd_find_free_minor() and reject out-of-range device numbers with -EINVAL before constructing the minor. Also remove the stale TODO in compress_offload.c that still claims multiple compress nodes are missing. Fixes: 3eafc959b32f ("ALSA: core: add support for compressed devices") Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260325-alsa-compress-static-minors-v1-1-0628573bee1c@gmail.com Signed-off-by: Takashi Iwai --- sound/core/compress_offload.c | 7 ------- sound/core/sound.c | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index fdba6e4b25fd..5a0308eb4e31 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -41,13 +41,6 @@ #define COMPR_CODEC_CAPS_OVERFLOW #endif -/* TODO: - * - add substream support for multiple devices in case of - * SND_DYNAMIC_MINORS is not used - * - Multiple node representation - * driver should be able to register multiple nodes - */ - struct snd_compr_file { unsigned long caps; struct snd_compr_stream stream; diff --git a/sound/core/sound.c b/sound/core/sound.c index 93436db24710..8d05fe0d263b 100644 --- a/sound/core/sound.c +++ b/sound/core/sound.c @@ -216,9 +216,16 @@ static int snd_find_free_minor(int type, struct snd_card *card, int dev) case SNDRV_DEVICE_TYPE_RAWMIDI: case SNDRV_DEVICE_TYPE_PCM_PLAYBACK: case SNDRV_DEVICE_TYPE_PCM_CAPTURE: + if (snd_BUG_ON(!card)) + return -EINVAL; + minor = SNDRV_MINOR(card->number, type + dev); + break; case SNDRV_DEVICE_TYPE_COMPRESS: if (snd_BUG_ON(!card)) return -EINVAL; + if (dev < 0 || + dev >= SNDRV_MINOR_HWDEP - SNDRV_MINOR_COMPRESS) + return -EINVAL; minor = SNDRV_MINOR(card->number, type + dev); break; default: From 18d4969e22cc3ff738257e1d7738aafc65a6d2d2 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 28 Mar 2026 18:28:08 +0800 Subject: [PATCH 210/359] ALSA: asihpi: detect truncated control names asihpi_ctl_init() builds mixer control names in the fixed 44-byte hpi_ctl->name buffer with sprintf(). This is not only a defensive cleanup. The current in-tree name tables and format strings can already exceed 44 bytes. For example, "Bitstream 0 Internal 0 Monitor Playback Volume" is 46 characters before the trailing NUL, so the current sprintf() call writes past the end of hpi_ctl->name. The generated control name is used as the ALSA control element key, so blindly truncating it is not sufficient. Switch the formatting to snprintf() and emit an error if truncation happens, showing the truncated name while still keeping the write bounded to hpi_ctl->name. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260328102808.33969-1-pengpeng@iscas.ac.cn Signed-off-by: Takashi Iwai --- sound/pci/asihpi/asihpi.c | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/sound/pci/asihpi/asihpi.c b/sound/pci/asihpi/asihpi.c index 3a64d0562803..b1c7ed7f1604 100644 --- a/sound/pci/asihpi/asihpi.c +++ b/sound/pci/asihpi/asihpi.c @@ -1362,6 +1362,7 @@ static void asihpi_ctl_init(struct snd_kcontrol_new *snd_control, struct hpi_control *hpi_ctl, char *name) { + int len; char *dir; memset(snd_control, 0, sizeof(*snd_control)); snd_control->name = hpi_ctl->name; @@ -1384,23 +1385,30 @@ static void asihpi_ctl_init(struct snd_kcontrol_new *snd_control, dir = "Playback "; /* PCM Playback source, or output node */ if (hpi_ctl->src_node_type && hpi_ctl->dst_node_type) - sprintf(hpi_ctl->name, "%s %d %s %d %s%s", - asihpi_src_names[hpi_ctl->src_node_type], - hpi_ctl->src_node_index, - asihpi_dst_names[hpi_ctl->dst_node_type], - hpi_ctl->dst_node_index, - dir, name); + len = snprintf(hpi_ctl->name, sizeof(hpi_ctl->name), + "%s %d %s %d %s%s", + asihpi_src_names[hpi_ctl->src_node_type], + hpi_ctl->src_node_index, + asihpi_dst_names[hpi_ctl->dst_node_type], + hpi_ctl->dst_node_index, + dir, name); else if (hpi_ctl->dst_node_type) { - sprintf(hpi_ctl->name, "%s %d %s%s", - asihpi_dst_names[hpi_ctl->dst_node_type], - hpi_ctl->dst_node_index, - dir, name); + len = snprintf(hpi_ctl->name, sizeof(hpi_ctl->name), + "%s %d %s%s", + asihpi_dst_names[hpi_ctl->dst_node_type], + hpi_ctl->dst_node_index, + dir, name); } else { - sprintf(hpi_ctl->name, "%s %d %s%s", - asihpi_src_names[hpi_ctl->src_node_type], - hpi_ctl->src_node_index, - dir, name); + len = snprintf(hpi_ctl->name, sizeof(hpi_ctl->name), + "%s %d %s%s", + asihpi_src_names[hpi_ctl->src_node_type], + hpi_ctl->src_node_index, + dir, name); } + + if (len >= sizeof(hpi_ctl->name)) + pr_err("asihpi: truncated control name: %s\n", + hpi_ctl->name); } /*------------------------------------------------------------ From 0da18c2dd1cc2a026416222ed206e2f269edf055 Mon Sep 17 00:00:00 2001 From: Phil Willoughby Date: Sat, 28 Mar 2026 11:08:41 +0000 Subject: [PATCH 211/359] ALSA: usb-audio: Add quirks for Arturia AF16Rig The AF16Rig supports 34 channels at 44.1k/48k, 18 channels at 88.2k/96k and 10 channels at 176.4k/192k. This quirks is necessary because the automatic probing process we would otherwise use fails. The root cause of that is that the AF16Rig clock is not readable (its descriptor says that it is but the reads fail). Except as described below, the values in the audio format quirks were copied from the USB descriptors of the device. The rate information is from the datasheet of the device. The clock is the internal clock of the AF16Rig. Tested-By: Phil Willoughby I have tested all the configurations enabled by this patch. Cc: Jaroslav Kysela Cc: Takashi Iwai Signed-off-by: Phil Willoughby Link: https://patch.msgid.link/20260328112426.14816-1-willerz@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks-table.h | 165 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index eafc0d73cca1..8f79a15055a6 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -3900,5 +3900,170 @@ YAMAHA_DEVICE(0x7010, "UB99"), QUIRK_RME_DIGIFACE(0x3f8c), QUIRK_RME_DIGIFACE(0x3fa0), +/* Arturia AudioFuse 16Rig Audio */ +/* AF16Rig MIDI has USB PID 0xaf21 and appears to work OK without quirks */ +{ + USB_DEVICE(0x1c75, 0xaf20), + QUIRK_DRIVER_INFO { + .vendor_name = "Arturia", + .product_name = "AF16Rig", + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(0) }, + { + QUIRK_DATA_AUDIOFORMAT(1) { /* Playback */ + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 34, + .fmt_type = UAC_FORMAT_TYPE_I_PCM, + .fmt_bits = 24, + .fmt_sz = 4, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .datainterval = 1, + .protocol = UAC_VERSION_2, + .maxpacksize = 0x03b8, + .rates = SNDRV_PCM_RATE_44100| + SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { 44100, 48000 }, + .clock = 41, + } + }, + { + QUIRK_DATA_AUDIOFORMAT(1) { /* Playback */ + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 18, + .fmt_type = UAC_FORMAT_TYPE_I_PCM, + .fmt_bits = 24, + .fmt_sz = 4, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .datainterval = 1, + .protocol = UAC_VERSION_2, + .maxpacksize = 0x03a8, + .rates = SNDRV_PCM_RATE_88200| + SNDRV_PCM_RATE_96000, + .rate_min = 88200, + .rate_max = 96000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { 88200, 96000 }, + .clock = 41, + } + }, + { + QUIRK_DATA_AUDIOFORMAT(1) { /* Playback */ + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 10, + .fmt_type = UAC_FORMAT_TYPE_I_PCM, + .fmt_bits = 24, + .fmt_sz = 4, + .iface = 1, + .altsetting = 3, + .altset_idx = 3, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .datainterval = 1, + .protocol = UAC_VERSION_2, + .maxpacksize = 0x03e8, + .rates = SNDRV_PCM_RATE_176400| + SNDRV_PCM_RATE_192000, + .rate_min = 176400, + .rate_max = 192000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { 176400, 192000 }, + .clock = 41, + } + }, + { + QUIRK_DATA_AUDIOFORMAT(2) { /* Capture */ + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 34, + .fmt_type = UAC_FORMAT_TYPE_I_PCM, + .fmt_bits = 24, + .fmt_sz = 4, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x81, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .datainterval = 1, + .protocol = UAC_VERSION_2, + .maxpacksize = 0x03b8, + .rates = SNDRV_PCM_RATE_44100| + SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { 44100, 48000 }, + .clock = 41, + } + }, + { + QUIRK_DATA_AUDIOFORMAT(2) { /* Capture */ + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 18, + .fmt_type = UAC_FORMAT_TYPE_I_PCM, + .fmt_bits = 24, + .fmt_sz = 4, + .iface = 2, + .altsetting = 2, + .altset_idx = 2, + .endpoint = 0x81, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .datainterval = 1, + .protocol = UAC_VERSION_2, + .maxpacksize = 0x03a8, + .rates = SNDRV_PCM_RATE_88200| + SNDRV_PCM_RATE_96000, + .rate_min = 88200, + .rate_max = 96000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { 88200, 96000 }, + .clock = 41, + } + }, + { + QUIRK_DATA_AUDIOFORMAT(2) { /* Capture */ + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 10, + .fmt_type = UAC_FORMAT_TYPE_I_PCM, + .fmt_bits = 24, + .fmt_sz = 4, + .iface = 2, + .altsetting = 3, + .altset_idx = 3, + .endpoint = 0x81, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .datainterval = 1, + .protocol = UAC_VERSION_2, + .maxpacksize = 0x03e8, + .rates = SNDRV_PCM_RATE_176400| + SNDRV_PCM_RATE_192000, + .rate_min = 176400, + .rate_max = 192000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { 176400, 192000 }, + .clock = 41, + } + }, + { QUIRK_DATA_IGNORE(3) }, /* Firmware update */ + QUIRK_COMPOSITE_END + } + } +}, + #undef USB_DEVICE_VENDOR_SPEC #undef USB_AUDIO_DEVICE From 3bd246d1cf609a80cae19e4aefb599256a72b1a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Sat, 28 Mar 2026 01:53:35 -0300 Subject: [PATCH 212/359] ALSA: hda/proc: show GPI and GPO state in codec proc output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit print_gpio() prints the GPIO capability header and the bidirectional GPIO state, but it never reports the separate GPI and GPO pins even though AC_PAR_GPIO_CAP exposes their counts. The HD-audio specification defines dedicated GPI and GPO verbs alongside the GPIO ones, so codecs with input-only or output-only general-purpose pins currently lose that state from /proc/asound/card*/codec#* altogether. Add the missing read verb definitions and extend print_gpio() to dump the GPI and GPO pins, too, while leaving the existing IO[] output unchanged. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260328-hda-proc-gpi-gpo-v1-1-fabb36564bee@gmail.com Signed-off-by: Takashi Iwai --- include/sound/hda_verbs.h | 7 ++- sound/hda/common/proc.c | 100 ++++++++++++++++++++++++++------------ 2 files changed, 74 insertions(+), 33 deletions(-) diff --git a/include/sound/hda_verbs.h b/include/sound/hda_verbs.h index 006d358acce2..127e7016e4fe 100644 --- a/include/sound/hda_verbs.h +++ b/include/sound/hda_verbs.h @@ -56,7 +56,12 @@ enum { #define AC_VERB_GET_DIGI_CONVERT_1 0x0f0d #define AC_VERB_GET_DIGI_CONVERT_2 0x0f0e /* unused */ #define AC_VERB_GET_VOLUME_KNOB_CONTROL 0x0f0f -/* f10-f1a: GPIO */ +/* f10-f1a: GPI/GPO/GPIO */ +#define AC_VERB_GET_GPI_DATA 0x0f10 +#define AC_VERB_GET_GPI_WAKE_MASK 0x0f11 +#define AC_VERB_GET_GPI_UNSOLICITED_RSP_MASK 0x0f12 +#define AC_VERB_GET_GPI_STICKY_MASK 0x0f13 +#define AC_VERB_GET_GPO_DATA 0x0f14 #define AC_VERB_GET_GPIO_DATA 0x0f15 #define AC_VERB_GET_GPIO_MASK 0x0f16 #define AC_VERB_GET_GPIO_DIRECTION 0x0f17 diff --git a/sound/hda/common/proc.c b/sound/hda/common/proc.c index 3bc33c5617b2..c83796b13d3d 100644 --- a/sound/hda/common/proc.c +++ b/sound/hda/common/proc.c @@ -640,41 +640,78 @@ static void print_gpio(struct snd_info_buffer *buffer, { unsigned int gpio = param_read(codec, codec->core.afg, AC_PAR_GPIO_CAP); - unsigned int enable, direction, wake, unsol, sticky, data; - int i, max; + int i, gpio_max, gpo_max, gpi_max; + + gpio_max = gpio & AC_GPIO_IO_COUNT; + gpo_max = (gpio & AC_GPIO_O_COUNT) >> AC_GPIO_O_COUNT_SHIFT; + gpi_max = (gpio & AC_GPIO_I_COUNT) >> AC_GPIO_I_COUNT_SHIFT; + snd_iprintf(buffer, "GPIO: io=%d, o=%d, i=%d, " "unsolicited=%d, wake=%d\n", - gpio & AC_GPIO_IO_COUNT, - (gpio & AC_GPIO_O_COUNT) >> AC_GPIO_O_COUNT_SHIFT, - (gpio & AC_GPIO_I_COUNT) >> AC_GPIO_I_COUNT_SHIFT, + gpio_max, gpo_max, gpi_max, (gpio & AC_GPIO_UNSOLICITED) ? 1 : 0, (gpio & AC_GPIO_WAKE) ? 1 : 0); - max = gpio & AC_GPIO_IO_COUNT; - if (!max || max > 8) - return; - enable = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_GPIO_MASK, 0); - direction = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_GPIO_DIRECTION, 0); - wake = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_GPIO_WAKE_MASK, 0); - unsol = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK, 0); - sticky = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_GPIO_STICKY_MASK, 0); - data = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_GPIO_DATA, 0); - for (i = 0; i < max; ++i) - snd_iprintf(buffer, - " IO[%d]: enable=%d, dir=%d, wake=%d, " - "sticky=%d, data=%d, unsol=%d\n", i, - (enable & (1<mixers); print_nid_array(buffer, codec, nid, &codec->nids); } @@ -940,4 +977,3 @@ int snd_hda_codec_proc_new(struct hda_codec *codec) snprintf(name, sizeof(name), "codec#%d", codec->core.addr); return snd_card_ro_proc_new(codec->card, name, codec, print_codec_info); } - From 38f6e93dedbc1b6c2a6e97110ab7e872c257a5e3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 28 Mar 2026 14:43:05 +0100 Subject: [PATCH 213/359] ALSA: hda: Add missing SET_GPI_* and SET_GPO_* verb definitions We've added the definitions of the missing GPI and GPO verbs for reading in the previous commit, but the counter-part for setting values is missing. Add the definitions of missing verbs for comprehensiveness. Link: https://patch.msgid.link/20260328134319.207482-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/hda_verbs.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/sound/hda_verbs.h b/include/sound/hda_verbs.h index 127e7016e4fe..6066954409aa 100644 --- a/include/sound/hda_verbs.h +++ b/include/sound/hda_verbs.h @@ -104,6 +104,11 @@ enum { #define AC_VERB_SET_DIGI_CONVERT_2 0x70e #define AC_VERB_SET_DIGI_CONVERT_3 0x73e #define AC_VERB_SET_VOLUME_KNOB_CONTROL 0x70f +#define AC_VERB_SET_GPI_DATA 0x710 +#define AC_VERB_SET_GPI_WAKE_MASK 0x711 +#define AC_VERB_SET_SPI_UNSOLICITED_RSP_MASK 0x712 +#define AC_VERB_SET_GPI_STICKY_MASK 0x713 +#define AC_VERB_SET_GPO_DATA 0x714 #define AC_VERB_SET_GPIO_DATA 0x715 #define AC_VERB_SET_GPIO_MASK 0x716 #define AC_VERB_SET_GPIO_DIRECTION 0x717 From 31183edd9cb3465af5c8b9cb16f42259cbf27109 Mon Sep 17 00:00:00 2001 From: Phil Willoughby Date: Sat, 28 Mar 2026 16:02:58 +0000 Subject: [PATCH 214/359] ALSA: usb-audio: tidy up the AF16Rig quirks Use macros to make the AF16Rig quirk table smaller. Add a disabled block containing the theoretical quirks for the other clock sources that the AF16Rig has. It's disabled because I cannot test it. Fixes: 0da18c2dd1cc ("ALSA: usb-audio: Add quirks for Arturia AF16Rig") Tested-By: Phil Willoughby Signed-off-by: Phil Willoughby Link: https://patch.msgid.link/20260328160326.23665-1-willerz@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks-table.h | 200 ++++++++++----------------------------- 1 file changed, 50 insertions(+), 150 deletions(-) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 8f79a15055a6..b6dfe3b63c67 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -3900,6 +3900,44 @@ YAMAHA_DEVICE(0x7010, "UB99"), QUIRK_RME_DIGIFACE(0x3f8c), QUIRK_RME_DIGIFACE(0x3fa0), +#define QUIRK_AF16RIG(channel_count_, alt_setting_, \ + low_rate_, high_rate_, pack_size_, \ + clock_, interface_, endpoint_) \ + { \ + QUIRK_DATA_AUDIOFORMAT(interface_) { \ + .formats = SNDRV_PCM_FMTBIT_S32_LE, \ + .channels = channel_count_, \ + .fmt_type = UAC_FORMAT_TYPE_I_PCM, \ + .fmt_bits = 24, \ + .fmt_sz = 4, \ + .iface = interface_, \ + .altsetting = alt_setting_, \ + .altset_idx = alt_setting_, \ + .endpoint = endpoint_, \ + .ep_attr = USB_ENDPOINT_XFER_ISOC | \ + USB_ENDPOINT_SYNC_ASYNC, \ + .datainterval = 1, \ + .protocol = UAC_VERSION_2, \ + .maxpacksize = pack_size_, \ + .rates = SNDRV_PCM_RATE_##low_rate_ | \ + SNDRV_PCM_RATE_##high_rate_, \ + .rate_min = low_rate_, \ + .rate_max = high_rate_, \ + .nr_rates = 2, \ + .rate_table = (unsigned int[]) { \ + low_rate_, high_rate_ }, \ + .clock = clock_, \ + } \ + } + +#define QUIRK_AF16RIG_CLOCK(clock) \ + QUIRK_AF16RIG(34, 1, 44100, 48000, 0x3b8, clock, 1, 0x01), \ + QUIRK_AF16RIG(34, 1, 44100, 48000, 0x3b8, clock, 2, 0x81), \ + QUIRK_AF16RIG(18, 2, 88200, 96000, 0x3a8, clock, 1, 0x01), \ + QUIRK_AF16RIG(18, 2, 88200, 96000, 0x3a8, clock, 2, 0x81), \ + QUIRK_AF16RIG(10, 3, 176400, 192000, 0x3e8, clock, 1, 0x01), \ + QUIRK_AF16RIG(10, 3, 176400, 192000, 0x3e8, clock, 2, 0x81) + /* Arturia AudioFuse 16Rig Audio */ /* AF16Rig MIDI has USB PID 0xaf21 and appears to work OK without quirks */ { @@ -3909,161 +3947,23 @@ QUIRK_RME_DIGIFACE(0x3fa0), .product_name = "AF16Rig", QUIRK_DATA_COMPOSITE { { QUIRK_DATA_STANDARD_MIXER(0) }, - { - QUIRK_DATA_AUDIOFORMAT(1) { /* Playback */ - .formats = SNDRV_PCM_FMTBIT_S32_LE, - .channels = 34, - .fmt_type = UAC_FORMAT_TYPE_I_PCM, - .fmt_bits = 24, - .fmt_sz = 4, - .iface = 1, - .altsetting = 1, - .altset_idx = 1, - .endpoint = 0x01, - .ep_attr = USB_ENDPOINT_XFER_ISOC| - USB_ENDPOINT_SYNC_ASYNC, - .datainterval = 1, - .protocol = UAC_VERSION_2, - .maxpacksize = 0x03b8, - .rates = SNDRV_PCM_RATE_44100| - SNDRV_PCM_RATE_48000, - .rate_min = 44100, - .rate_max = 48000, - .nr_rates = 2, - .rate_table = (unsigned int[]) { 44100, 48000 }, - .clock = 41, - } - }, - { - QUIRK_DATA_AUDIOFORMAT(1) { /* Playback */ - .formats = SNDRV_PCM_FMTBIT_S32_LE, - .channels = 18, - .fmt_type = UAC_FORMAT_TYPE_I_PCM, - .fmt_bits = 24, - .fmt_sz = 4, - .iface = 1, - .altsetting = 1, - .altset_idx = 1, - .endpoint = 0x01, - .ep_attr = USB_ENDPOINT_XFER_ISOC| - USB_ENDPOINT_SYNC_ASYNC, - .datainterval = 1, - .protocol = UAC_VERSION_2, - .maxpacksize = 0x03a8, - .rates = SNDRV_PCM_RATE_88200| - SNDRV_PCM_RATE_96000, - .rate_min = 88200, - .rate_max = 96000, - .nr_rates = 2, - .rate_table = (unsigned int[]) { 88200, 96000 }, - .clock = 41, - } - }, - { - QUIRK_DATA_AUDIOFORMAT(1) { /* Playback */ - .formats = SNDRV_PCM_FMTBIT_S32_LE, - .channels = 10, - .fmt_type = UAC_FORMAT_TYPE_I_PCM, - .fmt_bits = 24, - .fmt_sz = 4, - .iface = 1, - .altsetting = 3, - .altset_idx = 3, - .endpoint = 0x01, - .ep_attr = USB_ENDPOINT_XFER_ISOC| - USB_ENDPOINT_SYNC_ASYNC, - .datainterval = 1, - .protocol = UAC_VERSION_2, - .maxpacksize = 0x03e8, - .rates = SNDRV_PCM_RATE_176400| - SNDRV_PCM_RATE_192000, - .rate_min = 176400, - .rate_max = 192000, - .nr_rates = 2, - .rate_table = (unsigned int[]) { 176400, 192000 }, - .clock = 41, - } - }, - { - QUIRK_DATA_AUDIOFORMAT(2) { /* Capture */ - .formats = SNDRV_PCM_FMTBIT_S32_LE, - .channels = 34, - .fmt_type = UAC_FORMAT_TYPE_I_PCM, - .fmt_bits = 24, - .fmt_sz = 4, - .iface = 2, - .altsetting = 1, - .altset_idx = 1, - .endpoint = 0x81, - .ep_attr = USB_ENDPOINT_XFER_ISOC| - USB_ENDPOINT_SYNC_ASYNC, - .datainterval = 1, - .protocol = UAC_VERSION_2, - .maxpacksize = 0x03b8, - .rates = SNDRV_PCM_RATE_44100| - SNDRV_PCM_RATE_48000, - .rate_min = 44100, - .rate_max = 48000, - .nr_rates = 2, - .rate_table = (unsigned int[]) { 44100, 48000 }, - .clock = 41, - } - }, - { - QUIRK_DATA_AUDIOFORMAT(2) { /* Capture */ - .formats = SNDRV_PCM_FMTBIT_S32_LE, - .channels = 18, - .fmt_type = UAC_FORMAT_TYPE_I_PCM, - .fmt_bits = 24, - .fmt_sz = 4, - .iface = 2, - .altsetting = 2, - .altset_idx = 2, - .endpoint = 0x81, - .ep_attr = USB_ENDPOINT_XFER_ISOC| - USB_ENDPOINT_SYNC_ASYNC, - .datainterval = 1, - .protocol = UAC_VERSION_2, - .maxpacksize = 0x03a8, - .rates = SNDRV_PCM_RATE_88200| - SNDRV_PCM_RATE_96000, - .rate_min = 88200, - .rate_max = 96000, - .nr_rates = 2, - .rate_table = (unsigned int[]) { 88200, 96000 }, - .clock = 41, - } - }, - { - QUIRK_DATA_AUDIOFORMAT(2) { /* Capture */ - .formats = SNDRV_PCM_FMTBIT_S32_LE, - .channels = 10, - .fmt_type = UAC_FORMAT_TYPE_I_PCM, - .fmt_bits = 24, - .fmt_sz = 4, - .iface = 2, - .altsetting = 3, - .altset_idx = 3, - .endpoint = 0x81, - .ep_attr = USB_ENDPOINT_XFER_ISOC| - USB_ENDPOINT_SYNC_ASYNC, - .datainterval = 1, - .protocol = UAC_VERSION_2, - .maxpacksize = 0x03e8, - .rates = SNDRV_PCM_RATE_176400| - SNDRV_PCM_RATE_192000, - .rate_min = 176400, - .rate_max = 192000, - .nr_rates = 2, - .rate_table = (unsigned int[]) { 176400, 192000 }, - .clock = 41, - } - }, + QUIRK_AF16RIG_CLOCK(41), /* Internal clock */ +#if 0 +/* These are disabled because I don't have the required hardware to test + * them. I suspect that the ADAT clock might not follow 176400 or 192000 + * because the AF16Rig won't accept ADAT audio data at those rates. + */ + QUIRK_AF16RIG_CLOCK(43), /* ADAT clock */ + QUIRK_AF16RIG_CLOCK(44), /* BNC word clock */ +#endif { QUIRK_DATA_IGNORE(3) }, /* Firmware update */ QUIRK_COMPOSITE_END } } }, +#undef QUIRK_AF16RIG_CLOCK +#undef QUIRK_AF16RIG + #undef USB_DEVICE_VENDOR_SPEC #undef USB_AUDIO_DEVICE From 9220b8cc51c960e98a9532ec990c55bc546e3b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Sat, 28 Mar 2026 20:42:01 -0300 Subject: [PATCH 215/359] ALSA: hda: intel: Drop obsolete probe-work unlock workaround MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit ab949d519601 ("ALSA: hda - Fix deadlock of controller device lock at unbinding") added a temporary device_unlock()/device_lock() pair around probe-work cancellation to avoid a deadlock between controller unbind and codec probe. That deadlock depended on the driver core taking both a device lock and its parent lock during bind and unbind. Since commit 8c97a46af04b ("driver core: hold dev's parent lock when needed") and follow-up fixes, the parent lock is only taken when bus->need_parent_lock is set. The HDA bus does not set that flag, so codec binding no longer locks the controller device as the codec's parent. Keep cancel_delayed_work_sync(), since the async probe/remove race still needs to be serialized, but drop the stale unlock/relock workaround and its outdated FIXME comment. Keeping it around only opens an unnecessary unlocked window inside azx_remove(). Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260328-hda-intel-drop-obsolete-probe-workaround-v1-1-bc43aeafc98b@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/controllers/intel.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 3f434994c18d..89de18dc953d 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -2421,20 +2421,7 @@ static void azx_remove(struct pci_dev *pci) /* cancel the pending probing work */ chip = card->private_data; hda = container_of(chip, struct hda_intel, chip); - /* FIXME: below is an ugly workaround. - * Both device_release_driver() and driver_probe_device() - * take *both* the device's and its parent's lock before - * calling the remove() and probe() callbacks. The codec - * probe takes the locks of both the codec itself and its - * parent, i.e. the PCI controller dev. Meanwhile, when - * the PCI controller is unbound, it takes its lock, too - * ==> ouch, a deadlock! - * As a workaround, we unlock temporarily here the controller - * device during cancel_work_sync() call. - */ - device_unlock(&pci->dev); cancel_delayed_work_sync(&hda->probe_work); - device_lock(&pci->dev); clear_bit(chip->dev_index, probed_devs); pci_set_drvdata(pci, NULL); From 472571498baaa67b6ea70d6c0154730be3da3c36 Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Mon, 30 Mar 2026 13:41:31 +0800 Subject: [PATCH 216/359] ALSA: hda/cs8409: Fix error message in cs8409_i2c_bulk_read() The error message in cs8409_i2c_bulk_read() incorrectly says "I2C Bulk Write Failed" when it should say "I2C Bulk Read Failed". This is a copy-paste error from cs8409_i2c_bulk_write(). Signed-off-by: wangdicheng Link: https://patch.msgid.link/20260330054131.434994-1-wangdich9700@163.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/cirrus/cs8409.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/cirrus/cs8409.c b/sound/hda/codecs/cirrus/cs8409.c index fad705092777..2d8f482e6474 100644 --- a/sound/hda/codecs/cirrus/cs8409.c +++ b/sound/hda/codecs/cirrus/cs8409.c @@ -268,7 +268,7 @@ static int cs8409_i2c_bulk_read(struct sub_codec *scodec, struct cs8409_i2c_para return 0; error: - codec_err(codec, "I2C Bulk Write Failed 0x%02x\n", scodec->addr); + codec_err(codec, "I2C Bulk Read Failed 0x%02x\n", scodec->addr); return -EIO; } From 4ec93f070eda6b765b62efcaed9241c3b3b0b6ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Mon, 30 Mar 2026 01:00:34 -0300 Subject: [PATCH 217/359] ALSA: aoa: i2sbus: fix OF node lifetime handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i2sbus_add_dev() keeps the matched "sound" child pointer after for_each_child_of_node() has dropped the iterator reference. Take an extra reference before saving that node and drop it after the layout-id/device-id lookup is complete. The function also stores np in dev->sound.ofdev.dev.of_node without taking a reference for the embedded soundbus device. Since i2sbus overrides the embedded platform device release callback, balance that reference explicitly in the local error path and in i2sbus_release_dev(). Fixes: f3d9478b2ce4 ("[ALSA] snd-aoa: add snd-aoa") Cc: stable@vger.kernel.org Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260330-aoa-i2sbus-ofnode-lifetime-v1-1-51c309f4ff06@gmail.com Signed-off-by: Takashi Iwai --- sound/aoa/soundbus/i2sbus/core.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sound/aoa/soundbus/i2sbus/core.c b/sound/aoa/soundbus/i2sbus/core.c index 22c956267f4e..833c44c0a950 100644 --- a/sound/aoa/soundbus/i2sbus/core.c +++ b/sound/aoa/soundbus/i2sbus/core.c @@ -84,6 +84,7 @@ static void i2sbus_release_dev(struct device *dev) for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) free_irq(i2sdev->interrupts[i], i2sdev); i2sbus_control_remove_dev(i2sdev->control, i2sdev); + of_node_put(i2sdev->sound.ofdev.dev.of_node); mutex_destroy(&i2sdev->lock); kfree(i2sdev); } @@ -147,7 +148,6 @@ static int i2sbus_get_and_fixup_rsrc(struct device_node *np, int index, } /* Returns 1 if added, 0 for otherwise; don't return a negative value! */ -/* FIXME: look at device node refcounting */ static int i2sbus_add_dev(struct macio_dev *macio, struct i2sbus_control *control, struct device_node *np) @@ -178,8 +178,9 @@ static int i2sbus_add_dev(struct macio_dev *macio, i = 0; for_each_child_of_node(np, child) { if (of_node_name_eq(child, "sound")) { + of_node_put(sound); i++; - sound = child; + sound = of_node_get(child); } } if (i == 1) { @@ -205,6 +206,7 @@ static int i2sbus_add_dev(struct macio_dev *macio, } } } + of_node_put(sound); /* for the time being, until we can handle non-layout-id * things in some fabric, refuse to attach if there is no * layout-id property or we haven't been forced to attach. @@ -219,7 +221,7 @@ static int i2sbus_add_dev(struct macio_dev *macio, mutex_init(&dev->lock); spin_lock_init(&dev->low_lock); dev->sound.ofdev.archdata.dma_mask = macio->ofdev.archdata.dma_mask; - dev->sound.ofdev.dev.of_node = np; + dev->sound.ofdev.dev.of_node = of_node_get(np); dev->sound.ofdev.dev.dma_mask = &dev->sound.ofdev.archdata.dma_mask; dev->sound.ofdev.dev.parent = &macio->ofdev.dev; dev->sound.ofdev.dev.release = i2sbus_release_dev; @@ -327,6 +329,7 @@ static int i2sbus_add_dev(struct macio_dev *macio, for (i=0;i<3;i++) release_and_free_resource(dev->allocated_resource[i]); mutex_destroy(&dev->lock); + of_node_put(dev->sound.ofdev.dev.of_node); kfree(dev); return 0; } From 1e28cdeec31333b165f72b6ad647652c4c6f6ff2 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 29 Mar 2026 18:31:46 +0200 Subject: [PATCH 218/359] ASoC: codecs: inline i2c_check_functionality checks Inline i2c_check_functionality() checks, since the function returns a boolean status rather than an error code. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260329163152.366356-2-thorsten.blum@linux.dev Signed-off-by: Mark Brown --- sound/soc/codecs/aw87390.c | 3 +-- sound/soc/codecs/aw88081.c | 3 +-- sound/soc/codecs/aw88261.c | 3 +-- sound/soc/codecs/max98390.c | 6 ++---- sound/soc/codecs/max98520.c | 4 ++-- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/sound/soc/codecs/aw87390.c b/sound/soc/codecs/aw87390.c index 613daccca3af..37ca42a25889 100644 --- a/sound/soc/codecs/aw87390.c +++ b/sound/soc/codecs/aw87390.c @@ -544,8 +544,7 @@ static int aw87390_i2c_probe(struct i2c_client *i2c) const struct snd_soc_component_driver *priv; int ret; - ret = i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C); - if (!ret) + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C)) return dev_err_probe(&i2c->dev, -ENXIO, "check_functionality failed\n"); aw87390 = devm_kzalloc(&i2c->dev, sizeof(*aw87390), GFP_KERNEL); diff --git a/sound/soc/codecs/aw88081.c b/sound/soc/codecs/aw88081.c index fbd1fd12381a..8c5bb3ea0227 100644 --- a/sound/soc/codecs/aw88081.c +++ b/sound/soc/codecs/aw88081.c @@ -1253,8 +1253,7 @@ static int aw88081_i2c_probe(struct i2c_client *i2c) struct aw88081 *aw88081; int ret; - ret = i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C); - if (!ret) + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C)) return dev_err_probe(&i2c->dev, -ENXIO, "check_functionality failed"); aw88081 = devm_kzalloc(&i2c->dev, sizeof(*aw88081), GFP_KERNEL); diff --git a/sound/soc/codecs/aw88261.c b/sound/soc/codecs/aw88261.c index 02f8c2bb2569..a6805d5405cd 100644 --- a/sound/soc/codecs/aw88261.c +++ b/sound/soc/codecs/aw88261.c @@ -1242,8 +1242,7 @@ static int aw88261_i2c_probe(struct i2c_client *i2c) struct aw88261 *aw88261; int ret; - ret = i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C); - if (!ret) + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C)) return dev_err_probe(&i2c->dev, -ENXIO, "check_functionality failed"); aw88261 = devm_kzalloc(&i2c->dev, sizeof(*aw88261), GFP_KERNEL); diff --git a/sound/soc/codecs/max98390.c b/sound/soc/codecs/max98390.c index ff58805e97d1..65f095c47191 100644 --- a/sound/soc/codecs/max98390.c +++ b/sound/soc/codecs/max98390.c @@ -1015,10 +1015,8 @@ static int max98390_i2c_probe(struct i2c_client *i2c) struct i2c_adapter *adapter = i2c->adapter; struct gpio_desc *reset_gpio; - ret = i2c_check_functionality(adapter, - I2C_FUNC_SMBUS_BYTE - | I2C_FUNC_SMBUS_BYTE_DATA); - if (!ret) { + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA)) { dev_err(&i2c->dev, "I2C check functionality failed\n"); return -ENXIO; } diff --git a/sound/soc/codecs/max98520.c b/sound/soc/codecs/max98520.c index 2bf8976c1828..5bc3d95ade5a 100644 --- a/sound/soc/codecs/max98520.c +++ b/sound/soc/codecs/max98520.c @@ -681,8 +681,8 @@ static int max98520_i2c_probe(struct i2c_client *i2c) struct max98520_priv *max98520; struct i2c_adapter *adapter = to_i2c_adapter(i2c->dev.parent); - ret = i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA); - if (!ret) { + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA)) { dev_err(&i2c->dev, "I2C check functionality failed\n"); return -ENXIO; } From 6389dbd5c4a2d819ec342f89bd65883ab021278e Mon Sep 17 00:00:00 2001 From: Leonard Lausen Date: Fri, 27 Mar 2026 22:25:15 +0000 Subject: [PATCH 219/359] ALSA: hda: cs35l41: Fix boost type for HP Dragonfly 13.5 inch G4 The HP Dragonfly 13.5 inch G4 (SSID 103C8B63) has _DSD properties in ACPI firmware with valid reset-gpios and cs-gpios for the four CS35L41 amplifiers on SPI. However, the _DSD specifies cirrus,boost-type as Internal (0), while the hardware requires External Boost. With Internal Boost configured, the amplifiers trigger "Amp short error" when audio is played at moderate-to-high volume, eventually shutting down entirely. Add a configuration table entry to override the boost type to External, similar to the existing workaround for 103C89C6. All GPIO indices are set to -1 since the _DSD provides valid reset-gpios and cs-gpios. Confirmed on BIOS V90 01.11.00 (January 2026), the latest available. Link: https://bugzilla.kernel.org/show_bug.cgi?id=219520 Originally-by: Nicholas Wang Signed-off-by: Leonard Lausen Link: https://patch.msgid.link/db84dcf91bc8dbd217b35572b177d967655ff903@lausen.nl Signed-off-by: Takashi Iwai --- sound/hda/codecs/side-codecs/cs35l41_hda_property.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c index 16d5ea77192f..732ae534db36 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c @@ -55,6 +55,11 @@ static const struct cs35l41_config cs35l41_config_table[] = { { "103C8A30", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8A31", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8A6E", 4, EXTERNAL, { CS35L41_LEFT, CS35L41_LEFT, CS35L41_RIGHT, CS35L41_RIGHT }, 0, -1, -1, 0, 0, 0 }, +/* + * Device 103C8B63 has _DSD with valid reset-gpios and cs-gpios, however the + * boost type is incorrectly set to Internal. Override to External Boost. + */ + { "103C8B63", 4, EXTERNAL, { CS35L41_RIGHT, CS35L41_LEFT, CS35L41_RIGHT, CS35L41_LEFT }, -1, -1, -1, 0, 0, 0 }, { "103C8BB3", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8BB4", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8BDD", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, @@ -475,6 +480,7 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CSC3551", "103C8A30", generic_dsd_config }, { "CSC3551", "103C8A31", generic_dsd_config }, { "CSC3551", "103C8A6E", generic_dsd_config }, + { "CSC3551", "103C8B63", generic_dsd_config }, { "CSC3551", "103C8BB3", generic_dsd_config }, { "CSC3551", "103C8BB4", generic_dsd_config }, { "CSC3551", "103C8BDD", generic_dsd_config }, From 8dbbd39d0605b93a176f2c775dd2b6bb7c7a8adb Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Mon, 30 Mar 2026 14:46:17 +0100 Subject: [PATCH 220/359] ALSA: hda/realtek: Add support for HP Laptops Add support for HP Auster, Trekker and Agusta G7KX. Laptops use 2 CS35L41 Amps with HDA, using Internal boost, with I2C Signed-off-by: Stefan Binding Link: https://patch.msgid.link/20260330134651.443439-2-sbinding@opensource.cirrus.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 4c49f1195e1b..ededb650a235 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7130,6 +7130,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8e60, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e61, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e62, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e75, "HP Trekker G7JC", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e8a, "HP NexusX", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8e9c, "HP 16 Clipper OmniBook X X360", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e9d, "HP 17 Turbine OmniBook X UMA", ALC287_FIXUP_CS35L41_I2C_2), @@ -7151,8 +7152,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8ee4, "HP Bantie A6U", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x8ee5, "HP Bantie A6U", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x8ee7, "HP Abe A6U", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), + SND_PCI_QUIRK(0x103c, 0x8f07, "HP Agusta G7KX", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8f0c, "HP ZBook X G2i 16W", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8f0e, "HP ZBook X G2i 16W", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8f2d, "HP Auster 14", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8f2e, "HP Auster 14", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8f40, "HP ZBook 8 G2a 14", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8f41, "HP ZBook 8 G2a 16", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8f42, "HP ZBook 8 G2a 14W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), From 66a6333ba5087b00b7d6cb9ff671f4e2739383b3 Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Mon, 30 Mar 2026 14:46:18 +0100 Subject: [PATCH 221/359] ALSA: hda/realtek: Add support for ASUS 2026 Commercial laptops using CS35L41 HDA Add support for laptops: - ASUS PM5406CGA - ASUS PM5606CGA - ASUS P5406CCA - ASUS P5606CCA Laptops use 2 CS35L41 Amps with HDA, using Internal boost, with I2C or SPI. Signed-off-by: Stefan Binding Link: https://patch.msgid.link/20260330134651.443439-3-sbinding@opensource.cirrus.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index ededb650a235..9940fe7d5f9d 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7297,6 +7297,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x31e1, "ASUS B5605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x31f1, "ASUS B3605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x3391, "ASUS PM3606CKA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x3601, "ASUS PM5406CGA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x3611, "ASUS PM5606CGA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x3701, "ASUS P5406CCA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x3711, "ASUS P5606CCA", ALC245_FIXUP_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x3a20, "ASUS G614JZR", ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS), SND_PCI_QUIRK(0x1043, 0x3a30, "ASUS G814JVR/JIR", ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS), SND_PCI_QUIRK(0x1043, 0x3a40, "ASUS G814JZR", ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS), From b81f63108250818ed17fc7df9fdf9a7fb84f3f69 Mon Sep 17 00:00:00 2001 From: Jihed Chaibi Date: Tue, 24 Mar 2026 23:39:07 +0100 Subject: [PATCH 222/359] ASoC: samsung: spdif: Convert to devm_ioremap_resource() Replace the open-coded request_mem_region() + ioremap() sequence with devm_ioremap_resource(), which handles both the region claim and mapping under devres lifetime management. This eliminates the manual iounmap() and release_mem_region() calls in the error path (err3/err4 labels) and in spdif_remove(), simplifying the probe error handling. Signed-off-by: Jihed Chaibi Link: https://patch.msgid.link/20260324223907.98897-1-jihed.chaibi.dev@gmail.com Signed-off-by: Mark Brown --- sound/soc/samsung/spdif.c | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c index 235d0063d1b3..fb30f6b637a0 100644 --- a/sound/soc/samsung/spdif.c +++ b/sound/soc/samsung/spdif.c @@ -407,21 +407,12 @@ static int spdif_probe(struct platform_device *pdev) if (ret) goto err1; - /* Request S/PDIF Register's memory region */ - if (!request_mem_region(mem_res->start, - resource_size(mem_res), "samsung-spdif")) { - dev_err(&pdev->dev, "Unable to request register region\n"); - ret = -EBUSY; + spdif->regs = devm_ioremap_resource(&pdev->dev, mem_res); + if (IS_ERR(spdif->regs)) { + ret = PTR_ERR(spdif->regs); goto err2; } - spdif->regs = ioremap(mem_res->start, 0x100); - if (spdif->regs == NULL) { - dev_err(&pdev->dev, "Cannot ioremap registers\n"); - ret = -ENXIO; - goto err3; - } - spdif_stereo_out.addr_width = 2; spdif_stereo_out.addr = mem_res->start + DATA_OUTBUF; filter = NULL; @@ -435,7 +426,7 @@ static int spdif_probe(struct platform_device *pdev) NULL, NULL, NULL); if (ret) { dev_err(&pdev->dev, "failed to register DMA: %d\n", ret); - goto err4; + goto err2; } dev_set_drvdata(&pdev->dev, spdif); @@ -444,14 +435,10 @@ static int spdif_probe(struct platform_device *pdev) &samsung_spdif_component, &samsung_spdif_dai, 1); if (ret != 0) { dev_err(&pdev->dev, "fail to register dai\n"); - goto err4; + goto err2; } return 0; -err4: - iounmap(spdif->regs); -err3: - release_mem_region(mem_res->start, resource_size(mem_res)); err2: clk_disable_unprepare(spdif->sclk); err1: @@ -463,12 +450,6 @@ err0: static void spdif_remove(struct platform_device *pdev) { struct samsung_spdif_info *spdif = &spdif_info; - struct resource *mem_res; - - iounmap(spdif->regs); - - mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(mem_res->start, resource_size(mem_res)); clk_disable_unprepare(spdif->sclk); clk_disable_unprepare(spdif->pclk); From 9644e7f83d4441eca392c7dacb37bc4b6e412660 Mon Sep 17 00:00:00 2001 From: Jihed Chaibi Date: Mon, 23 Mar 2026 17:15:49 +0100 Subject: [PATCH 223/359] ASoC: jz4725b: Convert to devm_clk_get_enabled() The clock is obtained with devm_clk_get() in the platform probe, then manually enabled in the component probe and disabled in the component remove without checking the return value of clk_prepare_enable(). Use devm_clk_get_enabled() instead, which combines the get, prepare and enable operations into one call whose lifetime is tied to the device. This removes the need for explicit enable/disable in the component probe/remove callbacks, and ensures that clock enable failures are propagated as errors rather than silently ignored. Remove the now-unused struct clk pointer from struct jz_icdc and drop the empty component remove callback. Signed-off-by: Jihed Chaibi Link: https://patch.msgid.link/20260323161551.47181-2-jihed.chaibi.dev@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/jz4725b.c | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/sound/soc/codecs/jz4725b.c b/sound/soc/codecs/jz4725b.c index 39cebaa167be..8a7d26a08c03 100644 --- a/sound/soc/codecs/jz4725b.c +++ b/sound/soc/codecs/jz4725b.c @@ -160,7 +160,6 @@ enum { struct jz_icdc { struct regmap *regmap; void __iomem *base; - struct clk *clk; }; static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(jz4725b_adc_tlv, 0, 150, 0); @@ -405,8 +404,6 @@ static int jz4725b_codec_dev_probe(struct snd_soc_component *component) struct jz_icdc *icdc = snd_soc_component_get_drvdata(component); struct regmap *map = icdc->regmap; - clk_prepare_enable(icdc->clk); - /* Write CONFIGn (n=1 to 8) bits. * The value 0x0f is specified in the datasheet as a requirement. */ @@ -418,16 +415,8 @@ static int jz4725b_codec_dev_probe(struct snd_soc_component *component) return 0; } -static void jz4725b_codec_dev_remove(struct snd_soc_component *component) -{ - struct jz_icdc *icdc = snd_soc_component_get_drvdata(component); - - clk_disable_unprepare(icdc->clk); -} - static const struct snd_soc_component_driver jz4725b_codec = { .probe = jz4725b_codec_dev_probe, - .remove = jz4725b_codec_dev_remove, .set_bias_level = jz4725b_codec_set_bias_level, .controls = jz4725b_codec_controls, .num_controls = ARRAY_SIZE(jz4725b_codec_controls), @@ -618,6 +607,7 @@ static int jz4725b_codec_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct jz_icdc *icdc; + struct clk *clk; int ret; icdc = devm_kzalloc(dev, sizeof(*icdc), GFP_KERNEL); @@ -633,9 +623,9 @@ static int jz4725b_codec_probe(struct platform_device *pdev) if (IS_ERR(icdc->regmap)) return PTR_ERR(icdc->regmap); - icdc->clk = devm_clk_get(&pdev->dev, "aic"); - if (IS_ERR(icdc->clk)) - return PTR_ERR(icdc->clk); + clk = devm_clk_get_enabled(dev, "aic"); + if (IS_ERR(clk)) + return PTR_ERR(clk); platform_set_drvdata(pdev, icdc); From 00df61cbc78ecad5b4bf8552eab5bbf4301bfc0d Mon Sep 17 00:00:00 2001 From: Jihed Chaibi Date: Mon, 23 Mar 2026 17:15:50 +0100 Subject: [PATCH 224/359] ASoC: jz4760: Convert to devm_clk_get_enabled() The clock is obtained with devm_clk_get() in the platform probe, then manually enabled in the component probe and disabled in the component remove without checking the return value of clk_prepare_enable(). Use devm_clk_get_enabled() instead, which combines the get, prepare and enable operations into one call whose lifetime is tied to the device. This removes the need for explicit enable/disable in the component probe/remove callbacks, and ensures that clock enable failures are propagated as errors rather than silently ignored. Remove the now-unused struct clk pointer from struct jz_codec and drop the empty component remove callback. Signed-off-by: Jihed Chaibi Link: https://patch.msgid.link/20260323161551.47181-3-jihed.chaibi.dev@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/jz4760.c | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/sound/soc/codecs/jz4760.c b/sound/soc/codecs/jz4760.c index 344c251be397..6846ace06415 100644 --- a/sound/soc/codecs/jz4760.c +++ b/sound/soc/codecs/jz4760.c @@ -163,7 +163,6 @@ struct jz_codec { struct device *dev; struct regmap *regmap; void __iomem *base; - struct clk *clk; }; static int jz4760_codec_set_bias_level(struct snd_soc_component *codec, @@ -602,25 +601,13 @@ static void jz4760_codec_codec_init_regs(struct snd_soc_component *codec) static int jz4760_codec_codec_probe(struct snd_soc_component *codec) { - struct jz_codec *jz_codec = snd_soc_component_get_drvdata(codec); - - clk_prepare_enable(jz_codec->clk); - jz4760_codec_codec_init_regs(codec); return 0; } -static void jz4760_codec_codec_remove(struct snd_soc_component *codec) -{ - struct jz_codec *jz_codec = snd_soc_component_get_drvdata(codec); - - clk_disable_unprepare(jz_codec->clk); -} - static const struct snd_soc_component_driver jz4760_codec_soc_codec_dev = { .probe = jz4760_codec_codec_probe, - .remove = jz4760_codec_codec_remove, .set_bias_level = jz4760_codec_set_bias_level, .controls = jz4760_codec_snd_controls, .num_controls = ARRAY_SIZE(jz4760_codec_snd_controls), @@ -818,6 +805,7 @@ static int jz4760_codec_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct jz_codec *codec; + struct clk *clk; int ret; codec = devm_kzalloc(dev, sizeof(*codec), GFP_KERNEL); @@ -835,9 +823,9 @@ static int jz4760_codec_probe(struct platform_device *pdev) if (IS_ERR(codec->regmap)) return PTR_ERR(codec->regmap); - codec->clk = devm_clk_get(dev, "aic"); - if (IS_ERR(codec->clk)) - return PTR_ERR(codec->clk); + clk = devm_clk_get_enabled(dev, "aic"); + if (IS_ERR(clk)) + return PTR_ERR(clk); platform_set_drvdata(pdev, codec); From 7dcb79e5c03f2df84f780469a10e92c6a126314f Mon Sep 17 00:00:00 2001 From: Jihed Chaibi Date: Mon, 23 Mar 2026 17:15:51 +0100 Subject: [PATCH 225/359] ASoC: jz4770: Convert to devm_clk_get_enabled() The clock is obtained with devm_clk_get() in the platform probe, then manually enabled in the component probe and disabled in the component remove without checking the return value of clk_prepare_enable(). Use devm_clk_get_enabled() instead, which combines the get, prepare and enable operations into one call whose lifetime is tied to the device. This removes the need for explicit enable/disable in the component probe/remove callbacks, and ensures that clock enable failures are propagated as errors rather than silently ignored. Remove the now-unused struct clk pointer from struct jz_codec and drop the empty component remove callback. Signed-off-by: Jihed Chaibi Link: https://patch.msgid.link/20260323161551.47181-4-jihed.chaibi.dev@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/jz4770.c | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/sound/soc/codecs/jz4770.c b/sound/soc/codecs/jz4770.c index 6b86d47028d7..be1ecdcc737b 100644 --- a/sound/soc/codecs/jz4770.c +++ b/sound/soc/codecs/jz4770.c @@ -179,7 +179,6 @@ struct jz_codec { struct device *dev; struct regmap *regmap; void __iomem *base; - struct clk *clk; }; static int jz4770_codec_set_bias_level(struct snd_soc_component *codec, @@ -634,25 +633,13 @@ static void jz4770_codec_codec_init_regs(struct snd_soc_component *codec) static int jz4770_codec_codec_probe(struct snd_soc_component *codec) { - struct jz_codec *jz_codec = snd_soc_component_get_drvdata(codec); - - clk_prepare_enable(jz_codec->clk); - jz4770_codec_codec_init_regs(codec); return 0; } -static void jz4770_codec_codec_remove(struct snd_soc_component *codec) -{ - struct jz_codec *jz_codec = snd_soc_component_get_drvdata(codec); - - clk_disable_unprepare(jz_codec->clk); -} - static const struct snd_soc_component_driver jz4770_codec_soc_codec_dev = { .probe = jz4770_codec_codec_probe, - .remove = jz4770_codec_codec_remove, .set_bias_level = jz4770_codec_set_bias_level, .controls = jz4770_codec_snd_controls, .num_controls = ARRAY_SIZE(jz4770_codec_snd_controls), @@ -865,6 +852,7 @@ static int jz4770_codec_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct jz_codec *codec; + struct clk *clk; int ret; codec = devm_kzalloc(dev, sizeof(*codec), GFP_KERNEL); @@ -882,9 +870,9 @@ static int jz4770_codec_probe(struct platform_device *pdev) if (IS_ERR(codec->regmap)) return PTR_ERR(codec->regmap); - codec->clk = devm_clk_get(dev, "aic"); - if (IS_ERR(codec->clk)) - return PTR_ERR(codec->clk); + clk = devm_clk_get_enabled(dev, "aic"); + if (IS_ERR(clk)) + return PTR_ERR(clk); platform_set_drvdata(pdev, codec); From aa173b70d3720afabd2ba333838339ca24bc40da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Fri, 27 Mar 2026 14:16:27 -0300 Subject: [PATCH 226/359] ASoC: generic: keep fallback dai_name stable across rebind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit simple_parse_dai() and graph_util_parse_dai() first try to identify a DAI via dai_args. When that works the card can rebind without relying on dlc->dai_name. The fallback path still calls snd_soc_get_dlc(), which returns a borrowed dai_name pointer. If the CPU or codec component is unbound while the sound card stays registered, the generic card keeps that pointer and the next rebind may compare stale memory while matching the DAI. Stage the fallback result in a temporary dai_link_component and move only a card-owned copy of dai_name into the live link component. Use devm_kstrdup_const() so static names are reused and dynamic ones remain valid for the lifetime of the card device. Suggested-by: Kuninori Morimoto Acked-by: Kuninori Morimoto Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260327-asoc-generic-fallback-dai-name-rebind-v3-1-c206e44f40c8@gmail.com Signed-off-by: Mark Brown --- sound/soc/generic/simple-card-utils.c | 54 ++++++++++++--------------- sound/soc/generic/simple-card.c | 46 +++++++++++------------ 2 files changed, 45 insertions(+), 55 deletions(-) diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index da6353594d61..e5cb602fd248 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -1128,7 +1128,9 @@ int graph_util_parse_dai(struct simple_util_priv *priv, struct device_node *ep, struct device *dev = simple_priv_to_dev(priv); struct device_node *node; struct of_phandle_args args = {}; + struct snd_soc_dai_link_component resolved_dlc = {}; struct snd_soc_dai *dai; + const char *fallback_dai_name; int ret; if (!ep) @@ -1152,39 +1154,31 @@ int graph_util_parse_dai(struct simple_util_priv *priv, struct device_node *ep, dlc->of_node = node; dlc->dai_name = dai_name; dlc->dai_args = dai_args; + } else { + /* Get dai->name */ + args.np = node; + args.args[0] = graph_get_dai_id(ep); + args.args_count = (of_graph_get_endpoint_count(node) > 1); - goto parse_dai_end; + ret = snd_soc_get_dlc(&args, &resolved_dlc); + if (ret < 0) + goto err; + + /* Keep fallback dai_name valid across component rebind */ + fallback_dai_name = resolved_dlc.dai_name; + if (fallback_dai_name) { + fallback_dai_name = devm_kstrdup_const(dev, fallback_dai_name, + GFP_KERNEL); + ret = -ENOMEM; + if (!fallback_dai_name) + goto err; + } + + dlc->of_node = resolved_dlc.of_node; + dlc->dai_name = fallback_dai_name; + dlc->dai_args = resolved_dlc.dai_args; } - /* Get dai->name */ - args.np = node; - args.args[0] = graph_get_dai_id(ep); - args.args_count = (of_graph_get_endpoint_count(node) > 1); - - /* - * FIXME - * - * Here, dlc->dai_name is pointer to CPU/Codec DAI name. - * If user unbinded CPU or Codec driver, but not for Sound Card, - * dlc->dai_name is keeping unbinded CPU or Codec - * driver's pointer. - * - * If user re-bind CPU or Codec driver again, ALSA SoC will try - * to rebind Card via snd_soc_try_rebind_card(), but because of - * above reason, it might can't bind Sound Card. - * Because Sound Card is pointing to released dai_name pointer. - * - * To avoid this rebind Card issue, - * 1) It needs to alloc memory to keep dai_name eventhough - * CPU or Codec driver was unbinded, or - * 2) user need to rebind Sound Card everytime - * if he unbinded CPU or Codec. - */ - ret = snd_soc_get_dlc(&args, dlc); - if (ret < 0) - goto err; - -parse_dai_end: if (is_single_link) *is_single_link = of_graph_get_endpoint_count(node) == 1; ret = 0; diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index 9937a991846e..b24ba1330896 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -69,7 +69,9 @@ static int simple_parse_dai(struct simple_util_priv *priv, { struct device *dev = simple_priv_to_dev(priv); struct of_phandle_args args; + struct snd_soc_dai_link_component resolved_dlc = {}; struct snd_soc_dai *dai; + const char *fallback_dai_name; int ret; if (!node) @@ -94,34 +96,28 @@ static int simple_parse_dai(struct simple_util_priv *priv, dlc->dai_args = snd_soc_copy_dai_args(dev, &args); if (!dlc->dai_args) goto end; + } else { + ret = snd_soc_get_dlc(&args, &resolved_dlc); + if (ret < 0) + goto end; - goto parse_dai_end; + /* Keep fallback dai_name valid across component rebind */ + fallback_dai_name = resolved_dlc.dai_name; + if (fallback_dai_name) { + fallback_dai_name = devm_kstrdup_const(dev, fallback_dai_name, + GFP_KERNEL); + ret = -ENOMEM; + if (!fallback_dai_name) { + of_node_put(resolved_dlc.of_node); + goto end; + } + } + + dlc->of_node = resolved_dlc.of_node; + dlc->dai_name = fallback_dai_name; + dlc->dai_args = resolved_dlc.dai_args; } - /* - * FIXME - * - * Here, dlc->dai_name is pointer to CPU/Codec DAI name. - * If user unbinded CPU or Codec driver, but not for Sound Card, - * dlc->dai_name is keeping unbinded CPU or Codec - * driver's pointer. - * - * If user re-bind CPU or Codec driver again, ALSA SoC will try - * to rebind Card via snd_soc_try_rebind_card(), but because of - * above reason, it might can't bind Sound Card. - * Because Sound Card is pointing to released dai_name pointer. - * - * To avoid this rebind Card issue, - * 1) It needs to alloc memory to keep dai_name eventhough - * CPU or Codec driver was unbinded, or - * 2) user need to rebind Sound Card everytime - * if he unbinded CPU or Codec. - */ - ret = snd_soc_get_dlc(&args, dlc); - if (ret < 0) - goto end; - -parse_dai_end: if (is_single_link) *is_single_link = !args.args_count; ret = 0; From d3d75c767680557f0bf8ca58c69ec490e9e248e2 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 25 Mar 2026 19:30:53 -0700 Subject: [PATCH 227/359] ASoC: SOF: topology: use kzalloc_flex Simplify allocation by using a flexible array member. Add __counted_by for extra runtime analysis. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260326023053.53493-1-rosenp@gmail.com Signed-off-by: Mark Brown --- sound/soc/sof/sof-audio.h | 2 +- sound/soc/sof/topology.c | 14 ++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 80b11625915d..138e5fcc2dd0 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -411,11 +411,11 @@ struct snd_sof_dai_link { struct snd_sof_tuple *tuples; int num_tuples; struct snd_soc_dai_link *link; - struct snd_soc_tplg_hw_config *hw_configs; int num_hw_configs; int default_hw_cfg_id; int type; struct list_head list; + struct snd_soc_tplg_hw_config hw_configs[] __counted_by(num_hw_configs); }; /* ASoC SOF DAPM widget */ diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 907f9b1e9d1b..63d582c65891 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1908,18 +1908,12 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ return -EINVAL; } - slink = kzalloc_obj(*slink); + slink = kzalloc_flex(*slink, hw_configs, le32_to_cpu(cfg->num_hw_configs)); if (!slink) return -ENOMEM; slink->num_hw_configs = le32_to_cpu(cfg->num_hw_configs); - slink->hw_configs = kmemdup_array(cfg->hw_config, - slink->num_hw_configs, sizeof(*slink->hw_configs), - GFP_KERNEL); - if (!slink->hw_configs) { - kfree(slink); - return -ENOMEM; - } + memcpy(slink->hw_configs, cfg->hw_config, le32_to_cpu(cfg->num_hw_configs) * sizeof(*slink->hw_configs)); slink->default_hw_cfg_id = le32_to_cpu(cfg->default_hw_config_id); slink->link = link; @@ -1932,7 +1926,6 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ private->array, le32_to_cpu(private->size)); if (ret < 0) { dev_err(scomp->dev, "Failed tp parse common DAI link tokens\n"); - kfree(slink->hw_configs); kfree(slink); return ret; } @@ -2003,7 +1996,6 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ /* allocate memory for tuples array */ slink->tuples = kzalloc_objs(*slink->tuples, num_tuples); if (!slink->tuples) { - kfree(slink->hw_configs); kfree(slink); return -ENOMEM; } @@ -2061,7 +2053,6 @@ out: err: kfree(slink->tuples); - kfree(slink->hw_configs); kfree(slink); return ret; @@ -2078,7 +2069,6 @@ static int sof_link_unload(struct snd_soc_component *scomp, struct snd_soc_dobj kfree(slink->tuples); list_del(&slink->list); - kfree(slink->hw_configs); kfree(slink); dobj->private = NULL; From 5902e1f3c501375797dcd7ca21b58e2c9abbe317 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Mon, 30 Mar 2026 12:50:27 +0530 Subject: [PATCH 228/359] ASoC: amd: acp: update dmic_num logic for acp pdm dmic Currently there is no mechanism to read dmic_num in mach_params structure. In this scenario mach_params->dmic_num check always returns 0 which fails to add component string for dmic. Update the condition check with acp pdm dmic quirk check and pass the dmic_num as 1. Fixes: 2981d9b0789c ("ASoC: amd: acp: add soundwire machine driver for legacy stack") Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260330072431.3512358-2-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-legacy-mach.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c index 6388cd7cb28e..fa9b4b994e41 100644 --- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c +++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c @@ -535,11 +535,11 @@ static int mc_probe(struct platform_device *pdev) " cfg-amp:%d", amp_num); if (!card->components) return -ENOMEM; - if (mach->mach_params.dmic_num) { + if (soc_sdw_quirk & ASOC_SDW_ACP_DMIC) { card->components = devm_kasprintf(card->dev, GFP_KERNEL, "%s mic:dmic cfg-mics:%d", card->components, - mach->mach_params.dmic_num); + 1); if (!card->components) return -ENOMEM; } From 371f6a1d2a972f2d749b9fdff1a453904f43dfce Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Mon, 30 Mar 2026 12:50:28 +0530 Subject: [PATCH 229/359] ASoC: amd: acp-sdw-legacy: rename the dmic component name For acp pdm dmic use case, user space needs a reliable identifier to select the correct UCM configuration. Rename component string as acp-dmic to select the correct UCM configuration for acp pdm dmic. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260330072431.3512358-3-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-legacy-mach.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c index fa9b4b994e41..05484c72f9a8 100644 --- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c +++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c @@ -537,7 +537,7 @@ static int mc_probe(struct platform_device *pdev) return -ENOMEM; if (soc_sdw_quirk & ASOC_SDW_ACP_DMIC) { card->components = devm_kasprintf(card->dev, GFP_KERNEL, - "%s mic:dmic cfg-mics:%d", + "%s mic:acp-dmic cfg-mics:%d", card->components, 1); if (!card->components) From c1258a2924d3a2453a6e7a6581acd8d6e5c6ba70 Mon Sep 17 00:00:00 2001 From: Lei Huang Date: Tue, 31 Mar 2026 10:40:36 +0800 Subject: [PATCH 230/359] ALSA: hda/realtek: fix bad indentation for alc269 Mention complains about this coding style: ERROR: code indent should use tabs where possible #6640: FILE: sound/hda/codecs/realtek/alc269.c:6640: + [ALC233_FIXUP_LENOVO_GPIO2_MIC_HOTKEY] = {$ fix it up. Fixes: 5de5db35350d ("ALSA: hda/realtek - Enable Mute LED for Lenovo platform") Signed-off-by: Lei Huang Link: https://patch.msgid.link/20260331024036.30782-1-huanglei814@163.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 9940fe7d5f9d..cb7b2dd107b5 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6591,10 +6591,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc288_fixup_surface_swap_dacs, }, - [ALC233_FIXUP_LENOVO_GPIO2_MIC_HOTKEY] = { - .type = HDA_FIXUP_FUNC, - .v.func = alc233_fixup_lenovo_gpio2_mic_hotkey, - }, + [ALC233_FIXUP_LENOVO_GPIO2_MIC_HOTKEY] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_fixup_lenovo_gpio2_mic_hotkey, + }, [ALC245_FIXUP_BASS_HP_DAC] = { .type = HDA_FIXUP_FUNC, /* Borrow the DAC routing selected for those Thinkpads */ From d1888bf848ade6a9e71c7ba516fd215aa1bd8d65 Mon Sep 17 00:00:00 2001 From: Lei Huang Date: Tue, 31 Mar 2026 15:54:05 +0800 Subject: [PATCH 231/359] ALSA: hda/realtek: fix code style (ERROR: else should follow close brace '}') Fix checkpatch code style errors: ERROR: else should follow close brace '}' #2300: FILE: sound/hda/codecs/realtek/alc269.c:2300: + } + else Fixes: 31278997add6 ("ALSA: hda/realtek - Add headset quirk for Dell DT") Signed-off-by: Lei Huang Link: https://patch.msgid.link/20260331075405.78148-1-huanglei814@163.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index cb7b2dd107b5..bf837c6480f3 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -2270,9 +2270,9 @@ static void alc_fixup_headset_mode_alc255_no_hp_mic(struct hda_codec *codec, struct alc_spec *spec = codec->spec; spec->parse_flags |= HDA_PINCFG_HEADSET_MIC; alc255_set_default_jack_type(codec); - } - else + } else { alc_fixup_headset_mode(codec, fix, action); + } } static void alc288_update_headset_jack_cb(struct hda_codec *codec, From 9968aad12f362dc686f139e1525d322a768f8ddb Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 31 Mar 2026 01:25:04 +0000 Subject: [PATCH 232/359] ASoC: soc-core: remove duplicate snd_soc_flush_all_delayed_work() snd_soc_unbind_card() calls snd_soc_flush_all_delayed_work() (A), but it will be called in soc_cleanup_card_resources() (B). It is duplicated, let's remove it. (B) static void soc_cleanup_card_resources(...) { ... /* flush delayed work before removing DAIs and DAPM widgets */ (A)' snd_soc_flush_all_delayed_work(card); ... } static void snd_soc_unbind_card(...) { if (snd_soc_card_is_instantiated(card)) { card->instantiated = false; (A) snd_soc_flush_all_delayed_work(card); (B) soc_cleanup_card_resources(card); } } Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87h5pwdc3z.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 67d9ea319c84..071cff39e95a 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2153,8 +2153,6 @@ static void snd_soc_unbind_card(struct snd_soc_card *card) { if (snd_soc_card_is_instantiated(card)) { card->instantiated = false; - snd_soc_flush_all_delayed_work(card); - soc_cleanup_card_resources(card); } } From 6cbc8360f51a3df2ea16a786b262b9fe44d4c68c Mon Sep 17 00:00:00 2001 From: Ethan Tidmore Date: Tue, 24 Mar 2026 12:38:30 -0500 Subject: [PATCH 233/359] ASoC: SOF: Intel: hda: Place check before dereference The struct hext_stream is dereferenced before it is checked for NULL. Although it can never be NULL due to a check prior to hda_dsp_iccmax_stream_hw_params() being called, this change clears any confusion regarding hext_stream possibly being NULL. Check hext_stream for NULL and then assign its members. Detected by Smatch: sound/soc/sof/intel/hda-stream.c:488 hda_dsp_iccmax_stream_hw_params() warn: variable dereferenced before check 'hext_stream' (see line 486) Fixes: aca961f196e5d ("ASoC: SOF: Intel: hda: Add helper function to program ICCMAX stream") Signed-off-by: Ethan Tidmore Link: https://patch.msgid.link/20260324173830.17563-1-ethantidmore06@gmail.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/hda-stream.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c index 1c04b5d9c0d8..5c1f3b427cdb 100644 --- a/sound/soc/sof/intel/hda-stream.c +++ b/sound/soc/sof/intel/hda-stream.c @@ -480,16 +480,20 @@ int hda_dsp_iccmax_stream_hw_params(struct snd_sof_dev *sdev, struct hdac_ext_st struct snd_dma_buffer *dmab, struct snd_pcm_hw_params *params) { - struct hdac_stream *hstream = &hext_stream->hstream; - int sd_offset = SOF_STREAM_SD_OFFSET(hstream); + struct hdac_stream *hstream; + int sd_offset; int ret; - u32 mask = 0x1 << hstream->index; + u32 mask; if (!hext_stream) { dev_err(sdev->dev, "error: no stream available\n"); return -ENODEV; } + hstream = &hext_stream->hstream; + sd_offset = SOF_STREAM_SD_OFFSET(hstream); + mask = 0x1 << hstream->index; + if (!dmab) { dev_err(sdev->dev, "error: no dma buffer allocated!\n"); return -ENODEV; From 5ed060d5491597490fb53ec69da3edc4b1e8c165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Tue, 31 Mar 2026 18:14:04 -0300 Subject: [PATCH 234/359] ALSA: aoa: i2sbus: clear stale prepared state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The i2sbus PCM code uses pi->active to constrain the sibling stream to an already prepared duplex format and rate in i2sbus_pcm_open(). That state is set from i2sbus_pcm_prepare(), but the current code only clears it on close. As a result, the sibling stream can inherit stale constraints after the prepared state has been torn down. Clear pi->active when hw_params() or hw_free() tears down the prepared state, and set it again only after prepare succeeds. Replace the stale FIXME in the duplex constraint comment with a description of the current driver behavior: i2sbus still programs a single shared transport configuration for both directions, so mixed formats are not supported in duplex mode. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202604010125.AvkWBYKI-lkp@intel.com/ Fixes: f3d9478b2ce4 ("[ALSA] snd-aoa: add snd-aoa") Cc: stable@vger.kernel.org Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260331-aoa-i2sbus-clear-stale-active-v2-1-3764ae2889a1@gmail.com Signed-off-by: Takashi Iwai --- sound/aoa/soundbus/i2sbus/pcm.c | 55 ++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/sound/aoa/soundbus/i2sbus/pcm.c b/sound/aoa/soundbus/i2sbus/pcm.c index 97c807e67d56..63004ece94f9 100644 --- a/sound/aoa/soundbus/i2sbus/pcm.c +++ b/sound/aoa/soundbus/i2sbus/pcm.c @@ -165,17 +165,16 @@ static int i2sbus_pcm_open(struct i2sbus_dev *i2sdev, int in) * currently in use (if any). */ hw->rate_min = 5512; hw->rate_max = 192000; - /* if the other stream is active, then we can only - * support what it is currently using. - * FIXME: I lied. This comment is wrong. We can support - * anything that works with the same serial format, ie. - * when recording 24 bit sound we can well play 16 bit - * sound at the same time iff using the same transfer mode. + /* If the other stream is already prepared, keep this stream + * on the same duplex format and rate. + * + * i2sbus_pcm_prepare() still programs one shared transport + * configuration for both directions, so mixed duplex formats + * are not supported here. */ if (other->active) { - /* FIXME: is this guaranteed by the alsa api? */ hw->formats &= pcm_format_to_bits(i2sdev->format); - /* see above, restrict rates to the one we already have */ + /* Restrict rates to the one already in use. */ hw->rate_min = i2sdev->rate; hw->rate_max = i2sdev->rate; } @@ -283,6 +282,23 @@ void i2sbus_wait_for_stop_both(struct i2sbus_dev *i2sdev) } #endif +static void i2sbus_pcm_clear_active(struct i2sbus_dev *i2sdev, int in) +{ + struct pcm_info *pi; + + guard(mutex)(&i2sdev->lock); + + get_pcm_info(i2sdev, in, &pi, NULL); + pi->active = 0; +} + +static inline int i2sbus_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, int in) +{ + i2sbus_pcm_clear_active(snd_pcm_substream_chip(substream), in); + return 0; +} + static inline int i2sbus_hw_free(struct snd_pcm_substream *substream, int in) { struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); @@ -291,14 +307,27 @@ static inline int i2sbus_hw_free(struct snd_pcm_substream *substream, int in) get_pcm_info(i2sdev, in, &pi, NULL); if (pi->dbdma_ring.stopping) i2sbus_wait_for_stop(i2sdev, pi); + i2sbus_pcm_clear_active(i2sdev, in); return 0; } +static int i2sbus_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + return i2sbus_hw_params(substream, params, 0); +} + static int i2sbus_playback_hw_free(struct snd_pcm_substream *substream) { return i2sbus_hw_free(substream, 0); } +static int i2sbus_record_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + return i2sbus_hw_params(substream, params, 1); +} + static int i2sbus_record_hw_free(struct snd_pcm_substream *substream) { return i2sbus_hw_free(substream, 1); @@ -335,7 +364,6 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in) return -EINVAL; runtime = pi->substream->runtime; - pi->active = 1; if (other->active && ((i2sdev->format != runtime->format) || (i2sdev->rate != runtime->rate))) @@ -444,9 +472,11 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in) /* early exit if already programmed correctly */ /* not locking these is fine since we touch them only in this function */ - if (in_le32(&i2sdev->intfregs->serial_format) == sfr - && in_le32(&i2sdev->intfregs->data_word_sizes) == dws) + if (in_le32(&i2sdev->intfregs->serial_format) == sfr && + in_le32(&i2sdev->intfregs->data_word_sizes) == dws) { + pi->active = 1; return 0; + } /* let's notify the codecs about clocks going away. * For now we only do mastering on the i2s cell... */ @@ -484,6 +514,7 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in) if (cii->codec->switch_clock) cii->codec->switch_clock(cii, CLOCK_SWITCH_SLAVE); + pi->active = 1; return 0; } @@ -728,6 +759,7 @@ static snd_pcm_uframes_t i2sbus_playback_pointer(struct snd_pcm_substream static const struct snd_pcm_ops i2sbus_playback_ops = { .open = i2sbus_playback_open, .close = i2sbus_playback_close, + .hw_params = i2sbus_playback_hw_params, .hw_free = i2sbus_playback_hw_free, .prepare = i2sbus_playback_prepare, .trigger = i2sbus_playback_trigger, @@ -796,6 +828,7 @@ static snd_pcm_uframes_t i2sbus_record_pointer(struct snd_pcm_substream static const struct snd_pcm_ops i2sbus_record_ops = { .open = i2sbus_record_open, .close = i2sbus_record_close, + .hw_params = i2sbus_record_hw_params, .hw_free = i2sbus_record_hw_free, .prepare = i2sbus_record_prepare, .trigger = i2sbus_record_trigger, From 579e7b820de5dd5124585413bb5e9c278d255436 Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Wed, 1 Apr 2026 16:26:25 +0800 Subject: [PATCH 235/359] ALSA: hda/cmedia: Remove duplicate pin configuration parsing The cmedia_probe() function calls snd_hda_parse_pin_defcfg() and snd_hda_gen_parse_auto_config() twice unnecessarily. Remove The duplicate code. Fixes: 0f1e8306dcbe ("ALSA: hda/cmedia: Rewrite to new probe method") Signed-off-by: wangdicheng Link: https://patch.msgid.link/20260401082625.157868-1-wangdich9700@163.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/cmedia.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/sound/hda/codecs/cmedia.c b/sound/hda/codecs/cmedia.c index e6e12c01339f..88dd80d987d4 100644 --- a/sound/hda/codecs/cmedia.c +++ b/sound/hda/codecs/cmedia.c @@ -39,13 +39,6 @@ static int cmedia_probe(struct hda_codec *codec, const struct hda_device_id *id) spec->out_vol_mask = (1ULL << 0x10); } - err = snd_hda_parse_pin_defcfg(codec, cfg, NULL, 0); - if (err < 0) - goto error; - err = snd_hda_gen_parse_auto_config(codec, cfg); - if (err < 0) - goto error; - err = snd_hda_parse_pin_defcfg(codec, cfg, NULL, 0); if (err < 0) goto error; From 5a77906982df26975aa26caefb81b7d6f53d9b3f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 1 Apr 2026 00:19:06 +0000 Subject: [PATCH 236/359] ASoC: soc.h: remove snd_soc_of_parse_audio_prefix() No one is using snd_soc_of_parse_audio_prefix(). Remove it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/877bqrttvp.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index d66164fd83e5..f70edd9c23b2 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1337,15 +1337,6 @@ void snd_soc_of_parse_node_prefix(struct device_node *np, struct snd_soc_codec_conf *codec_conf, struct device_node *of_node, const char *propname); -static inline -void snd_soc_of_parse_audio_prefix(struct snd_soc_card *card, - struct snd_soc_codec_conf *codec_conf, - struct device_node *of_node, - const char *propname) -{ - snd_soc_of_parse_node_prefix(card->dev->of_node, - codec_conf, of_node, propname); -} int snd_soc_of_parse_audio_routing(struct snd_soc_card *card, const char *propname); From 9b4f93a8b1a5d058ff42000a226fa3a237078425 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 1 Apr 2026 00:19:23 +0000 Subject: [PATCH 237/359] ASoC: ak5558: remove unused snd_soc_component ak5558_priv::component has never been used. Remove it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/875x6bttv8.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/ak5558.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/codecs/ak5558.c b/sound/soc/codecs/ak5558.c index 73684fc5beb1..691db6565c20 100644 --- a/sound/soc/codecs/ak5558.c +++ b/sound/soc/codecs/ak5558.c @@ -38,7 +38,6 @@ static const char *ak5558_supply_names[AK5558_NUM_SUPPLIES] = { /* AK5558 Codec Private Data */ struct ak5558_priv { struct regulator_bulk_data supplies[AK5558_NUM_SUPPLIES]; - struct snd_soc_component component; struct regmap *regmap; struct i2c_client *i2c; struct gpio_desc *reset_gpiod; /* Reset & Power down GPIO */ From c6cd83cceec5f2a1e2dd319d98640b1f0007d668 Mon Sep 17 00:00:00 2001 From: Harin Lee Date: Wed, 1 Apr 2026 18:01:57 +0900 Subject: [PATCH 238/359] ALSA: ctxfi: Rename SPDIFI1 to SPDIFI_BAY Rename the SPDIFI1 enum value to SPDIFI_BAY to better reflect its purpose as the S/PDIF input on the internal drive bay, as opposed to the S/PDIF input via Flexijack or optical (SPDIFIO; not SPDIFI-zero). Signed-off-by: Harin Lee Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260401090159.2404387-2-me@harin.net --- sound/pci/ctxfi/ctatc.c | 4 ++-- sound/pci/ctxfi/ctdaio.c | 6 +++--- sound/pci/ctxfi/ctdaio.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/pci/ctxfi/ctatc.c b/sound/pci/ctxfi/ctatc.c index da2667cb2489..9e0532fb33ff 100644 --- a/sound/pci/ctxfi/ctatc.c +++ b/sound/pci/ctxfi/ctatc.c @@ -1429,10 +1429,10 @@ static int atc_get_resources(struct ct_atc *atc) for (i = 0; i < NUM_DAIOTYP; i++) { if (((i == MIC) && !cap.dedicated_mic) || ((i == RCA) && !cap.dedicated_rca) || - i == SPDIFI1) + i == SPDIFI_BAY) continue; if (atc->model == CTSB073X && i == SPDIFIO) - da_desc.type = SPDIFI1; + da_desc.type = SPDIFI_BAY; else da_desc.type = i; da_desc.output = (i < LINEIM) || (i == RCA); diff --git a/sound/pci/ctxfi/ctdaio.c b/sound/pci/ctxfi/ctdaio.c index 4dbb1dd7af32..128cf2f69ac1 100644 --- a/sound/pci/ctxfi/ctdaio.c +++ b/sound/pci/ctxfi/ctdaio.c @@ -35,7 +35,7 @@ static const struct daio_rsc_idx idx_20k1[NUM_DAIOTYP] = { [LINEIM] = {.left = 0x1b5, .right = 0x1bd}, [SPDIFOO] = {.left = 0x20, .right = 0x21}, [SPDIFIO] = {.left = 0x15, .right = 0x1d}, - [SPDIFI1] = {.left = 0x95, .right = 0x9d}, + [SPDIFI_BAY] = {.left = 0x95, .right = 0x9d}, }; static const struct daio_rsc_idx idx_20k2[NUM_DAIOTYP] = { @@ -106,7 +106,7 @@ static int daio_device_index(enum DAIOTYP type, struct hw *hw) switch (type) { case SPDIFOO: return 0; case SPDIFIO: return 0; - case SPDIFI1: return 1; + case SPDIFI_BAY: return 1; case LINEO1: return 4; case LINEO2: return 7; case LINEO3: return 5; @@ -120,7 +120,7 @@ static int daio_device_index(enum DAIOTYP type, struct hw *hw) switch (type) { case SPDIFOO: return 0; case SPDIFIO: return 0; - case SPDIFI1: return 1; + case SPDIFI_BAY: return 1; case LINEO1: return 4; case LINEO2: return 7; case LINEO3: return 5; diff --git a/sound/pci/ctxfi/ctdaio.h b/sound/pci/ctxfi/ctdaio.h index ff77d55539a5..c9f6207fe92f 100644 --- a/sound/pci/ctxfi/ctdaio.h +++ b/sound/pci/ctxfi/ctdaio.h @@ -32,7 +32,7 @@ enum DAIOTYP { SPDIFIO, /* S/PDIF In (Flexijack/Optical) on the card */ MIC, /* Dedicated mic on Titanium HD */ RCA, /* Dedicated RCA on SE-300PCIE */ - SPDIFI1, /* S/PDIF In on internal Drive Bay */ + SPDIFI_BAY, /* S/PDIF In on internal drive bay */ NUM_DAIOTYP }; From 07b116b44e52d78af40c2d39a8e1e34ef1283d0d Mon Sep 17 00:00:00 2001 From: Harin Lee Date: Wed, 1 Apr 2026 18:01:58 +0900 Subject: [PATCH 239/359] ALSA: ctxfi: Use correct DAIO type for da_desc Skip the unused DAIO type per model (SPDIFIO on CTSB073X, SPDIFI_BAY on all others) and use the correct DAIO type directly as da_desc type. This removes the mismatch and misleading between the actual DAIO resource and the da_desc type like SPDIFI_BAY (formerly SPDIFI1). Update related functions accordingly, and drop the unreachable SPDIFI_BAY case from the hw20k2 daio_device_index(). Signed-off-by: Harin Lee Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260401090159.2404387-3-me@harin.net --- sound/pci/ctxfi/ctatc.c | 21 ++++++++++++--------- sound/pci/ctxfi/ctdaio.c | 1 - 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/sound/pci/ctxfi/ctatc.c b/sound/pci/ctxfi/ctatc.c index 9e0532fb33ff..7c2f896d531d 100644 --- a/sound/pci/ctxfi/ctatc.c +++ b/sound/pci/ctxfi/ctatc.c @@ -983,6 +983,11 @@ static int atc_select_mic_in(struct ct_atc *atc) return 0; } +static inline enum DAIOTYP atc_spdif_in_type(struct ct_atc *atc) +{ + return (atc->model == CTSB073X) ? SPDIFI_BAY : SPDIFIO; +} + static struct capabilities atc_capabilities(struct ct_atc *atc) { struct hw *hw = atc->hw; @@ -1121,7 +1126,7 @@ static int atc_spdif_out_unmute(struct ct_atc *atc, unsigned char state) static int atc_spdif_in_unmute(struct ct_atc *atc, unsigned char state) { - return atc_daio_unmute(atc, state, SPDIFIO); + return atc_daio_unmute(atc, state, atc_spdif_in_type(atc)); } static int atc_spdif_out_get_status(struct ct_atc *atc, unsigned int *status) @@ -1427,14 +1432,12 @@ static int atc_get_resources(struct ct_atc *atc) daio_mgr = (struct daio_mgr *)atc->rsc_mgrs[DAIO]; da_desc.msr = atc->msr; for (i = 0; i < NUM_DAIOTYP; i++) { - if (((i == MIC) && !cap.dedicated_mic) || - ((i == RCA) && !cap.dedicated_rca) || - i == SPDIFI_BAY) + if (((i == SPDIFIO) && (atc->model == CTSB073X)) || + ((i == SPDIFI_BAY) && (atc->model != CTSB073X)) || + ((i == MIC) && !cap.dedicated_mic) || + ((i == RCA) && !cap.dedicated_rca)) continue; - if (atc->model == CTSB073X && i == SPDIFIO) - da_desc.type = SPDIFI_BAY; - else - da_desc.type = i; + da_desc.type = i; da_desc.output = (i < LINEIM) || (i == RCA); err = daio_mgr->get_daio(daio_mgr, &da_desc, (struct daio **)&atc->daios[i]); @@ -1569,7 +1572,7 @@ static void atc_connect_resources(struct ct_atc *atc) mixer->set_input_right(mixer, MIX_MIC_IN, &src->rsc); } - dai = container_of(atc->daios[SPDIFIO], struct dai, daio); + dai = container_of(atc->daios[atc_spdif_in_type(atc)], struct dai, daio); atc_connect_dai(atc->rsc_mgrs[SRC], dai, (struct src **)&atc->srcs[0], (struct srcimp **)&atc->srcimps[0]); diff --git a/sound/pci/ctxfi/ctdaio.c b/sound/pci/ctxfi/ctdaio.c index 128cf2f69ac1..69aacd06716c 100644 --- a/sound/pci/ctxfi/ctdaio.c +++ b/sound/pci/ctxfi/ctdaio.c @@ -120,7 +120,6 @@ static int daio_device_index(enum DAIOTYP type, struct hw *hw) switch (type) { case SPDIFOO: return 0; case SPDIFIO: return 0; - case SPDIFI_BAY: return 1; case LINEO1: return 4; case LINEO2: return 7; case LINEO3: return 5; From 80449e1966cb9df57617a1d22bccd1e29cbc4222 Mon Sep 17 00:00:00 2001 From: Harin Lee Date: Wed, 1 Apr 2026 18:01:59 +0900 Subject: [PATCH 240/359] ALSA: ctxfi: Precompute SRC allocation loop bound Replace the capability checks in the SRC and SRCIMP allocation loops with a precomputed loop bound. Cards with a dedicated mic input (SB1270, OK0010) allocate all NUM_ATC_SRCS entries, otherwise stop at 4. Signed-off-by: Harin Lee Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260401090159.2404387-4-me@harin.net --- sound/pci/ctxfi/ctatc.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sound/pci/ctxfi/ctatc.c b/sound/pci/ctxfi/ctatc.c index 7c2f896d531d..516c0a12ed9f 100644 --- a/sound/pci/ctxfi/ctatc.c +++ b/sound/pci/ctxfi/ctatc.c @@ -1409,9 +1409,11 @@ static int atc_get_resources(struct ct_atc *atc) struct sum_desc sum_dsc = {0}; struct sum_mgr *sum_mgr; struct capabilities cap; + int atc_srcs_limit; int err, i; cap = atc->capabilities(atc); + atc_srcs_limit = cap.dedicated_mic ? NUM_ATC_SRCS : 4; atc->daios = kcalloc(NUM_DAIOTYP, sizeof(void *), GFP_KERNEL); if (!atc->daios) @@ -1453,9 +1455,7 @@ static int atc_get_resources(struct ct_atc *atc) src_dsc.multi = 1; src_dsc.msr = atc->msr; src_dsc.mode = ARCRW; - for (i = 0; i < NUM_ATC_SRCS; i++) { - if (((i > 3) && !cap.dedicated_mic)) - continue; + for (i = 0; i < atc_srcs_limit; i++) { err = src_mgr->get_src(src_mgr, &src_dsc, (struct src **)&atc->srcs[i]); if (err) @@ -1464,9 +1464,7 @@ static int atc_get_resources(struct ct_atc *atc) srcimp_mgr = atc->rsc_mgrs[SRCIMP]; srcimp_dsc.msr = 8; - for (i = 0; i < NUM_ATC_SRCS; i++) { - if (((i > 3) && !cap.dedicated_mic)) - continue; + for (i = 0; i < atc_srcs_limit; i++) { err = srcimp_mgr->get_srcimp(srcimp_mgr, &srcimp_dsc, (struct srcimp **)&atc->srcimps[i]); if (err) From 872c7433582a3570dd0c827967ba291450096bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Wed, 1 Apr 2026 08:45:37 -0300 Subject: [PATCH 241/359] ALSA: es1688: add ISA suspend and resume callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ISA ES1688 driver still carries a disabled suspend/resume block in its isa_driver definition, while the same file already provides minimal power-management handling for the PnP ES968 path. Add ISA-specific PM callbacks and factor the existing ES1688 suspend and resume sequence into common card-level helpers shared by both probe paths. Suspend moves the card to D3hot. Resume reinitializes the chip with snd_es1688_reset() and restores the card to D0, propagating reset failures to the caller. This wires up power-management callbacks for the ISA path and keeps the PM handling consistent between the ISA and PnP probe paths. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260401-alsa-es1688-pm-v1-1-510767628fe6@gmail.com Signed-off-by: Takashi Iwai --- sound/isa/es1688/es1688.c | 50 +++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/sound/isa/es1688/es1688.c b/sound/isa/es1688/es1688.c index 6a95dfb7600a..7255b34f9148 100644 --- a/sound/isa/es1688/es1688.c +++ b/sound/isa/es1688/es1688.c @@ -184,12 +184,44 @@ static int snd_es1688_isa_probe(struct device *dev, unsigned int n) return 0; } +#ifdef CONFIG_PM +static int snd_es1688_card_suspend(struct snd_card *card) +{ + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + return 0; +} + +static int snd_es1688_card_resume(struct snd_card *card) +{ + struct snd_es1688 *chip = card->private_data; + int err; + + err = snd_es1688_reset(chip); + if (err < 0) + return err; + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static int snd_es1688_isa_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + return snd_es1688_card_suspend(dev_get_drvdata(dev)); +} + +static int snd_es1688_isa_resume(struct device *dev, unsigned int n) +{ + return snd_es1688_card_resume(dev_get_drvdata(dev)); +} +#endif + static struct isa_driver snd_es1688_driver = { .match = snd_es1688_match, .probe = snd_es1688_isa_probe, -#if 0 /* FIXME */ - .suspend = snd_es1688_suspend, - .resume = snd_es1688_resume, +#ifdef CONFIG_PM + .suspend = snd_es1688_isa_suspend, + .resume = snd_es1688_isa_resume, #endif .driver = { .name = DEV_NAME @@ -266,20 +298,12 @@ static void snd_es968_pnp_remove(struct pnp_card_link *pcard) static int snd_es968_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state) { - struct snd_card *card = pnp_get_card_drvdata(pcard); - - snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); - return 0; + return snd_es1688_card_suspend(pnp_get_card_drvdata(pcard)); } static int snd_es968_pnp_resume(struct pnp_card_link *pcard) { - struct snd_card *card = pnp_get_card_drvdata(pcard); - struct snd_es1688 *chip = card->private_data; - - snd_es1688_reset(chip); - snd_power_change_state(card, SNDRV_CTL_POWER_D0); - return 0; + return snd_es1688_card_resume(pnp_get_card_drvdata(pcard)); } #endif From c8ef13d692f19cdbbf195fb845421a5b71801704 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Wed, 1 Apr 2026 13:30:51 +0800 Subject: [PATCH 242/359] ASoC: mxs-sgtl5000: disable MCLK on error paths of mxs_sgtl5000_probe() Call mxs_saif_put_mclk() to disable MCLK on error paths of mxs_sgtl5000_probe(). Signed-off-by: Haoxiang Li Link: https://patch.msgid.link/20260401053051.586290-1-lihaoxiang@isrc.iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/mxs/mxs-sgtl5000.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sound/soc/mxs/mxs-sgtl5000.c b/sound/soc/mxs/mxs-sgtl5000.c index 245f17411638..f1c0e612313d 100644 --- a/sound/soc/mxs/mxs-sgtl5000.c +++ b/sound/soc/mxs/mxs-sgtl5000.c @@ -157,13 +157,16 @@ static int mxs_sgtl5000_probe(struct platform_device *pdev) if (ret) { dev_err(&pdev->dev, "failed to parse audio-routing (%d)\n", ret); + mxs_saif_put_mclk(0); return ret; } } ret = devm_snd_soc_register_card(&pdev->dev, card); - if (ret) + if (ret) { + mxs_saif_put_mclk(0); return dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed\n"); + } return 0; } From 00d6070ec02c55731c7655b3227edd8b84bbcdbc Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 1 Apr 2026 11:24:57 +0000 Subject: [PATCH 243/359] ASoC: tegra: Add error logging in tegra210_admaif driver Log errors in the Tegra210 ADMAIF probe and runtime callback paths. Drop redundant dev_err() at tegra_isomgr_adma_register() call site since it already logs errors internally. Signed-off-by: Sheetal Link: https://patch.msgid.link/20260401112500.4076861-2-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_admaif.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/sound/soc/tegra/tegra210_admaif.c b/sound/soc/tegra/tegra210_admaif.c index 0976779d29f2..b6a3db8fce08 100644 --- a/sound/soc/tegra/tegra210_admaif.c +++ b/sound/soc/tegra/tegra210_admaif.c @@ -408,6 +408,7 @@ static int tegra_admaif_start(struct snd_soc_dai *dai, int direction) reg = CH_RX_REG(TEGRA_ADMAIF_RX_ENABLE, dai->id); break; default: + dev_err(dai->dev, "invalid stream direction: %d\n", direction); return -EINVAL; } @@ -441,6 +442,7 @@ static int tegra_admaif_stop(struct snd_soc_dai *dai, int direction) reset_reg = CH_RX_REG(TEGRA_ADMAIF_RX_SOFT_RESET, dai->id); break; default: + dev_err(dai->dev, "invalid stream direction: %d\n", direction); return -EINVAL; } @@ -489,6 +491,7 @@ static int tegra_admaif_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_SUSPEND: return tegra_admaif_stop(dai, substream->stream); default: + dev_err(dai->dev, "invalid trigger command: %d\n", cmd); return -EINVAL; } } @@ -958,18 +961,15 @@ static int tegra_admaif_probe(struct platform_device *pdev) admaif->regmap = devm_regmap_init_mmio(&pdev->dev, regs, admaif->soc_data->regmap_conf); - if (IS_ERR(admaif->regmap)) { - dev_err(&pdev->dev, "regmap init failed\n"); - return PTR_ERR(admaif->regmap); - } + if (IS_ERR(admaif->regmap)) + return dev_err_probe(&pdev->dev, PTR_ERR(admaif->regmap), + "regmap init failed\n"); regcache_cache_only(admaif->regmap, true); err = tegra_isomgr_adma_register(&pdev->dev); - if (err) { - dev_err(&pdev->dev, "Failed to add interconnect path\n"); + if (err) return err; - } regmap_update_bits(admaif->regmap, admaif->soc_data->global_base + TEGRA_ADMAIF_GLOBAL_ENABLE, 1, 1); @@ -1009,11 +1009,9 @@ static int tegra_admaif_probe(struct platform_device *pdev) admaif->soc_data->cmpnt, admaif->soc_data->dais, admaif->soc_data->num_ch); - if (err) { - dev_err(&pdev->dev, - "can't register ADMAIF component, err: %d\n", err); - return err; - } + if (err) + return dev_err_probe(&pdev->dev, err, + "can't register ADMAIF component\n"); pm_runtime_enable(&pdev->dev); From 2333abb25c7e2ba823d2113b83a22be4c2ba63c1 Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 1 Apr 2026 11:24:58 +0000 Subject: [PATCH 244/359] ASoC: tegra: Use dev_err_probe() in OPE, PEQ and MBDRC drivers Log errors in the Tegra210 OPE, PEQ and MBDRC probe paths using dev_err_probe(). Drop redundant dev_err() at tegra210_peq_regmap_init() and tegra210_mbdrc_regmap_init() call sites in ope_probe() since these functions already log errors internally. Signed-off-by: Sheetal Link: https://patch.msgid.link/20260401112500.4076861-3-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_mbdrc.c | 17 ++++++++--------- sound/soc/tegra/tegra210_ope.c | 22 ++++++++-------------- sound/soc/tegra/tegra210_peq.c | 17 ++++++++--------- 3 files changed, 24 insertions(+), 32 deletions(-) diff --git a/sound/soc/tegra/tegra210_mbdrc.c b/sound/soc/tegra/tegra210_mbdrc.c index 6a268dbb7197..2e4f50b470a8 100644 --- a/sound/soc/tegra/tegra210_mbdrc.c +++ b/sound/soc/tegra/tegra210_mbdrc.c @@ -988,14 +988,14 @@ int tegra210_mbdrc_regmap_init(struct platform_device *pdev) child = of_get_child_by_name(dev->of_node, "dynamic-range-compressor"); if (!child) - return -ENODEV; + return dev_err_probe(dev, -ENODEV, + "missing 'dynamic-range-compressor' DT child node\n"); err = of_address_to_resource(child, 0, &mem); of_node_put(child); - if (err < 0) { - dev_err(dev, "fail to get MBDRC resource\n"); - return err; - } + if (err < 0) + return dev_err_probe(dev, err, + "failed to get MBDRC resource\n"); mem.flags = IORESOURCE_MEM; regs = devm_ioremap_resource(dev, &mem); @@ -1004,10 +1004,9 @@ int tegra210_mbdrc_regmap_init(struct platform_device *pdev) ope->mbdrc_regmap = devm_regmap_init_mmio(dev, regs, &tegra210_mbdrc_regmap_cfg); - if (IS_ERR(ope->mbdrc_regmap)) { - dev_err(dev, "regmap init failed\n"); - return PTR_ERR(ope->mbdrc_regmap); - } + if (IS_ERR(ope->mbdrc_regmap)) + return dev_err_probe(dev, PTR_ERR(ope->mbdrc_regmap), + "MBDRC regmap init failed\n"); regcache_cache_only(ope->mbdrc_regmap, true); diff --git a/sound/soc/tegra/tegra210_ope.c b/sound/soc/tegra/tegra210_ope.c index a440888dcdbd..ad4c400281e8 100644 --- a/sound/soc/tegra/tegra210_ope.c +++ b/sound/soc/tegra/tegra210_ope.c @@ -318,34 +318,28 @@ static int tegra210_ope_probe(struct platform_device *pdev) ope->regmap = devm_regmap_init_mmio(dev, regs, &tegra210_ope_regmap_config); - if (IS_ERR(ope->regmap)) { - dev_err(dev, "regmap init failed\n"); - return PTR_ERR(ope->regmap); - } + if (IS_ERR(ope->regmap)) + return dev_err_probe(dev, PTR_ERR(ope->regmap), + "regmap init failed\n"); regcache_cache_only(ope->regmap, true); dev_set_drvdata(dev, ope); err = tegra210_peq_regmap_init(pdev); - if (err < 0) { - dev_err(dev, "PEQ init failed\n"); + if (err < 0) return err; - } err = tegra210_mbdrc_regmap_init(pdev); - if (err < 0) { - dev_err(dev, "MBDRC init failed\n"); + if (err < 0) return err; - } err = devm_snd_soc_register_component(dev, &tegra210_ope_cmpnt, tegra210_ope_dais, ARRAY_SIZE(tegra210_ope_dais)); - if (err) { - dev_err(dev, "can't register OPE component, err: %d\n", err); - return err; - } + if (err) + return dev_err_probe(dev, err, + "can't register OPE component\n"); pm_runtime_enable(dev); diff --git a/sound/soc/tegra/tegra210_peq.c b/sound/soc/tegra/tegra210_peq.c index 2f72e9d541dc..f7f98dfdd1c3 100644 --- a/sound/soc/tegra/tegra210_peq.c +++ b/sound/soc/tegra/tegra210_peq.c @@ -408,14 +408,14 @@ int tegra210_peq_regmap_init(struct platform_device *pdev) child = of_get_child_by_name(dev->of_node, "equalizer"); if (!child) - return -ENODEV; + return dev_err_probe(dev, -ENODEV, + "missing 'equalizer' DT child node\n"); err = of_address_to_resource(child, 0, &mem); of_node_put(child); - if (err < 0) { - dev_err(dev, "fail to get PEQ resource\n"); - return err; - } + if (err < 0) + return dev_err_probe(dev, err, + "failed to get PEQ resource\n"); mem.flags = IORESOURCE_MEM; regs = devm_ioremap_resource(dev, &mem); @@ -423,10 +423,9 @@ int tegra210_peq_regmap_init(struct platform_device *pdev) return PTR_ERR(regs); ope->peq_regmap = devm_regmap_init_mmio(dev, regs, &tegra210_peq_regmap_config); - if (IS_ERR(ope->peq_regmap)) { - dev_err(dev, "regmap init failed\n"); - return PTR_ERR(ope->peq_regmap); - } + if (IS_ERR(ope->peq_regmap)) + return dev_err_probe(dev, PTR_ERR(ope->peq_regmap), + "PEQ regmap init failed\n"); regcache_cache_only(ope->peq_regmap, true); From 4a8fe27e122e5a1bb1896837ed47f99abaa965a0 Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 1 Apr 2026 11:24:59 +0000 Subject: [PATCH 245/359] ASoC: tegra: Use dev_err_probe() for regmap init failures Use dev_err_probe() for regmap init failures in Tegra audio driver probe paths. Signed-off-by: Sheetal Link: https://patch.msgid.link/20260401112500.4076861-4-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra186_asrc.c | 7 +++---- sound/soc/tegra/tegra186_dspk.c | 7 +++---- sound/soc/tegra/tegra210_adx.c | 7 +++---- sound/soc/tegra/tegra210_ahub.c | 7 +++---- sound/soc/tegra/tegra210_amx.c | 7 +++---- sound/soc/tegra/tegra210_dmic.c | 7 +++---- sound/soc/tegra/tegra210_i2s.c | 7 +++---- sound/soc/tegra/tegra210_mixer.c | 7 +++---- sound/soc/tegra/tegra210_mvc.c | 7 +++---- sound/soc/tegra/tegra210_sfc.c | 7 +++---- 10 files changed, 30 insertions(+), 40 deletions(-) diff --git a/sound/soc/tegra/tegra186_asrc.c b/sound/soc/tegra/tegra186_asrc.c index d2a5ec7c54cc..503836338da7 100644 --- a/sound/soc/tegra/tegra186_asrc.c +++ b/sound/soc/tegra/tegra186_asrc.c @@ -989,10 +989,9 @@ static int tegra186_asrc_platform_probe(struct platform_device *pdev) asrc->regmap = devm_regmap_init_mmio(dev, regs, &tegra186_asrc_regmap_config); - if (IS_ERR(asrc->regmap)) { - dev_err(dev, "regmap init failed\n"); - return PTR_ERR(asrc->regmap); - } + if (IS_ERR(asrc->regmap)) + return dev_err_probe(dev, PTR_ERR(asrc->regmap), + "regmap init failed\n"); asrc->soc_data = of_device_get_match_data(&pdev->dev); diff --git a/sound/soc/tegra/tegra186_dspk.c b/sound/soc/tegra/tegra186_dspk.c index 8816e4967331..66199f8359bb 100644 --- a/sound/soc/tegra/tegra186_dspk.c +++ b/sound/soc/tegra/tegra186_dspk.c @@ -506,10 +506,9 @@ static int tegra186_dspk_platform_probe(struct platform_device *pdev) return PTR_ERR(regs); dspk->regmap = devm_regmap_init_mmio(dev, regs, &tegra186_dspk_regmap); - if (IS_ERR(dspk->regmap)) { - dev_err(dev, "regmap init failed\n"); - return PTR_ERR(dspk->regmap); - } + if (IS_ERR(dspk->regmap)) + return dev_err_probe(dev, PTR_ERR(dspk->regmap), + "regmap init failed\n"); regcache_cache_only(dspk->regmap, true); diff --git a/sound/soc/tegra/tegra210_adx.c b/sound/soc/tegra/tegra210_adx.c index 95875c75ddf8..53d938af3f3c 100644 --- a/sound/soc/tegra/tegra210_adx.c +++ b/sound/soc/tegra/tegra210_adx.c @@ -693,10 +693,9 @@ static int tegra210_adx_platform_probe(struct platform_device *pdev) adx->regmap = devm_regmap_init_mmio(dev, regs, soc_data->regmap_conf); - if (IS_ERR(adx->regmap)) { - dev_err(dev, "regmap init failed\n"); - return PTR_ERR(adx->regmap); - } + if (IS_ERR(adx->regmap)) + return dev_err_probe(dev, PTR_ERR(adx->regmap), + "regmap init failed\n"); regcache_cache_only(adx->regmap, true); diff --git a/sound/soc/tegra/tegra210_ahub.c b/sound/soc/tegra/tegra210_ahub.c index 43a45f785d5b..e96933e5650b 100644 --- a/sound/soc/tegra/tegra210_ahub.c +++ b/sound/soc/tegra/tegra210_ahub.c @@ -2276,10 +2276,9 @@ static int tegra_ahub_probe(struct platform_device *pdev) ahub->regmap = devm_regmap_init_mmio(&pdev->dev, regs, ahub->soc_data->regmap_config); - if (IS_ERR(ahub->regmap)) { - dev_err(&pdev->dev, "regmap init failed\n"); - return PTR_ERR(ahub->regmap); - } + if (IS_ERR(ahub->regmap)) + return dev_err_probe(&pdev->dev, PTR_ERR(ahub->regmap), + "regmap init failed\n"); regcache_cache_only(ahub->regmap, true); diff --git a/sound/soc/tegra/tegra210_amx.c b/sound/soc/tegra/tegra210_amx.c index bfda82505298..8e86079426db 100644 --- a/sound/soc/tegra/tegra210_amx.c +++ b/sound/soc/tegra/tegra210_amx.c @@ -743,10 +743,9 @@ static int tegra210_amx_platform_probe(struct platform_device *pdev) amx->regmap = devm_regmap_init_mmio(dev, regs, amx->soc_data->regmap_conf); - if (IS_ERR(amx->regmap)) { - dev_err(dev, "regmap init failed\n"); - return PTR_ERR(amx->regmap); - } + if (IS_ERR(amx->regmap)) + return dev_err_probe(dev, PTR_ERR(amx->regmap), + "regmap init failed\n"); regcache_cache_only(amx->regmap, true); diff --git a/sound/soc/tegra/tegra210_dmic.c b/sound/soc/tegra/tegra210_dmic.c index 93def7ac4fde..1ab71d34139f 100644 --- a/sound/soc/tegra/tegra210_dmic.c +++ b/sound/soc/tegra/tegra210_dmic.c @@ -518,10 +518,9 @@ static int tegra210_dmic_probe(struct platform_device *pdev) dmic->regmap = devm_regmap_init_mmio(dev, regs, &tegra210_dmic_regmap_config); - if (IS_ERR(dmic->regmap)) { - dev_err(dev, "regmap init failed\n"); - return PTR_ERR(dmic->regmap); - } + if (IS_ERR(dmic->regmap)) + return dev_err_probe(dev, PTR_ERR(dmic->regmap), + "regmap init failed\n"); regcache_cache_only(dmic->regmap, true); diff --git a/sound/soc/tegra/tegra210_i2s.c b/sound/soc/tegra/tegra210_i2s.c index d8e02f0a3025..630d9415f73e 100644 --- a/sound/soc/tegra/tegra210_i2s.c +++ b/sound/soc/tegra/tegra210_i2s.c @@ -1090,10 +1090,9 @@ static int tegra210_i2s_probe(struct platform_device *pdev) i2s->regmap = devm_regmap_init_mmio(dev, regs, i2s->soc_data->regmap_conf); - if (IS_ERR(i2s->regmap)) { - dev_err(dev, "regmap init failed\n"); - return PTR_ERR(i2s->regmap); - } + if (IS_ERR(i2s->regmap)) + return dev_err_probe(dev, PTR_ERR(i2s->regmap), + "regmap init failed\n"); tegra210_parse_client_convert(dev); diff --git a/sound/soc/tegra/tegra210_mixer.c b/sound/soc/tegra/tegra210_mixer.c index 6d3a2b76fd61..85a22b86d808 100644 --- a/sound/soc/tegra/tegra210_mixer.c +++ b/sound/soc/tegra/tegra210_mixer.c @@ -641,10 +641,9 @@ static int tegra210_mixer_platform_probe(struct platform_device *pdev) mixer->regmap = devm_regmap_init_mmio(dev, regs, &tegra210_mixer_regmap_config); - if (IS_ERR(mixer->regmap)) { - dev_err(dev, "regmap init failed\n"); - return PTR_ERR(mixer->regmap); - } + if (IS_ERR(mixer->regmap)) + return dev_err_probe(dev, PTR_ERR(mixer->regmap), + "regmap init failed\n"); regcache_cache_only(mixer->regmap, true); diff --git a/sound/soc/tegra/tegra210_mvc.c b/sound/soc/tegra/tegra210_mvc.c index 6cdc5e1f5507..13cab787adc2 100644 --- a/sound/soc/tegra/tegra210_mvc.c +++ b/sound/soc/tegra/tegra210_mvc.c @@ -731,10 +731,9 @@ static int tegra210_mvc_platform_probe(struct platform_device *pdev) mvc->regmap = devm_regmap_init_mmio(dev, regs, &tegra210_mvc_regmap_config); - if (IS_ERR(mvc->regmap)) { - dev_err(dev, "regmap init failed\n"); - return PTR_ERR(mvc->regmap); - } + if (IS_ERR(mvc->regmap)) + return dev_err_probe(dev, PTR_ERR(mvc->regmap), + "regmap init failed\n"); regcache_cache_only(mvc->regmap, true); diff --git a/sound/soc/tegra/tegra210_sfc.c b/sound/soc/tegra/tegra210_sfc.c index b298bf0421b1..7afde8af0da0 100644 --- a/sound/soc/tegra/tegra210_sfc.c +++ b/sound/soc/tegra/tegra210_sfc.c @@ -3598,10 +3598,9 @@ static int tegra210_sfc_platform_probe(struct platform_device *pdev) sfc->regmap = devm_regmap_init_mmio(dev, regs, &tegra210_sfc_regmap_config); - if (IS_ERR(sfc->regmap)) { - dev_err(dev, "regmap init failed\n"); - return PTR_ERR(sfc->regmap); - } + if (IS_ERR(sfc->regmap)) + return dev_err_probe(dev, PTR_ERR(sfc->regmap), + "regmap init failed\n"); regcache_cache_only(sfc->regmap, true); From b015ef4eaeff76466a359b3f53175fc11502fd8f Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 1 Apr 2026 11:25:00 +0000 Subject: [PATCH 246/359] ASoC: tegra: Use dev_err_probe() in tegra_asoc_machine probe Use dev_err_probe() for clock errors in the tegra_asoc_machine probe path. Signed-off-by: Sheetal Link: https://patch.msgid.link/20260401112500.4076861-5-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra_asoc_machine.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/sound/soc/tegra/tegra_asoc_machine.c b/sound/soc/tegra/tegra_asoc_machine.c index d48463ac16fc..d9a1b35c25d1 100644 --- a/sound/soc/tegra/tegra_asoc_machine.c +++ b/sound/soc/tegra/tegra_asoc_machine.c @@ -591,16 +591,14 @@ int tegra_asoc_machine_probe(struct platform_device *pdev) * host controller and the external codec */ err = clk_set_rate(machine->clk_pll_a, 73728000); - if (err) { - dev_err(dev, "Can't set pll_a rate: %d\n", err); - return err; - } + if (err) + return dev_err_probe(dev, err, + "can't set pll_a rate\n"); err = clk_set_rate(machine->clk_pll_a_out0, 24576000); - if (err) { - dev_err(dev, "Can't set pll_a_out0 rate: %d\n", err); - return err; - } + if (err) + return dev_err_probe(dev, err, + "can't set pll_a_out0 rate\n"); machine->set_baseclock = 73728000; machine->set_mclk = 24576000; @@ -612,10 +610,9 @@ int tegra_asoc_machine_probe(struct platform_device *pdev) * only needed for audio. */ err = clk_prepare_enable(machine->clk_cdev1); - if (err) { - dev_err(dev, "Can't enable cdev1: %d\n", err); - return err; - } + if (err) + return dev_err_probe(dev, err, + "can't enable cdev1\n"); err = devm_snd_soc_register_card(dev, card); if (err) From c7661bfc7422443df394c01e069ae4e5c3a7f04c Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 1 Apr 2026 17:42:16 +0800 Subject: [PATCH 247/359] ASoC: fsl_micfil: Add access property for "VAD Detected" Add access property SNDRV_CTL_ELEM_ACCESS_READ for control "VAD Detected", which doesn't support put operation, otherwise there will be issue with mixer-test. Fixes: 29dbfeecab85 ("ASoC: fsl_micfil: Add Hardware Voice Activity Detector support") Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260401094226.2900532-2-shengjiu.wang@nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_micfil.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sound/soc/fsl/fsl_micfil.c b/sound/soc/fsl/fsl_micfil.c index d6cde2757c6d..79850211742c 100644 --- a/sound/soc/fsl/fsl_micfil.c +++ b/sound/soc/fsl/fsl_micfil.c @@ -503,7 +503,13 @@ static const struct snd_kcontrol_new fsl_micfil_snd_controls[] = { SOC_SINGLE("HWVAD ZCD Adjustment", REG_MICFIL_VAD0_ZCD, 8, 15, 0), SOC_SINGLE("HWVAD ZCD And Behavior Switch", REG_MICFIL_VAD0_ZCD, 4, 1, 0), - SOC_SINGLE_BOOL_EXT("VAD Detected", 0, hwvad_detected, NULL), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .name = "VAD Detected", + .info = snd_soc_info_bool_ext, + .get = hwvad_detected, + }, }; static int fsl_micfil_use_verid(struct device *dev) From 59b9061824f2179fe133e2636203548eaba3e528 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 1 Apr 2026 17:42:17 +0800 Subject: [PATCH 248/359] ASoC: fsl_micfil: Fix event generation in hwvad_put_enable() ALSA controls should return 1 if the value in the control changed but the control put operation hwvad_put_enable() only returns 0 or a negative error code, causing ALSA to not generate any change events. Add a suitable check in the function before updating the vad_enabled variable. Fixes: 29dbfeecab85 ("ASoC: fsl_micfil: Add Hardware Voice Activity Detector support") Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260401094226.2900532-3-shengjiu.wang@nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_micfil.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sound/soc/fsl/fsl_micfil.c b/sound/soc/fsl/fsl_micfil.c index 79850211742c..97f24c9bdd68 100644 --- a/sound/soc/fsl/fsl_micfil.c +++ b/sound/soc/fsl/fsl_micfil.c @@ -377,10 +377,15 @@ static int hwvad_put_enable(struct snd_kcontrol *kcontrol, unsigned int *item = ucontrol->value.enumerated.item; struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); int val = snd_soc_enum_item_to_val(e, item[0]); + bool change = false; + if (val < 0 || val > 1) + return -EINVAL; + + change = (micfil->vad_enabled != val); micfil->vad_enabled = val; - return 0; + return change; } static int hwvad_get_enable(struct snd_kcontrol *kcontrol, From 7e226209906906421f0d952d7304e48fdb0adabc Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 1 Apr 2026 17:42:18 +0800 Subject: [PATCH 249/359] ASoC: fsl_micfil: Fix event generation in hwvad_put_init_mode() ALSA controls should return 1 if the value in the control changed but the control put operation hwvad_put_init_mode() only returns 0 or a negative error code, causing ALSA to not generate any change events. Add a suitable check in the function before updating the vad_init_mode variable. Fixes: 29dbfeecab85 ("ASoC: fsl_micfil: Add Hardware Voice Activity Detector support") Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260401094226.2900532-4-shengjiu.wang@nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_micfil.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sound/soc/fsl/fsl_micfil.c b/sound/soc/fsl/fsl_micfil.c index 97f24c9bdd68..1c826e0cb1d5 100644 --- a/sound/soc/fsl/fsl_micfil.c +++ b/sound/soc/fsl/fsl_micfil.c @@ -407,13 +407,18 @@ static int hwvad_put_init_mode(struct snd_kcontrol *kcontrol, unsigned int *item = ucontrol->value.enumerated.item; struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); int val = snd_soc_enum_item_to_val(e, item[0]); + bool change = false; + + if (val < MICFIL_HWVAD_ENVELOPE_MODE || val > MICFIL_HWVAD_ENERGY_MODE) + return -EINVAL; /* 0 - Envelope-based Mode * 1 - Energy-based Mode */ + change = (micfil->vad_init_mode != val); micfil->vad_init_mode = val; - return 0; + return change; } static int hwvad_get_init_mode(struct snd_kcontrol *kcontrol, From fc4daaddb276d370b7da3819872044df446a1911 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 1 Apr 2026 17:42:19 +0800 Subject: [PATCH 250/359] ASoC: fsl_micfil: Fix event generation in micfil_range_set() ALSA controls should return 1 if the value in the control changed but the control put operation micfil_range_set() only returns 0 or a negative error code, causing ALSA to not generate any change events. Use snd_soc_component_update_bits() function to replace the regmap_update_bits(), for snd_soc_component_update_bits() has the capability of return check status. Also enable pm runtime before calling the function snd_soc_component_update_bits() to make the regmap cache data align with the value in hardware. Fixes: ef1a7e02fdb7 ("ASoC: fsl_micfil: Set channel range control") Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260401094226.2900532-5-shengjiu.wang@nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_micfil.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sound/soc/fsl/fsl_micfil.c b/sound/soc/fsl/fsl_micfil.c index 1c826e0cb1d5..0cfdd6343291 100644 --- a/sound/soc/fsl/fsl_micfil.c +++ b/sound/soc/fsl/fsl_micfil.c @@ -210,15 +210,23 @@ static int micfil_range_set(struct snd_kcontrol *kcontrol, (struct soc_mixer_control *)kcontrol->private_value; unsigned int shift = mc->shift; int max_range, new_range; + int ret; new_range = ucontrol->value.integer.value[0]; max_range = micfil_get_max_range(micfil); if (new_range > max_range) dev_warn(&micfil->pdev->dev, "range makes channel %d data unreliable\n", shift / 4); - regmap_update_bits(micfil->regmap, REG_MICFIL_OUT_CTRL, 0xF << shift, new_range << shift); + ret = pm_runtime_resume_and_get(cmpnt->dev); + if (ret) + return ret; - return 0; + ret = snd_soc_component_update_bits(cmpnt, REG_MICFIL_OUT_CTRL, 0xF << shift, + new_range << shift); + + pm_runtime_put_autosuspend(cmpnt->dev); + + return ret; } static int micfil_set_quality(struct fsl_micfil *micfil) From 7d2bd35100de370dc326b250e8f6b66bee06a2f3 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 1 Apr 2026 17:42:20 +0800 Subject: [PATCH 251/359] ASoC: fsl_micfil: Fix event generation in micfil_put_dc_remover_state() ALSA controls should return 1 if the value in the control changed but the control put operation micfil_put_dc_remover_state() only returns 0 or a negative error code, causing ALSA to not generate any change events. return the value of snd_soc_component_update_bits() directly, as it has the capability of return check status of changed or not. Also enable pm runtime before calling the function snd_soc_component_update_bits() to make the regmap cache data align with the value in hardware. Fixes: 29dbfeecab85 ("ASoC: fsl_micfil: Add Hardware Voice Activity Detector support") Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260401094226.2900532-6-shengjiu.wang@nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_micfil.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sound/soc/fsl/fsl_micfil.c b/sound/soc/fsl/fsl_micfil.c index 0cfdd6343291..983805bbaae2 100644 --- a/sound/soc/fsl/fsl_micfil.c +++ b/sound/soc/fsl/fsl_micfil.c @@ -351,6 +351,10 @@ static int micfil_put_dc_remover_state(struct snd_kcontrol *kcontrol, if (val < 0 || val > 3) return -EINVAL; + ret = pm_runtime_resume_and_get(comp->dev); + if (ret) + return ret; + micfil->dc_remover = val; /* Calculate total value for all channels */ @@ -360,10 +364,10 @@ static int micfil_put_dc_remover_state(struct snd_kcontrol *kcontrol, /* Update DC Remover mode for all channels */ ret = snd_soc_component_update_bits(comp, REG_MICFIL_DC_CTRL, MICFIL_DC_CTRL_CONFIG, reg_val); - if (ret < 0) - return ret; - return 0; + pm_runtime_put_autosuspend(comp->dev); + + return ret; } static int micfil_get_dc_remover_state(struct snd_kcontrol *kcontrol, From e5785093b1b45af7ee57d18619b2854a8aed073a Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 1 Apr 2026 17:42:21 +0800 Subject: [PATCH 252/359] ASoC: fsl_micfil: Fix event generation in micfil_quality_set() ALSA controls should return 1 if the value in the control changed but the control put operation micfil_quality_set() only returns 0 or a negative error code, causing ALSA to not generate any change events. Add a suitable check in the function before updating the quality variable. Also enable pm runtime before calling the function micfil_set_quality() to make the regmap cache data align with the value in hardware. Fixes: bea1d61d5892 ("ASoC: fsl_micfil: rework quality setting") Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260401094226.2900532-7-shengjiu.wang@nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_micfil.c | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/sound/soc/fsl/fsl_micfil.c b/sound/soc/fsl/fsl_micfil.c index 983805bbaae2..2e887f1f1f36 100644 --- a/sound/soc/fsl/fsl_micfil.c +++ b/sound/soc/fsl/fsl_micfil.c @@ -289,10 +289,34 @@ static int micfil_quality_set(struct snd_kcontrol *kcontrol, { struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol); struct fsl_micfil *micfil = snd_soc_component_get_drvdata(cmpnt); + int val = ucontrol->value.integer.value[0]; + bool change = false; + int old_val; + int ret; - micfil->quality = ucontrol->value.integer.value[0]; + if (val < QUALITY_HIGH || val > QUALITY_VLOW2) + return -EINVAL; - return micfil_set_quality(micfil); + if (micfil->quality != val) { + ret = pm_runtime_resume_and_get(cmpnt->dev); + if (ret) + return ret; + + old_val = micfil->quality; + micfil->quality = val; + ret = micfil_set_quality(micfil); + + pm_runtime_put_autosuspend(cmpnt->dev); + + if (ret) { + micfil->quality = old_val; + return ret; + } + + change = true; + } + + return change; } static const char * const micfil_hwvad_enable[] = { From 1b61c8103c9317a9c37fe544c2d83cee1c281149 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 1 Apr 2026 17:42:22 +0800 Subject: [PATCH 253/359] ASoC: fsl_xcvr: Fix event generation in fsl_xcvr_arc_mode_put() ALSA controls should return 1 if the value in the control changed but the control put operation fsl_xcvr_arc_mode_put() only returns 0 or a negative error code, causing ALSA to not generate any change events. Add a suitable check in the function before updating the arc_mode variable. Fixes: 28564486866f ("ASoC: fsl_xcvr: Add XCVR ASoC CPU DAI driver") Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260401094226.2900532-8-shengjiu.wang@nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_xcvr.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c index a268fb81a2f8..008e45009c83 100644 --- a/sound/soc/fsl/fsl_xcvr.c +++ b/sound/soc/fsl/fsl_xcvr.c @@ -115,10 +115,17 @@ static int fsl_xcvr_arc_mode_put(struct snd_kcontrol *kcontrol, struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int *item = ucontrol->value.enumerated.item; + int val = snd_soc_enum_item_to_val(e, item[0]); + int ret; - xcvr->arc_mode = snd_soc_enum_item_to_val(e, item[0]); + if (val < 0 || val > 1) + return -EINVAL; - return 0; + ret = (xcvr->arc_mode != val); + + xcvr->arc_mode = val; + + return ret; } static int fsl_xcvr_arc_mode_get(struct snd_kcontrol *kcontrol, From 64a496ba976324615b845d60739dfcdae3d57434 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 1 Apr 2026 17:42:23 +0800 Subject: [PATCH 254/359] ASoC: fsl_xcvr: Fix event generation in fsl_xcvr_mode_put() ALSA controls should return 1 if the value in the control changed but the control put operation fsl_xcvr_mode_put() only returns 0 or a negative error code, causing ALSA to not generate any change events. Add a suitable check in the function before updating the mode variable. Fixes: 28564486866f ("ASoC: fsl_xcvr: Add XCVR ASoC CPU DAI driver") Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260401094226.2900532-9-shengjiu.wang@nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_xcvr.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c index 008e45009c83..d7a823384c08 100644 --- a/sound/soc/fsl/fsl_xcvr.c +++ b/sound/soc/fsl/fsl_xcvr.c @@ -225,10 +225,17 @@ static int fsl_xcvr_mode_put(struct snd_kcontrol *kcontrol, struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int *item = ucontrol->value.enumerated.item; + int val = snd_soc_enum_item_to_val(e, item[0]); struct snd_soc_card *card = dai->component->card; struct snd_soc_pcm_runtime *rtd; + int ret; - xcvr->mode = snd_soc_enum_item_to_val(e, item[0]); + if (val < FSL_XCVR_MODE_SPDIF || val > FSL_XCVR_MODE_EARC) + return -EINVAL; + + ret = (xcvr->mode != val); + + xcvr->mode = val; fsl_xcvr_activate_ctl(dai, fsl_xcvr_arc_mode_kctl.name, (xcvr->mode == FSL_XCVR_MODE_ARC)); @@ -238,7 +245,7 @@ static int fsl_xcvr_mode_put(struct snd_kcontrol *kcontrol, rtd = snd_soc_get_pcm_runtime(card, card->dai_link); rtd->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count = (xcvr->mode == FSL_XCVR_MODE_SPDIF ? 1 : 0); - return 0; + return ret; } static int fsl_xcvr_mode_get(struct snd_kcontrol *kcontrol, From 00541b86fb578d4949cfdd6aff1f82d43fcf07af Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 1 Apr 2026 17:42:24 +0800 Subject: [PATCH 255/359] ASoC: fsl_easrc: Check the variable range in fsl_easrc_iec958_put_bits() Add check of input value's range in fsl_easrc_iec958_put_bits(), otherwise the wrong value may be written from user space. Fixes: 955ac624058f ("ASoC: fsl_easrc: Add EASRC ASoC CPU DAI drivers") Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260401094226.2900532-10-shengjiu.wang@nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_easrc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c index 6c56134c60cc..6de1e1d3d8dc 100644 --- a/sound/soc/fsl/fsl_easrc.c +++ b/sound/soc/fsl/fsl_easrc.c @@ -54,6 +54,9 @@ static int fsl_easrc_iec958_put_bits(struct snd_kcontrol *kcontrol, unsigned int regval = ucontrol->value.integer.value[0]; int ret; + if (regval < EASRC_WIDTH_16_BIT || regval > EASRC_WIDTH_24_BIT) + return -EINVAL; + ret = (easrc_priv->bps_iec958[mc->regbase] != regval); easrc_priv->bps_iec958[mc->regbase] = regval; From aa21fe4a81458cf469c2615b08cbde5997dde25a Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 1 Apr 2026 17:42:25 +0800 Subject: [PATCH 256/359] ASoC: fsl_easrc: Fix value type in fsl_easrc_iec958_get_bits() The value type of controls "Context 0 IEC958 Bits Per Sample" should be integer, not enumerated, the issue is found by the mixer-test. Fixes: 955ac624058f ("ASoC: fsl_easrc: Add EASRC ASoC CPU DAI drivers") Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260401094226.2900532-11-shengjiu.wang@nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_easrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c index 6de1e1d3d8dc..fbd1ba12aad0 100644 --- a/sound/soc/fsl/fsl_easrc.c +++ b/sound/soc/fsl/fsl_easrc.c @@ -73,7 +73,7 @@ static int fsl_easrc_iec958_get_bits(struct snd_kcontrol *kcontrol, struct soc_mreg_control *mc = (struct soc_mreg_control *)kcontrol->private_value; - ucontrol->value.enumerated.item[0] = easrc_priv->bps_iec958[mc->regbase]; + ucontrol->value.integer.value[0] = easrc_priv->bps_iec958[mc->regbase]; return 0; } From 47f28a5bd154a95d5aa563dde02a801bd32ddb81 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 1 Apr 2026 17:42:26 +0800 Subject: [PATCH 257/359] ASoC: fsl_easrc: Change the type for iec958 channel status controls Use the type SNDRV_CTL_ELEM_TYPE_IEC958 for iec958 channel status controls, the original type will cause mixer-test to iterate all 32bit values, which costs a lot of time. And using IEC958 type can reduce the control numbers. Also enable pm runtime before updating registers to make the regmap cache data align with the value in hardware. Fixes: 955ac624058f ("ASoC: fsl_easrc: Add EASRC ASoC CPU DAI drivers") Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260401094226.2900532-12-shengjiu.wang@nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_easrc.c | 118 +++++++++++++++++++++++++++----------- 1 file changed, 84 insertions(+), 34 deletions(-) diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c index fbd1ba12aad0..41985162df0b 100644 --- a/sound/soc/fsl/fsl_easrc.c +++ b/sound/soc/fsl/fsl_easrc.c @@ -78,17 +78,47 @@ static int fsl_easrc_iec958_get_bits(struct snd_kcontrol *kcontrol, return 0; } +static int fsl_easrc_iec958_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + static int fsl_easrc_get_reg(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct soc_mreg_control *mc = (struct soc_mreg_control *)kcontrol->private_value; - unsigned int regval; + struct fsl_asrc *easrc = snd_soc_component_get_drvdata(component); + unsigned int *regval = (unsigned int *)ucontrol->value.iec958.status; + int ret; - regval = snd_soc_component_read(component, mc->regbase); + ret = regmap_read(easrc->regmap, REG_EASRC_CS0(mc->regbase), ®val[0]); + if (ret) + return ret; - ucontrol->value.integer.value[0] = regval; + ret = regmap_read(easrc->regmap, REG_EASRC_CS1(mc->regbase), ®val[1]); + if (ret) + return ret; + + ret = regmap_read(easrc->regmap, REG_EASRC_CS2(mc->regbase), ®val[2]); + if (ret) + return ret; + + ret = regmap_read(easrc->regmap, REG_EASRC_CS3(mc->regbase), ®val[3]); + if (ret) + return ret; + + ret = regmap_read(easrc->regmap, REG_EASRC_CS4(mc->regbase), ®val[4]); + if (ret) + return ret; + + ret = regmap_read(easrc->regmap, REG_EASRC_CS5(mc->regbase), ®val[5]); + if (ret) + return ret; return 0; } @@ -100,22 +130,62 @@ static int fsl_easrc_set_reg(struct snd_kcontrol *kcontrol, struct soc_mreg_control *mc = (struct soc_mreg_control *)kcontrol->private_value; struct fsl_asrc *easrc = snd_soc_component_get_drvdata(component); - unsigned int regval = ucontrol->value.integer.value[0]; - bool changed; + unsigned int *regval = (unsigned int *)ucontrol->value.iec958.status; + bool changed, changed_all = false; int ret; - ret = regmap_update_bits_check(easrc->regmap, mc->regbase, - GENMASK(31, 0), regval, &changed); - if (ret != 0) + ret = pm_runtime_resume_and_get(component->dev); + if (ret) return ret; - return changed; + ret = regmap_update_bits_check(easrc->regmap, REG_EASRC_CS0(mc->regbase), + GENMASK(31, 0), regval[0], &changed); + if (ret != 0) + goto err; + changed_all |= changed; + + ret = regmap_update_bits_check(easrc->regmap, REG_EASRC_CS1(mc->regbase), + GENMASK(31, 0), regval[1], &changed); + if (ret != 0) + goto err; + changed_all |= changed; + + ret = regmap_update_bits_check(easrc->regmap, REG_EASRC_CS2(mc->regbase), + GENMASK(31, 0), regval[2], &changed); + if (ret != 0) + goto err; + changed_all |= changed; + + ret = regmap_update_bits_check(easrc->regmap, REG_EASRC_CS3(mc->regbase), + GENMASK(31, 0), regval[3], &changed); + if (ret != 0) + goto err; + changed_all |= changed; + + ret = regmap_update_bits_check(easrc->regmap, REG_EASRC_CS4(mc->regbase), + GENMASK(31, 0), regval[4], &changed); + if (ret != 0) + goto err; + changed_all |= changed; + + ret = regmap_update_bits_check(easrc->regmap, REG_EASRC_CS5(mc->regbase), + GENMASK(31, 0), regval[5], &changed); + if (ret != 0) + goto err; + changed_all |= changed; +err: + pm_runtime_put_autosuspend(component->dev); + + if (ret != 0) + return ret; + else + return changed_all; } #define SOC_SINGLE_REG_RW(xname, xreg) \ { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = (xname), \ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ - .info = snd_soc_info_xr_sx, .get = fsl_easrc_get_reg, \ + .info = fsl_easrc_iec958_info, .get = fsl_easrc_get_reg, \ .put = fsl_easrc_set_reg, \ .private_value = (unsigned long)&(struct soc_mreg_control) \ { .regbase = xreg, .regcount = 1, .nbits = 32, \ @@ -146,30 +216,10 @@ static const struct snd_kcontrol_new fsl_easrc_snd_controls[] = { SOC_SINGLE_VAL_RW("Context 2 IEC958 Bits Per Sample", 2), SOC_SINGLE_VAL_RW("Context 3 IEC958 Bits Per Sample", 3), - SOC_SINGLE_REG_RW("Context 0 IEC958 CS0", REG_EASRC_CS0(0)), - SOC_SINGLE_REG_RW("Context 1 IEC958 CS0", REG_EASRC_CS0(1)), - SOC_SINGLE_REG_RW("Context 2 IEC958 CS0", REG_EASRC_CS0(2)), - SOC_SINGLE_REG_RW("Context 3 IEC958 CS0", REG_EASRC_CS0(3)), - SOC_SINGLE_REG_RW("Context 0 IEC958 CS1", REG_EASRC_CS1(0)), - SOC_SINGLE_REG_RW("Context 1 IEC958 CS1", REG_EASRC_CS1(1)), - SOC_SINGLE_REG_RW("Context 2 IEC958 CS1", REG_EASRC_CS1(2)), - SOC_SINGLE_REG_RW("Context 3 IEC958 CS1", REG_EASRC_CS1(3)), - SOC_SINGLE_REG_RW("Context 0 IEC958 CS2", REG_EASRC_CS2(0)), - SOC_SINGLE_REG_RW("Context 1 IEC958 CS2", REG_EASRC_CS2(1)), - SOC_SINGLE_REG_RW("Context 2 IEC958 CS2", REG_EASRC_CS2(2)), - SOC_SINGLE_REG_RW("Context 3 IEC958 CS2", REG_EASRC_CS2(3)), - SOC_SINGLE_REG_RW("Context 0 IEC958 CS3", REG_EASRC_CS3(0)), - SOC_SINGLE_REG_RW("Context 1 IEC958 CS3", REG_EASRC_CS3(1)), - SOC_SINGLE_REG_RW("Context 2 IEC958 CS3", REG_EASRC_CS3(2)), - SOC_SINGLE_REG_RW("Context 3 IEC958 CS3", REG_EASRC_CS3(3)), - SOC_SINGLE_REG_RW("Context 0 IEC958 CS4", REG_EASRC_CS4(0)), - SOC_SINGLE_REG_RW("Context 1 IEC958 CS4", REG_EASRC_CS4(1)), - SOC_SINGLE_REG_RW("Context 2 IEC958 CS4", REG_EASRC_CS4(2)), - SOC_SINGLE_REG_RW("Context 3 IEC958 CS4", REG_EASRC_CS4(3)), - SOC_SINGLE_REG_RW("Context 0 IEC958 CS5", REG_EASRC_CS5(0)), - SOC_SINGLE_REG_RW("Context 1 IEC958 CS5", REG_EASRC_CS5(1)), - SOC_SINGLE_REG_RW("Context 2 IEC958 CS5", REG_EASRC_CS5(2)), - SOC_SINGLE_REG_RW("Context 3 IEC958 CS5", REG_EASRC_CS5(3)), + SOC_SINGLE_REG_RW("Context 0 IEC958 CS", 0), + SOC_SINGLE_REG_RW("Context 1 IEC958 CS", 1), + SOC_SINGLE_REG_RW("Context 2 IEC958 CS", 2), + SOC_SINGLE_REG_RW("Context 3 IEC958 CS", 3), }; /* From cf6c18cf83e48986ac40a053d09d3c33624135f6 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 1 Apr 2026 17:57:34 +0100 Subject: [PATCH 258/359] ALSA: compress: Refuse to update timestamps for unconfigured streams There are a number of mechanisms, including the userspace accessible timestamp and buffer availability ioctl()s, which allow us to trigger a timestamp update on a stream before it has been configured. Since drivers might rely on stream configuration for reporting of pcm_io_frames, including potentially doing a division by the number of channels, and these operations are not meaningful for an unconfigured stream reject attempts to read timestamps before any configuration is done. Signed-off-by: Mark Brown Acked-by: Vinod Koul Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260401-alsa-unconfigured-tstamp-v1-1-694c2cb5f71d@kernel.org --- sound/core/compress_offload.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index 5a0308eb4e31..db9f516df842 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -185,6 +185,14 @@ static int snd_compr_update_tstamp(struct snd_compr_stream *stream, { if (!stream->ops->pointer) return -ENOTSUPP; + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + return -EBADFD; + default: + break; + } + stream->ops->pointer(stream, tstamp); pr_debug("dsp consumed till %u total %llu bytes\n", tstamp->byte_offset, tstamp->copied_total); From 61327f3d817cb5820559ad4f8d0d9abed3d379b1 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 1 Apr 2026 17:57:35 +0100 Subject: [PATCH 259/359] ALSA: compress: Pay attention if drivers error out retrieving pointers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently we have a return code on the driver pointer operation but the core ignores that. Let's start paying attention. Reported-by: Péter Ujfalusi Signed-off-by: Mark Brown Acked-by: Vinod Koul Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260401-alsa-unconfigured-tstamp-v1-2-694c2cb5f71d@kernel.org --- sound/core/compress_offload.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index db9f516df842..fd63d219bf86 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -183,6 +183,8 @@ snd_compr_tstamp32_from_64(struct snd_compr_tstamp *tstamp32, static int snd_compr_update_tstamp(struct snd_compr_stream *stream, struct snd_compr_tstamp64 *tstamp) { + int ret; + if (!stream->ops->pointer) return -ENOTSUPP; @@ -193,7 +195,9 @@ static int snd_compr_update_tstamp(struct snd_compr_stream *stream, break; } - stream->ops->pointer(stream, tstamp); + ret = stream->ops->pointer(stream, tstamp); + if (ret != 0) + return ret; pr_debug("dsp consumed till %u total %llu bytes\n", tstamp->byte_offset, tstamp->copied_total); if (stream->direction == SND_COMPRESS_PLAYBACK) From 1558905669e4da922fbaa7cf6507eb14779bffbd Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Thu, 2 Apr 2026 10:36:04 +0800 Subject: [PATCH 260/359] ALSA: aoa/tas: Fix OF node leak on probe failure Add missing of_node_put() in the error path. Signed-off-by: wangdicheng Link: https://patch.msgid.link/20260402023604.54682-1-wangdich9700@163.com Signed-off-by: Takashi Iwai --- sound/aoa/codecs/tas.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/aoa/codecs/tas.c b/sound/aoa/codecs/tas.c index 13da2b159ad0..25214d3da65d 100644 --- a/sound/aoa/codecs/tas.c +++ b/sound/aoa/codecs/tas.c @@ -872,6 +872,7 @@ static int tas_i2c_probe(struct i2c_client *client) return 0; fail: mutex_destroy(&tas->mtx); + of_node_put(tas->codec.node); kfree(tas); return -EINVAL; } From 4513d3e0bbc0585b86ccf2631902593ff97e88f5 Mon Sep 17 00:00:00 2001 From: Cryolitia PukNgae Date: Thu, 2 Apr 2026 13:36:57 +0800 Subject: [PATCH 261/359] ALSA: usb-audio: apply quirk for MOONDROP JU Jiu It(ID 31b2:0111 JU Jiu) reports a MIN value -12800 for volume control, but will mute when setting it less than -10880. Thanks to my girlfriend Kagura for reporting this issue. Cc: Kagura Cc: stable@vger.kernel.org Signed-off-by: Cryolitia PukNgae Link: https://patch.msgid.link/20260402-syy-v1-1-068d3bc30ddc@linux.dev Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 69026cf54979..a25e8145af67 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1204,6 +1204,13 @@ static void volume_control_quirks(struct usb_mixer_elem_info *cval, cval->min = -11264; /* Mute under it */ } break; + case USB_ID(0x31b2, 0x0111): /* MOONDROP JU Jiu */ + if (!strcmp(kctl->id.name, "PCM Playback Volume")) { + usb_audio_info(chip, + "set volume quirk for MOONDROP JU Jiu\n"); + cval->min = -10880; /* Mute under it */ + } + break; } } From 6ec1235fc941dac6c011b30ee01d9220ff87e0cd Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 2 Apr 2026 08:11:06 +0000 Subject: [PATCH 262/359] ASoC: qcom: q6apm: move component registration to unmanaged version q6apm component registers dais dynamically from ASoC toplology, which are allocated using device managed version apis. Allocating both component and dynamic dais using managed version could lead to incorrect free ordering, dai will be freed while component still holding references to it. Fix this issue by moving component to unmanged version so that the dai pointers are only freeded after the component is removed. ================================================================== BUG: KASAN: slab-use-after-free in snd_soc_del_component_unlocked+0x3d4/0x400 [snd_soc_core] Read of size 8 at addr ffff00084493a6e8 by task kworker/u48:0/3426 Tainted: [W]=WARN Hardware name: LENOVO 21N2ZC5PUS/21N2ZC5PUS, BIOS N42ET57W (1.31 ) 08/08/2024 Workqueue: pdr_notifier_wq pdr_notifier_work [pdr_interface] Call trace: show_stack+0x28/0x7c (C) dump_stack_lvl+0x60/0x80 print_report+0x160/0x4b4 kasan_report+0xac/0xfc __asan_report_load8_noabort+0x20/0x34 snd_soc_del_component_unlocked+0x3d4/0x400 [snd_soc_core] snd_soc_unregister_component_by_driver+0x50/0x88 [snd_soc_core] devm_component_release+0x30/0x5c [snd_soc_core] devres_release_all+0x13c/0x210 device_unbind_cleanup+0x20/0x190 device_release_driver_internal+0x350/0x468 device_release_driver+0x18/0x30 bus_remove_device+0x1a0/0x35c device_del+0x314/0x7f0 device_unregister+0x20/0xbc apr_remove_device+0x5c/0x7c [apr] device_for_each_child+0xd8/0x160 apr_pd_status+0x7c/0xa8 [apr] pdr_notifier_work+0x114/0x240 [pdr_interface] process_one_work+0x500/0xb70 worker_thread+0x630/0xfb0 kthread+0x370/0x6c0 ret_from_fork+0x10/0x20 Allocated by task 77: kasan_save_stack+0x40/0x68 kasan_save_track+0x20/0x40 kasan_save_alloc_info+0x44/0x58 __kasan_kmalloc+0xbc/0xdc __kmalloc_node_track_caller_noprof+0x1f4/0x620 devm_kmalloc+0x7c/0x1c8 snd_soc_register_dai+0x50/0x4f0 [snd_soc_core] soc_tplg_pcm_elems_load+0x55c/0x1eb8 [snd_soc_core] snd_soc_tplg_component_load+0x4f8/0xb60 [snd_soc_core] audioreach_tplg_init+0x124/0x1fc [snd_q6apm] q6apm_audio_probe+0x10/0x1c [snd_q6apm] snd_soc_component_probe+0x5c/0x118 [snd_soc_core] soc_probe_component+0x44c/0xaf0 [snd_soc_core] snd_soc_bind_card+0xad0/0x2370 [snd_soc_core] snd_soc_register_card+0x3b0/0x4c0 [snd_soc_core] devm_snd_soc_register_card+0x50/0xc8 [snd_soc_core] x1e80100_platform_probe+0x208/0x368 [snd_soc_x1e80100] platform_probe+0xc0/0x188 really_probe+0x188/0x804 __driver_probe_device+0x158/0x358 driver_probe_device+0x60/0x190 __device_attach_driver+0x16c/0x2a8 bus_for_each_drv+0x100/0x194 __device_attach+0x174/0x380 device_initial_probe+0x14/0x20 bus_probe_device+0x124/0x154 deferred_probe_work_func+0x140/0x220 process_one_work+0x500/0xb70 worker_thread+0x630/0xfb0 kthread+0x370/0x6c0 ret_from_fork+0x10/0x20 Freed by task 3426: kasan_save_stack+0x40/0x68 kasan_save_track+0x20/0x40 __kasan_save_free_info+0x4c/0x80 __kasan_slab_free+0x78/0xa0 kfree+0x100/0x4a4 devres_release_all+0x144/0x210 device_unbind_cleanup+0x20/0x190 device_release_driver_internal+0x350/0x468 device_release_driver+0x18/0x30 bus_remove_device+0x1a0/0x35c device_del+0x314/0x7f0 device_unregister+0x20/0xbc apr_remove_device+0x5c/0x7c [apr] device_for_each_child+0xd8/0x160 apr_pd_status+0x7c/0xa8 [apr] pdr_notifier_work+0x114/0x240 [pdr_interface] process_one_work+0x500/0xb70 worker_thread+0x630/0xfb0 kthread+0x370/0x6c0 ret_from_fork+0x10/0x20 Fixes: 5477518b8a0e ("ASoC: qdsp6: audioreach: add q6apm support") Cc: Stable@vger.kernel.org Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260402081118.348071-2-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index 970b08c89bb3..069048db5367 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -747,13 +747,22 @@ static int apm_probe(gpr_device_t *gdev) q6apm_get_apm_state(apm); - ret = devm_snd_soc_register_component(dev, &q6apm_audio_component, NULL, 0); + ret = snd_soc_register_component(dev, &q6apm_audio_component, NULL, 0); if (ret < 0) { dev_err(dev, "failed to register q6apm: %d\n", ret); return ret; } - return of_platform_populate(dev->of_node, NULL, NULL, dev); + ret = of_platform_populate(dev->of_node, NULL, NULL, dev); + if (ret) + snd_soc_unregister_component(dev); + + return ret; +} + +static void apm_remove(gpr_device_t *gdev) +{ + snd_soc_unregister_component(&gdev->dev); } struct audioreach_module *q6apm_find_module_by_mid(struct q6apm_graph *graph, uint32_t mid) @@ -820,6 +829,7 @@ MODULE_DEVICE_TABLE(of, apm_device_id); static gpr_driver_t apm_driver = { .probe = apm_probe, + .remove = apm_remove, .gpr_callback = apm_callback, .driver = { .name = "qcom-apm", From 4a0e1bcc98f7281d1605768bd2fe71eacc34f9b7 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 2 Apr 2026 08:11:07 +0000 Subject: [PATCH 263/359] ASoC: qcom: q6apm: remove child devices when apm is removed looks like q6apm driver does not remove the child driver q6apm-dai and q6apm-bedais when the this driver is removed. Fix this by depopulating them in remove callback. With this change when the dsp is shutdown all the devices associated with q6apm will now be removed. Fixes: 5477518b8a0e ("ASoC: qdsp6: audioreach: add q6apm support") Cc: Stable@vger.kernel.org Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260402081118.348071-3-srinivas.kandagatla@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 069048db5367..2dc525c8be42 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -762,6 +762,7 @@ static int apm_probe(gpr_device_t *gdev) static void apm_remove(gpr_device_t *gdev) { + of_platform_depopulate(&gdev->dev); snd_soc_unregister_component(&gdev->dev); } From d5bfdd28e0cdd45043ae6e0ac168a451d59283dc Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 2 Apr 2026 08:11:08 +0000 Subject: [PATCH 264/359] ASoC: qcom: qdsp6: topology: check widget type before accessing data Check widget type before accessing the private data, as this could a virtual widget which is no associated with a dsp graph, container and module. Accessing witout check could lead to incorrect memory access. Fixes: 36ad9bf1d93d ("ASoC: qdsp6: audioreach: add topology support") Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260402081118.348071-4-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/topology.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/qcom/qdsp6/topology.c b/sound/soc/qcom/qdsp6/topology.c index e732fac9b8ca..1f69fba6de26 100644 --- a/sound/soc/qcom/qdsp6/topology.c +++ b/sound/soc/qcom/qdsp6/topology.c @@ -952,9 +952,6 @@ static int audioreach_widget_unload(struct snd_soc_component *scomp, struct audioreach_container *cont; struct audioreach_module *mod; - mod = dobj->private; - cont = mod->container; - if (w->id == snd_soc_dapm_mixer) { /* virtual widget */ struct snd_ar_control *scontrol = dobj->private; @@ -963,6 +960,11 @@ static int audioreach_widget_unload(struct snd_soc_component *scomp, kfree(scontrol); return 0; } + mod = dobj->private; + if (!mod) + return 0; + + cont = mod->container; mutex_lock(&apm->lock); idr_remove(&apm->modules_idr, mod->instance_id); From 69acc488aaf39d0ddf6c3cf0e47c1873d39919a2 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 2 Apr 2026 08:11:09 +0000 Subject: [PATCH 265/359] ASoC: qcom: q6apm-lpass-dai: Fix multiple graph opens As prepare can be called mulitple times, this can result in multiple graph opens for playback path. This will result in a memory leaks, fix this by adding a check before opening. Fixes: be1fae62cf25 ("ASoC: q6apm-lpass-dai: close graph on prepare errors") Cc: Stable@vger.kernel.org Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260402081118.348071-5-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-lpass-dais.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c index 5be37eeea329..ba64117b8cfe 100644 --- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c @@ -181,7 +181,7 @@ static int q6apm_lpass_dai_prepare(struct snd_pcm_substream *substream, struct s * It is recommend to load DSP with source graph first and then sink * graph, so sequence for playback and capture will be different */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && dai_data->graph[dai->id] == NULL) { graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id); if (IS_ERR(graph)) { dev_err(dai->dev, "Failed to open graph (%d)\n", graph_id); From cab45ab95ce7600fc0ff84585c77fd45b7b0d67c Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 2 Apr 2026 08:11:10 +0000 Subject: [PATCH 266/359] ASoC: qcom: q6apm-dai: reset queue ptr on trigger stop Reset queue pointer on SNDRV_PCM_TRIGGER_STOP event to be inline with resetting appl_ptr. Without this we will end up with a queue_ptr out of sync and driver could try to send data that is not ready yet. Fix this by resetting the queue_ptr. Fixes: 3d4a4411aa8bb ("ASoC: q6apm-dai: schedule all available frames to avoid dsp under-runs") Cc: Stable@vger.kernel.org Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260402081118.348071-6-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-dai.c | 1 + sound/soc/qcom/qdsp6/q6apm.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index 306e928e7b49..292be457764f 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -317,6 +317,7 @@ static int q6apm_dai_trigger(struct snd_soc_component *component, case SNDRV_PCM_TRIGGER_STOP: /* TODO support be handled via SoftPause Module */ prtd->state = Q6APM_STREAM_STOPPED; + prtd->queue_ptr = 0; break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index 2dc525c8be42..5751e80b3b92 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -215,6 +215,8 @@ int q6apm_map_memory_regions(struct q6apm_graph *graph, unsigned int dir, phys_a mutex_lock(&graph->lock); + data->dsp_buf = 0; + if (data->buf) { mutex_unlock(&graph->lock); return 0; From e46957f27c6004f3ab5ec456f4e848950364ebc1 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 2 Apr 2026 08:11:11 +0000 Subject: [PATCH 267/359] ASoC: dt-bindings: qcom: add LPASS LPI MI2S dai ids Add new dai ids entries for LPASS LPI MI2S and SENARY MI2S audio lines. Co-developed-by: Mohammad Rafi Shaik Signed-off-by: Mohammad Rafi Shaik Signed-off-by: Srinivas Kandagatla Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260402081118.348071-7-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- .../bindings/sound/qcom,q6dsp-lpass-ports.yaml | 5 ++++- include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml b/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml index 08c618e7e428..2b27d6c8f58f 100644 --- a/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml @@ -126,13 +126,16 @@ patternProperties: reg: contains: # MI2S DAI ID range PRIMARY_MI2S_RX - QUATERNARY_MI2S_TX and - # QUINARY_MI2S_RX - QUINARY_MI2S_TX + # QUINARY_MI2S_RX - QUINARY_MI2S_TX and + # LPI_MI2S_RX_0 - SENARY_MI2S_TX items: oneOf: - minimum: 16 maximum: 23 - minimum: 127 maximum: 128 + - minimum: 137 + maximum: 148 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 6d1ce7f5da51..45850f2d4342 100644 --- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h @@ -140,6 +140,18 @@ #define DISPLAY_PORT_RX_6 134 #define DISPLAY_PORT_RX_7 135 #define USB_RX 136 +#define LPI_MI2S_RX_0 137 +#define LPI_MI2S_TX_0 138 +#define LPI_MI2S_RX_1 139 +#define LPI_MI2S_TX_1 140 +#define LPI_MI2S_RX_2 141 +#define LPI_MI2S_TX_2 142 +#define LPI_MI2S_RX_3 143 +#define LPI_MI2S_TX_3 144 +#define LPI_MI2S_RX_4 145 +#define LPI_MI2S_TX_4 146 +#define SENARY_MI2S_RX 147 +#define SENARY_MI2S_TX 148 #define LPASS_CLK_ID_PRI_MI2S_IBIT 1 #define LPASS_CLK_ID_PRI_MI2S_EBIT 2 From d49ee8faefecd4d2eb5c6c9f419f4db9488d68aa Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 2 Apr 2026 08:11:12 +0000 Subject: [PATCH 268/359] ASoC: qcom: common: validate cpu dai id during parsing lpass ports numbers have been added but the afe/apm driver never got updated with new max port value that it uses to store dai specific data. There are more than one places these values are cached and always become out of sync. This will result in array out of bounds and weird driver behaviour. To catch such issues, first add a single place where we can define max port and second add a check in common parsing code which can error out before corrupting the memory with out of bounds array access. This should help both avoid and catch these type of mistakes in future. Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260402081118.348071-8-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/common.c | 6 ++++++ sound/soc/qcom/common.h | 3 +++ sound/soc/qcom/lpass.h | 3 ++- sound/soc/qcom/qdsp6/q6afe.h | 3 ++- sound/soc/qcom/qdsp6/q6apm.h | 3 ++- 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c index 7ee60a58a336..cf1f3a767cee 100644 --- a/sound/soc/qcom/common.c +++ b/sound/soc/qcom/common.c @@ -120,6 +120,12 @@ int qcom_snd_parse_of(struct snd_soc_card *card) link->id = args.args[0]; + if (link->id >= LPASS_MAX_PORT) { + dev_err(dev, "%s: Invalid cpu dai id %d\n", link->name, link->id); + ret = -EINVAL; + goto err; + } + if (platform) { link->platforms->of_node = of_parse_phandle(platform, "sound-dai", diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h index 1b8d3f90bffa..ee6662885593 100644 --- a/sound/soc/qcom/common.h +++ b/sound/soc/qcom/common.h @@ -4,8 +4,11 @@ #ifndef __QCOM_SND_COMMON_H__ #define __QCOM_SND_COMMON_H__ +#include #include +#define LPASS_MAX_PORT (SENARY_MI2S_TX + 1) + int qcom_snd_parse_of(struct snd_soc_card *card); int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, struct snd_soc_jack *jack, bool *jack_setup); diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h index de3ec6f594c1..68b71039b981 100644 --- a/sound/soc/qcom/lpass.h +++ b/sound/soc/qcom/lpass.h @@ -14,10 +14,11 @@ #include #include #include +#include "common.h" #include "lpass-hdmi.h" #define LPASS_AHBIX_CLOCK_FREQUENCY 131072000 -#define LPASS_MAX_PORTS (DISPLAY_PORT_RX_7 + 1) +#define LPASS_MAX_PORTS (LPASS_MAX_PORT) #define LPASS_MAX_MI2S_PORTS (8) #define LPASS_MAX_DMA_CHANNELS (8) #define LPASS_MAX_HDMI_DMA_CHANNELS (4) diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h index a29abe4ce436..0b8c3ec1315c 100644 --- a/sound/soc/qcom/qdsp6/q6afe.h +++ b/sound/soc/qcom/qdsp6/q6afe.h @@ -2,8 +2,9 @@ #ifndef __Q6AFE_H__ #define __Q6AFE_H__ +#include "../common.h" -#define AFE_PORT_MAX 137 +#define AFE_PORT_MAX (LPASS_MAX_PORT) #define MSM_AFE_PORT_TYPE_RX 0 #define MSM_AFE_PORT_TYPE_TX 1 diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h index 7ce08b401e31..5cf538397841 100644 --- a/sound/soc/qcom/qdsp6/q6apm.h +++ b/sound/soc/qcom/qdsp6/q6apm.h @@ -14,9 +14,10 @@ #include #include #include +#include "../common.h" #include "audioreach.h" -#define APM_PORT_MAX 127 +#define APM_PORT_MAX LPASS_MAX_PORT #define APM_PORT_MAX_AUDIO_CHAN_CNT 8 #define PCM_CHANNEL_NULL 0 #define PCM_CHANNEL_FL 1 /* Front left channel. */ From bcd0df1ebc9d71e2d53e47fd41ff1482753649b0 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 2 Apr 2026 08:11:13 +0000 Subject: [PATCH 269/359] ASoC: qcom: qdsp6: lpass-ports: add support for LPASS LPI MI2S dais Add support for LPASS LPI MI2S dais in the dai-driver, these dais are used in Monaco based platform devices. Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260402081118.348071-9-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c | 47 ++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c index 4eed54b071a5..d31388ed3ccf 100644 --- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c @@ -96,6 +96,42 @@ .id = did, \ } +#define Q6AFE_MI2S_RX_DAI(pre, did) { \ + .playback = { \ + .stream_name = pre" MI2S Playback", \ + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_176400, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + .channels_min = 1, \ + .channels_max = 8, \ + .rate_min = 8000, \ + .rate_max = 176400, \ + }, \ + .name = #did, \ + .id = did, \ + } + +#define Q6AFE_MI2S_TX_DAI(pre, did) { \ + .capture = { \ + .stream_name = pre" MI2S Capture", \ + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_176400, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + .channels_min = 1, \ + .channels_max = 8, \ + .rate_min = 8000, \ + .rate_max = 176400, \ + }, \ + .name = #did, \ + .id = did, \ + } + static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { { .playback = { @@ -484,6 +520,16 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { .id = QUINARY_MI2S_TX, .name = "QUIN_MI2S_TX", }, + 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), + Q6AFE_MI2S_RX_DAI("LPI RX3", LPI_MI2S_RX_3), + Q6AFE_MI2S_RX_DAI("LPI RX4", LPI_MI2S_RX_4), + Q6AFE_MI2S_TX_DAI("LPI TX0", LPI_MI2S_TX_0), + Q6AFE_MI2S_TX_DAI("LPI TX1", LPI_MI2S_TX_1), + Q6AFE_MI2S_TX_DAI("LPI TX2", LPI_MI2S_TX_2), + Q6AFE_MI2S_TX_DAI("LPI TX3", LPI_MI2S_TX_3), + Q6AFE_MI2S_TX_DAI("LPI TX4", LPI_MI2S_TX_4), 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), @@ -636,6 +682,7 @@ struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, break; case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: q6dsp_audio_fe_dais[i].ops = cfg->q6i2s_ops; break; case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: From ae0de4e50f8fbd828aff2d79e778ee0e171366ee Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Thu, 2 Apr 2026 08:11:14 +0000 Subject: [PATCH 270/359] ASoC: qcom: q6dsp: Add Senary MI2S audio interface support Introduces support for the Senary MI2S audio interface in the Qualcomm q6dsp. Add new AFE port IDs for Senary MI2S RX and TX and include the necessary mappings in the port configuration to allow audio routing over the Senary MI2S interface. Signed-off-by: Mohammad Rafi Shaik Signed-off-by: Srinivas Kandagatla Tested-by: Val Packett # sm7325-motorola-dubai Link: https://patch.msgid.link/20260402081118.348071-10-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6afe-dai.c | 6 +++++ sound/soc/qcom/qdsp6/q6afe.c | 8 +++++++ sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c | 29 ++++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c index 0f47aadaabe1..a0d21034a626 100644 --- a/sound/soc/qcom/qdsp6/q6afe-dai.c +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c @@ -409,6 +409,7 @@ static int q6afe_dai_prepare(struct snd_pcm_substream *substream, q6afe_slim_port_prepare(dai_data->port[dai->id], &dai_data->port_config[dai->id].slim); break; + case SENARY_MI2S_RX ... SENARY_MI2S_TX: case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: rc = q6afe_i2s_port_prepare(dai_data->port[dai->id], @@ -540,6 +541,7 @@ static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { {"Tertiary MI2S Playback", NULL, "TERT_MI2S_RX"}, {"Quaternary MI2S Playback", NULL, "QUAT_MI2S_RX"}, {"Quinary MI2S Playback", NULL, "QUIN_MI2S_RX"}, + {"Senary MI2S Playback", NULL, "SEN_MI2S_RX"}, {"Primary TDM0 Playback", NULL, "PRIMARY_TDM_RX_0"}, {"Primary TDM1 Playback", NULL, "PRIMARY_TDM_RX_1"}, @@ -636,6 +638,7 @@ static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { {"SEC_MI2S_TX", NULL, "Secondary MI2S Capture"}, {"QUAT_MI2S_TX", NULL, "Quaternary MI2S Capture"}, {"QUIN_MI2S_TX", NULL, "Quinary MI2S Capture"}, + {"SEN_MI2S_TX", NULL, "Senary MI2S Capture"}, {"WSA_CODEC_DMA_RX_0 Playback", NULL, "WSA_CODEC_DMA_RX_0"}, {"WSA_CODEC_DMA_TX_0", NULL, "WSA_CODEC_DMA_TX_0 Capture"}, @@ -770,6 +773,8 @@ static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = { SND_SOC_DAPM_AIF_OUT("SLIMBUS_4_TX", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("SLIMBUS_5_TX", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("SLIMBUS_6_TX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SEN_MI2S_RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEN_MI2S_TX", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("QUIN_MI2S_RX", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("QUIN_MI2S_TX", NULL, @@ -1037,6 +1042,7 @@ static void of_q6afe_parse_dai_data(struct device *dev, switch (id) { /* MI2S specific properties */ + case SENARY_MI2S_RX ... SENARY_MI2S_TX: case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: priv = &data->priv[id]; diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c index 43d877322bae..40237267fda0 100644 --- a/sound/soc/qcom/qdsp6/q6afe.c +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -130,6 +130,8 @@ #define AFE_PORT_ID_QUATERNARY_MI2S_TX 0x1007 #define AFE_PORT_ID_QUINARY_MI2S_RX 0x1016 #define AFE_PORT_ID_QUINARY_MI2S_TX 0x1017 +#define AFE_PORT_ID_SENARY_MI2S_RX 0x1018 +#define AFE_PORT_ID_SENARY_MI2S_TX 0x1019 /* Start of the range of port IDs for TDM devices. */ #define AFE_PORT_ID_TDM_PORT_RANGE_START 0x9000 @@ -718,6 +720,10 @@ static struct afe_port_map port_maps[AFE_PORT_MAX] = { QUINARY_MI2S_RX, 1, 1}, [QUINARY_MI2S_TX] = { AFE_PORT_ID_QUINARY_MI2S_TX, QUINARY_MI2S_TX, 0, 1}, + [SENARY_MI2S_RX] = { AFE_PORT_ID_SENARY_MI2S_RX, + SENARY_MI2S_RX, 1, 1}, + [SENARY_MI2S_TX] = { AFE_PORT_ID_SENARY_MI2S_TX, + SENARY_MI2S_TX, 0, 1}, [PRIMARY_TDM_RX_0] = { AFE_PORT_ID_PRIMARY_TDM_RX, PRIMARY_TDM_RX_0, 1, 1}, [PRIMARY_TDM_TX_0] = { AFE_PORT_ID_PRIMARY_TDM_TX, @@ -1777,6 +1783,8 @@ struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) case AFE_PORT_ID_QUATERNARY_MI2S_TX: case AFE_PORT_ID_QUINARY_MI2S_RX: case AFE_PORT_ID_QUINARY_MI2S_TX: + case AFE_PORT_ID_SENARY_MI2S_RX: + case AFE_PORT_ID_SENARY_MI2S_TX: cfg_type = AFE_PARAM_ID_I2S_CONFIG; break; case AFE_PORT_ID_PRIMARY_TDM_RX ... AFE_PORT_ID_QUINARY_TDM_TX_7: diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c index d31388ed3ccf..e5cd82f77b55 100644 --- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c @@ -519,6 +519,34 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { }, .id = QUINARY_MI2S_TX, .name = "QUIN_MI2S_TX", + }, { + .playback = { + .stream_name = "Senary MI2S Playback", + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + .id = SENARY_MI2S_RX, + .name = "SEN_MI2S_RX", + }, { + .capture = { + .stream_name = "Senary MI2S Capture", + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + .id = SENARY_MI2S_TX, + .name = "SEN_MI2S_TX", }, Q6AFE_MI2S_RX_DAI("LPI RX0", LPI_MI2S_RX_0), Q6AFE_MI2S_RX_DAI("LPI RX1", LPI_MI2S_RX_1), @@ -680,6 +708,7 @@ struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, case SLIMBUS_0_RX ... SLIMBUS_6_TX: q6dsp_audio_fe_dais[i].ops = cfg->q6slim_ops; break; + case SENARY_MI2S_RX ... SENARY_MI2S_TX: case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: From 8f542c7c4aa969331b9be2bad1a1c29883555ab8 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 2 Apr 2026 08:11:15 +0000 Subject: [PATCH 271/359] ASoC: qcom: qdapm-lpass-dai: correct the error message Fix the error message to reflect the actual graph stop error instead of graph close error. Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260402081118.348071-11-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-lpass-dais.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c index ba64117b8cfe..87f6db12003e 100644 --- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c @@ -150,7 +150,7 @@ static void q6apm_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct rc = q6apm_graph_stop(dai_data->graph[dai->id]); dai_data->is_port_started[dai->id] = false; if (rc < 0) - dev_err(dai->dev, "fail to close APM port (%d)\n", rc); + dev_err(dai->dev, "failed to stop APM port (%d)\n", rc); } if (dai_data->graph[dai->id]) { From b54a38af713830e76f60bab967fd06ee4301eaee Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 2 Apr 2026 08:11:16 +0000 Subject: [PATCH 272/359] ASoC: qcom: q6apm-lpass-dai: move graph start to trigger Start the graph at trigger callback. Staring the graph at prepare does not make sense as there is no data transfer at this point. Moving this to trigger will also help cope situation where pipewire is not happy if display port is not connected during start. Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260402081118.348071-12-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-lpass-dais.c | 40 +++++++++++++++++-------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c index 87f6db12003e..e904066484d5 100644 --- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c @@ -159,6 +159,31 @@ static void q6apm_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct } } +static int q6apm_lpass_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!dai_data->is_port_started[dai->id]) { + ret = q6apm_graph_start(dai_data->graph[dai->id]); + if (ret < 0) + dev_err(dai->dev, "Failed to start APM port %d\n", dai->id); + else + dai_data->is_port_started[dai->id] = true; + } + break; + default: + break; + } + + return ret; +} + static int q6apm_lpass_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); @@ -171,10 +196,6 @@ static int q6apm_lpass_dai_prepare(struct snd_pcm_substream *substream, struct s q6apm_graph_stop(dai_data->graph[dai->id]); dai_data->is_port_started[dai->id] = false; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - q6apm_graph_close(dai_data->graph[dai->id]); - dai_data->graph[dai->id] = NULL; - } } /** @@ -203,14 +224,6 @@ static int q6apm_lpass_dai_prepare(struct snd_pcm_substream *substream, struct s dev_err(dai->dev, "Failed to prepare Graph %d\n", rc); goto err; } - - rc = q6apm_graph_start(dai_data->graph[dai->id]); - if (rc < 0) { - dev_err(dai->dev, "Failed to start APM port %d\n", dai->id); - goto err; - } - dai_data->is_port_started[dai->id] = true; - return 0; err: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { @@ -254,6 +267,7 @@ static const struct snd_soc_dai_ops q6dma_ops = { .shutdown = q6apm_lpass_dai_shutdown, .set_channel_map = q6dma_set_channel_map, .hw_params = q6dma_hw_params, + .trigger = q6apm_lpass_dai_trigger, }; static const struct snd_soc_dai_ops q6i2s_ops = { @@ -263,6 +277,7 @@ static const struct snd_soc_dai_ops q6i2s_ops = { .set_channel_map = q6dma_set_channel_map, .hw_params = q6dma_hw_params, .set_fmt = q6i2s_set_fmt, + .trigger = q6apm_lpass_dai_trigger, }; static const struct snd_soc_dai_ops q6hdmi_ops = { @@ -271,6 +286,7 @@ static const struct snd_soc_dai_ops q6hdmi_ops = { .shutdown = q6apm_lpass_dai_shutdown, .hw_params = q6hdmi_hw_params, .set_fmt = q6i2s_set_fmt, + .trigger = q6apm_lpass_dai_trigger, }; static const struct snd_soc_component_driver q6apm_lpass_dai_component = { From d8b4163038dc02114474d96acefcb48adb4c6e0f Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 2 Apr 2026 08:11:17 +0000 Subject: [PATCH 273/359] ASoC: qcom: qdsp6: remove search for module iid in hot path Remove searching for Shared Memory module instance id on every read/write call, this is un-necessary if we can cache the shared memory module instance id per PCM graph. Add new member to graph struct to store shared memory module instance id to avoid searching for this in hot path. Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260402081118.348071-13-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-dai.c | 5 +++-- sound/soc/qcom/qdsp6/q6apm-lpass-dais.c | 4 ++-- sound/soc/qcom/qdsp6/q6apm.c | 14 +++++++++----- sound/soc/qcom/qdsp6/q6apm.h | 3 ++- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index 292be457764f..86d6438bd9fd 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -355,7 +355,7 @@ static int q6apm_dai_open(struct snd_soc_component *component, spin_lock_init(&prtd->lock); prtd->substream = substream; - prtd->graph = q6apm_graph_open(dev, event_handler, prtd, graph_id); + prtd->graph = q6apm_graph_open(dev, event_handler, prtd, graph_id, substream->stream); if (IS_ERR(prtd->graph)) { dev_err(dev, "%s: Could not allocate memory\n", __func__); ret = PTR_ERR(prtd->graph); @@ -496,7 +496,8 @@ static int q6apm_dai_compr_open(struct snd_soc_component *component, return -ENOMEM; prtd->cstream = stream; - prtd->graph = q6apm_graph_open(dev, event_handler_compr, prtd, graph_id); + prtd->graph = q6apm_graph_open(dev, event_handler_compr, prtd, graph_id, + SNDRV_PCM_STREAM_PLAYBACK); if (IS_ERR(prtd->graph)) { ret = PTR_ERR(prtd->graph); kfree(prtd); diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c index e904066484d5..006b283484d9 100644 --- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c @@ -203,7 +203,7 @@ static int q6apm_lpass_dai_prepare(struct snd_pcm_substream *substream, struct s * graph, so sequence for playback and capture will be different */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && dai_data->graph[dai->id] == NULL) { - graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id); + graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id, substream->stream); if (IS_ERR(graph)) { dev_err(dai->dev, "Failed to open graph (%d)\n", graph_id); rc = PTR_ERR(graph); @@ -240,7 +240,7 @@ static int q6apm_lpass_dai_startup(struct snd_pcm_substream *substream, struct s int graph_id = dai->id; if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { - graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id); + graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id, substream->stream); if (IS_ERR(graph)) { dev_err(dai->dev, "Failed to open graph (%d)\n", graph_id); return PTR_ERR(graph); diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index 5751e80b3b92..6a3942a1ed28 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -411,12 +411,11 @@ int q6apm_write_async(struct q6apm_graph *graph, uint32_t len, uint32_t msw_ts, { struct apm_data_cmd_wr_sh_mem_ep_data_buffer_v2 *write_buffer; struct audio_buffer *ab; - int iid = q6apm_graph_get_rx_shmem_module_iid(graph); struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_pkt(sizeof(*write_buffer), DATA_CMD_WR_SH_MEM_EP_DATA_BUFFER_V2, graph->rx_data.dsp_buf | (len << APM_WRITE_TOKEN_LEN_SHIFT), - graph->port->id, iid); + graph->port->id, graph->shm_iid); if (IS_ERR(pkt)) return PTR_ERR(pkt); @@ -449,11 +448,10 @@ int q6apm_read(struct q6apm_graph *graph) struct data_cmd_rd_sh_mem_ep_data_buffer_v2 *read_buffer; struct audioreach_graph_data *port; struct audio_buffer *ab; - int iid = q6apm_graph_get_tx_shmem_module_iid(graph); struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_pkt(sizeof(*read_buffer), DATA_CMD_RD_SH_MEM_EP_DATA_BUFFER_V2, - graph->tx_data.dsp_buf, graph->port->id, iid); + graph->tx_data.dsp_buf, graph->port->id, graph->shm_iid); if (IS_ERR(pkt)) return PTR_ERR(pkt); @@ -604,7 +602,7 @@ static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op) } struct q6apm_graph *q6apm_graph_open(struct device *dev, q6apm_cb cb, - void *priv, int graph_id) + void *priv, int graph_id, int dir) { struct q6apm *apm = dev_get_drvdata(dev->parent); struct audioreach_graph *ar_graph; @@ -631,6 +629,12 @@ struct q6apm_graph *q6apm_graph_open(struct device *dev, q6apm_cb cb, graph->id = ar_graph->id; graph->dev = dev; + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + graph->shm_iid = q6apm_graph_get_rx_shmem_module_iid(graph); + else + graph->shm_iid = q6apm_graph_get_tx_shmem_module_iid(graph); + + mutex_init(&graph->lock); init_waitqueue_head(&graph->cmd_wait); diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h index 5cf538397841..7c646ffcf956 100644 --- a/sound/soc/qcom/qdsp6/q6apm.h +++ b/sound/soc/qcom/qdsp6/q6apm.h @@ -99,6 +99,7 @@ struct q6apm_graph { void *priv; q6apm_cb cb; uint32_t id; + uint32_t shm_iid; struct device *dev; struct q6apm *apm; gpr_port_t *port; @@ -113,7 +114,7 @@ struct q6apm_graph { /* Graph Operations */ struct q6apm_graph *q6apm_graph_open(struct device *dev, q6apm_cb cb, - void *priv, int graph_id); + void *priv, int graph_id, int dir); int q6apm_graph_close(struct q6apm_graph *graph); int q6apm_graph_prepare(struct q6apm_graph *graph); int q6apm_graph_start(struct q6apm_graph *graph); From 8ea6e25c8536031604d95dc29a90ef0f114012d7 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 2 Apr 2026 08:11:18 +0000 Subject: [PATCH 274/359] ASoC: qcom: q6apm: Add support for early buffer mapping on DSP Buffers are allocated on pcm_new and mapped in the dsp on every prepare call, which is inefficient and unnecessary. Add new functions q6apm_[un]map_memory_fixed_region to map it on to dsp only once after allocation. Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260402081118.348071-14-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/audioreach.c | 60 ------------ sound/soc/qcom/qdsp6/audioreach.h | 5 +- sound/soc/qcom/qdsp6/q6apm-dai.c | 107 ++++++++++++++++++--- sound/soc/qcom/qdsp6/q6apm.c | 153 +++++++++++++++++++----------- sound/soc/qcom/qdsp6/q6apm.h | 14 +-- 5 files changed, 203 insertions(+), 136 deletions(-) diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c index 241c3b4479c6..b2975eebab71 100644 --- a/sound/soc/qcom/qdsp6/audioreach.c +++ b/sound/soc/qcom/qdsp6/audioreach.c @@ -1396,66 +1396,6 @@ void audioreach_graph_free_buf(struct q6apm_graph *graph) } EXPORT_SYMBOL_GPL(audioreach_graph_free_buf); -int audioreach_map_memory_regions(struct q6apm_graph *graph, unsigned int dir, size_t period_sz, - unsigned int periods, bool is_contiguous) -{ - struct apm_shared_map_region_payload *mregions; - struct apm_cmd_shared_mem_map_regions *cmd; - uint32_t num_regions, buf_sz, payload_size; - struct audioreach_graph_data *data; - struct gpr_pkt *pkt __free(kfree) = NULL; - void *p; - int i; - - if (dir == SNDRV_PCM_STREAM_PLAYBACK) - data = &graph->rx_data; - else - data = &graph->tx_data; - - if (is_contiguous) { - num_regions = 1; - buf_sz = period_sz * periods; - } else { - buf_sz = period_sz; - num_regions = periods; - } - - /* DSP expects size should be aligned to 4K */ - buf_sz = ALIGN(buf_sz, 4096); - - payload_size = sizeof(*cmd) + (sizeof(*mregions) * num_regions); - - pkt = audioreach_alloc_apm_pkt(payload_size, APM_CMD_SHARED_MEM_MAP_REGIONS, dir, - graph->port->id); - if (IS_ERR(pkt)) - return PTR_ERR(pkt); - - p = (void *)pkt + GPR_HDR_SIZE; - cmd = p; - cmd->mem_pool_id = APM_MEMORY_MAP_SHMEM8_4K_POOL; - cmd->num_regions = num_regions; - - cmd->property_flag = 0x0; - - mregions = p + sizeof(*cmd); - - mutex_lock(&graph->lock); - - for (i = 0; i < num_regions; i++) { - struct audio_buffer *ab; - - ab = &data->buf[i]; - mregions->shm_addr_lsw = lower_32_bits(ab->phys); - mregions->shm_addr_msw = upper_32_bits(ab->phys); - mregions->mem_size_bytes = buf_sz; - ++mregions; - } - mutex_unlock(&graph->lock); - - return audioreach_graph_send_cmd_sync(graph, pkt, APM_CMD_RSP_SHARED_MEM_MAP_REGIONS); -} -EXPORT_SYMBOL_GPL(audioreach_map_memory_regions); - int audioreach_shared_memory_send_eos(struct q6apm_graph *graph) { struct data_cmd_wr_sh_mem_ep_eos *eos; diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h index 89f172aab8c0..6ddc287f0fb4 100644 --- a/sound/soc/qcom/qdsp6/audioreach.h +++ b/sound/soc/qcom/qdsp6/audioreach.h @@ -722,6 +722,7 @@ struct audioreach_connection { struct audioreach_graph_info { int id; + uint32_t mem_map_handle; uint32_t num_sub_graphs; struct list_head sg_list; /* DPCM connection from FE Graph to BE graph */ @@ -838,10 +839,6 @@ int audioreach_tplg_init(struct snd_soc_component *component); /* Module specific */ void audioreach_graph_free_buf(struct q6apm_graph *graph); -int audioreach_map_memory_regions(struct q6apm_graph *graph, - unsigned int dir, size_t period_sz, - unsigned int periods, - bool is_contiguous); 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); diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index 86d6438bd9fd..ede19fdea6e9 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -228,11 +228,10 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, cfg.bit_width = prtd->bits_per_sample; cfg.fmt = SND_AUDIOCODEC_PCM; audioreach_set_default_channel_mapping(cfg.channel_map, runtime->channels); - if (prtd->state) { /* clear the previous setup if any */ q6apm_graph_stop(prtd->graph); - q6apm_unmap_memory_regions(prtd->graph, substream->stream); + q6apm_free_fragments(prtd->graph, substream->stream); } prtd->pcm_count = snd_pcm_lib_period_bytes(substream); @@ -247,8 +246,8 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, if (ret < 0) dev_err(dev, "%s: CMD Format block failed\n", __func__); - ret = q6apm_map_memory_regions(prtd->graph, substream->stream, prtd->phys, - (prtd->pcm_size / prtd->periods), prtd->periods); + ret = q6apm_alloc_fragments(prtd->graph, substream->stream, prtd->phys, + (prtd->pcm_size / prtd->periods), prtd->periods); if (ret < 0) { dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret); @@ -416,9 +415,10 @@ static int q6apm_dai_close(struct snd_soc_component *component, struct snd_pcm_runtime *runtime = substream->runtime; struct q6apm_dai_rtd *prtd = runtime->private_data; - if (prtd->state) { /* only stop graph that is started */ + if (prtd->state) { + /* only stop graph that is started */ q6apm_graph_stop(prtd->graph); - q6apm_unmap_memory_regions(prtd->graph, substream->stream); + q6apm_free_fragments(prtd->graph, substream->stream); } q6apm_graph_close(prtd->graph); @@ -467,11 +467,94 @@ static int q6apm_dai_hw_params(struct snd_soc_component *component, return 0; } +static int q6apm_dai_memory_map(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int graph_id) +{ + struct q6apm_dai_data *pdata; + struct device *dev = component->dev; + phys_addr_t phys; + int ret; + + pdata = snd_soc_component_get_drvdata(component); + if (!pdata) { + dev_err(component->dev, "Drv data not found ..\n"); + return -EINVAL; + } + + 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); + if (ret < 0) + dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret); + + return ret; +} + 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 snd_pcm *pcm = rtd->pcm; int size = BUFFER_BYTES_MAX; + int graph_id, ret; + struct snd_pcm_substream *substream; - return snd_pcm_set_fixed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, component->dev, size); + graph_id = cpu_dai->driver->id; + + ret = snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, component->dev, size); + if (ret) + return ret; + + /* 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; + ret = q6apm_dai_memory_map(component, substream, graph_id); + if (ret) + return ret; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + ret = q6apm_dai_memory_map(component, substream, graph_id); + if (ret) + return ret; + } + + return 0; +} + +static void q6apm_dai_memory_unmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_prtd; + struct snd_soc_dai *cpu_dai; + int graph_id; + + soc_prtd = snd_soc_substream_to_rtd(substream); + if (!soc_prtd) + return; + + cpu_dai = snd_soc_rtd_to_cpu(soc_prtd, 0); + if (!cpu_dai) + return; + + graph_id = cpu_dai->driver->id; + q6apm_unmap_memory_fixed_region(component->dev, graph_id); +} + +static void q6apm_dai_pcm_free(struct snd_soc_component *component, struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + if (substream) + q6apm_dai_memory_unmap(component, substream); + + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + if (substream) + q6apm_dai_memory_unmap(component, substream); } static int q6apm_dai_compr_open(struct snd_soc_component *component, @@ -530,7 +613,8 @@ static int q6apm_dai_compr_free(struct snd_soc_component *component, struct q6apm_dai_rtd *prtd = runtime->private_data; q6apm_graph_stop(prtd->graph); - q6apm_unmap_memory_regions(prtd->graph, SNDRV_PCM_STREAM_PLAYBACK); + q6apm_free_fragments(prtd->graph, SNDRV_PCM_STREAM_PLAYBACK); + q6apm_unmap_memory_fixed_region(component->dev, prtd->graph->id); q6apm_graph_close(prtd->graph); snd_dma_free_pages(&prtd->dma_buffer); prtd->graph = NULL; @@ -679,9 +763,9 @@ static int q6apm_dai_compr_set_params(struct snd_soc_component *component, if (ret) return ret; - ret = q6apm_map_memory_regions(prtd->graph, SNDRV_PCM_STREAM_PLAYBACK, - prtd->phys, (prtd->pcm_size / prtd->periods), - prtd->periods); + ret = q6apm_alloc_fragments(prtd->graph, SNDRV_PCM_STREAM_PLAYBACK, + prtd->phys, (prtd->pcm_size / prtd->periods), + prtd->periods); if (ret < 0) return -ENOMEM; @@ -834,6 +918,7 @@ static const struct snd_soc_component_driver q6apm_fe_dai_component = { .close = q6apm_dai_close, .prepare = q6apm_dai_prepare, .pcm_new = q6apm_dai_pcm_new, + .pcm_free = q6apm_dai_pcm_free, .hw_params = q6apm_dai_hw_params, .pointer = q6apm_dai_pointer, .trigger = q6apm_dai_trigger, diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index 6a3942a1ed28..3c119a6132e4 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -200,13 +200,53 @@ int q6apm_graph_media_format_shmem(struct q6apm_graph *graph, } EXPORT_SYMBOL_GPL(q6apm_graph_media_format_shmem); -int q6apm_map_memory_regions(struct q6apm_graph *graph, unsigned int dir, phys_addr_t phys, - size_t period_sz, unsigned int periods) +int q6apm_map_memory_fixed_region(struct device *dev, unsigned int graph_id, phys_addr_t phys, + size_t sz) +{ + struct audioreach_graph_info *info; + struct q6apm *apm = dev_get_drvdata(dev->parent); + struct apm_shared_map_region_payload *mregions; + struct apm_cmd_shared_mem_map_regions *cmd; + int payload_size = sizeof(*cmd) + (sizeof(*mregions)); + uint32_t buf_sz; + void *p; + struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(payload_size, + APM_CMD_SHARED_MEM_MAP_REGIONS, graph_id); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + info = idr_find(&apm->graph_info_idr, graph_id); + if (!info) + return -ENODEV; + + if (info->mem_map_handle) + return 0; + + /* DSP expects size should be aligned to 4K */ + buf_sz = ALIGN(sz, 4096); + + p = (void *)pkt + GPR_HDR_SIZE; + cmd = p; + cmd->mem_pool_id = APM_MEMORY_MAP_SHMEM8_4K_POOL; + cmd->num_regions = 1; + cmd->property_flag = 0x0; + + mregions = p + sizeof(*cmd); + + mregions->shm_addr_lsw = lower_32_bits(phys); + mregions->shm_addr_msw = upper_32_bits(phys); + mregions->mem_size_bytes = buf_sz; + + return q6apm_send_cmd_sync(apm, pkt, APM_CMD_RSP_SHARED_MEM_MAP_REGIONS); +} +EXPORT_SYMBOL_GPL(q6apm_map_memory_fixed_region); + +int q6apm_alloc_fragments(struct q6apm_graph *graph, unsigned int dir, phys_addr_t phys, + size_t period_sz, unsigned int periods) { struct audioreach_graph_data *data; struct audio_buffer *buf; int cnt; - int rc; if (dir == SNDRV_PCM_STREAM_PLAYBACK) data = &graph->rx_data; @@ -248,46 +288,41 @@ int q6apm_map_memory_regions(struct q6apm_graph *graph, unsigned int dir, phys_a mutex_unlock(&graph->lock); - rc = audioreach_map_memory_regions(graph, dir, period_sz, periods, 1); - if (rc < 0) { - dev_err(graph->dev, "Memory_map_regions failed\n"); - audioreach_graph_free_buf(graph); - } - - return rc; + return 0; } -EXPORT_SYMBOL_GPL(q6apm_map_memory_regions); +EXPORT_SYMBOL_GPL(q6apm_alloc_fragments); -int q6apm_unmap_memory_regions(struct q6apm_graph *graph, unsigned int dir) +int q6apm_unmap_memory_fixed_region(struct device *dev, unsigned int graph_id) { struct apm_cmd_shared_mem_unmap_regions *cmd; - struct audioreach_graph_data *data; - int rc; - - if (dir == SNDRV_PCM_STREAM_PLAYBACK) - data = &graph->rx_data; - else - data = &graph->tx_data; - - if (!data->mem_map_handle) - return 0; - - struct gpr_pkt *pkt __free(kfree) = - audioreach_alloc_apm_pkt(sizeof(*cmd), APM_CMD_SHARED_MEM_UNMAP_REGIONS, - dir, graph->port->id); + struct q6apm *apm = dev_get_drvdata(dev->parent); + struct audioreach_graph_info *info; + struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(sizeof(*cmd), + APM_CMD_SHARED_MEM_UNMAP_REGIONS, graph_id); if (IS_ERR(pkt)) return PTR_ERR(pkt); + info = idr_find(&apm->graph_info_idr, graph_id); + if (!info) + return -ENODEV; + + if (!info->mem_map_handle) + return 0; + cmd = (void *)pkt + GPR_HDR_SIZE; - cmd->mem_map_handle = data->mem_map_handle; + cmd->mem_map_handle = info->mem_map_handle; - rc = audioreach_graph_send_cmd_sync(graph, pkt, APM_CMD_SHARED_MEM_UNMAP_REGIONS); + return q6apm_send_cmd_sync(apm, pkt, APM_CMD_SHARED_MEM_UNMAP_REGIONS); +} +EXPORT_SYMBOL_GPL(q6apm_unmap_memory_fixed_region); +int q6apm_free_fragments(struct q6apm_graph *graph, unsigned int dir) +{ audioreach_graph_free_buf(graph); - return rc; + return 0; } -EXPORT_SYMBOL_GPL(q6apm_unmap_memory_regions); +EXPORT_SYMBOL_GPL(q6apm_free_fragments); int q6apm_remove_initial_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples) { @@ -429,7 +464,7 @@ int q6apm_write_async(struct q6apm_graph *graph, uint32_t len, uint32_t msw_ts, write_buffer->buf_size = len; write_buffer->timestamp_lsw = lsw_ts; write_buffer->timestamp_msw = msw_ts; - write_buffer->mem_map_handle = graph->rx_data.mem_map_handle; + write_buffer->mem_map_handle = graph->info->mem_map_handle; write_buffer->flags = wflags; graph->rx_data.dsp_buf++; @@ -463,7 +498,7 @@ int q6apm_read(struct q6apm_graph *graph) read_buffer->buf_addr_lsw = lower_32_bits(ab->phys); read_buffer->buf_addr_msw = upper_32_bits(ab->phys); - read_buffer->mem_map_handle = port->mem_map_handle; + read_buffer->mem_map_handle = graph->info->mem_map_handle; read_buffer->buf_size = ab->size; port->dsp_buf++; @@ -494,7 +529,6 @@ static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op) { struct data_cmd_rsp_rd_sh_mem_ep_data_buffer_done_v2 *rd_done; struct data_cmd_rsp_wr_sh_mem_ep_data_buffer_done_v2 *done; - struct apm_cmd_rsp_shared_mem_map_regions *rsp; const struct gpr_ibasic_rsp_result_t *result; struct q6apm_graph *graph = priv; const struct gpr_hdr *hdr = &data->hdr; @@ -529,18 +563,6 @@ static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op) done->buf_addr_msw); } - break; - case APM_CMD_RSP_SHARED_MEM_MAP_REGIONS: - graph->result.opcode = hdr->opcode; - graph->result.status = 0; - rsp = data->payload; - - if (hdr->token == SNDRV_PCM_STREAM_PLAYBACK) - graph->rx_data.mem_map_handle = rsp->mem_map_handle; - else - graph->tx_data.mem_map_handle = rsp->mem_map_handle; - - wake_up(&graph->cmd_wait); break; case DATA_CMD_RSP_RD_SH_MEM_EP_DATA_BUFFER_V2: if (!graph->ar_graph) @@ -571,16 +593,6 @@ static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op) break; case GPR_BASIC_RSP_RESULT: switch (result->opcode) { - case APM_CMD_SHARED_MEM_UNMAP_REGIONS: - graph->result.opcode = result->opcode; - graph->result.status = 0; - if (hdr->token == SNDRV_PCM_STREAM_PLAYBACK) - graph->rx_data.mem_map_handle = 0; - else - graph->tx_data.mem_map_handle = 0; - - wake_up(&graph->cmd_wait); - break; case APM_CMD_SHARED_MEM_MAP_REGIONS: case DATA_CMD_WR_SH_MEM_EP_MEDIA_FORMAT: case APM_CMD_SET_CFG: @@ -784,7 +796,9 @@ struct audioreach_module *q6apm_find_module_by_mid(struct q6apm_graph *graph, ui static int apm_callback(const struct gpr_resp_pkt *data, void *priv, int op) { gpr_device_t *gdev = priv; + struct audioreach_graph_info *info; struct q6apm *apm = dev_get_drvdata(&gdev->dev); + struct apm_cmd_rsp_shared_mem_map_regions *rsp; struct device *dev = &gdev->dev; struct gpr_ibasic_rsp_result_t *result; const struct gpr_hdr *hdr = &data->hdr; @@ -801,6 +815,7 @@ static int apm_callback(const struct gpr_resp_pkt *data, void *priv, int op) break; case GPR_BASIC_RSP_RESULT: switch (result->opcode) { + case APM_CMD_SHARED_MEM_MAP_REGIONS: case APM_CMD_GRAPH_START: case APM_CMD_GRAPH_OPEN: case APM_CMD_GRAPH_PREPARE: @@ -815,10 +830,38 @@ static int apm_callback(const struct gpr_resp_pkt *data, void *priv, int op) result->opcode); wake_up(&apm->wait); break; + case APM_CMD_SHARED_MEM_UNMAP_REGIONS: + apm->result.opcode = hdr->opcode; + apm->result.status = 0; + rsp = data->payload; + + info = idr_find(&apm->graph_info_idr, hdr->token); + if (info) + info->mem_map_handle = 0; + else + dev_err(dev, "Error (%d) Processing 0x%08x cmd\n", result->status, + result->opcode); + + wake_up(&apm->wait); + break; default: break; } break; + case APM_CMD_RSP_SHARED_MEM_MAP_REGIONS: + apm->result.opcode = hdr->opcode; + apm->result.status = 0; + rsp = data->payload; + + info = idr_find(&apm->graph_info_idr, hdr->token); + if (info) + info->mem_map_handle = rsp->mem_map_handle; + else + dev_err(dev, "Error (%d) Processing 0x%08x cmd\n", result->status, + result->opcode); + + wake_up(&apm->wait); + break; default: break; } diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h index 7c646ffcf956..909fc337fd28 100644 --- a/sound/soc/qcom/qdsp6/q6apm.h +++ b/sound/soc/qcom/qdsp6/q6apm.h @@ -78,7 +78,6 @@ struct audioreach_graph_data { struct audio_buffer *buf; uint32_t num_periods; uint32_t dsp_buf; - uint32_t mem_map_handle; atomic_t hw_ptr; }; @@ -134,11 +133,14 @@ int q6apm_write_async(struct q6apm_graph *graph, uint32_t len, uint32_t msw_ts, uint32_t lsw_ts, uint32_t wflags); /* Memory Map related */ -int q6apm_map_memory_regions(struct q6apm_graph *graph, - unsigned int dir, phys_addr_t phys, - size_t period_sz, unsigned int periods); -int q6apm_unmap_memory_regions(struct q6apm_graph *graph, - unsigned int dir); +int q6apm_map_memory_fixed_region(struct device *dev, + unsigned int graph_id, phys_addr_t phys, + size_t sz); +int q6apm_alloc_fragments(struct q6apm_graph *graph, + unsigned int dir, phys_addr_t phys, + size_t period_sz, unsigned int periods); +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, struct gpr_pkt *pkt, uint32_t rsp_opcode); From e5d5aef802a5f41283084f7d443ef4fd4b65d86d Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Fri, 3 Apr 2026 09:47:36 +0800 Subject: [PATCH 275/359] ALSA: aoa/onyx: Fix OF node leak on probe failure Add missing of_node_put() in the error path. Signed-off-by: wangdicheng Link: https://patch.msgid.link/20260403014736.33014-1-wangdich9700@163.com Signed-off-by: Takashi Iwai --- sound/aoa/codecs/onyx.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/aoa/codecs/onyx.c b/sound/aoa/codecs/onyx.c index 04961c456d2c..da0eebf5dfbc 100644 --- a/sound/aoa/codecs/onyx.c +++ b/sound/aoa/codecs/onyx.c @@ -980,10 +980,12 @@ static int onyx_i2c_probe(struct i2c_client *client) onyx->codec.node = of_node_get(node); if (aoa_codec_register(&onyx->codec)) { - goto fail; + goto fail_put; } printk(KERN_DEBUG PFX "created and attached onyx instance\n"); return 0; + fail_put: + of_node_put(onyx->codec.node); fail: kfree(onyx); return -ENODEV; From 6692ed9b4ced29aa819c95cc4ad9e2dc8720c081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Fri, 3 Apr 2026 00:21:34 -0300 Subject: [PATCH 276/359] ALSA: hda: Notify IEC958 Default PCM switch state changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "IEC958 Default PCM Playback Switch" control is backed directly by mout->share_spdif. The share-switch callbacks currently access that state without serialization, and spdif_share_sw_put() always returns 0, so normal userspace writes never emit the standard ALSA control value notification. snd_hda_multi_out_analog_open() may also clear mout->share_spdif when the analog PCM capabilities and the SPDIF capabilities no longer intersect. That fallback is still needed to avoid creating an impossible hw constraint set, but it changes the mixer backing value without notifying subscribers. Protect the share-switch callbacks with spdif_mutex like the other SPDIF control handlers, return the actual change value from spdif_share_sw_put(), and notify the cached control when the open path forcibly disables shared SPDIF mode after dropping spdif_mutex. This keeps the existing auto-disable behavior while making switch state changes visible to userspace. Fixes: 9a08160bdbe3 ("[ALSA] hda-codec - Add "IEC958 Default PCM" switch") Fixes: 022b466fc353 ("ALSA: hda - Avoid invalid formats and rates with shared SPDIF") Suggested-by: Takashi Iwai Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260403-hda-spdif-share-notify-v3-1-4eb1356b0f17@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/common/codec.c | 46 +++++++++++++++++++++++++++++------- sound/hda/common/hda_local.h | 1 + 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index 09b1329bb8f3..5123df32ad89 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -2529,7 +2529,10 @@ EXPORT_SYMBOL_GPL(snd_hda_spdif_ctls_assign); static int spdif_share_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol); + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_multi_out *mout = (void *)kcontrol->private_value; + + guard(mutex)(&codec->spdif_mutex); ucontrol->value.integer.value[0] = mout->share_spdif; return 0; } @@ -2537,9 +2540,15 @@ static int spdif_share_sw_get(struct snd_kcontrol *kcontrol, static int spdif_share_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol); - mout->share_spdif = !!ucontrol->value.integer.value[0]; - return 0; + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_multi_out *mout = (void *)kcontrol->private_value; + bool val = !!ucontrol->value.integer.value[0]; + int change; + + guard(mutex)(&codec->spdif_mutex); + change = mout->share_spdif != val; + mout->share_spdif = val; + return change; } static const struct snd_kcontrol_new spdif_share_sw = { @@ -2550,6 +2559,14 @@ static const struct snd_kcontrol_new spdif_share_sw = { .put = spdif_share_sw_put, }; +static void notify_spdif_share_sw(struct hda_codec *codec, + struct hda_multi_out *mout) +{ + if (mout->share_spdif_kctl) + snd_ctl_notify_one(codec->card, SNDRV_CTL_EVENT_MASK_VALUE, + mout->share_spdif_kctl, 0); +} + /** * snd_hda_create_spdif_share_sw - create Default PCM switch * @codec: the HDA codec @@ -2559,15 +2576,24 @@ int snd_hda_create_spdif_share_sw(struct hda_codec *codec, struct hda_multi_out *mout) { struct snd_kcontrol *kctl; + int err; if (!mout->dig_out_nid) return 0; - kctl = snd_ctl_new1(&spdif_share_sw, mout); + kctl = snd_ctl_new1(&spdif_share_sw, codec); if (!kctl) return -ENOMEM; - /* ATTENTION: here mout is passed as private_data, instead of codec */ - return snd_hda_ctl_add(codec, mout->dig_out_nid, kctl); + /* snd_ctl_new1() stores @codec in private_data; stash @mout in + * private_value for the share-switch callbacks and cache the + * assigned control for forced-disable notifications. + */ + kctl->private_value = (unsigned long)mout; + err = snd_hda_ctl_add(codec, mout->dig_out_nid, kctl); + if (err < 0) + return err; + mout->share_spdif_kctl = kctl; + return 0; } EXPORT_SYMBOL_GPL(snd_hda_create_spdif_share_sw); @@ -3701,6 +3727,8 @@ int snd_hda_multi_out_analog_open(struct hda_codec *codec, struct hda_pcm_stream *hinfo) { struct snd_pcm_runtime *runtime = substream->runtime; + bool notify_share_sw = false; + runtime->hw.channels_max = mout->max_channels; if (mout->dig_out_nid) { if (!mout->analog_rates) { @@ -3729,10 +3757,12 @@ int snd_hda_multi_out_analog_open(struct hda_codec *codec, hinfo->maxbps = mout->spdif_maxbps; } else { mout->share_spdif = 0; - /* FIXME: need notify? */ + notify_share_sw = true; } } } + if (notify_share_sw) + notify_spdif_share_sw(codec, mout); return snd_pcm_hw_constraint_step(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 2); } diff --git a/sound/hda/common/hda_local.h b/sound/hda/common/hda_local.h index ab423f1cef54..98b2c4acebc2 100644 --- a/sound/hda/common/hda_local.h +++ b/sound/hda/common/hda_local.h @@ -221,6 +221,7 @@ struct hda_multi_out { unsigned int spdif_rates; unsigned int spdif_maxbps; u64 spdif_formats; + struct snd_kcontrol *share_spdif_kctl; /* cached shared SPDIF switch */ }; int snd_hda_create_spdif_share_sw(struct hda_codec *codec, From d733fb463834cf97a0c667681e236fea0e833a05 Mon Sep 17 00:00:00 2001 From: Maciej Strozek Date: Fri, 3 Apr 2026 09:23:35 +0100 Subject: [PATCH 277/359] ASoC: intel: sof_sdw: Prepare for configuration without a jack In certain setups of cs42l43 UAJ function may be removed from ACPI and physically unconnected. Prepare a driver for that configuration by setting a system clock in the speaker path too. Signed-off-by: Maciej Strozek Link: https://patch.msgid.link/20260403082335.40798-1-mstrozek@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_cs42l43.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sound/soc/sdw_utils/soc_sdw_cs42l43.c b/sound/soc/sdw_utils/soc_sdw_cs42l43.c index 2685ff4f0932..4a451b9d4f13 100644 --- a/sound/soc/sdw_utils/soc_sdw_cs42l43.c +++ b/sound/soc/sdw_utils/soc_sdw_cs42l43.c @@ -107,6 +107,7 @@ EXPORT_SYMBOL_NS(asoc_sdw_cs42l43_hs_rtd_init, "SND_SOC_SDW_UTILS"); int asoc_sdw_cs42l43_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai) { + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; 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); @@ -131,8 +132,15 @@ int asoc_sdw_cs42l43_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_so ret = snd_soc_dapm_add_routes(dapm, cs42l43_spk_map, ARRAY_SIZE(cs42l43_spk_map)); - if (ret) + if (ret) { dev_err(card->dev, "cs42l43 speaker map addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_component_set_sysclk(component, CS42L43_SYSCLK, CS42L43_SYSCLK_SDW, + 0, SND_SOC_CLOCK_IN); + if (ret) + dev_err(card->dev, "Failed to set sysclk: %d\n", ret); return ret; } From 0178e64123129f56fc153b9d53121318fd71bdfc Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 3 Apr 2026 12:04:25 +0530 Subject: [PATCH 278/359] ASoC: amd: acp-sdw-legacy: remove unnecessary condition check Currently there is no mechanism to read dmic_num in mach_params structure. In this scenario mach_params->dmic_num check always returns 0. Remove unnecessary condition check for mach_params->dmic_num. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260403063452.159800-1-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-legacy-mach.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c index 05484c72f9a8..24e99d8d36ab 100644 --- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c +++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c @@ -378,10 +378,8 @@ static int create_dmic_dailinks(struct snd_soc_card *card, static int soc_card_dai_links_create(struct snd_soc_card *card) { struct device *dev = card->dev; - struct snd_soc_acpi_mach *mach = dev_get_platdata(card->dev); int sdw_be_num = 0, dmic_num = 0; struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); - struct snd_soc_acpi_mach_params *mach_params = &mach->mach_params; struct snd_soc_aux_dev *soc_aux; struct snd_soc_codec_conf *codec_conf; struct snd_soc_dai_link *dai_links; @@ -424,7 +422,7 @@ static int soc_card_dai_links_create(struct snd_soc_card *card) sdw_be_num = ret; /* enable dmic */ - if (soc_sdw_quirk & ASOC_SDW_ACP_DMIC || mach_params->dmic_num) + if (soc_sdw_quirk & ASOC_SDW_ACP_DMIC) dmic_num = 1; dev_dbg(dev, "sdw %d, dmic %d", sdw_be_num, dmic_num); From f9e437cddf6cf9e603bdaefe148c1f4792aaf39c Mon Sep 17 00:00:00 2001 From: Denis Rastyogin Date: Fri, 27 Mar 2026 13:33:11 +0300 Subject: [PATCH 279/359] ASoC: rsnd: Fix potential out-of-bounds access of component_dais[] component_dais[RSND_MAX_COMPONENT] is initially zero-initialized and later populated in rsnd_dai_of_node(). However, the existing boundary check: if (i >= RSND_MAX_COMPONENT) does not guarantee that the last valid element remains zero. As a result, the loop can rely on component_dais[RSND_MAX_COMPONENT] being zero, which may lead to an out-of-bounds access. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 547b02f74e4a ("ASoC: rsnd: enable multi Component support for Audio Graph Card/Card2") Signed-off-by: Denis Rastyogin Acked-by: Kuninori Morimoto Link: https://patch.msgid.link/20260327103311.459239-1-gerben@altlinux.org Signed-off-by: Mark Brown --- sound/soc/renesas/rcar/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/renesas/rcar/core.c b/sound/soc/renesas/rcar/core.c index 69fb19964a71..2dc078358612 100644 --- a/sound/soc/renesas/rcar/core.c +++ b/sound/soc/renesas/rcar/core.c @@ -1974,7 +1974,7 @@ static int rsnd_probe(struct platform_device *pdev) * asoc register */ ci = 0; - for (i = 0; priv->component_dais[i] > 0; i++) { + for (i = 0; i < RSND_MAX_COMPONENT && priv->component_dais[i] > 0; i++) { int nr = priv->component_dais[i]; ret = devm_snd_soc_register_component(dev, &rsnd_soc_component, From 9557ec3f9c9fc9ca737f0bc963088193132fd967 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 27 Mar 2026 16:27:32 +0000 Subject: [PATCH 280/359] ASoC: SDCA: Add RJ support to class driver Add the retaskable jack Function to the list of Functions supported by the class driver, it shouldn't require anything that isn't already supported. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260327162732.877257-1-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_class_function.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index 98fd3fd1052b..c3eb3c23a7db 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -368,8 +368,14 @@ static int class_function_probe(struct auxiliary_device *auxdev, return dev_err_probe(dev, PTR_ERR(drv->regmap), "failed to create regmap"); - if (desc->type == SDCA_FUNCTION_TYPE_UAJ) + switch (desc->type) { + case SDCA_FUNCTION_TYPE_UAJ: + case SDCA_FUNCTION_TYPE_RJ: cmp_drv->set_jack = class_function_set_jack; + break; + default: + break; + } ret = sdca_asoc_populate_component(dev, drv->function, cmp_drv, &dais, &num_dais, @@ -539,6 +545,10 @@ static const struct auxiliary_device_id class_function_id_table[] = { .name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_HID_NAME, .driver_data = SDCA_FUNCTION_TYPE_HID, }, + { + .name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_RJ_NAME, + .driver_data = SDCA_FUNCTION_TYPE_RJ, + }, {}, }; MODULE_DEVICE_TABLE(auxiliary, class_function_id_table); From 04cc47624a33a6b56c62bfd65ad6b2d1c42e17c4 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 1 Apr 2026 19:50:40 -0700 Subject: [PATCH 281/359] ASoC: codecs: tlv320dac33: remove kmemdup_array Use a flexible array member and struct_size to use one allocation. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260402025040.93569-1-rosenp@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320dac33.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 605da1259fc6..223c49dfc450 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -91,7 +91,6 @@ struct tlv320dac33_priv { int mode1_latency; /* latency caused by the i2c writes in * us */ u8 burst_bclkdiv; /* BCLK divider value in burst mode */ - u8 *reg_cache; unsigned int burst_rate; /* Interface speed in Burst modes */ int keep_bclk; /* Keep the BCLK continuously running @@ -108,6 +107,8 @@ struct tlv320dac33_priv { enum dac33_state state; struct i2c_client *i2c; + + u8 reg_cache[]; }; static const u8 dac33_reg[DAC33_CACHEREGNUM] = { @@ -1477,15 +1478,12 @@ static int dac33_i2c_probe(struct i2c_client *client) struct tlv320dac33_priv *dac33; int ret, i; - dac33 = devm_kzalloc(&client->dev, sizeof(struct tlv320dac33_priv), + dac33 = devm_kzalloc(&client->dev, struct_size(dac33, reg_cache, ARRAY_SIZE(dac33_reg)), GFP_KERNEL); if (dac33 == NULL) return -ENOMEM; - dac33->reg_cache = devm_kmemdup_array(&client->dev, dac33_reg, ARRAY_SIZE(dac33_reg), - sizeof(dac33_reg[0]), GFP_KERNEL); - if (!dac33->reg_cache) - return -ENOMEM; + memcpy(dac33->reg_cache, dac33_reg, ARRAY_SIZE(dac33_reg)); dac33->i2c = client; mutex_init(&dac33->mutex); From ba2a0e81d4d71c3bbc61c420b6fac5abaeddd77d Mon Sep 17 00:00:00 2001 From: Niranjan H Y Date: Wed, 1 Apr 2026 18:51:45 +0530 Subject: [PATCH 282/359] ASoC: SDCA: Export Q7.8 volume control helpers Export the Q7.8 volume control helpers to allow reuse by other ASoC drivers. These functions handle 16-bit signed Q7.8 fixed-point format values for volume controls. Changes include: - Rename q78_get_volsw to sdca_asoc_q78_get_volsw - Rename q78_put_volsw to sdca_asoc_q78_put_volsw - Add a convenience macro SDCA_SINGLE_Q78_TLV and SDCA_DOUBLE_Q78_TLV for creating mixer controls This allows other ASoC drivers to easily implement controls using the Q7.8 fixed-point format without duplicating code. Signed-off-by: Niranjan H Y Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260401132148.2367-1-niranjan.hy@ti.com Signed-off-by: Mark Brown --- include/sound/sdca_asoc.h | 43 +++++++++++++++++++++++++++++++++++++- sound/soc/sdca/sdca_asoc.c | 14 +++++++------ 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/include/sound/sdca_asoc.h b/include/sound/sdca_asoc.h index aa9124f93218..46a61a52decc 100644 --- a/include/sound/sdca_asoc.h +++ b/include/sound/sdca_asoc.h @@ -13,6 +13,8 @@ struct device; struct regmap; struct sdca_function_data; +struct snd_ctl_elem_value; +struct snd_kcontrol; struct snd_kcontrol_new; struct snd_pcm_hw_params; struct snd_pcm_substream; @@ -23,6 +25,42 @@ struct snd_soc_dai_ops; struct snd_soc_dapm_route; struct snd_soc_dapm_widget; +/* convenient macro to handle the mono volume in 7.8 fixed format representation */ +#define SDCA_SINGLE_Q78_TLV(xname, xreg, xmin, xmax, xstep, tlv_array) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, \ + .get = sdca_asoc_q78_get_volsw, \ + .put = sdca_asoc_q78_put_volsw, \ + .private_value = (unsigned long)&(struct soc_mixer_control) { \ + .reg = (xreg), .rreg = (xreg), \ + .min = (xmin), .max = (xmax), \ + .shift = (xstep), .rshift = (xstep), \ + .sign_bit = 15 \ + } \ +} + +/* convenient macro for stereo volume in 7.8 fixed format with separate registers for L/R */ +#define SDCA_DOUBLE_Q78_TLV(xname, xreg_l, xreg_r, xmin, xmax, xstep, tlv_array) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, \ + .get = sdca_asoc_q78_get_volsw, \ + .put = sdca_asoc_q78_put_volsw, \ + .private_value = (unsigned long)&(struct soc_mixer_control) { \ + .reg = (xreg_l), .rreg = (xreg_r), \ + .min = (xmin), .max = (xmax), \ + .shift = (xstep), .rshift = (xstep), \ + .sign_bit = 15 \ + } \ +} + int sdca_asoc_count_component(struct device *dev, struct sdca_function_data *function, int *num_widgets, int *num_routes, int *num_controls, int *num_dais); @@ -57,5 +95,8 @@ int sdca_asoc_hw_params(struct device *dev, struct regmap *regmap, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai); - +int sdca_asoc_q78_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int sdca_asoc_q78_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); #endif // __SDCA_ASOC_H__ diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c index 7709a4ce26e0..2bfc8e5aee31 100644 --- a/sound/soc/sdca/sdca_asoc.c +++ b/sound/soc/sdca/sdca_asoc.c @@ -820,8 +820,8 @@ static int q78_write(struct snd_soc_component *component, return snd_soc_component_update_bits(component, reg, mask, reg_val); } -static int q78_put_volsw(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +int sdca_asoc_q78_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); @@ -841,6 +841,7 @@ static int q78_put_volsw(struct snd_kcontrol *kcontrol, return ret; } +EXPORT_SYMBOL_NS(sdca_asoc_q78_put_volsw, "SND_SOC_SDCA"); static int q78_read(struct snd_soc_component *component, struct soc_mixer_control *mc, unsigned int reg) @@ -855,8 +856,8 @@ static int q78_read(struct snd_soc_component *component, return val & GENMASK(mc->sign_bit, 0); } -static int q78_get_volsw(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +int sdca_asoc_q78_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); @@ -868,6 +869,7 @@ static int q78_get_volsw(struct snd_kcontrol *kcontrol, return 0; } +EXPORT_SYMBOL_NS(sdca_asoc_q78_get_volsw, "SND_SOC_SDCA"); static int control_limit_kctl(struct device *dev, struct sdca_entity *entity, @@ -912,8 +914,8 @@ static int control_limit_kctl(struct device *dev, kctl->tlv.p = tlv; kctl->access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; - kctl->get = q78_get_volsw; - kctl->put = q78_put_volsw; + kctl->get = sdca_asoc_q78_get_volsw; + kctl->put = sdca_asoc_q78_put_volsw; return 0; } From ccd7e53b7d6f06b0fef0f657c8cf377be2e8978d Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Thu, 26 Mar 2026 14:56:14 +0900 Subject: [PATCH 283/359] ASoC: imx-rpmsg: Add DSD format support with dynamic DAI format switching Add hw_params callback to dynamically switch DAI format between I2S and PDM based on audio stream format. When DSD formats are detected, the DAI format is switched to PDM mode. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260326055614.3614104-1-chancel.liu@nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/imx-rpmsg.c | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/sound/soc/fsl/imx-rpmsg.c b/sound/soc/fsl/imx-rpmsg.c index 76a8e68c1b62..40e0043cfe15 100644 --- a/sound/soc/fsl/imx-rpmsg.c +++ b/sound/soc/fsl/imx-rpmsg.c @@ -30,6 +30,53 @@ static const struct snd_soc_dapm_widget imx_rpmsg_dapm_widgets[] = { SND_SOC_DAPM_MIC("Main MIC", NULL), }; +static int imx_rpmsg_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); + snd_pcm_format_t format = params_format(params); + struct device *dev = rtd->card->dev; + unsigned int fmt = rtd->dai_link->dai_fmt; + bool format_is_dsd = false; + int ret; + + switch (format) { + case SNDRV_PCM_FORMAT_DSD_U8: + case SNDRV_PCM_FORMAT_DSD_U16_LE: + case SNDRV_PCM_FORMAT_DSD_U16_BE: + case SNDRV_PCM_FORMAT_DSD_U32_LE: + case SNDRV_PCM_FORMAT_DSD_U32_BE: + format_is_dsd = true; + break; + default: + format_is_dsd = false; + break; + } + + if (format_is_dsd) + fmt = (rtd->dai_link->dai_fmt & ~SND_SOC_DAIFMT_FORMAT_MASK) | + SND_SOC_DAIFMT_PDM; + + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret && ret != -ENOTSUPP) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret && ret != -ENOTSUPP) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops imx_rpmsg_ops = { + .hw_params = imx_rpmsg_hw_params, +}; + static int imx_rpmsg_late_probe(struct snd_soc_card *card) { struct imx_rpmsg *data = snd_soc_card_get_drvdata(card); @@ -135,6 +182,7 @@ static int imx_rpmsg_probe(struct platform_device *pdev) data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + data->dai.ops = &imx_rpmsg_ops; /* * i.MX rpmsg sound cards work on codec slave mode. MCLK will be From a02496a29463e7f0d1643e83aab28adb3dd03f1a Mon Sep 17 00:00:00 2001 From: Aravind Anilraj Date: Wed, 1 Apr 2026 18:05:04 -0400 Subject: [PATCH 284/359] ASoC: Intel: bytcr_rt5640: Fix MCLK leak on platform_clock_control error If byt_rt5640_prepare_and_enable_pll1() fails, the function returns without calling clk_disable_unprepare() on priv->mclk, which was already enabled earlier in the same code path. Add the missing cleanup call to prevent the clock from leaking. Signed-off-by: Aravind Anilraj Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260401220507.23557-2-aravindanilraj0702@gmail.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/bytcr_rt5640.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/intel/boards/bytcr_rt5640.c b/sound/soc/intel/boards/bytcr_rt5640.c index 103e0b445603..e4c21c9c5b38 100644 --- a/sound/soc/intel/boards/bytcr_rt5640.c +++ b/sound/soc/intel/boards/bytcr_rt5640.c @@ -289,6 +289,8 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, return ret; } ret = byt_rt5640_prepare_and_enable_pll1(codec_dai, 48000); + if (ret < 0) + clk_disable_unprepare(priv->mclk); } else { /* * Set codec clock source to internal clock before From b022e5c142efe4c5497e6cfda1f143618b4b9254 Mon Sep 17 00:00:00 2001 From: Aravind Anilraj Date: Wed, 1 Apr 2026 18:05:05 -0400 Subject: [PATCH 285/359] ASoC: Intel: bytcr_rt5651: Fix MCLK leak on platform_clock_control error If byt_rt5651_prepare_and_enable_pll1() fails, the function returns without calling clk_disable_unprepare() on priv->mclk, which was already enabled earlier in the same code path. Add the missing cleanup call to prevent the clock from leaking. Signed-off-by: Aravind Anilraj Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260401220507.23557-3-aravindanilraj0702@gmail.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/bytcr_rt5651.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c index 68cf463f1d50..8932fc5d6f4f 100644 --- a/sound/soc/intel/boards/bytcr_rt5651.c +++ b/sound/soc/intel/boards/bytcr_rt5651.c @@ -209,6 +209,8 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, return ret; } ret = byt_rt5651_prepare_and_enable_pll1(codec_dai, 48000, 50); + if (ret < 0) + clk_disable_unprepare(priv->mclk); } else { /* * Set codec clock source to internal clock before From dced5a373a96cfd9f3bd0ffcf5339a7579d1473a Mon Sep 17 00:00:00 2001 From: Aravind Anilraj Date: Wed, 1 Apr 2026 18:05:06 -0400 Subject: [PATCH 286/359] ASoC: Intel: cht_bsw_rt5672: Fix MCLK leak on platform_clock_control error If snd_soc_dai_set_pll() or snd_soc_dai_set_sysclk() fail inside the EVENT_ON path, the function returns without calling clk_disable_unprepare() on ctx->mclk, which was already enabled earlier in the same code path. Add the missing clk_disable_unprepare() calls before returning the error. Signed-off-by: Aravind Anilraj Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260401220507.23557-4-aravindanilraj0702@gmail.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/cht_bsw_rt5672.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/soc/intel/boards/cht_bsw_rt5672.c b/sound/soc/intel/boards/cht_bsw_rt5672.c index 359723f2700e..57d6997eb12f 100644 --- a/sound/soc/intel/boards/cht_bsw_rt5672.c +++ b/sound/soc/intel/boards/cht_bsw_rt5672.c @@ -77,6 +77,8 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, CHT_PLAT_CLK_3_HZ, 48000 * 512); if (ret < 0) { dev_err(card->dev, "can't set codec pll: %d\n", ret); + if (ctx->mclk) + clk_disable_unprepare(ctx->mclk); return ret; } @@ -85,6 +87,8 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, 48000 * 512, SND_SOC_CLOCK_IN); if (ret < 0) { dev_err(card->dev, "can't set codec sysclk: %d\n", ret); + if (ctx->mclk) + clk_disable_unprepare(ctx->mclk); return ret; } } else { From 85c38c2fa17da23b2258edd96c89e63f92426ba5 Mon Sep 17 00:00:00 2001 From: Aravind Anilraj Date: Wed, 1 Apr 2026 18:05:07 -0400 Subject: [PATCH 287/359] ASoC: Intel: Standardize MCLK error logs across RT boards Standardize the error logging in platform_clock_control() by adding missing newline characters to dev_err() strings. Additionally, include the return code in the error messages to assist with debugging. Signed-off-by: Aravind Anilraj Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260401220507.23557-5-aravindanilraj0702@gmail.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/bytcr_rt5640.c | 2 +- sound/soc/intel/boards/bytcr_rt5651.c | 2 +- sound/soc/intel/boards/cht_bsw_rt5672.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/intel/boards/bytcr_rt5640.c b/sound/soc/intel/boards/bytcr_rt5640.c index e4c21c9c5b38..40da3eea5fa7 100644 --- a/sound/soc/intel/boards/bytcr_rt5640.c +++ b/sound/soc/intel/boards/bytcr_rt5640.c @@ -285,7 +285,7 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, if (SND_SOC_DAPM_EVENT_ON(event)) { ret = clk_prepare_enable(priv->mclk); if (ret < 0) { - dev_err(card->dev, "could not configure MCLK state\n"); + dev_err(card->dev, "could not configure MCLK state: %d\n", ret); return ret; } ret = byt_rt5640_prepare_and_enable_pll1(codec_dai, 48000); diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c index 8932fc5d6f4f..62cb4856c797 100644 --- a/sound/soc/intel/boards/bytcr_rt5651.c +++ b/sound/soc/intel/boards/bytcr_rt5651.c @@ -205,7 +205,7 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, if (SND_SOC_DAPM_EVENT_ON(event)) { ret = clk_prepare_enable(priv->mclk); if (ret < 0) { - dev_err(card->dev, "could not configure MCLK state"); + dev_err(card->dev, "could not configure MCLK state: %d\n", ret); return ret; } ret = byt_rt5651_prepare_and_enable_pll1(codec_dai, 48000, 50); diff --git a/sound/soc/intel/boards/cht_bsw_rt5672.c b/sound/soc/intel/boards/cht_bsw_rt5672.c index 57d6997eb12f..fd4cefd298d2 100644 --- a/sound/soc/intel/boards/cht_bsw_rt5672.c +++ b/sound/soc/intel/boards/cht_bsw_rt5672.c @@ -67,7 +67,7 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, ret = clk_prepare_enable(ctx->mclk); if (ret < 0) { dev_err(card->dev, - "could not configure MCLK state"); + "could not configure MCLK state: %d\n", ret); return ret; } } From 9551af27f8167bbb5f862a12f7f9bc5830e8f4e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Fri, 3 Apr 2026 00:47:13 -0300 Subject: [PATCH 288/359] ALSA: aoa: onyx: Update IEC958 sample-rate status for PCM playback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit onyx_prepare() accepts 32/44.1/48 kHz PCM playback, but it leaves the Onyx IEC958 sample-rate status bits at the driver's initial 44.1 kHz setting in DIG_INFO3. As a result, 32 kHz and 48 kHz PCM streams advertise a stale IEC958 sample rate unless userspace rewrites IEC958 Playback Default first. Update only the consumer sample-frequency bits in DIG_INFO3 from the PCM runtime during prepare, resolving the long-standing FIXME in the PCM playback path while leaving the other user-controlled IEC958 status bits unchanged. Mark IEC958 Playback Default as volatile as well, since prepare() now changes the exposed register contents outside the control put callback. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260403-onyx-spdif-pcm-rate-v1-1-dcfaf931cf83@gmail.com Signed-off-by: Takashi Iwai --- sound/aoa/codecs/onyx.c | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/sound/aoa/codecs/onyx.c b/sound/aoa/codecs/onyx.c index da0eebf5dfbc..4fb593e88fb1 100644 --- a/sound/aoa/codecs/onyx.c +++ b/sound/aoa/codecs/onyx.c @@ -32,6 +32,7 @@ #include #include #include +#include MODULE_AUTHOR("Johannes Berg "); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("pcm3052 (onyx) codec driver for snd-aoa"); @@ -514,8 +515,36 @@ static int onyx_spdif_put(struct snd_kcontrol *kcontrol, return 1; } +static int onyx_set_spdif_pcm_rate(struct onyx *onyx, unsigned int rate) +{ + u8 dig_info3, fs; + + switch (rate) { + case 32000: + fs = IEC958_AES3_CON_FS_32000; + break; + case 44100: + fs = IEC958_AES3_CON_FS_44100; + break; + case 48000: + fs = IEC958_AES3_CON_FS_48000; + break; + default: + return -EINVAL; + } + + if (onyx_read_register(onyx, ONYX_REG_DIG_INFO3, &dig_info3)) + return -EBUSY; + dig_info3 = (dig_info3 & ~IEC958_AES3_CON_FS) | fs; + if (onyx_write_register(onyx, ONYX_REG_DIG_INFO3, dig_info3)) + return -EBUSY; + + return 0; +} + static const struct snd_kcontrol_new onyx_spdif_ctrl = { - .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), .info = onyx_spdif_info, @@ -695,9 +724,9 @@ static int onyx_prepare(struct codec_info_item *cii, case 32000: case 44100: case 48000: - /* these rates are ok for all outputs */ - /* FIXME: program spdif channel control bits here so that - * userspace doesn't have to if it only plays pcm! */ + if (onyx->codec.connected & 2) + return onyx_set_spdif_pcm_rate(onyx, + substream->runtime->rate); return 0; default: /* got some rate that the digital output can't do, From 7b5b7d04498d84bc7abc05b4e09a0d17c820d3b0 Mon Sep 17 00:00:00 2001 From: songxiebing Date: Sun, 5 Apr 2026 09:42:08 +0800 Subject: [PATCH 289/359] ALSA: hda/realtek: Fix code style error Output of checkpatch shows error: ERROR: else should follow close brace '}' 2168: FILE: sound/hda/codecs/realtek/realtek.c:2168: + } + else So fix it. Signed-off-by: songxiebing Link: https://patch.msgid.link/20260405014208.167364-1-songxiebing@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/realtek.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/hda/codecs/realtek/realtek.c b/sound/hda/codecs/realtek/realtek.c index aad265c0a5b6..b240f13b0438 100644 --- a/sound/hda/codecs/realtek/realtek.c +++ b/sound/hda/codecs/realtek/realtek.c @@ -2164,8 +2164,7 @@ void alc_fixup_headset_mode_no_hp_mic(struct hda_codec *codec, if (action == HDA_FIXUP_ACT_PRE_PROBE) { struct alc_spec *spec = codec->spec; spec->parse_flags |= HDA_PINCFG_HEADSET_MIC; - } - else + } else alc_fixup_headset_mode(codec, fix, action); } EXPORT_SYMBOL_NS_GPL(alc_fixup_headset_mode_no_hp_mic, "SND_HDA_CODEC_REALTEK"); From 2428cd6e8b6fa80c36db4652702ca0acd2ce3f08 Mon Sep 17 00:00:00 2001 From: Panagiotis Petrakopoulos Date: Mon, 6 Apr 2026 01:25:48 +0300 Subject: [PATCH 290/359] ALSA: scarlett2: Add missing sentinel initializer field A "-Wmissing-field-initializers" warning was emitted when compiling the module using the W=2 option. There is a sentinel initializer field missing in the end of scarlett2_devices[]. Tested using a Scarlett Solo 4th gen. Fixes: d98cc489029d ("ALSA: scarlett2: Move USB IDs out from device_info struct") Signed-off-by: Panagiotis Petrakopoulos Link: https://patch.msgid.link/20260405222548.8903-1-npetrakopoulos2003@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/mixer_scarlett2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index fd1fb668929a..8eaa96222759 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -2262,7 +2262,7 @@ static const struct scarlett2_device_entry scarlett2_devices[] = { { USB_ID(0x1235, 0x820c), &clarett_8pre_info, "Clarett+" }, /* End of list */ - { 0, NULL }, + { 0, NULL, NULL }, }; /* get the starting port index number for a given port type/direction */ From e9418da50d9e5c496c22fe392e4ad74c038a94eb Mon Sep 17 00:00:00 2001 From: Harin Lee Date: Mon, 6 Apr 2026 16:48:57 +0900 Subject: [PATCH 291/359] ALSA: ctxfi: Limit PTP to a single page Commit 391e69143d0a increased CT_PTP_NUM from 1 to 4 to support 256 playback streams, but the additional pages are not used by the card correctly. The CT20K2 hardware already has multiple VMEM_PTPAL registers, but using them separately would require refactoring the entire virtual memory allocation logic. ct_vm_map() always uses PTEs in vm->ptp[0].area regardless of CT_PTP_NUM. On AMD64 systems, a single PTP covers 512 PTEs (2M). When aggregate memory allocations exceed this limit, ct_vm_map() tries to access beyond the allocated space and causes a page fault: BUG: unable to handle page fault for address: ffffd4ae8a10a000 Oops: Oops: 0002 [#1] SMP PTI RIP: 0010:ct_vm_map+0x17c/0x280 [snd_ctxfi] Call Trace: atc_pcm_playback_prepare+0x225/0x3b0 ct_pcm_playback_prepare+0x38/0x60 snd_pcm_do_prepare+0x2f/0x50 snd_pcm_action_single+0x36/0x90 snd_pcm_action_nonatomic+0xbf/0xd0 snd_pcm_ioctl+0x28/0x40 __x64_sys_ioctl+0x97/0xe0 do_syscall_64+0x81/0x610 entry_SYSCALL_64_after_hwframe+0x76/0x7e Revert CT_PTP_NUM to 1. The 256 SRC_RESOURCE_NUM and playback_count remain unchanged. Fixes: 391e69143d0a ("ALSA: ctxfi: Bump playback substreams to 256") Cc: stable@vger.kernel.org Signed-off-by: Harin Lee Link: https://patch.msgid.link/20260406074857.216034-1-me@harin.net Signed-off-by: Takashi Iwai --- sound/pci/ctxfi/ctvmem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/ctxfi/ctvmem.h b/sound/pci/ctxfi/ctvmem.h index da54cbcdb0be..43a0065b40c3 100644 --- a/sound/pci/ctxfi/ctvmem.h +++ b/sound/pci/ctxfi/ctvmem.h @@ -15,7 +15,7 @@ #ifndef CTVMEM_H #define CTVMEM_H -#define CT_PTP_NUM 4 /* num of device page table pages */ +#define CT_PTP_NUM 1 /* num of device page table pages */ #include #include From 7d61662197ecdc458e33e475b6ada7f6da61d364 Mon Sep 17 00:00:00 2001 From: Harin Lee Date: Mon, 6 Apr 2026 16:49:13 +0900 Subject: [PATCH 292/359] ALSA: ctxfi: Add fallback to default RSR for S/PDIF spdif_passthru_playback_get_resources() uses atc->pll_rate as the RSR for the MSR calculation loop. However, pll_rate is only updated in atc_pll_init() and not in hw_pll_init(), so it remains 0 after the card init. When spdif_passthru_playback_setup() skips atc_pll_init() for 32000 Hz, (rsr * desc.msr) always becomes 0, causing the loop to spin indefinitely. Add fallback to use atc->rsr when atc->pll_rate is 0. This reflects the hardware state, since hw_card_init() already configures the PLL to the default RSR. Fixes: 8cc72361481f ("ALSA: SB X-Fi driver merge") Cc: stable@vger.kernel.org Signed-off-by: Harin Lee Link: https://patch.msgid.link/20260406074913.217374-1-me@harin.net Signed-off-by: Takashi Iwai --- sound/pci/ctxfi/ctatc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/pci/ctxfi/ctatc.c b/sound/pci/ctxfi/ctatc.c index 516c0a12ed9f..02fe09330939 100644 --- a/sound/pci/ctxfi/ctatc.c +++ b/sound/pci/ctxfi/ctatc.c @@ -794,7 +794,8 @@ static int spdif_passthru_playback_get_resources(struct ct_atc *atc, struct src *src; int err; int n_amixer = apcm->substream->runtime->channels, i; - unsigned int pitch, rsr = atc->pll_rate; + unsigned int pitch; + unsigned int rsr = atc->pll_rate ? atc->pll_rate : atc->rsr; /* first release old resources */ atc_pcm_release_resources(atc, apcm); From d38e9457ddf0780dd55c953886ae48abbe4d33b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Mon, 6 Apr 2026 00:20:03 -0300 Subject: [PATCH 293/359] ALSA: gus: add shared GF1 suspend and resume helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gusclassic and gusextreme still leave their ISA PM callbacks disabled because the shared GF1 core only provides probe-time startup and full shutdown paths. Those helpers are not suitable for suspend and resume. They reset software handlers and tear down runtime state such as the DRAM allocator, timer state, DMA queues, PCM state and UART setup. Resume instead needs a narrower recovery path that rebuilds the GF1 hardware state without rerunning probe-only detection or discarding the bookkeeping kept by the card instance. Add shared GF1 suspend and resume helpers for that recovery path. Suspend now quiesces GF1 PCM, aborts queued GF1 DMA work, resets the UART and powers the chip down without tearing down allocator, timer or rawmidi bookkeeping. Resume rebuilds the GF1 hardware state, restores timer and UART handlers, and brings the chip back to a usable post-resume state for the ISA front-ends. The scope is limited to restoring post-resume usability. It does not attempt transparent continuation of active GF1 PCM or synth state across suspend, and userspace may still need to reprepare streams or reload onboard sample data after resume. Open rawmidi substreams are restored only to a usable post-resume state. Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260406-b4-alsa-gus-isa-pm-v1-1-b6829a7457cd@gmail.com --- include/sound/gus.h | 8 +++++ sound/isa/gus/gus_dma.c | 33 +++++++++++++++++++++ sound/isa/gus/gus_main.c | 36 +++++++++++++++++++++++ sound/isa/gus/gus_pcm.c | 7 +++-- sound/isa/gus/gus_reset.c | 62 ++++++++++++++++++++++++++++++++------- sound/isa/gus/gus_timer.c | 14 +++++++++ sound/isa/gus/gus_uart.c | 47 +++++++++++++++++++++++++++++ 7 files changed, 194 insertions(+), 13 deletions(-) diff --git a/include/sound/gus.h b/include/sound/gus.h index 321ae93625eb..3feb42627de1 100644 --- a/include/sound/gus.h +++ b/include/sound/gus.h @@ -536,6 +536,7 @@ int snd_gf1_dma_transfer_block(struct snd_gus_card * gus, struct snd_gf1_dma_block * block, int atomic, int synth); +void snd_gf1_dma_suspend(struct snd_gus_card *gus); /* gus_volume.c */ @@ -552,6 +553,8 @@ struct snd_gus_voice *snd_gf1_alloc_voice(struct snd_gus_card * gus, int type, i void snd_gf1_free_voice(struct snd_gus_card * gus, struct snd_gus_voice *voice); int snd_gf1_start(struct snd_gus_card * gus); int snd_gf1_stop(struct snd_gus_card * gus); +int snd_gf1_suspend(struct snd_gus_card *gus); +int snd_gf1_resume(struct snd_gus_card *gus); /* gus_mixer.c */ @@ -572,6 +575,8 @@ int snd_gus_create(struct snd_card *card, int effect, struct snd_gus_card ** rgus); int snd_gus_initialize(struct snd_gus_card * gus); +int snd_gus_suspend(struct snd_gus_card *gus); +int snd_gus_resume(struct snd_gus_card *gus); /* gus_irq.c */ @@ -583,6 +588,8 @@ void snd_gus_irq_profile_init(struct snd_gus_card *gus); /* gus_uart.c */ int snd_gf1_rawmidi_new(struct snd_gus_card *gus, int device); +void snd_gf1_uart_suspend(struct snd_gus_card *gus); +void snd_gf1_uart_resume(struct snd_gus_card *gus); /* gus_dram.c */ int snd_gus_dram_write(struct snd_gus_card *gus, char __user *ptr, @@ -593,5 +600,6 @@ int snd_gus_dram_read(struct snd_gus_card *gus, char __user *ptr, /* gus_timer.c */ void snd_gf1_timers_init(struct snd_gus_card *gus); void snd_gf1_timers_done(struct snd_gus_card *gus); +void snd_gf1_timers_resume(struct snd_gus_card *gus); #endif /* __SOUND_GUS_H */ diff --git a/sound/isa/gus/gus_dma.c b/sound/isa/gus/gus_dma.c index ffc69e26227e..30bd76eee96e 100644 --- a/sound/isa/gus/gus_dma.c +++ b/sound/isa/gus/gus_dma.c @@ -173,6 +173,39 @@ int snd_gf1_dma_done(struct snd_gus_card * gus) return 0; } +void snd_gf1_dma_suspend(struct snd_gus_card *gus) +{ + struct snd_gf1_dma_block *block; + + guard(mutex)(&gus->dma_mutex); + if (!gus->gf1.dma_shared) + return; + + snd_dma_disable(gus->gf1.dma1); + snd_gf1_dma_ack(gus); + if (gus->gf1.dma_ack) + gus->gf1.dma_ack(gus, gus->gf1.dma_private_data); + gus->gf1.dma_ack = NULL; + gus->gf1.dma_private_data = NULL; + + while ((block = gus->gf1.dma_data_pcm)) { + gus->gf1.dma_data_pcm = block->next; + if (block->ack) + block->ack(gus, block->private_data); + kfree(block); + } + while ((block = gus->gf1.dma_data_synth)) { + gus->gf1.dma_data_synth = block->next; + if (block->ack) + block->ack(gus, block->private_data); + kfree(block); + } + + gus->gf1.dma_data_pcm_last = NULL; + gus->gf1.dma_data_synth_last = NULL; + gus->gf1.dma_flags &= ~SNDRV_GF1_DMA_TRIGGER; +} + int snd_gf1_dma_transfer_block(struct snd_gus_card * gus, struct snd_gf1_dma_block * __block, int atomic, diff --git a/sound/isa/gus/gus_main.c b/sound/isa/gus/gus_main.c index b2b189c83569..6adf8b698e2b 100644 --- a/sound/isa/gus/gus_main.c +++ b/sound/isa/gus/gus_main.c @@ -404,6 +404,42 @@ int snd_gus_initialize(struct snd_gus_card *gus) return 0; } +int snd_gus_suspend(struct snd_gus_card *gus) +{ + int err; + + if (gus->pcm) { + err = snd_pcm_suspend_all(gus->pcm); + if (err < 0) + return err; + } + + err = snd_gf1_suspend(gus); + if (err < 0) + return err; + + snd_power_change_state(gus->card, SNDRV_CTL_POWER_D3hot); + return 0; +} +EXPORT_SYMBOL(snd_gus_suspend); + +int snd_gus_resume(struct snd_gus_card *gus) +{ + int err; + + err = snd_gus_init_dma_irq(gus, 1); + if (err < 0) + return err; + + err = snd_gf1_resume(gus); + if (err < 0) + return err; + + snd_power_change_state(gus->card, SNDRV_CTL_POWER_D0); + return 0; +} +EXPORT_SYMBOL(snd_gus_resume); + /* gus_io.c */ EXPORT_SYMBOL(snd_gf1_delay); EXPORT_SYMBOL(snd_gf1_write8); diff --git a/sound/isa/gus/gus_pcm.c b/sound/isa/gus/gus_pcm.c index caf371897b78..a0757e1ede46 100644 --- a/sound/isa/gus/gus_pcm.c +++ b/sound/isa/gus/gus_pcm.c @@ -471,7 +471,8 @@ static int snd_gf1_pcm_playback_trigger(struct snd_pcm_substream *substream, if (cmd == SNDRV_PCM_TRIGGER_START) { snd_gf1_pcm_trigger_up(substream); - } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + } else if (cmd == SNDRV_PCM_TRIGGER_STOP || + cmd == SNDRV_PCM_TRIGGER_SUSPEND) { scoped_guard(spinlock, &pcmp->lock) { pcmp->flags &= ~SNDRV_GF1_PCM_PFLG_ACTIVE; } @@ -558,7 +559,8 @@ static int snd_gf1_pcm_capture_trigger(struct snd_pcm_substream *substream, if (cmd == SNDRV_PCM_TRIGGER_START) { val = gus->gf1.pcm_rcntrl_reg; - } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + } else if (cmd == SNDRV_PCM_TRIGGER_STOP || + cmd == SNDRV_PCM_TRIGGER_SUSPEND) { val = 0; } else { return -EINVAL; @@ -856,4 +858,3 @@ int snd_gf1_pcm_new(struct snd_gus_card *gus, int pcm_dev, int control_index) return 0; } - diff --git a/sound/isa/gus/gus_reset.c b/sound/isa/gus/gus_reset.c index a7a3e764bb77..998fa245708c 100644 --- a/sound/isa/gus/gus_reset.c +++ b/sound/isa/gus/gus_reset.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -263,11 +264,18 @@ void snd_gf1_free_voice(struct snd_gus_card * gus, struct snd_gus_voice *voice) private_free(voice); } -/* - * call this function only by start of driver - */ +static void snd_gf1_init_software_state(struct snd_gus_card *gus) +{ + unsigned int i; -int snd_gf1_start(struct snd_gus_card * gus) + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_ALL); + for (i = 0; i < 32; i++) { + gus->gf1.voices[i].number = i; + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | i); + } +} + +static void snd_gf1_hw_start(struct snd_gus_card *gus, bool initial) { unsigned int i; @@ -277,14 +285,14 @@ int snd_gf1_start(struct snd_gus_card * gus) udelay(160); snd_gf1_i_write8(gus, SNDRV_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac); - snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_ALL); - for (i = 0; i < 32; i++) { - gus->gf1.voices[i].number = i; - snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | i); + if (initial) { + snd_gf1_init_software_state(gus); + snd_gf1_uart_cmd(gus, 0x03); + } else { + guard(spinlock_irqsave)(&gus->uart_cmd_lock); + outb(0x03, GUSP(gus, MIDICTRL)); } - snd_gf1_uart_cmd(gus, 0x03); /* huh.. this cleanup took me some time... */ - if (gus->gf1.enh_mode) { /* enhanced mode !!!! */ snd_gf1_i_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_i_look8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01); snd_gf1_i_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01); @@ -293,6 +301,8 @@ int snd_gf1_start(struct snd_gus_card * gus) snd_gf1_select_active_voices(gus); snd_gf1_delay(gus); gus->gf1.default_voice_address = gus->gf1.memory > 0 ? 0 : 512 - 8; + gus->gf1.hw_lfo = 0; + gus->gf1.sw_lfo = 0; /* initialize LFOs & clear LFOs memory */ if (gus->gf1.enh_mode && gus->gf1.memory) { gus->gf1.hw_lfo = 1; @@ -321,7 +331,15 @@ int snd_gf1_start(struct snd_gus_card * gus) outb(gus->gf1.active_voice = 0, GUSP(gus, GF1PAGE)); outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG)); } +} +int snd_gf1_start(struct snd_gus_card *gus) +{ + /* + * Probe-time startup initializes both GF1 hardware and the + * software state that suspend/resume keeps across PM cycles. + */ + snd_gf1_hw_start(gus, true); snd_gf1_timers_init(gus); snd_gf1_look_regs(gus); snd_gf1_mem_init(gus); @@ -357,3 +375,27 @@ int snd_gf1_stop(struct snd_gus_card * gus) return 0; } + +int snd_gf1_suspend(struct snd_gus_card *gus) +{ + snd_gf1_dma_suspend(gus); + snd_gf1_uart_suspend(gus); + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, 0); + snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL); + snd_gf1_stop_voices(gus, 0, 31); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); + snd_dma_disable(gus->gf1.dma2); + + return 0; +} + +int snd_gf1_resume(struct snd_gus_card *gus) +{ + snd_gf1_hw_start(gus, false); + snd_gf1_timers_resume(gus); + snd_gf1_uart_resume(gus); + + return 0; +} diff --git a/sound/isa/gus/gus_timer.c b/sound/isa/gus/gus_timer.c index e3a8847e02cf..14dcde138bc7 100644 --- a/sound/isa/gus/gus_timer.c +++ b/sound/isa/gus/gus_timer.c @@ -178,3 +178,17 @@ void snd_gf1_timers_done(struct snd_gus_card * gus) gus->gf1.timer2 = NULL; } } + +void snd_gf1_timers_resume(struct snd_gus_card *gus) +{ + if (gus->gf1.timer1) { + gus->gf1.interrupt_handler_timer1 = snd_gf1_interrupt_timer1; + if (gus->gf1.timer_enabled & 4) + snd_gf1_timer1_start(gus->gf1.timer1); + } + if (gus->gf1.timer2) { + gus->gf1.interrupt_handler_timer2 = snd_gf1_interrupt_timer2; + if (gus->gf1.timer_enabled & 8) + snd_gf1_timer2_start(gus->gf1.timer2); + } +} diff --git a/sound/isa/gus/gus_uart.c b/sound/isa/gus/gus_uart.c index 770d8f3e4cff..25057a5a81b0 100644 --- a/sound/isa/gus/gus_uart.c +++ b/sound/isa/gus/gus_uart.c @@ -232,3 +232,50 @@ int snd_gf1_rawmidi_new(struct snd_gus_card *gus, int device) gus->midi_uart = rmidi; return err; } + +void snd_gf1_uart_suspend(struct snd_gus_card *gus) +{ + guard(spinlock_irqsave)(&gus->uart_cmd_lock); + outb(0x03, GUSP(gus, MIDICTRL)); +} + +void snd_gf1_uart_resume(struct snd_gus_card *gus) +{ + unsigned short uart_cmd; + bool active; + int i; + + scoped_guard(spinlock_irqsave, &gus->uart_cmd_lock) { + active = gus->midi_substream_input || gus->midi_substream_output; + } + if (!active) + return; + + /* snd_gf1_hw_start() already left MIDICTRL in reset. */ + usleep_range(160, 200); + + guard(spinlock_irqsave)(&gus->uart_cmd_lock); + if (!gus->midi_substream_input && !gus->midi_substream_output) + return; + + if (gus->midi_substream_output) + gus->gf1.interrupt_handler_midi_out = snd_gf1_interrupt_midi_out; + if (gus->midi_substream_input) + gus->gf1.interrupt_handler_midi_in = snd_gf1_interrupt_midi_in; + + if (!gus->uart_enable) + return; + + uart_cmd = gus->gf1.uart_cmd; + snd_gf1_uart_cmd(gus, 0x00); + + if (gus->midi_substream_input) { + for (i = 0; i < 1000 && (snd_gf1_uart_stat(gus) & 0x01); i++) + snd_gf1_uart_get(gus); + if (i >= 1000) + dev_err(gus->card->dev, + "gus midi uart resume - cleanup error\n"); + } + + snd_gf1_uart_cmd(gus, uart_cmd); +} From d9bfa935a9855664f5cea12131fa809fd56ff82c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Mon, 6 Apr 2026 00:20:04 -0300 Subject: [PATCH 294/359] ALSA: gusclassic: add ISA suspend and resume callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gusclassic still leaves its ISA PM callbacks disabled because the shared GF1 core had no suspend and resume path suitable for PM recovery. Wire the driver up to the new shared GUS suspend and resume helpers so a suspend/resume cycle restores usable GF1 operation without rerunning probe-only detection or tearing down the runtime bookkeeping kept by the card instance. Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260406-b4-alsa-gus-isa-pm-v1-2-b6829a7457cd@gmail.com --- sound/isa/gus/gusclassic.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/sound/isa/gus/gusclassic.c b/sound/isa/gus/gusclassic.c index 101202acefb3..363c819ced89 100644 --- a/sound/isa/gus/gusclassic.c +++ b/sound/isa/gus/gusclassic.c @@ -145,6 +145,7 @@ static int snd_gusclassic_probe(struct device *dev, unsigned int n) error = snd_gusclassic_create(card, dev, n, &gus); if (error < 0) return error; + card->private_data = gus; error = snd_gusclassic_detect(gus); if (error < 0) @@ -193,11 +194,29 @@ static int snd_gusclassic_probe(struct device *dev, unsigned int n) return 0; } +#ifdef CONFIG_PM +static int snd_gusclassic_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + struct snd_card *card = dev_get_drvdata(dev); + + return snd_gus_suspend(card->private_data); +} + +static int snd_gusclassic_resume(struct device *dev, unsigned int n) +{ + struct snd_card *card = dev_get_drvdata(dev); + + return snd_gus_resume(card->private_data); +} +#endif + static struct isa_driver snd_gusclassic_driver = { .match = snd_gusclassic_match, .probe = snd_gusclassic_probe, -#if 0 /* FIXME */ +#ifdef CONFIG_PM .suspend = snd_gusclassic_suspend, + .resume = snd_gusclassic_resume, #endif .driver = { .name = DEV_NAME From 7da8af2541d01ce1a7ad5efbb3fee8567fdfc959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Mon, 6 Apr 2026 00:20:05 -0300 Subject: [PATCH 295/359] ALSA: gusextreme: add ISA suspend and resume callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gusextreme still leaves its ISA PM callbacks disabled because the shared GF1 core had no suspend and resume path suitable for PM recovery. Resume on this board needs one extra step before the shared GF1 path can touch the chip again: the ES1688 side must restore the GF1 routing. Split that routing sequence into a helper, reuse it for probe and resume, reset the ES1688 side first on resume, and then wire the driver up to the shared GUS PM helpers. This restores usable post-resume GF1 operation on GUS Extreme without rerunning probe-only detection in the shared GF1 path. Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260406-b4-alsa-gus-isa-pm-v1-3-b6829a7457cd@gmail.com --- sound/isa/gus/gusextreme.c | 57 ++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/sound/isa/gus/gusextreme.c b/sound/isa/gus/gusextreme.c index ed921b89b00a..0984731740c4 100644 --- a/sound/isa/gus/gusextreme.c +++ b/sound/isa/gus/gusextreme.c @@ -44,6 +44,11 @@ static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29}; static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24}; static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; +struct snd_gusextreme { + struct snd_es1688 es1688; + struct snd_gus_card *gus; +}; + module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard."); module_param_array(id, charp, NULL, 0444); @@ -142,17 +147,15 @@ static int snd_gusextreme_gus_card_create(struct snd_card *card, 0, channels[n], pcm_channels[n], 0, rgus); } -static int snd_gusextreme_detect(struct snd_gus_card *gus, - struct snd_es1688 *es1688) +static void snd_gusextreme_enable_gf1(struct snd_gus_card *gus, + struct snd_es1688 *es1688) { - unsigned char d; - /* * This is main stuff - enable access to GF1 chip... * I'm not sure, if this will work for card which have * ES1688 chip in another place than 0x220. - * - * I used reverse-engineering in DOSEMU. [--jk] + * + * I used reverse-engineering in DOSEMU. [--jk] * * ULTRINIT.EXE: * 0x230 = 0,2,3 @@ -172,7 +175,14 @@ static int snd_gusextreme_detect(struct snd_gus_card *gus, outb(0, 0x201); outb(gus->gf1.port & 0x010 ? 3 : 1, ES1688P(es1688, INIT1)); } +} +static int snd_gusextreme_detect(struct snd_gus_card *gus, + struct snd_es1688 *es1688) +{ + unsigned char d; + + snd_gusextreme_enable_gf1(gus, es1688); udelay(100); snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */ @@ -223,16 +233,18 @@ static int snd_gusextreme_probe(struct device *dev, unsigned int n) { struct snd_card *card; struct snd_gus_card *gus; + struct snd_gusextreme *gusextreme; struct snd_es1688 *es1688; struct snd_opl3 *opl3; int error; error = snd_devm_card_new(dev, index[n], id[n], THIS_MODULE, - sizeof(struct snd_es1688), &card); + sizeof(*gusextreme), &card); if (error < 0) return error; - es1688 = card->private_data; + gusextreme = card->private_data; + es1688 = &gusextreme->es1688; if (mpu_port[n] == SNDRV_AUTO_PORT) mpu_port[n] = 0; @@ -250,6 +262,7 @@ static int snd_gusextreme_probe(struct device *dev, unsigned int n) error = snd_gusextreme_gus_card_create(card, dev, n, &gus); if (error < 0) return error; + gusextreme->gus = gus; error = snd_gusextreme_detect(gus, es1688); if (error < 0) @@ -321,10 +334,36 @@ static int snd_gusextreme_probe(struct device *dev, unsigned int n) return 0; } +#ifdef CONFIG_PM +static int snd_gusextreme_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_gusextreme *gusextreme = card->private_data; + + return snd_gus_suspend(gusextreme->gus); +} + +static int snd_gusextreme_resume(struct device *dev, unsigned int n) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_gusextreme *gusextreme = card->private_data; + int err; + + err = snd_es1688_reset(&gusextreme->es1688); + if (err < 0) + return err; + + snd_gusextreme_enable_gf1(gusextreme->gus, &gusextreme->es1688); + usleep_range(100, 200); + return snd_gus_resume(gusextreme->gus); +} +#endif + static struct isa_driver snd_gusextreme_driver = { .match = snd_gusextreme_match, .probe = snd_gusextreme_probe, -#if 0 /* FIXME */ +#ifdef CONFIG_PM .suspend = snd_gusextreme_suspend, .resume = snd_gusextreme_resume, #endif From 1b64e52380abfd368baa8a53d73336ffcd52c0c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Mon, 6 Apr 2026 00:20:06 -0300 Subject: [PATCH 296/359] ALSA: gusmax: add ISA suspend and resume callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gusmax still leaves its ISA PM callbacks disabled even though the shared GF1 suspend and resume path now exists. This board needs one extra piece of PM glue around the shared GF1 helpers. The attached WSS codec has its own register image that must be saved and restored across suspend, and the MAX control register must be rewritten on resume before the codec and GF1 sides are brought back. Use the existing wss->suspend() and wss->resume() hooks for the codec, then wire the driver up to the shared GUS suspend and resume helpers for the GF1 side. Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260406-b4-alsa-gus-isa-pm-v1-4-b6829a7457cd@gmail.com --- sound/isa/gus/gusmax.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/sound/isa/gus/gusmax.c b/sound/isa/gus/gusmax.c index b572411c4422..f1fd7ff2121d 100644 --- a/sound/isa/gus/gusmax.c +++ b/sound/isa/gus/gusmax.c @@ -328,12 +328,38 @@ static int snd_gusmax_probe(struct device *pdev, unsigned int dev) return 0; } +#ifdef CONFIG_PM +static int snd_gusmax_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_gusmax *maxcard = card->private_data; + + maxcard->wss->suspend(maxcard->wss); + return snd_gus_suspend(maxcard->gus); +} + +static int snd_gusmax_resume(struct device *dev, unsigned int n) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_gusmax *maxcard = card->private_data; + + /* Restore the board routing latch before resuming the codec and GF1. */ + outb(maxcard->gus->max_cntrl_val, GUSP(maxcard->gus, MAXCNTRLPORT)); + maxcard->wss->resume(maxcard->wss); + return snd_gus_resume(maxcard->gus); +} +#endif + #define DEV_NAME "gusmax" static struct isa_driver snd_gusmax_driver = { .match = snd_gusmax_match, .probe = snd_gusmax_probe, - /* FIXME: suspend/resume */ +#ifdef CONFIG_PM + .suspend = snd_gusmax_suspend, + .resume = snd_gusmax_resume, +#endif .driver = { .name = DEV_NAME }, From 247d1c13992d2c501e2e020e84d9d2920e11bf78 Mon Sep 17 00:00:00 2001 From: Sheetal Date: Mon, 6 Apr 2026 09:05:47 +0000 Subject: [PATCH 297/359] ASoC: rt5640: Handle 0Hz sysclk during stream shutdown Commit 2458adb8f92a ("SoC: simple-card-utils: set 0Hz to sysclk when shutdown") sends a 0Hz sysclk request during stream shutdown to clear codec rate constraints. The rt5640 codec forwards this 0Hz to clk_set_rate(), which can cause clock controller firmware faults on platforms where MCLK is SoC-driven (e.g. Tegra) and 0Hz falls below the hardware minimum rate. Handle the 0Hz case by clearing the internal sysclk state and returning early, avoiding the invalid clk_set_rate() call. Signed-off-by: Sheetal Link: https://patch.msgid.link/20260406090547.988966-1-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5640.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/codecs/rt5640.c b/sound/soc/codecs/rt5640.c index db2222e6f2e7..f6c6294e1588 100644 --- a/sound/soc/codecs/rt5640.c +++ b/sound/soc/codecs/rt5640.c @@ -1838,6 +1838,11 @@ static int rt5640_set_dai_sysclk(struct snd_soc_dai *dai, unsigned int pll_bit = 0; int ret; + if (!freq) { + rt5640->sysclk = 0; + return 0; + } + switch (clk_id) { case RT5640_SCLK_S_MCLK: ret = clk_set_rate(rt5640->mclk, freq); From b481eabe5a193ba8499f446c2ab7e0ac042f8776 Mon Sep 17 00:00:00 2001 From: Ravi Hothi Date: Thu, 26 Mar 2026 17:05:31 +0530 Subject: [PATCH 298/359] ASoC: qcom: audioreach: explicitly enable speaker protection modules Speaker protection and VI feedback modules are disabled by default. Explicitly enable them when configuring speaker protection. Fixes: 3e43a8c033c3 ("ASoC: qcom: audioreach: Add support for VI Sense module") Fixes: 0db76f5b2235 ("ASoC: qcom: audioreach: Add support for Speaker Protection module") Signed-off-by: Ravi Hothi Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260326113531.3144998-1-ravi.hothi@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/audioreach.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c index b2975eebab71..0eff5e572573 100644 --- a/sound/soc/qcom/qdsp6/audioreach.c +++ b/sound/soc/qcom/qdsp6/audioreach.c @@ -1365,9 +1365,14 @@ int audioreach_set_media_format(struct q6apm_graph *graph, case MODULE_ID_SPEAKER_PROTECTION: rc = audioreach_speaker_protection(graph, module, PARAM_ID_SP_OP_MODE_NORMAL); + if (!rc) + rc = audioreach_module_enable(graph, module, true); + break; case MODULE_ID_SPEAKER_PROTECTION_VI: rc = audioreach_speaker_protection_vi(graph, module, cfg); + if (!rc) + rc = audioreach_module_enable(graph, module, true); break; default: From 2c4fdd055f92a2fc8602dcd88bcea08c374b7e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Wed, 25 Mar 2026 17:05:11 -0300 Subject: [PATCH 299/359] ASoC: SOF: compress: return the configured codec from get_params MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SOF compressed offload path accepts codec parameters in sof_compr_set_params() and forwards them to firmware as extended data in the SOF IPC stream params message. However, sof_compr_get_params() still returns success without filling the snd_codec structure. Since the compress core allocates that structure zeroed and copies it back to userspace on success, SNDRV_COMPRESS_GET_PARAMS returns an all-zero codec description even after the stream has been configured successfully. The stale TODO in this callback conflates get_params() with capability discovery. Supported codec enumeration belongs in get_caps() and get_codec_caps(). get_params() should report the current codec settings. Cache the codec accepted by sof_compr_set_params() in the per-stream SOF compress state and return it from sof_compr_get_params(). Fixes: 6324cf901e14 ("ASoC: SOF: compr: Add compress ops implementation") Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260325-sof-compr-get-params-v1-1-0758815f13c7@gmail.com Signed-off-by: Mark Brown --- sound/soc/sof/compress.c | 8 +++++--- sound/soc/sof/sof-priv.h | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/compress.c index 3dbc05bf3b79..f4c3e10e6344 100644 --- a/sound/soc/sof/compress.c +++ b/sound/soc/sof/compress.c @@ -255,6 +255,7 @@ static int sof_compr_set_params(struct snd_soc_component *component, sstream->sampling_rate = params->codec.sample_rate; sstream->channels = params->codec.ch_out; sstream->sample_container_bytes = pcm->params.sample_container_bytes; + sstream->codec_params = params->codec; spcm->prepared[cstream->direction] = true; @@ -267,9 +268,10 @@ out: static int sof_compr_get_params(struct snd_soc_component *component, struct snd_compr_stream *cstream, struct snd_codec *params) { - /* TODO: we don't query the supported codecs for now, if the - * application asks for an unsupported codec the set_params() will fail. - */ + struct sof_compr_stream *sstream = cstream->runtime->private_data; + + *params = sstream->codec_params; + return 0; } diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 693d063830fa..38753b088fc1 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -111,6 +112,7 @@ struct sof_compr_stream { u32 sampling_rate; u16 channels; u16 sample_container_bytes; + struct snd_codec codec_params; size_t posn_offset; }; From d78ddeb8938a366aabfabf60255c1a94de8d8ea1 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 6 Apr 2026 05:51:51 +0000 Subject: [PATCH 300/359] ASoC: soc.h: remove unused card->pmdown_time commit f0fba2ad1b6b ("ASoC: multi-component - ASoC Multi-Component Support") has replaced "card->pmdown_time" to "rtd->pmdown_time". card->pmdown_time has been not used this 15 years. Let's remove it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87eckstz49.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index f70edd9c23b2..5e3eb617d832 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1027,8 +1027,6 @@ struct snd_soc_card { void (*remove_dai_link)(struct snd_soc_card *, struct snd_soc_dai_link *link); - long pmdown_time; - /* CPU <--> Codec DAI links */ struct snd_soc_dai_link *dai_link; /* predefined links only */ int num_links; /* predefined links only */ From fd495be8aa198a0d57069c37a9b44ee9e86a7486 Mon Sep 17 00:00:00 2001 From: songxiebing Date: Wed, 25 Mar 2026 10:17:52 +0800 Subject: [PATCH 301/359] ASoC: intel: avs: Fix type mismatch in variable assignment The input parameter requirement for snd_pcm_format_physical_with is snd_pcm_format_t,but params->codec.format is __u32, resulting in a mismatch error: sparse warnings: (new ones prefixed by >>) >> sound/soc/intel/avs/probes.c:147:58: sparse: sparse: incorrect type in argument 1 (different base types) @@ expected restricted snd_pcm_format_t [usertype] format @@ got unsigned int [usertype] format @@ sound/soc/intel/avs/probes.c:147:58: sparse: expected restricted snd_pcm_format_t [usertype] format sound/soc/intel/avs/probes.c:147:58: sparse: got unsigned int [usertype] format So here, the format is cast to snd_pcm_format_t. Signed-off-by: songxiebing Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202512190032.hnwn9mCV-lkp@intel.com/ Link: https://patch.msgid.link/20260325021752.238203-1-songxiebing@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/intel/avs/probes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/intel/avs/probes.c b/sound/soc/intel/avs/probes.c index 74096236984a..099119ad28b3 100644 --- a/sound/soc/intel/avs/probes.c +++ b/sound/soc/intel/avs/probes.c @@ -144,7 +144,7 @@ static int avs_probe_compr_set_params(struct snd_compr_stream *cstream, ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); if (ret < 0) return ret; - bps = snd_pcm_format_physical_width(params->codec.format); + bps = snd_pcm_format_physical_width((__force snd_pcm_format_t)params->codec.format); if (bps < 0) return bps; format_val = snd_hdac_stream_format(params->codec.ch_out, bps, params->codec.sample_rate); From 3666dc0c47c399695d01fde7c36e08b14f834fa0 Mon Sep 17 00:00:00 2001 From: Syed Saba Kareem Date: Fri, 3 Apr 2026 15:36:17 +0530 Subject: [PATCH 302/359] ASoC: amd: ps: fix the pcm device numbering for acp pdm dmic Fixed PCM device numbering is required for acp pdm dmic pcm device to have a common UCM changes. Set the 'use_dai_pcm_id' flag true in acp pdm dma driver for acp 6.3 platform. This will fix the pcm device numbering based on dai_link->id. Fixes: 33cea6bbe488 ("ASoC: amd: add acp6.2 pdm platform driver") Signed-off-by: Syed Saba Kareem Fixes: tag. Link: https://patch.msgid.link/20260403100624.676953-1-syed.sabakareem@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/ps/ps-pdm-dma.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/amd/ps/ps-pdm-dma.c b/sound/soc/amd/ps/ps-pdm-dma.c index c6cd844d458c..04c014349347 100644 --- a/sound/soc/amd/ps/ps-pdm-dma.c +++ b/sound/soc/amd/ps/ps-pdm-dma.c @@ -352,6 +352,7 @@ static const struct snd_soc_component_driver acp63_pdm_component = { .hw_params = acp63_pdm_dma_hw_params, .pointer = acp63_pdm_dma_pointer, .pcm_new = acp63_pdm_dma_new, + .use_dai_pcm_id = true, }; static int acp63_pdm_audio_probe(struct platform_device *pdev) From 22cb174c0af85e83d02a7b44fcc20c77008ee885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Tue, 7 Apr 2026 12:35:41 -0300 Subject: [PATCH 303/359] ALSA: tea6330t: move snd_tea6330t_detect() EXPORT_SYMBOL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the remaining standalone snd_tea6330t_detect() EXPORT_SYMBOL() declaration next to its function definition so tea6330t.c follows the usual layout. No functional change intended. Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260407-alsa-interwave-pm-v2-1-8dd96c6129e9@gmail.com --- sound/i2c/tea6330t.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/i2c/tea6330t.c b/sound/i2c/tea6330t.c index e8c50a036bea..5341907b85d1 100644 --- a/sound/i2c/tea6330t.c +++ b/sound/i2c/tea6330t.c @@ -51,6 +51,7 @@ int snd_tea6330t_detect(struct snd_i2c_bus *bus, int equalizer) snd_i2c_unlock(bus); return res; } +EXPORT_SYMBOL(snd_tea6330t_detect); #if 0 static void snd_tea6330t_set(struct tea6330t *tea, @@ -356,5 +357,4 @@ int snd_tea6330t_update_mixer(struct snd_card *card, return err; } -EXPORT_SYMBOL(snd_tea6330t_detect); EXPORT_SYMBOL(snd_tea6330t_update_mixer); From 19cbb3e0c27f28feb7781641994226cb2ee206a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Tue, 7 Apr 2026 12:35:42 -0300 Subject: [PATCH 304/359] ALSA: tea6330t: add mixer state restore helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The InterWave STB variant uses a TEA6330T mixer on its private I2C bus. The mixer state is cached in software, but there is no helper to push that register image back to hardware after system resume. Add a small restore helper that reapplies the cached TEA6330T register image to the device so board drivers can restore the external mixer state as part of their PM resume path. Take snd_i2c_lock() around the full device lookup and restore sequence so the bus device list traversal is also protected. Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260407-alsa-interwave-pm-v2-2-8dd96c6129e9@gmail.com --- include/sound/tea6330t.h | 1 + sound/i2c/tea6330t.c | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/include/sound/tea6330t.h b/include/sound/tea6330t.h index 1c77b78f6533..3a34033d2aa3 100644 --- a/include/sound/tea6330t.h +++ b/include/sound/tea6330t.h @@ -12,5 +12,6 @@ int snd_tea6330t_detect(struct snd_i2c_bus *bus, int equalizer); int snd_tea6330t_update_mixer(struct snd_card *card, struct snd_i2c_bus *bus, int equalizer, int fader); +int snd_tea6330t_restore_mixer(struct snd_i2c_bus *bus); #endif /* __SOUND_TEA6330T_H */ diff --git a/sound/i2c/tea6330t.c b/sound/i2c/tea6330t.c index 5341907b85d1..39c5e87c6ab0 100644 --- a/sound/i2c/tea6330t.c +++ b/sound/i2c/tea6330t.c @@ -356,5 +356,42 @@ int snd_tea6330t_update_mixer(struct snd_card *card, snd_i2c_device_free(device); return err; } - EXPORT_SYMBOL(snd_tea6330t_update_mixer); + +int snd_tea6330t_restore_mixer(struct snd_i2c_bus *bus) +{ + struct snd_i2c_device *device; + struct tea6330t *tea; + unsigned char bytes[7]; + unsigned int idx; + int err; + + if (!bus) + return -EINVAL; + + snd_i2c_lock(bus); + list_for_each_entry(device, &bus->devices, list) { + if (device->addr != TEA6330T_ADDR) + continue; + + tea = device->private_data; + if (!tea) { + err = -EINVAL; + goto unlock; + } + + bytes[0] = TEA6330T_SADDR_VOLUME_LEFT; + for (idx = 0; idx < 6; idx++) + bytes[idx + 1] = tea->regs[idx]; + err = snd_i2c_sendbytes(device, bytes, 7); + err = err < 0 ? err : 0; + goto unlock; + } + + err = -ENODEV; + +unlock: + snd_i2c_unlock(bus); + return err; +} +EXPORT_SYMBOL(snd_tea6330t_restore_mixer); From 6f800c3397b7f64da4c9eb636a1206f8d8636c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Tue, 7 Apr 2026 12:35:43 -0300 Subject: [PATCH 305/359] ALSA: interwave: add ISA and PnP suspend and resume callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit interwave still leaves both its ISA and PnP PM callbacks disabled even though the shared GUS suspend and resume path now exists. This board needs InterWave-specific glue around the shared GUS PM path. The attached WSS codec has its own register image that must be saved and restored across suspend, the InterWave-specific GF1 compatibility, decode, MPU401, and emulation settings must be rewritten after the shared GF1 resume path reinitializes the chip, and the probe-detected InterWave memory layout must be restored without rerunning the destructive DRAM/ROM detection path. Track the optional STB TEA6330T bus at probe time, restore its cached mixer state after resume, add resume-safe helpers for the InterWave register and memory-configuration state, and wire both the ISA and PnP front-ends up to the shared GUS PM helpers. The resume path intentionally restores only the cached hardware setup. It does not attempt to preserve sample RAM contents across suspend. Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260407-alsa-interwave-pm-v2-3-8dd96c6129e9@gmail.com --- sound/isa/gus/interwave.c | 178 ++++++++++++++++++++++++++++++++------ 1 file changed, 151 insertions(+), 27 deletions(-) diff --git a/sound/isa/gus/interwave.c b/sound/isa/gus/interwave.c index 18adcd35e117..616c11e51a2f 100644 --- a/sound/isa/gus/interwave.c +++ b/sound/isa/gus/interwave.c @@ -96,6 +96,7 @@ struct snd_interwave { struct snd_gus_card *gus; struct snd_wss *wss; #ifdef SNDRV_STB + struct snd_i2c_bus *i2c_bus; struct resource *i2c_res; #endif unsigned short gus_status_reg; @@ -363,18 +364,30 @@ struct rom_hdr { /* 511 */ unsigned char csum; }; +static const unsigned int snd_interwave_memory_configs[] = { + 0x00000001, 0x00000101, 0x01010101, 0x00000401, + 0x04040401, 0x00040101, 0x04040101, 0x00000004, + 0x00000404, 0x04040404, 0x00000010, 0x00001010, + 0x10101010 +}; + +static int snd_interwave_find_memory_config(unsigned int lmct) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(snd_interwave_memory_configs); i++) { + if (lmct == snd_interwave_memory_configs[i]) + return i; + } + + return -EINVAL; +} + static void snd_interwave_detect_memory(struct snd_gus_card *gus) { - static const unsigned int lmc[13] = - { - 0x00000001, 0x00000101, 0x01010101, 0x00000401, - 0x04040401, 0x00040101, 0x04040101, 0x00000004, - 0x00000404, 0x04040404, 0x00000010, 0x00001010, - 0x10101010 - }; - int bank_pos, pages; unsigned int i, lmct; + int lmc_cfg; int psizes[4]; unsigned char iwave[8]; unsigned char csum; @@ -399,17 +412,20 @@ static void snd_interwave_detect_memory(struct snd_gus_card *gus) #if 0 dev_dbg(gus->card->dev, "lmct = 0x%08x\n", lmct); #endif - for (i = 0; i < ARRAY_SIZE(lmc); i++) - if (lmct == lmc[i]) { + lmc_cfg = snd_interwave_find_memory_config(lmct); + if (lmc_cfg >= 0) { #if 0 - dev_dbg(gus->card->dev, "found !!! %i\n", i); + dev_dbg(gus->card->dev, "found !!! %i\n", lmc_cfg); #endif - snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | i); - snd_interwave_bank_sizes(gus, psizes); - break; - } - if (i >= ARRAY_SIZE(lmc) && !gus->gf1.enh_mode) - snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | 2); + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, + (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | + lmc_cfg); + snd_interwave_bank_sizes(gus, psizes); + } else if (!gus->gf1.enh_mode) { + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, + (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | + 2); + } for (i = 0; i < 4; i++) { gus->gf1.mem_alloc.banks_8[i].address = gus->gf1.mem_alloc.banks_16[i].address = i << 22; @@ -454,24 +470,66 @@ static void snd_interwave_detect_memory(struct snd_gus_card *gus) snd_interwave_reset(gus); } +static void __snd_interwave_restore_regs(struct snd_gus_card *gus) +{ + snd_gf1_write8(gus, SNDRV_GF1_GB_COMPATIBILITY, 0x1f); + snd_gf1_write8(gus, SNDRV_GF1_GB_DECODE_CONTROL, 0x49); + snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, 0x11); + snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A, 0x00); + snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B, 0x30); + snd_gf1_write8(gus, SNDRV_GF1_GB_EMULATION_IRQ, 0x00); +} + +static void snd_interwave_restore_regs(struct snd_gus_card *gus) +{ + scoped_guard(spinlock_irqsave, &gus->reg_lock) + __snd_interwave_restore_regs(gus); +} + +static void snd_interwave_restore_memory(struct snd_gus_card *gus) +{ + unsigned short mem_cfg; + unsigned int lmct = 0; + int i, lmc_cfg; + + if (!gus->gf1.memory) + return; + + for (i = 0; i < 4; i++) + lmct |= (gus->gf1.mem_alloc.banks_16[i].size >> 18) << (i * 8); + + lmc_cfg = snd_interwave_find_memory_config(lmct); + if (lmc_cfg < 0) { + if (!gus->gf1.enh_mode) { + lmc_cfg = 2; + } else { + dev_warn(gus->card->dev, + "cannot restore InterWave memory layout 0x%08x\n", + lmct); + return; + } + } + + scoped_guard(spinlock_irqsave, &gus->reg_lock) { + mem_cfg = snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG); + mem_cfg = (mem_cfg & 0xfff0) | lmc_cfg; + mem_cfg = (mem_cfg & 0xff1f) | (4 << 5); + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, mem_cfg); + } +} + static void snd_interwave_init(int dev, struct snd_gus_card *gus) { - /* ok.. some InterWave specific initialization */ + /* Probe-time setup also clears the timer control register. */ scoped_guard(spinlock_irqsave, &gus->reg_lock) { snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0x00); - snd_gf1_write8(gus, SNDRV_GF1_GB_COMPATIBILITY, 0x1f); - snd_gf1_write8(gus, SNDRV_GF1_GB_DECODE_CONTROL, 0x49); - snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, 0x11); - snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A, 0x00); - snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B, 0x30); - snd_gf1_write8(gus, SNDRV_GF1_GB_EMULATION_IRQ, 0x00); + __snd_interwave_restore_regs(gus); } gus->equal_irq = 1; gus->codec_flag = 1; gus->interwave = 1; gus->max_flag = 1; gus->joystick_dac = joystick_dac[dev]; - } static const struct snd_kcontrol_new snd_interwave_controls[] = { @@ -724,6 +782,7 @@ static int snd_interwave_probe(struct snd_card *card, int dev, err = snd_tea6330t_update_mixer(card, i2c_bus, 0, 1); if (err < 0) return err; + iwcard->i2c_bus = i2c_bus; } #endif @@ -828,10 +887,59 @@ static int snd_interwave_isa_probe(struct device *pdev, return 0; } +#ifdef CONFIG_PM +static int snd_interwave_card_suspend(struct snd_card *card) +{ + struct snd_interwave *iwcard = card->private_data; + + iwcard->wss->suspend(iwcard->wss); + return snd_gus_suspend(iwcard->gus); +} + +static int snd_interwave_card_resume(struct snd_card *card) +{ + struct snd_interwave *iwcard = card->private_data; + int err; + + err = snd_gus_resume(iwcard->gus); + if (err < 0) + return err; + + snd_interwave_restore_regs(iwcard->gus); + snd_interwave_restore_memory(iwcard->gus); + iwcard->wss->resume(iwcard->wss); +#ifdef SNDRV_STB + if (iwcard->i2c_bus) { + err = snd_tea6330t_restore_mixer(iwcard->i2c_bus); + if (err < 0) + dev_warn(card->dev, + "failed to restore TEA6330T mixer state: %d\n", + err); + } +#endif + + return 0; +} + +static int snd_interwave_isa_suspend(struct device *pdev, unsigned int dev, + pm_message_t state) +{ + return snd_interwave_card_suspend(dev_get_drvdata(pdev)); +} + +static int snd_interwave_isa_resume(struct device *pdev, unsigned int dev) +{ + return snd_interwave_card_resume(dev_get_drvdata(pdev)); +} +#endif + static struct isa_driver snd_interwave_driver = { .match = snd_interwave_isa_match, .probe = snd_interwave_isa_probe, - /* FIXME: suspend,resume */ +#ifdef CONFIG_PM + .suspend = snd_interwave_isa_suspend, + .resume = snd_interwave_isa_resume, +#endif .driver = { .name = INTERWAVE_DRIVER }, @@ -871,12 +979,28 @@ static int snd_interwave_pnp_detect(struct pnp_card_link *pcard, return 0; } +#ifdef CONFIG_PM +static int snd_interwave_pnpc_suspend(struct pnp_card_link *pcard, + pm_message_t state) +{ + return snd_interwave_card_suspend(pnp_get_card_drvdata(pcard)); +} + +static int snd_interwave_pnpc_resume(struct pnp_card_link *pcard) +{ + return snd_interwave_card_resume(pnp_get_card_drvdata(pcard)); +} +#endif + static struct pnp_card_driver interwave_pnpc_driver = { .flags = PNP_DRIVER_RES_DISABLE, .name = INTERWAVE_PNP_DRIVER, .id_table = snd_interwave_pnpids, .probe = snd_interwave_pnp_detect, - /* FIXME: suspend,resume */ +#ifdef CONFIG_PM + .suspend = snd_interwave_pnpc_suspend, + .resume = snd_interwave_pnpc_resume, +#endif }; #endif /* CONFIG_PNP */ From 66f6f543283e91b8899b0dd109d8f15a529e8464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Tue, 7 Apr 2026 18:13:06 -0300 Subject: [PATCH 306/359] ALSA: i2c: ak4xxx-adda: implement AK4529 reset handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delta 410 uses snd_akm4xxx_reset() both around DFS changes and from its PM callbacks, but the AK4529 case in this helper is still left unimplemented and never drives the codec reset path. The AK4529 datasheet documents register 09h.RSTN as an internal timing reset. Clearing RSTN powers down the ADC and DAC blocks, but does not reinitialize the register map. That matches the existing ak4xxx helper model, which already keeps the desired codec state in the software register cache. Implement AK4529 reset handling by clearing 09h.RSTN on state == 1, then replaying the cached register image and setting RSTN back to 1 on state == 0. This restores cached Delta 410 mixer state after resume and gives the AK4529 DFS-change path a real codec reset sequence. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260407-ak4529-reset-handling-v1-1-b971c18b1a32@gmail.com Signed-off-by: Takashi Iwai --- sound/i2c/other/ak4xxx-adda.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/sound/i2c/other/ak4xxx-adda.c b/sound/i2c/other/ak4xxx-adda.c index b24c80410d45..bdeb3cef3640 100644 --- a/sound/i2c/other/ak4xxx-adda.c +++ b/sound/i2c/other/ak4xxx-adda.c @@ -53,6 +53,31 @@ static void ak4524_reset(struct snd_akm4xxx *ak, int state) } } +/* reset procedure for AK4529 */ +static void ak4529_reset(struct snd_akm4xxx *ak, int state) +{ + static const unsigned char regs[] = { + 0x0a, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x0b, 0x0c, 0x08, + }; + unsigned int i; + unsigned char reg; + + if (state) { + snd_akm4xxx_write(ak, 0, 0x09, + snd_akm4xxx_get(ak, 0, 0x09) & ~0x01); + return; + } + + for (i = 0; i < ARRAY_SIZE(regs); i++) { + reg = regs[i]; + snd_akm4xxx_write(ak, 0, reg, + snd_akm4xxx_get(ak, 0, reg)); + } + snd_akm4xxx_write(ak, 0, 0x09, + snd_akm4xxx_get(ak, 0, 0x09) | 0x01); +} + /* reset procedure for AK4355 and AK4358 */ static void ak435X_reset(struct snd_akm4xxx *ak, int state) { @@ -99,7 +124,7 @@ void snd_akm4xxx_reset(struct snd_akm4xxx *ak, int state) ak4524_reset(ak, state); break; case SND_AK4529: - /* FIXME: needed for ak4529? */ + ak4529_reset(ak, state); break; case SND_AK4355: ak435X_reset(ak, state); From 292286b2d229fb732421429b027d38ac3f969383 Mon Sep 17 00:00:00 2001 From: songxiebing Date: Wed, 8 Apr 2026 16:33:11 +0800 Subject: [PATCH 307/359] ALSA: usb-audio: qcom: Fix incorrect type in enable_audio_stream Fix sparse warning: sound/usb/qcom/qc_audio_offload.c:943:27: sparse: incorrect type in argument 2 expected unsigned int val but got snd_pcm_format_t. Explicitly cast pcm_format to unsigned int for snd_mask_leave(). Fixes: 326bbc348298 ("ALSA: usb-audio: qcom: Introduce QC USB SND offloading support") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202604062109.Oxi8JjWW-lkp@intel.com/ Signed-off-by: songxiebing Link: https://patch.msgid.link/20260408083311.774173-1-songxiebing@kylinos.cn Signed-off-by: Takashi Iwai --- sound/usb/qcom/qc_audio_offload.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index f161eb29f911..9c0d370860f3 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -947,7 +947,7 @@ static int enable_audio_stream(struct snd_usb_substream *subs, _snd_pcm_hw_params_any(¶ms); m = hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT); - snd_mask_leave(m, pcm_format); + snd_mask_leave(m, (__force unsigned int)pcm_format); i = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS); snd_interval_setinteger(i); From 3f4aa994c0cc3616b8758345508ccddc6f324dfc Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 8 Apr 2026 05:22:06 +0000 Subject: [PATCH 308/359] ASoC: amd: acp-da7219-max98357a: tidyup acp_soc_is_rltk_max() acp-da7219-max98357a() user exists behind it. No need to has pre-define. Remove it. And it is local function, add static. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87h5pmvxfm.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/amd/acp-da7219-max98357a.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/amd/acp-da7219-max98357a.c b/sound/soc/amd/acp-da7219-max98357a.c index 3c08e6e17e41..af559653e625 100644 --- a/sound/soc/amd/acp-da7219-max98357a.c +++ b/sound/soc/amd/acp-da7219-max98357a.c @@ -47,8 +47,6 @@ static struct clk *da7219_dai_bclk; static struct clk *rt5682_dai_wclk; static struct clk *rt5682_dai_bclk; -void *acp_soc_is_rltk_max(struct device *dev); - static int cz_da7219_init(struct snd_soc_pcm_runtime *rtd) { int ret; @@ -703,7 +701,7 @@ static struct snd_soc_card cz_rt5682_card = { .num_controls = ARRAY_SIZE(cz_mc_controls), }; -void *acp_soc_is_rltk_max(struct device *dev) +static void *acp_soc_is_rltk_max(struct device *dev) { const struct acpi_device_id *match; From 9a52d1b7cb4a00a0dde3036b56a9706f54fccbb9 Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Mon, 6 Apr 2026 18:31:31 +0800 Subject: [PATCH 309/359] ASoC: tas2781: Explicit association of Device, Device Name, and Device ID By correlating devices with their names and IDs, the driver becomes more discoverable. Signed-off-by: Shenghao Ding Link: https://patch.msgid.link/20260406103131.1883-1-shenghao-ding@ti.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2781-i2c.c | 104 ++++++++++++++++----------------- 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/sound/soc/codecs/tas2781-i2c.c b/sound/soc/codecs/tas2781-i2c.c index 41b89fcc69c3..c593f9da0c5b 100644 --- a/sound/soc/codecs/tas2781-i2c.c +++ b/sound/soc/codecs/tas2781-i2c.c @@ -121,29 +121,28 @@ static const struct i2c_device_id tasdevice_id[] = { { "tas5830", TAS5830 }, {} }; -MODULE_DEVICE_TABLE(i2c, tasdevice_id); #ifdef CONFIG_OF static const struct of_device_id tasdevice_of_match[] = { - { .compatible = "ti,tas2020" }, - { .compatible = "ti,tas2118" }, - { .compatible = "ti,tas2120" }, - { .compatible = "ti,tas2320" }, - { .compatible = "ti,tas2563" }, - { .compatible = "ti,tas2568" }, - { .compatible = "ti,tas2570" }, - { .compatible = "ti,tas2572" }, - { .compatible = "ti,tas2574" }, - { .compatible = "ti,tas2781" }, - { .compatible = "ti,tas5802" }, - { .compatible = "ti,tas5806m" }, - { .compatible = "ti,tas5806md" }, - { .compatible = "ti,tas5815" }, - { .compatible = "ti,tas5822" }, - { .compatible = "ti,tas5825" }, - { .compatible = "ti,tas5827" }, - { .compatible = "ti,tas5828" }, - { .compatible = "ti,tas5830" }, + { .compatible = "ti,tas2020", .data = &tasdevice_id[TAS2020] }, + { .compatible = "ti,tas2118", .data = &tasdevice_id[TAS2118] }, + { .compatible = "ti,tas2120", .data = &tasdevice_id[TAS2120] }, + { .compatible = "ti,tas2320", .data = &tasdevice_id[TAS2320] }, + { .compatible = "ti,tas2563", .data = &tasdevice_id[TAS2563] }, + { .compatible = "ti,tas2568", .data = &tasdevice_id[TAS2568] }, + { .compatible = "ti,tas2570", .data = &tasdevice_id[TAS2570] }, + { .compatible = "ti,tas2572", .data = &tasdevice_id[TAS2572] }, + { .compatible = "ti,tas2574", .data = &tasdevice_id[TAS2574] }, + { .compatible = "ti,tas2781", .data = &tasdevice_id[TAS2781] }, + { .compatible = "ti,tas5802", .data = &tasdevice_id[TAS5802] }, + { .compatible = "ti,tas5806m", .data = &tasdevice_id[TAS5806M] }, + { .compatible = "ti,tas5806md", .data = &tasdevice_id[TAS5806MD] }, + { .compatible = "ti,tas5815", .data = &tasdevice_id[TAS5815] }, + { .compatible = "ti,tas5822", .data = &tasdevice_id[TAS5822] }, + { .compatible = "ti,tas5825", .data = &tasdevice_id[TAS5825] }, + { .compatible = "ti,tas5827", .data = &tasdevice_id[TAS5827] }, + { .compatible = "ti,tas5828", .data = &tasdevice_id[TAS5828] }, + { .compatible = "ti,tas5830", .data = &tasdevice_id[TAS5830] }, {}, }; MODULE_DEVICE_TABLE(of, tasdevice_of_match); @@ -2023,15 +2022,12 @@ static void tasdevice_parse_dt(struct tasdevice_priv *tas_priv) if (IS_ERR(tas_priv->reset)) dev_err(tas_priv->dev, "%s Can't get reset GPIO\n", __func__); - - strscpy(tas_priv->dev_name, tasdevice_id[tas_priv->chip_id].name, - sizeof(tas_priv->dev_name)); } static int tasdevice_i2c_probe(struct i2c_client *i2c) { - const struct acpi_device_id *acpi_id; struct tasdevice_priv *tas_priv; + struct i2c_device_id *id_data; int ret; tas_priv = tasdevice_kzalloc(i2c); @@ -2041,20 +2037,23 @@ static int tasdevice_i2c_probe(struct i2c_client *i2c) dev_set_drvdata(&i2c->dev, tas_priv); if (ACPI_HANDLE(&i2c->dev)) { - acpi_id = acpi_match_device(i2c->dev.driver->acpi_match_table, - &i2c->dev); - if (!acpi_id) { - dev_err(&i2c->dev, "No driver data\n"); - ret = -EINVAL; - goto err; - } - tas_priv->chip_id = acpi_id->driver_data; + id_data = (struct i2c_device_id *) + acpi_device_get_match_data(&i2c->dev); tas_priv->isacpi = true; } else { - tas_priv->chip_id = (uintptr_t)i2c_get_match_data(i2c); + id_data = (struct i2c_device_id *)i2c_get_match_data(i2c); tas_priv->isacpi = false; } + if (!id_data) { + dev_err(&i2c->dev, "No driver data\n"); + ret = -EINVAL; + goto err; + } + + tas_priv->chip_id = (uintptr_t)id_data->driver_data; + strscpy(tas_priv->dev_name, id_data->name, sizeof(tas_priv->dev_name)); + tasdevice_parse_dt(tas_priv); ret = tasdevice_init(tas_priv); @@ -2086,25 +2085,25 @@ static void tasdevice_i2c_remove(struct i2c_client *client) #ifdef CONFIG_ACPI static const struct acpi_device_id tasdevice_acpi_match[] = { - { "TXNW2020", TAS2020 }, - { "TXNW2118", TAS2118 }, - { "TXNW2120", TAS2120 }, - { "TXNW2320", TAS2320 }, - { "TXNW2563", TAS2563 }, - { "TXNW2568", TAS2568 }, - { "TXNW2570", TAS2570 }, - { "TXNW2572", TAS2572 }, - { "TXNW2574", TAS2574 }, - { "TXNW2781", TAS2781 }, - { "TXNW5802", TAS5802 }, - { "TXNW806M", TAS5806M }, - { "TXNW806D", TAS5806MD }, - { "TXNW5815", TAS5815 }, - { "TXNW5822", TAS5822 }, - { "TXNW5825", TAS5825 }, - { "TXNW5827", TAS5827 }, - { "TXNW5828", TAS5828 }, - { "TXNW5830", TAS5830 }, + { "TXNW2020", (kernel_ulong_t)&tasdevice_id[TAS2020] }, + { "TXNW2118", (kernel_ulong_t)&tasdevice_id[TAS2118] }, + { "TXNW2120", (kernel_ulong_t)&tasdevice_id[TAS2120] }, + { "TXNW2320", (kernel_ulong_t)&tasdevice_id[TAS2320] }, + { "TXNW2563", (kernel_ulong_t)&tasdevice_id[TAS2563] }, + { "TXNW2568", (kernel_ulong_t)&tasdevice_id[TAS2568] }, + { "TXNW2570", (kernel_ulong_t)&tasdevice_id[TAS2570] }, + { "TXNW2572", (kernel_ulong_t)&tasdevice_id[TAS2572] }, + { "TXNW2574", (kernel_ulong_t)&tasdevice_id[TAS2574] }, + { "TXNW2781", (kernel_ulong_t)&tasdevice_id[TAS2781] }, + { "TXNW5802", (kernel_ulong_t)&tasdevice_id[TAS5802] }, + { "TXNW806M", (kernel_ulong_t)&tasdevice_id[TAS5806M] }, + { "TXNW806D", (kernel_ulong_t)&tasdevice_id[TAS5806MD] }, + { "TXNW5815", (kernel_ulong_t)&tasdevice_id[TAS5815] }, + { "TXNW5822", (kernel_ulong_t)&tasdevice_id[TAS5822] }, + { "TXNW5825", (kernel_ulong_t)&tasdevice_id[TAS5825] }, + { "TXNW5827", (kernel_ulong_t)&tasdevice_id[TAS5827] }, + { "TXNW5828", (kernel_ulong_t)&tasdevice_id[TAS5828] }, + { "TXNW5830", (kernel_ulong_t)&tasdevice_id[TAS5830] }, {}, }; @@ -2121,7 +2120,6 @@ static struct i2c_driver tasdevice_i2c_driver = { }, .probe = tasdevice_i2c_probe, .remove = tasdevice_i2c_remove, - .id_table = tasdevice_id, }; module_i2c_driver(tasdevice_i2c_driver); From f4ee8a882a560308c390cf45ffa616c59f60a731 Mon Sep 17 00:00:00 2001 From: Chaitanya Sabnis Date: Fri, 27 Mar 2026 14:51:06 +0530 Subject: [PATCH 310/359] ASoC: dt-bindings: hisilicon: Convert hi6210 I2S to dt-schema Convert the Hisilicon hi6210 I2S controller hardware binding from legacy plain text to modern YAML dt-schema format. During the conversion, the order of the dma-names properties in the example was corrected to "tx", "rx" to match the official property description, resolving a contradiction in the original text binding. Signed-off-by: Chaitanya Sabnis Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260327092106.4233-1-chaitanya.msabnis@gmail.com Signed-off-by: Mark Brown --- .../bindings/sound/hisilicon,hi6210-i2s.txt | 42 ---------- .../bindings/sound/hisilicon,hi6210-i2s.yaml | 80 +++++++++++++++++++ 2 files changed, 80 insertions(+), 42 deletions(-) delete mode 100644 Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.txt create mode 100644 Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml diff --git a/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.txt b/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.txt deleted file mode 100644 index 7a296784eb37..000000000000 --- a/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.txt +++ /dev/null @@ -1,42 +0,0 @@ -* Hisilicon 6210 i2s controller - -Required properties: - -- compatible: should be one of the following: - - "hisilicon,hi6210-i2s" -- reg: physical base address of the i2s controller unit and length of - memory mapped region. -- interrupts: should contain the i2s interrupt. -- clocks: a list of phandle + clock-specifier pairs, one for each entry - in clock-names. -- clock-names: should contain following: - - "dacodec" - - "i2s-base" -- dmas: DMA specifiers for tx dma. See the DMA client binding, - Documentation/devicetree/bindings/dma/dma.txt -- dma-names: should be "tx" and "rx" -- hisilicon,sysctrl-syscon: phandle to sysctrl syscon -- #sound-dai-cells: Should be set to 1 (for multi-dai) - - The dai cell indexes reference the following interfaces: - 0: S2 interface - (Currently that is the only one available, but more may be - supported in the future) - -Example for the hi6210 i2s controller: - -i2s0: i2s@f7118000{ - compatible = "hisilicon,hi6210-i2s"; - reg = <0x0 0xf7118000 0x0 0x8000>; /* i2s unit */ - interrupts = ; /* 155 "DigACodec_intr"-32 */ - clocks = <&sys_ctrl HI6220_DACODEC_PCLK>, - <&sys_ctrl HI6220_BBPPLL0_DIV>; - clock-names = "dacodec", "i2s-base"; - dmas = <&dma0 15 &dma0 14>; - dma-names = "rx", "tx"; - hisilicon,sysctrl-syscon = <&sys_ctrl>; - #sound-dai-cells = <1>; -}; - -Then when referencing the i2s controller: - sound-dai = <&i2s0 0>; /* index 0 => S2 interface */ - diff --git a/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml b/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml new file mode 100644 index 000000000000..5171f984630b --- /dev/null +++ b/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/hisilicon,hi6210-i2s.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: HiSilicon hi6210 I2S controller + +maintainers: + - John Stultz + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: hisilicon,hi6210-i2s + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 2 + + clock-names: + items: + - const: dacodec + - const: i2s-base + + dmas: + maxItems: 2 + + dma-names: + items: + - const: tx + - const: rx + + hisilicon,sysctrl-syscon: + $ref: /schemas/types.yaml#/definitions/phandle + description: phandle to sysctrl syscon + + "#sound-dai-cells": + const: 1 + description: | + The dai cell indexes reference the following interfaces: + 0: S2 interface + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - dmas + - dma-names + - hisilicon,sysctrl-syscon + - "#sound-dai-cells" + +unevaluatedProperties: false + +examples: + - | + #include + #include + + i2s@f7118000 { + compatible = "hisilicon,hi6210-i2s"; + reg = <0xf7118000 0x8000>; + interrupts = ; + clocks = <&sys_ctrl HI6220_DACODEC_PCLK>, + <&sys_ctrl HI6220_BBPPLL0_DIV>; + clock-names = "dacodec", "i2s-base"; + dmas = <&dma0 14>, <&dma0 15>; + dma-names = "tx", "rx"; + hisilicon,sysctrl-syscon = <&sys_ctrl>; + #sound-dai-cells = <1>; + }; From de65275fc94e2e0acc79bd016d60889bf251ccd9 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Thu, 9 Apr 2026 10:40:28 +0800 Subject: [PATCH 311/359] ALSA: hda/realtek: Add quirk for CSL Unity BF24B The CSL Unity BF24B all-in-one PC uses a Realtek ALC662 rev3 audio codec and requires the correct GPIO configuration to enable sound output from both the speakers and the headphone. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221258 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260409024028.1297587-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc662.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c index 5073165d1f3c..3abe41c7315c 100644 --- a/sound/hda/codecs/realtek/alc662.c +++ b/sound/hda/codecs/realtek/alc662.c @@ -255,6 +255,25 @@ static void alc_fixup_headset_mode_alc668(struct hda_codec *codec, alc_fixup_headset_mode(codec, fix, action); } +static void alc662_fixup_csl_amp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + spec->gpio_mask |= 0x03; + spec->gpio_dir |= 0x03; + break; + case HDA_FIXUP_ACT_INIT: + /* need to toggle GPIO to enable the amp */ + alc_update_gpio_data(codec, 0x03, true); + msleep(100); + alc_update_gpio_data(codec, 0x03, false); + break; + } +} + enum { ALC662_FIXUP_ASPIRE, ALC662_FIXUP_LED_GPIO1, @@ -313,6 +332,7 @@ enum { ALC897_FIXUP_HEADSET_MIC_PIN2, ALC897_FIXUP_UNIS_H3C_X500S, ALC897_FIXUP_HEADSET_MIC_PIN3, + ALC662_FIXUP_CSL_GPIO, }; static const struct hda_fixup alc662_fixups[] = { @@ -766,11 +786,16 @@ static const struct hda_fixup alc662_fixups[] = { { } }, }, + [ALC662_FIXUP_CSL_GPIO] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc662_fixup_csl_amp, + }, }; static const struct hda_quirk alc662_fixup_tbl[] = { SND_PCI_QUIRK(0x1019, 0x9087, "ECS", ALC662_FIXUP_ASUS_MODE2), SND_PCI_QUIRK(0x1019, 0x9859, "JP-IK LEAP W502", ALC897_FIXUP_HEADSET_MIC_PIN3), + SND_PCI_QUIRK(0x1022, 0xc950, "CSL Unity BF24B", ALC662_FIXUP_CSL_GPIO), SND_PCI_QUIRK(0x1025, 0x022f, "Acer Aspire One", ALC662_FIXUP_INV_DMIC), SND_PCI_QUIRK(0x1025, 0x0241, "Packard Bell DOTS", ALC662_FIXUP_INV_DMIC), SND_PCI_QUIRK(0x1025, 0x0308, "Acer Aspire 8942G", ALC662_FIXUP_ASPIRE), From aa6c1052b7730e18d5999f9a5cfb1dadaac82310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Wed, 8 Apr 2026 12:17:37 -0300 Subject: [PATCH 312/359] ALSA: i2c: ak4xxx-adda: seed AK5365 cache with reset defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_akm4xxx_init() clears the register and volume caches before dispatching by codec type. The AK5365 case then returns immediately, leaving the software cache at zero instead of the documented AK5365 reset defaults. The AK5365 capture volume controls read from volumes[] and the proc register dump reads from images[], so the initial capture volume state and proc output are wrong until a control write happens. Seed the AK5365 cache with its documented reset defaults instead of adding a guessed init sequence. The datasheet documents the reset values and states that MCLK/LRCK changes do not require a PDN/PWN reset because the chip has a built-in reset-free circuit. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260408-ak5365-cache-defaults-v1-1-fff639aca3e3@gmail.com Signed-off-by: Takashi Iwai --- sound/i2c/other/ak4xxx-adda.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sound/i2c/other/ak4xxx-adda.c b/sound/i2c/other/ak4xxx-adda.c index bdeb3cef3640..9dd36b82a6ac 100644 --- a/sound/i2c/other/ak4xxx-adda.c +++ b/sound/i2c/other/ak4xxx-adda.c @@ -281,6 +281,9 @@ void snd_akm4xxx_init(struct snd_akm4xxx *ak) 0x07, 0x00, /* 7: ROUT muted */ 0xff, 0xff }; + static const unsigned char ak5365_defaults[] = { + 0x01, 0x00, 0x00, 0x2b, 0x7f, 0x7f, 0x28, 0x89, + }; int chip; const unsigned char *ptr, *inits; @@ -327,10 +330,12 @@ void snd_akm4xxx_init(struct snd_akm4xxx *ak) ak->total_regs = 0x05; break; case SND_AK5365: - /* FIXME: any init sequence? */ ak->num_chips = 1; ak->name = "ak5365"; ak->total_regs = 0x08; + memcpy(ak->images, ak5365_defaults, sizeof(ak5365_defaults)); + snd_akm4xxx_set_vol(ak, 0, 0x04, 127); + snd_akm4xxx_set_vol(ak, 0, 0x05, 127); return; case SND_AK4620: inits = inits_ak4620; From 49690509ebdcbfa7618dd5a5ff3c89f7af9a5b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Thu, 9 Apr 2026 02:07:45 -0300 Subject: [PATCH 313/359] ALSA: msnd: prepare system sleep support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit System suspend cannot work for msnd today because the PCM trigger paths reject SNDRV_PCM_TRIGGER_SUSPEND, and the driver has only refcounted IRQ helpers. Add the small helpers needed by the PM callbacks and restore master volume from the cached ALSA mixer state when the DSP is reinitialized. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260409-msnd-pm-support-v1-1-2abef720d0e7@gmail.com Signed-off-by: Takashi Iwai --- sound/isa/msnd/msnd.c | 72 +++++++++++++++++++--------- sound/isa/msnd/msnd.h | 1 + sound/isa/msnd/msnd_pinnacle_mixer.c | 4 ++ 3 files changed, 54 insertions(+), 23 deletions(-) diff --git a/sound/isa/msnd/msnd.c b/sound/isa/msnd/msnd.c index 5e350234d572..77367e102fda 100644 --- a/sound/isa/msnd/msnd.c +++ b/sound/isa/msnd/msnd.c @@ -127,11 +127,8 @@ int snd_msnd_upload_host(struct snd_msnd *dev, const u8 *bin, int len) } EXPORT_SYMBOL(snd_msnd_upload_host); -int snd_msnd_enable_irq(struct snd_msnd *dev) +static int __snd_msnd_enable_irq(struct snd_msnd *dev) { - if (dev->irq_ref++) - return 0; - dev_dbg(dev->card->dev, LOGNAME ": Enabling IRQ\n"); guard(spinlock_irqsave)(&dev->lock); @@ -152,17 +149,9 @@ int snd_msnd_enable_irq(struct snd_msnd *dev) return -EIO; } -EXPORT_SYMBOL(snd_msnd_enable_irq); -int snd_msnd_disable_irq(struct snd_msnd *dev) +static int __snd_msnd_disable_irq(struct snd_msnd *dev) { - if (--dev->irq_ref > 0) - return 0; - - if (dev->irq_ref < 0) - dev_dbg(dev->card->dev, LOGNAME ": IRQ ref count is %d\n", - dev->irq_ref); - dev_dbg(dev->card->dev, LOGNAME ": Disabling IRQ\n"); guard(spinlock_irqsave)(&dev->lock); @@ -178,8 +167,39 @@ int snd_msnd_disable_irq(struct snd_msnd *dev) return -EIO; } + +int snd_msnd_enable_irq(struct snd_msnd *dev) +{ + if (dev->irq_ref++) + return 0; + + return __snd_msnd_enable_irq(dev); +} +EXPORT_SYMBOL(snd_msnd_enable_irq); + +int snd_msnd_disable_irq(struct snd_msnd *dev) +{ + if (--dev->irq_ref > 0) + return 0; + + if (dev->irq_ref < 0) + dev_dbg(dev->card->dev, LOGNAME ": IRQ ref count is %d\n", + dev->irq_ref); + + return __snd_msnd_disable_irq(dev); +} EXPORT_SYMBOL(snd_msnd_disable_irq); +int snd_msnd_force_irq(struct snd_msnd *dev, bool enable) +{ + if (!dev->irq_ref) + return 0; + + return enable ? __snd_msnd_enable_irq(dev) : + __snd_msnd_disable_irq(dev); +} +EXPORT_SYMBOL(snd_msnd_force_irq); + static inline long get_play_delay_jiffies(struct snd_msnd *chip, long size) { long tmp = (size * HZ * chip->play_sample_size) / 8; @@ -507,25 +527,27 @@ static int snd_msnd_playback_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_msnd *chip = snd_pcm_substream_chip(substream); - int result = 0; - if (cmd == SNDRV_PCM_TRIGGER_START) { + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: dev_dbg(chip->card->dev, "%s(START)\n", __func__); chip->banksPlayed = 0; set_bit(F_WRITING, &chip->flags); snd_msnd_DAPQ(chip, 1); - } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: dev_dbg(chip->card->dev, "%s(STOP)\n", __func__); - /* interrupt diagnostic, comment this out later */ clear_bit(F_WRITING, &chip->flags); snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP); - } else { + break; + default: dev_dbg(chip->card->dev, "%s(?????)\n", __func__); - result = -EINVAL; + return -EINVAL; } dev_dbg(chip->card->dev, "%s() ENDE\n", __func__); - return result; + return 0; } static snd_pcm_uframes_t @@ -589,17 +611,22 @@ static int snd_msnd_capture_trigger(struct snd_pcm_substream *substream, { struct snd_msnd *chip = snd_pcm_substream_chip(substream); - if (cmd == SNDRV_PCM_TRIGGER_START) { + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: chip->last_recbank = -1; set_bit(F_READING, &chip->flags); if (snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_START) == 0) return 0; clear_bit(F_READING, &chip->flags); - } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: clear_bit(F_READING, &chip->flags); snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP); return 0; + default: + break; } return -EINVAL; } @@ -668,4 +695,3 @@ EXPORT_SYMBOL(snd_msnd_pcm); MODULE_DESCRIPTION("Common routines for Turtle Beach Multisound drivers"); MODULE_LICENSE("GPL"); - diff --git a/sound/isa/msnd/msnd.h b/sound/isa/msnd/msnd.h index 3d7810ed9186..b25beca25c0d 100644 --- a/sound/isa/msnd/msnd.h +++ b/sound/isa/msnd/msnd.h @@ -280,6 +280,7 @@ int snd_msnd_upload_host(struct snd_msnd *chip, const u8 *bin, int len); int snd_msnd_enable_irq(struct snd_msnd *chip); int snd_msnd_disable_irq(struct snd_msnd *chip); +int snd_msnd_force_irq(struct snd_msnd *chip, bool enable); void snd_msnd_dsp_halt(struct snd_msnd *chip, struct file *file); int snd_msnd_DAPQ(struct snd_msnd *chip, int start); int snd_msnd_DARQ(struct snd_msnd *chip, int start); diff --git a/sound/isa/msnd/msnd_pinnacle_mixer.c b/sound/isa/msnd/msnd_pinnacle_mixer.c index ec354483b9f8..8ca987221753 100644 --- a/sound/isa/msnd/msnd_pinnacle_mixer.c +++ b/sound/isa/msnd/msnd_pinnacle_mixer.c @@ -310,6 +310,10 @@ EXPORT_SYMBOL(snd_msndmix_new); void snd_msndmix_setup(struct snd_msnd *dev) { + writew(dev->left_levels[MSND_MIXER_VOLUME], + dev->SMA + SMA_wCurrMastVolLeft); + writew(dev->right_levels[MSND_MIXER_VOLUME], + dev->SMA + SMA_wCurrMastVolRight); update_pot(MSND_MIXER_LINE, bInPotPos, HDEXAR_IN_SET_POTS); update_potm(MSND_MIXER_AUX, bAuxPotPos, HDEXAR_AUX_SET_POTS); update_volm(MSND_MIXER_PCM, wCurrPlayVol); From efca489a86fc6a5364215fdf03c2fad3b864d03a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Thu, 9 Apr 2026 02:07:46 -0300 Subject: [PATCH 314/359] ALSA: msnd: add ISA and PnP system sleep callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The msnd drivers do not implement system sleep callbacks today, so they have no defined way to recover DSP state after suspend. Add common card suspend/resume helpers, rerun the DSP initialization path on resume, restore the cached capture-source state, and rearm the shared IRQ for already-open users. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260409-msnd-pm-support-v1-2-2abef720d0e7@gmail.com Signed-off-by: Takashi Iwai --- sound/isa/msnd/msnd.h | 2 + sound/isa/msnd/msnd_pinnacle.c | 95 +++++++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/sound/isa/msnd/msnd.h b/sound/isa/msnd/msnd.h index b25beca25c0d..56a700e6a5cb 100644 --- a/sound/isa/msnd/msnd.h +++ b/sound/isa/msnd/msnd.h @@ -253,6 +253,8 @@ struct snd_msnd { spinlock_t mixer_lock; int nresets; unsigned recsrc; + u8 pm_recsrc; + bool pm_mpu_input; #define LEVEL_ENTRIES 32 int left_levels[LEVEL_ENTRIES]; int right_levels[LEVEL_ENTRIES]; diff --git a/sound/isa/msnd/msnd_pinnacle.c b/sound/isa/msnd/msnd_pinnacle.c index c4eec391cd29..5b729bb02ef6 100644 --- a/sound/isa/msnd/msnd_pinnacle.c +++ b/sound/isa/msnd/msnd_pinnacle.c @@ -513,6 +513,19 @@ static void snd_msnd_mpu401_close(struct snd_mpu401 *mpu) snd_msnd_disable_irq(mpu->private_data); } +#ifdef CONFIG_PM +static u8 snd_msnd_pm_recsrc(struct snd_msnd *chip) +{ + /* Convert recsrc to the Capture Source selector: 0=Analog, 1=MASS, 2=SPDIF. */ + if (chip->recsrc & BIT(4)) + return 1; + if ((chip->recsrc & BIT(17)) && + test_bit(F_HAVEDIGITAL, &chip->flags)) + return 2; + return 0; +} +#endif + static long mpu_io[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; @@ -1001,10 +1014,73 @@ static int snd_msnd_isa_probe(struct device *pdev, unsigned int idx) return 0; } +#ifdef CONFIG_PM +static int snd_msnd_card_suspend(struct snd_card *card) +{ + struct snd_msnd *chip = card->private_data; + struct snd_mpu401 *mpu; + int err; + + mpu = chip->rmidi ? chip->rmidi->private_data : NULL; + chip->pm_recsrc = snd_msnd_pm_recsrc(chip); + chip->pm_mpu_input = mpu && test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode); + if (chip->pm_mpu_input) + snd_msnd_send_dsp_cmd(chip, HDEX_MIDI_IN_STOP); + + err = snd_msnd_force_irq(chip, false); + if (err < 0) { + if (chip->pm_mpu_input) + snd_msnd_send_dsp_cmd(chip, HDEX_MIDI_IN_START); + return err; + } + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + return 0; +} + +static int snd_msnd_card_resume(struct snd_card *card) +{ + struct snd_msnd *chip = card->private_data; + int err; + + err = snd_msnd_initialize(card); + if (err < 0) + return err; + + snd_msnd_calibrate_adc(chip, chip->play_sample_rate); + snd_msndmix_force_recsrc(chip, chip->pm_recsrc); + + err = snd_msnd_force_irq(chip, true); + if (err < 0) + return err; + + if (chip->pm_mpu_input) + snd_msnd_send_dsp_cmd(chip, HDEX_MIDI_IN_START); + + chip->nresets = 0; + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static int snd_msnd_isa_suspend(struct device *dev, unsigned int idx, + pm_message_t state) +{ + return snd_msnd_card_suspend(dev_get_drvdata(dev)); +} + +static int snd_msnd_isa_resume(struct device *dev, unsigned int idx) +{ + return snd_msnd_card_resume(dev_get_drvdata(dev)); +} +#endif + static struct isa_driver snd_msnd_driver = { .match = snd_msnd_isa_match, .probe = snd_msnd_isa_probe, - /* FIXME: suspend, resume */ +#ifdef CONFIG_PM + .suspend = snd_msnd_isa_suspend, + .resume = snd_msnd_isa_resume, +#endif .driver = { .name = DEV_NAME }, @@ -1111,6 +1187,18 @@ static int snd_msnd_pnp_detect(struct pnp_card_link *pcard, return 0; } +#ifdef CONFIG_PM +static int snd_msnd_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state) +{ + return snd_msnd_card_suspend(pnp_get_card_drvdata(pcard)); +} + +static int snd_msnd_pnp_resume(struct pnp_card_link *pcard) +{ + return snd_msnd_card_resume(pnp_get_card_drvdata(pcard)); +} +#endif + static int isa_registered; static int pnp_registered; @@ -1127,6 +1215,10 @@ static struct pnp_card_driver msnd_pnpc_driver = { .name = "msnd_pinnacle", .id_table = msnd_pnpids, .probe = snd_msnd_pnp_detect, +#ifdef CONFIG_PM + .suspend = snd_msnd_pnp_suspend, + .resume = snd_msnd_pnp_resume, +#endif }; #endif /* CONFIG_PNP */ @@ -1161,4 +1253,3 @@ static void __exit snd_msnd_exit(void) module_init(snd_msnd_init); module_exit(snd_msnd_exit); - From 9575766a682f50ec4bcb85ecd438685bdc09f9cc Mon Sep 17 00:00:00 2001 From: Lianqin Hu Date: Thu, 9 Apr 2026 08:21:37 +0000 Subject: [PATCH 315/359] ALSA: usb-audio: Add iface reset and delay quirk for HUAWEI USB-C HEADSET Setting up the interface when suspended/resumeing fail on this card. Adding a reset and delay quirk will eliminate this problem. usb 1-1: new full-speed USB device number 2 using xhci-hcd usb 1-1: New USB device found, idVendor=12d1, idProduct=3a07 usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 1-1: Product: HUAWEI USB-C HEADSET usb 1-1: Manufacturer: bestechnic usb 1-1: SerialNumber: 0296C100000000000000000000000 Signed-off-by: Lianqin Hu Link: https://patch.msgid.link/TYUPR06MB62176A18EA7A9DD0AC2826BCD2582@TYUPR06MB6217.apcprd06.prod.outlook.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 48acd8dac689..6cf85922b7be 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2347,8 +2347,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */ QUIRK_FLAG_GET_SAMPLE_RATE), - DEVICE_FLG(0x12d1, 0x3a07, /* Huawei Technologies Co., Ltd. */ - QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), + DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE | + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x1224, 0x2a25, /* Jieli Technology USB PHY 2.0 */ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), DEVICE_FLG(0x1395, 0x740a, /* Sennheiser DECT */ From b0762dd2fcab5b8b4b953314f3f6eb9d92bc16bc Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:14 +0200 Subject: [PATCH 316/359] ALSA: hda: Add sync version of snd_hda_codec_write() We used snd_hda_codec_read() for the verb write when a synchronization is needed after the write, e.g. for the power state toggle or such cases. It works in principle, but it looks rather confusing and too hackish. For improving the code readability, introduce a new helper function, snd_hda_codec_write_sync(), which is another variant of snd_hda_codec_write(), and replace the existing snd_hda_codec_read() calls with this one. No behavior change but just the code refactoring. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-2-tiwai@suse.de --- include/sound/hda_codec.h | 11 +++++++++++ sound/hda/codecs/generic.c | 2 +- sound/hda/codecs/hdmi/intelhdmi.c | 2 +- sound/hda/codecs/realtek/realtek.c | 3 +-- sound/hda/codecs/sigmatel.c | 4 ++-- sound/hda/common/codec.c | 11 +++++------ 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h index 5d9f0ef228af..292d6024388b 100644 --- a/include/sound/hda_codec.h +++ b/include/sound/hda_codec.h @@ -336,6 +336,17 @@ snd_hda_codec_write(struct hda_codec *codec, hda_nid_t nid, int flags, return snd_hdac_codec_write(&codec->core, nid, flags, verb, parm); } +/* sync after write */ +static inline int +snd_hda_codec_write_sync(struct hda_codec *codec, hda_nid_t nid, int flags, + unsigned int verb, unsigned int parm) +{ + /* use snd_hda_codec_read() for writing; + * the returned value is usually discarded + */ + return snd_hdac_codec_read(&codec->core, nid, flags, verb, parm); +} + #define snd_hda_param_read(codec, nid, param) \ snd_hdac_read_parm(&(codec)->core, nid, param) #define snd_hda_get_sub_nodes(codec, nid, start_nid) \ diff --git a/sound/hda/codecs/generic.c b/sound/hda/codecs/generic.c index 092428ada29d..660a9f2c0ded 100644 --- a/sound/hda/codecs/generic.c +++ b/sound/hda/codecs/generic.c @@ -863,7 +863,7 @@ static void sync_power_state_change(struct hda_codec *codec, hda_nid_t nid) { if (nid) { msleep(10); - snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_POWER_STATE, 0); + snd_hda_codec_write_sync(codec, nid, 0, AC_VERB_GET_POWER_STATE, 0); } } diff --git a/sound/hda/codecs/hdmi/intelhdmi.c b/sound/hda/codecs/hdmi/intelhdmi.c index 9460c8db39a9..6a7882544ab7 100644 --- a/sound/hda/codecs/hdmi/intelhdmi.c +++ b/sound/hda/codecs/hdmi/intelhdmi.c @@ -85,7 +85,7 @@ static void haswell_set_power_state(struct hda_codec *codec, hda_nid_t fg, } } - snd_hda_codec_read(codec, fg, 0, AC_VERB_SET_POWER_STATE, power_state); + snd_hda_codec_write_sync(codec, fg, 0, AC_VERB_SET_POWER_STATE, power_state); snd_hda_codec_set_power_to_all(codec, fg, power_state); } diff --git a/sound/hda/codecs/realtek/realtek.c b/sound/hda/codecs/realtek/realtek.c index b240f13b0438..39a1ead3b743 100644 --- a/sound/hda/codecs/realtek/realtek.c +++ b/sound/hda/codecs/realtek/realtek.c @@ -411,9 +411,8 @@ void alc_headset_mic_no_shutup(struct hda_codec *codec) return; snd_array_for_each(&codec->init_pins, i, pin) { - /* use read here for syncing after issuing each verb */ if (pin->nid != mic_pin) - snd_hda_codec_read(codec, pin->nid, 0, + snd_hda_codec_write_sync(codec, pin->nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0); } diff --git a/sound/hda/codecs/sigmatel.c b/sound/hda/codecs/sigmatel.c index acbbc7c3508b..4ff80a65168f 100644 --- a/sound/hda/codecs/sigmatel.c +++ b/sound/hda/codecs/sigmatel.c @@ -311,12 +311,12 @@ static void stac_gpio_set(struct hda_codec *codec, unsigned int mask, snd_hda_codec_write(codec, fg, 0, AC_VERB_SET_GPIO_MASK, gpiomask); - snd_hda_codec_read(codec, fg, 0, + snd_hda_codec_write_sync(codec, fg, 0, AC_VERB_SET_GPIO_DIRECTION, gpiodir); /* sync */ msleep(1); - snd_hda_codec_read(codec, fg, 0, + snd_hda_codec_write_sync(codec, fg, 0, AC_VERB_SET_GPIO_DATA, gpiostate); /* sync */ } diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index 5123df32ad89..3ac4bf6005d6 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -606,9 +606,8 @@ void snd_hda_shutup_pins(struct hda_codec *codec) if (codec->bus->shutdown) return; snd_array_for_each(&codec->init_pins, i, pin) { - /* use read here for syncing after issuing each verb */ - snd_hda_codec_read(codec, pin->nid, 0, - AC_VERB_SET_PIN_WIDGET_CONTROL, 0); + snd_hda_codec_write_sync(codec, pin->nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0); } codec->pins_shutup = 1; } @@ -2794,9 +2793,9 @@ static unsigned int hda_set_power_state(struct hda_codec *codec, if (codec->power_filter) state = codec->power_filter(codec, fg, state); if (state == power_state || power_state != AC_PWRST_D3) - snd_hda_codec_read(codec, fg, flags, - AC_VERB_SET_POWER_STATE, - state); + snd_hda_codec_write_sync(codec, fg, flags, + AC_VERB_SET_POWER_STATE, + state); snd_hda_codec_set_power_to_all(codec, fg, power_state); } state = snd_hda_sync_power_state(codec, fg, power_state); From cd8fd5a0566e0d93fbd408e6b06ca484a78b5ccd Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:15 +0200 Subject: [PATCH 317/359] ALSA: hda: Add a simple GPIO setup helper function Introduce a common GPIO setup helper function, so that we can clean up the open code found in many codec drivers later. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-3-tiwai@suse.de --- include/sound/hda_codec.h | 4 ++++ sound/hda/common/codec.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h index 292d6024388b..24581080e26a 100644 --- a/include/sound/hda_codec.h +++ b/include/sound/hda_codec.h @@ -481,6 +481,10 @@ void snd_hda_unlock_devices(struct hda_bus *bus); void snd_hda_bus_reset(struct hda_bus *bus); void snd_hda_bus_reset_codecs(struct hda_bus *bus); +void snd_hda_codec_set_gpio(struct hda_codec *codec, unsigned int mask, + unsigned int dir, unsigned int data, + unsigned int delay); + int snd_hda_codec_set_name(struct hda_codec *codec, const char *name); /* diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index 3ac4bf6005d6..c2af2511a831 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -4052,6 +4052,35 @@ void snd_hda_bus_reset_codecs(struct hda_bus *bus) } } +/** + * snd_hda_codec_set_gpio - Set up GPIO bits for AFG + * @codec: the HDA codec + * @mask: GPIO bitmask + * @dir: GPIO direction bits + * @data: GPIO data bits + * @delay: the delay in msec before writing GPIO data bits + */ +void snd_hda_codec_set_gpio(struct hda_codec *codec, unsigned int mask, + unsigned int dir, unsigned int data, + unsigned int delay) +{ + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_MASK, mask); + if (delay) { + snd_hda_codec_write_sync(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DIRECTION, dir); + msleep(delay); + snd_hda_codec_write_sync(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, data); + } else { + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DIRECTION, dir); + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, data); + } +} +EXPORT_SYMBOL_GPL(snd_hda_codec_set_gpio); + /** * snd_print_pcm_bits - Print the supported PCM fmt bits to the string buffer * @pcm: PCM caps bits From d19ecd85a245a2052a502f72bee83982f535c2e6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:16 +0200 Subject: [PATCH 318/359] ALSA: hda/realtek: Clean up with snd_hda_codec_set_gpio() Use a new helper function to clean up the code. Along with it, make alc_write_gpio() static as well, which is used only locally in realtek.c. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-4-tiwai@suse.de --- sound/hda/codecs/realtek/realtek.c | 13 ++++--------- sound/hda/codecs/realtek/realtek.h | 1 - 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/sound/hda/codecs/realtek/realtek.c b/sound/hda/codecs/realtek/realtek.c index 39a1ead3b743..d9b2f1993eaf 100644 --- a/sound/hda/codecs/realtek/realtek.c +++ b/sound/hda/codecs/realtek/realtek.c @@ -123,22 +123,17 @@ void alc_update_gpio_data(struct hda_codec *codec, unsigned int mask, } EXPORT_SYMBOL_NS_GPL(alc_update_gpio_data, "SND_HDA_CODEC_REALTEK"); -void alc_write_gpio(struct hda_codec *codec) +static void alc_write_gpio(struct hda_codec *codec) { struct alc_spec *spec = codec->spec; if (!spec->gpio_mask) return; - snd_hda_codec_write(codec, codec->core.afg, 0, - AC_VERB_SET_GPIO_MASK, spec->gpio_mask); - snd_hda_codec_write(codec, codec->core.afg, 0, - AC_VERB_SET_GPIO_DIRECTION, spec->gpio_dir); - if (spec->gpio_write_delay) - msleep(1); - alc_write_gpio_data(codec); + snd_hda_codec_set_gpio(codec, spec->gpio_mask, spec->gpio_dir, + spec->gpio_data, + spec->gpio_write_delay ? 1 : 0); } -EXPORT_SYMBOL_NS_GPL(alc_write_gpio, "SND_HDA_CODEC_REALTEK"); void alc_fixup_gpio(struct hda_codec *codec, int action, unsigned int mask) { diff --git a/sound/hda/codecs/realtek/realtek.h b/sound/hda/codecs/realtek/realtek.h index b2a919904c4c..2ca15a6c2a08 100644 --- a/sound/hda/codecs/realtek/realtek.h +++ b/sound/hda/codecs/realtek/realtek.h @@ -171,7 +171,6 @@ void alc_setup_gpio(struct hda_codec *codec, unsigned int mask); void alc_write_gpio_data(struct hda_codec *codec); void alc_update_gpio_data(struct hda_codec *codec, unsigned int mask, bool on); -void alc_write_gpio(struct hda_codec *codec); /* common GPIO fixups */ void alc_fixup_gpio(struct hda_codec *codec, int action, unsigned int mask); From ef27d8ce0a3b55744b86e1866c62dc7537749180 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:17 +0200 Subject: [PATCH 319/359] ALSA: hda/alc662: Simplify the quirk for CSL Unity BF24B The previous implementation of the quirk for CSL Unity BF24B in commit de65275fc94e ("ALSA: hda/realtek: Add quirk for CSL Unity BF24B") introduced the unnecessary GPIO caching which leads to a superfluous write at each init/resume. Use the new helper to write GPIO bits directly for optimization. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-5-tiwai@suse.de --- sound/hda/codecs/realtek/alc662.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c index 3abe41c7315c..2cf3664a35ff 100644 --- a/sound/hda/codecs/realtek/alc662.c +++ b/sound/hda/codecs/realtek/alc662.c @@ -258,19 +258,11 @@ static void alc_fixup_headset_mode_alc668(struct hda_codec *codec, static void alc662_fixup_csl_amp(struct hda_codec *codec, const struct hda_fixup *fix, int action) { - struct alc_spec *spec = codec->spec; - - switch (action) { - case HDA_FIXUP_ACT_PRE_PROBE: - spec->gpio_mask |= 0x03; - spec->gpio_dir |= 0x03; - break; - case HDA_FIXUP_ACT_INIT: + if (action == HDA_FIXUP_ACT_INIT) { /* need to toggle GPIO to enable the amp */ - alc_update_gpio_data(codec, 0x03, true); + snd_hda_codec_set_gpio(codec, 0x03, 0x03, 0x03, 0); msleep(100); - alc_update_gpio_data(codec, 0x03, false); - break; + snd_hda_codec_set_gpio(codec, 0x03, 0x03, 0x00, 0); } } From 735b3739517b18983cb0347ba56d76bf1018e0c4 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:18 +0200 Subject: [PATCH 320/359] ALSA: hda/analog: Fix GPIO verb orders So far we used the verb cache to restore the GPIO mask, direction and data bits at PM resume. But, due to the nature of the cache resume mechanism, the calling order isn't guaranteed, and this might lead to some inconsistency at the restored state. For assuring the GPIO verb orders, use the new GPIO helper function to explicitly set up the GPIO bits, instead of using the codec verb caches, while keeping the current data bits in ad198x_spec. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-6-tiwai@suse.de --- sound/hda/codecs/analog.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sound/hda/codecs/analog.c b/sound/hda/codecs/analog.c index 11b1d30b23fd..1ba8ae54e25e 100644 --- a/sound/hda/codecs/analog.c +++ b/sound/hda/codecs/analog.c @@ -38,6 +38,8 @@ struct ad198x_spec { unsigned int beep_amp; /* beep amp value, set via set_beep_amp() */ int num_smux_conns; + + unsigned int gpio_data; }; @@ -934,9 +936,9 @@ static void ad1884_vmaster_hp_gpio_hook(void *private_data, int enabled) if (spec->eapd_nid) ad_vmaster_eapd_hook(private_data, enabled); - snd_hda_codec_write_cache(codec, 0x01, 0, - AC_VERB_SET_GPIO_DATA, - enabled ? 0x00 : 0x02); + spec->gpio_data = enabled ? 0x00 : 0x02; + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, spec->gpio_data); } static void ad1884_fixup_hp_eapd(struct hda_codec *codec, @@ -948,12 +950,7 @@ static void ad1884_fixup_hp_eapd(struct hda_codec *codec, case HDA_FIXUP_ACT_PRE_PROBE: spec->gen.vmaster_mute.hook = ad1884_vmaster_hp_gpio_hook; spec->gen.own_eapd_ctl = 1; - snd_hda_codec_write_cache(codec, 0x01, 0, - AC_VERB_SET_GPIO_MASK, 0x02); - snd_hda_codec_write_cache(codec, 0x01, 0, - AC_VERB_SET_GPIO_DIRECTION, 0x02); - snd_hda_codec_write_cache(codec, 0x01, 0, - AC_VERB_SET_GPIO_DATA, 0x02); + spec->gpio_data = 0x02; break; case HDA_FIXUP_ACT_PROBE: if (spec->gen.autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT) @@ -961,6 +958,9 @@ static void ad1884_fixup_hp_eapd(struct hda_codec *codec, else spec->eapd_nid = spec->gen.autocfg.speaker_pins[0]; break; + case HDA_FIXUP_ACT_INIT: + snd_hda_codec_set_gpio(codec, 0x02, 0x02, spec->gpio_data, 0); + break; } } From 37e4fccc21aa0e812d77e3e3f7b8373475ee3715 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:19 +0200 Subject: [PATCH 321/359] ALSA: hda/sigmatel: Clean up with the new GPIO helper Use the new GPIO helper function to clean up the open code. Merely a code refactoring, and no behavior change. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-7-tiwai@suse.de --- sound/hda/codecs/sigmatel.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/sound/hda/codecs/sigmatel.c b/sound/hda/codecs/sigmatel.c index 4ff80a65168f..ee3bd21adc36 100644 --- a/sound/hda/codecs/sigmatel.c +++ b/sound/hda/codecs/sigmatel.c @@ -309,15 +309,7 @@ static void stac_gpio_set(struct hda_codec *codec, unsigned int mask, /* Configure GPIOx as CMOS */ snd_hda_codec_write(codec, fg, 0, 0x7e7, 0); - snd_hda_codec_write(codec, fg, 0, - AC_VERB_SET_GPIO_MASK, gpiomask); - snd_hda_codec_write_sync(codec, fg, 0, - AC_VERB_SET_GPIO_DIRECTION, gpiodir); /* sync */ - - msleep(1); - - snd_hda_codec_write_sync(codec, fg, 0, - AC_VERB_SET_GPIO_DATA, gpiostate); /* sync */ + snd_hda_codec_set_gpio(codec, gpiomask, gpiodir, gpiostate, 1); } /* hook for controlling mic-mute LED GPIO */ From d35f8e8c6fc5c92c96be38e9ca94d99233ce1ddd Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:20 +0200 Subject: [PATCH 322/359] ALSA: hda/ca0132: Clean up with the new GPIO helper Use the new GPIO helper function to clean up the open code. Merely a code refactoring, and no behavior change. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-8-tiwai@suse.de --- sound/hda/codecs/ca0132.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c index a0677d7da8e2..ad533b04ab29 100644 --- a/sound/hda/codecs/ca0132.c +++ b/sound/hda/codecs/ca0132.c @@ -3755,22 +3755,12 @@ static void ca0132_gpio_setup(struct hda_codec *codec) switch (ca0132_quirk(spec)) { case QUIRK_SBZ: - snd_hda_codec_write(codec, 0x01, 0, - AC_VERB_SET_GPIO_DIRECTION, 0x07); - snd_hda_codec_write(codec, 0x01, 0, - AC_VERB_SET_GPIO_MASK, 0x07); - snd_hda_codec_write(codec, 0x01, 0, - AC_VERB_SET_GPIO_DATA, 0x04); + snd_hda_codec_set_gpio(codec, 0x07, 0x07, 0x04, 0); snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, 0x06); break; case QUIRK_R3DI: - snd_hda_codec_write(codec, 0x01, 0, - AC_VERB_SET_GPIO_DIRECTION, 0x1E); - snd_hda_codec_write(codec, 0x01, 0, - AC_VERB_SET_GPIO_MASK, 0x1F); - snd_hda_codec_write(codec, 0x01, 0, - AC_VERB_SET_GPIO_DATA, 0x0C); + snd_hda_codec_set_gpio(codec, 0x1F, 0x1E, 0x0C, 0); break; default: break; From daadb7fce1b53336acb195f34bd42d79754afa0e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:21 +0200 Subject: [PATCH 323/359] ALSA: hda/cirrus: Clean up with the new GPIO helper Use the new GPIO helper function to clean up the open code. Merely a code refactoring, and no behavior change. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-9-tiwai@suse.de --- sound/hda/codecs/cirrus/cs420x.c | 11 +++-------- sound/hda/codecs/cirrus/cs421x.c | 11 +++-------- sound/hda/codecs/cirrus/cs8409.c | 22 ++++++---------------- 3 files changed, 12 insertions(+), 32 deletions(-) diff --git a/sound/hda/codecs/cirrus/cs420x.c b/sound/hda/codecs/cirrus/cs420x.c index 52eda0a338ea..42559edbba05 100644 --- a/sound/hda/codecs/cirrus/cs420x.c +++ b/sound/hda/codecs/cirrus/cs420x.c @@ -264,14 +264,9 @@ static int cs_init(struct hda_codec *codec) snd_hda_gen_init(codec); - if (spec->gpio_mask) { - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, - spec->gpio_mask); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, - spec->gpio_dir); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, - spec->gpio_data); - } + if (spec->gpio_mask) + snd_hda_codec_set_gpio(codec, spec->gpio_mask, spec->gpio_dir, + spec->gpio_data, 0); if (spec->vendor_nid == CS420X_VENDOR_NID) { init_input_coef(codec); diff --git a/sound/hda/codecs/cirrus/cs421x.c b/sound/hda/codecs/cirrus/cs421x.c index c8349a2c5a36..645b06599e5f 100644 --- a/sound/hda/codecs/cirrus/cs421x.c +++ b/sound/hda/codecs/cirrus/cs421x.c @@ -442,14 +442,9 @@ static int cs421x_init(struct hda_codec *codec) snd_hda_gen_init(codec); - if (spec->gpio_mask) { - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, - spec->gpio_mask); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, - spec->gpio_dir); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, - spec->gpio_data); - } + if (spec->gpio_mask) + snd_hda_codec_set_gpio(codec, spec->gpio_mask, spec->gpio_dir, + spec->gpio_data, 0); cs4210_spdif_automute(codec, NULL); diff --git a/sound/hda/codecs/cirrus/cs8409.c b/sound/hda/codecs/cirrus/cs8409.c index 2d8f482e6474..c43ff3ef75b6 100644 --- a/sound/hda/codecs/cirrus/cs8409.c +++ b/sound/hda/codecs/cirrus/cs8409.c @@ -1042,14 +1042,9 @@ static void cs8409_cs42l42_hw_init(struct hda_codec *codec) struct cs8409_spec *spec = codec->spec; struct sub_codec *cs42l42 = spec->scodecs[CS8409_CODEC0]; - if (spec->gpio_mask) { - snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_MASK, - spec->gpio_mask); - snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DIRECTION, - spec->gpio_dir); - snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DATA, - spec->gpio_data); - } + if (spec->gpio_mask) + snd_hda_codec_set_gpio(codec, spec->gpio_mask, spec->gpio_dir, + spec->gpio_data, 0); for (; seq->nid; seq++) cs8409_vendor_coef_set(codec, seq->cir, seq->coeff); @@ -1442,14 +1437,9 @@ static void dolphin_hw_init(struct hda_codec *codec) struct sub_codec *cs42l42; int i; - if (spec->gpio_mask) { - snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_MASK, - spec->gpio_mask); - snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DIRECTION, - spec->gpio_dir); - snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DATA, - spec->gpio_data); - } + if (spec->gpio_mask) + snd_hda_codec_set_gpio(codec, spec->gpio_mask, spec->gpio_dir, + spec->gpio_data, 0); for (; seq->nid; seq++) cs8409_vendor_coef_set(codec, seq->cir, seq->coeff); From 9851bc2b9013674ba7dc151843f29b6255fedba3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:22 +0200 Subject: [PATCH 324/359] ALSA: hda/conexant: Clean up with the new GPIO helper Use the new GPIO helper function to clean up the open code. Merely a code refactoring, and no behavior change. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-10-tiwai@suse.de --- sound/hda/codecs/conexant.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index aa726eb323eb..3a9717df39b4 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -154,14 +154,8 @@ static void cxt_init_gpio_led(struct hda_codec *codec) struct conexant_spec *spec = codec->spec; unsigned int mask = spec->gpio_mute_led_mask | spec->gpio_mic_led_mask; - if (mask) { - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, - mask); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, - mask); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, - spec->gpio_led); - } + if (mask) + snd_hda_codec_set_gpio(codec, mask, mask, spec->gpio_led, 0); } static void cx_fixup_headset_recog(struct hda_codec *codec) @@ -775,9 +769,7 @@ static void cxt_setup_gpio_unmute(struct hda_codec *codec, { if (gpio_mute_mask) { // set gpio data to 0. - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, 0); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, gpio_mute_mask); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, gpio_mute_mask); + snd_hda_codec_set_gpio(codec, gpio_mute_mask, gpio_mute_mask, 0, 0); snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_STICKY_MASK, 0); } } From c2938a83a257ca1e3a8e74b385f543d6bd6eab8b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:23 +0200 Subject: [PATCH 325/359] ALSA: hda/senarytech: Clean up with the new GPIO helper Use the new GPIO helper function to clean up the open code. Merely a code refactoring, and no behavior change. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-11-tiwai@suse.de --- sound/hda/codecs/senarytech.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/sound/hda/codecs/senarytech.c b/sound/hda/codecs/senarytech.c index 29b554cdd81d..3ee8bc0ea3ab 100644 --- a/sound/hda/codecs/senarytech.c +++ b/sound/hda/codecs/senarytech.c @@ -159,14 +159,8 @@ static void senary_init_gpio_led(struct hda_codec *codec) struct senary_spec *spec = codec->spec; unsigned int mask = spec->gpio_mute_led_mask | spec->gpio_mic_led_mask; - if (mask) { - snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_MASK, - mask); - snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_DIRECTION, - mask); - snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_DATA, - spec->gpio_led); - } + if (mask) + snd_hda_codec_set_gpio(codec, mask, mask, spec->gpio_led, 0); } static int senary_init(struct hda_codec *codec) From 4f84e6caf38b05991b3b2afc0ddf4e48c2752d1d Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Thu, 9 Apr 2026 02:33:05 +0800 Subject: [PATCH 326/359] ALSA: usb-audio: Add quirk flags for Feaulle Rainbow Feaulle Rainbow is a wired USB-C dynamic in-ear monitor (IEM) featuring active noise cancellation (ANC). The supported sample rates are 48000Hz and 96000Hz at 16bit or 24bit, but it does not support reading the current sample rate and results in an error message printed to kmsg. Set QUIRK_FLAG_GET_SAMPLE_RATE to skip the sample rate check. Its playback mixer reports val = -15360/0/128. Setting -15360 (-60dB) mutes the playback, so QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE is needed. Add a quirk table entry matching VID/PID=0x0e0b/0xfa01 and applying the mentioned quirk flags, so that it can work properly. Quirky device sample: usb 7-1: New USB device found, idVendor=0e0b, idProduct=fa01, bcdDevice= 1.00 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: Feaulle Rainbow usb 7-1: Manufacturer: Generic usb 7-1: SerialNumber: 20210726905926 Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20260409-feaulle-rainbow-v1-1-09179e09000d@rong.moe Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 6cf85922b7be..519d9d1a2a41 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2337,6 +2337,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x0d8c, 0x0014, /* C-Media */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x0e0b, 0xfa01, /* Feaulle Rainbow */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x0ecb, 0x205c, /* JBL Quantum610 Wireless */ QUIRK_FLAG_FIXED_RATE), DEVICE_FLG(0x0ecb, 0x2069, /* JBL Quantum810 Wireless */ From 01f218d439acd5e129d214ea57e760ee2e34e869 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 16:37:29 +0200 Subject: [PATCH 327/359] ALSA: hda/alc269: Drop superfluous GPIO write at resume alc269_resume() has an extra code to write GPIO data, but this is basically already done in the standard alc_init(), hence it's superfluous. Let's drop the code. Since all external callers of alc_write_gpio_data() are gone after this, fold the only usage of alc_write_gpio_data() into the caller and drop the export as well. Link: https://patch.msgid.link/20260409143735.1412134-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 7 ------- sound/hda/codecs/realtek/realtek.c | 12 ++---------- sound/hda/codecs/realtek/realtek.h | 1 - 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 9a799919e0c8..0a5eadec3ef6 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -1004,13 +1004,6 @@ static int alc269_resume(struct hda_codec *codec) snd_hda_regmap_sync(codec); hda_call_check_power_status(codec, 0x01); - /* on some machine, the BIOS will clear the codec gpio data when enter - * suspend, and won't restore the data after resume, so we restore it - * in the driver. - */ - if (spec->gpio_data) - alc_write_gpio_data(codec); - if (spec->has_alc5505_dsp) alc5505_dsp_resume(codec); diff --git a/sound/hda/codecs/realtek/realtek.c b/sound/hda/codecs/realtek/realtek.c index d9b2f1993eaf..db365a746b1a 100644 --- a/sound/hda/codecs/realtek/realtek.c +++ b/sound/hda/codecs/realtek/realtek.c @@ -99,15 +99,6 @@ void alc_setup_gpio(struct hda_codec *codec, unsigned int mask) } EXPORT_SYMBOL_NS_GPL(alc_setup_gpio, "SND_HDA_CODEC_REALTEK"); -void alc_write_gpio_data(struct hda_codec *codec) -{ - struct alc_spec *spec = codec->spec; - - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, - spec->gpio_data); -} -EXPORT_SYMBOL_NS_GPL(alc_write_gpio_data, "SND_HDA_CODEC_REALTEK"); - void alc_update_gpio_data(struct hda_codec *codec, unsigned int mask, bool on) { @@ -119,7 +110,8 @@ void alc_update_gpio_data(struct hda_codec *codec, unsigned int mask, else spec->gpio_data &= ~mask; if (oldval != spec->gpio_data) - alc_write_gpio_data(codec); + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, + spec->gpio_data); } EXPORT_SYMBOL_NS_GPL(alc_update_gpio_data, "SND_HDA_CODEC_REALTEK"); diff --git a/sound/hda/codecs/realtek/realtek.h b/sound/hda/codecs/realtek/realtek.h index 2ca15a6c2a08..de95642bb648 100644 --- a/sound/hda/codecs/realtek/realtek.h +++ b/sound/hda/codecs/realtek/realtek.h @@ -168,7 +168,6 @@ void alc_process_coef_fw(struct hda_codec *codec, const struct coef_fw *fw); * GPIO helpers */ void alc_setup_gpio(struct hda_codec *codec, unsigned int mask); -void alc_write_gpio_data(struct hda_codec *codec); void alc_update_gpio_data(struct hda_codec *codec, unsigned int mask, bool on); From c271b0815f45078342bc4e778683c86fdc45fde7 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Thu, 9 Apr 2026 17:43:25 +0100 Subject: [PATCH 328/359] ASoC: SDCA: Correct kernel doc for sdca_irq_cleanup() Fix typo of function argument name in the kernel doc. Fixes: 0b8757b220f9 ("ASoC: SDCA: Unregister IRQ handlers on module remove") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202604090800.koxM6j6O-lkp@intel.com/ Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260409164328.3999434-2-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_interrupts.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c index 5cdabf8ae9da..475aebcafe3f 100644 --- a/sound/soc/sdca/sdca_interrupts.c +++ b/sound/soc/sdca/sdca_interrupts.c @@ -555,7 +555,7 @@ EXPORT_SYMBOL_NS_GPL(sdca_irq_populate, "SND_SOC_SDCA"); /** * sdca_irq_cleanup - Free all the individual IRQs for an SDCA Function - * @sdev: Device pointer against which the sdca_interrupt_info was allocated. + * @dev: Device pointer against which the sdca_interrupt_info was allocated. * @function: Pointer to the SDCA Function. * @info: Pointer to the SDCA interrupt info for this device. * From 7936490e04733ade80d0d445529c0a6de0f95515 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 9 Apr 2026 17:43:26 +0100 Subject: [PATCH 329/359] ASoC: SDCA: Fix cleanup inversion in class driver Fix inverted cleanup of the SoundWire IRQ and the function drivers that use it. The devm cleanup function to call sdca_dev_unregister_functions() was being registered at the end of class_sdw_probe(). The bus core creates the parent SoundWire IRQ handler after class_sdw_probe() has returned, and it registers a devm cleanup handler at the same time. This led to a cleanup inversion where the devm cleanup for the parent Soundwire IRQ runs before the handler that removes the function drivers. So the parent IRQ is destroyed before the function drivers had a chance to do any cleanup and remove their IRQ handlers. Move the registrations of the function driver cleanup into class_boot_work() after the function drivers are registered, so that it runs before the cleanup of the parent SoundWire IRQ handler. Fixes: 2d877d0659cb ("ASoC: SDCA: Add basic SDCA class driver") Signed-off-by: Richard Fitzgerald Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260409164328.3999434-3-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_class.c | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c index 918b638acb57..5def6ae2d99f 100644 --- a/sound/soc/sdca/sdca_class.c +++ b/sound/soc/sdca/sdca_class.c @@ -137,6 +137,13 @@ static const struct regmap_config class_dev_regmap_config = { .unlock = class_regmap_unlock, }; +static void class_remove_functions(void *data) +{ + struct sdca_class_drv *drv = data; + + sdca_dev_unregister_functions(drv->sdw); +} + static void class_boot_work(struct work_struct *work) { struct sdca_class_drv *drv = container_of(work, @@ -157,6 +164,11 @@ static void class_boot_work(struct work_struct *work) if (ret) goto err; + /* Ensure function drivers are removed before the IRQ is destroyed */ + ret = devm_add_action_or_reset(drv->dev, class_remove_functions, drv); + if (ret) + goto err; + dev_dbg(drv->dev, "boot work complete\n"); pm_runtime_mark_last_busy(drv->dev); @@ -168,15 +180,6 @@ err: pm_runtime_put_sync(drv->dev); } -static void class_dev_remove(void *data) -{ - struct sdca_class_drv *drv = data; - - cancel_work_sync(&drv->boot_work); - - sdca_dev_unregister_functions(drv->sdw); -} - static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id) { struct device *dev = &sdw->dev; @@ -230,15 +233,19 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id if (ret) return ret; - ret = devm_add_action_or_reset(dev, class_dev_remove, drv); - if (ret) - return ret; - queue_work(system_long_wq, &drv->boot_work); return 0; } +static void class_sdw_remove(struct sdw_slave *sdw) +{ + struct device *dev = &sdw->dev; + struct sdca_class_drv *drv = dev_get_drvdata(dev); + + cancel_work_sync(&drv->boot_work); +} + static int class_suspend(struct device *dev) { struct sdca_class_drv *drv = dev_get_drvdata(dev); @@ -328,6 +335,7 @@ static struct sdw_driver class_sdw_driver = { }, .probe = class_sdw_probe, + .remove = class_sdw_remove, .id_table = class_sdw_id, .ops = &class_sdw_ops, }; From 87ceac0a98e92f4efd031b5b9ab49ab5645d1c7e Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Thu, 9 Apr 2026 17:43:27 +0100 Subject: [PATCH 330/359] ASoC: SDCA: Tidy up irq_enable_flags()/sdca_irq_disable() In irq_enable_flags() and sdca_irq_disable() there is a NULL check on the interrupt data pointer, however this is just pulled from an array so can never be NULL. This was likely left over from an earlier version that looked up the data in a different way. Replace the check with checking for the IRQ itself being non-zero. Whilst here also drop the sdca_interrupt structure down into the loop within the function to better match the style of the rest of the code in this file. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260409164328.3999434-4-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_interrupts.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c index 475aebcafe3f..3d7eb1996724 100644 --- a/sound/soc/sdca/sdca_interrupts.c +++ b/sound/soc/sdca/sdca_interrupts.c @@ -630,13 +630,12 @@ EXPORT_SYMBOL_NS_GPL(sdca_irq_allocate, "SND_SOC_SDCA"); static void irq_enable_flags(struct sdca_function_data *function, struct sdca_interrupt_info *info, bool early) { - struct sdca_interrupt *interrupt; int i; for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) { - interrupt = &info->irqs[i]; + struct sdca_interrupt *interrupt = &info->irqs[i]; - if (!interrupt || interrupt->function != function) + if (!interrupt->irq || interrupt->function != function) continue; switch (SDCA_CTL_TYPE(interrupt->entity->type, @@ -689,13 +688,12 @@ EXPORT_SYMBOL_NS_GPL(sdca_irq_enable, "SND_SOC_SDCA"); void sdca_irq_disable(struct sdca_function_data *function, struct sdca_interrupt_info *info) { - struct sdca_interrupt *interrupt; int i; for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) { - interrupt = &info->irqs[i]; + struct sdca_interrupt *interrupt = &info->irqs[i]; - if (!interrupt || interrupt->function != function) + if (!interrupt->irq || interrupt->function != function) continue; disable_irq(interrupt->irq); From 833011cbe663b4ab49c125e09d02c900b599e13b Mon Sep 17 00:00:00 2001 From: Qinghua Zhao Date: Thu, 9 Apr 2026 21:52:13 +0800 Subject: [PATCH 331/359] ASoC: tegra: Fix spelling error 'recieved' -> 'received' Fix typo in comment where 'recieved' should be 'received'. Signed-off-by: Qinghua Zhao Link: https://patch.msgid.link/20260409135213.16558-1-zqh1630@126.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra20_spdif.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/tegra/tegra20_spdif.h b/sound/soc/tegra/tegra20_spdif.h index ff4b79e2052f..26ab8d5c7f99 100644 --- a/sound/soc/tegra/tegra20_spdif.h +++ b/sound/soc/tegra/tegra20_spdif.h @@ -171,7 +171,7 @@ /* * RX channel block data receive status: - * 0=entire block not recieved yet. + * 0=entire block not received yet. * 1=received entire block of channel status, */ #define TEGRA20_SPDIF_STATUS_IS_C (1 << 21) From fbb1f8ba4e2d847859d04220c4a775996f072d57 Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Thu, 9 Apr 2026 14:01:01 +0800 Subject: [PATCH 332/359] ASoC: rt1320-sdw: kcontrol for brown-out feature update Create a kcontrol to enable or disable brown-out dynamically. Signed-off-by: Jack Yu Link: https://patch.msgid.link/20260409060102.4177554-1-jack.yu@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt1320-sdw.c | 42 +++++++++++++++++++++++++++++++++++ sound/soc/codecs/rt1320-sdw.h | 1 + 2 files changed, 43 insertions(+) diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c index 8bb7e8497c72..168d2f391723 100644 --- a/sound/soc/codecs/rt1320-sdw.c +++ b/sound/soc/codecs/rt1320-sdw.c @@ -2486,6 +2486,45 @@ static int rt1320_rae_update_put(struct snd_kcontrol *kcontrol, return 0; } +static int rt1320_brown_out_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt1320_sdw_priv *rt1320 = snd_soc_component_get_drvdata(component); + int ret, changed = 0; + + if (!rt1320->hw_init) + return 0; + + ret = pm_runtime_resume(component->dev); + if (ret < 0 && ret != -EACCES) + return ret; + + if (rt1320->brown_out != ucontrol->value.integer.value[0]) { + changed = 1; + rt1320->brown_out = ucontrol->value.integer.value[0]; + } + + if (rt1320->brown_out == 0) + regmap_write(rt1320->regmap, 0xdb03, 0x00); + else + regmap_write(rt1320->regmap, 0xdb03, 0xf0); + + + return changed; +} + +static int rt1320_brown_out_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt1320_sdw_priv *rt1320 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = rt1320->brown_out; + + return 0; +} + static int rt1320_r0_temperature_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -2545,6 +2584,8 @@ static const struct snd_kcontrol_new rt1320_snd_controls[] = { rt1320_r0_temperature_get, rt1320_r0_temperature_put), SOC_SINGLE_EXT("RAE Update", SND_SOC_NOPM, 0, 1, 0, rt1320_rae_update_get, rt1320_rae_update_put), + SOC_SINGLE_EXT("Brown Out Switch", SND_SOC_NOPM, 0, 1, 0, + rt1320_brown_out_get, rt1320_brown_out_put), }; static const struct snd_kcontrol_new rt1320_spk_l_dac = @@ -2904,6 +2945,7 @@ static int rt1320_sdw_init(struct device *dev, struct regmap *regmap, rt1320->fu_dapm_mute = true; rt1320->fu_mixer_mute[0] = rt1320->fu_mixer_mute[1] = rt1320->fu_mixer_mute[2] = rt1320->fu_mixer_mute[3] = true; + rt1320->brown_out = 1; INIT_WORK(&rt1320->load_dspfw_work, rt1320_load_dspfw_work); diff --git a/sound/soc/codecs/rt1320-sdw.h b/sound/soc/codecs/rt1320-sdw.h index 5a9f496dd848..4a1d3fc4c8ee 100644 --- a/sound/soc/codecs/rt1320-sdw.h +++ b/sound/soc/codecs/rt1320-sdw.h @@ -159,6 +159,7 @@ struct rt1320_sdw_priv { bool hw_init; bool first_hw_init; int version_id; + int brown_out; unsigned int dev_id; bool fu_dapm_mute; bool fu_mixer_mute[4]; From 0cb7aa965ad02e90ba7d6bf847f3de07e8d0c05e Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 9 Apr 2026 23:39:31 +0200 Subject: [PATCH 333/359] ASoC: uda1380: Modernize the driver This codec driver depended on the legacy GPIO API, and nothing in the kernel is defining the platform data, so get rid of this. Two in-kernel device trees are defining this codec using undocumented device tree properties, so support these for now. The same properties can be defined using software nodes if board files are desired. The device tree use the "-gpio" rather than "-gpios" suffix but the GPIO DT parser will deal with that. Since there may be out of tree users, migrate to GPIO descriptors, drop the platform data that is unused, and assign the dac_clk the value that was used in all platforms found in a historical dig, and support setting the clock to the PLL using the undocumented device tree property. Add some menuconfig so the codec can be selected and tested. Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260409-asoc-uda1380-v3-1-b3d5a53f31be@kernel.org Signed-off-by: Mark Brown --- include/sound/uda1380.h | 19 ------------ sound/soc/codecs/Kconfig | 6 ++-- sound/soc/codecs/uda1380.c | 63 ++++++++++++++++++++------------------ 3 files changed, 37 insertions(+), 51 deletions(-) delete mode 100644 include/sound/uda1380.h diff --git a/include/sound/uda1380.h b/include/sound/uda1380.h deleted file mode 100644 index 2e42ea2d0cfd..000000000000 --- a/include/sound/uda1380.h +++ /dev/null @@ -1,19 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * UDA1380 ALSA SoC Codec driver - * - * Copyright 2009 Philipp Zabel - */ - -#ifndef __UDA1380_H -#define __UDA1380_H - -struct uda1380_platform_data { - int gpio_power; - int gpio_reset; - int dac_clk; -#define UDA1380_DAC_CLK_SYSCLK 0 -#define UDA1380_DAC_CLK_WSPLL 1 -}; - -#endif /* __UDA1380_H */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index ca3e47db126e..cf94a1c756e0 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -2373,9 +2373,11 @@ config SND_SOC_UDA1342 mic inputs), stereo audio DAC, with basic audio processing. config SND_SOC_UDA1380 - tristate + tristate "Philips UDA1380 CODEC" depends on I2C - depends on GPIOLIB_LEGACY + help + The UDA1380 codec is used in the HTC Magician and on a number of + Samsung reference boards, as well as the LPC32xx series. config SND_SOC_WCD_CLASSH tristate diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index 63c3ea878fcf..55c83d95bfba 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -16,16 +16,19 @@ #include #include #include -#include +#include #include #include +#include #include #include #include #include #include #include -#include + +#define UDA1380_DAC_CLK_SYSCLK 0 +#define UDA1380_DAC_CLK_WSPLL 1 #include "uda1380.h" @@ -36,6 +39,8 @@ struct uda1380_priv { struct work_struct work; struct i2c_client *i2c; u16 *reg_cache; + struct gpio_desc *power; + struct gpio_desc *reset; }; /* @@ -169,13 +174,12 @@ static void uda1380_sync_cache(struct snd_soc_component *component) static int uda1380_reset(struct snd_soc_component *component) { - struct uda1380_platform_data *pdata = component->dev->platform_data; struct uda1380_priv *uda1380 = snd_soc_component_get_drvdata(component); - if (gpio_is_valid(pdata->gpio_reset)) { - gpio_set_value(pdata->gpio_reset, 1); + if (uda1380->reset) { + gpiod_set_value(uda1380->reset, 1); mdelay(1); - gpio_set_value(pdata->gpio_reset, 0); + gpiod_set_value(uda1380->reset, 0); } else { u8 data[3]; @@ -608,9 +612,9 @@ static int uda1380_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 uda1380_priv *uda1380 = snd_soc_component_get_drvdata(component); int pm = uda1380_read_reg_cache(component, UDA1380_PM); int reg; - struct uda1380_platform_data *pdata = component->dev->platform_data; switch (level) { case SND_SOC_BIAS_ON: @@ -620,8 +624,8 @@ static int uda1380_set_bias_level(struct snd_soc_component *component, break; case SND_SOC_BIAS_STANDBY: if (snd_soc_dapm_get_bias_level(dapm) == SND_SOC_BIAS_OFF) { - if (gpio_is_valid(pdata->gpio_power)) { - gpio_set_value(pdata->gpio_power, 1); + if (uda1380->power) { + gpiod_set_value(uda1380->power, 1); mdelay(1); uda1380_reset(component); } @@ -631,10 +635,10 @@ static int uda1380_set_bias_level(struct snd_soc_component *component, uda1380_write(component, UDA1380_PM, 0x0); break; case SND_SOC_BIAS_OFF: - if (!gpio_is_valid(pdata->gpio_power)) + if (!uda1380->power) break; - gpio_set_value(pdata->gpio_power, 0); + gpiod_set_value(uda1380->power, 0); /* Mark mixer regs cache dirty to sync them with * codec regs on power on. @@ -713,13 +717,12 @@ static struct snd_soc_dai_driver uda1380_dai[] = { static int uda1380_probe(struct snd_soc_component *component) { - struct uda1380_platform_data *pdata =component->dev->platform_data; struct uda1380_priv *uda1380 = snd_soc_component_get_drvdata(component); int ret; uda1380->component = component; - if (!gpio_is_valid(pdata->gpio_power)) { + if (!uda1380->power) { ret = uda1380_reset(component); if (ret) return ret; @@ -728,7 +731,7 @@ static int uda1380_probe(struct snd_soc_component *component) INIT_WORK(&uda1380->work, uda1380_flush_work); /* set clock input */ - switch (pdata->dac_clk) { + switch (uda1380->dac_clk) { case UDA1380_DAC_CLK_SYSCLK: uda1380_write_reg_cache(component, UDA1380_CLK, 0); break; @@ -760,31 +763,31 @@ static const struct snd_soc_component_driver soc_component_dev_uda1380 = { static int uda1380_i2c_probe(struct i2c_client *i2c) { - struct uda1380_platform_data *pdata = i2c->dev.platform_data; + struct device *dev = &i2c->dev; struct uda1380_priv *uda1380; int ret; - if (!pdata) - return -EINVAL; - uda1380 = devm_kzalloc(&i2c->dev, sizeof(struct uda1380_priv), GFP_KERNEL); if (uda1380 == NULL) return -ENOMEM; - if (gpio_is_valid(pdata->gpio_reset)) { - ret = devm_gpio_request_one(&i2c->dev, pdata->gpio_reset, - GPIOF_OUT_INIT_LOW, "uda1380 reset"); - if (ret) - return ret; - } + uda1380->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(uda1380->reset)) + return dev_err_probe(dev, PTR_ERR(uda1380->reset), + "error obtaining reset GPIO\n"); + gpiod_set_consumer_name(uda1380->reset, "uda1380 reset"); - if (gpio_is_valid(pdata->gpio_power)) { - ret = devm_gpio_request_one(&i2c->dev, pdata->gpio_power, - GPIOF_OUT_INIT_LOW, "uda1380 power"); - if (ret) - return ret; - } + uda1380->power = devm_gpiod_get_optional(dev, "power", GPIOD_OUT_LOW); + if (IS_ERR(uda1380->power)) + return dev_err_probe(dev, PTR_ERR(uda1380->power), + "error obtaining power GPIO\n"); + gpiod_set_consumer_name(uda1380->power, "uda1380 power"); + + /* This is just some default */ + uda1380->dac_clk = UDA1380_DAC_CLK_SYSCLK; + if (device_property_match_string(dev, "dac-clk", "wspll") >= 0) + uda1380->dac_clk = UDA1380_DAC_CLK_WSPLL; uda1380->reg_cache = devm_kmemdup_array(&i2c->dev, uda1380_reg, ARRAY_SIZE(uda1380_reg), sizeof(uda1380_reg[0]), GFP_KERNEL); From 72dcd84938f5026dc44d0e7e1e68d9d571c113a0 Mon Sep 17 00:00:00 2001 From: Ihor Uzlov Date: Fri, 10 Apr 2026 11:45:53 +0200 Subject: [PATCH 334/359] ASoC: amd: yc: Add MSI Vector A16 HX A8WHG to quirk table Add the MSI Vector A16 HX A8WHG (board MS-15MM) to the DMI quirk table to enable DMIC support. This laptop uses an AMD Ryzen 9 7945HX (Dragon Range) with the ACP6x audio coprocessor (rev 0x62) and a Realtek ALC274 codec. The built-in digital microphone is connected via the ACP PDM interface and requires this DMI entry to be activated. Tested on MSI Vector A16 HX A8WHG with kernel 6.8.0-107 (Ubuntu 24.04). DMIC capture device appears as 'acp6x' and records audio correctly. Signed-off-by: Ihor Uzlov Link: https://patch.msgid.link/20260410094553.24654-1-igor.uzlov@gmail.com 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 85579c81378f..c5cf45881416 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -500,6 +500,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Bravo 17 D7VF"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vector A16 HX A8WHG"), + } + }, { .driver_data = &acp6x_card, .matches = { From 558f5228e1dbfa995b7303e20f26836525e85151 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 10 Apr 2026 11:45:00 +0100 Subject: [PATCH 335/359] ASoC: SDCA: Update text of FIXME A couple of attempts to correct this FIXME have been sent upstream but the situation is not quite a simple as the FIXME implies. Update the FIXME to include a better description of the situation. Link: https://lore.kernel.org/linux-sound/20260408085607.3813488-1-shumingf@realtek.com/ Link: https://lore.kernel.org/linux-sound/20260324-sdca-function-status-init-irq-v1-1-bba49417a4e0@gmail.com/ Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260410104500.163337-1-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_interrupts.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c index 3d7eb1996724..6e10b4e660d9 100644 --- a/sound/soc/sdca/sdca_interrupts.c +++ b/sound/soc/sdca/sdca_interrupts.c @@ -119,7 +119,17 @@ static irqreturn_t function_status_handler(int irq, void *data) for_each_set_bit(mask, &status, BITS_PER_BYTE) { switch (BIT(mask)) { case SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION: - //FIXME: Add init writes +/* + * FIXME: Should this do init writes? + * + * Currently init writes/cache sync are done from the suspend/resume + * infrastructure. It is unclear in what situations one would receive this + * IRQ outside of that flow. Presumably it would be something like the chip + * crashing. In that case however doing the init writes and a cache sync might + * not be sufficient, for example if the failure was during audio playback + * there could be ordering constraints on the register writes to restore the + * state that are not handled by a simple cache sync. + */ break; case SDCA_CTL_ENTITY_0_FUNCTION_FAULT: dev_err(dev, "function fault\n"); From 2603ea46ce5c59568cf968ce35d2bf2854042182 Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Fri, 10 Apr 2026 14:42:25 +0800 Subject: [PATCH 336/359] ASoC: rt1320-sdw: Add an approach to get new hardware advance gain Add an approach to get new hardware advance gain, and if there is no advance gain with this approach, we can still get advance gain with original method. Signed-off-by: Jack Yu Link: https://patch.msgid.link/20260410064225.662656-1-jack.yu@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt1320-sdw.c | 25 +++++++++++++++++++++++-- sound/soc/codecs/rt1320-sdw.h | 13 +++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c index 168d2f391723..b0aeeab26bd9 100644 --- a/sound/soc/codecs/rt1320-sdw.c +++ b/sound/soc/codecs/rt1320-sdw.c @@ -1013,13 +1013,34 @@ static void rt1320_set_advancemode(struct rt1320_sdw_priv *rt1320) struct device *dev = &rt1320->sdw_slave->dev; struct rt1320_datafixpoint r0_data[2]; unsigned short l_advancegain, r_advancegain; + FwPara_Get_HwSwGain audDriverDataHwSwGain = {0}; + unsigned int HwAdvGain = 0; int ret; + /* Get new hardware advance gain by ID 1300 */ + ret = rt1320_fw_param_protocol(rt1320, RT1320_GET_PARAM, 1300, + &audDriverDataHwSwGain, sizeof(audDriverDataHwSwGain)); + if (ret == 0) { + HwAdvGain = audDriverDataHwSwGain.HwAdvGain; + dev_dbg(dev, "%s, HwAdvGain=%d\n", __func__, HwAdvGain); + dev_dbg(dev, "%s, HwBasGain=%d\n", __func__, audDriverDataHwSwGain.HwBasGain); + dev_dbg(dev, "%s, SwAdvGain=%d\n", __func__, audDriverDataHwSwGain.SwAdvGain); + dev_dbg(dev, "%s, SwBasGain=%d\n", __func__, audDriverDataHwSwGain.SwBasGain); + } else { + dev_dbg(dev, "%s: param 1300 not supported, ret=%d\n", __func__, ret); + } + /* Get advance gain/r0 */ rt1320_fw_param_protocol(rt1320, RT1320_GET_PARAM, 6, &r0_data[0], sizeof(struct rt1320_datafixpoint)); rt1320_fw_param_protocol(rt1320, RT1320_GET_PARAM, 7, &r0_data[1], sizeof(struct rt1320_datafixpoint)); - l_advancegain = r0_data[0].advancegain; - r_advancegain = r0_data[1].advancegain; + + if (HwAdvGain != 0) { + l_advancegain = HwAdvGain & 0xffff; + r_advancegain = (HwAdvGain >> 16) & 0xffff; + } else { + l_advancegain = r0_data[0].advancegain; + r_advancegain = r0_data[1].advancegain; + } dev_dbg(dev, "%s, LR advanceGain=0x%x 0x%x\n", __func__, l_advancegain, r_advancegain); /* set R0 and enable protection by SetParameter id 6, 7 */ diff --git a/sound/soc/codecs/rt1320-sdw.h b/sound/soc/codecs/rt1320-sdw.h index 4a1d3fc4c8ee..a7b573883dd0 100644 --- a/sound/soc/codecs/rt1320-sdw.h +++ b/sound/soc/codecs/rt1320-sdw.h @@ -121,6 +121,19 @@ struct rt1320_datafixpoint { int invrs; }; +typedef struct FwPara_HwSwGain { + unsigned int SwAdvGain; + unsigned int SwBasGain; + unsigned int HwAdvGain; + unsigned int HwBasGain; + unsigned int reserve0; + unsigned int reserve1; + unsigned int reserve2; + unsigned int reserve3; + unsigned int reserve4; + unsigned int reserve5; +} __attribute__((packed)) FwPara_Get_HwSwGain; + struct rt1320_paramcmd { unsigned char moudleid; unsigned char commandtype; From c5b6285aae050ff1c3ea824ca3d88ac4be1e69c8 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 26 Mar 2026 14:52:41 +0000 Subject: [PATCH 337/359] ASoC: SOF: Don't allow pointer operations on unconfigured streams When reporting the pointer for a compressed stream we report the current I/O frame position by dividing the position by the number of channels multiplied by the number of container bytes. These values default to 0 and are only configured as part of setting the stream parameters so this allows a divide by zero to be configured. Validate that they are non zero, returning an error if not Fixes: c1a731c71359 ("ASoC: SOF: compress: Add support for computing timestamps") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260326-asoc-compress-tstamp-params-v1-1-3dc735b3d599@kernel.org Signed-off-by: Mark Brown --- sound/soc/sof/compress.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/compress.c index f4c3e10e6344..93f2376585db 100644 --- a/sound/soc/sof/compress.c +++ b/sound/soc/sof/compress.c @@ -381,6 +381,9 @@ static int sof_compr_pointer(struct snd_soc_component *component, if (!spcm) return -EINVAL; + if (!sstream->channels || !sstream->sample_container_bytes) + return -EBUSY; + tstamp->sampling_rate = sstream->sampling_rate; tstamp->copied_total = sstream->copied_total; tstamp->pcm_io_frames = div_u64(spcm->stream[cstream->direction].posn.dai_posn, From 48bd344e1040b9f2eb512be73c13f5db83efc191 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 9 Apr 2026 16:01:56 +0200 Subject: [PATCH 338/359] ALSA: usx2y: us144mkii: fix NULL deref on missing interface 0 A malicious USB device with the TASCAM US-144MKII device id can have a configuration containing bInterfaceNumber=1 but no interface 0. USB configuration descriptors are not required to assign interface numbers sequentially, so usb_ifnum_to_if(dev, 0) returns will NULL, which will then be dereferenced directly. Fix this up by checking the return value properly. Cc: Jaroslav Kysela Cc: Takashi Iwai Fixes: dee1bcf28a3d ("ALSA: usb-audio: Add initial driver for TASCAM US-144MKII") Cc: stable Assisted-by: gregkh_clanker_t1000 Signed-off-by: Greg Kroah-Hartman Link: https://patch.msgid.link/2026040955-fall-gaining-e338@gregkh Signed-off-by: Takashi Iwai --- sound/usb/usx2y/us144mkii.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c index 0cf4fa74e210..94553b61013c 100644 --- a/sound/usb/usx2y/us144mkii.c +++ b/sound/usb/usx2y/us144mkii.c @@ -420,7 +420,11 @@ static int tascam_probe(struct usb_interface *intf, /* The device has two interfaces; we drive both from this driver. */ if (intf->cur_altsetting->desc.bInterfaceNumber == 1) { - tascam = usb_get_intfdata(usb_ifnum_to_if(dev, 0)); + struct usb_interface *intf_zero = usb_ifnum_to_if(dev, 0); + + if (!intf_zero) + return -ENODEV; + tascam = usb_get_intfdata(intf_zero); if (tascam) { usb_set_intfdata(intf, tascam); tascam->iface1 = intf; From 07704bbf36f57e4379e4cadf96410dab14621e3b Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 9 Apr 2026 16:05:54 +0200 Subject: [PATCH 339/359] ALSA: fireworks: bound device-supplied status before string array lookup The status field in an EFW response is a 32-bit value supplied by the firewire device. efr_status_names[] has 17 entries so a status value outside that range goes off into the weeds when looking at the %s value. Even worse, the status could return EFR_STATUS_INCOMPLETE which is 0x80000000, and is obviously not in that array of potential strings. Fix this up by properly bounding the index against the array size and printing "unknown" if it's not recognized. Cc: Clemens Ladisch Cc: Takashi Sakamoto Cc: Jaroslav Kysela Cc: Takashi Iwai Fixes: bde8a8f23bbe ("ALSA: fireworks: Add transaction and some commands") Cc: stable Assisted-by: gregkh_clanker_t1000 Signed-off-by: Greg Kroah-Hartman Reviewed-by: Takashi Sakamoto Link: https://patch.msgid.link/2026040953-astute-camera-1aa1@gregkh Signed-off-by: Takashi Iwai --- sound/firewire/fireworks/fireworks_command.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sound/firewire/fireworks/fireworks_command.c b/sound/firewire/fireworks/fireworks_command.c index 2b595ee0bc35..05550f36fac5 100644 --- a/sound/firewire/fireworks/fireworks_command.c +++ b/sound/firewire/fireworks/fireworks_command.c @@ -151,10 +151,13 @@ efw_transaction(struct snd_efw *efw, unsigned int category, (be32_to_cpu(header->category) != category) || (be32_to_cpu(header->command) != command) || (be32_to_cpu(header->status) != EFR_STATUS_OK)) { + u32 st = be32_to_cpu(header->status); + dev_err(&efw->unit->device, "EFW command failed [%u/%u]: %s\n", be32_to_cpu(header->category), be32_to_cpu(header->command), - efr_status_names[be32_to_cpu(header->status)]); + st < ARRAY_SIZE(efr_status_names) ? + efr_status_names[st] : "unknown"); err = -EIO; goto end; } From b9c826916fdce6419b94eb0cd8810fdac18c2386 Mon Sep 17 00:00:00 2001 From: Berk Cem Goksel Date: Fri, 10 Apr 2026 08:13:41 +0300 Subject: [PATCH 340/359] ALSA: 6fire: fix use-after-free on disconnect In usb6fire_chip_abort(), the chip struct is allocated as the card's private data (via snd_card_new with sizeof(struct sfire_chip)). When snd_card_free_when_closed() is called and no file handles are open, the card and embedded chip are freed synchronously. The subsequent chip->card = NULL write then hits freed slab memory. Call trace: usb6fire_chip_abort sound/usb/6fire/chip.c:59 [inline] usb6fire_chip_disconnect+0x348/0x358 sound/usb/6fire/chip.c:182 usb_unbind_interface+0x1a8/0x88c drivers/usb/core/driver.c:458 ... hub_event+0x1a04/0x4518 drivers/usb/core/hub.c:5953 Fix by moving the card lifecycle out of usb6fire_chip_abort() and into usb6fire_chip_disconnect(). The card pointer is saved in a local before any teardown, snd_card_disconnect() is called first to prevent new opens, URBs are aborted while chip is still valid, and snd_card_free_when_closed() is called last so chip is never accessed after the card may be freed. Fixes: a0810c3d6dd2 ("ALSA: 6fire: Release resources at card release") Cc: stable@vger.kernel.org Cc: Andrey Konovalov Signed-off-by: Berk Cem Goksel Link: https://patch.msgid.link/20260410051341.1069716-1-berkcgoksel@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/6fire/chip.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/sound/usb/6fire/chip.c b/sound/usb/6fire/chip.c index 5ff78814e687..874f6cd503ca 100644 --- a/sound/usb/6fire/chip.c +++ b/sound/usb/6fire/chip.c @@ -53,11 +53,6 @@ static void usb6fire_chip_abort(struct sfire_chip *chip) usb6fire_comm_abort(chip); if (chip->control) usb6fire_control_abort(chip); - if (chip->card) { - snd_card_disconnect(chip->card); - snd_card_free_when_closed(chip->card); - chip->card = NULL; - } } } @@ -168,6 +163,7 @@ destroy_chip: static void usb6fire_chip_disconnect(struct usb_interface *intf) { struct sfire_chip *chip; + struct snd_card *card; chip = usb_get_intfdata(intf); if (chip) { /* if !chip, fw upload has been performed */ @@ -178,8 +174,19 @@ static void usb6fire_chip_disconnect(struct usb_interface *intf) chips[chip->regidx] = NULL; } + /* + * Save card pointer before teardown. + * snd_card_free_when_closed() may free card (and + * the embedded chip) immediately, so it must be + * called last and chip must not be accessed after. + */ + card = chip->card; chip->shutdown = true; + if (card) + snd_card_disconnect(card); usb6fire_chip_abort(chip); + if (card) + snd_card_free_when_closed(card); } } } From fb79bf127ac2577b4876132da6dba768018aad4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Fri, 10 Apr 2026 00:54:32 -0300 Subject: [PATCH 341/359] ALSA: sc6000: Keep the programmed board state in card-private data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver may auto-select IRQ and DMA resources at probe time, but sc6000_init_board() still derives the SC-6000 soft configuration from the module parameter arrays. When irq=auto or dma=auto is used, the codec is created with the selected resources while the board is programmed with the unresolved values. Store the mapped ports and generated SC-6000 board configuration in card-private data, build that configuration from the live probe results instead of the raw module parameters, and keep the probe-time board programming in a shared helper. This fixes the resource-programming mismatch and leaves the driver with a stable board-state block that can be reused by suspend/resume. Fixes: c282866101bf ("ALSA: sc6000: add support for SC-6600 and SC-7000") Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260410-alsa-sc6000-pm-v1-1-4d9e95493d26@gmail.com --- sound/isa/sc6000.c | 152 +++++++++++++++++++++++++++------------------ 1 file changed, 92 insertions(+), 60 deletions(-) diff --git a/sound/isa/sc6000.c b/sound/isa/sc6000.c index 6d618cc2ba45..9949e06403f6 100644 --- a/sound/isa/sc6000.c +++ b/sound/isa/sc6000.c @@ -100,6 +100,15 @@ MODULE_PARM_DESC(joystick, "Enable gameport."); #define PFX "sc6000: " #define DRV_NAME "SC-6000" +struct snd_sc6000 { + char __iomem *vport; + char __iomem *vmss_port; + u8 mss_config; + u8 config; + u8 hw_cfg[2]; + bool old_dsp; +}; + /* hardware dependent functions */ /* @@ -267,7 +276,7 @@ static int sc6000_dsp_reset(char __iomem *vport) /* detection and initialization */ static int sc6000_hw_cfg_write(struct device *devptr, - char __iomem *vport, const int *cfg) + char __iomem *vport, const u8 *cfg) { if (sc6000_write(devptr, vport, COMMAND_6C) < 0) { dev_warn(devptr, "CMD 0x%x: failed!\n", COMMAND_6C); @@ -353,8 +362,7 @@ static int sc6000_init_mss(struct device *devptr, return 0; } -static void sc6000_hw_cfg_encode(struct device *devptr, - char __iomem *vport, int *cfg, +static void sc6000_hw_cfg_encode(struct device *devptr, u8 *cfg, long xport, long xmpu, long xmss_port, int joystick) { @@ -376,27 +384,83 @@ static void sc6000_hw_cfg_encode(struct device *devptr, dev_dbg(devptr, "hw cfg %x, %x\n", cfg[0], cfg[1]); } -static int sc6000_init_board(struct device *devptr, - char __iomem *vport, - char __iomem *vmss_port, int dev) +static void sc6000_prepare_board(struct device *devptr, + struct snd_sc6000 *sc6000, + unsigned int dev, int xirq, int xdma) +{ + sc6000->mss_config = sc6000_irq_to_softcfg(xirq) | + sc6000_dma_to_softcfg(xdma); + sc6000->config = sc6000->mss_config | + sc6000_mpu_irq_to_softcfg(mpu_irq[dev]); + sc6000_hw_cfg_encode(devptr, sc6000->hw_cfg, port[dev], mpu_port[dev], + mss_port[dev], joystick[dev]); +} + +static void sc6000_detect_old_dsp(struct device *devptr, + struct snd_sc6000 *sc6000) +{ + sc6000_write(devptr, sc6000->vport, COMMAND_5C); + sc6000->old_dsp = sc6000_read(sc6000->vport) < 0; +} + +static int sc6000_program_board(struct device *devptr, + struct snd_sc6000 *sc6000) +{ + int err; + + if (!sc6000->old_dsp) { + if (sc6000_hw_cfg_write(devptr, sc6000->vport, + sc6000->hw_cfg) < 0) { + dev_err(devptr, "sc6000_hw_cfg_write: failed!\n"); + return -EIO; + } + } + + err = sc6000_setup_board(devptr, sc6000->vport, sc6000->config); + if (err < 0) { + dev_err(devptr, "sc6000_setup_board: failed!\n"); + return -ENODEV; + } + + sc6000_dsp_reset(sc6000->vport); + + if (!sc6000->old_dsp) { + sc6000_write(devptr, sc6000->vport, COMMAND_60); + sc6000_write(devptr, sc6000->vport, 0x02); + sc6000_dsp_reset(sc6000->vport); + } + + err = sc6000_setup_board(devptr, sc6000->vport, sc6000->config); + if (err < 0) { + dev_err(devptr, "sc6000_setup_board: failed!\n"); + return -ENODEV; + } + + err = sc6000_init_mss(devptr, sc6000->vport, sc6000->config, + sc6000->vmss_port, sc6000->mss_config); + if (err < 0) { + dev_err(devptr, "Cannot initialize Microsoft Sound System mode.\n"); + return -ENODEV; + } + + return 0; +} + +static int sc6000_init_board(struct device *devptr, struct snd_sc6000 *sc6000) { char answer[15]; char version[2]; - int mss_config = sc6000_irq_to_softcfg(irq[dev]) | - sc6000_dma_to_softcfg(dma[dev]); - int config = mss_config | - sc6000_mpu_irq_to_softcfg(mpu_irq[dev]); int err; - int old = 0; - err = sc6000_dsp_reset(vport); + err = sc6000_dsp_reset(sc6000->vport); if (err < 0) { dev_err(devptr, "sc6000_dsp_reset: failed!\n"); return err; } memset(answer, 0, sizeof(answer)); - err = sc6000_dsp_get_answer(devptr, vport, GET_DSP_COPYRIGHT, answer, 15); + err = sc6000_dsp_get_answer(devptr, sc6000->vport, GET_DSP_COPYRIGHT, + answer, 15); if (err <= 0) { dev_err(devptr, "sc6000_dsp_copyright: failed!\n"); return -ENODEV; @@ -408,54 +472,17 @@ static int sc6000_init_board(struct device *devptr, if (strncmp("SC-6000", answer, 7)) dev_warn(devptr, "Warning: non SC-6000 audio card!\n"); - if (sc6000_dsp_get_answer(devptr, vport, GET_DSP_VERSION, version, 2) < 2) { + if (sc6000_dsp_get_answer(devptr, sc6000->vport, + GET_DSP_VERSION, version, 2) < 2) { dev_err(devptr, "sc6000_dsp_version: failed!\n"); return -ENODEV; } dev_info(devptr, "Detected model: %s, DSP version %d.%d\n", answer, version[0], version[1]); - /* set configuration */ - sc6000_write(devptr, vport, COMMAND_5C); - if (sc6000_read(vport) < 0) - old = 1; + sc6000_detect_old_dsp(devptr, sc6000); - if (!old) { - int cfg[2]; - sc6000_hw_cfg_encode(devptr, - vport, &cfg[0], port[dev], mpu_port[dev], - mss_port[dev], joystick[dev]); - if (sc6000_hw_cfg_write(devptr, vport, cfg) < 0) { - dev_err(devptr, "sc6000_hw_cfg_write: failed!\n"); - return -EIO; - } - } - err = sc6000_setup_board(devptr, vport, config); - if (err < 0) { - dev_err(devptr, "sc6000_setup_board: failed!\n"); - return -ENODEV; - } - - sc6000_dsp_reset(vport); - - if (!old) { - sc6000_write(devptr, vport, COMMAND_60); - sc6000_write(devptr, vport, 0x02); - sc6000_dsp_reset(vport); - } - - err = sc6000_setup_board(devptr, vport, config); - if (err < 0) { - dev_err(devptr, "sc6000_setup_board: failed!\n"); - return -ENODEV; - } - err = sc6000_init_mss(devptr, vport, config, vmss_port, mss_config); - if (err < 0) { - dev_err(devptr, "Cannot initialize Microsoft Sound System mode.\n"); - return -ENODEV; - } - - return 0; + return sc6000_program_board(devptr, sc6000); } static int snd_sc6000_mixer(struct snd_wss *chip) @@ -538,10 +565,10 @@ static int snd_sc6000_match(struct device *devptr, unsigned int dev) static void snd_sc6000_free(struct snd_card *card) { - char __iomem *vport = (char __force __iomem *)card->private_data; + struct snd_sc6000 *sc6000 = card->private_data; - if (vport) - sc6000_setup_board(card->dev, vport, 0); + if (sc6000->vport) + sc6000_setup_board(card->dev, sc6000->vport, 0); } static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) @@ -552,15 +579,17 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) int xirq = irq[dev]; int xdma = dma[dev]; struct snd_card *card; + struct snd_sc6000 *sc6000; struct snd_wss *chip; struct snd_opl3 *opl3; char __iomem *vport; char __iomem *vmss_port; err = snd_devm_card_new(devptr, index[dev], id[dev], THIS_MODULE, - 0, &card); + sizeof(*sc6000), &card); if (err < 0) return err; + sc6000 = card->private_data; if (xirq == SNDRV_AUTO_IRQ) { xirq = snd_legacy_find_free_irq(possible_irqs); @@ -587,7 +616,7 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) dev_err(devptr, "I/O port cannot be iomapped.\n"); return -EBUSY; } - card->private_data = (void __force *)vport; + sc6000->vport = vport; /* to make it marked as used */ if (!devm_request_region(devptr, mss_port[dev], 4, DRV_NAME)) { @@ -600,12 +629,15 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) dev_err(devptr, "MSS port I/O cannot be iomapped.\n"); return -EBUSY; } + sc6000->vmss_port = vmss_port; dev_dbg(devptr, "Initializing BASE[0x%lx] IRQ[%d] DMA[%d] MIRQ[%d]\n", port[dev], xirq, xdma, mpu_irq[dev] == SNDRV_AUTO_IRQ ? 0 : mpu_irq[dev]); - err = sc6000_init_board(devptr, vport, vmss_port, dev); + sc6000_prepare_board(devptr, sc6000, dev, xirq, xdma); + + err = sc6000_init_board(devptr, sc6000); if (err < 0) return err; card->private_free = snd_sc6000_free; From 47f72d57ddb11222479c80bd07f5bc036d84c94d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Fri, 10 Apr 2026 00:54:33 -0300 Subject: [PATCH 342/359] ALSA: sc6000: Restore board setup across suspend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_wss_resume() restores only the codec register image. The SC-6000 driver also programs card-specific DSP routing and enters MSS mode during probe, and that setup is not replayed after suspend. Cache the WSS chip pointer in the SC-6000 card state and wire ISA suspend and resume callbacks to the shared board-programming helper, so the board is reinitialized before the codec state is restored. This keeps the old/new DSP split in one place and restores the board-level MSS setup that the codec resume path does not cover. Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260410-alsa-sc6000-pm-v1-2-4d9e95493d26@gmail.com --- sound/isa/sc6000.c | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/sound/isa/sc6000.c b/sound/isa/sc6000.c index 9949e06403f6..cd3a63c7c2a7 100644 --- a/sound/isa/sc6000.c +++ b/sound/isa/sc6000.c @@ -103,6 +103,7 @@ MODULE_PARM_DESC(joystick, "Enable gameport."); struct snd_sc6000 { char __iomem *vport; char __iomem *vmss_port; + struct snd_wss *chip; u8 mss_config; u8 config; u8 hw_cfg[2]; @@ -646,6 +647,7 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) WSS_HW_DETECT, 0, &chip); if (err < 0) return err; + sc6000->chip = chip; err = snd_wss_pcm(chip, 0); if (err < 0) { @@ -702,10 +704,47 @@ static int snd_sc6000_probe(struct device *devptr, unsigned int dev) return snd_card_free_on_error(devptr, __snd_sc6000_probe(devptr, dev)); } +#ifdef CONFIG_PM +static int snd_sc6000_suspend(struct device *devptr, unsigned int dev, + pm_message_t state) +{ + struct snd_card *card = dev_get_drvdata(devptr); + struct snd_sc6000 *sc6000 = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + sc6000->chip->suspend(sc6000->chip); + return 0; +} + +static int snd_sc6000_resume(struct device *devptr, unsigned int dev) +{ + struct snd_card *card = dev_get_drvdata(devptr); + struct snd_sc6000 *sc6000 = card->private_data; + int err; + + err = sc6000_dsp_reset(sc6000->vport); + if (err < 0) { + dev_err(devptr, "sc6000_dsp_reset: failed!\n"); + return err; + } + + err = sc6000_program_board(devptr, sc6000); + if (err < 0) + return err; + + sc6000->chip->resume(sc6000->chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif + static struct isa_driver snd_sc6000_driver = { .match = snd_sc6000_match, .probe = snd_sc6000_probe, - /* FIXME: suspend/resume */ +#ifdef CONFIG_PM + .suspend = snd_sc6000_suspend, + .resume = snd_sc6000_resume, +#endif .driver = { .name = DRV_NAME, }, From 52521e8398839105ef8eb22b3f0993f9b0d11a57 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 10 Apr 2026 16:32:19 +0200 Subject: [PATCH 343/359] ALSA: usb-audio: Evaluate packsize caps at the right place We introduced the upper bound checks of the packet sizes by the ep->maxframesize for avoiding the URB submission errors. However, the check was applied at an incorrect place in the function snd_usb_endpoint_set_params() where ep->maxframesize isn't defined yet; the value is defined at a bit later position. So this ended up with a failure at the first run while the second run works. For fixing it, move the check at the correct place, right after the calculation of ep->maxframesize in the same function. Fixes: 7fe8dec3f628 ("ALSA: usb-audio: Cap the packet size pre-calculations") Link: https://bugzilla.kernel.org/show_bug.cgi?id=221292 Cc: Link: https://patch.msgid.link/20260410143220.1676344-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/endpoint.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index bf4401aba76c..6fbcb117555c 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -1379,9 +1379,6 @@ int snd_usb_endpoint_set_params(struct snd_usb_audio *chip, return -EINVAL; } - ep->packsize[0] = min(ep->packsize[0], ep->maxframesize); - ep->packsize[1] = min(ep->packsize[1], ep->maxframesize); - /* calculate the frequency in 16.16 format */ ep->freqm = ep->freqn; ep->freqshift = INT_MIN; @@ -1408,6 +1405,9 @@ int snd_usb_endpoint_set_params(struct snd_usb_audio *chip, ep->maxframesize = ep->maxpacksize / ep->cur_frame_bytes; ep->curframesize = ep->curpacksize / ep->cur_frame_bytes; + ep->packsize[0] = min(ep->packsize[0], ep->maxframesize); + ep->packsize[1] = min(ep->packsize[1], ep->maxframesize); + err = update_clock_ref_rate(chip, ep); if (err >= 0) { ep->need_setup = false; From b7feba842c0d5f6c5b01592f80d164e974767501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Fri, 10 Apr 2026 10:56:52 -0300 Subject: [PATCH 344/359] ALSA: interwave: guard PM-only restore helpers with CONFIG_PM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The InterWave PM patch added snd_interwave_restore_regs() and snd_interwave_restore_memory() as static helpers, but both are used only from the resume path under CONFIG_PM. On configurations without CONFIG_PM, such as alpha allyesconfig, this leaves both helpers unused and triggers -Wunused-function warnings with W=1. Move the PM-only helpers into the existing CONFIG_PM section. Keep __snd_interwave_restore_regs() outside the guard because it is also used during probe-time initialization. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202604101958.x16oNkfo-lkp@intel.com/ Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260410-alsa-interwave-pm-warning-fix-v1-1-434d14c9c262@gmail.com Signed-off-by: Takashi Iwai --- sound/isa/gus/interwave.c | 76 +++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/sound/isa/gus/interwave.c b/sound/isa/gus/interwave.c index 616c11e51a2f..6c3a2977dcb3 100644 --- a/sound/isa/gus/interwave.c +++ b/sound/isa/gus/interwave.c @@ -480,44 +480,6 @@ static void __snd_interwave_restore_regs(struct snd_gus_card *gus) snd_gf1_write8(gus, SNDRV_GF1_GB_EMULATION_IRQ, 0x00); } -static void snd_interwave_restore_regs(struct snd_gus_card *gus) -{ - scoped_guard(spinlock_irqsave, &gus->reg_lock) - __snd_interwave_restore_regs(gus); -} - -static void snd_interwave_restore_memory(struct snd_gus_card *gus) -{ - unsigned short mem_cfg; - unsigned int lmct = 0; - int i, lmc_cfg; - - if (!gus->gf1.memory) - return; - - for (i = 0; i < 4; i++) - lmct |= (gus->gf1.mem_alloc.banks_16[i].size >> 18) << (i * 8); - - lmc_cfg = snd_interwave_find_memory_config(lmct); - if (lmc_cfg < 0) { - if (!gus->gf1.enh_mode) { - lmc_cfg = 2; - } else { - dev_warn(gus->card->dev, - "cannot restore InterWave memory layout 0x%08x\n", - lmct); - return; - } - } - - scoped_guard(spinlock_irqsave, &gus->reg_lock) { - mem_cfg = snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG); - mem_cfg = (mem_cfg & 0xfff0) | lmc_cfg; - mem_cfg = (mem_cfg & 0xff1f) | (4 << 5); - snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, mem_cfg); - } -} - static void snd_interwave_init(int dev, struct snd_gus_card *gus) { /* Probe-time setup also clears the timer control register. */ @@ -888,6 +850,44 @@ static int snd_interwave_isa_probe(struct device *pdev, } #ifdef CONFIG_PM +static void snd_interwave_restore_regs(struct snd_gus_card *gus) +{ + scoped_guard(spinlock_irqsave, &gus->reg_lock) + __snd_interwave_restore_regs(gus); +} + +static void snd_interwave_restore_memory(struct snd_gus_card *gus) +{ + unsigned short mem_cfg; + unsigned int lmct = 0; + int i, lmc_cfg; + + if (!gus->gf1.memory) + return; + + for (i = 0; i < 4; i++) + lmct |= (gus->gf1.mem_alloc.banks_16[i].size >> 18) << (i * 8); + + lmc_cfg = snd_interwave_find_memory_config(lmct); + if (lmc_cfg < 0) { + if (!gus->gf1.enh_mode) { + lmc_cfg = 2; + } else { + dev_warn(gus->card->dev, + "cannot restore InterWave memory layout 0x%08x\n", + lmct); + return; + } + } + + scoped_guard(spinlock_irqsave, &gus->reg_lock) { + mem_cfg = snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG); + mem_cfg = (mem_cfg & 0xfff0) | lmc_cfg; + mem_cfg = (mem_cfg & 0xff1f) | (4 << 5); + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, mem_cfg); + } +} + static int snd_interwave_card_suspend(struct snd_card *card) { struct snd_interwave *iwcard = card->private_data; From 34fe4a9df2476f52a809d0cd9659ff73de605774 Mon Sep 17 00:00:00 2001 From: Abhinav Mahadevan Date: Fri, 10 Apr 2026 20:03:35 +0530 Subject: [PATCH 345/359] ALSA: usb-audio: Add quirk for PreSonus AudioBox USB The PreSonus AudioBox USB (0x194f:0x0301) only supports S24_3LE format for both playback and capture. It does not support S16_LE despite being a USB full-speed device. Add explicit format quirks for both the playback (interface 2) and capture (interface 3) interfaces to ensure correct format negotiation. Signed-off-by: Abhinav Mahadevan Link: https://patch.msgid.link/20260410143335.5974-1-abhi220204@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks-table.h | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index b6dfe3b63c67..803e03d4d77b 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -2652,6 +2652,54 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, +{ + /* + * The AudioBox USB advertises S24_3LE as the only supported format + * for both playback and capture. It does not support S16_LE despite + * being a USB full-speed device. + */ + USB_DEVICE(0x194f, 0x0301), + QUIRK_DRIVER_INFO { + .vendor_name = "PreSonus", + .product_name = "AudioBox USB", + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { + QUIRK_DATA_AUDIOFORMAT(2) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 2, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + } + }, + { + QUIRK_DATA_AUDIOFORMAT(3) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 2, + .iface = 3, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x82, + .ep_attr = USB_ENDPOINT_XFER_ISOC, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + } + }, + QUIRK_COMPOSITE_END + } + } +}, #endif /* disabled */ { From 4f55a85cd4fc988712965f710ba1475e7ba3292a Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sat, 11 Apr 2026 01:49:02 +0800 Subject: [PATCH 346/359] ALSA: usb-audio: Add error checks against get_min_max*() All callers of get_min_max*() ignore the latter's return code completely. This means to ignore temporary errors at the probe time. However, it is not optimal and leads to some maintenance burdens. Return -EAGAIN for temporary errors, and check against it in the callers of get_min_max*(). If any other error occurs, bail out of the caller early. Suggested-by: Takashi Iwai Link: https://lore.kernel.org/r/87ldewi4j8.wl-tiwai@suse.de Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20260411-uac-sticky-mixer-v1-1-29d62717befd@rong.moe Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index a25e8145af67..e5993364c825 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1264,7 +1264,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, "%d:%d: cannot get min/max values for control %d (id %d)\n", cval->head.id, mixer_ctrl_intf(cval->head.mixer), cval->control, cval->head.id); - return -EINVAL; + return -EAGAIN; } if (get_ctl_value(cval, UAC_GET_RES, (cval->control << 8) | minchn, @@ -1388,6 +1388,7 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); + int ret; if (cval->val_type == USB_MIXER_BOOLEAN || cval->val_type == USB_MIXER_INV_BOOLEAN) @@ -1398,8 +1399,9 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, if (cval->val_type != USB_MIXER_BOOLEAN && cval->val_type != USB_MIXER_INV_BOOLEAN) { if (!cval->initialized) { - get_min_max_with_quirks(cval, 0, kcontrol); - if (cval->initialized && cval->dBmin >= cval->dBmax) { + ret = get_min_max_with_quirks(cval, 0, kcontrol); + if ((ret >= 0 || ret == -EAGAIN) && + cval->initialized && cval->dBmin >= cval->dBmax) { kcontrol->vd[0].access &= ~(SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK); @@ -1743,6 +1745,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, struct snd_kcontrol *kctl; struct usb_mixer_elem_info *cval; const struct usbmix_name_map *map; + int ret; if (control == UAC_FU_GRAPHIC_EQUALIZER) { /* FIXME: not supported yet */ @@ -1856,10 +1859,10 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, } /* get min/max values */ - get_min_max_with_quirks(cval, 0, kctl); + ret = get_min_max_with_quirks(cval, 0, kctl); /* skip a bogus volume range */ - if (cval->max <= cval->min) { + if ((ret < 0 && ret != -EAGAIN) || cval->max <= cval->min) { usb_audio_dbg(mixer->chip, "[%d] FU [%s] skipped due to invalid volume\n", cval->head.id, kctl->id.name); @@ -2233,6 +2236,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, unsigned int i, len; struct snd_kcontrol *kctl; const struct usbmix_name_map *map; + int ret; map = find_map(state->map, unitid, 0); if (check_ignored_ctl(map)) @@ -2255,7 +2259,11 @@ static void build_mixer_unit_ctl(struct mixer_build *state, } /* get min/max values */ - get_min_max(cval, 0); + ret = get_min_max(cval, 0); + if (ret < 0 && ret != -EAGAIN) { + usb_mixer_elem_info_free(cval); + return; + } kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); if (!kctl) { @@ -2627,7 +2635,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, break; } - get_min_max(cval, valinfo->min_value); + err = get_min_max(cval, valinfo->min_value); break; } case USB_XU_CLOCK_RATE: @@ -2639,11 +2647,16 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, cval->max = 5; cval->res = 1; cval->initialized = 1; + err = 0; break; default: - get_min_max(cval, valinfo->min_value); + err = get_min_max(cval, valinfo->min_value); break; } + if (err < 0 && err != -EAGAIN) { + usb_mixer_elem_info_free(cval); + return err; + } err = get_cur_ctl_value(cval, cval->control << 8, &val); if (err < 0) { From e3ad86a82868fcde16f213240a60891c2d7bbec4 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sat, 11 Apr 2026 01:49:03 +0800 Subject: [PATCH 347/359] ALSA: usb-audio: Move volume control resolution check into a function get_min_max_with_quirks() is too lengthy and hard to read. Move the volume control resolution check code into a function as it's relatively self-contained. Suggested-by: Takashi Iwai Link: https://lore.kernel.org/r/87o6jsk3vs.wl-tiwai@suse.de Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20260411-uac-sticky-mixer-v1-2-29d62717befd@rong.moe Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 65 ++++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index e5993364c825..e77c2d78a782 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1232,6 +1232,38 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) snd_usb_set_cur_mix_value(cval, ch, idx, cval->min); } +/* + * Additional checks for the proper resolution + * + * Some devices report smaller resolutions than actually reacting. + * They don't return errors but simply clip to the lower aligned value. + */ +static void check_volume_control_res(struct usb_mixer_elem_info *cval, + int channel, int saved) +{ + int last_valid_res = cval->res; + int test, check; + + for (;;) { + test = saved; + if (test < cval->max) + test += cval->res; + else + test -= cval->res; + + if (test < cval->min || test > cval->max || + snd_usb_set_cur_mix_value(cval, channel, 0, test) || + get_cur_mix_raw(cval, channel, &check)) { + cval->res = last_valid_res; + break; + } + if (test == check) + break; + + cval->res *= 2; + } +} + /* * retrieve the minimum and maximum values for the specified control */ @@ -1287,37 +1319,18 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, if (cval->res == 0) cval->res = 1; - /* Additional checks for the proper resolution - * - * Some devices report smaller resolutions than actually - * reacting. They don't return errors but simply clip - * to the lower aligned value. - */ if (cval->min + cval->res < cval->max) { - int last_valid_res = cval->res; - int saved, test, check; + int saved; + if (get_cur_mix_raw(cval, minchn, &saved) < 0) - goto no_res_check; - for (;;) { - test = saved; - if (test < cval->max) - test += cval->res; - else - test -= cval->res; - if (test < cval->min || test > cval->max || - snd_usb_set_cur_mix_value(cval, minchn, 0, test) || - get_cur_mix_raw(cval, minchn, &check)) { - cval->res = last_valid_res; - break; - } - if (test == check) - break; - cval->res *= 2; - } + goto no_checks; + + check_volume_control_res(cval, minchn, saved); + snd_usb_set_cur_mix_value(cval, minchn, 0, saved); } -no_res_check: +no_checks: cval->initialized = 1; } From 86aa1ea1f15ce6b56ac1b4c0d9b88a07a5b9bf03 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sat, 11 Apr 2026 01:49:04 +0800 Subject: [PATCH 348/359] ALSA: usb-audio: Do not expose sticky mixers Some devices' mixers are sticky, which accept SET_CUR but do absolutely nothing. Registering these mixers confuses userspace and results in ineffective volume control. Check if a mixer is sticky by setting the volume to the maximum or minimum value and checking for effectiveness afterward. Prevent the mixer from being registered if it turns out to be sticky. Quirky device sample: usb 7-1: New USB device found, idVendor=0e0b, idProduct=fa01, bcdDevice= 1.00 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: Feaulle Rainbow usb 7-1: Manufacturer: Generic usb 7-1: SerialNumber: 20210726905926 (Mic Capture Volume) Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20260411-uac-sticky-mixer-v1-3-29d62717befd@rong.moe Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 48 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index e77c2d78a782..d4ef45bf53d7 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1232,6 +1232,41 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) snd_usb_set_cur_mix_value(cval, ch, idx, cval->min); } +/* + * Additional checks for sticky mixers + * + * Some devices' volume control mixers are sticky, which accept SET_CUR but + * do absolutely nothing. + * + * Prevent sticky mixers from being registered, otherwise they confuses + * userspace and results in ineffective volume control. + */ +static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, + int channel, int saved) +{ + int sticky_test_values[] = { cval->min, cval->max }; + int test, check, i; + + for (i = 0; i < ARRAY_SIZE(sticky_test_values); i++) { + test = sticky_test_values[i]; + if (test == saved) + continue; + + /* Assume non-sticky on failure. */ + if (snd_usb_set_cur_mix_value(cval, channel, 0, test) || + get_cur_mix_raw(cval, channel, &check) || + check != saved) /* SET_CUR effective, non-sticky. */ + return 0; + } + + usb_audio_err(cval->head.mixer->chip, + "%d:%d: sticky mixer values (%d/%d/%d => %d), disabling\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + cval->min, cval->max, cval->res, saved); + + return -ENODEV; +} + /* * Additional checks for the proper resolution * @@ -1270,7 +1305,7 @@ static void check_volume_control_res(struct usb_mixer_elem_info *cval, static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, int default_min, struct snd_kcontrol *kctl) { - int i, idx; + int i, idx, ret; /* for failsafe */ cval->min = default_min; @@ -1319,13 +1354,20 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, if (cval->res == 0) cval->res = 1; - if (cval->min + cval->res < cval->max) { + if (cval->min < cval->max) { int saved; if (get_cur_mix_raw(cval, minchn, &saved) < 0) goto no_checks; - check_volume_control_res(cval, minchn, saved); + ret = check_sticky_volume_control(cval, minchn, saved); + if (ret < 0) { + snd_usb_set_cur_mix_value(cval, minchn, 0, saved); + return ret; + } + + if (cval->min + cval->res < cval->max) + check_volume_control_res(cval, minchn, saved); snd_usb_set_cur_mix_value(cval, minchn, 0, saved); } From f312f8b5988003a10d662904c58c8c6bc036782b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Sat, 11 Apr 2026 15:14:40 -0300 Subject: [PATCH 349/359] ALSA: sscape: Cache per-card resources for board reinitialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SoundScape driver programs the gate-array directly from the global resource arrays during probe. That is sufficient for initial bring-up, but a PM resume path also needs the resolved per-card IRQ, DMA, MPU IRQ and joystick settings after probe has finished. Store the resolved resources in struct soundscape and move the board setup into a reusable helper. Also factor the MIDI state programming so the same sequence can be reused by a later PM resume path. This is preparatory work for suspend/resume support and is not intended to change runtime behaviour. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260411-alsa-sscape-pm-v2-1-aeb5682e14b0@gmail.com Signed-off-by: Takashi Iwai --- sound/isa/sscape.c | 234 +++++++++++++++++++++++++++------------------ 1 file changed, 139 insertions(+), 95 deletions(-) diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c index a31ca75774a6..6e951b3c0080 100644 --- a/sound/isa/sscape.c +++ b/sound/isa/sscape.c @@ -131,6 +131,11 @@ enum card_type { struct soundscape { spinlock_t lock; unsigned io_base; + unsigned long wss_base; + int irq; + int mpu_irq; + int dma1; + int dma2; int ic_type; enum card_type type; struct resource *io_res; @@ -138,6 +143,7 @@ struct soundscape { struct snd_wss *chip; unsigned char midi_vol; + bool joystick; struct device *dev; }; @@ -149,6 +155,21 @@ static inline struct soundscape *get_card_soundscape(struct snd_card *c) return (struct soundscape *) (c->private_data); } +/* + * Store the resolved board settings in the per-card state so that + * the same configuration can be replayed later if necessary. + */ +static void sscape_store_settings(struct soundscape *sscape, int dev) +{ + sscape->io_base = port[dev]; + sscape->wss_base = wss_port[dev]; + sscape->irq = irq[dev]; + sscape->mpu_irq = mpu_irq[dev]; + sscape->dma1 = dma[dev]; + sscape->dma2 = dma2[dev]; + sscape->joystick = joystick[dev]; +} + /* * Allocates some kernel memory that we can use for DMA. * I think this means that the memory has to map to @@ -263,34 +284,36 @@ static int host_read_ctrl_unsafe(unsigned io_base, unsigned timeout) /* * Write to the SoundScape's host-mode control registers, but - * leave any locking issues to the caller ... + * leave any locking issues to the caller. Returns true if + * the write succeeded. */ -static inline int host_write_unsafe(unsigned io_base, unsigned char data) +static inline bool host_write_unsafe(unsigned int io_base, unsigned char data) { if ((inb(HOST_CTRL_IO(io_base)) & TX_READY) != 0) { outb(data, HOST_DATA_IO(io_base)); - return 1; + return true; } - return 0; + return false; } /* * Write to the SoundScape's host-mode control registers, performing * a limited amount of busy-waiting if the register isn't ready. - * Also leaves all locking-issues to the caller ... + * Also leaves all locking-issues to the caller. Returns true if + * the write succeeded before timing out. */ -static int host_write_ctrl_unsafe(unsigned io_base, unsigned char data, - unsigned timeout) +static bool host_write_ctrl_unsafe(unsigned int io_base, unsigned char data, + unsigned int timeout) { - int err; + bool written; - while (!(err = host_write_unsafe(io_base, data)) && (timeout != 0)) { + while (!(written = host_write_unsafe(io_base, data)) && timeout != 0) { udelay(100); --timeout; } /* while */ - return err; + return written; } @@ -560,6 +583,30 @@ static int sscape_upload_microcode(struct snd_card *card, int version) return err; } +/* + * Restore the SoundScape's MIDI control state after the firmware + * upload has made the host interface available again. + */ +static int sscape_restore_midi_state(struct soundscape *sscape) +{ + bool success; + + guard(spinlock_irqsave)(&sscape->lock); + set_host_mode_unsafe(sscape->io_base); + + success = host_write_ctrl_unsafe(sscape->io_base, CMD_SET_MIDI_VOL, 100) && + host_write_ctrl_unsafe(sscape->io_base, sscape->midi_vol, 100) && + host_write_ctrl_unsafe(sscape->io_base, CMD_XXX_MIDI_VOL, 100) && + host_write_ctrl_unsafe(sscape->io_base, sscape->midi_vol, 100) && + host_write_ctrl_unsafe(sscape->io_base, CMD_SET_EXTMIDI, 100) && + host_write_ctrl_unsafe(sscape->io_base, 0, 100) && + host_write_ctrl_unsafe(sscape->io_base, CMD_ACK, 100); + + set_midi_mode_unsafe(sscape->io_base); + + return success ? 0 : -EIO; +} + /* * Mixer control for the SoundScape's MIDI device. */ @@ -660,6 +707,59 @@ static unsigned get_irq_config(int sscape_type, int irq) return INVALID_IRQ; } +/* + * Program the SoundScape's board-specific routing and enable the + * codec path using the resolved IRQ, DMA and joystick settings. + */ +static int sscape_configure_board(struct soundscape *sscape) +{ + unsigned int dma_cfg; + unsigned int irq_cfg; + unsigned int mpu_irq_cfg; + int val; + + irq_cfg = get_irq_config(sscape->type, sscape->irq); + if (irq_cfg == INVALID_IRQ) + return -ENXIO; + + mpu_irq_cfg = get_irq_config(sscape->type, sscape->mpu_irq); + if (mpu_irq_cfg == INVALID_IRQ) + return -ENXIO; + + scoped_guard(spinlock_irqsave, &sscape->lock) { + if (sscape->ic_type == IC_OPUS) + activate_ad1845_unsafe(sscape->io_base); + + sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2e); + sscape_write_unsafe(sscape->io_base, GA_SMCFGB_REG, 0x00); + + /* + * Enable and configure the DMA channels ... + */ + sscape_write_unsafe(sscape->io_base, GA_DMACFG_REG, 0x50); + dma_cfg = (sscape->ic_type == IC_OPUS ? 0x40 : 0x70); + sscape_write_unsafe(sscape->io_base, GA_DMAA_REG, dma_cfg); + sscape_write_unsafe(sscape->io_base, GA_DMAB_REG, 0x20); + + mpu_irq_cfg |= mpu_irq_cfg << 2; + val = sscape_read_unsafe(sscape->io_base, GA_HMCTL_REG) & 0xf7; + if (sscape->joystick) + val |= 0x08; + sscape_write_unsafe(sscape->io_base, GA_HMCTL_REG, val | 0xd0); + sscape_write_unsafe(sscape->io_base, GA_INTCFG_REG, + 0xf0 | mpu_irq_cfg); + sscape_write_unsafe(sscape->io_base, GA_CDCFG_REG, + 0x09 | DMA_8BIT | + (sscape->dma1 << 4) | (irq_cfg << 1)); + /* + * Enable the master IRQ ... + */ + sscape_write_unsafe(sscape->io_base, GA_INTENA_REG, 0x80); + } + + return 0; +} + /* * Perform certain arcane port-checks to see whether there * is a SoundScape board lurking behind the given ports. @@ -890,37 +990,33 @@ _error: /* * Create an ALSA soundcard entry for the SoundScape, using - * the given list of port, IRQ and DMA resources. + * the resolved port, IRQ and DMA resources. */ -static int create_sscape(int dev, struct snd_card *card) +static int create_sscape(struct snd_card *card) { struct soundscape *sscape = get_card_soundscape(card); - unsigned dma_cfg; - unsigned irq_cfg; - unsigned mpu_irq_cfg; struct resource *io_res; struct resource *wss_res; int err; - int val; const char *name; /* * Grab IO ports that we will need to probe so that we * can detect and control this hardware ... */ - io_res = devm_request_region(card->dev, port[dev], 8, "SoundScape"); + io_res = devm_request_region(card->dev, sscape->io_base, 8, "SoundScape"); if (!io_res) { dev_err(card->dev, - "sscape: can't grab port 0x%lx\n", port[dev]); + "sscape: can't grab port 0x%x\n", sscape->io_base); return -EBUSY; } wss_res = NULL; if (sscape->type == SSCAPE_VIVO) { - wss_res = devm_request_region(card->dev, wss_port[dev], 4, + wss_res = devm_request_region(card->dev, sscape->wss_base, 4, "SoundScape"); if (!wss_res) { dev_err(card->dev, "sscape: can't grab port 0x%lx\n", - wss_port[dev]); + sscape->wss_base); return -EBUSY; } } @@ -928,18 +1024,17 @@ static int create_sscape(int dev, struct snd_card *card) /* * Grab one DMA channel ... */ - err = snd_devm_request_dma(card->dev, dma[dev], "SoundScape"); + err = snd_devm_request_dma(card->dev, sscape->dma1, "SoundScape"); if (err < 0) { - dev_err(card->dev, "sscape: can't grab DMA %d\n", dma[dev]); + dev_err(card->dev, "sscape: can't grab DMA %d\n", sscape->dma1); return err; } spin_lock_init(&sscape->lock); sscape->io_res = io_res; sscape->wss_res = wss_res; - sscape->io_base = port[dev]; - if (!detect_sscape(sscape, wss_port[dev])) { + if (!detect_sscape(sscape, sscape->wss_base)) { dev_err(card->dev, "sscape: hardware not detected at 0x%x\n", sscape->io_base); return -ENODEV; @@ -964,66 +1059,28 @@ static int create_sscape(int dev, struct snd_card *card) } dev_info(card->dev, "sscape: %s card detected at 0x%x, using IRQ %d, DMA %d\n", - name, sscape->io_base, irq[dev], dma[dev]); - - /* - * Check that the user didn't pass us garbage data ... - */ - irq_cfg = get_irq_config(sscape->type, irq[dev]); - if (irq_cfg == INVALID_IRQ) { - dev_err(card->dev, "sscape: Invalid IRQ %d\n", irq[dev]); - return -ENXIO; - } - - mpu_irq_cfg = get_irq_config(sscape->type, mpu_irq[dev]); - if (mpu_irq_cfg == INVALID_IRQ) { - dev_err(card->dev, "sscape: Invalid IRQ %d\n", mpu_irq[dev]); - return -ENXIO; - } + name, sscape->io_base, sscape->irq, sscape->dma1); /* * Tell the on-board devices where their resources are (I think - * I can't be sure without a datasheet ... So many magic values!) */ - scoped_guard(spinlock_irqsave, &sscape->lock) { - - sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2e); - sscape_write_unsafe(sscape->io_base, GA_SMCFGB_REG, 0x00); - - /* - * Enable and configure the DMA channels ... - */ - sscape_write_unsafe(sscape->io_base, GA_DMACFG_REG, 0x50); - dma_cfg = (sscape->ic_type == IC_OPUS ? 0x40 : 0x70); - sscape_write_unsafe(sscape->io_base, GA_DMAA_REG, dma_cfg); - sscape_write_unsafe(sscape->io_base, GA_DMAB_REG, 0x20); - - mpu_irq_cfg |= mpu_irq_cfg << 2; - val = sscape_read_unsafe(sscape->io_base, GA_HMCTL_REG) & 0xF7; - if (joystick[dev]) - val |= 8; - sscape_write_unsafe(sscape->io_base, GA_HMCTL_REG, val | 0x10); - sscape_write_unsafe(sscape->io_base, GA_INTCFG_REG, 0xf0 | mpu_irq_cfg); - sscape_write_unsafe(sscape->io_base, - GA_CDCFG_REG, 0x09 | DMA_8BIT - | (dma[dev] << 4) | (irq_cfg << 1)); - /* - * Enable the master IRQ ... - */ - sscape_write_unsafe(sscape->io_base, GA_INTENA_REG, 0x80); - + err = sscape_configure_board(sscape); + if (err < 0) { + dev_err(card->dev, "sscape: Invalid IRQ configuration\n"); + return err; } /* * We have now enabled the codec chip, and so we should * detect the AD1845 device ... */ - err = create_ad1845(card, wss_port[dev], irq[dev], - dma[dev], dma2[dev]); + err = create_ad1845(card, sscape->wss_base, sscape->irq, + sscape->dma1, sscape->dma2); if (err < 0) { dev_err(card->dev, "sscape: No AD1845 device at 0x%lx, IRQ %d\n", - wss_port[dev], irq[dev]); + sscape->wss_base, sscape->irq); return err; } strscpy(card->driver, "SoundScape"); @@ -1040,35 +1097,21 @@ static int create_sscape(int dev, struct snd_card *card) err = sscape_upload_microcode(card, err); if (err == 0) { - err = create_mpu401(card, MIDI_DEVNUM, port[dev], - mpu_irq[dev]); + err = create_mpu401(card, MIDI_DEVNUM, sscape->io_base, + sscape->mpu_irq); if (err < 0) { dev_err(card->dev, "sscape: Failed to create MPU-401 device at 0x%lx\n", - port[dev]); + (unsigned long)sscape->io_base); return err; } - /* - * Initialize mixer - */ - guard(spinlock_irqsave)(&sscape->lock); sscape->midi_vol = 0; - host_write_ctrl_unsafe(sscape->io_base, - CMD_SET_MIDI_VOL, 100); - host_write_ctrl_unsafe(sscape->io_base, - sscape->midi_vol, 100); - host_write_ctrl_unsafe(sscape->io_base, - CMD_XXX_MIDI_VOL, 100); - host_write_ctrl_unsafe(sscape->io_base, - sscape->midi_vol, 100); - host_write_ctrl_unsafe(sscape->io_base, - CMD_SET_EXTMIDI, 100); - host_write_ctrl_unsafe(sscape->io_base, - 0, 100); - host_write_ctrl_unsafe(sscape->io_base, CMD_ACK, 100); - - set_midi_mode_unsafe(sscape->io_base); + err = sscape_restore_midi_state(sscape); + if (err < 0) + dev_warn(card->dev, + "sscape: MIDI init incomplete: %d\n", + err); } } @@ -1111,8 +1154,9 @@ static int snd_sscape_probe(struct device *pdev, unsigned int dev) sscape->type = SSCAPE; dma[dev] &= 0x03; + sscape_store_settings(sscape, dev); - ret = create_sscape(dev, card); + ret = create_sscape(card); if (ret < 0) return ret; @@ -1130,7 +1174,6 @@ static int snd_sscape_probe(struct device *pdev, unsigned int dev) static struct isa_driver snd_sscape_driver = { .match = snd_sscape_match, .probe = snd_sscape_probe, - /* FIXME: suspend/resume */ .driver = { .name = DEV_NAME }, @@ -1211,8 +1254,9 @@ static int sscape_pnp_detect(struct pnp_card_link *pcard, wss_port[idx] = pnp_port_start(dev, 1); dma2[idx] = pnp_dma(dev, 1); } + sscape_store_settings(sscape, idx); - ret = create_sscape(idx, card); + ret = create_sscape(card); if (ret < 0) return ret; From 713e0f011178a2896e46db3244093454708066e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= Date: Sat, 11 Apr 2026 15:14:41 -0300 Subject: [PATCH 350/359] ALSA: sscape: Add suspend and resume support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SoundScape ISA driver has lacked suspend and resume callbacks since commit 277e926c9b27 ("[ALSA] sscape - Use platform_device"). A plain snd_wss resume is not sufficient for SoundScape. Resume also needs to restore the board-specific gate-array routing, and non-VIVO boards need to reinitialize the probe-time MIDI firmware and MIDI control state when the MPU-401 side was enabled during probe. That firmware reload can be handled in-kernel because commit acd47100914b ("ALSA: sscape: convert to firmware loader framework") moved the driver to request_firmware(). Add ISA and ISA-PnP PM callbacks, reconfigure the board on resume, reload the non-VIVO MIDI firmware, restore the MIDI state, and then resume the WSS codec. If MIDI firmware reload fails, keep the WSS resume path alive and leave MIDI unavailable instead of failing the whole device resume. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260411-alsa-sscape-pm-v2-2-aeb5682e14b0@gmail.com Signed-off-by: Takashi Iwai --- sound/isa/sscape.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c index 6e951b3c0080..553ceb92d298 100644 --- a/sound/isa/sscape.c +++ b/sound/isa/sscape.c @@ -144,6 +144,7 @@ struct soundscape { unsigned char midi_vol; bool joystick; + bool midi_enabled; struct device *dev; }; @@ -1107,6 +1108,7 @@ static int create_sscape(struct snd_card *card) } sscape->midi_vol = 0; + sscape->midi_enabled = true; err = sscape_restore_midi_state(sscape); if (err < 0) dev_warn(card->dev, @@ -1118,6 +1120,77 @@ static int create_sscape(struct snd_card *card) return 0; } +#ifdef CONFIG_PM +/* + * Reload the MIDI firmware and restore the saved MIDI state for + * boards whose MPU-401 side was enabled during probe. + */ +static int sscape_resume_midi(struct snd_card *card) +{ + struct soundscape *sscape = get_card_soundscape(card); + int err, version; + + if (!sscape->midi_enabled) + return 0; + + version = sscape_upload_bootblock(card); + if (version < 0) + return version; + + err = sscape_upload_microcode(card, version); + if (err < 0) + return err; + + outb(0, sscape->io_base); + + return sscape_restore_midi_state(sscape); +} + +/* + * Save the WSS codec state before the SoundScape is suspended. + */ +static int snd_sscape_suspend_card(struct snd_card *card) +{ + struct soundscape *sscape = get_card_soundscape(card); + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + sscape->chip->suspend(sscape->chip); + return 0; +} + +/* + * Restore the board-specific state before resuming the WSS codec. + */ +static int snd_sscape_resume_card(struct snd_card *card) +{ + struct soundscape *sscape = get_card_soundscape(card); + int err; + + err = sscape_configure_board(sscape); + if (err < 0) + return err; + + err = sscape_resume_midi(card); + if (err < 0) + dev_warn(card->dev, "sscape: MIDI restore failed: %d\n", err); + + sscape->chip->resume(sscape->chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static int snd_sscape_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + return snd_sscape_suspend_card(dev_get_drvdata(dev)); +} + +static int snd_sscape_resume(struct device *dev, unsigned int n) +{ + return snd_sscape_resume_card(dev_get_drvdata(dev)); +} +#endif + static int snd_sscape_match(struct device *pdev, unsigned int i) { @@ -1174,6 +1247,10 @@ static int snd_sscape_probe(struct device *pdev, unsigned int dev) static struct isa_driver snd_sscape_driver = { .match = snd_sscape_match, .probe = snd_sscape_probe, +#ifdef CONFIG_PM + .suspend = snd_sscape_suspend, + .resume = snd_sscape_resume, +#endif .driver = { .name = DEV_NAME }, @@ -1271,11 +1348,27 @@ static int sscape_pnp_detect(struct pnp_card_link *pcard, return 0; } +#ifdef CONFIG_PM +static int sscape_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state) +{ + return snd_sscape_suspend_card(pnp_get_card_drvdata(pcard)); +} + +static int sscape_pnp_resume(struct pnp_card_link *pcard) +{ + return snd_sscape_resume_card(pnp_get_card_drvdata(pcard)); +} +#endif + static struct pnp_card_driver sscape_pnpc_driver = { .flags = PNP_DRIVER_RES_DO_NOT_CHANGE, .name = "sscape", .id_table = sscape_pnpids, .probe = sscape_pnp_detect, +#ifdef CONFIG_PM + .suspend = sscape_pnp_suspend, + .resume = sscape_pnp_resume, +#endif }; #endif /* CONFIG_PNP */ From 115e7d764dad66a10e150bd4b3ba3bbb95b04d85 Mon Sep 17 00:00:00 2001 From: Anushka Badhe Date: Fri, 10 Apr 2026 11:25:32 +0530 Subject: [PATCH 351/359] ASoC: dt-bindings: rockchip: convert rk3399-gru-sound to DT Schema Convert the rockchip,rk3399-gru-sound.txt DT binding to DT Schema format. Update rockchip,cpu from a single I2S controller phandle to a phandle-array. Add an optional second entry for the SPDIF controller, as seen in rk3399-gru.dtsi, required by boards with DisplayPort audio. Signed-off-by: Anushka Badhe Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260410055532.60868-1-anushkabadhe@gmail.com Signed-off-by: Mark Brown --- .../sound/rockchip,rk3399-gru-sound.txt | 22 ------- .../sound/rockchip,rk3399-gru-sound.yaml | 60 +++++++++++++++++++ 2 files changed, 60 insertions(+), 22 deletions(-) delete mode 100644 Documentation/devicetree/bindings/sound/rockchip,rk3399-gru-sound.txt create mode 100644 Documentation/devicetree/bindings/sound/rockchip,rk3399-gru-sound.yaml diff --git a/Documentation/devicetree/bindings/sound/rockchip,rk3399-gru-sound.txt b/Documentation/devicetree/bindings/sound/rockchip,rk3399-gru-sound.txt deleted file mode 100644 index 72d3cf4c2606..000000000000 --- a/Documentation/devicetree/bindings/sound/rockchip,rk3399-gru-sound.txt +++ /dev/null @@ -1,22 +0,0 @@ -ROCKCHIP with MAX98357A/RT5514/DA7219 codecs on GRU boards - -Required properties: -- compatible: "rockchip,rk3399-gru-sound" -- rockchip,cpu: The phandle of the Rockchip I2S controller that's - connected to the codecs -- rockchip,codec: The phandle of the audio codecs - -Optional properties: -- dmic-wakeup-delay-ms : specify delay time (ms) for DMIC ready. - If this option is specified, which means it's required dmic need - delay for DMIC to ready so that rt5514 can avoid recording before - DMIC send valid data - -Example: - -sound { - compatible = "rockchip,rk3399-gru-sound"; - rockchip,cpu = <&i2s0>; - rockchip,codec = <&max98357a &rt5514 &da7219>; - dmic-wakeup-delay-ms = <20>; -}; diff --git a/Documentation/devicetree/bindings/sound/rockchip,rk3399-gru-sound.yaml b/Documentation/devicetree/bindings/sound/rockchip,rk3399-gru-sound.yaml new file mode 100644 index 000000000000..e9d13695cc77 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/rockchip,rk3399-gru-sound.yaml @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/rockchip,rk3399-gru-sound.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip with MAX98357A/RT5514/DA7219 codecs on GRU boards + +maintainers: + - Heiko Stuebner + +properties: + compatible: + const: rockchip,rk3399-gru-sound + + rockchip,cpu: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: | + List of phandles to the Rockchip CPU DAI controllers connected to codecs + minItems: 1 + items: + - items: + - description: Phandle to the Rockchip I2S controllers + - items: + - description: | + Phandle to the Rockchip SPDIF controller. Required when a + DisplayPort audio codec is referenced in rockchip,codec + + rockchip,codec: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: | + The phandles of the audio codecs connected to the Rockchip CPU DAI + controllers + minItems: 1 + maxItems: 6 + items: + maxItems: 1 + + dmic-wakeup-delay-ms: + description: | + specify delay time (ms) for DMIC ready. + If this option is specified, a delay is required for DMIC to get ready + so that rt5514 can avoid recording before DMIC sends valid data + +required: + - compatible + - rockchip,cpu + - rockchip,codec + +additionalProperties: false + +examples: + - | + sound { + compatible = "rockchip,rk3399-gru-sound"; + rockchip,cpu = <&i2s0 &spdif>; + rockchip,codec = <&max98357a &rt5514 &da7219 &cdn_dp>; + dmic-wakeup-delay-ms = <20>; + }; + From 80bb50e2d459213cccff3111d5ef98ed4238c0d5 Mon Sep 17 00:00:00 2001 From: Berk Cem Goksel Date: Mon, 13 Apr 2026 06:49:41 +0300 Subject: [PATCH 352/359] ALSA: caiaq: take a reference on the USB device in create_card() The caiaq driver stores a pointer to the parent USB device in cdev->chip.dev but never takes a reference on it. The card's private_free callback, snd_usb_caiaq_card_free(), can run asynchronously via snd_card_free_when_closed() after the USB device has already been disconnected and freed, so any access to cdev->chip.dev in that path dereferences a freed usb_device. On top of the refcounting issue, the current card_free implementation calls usb_reset_device(cdev->chip.dev). A reset in a free callback is inappropriate: the device is going away, the call takes the device lock in a teardown context, and the reset races with the disconnect path that the callback is already cleaning up after. Take a reference on the USB device in create_card() with usb_get_dev(), drop it with usb_put_dev() in the free callback, and remove the usb_reset_device() call. Fixes: b04dcbb7f7b1 ("ALSA: caiaq: Use snd_card_free_when_closed() at disconnection") Cc: stable@vger.kernel.org Cc: Andrey Konovalov Signed-off-by: Berk Cem Goksel Link: https://patch.msgid.link/20260413034941.1131465-3-berkcgoksel@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/caiaq/device.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index 3a71bab8a477..51177ebfb8c6 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -384,7 +384,7 @@ static void card_free(struct snd_card *card) snd_usb_caiaq_input_free(cdev); #endif snd_usb_caiaq_audio_free(cdev); - usb_reset_device(cdev->chip.dev); + usb_put_dev(cdev->chip.dev); } static int create_card(struct usb_device *usb_dev, @@ -410,7 +410,7 @@ static int create_card(struct usb_device *usb_dev, return err; cdev = caiaqdev(card); - cdev->chip.dev = usb_dev; + cdev->chip.dev = usb_get_dev(usb_dev); cdev->chip.card = card; cdev->chip.usb_id = USB_ID(le16_to_cpu(usb_dev->descriptor.idVendor), le16_to_cpu(usb_dev->descriptor.idProduct)); From 882321ccaeea52dd645dff98bfea2f92b286e673 Mon Sep 17 00:00:00 2001 From: songxiebing Date: Mon, 13 Apr 2026 09:18:54 +0800 Subject: [PATCH 353/359] ALSA: hda/realtek: Add quirk for HP Spectre x360 14-ea HP Spectre x360 Convertible 14-ea0xxx (2021 model or so) doesn't make produce sound,The Bang & Olufsen speaker amplifier is not enabled. Root causing: The PCI subsystem ID is 103c:0000 (HP left it unset), while the codec subsystem ID is 103c:885b. The vendor-wide catch-all SND_PCI_QUIRK_VENDOR(0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED) matches 103c:0000 before the codec SSID fallback is reached, so ALC245_FIXUP_HP_X360_AMP never applies. So add the quirk in alc269_fixup_tbl. Reported-by: dzidmail Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221341 Signed-off-by: songxiebing Link: https://patch.msgid.link/20260413011854.96520-1-songxiebing@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 0a5eadec3ef6..2cf1cc3cb91c 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6947,6 +6947,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8847, "HP EliteBook x360 830 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x884b, "HP EliteBook 840 Aero G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x884c, "HP EliteBook 840 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), + HDA_CODEC_QUIRK(0x103c, 0x885b, "HP Spectre x360 14-ea", ALC245_FIXUP_HP_X360_AMP), SND_PCI_QUIRK(0x103c, 0x8862, "HP ProBook 445 G8 Notebook PC", ALC236_FIXUP_HP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x103c, 0x8863, "HP ProBook 445 G8 Notebook PC", ALC236_FIXUP_HP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x103c, 0x886d, "HP ZBook Fury 17.3 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), From 67f4c61a73e9b17dc9593bf27badc6785ecadd78 Mon Sep 17 00:00:00 2001 From: Eric Naim Date: Mon, 13 Apr 2026 23:48:17 +0800 Subject: [PATCH 354/359] ALSA: hda/realtek: Add quirk for Legion S7 15IMH Fix speaker output on the Lenovo Legion S7 15IMH05. Cc: stable@vger.kernel.org Signed-off-by: Eric Naim Link: https://patch.msgid.link/20260413154818.351597-1-dnaim@cachyos.org Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 2cf1cc3cb91c..45bbd63370d6 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7607,6 +7607,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3801, "Lenovo Yoga9 14IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), HDA_CODEC_QUIRK(0x17aa, 0x3802, "DuetITL 2021", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3802, "Lenovo Yoga Pro 9 14IRP8", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x3811, "Legion S7 15IMH05", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3813, "Legion 7i 15IMHG05", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3818, "Lenovo C940 / Yoga Duet 7", ALC298_FIXUP_LENOVO_C940_DUET7), SND_PCI_QUIRK(0x17aa, 0x3819, "Lenovo 13s Gen2 ITL", ALC287_FIXUP_13S_GEN2_SPEAKERS), From a47306a74c31557b1e5cab54642950bbb20294cb Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Tue, 14 Apr 2026 03:03:00 +0930 Subject: [PATCH 355/359] ALSA: usb-audio: Exclude Scarlett 18i20 1st Gen from SKIP_IFACE_SETUP Same issue as the other 1st Gen Scarletts: QUIRK_FLAG_SKIP_IFACE_SETUP causes distorted audio on the Scarlett 18i20 1st Gen (1235:800c). Fixes: 38c322068a26 ("ALSA: usb-audio: Add QUIRK_FLAG_SKIP_IFACE_SETUP") Reported-by: tucktuckg00se [https://github.com/geoffreybennett/linux-fcp/issues/54] Signed-off-by: Geoffrey D. Bennett Link: https://patch.msgid.link/ad0ozNnkcFrcjVQz@m.b4.vu Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 519d9d1a2a41..8fc36d1cfb9d 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2501,6 +2501,7 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_VALIDATE_RATES), DEVICE_FLG(0x1235, 0x8006, 0), /* Focusrite Scarlett 2i2 1st Gen */ DEVICE_FLG(0x1235, 0x800a, 0), /* Focusrite Scarlett 2i4 1st Gen */ + DEVICE_FLG(0x1235, 0x800c, 0), /* Focusrite Scarlett 18i20 1st Gen */ DEVICE_FLG(0x1235, 0x8016, 0), /* Focusrite Scarlett 2i2 1st Gen */ DEVICE_FLG(0x1235, 0x801c, 0), /* Focusrite Scarlett Solo 1st Gen */ VENDOR_FLG(0x1235, /* Focusrite Novation */ From a7b56be59b47f4195ddc79ecab238c4401a60bbb Mon Sep 17 00:00:00 2001 From: Faye Nichols Date: Mon, 13 Apr 2026 15:20:25 -0600 Subject: [PATCH 356/359] ALSA: hda/realtek: Add quirk for Acer PT316-51S headset mic The Acer PT316-51S (PCI SSID 1025:160e) with ALC287 codec does not detect the headset microphone due to missing BIOS pin configuration for pin 0x19. Apply ALC2XX_FIXUP_HEADSET_MIC to enable it. Signed-off-by: Faye Nichols Link: https://patch.msgid.link/20260413212645.117119-1-faye.opensource@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 45bbd63370d6..0579762a191a 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6711,6 +6711,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x1539, "Acer Nitro 5 AN515-57", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x159c, "Acer Nitro 5 AN515-58", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1597, "Acer Nitro 5 AN517-55", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x173a, "Acer Swift SFG14-73", ALC245_FIXUP_ACER_MICMUTE_LED), From 46c862f5419e0a86b60b9f9558d247f6084c99f9 Mon Sep 17 00:00:00 2001 From: Kailang Yang Date: Tue, 14 Apr 2026 15:44:04 +0800 Subject: [PATCH 357/359] ALSA: hda/realtek - fixed speaker no sound update Fixed speaker has pop noise on Lenovo Thinkpad X11 Carbon Gen 12. Fixes: 630fbc6e870e ("ALSA: hda/realtek - fixed speaker no sound") Reported-and-tested-by: Jeremy Bethmont Closes: https://lore.kernel.org/CAC88DfsHrhyhy0Pn1O-z9egBvMYu=6NYgcvcC6KCgwh_-Ldkxg@mail.gmail.com Signed-off-by: Kailang Yang Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 0579762a191a..b33f425763f9 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -3654,22 +3654,11 @@ static void alc287_alc1318_playback_pcm_hook(struct hda_pcm_stream *hinfo, struct snd_pcm_substream *substream, int action) { - static const struct coef_fw dis_coefs[] = { - WRITE_COEF(0x24, 0x0013), WRITE_COEF(0x25, 0x0000), WRITE_COEF(0x26, 0xC203), - WRITE_COEF(0x28, 0x0004), WRITE_COEF(0x29, 0xb023), - }; /* Disable AMP silence detection */ - static const struct coef_fw en_coefs[] = { - WRITE_COEF(0x24, 0x0013), WRITE_COEF(0x25, 0x0000), WRITE_COEF(0x26, 0xC203), - WRITE_COEF(0x28, 0x0084), WRITE_COEF(0x29, 0xb023), - }; /* Enable AMP silence detection */ - switch (action) { case HDA_GEN_PCM_ACT_OPEN: - alc_process_coef_fw(codec, dis_coefs); alc_write_coefex_idx(codec, 0x5a, 0x00, 0x954f); /* write gpio3 to high */ break; case HDA_GEN_PCM_ACT_CLOSE: - alc_process_coef_fw(codec, en_coefs); alc_write_coefex_idx(codec, 0x5a, 0x00, 0x554f); /* write gpio3 as default value */ break; } @@ -3692,10 +3681,15 @@ static void alc287_fixup_lenovo_thinkpad_with_alc1318(struct hda_codec *codec, WRITE_COEF(0x24, 0x0013), WRITE_COEF(0x25, 0x0000), WRITE_COEF(0x26, 0xC301), WRITE_COEF(0x28, 0x0001), WRITE_COEF(0x29, 0xb023), }; + static const struct coef_fw dis_coefs[] = { + WRITE_COEF(0x24, 0x0013), WRITE_COEF(0x25, 0x0000), WRITE_COEF(0x26, 0xC203), + WRITE_COEF(0x28, 0x0004), WRITE_COEF(0x29, 0xb023), + }; /* Disable AMP silence detection */ if (action != HDA_FIXUP_ACT_PRE_PROBE) return; alc_update_coef_idx(codec, 0x10, 1<<11, 1<<11); + alc_process_coef_fw(codec, dis_coefs); alc_process_coef_fw(codec, coefs); spec->power_hook = alc287_s4_power_gpio3_default; spec->gen.pcm_playback_hook = alc287_alc1318_playback_pcm_hook; From a1ed2ec1c5458b4a99765439cb595dd0e026a352 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 14 Apr 2026 11:33:34 +0200 Subject: [PATCH 358/359] ALSA: usb-audio: Fix missing error handling for get_min_max*() The recent fix to add the error return value check from get_min_max*() missed one case in build_audio_procunit() where no error value is set. This may lead to an uninitialized variable and confuse the caller (although this wouldn't happen practically because err is set for the loop of num_ins at the beginning of the funciton). Fix it by setting "err = 0" properly at the missing case, too. Fixes: 4f55a85cd4fc ("ALSA: usb-audio: Add error checks against get_min_max*()") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/ad36dGpCBTGsyFr_@stanley.mountain Link: https://patch.msgid.link/20260414093336.305464-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index d4ef45bf53d7..aa6ea3be100a 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -2687,6 +2687,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, cval->max = control_spec[0]; cval->res = 1; cval->initialized = 1; + err = 0; break; } From e0da8a8cac74f4b9f577979d131f0d2b88a84487 Mon Sep 17 00:00:00 2001 From: Ziqing Chen Date: Tue, 14 Apr 2026 21:24:37 +0800 Subject: [PATCH 359/359] ALSA: control: Validate buf_len before strnlen() in snd_ctl_elem_init_enum_names() snd_ctl_elem_init_enum_names() advances pointer p through the names buffer while decrementing buf_len. If buf_len reaches zero but items remain, the next iteration calls strnlen(p, 0). While strnlen(p, 0) returns 0 and would hit the existing name_len == 0 error path, CONFIG_FORTIFY_SOURCE's fortified strnlen() first checks maxlen against __builtin_dynamic_object_size(). When Clang loses track of p's object size inside the loop, this triggers a BRK exception panic before the return value is examined. Add a buf_len == 0 guard at the loop entry to prevent calling fortified strnlen() on an exhausted buffer. Found by kernel fuzz testing through Xiaomi Smartphone. Fixes: 8d448162bda5 ("ALSA: control: add support for ENUMERATED user space controls") Cc: stable@vger.kernel.org Signed-off-by: Ziqing Chen Link: https://patch.msgid.link/20260414132437.261304-1-chenziqing@xiaomi.com Signed-off-by: Takashi Iwai --- sound/core/control.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/core/control.c b/sound/core/control.c index 374e703d15a9..5e51857635e6 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -1648,6 +1648,10 @@ static int snd_ctl_elem_init_enum_names(struct user_element *ue) /* check that there are enough valid names */ p = names; for (i = 0; i < ue->info.value.enumerated.items; ++i) { + if (buf_len == 0) { + kvfree(names); + return -EINVAL; + } name_len = strnlen(p, buf_len); if (name_len == 0 || name_len >= 64 || name_len == buf_len) { kvfree(names);