diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h index bd13958bf19d..883f6a7e50aa 100644 --- a/include/sound/cs35l56.h +++ b/include/sound/cs35l56.h @@ -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); diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 6da2fff9323c..433af9bc7564 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -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 diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c index 4fba4127c40c..7424e1353062 100644 --- a/sound/soc/codecs/cs35l56-shared.c +++ b/sound/soc/codecs/cs35l56-shared.c @@ -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) { diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index e1eb7360b058..6feef971024b 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -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");