From 191a27faf53edf9e9101901e402bfee49c44073c Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 21 Oct 2025 11:50:15 +0100 Subject: [PATCH] ASoC: cs35l56: Create debugfs files for factory calibration Create debugfs files that can be used to perform factory calibration. During manufacture, the production line must perform a factory calibration of the amps. This patch adds this functionality via debugfs files. As this is only needed during manufacture, there is no need for this to be available in a normal system so a Kconfig item has been added to enable this. The new Kconfig option is inside a sub-menu because items do not group and indent if the parent is invisible or there are multiple parent dependencies. Anyway the sub-menu reduces the clutter. Signed-off-by: Richard Fitzgerald Reviewed-by: Takashi Iwai Link: https://patch.msgid.link/20251021105022.1013685-5-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 15 ++++ sound/soc/codecs/cs35l56.c | 159 +++++++++++++++++++++++++++++++++++++ sound/soc/codecs/cs35l56.h | 6 ++ 3 files changed, 180 insertions(+) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 6bb24325c2d08..1e649da53ed95 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -899,6 +899,21 @@ config SND_SOC_CS35L56_SDW config SND_SOC_CS35L56_CAL_DEBUGFS_COMMON bool +menu "CS35L56 driver options" + depends on SND_SOC_CS35L56 + +config SND_SOC_CS35L56_CAL_DEBUGFS + bool "CS35L56 create debugfs for factory calibration" + default N + depends on DEBUG_FS + select SND_SOC_CS35L56_CAL_DEBUGFS_COMMON + help + Create debugfs entries used during factory-line manufacture + for factory calibration. + + If unsure select "N". +endmenu + config SND_SOC_CS40L50 tristate "Cirrus Logic CS40L50 CODEC" depends on MFD_CS40L50_CORE diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index 2c1edbd636efc..091a723255078 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -250,6 +251,8 @@ static const struct snd_soc_dapm_widget cs35l56_dapm_widgets[] = { SND_SOC_DAPM_SIGGEN("VDDBMON ADC"), SND_SOC_DAPM_SIGGEN("VBSTMON ADC"), SND_SOC_DAPM_SIGGEN("TEMPMON ADC"), + + SND_SOC_DAPM_INPUT("Calibrate"), }; #define CS35L56_SRC_ROUTE(name) \ @@ -286,6 +289,7 @@ static const struct snd_soc_dapm_route cs35l56_audio_map[] = { { "DSP1", NULL, "ASP1RX1" }, { "DSP1", NULL, "ASP1RX2" }, { "DSP1", NULL, "SDW1 Playback" }, + { "DSP1", NULL, "Calibrate" }, { "AMP", NULL, "DSP1" }, { "SPK", NULL, "AMP" }, @@ -874,6 +878,152 @@ err: pm_runtime_put_autosuspend(cs35l56->base.dev); } +static struct snd_soc_dapm_context *cs35l56_power_up_for_cal(struct cs35l56_private *cs35l56) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cs35l56->component); + int ret; + + ret = snd_soc_component_enable_pin(cs35l56->component, "Calibrate"); + if (ret) + return ERR_PTR(ret); + + snd_soc_dapm_sync(dapm); + + return dapm; +} + +static void cs35l56_power_down_after_cal(struct cs35l56_private *cs35l56) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cs35l56->component); + + snd_soc_component_disable_pin(cs35l56->component, "Calibrate"); + snd_soc_dapm_sync(dapm); +} + +static ssize_t cs35l56_debugfs_calibrate_write(struct file *file, + const char __user *from, + size_t count, loff_t *ppos) +{ + struct cs35l56_base *cs35l56_base = file->private_data; + struct cs35l56_private *cs35l56 = cs35l56_private_from_base(cs35l56_base); + struct snd_soc_dapm_context *dapm; + ssize_t ret; + + dapm = cs35l56_power_up_for_cal(cs35l56); + if (IS_ERR(dapm)) + return PTR_ERR(dapm); + + snd_soc_dapm_mutex_lock(dapm); + ret = cs35l56_calibrate_debugfs_write(&cs35l56->base, from, count, ppos); + snd_soc_dapm_mutex_unlock(dapm); + + cs35l56_power_down_after_cal(cs35l56); + + return ret; +} + +static ssize_t cs35l56_debugfs_cal_temperature_write(struct file *file, + const char __user *from, + size_t count, loff_t *ppos) +{ + struct cs35l56_base *cs35l56_base = file->private_data; + struct cs35l56_private *cs35l56 = cs35l56_private_from_base(cs35l56_base); + struct snd_soc_dapm_context *dapm; + ssize_t ret; + + dapm = cs35l56_power_up_for_cal(cs35l56); + if (IS_ERR(dapm)) + return PTR_ERR(dapm); + + ret = cs35l56_cal_ambient_debugfs_write(&cs35l56->base, from, count, ppos); + cs35l56_power_down_after_cal(cs35l56); + + return ret; +} + +static ssize_t cs35l56_debugfs_cal_data_read(struct file *file, + char __user *to, + size_t count, loff_t *ppos) +{ + struct cs35l56_base *cs35l56_base = file->private_data; + struct cs35l56_private *cs35l56 = cs35l56_private_from_base(cs35l56_base); + struct snd_soc_dapm_context *dapm; + ssize_t ret; + + dapm = cs35l56_power_up_for_cal(cs35l56); + if (IS_ERR(dapm)) + return PTR_ERR(dapm); + + ret = cs35l56_cal_data_debugfs_read(&cs35l56->base, to, count, ppos); + cs35l56_power_down_after_cal(cs35l56); + + return ret; +} + +static int cs35l56_new_cal_data_apply(struct cs35l56_private *cs35l56) +{ + struct snd_soc_dapm_context *dapm; + int ret; + + if (!cs35l56->base.cal_data_valid) + return -ENXIO; + + if (cs35l56->base.secured) + return -EACCES; + + dapm = cs35l56_power_up_for_cal(cs35l56); + if (IS_ERR(dapm)) + return PTR_ERR(dapm); + + snd_soc_dapm_mutex_lock(dapm); + ret = cs_amp_write_cal_coeffs(&cs35l56->dsp.cs_dsp, + cs35l56->base.calibration_controls, + &cs35l56->base.cal_data); + if (ret == 0) + cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT); + else + ret = -EIO; + + snd_soc_dapm_mutex_unlock(dapm); + cs35l56_power_down_after_cal(cs35l56); + + return ret; +} + +static ssize_t cs35l56_debugfs_cal_data_write(struct file *file, + const char __user *from, + size_t count, loff_t *ppos) +{ + struct cs35l56_base *cs35l56_base = file->private_data; + struct cs35l56_private *cs35l56 = cs35l56_private_from_base(cs35l56_base); + int ret; + + ret = cs35l56_cal_data_debugfs_write(&cs35l56->base, from, count, ppos); + if (ret == -ENODATA) + return count; /* Ignore writes of empty cal blobs */ + else if (ret < 0) + return -EIO; + + ret = cs35l56_new_cal_data_apply(cs35l56); + if (ret) + return ret; + + return count; +} + +static const struct cs35l56_cal_debugfs_fops cs35l56_cal_debugfs_fops = { + .calibrate = { + .write = cs35l56_debugfs_calibrate_write, + }, + .cal_temperature = { + .write = cs35l56_debugfs_cal_temperature_write, + }, + .cal_data = { + .read = cs35l56_debugfs_cal_data_read, + .write = cs35l56_debugfs_cal_data_write, + }, +}; + static int cs35l56_set_fw_suffix(struct cs35l56_private *cs35l56) { if (cs35l56->dsp.fwf_suffix) @@ -971,6 +1121,13 @@ static int cs35l56_component_probe(struct snd_soc_component *component) if (ret) return dev_err_probe(cs35l56->base.dev, ret, "unable to add controls\n"); + ret = snd_soc_component_disable_pin(component, "Calibrate"); + if (ret) + return ret; + + if (IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_DEBUGFS)) + cs35l56_create_cal_debugfs(&cs35l56->base, &cs35l56_cal_debugfs_fops); + queue_work(cs35l56->dsp_wq, &cs35l56->dsp_work); return 0; @@ -982,6 +1139,8 @@ static void cs35l56_component_remove(struct snd_soc_component *component) cancel_work_sync(&cs35l56->dsp_work); + cs35l56_remove_cal_debugfs(&cs35l56->base); + if (cs35l56->dsp.cs_dsp.booted) wm_adsp_power_down(&cs35l56->dsp); diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h index 40a1800a45851..4c59f92f32067 100644 --- a/sound/soc/codecs/cs35l56.h +++ b/sound/soc/codecs/cs35l56.h @@ -10,6 +10,7 @@ #define CS35L56_H #include +#include #include #include #include @@ -54,6 +55,11 @@ struct cs35l56_private { u8 sdw_unique_id; }; +static inline struct cs35l56_private *cs35l56_private_from_base(struct cs35l56_base *cs35l56_base) +{ + return container_of(cs35l56_base, struct cs35l56_private, base); +} + extern const struct dev_pm_ops cs35l56_pm_ops_i2c_spi; int cs35l56_system_suspend(struct device *dev); -- 2.47.3