ASoC: cs35l56: Allow restoring factory calibration through ALSA control

Add an ALSA control (CAL_DATA) that can be used to restore amp calibration,
instead of using debugfs. A readback control (CAL_DATA_RB) is also added
for factory testing.

On ChromeOS the process that restores amp calibration from NVRAM has
limited permissions and cannot access debugfs. It requires an ALSA control
that it can write the calibration blob into. ChromeOS also restricts access
to ALSA controls, which avoids the risk of accidental or malicious
overwriting of good calibration data with bad data. As this control is not
needed for normal Linux-based distros it is a Kconfig option.

A separate control, CAL_DATA_RB, provides a readback of the current
calibration data, which could be either from a write to CAL_DATA or the
result of factory production-line calibration.

The write and read are intentionally separate controls to defeat "dumb"
save-and-restore tools like alsa-restore that assume it is safe to save
all control values and write them back in any order at some undefined
future time. Such behavior carries the risk of restoring stale or bad data
over the top of good data.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
Link: https://patch.msgid.link/20251111130850.513969-3-rf@opensource.cirrus.com
Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
Richard Fitzgerald
2025-11-11 13:08:50 +00:00
committed by Mark Brown
parent 69f3474a01
commit 32172cf3cb
4 changed files with 86 additions and 2 deletions

View File

@@ -388,6 +388,8 @@ int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base);
int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_soundwire);
void cs35l56_init_cs_dsp(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_dsp);
int cs35l56_get_calibration(struct cs35l56_base *cs35l56_base);
int cs35l56_stash_calibration(struct cs35l56_base *cs35l56_base,
const struct cirrus_amp_cal_data *data);
ssize_t cs35l56_calibrate_debugfs_write(struct cs35l56_base *cs35l56_base,
const char __user *from, size_t count,
loff_t *ppos);

View File

@@ -912,6 +912,20 @@ config SND_SOC_CS35L56_CAL_DEBUGFS
Create debugfs entries used during factory-line manufacture
for factory calibration.
If unsure select "N".
config SND_SOC_CS35L56_CAL_SET_CTRL
bool "CS35L56 ALSA control to restore factory calibration"
default N
select SND_SOC_CS35L56_CAL_SYSFS_COMMON
help
Allow restoring factory calibration data through an ALSA
control. This is only needed on platforms without UEFI or
some other method of non-volatile storage that the driver
can access directly.
On most platforms this is not needed.
If unsure select "N".
endmenu

View File

@@ -962,8 +962,8 @@ int cs35l56_get_calibration(struct cs35l56_base *cs35l56_base)
}
EXPORT_SYMBOL_NS_GPL(cs35l56_get_calibration, "SND_SOC_CS35L56_SHARED");
static int cs35l56_stash_calibration(struct cs35l56_base *cs35l56_base,
const struct cirrus_amp_cal_data *data)
int cs35l56_stash_calibration(struct cs35l56_base *cs35l56_base,
const struct cirrus_amp_cal_data *data)
{
/* Ignore if it is empty */
@@ -980,6 +980,7 @@ static int cs35l56_stash_calibration(struct cs35l56_base *cs35l56_base,
return 0;
}
EXPORT_SYMBOL_NS_GPL(cs35l56_stash_calibration, "SND_SOC_CS35L56_SHARED");
static int cs35l56_perform_calibration(struct cs35l56_base *cs35l56_base)
{

View File

@@ -1040,6 +1040,67 @@ static const struct cs35l56_cal_debugfs_fops cs35l56_cal_debugfs_fops = {
},
};
static int cs35l56_cal_data_rb_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);
if (!cs35l56->base.cal_data_valid)
return -ENODATA;
memcpy(ucontrol->value.bytes.data, &cs35l56->base.cal_data,
sizeof(cs35l56->base.cal_data));
return 0;
}
static int cs35l56_cal_data_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);
/*
* This control is write-only but mixer libraries often try to read
* a control before writing it. So we have to implement read.
* Return zeros so a write of valid data will always be a change
* from its "current value".
*/
memset(ucontrol->value.bytes.data, 0, sizeof(cs35l56->base.cal_data));
return 0;
}
static int cs35l56_cal_data_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);
const struct cirrus_amp_cal_data *cal_data = (const void *)ucontrol->value.bytes.data;
int ret;
if (cs35l56->base.cal_data_valid)
return -EACCES;
ret = cs35l56_stash_calibration(&cs35l56->base, cal_data);
if (ret)
return ret;
ret = cs35l56_new_cal_data_apply(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),
SND_SOC_BYTES_E("CAL_DATA_RB", 0, sizeof(struct cirrus_amp_cal_data) / sizeof(u32),
cs35l56_cal_data_rb_ctl_get, NULL),
};
static int cs35l56_set_fw_suffix(struct cs35l56_private *cs35l56)
{
if (cs35l56->dsp.fwf_suffix)
@@ -1134,6 +1195,12 @@ static int cs35l56_component_probe(struct snd_soc_component *component)
break;
}
if (!ret && IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_SET_CTRL)) {
ret = snd_soc_add_component_controls(component,
cs35l56_cal_data_restore_controls,
ARRAY_SIZE(cs35l56_cal_data_restore_controls));
}
if (ret)
return dev_err_probe(cs35l56->base.dev, ret, "unable to add controls\n");