android_kernel_lenovo_1050f/sound/soc/soc-fw.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);