ASoC: Intel: avs: Add topology loading operations
AVS topology is split into two major parts: dictionaries - found within ASoC topology manifest - and path templates. Add custom handlers for a range of operations available in struct snd_soc_tplg_ops to allow for actually loading the topology file. Signed-off-by: Amadeusz Sławiński <amadeuszx.slawinski@linux.intel.com> Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com> Link: https://lore.kernel.org/r/20220331135246.993089-8-cezary.rojewski@intel.com Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
Родитель
eee475bb1c
Коммит
d73d1b67ff
|
@ -214,6 +214,7 @@ config SND_SOC_INTEL_AVS
|
|||
depends on PCI && ACPI
|
||||
depends on COMMON_CLK
|
||||
select SND_SOC_ACPI
|
||||
select SND_SOC_TOPOLOGY
|
||||
select SND_HDA_EXT_CORE
|
||||
select SND_HDA_DSP_LOADER
|
||||
help
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
snd-soc-avs-objs := dsp.o ipc.o messages.o utils.o core.o loader.o
|
||||
snd-soc-avs-objs := dsp.o ipc.o messages.o utils.o core.o loader.o \
|
||||
topology.o
|
||||
snd-soc-avs-objs += cldma.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC_INTEL_AVS) += snd-soc-avs.o
|
||||
|
|
|
@ -258,4 +258,6 @@ struct avs_soc_component {
|
|||
#define to_avs_soc_component(comp) \
|
||||
container_of(comp, struct avs_soc_component, base)
|
||||
|
||||
extern const struct snd_soc_dai_ops avs_dai_fe_ops;
|
||||
|
||||
#endif /* __SOUND_SOC_INTEL_AVS_H */
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com>
|
||||
//
|
||||
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/uuid.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-acpi.h>
|
||||
|
@ -14,6 +15,8 @@
|
|||
#include "avs.h"
|
||||
#include "topology.h"
|
||||
|
||||
const struct snd_soc_dai_ops avs_dai_fe_ops;
|
||||
|
||||
/* Get pointer to vendor array at the specified offset. */
|
||||
#define avs_tplg_vendor_array_at(array, offset) \
|
||||
((struct snd_soc_tplg_vendor_array *)((u8 *)array + offset))
|
||||
|
@ -369,26 +372,6 @@ parse_audio_format_bitfield(struct snd_soc_component *comp, void *elem, void *ob
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int parse_link_formatted_string(struct snd_soc_component *comp, void *elem,
|
||||
void *object, u32 offset)
|
||||
{
|
||||
struct snd_soc_tplg_vendor_string_elem *tuple = elem;
|
||||
struct snd_soc_acpi_mach *mach = dev_get_platdata(comp->card->dev);
|
||||
char *val = (char *)((u8 *)object + offset);
|
||||
|
||||
/*
|
||||
* Dynamic naming - string formats, e.g.: ssp%d - supported only for
|
||||
* topologies describing single device e.g.: an I2S codec on SSP0.
|
||||
*/
|
||||
if (hweight_long(mach->link_mask) != 1)
|
||||
return avs_parse_string_token(comp, elem, object, offset);
|
||||
|
||||
snprintf(val, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, tuple->string,
|
||||
__ffs(mach->link_mask));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_dictionary_header(struct snd_soc_component *comp,
|
||||
struct snd_soc_tplg_vendor_array *tuples,
|
||||
|
@ -945,7 +928,7 @@ static const struct avs_tplg_token_parser binding_parsers[] = {
|
|||
.token = AVS_TKN_BINDING_TARGET_TPLG_NAME_STRING,
|
||||
.type = SND_SOC_TPLG_TUPLE_TYPE_STRING,
|
||||
.offset = offsetof(struct avs_tplg_binding, target_tplg_name),
|
||||
.parse = parse_link_formatted_string,
|
||||
.parse = avs_parse_string_token,
|
||||
},
|
||||
{
|
||||
.token = AVS_TKN_BINDING_TARGET_PATH_TMPL_ID_U32,
|
||||
|
@ -1339,3 +1322,224 @@ avs_tplg_path_template_create(struct snd_soc_component *comp, struct avs_tplg *o
|
|||
|
||||
return template;
|
||||
}
|
||||
|
||||
static int avs_widget_load(struct snd_soc_component *comp, int index,
|
||||
struct snd_soc_dapm_widget *w,
|
||||
struct snd_soc_tplg_dapm_widget *dw)
|
||||
{
|
||||
struct snd_soc_acpi_mach *mach;
|
||||
struct avs_tplg_path_template *template;
|
||||
struct avs_soc_component *acomp = to_avs_soc_component(comp);
|
||||
struct avs_tplg *tplg;
|
||||
|
||||
if (!le32_to_cpu(dw->priv.size))
|
||||
return 0;
|
||||
|
||||
tplg = acomp->tplg;
|
||||
mach = dev_get_platdata(comp->card->dev);
|
||||
|
||||
template = avs_tplg_path_template_create(comp, tplg, dw->priv.array,
|
||||
le32_to_cpu(dw->priv.size));
|
||||
if (IS_ERR(template)) {
|
||||
dev_err(comp->dev, "widget %s load failed: %ld\n", dw->name,
|
||||
PTR_ERR(template));
|
||||
return PTR_ERR(template);
|
||||
}
|
||||
|
||||
w->priv = template; /* link path information to widget */
|
||||
list_add_tail(&template->node, &tplg->path_tmpl_list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int avs_dai_load(struct snd_soc_component *comp, int index,
|
||||
struct snd_soc_dai_driver *dai_drv, struct snd_soc_tplg_pcm *pcm,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
if (pcm)
|
||||
dai_drv->ops = &avs_dai_fe_ops;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int avs_link_load(struct snd_soc_component *comp, int index, struct snd_soc_dai_link *link,
|
||||
struct snd_soc_tplg_link_config *cfg)
|
||||
{
|
||||
if (!link->no_pcm) {
|
||||
/* Stream control handled by IPCs. */
|
||||
link->nonatomic = true;
|
||||
|
||||
/* Open LINK (BE) pipes last and close them first to prevent xruns. */
|
||||
link->trigger[0] = SND_SOC_DPCM_TRIGGER_PRE;
|
||||
link->trigger[1] = SND_SOC_DPCM_TRIGGER_PRE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct avs_tplg_token_parser manifest_parsers[] = {
|
||||
{
|
||||
.token = AVS_TKN_MANIFEST_NAME_STRING,
|
||||
.type = SND_SOC_TPLG_TUPLE_TYPE_STRING,
|
||||
.offset = offsetof(struct avs_tplg, name),
|
||||
.parse = avs_parse_string_token,
|
||||
},
|
||||
{
|
||||
.token = AVS_TKN_MANIFEST_VERSION_U32,
|
||||
.type = SND_SOC_TPLG_TUPLE_TYPE_WORD,
|
||||
.offset = offsetof(struct avs_tplg, version),
|
||||
.parse = avs_parse_word_token,
|
||||
},
|
||||
};
|
||||
|
||||
static int avs_manifest(struct snd_soc_component *comp, int index,
|
||||
struct snd_soc_tplg_manifest *manifest)
|
||||
{
|
||||
struct snd_soc_tplg_vendor_array *tuples = manifest->priv.array;
|
||||
struct avs_soc_component *acomp = to_avs_soc_component(comp);
|
||||
size_t remaining = le32_to_cpu(manifest->priv.size);
|
||||
u32 offset;
|
||||
int ret;
|
||||
|
||||
ret = avs_tplg_vendor_array_lookup(tuples, remaining,
|
||||
AVS_TKN_MANIFEST_NUM_LIBRARIES_U32, &offset);
|
||||
/* Manifest MUST begin with a header. */
|
||||
if (!ret && !offset)
|
||||
ret = -EINVAL;
|
||||
if (ret) {
|
||||
dev_err(comp->dev, "incorrect manifest format: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Process header which precedes any of the dictionaries. */
|
||||
ret = avs_parse_tokens(comp, acomp->tplg, manifest_parsers,
|
||||
ARRAY_SIZE(manifest_parsers), tuples, offset);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
remaining -= offset;
|
||||
tuples = avs_tplg_vendor_array_at(tuples, offset);
|
||||
|
||||
ret = avs_tplg_vendor_array_lookup(tuples, remaining,
|
||||
AVS_TKN_MANIFEST_NUM_AFMTS_U32, &offset);
|
||||
if (ret) {
|
||||
dev_err(comp->dev, "audio formats lookup failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Libraries dictionary. */
|
||||
ret = avs_tplg_parse_libraries(comp, tuples, offset);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
remaining -= offset;
|
||||
tuples = avs_tplg_vendor_array_at(tuples, offset);
|
||||
|
||||
ret = avs_tplg_vendor_array_lookup(tuples, remaining,
|
||||
AVS_TKN_MANIFEST_NUM_MODCFGS_BASE_U32, &offset);
|
||||
if (ret) {
|
||||
dev_err(comp->dev, "modcfgs_base lookup failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Audio formats dictionary. */
|
||||
ret = avs_tplg_parse_audio_formats(comp, tuples, offset);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
remaining -= offset;
|
||||
tuples = avs_tplg_vendor_array_at(tuples, offset);
|
||||
|
||||
ret = avs_tplg_vendor_array_lookup(tuples, remaining,
|
||||
AVS_TKN_MANIFEST_NUM_MODCFGS_EXT_U32, &offset);
|
||||
if (ret) {
|
||||
dev_err(comp->dev, "modcfgs_ext lookup failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Module configs-base dictionary. */
|
||||
ret = avs_tplg_parse_modcfgs_base(comp, tuples, offset);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
remaining -= offset;
|
||||
tuples = avs_tplg_vendor_array_at(tuples, offset);
|
||||
|
||||
ret = avs_tplg_vendor_array_lookup(tuples, remaining,
|
||||
AVS_TKN_MANIFEST_NUM_PPLCFGS_U32, &offset);
|
||||
if (ret) {
|
||||
dev_err(comp->dev, "pplcfgs lookup failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Module configs-ext dictionary. */
|
||||
ret = avs_tplg_parse_modcfgs_ext(comp, tuples, offset);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
remaining -= offset;
|
||||
tuples = avs_tplg_vendor_array_at(tuples, offset);
|
||||
|
||||
ret = avs_tplg_vendor_array_lookup(tuples, remaining,
|
||||
AVS_TKN_MANIFEST_NUM_BINDINGS_U32, &offset);
|
||||
if (ret) {
|
||||
dev_err(comp->dev, "bindings lookup failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Pipeline configs dictionary. */
|
||||
ret = avs_tplg_parse_pplcfgs(comp, tuples, offset);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
remaining -= offset;
|
||||
tuples = avs_tplg_vendor_array_at(tuples, offset);
|
||||
|
||||
/* Bindings dictionary. */
|
||||
return avs_tplg_parse_bindings(comp, tuples, remaining);
|
||||
}
|
||||
|
||||
static struct snd_soc_tplg_ops avs_tplg_ops = {
|
||||
.widget_load = avs_widget_load,
|
||||
.dai_load = avs_dai_load,
|
||||
.link_load = avs_link_load,
|
||||
.manifest = avs_manifest,
|
||||
};
|
||||
|
||||
struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp)
|
||||
{
|
||||
struct avs_tplg *tplg;
|
||||
|
||||
tplg = devm_kzalloc(comp->card->dev, sizeof(*tplg), GFP_KERNEL);
|
||||
if (!tplg)
|
||||
return NULL;
|
||||
|
||||
tplg->comp = comp;
|
||||
INIT_LIST_HEAD(&tplg->path_tmpl_list);
|
||||
|
||||
return tplg;
|
||||
}
|
||||
|
||||
int avs_load_topology(struct snd_soc_component *comp, const char *filename)
|
||||
{
|
||||
const struct firmware *fw;
|
||||
int ret;
|
||||
|
||||
ret = request_firmware(&fw, filename, comp->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(comp->dev, "request topology \"%s\" failed: %d\n", filename, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_tplg_component_load(comp, &avs_tplg_ops, fw);
|
||||
if (ret < 0)
|
||||
dev_err(comp->dev, "load topology \"%s\" failed: %d\n", filename, ret);
|
||||
|
||||
release_firmware(fw);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int avs_remove_topology(struct snd_soc_component *comp)
|
||||
{
|
||||
snd_soc_tplg_component_remove(comp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -186,4 +186,9 @@ struct avs_tplg_module {
|
|||
struct list_head node;
|
||||
};
|
||||
|
||||
struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp);
|
||||
|
||||
int avs_load_topology(struct snd_soc_component *comp, const char *filename);
|
||||
int avs_remove_topology(struct snd_soc_component *comp);
|
||||
|
||||
#endif
|
||||
|
|
Загрузка…
Ссылка в новой задаче