/*
Copyright(c) 2014-2015 Intel Corporation
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: Mengdong Lin <mengdong.lin@intel.com>
Yao Jin <yao.jin@intel.com>
Liam Girdwood <liam.r.girdwood@linux.intel.com>
*/
#include "list.h"
#include "tplg_local.h"
#define RATE(v) [SND_PCM_RATE_##v] = #v
static const char *const snd_pcm_rate_names[] = {
RATE(5512),
RATE(8000),
RATE(11025),
RATE(16000),
RATE(22050),
RATE(32000),
RATE(44100),
RATE(48000),
RATE(64000),
RATE(88200),
RATE(96000),
RATE(176400),
RATE(192000),
RATE(CONTINUOUS),
RATE(KNOT),
};
struct tplg_elem *lookup_pcm_dai_stream(struct list_head *base, const char* id)
{
struct list_head *pos;
struct tplg_elem *elem;
struct snd_soc_tplg_pcm *pcm;
list_for_each(pos, base) {
elem = list_entry(pos, struct tplg_elem, list);
if (elem->type != SND_TPLG_TYPE_PCM)
return NULL;
pcm = elem->pcm;
if (pcm && !strcmp(pcm->dai_name, id))
return elem;
}
return NULL;
}
/* copy referenced caps to the parent (pcm or be dai) */
static void copy_stream_caps(const char *id ATTRIBUTE_UNUSED,
struct snd_soc_tplg_stream_caps *caps,
struct tplg_elem *ref_elem)
{
struct snd_soc_tplg_stream_caps *ref_caps = ref_elem->stream_caps;
tplg_dbg("Copy pcm caps (%ld bytes) from '%s' to '%s'",
sizeof(*caps), ref_elem->id, id);
*caps = *ref_caps;
}
/* find and copy the referenced stream caps */
static int tplg_build_stream_caps(snd_tplg_t *tplg,
const char *id, int index,
struct snd_soc_tplg_stream_caps *caps)
{
struct tplg_elem *ref_elem = NULL;
unsigned int i;
for (i = 0; i < 2; i++) {
ref_elem = tplg_elem_lookup(&tplg->pcm_caps_list,
caps[i].name, SND_TPLG_TYPE_STREAM_CAPS, index);
if (ref_elem != NULL)
copy_stream_caps(id, &caps[i], ref_elem);
}
return 0;
}
/* build a PCM (FE DAI & DAI link) element */
static int build_pcm(snd_tplg_t *tplg, struct tplg_elem *elem)
{
struct tplg_ref *ref;
struct list_head *base, *pos;
int err;
err = tplg_build_stream_caps(tplg, elem->id, elem->index,
elem->pcm->caps);
if (err < 0)
return err;
/* merge private data from the referenced data elements */
base = &elem->ref_list;
list_for_each(pos, base) {
ref = list_entry(pos, struct tplg_ref, list);
if (ref->type == SND_TPLG_TYPE_DATA) {
err = tplg_copy_data(tplg, elem, ref);
if (err < 0)
return err;
}
if (!ref->elem) {
SNDERR("cannot find '%s' referenced by"
" PCM '%s'", ref->id, elem->id);
return -EINVAL;
}
}
return 0;
}
/* build all PCM (FE DAI & DAI link) elements */
int tplg_build_pcms(snd_tplg_t *tplg, unsigned int type)
{
struct list_head *base, *pos;
struct tplg_elem *elem;
int err = 0;
base = &tplg->pcm_list;
list_for_each(pos, base) {
elem = list_entry(pos, struct tplg_elem, list);
if (elem->type != type) {
SNDERR("invalid elem '%s'", elem->id);
return -EINVAL;
}
err = build_pcm(tplg, elem);
if (err < 0)
return err;
/* add PCM to manifest */
tplg->manifest.pcm_elems++;
}
return 0;
}
/* build a physical DAI */
static int tplg_build_dai(snd_tplg_t *tplg, struct tplg_elem *elem)
{
struct tplg_ref *ref;
struct list_head *base, *pos;
int err = 0;
/* get playback & capture stream caps */
err = tplg_build_stream_caps(tplg, elem->id, elem->index,
elem->dai->caps);
if (err < 0)
return err;
/* get private data */
base = &elem->ref_list;
list_for_each(pos, base) {
ref = list_entry(pos, struct tplg_ref, list);
if (ref->type == SND_TPLG_TYPE_DATA) {
err = tplg_copy_data(tplg, elem, ref);
if (err < 0)
return err;
}
}
/* add DAI to manifest */
tplg->manifest.dai_elems++;
return 0;
}
/* build physical DAIs*/
int tplg_build_dais(snd_tplg_t *tplg, unsigned int type)
{
struct list_head *base, *pos;
struct tplg_elem *elem;
int err = 0;
base = &tplg->dai_list;
list_for_each(pos, base) {
elem = list_entry(pos, struct tplg_elem, list);
if (elem->type != type) {
SNDERR("invalid elem '%s'", elem->id);
return -EINVAL;
}
err = tplg_build_dai(tplg, elem);
if (err < 0)
return err;
}
return 0;
}
static int tplg_build_stream_cfg(snd_tplg_t *tplg,
struct snd_soc_tplg_stream *stream,
int num_streams, int index)
{
struct snd_soc_tplg_stream *strm;
struct tplg_elem *ref_elem;
int i;
for (i = 0; i < num_streams; i++) {
strm = stream + i;
ref_elem = tplg_elem_lookup(&tplg->pcm_config_list,
strm->name, SND_TPLG_TYPE_STREAM_CONFIG, index);
if (ref_elem && ref_elem->stream_cfg)
*strm = *ref_elem->stream_cfg;
}
return 0;
}
static int build_link(snd_tplg_t *tplg, struct tplg_elem *elem)
{
struct snd_soc_tplg_link_config *link = elem->link;
struct tplg_ref *ref;
struct list_head *base, *pos;
int num_hw_configs = 0, err = 0;
err = tplg_build_stream_cfg(tplg, link->stream,
link->num_streams, elem->index);
if (err < 0)
return err;
/* hw configs & private data */
base = &elem->ref_list;
list_for_each(pos, base) {
ref = list_entry(pos, struct tplg_ref, list);
switch (ref->type) {
case SND_TPLG_TYPE_HW_CONFIG:
ref->elem = tplg_elem_lookup(&tplg->hw_cfg_list,
ref->id, SND_TPLG_TYPE_HW_CONFIG, elem->index);
if (!ref->elem) {
SNDERR("cannot find HW config '%s'"
" referenced by link '%s'",
ref->id, elem->id);
return -EINVAL;
}
memcpy(&link->hw_config[num_hw_configs],
ref->elem->hw_cfg,
sizeof(struct snd_soc_tplg_hw_config));
num_hw_configs++;
break;
case SND_TPLG_TYPE_DATA: /* merge private data */
err = tplg_copy_data(tplg, elem, ref);
if (err < 0)
return err;
link = elem->link; /* realloc */
break;
default:
break;
}
}
/* add link to manifest */
tplg->manifest.dai_link_elems++;
return 0;
}
/* build physical DAI link configurations */
int tplg_build_links(snd_tplg_t *tplg, unsigned int type)
{
struct list_head *base, *pos;
struct tplg_elem *elem;
int err = 0;
switch (type) {
case SND_TPLG_TYPE_LINK:
case SND_TPLG_TYPE_BE:
base = &tplg->be_list;
break;
case SND_TPLG_TYPE_CC:
base = &tplg->cc_list;
break;
default:
return -EINVAL;
}
list_for_each(pos, base) {
elem = list_entry(pos, struct tplg_elem, list);
err = build_link(tplg, elem);
if (err < 0)
return err;
}
return 0;
}
static int split_format(struct snd_soc_tplg_stream_caps *caps, char *str)
{
char *s = NULL;
snd_pcm_format_t format;
int i = 0;
s = strtok(str, ",");
while ((s != NULL) && (i < SND_SOC_TPLG_MAX_FORMATS)) {
format = snd_pcm_format_value(s);
if (format == SND_PCM_FORMAT_UNKNOWN) {
SNDERR("unsupported stream format %s", s);
return -EINVAL;
}
caps->formats |= 1ull << format;
s = strtok(NULL, ", ");
i++;
}
return 0;
}
static int get_rate_value(const char* name)
{
int rate;
for (rate = 0; rate <= SND_PCM_RATE_LAST; rate++) {
if (snd_pcm_rate_names[rate] &&
strcasecmp(name, snd_pcm_rate_names[rate]) == 0) {
return rate;
}
}
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;
snd_pcm_rates_t rate;
int i = 0;
s = strtok(str, ",");
while (s) {
rate = get_rate_value(s);
if (rate == SND_PCM_RATE_UNKNOWN) {
SNDERR("unsupported stream rate %s", s);
return -EINVAL;
}
caps->rates |= 1 << rate;
s = strtok(NULL, ", ");
i++;
}
return 0;
}
static int parse_unsigned(snd_config_t *n, void *dst)
{
int ival;
if (tplg_get_integer(n, &ival, 0) < 0)
return -EINVAL;
unaligned_put32(dst, ival);
#if TPLG_DEBUG
{
const char *id;
if (snd_config_get_id(n, &id) >= 0)
tplg_dbg("\t\t%s: %d", id, ival);
}
#endif
return 0;
}
/* Parse pcm stream capabilities */
int tplg_parse_stream_caps(snd_tplg_t *tplg,
snd_config_t *cfg,
void *private ATTRIBUTE_UNUSED)
{
struct snd_soc_tplg_stream_caps *sc;
struct tplg_elem *elem;
snd_config_iterator_t i, next;
snd_config_t *n;
const char *id, *val;
char *s;
int err;
elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_STREAM_CAPS);
if (!elem)
return -ENOMEM;
sc = elem->stream_caps;
sc->size = elem->size;
snd_strlcpy(sc->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
tplg_dbg(" PCM Capabilities: %s", elem->id);
snd_config_for_each(i, next, cfg) {
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
/* skip comments */
if (strcmp(id, "comment") == 0)
continue;
if (id[0] == '#')
continue;
if (strcmp(id, "formats") == 0) {
if (snd_config_get_string(n, &val) < 0)
return -EINVAL;
s = strdup(val);
if (s == NULL)
return -ENOMEM;
err = split_format(sc, s);
free(s);
if (err < 0)
return err;
tplg_dbg("\t\t%s: %s", id, val);
continue;
}
if (strcmp(id, "rates") == 0) {
if (snd_config_get_string(n, &val) < 0)
return -EINVAL;
s = strdup(val);
if (!s)
return -ENOMEM;
err = split_rate(sc, s);
free(s);
if (err < 0)
return err;
tplg_dbg("\t\t%s: %s", id, val);
continue;
}
if (strcmp(id, "rate_min") == 0) {
if (parse_unsigned(n, &sc->rate_min))
return -EINVAL;
continue;
}
if (strcmp(id, "rate_max") == 0) {
if (parse_unsigned(n, &sc->rate_max))
return -EINVAL;
continue;
}
if (strcmp(id, "channels_min") == 0) {
if (parse_unsigned(n, &sc->channels_min))
return -EINVAL;
continue;
}
if (strcmp(id, "channels_max") == 0) {
if (parse_unsigned(n, &sc->channels_max))
return -EINVAL;
continue;
}
if (strcmp(id, "periods_min") == 0) {
if (parse_unsigned(n, &sc->periods_min))
return -EINVAL;
continue;
}
if (strcmp(id, "periods_max") == 0) {
if (parse_unsigned(n, &sc->periods_max))
return -EINVAL;
continue;
}
if (strcmp(id, "period_size_min") == 0) {
if (parse_unsigned(n, &sc->period_size_min))
return -EINVAL;
continue;
}
if (strcmp(id, "period_size_max") == 0) {
if (parse_unsigned(n, &sc->period_size_max))
return -EINVAL;
continue;
}
if (strcmp(id, "buffer_size_min") == 0) {
if (parse_unsigned(n, &sc->buffer_size_min))
return -EINVAL;
continue;
}
if (strcmp(id, "buffer_size_max") == 0) {
if (parse_unsigned(n, &sc->buffer_size_max))
return -EINVAL;
continue;
}
if (strcmp(id, "sig_bits") == 0) {
if (parse_unsigned(n, &sc->sig_bits))
return -EINVAL;
continue;
}
}
return 0;
}
/* save stream caps */
int tplg_save_stream_caps(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
struct tplg_elem *elem,
struct tplg_buf *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 && sc->sig_bits)
err = tplg_save_printf(dst, pfx, "\tsig_bits %u\n",
sc->sig_bits);
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)
{
snd_config_iterator_t i, next;
snd_config_t *n;
struct tplg_elem *elem = private;
struct snd_soc_tplg_pcm *pcm;
struct snd_soc_tplg_dai *dai;
void *playback, *capture;
struct snd_soc_tplg_stream_caps *caps;
const char *id, *value;
int stream;
snd_config_get_id(cfg, &id);
tplg_dbg("\t%s:", id);
switch (elem->type) {
case SND_TPLG_TYPE_PCM:
pcm = elem->pcm;
playback = &pcm->playback;
capture = &pcm->capture;
caps = pcm->caps;
break;
case SND_TPLG_TYPE_DAI:
dai = elem->dai;
playback = &dai->playback;
capture = &dai->capture;
caps = dai->caps;
break;
default:
return -EINVAL;
}
if (strcmp(id, "playback") == 0) {
stream = SND_SOC_TPLG_STREAM_PLAYBACK;
unaligned_put32(playback, 1);
} else if (strcmp(id, "capture") == 0) {
stream = SND_SOC_TPLG_STREAM_CAPTURE;
unaligned_put32(capture, 1);
} else
return -EINVAL;
snd_config_for_each(i, next, cfg) {
n = snd_config_iterator_entry(i);
/* get id */
if (snd_config_get_id(n, &id) < 0)
continue;
if (strcmp(id, "capabilities") == 0) {
if (snd_config_get_string(n, &value) < 0)
continue;
/* store stream caps name, to find and merge
* the caps in building phase.
*/
snd_strlcpy(caps[stream].name, value,
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
tplg_dbg("\t\t%s\n\t\t\t%s", id, value);
continue;
}
}
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,
struct tplg_buf *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)
{
struct tplg_elem *elem = private;
struct snd_soc_tplg_pcm *pcm = elem->pcm;
snd_config_iterator_t i, next;
snd_config_t *n;
const char *id;
unsigned int dai_id;
snd_config_get_id(cfg, &id);
tplg_dbg("\t\tFE DAI %s:", id);
snd_strlcpy(pcm->dai_name, id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
snd_config_for_each(i, next, cfg) {
n = snd_config_iterator_entry(i);
/* get id */
if (snd_config_get_id(n, &id) < 0)
continue;
if (strcmp(id, "id") == 0) {
if (tplg_get_unsigned(n, &dai_id, 0)) {
SNDERR("invalid fe dai ID");
return -EINVAL;
}
unaligned_put32(&pcm->dai_id, dai_id);
tplg_dbg("\t\t\tindex: %d", dai_id);
}
}
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,
struct tplg_buf *dst, const char *pfx)
{
struct snd_soc_tplg_pcm *pcm = elem->pcm;
int err = 0;
if (strlen(pcm->dai_name))
err = tplg_save_printf(dst, pfx, "dai.'%s'.id %u\n", pcm->dai_name, pcm->dai_id);
else 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,
void *mask, void *flags)
{
int ret;
ret = snd_config_get_bool(n);
if (ret < 0)
return ret;
unaligned_put32(mask, unaligned_get32(mask) | mask_in);
if (ret)
unaligned_put32(flags, unaligned_get32(flags) | mask_in);
else
unaligned_put32(flags, unaligned_get32(flags) & (~mask_in));
return 0;
}
static int save_flags(unsigned int flags, unsigned int mask,
struct tplg_buf *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)
{
struct snd_soc_tplg_pcm *pcm;
struct tplg_elem *elem;
snd_config_iterator_t i, next;
snd_config_t *n;
const char *id;
int err, ival;
elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_PCM);
if (!elem)
return -ENOMEM;
pcm = elem->pcm;
pcm->size = elem->size;
snd_strlcpy(pcm->pcm_name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
tplg_dbg(" PCM: %s", elem->id);
snd_config_for_each(i, next, cfg) {
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
/* skip comments */
if (strcmp(id, "comment") == 0)
continue;
if (id[0] == '#')
continue;
if (strcmp(id, "id") == 0) {
if (parse_unsigned(n, &pcm->pcm_id))
return -EINVAL;
continue;
}
if (strcmp(id, "pcm") == 0) {
err = tplg_parse_compound(tplg, n,
tplg_parse_streams, elem);
if (err < 0)
return err;
continue;
}
if (strcmp(id, "compress") == 0) {
ival = snd_config_get_bool(n);
if (ival < 0)
return -EINVAL;
pcm->compress = ival;
tplg_dbg("\t%s: %d", id, ival);
continue;
}
if (strcmp(id, "dai") == 0) {
err = tplg_parse_compound(tplg, n,
tplg_parse_fe_dai, elem);
if (err < 0)
return err;
continue;
}
/* flags */
if (strcmp(id, "symmetric_rates") == 0) {
err = parse_flag(n,
SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_RATES,
&pcm->flag_mask, &pcm->flags);
if (err < 0)
return err;
continue;
}
if (strcmp(id, "symmetric_channels") == 0) {
err = parse_flag(n,
SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_CHANNELS,
&pcm->flag_mask, &pcm->flags);
if (err < 0)
return err;
continue;
}
if (strcmp(id, "symmetric_sample_bits") == 0) {
err = parse_flag(n,
SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_SAMPLEBITS,
&pcm->flag_mask, &pcm->flags);
if (err < 0)
return err;
continue;
}
/* private data */
if (strcmp(id, "data") == 0) {
err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA);
if (err < 0)
return err;
continue;
}
}
return 0;
}
/* save PCM */
int tplg_save_pcm(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
struct tplg_elem *elem,
struct tplg_buf *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)
{
struct snd_soc_tplg_dai *dai;
struct tplg_elem *elem;
snd_config_iterator_t i, next;
snd_config_t *n;
const char *id;
int err;
elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_DAI);
if (!elem)
return -ENOMEM;
dai = elem->dai;
dai->size = elem->size;
snd_strlcpy(dai->dai_name, elem->id,
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
tplg_dbg(" DAI: %s", elem->id);
snd_config_for_each(i, next, cfg) {
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
/* skip comments */
if (strcmp(id, "comment") == 0)
continue;
if (id[0] == '#')
continue;
if (strcmp(id, "id") == 0) {
if (parse_unsigned(n, &dai->dai_id))
return -EINVAL;
continue;
}
if (strcmp(id, "playback") == 0) {
if (parse_unsigned(n, &dai->playback))
return -EINVAL;
continue;
}
if (strcmp(id, "capture") == 0) {
if (parse_unsigned(n, &dai->capture))
return -EINVAL;
continue;
}
/* stream capabilities */
if (strcmp(id, "pcm") == 0) {
err = tplg_parse_compound(tplg, n,
tplg_parse_streams, elem);
if (err < 0)
return err;
continue;
}
/* flags */
if (strcmp(id, "symmetric_rates") == 0) {
err = parse_flag(n,
SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_RATES,
&dai->flag_mask, &dai->flags);
if (err < 0)
return err;
continue;
}
if (strcmp(id, "symmetric_channels") == 0) {
err = parse_flag(n,
SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_CHANNELS,
&dai->flag_mask, &dai->flags);
if (err < 0)
return err;
continue;
}
if (strcmp(id, "symmetric_sample_bits") == 0) {
err = parse_flag(n,
SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_SAMPLEBITS,
&dai->flag_mask, &dai->flags);
if (err < 0)
return err;
continue;
}
/* private data */
if (strcmp(id, "data") == 0) {
err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA);
if (err < 0)
return err;
continue;
}
}
return 0;
}
/* save DAI */
int tplg_save_dai(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
struct tplg_elem *elem,
struct tplg_buf *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;
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;
}
/* Parse a physical link element in text conf file */
int tplg_parse_link(snd_tplg_t *tplg, snd_config_t *cfg,
void *private ATTRIBUTE_UNUSED)
{
struct snd_soc_tplg_link_config *link;
struct tplg_elem *elem;
snd_config_iterator_t i, next;
snd_config_t *n;
const char *id, *val = NULL;
int err;
elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_BE);
if (!elem)
return -ENOMEM;
link = elem->link;
link->size = elem->size;
snd_strlcpy(link->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
tplg_dbg(" Link: %s", elem->id);
snd_config_for_each(i, next, cfg) {
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
/* skip comments */
if (strcmp(id, "comment") == 0)
continue;
if (id[0] == '#')
continue;
if (strcmp(id, "id") == 0) {
if (parse_unsigned(n, &link->id))
return -EINVAL;
continue;
}
if (strcmp(id, "stream_name") == 0) {
if (snd_config_get_string(n, &val) < 0)
return -EINVAL;
snd_strlcpy(link->stream_name, val,
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
tplg_dbg("\t%s: %s", id, val);
continue;
}
if (strcmp(id, "hw_configs") == 0) {
err = parse_hw_config_refs(tplg, n, elem);
if (err < 0)
return err;
continue;
}
if (strcmp(id, "default_hw_conf_id") == 0) {
if (parse_unsigned(n, &link->default_hw_config_id))
return -EINVAL;
continue;
}
/* flags */
if (strcmp(id, "symmetric_rates") == 0) {
err = parse_flag(n,
SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_RATES,
&link->flag_mask, &link->flags);
if (err < 0)
return err;
continue;
}
if (strcmp(id, "symmetric_channels") == 0) {
err = parse_flag(n,
SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_CHANNELS,
&link->flag_mask, &link->flags);
if (err < 0)
return err;
continue;
}
if (strcmp(id, "symmetric_sample_bits") == 0) {
err = parse_flag(n,
SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_SAMPLEBITS,
&link->flag_mask, &link->flags);
if (err < 0)
return err;
continue;
}
/* private data */
if (strcmp(id, "data") == 0) {
err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA);
if (err < 0)
return err;
continue;
}
}
return 0;
}
/* save physical link */
int tplg_save_link(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
struct tplg_elem *elem,
struct tplg_buf *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)
{
struct snd_soc_tplg_link_config *link;
struct tplg_elem *elem;
snd_config_iterator_t i, next;
snd_config_t *n;
const char *id;
elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_CC);
if (!elem)
return -ENOMEM;
link = elem->link;
link->size = elem->size;
tplg_dbg(" CC: %s", elem->id);
snd_config_for_each(i, next, cfg) {
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
/* skip comments */
if (strcmp(id, "comment") == 0)
continue;
if (id[0] == '#')
continue;
if (strcmp(id, "id") == 0) {
if (parse_unsigned(n, &link->id))
return -EINVAL;
continue;
}
}
return 0;
}
/* save CC */
int tplg_save_cc(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
struct tplg_elem *elem,
struct tplg_buf *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)
{
unsigned int i;
if (val[0] == '\0')
return -EINVAL;
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("invalid audio HW format %s", 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)
{
struct snd_soc_tplg_hw_config *hw_cfg;
struct tplg_elem *elem;
snd_config_iterator_t i, next;
snd_config_t *n;
const char *id, *val = NULL;
int ret, ival;
bool provider_legacy;
elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_HW_CONFIG);
if (!elem)
return -ENOMEM;
hw_cfg = elem->hw_cfg;
hw_cfg->size = elem->size;
tplg_dbg(" Link HW config: %s", elem->id);
snd_config_for_each(i, next, cfg) {
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
/* skip comments */
if (strcmp(id, "comment") == 0)
continue;
if (id[0] == '#')
continue;
if (strcmp(id, "id") == 0) {
if (parse_unsigned(n, &hw_cfg->id))
return -EINVAL;
continue;
}
if (strcmp(id, "format") == 0 ||
strcmp(id, "fmt") == 0) {
if (snd_config_get_string(n, &val) < 0)
return -EINVAL;
ret = get_audio_hw_format(val);
if (ret < 0)
return ret;
hw_cfg->fmt = ret;
continue;
}
provider_legacy = false;
if (strcmp(id, "bclk_master") == 0) {
SNDERR("deprecated option %s, please use 'bclk'\n", id);
provider_legacy = true;
}
if (provider_legacy ||
strcmp(id, "bclk") == 0) {
if (snd_config_get_string(n, &val) < 0)
return -EINVAL;
if (!strcmp(val, "master")) {
/* For backwards capability,
* "master" == "codec is slave"
*/
SNDERR("deprecated bclk value '%s'", val);
hw_cfg->bclk_provider = SND_SOC_TPLG_BCLK_CC;
} else if (!strcmp(val, "codec_slave")) {
SNDERR("deprecated bclk value '%s', use 'codec_consumer'", val);
hw_cfg->bclk_provider = SND_SOC_TPLG_BCLK_CC;
} else if (!strcmp(val, "codec_consumer")) {
hw_cfg->bclk_provider = SND_SOC_TPLG_BCLK_CC;
} else if (!strcmp(val, "codec_master")) {
SNDERR("deprecated bclk value '%s', use 'codec_provider", val);
hw_cfg->bclk_provider = SND_SOC_TPLG_BCLK_CP;
} else if (!strcmp(val, "codec_provider")) {
hw_cfg->bclk_provider = SND_SOC_TPLG_BCLK_CP;
}
continue;
}
if (strcmp(id, "bclk_freq") == 0 ||
strcmp(id, "bclk_rate") == 0) {
if (parse_unsigned(n, &hw_cfg->bclk_rate))
return -EINVAL;
continue;
}
if (strcmp(id, "bclk_invert") == 0 ||
strcmp(id, "invert_bclk") == 0) {
ival = snd_config_get_bool(n);
if (ival < 0)
return -EINVAL;
hw_cfg->invert_bclk = ival;
continue;
}
provider_legacy = false;
if (strcmp(id, "fsync_master") == 0) {
SNDERR("deprecated option %s, please use 'fsync'\n", id);
provider_legacy = true;
}
if (provider_legacy ||
strcmp(id, "fsync") == 0) {
if (snd_config_get_string(n, &val) < 0)
return -EINVAL;
if (!strcmp(val, "master")) {
/* For backwards capability,
* "master" == "codec is slave"
*/
SNDERR("deprecated fsync value '%s'", val);
hw_cfg->fsync_provider = SND_SOC_TPLG_FSYNC_CC;
} else if (!strcmp(val, "codec_slave")) {
SNDERR("deprecated fsync value '%s', use 'codec_consumer'", val);
hw_cfg->fsync_provider = SND_SOC_TPLG_FSYNC_CC;
} else if (!strcmp(val, "codec_consumer")) {
hw_cfg->fsync_provider = SND_SOC_TPLG_FSYNC_CC;
} else if (!strcmp(val, "codec_master")) {
SNDERR("deprecated fsync value '%s', use 'codec_provider'", val);
hw_cfg->fsync_provider = SND_SOC_TPLG_FSYNC_CP;
} else if (!strcmp(val, "codec_provider")) {
hw_cfg->fsync_provider = SND_SOC_TPLG_FSYNC_CP;
}
continue;
}
if (strcmp(id, "fsync_invert") == 0 ||
strcmp(id, "invert_fsync") == 0) {
ival = snd_config_get_bool(n);
if (ival < 0)
return -EINVAL;
hw_cfg->invert_fsync = ival;
continue;
}
if (strcmp(id, "fsync_freq") == 0 ||
strcmp(id, "fsync_rate") == 0) {
if (parse_unsigned(n, &hw_cfg->fsync_rate))
return -EINVAL;
continue;
}
if (strcmp(id, "mclk_freq") == 0 ||
strcmp(id, "mclk_rate") == 0) {
if (parse_unsigned(n, &hw_cfg->mclk_rate))
return -EINVAL;
continue;
}
if (strcmp(id, "mclk") == 0 ||
strcmp(id, "mclk_direction") == 0) {
if (snd_config_get_string(n, &val) < 0)
return -EINVAL;
if (!strcmp(val, "master")) {
/* For backwards capability,
* "master" == "for codec, mclk is input"
*/
SNDERR("deprecated mclk value '%s'", val);
hw_cfg->mclk_direction = SND_SOC_TPLG_MCLK_CI;
} else if (!strcmp(val, "codec_mclk_in")) {
hw_cfg->mclk_direction = SND_SOC_TPLG_MCLK_CI;
} else if (!strcmp(val, "codec_mclk_out")) {
hw_cfg->mclk_direction = SND_SOC_TPLG_MCLK_CO;
}
continue;
}
if (strcmp(id, "pm_gate_clocks") == 0 ||
strcmp(id, "clock_gated") == 0) {
ival = snd_config_get_bool(n);
if (ival < 0)
return -EINVAL;
if (ival)
hw_cfg->clock_gated =
SND_SOC_TPLG_DAI_CLK_GATE_GATED;
else
hw_cfg->clock_gated =
SND_SOC_TPLG_DAI_CLK_GATE_CONT;
continue;
}
if (strcmp(id, "tdm_slots") == 0) {
if (parse_unsigned(n, &hw_cfg->tdm_slots))
return -EINVAL;
continue;
}
if (strcmp(id, "tdm_slot_width") == 0) {
if (parse_unsigned(n, &hw_cfg->tdm_slot_width))
return -EINVAL;
continue;
}
if (strcmp(id, "tx_slots") == 0) {
if (parse_unsigned(n, &hw_cfg->tx_slots))
return -EINVAL;
continue;
}
if (strcmp(id, "rx_slots") == 0) {
if (parse_unsigned(n, &hw_cfg->rx_slots))
return -EINVAL;
continue;
}
if (strcmp(id, "tx_channels") == 0) {
if (parse_unsigned(n, &hw_cfg->tx_channels))
return -EINVAL;
continue;
}
if (strcmp(id, "rx_channels") == 0) {
if (parse_unsigned(n, &hw_cfg->rx_channels))
return -EINVAL;
continue;
}
}
return 0;
}
/* save hw config */
int tplg_save_hw_config(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
struct tplg_elem *elem,
struct tplg_buf *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_provider)
err = tplg_save_printf(dst, pfx, "\tbclk '%s'\n",
hc->bclk_provider == SND_SOC_TPLG_BCLK_CC ?
"codec_consumer" : "codec_provider");
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_provider)
err = tplg_save_printf(dst, pfx, "\tfsync_provider '%s'\n",
hc->fsync_provider == SND_SOC_TPLG_FSYNC_CC ?
"codec_consumer" : "codec_provider");
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)
{
snd_strlcpy(strm->name, strm_tpl->name,
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
strm->format = strm_tpl->format;
strm->rate = strm_tpl->rate;
strm->period_bytes = strm_tpl->period_bytes;
strm->buffer_bytes = strm_tpl->buffer_bytes;
strm->channels = strm_tpl->channels;
}
static int tplg_add_stream_caps(snd_tplg_t *tplg,
struct snd_tplg_stream_caps_template *caps_tpl)
{
struct snd_soc_tplg_stream_caps *caps;
struct tplg_elem *elem;
elem = tplg_elem_new_common(tplg, NULL, caps_tpl->name,
SND_TPLG_TYPE_STREAM_CAPS);
if (!elem)
return -ENOMEM;
caps = elem->stream_caps;
snd_strlcpy(caps->name, caps_tpl->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
caps->formats = caps_tpl->formats;
caps->rates = caps_tpl->rates;
caps->rate_min = caps_tpl->rate_min;
caps->rate_max = caps_tpl->rate_max;
caps->channels_min = caps_tpl->channels_min;
caps->channels_max = caps_tpl->channels_max;
caps->periods_min = caps_tpl->periods_min;
caps->periods_max = caps_tpl->periods_max;
caps->period_size_min = caps_tpl->period_size_min;
caps->period_size_max = caps_tpl->period_size_max;
caps->buffer_size_min = caps_tpl->buffer_size_min;
caps->buffer_size_max = caps_tpl->buffer_size_max;
caps->sig_bits = caps_tpl->sig_bits;
return 0;
}
/* Add a PCM element (FE DAI & DAI link) from C API */
int tplg_add_pcm_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t)
{
struct snd_tplg_pcm_template *pcm_tpl = t->pcm;
struct snd_soc_tplg_private *priv;
struct snd_soc_tplg_pcm *pcm;
struct tplg_elem *elem;
int ret, i;
tplg_dbg("PCM: %s, DAI %s", pcm_tpl->pcm_name, pcm_tpl->dai_name);
if (pcm_tpl->num_streams > SND_SOC_TPLG_STREAM_CONFIG_MAX)
return -EINVAL;
elem = tplg_elem_new_common(tplg, NULL, pcm_tpl->pcm_name,
SND_TPLG_TYPE_PCM);
if (!elem)
return -ENOMEM;
pcm = elem->pcm;
pcm->size = elem->size;
snd_strlcpy(pcm->pcm_name, pcm_tpl->pcm_name,
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
snd_strlcpy(pcm->dai_name, pcm_tpl->dai_name,
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
pcm->pcm_id = pcm_tpl->pcm_id;
pcm->dai_id = pcm_tpl->dai_id;
pcm->playback = pcm_tpl->playback;
pcm->capture = pcm_tpl->capture;
pcm->compress = pcm_tpl->compress;
for (i = 0; i < 2; i++) {
if (!pcm_tpl->caps[i] || !pcm_tpl->caps[i]->name)
continue;
ret = tplg_add_stream_caps(tplg, pcm_tpl->caps[i]);
if (ret < 0)
return ret;
snd_strlcpy(pcm->caps[i].name, pcm_tpl->caps[i]->name,
sizeof(pcm->caps[i].name));
}
pcm->flag_mask = pcm_tpl->flag_mask;
pcm->flags = pcm_tpl->flags;
pcm->num_streams = pcm_tpl->num_streams;
for (i = 0; i < pcm_tpl->num_streams; i++)
tplg_add_stream_object(&pcm->stream[i], &pcm_tpl->stream[i]);
/* private data */
priv = pcm_tpl->priv;
if (priv && priv->size > 0) {
ret = tplg_add_data(tplg, elem, priv,
sizeof(*priv) + priv->size);
if (ret < 0)
return ret;
}
return 0;
}
/* Set link HW config from C API template */
static int set_link_hw_config(struct snd_soc_tplg_hw_config *cfg,
struct snd_tplg_hw_config_template *tpl)
{
unsigned int i;
cfg->size = sizeof(*cfg);
cfg->id = tpl->id;
cfg->fmt = tpl->fmt;
cfg->clock_gated = tpl->clock_gated;
cfg->invert_bclk = tpl->invert_bclk;
cfg->invert_fsync = tpl->invert_fsync;
cfg->bclk_provider = tpl->bclk_provider;
cfg->fsync_provider = tpl->fsync_provider;
cfg->mclk_direction = tpl->mclk_direction;
cfg->reserved = tpl->reserved;
cfg->mclk_rate = tpl->mclk_rate;
cfg->bclk_rate = tpl->bclk_rate;
cfg->fsync_rate = tpl->fsync_rate;
cfg->tdm_slots = tpl->tdm_slots;
cfg->tdm_slot_width = tpl->tdm_slot_width;
cfg->tx_slots = tpl->tx_slots;
cfg->rx_slots = tpl->rx_slots;
if (cfg->tx_channels > SND_SOC_TPLG_MAX_CHAN
|| cfg->rx_channels > SND_SOC_TPLG_MAX_CHAN)
return -EINVAL;
cfg->tx_channels = tpl->tx_channels;
for (i = 0; i < cfg->tx_channels; i++)
cfg->tx_chanmap[i] = tpl->tx_chanmap[i];
cfg->rx_channels = tpl->rx_channels;
for (i = 0; i < cfg->rx_channels; i++)
cfg->rx_chanmap[i] = tpl->rx_chanmap[i];
return 0;
}
/* Add a physical DAI link element from C API */
int tplg_add_link_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t)
{
struct snd_tplg_link_template *link_tpl = t->link;
struct snd_soc_tplg_link_config *link;
struct snd_soc_tplg_private *priv;
struct tplg_elem *elem;
unsigned int i;
int ret;
if (t->type != SND_TPLG_TYPE_LINK && t->type != SND_TPLG_TYPE_BE
&& t->type != SND_TPLG_TYPE_CC)
return -EINVAL;
elem = tplg_elem_new_common(tplg, NULL, link_tpl->name, t->type);
if (!elem)
return -ENOMEM;
tplg_dbg("Link: %s", link_tpl->name);
link = elem->link;
link->size = elem->size;
/* ID and names */
link->id = link_tpl->id;
snd_strlcpy(link->name, link_tpl->name,
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
snd_strlcpy(link->stream_name, link_tpl->stream_name,
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
/* stream configs */
if (link_tpl->num_streams > SND_SOC_TPLG_STREAM_CONFIG_MAX)
return -EINVAL;
link->num_streams = link_tpl->num_streams;
for (i = 0; i < link->num_streams; i++)
tplg_add_stream_object(&link->stream[i], &link_tpl->stream[i]);
/* HW configs */
if (link_tpl->num_hw_configs > SND_SOC_TPLG_HW_CONFIG_MAX)
return -EINVAL;
link->num_hw_configs = link_tpl->num_hw_configs;
link->default_hw_config_id = link_tpl->default_hw_config_id;
for (i = 0; i < link->num_hw_configs; i++)
set_link_hw_config(&link->hw_config[i], &link_tpl->hw_config[i]);
/* flags */
link->flag_mask = link_tpl->flag_mask;
link->flags = link_tpl->flags;
/* private data */
priv = link_tpl->priv;
if (priv && priv->size > 0) {
ret = tplg_add_data(tplg, elem, priv,
sizeof(*priv) + priv->size);
if (ret < 0)
return ret;
}
return 0;
}
int tplg_add_dai_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t)
{
struct snd_tplg_dai_template *dai_tpl = t->dai;
struct snd_soc_tplg_dai *dai;
struct snd_soc_tplg_private *priv;
struct tplg_elem *elem;
int ret, i;
tplg_dbg("DAI %s", dai_tpl->dai_name);
elem = tplg_elem_new_common(tplg, NULL, dai_tpl->dai_name,
SND_TPLG_TYPE_DAI);
if (!elem)
return -ENOMEM;
dai = elem->dai;
dai->size = elem->size;
snd_strlcpy(dai->dai_name, dai_tpl->dai_name,
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
dai->dai_id = dai_tpl->dai_id;
/* stream caps */
dai->playback = dai_tpl->playback;
dai->capture = dai_tpl->capture;
for (i = 0; i < 2; i++) {
if (!dai_tpl->caps[i] || !dai_tpl->caps[i]->name)
continue;
ret = tplg_add_stream_caps(tplg, dai_tpl->caps[i]);
if (ret < 0)
return ret;
snd_strlcpy(dai->caps[i].name, dai_tpl->caps[i]->name,
sizeof(dai->caps[i].name));
}
/* flags */
dai->flag_mask = dai_tpl->flag_mask;
dai->flags = dai_tpl->flags;
/* private data */
priv = dai_tpl->priv;
if (priv && priv->size > 0) {
ret = tplg_add_data(tplg, elem, priv,
sizeof(*priv) + priv->size);
if (ret < 0)
return ret;
}
return 0;
}
/* decode pcm from the binary input */
int tplg_decode_pcm(snd_tplg_t *tplg,
size_t pos,
struct snd_soc_tplg_hdr *hdr,
void *bin, size_t size)
{
struct snd_soc_tplg_pcm *pcm;
snd_tplg_obj_template_t t;
struct snd_tplg_pcm_template *pt;
struct snd_tplg_stream_caps_template caps[2], *cap;
struct snd_tplg_stream_template *stream;
unsigned int i;
size_t asize;
int err;
err = tplg_decode_template(tplg, pos, hdr, &t);
if (err < 0)
return err;
asize = sizeof(*pt) + SND_SOC_TPLG_STREAM_CONFIG_MAX * sizeof(*stream);
pt = alloca(asize);
next:
memset(pt, 0, asize);
pcm = bin;
if (size < sizeof(*pcm)) {
SNDERR("pcm: small size %d", size);
return -EINVAL;
}
if (sizeof(*pcm) != pcm->size) {
SNDERR("pcm: unknown element size %d (expected %zd)",
pcm->size, sizeof(*pcm));
return -EINVAL;
}
if (pcm->num_streams > SND_SOC_TPLG_STREAM_CONFIG_MAX) {
SNDERR("pcm: wrong number of streams %d", pcm->num_streams);
return -EINVAL;
}
if (sizeof(*pcm) + pcm->priv.size > size) {
SNDERR("pcm: wrong private data size %d", pcm->priv.size);
return -EINVAL;
}
tplg_log(tplg, 'D', pos, "pcm: size %d private size %d streams %d",
pcm->size, pcm->priv.size, pcm->num_streams);
pt->pcm_name = pcm->pcm_name;
tplg_log(tplg, 'D', pos, "pcm: pcm_name '%s'", pt->pcm_name);
pt->dai_name = pcm->dai_name;
tplg_log(tplg, 'D', pos, "pcm: dai_name '%s'", pt->dai_name);
pt->pcm_id = pcm->pcm_id;
pt->dai_id = pcm->dai_id;
tplg_log(tplg, 'D', pos, "pcm: pcm_id %d dai_id %d", pt->pcm_id, pt->dai_id);
pt->playback = pcm->playback;
pt->capture = pcm->capture;
pt->compress = pcm->compress;
tplg_log(tplg, 'D', pos, "pcm: playback %d capture %d compress %d",
pt->playback, pt->capture, pt->compress);
pt->num_streams = pcm->num_streams;
pt->flag_mask = pcm->flag_mask;
pt->flags = pcm->flags;
for (i = 0; i < pcm->num_streams; i++) {
stream = &pt->stream[i];
if (pcm->stream[i].size != sizeof(pcm->stream[0])) {
SNDERR("pcm: unknown stream structure size %d",
pcm->stream[i].size);
return -EINVAL;
}
stream->name = pcm->stream[i].name;
tplg_log(tplg, 'D', pos + offsetof(struct snd_soc_tplg_pcm, stream[i]),
"stream %d: '%s'", i, stream->name);
stream->format = pcm->stream[i].format;
stream->rate = pcm->stream[i].rate;
stream->period_bytes = pcm->stream[i].period_bytes;
stream->buffer_bytes = pcm->stream[i].buffer_bytes;
stream->channels = pcm->stream[i].channels;
}
for (i = 0; i < 2; i++) {
if (i == 0 && !pcm->playback)
continue;
if (i == 1 && !pcm->capture)
continue;
cap = &caps[i];
pt->caps[i] = cap;
if (pcm->caps[i].size != sizeof(pcm->caps[0])) {
SNDERR("pcm: unknown caps structure size %d",
pcm->caps[i].size);
return -EINVAL;
}
cap->name = pcm->caps[i].name;
tplg_log(tplg, 'D', pos + offsetof(struct snd_soc_tplg_pcm, caps[i]),
"caps %d: '%s'", i, cap->name);
cap->formats = pcm->caps[i].formats;
cap->rates = pcm->caps[i].rates;
cap->rate_min = pcm->caps[i].rate_min;
cap->rate_max = pcm->caps[i].rate_max;
cap->channels_min = pcm->caps[i].channels_min;
cap->channels_max = pcm->caps[i].channels_max;
cap->periods_min = pcm->caps[i].periods_min;
cap->periods_max = pcm->caps[i].periods_max;
cap->period_size_min = pcm->caps[i].period_size_min;
cap->period_size_max = pcm->caps[i].period_size_max;
cap->buffer_size_min = pcm->caps[i].buffer_size_min;
cap->buffer_size_max = pcm->caps[i].buffer_size_max;
cap->sig_bits = pcm->caps[i].sig_bits;
}
tplg_log(tplg, 'D', pos + offsetof(struct snd_soc_tplg_pcm, priv),
"pcm: private start");
pt->priv = &pcm->priv;
bin += sizeof(*pcm) + pcm->priv.size;
size -= sizeof(*pcm) + pcm->priv.size;
pos += sizeof(*pcm) + pcm->priv.size;
t.pcm = pt;
err = snd_tplg_add_object(tplg, &t);
if (err < 0)
return err;
if (size > 0)
goto next;
return 0;
}
/* decode dai from the binary input */
int tplg_decode_dai(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
size_t pos ATTRIBUTE_UNUSED,
struct snd_soc_tplg_hdr *hdr ATTRIBUTE_UNUSED,
void *bin ATTRIBUTE_UNUSED,
size_t size ATTRIBUTE_UNUSED)
{
SNDERR("not implemented");
return -ENXIO;
}
/* decode cc from the binary input */
int tplg_decode_cc(snd_tplg_t *tplg ATTRIBUTE_UNUSED,
size_t pos ATTRIBUTE_UNUSED,
struct snd_soc_tplg_hdr *hdr ATTRIBUTE_UNUSED,
void *bin ATTRIBUTE_UNUSED,
size_t size ATTRIBUTE_UNUSED)
{
SNDERR("not implemented");
return -ENXIO;
}
/* decode link from the binary input */
int tplg_decode_link(snd_tplg_t *tplg,
size_t pos,
struct snd_soc_tplg_hdr *hdr,
void *bin, size_t size)
{
struct snd_soc_tplg_link_config *link;
snd_tplg_obj_template_t t;
struct snd_tplg_link_template lt;
struct snd_tplg_stream_template streams[SND_SOC_TPLG_STREAM_CONFIG_MAX];
struct snd_tplg_stream_template *stream;
struct snd_tplg_hw_config_template hws[SND_SOC_TPLG_HW_CONFIG_MAX];
struct snd_tplg_hw_config_template *hw;
unsigned int i, j;
int err;
err = tplg_decode_template(tplg, pos, hdr, &t);
if (err < 0)
return err;
next:
memset(<, 0, sizeof(lt));
memset(streams, 0, sizeof(streams));
memset(hws, 0, sizeof(hws));
link = bin;
if (size < sizeof(*link)) {
SNDERR("link: small size %d", size);
return -EINVAL;
}
if (sizeof(*link) != link->size) {
SNDERR("link: unknown element size %d (expected %zd)",
link->size, sizeof(*link));
return -EINVAL;
}
if (link->num_streams > SND_SOC_TPLG_STREAM_CONFIG_MAX) {
SNDERR("link: wrong number of streams %d", link->num_streams);
return -EINVAL;
}
if (link->num_hw_configs > SND_SOC_TPLG_HW_CONFIG_MAX) {
SNDERR("link: wrong number of streams %d", link->num_streams);
return -EINVAL;
}
if (sizeof(*link) + link->priv.size > size) {
SNDERR("link: wrong private data size %d", link->priv.size);
return -EINVAL;
}
tplg_log(tplg, 'D', pos, "link: size %d private size %d streams %d "
"hw_configs %d",
link->size, link->priv.size, link->num_streams,
link->num_hw_configs);
lt.id = link->id;
lt.name = link->name;
tplg_log(tplg, 'D', pos, "link: name '%s'", lt.name);
lt.stream_name = link->stream_name;
tplg_log(tplg, 'D', pos, "link: stream_name '%s'", lt.stream_name);
lt.num_streams = link->num_streams;
lt.num_hw_configs = link->num_hw_configs;
lt.default_hw_config_id = link->default_hw_config_id;
lt.flag_mask = link->flag_mask;
lt.flags = link->flags;
for (i = 0; i < link->num_streams; i++) {
stream = &streams[i];
if (link->stream[i].size != sizeof(link->stream[0])) {
SNDERR("link: unknown stream structure size %d",
link->stream[i].size);
return -EINVAL;
}
stream->name = link->stream[i].name;
tplg_log(tplg, 'D',
pos + offsetof(struct snd_soc_tplg_link_config, stream[i]),
"stream %d: '%s'", i, stream->name);
stream->format = link->stream[i].format;
stream->rate = link->stream[i].rate;
stream->period_bytes = link->stream[i].period_bytes;
stream->buffer_bytes = link->stream[i].buffer_bytes;
stream->channels = link->stream[i].channels;
}
lt.stream = streams;
for (i = 0; i < link->num_hw_configs; i++) {
hw = &hws[i];
if (link->hw_config[i].size != sizeof(link->hw_config[0])) {
SNDERR("link: unknown hw_config structure size %d",
link->hw_config[i].size);
return -EINVAL;
}
hw->id = link->hw_config[i].id;
hw->fmt = link->hw_config[i].fmt;
hw->clock_gated = link->hw_config[i].clock_gated;
hw->invert_bclk = link->hw_config[i].invert_bclk;
hw->invert_fsync = link->hw_config[i].invert_fsync;
hw->bclk_provider = link->hw_config[i].bclk_provider;
hw->fsync_provider = link->hw_config[i].fsync_provider;
hw->mclk_direction = link->hw_config[i].mclk_direction;
hw->mclk_rate = link->hw_config[i].mclk_rate;
hw->bclk_rate = link->hw_config[i].bclk_rate;
hw->fsync_rate = link->hw_config[i].fsync_rate;
hw->tdm_slots = link->hw_config[i].tdm_slots;
hw->tdm_slot_width = link->hw_config[i].tdm_slot_width;
hw->tx_slots = link->hw_config[i].tx_slots;
hw->rx_slots = link->hw_config[i].rx_slots;
hw->tx_channels = link->hw_config[i].tx_channels;
if (hw->tx_channels > SND_SOC_TPLG_MAX_CHAN) {
SNDERR("link: wrong tx channels %d", hw->tx_channels);
return -EINVAL;
}
for (j = 0; j < hw->tx_channels; j++)
hw->tx_chanmap[j] = link->hw_config[i].tx_chanmap[j];
hw->rx_channels = link->hw_config[i].rx_channels;
if (hw->rx_channels > SND_SOC_TPLG_MAX_CHAN) {
SNDERR("link: wrong rx channels %d", hw->tx_channels);
return -EINVAL;
}
for (j = 0; j < hw->rx_channels; j++)
hw->rx_chanmap[j] = link->hw_config[i].rx_chanmap[j];
}
lt.hw_config = hws;
tplg_log(tplg, 'D', pos + offsetof(struct snd_soc_tplg_pcm, priv),
"link: private start");
lt.priv = &link->priv;
bin += sizeof(*link) + link->priv.size;
size -= sizeof(*link) + link->priv.size;
pos += sizeof(*link) + link->priv.size;
t.link = <
err = snd_tplg_add_object(tplg, &t);
if (err < 0)
return err;
if (size > 0)
goto next;
return 0;
}