From 6339d2322c47f4b8ebabf9daf0130328ed72648b Mon Sep 17 00:00:00 2001 From: Vaibhav Agarwal Date: Wed, 13 Jan 2016 14:07:51 -0700 Subject: [PATCH] greybus: audio: Add topology parser for GB codec For each GB codec module inserted, DAPM widgets, kcontrols, routes and DAIs can be fetched through greybus in a binary chunk and parsed locally to create & populate DAPM graph for the specific module. It is required by each codec module to populate a minimum set of kcontrols with fixed names to support basic audio usecase. To support advanced features of codec module, the same can be polpulated with existing topology parser. However, to use them for different usecase separate mechanism (may be via MSP) is required to inform userspace about their configuration value & enable/disable sequence. ToDos: Currently, support for enumerated kcontrol/dapm control is hardcoded. Need to add complete logic within the parser. Signed-off-by: Vaibhav Agarwal Signed-off-by: Mark Greer Signed-off-by: Greg Kroah-Hartman --- drivers/staging/greybus/Makefile | 2 +- drivers/staging/greybus/audio_codec.c | 254 ++----- drivers/staging/greybus/audio_codec.h | 31 + drivers/staging/greybus/audio_topology.c | 1114 ++++++++++++++++++++++++++++++ 4 files changed, 1191 insertions(+), 210 deletions(-) create mode 100644 drivers/staging/greybus/audio_topology.c diff --git a/drivers/staging/greybus/Makefile b/drivers/staging/greybus/Makefile index 9364897..b1272cb 100644 --- a/drivers/staging/greybus/Makefile +++ b/drivers/staging/greybus/Makefile @@ -29,7 +29,7 @@ gb-raw-y := raw.o gb-hid-y := hid.o gb-es2-y := es2.o gb-arche-y := arche-platform.o arche-apb-ctrl.o -gb-audio-codec-y := audio_codec.o +gb-audio-codec-y := audio_codec.o audio_topology.o gb-audio-gb-y := audio_gb.o gb-audio-apbridgea-y := audio_apbridgea.o gb-audio-manager-y += audio_manager.o diff --git a/drivers/staging/greybus/audio_codec.c b/drivers/staging/greybus/audio_codec.c index fd94042..239a9b6 100644 --- a/drivers/staging/greybus/audio_codec.c +++ b/drivers/staging/greybus/audio_codec.c @@ -19,124 +19,6 @@ 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) { @@ -180,24 +62,9 @@ static struct snd_soc_dai_ops gbcodec_dai_ops = { .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 */ @@ -261,13 +128,6 @@ static struct snd_soc_codec_driver soc_codec_dev_gbcodec = { .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), }; /* @@ -369,6 +229,9 @@ static struct gbaudio_codec_info *gbaudio_get_codec(struct device *dev, 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; @@ -412,8 +275,9 @@ struct device_driver gb_codec_driver = { 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; @@ -425,8 +289,35 @@ static int gbaudio_codec_probe(struct gb_connection *connection) 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; @@ -435,7 +326,7 @@ static int gbaudio_codec_probe(struct gb_connection *connection) 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 */ @@ -455,8 +346,13 @@ static int gbaudio_codec_probe(struct gb_connection *connection) 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; } @@ -480,6 +376,8 @@ static void gbaudio_codec_remove(struct gb_connection *connection) 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; @@ -509,68 +407,6 @@ static struct gb_protocol gb_audio_mgmt_protocol = { .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; diff --git a/drivers/staging/greybus/audio_codec.h b/drivers/staging/greybus/audio_codec.h index 39bd995..5051e06 100644 --- a/drivers/staging/greybus/audio_codec.h +++ b/drivers/staging/greybus/audio_codec.h @@ -68,6 +68,19 @@ static const u8 gbcodec_reg_defaults[GBCODEC_REG_COUNT] = { GBCODEC_APB2_MUX_REG_DEFAULT, }; +struct gbaudio_widget { + __u8 id; + const char *name; + struct list_head list; +}; + +struct gbaudio_control { + __u8 id; + char *name; + const char * const *texts; + struct list_head list; +}; + struct gbaudio_dai { __le16 data_cport; char name[NAME_SIZE]; @@ -87,6 +100,7 @@ struct gbaudio_codec_info { char vstr[NAME_SIZE]; char pstr[NAME_SIZE]; struct list_head list; + struct gb_audio_topology *topology; char name[NAME_SIZE]; /* soc related data */ @@ -105,6 +119,10 @@ struct gbaudio_codec_info { int num_kcontrols; int num_dapm_widgets; int num_dapm_routes; + unsigned long dai_offset; + unsigned long widget_offset; + unsigned long control_offset; + unsigned long route_offset; struct snd_kcontrol_new *kctls; struct snd_soc_dapm_widget *widgets; struct snd_soc_dapm_route *routes; @@ -112,10 +130,23 @@ struct gbaudio_codec_info { /* lists */ struct list_head dai_list; + struct list_head widget_list; + struct list_head codec_ctl_list; + struct list_head widget_ctl_list; struct mutex lock; }; +struct gbaudio_dai *gbaudio_add_dai(struct gbaudio_codec_info *gbcodec, + int data_cport, + struct gb_connection *connection, + const char *name); +int gbaudio_tplg_parse_data(struct gbaudio_codec_info *gbcodec, + struct gb_audio_topology *tplg_data); +void gbaudio_tplg_release(struct gbaudio_codec_info *gbcodec); + /* protocol related */ +extern int gb_audio_gb_get_topology(struct gb_connection *connection, + struct gb_audio_topology **topology); extern int gb_audio_gb_get_control(struct gb_connection *connection, uint8_t control_id, uint8_t index, struct gb_audio_ctl_elem_value *value); diff --git a/drivers/staging/greybus/audio_topology.c b/drivers/staging/greybus/audio_topology.c new file mode 100644 index 0000000..8840a9c --- /dev/null +++ b/drivers/staging/greybus/audio_topology.c @@ -0,0 +1,1114 @@ +/* + * 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); +} -- 2.7.4