Blob Blame History Raw
From aa1bac2d04bd1fb4ccae96a1136e60454298a710 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela <perex@perex.cz>
Date: Mon, 16 Dec 2019 14:26:31 +0100
Subject: [PATCH 43/63] topology: add snd_tplg_save()

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
---
 include/topology.h        |  15 ++
 src/conf.c                |  17 ++
 src/topology/Makefile.am  |   3 +-
 src/topology/channel.c    |  45 ++++
 src/topology/ctl.c        | 201 ++++++++++++++-
 src/topology/dapm.c       | 209 ++++++++++-----
 src/topology/data.c       | 601 ++++++++++++++++++++++++++++++++++---------
 src/topology/elem.c       |  18 ++
 src/topology/ops.c        |  92 +++++++
 src/topology/parser.c     |   4 +
 src/topology/pcm.c        | 505 +++++++++++++++++++++++++++++++-----
 src/topology/save.c       | 632 ++++++++++++++++++++++++++++++++++++++++++++++
 src/topology/text.c       |  19 ++
 src/topology/tplg_local.h |  58 ++++-
 14 files changed, 2162 insertions(+), 257 deletions(-)
 create mode 100644 src/topology/save.c

diff --git a/include/topology.h b/include/topology.h
index c9f4ffea27de..69aa5ed733e3 100644
--- a/include/topology.h
+++ b/include/topology.h
@@ -1124,6 +1124,21 @@ int snd_tplg_set_manifest_data(snd_tplg_t *tplg, const void *data, int len);
  */
 int snd_tplg_set_version(snd_tplg_t *tplg, unsigned int version);
 
+/*
+ * Flags for the snd_tplg_save()
+ */
+#define SND_TPLG_SAVE_SORT	(1<<0)	/*!< sort identifiers */
+#define SND_TPLG_SAVE_GROUPS	(1<<1)	/*!< create the structure by group index */
+#define SND_TPLG_SAVE_NOCHECK	(1<<16)	/*!< unchecked output for debugging */
+
+/**
+ * \brief Save the topology to the text configuration string.
+ * \param tplg Topology instance.
+ * \param dst A pointer to string with result (malloc).
+ * \return Zero on success, otherwise a negative error code
+ */
+int snd_tplg_save(snd_tplg_t *tplg, char **dst, int flags);
+
 /* \} */
 
 #ifdef __cplusplus
diff --git a/src/conf.c b/src/conf.c
index 3e753b266b8d..c4db9f21a15e 100644
--- a/src/conf.c
+++ b/src/conf.c
@@ -874,6 +874,21 @@ static int get_nonwhite(input_t *input)
 	}
 }
 
+static inline int get_hexachar(input_t *input)
+{
+	int c, num = 0;
+
+	c = get_char(input);
+	if (c >= '0' && c <= '9') num |= (c - '0') << 4;
+	else if (c >= 'a' && c <= 'f') num |= (c - 'a') << 4;
+	else if (c >= 'A' && c <= 'F') num |= (c - 'A') << 4;
+	c = get_char(input);
+	if (c >= '0' && c <= '9') num |= (c - '0') << 0;
+	else if (c >= 'a' && c <= 'f') num |= (c - 'a') << 0;
+	else if (c >= 'A' && c <= 'F') num |= (c - 'A') << 0;
+	return c;
+}
+
 static int get_quotedchar(input_t *input)
 {
 	int c;
@@ -891,6 +906,8 @@ static int get_quotedchar(input_t *input)
 		return '\r';
 	case 'f':
 		return '\f';
+	case 'x':
+		return get_hexachar(input);
 	case '0': case '1': case '2': case '3':
 	case '4': case '5': case '6': case '7':
 	{
diff --git a/src/topology/Makefile.am b/src/topology/Makefile.am
index 9dc472d62fac..a850ec4c195c 100644
--- a/src/topology/Makefile.am
+++ b/src/topology/Makefile.am
@@ -27,7 +27,8 @@ libatopology_la_SOURCES =\
 	text.c \
 	channel.c \
 	ops.c \
-	elem.c
+	elem.c \
+	save.c
 
 noinst_HEADERS = tplg_local.h
 
diff --git a/src/topology/channel.c b/src/topology/channel.c
index 4569eb31e0c5..b54a10c89379 100644
--- a/src/topology/channel.c
+++ b/src/topology/channel.c
@@ -73,6 +73,18 @@ static int lookup_channel(const char *c)
 	return -EINVAL;
 }
 
+const char *tplg_channel_name(int type)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(channel_map); i++) {
+		if (channel_map[i].id == type)
+			return channel_map[i].name;
+	}
+
+	return NULL;
+}
+
 /* Parse a channel mapping. */
 int tplg_parse_channel(snd_tplg_t *tplg, snd_config_t *cfg,
 		       void *private)
@@ -123,3 +135,36 @@ int tplg_parse_channel(snd_tplg_t *tplg, snd_config_t *cfg,
 	tplg->channel_idx++;
 	return 0;
 }
+
+int tplg_save_channels(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+		       struct snd_soc_tplg_channel *channel,
+		       unsigned int count, char **dst, const char *pfx)
+{
+	struct snd_soc_tplg_channel *c;
+	const char *s;
+	unsigned int index;
+	int err;
+
+	if (count == 0)
+		return 0;
+	err = tplg_save_printf(dst, pfx, "channel {\n");
+	for (index = 0; err >= 0 && index < count; index++) {
+		c = channel + index;
+		s = tplg_channel_name(c->id);
+		if (s == NULL)
+			err = tplg_save_printf(dst, pfx, "\t%u", c->id);
+		else
+			err = tplg_save_printf(dst, pfx, "\t%s", s);
+		if (err >= 0)
+			err = tplg_save_printf(dst, NULL, " {\n");
+		if (err >= 0)
+			err = tplg_save_printf(dst, pfx, "\t\treg %d\n", c->reg);
+		if (err >= 0 && c->shift > 0)
+			err = tplg_save_printf(dst, pfx, "\t\tshift %u\n", c->shift);
+		if (err >= 0)
+			err = tplg_save_printf(dst, pfx, "\t}\n");
+	}
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return err;
+}
diff --git a/src/topology/ctl.c b/src/topology/ctl.c
index 539329cd661f..979cc1b035f1 100644
--- a/src/topology/ctl.c
+++ b/src/topology/ctl.c
@@ -28,15 +28,16 @@ struct ctl_access_elem {
 };
 
 /* CTL access strings and codes */
+/* place the multi-bit values on top - like read_write - for save */
 static const struct ctl_access_elem ctl_access[] = {
+	{"read_write", SNDRV_CTL_ELEM_ACCESS_READWRITE},
+	{"tlv_read_write", SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE},
 	{"read", SNDRV_CTL_ELEM_ACCESS_READ},
 	{"write", SNDRV_CTL_ELEM_ACCESS_WRITE},
-	{"read_write", SNDRV_CTL_ELEM_ACCESS_READWRITE},
 	{"volatile", SNDRV_CTL_ELEM_ACCESS_VOLATILE},
 	{"timestamp", SNDRV_CTL_ELEM_ACCESS_TIMESTAMP},
 	{"tlv_read", SNDRV_CTL_ELEM_ACCESS_TLV_READ},
 	{"tlv_write", SNDRV_CTL_ELEM_ACCESS_TLV_WRITE},
-	{"tlv_read_write", SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE},
 	{"tlv_command", SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND},
 	{"inactive", SNDRV_CTL_ELEM_ACCESS_INACTIVE},
 	{"lock", SNDRV_CTL_ELEM_ACCESS_LOCK},
@@ -103,6 +104,46 @@ int parse_access(snd_config_t *cfg,
 	return err;
 }
 
+/* Save Access */
+static int tplg_save_access(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+			    struct snd_soc_tplg_ctl_hdr *hdr, char **dst,
+			    const char *pfx)
+{
+	const char *last;
+	unsigned int j, count, access, cval;
+	int err;
+
+	if (hdr->access == 0)
+		return 0;
+
+	access = hdr->access;
+	for (j = 0, count = 0, last = NULL; j < ARRAY_SIZE(ctl_access); j++) {
+		cval = ctl_access[j].value;
+		if ((access & cval) == cval) {
+			access &= ~cval;
+			last = ctl_access[j].name;
+			count++;
+		}
+	}
+	if (count == 1)
+		return tplg_save_printf(dst, pfx, "access.0 %s\n", last);
+	err = tplg_save_printf(dst, pfx, "access [\n");
+	if (err < 0)
+		return err;
+	access = hdr->access;
+	for (j = 0; j < ARRAY_SIZE(ctl_access); j++) {
+		cval = ctl_access[j].value;
+		if ((access & cval) == cval) {
+			err = tplg_save_printf(dst, pfx, "\t%s\n",
+					       ctl_access[j].name);
+			if (err < 0)
+				return err;
+			access &= ~cval;
+		}
+	}
+	return tplg_save_printf(dst, pfx, "]\n");
+}
+
 /* copy referenced TLV to the mixer control */
 static int copy_tlv(struct tplg_elem *elem, struct tplg_elem *ref)
 {
@@ -358,6 +399,37 @@ int tplg_parse_tlv(snd_tplg_t *tplg, snd_config_t *cfg,
 	return err;
 }
 
+/* save TLV data */
+int tplg_save_tlv(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+		  struct tplg_elem *elem,
+		  char **dst, const char *pfx)
+{
+	struct snd_soc_tplg_ctl_tlv *tlv = elem->tlv;
+	struct snd_soc_tplg_tlv_dbscale *scale;
+	int err;
+
+	if (tlv->type != SNDRV_CTL_TLVT_DB_SCALE) {
+		SNDERR("unknown TLV type");
+		return -EINVAL;
+	}
+
+	scale = &tlv->scale;
+	err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id);
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "\tscale {\n");
+	if (err >= 0 && scale->min)
+		err = tplg_save_printf(dst, pfx, "\t\tmin %i\n", scale->min);
+	if (err >= 0 && scale->step > 0)
+		err = tplg_save_printf(dst, pfx, "\t\tstep %i\n", scale->step);
+	if (err >= 0 && scale->mute > 0)
+		err = tplg_save_printf(dst, pfx, "\t\tmute %i\n", scale->mute);
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "\t}\n");
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return err;
+}
+
 /* Parse Control Bytes */
 int tplg_parse_control_bytes(snd_tplg_t *tplg,
 			     snd_config_t *cfg,
@@ -430,7 +502,7 @@ int tplg_parse_control_bytes(snd_tplg_t *tplg,
 		}
 
 		if (strcmp(id, "data") == 0) {
-			err = tplg_parse_data_refs(n, elem);
+			err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA);
 			if (err < 0)
 				return err;
 			continue;
@@ -485,6 +557,49 @@ int tplg_parse_control_bytes(snd_tplg_t *tplg,
 	return 0;
 }
 
+/* save control bytes */
+int tplg_save_control_bytes(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+			    struct tplg_elem *elem,
+			    char **dst, const char *pfx)
+{
+	struct snd_soc_tplg_bytes_control *be = elem->bytes_ext;
+	char pfx2[16];
+	int err;
+
+	if (!be)
+		return 0;
+
+	snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: "");
+	err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id);
+	if (err < 0)
+		return err;
+	if (err >= 0 && elem->index > 0)
+		err = tplg_save_printf(dst, pfx, "\tindex %u\n", elem->index);
+	if (err >= 0 && be->base > 0)
+		err = tplg_save_printf(dst, pfx, "\tbase %u\n", be->base);
+	if (err >= 0 && be->num_regs > 0)
+		err = tplg_save_printf(dst, pfx, "\tnum_regs %u\n", be->num_regs);
+	if (err >= 0 && be->max > 0)
+		err = tplg_save_printf(dst, pfx, "\tmax %u\n", be->max);
+	if (err >= 0 && be->mask > 0)
+		err = tplg_save_printf(dst, pfx, "\tmask %u\n", be->mask);
+	if (err >= 0)
+		err = tplg_save_ops(tplg, &be->hdr, dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_ext_ops(tplg, be, dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_access(tplg, &be->hdr, dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_TLV,
+				     "tlv", dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_DATA,
+				     "data", dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return err;
+}
+
 /* Parse Control Enums. */
 int tplg_parse_control_enum(snd_tplg_t *tplg, snd_config_t *cfg,
 	void *private ATTRIBUTE_UNUSED)
@@ -559,7 +674,7 @@ int tplg_parse_control_enum(snd_tplg_t *tplg, snd_config_t *cfg,
 		}
 
 		if (strcmp(id, "data") == 0) {
-			err = tplg_parse_data_refs(n, elem);
+			err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA);
 			if (err < 0)
 				return err;
 			continue;
@@ -582,6 +697,42 @@ int tplg_parse_control_enum(snd_tplg_t *tplg, snd_config_t *cfg,
 	return 0;
 }
 
+/* save control eunm */
+int tplg_save_control_enum(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+			   struct tplg_elem *elem,
+			   char **dst, const char *pfx)
+{
+	struct snd_soc_tplg_enum_control *ec = elem->enum_ctrl;
+	char pfx2[16];
+	int err;
+
+	if (!ec)
+		return 0;
+
+	snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: "");
+	err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id);
+	if (err < 0)
+		return err;
+	if (err >= 0 && elem->index > 0)
+		err = tplg_save_printf(dst, pfx, "\tindex %u\n", elem->index);
+	if (err >= 0)
+		err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_TEXT,
+				     "texts", dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_channels(tplg, ec->channel, ec->num_channels,
+					 dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_ops(tplg, &ec->hdr, dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_access(tplg, &ec->hdr, dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_DATA,
+				     "data", dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return err;
+}
+
 /* Parse Controls.
  *
  * Mixer control. Supports multiple channels.
@@ -683,7 +834,7 @@ int tplg_parse_control_mixer(snd_tplg_t *tplg,
 		}
 
 		if (strcmp(id, "data") == 0) {
-			err = tplg_parse_data_refs(n, elem);
+			err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA);
 			if (err < 0)
 				return err;
 			continue;
@@ -709,6 +860,46 @@ int tplg_parse_control_mixer(snd_tplg_t *tplg,
 	return 0;
 }
 
+int tplg_save_control_mixer(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+			    struct tplg_elem *elem, char **dst,
+			    const char *pfx)
+{
+	struct snd_soc_tplg_mixer_control *mc = elem->mixer_ctrl;
+	char pfx2[16];
+	int err;
+
+	if (!mc)
+		return 0;
+	err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id);
+	if (err < 0)
+		return err;
+	snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: "");
+	if (err >= 0 && elem->index > 0)
+		err = tplg_save_printf(dst, pfx, "\tindex %u\n", elem->index);
+	if (err >= 0)
+		err = tplg_save_channels(tplg, mc->channel, mc->num_channels,
+					 dst, pfx2);
+	if (err >= 0 && mc->max > 0)
+		err = tplg_save_printf(dst, pfx, "\tmax %u\n", mc->max);
+	if (err >= 0 && mc->invert > 0)
+		err = tplg_save_printf(dst, pfx, "\tinvert 1\n", mc->max);
+	if (err >= 0 && mc->invert > 0)
+		err = tplg_save_printf(dst, pfx, "\tinvert 1\n", mc->max);
+	if (err >= 0)
+		err = tplg_save_ops(tplg, &mc->hdr, dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_access(tplg, &mc->hdr, dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_TLV,
+				     "tlv", dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_DATA,
+				     "data", dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return err;
+}
+
 static int init_ctl_hdr(struct snd_soc_tplg_ctl_hdr *hdr,
 		struct snd_tplg_ctl_template *t)
 {
diff --git a/src/topology/dapm.c b/src/topology/dapm.c
index ad7092107896..2bdacedca125 100644
--- a/src/topology/dapm.c
+++ b/src/topology/dapm.c
@@ -43,7 +43,6 @@ static const struct map_elem widget_map[] = {
 	{"effect", SND_SOC_TPLG_DAPM_EFFECT},
 	{"siggen", SND_SOC_TPLG_DAPM_SIGGEN},
 	{"src", SND_SOC_TPLG_DAPM_SRC},
-	{"asrc", SND_SOC_TPLG_DAPM_ASRC},
 	{"encoder", SND_SOC_TPLG_DAPM_ENCODER},
 	{"decoder", SND_SOC_TPLG_DAPM_DECODER},
 };
@@ -60,70 +59,16 @@ static int lookup_widget(const char *w)
 	return -EINVAL;
 }
 
-static int tplg_parse_dapm_mixers(snd_config_t *cfg, struct tplg_elem *elem)
+static const char *get_widget_name(unsigned int type)
 {
-	snd_config_iterator_t i, next;
-	snd_config_t *n;
-	const char *value = NULL;
-
-	tplg_dbg(" DAPM Mixer Controls: %s\n", elem->id);
-
-	snd_config_for_each(i, next, cfg) {
-		n = snd_config_iterator_entry(i);
-
-		/* get value */
-		if (snd_config_get_string(n, &value) < 0)
-			continue;
-
-		tplg_ref_add(elem, SND_TPLG_TYPE_MIXER, value);
-		tplg_dbg("\t\t %s\n", value);
-	}
-
-	return 0;
-}
-
-static int tplg_parse_dapm_enums(snd_config_t *cfg, struct tplg_elem *elem)
-{
-	snd_config_iterator_t i, next;
-	snd_config_t *n;
-	const char *value = NULL;
-
-	tplg_dbg(" DAPM Enum Controls: %s\n", elem->id);
-
-	snd_config_for_each(i, next, cfg) {
-		n = snd_config_iterator_entry(i);
-
-		/* get value */
-		if (snd_config_get_string(n, &value) < 0)
-			continue;
-
-		tplg_ref_add(elem, SND_TPLG_TYPE_ENUM, value);
-		tplg_dbg("\t\t %s\n", value);
-	}
-
-	return 0;
-}
-
-static int tplg_parse_dapm_bytes(snd_config_t *cfg, struct tplg_elem *elem)
-{
-	snd_config_iterator_t i, next;
-	snd_config_t *n;
-	const char *value = NULL;
-
-	tplg_dbg(" DAPM Bytes Controls: %s\n", elem->id);
-
-	snd_config_for_each(i, next, cfg) {
-		n = snd_config_iterator_entry(i);
-
-		/* get value */
-		if (snd_config_get_string(n, &value) < 0)
-			continue;
+	unsigned int i;
 
-		tplg_ref_add(elem, SND_TPLG_TYPE_BYTES, value);
-		tplg_dbg("\t\t %s\n", value);
+	for (i = 0; i < ARRAY_SIZE(widget_map); i++) {
+		if ((unsigned int)widget_map[i].id == type)
+			return widget_map[i].name;
 	}
 
-	return 0;
+	return NULL;
 }
 
 /* move referenced controls to the widget */
@@ -340,7 +285,7 @@ struct tplg_elem *tplg_elem_new_route(snd_tplg_t *tplg, int index)
 
 #define LINE_SIZE	1024
 
-/* line is defined as '"source, control, sink"' */
+/* line is defined as '"sink, control, source"' */
 static int tplg_parse_line(const char *text,
 			   struct snd_soc_tplg_dapm_graph_elem *line)
 {
@@ -470,6 +415,77 @@ int tplg_parse_dapm_graph(snd_tplg_t *tplg, snd_config_t *cfg,
 	return 0;
 }
 
+/* save DAPM graph */
+int tplg_save_dapm_graph(snd_tplg_t *tplg, int index, char **dst, const char *pfx)
+{
+	struct snd_soc_tplg_dapm_graph_elem *route;
+	struct list_head *pos;
+	struct tplg_elem *elem;
+	int err, first = 1, old_index = -1;
+	unsigned block = -1, count = 0;
+
+	list_for_each(pos, &tplg->route_list) {
+		elem = list_entry(pos, struct tplg_elem, list);
+		if (!elem->route || elem->type != SND_TPLG_TYPE_DAPM_GRAPH)
+			continue;
+		if (index >= 0 && elem->index != index)
+			continue;
+		count++;
+	}
+	if (count == 0)
+		return 0;
+	err = tplg_save_printf(dst, pfx, "SectionGraph {\n");
+	list_for_each(pos, &tplg->route_list) {
+		elem = list_entry(pos, struct tplg_elem, list);
+		if (!elem->route || elem->type != SND_TPLG_TYPE_DAPM_GRAPH)
+			continue;
+		if (index >= 0 && elem->index != index)
+			continue;
+		if (old_index != elem->index) {
+			if (old_index >= 0) {
+				err = tplg_save_printf(dst, pfx, "\t\t]\n");
+				if (err < 0)
+					return err;
+				err = tplg_save_printf(dst, pfx, "\t}\n");
+				if (err < 0)
+					return err;
+			}
+			old_index = elem->index;
+			block++;
+			first = 1;
+			err = tplg_save_printf(dst, pfx, "\tset%u {\n", block);
+			if (err >= 0)
+				err = tplg_save_printf(dst, pfx, "\t\tindex %u\n",
+						       elem->index);
+			if (err < 0)
+				return err;
+		}
+		if (first) {
+			first = 0;
+			err = tplg_save_printf(dst, pfx, "\t\tlines [\n", elem->index);
+			if (err < 0)
+				return err;
+		}
+		route = elem->route;
+		err = tplg_save_printf(dst, pfx, "\t\t\t'%s, %s, %s'\n",
+					route->sink, route->control,
+					route->source);
+		if (err < 0)
+			return err;
+	}
+
+	if (!first) {
+		if (err >= 0)
+			err = tplg_save_printf(dst, pfx, "\t\t]\n");
+		if (err >= 0)
+			err = tplg_save_printf(dst, pfx, "\t}\n");
+	}
+
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return err;
+}
+
 /* DAPM Widget */
 int tplg_parse_dapm_widget(snd_tplg_t *tplg,
 			   snd_config_t *cfg, void *private ATTRIBUTE_UNUSED)
@@ -595,7 +611,7 @@ int tplg_parse_dapm_widget(snd_tplg_t *tplg,
 		}
 
 		if (strcmp(id, "enum") == 0) {
-			err = tplg_parse_dapm_enums(n, elem);
+			err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_ENUM);
 			if (err < 0)
 				return err;
 
@@ -603,7 +619,7 @@ int tplg_parse_dapm_widget(snd_tplg_t *tplg,
 		}
 
 		if (strcmp(id, "mixer") == 0) {
-			err = tplg_parse_dapm_mixers(n, elem);
+			err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_MIXER);
 			if (err < 0)
 				return err;
 
@@ -611,7 +627,7 @@ int tplg_parse_dapm_widget(snd_tplg_t *tplg,
 		}
 
 		if (strcmp(id, "bytes") == 0) {
-			err = tplg_parse_dapm_bytes(n, elem);
+			err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_BYTES);
 			if (err < 0)
 				return err;
 
@@ -619,7 +635,7 @@ int tplg_parse_dapm_widget(snd_tplg_t *tplg,
 		}
 
 		if (strcmp(id, "data") == 0) {
-			err = tplg_parse_data_refs(n, elem);
+			err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA);
 			if (err < 0)
 				return err;
 			continue;
@@ -629,6 +645,66 @@ int tplg_parse_dapm_widget(snd_tplg_t *tplg,
 	return 0;
 }
 
+/* save DAPM widget */
+int tplg_save_dapm_widget(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+			  struct tplg_elem *elem,
+			  char **dst, const char *pfx)
+{
+	struct snd_soc_tplg_dapm_widget *widget = elem->widget;
+	const char *s;
+	char pfx2[16];
+	int err;
+
+	err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id);
+	if (err >= 0 && elem->index)
+		err = tplg_save_printf(dst, pfx, "\tindex %u\n",
+				       elem->index);
+	if (err >= 0) {
+		s = get_widget_name(widget->id);
+		if (s)
+			err = tplg_save_printf(dst, pfx, "\ttype %s\n", s);
+		else
+			err = tplg_save_printf(dst, pfx, "\ttype %u\n",
+					       widget->id);
+	}
+	if (err >= 0 && widget->sname[0])
+		err = tplg_save_printf(dst, pfx, "\tstream_name '%s'\n",
+				       widget->sname);
+	if (err >= 0 && widget->reg)
+		err = tplg_save_printf(dst, pfx, "\tno_pm 1\n");
+	if (err >= 0 && widget->shift)
+		err = tplg_save_printf(dst, pfx, "\tshift %u\n",
+				       widget->shift);
+	if (err >= 0 && widget->invert)
+		err = tplg_save_printf(dst, pfx, "\tinvert %u\n",
+				       widget->invert);
+	if (err >= 0 && widget->subseq)
+		err = tplg_save_printf(dst, pfx, "\tsubseq %u\n",
+				       widget->subseq);
+	if (err >= 0 && widget->event_type)
+		err = tplg_save_printf(dst, pfx, "\tevent_type %u\n",
+				       widget->event_type);
+	if (err >= 0 && widget->event_flags)
+		err = tplg_save_printf(dst, pfx, "\tevent_flags %u\n",
+				       widget->event_flags);
+	snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: "");
+	if (err >= 0)
+		err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_ENUM,
+				     "enum", dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_MIXER,
+				     "mixer", dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_BYTES,
+				     "bytes", dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_DATA,
+				     "data", dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return err;
+}
+
 int tplg_add_route(snd_tplg_t *tplg, struct snd_tplg_graph_elem *t, int index)
 {
 	struct tplg_elem *elem;
@@ -744,7 +820,6 @@ int tplg_add_widget_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t)
 		default:
 			SNDERR("error: widget %s: invalid type %d for ctl %d\n",
 				wt->name, ct->type, i);
-			ret = -EINVAL;
 			break;
 		}
 
diff --git a/src/topology/data.c b/src/topology/data.c
index 9807445e8c37..11cd73f5ed6e 100644
--- a/src/topology/data.c
+++ b/src/topology/data.c
@@ -21,6 +21,10 @@
 #include "tplg_local.h"
 #include <ctype.h>
 
+#define UUID_FORMAT "\
+0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, \
+0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x"
+
 /* Get private data buffer of an element */
 struct snd_soc_tplg_private *get_priv_data(struct tplg_elem *elem)
 {
@@ -64,6 +68,96 @@ struct snd_soc_tplg_private *get_priv_data(struct tplg_elem *elem)
 	return priv;
 }
 
+/* Parse references for the element, either a single data section
+ * or a list of data sections.
+ */
+int tplg_parse_refs(snd_config_t *cfg, struct tplg_elem *elem,
+		    unsigned int type)
+{
+	snd_config_type_t cfg_type;
+	snd_config_iterator_t i, next;
+	snd_config_t *n;
+	const char *val = NULL;
+	int err, count;
+
+	cfg_type = snd_config_get_type(cfg);
+
+	/* refer to a single data section */
+	if (cfg_type == SND_CONFIG_TYPE_STRING) {
+		if (snd_config_get_string(cfg, &val) < 0)
+			return -EINVAL;
+
+		tplg_dbg("\tref data: %s\n", val);
+		err = tplg_ref_add(elem, type, val);
+		if (err < 0)
+			return err;
+		return 1;
+	}
+
+	if (cfg_type != SND_CONFIG_TYPE_COMPOUND) {
+		SNDERR("error: compound type expected for %s", elem->id);
+		return -EINVAL;
+	}
+
+	/* refer to a list of data sections */
+	count = 0;
+	snd_config_for_each(i, next, cfg) {
+		const char *val;
+
+		n = snd_config_iterator_entry(i);
+		if (snd_config_get_string(n, &val) < 0)
+			continue;
+
+		tplg_dbg("\tref data: %s\n", val);
+		err = tplg_ref_add(elem, type, val);
+		if (err < 0)
+			return err;
+		count++;
+	}
+
+	return count;
+}
+
+/* save references */
+int tplg_save_refs(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+		   struct tplg_elem *elem, unsigned int type,
+		   const char *id, char **dst, const char *pfx)
+{
+	struct tplg_ref *ref, *last;
+	struct list_head *pos;
+	int err, count;
+
+	count = 0;
+	last = NULL;
+	list_for_each(pos, &elem->ref_list) {
+		ref = list_entry(pos, struct tplg_ref, list);
+		if (ref->type == type) {
+			last = ref;
+			count++;
+		}
+	}
+
+	if (count == 0)
+		return 0;
+
+	if (count == 1)
+		return tplg_save_printf(dst, pfx, "%s '%s'\n", id, last->id);
+
+	err = tplg_save_printf(dst, pfx, "%s [\n", id);
+	if (err < 0)
+		return err;
+	list_for_each(pos, &elem->ref_list) {
+		ref = list_entry(pos, struct tplg_ref, list);
+		if (ref->type == type) {
+			err = tplg_save_printf(dst, pfx, "\t'%s'\n", ref->id);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	return tplg_save_printf(dst, pfx, "]\n");
+}
+
 /* Get Private data from a file. */
 static int tplg_parse_data_file(snd_config_t *cfg, struct tplg_elem *elem)
 {
@@ -140,58 +234,98 @@ err:
 static void dump_priv_data(struct tplg_elem *elem)
 {
 	struct snd_soc_tplg_private *priv = elem->data;
-	unsigned int i, j = 0;
+	unsigned int i;
 
 	tplg_dbg(" elem size = %d, priv data size = %d\n",
 		elem->size, priv->size);
 
 	for (i = 0; i < priv->size; i++) {
-		if (j++ % 8 == 0)
+		if (i > 0 && (i % 16) == 0)
 			tplg_dbg("\n");
 
-		tplg_dbg(" 0x%x", *p++);
+		tplg_dbg(" %02x:", *p++);
 	}
 
 	tplg_dbg("\n\n");
 }
 
+static inline int check_nibble(unsigned char c)
+{
+	return (c >= '0' && c <= '9') ||
+	       (c >= 'a' && c <= 'f') ||
+	       (c >= 'A' && c <= 'F');
+}
+
 /* get number of hex value elements in CSV list */
 static int get_hex_num(const char *str)
 {
-	int commas = 0, values = 0, len = strlen(str);
-	const char *end = str + len;
+	int delims, values, len = strlen(str);
+	const char *s, *end = str + len;
+
+	/* check "aa:bb:00" syntax */
+	s = str;
+	delims = values = 0;
+	while (s < end) {
+		/* skip white space */
+		if (isspace(*s)) {
+			s++;
+			continue;
+		}
+		/* find delimeters */
+		if (*s == ':') {
+			delims++;
+			s++;
+			continue;
+		}
+		/* check 00 hexadecimal value */
+		if (s + 1 <= end) {
+			if (check_nibble(s[0]) && check_nibble(s[1])) {
+				values++;
+			} else {
+				goto format2;
+			}
+			s++;
+		}
+		s++;
+	}
+	goto end;
 
+format2:
 	/* we expect "0x0, 0x0, 0x0" */
-	while (str < end) {
+	s = str;
+	delims = values = 0;
+	while (s < end) {
 
 		/* skip white space */
-		if (isspace(*str)) {
-			str++;
+		if (isspace(*s)) {
+			s++;
 			continue;
 		}
 
 		/* find delimeters */
-		if (*str == ',') {
-			commas++;
-			str++;
+		if (*s == ',') {
+			delims++;
+			s++;
 			continue;
 		}
 
 		/* find 0x[0-9] values */
-		if (*str == '0' && str + 2 <= end) {
-			if (str[1] == 'x' && str[2] >= '0' && str[2] <= 'f') {
+		if (*s == '0' && s + 2 <= end) {
+			if (s[1] == 'x' && check_nibble(s[2])) {
+				if (check_nibble(s[3]))
+					s++;
 				values++;
-				str += 3;
-			} else {
-				str++;
+				s += 2;
 			}
+			s++;
 		}
 
-		str++;
+		s++;
 	}
 
+end:
 	/* there should always be one less comma than value */
-	if (values -1 != commas)
+	if (values - 1 != delims)
 		return -EINVAL;
 
 	return values;
@@ -547,6 +681,71 @@ static int build_tuples(snd_tplg_t *tplg, struct tplg_elem *elem)
 	return 0;
 }
 
+struct tuple_type {
+	unsigned int type;
+	const char *name;
+	unsigned int size;
+};
+
+static struct tuple_type tuple_types[] = {
+	{
+		.type = SND_SOC_TPLG_TUPLE_TYPE_UUID,
+		.name = "uuid",
+		.size = 4,
+	},
+	{
+		.type = SND_SOC_TPLG_TUPLE_TYPE_STRING,
+		.name = "string",
+		.size = 6,
+	},
+	{
+		.type = SND_SOC_TPLG_TUPLE_TYPE_BOOL,
+		.name = "bool",
+		.size = 4,
+	},
+	{
+		.type = SND_SOC_TPLG_TUPLE_TYPE_BYTE,
+		.name = "byte",
+		.size = 4,
+	},
+	{
+		.type = SND_SOC_TPLG_TUPLE_TYPE_SHORT,
+		.name = "short",
+		.size = 5,
+	},
+	{
+		.type = SND_SOC_TPLG_TUPLE_TYPE_WORD,
+		.name = "word",
+		.size = 4
+	},
+};
+
+static int get_tuple_type(const char *name)
+{
+	struct tuple_type *t;
+	unsigned int i;
+
+	/* skip initial index for sorting */
+	while ((*name >= '0' && *name <= '9') || *name == '_')
+		name++;
+	for (i = 0; i < ARRAY_SIZE(tuple_types); i++) {
+		t = &tuple_types[i];
+		if (strncasecmp(t->name, name, t->size) == 0)
+			return t->type;
+	}
+	return -EINVAL;
+}
+
+static const char *get_tuple_type_name(unsigned int type)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(tuple_types); i++)
+		if (tuple_types[i].type == type)
+			return tuple_types[i].name;
+	return NULL;
+}
+
 static int parse_tuple_set(snd_config_t *cfg,
 	struct tplg_tuple_set **s)
 {
@@ -554,28 +753,17 @@ static int parse_tuple_set(snd_config_t *cfg,
 	snd_config_t *n;
 	const char *id, *value;
 	struct tplg_tuple_set *set;
-	unsigned int type, num_tuples = 0;
+	unsigned int num_tuples = 0;
 	struct tplg_tuple *tuple;
 	unsigned int tuple_val;
-	int ival;
+	int type, ival;
 
 	snd_config_get_id(cfg, &id);
 
-	if (strncmp(id, "uuid", 4) == 0)
-		type = SND_SOC_TPLG_TUPLE_TYPE_UUID;
-	else if (strncmp(id, "string", 5) == 0)
-		type = SND_SOC_TPLG_TUPLE_TYPE_STRING;
-	else if (strncmp(id, "bool", 4) == 0)
-		type = SND_SOC_TPLG_TUPLE_TYPE_BOOL;
-	else if (strncmp(id, "byte", 4) == 0)
-		type = SND_SOC_TPLG_TUPLE_TYPE_BYTE;
-	else if (strncmp(id, "short", 5) == 0)
-		type = SND_SOC_TPLG_TUPLE_TYPE_SHORT;
-	else if (strncmp(id, "word", 4) == 0)
-		type = SND_SOC_TPLG_TUPLE_TYPE_WORD;
-	else {
-		SNDERR("error: invalid tuple type '%s'\n", id);
-		return -EINVAL;
+	type = get_tuple_type(id);
+	if (type < 0) {
+		SNDERR("error: invalid tuple type '%s'", id);
+		return type;
 	}
 
 	snd_config_for_each(i, next, cfg)
@@ -664,6 +852,84 @@ err:
 	return -EINVAL;
 }
 
+/* save tuple set */
+static int tplg_save_tuple_set(struct tplg_vendor_tuples *tuples,
+			       unsigned int set_index,
+			       char **dst, const char *pfx)
+{
+	struct tplg_tuple_set *set;
+	struct tplg_tuple *tuple;
+	const char *s, *fmt;
+	char buf[32];
+	unsigned int i;
+	int err;
+
+	set = tuples->set[set_index];
+	if (set->num_tuples == 0)
+		return 0;
+	s = get_tuple_type_name(set->type);
+	if (s == NULL)
+		return -EINVAL;
+	if (tuples->num_sets < 10)
+		fmt = "%u_";
+	else if (tuples->num_sets < 100)
+		fmt = "%02u_";
+	else if (tuples->num_sets < 1000)
+		fmt = "%03u_";
+	else
+		return -EINVAL;
+	if (set->num_tuples > 1) {
+		snprintf(buf, sizeof(buf), "tuples.%s%%s {\n", fmt);
+		err = tplg_save_printf(dst, NULL, buf, set_index, s);
+		if (err < 0)
+			return err;
+	}
+	for (i = 0; i < set->num_tuples; i++) {
+		tuple = &set->tuple[i];
+		if (set->num_tuples == 1) {
+			snprintf(buf, sizeof(buf), "tuples.%s%%s.'%%s' ", fmt);
+			err = tplg_save_printf(dst, NULL, buf,
+					       set_index, s, tuple->token);
+		} else {
+			err = tplg_save_printf(dst, pfx, "\t'%s' ",
+					       tuple->token);
+		}
+		switch (set->type) {
+		case SND_SOC_TPLG_TUPLE_TYPE_UUID:
+			err = tplg_save_printf(dst, NULL, "'" UUID_FORMAT "'\n",
+					       tuple->uuid[0], tuple->uuid[1],
+					       tuple->uuid[2], tuple->uuid[3],
+					       tuple->uuid[4], tuple->uuid[5],
+					       tuple->uuid[6], tuple->uuid[7],
+					       tuple->uuid[8], tuple->uuid[9],
+					       tuple->uuid[10], tuple->uuid[11],
+					       tuple->uuid[12], tuple->uuid[13],
+					       tuple->uuid[14], tuple->uuid[15]);
+			break;
+		case SND_SOC_TPLG_TUPLE_TYPE_STRING:
+			err = tplg_save_printf(dst, NULL, "'%s'\n",
+					       tuple->string);
+			break;
+		case SND_SOC_TPLG_TUPLE_TYPE_BOOL:
+		case SND_SOC_TPLG_TUPLE_TYPE_BYTE:
+		case SND_SOC_TPLG_TUPLE_TYPE_SHORT:
+			err = tplg_save_printf(dst, NULL, "%u\n", tuple->value);
+			break;
+		case SND_SOC_TPLG_TUPLE_TYPE_WORD:
+			tplg_nice_value_format(buf, sizeof(buf), tuple->value);
+			err = tplg_save_printf(dst, NULL, "%s\n", buf);
+			break;
+		default:
+			return -EINVAL;
+		}
+		if (err < 0)
+			return err;
+	}
+	if (set->num_tuples > 1)
+		return tplg_save_printf(dst, pfx, "}\n");
+	return 0;
+}
+
 static int parse_tuple_sets(snd_config_t *cfg,
 	struct tplg_vendor_tuples *tuples)
 {
@@ -710,87 +976,24 @@ static int parse_tuple_sets(snd_config_t *cfg,
 	return 0;
 }
 
-/* Parse tuples references for a data element, either a single tuples section
- * or a list of tuples sections.
- */
-static int parse_tuples_refs(snd_config_t *cfg,
-	struct tplg_elem *elem)
-{
-	snd_config_type_t  type;
-	snd_config_iterator_t i, next;
-	snd_config_t *n;
-	const char *val = NULL;
-
-	type = snd_config_get_type(cfg);
-
-	/* refer to a single tuples section */
-	if (type == SND_CONFIG_TYPE_STRING) {
-		if (snd_config_get_string(cfg, &val) < 0)
-			return -EINVAL;
-		tplg_dbg("\ttuples: %s\n", val);
-		return tplg_ref_add(elem, SND_TPLG_TYPE_TUPLE, val);
-	}
-
-	if (type != SND_CONFIG_TYPE_COMPOUND) {
-		SNDERR("error: compound type expected for %s", elem->id);
-		return -EINVAL;
-	}
-
-	/* refer to a list of data sections */
-	snd_config_for_each(i, next, cfg) {
-		const char *val;
-
-		n = snd_config_iterator_entry(i);
-		if (snd_config_get_string(n, &val) < 0)
-			continue;
-
-		tplg_dbg("\ttuples: %s\n", val);
-		tplg_ref_add(elem, SND_TPLG_TYPE_TUPLE, val);
-	}
-
-	return 0;
-}
-
-/* Parse private data references for the element, either a single data section
- * or a list of data sections.
- */
-int tplg_parse_data_refs(snd_config_t *cfg,
-	struct tplg_elem *elem)
+/* save tuple sets */
+int tplg_save_tuple_sets(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+			 struct tplg_elem *elem,
+			 char **dst, const char *pfx)
 {
-	snd_config_type_t  type;
-	snd_config_iterator_t i, next;
-	snd_config_t *n;
-	const char *val = NULL;
-
-	type = snd_config_get_type(cfg);
-
-	/* refer to a single data section */
-	if (type == SND_CONFIG_TYPE_STRING) {
-		if (snd_config_get_string(cfg, &val) < 0)
-			return -EINVAL;
-
-		tplg_dbg("\tdata: %s\n", val);
-		return tplg_ref_add(elem, SND_TPLG_TYPE_DATA, val);
-	}
-
-	if (type != SND_CONFIG_TYPE_COMPOUND) {
-		SNDERR("error: compound type expected for %s", elem->id);
-		return -EINVAL;
-	}
-
-	/* refer to a list of data sections */
-	snd_config_for_each(i, next, cfg) {
-		const char *val;
-
-		n = snd_config_iterator_entry(i);
-		if (snd_config_get_string(n, &val) < 0)
-			continue;
+	struct tplg_vendor_tuples *tuples = elem->tuples;
+	unsigned int i;
+	int err = 0;
 
-		tplg_dbg("\tdata: %s\n", val);
-		tplg_ref_add(elem, SND_TPLG_TYPE_DATA, val);
+	for (i = 0; i < tuples->num_sets; i++) {
+		err = tplg_save_printf(dst, pfx, "");
+		if (err < 0)
+			break;
+		err = tplg_save_tuple_set(tuples, i, dst, pfx);
+		if (err < 0)
+			break;
 	}
-
-	return 0;
+	return err;
 }
 
 /* Parse vendor tokens
@@ -844,6 +1047,31 @@ int tplg_parse_tokens(snd_tplg_t *tplg, snd_config_t *cfg,
 	return 0;
 }
 
+/* save vendor tokens */
+int tplg_save_tokens(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+		     struct tplg_elem *elem,
+		     char **dst, const char *pfx)
+{
+	struct tplg_vendor_tokens *tokens = elem->tokens;
+	unsigned int i;
+	int err;
+
+	if (!tokens || tokens->num_tokens == 0)
+		return 0;
+
+	err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id);
+	if (err < 0)
+		return err;
+	for (i = 0; err >= 0 && i < tokens->num_tokens; i++)
+		err = tplg_save_printf(dst, pfx, "\t'%s' %u\n",
+				       tokens->token[i].id,
+				       tokens->token[i].value);
+	err = tplg_save_printf(dst, pfx, "}\n");
+	if (err < 0)
+		return err;
+	return 0;
+}
+
 /* Parse vendor tuples.
  */
 int tplg_parse_tuples(snd_tplg_t *tplg, snd_config_t *cfg,
@@ -890,6 +1118,29 @@ int tplg_parse_tuples(snd_tplg_t *tplg, snd_config_t *cfg,
 	return 0;
 }
 
+/* save vendor tuples */
+int tplg_save_tuples(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+		     struct tplg_elem *elem,
+		     char **dst, const char *pfx)
+{
+	char pfx2[16];
+	int err;
+
+	if (!elem->tuples)
+		return 0;
+
+	err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id);
+	snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: "");
+	if (err >= 0)
+		err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_TOKEN,
+				     "tokens", dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_tuple_sets(tplg, elem, dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return 0;
+}
+
 /* Free handler of tuples */
 void tplg_free_tuples(void *obj)
 {
@@ -944,7 +1195,7 @@ int tplg_parse_manifest_data(snd_tplg_t *tplg, snd_config_t *cfg,
 
 
 		if (strcmp(id, "data") == 0) {
-			err = tplg_parse_data_refs(n, elem);
+			err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA);
 			if (err < 0)
 				return err;
 			continue;
@@ -954,6 +1205,51 @@ int tplg_parse_manifest_data(snd_tplg_t *tplg, snd_config_t *cfg,
 	return 0;
 }
 
+/* save manifest data */
+int tplg_save_manifest_data(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+			    struct tplg_elem *elem, char **dst,
+			    const char *pfx)
+{
+	struct list_head *pos;
+	struct tplg_ref *ref;
+	int err, index, count;
+
+	/* for each ref in this manifest elem */
+	count = 0;
+	list_for_each(pos, &elem->ref_list) {
+		ref = list_entry(pos, struct tplg_ref, list);
+		if (ref->type != SND_TPLG_TYPE_DATA)
+			continue;
+		count++;
+	}
+	if (count > 1) {
+		err = tplg_save_printf(dst, NULL, "'%s'.data [\n", elem->id);
+		if (err < 0)
+			return err;
+	}
+	index = 0;
+	list_for_each(pos, &elem->ref_list) {
+		ref = list_entry(pos, struct tplg_ref, list);
+		if (ref->type != SND_TPLG_TYPE_DATA)
+			continue;
+		if (count == 1) {
+			err = tplg_save_printf(dst, NULL, "'%s'.data.%u '%s'\n",
+					       elem->id, index, ref->id);
+		} else {
+			err = tplg_save_printf(dst, pfx, "\t'%s'\n", ref->id);
+			if (err < 0)
+				return err;
+		}
+		index++;
+	}
+	if (count > 1) {
+		err = tplg_save_printf(dst, pfx, "]\n");
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
 /* merge private data of manifest */
 int tplg_build_manifest_data(snd_tplg_t *tplg)
 {
@@ -1064,7 +1360,7 @@ int tplg_parse_data(snd_tplg_t *tplg, snd_config_t *cfg,
 		}
 
 		if (strcmp(id, "tuples") == 0) {
-			err = parse_tuples_refs(n, elem);
+			err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_TUPLE);
 			if (err < 0)
 				return err;
 			continue;
@@ -1083,6 +1379,81 @@ int tplg_parse_data(snd_tplg_t *tplg, snd_config_t *cfg,
 	return err;
 }
 
+/* save data element */
+int tplg_save_data(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+		   struct tplg_elem *elem,
+		   char **dst, const char *pfx)
+{
+	struct snd_soc_tplg_private *priv = elem->data;
+	struct list_head *pos;
+	struct tplg_ref *ref;
+	char pfx2[16];
+	unsigned int i, count;
+	int err;
+
+	count = 0;
+	if (priv && priv->size > 0)
+		count++;
+	list_for_each(pos, &elem->ref_list) {
+		ref = list_entry(pos, struct tplg_ref, list);
+		if (ref->type == SND_TPLG_TYPE_TUPLE)
+			count++;
+	}
+	if (elem->vendor_type > 0)
+		count++;
+
+	if (count > 1) {
+		err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id);
+		if (err >= 0)
+			err = tplg_save_printf(dst, NULL, "");
+	} else {
+		err = tplg_save_printf(dst, NULL, "'%s'.", elem->id);
+	}
+	if (err >= 0 && priv && priv->size > 0) {
+		if (count > 1) {
+			err = tplg_save_printf(dst, pfx, "");
+			if (err < 0)
+				return err;
+		}
+		if (priv->size > 8) {
+			err = tplg_save_printf(dst, NULL, "bytes\n");
+			if (err >= 0)
+				err = tplg_save_printf(dst, pfx, "\t'");
+		} else {
+			err = tplg_save_printf(dst, NULL, "bytes '");
+		}
+		if (err < 0)
+			return err;
+		for (i = 0; i < priv->size; i++) {
+			if (i > 0 && (i % 8) == 0) {
+				err = tplg_save_printf(dst, NULL, ":\n");
+				if (err < 0)
+					return err;
+				err = tplg_save_printf(dst, pfx, "\t ");
+				if (err < 0)
+					return err;
+			}
+			err = tplg_save_printf(dst, NULL, "%s%02x",
+					       (i % 8) == 0 ? "" : ":",
+					       (unsigned char)priv->data[i]);
+			if (err < 0)
+				return err;
+		}
+		err = tplg_save_printf(dst, NULL, "'\n");
+	}
+	snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: "");
+	if (err >= 0)
+		err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_TUPLE,
+				     "tuples", dst,
+				     count > 1 ? pfx2 : NULL);
+	if (err >= 0 && elem->vendor_type > 0)
+		err = tplg_save_printf(dst, pfx, "type %u",
+				       elem->vendor_type);
+	if (err >= 0 && count > 1)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return err;
+}
+
 /* Find a referenced data element and copy its data to the parent
  * element's private data buffer.
  * An element can refer to multiple data sections. Data of these sections
diff --git a/src/topology/elem.c b/src/topology/elem.c
index e79a68b71a91..89aed1fc0449 100644
--- a/src/topology/elem.c
+++ b/src/topology/elem.c
@@ -30,6 +30,7 @@ struct tplg_table tplg_table[] = {
 		.size  = sizeof(struct snd_soc_tplg_manifest),
 		.enew  = 1,
 		.parse = tplg_parse_manifest_data,
+		.save  = tplg_save_manifest_data,
 	},
 	{
 		.name  = "control mixer",
@@ -41,6 +42,7 @@ struct tplg_table tplg_table[] = {
 		.build = 1,
 		.enew  = 1,
 		.parse = tplg_parse_control_mixer,
+		.save  = tplg_save_control_mixer,
 	},
 	{
 		.name  = "control enum",
@@ -52,6 +54,7 @@ struct tplg_table tplg_table[] = {
 		.build = 1,
 		.enew  = 1,
 		.parse = tplg_parse_control_enum,
+		.save  = tplg_save_control_enum,
 	},
 	{
 		.name  = "control extended (bytes)",
@@ -63,6 +66,7 @@ struct tplg_table tplg_table[] = {
 		.build = 1,
 		.enew  = 1,
 		.parse = tplg_parse_control_bytes,
+		.save  = tplg_save_control_bytes,
 	},
 	{
 		.name  = "dapm widget",
@@ -74,6 +78,7 @@ struct tplg_table tplg_table[] = {
 		.build = 1,
 		.enew  = 1,
 		.parse = tplg_parse_dapm_widget,
+		.save  = tplg_save_dapm_widget,
 	},
 	{
 		.name  = "pcm",
@@ -85,6 +90,7 @@ struct tplg_table tplg_table[] = {
 		.build = 1,
 		.enew  = 1,
 		.parse = tplg_parse_pcm,
+		.save  = tplg_save_pcm,
 	},
 	{
 		.name  = "physical dai",
@@ -96,6 +102,7 @@ struct tplg_table tplg_table[] = {
 		.build = 1,
 		.enew  = 1,
 		.parse = tplg_parse_dai,
+		.save  = tplg_save_dai,
 	},
 	{
 		.name  = "be",
@@ -108,6 +115,7 @@ struct tplg_table tplg_table[] = {
 		.build = 1,
 		.enew  = 1,
 		.parse = tplg_parse_link,
+		.save  = tplg_save_link,
 	},
 	{
 		.name  = "cc",
@@ -119,6 +127,7 @@ struct tplg_table tplg_table[] = {
 		.build = 1,
 		.enew  = 1,
 		.parse = tplg_parse_cc,
+		.save  = tplg_save_cc,
 	},
 	{
 		.name  = "route (dapm graph)",
@@ -128,6 +137,7 @@ struct tplg_table tplg_table[] = {
 		.tsoc  = SND_SOC_TPLG_TYPE_DAPM_GRAPH,
 		.build = 1,
 		.parse = tplg_parse_dapm_graph,
+		.gsave = tplg_save_dapm_graph,
 	},
 	{
 		.name  = "private data",
@@ -138,6 +148,7 @@ struct tplg_table tplg_table[] = {
 		.build = 1,
 		.enew  = 1,
 		.parse = tplg_parse_data,
+		.save  = tplg_save_data,
 	},
 	{
 		.name  = "text",
@@ -147,6 +158,7 @@ struct tplg_table tplg_table[] = {
 		.size  = sizeof(struct tplg_texts),
 		.enew  = 1,
 		.parse = tplg_parse_text,
+		.save  = tplg_save_text,
 	},
 	{
 		.name  = "tlv",
@@ -156,6 +168,7 @@ struct tplg_table tplg_table[] = {
 		.size  = sizeof(struct snd_soc_tplg_ctl_tlv),
 		.enew  = 1,
 		.parse = tplg_parse_tlv,
+		.save  = tplg_save_tlv,
 	},
 	{
 		.name  = "stream config",
@@ -172,6 +185,7 @@ struct tplg_table tplg_table[] = {
 		.size  = sizeof(struct snd_soc_tplg_stream_caps),
 		.enew  = 1,
 		.parse = tplg_parse_stream_caps,
+		.save  = tplg_save_stream_caps,
 	},
 	{
 		.name  = "token",
@@ -180,6 +194,7 @@ struct tplg_table tplg_table[] = {
 		.type  = SND_TPLG_TYPE_TOKEN,
 		.enew  = 1,
 		.parse = tplg_parse_tokens,
+		.save  = tplg_save_tokens,
 	},
 	{
 		.name  = "tuple",
@@ -189,6 +204,7 @@ struct tplg_table tplg_table[] = {
 		.free  = tplg_free_tuples,
 		.enew  = 1,
 		.parse = tplg_parse_tuples,
+		.save  = tplg_save_tuples,
 	},
 	{
 		.name  = "hw config",
@@ -198,6 +214,7 @@ struct tplg_table tplg_table[] = {
 		.size  = sizeof(struct snd_soc_tplg_hw_config),
 		.enew  = 1,
 		.parse = tplg_parse_hw_config,
+		.save  = tplg_save_hw_config,
 	}
 };
 
@@ -394,6 +411,7 @@ struct tplg_elem* tplg_elem_new_common(snd_tplg_t *tplg,
 	tplg_elem_insert(elem, list);
 	obj_size = tptr->size;
 	elem->free = tptr->free;
+	elem->table = tptr;
 
 	/* create new object too if required */
 	if (obj_size > 0) {
diff --git a/src/topology/ops.c b/src/topology/ops.c
index 073acdcb1453..ad72ef1b2cb6 100644
--- a/src/topology/ops.c
+++ b/src/topology/ops.c
@@ -45,6 +45,18 @@ static int lookup_ops(const char *c)
 	return strtol(c, NULL, 0);
 }
 
+const char *tplg_ops_name(int type)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(control_map); i++) {
+		if (control_map[i].id == type)
+			return control_map[i].name;
+	}
+
+	return NULL;
+}
+
 /* Parse Control operations. Ops can come from standard names above or
  * bespoke driver controls with numbers >= 256
  */
@@ -84,6 +96,46 @@ int tplg_parse_ops(snd_tplg_t *tplg ATTRIBUTE_UNUSED, snd_config_t *cfg,
 	return 0;
 }
 
+/* save control operations */
+int tplg_save_ops(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+		  struct snd_soc_tplg_ctl_hdr *hdr, char **dst,
+		  const char *pfx)
+{
+	const char *s;
+	int err;
+
+	if (hdr->ops.info + hdr->ops.get + hdr->ops.put == 0)
+		return 0;
+	err = tplg_save_printf(dst, pfx, "ops.0 {\n");
+	if (err >= 0 && hdr->ops.info > 0) {
+		s = tplg_ops_name(hdr->ops.info);
+		if (s == NULL)
+			err = tplg_save_printf(dst, pfx, "\tinfo %u\n",
+					       hdr->ops.info);
+		else
+			err = tplg_save_printf(dst, pfx, "\tinfo %s\n", s);
+	}
+	if (err >= 0 && hdr->ops.get > 0) {
+		s = tplg_ops_name(hdr->ops.get);
+		if (s == NULL)
+			err = tplg_save_printf(dst, pfx, "\tget %u\n",
+					       hdr->ops.get);
+		else
+			err = tplg_save_printf(dst, pfx, "\tget %s\n", s);
+	}
+	if (err >= 0 && hdr->ops.put > 0) {
+		s = tplg_ops_name(hdr->ops.put);
+		if (s == NULL)
+			err = tplg_save_printf(dst, pfx, "\tput %u\n",
+					       hdr->ops.put);
+		else
+			err = tplg_save_printf(dst, pfx, "\tput %s\n", s);
+	}
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return err;
+}
+
 /* Parse External Control operations. Ops can come from standard names above or
  * bespoke driver controls with numbers >= 256
  */
@@ -121,3 +173,43 @@ int tplg_parse_ext_ops(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
 
 	return 0;
 }
+
+/* save external control operations */
+int tplg_save_ext_ops(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+		      struct snd_soc_tplg_bytes_control *be,
+		      char **dst, const char *pfx)
+{
+	const char *s;
+	int err;
+
+	if (be->ext_ops.info + be->ext_ops.get + be->ext_ops.put == 0)
+		return 0;
+	err = tplg_save_printf(dst, pfx, "extops.0 {\n");
+	if (err >= 0 && be->ext_ops.info > 0) {
+		s = tplg_ops_name(be->ext_ops.info);
+		if (s == NULL)
+			err = tplg_save_printf(dst, pfx, "\tinfo %u\n",
+					       be->ext_ops.info);
+		else
+			err = tplg_save_printf(dst, pfx, "\tinfo %s\n", s);
+	}
+	if (err >= 0 && be->ext_ops.get > 0) {
+		s = tplg_ops_name(be->ext_ops.get);
+		if (s == NULL)
+			err = tplg_save_printf(dst, pfx, "\tget %u\n",
+					       be->ext_ops.get);
+		else
+			err = tplg_save_printf(dst, pfx, "\tget %s\n", s);
+	}
+	if (err >= 0 && be->ext_ops.put > 0) {
+		s = tplg_ops_name(be->ext_ops.put);
+		if (s == NULL)
+			err = tplg_save_printf(dst, pfx, "\tput %u\n",
+					       be->ext_ops.put);
+		else
+			err = tplg_save_printf(dst, pfx, "\tput %s\n", s);
+	}
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return err;
+}
diff --git a/src/topology/parser.c b/src/topology/parser.c
index 11202769391c..de5edd1b6591 100644
--- a/src/topology/parser.c
+++ b/src/topology/parser.c
@@ -71,6 +71,8 @@ int tplg_get_unsigned(snd_config_t *n, unsigned *val, int base)
 		err = snd_config_get_integer(n, &lval);
 		if (err < 0)
 			return err;
+		if (lval < 0 && lval >= INT_MIN)
+			lval = UINT_MAX + lval + 1;
 		if (lval < 0 || lval > UINT_MAX)
 			return -ERANGE;
 		*val = lval;
@@ -79,6 +81,8 @@ int tplg_get_unsigned(snd_config_t *n, unsigned *val, int base)
 		err = snd_config_get_integer64(n, &llval);
 		if (err < 0)
 			return err;
+		if (llval < 0 && llval >= INT_MIN)
+			llval = UINT_MAX + llval + 1;
 		if (llval < 0 || llval > UINT_MAX)
 			return -ERANGE;
 		*val = llval;
diff --git a/src/topology/pcm.c b/src/topology/pcm.c
index 9b87549cabbd..d09fbe42f5da 100644
--- a/src/topology/pcm.c
+++ b/src/topology/pcm.c
@@ -345,6 +345,13 @@ static int get_rate_value(const char* name)
 	return SND_PCM_RATE_UNKNOWN;
 }
 
+static const char *get_rate_name(int rate)
+{
+	if (rate >= 0 && rate <= SND_PCM_RATE_LAST)
+		return snd_pcm_rate_names[rate];
+	return NULL;
+}
+
 static int split_rate(struct snd_soc_tplg_stream_caps *caps, char *str)
 {
 	char *s = NULL;
@@ -527,6 +534,80 @@ int tplg_parse_stream_caps(snd_tplg_t *tplg,
 	return 0;
 }
 
+/* save stream caps */
+int tplg_save_stream_caps(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+			  struct tplg_elem *elem,
+			  char **dst, const char *pfx)
+{
+	struct snd_soc_tplg_stream_caps *sc = elem->stream_caps;
+	const char *s;
+	unsigned int i;
+	int err, first;
+
+	err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id);
+	if (err >= 0 && sc->formats) {
+		err = tplg_save_printf(dst, pfx, "\tformats '");
+		first = 1;
+		for (i = 0; err >= 0 && i < SND_PCM_FORMAT_LAST; i++) {
+			if (sc->formats & (1ULL << i)) {
+				s = snd_pcm_format_name(i);
+				err = tplg_save_printf(dst, NULL, "%s%s",
+						       !first ? ", " : "", s);
+				first = 0;
+			}
+		}
+		if (err >= 0)
+			err = tplg_save_printf(dst, NULL, "'\n");
+	}
+	if (err >= 0 && sc->rates) {
+		err = tplg_save_printf(dst, pfx, "\trates '");
+		first = 1;
+		for (i = 0; err >= 0 && i < SND_PCM_RATE_LAST; i++) {
+			if (sc->rates & (1ULL << i)) {
+				s = get_rate_name(i);
+				err = tplg_save_printf(dst, NULL, "%s%s",
+						       !first ? ", " : "", s);
+				first = 0;
+			}
+		}
+		if (err >= 0)
+			err = tplg_save_printf(dst, NULL, "'\n");
+	}
+	if (err >= 0 && sc->rate_min)
+		err = tplg_save_printf(dst, pfx, "\trate_min %u\n",
+				       sc->rate_min);
+	if (err >= 0 && sc->rate_max)
+		err = tplg_save_printf(dst, pfx, "\trate_max %u\n",
+				       sc->rate_max);
+	if (err >= 0 && sc->channels_min)
+		err = tplg_save_printf(dst, pfx, "\tchannels_min %u\n",
+				       sc->channels_min);
+	if (err >= 0 && sc->channels_max)
+		err = tplg_save_printf(dst, pfx, "\tchannels_max %u\n",
+				       sc->channels_max);
+	if (err >= 0 && sc->periods_min)
+		err = tplg_save_printf(dst, pfx, "\tperiods_min %u\n",
+				       sc->periods_min);
+	if (err >= 0 && sc->periods_max)
+		err = tplg_save_printf(dst, pfx, "\tperiods_max %u\n",
+				       sc->periods_max);
+	if (err >= 0 && sc->period_size_min)
+		err = tplg_save_printf(dst, pfx, "\tperiod_size_min %u\n",
+				       sc->period_size_min);
+	if (err >= 0 && sc->period_size_max)
+		err = tplg_save_printf(dst, pfx, "\tperiod_size_max %u\n",
+				       sc->period_size_max);
+	if (err >= 0 && sc->buffer_size_min)
+		err = tplg_save_printf(dst, pfx, "\tbuffer_size_min %u\n",
+				       sc->buffer_size_min);
+	if (err >= 0 && sc->buffer_size_max)
+		err = tplg_save_printf(dst, pfx, "\tbuffer_size_max %u\n",
+				       sc->buffer_size_max);
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return err;
+}
+
 /* Parse the caps and config of a pcm stream */
 static int tplg_parse_streams(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
 			      snd_config_t *cfg, void *private)
@@ -598,6 +679,61 @@ static int tplg_parse_streams(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
 	return 0;
 }
 
+/* Save the caps and config of a pcm stream */
+int tplg_save_streams(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+		      struct tplg_elem *elem,
+		      char **dst, const char *pfx)
+{
+	static const char *stream_ids[2] = {
+		"playback",
+		"capture"
+	};
+	static unsigned int stream_types[2] = {
+		SND_SOC_TPLG_STREAM_PLAYBACK,
+		SND_SOC_TPLG_STREAM_CAPTURE
+	};
+	struct snd_soc_tplg_stream_caps *caps;
+	unsigned int streams[2], stream;
+	const char *s;
+	int err;
+
+	switch (elem->type) {
+	case SND_TPLG_TYPE_PCM:
+		streams[0] = elem->pcm->playback;
+		streams[1] = elem->pcm->capture;
+		caps = elem->pcm->caps;
+		break;
+	case SND_TPLG_TYPE_DAI:
+		streams[0] = elem->dai->playback;
+		streams[1] = elem->dai->capture;
+		caps = elem->dai->caps;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	for (stream = 0; stream < 2; stream++) {
+		if (streams[stream] == 0)
+			continue;
+		if (!caps)
+			continue;
+		s = caps[stream_types[stream]].name;
+		if (s[0] == '\0')
+			continue;
+		err = tplg_save_printf(dst, pfx, "pcm.%s {\n", stream_ids[stream]);
+		if (err < 0)
+			return err;
+		err = tplg_save_printf(dst, pfx, "\tcapabilities '%s'\n", s);
+		if (err < 0)
+			return err;
+		err = tplg_save_printf(dst, pfx, "}\n");
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
 /* Parse name and id of a front-end DAI (ie. cpu dai of a FE DAI link) */
 static int tplg_parse_fe_dai(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
 			     snd_config_t *cfg, void *private)
@@ -633,6 +769,19 @@ static int tplg_parse_fe_dai(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
 	return 0;
 }
 
+/* Save the caps and config of a pcm stream */
+int tplg_save_fe_dai(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+		     struct tplg_elem *elem,
+		     char **dst, const char *pfx)
+{
+	struct snd_soc_tplg_pcm *pcm = elem->pcm;
+	int err = 0;
+
+	if (pcm->dai_id > 0)
+		err = tplg_save_printf(dst, pfx, "dai.0.id %u\n", pcm->dai_id);
+	return err;
+}
+
 /* parse a flag bit of the given mask */
 static int parse_flag(snd_config_t *n, unsigned int mask_in,
 		      unsigned int *mask, unsigned int *flags)
@@ -652,6 +801,32 @@ static int parse_flag(snd_config_t *n, unsigned int mask_in,
 	return 0;
 }
 
+static int save_flags(unsigned int flags, unsigned int mask,
+		      char **dst, const char *pfx)
+{
+	static unsigned int flag_masks[3] = {
+		SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_RATES,
+		SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_CHANNELS,
+		SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_SAMPLEBITS,
+	};
+	static const char *flag_ids[3] = {
+		"symmetric_rates",
+		"symmetric_channels",
+		"symmetric_sample_bits",
+	};
+	unsigned int i;
+	int err = 0;
+
+	for (i = 0; err >= 0 && i < ARRAY_SIZE(flag_masks); i++) {
+		if (mask & flag_masks[i]) {
+			unsigned int v = (flags & flag_masks[i]) ? 1 : 0;
+			err = tplg_save_printf(dst, pfx, "%s %u\n",
+					       flag_ids[i], v);
+		}
+	}
+	return err;
+}
+
 /* Parse PCM (for front end DAI & DAI link) in text conf file */
 int tplg_parse_pcm(snd_tplg_t *tplg, snd_config_t *cfg,
 		   void *private ATTRIBUTE_UNUSED)
@@ -748,7 +923,7 @@ int tplg_parse_pcm(snd_tplg_t *tplg, snd_config_t *cfg,
 
 		/* private data */
 		if (strcmp(id, "data") == 0) {
-			err = tplg_parse_data_refs(n, elem);
+			err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA);
 			if (err < 0)
 				return err;
 			continue;
@@ -758,6 +933,40 @@ int tplg_parse_pcm(snd_tplg_t *tplg, snd_config_t *cfg,
 	return 0;
 }
 
+/* save PCM */
+int tplg_save_pcm(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+		  struct tplg_elem *elem,
+		  char **dst, const char *pfx)
+{
+	struct snd_soc_tplg_pcm *pcm = elem->pcm;
+	char pfx2[16];
+	int err;
+
+	snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: "");
+	err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id);
+	if (err >= 0 && elem->index)
+		err = tplg_save_printf(dst, pfx, "\tindex %u\n",
+				       elem->index);
+	if (err >= 0 && pcm->pcm_id)
+		err = tplg_save_printf(dst, pfx, "\tid %u\n",
+				       pcm->pcm_id);
+	if (err >= 0 && pcm->compress)
+		err = tplg_save_printf(dst, pfx, "\tcompress 1\n");
+	snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: "");
+	if (err >= 0)
+		err = tplg_save_fe_dai(tplg, elem, dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_streams(tplg, elem, dst, pfx2);
+	if (err >= 0)
+		err = save_flags(pcm->flags, pcm->flag_mask, dst, pfx);
+	if (err >= 0)
+		err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_DATA,
+				     "data", dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return err;
+}
+
 /* Parse physical DAI */
 int tplg_parse_dai(snd_tplg_t *tplg, snd_config_t *cfg,
 		   void *private ATTRIBUTE_UNUSED)
@@ -766,7 +975,7 @@ int tplg_parse_dai(snd_tplg_t *tplg, snd_config_t *cfg,
 	struct tplg_elem *elem;
 	snd_config_iterator_t i, next;
 	snd_config_t *n;
-	const char *id, *val = NULL;
+	const char *id;
 	int err;
 
 	elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_DAI);
@@ -851,11 +1060,9 @@ int tplg_parse_dai(snd_tplg_t *tplg, snd_config_t *cfg,
 
 		/* private data */
 		if (strcmp(id, "data") == 0) {
-			if (snd_config_get_string(n, &val) < 0)
-				return -EINVAL;
-
-			tplg_ref_add(elem, SND_TPLG_TYPE_DATA, val);
-			tplg_dbg("\t%s: %s\n", id, val);
+			err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA);
+			if (err < 0)
+				return err;
 			continue;
 		}
 	}
@@ -863,55 +1070,55 @@ int tplg_parse_dai(snd_tplg_t *tplg, snd_config_t *cfg,
 	return 0;
 }
 
+/* save DAI */
+int tplg_save_dai(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+		  struct tplg_elem *elem,
+		  char **dst, const char *pfx)
+{
+	struct snd_soc_tplg_dai *dai = elem->dai;
+	char pfx2[16];
+	int err;
+
+	if (!dai)
+		return 0;
+	snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: "");
+	err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id);
+	if (err >= 0 && elem->index)
+		err = tplg_save_printf(dst, pfx, "\tindex %u\n",
+				       elem->index);
+	if (err >= 0 && dai->dai_id)
+		err = tplg_save_printf(dst, pfx, "\tid %u\n",
+				       dai->dai_id);
+	if (err >= 0 && dai->playback)
+		err = tplg_save_printf(dst, pfx, "\tplayback %u\n",
+				       dai->playback);
+	if (err >= 0 && dai->capture)
+		err = tplg_save_printf(dst, pfx, "\tcapture %u\n",
+				       dai->capture);
+	if (err >= 0)
+		err = tplg_save_streams(tplg, elem, dst, pfx2);
+	if (err >= 0)
+		err = save_flags(dai->flags, dai->flag_mask, dst, pfx);
+	if (err >= 0)
+		err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_DATA,
+				     "data", dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return err;
+}
+
 /* parse physical link runtime supported HW configs in text conf file */
 static int parse_hw_config_refs(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
 				snd_config_t *cfg,
 				struct tplg_elem *elem)
 {
 	struct snd_soc_tplg_link_config *link = elem->link;
-	snd_config_type_t  type;
-	snd_config_iterator_t i, next;
-	snd_config_t *n;
-	const char *id, *val = NULL;
-
-	if (snd_config_get_id(cfg, &id) < 0)
-		return -EINVAL;
-	type = snd_config_get_type(cfg);
-
-	/* refer to a single HW config */
-	if (type == SND_CONFIG_TYPE_STRING) {
-		if (snd_config_get_string(cfg, &val) < 0)
-			return -EINVAL;
-
-		link->num_hw_configs = 1;
-		return tplg_ref_add(elem, SND_TPLG_TYPE_HW_CONFIG, val);
-	}
-
-	if (type != SND_CONFIG_TYPE_COMPOUND) {
-		SNDERR("error: compound type expected for %s", id);
-		return -EINVAL;
-	}
-
-	/* refer to a list of HW configs */
-	snd_config_for_each(i, next, cfg) {
-		const char *val;
-		int err;
-
-		n = snd_config_iterator_entry(i);
-		if (snd_config_get_string(n, &val) < 0)
-			continue;
-
-		if (link->num_hw_configs >= SND_SOC_TPLG_HW_CONFIG_MAX) {
-			SNDERR("error: exceed max hw configs for link %s", id);
-			return -EINVAL;
-		}
-
-		link->num_hw_configs++;
-		err = tplg_ref_add(elem, SND_TPLG_TYPE_HW_CONFIG, val);
-		if (err < 0)
-			return err;
-	}
+	int err;
 
+	err = tplg_parse_refs(cfg, elem, SND_TPLG_TYPE_HW_CONFIG);
+	if (err < 0)
+		return err;
+	link->num_hw_configs = err;
 	return 0;
 }
 
@@ -1007,7 +1214,7 @@ int tplg_parse_link(snd_tplg_t *tplg,
 
 		/* private data */
 		if (strcmp(id, "data") == 0) {
-			err = tplg_parse_data_refs(n, elem);
+			err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA);
 			if (err < 0)
 				return err;
 			continue;
@@ -1017,6 +1224,44 @@ int tplg_parse_link(snd_tplg_t *tplg,
 	return 0;
 }
 
+/* save physical link */
+int tplg_save_link(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+		   struct tplg_elem *elem,
+		   char **dst, const char *pfx)
+{
+	struct snd_soc_tplg_link_config *link = elem->link;
+	char pfx2[16];
+	int err;
+
+	if (!link)
+		return 0;
+	snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: "");
+	err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id);
+	if (err >= 0 && elem->index)
+		err = tplg_save_printf(dst, pfx, "\tindex %u\n",
+				       elem->index);
+	if (err >= 0 && link->id)
+		err = tplg_save_printf(dst, pfx, "\tid %u\n",
+				       link->id);
+	if (err >= 0 && link->stream_name[0])
+		err = tplg_save_printf(dst, pfx, "\tstream_name '%s'\n",
+				       link->stream_name);
+	if (err >= 0 && link->default_hw_config_id)
+		err = tplg_save_printf(dst, pfx, "\tdefault_hw_conf_id %u\n",
+				       link->default_hw_config_id);
+	if (err >= 0)
+		err = save_flags(link->flags, link->flag_mask, dst, pfx);
+	if (err >= 0)
+		err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_HW_CONFIG,
+				     "hw_configs", dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_DATA,
+				     "data", dst, pfx2);
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return err;
+}
+
 /* Parse cc */
 int tplg_parse_cc(snd_tplg_t *tplg,
 	snd_config_t *cfg, void *private ATTRIBUTE_UNUSED)
@@ -1059,36 +1304,95 @@ int tplg_parse_cc(snd_tplg_t *tplg,
 	return 0;
 }
 
-static int get_audio_hw_format(const char *val)
+/* save CC */
+int tplg_save_cc(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+		   struct tplg_elem *elem,
+		   char **dst, const char *pfx)
 {
-	if (!strlen(val))
-		return -EINVAL;
-
-	if (!strcmp(val, "I2S"))
-		return SND_SOC_DAI_FORMAT_I2S;
+	struct snd_soc_tplg_link_config *link = elem->link;
+	char pfx2[16];
+	int err;
 
-	if (!strcmp(val, "RIGHT_J"))
-		return SND_SOC_DAI_FORMAT_RIGHT_J;
+	if (!link)
+		return 0;
+	snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: "");
+	err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id);
+	if (err >= 0 && elem->index)
+		err = tplg_save_printf(dst, pfx, "\tindex %u\n",
+				       elem->index);
+	if (err >= 0 && link->id)
+		err = tplg_save_printf(dst, pfx, "\tid %u\n",
+				       link->id);
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return err;
+}
 
-	if (!strcmp(val, "LEFT_J"))
-		return SND_SOC_DAI_FORMAT_LEFT_J;
+struct audio_hw_format {
+	unsigned int type;
+	const char *name;
+};
 
-	if (!strcmp(val, "DSP_A"))
-		return SND_SOC_DAI_FORMAT_DSP_A;
+static struct audio_hw_format audio_hw_formats[] = {
+	{
+		.type = SND_SOC_DAI_FORMAT_I2S,
+		.name = "I2S",
+	},
+	{
+		.type = SND_SOC_DAI_FORMAT_RIGHT_J,
+		.name = "RIGHT_J",
+	},
+	{
+		.type = SND_SOC_DAI_FORMAT_LEFT_J,
+		.name = "LEFT_J",
+	},
+	{
+		.type = SND_SOC_DAI_FORMAT_DSP_A,
+		.name = "DSP_A",
+	},
+	{
+		.type = SND_SOC_DAI_FORMAT_DSP_B,
+		.name = "DSP_B",
+	},
+	{
+		.type = SND_SOC_DAI_FORMAT_AC97,
+		.name = "AC97",
+	},
+	{
+		.type = SND_SOC_DAI_FORMAT_AC97,
+		.name = "AC97",
+	},
+	{
+		.type = SND_SOC_DAI_FORMAT_PDM,
+		.name = "PDM",
+	},
+};
 
-	if (!strcmp(val, "DSP_B"))
-		return SND_SOC_DAI_FORMAT_DSP_B;
+static int get_audio_hw_format(const char *val)
+{
+	unsigned int i;
 
-	if (!strcmp(val, "AC97"))
-		return SND_SOC_DAI_FORMAT_AC97;
+	if (val[0] == '\0')
+		return -EINVAL;
 
-	if (!strcmp(val, "PDM"))
-		return SND_SOC_DAI_FORMAT_PDM;
+	for (i = 0; i < ARRAY_SIZE(audio_hw_formats); i++)
+		if (strcasecmp(audio_hw_formats[i].name, val) == 0)
+			return audio_hw_formats[i].type;
 
 	SNDERR("error: invalid audio HW format %s\n", val);
 	return -EINVAL;
 }
 
+static const char *get_audio_hw_format_name(unsigned int type)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(audio_hw_formats); i++)
+		if (audio_hw_formats[i].type == type)
+			return audio_hw_formats[i].name;
+	return NULL;
+}
+
 int tplg_parse_hw_config(snd_tplg_t *tplg, snd_config_t *cfg,
 			 void *private ATTRIBUTE_UNUSED)
 {
@@ -1299,6 +1603,71 @@ int tplg_parse_hw_config(snd_tplg_t *tplg, snd_config_t *cfg,
 	return 0;
 }
 
+/* save hw config */
+int tplg_save_hw_config(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+			struct tplg_elem *elem,
+			char **dst, const char *pfx)
+{
+	struct snd_soc_tplg_hw_config *hc = elem->hw_cfg;
+	int err;
+
+	err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id);
+	if (err >= 0 && hc->id)
+		err = tplg_save_printf(dst, pfx, "\tid %u\n",
+				       hc->id);
+	if (err >= 0 && hc->fmt)
+		err = tplg_save_printf(dst, pfx, "\tformat '%s'\n",
+				       get_audio_hw_format_name(hc->fmt));
+	if (err >= 0 && hc->bclk_master)
+		err = tplg_save_printf(dst, pfx, "\tbclk '%s'\n",
+				       hc->bclk_master == SND_SOC_TPLG_BCLK_CS ?
+						"codec_slave" : "codec_master");
+	if (err >= 0 && hc->bclk_rate)
+		err = tplg_save_printf(dst, pfx, "\tbclk_freq %u\n",
+				       hc->bclk_rate);
+	if (err >= 0 && hc->invert_bclk)
+		err = tplg_save_printf(dst, pfx, "\tbclk_invert 1\n");
+	if (err >= 0 && hc->fsync_master)
+		err = tplg_save_printf(dst, pfx, "\tfsync_master '%s'\n",
+				       hc->fsync_master == SND_SOC_TPLG_FSYNC_CS ?
+						"codec_slave" : "codec_master");
+	if (err >= 0 && hc->fsync_rate)
+		err = tplg_save_printf(dst, pfx, "\tfsync_freq %u\n",
+				       hc->fsync_rate);
+	if (err >= 0 && hc->invert_fsync)
+		err = tplg_save_printf(dst, pfx, "\tfsync_invert 1\n");
+	if (err >= 0 && hc->mclk_rate)
+		err = tplg_save_printf(dst, pfx, "\tmclk_freq %u\n",
+				       hc->mclk_rate);
+	if (err >= 0 && hc->mclk_direction)
+		err = tplg_save_printf(dst, pfx, "\tmclk '%s'\n",
+				       hc->mclk_direction == SND_SOC_TPLG_MCLK_CI ?
+						"codec_mclk_in" : "codec_mclk_out");
+	if (err >= 0 && hc->clock_gated)
+		err = tplg_save_printf(dst, pfx, "\tpm_gate_clocks 1\n");
+	if (err >= 0 && hc->tdm_slots)
+		err = tplg_save_printf(dst, pfx, "\ttdm_slots %u\n",
+				       hc->tdm_slots);
+	if (err >= 0 && hc->tdm_slot_width)
+		err = tplg_save_printf(dst, pfx, "\ttdm_slot_width %u\n",
+				       hc->tdm_slot_width);
+	if (err >= 0 && hc->tx_slots)
+		err = tplg_save_printf(dst, pfx, "\ttx_slots %u\n",
+				       hc->tx_slots);
+	if (err >= 0 && hc->rx_slots)
+		err = tplg_save_printf(dst, pfx, "\trx_slots %u\n",
+				       hc->rx_slots);
+	if (err >= 0 && hc->tx_channels)
+		err = tplg_save_printf(dst, pfx, "\ttx_channels %u\n",
+				       hc->tx_channels);
+	if (err >= 0 && hc->rx_channels)
+		err = tplg_save_printf(dst, pfx, "\trx_channels %u\n",
+				       hc->rx_channels);
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "}\n");
+	return err;
+}
+
 /* copy stream object */
 static void tplg_add_stream_object(struct snd_soc_tplg_stream *strm,
 				struct snd_tplg_stream_template *strm_tpl)
diff --git a/src/topology/save.c b/src/topology/save.c
new file mode 100644
index 000000000000..0498911f67d5
--- /dev/null
+++ b/src/topology/save.c
@@ -0,0 +1,632 @@
+/*
+  Copyright(c) 2019 Red Hat Inc.
+  All rights reserved.
+
+  This library is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of
+  the License, or (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Lesser General Public License for more details.
+
+  Authors: Jaroslav Kysela <perex@perex.cz>
+*/
+
+#include "list.h"
+#include "tplg_local.h"
+
+#define SAVE_ALLOC_SHIFT	(13)	/* 8192 bytes */
+
+int tplg_save_printf(char **dst, const char *pfx, const char *fmt, ...)
+{
+	va_list va;
+	char buf[1024], *s;
+	size_t n, l, t, pl;
+
+	if (pfx == NULL)
+		pfx = "";
+
+	va_start(va, fmt);
+	n = vsnprintf(buf, sizeof(buf), fmt, va);
+	va_end(va);
+
+	if (n >= sizeof(buf))
+		return -EOVERFLOW;
+
+	pl = strlen(pfx);
+	l = *dst ? strlen(*dst) : 0;
+	t = l + pl + n + 1;
+	/* allocate chunks */
+	if (*dst == NULL ||
+	    (l >> SAVE_ALLOC_SHIFT) != (t >> SAVE_ALLOC_SHIFT)) {
+		s = realloc(*dst, ((t >> SAVE_ALLOC_SHIFT) + 1) <<
+							SAVE_ALLOC_SHIFT);
+		if (s == NULL) {
+			free(*dst);
+			*dst = NULL;
+			return -ENOMEM;
+		}
+	} else {
+		s = *dst;
+	}
+
+	if (pl > 0)
+		strcpy(s + l, pfx);
+	strcpy(s + l + pl, buf);
+	*dst = s;
+	return 0;
+}
+
+int tplg_nice_value_format(char *dst, size_t dst_size, unsigned int value)
+{
+	if ((value % 1000) != 0) {
+		if (value > 0xfffffff0)
+			return snprintf(dst, dst_size, "%d", (int)value);
+		if (value >= 0xffff0000)
+			return snprintf(dst, dst_size, "0x%x", value);
+	}
+	return snprintf(dst, dst_size, "%u", value);
+}
+
+static int tplg_pprint_integer(snd_config_t *n, char **ret)
+{
+	long lval;
+	int err, type;
+	char buf[16];
+
+	type = snd_config_get_type(n);
+	if (type == SND_CONFIG_TYPE_INTEGER) {
+		err = snd_config_get_integer(n, &lval);
+		if (err < 0)
+			return err;
+		if (lval < INT_MIN || lval > UINT_MAX)
+			return snd_config_get_ascii(n, ret);
+	} else if (type == SND_CONFIG_TYPE_INTEGER64) {
+		long long llval;
+		err = snd_config_get_integer64(n, &llval);
+		if (err < 0)
+			return err;
+		if (llval < INT_MIN || llval > UINT_MAX)
+			return snd_config_get_ascii(n, ret);
+		lval = llval;
+	}
+	err = tplg_nice_value_format(buf, sizeof(buf), (unsigned int)lval);
+	if (err < 0)
+		return err;
+	*ret = strdup(buf);
+	if (*ret == NULL)
+		return -ENOMEM;
+	return 0;
+}
+
+static int tplg_check_array_item(const char *id, int index)
+{
+	const char *p;
+	long int val;
+
+	for (p = id; *p; p++) {
+		if (*p < '0' || *p > '9')
+			return 0;
+	}
+
+	errno = 0;
+	val = strtol(id, NULL, 10);
+	return errno == 0 && val == index;
+}
+
+static int _compar(const void *a, const void *b)
+{
+	const snd_config_t *c1 = *(snd_config_t **)a;
+	const snd_config_t *c2 = *(snd_config_t **)b;
+	const char *id1, *id2;
+	if (snd_config_get_id(c1, &id1)) return 0;
+	if (snd_config_get_id(c2, &id2)) return 0;
+	return strcmp(id1, id2);
+}
+
+static snd_config_t *sort_config(const char *id, snd_config_t *src)
+{
+	snd_config_t *dst, **a;
+	snd_config_iterator_t i, next;
+	int index, array, count;
+
+	if (snd_config_get_type(src) != SND_CONFIG_TYPE_COMPOUND) {
+
+		if (snd_config_copy(&dst, src) >= 0)
+			return dst;
+		return NULL;
+	}
+	count = 0;
+	snd_config_for_each(i, next, src)
+		count++;
+	a = malloc(sizeof(dst) * count);
+	if (a == NULL)
+		return NULL;
+	index = array = 0;
+	snd_config_for_each(i, next, src) {
+		snd_config_t *s = snd_config_iterator_entry(i);
+		const char *id2;
+		a[index++] = s;
+		if (array < 0)
+			continue;
+		if (snd_config_get_id(s, &id2)) {
+			free(a);
+			return NULL;
+		}
+		if (array >= 0 && tplg_check_array_item(id2, array))
+			array++;
+		else
+			array = -1;
+	}
+	if (array < 0)
+		qsort(a, count, sizeof(a[0]), _compar);
+	if (snd_config_make_compound(&dst, id, count == 1)) {
+		free(a);
+		return NULL;
+	}
+	for (index = 0; index < count; index++) {
+		snd_config_t *s = a[index];
+		const char *id2;
+		if (snd_config_get_id(s, &id2)) {
+			snd_config_delete(dst);
+			free(a);
+			return NULL;
+		}
+		s = sort_config(id2, s);
+		if (s == NULL || snd_config_add(dst, s)) {
+			if (s)
+				snd_config_delete(s);
+			snd_config_delete(dst);
+			free(a);
+			return NULL;
+		}
+	}
+	free(a);
+	return dst;
+}
+
+static int tplg_check_quoted(const unsigned char *p)
+{
+	for ( ; *p != '\0'; p++) {
+		switch (*p) {
+		case ' ':
+		case '=':
+		case ';':
+		case ',':
+		case '.':
+		case '{':
+		case '}':
+		case '\'':
+		case '"':
+			return 1;
+		default:
+			if (*p <= 31 || *p >= 127)
+				return 1;
+
+		}
+	}
+	return 0;
+}
+
+static int tplg_save_quoted(char **dst, const char *str)
+{
+	static const char nibble[16] = "0123456789abcdef";
+	unsigned char *p, *d, *t;
+	int c;
+
+	d = t = alloca(strlen(str) * 5 + 1 + 1);
+	for (p = (unsigned char *)str; *p != '\0'; p++) {
+		c = *p;
+		switch (c) {
+		case '\n':
+			*t++ = '\\';
+			*t++ = 'n';
+			break;
+		case '\t':
+			*t++ = '\\';
+			*t++ = 't';
+			break;
+		case '\v':
+			*t++ = '\\';
+			*t++ = 'v';
+			break;
+		case '\b':
+			*t++ = '\\';
+			*t++ = 'b';
+			break;
+		case '\r':
+			*t++ = '\\';
+			*t++ = 'r';
+			break;
+		case '\f':
+			*t++ = '\\';
+			*t++ = 'f';
+			break;
+		case '\'':
+			*t++ = '\\';
+			*t++ = c;
+			break;
+		default:
+			if (c >= 32 && c <= 126) {
+				*t++ = c;
+			} else {
+				*t++ = '\\';
+				*t++ = 'x';
+				*t++ = nibble[(c >> 4) & 0x0f];
+				*t++ = nibble[(c >> 0) & 0x0f];
+			}
+			break;
+		}
+	}
+	*t = '\0';
+	return tplg_save_printf(dst, NULL, "'%s'", d);
+}
+
+static int tplg_save_string(char **dst, const char *str, int id)
+{
+	const unsigned char *p = (const unsigned char *)str;
+
+	if (!p || !*p)
+		return tplg_save_printf(dst, NULL, "''");
+
+	if (!id && ((*p >= '0' && *p <= '9') || *p == '-'))
+		return tplg_save_quoted(dst, str);
+
+	if (tplg_check_quoted(p))
+		return tplg_save_quoted(dst, str);
+
+	return tplg_save_printf(dst, NULL, "%s", str);
+}
+
+static int save_config(char **dst, int level, const char *delim, snd_config_t *src)
+{
+	snd_config_iterator_t i, next;
+	snd_config_t *s;
+	const char *id;
+	char *pfx;
+	unsigned int count;
+	int type, err, quoted, array;
+
+	if (delim == NULL)
+		delim = "";
+
+	type = snd_config_get_type(src);
+	if (type != SND_CONFIG_TYPE_COMPOUND) {
+		char *val;
+		if (type == SND_CONFIG_TYPE_INTEGER ||
+		    type == SND_CONFIG_TYPE_INTEGER64) {
+			err = tplg_pprint_integer(src, &val);
+		} else {
+			err = snd_config_get_ascii(src, &val);
+		}
+		if (err < 0)
+			return err;
+		if (type == SND_CONFIG_TYPE_STRING) {
+			/* hexa array pretty print */
+			id = strchr(val, '\n');
+			if (id) {
+				err = tplg_save_printf(dst, NULL, "\n");
+				if (err < 0)
+					goto retval;
+				for (id++; *id == '\t'; id++) {
+					err = tplg_save_printf(dst, NULL, "\t");
+					if (err < 0)
+						goto retval;
+				}
+				delim = "";
+			}
+			err = tplg_save_printf(dst, NULL, "%s'%s'\n", delim, val);
+		} else {
+			err = tplg_save_printf(dst, NULL, "%s%s\n", delim, val);
+		}
+retval:
+		free(val);
+		return err;
+	}
+
+	count = 0;
+	quoted = 0;
+	array = 0;
+	s = NULL;
+	snd_config_for_each(i, next, src) {
+		s = snd_config_iterator_entry(i);
+		err = snd_config_get_id(s, &id);
+		if (err < 0)
+			return err;
+		if (!quoted && tplg_check_quoted((unsigned char *)id))
+			quoted = 1;
+		if (array >= 0 && tplg_check_array_item(id, array))
+			array++;
+		else
+			array = -1;
+		count++;
+	}
+	if (count == 0)
+		return 0;
+
+	if (count == 1) {
+		err = snd_config_get_id(s, &id);
+		if (err >= 0 && level > 0)
+			err = tplg_save_printf(dst, NULL, ".");
+		if (err >= 0)
+			err = tplg_save_string(dst, id, 1);
+		if (err >= 0)
+			err = save_config(dst, level, " ", s);
+		return err;
+	}
+
+	pfx = alloca(level + 1);
+	memset(pfx, '\t', level);
+	pfx[level] = '\0';
+
+	if (level > 0) {
+		err = tplg_save_printf(dst, NULL, "%s%s\n", delim,
+				       array >= 0 ? "[" : "{");
+		if (err < 0)
+			return err;
+	}
+
+	snd_config_for_each(i, next, src) {
+		s = snd_config_iterator_entry(i);
+		const char *id;
+		err = snd_config_get_id(s, &id);
+		if (err < 0)
+			return err;
+		err = tplg_save_printf(dst, pfx, "");
+		if (err < 0)
+			return err;
+		if (array < 0) {
+			delim = " ";
+			if (quoted) {
+				err = tplg_save_quoted(dst, id);
+			} else {
+				err = tplg_save_string(dst, id, 1);
+				if (err < 0)
+					return err;
+			}
+		} else {
+			delim = "";
+		}
+		err = save_config(dst, level + 1, delim, s);
+		if (err < 0)
+			return err;
+	}
+
+	if (level > 0) {
+		pfx[level - 1] = '\0';
+		err = tplg_save_printf(dst, pfx, "%s\n",
+				       array >= 0 ? "]" : "}");
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int tplg_save(snd_tplg_t *tplg, char **dst, int gindex, const char *prefix)
+{
+	struct tplg_table *tptr;
+	struct tplg_elem *elem;
+	struct list_head *list, *pos;
+	char pfx2[16];
+	unsigned int index;
+	int err, count;
+
+	snprintf(pfx2, sizeof(pfx2), "%s\t", prefix ?: "");
+
+	/* write all blocks */
+	for (index = 0; index < tplg_table_items; index++) {
+		tptr = &tplg_table[index];
+		list = (struct list_head *)((void *)tplg + tptr->loff);
+
+		/* count elements */
+		count = 0;
+		list_for_each(pos, list) {
+			elem = list_entry(pos, struct tplg_elem, list);
+			if (gindex >= 0 && elem->index != gindex)
+				continue;
+			if (tptr->save == NULL && tptr->gsave == NULL) {
+				SNDERR("unable to create %s block (no callback)",
+				       tptr->id);
+				err = -ENXIO;
+				goto _err;
+			}
+			if (tptr->save)
+				count++;
+		}
+
+		if (count == 0)
+			continue;
+
+		if (count > 1) {
+			err = tplg_save_printf(dst, prefix, "%s {\n",
+					       elem->table ?
+						elem->table->id : "_NOID_");
+		} else {
+			err = tplg_save_printf(dst, prefix, "%s.",
+					       elem->table ?
+						elem->table->id : "_NOID_");
+		}
+
+		if (err < 0)
+			goto _err;
+
+		list_for_each(pos, list) {
+			elem = list_entry(pos, struct tplg_elem, list);
+			if (gindex >= 0 && elem->index != gindex)
+				continue;
+			if (count > 1) {
+				err = tplg_save_printf(dst, pfx2, "");
+				if (err < 0)
+					goto _err;
+			}
+			err = tptr->save(tplg, elem, dst, count > 1 ? pfx2 : prefix);
+			if (err < 0) {
+				SNDERR("failed to save %s elements: %s",
+				       tptr->id, snd_strerror(-err));
+				goto _err;
+			}
+		}
+		if (count > 1) {
+			err = tplg_save_printf(dst, prefix, "}\n");
+			if (err < 0)
+				goto _err;
+		}
+	}
+
+	/* save globals */
+	for (index = 0; index < tplg_table_items; index++) {
+		tptr = &tplg_table[index];
+		if (tptr->gsave) {
+			err = tptr->gsave(tplg, gindex, dst, prefix);
+			if (err < 0)
+				goto _err;
+		}
+	}
+
+	return 0;
+
+_err:
+	free(*dst);
+	*dst = NULL;
+	return err;
+}
+
+static int tplg_index_compar(const void *a, const void *b)
+{
+	const int *a1 = a, *b1 = b;
+	return *a1 - *b1;
+}
+
+static int tplg_index_groups(snd_tplg_t *tplg, int **indexes)
+{
+	struct tplg_table *tptr;
+	struct tplg_elem *elem;
+	struct list_head *list, *pos;
+	unsigned int index, j, count, size;
+	int *a, *b;
+
+	count = 0;
+	size = 16;
+	a = malloc(size * sizeof(a[0]));
+
+	for (index = 0; index < tplg_table_items; index++) {
+		tptr = &tplg_table[index];
+		list = (struct list_head *)((void *)tplg + tptr->loff);
+		list_for_each(pos, list) {
+			elem = list_entry(pos, struct tplg_elem, list);
+			for (j = 0; j < count; j++) {
+				if (a[j] == elem->index)
+					break;
+			}
+			if (j < count)
+				continue;
+			if (count + 1 >= size) {
+				size += 8;
+				b = realloc(a, size * sizeof(a[0]));
+				if (b == NULL) {
+					free(a);
+					return -ENOMEM;
+				}
+				a = b;
+			}
+			a[count++] = elem->index;
+		}
+	}
+	a[count] = -1;
+
+	qsort(a, count, sizeof(a[0]), tplg_index_compar);
+
+	*indexes = a;
+	return 0;
+}
+
+int snd_tplg_save(snd_tplg_t *tplg, char **dst, int flags)
+{
+	snd_input_t *in;
+	snd_config_t *top, *top2;
+	char *dst2;
+	int *indexes, *a;
+	int err;
+
+	assert(tplg);
+	assert(dst);
+	*dst = NULL;
+
+	if (flags & SND_TPLG_SAVE_GROUPS) {
+		err = tplg_index_groups(tplg, &indexes);
+		if (err < 0)
+			return err;
+		for (a = indexes; err >= 0 && *a >= 0; a++) {
+			err = tplg_save_printf(dst, NULL,
+					       "IndexGroup.%d {\n",
+					       *a);
+			if (err >= 0)
+				err = tplg_save(tplg, dst, *a, "\t");
+			if (err >= 0)
+				err = tplg_save_printf(dst, NULL, "}\n");
+		}
+		free(indexes);
+	} else {
+		err = tplg_save(tplg, dst, -1, NULL);
+	}
+
+	if (err < 0)
+		goto _err;
+
+	if (flags & SND_TPLG_SAVE_NOCHECK)
+		return 0;
+
+	/* always load configuration - check */
+	err = snd_input_buffer_open(&in, *dst, strlen(*dst));
+	if (err < 0) {
+		SNDERR("could not create input buffer");
+		goto _err;
+	}
+
+	err = snd_config_top(&top);
+	if (err < 0) {
+		snd_input_close(in);
+		goto _err;
+	}
+
+	err = snd_config_load(top, in);
+	snd_input_close(in);
+	if (err < 0) {
+		SNDERR("could not load configuration");
+		snd_config_delete(top);
+		goto _err;
+	}
+
+	if (flags & SND_TPLG_SAVE_SORT) {
+		top2 = sort_config(NULL, top);
+		if (top2 == NULL) {
+			SNDERR("could not sort configuration");
+			snd_config_delete(top);
+			err = -EINVAL;
+			goto _err;
+		}
+		snd_config_delete(top);
+		top = top2;
+	}
+
+	dst2 = NULL;
+	err = save_config(&dst2, 0, NULL, top);
+	snd_config_delete(top);
+	if (err < 0) {
+		SNDERR("could not save configuration");
+		goto _err;
+	}
+
+	free(*dst);
+	*dst = dst2;
+	return 0;
+
+_err:
+	free(*dst);
+	*dst = NULL;
+	return err;
+}
diff --git a/src/topology/text.c b/src/topology/text.c
index f301a4ded727..e9386e7df492 100644
--- a/src/topology/text.c
+++ b/src/topology/text.c
@@ -89,3 +89,22 @@ int tplg_parse_text(snd_tplg_t *tplg, snd_config_t *cfg,
 
 	return err;
 }
+
+/* save text data */
+int tplg_save_text(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
+		   struct tplg_elem *elem,
+		   char **dst, const char *pfx)
+{
+	struct tplg_texts *texts = elem->texts;
+	unsigned int i;
+	int err;
+
+	if (!texts || texts->num_items == 0)
+		return 0;
+	err = tplg_save_printf(dst, pfx, "'%s'.values [\n", elem->id);
+	for (i = 0; err >= 0 && i < texts->num_items; i++)
+		err = tplg_save_printf(dst, pfx, "\t'%s'\n", texts->items[i][0]);
+	if (err >= 0)
+		err = tplg_save_printf(dst, pfx, "]\n");
+	return err;
+}
diff --git a/src/topology/tplg_local.h b/src/topology/tplg_local.h
index bea88ba35608..42a3aa96ba0e 100644
--- a/src/topology/tplg_local.h
+++ b/src/topology/tplg_local.h
@@ -37,6 +37,7 @@
 
 struct tplg_ref;
 struct tplg_elem;
+struct tplg_table;
 
 typedef enum _snd_pcm_rates {
 	SND_PCM_RATE_UNKNOWN = -1,
@@ -147,6 +148,8 @@ struct tplg_vendor_tuples {
 /* topology element */
 struct tplg_elem {
 
+	struct tplg_table *table;
+
 	char id[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
 
 	int index;
@@ -209,6 +212,10 @@ struct tplg_table {
 	unsigned enew: 1;
 	void (*free)(void *);
 	int (*parse)(snd_tplg_t *tplg, snd_config_t *cfg, void *priv);
+	int (*save)(snd_tplg_t *tplg, struct tplg_elem *elem,
+		    char **dst, const char *prefix);
+	int (*gsave)(snd_tplg_t *tplg, int index,
+		     char **dst, const char *prefix);
 };
 
 extern struct tplg_table tplg_table[];
@@ -250,7 +257,8 @@ int tplg_build_pcm_dai(snd_tplg_t *tplg, unsigned int type);
 int tplg_copy_data(snd_tplg_t *tplg, struct tplg_elem *elem,
 		   struct tplg_ref *ref);
 
-int tplg_parse_data_refs(snd_config_t *cfg, struct tplg_elem *elem);
+int tplg_parse_refs(snd_config_t *cfg, struct tplg_elem *elem,
+		    unsigned int type);
 
 int tplg_ref_add(struct tplg_elem *elem, int type, const char* id);
 int tplg_ref_add_elem(struct tplg_elem *elem, struct tplg_elem *elem_ref);
@@ -269,9 +277,11 @@ struct tplg_elem* tplg_elem_new_common(snd_tplg_t *tplg,
 int tplg_get_integer(snd_config_t *n, int *val, int base);
 int tplg_get_unsigned(snd_config_t *n, unsigned *val, int base);
 
+const char *tplg_channel_name(int type);
 int tplg_parse_channel(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
 	snd_config_t *cfg, void *private);
 
+const char *tplg_ops_name(int type);
 int tplg_parse_ops(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
 	snd_config_t *cfg, void *private);
 int tplg_parse_ext_ops(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
@@ -299,3 +309,49 @@ int tplg_build_links(snd_tplg_t *tplg, unsigned int type);
 int tplg_add_link_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t);
 int tplg_add_pcm_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t);
 int tplg_add_dai_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t);
+
+int tplg_nice_value_format(char *dst, size_t dst_size, unsigned int value);
+
+int tplg_save_printf(char **dst, const char *prefix, const char *fmt, ...);
+int tplg_save_refs(snd_tplg_t *tplg, struct tplg_elem *elem, unsigned int type,
+		   const char *id, char **dst, const char *pfx);
+int tplg_save_channels(snd_tplg_t *tplg, struct snd_soc_tplg_channel *channel,
+		       unsigned int channel_count, char **dst, const char *pfx);
+int tplg_save_ops(snd_tplg_t *tplg, struct snd_soc_tplg_ctl_hdr *hdr,
+		  char **dst, const char *pfx);
+int tplg_save_ext_ops(snd_tplg_t *tplg, struct snd_soc_tplg_bytes_control *be,
+		      char **dst, const char *pfx);
+int tplg_save_manifest_data(snd_tplg_t *tplg, struct tplg_elem *elem,
+			    char **dst, const char *pfx);
+int tplg_save_control_mixer(snd_tplg_t *tplg, struct tplg_elem *elem,
+			    char **dst, const char *pfx);
+int tplg_save_control_enum(snd_tplg_t *tplg, struct tplg_elem *elem,
+			   char **dst, const char *pfx);
+int tplg_save_control_bytes(snd_tplg_t *tplg, struct tplg_elem *elem,
+			    char **dst, const char *pfx);
+int tplg_save_tlv(snd_tplg_t *tplg, struct tplg_elem *elem,
+		  char **dst, const char *pfx);
+int tplg_save_data(snd_tplg_t *tplg, struct tplg_elem *elem,
+		   char **dst, const char *pfx);
+int tplg_save_text(snd_tplg_t *tplg, struct tplg_elem *elem,
+		   char **dst, const char *pfx);
+int tplg_save_tokens(snd_tplg_t *tplg, struct tplg_elem *elem,
+		     char **dst, const char *pfx);
+int tplg_save_tuples(snd_tplg_t *tplg, struct tplg_elem *elem,
+		     char **dst, const char *pfx);
+int tplg_save_dapm_graph(snd_tplg_t *tplg, int index,
+			 char **dst, const char *pfx);
+int tplg_save_dapm_widget(snd_tplg_t *tplg, struct tplg_elem *elem,
+			  char **dst, const char *pfx);
+int tplg_save_link(snd_tplg_t *tplg, struct tplg_elem *elem,
+		   char **dst, const char *pfx);
+int tplg_save_cc(snd_tplg_t *tplg, struct tplg_elem *elem,
+		 char **dst, const char *pfx);
+int tplg_save_pcm(snd_tplg_t *tplg, struct tplg_elem *elem,
+		  char **dst, const char *pfx);
+int tplg_save_hw_config(snd_tplg_t *tplg, struct tplg_elem *elem,
+			char **dst, const char *pfx);
+int tplg_save_stream_caps(snd_tplg_t *tplg, struct tplg_elem *elem,
+			  char **dst, const char *pfx);
+int tplg_save_dai(snd_tplg_t *tplg, struct tplg_elem *elem,
+		  char **dst, const char *pfx);
-- 
2.16.4