diff --git a/drivers/staging/greybus/Makefile b/drivers/staging/greybus/Makefile index 936489758533..b1272cbd55ee 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 fd94042d3f33..239a9b6b947c 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 39bd995719c2..5051e06dfff6 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 000000000000..8840a9c330de --- /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); +}