mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
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:
committed by
Mark Brown
parent
69f3474a01
commit
32172cf3cb
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user