topology: add snd_tplg_save()

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
This commit is contained in:
Jaroslav Kysela 2019-12-16 14:26:31 +01:00
parent 4a0efdc873
commit aa1bac2d04
14 changed files with 2165 additions and 260 deletions

View file

@ -27,7 +27,8 @@ libatopology_la_SOURCES =\
text.c \
channel.c \
ops.c \
elem.c
elem.c \
save.c
noinst_HEADERS = tplg_local.h

View file

@ -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;
}

View file

@ -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)
{

View file

@ -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;
unsigned int i;
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);
for (i = 0; i < ARRAY_SIZE(widget_map); i++) {
if ((unsigned int)widget_map[i].id == type)
return widget_map[i].name;
}
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;
tplg_ref_add(elem, SND_TPLG_TYPE_BYTES, value);
tplg_dbg("\t\t %s\n", value);
}
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;
}

View file

@ -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)
/* 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;
struct tplg_vendor_tuples *tuples = elem->tuples;
unsigned int i;
int err = 0;
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);
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;
}
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)
{
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;
tplg_dbg("\tdata: %s\n", val);
tplg_ref_add(elem, SND_TPLG_TYPE_DATA, val);
}
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

View file

@ -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) {

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}
/* save CC */
int tplg_save_cc(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)
err = tplg_save_printf(dst, pfx, "}\n");
return err;
}
struct audio_hw_format {
unsigned int type;
const char *name;
};
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",
},
};
static int get_audio_hw_format(const char *val)
{
if (!strlen(val))
unsigned int i;
if (val[0] == '\0')
return -EINVAL;
if (!strcmp(val, "I2S"))
return SND_SOC_DAI_FORMAT_I2S;
if (!strcmp(val, "RIGHT_J"))
return SND_SOC_DAI_FORMAT_RIGHT_J;
if (!strcmp(val, "LEFT_J"))
return SND_SOC_DAI_FORMAT_LEFT_J;
if (!strcmp(val, "DSP_A"))
return SND_SOC_DAI_FORMAT_DSP_A;
if (!strcmp(val, "DSP_B"))
return SND_SOC_DAI_FORMAT_DSP_B;
if (!strcmp(val, "AC97"))
return SND_SOC_DAI_FORMAT_AC97;
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)

632
src/topology/save.c Normal file
View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);