Support wm_adsp hibernation for runtime suspend

Merge series from Stefan Binding <sbinding@opensource.cirrus.com>:

When the CS35L41 and CS35L45 drivers suspend, they are 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 these spurious errors, add an apis into cs_dsp and wm_adsp
to allow wm_adsp to hibernate. In this hibernation mode, reads or
writes to the dsp controls would be rejected with -EPERM rather than
-EBUSY, and no error will be printed to the kernel log.
This commit is contained in:
Mark Brown 2026-03-02 13:35:21 +00:00
commit 727d1a1c4e
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
6 changed files with 63 additions and 4 deletions

View File

@ -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, &reg, 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 @@ static int cs_dsp_create_control(struct cs_dsp *dsp,
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);

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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)
{

View File

@ -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);