]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ASoC: cs35l56: Allow restoring factory calibration through ALSA control
authorRichard Fitzgerald <rf@opensource.cirrus.com>
Tue, 11 Nov 2025 13:08:50 +0000 (13:08 +0000)
committerMark Brown <broonie@kernel.org>
Tue, 11 Nov 2025 13:52:49 +0000 (13:52 +0000)
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>
include/sound/cs35l56.h
sound/soc/codecs/Kconfig
sound/soc/codecs/cs35l56-shared.c
sound/soc/codecs/cs35l56.c

index bd13958bf19db184ee4d8519f75f501026fd2fcc..883f6a7e50aabb33154c10a651d4cb94f755e417 100644 (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);
index 6da2fff9323c25afadce2e8f90c5ce29e565fe5d..433af9bc7564ad68b35ab161b553818f9aebff6b 100644 (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
 
index 4fba4127c40c6886b5c12137fe38c908c37c0adb..7424e13530623029a131e3e45334da6f13bf3641 100644 (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)
 {
index e1eb7360b05846b33b3253e5fa502fd1b9fe692c..6feef971024bcc52cd7e2bcdad741f2c41a5c0cc 100644 (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");