From 9e3d4f794cbe9a4e286b3052cb97908005807aee Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 9 Jan 2026 14:52:03 +0000 Subject: [PATCH 1/4] ASoC: SDCA: Add SDCA IRQ enable/disable helpers Add helpers to enable and disable the SDCA IRQs by Function. These are useful to sequence the powering down and up around system suspend. Signed-off-by: Charles Keepax Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20260109145206.3456151-2-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/sdca_interrupts.h | 7 +++ sound/soc/sdca/sdca_interrupts.c | 76 ++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/include/sound/sdca_interrupts.h b/include/sound/sdca_interrupts.h index 8f13417d129a..9bcb5d8fd592 100644 --- a/include/sound/sdca_interrupts.h +++ b/include/sound/sdca_interrupts.h @@ -84,4 +84,11 @@ int sdca_irq_populate(struct sdca_function_data *function, struct sdca_interrupt_info *sdca_irq_allocate(struct device *dev, struct regmap *regmap, int irq); +void sdca_irq_enable_early(struct sdca_function_data *function, + struct sdca_interrupt_info *info); +void sdca_irq_enable(struct sdca_function_data *function, + struct sdca_interrupt_info *info); +void sdca_irq_disable(struct sdca_function_data *function, + struct sdca_interrupt_info *info); + #endif diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c index ff3a7e405fdc..afef7bbf613c 100644 --- a/sound/soc/sdca/sdca_interrupts.c +++ b/sound/soc/sdca/sdca_interrupts.c @@ -541,3 +541,79 @@ struct sdca_interrupt_info *sdca_irq_allocate(struct device *sdev, return info; } 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]; + + if (!interrupt || interrupt->function != function) + continue; + + switch (SDCA_CTL_TYPE(interrupt->entity->type, + interrupt->control->sel)) { + case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER): + if (early) + enable_irq(interrupt->irq); + break; + default: + if (!early) + enable_irq(interrupt->irq); + break; + } + } +} + +/** + * sdca_irq_enable_early - Re-enable early SDCA IRQs for a given function + * @function: Pointer to the SDCA Function. + * @info: Pointer to the SDCA interrupt info for this device. + * + * The early version of the IRQ enable allows enabling IRQs which may be + * necessary to bootstrap functionality for other IRQs, such as the FDL + * process. + */ +void sdca_irq_enable_early(struct sdca_function_data *function, + struct sdca_interrupt_info *info) +{ + irq_enable_flags(function, info, true); +} +EXPORT_SYMBOL_NS_GPL(sdca_irq_enable_early, "SND_SOC_SDCA"); + +/** + * sdca_irq_enable - Re-enable SDCA IRQs for a given function + * @function: Pointer to the SDCA Function. + * @info: Pointer to the SDCA interrupt info for this device. + */ +void sdca_irq_enable(struct sdca_function_data *function, + struct sdca_interrupt_info *info) +{ + irq_enable_flags(function, info, false); +} +EXPORT_SYMBOL_NS_GPL(sdca_irq_enable, "SND_SOC_SDCA"); + +/** + * sdca_irq_disable - Disable SDCA IRQs for a given function + * @function: Pointer to the SDCA Function. + * @info: Pointer to the SDCA interrupt info for this device. + */ +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]; + + if (!interrupt || interrupt->function != function) + continue; + + disable_irq(interrupt->irq); + } +} +EXPORT_SYMBOL_NS_GPL(sdca_irq_disable, "SND_SOC_SDCA"); From 7a5214f769c7c953c58971027a762f2e191057d4 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 9 Jan 2026 14:52:04 +0000 Subject: [PATCH 2/4] ASoC: SDCA: Add basic system suspend support Add basic system suspend support. Disable the IRQs and force runtime suspend, during system suspend, because the device will likely fully power down during suspend. Signed-off-by: Charles Keepax Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20260109145206.3456151-3-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_class.c | 33 ++++++++++++++++++ sound/soc/sdca/sdca_class_function.c | 51 ++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c index 349d32933ba8..6d19a183683e 100644 --- a/sound/soc/sdca/sdca_class.c +++ b/sound/soc/sdca/sdca_class.c @@ -238,6 +238,38 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id return 0; } +static int class_suspend(struct device *dev) +{ + struct sdca_class_drv *drv = dev_get_drvdata(dev); + int ret; + + disable_irq(drv->sdw->irq); + + ret = pm_runtime_force_suspend(dev); + if (ret) { + dev_err(dev, "failed to force suspend: %d\n", ret); + return ret; + } + + return 0; +} + +static int class_resume(struct device *dev) +{ + struct sdca_class_drv *drv = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_resume(dev); + if (ret) { + dev_err(dev, "failed to force resume: %d\n", ret); + return ret; + } + + enable_irq(drv->sdw->irq); + + return 0; +} + static int class_runtime_suspend(struct device *dev) { struct sdca_class_drv *drv = dev_get_drvdata(dev); @@ -278,6 +310,7 @@ static int class_runtime_resume(struct device *dev) } static const struct dev_pm_ops class_pm_ops = { + SYSTEM_SLEEP_PM_OPS(class_suspend, class_resume) RUNTIME_PM_OPS(class_runtime_suspend, class_runtime_resume, NULL) }; diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index 416948cfb5cb..7d0a6c0adbfb 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -33,6 +33,7 @@ struct class_function_drv { struct sdca_class_drv *core; struct sdca_function_data *function; + bool suspended; }; static void class_function_regmap_lock(void *data) @@ -417,6 +418,14 @@ static int class_function_runtime_resume(struct device *dev) regcache_mark_dirty(drv->regmap); regcache_cache_only(drv->regmap, false); + if (drv->suspended) { + sdca_irq_enable_early(drv->function, drv->core->irq_info); + /* TODO: Add FDL process between early and late IRQs */ + sdca_irq_enable(drv->function, drv->core->irq_info); + + drv->suspended = false; + } + ret = regcache_sync(drv->regmap); if (ret) { dev_err(drv->dev, "failed to restore register cache: %d\n", ret); @@ -431,7 +440,49 @@ static int class_function_runtime_resume(struct device *dev) return ret; } +static int class_function_suspend(struct device *dev) +{ + struct auxiliary_device *auxdev = to_auxiliary_dev(dev); + struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); + int ret; + + drv->suspended = true; + + /* Ensure runtime resume runs on resume */ + ret = pm_runtime_resume_and_get(dev); + if (ret) { + dev_err(dev, "failed to resume for suspend: %d\n", ret); + return ret; + } + + sdca_irq_disable(drv->function, drv->core->irq_info); + + ret = pm_runtime_force_suspend(dev); + if (ret) { + dev_err(dev, "failed to force suspend: %d\n", ret); + return ret; + } + + pm_runtime_put_noidle(dev); + + return 0; +} + +static int class_function_resume(struct device *dev) +{ + int ret; + + ret = pm_runtime_force_resume(dev); + if (ret) { + dev_err(dev, "failed to force resume: %d\n", ret); + return ret; + } + + return 0; +} + static const struct dev_pm_ops class_function_pm_ops = { + SYSTEM_SLEEP_PM_OPS(class_function_suspend, class_function_resume) RUNTIME_PM_OPS(class_function_runtime_suspend, class_function_runtime_resume, NULL) }; From ffd7e8a101110cba86925a2906d925e0db7102f3 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 9 Jan 2026 14:52:05 +0000 Subject: [PATCH 3/4] ASoC: SDCA: Device boot into the system suspend process When system suspending the device may be powered off, this means all state will be lost and the firmware may need to be re-downloaded. Add the necessary calls to bring the device back up. This also requires that that the FDL (firmware download) IRQ handler is modified to allow it to run before runtime PM has been fully restored. Signed-off-by: Charles Keepax Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20260109145206.3456151-4-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_class_function.c | 80 +++++++++++++++++++--------- sound/soc/sdca/sdca_interrupts.c | 17 ++++-- 2 files changed, 66 insertions(+), 31 deletions(-) diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index 7d0a6c0adbfb..bbf486d9a3d0 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -211,6 +211,31 @@ static const struct snd_soc_component_driver class_function_component_drv = { .endianness = 1, }; +static int class_function_init_device(struct class_function_drv *drv, + unsigned int status) +{ + int ret; + + if (!(status & SDCA_CTL_ENTITY_0_FUNCTION_HAS_BEEN_RESET)) { + dev_dbg(drv->dev, "reset function device\n"); + + ret = sdca_reset_function(drv->dev, drv->function, drv->regmap); + if (ret) + return ret; + } + + if (status & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION) { + dev_dbg(drv->dev, "write initialisation\n"); + + ret = sdca_regmap_write_init(drv->dev, drv->core->dev_regmap, + drv->function); + if (ret) + return ret; + } + + return 0; +} + static int class_function_boot(struct class_function_drv *drv) { unsigned int reg = SDW_SDCA_CTL(drv->function->desc->adr, @@ -225,31 +250,9 @@ static int class_function_boot(struct class_function_drv *drv) return ret; } - if (!(val & SDCA_CTL_ENTITY_0_FUNCTION_HAS_BEEN_RESET)) { - dev_dbg(drv->dev, "reset function device\n"); - - ret = sdca_reset_function(drv->dev, drv->function, drv->regmap); - if (ret) - return ret; - } - - if (val & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION) { - dev_dbg(drv->dev, "write initialisation\n"); - - ret = sdca_regmap_write_init(drv->dev, drv->core->dev_regmap, - drv->function); - if (ret) - return ret; - - ret = regmap_write(drv->regmap, reg, - SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION); - if (ret < 0) { - dev_err(drv->dev, - "failed to clear function init status: %d\n", - ret); - return ret; - } - } + ret = class_function_init_device(drv, val); + if (ret) + return ret; /* Start FDL process */ ret = sdca_irq_populate_early(drv->dev, drv->regmap, drv->function, @@ -419,10 +422,35 @@ static int class_function_runtime_resume(struct device *dev) regcache_cache_only(drv->regmap, false); if (drv->suspended) { + unsigned int reg = SDW_SDCA_CTL(drv->function->desc->adr, + SDCA_ENTITY_TYPE_ENTITY_0, + SDCA_CTL_ENTITY_0_FUNCTION_STATUS, 0); + unsigned int val; + + ret = regmap_read(drv->regmap, reg, &val); + if (ret < 0) { + dev_err(drv->dev, "failed to read function status: %d\n", ret); + goto err; + } + + ret = class_function_init_device(drv, val); + if (ret) + goto err; + sdca_irq_enable_early(drv->function, drv->core->irq_info); - /* TODO: Add FDL process between early and late IRQs */ + + ret = sdca_fdl_sync(drv->dev, drv->function, drv->core->irq_info); + if (ret) + goto err; + sdca_irq_enable(drv->function, drv->core->irq_info); + ret = regmap_write(drv->regmap, reg, 0xFF); + if (ret < 0) { + dev_err(drv->dev, "failed to clear function status: %d\n", ret); + goto err; + } + drv->suspended = false; } diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c index afef7bbf613c..cc40732c30cc 100644 --- a/sound/soc/sdca/sdca_interrupts.c +++ b/sound/soc/sdca/sdca_interrupts.c @@ -205,10 +205,16 @@ static irqreturn_t fdl_owner_handler(int irq, void *data) irqreturn_t irqret = IRQ_NONE; int ret; - ret = pm_runtime_get_sync(dev); - if (ret < 0) { - dev_err(dev, "failed to resume for fdl: %d\n", ret); - goto error; + /* + * FDL has to run from the system resume handler, at which point + * we can't wait for the pm runtime. + */ + if (completion_done(&dev->power.completion)) { + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "failed to resume for fdl: %d\n", ret); + goto error; + } } ret = sdca_fdl_process(interrupt); @@ -217,7 +223,8 @@ static irqreturn_t fdl_owner_handler(int irq, void *data) irqret = IRQ_HANDLED; error: - pm_runtime_put(dev); + if (completion_done(&dev->power.completion)) + pm_runtime_put(dev); return irqret; } From da7afdc79cba00f952df12cd579e44832d829c0a Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 9 Jan 2026 14:52:06 +0000 Subject: [PATCH 4/4] ASoC: SDCA: Add lock to serialise the Function initialisation To avoid issues on some devices serialise the boot of each SDCA Function from the others. Signed-off-by: Charles Keepax Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20260109145206.3456151-5-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_class.c | 1 + sound/soc/sdca/sdca_class.h | 2 ++ sound/soc/sdca/sdca_class_function.c | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c index 6d19a183683e..918b638acb57 100644 --- a/sound/soc/sdca/sdca_class.c +++ b/sound/soc/sdca/sdca_class.c @@ -205,6 +205,7 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id drv->dev = dev; drv->sdw = sdw; mutex_init(&drv->regmap_lock); + mutex_init(&drv->init_lock); dev_set_drvdata(drv->dev, drv); diff --git a/sound/soc/sdca/sdca_class.h b/sound/soc/sdca/sdca_class.h index bb4c9dd12429..6f24ea2bbd38 100644 --- a/sound/soc/sdca/sdca_class.h +++ b/sound/soc/sdca/sdca_class.h @@ -28,6 +28,8 @@ struct sdca_class_drv { struct sdca_interrupt_info *irq_info; struct mutex regmap_lock; + /* Serialise function initialisations */ + struct mutex init_lock; struct work_struct boot_work; struct completion device_attach; diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index bbf486d9a3d0..0afa41c1ee93 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -244,6 +245,8 @@ static int class_function_boot(struct class_function_drv *drv) unsigned int val; int ret; + guard(mutex)(&drv->core->init_lock); + ret = regmap_read(drv->regmap, reg, &val); if (ret < 0) { dev_err(drv->dev, "failed to read function status: %d\n", ret); @@ -418,6 +421,8 @@ static int class_function_runtime_resume(struct device *dev) struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); int ret; + guard(mutex)(&drv->core->init_lock); + regcache_mark_dirty(drv->regmap); regcache_cache_only(drv->regmap, false);