2024-01-29 16:28:08

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 00/18] ALSA: Various fixes for Cirrus Logic CS35L56 support

This chain of patches fixes various things that were undocumented, unknown
or uncertain when the original driver code was written. And also a few
things that were just bugs.

The HDA patches have dependencies on the ASoC patches, except for the final
patch that removes a bogus test stub function.

Richard Fitzgerald (18):
ASoC: wm_adsp: Fix firmware file search order
ASoC: wm_adsp: Don't overwrite fwf_name with the default
ASoC: cs35l56: cs35l56_component_remove() must clear
cs35l56->component
ASoC: cs35l56: cs35l56_component_remove() must clean up wm_adsp
ASoC: cs35l56: Don't add the same register patch multiple times
ASoC: cs35l56: Remove buggy checks from cs35l56_is_fw_reload_needed()
ASoC: cs35l56: Fix to ensure ASP1 registers match cache
ASoC: cs35l56: Fix default SDW TX mixer registers
ALSA: hda: cs35l56: Initialize all ASP1 registers
ASoC: cs35l56: Fix for initializing ASP1 mixer registers
ASoC: cs35l56: Fix misuse of wm_adsp 'part' string for silicon
revision
ASoC: cs35l56: Firmware file must match the version of preloaded
firmware
ASoC: cs35l56: Load tunings for the correct speaker models
ASoC: cs35l56: Allow more time for firmware to boot
ALSA: hda: cs35l56: Fix order of searching for firmware files
ALSA: hda: cs35l56: Fix filename string field layout
ALSA: hda: cs35l56: Firmware file must match the version of preloaded
firmware
ALSA: hda: cs35l56: Remove unused test stub function

include/sound/cs35l56.h | 7 +-
sound/pci/hda/cs35l56_hda.c | 138 ++++++++------
sound/soc/codecs/cs35l56-shared.c | 140 ++++++++++++--
sound/soc/codecs/cs35l56.c | 307 +++++++++++++++++++++++++-----
sound/soc/codecs/cs35l56.h | 2 +
sound/soc/codecs/wm_adsp.c | 73 +++----
6 files changed, 498 insertions(+), 169 deletions(-)

--
2.39.2



2024-01-29 16:28:29

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 02/18] ASoC: wm_adsp: Don't overwrite fwf_name with the default

There's no need to overwrite fwf_name with a kstrdup() of the cs_dsp part
name. It is trivial to select either fwf_name or cs_dsp.part as the string
to use when building the filename in wm_adsp_request_firmware_file().

This leaves fwf_name entirely owned by the codec driver.

It also avoids problems with freeing the pointer. With the original code
fwf_name was either a pointer owned by the codec driver, or a kstrdup()
created by wm_adsp. This meant wm_adsp must free it if it set it, but not
if the codec driver set it. The code was handling this by using
devm_kstrdup().
But there is no absolute requirement that wm_adsp_common_init() must be
called from probe(), so this was a pseudo-memory leak - each new call to
wm_adsp_common_init() would allocate another block of memory but these
would only be freed if the owning codec driver was removed.

Signed-off-by: Richard Fitzgerald <[email protected]>
---
sound/soc/codecs/wm_adsp.c | 29 ++++++++++++-----------------
1 file changed, 12 insertions(+), 17 deletions(-)

diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c
index bd60ceebb6a9..36ea0dcdc7ab 100644
--- a/sound/soc/codecs/wm_adsp.c
+++ b/sound/soc/codecs/wm_adsp.c
@@ -739,19 +739,25 @@ static int wm_adsp_request_firmware_file(struct wm_adsp *dsp,
const char *filetype)
{
struct cs_dsp *cs_dsp = &dsp->cs_dsp;
+ const char *fwf;
char *s, c;
int ret = 0;

+ if (dsp->fwf_name)
+ fwf = dsp->fwf_name;
+ else
+ fwf = dsp->cs_dsp.name;
+
if (system_name && asoc_component_prefix)
*filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s-%s.%s", dir, dsp->part,
- dsp->fwf_name, wm_adsp_fw[dsp->fw].file, system_name,
+ fwf, wm_adsp_fw[dsp->fw].file, system_name,
asoc_component_prefix, filetype);
else if (system_name)
*filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s.%s", dir, dsp->part,
- dsp->fwf_name, wm_adsp_fw[dsp->fw].file, system_name,
+ fwf, wm_adsp_fw[dsp->fw].file, system_name,
filetype);
else
- *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s.%s", dir, dsp->part, dsp->fwf_name,
+ *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s.%s", dir, dsp->part, fwf,
wm_adsp_fw[dsp->fw].file, filetype);

if (*filename == NULL)
@@ -857,29 +863,18 @@ static int wm_adsp_request_firmware_files(struct wm_adsp *dsp,
}

adsp_err(dsp, "Failed to request firmware <%s>%s-%s-%s<-%s<%s>>.wmfw\n",
- cirrus_dir, dsp->part, dsp->fwf_name, wm_adsp_fw[dsp->fw].file,
- system_name, asoc_component_prefix);
+ cirrus_dir, dsp->part,
+ dsp->fwf_name ? dsp->fwf_name : dsp->cs_dsp.name,
+ wm_adsp_fw[dsp->fw].file, system_name, asoc_component_prefix);

return -ENOENT;
}

static int wm_adsp_common_init(struct wm_adsp *dsp)
{
- char *p;
-
INIT_LIST_HEAD(&dsp->compr_list);
INIT_LIST_HEAD(&dsp->buffer_list);

- if (!dsp->fwf_name) {
- p = devm_kstrdup(dsp->cs_dsp.dev, dsp->cs_dsp.name, GFP_KERNEL);
- if (!p)
- return -ENOMEM;
-
- dsp->fwf_name = p;
- for (; *p != 0; ++p)
- *p = tolower(*p);
- }
-
return 0;
}

--
2.39.2


2024-01-29 16:28:38

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 05/18] ASoC: cs35l56: Don't add the same register patch multiple times

Move the call to cs35l56_set_patch() earlier in cs35l56_init() so
that it only adds the register patch on first-time initialization.

The call was after the post_soft_reset label, so every time this
function was run to re-initialize the hardware after a reset it would
call regmap_register_patch() and add the same reg_sequence again.

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: 898673b905b9 ("ASoC: cs35l56: Move shared data into a common data structure")
---
sound/soc/codecs/cs35l56.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index 491da77112c3..ea5d2b2eb82a 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -1159,6 +1159,10 @@ int cs35l56_init(struct cs35l56_private *cs35l56)
if (ret < 0)
return ret;

+ ret = cs35l56_set_patch(&cs35l56->base);
+ if (ret)
+ return ret;
+
/* Populate the DSP information with the revision and security state */
cs35l56->dsp.part = devm_kasprintf(cs35l56->base.dev, GFP_KERNEL, "cs35l56%s-%02x",
cs35l56->base.secured ? "s" : "", cs35l56->base.rev);
@@ -1197,10 +1201,6 @@ int cs35l56_init(struct cs35l56_private *cs35l56)
if (ret)
return ret;

- ret = cs35l56_set_patch(&cs35l56->base);
- if (ret)
- return ret;
-
/* Registers could be dirty after soft reset or SoundWire enumeration */
regcache_sync(cs35l56->base.regmap);

--
2.39.2


2024-01-29 16:29:58

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 04/18] ASoC: cs35l56: cs35l56_component_remove() must clean up wm_adsp

cs35l56_component_remove() must call wm_adsp_power_down() and
wm_adsp2_component_remove().

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56")
---
sound/soc/codecs/cs35l56.c | 5 +++++
1 file changed, 5 insertions(+)

diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index 09944db4db30..491da77112c3 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -810,6 +810,11 @@ static void cs35l56_component_remove(struct snd_soc_component *component)

cancel_work_sync(&cs35l56->dsp_work);

+ if (cs35l56->dsp.cs_dsp.booted)
+ wm_adsp_power_down(&cs35l56->dsp);
+
+ wm_adsp2_component_remove(&cs35l56->dsp, component);
+
cs35l56->component = NULL;
}

--
2.39.2


2024-01-29 16:30:00

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 06/18] ASoC: cs35l56: Remove buggy checks from cs35l56_is_fw_reload_needed()

Remove the check of fw_patched from cs35l56_is_fw_reload_needed().
Also remove the redundant check for control of the reset GPIO.

The fw_patched flag is set when cs35l56_dsp_work() has completed its
steps to download firmware and power-up wm_adsp. There was a check in
cs35l56_is_fw_reload_needed() to make a quick exit of 'false' if
!fw_patched. The original idea was that the system might be suspended
before the driver has ever made any attempt to download firmware, and
in that case the driver doesn't need to return to a patched state
because it was never in a patched state.

This check of fw_patched is buggy because it prevented ever recovering
from a failed patch. If a previous attempt to patch and reboot the
silicon had failed it would leave fw_patched==false. This would mean
the driver never attempted another download even though the fault may
have been cleared (by a hard reset, for example).

It is also a redundant check because the calling code already makes
a quick exit if cs35l56_component_probe() has not been called, which
deals with the original intent of this check but in a safer way.

The check for reset GPIO is redundant: if the silicon was hard-reset
the FIRMWARE_MISSING flag will be 1. But this check created an
expectation that the suspend/resume code toggles reset. This can't
easily be protected against accidental code breakage. The only reason
for the check was to skip runtime-resuming the driver to read the
PROTECTION_STATUS register when it already knows it reset the silicon.
But in that case the driver will have to be runtime-resumed to do
the firmware download. So it created an assumption for no benefit.

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: 8a731fd37f8b ("ASoC: cs35l56: Move utility functions to shared file")
---
sound/soc/codecs/cs35l56-shared.c | 11 -----------
1 file changed, 11 deletions(-)

diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c
index 953ba066bab1..0cd572de73a9 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -400,17 +400,6 @@ 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.
--
2.39.2


2024-01-29 16:30:03

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 15/18] ALSA: hda: cs35l56: Fix order of searching for firmware files

Check for the cases of system-specific bin file without a
wmfw before falling back to looking for a generic wmfw.

All system-specific options should be tried before falling
back to loading a generic wmfw/bin. With the original code,
the presence of a fallback generic wmfw on the filesystem
would prevent using a system-specific tuning with a ROM
firmware.

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: 73cfbfa9caea ("ALSA: hda/cs35l56: Add driver for Cirrus Logic CS35L56 amplifier")
---
sound/pci/hda/cs35l56_hda.c | 24 ++++++++++++++----------
1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/sound/pci/hda/cs35l56_hda.c b/sound/pci/hda/cs35l56_hda.c
index f22bcb104a4e..7ba7234d8d9c 100644
--- a/sound/pci/hda/cs35l56_hda.c
+++ b/sound/pci/hda/cs35l56_hda.c
@@ -483,6 +483,20 @@ static void cs35l56_hda_request_firmware_files(struct cs35l56_hda *cs35l56,
NULL, "bin");
return;
}
+
+ /*
+ * Check for system-specific bin files without wmfw before
+ * falling back to generic firmware
+ */
+ 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)
+ return;
}

ret = cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_filename,
@@ -493,16 +507,6 @@ static void cs35l56_hda_request_firmware_files(struct cs35l56_hda *cs35l56,
return;
}

- /* 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");
--
2.39.2


2024-01-29 16:31:21

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 16/18] ALSA: hda: cs35l56: Fix filename string field layout

Change the filename field layout to:
cs35l56-rev[-s]-dsp1-misc[-sub].[wmfw|bin]

This is to keep the same firmware file naming scheme as the
CS35L56 ASoC driver.

This is not a compatibility break because no firmware files have
been published.

The original field layout matched the ASoC driver, but the way the
ASoC driver used the wm_adsp driver config to form this filename
was bugged. Fixing the ASoC driver to use the correct wm_adsp config
strings means that the 's' flag (to indicate a secured part) has to
move to somewhere after the first '-'.

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: 73cfbfa9caea ("ALSA: hda/cs35l56: Add driver for Cirrus Logic CS35L56 amplifier")
---
sound/pci/hda/cs35l56_hda.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/sound/pci/hda/cs35l56_hda.c b/sound/pci/hda/cs35l56_hda.c
index 7ba7234d8d9c..081479f65fe7 100644
--- a/sound/pci/hda/cs35l56_hda.c
+++ b/sound/pci/hda/cs35l56_hda.c
@@ -405,16 +405,19 @@ static int cs35l56_hda_request_firmware_file(struct cs35l56_hda *cs35l56,
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,
+ *filename = kasprintf(GFP_KERNEL, "%scs35l56-%02x%s-dsp1-misc-%s-%s.%s", dir,
+ cs35l56->base.rev,
+ cs35l56->base.secured ? "-s" : "",
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,
+ *filename = kasprintf(GFP_KERNEL, "%scs35l56-%02x%s-dsp1-misc-%s.%s", dir,
+ cs35l56->base.rev,
+ cs35l56->base.secured ? "-s" : "",
system_name, filetype);
else
- *filename = kasprintf(GFP_KERNEL, "%scs35l56%s-%02x-dsp1-misc.%s", dir,
- cs35l56->base.secured ? "s" : "", cs35l56->base.rev,
+ *filename = kasprintf(GFP_KERNEL, "%scs35l56-%02x%s-dsp1-misc.%s", dir,
+ cs35l56->base.rev,
+ cs35l56->base.secured ? "-s" : "",
filetype);

if (!*filename)
--
2.39.2


2024-01-29 16:31:50

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 13/18] ASoC: cs35l56: Load tunings for the correct speaker models

If the "spk-id-gpios" property is present it points to GPIOs whose
value must be used to select the correct bin file to match the
speakers.

Some manufacturers use multiple sources of speakers, which need
different tunings for best performance. On these models the type of
speaker fitted is indicated by the values of one or more GPIOs. The
number formed by the GPIOs identifies the tuning required.

The speaker ID must be used in combination with the subsystem ID
(either from PCI SSID or cirrus,firmware-uid property), because the
GPIOs can only indicate variants of a specific model.

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: 1a1c3d794ef6 ("ASoC: cs35l56: Use PCI SSID as the firmware UID")
---
include/sound/cs35l56.h | 1 +
sound/soc/codecs/cs35l56-shared.c | 36 +++++++++++++++++++++++++++++++
sound/soc/codecs/cs35l56.c | 32 ++++++++++++++++++++++-----
sound/soc/codecs/cs35l56.h | 1 +
4 files changed, 65 insertions(+), 5 deletions(-)

diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h
index 5d6aefc41e64..23da6298ab37 100644
--- a/include/sound/cs35l56.h
+++ b/include/sound/cs35l56.h
@@ -289,6 +289,7 @@ void cs35l56_init_cs_dsp(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_ds
int cs35l56_read_prot_status(struct cs35l56_base *cs35l56_base,
bool *fw_missing, unsigned int *fw_version);
int cs35l56_hw_init(struct cs35l56_base *cs35l56_base);
+int cs35l56_get_speaker_id(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 33835535ef84..02fba4bc0a14 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -5,6 +5,7 @@
// Copyright (C) 2023 Cirrus Logic, Inc. and
// Cirrus Logic International Semiconductor Ltd.

+#include <linux/gpio/consumer.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/types.h>
@@ -736,6 +737,41 @@ int cs35l56_hw_init(struct cs35l56_base *cs35l56_base)
}
EXPORT_SYMBOL_NS_GPL(cs35l56_hw_init, SND_SOC_CS35L56_SHARED);

+int cs35l56_get_speaker_id(struct cs35l56_base *cs35l56_base)
+{
+ struct gpio_descs *descs;
+ int speaker_id;
+ int i, ret;
+
+ /* Read the speaker type qualifier from the motherboard GPIOs */
+ descs = gpiod_get_array_optional(cs35l56_base->dev, "spk-id", GPIOD_IN);
+ if (!descs) {
+ return -ENOENT;
+ } else if (IS_ERR(descs)) {
+ ret = PTR_ERR(descs);
+ return dev_err_probe(cs35l56_base->dev, ret, "Failed to get spk-id-gpios\n");
+ }
+
+ speaker_id = 0;
+ for (i = 0; i < descs->ndescs; i++) {
+ ret = gpiod_get_value_cansleep(descs->desc[i]);
+ if (ret < 0) {
+ dev_err_probe(cs35l56_base->dev, ret, "Failed to read spk-id[%d]\n", i);
+ goto err;
+ }
+
+ speaker_id |= (ret << i);
+ }
+
+ dev_dbg(cs35l56_base->dev, "Speaker ID = %d\n", speaker_id);
+ ret = speaker_id;
+err:
+ gpiod_put_array(descs);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_get_speaker_id, SND_SOC_CS35L56_SHARED);
+
static const u32 cs35l56_bclk_valid_for_pll_freq_table[] = {
[0x0C] = 128000,
[0x0F] = 256000,
diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index 597677422547..c23e29da4cfb 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -959,10 +959,19 @@ static int cs35l56_component_probe(struct snd_soc_component *component)

if (!cs35l56->dsp.system_name &&
(snd_soc_card_get_pci_ssid(component->card, &vendor, &device) == 0)) {
- cs35l56->dsp.system_name = devm_kasprintf(cs35l56->base.dev,
- GFP_KERNEL,
- "%04x%04x",
- vendor, device);
+ /* Append a speaker qualifier if there is a speaker ID */
+ if (cs35l56->speaker_id >= 0) {
+ cs35l56->dsp.system_name = devm_kasprintf(cs35l56->base.dev,
+ GFP_KERNEL,
+ "%04x%04x-spkid%d",
+ vendor, device,
+ cs35l56->speaker_id);
+ } else {
+ cs35l56->dsp.system_name = devm_kasprintf(cs35l56->base.dev,
+ GFP_KERNEL,
+ "%04x%04x",
+ vendor, device);
+ }
if (!cs35l56->dsp.system_name)
return -ENOMEM;
}
@@ -1245,7 +1254,13 @@ static int cs35l56_get_firmware_uid(struct cs35l56_private *cs35l56)
if (ret < 0)
return 0;

- cs35l56->dsp.system_name = devm_kstrdup(dev, prop, GFP_KERNEL);
+ /* Append a speaker qualifier if there is a speaker ID */
+ if (cs35l56->speaker_id >= 0)
+ cs35l56->dsp.system_name = devm_kasprintf(dev, GFP_KERNEL, "%s-spkid%d",
+ prop, cs35l56->speaker_id);
+ else
+ cs35l56->dsp.system_name = devm_kstrdup(dev, prop, GFP_KERNEL);
+
if (cs35l56->dsp.system_name == NULL)
return -ENOMEM;

@@ -1260,6 +1275,7 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56)

init_completion(&cs35l56->init_completion);
mutex_init(&cs35l56->base.irq_lock);
+ cs35l56->speaker_id = -ENOENT;

dev_set_drvdata(cs35l56->base.dev, cs35l56);

@@ -1296,6 +1312,12 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56)
gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 1);
}

+ ret = cs35l56_get_speaker_id(&cs35l56->base);
+ if ((ret < 0) && (ret != -ENOENT))
+ goto err;
+
+ cs35l56->speaker_id = ret;
+
ret = cs35l56_get_firmware_uid(cs35l56);
if (ret != 0)
goto err;
diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h
index dc2fe4c91e67..596b141e3f96 100644
--- a/sound/soc/codecs/cs35l56.h
+++ b/sound/soc/codecs/cs35l56.h
@@ -45,6 +45,7 @@ struct cs35l56_private {
bool sdw_attached;
struct completion init_completion;

+ int speaker_id;
u32 rx_mask;
u32 tx_mask;
u8 asp_slot_width;
--
2.39.2


2024-01-29 16:32:09

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 17/18] ALSA: hda: cs35l56: Firmware file must match the version of preloaded firmware

Check whether the firmware is already patched. If so, include the
firmware version in the firmware file name.

If the firmware has already been patched by the BIOS the driver
can only replace it if it has control of hard RESET.

If the driver cannot replace the firmware, it can still load a wmfw
(for ALSA control definitions) and/or a bin (for additional tunings).
But these must match the version of firmware that is running on the
CS35L56.

The firmware is pre-patched if either:
- FIRMWARE_MISSING == 0, or
- it is a secured CS35L56 (which implies that is was already patched),

cs35l56_hw_init() will set preloaded_fw_ver to the (non-zero)
firmware version if either of these conditions is true.

Normal (unpatched or replaceable firmware):
cs35l56-rev-dsp1-misc[-system_name].[wmfw|bin]

Preloaded firmware:
cs35l56-rev[-s]-VVVVVV-dsp1-misc[-system_name].[wmfw|bin]

Where:
[-s] is an optional -s added into the name for a secured CS35L56
VVVVVV is the 24-bit firmware version in hexadecimal.

Backport note:
This won't apply to kernel versions older than v6.6.

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: 73cfbfa9caea ("ALSA: hda/cs35l56: Add driver for Cirrus Logic CS35L56 amplifier")
---
sound/pci/hda/cs35l56_hda.c | 95 +++++++++++++++++++------------------
1 file changed, 50 insertions(+), 45 deletions(-)

diff --git a/sound/pci/hda/cs35l56_hda.c b/sound/pci/hda/cs35l56_hda.c
index 081479f65fe7..32736d3e45ba 100644
--- a/sound/pci/hda/cs35l56_hda.c
+++ b/sound/pci/hda/cs35l56_hda.c
@@ -397,7 +397,7 @@ static const struct cs_dsp_client_ops cs35l56_hda_client_ops = {

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 *base_name, const char *system_name,
const char *amp_name,
const char *filetype)
{
@@ -405,20 +405,13 @@ static int cs35l56_hda_request_firmware_file(struct cs35l56_hda *cs35l56,
int ret = 0;

if (system_name && amp_name)
- *filename = kasprintf(GFP_KERNEL, "%scs35l56-%02x%s-dsp1-misc-%s-%s.%s", dir,
- cs35l56->base.rev,
- cs35l56->base.secured ? "-s" : "",
+ *filename = kasprintf(GFP_KERNEL, "%s-%s-%s.%s", base_name,
system_name, amp_name, filetype);
else if (system_name)
- *filename = kasprintf(GFP_KERNEL, "%scs35l56-%02x%s-dsp1-misc-%s.%s", dir,
- cs35l56->base.rev,
- cs35l56->base.secured ? "-s" : "",
+ *filename = kasprintf(GFP_KERNEL, "%s-%s.%s", base_name,
system_name, filetype);
else
- *filename = kasprintf(GFP_KERNEL, "%scs35l56-%02x%s-dsp1-misc.%s", dir,
- cs35l56->base.rev,
- cs35l56->base.secured ? "-s" : "",
- filetype);
+ *filename = kasprintf(GFP_KERNEL, "%s.%s", base_name, filetype);

if (!*filename)
return -ENOMEM;
@@ -451,8 +444,8 @@ static int cs35l56_hda_request_firmware_file(struct cs35l56_hda *cs35l56,
return 0;
}

-static const char cirrus_dir[] = "cirrus/";
static void cs35l56_hda_request_firmware_files(struct cs35l56_hda *cs35l56,
+ unsigned int preloaded_fw_ver,
const struct firmware **wmfw_firmware,
char **wmfw_filename,
const struct firmware **coeff_firmware,
@@ -460,29 +453,43 @@ static void cs35l56_hda_request_firmware_files(struct cs35l56_hda *cs35l56,
{
const char *system_name = cs35l56->system_name;
const char *amp_name = cs35l56->amp_name;
+ char base_name[37];
int ret;

+ if (preloaded_fw_ver) {
+ snprintf(base_name, sizeof(base_name),
+ "cirrus/cs35l56-%02x%s-%06x-dsp1-misc",
+ cs35l56->base.rev,
+ cs35l56->base.secured ? "-s" : "",
+ preloaded_fw_ver & 0xffffff);
+ } else {
+ snprintf(base_name, sizeof(base_name),
+ "cirrus/cs35l56-%02x%s-dsp1-misc",
+ cs35l56->base.rev,
+ cs35l56->base.secured ? "-s" : "");
+ }
+
if (system_name && amp_name) {
if (!cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_filename,
- cirrus_dir, system_name, amp_name, "wmfw")) {
+ base_name, system_name, amp_name, "wmfw")) {
cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
- cirrus_dir, system_name, amp_name, "bin");
+ base_name, system_name, amp_name, "bin");
return;
}
}

if (system_name) {
if (!cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_filename,
- cirrus_dir, system_name, NULL, "wmfw")) {
+ base_name, system_name, NULL, "wmfw")) {
if (amp_name)
cs35l56_hda_request_firmware_file(cs35l56,
coeff_firmware, coeff_filename,
- cirrus_dir, system_name,
+ base_name, system_name,
amp_name, "bin");
if (!*coeff_firmware)
cs35l56_hda_request_firmware_file(cs35l56,
coeff_firmware, coeff_filename,
- cirrus_dir, system_name,
+ base_name, system_name,
NULL, "bin");
return;
}
@@ -493,26 +500,26 @@ static void cs35l56_hda_request_firmware_files(struct cs35l56_hda *cs35l56,
*/
if (amp_name)
cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
- cirrus_dir, system_name, amp_name, "bin");
+ base_name, system_name, amp_name, "bin");
if (!*coeff_firmware)
cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
- cirrus_dir, system_name, NULL, "bin");
+ base_name, system_name, NULL, "bin");

if (*coeff_firmware)
return;
}

ret = cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_filename,
- cirrus_dir, NULL, NULL, "wmfw");
+ base_name, NULL, NULL, "wmfw");
if (!ret) {
cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
- cirrus_dir, NULL, NULL, "bin");
+ base_name, NULL, NULL, "bin");
return;
}

if (!*coeff_firmware)
cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
- cirrus_dir, NULL, NULL, "bin");
+ base_name, NULL, NULL, "bin");
}

static void cs35l56_hda_release_firmware_files(const struct firmware *wmfw_firmware,
@@ -546,7 +553,8 @@ static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56)
const struct firmware *wmfw_firmware = NULL;
char *coeff_filename = NULL;
char *wmfw_filename = NULL;
- unsigned int firmware_missing;
+ unsigned int preloaded_fw_ver;
+ bool firmware_missing;
int ret = 0;

/* Prepare for a new DSP power-up */
@@ -557,24 +565,21 @@ static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56)

pm_runtime_get_sync(cs35l56->base.dev);

- ret = regmap_read(cs35l56->base.regmap, CS35L56_PROTECTION_STATUS, &firmware_missing);
- if (ret) {
- dev_err(cs35l56->base.dev, "Failed to read PROTECTION_STATUS: %d\n", ret);
- goto err_pm_put;
- }
-
- firmware_missing &= CS35L56_FIRMWARE_MISSING;
-
/*
- * Firmware can only be downloaded if the CS35L56 is secured or is
- * running from the built-in ROM. If it is secured the BIOS will have
- * downloaded firmware, and the wmfw/bin files will only contain
- * tunings that are safe to download with the firmware running.
+ * The firmware can only be upgraded if it is currently running
+ * from the built-in ROM. If not, the wmfw/bin must be for the
+ * version of firmware that is running on the chip.
*/
- if (cs35l56->base.secured || firmware_missing) {
- cs35l56_hda_request_firmware_files(cs35l56, &wmfw_firmware, &wmfw_filename,
- &coeff_firmware, &coeff_filename);
- }
+ ret = cs35l56_read_prot_status(&cs35l56->base, &firmware_missing, &preloaded_fw_ver);
+ if (ret)
+ goto err_pm_put;
+
+ if (firmware_missing)
+ preloaded_fw_ver = 0;
+
+ cs35l56_hda_request_firmware_files(cs35l56, preloaded_fw_ver,
+ &wmfw_firmware, &wmfw_filename,
+ &coeff_firmware, &coeff_filename);

/*
* If the BIOS didn't patch the firmware a bin file is mandatory to
@@ -589,12 +594,12 @@ static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56)
mutex_lock(&cs35l56->base.irq_lock);

/*
- * 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 the firmware hasn't been patched it must be shutdown before
+ * doing a full patch and reset afterwards. If it is already
+ * running a patched version the firmware files only contain
+ * tunings and we can use the lower cost reinit sequence instead.
*/
- if (!cs35l56->base.secured && (wmfw_firmware || coeff_firmware)) {
+ if (firmware_missing && (wmfw_firmware || coeff_firmware)) {
ret = cs35l56_firmware_shutdown(&cs35l56->base);
if (ret)
goto err;
@@ -613,7 +618,7 @@ static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56)
if (coeff_filename)
dev_dbg(cs35l56->base.dev, "Loaded Coefficients: %s\n", coeff_filename);

- if (cs35l56->base.secured) {
+ if (!firmware_missing) {
ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT);
if (ret)
goto err_powered_up;
--
2.39.2


2024-01-29 16:37:38

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 08/18] ASoC: cs35l56: Fix default SDW TX mixer registers

Patch the SDW TX mixer registers to silicon defaults.

CS35L56 is designed for SDCA and a generic SDCA driver would
know nothing about these chip-specific registers. So the
firmware sets up the SDW TX mixer registers to whatever audio
is relevant on a specific system.

This means that the driver cannot assume the initial values
of these registers. But Linux has ALSA controls to configure
routing, so the registers can be patched to silicon default and
the ALSA controls used to select what audio to feed back to the
host capture path.

Backport note:
This won't apply to kernels older than v6.6.

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56")
---
sound/soc/codecs/cs35l56-shared.c | 9 +++++++++
1 file changed, 9 insertions(+)

diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c
index 35789ffc63af..a812abf90836 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -12,6 +12,15 @@
#include "cs35l56.h"

static const struct reg_sequence cs35l56_patch[] = {
+ /*
+ * Firmware can change these to non-defaults to satisfy SDCA.
+ * Ensure that they are at known defaults.
+ */
+ { CS35L56_SWIRE_DP3_CH1_INPUT, 0x00000018 },
+ { CS35L56_SWIRE_DP3_CH2_INPUT, 0x00000019 },
+ { CS35L56_SWIRE_DP3_CH3_INPUT, 0x00000029 },
+ { CS35L56_SWIRE_DP3_CH4_INPUT, 0x00000028 },
+
/* These are not reset by a soft-reset, so patch to defaults. */
{ CS35L56_MAIN_RENDER_USER_MUTE, 0x00000000 },
{ CS35L56_MAIN_RENDER_USER_VOLUME, 0x00000000 },
--
2.39.2


2024-01-29 16:37:51

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 09/18] ALSA: hda: cs35l56: Initialize all ASP1 registers

Add ASP1_FRAME_CONTROL1, ASP1_FRAME_CONTROL5 and the ASP1_TX?_INPUT
registers to the sequence used to initialize the ASP configuration.
Write this sequence to the cache and directly to the registers to
ensure that they match.

A system-specific firmware can patch these registers to values that are
not the silicon default, so that the CS35L56 boots already in the
configuration used by Windows or by "driverless" Windows setups such
as factory tuning.

These may not match how Linux is configuring the HDA codec. And anyway
on Linux the ALSA controls are used to configure routing options.

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: 73cfbfa9caea ("ALSA: hda/cs35l56: Add driver for Cirrus Logic CS35L56 amplifier")
---
sound/pci/hda/cs35l56_hda.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)

diff --git a/sound/pci/hda/cs35l56_hda.c b/sound/pci/hda/cs35l56_hda.c
index b61e1de8c4bf..f22bcb104a4e 100644
--- a/sound/pci/hda/cs35l56_hda.c
+++ b/sound/pci/hda/cs35l56_hda.c
@@ -30,14 +30,23 @@
* 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
+ *
+ * Override any Windows-specific mixer settings applied by the firmware.
*/
static const struct reg_sequence cs35l56_hda_dai_config[] = {
{ CS35L56_ASP1_CONTROL1, 0x00000021 },
{ CS35L56_ASP1_CONTROL2, 0x20200200 },
{ CS35L56_ASP1_CONTROL3, 0x00000003 },
+ { CS35L56_ASP1_FRAME_CONTROL1, 0x03020100 },
+ { CS35L56_ASP1_FRAME_CONTROL5, 0x00020100 },
{ CS35L56_ASP1_DATA_CONTROL5, 0x00000018 },
{ CS35L56_ASP1_DATA_CONTROL1, 0x00000018 },
{ CS35L56_ASP1_ENABLES1, 0x00000000 },
+ { CS35L56_ASP1TX1_INPUT, 0x00000018 },
+ { CS35L56_ASP1TX2_INPUT, 0x00000019 },
+ { CS35L56_ASP1TX3_INPUT, 0x00000020 },
+ { CS35L56_ASP1TX4_INPUT, 0x00000028 },
+
};

static void cs35l56_hda_play(struct cs35l56_hda *cs35l56)
@@ -133,6 +142,10 @@ static int cs35l56_hda_runtime_resume(struct device *dev)
}
}

+ ret = cs35l56_force_sync_asp1_registers_from_cache(&cs35l56->base);
+ if (ret)
+ goto err;
+
return 0;

err:
@@ -976,6 +989,9 @@ int cs35l56_hda_common_probe(struct cs35l56_hda *cs35l56, int id)

regmap_multi_reg_write(cs35l56->base.regmap, cs35l56_hda_dai_config,
ARRAY_SIZE(cs35l56_hda_dai_config));
+ ret = cs35l56_force_sync_asp1_registers_from_cache(&cs35l56->base);
+ if (ret)
+ goto err;

/*
* By default only enable one ASP1TXn, where n=amplifier index,
--
2.39.2


2024-01-29 16:38:02

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 11/18] ASoC: cs35l56: Fix misuse of wm_adsp 'part' string for silicon revision

Put the silicon revision and secured flag in the wm_adsp fwf_name
string instead of including them in the part string.

This changes the format of the firmware name string from

cs35l56[s]-rev-misc[-system_name]

to
cs35l56-rev[-s]-misc[-system_name]

No firmware files have been published, so this doesn't cause a
compatibility break.

Silicon revision and secured flag are included in the firmware
filename to pick a firmware compatible with the part. These strings
were being added to the part string, but that is a misuse of the
string. The correct place for these is the fwf_name string, which
is specifically intended to select between multiple firmware files
for the same part.

Backport note:
This won't apply to kernels older than v6.6.

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: 608f1b0dbdde ("ASoC: cs35l56: Move DSP part string generation so that it is done only once")
---
sound/soc/codecs/cs35l56.c | 23 ++++++++++++++++-------
1 file changed, 16 insertions(+), 7 deletions(-)

diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index 1b51650a19ff..8899c02c6dea 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -907,6 +907,18 @@ static void cs35l56_dsp_work(struct work_struct *work)

pm_runtime_get_sync(cs35l56->base.dev);

+ /* Populate fw file qualifier with the revision and security state */
+ if (!cs35l56->dsp.fwf_name) {
+ cs35l56->dsp.fwf_name = kasprintf(GFP_KERNEL, "%02x%s-dsp1",
+ cs35l56->base.rev,
+ cs35l56->base.secured ? "-s" : "");
+ if (!cs35l56->dsp.fwf_name)
+ goto err;
+ }
+
+ dev_dbg(cs35l56->base.dev, "DSP fwf name: '%s' system name: '%s'\n",
+ cs35l56->dsp.fwf_name, cs35l56->dsp.system_name);
+
/*
* When the device is running in secure mode the firmware files can
* only contain insecure tunings and therefore we do not need to
@@ -926,7 +938,7 @@ static void cs35l56_dsp_work(struct work_struct *work)
* on the DAPM mutex.
*/
queue_work(cs35l56->dsp_wq, &cs35l56->mux_init_work);
-
+err:
pm_runtime_mark_last_busy(cs35l56->base.dev);
pm_runtime_put_autosuspend(cs35l56->base.dev);
}
@@ -979,6 +991,9 @@ static void cs35l56_component_remove(struct snd_soc_component *component)

wm_adsp2_component_remove(&cs35l56->dsp, component);

+ kfree(cs35l56->dsp.fwf_name);
+ cs35l56->dsp.fwf_name = NULL;
+
cs35l56->component = NULL;
}

@@ -1330,12 +1345,6 @@ int cs35l56_init(struct cs35l56_private *cs35l56)
if (ret)
return ret;

- /* Populate the DSP information with the revision and security state */
- 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;
-
if (!cs35l56->base.reset_gpio) {
dev_dbg(cs35l56->base.dev, "No reset gpio: using soft reset\n");
cs35l56->soft_resetting = true;
--
2.39.2


2024-01-29 16:38:28

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 03/18] ASoC: cs35l56: cs35l56_component_remove() must clear cs35l56->component

The cs35l56->component pointer is used by the suspend-resume handling to
know whether the driver is fully instantiated. This is to prevent it
queuing dsp_work which would result in calling wm_adsp when the driver
is not an instantiated ASoC component. So this pointer must be cleared
by cs35l56_component_remove().

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56")
---
sound/soc/codecs/cs35l56.c | 2 ++
1 file changed, 2 insertions(+)

diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index 45b4de3eff94..09944db4db30 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -809,6 +809,8 @@ static void cs35l56_component_remove(struct snd_soc_component *component)
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);

cancel_work_sync(&cs35l56->dsp_work);
+
+ cs35l56->component = NULL;
}

static int cs35l56_set_bias_level(struct snd_soc_component *component,
--
2.39.2


2024-01-29 16:38:34

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 07/18] ASoC: cs35l56: Fix to ensure ASP1 registers match cache

Add a dummy SUPPLY widget connected to the ASP that forces the
chip registers to match the regmap cache when the ASP is
powered-up.

On a SoundWire system the ASP is free for use as a chip-to-chip
interconnect. This can be either for the firmware on multiple
CS35L56 to share reference audio; or as a bridge to another
device. If it is a firmware interconnect it is owned by the
firmware and the Linux driver should avoid writing the registers.
However. If it is a bridge then Linux may take over and handle
it as a normal codec-to-codec link.

CS35L56 is designed for SDCA and a generic SDCA driver would
know nothing about these chip-specific registers. So if the
ASP is being used on a SoundWire system the firmware sets up the
ASP registers. This means that we can't assume the default
state of the ASP registers. But we don't know the initial state
that the firmware set them to until after the firmware has been
downloaded and booted, which can take several seconds when
downloading multiple amps.

To avoid blocking probe() for several seconds waiting for the
firmware, the silicon defaults are assumed. This allows the machine
driver to setup the ASP configuration during probe() without being
blocked. If the ASP is hooked up and used, the SUPPLY widget
ensures that the chip registers match what was configured in the
regmap cache.

If the machine driver does not hook up the ASP, it is assumed that
it won't call any functions to configure the ASP DAI. Therefore
the regmap cache will be clean for these registers so a
regcache_sync() will not overwrite the chip registers. If the
DAI is not hooked up, the dummy SUPPLY widget will not be
invoked so it will never force-overwrite the chip registers.

Backport note:
This won't apply cleanly to kernels older than v6.6.

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56")
---
include/sound/cs35l56.h | 1 +
sound/soc/codecs/cs35l56-shared.c | 41 +++++++++++++++++++++++++++++++
sound/soc/codecs/cs35l56.c | 21 ++++++++++++++++
3 files changed, 63 insertions(+)

diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h
index 8c18e8b6d27d..4db36c893d9d 100644
--- a/include/sound/cs35l56.h
+++ b/include/sound/cs35l56.h
@@ -272,6 +272,7 @@ extern const char * const cs35l56_tx_input_texts[CS35L56_NUM_INPUT_SRC];
extern const unsigned int cs35l56_tx_input_values[CS35L56_NUM_INPUT_SRC];

int cs35l56_set_patch(struct cs35l56_base *cs35l56_base);
+int cs35l56_force_sync_asp1_registers_from_cache(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);
diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c
index 0cd572de73a9..35789ffc63af 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -195,6 +195,47 @@ static bool cs35l56_volatile_reg(struct device *dev, unsigned int reg)
}
}

+/*
+ * The firmware boot sequence can overwrite the ASP1 config registers so that
+ * they don't match regmap's view of their values. Rewrite the values from the
+ * regmap cache into the hardware registers.
+ */
+int cs35l56_force_sync_asp1_registers_from_cache(struct cs35l56_base *cs35l56_base)
+{
+ struct reg_sequence asp1_regs[] = {
+ { .reg = CS35L56_ASP1_ENABLES1 },
+ { .reg = CS35L56_ASP1_CONTROL1 },
+ { .reg = CS35L56_ASP1_CONTROL2 },
+ { .reg = CS35L56_ASP1_CONTROL3 },
+ { .reg = CS35L56_ASP1_FRAME_CONTROL1 },
+ { .reg = CS35L56_ASP1_FRAME_CONTROL5 },
+ { .reg = CS35L56_ASP1_DATA_CONTROL1 },
+ { .reg = CS35L56_ASP1_DATA_CONTROL5 },
+ };
+ int i, ret;
+
+ /* Read values from regmap cache into a write sequence */
+ for (i = 0; i < ARRAY_SIZE(asp1_regs); ++i) {
+ ret = regmap_read(cs35l56_base->regmap, asp1_regs[i].reg, &asp1_regs[i].def);
+ if (ret)
+ goto err;
+ }
+
+ /* Write the values cache-bypassed so that they will be written to silicon */
+ ret = regmap_multi_reg_write_bypassed(cs35l56_base->regmap, asp1_regs,
+ ARRAY_SIZE(asp1_regs));
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ dev_err(cs35l56_base->dev, "Failed to sync ASP1 registers: %d\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_force_sync_asp1_registers_from_cache, SND_SOC_CS35L56_SHARED);
+
int cs35l56_mbox_send(struct cs35l56_base *cs35l56_base, unsigned int command)
{
unsigned int val;
diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index ea5d2b2eb82a..41aa79848b15 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -148,6 +148,21 @@ static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_sdw1tx4_enum,
static const struct snd_kcontrol_new sdw1_tx4_mux =
SOC_DAPM_ENUM("SDW1TX4 SRC", cs35l56_sdw1tx4_enum);

+static int cs35l56_asp1_cfg_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /* Override register values set by firmware boot */
+ return cs35l56_force_sync_asp1_registers_from_cache(&cs35l56->base);
+ default:
+ return 0;
+ }
+}
+
static int cs35l56_play_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
@@ -184,6 +199,9 @@ static const struct snd_soc_dapm_widget cs35l56_dapm_widgets[] = {
SND_SOC_DAPM_REGULATOR_SUPPLY("VDD_B", 0, 0),
SND_SOC_DAPM_REGULATOR_SUPPLY("VDD_AMP", 0, 0),

+ SND_SOC_DAPM_SUPPLY("ASP1 CFG", SND_SOC_NOPM, 0, 0, cs35l56_asp1_cfg_event,
+ SND_SOC_DAPM_PRE_PMU),
+
SND_SOC_DAPM_SUPPLY("PLAY", SND_SOC_NOPM, 0, 0, cs35l56_play_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),

@@ -251,6 +269,9 @@ static const struct snd_soc_dapm_route cs35l56_audio_map[] = {
{ "AMP", NULL, "VDD_B" },
{ "AMP", NULL, "VDD_AMP" },

+ { "ASP1 Playback", NULL, "ASP1 CFG" },
+ { "ASP1 Capture", NULL, "ASP1 CFG" },
+
{ "ASP1 Playback", NULL, "PLAY" },
{ "SDW1 Playback", NULL, "PLAY" },

--
2.39.2


2024-01-29 16:40:47

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 14/18] ASoC: cs35l56: Allow more time for firmware to boot

The original 50ms timeout for firmware boot is not long enough for
worst-case time to reboot after a firmware download. Increase the
timeout to 250ms.

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56")
---
include/sound/cs35l56.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h
index 23da6298ab37..b24716ab2750 100644
--- a/include/sound/cs35l56.h
+++ b/include/sound/cs35l56.h
@@ -242,7 +242,7 @@

#define CS35L56_CONTROL_PORT_READY_US 2200
#define CS35L56_HALO_STATE_POLL_US 1000
-#define CS35L56_HALO_STATE_TIMEOUT_US 50000
+#define CS35L56_HALO_STATE_TIMEOUT_US 250000
#define CS35L56_RESET_PULSE_MIN_US 1100
#define CS35L56_WAKE_HOLD_TIME_US 1000

--
2.39.2


2024-01-29 16:40:56

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 10/18] ASoC: cs35l56: Fix for initializing ASP1 mixer registers

Defer initializing the state of the ASP1 mixer registers until
the firmware has been downloaded and rebooted.

On a SoundWire system the ASP is free for use as a chip-to-chip
interconnect. This can be either for the firmware on multiple
CS35L56 to share reference audio; or as a bridge to another
device. If it is a firmware interconnect it is owned by the
firmware and the Linux driver should avoid writing the registers.
However, if it is a bridge then Linux may take over and handle
it as a normal codec-to-codec link. Even if the ASP is used
as a firmware-firmware interconnect it is useful to have
ALSA controls for the ASP mixer. They are at least useful for
debugging.

CS35L56 is designed for SDCA and a generic SDCA driver would
know nothing about these chip-specific registers. So if the
ASP is being used on a SoundWire system the firmware sets up the
ASP mixer registers. This means that we can't assume the default
state of these registers. But we don't know the initial state
that the firmware set them to until after the firmware has been
downloaded and booted, which can take several seconds when
downloading multiple amps.

DAPM normally reads the initial state of mux registers during
probe() but this would mean blocking probe() for several seconds
until the firmware has initialized them. To avoid this, the
mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read
the register state. Custom get/set callbacks are implemented for
ALSA control access, and these can safely block waiting for the
firmware download.

After the firmware download has completed, the state of the
mux registers is known so a work job is queued to call
snd_soc_dapm_mux_update_power() on each of the mux widgets.

Backport note:
This won't apply cleanly to kernels older than v6.6.

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56")
---
sound/soc/codecs/cs35l56-shared.c | 7 +-
sound/soc/codecs/cs35l56.c | 172 +++++++++++++++++++++++++++---
sound/soc/codecs/cs35l56.h | 1 +
3 files changed, 163 insertions(+), 17 deletions(-)

diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c
index a812abf90836..9a70db0fa418 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -43,10 +43,9 @@ static const struct reg_default cs35l56_reg_defaults[] = {
{ CS35L56_ASP1_FRAME_CONTROL5, 0x00020100 },
{ CS35L56_ASP1_DATA_CONTROL1, 0x00000018 },
{ CS35L56_ASP1_DATA_CONTROL5, 0x00000018 },
- { CS35L56_ASP1TX1_INPUT, 0x00000018 },
- { CS35L56_ASP1TX2_INPUT, 0x00000019 },
- { CS35L56_ASP1TX3_INPUT, 0x00000020 },
- { CS35L56_ASP1TX4_INPUT, 0x00000028 },
+
+ /* no defaults for ASP1TX mixer */
+
{ CS35L56_SWIRE_DP3_CH1_INPUT, 0x00000018 },
{ CS35L56_SWIRE_DP3_CH2_INPUT, 0x00000019 },
{ CS35L56_SWIRE_DP3_CH3_INPUT, 0x00000029 },
diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index 41aa79848b15..1b51650a19ff 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -59,6 +59,135 @@ static int cs35l56_dspwait_put_volsw(struct snd_kcontrol *kcontrol,
return snd_soc_put_volsw(kcontrol, ucontrol);
}

+static const unsigned short cs35l56_asp1_mixer_regs[] = {
+ CS35L56_ASP1TX1_INPUT, CS35L56_ASP1TX2_INPUT,
+ CS35L56_ASP1TX3_INPUT, CS35L56_ASP1TX4_INPUT,
+};
+
+static const char * const cs35l56_asp1_mux_control_names[] = {
+ "ASP1 TX1 Source", "ASP1 TX2 Source", "ASP1 TX3 Source", "ASP1 TX4 Source"
+};
+
+static int cs35l56_dspwait_asp1tx_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol);
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ int index = e->shift_l;
+ unsigned int addr, val;
+ int ret;
+
+ /* Wait for mux to be initialized */
+ cs35l56_wait_dsp_ready(cs35l56);
+ flush_work(&cs35l56->mux_init_work);
+
+ addr = cs35l56_asp1_mixer_regs[index];
+ ret = regmap_read(cs35l56->base.regmap, addr, &val);
+ if (ret)
+ return ret;
+
+ val &= CS35L56_ASP_TXn_SRC_MASK;
+ ucontrol->value.enumerated.item[0] = snd_soc_enum_val_to_item(e, val);
+
+ return 0;
+}
+
+static int cs35l56_dspwait_asp1tx_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol);
+ struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ int item = ucontrol->value.enumerated.item[0];
+ int index = e->shift_l;
+ unsigned int addr, val;
+ bool changed;
+ int ret;
+
+ /* Wait for mux to be initialized */
+ cs35l56_wait_dsp_ready(cs35l56);
+ flush_work(&cs35l56->mux_init_work);
+
+ addr = cs35l56_asp1_mixer_regs[index];
+ val = snd_soc_enum_item_to_val(e, item);
+
+ ret = regmap_update_bits_check(cs35l56->base.regmap, addr,
+ CS35L56_ASP_TXn_SRC_MASK, val, &changed);
+ if (!ret)
+ return ret;
+
+ if (changed)
+ snd_soc_dapm_mux_update_power(dapm, kcontrol, item, e, NULL);
+
+ return changed;
+}
+
+static void cs35l56_mark_asp1_mixer_widgets_dirty(struct cs35l56_private *cs35l56)
+{
+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cs35l56->component);
+ const char *prefix = cs35l56->component->name_prefix;
+ char full_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ const char *name;
+ struct snd_kcontrol *kcontrol;
+ struct soc_enum *e;
+ unsigned int val[4];
+ int i, item, ret;
+
+ /*
+ * Resume so we can read the registers from silicon if the regmap
+ * cache has not yet been populated.
+ */
+ ret = pm_runtime_resume_and_get(cs35l56->base.dev);
+ if (ret < 0)
+ return;
+
+ ret = regmap_bulk_read(cs35l56->base.regmap, CS35L56_ASP1TX1_INPUT,
+ val, ARRAY_SIZE(val));
+
+ pm_runtime_mark_last_busy(cs35l56->base.dev);
+ pm_runtime_put_autosuspend(cs35l56->base.dev);
+
+ if (ret) {
+ dev_err(cs35l56->base.dev, "Failed to read ASP1 mixer regs: %d\n", ret);
+ return;
+ }
+
+ snd_soc_card_mutex_lock(dapm->card);
+ WARN_ON(!dapm->card->instantiated);
+
+ for (i = 0; i < ARRAY_SIZE(cs35l56_asp1_mux_control_names); ++i) {
+ name = cs35l56_asp1_mux_control_names[i];
+
+ if (prefix) {
+ snprintf(full_name, sizeof(full_name), "%s %s", prefix, name);
+ name = full_name;
+ }
+
+ kcontrol = snd_soc_card_get_kcontrol(dapm->card, name);
+ if (!kcontrol) {
+ dev_warn(cs35l56->base.dev, "Could not find control %s\n", name);
+ continue;
+ }
+
+ e = (struct soc_enum *)kcontrol->private_value;
+ item = snd_soc_enum_val_to_item(e, val[i] & CS35L56_ASP_TXn_SRC_MASK);
+ snd_soc_dapm_mux_update_power(dapm, kcontrol, item, e, NULL);
+ }
+
+ snd_soc_card_mutex_unlock(dapm->card);
+}
+
+static void cs35l56_mux_init_work(struct work_struct *work)
+{
+ struct cs35l56_private *cs35l56 = container_of(work,
+ struct cs35l56_private,
+ mux_init_work);
+
+ cs35l56_mark_asp1_mixer_widgets_dirty(cs35l56);
+}
+
static DECLARE_TLV_DB_SCALE(vol_tlv, -10000, 25, 0);

static const struct snd_kcontrol_new cs35l56_controls[] = {
@@ -77,40 +206,44 @@ static const struct snd_kcontrol_new cs35l56_controls[] = {
};

static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_asp1tx1_enum,
- CS35L56_ASP1TX1_INPUT,
- 0, CS35L56_ASP_TXn_SRC_MASK,
+ SND_SOC_NOPM,
+ 0, 0,
cs35l56_tx_input_texts,
cs35l56_tx_input_values);

static const struct snd_kcontrol_new asp1_tx1_mux =
- SOC_DAPM_ENUM("ASP1TX1 SRC", cs35l56_asp1tx1_enum);
+ SOC_DAPM_ENUM_EXT("ASP1TX1 SRC", cs35l56_asp1tx1_enum,
+ cs35l56_dspwait_asp1tx_get, cs35l56_dspwait_asp1tx_put);

static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_asp1tx2_enum,
- CS35L56_ASP1TX2_INPUT,
- 0, CS35L56_ASP_TXn_SRC_MASK,
+ SND_SOC_NOPM,
+ 1, 0,
cs35l56_tx_input_texts,
cs35l56_tx_input_values);

static const struct snd_kcontrol_new asp1_tx2_mux =
- SOC_DAPM_ENUM("ASP1TX2 SRC", cs35l56_asp1tx2_enum);
+ SOC_DAPM_ENUM_EXT("ASP1TX2 SRC", cs35l56_asp1tx2_enum,
+ cs35l56_dspwait_asp1tx_get, cs35l56_dspwait_asp1tx_put);

static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_asp1tx3_enum,
- CS35L56_ASP1TX3_INPUT,
- 0, CS35L56_ASP_TXn_SRC_MASK,
+ SND_SOC_NOPM,
+ 2, 0,
cs35l56_tx_input_texts,
cs35l56_tx_input_values);

static const struct snd_kcontrol_new asp1_tx3_mux =
- SOC_DAPM_ENUM("ASP1TX3 SRC", cs35l56_asp1tx3_enum);
+ SOC_DAPM_ENUM_EXT("ASP1TX3 SRC", cs35l56_asp1tx3_enum,
+ cs35l56_dspwait_asp1tx_get, cs35l56_dspwait_asp1tx_put);

static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_asp1tx4_enum,
- CS35L56_ASP1TX4_INPUT,
- 0, CS35L56_ASP_TXn_SRC_MASK,
+ SND_SOC_NOPM,
+ 3, 0,
cs35l56_tx_input_texts,
cs35l56_tx_input_values);

static const struct snd_kcontrol_new asp1_tx4_mux =
- SOC_DAPM_ENUM("ASP1TX4 SRC", cs35l56_asp1tx4_enum);
+ SOC_DAPM_ENUM_EXT("ASP1TX4 SRC", cs35l56_asp1tx4_enum,
+ cs35l56_dspwait_asp1tx_get, cs35l56_dspwait_asp1tx_put);

static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_sdw1tx1_enum,
CS35L56_SWIRE_DP3_CH1_INPUT,
@@ -785,6 +918,15 @@ static void cs35l56_dsp_work(struct work_struct *work)
else
cs35l56_patch(cs35l56);

+
+ /*
+ * Set starting value of ASP1 mux widgets. Updating a mux takes
+ * the DAPM mutex. Post this to a separate job so that DAPM
+ * power-up can wait for dsp_work to complete without deadlocking
+ * on the DAPM mutex.
+ */
+ queue_work(cs35l56->dsp_wq, &cs35l56->mux_init_work);
+
pm_runtime_mark_last_busy(cs35l56->base.dev);
pm_runtime_put_autosuspend(cs35l56->base.dev);
}
@@ -830,6 +972,7 @@ static void cs35l56_component_remove(struct snd_soc_component *component)
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);

cancel_work_sync(&cs35l56->dsp_work);
+ cancel_work_sync(&cs35l56->mux_init_work);

if (cs35l56->dsp.cs_dsp.booted)
wm_adsp_power_down(&cs35l56->dsp);
@@ -897,8 +1040,10 @@ int cs35l56_system_suspend(struct device *dev)

dev_dbg(dev, "system_suspend\n");

- if (cs35l56->component)
+ if (cs35l56->component) {
flush_work(&cs35l56->dsp_work);
+ cancel_work_sync(&cs35l56->mux_init_work);
+ }

/*
* The interrupt line is normally shared, but after we start suspending
@@ -1049,6 +1194,7 @@ static int cs35l56_dsp_init(struct cs35l56_private *cs35l56)
return -ENOMEM;

INIT_WORK(&cs35l56->dsp_work, cs35l56_dsp_work);
+ INIT_WORK(&cs35l56->mux_init_work, cs35l56_mux_init_work);

dsp = &cs35l56->dsp;
cs35l56_init_cs_dsp(&cs35l56->base, &dsp->cs_dsp);
diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h
index 8159c3e217d9..dc2fe4c91e67 100644
--- a/sound/soc/codecs/cs35l56.h
+++ b/sound/soc/codecs/cs35l56.h
@@ -34,6 +34,7 @@ struct cs35l56_private {
struct wm_adsp dsp; /* must be first member */
struct cs35l56_base base;
struct work_struct dsp_work;
+ struct work_struct mux_init_work;
struct workqueue_struct *dsp_wq;
struct snd_soc_component *component;
struct regulator_bulk_data supplies[CS35L56_NUM_BULK_SUPPLIES];
--
2.39.2


2024-01-29 16:41:26

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 01/18] ASoC: wm_adsp: Fix firmware file search order

Check for the cases of system-specific bin file without a
wmfw before falling back to looking for a generic wmfw.

All system-specific options should be tried before falling
back to loading a generic wmfw/bin. With the original code,
the presence of a fallback generic wmfw on the filesystem
would prevent using a system-specific tuning with a ROM
firmware.

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: 0e7d82cbea8b ("ASoC: wm_adsp: Add support for loading bin files without wmfw")
---
sound/soc/codecs/wm_adsp.c | 44 ++++++++++++++++----------------------
1 file changed, 19 insertions(+), 25 deletions(-)

diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c
index c01e31175015..bd60ceebb6a9 100644
--- a/sound/soc/codecs/wm_adsp.c
+++ b/sound/soc/codecs/wm_adsp.c
@@ -823,6 +823,23 @@ static int wm_adsp_request_firmware_files(struct wm_adsp *dsp,
}
}

+ /* Check system-specific bin without wmfw before falling back to generic */
+ if (dsp->wmfw_optional && system_name) {
+ if (asoc_component_prefix)
+ wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename,
+ cirrus_dir, system_name,
+ asoc_component_prefix, "bin");
+
+ if (!*coeff_firmware)
+ wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename,
+ cirrus_dir, system_name,
+ NULL, "bin");
+
+ if (*coeff_firmware)
+ return 0;
+ }
+
+ /* Check legacy location */
if (!wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename,
"", NULL, NULL, "wmfw")) {
wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename,
@@ -830,38 +847,15 @@ static int wm_adsp_request_firmware_files(struct wm_adsp *dsp,
return 0;
}

+ /* Fall back to generic wmfw and optional matching bin */
ret = wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename,
cirrus_dir, NULL, NULL, "wmfw");
- if (!ret) {
+ if (!ret || dsp->wmfw_optional) {
wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename,
cirrus_dir, NULL, NULL, "bin");
return 0;
}

- if (dsp->wmfw_optional) {
- if (system_name) {
- if (asoc_component_prefix)
- wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename,
- cirrus_dir, system_name,
- asoc_component_prefix, "bin");
-
- if (!*coeff_firmware)
- wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename,
- cirrus_dir, system_name,
- NULL, "bin");
- }
-
- if (!*coeff_firmware)
- wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename,
- "", NULL, NULL, "bin");
-
- if (!*coeff_firmware)
- wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename,
- cirrus_dir, NULL, NULL, "bin");
-
- return 0;
- }
-
adsp_err(dsp, "Failed to request firmware <%s>%s-%s-%s<-%s<%s>>.wmfw\n",
cirrus_dir, dsp->part, dsp->fwf_name, wm_adsp_fw[dsp->fw].file,
system_name, asoc_component_prefix);
--
2.39.2


2024-01-29 16:49:01

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 18/18] ALSA: hda: cs35l56: Remove unused test stub function

Remove an unused stub function that calls a non-existant function.

This function was accidentally added as part of commit
2144833e7b41 ("ALSA: hda: cirrus_scodec: Add KUnit test"). It was
a relic of an earlier version of the test that should have been
removed.

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: 2144833e7b41 ("ALSA: hda: cirrus_scodec: Add KUnit test")
---
sound/pci/hda/cs35l56_hda.c | 10 ----------
1 file changed, 10 deletions(-)

diff --git a/sound/pci/hda/cs35l56_hda.c b/sound/pci/hda/cs35l56_hda.c
index 32736d3e45ba..75a14ba54fcd 100644
--- a/sound/pci/hda/cs35l56_hda.c
+++ b/sound/pci/hda/cs35l56_hda.c
@@ -1063,16 +1063,6 @@ const struct dev_pm_ops cs35l56_hda_pm_ops = {
};
EXPORT_SYMBOL_NS_GPL(cs35l56_hda_pm_ops, SND_HDA_SCODEC_CS35L56);

-#if IS_ENABLED(CONFIG_SND_HDA_SCODEC_CS35L56_KUNIT_TEST)
-/* Hooks to export static function to KUnit test */
-
-int cs35l56_hda_test_hook_get_speaker_id(struct device *dev, int amp_index, int num_amps)
-{
- return cs35l56_hda_get_speaker_id(dev, amp_index, num_amps);
-}
-EXPORT_SYMBOL_NS_GPL(cs35l56_hda_test_hook_get_speaker_id, SND_HDA_SCODEC_CS35L56);
-#endif
-
MODULE_DESCRIPTION("CS35L56 HDA Driver");
MODULE_IMPORT_NS(SND_HDA_CIRRUS_SCODEC);
MODULE_IMPORT_NS(SND_HDA_CS_DSP_CONTROLS);
--
2.39.2


2024-01-29 16:49:32

by Richard Fitzgerald

[permalink] [raw]
Subject: [PATCH 12/18] ASoC: cs35l56: Firmware file must match the version of preloaded firmware

Check during initialization whether the firmware is already patched.
If so, include the firmware version in the wm_adsp fwf_name string.

If the firmware has already been patched by the BIOS the driver
can only replace it if it has control of hard RESET.

If the driver cannot replace the firmware, it can still load a wmfw
(for ALSA control definitions) and/or a bin (for additional tunings).
But these must match the version of firmware that is running on the
CS35L56.

The firmware is pre-patched if FIRMWARE_MISSING == 0.

Including the firmware version in the fwf_name string will
qualify the firmware file name:

Normal (unpatched or replaceable firmware):
cs35l56-rev-dsp1-misc[-system_name].[wmfw|bin]

Preloaded firmware:
cs35l56-rev[-s]-VVVVVV-dsp1-misc[-system_name].[wmfw|bin]

Where:
[-s] is an optional -s added into the name for a secured CS35L56
VVVVVV is the 24-bit firmware version in hexadecimal.

Signed-off-by: Richard Fitzgerald <[email protected]>
Fixes: 608f1b0dbdde ("ASoC: cs35l56: Move DSP part string generation so that it is done only once")
---
include/sound/cs35l56.h | 3 ++
sound/soc/codecs/cs35l56-shared.c | 36 +++++++++++++++++++--
sound/soc/codecs/cs35l56.c | 52 +++++++++++++++++--------------
3 files changed, 65 insertions(+), 26 deletions(-)

diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h
index 4db36c893d9d..5d6aefc41e64 100644
--- a/include/sound/cs35l56.h
+++ b/include/sound/cs35l56.h
@@ -75,6 +75,7 @@
#define CS35L56_DSP1_AHBM_WINDOW_DEBUG_0 0x25E2040
#define CS35L56_DSP1_AHBM_WINDOW_DEBUG_1 0x25E2044
#define CS35L56_DSP1_XMEM_UNPACKED24_0 0x2800000
+#define CS35L56_DSP1_FW_VER 0x2800010
#define CS35L56_DSP1_HALO_STATE_A1 0x2801E58
#define CS35L56_DSP1_HALO_STATE 0x28021E0
#define CS35L56_DSP1_PM_CUR_STATE_A1 0x2804000
@@ -285,6 +286,8 @@ 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_read_prot_status(struct cs35l56_base *cs35l56_base,
+ bool *fw_missing, unsigned int *fw_version);
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 9a70db0fa418..33835535ef84 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -628,10 +628,35 @@ 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_read_prot_status(struct cs35l56_base *cs35l56_base,
+ bool *fw_missing, unsigned int *fw_version)
+{
+ unsigned int prot_status;
+ int ret;
+
+ ret = regmap_read(cs35l56_base->regmap, CS35L56_PROTECTION_STATUS, &prot_status);
+ if (ret) {
+ dev_err(cs35l56_base->dev, "Get PROTECTION_STATUS failed: %d\n", ret);
+ return ret;
+ }
+
+ *fw_missing = !!(prot_status & CS35L56_FIRMWARE_MISSING);
+
+ ret = regmap_read(cs35l56_base->regmap, CS35L56_DSP1_FW_VER, fw_version);
+ if (ret) {
+ dev_err(cs35l56_base->dev, "Get FW VER failed: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_read_prot_status, SND_SOC_CS35L56_SHARED);
+
int cs35l56_hw_init(struct cs35l56_base *cs35l56_base)
{
int ret;
- unsigned int devid, revid, otpid, secured;
+ unsigned int devid, revid, otpid, secured, fw_ver;
+ bool fw_missing;

/*
* When the system is not using a reset_gpio ensure the device is
@@ -690,8 +715,13 @@ int cs35l56_hw_init(struct cs35l56_base *cs35l56_base)
return ret;
}

- dev_info(cs35l56_base->dev, "Cirrus Logic CS35L56%s Rev %02X OTP%d\n",
- cs35l56_base->secured ? "s" : "", cs35l56_base->rev, otpid);
+ ret = cs35l56_read_prot_status(cs35l56_base, &fw_missing, &fw_ver);
+ if (ret)
+ return ret;
+
+ dev_info(cs35l56_base->dev, "Cirrus Logic CS35L56%s Rev %02X OTP%d fw:%d.%d.%d (patched=%u)\n",
+ cs35l56_base->secured ? "s" : "", cs35l56_base->rev, otpid,
+ fw_ver >> 16, (fw_ver >> 8) & 0xff, fw_ver & 0xff, !fw_missing);

/* Wake source and *_BLOCKED interrupts default to unmasked, so mask them */
regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_MASK_20, 0xffffffff);
diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index 8899c02c6dea..597677422547 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -804,7 +804,7 @@ static struct snd_soc_dai_driver cs35l56_dai[] = {
}
};

-static void cs35l56_secure_patch(struct cs35l56_private *cs35l56)
+static void cs35l56_reinit_patch(struct cs35l56_private *cs35l56)
{
int ret;

@@ -816,19 +816,10 @@ static void cs35l56_secure_patch(struct cs35l56_private *cs35l56)
cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT);
}

-static void cs35l56_patch(struct cs35l56_private *cs35l56)
+static void cs35l56_patch(struct cs35l56_private *cs35l56, bool firmware_missing)
{
- unsigned int firmware_missing;
int ret;

- ret = regmap_read(cs35l56->base.regmap, CS35L56_PROTECTION_STATUS, &firmware_missing);
- if (ret) {
- dev_err(cs35l56->base.dev, "Failed to read PROTECTION_STATUS: %d\n", ret);
- return;
- }
-
- firmware_missing &= CS35L56_FIRMWARE_MISSING;
-
/*
* Disable SoundWire interrupts to prevent race with IRQ work.
* Setting sdw_irq_no_unmask prevents the handler re-enabling
@@ -901,34 +892,49 @@ static void cs35l56_dsp_work(struct work_struct *work)
struct cs35l56_private *cs35l56 = container_of(work,
struct cs35l56_private,
dsp_work);
+ unsigned int firmware_version;
+ bool firmware_missing;
+ int ret;

if (!cs35l56->base.init_done)
return;

pm_runtime_get_sync(cs35l56->base.dev);

+ ret = cs35l56_read_prot_status(&cs35l56->base, &firmware_missing, &firmware_version);
+ if (ret)
+ goto err;
+
/* Populate fw file qualifier with the revision and security state */
- if (!cs35l56->dsp.fwf_name) {
- cs35l56->dsp.fwf_name = kasprintf(GFP_KERNEL, "%02x%s-dsp1",
+ kfree(cs35l56->dsp.fwf_name);
+ if (firmware_missing) {
+ cs35l56->dsp.fwf_name = kasprintf(GFP_KERNEL, "%02x-dsp1", cs35l56->base.rev);
+ } else {
+ /* Firmware files must match the running firmware version */
+ cs35l56->dsp.fwf_name = kasprintf(GFP_KERNEL,
+ "%02x%s-%06x-dsp1",
cs35l56->base.rev,
- cs35l56->base.secured ? "-s" : "");
- if (!cs35l56->dsp.fwf_name)
- goto err;
+ cs35l56->base.secured ? "-s" : "",
+ firmware_version);
}

+ if (!cs35l56->dsp.fwf_name)
+ goto err;
+
dev_dbg(cs35l56->base.dev, "DSP fwf name: '%s' system name: '%s'\n",
cs35l56->dsp.fwf_name, cs35l56->dsp.system_name);

/*
- * 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.
+ * The firmware cannot be patched if it is already running from
+ * patch RAM. In this case the firmware files are versioned to
+ * match the running firmware version and will only contain
+ * tunings. We do not need to shutdown the firmware to apply
+ * tunings so can use the lower cost reinit sequence instead.
*/
- if (cs35l56->base.secured)
- cs35l56_secure_patch(cs35l56);
+ if (!firmware_missing)
+ cs35l56_reinit_patch(cs35l56);
else
- cs35l56_patch(cs35l56);
+ cs35l56_patch(cs35l56, firmware_missing);


/*
--
2.39.2


2024-01-30 09:12:05

by Pierre-Louis Bossart

[permalink] [raw]
Subject: Re: [PATCH 08/18] ASoC: cs35l56: Fix default SDW TX mixer registers



On 1/29/24 17:27, Richard Fitzgerald wrote:
> Patch the SDW TX mixer registers to silicon defaults.
>
> CS35L56 is designed for SDCA and a generic SDCA driver would
> know nothing about these chip-specific registers. So the
> firmware sets up the SDW TX mixer registers to whatever audio
> is relevant on a specific system.
>
> This means that the driver cannot assume the initial values
> of these registers. But Linux has ALSA controls to configure
> routing, so the registers can be patched to silicon default and
> the ALSA controls used to select what audio to feed back to the
> host capture path.

humm, which has the precedence then?
a) the values set by firmware
b) the default values set by the driver?

Also if the firmware touches those registers shouldn't they be marked as
'volatile'?


> Backport note:
> This won't apply to kernels older than v6.6.
>
> Signed-off-by: Richard Fitzgerald <[email protected]>
> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56")
> ---
> sound/soc/codecs/cs35l56-shared.c | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c
> index 35789ffc63af..a812abf90836 100644
> --- a/sound/soc/codecs/cs35l56-shared.c
> +++ b/sound/soc/codecs/cs35l56-shared.c
> @@ -12,6 +12,15 @@
> #include "cs35l56.h"
>
> static const struct reg_sequence cs35l56_patch[] = {
> + /*
> + * Firmware can change these to non-defaults to satisfy SDCA.
> + * Ensure that they are at known defaults.
> + */
> + { CS35L56_SWIRE_DP3_CH1_INPUT, 0x00000018 },
> + { CS35L56_SWIRE_DP3_CH2_INPUT, 0x00000019 },
> + { CS35L56_SWIRE_DP3_CH3_INPUT, 0x00000029 },
> + { CS35L56_SWIRE_DP3_CH4_INPUT, 0x00000028 },
> +
> /* These are not reset by a soft-reset, so patch to defaults. */
> { CS35L56_MAIN_RENDER_USER_MUTE, 0x00000000 },
> { CS35L56_MAIN_RENDER_USER_VOLUME, 0x00000000 },

2024-01-30 11:06:21

by Richard Fitzgerald

[permalink] [raw]
Subject: Re: [PATCH 08/18] ASoC: cs35l56: Fix default SDW TX mixer registers

On 29/01/2024 17:15, Pierre-Louis Bossart wrote:
>
>
> On 1/29/24 17:27, Richard Fitzgerald wrote:
>> Patch the SDW TX mixer registers to silicon defaults.
>>
>> CS35L56 is designed for SDCA and a generic SDCA driver would
>> know nothing about these chip-specific registers. So the
>> firmware sets up the SDW TX mixer registers to whatever audio
>> is relevant on a specific system.
>>
>> This means that the driver cannot assume the initial values
>> of these registers. But Linux has ALSA controls to configure
>> routing, so the registers can be patched to silicon default and
>> the ALSA controls used to select what audio to feed back to the
>> host capture path.
>
> humm, which has the precedence then?
> a) the values set by firmware
> b) the default values set by the driver?
>
> Also if the firmware touches those registers shouldn't they be marked as
> 'volatile'?
>

The firmware was designed to work with Windows, so it looks a bit
strange if you are coming at it from ALSA. There's not really any
defined 'precedence'. The firmware will setup the feedback monitor paths
to something that satisfies SDCA and Windows expectations.

We don't care about that in Linux, the firmware on the Intel DSP
probably isn't running the same algorithms for Linux, and we have ALSA
controls to configure those paths. So we patch the mixers back to their
silicon defaults and take over complete control of them.

The firmware only writes them during its power-up sequence so they
will only change when we are rebooting the firmware or coming out of
low-power standby, which is under the control of the driver. When that
happens regmap will re-apply the patch and then sync up the registers
again. The firmware won't touch them after boot, so we can avoid having
to mark them volatile (which would mean implementing our own manual
caching of the settings).

>
>> Backport note:
>> This won't apply to kernels older than v6.6.
>>
>> Signed-off-by: Richard Fitzgerald <[email protected]>
>> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56")
>> ---
>> sound/soc/codecs/cs35l56-shared.c | 9 +++++++++
>> 1 file changed, 9 insertions(+)
>>
>> diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c
>> index 35789ffc63af..a812abf90836 100644
>> --- a/sound/soc/codecs/cs35l56-shared.c
>> +++ b/sound/soc/codecs/cs35l56-shared.c
>> @@ -12,6 +12,15 @@
>> #include "cs35l56.h"
>>
>> static const struct reg_sequence cs35l56_patch[] = {
>> + /*
>> + * Firmware can change these to non-defaults to satisfy SDCA.
>> + * Ensure that they are at known defaults.
>> + */
>> + { CS35L56_SWIRE_DP3_CH1_INPUT, 0x00000018 },
>> + { CS35L56_SWIRE_DP3_CH2_INPUT, 0x00000019 },
>> + { CS35L56_SWIRE_DP3_CH3_INPUT, 0x00000029 },
>> + { CS35L56_SWIRE_DP3_CH4_INPUT, 0x00000028 },
>> +
>> /* These are not reset by a soft-reset, so patch to defaults. */
>> { CS35L56_MAIN_RENDER_USER_MUTE, 0x00000000 },
>> { CS35L56_MAIN_RENDER_USER_VOLUME, 0x00000000 },


2024-01-30 13:18:45

by Pierre-Louis Bossart

[permalink] [raw]
Subject: Re: [PATCH 08/18] ASoC: cs35l56: Fix default SDW TX mixer registers


>>> CS35L56 is designed for SDCA and a generic SDCA driver would
>>> know nothing about these chip-specific registers. So the
>>> firmware sets up the SDW TX mixer registers to whatever audio
>>> is relevant on a specific system.
>>>
>>> This means that the driver cannot assume the initial values
>>> of these registers. But Linux has ALSA controls to configure
>>> routing, so the registers can be patched to silicon default and
>>> the ALSA controls used to select what audio to feed back to the
>>> host capture path.
>>
>> humm, which has the precedence then?
>> a) the values set by firmware
>> b) the default values set by the driver?
>>
>> Also if the firmware touches those registers shouldn't they be marked as
>> 'volatile'?
>>
>
> The firmware was designed to work with Windows, so it looks a bit
> strange if you are coming at it from ALSA. There's not really any
> defined 'precedence'. The firmware will setup the feedback monitor paths
> to something that satisfies SDCA and Windows expectations.
>
> We don't care about that in Linux, the firmware on the Intel DSP
> probably isn't running the same algorithms for Linux, and we have ALSA
> controls to configure those paths. So we patch the mixers back to their
> silicon defaults and take over complete control of them.
>
> The firmware only writes them during its power-up sequence so they
> will only change when we are rebooting the firmware or coming out of
> low-power standby, which is under the control of the driver. When that
> happens regmap will re-apply the patch and then sync up the registers
> again. The firmware won't touch them after boot, so we can avoid having
> to mark them volatile (which would mean implementing our own manual
> caching of the settings).

ok, thanks for the explanations.


2024-02-01 12:49:52

by Takashi Iwai

[permalink] [raw]
Subject: Re: [PATCH 00/18] ALSA: Various fixes for Cirrus Logic CS35L56 support

On Thu, 01 Feb 2024 13:47:38 +0100,
Mark Brown wrote:
>
> On Mon, Jan 29, 2024 at 04:27:19PM +0000, Richard Fitzgerald wrote:
> > This chain of patches fixes various things that were undocumented, unknown
> > or uncertain when the original driver code was written. And also a few
> > things that were just bugs.
> >
> > The HDA patches have dependencies on the ASoC patches, except for the final
> > patch that removes a bogus test stub function.
>
> Takashi, should I apply the ALSA bits of this via ASoC?

Judging from the amount, better to go through your tree.
Please go ahead.


thanks,

Takashi

2024-02-01 12:50:05

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 00/18] ALSA: Various fixes for Cirrus Logic CS35L56 support

On Mon, Jan 29, 2024 at 04:27:19PM +0000, Richard Fitzgerald wrote:
> This chain of patches fixes various things that were undocumented, unknown
> or uncertain when the original driver code was written. And also a few
> things that were just bugs.
>
> The HDA patches have dependencies on the ASoC patches, except for the final
> patch that removes a bogus test stub function.

Takashi, should I apply the ALSA bits of this via ASoC?


Attachments:
(No filename) (444.00 B)
signature.asc (499.00 B)
Download all attachments

2024-02-01 19:10:17

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 00/18] ALSA: Various fixes for Cirrus Logic CS35L56 support

On Mon, 29 Jan 2024 16:27:19 +0000, Richard Fitzgerald wrote:
> This chain of patches fixes various things that were undocumented, unknown
> or uncertain when the original driver code was written. And also a few
> things that were just bugs.
>
> The HDA patches have dependencies on the ASoC patches, except for the final
> patch that removes a bogus test stub function.
>
> [...]

Applied to

https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next

Thanks!

[01/18] ASoC: wm_adsp: Fix firmware file search order
commit: 3657e4cb5a8abd9edf6c944e022fe9ef06989960
[02/18] ASoC: wm_adsp: Don't overwrite fwf_name with the default
commit: daf3f0f99cde93a066240462b7a87cdfeedc04c0
[03/18] ASoC: cs35l56: cs35l56_component_remove() must clear cs35l56->component
commit: ae861c466ee57e15a29d97629e1c564e3f714a4f
[04/18] ASoC: cs35l56: cs35l56_component_remove() must clean up wm_adsp
commit: cd38ccbecdace1469b4e0cfb3ddeec72a3fad226
[05/18] ASoC: cs35l56: Don't add the same register patch multiple times
commit: 07687cd0539f8185b6ba0c0afba8473517116d6a
[06/18] ASoC: cs35l56: Remove buggy checks from cs35l56_is_fw_reload_needed()
commit: 3739cc0733ba7eeafc08d4d4208d1f3c2451eabd
[07/18] ASoC: cs35l56: Fix to ensure ASP1 registers match cache
commit: 72a77d7631c6e392677c0134343cf5edcd3a4572
[08/18] ASoC: cs35l56: Fix default SDW TX mixer registers
commit: 782e6c538be43a17e34f552ab49e8c713cac7883
[09/18] ALSA: hda: cs35l56: Initialize all ASP1 registers
commit: 856ce8982169acb31a25c5f2ecd2570ab8a6af46
[10/18] ASoC: cs35l56: Fix for initializing ASP1 mixer registers
commit: 07f7d6e7a124d3e4de36771e2a4926d0e31c2258
[11/18] ASoC: cs35l56: Fix misuse of wm_adsp 'part' string for silicon revision
commit: f6c967941c5d6fa526fdd64733a8d86bf2bfab31
[12/18] ASoC: cs35l56: Firmware file must match the version of preloaded firmware
commit: f4ef5149953f2fc04907ca5b34db3df667dcddef
[13/18] ASoC: cs35l56: Load tunings for the correct speaker models
commit: 245eeff18d7a37693815250ae15979ce98c3d190
[14/18] ASoC: cs35l56: Allow more time for firmware to boot
commit: 9e92b77ceb6f362eb2e7995dad6c7f9863053d97
[15/18] ALSA: hda: cs35l56: Fix order of searching for firmware files
commit: 77c60722ded7d6739805e045e9648cda82dde5ed
[16/18] ALSA: hda: cs35l56: Fix filename string field layout
commit: e82bc517c6ef5d5c04b845420406e694c31bdb8a
[17/18] ALSA: hda: cs35l56: Firmware file must match the version of preloaded firmware
commit: 6f8ad0480d82245961dae4d3280908611633872d
[18/18] ALSA: hda: cs35l56: Remove unused test stub function
commit: 28876c1ae8b8cd1dacef50bd6c0555824774f0d2

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark