summaryrefslogtreecommitdiffstats
path: root/drivers/staging/greybus/audio_topology.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2016-10-05 14:50:51 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2016-10-05 14:50:51 -0700
commit41844e36206be90cd4d962ea49b0abc3612a99d0 (patch)
treece0b3a3403bc6abdb28f52779d0d7b57a51a5c86 /drivers/staging/greybus/audio_topology.c
parent5691f0e9a3e7855832d5fd094801bf600347c2d0 (diff)
parentfc1e2c8ea85e109acf09e74789e9b852f6eed251 (diff)
downloadop-kernel-dev-41844e36206be90cd4d962ea49b0abc3612a99d0.zip
op-kernel-dev-41844e36206be90cd4d962ea49b0abc3612a99d0.tar.gz
Merge tag 'staging-4.9-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging
Pull staging and IIO updates from Greg KH: "Here is the big staging and IIO driver pull request for 4.9-rc1. There are a lot of patches in here, the majority due to the drivers/staging/greybus/ subsystem being merged in with full development history that went back a few years, in order to preserve the work that those developers did over time. Lots and lots of tiny cleanups happened in the tree as well, due to the Outreachy application process and lots of other developers showing up for the first time to clean code up. Along with those changes, we deleted a wireless driver, and added a raspberrypi driver (currently marked broken), and lots of new iio drivers. Overall the tree still shrunk with more lines removed than added, about 10 thousand lines removed in total. Full details are in the very long shortlog below. All of this has been in the linux-next tree with no issues. There will be some merge problems with other subsystem trees, but those are all minor problems and shouldn't be hard to work out when they happen (MAINTAINERS and some lustre build problems with the IB tree)" And furter from me asking for clarification about greybus: "Right now there is a phone from Motorola shipping with this code (a slightly older version, but the same tree), so even though Ara is not alive in the same form, the functionality is happening. We are working with the developers of that phone to merge the newer stuff in with their fork so they can use the upstream version in future versions of their phone product line. Toshiba has at least one chip shipping in their catalog that needs/uses this protocol over a Unipro link, and rumor has it that there might be more in the future. There are also other users of the greybus protocols, there is a talk next week at ELC that shows how it is being used across a network connection to control a device, and previous ELC talks have showed the protocol stack being used over USB to drive embedded Linux boards. I've also talked to some people who are starting to work to add a host controller driver to control arduinos as the greybus PHY protocols are very useful to control a serial/i2c/spio/whatever device across a random physical link, as it is a way to have a self-describing device be attached to a host without needing manual configuration. So yes, people are using it, and there is still the chance that it will show up in a phone/laptop/tablet/whatever from Google in the future as well, the tech isn't dead, even if the original large phone project happens to be" * tag 'staging-4.9-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging: (3703 commits) Staging: fbtft: Fix bug in fbtft-core staging: rtl8188eu: fix double unlock error in rtw_resume_process() staging:r8188eu: remove GEN_MLME_EXT_HANDLER macro staging:r8188eu: remove GEN_DRV_CMD_HANDLER macro staging:r8188eu: remove GEN_EVT_CODE macro staging:r8188eu: remove GEN_CMD_CODE macro staging:r8188eu: remove pkt_newalloc member of the recv_buf structure staging:r8188eu: remove rtw_handle_dualmac declaration staging:r8188eu: remove (RGTRY|BSSID)_(OFT|SZ) macros staging:r8188eu: change rtl8188e_process_phy_info function argument type Staging: fsl-mc: Remove blank lines Staging: fsl-mc: Fix unaligned * in block comments Staging: comedi: Align the * in block comments Staging : ks7010 : Fix block comments warninig Staging: vt6655: Remove explicit NULL comparison using Coccinelle staging: rtl8188eu: core: rtw_xmit: Use macros instead of constants staging: rtl8188eu: core: rtw_xmit: Move constant of the right side staging: dgnc: Fix lines longer than 80 characters Staging: dgnc: constify attribute_group structures Staging: most: hdm-dim2: constify attribute_group structures ...
Diffstat (limited to 'drivers/staging/greybus/audio_topology.c')
-rw-r--r--drivers/staging/greybus/audio_topology.c1443
1 files changed, 1443 insertions, 0 deletions
diff --git a/drivers/staging/greybus/audio_topology.c b/drivers/staging/greybus/audio_topology.c
new file mode 100644
index 0000000..b625169
--- /dev/null
+++ b/drivers/staging/greybus/audio_topology.c
@@ -0,0 +1,1443 @@
+/*
+ * Greybus audio driver
+ * Copyright 2015-2016 Google Inc.
+ * Copyright 2015-2016 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include "audio_codec.h"
+#include "greybus_protocols.h"
+
+#define GBAUDIO_INVALID_ID 0xFF
+
+/* mixer control */
+struct gb_mixer_control {
+ int min, max;
+ unsigned int reg, rreg, shift, rshift, invert;
+};
+
+struct gbaudio_ctl_pvt {
+ unsigned int ctl_id;
+ unsigned int data_cport;
+ unsigned int access;
+ unsigned int vcount;
+ struct gb_audio_ctl_elem_info *info;
+};
+
+static struct gbaudio_module_info *find_gb_module(
+ struct gbaudio_codec_info *codec,
+ char const *name)
+{
+ int dev_id, ret;
+ char begin[NAME_SIZE];
+ struct gbaudio_module_info *module;
+
+ if (!name)
+ return NULL;
+
+ ret = sscanf(name, "%s %d", begin, &dev_id);
+ dev_dbg(codec->dev, "%s:Find module#%d\n", __func__, dev_id);
+
+ mutex_lock(&codec->lock);
+ list_for_each_entry(module, &codec->module_list, list) {
+ if (module->dev_id == dev_id) {
+ mutex_unlock(&codec->lock);
+ return module;
+ }
+ }
+ mutex_unlock(&codec->lock);
+ dev_warn(codec->dev, "%s: module#%d missing in codec list\n", name,
+ dev_id);
+ return NULL;
+}
+
+static const char *gbaudio_map_controlid(struct gbaudio_module_info *module,
+ __u8 control_id, __u8 index)
+{
+ struct gbaudio_control *control;
+
+ if (control_id == GBAUDIO_INVALID_ID)
+ return NULL;
+
+ list_for_each_entry(control, &module->ctl_list, list) {
+ if (control->id == control_id) {
+ if (index == GBAUDIO_INVALID_ID)
+ return control->name;
+ if (index >= control->items)
+ return NULL;
+ return control->texts[index];
+ }
+ }
+ list_for_each_entry(control, &module->widget_ctl_list, list) {
+ if (control->id == control_id) {
+ if (index == GBAUDIO_INVALID_ID)
+ return control->name;
+ if (index >= control->items)
+ return NULL;
+ return control->texts[index];
+ }
+ }
+ return NULL;
+}
+
+static int gbaudio_map_controlname(struct gbaudio_module_info *module,
+ const char *name)
+{
+ struct gbaudio_control *control;
+
+ list_for_each_entry(control, &module->ctl_list, list) {
+ if (!strncmp(control->name, name, NAME_SIZE))
+ return control->id;
+ }
+
+ dev_warn(module->dev, "%s: missing in modules controls list\n", name);
+
+ return -EINVAL;
+}
+
+static int gbaudio_map_wcontrolname(struct gbaudio_module_info *module,
+ const char *name)
+{
+ struct gbaudio_control *control;
+
+ list_for_each_entry(control, &module->widget_ctl_list, list) {
+ if (!strncmp(control->wname, name, NAME_SIZE))
+ return control->id;
+ }
+ dev_warn(module->dev, "%s: missing in modules controls list\n", name);
+
+ return -EINVAL;
+}
+
+static int gbaudio_map_widgetname(struct gbaudio_module_info *module,
+ const char *name)
+{
+ struct gbaudio_widget *widget;
+ list_for_each_entry(widget, &module->widget_list, list) {
+ if (!strncmp(widget->name, name, NAME_SIZE))
+ return widget->id;
+ }
+ dev_warn(module->dev, "%s: missing in modules widgets list\n", name);
+
+ return -EINVAL;
+}
+
+static const char *gbaudio_map_widgetid(struct gbaudio_module_info *module,
+ __u8 widget_id)
+{
+ struct gbaudio_widget *widget;
+
+ list_for_each_entry(widget, &module->widget_list, list) {
+ if (widget->id == widget_id)
+ return widget->name;
+ }
+ return NULL;
+}
+
+static const char **gb_generate_enum_strings(struct gbaudio_module_info *gb,
+ struct gb_audio_enumerated *gbenum)
+{
+ const char **strings;
+ int i;
+ __u8 *data;
+
+ strings = devm_kzalloc(gb->dev, sizeof(char *) * gbenum->items,
+ GFP_KERNEL);
+ data = gbenum->names;
+
+ for (i = 0; i < gbenum->items; i++) {
+ strings[i] = (const char *)data;
+ while (*data != '\0')
+ data++;
+ data++;
+ }
+
+ return strings;
+}
+
+static int gbcodec_mixer_ctl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ unsigned int max;
+ const char *name;
+ struct gbaudio_ctl_pvt *data;
+ struct gb_audio_ctl_elem_info *info;
+ struct gbaudio_module_info *module;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct gbaudio_codec_info *gbcodec = snd_soc_codec_get_drvdata(codec);
+
+ dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ info = (struct gb_audio_ctl_elem_info *)data->info;
+
+ if (!info) {
+ dev_err(codec->dev, "NULL info for %s\n", uinfo->id.name);
+ return -EINVAL;
+ }
+
+ /* update uinfo */
+ uinfo->access = data->access;
+ uinfo->count = data->vcount;
+ uinfo->type = (snd_ctl_elem_type_t)info->type;
+
+ switch (info->type) {
+ case GB_AUDIO_CTL_ELEM_TYPE_BOOLEAN:
+ case GB_AUDIO_CTL_ELEM_TYPE_INTEGER:
+ uinfo->value.integer.min = info->value.integer.min;
+ uinfo->value.integer.max = info->value.integer.max;
+ break;
+ case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED:
+ max = info->value.enumerated.items;
+ uinfo->value.enumerated.items = max;
+ if (uinfo->value.enumerated.item > max - 1)
+ uinfo->value.enumerated.item = max - 1;
+ module = find_gb_module(gbcodec, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+ name = gbaudio_map_controlid(module, data->ctl_id,
+ uinfo->value.enumerated.item);
+ strlcpy(uinfo->value.enumerated.name, name, NAME_SIZE);
+ break;
+ default:
+ dev_err(codec->dev, "Invalid type: %d for %s:kcontrol\n",
+ info->type, kcontrol->id.name);
+ break;
+ }
+ return 0;
+}
+
+static int gbcodec_mixer_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret;
+ struct gb_audio_ctl_elem_info *info;
+ struct gbaudio_ctl_pvt *data;
+ struct gb_audio_ctl_elem_value gbvalue;
+ struct gbaudio_module_info *module;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+ struct gb_bundle *bundle;
+
+ dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+ module = find_gb_module(gb, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ info = (struct gb_audio_ctl_elem_info *)data->info;
+ bundle = to_gb_bundle(module->dev);
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_get_control(module->mgmt_connection, data->ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ return ret;
+ }
+
+ /* update ucontrol */
+ switch (info->type) {
+ case GB_AUDIO_CTL_ELEM_TYPE_BOOLEAN:
+ case GB_AUDIO_CTL_ELEM_TYPE_INTEGER:
+ ucontrol->value.integer.value[0] =
+ gbvalue.value.integer_value[0];
+ if (data->vcount == 2)
+ ucontrol->value.integer.value[1] =
+ gbvalue.value.integer_value[1];
+ break;
+ case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED:
+ ucontrol->value.enumerated.item[0] =
+ gbvalue.value.enumerated_item[0];
+ if (data->vcount == 2)
+ ucontrol->value.enumerated.item[1] =
+ gbvalue.value.enumerated_item[1];
+ break;
+ default:
+ dev_err(codec->dev, "Invalid type: %d for %s:kcontrol\n",
+ info->type, kcontrol->id.name);
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int gbcodec_mixer_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct gb_audio_ctl_elem_info *info;
+ struct gbaudio_ctl_pvt *data;
+ struct gb_audio_ctl_elem_value gbvalue;
+ struct gbaudio_module_info *module;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+ struct gb_bundle *bundle;
+
+ dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+ module = find_gb_module(gb, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ info = (struct gb_audio_ctl_elem_info *)data->info;
+ bundle = to_gb_bundle(module->dev);
+
+ /* update ucontrol */
+ switch (info->type) {
+ case GB_AUDIO_CTL_ELEM_TYPE_BOOLEAN:
+ case GB_AUDIO_CTL_ELEM_TYPE_INTEGER:
+ gbvalue.value.integer_value[0] =
+ ucontrol->value.integer.value[0];
+ if (data->vcount == 2)
+ gbvalue.value.integer_value[1] =
+ ucontrol->value.integer.value[1];
+ break;
+ case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED:
+ gbvalue.value.enumerated_item[0] =
+ ucontrol->value.enumerated.item[0];
+ if (data->vcount == 2)
+ gbvalue.value.enumerated_item[1] =
+ ucontrol->value.enumerated.item[1];
+ break;
+ default:
+ dev_err(codec->dev, "Invalid type: %d for %s:kcontrol\n",
+ info->type, kcontrol->id.name);
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret)
+ return ret;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_set_control(module->mgmt_connection, data->ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ }
+
+ return ret;
+}
+
+#define SOC_MIXER_GB(xname, kcount, data) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .count = kcount, .info = gbcodec_mixer_ctl_info, \
+ .get = gbcodec_mixer_ctl_get, .put = gbcodec_mixer_ctl_put, \
+ .private_value = (unsigned long)data }
+
+/*
+ * although below callback functions seems redundant to above functions.
+ * same are kept to allow provision for different handling in case
+ * of DAPM related sequencing, etc.
+ */
+static int gbcodec_mixer_dapm_ctl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int platform_max, platform_min;
+ struct gbaudio_ctl_pvt *data;
+ struct gb_audio_ctl_elem_info *info;
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+
+ dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ info = (struct gb_audio_ctl_elem_info *)data->info;
+
+ /* update uinfo */
+ platform_max = info->value.integer.max;
+ platform_min = info->value.integer.min;
+
+ if (platform_max == 1 &&
+ !strnstr(kcontrol->id.name, " Volume", NAME_SIZE))
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ else
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+
+ uinfo->count = data->vcount;
+ uinfo->value.integer.min = 0;
+ if (info->value.integer.min < 0 &&
+ (uinfo->type == SNDRV_CTL_ELEM_TYPE_INTEGER))
+ uinfo->value.integer.max = platform_max - platform_min;
+ else
+ uinfo->value.integer.max = platform_max;
+
+ return 0;
+}
+
+static int gbcodec_mixer_dapm_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret;
+ struct gb_audio_ctl_elem_info *info;
+ struct gbaudio_ctl_pvt *data;
+ struct gb_audio_ctl_elem_value gbvalue;
+ struct gbaudio_module_info *module;
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+ struct gb_bundle *bundle;
+
+ dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+ module = find_gb_module(gb, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ info = (struct gb_audio_ctl_elem_info *)data->info;
+ bundle = to_gb_bundle(module->dev);
+
+ if (data->vcount == 2)
+ dev_warn(widget->dapm->dev,
+ "GB: Control '%s' is stereo, which is not supported\n",
+ kcontrol->id.name);
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_get_control(module->mgmt_connection, data->ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ return ret;
+ }
+ /* update ucontrol */
+ ucontrol->value.integer.value[0] = gbvalue.value.integer_value[0];
+
+ return ret;
+}
+
+static int gbcodec_mixer_dapm_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret, wi, max, connect;
+ unsigned int mask, val;
+ struct gb_audio_ctl_elem_info *info;
+ struct gbaudio_ctl_pvt *data;
+ struct gb_audio_ctl_elem_value gbvalue;
+ struct gbaudio_module_info *module;
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+ struct gb_bundle *bundle;
+
+ dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+ module = find_gb_module(gb, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ info = (struct gb_audio_ctl_elem_info *)data->info;
+ bundle = to_gb_bundle(module->dev);
+
+ if (data->vcount == 2)
+ dev_warn(widget->dapm->dev,
+ "GB: Control '%s' is stereo, which is not supported\n",
+ kcontrol->id.name);
+
+ max = info->value.integer.max;
+ mask = (1 << fls(max)) - 1;
+ val = ucontrol->value.integer.value[0] & mask;
+ connect = !!val;
+
+ /* update ucontrol */
+ if (gbvalue.value.integer_value[0] != val) {
+ for (wi = 0; wi < wlist->num_widgets; wi++) {
+ widget = wlist->widgets[wi];
+
+ widget->value = val;
+ widget->dapm->update = NULL;
+ snd_soc_dapm_mixer_update_power(widget, kcontrol,
+ connect);
+ }
+ gbvalue.value.integer_value[0] =
+ ucontrol->value.integer.value[0];
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_set_control(module->mgmt_connection,
+ data->ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(codec->dev,
+ "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+#define SOC_DAPM_MIXER_GB(xname, kcount, data) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .count = kcount, .info = gbcodec_mixer_dapm_ctl_info, \
+ .get = gbcodec_mixer_dapm_ctl_get, .put = gbcodec_mixer_dapm_ctl_put, \
+ .private_value = (unsigned long)data}
+
+static int gbcodec_event_spk(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ /* Ensure GB speaker is connected */
+
+ return 0;
+}
+
+static int gbcodec_event_hp(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ /* Ensure GB module supports jack slot */
+
+ return 0;
+}
+
+static int gbcodec_event_int_mic(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ /* Ensure GB module supports jack slot */
+
+ return 0;
+}
+
+static int gbaudio_validate_kcontrol_count(struct gb_audio_widget *w)
+{
+ int ret = 0;
+
+ switch (w->type) {
+ case snd_soc_dapm_spk:
+ case snd_soc_dapm_hp:
+ case snd_soc_dapm_mic:
+ case snd_soc_dapm_output:
+ case snd_soc_dapm_input:
+ if (w->ncontrols)
+ ret = -EINVAL;
+ break;
+ case snd_soc_dapm_switch:
+ case snd_soc_dapm_mux:
+ if (w->ncontrols != 1)
+ ret = -EINVAL;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int gbcodec_enum_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret, ctl_id;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ struct gb_audio_ctl_elem_value gbvalue;
+ struct gbaudio_module_info *module;
+ struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+ struct gb_bundle *bundle;
+
+ module = find_gb_module(gb, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+
+ ctl_id = gbaudio_map_controlname(module, kcontrol->id.name);
+ if (ctl_id < 0)
+ return -EINVAL;
+
+ bundle = to_gb_bundle(module->dev);
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_get_control(module->mgmt_connection, ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ return ret;
+ }
+
+ ucontrol->value.enumerated.item[0] = gbvalue.value.enumerated_item[0];
+ if (e->shift_l != e->shift_r)
+ ucontrol->value.enumerated.item[1] =
+ gbvalue.value.enumerated_item[1];
+
+ return 0;
+}
+
+static int gbcodec_enum_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret, ctl_id;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ struct gb_audio_ctl_elem_value gbvalue;
+ struct gbaudio_module_info *module;
+ struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+ struct gb_bundle *bundle;
+
+ module = find_gb_module(gb, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+
+ ctl_id = gbaudio_map_controlname(module, kcontrol->id.name);
+ if (ctl_id < 0)
+ return -EINVAL;
+
+ if (ucontrol->value.enumerated.item[0] > e->max - 1)
+ return -EINVAL;
+ gbvalue.value.enumerated_item[0] = ucontrol->value.enumerated.item[0];
+
+ if (e->shift_l != e->shift_r) {
+ if (ucontrol->value.enumerated.item[1] > e->max - 1)
+ return -EINVAL;
+ gbvalue.value.enumerated_item[1] =
+ ucontrol->value.enumerated.item[1];
+ }
+
+ bundle = to_gb_bundle(module->dev);
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_set_control(module->mgmt_connection, ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ }
+
+ return ret;
+}
+
+static int gbaudio_tplg_create_enum_kctl(struct gbaudio_module_info *gb,
+ struct snd_kcontrol_new *kctl,
+ struct gb_audio_control *ctl)
+{
+ struct soc_enum *gbe;
+ struct gb_audio_enumerated *gb_enum;
+ int i;
+
+ gbe = devm_kzalloc(gb->dev, sizeof(*gbe), GFP_KERNEL);
+ if (!gbe)
+ return -ENOMEM;
+
+ gb_enum = &ctl->info.value.enumerated;
+
+ /* since count=1, and reg is dummy */
+ gbe->max = gb_enum->items;
+ gbe->texts = gb_generate_enum_strings(gb, gb_enum);
+
+ /* debug enum info */
+ dev_dbg(gb->dev, "Max:%d, name_length:%d\n", gb_enum->items,
+ gb_enum->names_length);
+ for (i = 0; i < gb_enum->items; i++)
+ dev_dbg(gb->dev, "src[%d]: %s\n", i, gbe->texts[i]);
+
+ *kctl = (struct snd_kcontrol_new)
+ SOC_ENUM_EXT(ctl->name, *gbe, gbcodec_enum_ctl_get,
+ gbcodec_enum_ctl_put);
+ return 0;
+}
+
+static int gbaudio_tplg_create_kcontrol(struct gbaudio_module_info *gb,
+ struct snd_kcontrol_new *kctl,
+ struct gb_audio_control *ctl)
+{
+ int ret = 0;
+ struct gbaudio_ctl_pvt *ctldata;
+
+ switch (ctl->iface) {
+ case SNDRV_CTL_ELEM_IFACE_MIXER:
+ switch (ctl->info.type) {
+ case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED:
+ ret = gbaudio_tplg_create_enum_kctl(gb, kctl, ctl);
+ break;
+ default:
+ ctldata = devm_kzalloc(gb->dev,
+ sizeof(struct gbaudio_ctl_pvt),
+ GFP_KERNEL);
+ if (!ctldata)
+ return -ENOMEM;
+ ctldata->ctl_id = ctl->id;
+ ctldata->data_cport = ctl->data_cport;
+ ctldata->access = ctl->access;
+ ctldata->vcount = ctl->count_values;
+ ctldata->info = &ctl->info;
+ *kctl = (struct snd_kcontrol_new)
+ SOC_MIXER_GB(ctl->name, ctl->count, ctldata);
+ ctldata = NULL;
+ break;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(gb->dev, "%s:%d control created\n", ctl->name, ctl->id);
+ return ret;
+}
+
+static int gbcodec_enum_dapm_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret, ctl_id;
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct gbaudio_module_info *module;
+ struct gb_audio_ctl_elem_value gbvalue;
+ struct snd_soc_codec *codec = widget->codec;
+ struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ struct gb_bundle *bundle;
+
+ module = find_gb_module(gb, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+
+ ctl_id = gbaudio_map_wcontrolname(module, kcontrol->id.name);
+ if (ctl_id < 0)
+ return -EINVAL;
+
+ bundle = to_gb_bundle(module->dev);
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_get_control(module->mgmt_connection, ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ return ret;
+ }
+
+ ucontrol->value.enumerated.item[0] = gbvalue.value.enumerated_item[0];
+ if (e->shift_l != e->shift_r)
+ ucontrol->value.enumerated.item[1] =
+ gbvalue.value.enumerated_item[1];
+
+ return 0;
+}
+
+static int gbcodec_enum_dapm_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret, wi, ctl_id;
+ unsigned int val, mux, change;
+ unsigned int mask;
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct gb_audio_ctl_elem_value gbvalue;
+ struct gbaudio_module_info *module;
+ struct snd_soc_codec *codec = widget->codec;
+ struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ struct gb_bundle *bundle;
+
+ if (ucontrol->value.enumerated.item[0] > e->max - 1)
+ return -EINVAL;
+
+ module = find_gb_module(gb, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+
+ ctl_id = gbaudio_map_wcontrolname(module, kcontrol->id.name);
+ if (ctl_id < 0)
+ return -EINVAL;
+
+ change = 0;
+ bundle = to_gb_bundle(module->dev);
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_get_control(module->mgmt_connection, ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ return ret;
+ }
+
+ mux = ucontrol->value.enumerated.item[0];
+ val = mux << e->shift_l;
+ mask = e->mask << e->shift_l;
+
+ if (gbvalue.value.enumerated_item[0] !=
+ ucontrol->value.enumerated.item[0]) {
+ change = 1;
+ gbvalue.value.enumerated_item[0] =
+ ucontrol->value.enumerated.item[0];
+ }
+
+ if (e->shift_l != e->shift_r) {
+ if (ucontrol->value.enumerated.item[1] > e->max - 1)
+ return -EINVAL;
+ val |= ucontrol->value.enumerated.item[1] << e->shift_r;
+ mask |= e->mask << e->shift_r;
+ if (gbvalue.value.enumerated_item[1] !=
+ ucontrol->value.enumerated.item[1]) {
+ change = 1;
+ gbvalue.value.enumerated_item[1] =
+ ucontrol->value.enumerated.item[1];
+ }
+ }
+
+ if (change) {
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_set_control(module->mgmt_connection, ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(codec->dev,
+ "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ }
+ for (wi = 0; wi < wlist->num_widgets; wi++) {
+ widget = wlist->widgets[wi];
+
+ widget->value = val;
+ widget->dapm->update = NULL;
+ snd_soc_dapm_mux_update_power(widget, kcontrol, mux, e);
+ }
+ }
+
+ return change;
+}
+
+static int gbaudio_tplg_create_enum_ctl(struct gbaudio_module_info *gb,
+ struct snd_kcontrol_new *kctl,
+ struct gb_audio_control *ctl)
+{
+ struct soc_enum *gbe;
+ struct gb_audio_enumerated *gb_enum;
+ int i;
+
+ gbe = devm_kzalloc(gb->dev, sizeof(*gbe), GFP_KERNEL);
+ if (!gbe)
+ return -ENOMEM;
+
+ gb_enum = &ctl->info.value.enumerated;
+
+ /* since count=1, and reg is dummy */
+ gbe->max = gb_enum->items;
+ gbe->texts = gb_generate_enum_strings(gb, gb_enum);
+
+ /* debug enum info */
+ dev_dbg(gb->dev, "Max:%d, name_length:%d\n", gb_enum->items,
+ gb_enum->names_length);
+ for (i = 0; i < gb_enum->items; i++)
+ dev_dbg(gb->dev, "src[%d]: %s\n", i, gbe->texts[i]);
+
+ *kctl = (struct snd_kcontrol_new)
+ SOC_DAPM_ENUM_EXT(ctl->name, *gbe, gbcodec_enum_dapm_ctl_get,
+ gbcodec_enum_dapm_ctl_put);
+ return 0;
+}
+
+static int gbaudio_tplg_create_mixer_ctl(struct gbaudio_module_info *gb,
+ struct snd_kcontrol_new *kctl,
+ struct gb_audio_control *ctl)
+{
+ struct gbaudio_ctl_pvt *ctldata;
+
+ ctldata = devm_kzalloc(gb->dev, sizeof(struct gbaudio_ctl_pvt),
+ GFP_KERNEL);
+ if (!ctldata)
+ return -ENOMEM;
+ ctldata->ctl_id = ctl->id;
+ ctldata->data_cport = ctl->data_cport;
+ ctldata->access = ctl->access;
+ ctldata->vcount = ctl->count_values;
+ ctldata->info = &ctl->info;
+ *kctl = (struct snd_kcontrol_new)
+ SOC_DAPM_MIXER_GB(ctl->name, ctl->count, ctldata);
+
+ return 0;
+}
+
+static int gbaudio_tplg_create_wcontrol(struct gbaudio_module_info *gb,
+ struct snd_kcontrol_new *kctl,
+ struct gb_audio_control *ctl)
+{
+ int ret;
+
+ switch (ctl->iface) {
+ case SNDRV_CTL_ELEM_IFACE_MIXER:
+ switch (ctl->info.type) {
+ case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED:
+ ret = gbaudio_tplg_create_enum_ctl(gb, kctl, ctl);
+ break;
+ default:
+ ret = gbaudio_tplg_create_mixer_ctl(gb, kctl, ctl);
+ break;
+ }
+ break;
+ default:
+ return -EINVAL;
+
+ }
+
+ dev_dbg(gb->dev, "%s:%d DAPM control created, ret:%d\n", ctl->name,
+ ctl->id, ret);
+ return ret;
+}
+
+static int gbaudio_widget_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ int wid;
+ int ret;
+ struct snd_soc_codec *codec = w->codec;
+ struct gbaudio_codec_info *gbcodec = snd_soc_codec_get_drvdata(codec);
+ struct gbaudio_module_info *module;
+ struct gb_bundle *bundle;
+
+ dev_dbg(codec->dev, "%s %s %d\n", __func__, w->name, event);
+
+ /* Find relevant module */
+ module = find_gb_module(gbcodec, w->name);
+ if (!module)
+ return -EINVAL;
+
+ /* map name to widget id */
+ wid = gbaudio_map_widgetname(module, w->name);
+ if (wid < 0) {
+ dev_err(codec->dev, "Invalid widget name:%s\n", w->name);
+ return -EINVAL;
+ }
+
+ bundle = to_gb_bundle(module->dev);
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ ret = gb_audio_gb_enable_widget(module->mgmt_connection, wid);
+ if (!ret)
+ ret = gbaudio_module_update(gbcodec, w, module, 1);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ ret = gb_audio_gb_disable_widget(module->mgmt_connection, wid);
+ if (!ret)
+ ret = gbaudio_module_update(gbcodec, w, module, 0);
+ break;
+ }
+ if (ret)
+ dev_err_ratelimited(codec->dev,
+ "%d: widget, event:%d failed:%d\n", wid,
+ event, ret);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return ret;
+}
+
+static int gbaudio_tplg_create_widget(struct gbaudio_module_info *module,
+ struct snd_soc_dapm_widget *dw,
+ struct gb_audio_widget *w, int *w_size)
+{
+ int i, ret, csize;
+ struct snd_kcontrol_new *widget_kctls;
+ struct gb_audio_control *curr;
+ struct gbaudio_control *control, *_control;
+ size_t size;
+ char temp_name[NAME_SIZE];
+
+ ret = gbaudio_validate_kcontrol_count(w);
+ if (ret) {
+ dev_err(module->dev, "Inavlid kcontrol count=%d for %s\n",
+ w->ncontrols, w->name);
+ return ret;
+ }
+
+ /* allocate memory for kcontrol */
+ if (w->ncontrols) {
+ size = sizeof(struct snd_kcontrol_new) * w->ncontrols;
+ widget_kctls = devm_kzalloc(module->dev, size, GFP_KERNEL);
+ if (!widget_kctls)
+ return -ENOMEM;
+ }
+
+ *w_size = sizeof(struct gb_audio_widget);
+
+ /* create relevant kcontrols */
+ curr = w->ctl;
+ for (i = 0; i < w->ncontrols; i++) {
+ ret = gbaudio_tplg_create_wcontrol(module, &widget_kctls[i],
+ curr);
+ if (ret) {
+ dev_err(module->dev,
+ "%s:%d type widget_ctl not supported\n",
+ curr->name, curr->iface);
+ goto error;
+ }
+ control = devm_kzalloc(module->dev,
+ sizeof(struct gbaudio_control),
+ GFP_KERNEL);
+ if (!control) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ control->id = curr->id;
+ control->name = curr->name;
+ control->wname = w->name;
+
+ if (curr->info.type == GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED) {
+ struct gb_audio_enumerated *gbenum =
+ &curr->info.value.enumerated;
+
+ csize = offsetof(struct gb_audio_control, info);
+ csize += offsetof(struct gb_audio_ctl_elem_info, value);
+ csize += offsetof(struct gb_audio_enumerated, names);
+ csize += gbenum->names_length;
+ control->texts = (const char * const *)
+ gb_generate_enum_strings(module, gbenum);
+ control->items = gbenum->items;
+ } else
+ csize = sizeof(struct gb_audio_control);
+ *w_size += csize;
+ curr = (void *)curr + csize;
+ list_add(&control->list, &module->widget_ctl_list);
+ dev_dbg(module->dev, "%s: control of type %d created\n",
+ widget_kctls[i].name, widget_kctls[i].iface);
+ }
+
+ /* Prefix dev_id to widget control_name */
+ strlcpy(temp_name, w->name, NAME_SIZE);
+ snprintf(w->name, NAME_SIZE, "GB %d %s", module->dev_id, temp_name);
+
+ switch (w->type) {
+ case snd_soc_dapm_spk:
+ *dw = (struct snd_soc_dapm_widget)
+ SND_SOC_DAPM_SPK(w->name, gbcodec_event_spk);
+ module->op_devices |= GBAUDIO_DEVICE_OUT_SPEAKER;
+ break;
+ case snd_soc_dapm_hp:
+ *dw = (struct snd_soc_dapm_widget)
+ SND_SOC_DAPM_HP(w->name, gbcodec_event_hp);
+ module->op_devices |= (GBAUDIO_DEVICE_OUT_WIRED_HEADSET
+ | GBAUDIO_DEVICE_OUT_WIRED_HEADPHONE);
+ module->ip_devices |= GBAUDIO_DEVICE_IN_WIRED_HEADSET;
+ break;
+ case snd_soc_dapm_mic:
+ *dw = (struct snd_soc_dapm_widget)
+ SND_SOC_DAPM_MIC(w->name, gbcodec_event_int_mic);
+ module->ip_devices |= GBAUDIO_DEVICE_IN_BUILTIN_MIC;
+ break;
+ case snd_soc_dapm_output:
+ *dw = (struct snd_soc_dapm_widget)SND_SOC_DAPM_OUTPUT(w->name);
+ break;
+ case snd_soc_dapm_input:
+ *dw = (struct snd_soc_dapm_widget)SND_SOC_DAPM_INPUT(w->name);
+ break;
+ case snd_soc_dapm_switch:
+ *dw = (struct snd_soc_dapm_widget)
+ SND_SOC_DAPM_SWITCH_E(w->name, SND_SOC_NOPM, 0, 0,
+ widget_kctls, gbaudio_widget_event,
+ SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD);
+ break;
+ case snd_soc_dapm_pga:
+ *dw = (struct snd_soc_dapm_widget)
+ SND_SOC_DAPM_PGA_E(w->name, SND_SOC_NOPM, 0, 0, NULL, 0,
+ gbaudio_widget_event,
+ SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD);
+ break;
+ case snd_soc_dapm_mixer:
+ *dw = (struct snd_soc_dapm_widget)
+ SND_SOC_DAPM_MIXER_E(w->name, SND_SOC_NOPM, 0, 0, NULL,
+ 0, gbaudio_widget_event,
+ SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD);
+ break;
+ case snd_soc_dapm_mux:
+ *dw = (struct snd_soc_dapm_widget)
+ SND_SOC_DAPM_MUX_E(w->name, SND_SOC_NOPM, 0, 0,
+ widget_kctls, gbaudio_widget_event,
+ SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD);
+ break;
+ case snd_soc_dapm_aif_in:
+ *dw = (struct snd_soc_dapm_widget)
+ SND_SOC_DAPM_AIF_IN_E(w->name, w->sname, 0,
+ SND_SOC_NOPM,
+ 0, 0, gbaudio_widget_event,
+ SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD);
+ break;
+ case snd_soc_dapm_aif_out:
+ *dw = (struct snd_soc_dapm_widget)
+ SND_SOC_DAPM_AIF_OUT_E(w->name, w->sname, 0,
+ SND_SOC_NOPM,
+ 0, 0, gbaudio_widget_event,
+ SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD);
+ break;
+ default:
+ ret = -EINVAL;
+ goto error;
+ }
+
+ dev_dbg(module->dev, "%s: widget of type %d created\n", dw->name,
+ dw->id);
+ return 0;
+error:
+ list_for_each_entry_safe(control, _control, &module->widget_ctl_list,
+ list) {
+ list_del(&control->list);
+ devm_kfree(module->dev, control);
+ }
+ return ret;
+}
+
+static int gbaudio_tplg_process_kcontrols(struct gbaudio_module_info *module,
+ struct gb_audio_control *controls)
+{
+ int i, csize, ret;
+ struct snd_kcontrol_new *dapm_kctls;
+ struct gb_audio_control *curr;
+ struct gbaudio_control *control, *_control;
+ size_t size;
+ char temp_name[NAME_SIZE];
+
+ size = sizeof(struct snd_kcontrol_new) * module->num_controls;
+ dapm_kctls = devm_kzalloc(module->dev, size, GFP_KERNEL);
+ if (!dapm_kctls)
+ return -ENOMEM;
+
+ curr = controls;
+ for (i = 0; i < module->num_controls; i++) {
+ ret = gbaudio_tplg_create_kcontrol(module, &dapm_kctls[i],
+ curr);
+ if (ret) {
+ dev_err(module->dev, "%s:%d type not supported\n",
+ curr->name, curr->iface);
+ goto error;
+ }
+ control = devm_kzalloc(module->dev, sizeof(struct
+ gbaudio_control),
+ GFP_KERNEL);
+ if (!control) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ control->id = curr->id;
+ /* Prefix dev_id to widget_name */
+ strlcpy(temp_name, curr->name, NAME_SIZE);
+ snprintf(curr->name, NAME_SIZE, "GB %d %s", module->dev_id,
+ temp_name);
+ control->name = curr->name;
+ if (curr->info.type == GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED) {
+ struct gb_audio_enumerated *gbenum =
+ &curr->info.value.enumerated;
+
+ csize = offsetof(struct gb_audio_control, info);
+ csize += offsetof(struct gb_audio_ctl_elem_info, value);
+ csize += offsetof(struct gb_audio_enumerated, names);
+ csize += gbenum->names_length;
+ control->texts = (const char * const *)
+ gb_generate_enum_strings(module, gbenum);
+ control->items = gbenum->items;
+ } else
+ csize = sizeof(struct gb_audio_control);
+
+ list_add(&control->list, &module->ctl_list);
+ dev_dbg(module->dev, "%d:%s created of type %d\n", curr->id,
+ curr->name, curr->info.type);
+ curr = (void *)curr + csize;
+ }
+ module->controls = dapm_kctls;
+
+ return 0;
+error:
+ list_for_each_entry_safe(control, _control, &module->ctl_list,
+ list) {
+ list_del(&control->list);
+ devm_kfree(module->dev, control);
+ }
+ devm_kfree(module->dev, dapm_kctls);
+ return ret;
+}
+
+static int gbaudio_tplg_process_widgets(struct gbaudio_module_info *module,
+ struct gb_audio_widget *widgets)
+{
+ int i, ret, w_size;
+ struct snd_soc_dapm_widget *dapm_widgets;
+ struct gb_audio_widget *curr;
+ struct gbaudio_widget *widget, *_widget;
+ size_t size;
+
+ size = sizeof(struct snd_soc_dapm_widget) * module->num_dapm_widgets;
+ dapm_widgets = devm_kzalloc(module->dev, size, GFP_KERNEL);
+ if (!dapm_widgets)
+ return -ENOMEM;
+
+ curr = widgets;
+ for (i = 0; i < module->num_dapm_widgets; i++) {
+ ret = gbaudio_tplg_create_widget(module, &dapm_widgets[i],
+ curr, &w_size);
+ if (ret) {
+ dev_err(module->dev, "%s:%d type not supported\n",
+ curr->name, curr->type);
+ goto error;
+ }
+ widget = devm_kzalloc(module->dev, sizeof(struct
+ gbaudio_widget),
+ GFP_KERNEL);
+ if (!widget) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ widget->id = curr->id;
+ widget->name = curr->name;
+ list_add(&widget->list, &module->widget_list);
+ curr = (void *)curr + w_size;
+ }
+ module->dapm_widgets = dapm_widgets;
+
+ return 0;
+
+error:
+ list_for_each_entry_safe(widget, _widget, &module->widget_list,
+ list) {
+ list_del(&widget->list);
+ devm_kfree(module->dev, widget);
+ }
+ devm_kfree(module->dev, dapm_widgets);
+ return ret;
+}
+
+static int gbaudio_tplg_process_routes(struct gbaudio_module_info *module,
+ struct gb_audio_route *routes)
+{
+ int i, ret;
+ struct snd_soc_dapm_route *dapm_routes;
+ struct gb_audio_route *curr;
+ size_t size;
+
+ size = sizeof(struct snd_soc_dapm_route) * module->num_dapm_routes;
+ dapm_routes = devm_kzalloc(module->dev, size, GFP_KERNEL);
+ if (!dapm_routes)
+ return -ENOMEM;
+
+ module->dapm_routes = dapm_routes;
+ curr = routes;
+
+ for (i = 0; i < module->num_dapm_routes; i++) {
+ dapm_routes->sink =
+ gbaudio_map_widgetid(module, curr->destination_id);
+ if (!dapm_routes->sink) {
+ dev_err(module->dev, "%d:%d:%d:%d - Invalid sink\n",
+ curr->source_id, curr->destination_id,
+ curr->control_id, curr->index);
+ ret = -EINVAL;
+ goto error;
+ }
+ dapm_routes->source =
+ gbaudio_map_widgetid(module, curr->source_id);
+ if (!dapm_routes->source) {
+ dev_err(module->dev, "%d:%d:%d:%d - Invalid source\n",
+ curr->source_id, curr->destination_id,
+ curr->control_id, curr->index);
+ ret = -EINVAL;
+ goto error;
+ }
+ dapm_routes->control =
+ gbaudio_map_controlid(module,
+ curr->control_id,
+ curr->index);
+ if ((curr->control_id != GBAUDIO_INVALID_ID) &&
+ !dapm_routes->control) {
+ dev_err(module->dev, "%d:%d:%d:%d - Invalid control\n",
+ curr->source_id, curr->destination_id,
+ curr->control_id, curr->index);
+ ret = -EINVAL;
+ goto error;
+ }
+ dev_dbg(module->dev, "Route {%s, %s, %s}\n", dapm_routes->sink,
+ (dapm_routes->control) ? dapm_routes->control:"NULL",
+ dapm_routes->source);
+ dapm_routes++;
+ curr++;
+ }
+
+ return 0;
+
+error:
+ devm_kfree(module->dev, module->dapm_routes);
+ return ret;
+}
+
+static int gbaudio_tplg_process_header(struct gbaudio_module_info *module,
+ struct gb_audio_topology *tplg_data)
+{
+ /* fetch no. of kcontrols, widgets & routes */
+ module->num_controls = tplg_data->num_controls;
+ module->num_dapm_widgets = tplg_data->num_widgets;
+ module->num_dapm_routes = tplg_data->num_routes;
+
+ /* update block offset */
+ module->dai_offset = (unsigned long)&tplg_data->data;
+ module->control_offset = module->dai_offset + tplg_data->size_dais;
+ module->widget_offset = module->control_offset +
+ tplg_data->size_controls;
+ module->route_offset = module->widget_offset +
+ tplg_data->size_widgets;
+
+ dev_dbg(module->dev, "DAI offset is 0x%lx\n", module->dai_offset);
+ dev_dbg(module->dev, "control offset is %lx\n",
+ module->control_offset);
+ dev_dbg(module->dev, "widget offset is %lx\n", module->widget_offset);
+ dev_dbg(module->dev, "route offset is %lx\n", module->route_offset);
+
+ return 0;
+}
+
+int gbaudio_tplg_parse_data(struct gbaudio_module_info *module,
+ struct gb_audio_topology *tplg_data)
+{
+ int ret;
+ struct gb_audio_control *controls;
+ struct gb_audio_widget *widgets;
+ struct gb_audio_route *routes;
+
+ if (!tplg_data)
+ return -EINVAL;
+
+ ret = gbaudio_tplg_process_header(module, tplg_data);
+ if (ret) {
+ dev_err(module->dev, "%d: Error in parsing topology header\n",
+ ret);
+ return ret;
+ }
+
+ /* process control */
+ controls = (struct gb_audio_control *)module->control_offset;
+ ret = gbaudio_tplg_process_kcontrols(module, controls);
+ if (ret) {
+ dev_err(module->dev,
+ "%d: Error in parsing controls data\n", ret);
+ return ret;
+ }
+ dev_dbg(module->dev, "Control parsing finished\n");
+
+ /* process widgets */
+ widgets = (struct gb_audio_widget *)module->widget_offset;
+ ret = gbaudio_tplg_process_widgets(module, widgets);
+ if (ret) {
+ dev_err(module->dev,
+ "%d: Error in parsing widgets data\n", ret);
+ return ret;
+ }
+ dev_dbg(module->dev, "Widget parsing finished\n");
+
+ /* process route */
+ routes = (struct gb_audio_route *)module->route_offset;
+ ret = gbaudio_tplg_process_routes(module, routes);
+ if (ret) {
+ dev_err(module->dev,
+ "%d: Error in parsing routes data\n", ret);
+ return ret;
+ }
+ dev_dbg(module->dev, "Route parsing finished\n");
+
+ /* parse jack capabilities */
+ if (tplg_data->jack_type) {
+ module->jack_mask = tplg_data->jack_type & GBCODEC_JACK_MASK;
+ module->button_mask = tplg_data->jack_type &
+ GBCODEC_JACK_BUTTON_MASK;
+ }
+
+ return ret;
+}
+
+void gbaudio_tplg_release(struct gbaudio_module_info *module)
+{
+ struct gbaudio_control *control, *_control;
+ struct gbaudio_widget *widget, *_widget;
+
+ if (!module->topology)
+ return;
+
+ /* release kcontrols */
+ list_for_each_entry_safe(control, _control, &module->ctl_list,
+ list) {
+ list_del(&control->list);
+ devm_kfree(module->dev, control);
+ }
+ if (module->controls)
+ devm_kfree(module->dev, module->controls);
+
+ /* release widget controls */
+ list_for_each_entry_safe(control, _control, &module->widget_ctl_list,
+ list) {
+ list_del(&control->list);
+ devm_kfree(module->dev, control);
+ }
+
+ /* release widgets */
+ list_for_each_entry_safe(widget, _widget, &module->widget_list,
+ list) {
+ list_del(&widget->list);
+ devm_kfree(module->dev, widget);
+ }
+ if (module->dapm_widgets)
+ devm_kfree(module->dev, module->dapm_widgets);
+
+ /* release routes */
+ if (module->dapm_routes)
+ devm_kfree(module->dev, module->dapm_routes);
+}
OpenPOWER on IntegriCloud