ASoC: Intel: avs: Add topology loading operations
authorCezary Rojewski <cezary.rojewski@intel.com>
Thu, 31 Mar 2022 13:52:39 +0000 (15:52 +0200)
committerMark Brown <broonie@kernel.org>
Tue, 19 Apr 2022 11:03:56 +0000 (12:03 +0100)
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>
sound/soc/intel/Kconfig
sound/soc/intel/avs/Makefile
sound/soc/intel/avs/avs.h
sound/soc/intel/avs/topology.c
sound/soc/intel/avs/topology.h

index d025ca0c77fabc538a5e1d3e424ca67a7bcc3cce..5da3ce1535afb9877455aa46c03b05c7a0fd2847 100644 (file)
@@ -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
index f842bfc5e97e4a6f2ff4121866fcdd82b5c1dc15..5e12a3a89e6401d8574ef3881685603f86875f98 100644 (file)
@@ -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
index 77bbaf292b01ba809f5fffb850a1a2c5856df6cf..dcdd3d92a1580c4a1f4d95562df46a61038a402f 100644 (file)
@@ -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 */
index 243ec27e7ec785cf0a488b5d5f52ef9d74a22f74..739669153f6df9374b47e949ff2d21270f003559 100644 (file)
@@ -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;
+}
index 3067d63cfcf7905c0defe9eca8ab36c25678d4c9..68e5f6312353d2cad9e3a8cbf1ac18519747bb3d 100644 (file)
@@ -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