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 <rf@opensource.cirrus.com>
Reviewed-by: Takashi Iwai <tiwai@suse.de>
Link: https://patch.msgid.link/20251021105022.1013685-5-rf@opensource.cirrus.com
Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
Richard Fitzgerald 2025-10-21 11:50:15 +01:00 committed by Mark Brown
parent f7097161e9
commit 191a27faf5
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
3 changed files with 180 additions and 0 deletions

View File

@ -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

View File

@ -10,6 +10,7 @@
#include <linux/completion.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
@ -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 @@ static void cs35l56_dsp_work(struct work_struct *work)
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);

View File

@ -10,6 +10,7 @@
#define CS35L56_H
#include <linux/completion.h>
#include <linux/container_of.h>
#include <linux/regulator/consumer.h>
#include <linux/pm_runtime.h>
#include <linux/workqueue.h>
@ -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);