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_class.c b/sound/soc/sdca/sdca_class.c index 349d32933ba8..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); @@ -238,6 +239,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 +311,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.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 416948cfb5cb..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 @@ -33,6 +34,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) @@ -210,6 +212,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, @@ -218,37 +245,17 @@ 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); 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, @@ -414,9 +421,44 @@ 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); + 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); + + 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; + } + ret = regcache_sync(drv->regmap); if (ret) { dev_err(drv->dev, "failed to restore register cache: %d\n", ret); @@ -431,7 +473,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) }; diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c index ff3a7e405fdc..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; } @@ -541,3 +548,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");