2023-07-20 14:47:53

by Stefan Binding

[permalink] [raw]
Subject: [PATCH v1 00/11] Fix support for System Suspend for CS35L41 HDA

There have been a couple of customer reports of intermittant issues after
system resume, where sometimes the DSP firmware stops responding.
Investigations into this issue show that there is a race between receiving
a prepare from the HDA core, and the firmware reload which is started by
the system resume. This can causes the Global Enable on the CS35L41 to be
enabled during the firmware load, which can sometimes cause issues in the
DSP.

The existing system resume behaviour also did not resume the audio, if
audio was previously playing when it was suspended.
In addition, during investigation, it was found there were additional
problems in the System Resume sequence, as well as the Playback sequence
with External Boost, where the driver does not correctly follow its
enable sequence for this mode. This can cause additional issues such as
pops and clicks.

This chain intends to correct the sequences for playback and system
suspend/resume so that the driver: obeys the external boost enable sequence;
resumes audio on system resume; and avoids the race condition on firmware
load and playback during system resume.

Stefan Binding (11):
ALSA: cs35l41: Use mbox command to enable speaker output for external
boost
ALSA: hda: cs35l41: Check mailbox status of pause command after
firmware load
ALSA: hda: cs35l41: Ensure we correctly re-sync regmap before system
suspending.
ALSA: hda: cs35l41: Ensure we pass up any errors during system
suspend.
ALSA: hda: cs35l41: Move Play and Pause into separate functions
ALSA: hda: hda_component: Add pre and post playback hooks to
hda_component
ALSA: hda: cs35l41: Use pre and post playback hooks
ALSA: hda: cs35l41: Rework System Suspend to ensure correct call
separation
ALSA: hda/realtek: Support pre-/post- playback hooks for cs35l41
ALSA: hda: cs35l41: Add device_link between HDA and cs35l41_hda
ALSA: hda: cs35l41: Ensure amp is only unmuted during playback

include/sound/cs35l41.h | 5 +-
sound/pci/hda/cs35l41_hda.c | 288 +++++++++++++++++++++++++--------
sound/pci/hda/hda_component.h | 2 +
sound/pci/hda/patch_realtek.c | 10 +-
sound/soc/codecs/cs35l41-lib.c | 118 ++++++++++++--
sound/soc/codecs/cs35l41.c | 18 +--
6 files changed, 343 insertions(+), 98 deletions(-)

--
2.34.1



2023-07-20 14:50:38

by Stefan Binding

[permalink] [raw]
Subject: [PATCH v1 08/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 <[email protected]>
---
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 a482d4752b3f8..70aa819cfbd64 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.34.1


2023-07-20 14:50:54

by Stefan Binding

[permalink] [raw]
Subject: [PATCH v1 04/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 <[email protected]>
---
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 f42457147ce47..d4a11f7b5dbd1 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.34.1


2023-07-20 15:18:39

by Stefan Binding

[permalink] [raw]
Subject: [PATCH v1 07/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 <[email protected]>
---
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 f77583b46b6b0..a482d4752b3f8 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.34.1