linux-stable/drivers/staging/greybus/audio_topology.c
Jiasheng Jiang 2e81948177 staging: greybus: audio: Check null pointer
As the possible alloc failure of devm_kcalloc(), it could return null
pointer.
Therefore, 'strings' should be checked and return NULL if alloc fails to
prevent the dereference of the NULL pointer.
Also, the caller should also deal with the return value of the
gb_generate_enum_strings() and return -ENOMEM if returns NULL.
Moreover, because the memory allocated with devm_kzalloc() will be
freed automatically when the last reference to the device is dropped,
the 'gbe' in gbaudio_tplg_create_enum_kctl() and
gbaudio_tplg_create_enum_ctl() do not need to free manually.
But the 'control' in gbaudio_tplg_create_widget() and
gbaudio_tplg_process_kcontrols() has a specially error handle to
cleanup.
So it should be better to cleanup 'control' when fails.

Fixes: e65579e335 ("greybus: audio: topology: Enable enumerated control support")
Reviewed-by: Alex Elder <elder@linaro.org>
Signed-off-by: Jiasheng Jiang <jiasheng@iscas.ac.cn>
Link: https://lore.kernel.org/r/20220104150628.1987906-1-jiasheng@iscas.ac.cn
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2022-01-06 14:46:11 +01:00

1453 lines
39 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Greybus audio driver
* Copyright 2015-2016 Google Inc.
* Copyright 2015-2016 Linaro Ltd.
*/
#include <linux/greybus.h>
#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;
unsigned int mask;
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;
mask = e->mask << 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;
mask |= e->mask << 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);
}