[alsa-devel] [PATCH 1/4] ASoC: firmware: Add support for FW based kcontrols.
This patch adds initial support for firmware based kcontrols by allowing soc.h to be included by any userspace firmware generation tools and assigns IDs to the standard ASoC kcontrol types using the kcontrol_new index and IDs to kcontrol get/put/info functions.
Signed-off-by: Liam Girdwood lrg@ti.com --- include/sound/soc.h | 135 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 116 insertions(+), 19 deletions(-)
diff --git a/include/sound/soc.h b/include/sound/soc.h index 91244a0..b0b1703 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -13,6 +13,8 @@ #ifndef __LINUX_SND_SOC_H #define __LINUX_SND_SOC_H
+#ifdef __KERNEL__ + #include <linux/platform_device.h> #include <linux/types.h> #include <linux/notifier.h> @@ -27,6 +29,8 @@ #include <sound/control.h> #include <sound/ac97_codec.h>
+#endif + /* * Convenience kcontrol builders */ @@ -51,12 +55,12 @@ #define SOC_SINGLE(xname, reg, shift, max, invert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\ - .put = snd_soc_put_volsw, \ + .put = snd_soc_put_volsw, .index = SOC_CONTROL_IO_VOLSW, \ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } #define SOC_SINGLE_RANGE(xname, xreg, xshift, xmin, xmax, xinvert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ .info = snd_soc_info_volsw_range, .get = snd_soc_get_volsw_range, \ - .put = snd_soc_put_volsw_range, \ + .put = snd_soc_put_volsw_range, .index = SOC_CONTROL_IO_RANGE, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .shift = xshift, .min = xmin,\ .max = xmax, .platform_max = xmax, .invert = xinvert} } @@ -66,7 +70,7 @@ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\ - .put = snd_soc_put_volsw, \ + .put = snd_soc_put_volsw, .index = SOC_CONTROL_IO_VOLSW, \ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } #define SOC_SINGLE_SX_TLV(xname, xreg, xshift, xmin, xmax, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ @@ -76,6 +80,7 @@ .info = snd_soc_info_volsw, \ .get = snd_soc_get_volsw_sx,\ .put = snd_soc_put_volsw_sx, \ + .index = SOC_CONTROL_IO_VOLSW_SX \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .rreg = xreg, \ .shift = xshift, .rshift = xshift, \ @@ -85,7 +90,7 @@ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \ - .info = snd_soc_info_volsw_range, \ + .info = snd_soc_info_volsw_range, .index = SOC_CONTROL_IO_RANGE, \ .get = snd_soc_get_volsw_range, .put = snd_soc_put_volsw_range, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .shift = xshift, .min = xmin,\ @@ -93,19 +98,19 @@ #define SOC_DOUBLE(xname, reg, shift_left, shift_right, max, invert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \ - .put = snd_soc_put_volsw, \ + .put = snd_soc_put_volsw, .index = SOC_CONTROL_IO_VOLSW, \ .private_value = SOC_DOUBLE_VALUE(reg, shift_left, shift_right, \ max, invert) } #define SOC_DOUBLE_R(xname, reg_left, reg_right, xshift, xmax, xinvert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ - .info = snd_soc_info_volsw, \ + .info = snd_soc_info_volsw, .index = SOC_CONTROL_IO_VOLSW, \ .get = snd_soc_get_volsw, .put = snd_soc_put_volsw, \ .private_value = SOC_DOUBLE_R_VALUE(reg_left, reg_right, xshift, \ xmax, xinvert) } #define SOC_DOUBLE_R_RANGE(xname, reg_left, reg_right, xshift, xmin, \ xmax, xinvert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ - .info = snd_soc_info_volsw_range, \ + .info = snd_soc_info_volsw_range, .index = SOC_CONTROL_IO_RANGE, \ .get = snd_soc_get_volsw_range, .put = snd_soc_put_volsw_range, \ .private_value = SOC_DOUBLE_R_RANGE_VALUE(reg_left, reg_right, \ xshift, xmin, xmax, xinvert) } @@ -115,7 +120,7 @@ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \ - .put = snd_soc_put_volsw, \ + .put = snd_soc_put_volsw, .index = SOC_CONTROL_IO_VOLSW, \ .private_value = SOC_DOUBLE_VALUE(reg, shift_left, shift_right, \ max, invert) } #define SOC_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert, tlv_array) \ @@ -123,7 +128,7 @@ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \ - .info = snd_soc_info_volsw, \ + .info = snd_soc_info_volsw, .index = SOC_CONTROL_IO_VOLSW, \ .get = snd_soc_get_volsw, .put = snd_soc_put_volsw, \ .private_value = SOC_DOUBLE_R_VALUE(reg_left, reg_right, xshift, \ xmax, xinvert) } @@ -133,7 +138,7 @@ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \ - .info = snd_soc_info_volsw_range, \ + .info = snd_soc_info_volsw_range, .index = SOC_CONTROL_IO_RANGE, \ .get = snd_soc_get_volsw_range, .put = snd_soc_put_volsw_range, \ .private_value = SOC_DOUBLE_R_RANGE_VALUE(reg_left, reg_right, \ xshift, xmin, xmax, xinvert) } @@ -145,6 +150,7 @@ .info = snd_soc_info_volsw, \ .get = snd_soc_get_volsw_sx, \ .put = snd_soc_put_volsw_sx, \ + .index = SOC_CONTROL_IO_SX, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .rreg = xrreg, \ .shift = xshift, .rshift = xshift, \ @@ -155,7 +161,7 @@ SNDRV_CTL_ELEM_ACCESS_READWRITE, \ .tlv.p = (tlv_array), \ .info = snd_soc_info_volsw_s8, .get = snd_soc_get_volsw_s8, \ - .put = snd_soc_put_volsw_s8, \ + .put = snd_soc_put_volsw_s8, .index = SOC_CONTROL_IO_VOLSW_S8, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .min = xmin, .max = xmax, \ .platform_max = xmax} } @@ -174,7 +180,7 @@ SOC_VALUE_ENUM_DOUBLE(xreg, xshift, xshift, xmask, xmax, xtexts, xvalues) #define SOC_ENUM(xname, xenum) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\ - .info = snd_soc_info_enum_double, \ + .info = snd_soc_info_enum_double, .index = SOC_CONTROL_IO_ENUM, \ .get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double, \ .private_value = (unsigned long)&xenum } #define SOC_VALUE_ENUM(xname, xenum) \ @@ -182,17 +188,18 @@ .info = snd_soc_info_enum_double, \ .get = snd_soc_get_value_enum_double, \ .put = snd_soc_put_value_enum_double, \ + .index = SOC_CONTROL_IO_ENUM_VALUE, \ .private_value = (unsigned long)&xenum } #define SOC_SINGLE_EXT(xname, xreg, xshift, xmax, xinvert,\ xhandler_get, xhandler_put) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .info = snd_soc_info_volsw, \ + .info = snd_soc_info_volsw, .index = SOC_CONTROL_IO_EXT, \ .get = xhandler_get, .put = xhandler_put, \ .private_value = SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) } #define SOC_DOUBLE_EXT(xname, reg, shift_left, shift_right, max, invert,\ xhandler_get, xhandler_put) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ - .info = snd_soc_info_volsw, \ + .info = snd_soc_info_volsw, .index = SOC_CONTROL_IO_EXT, \ .get = xhandler_get, .put = xhandler_put, \ .private_value = \ SOC_DOUBLE_VALUE(reg, shift_left, shift_right, max, invert) } @@ -202,7 +209,7 @@ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \ - .info = snd_soc_info_volsw, \ + .info = snd_soc_info_volsw, .index = SOC_CONTROL_IO_EXT, \ .get = xhandler_get, .put = xhandler_put, \ .private_value = SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) } #define SOC_DOUBLE_EXT_TLV(xname, xreg, shift_left, shift_right, xmax, xinvert,\ @@ -211,7 +218,7 @@ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ SNDRV_CTL_ELEM_ACCESS_READWRITE, \ .tlv.p = (tlv_array), \ - .info = snd_soc_info_volsw, \ + .info = snd_soc_info_volsw, .index = SOC_CONTROL_IO_EXT,\ .get = xhandler_get, .put = xhandler_put, \ .private_value = SOC_DOUBLE_VALUE(xreg, shift_left, shift_right, \ xmax, xinvert) } @@ -221,24 +228,25 @@ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ SNDRV_CTL_ELEM_ACCESS_READWRITE, \ .tlv.p = (tlv_array), \ - .info = snd_soc_info_volsw, \ + .info = snd_soc_info_volsw, .index = SOC_CONTROL_IO_EXT, \ .get = xhandler_get, .put = xhandler_put, \ .private_value = SOC_DOUBLE_R_VALUE(reg_left, reg_right, xshift, \ xmax, xinvert) } #define SOC_SINGLE_BOOL_EXT(xname, xdata, xhandler_get, xhandler_put) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .info = snd_soc_info_bool_ext, \ + .info = snd_soc_info_bool_ext, .index = SOC_CONTROL_IO_BOOL_EXT, \ .get = xhandler_get, .put = xhandler_put, \ .private_value = xdata } #define SOC_ENUM_EXT(xname, xenum, xhandler_get, xhandler_put) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .info = snd_soc_info_enum_ext, \ + .info = snd_soc_info_enum_ext, .index = SOC_CONTROL_IO_ENUM_EXT, \ .get = xhandler_get, .put = xhandler_put, \ .private_value = (unsigned long)&xenum }
#define SND_SOC_BYTES(xname, xbase, xregs) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_bytes_info, .get = snd_soc_bytes_get, \ + .index = SOC_CONTROL_IO_BYTES, \ .put = snd_soc_bytes_put, .private_value = \ ((unsigned long)&(struct soc_bytes) \ {.base = xbase, .num_regs = xregs }) } @@ -247,6 +255,7 @@ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_bytes_info, .get = snd_soc_bytes_get, \ .put = snd_soc_bytes_put, .private_value = \ + .index = SOC_CONTROL_IO_BYTES, \ ((unsigned long)&(struct soc_bytes) \ {.base = xbase, .num_regs = xregs, \ .mask = xmask }) } @@ -256,6 +265,7 @@ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ .info = snd_soc_info_xr_sx, .get = snd_soc_get_xr_sx, \ .put = snd_soc_put_xr_sx, \ + .index = SOC_CONTROL_IO_XR_SX, \ .private_value = (unsigned long)&(struct soc_mreg_control) \ {.regbase = xregbase, .regcount = xregcount, .nbits = xnbits, \ .invert = xinvert, .min = xmin, .max = xmax} } @@ -281,6 +291,92 @@ #define SOC_VALUE_ENUM_SINGLE_DECL(name, xreg, xshift, xmask, xtexts, xvalues) \ SOC_VALUE_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xmask, xtexts, xvalues)
+ +/* + * Numeric IDs for stock mixer types that are used to enumerate FW based mixers. + */ + +#define SOC_CONTROL_ID_PUT(p) ((p & 0xff) << 16) +#define SOC_CONTROL_ID_GET(g) ((g & 0xff) << 8) +#define SOC_CONTROL_ID_INFO(i) ((i & 0xff) << 0) +#define SOC_CONTROL_ID(g, p, i) \ + (SOC_CONTROL_ID_PUT(p) | SOC_CONTROL_ID_GET(g) |\ + SOC_CONTROL_ID_INFO(i)) + +#define SOC_CONTROL_GET_ID_PUT(id) ((id & 0xff0000) >> 16) +#define SOC_CONTROL_GET_ID_GET(id) ((id & 0x00ff00) >> 8) +#define SOC_CONTROL_GET_ID_INFO(id) ((id & 0x0000ff) >> 0) + +/* individual kcontrol info types - can be mixed with other types */ +#define SOC_CONTROL_TYPE_EXT 0 /* driver defined */ +#define SOC_CONTROL_TYPE_VOLSW 1 +#define SOC_CONTROL_TYPE_VOLSW_SX 2 +#define SOC_CONTROL_TYPE_VOLSW_S8 3 +#define SOC_CONTROL_TYPE_VOLSW_XR_SX 4 +#define SOC_CONTROL_TYPE_ENUM 6 +#define SOC_CONTROL_TYPE_ENUM_EXT 7 +#define SOC_CONTROL_TYPE_BYTES 8 +#define SOC_CONTROL_TYPE_BOOL_EXT 9 +#define SOC_CONTROL_TYPE_ENUM_VALUE 10 +#define SOC_CONTROL_TYPE_RANGE 11 +#define SOC_CONTROL_TYPE_STROBE 12 + +/* compound control IDs */ +#define SOC_CONTROL_IO_VOLSW \ + SOC_CONTROL_ID(SOC_CONTROL_TYPE_VOLSW, \ + SOC_CONTROL_TYPE_VOLSW, \ + SOC_CONTROL_TYPE_VOLSW) +#define SOC_CONTROL_IO_VOLSW_SX \ + SOC_CONTROL_ID(SOC_CONTROL_TYPE_VOLSW_SX, \ + SOC_CONTROL_TYPE_VOLSW_SX, \ + SOC_CONTROL_TYPE_VOLSW) +#define SOC_CONTROL_IO_VOLSW_S8 \ + SOC_CONTROL_ID(SOC_CONTROL_TYPE_VOLSW_S8, \ + SOC_CONTROL_TYPE_VOLSW_S8, \ + SOC_CONTROL_TYPE_VOLSW_S8) +#define SOC_CONTROL_IO_VOLSW_XR_SX \ + SOC_CONTROL_ID(SOC_CONTROL_TYPE_VOLSW_XR_SX, \ + SOC_CONTROL_TYPE_VOLSW_XR_SX, \ + SOC_CONTROL_TYPE_VOLSW_XR_SX) +#define SOC_CONTROL_IO_EXT \ + SOC_CONTROL_ID(SOC_CONTROL_TYPE_EXT, \ + SOC_CONTROL_TYPE_EXT, \ + SOC_CONTROL_TYPE_VOLSW) +#define SOC_CONTROL_IO_ENUM \ + SOC_CONTROL_ID(SOC_CONTROL_TYPE_ENUM, \ + SOC_CONTROL_TYPE_ENUM, \ + SOC_CONTROL_TYPE_ENUM) +#define SOC_CONTROL_IO_ENUM_EXT \ + SOC_CONTROL_ID(SOC_CONTROL_TYPE_EXT, \ + SOC_CONTROL_TYPE_EXT, \ + SOC_CONTROL_TYPE_ENUM_EXT) +#define SOC_CONTROL_IO_BYTES \ + SOC_CONTROL_ID(SOC_CONTROL_TYPE_BYTES, \ + SOC_CONTROL_TYPE_BYTES, \ + SOC_CONTROL_TYPE_BYTES) +#define SOC_CONTROL_IO_BOOL_EXT \ + SOC_CONTROL_ID(SOC_CONTROL_TYPE_EXT, \ + SOC_CONTROL_TYPE_EXT, \ + SOC_CONTROL_TYPE_BOOL_EXT) +#define SOC_CONTROL_IO_ENUM_VALUE \ + SOC_CONTROL_ID(SOC_CONTROL_TYPE_ENUM_VALUE, \ + SOC_CONTROL_TYPE_ENUM_VALUE, \ + SOC_CONTROL_TYPE_ENUM) +#define SOC_CONTROL_IO_RANGE \ + SOC_CONTROL_ID(SOC_CONTROL_TYPE_RANGE, \ + SOC_CONTROL_TYPE_RANGE, \ + SOC_CONTROL_TYPE_RANGE) +#define SOC_CONTROL_IO_STROBE \ + SOC_CONTROL_ID(SOC_CONTROL_TYPE_STROBE, \ + SOC_CONTROL_TYPE_STROBE, \ + SOC_CONTROL_TYPE_STROBE) + +#ifdef __KERNEL__ + +#define snd_soc_get_enum_text(soc_enum, idx) \ + (soc_enum->texts ? soc_enum->texts[idx] : soc_enum->dtexts[idx]) + + /* * Component probe and remove ordering levels for components with runtime * dependencies. @@ -1178,3 +1274,4 @@ extern struct dentry *snd_soc_debugfs_root; extern const struct dev_pm_ops snd_soc_pm_ops;
#endif +#endif
This patch adds initial support for firmware based widgets by allowing soc-dapm.h to be included by userspace firmware generation tools and assigns IDs to the standard ASoC widgets kcontrol types using the kcontrol_new index and IDs to kcontrol get/put/info functions.
Signed-off-by: Liam Girdwood lrg@ti.com --- include/sound/soc-dapm.h | 48 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-)
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index e1ef63d..c6c9dad 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -13,8 +13,10 @@ #ifndef __LINUX_SND_SOC_DAPM_H #define __LINUX_SND_SOC_DAPM_H
+#ifdef __KERNEL__ #include <linux/types.h> #include <sound/control.h> +#endif
struct device;
@@ -254,12 +256,12 @@ struct device; /* dapm kcontrol types */ #define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .info = snd_soc_info_volsw, \ + .info = snd_soc_info_volsw, .index = SOC_DAPM_IO_VOLSW, \ .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } #define SOC_DAPM_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .info = snd_soc_info_volsw, \ + .info = snd_soc_info_volsw, .index = SOC_DAPM_IO_VOLSW, \ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \ .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \ @@ -269,16 +271,19 @@ struct device; .info = snd_soc_info_enum_double, \ .get = snd_soc_dapm_get_enum_double, \ .put = snd_soc_dapm_put_enum_double, \ + .index = SOC_DAPM_IO_ENUM_DOUBLE, \ .private_value = (unsigned long)&xenum } #define SOC_DAPM_ENUM_VIRT(xname, xenum) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_enum_double, \ .get = snd_soc_dapm_get_enum_virt, \ .put = snd_soc_dapm_put_enum_virt, \ + .index = SOC_DAPM_IO_ENUM_VIRT, \ .private_value = (unsigned long)&xenum } #define SOC_DAPM_ENUM_EXT(xname, xenum, xget, xput) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_enum_double, \ + .index = SOC_DAPM_IO_ENUM_EXT, \ .get = xget, \ .put = xput, \ .private_value = (unsigned long)&xenum } @@ -287,12 +292,14 @@ struct device; .info = snd_soc_info_enum_double, \ .get = snd_soc_dapm_get_value_enum_double, \ .put = snd_soc_dapm_put_value_enum_double, \ + .index = SOC_DAPM_IO_ENUM_VALUE, \ .private_value = (unsigned long)&xenum } #define SOC_DAPM_PIN_SWITCH(xname) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname " Switch", \ .info = snd_soc_dapm_info_pin_switch, \ .get = snd_soc_dapm_get_pin_switch, \ .put = snd_soc_dapm_put_pin_switch, \ + .index = SOC_DAPM_IO_PIN, \ .private_value = (unsigned long)xname }
/* dapm stream operations */ @@ -323,6 +330,40 @@ struct device; /* regulator widget flags */ #define SND_SOC_DAPM_REGULATOR_BYPASS 0x1 /* bypass when disabled */
+#define SOC_DAPM_TYPE_VOLSW 64 +#define SOC_DAPM_TYPE_ENUM_DOUBLE 65 +#define SOC_DAPM_TYPE_ENUM_VIRT 66 +#define SOC_DAPM_TYPE_ENUM_VALUE 67 +#define SOC_DAPM_TYPE_PIN 68 +#define SOC_DAPM_TYPE_ENUM_EXT 69 + +#define SOC_DAPM_IO_VOLSW \ + SOC_CONTROL_ID(SOC_DAPM_TYPE_VOLSW, \ + SOC_DAPM_TYPE_VOLSW, \ + SOC_DAPM_TYPE_VOLSW) +#define SOC_DAPM_IO_ENUM_DOUBLE \ + SOC_CONTROL_ID(SOC_DAPM_TYPE_ENUM_DOUBLE, \ + SOC_DAPM_TYPE_ENUM_DOUBLE, \ + SOC_CONTROL_TYPE_ENUM) +#define SOC_DAPM_IO_ENUM_VIRT \ + SOC_CONTROL_ID(SOC_DAPM_TYPE_ENUM_VIRT, \ + SOC_DAPM_TYPE_ENUM_VIRT, \ + SOC_CONTROL_TYPE_ENUM) +#define SOC_DAPM_IO_ENUM_VALUE \ + SOC_CONTROL_ID(SOC_DAPM_TYPE_ENUM_VALUE, \ + SOC_DAPM_TYPE_ENUM_VALUE, \ + SOC_CONTROL_TYPE_ENUM) +#define SOC_DAPM_IO_PIN \ + SOC_CONTROL_ID(SOC_DAPM_TYPE_PIN, \ + SOC_DAPM_TYPE_PIN, \ + SOC_DAPM_TYPE_PIN) +#define SOC_DAPM_IO_ENUM_EXT \ + SOC_CONTROL_ID(SOC_CONTROL_TYPE_EXT, \ + SOC_CONTROL_TYPE_EXT, \ + SOC_CONTROL_TYPE_ENUM) + +#ifdef __KERNEL__ + struct snd_soc_dapm_widget; enum snd_soc_dapm_type; struct snd_soc_dapm_path; @@ -421,6 +462,7 @@ void dapm_mark_io_dirty(struct snd_soc_dapm_context *dapm); /* dapm path query */ int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, struct snd_soc_dapm_widget_list **list); +#endif
/* dapm widget types */ enum snd_soc_dapm_type { @@ -459,6 +501,7 @@ enum snd_soc_dapm_subclass { SND_SOC_DAPM_CLASS_RUNTIME = 1, };
+#ifdef __KERNEL__ /* * DAPM audio route definition. * @@ -603,5 +646,6 @@ struct snd_soc_dapm_stats { int path_checks; int neighbour_checks; }; +#endif
#endif
On Mon, Nov 19, 2012 at 06:12:43PM +0000, Liam Girdwood wrote:
This patch adds initial support for firmware based widgets by allowing soc-dapm.h to be included by userspace firmware generation tools and assigns IDs to the standard ASoC widgets kcontrol types using the kcontrol_new index and IDs to kcontrol get/put/info functions.
Applied,t hanks.
Add support to the ASoC and DAPM core to allow firmware based runtime generated kcontrols and widgets to be used along side standard ones. Each widget/component maintains a list of mixer/enum kcontrols that can be removed when required.
Signed-off-by: Liam Girdwood lrg@ti.com --- include/sound/soc-dapm.h | 2 ++ include/sound/soc.h | 21 +++++++++++++++++++++ sound/soc/soc-core.c | 12 ++++++++++-- sound/soc/soc-dapm.c | 14 +++++++------- 4 files changed, 40 insertions(+), 9 deletions(-)
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index c6c9dad..0b1b86a 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -584,6 +584,8 @@ struct snd_soc_dapm_widget { int num_kcontrols; const struct snd_kcontrol_new *kcontrol_news; struct snd_kcontrol **kcontrols; + int denum; + int dmixer;
/* widget input and outputs */ struct list_head sources; diff --git a/include/sound/soc.h b/include/sound/soc.h index b0b1703..bcc2a95 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -803,6 +803,10 @@ struct snd_soc_codec { struct snd_soc_dapm_context dapm; unsigned int ignore_pmdown_time:1; /* pmdown_time is ignored at stop */
+ /* dynamic mixer and enum controls */ + struct list_head dmixers; + struct list_head denums; + #ifdef CONFIG_DEBUG_FS struct dentry *debugfs_codec_root; struct dentry *debugfs_reg; @@ -927,6 +931,10 @@ struct snd_soc_platform { struct list_head list; struct list_head card_list;
+ /* dynamic mixer and enum controls */ + struct list_head dmixers; + struct list_head denums; + struct snd_soc_dapm_context dapm;
#ifdef CONFIG_DEBUG_FS @@ -1107,6 +1115,10 @@ struct snd_soc_card { struct list_head dapm_list; struct list_head dapm_dirty;
+ /* dynamic mixer and enum controls */ + struct list_head dmixers; + struct list_head denums; + /* Generic DAPM context for the card */ struct snd_soc_dapm_context dapm; struct snd_soc_dapm_stats dapm_stats; @@ -1155,6 +1167,10 @@ struct snd_soc_pcm_runtime { struct soc_mixer_control { int min, max, platform_max; unsigned int reg, rreg, shift, rshift, invert; + + /* dynamic controls */ + struct list_head list; + struct snd_kcontrol *dcontrol; };
struct soc_bytes { @@ -1179,6 +1195,11 @@ struct soc_enum { unsigned int mask; const char * const *texts; const unsigned int *values; + /* dynamic enum controls */ + char **dtexts; + unsigned int *dvalues; + struct list_head list; + struct snd_kcontrol *dcontrol; void *dapm; };
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index cee37ee..1f448ab 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2404,7 +2404,8 @@ int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol, if (uinfo->value.enumerated.item > e->max - 1) uinfo->value.enumerated.item = e->max - 1; strcpy(uinfo->value.enumerated.name, - e->texts[uinfo->value.enumerated.item]); + snd_soc_get_enum_text(e, uinfo->value.enumerated.item)); + return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_enum_double); @@ -2564,7 +2565,7 @@ int snd_soc_info_enum_ext(struct snd_kcontrol *kcontrol, if (uinfo->value.enumerated.item > e->max - 1) uinfo->value.enumerated.item = e->max - 1; strcpy(uinfo->value.enumerated.name, - e->texts[uinfo->value.enumerated.item]); + snd_soc_get_enum_text(e, uinfo->value.enumerated.item)); return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_enum_ext); @@ -3600,6 +3601,7 @@ int snd_soc_register_card(struct snd_soc_card *card) if (card->rtd == NULL) return -ENOMEM; card->num_rtd = 0; + card->rtd_aux = &card->rtd[card->num_links];
for (i = 0; i < card->num_links; i++) @@ -3607,6 +3609,8 @@ int snd_soc_register_card(struct snd_soc_card *card)
INIT_LIST_HEAD(&card->list); INIT_LIST_HEAD(&card->dapm_dirty); + INIT_LIST_HEAD(&card->denums); + INIT_LIST_HEAD(&card->dmixers); card->instantiated = 0; mutex_init(&card->mutex); mutex_init(&card->dapm_mutex); @@ -3890,6 +3894,8 @@ int snd_soc_register_platform(struct device *dev, platform->dapm.platform = platform; platform->dapm.stream_event = platform_drv->stream_event; mutex_init(&platform->mutex); + INIT_LIST_HEAD(&platform->denums); + INIT_LIST_HEAD(&platform->dmixers);
mutex_lock(&client_mutex); list_add(&platform->list, &platform_list); @@ -4007,6 +4013,8 @@ int snd_soc_register_codec(struct device *dev, codec->driver = codec_drv; codec->num_dai = num_dai; mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->denums); + INIT_LIST_HEAD(&codec->dmixers);
/* allocate CODEC register cache */ if (codec_drv->reg_cache_size && codec_drv->reg_word_size) { diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 1e36bc8..f456378 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -365,7 +365,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
p->connect = 0; for (i = 0; i < e->max; i++) { - if (!(strcmp(p->name, e->texts[i])) && item == i) + if (!(strcmp(p->name, snd_soc_get_enum_text(e, i))) && item == i) p->connect = 1; } } @@ -381,7 +381,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, * that the default mux choice (the first) will be * correctly powered up during initialization. */ - if (!strcmp(p->name, e->texts[0])) + if (!strcmp(p->name, snd_soc_get_enum_text(e, 0))) p->connect = 1; } break; @@ -399,7 +399,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
p->connect = 0; for (i = 0; i < e->max; i++) { - if (!(strcmp(p->name, e->texts[i])) && item == i) + if (!(strcmp(p->name, snd_soc_get_enum_text(e, i))) && item == i) p->connect = 1; } } @@ -445,11 +445,11 @@ static int dapm_connect_mux(struct snd_soc_dapm_context *dapm, int i;
for (i = 0; i < e->max; i++) { - if (!(strcmp(control_name, e->texts[i]))) { + if (!(strcmp(control_name, snd_soc_get_enum_text(e, i)))) { list_add(&path->list, &dapm->card->paths); list_add(&path->list_sink, &dest->sources); list_add(&path->list_source, &src->sinks); - path->name = (char*)e->texts[i]; + path->name = (char*)snd_soc_get_enum_text(e, i); dapm_set_path_status(dest, path, 0); return 0; } @@ -1909,12 +1909,12 @@ static int soc_dapm_mux_update_power(struct snd_soc_dapm_widget *widget, if (path->kcontrol != kcontrol) continue;
- if (!path->name || !e->texts[mux]) + if (!path->name || !snd_soc_get_enum_text(e, mux)) continue;
found = 1; /* we now need to match the string in the enum to the path */ - if (!(strcmp(path->name, e->texts[mux]))) { + if (!(strcmp(path->name, snd_soc_get_enum_text(e, mux)))) { path->connect = 1; /* new connection */ dapm_mark_dirty(path->source, "mux connection"); } else {
On Mon, Nov 19, 2012 at 06:12:44PM +0000, Liam Girdwood wrote:
@@ -584,6 +584,8 @@ struct snd_soc_dapm_widget { int num_kcontrols; const struct snd_kcontrol_new *kcontrol_news; struct snd_kcontrol **kcontrols;
- int denum;
- int dmixer;
In the patch adding the actual functional code it looks like these are just treated as booleans saying if the control is dynamic. This in conjunction with the fact that we've got a single list of dynamic controls means that we can only have one block of dynamic controls on this device. Is that correct? If not then I've got the wrong end of the stick otherwise...
It occurs to me that if we just assign these to numbers per thing with firmware then we can easily use this data structure to support multiple firmwares per device. We just go through the list and delete all mixers numbered 1 or something. If we do that I think we can simplify the code a little and just make all the mixers and controls "dynamic", using ID 0 for the non-changable ones.
The patch itself looks good but leaving it for now since it's so closely tied in with patch 4, any change in patch 4 is likely to give changes in the data structure I guess.
On 20/11/12 02:36, Mark Brown wrote:
On Mon, Nov 19, 2012 at 06:12:44PM +0000, Liam Girdwood wrote:
@@ -584,6 +584,8 @@ struct snd_soc_dapm_widget { int num_kcontrols; const struct snd_kcontrol_new *kcontrol_news; struct snd_kcontrol **kcontrols;
- int denum;
- int dmixer;
In the patch adding the actual functional code it looks like these are just treated as booleans saying if the control is dynamic. This in conjunction with the fact that we've got a single list of dynamic controls means that we can only have one block of dynamic controls on this device. Is that correct? If not then I've got the wrong end of the stick otherwise...
That's correct atm.
It occurs to me that if we just assign these to numbers per thing with firmware then we can easily use this data structure to support multiple firmwares per device. We just go through the list and delete all mixers numbered 1 or something. If we do that I think we can simplify the code a little and just make all the mixers and controls "dynamic", using ID 0 for the non-changable ones.
Good idea. I'll make this change.
Liam
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@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@ti.com --- 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@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);
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@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@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@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@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On 19/11/12 18:46, Takashi Iwai wrote:
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.
Yeah, that's something we don't convert atm, but we do check with a u32 magic number in each header (and will reject that object if the header is missing or incorrect).
I think we are good with the magic number check atm and I can add some explicit endianess conversion when we have more users. Atm, the only users will be ARM based.
Thanks
Liam
At Tue, 20 Nov 2012 12:03:25 +0000, Liam Girdwood wrote:
On 19/11/12 18:46, Takashi Iwai wrote:
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.
Yeah, that's something we don't convert atm, but we do check with a u32 magic number in each header (and will reject that object if the header is missing or incorrect).
That's what I expected.
Then it'd be helpful if the driver checks about the wrong endian, too, instead of some unknown error.
Takashi
On Tue, Nov 20, 2012 at 12:03:25PM +0000, Liam Girdwood wrote:
I think we are good with the magic number check atm and I can add some explicit endianess conversion when we have more users. Atm, the only users will be ARM based.
It's very likely that Wolfson will pick this up quickly for some of our devices.
Could we make the endianness annotations in the header explicit for now but refuse to handle anything except LE? That way we avoid changing the externally visible interface which would be painful. I'm not so worried about actually implementing this properly, just worried that people will get upset if we change the published ABI.
At Wed, 21 Nov 2012 09:30:42 +0900, Mark Brown wrote:
On Tue, Nov 20, 2012 at 12:03:25PM +0000, Liam Girdwood wrote:
I think we are good with the magic number check atm and I can add some explicit endianess conversion when we have more users. Atm, the only users will be ARM based.
It's very likely that Wolfson will pick this up quickly for some of our devices.
Could we make the endianness annotations in the header explicit for now but refuse to handle anything except LE?
I guess Liam's idea is to check the first 4 magic bytes as endian identifier, too ("ASoC" or "CoSA"), and refuse else than native one. It would work, but I'm not sure whether this is the best form...
That way we avoid changing the externally visible interface which would be painful. I'm not so worried about actually implementing this properly, just worried that people will get upset if we change the published ABI.
Yeah, this firmware spec will have been used for years, so the compatibility issue should be well considered from the beginning.
thanks,
Takashi
On Wed, Nov 21, 2012 at 07:43:03AM +0100, Takashi Iwai wrote:
Mark Brown wrote:
Could we make the endianness annotations in the header explicit for now but refuse to handle anything except LE?
I guess Liam's idea is to check the first 4 magic bytes as endian identifier, too ("ASoC" or "CoSA"), and refuse else than native one. It would work, but I'm not sure whether this is the best form...
Yeah, and I do think that's OK for the implementation - I'm just suggesting to mark up the header with the endian correct types from the get go.
On Mon, Nov 19, 2012 at 06:12:45PM +0000, Liam Girdwood wrote:
Add generic support to create ASoC kcontrols, widgets, graphs and coefficients from
Word wrapping issue with the commit log here...
This is all really good stuff. I've nitpicked below and there's a few bigger things but overall it looks good.
The main thing that's standing out for me is the issue I mentioned in my reply to patch 3 with multiple firmwares per device; we've got a bunch of CODECs in mainline already with several independant DSPs in them which would want to be managed independently. It'd be good to at least write the APIs to support that so that we don't churn the driver APIs too much.
One thing I'm missing is signal generator widgets - those seem like something that's likely to be often software defined. It'd also be good to see a bit more documentation for the file format, like an explanation of what things like coefficients are since we're going to make it an ABI.
With coefficeints we have been moving towards a model where we just expose them as binary controls rather than doing the enumeration thing.
- File andBlock header data types.
Typo.
+/*
- 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 */
+};
As Takashi says we should take care of byte ordering issues with this stuff. For the stuff I've done recently I ended up marking all the structs __packed and using __be32 and similar for the types.
Obviously on-SoC devices will only be used with a single AP so it's not a big deal there but when this is used on external devices we're going to run into issues sooner or later.
+struct snd_soc_fw_ctl_tlv {
- u32 numid; /* control element numeric identification */
- u32 length; /* in bytes aligned to 4 */
- /* tlv data starts here */
+};
This is just raw ALSA TLV data (it's already an ABI after all).
+/*
- Mixer KControl.
- */
kcontrol.
+struct snd_soc_fw_mixer_control {
- struct snd_soc_fw_control_hdr hdr;
- s32 min;
- s32 max;
- s32 platform_max;
I guess platform_max isn't needed here as it's something the platform adds to further restrict capabilities of devices. Though quite how platforms will do that for firmwares is an interesting question in itself :)
+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];
I'm having a hard time visualising how this will get filled in - I'd expect we'd have something more like:
u32 num_values; struct { u32 value; char text[SND_SOC_FW_TEXT_SIZE]; };
- I can't visualise how something with only values would work? But perhaps I'm just missing something.
+/*
- DAPM Pin Element.
- */
+struct snd_soc_fw_dapm_pin_elem {
- char name[SND_SOC_FW_TEXT_SIZE];
- u32 disconnect:1;
- u32 ignore_suspend:1;
+};
I think we should probably just skip pins for now - software defined pins are a bit fun in hardware and
ignore_suspend is a machine driver thing, and if I were a firmware author I'd just be skipping disconnected pins entirely rather than showing people I'd been using firmware resources for things that don't do anything :)
+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();
We should probably have those BUG() calls on the sets too.
+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;
Shouldn't this be returning an error? The same is true for a bunch of other functions. Or shouild it be a dev_dbg()?
+static inline void soc_fw_free_tlv(struct soc_fw *sfw,
- struct snd_kcontrol_new *kc)
+{
- kfree (kc->tlv.p);
Coding style, kfree().
dev_dbg(sfw->dev, "ASoC: adding mixer kcontrol %s with access"
" 0x%x\n", mc->hdr.name, mc->hdr.access);
Don't split log messages like this, makes it harder to grep - just break the 80 columns or wrap after sfw->dev.
if (ext) {
dev_err(sfw->dev, "ASoC: no complete mixer IO"
"handler for %s type (g,p,i) %d:%d:%d\n",
This one is going to be especially bad for example.
- if (se->dvalues)
kfree(se->dvalues);
- else {
Braces on both sides if they're needed on one.
On 20/11/12 03:27, Mark Brown wrote:
One thing I'm missing is signal generator widgets - those seem like something that's likely to be often software defined. It'd also be good to see a bit more documentation for the file format, like an explanation of what things like coefficients are since we're going to make it an ABI.
The coefficients atm can either be a vendor blob or can be a vendor blob attached to a generic kcontrol e.g. and EQU kcontrol with texts that correspond to the coefficients. I'll detail this more in documentation.
With coefficeints we have been moving towards a model where we just expose them as binary controls rather than doing the enumeration thing.
For ABE we have different coefficients tied to each enumerated EQU kcontrol setting and just configure the ABE coefficient depending on the kcontrol value. We load the coefficients and EQU kcontrol at the same time since it's enumerated text strings are coupled to the coefficients.
+struct snd_soc_fw_ctl_tlv {
- u32 numid; /* control element numeric identification */
- u32 length; /* in bytes aligned to 4 */
- /* tlv data starts here */
+};
This is just raw ALSA TLV data (it's already an ABI after all).
True and I think it will be in the new include/uapi/sound too. I may just go and create and move the ASoC userspace stuff into a new ASoC userspace header file that can also be included by soc.h, etc (a bit like asound.h)
+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];
I'm having a hard time visualising how this will get filled in - I'd expect we'd have something more like:
u32 num_values; struct { u32 value; char text[SND_SOC_FW_TEXT_SIZE]; };
- I can't visualise how something with only values would work? But
perhaps I'm just missing something.
We need to store on file, either an array of strings or an array of u32 values for the enumerated kcontrols (based on soc_enum). This gives us a fixed size file entry which is easy to read, write and count. But maybe I'm missing something from your question...
+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;
Shouldn't this be returning an error? The same is true for a bunch of other functions. Or shouild it be a dev_dbg()?
It should probably have a comment. This is an optional callback to component drivers in case they want to use set the widgets private data at widget init time.
dev_dbg(sfw->dev, "ASoC: adding mixer kcontrol %s with access"
" 0x%x\n", mc->hdr.name, mc->hdr.access);
Don't split log messages like this, makes it harder to grep - just break the 80 columns or wrap after sfw->dev.
if (ext) {
dev_err(sfw->dev, "ASoC: no complete mixer IO"
"handler for %s type (g,p,i) %d:%d:%d\n",
This one is going to be especially bad for example.
- if (se->dvalues)
kfree(se->dvalues);
- else {
Braces on both sides if they're needed on one.
The last three points are personal taste, I don't mind too much so I'll change them ;)
Liam
On Tue, Nov 20, 2012 at 03:14:30PM +0000, Liam Girdwood wrote:
On 20/11/12 03:27, Mark Brown wrote:
With coefficeints we have been moving towards a model where we just expose them as binary controls rather than doing the enumeration thing.
For ABE we have different coefficients tied to each enumerated EQU kcontrol setting and just configure the ABE coefficient depending on the kcontrol value. We load the coefficients and EQU kcontrol at the same time since it's enumerated text strings are coupled to the coefficients.
So, the text strings for coefficients were what drivers used to be using
We need to store on file, either an array of strings or an array of u32 values for the enumerated kcontrols (based on soc_enum). This gives us a fixed size file entry which is easy to read, write and count. But maybe I'm missing something from your question...
The issue is that when we have the u32 array we also need the strings to be able to present the control - I would suggest just having the format always include both the numbers and the strings so we don't need to worry about the special case where the numbers are 0, 1, 2... or matching things up if the idea is that we have two separate records.
- dev_info(sfw->dev, "ASoC: no handler specified for ext widget %s\n",
w->name);
- return 0;
Shouldn't this be returning an error? The same is true for a bunch of other functions. Or shouild it be a dev_dbg()?
It should probably have a comment. This is an optional callback to component drivers in case they want to use set the widgets private data at widget init time.
In that case the log message should definitely be dev_dbg().
This one is going to be especially bad for example.
- if (se->dvalues)
kfree(se->dvalues);
- else {
Braces on both sides if they're needed on one.
The last three points are personal taste, I don't mind too much so I'll change them ;)
They're all kernel coding style rules (the issue with breaking log messages is a particular bugbear of Linus IIRC, rmk also gets very upset).
On 21/11/12 00:43, Mark Brown wrote:
On Tue, Nov 20, 2012 at 03:14:30PM +0000, Liam Girdwood wrote:
On 20/11/12 03:27, Mark Brown wrote:
With coefficeints we have been moving towards a model where we just expose them as binary controls rather than doing the enumeration thing.
For ABE we have different coefficients tied to each enumerated EQU kcontrol setting and just configure the ABE coefficient depending on the kcontrol value. We load the coefficients and EQU kcontrol at the same time since it's enumerated text strings are coupled to the coefficients.
So, the text strings for coefficients were what drivers used to be using
Yeah, it will be your standard alsamixer enum kcontrol strings e.g. for the ABE DL1 EQU has "Flat Response", "800Hz HPF 0dB", "800Hz HPF -12dB", "800Hz HPF -20dB", "4000Hz LPF 0dB" where the settings are tied to the associated blob of coefficients.
Liam
On Wed, Nov 21, 2012 at 10:21:17AM +0000, Liam Girdwood wrote:
On 21/11/12 00:43, Mark Brown wrote:
So, the text strings for coefficients were what drivers used to be using
Yeah, it will be your standard alsamixer enum kcontrol strings e.g. for the ABE DL1 EQU has "Flat Response", "800Hz HPF 0dB", "800Hz HPF -12dB", "800Hz HPF -20dB", "4000Hz LPF 0dB" where the settings are tied to the associated blob of coefficients.
Right, but the decision we took at the ASoC conference and have been implementing was to move away from that to just letting applications set the binary blob to change the enumeration value instead and then putting the management of multiple names for the blobs into userspace.
On 21/11/12 10:37, Mark Brown wrote:
On Wed, Nov 21, 2012 at 10:21:17AM +0000, Liam Girdwood wrote:
On 21/11/12 00:43, Mark Brown wrote:
So, the text strings for coefficients were what drivers used to be using
Yeah, it will be your standard alsamixer enum kcontrol strings e.g. for the ABE DL1 EQU has "Flat Response", "800Hz HPF 0dB", "800Hz HPF -12dB", "800Hz HPF -20dB", "4000Hz LPF 0dB" where the settings are tied to the associated blob of coefficients.
Right, but the decision we took at the ASoC conference and have been implementing was to move away from that to just letting applications set the binary blob to change the enumeration value instead and then putting the management of multiple names for the blobs into userspace.
Ok, this is fine and works well for larger or multiple blobs that will be frequently changed at runtime depending on use case. However, the ABE coefficients here (around 400 bytes per EQU) are only loaded once at boot (where the number and graph positions of the EQUs is tightly coupled to the FW) and are not unloaded. The simplification here is that we dont have to write any new userspace code to manage the ABE EQUs and treat them as regular alsamixer controls which makes the integration and testing easier in this case.
Liam
On Wed, Nov 21, 2012 at 11:16:18AM +0000, Liam Girdwood wrote:
On 21/11/12 10:37, Mark Brown wrote:
Right, but the decision we took at the ASoC conference and have been implementing was to move away from that to just letting applications set the binary blob to change the enumeration value instead and then putting the management of multiple names for the blobs into userspace.
Ok, this is fine and works well for larger or multiple blobs that will be frequently changed at runtime depending on use case. However, the ABE coefficients here (around 400 bytes per EQU) are only loaded once at boot (where the number and graph positions of the EQUs is tightly coupled to the FW) and are not unloaded. The simplification here is that we dont have to write any new userspace code to manage the ABE EQUs and treat them as regular alsamixer controls which makes the integration and testing easier in this case.
That's the case for essentially all blobs right now (nobody got round to fixing the ABI limit of 512 bytes!). Are we saying we want to use this infrastructure for all blobs? If that's the case then we definitely need to support multiple files per device...
At Mon, 19 Nov 2012 18:12:42 +0000, Liam Girdwood wrote:
This patch adds initial support for firmware based kcontrols by allowing soc.h to be included by any userspace firmware generation tools and assigns IDs to the standard ASoC kcontrol types using the kcontrol_new index and IDs to kcontrol get/put/info functions.
Signed-off-by: Liam Girdwood lrg@ti.com
The user-space headers are recently moved to include/uapi/sound, so this new stuff should follow that rule.
For the sound stuff, the patch is queued for 3.8 in for-next branch. Please rebase on it.
thanks,
Takashi
include/sound/soc.h | 135 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 116 insertions(+), 19 deletions(-)
diff --git a/include/sound/soc.h b/include/sound/soc.h index 91244a0..b0b1703 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -13,6 +13,8 @@ #ifndef __LINUX_SND_SOC_H #define __LINUX_SND_SOC_H
+#ifdef __KERNEL__
#include <linux/platform_device.h> #include <linux/types.h> #include <linux/notifier.h> @@ -27,6 +29,8 @@ #include <sound/control.h> #include <sound/ac97_codec.h>
+#endif
/*
- Convenience kcontrol builders
*/ @@ -51,12 +55,12 @@ #define SOC_SINGLE(xname, reg, shift, max, invert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
- .put = snd_soc_put_volsw, \
- .put = snd_soc_put_volsw, .index = SOC_CONTROL_IO_VOLSW, \ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
#define SOC_SINGLE_RANGE(xname, xreg, xshift, xmin, xmax, xinvert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ .info = snd_soc_info_volsw_range, .get = snd_soc_get_volsw_range, \
- .put = snd_soc_put_volsw_range, \
- .put = snd_soc_put_volsw_range, .index = SOC_CONTROL_IO_RANGE, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .shift = xshift, .min = xmin,\ .max = xmax, .platform_max = xmax, .invert = xinvert} }
@@ -66,7 +70,7 @@ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
- .put = snd_soc_put_volsw, \
- .put = snd_soc_put_volsw, .index = SOC_CONTROL_IO_VOLSW, \ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
#define SOC_SINGLE_SX_TLV(xname, xreg, xshift, xmin, xmax, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ @@ -76,6 +80,7 @@ .info = snd_soc_info_volsw, \ .get = snd_soc_get_volsw_sx,\ .put = snd_soc_put_volsw_sx, \
- .index = SOC_CONTROL_IO_VOLSW_SX \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .rreg = xreg, \ .shift = xshift, .rshift = xshift, \
@@ -85,7 +90,7 @@ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \
- .info = snd_soc_info_volsw_range, \
- .info = snd_soc_info_volsw_range, .index = SOC_CONTROL_IO_RANGE, \ .get = snd_soc_get_volsw_range, .put = snd_soc_put_volsw_range, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .shift = xshift, .min = xmin,\
@@ -93,19 +98,19 @@ #define SOC_DOUBLE(xname, reg, shift_left, shift_right, max, invert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \
- .put = snd_soc_put_volsw, \
- .put = snd_soc_put_volsw, .index = SOC_CONTROL_IO_VOLSW, \ .private_value = SOC_DOUBLE_VALUE(reg, shift_left, shift_right, \ max, invert) }
#define SOC_DOUBLE_R(xname, reg_left, reg_right, xshift, xmax, xinvert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
- .info = snd_soc_info_volsw, \
- .info = snd_soc_info_volsw, .index = SOC_CONTROL_IO_VOLSW, \ .get = snd_soc_get_volsw, .put = snd_soc_put_volsw, \ .private_value = SOC_DOUBLE_R_VALUE(reg_left, reg_right, xshift, \ xmax, xinvert) }
#define SOC_DOUBLE_R_RANGE(xname, reg_left, reg_right, xshift, xmin, \ xmax, xinvert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
- .info = snd_soc_info_volsw_range, \
- .info = snd_soc_info_volsw_range, .index = SOC_CONTROL_IO_RANGE, \ .get = snd_soc_get_volsw_range, .put = snd_soc_put_volsw_range, \ .private_value = SOC_DOUBLE_R_RANGE_VALUE(reg_left, reg_right, \ xshift, xmin, xmax, xinvert) }
@@ -115,7 +120,7 @@ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \
- .put = snd_soc_put_volsw, \
- .put = snd_soc_put_volsw, .index = SOC_CONTROL_IO_VOLSW, \ .private_value = SOC_DOUBLE_VALUE(reg, shift_left, shift_right, \ max, invert) }
#define SOC_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert, tlv_array) \ @@ -123,7 +128,7 @@ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \
- .info = snd_soc_info_volsw, \
- .info = snd_soc_info_volsw, .index = SOC_CONTROL_IO_VOLSW, \ .get = snd_soc_get_volsw, .put = snd_soc_put_volsw, \ .private_value = SOC_DOUBLE_R_VALUE(reg_left, reg_right, xshift, \ xmax, xinvert) }
@@ -133,7 +138,7 @@ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \
- .info = snd_soc_info_volsw_range, \
- .info = snd_soc_info_volsw_range, .index = SOC_CONTROL_IO_RANGE, \ .get = snd_soc_get_volsw_range, .put = snd_soc_put_volsw_range, \ .private_value = SOC_DOUBLE_R_RANGE_VALUE(reg_left, reg_right, \ xshift, xmin, xmax, xinvert) }
@@ -145,6 +150,7 @@ .info = snd_soc_info_volsw, \ .get = snd_soc_get_volsw_sx, \ .put = snd_soc_put_volsw_sx, \
- .index = SOC_CONTROL_IO_SX, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .rreg = xrreg, \ .shift = xshift, .rshift = xshift, \
@@ -155,7 +161,7 @@ SNDRV_CTL_ELEM_ACCESS_READWRITE, \ .tlv.p = (tlv_array), \ .info = snd_soc_info_volsw_s8, .get = snd_soc_get_volsw_s8, \
- .put = snd_soc_put_volsw_s8, \
- .put = snd_soc_put_volsw_s8, .index = SOC_CONTROL_IO_VOLSW_S8, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .min = xmin, .max = xmax, \ .platform_max = xmax} }
@@ -174,7 +180,7 @@ SOC_VALUE_ENUM_DOUBLE(xreg, xshift, xshift, xmask, xmax, xtexts, xvalues) #define SOC_ENUM(xname, xenum) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\
- .info = snd_soc_info_enum_double, \
- .info = snd_soc_info_enum_double, .index = SOC_CONTROL_IO_ENUM, \ .get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double, \ .private_value = (unsigned long)&xenum }
#define SOC_VALUE_ENUM(xname, xenum) \ @@ -182,17 +188,18 @@ .info = snd_soc_info_enum_double, \ .get = snd_soc_get_value_enum_double, \ .put = snd_soc_put_value_enum_double, \
- .index = SOC_CONTROL_IO_ENUM_VALUE, \ .private_value = (unsigned long)&xenum }
#define SOC_SINGLE_EXT(xname, xreg, xshift, xmax, xinvert,\ xhandler_get, xhandler_put) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
- .info = snd_soc_info_volsw, \
- .info = snd_soc_info_volsw, .index = SOC_CONTROL_IO_EXT, \ .get = xhandler_get, .put = xhandler_put, \ .private_value = SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) }
#define SOC_DOUBLE_EXT(xname, reg, shift_left, shift_right, max, invert,\ xhandler_get, xhandler_put) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
- .info = snd_soc_info_volsw, \
- .info = snd_soc_info_volsw, .index = SOC_CONTROL_IO_EXT, \ .get = xhandler_get, .put = xhandler_put, \ .private_value = \ SOC_DOUBLE_VALUE(reg, shift_left, shift_right, max, invert) }
@@ -202,7 +209,7 @@ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \
- .info = snd_soc_info_volsw, \
- .info = snd_soc_info_volsw, .index = SOC_CONTROL_IO_EXT, \ .get = xhandler_get, .put = xhandler_put, \ .private_value = SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) }
#define SOC_DOUBLE_EXT_TLV(xname, xreg, shift_left, shift_right, xmax, xinvert,\ @@ -211,7 +218,7 @@ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ SNDRV_CTL_ELEM_ACCESS_READWRITE, \ .tlv.p = (tlv_array), \
- .info = snd_soc_info_volsw, \
- .info = snd_soc_info_volsw, .index = SOC_CONTROL_IO_EXT,\ .get = xhandler_get, .put = xhandler_put, \ .private_value = SOC_DOUBLE_VALUE(xreg, shift_left, shift_right, \ xmax, xinvert) }
@@ -221,24 +228,25 @@ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ SNDRV_CTL_ELEM_ACCESS_READWRITE, \ .tlv.p = (tlv_array), \
- .info = snd_soc_info_volsw, \
- .info = snd_soc_info_volsw, .index = SOC_CONTROL_IO_EXT, \ .get = xhandler_get, .put = xhandler_put, \ .private_value = SOC_DOUBLE_R_VALUE(reg_left, reg_right, xshift, \ xmax, xinvert) }
#define SOC_SINGLE_BOOL_EXT(xname, xdata, xhandler_get, xhandler_put) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
- .info = snd_soc_info_bool_ext, \
- .info = snd_soc_info_bool_ext, .index = SOC_CONTROL_IO_BOOL_EXT, \ .get = xhandler_get, .put = xhandler_put, \ .private_value = xdata }
#define SOC_ENUM_EXT(xname, xenum, xhandler_get, xhandler_put) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
- .info = snd_soc_info_enum_ext, \
- .info = snd_soc_info_enum_ext, .index = SOC_CONTROL_IO_ENUM_EXT, \ .get = xhandler_get, .put = xhandler_put, \ .private_value = (unsigned long)&xenum }
#define SND_SOC_BYTES(xname, xbase, xregs) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_bytes_info, .get = snd_soc_bytes_get, \
- .index = SOC_CONTROL_IO_BYTES, \ .put = snd_soc_bytes_put, .private_value = \ ((unsigned long)&(struct soc_bytes) \ {.base = xbase, .num_regs = xregs }) }
@@ -247,6 +255,7 @@ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_bytes_info, .get = snd_soc_bytes_get, \ .put = snd_soc_bytes_put, .private_value = \
- .index = SOC_CONTROL_IO_BYTES, \ ((unsigned long)&(struct soc_bytes) \ {.base = xbase, .num_regs = xregs, \ .mask = xmask }) }
@@ -256,6 +265,7 @@ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ .info = snd_soc_info_xr_sx, .get = snd_soc_get_xr_sx, \ .put = snd_soc_put_xr_sx, \
- .index = SOC_CONTROL_IO_XR_SX, \ .private_value = (unsigned long)&(struct soc_mreg_control) \ {.regbase = xregbase, .regcount = xregcount, .nbits = xnbits, \ .invert = xinvert, .min = xmin, .max = xmax} }
@@ -281,6 +291,92 @@ #define SOC_VALUE_ENUM_SINGLE_DECL(name, xreg, xshift, xmask, xtexts, xvalues) \ SOC_VALUE_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xmask, xtexts, xvalues)
+/*
- Numeric IDs for stock mixer types that are used to enumerate FW based mixers.
- */
+#define SOC_CONTROL_ID_PUT(p) ((p & 0xff) << 16) +#define SOC_CONTROL_ID_GET(g) ((g & 0xff) << 8) +#define SOC_CONTROL_ID_INFO(i) ((i & 0xff) << 0) +#define SOC_CONTROL_ID(g, p, i) \
- (SOC_CONTROL_ID_PUT(p) | SOC_CONTROL_ID_GET(g) |\
- SOC_CONTROL_ID_INFO(i))
+#define SOC_CONTROL_GET_ID_PUT(id) ((id & 0xff0000) >> 16) +#define SOC_CONTROL_GET_ID_GET(id) ((id & 0x00ff00) >> 8) +#define SOC_CONTROL_GET_ID_INFO(id) ((id & 0x0000ff) >> 0)
+/* individual kcontrol info types - can be mixed with other types */ +#define SOC_CONTROL_TYPE_EXT 0 /* driver defined */ +#define SOC_CONTROL_TYPE_VOLSW 1 +#define SOC_CONTROL_TYPE_VOLSW_SX 2 +#define SOC_CONTROL_TYPE_VOLSW_S8 3 +#define SOC_CONTROL_TYPE_VOLSW_XR_SX 4 +#define SOC_CONTROL_TYPE_ENUM 6 +#define SOC_CONTROL_TYPE_ENUM_EXT 7 +#define SOC_CONTROL_TYPE_BYTES 8 +#define SOC_CONTROL_TYPE_BOOL_EXT 9 +#define SOC_CONTROL_TYPE_ENUM_VALUE 10 +#define SOC_CONTROL_TYPE_RANGE 11 +#define SOC_CONTROL_TYPE_STROBE 12
+/* compound control IDs */ +#define SOC_CONTROL_IO_VOLSW \
- SOC_CONTROL_ID(SOC_CONTROL_TYPE_VOLSW, \
SOC_CONTROL_TYPE_VOLSW, \
SOC_CONTROL_TYPE_VOLSW)
+#define SOC_CONTROL_IO_VOLSW_SX \
- SOC_CONTROL_ID(SOC_CONTROL_TYPE_VOLSW_SX, \
SOC_CONTROL_TYPE_VOLSW_SX, \
SOC_CONTROL_TYPE_VOLSW)
+#define SOC_CONTROL_IO_VOLSW_S8 \
- SOC_CONTROL_ID(SOC_CONTROL_TYPE_VOLSW_S8, \
SOC_CONTROL_TYPE_VOLSW_S8, \
SOC_CONTROL_TYPE_VOLSW_S8)
+#define SOC_CONTROL_IO_VOLSW_XR_SX \
- SOC_CONTROL_ID(SOC_CONTROL_TYPE_VOLSW_XR_SX, \
SOC_CONTROL_TYPE_VOLSW_XR_SX, \
SOC_CONTROL_TYPE_VOLSW_XR_SX)
+#define SOC_CONTROL_IO_EXT \
- SOC_CONTROL_ID(SOC_CONTROL_TYPE_EXT, \
SOC_CONTROL_TYPE_EXT, \
SOC_CONTROL_TYPE_VOLSW)
+#define SOC_CONTROL_IO_ENUM \
- SOC_CONTROL_ID(SOC_CONTROL_TYPE_ENUM, \
SOC_CONTROL_TYPE_ENUM, \
SOC_CONTROL_TYPE_ENUM)
+#define SOC_CONTROL_IO_ENUM_EXT \
- SOC_CONTROL_ID(SOC_CONTROL_TYPE_EXT, \
SOC_CONTROL_TYPE_EXT, \
SOC_CONTROL_TYPE_ENUM_EXT)
+#define SOC_CONTROL_IO_BYTES \
- SOC_CONTROL_ID(SOC_CONTROL_TYPE_BYTES, \
SOC_CONTROL_TYPE_BYTES, \
SOC_CONTROL_TYPE_BYTES)
+#define SOC_CONTROL_IO_BOOL_EXT \
- SOC_CONTROL_ID(SOC_CONTROL_TYPE_EXT, \
SOC_CONTROL_TYPE_EXT, \
SOC_CONTROL_TYPE_BOOL_EXT)
+#define SOC_CONTROL_IO_ENUM_VALUE \
- SOC_CONTROL_ID(SOC_CONTROL_TYPE_ENUM_VALUE, \
SOC_CONTROL_TYPE_ENUM_VALUE, \
SOC_CONTROL_TYPE_ENUM)
+#define SOC_CONTROL_IO_RANGE \
- SOC_CONTROL_ID(SOC_CONTROL_TYPE_RANGE, \
SOC_CONTROL_TYPE_RANGE, \
SOC_CONTROL_TYPE_RANGE)
+#define SOC_CONTROL_IO_STROBE \
- SOC_CONTROL_ID(SOC_CONTROL_TYPE_STROBE, \
SOC_CONTROL_TYPE_STROBE, \
SOC_CONTROL_TYPE_STROBE)
+#ifdef __KERNEL__
+#define snd_soc_get_enum_text(soc_enum, idx) \
- (soc_enum->texts ? soc_enum->texts[idx] : soc_enum->dtexts[idx])
/*
- Component probe and remove ordering levels for components with runtime
- dependencies.
@@ -1178,3 +1274,4 @@ extern struct dentry *snd_soc_debugfs_root; extern const struct dev_pm_ops snd_soc_pm_ops;
#endif
+#endif
1.7.10.4
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On 19/11/12 18:36, Takashi Iwai wrote:
At Mon, 19 Nov 2012 18:12:42 +0000, Liam Girdwood wrote:
This patch adds initial support for firmware based kcontrols by allowing soc.h to be included by any userspace firmware generation tools and assigns IDs to the standard ASoC kcontrol types using the kcontrol_new index and IDs to kcontrol get/put/info functions.
Signed-off-by: Liam Girdwood lrg@ti.com
The user-space headers are recently moved to include/uapi/sound, so this new stuff should follow that rule.
For the sound stuff, the patch is queued for 3.8 in for-next branch. Please rebase on it.
Done, but I'm now seeing some type conflict errors when building my userspace tools (i.e. after moving to include uapi/sound/asound.h from sound/asound.h).
e.g.
#include <stdlib.h> #include <uapi/sound/asound.h>
int main () { }
gives :-
gcc uapi-test.c -I ~/source/linux.git/include In file included from /home/lrg/source/linux.git/include/uapi/sound/asound.h:26:0, from uapi-test.c:2: /home/lrg/source/linux.git/include/linux/types.h:14:26: error: conflicting types for ‘fd_set’ In file included from /usr/include/x86_64-linux-gnu/sys/types.h:220:0, from /usr/include/stdlib.h:320, from uapi-test.c:1: /usr/include/x86_64-linux-gnu/sys/select.h:76:5: note: previous declaration of ‘fd_set’ was here In file included from /home/lrg/source/linux.git/include/uapi/sound/asound.h:26:0, from uapi-test.c:2: /home/lrg/source/linux.git/include/linux/types.h:15:25: error: conflicting types for ‘dev_t’ In file included from /usr/include/stdlib.h:320:0, from uapi-test.c:1: /usr/include/x86_64-linux-gnu/sys/types.h:61:17: note: previous declaration of ‘dev_t’ was here
etc.....
I'm guessing here you have this building already with uapi alsa-lib ? Although I cant see any alsa-lib changes in git to use the new header.
Thanks
Liam
At Thu, 29 Nov 2012 12:08:18 +0000, Liam Girdwood wrote:
On 19/11/12 18:36, Takashi Iwai wrote:
At Mon, 19 Nov 2012 18:12:42 +0000, Liam Girdwood wrote:
This patch adds initial support for firmware based kcontrols by allowing soc.h to be included by any userspace firmware generation tools and assigns IDs to the standard ASoC kcontrol types using the kcontrol_new index and IDs to kcontrol get/put/info functions.
Signed-off-by: Liam Girdwood lrg@ti.com
The user-space headers are recently moved to include/uapi/sound, so this new stuff should follow that rule.
For the sound stuff, the patch is queued for 3.8 in for-next branch. Please rebase on it.
Done, but I'm now seeing some type conflict errors when building my userspace tools (i.e. after moving to include uapi/sound/asound.h from sound/asound.h).
e.g.
#include <stdlib.h> #include <uapi/sound/asound.h>
int main () { }
gives :-
gcc uapi-test.c -I ~/source/linux.git/include
Pass -I ~/source/linux.git/include/uapi
then
#include <sound/asound.h>
Takashi
In file included from /home/lrg/source/linux.git/include/uapi/sound/asound.h:26:0, from uapi-test.c:2: /home/lrg/source/linux.git/include/linux/types.h:14:26: error: conflicting types for ‘fd_set’ In file included from /usr/include/x86_64-linux-gnu/sys/types.h:220:0, from /usr/include/stdlib.h:320, from uapi-test.c:1: /usr/include/x86_64-linux-gnu/sys/select.h:76:5: note: previous declaration of ‘fd_set’ was here In file included from /home/lrg/source/linux.git/include/uapi/sound/asound.h:26:0, from uapi-test.c:2: /home/lrg/source/linux.git/include/linux/types.h:15:25: error: conflicting types for ‘dev_t’ In file included from /usr/include/stdlib.h:320:0, from uapi-test.c:1: /usr/include/x86_64-linux-gnu/sys/types.h:61:17: note: previous declaration of ‘dev_t’ was here
etc.....
I'm guessing here you have this building already with uapi alsa-lib ? Although I cant see any alsa-lib changes in git to use the new header.
Thanks
Liam
On 29/11/12 15:15, Takashi Iwai wrote:
At Thu, 29 Nov 2012 12:08:18 +0000, Liam Girdwood wrote:
On 19/11/12 18:36, Takashi Iwai wrote:
For the sound stuff, the patch is queued for 3.8 in for-next branch. Please rebase on it.
Done, but I'm now seeing some type conflict errors when building my userspace tools (i.e. after moving to include uapi/sound/asound.h from sound/asound.h).
e.g.
#include <stdlib.h> #include <uapi/sound/asound.h>
int main () { }
gives :-
gcc uapi-test.c -I ~/source/linux.git/include
Pass -I ~/source/linux.git/include/uapi
then
#include <sound/asound.h>
Ah, I did initially try this before emailing :-
gcc uapi-test.c -I ~/source/linux.git/include/uapi In file included from /home/lrg/source/linux.git/include/uapi/sound/asound.h:26:0, from uapi-test.c:2: /home/lrg/source/linux.git/include/uapi/linux/types.h:9:2: warning: #warning "Attempt to use kernel headers from user space, see http://kernelnewbies.org/KernelHeaders" [-Wcpp] In file included from /home/lrg/source/linux.git/include/uapi/linux/posix_types.h:4:0, from /home/lrg/source/linux.git/include/uapi/linux/types.h:13, from /home/lrg/source/linux.git/include/uapi/sound/asound.h:26, from uapi-test.c:2: /home/lrg/source/linux.git/include/uapi/linux/stddef.h:1:28: fatal error: linux/compiler.h: No such file or directory
adding -D__EXPORTED_HEADERS__ to gcc command line gets rid of the warning, but it seems we are missing compiler.h (even in todays linux-next).
If this works for you, it may be an issue at my end.
Thanks
Liam
On 29/11/12 15:51, Liam Girdwood wrote:
On 29/11/12 15:15, Takashi Iwai wrote:
At Thu, 29 Nov 2012 12:08:18 +0000, Liam Girdwood wrote:
On 19/11/12 18:36, Takashi Iwai wrote:
For the sound stuff, the patch is queued for 3.8 in for-next branch. Please rebase on it.
Done, but I'm now seeing some type conflict errors when building my userspace tools (i.e. after moving to include uapi/sound/asound.h from sound/asound.h).
e.g.
#include <stdlib.h> #include <uapi/sound/asound.h>
int main () { }
gives :-
gcc uapi-test.c -I ~/source/linux.git/include
Pass -I ~/source/linux.git/include/uapi
then
#include <sound/asound.h>
Ah, I did initially try this before emailing :-
gcc uapi-test.c -I ~/source/linux.git/include/uapi In file included from /home/lrg/source/linux.git/include/uapi/sound/asound.h:26:0, from uapi-test.c:2: /home/lrg/source/linux.git/include/uapi/linux/types.h:9:2: warning: #warning "Attempt to use kernel headers from user space, see http://kernelnewbies.org/KernelHeaders" [-Wcpp] In file included from /home/lrg/source/linux.git/include/uapi/linux/posix_types.h:4:0, from /home/lrg/source/linux.git/include/uapi/linux/types.h:13, from /home/lrg/source/linux.git/include/uapi/sound/asound.h:26, from uapi-test.c:2: /home/lrg/source/linux.git/include/uapi/linux/stddef.h:1:28: fatal error: linux/compiler.h: No such file or directory
adding -D__EXPORTED_HEADERS__ to gcc command line gets rid of the warning, but it seems we are missing compiler.h (even in todays linux-next).
If this works for you, it may be an issue at my end.
In fact adding the old include path at the end works, but is probably an interim fix until the uapi work is completed.
gcc uapi-test.c -I ~/source/linux.git/include/uapi -D__EXPORTED_HEADERS__ -I /home/lrg/source/linux.git/include/
Thanks
Liam
At Thu, 29 Nov 2012 16:01:25 +0000, Liam Girdwood wrote:
On 29/11/12 15:51, Liam Girdwood wrote:
On 29/11/12 15:15, Takashi Iwai wrote:
At Thu, 29 Nov 2012 12:08:18 +0000, Liam Girdwood wrote:
On 19/11/12 18:36, Takashi Iwai wrote:
For the sound stuff, the patch is queued for 3.8 in for-next branch. Please rebase on it.
Done, but I'm now seeing some type conflict errors when building my userspace tools (i.e. after moving to include uapi/sound/asound.h from sound/asound.h).
e.g.
#include <stdlib.h> #include <uapi/sound/asound.h>
int main () { }
gives :-
gcc uapi-test.c -I ~/source/linux.git/include
Pass -I ~/source/linux.git/include/uapi
then
#include <sound/asound.h>
Ah, I did initially try this before emailing :-
gcc uapi-test.c -I ~/source/linux.git/include/uapi In file included from /home/lrg/source/linux.git/include/uapi/sound/asound.h:26:0, from uapi-test.c:2: /home/lrg/source/linux.git/include/uapi/linux/types.h:9:2: warning: #warning "Attempt to use kernel headers from user space, see http://kernelnewbies.org/KernelHeaders" [-Wcpp] In file included from /home/lrg/source/linux.git/include/uapi/linux/posix_types.h:4:0, from /home/lrg/source/linux.git/include/uapi/linux/types.h:13, from /home/lrg/source/linux.git/include/uapi/sound/asound.h:26, from uapi-test.c:2: /home/lrg/source/linux.git/include/uapi/linux/stddef.h:1:28: fatal error: linux/compiler.h: No such file or directory
adding -D__EXPORTED_HEADERS__ to gcc command line gets rid of the warning, but it seems we are missing compiler.h (even in todays linux-next).
If this works for you, it may be an issue at my end.
In fact adding the old include path at the end works, but is probably an interim fix until the uapi work is completed.
gcc uapi-test.c -I ~/source/linux.git/include/uapi -D__EXPORTED_HEADERS__ -I /home/lrg/source/linux.git/include/
Well, actually the content of uapi/sound/asound.h won't be changed.
Looking at this issue more closely, I found that glibc package also provides the copy of linux kernel headers as well, but they are stripped. And the stripped form is almost what we have now in include/uapi/*. The difference is that all ifdef KERNEL will be stripped, thus the check of non-kernel access in linux/types.h is also stripped.
That being said, once when kernel uapi things get merged to glibc, everything will be fine.... Well, I hope.
Takashi
On 29/11/12 17:35, Takashi Iwai wrote:
At Thu, 29 Nov 2012 16:01:25 +0000, Liam Girdwood wrote:
On 29/11/12 15:51, Liam Girdwood wrote:
In fact adding the old include path at the end works, but is probably an interim fix until the uapi work is completed.
gcc uapi-test.c -I ~/source/linux.git/include/uapi -D__EXPORTED_HEADERS__ -I /home/lrg/source/linux.git/include/
Well, actually the content of uapi/sound/asound.h won't be changed.
Sorry, I was meaning the other uapi includes here. Like compiler.h
Looking at this issue more closely, I found that glibc package also provides the copy of linux kernel headers as well, but they are stripped. And the stripped form is almost what we have now in include/uapi/*. The difference is that all ifdef KERNEL will be stripped, thus the check of non-kernel access in linux/types.h is also stripped.
That being said, once when kernel uapi things get merged to glibc, everything will be fine.... Well, I hope.
Hehe, I hope too.
Thanks
Liam
On Mon, Nov 19, 2012 at 06:12:42PM +0000, Liam Girdwood wrote:
This patch adds initial support for firmware based kcontrols by allowing soc.h to be included by any userspace firmware generation tools and assigns IDs to the standard ASoC kcontrol types using the kcontrol_new index and IDs to kcontrol get/put/info functions.
Applied, thanks.
Should really be two patches, one for the new ifdefs and one for adding the IDs.
participants (3)
-
Liam Girdwood
-
Mark Brown
-
Takashi Iwai