summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-03-26 13:20:46 +0300
committerMark Brown <broonie@kernel.org>2026-03-26 13:20:46 +0300
commit7b907b55eb180f89b5ce9d66ed230892aef30e33 (patch)
treea63808684c2e5656bdf6cffdb4fadc114a630e75
parent8a6391ec669366cbe7bde92b468c561e8b309fd6 (diff)
parentee7d655dbaf5e57145c73fd3925b5f44f7a1a5cc (diff)
downloadlinux-7b907b55eb180f89b5ce9d66ed230892aef30e33.tar.xz
ASoC: cs35l56: Support for factory calibration through ALSA controls
Richard Fitzgerald <rf@opensource.cirrus.com> says: Factory calibration is normally done through debugfs files. Google have requested that factory calibration can be performed by repair shops. These repair shops only have access to the standard "user" kernel, which does not include debugfs. Patch #1 adds a new control definition macro to create a boolean control with specified access permissions. (new in V2) Patch #2 is the implementation in the cs35l56 driver.
-rw-r--r--include/sound/cs35l56.h1
-rw-r--r--include/sound/soc.h6
-rw-r--r--sound/soc/codecs/Kconfig13
-rw-r--r--sound/soc/codecs/cs35l56-shared.c9
-rw-r--r--sound/soc/codecs/cs35l56.c96
-rw-r--r--sound/soc/codecs/cs35l56.h2
6 files changed, 127 insertions, 0 deletions
diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h
index 7ca25487030a..c3b10587cb4c 100644
--- a/include/sound/cs35l56.h
+++ b/include/sound/cs35l56.h
@@ -435,6 +435,7 @@ ssize_t cs35l56_cal_data_debugfs_read(struct cs35l56_base *cs35l56_base,
ssize_t cs35l56_cal_data_debugfs_write(struct cs35l56_base *cs35l56_base,
const char __user *from, size_t count,
loff_t *ppos);
+int cs35l56_factory_calibrate(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);
diff --git a/include/sound/soc.h b/include/sound/soc.h
index a30f95ff7d86..fd6c1c8055d2 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -311,6 +311,12 @@ struct platform_device;
.info = snd_soc_info_bool_ext, \
.get = xhandler_get, .put = xhandler_put, \
.private_value = xdata }
+#define SOC_SINGLE_BOOL_EXT_ACC(xname, xdata, xhandler_get, xhandler_put, xaccess) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .access = xaccess, \
+ .info = snd_soc_info_bool_ext, \
+ .get = xhandler_get, .put = xhandler_put, \
+ .private_value = xdata }
#define SOC_ENUM_EXT(xname, xenum, xhandler_get, xhandler_put) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_enum_double, \
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index d6104796db4f..ca3e47db126e 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -921,6 +921,19 @@ config SND_SOC_CS35L56_CAL_SET_CTRL
If unsure select "N".
+config SND_SOC_CS35L56_CAL_PERFORM_CTRL
+ bool "CS35L56 ALSA control to perform factory calibration"
+ default N
+ select SND_SOC_CS35L56_CAL_DEBUGFS_COMMON
+ help
+ Allow performing factory calibration data through an ALSA
+ control. It is recommended to use the debugfs method instead
+ because debugfs has restricted access permissions.
+
+ On most platforms this is not needed.
+
+ If unsure select "N".
+
config SND_SOC_CS35L56_TEST
tristate "KUnit test for Cirrus Logic cs35l56 driver" if !KUNIT_ALL_TESTS
depends on SND_SOC_CS35L56 && KUNIT
diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c
index af87ebae98cb..e05d975ba794 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -1185,6 +1185,15 @@ ssize_t cs35l56_calibrate_debugfs_write(struct cs35l56_base *cs35l56_base,
}
EXPORT_SYMBOL_NS_GPL(cs35l56_calibrate_debugfs_write, "SND_SOC_CS35L56_SHARED");
+int cs35l56_factory_calibrate(struct cs35l56_base *cs35l56_base)
+{
+ if (!IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_PERFORM_CTRL))
+ return -ENXIO;
+
+ return cs35l56_perform_calibration(cs35l56_base);
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_factory_calibrate, "SND_SOC_CS35L56_SHARED");
+
ssize_t cs35l56_cal_ambient_debugfs_write(struct cs35l56_base *cs35l56_base,
const char __user *from, size_t count,
loff_t *ppos)
diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index 9d35797e000a..378017fcea10 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -1109,6 +1109,88 @@ static int cs35l56_cal_data_ctl_set(struct snd_kcontrol *kcontrol,
return 1;
}
+static int cs35l56_cal_ambient_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);
+
+ ucontrol->value.integer.value[0] = cs35l56->ambient_ctl_value;
+
+ return 0;
+}
+
+static int cs35l56_cal_ambient_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);
+ struct snd_soc_dapm_context *dapm;
+ int temperature = ucontrol->value.integer.value[0];
+ int ret;
+
+ if (temperature == cs35l56->ambient_ctl_value)
+ return 0;
+
+ if ((temperature < 0) || (temperature > 40))
+ return -EINVAL;
+
+ dapm = cs35l56_power_up_for_cal(cs35l56);
+ if (IS_ERR(dapm))
+ return PTR_ERR(dapm);
+
+ ret = cs_amp_write_ambient_temp(&cs35l56->dsp.cs_dsp,
+ cs35l56->base.calibration_controls,
+ temperature);
+ cs35l56_power_down_after_cal(cs35l56);
+
+ if (ret)
+ return ret;
+
+ cs35l56->ambient_ctl_value = temperature;
+
+ return 1;
+}
+
+static int cs35l56_calibrate_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ /*
+ * Allow reading because of user-side libraries that assume all
+ * controls are readable. But always return false to prevent dumb
+ * save-restore tools like alsactl accidentically triggering a
+ * factory calibration when they restore.
+ */
+ ucontrol->value.integer.value[0] = 0;
+
+ return 0;
+}
+
+static int cs35l56_calibrate_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);
+ struct snd_soc_dapm_context *dapm;
+ int ret;
+
+ if (ucontrol->value.integer.value[0] == 0)
+ return 0;
+
+ dapm = cs35l56_power_up_for_cal(cs35l56);
+ if (IS_ERR(dapm))
+ return PTR_ERR(dapm);
+
+ snd_soc_dapm_mutex_lock(dapm);
+ ret = cs35l56_factory_calibrate(&cs35l56->base);
+ snd_soc_dapm_mutex_unlock(dapm);
+ cs35l56_power_down_after_cal(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),
@@ -1117,6 +1199,14 @@ static const struct snd_kcontrol_new cs35l56_cal_data_restore_controls[] = {
SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE),
};
+static const struct snd_kcontrol_new cs35l56_cal_perform_controls[] = {
+ SOC_SINGLE_EXT("CAL_AMBIENT", SND_SOC_NOPM, 0, 40, 0,
+ cs35l56_cal_ambient_ctl_get, cs35l56_cal_ambient_ctl_set),
+ SOC_SINGLE_BOOL_EXT_ACC("Calibrate Switch", 0,
+ cs35l56_calibrate_ctl_get, cs35l56_calibrate_ctl_set,
+ SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE),
+};
+
VISIBLE_IF_KUNIT int cs35l56_set_fw_suffix(struct cs35l56_private *cs35l56)
{
unsigned short vendor, device;
@@ -1290,6 +1380,12 @@ static int cs35l56_component_probe(struct snd_soc_component *component)
ARRAY_SIZE(cs35l56_cal_data_restore_controls));
}
+ if (!ret && IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_PERFORM_CTRL)) {
+ ret = snd_soc_add_component_controls(component,
+ cs35l56_cal_perform_controls,
+ ARRAY_SIZE(cs35l56_cal_perform_controls));
+ }
+
if (ret)
return dev_err_probe(cs35l56->base.dev, ret, "unable to add controls\n");
diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h
index 36d239d571cd..cd71b23b2a3a 100644
--- a/sound/soc/codecs/cs35l56.h
+++ b/sound/soc/codecs/cs35l56.h
@@ -54,6 +54,8 @@ struct cs35l56_private {
bool sysclk_set;
u8 sdw_link_num;
u8 sdw_unique_id;
+
+ u8 ambient_ctl_value;
};
static inline struct cs35l56_private *cs35l56_private_from_base(struct cs35l56_base *cs35l56_base)