diff options
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/Makefile | 1 | ||||
-rw-r--r-- | sound/soc/codecs/twl6040.c | 3 | ||||
-rw-r--r-- | sound/soc/codecs/wm5100.c | 6 | ||||
-rw-r--r-- | sound/soc/codecs/wm8741.c | 129 | ||||
-rw-r--r-- | sound/soc/codecs/wm8741.h | 10 | ||||
-rw-r--r-- | sound/soc/codecs/wm8960.c | 109 | ||||
-rw-r--r-- | sound/soc/soc-core.c | 4 | ||||
-rw-r--r-- | sound/soc/soc-topology.c | 1826 |
8 files changed, 2071 insertions, 17 deletions
diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 974ba708b482..c2ef1ecefcbd 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -1,5 +1,6 @@ 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 soc-devres.o soc-ops.o +snd-soc-core-objs += soc-topology.o ifneq ($(CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM),) snd-soc-core-objs += soc-generic-dmaengine-pcm.o diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 9db7408f6e05..4cad8929d262 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -1121,7 +1121,8 @@ static int twl6040_probe(struct snd_soc_codec *codec) mutex_init(&priv->mutex); ret = request_threaded_irq(priv->plug_irq, NULL, - twl6040_audio_handler, IRQF_NO_SUSPEND, + twl6040_audio_handler, + IRQF_NO_SUSPEND | IRQF_ONESHOT, "twl6040_irq_plug", codec); if (ret) { dev_err(codec->dev, "PLUG IRQ request failed: %d\n", ret); diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c index 98495dd61239..4c10cd88c1af 100644 --- a/sound/soc/codecs/wm5100.c +++ b/sound/soc/codecs/wm5100.c @@ -2570,11 +2570,13 @@ static int wm5100_i2c_probe(struct i2c_client *i2c, if (irq_flags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) ret = request_threaded_irq(i2c->irq, NULL, - wm5100_edge_irq, irq_flags, + wm5100_edge_irq, + irq_flags | IRQF_ONESHOT, "wm5100", wm5100); else ret = request_threaded_irq(i2c->irq, NULL, wm5100_irq, - irq_flags, "wm5100", + irq_flags | IRQF_ONESHOT, + "wm5100", wm5100); if (ret != 0) { diff --git a/sound/soc/codecs/wm8741.c b/sound/soc/codecs/wm8741.c index 9e71c768966f..09ff01f2fc1e 100644 --- a/sound/soc/codecs/wm8741.c +++ b/sound/soc/codecs/wm8741.c @@ -41,6 +41,7 @@ static const char *wm8741_supply_names[WM8741_NUM_SUPPLIES] = { /* codec private data */ struct wm8741_priv { + struct wm8741_platform_data pdata; struct regmap *regmap; struct regulator_bulk_data supplies[WM8741_NUM_SUPPLIES]; unsigned int sysclk; @@ -87,13 +88,27 @@ static int wm8741_reset(struct snd_soc_codec *codec) static const DECLARE_TLV_DB_SCALE(dac_tlv_fine, -12700, 13, 0); static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 400, 0); -static const struct snd_kcontrol_new wm8741_snd_controls[] = { +static const struct snd_kcontrol_new wm8741_snd_controls_stereo[] = { SOC_DOUBLE_R_TLV("Fine Playback Volume", WM8741_DACLLSB_ATTENUATION, WM8741_DACRLSB_ATTENUATION, 1, 255, 1, dac_tlv_fine), SOC_DOUBLE_R_TLV("Playback Volume", WM8741_DACLMSB_ATTENUATION, WM8741_DACRMSB_ATTENUATION, 0, 511, 1, dac_tlv), }; +static const struct snd_kcontrol_new wm8741_snd_controls_mono_left[] = { +SOC_SINGLE_TLV("Fine Playback Volume", WM8741_DACLLSB_ATTENUATION, + 1, 255, 1, dac_tlv_fine), +SOC_SINGLE_TLV("Playback Volume", WM8741_DACLMSB_ATTENUATION, + 0, 511, 1, dac_tlv), +}; + +static const struct snd_kcontrol_new wm8741_snd_controls_mono_right[] = { +SOC_SINGLE_TLV("Fine Playback Volume", WM8741_DACRLSB_ATTENUATION, + 1, 255, 1, dac_tlv_fine), +SOC_SINGLE_TLV("Playback Volume", WM8741_DACRMSB_ATTENUATION, + 0, 511, 1, dac_tlv), +}; + static const struct snd_soc_dapm_widget wm8741_dapm_widgets[] = { SND_SOC_DAPM_DAC("DACL", "Playback", SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_DAC("DACR", "Playback", SND_SOC_NOPM, 0, 0), @@ -398,7 +413,7 @@ static struct snd_soc_dai_driver wm8741_dai = { .name = "wm8741", .playback = { .stream_name = "Playback", - .channels_min = 2, /* Mono modes not yet supported */ + .channels_min = 2, .channels_max = 2, .rates = WM8741_RATES, .formats = WM8741_FORMATS, @@ -416,6 +431,65 @@ static int wm8741_resume(struct snd_soc_codec *codec) #define wm8741_resume NULL #endif +static int wm8741_configure(struct snd_soc_codec *codec) +{ + struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec); + + /* Configure differential mode */ + switch (wm8741->pdata.diff_mode) { + case WM8741_DIFF_MODE_STEREO: + case WM8741_DIFF_MODE_STEREO_REVERSED: + case WM8741_DIFF_MODE_MONO_LEFT: + case WM8741_DIFF_MODE_MONO_RIGHT: + snd_soc_update_bits(codec, WM8741_MODE_CONTROL_2, + WM8741_DIFF_MASK, + wm8741->pdata.diff_mode << WM8741_DIFF_SHIFT); + break; + default: + return -EINVAL; + } + + /* Change some default settings - latch VU */ + snd_soc_update_bits(codec, WM8741_DACLLSB_ATTENUATION, + WM8741_UPDATELL, WM8741_UPDATELL); + snd_soc_update_bits(codec, WM8741_DACLMSB_ATTENUATION, + WM8741_UPDATELM, WM8741_UPDATELM); + snd_soc_update_bits(codec, WM8741_DACRLSB_ATTENUATION, + WM8741_UPDATERL, WM8741_UPDATERL); + snd_soc_update_bits(codec, WM8741_DACRMSB_ATTENUATION, + WM8741_UPDATERM, WM8741_UPDATERM); + + return 0; +} + +static int wm8741_add_controls(struct snd_soc_codec *codec) +{ + struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec); + + switch (wm8741->pdata.diff_mode) { + case WM8741_DIFF_MODE_STEREO: + case WM8741_DIFF_MODE_STEREO_REVERSED: + snd_soc_add_codec_controls(codec, + wm8741_snd_controls_stereo, + ARRAY_SIZE(wm8741_snd_controls_stereo)); + break; + case WM8741_DIFF_MODE_MONO_LEFT: + snd_soc_add_codec_controls(codec, + wm8741_snd_controls_mono_left, + ARRAY_SIZE(wm8741_snd_controls_mono_left)); + break; + case WM8741_DIFF_MODE_MONO_RIGHT: + snd_soc_add_codec_controls(codec, + wm8741_snd_controls_mono_right, + ARRAY_SIZE(wm8741_snd_controls_mono_right)); + break; + default: + return -EINVAL; + } + + return 0; +} + static int wm8741_probe(struct snd_soc_codec *codec) { struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec); @@ -434,15 +508,17 @@ static int wm8741_probe(struct snd_soc_codec *codec) goto err_enable; } - /* Change some default settings - latch VU */ - snd_soc_update_bits(codec, WM8741_DACLLSB_ATTENUATION, - WM8741_UPDATELL, WM8741_UPDATELL); - snd_soc_update_bits(codec, WM8741_DACLMSB_ATTENUATION, - WM8741_UPDATELM, WM8741_UPDATELM); - snd_soc_update_bits(codec, WM8741_DACRLSB_ATTENUATION, - WM8741_UPDATERL, WM8741_UPDATERL); - snd_soc_update_bits(codec, WM8741_DACRMSB_ATTENUATION, - WM8741_UPDATERM, WM8741_UPDATERM); + ret = wm8741_configure(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to change default settings\n"); + goto err_enable; + } + + ret = wm8741_add_controls(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to add controls\n"); + goto err_enable; + } dev_dbg(codec->dev, "Successful registration\n"); return ret; @@ -467,8 +543,6 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8741 = { .remove = wm8741_remove, .resume = wm8741_resume, - .controls = wm8741_snd_controls, - .num_controls = ARRAY_SIZE(wm8741_snd_controls), .dapm_widgets = wm8741_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(wm8741_dapm_widgets), .dapm_routes = wm8741_dapm_routes, @@ -493,6 +567,23 @@ static const struct regmap_config wm8741_regmap = { .readable_reg = wm8741_readable, }; +static int wm8741_set_pdata(struct device *dev, struct wm8741_priv *wm8741) +{ + const struct wm8741_platform_data *pdata = dev_get_platdata(dev); + u32 diff_mode; + + if (dev->of_node) { + if (of_property_read_u32(dev->of_node, "diff-mode", &diff_mode) + >= 0) + wm8741->pdata.diff_mode = diff_mode; + } else { + if (pdata != NULL) + memcpy(&wm8741->pdata, pdata, sizeof(wm8741->pdata)); + } + + return 0; +} + #if IS_ENABLED(CONFIG_I2C) static int wm8741_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) @@ -522,6 +613,12 @@ static int wm8741_i2c_probe(struct i2c_client *i2c, return ret; } + ret = wm8741_set_pdata(&i2c->dev, wm8741); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to set pdata: %d\n", ret); + return ret; + } + i2c_set_clientdata(i2c, wm8741); ret = snd_soc_register_codec(&i2c->dev, @@ -582,6 +679,12 @@ static int wm8741_spi_probe(struct spi_device *spi) return ret; } + ret = wm8741_set_pdata(&spi->dev, wm8741); + if (ret != 0) { + dev_err(&spi->dev, "Failed to set pdata: %d\n", ret); + return ret; + } + spi_set_drvdata(spi, wm8741); ret = snd_soc_register_codec(&spi->dev, diff --git a/sound/soc/codecs/wm8741.h b/sound/soc/codecs/wm8741.h index 56c1b1d4a681..c8835f65f342 100644 --- a/sound/soc/codecs/wm8741.h +++ b/sound/soc/codecs/wm8741.h @@ -194,6 +194,12 @@ #define WM8741_DITHER_SHIFT 0 /* DITHER - [1:0] */ #define WM8741_DITHER_WIDTH 2 /* DITHER - [1:0] */ +/* DIFF field values */ +#define WM8741_DIFF_MODE_STEREO 0 /* stereo normal */ +#define WM8741_DIFF_MODE_STEREO_REVERSED 2 /* stereo reversed */ +#define WM8741_DIFF_MODE_MONO_LEFT 1 /* mono left */ +#define WM8741_DIFF_MODE_MONO_RIGHT 3 /* mono right */ + /* * R32 (0x20) - ADDITONAL_CONTROL_1 */ @@ -208,4 +214,8 @@ #define WM8741_SYSCLK 0 +struct wm8741_platform_data { + u32 diff_mode; /* Differential Output Mode */ +}; + #endif diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index af095b64f880..761418f05d59 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -127,6 +127,8 @@ struct wm8960_priv { struct snd_soc_dapm_widget *out3; bool deemph; int playback_fs; + int bclk; + int sysclk; struct wm8960_data pdata; }; @@ -563,6 +565,72 @@ static struct { { 8000, 5 }, }; +/* Multiply 256 for internal 256 div */ +static const int dac_divs[] = { 256, 384, 512, 768, 1024, 1408, 1536 }; + +/* Multiply 10 to eliminate decimials */ +static const int bclk_divs[] = { + 10, 15, 20, 30, 40, 55, 60, 80, 110, + 120, 160, 220, 240, 320, 320, 320 +}; + +static void wm8960_configure_clocking(struct snd_soc_codec *codec, + bool tx, int lrclk) +{ + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + u16 iface1 = snd_soc_read(codec, WM8960_IFACE1); + u16 iface2 = snd_soc_read(codec, WM8960_IFACE2); + u32 sysclk; + int i, j; + + if (!(iface1 & (1<<6))) { + dev_dbg(codec->dev, + "Codec is slave mode, no need to configure clock\n"); + return; + } + + if (!wm8960->sysclk) { + dev_dbg(codec->dev, "No SYSCLK configured\n"); + return; + } + + if (!wm8960->bclk || !lrclk) { + dev_dbg(codec->dev, "No audio clocks configured\n"); + return; + } + + for (i = 0; i < ARRAY_SIZE(dac_divs); ++i) { + if (wm8960->sysclk == lrclk * dac_divs[i]) { + for (j = 0; j < ARRAY_SIZE(bclk_divs); ++j) { + sysclk = wm8960->bclk * bclk_divs[j] / 10; + if (wm8960->sysclk == sysclk) + break; + } + if(j != ARRAY_SIZE(bclk_divs)) + break; + } + } + + if (i == ARRAY_SIZE(dac_divs)) { + dev_err(codec->dev, "Unsupported sysclk %d\n", wm8960->sysclk); + return; + } + + /* + * configure frame clock. If ADCLRC configure as GPIO pin, DACLRC + * pin is used as a frame clock for ADCs and DACs. + */ + if (iface2 & (1<<6)) + snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 3, i << 3); + else if (tx) + snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 3, i << 3); + else if (!tx) + snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 6, i << 6); + + /* configure bit clock */ + snd_soc_update_bits(codec, WM8960_CLOCK2, 0xf, j); +} + static int wm8960_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -570,8 +638,13 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = dai->codec; struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; int i; + wm8960->bclk = snd_soc_params_to_bclk(params); + if (params_channels(params) == 1) + wm8960->bclk *= 2; + /* bit size */ switch (params_width(params)) { case 16: @@ -582,6 +655,12 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream, case 24: iface |= 0x0008; break; + case 32: + /* right justify mode does not support 32 word length */ + if ((iface & 0x3) != 0) { + iface |= 0x000c; + break; + } default: dev_err(codec->dev, "unsupported width %d\n", params_width(params)); @@ -602,6 +681,9 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream, /* set iface */ snd_soc_write(codec, WM8960_IFACE1, iface); + + wm8960_configure_clocking(codec, tx, params_rate(params)); + return 0; } @@ -946,11 +1028,35 @@ static int wm8960_set_bias_level(struct snd_soc_codec *codec, return wm8960->set_bias_level(codec, level); } +static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + + switch (clk_id) { + case WM8960_SYSCLK_MCLK: + snd_soc_update_bits(codec, WM8960_CLOCK1, + 0x1, WM8960_SYSCLK_MCLK); + break; + case WM8960_SYSCLK_PLL: + snd_soc_update_bits(codec, WM8960_CLOCK1, + 0x1, WM8960_SYSCLK_PLL); + break; + default: + return -EINVAL; + } + + wm8960->sysclk = freq; + + return 0; +} + #define WM8960_RATES SNDRV_PCM_RATE_8000_48000 #define WM8960_FORMATS \ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ - SNDRV_PCM_FMTBIT_S24_LE) + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) static const struct snd_soc_dai_ops wm8960_dai_ops = { .hw_params = wm8960_hw_params, @@ -958,6 +1064,7 @@ static const struct snd_soc_dai_ops wm8960_dai_ops = { .set_fmt = wm8960_set_dai_fmt, .set_clkdiv = wm8960_set_dai_clkdiv, .set_pll = wm8960_set_dai_pll, + .set_sysclk = wm8960_set_dai_sysclk, }; static struct snd_soc_dai_driver wm8960_dai = { diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 95f83bec1d14..3a4a5c0e3f97 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-topology.h> #include <sound/initval.h> #define CREATE_TRACE_POINTS @@ -2418,6 +2419,7 @@ int snd_soc_register_card(struct snd_soc_card *card) card->rtd_aux[i].card = card; INIT_LIST_HEAD(&card->dapm_dirty); + INIT_LIST_HEAD(&card->dobj_list); card->instantiated = 0; mutex_init(&card->mutex); mutex_init(&card->dapm_mutex); @@ -2733,6 +2735,7 @@ static void snd_soc_component_add_unlocked(struct snd_soc_component *component) } list_add(&component->list, &component_list); + INIT_LIST_HEAD(&component->dobj_list); } static void snd_soc_component_add(struct snd_soc_component *component) @@ -2809,6 +2812,7 @@ void snd_soc_unregister_component(struct device *dev) return; found: + snd_soc_tplg_component_remove(cmpnt, SND_SOC_TPLG_INDEX_ALL); snd_soc_component_del_unlocked(cmpnt); mutex_unlock(&client_mutex); snd_soc_component_cleanup(cmpnt); diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c new file mode 100644 index 000000000000..d0960683c409 --- /dev/null +++ b/sound/soc/soc-topology.c @@ -0,0 +1,1826 @@ +/* + * soc-topology.c -- ALSA SoC Topology + * + * Copyright (C) 2012 Texas Instruments Inc. + * Copyright (C) 2015 Intel Corporation. + * + * Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> + * K, Mythri P <mythri.p.k@intel.com> + * Prusty, Subhransu S <subhransu.s.prusty@intel.com> + * B, Jayachandran <jayachandran.b@intel.com> + * Abdullah, Omair M <omair.m.abdullah@intel.com> + * Jin, Yao <yao.jin@intel.com> + * Lin, Mengdong <mengdong.lin@intel.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. + * + * Add support to read audio firmware topology alongside firmware text. The + * topology data can contain kcontrols, DAPM graphs, widgets, DAIs, DAI links, + * equalizers, firmware, coefficients etc. + * + * This file only manages the core ALSA and ASoC components, all other bespoke + * firmware topology 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-topology.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_TPLG_PASS_MANIFEST 0 +#define SOC_TPLG_PASS_VENDOR 1 +#define SOC_TPLG_PASS_MIXER 2 +#define SOC_TPLG_PASS_WIDGET 3 +#define SOC_TPLG_PASS_GRAPH 4 +#define SOC_TPLG_PASS_PINS 5 +#define SOC_TPLG_PASS_PCM_DAI 6 + +#define SOC_TPLG_PASS_START SOC_TPLG_PASS_MANIFEST +#define SOC_TPLG_PASS_END SOC_TPLG_PASS_PCM_DAI + +struct soc_tplg { + 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_component *comp; + u32 index; /* current block index */ + u32 req_index; /* required index, only loaded/free matching blocks */ + + /* kcontrol operations */ + const struct snd_soc_tplg_kcontrol_ops *io_ops; + int io_ops_count; + + /* optional fw loading callbacks to component drivers */ + struct snd_soc_tplg_ops *ops; +}; + +static int soc_tplg_process_headers(struct soc_tplg *tplg); +static void soc_tplg_complete(struct soc_tplg *tplg); +struct snd_soc_dapm_widget * +snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_widget *widget); +struct snd_soc_dapm_widget * +snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_widget *widget); + +/* check we dont overflow the data for this control chunk */ +static int soc_tplg_check_elem_count(struct soc_tplg *tplg, size_t elem_size, + unsigned int count, size_t bytes, const char *elem_type) +{ + const u8 *end = tplg->pos + elem_size * count; + + if (end > tplg->fw->data + tplg->fw->size) { + dev_err(tplg->dev, "ASoC: %s overflow end of data\n", + elem_type); + 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(tplg->dev, + "ASoC: %s count %d of size %zu is bigger than chunk %zu\n", + elem_type, count, elem_size, bytes); + return -EINVAL; + } + + return 0; +} + +static inline int soc_tplg_is_eof(struct soc_tplg *tplg) +{ + const u8 *end = tplg->hdr_pos; + + if (end >= tplg->fw->data + tplg->fw->size) + return 1; + return 0; +} + +static inline unsigned long soc_tplg_get_hdr_offset(struct soc_tplg *tplg) +{ + return (unsigned long)(tplg->hdr_pos - tplg->fw->data); +} + +static inline unsigned long soc_tplg_get_offset(struct soc_tplg *tplg) +{ + return (unsigned long)(tplg->pos - tplg->fw->data); +} + +/* mapping of Kcontrol types and associated operations. */ +static const struct snd_soc_tplg_kcontrol_ops io_ops[] = { + {SND_SOC_TPLG_CTL_VOLSW, snd_soc_get_volsw, + snd_soc_put_volsw, snd_soc_info_volsw}, + {SND_SOC_TPLG_CTL_VOLSW_SX, snd_soc_get_volsw_sx, + snd_soc_put_volsw_sx, NULL}, + {SND_SOC_TPLG_CTL_ENUM, snd_soc_get_enum_double, + snd_soc_put_enum_double, snd_soc_info_enum_double}, + {SND_SOC_TPLG_CTL_ENUM_VALUE, snd_soc_get_enum_double, + snd_soc_put_enum_double, NULL}, + {SND_SOC_TPLG_CTL_BYTES, snd_soc_bytes_get, + snd_soc_bytes_put, snd_soc_bytes_info}, + {SND_SOC_TPLG_CTL_RANGE, snd_soc_get_volsw_range, + snd_soc_put_volsw_range, snd_soc_info_volsw_range}, + {SND_SOC_TPLG_CTL_VOLSW_XR_SX, snd_soc_get_xr_sx, + snd_soc_put_xr_sx, snd_soc_info_xr_sx}, + {SND_SOC_TPLG_CTL_STROBE, snd_soc_get_strobe, + snd_soc_put_strobe, NULL}, + {SND_SOC_TPLG_DAPM_CTL_VOLSW, snd_soc_dapm_get_volsw, + snd_soc_dapm_put_volsw, NULL}, + {SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE, snd_soc_dapm_get_enum_double, + snd_soc_dapm_put_enum_double, snd_soc_info_enum_double}, + {SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT, snd_soc_dapm_get_enum_double, + snd_soc_dapm_put_enum_double, NULL}, + {SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE, snd_soc_dapm_get_enum_double, + snd_soc_dapm_put_enum_double, NULL}, + {SND_SOC_TPLG_DAPM_CTL_PIN, snd_soc_dapm_get_pin_switch, + snd_soc_dapm_put_pin_switch, snd_soc_dapm_info_pin_switch}, +}; + +struct soc_tplg_map { + int uid; + int kid; +}; + +/* mapping of widget types from UAPI IDs to kernel IDs */ +static const struct soc_tplg_map dapm_map[] = { + {SND_SOC_TPLG_DAPM_INPUT, snd_soc_dapm_input}, + {SND_SOC_TPLG_DAPM_OUTPUT, snd_soc_dapm_output}, + {SND_SOC_TPLG_DAPM_MUX, snd_soc_dapm_mux}, + {SND_SOC_TPLG_DAPM_MIXER, snd_soc_dapm_mixer}, + {SND_SOC_TPLG_DAPM_PGA, snd_soc_dapm_pga}, + {SND_SOC_TPLG_DAPM_OUT_DRV, snd_soc_dapm_out_drv}, + {SND_SOC_TPLG_DAPM_ADC, snd_soc_dapm_adc}, + {SND_SOC_TPLG_DAPM_DAC, snd_soc_dapm_dac}, + {SND_SOC_TPLG_DAPM_SWITCH, snd_soc_dapm_switch}, + {SND_SOC_TPLG_DAPM_PRE, snd_soc_dapm_pre}, + {SND_SOC_TPLG_DAPM_POST, snd_soc_dapm_post}, + {SND_SOC_TPLG_DAPM_AIF_IN, snd_soc_dapm_aif_in}, + {SND_SOC_TPLG_DAPM_AIF_OUT, snd_soc_dapm_aif_out}, + {SND_SOC_TPLG_DAPM_DAI_IN, snd_soc_dapm_dai_in}, + {SND_SOC_TPLG_DAPM_DAI_OUT, snd_soc_dapm_dai_out}, + {SND_SOC_TPLG_DAPM_DAI_LINK, snd_soc_dapm_dai_link}, +}; + +static int tplc_chan_get_reg(struct soc_tplg *tplg, + struct snd_soc_tplg_channel *chan, int map) +{ + int i; + + for (i = 0; i < SND_SOC_TPLG_MAX_CHAN; i++) { + if (chan[i].id == map) + return chan[i].reg; + } + + return -EINVAL; +} + +static int tplc_chan_get_shift(struct soc_tplg *tplg, + struct snd_soc_tplg_channel *chan, int map) +{ + int i; + + for (i = 0; i < SND_SOC_TPLG_MAX_CHAN; i++) { + if (chan[i].id == map) + return chan[i].shift; + } + + return -EINVAL; +} + +static int get_widget_id(int tplg_type) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dapm_map); i++) { + if (tplg_type == dapm_map[i].uid) + return dapm_map[i].kid; + } + + return -EINVAL; +} + +static enum snd_soc_dobj_type get_dobj_mixer_type( + struct snd_soc_tplg_ctl_hdr *control_hdr) +{ + if (control_hdr == NULL) + return SND_SOC_DOBJ_NONE; + + switch (control_hdr->ops.info) { + case SND_SOC_TPLG_CTL_VOLSW: + case SND_SOC_TPLG_CTL_VOLSW_SX: + case SND_SOC_TPLG_CTL_VOLSW_XR_SX: + case SND_SOC_TPLG_CTL_RANGE: + case SND_SOC_TPLG_CTL_STROBE: + return SND_SOC_DOBJ_MIXER; + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_CTL_ENUM_VALUE: + return SND_SOC_DOBJ_ENUM; + case SND_SOC_TPLG_CTL_BYTES: + return SND_SOC_DOBJ_BYTES; + default: + return SND_SOC_DOBJ_NONE; + } +} + +static enum snd_soc_dobj_type get_dobj_type(struct snd_soc_tplg_hdr *hdr, + struct snd_soc_tplg_ctl_hdr *control_hdr) +{ + switch (hdr->type) { + case SND_SOC_TPLG_TYPE_MIXER: + return get_dobj_mixer_type(control_hdr); + case SND_SOC_TPLG_TYPE_DAPM_GRAPH: + case SND_SOC_TPLG_TYPE_MANIFEST: + return SND_SOC_DOBJ_NONE; + case SND_SOC_TPLG_TYPE_DAPM_WIDGET: + return SND_SOC_DOBJ_WIDGET; + case SND_SOC_TPLG_TYPE_DAI_LINK: + return SND_SOC_DOBJ_DAI_LINK; + case SND_SOC_TPLG_TYPE_PCM: + return SND_SOC_DOBJ_PCM; + case SND_SOC_TPLG_TYPE_CODEC_LINK: + return SND_SOC_DOBJ_CODEC_LINK; + default: + return SND_SOC_DOBJ_NONE; + } +} + +static inline void soc_bind_err(struct soc_tplg *tplg, + struct snd_soc_tplg_ctl_hdr *hdr, int index) +{ + dev_err(tplg->dev, + "ASoC: invalid control type (g,p,i) %d:%d:%d index %d at 0x%lx\n", + hdr->ops.get, hdr->ops.put, hdr->ops.info, index, + soc_tplg_get_offset(tplg)); +} + +static inline void soc_control_err(struct soc_tplg *tplg, + struct snd_soc_tplg_ctl_hdr *hdr, const char *name) +{ + dev_err(tplg->dev, + "ASoC: no complete mixer IO handler for %s type (g,p,i) %d:%d:%d at 0x%lx\n", + name, hdr->ops.get, hdr->ops.put, hdr->ops.info, + soc_tplg_get_offset(tplg)); +} + +/* pass vendor data to component driver for processing */ +static int soc_tplg_vendor_load_(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + int ret = 0; + + if (tplg->comp && tplg->ops && tplg->ops->vendor_load) + ret = tplg->ops->vendor_load(tplg->comp, hdr); + else { + dev_err(tplg->dev, "ASoC: no vendor load callback for ID %d\n", + hdr->vendor_type); + return -EINVAL; + } + + if (ret < 0) + dev_err(tplg->dev, + "ASoC: vendor load failed at hdr offset %ld/0x%lx for type %d:%d\n", + soc_tplg_get_hdr_offset(tplg), + soc_tplg_get_hdr_offset(tplg), + hdr->type, hdr->vendor_type); + return ret; +} + +/* pass vendor data to component driver for processing */ +static int soc_tplg_vendor_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + if (tplg->pass != SOC_TPLG_PASS_VENDOR) + return 0; + + return soc_tplg_vendor_load_(tplg, hdr); +} + +/* optionally pass new dynamic widget to component driver. This is mainly for + * external widgets where we can assign private data/ops */ +static int soc_tplg_widget_load(struct soc_tplg *tplg, + struct snd_soc_dapm_widget *w, struct snd_soc_tplg_dapm_widget *tplg_w) +{ + if (tplg->comp && tplg->ops && tplg->ops->widget_load) + return tplg->ops->widget_load(tplg->comp, w, tplg_w); + + return 0; +} + +/* pass dynamic FEs configurations to component driver */ +static int soc_tplg_pcm_dai_load(struct soc_tplg *tplg, + struct snd_soc_tplg_pcm_dai *pcm_dai, int num_pcm_dai) +{ + if (tplg->comp && tplg->ops && tplg->ops->pcm_dai_load) + return tplg->ops->pcm_dai_load(tplg->comp, pcm_dai, num_pcm_dai); + + return 0; +} + +/* tell the component driver that all firmware has been loaded in this request */ +static void soc_tplg_complete(struct soc_tplg *tplg) +{ + if (tplg->comp && tplg->ops && tplg->ops->complete) + tplg->ops->complete(tplg->comp); +} + +/* add a dynamic kcontrol */ +static int soc_tplg_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) { + 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_tplg_add_kcontrol(struct soc_tplg *tplg, + struct snd_kcontrol_new *k, struct snd_kcontrol **kcontrol) +{ + struct snd_soc_component *comp = tplg->comp; + + return soc_tplg_add_dcontrol(comp->card->snd_card, + comp->dev, k, NULL, comp, kcontrol); +} + +/* remove a mixer kcontrol */ +static void remove_mixer(struct snd_soc_component *comp, + struct snd_soc_dobj *dobj, int pass) +{ + struct snd_card *card = comp->card->snd_card; + struct soc_mixer_control *sm = + container_of(dobj, struct soc_mixer_control, dobj); + const unsigned int *p = NULL; + + if (pass != SOC_TPLG_PASS_MIXER) + return; + + if (dobj->ops && dobj->ops->control_unload) + dobj->ops->control_unload(comp, dobj); + + if (sm->dobj.control.kcontrol->tlv.p) + p = sm->dobj.control.kcontrol->tlv.p; + snd_ctl_remove(card, sm->dobj.control.kcontrol); + list_del(&sm->dobj.list); + kfree(sm); + kfree(p); +} + +/* remove an enum kcontrol */ +static void remove_enum(struct snd_soc_component *comp, + struct snd_soc_dobj *dobj, int pass) +{ + struct snd_card *card = comp->card->snd_card; + struct soc_enum *se = container_of(dobj, struct soc_enum, dobj); + int i; + + if (pass != SOC_TPLG_PASS_MIXER) + return; + + if (dobj->ops && dobj->ops->control_unload) + dobj->ops->control_unload(comp, dobj); + + snd_ctl_remove(card, se->dobj.control.kcontrol); + list_del(&se->dobj.list); + + kfree(se->dobj.control.dvalues); + for (i = 0; i < se->items; i++) + kfree(se->dobj.control.dtexts[i]); + kfree(se); +} + +/* remove a byte kcontrol */ +static void remove_bytes(struct snd_soc_component *comp, + struct snd_soc_dobj *dobj, int pass) +{ + struct snd_card *card = comp->card->snd_card; + struct soc_bytes_ext *sb = + container_of(dobj, struct soc_bytes_ext, dobj); + + if (pass != SOC_TPLG_PASS_MIXER) + return; + + if (dobj->ops && dobj->ops->control_unload) + dobj->ops->control_unload(comp, dobj); + + snd_ctl_remove(card, sb->dobj.control.kcontrol); + list_del(&sb->dobj.list); + kfree(sb); +} + +/* remove a widget and it's kcontrols - routes must be removed first */ +static void remove_widget(struct snd_soc_component *comp, + struct snd_soc_dobj *dobj, int pass) +{ + struct snd_card *card = comp->card->snd_card; + struct snd_soc_dapm_widget *w = + container_of(dobj, struct snd_soc_dapm_widget, dobj); + int i; + + if (pass != SOC_TPLG_PASS_WIDGET) + return; + + if (dobj->ops && dobj->ops->widget_unload) + dobj->ops->widget_unload(comp, dobj); + + /* + * Dynamic Widgets either have 1 enum kcontrol or 1..N mixers. + * The enum may either have an array of values or strings. + */ + if (dobj->widget.kcontrol_enum) { + /* enumerated widget mixer */ + struct soc_enum *se = + (struct soc_enum *)w->kcontrols[0]->private_value; + + snd_ctl_remove(card, w->kcontrols[0]); + + kfree(se->dobj.control.dvalues); + for (i = 0; i < se->items; i++) + kfree(se->dobj.control.dtexts[i]); + + kfree(se); + kfree(w->kcontrol_news); + } else { + /* non enumerated widget mixer */ + 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; + + kfree(w->kcontrols[i]->tlv.p); + + snd_ctl_remove(card, w->kcontrols[i]); + kfree(sm); + } + kfree(w->kcontrol_news); + } + /* widget w is freed by soc-dapm.c */ +} + +/* remove PCM DAI configurations */ +static void remove_pcm_dai(struct snd_soc_component *comp, + struct snd_soc_dobj *dobj, int pass) +{ + if (pass != SOC_TPLG_PASS_PCM_DAI) + return; + + if (dobj->ops && dobj->ops->pcm_dai_unload) + dobj->ops->pcm_dai_unload(comp, dobj); + + list_del(&dobj->list); + kfree(dobj); +} + +/* bind a kcontrol to it's IO handlers */ +static int soc_tplg_kcontrol_bind_io(struct snd_soc_tplg_ctl_hdr *hdr, + struct snd_kcontrol_new *k, + const struct snd_soc_tplg_kcontrol_ops *ops, int num_ops, + const struct snd_soc_tplg_kcontrol_ops *bops, int num_bops) +{ + int i; + + /* try and map standard kcontrols handler first */ + for (i = 0; i < num_ops; i++) { + + if (ops[i].id == hdr->ops.put) + k->put = ops[i].put; + if (ops[i].id == hdr->ops.get) + k->get = ops[i].get; + if (ops[i].id == hdr->ops.info) + k->info = ops[i].info; + } + + /* standard handlers found ? */ + if (k->put && k->get && k->info) + return 0; + + /* none found so try bespoke handlers */ + for (i = 0; i < num_bops; i++) { + + if (k->put == NULL && bops[i].id == hdr->ops.put) + k->put = bops[i].put; + if (k->get == NULL && bops[i].id == hdr->ops.get) + k->get = bops[i].get; + if (k->info == NULL && ops[i].id == hdr->ops.info) + k->info = bops[i].info; + } + + /* bespoke handlers found ? */ + if (k->put && k->get && k->info) + return 0; + + /* nothing to bind */ + return -EINVAL; +} + +/* bind a widgets to it's evnt handlers */ +int snd_soc_tplg_widget_bind_event(struct snd_soc_dapm_widget *w, + const struct snd_soc_tplg_widget_events *events, + int num_events, u16 event_type) +{ + int i; + + w->event = NULL; + + for (i = 0; i < num_events; i++) { + if (event_type == events[i].type) { + + /* found - so assign event */ + w->event = events[i].event_handler; + return 0; + } + } + + /* not found */ + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_tplg_widget_bind_event); + +/* optionally pass new dynamic kcontrol to component driver. */ +static int soc_tplg_init_kcontrol(struct soc_tplg *tplg, + struct snd_kcontrol_new *k, struct snd_soc_tplg_ctl_hdr *hdr) +{ + if (tplg->comp && tplg->ops && tplg->ops->control_load) + return tplg->ops->control_load(tplg->comp, k, hdr); + + return 0; +} + +static int soc_tplg_create_tlv(struct soc_tplg *tplg, + struct snd_kcontrol_new *kc, u32 tlv_size) +{ + struct snd_soc_tplg_ctl_tlv *tplg_tlv; + struct snd_ctl_tlv *tlv; + + if (tlv_size == 0) + return 0; + + tplg_tlv = (struct snd_soc_tplg_ctl_tlv *) tplg->pos; + tplg->pos += tlv_size; + + tlv = kzalloc(sizeof(*tlv) + tlv_size, GFP_KERNEL); + if (tlv == NULL) + return -ENOMEM; + + dev_dbg(tplg->dev, " created TLV type %d size %d bytes\n", + tplg_tlv->numid, tplg_tlv->size); + + tlv->numid = tplg_tlv->numid; + tlv->length = tplg_tlv->size; + memcpy(tlv->tlv, tplg_tlv + 1, tplg_tlv->size); + kc->tlv.p = (void *)tlv; + + return 0; +} + +static inline void soc_tplg_free_tlv(struct soc_tplg *tplg, + struct snd_kcontrol_new *kc) +{ + kfree(kc->tlv.p); +} + +static int soc_tplg_dbytes_create(struct soc_tplg *tplg, unsigned int count, + size_t size) +{ + struct snd_soc_tplg_bytes_control *be; + struct soc_bytes_ext *sbe; + struct snd_kcontrol_new kc; + int i, err; + + if (soc_tplg_check_elem_count(tplg, + sizeof(struct snd_soc_tplg_bytes_control), count, + size, "mixer bytes")) { + dev_err(tplg->dev, "ASoC: Invalid count %d for byte control\n", + count); + return -EINVAL; + } + + for (i = 0; i < count; i++) { + be = (struct snd_soc_tplg_bytes_control *)tplg->pos; + + /* validate kcontrol */ + if (strnlen(be->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + + sbe = kzalloc(sizeof(*sbe), GFP_KERNEL); + if (sbe == NULL) + return -ENOMEM; + + tplg->pos += (sizeof(struct snd_soc_tplg_bytes_control) + + be->priv.size); + + dev_dbg(tplg->dev, + "ASoC: adding bytes kcontrol %s with access 0x%x\n", + be->hdr.name, be->hdr.access); + + memset(&kc, 0, sizeof(kc)); + kc.name = be->hdr.name; + kc.private_value = (long)sbe; + kc.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kc.access = be->hdr.access; + + sbe->max = be->max; + sbe->dobj.type = SND_SOC_DOBJ_BYTES; + sbe->dobj.ops = tplg->ops; + INIT_LIST_HEAD(&sbe->dobj.list); + + /* map io handlers */ + err = soc_tplg_kcontrol_bind_io(&be->hdr, &kc, io_ops, + ARRAY_SIZE(io_ops), tplg->io_ops, tplg->io_ops_count); + if (err) { + soc_control_err(tplg, &be->hdr, be->hdr.name); + kfree(sbe); + continue; + } + + /* pass control to driver for optional further init */ + err = soc_tplg_init_kcontrol(tplg, &kc, + (struct snd_soc_tplg_ctl_hdr *)be); + if (err < 0) { + dev_err(tplg->dev, "ASoC: failed to init %s\n", + be->hdr.name); + kfree(sbe); + continue; + } + + /* register control here */ + err = soc_tplg_add_kcontrol(tplg, &kc, + &sbe->dobj.control.kcontrol); + if (err < 0) { + dev_err(tplg->dev, "ASoC: failed to add %s\n", + be->hdr.name); + kfree(sbe); + continue; + } + + list_add(&sbe->dobj.list, &tplg->comp->dobj_list); + } + return 0; + +} + +static int soc_tplg_dmixer_create(struct soc_tplg *tplg, unsigned int count, + size_t size) +{ + struct snd_soc_tplg_mixer_control *mc; + struct soc_mixer_control *sm; + struct snd_kcontrol_new kc; + int i, err; + + if (soc_tplg_check_elem_count(tplg, + sizeof(struct snd_soc_tplg_mixer_control), + count, size, "mixers")) { + + dev_err(tplg->dev, "ASoC: invalid count %d for controls\n", + count); + return -EINVAL; + } + + for (i = 0; i < count; i++) { + mc = (struct snd_soc_tplg_mixer_control *)tplg->pos; + + /* validate kcontrol */ + if (strnlen(mc->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + + sm = kzalloc(sizeof(*sm), GFP_KERNEL); + if (sm == NULL) + return -ENOMEM; + tplg->pos += (sizeof(struct snd_soc_tplg_mixer_control) + + mc->priv.size); + + dev_dbg(tplg->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; + + /* we only support FL/FR channel mapping atm */ + sm->reg = tplc_chan_get_reg(tplg, mc->channel, + SNDRV_CHMAP_FL); + sm->rreg = tplc_chan_get_reg(tplg, mc->channel, + SNDRV_CHMAP_FR); + sm->shift = tplc_chan_get_shift(tplg, mc->channel, + SNDRV_CHMAP_FL); + sm->rshift = tplc_chan_get_shift(tplg, mc->channel, + SNDRV_CHMAP_FR); + + sm->max = mc->max; + sm->min = mc->min; + sm->invert = mc->invert; + sm->platform_max = mc->platform_max; + sm->dobj.index = tplg->index; + sm->dobj.ops = tplg->ops; + sm->dobj.type = SND_SOC_DOBJ_MIXER; + INIT_LIST_HEAD(&sm->dobj.list); + + /* map io handlers */ + err = soc_tplg_kcontrol_bind_io(&mc->hdr, &kc, io_ops, + ARRAY_SIZE(io_ops), tplg->io_ops, tplg->io_ops_count); + if (err) { + soc_control_err(tplg, &mc->hdr, mc->hdr.name); + kfree(sm); + continue; + } + + /* pass control to driver for optional further init */ + err = soc_tplg_init_kcontrol(tplg, &kc, + (struct snd_soc_tplg_ctl_hdr *) mc); + if (err < 0) { + dev_err(tplg->dev, "ASoC: failed to init %s\n", + mc->hdr.name); + kfree(sm); + continue; + } + + /* create any TLV data */ + soc_tplg_create_tlv(tplg, &kc, mc->hdr.tlv_size); + + /* register control here */ + err = soc_tplg_add_kcontrol(tplg, &kc, + &sm->dobj.control.kcontrol); + if (err < 0) { + dev_err(tplg->dev, "ASoC: failed to add %s\n", + mc->hdr.name); + soc_tplg_free_tlv(tplg, &kc); + kfree(sm); + continue; + } + + list_add(&sm->dobj.list, &tplg->comp->dobj_list); + } + + return 0; +} + +static int soc_tplg_denum_create_texts(struct soc_enum *se, + struct snd_soc_tplg_enum_control *ec) +{ + int i, ret; + + se->dobj.control.dtexts = + kzalloc(sizeof(char *) * ec->items, GFP_KERNEL); + if (se->dobj.control.dtexts == NULL) + return -ENOMEM; + + for (i = 0; i < ec->items; i++) { + + if (strnlen(ec->texts[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) { + ret = -EINVAL; + goto err; + } + + se->dobj.control.dtexts[i] = kstrdup(ec->texts[i], GFP_KERNEL); + if (!se->dobj.control.dtexts[i]) { + ret = -ENOMEM; + goto err; + } + } + + return 0; + +err: + for (--i; i >= 0; i--) + kfree(se->dobj.control.dtexts[i]); + kfree(se->dobj.control.dtexts); + return ret; +} + +static int soc_tplg_denum_create_values(struct soc_enum *se, + struct snd_soc_tplg_enum_control *ec) +{ + if (ec->items > sizeof(*ec->values)) + return -EINVAL; + + se->dobj.control.dvalues = + kmalloc(ec->items * sizeof(u32), GFP_KERNEL); + if (!se->dobj.control.dvalues) + return -ENOMEM; + + memcpy(se->dobj.control.dvalues, ec->values, ec->items * sizeof(u32)); + return 0; +} + +static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, + size_t size) +{ + struct snd_soc_tplg_enum_control *ec; + struct soc_enum *se; + struct snd_kcontrol_new kc; + int i, ret, err; + + if (soc_tplg_check_elem_count(tplg, + sizeof(struct snd_soc_tplg_enum_control), + count, size, "enums")) { + + dev_err(tplg->dev, "ASoC: invalid count %d for enum controls\n", + count); + return -EINVAL; + } + + for (i = 0; i < count; i++) { + ec = (struct snd_soc_tplg_enum_control *)tplg->pos; + tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + + ec->priv.size); + + /* validate kcontrol */ + if (strnlen(ec->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + + se = kzalloc((sizeof(*se)), GFP_KERNEL); + if (se == NULL) + return -ENOMEM; + + dev_dbg(tplg->dev, "ASoC: adding enum kcontrol %s size %d\n", + ec->hdr.name, ec->items); + + 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 = tplc_chan_get_reg(tplg, ec->channel, SNDRV_CHMAP_FL); + se->shift_l = tplc_chan_get_shift(tplg, ec->channel, + SNDRV_CHMAP_FL); + se->shift_r = tplc_chan_get_shift(tplg, ec->channel, + SNDRV_CHMAP_FL); + + se->items = ec->items; + se->mask = ec->mask; + se->dobj.index = tplg->index; + se->dobj.type = SND_SOC_DOBJ_ENUM; + se->dobj.ops = tplg->ops; + INIT_LIST_HEAD(&se->dobj.list); + + switch (ec->hdr.ops.info) { + case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: + case SND_SOC_TPLG_CTL_ENUM_VALUE: + err = soc_tplg_denum_create_values(se, ec); + if (err < 0) { + dev_err(tplg->dev, + "ASoC: could not create values for %s\n", + ec->hdr.name); + kfree(se); + continue; + } + /* fall through and create texts */ + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: + err = soc_tplg_denum_create_texts(se, ec); + if (err < 0) { + dev_err(tplg->dev, + "ASoC: could not create texts for %s\n", + ec->hdr.name); + kfree(se); + continue; + } + break; + default: + dev_err(tplg->dev, + "ASoC: invalid enum control type %d for %s\n", + ec->hdr.ops.info, ec->hdr.name); + kfree(se); + continue; + } + + /* map io handlers */ + err = soc_tplg_kcontrol_bind_io(&ec->hdr, &kc, io_ops, + ARRAY_SIZE(io_ops), tplg->io_ops, tplg->io_ops_count); + if (err) { + soc_control_err(tplg, &ec->hdr, ec->hdr.name); + kfree(se); + continue; + } + + /* pass control to driver for optional further init */ + err = soc_tplg_init_kcontrol(tplg, &kc, + (struct snd_soc_tplg_ctl_hdr *) ec); + if (err < 0) { + dev_err(tplg->dev, "ASoC: failed to init %s\n", + ec->hdr.name); + kfree(se); + continue; + } + + /* register control here */ + ret = soc_tplg_add_kcontrol(tplg, + &kc, &se->dobj.control.kcontrol); + if (ret < 0) { + dev_err(tplg->dev, "ASoC: could not add kcontrol %s\n", + ec->hdr.name); + kfree(se); + continue; + } + + list_add(&se->dobj.list, &tplg->comp->dobj_list); + } + + return 0; +} + +static int soc_tplg_kcontrol_elems_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + struct snd_soc_tplg_ctl_hdr *control_hdr; + int i; + + if (tplg->pass != SOC_TPLG_PASS_MIXER) { + tplg->pos += hdr->size + hdr->payload_size; + return 0; + } + + dev_dbg(tplg->dev, "ASoC: adding %d kcontrols at 0x%lx\n", hdr->count, + soc_tplg_get_offset(tplg)); + + for (i = 0; i < hdr->count; i++) { + + control_hdr = (struct snd_soc_tplg_ctl_hdr *)tplg->pos; + + switch (control_hdr->ops.info) { + case SND_SOC_TPLG_CTL_VOLSW: + case SND_SOC_TPLG_CTL_STROBE: + case SND_SOC_TPLG_CTL_VOLSW_SX: + case SND_SOC_TPLG_CTL_VOLSW_XR_SX: + case SND_SOC_TPLG_CTL_RANGE: + case SND_SOC_TPLG_DAPM_CTL_VOLSW: + case SND_SOC_TPLG_DAPM_CTL_PIN: + soc_tplg_dmixer_create(tplg, 1, hdr->payload_size); + break; + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_CTL_ENUM_VALUE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: + soc_tplg_denum_create(tplg, 1, hdr->payload_size); + break; + case SND_SOC_TPLG_CTL_BYTES: + soc_tplg_dbytes_create(tplg, 1, hdr->payload_size); + break; + default: + soc_bind_err(tplg, control_hdr, i); + return -EINVAL; + } + } + + return 0; +} + +static int soc_tplg_dapm_graph_elems_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + struct snd_soc_dapm_context *dapm = &tplg->comp->dapm; + struct snd_soc_dapm_route route; + struct snd_soc_tplg_dapm_graph_elem *elem; + int count = hdr->count, i; + + if (tplg->pass != SOC_TPLG_PASS_GRAPH) { + tplg->pos += hdr->size + hdr->payload_size; + return 0; + } + + if (soc_tplg_check_elem_count(tplg, + sizeof(struct snd_soc_tplg_dapm_graph_elem), + count, hdr->payload_size, "graph")) { + + dev_err(tplg->dev, "ASoC: invalid count %d for DAPM routes\n", + count); + return -EINVAL; + } + + dev_dbg(tplg->dev, "ASoC: adding %d DAPM routes\n", count); + + for (i = 0; i < count; i++) { + elem = (struct snd_soc_tplg_dapm_graph_elem *)tplg->pos; + tplg->pos += sizeof(struct snd_soc_tplg_dapm_graph_elem); + + /* validate routes */ + if (strnlen(elem->source, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + if (strnlen(elem->sink, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + if (strnlen(elem->control, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + + route.source = elem->source; + route.sink = elem->sink; + route.connected = NULL; /* set to NULL atm for tplg users */ + if (strnlen(elem->control, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == 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_tplg_dapm_widget_dmixer_create( + struct soc_tplg *tplg, int num_kcontrols) +{ + struct snd_kcontrol_new *kc; + struct soc_mixer_control *sm; + struct snd_soc_tplg_mixer_control *mc; + int i, err; + + kc = kzalloc(sizeof(*kc) * num_kcontrols, GFP_KERNEL); + if (kc == NULL) + return NULL; + + for (i = 0; i < num_kcontrols; i++) { + mc = (struct snd_soc_tplg_mixer_control *)tplg->pos; + sm = kzalloc(sizeof(*sm), GFP_KERNEL); + if (sm == NULL) + goto err; + + tplg->pos += (sizeof(struct snd_soc_tplg_mixer_control) + + mc->priv.size); + + /* validate kcontrol */ + if (strnlen(mc->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + goto err_str; + + dev_dbg(tplg->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; + + /* we only support FL/FR channel mapping atm */ + sm->reg = tplc_chan_get_reg(tplg, mc->channel, + SNDRV_CHMAP_FL); + sm->rreg = tplc_chan_get_reg(tplg, mc->channel, + SNDRV_CHMAP_FR); + sm->shift = tplc_chan_get_shift(tplg, mc->channel, + SNDRV_CHMAP_FL); + sm->rshift = tplc_chan_get_shift(tplg, mc->channel, + SNDRV_CHMAP_FR); + + sm->max = mc->max; + sm->min = mc->min; + sm->invert = mc->invert; + sm->platform_max = mc->platform_max; + sm->dobj.index = tplg->index; + INIT_LIST_HEAD(&sm->dobj.list); + + /* map io handlers */ + err = soc_tplg_kcontrol_bind_io(&mc->hdr, &kc[i], io_ops, + ARRAY_SIZE(io_ops), tplg->io_ops, tplg->io_ops_count); + if (err) { + soc_control_err(tplg, &mc->hdr, mc->hdr.name); + kfree(sm); + continue; + } + + /* pass control to driver for optional further init */ + err = soc_tplg_init_kcontrol(tplg, &kc[i], + (struct snd_soc_tplg_ctl_hdr *)mc); + if (err < 0) { + dev_err(tplg->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_tplg_dapm_widget_denum_create( + struct soc_tplg *tplg) +{ + struct snd_kcontrol_new *kc; + struct snd_soc_tplg_enum_control *ec; + struct soc_enum *se; + int i, err; + + ec = (struct snd_soc_tplg_enum_control *)tplg->pos; + tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + + ec->priv.size); + + /* validate kcontrol */ + if (strnlen(ec->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return NULL; + + kc = kzalloc(sizeof(*kc), GFP_KERNEL); + if (kc == NULL) + return NULL; + + se = kzalloc(sizeof(*se), GFP_KERNEL); + if (se == NULL) + goto err; + + dev_dbg(tplg->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; + + /* we only support FL/FR channel mapping atm */ + se->reg = tplc_chan_get_reg(tplg, ec->channel, SNDRV_CHMAP_FL); + se->shift_l = tplc_chan_get_shift(tplg, ec->channel, SNDRV_CHMAP_FL); + se->shift_r = tplc_chan_get_shift(tplg, ec->channel, SNDRV_CHMAP_FR); + + se->items = ec->items; + se->mask = ec->mask; + se->dobj.index = tplg->index; + + switch (ec->hdr.ops.info) { + case SND_SOC_TPLG_CTL_ENUM_VALUE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: + err = soc_tplg_denum_create_values(se, ec); + if (err < 0) { + dev_err(tplg->dev, "ASoC: could not create values for %s\n", + ec->hdr.name); + goto err_se; + } + /* fall through to create texts */ + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: + err = soc_tplg_denum_create_texts(se, ec); + if (err < 0) { + dev_err(tplg->dev, "ASoC: could not create texts for %s\n", + ec->hdr.name); + goto err_se; + } + break; + default: + dev_err(tplg->dev, "ASoC: invalid enum control type %d for %s\n", + ec->hdr.ops.info, ec->hdr.name); + goto err_se; + } + + /* map io handlers */ + err = soc_tplg_kcontrol_bind_io(&ec->hdr, kc, io_ops, + ARRAY_SIZE(io_ops), tplg->io_ops, tplg->io_ops_count); + if (err) { + soc_control_err(tplg, &ec->hdr, ec->hdr.name); + goto err_se; + } + + /* pass control to driver for optional further init */ + err = soc_tplg_init_kcontrol(tplg, kc, + (struct snd_soc_tplg_ctl_hdr *)ec); + if (err < 0) { + dev_err(tplg->dev, "ASoC: failed to init %s\n", + ec->hdr.name); + goto err_se; + } + + return kc; + +err_se: + /* free values and texts */ + kfree(se->dobj.control.dvalues); + for (i = 0; i < ec->items; i++) + kfree(se->dobj.control.dtexts[i]); + + kfree(se); +err: + kfree(kc); + + return NULL; +} + +static struct snd_kcontrol_new *soc_tplg_dapm_widget_dbytes_create( + struct soc_tplg *tplg, int count) +{ + struct snd_soc_tplg_bytes_control *be; + struct soc_bytes_ext *sbe; + struct snd_kcontrol_new *kc; + int i, err; + + kc = kzalloc(sizeof(*kc) * count, GFP_KERNEL); + if (!kc) + return NULL; + + for (i = 0; i < count; i++) { + be = (struct snd_soc_tplg_bytes_control *)tplg->pos; + + /* validate kcontrol */ + if (strnlen(be->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + goto err; + + sbe = kzalloc(sizeof(*sbe), GFP_KERNEL); + if (sbe == NULL) + goto err; + + tplg->pos += (sizeof(struct snd_soc_tplg_bytes_control) + + be->priv.size); + + dev_dbg(tplg->dev, + "ASoC: adding bytes kcontrol %s with access 0x%x\n", + be->hdr.name, be->hdr.access); + + memset(kc, 0, sizeof(*kc)); + kc[i].name = be->hdr.name; + kc[i].private_value = (long)sbe; + kc[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kc[i].access = be->hdr.access; + + sbe->max = be->max; + INIT_LIST_HEAD(&sbe->dobj.list); + + /* map standard io handlers and check for external handlers */ + err = soc_tplg_kcontrol_bind_io(&be->hdr, &kc[i], io_ops, + ARRAY_SIZE(io_ops), tplg->io_ops, + tplg->io_ops_count); + if (err) { + soc_control_err(tplg, &be->hdr, be->hdr.name); + kfree(sbe); + continue; + } + + /* pass control to driver for optional further init */ + err = soc_tplg_init_kcontrol(tplg, &kc[i], + (struct snd_soc_tplg_ctl_hdr *)be); + if (err < 0) { + dev_err(tplg->dev, "ASoC: failed to init %s\n", + be->hdr.name); + kfree(sbe); + continue; + } + } + + return kc; + +err: + for (--i; i >= 0; i--) + kfree((void *)kc[i].private_value); + + kfree(kc); + return NULL; +} + +static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, + struct snd_soc_tplg_dapm_widget *w) +{ + struct snd_soc_dapm_context *dapm = &tplg->comp->dapm; + struct snd_soc_dapm_widget template, *widget; + struct snd_soc_tplg_ctl_hdr *control_hdr; + struct snd_soc_card *card = tplg->comp->card; + int ret = 0; + + if (strnlen(w->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + if (strnlen(w->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + + dev_dbg(tplg->dev, "ASoC: creating DAPM widget %s id %d\n", + w->name, w->id); + + memset(&template, 0, sizeof(template)); + + /* map user to kernel widget ID */ + template.id = get_widget_id(w->id); + if (template.id < 0) + return template.id; + + template.name = kstrdup(w->name, GFP_KERNEL); + if (!template.name) + return -ENOMEM; + template.sname = kstrdup(w->sname, GFP_KERNEL); + if (!template.sname) { + ret = -ENOMEM; + goto err; + } + template.reg = w->reg; + template.shift = w->shift; + template.mask = w->mask; + template.on_val = w->invert ? 0 : 1; + template.off_val = w->invert ? 1 : 0; + template.ignore_suspend = w->ignore_suspend; + template.event_flags = w->event_flags; + template.dobj.index = tplg->index; + + tplg->pos += + (sizeof(struct snd_soc_tplg_dapm_widget) + w->priv.size); + if (w->num_kcontrols == 0) { + template.num_kcontrols = 0; + goto widget; + } + + control_hdr = (struct snd_soc_tplg_ctl_hdr *)tplg->pos; + dev_dbg(tplg->dev, "ASoC: template %s has %d controls of type %x\n", + w->name, w->num_kcontrols, control_hdr->type); + + switch (control_hdr->ops.info) { + case SND_SOC_TPLG_CTL_VOLSW: + case SND_SOC_TPLG_CTL_STROBE: + case SND_SOC_TPLG_CTL_VOLSW_SX: + case SND_SOC_TPLG_CTL_VOLSW_XR_SX: + case SND_SOC_TPLG_CTL_RANGE: + case SND_SOC_TPLG_DAPM_CTL_VOLSW: + template.num_kcontrols = w->num_kcontrols; + template.kcontrol_news = + soc_tplg_dapm_widget_dmixer_create(tplg, + template.num_kcontrols); + if (!template.kcontrol_news) { + ret = -ENOMEM; + goto hdr_err; + } + break; + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_CTL_ENUM_VALUE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: + template.dobj.widget.kcontrol_enum = 1; + template.num_kcontrols = 1; + template.kcontrol_news = + soc_tplg_dapm_widget_denum_create(tplg); + if (!template.kcontrol_news) { + ret = -ENOMEM; + goto hdr_err; + } + break; + case SND_SOC_TPLG_CTL_BYTES: + template.num_kcontrols = w->num_kcontrols; + template.kcontrol_news = + soc_tplg_dapm_widget_dbytes_create(tplg, + template.num_kcontrols); + if (!template.kcontrol_news) { + ret = -ENOMEM; + goto hdr_err; + } + break; + default: + dev_err(tplg->dev, "ASoC: invalid widget control type %d:%d:%d\n", + control_hdr->ops.get, control_hdr->ops.put, + control_hdr->ops.info); + ret = -EINVAL; + goto hdr_err; + } + +widget: + ret = soc_tplg_widget_load(tplg, &template, w); + if (ret < 0) + goto hdr_err; + + /* card dapm mutex is held by the core if we are loading topology + * data during sound card init. */ + if (card->instantiated) + widget = snd_soc_dapm_new_control(dapm, &template); + else + widget = snd_soc_dapm_new_control_unlocked(dapm, &template); + if (widget == NULL) { + dev_err(tplg->dev, "ASoC: failed to create widget %s controls\n", + w->name); + goto hdr_err; + } + + widget->dobj.type = SND_SOC_DOBJ_WIDGET; + widget->dobj.ops = tplg->ops; + widget->dobj.index = tplg->index; + list_add(&widget->dobj.list, &tplg->comp->dobj_list); + return 0; + +hdr_err: + kfree(template.sname); +err: + kfree(template.name); + return ret; +} + +static int soc_tplg_dapm_widget_elems_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + struct snd_soc_tplg_dapm_widget *widget; + int ret, count = hdr->count, i; + + if (tplg->pass != SOC_TPLG_PASS_WIDGET) + return 0; + + dev_dbg(tplg->dev, "ASoC: adding %d DAPM widgets\n", count); + + for (i = 0; i < count; i++) { + widget = (struct snd_soc_tplg_dapm_widget *) tplg->pos; + ret = soc_tplg_dapm_widget_create(tplg, widget); + if (ret < 0) + dev_err(tplg->dev, "ASoC: failed to load widget %s\n", + widget->name); + } + + return 0; +} + +static int soc_tplg_dapm_complete(struct soc_tplg *tplg) +{ + struct snd_soc_card *card = tplg->comp->card; + int ret; + + /* Card might not have been registered at this point. + * If so, just return success. + */ + if (!card || !card->instantiated) { + dev_warn(tplg->dev, "ASoC: Parent card not yet available," + "Do not add new widgets now\n"); + return 0; + } + + ret = snd_soc_dapm_new_widgets(card); + if (ret < 0) + dev_err(tplg->dev, "ASoC: failed to create new widgets %d\n", + ret); + + return 0; +} + +static int soc_tplg_pcm_dai_elems_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + struct snd_soc_tplg_pcm_dai *pcm_dai; + struct snd_soc_dobj *dobj; + int count = hdr->count; + int ret; + + if (tplg->pass != SOC_TPLG_PASS_PCM_DAI) + return 0; + + pcm_dai = (struct snd_soc_tplg_pcm_dai *)tplg->pos; + + if (soc_tplg_check_elem_count(tplg, + sizeof(struct snd_soc_tplg_pcm_dai), count, + hdr->payload_size, "PCM DAI")) { + dev_err(tplg->dev, "ASoC: invalid count %d for PCM DAI elems\n", + count); + return -EINVAL; + } + + dev_dbg(tplg->dev, "ASoC: adding %d PCM DAIs\n", count); + tplg->pos += sizeof(struct snd_soc_tplg_pcm_dai) * count; + + dobj = kzalloc(sizeof(struct snd_soc_dobj), GFP_KERNEL); + if (dobj == NULL) + return -ENOMEM; + + /* Call the platform driver call back to register the dais */ + ret = soc_tplg_pcm_dai_load(tplg, pcm_dai, count); + if (ret < 0) { + dev_err(tplg->comp->dev, "ASoC: PCM DAI loading failed\n"); + goto err; + } + + dobj->type = get_dobj_type(hdr, NULL); + dobj->pcm_dai.count = count; + dobj->pcm_dai.pd = pcm_dai; + dobj->ops = tplg->ops; + dobj->index = tplg->index; + list_add(&dobj->list, &tplg->comp->dobj_list); + return 0; + +err: + kfree(dobj); + return ret; +} + +static int soc_tplg_manifest_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + struct snd_soc_tplg_manifest *manifest; + + if (tplg->pass != SOC_TPLG_PASS_MANIFEST) + return 0; + + manifest = (struct snd_soc_tplg_manifest *)tplg->pos; + tplg->pos += sizeof(struct snd_soc_tplg_manifest); + + if (tplg->comp && tplg->ops && tplg->ops->manifest) + return tplg->ops->manifest(tplg->comp, manifest); + + dev_err(tplg->dev, "ASoC: Firmware manifest not supported\n"); + return 0; +} + +/* validate header magic, size and type */ +static int soc_valid_header(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + if (soc_tplg_get_hdr_offset(tplg) >= tplg->fw->size) + return 0; + + /* big endian firmware objects not supported atm */ + if (hdr->magic == cpu_to_be32(SND_SOC_TPLG_MAGIC)) { + dev_err(tplg->dev, + "ASoC: pass %d big endian not supported header got %x at offset 0x%lx size 0x%zx.\n", + tplg->pass, hdr->magic, + soc_tplg_get_hdr_offset(tplg), tplg->fw->size); + return -EINVAL; + } + + if (hdr->magic != SND_SOC_TPLG_MAGIC) { + dev_err(tplg->dev, + "ASoC: pass %d does not have a valid header got %x at offset 0x%lx size 0x%zx.\n", + tplg->pass, hdr->magic, + soc_tplg_get_hdr_offset(tplg), tplg->fw->size); + return -EINVAL; + } + + if (hdr->abi != SND_SOC_TPLG_ABI_VERSION) { + dev_err(tplg->dev, + "ASoC: pass %d invalid ABI version got 0x%x need 0x%x at offset 0x%lx size 0x%zx.\n", + tplg->pass, hdr->abi, + SND_SOC_TPLG_ABI_VERSION, soc_tplg_get_hdr_offset(tplg), + tplg->fw->size); + return -EINVAL; + } + + if (hdr->payload_size == 0) { + dev_err(tplg->dev, "ASoC: header has 0 size at offset 0x%lx.\n", + soc_tplg_get_hdr_offset(tplg)); + return -EINVAL; + } + + if (tplg->pass == hdr->type) + dev_dbg(tplg->dev, + "ASoC: Got 0x%x bytes of type %d version %d vendor %d at pass %d\n", + hdr->payload_size, hdr->type, hdr->version, + hdr->vendor_type, tplg->pass); + + return 1; +} + +/* check header type and call appropriate handler */ +static int soc_tplg_load_header(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + tplg->pos = tplg->hdr_pos + sizeof(struct snd_soc_tplg_hdr); + + /* check for matching ID */ + if (hdr->index != tplg->req_index && + hdr->index != SND_SOC_TPLG_INDEX_ALL) + return 0; + + tplg->index = hdr->index; + + switch (hdr->type) { + case SND_SOC_TPLG_TYPE_MIXER: + case SND_SOC_TPLG_TYPE_ENUM: + case SND_SOC_TPLG_TYPE_BYTES: + return soc_tplg_kcontrol_elems_load(tplg, hdr); + case SND_SOC_TPLG_TYPE_DAPM_GRAPH: + return soc_tplg_dapm_graph_elems_load(tplg, hdr); + case SND_SOC_TPLG_TYPE_DAPM_WIDGET: + return soc_tplg_dapm_widget_elems_load(tplg, hdr); + case SND_SOC_TPLG_TYPE_PCM: + case SND_SOC_TPLG_TYPE_DAI_LINK: + case SND_SOC_TPLG_TYPE_CODEC_LINK: + return soc_tplg_pcm_dai_elems_load(tplg, hdr); + case SND_SOC_TPLG_TYPE_MANIFEST: + return soc_tplg_manifest_load(tplg, hdr); + default: + /* bespoke vendor data object */ + return soc_tplg_vendor_load(tplg, hdr); + } + + return 0; +} + +/* process the topology file headers */ +static int soc_tplg_process_headers(struct soc_tplg *tplg) +{ + struct snd_soc_tplg_hdr *hdr; + int ret; + + tplg->pass = SOC_TPLG_PASS_START; + + /* process the header types from start to end */ + while (tplg->pass <= SOC_TPLG_PASS_END) { + + tplg->hdr_pos = tplg->fw->data; + hdr = (struct snd_soc_tplg_hdr *)tplg->hdr_pos; + + while (!soc_tplg_is_eof(tplg)) { + + /* make sure header is valid before loading */ + ret = soc_valid_header(tplg, hdr); + if (ret < 0) + return ret; + else if (ret == 0) + break; + + /* load the header object */ + ret = soc_tplg_load_header(tplg, hdr); + if (ret < 0) + return ret; + + /* goto next header */ + tplg->hdr_pos += hdr->payload_size + + sizeof(struct snd_soc_tplg_hdr); + hdr = (struct snd_soc_tplg_hdr *)tplg->hdr_pos; + } + + /* next data type pass */ + tplg->pass++; + } + + /* signal DAPM we are complete */ + ret = soc_tplg_dapm_complete(tplg); + if (ret < 0) + dev_err(tplg->dev, + "ASoC: failed to initialise DAPM from Firmware\n"); + + return ret; +} + +static int soc_tplg_load(struct soc_tplg *tplg) +{ + int ret; + + ret = soc_tplg_process_headers(tplg); + if (ret == 0) + soc_tplg_complete(tplg); + + return ret; +} + +/* load audio component topology from "firmware" file */ +int snd_soc_tplg_component_load(struct snd_soc_component *comp, + struct snd_soc_tplg_ops *ops, const struct firmware *fw, u32 id) +{ + struct soc_tplg tplg; + + /* setup parsing context */ + memset(&tplg, 0, sizeof(tplg)); + tplg.fw = fw; + tplg.dev = comp->dev; + tplg.comp = comp; + tplg.ops = ops; + tplg.req_index = id; + tplg.io_ops = ops->io_ops; + tplg.io_ops_count = ops->io_ops_count; + + return soc_tplg_load(&tplg); +} +EXPORT_SYMBOL_GPL(snd_soc_tplg_component_load); + +/* remove this dynamic widget */ +void snd_soc_tplg_widget_remove(struct snd_soc_dapm_widget *w) +{ + /* make sure we are a widget */ + if (w->dobj.type != SND_SOC_DOBJ_WIDGET) + return; + + remove_widget(w->dapm->component, &w->dobj, SOC_TPLG_PASS_WIDGET); +} +EXPORT_SYMBOL_GPL(snd_soc_tplg_widget_remove); + +/* remove all dynamic widgets from this DAPM context */ +void snd_soc_tplg_widget_remove_all(struct snd_soc_dapm_context *dapm, + u32 index) +{ + 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) { + + /* make sure we are a widget with correct context */ + if (w->dobj.type != SND_SOC_DOBJ_WIDGET || w->dapm != dapm) + continue; + + /* match ID */ + if (w->dobj.index != index && + w->dobj.index != SND_SOC_TPLG_INDEX_ALL) + 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); + } + 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); + } + /* check and free and dynamic widget kcontrols */ + snd_soc_tplg_widget_remove(w); + kfree(w->kcontrols); + kfree(w->name); + kfree(w); + } +} +EXPORT_SYMBOL_GPL(snd_soc_tplg_widget_remove_all); + +/* remove dynamic controls from the component driver */ +int snd_soc_tplg_component_remove(struct snd_soc_component *comp, u32 index) +{ + struct snd_soc_dobj *dobj, *next_dobj; + int pass = SOC_TPLG_PASS_END; + + /* process the header types from end to start */ + while (pass >= SOC_TPLG_PASS_START) { + + /* remove mixer controls */ + list_for_each_entry_safe(dobj, next_dobj, &comp->dobj_list, + list) { + + /* match index */ + if (dobj->index != index && + dobj->index != SND_SOC_TPLG_INDEX_ALL) + continue; + + switch (dobj->type) { + case SND_SOC_DOBJ_MIXER: + remove_mixer(comp, dobj, pass); + break; + case SND_SOC_DOBJ_ENUM: + remove_enum(comp, dobj, pass); + break; + case SND_SOC_DOBJ_BYTES: + remove_bytes(comp, dobj, pass); + break; + case SND_SOC_DOBJ_WIDGET: + remove_widget(comp, dobj, pass); + break; + case SND_SOC_DOBJ_PCM: + case SND_SOC_DOBJ_DAI_LINK: + case SND_SOC_DOBJ_CODEC_LINK: + remove_pcm_dai(comp, dobj, pass); + break; + default: + dev_err(comp->dev, "ASoC: invalid component type %d for removal\n", + dobj->type); + break; + } + } + pass--; + } + + /* let caller know if FW can be freed when no objects are left */ + return !list_empty(&comp->dobj_list); +} +EXPORT_SYMBOL_GPL(snd_soc_tplg_component_remove); |