All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems
@ 2023-05-25 15:06 Richard Fitzgerald
  2023-05-25 15:06 ` [PATCH 01/13] ASoC: cs35l56: Move shared data into a common data structure Richard Fitzgerald
                   ` (13 more replies)
  0 siblings, 14 replies; 20+ messages in thread
From: Richard Fitzgerald @ 2023-05-25 15:06 UTC (permalink / raw)
  To: tiwai, broonie, perex
  Cc: alsa-devel, linux-kernel, patches, Richard Fitzgerald

This set of patches adds support for using the CS35L56 boosted smart
amplifier on HDA systems. In these systems the CS35L56 audio is
routed through a HDA-to-I2S bridge codec.

This doesn't include the changes to the Realtek driver to actually hook
up the CS35L56 driver, because we don't yet have the QUIRK IDs to
associate it with. But we want to publish the driver now so that it is
available for bringing up hardware with the CS35L56.

The first 10 patches are moving code out of the ASoC driver and into the
shared library so that it can be shared with the HDA driver.

Patch #11 fixes missing #includes in the HDA headers so that the CS35L56
driver doesn't have to #include headers that it doesn't use.

Finally, #12 and #13 actually add the support for CS35L56 on HDA.

Richard Fitzgerald (5):
  ASoC: cs35l56: Move runtime suspend/resume to shared library
  ASoC: cs35l56: Move cs_dsp init into shared library
  ASoC: cs35l56: Move part of cs35l56_init() to shared library
  ASoC: cs35l56: Pass correct pointer to cs35l56_irq()
  ALSA: hda: Fix missing header dependencies

Simon Trimmer (8):
  ASoC: cs35l56: Move shared data into a common data structure
  ASoC: cs35l56: Make cs35l56_system_reset() code more generic
  ASoC: cs35l56: Convert utility functions to use common data structure
  ASoC: cs35l56: Move utility functions to shared file
  ASoC: cs35l56: Make common function for control port wait
  ASoC: cs35l56: Make a common function to shutdown the DSP
  ALSA: hda: Add mute_hook to hda_component
  ALSA: hda/cs35l56: Add driver for Cirrus Logic CS35L56 amplifier

 include/sound/cs35l56.h           |  29 +-
 sound/pci/hda/Kconfig             |  31 +
 sound/pci/hda/Makefile            |   6 +
 sound/pci/hda/cs35l56_hda.c       | 995 ++++++++++++++++++++++++++++++
 sound/pci/hda/cs35l56_hda.h       |  48 ++
 sound/pci/hda/cs35l56_hda_i2c.c   |  69 +++
 sound/pci/hda/cs35l56_hda_spi.c   |  68 ++
 sound/pci/hda/hda_auto_parser.h   |   2 +
 sound/pci/hda/hda_component.h     |   1 +
 sound/pci/hda/hda_generic.h       |   3 +
 sound/soc/codecs/cs35l56-i2c.c    |  14 +-
 sound/soc/codecs/cs35l56-sdw.c    |  72 +--
 sound/soc/codecs/cs35l56-shared.c | 459 +++++++++++++-
 sound/soc/codecs/cs35l56-spi.c    |  10 +-
 sound/soc/codecs/cs35l56.c        | 648 ++++---------------
 sound/soc/codecs/cs35l56.h        |  15 +-
 16 files changed, 1878 insertions(+), 592 deletions(-)
 create mode 100644 sound/pci/hda/cs35l56_hda.c
 create mode 100644 sound/pci/hda/cs35l56_hda.h
 create mode 100644 sound/pci/hda/cs35l56_hda_i2c.c
 create mode 100644 sound/pci/hda/cs35l56_hda_spi.c

-- 
2.30.2


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 01/13] ASoC: cs35l56: Move shared data into a common data structure
  2023-05-25 15:06 [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Richard Fitzgerald
@ 2023-05-25 15:06 ` Richard Fitzgerald
  2023-05-25 15:06 ` [PATCH 02/13] ASoC: cs35l56: Make cs35l56_system_reset() code more generic Richard Fitzgerald
                   ` (12 subsequent siblings)
  13 siblings, 0 replies; 20+ messages in thread
From: Richard Fitzgerald @ 2023-05-25 15:06 UTC (permalink / raw)
  To: tiwai, broonie, perex
  Cc: alsa-devel, linux-kernel, patches, Simon Trimmer, Richard Fitzgerald

From: Simon Trimmer <simont@opensource.cirrus.com>

The ASoC and HDA drivers have structures that contain some of the same
information - instead of maintaining two locations for this data the
drivers should share a common data structure as this will enable common
utility functions to be created.

The first step is to move the location of these members in the ASoC
driver.

Signed-off-by: Simon Trimmer <simont@opensource.cirrus.com>
Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 include/sound/cs35l56.h           |  15 +-
 sound/soc/codecs/cs35l56-i2c.c    |  14 +-
 sound/soc/codecs/cs35l56-sdw.c    |  66 ++---
 sound/soc/codecs/cs35l56-shared.c |   6 +-
 sound/soc/codecs/cs35l56-spi.c    |  10 +-
 sound/soc/codecs/cs35l56.c        | 404 +++++++++++++++---------------
 sound/soc/codecs/cs35l56.h        |  13 +-
 7 files changed, 269 insertions(+), 259 deletions(-)

diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h
index 1f9713d7ca76..3a029c6621c9 100644
--- a/include/sound/cs35l56.h
+++ b/include/sound/cs35l56.h
@@ -252,6 +252,19 @@
 #define CS35L56_NUM_BULK_SUPPLIES			3
 #define CS35L56_NUM_DSP_REGIONS				5
 
+struct cs35l56_base {
+	struct device *dev;
+	struct regmap *regmap;
+	int irq;
+	struct mutex irq_lock;
+	u8 rev;
+	bool init_done;
+	bool fw_patched;
+	bool secured;
+	bool can_hibernate;
+	struct gpio_desc *reset_gpio;
+};
+
 extern struct regmap_config cs35l56_regmap_i2c;
 extern struct regmap_config cs35l56_regmap_spi;
 extern struct regmap_config cs35l56_regmap_sdw;
@@ -260,7 +273,7 @@ extern const struct cs_dsp_region cs35l56_dsp1_regions[CS35L56_NUM_DSP_REGIONS];
 extern const char * const cs35l56_tx_input_texts[CS35L56_NUM_INPUT_SRC];
 extern const unsigned int cs35l56_tx_input_values[CS35L56_NUM_INPUT_SRC];
 
-void cs35l56_reread_firmware_registers(struct device *dev, struct regmap *regmap);
+void cs35l56_reread_firmware_registers(struct cs35l56_base *cs35l56_base);
 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-i2c.c b/sound/soc/codecs/cs35l56-i2c.c
index 295caad26224..1f430829214b 100644
--- a/sound/soc/codecs/cs35l56-i2c.c
+++ b/sound/soc/codecs/cs35l56-i2c.c
@@ -26,14 +26,14 @@ static int cs35l56_i2c_probe(struct i2c_client *client)
 	if (!cs35l56)
 		return -ENOMEM;
 
-	cs35l56->dev = dev;
-	cs35l56->can_hibernate = true;
+	cs35l56->base.dev = dev;
+	cs35l56->base.can_hibernate = true;
 
 	i2c_set_clientdata(client, cs35l56);
-	cs35l56->regmap = devm_regmap_init_i2c(client, regmap_config);
-	if (IS_ERR(cs35l56->regmap)) {
-		ret = PTR_ERR(cs35l56->regmap);
-		return dev_err_probe(cs35l56->dev, ret, "Failed to allocate register map\n");
+	cs35l56->base.regmap = devm_regmap_init_i2c(client, regmap_config);
+	if (IS_ERR(cs35l56->base.regmap)) {
+		ret = PTR_ERR(cs35l56->base.regmap);
+		return dev_err_probe(cs35l56->base.dev, ret, "Failed to allocate register map\n");
 	}
 
 	ret = cs35l56_common_probe(cs35l56);
@@ -42,7 +42,7 @@ static int cs35l56_i2c_probe(struct i2c_client *client)
 
 	ret = cs35l56_init(cs35l56);
 	if (ret == 0)
-		ret = cs35l56_irq_request(cs35l56, client->irq);
+		ret = cs35l56_irq_request(&cs35l56->base, client->irq);
 	if (ret < 0)
 		cs35l56_remove(cs35l56);
 
diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c
index 2cde78605ba9..e6c9e8bce22c 100644
--- a/sound/soc/codecs/cs35l56-sdw.c
+++ b/sound/soc/codecs/cs35l56-sdw.c
@@ -166,13 +166,13 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral)
 	struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev);
 	int ret;
 
-	pm_runtime_get_noresume(cs35l56->dev);
+	pm_runtime_get_noresume(cs35l56->base.dev);
 
-	regcache_cache_only(cs35l56->regmap, false);
+	regcache_cache_only(cs35l56->base.regmap, false);
 
 	ret = cs35l56_init(cs35l56);
 	if (ret < 0) {
-		regcache_cache_only(cs35l56->regmap, true);
+		regcache_cache_only(cs35l56->base.regmap, true);
 		goto out;
 	}
 
@@ -180,15 +180,15 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral)
 	 * cs35l56_init can return with !init_done if it triggered
 	 * a soft reset.
 	 */
-	if (cs35l56->init_done) {
+	if (cs35l56->base.init_done) {
 		/* Enable SoundWire interrupts */
 		sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1,
 				CS35L56_SDW_INT_MASK_CODEC_IRQ);
 	}
 
 out:
-	pm_runtime_mark_last_busy(cs35l56->dev);
-	pm_runtime_put_autosuspend(cs35l56->dev);
+	pm_runtime_mark_last_busy(cs35l56->base.dev);
+	pm_runtime_put_autosuspend(cs35l56->base.dev);
 }
 
 static int cs35l56_sdw_interrupt(struct sdw_slave *peripheral,
@@ -198,7 +198,7 @@ static int cs35l56_sdw_interrupt(struct sdw_slave *peripheral,
 
 	/* SoundWire core holds our pm_runtime when calling this function. */
 
-	dev_dbg(cs35l56->dev, "int control_port=%#x\n", status->control_port);
+	dev_dbg(cs35l56->base.dev, "int control_port=%#x\n", status->control_port);
 
 	if ((status->control_port & SDW_SCP_INT1_IMPL_DEF) == 0)
 		return 0;
@@ -207,7 +207,7 @@ static int cs35l56_sdw_interrupt(struct sdw_slave *peripheral,
 	 * Prevent bus manager suspending and possibly issuing a
 	 * bus-reset before the queued work has run.
 	 */
-	pm_runtime_get_noresume(cs35l56->dev);
+	pm_runtime_get_noresume(cs35l56->base.dev);
 
 	/*
 	 * Mask and clear until it has been handled. The read of GEN_INT_STAT_1
@@ -237,7 +237,7 @@ static void cs35l56_sdw_irq_work(struct work_struct *work)
 		sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1,
 				CS35L56_SDW_INT_MASK_CODEC_IRQ);
 
-	pm_runtime_put_autosuspend(cs35l56->dev);
+	pm_runtime_put_autosuspend(cs35l56->base.dev);
 }
 
 static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral)
@@ -246,7 +246,7 @@ static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral)
 	struct sdw_slave_prop *prop = &peripheral->prop;
 	struct sdw_dpn_prop *ports;
 
-	ports = devm_kcalloc(cs35l56->dev, 2, sizeof(*ports), GFP_KERNEL);
+	ports = devm_kcalloc(cs35l56->base.dev, 2, sizeof(*ports), GFP_KERNEL);
 	if (!ports)
 		return -ENOMEM;
 
@@ -279,17 +279,17 @@ static int cs35l56_sdw_update_status(struct sdw_slave *peripheral,
 
 	switch (status) {
 	case SDW_SLAVE_ATTACHED:
-		dev_dbg(cs35l56->dev, "%s: ATTACHED\n", __func__);
+		dev_dbg(cs35l56->base.dev, "%s: ATTACHED\n", __func__);
 		if (cs35l56->sdw_attached)
 			break;
 
-		if (!cs35l56->init_done || cs35l56->soft_resetting)
+		if (!cs35l56->base.init_done || cs35l56->soft_resetting)
 			cs35l56_sdw_init(peripheral);
 
 		cs35l56->sdw_attached = true;
 		break;
 	case SDW_SLAVE_UNATTACHED:
-		dev_dbg(cs35l56->dev, "%s: UNATTACHED\n", __func__);
+		dev_dbg(cs35l56->base.dev, "%s: UNATTACHED\n", __func__);
 		cs35l56->sdw_attached = false;
 		break;
 	default:
@@ -305,7 +305,7 @@ static int cs35l56_a1_kick_divider(struct cs35l56_private *cs35l56,
 	unsigned int curr_scale_reg, next_scale_reg;
 	int curr_scale, next_scale, ret;
 
-	if (!cs35l56->init_done)
+	if (!cs35l56->base.init_done)
 		return 0;
 
 	if (peripheral->bus->params.curr_bank) {
@@ -324,13 +324,13 @@ static int cs35l56_a1_kick_divider(struct cs35l56_private *cs35l56,
 	 */
 	curr_scale = sdw_read_no_pm(peripheral, curr_scale_reg);
 	if (curr_scale < 0) {
-		dev_err(cs35l56->dev, "Failed to read current clock scale: %d\n", curr_scale);
+		dev_err(cs35l56->base.dev, "Failed to read current clock scale: %d\n", curr_scale);
 		return curr_scale;
 	}
 
 	next_scale = sdw_read_no_pm(peripheral, next_scale_reg);
 	if (next_scale < 0) {
-		dev_err(cs35l56->dev, "Failed to read next clock scale: %d\n", next_scale);
+		dev_err(cs35l56->base.dev, "Failed to read next clock scale: %d\n", next_scale);
 		return next_scale;
 	}
 
@@ -338,7 +338,8 @@ static int cs35l56_a1_kick_divider(struct cs35l56_private *cs35l56,
 		next_scale = cs35l56->old_sdw_clock_scale;
 		ret = sdw_write_no_pm(peripheral, next_scale_reg, next_scale);
 		if (ret < 0) {
-			dev_err(cs35l56->dev, "Failed to modify current clock scale: %d\n", ret);
+			dev_err(cs35l56->base.dev, "Failed to modify current clock scale: %d\n",
+				ret);
 			return ret;
 		}
 	}
@@ -346,11 +347,11 @@ static int cs35l56_a1_kick_divider(struct cs35l56_private *cs35l56,
 	cs35l56->old_sdw_clock_scale = curr_scale;
 	ret = sdw_write_no_pm(peripheral, curr_scale_reg, CS35L56_SDW_INVALID_BUS_SCALE);
 	if (ret < 0) {
-		dev_err(cs35l56->dev, "Failed to modify current clock scale: %d\n", ret);
+		dev_err(cs35l56->base.dev, "Failed to modify current clock scale: %d\n", ret);
 		return ret;
 	}
 
-	dev_dbg(cs35l56->dev, "Next bus scale: %#x\n", next_scale);
+	dev_dbg(cs35l56->base.dev, "Next bus scale: %#x\n", next_scale);
 
 	return 0;
 }
@@ -362,9 +363,10 @@ static int cs35l56_sdw_bus_config(struct sdw_slave *peripheral,
 	int sclk;
 
 	sclk = params->curr_dr_freq / 2;
-	dev_dbg(cs35l56->dev, "%s: sclk=%u c=%u r=%u\n", __func__, sclk, params->col, params->row);
+	dev_dbg(cs35l56->base.dev, "%s: sclk=%u c=%u r=%u\n",
+		__func__, sclk, params->col, params->row);
 
-	if (cs35l56->rev < 0xb0)
+	if (cs35l56->base.rev < 0xb0)
 		return cs35l56_a1_kick_divider(cs35l56, peripheral);
 
 	return 0;
@@ -376,7 +378,7 @@ static int __maybe_unused cs35l56_sdw_clk_stop(struct sdw_slave *peripheral,
 {
 	struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev);
 
-	dev_dbg(cs35l56->dev, "%s: mode:%d type:%d\n", __func__, mode, type);
+	dev_dbg(cs35l56->base.dev, "%s: mode:%d type:%d\n", __func__, mode, type);
 
 	return 0;
 }
@@ -397,10 +399,10 @@ static int __maybe_unused cs35l56_sdw_handle_unattach(struct cs35l56_private *cs
 
 	if (peripheral->unattach_request) {
 		/* Cannot access registers until bus is re-initialized. */
-		dev_dbg(cs35l56->dev, "Wait for initialization_complete\n");
+		dev_dbg(cs35l56->base.dev, "Wait for initialization_complete\n");
 		if (!wait_for_completion_timeout(&peripheral->initialization_complete,
 						 msecs_to_jiffies(5000))) {
-			dev_err(cs35l56->dev, "initialization_complete timed out\n");
+			dev_err(cs35l56->base.dev, "initialization_complete timed out\n");
 			return -ETIMEDOUT;
 		}
 
@@ -419,7 +421,7 @@ static int __maybe_unused cs35l56_sdw_runtime_suspend(struct device *dev)
 {
 	struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
 
-	if (!cs35l56->init_done)
+	if (!cs35l56->base.init_done)
 		return 0;
 
 	return cs35l56_runtime_suspend(dev);
@@ -432,7 +434,7 @@ static int __maybe_unused cs35l56_sdw_runtime_resume(struct device *dev)
 
 	dev_dbg(dev, "Runtime resume\n");
 
-	if (!cs35l56->init_done)
+	if (!cs35l56->base.init_done)
 		return 0;
 
 	ret = cs35l56_sdw_handle_unattach(cs35l56);
@@ -454,7 +456,7 @@ static int __maybe_unused cs35l56_sdw_system_suspend(struct device *dev)
 {
 	struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
 
-	if (!cs35l56->init_done)
+	if (!cs35l56->base.init_done)
 		return 0;
 
 	/*
@@ -493,21 +495,21 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi
 	if (!cs35l56)
 		return -ENOMEM;
 
-	cs35l56->dev = dev;
+	cs35l56->base.dev = dev;
 	cs35l56->sdw_peripheral = peripheral;
 	INIT_WORK(&cs35l56->sdw_irq_work, cs35l56_sdw_irq_work);
 
 	dev_set_drvdata(dev, cs35l56);
 
-	cs35l56->regmap = devm_regmap_init(dev, &cs35l56_regmap_bus_sdw,
+	cs35l56->base.regmap = devm_regmap_init(dev, &cs35l56_regmap_bus_sdw,
 					   peripheral, &cs35l56_regmap_sdw);
-	if (IS_ERR(cs35l56->regmap)) {
-		ret = PTR_ERR(cs35l56->regmap);
+	if (IS_ERR(cs35l56->base.regmap)) {
+		ret = PTR_ERR(cs35l56->base.regmap);
 		return dev_err_probe(dev, ret, "Failed to allocate register map\n");
 	}
 
 	/* Start in cache-only until device is enumerated */
-	regcache_cache_only(cs35l56->regmap, true);
+	regcache_cache_only(cs35l56->base.regmap, true);
 
 	ret = cs35l56_common_probe(cs35l56);
 	if (ret != 0)
diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c
index 60da8c75b7b9..0cbaf8c7b05a 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -187,14 +187,14 @@ static const u32 cs35l56_firmware_registers[] = {
 	CS35L56_MAIN_POSTURE_NUMBER,
 };
 
-void cs35l56_reread_firmware_registers(struct device *dev, struct regmap *regmap)
+void cs35l56_reread_firmware_registers(struct cs35l56_base *cs35l56_base)
 {
 	int i;
 	unsigned int val;
 
 	for (i = 0; i < ARRAY_SIZE(cs35l56_firmware_registers); i++) {
-		regmap_read(regmap, cs35l56_firmware_registers[i], &val);
-		dev_dbg(dev, "%s: %d: %#x: %#x\n", __func__,
+		regmap_read(cs35l56_base->regmap, cs35l56_firmware_registers[i], &val);
+		dev_dbg(cs35l56_base->dev, "%s: %d: %#x: %#x\n", __func__,
 			i, cs35l56_firmware_registers[i], val);
 	}
 }
diff --git a/sound/soc/codecs/cs35l56-spi.c b/sound/soc/codecs/cs35l56-spi.c
index 996aab10500e..2057fce435be 100644
--- a/sound/soc/codecs/cs35l56-spi.c
+++ b/sound/soc/codecs/cs35l56-spi.c
@@ -25,13 +25,13 @@ static int cs35l56_spi_probe(struct spi_device *spi)
 		return -ENOMEM;
 
 	spi_set_drvdata(spi, cs35l56);
-	cs35l56->regmap = devm_regmap_init_spi(spi, regmap_config);
-	if (IS_ERR(cs35l56->regmap)) {
-		ret = PTR_ERR(cs35l56->regmap);
+	cs35l56->base.regmap = devm_regmap_init_spi(spi, regmap_config);
+	if (IS_ERR(cs35l56->base.regmap)) {
+		ret = PTR_ERR(cs35l56->base.regmap);
 		return dev_err_probe(&spi->dev, ret, "Failed to allocate register map\n");
 	}
 
-	cs35l56->dev = &spi->dev;
+	cs35l56->base.dev = &spi->dev;
 
 	ret = cs35l56_common_probe(cs35l56);
 	if (ret != 0)
@@ -39,7 +39,7 @@ static int cs35l56_spi_probe(struct spi_device *spi)
 
 	ret = cs35l56_init(cs35l56);
 	if (ret == 0)
-		ret = cs35l56_irq_request(cs35l56, spi->irq);
+		ret = cs35l56_irq_request(&cs35l56->base, spi->irq);
 	if (ret < 0)
 		cs35l56_remove(cs35l56);
 
diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index 3c07bd1e959e..4d41c4b040a4 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -34,17 +34,17 @@
 static int cs35l56_dsp_event(struct snd_soc_dapm_widget *w,
 			     struct snd_kcontrol *kcontrol, int event);
 
-static int cs35l56_mbox_send(struct cs35l56_private *cs35l56, unsigned int command)
+static int cs35l56_mbox_send(struct cs35l56_base *cs35l56_base, unsigned int command)
 {
 	unsigned int val;
 	int ret;
 
-	regmap_write(cs35l56->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1, command);
-	ret = regmap_read_poll_timeout(cs35l56->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
+	regmap_write(cs35l56_base->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1, command);
+	ret = regmap_read_poll_timeout(cs35l56_base->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
 				       val, (val == 0),
 				       CS35L56_MBOX_POLL_US, CS35L56_MBOX_TIMEOUT_US);
 	if (ret) {
-		dev_warn(cs35l56->dev, "MBOX command %#x failed: %d\n", command, ret);
+		dev_warn(cs35l56_base->dev, "MBOX command %#x failed: %d\n", command, ret);
 		return ret;
 	}
 
@@ -174,25 +174,25 @@ static int cs35l56_play_event(struct snd_soc_dapm_widget *w,
 	unsigned int val;
 	int ret;
 
-	dev_dbg(cs35l56->dev, "play: %d\n", event);
+	dev_dbg(cs35l56->base.dev, "play: %d\n", event);
 
 	switch (event) {
 	case SND_SOC_DAPM_PRE_PMU:
 		/* Don't wait for ACK, we check in POST_PMU that it completed */
-		return regmap_write(cs35l56->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
+		return regmap_write(cs35l56->base.regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
 				    CS35L56_MBOX_CMD_AUDIO_PLAY);
 	case SND_SOC_DAPM_POST_PMU:
 		/* Wait for firmware to enter PS0 power state */
-		ret = regmap_read_poll_timeout(cs35l56->regmap,
+		ret = regmap_read_poll_timeout(cs35l56->base.regmap,
 					       CS35L56_TRANSDUCER_ACTUAL_PS,
 					       val, (val == CS35L56_PS0),
 					       CS35L56_PS0_POLL_US,
 					       CS35L56_PS0_TIMEOUT_US);
 		if (ret)
-			dev_err(cs35l56->dev, "PS0 wait failed: %d\n", ret);
+			dev_err(cs35l56->base.dev, "PS0 wait failed: %d\n", ret);
 		return ret;
 	case SND_SOC_DAPM_POST_PMD:
-		return cs35l56_mbox_send(cs35l56, CS35L56_MBOX_CMD_AUDIO_PAUSE);
+		return cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_PAUSE);
 	default:
 		return 0;
 	}
@@ -310,14 +310,14 @@ static int cs35l56_dsp_event(struct snd_soc_dapm_widget *w,
 	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
 	struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
 
-	dev_dbg(cs35l56->dev, "%s: %d\n", __func__, event);
+	dev_dbg(cs35l56->base.dev, "%s: %d\n", __func__, event);
 
 	return wm_adsp_event(w, kcontrol, event);
 }
 
 irqreturn_t cs35l56_irq(int irq, void *data)
 {
-	struct cs35l56_private *cs35l56 = data;
+	struct cs35l56_base *cs35l56_base = data;
 	unsigned int status1 = 0, status8 = 0, status20 = 0;
 	unsigned int mask1, mask8, mask20;
 	unsigned int val;
@@ -325,77 +325,77 @@ irqreturn_t cs35l56_irq(int irq, void *data)
 
 	irqreturn_t ret = IRQ_NONE;
 
-	if (!cs35l56->init_done)
+	if (!cs35l56_base->init_done)
 		return IRQ_NONE;
 
-	mutex_lock(&cs35l56->irq_lock);
+	mutex_lock(&cs35l56_base->irq_lock);
 
-	rv = pm_runtime_resume_and_get(cs35l56->dev);
+	rv = pm_runtime_resume_and_get(cs35l56_base->dev);
 	if (rv < 0) {
-		dev_err(cs35l56->dev, "irq: failed to get pm_runtime: %d\n", rv);
+		dev_err(cs35l56_base->dev, "irq: failed to get pm_runtime: %d\n", rv);
 		goto err_unlock;
 	}
 
-	regmap_read(cs35l56->regmap, CS35L56_IRQ1_STATUS, &val);
+	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_STATUS, &val);
 	if ((val & CS35L56_IRQ1_STS_MASK) == 0) {
-		dev_dbg(cs35l56->dev, "Spurious IRQ: no pending interrupt\n");
+		dev_dbg(cs35l56_base->dev, "Spurious IRQ: no pending interrupt\n");
 		goto err;
 	}
 
 	/* Ack interrupts */
-	regmap_read(cs35l56->regmap, CS35L56_IRQ1_EINT_1, &status1);
-	regmap_read(cs35l56->regmap, CS35L56_IRQ1_MASK_1, &mask1);
+	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_EINT_1, &status1);
+	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_MASK_1, &mask1);
 	status1 &= ~mask1;
-	regmap_write(cs35l56->regmap, CS35L56_IRQ1_EINT_1, status1);
+	regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_EINT_1, status1);
 
-	regmap_read(cs35l56->regmap, CS35L56_IRQ1_EINT_8, &status8);
-	regmap_read(cs35l56->regmap, CS35L56_IRQ1_MASK_8, &mask8);
+	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_EINT_8, &status8);
+	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_MASK_8, &mask8);
 	status8 &= ~mask8;
-	regmap_write(cs35l56->regmap, CS35L56_IRQ1_EINT_8, status8);
+	regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_EINT_8, status8);
 
-	regmap_read(cs35l56->regmap, CS35L56_IRQ1_EINT_20, &status20);
-	regmap_read(cs35l56->regmap, CS35L56_IRQ1_MASK_20, &mask20);
+	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_EINT_20, &status20);
+	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_MASK_20, &mask20);
 	status20 &= ~mask20;
 	/* We don't want EINT20 but they default to unmasked: force mask */
-	regmap_write(cs35l56->regmap, CS35L56_IRQ1_MASK_20, 0xffffffff);
+	regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_MASK_20, 0xffffffff);
 
-	dev_dbg(cs35l56->dev, "%s: %#x %#x\n", __func__, status1, status8);
+	dev_dbg(cs35l56_base->dev, "%s: %#x %#x\n", __func__, status1, status8);
 
 	/* Check to see if unmasked bits are active */
 	if (!status1 && !status8 && !status20)
 		goto err;
 
 	if (status1 & CS35L56_AMP_SHORT_ERR_EINT1_MASK)
-		dev_crit(cs35l56->dev, "Amp short error\n");
+		dev_crit(cs35l56_base->dev, "Amp short error\n");
 
 	if (status8 & CS35L56_TEMP_ERR_EINT1_MASK)
-		dev_crit(cs35l56->dev, "Overtemp error\n");
+		dev_crit(cs35l56_base->dev, "Overtemp error\n");
 
 	ret = IRQ_HANDLED;
 
 err:
-	pm_runtime_put(cs35l56->dev);
+	pm_runtime_put(cs35l56_base->dev);
 err_unlock:
-	mutex_unlock(&cs35l56->irq_lock);
+	mutex_unlock(&cs35l56_base->irq_lock);
 
 	return ret;
 }
 EXPORT_SYMBOL_NS_GPL(cs35l56_irq, SND_SOC_CS35L56_CORE);
 
-int cs35l56_irq_request(struct cs35l56_private *cs35l56, int irq)
+int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq)
 {
 	int ret;
 
 	if (!irq)
 		return 0;
 
-	ret = devm_request_threaded_irq(cs35l56->dev, irq, NULL, cs35l56_irq,
+	ret = devm_request_threaded_irq(cs35l56_base->dev, irq, NULL, cs35l56_irq,
 					IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW,
-					"cs35l56", cs35l56);
+					"cs35l56", cs35l56_base);
 	if (!ret)
-		cs35l56->irq = irq;
+		cs35l56_base->irq = irq;
 	else
-		dev_err(cs35l56->dev, "Failed to get IRQ: %d\n", ret);
+		dev_err(cs35l56_base->dev, "Failed to get IRQ: %d\n", ret);
 
 	return ret;
 }
@@ -406,13 +406,13 @@ static int cs35l56_asp_dai_set_fmt(struct snd_soc_dai *codec_dai, unsigned int f
 	struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(codec_dai->component);
 	unsigned int val;
 
-	dev_dbg(cs35l56->dev, "%s: %#x\n", __func__, fmt);
+	dev_dbg(cs35l56->base.dev, "%s: %#x\n", __func__, fmt);
 
 	switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
 	case SND_SOC_DAIFMT_CBC_CFC:
 		break;
 	default:
-		dev_err(cs35l56->dev, "Unsupported clock source mode\n");
+		dev_err(cs35l56->base.dev, "Unsupported clock source mode\n");
 		return -EINVAL;
 	}
 
@@ -426,7 +426,7 @@ static int cs35l56_asp_dai_set_fmt(struct snd_soc_dai *codec_dai, unsigned int f
 		cs35l56->tdm_mode = false;
 		break;
 	default:
-		dev_err(cs35l56->dev, "Unsupported DAI format\n");
+		dev_err(cs35l56->base.dev, "Unsupported DAI format\n");
 		return -EINVAL;
 	}
 
@@ -443,18 +443,18 @@ static int cs35l56_asp_dai_set_fmt(struct snd_soc_dai *codec_dai, unsigned int f
 	case SND_SOC_DAIFMT_NB_NF:
 		break;
 	default:
-		dev_err(cs35l56->dev, "Invalid clock invert\n");
+		dev_err(cs35l56->base.dev, "Invalid clock invert\n");
 		return -EINVAL;
 	}
 
-	regmap_update_bits(cs35l56->regmap,
+	regmap_update_bits(cs35l56->base.regmap,
 			   CS35L56_ASP1_CONTROL2,
 			   CS35L56_ASP_FMT_MASK |
 			   CS35L56_ASP_BCLK_INV_MASK | CS35L56_ASP_FSYNC_INV_MASK,
 			   val);
 
 	/* Hi-Z DOUT in unused slots and when all TX are disabled */
-	regmap_update_bits(cs35l56->regmap, CS35L56_ASP1_CONTROL3,
+	regmap_update_bits(cs35l56->base.regmap, CS35L56_ASP1_CONTROL3,
 			   CS35L56_ASP1_DOUT_HIZ_CTRL_MASK,
 			   CS35L56_ASP_UNUSED_HIZ_OFF_HIZ);
 
@@ -485,7 +485,7 @@ static void cs35l56_set_asp_slot_positions(struct cs35l56_private *cs35l56,
 		channel_shift += 8;
 	}
 
-	regmap_write(cs35l56->regmap, reg, reg_val);
+	regmap_write(cs35l56->base.regmap, reg, reg_val);
 }
 
 static int cs35l56_asp_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
@@ -494,20 +494,20 @@ static int cs35l56_asp_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx
 	struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(dai->component);
 
 	if ((slots == 0) || (slot_width == 0)) {
-		dev_dbg(cs35l56->dev, "tdm config cleared\n");
+		dev_dbg(cs35l56->base.dev, "tdm config cleared\n");
 		cs35l56->asp_slot_width = 0;
 		cs35l56->asp_slot_count = 0;
 		return 0;
 	}
 
 	if (slot_width > (CS35L56_ASP_RX_WIDTH_MASK >> CS35L56_ASP_RX_WIDTH_SHIFT)) {
-		dev_err(cs35l56->dev, "tdm invalid slot width %d\n", slot_width);
+		dev_err(cs35l56->base.dev, "tdm invalid slot width %d\n", slot_width);
 		return -EINVAL;
 	}
 
 	/* More than 32 slots would give an unsupportable BCLK frequency */
 	if (slots > 32) {
-		dev_err(cs35l56->dev, "tdm invalid slot count %d\n", slots);
+		dev_err(cs35l56->base.dev, "tdm invalid slot count %d\n", slots);
 		return -EINVAL;
 	}
 
@@ -524,7 +524,7 @@ static int cs35l56_asp_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx
 	cs35l56_set_asp_slot_positions(cs35l56, CS35L56_ASP1_FRAME_CONTROL1, rx_mask);
 	cs35l56_set_asp_slot_positions(cs35l56, CS35L56_ASP1_FRAME_CONTROL5, tx_mask);
 
-	dev_dbg(cs35l56->dev, "tdm slot width: %u count: %u tx_mask: %#x rx_mask: %#x\n",
+	dev_dbg(cs35l56->base.dev, "tdm slot width: %u count: %u tx_mask: %#x rx_mask: %#x\n",
 		cs35l56->asp_slot_width, cs35l56->asp_slot_count, tx_mask, rx_mask);
 
 	return 0;
@@ -544,7 +544,8 @@ static int cs35l56_asp_dai_hw_params(struct snd_pcm_substream *substream,
 	else
 		asp_width = asp_wl;
 
-	dev_dbg(cs35l56->dev, "%s: wl=%d, width=%d, rate=%d", __func__, asp_wl, asp_width, rate);
+	dev_dbg(cs35l56->base.dev, "%s: wl=%d, width=%d, rate=%d",
+		__func__, asp_wl, asp_width, rate);
 
 	if (!cs35l56->sysclk_set) {
 		unsigned int slots = cs35l56->asp_slot_count;
@@ -562,26 +563,26 @@ static int cs35l56_asp_dai_hw_params(struct snd_pcm_substream *substream,
 		bclk_freq = asp_width * slots * rate;
 		freq_id = cs35l56_get_bclk_freq_id(bclk_freq);
 		if (freq_id < 0) {
-			dev_err(cs35l56->dev, "%s: Invalid BCLK %u\n", __func__, bclk_freq);
+			dev_err(cs35l56->base.dev, "%s: Invalid BCLK %u\n", __func__, bclk_freq);
 			return -EINVAL;
 		}
 
-		regmap_update_bits(cs35l56->regmap, CS35L56_ASP1_CONTROL1,
+		regmap_update_bits(cs35l56->base.regmap, CS35L56_ASP1_CONTROL1,
 				   CS35L56_ASP_BCLK_FREQ_MASK,
 				   freq_id << CS35L56_ASP_BCLK_FREQ_SHIFT);
 	}
 
 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
-		regmap_update_bits(cs35l56->regmap, CS35L56_ASP1_CONTROL2,
+		regmap_update_bits(cs35l56->base.regmap, CS35L56_ASP1_CONTROL2,
 				   CS35L56_ASP_RX_WIDTH_MASK, asp_width <<
 				   CS35L56_ASP_RX_WIDTH_SHIFT);
-		regmap_update_bits(cs35l56->regmap, CS35L56_ASP1_DATA_CONTROL5,
+		regmap_update_bits(cs35l56->base.regmap, CS35L56_ASP1_DATA_CONTROL5,
 				   CS35L56_ASP_RX_WL_MASK, asp_wl);
 	} else {
-		regmap_update_bits(cs35l56->regmap, CS35L56_ASP1_CONTROL2,
+		regmap_update_bits(cs35l56->base.regmap, CS35L56_ASP1_CONTROL2,
 				   CS35L56_ASP_TX_WIDTH_MASK, asp_width <<
 				   CS35L56_ASP_TX_WIDTH_SHIFT);
-		regmap_update_bits(cs35l56->regmap, CS35L56_ASP1_DATA_CONTROL1,
+		regmap_update_bits(cs35l56->base.regmap, CS35L56_ASP1_DATA_CONTROL1,
 				   CS35L56_ASP_TX_WL_MASK, asp_wl);
 	}
 
@@ -603,7 +604,7 @@ static int cs35l56_asp_dai_set_sysclk(struct snd_soc_dai *dai,
 	if (freq_id < 0)
 		return freq_id;
 
-	regmap_update_bits(cs35l56->regmap, CS35L56_ASP1_CONTROL1,
+	regmap_update_bits(cs35l56->base.regmap, CS35L56_ASP1_CONTROL1,
 			   CS35L56_ASP_BCLK_FREQ_MASK,
 			   freq_id << CS35L56_ASP_BCLK_FREQ_SHIFT);
 	cs35l56->sysclk_set = true;
@@ -646,9 +647,9 @@ static int cs35l56_sdw_dai_hw_params(struct snd_pcm_substream *substream,
 	struct sdw_port_config pconfig;
 	int ret;
 
-	dev_dbg(cs35l56->dev, "%s: rate %d\n", __func__, params_rate(params));
+	dev_dbg(cs35l56->base.dev, "%s: rate %d\n", __func__, params_rate(params));
 
-	if (!cs35l56->init_done)
+	if (!cs35l56->base.init_done)
 		return -ENODEV;
 
 	if (!sdw_stream)
@@ -764,30 +765,30 @@ static struct snd_soc_dai_driver cs35l56_dai[] = {
 	}
 };
 
-static int cs35l56_wait_for_firmware_boot(struct cs35l56_private *cs35l56)
+static int cs35l56_wait_for_firmware_boot(struct cs35l56_base *cs35l56_base)
 {
 	unsigned int reg;
 	unsigned int val;
 	int ret;
 
-	if (cs35l56->rev < CS35L56_REVID_B0)
+	if (cs35l56_base->rev < CS35L56_REVID_B0)
 		reg = CS35L56_DSP1_HALO_STATE_A1;
 	else
 		reg = CS35L56_DSP1_HALO_STATE;
 
-	ret = regmap_read_poll_timeout(cs35l56->regmap, reg,
+	ret = regmap_read_poll_timeout(cs35l56_base->regmap, reg,
 				       val,
 				       (val < 0xFFFF) && (val >= CS35L56_HALO_STATE_BOOT_DONE),
 				       CS35L56_HALO_STATE_POLL_US,
 				       CS35L56_HALO_STATE_TIMEOUT_US);
 
 	if ((ret < 0) && (ret != -ETIMEDOUT)) {
-		dev_err(cs35l56->dev, "Failed to read HALO_STATE: %d\n", ret);
+		dev_err(cs35l56_base->dev, "Failed to read HALO_STATE: %d\n", ret);
 		return ret;
 	}
 
 	if ((ret == -ETIMEDOUT) || (val != CS35L56_HALO_STATE_BOOT_DONE)) {
-		dev_err(cs35l56->dev, "Firmware boot fail: HALO_STATE=%#x\n", val);
+		dev_err(cs35l56_base->dev, "Firmware boot fail: HALO_STATE=%#x\n", val);
 		return -EIO;
 	}
 
@@ -812,8 +813,8 @@ static void cs35l56_system_reset(struct cs35l56_private *cs35l56)
 	 * Must enter cache-only first so there can't be any more register
 	 * accesses other than the controlled system reset sequence below.
 	 */
-	regcache_cache_only(cs35l56->regmap, true);
-	regmap_multi_reg_write_bypassed(cs35l56->regmap,
+	regcache_cache_only(cs35l56->base.regmap, true);
+	regmap_multi_reg_write_bypassed(cs35l56->base.regmap,
 					cs35l56_system_reset_seq,
 					ARRAY_SIZE(cs35l56_system_reset_seq));
 
@@ -822,7 +823,7 @@ static void cs35l56_system_reset(struct cs35l56_private *cs35l56)
 		return;
 
 	usleep_range(CS35L56_CONTROL_PORT_READY_US, CS35L56_CONTROL_PORT_READY_US + 400);
-	regcache_cache_only(cs35l56->regmap, false);
+	regcache_cache_only(cs35l56->base.regmap, false);
 }
 
 static void cs35l56_secure_patch(struct cs35l56_private *cs35l56)
@@ -832,9 +833,9 @@ static void cs35l56_secure_patch(struct cs35l56_private *cs35l56)
 	/* Use wm_adsp to load and apply the firmware patch and coefficient files */
 	ret = wm_adsp_power_up(&cs35l56->dsp);
 	if (ret)
-		dev_dbg(cs35l56->dev, "%s: wm_adsp_power_up ret %d\n", __func__, ret);
+		dev_dbg(cs35l56->base.dev, "%s: wm_adsp_power_up ret %d\n", __func__, ret);
 	else
-		cs35l56_mbox_send(cs35l56, CS35L56_MBOX_CMD_AUDIO_REINIT);
+		cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT);
 }
 
 static void cs35l56_patch(struct cs35l56_private *cs35l56)
@@ -857,31 +858,31 @@ static void cs35l56_patch(struct cs35l56_private *cs35l56)
 		flush_work(&cs35l56->sdw_irq_work);
 	}
 
-	ret = cs35l56_mbox_send(cs35l56, CS35L56_MBOX_CMD_SHUTDOWN);
+	ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_SHUTDOWN);
 	if (ret)
 		goto err;
 
-	if (cs35l56->rev < CS35L56_REVID_B0)
+	if (cs35l56->base.rev < CS35L56_REVID_B0)
 		reg = CS35L56_DSP1_PM_CUR_STATE_A1;
 	else
 		reg = CS35L56_DSP1_PM_CUR_STATE;
 
-	ret = regmap_read_poll_timeout(cs35l56->regmap, reg,
+	ret = regmap_read_poll_timeout(cs35l56->base.regmap, reg,
 				       val, (val == CS35L56_HALO_STATE_SHUTDOWN),
 				       CS35L56_HALO_STATE_POLL_US,
 				       CS35L56_HALO_STATE_TIMEOUT_US);
 	if (ret < 0)
-		dev_err(cs35l56->dev, "Failed to poll PM_CUR_STATE to 1 is %d (ret %d)\n",
+		dev_err(cs35l56->base.dev, "Failed to poll PM_CUR_STATE to 1 is %d (ret %d)\n",
 			val, ret);
 
 	/* Use wm_adsp to load and apply the firmware patch and coefficient files */
 	ret = wm_adsp_power_up(&cs35l56->dsp);
 	if (ret) {
-		dev_dbg(cs35l56->dev, "%s: wm_adsp_power_up ret %d\n", __func__, ret);
+		dev_dbg(cs35l56->base.dev, "%s: wm_adsp_power_up ret %d\n", __func__, ret);
 		goto err;
 	}
 
-	mutex_lock(&cs35l56->irq_lock);
+	mutex_lock(&cs35l56->base.irq_lock);
 
 	init_completion(&cs35l56->init_completion);
 
@@ -895,18 +896,20 @@ static void cs35l56_patch(struct cs35l56_private *cs35l56)
 		 */
 		if (!wait_for_completion_timeout(&cs35l56->init_completion,
 						 msecs_to_jiffies(5000))) {
-			dev_err(cs35l56->dev, "%s: init_completion timed out (SDW)\n", __func__);
+			dev_err(cs35l56->base.dev, "%s: init_completion timed out (SDW)\n",
+				__func__);
 			goto err_unlock;
 		}
 	} else if (cs35l56_init(cs35l56)) {
 		goto err_unlock;
 	}
 
-	regmap_clear_bits(cs35l56->regmap, CS35L56_PROTECTION_STATUS, CS35L56_FIRMWARE_MISSING);
-	cs35l56->fw_patched = true;
+	regmap_clear_bits(cs35l56->base.regmap, CS35L56_PROTECTION_STATUS,
+			  CS35L56_FIRMWARE_MISSING);
+	cs35l56->base.fw_patched = true;
 
 err_unlock:
-	mutex_unlock(&cs35l56->irq_lock);
+	mutex_unlock(&cs35l56->base.irq_lock);
 err:
 	/* Re-enable SoundWire interrupts */
 	if (cs35l56->sdw_peripheral) {
@@ -922,10 +925,10 @@ static void cs35l56_dsp_work(struct work_struct *work)
 						       struct cs35l56_private,
 						       dsp_work);
 
-	if (!cs35l56->init_done)
+	if (!cs35l56->base.init_done)
 		return;
 
-	pm_runtime_get_sync(cs35l56->dev);
+	pm_runtime_get_sync(cs35l56->base.dev);
 
 	/*
 	 * When the device is running in secure mode the firmware files can
@@ -933,13 +936,13 @@ static void cs35l56_dsp_work(struct work_struct *work)
 	 * shutdown the firmware to apply them and can use the lower cost
 	 * reinit sequence instead.
 	 */
-	if (cs35l56->secured)
+	if (cs35l56->base.secured)
 		cs35l56_secure_patch(cs35l56);
 	else
 		cs35l56_patch(cs35l56);
 
-	pm_runtime_mark_last_busy(cs35l56->dev);
-	pm_runtime_put_autosuspend(cs35l56->dev);
+	pm_runtime_mark_last_busy(cs35l56->base.dev);
+	pm_runtime_put_autosuspend(cs35l56->base.dev);
 }
 
 static int cs35l56_component_probe(struct snd_soc_component *component)
@@ -951,16 +954,16 @@ static int cs35l56_component_probe(struct snd_soc_component *component)
 
 	if (!wait_for_completion_timeout(&cs35l56->init_completion,
 					 msecs_to_jiffies(5000))) {
-		dev_err(cs35l56->dev, "%s: init_completion timed out\n", __func__);
+		dev_err(cs35l56->base.dev, "%s: init_completion timed out\n", __func__);
 		return -ENODEV;
 	}
 
 	cs35l56->component = component;
 	wm_adsp2_component_probe(&cs35l56->dsp, component);
 
-	debugfs_create_bool("init_done", 0444, debugfs_root, &cs35l56->init_done);
-	debugfs_create_bool("can_hibernate", 0444, debugfs_root, &cs35l56->can_hibernate);
-	debugfs_create_bool("fw_patched", 0444, debugfs_root, &cs35l56->fw_patched);
+	debugfs_create_bool("init_done", 0444, debugfs_root, &cs35l56->base.init_done);
+	debugfs_create_bool("can_hibernate", 0444, debugfs_root, &cs35l56->base.can_hibernate);
+	debugfs_create_bool("fw_patched", 0444, debugfs_root, &cs35l56->base.fw_patched);
 
 	queue_work(cs35l56->dsp_wq, &cs35l56->dsp_work);
 
@@ -1027,23 +1030,23 @@ int cs35l56_runtime_suspend(struct device *dev)
 	unsigned int val;
 	int ret;
 
-	if (!cs35l56->init_done)
+	if (!cs35l56->base.init_done)
 		return 0;
 
 	/* Firmware must have entered a power-save state */
-	ret = regmap_read_poll_timeout(cs35l56->regmap,
+	ret = regmap_read_poll_timeout(cs35l56->base.regmap,
 				       CS35L56_TRANSDUCER_ACTUAL_PS,
 				       val, (val >= CS35L56_PS3),
 				       CS35L56_PS3_POLL_US,
 				       CS35L56_PS3_TIMEOUT_US);
 	if (ret)
-		dev_warn(cs35l56->dev, "PS3 wait failed: %d\n", ret);
+		dev_warn(cs35l56->base.dev, "PS3 wait failed: %d\n", ret);
 
 	/* Clear BOOT_DONE so it can be used to detect a reboot */
-	regmap_write(cs35l56->regmap, CS35L56_IRQ1_EINT_4, CS35L56_OTP_BOOT_DONE_MASK);
+	regmap_write(cs35l56->base.regmap, CS35L56_IRQ1_EINT_4, CS35L56_OTP_BOOT_DONE_MASK);
 
-	if (!cs35l56->can_hibernate) {
-		regcache_cache_only(cs35l56->regmap, true);
+	if (!cs35l56->base.can_hibernate) {
+		regcache_cache_only(cs35l56->base.regmap, true);
 		dev_dbg(dev, "Suspended: no hibernate");
 
 		return 0;
@@ -1053,15 +1056,15 @@ int cs35l56_runtime_suspend(struct device *dev)
 	 * Enable auto-hibernate. If it is woken by some other wake source
 	 * it will automatically return to hibernate.
 	 */
-	cs35l56_mbox_send(cs35l56, CS35L56_MBOX_CMD_ALLOW_AUTO_HIBERNATE);
+	cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_ALLOW_AUTO_HIBERNATE);
 
 	/*
 	 * Must enter cache-only first so there can't be any more register
 	 * accesses other than the controlled hibernate sequence below.
 	 */
-	regcache_cache_only(cs35l56->regmap, true);
+	regcache_cache_only(cs35l56->base.regmap, true);
 
-	regmap_multi_reg_write_bypassed(cs35l56->regmap,
+	regmap_multi_reg_write_bypassed(cs35l56->base.regmap,
 					cs35l56_hibernate_seq,
 					ARRAY_SIZE(cs35l56_hibernate_seq));
 
@@ -1075,7 +1078,7 @@ static int __maybe_unused cs35l56_runtime_resume_i2c_spi(struct device *dev)
 {
 	struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
 
-	if (!cs35l56->init_done)
+	if (!cs35l56->base.init_done)
 		return 0;
 
 	return cs35l56_runtime_resume_common(cs35l56);
@@ -1086,7 +1089,7 @@ int cs35l56_runtime_resume_common(struct cs35l56_private *cs35l56)
 	unsigned int val;
 	int ret;
 
-	if (!cs35l56->can_hibernate)
+	if (!cs35l56->base.can_hibernate)
 		goto out_sync;
 
 	if (!cs35l56->sdw_peripheral) {
@@ -1094,7 +1097,7 @@ int cs35l56_runtime_resume_common(struct cs35l56_private *cs35l56)
 		 * Dummy transaction to trigger I2C/SPI auto-wake. This will NAK on I2C.
 		 * Must be done before releasing cache-only.
 		 */
-		regmap_multi_reg_write_bypassed(cs35l56->regmap,
+		regmap_multi_reg_write_bypassed(cs35l56->base.regmap,
 						cs35l56_hibernate_wake_seq,
 						ARRAY_SIZE(cs35l56_hibernate_wake_seq));
 
@@ -1103,36 +1106,36 @@ int cs35l56_runtime_resume_common(struct cs35l56_private *cs35l56)
 	}
 
 out_sync:
-	regcache_cache_only(cs35l56->regmap, false);
+	regcache_cache_only(cs35l56->base.regmap, false);
 
-	ret = cs35l56_wait_for_firmware_boot(cs35l56);
+	ret = cs35l56_wait_for_firmware_boot(&cs35l56->base);
 	if (ret) {
-		dev_err(cs35l56->dev, "Hibernate wake failed: %d\n", ret);
+		dev_err(cs35l56->base.dev, "Hibernate wake failed: %d\n", ret);
 		goto err;
 	}
 
-	ret = cs35l56_mbox_send(cs35l56, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE);
+	ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE);
 	if (ret)
 		goto err;
 
 	/* BOOT_DONE will be 1 if the amp reset */
-	regmap_read(cs35l56->regmap, CS35L56_IRQ1_EINT_4, &val);
+	regmap_read(cs35l56->base.regmap, CS35L56_IRQ1_EINT_4, &val);
 	if (val & CS35L56_OTP_BOOT_DONE_MASK) {
-		dev_dbg(cs35l56->dev, "Registers reset in suspend\n");
-		regcache_mark_dirty(cs35l56->regmap);
+		dev_dbg(cs35l56->base.dev, "Registers reset in suspend\n");
+		regcache_mark_dirty(cs35l56->base.regmap);
 	}
 
-	regcache_sync(cs35l56->regmap);
+	regcache_sync(cs35l56->base.regmap);
 
-	dev_dbg(cs35l56->dev, "Resumed");
+	dev_dbg(cs35l56->base.dev, "Resumed");
 
 	return 0;
 
 err:
-	regmap_write(cs35l56->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
+	regmap_write(cs35l56->base.regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
 		     CS35L56_MBOX_CMD_HIBERNATE_NOW);
 
-	regcache_cache_only(cs35l56->regmap, true);
+	regcache_cache_only(cs35l56->base.regmap, true);
 
 	return ret;
 }
@@ -1144,14 +1147,14 @@ static int cs35l56_is_fw_reload_needed(struct cs35l56_private *cs35l56)
 	int ret;
 
 	/* Nothing to re-patch if we haven't done any patching yet. */
-	if (!cs35l56->fw_patched)
+	if (!cs35l56->base.fw_patched)
 		return false;
 
 	/*
 	 * If we have control of RESET we will have asserted it so the firmware
 	 * will need re-patching.
 	 */
-	if (cs35l56->reset_gpio)
+	if (cs35l56->base.reset_gpio)
 		return true;
 
 	/*
@@ -1159,22 +1162,22 @@ static int cs35l56_is_fw_reload_needed(struct cs35l56_private *cs35l56)
 	 * can't be used here to test for memory retention.
 	 * Assume that tuning must be re-loaded.
 	 */
-	if (cs35l56->secured)
+	if (cs35l56->base.secured)
 		return true;
 
-	ret = pm_runtime_resume_and_get(cs35l56->dev);
+	ret = pm_runtime_resume_and_get(cs35l56->base.dev);
 	if (ret) {
-		dev_err(cs35l56->dev, "Failed to runtime_get: %d\n", ret);
+		dev_err(cs35l56->base.dev, "Failed to runtime_get: %d\n", ret);
 		return ret;
 	}
 
-	ret = regmap_read(cs35l56->regmap, CS35L56_PROTECTION_STATUS, &val);
+	ret = regmap_read(cs35l56->base.regmap, CS35L56_PROTECTION_STATUS, &val);
 	if (ret)
-		dev_err(cs35l56->dev, "Failed to read PROTECTION_STATUS: %d\n", ret);
+		dev_err(cs35l56->base.dev, "Failed to read PROTECTION_STATUS: %d\n", ret);
 	else
 		ret = !!(val & CS35L56_FIRMWARE_MISSING);
 
-	pm_runtime_put_autosuspend(cs35l56->dev);
+	pm_runtime_put_autosuspend(cs35l56->base.dev);
 
 	return ret;
 }
@@ -1194,8 +1197,8 @@ int cs35l56_system_suspend(struct device *dev)
 	 * clear it. Prevent this race by temporarily disabling the parent irq
 	 * until we reach _no_irq.
 	 */
-	if (cs35l56->irq)
-		disable_irq(cs35l56->irq);
+	if (cs35l56->base.irq)
+		disable_irq(cs35l56->base.irq);
 
 	return pm_runtime_force_suspend(dev);
 }
@@ -1212,8 +1215,8 @@ int cs35l56_system_suspend_late(struct device *dev)
 	 * RESET is usually shared by all amps so it must not be asserted until
 	 * all driver instances have done their suspend() stage.
 	 */
-	if (cs35l56->reset_gpio) {
-		gpiod_set_value_cansleep(cs35l56->reset_gpio, 0);
+	if (cs35l56->base.reset_gpio) {
+		gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
 		cs35l56_wait_min_reset_pulse();
 	}
 
@@ -1230,8 +1233,8 @@ int cs35l56_system_suspend_no_irq(struct device *dev)
 	dev_dbg(dev, "system_suspend_no_irq\n");
 
 	/* Handlers are now disabled so the parent IRQ can safely be re-enabled. */
-	if (cs35l56->irq)
-		enable_irq(cs35l56->irq);
+	if (cs35l56->base.irq)
+		enable_irq(cs35l56->base.irq);
 
 	return 0;
 }
@@ -1250,8 +1253,8 @@ int cs35l56_system_resume_no_irq(struct device *dev)
 	 * clear it, until it has fully resumed. Prevent this race by temporarily
 	 * disabling the parent irq until we complete resume().
 	 */
-	if (cs35l56->irq)
-		disable_irq(cs35l56->irq);
+	if (cs35l56->base.irq)
+		disable_irq(cs35l56->base.irq);
 
 	return 0;
 }
@@ -1265,8 +1268,8 @@ int cs35l56_system_resume_early(struct device *dev)
 	dev_dbg(dev, "system_resume_early\n");
 
 	/* Ensure a spec-compliant RESET pulse. */
-	if (cs35l56->reset_gpio) {
-		gpiod_set_value_cansleep(cs35l56->reset_gpio, 0);
+	if (cs35l56->base.reset_gpio) {
+		gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
 		cs35l56_wait_min_reset_pulse();
 	}
 
@@ -1278,7 +1281,7 @@ int cs35l56_system_resume_early(struct device *dev)
 	}
 
 	/* Release shared RESET before drivers start resume(). */
-	gpiod_set_value_cansleep(cs35l56->reset_gpio, 1);
+	gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 1);
 
 	return 0;
 }
@@ -1293,8 +1296,8 @@ int cs35l56_system_resume(struct device *dev)
 
 	/* Undo pm_runtime_force_suspend() before re-enabling the irq */
 	ret = pm_runtime_force_resume(dev);
-	if (cs35l56->irq)
-		enable_irq(cs35l56->irq);
+	if (cs35l56->base.irq)
+		enable_irq(cs35l56->base.irq);
 
 	if (ret)
 		return ret;
@@ -1304,11 +1307,11 @@ int cs35l56_system_resume(struct device *dev)
 		return 0;
 
 	ret = cs35l56_is_fw_reload_needed(cs35l56);
-	dev_dbg(cs35l56->dev, "fw_reload_needed: %d\n", ret);
+	dev_dbg(cs35l56->base.dev, "fw_reload_needed: %d\n", ret);
 	if (ret < 1)
 		return ret;
 
-	cs35l56->fw_patched = false;
+	cs35l56->base.fw_patched = false;
 	queue_work(cs35l56->dsp_wq, &cs35l56->dsp_work);
 
 	/*
@@ -1337,8 +1340,8 @@ static int cs35l56_dsp_init(struct cs35l56_private *cs35l56)
 	dsp->cs_dsp.type = WMFW_HALO;
 	dsp->cs_dsp.rev = 0;
 	dsp->fw = 12;
-	dsp->cs_dsp.dev = cs35l56->dev;
-	dsp->cs_dsp.regmap = cs35l56->regmap;
+	dsp->cs_dsp.dev = cs35l56->base.dev;
+	dsp->cs_dsp.regmap = cs35l56->base.regmap;
 	dsp->cs_dsp.base = CS35L56_DSP1_CORE_BASE;
 	dsp->cs_dsp.base_sysinfo = CS35L56_DSP1_SYS_INFO_ID;
 	dsp->cs_dsp.mem = cs35l56_dsp1_regions;
@@ -1346,11 +1349,11 @@ static int cs35l56_dsp_init(struct cs35l56_private *cs35l56)
 	dsp->cs_dsp.no_core_startstop = true;
 	dsp->wmfw_optional = true;
 
-	dev_dbg(cs35l56->dev, "DSP system name: '%s'\n", dsp->system_name);
+	dev_dbg(cs35l56->base.dev, "DSP system name: '%s'\n", dsp->system_name);
 
 	ret = wm_halo_init(dsp);
 	if (ret != 0) {
-		dev_err(cs35l56->dev, "wm_halo_init failed\n");
+		dev_err(cs35l56->base.dev, "wm_halo_init failed\n");
 		return ret;
 	}
 
@@ -1359,7 +1362,7 @@ static int cs35l56_dsp_init(struct cs35l56_private *cs35l56)
 
 static int cs35l56_acpi_get_name(struct cs35l56_private *cs35l56)
 {
-	acpi_handle handle = ACPI_HANDLE(cs35l56->dev);
+	acpi_handle handle = ACPI_HANDLE(cs35l56->base.dev);
 	const char *sub;
 
 	/* If there is no ACPI_HANDLE, there is no ACPI for this system, return 0 */
@@ -1376,7 +1379,7 @@ static int cs35l56_acpi_get_name(struct cs35l56_private *cs35l56)
 	}
 
 	cs35l56->dsp.system_name = sub;
-	dev_dbg(cs35l56->dev, "Subsystem ID: %s\n", cs35l56->dsp.system_name);
+	dev_dbg(cs35l56->base.dev, "Subsystem ID: %s\n", cs35l56->dsp.system_name);
 
 	return 0;
 }
@@ -1386,38 +1389,39 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56)
 	int ret;
 
 	init_completion(&cs35l56->init_completion);
-	mutex_init(&cs35l56->irq_lock);
+	mutex_init(&cs35l56->base.irq_lock);
 
-	dev_set_drvdata(cs35l56->dev, cs35l56);
+	dev_set_drvdata(cs35l56->base.dev, cs35l56);
 
 	cs35l56_fill_supply_names(cs35l56->supplies);
-	ret = devm_regulator_bulk_get(cs35l56->dev, ARRAY_SIZE(cs35l56->supplies),
+	ret = devm_regulator_bulk_get(cs35l56->base.dev, ARRAY_SIZE(cs35l56->supplies),
 				      cs35l56->supplies);
 	if (ret != 0)
-		return dev_err_probe(cs35l56->dev, ret, "Failed to request supplies\n");
+		return dev_err_probe(cs35l56->base.dev, ret, "Failed to request supplies\n");
 
 	/* Reset could be controlled by the BIOS or shared by multiple amps */
-	cs35l56->reset_gpio = devm_gpiod_get_optional(cs35l56->dev, "reset", GPIOD_OUT_LOW);
-	if (IS_ERR(cs35l56->reset_gpio)) {
-		ret = PTR_ERR(cs35l56->reset_gpio);
+	cs35l56->base.reset_gpio = devm_gpiod_get_optional(cs35l56->base.dev, "reset",
+							   GPIOD_OUT_LOW);
+	if (IS_ERR(cs35l56->base.reset_gpio)) {
+		ret = PTR_ERR(cs35l56->base.reset_gpio);
 		/*
 		 * If RESET is shared the first amp to probe will grab the reset
 		 * line and reset all the amps
 		 */
 		if (ret != -EBUSY)
-			return dev_err_probe(cs35l56->dev, ret, "Failed to get reset GPIO\n");
+			return dev_err_probe(cs35l56->base.dev, ret, "Failed to get reset GPIO\n");
 
-		dev_info(cs35l56->dev, "Reset GPIO busy, assume shared reset\n");
-		cs35l56->reset_gpio = NULL;
+		dev_info(cs35l56->base.dev, "Reset GPIO busy, assume shared reset\n");
+		cs35l56->base.reset_gpio = NULL;
 	}
 
 	ret = regulator_bulk_enable(ARRAY_SIZE(cs35l56->supplies), cs35l56->supplies);
 	if (ret != 0)
-		return dev_err_probe(cs35l56->dev, ret, "Failed to enable supplies\n");
+		return dev_err_probe(cs35l56->base.dev, ret, "Failed to enable supplies\n");
 
-	if (cs35l56->reset_gpio) {
+	if (cs35l56->base.reset_gpio) {
 		cs35l56_wait_min_reset_pulse();
-		gpiod_set_value_cansleep(cs35l56->reset_gpio, 1);
+		gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 1);
 	}
 
 	ret = cs35l56_acpi_get_name(cs35l56);
@@ -1426,22 +1430,22 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56)
 
 	ret = cs35l56_dsp_init(cs35l56);
 	if (ret < 0) {
-		dev_err_probe(cs35l56->dev, ret, "DSP init failed\n");
+		dev_err_probe(cs35l56->base.dev, ret, "DSP init failed\n");
 		goto err;
 	}
 
-	ret = devm_snd_soc_register_component(cs35l56->dev,
+	ret = devm_snd_soc_register_component(cs35l56->base.dev,
 					      &soc_component_dev_cs35l56,
 					      cs35l56_dai, ARRAY_SIZE(cs35l56_dai));
 	if (ret < 0) {
-		dev_err_probe(cs35l56->dev, ret, "Register codec failed\n");
+		dev_err_probe(cs35l56->base.dev, ret, "Register codec failed\n");
 		goto err;
 	}
 
 	return 0;
 
 err:
-	gpiod_set_value_cansleep(cs35l56->reset_gpio, 0);
+	gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
 	regulator_bulk_disable(ARRAY_SIZE(cs35l56->supplies), cs35l56->supplies);
 
 	return ret;
@@ -1460,20 +1464,20 @@ int cs35l56_init(struct cs35l56_private *cs35l56)
 	if (cs35l56->soft_resetting)
 		goto post_soft_reset;
 
-	if (cs35l56->init_done)
+	if (cs35l56->base.init_done)
 		return 0;
 
-	pm_runtime_set_autosuspend_delay(cs35l56->dev, 100);
-	pm_runtime_use_autosuspend(cs35l56->dev);
-	pm_runtime_set_active(cs35l56->dev);
-	pm_runtime_enable(cs35l56->dev);
+	pm_runtime_set_autosuspend_delay(cs35l56->base.dev, 100);
+	pm_runtime_use_autosuspend(cs35l56->base.dev);
+	pm_runtime_set_active(cs35l56->base.dev);
+	pm_runtime_enable(cs35l56->base.dev);
 
 	/*
 	 * If the system is not using a reset_gpio then issue a
 	 * dummy read to force a wakeup.
 	 */
-	if (!cs35l56->reset_gpio)
-		regmap_read(cs35l56->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1, &devid);
+	if (!cs35l56->base.reset_gpio)
+		regmap_read(cs35l56->base.regmap, CS35L56_DSP_VIRTUAL1_MBOX_1, &devid);
 
 	/* Wait for control port to be ready (datasheet tIRS). */
 	usleep_range(CS35L56_CONTROL_PORT_READY_US,
@@ -1484,20 +1488,20 @@ int cs35l56_init(struct cs35l56_private *cs35l56)
 	 * devices so the REVID needs to be determined before waiting for the
 	 * firmware to boot.
 	 */
-	ret = regmap_read(cs35l56->regmap, CS35L56_REVID, &revid);
+	ret = regmap_read(cs35l56->base.regmap, CS35L56_REVID, &revid);
 	if (ret < 0) {
-		dev_err(cs35l56->dev, "Get Revision ID failed\n");
+		dev_err(cs35l56->base.dev, "Get Revision ID failed\n");
 		return ret;
 	}
-	cs35l56->rev = revid & (CS35L56_AREVID_MASK | CS35L56_MTLREVID_MASK);
+	cs35l56->base.rev = revid & (CS35L56_AREVID_MASK | CS35L56_MTLREVID_MASK);
 
-	ret = cs35l56_wait_for_firmware_boot(cs35l56);
+	ret = cs35l56_wait_for_firmware_boot(&cs35l56->base);
 	if (ret)
 		return ret;
 
-	ret = regmap_read(cs35l56->regmap, CS35L56_DEVID, &devid);
+	ret = regmap_read(cs35l56->base.regmap, CS35L56_DEVID, &devid);
 	if (ret < 0) {
-		dev_err(cs35l56->dev, "Get Device ID failed\n");
+		dev_err(cs35l56->base.dev, "Get Device ID failed\n");
 		return ret;
 	}
 	devid &= CS35L56_DEVID_MASK;
@@ -1506,50 +1510,50 @@ int cs35l56_init(struct cs35l56_private *cs35l56)
 	case 0x35A56:
 		break;
 	default:
-		dev_err(cs35l56->dev, "Unknown device %x\n", devid);
+		dev_err(cs35l56->base.dev, "Unknown device %x\n", devid);
 		return ret;
 	}
 
-	ret = regmap_read(cs35l56->regmap, CS35L56_DSP_RESTRICT_STS1, &secured);
+	ret = regmap_read(cs35l56->base.regmap, CS35L56_DSP_RESTRICT_STS1, &secured);
 	if (ret) {
-		dev_err(cs35l56->dev, "Get Secure status failed\n");
+		dev_err(cs35l56->base.dev, "Get Secure status failed\n");
 		return ret;
 	}
 
 	/* When any bus is restricted treat the device as secured */
 	if (secured & CS35L56_RESTRICTED_MASK)
-		cs35l56->secured = true;
+		cs35l56->base.secured = true;
 
-	ret = regmap_read(cs35l56->regmap, CS35L56_OTPID, &otpid);
+	ret = regmap_read(cs35l56->base.regmap, CS35L56_OTPID, &otpid);
 	if (ret < 0) {
-		dev_err(cs35l56->dev, "Get OTP ID failed\n");
+		dev_err(cs35l56->base.dev, "Get OTP ID failed\n");
 		return ret;
 	}
 
-	dev_info(cs35l56->dev, "Cirrus Logic CS35L56%s Rev %02X OTP%d\n",
-		 cs35l56->secured ? "s" : "", cs35l56->rev, otpid);
+	dev_info(cs35l56->base.dev, "Cirrus Logic CS35L56%s Rev %02X OTP%d\n",
+		 cs35l56->base.secured ? "s" : "", cs35l56->base.rev, otpid);
 
 	/* Populate the DSP information with the revision and security state */
-	cs35l56->dsp.part = devm_kasprintf(cs35l56->dev, GFP_KERNEL, "cs35l56%s-%02x",
-					   cs35l56->secured ? "s" : "", cs35l56->rev);
+	cs35l56->dsp.part = devm_kasprintf(cs35l56->base.dev, GFP_KERNEL, "cs35l56%s-%02x",
+					   cs35l56->base.secured ? "s" : "", cs35l56->base.rev);
 	if (!cs35l56->dsp.part)
 		return -ENOMEM;
 
 	/* Wake source and *_BLOCKED interrupts default to unmasked, so mask them */
-	regmap_write(cs35l56->regmap, CS35L56_IRQ1_MASK_20, 0xffffffff);
-	regmap_update_bits(cs35l56->regmap, CS35L56_IRQ1_MASK_1,
+	regmap_write(cs35l56->base.regmap, CS35L56_IRQ1_MASK_20, 0xffffffff);
+	regmap_update_bits(cs35l56->base.regmap, CS35L56_IRQ1_MASK_1,
 			   CS35L56_AMP_SHORT_ERR_EINT1_MASK,
 			   0);
-	regmap_update_bits(cs35l56->regmap, CS35L56_IRQ1_MASK_8,
+	regmap_update_bits(cs35l56->base.regmap, CS35L56_IRQ1_MASK_8,
 			   CS35L56_TEMP_ERR_EINT1_MASK,
 			   0);
 
-	if (!cs35l56->reset_gpio) {
-		dev_dbg(cs35l56->dev, "No reset gpio: using soft reset\n");
+	if (!cs35l56->base.reset_gpio) {
+		dev_dbg(cs35l56->base.dev, "No reset gpio: using soft reset\n");
 		cs35l56_system_reset(cs35l56);
 		if (cs35l56->sdw_peripheral) {
 			/* Keep alive while we wait for re-enumeration */
-			pm_runtime_get_noresume(cs35l56->dev);
+			pm_runtime_get_noresume(cs35l56->base.dev);
 			return 0;
 		}
 	}
@@ -1559,29 +1563,29 @@ int cs35l56_init(struct cs35l56_private *cs35l56)
 		cs35l56->soft_resetting = false;
 
 		/* Done re-enumerating after one-time init so release the keep-alive */
-		if (cs35l56->sdw_peripheral && !cs35l56->init_done)
-			pm_runtime_put_noidle(cs35l56->dev);
+		if (cs35l56->sdw_peripheral && !cs35l56->base.init_done)
+			pm_runtime_put_noidle(cs35l56->base.dev);
 
-		regcache_mark_dirty(cs35l56->regmap);
-		ret = cs35l56_wait_for_firmware_boot(cs35l56);
+		regcache_mark_dirty(cs35l56->base.regmap);
+		ret = cs35l56_wait_for_firmware_boot(&cs35l56->base);
 		if (ret)
 			return ret;
 
-		dev_dbg(cs35l56->dev, "Firmware rebooted after soft reset\n");
+		dev_dbg(cs35l56->base.dev, "Firmware rebooted after soft reset\n");
 	}
 
 	/* Disable auto-hibernate so that runtime_pm has control */
-	ret = cs35l56_mbox_send(cs35l56, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE);
+	ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE);
 	if (ret)
 		return ret;
 
 	/* Populate soft registers in the regmap cache */
-	cs35l56_reread_firmware_registers(cs35l56->dev, cs35l56->regmap);
+	cs35l56_reread_firmware_registers(&cs35l56->base);
 
 	/* Registers could be dirty after soft reset or SoundWire enumeration */
-	regcache_sync(cs35l56->regmap);
+	regcache_sync(cs35l56->base.regmap);
 
-	cs35l56->init_done = true;
+	cs35l56->base.init_done = true;
 	complete(&cs35l56->init_completion);
 
 	return 0;
@@ -1590,26 +1594,26 @@ EXPORT_SYMBOL_NS_GPL(cs35l56_init, SND_SOC_CS35L56_CORE);
 
 void cs35l56_remove(struct cs35l56_private *cs35l56)
 {
-	cs35l56->init_done = false;
+	cs35l56->base.init_done = false;
 
 	/*
 	 * WAKE IRQs unmask if CS35L56 hibernates so free the handler to
 	 * prevent it racing with remove().
 	 */
-	if (cs35l56->irq)
-		devm_free_irq(cs35l56->dev, cs35l56->irq, cs35l56);
+	if (cs35l56->base.irq)
+		devm_free_irq(cs35l56->base.dev, cs35l56->base.irq, cs35l56);
 
 	flush_workqueue(cs35l56->dsp_wq);
 	destroy_workqueue(cs35l56->dsp_wq);
 
-	pm_runtime_suspend(cs35l56->dev);
-	pm_runtime_disable(cs35l56->dev);
+	pm_runtime_suspend(cs35l56->base.dev);
+	pm_runtime_disable(cs35l56->base.dev);
 
-	regcache_cache_only(cs35l56->regmap, true);
+	regcache_cache_only(cs35l56->base.regmap, true);
 
 	kfree(cs35l56->dsp.system_name);
 
-	gpiod_set_value_cansleep(cs35l56->reset_gpio, 0);
+	gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
 	regulator_bulk_disable(ARRAY_SIZE(cs35l56->supplies), cs35l56->supplies);
 }
 EXPORT_SYMBOL_NS_GPL(cs35l56_remove, SND_SOC_CS35L56_CORE);
diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h
index 1f7894662fcb..f39f8fa9e37e 100644
--- a/sound/soc/codecs/cs35l56.h
+++ b/sound/soc/codecs/cs35l56.h
@@ -32,26 +32,17 @@ struct sdw_slave;
 
 struct cs35l56_private {
 	struct wm_adsp dsp; /* must be first member */
+	struct cs35l56_base base;
 	struct work_struct dsp_work;
 	struct workqueue_struct *dsp_wq;
-	struct mutex irq_lock;
 	struct snd_soc_component *component;
-	struct device *dev;
-	struct regmap *regmap;
 	struct regulator_bulk_data supplies[CS35L56_NUM_BULK_SUPPLIES];
-	int irq;
 	struct sdw_slave *sdw_peripheral;
-	u8 rev;
 	struct work_struct sdw_irq_work;
-	bool secured;
 	bool sdw_irq_no_unmask;
 	bool soft_resetting;
-	bool init_done;
 	bool sdw_attached;
-	bool fw_patched;
-	bool can_hibernate;
 	struct completion init_completion;
-	struct gpio_desc *reset_gpio;
 
 	u32 rx_mask;
 	u32 tx_mask;
@@ -73,7 +64,7 @@ int cs35l56_system_resume_no_irq(struct device *dev);
 int cs35l56_system_resume_early(struct device *dev);
 int cs35l56_system_resume(struct device *dev);
 irqreturn_t cs35l56_irq(int irq, void *data);
-int cs35l56_irq_request(struct cs35l56_private *cs35l56, int irq);
+int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq);
 int cs35l56_common_probe(struct cs35l56_private *cs35l56);
 int cs35l56_init(struct cs35l56_private *cs35l56);
 void cs35l56_remove(struct cs35l56_private *cs35l56);
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 02/13] ASoC: cs35l56: Make cs35l56_system_reset() code more generic
  2023-05-25 15:06 [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Richard Fitzgerald
  2023-05-25 15:06 ` [PATCH 01/13] ASoC: cs35l56: Move shared data into a common data structure Richard Fitzgerald
@ 2023-05-25 15:06 ` Richard Fitzgerald
  2023-05-25 15:06 ` [PATCH 03/13] ASoC: cs35l56: Convert utility functions to use common data structure Richard Fitzgerald
                   ` (11 subsequent siblings)
  13 siblings, 0 replies; 20+ messages in thread
From: Richard Fitzgerald @ 2023-05-25 15:06 UTC (permalink / raw)
  To: tiwai, broonie, perex
  Cc: alsa-devel, linux-kernel, patches, Simon Trimmer, Richard Fitzgerald

From: Simon Trimmer <simont@opensource.cirrus.com>

The function can be more easily reused in HDA if the tracking of whether
a soft reset is being performed and whether the device is connected to a
SoundWire bus is moved out of the function.

Signed-off-by: Simon Trimmer <simont@opensource.cirrus.com>
Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 sound/soc/codecs/cs35l56.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index 4d41c4b040a4..76cc9110d4e0 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -805,10 +805,8 @@ static const struct reg_sequence cs35l56_system_reset_seq[] = {
 	REG_SEQ0(CS35L56_DSP_VIRTUAL1_MBOX_1, CS35L56_MBOX_CMD_SYSTEM_RESET),
 };
 
-static void cs35l56_system_reset(struct cs35l56_private *cs35l56)
+static void cs35l56_system_reset(struct cs35l56_private *cs35l56, bool is_soundwire)
 {
-	cs35l56->soft_resetting = true;
-
 	/*
 	 * Must enter cache-only first so there can't be any more register
 	 * accesses other than the controlled system reset sequence below.
@@ -819,7 +817,7 @@ static void cs35l56_system_reset(struct cs35l56_private *cs35l56)
 					ARRAY_SIZE(cs35l56_system_reset_seq));
 
 	/* On SoundWire the registers won't be accessible until it re-enumerates. */
-	if (cs35l56->sdw_peripheral)
+	if (is_soundwire)
 		return;
 
 	usleep_range(CS35L56_CONTROL_PORT_READY_US, CS35L56_CONTROL_PORT_READY_US + 400);
@@ -886,7 +884,8 @@ static void cs35l56_patch(struct cs35l56_private *cs35l56)
 
 	init_completion(&cs35l56->init_completion);
 
-	cs35l56_system_reset(cs35l56);
+	cs35l56->soft_resetting = true;
+	cs35l56_system_reset(cs35l56, !!cs35l56->sdw_peripheral);
 
 	if (cs35l56->sdw_peripheral) {
 		/*
@@ -1550,7 +1549,8 @@ int cs35l56_init(struct cs35l56_private *cs35l56)
 
 	if (!cs35l56->base.reset_gpio) {
 		dev_dbg(cs35l56->base.dev, "No reset gpio: using soft reset\n");
-		cs35l56_system_reset(cs35l56);
+		cs35l56->soft_resetting = true;
+		cs35l56_system_reset(cs35l56, !!cs35l56->sdw_peripheral);
 		if (cs35l56->sdw_peripheral) {
 			/* Keep alive while we wait for re-enumeration */
 			pm_runtime_get_noresume(cs35l56->base.dev);
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 03/13] ASoC: cs35l56: Convert utility functions to use common data structure
  2023-05-25 15:06 [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Richard Fitzgerald
  2023-05-25 15:06 ` [PATCH 01/13] ASoC: cs35l56: Move shared data into a common data structure Richard Fitzgerald
  2023-05-25 15:06 ` [PATCH 02/13] ASoC: cs35l56: Make cs35l56_system_reset() code more generic Richard Fitzgerald
@ 2023-05-25 15:06 ` Richard Fitzgerald
  2023-05-25 15:06 ` [PATCH 04/13] ASoC: cs35l56: Move utility functions to shared file Richard Fitzgerald
                   ` (10 subsequent siblings)
  13 siblings, 0 replies; 20+ messages in thread
From: Richard Fitzgerald @ 2023-05-25 15:06 UTC (permalink / raw)
  To: tiwai, broonie, perex
  Cc: alsa-devel, linux-kernel, patches, Simon Trimmer, Richard Fitzgerald

From: Simon Trimmer <simont@opensource.cirrus.com>

Use the new cs35l56_base struct for utility functions.

Signed-off-by: Simon Trimmer <simont@opensource.cirrus.com>
Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 sound/soc/codecs/cs35l56.c | 32 ++++++++++++++++----------------
 1 file changed, 16 insertions(+), 16 deletions(-)

diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index 76cc9110d4e0..48e856583477 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -805,14 +805,14 @@ static const struct reg_sequence cs35l56_system_reset_seq[] = {
 	REG_SEQ0(CS35L56_DSP_VIRTUAL1_MBOX_1, CS35L56_MBOX_CMD_SYSTEM_RESET),
 };
 
-static void cs35l56_system_reset(struct cs35l56_private *cs35l56, bool is_soundwire)
+static void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire)
 {
 	/*
 	 * Must enter cache-only first so there can't be any more register
 	 * accesses other than the controlled system reset sequence below.
 	 */
-	regcache_cache_only(cs35l56->base.regmap, true);
-	regmap_multi_reg_write_bypassed(cs35l56->base.regmap,
+	regcache_cache_only(cs35l56_base->regmap, true);
+	regmap_multi_reg_write_bypassed(cs35l56_base->regmap,
 					cs35l56_system_reset_seq,
 					ARRAY_SIZE(cs35l56_system_reset_seq));
 
@@ -821,7 +821,7 @@ static void cs35l56_system_reset(struct cs35l56_private *cs35l56, bool is_soundw
 		return;
 
 	usleep_range(CS35L56_CONTROL_PORT_READY_US, CS35L56_CONTROL_PORT_READY_US + 400);
-	regcache_cache_only(cs35l56->base.regmap, false);
+	regcache_cache_only(cs35l56_base->regmap, false);
 }
 
 static void cs35l56_secure_patch(struct cs35l56_private *cs35l56)
@@ -885,7 +885,7 @@ static void cs35l56_patch(struct cs35l56_private *cs35l56)
 	init_completion(&cs35l56->init_completion);
 
 	cs35l56->soft_resetting = true;
-	cs35l56_system_reset(cs35l56, !!cs35l56->sdw_peripheral);
+	cs35l56_system_reset(&cs35l56->base, !!cs35l56->sdw_peripheral);
 
 	if (cs35l56->sdw_peripheral) {
 		/*
@@ -1140,20 +1140,20 @@ int cs35l56_runtime_resume_common(struct cs35l56_private *cs35l56)
 }
 EXPORT_SYMBOL_NS_GPL(cs35l56_runtime_resume_common, SND_SOC_CS35L56_CORE);
 
-static int cs35l56_is_fw_reload_needed(struct cs35l56_private *cs35l56)
+static int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base)
 {
 	unsigned int val;
 	int ret;
 
 	/* Nothing to re-patch if we haven't done any patching yet. */
-	if (!cs35l56->base.fw_patched)
+	if (!cs35l56_base->fw_patched)
 		return false;
 
 	/*
 	 * If we have control of RESET we will have asserted it so the firmware
 	 * will need re-patching.
 	 */
-	if (cs35l56->base.reset_gpio)
+	if (cs35l56_base->reset_gpio)
 		return true;
 
 	/*
@@ -1161,22 +1161,22 @@ static int cs35l56_is_fw_reload_needed(struct cs35l56_private *cs35l56)
 	 * can't be used here to test for memory retention.
 	 * Assume that tuning must be re-loaded.
 	 */
-	if (cs35l56->base.secured)
+	if (cs35l56_base->secured)
 		return true;
 
-	ret = pm_runtime_resume_and_get(cs35l56->base.dev);
+	ret = pm_runtime_resume_and_get(cs35l56_base->dev);
 	if (ret) {
-		dev_err(cs35l56->base.dev, "Failed to runtime_get: %d\n", ret);
+		dev_err(cs35l56_base->dev, "Failed to runtime_get: %d\n", ret);
 		return ret;
 	}
 
-	ret = regmap_read(cs35l56->base.regmap, CS35L56_PROTECTION_STATUS, &val);
+	ret = regmap_read(cs35l56_base->regmap, CS35L56_PROTECTION_STATUS, &val);
 	if (ret)
-		dev_err(cs35l56->base.dev, "Failed to read PROTECTION_STATUS: %d\n", ret);
+		dev_err(cs35l56_base->dev, "Failed to read PROTECTION_STATUS: %d\n", ret);
 	else
 		ret = !!(val & CS35L56_FIRMWARE_MISSING);
 
-	pm_runtime_put_autosuspend(cs35l56->base.dev);
+	pm_runtime_put_autosuspend(cs35l56_base->dev);
 
 	return ret;
 }
@@ -1305,7 +1305,7 @@ int cs35l56_system_resume(struct device *dev)
 	if (!cs35l56->component)
 		return 0;
 
-	ret = cs35l56_is_fw_reload_needed(cs35l56);
+	ret = cs35l56_is_fw_reload_needed(&cs35l56->base);
 	dev_dbg(cs35l56->base.dev, "fw_reload_needed: %d\n", ret);
 	if (ret < 1)
 		return ret;
@@ -1550,7 +1550,7 @@ int cs35l56_init(struct cs35l56_private *cs35l56)
 	if (!cs35l56->base.reset_gpio) {
 		dev_dbg(cs35l56->base.dev, "No reset gpio: using soft reset\n");
 		cs35l56->soft_resetting = true;
-		cs35l56_system_reset(cs35l56, !!cs35l56->sdw_peripheral);
+		cs35l56_system_reset(&cs35l56->base, !!cs35l56->sdw_peripheral);
 		if (cs35l56->sdw_peripheral) {
 			/* Keep alive while we wait for re-enumeration */
 			pm_runtime_get_noresume(cs35l56->base.dev);
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 04/13] ASoC: cs35l56: Move utility functions to shared file
  2023-05-25 15:06 [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Richard Fitzgerald
                   ` (2 preceding siblings ...)
  2023-05-25 15:06 ` [PATCH 03/13] ASoC: cs35l56: Convert utility functions to use common data structure Richard Fitzgerald
@ 2023-05-25 15:06 ` Richard Fitzgerald
  2023-05-25 15:06 ` [PATCH 05/13] ASoC: cs35l56: Move runtime suspend/resume to shared library Richard Fitzgerald
                   ` (9 subsequent siblings)
  13 siblings, 0 replies; 20+ messages in thread
From: Richard Fitzgerald @ 2023-05-25 15:06 UTC (permalink / raw)
  To: tiwai, broonie, perex
  Cc: alsa-devel, linux-kernel, patches, Simon Trimmer, Richard Fitzgerald

From: Simon Trimmer <simont@opensource.cirrus.com>

Move the cs35l56 utility functions into the shared file so they are
available for use in HDA.

Signed-off-by: Simon Trimmer <simont@opensource.cirrus.com>
Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 include/sound/cs35l56.h           |   7 +
 sound/soc/codecs/cs35l56-shared.c | 208 ++++++++++++++++++++++++++++++
 sound/soc/codecs/cs35l56.c        | 203 -----------------------------
 3 files changed, 215 insertions(+), 203 deletions(-)

diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h
index 3a029c6621c9..4d270eb23473 100644
--- a/include/sound/cs35l56.h
+++ b/include/sound/cs35l56.h
@@ -274,6 +274,13 @@ extern const char * const cs35l56_tx_input_texts[CS35L56_NUM_INPUT_SRC];
 extern const unsigned int cs35l56_tx_input_values[CS35L56_NUM_INPUT_SRC];
 
 void cs35l56_reread_firmware_registers(struct cs35l56_base *cs35l56_base);
+int cs35l56_mbox_send(struct cs35l56_base *cs35l56_base, unsigned int command);
+int cs35l56_wait_for_firmware_boot(struct cs35l56_base *cs35l56_base);
+void cs35l56_wait_min_reset_pulse(void);
+void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire);
+int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq);
+irqreturn_t cs35l56_irq(int irq, void *data);
+int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base);
 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 0cbaf8c7b05a..93cd898dbab6 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -200,6 +200,214 @@ void cs35l56_reread_firmware_registers(struct cs35l56_base *cs35l56_base)
 }
 EXPORT_SYMBOL_NS_GPL(cs35l56_reread_firmware_registers, SND_SOC_CS35L56_SHARED);
 
+int cs35l56_mbox_send(struct cs35l56_base *cs35l56_base, unsigned int command)
+{
+	unsigned int val;
+	int ret;
+
+	regmap_write(cs35l56_base->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1, command);
+	ret = regmap_read_poll_timeout(cs35l56_base->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
+				       val, (val == 0),
+				       CS35L56_MBOX_POLL_US, CS35L56_MBOX_TIMEOUT_US);
+	if (ret) {
+		dev_warn(cs35l56_base->dev, "MBOX command %#x failed: %d\n", command, ret);
+		return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_mbox_send, SND_SOC_CS35L56_SHARED);
+
+int cs35l56_wait_for_firmware_boot(struct cs35l56_base *cs35l56_base)
+{
+	unsigned int reg;
+	unsigned int val;
+	int ret;
+
+	if (cs35l56_base->rev < CS35L56_REVID_B0)
+		reg = CS35L56_DSP1_HALO_STATE_A1;
+	else
+		reg = CS35L56_DSP1_HALO_STATE;
+
+	ret = regmap_read_poll_timeout(cs35l56_base->regmap, reg,
+				       val,
+				       (val < 0xFFFF) && (val >= CS35L56_HALO_STATE_BOOT_DONE),
+				       CS35L56_HALO_STATE_POLL_US,
+				       CS35L56_HALO_STATE_TIMEOUT_US);
+
+	if ((ret < 0) && (ret != -ETIMEDOUT)) {
+		dev_err(cs35l56_base->dev, "Failed to read HALO_STATE: %d\n", ret);
+		return ret;
+	}
+
+	if ((ret == -ETIMEDOUT) || (val != CS35L56_HALO_STATE_BOOT_DONE)) {
+		dev_err(cs35l56_base->dev, "Firmware boot fail: HALO_STATE=%#x\n", val);
+		return -EIO;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_wait_for_firmware_boot, SND_SOC_CS35L56_SHARED);
+
+void cs35l56_wait_min_reset_pulse(void)
+{
+	/* Satisfy minimum reset pulse width spec */
+	usleep_range(CS35L56_RESET_PULSE_MIN_US, 2 * CS35L56_RESET_PULSE_MIN_US);
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_wait_min_reset_pulse, SND_SOC_CS35L56_SHARED);
+
+static const struct reg_sequence cs35l56_system_reset_seq[] = {
+	REG_SEQ0(CS35L56_DSP_VIRTUAL1_MBOX_1, CS35L56_MBOX_CMD_SYSTEM_RESET),
+};
+
+void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire)
+{
+	/*
+	 * Must enter cache-only first so there can't be any more register
+	 * accesses other than the controlled system reset sequence below.
+	 */
+	regcache_cache_only(cs35l56_base->regmap, true);
+	regmap_multi_reg_write_bypassed(cs35l56_base->regmap,
+					cs35l56_system_reset_seq,
+					ARRAY_SIZE(cs35l56_system_reset_seq));
+
+	/* On SoundWire the registers won't be accessible until it re-enumerates. */
+	if (is_soundwire)
+		return;
+
+	usleep_range(CS35L56_CONTROL_PORT_READY_US, CS35L56_CONTROL_PORT_READY_US + 400);
+	regcache_cache_only(cs35l56_base->regmap, false);
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_system_reset, SND_SOC_CS35L56_SHARED);
+
+int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq)
+{
+	int ret;
+
+	if (!irq)
+		return 0;
+
+	ret = devm_request_threaded_irq(cs35l56_base->dev, irq, NULL, cs35l56_irq,
+					IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW,
+					"cs35l56", cs35l56_base);
+	if (!ret)
+		cs35l56_base->irq = irq;
+	else
+		dev_err(cs35l56_base->dev, "Failed to get IRQ: %d\n", ret);
+
+	return ret;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_irq_request, SND_SOC_CS35L56_SHARED);
+
+irqreturn_t cs35l56_irq(int irq, void *data)
+{
+	struct cs35l56_base *cs35l56_base = data;
+	unsigned int status1 = 0, status8 = 0, status20 = 0;
+	unsigned int mask1, mask8, mask20;
+	unsigned int val;
+	int rv;
+
+	irqreturn_t ret = IRQ_NONE;
+
+	if (!cs35l56_base->init_done)
+		return IRQ_NONE;
+
+	mutex_lock(&cs35l56_base->irq_lock);
+
+	rv = pm_runtime_resume_and_get(cs35l56_base->dev);
+	if (rv < 0) {
+		dev_err(cs35l56_base->dev, "irq: failed to get pm_runtime: %d\n", rv);
+		goto err_unlock;
+	}
+
+	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_STATUS, &val);
+	if ((val & CS35L56_IRQ1_STS_MASK) == 0) {
+		dev_dbg(cs35l56_base->dev, "Spurious IRQ: no pending interrupt\n");
+		goto err;
+	}
+
+	/* Ack interrupts */
+	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_EINT_1, &status1);
+	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_MASK_1, &mask1);
+	status1 &= ~mask1;
+	regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_EINT_1, status1);
+
+	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_EINT_8, &status8);
+	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_MASK_8, &mask8);
+	status8 &= ~mask8;
+	regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_EINT_8, status8);
+
+	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_EINT_20, &status20);
+	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_MASK_20, &mask20);
+	status20 &= ~mask20;
+	/* We don't want EINT20 but they default to unmasked: force mask */
+	regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_MASK_20, 0xffffffff);
+
+	dev_dbg(cs35l56_base->dev, "%s: %#x %#x\n", __func__, status1, status8);
+
+	/* Check to see if unmasked bits are active */
+	if (!status1 && !status8 && !status20)
+		goto err;
+
+	if (status1 & CS35L56_AMP_SHORT_ERR_EINT1_MASK)
+		dev_crit(cs35l56_base->dev, "Amp short error\n");
+
+	if (status8 & CS35L56_TEMP_ERR_EINT1_MASK)
+		dev_crit(cs35l56_base->dev, "Overtemp error\n");
+
+	ret = IRQ_HANDLED;
+
+err:
+	pm_runtime_put(cs35l56_base->dev);
+err_unlock:
+	mutex_unlock(&cs35l56_base->irq_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_irq, SND_SOC_CS35L56_SHARED);
+
+int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base)
+{
+	unsigned int val;
+	int ret;
+
+	/* Nothing to re-patch if we haven't done any patching yet. */
+	if (!cs35l56_base->fw_patched)
+		return false;
+
+	/*
+	 * If we have control of RESET we will have asserted it so the firmware
+	 * will need re-patching.
+	 */
+	if (cs35l56_base->reset_gpio)
+		return true;
+
+	/*
+	 * In secure mode FIRMWARE_MISSING is cleared by the BIOS loader so
+	 * can't be used here to test for memory retention.
+	 * Assume that tuning must be re-loaded.
+	 */
+	if (cs35l56_base->secured)
+		return true;
+
+	ret = pm_runtime_resume_and_get(cs35l56_base->dev);
+	if (ret) {
+		dev_err(cs35l56_base->dev, "Failed to runtime_get: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_read(cs35l56_base->regmap, CS35L56_PROTECTION_STATUS, &val);
+	if (ret)
+		dev_err(cs35l56_base->dev, "Failed to read PROTECTION_STATUS: %d\n", ret);
+	else
+		ret = !!(val & CS35L56_FIRMWARE_MISSING);
+
+	pm_runtime_put_autosuspend(cs35l56_base->dev);
+
+	return ret;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_is_fw_reload_needed, SND_SOC_CS35L56_SHARED);
+
 const struct cs_dsp_region cs35l56_dsp1_regions[] = {
 	{ .type = WMFW_HALO_PM_PACKED,	.base = CS35L56_DSP1_PMEM_0 },
 	{ .type = WMFW_HALO_XM_PACKED,	.base = CS35L56_DSP1_XMEM_PACKED_0 },
diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index 48e856583477..c1c7b7e408c3 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -34,23 +34,6 @@
 static int cs35l56_dsp_event(struct snd_soc_dapm_widget *w,
 			     struct snd_kcontrol *kcontrol, int event);
 
-static int cs35l56_mbox_send(struct cs35l56_base *cs35l56_base, unsigned int command)
-{
-	unsigned int val;
-	int ret;
-
-	regmap_write(cs35l56_base->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1, command);
-	ret = regmap_read_poll_timeout(cs35l56_base->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
-				       val, (val == 0),
-				       CS35L56_MBOX_POLL_US, CS35L56_MBOX_TIMEOUT_US);
-	if (ret) {
-		dev_warn(cs35l56_base->dev, "MBOX command %#x failed: %d\n", command, ret);
-		return ret;
-	}
-
-	return 0;
-}
-
 static void cs35l56_wait_dsp_ready(struct cs35l56_private *cs35l56)
 {
 	/* Wait for patching to complete */
@@ -315,92 +298,6 @@ static int cs35l56_dsp_event(struct snd_soc_dapm_widget *w,
 	return wm_adsp_event(w, kcontrol, event);
 }
 
-irqreturn_t cs35l56_irq(int irq, void *data)
-{
-	struct cs35l56_base *cs35l56_base = data;
-	unsigned int status1 = 0, status8 = 0, status20 = 0;
-	unsigned int mask1, mask8, mask20;
-	unsigned int val;
-	int rv;
-
-	irqreturn_t ret = IRQ_NONE;
-
-	if (!cs35l56_base->init_done)
-		return IRQ_NONE;
-
-	mutex_lock(&cs35l56_base->irq_lock);
-
-	rv = pm_runtime_resume_and_get(cs35l56_base->dev);
-	if (rv < 0) {
-		dev_err(cs35l56_base->dev, "irq: failed to get pm_runtime: %d\n", rv);
-		goto err_unlock;
-	}
-
-	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_STATUS, &val);
-	if ((val & CS35L56_IRQ1_STS_MASK) == 0) {
-		dev_dbg(cs35l56_base->dev, "Spurious IRQ: no pending interrupt\n");
-		goto err;
-	}
-
-	/* Ack interrupts */
-	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_EINT_1, &status1);
-	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_MASK_1, &mask1);
-	status1 &= ~mask1;
-	regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_EINT_1, status1);
-
-	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_EINT_8, &status8);
-	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_MASK_8, &mask8);
-	status8 &= ~mask8;
-	regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_EINT_8, status8);
-
-	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_EINT_20, &status20);
-	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_MASK_20, &mask20);
-	status20 &= ~mask20;
-	/* We don't want EINT20 but they default to unmasked: force mask */
-	regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_MASK_20, 0xffffffff);
-
-	dev_dbg(cs35l56_base->dev, "%s: %#x %#x\n", __func__, status1, status8);
-
-	/* Check to see if unmasked bits are active */
-	if (!status1 && !status8 && !status20)
-		goto err;
-
-	if (status1 & CS35L56_AMP_SHORT_ERR_EINT1_MASK)
-		dev_crit(cs35l56_base->dev, "Amp short error\n");
-
-	if (status8 & CS35L56_TEMP_ERR_EINT1_MASK)
-		dev_crit(cs35l56_base->dev, "Overtemp error\n");
-
-	ret = IRQ_HANDLED;
-
-err:
-	pm_runtime_put(cs35l56_base->dev);
-err_unlock:
-	mutex_unlock(&cs35l56_base->irq_lock);
-
-	return ret;
-}
-EXPORT_SYMBOL_NS_GPL(cs35l56_irq, SND_SOC_CS35L56_CORE);
-
-int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq)
-{
-	int ret;
-
-	if (!irq)
-		return 0;
-
-	ret = devm_request_threaded_irq(cs35l56_base->dev, irq, NULL, cs35l56_irq,
-					IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW,
-					"cs35l56", cs35l56_base);
-	if (!ret)
-		cs35l56_base->irq = irq;
-	else
-		dev_err(cs35l56_base->dev, "Failed to get IRQ: %d\n", ret);
-
-	return ret;
-}
-EXPORT_SYMBOL_NS_GPL(cs35l56_irq_request, SND_SOC_CS35L56_CORE);
-
 static int cs35l56_asp_dai_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
 {
 	struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(codec_dai->component);
@@ -765,65 +662,6 @@ static struct snd_soc_dai_driver cs35l56_dai[] = {
 	}
 };
 
-static int cs35l56_wait_for_firmware_boot(struct cs35l56_base *cs35l56_base)
-{
-	unsigned int reg;
-	unsigned int val;
-	int ret;
-
-	if (cs35l56_base->rev < CS35L56_REVID_B0)
-		reg = CS35L56_DSP1_HALO_STATE_A1;
-	else
-		reg = CS35L56_DSP1_HALO_STATE;
-
-	ret = regmap_read_poll_timeout(cs35l56_base->regmap, reg,
-				       val,
-				       (val < 0xFFFF) && (val >= CS35L56_HALO_STATE_BOOT_DONE),
-				       CS35L56_HALO_STATE_POLL_US,
-				       CS35L56_HALO_STATE_TIMEOUT_US);
-
-	if ((ret < 0) && (ret != -ETIMEDOUT)) {
-		dev_err(cs35l56_base->dev, "Failed to read HALO_STATE: %d\n", ret);
-		return ret;
-	}
-
-	if ((ret == -ETIMEDOUT) || (val != CS35L56_HALO_STATE_BOOT_DONE)) {
-		dev_err(cs35l56_base->dev, "Firmware boot fail: HALO_STATE=%#x\n", val);
-		return -EIO;
-	}
-
-	return 0;
-}
-
-static inline void cs35l56_wait_min_reset_pulse(void)
-{
-	/* Satisfy minimum reset pulse width spec */
-	usleep_range(CS35L56_RESET_PULSE_MIN_US, 2 * CS35L56_RESET_PULSE_MIN_US);
-}
-
-static const struct reg_sequence cs35l56_system_reset_seq[] = {
-	REG_SEQ0(CS35L56_DSP_VIRTUAL1_MBOX_1, CS35L56_MBOX_CMD_SYSTEM_RESET),
-};
-
-static void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire)
-{
-	/*
-	 * Must enter cache-only first so there can't be any more register
-	 * accesses other than the controlled system reset sequence below.
-	 */
-	regcache_cache_only(cs35l56_base->regmap, true);
-	regmap_multi_reg_write_bypassed(cs35l56_base->regmap,
-					cs35l56_system_reset_seq,
-					ARRAY_SIZE(cs35l56_system_reset_seq));
-
-	/* On SoundWire the registers won't be accessible until it re-enumerates. */
-	if (is_soundwire)
-		return;
-
-	usleep_range(CS35L56_CONTROL_PORT_READY_US, CS35L56_CONTROL_PORT_READY_US + 400);
-	regcache_cache_only(cs35l56_base->regmap, false);
-}
-
 static void cs35l56_secure_patch(struct cs35l56_private *cs35l56)
 {
 	int ret;
@@ -1140,47 +978,6 @@ int cs35l56_runtime_resume_common(struct cs35l56_private *cs35l56)
 }
 EXPORT_SYMBOL_NS_GPL(cs35l56_runtime_resume_common, SND_SOC_CS35L56_CORE);
 
-static int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base)
-{
-	unsigned int val;
-	int ret;
-
-	/* Nothing to re-patch if we haven't done any patching yet. */
-	if (!cs35l56_base->fw_patched)
-		return false;
-
-	/*
-	 * If we have control of RESET we will have asserted it so the firmware
-	 * will need re-patching.
-	 */
-	if (cs35l56_base->reset_gpio)
-		return true;
-
-	/*
-	 * In secure mode FIRMWARE_MISSING is cleared by the BIOS loader so
-	 * can't be used here to test for memory retention.
-	 * Assume that tuning must be re-loaded.
-	 */
-	if (cs35l56_base->secured)
-		return true;
-
-	ret = pm_runtime_resume_and_get(cs35l56_base->dev);
-	if (ret) {
-		dev_err(cs35l56_base->dev, "Failed to runtime_get: %d\n", ret);
-		return ret;
-	}
-
-	ret = regmap_read(cs35l56_base->regmap, CS35L56_PROTECTION_STATUS, &val);
-	if (ret)
-		dev_err(cs35l56_base->dev, "Failed to read PROTECTION_STATUS: %d\n", ret);
-	else
-		ret = !!(val & CS35L56_FIRMWARE_MISSING);
-
-	pm_runtime_put_autosuspend(cs35l56_base->dev);
-
-	return ret;
-}
-
 int cs35l56_system_suspend(struct device *dev)
 {
 	struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 05/13] ASoC: cs35l56: Move runtime suspend/resume to shared library
  2023-05-25 15:06 [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Richard Fitzgerald
                   ` (3 preceding siblings ...)
  2023-05-25 15:06 ` [PATCH 04/13] ASoC: cs35l56: Move utility functions to shared file Richard Fitzgerald
@ 2023-05-25 15:06 ` Richard Fitzgerald
  2023-05-25 15:06 ` [PATCH 06/13] ASoC: cs35l56: Move cs_dsp init into " Richard Fitzgerald
                   ` (8 subsequent siblings)
  13 siblings, 0 replies; 20+ messages in thread
From: Richard Fitzgerald @ 2023-05-25 15:06 UTC (permalink / raw)
  To: tiwai, broonie, perex
  Cc: alsa-devel, linux-kernel, patches, Richard Fitzgerald

The majority of runtime_suspend and runtime_resume handling
doesn't have anything specific to the ASoC driver, so can be
shared by the HDA driver. Move this code into the shared
library.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 include/sound/cs35l56.h           |   2 +
 sound/soc/codecs/cs35l56-sdw.c    |   4 +-
 sound/soc/codecs/cs35l56-shared.c | 118 +++++++++++++++++++++++++++++
 sound/soc/codecs/cs35l56.c        | 120 +-----------------------------
 sound/soc/codecs/cs35l56.h        |   2 -
 5 files changed, 126 insertions(+), 120 deletions(-)

diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h
index 4d270eb23473..4a885c2321c1 100644
--- a/include/sound/cs35l56.h
+++ b/include/sound/cs35l56.h
@@ -281,6 +281,8 @@ void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire);
 int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq);
 irqreturn_t cs35l56_irq(int irq, void *data);
 int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base);
+int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base);
+int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_soundwire);
 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-sdw.c b/sound/soc/codecs/cs35l56-sdw.c
index e6c9e8bce22c..09d255cc7652 100644
--- a/sound/soc/codecs/cs35l56-sdw.c
+++ b/sound/soc/codecs/cs35l56-sdw.c
@@ -424,7 +424,7 @@ static int __maybe_unused cs35l56_sdw_runtime_suspend(struct device *dev)
 	if (!cs35l56->base.init_done)
 		return 0;
 
-	return cs35l56_runtime_suspend(dev);
+	return cs35l56_runtime_suspend_common(&cs35l56->base);
 }
 
 static int __maybe_unused cs35l56_sdw_runtime_resume(struct device *dev)
@@ -441,7 +441,7 @@ static int __maybe_unused cs35l56_sdw_runtime_resume(struct device *dev)
 	if (ret < 0)
 		return ret;
 
-	ret = cs35l56_runtime_resume_common(cs35l56);
+	ret = cs35l56_runtime_resume_common(&cs35l56->base, true);
 	if (ret)
 		return ret;
 
diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c
index 93cd898dbab6..194fa08e1cc2 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -408,6 +408,124 @@ int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base)
 }
 EXPORT_SYMBOL_NS_GPL(cs35l56_is_fw_reload_needed, SND_SOC_CS35L56_SHARED);
 
+static const struct reg_sequence cs35l56_hibernate_seq[] = {
+	/* This must be the last register access */
+	REG_SEQ0(CS35L56_DSP_VIRTUAL1_MBOX_1, CS35L56_MBOX_CMD_HIBERNATE_NOW),
+};
+
+static const struct reg_sequence cs35l56_hibernate_wake_seq[] = {
+	REG_SEQ0(CS35L56_DSP_VIRTUAL1_MBOX_1, CS35L56_MBOX_CMD_WAKEUP),
+};
+
+int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base)
+{
+	unsigned int val;
+	int ret;
+
+	if (!cs35l56_base->init_done)
+		return 0;
+
+	/* Firmware must have entered a power-save state */
+	ret = regmap_read_poll_timeout(cs35l56_base->regmap,
+				       CS35L56_TRANSDUCER_ACTUAL_PS,
+				       val, (val >= CS35L56_PS3),
+				       CS35L56_PS3_POLL_US,
+				       CS35L56_PS3_TIMEOUT_US);
+	if (ret)
+		dev_warn(cs35l56_base->dev, "PS3 wait failed: %d\n", ret);
+
+	/* Clear BOOT_DONE so it can be used to detect a reboot */
+	regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_EINT_4, CS35L56_OTP_BOOT_DONE_MASK);
+
+	if (!cs35l56_base->can_hibernate) {
+		regcache_cache_only(cs35l56_base->regmap, true);
+		dev_dbg(cs35l56_base->dev, "Suspended: no hibernate");
+
+		return 0;
+	}
+
+	/*
+	 * Enable auto-hibernate. If it is woken by some other wake source
+	 * it will automatically return to hibernate.
+	 */
+	cs35l56_mbox_send(cs35l56_base, CS35L56_MBOX_CMD_ALLOW_AUTO_HIBERNATE);
+
+	/*
+	 * Must enter cache-only first so there can't be any more register
+	 * accesses other than the controlled hibernate sequence below.
+	 */
+	regcache_cache_only(cs35l56_base->regmap, true);
+
+	regmap_multi_reg_write_bypassed(cs35l56_base->regmap,
+					cs35l56_hibernate_seq,
+					ARRAY_SIZE(cs35l56_hibernate_seq));
+
+	dev_dbg(cs35l56_base->dev, "Suspended: hibernate");
+
+	return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_runtime_suspend_common, SND_SOC_CS35L56_SHARED);
+
+int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_soundwire)
+{
+	unsigned int val;
+	int ret;
+
+	if (!cs35l56_base->init_done)
+		return 0;
+
+	if (!cs35l56_base->can_hibernate)
+		goto out_sync;
+
+	if (!is_soundwire) {
+		/*
+		 * Dummy transaction to trigger I2C/SPI auto-wake. This will NAK on I2C.
+		 * Must be done before releasing cache-only.
+		 */
+		regmap_multi_reg_write_bypassed(cs35l56_base->regmap,
+						cs35l56_hibernate_wake_seq,
+						ARRAY_SIZE(cs35l56_hibernate_wake_seq));
+
+		usleep_range(CS35L56_CONTROL_PORT_READY_US,
+			     CS35L56_CONTROL_PORT_READY_US + 400);
+	}
+
+out_sync:
+	regcache_cache_only(cs35l56_base->regmap, false);
+
+	ret = cs35l56_wait_for_firmware_boot(cs35l56_base);
+	if (ret) {
+		dev_err(cs35l56_base->dev, "Hibernate wake failed: %d\n", ret);
+		goto err;
+	}
+
+	ret = cs35l56_mbox_send(cs35l56_base, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE);
+	if (ret)
+		goto err;
+
+	/* BOOT_DONE will be 1 if the amp reset */
+	regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_EINT_4, &val);
+	if (val & CS35L56_OTP_BOOT_DONE_MASK) {
+		dev_dbg(cs35l56_base->dev, "Registers reset in suspend\n");
+		regcache_mark_dirty(cs35l56_base->regmap);
+	}
+
+	regcache_sync(cs35l56_base->regmap);
+
+	dev_dbg(cs35l56_base->dev, "Resumed");
+
+	return 0;
+
+err:
+	regmap_write(cs35l56_base->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
+		     CS35L56_MBOX_CMD_HIBERNATE_NOW);
+
+	regcache_cache_only(cs35l56_base->regmap, true);
+
+	return ret;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_runtime_resume_common, SND_SOC_CS35L56_SHARED);
+
 const struct cs_dsp_region cs35l56_dsp1_regions[] = {
 	{ .type = WMFW_HALO_PM_PACKED,	.base = CS35L56_DSP1_PMEM_0 },
 	{ .type = WMFW_HALO_XM_PACKED,	.base = CS35L56_DSP1_XMEM_PACKED_0 },
diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index c1c7b7e408c3..ce8142249f05 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -852,132 +852,20 @@ static const struct snd_soc_component_driver soc_component_dev_cs35l56 = {
 	.suspend_bias_off = 1, /* see cs35l56_system_resume() */
 };
 
-static const struct reg_sequence cs35l56_hibernate_seq[] = {
-	/* This must be the last register access */
-	REG_SEQ0(CS35L56_DSP_VIRTUAL1_MBOX_1, CS35L56_MBOX_CMD_HIBERNATE_NOW),
-};
-
-static const struct reg_sequence cs35l56_hibernate_wake_seq[] = {
-	REG_SEQ0(CS35L56_DSP_VIRTUAL1_MBOX_1, CS35L56_MBOX_CMD_WAKEUP),
-};
-
-int cs35l56_runtime_suspend(struct device *dev)
+static int __maybe_unused cs35l56_runtime_suspend_i2c_spi(struct device *dev)
 {
 	struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
-	unsigned int val;
-	int ret;
 
-	if (!cs35l56->base.init_done)
-		return 0;
-
-	/* Firmware must have entered a power-save state */
-	ret = regmap_read_poll_timeout(cs35l56->base.regmap,
-				       CS35L56_TRANSDUCER_ACTUAL_PS,
-				       val, (val >= CS35L56_PS3),
-				       CS35L56_PS3_POLL_US,
-				       CS35L56_PS3_TIMEOUT_US);
-	if (ret)
-		dev_warn(cs35l56->base.dev, "PS3 wait failed: %d\n", ret);
-
-	/* Clear BOOT_DONE so it can be used to detect a reboot */
-	regmap_write(cs35l56->base.regmap, CS35L56_IRQ1_EINT_4, CS35L56_OTP_BOOT_DONE_MASK);
-
-	if (!cs35l56->base.can_hibernate) {
-		regcache_cache_only(cs35l56->base.regmap, true);
-		dev_dbg(dev, "Suspended: no hibernate");
-
-		return 0;
-	}
-
-	/*
-	 * Enable auto-hibernate. If it is woken by some other wake source
-	 * it will automatically return to hibernate.
-	 */
-	cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_ALLOW_AUTO_HIBERNATE);
-
-	/*
-	 * Must enter cache-only first so there can't be any more register
-	 * accesses other than the controlled hibernate sequence below.
-	 */
-	regcache_cache_only(cs35l56->base.regmap, true);
-
-	regmap_multi_reg_write_bypassed(cs35l56->base.regmap,
-					cs35l56_hibernate_seq,
-					ARRAY_SIZE(cs35l56_hibernate_seq));
-
-	dev_dbg(dev, "Suspended: hibernate");
-
-	return 0;
+	return cs35l56_runtime_suspend_common(&cs35l56->base);
 }
-EXPORT_SYMBOL_NS_GPL(cs35l56_runtime_suspend, SND_SOC_CS35L56_CORE);
 
 static int __maybe_unused cs35l56_runtime_resume_i2c_spi(struct device *dev)
 {
 	struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
 
-	if (!cs35l56->base.init_done)
-		return 0;
-
-	return cs35l56_runtime_resume_common(cs35l56);
+	return cs35l56_runtime_resume_common(&cs35l56->base, false);
 }
 
-int cs35l56_runtime_resume_common(struct cs35l56_private *cs35l56)
-{
-	unsigned int val;
-	int ret;
-
-	if (!cs35l56->base.can_hibernate)
-		goto out_sync;
-
-	if (!cs35l56->sdw_peripheral) {
-		/*
-		 * Dummy transaction to trigger I2C/SPI auto-wake. This will NAK on I2C.
-		 * Must be done before releasing cache-only.
-		 */
-		regmap_multi_reg_write_bypassed(cs35l56->base.regmap,
-						cs35l56_hibernate_wake_seq,
-						ARRAY_SIZE(cs35l56_hibernate_wake_seq));
-
-		usleep_range(CS35L56_CONTROL_PORT_READY_US,
-			     CS35L56_CONTROL_PORT_READY_US + 400);
-	}
-
-out_sync:
-	regcache_cache_only(cs35l56->base.regmap, false);
-
-	ret = cs35l56_wait_for_firmware_boot(&cs35l56->base);
-	if (ret) {
-		dev_err(cs35l56->base.dev, "Hibernate wake failed: %d\n", ret);
-		goto err;
-	}
-
-	ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE);
-	if (ret)
-		goto err;
-
-	/* BOOT_DONE will be 1 if the amp reset */
-	regmap_read(cs35l56->base.regmap, CS35L56_IRQ1_EINT_4, &val);
-	if (val & CS35L56_OTP_BOOT_DONE_MASK) {
-		dev_dbg(cs35l56->base.dev, "Registers reset in suspend\n");
-		regcache_mark_dirty(cs35l56->base.regmap);
-	}
-
-	regcache_sync(cs35l56->base.regmap);
-
-	dev_dbg(cs35l56->base.dev, "Resumed");
-
-	return 0;
-
-err:
-	regmap_write(cs35l56->base.regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
-		     CS35L56_MBOX_CMD_HIBERNATE_NOW);
-
-	regcache_cache_only(cs35l56->base.regmap, true);
-
-	return ret;
-}
-EXPORT_SYMBOL_NS_GPL(cs35l56_runtime_resume_common, SND_SOC_CS35L56_CORE);
-
 int cs35l56_system_suspend(struct device *dev)
 {
 	struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
@@ -1416,7 +1304,7 @@ void cs35l56_remove(struct cs35l56_private *cs35l56)
 EXPORT_SYMBOL_NS_GPL(cs35l56_remove, SND_SOC_CS35L56_CORE);
 
 const struct dev_pm_ops cs35l56_pm_ops_i2c_spi = {
-	SET_RUNTIME_PM_OPS(cs35l56_runtime_suspend, cs35l56_runtime_resume_i2c_spi, NULL)
+	SET_RUNTIME_PM_OPS(cs35l56_runtime_suspend_i2c_spi, cs35l56_runtime_resume_i2c_spi, NULL)
 	SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend, cs35l56_system_resume)
 	LATE_SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend_late, cs35l56_system_resume_early)
 	NOIRQ_SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend_no_irq, cs35l56_system_resume_no_irq)
diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h
index f39f8fa9e37e..8159c3e217d9 100644
--- a/sound/soc/codecs/cs35l56.h
+++ b/sound/soc/codecs/cs35l56.h
@@ -55,8 +55,6 @@ struct cs35l56_private {
 
 extern const struct dev_pm_ops cs35l56_pm_ops_i2c_spi;
 
-int cs35l56_runtime_suspend(struct device *dev);
-int cs35l56_runtime_resume_common(struct cs35l56_private *cs35l56);
 int cs35l56_system_suspend(struct device *dev);
 int cs35l56_system_suspend_late(struct device *dev);
 int cs35l56_system_suspend_no_irq(struct device *dev);
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 06/13] ASoC: cs35l56: Move cs_dsp init into shared library
  2023-05-25 15:06 [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Richard Fitzgerald
                   ` (4 preceding siblings ...)
  2023-05-25 15:06 ` [PATCH 05/13] ASoC: cs35l56: Move runtime suspend/resume to shared library Richard Fitzgerald
@ 2023-05-25 15:06 ` Richard Fitzgerald
  2023-05-25 15:06 ` [PATCH 07/13] ASoC: cs35l56: Move part of cs35l56_init() to " Richard Fitzgerald
                   ` (7 subsequent siblings)
  13 siblings, 0 replies; 20+ messages in thread
From: Richard Fitzgerald @ 2023-05-25 15:06 UTC (permalink / raw)
  To: tiwai, broonie, perex
  Cc: alsa-devel, linux-kernel, patches, Richard Fitzgerald

Move the code that initialized the struct cs_dsp members
into the shared library so that the HDA driver can use it.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 include/sound/cs35l56.h           |  2 +-
 sound/soc/codecs/cs35l56-shared.c | 18 ++++++++++++++++--
 sound/soc/codecs/cs35l56.c        | 11 +----------
 3 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h
index 4a885c2321c1..e97c7ccfc051 100644
--- a/include/sound/cs35l56.h
+++ b/include/sound/cs35l56.h
@@ -269,7 +269,6 @@ extern struct regmap_config cs35l56_regmap_i2c;
 extern struct regmap_config cs35l56_regmap_spi;
 extern struct regmap_config cs35l56_regmap_sdw;
 
-extern const struct cs_dsp_region cs35l56_dsp1_regions[CS35L56_NUM_DSP_REGIONS];
 extern const char * const cs35l56_tx_input_texts[CS35L56_NUM_INPUT_SRC];
 extern const unsigned int cs35l56_tx_input_values[CS35L56_NUM_INPUT_SRC];
 
@@ -283,6 +282,7 @@ irqreturn_t cs35l56_irq(int irq, void *data);
 int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base);
 int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base);
 int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_soundwire);
+void cs35l56_init_cs_dsp(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_dsp);
 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 194fa08e1cc2..82e5edef1b3f 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -526,14 +526,28 @@ int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_sou
 }
 EXPORT_SYMBOL_NS_GPL(cs35l56_runtime_resume_common, SND_SOC_CS35L56_SHARED);
 
-const struct cs_dsp_region cs35l56_dsp1_regions[] = {
+static const struct cs_dsp_region cs35l56_dsp1_regions[] = {
 	{ .type = WMFW_HALO_PM_PACKED,	.base = CS35L56_DSP1_PMEM_0 },
 	{ .type = WMFW_HALO_XM_PACKED,	.base = CS35L56_DSP1_XMEM_PACKED_0 },
 	{ .type = WMFW_HALO_YM_PACKED,	.base = CS35L56_DSP1_YMEM_PACKED_0 },
 	{ .type = WMFW_ADSP2_XM,	.base = CS35L56_DSP1_XMEM_UNPACKED24_0 },
 	{ .type = WMFW_ADSP2_YM,	.base = CS35L56_DSP1_YMEM_UNPACKED24_0 },
 };
-EXPORT_SYMBOL_NS_GPL(cs35l56_dsp1_regions, SND_SOC_CS35L56_SHARED);
+
+void cs35l56_init_cs_dsp(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_dsp)
+{
+	cs_dsp->num = 1;
+	cs_dsp->type = WMFW_HALO;
+	cs_dsp->rev = 0;
+	cs_dsp->dev = cs35l56_base->dev;
+	cs_dsp->regmap = cs35l56_base->regmap;
+	cs_dsp->base = CS35L56_DSP1_CORE_BASE;
+	cs_dsp->base_sysinfo = CS35L56_DSP1_SYS_INFO_ID;
+	cs_dsp->mem = cs35l56_dsp1_regions;
+	cs_dsp->num_mems = ARRAY_SIZE(cs35l56_dsp1_regions);
+	cs_dsp->no_core_startstop = true;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_init_cs_dsp, SND_SOC_CS35L56_SHARED);
 
 static const u32 cs35l56_bclk_valid_for_pll_freq_table[] = {
 	[0x0C] = 128000,
diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index ce8142249f05..8b9b5822d515 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -1019,18 +1019,9 @@ static int cs35l56_dsp_init(struct cs35l56_private *cs35l56)
 	INIT_WORK(&cs35l56->dsp_work, cs35l56_dsp_work);
 
 	dsp = &cs35l56->dsp;
+	cs35l56_init_cs_dsp(&cs35l56->base, &dsp->cs_dsp);
 	dsp->part = "cs35l56";
-	dsp->cs_dsp.num = 1;
-	dsp->cs_dsp.type = WMFW_HALO;
-	dsp->cs_dsp.rev = 0;
 	dsp->fw = 12;
-	dsp->cs_dsp.dev = cs35l56->base.dev;
-	dsp->cs_dsp.regmap = cs35l56->base.regmap;
-	dsp->cs_dsp.base = CS35L56_DSP1_CORE_BASE;
-	dsp->cs_dsp.base_sysinfo = CS35L56_DSP1_SYS_INFO_ID;
-	dsp->cs_dsp.mem = cs35l56_dsp1_regions;
-	dsp->cs_dsp.num_mems = ARRAY_SIZE(cs35l56_dsp1_regions);
-	dsp->cs_dsp.no_core_startstop = true;
 	dsp->wmfw_optional = true;
 
 	dev_dbg(cs35l56->base.dev, "DSP system name: '%s'\n", dsp->system_name);
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 07/13] ASoC: cs35l56: Move part of cs35l56_init() to shared library
  2023-05-25 15:06 [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Richard Fitzgerald
                   ` (5 preceding siblings ...)
  2023-05-25 15:06 ` [PATCH 06/13] ASoC: cs35l56: Move cs_dsp init into " Richard Fitzgerald
@ 2023-05-25 15:06 ` Richard Fitzgerald
  2023-05-25 15:06 ` [PATCH 08/13] ASoC: cs35l56: Pass correct pointer to cs35l56_irq() Richard Fitzgerald
                   ` (6 subsequent siblings)
  13 siblings, 0 replies; 20+ messages in thread
From: Richard Fitzgerald @ 2023-05-25 15:06 UTC (permalink / raw)
  To: tiwai, broonie, perex
  Cc: alsa-devel, linux-kernel, patches, Richard Fitzgerald

Part of the initialization code in cs35l56_init() can be re-used
by the HDA driver so move it into a new function in the shared
library.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 include/sound/cs35l56.h           |  1 +
 sound/soc/codecs/cs35l56-shared.c | 79 +++++++++++++++++++++++++++++++
 sound/soc/codecs/cs35l56.c        | 71 +--------------------------
 3 files changed, 82 insertions(+), 69 deletions(-)

diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h
index e97c7ccfc051..489a61f84325 100644
--- a/include/sound/cs35l56.h
+++ b/include/sound/cs35l56.h
@@ -283,6 +283,7 @@ int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base);
 int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base);
 int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_soundwire);
 void cs35l56_init_cs_dsp(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_dsp);
+int cs35l56_hw_init(struct cs35l56_base *cs35l56_base);
 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 82e5edef1b3f..e3b935bd9037 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -549,6 +549,85 @@ void cs35l56_init_cs_dsp(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_ds
 }
 EXPORT_SYMBOL_NS_GPL(cs35l56_init_cs_dsp, SND_SOC_CS35L56_SHARED);
 
+int cs35l56_hw_init(struct cs35l56_base *cs35l56_base)
+{
+	int ret;
+	unsigned int devid, revid, otpid, secured;
+
+	/*
+	 * If the system is not using a reset_gpio then issue a
+	 * dummy read to force a wakeup.
+	 */
+	if (!cs35l56_base->reset_gpio)
+		regmap_read(cs35l56_base->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1, &devid);
+
+	/* Wait for control port to be ready (datasheet tIRS). */
+	usleep_range(CS35L56_CONTROL_PORT_READY_US,
+		     CS35L56_CONTROL_PORT_READY_US + 400);
+
+	/*
+	 * The HALO_STATE register is in different locations on Ax and B0
+	 * devices so the REVID needs to be determined before waiting for the
+	 * firmware to boot.
+	 */
+	ret = regmap_read(cs35l56_base->regmap, CS35L56_REVID, &revid);
+	if (ret < 0) {
+		dev_err(cs35l56_base->dev, "Get Revision ID failed\n");
+		return ret;
+	}
+	cs35l56_base->rev = revid & (CS35L56_AREVID_MASK | CS35L56_MTLREVID_MASK);
+
+	ret = cs35l56_wait_for_firmware_boot(cs35l56_base);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(cs35l56_base->regmap, CS35L56_DEVID, &devid);
+	if (ret < 0) {
+		dev_err(cs35l56_base->dev, "Get Device ID failed\n");
+		return ret;
+	}
+	devid &= CS35L56_DEVID_MASK;
+
+	switch (devid) {
+	case 0x35A56:
+		break;
+	default:
+		dev_err(cs35l56_base->dev, "Unknown device %x\n", devid);
+		return ret;
+	}
+
+	ret = regmap_read(cs35l56_base->regmap, CS35L56_DSP_RESTRICT_STS1, &secured);
+	if (ret) {
+		dev_err(cs35l56_base->dev, "Get Secure status failed\n");
+		return ret;
+	}
+
+	/* When any bus is restricted treat the device as secured */
+	if (secured & CS35L56_RESTRICTED_MASK)
+		cs35l56_base->secured = true;
+
+	ret = regmap_read(cs35l56_base->regmap, CS35L56_OTPID, &otpid);
+	if (ret < 0) {
+		dev_err(cs35l56_base->dev, "Get OTP ID failed\n");
+		return ret;
+	}
+
+	dev_info(cs35l56_base->dev, "Cirrus Logic CS35L56%s Rev %02X OTP%d\n",
+		 cs35l56_base->secured ? "s" : "", cs35l56_base->rev, otpid);
+
+	/* Wake source and *_BLOCKED interrupts default to unmasked, so mask them */
+	regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_MASK_20, 0xffffffff);
+	regmap_update_bits(cs35l56_base->regmap, CS35L56_IRQ1_MASK_1,
+			   CS35L56_AMP_SHORT_ERR_EINT1_MASK,
+			   0);
+	regmap_update_bits(cs35l56_base->regmap, CS35L56_IRQ1_MASK_8,
+			   CS35L56_TEMP_ERR_EINT1_MASK,
+			   0);
+
+	return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_hw_init, 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 8b9b5822d515..f472bde6d21a 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -1130,7 +1130,6 @@ EXPORT_SYMBOL_NS_GPL(cs35l56_common_probe, SND_SOC_CS35L56_CORE);
 int cs35l56_init(struct cs35l56_private *cs35l56)
 {
 	int ret;
-	unsigned int devid, revid, otpid, secured;
 
 	/*
 	 * Check whether the actions associated with soft reset or one time
@@ -1147,66 +1146,9 @@ int cs35l56_init(struct cs35l56_private *cs35l56)
 	pm_runtime_set_active(cs35l56->base.dev);
 	pm_runtime_enable(cs35l56->base.dev);
 
-	/*
-	 * If the system is not using a reset_gpio then issue a
-	 * dummy read to force a wakeup.
-	 */
-	if (!cs35l56->base.reset_gpio)
-		regmap_read(cs35l56->base.regmap, CS35L56_DSP_VIRTUAL1_MBOX_1, &devid);
-
-	/* Wait for control port to be ready (datasheet tIRS). */
-	usleep_range(CS35L56_CONTROL_PORT_READY_US,
-		     CS35L56_CONTROL_PORT_READY_US + 400);
-
-	/*
-	 * The HALO_STATE register is in different locations on Ax and B0
-	 * devices so the REVID needs to be determined before waiting for the
-	 * firmware to boot.
-	 */
-	ret = regmap_read(cs35l56->base.regmap, CS35L56_REVID, &revid);
-	if (ret < 0) {
-		dev_err(cs35l56->base.dev, "Get Revision ID failed\n");
+	ret = cs35l56_hw_init(&cs35l56->base);
+	if (ret < 0)
 		return ret;
-	}
-	cs35l56->base.rev = revid & (CS35L56_AREVID_MASK | CS35L56_MTLREVID_MASK);
-
-	ret = cs35l56_wait_for_firmware_boot(&cs35l56->base);
-	if (ret)
-		return ret;
-
-	ret = regmap_read(cs35l56->base.regmap, CS35L56_DEVID, &devid);
-	if (ret < 0) {
-		dev_err(cs35l56->base.dev, "Get Device ID failed\n");
-		return ret;
-	}
-	devid &= CS35L56_DEVID_MASK;
-
-	switch (devid) {
-	case 0x35A56:
-		break;
-	default:
-		dev_err(cs35l56->base.dev, "Unknown device %x\n", devid);
-		return ret;
-	}
-
-	ret = regmap_read(cs35l56->base.regmap, CS35L56_DSP_RESTRICT_STS1, &secured);
-	if (ret) {
-		dev_err(cs35l56->base.dev, "Get Secure status failed\n");
-		return ret;
-	}
-
-	/* When any bus is restricted treat the device as secured */
-	if (secured & CS35L56_RESTRICTED_MASK)
-		cs35l56->base.secured = true;
-
-	ret = regmap_read(cs35l56->base.regmap, CS35L56_OTPID, &otpid);
-	if (ret < 0) {
-		dev_err(cs35l56->base.dev, "Get OTP ID failed\n");
-		return ret;
-	}
-
-	dev_info(cs35l56->base.dev, "Cirrus Logic CS35L56%s Rev %02X OTP%d\n",
-		 cs35l56->base.secured ? "s" : "", cs35l56->base.rev, otpid);
 
 	/* Populate the DSP information with the revision and security state */
 	cs35l56->dsp.part = devm_kasprintf(cs35l56->base.dev, GFP_KERNEL, "cs35l56%s-%02x",
@@ -1214,15 +1156,6 @@ int cs35l56_init(struct cs35l56_private *cs35l56)
 	if (!cs35l56->dsp.part)
 		return -ENOMEM;
 
-	/* Wake source and *_BLOCKED interrupts default to unmasked, so mask them */
-	regmap_write(cs35l56->base.regmap, CS35L56_IRQ1_MASK_20, 0xffffffff);
-	regmap_update_bits(cs35l56->base.regmap, CS35L56_IRQ1_MASK_1,
-			   CS35L56_AMP_SHORT_ERR_EINT1_MASK,
-			   0);
-	regmap_update_bits(cs35l56->base.regmap, CS35L56_IRQ1_MASK_8,
-			   CS35L56_TEMP_ERR_EINT1_MASK,
-			   0);
-
 	if (!cs35l56->base.reset_gpio) {
 		dev_dbg(cs35l56->base.dev, "No reset gpio: using soft reset\n");
 		cs35l56->soft_resetting = true;
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 08/13] ASoC: cs35l56: Pass correct pointer to cs35l56_irq()
  2023-05-25 15:06 [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Richard Fitzgerald
                   ` (6 preceding siblings ...)
  2023-05-25 15:06 ` [PATCH 07/13] ASoC: cs35l56: Move part of cs35l56_init() to " Richard Fitzgerald
@ 2023-05-25 15:06 ` Richard Fitzgerald
  2023-05-25 15:14   ` Mark Brown
  2023-05-25 15:06 ` [PATCH 09/13] ASoC: cs35l56: Make common function for control port wait Richard Fitzgerald
                   ` (5 subsequent siblings)
  13 siblings, 1 reply; 20+ messages in thread
From: Richard Fitzgerald @ 2023-05-25 15:06 UTC (permalink / raw)
  To: tiwai, broonie, perex
  Cc: alsa-devel, linux-kernel, patches, Richard Fitzgerald

cs35l56_irq() was changed to take a cs35l56_base* but the code
in cs35l56-sdw was still passing it a cs35l56_private*.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 sound/soc/codecs/cs35l56-sdw.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c
index 09d255cc7652..b433266b7844 100644
--- a/sound/soc/codecs/cs35l56-sdw.c
+++ b/sound/soc/codecs/cs35l56-sdw.c
@@ -230,7 +230,7 @@ static void cs35l56_sdw_irq_work(struct work_struct *work)
 						       struct cs35l56_private,
 						       sdw_irq_work);
 
-	cs35l56_irq(-1, cs35l56);
+	cs35l56_irq(-1, &cs35l56->base);
 
 	/* unmask interrupts */
 	if (!cs35l56->sdw_irq_no_unmask)
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 09/13] ASoC: cs35l56: Make common function for control port wait
  2023-05-25 15:06 [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Richard Fitzgerald
                   ` (7 preceding siblings ...)
  2023-05-25 15:06 ` [PATCH 08/13] ASoC: cs35l56: Pass correct pointer to cs35l56_irq() Richard Fitzgerald
@ 2023-05-25 15:06 ` Richard Fitzgerald
  2023-05-25 15:06 ` [PATCH 10/13] ASoC: cs35l56: Make a common function to shutdown the DSP Richard Fitzgerald
                   ` (4 subsequent siblings)
  13 siblings, 0 replies; 20+ messages in thread
From: Richard Fitzgerald @ 2023-05-25 15:06 UTC (permalink / raw)
  To: tiwai, broonie, perex
  Cc: alsa-devel, linux-kernel, patches, Simon Trimmer, Richard Fitzgerald

From: Simon Trimmer <simont@opensource.cirrus.com>

Move the waits for CS35L56_CONTROL_PORT_READY_US into a common
function, and also allow a wider range of allowed wait times.

Signed-off-by: Simon Trimmer <simont@opensource.cirrus.com>
Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 include/sound/cs35l56.h           |  1 +
 sound/soc/codecs/cs35l56-shared.c | 16 ++++++++++------
 2 files changed, 11 insertions(+), 6 deletions(-)

diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h
index 489a61f84325..ae9e8f55962a 100644
--- a/include/sound/cs35l56.h
+++ b/include/sound/cs35l56.h
@@ -275,6 +275,7 @@ extern const unsigned int cs35l56_tx_input_values[CS35L56_NUM_INPUT_SRC];
 void cs35l56_reread_firmware_registers(struct cs35l56_base *cs35l56_base);
 int cs35l56_mbox_send(struct cs35l56_base *cs35l56_base, unsigned int command);
 int cs35l56_wait_for_firmware_boot(struct cs35l56_base *cs35l56_base);
+void cs35l56_wait_control_port_ready(void);
 void cs35l56_wait_min_reset_pulse(void);
 void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire);
 int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq);
diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c
index e3b935bd9037..7e02d023338c 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -249,6 +249,13 @@ int cs35l56_wait_for_firmware_boot(struct cs35l56_base *cs35l56_base)
 }
 EXPORT_SYMBOL_NS_GPL(cs35l56_wait_for_firmware_boot, SND_SOC_CS35L56_SHARED);
 
+void cs35l56_wait_control_port_ready(void)
+{
+	/* Wait for control port to be ready (datasheet tIRS). */
+	usleep_range(CS35L56_CONTROL_PORT_READY_US, 2 * CS35L56_CONTROL_PORT_READY_US);
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_wait_control_port_ready, SND_SOC_CS35L56_SHARED);
+
 void cs35l56_wait_min_reset_pulse(void)
 {
 	/* Satisfy minimum reset pulse width spec */
@@ -275,7 +282,7 @@ void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire)
 	if (is_soundwire)
 		return;
 
-	usleep_range(CS35L56_CONTROL_PORT_READY_US, CS35L56_CONTROL_PORT_READY_US + 400);
+	cs35l56_wait_control_port_ready();
 	regcache_cache_only(cs35l56_base->regmap, false);
 }
 EXPORT_SYMBOL_NS_GPL(cs35l56_system_reset, SND_SOC_CS35L56_SHARED);
@@ -486,8 +493,7 @@ int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_sou
 						cs35l56_hibernate_wake_seq,
 						ARRAY_SIZE(cs35l56_hibernate_wake_seq));
 
-		usleep_range(CS35L56_CONTROL_PORT_READY_US,
-			     CS35L56_CONTROL_PORT_READY_US + 400);
+		cs35l56_wait_control_port_ready();
 	}
 
 out_sync:
@@ -561,9 +567,7 @@ int cs35l56_hw_init(struct cs35l56_base *cs35l56_base)
 	if (!cs35l56_base->reset_gpio)
 		regmap_read(cs35l56_base->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1, &devid);
 
-	/* Wait for control port to be ready (datasheet tIRS). */
-	usleep_range(CS35L56_CONTROL_PORT_READY_US,
-		     CS35L56_CONTROL_PORT_READY_US + 400);
+	cs35l56_wait_control_port_ready();
 
 	/*
 	 * The HALO_STATE register is in different locations on Ax and B0
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 10/13] ASoC: cs35l56: Make a common function to shutdown the DSP
  2023-05-25 15:06 [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Richard Fitzgerald
                   ` (8 preceding siblings ...)
  2023-05-25 15:06 ` [PATCH 09/13] ASoC: cs35l56: Make common function for control port wait Richard Fitzgerald
@ 2023-05-25 15:06 ` Richard Fitzgerald
  2023-05-25 15:06 ` [PATCH 11/13] ALSA: hda: Fix missing header dependencies Richard Fitzgerald
                   ` (3 subsequent siblings)
  13 siblings, 0 replies; 20+ messages in thread
From: Richard Fitzgerald @ 2023-05-25 15:06 UTC (permalink / raw)
  To: tiwai, broonie, perex
  Cc: alsa-devel, linux-kernel, patches, Simon Trimmer, Richard Fitzgerald

From: Simon Trimmer <simont@opensource.cirrus.com>

Move issuing of a CS35L56_MBOX_CMD_SHUTDOWN command and then waiting for
the DSP to reach CS35L56_HALO_STATE_SHUTDOWN in the register appropriate
for the hardware revision into a common function.

Signed-off-by: Simon Trimmer <simont@opensource.cirrus.com>
Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 include/sound/cs35l56.h           |  1 +
 sound/soc/codecs/cs35l56-shared.c | 26 ++++++++++++++++++++++++++
 sound/soc/codecs/cs35l56.c        | 17 +----------------
 3 files changed, 28 insertions(+), 16 deletions(-)

diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h
index ae9e8f55962a..5b32aa7468cb 100644
--- a/include/sound/cs35l56.h
+++ b/include/sound/cs35l56.h
@@ -274,6 +274,7 @@ extern const unsigned int cs35l56_tx_input_values[CS35L56_NUM_INPUT_SRC];
 
 void cs35l56_reread_firmware_registers(struct cs35l56_base *cs35l56_base);
 int cs35l56_mbox_send(struct cs35l56_base *cs35l56_base, unsigned int command);
+int cs35l56_firmware_shutdown(struct cs35l56_base *cs35l56_base);
 int cs35l56_wait_for_firmware_boot(struct cs35l56_base *cs35l56_base);
 void cs35l56_wait_control_port_ready(void);
 void cs35l56_wait_min_reset_pulse(void);
diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c
index 7e02d023338c..95ef71c7b8f1 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -218,6 +218,32 @@ int cs35l56_mbox_send(struct cs35l56_base *cs35l56_base, unsigned int command)
 }
 EXPORT_SYMBOL_NS_GPL(cs35l56_mbox_send, SND_SOC_CS35L56_SHARED);
 
+int cs35l56_firmware_shutdown(struct cs35l56_base *cs35l56_base)
+{
+	int ret;
+	unsigned int reg;
+	unsigned int val;
+
+	ret = cs35l56_mbox_send(cs35l56_base, CS35L56_MBOX_CMD_SHUTDOWN);
+	if (ret)
+		return ret;
+
+	if (cs35l56_base->rev < CS35L56_REVID_B0)
+		reg = CS35L56_DSP1_PM_CUR_STATE_A1;
+	else
+		reg = CS35L56_DSP1_PM_CUR_STATE;
+
+	ret = regmap_read_poll_timeout(cs35l56_base->regmap,  reg,
+				       val, (val == CS35L56_HALO_STATE_SHUTDOWN),
+				       CS35L56_HALO_STATE_POLL_US,
+				       CS35L56_HALO_STATE_TIMEOUT_US);
+	if (ret < 0)
+		dev_err(cs35l56_base->dev, "Failed to poll PM_CUR_STATE to 1 is %d (ret %d)\n",
+			val, ret);
+	return ret;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_firmware_shutdown, SND_SOC_CS35L56_SHARED);
+
 int cs35l56_wait_for_firmware_boot(struct cs35l56_base *cs35l56_base)
 {
 	unsigned int reg;
diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index f472bde6d21a..dbe9134ae09a 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -676,8 +676,6 @@ static void cs35l56_secure_patch(struct cs35l56_private *cs35l56)
 
 static void cs35l56_patch(struct cs35l56_private *cs35l56)
 {
-	unsigned int reg;
-	unsigned int val;
 	int ret;
 
 	/*
@@ -694,23 +692,10 @@ static void cs35l56_patch(struct cs35l56_private *cs35l56)
 		flush_work(&cs35l56->sdw_irq_work);
 	}
 
-	ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_SHUTDOWN);
+	ret = cs35l56_firmware_shutdown(&cs35l56->base);
 	if (ret)
 		goto err;
 
-	if (cs35l56->base.rev < CS35L56_REVID_B0)
-		reg = CS35L56_DSP1_PM_CUR_STATE_A1;
-	else
-		reg = CS35L56_DSP1_PM_CUR_STATE;
-
-	ret = regmap_read_poll_timeout(cs35l56->base.regmap, reg,
-				       val, (val == CS35L56_HALO_STATE_SHUTDOWN),
-				       CS35L56_HALO_STATE_POLL_US,
-				       CS35L56_HALO_STATE_TIMEOUT_US);
-	if (ret < 0)
-		dev_err(cs35l56->base.dev, "Failed to poll PM_CUR_STATE to 1 is %d (ret %d)\n",
-			val, ret);
-
 	/* Use wm_adsp to load and apply the firmware patch and coefficient files */
 	ret = wm_adsp_power_up(&cs35l56->dsp);
 	if (ret) {
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 11/13] ALSA: hda: Fix missing header dependencies
  2023-05-25 15:06 [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Richard Fitzgerald
                   ` (9 preceding siblings ...)
  2023-05-25 15:06 ` [PATCH 10/13] ASoC: cs35l56: Make a common function to shutdown the DSP Richard Fitzgerald
@ 2023-05-25 15:06 ` Richard Fitzgerald
  2023-05-25 15:06 ` [PATCH 12/13] ALSA: hda: Add mute_hook to hda_component Richard Fitzgerald
                   ` (2 subsequent siblings)
  13 siblings, 0 replies; 20+ messages in thread
From: Richard Fitzgerald @ 2023-05-25 15:06 UTC (permalink / raw)
  To: tiwai, broonie, perex
  Cc: alsa-devel, linux-kernel, patches, Richard Fitzgerald

Add #includes of dependencies into hda_auto_parser.h and hda_generic.h

hda_auto_parser.h uses definitions in hda_local.h.

hda_generic.h uses definitions in hda_local.h and hda_auto_parser.h.
It also references struct hda_jack_callback, but only as a pointer.
This has been forward-declared so hda_jack.h only needs to be
included in source that actually uses it.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 sound/pci/hda/hda_auto_parser.h | 2 ++
 sound/pci/hda/hda_generic.h     | 3 +++
 2 files changed, 5 insertions(+)

diff --git a/sound/pci/hda/hda_auto_parser.h b/sound/pci/hda/hda_auto_parser.h
index df63d66af1ab..579b11beac71 100644
--- a/sound/pci/hda/hda_auto_parser.h
+++ b/sound/pci/hda/hda_auto_parser.h
@@ -8,6 +8,8 @@
 #ifndef __SOUND_HDA_AUTO_PARSER_H
 #define __SOUND_HDA_AUTO_PARSER_H
 
+#include "hda_local.h"
+
 /*
  * Helper for automatic pin configuration
  */
diff --git a/sound/pci/hda/hda_generic.h b/sound/pci/hda/hda_generic.h
index 34eba40cc6e6..a8eea8367629 100644
--- a/sound/pci/hda/hda_generic.h
+++ b/sound/pci/hda/hda_generic.h
@@ -9,6 +9,9 @@
 #define __SOUND_HDA_GENERIC_H
 
 #include <linux/leds.h>
+#include "hda_auto_parser.h"
+
+struct hda_jack_callback;
 
 /* table entry for multi-io paths */
 struct hda_multi_io {
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 12/13] ALSA: hda: Add mute_hook to hda_component
  2023-05-25 15:06 [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Richard Fitzgerald
                   ` (10 preceding siblings ...)
  2023-05-25 15:06 ` [PATCH 11/13] ALSA: hda: Fix missing header dependencies Richard Fitzgerald
@ 2023-05-25 15:06 ` Richard Fitzgerald
  2023-05-25 15:06 ` [PATCH 13/13] ALSA: hda/cs35l56: Add driver for Cirrus Logic CS35L56 amplifier Richard Fitzgerald
  2023-05-25 15:43 ` [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Mark Brown
  13 siblings, 0 replies; 20+ messages in thread
From: Richard Fitzgerald @ 2023-05-25 15:06 UTC (permalink / raw)
  To: tiwai, broonie, perex
  Cc: alsa-devel, linux-kernel, patches, Simon Trimmer, Richard Fitzgerald

From: Simon Trimmer <simont@opensource.cirrus.com>

Add a hook into hda_component to allow the amplifier mute to be
controlled by the owning HDA codec driver.

Signed-off-by: Simon Trimmer <simont@opensource.cirrus.com>
Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 sound/pci/hda/hda_component.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/sound/pci/hda/hda_component.h b/sound/pci/hda/hda_component.h
index 534e845b9cd1..2a2dfed3aed4 100644
--- a/sound/pci/hda/hda_component.h
+++ b/sound/pci/hda/hda_component.h
@@ -16,4 +16,5 @@ struct hda_component {
 	char name[HDA_MAX_NAME_SIZE];
 	struct hda_codec *codec;
 	void (*playback_hook)(struct device *dev, int action);
+	void (*mute_hook)(struct device *dev, bool mute);
 };
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 13/13] ALSA: hda/cs35l56: Add driver for Cirrus Logic CS35L56 amplifier
  2023-05-25 15:06 [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Richard Fitzgerald
                   ` (11 preceding siblings ...)
  2023-05-25 15:06 ` [PATCH 12/13] ALSA: hda: Add mute_hook to hda_component Richard Fitzgerald
@ 2023-05-25 15:06 ` Richard Fitzgerald
  2023-05-26  4:40   ` Claudiu.Beznea
  2023-05-25 15:43 ` [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Mark Brown
  13 siblings, 1 reply; 20+ messages in thread
From: Richard Fitzgerald @ 2023-05-25 15:06 UTC (permalink / raw)
  To: tiwai, broonie, perex
  Cc: alsa-devel, linux-kernel, patches, Simon Trimmer, Richard Fitzgerald

From: Simon Trimmer <simont@opensource.cirrus.com>

Add a driver for the Cirrus Logic CS35L56 amplifier. This uses the same
component binding API as the CS35L41 driver. This is not a standalone
HDA device; it provides control of the CS35L56 for systems that use a
combination of an HDA codec and CS35L56 amplifiers with audio routed
through the HDA codec.

The CS35L56 combines a high-performance mono audio amplifier, Class-H
tracking inductive boost converter, Halo Core(TM) DSP and a DC-DC boost
converter supporting Class-H tracking.

Control interfaces are I2C or SPI through the standard Linux I2C or SPI
bus framework.

Most chip functionality is controlled by on-board ROM firmware that is
always running. Firmware patches can be applied by the driver in the form
of a .wmfw file (firmware patch) and/or a .bin file (system tuning).

Signed-off-by: Simon Trimmer <simont@opensource.cirrus.com>
Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 sound/pci/hda/Kconfig           |  31 +
 sound/pci/hda/Makefile          |   6 +
 sound/pci/hda/cs35l56_hda.c     | 995 ++++++++++++++++++++++++++++++++
 sound/pci/hda/cs35l56_hda.h     |  48 ++
 sound/pci/hda/cs35l56_hda_i2c.c |  69 +++
 sound/pci/hda/cs35l56_hda_spi.c |  68 +++
 6 files changed, 1217 insertions(+)
 create mode 100644 sound/pci/hda/cs35l56_hda.c
 create mode 100644 sound/pci/hda/cs35l56_hda.h
 create mode 100644 sound/pci/hda/cs35l56_hda_i2c.c
 create mode 100644 sound/pci/hda/cs35l56_hda_spi.c

diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig
index 886255a03e8b..0f2e941ce646 100644
--- a/sound/pci/hda/Kconfig
+++ b/sound/pci/hda/Kconfig
@@ -130,6 +130,37 @@ config SND_HDA_SCODEC_CS35L41_SPI
 comment "Set to Y if you want auto-loading the side codec driver"
 	depends on SND_HDA=y && SND_HDA_SCODEC_CS35L41_SPI=m
 
+config SND_HDA_SCODEC_CS35L56
+	tristate
+
+config SND_HDA_SCODEC_CS35L56_I2C
+	tristate "Build CS35L56 HD-audio side codec support for I2C Bus"
+	depends on I2C
+	depends on ACPI || COMPILE_TEST
+	depends on SND_SOC
+	select CS_DSP
+	select SND_HDA_GENERIC
+	select SND_SOC_CS35L56_SHARED
+	select SND_HDA_SCODEC_CS35L56
+	select SND_HDA_CS_DSP_CONTROLS
+	help
+	  Say Y or M here to include CS35L56 amplifier support with
+	  I2C control.
+
+config SND_HDA_SCODEC_CS35L56_SPI
+	tristate "Build CS35L56 HD-audio codec support for SPI Bus"
+	depends on SPI_MASTER
+	depends on ACPI || COMPILE_TEST
+	depends on SND_SOC
+	select CS_DSP
+	select SND_HDA_GENERIC
+	select SND_SOC_CS35L56_SHARED
+	select SND_HDA_SCODEC_CS35L56
+	select SND_HDA_CS_DSP_CONTROLS
+	help
+	  Say Y or M here to include CS35L56 amplifier support with
+	  SPI control.
+
 config SND_HDA_CODEC_REALTEK
 	tristate "Build Realtek HD-audio codec support"
 	select SND_HDA_GENERIC
diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile
index 00d306104484..c6e6509e7b8e 100644
--- a/sound/pci/hda/Makefile
+++ b/sound/pci/hda/Makefile
@@ -31,6 +31,9 @@ snd-hda-codec-hdmi-objs :=	patch_hdmi.o hda_eld.o
 snd-hda-scodec-cs35l41-objs :=		cs35l41_hda.o
 snd-hda-scodec-cs35l41-i2c-objs :=	cs35l41_hda_i2c.o
 snd-hda-scodec-cs35l41-spi-objs :=	cs35l41_hda_spi.o
+snd-hda-scodec-cs35l56-objs :=		cs35l56_hda.o
+snd-hda-scodec-cs35l56-i2c-objs :=	cs35l56_hda_i2c.o
+snd-hda-scodec-cs35l56-spi-objs :=	cs35l56_hda_spi.o
 snd-hda-cs-dsp-ctls-objs :=		hda_cs_dsp_ctl.o
 
 # common driver
@@ -55,6 +58,9 @@ obj-$(CONFIG_SND_HDA_CODEC_HDMI) += snd-hda-codec-hdmi.o
 obj-$(CONFIG_SND_HDA_SCODEC_CS35L41) += snd-hda-scodec-cs35l41.o
 obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_I2C) += snd-hda-scodec-cs35l41-i2c.o
 obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_SPI) += snd-hda-scodec-cs35l41-spi.o
+obj-$(CONFIG_SND_HDA_SCODEC_CS35L56) += snd-hda-scodec-cs35l56.o
+obj-$(CONFIG_SND_HDA_SCODEC_CS35L56_I2C) += snd-hda-scodec-cs35l56-i2c.o
+obj-$(CONFIG_SND_HDA_SCODEC_CS35L56_SPI) += snd-hda-scodec-cs35l56-spi.o
 obj-$(CONFIG_SND_HDA_CS_DSP_CONTROLS) += snd-hda-cs-dsp-ctls.o
 
 # this must be the last entry after codec drivers;
diff --git a/sound/pci/hda/cs35l56_hda.c b/sound/pci/hda/cs35l56_hda.c
new file mode 100644
index 000000000000..5189f1e89a87
--- /dev/null
+++ b/sound/pci/hda/cs35l56_hda.c
@@ -0,0 +1,995 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// HDA audio driver for Cirrus Logic CS35L56 smart amp
+//
+// Copyright (C) 2023 Cirrus Logic, Inc. and
+//                    Cirrus Logic International Semiconductor Ltd.
+//
+
+#include <linux/acpi.h>
+#include <linux/debugfs.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/hda_codec.h>
+#include <sound/tlv.h>
+#include "cs35l56_hda.h"
+#include "hda_component.h"
+#include "hda_cs_dsp_ctl.h"
+#include "hda_generic.h"
+
+ /*
+  * The cs35l56_hda_dai_config[] reg sequence configures the device as
+  *  ASP1_BCLK_FREQ = 3.072 MHz
+  *  ASP1_RX_WIDTH = 32 cycles per slot, ASP1_TX_WIDTH = 32 cycles per slot, ASP1_FMT = I2S
+  *  ASP1_DOUT_HIZ_CONTROL = Hi-Z during unused timeslots
+  *  ASP1_RX_WL = 24 bits per sample
+  *  ASP1_TX_WL = 24 bits per sample
+  *  ASP1_RXn_EN 1..3 and ASP1_TXn_EN 1..4 disabled
+  */
+static const struct reg_sequence cs35l56_hda_dai_config[] = {
+	{ CS35L56_ASP1_CONTROL1,	0x00000021 },
+	{ CS35L56_ASP1_CONTROL2,	0x20200200 },
+	{ CS35L56_ASP1_CONTROL3,	0x00000003 },
+	{ CS35L56_ASP1_DATA_CONTROL5,	0x00000018 },
+	{ CS35L56_ASP1_DATA_CONTROL1,	0x00000018 },
+	{ CS35L56_ASP1_ENABLES1,	0x00000000 },
+};
+
+static void cs35l56_hda_play(struct cs35l56_hda *cs35l56)
+{
+	unsigned int val;
+	int ret;
+
+	pm_runtime_get_sync(cs35l56->base.dev);
+	ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_PLAY);
+	if (ret == 0) {
+		/* Wait for firmware to enter PS0 power state */
+		ret = regmap_read_poll_timeout(cs35l56->base.regmap,
+					       CS35L56_TRANSDUCER_ACTUAL_PS,
+					       val, (val == CS35L56_PS0),
+					       CS35L56_PS0_POLL_US,
+					       CS35L56_PS0_TIMEOUT_US);
+		if (ret)
+			dev_warn(cs35l56->base.dev, "PS0 wait failed: %d\n", ret);
+	}
+	regmap_set_bits(cs35l56->base.regmap, CS35L56_ASP1_ENABLES1,
+			BIT(CS35L56_ASP_RX1_EN_SHIFT) | BIT(CS35L56_ASP_RX2_EN_SHIFT) |
+			cs35l56->asp_tx_mask);
+	cs35l56->playing = true;
+}
+
+static void cs35l56_hda_pause(struct cs35l56_hda *cs35l56)
+{
+	cs35l56->playing = false;
+	cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_PAUSE);
+	regmap_clear_bits(cs35l56->base.regmap, CS35L56_ASP1_ENABLES1,
+			  BIT(CS35L56_ASP_RX1_EN_SHIFT) | BIT(CS35L56_ASP_RX2_EN_SHIFT) |
+			  BIT(CS35L56_ASP_TX1_EN_SHIFT) | BIT(CS35L56_ASP_TX2_EN_SHIFT) |
+			  BIT(CS35L56_ASP_TX3_EN_SHIFT) | BIT(CS35L56_ASP_TX4_EN_SHIFT));
+
+	pm_runtime_mark_last_busy(cs35l56->base.dev);
+	pm_runtime_put_autosuspend(cs35l56->base.dev);
+}
+
+static void cs35l56_hda_playback_hook(struct device *dev, int action)
+{
+	struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
+
+	dev_dbg(cs35l56->base.dev, "%s()%d: action: %d\n", __func__, __LINE__, action);
+
+	switch (action) {
+	case HDA_GEN_PCM_ACT_PREPARE:
+		if (cs35l56->playing)
+			break;
+
+		/* If we're suspended: flag that resume should start playback */
+		if (cs35l56->suspended) {
+			cs35l56->playing = true;
+			break;
+		}
+
+		cs35l56_hda_play(cs35l56);
+		break;
+	case HDA_GEN_PCM_ACT_CLEANUP:
+		if (!cs35l56->playing)
+			break;
+
+		cs35l56_hda_pause(cs35l56);
+		break;
+	default:
+		break;
+	}
+}
+
+static void cs35l56_hda_mute_hook(struct device *dev, bool mute)
+{
+	struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
+	unsigned int val;
+
+	if (mute)
+		val = CS35L56_MAIN_RENDER_USER_MUTE_MASK;
+	else
+		val = 0;
+
+	regmap_write(cs35l56->base.regmap, CS35L56_MAIN_RENDER_USER_MUTE, val);
+}
+
+static int cs35l56_hda_runtime_suspend(struct device *dev)
+{
+	struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
+
+	if (cs35l56->cs_dsp.booted)
+		cs_dsp_stop(&cs35l56->cs_dsp);
+
+	return cs35l56_runtime_suspend_common(&cs35l56->base);
+}
+
+static int cs35l56_hda_runtime_resume(struct device *dev)
+{
+	struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
+	int ret;
+
+	ret = cs35l56_runtime_resume_common(&cs35l56->base, false);
+	if (ret < 0)
+		return ret;
+
+	if (cs35l56->cs_dsp.booted) {
+		ret = cs_dsp_run(&cs35l56->cs_dsp);
+		if (ret) {
+			dev_dbg(cs35l56->base.dev, "%s: cs_dsp_run ret %d\n", __func__, ret);
+			goto err;
+		}
+	}
+
+	return 0;
+
+err:
+	cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_ALLOW_AUTO_HIBERNATE);
+	regmap_write(cs35l56->base.regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
+		     CS35L56_MBOX_CMD_HIBERNATE_NOW);
+
+	regcache_cache_only(cs35l56->base.regmap, true);
+
+	return ret;
+}
+
+static int cs35l56_hda_mixer_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = CS35L56_NUM_INPUT_SRC;
+	if (uinfo->value.enumerated.item >= CS35L56_NUM_INPUT_SRC)
+		uinfo->value.enumerated.item = CS35L56_NUM_INPUT_SRC - 1;
+	strcpy(uinfo->value.enumerated.name, cs35l56_tx_input_texts[uinfo->value.enumerated.item]);
+
+	return 0;
+}
+
+static int cs35l56_hda_mixer_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
+	unsigned int reg_val;
+	int i;
+
+	regmap_read(cs35l56->base.regmap, kcontrol->private_value, &reg_val);
+	reg_val &= CS35L56_ASP_TXn_SRC_MASK;
+
+	for (i = 0; i < CS35L56_NUM_INPUT_SRC; ++i) {
+		if (cs35l56_tx_input_values[i] == reg_val) {
+			ucontrol->value.enumerated.item[0] = i;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int cs35l56_hda_mixer_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
+	unsigned int item = ucontrol->value.enumerated.item[0];
+	bool changed;
+
+	if (item >= CS35L56_NUM_INPUT_SRC)
+		return -EINVAL;
+
+	regmap_update_bits_check(cs35l56->base.regmap, kcontrol->private_value,
+				 CS35L56_INPUT_MASK, cs35l56_tx_input_values[item],
+				 &changed);
+
+	return changed;
+}
+
+static int cs35l56_hda_posture_info(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = CS35L56_MAIN_POSTURE_MIN;
+	uinfo->value.integer.max = CS35L56_MAIN_POSTURE_MAX;
+	return 0;
+}
+
+static int cs35l56_hda_posture_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
+	unsigned int pos;
+	int ret;
+
+	ret = regmap_read(cs35l56->base.regmap, CS35L56_MAIN_POSTURE_NUMBER, &pos);
+	if (ret)
+		return ret;
+
+	ucontrol->value.integer.value[0] = pos;
+
+	return ret;
+}
+
+static int cs35l56_hda_posture_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
+	unsigned long pos = ucontrol->value.integer.value[0];
+	bool changed;
+	int ret;
+
+	if ((pos < CS35L56_MAIN_POSTURE_MIN) ||
+	    (pos > CS35L56_MAIN_POSTURE_MAX))
+		return -EINVAL;
+
+	ret = regmap_update_bits_check(cs35l56->base.regmap,
+				       CS35L56_MAIN_POSTURE_NUMBER,
+				       CS35L56_MAIN_POSTURE_MASK,
+				       pos, &changed);
+	if (ret)
+		return ret;
+
+	return changed;
+}
+
+static const struct {
+	const char *name;
+	unsigned int reg;
+} cs35l56_hda_mixer_controls[] = {
+	{ "ASP1 TX1 Source", CS35L56_ASP1TX1_INPUT },
+	{ "ASP1 TX2 Source", CS35L56_ASP1TX2_INPUT },
+	{ "ASP1 TX3 Source", CS35L56_ASP1TX3_INPUT },
+	{ "ASP1 TX4 Source", CS35L56_ASP1TX4_INPUT },
+};
+
+static const DECLARE_TLV_DB_SCALE(cs35l56_hda_vol_tlv, -10000, 25, 0);
+
+static int cs35l56_hda_vol_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.step = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = CS35L56_MAIN_RENDER_USER_VOLUME_MAX -
+				   CS35L56_MAIN_RENDER_USER_VOLUME_MIN;
+
+	return 0;
+}
+
+static int cs35l56_hda_vol_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
+	unsigned int raw_vol;
+	int vol;
+	int ret;
+
+	ret = regmap_read(cs35l56->base.regmap, CS35L56_MAIN_RENDER_USER_VOLUME, &raw_vol);
+
+	if (ret)
+		return ret;
+
+	vol = (s16)(raw_vol & 0xFFFF);
+	vol >>= CS35L56_MAIN_RENDER_USER_VOLUME_SHIFT;
+
+	if (vol & BIT(CS35L56_MAIN_RENDER_USER_VOLUME_SIGNBIT))
+		vol |= ~((int)(BIT(CS35L56_MAIN_RENDER_USER_VOLUME_SIGNBIT) - 1));
+
+	ucontrol->value.integer.value[0] = vol - CS35L56_MAIN_RENDER_USER_VOLUME_MIN;
+
+	return 0;
+}
+
+static int cs35l56_hda_vol_put(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
+	long vol = ucontrol->value.integer.value[0];
+	unsigned int raw_vol;
+	bool changed;
+	int ret;
+
+	if ((vol < 0) || (vol > (CS35L56_MAIN_RENDER_USER_VOLUME_MAX -
+				 CS35L56_MAIN_RENDER_USER_VOLUME_MIN)))
+		return -EINVAL;
+
+	raw_vol = (vol + CS35L56_MAIN_RENDER_USER_VOLUME_MIN) <<
+		  CS35L56_MAIN_RENDER_USER_VOLUME_SHIFT;
+
+	ret = regmap_update_bits_check(cs35l56->base.regmap,
+				       CS35L56_MAIN_RENDER_USER_VOLUME,
+				       CS35L56_MAIN_RENDER_USER_VOLUME_MASK,
+				       raw_vol, &changed);
+	if (ret)
+		return ret;
+
+	return changed;
+}
+
+static void cs35l56_hda_create_controls(struct cs35l56_hda *cs35l56)
+{
+	struct snd_kcontrol_new ctl_template = {
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+		.info = cs35l56_hda_posture_info,
+		.get = cs35l56_hda_posture_get,
+		.put = cs35l56_hda_posture_put,
+	};
+	char name[64];
+	int i;
+
+	snprintf(name, sizeof(name), "%s Posture Number", cs35l56->amp_name);
+	ctl_template.name = name;
+	cs35l56->posture_ctl = snd_ctl_new1(&ctl_template, cs35l56);
+	if (snd_ctl_add(cs35l56->codec->card, cs35l56->posture_ctl)) {
+		dev_err(cs35l56->base.dev, "Failed to add KControl: %s\n", ctl_template.name);
+		return;
+	}
+
+	/* Mixer controls */
+	ctl_template.info = cs35l56_hda_mixer_info;
+	ctl_template.get = cs35l56_hda_mixer_get;
+	ctl_template.put = cs35l56_hda_mixer_put;
+
+	BUILD_BUG_ON(ARRAY_SIZE(cs35l56->mixer_ctl) != ARRAY_SIZE(cs35l56_hda_mixer_controls));
+
+	for (i = 0; i < ARRAY_SIZE(cs35l56_hda_mixer_controls); ++i) {
+		snprintf(name, sizeof(name), "%s %s", cs35l56->amp_name,
+			 cs35l56_hda_mixer_controls[i].name);
+		ctl_template.private_value = cs35l56_hda_mixer_controls[i].reg;
+		cs35l56->mixer_ctl[i] = snd_ctl_new1(&ctl_template, cs35l56);
+		if (snd_ctl_add(cs35l56->codec->card, cs35l56->mixer_ctl[i])) {
+			dev_err(cs35l56->base.dev, "Failed to add KControl: %s\n",
+				ctl_template.name);
+			return;
+		}
+	}
+
+	ctl_template.info = cs35l56_hda_vol_info;
+	ctl_template.get = cs35l56_hda_vol_get;
+	ctl_template.put = cs35l56_hda_vol_put;
+	ctl_template.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ);
+	ctl_template.tlv.p = cs35l56_hda_vol_tlv;
+	snprintf(name, sizeof(name), "%s Speaker Playback Volume", cs35l56->amp_name);
+	ctl_template.name = name;
+	cs35l56->volume_ctl = snd_ctl_new1(&ctl_template, cs35l56);
+	if (snd_ctl_add(cs35l56->codec->card, cs35l56->volume_ctl)) {
+		dev_err(cs35l56->base.dev, "Failed to add KControl: %s\n", ctl_template.name);
+		return;
+	}
+}
+
+static void cs35l56_hda_remove_controls(struct cs35l56_hda *cs35l56)
+{
+	int i;
+
+	for (i = ARRAY_SIZE(cs35l56->mixer_ctl) - 1; i >= 0; i--)
+		snd_ctl_remove(cs35l56->codec->card, cs35l56->mixer_ctl[i]);
+
+	snd_ctl_remove(cs35l56->codec->card, cs35l56->posture_ctl);
+	snd_ctl_remove(cs35l56->codec->card, cs35l56->volume_ctl);
+}
+
+static const struct cs_dsp_client_ops cs35l56_hda_client_ops = {
+	.control_remove = hda_cs_dsp_control_remove,
+};
+
+static int cs35l56_hda_request_firmware_file(struct cs35l56_hda *cs35l56,
+					     const struct firmware **firmware, char **filename,
+					     const char *dir, const char *system_name,
+					     const char *amp_name,
+					     const char *filetype)
+{
+	char *s, c;
+	int ret = 0;
+
+	if (system_name && amp_name)
+		*filename = kasprintf(GFP_KERNEL, "%scs35l56%s-%02x-dsp1-misc-%s-%s.%s", dir,
+				      cs35l56->base.secured ? "s" : "", cs35l56->base.rev,
+				      system_name, amp_name, filetype);
+	else if (system_name)
+		*filename = kasprintf(GFP_KERNEL, "%scs35l56%s-%02x-dsp1-misc-%s.%s", dir,
+				      cs35l56->base.secured ? "s" : "", cs35l56->base.rev,
+				      system_name, filetype);
+	else
+		*filename = kasprintf(GFP_KERNEL, "%scs35l56%s-%02x-dsp1-misc.%s", dir,
+				      cs35l56->base.secured ? "s" : "", cs35l56->base.rev,
+				      filetype);
+
+	if (!*filename)
+		return -ENOMEM;
+
+	/*
+	 * Make sure that filename is lower-case and any non alpha-numeric
+	 * characters except full stop and forward slash are replaced with
+	 * hyphens.
+	 */
+	s = *filename;
+	while (*s) {
+		c = *s;
+		if (isalnum(c))
+			*s = tolower(c);
+		else if (c != '.' && c != '/')
+			*s = '-';
+		s++;
+	}
+
+	ret = firmware_request_nowarn(firmware, *filename, cs35l56->base.dev);
+	if (ret != 0) {
+		dev_dbg(cs35l56->base.dev, "Failed to request '%s'\n", *filename);
+		kfree(*filename);
+		*filename = NULL;
+	} else {
+		dev_dbg(cs35l56->base.dev, "Found '%s'\n", *filename);
+	}
+
+	return ret;
+}
+
+static const char cirrus_dir[] = "cirrus/";
+static int cs35l56_hda_request_firmware_files(struct cs35l56_hda *cs35l56,
+					      const struct firmware **wmfw_firmware,
+					      char **wmfw_filename,
+					      const struct firmware **coeff_firmware,
+					      char **coeff_filename)
+{
+	const char *system_name = cs35l56->system_name;
+	const char *amp_name = cs35l56->amp_name;
+	int ret;
+
+	if (system_name && amp_name) {
+		if (!cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_filename,
+						       cirrus_dir, system_name, amp_name, "wmfw")) {
+			cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
+							  cirrus_dir, system_name, amp_name, "bin");
+			return 0;
+		}
+	}
+
+	if (system_name) {
+		if (!cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_filename,
+						       cirrus_dir, system_name, NULL, "wmfw")) {
+			if (amp_name)
+				cs35l56_hda_request_firmware_file(cs35l56,
+								  coeff_firmware, coeff_filename,
+								  cirrus_dir, system_name,
+								  amp_name, "bin");
+			if (!*coeff_firmware)
+				cs35l56_hda_request_firmware_file(cs35l56,
+								  coeff_firmware, coeff_filename,
+								  cirrus_dir, system_name,
+								  NULL, "bin");
+			return 0;
+		}
+	}
+
+	ret = cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_filename,
+						cirrus_dir, NULL, NULL, "wmfw");
+	if (!ret) {
+		cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
+						  cirrus_dir, NULL, NULL, "bin");
+		return 0;
+	}
+
+	/* When a firmware file is not found must still search for the coeff files */
+	if (system_name) {
+		if (amp_name)
+			cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
+							  cirrus_dir, system_name, amp_name, "bin");
+		if (!*coeff_firmware)
+			cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
+							  cirrus_dir, system_name, NULL, "bin");
+	}
+
+	if (!*coeff_firmware)
+		cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
+						  cirrus_dir, NULL, NULL, "bin");
+
+	return 0;
+}
+
+static void cs35l56_hda_add_dsp_controls(struct cs35l56_hda *cs35l56)
+{
+	struct hda_cs_dsp_ctl_info info;
+
+	info.device_name = cs35l56->amp_name;
+	info.fw_type = HDA_CS_DSP_FW_MISC;
+	info.card = cs35l56->codec->card;
+
+	hda_cs_dsp_add_controls(&cs35l56->cs_dsp, &info);
+}
+
+static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56)
+{
+	const struct firmware *coeff_firmware = NULL;
+	const struct firmware *wmfw_firmware = NULL;
+	char *coeff_filename = NULL;
+	char *wmfw_filename = NULL;
+	int ret = 0;
+
+	mutex_lock(&cs35l56->base.irq_lock);
+	pm_runtime_get_sync(cs35l56->base.dev);
+
+	/*
+	 * When the device is running in secure mode the firmware files can
+	 * only contain insecure tunings and therefore we do not need to
+	 * shutdown the firmware to apply them and can use the lower cost
+	 * reinit sequence instead.
+	 */
+	if (!cs35l56->base.secured) {
+		ret = cs35l56_firmware_shutdown(&cs35l56->base);
+		if (ret)
+			goto err;
+	}
+
+	cs35l56_hda_request_firmware_files(cs35l56, &wmfw_firmware, &wmfw_filename,
+					   &coeff_firmware, &coeff_filename);
+
+	ret = cs_dsp_power_up(&cs35l56->cs_dsp, wmfw_firmware, wmfw_filename,
+			      coeff_firmware, coeff_filename, "misc");
+	if (ret) {
+		dev_dbg(cs35l56->base.dev, "%s: cs_dsp_power_up ret %d\n", __func__, ret);
+		goto err;
+	}
+
+	if (wmfw_filename)
+		dev_dbg(cs35l56->base.dev, "Loaded WMFW Firmware: %s\n", wmfw_filename);
+
+	if (coeff_filename)
+		dev_dbg(cs35l56->base.dev, "Loaded Coefficients: %s\n", coeff_filename);
+
+	ret = cs_dsp_run(&cs35l56->cs_dsp);
+	if (ret) {
+		dev_dbg(cs35l56->base.dev, "%s: cs_dsp_run ret %d\n", __func__, ret);
+		goto err;
+	}
+
+	if (cs35l56->base.secured) {
+		ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT);
+		if (ret)
+			goto err;
+	} else {
+		/* Reset the device and wait for it to boot */
+		cs35l56_system_reset(&cs35l56->base, false);
+		ret = cs35l56_wait_for_firmware_boot(&cs35l56->base);
+		if (ret)
+			goto err;
+	}
+
+	/* Disable auto-hibernate so that runtime_pm has control */
+	ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE);
+	if (ret)
+		goto err;
+
+	/* Re-read the values from the device after a firmware/cofficient download */
+	cs35l56_reread_firmware_registers(&cs35l56->base);
+
+	regcache_mark_dirty(cs35l56->base.regmap);
+	regcache_sync(cs35l56->base.regmap);
+
+	regmap_clear_bits(cs35l56->base.regmap, CS35L56_PROTECTION_STATUS,
+			  CS35L56_FIRMWARE_MISSING);
+	cs35l56->base.fw_patched = true;
+err:
+	pm_runtime_put(cs35l56->base.dev);
+	mutex_unlock(&cs35l56->base.irq_lock);
+
+	return ret;
+}
+
+static int cs35l56_hda_bind(struct device *dev, struct device *master, void *master_data)
+{
+	struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
+	struct hda_component *comps = master_data;
+	int ret;
+
+	if (!comps || cs35l56->index < 0 || cs35l56->index >= HDA_MAX_COMPONENTS)
+		return -EINVAL;
+
+	comps = &comps[cs35l56->index];
+	if (comps->dev)
+		return -EBUSY;
+
+	comps->dev = dev;
+	cs35l56->codec = comps->codec;
+	strscpy(comps->name, dev_name(dev), sizeof(comps->name));
+	comps->playback_hook = cs35l56_hda_playback_hook;
+	comps->mute_hook = cs35l56_hda_mute_hook;
+
+	ret = cs35l56_hda_fw_load(cs35l56);
+	if (ret)
+		return ret;
+
+	cs35l56_hda_create_controls(cs35l56);
+	cs35l56_hda_add_dsp_controls(cs35l56);
+
+#if IS_ENABLED(CONFIG_SND_DEBUG)
+	cs35l56->debugfs_root = debugfs_create_dir(dev_name(cs35l56->base.dev), sound_debugfs_root);
+	cs_dsp_init_debugfs(&cs35l56->cs_dsp, cs35l56->debugfs_root);
+#endif
+
+	dev_dbg(cs35l56->base.dev, "Bound\n");
+
+	return 0;
+}
+
+static void cs35l56_hda_unbind(struct device *dev, struct device *master, void *master_data)
+{
+	struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
+	struct hda_component *comps = master_data;
+
+	cs35l56_hda_remove_controls(cs35l56);
+
+#if IS_ENABLED(CONFIG_SND_DEBUG)
+	cs_dsp_cleanup_debugfs(&cs35l56->cs_dsp);
+	debugfs_remove_recursive(cs35l56->debugfs_root);
+#endif
+
+	cs_dsp_remove(&cs35l56->cs_dsp);
+
+	if (comps[cs35l56->index].dev == dev)
+		memset(&comps[cs35l56->index], 0, sizeof(*comps));
+
+	dev_dbg(cs35l56->base.dev, "Unbound\n");
+}
+
+static const struct component_ops cs35l56_hda_comp_ops = {
+	.bind = cs35l56_hda_bind,
+	.unbind = cs35l56_hda_unbind,
+};
+
+static int __maybe_unused cs35l56_hda_system_suspend(struct device *dev)
+{
+	struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
+
+	if (cs35l56->playing)
+		cs35l56_hda_pause(cs35l56);
+
+	cs35l56->suspended = true;
+
+	/*
+	 * The interrupt line is normally shared, but after we start suspending
+	 * we can't check if our device is the source of an interrupt, and can't
+	 * clear it. Prevent this race by temporarily disabling the parent irq
+	 * until we reach _no_irq.
+	 */
+	if (cs35l56->base.irq)
+		disable_irq(cs35l56->base.irq);
+
+	return pm_runtime_force_suspend(dev);
+}
+
+static int __maybe_unused cs35l56_hda_system_suspend_late(struct device *dev)
+{
+	struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
+
+	/*
+	 * RESET is usually shared by all amps so it must not be asserted until
+	 * all driver instances have done their suspend() stage.
+	 */
+	if (cs35l56->base.reset_gpio) {
+		gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
+		cs35l56_wait_min_reset_pulse();
+	}
+
+	return 0;
+}
+
+static int __maybe_unused cs35l56_hda_system_suspend_no_irq(struct device *dev)
+{
+	struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
+
+	/* Handlers are now disabled so the parent IRQ can safely be re-enabled. */
+	if (cs35l56->base.irq)
+		enable_irq(cs35l56->base.irq);
+
+	return 0;
+}
+
+static int __maybe_unused cs35l56_hda_system_resume_no_irq(struct device *dev)
+{
+	struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
+
+	/*
+	 * WAKE interrupts unmask if the CS35L56 hibernates, which can cause
+	 * spurious interrupts, and the interrupt line is normally shared.
+	 * We can't check if our device is the source of an interrupt, and can't
+	 * clear it, until it has fully resumed. Prevent this race by temporarily
+	 * disabling the parent irq until we complete resume().
+	 */
+	if (cs35l56->base.irq)
+		disable_irq(cs35l56->base.irq);
+
+	return 0;
+}
+
+static int __maybe_unused cs35l56_hda_system_resume_early(struct device *dev)
+{
+	struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
+
+	/* Ensure a spec-compliant RESET pulse. */
+	if (cs35l56->base.reset_gpio) {
+		gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
+		cs35l56_wait_min_reset_pulse();
+
+		/* Release shared RESET before drivers start resume(). */
+		gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 1);
+		cs35l56_wait_control_port_ready();
+	}
+
+	return 0;
+}
+
+static int __maybe_unused cs35l56_hda_system_resume(struct device *dev)
+{
+	struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
+	int ret;
+
+	/* Undo pm_runtime_force_suspend() before re-enabling the irq */
+	ret = pm_runtime_force_resume(dev);
+	if (cs35l56->base.irq)
+		enable_irq(cs35l56->base.irq);
+
+	if (ret)
+		return ret;
+
+	cs35l56->suspended = false;
+
+	ret = cs35l56_is_fw_reload_needed(&cs35l56->base);
+	dev_dbg(cs35l56->base.dev, "fw_reload_needed: %d\n", ret);
+	if (ret > 0) {
+		ret = cs35l56_hda_fw_load(cs35l56);
+		if (ret)
+			return ret;
+	}
+
+	if (cs35l56->playing)
+		cs35l56_hda_play(cs35l56);
+
+	return 0;
+}
+
+static int cs35l56_hda_read_acpi(struct cs35l56_hda *cs35l56, int id)
+{
+	u32 values[HDA_MAX_COMPONENTS];
+	struct acpi_device *adev;
+	const char *property, *sub;
+	size_t nval;
+	int i, ret;
+
+	/*
+	 * ACPI_COMPANION isn't available when this driver was instantiated by
+	 * the serial-multi-instantiate driver, so lookup the node by HID
+	 */
+	if (!ACPI_COMPANION(cs35l56->base.dev)) {
+		adev = acpi_dev_get_first_match_dev("CSC3556", NULL, -1);
+		if (!adev) {
+			dev_err(cs35l56->base.dev, "Failed to find an ACPI device for %s\n",
+				dev_name(cs35l56->base.dev));
+			return -ENODEV;
+		}
+		ACPI_COMPANION_SET(cs35l56->base.dev, adev);
+	}
+
+	property = "cirrus,dev-index";
+	ret = device_property_count_u32(cs35l56->base.dev, property);
+	if (ret <= 0)
+		goto err;
+
+	if (ret > ARRAY_SIZE(values)) {
+		ret = -EINVAL;
+		goto err;
+	}
+	nval = ret;
+
+	ret = device_property_read_u32_array(cs35l56->base.dev, property, values, nval);
+	if (ret)
+		goto err;
+
+	cs35l56->index = -1;
+	for (i = 0; i < nval; i++) {
+		if (values[i] == id) {
+			cs35l56->index = i;
+			break;
+		}
+	}
+	if (cs35l56->index == -1) {
+		dev_err(cs35l56->base.dev, "No index found in %s\n", property);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	sub = acpi_get_subsystem_id(ACPI_HANDLE(cs35l56->base.dev));
+
+	if (IS_ERR(sub)) {
+		/* If no ACPI SUB, return 0 and fallback to legacy firmware path, otherwise fail */
+		if (PTR_ERR(sub) == -ENODATA)
+			return 0;
+		else
+			return PTR_ERR(sub);
+	}
+
+	cs35l56->system_name = sub;
+
+	cs35l56->base.reset_gpio = devm_gpiod_get_index_optional(cs35l56->base.dev,
+								 "reset",
+								 cs35l56->index,
+								 GPIOD_OUT_LOW);
+	if (IS_ERR(cs35l56->base.reset_gpio)) {
+		ret = PTR_ERR(cs35l56->base.reset_gpio);
+
+		/*
+		 * If RESET is shared the first amp to probe will grab the reset
+		 * line and reset all the amps
+		 */
+		if (ret != -EBUSY)
+			return dev_err_probe(cs35l56->base.dev, ret, "Failed to get reset GPIO\n");
+
+		dev_info(cs35l56->base.dev, "Reset GPIO busy, assume shared reset\n");
+		cs35l56->base.reset_gpio = NULL;
+	}
+
+	return 0;
+
+err:
+	dev_err(cs35l56->base.dev, "Failed property %s: %d\n", property, ret);
+
+	return ret;
+}
+
+int cs35l56_hda_common_probe(struct cs35l56_hda *cs35l56, int id)
+{
+	int ret;
+
+	mutex_init(&cs35l56->base.irq_lock);
+	dev_set_drvdata(cs35l56->base.dev, cs35l56);
+
+	ret = cs35l56_hda_read_acpi(cs35l56, id);
+	if (ret) {
+		dev_err_probe(cs35l56->base.dev, ret, "Platform not supported\n");
+		goto err;
+	}
+
+	cs35l56->amp_name = devm_kasprintf(cs35l56->base.dev, GFP_KERNEL, "AMP%d",
+					   cs35l56->index + 1);
+	if (!cs35l56->amp_name) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	cs35l56_init_cs_dsp(&cs35l56->base, &cs35l56->cs_dsp);
+	cs35l56->cs_dsp.client_ops = &cs35l56_hda_client_ops;
+
+	if (cs35l56->base.reset_gpio) {
+		dev_dbg(cs35l56->base.dev, "Hard reset\n");
+
+		/*
+		 * The GPIOD_OUT_LOW to *_gpiod_get_*() will be ignored if the
+		 * ACPI defines a different default state. So explicitly set low.
+		 */
+		gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
+		cs35l56_wait_min_reset_pulse();
+		gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 1);
+	}
+
+	ret = cs35l56_hw_init(&cs35l56->base);
+	if (ret < 0)
+		goto err;
+
+	/* Reset the device and wait for it to boot */
+	cs35l56_system_reset(&cs35l56->base, false);
+	ret = cs35l56_wait_for_firmware_boot(&cs35l56->base);
+	if (ret)
+		goto err;
+
+	regcache_mark_dirty(cs35l56->base.regmap);
+	regcache_sync(cs35l56->base.regmap);
+
+	/* Disable auto-hibernate so that runtime_pm has control */
+	ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE);
+	if (ret)
+		goto err;
+
+	ret = cs_dsp_halo_init(&cs35l56->cs_dsp);
+	if (ret != 0) {
+		dev_err_probe(cs35l56->base.dev, ret, "cs_dsp_halo_init failed\n");
+		goto err;
+	}
+
+	dev_dbg(cs35l56->base.dev, "DSP system name: '%s', amp name: '%s'\n",
+		cs35l56->system_name, cs35l56->amp_name);
+
+	/* Populate soft registers in the regmap cache */
+	cs35l56_reread_firmware_registers(&cs35l56->base);
+
+	regmap_multi_reg_write(cs35l56->base.regmap, cs35l56_hda_dai_config,
+			       ARRAY_SIZE(cs35l56_hda_dai_config));
+
+	/*
+	 * By default only enable one ASP1TXn, where n=amplifier index,
+	 * This prevents multiple amps trying to drive the same slot.
+	 */
+	cs35l56->asp_tx_mask = BIT(cs35l56->index);
+
+	pm_runtime_set_autosuspend_delay(cs35l56->base.dev, 3000);
+	pm_runtime_use_autosuspend(cs35l56->base.dev);
+	pm_runtime_set_active(cs35l56->base.dev);
+	pm_runtime_mark_last_busy(cs35l56->base.dev);
+	pm_runtime_enable(cs35l56->base.dev);
+
+	ret = component_add(cs35l56->base.dev, &cs35l56_hda_comp_ops);
+	if (ret) {
+		dev_err(cs35l56->base.dev, "Register component failed: %d\n", ret);
+		goto pm_err;
+	}
+
+	cs35l56->base.init_done = true;
+
+	return 0;
+
+pm_err:
+	pm_runtime_disable(cs35l56->base.dev);
+err:
+	gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
+
+	return ret;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_hda_common_probe, SND_HDA_SCODEC_CS35L56);
+
+void cs35l56_hda_remove(struct device *dev)
+{
+	struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
+
+	pm_runtime_get_sync(cs35l56->base.dev);
+	pm_runtime_disable(cs35l56->base.dev);
+
+	component_del(cs35l56->base.dev, &cs35l56_hda_comp_ops);
+
+	kfree(cs35l56->system_name);
+	pm_runtime_put_noidle(cs35l56->base.dev);
+
+	gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_hda_remove, SND_HDA_SCODEC_CS35L56);
+
+const struct dev_pm_ops cs35l56_hda_pm_ops = {
+	SET_RUNTIME_PM_OPS(cs35l56_hda_runtime_suspend, cs35l56_hda_runtime_resume, NULL)
+	SYSTEM_SLEEP_PM_OPS(cs35l56_hda_system_suspend, cs35l56_hda_system_resume)
+	LATE_SYSTEM_SLEEP_PM_OPS(cs35l56_hda_system_suspend_late,
+				 cs35l56_hda_system_resume_early)
+	NOIRQ_SYSTEM_SLEEP_PM_OPS(cs35l56_hda_system_suspend_no_irq,
+				  cs35l56_hda_system_resume_no_irq)
+};
+EXPORT_SYMBOL_NS_GPL(cs35l56_hda_pm_ops, SND_HDA_SCODEC_CS35L56);
+
+MODULE_DESCRIPTION("CS35L56 HDA Driver");
+MODULE_IMPORT_NS(SND_HDA_CS_DSP_CONTROLS);
+MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED);
+MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
+MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(FW_CS_DSP);
diff --git a/sound/pci/hda/cs35l56_hda.h b/sound/pci/hda/cs35l56_hda.h
new file mode 100644
index 000000000000..6e5bc5397db5
--- /dev/null
+++ b/sound/pci/hda/cs35l56_hda.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * HDA audio driver for Cirrus Logic CS35L56 smart amp
+ *
+ * Copyright (C) 2023 Cirrus Logic, Inc. and
+ *                    Cirrus Logic International Semiconductor Ltd.
+ */
+
+#ifndef __CS35L56_HDA_H__
+#define __CS35L56_HDA_H__
+
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/firmware/cirrus/cs_dsp.h>
+#include <linux/firmware/cirrus/wmfw.h>
+#include <linux/regulator/consumer.h>
+#include <sound/cs35l56.h>
+
+struct dentry;
+
+struct cs35l56_hda {
+	struct cs35l56_base base;
+	struct hda_codec *codec;
+
+	int index;
+	const char *system_name;
+	const char *amp_name;
+
+	struct cs_dsp cs_dsp;
+	bool playing;
+	bool suspended;
+	u8 asp_tx_mask;
+
+	struct snd_kcontrol *posture_ctl;
+	struct snd_kcontrol *volume_ctl;
+	struct snd_kcontrol *mixer_ctl[4];
+
+#if IS_ENABLED(CONFIG_SND_DEBUG)
+	struct dentry *debugfs_root;
+#endif
+};
+
+extern const struct dev_pm_ops cs35l56_hda_pm_ops;
+
+int cs35l56_hda_common_probe(struct cs35l56_hda *cs35l56, int id);
+void cs35l56_hda_remove(struct device *dev);
+
+#endif /*__CS35L56_HDA_H__*/
diff --git a/sound/pci/hda/cs35l56_hda_i2c.c b/sound/pci/hda/cs35l56_hda_i2c.c
new file mode 100644
index 000000000000..5dfe79554d0b
--- /dev/null
+++ b/sound/pci/hda/cs35l56_hda_i2c.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// CS35L56 HDA audio driver I2C binding
+//
+// Copyright (C) 2023 Cirrus Logic, Inc. and
+//                    Cirrus Logic International Semiconductor Ltd.
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "cs35l56_hda.h"
+
+static int cs35l56_hda_i2c_probe(struct i2c_client *clt)
+{
+	struct cs35l56_hda *cs35l56;
+	int ret;
+
+	cs35l56 = devm_kzalloc(&clt->dev, sizeof(*cs35l56), GFP_KERNEL);
+	if (!cs35l56)
+		return -ENOMEM;
+
+	cs35l56->base.dev = &clt->dev;
+	cs35l56->base.can_hibernate = true;
+	cs35l56->base.regmap = devm_regmap_init_i2c(clt, &cs35l56_regmap_i2c);
+	if (IS_ERR(cs35l56->base.regmap)) {
+		ret = PTR_ERR(cs35l56->base.regmap);
+		dev_err(cs35l56->base.dev, "Failed to allocate register map: %d\n",
+			ret);
+		return ret;
+	}
+
+	ret = cs35l56_hda_common_probe(cs35l56, clt->addr);
+	if (ret != 0)
+		return ret;
+	ret = cs35l56_irq_request(&cs35l56->base, clt->irq);
+	if (ret < 0)
+		cs35l56_hda_remove(cs35l56->base.dev);
+
+	return ret;
+}
+
+static void cs35l56_hda_i2c_remove(struct i2c_client *clt)
+{
+	cs35l56_hda_remove(&clt->dev);
+}
+
+static const struct i2c_device_id cs35l56_hda_i2c_id[] = {
+	{ "cs35l56-hda", 0 },
+	{}
+};
+
+static struct i2c_driver cs35l56_hda_i2c_driver = {
+	.driver = {
+		.name		= "cs35l56-hda",
+		.pm		= &cs35l56_hda_pm_ops,
+	},
+	.id_table	= cs35l56_hda_i2c_id,
+	.probe_new	= cs35l56_hda_i2c_probe,
+	.remove		= cs35l56_hda_i2c_remove,
+};
+module_i2c_driver(cs35l56_hda_i2c_driver);
+
+MODULE_DESCRIPTION("HDA CS35L56 I2C driver");
+MODULE_IMPORT_NS(SND_HDA_SCODEC_CS35L56);
+MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED);
+MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
+MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/pci/hda/cs35l56_hda_spi.c b/sound/pci/hda/cs35l56_hda_spi.c
new file mode 100644
index 000000000000..bd572ce796d8
--- /dev/null
+++ b/sound/pci/hda/cs35l56_hda_spi.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// CS35L56 HDA audio driver SPI binding
+//
+// Copyright (C) 2023 Cirrus Logic, Inc. and
+//                    Cirrus Logic International Semiconductor Ltd.
+
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include "cs35l56_hda.h"
+
+static int cs35l56_hda_spi_probe(struct spi_device *spi)
+{
+	struct cs35l56_hda *cs35l56;
+	int ret;
+
+	cs35l56 = devm_kzalloc(&spi->dev, sizeof(*cs35l56), GFP_KERNEL);
+	if (!cs35l56)
+		return -ENOMEM;
+
+	cs35l56->base.dev = &spi->dev;
+	cs35l56->base.regmap = devm_regmap_init_spi(spi, &cs35l56_regmap_spi);
+	if (IS_ERR(cs35l56->base.regmap)) {
+		ret = PTR_ERR(cs35l56->base.regmap);
+		dev_err(cs35l56->base.dev, "Failed to allocate register map: %d\n",
+			ret);
+		return ret;
+	}
+
+	ret = cs35l56_hda_common_probe(cs35l56, spi->chip_select);
+	if (ret != 0)
+		return ret;
+	ret = cs35l56_irq_request(&cs35l56->base, spi->irq);
+	if (ret < 0)
+		cs35l56_hda_remove(cs35l56->base.dev);
+
+	return ret;
+}
+
+static void cs35l56_hda_spi_remove(struct spi_device *spi)
+{
+	cs35l56_hda_remove(&spi->dev);
+}
+
+static const struct spi_device_id cs35l56_hda_spi_id[] = {
+	{ "cs35l56-hda", 0 },
+	{}
+};
+
+static struct spi_driver cs35l56_hda_spi_driver = {
+	.driver = {
+		.name		= "cs35l56-hda",
+		.pm		= &cs35l56_hda_pm_ops,
+	},
+	.id_table	= cs35l56_hda_spi_id,
+	.probe		= cs35l56_hda_spi_probe,
+	.remove		= cs35l56_hda_spi_remove,
+};
+module_spi_driver(cs35l56_hda_spi_driver);
+
+MODULE_DESCRIPTION("HDA CS35L56 SPI driver");
+MODULE_IMPORT_NS(SND_HDA_SCODEC_CS35L56);
+MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED);
+MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
+MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
+MODULE_LICENSE("GPL");
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH 08/13] ASoC: cs35l56: Pass correct pointer to cs35l56_irq()
  2023-05-25 15:06 ` [PATCH 08/13] ASoC: cs35l56: Pass correct pointer to cs35l56_irq() Richard Fitzgerald
@ 2023-05-25 15:14   ` Mark Brown
  2023-05-25 15:20     ` Richard Fitzgerald
  0 siblings, 1 reply; 20+ messages in thread
From: Mark Brown @ 2023-05-25 15:14 UTC (permalink / raw)
  To: Richard Fitzgerald; +Cc: tiwai, perex, alsa-devel, linux-kernel, patches

[-- Attachment #1: Type: text/plain, Size: 259 bytes --]

On Thu, May 25, 2023 at 04:06:54PM +0100, Richard Fitzgerald wrote:
> cs35l56_irq() was changed to take a cs35l56_base* but the code
> in cs35l56-sdw was still passing it a cs35l56_private*.

For bisection this should be squashed into the change it's fixing.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 08/13] ASoC: cs35l56: Pass correct pointer to cs35l56_irq()
  2023-05-25 15:14   ` Mark Brown
@ 2023-05-25 15:20     ` Richard Fitzgerald
  0 siblings, 0 replies; 20+ messages in thread
From: Richard Fitzgerald @ 2023-05-25 15:20 UTC (permalink / raw)
  To: Mark Brown; +Cc: tiwai, perex, alsa-devel, linux-kernel, patches

On 25/5/23 16:14, Mark Brown wrote:
> On Thu, May 25, 2023 at 04:06:54PM +0100, Richard Fitzgerald wrote:
>> cs35l56_irq() was changed to take a cs35l56_base* but the code
>> in cs35l56-sdw was still passing it a cs35l56_private*.
> 
> For bisection this should be squashed into the change it's fixing.

Oh damn. I knew I had to do that and forgot.

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems
  2023-05-25 15:06 [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Richard Fitzgerald
                   ` (12 preceding siblings ...)
  2023-05-25 15:06 ` [PATCH 13/13] ALSA: hda/cs35l56: Add driver for Cirrus Logic CS35L56 amplifier Richard Fitzgerald
@ 2023-05-25 15:43 ` Mark Brown
  13 siblings, 0 replies; 20+ messages in thread
From: Mark Brown @ 2023-05-25 15:43 UTC (permalink / raw)
  To: Richard Fitzgerald; +Cc: tiwai, perex, alsa-devel, linux-kernel, patches

[-- Attachment #1: Type: text/plain, Size: 654 bytes --]

On Thu, May 25, 2023 at 04:06:46PM +0100, Richard Fitzgerald wrote:
> This set of patches adds support for using the CS35L56 boosted smart
> amplifier on HDA systems. In these systems the CS35L56 audio is
> routed through a HDA-to-I2S bridge codec.
> 
> This doesn't include the changes to the Realtek driver to actually hook
> up the CS35L56 driver, because we don't yet have the QUIRK IDs to
> associate it with. But we want to publish the driver now so that it is
> available for bringing up hardware with the CS35L56.

The ASoC bits look fine modulo the one issue I mentioned, with that
fixed

Acked-by: Mark Brown <broonie@kernel.org>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 13/13] ALSA: hda/cs35l56: Add driver for Cirrus Logic CS35L56 amplifier
  2023-05-25 15:06 ` [PATCH 13/13] ALSA: hda/cs35l56: Add driver for Cirrus Logic CS35L56 amplifier Richard Fitzgerald
@ 2023-05-26  4:40   ` Claudiu.Beznea
  2023-05-26 12:20     ` Richard Fitzgerald
  2023-05-26 15:25     ` Mark Brown
  0 siblings, 2 replies; 20+ messages in thread
From: Claudiu.Beznea @ 2023-05-26  4:40 UTC (permalink / raw)
  To: rf, tiwai, broonie, perex; +Cc: alsa-devel, linux-kernel, patches, simont

On 25.05.2023 18:06, Richard Fitzgerald wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> 
> From: Simon Trimmer <simont@opensource.cirrus.com>
> 
> Add a driver for the Cirrus Logic CS35L56 amplifier. This uses the same
> component binding API as the CS35L41 driver. This is not a standalone
> HDA device; it provides control of the CS35L56 for systems that use a
> combination of an HDA codec and CS35L56 amplifiers with audio routed
> through the HDA codec.
> 
> The CS35L56 combines a high-performance mono audio amplifier, Class-H
> tracking inductive boost converter, Halo Core(TM) DSP and a DC-DC boost
> converter supporting Class-H tracking.
> 
> Control interfaces are I2C or SPI through the standard Linux I2C or SPI
> bus framework.
> 
> Most chip functionality is controlled by on-board ROM firmware that is
> always running. Firmware patches can be applied by the driver in the form
> of a .wmfw file (firmware patch) and/or a .bin file (system tuning).
> 
> Signed-off-by: Simon Trimmer <simont@opensource.cirrus.com>
> Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
> ---
>  sound/pci/hda/Kconfig           |  31 +
>  sound/pci/hda/Makefile          |   6 +
>  sound/pci/hda/cs35l56_hda.c     | 995 ++++++++++++++++++++++++++++++++
>  sound/pci/hda/cs35l56_hda.h     |  48 ++
>  sound/pci/hda/cs35l56_hda_i2c.c |  69 +++
>  sound/pci/hda/cs35l56_hda_spi.c |  68 +++
>  6 files changed, 1217 insertions(+)
>  create mode 100644 sound/pci/hda/cs35l56_hda.c
>  create mode 100644 sound/pci/hda/cs35l56_hda.h
>  create mode 100644 sound/pci/hda/cs35l56_hda_i2c.c
>  create mode 100644 sound/pci/hda/cs35l56_hda_spi.c
> 
> diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig
> index 886255a03e8b..0f2e941ce646 100644
> --- a/sound/pci/hda/Kconfig
> +++ b/sound/pci/hda/Kconfig
> @@ -130,6 +130,37 @@ config SND_HDA_SCODEC_CS35L41_SPI
>  comment "Set to Y if you want auto-loading the side codec driver"
>         depends on SND_HDA=y && SND_HDA_SCODEC_CS35L41_SPI=m
> 
> +config SND_HDA_SCODEC_CS35L56
> +       tristate
> +
> +config SND_HDA_SCODEC_CS35L56_I2C
> +       tristate "Build CS35L56 HD-audio side codec support for I2C Bus"
> +       depends on I2C
> +       depends on ACPI || COMPILE_TEST
> +       depends on SND_SOC
> +       select CS_DSP
> +       select SND_HDA_GENERIC
> +       select SND_SOC_CS35L56_SHARED
> +       select SND_HDA_SCODEC_CS35L56
> +       select SND_HDA_CS_DSP_CONTROLS
> +       help
> +         Say Y or M here to include CS35L56 amplifier support with
> +         I2C control.
> +
> +config SND_HDA_SCODEC_CS35L56_SPI
> +       tristate "Build CS35L56 HD-audio codec support for SPI Bus"
> +       depends on SPI_MASTER
> +       depends on ACPI || COMPILE_TEST
> +       depends on SND_SOC
> +       select CS_DSP
> +       select SND_HDA_GENERIC
> +       select SND_SOC_CS35L56_SHARED
> +       select SND_HDA_SCODEC_CS35L56
> +       select SND_HDA_CS_DSP_CONTROLS
> +       help
> +         Say Y or M here to include CS35L56 amplifier support with
> +         SPI control.
> +
>  config SND_HDA_CODEC_REALTEK
>         tristate "Build Realtek HD-audio codec support"
>         select SND_HDA_GENERIC
> diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile
> index 00d306104484..c6e6509e7b8e 100644
> --- a/sound/pci/hda/Makefile
> +++ b/sound/pci/hda/Makefile
> @@ -31,6 +31,9 @@ snd-hda-codec-hdmi-objs :=    patch_hdmi.o hda_eld.o
>  snd-hda-scodec-cs35l41-objs :=         cs35l41_hda.o
>  snd-hda-scodec-cs35l41-i2c-objs :=     cs35l41_hda_i2c.o
>  snd-hda-scodec-cs35l41-spi-objs :=     cs35l41_hda_spi.o
> +snd-hda-scodec-cs35l56-objs :=         cs35l56_hda.o
> +snd-hda-scodec-cs35l56-i2c-objs :=     cs35l56_hda_i2c.o
> +snd-hda-scodec-cs35l56-spi-objs :=     cs35l56_hda_spi.o
>  snd-hda-cs-dsp-ctls-objs :=            hda_cs_dsp_ctl.o
> 
>  # common driver
> @@ -55,6 +58,9 @@ obj-$(CONFIG_SND_HDA_CODEC_HDMI) += snd-hda-codec-hdmi.o
>  obj-$(CONFIG_SND_HDA_SCODEC_CS35L41) += snd-hda-scodec-cs35l41.o
>  obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_I2C) += snd-hda-scodec-cs35l41-i2c.o
>  obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_SPI) += snd-hda-scodec-cs35l41-spi.o
> +obj-$(CONFIG_SND_HDA_SCODEC_CS35L56) += snd-hda-scodec-cs35l56.o
> +obj-$(CONFIG_SND_HDA_SCODEC_CS35L56_I2C) += snd-hda-scodec-cs35l56-i2c.o
> +obj-$(CONFIG_SND_HDA_SCODEC_CS35L56_SPI) += snd-hda-scodec-cs35l56-spi.o
>  obj-$(CONFIG_SND_HDA_CS_DSP_CONTROLS) += snd-hda-cs-dsp-ctls.o
> 
>  # this must be the last entry after codec drivers;
> diff --git a/sound/pci/hda/cs35l56_hda.c b/sound/pci/hda/cs35l56_hda.c
> new file mode 100644
> index 000000000000..5189f1e89a87
> --- /dev/null
> +++ b/sound/pci/hda/cs35l56_hda.c
> @@ -0,0 +1,995 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +//
> +// HDA audio driver for Cirrus Logic CS35L56 smart amp
> +//
> +// Copyright (C) 2023 Cirrus Logic, Inc. and
> +//                    Cirrus Logic International Semiconductor Ltd.
> +//
> +
> +#include <linux/acpi.h>
> +#include <linux/debugfs.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/module.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <sound/core.h>
> +#include <sound/hda_codec.h>
> +#include <sound/tlv.h>
> +#include "cs35l56_hda.h"
> +#include "hda_component.h"
> +#include "hda_cs_dsp_ctl.h"
> +#include "hda_generic.h"
> +
> + /*
> +  * The cs35l56_hda_dai_config[] reg sequence configures the device as
> +  *  ASP1_BCLK_FREQ = 3.072 MHz
> +  *  ASP1_RX_WIDTH = 32 cycles per slot, ASP1_TX_WIDTH = 32 cycles per slot, ASP1_FMT = I2S
> +  *  ASP1_DOUT_HIZ_CONTROL = Hi-Z during unused timeslots
> +  *  ASP1_RX_WL = 24 bits per sample
> +  *  ASP1_TX_WL = 24 bits per sample
> +  *  ASP1_RXn_EN 1..3 and ASP1_TXn_EN 1..4 disabled
> +  */
> +static const struct reg_sequence cs35l56_hda_dai_config[] = {
> +       { CS35L56_ASP1_CONTROL1,        0x00000021 },
> +       { CS35L56_ASP1_CONTROL2,        0x20200200 },
> +       { CS35L56_ASP1_CONTROL3,        0x00000003 },
> +       { CS35L56_ASP1_DATA_CONTROL5,   0x00000018 },
> +       { CS35L56_ASP1_DATA_CONTROL1,   0x00000018 },
> +       { CS35L56_ASP1_ENABLES1,        0x00000000 },
> +};
> +
> +static void cs35l56_hda_play(struct cs35l56_hda *cs35l56)
> +{
> +       unsigned int val;
> +       int ret;
> +
> +       pm_runtime_get_sync(cs35l56->base.dev);
> +       ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_PLAY);
> +       if (ret == 0) {
> +               /* Wait for firmware to enter PS0 power state */
> +               ret = regmap_read_poll_timeout(cs35l56->base.regmap,
> +                                              CS35L56_TRANSDUCER_ACTUAL_PS,
> +                                              val, (val == CS35L56_PS0),
> +                                              CS35L56_PS0_POLL_US,
> +                                              CS35L56_PS0_TIMEOUT_US);
> +               if (ret)
> +                       dev_warn(cs35l56->base.dev, "PS0 wait failed: %d\n", ret);
> +       }
> +       regmap_set_bits(cs35l56->base.regmap, CS35L56_ASP1_ENABLES1,
> +                       BIT(CS35L56_ASP_RX1_EN_SHIFT) | BIT(CS35L56_ASP_RX2_EN_SHIFT) |
> +                       cs35l56->asp_tx_mask);
> +       cs35l56->playing = true;
> +}
> +
> +static void cs35l56_hda_pause(struct cs35l56_hda *cs35l56)
> +{
> +       cs35l56->playing = false;
> +       cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_PAUSE);
> +       regmap_clear_bits(cs35l56->base.regmap, CS35L56_ASP1_ENABLES1,
> +                         BIT(CS35L56_ASP_RX1_EN_SHIFT) | BIT(CS35L56_ASP_RX2_EN_SHIFT) |
> +                         BIT(CS35L56_ASP_TX1_EN_SHIFT) | BIT(CS35L56_ASP_TX2_EN_SHIFT) |
> +                         BIT(CS35L56_ASP_TX3_EN_SHIFT) | BIT(CS35L56_ASP_TX4_EN_SHIFT));
> +
> +       pm_runtime_mark_last_busy(cs35l56->base.dev);
> +       pm_runtime_put_autosuspend(cs35l56->base.dev);
> +}
> +
> +static void cs35l56_hda_playback_hook(struct device *dev, int action)
> +{
> +       struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
> +
> +       dev_dbg(cs35l56->base.dev, "%s()%d: action: %d\n", __func__, __LINE__, action);
> +
> +       switch (action) {
> +       case HDA_GEN_PCM_ACT_PREPARE:
> +               if (cs35l56->playing)
> +                       break;
> +
> +               /* If we're suspended: flag that resume should start playback */
> +               if (cs35l56->suspended) {
> +                       cs35l56->playing = true;
> +                       break;
> +               }
> +
> +               cs35l56_hda_play(cs35l56);
> +               break;
> +       case HDA_GEN_PCM_ACT_CLEANUP:
> +               if (!cs35l56->playing)
> +                       break;
> +
> +               cs35l56_hda_pause(cs35l56);
> +               break;
> +       default:
> +               break;
> +       }
> +}
> +
> +static void cs35l56_hda_mute_hook(struct device *dev, bool mute)
> +{
> +       struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
> +       unsigned int val;
> +
> +       if (mute)
> +               val = CS35L56_MAIN_RENDER_USER_MUTE_MASK;
> +       else
> +               val = 0;
> +
> +       regmap_write(cs35l56->base.regmap, CS35L56_MAIN_RENDER_USER_MUTE, val);
> +}
> +
> +static int cs35l56_hda_runtime_suspend(struct device *dev)
> +{
> +       struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
> +
> +       if (cs35l56->cs_dsp.booted)
> +               cs_dsp_stop(&cs35l56->cs_dsp);
> +
> +       return cs35l56_runtime_suspend_common(&cs35l56->base);
> +}
> +
> +static int cs35l56_hda_runtime_resume(struct device *dev)
> +{
> +       struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
> +       int ret;
> +
> +       ret = cs35l56_runtime_resume_common(&cs35l56->base, false);
> +       if (ret < 0)
> +               return ret;
> +
> +       if (cs35l56->cs_dsp.booted) {
> +               ret = cs_dsp_run(&cs35l56->cs_dsp);
> +               if (ret) {
> +                       dev_dbg(cs35l56->base.dev, "%s: cs_dsp_run ret %d\n", __func__, ret);
> +                       goto err;
> +               }
> +       }
> +
> +       return 0;
> +
> +err:
> +       cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_ALLOW_AUTO_HIBERNATE);
> +       regmap_write(cs35l56->base.regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
> +                    CS35L56_MBOX_CMD_HIBERNATE_NOW);
> +
> +       regcache_cache_only(cs35l56->base.regmap, true);
> +
> +       return ret;
> +}
> +
> +static int cs35l56_hda_mixer_info(struct snd_kcontrol *kcontrol,
> +                                 struct snd_ctl_elem_info *uinfo)
> +{
> +       uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
> +       uinfo->count = 1;
> +       uinfo->value.enumerated.items = CS35L56_NUM_INPUT_SRC;
> +       if (uinfo->value.enumerated.item >= CS35L56_NUM_INPUT_SRC)
> +               uinfo->value.enumerated.item = CS35L56_NUM_INPUT_SRC - 1;
> +       strcpy(uinfo->value.enumerated.name, cs35l56_tx_input_texts[uinfo->value.enumerated.item]);
> +
> +       return 0;
> +}
> +
> +static int cs35l56_hda_mixer_get(struct snd_kcontrol *kcontrol,
> +                                struct snd_ctl_elem_value *ucontrol)
> +{
> +       struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
> +       unsigned int reg_val;
> +       int i;
> +
> +       regmap_read(cs35l56->base.regmap, kcontrol->private_value, &reg_val);
> +       reg_val &= CS35L56_ASP_TXn_SRC_MASK;
> +
> +       for (i = 0; i < CS35L56_NUM_INPUT_SRC; ++i) {
> +               if (cs35l56_tx_input_values[i] == reg_val) {
> +                       ucontrol->value.enumerated.item[0] = i;
> +                       break;
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static int cs35l56_hda_mixer_put(struct snd_kcontrol *kcontrol,
> +                                struct snd_ctl_elem_value *ucontrol)
> +{
> +       struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
> +       unsigned int item = ucontrol->value.enumerated.item[0];
> +       bool changed;
> +
> +       if (item >= CS35L56_NUM_INPUT_SRC)
> +               return -EINVAL;
> +
> +       regmap_update_bits_check(cs35l56->base.regmap, kcontrol->private_value,
> +                                CS35L56_INPUT_MASK, cs35l56_tx_input_values[item],
> +                                &changed);
> +
> +       return changed;
> +}
> +
> +static int cs35l56_hda_posture_info(struct snd_kcontrol *kcontrol,
> +                                   struct snd_ctl_elem_info *uinfo)
> +{
> +       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
> +       uinfo->count = 1;
> +       uinfo->value.integer.min = CS35L56_MAIN_POSTURE_MIN;
> +       uinfo->value.integer.max = CS35L56_MAIN_POSTURE_MAX;
> +       return 0;
> +}
> +
> +static int cs35l56_hda_posture_get(struct snd_kcontrol *kcontrol,
> +                                  struct snd_ctl_elem_value *ucontrol)
> +{
> +       struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
> +       unsigned int pos;
> +       int ret;
> +
> +       ret = regmap_read(cs35l56->base.regmap, CS35L56_MAIN_POSTURE_NUMBER, &pos);
> +       if (ret)
> +               return ret;
> +
> +       ucontrol->value.integer.value[0] = pos;
> +
> +       return ret;
> +}
> +
> +static int cs35l56_hda_posture_put(struct snd_kcontrol *kcontrol,
> +                                  struct snd_ctl_elem_value *ucontrol)
> +{
> +       struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
> +       unsigned long pos = ucontrol->value.integer.value[0];
> +       bool changed;
> +       int ret;
> +
> +       if ((pos < CS35L56_MAIN_POSTURE_MIN) ||
> +           (pos > CS35L56_MAIN_POSTURE_MAX))
> +               return -EINVAL;
> +
> +       ret = regmap_update_bits_check(cs35l56->base.regmap,
> +                                      CS35L56_MAIN_POSTURE_NUMBER,
> +                                      CS35L56_MAIN_POSTURE_MASK,
> +                                      pos, &changed);
> +       if (ret)
> +               return ret;
> +
> +       return changed;
> +}
> +
> +static const struct {
> +       const char *name;
> +       unsigned int reg;
> +} cs35l56_hda_mixer_controls[] = {
> +       { "ASP1 TX1 Source", CS35L56_ASP1TX1_INPUT },
> +       { "ASP1 TX2 Source", CS35L56_ASP1TX2_INPUT },
> +       { "ASP1 TX3 Source", CS35L56_ASP1TX3_INPUT },
> +       { "ASP1 TX4 Source", CS35L56_ASP1TX4_INPUT },
> +};
> +
> +static const DECLARE_TLV_DB_SCALE(cs35l56_hda_vol_tlv, -10000, 25, 0);
> +
> +static int cs35l56_hda_vol_info(struct snd_kcontrol *kcontrol,
> +                               struct snd_ctl_elem_info *uinfo)
> +{
> +       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
> +       uinfo->count = 1;
> +       uinfo->value.integer.step = 1;
> +       uinfo->value.integer.min = 0;
> +       uinfo->value.integer.max = CS35L56_MAIN_RENDER_USER_VOLUME_MAX -
> +                                  CS35L56_MAIN_RENDER_USER_VOLUME_MIN;
> +
> +       return 0;
> +}
> +
> +static int cs35l56_hda_vol_get(struct snd_kcontrol *kcontrol,
> +                              struct snd_ctl_elem_value *ucontrol)
> +{
> +       struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
> +       unsigned int raw_vol;
> +       int vol;
> +       int ret;
> +
> +       ret = regmap_read(cs35l56->base.regmap, CS35L56_MAIN_RENDER_USER_VOLUME, &raw_vol);
> +
> +       if (ret)
> +               return ret;
> +
> +       vol = (s16)(raw_vol & 0xFFFF);
> +       vol >>= CS35L56_MAIN_RENDER_USER_VOLUME_SHIFT;
> +
> +       if (vol & BIT(CS35L56_MAIN_RENDER_USER_VOLUME_SIGNBIT))
> +               vol |= ~((int)(BIT(CS35L56_MAIN_RENDER_USER_VOLUME_SIGNBIT) - 1));
> +
> +       ucontrol->value.integer.value[0] = vol - CS35L56_MAIN_RENDER_USER_VOLUME_MIN;
> +
> +       return 0;
> +}
> +
> +static int cs35l56_hda_vol_put(struct snd_kcontrol *kcontrol,
> +                              struct snd_ctl_elem_value *ucontrol)
> +{
> +       struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
> +       long vol = ucontrol->value.integer.value[0];
> +       unsigned int raw_vol;
> +       bool changed;
> +       int ret;
> +
> +       if ((vol < 0) || (vol > (CS35L56_MAIN_RENDER_USER_VOLUME_MAX -
> +                                CS35L56_MAIN_RENDER_USER_VOLUME_MIN)))
> +               return -EINVAL;
> +
> +       raw_vol = (vol + CS35L56_MAIN_RENDER_USER_VOLUME_MIN) <<
> +                 CS35L56_MAIN_RENDER_USER_VOLUME_SHIFT;
> +
> +       ret = regmap_update_bits_check(cs35l56->base.regmap,
> +                                      CS35L56_MAIN_RENDER_USER_VOLUME,
> +                                      CS35L56_MAIN_RENDER_USER_VOLUME_MASK,
> +                                      raw_vol, &changed);
> +       if (ret)
> +               return ret;
> +
> +       return changed;
> +}
> +
> +static void cs35l56_hda_create_controls(struct cs35l56_hda *cs35l56)
> +{
> +       struct snd_kcontrol_new ctl_template = {
> +               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
> +               .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
> +               .info = cs35l56_hda_posture_info,
> +               .get = cs35l56_hda_posture_get,
> +               .put = cs35l56_hda_posture_put,
> +       };
> +       char name[64];
> +       int i;
> +
> +       snprintf(name, sizeof(name), "%s Posture Number", cs35l56->amp_name);
> +       ctl_template.name = name;
> +       cs35l56->posture_ctl = snd_ctl_new1(&ctl_template, cs35l56);
> +       if (snd_ctl_add(cs35l56->codec->card, cs35l56->posture_ctl)) {
> +               dev_err(cs35l56->base.dev, "Failed to add KControl: %s\n", ctl_template.name);
> +               return;
> +       }
> +
> +       /* Mixer controls */
> +       ctl_template.info = cs35l56_hda_mixer_info;
> +       ctl_template.get = cs35l56_hda_mixer_get;
> +       ctl_template.put = cs35l56_hda_mixer_put;
> +
> +       BUILD_BUG_ON(ARRAY_SIZE(cs35l56->mixer_ctl) != ARRAY_SIZE(cs35l56_hda_mixer_controls));
> +
> +       for (i = 0; i < ARRAY_SIZE(cs35l56_hda_mixer_controls); ++i) {
> +               snprintf(name, sizeof(name), "%s %s", cs35l56->amp_name,
> +                        cs35l56_hda_mixer_controls[i].name);
> +               ctl_template.private_value = cs35l56_hda_mixer_controls[i].reg;
> +               cs35l56->mixer_ctl[i] = snd_ctl_new1(&ctl_template, cs35l56);
> +               if (snd_ctl_add(cs35l56->codec->card, cs35l56->mixer_ctl[i])) {
> +                       dev_err(cs35l56->base.dev, "Failed to add KControl: %s\n",
> +                               ctl_template.name);
> +                       return;
> +               }
> +       }
> +
> +       ctl_template.info = cs35l56_hda_vol_info;
> +       ctl_template.get = cs35l56_hda_vol_get;
> +       ctl_template.put = cs35l56_hda_vol_put;
> +       ctl_template.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ);
> +       ctl_template.tlv.p = cs35l56_hda_vol_tlv;
> +       snprintf(name, sizeof(name), "%s Speaker Playback Volume", cs35l56->amp_name);
> +       ctl_template.name = name;
> +       cs35l56->volume_ctl = snd_ctl_new1(&ctl_template, cs35l56);
> +       if (snd_ctl_add(cs35l56->codec->card, cs35l56->volume_ctl)) {
> +               dev_err(cs35l56->base.dev, "Failed to add KControl: %s\n", ctl_template.name);
> +               return;

No need for return here.

> +       }
> +}
> +
> +static void cs35l56_hda_remove_controls(struct cs35l56_hda *cs35l56)
> +{
> +       int i;
> +
> +       for (i = ARRAY_SIZE(cs35l56->mixer_ctl) - 1; i >= 0; i--)
> +               snd_ctl_remove(cs35l56->codec->card, cs35l56->mixer_ctl[i]);
> +
> +       snd_ctl_remove(cs35l56->codec->card, cs35l56->posture_ctl);
> +       snd_ctl_remove(cs35l56->codec->card, cs35l56->volume_ctl);
> +}
> +
> +static const struct cs_dsp_client_ops cs35l56_hda_client_ops = {
> +       .control_remove = hda_cs_dsp_control_remove,
> +};
> +
> +static int cs35l56_hda_request_firmware_file(struct cs35l56_hda *cs35l56,
> +                                            const struct firmware **firmware, char **filename,
> +                                            const char *dir, const char *system_name,
> +                                            const char *amp_name,
> +                                            const char *filetype)
> +{
> +       char *s, c;
> +       int ret = 0;
> +
> +       if (system_name && amp_name)
> +               *filename = kasprintf(GFP_KERNEL, "%scs35l56%s-%02x-dsp1-misc-%s-%s.%s", dir,
> +                                     cs35l56->base.secured ? "s" : "", cs35l56->base.rev,
> +                                     system_name, amp_name, filetype);
> +       else if (system_name)
> +               *filename = kasprintf(GFP_KERNEL, "%scs35l56%s-%02x-dsp1-misc-%s.%s", dir,
> +                                     cs35l56->base.secured ? "s" : "", cs35l56->base.rev,
> +                                     system_name, filetype);
> +       else
> +               *filename = kasprintf(GFP_KERNEL, "%scs35l56%s-%02x-dsp1-misc.%s", dir,
> +                                     cs35l56->base.secured ? "s" : "", cs35l56->base.rev,
> +                                     filetype);
> +
> +       if (!*filename)
> +               return -ENOMEM;
> +
> +       /*
> +        * Make sure that filename is lower-case and any non alpha-numeric
> +        * characters except full stop and forward slash are replaced with
> +        * hyphens.
> +        */
> +       s = *filename;
> +       while (*s) {
> +               c = *s;
> +               if (isalnum(c))
> +                       *s = tolower(c);
> +               else if (c != '.' && c != '/')
> +                       *s = '-';
> +               s++;
> +       }
> +
> +       ret = firmware_request_nowarn(firmware, *filename, cs35l56->base.dev);
> +       if (ret != 0) {

if (ret)

> +               dev_dbg(cs35l56->base.dev, "Failed to request '%s'\n", *filename);
> +               kfree(*filename);
> +               *filename = NULL;
> +       } else {
> +               dev_dbg(cs35l56->base.dev, "Found '%s'\n", *filename);
> +       }
> +
> +       return ret;
> +}
> +
> +static const char cirrus_dir[] = "cirrus/";
> +static int cs35l56_hda_request_firmware_files(struct cs35l56_hda *cs35l56,
> +                                             const struct firmware **wmfw_firmware,
> +                                             char **wmfw_filename,
> +                                             const struct firmware **coeff_firmware,
> +                                             char **coeff_filename)
> +{
> +       const char *system_name = cs35l56->system_name;
> +       const char *amp_name = cs35l56->amp_name;
> +       int ret;
> +
> +       if (system_name && amp_name) {
> +               if (!cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_filename,
> +                                                      cirrus_dir, system_name, amp_name, "wmfw")) {
> +                       cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
> +                                                         cirrus_dir, system_name, amp_name, "bin");
> +                       return 0;
> +               }
> +       }
> +
> +       if (system_name) {
> +               if (!cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_filename,
> +                                                      cirrus_dir, system_name, NULL, "wmfw")) {
> +                       if (amp_name)
> +                               cs35l56_hda_request_firmware_file(cs35l56,
> +                                                                 coeff_firmware, coeff_filename,
> +                                                                 cirrus_dir, system_name,
> +                                                                 amp_name, "bin");
> +                       if (!*coeff_firmware)
> +                               cs35l56_hda_request_firmware_file(cs35l56,
> +                                                                 coeff_firmware, coeff_filename,
> +                                                                 cirrus_dir, system_name,
> +                                                                 NULL, "bin");
> +                       return 0;
> +               }
> +       }
> +
> +       ret = cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_filename,
> +                                               cirrus_dir, NULL, NULL, "wmfw");
> +       if (!ret) {
> +               cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
> +                                                 cirrus_dir, NULL, NULL, "bin");
> +               return 0;
> +       }
> +
> +       /* When a firmware file is not found must still search for the coeff files */
> +       if (system_name) {
> +               if (amp_name)
> +                       cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
> +                                                         cirrus_dir, system_name, amp_name, "bin");
> +               if (!*coeff_firmware)
> +                       cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
> +                                                         cirrus_dir, system_name, NULL, "bin");
> +       }
> +
> +       if (!*coeff_firmware)
> +               cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
> +                                                 cirrus_dir, NULL, NULL, "bin");
> +
> +       return 0;
> +}
> +
> +static void cs35l56_hda_add_dsp_controls(struct cs35l56_hda *cs35l56)
> +{
> +       struct hda_cs_dsp_ctl_info info;
> +
> +       info.device_name = cs35l56->amp_name;
> +       info.fw_type = HDA_CS_DSP_FW_MISC;
> +       info.card = cs35l56->codec->card;
> +
> +       hda_cs_dsp_add_controls(&cs35l56->cs_dsp, &info);
> +}
> +
> +static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56)
> +{
> +       const struct firmware *coeff_firmware = NULL;
> +       const struct firmware *wmfw_firmware = NULL;
> +       char *coeff_filename = NULL;
> +       char *wmfw_filename = NULL;
> +       int ret = 0;
> +
> +       mutex_lock(&cs35l56->base.irq_lock);
> +       pm_runtime_get_sync(cs35l56->base.dev);
> +
> +       /*
> +        * When the device is running in secure mode the firmware files can
> +        * only contain insecure tunings and therefore we do not need to
> +        * shutdown the firmware to apply them and can use the lower cost
> +        * reinit sequence instead.
> +        */
> +       if (!cs35l56->base.secured) {
> +               ret = cs35l56_firmware_shutdown(&cs35l56->base);
> +               if (ret)
> +                       goto err;
> +       }
> +
> +       cs35l56_hda_request_firmware_files(cs35l56, &wmfw_firmware, &wmfw_filename,
> +                                          &coeff_firmware, &coeff_filename);
> +
> +       ret = cs_dsp_power_up(&cs35l56->cs_dsp, wmfw_firmware, wmfw_filename,
> +                             coeff_firmware, coeff_filename, "misc");
> +       if (ret) {
> +               dev_dbg(cs35l56->base.dev, "%s: cs_dsp_power_up ret %d\n", __func__, ret);
> +               goto err;
> +       }
> +
> +       if (wmfw_filename)
> +               dev_dbg(cs35l56->base.dev, "Loaded WMFW Firmware: %s\n", wmfw_filename);
> +
> +       if (coeff_filename)
> +               dev_dbg(cs35l56->base.dev, "Loaded Coefficients: %s\n", coeff_filename);
> +
> +       ret = cs_dsp_run(&cs35l56->cs_dsp);
> +       if (ret) {
> +               dev_dbg(cs35l56->base.dev, "%s: cs_dsp_run ret %d\n", __func__, ret);
> +               goto err;
> +       }
> +
> +       if (cs35l56->base.secured) {
> +               ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT);
> +               if (ret)
> +                       goto err;
> +       } else {
> +               /* Reset the device and wait for it to boot */
> +               cs35l56_system_reset(&cs35l56->base, false);
> +               ret = cs35l56_wait_for_firmware_boot(&cs35l56->base);
> +               if (ret)
> +                       goto err;
> +       }
> +
> +       /* Disable auto-hibernate so that runtime_pm has control */
> +       ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE);
> +       if (ret)
> +               goto err;
> +
> +       /* Re-read the values from the device after a firmware/cofficient download */
> +       cs35l56_reread_firmware_registers(&cs35l56->base);
> +
> +       regcache_mark_dirty(cs35l56->base.regmap);
> +       regcache_sync(cs35l56->base.regmap);
> +
> +       regmap_clear_bits(cs35l56->base.regmap, CS35L56_PROTECTION_STATUS,
> +                         CS35L56_FIRMWARE_MISSING);
> +       cs35l56->base.fw_patched = true;
> +err:
> +       pm_runtime_put(cs35l56->base.dev);
> +       mutex_unlock(&cs35l56->base.irq_lock);
> +
> +       return ret;
> +}
> +
> +static int cs35l56_hda_bind(struct device *dev, struct device *master, void *master_data)
> +{
> +       struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
> +       struct hda_component *comps = master_data;
> +       int ret;
> +
> +       if (!comps || cs35l56->index < 0 || cs35l56->index >= HDA_MAX_COMPONENTS)
> +               return -EINVAL;
> +
> +       comps = &comps[cs35l56->index];
> +       if (comps->dev)
> +               return -EBUSY;
> +
> +       comps->dev = dev;
> +       cs35l56->codec = comps->codec;
> +       strscpy(comps->name, dev_name(dev), sizeof(comps->name));
> +       comps->playback_hook = cs35l56_hda_playback_hook;
> +       comps->mute_hook = cs35l56_hda_mute_hook;
> +
> +       ret = cs35l56_hda_fw_load(cs35l56);
> +       if (ret)
> +               return ret;
> +
> +       cs35l56_hda_create_controls(cs35l56);
> +       cs35l56_hda_add_dsp_controls(cs35l56);
> +
> +#if IS_ENABLED(CONFIG_SND_DEBUG)
> +       cs35l56->debugfs_root = debugfs_create_dir(dev_name(cs35l56->base.dev), sound_debugfs_root);
> +       cs_dsp_init_debugfs(&cs35l56->cs_dsp, cs35l56->debugfs_root);
> +#endif
> +
> +       dev_dbg(cs35l56->base.dev, "Bound\n");
> +
> +       return 0;
> +}
> +
> +static void cs35l56_hda_unbind(struct device *dev, struct device *master, void *master_data)
> +{
> +       struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
> +       struct hda_component *comps = master_data;
> +
> +       cs35l56_hda_remove_controls(cs35l56);
> +
> +#if IS_ENABLED(CONFIG_SND_DEBUG)
> +       cs_dsp_cleanup_debugfs(&cs35l56->cs_dsp);
> +       debugfs_remove_recursive(cs35l56->debugfs_root);
> +#endif
> +
> +       cs_dsp_remove(&cs35l56->cs_dsp);
> +
> +       if (comps[cs35l56->index].dev == dev)
> +               memset(&comps[cs35l56->index], 0, sizeof(*comps));
> +
> +       dev_dbg(cs35l56->base.dev, "Unbound\n");
> +}
> +
> +static const struct component_ops cs35l56_hda_comp_ops = {
> +       .bind = cs35l56_hda_bind,
> +       .unbind = cs35l56_hda_unbind,
> +};
> +
> +static int __maybe_unused cs35l56_hda_system_suspend(struct device *dev)

You can get rid of __maybe_unused here if using SYSTEM_SLEEP_PM_OPS(). Same
for the resume counterpart function.

> +{
> +       struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
> +
> +       if (cs35l56->playing)
> +               cs35l56_hda_pause(cs35l56);
> +
> +       cs35l56->suspended = true;
> +
> +       /*
> +        * The interrupt line is normally shared, but after we start suspending
> +        * we can't check if our device is the source of an interrupt, and can't
> +        * clear it. Prevent this race by temporarily disabling the parent irq
> +        * until we reach _no_irq.
> +        */
> +       if (cs35l56->base.irq)
> +               disable_irq(cs35l56->base.irq);
> +
> +       return pm_runtime_force_suspend(dev);
> +}
> +
> +static int __maybe_unused cs35l56_hda_system_suspend_late(struct device *dev)

There shoud be no need of __maybe_unused here. Same for the resume
counterpart function.

> +{
> +       struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
> +
> +       /*
> +        * RESET is usually shared by all amps so it must not be asserted until
> +        * all driver instances have done their suspend() stage.
> +        */
> +       if (cs35l56->base.reset_gpio) {
> +               gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
> +               cs35l56_wait_min_reset_pulse();
> +       }
> +
> +       return 0;
> +}
> +
> +static int __maybe_unused cs35l56_hda_system_suspend_no_irq(struct device *dev)

Same here.

> +{
> +       struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
> +
> +       /* Handlers are now disabled so the parent IRQ can safely be re-enabled. */
> +       if (cs35l56->base.irq)
> +               enable_irq(cs35l56->base.irq);
> +
> +       return 0;
> +}
> +
> +static int __maybe_unused cs35l56_hda_system_resume_no_irq(struct device *dev)
> +{
> +       struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
> +
> +       /*
> +        * WAKE interrupts unmask if the CS35L56 hibernates, which can cause
> +        * spurious interrupts, and the interrupt line is normally shared.
> +        * We can't check if our device is the source of an interrupt, and can't
> +        * clear it, until it has fully resumed. Prevent this race by temporarily
> +        * disabling the parent irq until we complete resume().
> +        */
> +       if (cs35l56->base.irq)
> +               disable_irq(cs35l56->base.irq);
> +
> +       return 0;
> +}
> +
> +static int __maybe_unused cs35l56_hda_system_resume_early(struct device *dev)
> +{
> +       struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
> +
> +       /* Ensure a spec-compliant RESET pulse. */
> +       if (cs35l56->base.reset_gpio) {
> +               gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
> +               cs35l56_wait_min_reset_pulse();
> +
> +               /* Release shared RESET before drivers start resume(). */
> +               gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 1);
> +               cs35l56_wait_control_port_ready();
> +       }
> +
> +       return 0;
> +}
> +
> +static int __maybe_unused cs35l56_hda_system_resume(struct device *dev)
> +{
> +       struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
> +       int ret;
> +
> +       /* Undo pm_runtime_force_suspend() before re-enabling the irq */
> +       ret = pm_runtime_force_resume(dev);
> +       if (cs35l56->base.irq)
> +               enable_irq(cs35l56->base.irq);
> +
> +       if (ret)
> +               return ret;
> +
> +       cs35l56->suspended = false;
> +
> +       ret = cs35l56_is_fw_reload_needed(&cs35l56->base);
> +       dev_dbg(cs35l56->base.dev, "fw_reload_needed: %d\n", ret);
> +       if (ret > 0) {
> +               ret = cs35l56_hda_fw_load(cs35l56);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       if (cs35l56->playing)
> +               cs35l56_hda_play(cs35l56);
> +
> +       return 0;
> +}
> +
> +static int cs35l56_hda_read_acpi(struct cs35l56_hda *cs35l56, int id)
> +{
> +       u32 values[HDA_MAX_COMPONENTS];
> +       struct acpi_device *adev;
> +       const char *property, *sub;
> +       size_t nval;
> +       int i, ret;
> +
> +       /*
> +        * ACPI_COMPANION isn't available when this driver was instantiated by
> +        * the serial-multi-instantiate driver, so lookup the node by HID
> +        */
> +       if (!ACPI_COMPANION(cs35l56->base.dev)) {
> +               adev = acpi_dev_get_first_match_dev("CSC3556", NULL, -1);
> +               if (!adev) {
> +                       dev_err(cs35l56->base.dev, "Failed to find an ACPI device for %s\n",
> +                               dev_name(cs35l56->base.dev));
> +                       return -ENODEV;
> +               }
> +               ACPI_COMPANION_SET(cs35l56->base.dev, adev);
> +       }
> +
> +       property = "cirrus,dev-index";
> +       ret = device_property_count_u32(cs35l56->base.dev, property);
> +       if (ret <= 0)
> +               goto err;
> +
> +       if (ret > ARRAY_SIZE(values)) {
> +               ret = -EINVAL;
> +               goto err;
> +       }
> +       nval = ret;
> +
> +       ret = device_property_read_u32_array(cs35l56->base.dev, property, values, nval);
> +       if (ret)
> +               goto err;
> +
> +       cs35l56->index = -1;
> +       for (i = 0; i < nval; i++) {
> +               if (values[i] == id) {
> +                       cs35l56->index = i;
> +                       break;
> +               }
> +       }
> +       if (cs35l56->index == -1) {
> +               dev_err(cs35l56->base.dev, "No index found in %s\n", property);
> +               ret = -ENODEV;
> +               goto err;
> +       }
> +
> +       sub = acpi_get_subsystem_id(ACPI_HANDLE(cs35l56->base.dev));
> +
> +       if (IS_ERR(sub)) {
> +               /* If no ACPI SUB, return 0 and fallback to legacy firmware path, otherwise fail */
> +               if (PTR_ERR(sub) == -ENODATA)
> +                       return 0;
> +               else
> +                       return PTR_ERR(sub);
> +       }
> +
> +       cs35l56->system_name = sub;
> +
> +       cs35l56->base.reset_gpio = devm_gpiod_get_index_optional(cs35l56->base.dev,
> +                                                                "reset",
> +                                                                cs35l56->index,
> +                                                                GPIOD_OUT_LOW);
> +       if (IS_ERR(cs35l56->base.reset_gpio)) {

devm_gpiod_get_index_optional() can also return NULL.

> +               ret = PTR_ERR(cs35l56->base.reset_gpio);
> +
> +               /*
> +                * If RESET is shared the first amp to probe will grab the reset
> +                * line and reset all the amps
> +                */
> +               if (ret != -EBUSY)
> +                       return dev_err_probe(cs35l56->base.dev, ret, "Failed to get reset GPIO\n");
> +
> +               dev_info(cs35l56->base.dev, "Reset GPIO busy, assume shared reset\n");
> +               cs35l56->base.reset_gpio = NULL;
> +       }
> +
> +       return 0;
> +
> +err:
> +       dev_err(cs35l56->base.dev, "Failed property %s: %d\n", property, ret);
> +
> +       return ret;
> +}
> +
> +int cs35l56_hda_common_probe(struct cs35l56_hda *cs35l56, int id)
> +{
> +       int ret;
> +
> +       mutex_init(&cs35l56->base.irq_lock);
> +       dev_set_drvdata(cs35l56->base.dev, cs35l56);
> +
> +       ret = cs35l56_hda_read_acpi(cs35l56, id);
> +       if (ret) {
> +               dev_err_probe(cs35l56->base.dev, ret, "Platform not supported\n");
> +               goto err;
> +       }
> +
> +       cs35l56->amp_name = devm_kasprintf(cs35l56->base.dev, GFP_KERNEL, "AMP%d",
> +                                          cs35l56->index + 1);
> +       if (!cs35l56->amp_name) {
> +               ret = -ENOMEM;
> +               goto err;
> +       }
> +
> +       cs35l56_init_cs_dsp(&cs35l56->base, &cs35l56->cs_dsp);
> +       cs35l56->cs_dsp.client_ops = &cs35l56_hda_client_ops;
> +
> +       if (cs35l56->base.reset_gpio) {
> +               dev_dbg(cs35l56->base.dev, "Hard reset\n");
> +
> +               /*
> +                * The GPIOD_OUT_LOW to *_gpiod_get_*() will be ignored if the
> +                * ACPI defines a different default state. So explicitly set low.
> +                */
> +               gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
> +               cs35l56_wait_min_reset_pulse();
> +               gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 1);
> +       }
> +
> +       ret = cs35l56_hw_init(&cs35l56->base);
> +       if (ret < 0)
> +               goto err;
> +
> +       /* Reset the device and wait for it to boot */
> +       cs35l56_system_reset(&cs35l56->base, false);
> +       ret = cs35l56_wait_for_firmware_boot(&cs35l56->base);
> +       if (ret)
> +               goto err;
> +
> +       regcache_mark_dirty(cs35l56->base.regmap);
> +       regcache_sync(cs35l56->base.regmap);
> +
> +       /* Disable auto-hibernate so that runtime_pm has control */
> +       ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE);
> +       if (ret)
> +               goto err;
> +
> +       ret = cs_dsp_halo_init(&cs35l56->cs_dsp);
> +       if (ret != 0) {

if (ret)

> +               dev_err_probe(cs35l56->base.dev, ret, "cs_dsp_halo_init failed\n");
> +               goto err;
> +       }
> +
> +       dev_dbg(cs35l56->base.dev, "DSP system name: '%s', amp name: '%s'\n",
> +               cs35l56->system_name, cs35l56->amp_name);
> +
> +       /* Populate soft registers in the regmap cache */
> +       cs35l56_reread_firmware_registers(&cs35l56->base);
> +
> +       regmap_multi_reg_write(cs35l56->base.regmap, cs35l56_hda_dai_config,
> +                              ARRAY_SIZE(cs35l56_hda_dai_config));
> +
> +       /*
> +        * By default only enable one ASP1TXn, where n=amplifier index,
> +        * This prevents multiple amps trying to drive the same slot.
> +        */
> +       cs35l56->asp_tx_mask = BIT(cs35l56->index);
> +
> +       pm_runtime_set_autosuspend_delay(cs35l56->base.dev, 3000);
> +       pm_runtime_use_autosuspend(cs35l56->base.dev);
> +       pm_runtime_set_active(cs35l56->base.dev);
> +       pm_runtime_mark_last_busy(cs35l56->base.dev);
> +       pm_runtime_enable(cs35l56->base.dev);
> +
> +       ret = component_add(cs35l56->base.dev, &cs35l56_hda_comp_ops);
> +       if (ret) {
> +               dev_err(cs35l56->base.dev, "Register component failed: %d\n", ret);
> +               goto pm_err;
> +       }
> +
> +       cs35l56->base.init_done = true;
> +
> +       return 0;
> +
> +pm_err:
> +       pm_runtime_disable(cs35l56->base.dev);
> +err:
> +       gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
> +
> +       return ret;
> +}
> +EXPORT_SYMBOL_NS_GPL(cs35l56_hda_common_probe, SND_HDA_SCODEC_CS35L56);
> +
> +void cs35l56_hda_remove(struct device *dev)
> +{
> +       struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
> +
> +       pm_runtime_get_sync(cs35l56->base.dev);
> +       pm_runtime_disable(cs35l56->base.dev);
> +
> +       component_del(cs35l56->base.dev, &cs35l56_hda_comp_ops);
> +
> +       kfree(cs35l56->system_name);
> +       pm_runtime_put_noidle(cs35l56->base.dev);
> +
> +       gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
> +}
> +EXPORT_SYMBOL_NS_GPL(cs35l56_hda_remove, SND_HDA_SCODEC_CS35L56);
> +
> +const struct dev_pm_ops cs35l56_hda_pm_ops = {
> +       SET_RUNTIME_PM_OPS(cs35l56_hda_runtime_suspend, cs35l56_hda_runtime_resume, NULL)
> +       SYSTEM_SLEEP_PM_OPS(cs35l56_hda_system_suspend, cs35l56_hda_system_resume)
> +       LATE_SYSTEM_SLEEP_PM_OPS(cs35l56_hda_system_suspend_late,
> +                                cs35l56_hda_system_resume_early)
> +       NOIRQ_SYSTEM_SLEEP_PM_OPS(cs35l56_hda_system_suspend_no_irq,
> +                                 cs35l56_hda_system_resume_no_irq)
> +};
> +EXPORT_SYMBOL_NS_GPL(cs35l56_hda_pm_ops, SND_HDA_SCODEC_CS35L56);
> +
> +MODULE_DESCRIPTION("CS35L56 HDA Driver");
> +MODULE_IMPORT_NS(SND_HDA_CS_DSP_CONTROLS);
> +MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED);
> +MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
> +MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS(FW_CS_DSP);
> diff --git a/sound/pci/hda/cs35l56_hda.h b/sound/pci/hda/cs35l56_hda.h
> new file mode 100644
> index 000000000000..6e5bc5397db5
> --- /dev/null
> +++ b/sound/pci/hda/cs35l56_hda.h
> @@ -0,0 +1,48 @@
> +/* SPDX-License-Identifier: GPL-2.0-only
> + *
> + * HDA audio driver for Cirrus Logic CS35L56 smart amp
> + *
> + * Copyright (C) 2023 Cirrus Logic, Inc. and
> + *                    Cirrus Logic International Semiconductor Ltd.
> + */
> +
> +#ifndef __CS35L56_HDA_H__
> +#define __CS35L56_HDA_H__
> +
> +#include <linux/device.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/firmware/cirrus/cs_dsp.h>
> +#include <linux/firmware/cirrus/wmfw.h>
> +#include <linux/regulator/consumer.h>
> +#include <sound/cs35l56.h>
> +
> +struct dentry;
> +
> +struct cs35l56_hda {
> +       struct cs35l56_base base;
> +       struct hda_codec *codec;
> +
> +       int index;
> +       const char *system_name;
> +       const char *amp_name;
> +
> +       struct cs_dsp cs_dsp;
> +       bool playing;
> +       bool suspended;
> +       u8 asp_tx_mask;
> +
> +       struct snd_kcontrol *posture_ctl;
> +       struct snd_kcontrol *volume_ctl;
> +       struct snd_kcontrol *mixer_ctl[4];
> +
> +#if IS_ENABLED(CONFIG_SND_DEBUG)
> +       struct dentry *debugfs_root;
> +#endif
> +};
> +
> +extern const struct dev_pm_ops cs35l56_hda_pm_ops;
> +
> +int cs35l56_hda_common_probe(struct cs35l56_hda *cs35l56, int id);
> +void cs35l56_hda_remove(struct device *dev);
> +
> +#endif /*__CS35L56_HDA_H__*/
> diff --git a/sound/pci/hda/cs35l56_hda_i2c.c b/sound/pci/hda/cs35l56_hda_i2c.c
> new file mode 100644
> index 000000000000..5dfe79554d0b
> --- /dev/null
> +++ b/sound/pci/hda/cs35l56_hda_i2c.c
> @@ -0,0 +1,69 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +//
> +// CS35L56 HDA audio driver I2C binding
> +//
> +// Copyright (C) 2023 Cirrus Logic, Inc. and
> +//                    Cirrus Logic International Semiconductor Ltd.
> +
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +
> +#include "cs35l56_hda.h"
> +
> +static int cs35l56_hda_i2c_probe(struct i2c_client *clt)
> +{
> +       struct cs35l56_hda *cs35l56;
> +       int ret;
> +
> +       cs35l56 = devm_kzalloc(&clt->dev, sizeof(*cs35l56), GFP_KERNEL);
> +       if (!cs35l56)
> +               return -ENOMEM;
> +
> +       cs35l56->base.dev = &clt->dev;
> +       cs35l56->base.can_hibernate = true;
> +       cs35l56->base.regmap = devm_regmap_init_i2c(clt, &cs35l56_regmap_i2c);
> +       if (IS_ERR(cs35l56->base.regmap)) {
> +               ret = PTR_ERR(cs35l56->base.regmap);
> +               dev_err(cs35l56->base.dev, "Failed to allocate register map: %d\n",
> +                       ret);
> +               return ret;
> +       }
> +
> +       ret = cs35l56_hda_common_probe(cs35l56, clt->addr);
> +       if (ret != 0)

if (ret)

> +               return ret;
> +       ret = cs35l56_irq_request(&cs35l56->base, clt->irq);
> +       if (ret < 0)
> +               cs35l56_hda_remove(cs35l56->base.dev);
> +
> +       return ret;
> +}
> +
> +static void cs35l56_hda_i2c_remove(struct i2c_client *clt)
> +{
> +       cs35l56_hda_remove(&clt->dev);
> +}
> +
> +static const struct i2c_device_id cs35l56_hda_i2c_id[] = {
> +       { "cs35l56-hda", 0 },
> +       {}
> +};
> +
> +static struct i2c_driver cs35l56_hda_i2c_driver = {
> +       .driver = {
> +               .name           = "cs35l56-hda",
> +               .pm             = &cs35l56_hda_pm_ops,
> +       },
> +       .id_table       = cs35l56_hda_i2c_id,
> +       .probe_new      = cs35l56_hda_i2c_probe,
> +       .remove         = cs35l56_hda_i2c_remove,
> +};
> +module_i2c_driver(cs35l56_hda_i2c_driver);
> +
> +MODULE_DESCRIPTION("HDA CS35L56 I2C driver");
> +MODULE_IMPORT_NS(SND_HDA_SCODEC_CS35L56);
> +MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED);
> +MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
> +MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/sound/pci/hda/cs35l56_hda_spi.c b/sound/pci/hda/cs35l56_hda_spi.c
> new file mode 100644
> index 000000000000..bd572ce796d8
> --- /dev/null
> +++ b/sound/pci/hda/cs35l56_hda_spi.c
> @@ -0,0 +1,68 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +//
> +// CS35L56 HDA audio driver SPI binding
> +//
> +// Copyright (C) 2023 Cirrus Logic, Inc. and
> +//                    Cirrus Logic International Semiconductor Ltd.
> +
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/spi/spi.h>
> +
> +#include "cs35l56_hda.h"
> +
> +static int cs35l56_hda_spi_probe(struct spi_device *spi)
> +{
> +       struct cs35l56_hda *cs35l56;
> +       int ret;
> +
> +       cs35l56 = devm_kzalloc(&spi->dev, sizeof(*cs35l56), GFP_KERNEL);
> +       if (!cs35l56)
> +               return -ENOMEM;
> +
> +       cs35l56->base.dev = &spi->dev;
> +       cs35l56->base.regmap = devm_regmap_init_spi(spi, &cs35l56_regmap_spi);
> +       if (IS_ERR(cs35l56->base.regmap)) {
> +               ret = PTR_ERR(cs35l56->base.regmap);
> +               dev_err(cs35l56->base.dev, "Failed to allocate register map: %d\n",
> +                       ret);
> +               return ret;
> +       }
> +
> +       ret = cs35l56_hda_common_probe(cs35l56, spi->chip_select);
> +       if (ret != 0)

if (ret)

> +               return ret;
> +       ret = cs35l56_irq_request(&cs35l56->base, spi->irq);
> +       if (ret < 0)
> +               cs35l56_hda_remove(cs35l56->base.dev);
> +
> +       return ret;
> +}
> +
> +static void cs35l56_hda_spi_remove(struct spi_device *spi)
> +{
> +       cs35l56_hda_remove(&spi->dev);
> +}
> +
> +static const struct spi_device_id cs35l56_hda_spi_id[] = {
> +       { "cs35l56-hda", 0 },
> +       {}
> +};
> +
> +static struct spi_driver cs35l56_hda_spi_driver = {
> +       .driver = {
> +               .name           = "cs35l56-hda",
> +               .pm             = &cs35l56_hda_pm_ops,
> +       },
> +       .id_table       = cs35l56_hda_spi_id,
> +       .probe          = cs35l56_hda_spi_probe,
> +       .remove         = cs35l56_hda_spi_remove,
> +};
> +module_spi_driver(cs35l56_hda_spi_driver);
> +
> +MODULE_DESCRIPTION("HDA CS35L56 SPI driver");
> +MODULE_IMPORT_NS(SND_HDA_SCODEC_CS35L56);
> +MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED);
> +MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
> +MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
> +MODULE_LICENSE("GPL");
> --
> 2.30.2
> 


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 13/13] ALSA: hda/cs35l56: Add driver for Cirrus Logic CS35L56 amplifier
  2023-05-26  4:40   ` Claudiu.Beznea
@ 2023-05-26 12:20     ` Richard Fitzgerald
  2023-05-26 15:25     ` Mark Brown
  1 sibling, 0 replies; 20+ messages in thread
From: Richard Fitzgerald @ 2023-05-26 12:20 UTC (permalink / raw)
  To: Claudiu.Beznea, tiwai, broonie, perex
  Cc: alsa-devel, linux-kernel, patches, simont

On 26/5/23 05:40, Claudiu.Beznea@microchip.com wrote:
> On 25.05.2023 18:06, Richard Fitzgerald wrote:
>> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>>
>> From: Simon Trimmer <simont@opensource.cirrus.com>
>>
>> Add a driver for the Cirrus Logic CS35L56 amplifier. This uses the same
>> component binding API as the CS35L41 driver. This is not a standalone
>> HDA device; it provides control of the CS35L56 for systems that use a
>> combination of an HDA codec and CS35L56 amplifiers with audio routed
>> through the HDA codec.
>>

<SNIP>

>> +
>> +       cs35l56->base.reset_gpio = devm_gpiod_get_index_optional(cs35l56->base.dev,
>> +                                                                "reset",
>> +                                                                cs35l56->index,
>> +                                                                GPIOD_OUT_LOW);
>> +       if (IS_ERR(cs35l56->base.reset_gpio)) {
> 
> devm_gpiod_get_index_optional() can also return NULL.
> 

Yes, that is expected. It's optional. It's not an error.
In that case cs35l56->base.reset_gpio will already be NULL.

>> +               ret = PTR_ERR(cs35l56->base.reset_gpio);
>> +
>> +               /*
>> +                * If RESET is shared the first amp to probe will grab the reset
>> +                * line and reset all the amps
>> +                */
>> +               if (ret != -EBUSY)
>> +                       return dev_err_probe(cs35l56->base.dev, ret, "Failed to get reset GPIO\n");
>> +
>> +               dev_info(cs35l56->base.dev, "Reset GPIO busy, assume shared reset\n");
>> +               cs35l56->base.reset_gpio = NULL;
>> +       }
>> +
>> +       return 0;
>> +
>> +err:
>> +       dev_err(cs35l56->base.dev, "Failed property %s: %d\n", property, ret);
>> +
>> +       return ret;
>> +}

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 13/13] ALSA: hda/cs35l56: Add driver for Cirrus Logic CS35L56 amplifier
  2023-05-26  4:40   ` Claudiu.Beznea
  2023-05-26 12:20     ` Richard Fitzgerald
@ 2023-05-26 15:25     ` Mark Brown
  1 sibling, 0 replies; 20+ messages in thread
From: Mark Brown @ 2023-05-26 15:25 UTC (permalink / raw)
  To: Claudiu.Beznea
  Cc: rf, tiwai, perex, alsa-devel, linux-kernel, patches, simont

[-- Attachment #1: Type: text/plain, Size: 745 bytes --]

On Fri, May 26, 2023 at 04:40:23AM +0000, Claudiu.Beznea@microchip.com wrote:
> On 25.05.2023 18:06, Richard Fitzgerald wrote:
> > EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> > 
> > From: Simon Trimmer <simont@opensource.cirrus.com>
> > 
> > Add a driver for the Cirrus Logic CS35L56 amplifier. This uses the same
> > component binding API as the CS35L41 driver. This is not a standalone
> > HDA device; it provides control of the CS35L56 for systems that use a

Please delete unneeded context from mails when replying.  Doing this
makes it much easier to find your reply in the message, helping ensure
it won't be missed by people scrolling through the irrelevant quoted
material.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2023-05-26 15:26 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-05-25 15:06 [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Richard Fitzgerald
2023-05-25 15:06 ` [PATCH 01/13] ASoC: cs35l56: Move shared data into a common data structure Richard Fitzgerald
2023-05-25 15:06 ` [PATCH 02/13] ASoC: cs35l56: Make cs35l56_system_reset() code more generic Richard Fitzgerald
2023-05-25 15:06 ` [PATCH 03/13] ASoC: cs35l56: Convert utility functions to use common data structure Richard Fitzgerald
2023-05-25 15:06 ` [PATCH 04/13] ASoC: cs35l56: Move utility functions to shared file Richard Fitzgerald
2023-05-25 15:06 ` [PATCH 05/13] ASoC: cs35l56: Move runtime suspend/resume to shared library Richard Fitzgerald
2023-05-25 15:06 ` [PATCH 06/13] ASoC: cs35l56: Move cs_dsp init into " Richard Fitzgerald
2023-05-25 15:06 ` [PATCH 07/13] ASoC: cs35l56: Move part of cs35l56_init() to " Richard Fitzgerald
2023-05-25 15:06 ` [PATCH 08/13] ASoC: cs35l56: Pass correct pointer to cs35l56_irq() Richard Fitzgerald
2023-05-25 15:14   ` Mark Brown
2023-05-25 15:20     ` Richard Fitzgerald
2023-05-25 15:06 ` [PATCH 09/13] ASoC: cs35l56: Make common function for control port wait Richard Fitzgerald
2023-05-25 15:06 ` [PATCH 10/13] ASoC: cs35l56: Make a common function to shutdown the DSP Richard Fitzgerald
2023-05-25 15:06 ` [PATCH 11/13] ALSA: hda: Fix missing header dependencies Richard Fitzgerald
2023-05-25 15:06 ` [PATCH 12/13] ALSA: hda: Add mute_hook to hda_component Richard Fitzgerald
2023-05-25 15:06 ` [PATCH 13/13] ALSA: hda/cs35l56: Add driver for Cirrus Logic CS35L56 amplifier Richard Fitzgerald
2023-05-26  4:40   ` Claudiu.Beznea
2023-05-26 12:20     ` Richard Fitzgerald
2023-05-26 15:25     ` Mark Brown
2023-05-25 15:43 ` [PATCH 00/13] ALSA: hda: Adding support for CS35L56 on HDA systems Mark Brown

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.