// SPDX-License-Identifier: GPL-2.0+ // // soc-topology.c -- ALSA SoC Topology // // Copyright (C) 2012 Texas Instruments Inc. // Copyright (C) 2015 Intel Corporation. // // Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> // K, Mythri P <mythri.p.k@intel.com> // Prusty, Subhransu S <subhransu.s.prusty@intel.com> // B, Jayachandran <jayachandran.b@intel.com> // Abdullah, Omair M <omair.m.abdullah@intel.com> // Jin, Yao <yao.jin@intel.com> // Lin, Mengdong <mengdong.lin@intel.com> // // Add support to read audio firmware topology alongside firmware text. The // topology data can contain kcontrols, DAPM graphs, widgets, DAIs, DAI links, // equalizers, firmware, coefficients etc. // // This file only manages the core ALSA and ASoC components, all other bespoke // firmware topology data is passed to component drivers for bespoke handling.
#define SOC_TPLG_MAGIC_BIG_ENDIAN 0x436F5341 /* ASoC in reverse */
/* * 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_TPLG_PASS_MANIFEST 0 #define SOC_TPLG_PASS_VENDOR 1 #define SOC_TPLG_PASS_CONTROL 2 #define SOC_TPLG_PASS_WIDGET 3 #define SOC_TPLG_PASS_PCM_DAI 4 #define SOC_TPLG_PASS_GRAPH 5 #define SOC_TPLG_PASS_BE_DAI 6 #define SOC_TPLG_PASS_LINK 7
/* check we dont overflow the data for this control chunk */ staticint soc_tplg_check_elem_count(struct soc_tplg *tplg, size_t elem_size, unsignedint count, size_t bytes, constchar *elem_type)
{ const u8 *end = tplg->pos + elem_size * count;
if (end > tplg->fw->data + tplg->fw->size) {
dev_err(tplg->dev, "ASoC: %s overflow end of data\n",
elem_type); 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(tplg->dev, "ASoC: %s count %d of size %zu is bigger than chunk %zu\n",
elem_type, count, elem_size, bytes); return -EINVAL;
}
staticint tplg_chan_get_reg(struct soc_tplg *tplg, struct snd_soc_tplg_channel *chan, int map)
{ int i;
for (i = 0; i < SND_SOC_TPLG_MAX_CHAN; i++) { if (le32_to_cpu(chan[i].id) == map) return le32_to_cpu(chan[i].reg);
}
return -EINVAL;
}
staticint tplg_chan_get_shift(struct soc_tplg *tplg, struct snd_soc_tplg_channel *chan, int map)
{ int i;
for (i = 0; i < SND_SOC_TPLG_MAX_CHAN; i++) { if (le32_to_cpu(chan[i].id) == map) return le32_to_cpu(chan[i].shift);
}
return -EINVAL;
}
staticint get_widget_id(int tplg_type)
{ int i;
for (i = 0; i < ARRAY_SIZE(dapm_map); i++) { if (tplg_type == dapm_map[i].uid) return dapm_map[i].kid;
}
return -EINVAL;
}
staticinlinevoid soc_control_err(struct soc_tplg *tplg, struct snd_soc_tplg_ctl_hdr *hdr, constchar *name)
{
dev_err(tplg->dev, "ASoC: no complete control IO handler for %s type (g,p,i) %d:%d:%d at 0x%lx\n",
name, hdr->ops.get, hdr->ops.put, hdr->ops.info,
soc_tplg_get_offset(tplg));
}
/* pass vendor data to component driver for processing */ staticint soc_tplg_vendor_load(struct soc_tplg *tplg, struct snd_soc_tplg_hdr *hdr)
{ int ret = 0;
if (tplg->ops && tplg->ops->vendor_load)
ret = tplg->ops->vendor_load(tplg->comp, tplg->index, hdr); else {
dev_err(tplg->dev, "ASoC: no vendor load callback for ID %d\n",
hdr->vendor_type); return -EINVAL;
}
if (ret < 0)
dev_err(tplg->dev, "ASoC: vendor load failed at hdr offset %ld/0x%lx for type %d:%d\n",
soc_tplg_get_hdr_offset(tplg),
soc_tplg_get_hdr_offset(tplg),
hdr->type, hdr->vendor_type); return ret;
}
/* optionally pass new dynamic widget to component driver. This is mainly for
* external widgets where we can assign private data/ops */ staticint soc_tplg_widget_load(struct soc_tplg *tplg, struct snd_soc_dapm_widget *w, struct snd_soc_tplg_dapm_widget *tplg_w)
{ if (tplg->ops && tplg->ops->widget_load) return tplg->ops->widget_load(tplg->comp, tplg->index, w,
tplg_w);
return 0;
}
/* optionally pass new dynamic widget to component driver. This is mainly for
* external widgets where we can assign private data/ops */ staticint soc_tplg_widget_ready(struct soc_tplg *tplg, struct snd_soc_dapm_widget *w, struct snd_soc_tplg_dapm_widget *tplg_w)
{ if (tplg->ops && tplg->ops->widget_ready) return tplg->ops->widget_ready(tplg->comp, tplg->index, w,
tplg_w);
return 0;
}
/* pass DAI configurations to component driver for extra initialization */ staticint soc_tplg_dai_load(struct soc_tplg *tplg, struct snd_soc_dai_driver *dai_drv, struct snd_soc_tplg_pcm *pcm, struct snd_soc_dai *dai)
{ if (tplg->ops && tplg->ops->dai_load) return tplg->ops->dai_load(tplg->comp, tplg->index, dai_drv,
pcm, dai);
return 0;
}
/* pass link configurations to component driver for extra initialization */ staticint soc_tplg_dai_link_load(struct soc_tplg *tplg, struct snd_soc_dai_link *link, struct snd_soc_tplg_link_config *cfg)
{ if (tplg->ops && tplg->ops->link_load) return tplg->ops->link_load(tplg->comp, tplg->index, link, cfg);
return 0;
}
/* tell the component driver that all firmware has been loaded in this request */ staticint soc_tplg_complete(struct soc_tplg *tplg)
{ if (tplg->ops && tplg->ops->complete) return tplg->ops->complete(tplg->comp);
/* Ignored links do not need to be removed, they are not added */ if (!link->ignore)
snd_soc_remove_pcm_runtime(comp->card,
snd_soc_get_pcm_runtime(comp->card, link));
}
/* unload dai link */ staticvoid remove_backend_link(struct snd_soc_component *comp, struct snd_soc_dobj *dobj, int pass)
{ if (pass != SOC_TPLG_PASS_LINK) return;
if (dobj->unload)
dobj->unload(comp, dobj);
/* * We don't free the link here as what soc_tplg_remove_link() do since BE * links are not allocated by topology. * We however need to reset the dobj type to its initial values
*/
dobj->type = SND_SOC_DOBJ_NONE;
list_del(&dobj->list);
}
/* bind a kcontrol to it's IO handlers */ staticint soc_tplg_kcontrol_bind_io(struct snd_soc_tplg_ctl_hdr *hdr, struct snd_kcontrol_new *k, conststruct soc_tplg *tplg)
{ conststruct snd_soc_tplg_kcontrol_ops *ops; conststruct snd_soc_tplg_bytes_ext_ops *ext_ops; int num_ops, i;
/* TLV bytes controls need standard kcontrol info handler, * TLV callback and extended put/get handlers.
*/
k->info = snd_soc_bytes_info_ext;
k->tlv.c = snd_soc_bytes_tlv_callback;
/* * When a topology-based implementation abuses the * control interface and uses bytes_ext controls of * more than 512 bytes, we need to disable the size * checks, otherwise accesses to such controls will * return an -EINVAL error and prevent the card from * being configured.
*/ if (sbe->max > 512)
k->access |= SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK;
ext_ops = tplg->bytes_ext_ops;
num_ops = tplg->bytes_ext_ops_count; for (i = 0; i < num_ops; i++) { if (!sbe->put &&
ext_ops[i].id == le32_to_cpu(be->ext_ops.put))
sbe->put = ext_ops[i].put; if (!sbe->get &&
ext_ops[i].id == le32_to_cpu(be->ext_ops.get))
sbe->get = ext_ops[i].get;
}
if ((k->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ) && !sbe->get) return -EINVAL; if ((k->access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) && !sbe->put) return -EINVAL; return 0;
}
/* try and map vendor specific kcontrol handlers first */
ops = tplg->io_ops;
num_ops = tplg->io_ops_count; for (i = 0; i < num_ops; i++) {
/* standard handlers found ? */ if (k->put && k->get && k->info) return 0;
/* nothing to bind */ return -EINVAL;
}
/* bind a widgets to it's evnt handlers */ int snd_soc_tplg_widget_bind_event(struct snd_soc_dapm_widget *w, conststruct snd_soc_tplg_widget_events *events, int num_events, u16 event_type)
{ int i;
w->event = NULL;
for (i = 0; i < num_events; i++) { if (event_type == events[i].type) {
/* found - so assign event */
w->event = events[i].event_handler; return 0;
}
}
/* not found */ return -EINVAL;
}
EXPORT_SYMBOL_GPL(snd_soc_tplg_widget_bind_event);
/* optionally pass new dynamic kcontrol to component driver. */ staticint soc_tplg_control_load(struct soc_tplg *tplg, struct snd_kcontrol_new *k, struct snd_soc_tplg_ctl_hdr *hdr)
{ int ret = 0;
if (tplg->ops && tplg->ops->control_load)
ret = tplg->ops->control_load(tplg->comp, tplg->index, k, hdr);
if (ret)
dev_err(tplg->dev, "ASoC: failed to init %s\n", hdr->name);
/* * Following "if" checks if we have at most SND_SOC_TPLG_NUM_TEXTS * values instead of using ARRAY_SIZE(ec->values) due to the fact that * it is oversized for its purpose. Additionally it is done so because * it is defined in UAPI header where it can't be easily changed.
*/ if (le32_to_cpu(ec->items) > SND_SOC_TPLG_NUM_TEXTS) return -EINVAL;
se->dobj.control.dvalues = devm_kcalloc(tplg->dev, le32_to_cpu(ec->items), sizeof(*se->dobj.control.dvalues),
GFP_KERNEL); if (!se->dobj.control.dvalues) return -ENOMEM;
/* convert from little-endian */ for (i = 0; i < le32_to_cpu(ec->items); i++) {
se->dobj.control.dvalues[i] = le32_to_cpu(ec->values[i]);
}
/* we only support FL/FR channel mapping atm */
se->reg = tplg_chan_get_reg(tplg, ec->channel, SNDRV_CHMAP_FL);
se->shift_l = tplg_chan_get_shift(tplg, ec->channel, SNDRV_CHMAP_FL);
se->shift_r = tplg_chan_get_shift(tplg, ec->channel, SNDRV_CHMAP_FR);
se->mask = le32_to_cpu(ec->mask);
switch (le32_to_cpu(ec->hdr.ops.info)) { case SND_SOC_TPLG_CTL_ENUM_VALUE: case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE:
err = soc_tplg_denum_create_values(tplg, se, ec); if (err < 0) {
dev_err(tplg->dev, "ASoC: could not create values for %s\n", ec->hdr.name); return err;
}
fallthrough; case SND_SOC_TPLG_CTL_ENUM: case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT:
err = soc_tplg_denum_create_texts(tplg, se, ec); if (err < 0) {
dev_err(tplg->dev, "ASoC: could not create texts for %s\n", ec->hdr.name); return err;
} break; default:
dev_err(tplg->dev, "ASoC: invalid enum control type %d for %s\n",
ec->hdr.ops.info, ec->hdr.name); return -EINVAL;
}
/* create control directly */
ret = soc_tplg_add_kcontrol(tplg, &kc, &se->dobj.control.kcontrol); if (ret < 0) return ret;
list_add(&se->dobj.list, &tplg->comp->dobj_list);
return ret;
}
staticint soc_tplg_kcontrol_elems_load(struct soc_tplg *tplg, struct snd_soc_tplg_hdr *hdr)
{ int ret; int i;
dev_dbg(tplg->dev, "ASoC: adding %d kcontrols at 0x%lx\n", hdr->count,
soc_tplg_get_offset(tplg));
for (i = 0; i < le32_to_cpu(hdr->count); i++) { struct snd_soc_tplg_ctl_hdr *control_hdr = (struct snd_soc_tplg_ctl_hdr *)tplg->pos;
if (le32_to_cpu(control_hdr->size) != sizeof(*control_hdr)) {
dev_err(tplg->dev, "ASoC: invalid control size\n"); return -EINVAL;
}
switch (le32_to_cpu(control_hdr->type)) { case SND_SOC_TPLG_TYPE_MIXER:
ret = soc_tplg_dmixer_create(tplg, le32_to_cpu(hdr->payload_size)); break; case SND_SOC_TPLG_TYPE_ENUM:
ret = soc_tplg_denum_create(tplg, le32_to_cpu(hdr->payload_size)); break; case SND_SOC_TPLG_TYPE_BYTES:
ret = soc_tplg_dbytes_create(tplg, le32_to_cpu(hdr->payload_size)); break; default:
ret = -EINVAL; break;
}
if (ret < 0) {
dev_err(tplg->dev, "ASoC: invalid control type: %d, index: %d at 0x%lx\n",
control_hdr->type, i, soc_tplg_get_offset(tplg)); return ret;
}
}
return 0;
}
/* optionally pass new dynamic kcontrol to component driver. */ staticint soc_tplg_add_route(struct soc_tplg *tplg, struct snd_soc_dapm_route *route)
{ if (tplg->ops && tplg->ops->dapm_route_load) return tplg->ops->dapm_route_load(tplg->comp, tplg->index,
route);
widget:
ret = soc_tplg_widget_load(tplg, &template, w); if (ret < 0) goto hdr_err;
/* card dapm mutex is held by the core if we are loading topology
* data during sound card init. */ if (snd_soc_card_is_instantiated(card))
widget = snd_soc_dapm_new_control(dapm, &template); else
widget = snd_soc_dapm_new_control_unlocked(dapm, &template); if (IS_ERR(widget)) {
ret = PTR_ERR(widget); goto hdr_err;
}
for (i = 0; i < count; i++) { struct snd_soc_tplg_dapm_widget *widget = (struct snd_soc_tplg_dapm_widget *) tplg->pos; int ret;
/* * check if widget itself fits within topology file * use sizeof instead of widget->size, as we can't be sure * it is set properly yet (file may end before it is present)
*/ if (soc_tplg_get_offset(tplg) + sizeof(*widget) >= tplg->fw->size) {
dev_err(tplg->dev, "ASoC: invalid widget data size\n"); return -EINVAL;
}
/* check if widget has proper size */ if (le32_to_cpu(widget->size) != sizeof(*widget)) {
dev_err(tplg->dev, "ASoC: invalid widget size\n"); return -EINVAL;
}
/* check if widget private data fits within topology file */ if (soc_tplg_get_offset(tplg) + le32_to_cpu(widget->priv.size) >= tplg->fw->size) {
dev_err(tplg->dev, "ASoC: invalid widget private data size\n"); return -EINVAL;
}
ret = soc_tplg_dapm_widget_create(tplg, widget); if (ret < 0) {
dev_err(tplg->dev, "ASoC: failed to load widget %s\n",
widget->name); return ret;
}
}
/* Card might not have been registered at this point. * If so, just return success.
*/ if (!snd_soc_card_is_instantiated(card)) {
dev_warn(tplg->dev, "ASoC: Parent card not yet available, widget card binding deferred\n"); return 0;
}
ret = snd_soc_dapm_new_widgets(card); if (ret < 0)
dev_err(tplg->dev, "ASoC: failed to create new widgets %d\n", ret);
if (strlen(pcm->dai_name)) {
dai_drv->name = devm_kstrdup(tplg->dev, pcm->dai_name, GFP_KERNEL); if (!dai_drv->name) {
ret = -ENOMEM; goto err;
}
}
dai_drv->id = le32_to_cpu(pcm->dai_id);
if (pcm->playback) {
stream = &dai_drv->playback;
caps = &pcm->caps[SND_SOC_TPLG_STREAM_PLAYBACK];
ret = set_stream_info(tplg, stream, caps); if (ret < 0) goto err;
}
if (pcm->capture) {
stream = &dai_drv->capture;
caps = &pcm->caps[SND_SOC_TPLG_STREAM_CAPTURE];
ret = set_stream_info(tplg, stream, caps); if (ret < 0) goto err;
}
if (pcm->compress)
dai_drv->ops = &tplg_dai_ops;
/* pass control to component driver for optional further init */
ret = soc_tplg_dai_load(tplg, dai_drv, pcm, NULL); if (ret < 0) {
dev_err(tplg->dev, "ASoC: DAI loading failed\n"); goto err;
}
/* register the DAI to the component */
dai = snd_soc_register_dai(tplg->comp, dai_drv, false); if (!dai) return -ENOMEM;
/* Create the DAI widgets here */
ret = snd_soc_dapm_new_dai_widgets(dapm, dai); if (ret != 0) {
dev_err(dai->dev, "Failed to create DAI widgets %d\n", ret);
snd_soc_unregister_dai(dai); return ret;
}
/* create the FE DAI link */ staticint soc_tplg_fe_link_create(struct soc_tplg *tplg, struct snd_soc_tplg_pcm *pcm)
{ struct snd_soc_dai_link *link; struct snd_soc_dai_link_component *dlc; int ret;
/* link + cpu + codec + platform */
link = devm_kzalloc(tplg->dev, sizeof(*link) + (3 * sizeof(*dlc)), GFP_KERNEL); if (link == NULL) return -ENOMEM;
if (strlen(pcm->pcm_name)) {
link->name = devm_kstrdup(tplg->dev, pcm->pcm_name, GFP_KERNEL);
link->stream_name = devm_kstrdup(tplg->dev, pcm->pcm_name, GFP_KERNEL); if (!link->name || !link->stream_name) {
ret = -ENOMEM; goto err;
}
}
link->id = le32_to_cpu(pcm->pcm_id);
if (strlen(pcm->dai_name)) {
link->cpus->dai_name = devm_kstrdup(tplg->dev, pcm->dai_name, GFP_KERNEL); if (!link->cpus->dai_name) {
ret = -ENOMEM; goto err;
}
}
/* * Many topology are assuming link has Codec / Platform, and * these might be overwritten at soc_tplg_dai_link_load(). * Don't use &snd_soc_dummy_dlc here.
*/
link->codecs = &dlc[1]; /* Don't use &snd_soc_dummy_dlc here */
link->codecs->name = "snd-soc-dummy";
link->codecs->dai_name = "snd-soc-dummy-dai";
link->num_codecs = 1;
link->platforms = &dlc[2]; /* Don't use &snd_soc_dummy_dlc here */
link->platforms->name = "snd-soc-dummy";
link->num_platforms = 1;
/* pass control to component driver for optional further init */
ret = soc_tplg_dai_link_load(tplg, link, NULL); if (ret < 0) {
dev_err(tplg->dev, "ASoC: FE link loading failed\n"); goto err;
}
ret = snd_soc_add_pcm_runtimes(tplg->comp->card, link, 1); if (ret < 0) { if (ret != -EPROBE_DEFER)
dev_err(tplg->dev, "ASoC: adding FE link failed\n"); goto err;
}
/* create a FE DAI and DAI link from the PCM object */ staticint soc_tplg_pcm_create(struct soc_tplg *tplg, struct snd_soc_tplg_pcm *pcm)
{ int ret;
ret = soc_tplg_dai_create(tplg, pcm); if (ret < 0) return ret;
return soc_tplg_fe_link_create(tplg, pcm);
}
staticint soc_tplg_pcm_elems_load(struct soc_tplg *tplg, struct snd_soc_tplg_hdr *hdr)
{ struct snd_soc_tplg_pcm *pcm; int count; int size; int i; int ret;
count = le32_to_cpu(hdr->count);
/* check the element size and count */
pcm = (struct snd_soc_tplg_pcm *)tplg->pos;
size = le32_to_cpu(pcm->size); if (size > sizeof(struct snd_soc_tplg_pcm)) {
dev_err(tplg->dev, "ASoC: invalid size %d for PCM elems\n",
size); return -EINVAL;
}
if (soc_tplg_check_elem_count(tplg,
size, count,
le32_to_cpu(hdr->payload_size), "PCM DAI")) return -EINVAL;
for (i = 0; i < count; i++) {
pcm = (struct snd_soc_tplg_pcm *)tplg->pos;
size = le32_to_cpu(pcm->size);
/* check ABI version by size, create a new version of pcm * if abi not match.
*/ if (size != sizeof(*pcm)) return -EINVAL;
/* create the FE DAIs and DAI links */
ret = soc_tplg_pcm_create(tplg, pcm); if (ret < 0) return ret;
/* offset by version-specific struct size and * real priv data size
*/
tplg->pos += size + le32_to_cpu(pcm->priv.size);
}
/** * set_link_hw_format - Set the HW audio format of the physical DAI link. * @link: &snd_soc_dai_link which should be updated * @cfg: physical link configs. * * Topology context contains a list of supported HW formats (configs) and * a default format ID for the physical link. This function will use this * default ID to choose the HW format to set the link's DAI format for init.
*/ staticvoid set_link_hw_format(struct snd_soc_dai_link *link, struct snd_soc_tplg_link_config *cfg)
{ struct snd_soc_tplg_hw_config *hw_config; unsignedchar bclk_provider, fsync_provider; unsignedchar invert_bclk, invert_fsync; int i;
for (i = 0; i < le32_to_cpu(cfg->num_hw_configs); i++) {
hw_config = &cfg->hw_config[i]; if (hw_config->id != cfg->default_hw_config_id) continue;
/** * snd_soc_find_dai_link - Find a DAI link * * @card: soc card * @id: DAI link ID to match * @name: DAI link name to match, optional * @stream_name: DAI link stream name to match, optional * * This function will search all existing DAI links of the soc card to * find the link of the same ID. Since DAI links may not have their * unique ID, so name and stream name should also match if being * specified. * * Return: pointer of DAI link, or NULL if not found.
*/ staticstruct snd_soc_dai_link *snd_soc_find_dai_link(struct snd_soc_card *card, int id, constchar *name, constchar *stream_name)
{ struct snd_soc_pcm_runtime *rtd;
if (name && (!link->name || !strstr(link->name, name))) continue;
if (stream_name && (!link->stream_name ||
!strstr(link->stream_name, stream_name))) continue;
return link;
}
return NULL;
}
/* Find and configure an existing physical DAI link */ staticint soc_tplg_link_config(struct soc_tplg *tplg, struct snd_soc_tplg_link_config *cfg)
{ struct snd_soc_dai_link *link; constchar *name, *stream_name;
size_t len; int ret;
len = strnlen(cfg->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); if (len == SNDRV_CTL_ELEM_ID_NAME_MAXLEN) return -EINVAL; elseif (len)
name = cfg->name; else
name = NULL;
len = strnlen(cfg->stream_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); if (len == SNDRV_CTL_ELEM_ID_NAME_MAXLEN) return -EINVAL; elseif (len)
stream_name = cfg->stream_name; else
stream_name = NULL;
link = snd_soc_find_dai_link(tplg->comp->card, le32_to_cpu(cfg->id),
name, stream_name); if (!link) {
dev_err(tplg->dev, "ASoC: physical link %s (id %d) not exist\n",
name, cfg->id); return -EINVAL;
}
/* hw format */ if (cfg->num_hw_configs)
set_link_hw_format(link, cfg);
/* flags */ if (cfg->flag_mask)
set_link_flags(link,
le32_to_cpu(cfg->flag_mask),
le32_to_cpu(cfg->flags));
/* pass control to component driver for optional further init */
ret = soc_tplg_dai_link_load(tplg, link, cfg); if (ret < 0) {
dev_err(tplg->dev, "ASoC: physical link loading failed\n"); return ret;
}
/* for unloading it in snd_soc_tplg_component_remove */
link->dobj.index = tplg->index;
link->dobj.type = SND_SOC_DOBJ_BACKEND_LINK; if (tplg->ops)
link->dobj.unload = tplg->ops->link_unload;
list_add(&link->dobj.list, &tplg->comp->dobj_list);
return 0;
}
/* Load physical link config elements from the topology context */ staticint soc_tplg_link_elems_load(struct soc_tplg *tplg, struct snd_soc_tplg_hdr *hdr)
{ struct snd_soc_tplg_link_config *link; int count; int size; int i, ret;
count = le32_to_cpu(hdr->count);
/* check the element size and count */
link = (struct snd_soc_tplg_link_config *)tplg->pos;
size = le32_to_cpu(link->size); if (size > sizeof(struct snd_soc_tplg_link_config)) {
dev_err(tplg->dev, "ASoC: invalid size %d for physical link elems\n",
size); return -EINVAL;
}
if (soc_tplg_check_elem_count(tplg, size, count,
le32_to_cpu(hdr->payload_size), "physical link config")) return -EINVAL;
/* config physical DAI links */ for (i = 0; i < count; i++) {
link = (struct snd_soc_tplg_link_config *)tplg->pos;
size = le32_to_cpu(link->size); if (size != sizeof(*link)) return -EINVAL;
ret = soc_tplg_link_config(tplg, link); if (ret < 0) return ret;
/* offset by version-specific struct size and * real priv data size
*/
tplg->pos += size + le32_to_cpu(link->priv.size);
}
return 0;
}
/** * soc_tplg_dai_config - Find and configure an existing physical DAI. * @tplg: topology context * @d: physical DAI configs. * * The physical dai should already be registered by the platform driver. * The platform driver should specify the DAI name and ID for matching.
*/ staticint soc_tplg_dai_config(struct soc_tplg *tplg, struct snd_soc_tplg_dai *d)
{ struct snd_soc_dai_link_component dai_component; struct snd_soc_dai *dai; struct snd_soc_dai_driver *dai_drv; struct snd_soc_pcm_stream *stream; struct snd_soc_tplg_stream_caps *caps; int ret;
memset(&dai_component, 0, sizeof(dai_component));
dai_component.dai_name = d->dai_name;
dai = snd_soc_find_dai(&dai_component); if (!dai) {
dev_err(tplg->dev, "ASoC: physical DAI %s not registered\n",
d->dai_name); return -EINVAL;
}
if (le32_to_cpu(d->dai_id) != dai->id) {
dev_err(tplg->dev, "ASoC: physical DAI %s id mismatch\n",
d->dai_name); return -EINVAL;
}
dai_drv = dai->driver; if (!dai_drv) return -EINVAL;
if (d->playback) {
stream = &dai_drv->playback;
caps = &d->caps[SND_SOC_TPLG_STREAM_PLAYBACK];
ret = set_stream_info(tplg, stream, caps); if (ret < 0) return ret;
}
if (d->capture) {
stream = &dai_drv->capture;
caps = &d->caps[SND_SOC_TPLG_STREAM_CAPTURE];
ret = set_stream_info(tplg, stream, caps); if (ret < 0) return ret;
}
if (d->flag_mask)
set_dai_flags(dai_drv,
le32_to_cpu(d->flag_mask),
le32_to_cpu(d->flags));
/* pass control to component driver for optional further init */
ret = soc_tplg_dai_load(tplg, dai_drv, NULL, dai); if (ret < 0) {
dev_err(tplg->dev, "ASoC: DAI loading failed\n"); return ret;
}
return 0;
}
/* load physical DAI elements */ staticint soc_tplg_dai_elems_load(struct soc_tplg *tplg, struct snd_soc_tplg_hdr *hdr)
{ int count; int i;
count = le32_to_cpu(hdr->count);
/* config the existing BE DAIs */ for (i = 0; i < count; i++) { struct snd_soc_tplg_dai *dai = (struct snd_soc_tplg_dai *)tplg->pos; int ret;
if (le32_to_cpu(dai->size) != sizeof(*dai)) {
dev_err(tplg->dev, "ASoC: invalid physical DAI size\n"); return -EINVAL;
}
ret = soc_tplg_dai_config(tplg, dai); if (ret < 0) {
dev_err(tplg->dev, "ASoC: failed to configure DAI\n"); return ret;
}
/* check ABI version by size, create a new manifest if abi not match */ if (le32_to_cpu(manifest->size) != sizeof(*manifest)) return -EINVAL;
/* pass control to component driver for optional further init */ if (tplg->ops && tplg->ops->manifest)
ret = tplg->ops->manifest(tplg->comp, tplg->index, manifest);
return ret;
}
/* validate header magic, size and type */ staticint soc_tplg_valid_header(struct soc_tplg *tplg, struct snd_soc_tplg_hdr *hdr)
{ if (le32_to_cpu(hdr->size) != sizeof(*hdr)) {
dev_err(tplg->dev, "ASoC: invalid header size for type %d at offset 0x%lx size 0x%zx.\n",
le32_to_cpu(hdr->type), soc_tplg_get_hdr_offset(tplg),
tplg->fw->size); return -EINVAL;
}
if (soc_tplg_get_hdr_offset(tplg) + le32_to_cpu(hdr->payload_size) >= tplg->fw->size) {
dev_err(tplg->dev, "ASoC: invalid header of type %d at offset %ld payload_size %d\n",
le32_to_cpu(hdr->type), soc_tplg_get_hdr_offset(tplg),
hdr->payload_size); return -EINVAL;
}
/* big endian firmware objects not supported atm */ if (le32_to_cpu(hdr->magic) == SOC_TPLG_MAGIC_BIG_ENDIAN) {
dev_err(tplg->dev, "ASoC: pass %d big endian not supported header got %x at offset 0x%lx size 0x%zx.\n",
tplg->pass, hdr->magic,
soc_tplg_get_hdr_offset(tplg), tplg->fw->size); return -EINVAL;
}
if (le32_to_cpu(hdr->magic) != SND_SOC_TPLG_MAGIC) {
dev_err(tplg->dev, "ASoC: pass %d does not have a valid header got %x at offset 0x%lx size 0x%zx.\n",
tplg->pass, hdr->magic,
soc_tplg_get_hdr_offset(tplg), tplg->fw->size); return -EINVAL;
}
/* Support ABI from version 4 */ if (le32_to_cpu(hdr->abi) > SND_SOC_TPLG_ABI_VERSION ||
le32_to_cpu(hdr->abi) < SND_SOC_TPLG_ABI_VERSION_MIN) {
dev_err(tplg->dev, "ASoC: pass %d invalid ABI version got 0x%x need 0x%x at offset 0x%lx size 0x%zx.\n",
tplg->pass, hdr->abi,
SND_SOC_TPLG_ABI_VERSION, soc_tplg_get_hdr_offset(tplg),
tplg->fw->size); return -EINVAL;
}
if (hdr->payload_size == 0) {
dev_err(tplg->dev, "ASoC: header has 0 size at offset 0x%lx.\n",
soc_tplg_get_hdr_offset(tplg)); return -EINVAL;
}
return 0;
}
/* check header type and call appropriate handler */ staticint soc_tplg_load_header(struct soc_tplg *tplg, struct snd_soc_tplg_hdr *hdr)
{ int (*elem_load)(struct soc_tplg *tplg, struct snd_soc_tplg_hdr *hdr); unsignedint hdr_pass;
switch (le32_to_cpu(hdr->type)) { case SND_SOC_TPLG_TYPE_MIXER: case SND_SOC_TPLG_TYPE_ENUM: case SND_SOC_TPLG_TYPE_BYTES:
hdr_pass = SOC_TPLG_PASS_CONTROL;
elem_load = soc_tplg_kcontrol_elems_load; break; case SND_SOC_TPLG_TYPE_DAPM_GRAPH:
hdr_pass = SOC_TPLG_PASS_GRAPH;
elem_load = soc_tplg_dapm_graph_elems_load; break; case SND_SOC_TPLG_TYPE_DAPM_WIDGET:
hdr_pass = SOC_TPLG_PASS_WIDGET;
elem_load = soc_tplg_dapm_widget_elems_load; break; case SND_SOC_TPLG_TYPE_PCM:
hdr_pass = SOC_TPLG_PASS_PCM_DAI;
elem_load = soc_tplg_pcm_elems_load; break; case SND_SOC_TPLG_TYPE_DAI:
hdr_pass = SOC_TPLG_PASS_BE_DAI;
elem_load = soc_tplg_dai_elems_load; break; case SND_SOC_TPLG_TYPE_DAI_LINK: case SND_SOC_TPLG_TYPE_BACKEND_LINK: /* physical link configurations */
hdr_pass = SOC_TPLG_PASS_LINK;
elem_load = soc_tplg_link_elems_load; break; case SND_SOC_TPLG_TYPE_MANIFEST:
hdr_pass = SOC_TPLG_PASS_MANIFEST;
elem_load = soc_tplg_manifest_load; break; default: /* bespoke vendor data object */
hdr_pass = SOC_TPLG_PASS_VENDOR;
elem_load = soc_tplg_vendor_load; break;
}
if (tplg->pass == hdr_pass) {
dev_dbg(tplg->dev, "ASoC: Got 0x%x bytes of type %d version %d vendor %d at pass %d\n",
hdr->payload_size, hdr->type, hdr->version,
hdr->vendor_type, tplg->pass); return elem_load(tplg, hdr);
}
return 0;
}
/* process the topology file headers */ staticint soc_tplg_process_headers(struct soc_tplg *tplg)
{ int ret;
/* process the header types from start to end */ for (tplg->pass = SOC_TPLG_PASS_START; tplg->pass <= SOC_TPLG_PASS_END; tplg->pass++) { struct snd_soc_tplg_hdr *hdr;
/* signal DAPM we are complete */
ret = soc_tplg_dapm_complete(tplg);
return ret;
}
staticint soc_tplg_load(struct soc_tplg *tplg)
{ int ret;
ret = soc_tplg_process_headers(tplg); if (ret == 0) return soc_tplg_complete(tplg);
return ret;
}
/* load audio component topology from "firmware" file */ int snd_soc_tplg_component_load(struct snd_soc_component *comp, conststruct snd_soc_tplg_ops *ops, conststruct firmware *fw)
{ struct soc_tplg tplg; int ret;
/* * check if we have sane parameters: * comp - needs to exist to keep and reference data while parsing * comp->card - used for setting card related parameters * comp->card->dev - used for resource management and prints * fw - we need it, as it is the very thing we parse
*/ if (!comp || !comp->card || !comp->card->dev || !fw) return -EINVAL;
/* remove dynamic controls from the component driver */ int snd_soc_tplg_component_remove(struct snd_soc_component *comp)
{ struct snd_soc_dobj *dobj, *next_dobj; int pass;
/* process the header types from end to start */ for (pass = SOC_TPLG_PASS_END; pass >= SOC_TPLG_PASS_START; pass--) {
switch (dobj->type) { case SND_SOC_DOBJ_BYTES: case SND_SOC_DOBJ_ENUM: case SND_SOC_DOBJ_MIXER:
soc_tplg_remove_kcontrol(comp, dobj, pass); break; case SND_SOC_DOBJ_GRAPH:
soc_tplg_remove_route(comp, dobj, pass); break; case SND_SOC_DOBJ_WIDGET:
soc_tplg_remove_widget(comp, dobj, pass); break; case SND_SOC_DOBJ_PCM:
soc_tplg_remove_dai(comp, dobj, pass); break; case SND_SOC_DOBJ_DAI_LINK:
soc_tplg_remove_link(comp, dobj, pass); break; case SND_SOC_DOBJ_BACKEND_LINK: /* * call link_unload ops if extra * deinitialization is needed.
*/
remove_backend_link(comp, dobj, pass); break; default:
dev_err(comp->dev, "ASoC: invalid component type %d for removal\n",
dobj->type); break;
}
}
}
/* let caller know if FW can be freed when no objects are left */ return !list_empty(&comp->dobj_list);
}
EXPORT_SYMBOL_GPL(snd_soc_tplg_component_remove);
Messung V0.5
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.