2227 lines
57 KiB
C
Executable file
2227 lines
57 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* ALSA SoC - Samsung Abox Topology driver
|
|
*
|
|
* Copyright (c) 2018 Samsung Electronics Co. Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/pm_runtime.h>
|
|
|
|
#include "abox.h"
|
|
#include "abox_ipc.h"
|
|
#include "abox_dump.h"
|
|
#include "abox_dma.h"
|
|
#include "abox_vdma.h"
|
|
#include "abox_tplg.h"
|
|
#include "abox_memlog.h"
|
|
|
|
#define ABOX_TPLG_DAPM_CTL_VOLSW 0x100
|
|
#define ABOX_TPLG_DAPM_CTL_ENUM_DOUBLE 0x101
|
|
#define ABOX_TPLG_DAPM_CTL_ENUM_VIRT 0x102
|
|
#define ABOX_TPLG_DAPM_CTL_ENUM_VALUE 0x103
|
|
#define ABOX_TPLG_DAPM_CTL_PIN 0x104
|
|
#define ABOX_TPLG_CTL_VOLSW 0x110
|
|
#define ABOX_TPLG_CTL_ENUM_DOUBLE 0x111
|
|
#define ABOX_TPLG_CTL_ENUM_VALUE 0x112
|
|
#define ABOX_TPLG_CTL_PIPELINE 0x113
|
|
#define ABOX_TPLG_CTL_VOLSW_WRITE_ONLY 0x120
|
|
|
|
#define UNKNOWN_VALUE -1
|
|
|
|
static const int MIN_VIRTUAL_WIDGET_ID = 0x1000;
|
|
|
|
static struct device *dev_abox;
|
|
static LIST_HEAD(widget_list);
|
|
static LIST_HEAD(kcontrol_list);
|
|
static LIST_HEAD(dai_list);
|
|
static DEFINE_MUTEX(kcontrol_mutex);
|
|
|
|
struct abox_tplg_widget_data {
|
|
struct list_head list;
|
|
int gid;
|
|
int id;
|
|
unsigned int value;
|
|
bool weak;
|
|
struct snd_soc_component *cmpnt;
|
|
struct snd_soc_dapm_widget *w;
|
|
struct snd_soc_tplg_dapm_widget *tplg_w;
|
|
};
|
|
|
|
struct abox_tplg_pipeline_item {
|
|
struct abox_tplg_kcontrol_data *kdata;
|
|
int value;
|
|
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
|
char text[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
|
struct snd_soc_tplg_vendor_string_elem *tplg_vse;
|
|
struct list_head work;
|
|
};
|
|
|
|
struct abox_tplg_pipeline {
|
|
unsigned int id;
|
|
unsigned int count;
|
|
const char *name;
|
|
struct snd_soc_tplg_vendor_array *tplg_va;
|
|
struct abox_tplg_pipeline_item *items;
|
|
};
|
|
|
|
struct abox_tplg_pipelines {
|
|
unsigned int count;
|
|
struct abox_tplg_pipeline *pl;
|
|
};
|
|
|
|
struct abox_tplg_kcontrol_data {
|
|
struct list_head list;
|
|
int gid;
|
|
int id;
|
|
unsigned int value[128];
|
|
int count;
|
|
bool is_volatile;
|
|
bool synchronous;
|
|
bool force_restore;
|
|
unsigned int addr;
|
|
unsigned int *kaddr;
|
|
struct abox_tplg_pipelines pls;
|
|
struct snd_soc_component *cmpnt;
|
|
struct snd_kcontrol_new *kcontrol_new;
|
|
struct snd_soc_dobj *dobj;
|
|
struct snd_kcontrol *kcontrol; /* use abox_tplg_get_kcontrol() */
|
|
union {
|
|
struct snd_soc_tplg_ctl_hdr *hdr;
|
|
struct snd_soc_tplg_mixer_control *tplg_mc;
|
|
struct snd_soc_tplg_enum_control *tplg_ec;
|
|
};
|
|
};
|
|
|
|
struct abox_tplg_dai_data {
|
|
struct list_head list;
|
|
int id;
|
|
struct snd_soc_dai_driver *dai_drv;
|
|
struct device *dev_platform;
|
|
};
|
|
|
|
struct abox_tplg_firmware {
|
|
const char * const fw_name;
|
|
const struct firmware *fw;
|
|
bool optional;
|
|
};
|
|
|
|
static struct abox_tplg_firmware abox_tplg_fw[] = {
|
|
{
|
|
.fw_name = "abox_tplg.bin",
|
|
},
|
|
{
|
|
.fw_name = "sectiongraph_tplg.bin",
|
|
.optional = true,
|
|
},
|
|
};
|
|
|
|
static struct abox_tplg_kcontrol_data *abox_tplg_find_kcontrol_data(const char *name)
|
|
{
|
|
struct abox_tplg_kcontrol_data *kdata;
|
|
|
|
list_for_each_entry(kdata, &kcontrol_list, list) {
|
|
if (!strcmp(kdata->hdr->name, name))
|
|
return kdata;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct snd_kcontrol *abox_tplg_get_kcontrol(struct abox_tplg_kcontrol_data *kdata)
|
|
{
|
|
struct snd_soc_component *cmpnt = kdata->cmpnt;
|
|
struct device *dev = cmpnt->dev;
|
|
struct snd_soc_dapm_context *dapm;
|
|
struct snd_soc_dapm_widget *w;
|
|
int i;
|
|
|
|
if (kdata->kcontrol)
|
|
return kdata->kcontrol;
|
|
|
|
switch (kdata->hdr->ops.info) {
|
|
case SND_SOC_TPLG_CTL_ENUM:
|
|
case SND_SOC_TPLG_CTL_ENUM_VALUE:
|
|
case SND_SOC_TPLG_CTL_VOLSW:
|
|
case SND_SOC_TPLG_CTL_VOLSW_SX:
|
|
kdata->kcontrol = kdata->dobj->control.kcontrol;
|
|
break;
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE:
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT:
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE:
|
|
case SND_SOC_TPLG_DAPM_CTL_VOLSW:
|
|
case SND_SOC_TPLG_DAPM_CTL_PIN:
|
|
dapm = snd_soc_component_get_dapm(cmpnt);
|
|
list_for_each_entry(w, &dapm->card->widgets, list) {
|
|
for (i = 0; i < w->num_kcontrols; i++) {
|
|
if (&w->kcontrol_news[i] == kdata->kcontrol_new) {
|
|
if (w->kcontrols) {
|
|
kdata->kcontrol = w->kcontrols[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
abox_warn(dev, "unsupported pipeline item type: %d\n",
|
|
kdata->hdr->name);
|
|
break;
|
|
}
|
|
|
|
if (!kdata->kcontrol)
|
|
abox_warn(dev, "can't find kcontrol: %s\n", kdata->hdr->name);
|
|
|
|
return kdata->kcontrol;
|
|
}
|
|
|
|
static int abox_tplg_register_dump(struct device *dev, int gid, int id,
|
|
const char *name)
|
|
{
|
|
struct abox_data *data = dev_get_drvdata(dev_abox);
|
|
|
|
return abox_dump_register(data, gid, id, name, NULL, 0, 0);
|
|
}
|
|
|
|
static int abox_tplg_request_ipc(ABOX_IPC_MSG *msg)
|
|
{
|
|
return abox_request_ipc(dev_abox, msg->ipcid, msg, sizeof(*msg), 0, 0);
|
|
}
|
|
|
|
static bool abox_tplg_get_bool_at(struct snd_soc_tplg_private *priv, int token,
|
|
int idx)
|
|
{
|
|
struct snd_soc_tplg_vendor_array *array;
|
|
struct snd_soc_tplg_vendor_value_elem *value;
|
|
int sz;
|
|
|
|
for (sz = 0; sz < priv->size; sz += array->size) {
|
|
array = (struct snd_soc_tplg_vendor_array *)(priv->data + sz);
|
|
|
|
if (array->type != SND_SOC_TPLG_TUPLE_TYPE_BOOL)
|
|
continue;
|
|
|
|
for (value = array->value; value - array->value <
|
|
array->num_elems; value++) {
|
|
if (value->token == token && !idx--)
|
|
return !!value->value;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int abox_tplg_get_int_at(struct snd_soc_tplg_private *priv, int token,
|
|
int idx)
|
|
{
|
|
struct snd_soc_tplg_vendor_array *array;
|
|
struct snd_soc_tplg_vendor_value_elem *value;
|
|
int sz;
|
|
|
|
for (sz = 0; sz < priv->size; sz += array->size) {
|
|
array = (struct snd_soc_tplg_vendor_array *)(priv->data + sz);
|
|
|
|
if (array->type != SND_SOC_TPLG_TUPLE_TYPE_WORD)
|
|
continue;
|
|
|
|
for (value = array->value; value - array->value <
|
|
array->num_elems; value++) {
|
|
if (value->token == token && !idx--)
|
|
return value->value;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static const char *abox_tplg_get_string_at(struct snd_soc_tplg_private *priv,
|
|
int token, int idx)
|
|
{
|
|
struct snd_soc_tplg_vendor_array *array;
|
|
struct snd_soc_tplg_vendor_string_elem *string;
|
|
int sz;
|
|
|
|
for (sz = 0; sz < priv->size; sz += array->size) {
|
|
array = (struct snd_soc_tplg_vendor_array *)(priv->data + sz);
|
|
|
|
if (array->type != SND_SOC_TPLG_TUPLE_TYPE_STRING)
|
|
continue;
|
|
|
|
for (string = array->string; string - array->string <
|
|
array->num_elems; string++) {
|
|
if (string->token == token && !idx--)
|
|
return string->string;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool abox_tplg_get_bool(struct snd_soc_tplg_private *priv, int token)
|
|
{
|
|
return abox_tplg_get_bool_at(priv, token, 0);
|
|
}
|
|
|
|
static int abox_tplg_get_int(struct snd_soc_tplg_private *priv, int token)
|
|
{
|
|
return abox_tplg_get_int_at(priv, token, 0);
|
|
}
|
|
|
|
static int abox_tplg_get_id(struct snd_soc_tplg_private *priv)
|
|
{
|
|
return abox_tplg_get_int(priv, ABOX_TKN_ID);
|
|
}
|
|
|
|
static int abox_tplg_get_gid(struct snd_soc_tplg_private *priv)
|
|
{
|
|
int ret = abox_tplg_get_int(priv, ABOX_TKN_GID);
|
|
|
|
if (ret < 0)
|
|
ret = ABOX_TPLG_GID_DEFAULT;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int abox_tplg_get_min(struct snd_soc_tplg_private *priv)
|
|
{
|
|
int ret = abox_tplg_get_int(priv, ABOX_TKN_MIN);
|
|
|
|
return (ret < 0) ? 0 : ret;
|
|
}
|
|
|
|
static int abox_tplg_get_count(struct snd_soc_tplg_private *priv)
|
|
{
|
|
int ret = abox_tplg_get_int(priv, ABOX_TKN_COUNT);
|
|
|
|
return (ret < 1) ? 1 : ret;
|
|
}
|
|
|
|
static bool abox_tplg_is_volatile(struct snd_soc_tplg_private *priv)
|
|
{
|
|
return abox_tplg_get_bool(priv, ABOX_TKN_VOLATILE);
|
|
}
|
|
|
|
static bool abox_tplg_synchronous(struct snd_soc_tplg_private *priv)
|
|
{
|
|
return abox_tplg_get_bool(priv, ABOX_TKN_SYNCHRONOUS);
|
|
}
|
|
|
|
static unsigned int abox_tplg_get_address(struct snd_soc_tplg_private *priv)
|
|
{
|
|
int ret = abox_tplg_get_int(priv, ABOX_TKN_ADDRESS);
|
|
|
|
return (ret < 0 && ret > -MAX_ERRNO) ? 0 : ret;
|
|
}
|
|
|
|
static bool abox_tplg_is_weak(struct snd_soc_tplg_private *priv)
|
|
{
|
|
return abox_tplg_get_bool(priv, ABOX_TKN_WEAK);
|
|
}
|
|
|
|
static bool abox_tplg_force_restore(struct snd_soc_tplg_private *priv)
|
|
{
|
|
return abox_tplg_get_bool(priv, ABOX_TKN_FORCE_RESTORE);
|
|
}
|
|
|
|
static DECLARE_COMPLETION(report_control_completion);
|
|
static DECLARE_COMPLETION(update_control_completion);
|
|
|
|
static int abox_tplg_ipc_get(struct device *dev, int gid, int id)
|
|
{
|
|
ABOX_IPC_MSG msg;
|
|
struct IPC_SYSTEM_MSG *system_msg = &msg.msg.system;
|
|
unsigned long timeout;
|
|
int ret;
|
|
|
|
abox_dbg(dev, "%s(%#x, %d)\n", __func__, gid, id);
|
|
|
|
reinit_completion(&report_control_completion);
|
|
|
|
msg.ipcid = IPC_SYSTEM;
|
|
system_msg->msgtype = ABOX_REQUEST_COMPONENT_CONTROL;
|
|
system_msg->param1 = gid;
|
|
system_msg->param2 = id;
|
|
ret = abox_tplg_request_ipc(&msg);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
timeout = wait_for_completion_timeout(&report_control_completion,
|
|
abox_get_waiting_jiffies(true));
|
|
if (timeout <= 0)
|
|
return -ETIME;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_ipc_get_complete(int gid, int id, unsigned int *value)
|
|
{
|
|
struct abox_tplg_kcontrol_data *kdata;
|
|
int i;
|
|
|
|
list_for_each_entry(kdata, &kcontrol_list, list) {
|
|
if (kdata->gid == gid && kdata->id == id) {
|
|
struct device *dev = kdata->cmpnt->dev;
|
|
|
|
for (i = 0; i < kdata->count; i++) {
|
|
abox_dbg(dev, "%s: %#x, %#x, %d\n", __func__,
|
|
gid, id, value[i]);
|
|
kdata->value[i] = value[i];
|
|
}
|
|
complete(&report_control_completion);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int abox_tplg_ipc_put_bundle(struct device *dev, int gid, int id,
|
|
unsigned int value, const char *bundle, bool sync)
|
|
{
|
|
ABOX_IPC_MSG msg;
|
|
struct IPC_SYSTEM_MSG *system_msg = &msg.msg.system;
|
|
unsigned long timeout;
|
|
int ret;
|
|
|
|
abox_dbg(dev, "%s(%#x, %d, %s, %d)\n", __func__, gid, id, bundle, sync);
|
|
|
|
if (sync)
|
|
reinit_completion(&update_control_completion);
|
|
|
|
msg.ipcid = IPC_SYSTEM;
|
|
system_msg->msgtype = ABOX_UPDATE_COMPONENT_CONTROL;
|
|
system_msg->param1 = gid;
|
|
system_msg->param2 = id;
|
|
system_msg->param3 = value;
|
|
strscpy(system_msg->bundle.param_bundle, bundle,
|
|
sizeof(system_msg->bundle.param_bundle));
|
|
|
|
ret = abox_tplg_request_ipc(&msg);
|
|
|
|
if (sync) {
|
|
timeout = wait_for_completion_timeout(
|
|
&update_control_completion,
|
|
abox_get_waiting_jiffies(true));
|
|
if (timeout <= 0)
|
|
return -ETIME;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int abox_tplg_ipc_put(struct device *dev, int gid, int id,
|
|
unsigned int *value, int count, bool sync)
|
|
{
|
|
ABOX_IPC_MSG msg;
|
|
struct IPC_SYSTEM_MSG *system_msg = &msg.msg.system;
|
|
unsigned long timeout;
|
|
int i, ret;
|
|
|
|
abox_dbg(dev, "%s(%#x, %d, %u, %d, %d)\n", __func__, gid, id,
|
|
value[0], count, sync);
|
|
|
|
if (sync)
|
|
reinit_completion(&update_control_completion);
|
|
|
|
msg.ipcid = IPC_SYSTEM;
|
|
system_msg->msgtype = ABOX_UPDATE_COMPONENT_CONTROL;
|
|
system_msg->param1 = gid;
|
|
system_msg->param2 = id;
|
|
for (i = 0; i < count; i++)
|
|
system_msg->bundle.param_s32[i] = value[i];
|
|
|
|
ret = abox_tplg_request_ipc(&msg);
|
|
|
|
if (sync) {
|
|
timeout = wait_for_completion_timeout(
|
|
&update_control_completion,
|
|
abox_get_waiting_jiffies(true));
|
|
if (timeout <= 0)
|
|
return -ETIME;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int abox_tplg_ipc_put_complete(int gid, int id, unsigned int *value)
|
|
{
|
|
struct abox_tplg_kcontrol_data *kdata;
|
|
int i;
|
|
|
|
list_for_each_entry(kdata, &kcontrol_list, list) {
|
|
if (kdata->gid == gid && kdata->id == id) {
|
|
struct device *dev = kdata->cmpnt->dev;
|
|
|
|
for (i = 0; i < kdata->count; i++) {
|
|
abox_dbg(dev, "%s: %#x, %#x, %d\n", __func__,
|
|
gid, id, value[i]);
|
|
kdata->value[i] = value[i];
|
|
}
|
|
complete(&update_control_completion);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int abox_tplg_val_get(struct device *dev,
|
|
struct abox_tplg_kcontrol_data *kdata)
|
|
{
|
|
int i;
|
|
|
|
abox_dbg(dev, "%s(%#x, %d)\n", __func__, kdata->gid, kdata->id);
|
|
|
|
for (i = 0; i < kdata->count; i++) {
|
|
kdata->value[i] = kdata->kaddr[i];
|
|
abox_dbg(dev, "%d\n", kdata->value[i]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void abox_tplg_val_set(struct device *dev,
|
|
struct abox_tplg_kcontrol_data *kdata)
|
|
{
|
|
int i;
|
|
|
|
abox_dbg(dev, "%s(%#x, %d, %u)\n", __func__, kdata->gid, kdata->id,
|
|
kdata->value[0]);
|
|
|
|
for (i = 0; i < kdata->count; i++)
|
|
kdata->kaddr[i] = kdata->value[i];
|
|
}
|
|
|
|
static int abox_tplg_val_put(struct device *dev,
|
|
struct abox_tplg_kcontrol_data *kdata)
|
|
{
|
|
ABOX_IPC_MSG msg;
|
|
struct IPC_SYSTEM_MSG *system_msg = &msg.msg.system;
|
|
unsigned long timeout;
|
|
int i, ret;
|
|
|
|
abox_dbg(dev, "%s(%#x, %d, %u)\n", __func__, kdata->gid, kdata->id,
|
|
kdata->value[0]);
|
|
|
|
abox_tplg_val_set(dev, kdata);
|
|
|
|
msg.ipcid = IPC_SYSTEM;
|
|
system_msg->msgtype = ABOX_UPDATE_COMPONENT_VALUE;
|
|
system_msg->param1 = kdata->gid;
|
|
system_msg->param2 = kdata->id;
|
|
for (i = 0; i < kdata->count; i++)
|
|
system_msg->bundle.param_s32[i] = kdata->value[i];
|
|
|
|
ret = abox_tplg_request_ipc(&msg);
|
|
|
|
if (kdata->synchronous) {
|
|
timeout = wait_for_completion_timeout(
|
|
&update_control_completion,
|
|
abox_get_waiting_jiffies(true));
|
|
if (timeout <= 0)
|
|
return -ETIME;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool abox_tplg_is_virtual_control(struct abox_tplg_kcontrol_data *kdata)
|
|
{
|
|
return (kdata->hdr->ops.put == ABOX_TPLG_DAPM_CTL_ENUM_VIRT);
|
|
}
|
|
|
|
static inline int abox_tplg_kcontrol_get(struct device *dev,
|
|
struct abox_tplg_kcontrol_data *kdata)
|
|
{
|
|
if (abox_tplg_is_virtual_control(kdata))
|
|
return 0;
|
|
|
|
if (kdata->addr)
|
|
return abox_tplg_val_get(dev, kdata);
|
|
else
|
|
return abox_tplg_ipc_get(dev, kdata->gid, kdata->id);
|
|
}
|
|
|
|
static inline int abox_tplg_kcontrol_put(struct device *dev,
|
|
struct abox_tplg_kcontrol_data *kdata)
|
|
{
|
|
if (abox_tplg_is_virtual_control(kdata))
|
|
return 0;
|
|
|
|
if (kdata->addr)
|
|
return abox_tplg_val_put(dev, kdata);
|
|
else
|
|
return abox_tplg_ipc_put(dev, kdata->gid, kdata->id,
|
|
kdata->value, kdata->count, kdata->synchronous);
|
|
}
|
|
|
|
static inline int abox_tplg_pipeline_put(struct device *dev,
|
|
struct abox_tplg_kcontrol_data *kdata)
|
|
{
|
|
return abox_tplg_ipc_put_bundle(dev, kdata->gid, kdata->id, kdata->value[0],
|
|
kdata->pls.pl[kdata->value[0]].name, kdata->synchronous);
|
|
}
|
|
|
|
static inline int abox_tplg_kcontrol_restore(struct device *dev,
|
|
struct abox_tplg_kcontrol_data *kdata)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (abox_tplg_is_virtual_control(kdata))
|
|
return 0;
|
|
|
|
if (kdata->addr)
|
|
abox_tplg_val_set(dev, kdata);
|
|
else if (kdata->pls.count)
|
|
ret = abox_tplg_ipc_put_bundle(dev, kdata->gid, kdata->id, kdata->value[0],
|
|
kdata->pls.pl[kdata->value[0]].name, kdata->synchronous);
|
|
else
|
|
ret = abox_tplg_ipc_put(dev, kdata->gid, kdata->id,
|
|
kdata->value, kdata->count, kdata->synchronous);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline int abox_tplg_widget_get(struct device *dev,
|
|
struct abox_tplg_widget_data *wdata)
|
|
{
|
|
return abox_tplg_ipc_get(dev, wdata->gid, wdata->id);
|
|
}
|
|
|
|
static inline int abox_tplg_widget_put(struct device *dev,
|
|
struct abox_tplg_widget_data *wdata)
|
|
{
|
|
return abox_tplg_ipc_put(dev, wdata->gid, wdata->id, &wdata->value, 1,
|
|
false);
|
|
}
|
|
|
|
static int abox_tplg_mixer_event(struct snd_soc_dapm_widget *w,
|
|
struct snd_kcontrol *k, int e)
|
|
{
|
|
struct device *dev = w->dapm->dev;
|
|
struct abox_tplg_widget_data *wdata = w->dobj.private;
|
|
|
|
abox_dbg(dev, "%s(%s, %d)\n", __func__, w->name, e);
|
|
|
|
switch (e) {
|
|
case SND_SOC_DAPM_PRE_PMU:
|
|
wdata->value = 1;
|
|
break;
|
|
case SND_SOC_DAPM_POST_PMD:
|
|
wdata->value = 0;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return abox_tplg_widget_put(dev, wdata);
|
|
}
|
|
|
|
static int abox_tplg_mux_event(struct snd_soc_dapm_widget *w,
|
|
struct snd_kcontrol *k, int e)
|
|
{
|
|
struct device *dev = w->dapm->dev;
|
|
|
|
abox_dbg(dev, "%s(%s, %d)\n", __func__, w->name, e);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_tplg_widget_events abox_tplg_widget_ops[] = {
|
|
{ABOX_EVENT_NONE, NULL},
|
|
{ABOX_EVENT_MIXER, abox_tplg_mixer_event},
|
|
{ABOX_EVENT_MUX, abox_tplg_mux_event},
|
|
};
|
|
|
|
static int abox_tplg_widget_load(struct snd_soc_component *cmpnt, int index,
|
|
struct snd_soc_dapm_widget *w,
|
|
struct snd_soc_tplg_dapm_widget *tplg_w)
|
|
{
|
|
abox_dbg(cmpnt->dev, "%s(%d, %d, %s)\n", __func__,
|
|
tplg_w->size, tplg_w->id, tplg_w->name);
|
|
|
|
return snd_soc_tplg_widget_bind_event(w, abox_tplg_widget_ops,
|
|
ARRAY_SIZE(abox_tplg_widget_ops),
|
|
tplg_w->event_type);
|
|
}
|
|
|
|
static int abox_tplg_widget_ready(struct snd_soc_component *cmpnt, int index,
|
|
struct snd_soc_dapm_widget *w,
|
|
struct snd_soc_tplg_dapm_widget *tplg_w)
|
|
{
|
|
struct device *dev = cmpnt->dev;
|
|
struct abox_tplg_widget_data *wdata;
|
|
struct snd_soc_dapm_route route;
|
|
int id, gid, ret = 0;
|
|
bool weak;
|
|
|
|
abox_dbg(dev, "%s(%d, %d, %s)\n", __func__,
|
|
tplg_w->size, tplg_w->id, tplg_w->name);
|
|
|
|
id = abox_tplg_get_id(&tplg_w->priv);
|
|
if (id < 0) {
|
|
abox_err(dev, "%s: invalid widget id: %d\n", tplg_w->name, id);
|
|
return id;
|
|
}
|
|
gid = abox_tplg_get_gid(&tplg_w->priv);
|
|
if (gid < 0) {
|
|
abox_err(dev, "%s: invalid widget gid: %d\n", tplg_w->name, gid);
|
|
return gid;
|
|
}
|
|
weak = abox_tplg_is_weak(&tplg_w->priv);
|
|
|
|
wdata = kzalloc(sizeof(*wdata), GFP_KERNEL);
|
|
if (!wdata)
|
|
return -ENOMEM;
|
|
|
|
wdata->id = id;
|
|
wdata->gid = gid;
|
|
wdata->weak = weak;
|
|
wdata->cmpnt = cmpnt;
|
|
wdata->w = w;
|
|
wdata->tplg_w = tplg_w;
|
|
w->dobj.private = wdata;
|
|
list_add_tail(&wdata->list, &widget_list);
|
|
|
|
switch (tplg_w->id) {
|
|
case SND_SOC_TPLG_DAPM_MUX:
|
|
/* Add none route in here. */
|
|
route.sink = tplg_w->name;
|
|
route.control = "None";
|
|
route.source = "None";
|
|
route.connected = NULL;
|
|
ret = snd_soc_dapm_add_routes(w->dapm, &route, 1);
|
|
break;
|
|
case SND_SOC_TPLG_DAPM_MIXER:
|
|
if (id < MIN_VIRTUAL_WIDGET_ID)
|
|
abox_tplg_register_dump(dev, gid, id, tplg_w->name);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int abox_tplg_widget_unload(struct snd_soc_component *cmpnt,
|
|
struct snd_soc_dobj *dobj)
|
|
{
|
|
struct abox_tplg_widget_data *wdata = dobj->private;
|
|
|
|
list_del(&wdata->list);
|
|
kfree(dobj->private);
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_get_mux(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
|
struct abox_tplg_kcontrol_data *kdata = e->dobj.private;
|
|
struct snd_soc_component *cmpnt = kdata->cmpnt;
|
|
struct device *dev = cmpnt->dev;
|
|
int ret = 0;
|
|
|
|
abox_dbg(dev, "%s(%s)\n", __func__, kcontrol->id.name);
|
|
|
|
if (!pm_runtime_suspended(dev_abox) && kdata->is_volatile) {
|
|
ret = abox_tplg_kcontrol_get(dev, kdata);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
ucontrol->value.enumerated.item[0] = kdata->value[0];
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int abox_tplg_put_mux(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
|
struct abox_tplg_kcontrol_data *kdata = e->dobj.private;
|
|
struct snd_soc_component *cmpnt = kdata->cmpnt;
|
|
struct device *dev = cmpnt->dev;
|
|
unsigned int value = ucontrol->value.enumerated.item[0];
|
|
int ret = 0;
|
|
|
|
if (value >= e->items) {
|
|
abox_err(dev, "%s: value=%d, items=%d\n",
|
|
kcontrol->id.name, value, e->items);
|
|
return -EINVAL;
|
|
}
|
|
|
|
abox_dbg(dev, "%s(%s, %s)\n", __func__, kcontrol->id.name,
|
|
e->texts[value]);
|
|
|
|
kdata->value[0] = value;
|
|
if (!pm_runtime_suspended(dev_abox)) {
|
|
ret = abox_tplg_kcontrol_put(dev, kdata);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int abox_tplg_get_pipeline(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
|
struct abox_tplg_kcontrol_data *kdata = e->dobj.private;
|
|
struct snd_soc_component *cmpnt = kdata->cmpnt;
|
|
struct device *dev = cmpnt->dev;
|
|
int ret = 0;
|
|
|
|
abox_dbg(dev, "%s(%s)\n", __func__, kcontrol->id.name);
|
|
|
|
ucontrol->value.enumerated.item[0] = kdata->value[0];
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int abox_tplg_pipeline_item_value(struct abox_tplg_pipeline_item *item)
|
|
{
|
|
struct abox_tplg_kcontrol_data *kdata;
|
|
struct device *dev;
|
|
struct snd_kcontrol *kc;
|
|
struct soc_mixer_control *mc;
|
|
struct soc_enum *se;
|
|
int i;
|
|
|
|
if (item->value != UNKNOWN_VALUE)
|
|
return item->value;
|
|
|
|
kdata = item->kdata;
|
|
dev = kdata->cmpnt->dev;
|
|
kc = abox_tplg_get_kcontrol(kdata);
|
|
switch (kdata->hdr->ops.info) {
|
|
case SND_SOC_TPLG_CTL_ENUM:
|
|
case SND_SOC_TPLG_CTL_ENUM_VALUE:
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE:
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT:
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE:
|
|
se = (struct soc_enum *)kc->private_value;
|
|
for (i = 0; i < se->items; i++) {
|
|
if (!strcmp(se->texts[i], item->text)) {
|
|
item->value = snd_soc_enum_item_to_val(se, i);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case SND_SOC_TPLG_CTL_VOLSW:
|
|
case SND_SOC_TPLG_CTL_VOLSW_SX:
|
|
case SND_SOC_TPLG_DAPM_CTL_VOLSW:
|
|
case SND_SOC_TPLG_DAPM_CTL_PIN:
|
|
mc = (struct soc_mixer_control *)kc->private_value;
|
|
if (kstrtoint(item->text, 0, &i) < 0)
|
|
break;
|
|
if (i < mc->min || i > mc->max)
|
|
break;
|
|
item->value = i;
|
|
break;
|
|
default:
|
|
abox_warn(dev, "unsupported pipeline item type: %d\n",
|
|
kdata->hdr->ops.info);
|
|
break;
|
|
}
|
|
|
|
if (item->value == UNKNOWN_VALUE)
|
|
abox_err(dev, "%s: invalid pipeline item text: %s\n",
|
|
kdata->hdr->name, item->text);
|
|
|
|
return item->value;
|
|
}
|
|
|
|
static int abox_tplg_put_pipeline_item(struct abox_tplg_pipeline_item *item, bool reset)
|
|
{
|
|
struct abox_tplg_kcontrol_data *kdata = item->kdata;
|
|
struct abox_data *data = dev_get_drvdata(dev_abox);
|
|
struct snd_soc_component *cmpnt;
|
|
struct device *dev;
|
|
struct snd_kcontrol *kc;
|
|
int ret = 0;
|
|
struct snd_ctl_elem_value ucontrol = { 0, };
|
|
|
|
if (!kdata)
|
|
kdata = item->kdata = abox_tplg_find_kcontrol_data(item->name);
|
|
|
|
if (!kdata)
|
|
return -EINVAL;
|
|
|
|
cmpnt = kdata->cmpnt;
|
|
dev = cmpnt->dev;
|
|
kdata->value[0] = reset ? 0 : abox_tplg_pipeline_item_value(item);
|
|
|
|
abox_dbg(dev, "%s(%s, %d): %u\n", __func__, item->name, reset, kdata->value[0]);
|
|
|
|
if (data->debug_mode)
|
|
abox_info(dev, "kcontrol %s : %s\n", item->name, reset ? "None" : item->text);
|
|
|
|
if (!pm_runtime_suspended(dev_abox)) {
|
|
ret = abox_tplg_kcontrol_put(dev, item->kdata);
|
|
if (ret < 0) {
|
|
abox_err(dev, "pipeline item put fail: %s\n", item->name);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
kc = abox_tplg_get_kcontrol(kdata);
|
|
switch (kdata->hdr->ops.info) {
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE:
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT:
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE:
|
|
ucontrol.value.enumerated.item[0] = kdata->value[0];
|
|
ret = snd_soc_dapm_put_enum_double(kc, &ucontrol);
|
|
break;
|
|
case SND_SOC_TPLG_DAPM_CTL_VOLSW:
|
|
case SND_SOC_TPLG_DAPM_CTL_PIN:
|
|
ucontrol.value.integer.value[0] = kdata->value[0];
|
|
ret = snd_soc_dapm_put_volsw(kc, &ucontrol);
|
|
break;
|
|
case SND_SOC_TPLG_CTL_ENUM:
|
|
case SND_SOC_TPLG_CTL_ENUM_VALUE:
|
|
case SND_SOC_TPLG_CTL_VOLSW:
|
|
case SND_SOC_TPLG_CTL_VOLSW_SX:
|
|
/* nothing to do */
|
|
break;
|
|
default:
|
|
abox_warn(dev, "unknown pipeline item type: %s\b", item->name);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int abox_tplg_put_pipeline(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
|
struct abox_tplg_kcontrol_data *kdata = e->dobj.private;
|
|
struct abox_tplg_pipeline *pl;
|
|
struct abox_tplg_pipeline_item *item;
|
|
struct snd_soc_component *cmpnt = kdata->cmpnt;
|
|
struct device *dev = cmpnt->dev;
|
|
unsigned int value = ucontrol->value.enumerated.item[0];
|
|
LIST_HEAD(list_reset);
|
|
LIST_HEAD(list_apply);
|
|
int ret = 0;
|
|
|
|
if (value >= e->items) {
|
|
abox_err(dev, "%s: value=%d, items=%d\n",
|
|
kcontrol->id.name, value, e->items);
|
|
return -EINVAL;
|
|
}
|
|
|
|
abox_dbg(dev, "%s(%s, %s)\n", __func__, kcontrol->id.name, e->texts[value]);
|
|
abox_info(dev, "pipeline %s : %s\n", kcontrol->id.name, e->texts[value]);
|
|
|
|
/* create working list */
|
|
pl = &kdata->pls.pl[value];
|
|
for (item = pl->items; item - pl->items < pl->count; item++)
|
|
INIT_LIST_HEAD(&item->work);
|
|
|
|
pl = &kdata->pls.pl[kdata->value[0]];
|
|
for (item = pl->items; item - pl->items < pl->count; item++)
|
|
list_add(&item->work, &list_reset);
|
|
|
|
pl = &kdata->pls.pl[value];
|
|
for (item = pl->items; item - pl->items < pl->count; item++)
|
|
list_move_tail(&item->work, &list_apply);
|
|
|
|
/* reset previous */
|
|
list_for_each_entry(item, &list_reset, work) {
|
|
abox_dbg(dev, "pipeline item reset: %s\n", item->name);
|
|
ret = abox_tplg_put_pipeline_item(item, true);
|
|
if (ret < 0)
|
|
abox_err(dev, "pipeline item reset fail: %s %d\n",
|
|
item->name, ret);
|
|
}
|
|
|
|
/* apply current */
|
|
list_for_each_entry(item, &list_apply, work) {
|
|
abox_dbg(dev, "pipeline item apply: %s\n", item->name);
|
|
ret = abox_tplg_put_pipeline_item(item, false);
|
|
if (ret < 0)
|
|
abox_err(dev, "pipeline item apply fail: %s %d\n",
|
|
item->name, ret);
|
|
}
|
|
|
|
kdata->value[0] = value;
|
|
|
|
if (!pm_runtime_suspended(dev_abox)) {
|
|
ret = abox_tplg_pipeline_put(dev, kdata);
|
|
if (ret < 0)
|
|
dev_warn(dev, "%s: failed to report pipeline change: %d\n",
|
|
kcontrol->id.name, ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int abox_tplg_dapm_get_mux_virt(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
|
struct abox_tplg_kcontrol_data *kdata = e->dobj.private;
|
|
struct snd_soc_component *cmpnt = kdata->cmpnt;
|
|
struct device *dev = cmpnt->dev;
|
|
|
|
abox_dbg(dev, "%s(%s)\n", __func__, kcontrol->id.name);
|
|
|
|
return snd_soc_dapm_get_enum_double(kcontrol, ucontrol);
|
|
}
|
|
|
|
static int abox_tplg_dapm_put_mux_virt(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
|
struct abox_tplg_kcontrol_data *kdata = e->dobj.private;
|
|
struct snd_soc_component *cmpnt = kdata->cmpnt;
|
|
struct device *dev = cmpnt->dev;
|
|
unsigned int value = ucontrol->value.enumerated.item[0];
|
|
|
|
if (value >= e->items) {
|
|
abox_err(dev, "%s: value=%d, items=%d\n",
|
|
kcontrol->id.name, value, e->items);
|
|
return -EINVAL;
|
|
}
|
|
|
|
abox_dbg(dev, "%s(%s, %s)\n", __func__, kcontrol->id.name,
|
|
e->texts[value]);
|
|
|
|
kdata->value[0] = value;
|
|
|
|
return snd_soc_dapm_put_enum_double(kcontrol, ucontrol);
|
|
}
|
|
|
|
static int abox_tplg_dapm_get_mux(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
|
struct abox_tplg_kcontrol_data *kdata = e->dobj.private;
|
|
struct snd_soc_component *cmpnt = kdata->cmpnt;
|
|
struct device *dev = cmpnt->dev;
|
|
int ret;
|
|
|
|
abox_dbg(dev, "%s(%s)\n", __func__, kcontrol->id.name);
|
|
|
|
if (!pm_runtime_suspended(dev_abox) && kdata->is_volatile) {
|
|
ret = abox_tplg_kcontrol_get(dev, kdata);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ucontrol->value.enumerated.item[0] = kdata->value[0];
|
|
|
|
ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return snd_soc_dapm_get_enum_double(kcontrol, ucontrol);
|
|
}
|
|
|
|
static int abox_tplg_dapm_put_mux(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
|
struct abox_tplg_kcontrol_data *kdata = e->dobj.private;
|
|
struct snd_soc_component *cmpnt = kdata->cmpnt;
|
|
struct device *dev = cmpnt->dev;
|
|
unsigned int value = ucontrol->value.enumerated.item[0];
|
|
int ret;
|
|
|
|
if (value >= e->items) {
|
|
abox_err(dev, "%s: value=%d, items=%d\n",
|
|
kcontrol->id.name, value, e->items);
|
|
return -EINVAL;
|
|
}
|
|
|
|
abox_dbg(dev, "%s(%s, %s)\n", __func__, kcontrol->id.name,
|
|
e->texts[value]);
|
|
|
|
kdata->value[0] = value;
|
|
if (!pm_runtime_suspended(dev_abox)) {
|
|
ret = abox_tplg_kcontrol_put(dev, kdata);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return snd_soc_dapm_put_enum_double(kcontrol, ucontrol);
|
|
}
|
|
|
|
static int abox_tplg_info_mixer(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
struct abox_tplg_kcontrol_data *kdata = mc->dobj.private;
|
|
|
|
snd_soc_info_volsw_sx(kcontrol, uinfo);
|
|
/* Android's libtinyalsa uses min and max of uinfo as it is,
|
|
* not the number of levels.
|
|
*/
|
|
uinfo->value.integer.min = mc->min;
|
|
uinfo->value.integer.max = mc->platform_max;
|
|
uinfo->count = kdata->count;
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_get_mixer_write_only(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
struct abox_tplg_kcontrol_data *kdata = mc->dobj.private;
|
|
struct snd_soc_component *cmpnt = kdata->cmpnt;
|
|
struct device *dev = cmpnt->dev;
|
|
int i;
|
|
|
|
abox_dbg(dev, "%s(%s)\n", __func__, kcontrol->id.name);
|
|
|
|
for (i = 0; i < kdata->count; i++)
|
|
ucontrol->value.integer.value[i] = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_get_mixer(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
struct abox_tplg_kcontrol_data *kdata = mc->dobj.private;
|
|
struct snd_soc_component *cmpnt = kdata->cmpnt;
|
|
struct device *dev = cmpnt->dev;
|
|
int i, ret;
|
|
|
|
abox_dbg(dev, "%s(%s)\n", __func__, kcontrol->id.name);
|
|
|
|
if (!pm_runtime_suspended(dev_abox)) {
|
|
ret = abox_tplg_kcontrol_get(dev, kdata);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < kdata->count; i++)
|
|
ucontrol->value.integer.value[i] = kdata->value[i];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_put_mixer(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
struct abox_tplg_kcontrol_data *kdata = mc->dobj.private;
|
|
struct snd_soc_component *cmpnt = kdata->cmpnt;
|
|
struct device *dev = cmpnt->dev;
|
|
long *value = ucontrol->value.integer.value;
|
|
int i, ret;
|
|
|
|
abox_dbg(dev, "%s(%s, %ld)\n", __func__, kcontrol->id.name, value[0]);
|
|
|
|
for (i = 0; i < kdata->count; i++) {
|
|
if (value[i] < mc->min || value[i] > mc->max) {
|
|
abox_err(dev, "%s: value[%d]=%d, min=%d, max=%d\n",
|
|
kcontrol->id.name, i, value[i], mc->min,
|
|
mc->max);
|
|
return -EINVAL;
|
|
}
|
|
|
|
kdata->value[i] = (unsigned int)value[i];
|
|
}
|
|
|
|
if (!pm_runtime_suspended(dev_abox)) {
|
|
ret = abox_tplg_kcontrol_put(dev, kdata);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_dapm_get_mixer(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
struct abox_tplg_kcontrol_data *kdata = mc->dobj.private;
|
|
struct snd_soc_component *cmpnt = kdata->cmpnt;
|
|
struct device *dev = cmpnt->dev;
|
|
int ret;
|
|
|
|
abox_dbg(dev, "%s(%s)\n", __func__, kcontrol->id.name);
|
|
|
|
if (!pm_runtime_suspended(dev_abox) && kdata->is_volatile) {
|
|
ret = abox_tplg_kcontrol_get(dev, kdata);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ucontrol->value.integer.value[0] = kdata->value[0];
|
|
|
|
ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return snd_soc_dapm_get_volsw(kcontrol, ucontrol);
|
|
}
|
|
|
|
static int abox_tplg_dapm_put_mixer(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
struct abox_tplg_kcontrol_data *kdata = mc->dobj.private;
|
|
struct snd_soc_component *cmpnt = kdata->cmpnt;
|
|
struct device *dev = cmpnt->dev;
|
|
unsigned int value = (unsigned int)ucontrol->value.integer.value[0];
|
|
int ret;
|
|
|
|
abox_dbg(dev, "%s(%s, %u)\n", __func__, kcontrol->id.name, value);
|
|
|
|
if (value < mc->min || value > mc->max) {
|
|
abox_err(dev, "%s: value=%d, min=%d, max=%d\n",
|
|
kcontrol->id.name, value, mc->min, mc->max);
|
|
return -EINVAL;
|
|
}
|
|
|
|
kdata->value[0] = value;
|
|
if (!pm_runtime_suspended(dev_abox)) {
|
|
ret = abox_tplg_kcontrol_put(dev, kdata);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return snd_soc_dapm_put_volsw(kcontrol, ucontrol);
|
|
}
|
|
|
|
static int abox_tplg_dapm_get_pin(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
struct abox_tplg_kcontrol_data *kdata = mc->dobj.private;
|
|
struct snd_soc_component *cmpnt = kdata->cmpnt;
|
|
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
|
|
struct device *dev = cmpnt->dev;
|
|
const char *pin = kdata->hdr->name;
|
|
int ret;
|
|
|
|
abox_dbg(dev, "%s(%s)\n", __func__, kcontrol->id.name);
|
|
|
|
if (!pm_runtime_suspended(dev_abox) && kdata->is_volatile) {
|
|
ret = abox_tplg_kcontrol_get(dev, kdata);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (kdata->value[0])
|
|
snd_soc_dapm_enable_pin(dapm, pin);
|
|
else
|
|
snd_soc_dapm_disable_pin(dapm, pin);
|
|
|
|
snd_soc_dapm_sync(dapm);
|
|
}
|
|
|
|
ucontrol->value.integer.value[0] =
|
|
snd_soc_dapm_get_pin_status(dapm, pin);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_dapm_put_pin(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
struct abox_tplg_kcontrol_data *kdata = mc->dobj.private;
|
|
struct snd_soc_component *cmpnt = kdata->cmpnt;
|
|
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
|
|
struct device *dev = cmpnt->dev;
|
|
const char *pin = kdata->hdr->name;
|
|
unsigned int value = (unsigned int)ucontrol->value.integer.value[0];
|
|
int ret;
|
|
|
|
abox_dbg(dev, "%s(%s, %u)\n", __func__, kcontrol->id.name, value);
|
|
|
|
if (value < mc->min || value > mc->max) {
|
|
abox_err(dev, "%s: value=%d, min=%d, max=%d\n",
|
|
kcontrol->id.name, value, mc->min, mc->max);
|
|
return -EINVAL;
|
|
}
|
|
|
|
kdata->value[0] = !!value;
|
|
if (!pm_runtime_suspended(dev_abox)) {
|
|
ret = abox_tplg_kcontrol_put(dev, kdata);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
if (kdata->value[0])
|
|
snd_soc_dapm_enable_pin(dapm, pin);
|
|
else
|
|
snd_soc_dapm_disable_pin(dapm, pin);
|
|
|
|
return snd_soc_dapm_sync(dapm);
|
|
}
|
|
|
|
static const struct snd_soc_tplg_kcontrol_ops abox_tplg_kcontrol_ops[] = {
|
|
{
|
|
ABOX_TPLG_DAPM_CTL_VOLSW,
|
|
abox_tplg_dapm_get_mixer,
|
|
abox_tplg_dapm_put_mixer,
|
|
snd_soc_info_volsw,
|
|
}, {
|
|
ABOX_TPLG_DAPM_CTL_ENUM_DOUBLE,
|
|
abox_tplg_dapm_get_mux,
|
|
abox_tplg_dapm_put_mux,
|
|
snd_soc_info_enum_double,
|
|
}, {
|
|
ABOX_TPLG_DAPM_CTL_ENUM_VIRT,
|
|
abox_tplg_dapm_get_mux_virt,
|
|
abox_tplg_dapm_put_mux_virt,
|
|
snd_soc_info_enum_double,
|
|
}, {
|
|
ABOX_TPLG_DAPM_CTL_ENUM_VALUE,
|
|
abox_tplg_dapm_get_mux,
|
|
abox_tplg_dapm_put_mux,
|
|
snd_soc_info_enum_double,
|
|
}, {
|
|
ABOX_TPLG_DAPM_CTL_PIN,
|
|
abox_tplg_dapm_get_pin,
|
|
abox_tplg_dapm_put_pin,
|
|
snd_soc_dapm_info_pin_switch,
|
|
}, {
|
|
ABOX_TPLG_CTL_VOLSW,
|
|
abox_tplg_get_mixer,
|
|
abox_tplg_put_mixer,
|
|
abox_tplg_info_mixer,
|
|
}, {
|
|
ABOX_TPLG_CTL_VOLSW_WRITE_ONLY,
|
|
abox_tplg_get_mixer_write_only,
|
|
abox_tplg_put_mixer,
|
|
abox_tplg_info_mixer,
|
|
}, {
|
|
ABOX_TPLG_CTL_ENUM_DOUBLE,
|
|
abox_tplg_get_mux,
|
|
abox_tplg_put_mux,
|
|
snd_soc_info_enum_double,
|
|
}, {
|
|
ABOX_TPLG_CTL_ENUM_VALUE,
|
|
abox_tplg_get_mux,
|
|
abox_tplg_put_mux,
|
|
snd_soc_info_enum_double,
|
|
}, {
|
|
SND_SOC_TPLG_CTL_VOLSW_SX,
|
|
abox_tplg_get_mixer,
|
|
abox_tplg_put_mixer,
|
|
abox_tplg_info_mixer,
|
|
}, {
|
|
ABOX_TPLG_CTL_PIPELINE,
|
|
abox_tplg_get_pipeline,
|
|
abox_tplg_put_pipeline,
|
|
snd_soc_info_enum_double,
|
|
}
|
|
};
|
|
|
|
static int abox_tplg_control_copy_enum_values(
|
|
struct snd_soc_dobj_control *control,
|
|
struct snd_soc_tplg_vendor_string_elem *string, int items)
|
|
{
|
|
int i;
|
|
|
|
control->dvalues = kcalloc(items, sizeof(control->dvalues[0]),
|
|
GFP_KERNEL);
|
|
if (!control->dvalues)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < items; i++)
|
|
control->dvalues[i] = string[i].token;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_control_copy_enum_texts(
|
|
struct snd_soc_dobj_control *control,
|
|
struct snd_soc_tplg_vendor_string_elem *string, int items)
|
|
{
|
|
int i;
|
|
|
|
control->dtexts = kcalloc(items, sizeof(control->dtexts[0]),
|
|
GFP_KERNEL);
|
|
if (!control->dtexts)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < items; i++) {
|
|
if (strnlen(string[i].string, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) ==
|
|
SNDRV_CTL_ELEM_ID_NAME_MAXLEN)
|
|
return -EINVAL;
|
|
|
|
control->dtexts[i] = kstrdup(string[i].string, GFP_KERNEL);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_control_load_enum_items(struct snd_soc_component *cmpnt,
|
|
struct snd_soc_tplg_enum_control *tplg_ec,
|
|
struct soc_enum *se)
|
|
{
|
|
struct device *dev = cmpnt->dev;
|
|
struct snd_soc_tplg_private *priv = &tplg_ec->priv;
|
|
struct snd_soc_tplg_vendor_array *array;
|
|
struct snd_soc_dobj_control *control = &se->dobj.control;
|
|
int i, sz, ret = -EINVAL;
|
|
|
|
abox_dbg(dev, "%s(%s, %d)\n", __func__, tplg_ec->hdr.name, priv->size);
|
|
|
|
for (sz = 0; sz < priv->size; sz += array->size) {
|
|
array = (struct snd_soc_tplg_vendor_array *)(priv->data + sz);
|
|
|
|
if (array->type != SND_SOC_TPLG_TUPLE_TYPE_STRING)
|
|
continue;
|
|
|
|
switch (tplg_ec->hdr.ops.info) {
|
|
case SND_SOC_TPLG_CTL_ENUM_VALUE:
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE:
|
|
ret = abox_tplg_control_copy_enum_values(control,
|
|
array->string, array->num_elems);
|
|
if (ret < 0) {
|
|
abox_err(dev, "invalid enum values: %d\n", ret);
|
|
break;
|
|
}
|
|
/* fall through to create texts */
|
|
case SND_SOC_TPLG_CTL_ENUM:
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE:
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT:
|
|
ret = abox_tplg_control_copy_enum_texts(control,
|
|
array->string, array->num_elems);
|
|
if (ret < 0) {
|
|
abox_err(dev, "invalid enum texts: %d\n", ret);
|
|
break;
|
|
}
|
|
se->texts = (const char * const *)control->dtexts;
|
|
se->items = array->num_elems;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (ret < 0) {
|
|
kfree(control->dvalues);
|
|
if (control->dtexts) {
|
|
for (i = 0; i < array->num_elems; i++)
|
|
kfree(control->dtexts[i]);
|
|
}
|
|
kfree(control->dtexts);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int abox_tplg_control_load_pipeline_item_id(struct device *dev,
|
|
struct soc_enum *se, struct snd_soc_tplg_vendor_array *array,
|
|
struct snd_soc_tplg_vendor_string_elem *elem,
|
|
struct abox_tplg_pipelines *pls, int pl_id)
|
|
{
|
|
const char *enum_text;
|
|
struct abox_tplg_pipeline *pl;
|
|
int ret;
|
|
|
|
if (pl_id >= pls->count)
|
|
return -EINVAL;
|
|
|
|
enum_text = se->dobj.control.dtexts[pl_id];
|
|
ret = strncmp(enum_text, elem->string, sizeof(elem->string));
|
|
if (ret) {
|
|
abox_err(dev, "%s: unmatched enum text: %s != %s\n",
|
|
se->dobj.control.kcontrol->id.name,
|
|
enum_text, elem->string);
|
|
BUG();
|
|
}
|
|
|
|
pl = &pls->pl[pl_id];
|
|
pl->id = pl_id;
|
|
abox_dbg(dev, "pipeline id=%u\n", pl->id);
|
|
pl->tplg_va = array;
|
|
pl->count = 4;
|
|
pl->name = elem->string;
|
|
pl->items = devm_kcalloc(dev, pl->count, sizeof(*pl->items), GFP_KERNEL);
|
|
if (!pl->items)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_control_load_pipeline_item_value(struct device *dev,
|
|
struct snd_soc_tplg_vendor_string_elem *elem,
|
|
struct abox_tplg_pipeline *pl, int pl_count)
|
|
{
|
|
struct abox_tplg_pipeline_item *item;
|
|
char buf[sizeof(elem->string)];
|
|
char *name, *value;
|
|
|
|
if (elem->token <= ABOX_TKN_PIPELINE)
|
|
return -EINVAL;
|
|
|
|
while (pl->count <= pl_count) {
|
|
void *buf_new;
|
|
|
|
pl->count = pl_count + 4;
|
|
buf_new = devm_krealloc(dev, pl->items, sizeof(*pl->items) * pl->count, GFP_KERNEL | __GFP_ZERO);
|
|
if (IS_ERR_OR_NULL(buf_new))
|
|
return -ENOMEM;
|
|
pl->items = buf_new;
|
|
}
|
|
|
|
strscpy(buf, elem->string, sizeof(buf));
|
|
value = buf;
|
|
name = strsep(&value, ",");
|
|
if (!value || strlen(value) == 0)
|
|
return -EINVAL;
|
|
name = strim(name);
|
|
value = strim(value);
|
|
item = &pl->items[pl_count];
|
|
strscpy(item->name, name, sizeof(item->name));
|
|
strscpy(item->text, value, sizeof(item->text));
|
|
abox_dbg(dev, "pipeline name=%s value=%s\n", item->name, item->text);
|
|
item->value = UNKNOWN_VALUE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_control_load_pipeline_items(
|
|
struct snd_soc_component *cmpnt,
|
|
struct snd_soc_tplg_enum_control *tplg_ec,
|
|
struct soc_enum *se,
|
|
struct abox_tplg_kcontrol_data *kdata)
|
|
{
|
|
struct device *dev = cmpnt->dev;
|
|
struct snd_soc_tplg_private *priv = &tplg_ec->priv;
|
|
struct snd_soc_tplg_vendor_array *array;
|
|
struct snd_soc_tplg_vendor_string_elem *elem;
|
|
struct abox_tplg_pipelines *pls = &kdata->pls;
|
|
struct abox_tplg_pipeline *pl;
|
|
int sz, pl_id, pl_count, ret = 0;
|
|
|
|
abox_dbg(dev, "%s(%s, %d)\n", __func__, tplg_ec->hdr.name, priv->size);
|
|
|
|
pls->count = se->items;
|
|
pls->pl = devm_kcalloc(dev, pls->count, sizeof(*pls->pl), GFP_KERNEL);
|
|
if (!pls->pl)
|
|
return -ENOMEM;
|
|
|
|
pl_id = -1;
|
|
pl_count = 0;
|
|
for (sz = 0; sz < priv->size; sz += array->size) {
|
|
array = (struct snd_soc_tplg_vendor_array *)(priv->data + sz);
|
|
|
|
if (array->type != SND_SOC_TPLG_TUPLE_TYPE_STRING)
|
|
continue;
|
|
|
|
if (array->num_elems < 1)
|
|
continue;
|
|
|
|
for (elem = array->string; elem - array->string < array->num_elems; elem++) {
|
|
if (elem->token < ABOX_TKN_PIPELINE)
|
|
continue;
|
|
|
|
switch (elem->token) {
|
|
case ABOX_TKN_PIPELINE:
|
|
ret = abox_tplg_control_load_pipeline_item_id(
|
|
dev, se, array, elem, pls, ++pl_id);
|
|
if (ret < 0)
|
|
break;
|
|
|
|
pl_count = 0;
|
|
default:
|
|
if (pl_id < 0)
|
|
break;
|
|
|
|
pl = &pls->pl[pl_id];
|
|
ret = abox_tplg_control_load_pipeline_item_value(
|
|
dev, elem, pl, pl_count);
|
|
if (ret < 0)
|
|
break;
|
|
|
|
pl_count++;
|
|
}
|
|
}
|
|
|
|
if (pl_id < 0)
|
|
continue;
|
|
|
|
pl = &pls->pl[pl_id];
|
|
pl->count = pl_count;
|
|
abox_dbg(dev, "pipeline %s count=%u\n", pl->name, pl->count);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int abox_tplg_control_load_enum(struct snd_soc_component *cmpnt,
|
|
struct snd_kcontrol_new *kctl,
|
|
struct snd_soc_tplg_ctl_hdr *hdr)
|
|
{
|
|
struct device *dev = cmpnt->dev;
|
|
struct abox_tplg_kcontrol_data *kdata;
|
|
struct snd_soc_tplg_enum_control *tplg_ec;
|
|
struct soc_enum *se = (struct soc_enum *)kctl->private_value;
|
|
int id, gid, ret;
|
|
|
|
tplg_ec = container_of(hdr, struct snd_soc_tplg_enum_control, hdr);
|
|
id = abox_tplg_get_id(&tplg_ec->priv);
|
|
if (id < 0) {
|
|
abox_err(dev, "%s: invalid enum id: %d\n", hdr->name, id);
|
|
return id;
|
|
}
|
|
gid = abox_tplg_get_gid(&tplg_ec->priv);
|
|
if (gid < 0) {
|
|
abox_err(dev, "%s: invalid enum gid: %d\n", hdr->name, gid);
|
|
return gid;
|
|
}
|
|
|
|
ret = abox_tplg_control_load_enum_items(cmpnt, tplg_ec, se);
|
|
if (ret < 0) {
|
|
abox_err(dev, "%s: invalid enum items: %d\n", hdr->name, ret);
|
|
return ret;
|
|
}
|
|
|
|
if (se->reg < SND_SOC_NOPM) {
|
|
se->reg = SND_SOC_NOPM;
|
|
se->shift_l = se->shift_r = 0;
|
|
se->mask = ~0U;
|
|
}
|
|
|
|
kdata = kzalloc(sizeof(*kdata), GFP_KERNEL);
|
|
if (!kdata)
|
|
return -ENOMEM;
|
|
|
|
kdata->gid = gid;
|
|
kdata->id = id;
|
|
kdata->count = abox_tplg_get_count(&tplg_ec->priv);
|
|
kdata->is_volatile = abox_tplg_is_volatile(&tplg_ec->priv);
|
|
kdata->synchronous = abox_tplg_synchronous(&tplg_ec->priv);
|
|
kdata->force_restore = abox_tplg_force_restore(&tplg_ec->priv);
|
|
kdata->cmpnt = cmpnt;
|
|
kdata->kcontrol_new = kctl;
|
|
kdata->tplg_ec = tplg_ec;
|
|
kdata->dobj = &se->dobj;
|
|
se->dobj.private = kdata;
|
|
mutex_lock(&kcontrol_mutex);
|
|
list_add_tail(&kdata->list, &kcontrol_list);
|
|
mutex_unlock(&kcontrol_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_control_load_mixer(struct snd_soc_component *cmpnt,
|
|
struct snd_kcontrol_new *kctl,
|
|
struct snd_soc_tplg_ctl_hdr *hdr)
|
|
{
|
|
struct device *dev = cmpnt->dev;
|
|
struct abox_data *data = dev_get_drvdata(dev_abox);
|
|
struct abox_tplg_kcontrol_data *kdata;
|
|
struct snd_soc_tplg_mixer_control *tplg_mc;
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kctl->private_value;
|
|
int id, gid;
|
|
|
|
tplg_mc = container_of(hdr, struct snd_soc_tplg_mixer_control, hdr);
|
|
id = abox_tplg_get_id(&tplg_mc->priv);
|
|
if (id < 0) {
|
|
abox_err(dev, "%s: invalid enum id: %d\n", hdr->name, id);
|
|
return id;
|
|
}
|
|
gid = abox_tplg_get_gid(&tplg_mc->priv);
|
|
if (gid < 0) {
|
|
abox_err(dev, "%s: invalid enum gid: %d\n", hdr->name, gid);
|
|
return gid;
|
|
}
|
|
|
|
if (mc->reg < SND_SOC_NOPM) {
|
|
mc->reg = mc->rreg = SND_SOC_NOPM;
|
|
mc->shift = mc->rshift = 0;
|
|
}
|
|
|
|
kdata = kzalloc(sizeof(*kdata), GFP_KERNEL);
|
|
if (!kdata)
|
|
return -ENOMEM;
|
|
|
|
kdata->gid = gid;
|
|
kdata->id = id;
|
|
kdata->count = abox_tplg_get_count(&tplg_mc->priv);
|
|
kdata->is_volatile = abox_tplg_is_volatile(&tplg_mc->priv);
|
|
kdata->synchronous = abox_tplg_synchronous(&tplg_mc->priv);
|
|
kdata->force_restore = abox_tplg_force_restore(&tplg_mc->priv);
|
|
kdata->addr = abox_tplg_get_address(&tplg_mc->priv);
|
|
if (kdata->addr)
|
|
kdata->kaddr = abox_addr_to_kernel_addr(data, kdata->addr);
|
|
kdata->cmpnt = cmpnt;
|
|
kdata->kcontrol_new = kctl;
|
|
kdata->tplg_mc = tplg_mc;
|
|
kdata->dobj = &mc->dobj;
|
|
mc->dobj.private = kdata;
|
|
mutex_lock(&kcontrol_mutex);
|
|
list_add_tail(&kdata->list, &kcontrol_list);
|
|
mutex_unlock(&kcontrol_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_control_load_sx(struct snd_soc_component *cmpnt,
|
|
struct snd_kcontrol_new *kctl,
|
|
struct snd_soc_tplg_ctl_hdr *hdr)
|
|
{
|
|
struct snd_soc_tplg_mixer_control *tplg_mc;
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kctl->private_value;
|
|
|
|
tplg_mc = container_of(hdr, struct snd_soc_tplg_mixer_control, hdr);
|
|
/* Current ALSA topology doesn't process soc_mixer_control->min. */
|
|
mc->min = abox_tplg_get_min(&tplg_mc->priv);
|
|
return abox_tplg_control_load_mixer(cmpnt, kctl, hdr);
|
|
}
|
|
|
|
static int abox_tplg_control_load_pipeline(struct snd_soc_component *cmpnt,
|
|
struct snd_kcontrol_new *kctl,
|
|
struct snd_soc_tplg_ctl_hdr *hdr)
|
|
{
|
|
struct device *dev = cmpnt->dev;
|
|
struct abox_tplg_kcontrol_data *kdata;
|
|
struct snd_soc_tplg_enum_control *tplg_ec;
|
|
struct soc_enum *se = (struct soc_enum *)kctl->private_value;
|
|
int id, gid, ret;
|
|
|
|
tplg_ec = container_of(hdr, struct snd_soc_tplg_enum_control, hdr);
|
|
id = abox_tplg_get_id(&tplg_ec->priv);
|
|
if (id < 0) {
|
|
abox_err(dev, "%s: invalid enum id: %d\n", hdr->name, id);
|
|
return id;
|
|
}
|
|
gid = abox_tplg_get_gid(&tplg_ec->priv);
|
|
if (gid < 0) {
|
|
abox_err(dev, "%s: invalid enum gid: %d\n", hdr->name, gid);
|
|
return gid;
|
|
}
|
|
|
|
ret = abox_tplg_control_load_enum_items(cmpnt, tplg_ec, se);
|
|
if (ret < 0) {
|
|
abox_err(dev, "%s: invalid enum items: %d\n", hdr->name, ret);
|
|
return ret;
|
|
}
|
|
|
|
if (se->reg < SND_SOC_NOPM) {
|
|
se->reg = SND_SOC_NOPM;
|
|
se->shift_l = se->shift_r = 0;
|
|
se->mask = ~0U;
|
|
}
|
|
|
|
kdata = kzalloc(sizeof(*kdata), GFP_KERNEL);
|
|
if (!kdata)
|
|
return -ENOMEM;
|
|
|
|
kdata->gid = gid;
|
|
kdata->id = id;
|
|
kdata->count = abox_tplg_get_count(&tplg_ec->priv);
|
|
kdata->is_volatile = abox_tplg_is_volatile(&tplg_ec->priv);
|
|
kdata->synchronous = abox_tplg_synchronous(&tplg_ec->priv);
|
|
kdata->force_restore = abox_tplg_force_restore(&tplg_ec->priv);
|
|
kdata->cmpnt = cmpnt;
|
|
kdata->kcontrol_new = kctl;
|
|
kdata->tplg_ec = tplg_ec;
|
|
kdata->dobj = &se->dobj;
|
|
se->dobj.private = kdata;
|
|
ret = abox_tplg_control_load_pipeline_items(cmpnt, tplg_ec, se, kdata);
|
|
if (ret < 0) {
|
|
abox_err(dev, "%s: invalid piepline items: %d\n", hdr->name,
|
|
ret);
|
|
return ret;
|
|
}
|
|
list_add_tail(&kdata->list, &kcontrol_list);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int abox_tplg_control_load(struct snd_soc_component *cmpnt, int index,
|
|
struct snd_kcontrol_new *kctl,
|
|
struct snd_soc_tplg_ctl_hdr *hdr)
|
|
{
|
|
struct device *dev = cmpnt->dev;
|
|
int ret = 0;
|
|
|
|
abox_dbg(cmpnt->dev, "%s(%d, %d, %s)\n", __func__,
|
|
hdr->size, hdr->type, hdr->name);
|
|
|
|
switch (hdr->ops.info) {
|
|
case SND_SOC_TPLG_CTL_ENUM:
|
|
case SND_SOC_TPLG_CTL_ENUM_VALUE:
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE:
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT:
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE:
|
|
if (!(kctl->access & SNDRV_CTL_ELEM_ACCESS_READWRITE))
|
|
break;
|
|
|
|
switch (hdr->ops.get) {
|
|
case ABOX_TPLG_CTL_PIPELINE:
|
|
ret = abox_tplg_control_load_pipeline(cmpnt, kctl, hdr);
|
|
break;
|
|
default:
|
|
ret = abox_tplg_control_load_enum(cmpnt, kctl, hdr);
|
|
break;
|
|
}
|
|
break;
|
|
case SND_SOC_TPLG_CTL_VOLSW:
|
|
case SND_SOC_TPLG_DAPM_CTL_VOLSW:
|
|
case SND_SOC_TPLG_DAPM_CTL_PIN:
|
|
if (kctl->access & SNDRV_CTL_ELEM_ACCESS_READWRITE)
|
|
ret = abox_tplg_control_load_mixer(cmpnt, kctl, hdr);
|
|
break;
|
|
case SND_SOC_TPLG_CTL_VOLSW_SX:
|
|
if (kctl->access & SNDRV_CTL_ELEM_ACCESS_READWRITE)
|
|
ret = abox_tplg_control_load_sx(cmpnt, kctl, hdr);
|
|
break;
|
|
default:
|
|
abox_warn(dev, "unknown control %s:%d\n", hdr->name,
|
|
hdr->ops.info);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int abox_tplg_control_unload(struct snd_soc_component *cmpnt,
|
|
struct snd_soc_dobj *dobj)
|
|
{
|
|
struct abox_tplg_kcontrol_data *kdata = dobj->private;
|
|
|
|
list_del(&kdata->list);
|
|
kfree(dobj->private);
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_dai_load(struct snd_soc_component *cmpnt, int index,
|
|
struct snd_soc_dai_driver *dai_drv,
|
|
struct snd_soc_tplg_pcm *pcm, struct snd_soc_dai *dai)
|
|
{
|
|
struct device *dev = cmpnt->dev;
|
|
struct device *dev_platform;
|
|
struct abox_tplg_dai_data *data;
|
|
struct snd_pcm_hardware *hardware, playback = {0, }, capture = {0, };
|
|
struct snd_soc_tplg_stream_caps *caps;
|
|
|
|
abox_dbg(dev, "%s(%s)\n", __func__, dai_drv->name);
|
|
|
|
if (pcm->playback) {
|
|
hardware = &playback;
|
|
caps = &pcm->caps[SNDRV_PCM_STREAM_PLAYBACK];
|
|
hardware->formats = caps->formats;
|
|
hardware->rates = caps->rates;
|
|
hardware->rate_min = caps->rate_min;
|
|
hardware->rate_max = caps->rate_max;
|
|
hardware->channels_min = caps->channels_min;
|
|
hardware->channels_max = caps->channels_max;
|
|
hardware->buffer_bytes_max = caps->buffer_size_max;
|
|
hardware->period_bytes_min = caps->period_size_min;
|
|
hardware->period_bytes_max = caps->period_size_max;
|
|
hardware->periods_min = caps->periods_min;
|
|
hardware->periods_max = caps->periods_max;
|
|
}
|
|
|
|
if (pcm->capture) {
|
|
hardware = &capture;
|
|
caps = &pcm->caps[SNDRV_PCM_STREAM_CAPTURE];
|
|
hardware->formats = caps->formats;
|
|
hardware->rates = caps->rates;
|
|
hardware->rate_min = caps->rate_min;
|
|
hardware->rate_max = caps->rate_max;
|
|
hardware->channels_min = caps->channels_min;
|
|
hardware->channels_max = caps->channels_max;
|
|
hardware->buffer_bytes_max = caps->buffer_size_max;
|
|
hardware->period_bytes_min = caps->period_size_min;
|
|
hardware->period_bytes_max = caps->period_size_max;
|
|
hardware->periods_min = caps->periods_min;
|
|
hardware->periods_max = caps->periods_max;
|
|
}
|
|
|
|
dev_platform = abox_vdma_register_component(dev,
|
|
dai_drv->id, dai_drv->name, &playback, &capture);
|
|
if (IS_ERR(dev_platform))
|
|
dev_platform = NULL;
|
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
data->id = dai_drv->id;
|
|
data->dai_drv = dai_drv;
|
|
data->dev_platform = dev_platform;
|
|
dai_drv->dobj.private = data;
|
|
list_add_tail(&data->list, &dai_list);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_dai_unload(struct snd_soc_component *cmpnt,
|
|
struct snd_soc_dobj *dobj)
|
|
{
|
|
struct abox_tplg_dai_data *data = dobj->private;
|
|
|
|
list_del(&data->list);
|
|
kfree(dobj->private);
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_link_load(struct snd_soc_component *cmpnt, int index,
|
|
struct snd_soc_dai_link *link,
|
|
struct snd_soc_tplg_link_config *cfg)
|
|
{
|
|
struct device *dev = cmpnt->dev;
|
|
struct snd_soc_dai *dai;
|
|
|
|
abox_dbg(dev, "%s(%s)\n", __func__, link->stream_name);
|
|
|
|
if (cfg) {
|
|
struct snd_soc_tplg_private *priv = &cfg->priv;
|
|
unsigned int rate, width, channels, period_size, periods;
|
|
bool packed;
|
|
|
|
rate = abox_tplg_get_int(priv, ABOX_TKN_RATE);
|
|
width = abox_tplg_get_int(priv, ABOX_TKN_WIDTH);
|
|
channels = abox_tplg_get_int(priv, ABOX_TKN_CHANNELS);
|
|
period_size = abox_tplg_get_int(priv, ABOX_TKN_PERIOD_SIZE);
|
|
periods = abox_tplg_get_int(priv, ABOX_TKN_PERIODS);
|
|
packed = abox_tplg_get_bool(priv, ABOX_TKN_PACKED);
|
|
|
|
dai = snd_soc_find_dai(link->cpus);
|
|
if (dai)
|
|
abox_dma_hw_params_set(dai->dev, rate, width, channels,
|
|
period_size, periods, packed);
|
|
else
|
|
abox_err(dev, "%s: can't find dai\n", link->name);
|
|
}
|
|
|
|
list_for_each_entry(dai, &cmpnt->dai_list, list) {
|
|
if (strcmp(dai->name, link->cpus->dai_name) == 0) {
|
|
struct abox_tplg_dai_data *data;
|
|
|
|
data = dai->driver->dobj.private;
|
|
if (!data || !data->dev_platform)
|
|
continue;
|
|
link->platforms->name = dev_name(data->dev_platform);
|
|
link->ignore_suspend = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_tplg_link_unload(struct snd_soc_component *cmpnt,
|
|
struct snd_soc_dobj *dobj)
|
|
{
|
|
/* nothing to do */
|
|
return 0;
|
|
}
|
|
|
|
static void abox_tplg_route_mux(struct snd_soc_component *cmpnt,
|
|
struct abox_tplg_widget_data *wdata)
|
|
{
|
|
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
|
|
struct snd_soc_tplg_dapm_widget *tplg_w = wdata->tplg_w;
|
|
struct snd_soc_dapm_widget *w = wdata->w;
|
|
const struct snd_kcontrol_new *kcontrol_new = w->kcontrol_news;
|
|
struct abox_tplg_widget_data *wdata_src;
|
|
struct soc_enum *e;
|
|
struct snd_soc_dapm_route route = {0, };
|
|
int i;
|
|
|
|
abox_dbg(cmpnt->dev, "%s(%s)\n", __func__, tplg_w->name);
|
|
e = (struct soc_enum *)kcontrol_new->private_value;
|
|
|
|
/* Auto-generating routes in driver is more brilliant
|
|
* than typing all routes in the topology file.
|
|
*/
|
|
for (i = 0; i < e->items; i++) {
|
|
list_for_each_entry(wdata_src, &widget_list, list) {
|
|
if (strcmp(wdata_src->tplg_w->name, e->texts[i]))
|
|
continue;
|
|
|
|
route.sink = tplg_w->name;
|
|
route.control = e->texts[i];
|
|
route.source = e->texts[i];
|
|
snd_soc_dapm_add_routes(dapm, &route, 1);
|
|
if (wdata->weak) {
|
|
route.control = NULL;
|
|
snd_soc_dapm_weak_routes(dapm, &route, 1);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void abox_tplg_complete(struct snd_soc_component *cmpnt)
|
|
{
|
|
abox_dbg(cmpnt->dev, "%s\n", __func__);
|
|
}
|
|
|
|
/* Dummy widget for connect to none. */
|
|
static const struct snd_soc_dapm_widget abox_tplg_widgets[] = {
|
|
SND_SOC_DAPM_PGA("None", SND_SOC_NOPM, 0, 0, NULL, 0),
|
|
};
|
|
|
|
static int abox_tplg_bin_load(struct device *dev,
|
|
struct snd_soc_tplg_private *priv)
|
|
{
|
|
struct abox_data *data = dev_get_drvdata(dev_abox);
|
|
const char *name;
|
|
bool changeable;
|
|
int idx, area, offset, i;
|
|
int ret = 0;
|
|
|
|
for (i = 0; ret >= 0; i++) {
|
|
idx = abox_tplg_get_int_at(priv, ABOX_BIN_IDX, i);
|
|
if (idx < 0)
|
|
break;
|
|
|
|
name = abox_tplg_get_string_at(priv, ABOX_BIN_NAME, i);
|
|
if (IS_ERR_OR_NULL(name))
|
|
break;
|
|
|
|
area = abox_tplg_get_int_at(priv, ABOX_BIN_AREA, i);
|
|
if (area < 0)
|
|
break;
|
|
|
|
offset = abox_tplg_get_int_at(priv, ABOX_BIN_OFFSET, i);
|
|
if (offset < 0)
|
|
break;
|
|
|
|
changeable = abox_tplg_get_bool_at(priv, ABOX_BIN_CHANGEABLE, i);
|
|
|
|
ret = abox_add_extra_firmware(dev, data, idx, name, area,
|
|
offset, changeable);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int abox_tplg_manifest(struct snd_soc_component *cmpnt, int index,
|
|
struct snd_soc_tplg_manifest *manifest)
|
|
{
|
|
abox_dbg(cmpnt->dev, "%s\n", __func__);
|
|
|
|
return abox_tplg_bin_load(cmpnt->dev, &manifest->priv);
|
|
}
|
|
|
|
static struct snd_soc_tplg_ops abox_tplg_ops = {
|
|
.widget_load = abox_tplg_widget_load,
|
|
.widget_ready = abox_tplg_widget_ready,
|
|
.widget_unload = abox_tplg_widget_unload,
|
|
.control_load = abox_tplg_control_load,
|
|
.control_unload = abox_tplg_control_unload,
|
|
.dai_load = abox_tplg_dai_load,
|
|
.dai_unload = abox_tplg_dai_unload,
|
|
.link_load = abox_tplg_link_load,
|
|
.link_unload = abox_tplg_link_unload,
|
|
.complete = abox_tplg_complete,
|
|
.manifest = abox_tplg_manifest,
|
|
.io_ops = abox_tplg_kcontrol_ops,
|
|
.io_ops_count = ARRAY_SIZE(abox_tplg_kcontrol_ops),
|
|
};
|
|
|
|
static irqreturn_t abox_tplg_ipc_handler(int ipc_id, void *dev_id,
|
|
ABOX_IPC_MSG *msg)
|
|
{
|
|
struct IPC_SYSTEM_MSG *system = &msg->msg.system;
|
|
irqreturn_t ret = IRQ_NONE;
|
|
|
|
switch (system->msgtype) {
|
|
case ABOX_REPORT_COMPONENT_CONTROL:
|
|
if (abox_tplg_ipc_get_complete(system->param1, system->param2,
|
|
(unsigned int *)system->bundle.param_s32) >= 0)
|
|
ret = IRQ_HANDLED;
|
|
break;
|
|
case ABOX_UPDATED_COMPONENT_CONTROL:
|
|
if (abox_tplg_ipc_put_complete(system->param1, system->param2,
|
|
(unsigned int *)system->bundle.param_s32) >= 0)
|
|
ret = IRQ_HANDLED;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int abox_tplg_restore(struct device *dev)
|
|
{
|
|
struct abox_tplg_kcontrol_data *kdata;
|
|
int i;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
mutex_lock(&kcontrol_mutex);
|
|
list_for_each_entry(kdata, &kcontrol_list, list) {
|
|
switch (kdata->hdr->ops.info) {
|
|
case SND_SOC_TPLG_CTL_ENUM:
|
|
case SND_SOC_TPLG_CTL_ENUM_VALUE:
|
|
case SND_SOC_TPLG_CTL_VOLSW:
|
|
case SND_SOC_TPLG_CTL_VOLSW_SX:
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE:
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT:
|
|
case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE:
|
|
case SND_SOC_TPLG_DAPM_CTL_VOLSW:
|
|
/* Restore non-zero value only, because
|
|
* value in firmware is already zero after reset.
|
|
*/
|
|
for (i = 0; i < kdata->count; i++) {
|
|
if (kdata->value[i] || kdata->force_restore) {
|
|
abox_tplg_kcontrol_restore(dev, kdata);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
abox_warn(dev, "unknown control %s:%d\n",
|
|
kdata->hdr->name, kdata->hdr->ops.info);
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&kcontrol_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int abox_tplg_probe(struct snd_soc_component *cmpnt)
|
|
{
|
|
static const int retry = 80;
|
|
struct device *dev = cmpnt->dev;
|
|
struct abox_tplg_firmware *tplg_fw;
|
|
struct abox_tplg_widget_data *wdata;
|
|
int i, ret;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
ret = snd_soc_dapm_new_controls(snd_soc_component_get_dapm(cmpnt),
|
|
abox_tplg_widgets, ARRAY_SIZE(abox_tplg_widgets));
|
|
if (ret < 0)
|
|
goto err_firmware;
|
|
|
|
for (tplg_fw = abox_tplg_fw; tplg_fw - abox_tplg_fw < ARRAY_SIZE(abox_tplg_fw); tplg_fw++) {
|
|
if (tplg_fw->fw) {
|
|
release_firmware(tplg_fw->fw);
|
|
tplg_fw->fw = NULL;
|
|
}
|
|
|
|
ret = firmware_request_nowarn(&tplg_fw->fw, tplg_fw->fw_name, dev);
|
|
if (ret < 0 && tplg_fw->optional) {
|
|
abox_info(dev, "not loaded %s\n", tplg_fw->fw_name);
|
|
continue;
|
|
}
|
|
|
|
for (i = retry; ret && i; i--) {
|
|
msleep(1000);
|
|
ret = firmware_request_nowarn(&tplg_fw->fw, tplg_fw->fw_name, dev);
|
|
}
|
|
if (ret < 0) {
|
|
ret = -ENODEV;
|
|
goto err_firmware;
|
|
}
|
|
|
|
abox_info(dev, "loaded %s\n", tplg_fw->fw_name);
|
|
|
|
ret = snd_soc_tplg_component_load(cmpnt, &abox_tplg_ops, tplg_fw->fw, 0);
|
|
if (ret < 0)
|
|
goto err_load;
|
|
}
|
|
|
|
list_for_each_entry(wdata, &widget_list, list) {
|
|
if (wdata->tplg_w->id == SND_SOC_TPLG_DAPM_MUX)
|
|
abox_tplg_route_mux(cmpnt, wdata);
|
|
}
|
|
|
|
ret = abox_ipc_register_handler(dev, IPC_SYSTEM, abox_tplg_ipc_handler, NULL);
|
|
if (ret < 0)
|
|
goto err_ipc;
|
|
|
|
return ret;
|
|
|
|
err_ipc:
|
|
snd_soc_tplg_component_remove(cmpnt, 0);
|
|
err_load:
|
|
for (tplg_fw = abox_tplg_fw; tplg_fw - abox_tplg_fw < ARRAY_SIZE(abox_tplg_fw); tplg_fw++) {
|
|
release_firmware(tplg_fw->fw);
|
|
tplg_fw->fw = NULL;
|
|
}
|
|
err_firmware:
|
|
return ret;
|
|
}
|
|
|
|
void abox_tplg_remove(struct snd_soc_component *cmpnt)
|
|
{
|
|
struct device *dev = cmpnt->dev;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
}
|
|
|
|
static const struct snd_soc_component_driver abox_tplg = {
|
|
.probe = abox_tplg_probe,
|
|
.remove = abox_tplg_remove,
|
|
.probe_order = SND_SOC_COMP_ORDER_LAST,
|
|
};
|
|
|
|
static int samsung_abox_tplg_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
|
|
dev_abox = dev->parent;
|
|
|
|
return snd_soc_register_component(dev, &abox_tplg, NULL, 0);
|
|
}
|
|
|
|
static int samsung_abox_tplg_remove(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct snd_soc_component *cmpnt;
|
|
struct abox_tplg_firmware *tplg_fw;
|
|
|
|
cmpnt = snd_soc_lookup_component(dev, NULL);
|
|
if (cmpnt)
|
|
snd_soc_tplg_component_remove(cmpnt, 0);
|
|
snd_soc_unregister_component(dev);
|
|
for (tplg_fw = abox_tplg_fw; tplg_fw - abox_tplg_fw < ARRAY_SIZE(abox_tplg_fw); tplg_fw++) {
|
|
if (!tplg_fw->fw)
|
|
continue;
|
|
|
|
release_firmware(tplg_fw->fw);
|
|
tplg_fw->fw = NULL;
|
|
}
|
|
dev_abox = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id samsung_abox_tplg_match[] = {
|
|
{
|
|
.compatible = "samsung,abox-tplg",
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, samsung_abox_tplg_match);
|
|
|
|
struct platform_driver samsung_abox_tplg_driver = {
|
|
.probe = samsung_abox_tplg_probe,
|
|
.remove = samsung_abox_tplg_remove,
|
|
.driver = {
|
|
.name = "abox-tplg",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(samsung_abox_tplg_match),
|
|
},
|
|
};
|