[SRU][R][PATCH 18/18] ASoC: cs35l56: Support for reading speaker ID from on-chip GPIOs

Chris Chiu chris.chiu at canonical.com
Tue Mar 17 05:22:03 UTC 2026


From: Richard Fitzgerald <rf at opensource.cirrus.com>

BugLink: https://bugs.launchpad.net/bugs/2139391

Add support for using the state of pins on the amplifier to indicate
the type of speaker fitted.

Previously, where there were alternate speaker vendors, this was
indicated using host CPU GPIOs.

Some new Dell models use spare pins on the CS35L63 as GPIOs for the
speaker ID detection.

Cirrus-specific SDCA Disco properties provide a list of the pins to be
used, and pull-up/down settings for the pads. This list is ordered,
MSbit to LSbit.

The code to set the firmware filename has been modified to check for
using chip pins for speaker ID. The entire block of code to set
firmware name has been moved out of cs35l56_component_probe() into
its own function to make it easier to KUnit test.

Signed-off-by: Richard Fitzgerald <rf at opensource.cirrus.com>
Link: https://patch.msgid.link/20260205164838.1611295-2-rf@opensource.cirrus.com
Signed-off-by: Mark Brown <broonie at kernel.org>
(backported from commit 4d1e3e2c404dc30e039d81ba7396c8bb82ade991 linux-next)
[ChrisChiu: drop all irrelevant functions which are not for speaker ID reading]
Signed-off-by: Chris Chiu <chris.chiu at canonical.com>
---
 include/sound/cs35l56.h           | 13 +++++
 sound/soc/codecs/cs35l56-shared.c | 93 +++++++++++++++++++++++++++++++
 sound/soc/codecs/cs35l56.c        | 22 ++++++++
 3 files changed, 128 insertions(+)

diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h
index ab044ce2aa8b..1349beb77041 100644
--- a/include/sound/cs35l56.h
+++ b/include/sound/cs35l56.h
@@ -62,6 +62,9 @@
 #define CS35L56_IRQ1_MASK_8				0x000E0AC
 #define CS35L56_IRQ1_MASK_18				0x000E0D4
 #define CS35L56_IRQ1_MASK_20				0x000E0DC
+#define CS35L56_GPIO_STATUS1				0x000F000
+#define CS35L56_GPIO1_CTRL1				0x000F008
+#define CS35L56_GPIO13_CTRL1				0x000F038
 #define CS35L56_DSP_MBOX_1_RAW				0x0011000
 #define CS35L56_DSP_VIRTUAL1_MBOX_1			0x0011020
 #define CS35L56_DSP_VIRTUAL1_MBOX_2			0x0011024
@@ -177,6 +180,12 @@
 /* IRQ1_EINT_8 */
 #define CS35L56_TEMP_ERR_EINT1_MASK			0x80000000
 
+/* GPIOn_CTRL1 */
+#define CS35L56_GPIO_DIR_MASK				0x80000000
+#define CS35L56_GPIO_FN_MASK				0x00000007
+
+#define CS35L56_GPIO_FN_GPIO				0x00000001
+
 /* Mixer input sources */
 #define CS35L56_INPUT_SRC_NONE				0x00
 #define CS35L56_INPUT_SRC_ASP1RX1			0x08
@@ -270,6 +279,8 @@
 #define CS35L56_NUM_BULK_SUPPLIES			3
 #define CS35L56_NUM_DSP_REGIONS				5
 
+#define CS35L56_MAX_GPIO				13
+
 /* Additional margin for SYSTEM_RESET to control port ready on SPI */
 #define CS35L56_SPI_RESET_TO_PORT_READY_US (CS35L56_CONTROL_PORT_READY_US + 2500)
 
@@ -363,6 +374,8 @@ int cs35l56_read_prot_status(struct cs35l56_base *cs35l56_base,
 void cs35l56_log_tuning(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_dsp);
 int cs35l56_hw_init(struct cs35l56_base *cs35l56_base);
 int cs35l56_get_speaker_id(struct cs35l56_base *cs35l56_base);
+int cs35l56_get_onchip_speaker_id(struct cs35l56_base *cs35l56_base,
+				  struct fwnode_handle *ext_node);
 int cs35l56_get_bclk_freq_id(unsigned int freq);
 void cs35l56_fill_supply_names(struct regulator_bulk_data *data);
 
diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c
index 278d9313c48b..0c8b3b01ca94 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -206,6 +206,7 @@ static bool cs35l56_readable_reg(struct device *dev, unsigned int reg)
 	case CS35L56_IRQ1_MASK_8:
 	case CS35L56_IRQ1_MASK_18:
 	case CS35L56_IRQ1_MASK_20:
+	case CS35L56_GPIO_STATUS1 ... CS35L56_GPIO13_CTRL1:
 	case CS35L56_DSP_VIRTUAL1_MBOX_1:
 	case CS35L56_DSP_VIRTUAL1_MBOX_2:
 	case CS35L56_DSP_VIRTUAL1_MBOX_3:
@@ -263,6 +264,7 @@ static bool cs35l56_common_volatile_reg(unsigned int reg)
 	case CS35L56_IRQ1_EINT_1 ... CS35L56_IRQ1_EINT_8:
 	case CS35L56_IRQ1_EINT_18:
 	case CS35L56_IRQ1_EINT_20:
+	case CS35L56_GPIO_STATUS1 ... CS35L56_GPIO13_CTRL1:
 	case CS35L56_DSP_VIRTUAL1_MBOX_1:
 	case CS35L56_DSP_VIRTUAL1_MBOX_2:
 	case CS35L56_DSP_VIRTUAL1_MBOX_3:
@@ -1157,6 +1159,97 @@ int cs35l56_get_speaker_id(struct cs35l56_base *cs35l56_base)
 }
 EXPORT_SYMBOL_NS_GPL(cs35l56_get_speaker_id, "SND_SOC_CS35L56_SHARED");
 
+int cs35l56_get_onchip_speaker_id(struct cs35l56_base *cs35l56_base,
+				  struct fwnode_handle *ext_node)
+{
+	static const char * const node_name = "01fa-spk-id-gpios-onchip";
+	struct regmap *regmap = cs35l56_base->regmap;
+	unsigned int saved_ctrl[8];
+	u32 gpios[8];
+	int num_gpios;
+	unsigned int addr, status;
+	int speaker_id = 0;
+	int i, ret;
+
+	num_gpios = fwnode_property_count_u32(ext_node, node_name);
+	if (num_gpios < 1)
+		return -ENOENT;
+
+	if (num_gpios > ARRAY_SIZE(gpios)) {
+		dev_err(cs35l56_base->dev, "%s has too many entries (%d)\n", node_name, num_gpios);
+		return -EOVERFLOW;
+	}
+
+	ret = fwnode_property_read_u32_array(ext_node, node_name, gpios, num_gpios);
+	if (ret) {
+		dev_err(cs35l56_base->dev, "Error reading %s: %d\n", node_name, ret);
+		return ret;
+	}
+
+	ret = 0;
+	for (i = 0; i < num_gpios; i++) {
+		if (gpios[i] < 1 || gpios[i] > CS35L56_MAX_GPIO) {
+			dev_err(cs35l56_base->dev, "Bad GPIO#%d in %s\n", gpios[i], node_name);
+			ret = -EINVAL;
+		}
+
+		/* Change to zero-based */
+		gpios[i]--;
+	}
+	if (ret)
+		return ret;
+
+	for (i = 0; i < num_gpios; i++) {
+		addr = CS35L56_GPIO1_CTRL1 + (gpios[i] * sizeof(u32));
+		ret = regmap_read(regmap, addr, &saved_ctrl[i]);
+		if (ret) {
+			dev_err(cs35l56_base->dev, "GPIO%d read failed: %d\n", gpios[i] + 1, ret);
+			return ret;
+		}
+	}
+
+	for (i = 0; i < num_gpios; i++) {
+		addr = CS35L56_GPIO1_CTRL1 + (gpios[i] * sizeof(u32));
+		ret = regmap_update_bits(regmap, addr,
+					 CS35L56_GPIO_DIR_MASK | CS35L56_GPIO_FN_MASK,
+					 CS35L56_GPIO_DIR_MASK | CS35L56_GPIO_FN_GPIO);
+		if (ret) {
+			dev_err(cs35l56_base->dev, "GPIO%d func failed: %d\n", gpios[i] + 1, ret);
+			goto restore;
+		}
+	}
+
+	ret = regmap_read(regmap, CS35L56_GPIO_STATUS1, &status);
+	if (ret) {
+		dev_err(cs35l56_base->dev, "GPIO%d status read failed: %d\n", gpios[i] + 1, ret);
+		goto restore;
+	}
+
+	for (i = 0; i < num_gpios; i++) {
+		speaker_id <<= 1;
+
+		if (status & BIT(gpios[i]))
+			speaker_id |= 1;
+	}
+
+	ret = 0;
+
+restore:
+	for (i = 0; i < num_gpios; i++) {
+		addr = CS35L56_GPIO1_CTRL1 + (gpios[i] * sizeof(u32));
+		if (regmap_write(regmap, addr, saved_ctrl[i]))
+			dev_warn(cs35l56_base->dev, "GPIO%d restore failed\n", gpios[i] + 1);
+	}
+
+	if (ret < 0)
+		return ret;
+
+	dev_dbg(cs35l56_base->dev, "Onchip GPIO Speaker ID = %d\n", speaker_id);
+
+	return speaker_id;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_get_onchip_speaker_id, "SND_SOC_CS35L56_SHARED");
+
 static const u32 cs35l56_bclk_valid_for_pll_freq_table[] = {
 	[0x0C] = 128000,
 	[0x0F] = 256000,
diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index 310ce1df37a3..278f627a980f 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -1279,6 +1279,26 @@ static int cs35l56_dsp_init(struct cs35l56_private *cs35l56)
 	return 0;
 }
 
+static void cs35l56_process_xu_properties(struct cs35l56_private *cs35l56)
+{
+	struct fwnode_handle *link;
+	struct fwnode_handle *ext_node;
+
+	if (!cs35l56->sdw_peripheral)
+		return;
+
+	fwnode_for_each_child_node(dev_fwnode(cs35l56->base.dev), link) {
+		ext_node = fwnode_get_named_child_node(link,
+						"mipi-sdca-function-expansion-subproperties");
+		if (IS_ERR_OR_NULL(ext_node))
+			continue;
+
+		cs35l56->speaker_id = cs35l56_get_onchip_speaker_id(&cs35l56->base, ext_node);
+		fwnode_handle_put(ext_node);
+		break;
+	}
+}
+
 static int cs35l56_get_firmware_uid(struct cs35l56_private *cs35l56)
 {
 	struct device *dev = cs35l56->base.dev;
@@ -1514,6 +1534,8 @@ int cs35l56_init(struct cs35l56_private *cs35l56)
 	if (ret)
 		return ret;
 
+	cs35l56_process_xu_properties(cs35l56);
+
 	if (!cs35l56->base.reset_gpio) {
 		dev_dbg(cs35l56->base.dev, "No reset gpio: using soft reset\n");
 		cs35l56->soft_resetting = true;
-- 
2.43.0




More information about the kernel-team mailing list