// SPDX-License-Identifier: GPL-2.0 /* * Greybus audio driver * Copyright 2015-2016 Google Inc. * Copyright 2015-2016 Linaro Ltd. */ #include #include "audio_codec.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; char begin[NAME_SIZE]; struct gbaudio_module_info *module; if (!name) return NULL; if (sscanf(name, "%s %d", begin, &dev_id) != 2) return NULL; dev_dbg(codec->dev, "%s:Find module#%d\n", __func__, dev_id); mutex_lock(&codec->lock); list_for_each_entry(module, &codec->module_list, list) { if (module->dev_id == dev_id) { mutex_unlock(&codec->lock); return module; } } mutex_unlock(&codec->lock); dev_warn(codec->dev, "%s: module#%d missing in codec list\n", name, dev_id); return NULL; } static const char *gbaudio_map_controlid(struct gbaudio_module_info *module, __u8 control_id, __u8 index) { struct gbaudio_control *control; if (control_id == GBAUDIO_INVALID_ID) return NULL; list_for_each_entry(control, &module->ctl_list, list) { if (control->id == control_id) { if (index == GBAUDIO_INVALID_ID) return control->name; if (index >= control->items) return NULL; return control->texts[index]; } } list_for_each_entry(control, &module->widget_ctl_list, list) { if (control->id == control_id) { if (index == GBAUDIO_INVALID_ID) return control->name; if (index >= control->items) return NULL; return control->texts[index]; } } return NULL; } static int gbaudio_map_controlname(struct gbaudio_module_info *module, const char *name) { struct gbaudio_control *control; list_for_each_entry(control, &module->ctl_list, list) { if (!strncmp(control->name, name, NAME_SIZE)) return control->id; } dev_warn(module->dev, "%s: missing in modules controls list\n", name); return -EINVAL; } static int gbaudio_map_wcontrolname(struct gbaudio_module_info *module, const char *name) { struct gbaudio_control *control; list_for_each_entry(control, &module->widget_ctl_list, list) { if (!strncmp(control->wname, name, NAME_SIZE)) return control->id; } dev_warn(module->dev, "%s: missing in modules controls list\n", name); return -EINVAL; } static int gbaudio_map_widgetname(struct gbaudio_module_info *module, const char *name) { struct gbaudio_widget *widget; list_for_each_entry(widget, &module->widget_list, list) { if (!strncmp(widget->name, name, NAME_SIZE)) return widget->id; } dev_warn(module->dev, "%s: missing in modules widgets list\n", name); return -EINVAL; } static const char *gbaudio_map_widgetid(struct gbaudio_module_info *module, __u8 widget_id) { struct gbaudio_widget *widget; list_for_each_entry(widget, &module->widget_list, list) { if (widget->id == widget_id) return widget->name; } return NULL; } static const char **gb_generate_enum_strings(struct gbaudio_module_info *gb, struct gb_audio_enumerated *gbenum) { const char **strings; int i; unsigned int items; __u8 *data; items = le32_to_cpu(gbenum->items); strings = devm_kcalloc(gb->dev, items, sizeof(char *), GFP_KERNEL); if (!strings) return NULL; data = gbenum->names; for (i = 0; i < items; i++) { strings[i] = (const char *)data; while (*data != '\0') data++; data++; } return strings; } static int gbcodec_mixer_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { unsigned int max; const char *name; struct gbaudio_ctl_pvt *data; struct gb_audio_ctl_elem_info *info; struct gbaudio_module_info *module; struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); struct gbaudio_codec_info *gbcodec = snd_soc_component_get_drvdata(comp); dev_dbg(comp->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(comp->dev, "NULL info for %s\n", uinfo->id.name); return -EINVAL; } /* update uinfo */ uinfo->access = data->access; uinfo->count = data->vcount; uinfo->type = (__force 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 = le32_to_cpu(info->value.integer.min); uinfo->value.integer.max = le32_to_cpu(info->value.integer.max); break; case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED: max = le32_to_cpu(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); strscpy(uinfo->value.enumerated.name, name, sizeof(uinfo->value.enumerated.name)); break; default: dev_err(comp->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_component *comp = snd_soc_kcontrol_component(kcontrol); struct gbaudio_codec_info *gb = snd_soc_component_get_drvdata(comp); struct gb_bundle *bundle; dev_dbg(comp->dev, "Entered %s:%s\n", __func__, kcontrol->id.name); module = find_gb_module(gb, kcontrol->id.name); if (!module) return -EINVAL; data = (struct gbaudio_ctl_pvt *)kcontrol->private_value; info = (struct gb_audio_ctl_elem_info *)data->info; bundle = to_gb_bundle(module->dev); ret = gb_pm_runtime_get_sync(bundle); if (ret) return ret; ret = gb_audio_gb_get_control(module->mgmt_connection, data->ctl_id, GB_AUDIO_INVALID_INDEX, &gbvalue); gb_pm_runtime_put_autosuspend(bundle); if (ret) { dev_err_ratelimited(comp->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] = le32_to_cpu(gbvalue.value.integer_value[0]); if (data->vcount == 2) ucontrol->value.integer.value[1] = le32_to_cpu(gbvalue.value.integer_value[1]); break; case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED: ucontrol->value.enumerated.item[0] = le32_to_cpu(gbvalue.value.enumerated_item[0]); if (data->vcount == 2) ucontrol->value.enumerated.item[1] = le32_to_cpu(gbvalue.value.enumerated_item[1]); break; default: dev_err(comp->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_component *comp = snd_soc_kcontrol_component(kcontrol); struct gbaudio_codec_info *gb = snd_soc_component_get_drvdata(comp); struct gb_bundle *bundle; dev_dbg(comp->dev, "Entered %s:%s\n", __func__, kcontrol->id.name); module = find_gb_module(gb, kcontrol->id.name); if (!module) return -EINVAL; data = (struct gbaudio_ctl_pvt *)kcontrol->private_value; info = (struct gb_audio_ctl_elem_info *)data->info; bundle = to_gb_bundle(module->dev); /* update ucontrol */ switch (info->type) { case GB_AUDIO_CTL_ELEM_TYPE_BOOLEAN: case GB_AUDIO_CTL_ELEM_TYPE_INTEGER: gbvalue.value.integer_value[0] = cpu_to_le32(ucontrol->value.integer.value[0]); if (data->vcount == 2) gbvalue.value.integer_value[1] = cpu_to_le32(ucontrol->value.integer.value[1]); break; case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED: gbvalue.value.enumerated_item[0] = cpu_to_le32(ucontrol->value.enumerated.item[0]); if (data->vcount == 2) gbvalue.value.enumerated_item[1] = cpu_to_le32(ucontrol->value.enumerated.item[1]); break; default: dev_err(comp->dev, "Invalid type: %d for %s:kcontrol\n", info->type, kcontrol->id.name); ret = -EINVAL; break; } if (ret) return ret; ret = gb_pm_runtime_get_sync(bundle); if (ret) return ret; ret = gb_audio_gb_set_control(module->mgmt_connection, data->ctl_id, GB_AUDIO_INVALID_INDEX, &gbvalue); gb_pm_runtime_put_autosuspend(bundle); if (ret) { dev_err_ratelimited(comp->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 = le32_to_cpu(info->value.integer.max); platform_min = le32_to_cpu(info->value.integer.min); if (platform_max == 1 && !strnstr(kcontrol->id.name, " Volume", sizeof(kcontrol->id.name))) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; else uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = data->vcount; uinfo->value.integer.min = platform_min; 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 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 device *codec_dev = widget->dapm->dev; struct gbaudio_codec_info *gb = dev_get_drvdata(codec_dev); struct gb_bundle *bundle; dev_dbg(codec_dev, "Entered %s:%s\n", __func__, kcontrol->id.name); module = find_gb_module(gb, kcontrol->id.name); if (!module) return -EINVAL; data = (struct gbaudio_ctl_pvt *)kcontrol->private_value; bundle = to_gb_bundle(module->dev); if (data->vcount == 2) dev_warn(widget->dapm->dev, "GB: Control '%s' is stereo, which is not supported\n", kcontrol->id.name); ret = gb_pm_runtime_get_sync(bundle); if (ret) return ret; ret = gb_audio_gb_get_control(module->mgmt_connection, data->ctl_id, GB_AUDIO_INVALID_INDEX, &gbvalue); gb_pm_runtime_put_autosuspend(bundle); if (ret) { dev_err_ratelimited(codec_dev, "%d:Error in %s for %s\n", ret, __func__, kcontrol->id.name); return ret; } /* update ucontrol */ ucontrol->value.integer.value[0] = le32_to_cpu(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 device *codec_dev = widget->dapm->dev; struct gbaudio_codec_info *gb = dev_get_drvdata(codec_dev); struct gb_bundle *bundle; dev_dbg(codec_dev, "Entered %s:%s\n", __func__, kcontrol->id.name); module = find_gb_module(gb, kcontrol->id.name); if (!module) return -EINVAL; data = (struct gbaudio_ctl_pvt *)kcontrol->private_value; info = (struct gb_audio_ctl_elem_info *)data->info; bundle = to_gb_bundle(module->dev); if (data->vcount == 2) dev_warn(widget->dapm->dev, "GB: Control '%s' is stereo, which is not supported\n", kcontrol->id.name); max = le32_to_cpu(info->value.integer.max); mask = (1 << fls(max)) - 1; val = ucontrol->value.integer.value[0] & mask; connect = !!val; ret = gb_pm_runtime_get_sync(bundle); if (ret) return ret; ret = gb_audio_gb_get_control(module->mgmt_connection, data->ctl_id, GB_AUDIO_INVALID_INDEX, &gbvalue); if (ret) goto exit; /* update ucontrol */ if (le32_to_cpu(gbvalue.value.integer_value[0]) != val) { for (wi = 0; wi < wlist->num_widgets; wi++) { widget = wlist->widgets[wi]; snd_soc_dapm_mixer_update_power(widget->dapm, kcontrol, connect, NULL); } gbvalue.value.integer_value[0] = cpu_to_le32(ucontrol->value.integer.value[0]); ret = gb_audio_gb_set_control(module->mgmt_connection, data->ctl_id, GB_AUDIO_INVALID_INDEX, &gbvalue); } exit: gb_pm_runtime_put_autosuspend(bundle); if (ret) dev_err_ratelimited(codec_dev, "%d:Error in %s for %s\n", ret, __func__, kcontrol->id.name); return ret; } #define SOC_DAPM_MIXER_GB(xname, kcount, data) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .count = kcount, .info = gbcodec_mixer_dapm_ctl_info, \ .get = gbcodec_mixer_dapm_ctl_get, .put = gbcodec_mixer_dapm_ctl_put, \ .private_value = (unsigned long)data} static int gbcodec_event_spk(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { /* Ensure GB speaker is connected */ return 0; } static int gbcodec_event_hp(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { /* Ensure GB module supports jack slot */ return 0; } static int gbcodec_event_int_mic(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { /* Ensure GB module supports jack slot */ return 0; } static int gbaudio_validate_kcontrol_count(struct gb_audio_widget *w) { int ret = 0; switch (w->type) { case snd_soc_dapm_spk: case snd_soc_dapm_hp: case snd_soc_dapm_mic: case snd_soc_dapm_output: case snd_soc_dapm_input: if (w->ncontrols) ret = -EINVAL; break; case snd_soc_dapm_switch: case snd_soc_dapm_mux: if (w->ncontrols != 1) ret = -EINVAL; break; default: break; } return ret; } static int gbcodec_enum_ctl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { int ret, ctl_id; struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); struct gbaudio_codec_info *gb = snd_soc_component_get_drvdata(comp); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; struct gb_audio_ctl_elem_value gbvalue; struct gbaudio_module_info *module; struct gb_bundle *bundle; module = find_gb_module(gb, kcontrol->id.name); if (!module) return -EINVAL; ctl_id = gbaudio_map_controlname(module, kcontrol->id.name); if (ctl_id < 0) return -EINVAL; bundle = to_gb_bundle(module->dev); ret = gb_pm_runtime_get_sync(bundle); if (ret) return ret; ret = gb_audio_gb_get_control(module->mgmt_connection, ctl_id, GB_AUDIO_INVALID_INDEX, &gbvalue); gb_pm_runtime_put_autosuspend(bundle); if (ret) { dev_err_ratelimited(comp->dev, "%d:Error in %s for %s\n", ret, __func__, kcontrol->id.name); return ret; } ucontrol->value.enumerated.item[0] = le32_to_cpu(gbvalue.value.enumerated_item[0]); if (e->shift_l != e->shift_r) ucontrol->value.enumerated.item[1] = le32_to_cpu(gbvalue.value.enumerated_item[1]); return 0; } static int gbcodec_enum_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { int ret, ctl_id; struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); struct gbaudio_codec_info *gb = snd_soc_component_get_drvdata(comp); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; struct gb_audio_ctl_elem_value gbvalue; struct gbaudio_module_info *module; struct gb_bundle *bundle; module = find_gb_module(gb, kcontrol->id.name); if (!module) return -EINVAL; ctl_id = gbaudio_map_controlname(module, kcontrol->id.name); if (ctl_id < 0) return -EINVAL; if (ucontrol->value.enumerated.item[0] > e->items - 1) return -EINVAL; gbvalue.value.enumerated_item[0] = cpu_to_le32(ucontrol->value.enumerated.item[0]); if (e->shift_l != e->shift_r) { if (ucontrol->value.enumerated.item[1] > e->items - 1) return -EINVAL; gbvalue.value.enumerated_item[1] = cpu_to_le32(ucontrol->value.enumerated.item[1]); } bundle = to_gb_bundle(module->dev); ret = gb_pm_runtime_get_sync(bundle); if (ret) return ret; ret = gb_audio_gb_set_control(module->mgmt_connection, ctl_id, GB_AUDIO_INVALID_INDEX, &gbvalue); gb_pm_runtime_put_autosuspend(bundle); if (ret) { dev_err_ratelimited(comp->dev, "%d:Error in %s for %s\n", ret, __func__, kcontrol->id.name); } return ret; } static int gbaudio_tplg_create_enum_kctl(struct gbaudio_module_info *gb, struct snd_kcontrol_new *kctl, struct gb_audio_control *ctl) { struct soc_enum *gbe; struct gb_audio_enumerated *gb_enum; int i; gbe = devm_kzalloc(gb->dev, sizeof(*gbe), GFP_KERNEL); if (!gbe) return -ENOMEM; gb_enum = &ctl->info.value.enumerated; /* since count=1, and reg is dummy */ gbe->items = le32_to_cpu(gb_enum->items); gbe->texts = gb_generate_enum_strings(gb, gb_enum); if (!gbe->texts) return -ENOMEM; /* debug enum info */ dev_dbg(gb->dev, "Max:%d, name_length:%d\n", gbe->items, le16_to_cpu(gb_enum->names_length)); for (i = 0; i < gbe->items; i++) dev_dbg(gb->dev, "src[%d]: %s\n", i, gbe->texts[i]); *kctl = (struct snd_kcontrol_new) SOC_ENUM_EXT(ctl->name, *gbe, gbcodec_enum_ctl_get, gbcodec_enum_ctl_put); return 0; } static int gbaudio_tplg_create_kcontrol(struct gbaudio_module_info *gb, struct snd_kcontrol_new *kctl, struct gb_audio_control *ctl) { int ret = 0; struct gbaudio_ctl_pvt *ctldata; switch (ctl->iface) { case (__force int)SNDRV_CTL_ELEM_IFACE_MIXER: switch (ctl->info.type) { case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED: ret = gbaudio_tplg_create_enum_kctl(gb, kctl, ctl); break; default: ctldata = devm_kzalloc(gb->dev, sizeof(struct gbaudio_ctl_pvt), GFP_KERNEL); if (!ctldata) return -ENOMEM; ctldata->ctl_id = ctl->id; ctldata->data_cport = le16_to_cpu(ctl->data_cport); ctldata->access = le32_to_cpu(ctl->access); ctldata->vcount = ctl->count_values; ctldata->info = &ctl->info; *kctl = (struct snd_kcontrol_new) SOC_MIXER_GB(ctl->name, ctl->count, ctldata); ctldata = NULL; break; } break; default: return -EINVAL; } dev_dbg(gb->dev, "%s:%d control created\n", ctl->name, ctl->id); return ret; } static int gbcodec_enum_dapm_ctl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { int ret, ctl_id; struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); struct snd_soc_dapm_widget *widget = wlist->widgets[0]; struct gbaudio_module_info *module; struct gb_audio_ctl_elem_value gbvalue; struct device *codec_dev = widget->dapm->dev; struct gbaudio_codec_info *gb = dev_get_drvdata(codec_dev); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; struct gb_bundle *bundle; module = find_gb_module(gb, kcontrol->id.name); if (!module) return -EINVAL; ctl_id = gbaudio_map_wcontrolname(module, kcontrol->id.name); if (ctl_id < 0) return -EINVAL; bundle = to_gb_bundle(module->dev); ret = gb_pm_runtime_get_sync(bundle); if (ret) return ret; ret = gb_audio_gb_get_control(module->mgmt_connection, ctl_id, GB_AUDIO_INVALID_INDEX, &gbvalue); gb_pm_runtime_put_autosuspend(bundle); if (ret) { dev_err_ratelimited(codec_dev, "%d:Error in %s for %s\n", ret, __func__, kcontrol->id.name); return ret; } ucontrol->value.enumerated.item[0] = le32_to_cpu(gbvalue.value.enumerated_item[0]); if (e->shift_l != e->shift_r) ucontrol->value.enumerated.item[1] = le32_to_cpu(gbvalue.value.enumerated_item[1]); return 0; } static int gbcodec_enum_dapm_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { int ret, wi, ctl_id; unsigned int val, mux, change; struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); struct snd_soc_dapm_widget *widget = wlist->widgets[0]; struct gb_audio_ctl_elem_value gbvalue; struct gbaudio_module_info *module; struct device *codec_dev = widget->dapm->dev; struct gbaudio_codec_info *gb = dev_get_drvdata(codec_dev); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; struct gb_bundle *bundle; if (ucontrol->value.enumerated.item[0] > e->items - 1) return -EINVAL; module = find_gb_module(gb, kcontrol->id.name); if (!module) return -EINVAL; ctl_id = gbaudio_map_wcontrolname(module, kcontrol->id.name); if (ctl_id < 0) return -EINVAL; change = 0; bundle = to_gb_bundle(module->dev); ret = gb_pm_runtime_get_sync(bundle); if (ret) return ret; ret = gb_audio_gb_get_control(module->mgmt_connection, ctl_id, GB_AUDIO_INVALID_INDEX, &gbvalue); gb_pm_runtime_put_autosuspend(bundle); if (ret) { dev_err_ratelimited(codec_dev, "%d:Error in %s for %s\n", ret, __func__, kcontrol->id.name); return ret; } mux = ucontrol->value.enumerated.item[0]; val = mux << e->shift_l; if (le32_to_cpu(gbvalue.value.enumerated_item[0]) != ucontrol->value.enumerated.item[0]) { change = 1; gbvalue.value.enumerated_item[0] = cpu_to_le32(ucontrol->value.enumerated.item[0]); } if (e->shift_l != e->shift_r) { if (ucontrol->value.enumerated.item[1] > e->items - 1) return -EINVAL; val |= ucontrol->value.enumerated.item[1] << e->shift_r; if (le32_to_cpu(gbvalue.value.enumerated_item[1]) != ucontrol->value.enumerated.item[1]) { change = 1; gbvalue.value.enumerated_item[1] = cpu_to_le32(ucontrol->value.enumerated.item[1]); } } if (change) { ret = gb_pm_runtime_get_sync(bundle); if (ret) return ret; ret = gb_audio_gb_set_control(module->mgmt_connection, ctl_id, GB_AUDIO_INVALID_INDEX, &gbvalue); gb_pm_runtime_put_autosuspend(bundle); if (ret) { dev_err_ratelimited(codec_dev, "%d:Error in %s for %s\n", ret, __func__, kcontrol->id.name); } for (wi = 0; wi < wlist->num_widgets; wi++) { widget = wlist->widgets[wi]; snd_soc_dapm_mux_update_power(widget->dapm, kcontrol, val, e, NULL); } } return change; } static int gbaudio_tplg_create_enum_ctl(struct gbaudio_module_info *gb, struct snd_kcontrol_new *kctl, struct gb_audio_control *ctl) { struct soc_enum *gbe; struct gb_audio_enumerated *gb_enum; int i; gbe = devm_kzalloc(gb->dev, sizeof(*gbe), GFP_KERNEL); if (!gbe) return -ENOMEM; gb_enum = &ctl->info.value.enumerated; /* since count=1, and reg is dummy */ gbe->items = le32_to_cpu(gb_enum->items); gbe->texts = gb_generate_enum_strings(gb, gb_enum); if (!gbe->texts) return -ENOMEM; /* debug enum info */ dev_dbg(gb->dev, "Max:%d, name_length:%d\n", gbe->items, le16_to_cpu(gb_enum->names_length)); for (i = 0; i < gbe->items; i++) dev_dbg(gb->dev, "src[%d]: %s\n", i, gbe->texts[i]); *kctl = (struct snd_kcontrol_new) SOC_DAPM_ENUM_EXT(ctl->name, *gbe, gbcodec_enum_dapm_ctl_get, gbcodec_enum_dapm_ctl_put); return 0; } static int gbaudio_tplg_create_mixer_ctl(struct gbaudio_module_info *gb, struct snd_kcontrol_new *kctl, struct gb_audio_control *ctl) { struct gbaudio_ctl_pvt *ctldata; ctldata = devm_kzalloc(gb->dev, sizeof(struct gbaudio_ctl_pvt), GFP_KERNEL); if (!ctldata) return -ENOMEM; ctldata->ctl_id = ctl->id; ctldata->data_cport = le16_to_cpu(ctl->data_cport); ctldata->access = le32_to_cpu(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 (__force int)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 device *codec_dev = w->dapm->dev; struct gbaudio_codec_info *gbcodec = dev_get_drvdata(codec_dev); struct gbaudio_module_info *module; struct gb_bundle *bundle; dev_dbg(codec_dev, "%s %s %d\n", __func__, w->name, event); /* Find relevant module */ module = find_gb_module(gbcodec, w->name); if (!module) return -EINVAL; /* map name to widget id */ wid = gbaudio_map_widgetname(module, w->name); if (wid < 0) { dev_err(codec_dev, "Invalid widget name:%s\n", w->name); return -EINVAL; } bundle = to_gb_bundle(module->dev); ret = gb_pm_runtime_get_sync(bundle); if (ret) return ret; switch (event) { case SND_SOC_DAPM_PRE_PMU: ret = gb_audio_gb_enable_widget(module->mgmt_connection, wid); if (!ret) ret = gbaudio_module_update(gbcodec, w, module, 1); break; case SND_SOC_DAPM_POST_PMD: ret = gb_audio_gb_disable_widget(module->mgmt_connection, wid); if (!ret) ret = gbaudio_module_update(gbcodec, w, module, 0); break; } if (ret) dev_err_ratelimited(codec_dev, "%d: widget, event:%d failed:%d\n", wid, event, ret); gb_pm_runtime_put_autosuspend(bundle); return ret; } static const struct snd_soc_dapm_widget gbaudio_widgets[] = { [snd_soc_dapm_spk] = SND_SOC_DAPM_SPK(NULL, gbcodec_event_spk), [snd_soc_dapm_hp] = SND_SOC_DAPM_HP(NULL, gbcodec_event_hp), [snd_soc_dapm_mic] = SND_SOC_DAPM_MIC(NULL, gbcodec_event_int_mic), [snd_soc_dapm_output] = SND_SOC_DAPM_OUTPUT(NULL), [snd_soc_dapm_input] = SND_SOC_DAPM_INPUT(NULL), [snd_soc_dapm_switch] = SND_SOC_DAPM_SWITCH_E(NULL, SND_SOC_NOPM, 0, 0, NULL, gbaudio_widget_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), [snd_soc_dapm_pga] = SND_SOC_DAPM_PGA_E(NULL, SND_SOC_NOPM, 0, 0, NULL, 0, gbaudio_widget_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), [snd_soc_dapm_mixer] = SND_SOC_DAPM_MIXER_E(NULL, SND_SOC_NOPM, 0, 0, NULL, 0, gbaudio_widget_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), [snd_soc_dapm_mux] = SND_SOC_DAPM_MUX_E(NULL, SND_SOC_NOPM, 0, 0, NULL, gbaudio_widget_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), [snd_soc_dapm_aif_in] = SND_SOC_DAPM_AIF_IN_E(NULL, NULL, 0, SND_SOC_NOPM, 0, 0, gbaudio_widget_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), [snd_soc_dapm_aif_out] = SND_SOC_DAPM_AIF_OUT_E(NULL, NULL, 0, SND_SOC_NOPM, 0, 0, gbaudio_widget_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), }; static int gbaudio_tplg_create_widget(struct gbaudio_module_info *module, struct snd_soc_dapm_widget *dw, struct gb_audio_widget *w, int *w_size) { int i, ret, csize; struct snd_kcontrol_new *widget_kctls; struct gb_audio_control *curr; struct gbaudio_control *control, *_control; size_t size; char temp_name[NAME_SIZE]; ret = gbaudio_validate_kcontrol_count(w); if (ret) { dev_err(module->dev, "Invalid kcontrol count=%d for %s\n", w->ncontrols, w->name); return ret; } /* allocate memory for kcontrol */ if (w->ncontrols) { size = sizeof(struct snd_kcontrol_new) * w->ncontrols; widget_kctls = devm_kzalloc(module->dev, size, GFP_KERNEL); if (!widget_kctls) return -ENOMEM; } *w_size = sizeof(struct gb_audio_widget); /* create relevant kcontrols */ curr = w->ctl; for (i = 0; i < w->ncontrols; i++) { ret = gbaudio_tplg_create_wcontrol(module, &widget_kctls[i], curr); if (ret) { dev_err(module->dev, "%s:%d type widget_ctl not supported\n", curr->name, curr->iface); goto error; } control = devm_kzalloc(module->dev, sizeof(struct gbaudio_control), GFP_KERNEL); if (!control) { ret = -ENOMEM; goto error; } control->id = curr->id; control->name = curr->name; control->wname = w->name; if (curr->info.type == GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED) { struct gb_audio_enumerated *gbenum = &curr->info.value.enumerated; csize = offsetof(struct gb_audio_control, info); csize += offsetof(struct gb_audio_ctl_elem_info, value); csize += offsetof(struct gb_audio_enumerated, names); csize += le16_to_cpu(gbenum->names_length); control->texts = (const char * const *) gb_generate_enum_strings(module, gbenum); if (!control->texts) { ret = -ENOMEM; goto error; } control->items = le32_to_cpu(gbenum->items); } else { csize = sizeof(struct gb_audio_control); } *w_size += csize; curr = (void *)curr + csize; list_add(&control->list, &module->widget_ctl_list); dev_dbg(module->dev, "%s: control of type %d created\n", widget_kctls[i].name, widget_kctls[i].iface); } /* Prefix dev_id to widget control_name */ strscpy(temp_name, w->name, sizeof(temp_name)); snprintf(w->name, sizeof(w->name), "GB %d %s", module->dev_id, temp_name); switch (w->type) { case snd_soc_dapm_spk: *dw = gbaudio_widgets[w->type]; module->op_devices |= GBAUDIO_DEVICE_OUT_SPEAKER; break; case snd_soc_dapm_hp: *dw = gbaudio_widgets[w->type]; 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 = gbaudio_widgets[w->type]; module->ip_devices |= GBAUDIO_DEVICE_IN_BUILTIN_MIC; break; case snd_soc_dapm_output: case snd_soc_dapm_input: case snd_soc_dapm_switch: case snd_soc_dapm_pga: case snd_soc_dapm_mixer: case snd_soc_dapm_mux: *dw = gbaudio_widgets[w->type]; break; case snd_soc_dapm_aif_in: case snd_soc_dapm_aif_out: *dw = gbaudio_widgets[w->type]; dw->sname = w->sname; break; default: ret = -EINVAL; goto error; } dw->name = w->name; dev_dbg(module->dev, "%s: widget of type %d created\n", dw->name, dw->id); return 0; error: list_for_each_entry_safe(control, _control, &module->widget_ctl_list, list) { list_del(&control->list); devm_kfree(module->dev, control); } return ret; } static int gbaudio_tplg_process_kcontrols(struct gbaudio_module_info *module, struct gb_audio_control *controls) { int i, csize, ret; struct snd_kcontrol_new *dapm_kctls; struct gb_audio_control *curr; struct gbaudio_control *control, *_control; size_t size; char temp_name[NAME_SIZE]; size = sizeof(struct snd_kcontrol_new) * module->num_controls; dapm_kctls = devm_kzalloc(module->dev, size, GFP_KERNEL); if (!dapm_kctls) return -ENOMEM; curr = controls; for (i = 0; i < module->num_controls; i++) { ret = gbaudio_tplg_create_kcontrol(module, &dapm_kctls[i], curr); if (ret) { dev_err(module->dev, "%s:%d type not supported\n", curr->name, curr->iface); goto error; } control = devm_kzalloc(module->dev, sizeof(struct gbaudio_control), GFP_KERNEL); if (!control) { ret = -ENOMEM; goto error; } control->id = curr->id; /* Prefix dev_id to widget_name */ strscpy(temp_name, curr->name, sizeof(temp_name)); snprintf(curr->name, sizeof(curr->name), "GB %d %s", module->dev_id, temp_name); control->name = curr->name; if (curr->info.type == GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED) { struct gb_audio_enumerated *gbenum = &curr->info.value.enumerated; csize = offsetof(struct gb_audio_control, info); csize += offsetof(struct gb_audio_ctl_elem_info, value); csize += offsetof(struct gb_audio_enumerated, names); csize += le16_to_cpu(gbenum->names_length); control->texts = (const char * const *) gb_generate_enum_strings(module, gbenum); if (!control->texts) { ret = -ENOMEM; goto error; } control->items = le32_to_cpu(gbenum->items); } else { csize = sizeof(struct gb_audio_control); } list_add(&control->list, &module->ctl_list); dev_dbg(module->dev, "%d:%s created of type %d\n", curr->id, curr->name, curr->info.type); curr = (void *)curr + csize; } module->controls = dapm_kctls; return 0; error: list_for_each_entry_safe(control, _control, &module->ctl_list, list) { list_del(&control->list); devm_kfree(module->dev, control); } devm_kfree(module->dev, dapm_kctls); return ret; } static int gbaudio_tplg_process_widgets(struct gbaudio_module_info *module, struct gb_audio_widget *widgets) { int i, ret, w_size; struct snd_soc_dapm_widget *dapm_widgets; struct gb_audio_widget *curr; struct gbaudio_widget *widget, *_widget; size_t size; size = sizeof(struct snd_soc_dapm_widget) * module->num_dapm_widgets; dapm_widgets = devm_kzalloc(module->dev, size, GFP_KERNEL); if (!dapm_widgets) return -ENOMEM; curr = widgets; for (i = 0; i < module->num_dapm_widgets; i++) { ret = gbaudio_tplg_create_widget(module, &dapm_widgets[i], curr, &w_size); if (ret) { dev_err(module->dev, "%s:%d type not supported\n", curr->name, curr->type); goto error; } widget = devm_kzalloc(module->dev, sizeof(struct gbaudio_widget), GFP_KERNEL); if (!widget) { ret = -ENOMEM; goto error; } widget->id = curr->id; widget->name = curr->name; list_add(&widget->list, &module->widget_list); curr = (void *)curr + w_size; } module->dapm_widgets = dapm_widgets; return 0; error: list_for_each_entry_safe(widget, _widget, &module->widget_list, list) { list_del(&widget->list); devm_kfree(module->dev, widget); } devm_kfree(module->dev, dapm_widgets); return ret; } static int gbaudio_tplg_process_routes(struct gbaudio_module_info *module, struct gb_audio_route *routes) { int i, ret; struct snd_soc_dapm_route *dapm_routes; struct gb_audio_route *curr; size_t size; size = sizeof(struct snd_soc_dapm_route) * module->num_dapm_routes; dapm_routes = devm_kzalloc(module->dev, size, GFP_KERNEL); if (!dapm_routes) return -ENOMEM; module->dapm_routes = dapm_routes; curr = routes; for (i = 0; i < module->num_dapm_routes; i++) { dapm_routes->sink = gbaudio_map_widgetid(module, curr->destination_id); if (!dapm_routes->sink) { dev_err(module->dev, "%d:%d:%d:%d - Invalid sink\n", curr->source_id, curr->destination_id, curr->control_id, curr->index); ret = -EINVAL; goto error; } dapm_routes->source = gbaudio_map_widgetid(module, curr->source_id); if (!dapm_routes->source) { dev_err(module->dev, "%d:%d:%d:%d - Invalid source\n", curr->source_id, curr->destination_id, curr->control_id, curr->index); ret = -EINVAL; goto error; } dapm_routes->control = gbaudio_map_controlid(module, curr->control_id, curr->index); if ((curr->control_id != GBAUDIO_INVALID_ID) && !dapm_routes->control) { dev_err(module->dev, "%d:%d:%d:%d - Invalid control\n", curr->source_id, curr->destination_id, curr->control_id, curr->index); ret = -EINVAL; goto error; } dev_dbg(module->dev, "Route {%s, %s, %s}\n", dapm_routes->sink, (dapm_routes->control) ? dapm_routes->control : "NULL", dapm_routes->source); dapm_routes++; curr++; } return 0; error: devm_kfree(module->dev, module->dapm_routes); return ret; } static int gbaudio_tplg_process_header(struct gbaudio_module_info *module, struct gb_audio_topology *tplg_data) { /* fetch no. of kcontrols, widgets & routes */ module->num_controls = tplg_data->num_controls; module->num_dapm_widgets = tplg_data->num_widgets; module->num_dapm_routes = tplg_data->num_routes; /* update block offset */ module->dai_offset = (unsigned long)&tplg_data->data; module->control_offset = module->dai_offset + le32_to_cpu(tplg_data->size_dais); module->widget_offset = module->control_offset + le32_to_cpu(tplg_data->size_controls); module->route_offset = module->widget_offset + le32_to_cpu(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; unsigned int jack_type; if (!tplg_data) return -EINVAL; ret = gbaudio_tplg_process_header(module, tplg_data); if (ret) { dev_err(module->dev, "%d: Error in parsing topology header\n", ret); return ret; } /* process control */ controls = (struct gb_audio_control *)module->control_offset; ret = gbaudio_tplg_process_kcontrols(module, controls); if (ret) { dev_err(module->dev, "%d: Error in parsing controls data\n", ret); return ret; } dev_dbg(module->dev, "Control parsing finished\n"); /* process widgets */ widgets = (struct gb_audio_widget *)module->widget_offset; ret = gbaudio_tplg_process_widgets(module, widgets); if (ret) { dev_err(module->dev, "%d: Error in parsing widgets data\n", ret); return ret; } dev_dbg(module->dev, "Widget parsing finished\n"); /* process route */ routes = (struct gb_audio_route *)module->route_offset; ret = gbaudio_tplg_process_routes(module, routes); if (ret) { dev_err(module->dev, "%d: Error in parsing routes data\n", ret); return ret; } dev_dbg(module->dev, "Route parsing finished\n"); /* parse jack capabilities */ jack_type = le32_to_cpu(tplg_data->jack_type); if (jack_type) { module->jack_mask = jack_type & GBCODEC_JACK_MASK; module->button_mask = jack_type & GBCODEC_JACK_BUTTON_MASK; } return ret; } void gbaudio_tplg_release(struct gbaudio_module_info *module) { struct gbaudio_control *control, *_control; struct gbaudio_widget *widget, *_widget; if (!module->topology) return; /* release kcontrols */ list_for_each_entry_safe(control, _control, &module->ctl_list, list) { list_del(&control->list); devm_kfree(module->dev, control); } if (module->controls) devm_kfree(module->dev, module->controls); /* release widget controls */ list_for_each_entry_safe(control, _control, &module->widget_ctl_list, list) { list_del(&control->list); devm_kfree(module->dev, control); } /* release widgets */ list_for_each_entry_safe(widget, _widget, &module->widget_list, list) { list_del(&widget->list); devm_kfree(module->dev, widget); } if (module->dapm_widgets) devm_kfree(module->dev, module->dapm_widgets); /* release routes */ if (module->dapm_routes) devm_kfree(module->dev, module->dapm_routes); }