public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: Richard Fitzgerald <rf@opensource.cirrus.com>
To: broonie@kernel.org, tiwai@suse.com
Cc: linux-sound@vger.kernel.org, linux-kernel@vger.kernel.org,
	patches@opensource.cirrus.com
Subject: [PATCH 04/11] ASoC: cs35l56: Create sysfs files for factory calibration
Date: Thu, 16 Oct 2025 11:42:35 +0100	[thread overview]
Message-ID: <20251016104242.157325-5-rf@opensource.cirrus.com> (raw)
In-Reply-To: <20251016104242.157325-1-rf@opensource.cirrus.com>

Create sysfs 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 sysfs files.

Sysfs is used here to restrict access to the factory calibration.
It is only intended to be used during manufacture. It is not something
that a normal user should ever touch. Calibration affects the matching of
the amp hardware to the external speakers. If not done correctly it can
cause the speakers to be under-protected.

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>
---
 sound/soc/codecs/Kconfig   |  15 +++
 sound/soc/codecs/cs35l56.c | 182 +++++++++++++++++++++++++++++++++++++
 2 files changed, 197 insertions(+)

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 5917bf5a72f8..4bb57223ef82 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_SYSFS_COMMON
 	bool
 
+menu "CS35L56 driver options"
+	depends on SND_SOC_CS35L56
+
+config SND_SOC_CS35L56_CAL_SYSFS
+	bool "CS35L56 create sysfs for factory calibration"
+	default N
+	select SND_SOC_CS35L56_CAL_SYSFS_COMMON
+	help
+	  Create sysfs entries used during factory-line manufacture
+	  for factory calibration.
+	  This is not needed for normal use.
+
+	  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 2c1edbd636ef..930991dd4b6f 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -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>
@@ -22,6 +23,7 @@
 #include <linux/regulator/consumer.h>
 #include <linux/slab.h>
 #include <linux/soundwire/sdw.h>
+#include <linux/sysfs.h>
 #include <linux/types.h>
 #include <linux/workqueue.h>
 #include <sound/cs-amp-lib.h>
@@ -250,6 +252,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 +290,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 +879,175 @@ 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 calibrate_store(struct device *dev, struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
+	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_sysfs_store(&cs35l56->base, buf, count);
+	snd_soc_dapm_mutex_unlock(dapm);
+
+	cs35l56_power_down_after_cal(cs35l56);
+
+	return ret;
+}
+
+static ssize_t cal_temperature_store(struct device *dev, struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
+	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_sysfs_store(&cs35l56->base, buf, count);
+	cs35l56_power_down_after_cal(cs35l56);
+
+	return ret;
+}
+
+static ssize_t cal_data_read(struct file *filp, struct kobject *kobj,
+			     const struct bin_attribute *battr, char *buf, loff_t pos,
+			     size_t count)
+{
+	struct cs35l56_private *cs35l56 = dev_get_drvdata(kobj_to_dev(kobj));
+	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_sysfs_read(&cs35l56->base, buf, pos, count);
+	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 cal_data_write(struct file *filp, struct kobject *kobj,
+			      const struct bin_attribute *battr, char *buf, loff_t pos,
+			      size_t count)
+{
+	struct cs35l56_private *cs35l56 = dev_get_drvdata(kobj_to_dev(kobj));
+	int ret;
+
+	ret = cs35l56_cal_data_sysfs_write(&cs35l56->base, buf, pos, count);
+	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 DEVICE_ATTR_WO(calibrate);
+static const DEVICE_ATTR_WO(cal_temperature);
+static const BIN_ATTR_RW(cal_data, sizeof_field(struct cs35l56_base, cal_data));
+
+static const struct attribute *cs35l56_cal_attributes[] = {
+	&dev_attr_calibrate.attr,
+	&dev_attr_cal_temperature.attr,
+	NULL
+};
+
+static void cs35l56_create_calibration_sysfs(struct cs35l56_private *cs35l56)
+{
+	struct device *dev = cs35l56->base.dev;
+	int ret;
+
+	if (!IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_SYSFS))
+		return;
+
+	ret = sysfs_create_files(&dev->kobj, cs35l56_cal_attributes);
+	if (ret)
+		goto err;
+
+	ret = sysfs_create_bin_file(&dev->kobj, &bin_attr_cal_data);
+	if (ret)
+		goto err;
+
+	return;
+err:
+	dev_err_probe(dev, ret, "Failed creating calibration sysfs\n");
+}
+
+static void cs35l56_remove_calibration_sysfs(struct cs35l56_private *cs35l56)
+{
+	struct device *dev = cs35l56->base.dev;
+
+	if (!IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_SYSFS))
+		return;
+
+	sysfs_remove_files(&dev->kobj, cs35l56_cal_attributes);
+	sysfs_remove_bin_file(&dev->kobj, &bin_attr_cal_data);
+}
+
 static int cs35l56_set_fw_suffix(struct cs35l56_private *cs35l56)
 {
 	if (cs35l56->dsp.fwf_suffix)
@@ -971,6 +1145,12 @@ 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;
+
+	cs35l56_create_calibration_sysfs(cs35l56);
+
 	queue_work(cs35l56->dsp_wq, &cs35l56->dsp_work);
 
 	return 0;
@@ -982,6 +1162,8 @@ static void cs35l56_component_remove(struct snd_soc_component *component)
 
 	cancel_work_sync(&cs35l56->dsp_work);
 
+	cs35l56_remove_calibration_sysfs(cs35l56);
+
 	if (cs35l56->dsp.cs_dsp.booted)
 		wm_adsp_power_down(&cs35l56->dsp);
 
-- 
2.47.3


  parent reply	other threads:[~2025-10-16 10:43 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-10-16 10:42 [PATCH 00/11] ALSA: cs35l56: Add support for factory calibration Richard Fitzgerald
2025-10-16 10:42 ` [PATCH 01/11] ASoC: cs35l56: Read silicon ID during initialization and save it Richard Fitzgerald
2025-10-16 10:42 ` [PATCH 02/11] ASoC: cs-amp-lib: Add helpers for factory calibration Richard Fitzgerald
2025-10-16 10:42 ` [PATCH 03/11] ASoC: cs35l56: Add common code " Richard Fitzgerald
2025-10-16 10:42 ` Richard Fitzgerald [this message]
2025-10-16 10:42 ` [PATCH 05/11] ALSA: hda/cs35l56: Create sysfs files " Richard Fitzgerald
2025-10-16 11:01   ` Takashi Iwai
2025-10-16 11:27     ` Mark Brown
2025-10-16 11:58       ` Richard Fitzgerald
2025-10-16 12:13         ` Mark Brown
2025-10-16 12:45           ` Takashi Iwai
2025-10-16 10:42 ` [PATCH 06/11] ASoC: cs-amp-lib-test: Add cases for factory calibration helpers Richard Fitzgerald
2025-10-16 10:42 ` [PATCH 07/11] ASoC: cs-amp-lib: Return attributes from cs_amp_get_efi_variable() Richard Fitzgerald
2025-10-16 10:42 ` [PATCH 08/11] ASoC: cs-amp-lib: Add function to write calibration to EFI Richard Fitzgerald
2025-10-16 10:42 ` [PATCH 09/11] ASoC: cs35l56: Add calibration command to store into UEFI Richard Fitzgerald
2025-10-16 10:42 ` [PATCH 10/11] ALSA: hda/cs35l56: Set cal_index to the amp index Richard Fitzgerald
2025-10-16 10:42 ` [PATCH 11/11] ASoC: cs-amp-lib-test: Add test cases for cs_amp_set_efi_calibration_data() Richard Fitzgerald

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20251016104242.157325-5-rf@opensource.cirrus.com \
    --to=rf@opensource.cirrus.com \
    --cc=broonie@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-sound@vger.kernel.org \
    --cc=patches@opensource.cirrus.com \
    --cc=tiwai@suse.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox