static DEFINE_MUTEX(gb_codec_list_lock);
static LIST_HEAD(gb_codec_list);
-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 const struct snd_kcontrol_new gbcodec_snd_controls[] = {
- SOC_DOUBLE("Playback Mute", GBCODEC_MUTE_REG, 0, 1, 1, 1),
- SOC_DOUBLE("Capture Mute", GBCODEC_MUTE_REG, 4, 5, 1, 1),
- SOC_DOUBLE_R("Playback Volume", GBCODEC_PB_LVOL_REG,
- GBCODEC_PB_RVOL_REG, 0, 127, 0),
- SOC_DOUBLE_R("Capture Volume", GBCODEC_CAP_LVOL_REG,
- GBCODEC_CAP_RVOL_REG, 0, 127, 0),
-};
-
-static const struct snd_kcontrol_new spk_amp_ctl =
- SOC_DAPM_SINGLE("Switch", GBCODEC_CTL_REG, 0, 1, 0);
-
-static const struct snd_kcontrol_new hp_amp_ctl =
- SOC_DAPM_SINGLE("Switch", GBCODEC_CTL_REG, 1, 1, 0);
-
-static const struct snd_kcontrol_new mic_adc_ctl =
- SOC_DAPM_SINGLE("Switch", GBCODEC_CTL_REG, 4, 1, 0);
-
-/* APB1-GBSPK source */
-static const char * const gbcodec_apb1_src[] = {"Stereo", "Left", "Right"};
-
-static const SOC_ENUM_SINGLE_DECL(
- gbcodec_apb1_rx_enum, GBCODEC_APB1_MUX_REG, 0, gbcodec_apb1_src);
-
-static const struct snd_kcontrol_new gbcodec_apb1_rx_mux =
- SOC_DAPM_ENUM("APB1 source", gbcodec_apb1_rx_enum);
-
-static const SOC_ENUM_SINGLE_DECL(
- gbcodec_mic_enum, GBCODEC_APB1_MUX_REG, 4, gbcodec_apb1_src);
-
-static const struct snd_kcontrol_new gbcodec_mic_mux =
- SOC_DAPM_ENUM("MIC source", gbcodec_mic_enum);
-
-static const struct snd_soc_dapm_widget gbcodec_dapm_widgets[] = {
- SND_SOC_DAPM_SPK("Spk", gbcodec_event_spk),
- SND_SOC_DAPM_SPK("HP", gbcodec_event_hp),
- SND_SOC_DAPM_MIC("Int Mic", gbcodec_event_int_mic),
-
- SND_SOC_DAPM_OUTPUT("SPKOUT"),
- SND_SOC_DAPM_OUTPUT("HPOUT"),
-
- SND_SOC_DAPM_INPUT("MIC"),
- SND_SOC_DAPM_INPUT("HSMIC"),
-
- SND_SOC_DAPM_SWITCH("SPK Amp", SND_SOC_NOPM, 0, 0, &spk_amp_ctl),
- SND_SOC_DAPM_SWITCH("HP Amp", SND_SOC_NOPM, 0, 0, &hp_amp_ctl),
- SND_SOC_DAPM_SWITCH("MIC ADC", SND_SOC_NOPM, 0, 0, &mic_adc_ctl),
-
- SND_SOC_DAPM_PGA("SPK DAC", SND_SOC_NOPM, 0, 0, NULL, 0),
- SND_SOC_DAPM_PGA("HP DAC", SND_SOC_NOPM, 0, 0, NULL, 0),
-
- SND_SOC_DAPM_MIXER("SPK Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
- SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
- SND_SOC_DAPM_MIXER("APB1_TX Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
-
- SND_SOC_DAPM_MUX("APB1_RX Mux", SND_SOC_NOPM, 0, 0,
- &gbcodec_apb1_rx_mux),
- SND_SOC_DAPM_MUX("MIC Mux", SND_SOC_NOPM, 0, 0, &gbcodec_mic_mux),
-
- SND_SOC_DAPM_AIF_IN("APB1RX", "APBridgeA1 Playback", 0, SND_SOC_NOPM, 0,
- 0),
- SND_SOC_DAPM_AIF_OUT("APB1TX", "APBridgeA1 Capture", 0, SND_SOC_NOPM, 0,
- 0),
-};
-
-static const struct snd_soc_dapm_route gbcodec_dapm_routes[] = {
- /* Playback path */
- {"Spk", NULL, "SPKOUT"},
- {"SPKOUT", NULL, "SPK Amp"},
- {"SPK Amp", "Switch", "SPK DAC"},
- {"SPK DAC", NULL, "SPK Mixer"},
-
- {"HP", NULL, "HPOUT"},
- {"HPOUT", NULL, "HP Amp"},
- {"HP Amp", "Switch", "HP DAC"},
- {"HP DAC", NULL, "HP Mixer"},
-
- {"SPK Mixer", NULL, "APB1_RX Mux"},
- {"HP Mixer", NULL, "APB1_RX Mux"},
-
- {"APB1_RX Mux", "Left", "APB1RX"},
- {"APB1_RX Mux", "Right", "APB1RX"},
- {"APB1_RX Mux", "Stereo", "APB1RX"},
-
- /* Capture path */
- {"MIC", NULL, "Int Mic"},
- {"MIC", NULL, "MIC Mux"},
- {"MIC Mux", "Left", "MIC ADC"},
- {"MIC Mux", "Right", "MIC ADC"},
- {"MIC Mux", "Stereo", "MIC ADC"},
- {"MIC ADC", "Switch", "APB1_TX Mixer"},
- {"APB1_TX Mixer", NULL, "APB1TX"}
-};
-
static int gbcodec_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
.digital_mute = gbcodec_digital_mute,
};
-static struct snd_soc_dai_driver gbcodec_dai = {
- .playback = {
- .stream_name = "APBridgeA1 Playback",
- .channels_min = 1,
- .channels_max = 2,
- .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
- .capture = {
- .stream_name = "APBridgeA1 Capture",
- .channels_min = 2,
- .channels_max = 2,
- .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
- .ops = &gbcodec_dai_ops,
-};
-
+/*
+ * codec driver ops
+ */
static int gbcodec_probe(struct snd_soc_codec *codec)
{
/* Empty function for now */
.reg_word_size = 1,
.idle_bias_off = true,
-
- .controls = gbcodec_snd_controls,
- .num_controls = ARRAY_SIZE(gbcodec_snd_controls),
- .dapm_widgets = gbcodec_dapm_widgets,
- .num_dapm_widgets = ARRAY_SIZE(gbcodec_dapm_widgets),
- .dapm_routes = gbcodec_dapm_routes,
- .num_dapm_routes = ARRAY_SIZE(gbcodec_dapm_routes),
};
/*
mutex_init(&gbcodec->lock);
INIT_LIST_HEAD(&gbcodec->dai_list);
+ INIT_LIST_HEAD(&gbcodec->widget_list);
+ INIT_LIST_HEAD(&gbcodec->codec_ctl_list);
+ INIT_LIST_HEAD(&gbcodec->widget_ctl_list);
gbcodec->dev_id = dev_id;
dev_set_drvdata(dev, gbcodec);
gbcodec->dev = dev;
static int gbaudio_codec_probe(struct gb_connection *connection)
{
- int ret;
+ int ret, i;
struct gbaudio_codec_info *gbcodec;
+ struct gb_audio_topology *topology;
struct device *dev = &connection->bundle->dev;
int dev_id = connection->bundle->id;
gbcodec->mgmt_connection = connection;
+ /* fetch topology data */
+ ret = gb_audio_gb_get_topology(connection, &topology);
+ if (ret) {
+ dev_err(gbcodec->dev,
+ "%d:Error while fetching topology\n", ret);
+ goto base_error;
+ }
+
+ /* process topology data */
+ ret = gbaudio_tplg_parse_data(gbcodec, topology);
+ if (ret) {
+ dev_err(dev, "%d:Error while parsing topology data\n",
+ ret);
+ goto topology_error;
+ }
+ gbcodec->topology = topology;
+
+ /* update codec info */
+ soc_codec_dev_gbcodec.controls = gbcodec->kctls;
+ soc_codec_dev_gbcodec.num_controls = gbcodec->num_kcontrols;
+ soc_codec_dev_gbcodec.dapm_widgets = gbcodec->widgets;
+ soc_codec_dev_gbcodec.num_dapm_widgets = gbcodec->num_dapm_widgets;
+ soc_codec_dev_gbcodec.dapm_routes = gbcodec->routes;
+ soc_codec_dev_gbcodec.num_dapm_routes = gbcodec->num_dapm_routes;
+
/* update DAI info */
- gbcodec->dais = &gbcodec_dai;
+ for (i = 0; i < gbcodec->num_dais; i++)
+ gbcodec->dais[i].ops = &gbcodec_dai_ops;
+
/* FIXME */
dev->driver = &gb_codec_driver;
gbcodec->dais, 1);
if (ret) {
dev_err(dev, "%d:Failed to register codec\n", ret);
- goto base_error;
+ goto parse_error;
}
/* update DAI links in response to this codec */
codec_reg_error:
snd_soc_unregister_codec(dev);
-base_error:
dev->driver = NULL;
+parse_error:
+ gbaudio_tplg_release(gbcodec);
+ gbcodec->topology = NULL;
+topology_error:
+ kfree(topology);
+base_error:
gbcodec->mgmt_connection = NULL;
return ret;
}
snd_soc_unregister_codec(dev);
dev->driver = NULL;
+ gbaudio_tplg_release(gbcodec);
+ kfree(gbcodec->topology);
gbcodec->mgmt_connection = NULL;
mutex_lock(&gbcodec->lock);
gbcodec->codec_registered = 0;
.request_recv = gbaudio_codec_report_event_recv,
};
-static struct gbaudio_dai *gbaudio_allocate_dai(struct gbaudio_codec_info *gb,
- int data_cport,
- struct gb_connection *connection,
- const char *name)
-{
- struct gbaudio_dai *dai;
-
- mutex_lock(&gb->lock);
- dai = devm_kzalloc(gb->dev, sizeof(*dai), GFP_KERNEL);
- if (!dai) {
- dev_err(gb->dev, "%s:DAI Malloc failure\n", name);
- mutex_unlock(&gb->lock);
- return NULL;
- }
-
- dai->data_cport = data_cport;
- dai->connection = connection;
-
- /* update name */
- if (name)
- strlcpy(dai->name, name, NAME_SIZE);
- list_add(&dai->list, &gb->dai_list);
- dev_dbg(gb->dev, "%d:%s: DAI added\n", data_cport, dai->name);
- mutex_unlock(&gb->lock);
-
- return dai;
-}
-
-struct gbaudio_dai *gbaudio_add_dai(struct gbaudio_codec_info *gbcodec,
- int data_cport,
- struct gb_connection *connection,
- const char *name)
-{
- struct gbaudio_dai *dai, *_dai;
-
- /* FIXME need to take care for multiple DAIs */
- mutex_lock(&gbcodec->lock);
- if (list_empty(&gbcodec->dai_list)) {
- mutex_unlock(&gbcodec->lock);
- return gbaudio_allocate_dai(gbcodec, data_cport, connection,
- name);
- }
-
- list_for_each_entry_safe(dai, _dai, &gbcodec->dai_list, list) {
- if (dai->data_cport == data_cport) {
- if (connection)
- dai->connection = connection;
-
- if (name)
- strlcpy(dai->name, name, NAME_SIZE);
- dev_dbg(gbcodec->dev, "%d:%s: DAI updated\n",
- data_cport, dai->name);
- mutex_unlock(&gbcodec->lock);
- return dai;
- }
- }
-
- dev_err(gbcodec->dev, "%s:DAI not found\n", name);
- mutex_unlock(&gbcodec->lock);
- return NULL;
-}
-
static int gbaudio_dai_probe(struct gb_connection *connection)
{
struct gbaudio_dai *dai;
--- /dev/null
+/*
+ * 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 const char *gbaudio_map_controlid(struct gbaudio_codec_info *gbcodec,
+ __u8 control_id, __u8 index)
+{
+ struct gbaudio_control *control;
+
+ if (control_id == GBAUDIO_INVALID_ID)
+ return NULL;
+
+ list_for_each_entry(control, &gbcodec->codec_ctl_list, list) {
+ if (control->id == control_id) {
+ if (index == GBAUDIO_INVALID_ID)
+ return control->name;
+ return control->texts[index];
+ }
+ }
+ list_for_each_entry(control, &gbcodec->widget_ctl_list, list) {
+ if (control->id == control_id) {
+ if (index == GBAUDIO_INVALID_ID)
+ return control->name;
+ return control->texts[index];
+ }
+ }
+ return NULL;
+}
+
+static int gbaudio_map_widgetname(struct gbaudio_codec_info *gbcodec,
+ const char *name)
+{
+ struct gbaudio_widget *widget;
+ char widget_name[NAME_SIZE];
+ char prefix_name[NAME_SIZE];
+
+ snprintf(prefix_name, NAME_SIZE, "GB %d ", gbcodec->dev_id);
+ if (strncmp(name, prefix_name, strlen(prefix_name)))
+ return -EINVAL;
+
+ strlcpy(widget_name, name+strlen(prefix_name), NAME_SIZE);
+ dev_dbg(gbcodec->dev, "widget_name:%s, truncated widget_name:%s\n",
+ name, widget_name);
+
+ list_for_each_entry(widget, &gbcodec->widget_list, list) {
+ if (!strncmp(widget->name, widget_name, NAME_SIZE))
+ return widget->id;
+ }
+ return -EINVAL;
+}
+
+static const char *gbaudio_map_widgetid(struct gbaudio_codec_info *gbcodec,
+ __u8 widget_id)
+{
+ struct gbaudio_widget *widget;
+
+ list_for_each_entry(widget, &gbcodec->widget_list, list) {
+ if (widget->id == widget_id)
+ return widget->name;
+ }
+ return NULL;
+}
+
+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 snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct gbaudio_codec_info *gbcodec = snd_soc_codec_get_drvdata(codec);
+
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ info = (struct gb_audio_ctl_elem_info *)data->info;
+
+ if (!info) {
+ dev_err(gbcodec->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;
+ name = gbaudio_map_controlid(gbcodec, 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 snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ info = (struct gb_audio_ctl_elem_info *)data->info;
+
+ ret = gb_audio_gb_get_control(gb->mgmt_connection, data->ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+ if (ret) {
+ dev_err(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 snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ info = (struct gb_audio_ctl_elem_info *)data->info;
+
+ /* 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_audio_gb_set_control(gb->mgmt_connection, data->ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+ if (ret) {
+ dev_err(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;
+
+ 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 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);
+
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ info = (struct gb_audio_ctl_elem_info *)data->info;
+
+ if (data->vcount == 2)
+ dev_warn(widget->dapm->dev,
+ "GB: Control '%s' is stereo, which is not supported\n",
+ kcontrol->id.name);
+
+ ret = gb_audio_gb_get_control(gb->mgmt_connection, data->ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+ if (ret) {
+ dev_err(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 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);
+
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ info = (struct gb_audio_ctl_elem_info *)data->info;
+
+ 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_audio_gb_set_control(gb->mgmt_connection,
+ data->ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+ if (ret) {
+ dev_err(codec->dev,
+ "%d:Error in %s for %s\n", ret, __func__,
+ kcontrol->id.name);
+ }
+ }
+
+ return ret;
+}
+
+#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 gbaudio_tplg_create_kcontrol(struct gbaudio_codec_info *gb,
+ struct snd_kcontrol_new *kctl,
+ struct gb_audio_control *ctl)
+{
+ struct gbaudio_ctl_pvt *ctldata;
+
+ switch (ctl->iface) {
+ case SNDRV_CTL_ELEM_IFACE_MIXER:
+ 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;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(gb->dev, "%s:%d control created\n", ctl->name, ctl->id);
+ return 0;
+}
+
+static const char * const gbtexts[] = {"Stereo", "Left", "Right"};
+
+static const SOC_ENUM_SINGLE_DECL(
+ gbcodec_apb1_rx_enum, GBCODEC_APB1_MUX_REG, 0, gbtexts);
+
+static const SOC_ENUM_SINGLE_DECL(
+ gbcodec_mic_enum, GBCODEC_APB1_MUX_REG, 4, gbtexts);
+
+static int gbaudio_tplg_create_enum_ctl(struct gbaudio_codec_info *gb,
+ struct snd_kcontrol_new *kctl,
+ struct gb_audio_control *ctl)
+{
+ switch (ctl->id) {
+ case 8:
+ *kctl = (struct snd_kcontrol_new)
+ SOC_DAPM_ENUM(ctl->name, gbcodec_apb1_rx_enum);
+ break;
+ case 9:
+ *kctl = (struct snd_kcontrol_new)
+ SOC_DAPM_ENUM(ctl->name, gbcodec_mic_enum);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int gbaudio_tplg_create_mixer_ctl(struct gbaudio_codec_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_codec_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);
+
+ dev_dbg(codec->dev, "%s %s %d\n", __func__, w->name, event);
+
+ /* map name to widget id */
+ wid = gbaudio_map_widgetname(gbcodec, w->name);
+ if (wid < 0) {
+ dev_err(codec->dev, "Invalid widget name:%s\n", w->name);
+ return -EINVAL;
+ }
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ ret = gb_audio_gb_enable_widget(gbcodec->mgmt_connection, wid);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ ret = gb_audio_gb_disable_widget(gbcodec->mgmt_connection, wid);
+ break;
+ }
+ if (ret)
+ dev_err(codec->dev, "%d: widget, event:%d failed:%d\n", wid,
+ event, ret);
+ return ret;
+}
+
+static int gbaudio_tplg_create_widget(struct gbaudio_codec_info *gbcodec,
+ struct snd_soc_dapm_widget *dw,
+ struct gb_audio_widget *w)
+{
+ int i, ret;
+ struct snd_kcontrol_new *widget_kctls;
+ struct gb_audio_control *curr;
+ struct gbaudio_control *control, *_control;
+ size_t size;
+
+ ret = gbaudio_validate_kcontrol_count(w);
+ if (ret) {
+ dev_err(gbcodec->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(gbcodec->dev, size, GFP_KERNEL);
+ if (!widget_kctls)
+ return -ENOMEM;
+ }
+
+ /* create relevant kcontrols */
+ for (i = 0; i < w->ncontrols; i++) {
+ curr = &w->ctl[i];
+ ret = gbaudio_tplg_create_wcontrol(gbcodec, &widget_kctls[i],
+ curr);
+ if (ret) {
+ dev_err(gbcodec->dev,
+ "%s:%d type widget_ctl not supported\n",
+ curr->name, curr->iface);
+ goto error;
+ }
+ control = devm_kzalloc(gbcodec->dev,
+ sizeof(struct gbaudio_control),
+ GFP_KERNEL);
+ if (!control) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ control->id = curr->id;
+ control->name = curr->name;
+ if (curr->info.type == GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED)
+ control->texts = (const char * const *)
+ curr->info.value.enumerated.names;
+ list_add(&control->list, &gbcodec->widget_ctl_list);
+ dev_dbg(gbcodec->dev, "%s: control of type %d created\n",
+ widget_kctls[i].name, widget_kctls[i].iface);
+ }
+
+ switch (w->type) {
+ case snd_soc_dapm_spk:
+ *dw = (struct snd_soc_dapm_widget)
+ SND_SOC_DAPM_SPK(w->name, gbcodec_event_spk);
+ break;
+ case snd_soc_dapm_hp:
+ *dw = (struct snd_soc_dapm_widget)
+ SND_SOC_DAPM_HP(w->name, gbcodec_event_hp);
+ break;
+ case snd_soc_dapm_mic:
+ *dw = (struct snd_soc_dapm_widget)
+ SND_SOC_DAPM_MIC(w->name, gbcodec_event_int_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(gbcodec->dev, "%s: widget of type %d created\n", dw->name,
+ dw->id);
+ return 0;
+error:
+ list_for_each_entry_safe(control, _control, &gbcodec->widget_ctl_list,
+ list) {
+ list_del(&control->list);
+ devm_kfree(gbcodec->dev, control);
+ }
+ return ret;
+}
+
+static int gbaudio_tplg_create_dai(struct gbaudio_codec_info *gbcodec,
+ struct snd_soc_dai_driver *gb_dai,
+ struct gb_audio_dai *dai)
+{
+ /*
+ * do not update name here,
+ * append dev_id before assigning it here
+ */
+
+ gb_dai->playback.stream_name = dai->playback.stream_name;
+ gb_dai->playback.channels_min = dai->playback.chan_min;
+ gb_dai->playback.channels_max = dai->playback.chan_max;
+ gb_dai->playback.formats = dai->playback.formats;
+ gb_dai->playback.rates = dai->playback.rates;
+ gb_dai->playback.sig_bits = dai->playback.sig_bits;
+
+ gb_dai->capture.stream_name = dai->capture.stream_name;
+ gb_dai->capture.channels_min = dai->capture.chan_min;
+ gb_dai->capture.channels_max = dai->capture.chan_max;
+ gb_dai->capture.formats = dai->capture.formats;
+ gb_dai->capture.rates = dai->capture.rates;
+ gb_dai->capture.sig_bits = dai->capture.sig_bits;
+
+ return 0;
+}
+
+static int gbaudio_tplg_process_kcontrols(struct gbaudio_codec_info *gbcodec,
+ struct gb_audio_control *controls)
+{
+ int i, ret;
+ struct snd_kcontrol_new *dapm_kctls;
+ struct gb_audio_control *curr;
+ struct gbaudio_control *control, *_control;
+ size_t size;
+
+ size = sizeof(struct snd_kcontrol_new) * gbcodec->num_kcontrols;
+ dapm_kctls = devm_kzalloc(gbcodec->dev, size, GFP_KERNEL);
+ if (!dapm_kctls)
+ return -ENOMEM;
+
+ curr = controls;
+ for (i = 0; i < gbcodec->num_kcontrols; i++) {
+ ret = gbaudio_tplg_create_kcontrol(gbcodec, &dapm_kctls[i],
+ curr);
+ if (ret) {
+ dev_err(gbcodec->dev, "%s:%d type not supported\n",
+ curr->name, curr->iface);
+ goto error;
+ }
+ control = devm_kzalloc(gbcodec->dev, sizeof(struct
+ gbaudio_control),
+ GFP_KERNEL);
+ if (!control) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ control->id = curr->id;
+ control->name = curr->name;
+ if (curr->info.type == GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED)
+ control->texts = (const char * const *)
+ curr->info.value.enumerated.names;
+ list_add(&control->list, &gbcodec->codec_ctl_list);
+ dev_dbg(gbcodec->dev, "%d:%s created of type %d\n", curr->id,
+ curr->name, curr->info.type);
+ curr++;
+ }
+ gbcodec->kctls = dapm_kctls;
+
+ return 0;
+error:
+ list_for_each_entry_safe(control, _control, &gbcodec->codec_ctl_list,
+ list) {
+ list_del(&control->list);
+ devm_kfree(gbcodec->dev, control);
+ }
+ devm_kfree(gbcodec->dev, dapm_kctls);
+ return ret;
+}
+
+static int gbaudio_tplg_process_widgets(struct gbaudio_codec_info *gbcodec,
+ struct gb_audio_widget *widgets)
+{
+ int i, ret, ncontrols;
+ 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) * gbcodec->num_dapm_widgets;
+ dapm_widgets = devm_kzalloc(gbcodec->dev, size, GFP_KERNEL);
+ if (!dapm_widgets)
+ return -ENOMEM;
+
+ curr = widgets;
+ for (i = 0; i < gbcodec->num_dapm_widgets; i++) {
+ ret = gbaudio_tplg_create_widget(gbcodec, &dapm_widgets[i],
+ curr);
+ if (ret) {
+ dev_err(gbcodec->dev, "%s:%d type not supported\n",
+ curr->name, curr->type);
+ goto error;
+ }
+ widget = devm_kzalloc(gbcodec->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, &gbcodec->widget_list);
+ ncontrols = curr->ncontrols;
+ curr++;
+ curr += ncontrols * sizeof(struct gb_audio_control);
+ }
+ gbcodec->widgets = dapm_widgets;
+
+ return 0;
+
+error:
+ list_for_each_entry_safe(widget, _widget, &gbcodec->widget_list,
+ list) {
+ list_del(&widget->list);
+ devm_kfree(gbcodec->dev, widget);
+ }
+ devm_kfree(gbcodec->dev, dapm_widgets);
+ return ret;
+}
+
+static int gbaudio_tplg_process_dais(struct gbaudio_codec_info *gbcodec,
+ struct gb_audio_dai *dais)
+{
+ int i, ret;
+ struct snd_soc_dai_driver *gb_dais;
+ struct gb_audio_dai *curr;
+ struct gbaudio_dai *dai, *_dai;
+ size_t size;
+ char dai_name[NAME_SIZE];
+
+ size = sizeof(struct snd_soc_dai_driver) * gbcodec->num_dais;
+ gb_dais = devm_kzalloc(gbcodec->dev, size, GFP_KERNEL);
+ if (!gb_dais)
+ return -ENOMEM;
+
+ curr = dais;
+ for (i = 0; i < gbcodec->num_dais; i++) {
+ ret = gbaudio_tplg_create_dai(gbcodec, &gb_dais[i], curr);
+ if (ret) {
+ dev_err(gbcodec->dev, "%s failed to create\n",
+ curr->name);
+ goto error;
+ }
+ /* append dev_id to dai_name */
+ snprintf(dai_name, NAME_SIZE, "%s.%d", curr->name,
+ gbcodec->dev_id);
+ dai = gbaudio_add_dai(gbcodec, curr->data_cport, NULL,
+ dai_name);
+ if (!dai)
+ goto error;
+ dev_err(gbcodec->dev, "%s:DAI added\n", dai->name);
+ gb_dais[i].name = dai->name;
+ curr++;
+ }
+ gbcodec->dais = gb_dais;
+
+ return 0;
+
+error:
+ list_for_each_entry_safe(dai, _dai, &gbcodec->dai_list, list) {
+ list_del(&dai->list);
+ devm_kfree(gbcodec->dev, dai);
+ }
+ devm_kfree(gbcodec->dev, gb_dais);
+ return ret;
+}
+
+static int gbaudio_tplg_process_routes(struct gbaudio_codec_info *gbcodec,
+ 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) * gbcodec->num_dapm_routes;
+ dapm_routes = devm_kzalloc(gbcodec->dev, size, GFP_KERNEL);
+ if (!dapm_routes)
+ return -ENOMEM;
+
+
+ gbcodec->routes = dapm_routes;
+ curr = routes;
+
+ for (i = 0; i < gbcodec->num_dapm_routes; i++) {
+ dapm_routes->sink =
+ gbaudio_map_widgetid(gbcodec, curr->destination_id);
+ if (!dapm_routes->sink) {
+ dev_err(gbcodec->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(gbcodec, curr->source_id);
+ if (!dapm_routes->source) {
+ dev_err(gbcodec->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(gbcodec,
+ curr->control_id,
+ curr->index);
+ if ((curr->control_id != GBAUDIO_INVALID_ID) &&
+ !dapm_routes->control) {
+ dev_err(gbcodec->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(gbcodec->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(gbcodec->dev, dapm_routes);
+ return ret;
+}
+
+static int gbaudio_tplg_process_header(struct gbaudio_codec_info *gbcodec,
+ struct gb_audio_topology *tplg_data)
+{
+ /* fetch no. of kcontrols, widgets & routes */
+ gbcodec->num_dais = tplg_data->num_dais;
+ gbcodec->num_kcontrols = tplg_data->num_controls;
+ gbcodec->num_dapm_widgets = tplg_data->num_widgets;
+ gbcodec->num_dapm_routes = tplg_data->num_routes;
+
+ /* update block offset */
+ gbcodec->dai_offset = (unsigned long)&tplg_data->data;
+ gbcodec->control_offset = gbcodec->dai_offset + tplg_data->size_dais;
+ gbcodec->widget_offset = gbcodec->control_offset +
+ tplg_data->size_controls;
+ gbcodec->route_offset = gbcodec->widget_offset +
+ tplg_data->size_widgets;
+
+ dev_dbg(gbcodec->dev, "DAI offset is 0x%lx\n", gbcodec->dai_offset);
+ dev_dbg(gbcodec->dev, "control offset is %lx\n",
+ gbcodec->control_offset);
+ dev_dbg(gbcodec->dev, "widget offset is %lx\n", gbcodec->widget_offset);
+ dev_dbg(gbcodec->dev, "route offset is %lx\n", gbcodec->route_offset);
+
+ return 0;
+}
+
+static struct gbaudio_dai *gbaudio_allocate_dai(struct gbaudio_codec_info *gb,
+ int data_cport,
+ struct gb_connection *connection,
+ const char *name)
+{
+ struct gbaudio_dai *dai;
+
+ mutex_lock(&gb->lock);
+ dai = devm_kzalloc(gb->dev, sizeof(*dai), GFP_KERNEL);
+ if (!dai) {
+ dev_err(gb->dev, "%s:DAI Malloc failure\n", name);
+ mutex_unlock(&gb->lock);
+ return NULL;
+ }
+
+ dai->data_cport = data_cport;
+ dai->connection = connection;
+
+ /* update name */
+ if (name)
+ strlcpy(dai->name, name, NAME_SIZE);
+ list_add(&dai->list, &gb->dai_list);
+ dev_dbg(gb->dev, "%d:%s: DAI added\n", data_cport, dai->name);
+ mutex_unlock(&gb->lock);
+
+ return dai;
+}
+
+struct gbaudio_dai *gbaudio_add_dai(struct gbaudio_codec_info *gbcodec,
+ int data_cport,
+ struct gb_connection *connection,
+ const char *name)
+{
+ struct gbaudio_dai *dai, *_dai;
+
+ /* FIXME need to take care for multiple DAIs */
+ mutex_lock(&gbcodec->lock);
+ if (list_empty(&gbcodec->dai_list)) {
+ mutex_unlock(&gbcodec->lock);
+ return gbaudio_allocate_dai(gbcodec, data_cport, connection,
+ name);
+ }
+
+ list_for_each_entry_safe(dai, _dai, &gbcodec->dai_list, list) {
+ if (dai->data_cport == data_cport) {
+ if (connection)
+ dai->connection = connection;
+
+ if (name)
+ strlcpy(dai->name, name, NAME_SIZE);
+ dev_dbg(gbcodec->dev, "%d:%s: DAI updated\n",
+ data_cport, dai->name);
+ mutex_unlock(&gbcodec->lock);
+ return dai;
+ }
+ }
+
+ dev_err(gbcodec->dev, "%s:DAI not found\n", name);
+ mutex_unlock(&gbcodec->lock);
+ return NULL;
+}
+
+int gbaudio_tplg_parse_data(struct gbaudio_codec_info *gbcodec,
+ struct gb_audio_topology *tplg_data)
+{
+ int ret;
+ struct gb_audio_dai *dais;
+ 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(gbcodec, tplg_data);
+ if (ret) {
+ dev_err(gbcodec->dev, "%d: Error in parsing topology header\n",
+ ret);
+ return ret;
+ }
+
+ /* process control */
+ controls = (struct gb_audio_control *)gbcodec->control_offset;
+ ret = gbaudio_tplg_process_kcontrols(gbcodec, controls);
+ if (ret) {
+ dev_err(gbcodec->dev,
+ "%d: Error in parsing controls data\n", ret);
+ return ret;
+ }
+ dev_err(gbcodec->dev, "Control parsing finished\n");
+
+ /* process DAI */
+ dais = (struct gb_audio_dai *)gbcodec->dai_offset;
+ ret = gbaudio_tplg_process_dais(gbcodec, dais);
+ if (ret) {
+ dev_err(gbcodec->dev,
+ "%d: Error in parsing DAIs data\n", ret);
+ return ret;
+ }
+ dev_err(gbcodec->dev, "DAI parsing finished\n");
+
+ /* process widgets */
+ widgets = (struct gb_audio_widget *)gbcodec->widget_offset;
+ ret = gbaudio_tplg_process_widgets(gbcodec, widgets);
+ if (ret) {
+ dev_err(gbcodec->dev,
+ "%d: Error in parsing widgets data\n", ret);
+ return ret;
+ }
+ dev_err(gbcodec->dev, "Widget parsing finished\n");
+
+ /* process route */
+ routes = (struct gb_audio_route *)gbcodec->route_offset;
+ ret = gbaudio_tplg_process_routes(gbcodec, routes);
+ if (ret) {
+ dev_err(gbcodec->dev,
+ "%d: Error in parsing routes data\n", ret);
+ return ret;
+ }
+ dev_err(gbcodec->dev, "Route parsing finished\n");
+
+ return ret;
+}
+
+void gbaudio_tplg_release(struct gbaudio_codec_info *gbcodec)
+{
+ struct gbaudio_dai *dai, *_dai;
+ struct gbaudio_control *control, *_control;
+ struct gbaudio_widget *widget, *_widget;
+
+ if (!gbcodec->topology)
+ return;
+
+ /* release kcontrols */
+ list_for_each_entry_safe(control, _control, &gbcodec->codec_ctl_list,
+ list) {
+ list_del(&control->list);
+ devm_kfree(gbcodec->dev, control);
+ }
+ if (gbcodec->kctls)
+ devm_kfree(gbcodec->dev, gbcodec->kctls);
+
+ /* release widget controls */
+ list_for_each_entry_safe(control, _control, &gbcodec->widget_ctl_list,
+ list) {
+ list_del(&control->list);
+ devm_kfree(gbcodec->dev, control);
+ }
+
+ /* release widgets */
+ list_for_each_entry_safe(widget, _widget, &gbcodec->widget_list,
+ list) {
+ list_del(&widget->list);
+ devm_kfree(gbcodec->dev, widget);
+ }
+ if (gbcodec->widgets)
+ devm_kfree(gbcodec->dev, gbcodec->widgets);
+
+ /* release routes */
+ if (gbcodec->routes)
+ devm_kfree(gbcodec->dev, gbcodec->routes);
+
+ /* release DAIs */
+ mutex_lock(&gbcodec->lock);
+ list_for_each_entry_safe(dai, _dai, &gbcodec->dai_list, list) {
+ list_del(&dai->list);
+ devm_kfree(gbcodec->dev, dai);
+ }
+ mutex_unlock(&gbcodec->lock);
+}