| /* |
| * Greybus audio driver |
| * Copyright 2015-2016 Google Inc. |
| * Copyright 2015-2016 Linaro Ltd. |
| * |
| * Released under the GPLv2 only. |
| */ |
| |
| #include "audio_codec.h" |
| #include "greybus_protocols.h" |
| |
| #define GBAUDIO_INVALID_ID 0xFF |
| |
| /* mixer control */ |
| struct gb_mixer_control { |
| int min, max; |
| unsigned int reg, rreg, shift, rshift, invert; |
| }; |
| |
| struct gbaudio_ctl_pvt { |
| unsigned int ctl_id; |
| unsigned int data_cport; |
| unsigned int access; |
| unsigned int vcount; |
| struct gb_audio_ctl_elem_info *info; |
| }; |
| |
| static struct gbaudio_module_info *find_gb_module( |
| struct gbaudio_codec_info *codec, |
| char const *name) |
| { |
| int dev_id, ret; |
| char begin[NAME_SIZE]; |
| struct gbaudio_module_info *module; |
| |
| if (!name) |
| return NULL; |
| |
| ret = sscanf(name, "%s %d", begin, &dev_id); |
| dev_dbg(codec->dev, "%s:Find module#%d\n", __func__, dev_id); |
| |
| mutex_lock(&codec->lock); |
| list_for_each_entry(module, &codec->module_list, list) { |
| if (module->dev_id == dev_id) { |
| mutex_unlock(&codec->lock); |
| return module; |
| } |
| } |
| mutex_unlock(&codec->lock); |
| dev_warn(codec->dev, "%s: module#%d missing in codec list\n", name, |
| dev_id); |
| return NULL; |
| } |
| |
| static const char *gbaudio_map_controlid(struct gbaudio_module_info *module, |
| __u8 control_id, __u8 index) |
| { |
| struct gbaudio_control *control; |
| |
| if (control_id == GBAUDIO_INVALID_ID) |
| return NULL; |
| |
| list_for_each_entry(control, &module->ctl_list, list) { |
| if (control->id == control_id) { |
| if (index == GBAUDIO_INVALID_ID) |
| return control->name; |
| return control->texts[index]; |
| } |
| } |
| list_for_each_entry(control, &module->widget_ctl_list, list) { |
| if (control->id == control_id) { |
| if (index == GBAUDIO_INVALID_ID) |
| return control->name; |
| return control->texts[index]; |
| } |
| } |
| return NULL; |
| } |
| |
| static int gbaudio_map_widgetname(struct gbaudio_module_info *module, |
| const char *name) |
| { |
| struct gbaudio_widget *widget; |
| list_for_each_entry(widget, &module->widget_list, list) { |
| if (!strncmp(widget->name, name, NAME_SIZE)) |
| return widget->id; |
| } |
| return -EINVAL; |
| } |
| |
| static const char *gbaudio_map_widgetid(struct gbaudio_module_info *module, |
| __u8 widget_id) |
| { |
| struct gbaudio_widget *widget; |
| |
| list_for_each_entry(widget, &module->widget_list, list) { |
| if (widget->id == widget_id) |
| return widget->name; |
| } |
| return NULL; |
| } |
| |
| static int gbcodec_mixer_ctl_info(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_info *uinfo) |
| { |
| unsigned int max; |
| const char *name; |
| struct gbaudio_ctl_pvt *data; |
| struct gb_audio_ctl_elem_info *info; |
| struct gbaudio_module_info *module; |
| struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct gbaudio_codec_info *gbcodec = snd_soc_codec_get_drvdata(codec); |
| |
| dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name); |
| data = (struct gbaudio_ctl_pvt *)kcontrol->private_value; |
| info = (struct gb_audio_ctl_elem_info *)data->info; |
| |
| if (!info) { |
| dev_err(module->dev, "NULL info for %s\n", uinfo->id.name); |
| return -EINVAL; |
| } |
| |
| /* update uinfo */ |
| uinfo->access = data->access; |
| uinfo->count = data->vcount; |
| uinfo->type = (snd_ctl_elem_type_t)info->type; |
| |
| switch (info->type) { |
| case GB_AUDIO_CTL_ELEM_TYPE_BOOLEAN: |
| case GB_AUDIO_CTL_ELEM_TYPE_INTEGER: |
| uinfo->value.integer.min = info->value.integer.min; |
| uinfo->value.integer.max = info->value.integer.max; |
| break; |
| case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED: |
| max = info->value.enumerated.items; |
| uinfo->value.enumerated.items = max; |
| if (uinfo->value.enumerated.item > max - 1) |
| uinfo->value.enumerated.item = max - 1; |
| module = find_gb_module(gbcodec, kcontrol->id.name); |
| if (!module) |
| return -EINVAL; |
| name = gbaudio_map_controlid(module, data->ctl_id, |
| uinfo->value.enumerated.item); |
| strlcpy(uinfo->value.enumerated.name, name, NAME_SIZE); |
| break; |
| default: |
| dev_err(codec->dev, "Invalid type: %d for %s:kcontrol\n", |
| info->type, kcontrol->id.name); |
| break; |
| } |
| return 0; |
| } |
| |
| static int gbcodec_mixer_ctl_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| int ret; |
| struct gb_audio_ctl_elem_info *info; |
| struct gbaudio_ctl_pvt *data; |
| struct gb_audio_ctl_elem_value gbvalue; |
| struct gbaudio_module_info *module; |
| struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec); |
| |
| dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name); |
| module = find_gb_module(gb, kcontrol->id.name); |
| if (!module) |
| return -EINVAL; |
| |
| data = (struct gbaudio_ctl_pvt *)kcontrol->private_value; |
| info = (struct gb_audio_ctl_elem_info *)data->info; |
| |
| ret = gb_audio_gb_get_control(module->mgmt_connection, data->ctl_id, |
| GB_AUDIO_INVALID_INDEX, &gbvalue); |
| if (ret) { |
| dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret, |
| __func__, kcontrol->id.name); |
| return ret; |
| } |
| |
| /* update ucontrol */ |
| switch (info->type) { |
| case GB_AUDIO_CTL_ELEM_TYPE_BOOLEAN: |
| case GB_AUDIO_CTL_ELEM_TYPE_INTEGER: |
| ucontrol->value.integer.value[0] = |
| gbvalue.value.integer_value[0]; |
| if (data->vcount == 2) |
| ucontrol->value.integer.value[1] = |
| gbvalue.value.integer_value[1]; |
| break; |
| case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED: |
| ucontrol->value.enumerated.item[0] = |
| gbvalue.value.enumerated_item[0]; |
| if (data->vcount == 2) |
| ucontrol->value.enumerated.item[1] = |
| gbvalue.value.enumerated_item[1]; |
| break; |
| default: |
| dev_err(codec->dev, "Invalid type: %d for %s:kcontrol\n", |
| info->type, kcontrol->id.name); |
| ret = -EINVAL; |
| break; |
| } |
| return ret; |
| } |
| |
| static int gbcodec_mixer_ctl_put(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| int ret = 0; |
| struct gb_audio_ctl_elem_info *info; |
| struct gbaudio_ctl_pvt *data; |
| struct gb_audio_ctl_elem_value gbvalue; |
| struct gbaudio_module_info *module; |
| struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec); |
| |
| dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name); |
| module = find_gb_module(gb, kcontrol->id.name); |
| if (!module) |
| return -EINVAL; |
| |
| data = (struct gbaudio_ctl_pvt *)kcontrol->private_value; |
| info = (struct gb_audio_ctl_elem_info *)data->info; |
| |
| /* 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(module->mgmt_connection, data->ctl_id, |
| GB_AUDIO_INVALID_INDEX, &gbvalue); |
| if (ret) { |
| dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret, |
| __func__, kcontrol->id.name); |
| } |
| |
| return ret; |
| } |
| |
| #define SOC_MIXER_GB(xname, kcount, data) \ |
| { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ |
| .count = kcount, .info = gbcodec_mixer_ctl_info, \ |
| .get = gbcodec_mixer_ctl_get, .put = gbcodec_mixer_ctl_put, \ |
| .private_value = (unsigned long)data } |
| |
| /* |
| * although below callback functions seems redundant to above functions. |
| * same are kept to allow provision for different handling in case |
| * of DAPM related sequencing, etc. |
| */ |
| static int gbcodec_mixer_dapm_ctl_info(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_info *uinfo) |
| { |
| int platform_max, platform_min; |
| struct gbaudio_ctl_pvt *data; |
| struct gb_audio_ctl_elem_info *info; |
| struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); |
| struct snd_soc_dapm_widget *widget = wlist->widgets[0]; |
| struct snd_soc_codec *codec = widget->codec; |
| |
| dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name); |
| data = (struct gbaudio_ctl_pvt *)kcontrol->private_value; |
| info = (struct gb_audio_ctl_elem_info *)data->info; |
| |
| /* update uinfo */ |
| platform_max = info->value.integer.max; |
| platform_min = info->value.integer.min; |
| |
| if (platform_max == 1 && |
| !strnstr(kcontrol->id.name, " Volume", NAME_SIZE)) |
| uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; |
| else |
| uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
| |
| uinfo->count = data->vcount; |
| uinfo->value.integer.min = 0; |
| if (info->value.integer.min < 0 && |
| (uinfo->type == SNDRV_CTL_ELEM_TYPE_INTEGER)) |
| uinfo->value.integer.max = platform_max - platform_min; |
| else |
| uinfo->value.integer.max = platform_max; |
| |
| return 0; |
| } |
| |
| static int gbcodec_mixer_dapm_ctl_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| int ret; |
| struct gb_audio_ctl_elem_info *info; |
| struct gbaudio_ctl_pvt *data; |
| struct gb_audio_ctl_elem_value gbvalue; |
| struct gbaudio_module_info *module; |
| struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); |
| struct snd_soc_dapm_widget *widget = wlist->widgets[0]; |
| struct snd_soc_codec *codec = widget->codec; |
| struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec); |
| |
| dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name); |
| module = find_gb_module(gb, kcontrol->id.name); |
| if (!module) |
| return -EINVAL; |
| |
| data = (struct gbaudio_ctl_pvt *)kcontrol->private_value; |
| info = (struct gb_audio_ctl_elem_info *)data->info; |
| |
| 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(module->mgmt_connection, data->ctl_id, |
| GB_AUDIO_INVALID_INDEX, &gbvalue); |
| if (ret) { |
| dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret, |
| __func__, kcontrol->id.name); |
| return ret; |
| } |
| /* update ucontrol */ |
| ucontrol->value.integer.value[0] = gbvalue.value.integer_value[0]; |
| |
| return ret; |
| } |
| |
| static int gbcodec_mixer_dapm_ctl_put(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| int ret, wi, max, connect; |
| unsigned int mask, val; |
| struct gb_audio_ctl_elem_info *info; |
| struct gbaudio_ctl_pvt *data; |
| struct gb_audio_ctl_elem_value gbvalue; |
| struct gbaudio_module_info *module; |
| struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); |
| struct snd_soc_dapm_widget *widget = wlist->widgets[0]; |
| struct snd_soc_codec *codec = widget->codec; |
| struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec); |
| |
| dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name); |
| module = find_gb_module(gb, kcontrol->id.name); |
| if (!module) |
| return -EINVAL; |
| |
| data = (struct gbaudio_ctl_pvt *)kcontrol->private_value; |
| info = (struct gb_audio_ctl_elem_info *)data->info; |
| |
| 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(module->mgmt_connection, |
| data->ctl_id, |
| GB_AUDIO_INVALID_INDEX, &gbvalue); |
| if (ret) { |
| dev_err_ratelimited(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_module_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( |
| module_apb1_rx_enum, GBCODEC_APB1_MUX_REG, 0, gbtexts); |
| |
| static const SOC_ENUM_SINGLE_DECL( |
| module_mic_enum, GBCODEC_APB1_MUX_REG, 4, gbtexts); |
| |
| static int gbaudio_tplg_create_enum_ctl(struct gbaudio_module_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, module_apb1_rx_enum); |
| break; |
| case 9: |
| *kctl = (struct snd_kcontrol_new) |
| SOC_DAPM_ENUM(ctl->name, module_mic_enum); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int gbaudio_tplg_create_mixer_ctl(struct gbaudio_module_info *gb, |
| struct snd_kcontrol_new *kctl, |
| struct gb_audio_control *ctl) |
| { |
| struct gbaudio_ctl_pvt *ctldata; |
| |
| ctldata = devm_kzalloc(gb->dev, sizeof(struct gbaudio_ctl_pvt), |
| GFP_KERNEL); |
| if (!ctldata) |
| return -ENOMEM; |
| ctldata->ctl_id = ctl->id; |
| ctldata->data_cport = ctl->data_cport; |
| ctldata->access = ctl->access; |
| ctldata->vcount = ctl->count_values; |
| ctldata->info = &ctl->info; |
| *kctl = (struct snd_kcontrol_new) |
| SOC_DAPM_MIXER_GB(ctl->name, ctl->count, ctldata); |
| |
| return 0; |
| } |
| |
| static int gbaudio_tplg_create_wcontrol(struct gbaudio_module_info *gb, |
| struct snd_kcontrol_new *kctl, |
| struct gb_audio_control *ctl) |
| { |
| int ret; |
| |
| switch (ctl->iface) { |
| case SNDRV_CTL_ELEM_IFACE_MIXER: |
| switch (ctl->info.type) { |
| case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED: |
| ret = gbaudio_tplg_create_enum_ctl(gb, kctl, ctl); |
| break; |
| default: |
| ret = gbaudio_tplg_create_mixer_ctl(gb, kctl, ctl); |
| break; |
| } |
| break; |
| default: |
| return -EINVAL; |
| |
| } |
| |
| dev_dbg(gb->dev, "%s:%d DAPM control created, ret:%d\n", ctl->name, |
| ctl->id, ret); |
| return ret; |
| } |
| |
| static int gbaudio_widget_event(struct snd_soc_dapm_widget *w, |
| struct snd_kcontrol *kcontrol, int event) |
| { |
| int wid; |
| int ret; |
| struct snd_soc_codec *codec = w->codec; |
| struct gbaudio_codec_info *gbcodec = snd_soc_codec_get_drvdata(codec); |
| struct gbaudio_module_info *module; |
| |
| dev_dbg(codec->dev, "%s %s %d\n", __func__, w->name, event); |
| |
| /* Find relevant module */ |
| module = find_gb_module(gbcodec, w->name); |
| if (!module) |
| return -EINVAL; |
| |
| /* map name to widget id */ |
| wid = gbaudio_map_widgetname(module, w->name); |
| if (wid < 0) { |
| dev_err(codec->dev, "Invalid widget name:%s\n", w->name); |
| return -EINVAL; |
| } |
| |
| switch (event) { |
| case SND_SOC_DAPM_PRE_PMU: |
| ret = gb_audio_gb_enable_widget(module->mgmt_connection, wid); |
| if (!ret) |
| ret = gbaudio_module_update(gbcodec, w->name, module, |
| 1); |
| break; |
| case SND_SOC_DAPM_POST_PMD: |
| ret = gb_audio_gb_disable_widget(module->mgmt_connection, wid); |
| if (!ret) |
| ret = gbaudio_module_update(gbcodec, w->name, module, |
| 0); |
| break; |
| } |
| if (ret) |
| dev_err_ratelimited(codec->dev, |
| "%d: widget, event:%d failed:%d\n", wid, |
| event, ret); |
| return ret; |
| } |
| |
| static int gbaudio_tplg_create_widget(struct gbaudio_module_info *module, |
| struct snd_soc_dapm_widget *dw, |
| struct gb_audio_widget *w) |
| { |
| int i, ret; |
| struct snd_kcontrol_new *widget_kctls; |
| struct gb_audio_control *curr; |
| struct gbaudio_control *control, *_control; |
| size_t size; |
| char temp_name[NAME_SIZE]; |
| |
| ret = gbaudio_validate_kcontrol_count(w); |
| if (ret) { |
| dev_err(module->dev, "Inavlid kcontrol count=%d for %s\n", |
| w->ncontrols, w->name); |
| return ret; |
| } |
| |
| /* allocate memory for kcontrol */ |
| if (w->ncontrols) { |
| size = sizeof(struct snd_kcontrol_new) * w->ncontrols; |
| widget_kctls = devm_kzalloc(module->dev, size, GFP_KERNEL); |
| if (!widget_kctls) |
| return -ENOMEM; |
| } |
| |
| /* create relevant kcontrols */ |
| for (i = 0; i < w->ncontrols; i++) { |
| curr = &w->ctl[i]; |
| ret = gbaudio_tplg_create_wcontrol(module, &widget_kctls[i], |
| curr); |
| if (ret) { |
| dev_err(module->dev, |
| "%s:%d type widget_ctl not supported\n", |
| curr->name, curr->iface); |
| goto error; |
| } |
| control = devm_kzalloc(module->dev, |
| sizeof(struct gbaudio_control), |
| GFP_KERNEL); |
| if (!control) { |
| ret = -ENOMEM; |
| goto error; |
| } |
| control->id = curr->id; |
| control->name = curr->name; |
| if (curr->info.type == GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED) |
| control->texts = (const char * const *) |
| curr->info.value.enumerated.names; |
| list_add(&control->list, &module->widget_ctl_list); |
| dev_dbg(module->dev, "%s: control of type %d created\n", |
| widget_kctls[i].name, widget_kctls[i].iface); |
| } |
| |
| /* Prefix dev_id to widget control_name */ |
| strlcpy(temp_name, w->name, NAME_SIZE); |
| snprintf(w->name, NAME_SIZE, "GB %d %s", module->dev_id, temp_name); |
| |
| switch (w->type) { |
| case snd_soc_dapm_spk: |
| *dw = (struct snd_soc_dapm_widget) |
| SND_SOC_DAPM_SPK(w->name, gbcodec_event_spk); |
| module->op_devices |= GBAUDIO_DEVICE_OUT_SPEAKER; |
| break; |
| case snd_soc_dapm_hp: |
| *dw = (struct snd_soc_dapm_widget) |
| SND_SOC_DAPM_HP(w->name, gbcodec_event_hp); |
| module->num_jacks++; |
| module->op_devices |= (GBAUDIO_DEVICE_OUT_WIRED_HEADSET |
| | GBAUDIO_DEVICE_OUT_WIRED_HEADPHONE); |
| module->ip_devices |= GBAUDIO_DEVICE_IN_WIRED_HEADSET; |
| break; |
| case snd_soc_dapm_mic: |
| *dw = (struct snd_soc_dapm_widget) |
| SND_SOC_DAPM_MIC(w->name, gbcodec_event_int_mic); |
| module->ip_devices |= GBAUDIO_DEVICE_IN_BUILTIN_MIC; |
| break; |
| case snd_soc_dapm_output: |
| *dw = (struct snd_soc_dapm_widget)SND_SOC_DAPM_OUTPUT(w->name); |
| break; |
| case snd_soc_dapm_input: |
| *dw = (struct snd_soc_dapm_widget)SND_SOC_DAPM_INPUT(w->name); |
| break; |
| case snd_soc_dapm_switch: |
| *dw = (struct snd_soc_dapm_widget) |
| SND_SOC_DAPM_SWITCH_E(w->name, SND_SOC_NOPM, 0, 0, |
| widget_kctls, gbaudio_widget_event, |
| SND_SOC_DAPM_PRE_PMU | |
| SND_SOC_DAPM_POST_PMD); |
| break; |
| case snd_soc_dapm_pga: |
| *dw = (struct snd_soc_dapm_widget) |
| SND_SOC_DAPM_PGA_E(w->name, SND_SOC_NOPM, 0, 0, NULL, 0, |
| gbaudio_widget_event, |
| SND_SOC_DAPM_PRE_PMU | |
| SND_SOC_DAPM_POST_PMD); |
| break; |
| case snd_soc_dapm_mixer: |
| *dw = (struct snd_soc_dapm_widget) |
| SND_SOC_DAPM_MIXER_E(w->name, SND_SOC_NOPM, 0, 0, NULL, |
| 0, gbaudio_widget_event, |
| SND_SOC_DAPM_PRE_PMU | |
| SND_SOC_DAPM_POST_PMD); |
| break; |
| case snd_soc_dapm_mux: |
| *dw = (struct snd_soc_dapm_widget) |
| SND_SOC_DAPM_MUX_E(w->name, SND_SOC_NOPM, 0, 0, |
| widget_kctls, gbaudio_widget_event, |
| SND_SOC_DAPM_PRE_PMU | |
| SND_SOC_DAPM_POST_PMD); |
| break; |
| case snd_soc_dapm_aif_in: |
| *dw = (struct snd_soc_dapm_widget) |
| SND_SOC_DAPM_AIF_IN_E(w->name, w->sname, 0, |
| SND_SOC_NOPM, |
| 0, 0, gbaudio_widget_event, |
| SND_SOC_DAPM_PRE_PMU | |
| SND_SOC_DAPM_POST_PMD); |
| break; |
| case snd_soc_dapm_aif_out: |
| *dw = (struct snd_soc_dapm_widget) |
| SND_SOC_DAPM_AIF_OUT_E(w->name, w->sname, 0, |
| SND_SOC_NOPM, |
| 0, 0, gbaudio_widget_event, |
| SND_SOC_DAPM_PRE_PMU | |
| SND_SOC_DAPM_POST_PMD); |
| break; |
| default: |
| ret = -EINVAL; |
| goto error; |
| } |
| |
| dev_dbg(module->dev, "%s: widget of type %d created\n", dw->name, |
| dw->id); |
| return 0; |
| error: |
| list_for_each_entry_safe(control, _control, &module->widget_ctl_list, |
| list) { |
| list_del(&control->list); |
| devm_kfree(module->dev, control); |
| } |
| return ret; |
| } |
| |
| static int gbaudio_tplg_process_kcontrols(struct gbaudio_module_info *module, |
| struct gb_audio_control *controls) |
| { |
| int i, ret; |
| struct snd_kcontrol_new *dapm_kctls; |
| struct gb_audio_control *curr; |
| struct gbaudio_control *control, *_control; |
| size_t size; |
| char temp_name[NAME_SIZE]; |
| |
| size = sizeof(struct snd_kcontrol_new) * module->num_controls; |
| dapm_kctls = devm_kzalloc(module->dev, size, GFP_KERNEL); |
| if (!dapm_kctls) |
| return -ENOMEM; |
| |
| curr = controls; |
| for (i = 0; i < module->num_controls; i++) { |
| ret = gbaudio_tplg_create_kcontrol(module, &dapm_kctls[i], |
| curr); |
| if (ret) { |
| dev_err(module->dev, "%s:%d type not supported\n", |
| curr->name, curr->iface); |
| goto error; |
| } |
| control = devm_kzalloc(module->dev, sizeof(struct |
| gbaudio_control), |
| GFP_KERNEL); |
| if (!control) { |
| ret = -ENOMEM; |
| goto error; |
| } |
| control->id = curr->id; |
| /* Prefix dev_id to widget_name */ |
| strlcpy(temp_name, curr->name, NAME_SIZE); |
| snprintf(curr->name, NAME_SIZE, "GB %d %s", module->dev_id, |
| temp_name); |
| control->name = curr->name; |
| if (curr->info.type == GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED) |
| control->texts = (const char * const *) |
| curr->info.value.enumerated.names; |
| list_add(&control->list, &module->ctl_list); |
| dev_dbg(module->dev, "%d:%s created of type %d\n", curr->id, |
| curr->name, curr->info.type); |
| curr++; |
| } |
| module->controls = dapm_kctls; |
| |
| return 0; |
| error: |
| list_for_each_entry_safe(control, _control, &module->ctl_list, |
| list) { |
| list_del(&control->list); |
| devm_kfree(module->dev, control); |
| } |
| devm_kfree(module->dev, dapm_kctls); |
| return ret; |
| } |
| |
| static int gbaudio_tplg_process_widgets(struct gbaudio_module_info *module, |
| struct gb_audio_widget *widgets) |
| { |
| int i, ret, 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) * module->num_dapm_widgets; |
| dapm_widgets = devm_kzalloc(module->dev, size, GFP_KERNEL); |
| if (!dapm_widgets) |
| return -ENOMEM; |
| |
| curr = widgets; |
| for (i = 0; i < module->num_dapm_widgets; i++) { |
| ret = gbaudio_tplg_create_widget(module, &dapm_widgets[i], |
| curr); |
| if (ret) { |
| dev_err(module->dev, "%s:%d type not supported\n", |
| curr->name, curr->type); |
| goto error; |
| } |
| widget = devm_kzalloc(module->dev, sizeof(struct |
| gbaudio_widget), |
| GFP_KERNEL); |
| if (!widget) { |
| ret = -ENOMEM; |
| goto error; |
| } |
| widget->id = curr->id; |
| widget->name = curr->name; |
| list_add(&widget->list, &module->widget_list); |
| ncontrols = curr->ncontrols; |
| curr++; |
| curr = (void *)curr + ncontrols*sizeof(struct gb_audio_control); |
| } |
| module->dapm_widgets = dapm_widgets; |
| |
| return 0; |
| |
| error: |
| list_for_each_entry_safe(widget, _widget, &module->widget_list, |
| list) { |
| list_del(&widget->list); |
| devm_kfree(module->dev, widget); |
| } |
| devm_kfree(module->dev, dapm_widgets); |
| return ret; |
| } |
| |
| static int gbaudio_tplg_process_routes(struct gbaudio_module_info *module, |
| struct gb_audio_route *routes) |
| { |
| int i, ret; |
| struct snd_soc_dapm_route *dapm_routes; |
| struct gb_audio_route *curr; |
| size_t size; |
| |
| size = sizeof(struct snd_soc_dapm_route) * module->num_dapm_routes; |
| dapm_routes = devm_kzalloc(module->dev, size, GFP_KERNEL); |
| if (!dapm_routes) |
| return -ENOMEM; |
| |
| module->dapm_routes = dapm_routes; |
| curr = routes; |
| |
| for (i = 0; i < module->num_dapm_routes; i++) { |
| dapm_routes->sink = |
| gbaudio_map_widgetid(module, curr->destination_id); |
| if (!dapm_routes->sink) { |
| dev_err(module->dev, "%d:%d:%d:%d - Invalid sink\n", |
| curr->source_id, curr->destination_id, |
| curr->control_id, curr->index); |
| ret = -EINVAL; |
| goto error; |
| } |
| dapm_routes->source = |
| gbaudio_map_widgetid(module, curr->source_id); |
| if (!dapm_routes->source) { |
| dev_err(module->dev, "%d:%d:%d:%d - Invalid source\n", |
| curr->source_id, curr->destination_id, |
| curr->control_id, curr->index); |
| ret = -EINVAL; |
| goto error; |
| } |
| dapm_routes->control = |
| gbaudio_map_controlid(module, |
| curr->control_id, |
| curr->index); |
| if ((curr->control_id != GBAUDIO_INVALID_ID) && |
| !dapm_routes->control) { |
| dev_err(module->dev, "%d:%d:%d:%d - Invalid control\n", |
| curr->source_id, curr->destination_id, |
| curr->control_id, curr->index); |
| ret = -EINVAL; |
| goto error; |
| } |
| dev_dbg(module->dev, "Route {%s, %s, %s}\n", dapm_routes->sink, |
| (dapm_routes->control) ? dapm_routes->control:"NULL", |
| dapm_routes->source); |
| dapm_routes++; |
| curr++; |
| } |
| |
| return 0; |
| |
| error: |
| devm_kfree(module->dev, dapm_routes); |
| return ret; |
| } |
| |
| static int gbaudio_tplg_process_header(struct gbaudio_module_info *module, |
| struct gb_audio_topology *tplg_data) |
| { |
| /* fetch no. of kcontrols, widgets & routes */ |
| module->num_controls = tplg_data->num_controls; |
| module->num_dapm_widgets = tplg_data->num_widgets; |
| module->num_dapm_routes = tplg_data->num_routes; |
| |
| /* update block offset */ |
| module->dai_offset = (unsigned long)&tplg_data->data; |
| module->control_offset = module->dai_offset + tplg_data->size_dais; |
| module->widget_offset = module->control_offset + |
| tplg_data->size_controls; |
| module->route_offset = module->widget_offset + |
| tplg_data->size_widgets; |
| |
| dev_dbg(module->dev, "DAI offset is 0x%lx\n", module->dai_offset); |
| dev_dbg(module->dev, "control offset is %lx\n", |
| module->control_offset); |
| dev_dbg(module->dev, "widget offset is %lx\n", module->widget_offset); |
| dev_dbg(module->dev, "route offset is %lx\n", module->route_offset); |
| |
| return 0; |
| } |
| |
| int gbaudio_tplg_parse_data(struct gbaudio_module_info *module, |
| struct gb_audio_topology *tplg_data) |
| { |
| int ret; |
| struct gb_audio_control *controls; |
| struct gb_audio_widget *widgets; |
| struct gb_audio_route *routes; |
| |
| if (!tplg_data) |
| return -EINVAL; |
| |
| ret = gbaudio_tplg_process_header(module, tplg_data); |
| if (ret) { |
| dev_err(module->dev, "%d: Error in parsing topology header\n", |
| ret); |
| return ret; |
| } |
| |
| /* process control */ |
| controls = (struct gb_audio_control *)module->control_offset; |
| ret = gbaudio_tplg_process_kcontrols(module, controls); |
| if (ret) { |
| dev_err(module->dev, |
| "%d: Error in parsing controls data\n", ret); |
| return ret; |
| } |
| dev_dbg(module->dev, "Control parsing finished\n"); |
| |
| /* process widgets */ |
| widgets = (struct gb_audio_widget *)module->widget_offset; |
| ret = gbaudio_tplg_process_widgets(module, widgets); |
| if (ret) { |
| dev_err(module->dev, |
| "%d: Error in parsing widgets data\n", ret); |
| return ret; |
| } |
| dev_dbg(module->dev, "Widget parsing finished\n"); |
| |
| /* process route */ |
| routes = (struct gb_audio_route *)module->route_offset; |
| ret = gbaudio_tplg_process_routes(module, routes); |
| if (ret) { |
| dev_err(module->dev, |
| "%d: Error in parsing routes data\n", ret); |
| return ret; |
| } |
| dev_dbg(module->dev, "Route parsing finished\n"); |
| |
| return ret; |
| } |
| |
| void gbaudio_tplg_release(struct gbaudio_module_info *module) |
| { |
| struct gbaudio_control *control, *_control; |
| struct gbaudio_widget *widget, *_widget; |
| |
| if (!module->topology) |
| return; |
| |
| /* release kcontrols */ |
| list_for_each_entry_safe(control, _control, &module->ctl_list, |
| list) { |
| list_del(&control->list); |
| devm_kfree(module->dev, control); |
| } |
| if (module->controls) |
| devm_kfree(module->dev, module->controls); |
| |
| /* release widget controls */ |
| list_for_each_entry_safe(control, _control, &module->widget_ctl_list, |
| list) { |
| list_del(&control->list); |
| devm_kfree(module->dev, control); |
| } |
| |
| /* release widgets */ |
| list_for_each_entry_safe(widget, _widget, &module->widget_list, |
| list) { |
| list_del(&widget->list); |
| devm_kfree(module->dev, widget); |
| } |
| if (module->dapm_widgets) |
| devm_kfree(module->dev, module->dapm_widgets); |
| |
| /* release routes */ |
| if (module->dapm_routes) |
| devm_kfree(module->dev, module->dapm_routes); |
| } |