ASoC: cs35l56: Support for restoring calibration on

Merge series from Richard Fitzgerald <rf@opensource.cirrus.com>:

These two patches add ALSA controls to support restoring factory calibration
during OS boot on ChromeOS.

ChromeOS applies calibration during boot using a process that has restricted
access permissions. This process needs ALSA controls for all settings that
it must restore.
This commit is contained in:
Mark Brown 2025-11-11 18:46:59 +00:00
commit 2b0d5d9b39
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
4 changed files with 159 additions and 2 deletions

View File

@ -16,6 +16,8 @@
#include <linux/spi/spi.h>
#include <sound/cs-amp-lib.h>
struct snd_ctl_elem_value;
#define CS35L56_DEVID 0x0000000
#define CS35L56_REVID 0x0000004
#define CS35L56_RELID 0x000000C
@ -268,6 +270,10 @@
#define CS35L56_CAL_STATUS_SUCCESS 1
#define CS35L56_CAL_STATUS_OUT_OF_RANGE 3
#define CS35L56_CAL_SET_STATUS_UNKNOWN 0
#define CS35L56_CAL_SET_STATUS_DEFAULT 1
#define CS35L56_CAL_SET_STATUS_SET 2
#define CS35L56_CONTROL_PORT_READY_US 2200
#define CS35L56_HALO_STATE_POLL_US 1000
#define CS35L56_HALO_STATE_TIMEOUT_US 250000
@ -363,6 +369,7 @@ extern const struct regmap_config cs35l63_regmap_i2c;
extern const struct regmap_config cs35l63_regmap_sdw;
extern const struct cirrus_amp_cal_controls cs35l56_calibration_controls;
extern const char * const cs35l56_cal_set_status_text[3];
extern const char * const cs35l56_tx_input_texts[CS35L56_NUM_INPUT_SRC];
extern const unsigned int cs35l56_tx_input_values[CS35L56_NUM_INPUT_SRC];
@ -381,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);
@ -396,6 +405,8 @@ ssize_t cs35l56_cal_data_debugfs_write(struct cs35l56_base *cs35l56_base,
void cs35l56_create_cal_debugfs(struct cs35l56_base *cs35l56_base,
const struct cs35l56_cal_debugfs_fops *fops);
void cs35l56_remove_cal_debugfs(struct cs35l56_base *cs35l56_base);
int cs35l56_cal_set_status_get(struct cs35l56_base *cs35l56_base,
struct snd_ctl_elem_value *uvalue);
int cs35l56_read_prot_status(struct cs35l56_base *cs35l56_base,
bool *fw_missing, unsigned int *fw_version);
void cs35l56_log_tuning(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_dsp);

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)
{
@ -1262,6 +1263,54 @@ void cs35l56_remove_cal_debugfs(struct cs35l56_base *cs35l56_base)
}
EXPORT_SYMBOL_NS_GPL(cs35l56_remove_cal_debugfs, "SND_SOC_CS35L56_SHARED");
const char * const cs35l56_cal_set_status_text[] = {
"Unknown", "Default", "Set",
};
EXPORT_SYMBOL_NS_GPL(cs35l56_cal_set_status_text, "SND_SOC_CS35L56_SHARED");
int cs35l56_cal_set_status_get(struct cs35l56_base *cs35l56_base,
struct snd_ctl_elem_value *uvalue)
{
struct cs_dsp *dsp = cs35l56_base->dsp;
__be32 cal_set_status_be;
int alg_id;
int ret;
switch (cs35l56_base->type) {
case 0x54:
case 0x56:
case 0x57:
alg_id = 0x9f210;
break;
default:
alg_id = 0xbf210;
break;
}
scoped_guard(mutex, &dsp->pwr_lock) {
ret = cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(dsp,
"CAL_SET_STATUS",
WMFW_ADSP2_YM, alg_id),
0, &cal_set_status_be,
sizeof(cal_set_status_be));
}
if (ret) {
uvalue->value.enumerated.item[0] = CS35L56_CAL_SET_STATUS_UNKNOWN;
return 0;
}
switch (be32_to_cpu(cal_set_status_be)) {
case CS35L56_CAL_SET_STATUS_DEFAULT:
case CS35L56_CAL_SET_STATUS_SET:
uvalue->value.enumerated.item[0] = be32_to_cpu(cal_set_status_be);
return 0;
default:
uvalue->value.enumerated.item[0] = CS35L56_CAL_SET_STATUS_UNKNOWN;
return 0;
}
}
EXPORT_SYMBOL_NS_GPL(cs35l56_cal_set_status_get, "SND_SOC_CS35L56_SHARED");
int cs35l56_read_prot_status(struct cs35l56_base *cs35l56_base,
bool *fw_missing, unsigned int *fw_version)
{

View File

@ -66,6 +66,18 @@ static int cs35l56_dspwait_put_volsw(struct snd_kcontrol *kcontrol,
static DECLARE_TLV_DB_SCALE(vol_tlv, -10000, 25, 0);
static SOC_ENUM_SINGLE_DECL(cs35l56_cal_set_status_enum, SND_SOC_NOPM, 0,
cs35l56_cal_set_status_text);
static int cs35l56_cal_set_status_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);
return cs35l56_cal_set_status_get(&cs35l56->base, ucontrol);
}
static const struct snd_kcontrol_new cs35l56_controls[] = {
SOC_SINGLE_EXT("Speaker Switch",
CS35L56_MAIN_RENDER_USER_MUTE, 0, 1, 1,
@ -83,6 +95,8 @@ static const struct snd_kcontrol_new cs35l56_controls[] = {
SOC_SINGLE_EXT("Posture Number", CS35L56_MAIN_POSTURE_NUMBER,
0, 255, 0,
cs35l56_dspwait_get_volsw, cs35l56_dspwait_put_volsw),
SOC_ENUM_EXT("CAL_SET_STATUS", cs35l56_cal_set_status_enum,
cs35l56_cal_set_status_ctl_get, NULL),
};
static const struct snd_kcontrol_new cs35l63_controls[] = {
@ -102,6 +116,8 @@ static const struct snd_kcontrol_new cs35l63_controls[] = {
SOC_SINGLE_EXT("Posture Number", CS35L63_MAIN_POSTURE_NUMBER,
0, 255, 0,
cs35l56_dspwait_get_volsw, cs35l56_dspwait_put_volsw),
SOC_ENUM_EXT("CAL_SET_STATUS", cs35l56_cal_set_status_enum,
cs35l56_cal_set_status_ctl_get, NULL),
};
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_asp1tx1_enum,
@ -1024,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)
@ -1118,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");