[alsa-devel] [PATCH 4/4] ASoC: firmware core: Add core support to create and destroy firmware components.

Takashi Iwai tiwai at suse.de
Mon Nov 19 19:46:34 CET 2012


At Mon, 19 Nov 2012 18:12:45 +0000,
Liam Girdwood wrote:
> 
> Add generic support to create ASoC kcontrols, widgets, graphs and coefficients from
> firmware files.  This is useful for many modern audio DSP devices where the firmware
> often defines the graph and mixers and allows the vendor firmware file to ship with the
> ASoC specific components along with the firmware text.
> 
> The generic FW format is very simple and uses a small header to describe each file
> section. e.g. a FW file with text and kcontrols would have a header for the text and
> another header for the kcontrols section. The sections can be in any order within
> the firmware file meaning many different FW section files can be cat'ed together into
> a larger file.
> 
> This patch creates the generic ASoC firmware core and an associated header API
> that can also be used by userspace firmware generation tools. It also defines a
> small API that can be used by ASoc component drivers to interrogate and process
> the firmware data. Most firmware data is handled by the core, e.g. widgets, kcontrols
> and graph, whilst other data is passed directly to the component driver for
> enumeration e.g. text.
> 
> This patch has working support to load ASoC :-
> 
>  o Kcontrols
>  o TLV Kcontrols
>  o DAPM widgets
>  o DAPM Graph
>  o Kcontrols with coefficients.
>  o Firmware Text
>  o Custom vendor blocks
> 
> There is a command line firmware file generation tool that is mostly complete at :-
> 
> git at gitorious.org:omap-audio/asoc-fw.git
> 
> This tool generates the headers and generic components based on the ASoC types above
> and it's intended to be upstreamed in to alsa-utils soon. There is example code with
> the tool for generating firmware for the OMAP4 ABE.
> 
> Signed-off-by: Liam Girdwood <lrg at ti.com>

I find the idea is great.  Looking through the patch, a few things
came to my mind:

- Endianness and alignment
- Forward-compatibility
- Avoid the bitfield usages

About endianness: the firmware is usually provided as
endian-independent.  That is, the driver is supposed to convert to CPU
endianness properly, or check and reject the invalid firmware, at
least.

The alignment isn't a big issue as long as I see the current
definitions, but better to add packed attribute to each structure.

For the forward-compatibility, it'd be good to put a version number
corresponding the firmware data definition, not only the
vendor-specific version number.

Last one, the bit field usage, is always a thing to avoid if you use a
struct for communication.  Use the explicit bit operation instead.


thanks,

Takashi


> ---
>  include/sound/soc-fw.h |  270 +++++++++
>  sound/soc/Makefile     |    2 +-
>  sound/soc/soc-core.c   |    4 +
>  sound/soc/soc-dapm.c   |    6 +
>  sound/soc/soc-fw.c     | 1505 ++++++++++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 1786 insertions(+), 1 deletion(-)
>  create mode 100644 include/sound/soc-fw.h
>  create mode 100644 sound/soc/soc-fw.c
> 
> diff --git a/include/sound/soc-fw.h b/include/sound/soc-fw.h
> new file mode 100644
> index 0000000..ada25b5
> --- /dev/null
> +++ b/include/sound/soc-fw.h
> @@ -0,0 +1,270 @@
> +/*
> + * linux/sound/soc-fw.h -- ALSA SoC Firmware Controls and DAPM
> + *
> + * Copyright:	2012 Texas Instruments Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * Simple file API to load FW that includes mixers, coefficients, DAPM graphs,
> + * algorithms, equalisers, DAIs, widgets etc.
> + */
> +
> +#ifndef __LINUX_SND_SOC_FW_H
> +#define __LINUX_SND_SOC_FW_H
> +
> +/* Header magic number and string sizes */
> +#define SND_SOC_FW_MAGIC	0x41536F43 /* ASoC */
> +#define SND_SOC_FW_TEXT_SIZE	32
> +#define SND_SOC_FW_NUM_TEXTS	16
> +
> +/*
> + * File andBlock header data types.
> + * Add new generic and vendor types to end of list.
> + * Generic types are handled by the core whilst vendors types are passed
> + * to the component drivers for handling.
> + */
> +#define SND_SOC_FW_MIXER		1
> +#define SND_SOC_FW_DAPM_GRAPH		2
> +#define SND_SOC_FW_DAPM_PINS		3
> +#define SND_SOC_FW_DAPM_WIDGET		4
> +#define SND_SOC_FW_DAI_LINK		5
> +#define SND_SOC_FW_COEFF		6
> +
> +#define SND_SOC_FW_VENDOR_FW		1000
> +#define SND_SOC_FW_VENDOR_CONFIG	1001
> +#define SND_SOC_FW_VENDOR_COEFF	1002
> +#define SND_SOC_FW_VENDOR_CODEC	1003
> +
> +struct firmware;
> +
> +/*
> + * File and Block Header
> + */
> +struct snd_soc_fw_hdr {
> +	u32 magic;
> +	u32 type;
> +	u32 vendor_type; /* optional vendor specific type info */
> +	u32 version; /* optional vendor specific version details */
> +	u32 size; /* data bytes, excluding this header */
> +	/* file data contents start here */
> +};
> +
> +
> +struct snd_soc_fw_ctl_tlv {
> +	u32 numid;	/* control element numeric identification */
> +	u32 length;	/* in bytes aligned to 4 */
> +	/* tlv data starts here */
> +};
> +
> +struct snd_soc_fw_control_hdr {
> +	char name[SND_SOC_FW_TEXT_SIZE];
> +	u32 index;
> +	u32 access;
> +	u32 tlv_size;
> +};
> +
> +/*
> + * Mixer KControl.
> + */
> +struct snd_soc_fw_mixer_control {
> +	struct snd_soc_fw_control_hdr hdr;
> +	s32 min;
> +	s32 max;
> +	s32 platform_max;
> +	u32 reg;
> +	u32 rreg;
> +	u32 shift;
> +	u32 rshift;
> +	u32 invert;
> +};
> +
> +/*
> + * Enumerated KControl
> + */
> +struct snd_soc_fw_enum_control {
> +	struct snd_soc_fw_control_hdr hdr;
> +	u32 reg;
> +	u32 reg2;
> +	u32 shift_l;
> +	u32 shift_r;
> +	u32 max;
> +	u32 mask;
> +	union {	/* both texts and values are the same size */
> +		char texts[SND_SOC_FW_NUM_TEXTS][SND_SOC_FW_TEXT_SIZE];
> +		u32 values[SND_SOC_FW_NUM_TEXTS * SND_SOC_FW_TEXT_SIZE / 4];
> +	};
> +};
> +
> +/*
> + * Kcontrol Header
> + */
> +struct snd_soc_fw_kcontrol {
> +	u32 count; /* in kcontrols (based on type) */
> +	/* kcontrols here */
> +};
> +
> +/*
> + * DAPM Graph Element
> + */
> +struct snd_soc_fw_dapm_graph_elem {
> +	char sink[SND_SOC_FW_TEXT_SIZE];
> +	char control[SND_SOC_FW_TEXT_SIZE];
> +	char source[SND_SOC_FW_TEXT_SIZE];
> +};
> +
> +/*
> + * DAPM Pin Element.
> + */
> +struct snd_soc_fw_dapm_pin_elem {
> +	char name[SND_SOC_FW_TEXT_SIZE];
> +	u32 disconnect:1;
> +	u32 ignore_suspend:1;
> +};
> +
> +
> +/*
> + * DAPM Widget.
> + */
> +struct snd_soc_fw_dapm_widget {
> +	u32 id;		/* snd_soc_dapm_type */
> +	char name[SND_SOC_FW_TEXT_SIZE];
> +	char sname[SND_SOC_FW_TEXT_SIZE];
> +
> +	s32 reg;		/* negative reg = no direct dapm */
> +	u32 shift;		/* bits to shift */
> +	u32 mask;		/* non-shifted mask */
> +	u32 invert:1;		/* invert the power bit */
> +	u32 ignore_suspend:1;	/* kept enabled over suspend */
> +
> +	/* kcontrols that relate to this widget */
> +	struct snd_soc_fw_kcontrol kcontrol;
> +	/* controls follow here */
> +};
> +
> +/*
> + * DAPM Graph and Pins.
> + */
> +struct snd_soc_fw_dapm_elems {
> +	u32 count; /* in elements */
> +	/* elements here */
> +};
> +
> +/*
> + * Coeffcient File Data.
> + */
> +struct snd_soc_file_coeff_data {
> +	u32 count; /* in elems */
> +	u32 size;	/* total data size */
> +	u32 id; /* associated mixer ID */
> +	/* data here */
> +};
> +
> +#ifdef __KERNEL__
> +
> +/*
> + * Kcontrol operations - used to map handlers onto firmware based controls.
> + */
> +struct snd_soc_fw_kcontrol_ops {
> +	u32 id;
> +	int (*get)(struct snd_kcontrol *kcontrol,
> +			struct snd_ctl_elem_value *ucontrol);
> +	int (*put)(struct snd_kcontrol *kcontrol,
> +			struct snd_ctl_elem_value *ucontrol);
> +	int (*info)(struct snd_kcontrol *kcontrol,
> +		struct snd_ctl_elem_info *uinfo);
> +};
> +
> +/*
> + * Public API - Used by component drivers to load new mixers, DAPM, vendor
> + * specific data.
> + */
> +struct snd_soc_fw_codec_ops {
> +
> +	/* external kcontrol init - can be used to set ext funcs + pdata */
> +	int (*control_load) (struct snd_soc_codec *, struct snd_kcontrol_new *);
> +
> +	/* external widget init - can be used to set ext funcs + pdata */
> +	int (*widget_load) (struct snd_soc_codec *, struct snd_soc_dapm_widget *);
> +
> +	/* callback to handle vendor data */
> +	int (*vendor_load) (struct snd_soc_codec *, struct snd_soc_fw_hdr *);
> +	int (*vendor_unload) (struct snd_soc_codec *, struct snd_soc_fw_hdr *);
> +
> +	/* completion - called at completion of firmware loading */
> +	void (*complete) (struct snd_soc_codec *);
> +
> +	/* kcontrols operations */
> +	const struct snd_soc_fw_kcontrol_ops *io_ops;
> +	int io_ops_count;
> +};
> +
> +struct snd_soc_fw_platform_ops {
> +
> +	/* external kcontrol init - can be used to set ext funcs + pdata */
> +	int (*control_load) (struct snd_soc_platform *, struct snd_kcontrol_new *);
> +
> +	/* external widget init - can be used to set ext funcs + pdata */
> +	int (*widget_load) (struct snd_soc_platform *, struct snd_soc_dapm_widget *);
> +
> +	/* callback to handle vendor data */
> +	int (*vendor_load) (struct snd_soc_platform *, struct snd_soc_fw_hdr *);
> +	int (*vendor_unload) (struct snd_soc_platform *, struct snd_soc_fw_hdr *);
> +
> +	/* completion - called at completion of firmware loading */
> +	void (*complete) (struct snd_soc_platform *);
> +
> +	/* kcontrols operations */
> +	const struct snd_soc_fw_kcontrol_ops *io_ops;
> +	int io_ops_count;
> +};
> +
> +struct snd_soc_fw_card_ops {
> +
> +	/* external kcontrol init - can be used to set ext funcs + pdata */
> +	int (*control_load) (struct snd_soc_card *, struct snd_kcontrol_new *);
> +
> +	/* external widget init - can be used to set ext funcs + pdata */
> +	int (*widget_load) (struct snd_soc_card *, struct snd_soc_dapm_widget *);
> +
> +	/* callback to handle vendor data */
> +	int (*vendor_load) (struct snd_soc_card *, struct snd_soc_fw_hdr *);
> +	int (*vendor_unload) (struct snd_soc_card *, struct snd_soc_fw_hdr *);
> +
> +	/* completion */
> +	void (*complete) (struct snd_soc_card *);
> +
> +	/* kcontrols operations */
> +	const struct snd_soc_fw_kcontrol_ops *io_ops;
> +	int io_ops_count;
> +};
> +
> +/* gets a pointer to data from the firmware block header */
> +static inline const void *snd_soc_fw_get_data(struct snd_soc_fw_hdr *hdr)
> +{
> +	const void *ptr = hdr;
> +
> +	return ptr + sizeof(*hdr);
> +}
> +
> +/* Firmware loading for component drivers */
> +int snd_soc_fw_load_card(struct snd_soc_card *card,
> +	struct snd_soc_fw_card_ops *ops, const struct firmware *fw);
> +int snd_soc_fw_load_platform(struct snd_soc_platform *platform,
> +	struct snd_soc_fw_platform_ops *ops, const struct firmware *fw);
> +int snd_soc_fw_load_codec(struct snd_soc_codec *codec,
> +	struct snd_soc_fw_codec_ops *ops, const struct firmware *fw);
> +
> +/* Firmware based dynamic widget and assoc kcontrol removal */
> +void snd_soc_fw_dcontrols_remove_widgets(struct snd_soc_dapm_context *dapm);
> +void snd_soc_fw_dcontrols_remove_widget(struct snd_soc_dapm_widget *w);
> +
> +/* Firmware based dynamic kcontrol removal for components */
> +void snd_soc_fw_dcontrols_remove_codec(struct snd_soc_codec *codec);
> +void snd_soc_fw_dcontrols_remove_platform(struct snd_soc_platform *platform);
> +void snd_soc_fw_dcontrols_remove_card(struct snd_soc_card *soc_card);
> +int snd_soc_fw_dcontrols_remove_all(struct snd_soc_card *soc_card);
> +
> +#endif
> +#endif
> diff --git a/sound/soc/Makefile b/sound/soc/Makefile
> index 99f32f7..4df20e5 100644
> --- a/sound/soc/Makefile
> +++ b/sound/soc/Makefile
> @@ -1,5 +1,5 @@
>  snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-cache.o soc-utils.o
> -snd-soc-core-objs += soc-pcm.o soc-compress.o soc-io.o
> +snd-soc-core-objs += soc-pcm.o soc-compress.o soc-io.o soc-fw.o
>  
>  ifneq ($(CONFIG_SND_SOC_DMAENGINE_PCM),)
>  snd-soc-core-objs += soc-dmaengine-pcm.o
> diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
> index 1f448ab..614bb5b 100644
> --- a/sound/soc/soc-core.c
> +++ b/sound/soc/soc-core.c
> @@ -40,6 +40,7 @@
>  #include <sound/pcm_params.h>
>  #include <sound/soc.h>
>  #include <sound/soc-dpcm.h>
> +#include <sound/soc-fw.h>
>  #include <sound/initval.h>
>  
>  #define CREATE_TRACE_POINTS
> @@ -1881,6 +1882,9 @@ static int soc_cleanup_card_resources(struct snd_soc_card *card)
>  	/* remove and free each DAI */
>  	soc_remove_dai_links(card);
>  
> +	/* remove any dynamic kcontrols */
> +	snd_soc_fw_dcontrols_remove_all(card);
> +
>  	soc_cleanup_card_debugfs(card);
>  
>  	/* remove the card */
> diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
> index f456378..84ff20b 100644
> --- a/sound/soc/soc-dapm.c
> +++ b/sound/soc/soc-dapm.c
> @@ -41,6 +41,7 @@
>  #include <sound/pcm.h>
>  #include <sound/pcm_params.h>
>  #include <sound/soc.h>
> +#include <sound/soc-fw.h>
>  #include <sound/initval.h>
>  
>  #include <trace/events/asoc.h>
> @@ -2093,6 +2094,11 @@ static void dapm_free_widgets(struct snd_soc_dapm_context *dapm)
>  			kfree(p->long_name);
>  			kfree(p);
>  		}
> +
> +		/* check and free and dynamic widget kcontrols */
> +		if (w->denum || w->dmixer)
> +			snd_soc_fw_dcontrols_remove_widget(w);
> +
>  		kfree(w->kcontrols);
>  		kfree(w->name);
>  		kfree(w);
> diff --git a/sound/soc/soc-fw.c b/sound/soc/soc-fw.c
> new file mode 100644
> index 0000000..e9144aa
> --- /dev/null
> +++ b/sound/soc/soc-fw.c
> @@ -0,0 +1,1505 @@
> +/*
> + * soc-fw.c  --  ALSA SoC Firmware
> + *
> + * Copyright (C) 2012 Texas Instruments Inc.
> + *
> + * Author: Liam Girdwood <lrg at ti.com>
> + *
> + *  This program is free software; you can redistribute  it and/or modify it
> + *  under  the terms of  the GNU General  Public License as published by the
> + *  Free Software Foundation;  either version 2 of the  License, or (at your
> + *  option) any later version.
> + *
> + *  Support for audio fimrware to contain kcontrols, DAPM graphs, widgets,
> + *  DAIs, equalizers, firmware, coefficienst etc.
> + *
> + *  This file only manages the DAPM and Kcontrol components, all other firmware
> + *  data is passed to component drivers for bespoke handling.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/export.h>
> +#include <linux/list.h>
> +#include <linux/firmware.h>
> +#include <linux/slab.h>
> +#include <sound/soc.h>
> +#include <sound/soc-dapm.h>
> +#include <sound/soc-fw.h>
> +
> +/*
> + * We make several passes over the data (since it wont necessarily be ordered)
> + * and process objects in the following order. This guarantees the component
> + * drivers will be ready with any vendor data before the mixers and DAPM objects
> + * are loaded (that may make use of the vendor data).
> + */
> +#define SOC_FW_PASS_VENDOR	0
> +#define SOC_FW_PASS_MIXER	1
> +#define SOC_FW_PASS_COEFF	SOC_FW_PASS_MIXER
> +#define SOC_FW_PASS_WIDGET	2
> +#define SOC_FW_PASS_GRAPH	3
> +#define SOC_FW_PASS_PINS	4
> +
> +#define SOC_FW_PASS_START	SOC_FW_PASS_VENDOR
> +#define SOC_FW_PASS_END	SOC_FW_PASS_PINS
> +
> +struct soc_fw {
> +	const char *file;
> +	const struct firmware *fw;
> +
> +	/* runtime FW parsing */
> +	const u8 *pos;		/* read postion */
> +	const u8 *hdr_pos;	/* header position */
> +	unsigned int pass;	/* pass number */
> +
> +	/* component caller */
> +	struct device *dev;
> +	struct snd_soc_codec *codec;
> +	struct snd_soc_platform *platform;
> +	struct snd_soc_card *card;
> +
> +	/* kcontrol operations */
> +	const struct snd_soc_fw_kcontrol_ops *io_ops;
> +	int io_ops_count;
> +
> +	/* optional fw loading callbacks to component drivers */
> +	union {
> +		struct snd_soc_fw_codec_ops *codec_ops;
> +		struct snd_soc_fw_platform_ops *platform_ops;
> +		struct snd_soc_fw_card_ops *card_ops;
> +	};
> +};
> +
> +static int soc_fw_process_headers(struct soc_fw *sfw);
> +static void soc_fw_complete(struct soc_fw *sfw);
> +
> +/* List of Kcontrol types and associated operations. */
> +static const struct snd_soc_fw_kcontrol_ops io_ops[] = {
> +	{SOC_CONTROL_IO_VOLSW, snd_soc_get_volsw,
> +		snd_soc_put_volsw, snd_soc_info_volsw},
> +	{SOC_CONTROL_IO_VOLSW_SX, snd_soc_get_volsw_sx,
> +		snd_soc_put_volsw_sx, NULL},
> +	{SOC_CONTROL_IO_VOLSW_S8, snd_soc_get_volsw_s8,
> +		snd_soc_put_volsw_s8, snd_soc_info_volsw_s8},
> +	{SOC_CONTROL_IO_ENUM, snd_soc_get_enum_double,
> +		snd_soc_put_enum_double, snd_soc_info_enum_double},
> +	{SOC_CONTROL_IO_ENUM_EXT, NULL,
> +		NULL, snd_soc_info_enum_ext},
> +	{SOC_CONTROL_IO_BYTES, snd_soc_bytes_get,
> +		snd_soc_bytes_put, snd_soc_bytes_info},
> +	{SOC_CONTROL_IO_BOOL_EXT, NULL,
> +		NULL, snd_ctl_boolean_mono_info},
> +	{SOC_CONTROL_IO_ENUM_VALUE, snd_soc_get_value_enum_double,
> +		snd_soc_put_value_enum_double, NULL},
> +	{SOC_CONTROL_IO_RANGE, snd_soc_get_volsw_range,
> +		snd_soc_put_volsw_range, snd_soc_info_volsw_range},
> +	{SOC_CONTROL_IO_VOLSW_XR_SX, snd_soc_get_xr_sx,
> +		snd_soc_put_xr_sx, snd_soc_info_xr_sx},
> +	{SOC_CONTROL_IO_STROBE, snd_soc_get_strobe,
> +		snd_soc_put_strobe, NULL},
> +
> +	{SOC_DAPM_IO_VOLSW, snd_soc_dapm_get_volsw,
> +		snd_soc_dapm_put_volsw, NULL},
> +	{SOC_DAPM_IO_ENUM_DOUBLE, snd_soc_dapm_get_enum_double,
> +		snd_soc_dapm_put_enum_double, snd_soc_info_enum_double},
> +	{SOC_DAPM_IO_ENUM_VIRT, snd_soc_dapm_get_enum_virt,
> +		snd_soc_dapm_put_enum_virt, NULL},
> +	{SOC_DAPM_IO_ENUM_VALUE, snd_soc_dapm_get_value_enum_double,
> +		snd_soc_dapm_put_value_enum_double, NULL},
> +	{SOC_DAPM_IO_PIN, snd_soc_dapm_get_pin_switch,
> +		snd_soc_dapm_put_pin_switch, snd_soc_dapm_info_pin_switch},
> +};
> +
> +static inline void soc_fw_list_add_enum(struct soc_fw *sfw, struct soc_enum *se)
> +{
> +	if (sfw->codec)
> +		list_add(&se->list, &sfw->codec->denums);
> +	else if (sfw->platform)
> +		list_add(&se->list, &sfw->platform->denums);
> +	else if (sfw->card)
> +		list_add(&se->list, &sfw->card->denums);
> +}
> +
> +static inline void soc_fw_list_add_mixer(struct soc_fw *sfw,
> +	struct soc_mixer_control *mc)
> +{
> +	if (sfw->codec)
> +		list_add(&mc->list, &sfw->codec->dmixers);
> +	else if (sfw->platform)
> +		list_add(&mc->list, &sfw->platform->dmixers);
> +	else if (sfw->card)
> +		list_add(&mc->list, &sfw->card->dmixers);
> +}
> +
> +static inline struct snd_soc_dapm_context *soc_fw_dapm_get(struct soc_fw *sfw)
> +{
> +	if (sfw->codec)
> +		return &sfw->codec->dapm;
> +	else if (sfw->platform)
> +		return &sfw->platform->dapm;
> +	else if (sfw->card)
> +		return &sfw->card->dapm;
> +	BUG();
> +}
> +
> +static inline struct snd_soc_card *soc_fw_card_get(struct soc_fw *sfw)
> +{
> +	if (sfw->codec)
> +		return sfw->codec->card;
> +	else if (sfw->platform)
> +		return sfw->platform->card;
> +	else if (sfw->card)
> +		return sfw->card;
> +	BUG();
> +}
> +
> +/* check we dont overflow the data for this control chunk */
> +static int soc_fw_check_control_count(struct soc_fw *sfw, size_t elem_size,
> +	unsigned int count, size_t bytes)
> +{
> +	const u8 *end = sfw->pos + elem_size * count;
> +
> +	if (end > sfw->fw->data + sfw->fw->size) {
> +		dev_err(sfw->dev, "ASoC: controls overflow end of data\n");
> +		return -EINVAL;
> +	}
> +
> +	/* check there is enough room in chunk for control.
> +	   extra bytes at the end of control are for vendor data here  */
> +	if (elem_size * count > bytes) {
> +		dev_err(sfw->dev, "ASoC: controls count %d of elem size %d "
> +			"are bigger than chunk %d\n", count, elem_size, bytes);
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static inline int soc_fw_is_eof(struct soc_fw *sfw)
> +{
> +	const u8 *end = sfw->hdr_pos;
> +
> +	if (end >= sfw->fw->data + sfw->fw->size)
> +		return 1;
> +	return 0;
> +}
> +
> +static inline unsigned int soc_fw_get_hdr_offset(struct soc_fw *sfw)
> +{
> +	return (unsigned int)(sfw->hdr_pos - sfw->fw->data);
> +}
> +
> +static inline unsigned int soc_fw_get_offset(struct soc_fw *sfw)
> +{
> +	return (unsigned int)(sfw->pos - sfw->fw->data);
> +}
> +
> +/* pass vendor data to component driver for processing */
> +static int soc_fw_vendor_load_(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
> +{
> +	int ret = 0;
> +
> +	if (sfw->codec && sfw->codec_ops && sfw->codec_ops->vendor_load)
> +		ret = sfw->codec_ops->vendor_load(sfw->codec, hdr);
> +
> +	if (sfw->platform && sfw->platform_ops && sfw->platform_ops->vendor_load)
> +		ret = sfw->platform_ops->vendor_load(sfw->platform, hdr);
> +
> +	if (sfw->card && sfw->card_ops && sfw->card_ops->vendor_load)
> +		ret = sfw->card_ops->vendor_load(sfw->card, hdr);
> +
> +	if (ret < 0)
> +		dev_err(sfw->dev, "ASoC: vendor load failed at hdr offset"
> +			" %d/0x%x for type %d:%d\n", soc_fw_get_hdr_offset(sfw),
> +			soc_fw_get_hdr_offset(sfw), hdr->type, hdr->vendor_type);
> +	return ret;
> +}
> +
> +/* pass vendor data to component driver for processing */
> +static int soc_fw_vendor_load(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
> +{
> +	if (sfw->pass != SOC_FW_PASS_VENDOR)
> +		return 0;
> +
> +	return soc_fw_vendor_load_(sfw, hdr);
> +}
> +
> +/* pass new dynamic widget to component driver. mainly for external widgets */
> +static int soc_fw_widget_load(struct soc_fw *sfw, struct snd_soc_dapm_widget *w)
> +{
> +	if (sfw->codec && sfw->codec_ops && sfw->codec_ops->widget_load)
> +		return sfw->codec_ops->widget_load(sfw->codec, w);
> +
> +	if (sfw->platform && sfw->platform_ops && sfw->platform_ops->widget_load)
> +		return sfw->platform_ops->widget_load(sfw->platform, w);
> +
> +	if (sfw->card && sfw->card_ops && sfw->card_ops->widget_load)
> +		return sfw->card_ops->widget_load(sfw->card, w);
> +
> +	dev_info(sfw->dev, "ASoC: no handler specified for ext widget %s\n",
> +		w->name);
> +	return 0;
> +}
> +
> +/* tell the component driver that all firmware has been loaded in this request */
> +static void soc_fw_complete(struct soc_fw *sfw)
> +{
> +	if (sfw->codec && sfw->codec_ops && sfw->codec_ops->complete)
> +		sfw->codec_ops->complete(sfw->codec);
> +	if (sfw->platform && sfw->platform_ops && sfw->platform_ops->complete)
> +		sfw->platform_ops->complete(sfw->platform);
> +	if (sfw->card && sfw->card_ops && sfw->card_ops->complete)
> +		sfw->card_ops->complete(sfw->card);
> +}
> +
> +/* add a dynamic kcontrol */
> +static int soc_fw_add_dcontrol(struct snd_card *card, struct device *dev,
> +	const struct snd_kcontrol_new *control_new, const char *prefix,
> +	void *data, struct snd_kcontrol **kcontrol)
> +{
> +	int err;
> +
> +	*kcontrol = snd_soc_cnew(control_new, data, control_new->name, prefix);
> +	if (*kcontrol == NULL) {
> +		dev_err(dev, "ASoC: Failed to create new kcontrol %s\n",
> +		control_new->name);
> +		return -ENOMEM;
> +	}
> +
> +	err = snd_ctl_add(card, *kcontrol);
> +	if (err < 0) {
> +		kfree(*kcontrol);
> +		dev_err(dev, "ASoC: Failed to add %s: %d\n", control_new->name,
> +			err);
> +		return err;
> +	}
> +
> +	return 0;
> +}
> +
> +/* add a dynamic kcontrol for component driver */
> +static int soc_fw_add_kcontrol(struct soc_fw *sfw, struct snd_kcontrol_new *k,
> +	struct snd_kcontrol **kcontrol)
> +{
> +	if (sfw->codec) {
> +		struct snd_soc_codec *codec = sfw->codec;
> +
> +		return soc_fw_add_dcontrol(codec->card->snd_card, codec->dev,
> +				k, codec->name_prefix, codec, kcontrol);
> +	} else if (sfw->platform) {
> +		struct snd_soc_platform *platform = sfw->platform;
> +
> +		return soc_fw_add_dcontrol(platform->card->snd_card,
> +				platform->dev, k, NULL, platform, kcontrol);
> +	} else if (sfw->card) {
> +		struct snd_soc_card *card = sfw->card;
> +
> +		return soc_fw_add_dcontrol(card->snd_card, card->dev,
> +				k, NULL, card, kcontrol);
> +	} else
> +		dev_info(sfw->dev, "ASoC: no handler specified for kcontrol %s\n",
> +			k->name);
> +	return 0;
> +}
> +
> +/* bind a kcontrol to it's IO handlers */
> +static int soc_fw_kcontrol_bind_io(u32 io_type, struct snd_kcontrol_new *k,
> +	const struct snd_soc_fw_kcontrol_ops *ops, int num_ops)
> +{
> +	int i;
> +
> +	for (i = 0; i < num_ops; i++) {
> +
> +		if (SOC_CONTROL_GET_ID_PUT(ops[i].id) ==
> +			SOC_CONTROL_GET_ID_PUT(io_type) && ops[i].put)
> +			k->put = ops[i].put;
> +		if (SOC_CONTROL_GET_ID_GET(ops[i].id) ==
> +			SOC_CONTROL_GET_ID_GET(io_type) && ops[i].get)
> +			k->get = ops[i].get;
> +		if (SOC_CONTROL_GET_ID_INFO(ops[i].id) ==
> +			SOC_CONTROL_GET_ID_INFO(io_type) && ops[i].info)
> +			k->info = ops[i].info;
> +	}
> +
> +	/* let the caller know if we need to bind external kcontrols */
> +	if (!k->put || !k->get || !k->info)
> +		return 1;
> +
> +	return 0;
> +}
> +
> +/* optionally pass new dynamic kcontrol to component driver. */
> +static int soc_fw_init_kcontrol(struct soc_fw *sfw, struct snd_kcontrol_new *k)
> +{
> +	if (sfw->codec && sfw->codec_ops && sfw->codec_ops->control_load)
> +		return sfw->codec_ops->control_load(sfw->codec, k);
> +
> +	if (sfw->platform && sfw->platform_ops && sfw->platform_ops->control_load)
> +		return sfw->platform_ops->control_load(sfw->platform, k);
> +
> +	if (sfw->card && sfw->card_ops && sfw->card_ops->control_load)
> +		return sfw->card_ops->control_load(sfw->card, k);
> +
> +	dev_info(sfw->dev, "ASoC: no handler specified for kcontrol %s\n",
> +		k->name);
> +	return 0;
> +}
> +
> +static int soc_fw_create_tlv(struct soc_fw *sfw, struct snd_kcontrol_new *kc,
> +	u32 tlv_size)
> +{
> +	struct snd_soc_fw_ctl_tlv *fw_tlv;
> +	struct snd_ctl_tlv *tlv;
> +
> +	if (tlv_size == 0)
> +		return 0;
> +
> +	fw_tlv = (struct snd_soc_fw_ctl_tlv *) sfw->pos;
> +	sfw->pos += tlv_size;
> +
> +	tlv = kzalloc(sizeof(*tlv) + tlv_size, GFP_KERNEL);
> +	if (tlv == NULL)
> +		return -ENOMEM;
> +
> +	dev_dbg(sfw->dev, " created TLV type %d size %d bytes\n",
> +		fw_tlv->numid, fw_tlv->length);
> +	tlv->numid = fw_tlv->numid;
> +	tlv->length = fw_tlv->length;
> +	memcpy(tlv->tlv, fw_tlv + 1, fw_tlv->length);
> +	kc->tlv.p = (void*)tlv;
> +
> +	return 0;
> +}
> +
> +static inline void soc_fw_free_tlv(struct soc_fw *sfw,
> +	struct snd_kcontrol_new *kc)
> +{
> +	kfree (kc->tlv.p);
> +}
> +
> +static int soc_fw_dmixer_create(struct soc_fw *sfw, unsigned int count,
> +	size_t size)
> +{
> +	struct snd_soc_fw_mixer_control *mc;
> +	struct soc_mixer_control *sm;
> +	struct snd_kcontrol_new kc;
> +	int i, err, ext;
> +
> +	if (soc_fw_check_control_count(sfw,
> +		sizeof(struct snd_soc_fw_mixer_control), count, size)) {
> +		dev_err(sfw->dev, "ASoC: invalid count %d for controls\n", count);
> +		return -EINVAL;
> +	}
> +
> +	for (i = 0; i < count; i++) {
> +		mc = (struct snd_soc_fw_mixer_control*)sfw->pos;
> +		sfw->pos += sizeof(struct snd_soc_fw_mixer_control);
> +
> +		/* validate kcontrol */
> +		if (strnlen(mc->hdr.name, SND_SOC_FW_TEXT_SIZE) ==
> +			SND_SOC_FW_TEXT_SIZE)
> +			return -EINVAL;
> +
> +		sm = kzalloc(sizeof(*sm), GFP_KERNEL);
> +		if (!sm)
> +			return -ENOMEM;
> +
> +		dev_dbg(sfw->dev, "ASoC: adding mixer kcontrol %s with access"
> +			" 0x%x\n", mc->hdr.name, mc->hdr.access);
> +
> +		memset(&kc, 0, sizeof(kc));
> +		kc.name = mc->hdr.name;
> +		kc.private_value = (long)sm;
> +		kc.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
> +		kc.access = mc->hdr.access;
> +
> +		sm->reg = mc->reg;
> +		sm->rreg = mc->rreg;
> +		sm->shift = mc->shift;
> +		sm->rshift = mc->rshift;
> +		sm->max = mc->max;
> +		sm->min = mc->min;
> +		sm->invert = mc->invert;
> +		sm->platform_max = mc->platform_max;
> +		INIT_LIST_HEAD(&sm->list);
> +
> +		/* map standard io handlers and check for external handlers */
> +		ext = soc_fw_kcontrol_bind_io(mc->hdr.index, &kc, io_ops,
> +			ARRAY_SIZE(io_ops));
> +		if (ext) {
> +			/* none exist, so now try and map ext handlers */
> +			ext = soc_fw_kcontrol_bind_io(mc->hdr.index, &kc,
> +				sfw->io_ops, sfw->io_ops_count);
> +			if (ext) {
> +				dev_err(sfw->dev, "ASoC: no complete mixer IO"
> +					"handler for %s type (g,p,i) %d:%d:%d\n",
> +					mc->hdr.name,
> +					SOC_CONTROL_GET_ID_GET(mc->hdr.index),
> +					SOC_CONTROL_GET_ID_PUT(mc->hdr.index),
> +					SOC_CONTROL_GET_ID_INFO(mc->hdr.index));
> +				kfree(sm);
> +				continue;
> +			}
> +
> +			err = soc_fw_init_kcontrol(sfw, &kc);
> +			if (err < 0) {
> +				dev_err(sfw->dev, "ASoC: failed to init %s\n",
> +					mc->hdr.name);
> +				kfree(sm);
> +				continue;
> +			}
> +		}
> +
> +		/* create any TLV data */
> +		soc_fw_create_tlv(sfw, &kc, mc->hdr.tlv_size);
> +
> +		/* register control here */
> +		err = soc_fw_add_kcontrol(sfw, &kc, &sm->dcontrol);
> +		if (err < 0) {
> +			dev_err(sfw->dev, "ASoC: failed to add %s\n", mc->hdr.name);
> +			soc_fw_free_tlv(sfw, &kc);
> +			kfree(sm);
> +			continue;
> +		}
> +
> +		soc_fw_list_add_mixer(sfw, sm);
> +	}
> +
> +	return 0;
> +}
> +
> +static inline void soc_fw_denum_free_data(struct soc_enum *se)
> +{
> +	int i;
> +
> +	if (se->dvalues)
> +		kfree(se->dvalues);
> +	else {
> +		for (i = 0; i < se->max - 1; i++)
> +			kfree(se->dtexts[i]);
> +	}
> +}
> +
> +static int soc_fw_denum_create_texts(struct soc_enum *se,
> +	struct snd_soc_fw_enum_control *ec)
> +{
> +	int i, ret;
> +
> +	se->dtexts = kzalloc(sizeof(char *) * ec->max, GFP_KERNEL);
> +	if (se->dtexts == NULL)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < ec->max; i++) {
> +
> +		if (strnlen(ec->texts[i], SND_SOC_FW_TEXT_SIZE) ==
> +			SND_SOC_FW_TEXT_SIZE) {
> +			ret = -EINVAL;
> +			goto err;
> +		}
> +
> +		se->dtexts[i] = kstrdup(ec->texts[i], GFP_KERNEL);
> +		if (!se->dtexts[i]) {
> +			ret = -ENOMEM;
> +			goto err;
> +		}
> +	}
> +
> +	return 0;
> +
> +err:
> +	for (--i; i >= 0; i--)
> +		kfree(se->dtexts[i]);
> +	kfree(se->dtexts);
> +	return ret;
> +}
> +
> +static int soc_fw_denum_create_values(struct soc_enum *se,
> +	struct snd_soc_fw_enum_control *ec)
> +{
> +	if (ec->max > sizeof(*ec->values))
> +		return -EINVAL;
> +
> +	se->dvalues = kmalloc(ec->max * sizeof(u32), GFP_KERNEL);
> +	if (!se->dvalues)
> +		return -ENOMEM;
> +
> +	memcpy(se->dvalues, ec->values, ec->max * sizeof(u32));
> +	return 0;
> +}
> +
> +static int soc_fw_denum_create(struct soc_fw *sfw, unsigned int count,
> +	size_t size)
> +{
> +	struct snd_soc_fw_enum_control *ec;
> +	struct soc_enum *se;
> +	struct snd_kcontrol_new kc;
> +	int i, ret, err, ext;
> +
> +	if (soc_fw_check_control_count(sfw,
> +		sizeof(struct snd_soc_fw_enum_control), count, size)) {
> +		dev_err(sfw->dev, "ASoC: invalid count %d for enum controls\n",
> +			count);
> +		return -EINVAL;
> +	}
> +
> +	for (i = 0; i < count; i++) {
> +		ec = (struct snd_soc_fw_enum_control*)sfw->pos;
> +		sfw->pos += sizeof(struct snd_soc_fw_enum_control);
> +
> +		/* validate kcontrol */
> +		if (strnlen(ec->hdr.name, SND_SOC_FW_TEXT_SIZE) ==
> +			SND_SOC_FW_TEXT_SIZE)
> +			return -EINVAL;
> +
> +		se = kzalloc(sizeof(*se), GFP_KERNEL);
> +		if (!se)
> +			return -ENOMEM;
> +
> +		dev_dbg(sfw->dev, "ASoC: adding enum kcontrol %s size %d\n",
> +			ec->hdr.name, ec->max);
> +
> +		memset(&kc, 0, sizeof(kc));
> +		kc.name = ec->hdr.name;
> +		kc.private_value = (long)se;
> +		kc.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
> +		kc.access = ec->hdr.access;
> +
> +		se->reg = ec->reg;
> +		se->reg2 = ec->reg2;
> +		se->shift_l = ec->shift_l;
> +		se->shift_r = ec->shift_r;
> +		se->max = ec->max;
> +		se->mask = ec->mask;
> +		INIT_LIST_HEAD(&se->list);
> +
> +		switch (SOC_CONTROL_GET_ID_INFO(ec->hdr.index)) {
> +		case SOC_CONTROL_TYPE_ENUM:
> +		case SOC_CONTROL_TYPE_ENUM_EXT:
> +		case SOC_DAPM_TYPE_ENUM_EXT:
> +		case SOC_DAPM_TYPE_ENUM_DOUBLE:
> +		case SOC_DAPM_TYPE_ENUM_VIRT:
> +			err = soc_fw_denum_create_texts(se, ec);
> +			if (err < 0) {
> +				dev_err(sfw->dev, "ASoC: could not create"
> +					" texts for %s\n", ec->hdr.name);
> +				kfree(se);
> +				continue;
> +			}
> +			break;
> +		case SOC_DAPM_TYPE_ENUM_VALUE:
> +		case SOC_CONTROL_TYPE_ENUM_VALUE:
> +			err = soc_fw_denum_create_values(se, ec);
> +			if (err < 0) {
> +				dev_err(sfw->dev, "ASoC: could not create"
> +					" values for %s\n", ec->hdr.name);
> +				kfree(se);
> +				continue;
> +			}
> +			break;
> +		default:
> +			dev_err(sfw->dev, "ASoC: invalid enum control type %d"
> +				" for %s\n", ec->hdr.index, ec->hdr.name);
> +			kfree(se);
> +			continue;
> +		}
> +
> +		/* map standard io handlers and check for external handlers */
> +		ext = soc_fw_kcontrol_bind_io(ec->hdr.index, &kc, io_ops,
> +			ARRAY_SIZE(io_ops));
> +		if (ext) {
> +			/* none exist, so now try and map ext handlers */
> +			ext = soc_fw_kcontrol_bind_io(ec->hdr.index, &kc,
> +				sfw->io_ops, sfw->io_ops_count);
> +			if (ext) {
> +				dev_err(sfw->dev, "ASoC: no complete enum IO handler"
> +					" for %s type (g,p,i) %d:%d:%d\n",
> +					ec->hdr.name,
> +					SOC_CONTROL_GET_ID_GET(ec->hdr.index),
> +					SOC_CONTROL_GET_ID_PUT(ec->hdr.index),
> +					SOC_CONTROL_GET_ID_INFO(ec->hdr.index));
> +				kfree(se);
> +				continue;
> +			}
> +
> +			err = soc_fw_init_kcontrol(sfw, &kc);
> +			if (err < 0) {
> +				dev_err(sfw->dev, "ASoC: failed to init %s\n",
> +					ec->hdr.name);
> +				kfree(se);
> +				continue;
> +			}
> +		}
> +
> +		/* register control here */
> +		ret = soc_fw_add_kcontrol(sfw, &kc, &se->dcontrol);
> +		if (ret < 0) {
> +			dev_err(sfw->dev, "ASoC: could not add kcontrol %s\n",
> +				ec->hdr.name);
> +			kfree(se);
> +			continue;
> +		}
> +
> +		soc_fw_list_add_enum(sfw, se);
> +	}
> +
> +	return 0;
> +}
> +
> +static int soc_fw_kcontrol_load(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
> +{
> +	struct snd_soc_fw_kcontrol *sfwk =
> +		(struct snd_soc_fw_kcontrol*)sfw->pos;
> +	struct snd_soc_fw_control_hdr *control_hdr;
> +	int i;
> +
> +	if (sfw->pass != SOC_FW_PASS_MIXER) {
> +		sfw->pos += sizeof(struct snd_soc_fw_kcontrol) + hdr->size;
> +		return 0;
> +	}
> +
> +	sfw->pos += sizeof(struct snd_soc_fw_kcontrol);
> +	control_hdr = (struct snd_soc_fw_control_hdr*)sfw->pos;
> +
> +	dev_dbg(sfw->dev, "ASoC: adding %d kcontrols\n", sfwk->count);
> +
> +	for (i = 0; i < sfwk->count; i++) {
> +		switch (SOC_CONTROL_GET_ID_INFO(control_hdr->index)) {
> +		case SOC_CONTROL_TYPE_VOLSW:
> +		case SOC_CONTROL_TYPE_STROBE:
> +		case SOC_CONTROL_TYPE_VOLSW_SX:
> +		case SOC_CONTROL_TYPE_VOLSW_S8:
> +		case SOC_CONTROL_TYPE_VOLSW_XR_SX:
> +		case SOC_CONTROL_TYPE_BYTES:
> +		case SOC_CONTROL_TYPE_BOOL_EXT:
> +		case SOC_CONTROL_TYPE_RANGE:
> +		case SOC_DAPM_TYPE_VOLSW:
> +		case SOC_DAPM_TYPE_PIN:
> +			soc_fw_dmixer_create(sfw, 1, hdr->size);
> +			break;
> +		case SOC_CONTROL_TYPE_ENUM:
> +		case SOC_CONTROL_TYPE_ENUM_EXT:
> +		case SOC_CONTROL_TYPE_ENUM_VALUE:
> +		case SOC_DAPM_TYPE_ENUM_DOUBLE:
> +		case SOC_DAPM_TYPE_ENUM_VIRT:
> +		case SOC_DAPM_TYPE_ENUM_VALUE:
> +		case SOC_DAPM_TYPE_ENUM_EXT:
> +			soc_fw_denum_create(sfw, 1, hdr->size);
> +			break;
> +		default:
> +			dev_err(sfw->dev, "ASoC: invalid control type %d:%d:%d count %d\n",
> +				SOC_CONTROL_GET_ID_GET(control_hdr->index),
> +				SOC_CONTROL_GET_ID_PUT(control_hdr->index),
> +				SOC_CONTROL_GET_ID_INFO(control_hdr->index),
> +				sfwk->count);
> +		}
> +	}
> +	return 0;
> +}
> +
> +static int soc_fw_dapm_graph_load(struct soc_fw *sfw,
> +	struct snd_soc_fw_hdr *hdr)
> +{
> +	struct snd_soc_dapm_context *dapm = soc_fw_dapm_get(sfw);
> +	struct snd_soc_dapm_route route;
> +	struct snd_soc_fw_dapm_elems *elem_info =
> +		(struct snd_soc_fw_dapm_elems*)sfw->pos;
> +	struct snd_soc_fw_dapm_graph_elem *elem;
> +	int count = elem_info->count, i;
> +
> +	if (sfw->pass != SOC_FW_PASS_GRAPH) {
> +		sfw->pos += sizeof(struct snd_soc_fw_dapm_elems) + hdr->size;
> +		return 0;
> +	}
> +
> +	sfw->pos += sizeof(struct snd_soc_fw_dapm_elems);
> +
> +	if (soc_fw_check_control_count(sfw,
> +		sizeof(struct snd_soc_fw_dapm_graph_elem), count, hdr->size)) {
> +		dev_err(sfw->dev, "ASoC: invalid count %d for DAPM routes\n",
> +			count);
> +		return -EINVAL;
> +	}
> +
> +	dev_dbg(sfw->dev, "ASoC: adding %d DAPM routes\n", count);
> +
> +	for (i = 0; i < count; i++) {
> +		elem = (struct snd_soc_fw_dapm_graph_elem *)sfw->pos;
> +		sfw->pos += sizeof(struct snd_soc_fw_dapm_graph_elem);
> +
> +		/* validate routes */
> +		if (strnlen(elem->source, SND_SOC_FW_TEXT_SIZE) ==
> +			SND_SOC_FW_TEXT_SIZE)
> +			return -EINVAL;
> +		if (strnlen(elem->sink, SND_SOC_FW_TEXT_SIZE) ==
> +			SND_SOC_FW_TEXT_SIZE)
> +			return -EINVAL;
> +		if (strnlen(elem->control, SND_SOC_FW_TEXT_SIZE) ==
> +			SND_SOC_FW_TEXT_SIZE)
> +			return -EINVAL;
> +
> +		route.source = elem->source;
> +		route.sink = elem->sink;
> +		if (strnlen(elem->control, SND_SOC_FW_TEXT_SIZE) == 0)
> +			route.control = NULL;
> +		else
> +			route.control = elem->control;
> +
> +		/* add route, but keep going if some fail */
> +		snd_soc_dapm_add_routes(dapm, &route, 1);
> +	}
> +
> +	return 0;
> +}
> +
> +static struct snd_kcontrol_new *soc_fw_dapm_widget_dmixer_create(struct soc_fw *sfw,
> +	int num_kcontrols)
> +{
> +	struct snd_kcontrol_new *kc;
> +	struct soc_mixer_control *sm;
> +	struct snd_soc_fw_mixer_control *mc;
> +	int i, err, ext;
> +
> +	kc = kzalloc(sizeof(*kc) * num_kcontrols, GFP_KERNEL);
> +	if (!kc)
> +		return NULL;
> +
> +	for (i = 0; i < num_kcontrols; i++) {
> +		sm = kzalloc(sizeof(*sm), GFP_KERNEL);
> +		if (!sm)
> +			goto err;
> +
> +		mc = (struct snd_soc_fw_mixer_control*)sfw->pos;
> +		sfw->pos += sizeof(struct snd_soc_fw_mixer_control);
> +
> +		/* validate kcontrol */
> +		if (strnlen(mc->hdr.name, SND_SOC_FW_TEXT_SIZE) ==
> +			SND_SOC_FW_TEXT_SIZE)
> +			goto err_str;
> +
> +		dev_dbg(sfw->dev, " adding DAPM widget mixer control %s at %d\n",
> +			mc->hdr.name, i);
> +
> +		kc[i].name = mc->hdr.name;
> +		kc[i].private_value = (long)sm;
> +		kc[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER;
> +		kc[i].access = mc->hdr.access;
> +
> +		sm->reg = mc->reg;
> +		sm->rreg = mc->rreg;
> +		sm->shift = mc->shift;
> +		sm->rshift = mc->rshift;
> +		sm->max = mc->max;
> +		sm->min = mc->min;
> +		sm->invert = mc->invert;
> +		sm->platform_max = mc->platform_max;
> +		INIT_LIST_HEAD(&sm->list);
> +
> +		/* map standard io handlers and check for external handlers */
> +		ext = soc_fw_kcontrol_bind_io(mc->hdr.index, &kc[i], io_ops,
> +			ARRAY_SIZE(io_ops));
> +		if (ext) {
> +			/* none exist, so now try and map ext handlers */
> +			ext = soc_fw_kcontrol_bind_io(mc->hdr.index, &kc[i],
> +				sfw->io_ops, sfw->io_ops_count);
> +			if (ext) {
> +				dev_err(sfw->dev, "ASoC: no complete widget mixer IO handler"
> +					" for %s type (g,p,i) %d:%d:%d\n",
> +					mc->hdr.name,
> +					SOC_CONTROL_GET_ID_GET(mc->hdr.index),
> +					SOC_CONTROL_GET_ID_PUT(mc->hdr.index),
> +					SOC_CONTROL_GET_ID_INFO(mc->hdr.index));
> +				kfree(sm);
> +				continue;
> +			}
> +
> +			err = soc_fw_init_kcontrol(sfw, &kc[i]);
> +			if (err < 0) {
> +				dev_err(sfw->dev, "ASoC: failed to init %s\n",
> +					mc->hdr.name);
> +				kfree(sm);
> +				continue;
> +			}
> +		}
> +	}
> +	return kc;
> +err_str:
> +	kfree(sm);
> +err:
> +	for (--i; i >= 0; i--)
> +		kfree((void*)kc[i].private_value);
> +	kfree(kc);
> +	return NULL;
> +}
> +
> +static struct snd_kcontrol_new *soc_fw_dapm_widget_denum_create(struct soc_fw *sfw)
> +{
> +	struct snd_kcontrol_new *kc;
> +	struct snd_soc_fw_enum_control *ec;
> +	struct soc_enum *se;
> +	int i, err, ext;
> +
> +	ec = (struct snd_soc_fw_enum_control*)sfw->pos;
> +	sfw->pos += sizeof(struct snd_soc_fw_enum_control);
> +
> +	/* validate kcontrol */
> +	if (strnlen(ec->hdr.name, SND_SOC_FW_TEXT_SIZE) ==
> +		SND_SOC_FW_TEXT_SIZE)
> +		return NULL;
> +
> +	kc = kzalloc(sizeof(*kc), GFP_KERNEL);
> +	if (!kc)
> +		return NULL;
> +
> +	se = kzalloc(sizeof(*se), GFP_KERNEL);
> +	if (!se)
> +		goto err_se;
> +
> +	dev_dbg(sfw->dev, " adding DAPM widget enum control %s\n",
> +		ec->hdr.name);
> +
> +	kc->name = ec->hdr.name;
> +	kc->private_value = (long)se;
> +	kc->iface = SNDRV_CTL_ELEM_IFACE_MIXER;
> +	kc->access = ec->hdr.access;
> +
> +	se->reg = ec->reg;
> +	se->reg2 = ec->reg2;
> +	se->shift_l = ec->shift_l;
> +	se->shift_r = ec->shift_r;
> +	se->max = ec->max;
> +	se->mask = ec->mask;
> +
> +	switch (SOC_CONTROL_GET_ID_INFO(ec->hdr.index)) {
> +	case SOC_CONTROL_TYPE_ENUM:
> +	case SOC_CONTROL_TYPE_ENUM_EXT:
> +	case SOC_DAPM_TYPE_ENUM_EXT:
> +	case SOC_DAPM_TYPE_ENUM_DOUBLE:
> +	case SOC_DAPM_TYPE_ENUM_VIRT:
> +		err = soc_fw_denum_create_texts(se, ec);
> +		if (err < 0) {
> +			dev_err(sfw->dev, "ASoC: could not create"
> +				" texts for %s\n", ec->hdr.name);
> +			goto err_se;
> +		}
> +		break;
> +	case SOC_CONTROL_TYPE_ENUM_VALUE:
> +	case SOC_DAPM_TYPE_ENUM_VALUE:
> +		err = soc_fw_denum_create_values(se, ec);
> +		if (err < 0) {
> +			dev_err(sfw->dev, "ASoC: could not create"
> +				" values for %s\n", ec->hdr.name);
> +			goto err_se;
> +		}
> +		break;
> +	default:
> +		dev_err(sfw->dev, "ASoC: invalid enum control type %d for %s\n",
> +			ec->hdr.index, ec->hdr.name);
> +		goto err_se;
> +	}
> +
> +	/* map standard io handlers and check for external handlers */
> +	ext = soc_fw_kcontrol_bind_io(ec->hdr.index, kc, io_ops,
> +		ARRAY_SIZE(io_ops));
> +	if (ext) {
> +		/* none exist, so now try and map ext handlers */
> +		ext = soc_fw_kcontrol_bind_io(ec->hdr.index, kc,
> +			sfw->io_ops, sfw->io_ops_count);
> +		if (ext) {
> +			dev_err(sfw->dev, "ASoC: no complete widget enum IO handler"
> +				" for %s type (g,p,i) %d:%d:%d\n",
> +				ec->hdr.name,
> +				SOC_CONTROL_GET_ID_GET(ec->hdr.index),
> +				SOC_CONTROL_GET_ID_PUT(ec->hdr.index),
> +				SOC_CONTROL_GET_ID_INFO(ec->hdr.index));
> +			goto err_se;
> +		}
> +
> +		err = soc_fw_init_kcontrol(sfw, kc);
> +		if (err < 0) {
> +			dev_err(sfw->dev, "ASoC: failed to init %s\n",
> +				ec->hdr.name);
> +			goto err_se;
> +		}
> +	}
> +	return kc;
> +
> +err_se:
> +	kfree(kc);
> +
> +	/* free texts */
> +	if (se->dvalues)
> +		kfree(se->dvalues);
> +	else {
> +		for (i = 0; i < ec->max; i++)
> +			kfree(se->dtexts[i]);
> +	}
> +	kfree(se);
> +
> +	return NULL;
> +}
> +
> +static int soc_fw_dapm_widget_create(struct soc_fw *sfw,
> +	struct snd_soc_fw_dapm_widget *w)
> +{
> +	struct snd_soc_dapm_context *dapm = soc_fw_dapm_get(sfw);
> +	struct snd_soc_dapm_widget widget;
> +	struct snd_soc_fw_control_hdr *control_hdr;
> +	int ret = 0;
> +
> +	if (strnlen(w->name, SND_SOC_FW_TEXT_SIZE) ==
> +		SND_SOC_FW_TEXT_SIZE)
> +		return -EINVAL;
> +	if (strnlen(w->sname, SND_SOC_FW_TEXT_SIZE) ==
> +		SND_SOC_FW_TEXT_SIZE)
> +		return -EINVAL;
> +
> +	dev_dbg(sfw->dev, "ASoC: creating DAPM widget %s id %d\n",
> +		w->name, w->id);
> +
> +	memset(&widget, 0, sizeof(widget));
> +	widget.id = w->id;
> +	widget.name = kstrdup(w->name, GFP_KERNEL);
> +	if (!widget.name)
> +		return -ENOMEM;
> +	widget.sname = kstrdup(w->sname, GFP_KERNEL);
> +	if (!widget.sname) {
> +		ret = -ENOMEM;
> +		goto err;
> +	}
> +	widget.reg = w->reg;
> +	widget.shift = w->shift;
> +	widget.mask = w->mask;
> +	widget.invert = w->invert;
> +	widget.ignore_suspend = w->ignore_suspend;
> +
> +	sfw->pos += sizeof(struct snd_soc_fw_dapm_widget);
> +	if (w->kcontrol.count == 0) {
> +		widget.num_kcontrols = 0;
> +		goto widget;
> +	}
> +
> +	control_hdr = (struct snd_soc_fw_control_hdr*)sfw->pos;
> +	dev_dbg(sfw->dev, "ASoC: widget %s has %d controls of type %x\n",
> +		w->name, w->kcontrol.count, control_hdr->index);
> +
> +	switch (SOC_CONTROL_GET_ID_INFO(control_hdr->index)) {
> +	case SOC_CONTROL_TYPE_VOLSW:
> +	case SOC_CONTROL_TYPE_STROBE:
> +	case SOC_CONTROL_TYPE_VOLSW_SX:
> +	case SOC_CONTROL_TYPE_VOLSW_S8:
> +	case SOC_CONTROL_TYPE_VOLSW_XR_SX:
> +	case SOC_CONTROL_TYPE_BYTES:
> +	case SOC_CONTROL_TYPE_BOOL_EXT:
> +	case SOC_CONTROL_TYPE_RANGE:
> +	case SOC_DAPM_TYPE_VOLSW:
> +		widget.num_kcontrols = widget.dmixer = w->kcontrol.count;
> +		widget.kcontrol_news = soc_fw_dapm_widget_dmixer_create(sfw,
> +			widget.num_kcontrols);
> +		if (!widget.kcontrol_news) {
> +			ret = -ENOMEM;
> +			goto hdr_err;
> +		}
> +		ret = soc_fw_widget_load(sfw, &widget);
> +		if (ret < 0)
> +			goto hdr_err;
> +		break;
> +	case SOC_CONTROL_TYPE_ENUM:
> +	case SOC_CONTROL_TYPE_ENUM_EXT:
> +	case SOC_CONTROL_TYPE_ENUM_VALUE:
> +	case SOC_DAPM_TYPE_ENUM_DOUBLE:
> +	case SOC_DAPM_TYPE_ENUM_VIRT:
> +	case SOC_DAPM_TYPE_ENUM_VALUE:
> +	case SOC_DAPM_TYPE_ENUM_EXT:
> +		widget.num_kcontrols = widget.denum = 1;
> +		widget.kcontrol_news = soc_fw_dapm_widget_denum_create(sfw);
> +		if (!widget.kcontrol_news) {
> +			ret = -ENOMEM;
> +			goto hdr_err;
> +		}
> +		ret = soc_fw_widget_load(sfw, &widget);
> +		if (ret < 0)
> +			goto hdr_err;
> +		break;
> +	default:
> +		dev_err(sfw->dev, "ASoC: invalid widget control type %d:%d:%d\n",
> +			SOC_CONTROL_GET_ID_GET(control_hdr->index),
> +			SOC_CONTROL_GET_ID_PUT(control_hdr->index),
> +			SOC_CONTROL_GET_ID_INFO(control_hdr->index));
> +		ret = -EINVAL;
> +		goto hdr_err;
> +	}
> +
> +widget:
> +	ret = snd_soc_dapm_new_controls(dapm, &widget, 1);
> +	if (ret < 0) {
> +		dev_err(sfw->dev, "ASoC: failed to create widget %s controls\n",
> +			w->name);
> +		goto hdr_err;
> +	}
> +
> +hdr_err:
> +	kfree(widget.sname);
> +err:
> +	kfree(widget.name);
> +	return ret;
> +}
> +
> +static int soc_fw_dapm_widget_load(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
> +{
> +	struct snd_soc_fw_dapm_elems *elem_info =
> +		(struct snd_soc_fw_dapm_elems*)sfw->pos;
> +	struct snd_soc_fw_dapm_widget *widget;
> +	int ret, count = elem_info->count, i;
> +
> +	if (sfw->pass != SOC_FW_PASS_WIDGET)
> +		return 0;
> +
> +	sfw->pos += sizeof(struct snd_soc_fw_dapm_elems);
> +
> +	if (soc_fw_check_control_count(sfw,
> +		sizeof(struct snd_soc_fw_dapm_graph_elem), count, hdr->size)) {
> +		dev_err(sfw->dev, "ASoC: invalid count %d for widgets\n", count);
> +		return -EINVAL;
> +	}
> +
> +	dev_dbg(sfw->dev, "ASoC: adding %d DAPM widgets\n", count);
> +
> +	for (i = 0; i < count; i++) {
> +		widget = (struct snd_soc_fw_dapm_widget*) sfw->pos;
> +		ret = soc_fw_dapm_widget_create(sfw, widget);
> +		if (ret < 0)
> +			dev_err(sfw->dev, "ASoC: failed to load widget %s\n",
> +				widget->name);
> +	}
> +
> +	return 0;
> +}
> +
> +static int soc_fw_dapm_complete(struct soc_fw *sfw)
> +{
> +	struct snd_soc_dapm_context *dapm = soc_fw_dapm_get(sfw);
> +	int ret;
> +
> +	ret = snd_soc_dapm_new_widgets(dapm);
> +	if (ret < 0)
> +		dev_err(sfw->dev, "ASoC: failed to create new widgets %d\n",
> +			ret);
> +
> +	return ret;
> +}
> +
> +/* Coefficients with mixer header */
> +static int soc_fw_coeff_load(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
> +{
> +	struct snd_soc_fw_kcontrol *sfwk =
> +		(struct snd_soc_fw_kcontrol*)sfw->pos;
> +	struct snd_soc_fw_control_hdr *control_hdr;
> +	struct snd_soc_fw_hdr *vhdr;
> +	int ret;
> +
> +	if (sfw->pass != SOC_FW_PASS_COEFF)
> +		return 0;
> +
> +	/* vendor coefficient data is encapsulated with hdrs in generic
> +	  coefficient controls */
> +	if (hdr->vendor_type != 0)
> +		return 0;
> +
> +	dev_dbg(sfw->dev, "ASoC: got %d new coefficients\n", sfwk->count);
> +
> +	sfw->pos += sizeof(struct snd_soc_fw_kcontrol);
> +	control_hdr = (struct snd_soc_fw_control_hdr*)sfw->pos;
> +
> +	switch (SOC_CONTROL_GET_ID_INFO(control_hdr->index)) {
> +	case SOC_CONTROL_TYPE_ENUM:
> +	case SOC_CONTROL_TYPE_ENUM_EXT:
> +	case SOC_CONTROL_TYPE_ENUM_VALUE:
> +		ret = soc_fw_denum_create(sfw, 1, hdr->size);
> +		if (ret < 0) {
> +			dev_err(sfw->dev, "ASoC: failed to create coeff enum %d\n",
> +				ret);
> +			return ret;
> +		}
> +		break;
> +	default:
> +		dev_err(sfw->dev, "ASoC: invalid coeff control type %d count %d\n",
> +			SOC_CONTROL_GET_ID_INFO(control_hdr->index),
> +			sfwk->count);
> +		return -EINVAL;
> +	}
> +
> +	vhdr = (struct snd_soc_fw_hdr *)sfw->pos;
> +
> +	ret = soc_fw_vendor_load_(sfw, vhdr);
> +	if (ret < 0) {
> +		dev_err(sfw->dev, "ASoC: unabled to load coeff data %d\n", ret);
> +		return ret;
> +	}
> +	sfw->pos += sizeof(*vhdr) + vhdr->size;
> +	vhdr = (struct snd_soc_fw_hdr *)sfw->pos;
> +
> +	return 0;
> +}
> +
> +static int soc_fw_dapm_pin_load(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
> +{
> +	/* TODO: add static enabled/disabled pins */
> +	dev_err(sfw->dev, "ASoC: Firmware pins not supported\n");
> +	return 0;
> +}
> +
> +static int soc_fw_dai_link_load(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
> +{
> +	/* TODO: add DAI links based on FW routing between components */
> +	dev_err(sfw->dev, "ASoC: Firmware DAIs not supported\n");
> +	return 0;
> +}
> +
> +static int soc_valid_header(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
> +{
> +	if (soc_fw_get_hdr_offset(sfw) >= sfw->fw->size)
> +		return 0;
> +
> +	if (hdr->magic != SND_SOC_FW_MAGIC) {
> +		dev_err(sfw->dev, "ASoC: %s at pass %d does not have a valid"
> +			" header got %x at offset 0x%x size 0x%x.\n",
> +			sfw->file, sfw->pass, hdr->magic,
> +			soc_fw_get_hdr_offset(sfw), sfw->fw->size);
> +		return -EINVAL;
> +	}
> +
> +	if (hdr->size == 0) {
> +		dev_err(sfw->dev, "ASoC: %s header has 0 size at offset 0x%x.\n",
> +			sfw->file, soc_fw_get_hdr_offset(sfw));
> +		return -EINVAL;
> +	}
> +
> +	if (sfw->pass == hdr->type)
> +		dev_dbg(sfw->dev, "ASoC: Got 0x%x bytes of type %d version %d"
> +			" vendor %d at pass %d\n", hdr->size, hdr->type,
> +			hdr->version, hdr->vendor_type, sfw->pass);
> +
> +	return 1;
> +}
> +
> +static int soc_fw_load_header(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
> +{
> +	sfw->pos = sfw->hdr_pos + sizeof(struct snd_soc_fw_hdr);
> +
> +	switch (hdr->type) {
> +	case SND_SOC_FW_MIXER:
> +		return soc_fw_kcontrol_load(sfw, hdr);
> +	case SND_SOC_FW_DAPM_GRAPH:
> +		return soc_fw_dapm_graph_load(sfw, hdr);
> +	case SND_SOC_FW_DAPM_PINS:
> +		return soc_fw_dapm_pin_load(sfw, hdr);
> +	case SND_SOC_FW_DAPM_WIDGET:
> +		return soc_fw_dapm_widget_load(sfw, hdr);
> +	case SND_SOC_FW_DAI_LINK:
> +		return soc_fw_dai_link_load(sfw, hdr);
> +	case SND_SOC_FW_COEFF:
> +		return soc_fw_coeff_load(sfw, hdr);
> +	default:
> +		return soc_fw_vendor_load(sfw, hdr);
> +	}
> +
> +	return 0;
> +}
> +
> +static int soc_fw_process_headers(struct soc_fw *sfw)
> +{
> +	struct snd_soc_fw_hdr *hdr;
> +	int ret;
> +
> +	sfw->pass = SOC_FW_PASS_START;
> +
> +	while (sfw->pass <= SOC_FW_PASS_END) {
> +
> +		sfw->hdr_pos = sfw->fw->data;
> +		hdr = (struct snd_soc_fw_hdr *)sfw->hdr_pos;
> +
> +		while (!soc_fw_is_eof(sfw)) {
> +
> +			ret = soc_valid_header(sfw, hdr);
> +			if (ret < 0)
> +				return ret;
> +			else if (ret == 0)
> +				break;
> +
> +			ret = soc_fw_load_header(sfw, hdr);
> +			if (ret < 0)
> +				return ret;
> +
> +			sfw->hdr_pos += hdr->size + sizeof(struct snd_soc_fw_hdr);
> +			hdr = (struct snd_soc_fw_hdr *)sfw->hdr_pos;
> +		}
> +		sfw->pass++;
> +	}
> +
> +	ret = soc_fw_dapm_complete(sfw);
> +	if (ret < 0)
> +		dev_err(sfw->dev, "ASoC: failed to initialise DAPM from Firmware\n");
> +
> +	return ret;
> +}
> +
> +static int soc_fw_load(struct soc_fw *sfw)
> +{
> +	int ret;
> +
> +	ret = soc_fw_process_headers(sfw);
> +	if (ret == 0)
> +		soc_fw_complete(sfw);
> +
> +	return ret;
> +}
> +
> +int snd_soc_fw_load_codec(struct snd_soc_codec *codec,
> +	struct snd_soc_fw_codec_ops *ops, const struct firmware *fw)
> +{
> +	struct soc_fw sfw;
> +
> +	memset(&sfw, 0, sizeof(sfw));
> +
> +	sfw.fw = fw;
> +	sfw.dev = codec->dev;
> +	sfw.codec = codec;
> +	sfw.codec_ops = ops;
> +	sfw.io_ops = ops->io_ops;
> +	sfw.io_ops_count = ops->io_ops_count;
> +
> +	return soc_fw_load(&sfw);
> +}
> +EXPORT_SYMBOL_GPL(snd_soc_fw_load_codec);
> +
> +int snd_soc_fw_load_platform(struct snd_soc_platform *platform,
> +	struct snd_soc_fw_platform_ops *ops, const struct firmware *fw)
> +{
> +	struct soc_fw sfw;
> +
> +	memset(&sfw, 0, sizeof(sfw));
> +
> +	sfw.fw = fw;
> +	sfw.dev = platform->dev;
> +	sfw.platform = platform;
> +	sfw.platform_ops = ops;
> +	sfw.io_ops = ops->io_ops;
> +	sfw.io_ops_count = ops->io_ops_count;
> +
> +	return soc_fw_load(&sfw);
> +}
> +EXPORT_SYMBOL_GPL(snd_soc_fw_load_platform);
> +
> +int snd_soc_fw_load_card(struct snd_soc_card *card,
> +	struct snd_soc_fw_card_ops *ops, const struct firmware *fw)
> +{
> +	struct soc_fw sfw;
> +
> +	memset(&sfw, 0, sizeof(sfw));
> +
> +	sfw.fw = fw;
> +	sfw.dev = card->dev;
> +	sfw.card = card;
> +	sfw.card_ops = ops;
> +	sfw.io_ops = ops->io_ops;
> +	sfw.io_ops_count = ops->io_ops_count;
> +
> +	return soc_fw_load(&sfw);
> +}
> +EXPORT_SYMBOL_GPL(snd_soc_fw_load_card);
> +
> +/* remove this dynamic widget */
> +void snd_soc_fw_dcontrols_remove_widget(struct snd_soc_dapm_widget *w)
> +{
> +	struct snd_card *card = w->dapm->card->snd_card;
> +	int i;
> +
> +	/*
> +	 * Dynamic Widgets either have 1 enum kcontrol or 1..N mixers.
> +	 * The enumm may either have an array of values or strings.
> +	 */
> +	if (w->denum) {
> +		struct soc_enum *se =
> +			(struct soc_enum *)w->kcontrols[0]->private_value;
> +
> +		snd_ctl_remove(card, w->kcontrols[0]);
> +		if (se->dvalues)
> +			kfree(se->dvalues);
> +		else {
> +			for (i = 0; i < se->max; i++)
> +				kfree(se->dtexts[i]);
> +		}
> +
> +		kfree(se);
> +	} else if (w->dmixer) {
> +		for (i = 0; i < w->num_kcontrols; i++) {
> +			struct snd_kcontrol *kcontrol = w->kcontrols[i];
> +			struct soc_mixer_control *sm =
> +			(struct soc_mixer_control *) kcontrol->private_value;
> +
> +			if (w->kcontrols[i]->tlv.p)
> +				kfree(w->kcontrols[i]->tlv.p);
> +
> +			snd_ctl_remove(card, w->kcontrols[i]);
> +			kfree(sm);
> +		}
> +	}
> +	kfree(w->kcontrol_news);
> +}
> +EXPORT_SYMBOL_GPL(snd_soc_fw_dcontrols_remove_widget);
> +
> +/* remove all dynamic widgets from this context */
> +void snd_soc_fw_dcontrols_remove_widgets(struct snd_soc_dapm_context *dapm)
> +{
> +	struct snd_soc_dapm_widget *w, *next_w;
> +	struct snd_soc_dapm_path *p, *next_p;
> +
> +	list_for_each_entry_safe(w, next_w, &dapm->card->widgets, list) {
> +		if (!w->dmixer && !w->denum && w->dapm != dapm)
> +			continue;
> +		list_del(&w->list);
> +		/*
> +		 * remove source and sink paths associated to this widget.
> +		 * While removing the path, remove reference to it from both
> +		 * source and sink widgets so that path is removed only once.
> +		 */
> +		list_for_each_entry_safe(p, next_p, &w->sources, list_sink) {
> +			list_del(&p->list_sink);
> +			list_del(&p->list_source);
> +			list_del(&p->list);
> +			kfree(p->long_name);
> +			kfree(p);
> +		}
> +		list_for_each_entry_safe(p, next_p, &w->sinks, list_source) {
> +			list_del(&p->list_sink);
> +			list_del(&p->list_source);
> +			list_del(&p->list);
> +			kfree(p->long_name);
> +			kfree(p);
> +		}
> +		/* check and free and dynamic widget kcontrols */
> +		snd_soc_fw_dcontrols_remove_widget(w);
> +		kfree(w->kcontrols);
> +		kfree(w->name);
> +		kfree(w);
> +	}
> +}
> +EXPORT_SYMBOL_GPL(snd_soc_fw_dcontrols_remove_widgets);
> +
> +/* remove dynamic controls from the codec driver only */
> +void snd_soc_fw_dcontrols_remove_codec(struct snd_soc_codec *codec)
> +{
> +	struct soc_mixer_control *sm, *next_sm;
> +	struct soc_enum *se, *next_se;
> +	struct snd_card *card = codec->card->snd_card;
> +	const unsigned int *p = NULL;
> +	int i;
> +
> +	list_for_each_entry_safe(sm, next_sm, &codec->dmixers, list) {
> +
> +		if (sm->dcontrol->tlv.p)
> +			p = sm->dcontrol->tlv.p;
> +		snd_ctl_remove(card, sm->dcontrol);
> +		list_del(&sm->list);
> +		kfree(sm);
> +		kfree(p);
> +	}
> +
> +	list_for_each_entry_safe(se, next_se, &codec->denums, list) {
> +
> +		snd_ctl_remove(card, se->dcontrol);
> +		list_del(&se->list);
> +		if (se->dvalues)
> +			kfree(se->dvalues);
> +		else {
> +			for (i = 0; i < se->max; i++)
> +				kfree(se->dtexts[i]);
> +		}
> +		kfree(se);
> +	}
> +}
> +EXPORT_SYMBOL_GPL(snd_soc_fw_dcontrols_remove_codec);
> +
> +/* remove dynamic controls from the platform driver only */
> +void snd_soc_fw_dcontrols_remove_platform(struct snd_soc_platform *platform)
> +{
> +	struct soc_mixer_control *sm, *next_sm;
> +	struct soc_enum *se, *next_se;
> +	struct snd_card *card = platform->card->snd_card;
> +	const unsigned int *p = NULL;
> +	int i;
> +
> +	list_for_each_entry_safe(sm, next_sm, &platform->dmixers, list) {
> +
> +		if (sm->dcontrol->tlv.p)
> +			p = sm->dcontrol->tlv.p;
> +		snd_ctl_remove(card, sm->dcontrol);
> +		list_del(&sm->list);
> +		kfree(sm);
> +		kfree(p);
> +	}
> +
> +	list_for_each_entry_safe(se, next_se, &platform->denums, list) {
> +
> +		snd_ctl_remove(card, se->dcontrol);
> +		list_del(&se->list);
> +		if (se->dvalues)
> +			kfree(se->dvalues);
> +		else {
> +			for (i = 0; i < se->max; i++)
> +				kfree(se->dtexts[i]);
> +		}
> +		kfree(se);
> +	}
> +}
> +EXPORT_SYMBOL_GPL(snd_soc_fw_dcontrols_remove_platform);
> +
> +/* remove dynamic controls from the card driver only */
> +void snd_soc_fw_dcontrols_remove_card(struct snd_soc_card *soc_card)
> +{
> +	struct soc_mixer_control *sm, *next_sm;
> +	struct soc_enum *se, *next_se;
> +	struct snd_card *card = soc_card->snd_card;
> +	const unsigned int *p = NULL;
> +	int i;
> +
> +	list_for_each_entry_safe(sm, next_sm, &soc_card->dmixers, list) {
> +
> +		if (sm->dcontrol->tlv.p)
> +			p = sm->dcontrol->tlv.p;
> +		snd_ctl_remove(card, sm->dcontrol);
> +		list_del(&sm->list);
> +		kfree(sm);
> +		kfree(p);
> +	}
> +
> +	list_for_each_entry_safe(se, next_se, &soc_card->denums, list) {
> +
> +		snd_ctl_remove(card, se->dcontrol);
> +		list_del(&se->list);
> +		if (se->dvalues)
> +			kfree(se->dvalues);
> +		else {
> +			for (i = 0; i < se->max; i++)
> +				kfree(se->dtexts[i]);
> +		}
> +		kfree(se);
> +	}
> +}
> +EXPORT_SYMBOL_GPL(snd_soc_fw_dcontrols_remove_card);
> +
> +/* remove all dynamic controls from sound card and components */
> +int snd_soc_fw_dcontrols_remove_all(struct snd_soc_card *card)
> +{
> +	struct snd_soc_codec *codec;
> +	struct snd_soc_platform *platform;
> +
> +	list_for_each_entry(codec, &card->codec_dev_list, card_list)
> +		snd_soc_fw_dcontrols_remove_codec(codec);
> +
> +	list_for_each_entry(platform, &card->platform_dev_list, card_list)
> +		snd_soc_fw_dcontrols_remove_platform(platform);
> +
> +	snd_soc_fw_dcontrols_remove_card(card);
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(snd_soc_fw_dcontrols_remove_all);
> -- 
> 1.7.10.4
> 
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel at alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
> 


More information about the Alsa-devel mailing list