From 742e66f525170fe02dec42e47aedf53d3dc85195 Mon Sep 17 00:00:00 2001
From: Philip Mueller <philm@manjaro.org>
Date: Fri, 4 Aug 2023 07:17:42 +0200
Subject: [PATCH] [pkg-upd] 6.4.8-3 - add patches for ALLY

---
 ...-mbox-command-to-enable-speaker-outp.patch | 238 ++++++++++++++++++
 ...l-for-Power-Up-Down-rather-than-wait.patch | 141 +++++++++++
 ...-Check-mailbox-status-of-pause-comma.patch |  36 +++
 ...-Ensure-we-correctly-re-sync-regmap-.patch |  74 ++++++
 ...-Ensure-we-pass-up-any-errors-during.patch |  63 +++++
 ...-Move-Play-and-Pause-into-separate-f.patch | 193 ++++++++++++++
 ...ponent-Add-pre-and-post-playback-hoo.patch |  56 +++++
 ...5l41-Use-pre-and-post-playback-hooks.patch | 122 +++++++++
 ...-Rework-System-Suspend-to-ensure-cor.patch | 114 +++++++++
 ...-Add-device_link-between-HDA-and-cs3.patch |  60 +++++
 ...-Ensure-amp-is-only-unmuted-during-p.patch |  73 ++++++
 PKGBUILD                                      |  27 +-
 12 files changed, 1195 insertions(+), 2 deletions(-)
 create mode 100644 0001-ALSA-cs35l41-Use-mbox-command-to-enable-speaker-outp.patch
 create mode 100644 0002-ALSA-cs35l41-Poll-for-Power-Up-Down-rather-than-wait.patch
 create mode 100644 0003-ALSA-hda-cs35l41-Check-mailbox-status-of-pause-comma.patch
 create mode 100644 0004-ALSA-hda-cs35l41-Ensure-we-correctly-re-sync-regmap-.patch
 create mode 100644 0005-ALSA-hda-cs35l41-Ensure-we-pass-up-any-errors-during.patch
 create mode 100644 0006-ALSA-hda-cs35l41-Move-Play-and-Pause-into-separate-f.patch
 create mode 100644 0007-ALSA-hda-hda_component-Add-pre-and-post-playback-hoo.patch
 create mode 100644 0008-ALSA-hda-cs35l41-Use-pre-and-post-playback-hooks.patch
 create mode 100644 0009-ALSA-hda-cs35l41-Rework-System-Suspend-to-ensure-cor.patch
 create mode 100644 0010-ALSA-hda-cs35l41-Add-device_link-between-HDA-and-cs3.patch
 create mode 100644 0011-ALSA-hda-cs35l41-Ensure-amp-is-only-unmuted-during-p.patch

diff --git a/0001-ALSA-cs35l41-Use-mbox-command-to-enable-speaker-outp.patch b/0001-ALSA-cs35l41-Use-mbox-command-to-enable-speaker-outp.patch
new file mode 100644
index 0000000..5782393
--- /dev/null
+++ b/0001-ALSA-cs35l41-Use-mbox-command-to-enable-speaker-outp.patch
@@ -0,0 +1,238 @@
+From c21139c6470a5b08c7463e451f2ff404e55f652f Mon Sep 17 00:00:00 2001
+From: Stefan Binding <sbinding@opensource.cirrus.com>
+Date: Fri, 21 Jul 2023 16:18:06 +0100
+Subject: [PATCH 01/11] ALSA: cs35l41: Use mbox command to enable speaker
+ output for external boost
+
+To enable the speaker output in external boost mode, 2 registers must
+be set, one after another. The longer the time between the writes of
+the two registers, the more likely, and more loudly a pop may occur.
+To minimize this, an mbox command can be used to allow the firmware
+to perform this action, minimizing any delay between write, thus
+minimizing any pop or click as a result. The old method will remain
+when running without firmware.
+
+Acked-by: Mark Brown <broonie@kernel.org>
+Signed-off-by: Stefan Binding <sbinding@opensource.cirrus.com>
+---
+ include/sound/cs35l41.h        |  5 ++-
+ sound/pci/hda/cs35l41_hda.c    |  9 ++--
+ sound/soc/codecs/cs35l41-lib.c | 76 +++++++++++++++++++++++++++-------
+ sound/soc/codecs/cs35l41.c     |  8 ++--
+ 4 files changed, 74 insertions(+), 24 deletions(-)
+
+diff --git a/include/sound/cs35l41.h b/include/sound/cs35l41.h
+index 7239d943942c..1bf757901d02 100644
+--- a/include/sound/cs35l41.h
++++ b/include/sound/cs35l41.h
+@@ -829,6 +829,7 @@ enum cs35l41_cspl_mbox_cmd {
+ 	CSPL_MBOX_CMD_STOP_PRE_REINIT = 4,
+ 	CSPL_MBOX_CMD_HIBERNATE = 5,
+ 	CSPL_MBOX_CMD_OUT_OF_HIBERNATE = 6,
++	CSPL_MBOX_CMD_SPK_OUT_ENABLE = 7,
+ 	CSPL_MBOX_CMD_UNKNOWN_CMD = -1,
+ 	CSPL_MBOX_CMD_INVALID_SEQUENCE = -2,
+ };
+@@ -901,7 +902,7 @@ int cs35l41_exit_hibernate(struct device *dev, struct regmap *regmap);
+ int cs35l41_init_boost(struct device *dev, struct regmap *regmap,
+ 		       struct cs35l41_hw_cfg *hw_cfg);
+ bool cs35l41_safe_reset(struct regmap *regmap, enum cs35l41_boost_type b_type);
+-int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, int enable,
+-			  struct completion *pll_lock);
++int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l41_boost_type b_type,
++			  int enable, struct completion *pll_lock, bool firmware_running);
+ 
+ #endif /* __CS35L41_H */
+diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
+index ce5faa620517..f9c97270db6f 100644
+--- a/sound/pci/hda/cs35l41_hda.c
++++ b/sound/pci/hda/cs35l41_hda.c
+@@ -514,13 +514,15 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action)
+ 		break;
+ 	case HDA_GEN_PCM_ACT_PREPARE:
+ 		mutex_lock(&cs35l41->fw_mutex);
+-		ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 1, NULL);
++		ret = cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 1, NULL,
++					    cs35l41->firmware_running);
+ 		mutex_unlock(&cs35l41->fw_mutex);
+ 		break;
+ 	case HDA_GEN_PCM_ACT_CLEANUP:
+ 		mutex_lock(&cs35l41->fw_mutex);
+ 		regmap_multi_reg_write(reg, cs35l41_hda_mute, ARRAY_SIZE(cs35l41_hda_mute));
+-		ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 0, NULL);
++		ret = cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 0, NULL,
++					    cs35l41->firmware_running);
+ 		mutex_unlock(&cs35l41->fw_mutex);
+ 		break;
+ 	case HDA_GEN_PCM_ACT_CLOSE:
+@@ -672,7 +674,8 @@ static int cs35l41_runtime_suspend(struct device *dev)
+ 	if (cs35l41->playback_started) {
+ 		regmap_multi_reg_write(cs35l41->regmap, cs35l41_hda_mute,
+ 				       ARRAY_SIZE(cs35l41_hda_mute));
+-		cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0, NULL);
++		cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0,
++				      NULL, cs35l41->firmware_running);
+ 		regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
+ 				   CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT);
+ 		if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
+diff --git a/sound/soc/codecs/cs35l41-lib.c b/sound/soc/codecs/cs35l41-lib.c
+index 1e4205295a0d..a7556fa33cdd 100644
+--- a/sound/soc/codecs/cs35l41-lib.c
++++ b/sound/soc/codecs/cs35l41-lib.c
+@@ -1080,28 +1080,32 @@ static const struct reg_sequence cs35l41_safe_to_reset[] = {
+ 	{ 0x00000040,			0x00000033 },
+ };
+ 
+-static const struct reg_sequence cs35l41_active_to_safe[] = {
++static const struct reg_sequence cs35l41_active_to_safe_start[] = {
+ 	{ 0x00000040,			0x00000055 },
+ 	{ 0x00000040,			0x000000AA },
+ 	{ 0x00007438,			0x00585941 },
+ 	{ CS35L41_PWR_CTRL1,		0x00000000 },
+-	{ 0x0000742C,			0x00000009, 3000 },
++	{ 0x0000742C,			0x00000009 },
++};
++
++static const struct reg_sequence cs35l41_active_to_safe_end[] = {
+ 	{ 0x00007438,			0x00580941 },
+ 	{ 0x00000040,			0x000000CC },
+ 	{ 0x00000040,			0x00000033 },
+ };
+ 
+-static const struct reg_sequence cs35l41_safe_to_active[] = {
++static const struct reg_sequence cs35l41_safe_to_active_start[] = {
+ 	{ 0x00000040,			0x00000055 },
+ 	{ 0x00000040,			0x000000AA },
+ 	{ 0x0000742C,			0x0000000F },
+ 	{ 0x0000742C,			0x00000079 },
+ 	{ 0x00007438,			0x00585941 },
+-	{ CS35L41_PWR_CTRL1,		0x00000001, 3000 }, // GLOBAL_EN = 1
++	{ CS35L41_PWR_CTRL1,		0x00000001 }, // GLOBAL_EN = 1
++};
++
++static const struct reg_sequence cs35l41_safe_to_active_en_spk[] = {
+ 	{ 0x0000742C,			0x000000F9 },
+ 	{ 0x00007438,			0x00580941 },
+-	{ 0x00000040,			0x000000CC },
+-	{ 0x00000040,			0x00000033 },
+ };
+ 
+ static const struct reg_sequence cs35l41_reset_to_safe[] = {
+@@ -1188,11 +1192,11 @@ bool cs35l41_safe_reset(struct regmap *regmap, enum cs35l41_boost_type b_type)
+ }
+ EXPORT_SYMBOL_GPL(cs35l41_safe_reset);
+ 
+-int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, int enable,
+-			  struct completion *pll_lock)
++int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l41_boost_type b_type,
++			  int enable, struct completion *pll_lock, bool firmware_running)
+ {
+ 	int ret;
+-	unsigned int gpio1_func, pad_control, pwr_ctrl1, pwr_ctrl3;
++	unsigned int gpio1_func, pad_control, pwr_ctrl1, pwr_ctrl3, int_status;
+ 	struct reg_sequence cs35l41_mdsync_down_seq[] = {
+ 		{CS35L41_PWR_CTRL3,		0},
+ 		{CS35L41_GPIO_PAD_CONTROL,	0},
+@@ -1204,6 +1208,14 @@ int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type,
+ 		{CS35L41_PWR_CTRL1,	0x00000001, 3000},
+ 	};
+ 
++	if ((pwr_ctl1_val & CS35L41_GLOBAL_EN_MASK) && enable) {
++		dev_dbg(dev, "Cannot set Global Enable - already set.\n");
++		return 0;
++	} else if (!(pwr_ctl1_val & CS35L41_GLOBAL_EN_MASK) && !enable) {
++		dev_dbg(dev, "Cannot unset Global Enable - not set.\n");
++		return 0;
++	}
++
+ 	switch (b_type) {
+ 	case CS35L41_SHD_BOOST_ACTV:
+ 	case CS35L41_SHD_BOOST_PASS:
+@@ -1244,16 +1256,48 @@ int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type,
+ 	case CS35L41_INT_BOOST:
+ 		ret = regmap_update_bits(regmap, CS35L41_PWR_CTRL1, CS35L41_GLOBAL_EN_MASK,
+ 					 enable << CS35L41_GLOBAL_EN_SHIFT);
++		if (ret) {
++			dev_err(dev, "CS35L41_PWR_CTRL1 set failed: %d\n", ret);
++			return ret;
++		}
+ 		usleep_range(3000, 3100);
+ 		break;
+ 	case CS35L41_EXT_BOOST:
+ 	case CS35L41_EXT_BOOST_NO_VSPK_SWITCH:
+-		if (enable)
+-			ret = regmap_multi_reg_write(regmap, cs35l41_safe_to_active,
+-						     ARRAY_SIZE(cs35l41_safe_to_active));
+-		else
+-			ret = regmap_multi_reg_write(regmap, cs35l41_active_to_safe,
+-						     ARRAY_SIZE(cs35l41_active_to_safe));
++		if (enable) {
++			/* Test Key is unlocked here */
++			ret = regmap_multi_reg_write(regmap, cs35l41_safe_to_active_start,
++						     ARRAY_SIZE(cs35l41_safe_to_active_start));
++			if (ret)
++				return ret;
++
++			usleep_range(3000, 3100);
++
++			if (firmware_running)
++				ret = cs35l41_set_cspl_mbox_cmd(dev, regmap,
++								CSPL_MBOX_CMD_SPK_OUT_ENABLE);
++			else
++				ret = regmap_multi_reg_write(regmap, cs35l41_safe_to_active_en_spk,
++							ARRAY_SIZE(cs35l41_safe_to_active_en_spk));
++
++			/* Lock the test key, it was unlocked during the multi_reg_write */
++			cs35l41_test_key_lock(dev, regmap);
++		} else {
++			/* Test Key is unlocked here */
++			ret = regmap_multi_reg_write(regmap, cs35l41_active_to_safe_start,
++						     ARRAY_SIZE(cs35l41_active_to_safe_start));
++			if (ret) {
++				/* Lock the test key, it was unlocked during the multi_reg_write */
++				cs35l41_test_key_lock(dev, regmap);
++				return ret;
++			}
++
++			usleep_range(3000, 3100);
++
++			/* Test Key is locked here */
++			ret = regmap_multi_reg_write(regmap, cs35l41_active_to_safe_end,
++						     ARRAY_SIZE(cs35l41_active_to_safe_end));
++		}
+ 		break;
+ 	default:
+ 		ret = -EINVAL;
+@@ -1344,6 +1388,8 @@ static bool cs35l41_check_cspl_mbox_sts(enum cs35l41_cspl_mbox_cmd cmd,
+ 		return (sts == CSPL_MBOX_STS_RUNNING);
+ 	case CSPL_MBOX_CMD_STOP_PRE_REINIT:
+ 		return (sts == CSPL_MBOX_STS_RDY_FOR_REINIT);
++	case CSPL_MBOX_CMD_SPK_OUT_ENABLE:
++		return (sts == CSPL_MBOX_STS_RUNNING);
+ 	default:
+ 		return false;
+ 	}
+diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c
+index 6ac501f008ec..d4e9c9d9b50a 100644
+--- a/sound/soc/codecs/cs35l41.c
++++ b/sound/soc/codecs/cs35l41.c
+@@ -500,12 +500,12 @@ static int cs35l41_main_amp_event(struct snd_soc_dapm_widget *w,
+ 						cs35l41_pup_patch,
+ 						ARRAY_SIZE(cs35l41_pup_patch));
+ 
+-		cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 1,
+-				      &cs35l41->pll_lock);
++		ret = cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type,
++					    1, &cs35l41->pll_lock, cs35l41->dsp.cs_dsp.running);
+ 		break;
+ 	case SND_SOC_DAPM_POST_PMD:
+-		cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0,
+-				      &cs35l41->pll_lock);
++		ret = cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type,
++					    0, &cs35l41->pll_lock, cs35l41->dsp.cs_dsp.running);
+ 
+ 		ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
+ 					       val, val &  CS35L41_PDN_DONE_MASK,
+-- 
+2.41.0
+
diff --git a/0002-ALSA-cs35l41-Poll-for-Power-Up-Down-rather-than-wait.patch b/0002-ALSA-cs35l41-Poll-for-Power-Up-Down-rather-than-wait.patch
new file mode 100644
index 0000000..396384f
--- /dev/null
+++ b/0002-ALSA-cs35l41-Poll-for-Power-Up-Down-rather-than-wait.patch
@@ -0,0 +1,141 @@
+From 437f5415c5ac8e49b0675f74132b6e1308b6e5c7 Mon Sep 17 00:00:00 2001
+From: Stefan Binding <sbinding@opensource.cirrus.com>
+Date: Fri, 21 Jul 2023 16:18:07 +0100
+Subject: [PATCH 02/11] ALSA: cs35l41: Poll for Power Up/Down rather than
+ waiting a fixed delay
+
+To ensure the chip has correctly powered up or down before continuing,
+the driver will now poll a register, rather than wait a fixed delay.
+
+Acked-by: Mark Brown <broonie@kernel.org>
+Signed-off-by: Stefan Binding <sbinding@opensource.cirrus.com>
+---
+ sound/soc/codecs/cs35l41-lib.c | 48 +++++++++++++++++++++++++++++++---
+ sound/soc/codecs/cs35l41.c     | 10 -------
+ 2 files changed, 44 insertions(+), 14 deletions(-)
+
+diff --git a/sound/soc/codecs/cs35l41-lib.c b/sound/soc/codecs/cs35l41-lib.c
+index a7556fa33cdd..a9c559a676e7 100644
+--- a/sound/soc/codecs/cs35l41-lib.c
++++ b/sound/soc/codecs/cs35l41-lib.c
+@@ -1196,7 +1196,8 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4
+ 			  int enable, struct completion *pll_lock, bool firmware_running)
+ {
+ 	int ret;
+-	unsigned int gpio1_func, pad_control, pwr_ctrl1, pwr_ctrl3, int_status;
++	unsigned int gpio1_func, pad_control, pwr_ctrl1, pwr_ctrl3, int_status, pup_pdn_mask;
++	unsigned int pwr_ctl1_val;
+ 	struct reg_sequence cs35l41_mdsync_down_seq[] = {
+ 		{CS35L41_PWR_CTRL3,		0},
+ 		{CS35L41_GPIO_PAD_CONTROL,	0},
+@@ -1208,6 +1209,12 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4
+ 		{CS35L41_PWR_CTRL1,	0x00000001, 3000},
+ 	};
+ 
++	pup_pdn_mask = enable ? CS35L41_PUP_DONE_MASK : CS35L41_PDN_DONE_MASK;
++
++	ret = regmap_read(regmap, CS35L41_PWR_CTRL1, &pwr_ctl1_val);
++	if (ret)
++		return ret;
++
+ 	if ((pwr_ctl1_val & CS35L41_GLOBAL_EN_MASK) && enable) {
+ 		dev_dbg(dev, "Cannot set Global Enable - already set.\n");
+ 		return 0;
+@@ -1252,6 +1259,15 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4
+ 			ret = regmap_multi_reg_write(regmap, cs35l41_mdsync_up_seq,
+ 						     ARRAY_SIZE(cs35l41_mdsync_up_seq));
+ 		}
++
++		ret = regmap_read_poll_timeout(regmap, CS35L41_IRQ1_STATUS1,
++					int_status, int_status & pup_pdn_mask,
++					1000, 100000);
++		if (ret)
++			dev_err(dev, "Enable(%d) failed: %d\n", enable, ret);
++
++		// Clear PUP/PDN status
++		regmap_write(regmap, CS35L41_IRQ1_STATUS1, pup_pdn_mask);
+ 		break;
+ 	case CS35L41_INT_BOOST:
+ 		ret = regmap_update_bits(regmap, CS35L41_PWR_CTRL1, CS35L41_GLOBAL_EN_MASK,
+@@ -1260,7 +1276,15 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4
+ 			dev_err(dev, "CS35L41_PWR_CTRL1 set failed: %d\n", ret);
+ 			return ret;
+ 		}
+-		usleep_range(3000, 3100);
++
++		ret = regmap_read_poll_timeout(regmap, CS35L41_IRQ1_STATUS1,
++					int_status, int_status & pup_pdn_mask,
++					1000, 100000);
++		if (ret)
++			dev_err(dev, "Enable(%d) failed: %d\n", enable, ret);
++
++		/* Clear PUP/PDN status */
++		regmap_write(regmap, CS35L41_IRQ1_STATUS1, pup_pdn_mask);
+ 		break;
+ 	case CS35L41_EXT_BOOST:
+ 	case CS35L41_EXT_BOOST_NO_VSPK_SWITCH:
+@@ -1271,7 +1295,15 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4
+ 			if (ret)
+ 				return ret;
+ 
+-			usleep_range(3000, 3100);
++			ret = regmap_read_poll_timeout(regmap, CS35L41_IRQ1_STATUS1, int_status,
++				       int_status & CS35L41_PUP_DONE_MASK, 1000, 100000);
++			if (ret) {
++				dev_err(dev, "Failed waiting for CS35L41_PUP_DONE_MASK: %d\n", ret);
++				/* Lock the test key, it was unlocked during the multi_reg_write */
++				cs35l41_test_key_lock(dev, regmap);
++				return ret;
++			}
++			regmap_write(regmap, CS35L41_IRQ1_STATUS1, CS35L41_PUP_DONE_MASK);
+ 
+ 			if (firmware_running)
+ 				ret = cs35l41_set_cspl_mbox_cmd(dev, regmap,
+@@ -1292,7 +1324,15 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4
+ 				return ret;
+ 			}
+ 
+-			usleep_range(3000, 3100);
++			ret = regmap_read_poll_timeout(regmap, CS35L41_IRQ1_STATUS1, int_status,
++				       int_status & CS35L41_PDN_DONE_MASK, 1000, 100000);
++			if (ret) {
++				dev_err(dev, "Failed waiting for CS35L41_PDN_DONE_MASK: %d\n", ret);
++				/* Lock the test key, it was unlocked during the multi_reg_write */
++				cs35l41_test_key_lock(dev, regmap);
++				return ret;
++			}
++			regmap_write(regmap, CS35L41_IRQ1_STATUS1, CS35L41_PDN_DONE_MASK);
+ 
+ 			/* Test Key is locked here */
+ 			ret = regmap_multi_reg_write(regmap, cs35l41_active_to_safe_end,
+diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c
+index d4e9c9d9b50a..2b3c36f02edb 100644
+--- a/sound/soc/codecs/cs35l41.c
++++ b/sound/soc/codecs/cs35l41.c
+@@ -491,7 +491,6 @@ static int cs35l41_main_amp_event(struct snd_soc_dapm_widget *w,
+ {
+ 	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+ 	struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(component);
+-	unsigned int val;
+ 	int ret = 0;
+ 
+ 	switch (event) {
+@@ -507,15 +506,6 @@ static int cs35l41_main_amp_event(struct snd_soc_dapm_widget *w,
+ 		ret = cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type,
+ 					    0, &cs35l41->pll_lock, cs35l41->dsp.cs_dsp.running);
+ 
+-		ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
+-					       val, val &  CS35L41_PDN_DONE_MASK,
+-					       1000, 100000);
+-		if (ret)
+-			dev_warn(cs35l41->dev, "PDN failed: %d\n", ret);
+-
+-		regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
+-			     CS35L41_PDN_DONE_MASK);
+-
+ 		regmap_multi_reg_write_bypassed(cs35l41->regmap,
+ 						cs35l41_pdn_patch,
+ 						ARRAY_SIZE(cs35l41_pdn_patch));
+-- 
+2.41.0
+
diff --git a/0003-ALSA-hda-cs35l41-Check-mailbox-status-of-pause-comma.patch b/0003-ALSA-hda-cs35l41-Check-mailbox-status-of-pause-comma.patch
new file mode 100644
index 0000000..6eb272a
--- /dev/null
+++ b/0003-ALSA-hda-cs35l41-Check-mailbox-status-of-pause-comma.patch
@@ -0,0 +1,36 @@
+From 796af5ec6c6bb2eadf78a96f629e2c7fba11123b Mon Sep 17 00:00:00 2001
+From: Stefan Binding <sbinding@opensource.cirrus.com>
+Date: Fri, 21 Jul 2023 16:18:08 +0100
+Subject: [PATCH 03/11] ALSA: hda: cs35l41: Check mailbox status of pause
+ command after firmware load
+
+Currently, we do not check the return status of the pause command,
+immediately after we load firmware. If the pause has failed,
+the firmware is not running.
+
+Signed-off-by: Stefan Binding <sbinding@opensource.cirrus.com>
+---
+ sound/pci/hda/cs35l41_hda.c | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
+index f9c97270db6f..29f1dce45f1d 100644
+--- a/sound/pci/hda/cs35l41_hda.c
++++ b/sound/pci/hda/cs35l41_hda.c
+@@ -781,7 +781,12 @@ static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41)
+ 		goto clean_dsp;
+ 	}
+ 
+-	cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, CSPL_MBOX_CMD_PAUSE);
++	ret = cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, CSPL_MBOX_CMD_PAUSE);
++	if (ret) {
++		dev_err(cs35l41->dev, "Error waiting for DSP to pause: %u\n", ret);
++		goto clean_dsp;
++	}
++
+ 	cs35l41->firmware_running = true;
+ 
+ 	return 0;
+-- 
+2.41.0
+
diff --git a/0004-ALSA-hda-cs35l41-Ensure-we-correctly-re-sync-regmap-.patch b/0004-ALSA-hda-cs35l41-Ensure-we-correctly-re-sync-regmap-.patch
new file mode 100644
index 0000000..d7cce60
--- /dev/null
+++ b/0004-ALSA-hda-cs35l41-Ensure-we-correctly-re-sync-regmap-.patch
@@ -0,0 +1,74 @@
+From 9684d3a1fbe55573eccd6c7e5f72dd519a4e406b Mon Sep 17 00:00:00 2001
+From: Stefan Binding <sbinding@opensource.cirrus.com>
+Date: Fri, 21 Jul 2023 16:18:09 +0100
+Subject: [PATCH 04/11] ALSA: hda: cs35l41: Ensure we correctly re-sync regmap
+ before system suspending.
+
+In order to properly system suspend, it is necessary to unload the firmware
+and ensure the chip is ready for shutdown (if necessary). If the system
+is currently in runtime suspend, it is necessary to wake up the device,
+and then make it ready. Currently, the wake does not correctly resync
+the device, which may mean it cannot suspend correctly. Fix this by
+performaing a resync.
+
+Signed-off-by: Stefan Binding <sbinding@opensource.cirrus.com>
+---
+ sound/pci/hda/cs35l41_hda.c | 32 +++++++++++++++++++++++++++-----
+ 1 file changed, 27 insertions(+), 5 deletions(-)
+
+diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
+index 29f1dce45f1d..f42457147ce4 100644
+--- a/sound/pci/hda/cs35l41_hda.c
++++ b/sound/pci/hda/cs35l41_hda.c
+@@ -574,21 +574,43 @@ static int cs35l41_hda_channel_map(struct device *dev, unsigned int tx_num, unsi
+ 				    rx_slot);
+ }
+ 
+-static void cs35l41_ready_for_reset(struct cs35l41_hda *cs35l41)
++static int cs35l41_ready_for_reset(struct cs35l41_hda *cs35l41)
+ {
++	int ret = 0;
++
+ 	mutex_lock(&cs35l41->fw_mutex);
+ 	if (cs35l41->firmware_running) {
+ 
+ 		regcache_cache_only(cs35l41->regmap, false);
+ 
+-		cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap);
++		ret = cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap);
++		if (ret) {
++			dev_warn(cs35l41->dev, "Unable to exit Hibernate.");
++			goto err;
++		}
++
++		/* Test key needs to be unlocked to allow the OTP settings to re-apply */
++		cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap);
++		ret = regcache_sync(cs35l41->regmap);
++		cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap);
++		if (ret) {
++			dev_err(cs35l41->dev, "Failed to restore register cache: %d\n", ret);
++			goto err;
++		}
++
++		if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
++			cs35l41_init_boost(cs35l41->dev, cs35l41->regmap, &cs35l41->hw_cfg);
++
+ 		cs35l41_shutdown_dsp(cs35l41);
+ 		cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type);
+-
+-		regcache_cache_only(cs35l41->regmap, true);
+-		regcache_mark_dirty(cs35l41->regmap);
+ 	}
++err:
++	regcache_cache_only(cs35l41->regmap, true);
++	regcache_mark_dirty(cs35l41->regmap);
++
+ 	mutex_unlock(&cs35l41->fw_mutex);
++
++	return ret;
+ }
+ 
+ static int cs35l41_system_suspend(struct device *dev)
+-- 
+2.41.0
+
diff --git a/0005-ALSA-hda-cs35l41-Ensure-we-pass-up-any-errors-during.patch b/0005-ALSA-hda-cs35l41-Ensure-we-pass-up-any-errors-during.patch
new file mode 100644
index 0000000..5ced084
--- /dev/null
+++ b/0005-ALSA-hda-cs35l41-Ensure-we-pass-up-any-errors-during.patch
@@ -0,0 +1,63 @@
+From 05bfc01172a34466e660465922d1cab5b460880f Mon Sep 17 00:00:00 2001
+From: Stefan Binding <sbinding@opensource.cirrus.com>
+Date: Fri, 21 Jul 2023 16:18:10 +0100
+Subject: [PATCH 05/11] ALSA: hda: cs35l41: Ensure we pass up any errors during
+ system suspend.
+
+There are several steps required to put the system into system suspend.
+Some of these steps may fail, so the driver should pass up the errors
+if they occur.
+
+Signed-off-by: Stefan Binding <sbinding@opensource.cirrus.com>
+---
+ sound/pci/hda/cs35l41_hda.c | 17 +++++++++++++----
+ 1 file changed, 13 insertions(+), 4 deletions(-)
+
+diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
+index f42457147ce4..d4a11f7b5dbd 100644
+--- a/sound/pci/hda/cs35l41_hda.c
++++ b/sound/pci/hda/cs35l41_hda.c
+@@ -626,17 +626,22 @@ static int cs35l41_system_suspend(struct device *dev)
+ 	}
+ 
+ 	ret = pm_runtime_force_suspend(dev);
+-	if (ret)
++	if (ret) {
++		dev_err(dev, "System Suspend Failed, unable to runtime suspend: %d\n", ret);
+ 		return ret;
++	}
+ 
+ 	/* Shutdown DSP before system suspend */
+-	cs35l41_ready_for_reset(cs35l41);
++	ret = cs35l41_ready_for_reset(cs35l41);
++
++	if (ret)
++		dev_err(dev, "System Suspend Failed, not ready for Reset: %d\n", ret);
+ 
+ 	/*
+ 	 * Reset GPIO may be shared, so cannot reset here.
+ 	 * However beyond this point, amps may be powered down.
+ 	 */
+-	return 0;
++	return ret;
+ }
+ 
+ static int cs35l41_system_resume(struct device *dev)
+@@ -659,9 +664,13 @@ static int cs35l41_system_resume(struct device *dev)
+ 	usleep_range(2000, 2100);
+ 
+ 	ret = pm_runtime_force_resume(dev);
++	if (ret) {
++		dev_err(dev, "System Resume Failed: Unable to runtime resume: %d\n", ret);
++		return ret;
++	}
+ 
+ 	mutex_lock(&cs35l41->fw_mutex);
+-	if (!ret && cs35l41->request_fw_load && !cs35l41->fw_request_ongoing) {
++	if (cs35l41->request_fw_load && !cs35l41->fw_request_ongoing) {
+ 		cs35l41->fw_request_ongoing = true;
+ 		schedule_work(&cs35l41->fw_load_work);
+ 	}
+-- 
+2.41.0
+
diff --git a/0006-ALSA-hda-cs35l41-Move-Play-and-Pause-into-separate-f.patch b/0006-ALSA-hda-cs35l41-Move-Play-and-Pause-into-separate-f.patch
new file mode 100644
index 0000000..b797928
--- /dev/null
+++ b/0006-ALSA-hda-cs35l41-Move-Play-and-Pause-into-separate-f.patch
@@ -0,0 +1,193 @@
+From f352ce9e5389e4746f25bfec33f4e0ee4dcbf690 Mon Sep 17 00:00:00 2001
+From: Stefan Binding <sbinding@opensource.cirrus.com>
+Date: Fri, 21 Jul 2023 16:18:11 +0100
+Subject: [PATCH 06/11] ALSA: hda: cs35l41: Move Play and Pause into separate
+ functions
+
+This allows play and pause to be called from multiple places,
+which is necessary for system suspend and resume.
+
+Signed-off-by: Stefan Binding <sbinding@opensource.cirrus.com>
+---
+ sound/pci/hda/cs35l41_hda.c | 131 ++++++++++++++++++++++--------------
+ 1 file changed, 79 insertions(+), 52 deletions(-)
+
+diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
+index d4a11f7b5dbd..f77583b46b6b 100644
+--- a/sound/pci/hda/cs35l41_hda.c
++++ b/sound/pci/hda/cs35l41_hda.c
+@@ -483,63 +483,103 @@ static void cs35l41_irq_release(struct cs35l41_hda *cs35l41)
+ 	cs35l41->irq_errors = 0;
+ }
+ 
+-static void cs35l41_hda_playback_hook(struct device *dev, int action)
++static void cs35l41_hda_play_start(struct device *dev)
+ {
+ 	struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
+ 	struct regmap *reg = cs35l41->regmap;
+-	int ret = 0;
++
++	dev_dbg(dev, "Play (Start)\n");
++
++	if (cs35l41->playback_started) {
++		dev_dbg(dev, "Playback already started.");
++		return;
++	}
++
++	cs35l41->playback_started = true;
++
++	if (cs35l41->firmware_running) {
++		regmap_multi_reg_write(reg, cs35l41_hda_config_dsp,
++				       ARRAY_SIZE(cs35l41_hda_config_dsp));
++		regmap_update_bits(reg, CS35L41_PWR_CTRL2,
++				   CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK,
++				   1 << CS35L41_VMON_EN_SHIFT | 1 << CS35L41_IMON_EN_SHIFT);
++		cs35l41_set_cspl_mbox_cmd(cs35l41->dev, reg, CSPL_MBOX_CMD_RESUME);
++	} else {
++		regmap_multi_reg_write(reg, cs35l41_hda_config, ARRAY_SIZE(cs35l41_hda_config));
++	}
++	regmap_update_bits(reg, CS35L41_PWR_CTRL2, CS35L41_AMP_EN_MASK, 1 << CS35L41_AMP_EN_SHIFT);
++	if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
++		regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00008001);
++
++}
++
++static void cs35l41_hda_play_done(struct device *dev)
++{
++	struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
++	struct regmap *reg = cs35l41->regmap;
++
++	dev_dbg(dev, "Play (Complete)\n");
++
++	cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 1, NULL,
++			      cs35l41->firmware_running);
++}
++
++static void cs35l41_hda_pause_start(struct device *dev)
++{
++	struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
++	struct regmap *reg = cs35l41->regmap;
++
++	dev_dbg(dev, "Pause (Start)\n");
++
++	regmap_multi_reg_write(reg, cs35l41_hda_mute, ARRAY_SIZE(cs35l41_hda_mute));
++	cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 0, NULL,
++			      cs35l41->firmware_running);
++}
++
++static void cs35l41_hda_pause_done(struct device *dev)
++{
++	struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
++	struct regmap *reg = cs35l41->regmap;
++
++	dev_dbg(dev, "Pause (Complete)\n");
++
++	regmap_update_bits(reg, CS35L41_PWR_CTRL2, CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT);
++	if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
++		regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00000001);
++	if (cs35l41->firmware_running) {
++		cs35l41_set_cspl_mbox_cmd(dev, reg, CSPL_MBOX_CMD_PAUSE);
++		regmap_update_bits(reg, CS35L41_PWR_CTRL2,
++				   CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK,
++				   0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT);
++	}
++	cs35l41_irq_release(cs35l41);
++	cs35l41->playback_started = false;
++}
++
++static void cs35l41_hda_playback_hook(struct device *dev, int action)
++{
++	struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
+ 
+ 	switch (action) {
+ 	case HDA_GEN_PCM_ACT_OPEN:
+ 		pm_runtime_get_sync(dev);
+ 		mutex_lock(&cs35l41->fw_mutex);
+-		cs35l41->playback_started = true;
+-		if (cs35l41->firmware_running) {
+-			regmap_multi_reg_write(reg, cs35l41_hda_config_dsp,
+-					       ARRAY_SIZE(cs35l41_hda_config_dsp));
+-			regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
+-					   CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK,
+-					   1 << CS35L41_VMON_EN_SHIFT | 1 << CS35L41_IMON_EN_SHIFT);
+-			cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap,
+-						  CSPL_MBOX_CMD_RESUME);
+-		} else {
+-			regmap_multi_reg_write(reg, cs35l41_hda_config,
+-					       ARRAY_SIZE(cs35l41_hda_config));
+-		}
+-		ret = regmap_update_bits(reg, CS35L41_PWR_CTRL2,
+-					 CS35L41_AMP_EN_MASK, 1 << CS35L41_AMP_EN_SHIFT);
+-		if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
+-			regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00008001);
++		cs35l41_hda_play_start(dev);
+ 		mutex_unlock(&cs35l41->fw_mutex);
+ 		break;
+ 	case HDA_GEN_PCM_ACT_PREPARE:
+ 		mutex_lock(&cs35l41->fw_mutex);
+-		ret = cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 1, NULL,
+-					    cs35l41->firmware_running);
++		cs35l41_hda_play_done(dev);
+ 		mutex_unlock(&cs35l41->fw_mutex);
+ 		break;
+ 	case HDA_GEN_PCM_ACT_CLEANUP:
+ 		mutex_lock(&cs35l41->fw_mutex);
+-		regmap_multi_reg_write(reg, cs35l41_hda_mute, ARRAY_SIZE(cs35l41_hda_mute));
+-		ret = cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 0, NULL,
+-					    cs35l41->firmware_running);
++		cs35l41_hda_pause_start(dev);
+ 		mutex_unlock(&cs35l41->fw_mutex);
+ 		break;
+ 	case HDA_GEN_PCM_ACT_CLOSE:
+ 		mutex_lock(&cs35l41->fw_mutex);
+-		ret = regmap_update_bits(reg, CS35L41_PWR_CTRL2,
+-					 CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT);
+-		if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
+-			regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00000001);
+-		if (cs35l41->firmware_running) {
+-			cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap,
+-						  CSPL_MBOX_CMD_PAUSE);
+-			regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
+-					   CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK,
+-					   0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT);
+-		}
+-		cs35l41_irq_release(cs35l41);
+-		cs35l41->playback_started = false;
++		cs35l41_hda_pause_done(dev);
+ 		mutex_unlock(&cs35l41->fw_mutex);
+ 
+ 		pm_runtime_mark_last_busy(dev);
+@@ -549,9 +589,6 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action)
+ 		dev_warn(cs35l41->dev, "Playback action not supported: %d\n", action);
+ 		break;
+ 	}
+-
+-	if (ret)
+-		dev_err(cs35l41->dev, "Regmap access fail: %d\n", ret);
+ }
+ 
+ static int cs35l41_hda_channel_map(struct device *dev, unsigned int tx_num, unsigned int *tx_slot,
+@@ -703,18 +740,8 @@ static int cs35l41_runtime_suspend(struct device *dev)
+ 	mutex_lock(&cs35l41->fw_mutex);
+ 
+ 	if (cs35l41->playback_started) {
+-		regmap_multi_reg_write(cs35l41->regmap, cs35l41_hda_mute,
+-				       ARRAY_SIZE(cs35l41_hda_mute));
+-		cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0,
+-				      NULL, cs35l41->firmware_running);
+-		regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
+-				   CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT);
+-		if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
+-			regmap_write(cs35l41->regmap, CS35L41_GPIO1_CTRL1, 0x00000001);
+-		regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
+-				   CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK,
+-				   0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT);
+-		cs35l41->playback_started = false;
++		cs35l41_hda_pause_start(dev);
++		cs35l41_hda_pause_done(dev);
+ 	}
+ 
+ 	if (cs35l41->firmware_running) {
+-- 
+2.41.0
+
diff --git a/0007-ALSA-hda-hda_component-Add-pre-and-post-playback-hoo.patch b/0007-ALSA-hda-hda_component-Add-pre-and-post-playback-hoo.patch
new file mode 100644
index 0000000..ab7d899
--- /dev/null
+++ b/0007-ALSA-hda-hda_component-Add-pre-and-post-playback-hoo.patch
@@ -0,0 +1,56 @@
+From c1bf8ed3a5f3d011276d975c7b1f62039bed160e Mon Sep 17 00:00:00 2001
+From: Stefan Binding <sbinding@opensource.cirrus.com>
+Date: Fri, 21 Jul 2023 16:18:12 +0100
+Subject: [PATCH 07/11] ALSA: hda: hda_component: Add pre and post playback
+ hooks to hda_component
+
+These hooks can be used to add callbacks that would be run before and after
+the main playback hooks. These hooks would be called for all amps, before
+moving on to the next hook, i.e. pre_playback_hook would be called for
+all amps, before the playback_hook is called for all amps, then finally
+the post_playback_hook is called for all amps.
+
+Signed-off-by: Stefan Binding <sbinding@opensource.cirrus.com>
+---
+ sound/pci/hda/hda_component.h |  2 ++
+ sound/pci/hda/patch_realtek.c | 10 +++++++++-
+ 2 files changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/sound/pci/hda/hda_component.h b/sound/pci/hda/hda_component.h
+index 534e845b9cd1..f170aec967c1 100644
+--- a/sound/pci/hda/hda_component.h
++++ b/sound/pci/hda/hda_component.h
+@@ -15,5 +15,7 @@ struct hda_component {
+ 	struct device *dev;
+ 	char name[HDA_MAX_NAME_SIZE];
+ 	struct hda_codec *codec;
++	void (*pre_playback_hook)(struct device *dev, int action);
+ 	void (*playback_hook)(struct device *dev, int action);
++	void (*post_playback_hook)(struct device *dev, int action);
+ };
+diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
+index 44fccfb93cff..dff92679ae72 100644
+--- a/sound/pci/hda/patch_realtek.c
++++ b/sound/pci/hda/patch_realtek.c
+@@ -6716,9 +6716,17 @@ static void comp_generic_playback_hook(struct hda_pcm_stream *hinfo, struct hda_
+ 	int i;
+ 
+ 	for (i = 0; i < HDA_MAX_COMPONENTS; i++) {
+-		if (spec->comps[i].dev)
++		if (spec->comps[i].dev && spec->comps[i].pre_playback_hook)
++			spec->comps[i].pre_playback_hook(spec->comps[i].dev, action);
++	}
++	for (i = 0; i < HDA_MAX_COMPONENTS; i++) {
++		if (spec->comps[i].dev && spec->comps[i].playback_hook)
+ 			spec->comps[i].playback_hook(spec->comps[i].dev, action);
+ 	}
++	for (i = 0; i < HDA_MAX_COMPONENTS; i++) {
++		if (spec->comps[i].dev && spec->comps[i].post_playback_hook)
++			spec->comps[i].post_playback_hook(spec->comps[i].dev, action);
++	}
+ }
+ 
+ struct cs35l41_dev_name {
+-- 
+2.41.0
+
diff --git a/0008-ALSA-hda-cs35l41-Use-pre-and-post-playback-hooks.patch b/0008-ALSA-hda-cs35l41-Use-pre-and-post-playback-hooks.patch
new file mode 100644
index 0000000..ac5b029
--- /dev/null
+++ b/0008-ALSA-hda-cs35l41-Use-pre-and-post-playback-hooks.patch
@@ -0,0 +1,122 @@
+From 4f3b42e2f126f96b1e512871d7073fb10d9a7283 Mon Sep 17 00:00:00 2001
+From: Stefan Binding <sbinding@opensource.cirrus.com>
+Date: Fri, 21 Jul 2023 16:18:13 +0100
+Subject: [PATCH 08/11] ALSA: hda: cs35l41: Use pre and post playback hooks
+
+Use new hooks to ensure separation between play/pause actions,
+as required by external boost.
+
+External Boost on CS35L41 requires the amp to go through a
+particular sequence of steps. One of these steps involes
+the setting of a GPIO. This GPIO is connected to one or
+more of the amps, and it may control the boost for all of
+the amps. To ensure that the GPIO is set when it is safe
+to do so, and to ensure that boost is ready for the rest of
+the sequence to be able to continue, we must ensure that
+the each part of the sequence is executed for each amp
+before moving on to the next part of the sequence.
+
+Some of the Play and Pause actions have moved from Open to
+Prepare. This is because Open is not guaranteed to be called
+again on system resume, whereas Prepare should.
+
+Signed-off-by: Stefan Binding <sbinding@opensource.cirrus.com>
+---
+ sound/pci/hda/cs35l41_hda.c | 53 ++++++++++++++++++++++++++++++-------
+ 1 file changed, 43 insertions(+), 10 deletions(-)
+
+diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
+index f77583b46b6b..a482d4752b3f 100644
+--- a/sound/pci/hda/cs35l41_hda.c
++++ b/sound/pci/hda/cs35l41_hda.c
+@@ -556,37 +556,68 @@ static void cs35l41_hda_pause_done(struct device *dev)
+ 	cs35l41->playback_started = false;
+ }
+ 
++static void cs35l41_hda_pre_playback_hook(struct device *dev, int action)
++{
++	struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
++
++	switch (action) {
++	case HDA_GEN_PCM_ACT_CLEANUP:
++		mutex_lock(&cs35l41->fw_mutex);
++		cs35l41_hda_pause_start(dev);
++		mutex_unlock(&cs35l41->fw_mutex);
++		break;
++	default:
++		break;
++	}
++}
+ static void cs35l41_hda_playback_hook(struct device *dev, int action)
+ {
+ 	struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
+ 
+ 	switch (action) {
+ 	case HDA_GEN_PCM_ACT_OPEN:
++		/*
++		 * All amps must be resumed before we can start playing back.
++		 * This ensures, for external boost, that all amps are in AMP_SAFE mode.
++		 * Do this in HDA_GEN_PCM_ACT_OPEN, since this is run prior to any of the
++		 * other actions.
++		 */
+ 		pm_runtime_get_sync(dev);
+-		mutex_lock(&cs35l41->fw_mutex);
+-		cs35l41_hda_play_start(dev);
+-		mutex_unlock(&cs35l41->fw_mutex);
+ 		break;
+ 	case HDA_GEN_PCM_ACT_PREPARE:
+ 		mutex_lock(&cs35l41->fw_mutex);
+-		cs35l41_hda_play_done(dev);
++		cs35l41_hda_play_start(dev);
+ 		mutex_unlock(&cs35l41->fw_mutex);
+ 		break;
+ 	case HDA_GEN_PCM_ACT_CLEANUP:
+ 		mutex_lock(&cs35l41->fw_mutex);
+-		cs35l41_hda_pause_start(dev);
++		cs35l41_hda_pause_done(dev);
+ 		mutex_unlock(&cs35l41->fw_mutex);
+ 		break;
+ 	case HDA_GEN_PCM_ACT_CLOSE:
+-		mutex_lock(&cs35l41->fw_mutex);
+-		cs35l41_hda_pause_done(dev);
+-		mutex_unlock(&cs35l41->fw_mutex);
+-
++		/*
++		 * Playback must be finished for all amps before we start runtime suspend.
++		 * This ensures no amps are playing back when we start putting them to sleep.
++		 */
+ 		pm_runtime_mark_last_busy(dev);
+ 		pm_runtime_put_autosuspend(dev);
+ 		break;
+ 	default:
+-		dev_warn(cs35l41->dev, "Playback action not supported: %d\n", action);
++		break;
++	}
++}
++
++static void cs35l41_hda_post_playback_hook(struct device *dev, int action)
++{
++	struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
++
++	switch (action) {
++	case HDA_GEN_PCM_ACT_PREPARE:
++		mutex_lock(&cs35l41->fw_mutex);
++		cs35l41_hda_play_done(dev);
++		mutex_unlock(&cs35l41->fw_mutex);
++		break;
++	default:
+ 		break;
+ 	}
+ }
+@@ -1037,6 +1068,8 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas
+ 	ret = cs35l41_create_controls(cs35l41);
+ 
+ 	comps->playback_hook = cs35l41_hda_playback_hook;
++	comps->pre_playback_hook = cs35l41_hda_pre_playback_hook;
++	comps->post_playback_hook = cs35l41_hda_post_playback_hook;
+ 
+ 	mutex_unlock(&cs35l41->fw_mutex);
+ 
+-- 
+2.41.0
+
diff --git a/0009-ALSA-hda-cs35l41-Rework-System-Suspend-to-ensure-cor.patch b/0009-ALSA-hda-cs35l41-Rework-System-Suspend-to-ensure-cor.patch
new file mode 100644
index 0000000..02d98e4
--- /dev/null
+++ b/0009-ALSA-hda-cs35l41-Rework-System-Suspend-to-ensure-cor.patch
@@ -0,0 +1,114 @@
+From 5091ba7ad9ea6a88db464b84b4993cc9e5033a84 Mon Sep 17 00:00:00 2001
+From: Stefan Binding <sbinding@opensource.cirrus.com>
+Date: Fri, 21 Jul 2023 16:18:14 +0100
+Subject: [PATCH 09/11] ALSA: hda: cs35l41: Rework System Suspend to ensure
+ correct call separation
+
+In order to correctly pause audio on suspend, amps using external boost
+require parts of the pause sequence to be called for all amps before moving
+on to the next steps.
+For example, as part of pausing the audio, the VSPK GPIO must be disabled,
+but since this GPIO is controlled by one amp, but controls the boost for
+all amps, it is required to separate the calls.
+During playback this is achieved by using the pre and post playback hooks,
+however during system suspend, this is not possible, so to separate the
+calls, we use both the .prepare and .suspend calls to pause the audio.
+
+Currently, for this reason, we do not restart audio on system resume.
+However, we can support this by relying on the playback hook to resume
+playback after system suspend.
+
+Signed-off-by: Stefan Binding <sbinding@opensource.cirrus.com>
+---
+ sound/pci/hda/cs35l41_hda.c | 40 ++++++++++++++++++++++++++++++++-----
+ 1 file changed, 35 insertions(+), 5 deletions(-)
+
+diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
+index a482d4752b3f..70aa819cfbd6 100644
+--- a/sound/pci/hda/cs35l41_hda.c
++++ b/sound/pci/hda/cs35l41_hda.c
+@@ -595,6 +595,15 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action)
+ 		mutex_unlock(&cs35l41->fw_mutex);
+ 		break;
+ 	case HDA_GEN_PCM_ACT_CLOSE:
++		mutex_lock(&cs35l41->fw_mutex);
++		if (!cs35l41->firmware_running && cs35l41->request_fw_load &&
++		    !cs35l41->fw_request_ongoing) {
++			dev_info(dev, "Requesting Firmware Load after HDA_GEN_PCM_ACT_CLOSE\n");
++			cs35l41->fw_request_ongoing = true;
++			schedule_work(&cs35l41->fw_load_work);
++		}
++		mutex_unlock(&cs35l41->fw_mutex);
++
+ 		/*
+ 		 * Playback must be finished for all amps before we start runtime suspend.
+ 		 * This ensures no amps are playing back when we start putting them to sleep.
+@@ -681,6 +690,25 @@ static int cs35l41_ready_for_reset(struct cs35l41_hda *cs35l41)
+ 	return ret;
+ }
+ 
++static int cs35l41_system_suspend_prep(struct device *dev)
++{
++	struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
++
++	dev_dbg(cs35l41->dev, "System Suspend Prepare\n");
++
++	if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) {
++		dev_err_once(cs35l41->dev, "System Suspend not supported\n");
++		return 0; /* don't block the whole system suspend */
++	}
++
++	mutex_lock(&cs35l41->fw_mutex);
++	if (cs35l41->playback_started)
++		cs35l41_hda_pause_start(dev);
++	mutex_unlock(&cs35l41->fw_mutex);
++
++	return 0;
++}
++
+ static int cs35l41_system_suspend(struct device *dev)
+ {
+ 	struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
+@@ -693,6 +721,11 @@ static int cs35l41_system_suspend(struct device *dev)
+ 		return 0; /* don't block the whole system suspend */
+ 	}
+ 
++	mutex_lock(&cs35l41->fw_mutex);
++	if (cs35l41->playback_started)
++		cs35l41_hda_pause_done(dev);
++	mutex_unlock(&cs35l41->fw_mutex);
++
+ 	ret = pm_runtime_force_suspend(dev);
+ 	if (ret) {
+ 		dev_err(dev, "System Suspend Failed, unable to runtime suspend: %d\n", ret);
+@@ -738,6 +771,7 @@ static int cs35l41_system_resume(struct device *dev)
+ 	}
+ 
+ 	mutex_lock(&cs35l41->fw_mutex);
++
+ 	if (cs35l41->request_fw_load && !cs35l41->fw_request_ongoing) {
+ 		cs35l41->fw_request_ongoing = true;
+ 		schedule_work(&cs35l41->fw_load_work);
+@@ -770,11 +804,6 @@ static int cs35l41_runtime_suspend(struct device *dev)
+ 
+ 	mutex_lock(&cs35l41->fw_mutex);
+ 
+-	if (cs35l41->playback_started) {
+-		cs35l41_hda_pause_start(dev);
+-		cs35l41_hda_pause_done(dev);
+-	}
+-
+ 	if (cs35l41->firmware_running) {
+ 		ret = cs35l41_enter_hibernate(cs35l41->dev, cs35l41->regmap,
+ 					      cs35l41->hw_cfg.bst_type);
+@@ -1641,6 +1670,7 @@ EXPORT_SYMBOL_NS_GPL(cs35l41_hda_remove, SND_HDA_SCODEC_CS35L41);
+ const struct dev_pm_ops cs35l41_hda_pm_ops = {
+ 	RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume,
+ 		       cs35l41_runtime_idle)
++	.prepare = cs35l41_system_suspend_prep,
+ 	SYSTEM_SLEEP_PM_OPS(cs35l41_system_suspend, cs35l41_system_resume)
+ };
+ EXPORT_SYMBOL_NS_GPL(cs35l41_hda_pm_ops, SND_HDA_SCODEC_CS35L41);
+-- 
+2.41.0
+
diff --git a/0010-ALSA-hda-cs35l41-Add-device_link-between-HDA-and-cs3.patch b/0010-ALSA-hda-cs35l41-Add-device_link-between-HDA-and-cs3.patch
new file mode 100644
index 0000000..ca5244f
--- /dev/null
+++ b/0010-ALSA-hda-cs35l41-Add-device_link-between-HDA-and-cs3.patch
@@ -0,0 +1,60 @@
+From 74c165859e33b62888b93891d82680350b9a615f Mon Sep 17 00:00:00 2001
+From: Stefan Binding <sbinding@opensource.cirrus.com>
+Date: Fri, 21 Jul 2023 16:18:15 +0100
+Subject: [PATCH 10/11] ALSA: hda: cs35l41: Add device_link between HDA and
+ cs35l41_hda
+
+To ensure consistency between the HDA core and the CS35L41 HDA
+driver, add a device_link between them. This ensures that the
+HDA core will suspend first, and resume second, meaning the
+amp driver will not miss any events from the playback hook from
+the HDA core during system suspend and resume.
+
+Signed-off-by: Stefan Binding <sbinding@opensource.cirrus.com>
+---
+ sound/pci/hda/cs35l41_hda.c | 13 ++++++++++++-
+ 1 file changed, 12 insertions(+), 1 deletion(-)
+
+diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
+index 70aa819cfbd6..175378cdf9df 100644
+--- a/sound/pci/hda/cs35l41_hda.c
++++ b/sound/pci/hda/cs35l41_hda.c
+@@ -1063,6 +1063,7 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas
+ {
+ 	struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
+ 	struct hda_component *comps = master_data;
++	unsigned int sleep_flags;
+ 	int ret = 0;
+ 
+ 	if (!comps || cs35l41->index < 0 || cs35l41->index >= HDA_MAX_COMPONENTS)
+@@ -1102,6 +1103,11 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas
+ 
+ 	mutex_unlock(&cs35l41->fw_mutex);
+ 
++	sleep_flags = lock_system_sleep();
++	if (!device_link_add(&comps->codec->core.dev, cs35l41->dev, DL_FLAG_STATELESS))
++		dev_warn(dev, "Unable to create device link\n");
++	unlock_system_sleep(sleep_flags);
++
+ 	pm_runtime_mark_last_busy(dev);
+ 	pm_runtime_put_autosuspend(dev);
+ 
+@@ -1112,9 +1118,14 @@ static void cs35l41_hda_unbind(struct device *dev, struct device *master, void *
+ {
+ 	struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
+ 	struct hda_component *comps = master_data;
++	unsigned int sleep_flags;
+ 
+-	if (comps[cs35l41->index].dev == dev)
++	if (comps[cs35l41->index].dev == dev) {
+ 		memset(&comps[cs35l41->index], 0, sizeof(*comps));
++		sleep_flags = lock_system_sleep();
++		device_link_remove(&comps->codec->core.dev, cs35l41->dev);
++		unlock_system_sleep(sleep_flags);
++	}
+ }
+ 
+ static const struct component_ops cs35l41_hda_comp_ops = {
+-- 
+2.41.0
+
diff --git a/0011-ALSA-hda-cs35l41-Ensure-amp-is-only-unmuted-during-p.patch b/0011-ALSA-hda-cs35l41-Ensure-amp-is-only-unmuted-during-p.patch
new file mode 100644
index 0000000..5049e39
--- /dev/null
+++ b/0011-ALSA-hda-cs35l41-Ensure-amp-is-only-unmuted-during-p.patch
@@ -0,0 +1,73 @@
+From 6f1a7b41a626a567fcfe915e9dbe3aea34b6c3ec Mon Sep 17 00:00:00 2001
+From: Stefan Binding <sbinding@opensource.cirrus.com>
+Date: Fri, 21 Jul 2023 16:18:16 +0100
+Subject: [PATCH 11/11] ALSA: hda: cs35l41: Ensure amp is only unmuted during
+ playback
+
+Currently we only mute after playback has finished, and unmute
+prior to setting global enable. To prevent any possible pops
+and clicks, mute at probe, and then only unmute after global
+enable is set.
+
+Signed-off-by: Stefan Binding <sbinding@opensource.cirrus.com>
+---
+ sound/pci/hda/cs35l41_hda.c | 22 ++++++++++++++++++++--
+ 1 file changed, 20 insertions(+), 2 deletions(-)
+
+diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
+index 175378cdf9df..98feb5ccd586 100644
+--- a/sound/pci/hda/cs35l41_hda.c
++++ b/sound/pci/hda/cs35l41_hda.c
+@@ -58,8 +58,6 @@ static const struct reg_sequence cs35l41_hda_config[] = {
+ 	{ CS35L41_DSP1_RX3_SRC,         0x00000018 }, // DSP1RX3 SRC = VMON
+ 	{ CS35L41_DSP1_RX4_SRC,         0x00000019 }, // DSP1RX4 SRC = IMON
+ 	{ CS35L41_DSP1_RX5_SRC,         0x00000020 }, // DSP1RX5 SRC = ERRVOL
+-	{ CS35L41_AMP_DIG_VOL_CTRL,	0x00008000 }, // AMP_HPF_PCM_EN = 1, AMP_VOL_PCM  0.0 dB
+-	{ CS35L41_AMP_GAIN_CTRL,	0x00000084 }, // AMP_GAIN_PCM 4.5 dB
+ };
+ 
+ static const struct reg_sequence cs35l41_hda_config_dsp[] = {
+@@ -82,6 +80,14 @@ static const struct reg_sequence cs35l41_hda_config_dsp[] = {
+ 	{ CS35L41_DSP1_RX3_SRC,         0x00000018 }, // DSP1RX3 SRC = VMON
+ 	{ CS35L41_DSP1_RX4_SRC,         0x00000019 }, // DSP1RX4 SRC = IMON
+ 	{ CS35L41_DSP1_RX5_SRC,         0x00000029 }, // DSP1RX5 SRC = VBSTMON
++};
++
++static const struct reg_sequence cs35l41_hda_unmute[] = {
++	{ CS35L41_AMP_DIG_VOL_CTRL,	0x00008000 }, // AMP_HPF_PCM_EN = 1, AMP_VOL_PCM  0.0 dB
++	{ CS35L41_AMP_GAIN_CTRL,	0x00000084 }, // AMP_GAIN_PCM 4.5 dB
++};
++
++static const struct reg_sequence cs35l41_hda_unmute_dsp[] = {
+ 	{ CS35L41_AMP_DIG_VOL_CTRL,	0x00008000 }, // AMP_HPF_PCM_EN = 1, AMP_VOL_PCM  0.0 dB
+ 	{ CS35L41_AMP_GAIN_CTRL,	0x00000233 }, // AMP_GAIN_PCM = 17.5dB AMP_GAIN_PDM = 19.5dB
+ };
+@@ -522,6 +528,13 @@ static void cs35l41_hda_play_done(struct device *dev)
+ 
+ 	cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 1, NULL,
+ 			      cs35l41->firmware_running);
++	if (cs35l41->firmware_running) {
++		regmap_multi_reg_write(reg, cs35l41_hda_unmute_dsp,
++				       ARRAY_SIZE(cs35l41_hda_unmute_dsp));
++	} else {
++		regmap_multi_reg_write(reg, cs35l41_hda_unmute,
++				       ARRAY_SIZE(cs35l41_hda_unmute));
++	}
+ }
+ 
+ static void cs35l41_hda_pause_start(struct device *dev)
+@@ -1616,6 +1629,11 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i
+ 	if (ret)
+ 		goto err;
+ 
++	ret = regmap_multi_reg_write(cs35l41->regmap, cs35l41_hda_mute,
++				     ARRAY_SIZE(cs35l41_hda_mute));
++	if (ret)
++		goto err;
++
+ 	INIT_WORK(&cs35l41->fw_load_work, cs35l41_fw_load_work);
+ 	mutex_init(&cs35l41->fw_mutex);
+ 
+-- 
+2.41.0
+
diff --git a/PKGBUILD b/PKGBUILD
index d89cfcc..0cf62a7 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -10,7 +10,7 @@ _kernelname=-MANJARO
 pkgbase=linux${_basever}
 pkgname=("$pkgbase" "$pkgbase-headers")
 pkgver=6.4.8
-pkgrel=2
+pkgrel=3
 arch=('x86_64')
 url="https://www.kernel.org/"
 license=('GPL2')
@@ -29,6 +29,18 @@ source=("https://www.kernel.org/pub/linux/kernel/v6.x/linux-${_basekernel}.tar.x
         '0206-asus-ally-bluetooth.patch'
         # HDR patches might create issues for now
         #'0207-AMD-HDR.patch'
+        # Fixup sleep cs35l41
+  0001-ALSA-cs35l41-Use-mbox-command-to-enable-speaker-outp.patch
+  0002-ALSA-cs35l41-Poll-for-Power-Up-Down-rather-than-wait.patch
+  0003-ALSA-hda-cs35l41-Check-mailbox-status-of-pause-comma.patch
+  0004-ALSA-hda-cs35l41-Ensure-we-correctly-re-sync-regmap-.patch
+  0005-ALSA-hda-cs35l41-Ensure-we-pass-up-any-errors-during.patch
+  0006-ALSA-hda-cs35l41-Move-Play-and-Pause-into-separate-f.patch
+  0007-ALSA-hda-hda_component-Add-pre-and-post-playback-hoo.patch
+  0008-ALSA-hda-cs35l41-Use-pre-and-post-playback-hooks.patch
+  0009-ALSA-hda-cs35l41-Rework-System-Suspend-to-ensure-cor.patch
+  0010-ALSA-hda-cs35l41-Add-device_link-between-HDA-and-cs3.patch
+  0011-ALSA-hda-cs35l41-Ensure-amp-is-only-unmuted-during-p.patch
 )
 
 sha256sums=('8fa0588f0c2ceca44cac77a0e39ba48c9f00a6b9dc69761c02a5d3efac8da7f3'
@@ -38,7 +50,18 @@ sha256sums=('8fa0588f0c2ceca44cac77a0e39ba48c9f00a6b9dc69761c02a5d3efac8da7f3'
             '7e79a1cc688189c66837611aa42ecf65a4f2fb071920830e9f4db923a13e9105'
             'a38b50b8c73332d3d16950bf8adcae7ead34391a60f74d05a58935cd3dc8a12d'
             'd673d034fbcd80426fd8d9c6af56537c5fe5b55fe49d74e313474d7fc285ecc1'
-            'd5f95fe43882fb68c0a7e1ce8d831788e222f841de3e636e85a89ea655fed40e')
+            'd5f95fe43882fb68c0a7e1ce8d831788e222f841de3e636e85a89ea655fed40e'
+            '27aaf7e14c7f5e127f5b658352ca5c3650477a92462139557aefb73bcea2b418'
+            '74da118887929f06afb57eaee716ff433ee5972c9dc91166fc08e66f44edb8e8'
+            'c5ac510677e58ac6b189939ac853e64bf9ad026a614a47f4cb535ad62bf41163'
+            '88f0d69dad01ccfef899b6b08abe162fc7743d40571232dff9a7d9093890d0a8'
+            '826bfa21b613d9c198d375d902958c90bb30171aee602c1806aaf99212abbb40'
+            '0dae5e24249b712f1501ead600c8ef4a5df21484e39e06a1dbafb57929c4999f'
+            '8dddf5537e3feedbf9f9c67f3c19fa7412d9e01b4f78023262b8fa340d3f47b2'
+            '3774b4eba753eb5f3768a28a68eb1a17557c0347275c19b8133f9f74d64a80df'
+            'a5daf210a6f72dde5b477d4b6d38a162b2698cac6c5fcfd4e4fd606274f34cec'
+            'b9298bde48a9f6c5d028150d627c05c71880e2693933ef2fe070f090e80876a5'
+            '4d53a6853b63c0f01b60b408bee61fa729656f925e50fa55ae3cba309668242a')
 
 prepare() {
   cd "linux-${_basekernel}"
-- 
GitLab