Message ID | 20220519174749.15459-2-vitalyr@opensource.cirrus.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | ALSA: hda: cirrus: Add initial DSP support and firmware loading | expand |
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
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__*/