[PATCH v4 00/17] 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. Stefan Binding (16): 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 ASoC: cs35l41: Move cs35l41 exit hibernate function into shared code ASoC: cs35l41: Do not print error when waking from hibernation ASoC: cs35l41: Add common cs35l41 enter hibernate function 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 | 7 + sound/pci/hda/Kconfig | 8 + sound/pci/hda/Makefile | 2 + sound/pci/hda/cs35l41_hda.c | 853 +++++++++++++++++++++++++++++++- sound/pci/hda/cs35l41_hda.h | 37 ++ 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 | 302 +++++++++++ sound/pci/hda/hda_cs_dsp_ctl.h | 40 ++ sound/pci/hda/patch_realtek.c | 27 +- sound/soc/codecs/cs35l41-lib.c | 82 ++- sound/soc/codecs/cs35l41.c | 71 +-- 14 files changed, 1362 insertions(+), 74 deletions(-) create mode 100644 sound/pci/hda/hda_cs_dsp_ctl.c create mode 100644 sound/pci/hda/hda_cs_dsp_ctl.h
From: Stefan Binding sbinding@opensource.cirrus.com
The cs35l41 part contains a DSP which is able to run firmware. The cs_dsp library can be used to control the DSP. These controls can be exposed to userspace using ALSA controls. This library adds apis to be able to interface between cs_dsp and hda drivers and expose the relevant controls as ALSA controls.
The apis to add and remove the controls start new threads when adding/removing controls since it is possible that setting an ALSA control would end up calling this api, which would then deadlock.
In the future, it will be necessary to load/unload firmware (and firmware ALSA controls) using a separate ALSA control, which means that the ability to call these apis from another ALSA control is required.
Signed-off-by: Stefan Binding sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - Updated commit message to describe reasons for adding/removing controls asynchronously - Removed unnecessary code which handled unused tlv or acked controls. - Removed code which handled outdate firmware versions when adding controls
MAINTAINERS | 1 + sound/pci/hda/Kconfig | 4 + sound/pci/hda/Makefile | 2 + sound/pci/hda/hda_cs_dsp_ctl.c | 242 +++++++++++++++++++++++++++++++++ sound/pci/hda/hda_cs_dsp_ctl.h | 34 +++++ 5 files changed, 283 insertions(+) create mode 100644 sound/pci/hda/hda_cs_dsp_ctl.c create mode 100644 sound/pci/hda/hda_cs_dsp_ctl.h
diff --git a/MAINTAINERS b/MAINTAINERS index 15674926a506..c47845b1332b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4695,6 +4695,7 @@ S: Maintained F: Documentation/devicetree/bindings/sound/cirrus,cs* F: include/dt-bindings/sound/cs* F: sound/pci/hda/cs* +F: sound/pci/hda/hda_cs_dsp_ctl.* F: sound/soc/codecs/cs*
CIRRUS LOGIC DSP FIRMWARE DRIVER diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig index 79ade4787d95..d1fd6cf82beb 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig @@ -94,6 +94,10 @@ config SND_HDA_PATCH_LOADER config SND_HDA_SCODEC_CS35L41 tristate
+config SND_HDA_CS_DSP_CONTROLS + tristate + depends on CS_DSP + config SND_HDA_SCODEC_CS35L41_I2C tristate "Build CS35L41 HD-audio side codec support for I2C Bus" depends on I2C diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile index 3e7bc608d45f..00d306104484 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -31,6 +31,7 @@ snd-hda-codec-hdmi-objs := patch_hdmi.o hda_eld.o snd-hda-scodec-cs35l41-objs := cs35l41_hda.o snd-hda-scodec-cs35l41-i2c-objs := cs35l41_hda_i2c.o snd-hda-scodec-cs35l41-spi-objs := cs35l41_hda_spi.o +snd-hda-cs-dsp-ctls-objs := hda_cs_dsp_ctl.o
# common driver obj-$(CONFIG_SND_HDA) := snd-hda-codec.o @@ -54,6 +55,7 @@ obj-$(CONFIG_SND_HDA_CODEC_HDMI) += snd-hda-codec-hdmi.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41) += snd-hda-scodec-cs35l41.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_I2C) += snd-hda-scodec-cs35l41-i2c.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_SPI) += snd-hda-scodec-cs35l41-spi.o +obj-$(CONFIG_SND_HDA_CS_DSP_CONTROLS) += snd-hda-cs-dsp-ctls.o
# this must be the last entry after codec drivers; # otherwise the codec patches won't be hooked before the PCI probe diff --git a/sound/pci/hda/hda_cs_dsp_ctl.c b/sound/pci/hda/hda_cs_dsp_ctl.c new file mode 100644 index 000000000000..46df48ff2ae1 --- /dev/null +++ b/sound/pci/hda/hda_cs_dsp_ctl.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// HDA DSP ALSA Control Driver +// +// Copyright 2022 Cirrus Logic, Inc. +// +// Author: Stefan Binding sbinding@opensource.cirrus.com + +#include <linux/module.h> +#include <sound/soc.h> +#include <linux/firmware/cirrus/cs_dsp.h> +#include <linux/firmware/cirrus/wmfw.h> +#include "hda_cs_dsp_ctl.h" + +#define ADSP_MAX_STD_CTRL_SIZE 512 + +struct hda_cs_dsp_coeff_ctl { + const char *name; + struct cs_dsp_coeff_ctl *cs_ctl; + struct snd_card *card; + struct soc_bytes_ext bytes_ext; + struct work_struct add_work; + struct work_struct remove_work; +}; + +static const char * const hda_cs_dsp_fw_text[HDA_CS_DSP_NUM_FW] = { + [HDA_CS_DSP_FW_SPK_PROT] = "Prot", + [HDA_CS_DSP_FW_SPK_CALI] = "Cali", + [HDA_CS_DSP_FW_SPK_DIAG] = "Diag", + [HDA_CS_DSP_FW_MISC] = "Misc", +}; + +static inline struct hda_cs_dsp_coeff_ctl *bytes_ext_to_ctl(struct soc_bytes_ext *ext) +{ + return container_of(ext, struct hda_cs_dsp_coeff_ctl, bytes_ext); +} + +static int hda_cs_dsp_coeff_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *)kctl->private_value; + struct hda_cs_dsp_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = cs_ctl->len; + + return 0; +} + +static int hda_cs_dsp_coeff_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *)kctl->private_value; + struct hda_cs_dsp_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + char *p = ucontrol->value.bytes.data; + int ret = 0; + + mutex_lock(&cs_ctl->dsp->pwr_lock); + ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, p, cs_ctl->len); + mutex_unlock(&cs_ctl->dsp->pwr_lock); + + return ret; +} + +static int hda_cs_dsp_coeff_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *)kctl->private_value; + struct hda_cs_dsp_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + char *p = ucontrol->value.bytes.data; + int ret; + + mutex_lock(&cs_ctl->dsp->pwr_lock); + ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, p, cs_ctl->len); + mutex_unlock(&cs_ctl->dsp->pwr_lock); + + return ret; +} + +static unsigned int wmfw_convert_flags(unsigned int in, unsigned int len) +{ + unsigned int out, rd, wr, vol; + + if (len > ADSP_MAX_STD_CTRL_SIZE) { + rd = SNDRV_CTL_ELEM_ACCESS_TLV_READ; + wr = SNDRV_CTL_ELEM_ACCESS_TLV_WRITE; + vol = SNDRV_CTL_ELEM_ACCESS_VOLATILE; + + out = SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + } else { + rd = SNDRV_CTL_ELEM_ACCESS_READ; + wr = SNDRV_CTL_ELEM_ACCESS_WRITE; + vol = SNDRV_CTL_ELEM_ACCESS_VOLATILE; + + out = 0; + } + + if (in) { + out |= rd; + if (in & WMFW_CTL_FLAG_WRITEABLE) + out |= wr; + if (in & WMFW_CTL_FLAG_VOLATILE) + out |= vol; + } else { + out |= rd | wr | vol; + } + + return out; +} + +static void hda_cs_dsp_ctl_add_work(struct work_struct *work) +{ + struct hda_cs_dsp_coeff_ctl *ctl = container_of(work, + struct hda_cs_dsp_coeff_ctl, + add_work); + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + struct snd_kcontrol_new *kcontrol; + + kcontrol = kzalloc(sizeof(*kcontrol), GFP_KERNEL); + if (!kcontrol) + return; + + kcontrol->name = ctl->name; + kcontrol->info = hda_cs_dsp_coeff_info; + kcontrol->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kcontrol->tlv.c = snd_soc_bytes_tlv_callback; + kcontrol->private_value = (unsigned long)&ctl->bytes_ext; + kcontrol->access = wmfw_convert_flags(cs_ctl->flags, cs_ctl->len); + + kcontrol->get = hda_cs_dsp_coeff_get; + kcontrol->put = hda_cs_dsp_coeff_put; + + if (snd_ctl_add(ctl->card, snd_ctl_new1(kcontrol, NULL))) + dev_err(cs_ctl->dsp->dev, "Failed to add KControl: %s\n", kcontrol->name); + else + dev_dbg(cs_ctl->dsp->dev, "Added KControl: %s\n", kcontrol->name); + + kfree(kcontrol); +} + +int hda_cs_dsp_control_add(struct cs_dsp_coeff_ctl *cs_ctl, struct hda_cs_dsp_ctl_info *info) +{ + struct cs_dsp *cs_dsp = cs_ctl->dsp; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct hda_cs_dsp_coeff_ctl *ctl; + const char *region_name; + int ret; + + if (cs_ctl->flags & WMFW_CTL_FLAG_SYS) { + dev_dbg(cs_dsp->dev, "cs_ctl->flags = WMFW_CTL_FLAG_SYS\n"); + return 0; + } + + region_name = cs_dsp_mem_region_name(cs_ctl->alg_region.type); + if (!region_name) { + dev_err(cs_dsp->dev, "Unknown region type: %d\n", cs_ctl->alg_region.type); + return -EINVAL; + } + + ret = scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s %s %.12s %x", info->amp_name, + cs_dsp->name, hda_cs_dsp_fw_text[info->fw_type], cs_ctl->alg_region.alg); + + if (cs_ctl->subname) { + int avail = SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret - 2; + int skip = 0; + + /* Truncate the subname from the start if it is too long */ + if (cs_ctl->subname_len > avail) + skip = cs_ctl->subname_len - avail; + + snprintf(name + ret, SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret, + " %.*s", cs_ctl->subname_len - skip, cs_ctl->subname + skip); + } + + ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); + if (!ctl) + return -ENOMEM; + ctl->cs_ctl = cs_ctl; + ctl->card = info->card; + + ctl->name = kmemdup(name, strlen(name) + 1, GFP_KERNEL); + if (!ctl->name) { + ret = -ENOMEM; + dev_err(cs_dsp->dev, "Cannot save ctl name\n"); + goto err_ctl; + } + + cs_ctl->priv = ctl; + + INIT_WORK(&ctl->add_work, hda_cs_dsp_ctl_add_work); + schedule_work(&ctl->add_work); + + return 0; + +err_ctl: + dev_err(cs_dsp->dev, "Error adding control: %s\n", name); + kfree(ctl); + return ret; +} +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_control_add, SND_HDA_CS_DSP_CONTROLS); + +int hda_cs_dsp_remove_kcontrol(struct snd_card *card, const char *name) +{ + struct snd_kcontrol *kctl; + + list_for_each_entry(kctl, &card->controls, list) + if (!strncmp(kctl->id.name, name, sizeof(kctl->id.name))) + return snd_ctl_remove_id(card, &kctl->id); + + return -EINVAL; +} +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_remove_kcontrol, SND_HDA_CS_DSP_CONTROLS); + +static void hda_cs_dsp_ctl_del_work(struct work_struct *work) +{ + struct hda_cs_dsp_coeff_ctl *ctl = container_of(work, + struct hda_cs_dsp_coeff_ctl, + remove_work); + + cancel_work_sync(&ctl->add_work); + + hda_cs_dsp_remove_kcontrol(ctl->card, ctl->name); + + kfree(ctl->name); + kfree(ctl); +} + +void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl) +{ + struct hda_cs_dsp_coeff_ctl *ctl = cs_ctl->priv; + + INIT_WORK(&ctl->remove_work, hda_cs_dsp_ctl_del_work); + schedule_work(&ctl->remove_work); +} +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_control_remove, SND_HDA_CS_DSP_CONTROLS); + +MODULE_DESCRIPTION("CS_DSP ALSA Control HDA Library"); +MODULE_AUTHOR("Stefan Binding, sbinding@opensource.cirrus.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/pci/hda/hda_cs_dsp_ctl.h b/sound/pci/hda/hda_cs_dsp_ctl.h new file mode 100644 index 000000000000..3c90312b45d6 --- /dev/null +++ b/sound/pci/hda/hda_cs_dsp_ctl.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * HDA DSP ALSA Control Driver + * + * Copyright 2022 Cirrus Logic, Inc. + * + * Author: Stefan Binding sbinding@opensource.cirrus.com + */ + +#ifndef __HDA_CS_DSP_CTL_H__ +#define __HDA_CS_DSP_CTL_H__ + +#include <sound/soc.h> +#include <linux/firmware/cirrus/cs_dsp.h> + +enum hda_cs_dsp_fw_id { + HDA_CS_DSP_FW_SPK_PROT, + HDA_CS_DSP_FW_SPK_CALI, + HDA_CS_DSP_FW_SPK_DIAG, + HDA_CS_DSP_FW_MISC, + HDA_CS_DSP_NUM_FW +}; + +struct hda_cs_dsp_ctl_info { + struct snd_card *card; + enum hda_cs_dsp_fw_id fw_type; + const char *amp_name; +}; + +int hda_cs_dsp_control_add(struct cs_dsp_coeff_ctl *cs_ctl, struct hda_cs_dsp_ctl_info *info); +void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl); +int hda_cs_dsp_remove_kcontrol(struct snd_card *card, const char *name); + +#endif /*__HDA_CS_DSP_CTL_H__*/
On Wed, May 25, 2022 at 02:16:22PM +0100, Vitaly Rodionov wrote:
From: Stefan Binding sbinding@opensource.cirrus.com
The cs35l41 part contains a DSP which is able to run firmware. The cs_dsp library can be used to control the DSP. These controls can be exposed to userspace using ALSA controls. This library adds apis to be able to interface between cs_dsp and hda drivers and expose the relevant controls as ALSA controls.
The apis to add and remove the controls start new threads when adding/removing controls since it is possible that setting an ALSA control would end up calling this api, which would then deadlock.
In the future, it will be necessary to load/unload firmware (and firmware ALSA controls) using a separate ALSA control, which means that the ability to call these apis from another ALSA control is required.
Signed-off-by: Stefan Binding sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com
Changes since v2:
- Updated commit message to describe reasons for adding/removing controls asynchronously
- Removed unnecessary code which handled unused tlv or acked controls.
- Removed code which handled outdate firmware versions when adding controls
+static unsigned int wmfw_convert_flags(unsigned int in, unsigned int len) +{
- unsigned int out, rd, wr, vol;
- if (len > ADSP_MAX_STD_CTRL_SIZE) {
rd = SNDRV_CTL_ELEM_ACCESS_TLV_READ;
wr = SNDRV_CTL_ELEM_ACCESS_TLV_WRITE;
vol = SNDRV_CTL_ELEM_ACCESS_VOLATILE;
out = SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
- } else {
rd = SNDRV_CTL_ELEM_ACCESS_READ;
wr = SNDRV_CTL_ELEM_ACCESS_WRITE;
vol = SNDRV_CTL_ELEM_ACCESS_VOLATILE;
out = 0;
- }
This if should be changed if we are no longer supporting the TLV controls, you want to report an error if we exceed the max control size not switch to the TLV access flags.
+static void hda_cs_dsp_ctl_add_work(struct work_struct *work) +{
- struct hda_cs_dsp_coeff_ctl *ctl = container_of(work,
struct hda_cs_dsp_coeff_ctl,
add_work);
- struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl;
- struct snd_kcontrol_new *kcontrol;
- kcontrol = kzalloc(sizeof(*kcontrol), GFP_KERNEL);
- if (!kcontrol)
return;
- kcontrol->name = ctl->name;
- kcontrol->info = hda_cs_dsp_coeff_info;
- kcontrol->iface = SNDRV_CTL_ELEM_IFACE_MIXER;
- kcontrol->tlv.c = snd_soc_bytes_tlv_callback;
- kcontrol->private_value = (unsigned long)&ctl->bytes_ext;
Don't need to set tlv.c or the private_value for bytes_ext if we are not using TLVs.
+struct hda_cs_dsp_ctl_info {
- struct snd_card *card;
- enum hda_cs_dsp_fw_id fw_type;
- const char *amp_name;
Might be better to call this something slightly more generic than amp_name. Just incase this stuff gets used with a CODEC or something in the future.
Thanks, Charles
From: Stefan Binding sbinding@opensource.cirrus.com
DSP controls are exposed as ALSA controls, however, some of these controls are required to be accessed by the driver. Add apis which allow read/write of these controls. The write api will also notify the ALSA control on value change.
Signed-off-by: Stefan Binding sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - No change
sound/pci/hda/hda_cs_dsp_ctl.c | 52 ++++++++++++++++++++++++++++++++++ sound/pci/hda/hda_cs_dsp_ctl.h | 4 +++ 2 files changed, 56 insertions(+)
diff --git a/sound/pci/hda/hda_cs_dsp_ctl.c b/sound/pci/hda/hda_cs_dsp_ctl.c index 46df48ff2ae1..3b837d000a00 100644 --- a/sound/pci/hda/hda_cs_dsp_ctl.c +++ b/sound/pci/hda/hda_cs_dsp_ctl.c @@ -237,6 +237,58 @@ void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl) } EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_control_remove, SND_HDA_CS_DSP_CONTROLS);
+int hda_cs_dsp_write_ctl(struct cs_dsp *dsp, const char *name, int type, + unsigned int alg, void *buf, size_t len) +{ + struct cs_dsp_coeff_ctl *cs_ctl; + struct hda_cs_dsp_coeff_ctl *ctl; + struct snd_kcontrol *kctl; + int ret; + + cs_ctl = cs_dsp_get_ctl(dsp, name, type, alg); + if (!cs_ctl) + return -EINVAL; + + ctl = cs_ctl->priv; + + if (len > cs_ctl->len) + return -EINVAL; + + ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, buf, len); + if (ret) + return ret; + + if (cs_ctl->flags & WMFW_CTL_FLAG_SYS) + return 0; + + list_for_each_entry(kctl, &ctl->card->controls, list) + if (!strncmp(kctl->id.name, ctl->name, sizeof(kctl->id.name))) { + snd_ctl_notify(ctl->card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); + return 0; + } + + dev_warn(dsp->dev, "Cannot find Control for %s\n", name); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_write_ctl, SND_HDA_CS_DSP_CONTROLS); + +int hda_cs_dsp_read_ctl(struct cs_dsp *dsp, const char *name, int type, + unsigned int alg, void *buf, size_t len) +{ + struct cs_dsp_coeff_ctl *cs_ctl; + + cs_ctl = cs_dsp_get_ctl(dsp, name, type, alg); + if (!cs_ctl) + return -EINVAL; + + if (len > cs_ctl->len) + return -EINVAL; + + return cs_dsp_coeff_read_ctrl(cs_ctl, 0, buf, len); +} +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_read_ctl, SND_HDA_CS_DSP_CONTROLS); + MODULE_DESCRIPTION("CS_DSP ALSA Control HDA Library"); MODULE_AUTHOR("Stefan Binding, sbinding@opensource.cirrus.com"); MODULE_LICENSE("GPL"); diff --git a/sound/pci/hda/hda_cs_dsp_ctl.h b/sound/pci/hda/hda_cs_dsp_ctl.h index 3c90312b45d6..65b9c5c68957 100644 --- a/sound/pci/hda/hda_cs_dsp_ctl.h +++ b/sound/pci/hda/hda_cs_dsp_ctl.h @@ -30,5 +30,9 @@ struct hda_cs_dsp_ctl_info { int hda_cs_dsp_control_add(struct cs_dsp_coeff_ctl *cs_ctl, struct hda_cs_dsp_ctl_info *info); void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl); int hda_cs_dsp_remove_kcontrol(struct snd_card *card, const char *name); +int hda_cs_dsp_write_ctl(struct cs_dsp *dsp, const char *name, int type, + unsigned int alg, void *buf, size_t len); +int hda_cs_dsp_read_ctl(struct cs_dsp *dsp, const char *name, int type, + unsigned int alg, void *buf, size_t len);
#endif /*__HDA_CS_DSP_CTL_H__*/
On Wed, May 25, 2022 at 02:16:23PM +0100, Vitaly Rodionov wrote:
From: Stefan Binding sbinding@opensource.cirrus.com
DSP controls are exposed as ALSA controls, however, some of these controls are required to be accessed by the driver. Add apis which allow read/write of these controls. The write api will also notify the ALSA control on value change.
Signed-off-by: Stefan Binding sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com
Changes since v2:
- No change
sound/pci/hda/hda_cs_dsp_ctl.c | 52 ++++++++++++++++++++++++++++++++++ sound/pci/hda/hda_cs_dsp_ctl.h | 4 +++ 2 files changed, 56 insertions(+)
diff --git a/sound/pci/hda/hda_cs_dsp_ctl.c b/sound/pci/hda/hda_cs_dsp_ctl.c index 46df48ff2ae1..3b837d000a00 100644 --- a/sound/pci/hda/hda_cs_dsp_ctl.c +++ b/sound/pci/hda/hda_cs_dsp_ctl.c @@ -237,6 +237,58 @@ void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl) } EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_control_remove, SND_HDA_CS_DSP_CONTROLS);
+int hda_cs_dsp_write_ctl(struct cs_dsp *dsp, const char *name, int type,
unsigned int alg, void *buf, size_t len)
+{
- struct cs_dsp_coeff_ctl *cs_ctl;
- struct hda_cs_dsp_coeff_ctl *ctl;
- struct snd_kcontrol *kctl;
- int ret;
- cs_ctl = cs_dsp_get_ctl(dsp, name, type, alg);
- if (!cs_ctl)
return -EINVAL;
- ctl = cs_ctl->priv;
- if (len > cs_ctl->len)
return -EINVAL;
Is it just me or are these length check unnecessary? I realise they are also in the wm_adsp code you are copying, but it looks to me like they are redundant in both cases, cs_dsp_coeff_*_ctrl appears to do a length check itself.
- ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, buf, len);
- if (ret)
return ret;
- if (cs_ctl->flags & WMFW_CTL_FLAG_SYS)
return 0;
- list_for_each_entry(kctl, &ctl->card->controls, list)
if (!strncmp(kctl->id.name, ctl->name, sizeof(kctl->id.name))) {
snd_ctl_notify(ctl->card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
return 0;
}
- dev_warn(dsp->dev, "Cannot find Control for %s\n", name);
- return 0;
+} +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_write_ctl, SND_HDA_CS_DSP_CONTROLS);
+int hda_cs_dsp_read_ctl(struct cs_dsp *dsp, const char *name, int type,
unsigned int alg, void *buf, size_t len)
+{
- struct cs_dsp_coeff_ctl *cs_ctl;
- cs_ctl = cs_dsp_get_ctl(dsp, name, type, alg);
- if (!cs_ctl)
return -EINVAL;
- if (len > cs_ctl->len)
return -EINVAL;
ditto.
Thanks, Charles
From: Stefan Binding sbinding@opensource.cirrus.com
This is required for ALSA control support.
Signed-off-by: Stefan Binding sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - No change
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 80e4955e8c10..cd289b17ca13 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -6631,6 +6631,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);
From: Vitaly Rodionov vitaly.rodionov@cirrus.com
This patch adds support for the CS35L41 DSP. The DSP allows for extra features, such as running speaker protection algorithms and hibernations.
To utilize these features, the driver must load firmware into the DSP, as well as various tuning files which allow for cusomtization for specific models.
Signed-off-by: Vitaly Rodionov vitaly.rodionov@cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - No change
include/sound/cs35l41.h | 4 + sound/pci/hda/Kconfig | 4 + sound/pci/hda/cs35l41_hda.c | 238 +++++++++++++++++++++++++++++++++++- sound/pci/hda/cs35l41_hda.h | 12 ++ 4 files changed, 257 insertions(+), 1 deletion(-)
diff --git a/include/sound/cs35l41.h b/include/sound/cs35l41.h index 8972fa697622..8887087815a7 100644 --- a/include/sound/cs35l41.h +++ b/include/sound/cs35l41.h @@ -665,6 +665,10 @@ #define CS35L41_BST_EN_DEFAULT 0x2 #define CS35L41_AMP_EN_SHIFT 0 #define CS35L41_AMP_EN_MASK 1 +#define CS35L41_VMON_EN_MASK 0x1000 +#define CS35L41_VMON_EN_SHIFT 12 +#define CS35L41_IMON_EN_MASK 0x2000 +#define CS35L41_IMON_EN_SHIFT 13
#define CS35L41_PDN_DONE_MASK 0x00800000 #define CS35L41_PDN_DONE_SHIFT 23 diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig index d1fd6cf82beb..1c378cca5dac 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig @@ -106,6 +106,8 @@ config SND_HDA_SCODEC_CS35L41_I2C select SND_HDA_GENERIC select SND_SOC_CS35L41_LIB select SND_HDA_SCODEC_CS35L41 + select SND_HDA_CS_DSP_CONTROLS + select CS_DSP select REGMAP_IRQ help Say Y or M here to include CS35L41 I2C HD-audio side codec support @@ -122,6 +124,8 @@ config SND_HDA_SCODEC_CS35L41_SPI select SND_HDA_GENERIC select SND_SOC_CS35L41_LIB select SND_HDA_SCODEC_CS35L41 + select SND_HDA_CS_DSP_CONTROLS + select CS_DSP select REGMAP_IRQ help Say Y or M here to include CS35L41 SPI HD-audio side codec support diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c index bbbaafac50c3..fe2ba03e602b 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -9,12 +9,22 @@ #include <linux/acpi.h> #include <linux/module.h> #include <sound/hda_codec.h> +#include <sound/soc.h> #include "hda_local.h" #include "hda_auto_parser.h" #include "hda_jack.h" #include "hda_generic.h" #include "hda_component.h" #include "cs35l41_hda.h" +#include "hda_cs_dsp_ctl.h" + +#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 +#define HALO_STATE_DSP_CTL_ALG 262308
static const struct reg_sequence cs35l41_hda_config[] = { { CS35L41_PLL_CLK_CTRL, 0x00000430 }, // 3072000Hz, BCLK Input, PLL_REFCLK_EN = 1 @@ -27,11 +37,167 @@ static const struct reg_sequence cs35l41_hda_config[] = { { CS35L41_AMP_GAIN_CTRL, 0x00000084 }, // AMP_GAIN_PCM 4.5 dB };
+static const struct reg_sequence cs35l41_hda_config_dsp[] = { + { CS35L41_PLL_CLK_CTRL, 0x00000430 }, // 3072000Hz, BCLK Input, PLL_REFCLK_EN = 1 + { CS35L41_DSP_CLK_CTRL, 0x00000003 }, // DSP CLK EN + { CS35L41_GLOBAL_CLK_CTRL, 0x00000003 }, // GLOBAL_FS = 48 kHz + { CS35L41_SP_ENABLES, 0x00010001 }, // ASP_RX1_EN = 1, ASP_TX1_EN = 1 + { CS35L41_SP_RATE_CTRL, 0x00000021 }, // ASP_BCLK_FREQ = 3.072 MHz + { CS35L41_SP_FORMAT, 0x20200200 }, // 32 bits RX/TX slots, I2S, clk consumer + { CS35L41_SP_HIZ_CTRL, 0x00000003 }, // Hi-Z unused/disabled + { CS35L41_SP_TX_WL, 0x00000018 }, // 24 cycles/slot + { CS35L41_SP_RX_WL, 0x00000018 }, // 24 cycles/slot + { CS35L41_DAC_PCM1_SRC, 0x00000032 }, // DACPCM1_SRC = ERR_VOL + { CS35L41_ASP_TX1_SRC, 0x00000018 }, // ASPTX1 SRC = VMON + { CS35L41_ASP_TX2_SRC, 0x00000019 }, // ASPTX2 SRC = IMON + { CS35L41_ASP_TX3_SRC, 0x00000028 }, // ASPTX3 SRC = VPMON + { CS35L41_ASP_TX4_SRC, 0x00000029 }, // ASPTX4 SRC = VBSTMON + { CS35L41_DSP1_RX1_SRC, 0x00000008 }, // DSP1RX1 SRC = ASPRX1 + { CS35L41_DSP1_RX2_SRC, 0x00000008 }, // DSP1RX2 SRC = ASPRX1 + { CS35L41_DSP1_RX3_SRC, 0x00000018 }, // DSP1RX3 SRC = VMON + { CS35L41_DSP1_RX4_SRC, 0x00000019 }, // DSP1RX4 SRC = IMON + { CS35L41_DSP1_RX5_SRC, 0x00000029 }, // DSP1RX5 SRC = VBSTMON + { CS35L41_AMP_DIG_VOL_CTRL, 0x00000000 }, // AMP_VOL_PCM 0.0 dB + { CS35L41_AMP_GAIN_CTRL, 0x00000233 }, // AMP_GAIN_PCM = 17.5dB AMP_GAIN_PDM = 19.5dB +}; + static const struct reg_sequence cs35l41_hda_mute[] = { { CS35L41_AMP_GAIN_CTRL, 0x00000000 }, // AMP_GAIN_PCM 0.5 dB { CS35L41_AMP_DIG_VOL_CTRL, 0x0000A678 }, // AMP_VOL_PCM Mute };
+static int cs35l41_control_add(struct cs_dsp_coeff_ctl *cs_ctl) +{ + struct cs35l41_hda *cs35l41 = container_of(cs_ctl->dsp, struct cs35l41_hda, cs_dsp); + struct hda_cs_dsp_ctl_info info; + + info.amp_name = cs35l41->amp_name; + info.fw_type = HDA_CS_DSP_FW_SPK_PROT; + info.card = cs35l41->codec->card; + + return hda_cs_dsp_control_add(cs_ctl, &info); +} + +static const struct cs_dsp_client_ops client_ops = { + .control_add = cs35l41_control_add, + .control_remove = hda_cs_dsp_control_remove, +}; + +static int cs35l41_request_firmware_file(struct cs35l41_hda *cs35l41, + const struct firmware **firmware, char **filename, + const char *dir, 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 (*filename == NULL) + return -ENOMEM; + + /* + * Make sure that filename is lower-case and any non alpha-numeric + * characters except full stop and '/' are replaced with hyphens. + */ + s = *filename; + while (*s) { + c = *s; + if (isalnum(c)) + *s = tolower(c); + else if (c != '.' && c != '/') + *s = '-'; + s++; + } + + ret = firmware_request_nowarn(firmware, *filename, cs35l41->dev); + if (ret != 0) { + dev_dbg(cs35l41->dev, "Failed to request '%s'\n", *filename); + kfree(*filename); + *filename = NULL; + } + + return ret; +} + +static int cs35l41_request_firmware_files(struct cs35l41_hda *cs35l41, + const struct firmware **wmfw_firmware, + char **wmfw_filename, + const struct firmware **coeff_firmware, + char **coeff_filename) +{ + int ret; + + /* cirrus/part-dspN-fwtype.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, "wmfw"); + if (!ret) { + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, "bin"); + return 0; + } + + dev_warn(cs35l41->dev, "Failed to request firmware\n"); + + return ret; +} + + +static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41) +{ + const struct firmware *coeff_firmware = NULL; + const struct firmware *wmfw_firmware = NULL; + struct cs_dsp *dsp = &cs35l41->cs_dsp; + char *coeff_filename = NULL; + char *wmfw_filename = NULL; + int ret; + + cs35l41_configure_cs_dsp(cs35l41->dev, cs35l41->regmap, dsp); + dsp->client_ops = &client_ops; + + if (!cs35l41->halo_initialized) { + ret = cs_dsp_halo_init(&cs35l41->cs_dsp); + if (ret) + return ret; + cs35l41->halo_initialized = true; + } + + ret = cs35l41_request_firmware_files(cs35l41, &wmfw_firmware, &wmfw_filename, + &coeff_firmware, &coeff_filename); + if (ret < 0) + return ret; + + dev_dbg(cs35l41->dev, "Loaded WMFW Firmware: %s\n", wmfw_filename); + if (coeff_filename) + dev_dbg(cs35l41->dev, "Loaded Coefficient File: %s\n", coeff_filename); + else + 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); + + if (wmfw_firmware) + release_firmware(wmfw_firmware); + if (coeff_firmware) + release_firmware(coeff_firmware); + kfree(wmfw_filename); + kfree(coeff_filename); + + return ret; +} + +static void cs35l41_remove_dsp(struct cs35l41_hda *cs35l41) +{ + struct cs_dsp *dsp = &cs35l41->cs_dsp; + + cs_dsp_stop(dsp); + cs_dsp_power_down(dsp); + cs_dsp_remove(dsp); + cs35l41->firmware_running = false; + dev_dbg(cs35l41->dev, "Unloaded Firmware\n"); +} + /* Protection release cycle to get the speaker out of Safe-Mode */ static void cs35l41_error_release(struct device *dev, struct regmap *regmap, unsigned int mask) { @@ -55,7 +221,18 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action)
switch (action) { case HDA_GEN_PCM_ACT_OPEN: - regmap_multi_reg_write(reg, cs35l41_hda_config, ARRAY_SIZE(cs35l41_hda_config)); + if (cs35l41->firmware_running) { + regmap_multi_reg_write(reg, cs35l41_hda_config_dsp, + ARRAY_SIZE(cs35l41_hda_config_dsp)); + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, + CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, + 1 << CS35L41_VMON_EN_SHIFT | 1 << CS35L41_IMON_EN_SHIFT); + cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, + CSPL_MBOX_CMD_RESUME); + } else { + regmap_multi_reg_write(reg, cs35l41_hda_config, + ARRAY_SIZE(cs35l41_hda_config)); + } ret = regmap_update_bits(reg, CS35L41_PWR_CTRL2, CS35L41_AMP_EN_MASK, 1 << CS35L41_AMP_EN_SHIFT); if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) @@ -73,6 +250,13 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action) CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT); if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00000001); + if (cs35l41->firmware_running) { + cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, + CSPL_MBOX_CMD_PAUSE); + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, + CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, + 0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT); + } cs35l41_irq_release(cs35l41); break; default: @@ -104,6 +288,51 @@ static int cs35l41_hda_channel_map(struct device *dev, unsigned int tx_num, unsi rx_slot); }
+static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41) +{ + int halo_sts; + int ret; + + ret = cs35l41_init_dsp(cs35l41); + if (ret) { + dev_warn(cs35l41->dev, "Cannot Initialize Firmware. Error: %d\n", ret); + goto clean_dsp; + } + + ret = cs35l41_write_fs_errata(cs35l41->dev, cs35l41->regmap); + if (ret) { + dev_err(cs35l41->dev, "Cannot Write FS Errata: %d\n", ret); + goto clean_dsp; + } + + ret = cs_dsp_run(&cs35l41->cs_dsp); + if (ret) { + dev_err(cs35l41->dev, "Fail to start dsp: %d\n", ret); + goto clean_dsp; + } + + ret = read_poll_timeout(hda_cs_dsp_read_ctl, ret, + be32_to_cpu(halo_sts) == HALO_STATE_CODE_RUN, + 1000, 15000, false, &cs35l41->cs_dsp, HALO_STATE_DSP_CTL_NAME, + HALO_STATE_DSP_CTL_TYPE, HALO_STATE_DSP_CTL_ALG, + &halo_sts, sizeof(halo_sts)); + + if (ret) { + dev_err(cs35l41->dev, "Timeout waiting for HALO Core to start. State: %d\n", + halo_sts); + goto clean_dsp; + } + + cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, CSPL_MBOX_CMD_PAUSE); + cs35l41->firmware_running = true; + + return 0; + +clean_dsp: + cs35l41_remove_dsp(cs35l41); + return ret; +} + static int cs35l41_hda_bind(struct device *dev, struct device *master, void *master_data) { struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); @@ -121,6 +350,9 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas strscpy(comps->name, dev_name(dev), sizeof(comps->name)); comps->playback_hook = cs35l41_hda_playback_hook;
+ if (cs35l41_smart_amp(cs35l41) < 0) + dev_warn(cs35l41->dev, "Cannot Run Firmware, reverting to dsp bypass...\n"); + return 0; }
@@ -564,6 +796,9 @@ void cs35l41_hda_remove(struct device *dev) { struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
+ if (cs35l41->firmware_running) + cs35l41_remove_dsp(cs35l41); + component_del(cs35l41->dev, &cs35l41_hda_comp_ops);
if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type)) @@ -573,5 +808,6 @@ void cs35l41_hda_remove(struct device *dev) EXPORT_SYMBOL_NS_GPL(cs35l41_hda_remove, SND_HDA_SCODEC_CS35L41);
MODULE_DESCRIPTION("CS35L41 HDA Driver"); +MODULE_IMPORT_NS(SND_HDA_CS_DSP_CONTROLS); MODULE_AUTHOR("Lucas Tanure, Cirrus Logic Inc, tanureal@opensource.cirrus.com"); MODULE_LICENSE("GPL"); diff --git a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h index aaf9e16684c2..03c5f14631dd 100644 --- a/sound/pci/hda/cs35l41_hda.h +++ b/sound/pci/hda/cs35l41_hda.h @@ -15,6 +15,9 @@ #include <linux/device.h> #include <sound/cs35l41.h>
+#include <linux/firmware/cirrus/cs_dsp.h> +#include <linux/firmware/cirrus/wmfw.h> + enum cs35l41_hda_spk_pos { CS35l41_LEFT, CS35l41_RIGHT, @@ -40,6 +43,15 @@ struct cs35l41_hda { unsigned volatile long irq_errors; const char *amp_name; struct regmap_irq_chip_data *irq_data; + bool firmware_running; + bool halo_initialized; + struct cs_dsp cs_dsp; +}; + +enum halo_state { + HALO_STATE_CODE_INIT_DOWNLOAD = 0, + HALO_STATE_CODE_START, + HALO_STATE_CODE_RUN };
int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int irq,
From: Stefan Binding sbinding@opensource.cirrus.com
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 sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - No change
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 fe2ba03e602b..c235b899aa04 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -346,6 +346,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 03c5f14631dd..b8352088a5cb 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 regmap_irq_chip_data *irq_data; bool firmware_running; bool halo_initialized; 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 cd289b17ca13..b8249c4dcb1d 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -6632,6 +6632,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);
From: Stefan Binding sbinding@opensource.cirrus.com
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 sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - No change
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 c235b899aa04..81d6f4cf0166 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -533,6 +533,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; @@ -552,6 +582,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)
From: Stefan Binding sbinding@opensource.cirrus.com
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 sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - No change
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 81d6f4cf0166..0957b4984143 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;
From: Stefan Binding sbinding@opensource.cirrus.com
Some Laptops use a number of gpios to define which vendor is used for a particular laptop. Different coefficient files are used for different vendors.
Signed-off-by: Stefan Binding sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - No change
sound/pci/hda/cs35l41_hda.c | 174 ++++++++++++++++++++++++++++++++++-- sound/pci/hda/cs35l41_hda.h | 1 + 2 files changed, 166 insertions(+), 9 deletions(-)
diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c index 0957b4984143..1d62a41fbc75 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -86,13 +86,19 @@ 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 *ssid, const char *amp_name, - const char *filetype) + int spkid, const char *filetype) { const char * const dsp_name = cs35l41->cs_dsp.name; char *s, c; int ret = 0;
- if (ssid && amp_name) + 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); + 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); + 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); @@ -130,6 +136,93 @@ static int cs35l41_request_firmware_file(struct cs35l41_hda *cs35l41, return ret; }
+static int cs35l41_request_firmware_files_spkid(struct cs35l41_hda *cs35l41, + const struct firmware **wmfw_firmware, + char **wmfw_filename, + const struct firmware **coeff_firmware, + char **coeff_filename) +{ + int ret; + + /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, cs35l41->amp_name, + cs35l41->speaker_id, "wmfw"); + if (!ret) { + /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, cs35l41->amp_name, + cs35l41->speaker_id, "bin"); + return 0; + } + + /* 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, -1, "wmfw"); + if (!ret) { + /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + cs35l41->amp_name, cs35l41->speaker_id, "bin"); + return 0; + } + + /* try cirrus/part-dspN-fwtype-sub<-spkidN>.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + NULL, cs35l41->speaker_id, "wmfw"); + if (!ret) { + /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.bin */ + ret = cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, + cs35l41->amp_name, cs35l41->speaker_id, "bin"); + if (ret) + /* try cirrus/part-dspN-fwtype-sub<-spkidN>.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, + NULL, cs35l41->speaker_id, "bin"); + return 0; + } + + /* try cirrus/part-dspN-fwtype-sub.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + NULL, -1, "wmfw"); + if (!ret) { + /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.bin */ + ret = cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, + cs35l41->amp_name, cs35l41->speaker_id, "bin"); + if (ret) + /* try cirrus/part-dspN-fwtype-sub<-spkidN>.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, + NULL, cs35l41->speaker_id, "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, -1, "wmfw"); + if (!ret) { + /* fallback try cirrus/part-dspN-fwtype.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, NULL, NULL, -1, "bin"); + return 0; + } + + dev_warn(cs35l41->dev, "Failed to request firmware\n"); + + return ret; +} + static int cs35l41_request_firmware_files(struct cs35l41_hda *cs35l41, const struct firmware **wmfw_firmware, char **wmfw_filename, @@ -138,43 +231,48 @@ static int cs35l41_request_firmware_files(struct cs35l41_hda *cs35l41, { int ret;
+ if (cs35l41->speaker_id > -1) + return cs35l41_request_firmware_files_spkid(cs35l41, wmfw_firmware, wmfw_filename, + coeff_firmware, coeff_filename); + /* 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"); + cs35l41->amp_name, -1, "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"); + cs35l41->amp_name, -1, "bin"); return 0; }
/* try cirrus/part-dspN-fwtype-sub.wmfw */ ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, - NULL, "wmfw"); + NULL, -1, "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"); + cs35l41->amp_name, -1, "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"); + cs35l41->acpi_subsystem_id, + NULL, -1, "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"); + CS35L41_FIRMWARE_ROOT, NULL, NULL, -1, "wmfw"); if (!ret) { /* fallback try cirrus/part-dspN-fwtype.bin */ cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, - CS35L41_FIRMWARE_ROOT, NULL, NULL, "bin"); + CS35L41_FIRMWARE_ROOT, NULL, NULL, -1, "bin"); return 0; }
@@ -602,6 +700,61 @@ static int cs35l41_get_acpi_sub_string(struct device *dev, struct acpi_device *a return ret; }
+static int cs35l41_get_speaker_id(struct device *dev, int amp_index, + int num_amps, int fixed_gpio_id) +{ + struct gpio_desc *speaker_id_desc; + int speaker_id = -ENODEV; + + if (fixed_gpio_id >= 0) { + dev_dbg(dev, "Found Fixed Speaker ID GPIO (index = %d)\n", fixed_gpio_id); + speaker_id_desc = gpiod_get_index(dev, NULL, fixed_gpio_id, GPIOD_IN); + if (IS_ERR(speaker_id_desc)) { + speaker_id = PTR_ERR(speaker_id_desc); + return speaker_id; + } + speaker_id = gpiod_get_value_cansleep(speaker_id_desc); + gpiod_put(speaker_id_desc); + dev_dbg(dev, "Speaker ID = %d\n", speaker_id); + } else { + int base_index; + int gpios_per_amp; + int count; + int tmp; + int i; + + count = gpiod_count(dev, "spk-id"); + if (count > 0) { + speaker_id = 0; + gpios_per_amp = count / num_amps; + base_index = gpios_per_amp * amp_index; + + if (count % num_amps) + return -EINVAL; + + dev_dbg(dev, "Found %d Speaker ID GPIOs per Amp\n", gpios_per_amp); + + for (i = 0; i < gpios_per_amp; i++) { + speaker_id_desc = gpiod_get_index(dev, "spk-id", i + base_index, + GPIOD_IN); + if (IS_ERR(speaker_id_desc)) { + speaker_id = PTR_ERR(speaker_id_desc); + break; + } + tmp = gpiod_get_value_cansleep(speaker_id_desc); + gpiod_put(speaker_id_desc); + if (tmp < 0) { + speaker_id = tmp; + break; + } + speaker_id |= tmp << i; + } + dev_dbg(dev, "Speaker ID = %d\n", speaker_id); + } + } + return speaker_id; +} + static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, int id) { struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; @@ -707,6 +860,8 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i else hw_cfg->bst_cap = -1;
+ cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, cs35l41->index, nval, -1); + if (hw_cfg->bst_ind > 0 || hw_cfg->bst_cap > 0 || hw_cfg->bst_ipk > 0) hw_cfg->bst_type = CS35L41_INT_BOOST; else @@ -740,6 +895,7 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i cs35l41->channel_index = 0; cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 0, GPIOD_OUT_HIGH); cs35l41->hw_cfg.bst_type = CS35L41_EXT_BOOST_NO_VSPK_SWITCH; + cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, 0, 0, 2); hw_cfg->gpio2.func = CS35L41_GPIO2_INT_OPEN_DRAIN; hw_cfg->gpio2.valid = true; cs35l41->hw_cfg.valid = true; diff --git a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h index b8352088a5cb..717b30a2e33b 100644 --- a/sound/pci/hda/cs35l41_hda.h +++ b/sound/pci/hda/cs35l41_hda.h @@ -43,6 +43,7 @@ struct cs35l41_hda { unsigned volatile long irq_errors; const char *amp_name; const char *acpi_subsystem_id; + int speaker_id; struct regmap_irq_chip_data *irq_data; bool firmware_running; bool halo_initialized;
From: Stefan Binding sbinding@opensource.cirrus.com
CS35L41 HDA Driver will support hibernation using DSP firmware, move the exit hibernate function into shared code so this can be reused.
Acked-by: Charles Keepax ckeepax@opensource.cirrus.com
Signed-off-by: Stefan Binding sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - No change
include/sound/cs35l41.h | 1 + sound/soc/codecs/cs35l41-lib.c | 60 +++++++++++++++++++++++++++++++++ sound/soc/codecs/cs35l41.c | 61 +--------------------------------- 3 files changed, 62 insertions(+), 60 deletions(-)
diff --git a/include/sound/cs35l41.h b/include/sound/cs35l41.h index 8887087815a7..f848ba1e75b3 100644 --- a/include/sound/cs35l41.h +++ b/include/sound/cs35l41.h @@ -885,6 +885,7 @@ void cs35l41_configure_cs_dsp(struct device *dev, struct regmap *reg, struct cs_ int cs35l41_set_cspl_mbox_cmd(struct device *dev, struct regmap *regmap, enum cs35l41_cspl_mbox_cmd cmd); int cs35l41_write_fs_errata(struct device *dev, struct regmap *regmap); +int cs35l41_exit_hibernate(struct device *dev, struct regmap *regmap); int cs35l41_init_boost(struct device *dev, struct regmap *regmap, struct cs35l41_hw_cfg *hw_cfg); bool cs35l41_safe_reset(struct regmap *regmap, enum cs35l41_boost_type b_type); diff --git a/sound/soc/codecs/cs35l41-lib.c b/sound/soc/codecs/cs35l41-lib.c index 6d3070ea9e06..cc5366c8bdd6 100644 --- a/sound/soc/codecs/cs35l41-lib.c +++ b/sound/soc/codecs/cs35l41-lib.c @@ -1321,6 +1321,66 @@ int cs35l41_write_fs_errata(struct device *dev, struct regmap *regmap) } EXPORT_SYMBOL_GPL(cs35l41_write_fs_errata);
+static void cs35l41_wait_for_pwrmgt_sts(struct device *dev, struct regmap *regmap) +{ + const int pwrmgt_retries = 10; + unsigned int sts; + int i, ret; + + for (i = 0; i < pwrmgt_retries; i++) { + ret = regmap_read(regmap, CS35L41_PWRMGT_STS, &sts); + if (ret) + dev_err(dev, "Failed to read PWRMGT_STS: %d\n", ret); + else if (!(sts & CS35L41_WR_PEND_STS_MASK)) + return; + + udelay(20); + } + + dev_err(dev, "Timed out reading PWRMGT_STS\n"); +} + +int cs35l41_exit_hibernate(struct device *dev, struct regmap *regmap) +{ + const int wake_retries = 20; + const int sleep_retries = 5; + int ret, i, j; + + for (i = 0; i < sleep_retries; i++) { + dev_dbg(dev, "Exit hibernate\n"); + + for (j = 0; j < wake_retries; j++) { + ret = cs35l41_set_cspl_mbox_cmd(dev, regmap, + CSPL_MBOX_CMD_OUT_OF_HIBERNATE); + if (!ret) + break; + + usleep_range(100, 200); + } + + if (j < wake_retries) { + dev_dbg(dev, "Wake success at cycle: %d\n", j); + return 0; + } + + dev_err(dev, "Wake failed, re-enter hibernate: %d\n", ret); + + cs35l41_wait_for_pwrmgt_sts(dev, regmap); + regmap_write(regmap, CS35L41_WAKESRC_CTL, 0x0088); + + cs35l41_wait_for_pwrmgt_sts(dev, regmap); + regmap_write(regmap, CS35L41_WAKESRC_CTL, 0x0188); + + cs35l41_wait_for_pwrmgt_sts(dev, regmap); + regmap_write(regmap, CS35L41_PWRMGT_CTL, 0x3); + } + + dev_err(dev, "Timed out waking device\n"); + + return -ETIMEDOUT; +} +EXPORT_SYMBOL_GPL(cs35l41_exit_hibernate); + MODULE_DESCRIPTION("CS35L41 library"); MODULE_AUTHOR("David Rhodes, Cirrus Logic Inc, david.rhodes@cirrus.com"); MODULE_AUTHOR("Lucas Tanure, Cirrus Logic Inc, tanureal@opensource.cirrus.com"); diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c index 3e68a07a3c8e..be7d02517739 100644 --- a/sound/soc/codecs/cs35l41.c +++ b/sound/soc/codecs/cs35l41.c @@ -1351,65 +1351,6 @@ static int __maybe_unused cs35l41_runtime_suspend(struct device *dev) return 0; }
-static void cs35l41_wait_for_pwrmgt_sts(struct cs35l41_private *cs35l41) -{ - const int pwrmgt_retries = 10; - unsigned int sts; - int i, ret; - - for (i = 0; i < pwrmgt_retries; i++) { - ret = regmap_read(cs35l41->regmap, CS35L41_PWRMGT_STS, &sts); - if (ret) - dev_err(cs35l41->dev, "Failed to read PWRMGT_STS: %d\n", ret); - else if (!(sts & CS35L41_WR_PEND_STS_MASK)) - return; - - udelay(20); - } - - dev_err(cs35l41->dev, "Timed out reading PWRMGT_STS\n"); -} - -static int cs35l41_exit_hibernate(struct cs35l41_private *cs35l41) -{ - const int wake_retries = 20; - const int sleep_retries = 5; - int ret, i, j; - - for (i = 0; i < sleep_retries; i++) { - dev_dbg(cs35l41->dev, "Exit hibernate\n"); - - for (j = 0; j < wake_retries; j++) { - ret = cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, - CSPL_MBOX_CMD_OUT_OF_HIBERNATE); - if (!ret) - break; - - usleep_range(100, 200); - } - - if (j < wake_retries) { - dev_dbg(cs35l41->dev, "Wake success at cycle: %d\n", j); - return 0; - } - - dev_err(cs35l41->dev, "Wake failed, re-enter hibernate: %d\n", ret); - - cs35l41_wait_for_pwrmgt_sts(cs35l41); - regmap_write(cs35l41->regmap, CS35L41_WAKESRC_CTL, 0x0088); - - cs35l41_wait_for_pwrmgt_sts(cs35l41); - regmap_write(cs35l41->regmap, CS35L41_WAKESRC_CTL, 0x0188); - - cs35l41_wait_for_pwrmgt_sts(cs35l41); - regmap_write(cs35l41->regmap, CS35L41_PWRMGT_CTL, 0x3); - } - - dev_err(cs35l41->dev, "Timed out waking device\n"); - - return -ETIMEDOUT; -} - static int __maybe_unused cs35l41_runtime_resume(struct device *dev) { struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); @@ -1422,7 +1363,7 @@ static int __maybe_unused cs35l41_runtime_resume(struct device *dev)
regcache_cache_only(cs35l41->regmap, false);
- ret = cs35l41_exit_hibernate(cs35l41); + ret = cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap); if (ret) return ret;
From: Stefan Binding sbinding@opensource.cirrus.com
When waking from hibernation, it is possible for the function which sends the wake command to fail initially, but after a retry it will succeed. There is no need to print an error if the initial attempts fail.
Signed-off-by: Stefan Binding sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - No change
sound/soc/codecs/cs35l41-lib.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/sound/soc/codecs/cs35l41-lib.c b/sound/soc/codecs/cs35l41-lib.c index cc5366c8bdd6..e726a38f1997 100644 --- a/sound/soc/codecs/cs35l41-lib.c +++ b/sound/soc/codecs/cs35l41-lib.c @@ -1302,7 +1302,8 @@ int cs35l41_set_cspl_mbox_cmd(struct device *dev, struct regmap *regmap, return 0; }
- dev_err(dev, "Failed to set mailbox cmd %u (status %u)\n", cmd, sts); + if (cmd != CSPL_MBOX_CMD_OUT_OF_HIBERNATE) + dev_err(dev, "Failed to set mailbox cmd %u (status %u)\n", cmd, sts);
return -ENOMSG; }
On Wed, May 25, 2022 at 02:16:31PM +0100, Vitaly Rodionov wrote:
From: Stefan Binding sbinding@opensource.cirrus.com
When waking from hibernation, it is possible for the function which sends the wake command to fail initially, but after a retry it will succeed. There is no need to print an error if the initial attempts fail.
Signed-off-by: Stefan Binding sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com
Acked-by: Charles Keepax ckeepax@opensource.cirrus.com
Thanks, Charles
From: Stefan Binding sbinding@opensource.cirrus.com
Since the CS35L41 HDA driver also support hibernation, it makes sense to move code from the ASoC driver to enter hibernation into common code.
Since HDA must support laptops which do not support hibernation due to lack of external boost GPIO it is necessary to ensure the function returns an error when an unsupported boost type is in use.
Acked-by: Charles Keepax ckeepax@opensource.cirrus.com
Signed-off-by: Stefan Binding sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - No change
include/sound/cs35l41.h | 2 ++ sound/soc/codecs/cs35l41-lib.c | 19 +++++++++++++++++++ sound/soc/codecs/cs35l41.c | 10 +--------- 3 files changed, 22 insertions(+), 9 deletions(-)
diff --git a/include/sound/cs35l41.h b/include/sound/cs35l41.h index f848ba1e75b3..9ac5918269a5 100644 --- a/include/sound/cs35l41.h +++ b/include/sound/cs35l41.h @@ -885,6 +885,8 @@ void cs35l41_configure_cs_dsp(struct device *dev, struct regmap *reg, struct cs_ int cs35l41_set_cspl_mbox_cmd(struct device *dev, struct regmap *regmap, enum cs35l41_cspl_mbox_cmd cmd); int cs35l41_write_fs_errata(struct device *dev, struct regmap *regmap); +int cs35l41_enter_hibernate(struct device *dev, struct regmap *regmap, + enum cs35l41_boost_type b_type); int cs35l41_exit_hibernate(struct device *dev, struct regmap *regmap); int cs35l41_init_boost(struct device *dev, struct regmap *regmap, struct cs35l41_hw_cfg *hw_cfg); diff --git a/sound/soc/codecs/cs35l41-lib.c b/sound/soc/codecs/cs35l41-lib.c index e726a38f1997..0c7d1c791279 100644 --- a/sound/soc/codecs/cs35l41-lib.c +++ b/sound/soc/codecs/cs35l41-lib.c @@ -1322,6 +1322,25 @@ int cs35l41_write_fs_errata(struct device *dev, struct regmap *regmap) } EXPORT_SYMBOL_GPL(cs35l41_write_fs_errata);
+int cs35l41_enter_hibernate(struct device *dev, struct regmap *regmap, + enum cs35l41_boost_type b_type) +{ + if (!cs35l41_safe_reset(regmap, b_type)) { + dev_dbg(dev, "System does not support Suspend\n"); + return -EINVAL; + } + + dev_dbg(dev, "Enter hibernate\n"); + regmap_write(regmap, CS35L41_WAKESRC_CTL, 0x0088); + regmap_write(regmap, CS35L41_WAKESRC_CTL, 0x0188); + + // Don't wait for ACK since bus activity would wake the device + regmap_write(regmap, CS35L41_DSP_VIRT1_MBOX_1, CSPL_MBOX_CMD_HIBERNATE); + + return 0; +} +EXPORT_SYMBOL_GPL(cs35l41_enter_hibernate); + static void cs35l41_wait_for_pwrmgt_sts(struct device *dev, struct regmap *regmap) { const int pwrmgt_retries = 10; diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c index be7d02517739..a115ea35b92d 100644 --- a/sound/soc/codecs/cs35l41.c +++ b/sound/soc/codecs/cs35l41.c @@ -1335,15 +1335,7 @@ static int __maybe_unused cs35l41_runtime_suspend(struct device *dev) if (!cs35l41->dsp.preloaded || !cs35l41->dsp.cs_dsp.running) return 0;
- dev_dbg(cs35l41->dev, "Enter hibernate\n"); - - cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type); - regmap_write(cs35l41->regmap, CS35L41_WAKESRC_CTL, 0x0088); - regmap_write(cs35l41->regmap, CS35L41_WAKESRC_CTL, 0x0188); - - // Don't wait for ACK since bus activity would wake the device - regmap_write(cs35l41->regmap, CS35L41_DSP_VIRT1_MBOX_1, - CSPL_MBOX_CMD_HIBERNATE); + cs35l41_enter_hibernate(dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type);
regcache_cache_only(cs35l41->regmap, true); regcache_mark_dirty(cs35l41->regmap);
From: Stefan Binding sbinding@opensource.cirrus.com
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 sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - No change
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 1d62a41fbc75..9c622104bf01 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" @@ -425,6 +426,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; @@ -482,17 +552,25 @@ 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;
if (cs35l41_smart_amp(cs35l41) < 0) dev_warn(cs35l41->dev, "Cannot Run Firmware, reverting to dsp bypass...\n");
+ 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; }
@@ -588,7 +666,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, @@ -596,6 +674,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) @@ -1003,13 +1082,23 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i if (ret) goto err;
+ 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; }
@@ -1017,6 +1106,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); @@ -1030,17 +1123,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->firmware_running) 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, tanureal@opensource.cirrus.com"); diff --git a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h index 717b30a2e33b..54521a013e78 100644 --- a/sound/pci/hda/cs35l41_hda.h +++ b/sound/pci/hda/cs35l41_hda.h @@ -56,6 +56,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 b8249c4dcb1d..fc2467d03b8a 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -4006,15 +4006,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); @@ -4045,6 +4052,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 */ @@ -6587,8 +6598,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)
From: Stefan Binding sbinding@opensource.cirrus.com
Speaker Calibration data, specific to an individual speaker is stored inside UEFI variables during calibration, and can be used by the DSP.
Signed-off-by: Stefan Binding sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - Fixed building without CONFIG_EFI
Changes since v3: - Apply all 4 calibration parameters after fw download
sound/pci/hda/cs35l41_hda.c | 63 +++++++++++++++++++++++++++++++++++++ sound/pci/hda/cs35l41_hda.h | 15 +++++++++ 2 files changed, 78 insertions(+)
diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c index 9c622104bf01..b4087d548646 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -26,6 +26,12 @@ #define HALO_STATE_DSP_CTL_NAME "HALO_STATE" #define HALO_STATE_DSP_CTL_TYPE 5 #define HALO_STATE_DSP_CTL_ALG 262308 +#define CAL_R_DSP_CTL_NAME "CAL_R" +#define CAL_STATUS_DSP_CTL_NAME "CAL_STATUS" +#define CAL_CHECKSUM_DSP_CTL_NAME "CAL_CHECKSUM" +#define CAL_AMBIENT_DSP_CTL_NAME "CAL_AMBIENT" +#define CAL_DSP_CTL_TYPE 5 +#define CAL_DSP_CTL_ALG 205
static const struct reg_sequence cs35l41_hda_config[] = { { CS35L41_PLL_CLK_CTRL, 0x00000430 }, // 3072000Hz, BCLK Input, PLL_REFCLK_EN = 1 @@ -282,6 +288,96 @@ static int cs35l41_request_firmware_files(struct cs35l41_hda *cs35l41, return ret; }
+#if IS_ENABLED(CONFIG_EFI) +static int cs35l41_apply_calibration(struct cs35l41_hda *cs35l41, unsigned int ambient, + unsigned int r0, unsigned int status, unsigned int checksum) +{ + int ret; + + ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_AMBIENT_DSP_CTL_NAME, CAL_DSP_CTL_TYPE, + CAL_DSP_CTL_ALG, &ambient, 4); + if (ret) { + dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", CAL_AMBIENT_DSP_CTL_NAME, + ret); + return ret; + } + ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_R_DSP_CTL_NAME, CAL_DSP_CTL_TYPE, + CAL_DSP_CTL_ALG, &r0, 4); + if (ret) { + dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", CAL_R_DSP_CTL_NAME, ret); + return ret; + } + ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_STATUS_DSP_CTL_NAME, CAL_DSP_CTL_TYPE, + CAL_DSP_CTL_ALG, &status, 4); + if (ret) { + dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", CAL_STATUS_DSP_CTL_NAME, + ret); + return ret; + } + ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_CHECKSUM_DSP_CTL_NAME, CAL_DSP_CTL_TYPE, + CAL_DSP_CTL_ALG, &checksum, 4); + if (ret) { + dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", CAL_CHECKSUM_DSP_CTL_NAME, + ret); + return ret; + } + + return 0; +} + +static int cs35l41_save_calibration(struct cs35l41_hda *cs35l41) +{ + static efi_guid_t efi_guid = EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, + 0x5a, 0xa3, 0x5d, 0xb3); + static efi_char16_t efi_name[] = L"CirrusSmartAmpCalibrationData"; + const struct cs35l41_amp_efi_data *efi_data; + const struct cs35l41_amp_cal_data *cl; + unsigned long data_size = 0; + efi_status_t status; + int ret = 0; + u8 *data = NULL; + u32 attr; + + /* Get real size of UEFI variable */ + status = efi.get_variable(efi_name, &efi_guid, &attr, &data_size, data); + if (status == EFI_BUFFER_TOO_SMALL) { + ret = -ENODEV; + /* Allocate data buffer of data_size bytes */ + data = vmalloc(data_size); + if (!data) + return -ENOMEM; + /* Get variable contents into buffer */ + status = efi.get_variable(efi_name, &efi_guid, &attr, &data_size, data); + if (status == EFI_SUCCESS) { + efi_data = (struct cs35l41_amp_efi_data *)data; + dev_dbg(cs35l41->dev, "Calibration: Size=%d, Amp Count=%d\n", + efi_data->size, efi_data->count); + if (efi_data->count > cs35l41->index) { + cl = &efi_data->data[cs35l41->index]; + dev_dbg(cs35l41->dev, + "Calibration: Ambient=%02x, Status=%02x, R0=%d\n", + cl->calAmbient, cl->calStatus, cl->calR); + + /* Calibration can only be applied whilst the DSP is not running */ + ret = cs35l41_apply_calibration(cs35l41, + cpu_to_be32(cl->calAmbient), + cpu_to_be32(cl->calR), + cpu_to_be32(cl->calStatus), + cpu_to_be32(cl->calR + 1)); + } + } + vfree(data); + } + return ret; +} +#else +static int cs35l41_save_calibration(struct cs35l41_hda *cs35l41) +{ + dev_warn(cs35l41->dev, "Calibration not supported without EFI support.\n"); + return 0; +} +#endif + static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41) { const struct firmware *coeff_firmware = NULL; @@ -314,7 +410,12 @@ static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41)
ret = cs_dsp_power_up(dsp, wmfw_firmware, wmfw_filename, coeff_firmware, coeff_filename, FW_NAME); + if (ret) + goto err_release; + + ret = cs35l41_save_calibration(cs35l41);
+err_release: if (wmfw_firmware) release_firmware(wmfw_firmware); if (coeff_firmware) diff --git a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h index 54521a013e78..3cf9871fbed2 100644 --- a/sound/pci/hda/cs35l41_hda.h +++ b/sound/pci/hda/cs35l41_hda.h @@ -10,6 +10,7 @@ #ifndef __CS35L41_HDA_H__ #define __CS35L41_HDA_H__
+#include <linux/efi.h> #include <linux/regulator/consumer.h> #include <linux/gpio/consumer.h> #include <linux/device.h> @@ -18,6 +19,20 @@ #include <linux/firmware/cirrus/cs_dsp.h> #include <linux/firmware/cirrus/wmfw.h>
+struct cs35l41_amp_cal_data { + u32 calTarget[2]; + u32 calTime[2]; + s8 calAmbient; + u8 calStatus; + u16 calR; +} __packed; + +struct cs35l41_amp_efi_data { + u32 size; + u32 count; + struct cs35l41_amp_cal_data data[]; +} __packed; + enum cs35l41_hda_spk_pos { CS35l41_LEFT, CS35l41_RIGHT,
From: Stefan Binding sbinding@opensource.cirrus.com
This will be used to define the firmware names.
Signed-off-by: Stefan Binding sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - No change
sound/pci/hda/hda_cs_dsp_ctl.c | 8 ++++++++ sound/pci/hda/hda_cs_dsp_ctl.h | 2 ++ 2 files changed, 10 insertions(+)
diff --git a/sound/pci/hda/hda_cs_dsp_ctl.c b/sound/pci/hda/hda_cs_dsp_ctl.c index 3b837d000a00..4cf5f7c2e267 100644 --- a/sound/pci/hda/hda_cs_dsp_ctl.c +++ b/sound/pci/hda/hda_cs_dsp_ctl.c @@ -30,6 +30,14 @@ static const char * const hda_cs_dsp_fw_text[HDA_CS_DSP_NUM_FW] = { [HDA_CS_DSP_FW_MISC] = "Misc", };
+const char * const hda_cs_dsp_fw_ids[HDA_CS_DSP_NUM_FW] = { + [HDA_CS_DSP_FW_SPK_PROT] = "spk-prot", + [HDA_CS_DSP_FW_SPK_CALI] = "spk-cali", + [HDA_CS_DSP_FW_SPK_DIAG] = "spk-diag", + [HDA_CS_DSP_FW_MISC] = "misc", +}; +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_fw_ids, SND_HDA_CS_DSP_CONTROLS); + static inline struct hda_cs_dsp_coeff_ctl *bytes_ext_to_ctl(struct soc_bytes_ext *ext) { return container_of(ext, struct hda_cs_dsp_coeff_ctl, bytes_ext); diff --git a/sound/pci/hda/hda_cs_dsp_ctl.h b/sound/pci/hda/hda_cs_dsp_ctl.h index 65b9c5c68957..265d8024eec9 100644 --- a/sound/pci/hda/hda_cs_dsp_ctl.h +++ b/sound/pci/hda/hda_cs_dsp_ctl.h @@ -27,6 +27,8 @@ struct hda_cs_dsp_ctl_info { const char *amp_name; };
+extern const char * const hda_cs_dsp_fw_ids[HDA_CS_DSP_NUM_FW]; + int hda_cs_dsp_control_add(struct cs_dsp_coeff_ctl *cs_ctl, struct hda_cs_dsp_ctl_info *info); void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl); int hda_cs_dsp_remove_kcontrol(struct snd_card *card, const char *name);
From: Stefan Binding sbinding@opensource.cirrus.com
The config sequences for running with and without firmware and DSP are different. The original behavior assumed that we would only run without DSP only in the case where firmware load failed. This meant the non-firmware sequence was written with the assumtion that various registers would be set to their default value. However, to support the ability to unload the firmware, the non-firmware register sequence must be updated to update all required registers, including values that would be defaulted, in case the firmware sequence, which could have already run, has changed their value.
Signed-off-by: Stefan Binding sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - No change
sound/pci/hda/cs35l41_hda.c | 13 +++++++++++++ 1 file changed, 13 insertions(+)
diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c index b4087d548646..8de956e6850c 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -35,11 +35,24 @@
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 { CS35L41_GLOBAL_CLK_CTRL, 0x00000003 }, // GLOBAL_FS = 48 kHz { CS35L41_SP_ENABLES, 0x00010000 }, // ASP_RX1_EN = 1 { CS35L41_SP_RATE_CTRL, 0x00000021 }, // ASP_BCLK_FREQ = 3.072 MHz { CS35L41_SP_FORMAT, 0x20200200 }, // 32 bits RX/TX slots, I2S, clk consumer + { CS35L41_SP_HIZ_CTRL, 0x00000002 }, // Hi-Z unused + { CS35L41_SP_TX_WL, 0x00000018 }, // 24 cycles/slot + { CS35L41_SP_RX_WL, 0x00000018 }, // 24 cycles/slot { CS35L41_DAC_PCM1_SRC, 0x00000008 }, // DACPCM1_SRC = ASPRX1 + { CS35L41_ASP_TX1_SRC, 0x00000018 }, // ASPTX1 SRC = VMON + { CS35L41_ASP_TX2_SRC, 0x00000019 }, // ASPTX2 SRC = IMON + { CS35L41_ASP_TX3_SRC, 0x00000032 }, // ASPTX3 SRC = ERRVOL + { CS35L41_ASP_TX4_SRC, 0x00000033 }, // ASPTX4 SRC = CLASSH_TGT + { CS35L41_DSP1_RX1_SRC, 0x00000008 }, // DSP1RX1 SRC = ASPRX1 + { CS35L41_DSP1_RX2_SRC, 0x00000009 }, // DSP1RX2 SRC = ASPRX2 + { CS35L41_DSP1_RX3_SRC, 0x00000018 }, // DSP1RX3 SRC = VMON + { CS35L41_DSP1_RX4_SRC, 0x00000019 }, // DSP1RX4 SRC = IMON + { CS35L41_DSP1_RX5_SRC, 0x00000020 }, // DSP1RX5 SRC = ERRVOL { CS35L41_AMP_DIG_VOL_CTRL, 0x00000000 }, // AMP_VOL_PCM 0.0 dB { CS35L41_AMP_GAIN_CTRL, 0x00000084 }, // AMP_GAIN_PCM 4.5 dB };
From: Stefan Binding sbinding@opensource.cirrus.com
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"
Signed-off-by: Stefan Binding sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - No change
sound/pci/hda/cs35l41_hda.c | 163 ++++++++++++++++++++++++++++++++++-- sound/pci/hda/cs35l41_hda.h | 5 ++ 2 files changed, 161 insertions(+), 7 deletions(-)
diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c index 8de956e6850c..4a164b7ebab1 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -92,7 +92,7 @@ static int cs35l41_control_add(struct cs_dsp_coeff_ctl *cs_ctl) struct hda_cs_dsp_ctl_info info;
info.amp_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 +114,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; @@ -471,8 +475,11 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action) struct regmap *reg = cs35l41->regmap; int ret = 0;
+ mutex_lock(&cs35l41->fw_mutex); + 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)); @@ -510,12 +517,15 @@ 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); break; }
+ mutex_unlock(&cs35l41->fw_mutex); + if (ret) dev_err(cs35l41->dev, "Regmap access fail: %d\n", ret); } @@ -654,6 +664,136 @@ 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_remove_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 int cs35l41_fw_load_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol); + int ret = 0; + + mutex_lock(&cs35l41->fw_mutex); + if (cs35l41->request_fw_load != ucontrol->value.integer.value[0]) { + if (cs35l41->playback_started) { + dev_err(cs35l41->dev, "Cannot Load/Unload firmware during Playback\n"); + ret = -EBUSY; + } else { + cs35l41->request_fw_load = ucontrol->value.integer.value[0]; + cs35l41_load_firmware(cs35l41, ucontrol->value.integer.value[0]); + } + } + + 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_MIXER, + .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_MIXER, + .info = snd_ctl_boolean_mono_info, + .get = cs35l41_fw_load_ctl_get, + .put = cs35l41_fw_load_ctl_put, + }; + 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; + } + + if (snd_ctl_add(cs35l41->codec->card, snd_ctl_new1(&fw_type_ctl, cs35l41))) { + 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); + + if (snd_ctl_add(cs35l41->codec->card, snd_ctl_new1(&fw_load_ctl, cs35l41))) { + ret = -ENODEV; + dev_err(cs35l41->dev, "Failed to add KControl: %s, removing all controls\n", + fw_load_ctl.name); + hda_cs_dsp_remove_kcontrol(cs35l41->codec->card, fw_type_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); @@ -675,8 +815,15 @@ 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); + + cs35l41_create_controls(cs35l41);
comps->playback_hook = cs35l41_hda_playback_hook; comps->suspend_hook = cs35l41_hda_suspend_hook; @@ -1196,6 +1343,8 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i if (ret) goto err;
+ 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); diff --git a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h index 3cf9871fbed2..19f0585d12db 100644 --- a/sound/pci/hda/cs35l41_hda.h +++ b/sound/pci/hda/cs35l41_hda.h @@ -58,10 +58,15 @@ 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 regmap_irq_chip_data *irq_data; bool firmware_running; + bool request_fw_load; bool halo_initialized; + bool playback_started; struct cs_dsp cs_dsp; };
On 25. 05. 22 15:16, Vitaly Rodionov wrote:
This patch adds 2 ALSA Controls for each amp: "DSP1 Firmware Load" "DSP1 Firmware Type"
...
- struct snd_kcontrol_new fw_type_ctl = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.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_MIXER,
.info = snd_ctl_boolean_mono_info,
.get = cs35l41_fw_load_ctl_get,
.put = cs35l41_fw_load_ctl_put,
- };
I don't think that those controls should be SNDRV_CTL_ELEM_IFACE_MIXER type. The SNDRV_CTL_ELEM_IFACE_CARD seems more appropriate (service controls for the sound card).
Jaroslav
From: Stefan Binding sbinding@opensource.cirrus.com
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 sbinding@opensource.cirrus.com Signed-off-by: Vitaly Rodionov vitalyr@opensource.cirrus.com ---
Changes since v2: - Added module paramter to control firmware loading
sound/pci/hda/cs35l41_hda.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c index 4a164b7ebab1..c836323a8aaf 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -33,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 @@ -817,11 +822,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"); + }
cs35l41_create_controls(cs35l41);
On Wed, 25 May 2022 15:16:21 +0200, Vitaly Rodionov wrote:
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.
The idea to add / delete controls by the control element change doesn't sound good; as already mentioned in my reply to the previous patch set, the change of the control elements can be triggered too easily by any normal users who have the access to the sound devices. It means thousands of additions and removals per second could be attacked by any user.
Moreover, the new controls with TLV controls don't look following the standard TLV syntax (type-length-value). My previous questions about the TLV usages are still unanswered, so I'm not sure what impact this would have, though. At least, alsactl behavior must be checked beforehand. If this is really device-specific (non-)TLV usages, it has to be clearly documented.
I don't mean fully against such a TLV usage, *IFF* the same pattern has been already used in ASoC side. In that case, we may need to introduce some PCM info flag to indicate a non-standard TLV usage (but it's a bit different story).
OTOH, the too easily triggered control addition/removal is likely no-go, as this could be a cause of DoS-like attacks. If we must to in this direction, it has to be verified and clarified.
thanks,
Takashi
On Fri, May 27, 2022 at 06:13:38PM +0200, Takashi Iwai wrote:
On Wed, 25 May 2022 15:16:21 +0200, Vitaly Rodionov wrote: The idea to add / delete controls by the control element change doesn't sound good; as already mentioned in my reply to the previous patch set, the change of the control elements can be triggered too easily by any normal users who have the access to the sound devices. It means thousands of additions and removals per second could be attacked by any user.
This I am a little less sure how we handle. I mean arn't there already a few ways to do this? Both the existing ASoC wm_adsp stuff, and the topology stuff (used on all new Intel platforms, so very widely deployed) let you create controls by loading a firmware file. Also within ALSA itself can't user-space create user ALSA controls? Is there some rate limiting on that? How is this issue tackled there?
Moreover, the new controls with TLV controls don't look following the standard TLV syntax (type-length-value). My previous questions about the TLV usages are still unanswered, so I'm not sure what impact this would have, though. At least, alsactl behavior must be checked beforehand. If this is really device-specific (non-)TLV usages, it has to be clearly documented.
The TLV stuff should be completely removed once my latest comments have been updated. I don't think we need this for the amps and I would also rather keep the usage to a minimum until one of us finally gets around to resolving the large control issues in a way that is more acceptable to everyone (likely with a new IOCTL).
Thanks, Charles
On Mon, 30 May 2022 11:08:46 +0200, Charles Keepax wrote:
On Fri, May 27, 2022 at 06:13:38PM +0200, Takashi Iwai wrote:
On Wed, 25 May 2022 15:16:21 +0200, Vitaly Rodionov wrote: The idea to add / delete controls by the control element change doesn't sound good; as already mentioned in my reply to the previous patch set, the change of the control elements can be triggered too easily by any normal users who have the access to the sound devices. It means thousands of additions and removals per second could be attacked by any user.
This I am a little less sure how we handle. I mean arn't there already a few ways to do this? Both the existing ASoC wm_adsp stuff, and the topology stuff (used on all new Intel platforms, so very widely deployed) let you create controls by loading a firmware file. Also within ALSA itself can't user-space create user ALSA controls? Is there some rate limiting on that? How is this issue tackled there?
The creation of kctls via firmware loading would be OK, as the code path can't be triggered so frequently. Is it the case for this patch set? There was too little information about the implementation (and more importantly, how to use the controls), so it's hard to judge...
And yes, a rate-limit could be implemented for the user controls. Or, even the hard upper limit for number of additions/deletions per process could be set, I suppose. (Currently only the total number of ctl elements are managed.)
Moreover, the new controls with TLV controls don't look following the standard TLV syntax (type-length-value). My previous questions about the TLV usages are still unanswered, so I'm not sure what impact this would have, though. At least, alsactl behavior must be checked beforehand. If this is really device-specific (non-)TLV usages, it has to be clearly documented.
The TLV stuff should be completely removed once my latest comments have been updated. I don't think we need this for the amps and I would also rather keep the usage to a minimum until one of us finally gets around to resolving the large control issues in a way that is more acceptable to everyone (likely with a new IOCTL).
It'll be great if the complexity could be reduced.
thanks,
Takashi
On Mon, May 30, 2022 at 11:18:43AM +0200, Takashi Iwai wrote:
On Mon, 30 May 2022 11:08:46 +0200, Charles Keepax wrote:
On Fri, May 27, 2022 at 06:13:38PM +0200, Takashi Iwai wrote:
On Wed, 25 May 2022 15:16:21 +0200, Vitaly Rodionov wrote: The idea to add / delete controls by the control element change doesn't sound good; as already mentioned in my reply to the previous patch set, the change of the control elements can be triggered too easily by any normal users who have the access to the sound devices. It means thousands of additions and removals per second could be attacked by any user.
This I am a little less sure how we handle. I mean arn't there already a few ways to do this? Both the existing ASoC wm_adsp stuff, and the topology stuff (used on all new Intel platforms, so very widely deployed) let you create controls by loading a firmware file. Also within ALSA itself can't user-space create user ALSA controls? Is there some rate limiting on that? How is this issue tackled there?
The creation of kctls via firmware loading would be OK, as the code path can't be triggered so frequently. Is it the case for this patch set? There was too little information about the implementation (and more importantly, how to use the controls), so it's hard to judge...
Yeah that should be what is happening here. Although it looks like this code might be removing all the controls if the firmware is unloaded. I will discuss that with the guys, we normal just disable the controls on the wm_adsp stuff.
Thanks, Charles
On Mon, 30 May 2022 11:36:39 +0200, Charles Keepax wrote:
On Mon, May 30, 2022 at 11:18:43AM +0200, Takashi Iwai wrote:
On Mon, 30 May 2022 11:08:46 +0200, Charles Keepax wrote:
On Fri, May 27, 2022 at 06:13:38PM +0200, Takashi Iwai wrote:
On Wed, 25 May 2022 15:16:21 +0200, Vitaly Rodionov wrote: The idea to add / delete controls by the control element change doesn't sound good; as already mentioned in my reply to the previous patch set, the change of the control elements can be triggered too easily by any normal users who have the access to the sound devices. It means thousands of additions and removals per second could be attacked by any user.
This I am a little less sure how we handle. I mean arn't there already a few ways to do this? Both the existing ASoC wm_adsp stuff, and the topology stuff (used on all new Intel platforms, so very widely deployed) let you create controls by loading a firmware file. Also within ALSA itself can't user-space create user ALSA controls? Is there some rate limiting on that? How is this issue tackled there?
The creation of kctls via firmware loading would be OK, as the code path can't be triggered so frequently. Is it the case for this patch set? There was too little information about the implementation (and more importantly, how to use the controls), so it's hard to judge...
Yeah that should be what is happening here. Although it looks like this code might be removing all the controls if the firmware is unloaded. I will discuss that with the guys, we normal just disable the controls on the wm_adsp stuff.
OK, that sounds good. Basically my concern came up from the code snippet doing asynchronous addition/removal via work. This showed some yellow signal, as such a pattern doesn't appear in the normal implementation. If this is (still) really necessary, it has to be clarified as an exception.
thanks,
Takashi
On Mon, May 30, 2022 at 12:14:26PM +0200, Takashi Iwai wrote:
On Mon, 30 May 2022 11:36:39 +0200, Charles Keepax wrote:
On Mon, May 30, 2022 at 11:18:43AM +0200, Takashi Iwai wrote:
On Mon, 30 May 2022 11:08:46 +0200, Charles Keepax wrote:
On Fri, May 27, 2022 at 06:13:38PM +0200, Takashi Iwai wrote:
On Wed, 25 May 2022 15:16:21 +0200, Vitaly Rodionov wrote:
Yeah that should be what is happening here. Although it looks like this code might be removing all the controls if the firmware is unloaded. I will discuss that with the guys, we normal just disable the controls on the wm_adsp stuff.
OK, that sounds good. Basically my concern came up from the code snippet doing asynchronous addition/removal via work. This showed some yellow signal, as such a pattern doesn't appear in the normal implementation. If this is (still) really necessary, it has to be clarified as an exception.
Hm... ok we will think about that. I think that part will probably still be necessary. Because there is an ALSA control that selects the firmware, then it is necesarry to defer creating the controls to some work, since you are already holding the lock.
I guess we could look at adding locked versions of the add control functions as well if that might be preferred?
Thanks, Charles
On Mon, 30 May 2022 12:34:15 +0200, Charles Keepax wrote:
On Mon, May 30, 2022 at 12:14:26PM +0200, Takashi Iwai wrote:
On Mon, 30 May 2022 11:36:39 +0200, Charles Keepax wrote:
On Mon, May 30, 2022 at 11:18:43AM +0200, Takashi Iwai wrote:
On Mon, 30 May 2022 11:08:46 +0200, Charles Keepax wrote:
On Fri, May 27, 2022 at 06:13:38PM +0200, Takashi Iwai wrote:
On Wed, 25 May 2022 15:16:21 +0200, Vitaly Rodionov wrote:
Yeah that should be what is happening here. Although it looks like this code might be removing all the controls if the firmware is unloaded. I will discuss that with the guys, we normal just disable the controls on the wm_adsp stuff.
OK, that sounds good. Basically my concern came up from the code snippet doing asynchronous addition/removal via work. This showed some yellow signal, as such a pattern doesn't appear in the normal implementation. If this is (still) really necessary, it has to be clarified as an exception.
Hm... ok we will think about that. I think that part will probably still be necessary. Because there is an ALSA control that selects the firmware, then it is necesarry to defer creating the controls to some work, since you are already holding the lock.
Well, if an ALSA control can trigger the firmware loading, that's already fragile. A firmware loading is a heavy task, which should happen only at probing and/or resuming in general. Do we have other drivers doing the f/w loading triggered by a kctl...?
I guess we could look at adding locked versions of the add control functions as well if that might be preferred?
If the patterns of additional kctls (specific for firmware?) are fixed, we may create all such kctls beforehand and let them inactive unless the corresponding firmware is really loaded, too.
thanks,
Takashi
On Mon, May 30, 2022 at 12:45:08PM +0200, Takashi Iwai wrote:
On Mon, 30 May 2022 12:34:15 +0200, Charles Keepax wrote: Well, if an ALSA control can trigger the firmware loading, that's already fragile. A firmware loading is a heavy task, which should happen only at probing and/or resuming in general. Do we have other drivers doing the f/w loading triggered by a kctl...?
I guess we could look at adding locked versions of the add control functions as well if that might be preferred?
If the patterns of additional kctls (specific for firmware?) are fixed, we may create all such kctls beforehand and let them inactive unless the corresponding firmware is really loaded, too.
I am afraid we do, basically all the Wolfson/Cirrus audio devices allow you to select the firmware through a kctl. The patterns of controls are specific to the firmwares, so we can't really create them ahead of time. One could maybe look at changing when the firmwares are loaded, such as attempting to load all possible firmwares on boot or something but its a fairly sizable change that isn't without some side effects.
Thanks, Charles
On Mon, 30 May 2022 12:53:29 +0200, Charles Keepax wrote:
On Mon, May 30, 2022 at 12:45:08PM +0200, Takashi Iwai wrote:
On Mon, 30 May 2022 12:34:15 +0200, Charles Keepax wrote: Well, if an ALSA control can trigger the firmware loading, that's already fragile. A firmware loading is a heavy task, which should happen only at probing and/or resuming in general. Do we have other drivers doing the f/w loading triggered by a kctl...?
I guess we could look at adding locked versions of the add control functions as well if that might be preferred?
If the patterns of additional kctls (specific for firmware?) are fixed, we may create all such kctls beforehand and let them inactive unless the corresponding firmware is really loaded, too.
I am afraid we do, basically all the Wolfson/Cirrus audio devices allow you to select the firmware through a kctl. The patterns of controls are specific to the firmwares, so we can't really create them ahead of time. One could maybe look at changing when the firmwares are loaded, such as attempting to load all possible firmwares on boot or something but its a fairly sizable change that isn't without some side effects.
The call of request_firmawre() itself can be pretty lengthy (e.g. it may hold until user-space process uploads the firmware if the fallback mode is enabled), and it implies that the request_firmware() call doesn't fit well as the operation to be done in a kctl put callback. So, even if we accept the firmware loading behavior via kctl as-is, the whole procedure should be async in work instead; namely, not only kctl creation/deletion but both request_firmware() + post-process should be done in the work.
And yet moreover, we'll need to consider some way for protecting against DoS-like behavior by frequent kctl changes.
thanks,
Takashi
On 30. 05. 22 13:07, Takashi Iwai wrote:
And yet moreover, we'll need to consider some way for protecting against DoS-like behavior by frequent kctl changes.
I agree, but only the driver knows details about the kctl operation time and resource constraints. So the driver should implement a rate or i/o limit for those controls. We may offer helper functions in the ALSA core for this job (if required).
Jaroslav
On 30/05/2022 11:45, Takashi Iwai wrote:
On Mon, 30 May 2022 12:34:15 +0200, Charles Keepax wrote:
On Mon, May 30, 2022 at 12:14:26PM +0200, Takashi Iwai wrote:
On Mon, 30 May 2022 11:36:39 +0200, Charles Keepax wrote:
On Mon, May 30, 2022 at 11:18:43AM +0200, Takashi Iwai wrote:
On Mon, 30 May 2022 11:08:46 +0200, Charles Keepax wrote:
On Fri, May 27, 2022 at 06:13:38PM +0200, Takashi Iwai wrote: > On Wed, 25 May 2022 15:16:21 +0200, > Vitaly Rodionov wrote:
Yeah that should be what is happening here. Although it looks like this code might be removing all the controls if the firmware is unloaded. I will discuss that with the guys, we normal just disable the controls on the wm_adsp stuff.
OK, that sounds good. Basically my concern came up from the code snippet doing asynchronous addition/removal via work. This showed some yellow signal, as such a pattern doesn't appear in the normal implementation. If this is (still) really necessary, it has to be clarified as an exception.
Hm... ok we will think about that. I think that part will probably still be necessary. Because there is an ALSA control that selects the firmware, then it is necesarry to defer creating the controls to some work, since you are already holding the lock.
Well, if an ALSA control can trigger the firmware loading, that's already fragile. A firmware loading is a heavy task, which should happen only at probing and/or resuming in general. Do we have other drivers doing the f/w loading triggered by a kctl...?
On Wolfson/Cirrus codecs the firmware isn't to "make the chip work". The DSP is programmable to allow for additional audio processing algorithms. Which algorithm you need depends on the audio use case(s) you are running, and can change as you change use-case. Many of the codecs don't have enough DSP memory to hold all possible algorithms. Which is why the firmware load has always been triggered from ALSA controls in the ASoC code. It's not something that can be loaded once in probe().
On Wed, 01 Jun 2022 18:43:31 +0200, Richard Fitzgerald wrote:
On 30/05/2022 11:45, Takashi Iwai wrote:
On Mon, 30 May 2022 12:34:15 +0200, Charles Keepax wrote:
On Mon, May 30, 2022 at 12:14:26PM +0200, Takashi Iwai wrote:
On Mon, 30 May 2022 11:36:39 +0200, Charles Keepax wrote:
On Mon, May 30, 2022 at 11:18:43AM +0200, Takashi Iwai wrote:
On Mon, 30 May 2022 11:08:46 +0200, Charles Keepax wrote: > On Fri, May 27, 2022 at 06:13:38PM +0200, Takashi Iwai wrote: >> On Wed, 25 May 2022 15:16:21 +0200, >> Vitaly Rodionov wrote:
Yeah that should be what is happening here. Although it looks like this code might be removing all the controls if the firmware is unloaded. I will discuss that with the guys, we normal just disable the controls on the wm_adsp stuff.
OK, that sounds good. Basically my concern came up from the code snippet doing asynchronous addition/removal via work. This showed some yellow signal, as such a pattern doesn't appear in the normal implementation. If this is (still) really necessary, it has to be clarified as an exception.
Hm... ok we will think about that. I think that part will probably still be necessary. Because there is an ALSA control that selects the firmware, then it is necesarry to defer creating the controls to some work, since you are already holding the lock.
Well, if an ALSA control can trigger the firmware loading, that's already fragile. A firmware loading is a heavy task, which should happen only at probing and/or resuming in general. Do we have other drivers doing the f/w loading triggered by a kctl...?
On Wolfson/Cirrus codecs the firmware isn't to "make the chip work". The DSP is programmable to allow for additional audio processing algorithms. Which algorithm you need depends on the audio use case(s) you are running, and can change as you change use-case. Many of the codecs don't have enough DSP memory to hold all possible algorithms. Which is why the firmware load has always been triggered from ALSA controls in the ASoC code. It's not something that can be loaded once in probe().
But it's still a question whether such an easily triggerable interface should be used for a heavy procedure like the DSP firmware load. e.g. calling the standard request_firmware() API directly from a control callback doesn't sound like a good idea at all. The similar argument is applied to the dynamic addition/removal of other controls followed by a kctl callback.
thanks,
Takashi
On Wed, 25 May 2022 14:16:21 +0100, Vitaly Rodionov wrote:
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.
[...]
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
Thanks!
[09/17] ASoC: cs35l41: Move cs35l41 exit hibernate function into shared code commit: 94e0bc317ad241c022a6bb311b3a28b4d51ea8b6 [10/17] ASoC: cs35l41: Do not print error when waking from hibernation commit: 97076475e2fdf471348b9ce73215cdbceeb4390f [11/17] ASoC: cs35l41: Add common cs35l41 enter hibernate function commit: e341efc308e5374ded6b471f9e1ec01450bcc93e
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
participants (6)
-
Charles Keepax
-
Jaroslav Kysela
-
Mark Brown
-
Richard Fitzgerald
-
Takashi Iwai
-
Vitaly Rodionov