1889 lines
49 KiB
C
1889 lines
49 KiB
C
/*
|
|
* soc-fw.c -- ALSA SoC Firmware
|
|
*
|
|
* Copyright (C) 2012 Texas Instruments Inc.
|
|
*
|
|
* Author: Liam Girdwood <lrg@ti.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* Support for audio fimrware to contain kcontrols, DAPM graphs, widgets,
|
|
* DAIs, equalizers, firmware, coefficienst etc.
|
|
*
|
|
* This file only manages the DAPM and Kcontrol components, all other firmware
|
|
* data is passed to component drivers for bespoke handling.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/export.h>
|
|
#include <linux/list.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/slab.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/soc-dapm.h>
|
|
#include <sound/soc-fw.h>
|
|
|
|
/*
|
|
* We make several passes over the data (since it wont necessarily be ordered)
|
|
* and process objects in the following order. This guarantees the component
|
|
* drivers will be ready with any vendor data before the mixers and DAPM objects
|
|
* are loaded (that may make use of the vendor data).
|
|
*/
|
|
#define SOC_FW_PASS_VENDOR 0
|
|
#define SOC_FW_PASS_MIXER 1
|
|
#define SOC_FW_PASS_COEFF SOC_FW_PASS_MIXER
|
|
#define SOC_FW_PASS_WIDGET 2
|
|
#define SOC_FW_PASS_GRAPH 3
|
|
#define SOC_FW_PASS_PINS 4
|
|
#define SOC_FW_PASS_DAI 5
|
|
|
|
#define SOC_FW_PASS_START SOC_FW_PASS_VENDOR
|
|
#define SOC_FW_PASS_END SOC_FW_PASS_DAI
|
|
|
|
struct soc_fw {
|
|
const char *file;
|
|
const struct firmware *fw;
|
|
|
|
/* runtime FW parsing */
|
|
const u8 *pos; /* read postion */
|
|
const u8 *hdr_pos; /* header position */
|
|
unsigned int pass; /* pass number */
|
|
|
|
/* component caller */
|
|
struct device *dev;
|
|
struct snd_soc_codec *codec;
|
|
struct snd_soc_platform *platform;
|
|
struct snd_soc_card *card;
|
|
u32 index;
|
|
|
|
/* kcontrol operations */
|
|
const struct snd_soc_fw_kcontrol_ops *io_ops;
|
|
int io_ops_count;
|
|
|
|
/* dai operations */
|
|
const struct snd_soc_fw_dai_ops *dai_ops;
|
|
int dai_ops_count;
|
|
|
|
/* optional fw loading callbacks to component drivers */
|
|
union {
|
|
struct snd_soc_fw_codec_ops *codec_ops;
|
|
struct snd_soc_fw_platform_ops *platform_ops;
|
|
struct snd_soc_fw_card_ops *card_ops;
|
|
};
|
|
};
|
|
|
|
static int soc_fw_process_headers(struct soc_fw *sfw);
|
|
static void soc_fw_complete(struct soc_fw *sfw);
|
|
|
|
/* List of Kcontrol types and associated operations. */
|
|
static const struct snd_soc_fw_kcontrol_ops io_ops[] = {
|
|
{SOC_CONTROL_IO_VOLSW, snd_soc_get_volsw,
|
|
snd_soc_put_volsw, snd_soc_info_volsw},
|
|
{SOC_CONTROL_IO_VOLSW_SX, snd_soc_get_volsw_sx,
|
|
snd_soc_put_volsw_sx, NULL},
|
|
{SOC_CONTROL_IO_VOLSW_S8, snd_soc_get_volsw_s8,
|
|
snd_soc_put_volsw_s8, snd_soc_info_volsw_s8},
|
|
{SOC_CONTROL_IO_ENUM, snd_soc_get_enum_double,
|
|
snd_soc_put_enum_double, snd_soc_info_enum_double},
|
|
{SOC_CONTROL_IO_ENUM_EXT, NULL,
|
|
NULL, snd_soc_info_enum_ext},
|
|
{SOC_CONTROL_IO_BYTES, snd_soc_bytes_get,
|
|
snd_soc_bytes_put, snd_soc_bytes_info},
|
|
{SOC_CONTROL_IO_BOOL_EXT, NULL,
|
|
NULL, snd_ctl_boolean_mono_info},
|
|
{SOC_CONTROL_IO_BYTES_EXT, NULL,
|
|
NULL, snd_soc_info_bytes_ext},
|
|
{SOC_CONTROL_IO_ENUM_VALUE, snd_soc_get_value_enum_double,
|
|
snd_soc_put_value_enum_double, NULL},
|
|
{SOC_CONTROL_IO_RANGE, snd_soc_get_volsw_range,
|
|
snd_soc_put_volsw_range, snd_soc_info_volsw_range},
|
|
{SOC_CONTROL_IO_VOLSW_XR_SX, snd_soc_get_xr_sx,
|
|
snd_soc_put_xr_sx, snd_soc_info_xr_sx},
|
|
{SOC_CONTROL_IO_STROBE, snd_soc_get_strobe,
|
|
snd_soc_put_strobe, NULL},
|
|
|
|
{SOC_CONTROL_IO_VOLSW_EXT, NULL,
|
|
NULL, snd_soc_info_volsw},
|
|
{SOC_DAPM_IO_VOLSW, snd_soc_dapm_get_volsw,
|
|
snd_soc_dapm_put_volsw, NULL},
|
|
{SOC_DAPM_IO_ENUM_DOUBLE, snd_soc_dapm_get_enum_double,
|
|
snd_soc_dapm_put_enum_double, snd_soc_info_enum_double},
|
|
{SOC_DAPM_IO_ENUM_VIRT, snd_soc_dapm_get_enum_virt,
|
|
snd_soc_dapm_put_enum_virt, NULL},
|
|
{SOC_DAPM_IO_ENUM_VALUE, snd_soc_dapm_get_value_enum_double,
|
|
snd_soc_dapm_put_value_enum_double, NULL},
|
|
{SOC_DAPM_IO_PIN, snd_soc_dapm_get_pin_switch,
|
|
snd_soc_dapm_put_pin_switch, snd_soc_dapm_info_pin_switch},
|
|
};
|
|
|
|
static inline void soc_fw_list_add_enum(struct soc_fw *sfw, struct soc_enum *se)
|
|
{
|
|
if (sfw->codec)
|
|
list_add(&se->list, &sfw->codec->denums);
|
|
else if (sfw->platform)
|
|
list_add(&se->list, &sfw->platform->denums);
|
|
else if (sfw->card)
|
|
list_add(&se->list, &sfw->card->denums);
|
|
else
|
|
BUG();
|
|
}
|
|
|
|
static inline void soc_fw_list_add_mixer(struct soc_fw *sfw,
|
|
struct soc_mixer_control *mc)
|
|
{
|
|
if (sfw->codec)
|
|
list_add(&mc->list, &sfw->codec->dmixers);
|
|
else if (sfw->platform)
|
|
list_add(&mc->list, &sfw->platform->dmixers);
|
|
else if (sfw->card)
|
|
list_add(&mc->list, &sfw->card->dmixers);
|
|
else
|
|
BUG();
|
|
}
|
|
|
|
static inline void soc_fw_list_add_bytes(struct soc_fw *sfw,
|
|
struct soc_bytes_ext *sb)
|
|
{
|
|
if (sfw->codec)
|
|
list_add(&sb->list, &sfw->codec->dbytes);
|
|
else if (sfw->platform)
|
|
list_add(&sb->list, &sfw->platform->dbytes);
|
|
else if (sfw->card)
|
|
list_add(&sb->list, &sfw->card->dbytes);
|
|
else
|
|
dev_err(sfw->dev, "Cannot add dbytes no valid type\n");
|
|
}
|
|
static inline struct snd_soc_dapm_context *soc_fw_dapm_get(struct soc_fw *sfw)
|
|
{
|
|
if (sfw->codec)
|
|
return &sfw->codec->dapm;
|
|
else if (sfw->platform)
|
|
return &sfw->platform->dapm;
|
|
else if (sfw->card)
|
|
return &sfw->card->dapm;
|
|
BUG();
|
|
}
|
|
|
|
static inline struct snd_soc_card *soc_fw_card_get(struct soc_fw *sfw)
|
|
{
|
|
if (sfw->codec)
|
|
return sfw->codec->card;
|
|
else if (sfw->platform)
|
|
return sfw->platform->card;
|
|
else if (sfw->card)
|
|
return sfw->card;
|
|
BUG();
|
|
}
|
|
|
|
/* check we dont overflow the data for this control chunk */
|
|
static int soc_fw_check_control_count(struct soc_fw *sfw, size_t elem_size,
|
|
unsigned int count, size_t bytes)
|
|
{
|
|
const u8 *end = sfw->pos + elem_size * count;
|
|
|
|
if (end > sfw->fw->data + sfw->fw->size) {
|
|
dev_err(sfw->dev, "ASoC: controls overflow end of data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* check there is enough room in chunk for control.
|
|
extra bytes at the end of control are for vendor data here */
|
|
if (elem_size * count > bytes) {
|
|
dev_err(sfw->dev,
|
|
"ASoC: controls count %d of elem size %zu are bigger than chunk %zu\n",
|
|
count, elem_size, bytes);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int soc_fw_is_eof(struct soc_fw *sfw)
|
|
{
|
|
const u8 *end = sfw->hdr_pos;
|
|
|
|
if (end >= sfw->fw->data + sfw->fw->size)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static inline unsigned int soc_fw_get_hdr_offset(struct soc_fw *sfw)
|
|
{
|
|
return (unsigned int)(sfw->hdr_pos - sfw->fw->data);
|
|
}
|
|
|
|
static inline unsigned int soc_fw_get_offset(struct soc_fw *sfw)
|
|
{
|
|
return (unsigned int)(sfw->pos - sfw->fw->data);
|
|
}
|
|
|
|
/* pass vendor data to component driver for processing */
|
|
static int soc_fw_vendor_load_(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (sfw->codec && sfw->codec_ops && sfw->codec_ops->vendor_load)
|
|
ret = sfw->codec_ops->vendor_load(sfw->codec, hdr);
|
|
|
|
if (sfw->platform && sfw->platform_ops && sfw->platform_ops->vendor_load)
|
|
ret = sfw->platform_ops->vendor_load(sfw->platform, hdr);
|
|
|
|
if (sfw->card && sfw->card_ops && sfw->card_ops->vendor_load)
|
|
ret = sfw->card_ops->vendor_load(sfw->card, hdr);
|
|
|
|
if (ret < 0)
|
|
dev_err(sfw->dev, "ASoC: vendor load failed at hdr offset %d/0x%x for type %d:%d\n",
|
|
soc_fw_get_hdr_offset(sfw), soc_fw_get_hdr_offset(sfw),
|
|
hdr->type, hdr->vendor_type);
|
|
return ret;
|
|
}
|
|
|
|
/* pass vendor data to component driver for processing */
|
|
static int soc_fw_vendor_load(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
|
|
{
|
|
if (sfw->pass != SOC_FW_PASS_VENDOR)
|
|
return 0;
|
|
|
|
return soc_fw_vendor_load_(sfw, hdr);
|
|
}
|
|
|
|
/* optionally pass new dynamic widget to component driver. mainly for external
|
|
widgets where we can assign private data/ops */
|
|
static int soc_fw_widget_load(struct soc_fw *sfw, struct snd_soc_dapm_widget *w,
|
|
struct snd_soc_fw_dapm_widget *fw_w)
|
|
{
|
|
if (sfw->codec && sfw->codec_ops && sfw->codec_ops->widget_load)
|
|
return sfw->codec_ops->widget_load(sfw->codec, w, fw_w);
|
|
|
|
if (sfw->platform && sfw->platform_ops && sfw->platform_ops->widget_load)
|
|
return sfw->platform_ops->widget_load(sfw->platform, w, fw_w);
|
|
|
|
if (sfw->card && sfw->card_ops && sfw->card_ops->widget_load)
|
|
return sfw->card_ops->widget_load(sfw->card, w, fw_w);
|
|
|
|
dev_dbg(sfw->dev, "ASoC: no handler specified for ext widget %s\n",
|
|
w->name);
|
|
return 0;
|
|
}
|
|
|
|
/* pass new dynamic dais to component driver for dai registration */
|
|
static int soc_fw_dai_load(struct soc_fw *sfw, struct snd_soc_dai_driver *dai_drv, int n)
|
|
{
|
|
if (sfw->platform && sfw->platform_ops && sfw->platform_ops->dai_load)
|
|
return sfw->platform_ops->dai_load(sfw->platform, dai_drv, n);
|
|
|
|
dev_dbg(sfw->dev, "ASoC: no handler specified for dai registration\n");
|
|
|
|
return 0;
|
|
}
|
|
/* tell the component driver that all firmware has been loaded in this request */
|
|
static void soc_fw_complete(struct soc_fw *sfw)
|
|
{
|
|
if (sfw->codec && sfw->codec_ops && sfw->codec_ops->complete)
|
|
sfw->codec_ops->complete(sfw->codec);
|
|
if (sfw->platform && sfw->platform_ops && sfw->platform_ops->complete)
|
|
sfw->platform_ops->complete(sfw->platform);
|
|
if (sfw->card && sfw->card_ops && sfw->card_ops->complete)
|
|
sfw->card_ops->complete(sfw->card);
|
|
}
|
|
|
|
/* add a dynamic kcontrol */
|
|
static int soc_fw_add_dcontrol(struct snd_card *card, struct device *dev,
|
|
const struct snd_kcontrol_new *control_new, const char *prefix,
|
|
void *data, struct snd_kcontrol **kcontrol)
|
|
{
|
|
int err;
|
|
|
|
*kcontrol = snd_soc_cnew(control_new, data, control_new->name, prefix);
|
|
if (*kcontrol == NULL) {
|
|
dev_err(dev, "ASoC: Failed to create new kcontrol %s\n",
|
|
control_new->name);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
err = snd_ctl_add(card, *kcontrol);
|
|
if (err < 0) {
|
|
kfree(*kcontrol);
|
|
dev_err(dev, "ASoC: Failed to add %s: %d\n", control_new->name,
|
|
err);
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* add a dynamic kcontrol for component driver */
|
|
static int soc_fw_add_kcontrol(struct soc_fw *sfw, struct snd_kcontrol_new *k,
|
|
struct snd_kcontrol **kcontrol)
|
|
{
|
|
if (sfw->codec) {
|
|
struct snd_soc_codec *codec = sfw->codec;
|
|
|
|
return soc_fw_add_dcontrol(codec->card->snd_card, codec->dev,
|
|
k, codec->name_prefix, codec, kcontrol);
|
|
} else if (sfw->platform) {
|
|
struct snd_soc_platform *platform = sfw->platform;
|
|
|
|
return soc_fw_add_dcontrol(platform->card->snd_card,
|
|
platform->dev, k, NULL, platform, kcontrol);
|
|
} else if (sfw->card) {
|
|
struct snd_soc_card *card = sfw->card;
|
|
|
|
return soc_fw_add_dcontrol(card->snd_card, card->dev,
|
|
k, NULL, card, kcontrol);
|
|
} else
|
|
dev_dbg(sfw->dev,
|
|
"ASoC: no handler specified for kcontrol %s\n", k->name);
|
|
return 0;
|
|
}
|
|
|
|
/* bind a kcontrol to it's IO handlers */
|
|
static int soc_fw_kcontrol_bind_io(u32 io_type, struct snd_kcontrol_new *k,
|
|
const struct snd_soc_fw_kcontrol_ops *ops, int num_ops)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < num_ops; i++) {
|
|
|
|
if (SOC_CONTROL_GET_ID_PUT(ops[i].id) ==
|
|
SOC_CONTROL_GET_ID_PUT(io_type) && ops[i].put)
|
|
k->put = ops[i].put;
|
|
if (SOC_CONTROL_GET_ID_GET(ops[i].id) ==
|
|
SOC_CONTROL_GET_ID_GET(io_type) && ops[i].get)
|
|
k->get = ops[i].get;
|
|
if (SOC_CONTROL_GET_ID_INFO(ops[i].id) ==
|
|
SOC_CONTROL_GET_ID_INFO(io_type) && ops[i].info)
|
|
k->info = ops[i].info;
|
|
}
|
|
|
|
/* let the caller know if we need to bind external kcontrols */
|
|
if (!k->put || !k->get || !k->info)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int snd_soc_fw_widget_bind_event(u16 event_type, struct snd_soc_dapm_widget *w,
|
|
const struct snd_soc_fw_widget_events *events, int num_events)
|
|
{
|
|
int i;
|
|
|
|
w->event = NULL;
|
|
|
|
if (event_type == 0) {
|
|
pr_debug("ASoC: No event type registered\n");
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < num_events; i++) {
|
|
if (event_type == events[i].type) {
|
|
w->event = events[i].event_handler;
|
|
break;
|
|
}
|
|
}
|
|
if (!w->event)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_soc_fw_widget_bind_event);
|
|
|
|
/* optionally pass new dynamic kcontrol to component driver. */
|
|
static int soc_fw_init_kcontrol(struct soc_fw *sfw, struct snd_kcontrol_new *k)
|
|
{
|
|
if (sfw->codec && sfw->codec_ops && sfw->codec_ops->control_load)
|
|
return sfw->codec_ops->control_load(sfw->codec, k);
|
|
|
|
if (sfw->platform && sfw->platform_ops && sfw->platform_ops->control_load)
|
|
return sfw->platform_ops->control_load(sfw->platform, k);
|
|
|
|
if (sfw->card && sfw->card_ops && sfw->card_ops->control_load)
|
|
return sfw->card_ops->control_load(sfw->card, k);
|
|
|
|
dev_dbg(sfw->dev, "ASoC: no handler specified for kcontrol %s\n",
|
|
k->name);
|
|
return 0;
|
|
}
|
|
|
|
/* optionally pass private data to be habdled by component driver. */
|
|
static int soc_fw_init_pvt_data(struct soc_fw *sfw, u32 io_type, unsigned long sm,
|
|
unsigned long mc)
|
|
{
|
|
if (sfw->codec && sfw->codec_ops && sfw->codec_ops->pvt_load)
|
|
return sfw->codec_ops->pvt_load(sfw->codec, io_type, sm, mc);
|
|
|
|
if (sfw->platform && sfw->platform_ops && sfw->platform_ops->pvt_load)
|
|
return sfw->platform_ops->pvt_load(sfw->platform, io_type, sm, mc);
|
|
|
|
if (sfw->card && sfw->card_ops && sfw->card_ops->pvt_load)
|
|
return sfw->card_ops->pvt_load(sfw->card, io_type, sm, mc);
|
|
|
|
dev_dbg(sfw->dev, "ASoC: no handler specified for pvt data copy\n");
|
|
return 0;
|
|
}
|
|
|
|
static int soc_fw_create_tlv(struct soc_fw *sfw, struct snd_kcontrol_new *kc,
|
|
u32 tlv_size)
|
|
{
|
|
struct snd_soc_fw_ctl_tlv *fw_tlv;
|
|
struct snd_ctl_tlv *tlv;
|
|
|
|
if (tlv_size == 0)
|
|
return 0;
|
|
|
|
fw_tlv = (struct snd_soc_fw_ctl_tlv *) sfw->pos;
|
|
sfw->pos += tlv_size;
|
|
|
|
tlv = kzalloc(sizeof(*tlv) + tlv_size, GFP_KERNEL);
|
|
if (tlv == NULL)
|
|
return -ENOMEM;
|
|
|
|
dev_dbg(sfw->dev, " created TLV type %d size %d bytes\n",
|
|
fw_tlv->numid, fw_tlv->length);
|
|
tlv->numid = fw_tlv->numid;
|
|
tlv->length = fw_tlv->length;
|
|
memcpy(tlv->tlv, fw_tlv + 1, fw_tlv->length);
|
|
kc->tlv.p = (void *)tlv;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void soc_fw_free_tlv(struct soc_fw *sfw,
|
|
struct snd_kcontrol_new *kc)
|
|
{
|
|
kfree(kc->tlv.p);
|
|
}
|
|
|
|
static int soc_fw_dbytes_create(struct soc_fw *sfw, unsigned int count,
|
|
size_t size)
|
|
{
|
|
struct snd_soc_fw_bytes_ext *be;
|
|
struct soc_bytes_ext *sbe;
|
|
struct snd_kcontrol_new kc;
|
|
int i, err, ext;
|
|
|
|
if (soc_fw_check_control_count(sfw,
|
|
sizeof(struct snd_soc_fw_bytes_ext), count, size)) {
|
|
dev_err(sfw->dev, "Asoc: Invalid count %d for byte control\n",
|
|
count);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
be = (struct snd_soc_fw_bytes_ext *)sfw->pos;
|
|
|
|
/* validate kcontrol */
|
|
if (strnlen(be->hdr.name, SND_SOC_FW_TEXT_SIZE) ==
|
|
SND_SOC_FW_TEXT_SIZE)
|
|
return -EINVAL;
|
|
|
|
sbe = kzalloc(sizeof(*sbe) + be->pvt_data_len, GFP_KERNEL);
|
|
if (!sbe)
|
|
return -ENOMEM;
|
|
|
|
sfw->pos += (sizeof(struct snd_soc_fw_bytes_ext) + be->pvt_data_len);
|
|
|
|
dev_dbg(sfw->dev,
|
|
"ASoC: adding bytes kcontrol %s with access 0x%x\n",
|
|
be->hdr.name, be->hdr.access);
|
|
|
|
memset(&kc, 0, sizeof(kc));
|
|
kc.name = be->hdr.name;
|
|
kc.private_value = (long)sbe;
|
|
kc.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
|
|
kc.access = be->hdr.access;
|
|
|
|
sbe->max = be->max;
|
|
|
|
if (be->pvt_data_len)
|
|
soc_fw_init_pvt_data(sfw, be->hdr.index, (unsigned long)sbe, (unsigned long)be);
|
|
|
|
INIT_LIST_HEAD(&sbe->list);
|
|
|
|
/* map standard io handlers and check for external handlers */
|
|
ext = soc_fw_kcontrol_bind_io(be->hdr.index, &kc, io_ops,
|
|
ARRAY_SIZE(io_ops));
|
|
|
|
if (ext) {
|
|
/* none exist, so now try and map ext handlers */
|
|
ext = soc_fw_kcontrol_bind_io(be->hdr.index, &kc,
|
|
sfw->io_ops, sfw->io_ops_count);
|
|
if (ext) {
|
|
dev_err(sfw->dev,
|
|
"ASoC: no complete mixer IO handler for %s type (g,p,i) %d:%d:%d\n",
|
|
be->hdr.name,
|
|
SOC_CONTROL_GET_ID_GET(be->hdr.index),
|
|
SOC_CONTROL_GET_ID_PUT(be->hdr.index),
|
|
SOC_CONTROL_GET_ID_INFO(be->hdr.index));
|
|
kfree(sbe);
|
|
continue;
|
|
}
|
|
|
|
err = soc_fw_init_kcontrol(sfw, &kc);
|
|
if (err < 0) {
|
|
dev_err(sfw->dev, "ASoC: failed to init %s\n",
|
|
be->hdr.name);
|
|
kfree(sbe);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* register control here */
|
|
err = soc_fw_add_kcontrol(sfw, &kc, &sbe->dcontrol);
|
|
if (err < 0) {
|
|
dev_err(sfw->dev, "ASoC: failed to add %s\n", be->hdr.name);
|
|
kfree(sbe);
|
|
continue;
|
|
}
|
|
/* This needs to be change to a widget which would not work
|
|
* unless we made changes to snd_soc_code, snd_soc_platform as
|
|
* that has only enumns and mixer everywhere that list is used*/
|
|
soc_fw_list_add_bytes(sfw, sbe);
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
static int soc_fw_dmixer_create(struct soc_fw *sfw, unsigned int count,
|
|
size_t size)
|
|
{
|
|
struct snd_soc_fw_mixer_control *mc;
|
|
struct soc_mixer_control *sm;
|
|
struct snd_kcontrol_new kc;
|
|
int i, err, ext;
|
|
|
|
if (soc_fw_check_control_count(sfw,
|
|
sizeof(struct snd_soc_fw_mixer_control), count, size)) {
|
|
dev_err(sfw->dev, "ASoC: invalid count %d for controls\n",
|
|
count);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
mc = (struct snd_soc_fw_mixer_control *)sfw->pos;
|
|
|
|
/* validate kcontrol */
|
|
if (strnlen(mc->hdr.name, SND_SOC_FW_TEXT_SIZE) ==
|
|
SND_SOC_FW_TEXT_SIZE)
|
|
return -EINVAL;
|
|
|
|
sm = kzalloc(sizeof(*sm) + mc->pvt_data_len, GFP_KERNEL);
|
|
if (!sm)
|
|
return -ENOMEM;
|
|
sfw->pos += (sizeof(struct snd_soc_fw_mixer_control) + mc->pvt_data_len);
|
|
|
|
dev_dbg(sfw->dev,
|
|
"ASoC: adding mixer kcontrol %s with access 0x%x\n",
|
|
mc->hdr.name, mc->hdr.access);
|
|
|
|
memset(&kc, 0, sizeof(kc));
|
|
kc.name = mc->hdr.name;
|
|
kc.private_value = (long)sm;
|
|
kc.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
|
|
kc.access = mc->hdr.access;
|
|
|
|
sm->reg = mc->reg;
|
|
sm->rreg = mc->rreg;
|
|
sm->shift = mc->shift;
|
|
sm->rshift = mc->rshift;
|
|
sm->max = mc->max;
|
|
sm->min = mc->min;
|
|
sm->invert = mc->invert;
|
|
sm->platform_max = mc->platform_max;
|
|
sm->index = sfw->index;
|
|
if (mc->pvt_data_len)
|
|
soc_fw_init_pvt_data(sfw, mc->hdr.index, (unsigned long)sm, (unsigned long)mc);
|
|
|
|
INIT_LIST_HEAD(&sm->list);
|
|
|
|
/* map standard io handlers and check for external handlers */
|
|
ext = soc_fw_kcontrol_bind_io(mc->hdr.index, &kc, io_ops,
|
|
ARRAY_SIZE(io_ops));
|
|
if (ext) {
|
|
/* none exist, so now try and map ext handlers */
|
|
ext = soc_fw_kcontrol_bind_io(mc->hdr.index, &kc,
|
|
sfw->io_ops, sfw->io_ops_count);
|
|
if (ext) {
|
|
dev_err(sfw->dev,
|
|
"ASoC: no complete mixer IO handler for %s type (g,p,i) %d:%d:%d\n",
|
|
mc->hdr.name,
|
|
SOC_CONTROL_GET_ID_GET(mc->hdr.index),
|
|
SOC_CONTROL_GET_ID_PUT(mc->hdr.index),
|
|
SOC_CONTROL_GET_ID_INFO(mc->hdr.index));
|
|
kfree(sm);
|
|
continue;
|
|
}
|
|
|
|
err = soc_fw_init_kcontrol(sfw, &kc);
|
|
if (err < 0) {
|
|
dev_err(sfw->dev, "ASoC: failed to init %s\n",
|
|
mc->hdr.name);
|
|
kfree(sm);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* create any TLV data */
|
|
soc_fw_create_tlv(sfw, &kc, mc->hdr.tlv_size);
|
|
|
|
/* register control here */
|
|
err = soc_fw_add_kcontrol(sfw, &kc, &sm->dcontrol);
|
|
if (err < 0) {
|
|
dev_err(sfw->dev, "ASoC: failed to add %s\n", mc->hdr.name);
|
|
soc_fw_free_tlv(sfw, &kc);
|
|
kfree(sm);
|
|
continue;
|
|
}
|
|
|
|
soc_fw_list_add_mixer(sfw, sm);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void soc_fw_denum_free_data(struct soc_enum *se)
|
|
{
|
|
int i;
|
|
|
|
kfree(se->dvalues);
|
|
for (i = 0; i < se->max - 1; i++)
|
|
kfree(se->dtexts[i]);
|
|
}
|
|
|
|
static int soc_fw_denum_create_texts(struct soc_enum *se,
|
|
struct snd_soc_fw_enum_control *ec)
|
|
{
|
|
int i, ret;
|
|
|
|
se->dtexts = kzalloc(sizeof(char *) * ec->max, GFP_KERNEL);
|
|
if (se->dtexts == NULL)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < ec->max; i++) {
|
|
|
|
if (strnlen(ec->texts[i], SND_SOC_FW_TEXT_SIZE) ==
|
|
SND_SOC_FW_TEXT_SIZE) {
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
se->dtexts[i] = kstrdup(ec->texts[i], GFP_KERNEL);
|
|
if (!se->dtexts[i]) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
for (--i; i >= 0; i--)
|
|
kfree(se->dtexts[i]);
|
|
kfree(se->dtexts);
|
|
return ret;
|
|
}
|
|
|
|
static int soc_fw_denum_create_values(struct soc_enum *se,
|
|
struct snd_soc_fw_enum_control *ec)
|
|
{
|
|
if (ec->max > sizeof(*ec->values))
|
|
return -EINVAL;
|
|
|
|
se->dvalues = kmalloc(ec->max * sizeof(u32), GFP_KERNEL);
|
|
if (!se->dvalues)
|
|
return -ENOMEM;
|
|
|
|
memcpy(se->dvalues, ec->values, ec->max * sizeof(u32));
|
|
return 0;
|
|
}
|
|
|
|
static int soc_fw_denum_create(struct soc_fw *sfw, unsigned int count,
|
|
size_t size)
|
|
{
|
|
struct snd_soc_fw_enum_control *ec;
|
|
struct soc_enum *se;
|
|
struct snd_kcontrol_new kc;
|
|
int i, ret, err, ext;
|
|
|
|
if (soc_fw_check_control_count(sfw,
|
|
sizeof(struct snd_soc_fw_enum_control), count, size)) {
|
|
dev_err(sfw->dev, "ASoC: invalid count %d for enum controls\n",
|
|
count);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
ec = (struct snd_soc_fw_enum_control *)sfw->pos;
|
|
sfw->pos += (sizeof(struct snd_soc_fw_enum_control) + ec->pvt_data_len);
|
|
|
|
/* validate kcontrol */
|
|
if (strnlen(ec->hdr.name, SND_SOC_FW_TEXT_SIZE) ==
|
|
SND_SOC_FW_TEXT_SIZE)
|
|
return -EINVAL;
|
|
|
|
se = kzalloc((sizeof(*se) + ec->pvt_data_len), GFP_KERNEL);
|
|
if (!se)
|
|
return -ENOMEM;
|
|
|
|
dev_dbg(sfw->dev, "ASoC: adding enum kcontrol %s size %d\n",
|
|
ec->hdr.name, ec->max);
|
|
|
|
memset(&kc, 0, sizeof(kc));
|
|
kc.name = ec->hdr.name;
|
|
kc.private_value = (long)se;
|
|
kc.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
|
|
kc.access = ec->hdr.access;
|
|
|
|
se->reg = ec->reg;
|
|
se->reg2 = ec->reg2;
|
|
se->shift_l = ec->shift_l;
|
|
se->shift_r = ec->shift_r;
|
|
se->max = ec->max;
|
|
se->mask = ec->mask;
|
|
se->index = sfw->index;
|
|
se->pvt_data_len = ec->pvt_data_len;
|
|
if (ec->pvt_data_len)
|
|
soc_fw_init_pvt_data(sfw, ec->hdr.index, (unsigned long)se, (unsigned long)ec);
|
|
|
|
INIT_LIST_HEAD(&se->list);
|
|
|
|
switch (SOC_CONTROL_GET_ID_INFO(ec->hdr.index)) {
|
|
case SOC_DAPM_TYPE_ENUM_VALUE:
|
|
case SOC_CONTROL_TYPE_ENUM_VALUE:
|
|
err = soc_fw_denum_create_values(se, ec);
|
|
if (err < 0) {
|
|
dev_err(sfw->dev,
|
|
"ASoC: could not create values for %s\n",
|
|
ec->hdr.name);
|
|
kfree(se);
|
|
continue;
|
|
}
|
|
/* fall through and create texts */
|
|
case SOC_CONTROL_TYPE_ENUM:
|
|
case SOC_CONTROL_TYPE_ENUM_EXT:
|
|
case SOC_DAPM_TYPE_ENUM_EXT:
|
|
case SOC_DAPM_TYPE_ENUM_DOUBLE:
|
|
case SOC_DAPM_TYPE_ENUM_VIRT:
|
|
err = soc_fw_denum_create_texts(se, ec);
|
|
if (err < 0) {
|
|
dev_err(sfw->dev,
|
|
"ASoC: could not create texts for %s\n",
|
|
ec->hdr.name);
|
|
kfree(se);
|
|
continue;
|
|
}
|
|
break;
|
|
default:
|
|
dev_err(sfw->dev,
|
|
"ASoC: invalid enum control type %d for %s\n",
|
|
ec->hdr.index, ec->hdr.name);
|
|
kfree(se);
|
|
continue;
|
|
}
|
|
|
|
/* map standard io handlers and check for external handlers */
|
|
ext = soc_fw_kcontrol_bind_io(ec->hdr.index, &kc, io_ops,
|
|
ARRAY_SIZE(io_ops));
|
|
if (ext) {
|
|
/* none exist, so now try and map ext handlers */
|
|
ext = soc_fw_kcontrol_bind_io(ec->hdr.index, &kc,
|
|
sfw->io_ops, sfw->io_ops_count);
|
|
if (ext) {
|
|
dev_err(sfw->dev, "ASoC: no complete enum IO handler for %s type (g,p,i) %d:%d:%d\n",
|
|
ec->hdr.name,
|
|
SOC_CONTROL_GET_ID_GET(ec->hdr.index),
|
|
SOC_CONTROL_GET_ID_PUT(ec->hdr.index),
|
|
SOC_CONTROL_GET_ID_INFO(ec->hdr.index));
|
|
kfree(se);
|
|
continue;
|
|
}
|
|
|
|
err = soc_fw_init_kcontrol(sfw, &kc);
|
|
if (err < 0) {
|
|
dev_err(sfw->dev, "ASoC: failed to init %s\n",
|
|
ec->hdr.name);
|
|
kfree(se);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* register control here */
|
|
ret = soc_fw_add_kcontrol(sfw, &kc, &se->dcontrol);
|
|
if (ret < 0) {
|
|
dev_err(sfw->dev, "ASoC: could not add kcontrol %s\n",
|
|
ec->hdr.name);
|
|
kfree(se);
|
|
continue;
|
|
}
|
|
|
|
soc_fw_list_add_enum(sfw, se);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int soc_fw_kcontrol_load(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
|
|
{
|
|
struct snd_soc_fw_kcontrol *sfwk =
|
|
(struct snd_soc_fw_kcontrol *)sfw->pos;
|
|
struct snd_soc_fw_control_hdr *control_hdr;
|
|
int i;
|
|
|
|
if (sfw->pass != SOC_FW_PASS_MIXER) {
|
|
sfw->pos += sizeof(struct snd_soc_fw_kcontrol) + hdr->size;
|
|
return 0;
|
|
}
|
|
|
|
sfw->pos += sizeof(struct snd_soc_fw_kcontrol);
|
|
|
|
dev_dbg(sfw->dev, "ASoC: adding %d kcontrols\n", sfwk->count);
|
|
|
|
for (i = 0; i < sfwk->count; i++) {
|
|
|
|
control_hdr = (struct snd_soc_fw_control_hdr *)sfw->pos;
|
|
|
|
switch (SOC_CONTROL_GET_ID_INFO(control_hdr->index)) {
|
|
case SOC_CONTROL_TYPE_VOLSW:
|
|
case SOC_CONTROL_TYPE_VOLSW_EXT:
|
|
case SOC_CONTROL_TYPE_STROBE:
|
|
case SOC_CONTROL_TYPE_VOLSW_SX:
|
|
case SOC_CONTROL_TYPE_VOLSW_S8:
|
|
case SOC_CONTROL_TYPE_VOLSW_XR_SX:
|
|
case SOC_CONTROL_TYPE_BYTES:
|
|
case SOC_CONTROL_TYPE_BOOL_EXT:
|
|
case SOC_CONTROL_TYPE_RANGE:
|
|
case SOC_DAPM_TYPE_VOLSW:
|
|
case SOC_DAPM_TYPE_PIN:
|
|
soc_fw_dmixer_create(sfw, 1, hdr->size);
|
|
break;
|
|
case SOC_CONTROL_TYPE_ENUM:
|
|
case SOC_CONTROL_TYPE_ENUM_EXT:
|
|
case SOC_CONTROL_TYPE_ENUM_VALUE:
|
|
case SOC_DAPM_TYPE_ENUM_DOUBLE:
|
|
case SOC_DAPM_TYPE_ENUM_VIRT:
|
|
case SOC_DAPM_TYPE_ENUM_VALUE:
|
|
case SOC_DAPM_TYPE_ENUM_EXT:
|
|
soc_fw_denum_create(sfw, 1, hdr->size);
|
|
break;
|
|
case SOC_CONTROL_TYPE_BYTES_EXT:
|
|
soc_fw_dbytes_create(sfw, 1, hdr->size);
|
|
break;
|
|
default:
|
|
dev_err(sfw->dev, "ASoC: invalid control type %d:%d:%d count %d\n",
|
|
SOC_CONTROL_GET_ID_GET(control_hdr->index),
|
|
SOC_CONTROL_GET_ID_PUT(control_hdr->index),
|
|
SOC_CONTROL_GET_ID_INFO(control_hdr->index),
|
|
sfwk->count);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int soc_fw_dapm_graph_load(struct soc_fw *sfw,
|
|
struct snd_soc_fw_hdr *hdr)
|
|
{
|
|
struct snd_soc_dapm_context *dapm = soc_fw_dapm_get(sfw);
|
|
struct snd_soc_dapm_route route;
|
|
struct snd_soc_fw_dapm_elems *elem_info =
|
|
(struct snd_soc_fw_dapm_elems *)sfw->pos;
|
|
struct snd_soc_fw_dapm_graph_elem *elem;
|
|
int count = elem_info->count, i;
|
|
|
|
if (sfw->pass != SOC_FW_PASS_GRAPH) {
|
|
sfw->pos += sizeof(struct snd_soc_fw_dapm_elems) + hdr->size;
|
|
return 0;
|
|
}
|
|
|
|
sfw->pos += sizeof(struct snd_soc_fw_dapm_elems);
|
|
|
|
if (soc_fw_check_control_count(sfw,
|
|
sizeof(struct snd_soc_fw_dapm_graph_elem), count, hdr->size)) {
|
|
dev_err(sfw->dev, "ASoC: invalid count %d for DAPM routes\n",
|
|
count);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_dbg(sfw->dev, "ASoC: adding %d DAPM routes\n", count);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
elem = (struct snd_soc_fw_dapm_graph_elem *)sfw->pos;
|
|
sfw->pos += sizeof(struct snd_soc_fw_dapm_graph_elem);
|
|
|
|
/* validate routes */
|
|
if (strnlen(elem->source, SND_SOC_FW_TEXT_SIZE) ==
|
|
SND_SOC_FW_TEXT_SIZE)
|
|
return -EINVAL;
|
|
if (strnlen(elem->sink, SND_SOC_FW_TEXT_SIZE) ==
|
|
SND_SOC_FW_TEXT_SIZE)
|
|
return -EINVAL;
|
|
if (strnlen(elem->control, SND_SOC_FW_TEXT_SIZE) ==
|
|
SND_SOC_FW_TEXT_SIZE)
|
|
return -EINVAL;
|
|
|
|
route.source = elem->source;
|
|
route.sink = elem->sink;
|
|
/* TODO: Connected is an additional field in dapm_route struct
|
|
* which is newly added and not in the binary, this field is
|
|
* currently not used and is set to NULL but this value should
|
|
* come from the binary. */
|
|
route.connected = NULL;
|
|
if (strnlen(elem->control, SND_SOC_FW_TEXT_SIZE) == 0)
|
|
route.control = NULL;
|
|
else
|
|
route.control = elem->control;
|
|
|
|
/* add route, but keep going if some fail */
|
|
snd_soc_dapm_add_routes(dapm, &route, 1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct snd_kcontrol_new *soc_fw_dapm_widget_dmixer_create(struct soc_fw *sfw,
|
|
int num_kcontrols)
|
|
{
|
|
struct snd_kcontrol_new *kc;
|
|
struct soc_mixer_control *sm;
|
|
struct snd_soc_fw_mixer_control *mc;
|
|
int i, err, ext;
|
|
|
|
kc = kzalloc(sizeof(*kc) * num_kcontrols, GFP_KERNEL);
|
|
if (!kc)
|
|
return NULL;
|
|
|
|
for (i = 0; i < num_kcontrols; i++) {
|
|
mc = (struct snd_soc_fw_mixer_control *)sfw->pos;
|
|
sm = kzalloc(sizeof(*sm) + mc->pvt_data_len, GFP_KERNEL);
|
|
if (!sm)
|
|
goto err;
|
|
|
|
sfw->pos += (sizeof(struct snd_soc_fw_mixer_control) + mc->pvt_data_len);
|
|
|
|
/* validate kcontrol */
|
|
if (strnlen(mc->hdr.name, SND_SOC_FW_TEXT_SIZE) ==
|
|
SND_SOC_FW_TEXT_SIZE)
|
|
goto err_str;
|
|
|
|
dev_dbg(sfw->dev, " adding DAPM widget mixer control %s at %d\n",
|
|
mc->hdr.name, i);
|
|
|
|
kc[i].name = mc->hdr.name;
|
|
kc[i].private_value = (long)sm;
|
|
kc[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER;
|
|
kc[i].access = mc->hdr.access;
|
|
|
|
sm->reg = mc->reg;
|
|
sm->rreg = mc->rreg;
|
|
sm->shift = mc->shift;
|
|
sm->rshift = mc->rshift;
|
|
sm->max = mc->max;
|
|
sm->min = mc->min;
|
|
sm->invert = mc->invert;
|
|
sm->platform_max = mc->platform_max;
|
|
sm->index = sfw->index;
|
|
|
|
if (mc->pvt_data_len)
|
|
soc_fw_init_pvt_data(sfw, mc->hdr.index, (unsigned long)sm, (unsigned long)mc);
|
|
|
|
INIT_LIST_HEAD(&sm->list);
|
|
|
|
/* map standard io handlers and check for external handlers */
|
|
ext = soc_fw_kcontrol_bind_io(mc->hdr.index, &kc[i], io_ops,
|
|
ARRAY_SIZE(io_ops));
|
|
if (ext) {
|
|
/* none exist, so now try and map ext handlers */
|
|
ext = soc_fw_kcontrol_bind_io(mc->hdr.index, &kc[i],
|
|
sfw->io_ops, sfw->io_ops_count);
|
|
if (ext) {
|
|
dev_err(sfw->dev,
|
|
"ASoC: no complete widget mixer IO handler for %s type (g,p,i) %d:%d:%d\n",
|
|
mc->hdr.name,
|
|
SOC_CONTROL_GET_ID_GET(mc->hdr.index),
|
|
SOC_CONTROL_GET_ID_PUT(mc->hdr.index),
|
|
SOC_CONTROL_GET_ID_INFO(mc->hdr.index));
|
|
kfree(sm);
|
|
continue;
|
|
}
|
|
|
|
err = soc_fw_init_kcontrol(sfw, &kc[i]);
|
|
if (err < 0) {
|
|
dev_err(sfw->dev, "ASoC: failed to init %s\n",
|
|
mc->hdr.name);
|
|
kfree(sm);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
return kc;
|
|
err_str:
|
|
kfree(sm);
|
|
err:
|
|
for (--i; i >= 0; i--)
|
|
kfree((void *)kc[i].private_value);
|
|
kfree(kc);
|
|
return NULL;
|
|
}
|
|
|
|
static struct snd_kcontrol_new *soc_fw_dapm_widget_denum_create(struct soc_fw *sfw)
|
|
{
|
|
struct snd_kcontrol_new *kc;
|
|
struct snd_soc_fw_enum_control *ec;
|
|
struct soc_enum *se;
|
|
int i, err, ext;
|
|
|
|
ec = (struct snd_soc_fw_enum_control *)sfw->pos;
|
|
sfw->pos += (sizeof(struct snd_soc_fw_enum_control) + ec->pvt_data_len);
|
|
|
|
/* validate kcontrol */
|
|
if (strnlen(ec->hdr.name, SND_SOC_FW_TEXT_SIZE) ==
|
|
SND_SOC_FW_TEXT_SIZE)
|
|
return NULL;
|
|
|
|
kc = kzalloc(sizeof(*kc), GFP_KERNEL);
|
|
if (!kc)
|
|
return NULL;
|
|
|
|
se = kzalloc((sizeof(*se) + ec->pvt_data_len), GFP_KERNEL);
|
|
if (!se)
|
|
goto err;
|
|
|
|
dev_dbg(sfw->dev, " adding DAPM widget enum control %s\n",
|
|
ec->hdr.name);
|
|
|
|
kc->name = ec->hdr.name;
|
|
kc->private_value = (long)se;
|
|
kc->iface = SNDRV_CTL_ELEM_IFACE_MIXER;
|
|
kc->access = ec->hdr.access;
|
|
|
|
se->reg = ec->reg;
|
|
se->reg2 = ec->reg2;
|
|
se->shift_l = ec->shift_l;
|
|
se->shift_r = ec->shift_r;
|
|
se->max = ec->max;
|
|
se->mask = ec->mask;
|
|
se->index = sfw->index;
|
|
se->pvt_data_len = ec->pvt_data_len;
|
|
if (se->pvt_data_len)
|
|
soc_fw_init_pvt_data(sfw, ec->hdr.index, (unsigned long)se, (unsigned long)ec);
|
|
|
|
switch (SOC_CONTROL_GET_ID_INFO(ec->hdr.index)) {
|
|
case SOC_CONTROL_TYPE_ENUM_VALUE:
|
|
case SOC_DAPM_TYPE_ENUM_VALUE:
|
|
err = soc_fw_denum_create_values(se, ec);
|
|
if (err < 0) {
|
|
dev_err(sfw->dev, "ASoC: could not create values for %s\n",
|
|
ec->hdr.name);
|
|
goto err_se;
|
|
}
|
|
/* fall through to create texts */
|
|
case SOC_CONTROL_TYPE_ENUM:
|
|
case SOC_CONTROL_TYPE_ENUM_EXT:
|
|
case SOC_DAPM_TYPE_ENUM_EXT:
|
|
case SOC_DAPM_TYPE_ENUM_DOUBLE:
|
|
case SOC_DAPM_TYPE_ENUM_VIRT:
|
|
err = soc_fw_denum_create_texts(se, ec);
|
|
if (err < 0) {
|
|
dev_err(sfw->dev, "ASoC: could not create texts for %s\n",
|
|
ec->hdr.name);
|
|
goto err_se;
|
|
}
|
|
break;
|
|
default:
|
|
dev_err(sfw->dev, "ASoC: invalid enum control type %d for %s\n",
|
|
ec->hdr.index, ec->hdr.name);
|
|
goto err_se;
|
|
}
|
|
|
|
/* map standard io handlers and check for external handlers */
|
|
ext = soc_fw_kcontrol_bind_io(ec->hdr.index, kc, io_ops,
|
|
ARRAY_SIZE(io_ops));
|
|
if (ext) {
|
|
/* none exist, so now try and map ext handlers */
|
|
ext = soc_fw_kcontrol_bind_io(ec->hdr.index, kc,
|
|
sfw->io_ops, sfw->io_ops_count);
|
|
if (ext) {
|
|
dev_err(sfw->dev,
|
|
"ASoC: no complete widget enum IO handler for %s type (g,p,i) %d:%d:%d\n",
|
|
ec->hdr.name,
|
|
SOC_CONTROL_GET_ID_GET(ec->hdr.index),
|
|
SOC_CONTROL_GET_ID_PUT(ec->hdr.index),
|
|
SOC_CONTROL_GET_ID_INFO(ec->hdr.index));
|
|
goto err_se;
|
|
}
|
|
|
|
err = soc_fw_init_kcontrol(sfw, kc);
|
|
if (err < 0) {
|
|
dev_err(sfw->dev, "ASoC: failed to init %s\n",
|
|
ec->hdr.name);
|
|
goto err_se;
|
|
}
|
|
}
|
|
return kc;
|
|
|
|
err_se:
|
|
/* free values and texts */
|
|
kfree(se->dvalues);
|
|
for (i = 0; i < ec->max; i++)
|
|
kfree(se->dtexts[i]);
|
|
|
|
kfree(se);
|
|
err:
|
|
kfree(kc);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int soc_fw_dapm_widget_create(struct soc_fw *sfw,
|
|
struct snd_soc_fw_dapm_widget *w)
|
|
{
|
|
struct snd_soc_dapm_context *dapm = soc_fw_dapm_get(sfw);
|
|
struct snd_soc_dapm_widget widget;
|
|
struct snd_soc_fw_control_hdr *control_hdr;
|
|
int ret = 0;
|
|
|
|
if (strnlen(w->name, SND_SOC_FW_TEXT_SIZE) ==
|
|
SND_SOC_FW_TEXT_SIZE)
|
|
return -EINVAL;
|
|
if (strnlen(w->sname, SND_SOC_FW_TEXT_SIZE) ==
|
|
SND_SOC_FW_TEXT_SIZE)
|
|
return -EINVAL;
|
|
|
|
dev_dbg(sfw->dev, "ASoC: creating DAPM widget %s id %d\n",
|
|
w->name, w->id);
|
|
|
|
memset(&widget, 0, sizeof(widget));
|
|
widget.id = w->id;
|
|
widget.name = kstrdup(w->name, GFP_KERNEL);
|
|
if (!widget.name)
|
|
return -ENOMEM;
|
|
widget.sname = kstrdup(w->sname, GFP_KERNEL);
|
|
if (!widget.sname) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
widget.reg = w->reg;
|
|
widget.shift = w->shift;
|
|
widget.mask = w->mask;
|
|
widget.invert = w->invert;
|
|
widget.ignore_suspend = w->ignore_suspend;
|
|
widget.event_flags = w->event_flags;
|
|
widget.index = sfw->index;
|
|
widget.pvt_data_len = w->pvt_data_len;
|
|
|
|
sfw->pos += (sizeof(struct snd_soc_fw_dapm_widget) + w->pvt_data_len);
|
|
if (w->num_kcontrols == 0) {
|
|
widget.num_kcontrols = 0;
|
|
goto widget;
|
|
}
|
|
|
|
control_hdr = (struct snd_soc_fw_control_hdr *)sfw->pos;
|
|
dev_dbg(sfw->dev, "ASoC: widget %s has %d controls of type %x\n",
|
|
w->name, w->num_kcontrols, control_hdr->index);
|
|
|
|
switch (SOC_CONTROL_GET_ID_INFO(control_hdr->index)) {
|
|
case SOC_CONTROL_TYPE_VOLSW:
|
|
case SOC_CONTROL_TYPE_STROBE:
|
|
case SOC_CONTROL_TYPE_VOLSW_SX:
|
|
case SOC_CONTROL_TYPE_VOLSW_S8:
|
|
case SOC_CONTROL_TYPE_VOLSW_XR_SX:
|
|
case SOC_CONTROL_TYPE_BYTES:
|
|
case SOC_CONTROL_TYPE_BOOL_EXT:
|
|
case SOC_CONTROL_TYPE_RANGE:
|
|
case SOC_DAPM_TYPE_VOLSW:
|
|
widget.num_kcontrols = w->num_kcontrols;
|
|
widget.kcontrol_news = soc_fw_dapm_widget_dmixer_create(sfw,
|
|
widget.num_kcontrols);
|
|
if (!widget.kcontrol_news) {
|
|
ret = -ENOMEM;
|
|
goto hdr_err;
|
|
}
|
|
break;
|
|
case SOC_CONTROL_TYPE_ENUM:
|
|
case SOC_CONTROL_TYPE_ENUM_EXT:
|
|
case SOC_CONTROL_TYPE_ENUM_VALUE:
|
|
case SOC_DAPM_TYPE_ENUM_DOUBLE:
|
|
case SOC_DAPM_TYPE_ENUM_VIRT:
|
|
case SOC_DAPM_TYPE_ENUM_VALUE:
|
|
case SOC_DAPM_TYPE_ENUM_EXT:
|
|
widget.num_kcontrols = 1;
|
|
widget.kcontrol_enum = 1;
|
|
widget.kcontrol_news = soc_fw_dapm_widget_denum_create(sfw);
|
|
if (!widget.kcontrol_news) {
|
|
ret = -ENOMEM;
|
|
goto hdr_err;
|
|
}
|
|
break;
|
|
default:
|
|
dev_err(sfw->dev, "ASoC: invalid widget control type %d:%d:%d\n",
|
|
SOC_CONTROL_GET_ID_GET(control_hdr->index),
|
|
SOC_CONTROL_GET_ID_PUT(control_hdr->index),
|
|
SOC_CONTROL_GET_ID_INFO(control_hdr->index));
|
|
ret = -EINVAL;
|
|
goto hdr_err;
|
|
}
|
|
|
|
widget:
|
|
ret = soc_fw_widget_load(sfw, &widget, w);
|
|
if (ret < 0)
|
|
goto hdr_err;
|
|
|
|
ret = snd_soc_dapm_new_controls(dapm, &widget, 1);
|
|
if (ret < 0) {
|
|
dev_err(sfw->dev, "ASoC: failed to create widget %s controls\n",
|
|
w->name);
|
|
goto hdr_err;
|
|
}
|
|
|
|
hdr_err:
|
|
kfree(widget.sname);
|
|
err:
|
|
kfree(widget.name);
|
|
return ret;
|
|
}
|
|
|
|
static int soc_fw_dapm_widget_load(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
|
|
{
|
|
struct snd_soc_fw_dapm_elems *elem_info =
|
|
(struct snd_soc_fw_dapm_elems *)sfw->pos;
|
|
struct snd_soc_fw_dapm_widget *widget;
|
|
int ret, count = elem_info->count, i;
|
|
|
|
if (sfw->pass != SOC_FW_PASS_WIDGET)
|
|
return 0;
|
|
|
|
sfw->pos += sizeof(struct snd_soc_fw_dapm_elems);
|
|
|
|
if (soc_fw_check_control_count(sfw,
|
|
sizeof(struct snd_soc_fw_dapm_graph_elem), count, hdr->size)) {
|
|
dev_err(sfw->dev, "ASoC: invalid count %d for widgets\n", count);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_dbg(sfw->dev, "ASoC: adding %d DAPM widgets\n", count);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
widget = (struct snd_soc_fw_dapm_widget *) sfw->pos;
|
|
ret = soc_fw_dapm_widget_create(sfw, widget);
|
|
if (ret < 0)
|
|
dev_err(sfw->dev, "ASoC: failed to load widget %s\n",
|
|
widget->name);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int soc_fw_dapm_complete(struct soc_fw *sfw)
|
|
{
|
|
struct snd_soc_dapm_context *dapm = soc_fw_dapm_get(sfw);
|
|
int ret;
|
|
|
|
/* Card might not have been registered at this point.
|
|
* If so, just return success.
|
|
*/
|
|
if (!dapm->card) {
|
|
dev_warn(sfw->dev, "ASoC: Parent card not yet available,"
|
|
"Do not add new widgets now\n");
|
|
return 0;
|
|
}
|
|
|
|
ret = snd_soc_dapm_new_widgets(dapm);
|
|
if (ret < 0)
|
|
dev_err(sfw->dev, "ASoC: failed to create new widgets %d\n",
|
|
ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Coefficients with mixer header */
|
|
static int soc_fw_coeff_load(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
|
|
{
|
|
struct snd_soc_fw_kcontrol *sfwk =
|
|
(struct snd_soc_fw_kcontrol *)sfw->pos;
|
|
struct snd_soc_fw_control_hdr *control_hdr;
|
|
struct snd_soc_fw_hdr *vhdr;
|
|
int ret;
|
|
|
|
if (sfw->pass != SOC_FW_PASS_COEFF)
|
|
return 0;
|
|
|
|
/* vendor coefficient data is encapsulated with hdrs in generic
|
|
coefficient controls */
|
|
if (hdr->vendor_type != 0)
|
|
return 0;
|
|
|
|
dev_dbg(sfw->dev, "ASoC: got %d new coefficients\n", sfwk->count);
|
|
|
|
sfw->pos += sizeof(struct snd_soc_fw_kcontrol);
|
|
control_hdr = (struct snd_soc_fw_control_hdr *)sfw->pos;
|
|
|
|
switch (SOC_CONTROL_GET_ID_INFO(control_hdr->index)) {
|
|
case SOC_CONTROL_TYPE_ENUM:
|
|
case SOC_CONTROL_TYPE_ENUM_EXT:
|
|
case SOC_CONTROL_TYPE_ENUM_VALUE:
|
|
ret = soc_fw_denum_create(sfw, 1, hdr->size);
|
|
if (ret < 0) {
|
|
dev_err(sfw->dev, "ASoC: failed to create coeff enum %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
break;
|
|
default:
|
|
dev_err(sfw->dev, "ASoC: invalid coeff control type %d count %d\n",
|
|
SOC_CONTROL_GET_ID_INFO(control_hdr->index),
|
|
sfwk->count);
|
|
return -EINVAL;
|
|
}
|
|
|
|
vhdr = (struct snd_soc_fw_hdr *)sfw->pos;
|
|
|
|
ret = soc_fw_vendor_load_(sfw, vhdr);
|
|
if (ret < 0) {
|
|
dev_err(sfw->dev, "ASoC: unabled to load coeff data %d\n", ret);
|
|
return ret;
|
|
}
|
|
sfw->pos += sizeof(*vhdr) + vhdr->size;
|
|
vhdr = (struct snd_soc_fw_hdr *)sfw->pos;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int soc_fw_dai_link_load(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
|
|
{
|
|
/* TODO: add DAI links based on FW routing between components */
|
|
dev_err(sfw->dev, "ASoC: Firmware DAIs not supported\n");
|
|
return 0;
|
|
}
|
|
|
|
/* bind a dai to its ops */
|
|
static int soc_fw_dai_bind_ops(unsigned int dai_type, struct snd_soc_dai_driver *dai_drv,
|
|
const struct snd_soc_fw_dai_ops *ops, int num_ops)
|
|
{
|
|
int i;
|
|
|
|
dai_drv->ops = NULL;
|
|
for (i = 0; i < num_ops; i++) {
|
|
if (dai_type == ops[i].id) {
|
|
dai_drv->ops = ops[i].ops;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int soc_fw_set_dai_caps(struct snd_soc_pcm_stream *stream,
|
|
struct snd_soc_fw_dai_caps *caps)
|
|
{
|
|
/* validate stream names */
|
|
if (strnlen(caps->stream_name, SND_SOC_FW_TEXT_SIZE) ==
|
|
SND_SOC_FW_TEXT_SIZE)
|
|
return -EINVAL;
|
|
|
|
stream->stream_name = caps->stream_name;
|
|
stream->formats = caps->formats;
|
|
stream->rates = caps->rates;
|
|
stream->rate_min = caps->rate_min;
|
|
stream->rate_max = caps->rate_max;
|
|
stream->channels_min = caps->channels_min;
|
|
stream->channels_max = caps->channels_max;
|
|
return 0;
|
|
}
|
|
|
|
static int soc_fw_ddai_load(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
|
|
{
|
|
struct snd_soc_fw_dai_data *dai_data =
|
|
(struct snd_soc_fw_dai_data *)sfw->pos;
|
|
struct snd_soc_fw_dai_elem *elem;
|
|
int count = dai_data->count, i;
|
|
struct snd_soc_dai_driver *dai_drv;
|
|
|
|
if (sfw->pass != SOC_FW_PASS_DAI)
|
|
return 0;
|
|
|
|
sfw->pos += sizeof(struct snd_soc_fw_dai_data);
|
|
/* The following function for control count has been reused to
|
|
* validate the dai count as well
|
|
*/
|
|
if (soc_fw_check_control_count(sfw,
|
|
sizeof(struct snd_soc_fw_dai_elem),
|
|
count, hdr->size)) {
|
|
dev_err(sfw->dev, "ASoC: invalid count %d for DAI elems\n",
|
|
count);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_dbg(sfw->dev, "ASoC: adding %d DAIs\n", count);
|
|
dai_drv = devm_kzalloc(sfw->dev,
|
|
sizeof(struct snd_soc_dai_driver) * count, GFP_KERNEL);
|
|
if (!dai_drv) {
|
|
dev_err(sfw->dev, "ASoC: Mem alloc failue\n");
|
|
return -ENOMEM;
|
|
}
|
|
for (i = 0; i < count; i++) {
|
|
elem = (struct snd_soc_fw_dai_elem *)sfw->pos;
|
|
sfw->pos += sizeof(struct snd_soc_fw_dai_elem);
|
|
|
|
/* validate dai name */
|
|
if (strnlen(elem->name, SND_SOC_FW_TEXT_SIZE) ==
|
|
SND_SOC_FW_TEXT_SIZE) {
|
|
dev_err(sfw->dev, "ASoC: invalid dai name\n");
|
|
return -EINVAL;
|
|
}
|
|
dai_drv[i].name = elem->name;
|
|
dai_drv[i].compress_dai = elem->compress_dai;
|
|
|
|
if (elem->pb_stream) {
|
|
if (soc_fw_set_dai_caps(&dai_drv[i].playback,
|
|
&elem->playback_caps)) {
|
|
dev_err(sfw->dev, "ASoC: invalid playback stream name\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
}
|
|
if (elem->cp_stream) {
|
|
if (soc_fw_set_dai_caps(&dai_drv[i].capture,
|
|
&elem->capture_caps)) {
|
|
dev_err(sfw->dev, "ASoC: invalid capture stream name\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
soc_fw_dai_bind_ops(elem->dai_type, &dai_drv[i],
|
|
sfw->dai_ops, sfw->dai_ops_count);
|
|
}
|
|
|
|
/* Call the platform driver call back to register the dais */
|
|
return soc_fw_dai_load(sfw, dai_drv, count);
|
|
}
|
|
|
|
static int soc_valid_header(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
|
|
{
|
|
if (soc_fw_get_hdr_offset(sfw) >= sfw->fw->size)
|
|
return 0;
|
|
|
|
/* big endian firmware objects not supported atm */
|
|
if (hdr->magic == cpu_to_be32(SND_SOC_FW_MAGIC)) {
|
|
dev_err(sfw->dev,
|
|
"ASoC: %s at pass %d big endian not supported header got %x at offset 0x%x size 0x%zx.\n",
|
|
sfw->file, sfw->pass, hdr->magic,
|
|
soc_fw_get_hdr_offset(sfw), sfw->fw->size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (hdr->magic != SND_SOC_FW_MAGIC) {
|
|
dev_err(sfw->dev,
|
|
"ASoC: %s at pass %d does not have a valid header got %x at offset 0x%x size 0x%zx.\n",
|
|
sfw->file, sfw->pass, hdr->magic,
|
|
soc_fw_get_hdr_offset(sfw), sfw->fw->size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (hdr->abi != SND_SOC_FW_ABI_VERSION) {
|
|
dev_err(sfw->dev,
|
|
"ASoC: %s at pass %d invalid ABI version got 0x%x need 0x%x at offset 0x%x size 0x%zx.\n",
|
|
sfw->file, sfw->pass, hdr->abi, SND_SOC_FW_ABI_VERSION,
|
|
soc_fw_get_hdr_offset(sfw), sfw->fw->size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (hdr->size == 0) {
|
|
dev_err(sfw->dev, "ASoC: %s header has 0 size at offset 0x%x.\n",
|
|
sfw->file, soc_fw_get_hdr_offset(sfw));
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (sfw->pass == hdr->type)
|
|
dev_dbg(sfw->dev,
|
|
"ASoC: Got 0x%x bytes of type %d version %d vendor %d at pass %d\n",
|
|
hdr->size, hdr->type, hdr->version, hdr->vendor_type,
|
|
sfw->pass);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int soc_fw_load_header(struct soc_fw *sfw, struct snd_soc_fw_hdr *hdr)
|
|
{
|
|
sfw->pos = sfw->hdr_pos + sizeof(struct snd_soc_fw_hdr);
|
|
|
|
switch (hdr->type) {
|
|
case SND_SOC_FW_MIXER:
|
|
return soc_fw_kcontrol_load(sfw, hdr);
|
|
case SND_SOC_FW_DAPM_GRAPH:
|
|
return soc_fw_dapm_graph_load(sfw, hdr);
|
|
case SND_SOC_FW_DAPM_WIDGET:
|
|
return soc_fw_dapm_widget_load(sfw, hdr);
|
|
case SND_SOC_FW_DAI_LINK:
|
|
return soc_fw_dai_link_load(sfw, hdr);
|
|
case SND_SOC_FW_COEFF:
|
|
return soc_fw_coeff_load(sfw, hdr);
|
|
case SND_SOC_FW_DAI:
|
|
return soc_fw_ddai_load(sfw, hdr);
|
|
default:
|
|
return soc_fw_vendor_load(sfw, hdr);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int soc_fw_process_headers(struct soc_fw *sfw)
|
|
{
|
|
struct snd_soc_fw_hdr *hdr;
|
|
int ret;
|
|
|
|
sfw->pass = SOC_FW_PASS_START;
|
|
|
|
while (sfw->pass <= SOC_FW_PASS_END) {
|
|
|
|
sfw->hdr_pos = sfw->fw->data;
|
|
hdr = (struct snd_soc_fw_hdr *)sfw->hdr_pos;
|
|
|
|
while (!soc_fw_is_eof(sfw)) {
|
|
|
|
ret = soc_valid_header(sfw, hdr);
|
|
if (ret < 0)
|
|
return ret;
|
|
else if (ret == 0)
|
|
break;
|
|
|
|
ret = soc_fw_load_header(sfw, hdr);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
sfw->hdr_pos += hdr->size + sizeof(struct snd_soc_fw_hdr);
|
|
hdr = (struct snd_soc_fw_hdr *)sfw->hdr_pos;
|
|
}
|
|
sfw->pass++;
|
|
}
|
|
|
|
ret = soc_fw_dapm_complete(sfw);
|
|
if (ret < 0)
|
|
dev_err(sfw->dev, "ASoC: failed to initialise DAPM from Firmware\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int soc_fw_load(struct soc_fw *sfw)
|
|
{
|
|
int ret;
|
|
|
|
ret = soc_fw_process_headers(sfw);
|
|
if (ret == 0)
|
|
soc_fw_complete(sfw);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int snd_soc_fw_load_codec(struct snd_soc_codec *codec,
|
|
struct snd_soc_fw_codec_ops *ops, const struct firmware *fw,
|
|
u32 index)
|
|
{
|
|
struct soc_fw sfw;
|
|
|
|
memset(&sfw, 0, sizeof(sfw));
|
|
|
|
sfw.fw = fw;
|
|
sfw.dev = codec->dev;
|
|
sfw.codec = codec;
|
|
sfw.codec_ops = ops;
|
|
sfw.index = index;
|
|
sfw.io_ops = ops->io_ops;
|
|
sfw.io_ops_count = ops->io_ops_count;
|
|
|
|
return soc_fw_load(&sfw);
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_soc_fw_load_codec);
|
|
|
|
int snd_soc_fw_load_platform(struct snd_soc_platform *platform,
|
|
struct snd_soc_fw_platform_ops *ops, const struct firmware *fw,
|
|
u32 index)
|
|
{
|
|
struct soc_fw sfw;
|
|
|
|
memset(&sfw, 0, sizeof(sfw));
|
|
|
|
sfw.fw = fw;
|
|
sfw.dev = platform->dev;
|
|
sfw.platform = platform;
|
|
sfw.platform_ops = ops;
|
|
sfw.index = index;
|
|
sfw.io_ops = ops->io_ops;
|
|
sfw.io_ops_count = ops->io_ops_count;
|
|
sfw.dai_ops = ops->dai_ops;
|
|
sfw.dai_ops_count = ops->dai_ops_count;
|
|
|
|
return soc_fw_load(&sfw);
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_soc_fw_load_platform);
|
|
|
|
int snd_soc_fw_load_card(struct snd_soc_card *card,
|
|
struct snd_soc_fw_card_ops *ops, const struct firmware *fw,
|
|
u32 index)
|
|
{
|
|
struct soc_fw sfw;
|
|
|
|
memset(&sfw, 0, sizeof(sfw));
|
|
|
|
sfw.fw = fw;
|
|
sfw.dev = card->dev;
|
|
sfw.card = card;
|
|
sfw.card_ops = ops;
|
|
sfw.index = index;
|
|
sfw.io_ops = ops->io_ops;
|
|
sfw.io_ops_count = ops->io_ops_count;
|
|
|
|
return soc_fw_load(&sfw);
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_soc_fw_load_card);
|
|
|
|
/* remove this dynamic widget */
|
|
void snd_soc_fw_dcontrols_remove_widget(struct snd_soc_dapm_widget *w)
|
|
{
|
|
struct snd_card *card = w->dapm->card->snd_card;
|
|
int i;
|
|
|
|
/*
|
|
* Dynamic Widgets either have 1 enum kcontrol or 1..N mixers.
|
|
* The enumm may either have an array of values or strings.
|
|
*/
|
|
if (w->kcontrol_enum) {
|
|
struct soc_enum *se =
|
|
(struct soc_enum *)w->kcontrols[0]->private_value;
|
|
|
|
snd_ctl_remove(card, w->kcontrols[0]);
|
|
|
|
kfree(se->dvalues);
|
|
for (i = 0; i < se->max; i++)
|
|
kfree(se->dtexts[i]);
|
|
|
|
kfree(se);
|
|
kfree(w->kcontrol_news);
|
|
} else {
|
|
for (i = 0; i < w->num_kcontrols; i++) {
|
|
struct snd_kcontrol *kcontrol = w->kcontrols[i];
|
|
struct soc_mixer_control *sm =
|
|
(struct soc_mixer_control *) kcontrol->private_value;
|
|
|
|
kfree(w->kcontrols[i]->tlv.p);
|
|
|
|
snd_ctl_remove(card, w->kcontrols[i]);
|
|
kfree(sm);
|
|
}
|
|
kfree(w->kcontrol_news);
|
|
}
|
|
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_soc_fw_dcontrols_remove_widget);
|
|
|
|
/* remove all dynamic widgets from this context */
|
|
void snd_soc_fw_dcontrols_remove_widgets(struct snd_soc_dapm_context *dapm,
|
|
u32 index)
|
|
{
|
|
struct snd_soc_dapm_widget *w, *next_w;
|
|
struct snd_soc_dapm_path *p, *next_p;
|
|
|
|
list_for_each_entry_safe(w, next_w, &dapm->card->widgets, list) {
|
|
if (w->index != index || w->dapm != dapm)
|
|
continue;
|
|
|
|
list_del(&w->list);
|
|
/*
|
|
* remove source and sink paths associated to this widget.
|
|
* While removing the path, remove reference to it from both
|
|
* source and sink widgets so that path is removed only once.
|
|
*/
|
|
list_for_each_entry_safe(p, next_p, &w->sources, list_sink) {
|
|
list_del(&p->list_sink);
|
|
list_del(&p->list_source);
|
|
list_del(&p->list);
|
|
kfree(p->long_name);
|
|
kfree(p);
|
|
}
|
|
list_for_each_entry_safe(p, next_p, &w->sinks, list_source) {
|
|
list_del(&p->list_sink);
|
|
list_del(&p->list_source);
|
|
list_del(&p->list);
|
|
kfree(p->long_name);
|
|
kfree(p);
|
|
}
|
|
/* check and free and dynamic widget kcontrols */
|
|
snd_soc_fw_dcontrols_remove_widget(w);
|
|
kfree(w->kcontrols);
|
|
kfree(w->name);
|
|
kfree(w);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_soc_fw_dcontrols_remove_widgets);
|
|
|
|
/* remove dynamic controls from the codec driver only */
|
|
void snd_soc_fw_dcontrols_remove_codec(struct snd_soc_codec *codec,
|
|
u32 index)
|
|
{
|
|
struct soc_mixer_control *sm, *next_sm;
|
|
struct soc_enum *se, *next_se;
|
|
struct soc_bytes_ext *sb, *next_sb;
|
|
struct snd_card *card = codec->card->snd_card;
|
|
const unsigned int *p;
|
|
int i;
|
|
|
|
list_for_each_entry_safe(sm, next_sm, &codec->dmixers, list) {
|
|
|
|
p = NULL;
|
|
if (sm->index != index)
|
|
continue;
|
|
|
|
if (sm->dcontrol->tlv.p)
|
|
p = sm->dcontrol->tlv.p;
|
|
snd_ctl_remove(card, sm->dcontrol);
|
|
list_del(&sm->list);
|
|
kfree(sm);
|
|
kfree(p);
|
|
}
|
|
|
|
list_for_each_entry_safe(se, next_se, &codec->denums, list) {
|
|
|
|
if (sm->index != index)
|
|
continue;
|
|
|
|
snd_ctl_remove(card, se->dcontrol);
|
|
list_del(&se->list);
|
|
|
|
kfree(se->dvalues);
|
|
for (i = 0; i < se->max; i++)
|
|
kfree(se->dtexts[i]);
|
|
kfree(se);
|
|
}
|
|
|
|
list_for_each_entry_safe(sb, next_sb, &codec->dbytes, list) {
|
|
|
|
if (sm->index != index)
|
|
continue;
|
|
|
|
snd_ctl_remove(card, sb->dcontrol);
|
|
list_del(&sb->list);
|
|
kfree(sb);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_soc_fw_dcontrols_remove_codec);
|
|
|
|
/* remove dynamic controls from the platform driver only */
|
|
void snd_soc_fw_dcontrols_remove_platform(struct snd_soc_platform *platform,
|
|
u32 index)
|
|
{
|
|
struct soc_mixer_control *sm, *next_sm;
|
|
struct soc_enum *se, *next_se;
|
|
struct soc_bytes_ext *sb, *next_sb;
|
|
struct snd_card *card = platform->card->snd_card;
|
|
const unsigned int *p;
|
|
int i;
|
|
|
|
list_for_each_entry_safe(sm, next_sm, &platform->dmixers, list) {
|
|
|
|
p = NULL;
|
|
|
|
if (sm->index != index)
|
|
continue;
|
|
|
|
if (sm->dcontrol->tlv.p)
|
|
p = sm->dcontrol->tlv.p;
|
|
snd_ctl_remove(card, sm->dcontrol);
|
|
list_del(&sm->list);
|
|
kfree(sm);
|
|
kfree(p);
|
|
}
|
|
|
|
list_for_each_entry_safe(se, next_se, &platform->denums, list) {
|
|
|
|
if (sm->index != index)
|
|
continue;
|
|
|
|
snd_ctl_remove(card, se->dcontrol);
|
|
list_del(&se->list);
|
|
|
|
kfree(se->dvalues);
|
|
for (i = 0; i < se->max; i++)
|
|
kfree(se->dtexts[i]);
|
|
kfree(se);
|
|
}
|
|
|
|
list_for_each_entry_safe(sb, next_sb, &platform->dbytes, list) {
|
|
|
|
if (sm->index != index)
|
|
continue;
|
|
|
|
snd_ctl_remove(card, sb->dcontrol);
|
|
list_del(&sb->list);
|
|
kfree(sb);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_soc_fw_dcontrols_remove_platform);
|
|
|
|
/* remove dynamic controls from the card driver only */
|
|
void snd_soc_fw_dcontrols_remove_card(struct snd_soc_card *soc_card,
|
|
u32 index)
|
|
{
|
|
struct soc_mixer_control *sm, *next_sm;
|
|
struct soc_enum *se, *next_se;
|
|
struct soc_bytes_ext *sb, *next_sb;
|
|
struct snd_card *card = soc_card->snd_card;
|
|
const unsigned int *p;
|
|
int i;
|
|
|
|
list_for_each_entry_safe(sm, next_sm, &soc_card->dmixers, list) {
|
|
|
|
p = NULL;
|
|
if (sm->index != index)
|
|
continue;
|
|
|
|
if (sm->dcontrol->tlv.p)
|
|
p = sm->dcontrol->tlv.p;
|
|
snd_ctl_remove(card, sm->dcontrol);
|
|
list_del(&sm->list);
|
|
kfree(sm);
|
|
kfree(p);
|
|
}
|
|
|
|
list_for_each_entry_safe(se, next_se, &soc_card->denums, list) {
|
|
|
|
if (sm->index != index)
|
|
continue;
|
|
|
|
snd_ctl_remove(card, se->dcontrol);
|
|
list_del(&se->list);
|
|
|
|
kfree(se->dvalues);
|
|
for (i = 0; i < se->max; i++)
|
|
kfree(se->dtexts[i]);
|
|
kfree(se);
|
|
}
|
|
|
|
list_for_each_entry_safe(sb, next_sb, &soc_card->dbytes, list) {
|
|
|
|
if (sm->index != index)
|
|
continue;
|
|
|
|
snd_ctl_remove(card, sb->dcontrol);
|
|
list_del(&sb->list);
|
|
kfree(sb);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_soc_fw_dcontrols_remove_card);
|
|
|
|
/* remove all dynamic controls from sound card and components */
|
|
int snd_soc_fw_dcontrols_remove_all(struct snd_soc_card *card, u32 index)
|
|
{
|
|
struct snd_soc_codec *codec;
|
|
struct snd_soc_platform *platform;
|
|
|
|
list_for_each_entry(codec, &card->codec_dev_list, card_list)
|
|
snd_soc_fw_dcontrols_remove_codec(codec, index);
|
|
|
|
list_for_each_entry(platform, &card->platform_dev_list, card_list)
|
|
snd_soc_fw_dcontrols_remove_platform(platform, index);
|
|
|
|
snd_soc_fw_dcontrols_remove_card(card, index);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_soc_fw_dcontrols_remove_all);
|