[PATCH v3 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 | 815 +++++++++++++++++++++++++++++++- 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, 1324 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 11ed24f1f9bb..0c1f6817d8a3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4729,6 +4729,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 Thu, 19 May 2022 19:47:33 +0200, Vitaly Rodionov wrote: On Thu, 19 May 2022 19:47:33 +0200, 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.
Well, I still don't understand why the addition/deletion itself has to be in a work. As far as I see, it's simple calls of snd_ctl_add() and snd_ctl_remove_id().
And, if the problem is that you're calling snd_ctl_add() from another control callback, it's rather the problem of the caller's side, not here. IOW, the async implementation should be rather in the caller side.
thanks,
Takashi
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 11ed24f1f9bb..0c1f6817d8a3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4729,6 +4729,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__*/
2.34.1
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 11ed24f1f9bb..0c1f6817d8a3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4729,6 +4729,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__*/
2.34.1
On Fri, 20 May 2022 09:50:22 +0200, Takashi Iwai wrote:
On Thu, 19 May 2022 19:47:33 +0200, Vitaly Rodionov wrote: On Thu, 19 May 2022 19:47:33 +0200, 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.
Well, I still don't understand why the addition/deletion itself has to be in a work. As far as I see, it's simple calls of snd_ctl_add() and snd_ctl_remove_id().
And, if the problem is that you're calling snd_ctl_add() from another control callback, it's rather the problem of the caller's side, not here. IOW, the async implementation should be rather in the caller side.
Also, the description about the newly added controls is missing. It looks like a really special (non-standard) control that uses TLV for other purposes, and this must be mentioned somewhere.
And, I wonder what happens with alsactl store/restore with those controls. Are TLV contents properly parsed there?
thanks,
Takashi
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__*/
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 7b706805a897..261b6ca6e85d 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -6628,6 +6628,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,
On Thu, 19 May 2022 19:47:36 +0200, Vitaly Rodionov wrote:
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.
Wouldn't this sequence be required after S3/S4 resume? I thought cs35l41_hda_bind() is called only at the first binding.
thanks,
Takashi
On Fri, 20 May 2022 10:00:18 +0200, Takashi Iwai wrote:
On Thu, 19 May 2022 19:47:36 +0200, Vitaly Rodionov wrote:
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.
Wouldn't this sequence be required after S3/S4 resume? I thought cs35l41_hda_bind() is called only at the first binding.
OK, I see that the later patches in the series are about hibernation. It might be good to mention that in this patch, though.
Takashi
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 261b6ca6e85d..ef716f0b0052 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -6629,6 +6629,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; }
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 ef716f0b0052..e84f278f0d0d 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -4003,15 +4003,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); @@ -4042,6 +4049,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 */ @@ -6584,8 +6595,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
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..016f97f61c34 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -26,6 +26,9 @@ #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_R_DSP_CTL_TYPE 5 +#define CAL_R_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 +285,61 @@ 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) +{ + 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; + u32 r0; + + /* 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); + r0 = cpu_to_be32(cl->calR); + ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_R_DSP_CTL_NAME, + CAL_R_DSP_CTL_TYPE, CAL_R_DSP_CTL_ALG, + &r0, 4); + if (ret) + dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", + CAL_R_DSP_CTL_NAME, ret); + } + } + vfree(data); + } + return ret; +} +#else +static int cs35l41_apply_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 +372,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_apply_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 016f97f61c34..4ca5c7b974ce 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -32,11 +32,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 4ca5c7b974ce..06abd03e2996 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -89,7 +89,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); @@ -111,20 +111,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; @@ -433,8 +437,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)); @@ -472,12 +479,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); } @@ -616,6 +626,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); @@ -637,8 +777,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; @@ -1158,6 +1305,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 Thu, 19 May 2022 19:47:48 +0200, Vitaly Rodionov wrote:
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"
Hmm. This essentially means that you can execute the firmware load and the whole DSP init / removal just by changing this control element value, right? The end-effect is too strong, IMO. The control element is available to all users who can access the sound device, and any malicious program may change it randomly thousands times per second. That is, it can easily lead to some weird issue, I'm afraid.
thanks,
Takashi
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 06abd03e2996..8aea09870bc2 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -30,6 +30,11 @@ #define CAL_R_DSP_CTL_TYPE 5 #define CAL_R_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 @@ -779,11 +784,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 Thu, 19 May 2022 18:47:32 +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 (3)
-
Mark Brown
-
Takashi Iwai
-
Vitaly Rodionov