lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <s5hzgjofacn.wl-tiwai@suse.de>
Date:   Wed, 11 May 2022 15:49:12 +0200
From:   Takashi Iwai <tiwai@...e.de>
To:     Vitaly Rodionov <vitalyr@...nsource.cirrus.com>
Cc:     Jaroslav Kysela <perex@...ex.cz>, Takashi Iwai <tiwai@...e.com>,
        Mark Brown <broonie@...nel.org>, <alsa-devel@...a-project.org>,
        <patches@...nsource.cirrus.com>, <linux-kernel@...r.kernel.org>,
        Stefan Binding <sbinding@...nsource.cirrus.com>
Subject: Re: [PATCH v2 10/26] ALSA: hda: hda_cs_dsp_ctl: Add Library to support CS_DSP ALSA controls

On Mon, 09 May 2022 23:46:47 +0200,
Vitaly Rodionov wrote:
> 
> From: Stefan Binding <sbinding@...nsource.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.

Hmm, quite lots of things aren't explained here.

First off, as far as I see, the control elements that are implemented
in this patch are pretty unique, they don't follow the standard way.
Admittedly, ASoC core (ab)uses the TLV read/write for the arbitrary
data bytes, and this seems following that instead.  If so, it needs
more clear explanation in the comments add/or commit logs.

Second, hda_cs_dsp_control_add() and _remove() functions do the
addition and the removal asynchronously with a dedicated work.  It's
fragile as an API, and even no clarification is given why it's done
so.


thanks,

Takashi

> 
> Signed-off-by: Stefan Binding <sbinding@...nsource.cirrus.com>
> Signed-off-by: Vitaly Rodionov <vitalyr@...nsource.cirrus.com>
> ---
>  MAINTAINERS                    |   1 +
>  sound/pci/hda/Kconfig          |   4 +
>  sound/pci/hda/Makefile         |   2 +
>  sound/pci/hda/hda_cs_dsp_ctl.c | 364 +++++++++++++++++++++++++++++++++
>  sound/pci/hda/hda_cs_dsp_ctl.h |  34 +++
>  5 files changed, 405 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 f57e6d38a542..b7e4d2a0ef41 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4713,6 +4713,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..e94740c5557a
> --- /dev/null
> +++ b/sound/pci/hda/hda_cs_dsp_ctl.c
> @@ -0,0 +1,364 @@
> +// SPDX-License-Identifier: GPL-2.0
> +//
> +// HDA DSP ALSA Control Driver
> +//
> +// Copyright 2022 Cirrus Logic, Inc.
> +//
> +// Author: Stefan Binding <sbinding@...nsource.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"
> +
> +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;
> +
> +	switch (cs_ctl->type) {
> +	case WMFW_CTL_TYPE_ACKED:
> +		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
> +		uinfo->value.integer.min = CS_DSP_ACKED_CTL_MIN_VALUE;
> +		uinfo->value.integer.max = CS_DSP_ACKED_CTL_MAX_VALUE;
> +		uinfo->value.integer.step = 1;
> +		uinfo->count = 1;
> +		break;
> +	default:
> +		uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
> +		uinfo->count = cs_ctl->len;
> +		break;
> +	}
> +
> +	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_tlv_put(struct snd_kcontrol *kctl,
> +				    const unsigned int __user *bytes, unsigned int size)
> +{
> +	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;
> +	int ret = 0;
> +
> +	mutex_lock(&cs_ctl->dsp->pwr_lock);
> +
> +	if (copy_from_user(cs_ctl->cache, bytes, size))
> +		ret = -EFAULT;
> +	else
> +		ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, cs_ctl->cache, size);
> +
> +	mutex_unlock(&cs_ctl->dsp->pwr_lock);
> +
> +	return ret;
> +}
> +
> +static int hda_cs_dsp_coeff_put_acked(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;
> +	unsigned int val = ucontrol->value.integer.value[0];
> +	int ret;
> +
> +	if (val == 0)
> +		return 0;	/* 0 means no event */
> +
> +	mutex_lock(&cs_ctl->dsp->pwr_lock);
> +
> +	if (cs_ctl->enabled)
> +		ret = cs_dsp_coeff_write_acked_control(cs_ctl, val);
> +	else
> +		ret = -EPERM;
> +
> +	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 int hda_cs_dsp_coeff_tlv_get(struct snd_kcontrol *kctl,
> +				    unsigned int __user *bytes, unsigned int size)
> +{
> +	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;
> +	int ret = 0;
> +
> +	mutex_lock(&cs_ctl->dsp->pwr_lock);
> +
> +	ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, cs_ctl->cache, size);
> +
> +	if (!ret && copy_to_user(bytes, cs_ctl->cache, size))
> +		ret = -EFAULT;
> +
> +	mutex_unlock(&cs_ctl->dsp->pwr_lock);
> +
> +	return ret;
> +}
> +
> +static int hda_cs_dsp_coeff_get_acked(struct snd_kcontrol *kcontrol,
> +				      struct snd_ctl_elem_value *ucontrol)
> +{
> +	/*
> +	 * Although it's not useful to read an acked control, we must satisfy
> +	 * user-side assumptions that all controls are readable and that a
> +	 * write of the same value should be filtered out (it's valid to send
> +	 * the same event number again to the firmware). We therefore return 0,
> +	 * meaning "no event" so valid event numbers will always be a change
> +	 */
> +	ucontrol->value.integer.value[0] = 0;
> +
> +	return 0;
> +}
> +#define ADSP_MAX_STD_CTRL_SIZE               512
> +
> +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);
> +
> +	switch (cs_ctl->type) {
> +	case WMFW_CTL_TYPE_ACKED:
> +		kcontrol->get = hda_cs_dsp_coeff_get_acked;
> +		kcontrol->put = hda_cs_dsp_coeff_put_acked;
> +		break;
> +	default:
> +		if (kcontrol->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) {
> +			ctl->bytes_ext.max = cs_ctl->len;
> +			ctl->bytes_ext.get = hda_cs_dsp_coeff_tlv_get;
> +			ctl->bytes_ext.put = hda_cs_dsp_coeff_tlv_put;
> +		} else {
> +			kcontrol->get = hda_cs_dsp_coeff_get;
> +			kcontrol->put = hda_cs_dsp_coeff_put;
> +		}
> +		break;
> +	}
> +
> +	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;
> +	}
> +
> +	switch (cs_dsp->fw_ver) {
> +	case 0:
> +	case 1:
> +		ret = scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
> +				"%s %s %s %x", info->amp_name, cs_dsp->name, region_name,
> +				cs_ctl->alg_region.alg);
> +		break;
> +	case 2:
> +		ret = scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
> +				"%s %s%c %.12s %x", info->amp_name, cs_dsp->name, *region_name,
> +				hda_cs_dsp_fw_text[info->fw_type], cs_ctl->alg_region.alg);
> +		break;
> +	default:
> +		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);
> +		break;
> +	}
> +
> +	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@...nsource.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@...nsource.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
> 

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ