Unverified Commit 2b0d5d9b authored by Mark Brown's avatar Mark Brown
Browse files

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.
parents 23523e93 32172cf3
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -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);
+14 −0
Original line number Diff line number Diff line
@@ -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

+51 −2
Original line number Diff line number Diff line
@@ -962,7 +962,7 @@ 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,
int cs35l56_stash_calibration(struct cs35l56_base *cs35l56_base,
			      const struct cirrus_amp_cal_data *data)
{

@@ -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)
{
+83 −0
Original line number Diff line number Diff line
@@ -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");