summaryrefslogtreecommitdiff
path: root/sound/soc
diff options
context:
space:
mode:
authorRichard Fitzgerald <rf@opensource.cirrus.com>2026-02-05 16:48:36 +0000
committerMark Brown <broonie@kernel.org>2026-02-05 16:58:11 +0000
commit4d1e3e2c404dc30e039d81ba7396c8bb82ade991 (patch)
tree6a76798d2de4f21c4aecc34e0701f6f60d0794ea /sound/soc
parentdc8384d85c034b3c3912ec7fc43784da5b884c27 (diff)
ASoC: cs35l56: Support for reading speaker ID from on-chip GPIOs
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@opensource.cirrus.com> Link: https://patch.msgid.link/20260205164838.1611295-2-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
Diffstat (limited to 'sound/soc')
-rw-r--r--sound/soc/codecs/cs35l56-shared.c167
-rw-r--r--sound/soc/codecs/cs35l56.c141
-rw-r--r--sound/soc/codecs/cs35l56.h2
3 files changed, 306 insertions, 4 deletions
diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c
index 60100c8f8c95..55c75b9e4172 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -6,12 +6,14 @@
// Cirrus Logic International Semiconductor Ltd.
#include <linux/array_size.h>
+#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/firmware/cirrus/wmfw.h>
#include <linux/fs.h>
#include <linux/gpio/consumer.h>
#include <linux/kstrtox.h>
+#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
@@ -182,6 +184,8 @@ static bool cs35l56_readable_reg(struct device *dev, unsigned int reg)
case CS35L56_OTP_MEM_53:
case CS35L56_OTP_MEM_54:
case CS35L56_OTP_MEM_55:
+ case CS35L56_SYNC_GPIO1_CFG ... CS35L56_ASP2_DIO_GPIO13_CFG:
+ case CS35L56_UPDATE_REGS:
case CS35L56_ASP1_ENABLES1:
case CS35L56_ASP1_CONTROL1:
case CS35L56_ASP1_CONTROL2:
@@ -213,6 +217,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_MIXER_NGATE_CH1_CFG:
case CS35L56_MIXER_NGATE_CH2_CFG:
case CS35L56_DSP_VIRTUAL1_MBOX_1:
@@ -262,6 +267,8 @@ static bool cs35l56_common_volatile_reg(unsigned int reg)
case CS35L56_GLOBAL_ENABLES: /* owned by firmware */
case CS35L56_BLOCK_ENABLES: /* owned by firmware */
case CS35L56_BLOCK_ENABLES2: /* owned by firmware */
+ case CS35L56_SYNC_GPIO1_CFG ... CS35L56_ASP2_DIO_GPIO13_CFG:
+ case CS35L56_UPDATE_REGS:
case CS35L56_REFCLK_INPUT: /* owned by firmware */
case CS35L56_GLOBAL_SAMPLE_RATE: /* owned by firmware */
case CS35L56_DACPCM1_INPUT: /* owned by firmware */
@@ -272,6 +279,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_MIXER_NGATE_CH1_CFG:
case CS35L56_MIXER_NGATE_CH2_CFG:
case CS35L56_DSP_VIRTUAL1_MBOX_1:
@@ -1552,6 +1560,165 @@ err:
}
EXPORT_SYMBOL_NS_GPL(cs35l56_get_speaker_id, "SND_SOC_CS35L56_SHARED");
+int cs35l56_check_and_save_onchip_spkid_gpios(struct cs35l56_base *cs35l56_base,
+ const u32 *gpios, int num_gpios,
+ const u32 *pulls, int num_pulls)
+{
+ int max_gpio;
+ int ret = 0;
+ int i;
+
+ if ((num_gpios > ARRAY_SIZE(cs35l56_base->onchip_spkid_gpios)) ||
+ (num_pulls > ARRAY_SIZE(cs35l56_base->onchip_spkid_pulls)))
+ return -EOVERFLOW;
+
+ switch (cs35l56_base->type) {
+ case 0x54:
+ case 0x56:
+ case 0x57:
+ max_gpio = CS35L56_MAX_GPIO;
+ break;
+ default:
+ max_gpio = CS35L63_MAX_GPIO;
+ break;
+ }
+
+ for (i = 0; i < num_gpios; i++) {
+ if (gpios[i] < 1 || gpios[i] > max_gpio) {
+ dev_err(cs35l56_base->dev, "Invalid spkid GPIO %d\n", gpios[i]);
+ /* Keep going so we log all bad values */
+ ret = -EINVAL;
+ }
+
+ /* Change to zero-based */
+ cs35l56_base->onchip_spkid_gpios[i] = gpios[i] - 1;
+ }
+
+ for (i = 0; i < num_pulls; i++) {
+ switch (pulls[i]) {
+ case 0:
+ cs35l56_base->onchip_spkid_pulls[i] = CS35L56_PAD_PULL_NONE;
+ break;
+ case 1:
+ cs35l56_base->onchip_spkid_pulls[i] = CS35L56_PAD_PULL_UP;
+ break;
+ case 2:
+ cs35l56_base->onchip_spkid_pulls[i] = CS35L56_PAD_PULL_DOWN;
+ break;
+ default:
+ dev_err(cs35l56_base->dev, "Invalid spkid pull %d\n", pulls[i]);
+ /* Keep going so we log all bad values */
+ ret = -EINVAL;
+ break;
+ }
+ }
+ if (ret)
+ return ret;
+
+ cs35l56_base->num_onchip_spkid_gpios = num_gpios;
+ cs35l56_base->num_onchip_spkid_pulls = num_pulls;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_check_and_save_onchip_spkid_gpios, "SND_SOC_CS35L56_SHARED");
+
+/* Caller must pm_runtime resume before calling this function */
+int cs35l56_configure_onchip_spkid_pads(struct cs35l56_base *cs35l56_base)
+{
+ struct regmap *regmap = cs35l56_base->regmap;
+ unsigned int addr_offset, val;
+ int num_gpios, num_pulls;
+ int i, ret;
+
+ if (cs35l56_base->num_onchip_spkid_gpios == 0)
+ return 0;
+
+ num_gpios = min(cs35l56_base->num_onchip_spkid_gpios,
+ ARRAY_SIZE(cs35l56_base->onchip_spkid_gpios));
+ num_pulls = min(cs35l56_base->num_onchip_spkid_pulls,
+ ARRAY_SIZE(cs35l56_base->onchip_spkid_pulls));
+
+ for (i = 0; i < num_gpios; i++) {
+ addr_offset = cs35l56_base->onchip_spkid_gpios[i] * sizeof(u32);
+
+ /* Set unspecified pulls to NONE */
+ if (i < num_pulls) {
+ val = FIELD_PREP(CS35L56_PAD_GPIO_PULL_MASK,
+ cs35l56_base->onchip_spkid_pulls[i]);
+ } else {
+ val = FIELD_PREP(CS35L56_PAD_GPIO_PULL_MASK, CS35L56_PAD_PULL_NONE);
+ }
+
+ ret = regmap_update_bits(regmap, CS35L56_SYNC_GPIO1_CFG + addr_offset,
+ CS35L56_PAD_GPIO_PULL_MASK | CS35L56_PAD_GPIO_IE,
+ val | CS35L56_PAD_GPIO_IE);
+ if (ret) {
+ dev_err(cs35l56_base->dev, "GPIO%d set pad fail: %d\n",
+ cs35l56_base->onchip_spkid_gpios[i] + 1, ret);
+ return ret;
+ }
+ }
+
+ ret = regmap_write(regmap, CS35L56_UPDATE_REGS, CS35L56_UPDT_GPIO_PRES);
+ if (ret) {
+ dev_err(cs35l56_base->dev, "UPDT_GPIO_PRES failed:%d\n", ret);
+ return ret;
+ }
+
+ usleep_range(CS35L56_PAD_PULL_SETTLE_US, CS35L56_PAD_PULL_SETTLE_US * 2);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_configure_onchip_spkid_pads, "SND_SOC_CS35L56_SHARED");
+
+/* Caller must pm_runtime resume before calling this function */
+int cs35l56_read_onchip_spkid(struct cs35l56_base *cs35l56_base)
+{
+ struct regmap *regmap = cs35l56_base->regmap;
+ unsigned int addr_offset, val;
+ int num_gpios;
+ int speaker_id = 0;
+ int i, ret;
+
+ if (cs35l56_base->num_onchip_spkid_gpios == 0)
+ return -ENOENT;
+
+ num_gpios = min(cs35l56_base->num_onchip_spkid_gpios,
+ ARRAY_SIZE(cs35l56_base->onchip_spkid_gpios));
+
+ for (i = 0; i < num_gpios; i++) {
+ addr_offset = cs35l56_base->onchip_spkid_gpios[i] * sizeof(u32);
+
+ ret = regmap_update_bits(regmap, CS35L56_GPIO1_CTRL1 + addr_offset,
+ CS35L56_GPIO_DIR_MASK | CS35L56_GPIO_FN_MASK,
+ CS35L56_GPIO_DIR_MASK | CS35L56_GPIO_FN_GPIO);
+ if (ret) {
+ dev_err(cs35l56_base->dev, "GPIO%u set func fail: %d\n",
+ cs35l56_base->onchip_spkid_gpios[i] + 1, ret);
+ return ret;
+ }
+ }
+
+ ret = regmap_read(regmap, CS35L56_GPIO_STATUS1, &val);
+ if (ret) {
+ dev_err(cs35l56_base->dev, "GPIO%d status read failed: %d\n",
+ cs35l56_base->onchip_spkid_gpios[i] + 1, ret);
+ return ret;
+ }
+
+ for (i = 0; i < num_gpios; i++) {
+ speaker_id <<= 1;
+
+ if (val & BIT(cs35l56_base->onchip_spkid_gpios[i]))
+ speaker_id |= 1;
+ }
+
+ dev_dbg(cs35l56_base->dev, "Onchip GPIO Speaker ID = %d\n", speaker_id);
+
+ return speaker_id;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_read_onchip_spkid, "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 31dd2f7b2858..2ff8b172b76e 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -1179,15 +1179,28 @@ VISIBLE_IF_KUNIT int cs35l56_set_fw_suffix(struct cs35l56_private *cs35l56)
}
EXPORT_SYMBOL_IF_KUNIT(cs35l56_set_fw_suffix);
-static int cs35l56_component_probe(struct snd_soc_component *component)
+VISIBLE_IF_KUNIT int cs35l56_set_fw_name(struct snd_soc_component *component)
{
- struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
- struct dentry *debugfs_root = component->debugfs_root;
unsigned short vendor, device;
int ret;
- BUILD_BUG_ON(ARRAY_SIZE(cs35l56_tx_input_texts) != ARRAY_SIZE(cs35l56_tx_input_values));
+ if ((cs35l56->speaker_id < 0) && cs35l56->base.num_onchip_spkid_gpios) {
+ PM_RUNTIME_ACQUIRE(cs35l56->base.dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ ret = cs35l56_configure_onchip_spkid_pads(&cs35l56->base);
+ if (ret)
+ return ret;
+
+ ret = cs35l56_read_onchip_spkid(&cs35l56->base);
+ if (ret < 0)
+ return ret;
+
+ cs35l56->speaker_id = ret;
+ }
if (!cs35l56->dsp.system_name &&
(snd_soc_card_get_pci_ssid(component->card, &vendor, &device) == 0)) {
@@ -1208,6 +1221,19 @@ static int cs35l56_component_probe(struct snd_soc_component *component)
return -ENOMEM;
}
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(cs35l56_set_fw_name);
+
+static int cs35l56_component_probe(struct snd_soc_component *component)
+{
+ struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
+ struct dentry *debugfs_root = component->debugfs_root;
+ int ret;
+
+ BUILD_BUG_ON(ARRAY_SIZE(cs35l56_tx_input_texts) != ARRAY_SIZE(cs35l56_tx_input_values));
+
if (!wait_for_completion_timeout(&cs35l56->init_completion,
msecs_to_jiffies(5000))) {
dev_err(cs35l56->base.dev, "%s: init_completion timed out\n", __func__);
@@ -1219,6 +1245,10 @@ static int cs35l56_component_probe(struct snd_soc_component *component)
return -ENOMEM;
cs35l56->component = component;
+ ret = cs35l56_set_fw_name(component);
+ if (ret)
+ return ret;
+
ret = cs35l56_set_fw_suffix(cs35l56);
if (ret)
return ret;
@@ -1532,6 +1562,105 @@ static int cs35l56_dsp_init(struct cs35l56_private *cs35l56)
return 0;
}
+static int cs35l56_read_fwnode_u32_array(struct device *dev,
+ struct fwnode_handle *parent_node,
+ const char *prop_name,
+ int max_count,
+ u32 *dest)
+{
+ int count, ret;
+
+ count = fwnode_property_count_u32(parent_node, prop_name);
+ if ((count == 0) || (count == -EINVAL) || (count == -ENODATA)) {
+ dev_dbg(dev, "%s not found in %s\n", prop_name, fwnode_get_name(parent_node));
+ return 0;
+ }
+
+ if (count < 0) {
+ dev_err(dev, "Get %s error:%d\n", prop_name, count);
+ return count;
+ }
+
+ if (count > max_count) {
+ dev_err(dev, "%s too many entries (%d)\n", prop_name, count);
+ return -EOVERFLOW;
+ }
+
+ ret = fwnode_property_read_u32_array(parent_node, prop_name, dest, count);
+ if (ret) {
+ dev_err(dev, "Error reading %s: %d\n", prop_name, ret);
+ return ret;
+ }
+
+ return count;
+}
+
+static int cs35l56_process_xu_onchip_speaker_id(struct cs35l56_private *cs35l56,
+ struct fwnode_handle *ext_node)
+{
+ static const char * const gpio_name = "01fa-spk-id-gpios-onchip";
+ static const char * const pull_name = "01fa-spk-id-gpios-onchip-pull";
+ u32 gpios[5], pulls[5];
+ int num_gpios, num_pulls;
+ int ret;
+
+ static_assert(ARRAY_SIZE(gpios) == ARRAY_SIZE(cs35l56->base.onchip_spkid_gpios));
+ static_assert(ARRAY_SIZE(pulls) == ARRAY_SIZE(cs35l56->base.onchip_spkid_pulls));
+
+ num_gpios = cs35l56_read_fwnode_u32_array(cs35l56->base.dev, ext_node, gpio_name,
+ ARRAY_SIZE(gpios), gpios);
+ if (num_gpios < 1)
+ return num_gpios;
+
+ num_pulls = cs35l56_read_fwnode_u32_array(cs35l56->base.dev, ext_node, pull_name,
+ ARRAY_SIZE(pulls), pulls);
+ if (num_pulls < 0)
+ return num_pulls;
+
+ if (num_pulls != num_gpios) {
+ dev_warn(cs35l56->base.dev, "%s count(%d) != %s count(%d)\n",
+ pull_name, num_pulls, gpio_name, num_gpios);
+ }
+
+ ret = cs35l56_check_and_save_onchip_spkid_gpios(&cs35l56->base,
+ gpios, num_gpios,
+ pulls, num_pulls);
+ if (ret) {
+ return dev_err_probe(cs35l56->base.dev, ret, "Error in %s/%s\n",
+ gpio_name, pull_name);
+ }
+
+ return 0;
+}
+
+VISIBLE_IF_KUNIT int cs35l56_process_xu_properties(struct cs35l56_private *cs35l56)
+{
+ struct fwnode_handle *ext_node = NULL;
+ struct fwnode_handle *link;
+ int ret;
+
+ if (!cs35l56->sdw_peripheral)
+ return 0;
+
+ 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 (ext_node) {
+ fwnode_handle_put(link);
+ break;
+ }
+ }
+
+ if (!ext_node)
+ return 0;
+
+ ret = cs35l56_process_xu_onchip_speaker_id(cs35l56, ext_node);
+ fwnode_handle_put(ext_node);
+
+ return ret;
+}
+EXPORT_SYMBOL_IF_KUNIT(cs35l56_process_xu_properties);
+
static int cs35l56_get_firmware_uid(struct cs35l56_private *cs35l56)
{
struct device *dev = cs35l56->base.dev;
@@ -1712,6 +1841,10 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56)
if (ret != 0)
goto err;
+ ret = cs35l56_process_xu_properties(cs35l56);
+ if (ret)
+ goto err;
+
ret = cs35l56_dsp_init(cs35l56);
if (ret < 0) {
dev_err_probe(cs35l56->base.dev, ret, "DSP init failed\n");
diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h
index 7187885a13c1..691f857d0bd8 100644
--- a/sound/soc/codecs/cs35l56.h
+++ b/sound/soc/codecs/cs35l56.h
@@ -76,6 +76,8 @@ void cs35l56_remove(struct cs35l56_private *cs35l56);
#if IS_ENABLED(CONFIG_KUNIT)
int cs35l56_set_fw_suffix(struct cs35l56_private *cs35l56);
+int cs35l56_set_fw_name(struct snd_soc_component *component);
+int cs35l56_process_xu_properties(struct cs35l56_private *cs35l56);
#endif
#endif /* ifndef CS35L56_H */