2022-06-22 07:53:09

by Vitaly Rodionov

[permalink] [raw]
Subject: [PATCH v7 00/14] ALSA: hda: cirrus: Add initial DSP support and firmware loading

The CS35L41 Amplifier contains a DSP, capable of running firmware.
The firmware can run algorithms such as Speaker Protection, to ensure
that playback at high gains do not harm the speakers.
Adding support for CS35L41 firmware into the CS35L41 HDA driver also
allows us to support several extra features, such as hiberation
and interrupts.

The chain adds support in stages:
- General fixes to improve generalization and code re-use inside
the CS35L41 HDA driver.
- Add support for interrupts into the driver, which is required
for complete support of the firmware.
- Refactor ASoC CS35L41 code which deals with firmware to allow
for code re-use inside the CS35L41 HDA driver.
- Add support for loading firmware and tuning files from file system,
and creating alsa controls to control it.
- Support firmware load paths for different hardware systems.
- Support suspend/resume in the driver when using firmware. The firmware
supports hibernation, which allows the CS35L41 to drop into a low
power mode during suspend.
- Support the ability to unload firmware, swap and reload the firmware.
This is to allow different firmware to run during calibration.

The intended use-case is to load the firmware once on boot, and the driver
autmatically tries to load the firmware after it binds to the HDA driver.
This behaviour can be switched off using a kconfig, if desired.

changes since v6:
- Fix warning by kernel test robot <[email protected]>

changes since v5:
- Fix warning by kernel test robot <[email protected]>

changes since v4:
- Fully remove tlv remnants from control add apis
- Remove unnecessary debug
- Rename variable to be more generic
- Remove redundent length check from read/write control apis


- Use SNDRV_CTL_ELEM_IFACE_CARD for firmware load controls
- Make kcontrol add/remove synchronous
- Load firmware asynchronous when loading via control
- Used cached controls when reloading firmware; only delete
controls when removing the driver itself


- Improve kcontrol remove
- Fix control write + notify
- Cleanup of unnecessary code
- Fix race condition when loading firmware before playback
- Ensure errors are properly propogated
- Fix include for Module parameters

Stefan Binding (13):
ALSA: hda: hda_cs_dsp_ctl: Add Library to support CS_DSP ALSA controls
ALSA: hda: hda_cs_dsp_ctl: Add apis to write the controls directly
ALSA: hda: cs35l41: Save codec object inside component struct
ALSA: hda: cs35l41: Save Subsystem ID inside CS35L41 Driver
ALSA: hda: cs35l41: Support reading subsystem id from ACPI
ALSA: hda: cs35l41: Support multiple load paths for firmware
ALSA: hda: cs35l41: Support Speaker ID for laptops
ALSA: hda: cs35l41: Support Hibernation during Suspend
ALSA: hda: cs35l41: Read Speaker Calibration data from UEFI variables
ALSA: hda: hda_cs_dsp_ctl: Add fw id strings
ALSA: hda: cs35l41: Add defaulted values into dsp bypass config
sequence
ALSA: hda: cs35l41: Support Firmware switching and reloading
ALSA: hda: cs35l41: Add module parameter to control firmware load

Vitaly Rodionov (1):
ALSA: hda: cs35l41: Add initial DSP support and firmware loading

MAINTAINERS | 1 +
include/sound/cs35l41.h | 4 +
sound/pci/hda/Kconfig | 8 +
sound/pci/hda/Makefile | 2 +
sound/pci/hda/cs35l41_hda.c | 902 +++++++++++++++++++++++++++++++-
sound/pci/hda/cs35l41_hda.h | 39 ++
sound/pci/hda/cs35l41_hda_i2c.c | 1 +
sound/pci/hda/cs35l41_hda_spi.c | 1 +
sound/pci/hda/hda_component.h | 4 +
sound/pci/hda/hda_cs_dsp_ctl.c | 254 +++++++++
sound/pci/hda/hda_cs_dsp_ctl.h | 39 ++
sound/pci/hda/patch_realtek.c | 27 +-
12 files changed, 1277 insertions(+), 5 deletions(-)
create mode 100644 sound/pci/hda/hda_cs_dsp_ctl.c
create mode 100644 sound/pci/hda/hda_cs_dsp_ctl.h

--
2.34.1


2022-06-22 08:04:19

by Vitaly Rodionov

[permalink] [raw]
Subject: [PATCH v7 07/14] ALSA: hda: cs35l41: Support multiple load paths for firmware

From: Stefan Binding <[email protected]>

To be able to support different firmwares and tuning
for different models, the driver needs to be able to
load a different firmware and coefficient file based
on its Subsystem ID.

The driver attempts to load the firmware in the
following order:

/lib/firmware/cirrus/cs35l41-dsp1-<fw-type>-<ssid>-dev<#>.wmfw
/lib/firmware/cirrus/cs35l41-dsp1-<fw-type>-<ssid>.wmfw
/lib/firmware/cirrus/cs35l41-dsp1-<fw-type>.wmfw

Signed-off-by: Stefan Binding <[email protected]>
Signed-off-by: Vitaly Rodionov <[email protected]>
---
sound/pci/hda/cs35l41_hda.c | 53 ++++++++++++++++++++++++++++++++-----
1 file changed, 46 insertions(+), 7 deletions(-)

diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
index 5f89a0462eb6..6d90bb8f74ea 100644
--- a/sound/pci/hda/cs35l41_hda.c
+++ b/sound/pci/hda/cs35l41_hda.c
@@ -85,14 +85,23 @@ static const struct cs_dsp_client_ops client_ops = {

static int cs35l41_request_firmware_file(struct cs35l41_hda *cs35l41,
const struct firmware **firmware, char **filename,
- const char *dir, const char *filetype)
+ const char *dir, const char *ssid, const char *amp_name,
+ const char *filetype)
{
const char * const dsp_name = cs35l41->cs_dsp.name;
char *s, c;
int ret = 0;

- *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s.%s", dir, CS35L41_PART, dsp_name, "spk-prot",
- filetype);
+ if (ssid && amp_name)
+ *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s-%s.%s", dir, CS35L41_PART,
+ dsp_name, "spk-prot", ssid, amp_name,
+ filetype);
+ else if (ssid)
+ *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s.%s", dir, CS35L41_PART,
+ dsp_name, "spk-prot", ssid, filetype);
+ else
+ *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s.%s", dir, CS35L41_PART,
+ dsp_name, "spk-prot", filetype);

if (*filename == NULL)
return -ENOMEM;
@@ -129,12 +138,43 @@ static int cs35l41_request_firmware_files(struct cs35l41_hda *cs35l41,
{
int ret;

- /* cirrus/part-dspN-fwtype.wmfw */
+ /* try cirrus/part-dspN-fwtype-sub<-ampname>.wmfw */
+ ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename,
+ CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id,
+ cs35l41->amp_name, "wmfw");
+ if (!ret) {
+ /* try cirrus/part-dspN-fwtype-sub<-ampname>.bin */
+ cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename,
+ CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id,
+ cs35l41->amp_name, "bin");
+ return 0;
+ }
+
+ /* try cirrus/part-dspN-fwtype-sub.wmfw */
ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename,
- CS35L41_FIRMWARE_ROOT, "wmfw");
+ CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id,
+ NULL, "wmfw");
if (!ret) {
+ /* try cirrus/part-dspN-fwtype-sub<-ampname>.bin */
+ ret = cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename,
+ CS35L41_FIRMWARE_ROOT,
+ cs35l41->acpi_subsystem_id,
+ cs35l41->amp_name, "bin");
+ if (ret)
+ /* try cirrus/part-dspN-fwtype-sub.bin */
+ cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename,
+ CS35L41_FIRMWARE_ROOT,
+ cs35l41->acpi_subsystem_id, NULL, "bin");
+ return 0;
+ }
+
+ /* fallback try cirrus/part-dspN-fwtype.wmfw */
+ ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename,
+ CS35L41_FIRMWARE_ROOT, NULL, NULL, "wmfw");
+ if (!ret) {
+ /* fallback try cirrus/part-dspN-fwtype.bin */
cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename,
- CS35L41_FIRMWARE_ROOT, "bin");
+ CS35L41_FIRMWARE_ROOT, NULL, NULL, "bin");
return 0;
}

@@ -143,7 +183,6 @@ static int cs35l41_request_firmware_files(struct cs35l41_hda *cs35l41,
return ret;
}

-
static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41)
{
const struct firmware *coeff_firmware = NULL;
--
2.34.1

2022-06-22 08:04:19

by Vitaly Rodionov

[permalink] [raw]
Subject: [PATCH v7 14/14] ALSA: hda: cs35l41: Add module parameter to control firmware load

From: Stefan Binding <[email protected]>

By default, the driver will automatically load DSP firmware
for the amps, if available. Adding this option allows the
autoload to be optional, which allows for different configurations.

Signed-off-by: Stefan Binding <[email protected]>
Signed-off-by: Vitaly Rodionov <[email protected]>
---
sound/pci/hda/cs35l41_hda.c | 21 ++++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)

diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
index 3aa36c5ff972..4151ac5ab399 100644
--- a/sound/pci/hda/cs35l41_hda.c
+++ b/sound/pci/hda/cs35l41_hda.c
@@ -8,6 +8,7 @@

#include <linux/acpi.h>
#include <linux/module.h>
+#include <linux/moduleparam.h>
#include <sound/hda_codec.h>
#include <sound/soc.h>
#include <linux/pm_runtime.h>
@@ -32,6 +33,11 @@
#define CAL_DSP_CTL_TYPE 5
#define CAL_DSP_CTL_ALG 205

+static bool firmware_autostart = 1;
+module_param(firmware_autostart, bool, 0444);
+MODULE_PARM_DESC(firmware_autostart, "Allow automatic firmware download on boot"
+ "(0=Disable, 1=Enable) (default=1); ");
+
static const struct reg_sequence cs35l41_hda_config[] = {
{ CS35L41_PLL_CLK_CTRL, 0x00000430 }, // 3072000Hz, BCLK Input, PLL_REFCLK_EN = 1
{ CS35L41_DSP_CLK_CTRL, 0x00000003 }, // DSP CLK EN
@@ -862,11 +868,16 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas

cs35l41->firmware_type = HDA_CS_DSP_FW_SPK_PROT;

- cs35l41->request_fw_load = true;
- mutex_lock(&cs35l41->fw_mutex);
- if (cs35l41_smart_amp(cs35l41) < 0)
- dev_warn(cs35l41->dev, "Cannot Run Firmware, reverting to dsp bypass...\n");
- mutex_unlock(&cs35l41->fw_mutex);
+ if (firmware_autostart) {
+ dev_dbg(cs35l41->dev, "Firmware Autostart.\n");
+ cs35l41->request_fw_load = true;
+ mutex_lock(&cs35l41->fw_mutex);
+ if (cs35l41_smart_amp(cs35l41) < 0)
+ dev_warn(cs35l41->dev, "Cannot Run Firmware, reverting to dsp bypass...\n");
+ mutex_unlock(&cs35l41->fw_mutex);
+ } else {
+ dev_dbg(cs35l41->dev, "Firmware Autostart is disabled.\n");
+ }

ret = cs35l41_create_controls(cs35l41);

--
2.34.1

2022-06-22 08:05:46

by Vitaly Rodionov

[permalink] [raw]
Subject: [PATCH v7 05/14] ALSA: hda: cs35l41: Save Subsystem ID inside CS35L41 Driver

From: Stefan Binding <[email protected]>

The Subsystem ID is read from the HDA driver, and will
be used by the CS35L41 driver to be able to uniquely
identify the laptop, which is required to be able to
define firmware to be used by specific models.

Signed-off-by: Stefan Binding <[email protected]>
Signed-off-by: Vitaly Rodionov <[email protected]>
---
sound/pci/hda/cs35l41_hda.c | 3 +++
sound/pci/hda/cs35l41_hda.h | 1 +
sound/pci/hda/hda_component.h | 1 +
sound/pci/hda/patch_realtek.c | 1 +
4 files changed, 6 insertions(+)

diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
index 92c6d8b7052e..7f0132694774 100644
--- a/sound/pci/hda/cs35l41_hda.c
+++ b/sound/pci/hda/cs35l41_hda.c
@@ -356,6 +356,9 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas
return -EBUSY;

comps->dev = dev;
+ if (!cs35l41->acpi_subsystem_id)
+ cs35l41->acpi_subsystem_id = devm_kasprintf(dev, GFP_KERNEL,
+ "%.8x", comps->subsystem_id);
cs35l41->codec = comps->codec;
strscpy(comps->name, dev_name(dev), sizeof(comps->name));
comps->playback_hook = cs35l41_hda_playback_hook;
diff --git a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h
index 5814af050944..b57f59a1ba49 100644
--- a/sound/pci/hda/cs35l41_hda.h
+++ b/sound/pci/hda/cs35l41_hda.h
@@ -42,6 +42,7 @@ struct cs35l41_hda {
int channel_index;
unsigned volatile long irq_errors;
const char *amp_name;
+ const char *acpi_subsystem_id;
struct mutex fw_mutex;
struct regmap_irq_chip_data *irq_data;
bool firmware_running;
diff --git a/sound/pci/hda/hda_component.h b/sound/pci/hda/hda_component.h
index 534e845b9cd1..fa6df52e7855 100644
--- a/sound/pci/hda/hda_component.h
+++ b/sound/pci/hda/hda_component.h
@@ -14,6 +14,7 @@
struct hda_component {
struct device *dev;
char name[HDA_MAX_NAME_SIZE];
+ int subsystem_id;
struct hda_codec *codec;
void (*playback_hook)(struct device *dev, int action);
};
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index 923c0d498d54..6a944396582b 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -6655,6 +6655,7 @@ static void cs35l41_generic_fixup(struct hda_codec *cdc, int action, const char
if (!name)
return;
spec->comps[i].codec = cdc;
+ spec->comps[i].subsystem_id = cdc->core.subsystem_id;
component_match_add(dev, &spec->match, component_compare_dev_name, name);
}
ret = component_master_add_with_match(dev, &comp_master_ops, spec->match);
--
2.34.1

2022-06-22 08:06:59

by Vitaly Rodionov

[permalink] [raw]
Subject: [PATCH v7 09/14] ALSA: hda: cs35l41: Support Hibernation during Suspend

From: Stefan Binding <[email protected]>

CS35L41 supports hibernation during suspend when using
DSP firmware.
When the driver suspends it will hibernate the part, if
firmware is running, and resume will wake from hibernation.
CS35L41 driver will suspend/resume when requested by
hda driver.
Note that suspend/resume and hibernation is only supported
when firmware is running.

Signed-off-by: Stefan Binding <[email protected]>
Signed-off-by: Vitaly Rodionov <[email protected]>
---
sound/pci/hda/cs35l41_hda.c | 109 +++++++++++++++++++++++++++++++-
sound/pci/hda/cs35l41_hda.h | 2 +
sound/pci/hda/cs35l41_hda_i2c.c | 1 +
sound/pci/hda/cs35l41_hda_spi.c | 1 +
sound/pci/hda/hda_component.h | 2 +
sound/pci/hda/patch_realtek.c | 25 +++++++-
6 files changed, 136 insertions(+), 4 deletions(-)

diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
index 6ac68b00cafe..b502806fbfa1 100644
--- a/sound/pci/hda/cs35l41_hda.c
+++ b/sound/pci/hda/cs35l41_hda.c
@@ -10,6 +10,7 @@
#include <linux/module.h>
#include <sound/hda_codec.h>
#include <sound/soc.h>
+#include <linux/pm_runtime.h>
#include "hda_local.h"
#include "hda_auto_parser.h"
#include "hda_jack.h"
@@ -435,6 +436,75 @@ static int cs35l41_hda_channel_map(struct device *dev, unsigned int tx_num, unsi
rx_slot);
}

+static int cs35l41_runtime_suspend(struct device *dev)
+{
+ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
+
+ dev_dbg(cs35l41->dev, "Suspend\n");
+
+ if (!cs35l41->firmware_running)
+ return 0;
+
+ if (cs35l41_enter_hibernate(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type) < 0)
+ return 0;
+
+ regcache_cache_only(cs35l41->regmap, true);
+ regcache_mark_dirty(cs35l41->regmap);
+
+ return 0;
+}
+
+static int cs35l41_runtime_resume(struct device *dev)
+{
+ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(cs35l41->dev, "Resume.\n");
+
+ if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) {
+ dev_dbg(cs35l41->dev, "System does not support Resume\n");
+ return 0;
+ }
+
+ if (!cs35l41->firmware_running)
+ return 0;
+
+ regcache_cache_only(cs35l41->regmap, false);
+
+ ret = cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap);
+ if (ret) {
+ regcache_cache_only(cs35l41->regmap, true);
+ return ret;
+ }
+
+ /* 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);
+ return ret;
+ }
+
+ if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
+ cs35l41_init_boost(cs35l41->dev, cs35l41->regmap, &cs35l41->hw_cfg);
+
+ return 0;
+}
+
+static int cs35l41_hda_suspend_hook(struct device *dev)
+{
+ dev_dbg(dev, "Request Suspend\n");
+ pm_runtime_mark_last_busy(dev);
+ return pm_runtime_put_autosuspend(dev);
+}
+
+static int cs35l41_hda_resume_hook(struct device *dev)
+{
+ dev_dbg(dev, "Request Resume\n");
+ return pm_runtime_get_sync(dev);
+}
+
static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41)
{
int halo_sts;
@@ -492,19 +562,27 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas
if (comps->dev)
return -EBUSY;

+ pm_runtime_get_sync(dev);
+
comps->dev = dev;
if (!cs35l41->acpi_subsystem_id)
cs35l41->acpi_subsystem_id = devm_kasprintf(dev, GFP_KERNEL,
"%.8x", comps->subsystem_id);
cs35l41->codec = comps->codec;
strscpy(comps->name, dev_name(dev), sizeof(comps->name));
- comps->playback_hook = cs35l41_hda_playback_hook;

mutex_lock(&cs35l41->fw_mutex);
if (cs35l41_smart_amp(cs35l41) < 0)
dev_warn(cs35l41->dev, "Cannot Run Firmware, reverting to dsp bypass...\n");
mutex_unlock(&cs35l41->fw_mutex);

+ comps->playback_hook = cs35l41_hda_playback_hook;
+ comps->suspend_hook = cs35l41_hda_suspend_hook;
+ comps->resume_hook = cs35l41_hda_resume_hook;
+
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
return 0;
}

@@ -600,7 +678,7 @@ static const struct regmap_irq cs35l41_reg_irqs[] = {
CS35L41_REG_IRQ(IRQ1_STATUS1, AMP_SHORT_ERR),
};

-static const struct regmap_irq_chip cs35l41_regmap_irq_chip = {
+static struct regmap_irq_chip cs35l41_regmap_irq_chip = {
.name = "cs35l41 IRQ1 Controller",
.status_base = CS35L41_IRQ1_STATUS1,
.mask_base = CS35L41_IRQ1_MASK1,
@@ -608,6 +686,7 @@ static const struct regmap_irq_chip cs35l41_regmap_irq_chip = {
.num_regs = 4,
.irqs = cs35l41_reg_irqs,
.num_irqs = ARRAY_SIZE(cs35l41_reg_irqs),
+ .runtime_pm = true,
};

static int cs35l41_hda_apply_properties(struct cs35l41_hda *cs35l41)
@@ -1017,13 +1096,23 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i

mutex_init(&cs35l41->fw_mutex);

+ pm_runtime_set_autosuspend_delay(cs35l41->dev, 3000);
+ pm_runtime_use_autosuspend(cs35l41->dev);
+ pm_runtime_mark_last_busy(cs35l41->dev);
+ pm_runtime_set_active(cs35l41->dev);
+ pm_runtime_get_noresume(cs35l41->dev);
+ pm_runtime_enable(cs35l41->dev);
+
ret = cs35l41_hda_apply_properties(cs35l41);
if (ret)
- goto err;
+ goto err_pm;
+
+ pm_runtime_put_autosuspend(cs35l41->dev);

ret = component_add(cs35l41->dev, &cs35l41_hda_comp_ops);
if (ret) {
dev_err(cs35l41->dev, "Register component failed: %d\n", ret);
+ pm_runtime_disable(cs35l41->dev);
goto err;
}

@@ -1031,6 +1120,10 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i

return 0;

+err_pm:
+ pm_runtime_disable(cs35l41->dev);
+ pm_runtime_put_noidle(cs35l41->dev);
+
err:
if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type))
gpiod_set_value_cansleep(cs35l41->reset_gpio, 0);
@@ -1044,17 +1137,27 @@ void cs35l41_hda_remove(struct device *dev)
{
struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);

+ pm_runtime_get_sync(cs35l41->dev);
+ pm_runtime_disable(cs35l41->dev);
+
if (cs35l41->halo_initialized)
cs35l41_remove_dsp(cs35l41);

component_del(cs35l41->dev, &cs35l41_hda_comp_ops);

+ pm_runtime_put_noidle(cs35l41->dev);
+
if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type))
gpiod_set_value_cansleep(cs35l41->reset_gpio, 0);
gpiod_put(cs35l41->reset_gpio);
}
EXPORT_SYMBOL_NS_GPL(cs35l41_hda_remove, SND_HDA_SCODEC_CS35L41);

+const struct dev_pm_ops cs35l41_hda_pm_ops = {
+ SET_RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume, NULL)
+};
+EXPORT_SYMBOL_NS_GPL(cs35l41_hda_pm_ops, SND_HDA_SCODEC_CS35L41);
+
MODULE_DESCRIPTION("CS35L41 HDA Driver");
MODULE_IMPORT_NS(SND_HDA_CS_DSP_CONTROLS);
MODULE_AUTHOR("Lucas Tanure, Cirrus Logic Inc, <[email protected]>");
diff --git a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h
index a9dbc1c19248..439c4b705328 100644
--- a/sound/pci/hda/cs35l41_hda.h
+++ b/sound/pci/hda/cs35l41_hda.h
@@ -57,6 +57,8 @@ enum halo_state {
HALO_STATE_CODE_RUN
};

+extern const struct dev_pm_ops cs35l41_hda_pm_ops;
+
int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int irq,
struct regmap *regmap);
void cs35l41_hda_remove(struct device *dev);
diff --git a/sound/pci/hda/cs35l41_hda_i2c.c b/sound/pci/hda/cs35l41_hda_i2c.c
index e810b278fb91..a669090a18e8 100644
--- a/sound/pci/hda/cs35l41_hda_i2c.c
+++ b/sound/pci/hda/cs35l41_hda_i2c.c
@@ -55,6 +55,7 @@ static struct i2c_driver cs35l41_i2c_driver = {
.driver = {
.name = "cs35l41-hda",
.acpi_match_table = ACPI_PTR(cs35l41_acpi_hda_match),
+ .pm = &cs35l41_hda_pm_ops,
},
.id_table = cs35l41_hda_i2c_id,
.probe = cs35l41_hda_i2c_probe,
diff --git a/sound/pci/hda/cs35l41_hda_spi.c b/sound/pci/hda/cs35l41_hda_spi.c
index 22e088f28438..d7f15e2abe66 100644
--- a/sound/pci/hda/cs35l41_hda_spi.c
+++ b/sound/pci/hda/cs35l41_hda_spi.c
@@ -50,6 +50,7 @@ static struct spi_driver cs35l41_spi_driver = {
.driver = {
.name = "cs35l41-hda",
.acpi_match_table = ACPI_PTR(cs35l41_acpi_hda_match),
+ .pm = &cs35l41_hda_pm_ops,
},
.id_table = cs35l41_hda_spi_id,
.probe = cs35l41_hda_spi_probe,
diff --git a/sound/pci/hda/hda_component.h b/sound/pci/hda/hda_component.h
index fa6df52e7855..72ec0d865a28 100644
--- a/sound/pci/hda/hda_component.h
+++ b/sound/pci/hda/hda_component.h
@@ -17,4 +17,6 @@ struct hda_component {
int subsystem_id;
struct hda_codec *codec;
void (*playback_hook)(struct device *dev, int action);
+ int (*suspend_hook)(struct device *dev);
+ int (*resume_hook)(struct device *dev);
};
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index 6a944396582b..cf06b12a24c1 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -4021,15 +4021,22 @@ static void alc5505_dsp_init(struct hda_codec *codec)
static int alc269_suspend(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
+ int i;

if (spec->has_alc5505_dsp)
alc5505_dsp_suspend(codec);
+
+ for (i = 0; i < HDA_MAX_COMPONENTS; i++)
+ if (spec->comps[i].suspend_hook)
+ spec->comps[i].suspend_hook(spec->comps[i].dev);
+
return alc_suspend(codec);
}

static int alc269_resume(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
+ int i;

if (spec->codec_variant == ALC269_TYPE_ALC269VB)
alc269vb_toggle_power_output(codec, 0);
@@ -4060,6 +4067,10 @@ static int alc269_resume(struct hda_codec *codec)
if (spec->has_alc5505_dsp)
alc5505_dsp_resume(codec);

+ for (i = 0; i < HDA_MAX_COMPONENTS; i++)
+ if (spec->comps[i].resume_hook)
+ spec->comps[i].resume_hook(spec->comps[i].dev);
+
return 0;
}
#endif /* CONFIG_PM */
@@ -6610,8 +6621,20 @@ static int comp_bind(struct device *dev)
{
struct hda_codec *cdc = dev_to_hda_codec(dev);
struct alc_spec *spec = cdc->spec;
+ int ret, i;
+
+ ret = component_bind_all(dev, spec->comps);
+ if (ret)
+ return ret;

- return component_bind_all(dev, spec->comps);
+ if (snd_hdac_is_power_on(&cdc->core)) {
+ codec_dbg(cdc, "Resuming after bind.\n");
+ for (i = 0; i < HDA_MAX_COMPONENTS; i++)
+ if (spec->comps[i].resume_hook)
+ spec->comps[i].resume_hook(spec->comps[i].dev);
+ }
+
+ return 0;
}

static void comp_unbind(struct device *dev)
--
2.34.1

2022-06-22 08:26:18

by Vitaly Rodionov

[permalink] [raw]
Subject: [PATCH v7 13/14] ALSA: hda: cs35l41: Support Firmware switching and reloading

From: Stefan Binding <[email protected]>

This is required to support CS35L41 calibration.

By default, speaker protection firmware will be loaded, if
available. However, different firmware is required to run
the calibration sequence, so it is necessary to add support
to be able to unload, switch and reload firmware.

This patch adds 2 ALSA Controls for each amp:
"DSP1 Firmware Load"
"DSP1 Firmware Type"

"DSP1 Firmware Load" can be used to unload and
load the firmware.
"DSP1 Firmware Type" can be used to switch the
target firmware to be loaded by "DSP1 Firmware Load"

Since loading firmware can add new ALSA controls, it is
necessary to ensure the firmware loading is run asynchronously
from the ALSA control itself to prevent deadlocks.

Note: When switching between firmwares, an ALSA control is
only added if it has not previously existed. If it had existed
previously, it will be re-enabled instead.

Signed-off-by: Stefan Binding <[email protected]>
Signed-off-by: Vitaly Rodionov <[email protected]>
---
sound/pci/hda/cs35l41_hda.c | 201 ++++++++++++++++++++++++++++++++++--
sound/pci/hda/cs35l41_hda.h | 6 ++
2 files changed, 197 insertions(+), 10 deletions(-)

diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
index e51cdf42d3dc..3aa36c5ff972 100644
--- a/sound/pci/hda/cs35l41_hda.c
+++ b/sound/pci/hda/cs35l41_hda.c
@@ -21,7 +21,6 @@

#define CS35L41_FIRMWARE_ROOT "cirrus/"
#define CS35L41_PART "cs35l41"
-#define FW_NAME "CSPL"

#define HALO_STATE_DSP_CTL_NAME "HALO_STATE"
#define HALO_STATE_DSP_CTL_TYPE 5
@@ -92,7 +91,7 @@ static int cs35l41_control_add(struct cs_dsp_coeff_ctl *cs_ctl)
struct hda_cs_dsp_ctl_info info;

info.device_name = cs35l41->amp_name;
- info.fw_type = HDA_CS_DSP_FW_SPK_PROT;
+ info.fw_type = cs35l41->firmware_type;
info.card = cs35l41->codec->card;

return hda_cs_dsp_control_add(cs_ctl, &info);
@@ -114,20 +113,24 @@ static int cs35l41_request_firmware_file(struct cs35l41_hda *cs35l41,

if (spkid > -1 && ssid && amp_name)
*filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s-spkid%d-%s.%s", dir, CS35L41_PART,
- dsp_name, "spk-prot", ssid, spkid, amp_name, filetype);
+ dsp_name, hda_cs_dsp_fw_ids[cs35l41->firmware_type],
+ ssid, spkid, amp_name, filetype);
else if (spkid > -1 && ssid)
*filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s-spkid%d.%s", dir, CS35L41_PART,
- dsp_name, "spk-prot", ssid, spkid, filetype);
+ dsp_name, hda_cs_dsp_fw_ids[cs35l41->firmware_type],
+ ssid, spkid, filetype);
else if (ssid && amp_name)
*filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s-%s.%s", dir, CS35L41_PART,
- dsp_name, "spk-prot", ssid, amp_name,
- filetype);
+ dsp_name, hda_cs_dsp_fw_ids[cs35l41->firmware_type],
+ ssid, amp_name, filetype);
else if (ssid)
*filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s.%s", dir, CS35L41_PART,
- dsp_name, "spk-prot", ssid, filetype);
+ dsp_name, hda_cs_dsp_fw_ids[cs35l41->firmware_type],
+ ssid, filetype);
else
*filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s.%s", dir, CS35L41_PART,
- dsp_name, "spk-prot", filetype);
+ dsp_name, hda_cs_dsp_fw_ids[cs35l41->firmware_type],
+ filetype);

if (*filename == NULL)
return -ENOMEM;
@@ -422,7 +425,7 @@ static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41)
dev_warn(cs35l41->dev, "No Coefficient File available.\n");

ret = cs_dsp_power_up(dsp, wmfw_firmware, wmfw_filename, coeff_firmware, coeff_filename,
- FW_NAME);
+ hda_cs_dsp_fw_ids[cs35l41->firmware_type]);
if (ret)
goto err_release;

@@ -451,6 +454,7 @@ static void cs35l41_remove_dsp(struct cs35l41_hda *cs35l41)
{
struct cs_dsp *dsp = &cs35l41->cs_dsp;

+ cancel_work_sync(&cs35l41->fw_load_work);
cs35l41_shutdown_dsp(cs35l41);
cs_dsp_remove(dsp);
cs35l41->halo_initialized = false;
@@ -481,6 +485,7 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action)

switch (action) {
case HDA_GEN_PCM_ACT_OPEN:
+ cs35l41->playback_started = true;
if (cs35l41->firmware_running) {
regmap_multi_reg_write(reg, cs35l41_hda_config_dsp,
ARRAY_SIZE(cs35l41_hda_config_dsp));
@@ -518,6 +523,7 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action)
0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT);
}
cs35l41_irq_release(cs35l41);
+ cs35l41->playback_started = false;
break;
default:
dev_warn(cs35l41->dev, "Playback action not supported: %d\n", action);
@@ -664,10 +670,179 @@ static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41)
return ret;
}

+static void cs35l41_load_firmware(struct cs35l41_hda *cs35l41, bool load)
+{
+ pm_runtime_get_sync(cs35l41->dev);
+
+ if (cs35l41->firmware_running && !load) {
+ dev_dbg(cs35l41->dev, "Unloading Firmware\n");
+ cs35l41_shutdown_dsp(cs35l41);
+ } else if (!cs35l41->firmware_running && load) {
+ dev_dbg(cs35l41->dev, "Loading Firmware\n");
+ cs35l41_smart_amp(cs35l41);
+ } else {
+ dev_dbg(cs35l41->dev, "Unable to Load firmware.\n");
+ }
+
+ pm_runtime_mark_last_busy(cs35l41->dev);
+ pm_runtime_put_autosuspend(cs35l41->dev);
+}
+
+static int cs35l41_fw_load_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = cs35l41->request_fw_load;
+ return 0;
+}
+
+static void cs35l41_fw_load_work(struct work_struct *work)
+{
+ struct cs35l41_hda *cs35l41 = container_of(work, struct cs35l41_hda, fw_load_work);
+
+ mutex_lock(&cs35l41->fw_mutex);
+
+ /* Recheck if playback is ongoing, mutex will block playback during firmware loading */
+ if (cs35l41->playback_started)
+ dev_err(cs35l41->dev, "Cannot Load/Unload firmware during Playback\n");
+ else
+ cs35l41_load_firmware(cs35l41, cs35l41->request_fw_load);
+
+ cs35l41->fw_request_ongoing = false;
+ mutex_unlock(&cs35l41->fw_mutex);
+}
+
+static int cs35l41_fw_load_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol);
+ unsigned int ret = 0;
+
+ mutex_lock(&cs35l41->fw_mutex);
+
+ if (cs35l41->request_fw_load == ucontrol->value.integer.value[0])
+ goto err;
+
+ if (cs35l41->fw_request_ongoing) {
+ dev_dbg(cs35l41->dev, "Existing request not complete\n");
+ ret = -EBUSY;
+ goto err;
+ }
+
+ /* Check if playback is ongoing when initial request is made */
+ if (cs35l41->playback_started) {
+ dev_err(cs35l41->dev, "Cannot Load/Unload firmware during Playback\n");
+ ret = -EBUSY;
+ goto err;
+ }
+
+ cs35l41->fw_request_ongoing = true;
+ cs35l41->request_fw_load = ucontrol->value.integer.value[0];
+ schedule_work(&cs35l41->fw_load_work);
+
+err:
+ mutex_unlock(&cs35l41->fw_mutex);
+
+ return ret;
+}
+
+static int cs35l41_fw_type_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.enumerated.item[0] = cs35l41->firmware_type;
+
+ return 0;
+}
+
+static int cs35l41_fw_type_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol);
+
+ if (ucontrol->value.enumerated.item[0] < HDA_CS_DSP_NUM_FW) {
+ cs35l41->firmware_type = ucontrol->value.enumerated.item[0];
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int cs35l41_fw_type_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(hda_cs_dsp_fw_ids), hda_cs_dsp_fw_ids);
+}
+
+static int cs35l41_create_controls(struct cs35l41_hda *cs35l41)
+{
+ struct snd_kcontrol_new fw_type_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .info = cs35l41_fw_type_ctl_info,
+ .get = cs35l41_fw_type_ctl_get,
+ .put = cs35l41_fw_type_ctl_put,
+ };
+ struct snd_kcontrol_new fw_load_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .info = snd_ctl_boolean_mono_info,
+ .get = cs35l41_fw_load_ctl_get,
+ .put = cs35l41_fw_load_ctl_put,
+ };
+ struct snd_kcontrol *kctl1, *kctl2;
+ int ret = 0;
+
+ fw_load_ctl.name = kasprintf(GFP_KERNEL, "%s DSP1 Firmware Load", cs35l41->amp_name);
+ if (!fw_load_ctl.name) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ fw_type_ctl.name = kasprintf(GFP_KERNEL, "%s DSP1 Firmware Type", cs35l41->amp_name);
+ if (!fw_type_ctl.name) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ kctl1 = snd_ctl_new1(&fw_type_ctl, cs35l41);
+ if (!kctl1) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ if (snd_ctl_add(cs35l41->codec->card, kctl1)) {
+ ret = -ENODEV;
+ dev_err(cs35l41->dev, "Failed to add KControl: %s\n", fw_type_ctl.name);
+ goto err;
+ }
+
+ dev_dbg(cs35l41->dev, "Added Control %s\n", fw_type_ctl.name);
+
+ kctl2 = snd_ctl_new1(&fw_load_ctl, cs35l41);
+ if (!kctl2) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ if (snd_ctl_add(cs35l41->codec->card, kctl2)) {
+ ret = -ENODEV;
+ dev_err(cs35l41->dev, "Failed to add KControl: %s, removing all controls\n",
+ fw_load_ctl.name);
+ goto err;
+ }
+
+ dev_dbg(cs35l41->dev, "Added Control %s\n", fw_load_ctl.name);
+
+err:
+ kfree(fw_load_ctl.name);
+ kfree(fw_type_ctl.name);
+
+ return ret;
+}
+
static int cs35l41_hda_bind(struct device *dev, struct device *master, void *master_data)
{
struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
struct hda_component *comps = master_data;
+ int ret = 0;

if (!comps || cs35l41->index < 0 || cs35l41->index >= HDA_MAX_COMPONENTS)
return -EINVAL;
@@ -685,11 +860,16 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas
cs35l41->codec = comps->codec;
strscpy(comps->name, dev_name(dev), sizeof(comps->name));

+ cs35l41->firmware_type = HDA_CS_DSP_FW_SPK_PROT;
+
+ cs35l41->request_fw_load = true;
mutex_lock(&cs35l41->fw_mutex);
if (cs35l41_smart_amp(cs35l41) < 0)
dev_warn(cs35l41->dev, "Cannot Run Firmware, reverting to dsp bypass...\n");
mutex_unlock(&cs35l41->fw_mutex);

+ ret = cs35l41_create_controls(cs35l41);
+
comps->playback_hook = cs35l41_hda_playback_hook;
comps->suspend_hook = cs35l41_hda_suspend_hook;
comps->resume_hook = cs35l41_hda_resume_hook;
@@ -697,7 +877,7 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);

- return 0;
+ return ret;
}

static void cs35l41_hda_unbind(struct device *dev, struct device *master, void *master_data)
@@ -1208,6 +1388,7 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i
if (ret)
goto err;

+ INIT_WORK(&cs35l41->fw_load_work, cs35l41_fw_load_work);
mutex_init(&cs35l41->fw_mutex);

pm_runtime_set_autosuspend_delay(cs35l41->dev, 3000);
diff --git a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h
index 59a9461d0444..bdb35f3be68a 100644
--- a/sound/pci/hda/cs35l41_hda.h
+++ b/sound/pci/hda/cs35l41_hda.h
@@ -58,11 +58,17 @@ struct cs35l41_hda {
unsigned volatile long irq_errors;
const char *amp_name;
const char *acpi_subsystem_id;
+ int firmware_type;
int speaker_id;
struct mutex fw_mutex;
+ struct work_struct fw_load_work;
+
struct regmap_irq_chip_data *irq_data;
bool firmware_running;
+ bool request_fw_load;
+ bool fw_request_ongoing;
bool halo_initialized;
+ bool playback_started;
struct cs_dsp cs_dsp;
};

--
2.34.1

2022-06-22 08:27:01

by Vitaly Rodionov

[permalink] [raw]
Subject: [PATCH v7 06/14] ALSA: hda: cs35l41: Support reading subsystem id from ACPI

From: Stefan Binding <[email protected]>

On some laptop models, the ACPI contains the unique
Subsystem ID, and this value should be preferred
over the value from the HDA driver.

Signed-off-by: Stefan Binding <[email protected]>
Signed-off-by: Vitaly Rodionov <[email protected]>
---
sound/pci/hda/cs35l41_hda.c | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)

diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
index 7f0132694774..5f89a0462eb6 100644
--- a/sound/pci/hda/cs35l41_hda.c
+++ b/sound/pci/hda/cs35l41_hda.c
@@ -545,6 +545,36 @@ static int cs35l41_hda_apply_properties(struct cs35l41_hda *cs35l41)
return cs35l41_hda_channel_map(cs35l41->dev, 0, NULL, 1, &hw_cfg->spk_pos);
}

+static int cs35l41_get_acpi_sub_string(struct device *dev, struct acpi_device *adev,
+ const char **subsysid)
+{
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ acpi_status status;
+ int ret = 0;
+
+ status = acpi_evaluate_object(adev->handle, "_SUB", NULL, &buffer);
+ if (ACPI_SUCCESS(status)) {
+ obj = buffer.pointer;
+ if (obj->type == ACPI_TYPE_STRING) {
+ *subsysid = devm_kstrdup(dev, obj->string.pointer, GFP_KERNEL);
+ if (*subsysid == NULL) {
+ dev_err(dev, "Cannot allocate Subsystem ID");
+ ret = -ENOMEM;
+ }
+ } else {
+ dev_warn(dev, "Warning ACPI _SUB did not return a string\n");
+ ret = -ENODEV;
+ }
+ acpi_os_free(buffer.pointer);
+ } else {
+ dev_dbg(dev, "Warning ACPI _SUB failed: %#x\n", status);
+ ret = -ENODEV;
+ }
+
+ return ret;
+}
+
static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, int id)
{
struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg;
@@ -564,6 +594,12 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i
physdev = get_device(acpi_get_first_physical_node(adev));
acpi_dev_put(adev);

+ ret = cs35l41_get_acpi_sub_string(cs35l41->dev, adev, &cs35l41->acpi_subsystem_id);
+ if (ret)
+ dev_info(cs35l41->dev, "No Subsystem ID found in ACPI: %d", ret);
+ else
+ dev_dbg(cs35l41->dev, "Subsystem ID %s found", cs35l41->acpi_subsystem_id);
+
property = "cirrus,dev-index";
ret = device_property_count_u32(physdev, property);
if (ret <= 0)
--
2.34.1

2022-06-22 08:27:11

by Vitaly Rodionov

[permalink] [raw]
Subject: [PATCH v7 03/14] ALSA: hda: cs35l41: Save codec object inside component struct

From: Stefan Binding <[email protected]>

This is required for ALSA control support.

Signed-off-by: Stefan Binding <[email protected]>
Signed-off-by: Vitaly Rodionov <[email protected]>
---
sound/pci/hda/cs35l41_hda.c | 1 +
sound/pci/hda/cs35l41_hda.h | 1 +
sound/pci/hda/hda_component.h | 1 +
sound/pci/hda/patch_realtek.c | 1 +
4 files changed, 4 insertions(+)

diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
index cce27a86267f..bbbaafac50c3 100644
--- a/sound/pci/hda/cs35l41_hda.c
+++ b/sound/pci/hda/cs35l41_hda.c
@@ -117,6 +117,7 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas
return -EBUSY;

comps->dev = dev;
+ cs35l41->codec = comps->codec;
strscpy(comps->name, dev_name(dev), sizeof(comps->name));
comps->playback_hook = cs35l41_hda_playback_hook;

diff --git a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h
index a52ffd1f7999..aaf9e16684c2 100644
--- a/sound/pci/hda/cs35l41_hda.h
+++ b/sound/pci/hda/cs35l41_hda.h
@@ -32,6 +32,7 @@ struct cs35l41_hda {
struct regmap *regmap;
struct gpio_desc *reset_gpio;
struct cs35l41_hw_cfg hw_cfg;
+ struct hda_codec *codec;

int irq;
int index;
diff --git a/sound/pci/hda/hda_component.h b/sound/pci/hda/hda_component.h
index e26c896a13f3..534e845b9cd1 100644
--- a/sound/pci/hda/hda_component.h
+++ b/sound/pci/hda/hda_component.h
@@ -14,5 +14,6 @@
struct hda_component {
struct device *dev;
char name[HDA_MAX_NAME_SIZE];
+ struct hda_codec *codec;
void (*playback_hook)(struct device *dev, int action);
};
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index ff9a09a670ed..923c0d498d54 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -6654,6 +6654,7 @@ static void cs35l41_generic_fixup(struct hda_codec *cdc, int action, const char
"%s-%s:00-cs35l41-hda.%d", bus, hid, i);
if (!name)
return;
+ spec->comps[i].codec = cdc;
component_match_add(dev, &spec->match, component_compare_dev_name, name);
}
ret = component_master_add_with_match(dev, &comp_master_ops, spec->match);
--
2.34.1

2022-06-22 08:43:04

by Amadeusz Sławiński

[permalink] [raw]
Subject: Re: [PATCH v7 05/14] ALSA: hda: cs35l41: Save Subsystem ID inside CS35L41 Driver

On 6/22/2022 9:46 AM, Vitaly Rodionov wrote:
> From: Stefan Binding <[email protected]>
>
> The Subsystem ID is read from the HDA driver, and will
> be used by the CS35L41 driver to be able to uniquely
> identify the laptop, which is required to be able to
> define firmware to be used by specific models.
>
> Signed-off-by: Stefan Binding <[email protected]>
> Signed-off-by: Vitaly Rodionov <[email protected]>
> ---
> sound/pci/hda/cs35l41_hda.c | 3 +++
> sound/pci/hda/cs35l41_hda.h | 1 +
> sound/pci/hda/hda_component.h | 1 +
> sound/pci/hda/patch_realtek.c | 1 +
> 4 files changed, 6 insertions(+)
>
> diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
> index 92c6d8b7052e..7f0132694774 100644
> --- a/sound/pci/hda/cs35l41_hda.c
> +++ b/sound/pci/hda/cs35l41_hda.c
> @@ -356,6 +356,9 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas
> return -EBUSY;
>
> comps->dev = dev;
> + if (!cs35l41->acpi_subsystem_id)
> + cs35l41->acpi_subsystem_id = devm_kasprintf(dev, GFP_KERNEL,
> + "%.8x", comps->subsystem_id);

Wouldn't comps->codec->core.subsystem_id instead of comps->subsystem_id
work here? You already added saving of codec in patch 3, and then you
don't need rest of this patch?

2022-06-22 09:01:09

by Amadeusz Sławiński

[permalink] [raw]
Subject: Re: [PATCH v7 07/14] ALSA: hda: cs35l41: Support multiple load paths for firmware

On 6/22/2022 9:46 AM, Vitaly Rodionov wrote:
> From: Stefan Binding <[email protected]>
>
> To be able to support different firmwares and tuning
> for different models, the driver needs to be able to
> load a different firmware and coefficient file based
> on its Subsystem ID.
>
> The driver attempts to load the firmware in the
> following order:
>
> /lib/firmware/cirrus/cs35l41-dsp1-<fw-type>-<ssid>-dev<#>.wmfw
> /lib/firmware/cirrus/cs35l41-dsp1-<fw-type>-<ssid>.wmfw
> /lib/firmware/cirrus/cs35l41-dsp1-<fw-type>.wmfw
>
> Signed-off-by: Stefan Binding <[email protected]>
> Signed-off-by: Vitaly Rodionov <[email protected]>
> ---

...

CS35L41_FIRMWARE_ROOT, NULL, NULL, "wmfw");
> + if (!ret) {
> + /* fallback try cirrus/part-dspN-fwtype.bin */
> cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename,
> - CS35L41_FIRMWARE_ROOT, "bin");
> + CS35L41_FIRMWARE_ROOT, NULL, NULL, "bin");
> return 0;
> }
>
> @@ -143,7 +183,6 @@ static int cs35l41_request_firmware_files(struct cs35l41_hda *cs35l41,
> return ret;
> }
>
> -

You add unnecessary line in patch 4 and remove in this one?

> static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41)
> {
> const struct firmware *coeff_firmware = NULL;