2016-01-13 14:07:51 -07:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
};
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
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);
|
2016-06-03 06:15:29 -06:00
|
|
|
dev_warn(codec->dev, "%s: module#%d missing in codec list\n", name,
|
|
|
|
dev_id);
|
2016-03-29 12:01:41 -06:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *gbaudio_map_controlid(struct gbaudio_module_info *module,
|
2016-06-30 07:15:37 -06:00
|
|
|
__u8 control_id, __u8 index)
|
2016-01-13 14:07:51 -07:00
|
|
|
{
|
|
|
|
struct gbaudio_control *control;
|
|
|
|
|
|
|
|
if (control_id == GBAUDIO_INVALID_ID)
|
|
|
|
return NULL;
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
list_for_each_entry(control, &module->ctl_list, list) {
|
2016-01-13 14:07:51 -07:00
|
|
|
if (control->id == control_id) {
|
|
|
|
if (index == GBAUDIO_INVALID_ID)
|
|
|
|
return control->name;
|
2016-08-16 01:06:50 -06:00
|
|
|
if (index >= control->items)
|
|
|
|
return NULL;
|
2016-01-13 14:07:51 -07:00
|
|
|
return control->texts[index];
|
|
|
|
}
|
|
|
|
}
|
2016-03-29 12:01:41 -06:00
|
|
|
list_for_each_entry(control, &module->widget_ctl_list, list) {
|
2016-01-13 14:07:51 -07:00
|
|
|
if (control->id == control_id) {
|
|
|
|
if (index == GBAUDIO_INVALID_ID)
|
|
|
|
return control->name;
|
2016-08-16 01:06:50 -06:00
|
|
|
if (index >= control->items)
|
|
|
|
return NULL;
|
2016-01-13 14:07:51 -07:00
|
|
|
return control->texts[index];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-07-05 16:09:20 -06:00
|
|
|
static int gbaudio_map_wcontrolname(struct gbaudio_module_info *module,
|
2016-06-30 07:15:37 -06:00
|
|
|
const char *name)
|
2016-07-05 16:09:20 -06:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
static int gbaudio_map_widgetname(struct gbaudio_module_info *module,
|
2016-06-30 07:15:37 -06:00
|
|
|
const char *name)
|
2016-01-13 14:07:51 -07:00
|
|
|
{
|
|
|
|
struct gbaudio_widget *widget;
|
2016-10-04 14:06:10 -06:00
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
list_for_each_entry(widget, &module->widget_list, list) {
|
|
|
|
if (!strncmp(widget->name, name, NAME_SIZE))
|
2016-01-13 14:07:51 -07:00
|
|
|
return widget->id;
|
|
|
|
}
|
2016-06-30 07:15:35 -06:00
|
|
|
dev_warn(module->dev, "%s: missing in modules widgets list\n", name);
|
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
static const char *gbaudio_map_widgetid(struct gbaudio_module_info *module,
|
2016-06-30 07:15:37 -06:00
|
|
|
__u8 widget_id)
|
2016-01-13 14:07:51 -07:00
|
|
|
{
|
|
|
|
struct gbaudio_widget *widget;
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
list_for_each_entry(widget, &module->widget_list, list) {
|
2016-01-13 14:07:51 -07:00
|
|
|
if (widget->id == widget_id)
|
|
|
|
return widget->name;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-07-16 10:44:28 -06:00
|
|
|
static const char **gb_generate_enum_strings(struct gbaudio_module_info *gb,
|
|
|
|
struct gb_audio_enumerated *gbenum)
|
2016-06-30 07:15:37 -06:00
|
|
|
{
|
|
|
|
const char **strings;
|
|
|
|
int i;
|
|
|
|
__u8 *data;
|
|
|
|
|
|
|
|
strings = devm_kzalloc(gb->dev, sizeof(char *) * gbenum->items,
|
|
|
|
GFP_KERNEL);
|
|
|
|
data = gbenum->names;
|
|
|
|
|
|
|
|
for (i = 0; i < gbenum->items; i++) {
|
|
|
|
strings[i] = (const char *)data;
|
|
|
|
while (*data != '\0')
|
|
|
|
data++;
|
|
|
|
data++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings;
|
|
|
|
}
|
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
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;
|
2016-03-29 12:01:41 -06:00
|
|
|
struct gbaudio_module_info *module;
|
2016-01-13 14:07:51 -07:00
|
|
|
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
2016-01-28 08:45:39 -07:00
|
|
|
struct gbaudio_codec_info *gbcodec = snd_soc_codec_get_drvdata(codec);
|
2016-01-13 14:07:51 -07:00
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
|
2016-01-13 14:07:51 -07:00
|
|
|
data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
|
|
|
|
info = (struct gb_audio_ctl_elem_info *)data->info;
|
|
|
|
|
|
|
|
if (!info) {
|
2016-09-24 11:36:26 -06:00
|
|
|
dev_err(codec->dev, "NULL info for %s\n", uinfo->id.name);
|
2016-01-13 14:07:51 -07:00
|
|
|
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;
|
2016-03-29 12:01:41 -06:00
|
|
|
module = find_gb_module(gbcodec, kcontrol->id.name);
|
|
|
|
if (!module)
|
|
|
|
return -EINVAL;
|
|
|
|
name = gbaudio_map_controlid(module, data->ctl_id,
|
2016-01-13 14:07:51 -07:00
|
|
|
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;
|
2016-03-29 12:01:41 -06:00
|
|
|
struct gbaudio_module_info *module;
|
2016-01-13 14:07:51 -07:00
|
|
|
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
2016-01-28 08:45:39 -07:00
|
|
|
struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
|
2016-07-14 14:13:00 -06:00
|
|
|
struct gb_bundle *bundle;
|
2016-01-13 14:07:51 -07:00
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
|
|
|
|
module = find_gb_module(gb, kcontrol->id.name);
|
|
|
|
if (!module)
|
|
|
|
return -EINVAL;
|
2016-01-28 08:45:40 -07:00
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
|
|
|
|
info = (struct gb_audio_ctl_elem_info *)data->info;
|
2016-07-14 14:13:00 -06:00
|
|
|
bundle = to_gb_bundle(module->dev);
|
|
|
|
|
|
|
|
ret = gb_pm_runtime_get_sync(bundle);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2016-01-13 14:07:51 -07:00
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
ret = gb_audio_gb_get_control(module->mgmt_connection, data->ctl_id,
|
2016-01-13 14:07:51 -07:00
|
|
|
GB_AUDIO_INVALID_INDEX, &gbvalue);
|
2016-07-14 14:13:00 -06:00
|
|
|
|
|
|
|
gb_pm_runtime_put_autosuspend(bundle);
|
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
if (ret) {
|
2016-06-10 01:29:11 -06:00
|
|
|
dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
|
|
|
|
__func__, kcontrol->id.name);
|
2016-01-13 14:07:51 -07:00
|
|
|
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;
|
2016-03-29 12:01:41 -06:00
|
|
|
struct gbaudio_module_info *module;
|
2016-01-13 14:07:51 -07:00
|
|
|
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
2016-01-28 08:45:39 -07:00
|
|
|
struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
|
2016-07-14 14:13:00 -06:00
|
|
|
struct gb_bundle *bundle;
|
2016-01-13 14:07:51 -07:00
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
|
|
|
|
module = find_gb_module(gb, kcontrol->id.name);
|
|
|
|
if (!module)
|
|
|
|
return -EINVAL;
|
2016-01-28 08:45:40 -07:00
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
|
|
|
|
info = (struct gb_audio_ctl_elem_info *)data->info;
|
2016-07-14 14:13:00 -06:00
|
|
|
bundle = to_gb_bundle(module->dev);
|
2016-01-13 14:07:51 -07:00
|
|
|
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2016-07-14 14:13:00 -06:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = gb_pm_runtime_get_sync(bundle);
|
2016-01-13 14:07:51 -07:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
ret = gb_audio_gb_set_control(module->mgmt_connection, data->ctl_id,
|
2016-01-13 14:07:51 -07:00
|
|
|
GB_AUDIO_INVALID_INDEX, &gbvalue);
|
2016-07-14 14:13:00 -06:00
|
|
|
|
|
|
|
gb_pm_runtime_put_autosuspend(bundle);
|
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
if (ret) {
|
2016-06-10 01:29:11 -06:00
|
|
|
dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
|
|
|
|
__func__, kcontrol->id.name);
|
2016-01-13 14:07:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2016-03-29 12:01:41 -06:00
|
|
|
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;
|
2016-01-13 14:07:51 -07:00
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
|
2016-01-13 14:07:51 -07:00
|
|
|
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;
|
2016-03-29 12:01:41 -06:00
|
|
|
struct gbaudio_module_info *module;
|
2016-01-13 14:07:51 -07:00
|
|
|
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;
|
2016-01-28 08:45:39 -07:00
|
|
|
struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
|
2016-07-14 14:13:00 -06:00
|
|
|
struct gb_bundle *bundle;
|
2016-01-13 14:07:51 -07:00
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
|
|
|
|
module = find_gb_module(gb, kcontrol->id.name);
|
|
|
|
if (!module)
|
|
|
|
return -EINVAL;
|
2016-01-28 08:45:40 -07:00
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
|
|
|
|
info = (struct gb_audio_ctl_elem_info *)data->info;
|
2016-07-14 14:13:00 -06:00
|
|
|
bundle = to_gb_bundle(module->dev);
|
2016-01-13 14:07:51 -07:00
|
|
|
|
|
|
|
if (data->vcount == 2)
|
|
|
|
dev_warn(widget->dapm->dev,
|
|
|
|
"GB: Control '%s' is stereo, which is not supported\n",
|
|
|
|
kcontrol->id.name);
|
|
|
|
|
2016-07-14 14:13:00 -06:00
|
|
|
ret = gb_pm_runtime_get_sync(bundle);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
ret = gb_audio_gb_get_control(module->mgmt_connection, data->ctl_id,
|
2016-01-13 14:07:51 -07:00
|
|
|
GB_AUDIO_INVALID_INDEX, &gbvalue);
|
2016-07-14 14:13:00 -06:00
|
|
|
|
|
|
|
gb_pm_runtime_put_autosuspend(bundle);
|
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
if (ret) {
|
2016-06-10 01:29:11 -06:00
|
|
|
dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
|
|
|
|
__func__, kcontrol->id.name);
|
2016-01-13 14:07:51 -07:00
|
|
|
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;
|
2016-03-29 12:01:41 -06:00
|
|
|
struct gbaudio_module_info *module;
|
2016-01-13 14:07:51 -07:00
|
|
|
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;
|
2016-01-28 08:45:39 -07:00
|
|
|
struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
|
2016-07-14 14:13:00 -06:00
|
|
|
struct gb_bundle *bundle;
|
2016-01-13 14:07:51 -07:00
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
|
|
|
|
module = find_gb_module(gb, kcontrol->id.name);
|
|
|
|
if (!module)
|
|
|
|
return -EINVAL;
|
2016-01-28 08:45:40 -07:00
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
|
|
|
|
info = (struct gb_audio_ctl_elem_info *)data->info;
|
2016-07-14 14:13:00 -06:00
|
|
|
bundle = to_gb_bundle(module->dev);
|
2016-01-13 14:07:51 -07:00
|
|
|
|
|
|
|
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;
|
2016-09-20 23:03:24 -06:00
|
|
|
val = ucontrol->value.integer.value[0] & mask;
|
2016-01-13 14:07:51 -07:00
|
|
|
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];
|
2016-07-14 14:13:00 -06:00
|
|
|
|
|
|
|
ret = gb_pm_runtime_get_sync(bundle);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
ret = gb_audio_gb_set_control(module->mgmt_connection,
|
2016-01-13 14:07:51 -07:00
|
|
|
data->ctl_id,
|
|
|
|
GB_AUDIO_INVALID_INDEX, &gbvalue);
|
2016-07-14 14:13:00 -06:00
|
|
|
|
|
|
|
gb_pm_runtime_put_autosuspend(bundle);
|
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
if (ret) {
|
2016-06-10 01:29:11 -06:00
|
|
|
dev_err_ratelimited(codec->dev,
|
|
|
|
"%d:Error in %s for %s\n", ret,
|
|
|
|
__func__, kcontrol->id.name);
|
2016-09-24 11:36:26 -06:00
|
|
|
return ret;
|
2016-01-13 14:07:51 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-24 11:36:26 -06:00
|
|
|
return 0;
|
2016-01-13 14:07:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#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;
|
|
|
|
}
|
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
static int gbcodec_enum_ctl_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
int ret, ctl_id;
|
|
|
|
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
|
|
|
struct gb_audio_ctl_elem_value gbvalue;
|
|
|
|
struct gbaudio_module_info *module;
|
|
|
|
struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
|
2016-07-25 17:29:20 -06:00
|
|
|
struct gb_bundle *bundle;
|
2016-06-30 07:15:37 -06:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2016-07-25 17:29:20 -06:00
|
|
|
bundle = to_gb_bundle(module->dev);
|
|
|
|
|
|
|
|
ret = gb_pm_runtime_get_sync(bundle);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
ret = gb_audio_gb_get_control(module->mgmt_connection, ctl_id,
|
|
|
|
GB_AUDIO_INVALID_INDEX, &gbvalue);
|
2016-07-25 17:29:20 -06:00
|
|
|
|
|
|
|
gb_pm_runtime_put_autosuspend(bundle);
|
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
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] = gbvalue.value.enumerated_item[0];
|
|
|
|
if (e->shift_l != e->shift_r)
|
|
|
|
ucontrol->value.enumerated.item[1] =
|
|
|
|
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_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
|
|
|
struct gb_audio_ctl_elem_value gbvalue;
|
|
|
|
struct gbaudio_module_info *module;
|
|
|
|
struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
|
2016-07-25 17:29:20 -06:00
|
|
|
struct gb_bundle *bundle;
|
2016-06-30 07:15:37 -06:00
|
|
|
|
|
|
|
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->max - 1)
|
|
|
|
return -EINVAL;
|
|
|
|
gbvalue.value.enumerated_item[0] = ucontrol->value.enumerated.item[0];
|
|
|
|
|
|
|
|
if (e->shift_l != e->shift_r) {
|
|
|
|
if (ucontrol->value.enumerated.item[1] > e->max - 1)
|
|
|
|
return -EINVAL;
|
|
|
|
gbvalue.value.enumerated_item[1] =
|
|
|
|
ucontrol->value.enumerated.item[1];
|
|
|
|
}
|
|
|
|
|
2016-07-25 17:29:20 -06:00
|
|
|
bundle = to_gb_bundle(module->dev);
|
|
|
|
|
|
|
|
ret = gb_pm_runtime_get_sync(bundle);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
ret = gb_audio_gb_set_control(module->mgmt_connection, ctl_id,
|
|
|
|
GB_AUDIO_INVALID_INDEX, &gbvalue);
|
2016-07-25 17:29:20 -06:00
|
|
|
|
|
|
|
gb_pm_runtime_put_autosuspend(bundle);
|
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
if (ret) {
|
|
|
|
dev_err_ratelimited(codec->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->max = gb_enum->items;
|
|
|
|
gbe->texts = gb_generate_enum_strings(gb, gb_enum);
|
|
|
|
|
|
|
|
/* debug enum info */
|
|
|
|
dev_dbg(gb->dev, "Max:%d, name_length:%d\n", gb_enum->items,
|
|
|
|
gb_enum->names_length);
|
|
|
|
for (i = 0; i < gb_enum->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;
|
|
|
|
}
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
static int gbaudio_tplg_create_kcontrol(struct gbaudio_module_info *gb,
|
2016-01-13 14:07:51 -07:00
|
|
|
struct snd_kcontrol_new *kctl,
|
|
|
|
struct gb_audio_control *ctl)
|
|
|
|
{
|
2016-06-30 07:15:37 -06:00
|
|
|
int ret = 0;
|
2016-01-13 14:07:51 -07:00
|
|
|
struct gbaudio_ctl_pvt *ctldata;
|
|
|
|
|
|
|
|
switch (ctl->iface) {
|
|
|
|
case SNDRV_CTL_ELEM_IFACE_MIXER:
|
2016-06-30 07:15:37 -06:00
|
|
|
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 = 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;
|
|
|
|
}
|
2016-01-13 14:07:51 -07:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_dbg(gb->dev, "%s:%d control created\n", ctl->name, ctl->id);
|
2016-06-30 07:15:37 -06:00
|
|
|
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 snd_soc_codec *codec = widget->codec;
|
|
|
|
struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
|
|
|
|
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
2016-07-25 17:29:20 -06:00
|
|
|
struct gb_bundle *bundle;
|
2016-06-30 07:15:37 -06:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2016-07-25 17:29:20 -06:00
|
|
|
bundle = to_gb_bundle(module->dev);
|
|
|
|
|
|
|
|
ret = gb_pm_runtime_get_sync(bundle);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
ret = gb_audio_gb_get_control(module->mgmt_connection, ctl_id,
|
|
|
|
GB_AUDIO_INVALID_INDEX, &gbvalue);
|
2016-07-25 17:29:20 -06:00
|
|
|
|
|
|
|
gb_pm_runtime_put_autosuspend(bundle);
|
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
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] = gbvalue.value.enumerated_item[0];
|
|
|
|
if (e->shift_l != e->shift_r)
|
|
|
|
ucontrol->value.enumerated.item[1] =
|
|
|
|
gbvalue.value.enumerated_item[1];
|
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
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 snd_soc_codec *codec = widget->codec;
|
|
|
|
struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
|
|
|
|
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
2016-07-25 17:29:20 -06:00
|
|
|
struct gb_bundle *bundle;
|
2016-06-30 07:15:37 -06:00
|
|
|
|
|
|
|
if (ucontrol->value.enumerated.item[0] > e->max - 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;
|
2016-07-25 17:29:20 -06:00
|
|
|
bundle = to_gb_bundle(module->dev);
|
|
|
|
|
|
|
|
ret = gb_pm_runtime_get_sync(bundle);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
ret = gb_audio_gb_get_control(module->mgmt_connection, ctl_id,
|
|
|
|
GB_AUDIO_INVALID_INDEX, &gbvalue);
|
2016-07-25 17:29:20 -06:00
|
|
|
|
|
|
|
gb_pm_runtime_put_autosuspend(bundle);
|
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
if (ret) {
|
|
|
|
dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
|
|
|
|
__func__, kcontrol->id.name);
|
|
|
|
return ret;
|
|
|
|
}
|
2016-01-13 14:07:51 -07:00
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
mux = ucontrol->value.enumerated.item[0];
|
|
|
|
val = mux << e->shift_l;
|
|
|
|
mask = e->mask << e->shift_l;
|
2016-01-13 14:07:51 -07:00
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
if (gbvalue.value.enumerated_item[0] !=
|
|
|
|
ucontrol->value.enumerated.item[0]) {
|
|
|
|
change = 1;
|
|
|
|
gbvalue.value.enumerated_item[0] =
|
|
|
|
ucontrol->value.enumerated.item[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e->shift_l != e->shift_r) {
|
|
|
|
if (ucontrol->value.enumerated.item[1] > e->max - 1)
|
|
|
|
return -EINVAL;
|
|
|
|
val |= ucontrol->value.enumerated.item[1] << e->shift_r;
|
|
|
|
mask |= e->mask << e->shift_r;
|
|
|
|
if (gbvalue.value.enumerated_item[1] !=
|
|
|
|
ucontrol->value.enumerated.item[1]) {
|
|
|
|
change = 1;
|
|
|
|
gbvalue.value.enumerated_item[1] =
|
|
|
|
ucontrol->value.enumerated.item[1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (change) {
|
2016-07-25 17:29:20 -06:00
|
|
|
ret = gb_pm_runtime_get_sync(bundle);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
ret = gb_audio_gb_set_control(module->mgmt_connection, ctl_id,
|
|
|
|
GB_AUDIO_INVALID_INDEX, &gbvalue);
|
2016-07-25 17:29:20 -06:00
|
|
|
|
|
|
|
gb_pm_runtime_put_autosuspend(bundle);
|
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
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];
|
|
|
|
|
|
|
|
widget->value = val;
|
|
|
|
widget->dapm->update = NULL;
|
|
|
|
snd_soc_dapm_mux_update_power(widget, kcontrol, mux, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return change;
|
|
|
|
}
|
2016-01-13 14:07:51 -07:00
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
static int gbaudio_tplg_create_enum_ctl(struct gbaudio_module_info *gb,
|
2016-01-13 14:07:51 -07:00
|
|
|
struct snd_kcontrol_new *kctl,
|
|
|
|
struct gb_audio_control *ctl)
|
|
|
|
{
|
2016-06-30 07:15:37 -06:00
|
|
|
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;
|
2016-01-13 14:07:51 -07:00
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
gb_enum = &ctl->info.value.enumerated;
|
|
|
|
|
|
|
|
/* since count=1, and reg is dummy */
|
|
|
|
gbe->max = gb_enum->items;
|
|
|
|
gbe->texts = gb_generate_enum_strings(gb, gb_enum);
|
|
|
|
|
|
|
|
/* debug enum info */
|
|
|
|
dev_dbg(gb->dev, "Max:%d, name_length:%d\n", gb_enum->items,
|
|
|
|
gb_enum->names_length);
|
|
|
|
for (i = 0; i < gb_enum->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);
|
2016-01-13 14:07:51 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
static int gbaudio_tplg_create_mixer_ctl(struct gbaudio_module_info *gb,
|
2016-01-13 14:07:51 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
static int gbaudio_tplg_create_wcontrol(struct gbaudio_module_info *gb,
|
2016-01-13 14:07:51 -07:00
|
|
|
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;
|
2016-01-28 08:45:39 -07:00
|
|
|
struct gbaudio_codec_info *gbcodec = snd_soc_codec_get_drvdata(codec);
|
2016-03-29 12:01:41 -06:00
|
|
|
struct gbaudio_module_info *module;
|
2016-07-14 14:13:00 -06:00
|
|
|
struct gb_bundle *bundle;
|
2016-01-13 14:07:51 -07:00
|
|
|
|
|
|
|
dev_dbg(codec->dev, "%s %s %d\n", __func__, w->name, event);
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
/* Find relevant module */
|
|
|
|
module = find_gb_module(gbcodec, w->name);
|
|
|
|
if (!module)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
/* map name to widget id */
|
2016-03-29 12:01:41 -06:00
|
|
|
wid = gbaudio_map_widgetname(module, w->name);
|
2016-01-13 14:07:51 -07:00
|
|
|
if (wid < 0) {
|
|
|
|
dev_err(codec->dev, "Invalid widget name:%s\n", w->name);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2016-07-14 14:13:00 -06:00
|
|
|
bundle = to_gb_bundle(module->dev);
|
|
|
|
|
|
|
|
ret = gb_pm_runtime_get_sync(bundle);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
switch (event) {
|
|
|
|
case SND_SOC_DAPM_PRE_PMU:
|
2016-03-29 12:01:41 -06:00
|
|
|
ret = gb_audio_gb_enable_widget(module->mgmt_connection, wid);
|
|
|
|
if (!ret)
|
2016-08-04 03:44:32 -06:00
|
|
|
ret = gbaudio_module_update(gbcodec, w, module, 1);
|
2016-01-13 14:07:51 -07:00
|
|
|
break;
|
|
|
|
case SND_SOC_DAPM_POST_PMD:
|
2016-03-29 12:01:41 -06:00
|
|
|
ret = gb_audio_gb_disable_widget(module->mgmt_connection, wid);
|
|
|
|
if (!ret)
|
2016-08-04 03:44:32 -06:00
|
|
|
ret = gbaudio_module_update(gbcodec, w, module, 0);
|
2016-01-13 14:07:51 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (ret)
|
2016-06-10 01:29:11 -06:00
|
|
|
dev_err_ratelimited(codec->dev,
|
|
|
|
"%d: widget, event:%d failed:%d\n", wid,
|
|
|
|
event, ret);
|
2016-07-14 14:13:00 -06:00
|
|
|
|
|
|
|
gb_pm_runtime_put_autosuspend(bundle);
|
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
static int gbaudio_tplg_create_widget(struct gbaudio_module_info *module,
|
2016-01-13 14:07:51 -07:00
|
|
|
struct snd_soc_dapm_widget *dw,
|
2016-07-05 16:09:20 -06:00
|
|
|
struct gb_audio_widget *w, int *w_size)
|
2016-01-13 14:07:51 -07:00
|
|
|
{
|
2016-07-05 16:09:20 -06:00
|
|
|
int i, ret, csize;
|
2016-01-13 14:07:51 -07:00
|
|
|
struct snd_kcontrol_new *widget_kctls;
|
|
|
|
struct gb_audio_control *curr;
|
|
|
|
struct gbaudio_control *control, *_control;
|
|
|
|
size_t size;
|
2016-03-29 12:01:41 -06:00
|
|
|
char temp_name[NAME_SIZE];
|
2016-01-13 14:07:51 -07:00
|
|
|
|
|
|
|
ret = gbaudio_validate_kcontrol_count(w);
|
|
|
|
if (ret) {
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_err(module->dev, "Inavlid kcontrol count=%d for %s\n",
|
2016-01-13 14:07:51 -07:00
|
|
|
w->ncontrols, w->name);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* allocate memory for kcontrol */
|
|
|
|
if (w->ncontrols) {
|
|
|
|
size = sizeof(struct snd_kcontrol_new) * w->ncontrols;
|
2016-03-29 12:01:41 -06:00
|
|
|
widget_kctls = devm_kzalloc(module->dev, size, GFP_KERNEL);
|
2016-01-13 14:07:51 -07:00
|
|
|
if (!widget_kctls)
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2016-07-05 16:09:20 -06:00
|
|
|
*w_size = sizeof(struct gb_audio_widget);
|
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
/* create relevant kcontrols */
|
2016-07-05 16:09:20 -06:00
|
|
|
curr = w->ctl;
|
2016-01-13 14:07:51 -07:00
|
|
|
for (i = 0; i < w->ncontrols; i++) {
|
2016-03-29 12:01:41 -06:00
|
|
|
ret = gbaudio_tplg_create_wcontrol(module, &widget_kctls[i],
|
2016-01-13 14:07:51 -07:00
|
|
|
curr);
|
|
|
|
if (ret) {
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_err(module->dev,
|
2016-01-13 14:07:51 -07:00
|
|
|
"%s:%d type widget_ctl not supported\n",
|
|
|
|
curr->name, curr->iface);
|
|
|
|
goto error;
|
|
|
|
}
|
2016-03-29 12:01:41 -06:00
|
|
|
control = devm_kzalloc(module->dev,
|
2016-01-13 14:07:51 -07:00
|
|
|
sizeof(struct gbaudio_control),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!control) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
control->id = curr->id;
|
|
|
|
control->name = curr->name;
|
2016-07-05 16:09:20 -06:00
|
|
|
control->wname = w->name;
|
|
|
|
|
2016-06-30 07:15:37 -06:00
|
|
|
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 += gbenum->names_length;
|
2016-01-13 14:07:51 -07:00
|
|
|
control->texts = (const char * const *)
|
2016-06-30 07:15:37 -06:00
|
|
|
gb_generate_enum_strings(module, gbenum);
|
2016-08-16 01:06:50 -06:00
|
|
|
control->items = gbenum->items;
|
2016-10-06 20:38:41 -06:00
|
|
|
} else {
|
2016-06-30 07:15:37 -06:00
|
|
|
csize = sizeof(struct gb_audio_control);
|
2016-10-06 20:38:41 -06:00
|
|
|
}
|
|
|
|
|
2016-07-05 16:09:20 -06:00
|
|
|
*w_size += csize;
|
|
|
|
curr = (void *)curr + csize;
|
2016-03-29 12:01:41 -06:00
|
|
|
list_add(&control->list, &module->widget_ctl_list);
|
|
|
|
dev_dbg(module->dev, "%s: control of type %d created\n",
|
2016-01-13 14:07:51 -07:00
|
|
|
widget_kctls[i].name, widget_kctls[i].iface);
|
|
|
|
}
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
/* 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);
|
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
switch (w->type) {
|
|
|
|
case snd_soc_dapm_spk:
|
|
|
|
*dw = (struct snd_soc_dapm_widget)
|
|
|
|
SND_SOC_DAPM_SPK(w->name, gbcodec_event_spk);
|
2016-03-30 01:53:56 -06:00
|
|
|
module->op_devices |= GBAUDIO_DEVICE_OUT_SPEAKER;
|
2016-01-13 14:07:51 -07:00
|
|
|
break;
|
|
|
|
case snd_soc_dapm_hp:
|
|
|
|
*dw = (struct snd_soc_dapm_widget)
|
|
|
|
SND_SOC_DAPM_HP(w->name, gbcodec_event_hp);
|
2016-03-30 01:53:56 -06:00
|
|
|
module->op_devices |= (GBAUDIO_DEVICE_OUT_WIRED_HEADSET
|
|
|
|
| GBAUDIO_DEVICE_OUT_WIRED_HEADPHONE);
|
|
|
|
module->ip_devices |= GBAUDIO_DEVICE_IN_WIRED_HEADSET;
|
2016-01-13 14:07:51 -07:00
|
|
|
break;
|
|
|
|
case snd_soc_dapm_mic:
|
|
|
|
*dw = (struct snd_soc_dapm_widget)
|
|
|
|
SND_SOC_DAPM_MIC(w->name, gbcodec_event_int_mic);
|
2016-03-30 01:53:56 -06:00
|
|
|
module->ip_devices |= GBAUDIO_DEVICE_IN_BUILTIN_MIC;
|
2016-01-13 14:07:51 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_dbg(module->dev, "%s: widget of type %d created\n", dw->name,
|
2016-01-13 14:07:51 -07:00
|
|
|
dw->id);
|
|
|
|
return 0;
|
|
|
|
error:
|
2016-03-29 12:01:41 -06:00
|
|
|
list_for_each_entry_safe(control, _control, &module->widget_ctl_list,
|
2016-01-13 14:07:51 -07:00
|
|
|
list) {
|
|
|
|
list_del(&control->list);
|
2016-03-29 12:01:41 -06:00
|
|
|
devm_kfree(module->dev, control);
|
2016-01-13 14:07:51 -07:00
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
static int gbaudio_tplg_process_kcontrols(struct gbaudio_module_info *module,
|
2016-01-13 14:07:51 -07:00
|
|
|
struct gb_audio_control *controls)
|
|
|
|
{
|
2016-07-05 16:09:20 -06:00
|
|
|
int i, csize, ret;
|
2016-01-13 14:07:51 -07:00
|
|
|
struct snd_kcontrol_new *dapm_kctls;
|
|
|
|
struct gb_audio_control *curr;
|
|
|
|
struct gbaudio_control *control, *_control;
|
|
|
|
size_t size;
|
2016-03-29 12:01:41 -06:00
|
|
|
char temp_name[NAME_SIZE];
|
2016-01-13 14:07:51 -07:00
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
size = sizeof(struct snd_kcontrol_new) * module->num_controls;
|
|
|
|
dapm_kctls = devm_kzalloc(module->dev, size, GFP_KERNEL);
|
2016-01-13 14:07:51 -07:00
|
|
|
if (!dapm_kctls)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
curr = controls;
|
2016-03-29 12:01:41 -06:00
|
|
|
for (i = 0; i < module->num_controls; i++) {
|
|
|
|
ret = gbaudio_tplg_create_kcontrol(module, &dapm_kctls[i],
|
2016-01-13 14:07:51 -07:00
|
|
|
curr);
|
|
|
|
if (ret) {
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_err(module->dev, "%s:%d type not supported\n",
|
2016-01-13 14:07:51 -07:00
|
|
|
curr->name, curr->iface);
|
|
|
|
goto error;
|
|
|
|
}
|
2016-03-29 12:01:41 -06:00
|
|
|
control = devm_kzalloc(module->dev, sizeof(struct
|
2016-01-13 14:07:51 -07:00
|
|
|
gbaudio_control),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!control) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
control->id = curr->id;
|
2016-03-29 12:01:41 -06:00
|
|
|
/* 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);
|
2016-01-13 14:07:51 -07:00
|
|
|
control->name = curr->name;
|
2016-06-30 07:15:37 -06:00
|
|
|
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 += gbenum->names_length;
|
2016-01-13 14:07:51 -07:00
|
|
|
control->texts = (const char * const *)
|
2016-06-30 07:15:37 -06:00
|
|
|
gb_generate_enum_strings(module, gbenum);
|
2016-08-16 01:06:50 -06:00
|
|
|
control->items = gbenum->items;
|
2016-10-06 20:38:41 -06:00
|
|
|
} else {
|
2016-06-30 07:15:37 -06:00
|
|
|
csize = sizeof(struct gb_audio_control);
|
2016-10-06 20:38:41 -06:00
|
|
|
}
|
2016-07-05 16:09:20 -06:00
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
list_add(&control->list, &module->ctl_list);
|
|
|
|
dev_dbg(module->dev, "%d:%s created of type %d\n", curr->id,
|
2016-01-13 14:07:51 -07:00
|
|
|
curr->name, curr->info.type);
|
2016-07-05 16:09:20 -06:00
|
|
|
curr = (void *)curr + csize;
|
2016-01-13 14:07:51 -07:00
|
|
|
}
|
2016-03-29 12:01:41 -06:00
|
|
|
module->controls = dapm_kctls;
|
2016-01-13 14:07:51 -07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
error:
|
2016-03-29 12:01:41 -06:00
|
|
|
list_for_each_entry_safe(control, _control, &module->ctl_list,
|
2016-01-13 14:07:51 -07:00
|
|
|
list) {
|
|
|
|
list_del(&control->list);
|
2016-03-29 12:01:41 -06:00
|
|
|
devm_kfree(module->dev, control);
|
2016-01-13 14:07:51 -07:00
|
|
|
}
|
2016-03-29 12:01:41 -06:00
|
|
|
devm_kfree(module->dev, dapm_kctls);
|
2016-01-13 14:07:51 -07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
static int gbaudio_tplg_process_widgets(struct gbaudio_module_info *module,
|
2016-01-13 14:07:51 -07:00
|
|
|
struct gb_audio_widget *widgets)
|
|
|
|
{
|
2016-07-05 16:09:20 -06:00
|
|
|
int i, ret, w_size;
|
2016-01-13 14:07:51 -07:00
|
|
|
struct snd_soc_dapm_widget *dapm_widgets;
|
|
|
|
struct gb_audio_widget *curr;
|
|
|
|
struct gbaudio_widget *widget, *_widget;
|
|
|
|
size_t size;
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
size = sizeof(struct snd_soc_dapm_widget) * module->num_dapm_widgets;
|
|
|
|
dapm_widgets = devm_kzalloc(module->dev, size, GFP_KERNEL);
|
2016-01-13 14:07:51 -07:00
|
|
|
if (!dapm_widgets)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
curr = widgets;
|
2016-03-29 12:01:41 -06:00
|
|
|
for (i = 0; i < module->num_dapm_widgets; i++) {
|
|
|
|
ret = gbaudio_tplg_create_widget(module, &dapm_widgets[i],
|
2016-07-05 16:09:20 -06:00
|
|
|
curr, &w_size);
|
2016-01-13 14:07:51 -07:00
|
|
|
if (ret) {
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_err(module->dev, "%s:%d type not supported\n",
|
2016-01-13 14:07:51 -07:00
|
|
|
curr->name, curr->type);
|
|
|
|
goto error;
|
|
|
|
}
|
2016-03-29 12:01:41 -06:00
|
|
|
widget = devm_kzalloc(module->dev, sizeof(struct
|
2016-01-13 14:07:51 -07:00
|
|
|
gbaudio_widget),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!widget) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
widget->id = curr->id;
|
|
|
|
widget->name = curr->name;
|
2016-03-29 12:01:41 -06:00
|
|
|
list_add(&widget->list, &module->widget_list);
|
2016-07-05 16:09:20 -06:00
|
|
|
curr = (void *)curr + w_size;
|
2016-01-13 14:07:51 -07:00
|
|
|
}
|
2016-03-29 12:01:41 -06:00
|
|
|
module->dapm_widgets = dapm_widgets;
|
2016-01-13 14:07:51 -07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
2016-03-29 12:01:41 -06:00
|
|
|
list_for_each_entry_safe(widget, _widget, &module->widget_list,
|
2016-01-13 14:07:51 -07:00
|
|
|
list) {
|
|
|
|
list_del(&widget->list);
|
2016-03-29 12:01:41 -06:00
|
|
|
devm_kfree(module->dev, widget);
|
2016-01-13 14:07:51 -07:00
|
|
|
}
|
2016-03-29 12:01:41 -06:00
|
|
|
devm_kfree(module->dev, dapm_widgets);
|
2016-01-13 14:07:51 -07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
static int gbaudio_tplg_process_routes(struct gbaudio_module_info *module,
|
2016-01-13 14:07:51 -07:00
|
|
|
struct gb_audio_route *routes)
|
|
|
|
{
|
|
|
|
int i, ret;
|
|
|
|
struct snd_soc_dapm_route *dapm_routes;
|
|
|
|
struct gb_audio_route *curr;
|
|
|
|
size_t size;
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
size = sizeof(struct snd_soc_dapm_route) * module->num_dapm_routes;
|
|
|
|
dapm_routes = devm_kzalloc(module->dev, size, GFP_KERNEL);
|
2016-01-13 14:07:51 -07:00
|
|
|
if (!dapm_routes)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
module->dapm_routes = dapm_routes;
|
2016-01-13 14:07:51 -07:00
|
|
|
curr = routes;
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
for (i = 0; i < module->num_dapm_routes; i++) {
|
2016-01-13 14:07:51 -07:00
|
|
|
dapm_routes->sink =
|
2016-03-29 12:01:41 -06:00
|
|
|
gbaudio_map_widgetid(module, curr->destination_id);
|
2016-01-13 14:07:51 -07:00
|
|
|
if (!dapm_routes->sink) {
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_err(module->dev, "%d:%d:%d:%d - Invalid sink\n",
|
2016-01-13 14:07:51 -07:00
|
|
|
curr->source_id, curr->destination_id,
|
|
|
|
curr->control_id, curr->index);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
dapm_routes->source =
|
2016-03-29 12:01:41 -06:00
|
|
|
gbaudio_map_widgetid(module, curr->source_id);
|
2016-01-13 14:07:51 -07:00
|
|
|
if (!dapm_routes->source) {
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_err(module->dev, "%d:%d:%d:%d - Invalid source\n",
|
2016-01-13 14:07:51 -07:00
|
|
|
curr->source_id, curr->destination_id,
|
|
|
|
curr->control_id, curr->index);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
dapm_routes->control =
|
2016-03-29 12:01:41 -06:00
|
|
|
gbaudio_map_controlid(module,
|
2016-01-13 14:07:51 -07:00
|
|
|
curr->control_id,
|
|
|
|
curr->index);
|
|
|
|
if ((curr->control_id != GBAUDIO_INVALID_ID) &&
|
|
|
|
!dapm_routes->control) {
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_err(module->dev, "%d:%d:%d:%d - Invalid control\n",
|
2016-01-13 14:07:51 -07:00
|
|
|
curr->source_id, curr->destination_id,
|
|
|
|
curr->control_id, curr->index);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_dbg(module->dev, "Route {%s, %s, %s}\n", dapm_routes->sink,
|
2017-01-04 09:08:18 -07:00
|
|
|
(dapm_routes->control) ? dapm_routes->control : "NULL",
|
2016-01-13 14:07:51 -07:00
|
|
|
dapm_routes->source);
|
|
|
|
dapm_routes++;
|
|
|
|
curr++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
2016-08-16 01:06:51 -06:00
|
|
|
devm_kfree(module->dev, module->dapm_routes);
|
2016-01-13 14:07:51 -07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
static int gbaudio_tplg_process_header(struct gbaudio_module_info *module,
|
2016-01-13 14:07:51 -07:00
|
|
|
struct gb_audio_topology *tplg_data)
|
|
|
|
{
|
|
|
|
/* fetch no. of kcontrols, widgets & routes */
|
2016-03-29 12:01:41 -06:00
|
|
|
module->num_controls = tplg_data->num_controls;
|
|
|
|
module->num_dapm_widgets = tplg_data->num_widgets;
|
|
|
|
module->num_dapm_routes = tplg_data->num_routes;
|
2016-01-13 14:07:51 -07:00
|
|
|
|
|
|
|
/* update block offset */
|
2016-03-29 12:01:41 -06:00
|
|
|
module->dai_offset = (unsigned long)&tplg_data->data;
|
|
|
|
module->control_offset = module->dai_offset + tplg_data->size_dais;
|
|
|
|
module->widget_offset = module->control_offset +
|
2016-01-13 14:07:51 -07:00
|
|
|
tplg_data->size_controls;
|
2016-03-29 12:01:41 -06:00
|
|
|
module->route_offset = module->widget_offset +
|
2016-01-13 14:07:51 -07:00
|
|
|
tplg_data->size_widgets;
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
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);
|
2016-01-13 14:07:51 -07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
int gbaudio_tplg_parse_data(struct gbaudio_module_info *module,
|
2016-01-13 14:07:51 -07:00
|
|
|
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;
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
ret = gbaudio_tplg_process_header(module, tplg_data);
|
2016-01-13 14:07:51 -07:00
|
|
|
if (ret) {
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_err(module->dev, "%d: Error in parsing topology header\n",
|
2016-01-13 14:07:51 -07:00
|
|
|
ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* process control */
|
2016-03-29 12:01:41 -06:00
|
|
|
controls = (struct gb_audio_control *)module->control_offset;
|
|
|
|
ret = gbaudio_tplg_process_kcontrols(module, controls);
|
2016-01-13 14:07:51 -07:00
|
|
|
if (ret) {
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_err(module->dev,
|
2016-01-13 14:07:51 -07:00
|
|
|
"%d: Error in parsing controls data\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_dbg(module->dev, "Control parsing finished\n");
|
2016-01-13 14:07:51 -07:00
|
|
|
|
|
|
|
/* process widgets */
|
2016-03-29 12:01:41 -06:00
|
|
|
widgets = (struct gb_audio_widget *)module->widget_offset;
|
|
|
|
ret = gbaudio_tplg_process_widgets(module, widgets);
|
2016-01-13 14:07:51 -07:00
|
|
|
if (ret) {
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_err(module->dev,
|
2016-01-13 14:07:51 -07:00
|
|
|
"%d: Error in parsing widgets data\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_dbg(module->dev, "Widget parsing finished\n");
|
2016-01-13 14:07:51 -07:00
|
|
|
|
|
|
|
/* process route */
|
2016-03-29 12:01:41 -06:00
|
|
|
routes = (struct gb_audio_route *)module->route_offset;
|
|
|
|
ret = gbaudio_tplg_process_routes(module, routes);
|
2016-01-13 14:07:51 -07:00
|
|
|
if (ret) {
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_err(module->dev,
|
2016-01-13 14:07:51 -07:00
|
|
|
"%d: Error in parsing routes data\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
2016-03-29 12:01:41 -06:00
|
|
|
dev_dbg(module->dev, "Route parsing finished\n");
|
2016-01-13 14:07:51 -07:00
|
|
|
|
2016-09-01 00:08:40 -06:00
|
|
|
/* parse jack capabilities */
|
|
|
|
if (tplg_data->jack_type) {
|
|
|
|
module->jack_mask = tplg_data->jack_type & GBCODEC_JACK_MASK;
|
|
|
|
module->button_mask = tplg_data->jack_type &
|
|
|
|
GBCODEC_JACK_BUTTON_MASK;
|
|
|
|
}
|
|
|
|
|
2016-01-13 14:07:51 -07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
void gbaudio_tplg_release(struct gbaudio_module_info *module)
|
2016-01-13 14:07:51 -07:00
|
|
|
{
|
|
|
|
struct gbaudio_control *control, *_control;
|
|
|
|
struct gbaudio_widget *widget, *_widget;
|
|
|
|
|
2016-03-29 12:01:41 -06:00
|
|
|
if (!module->topology)
|
2016-01-13 14:07:51 -07:00
|
|
|
return;
|
|
|
|
|
|
|
|
/* release kcontrols */
|
2016-03-29 12:01:41 -06:00
|
|
|
list_for_each_entry_safe(control, _control, &module->ctl_list,
|
2016-01-13 14:07:51 -07:00
|
|
|
list) {
|
|
|
|
list_del(&control->list);
|
2016-03-29 12:01:41 -06:00
|
|
|
devm_kfree(module->dev, control);
|
2016-01-13 14:07:51 -07:00
|
|
|
}
|
2016-03-29 12:01:41 -06:00
|
|
|
if (module->controls)
|
|
|
|
devm_kfree(module->dev, module->controls);
|
2016-01-13 14:07:51 -07:00
|
|
|
|
|
|
|
/* release widget controls */
|
2016-03-29 12:01:41 -06:00
|
|
|
list_for_each_entry_safe(control, _control, &module->widget_ctl_list,
|
2016-01-13 14:07:51 -07:00
|
|
|
list) {
|
|
|
|
list_del(&control->list);
|
2016-03-29 12:01:41 -06:00
|
|
|
devm_kfree(module->dev, control);
|
2016-01-13 14:07:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* release widgets */
|
2016-03-29 12:01:41 -06:00
|
|
|
list_for_each_entry_safe(widget, _widget, &module->widget_list,
|
2016-01-13 14:07:51 -07:00
|
|
|
list) {
|
|
|
|
list_del(&widget->list);
|
2016-03-29 12:01:41 -06:00
|
|
|
devm_kfree(module->dev, widget);
|
2016-01-13 14:07:51 -07:00
|
|
|
}
|
2016-03-29 12:01:41 -06:00
|
|
|
if (module->dapm_widgets)
|
|
|
|
devm_kfree(module->dev, module->dapm_widgets);
|
2016-01-13 14:07:51 -07:00
|
|
|
|
|
|
|
/* release routes */
|
2016-03-29 12:01:41 -06:00
|
|
|
if (module->dapm_routes)
|
|
|
|
devm_kfree(module->dev, module->dapm_routes);
|
2016-01-13 14:07:51 -07:00
|
|
|
}
|