Message ID | 1435595578-6899-5-git-send-email-liam.r.girdwood@linux.intel.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
At Mon, 29 Jun 2015 17:32:50 +0100, Liam Girdwood wrote: > > Parse PCM configurations and capabilities. These can then be used to define > the capabilities and config for FE DAI links, PCM devices and > codec <-> codec style links. > > Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com> > --- > src/topology/pcm.c | 782 +++++++++++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 782 insertions(+) > create mode 100644 src/topology/pcm.c > > diff --git a/src/topology/pcm.c b/src/topology/pcm.c > new file mode 100644 > index 0000000..476af55 > --- /dev/null > +++ b/src/topology/pcm.c > @@ -0,0 +1,782 @@ > +/* > + Copyright(c) 2014-2015 Intel Corporation > + All rights reserved. > + > + This program is free software; you can redistribute it and/or modify > + it under the terms of version 2 of the GNU General Public License as > + published by the Free Software Foundation. > + > + 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 > + 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" > + > +/* mapping of format text names to types */ > +static const struct map_elem pcm_format_map[] = { > + {"S16_LE", SNDRV_PCM_FORMAT_S16_LE}, > + {"S16_BE", SNDRV_PCM_FORMAT_S16_BE}, > + {"U16_LE", SNDRV_PCM_FORMAT_U16_LE}, > + {"U16_BE", SNDRV_PCM_FORMAT_U16_BE}, > + {"S24_LE", SNDRV_PCM_FORMAT_S24_LE}, > + {"S24_BE", SNDRV_PCM_FORMAT_S24_BE}, > + {"U24_LE", SNDRV_PCM_FORMAT_U24_LE}, > + {"U24_BE", SNDRV_PCM_FORMAT_U24_BE}, > + {"S32_LE", SNDRV_PCM_FORMAT_S32_LE}, > + {"S32_BE", SNDRV_PCM_FORMAT_S32_BE}, > + {"U32_LE", SNDRV_PCM_FORMAT_U32_LE}, > + {"U32_BE", SNDRV_PCM_FORMAT_U32_BE}, > +}; > + > +static int lookup_pcm_format(const char *c, snd_pcm_format_t *format) > +{ > + unsigned int i; > + > + for (i = 0; i < ARRAY_SIZE(pcm_format_map); i++) { > + if (strcmp(pcm_format_map[i].name, c) == 0) { > + *format = pcm_format_map[i].id; > + return 0; > + } > + } > + > + return -EINVAL; > +} You can use snd_pcm_format_value(), I suppose. Takashi > + > +struct tplg_elem *lookup_pcm_dai_stream(struct list_head *base, const char* id) > +{ > + struct list_head *pos, *npos; > + struct tplg_elem *elem; > + struct snd_soc_tplg_pcm_dai *pcm_dai; > + > + list_for_each_safe(pos, npos, base) { > + > + elem = list_entry(pos, struct tplg_elem, list); > + if (elem->type != PARSER_TYPE_PCM) > + return NULL; > + > + pcm_dai = elem->pcm; > + > + if (pcm_dai && (!strcmp(pcm_dai->capconf[0].caps.name, id) > + || !strcmp(pcm_dai->capconf[1].caps.name, id))) > + return elem; > + } > + > + return NULL; > +} > + > +/* copy referenced caps to the pcm */ > +static void copy_pcm_caps(const char *id, 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' \n", > + sizeof(*caps), ref_elem->id, id); > + > + memcpy((void*)caps, ref_caps, sizeof(*caps)); > +} > + > +/* copy referenced config to the pcm */ > +static void copy_pcm_config(const char *id, > + struct snd_soc_tplg_stream_config *cfg, struct tplg_elem *ref_elem) > +{ > + struct snd_soc_tplg_stream_config *ref_cfg = ref_elem->stream_cfg; > + > + tplg_dbg("Copy pcm config (%ld bytes) from '%s' to '%s' \n", > + sizeof(*cfg), ref_elem->id, id); > + > + memcpy((void*)cfg, ref_cfg, sizeof(*cfg)); > +} > + > +/* check referenced config and caps for a pcm */ > +static int tplg_build_pcm_cfg_caps(snd_tplg_t *tplg, struct tplg_elem *elem) > +{ > + struct tplg_elem *ref_elem = NULL; > + struct snd_soc_tplg_pcm_cfg_caps *capconf; > + struct snd_soc_tplg_pcm_dai *pcm_dai; > + unsigned int i, j; > + > + switch (elem->type) { > + case PARSER_TYPE_PCM: > + pcm_dai = elem->pcm; > + break; > + case PARSER_TYPE_BE: > + pcm_dai = elem->be; > + break; > + case PARSER_TYPE_CC: > + pcm_dai = elem->cc; > + break; > + default: > + return -EINVAL; > + } > + > + for (i = 0; i < 2; i++) { > + capconf = &pcm_dai->capconf[i]; > + > + ref_elem = tplg_elem_lookup(&tplg->pcm_caps_list, > + capconf->caps.name, PARSER_TYPE_STREAM_CAPS); > + > + if (ref_elem != NULL) > + copy_pcm_caps(elem->id, &capconf->caps, ref_elem); > + > + for (j = 0; j < capconf->num_configs; j++) { > + ref_elem = tplg_elem_lookup(&tplg->pcm_config_list, > + capconf->configs[j].name, > + PARSER_TYPE_STREAM_CONFIG); > + > + if (ref_elem != NULL) > + copy_pcm_config(elem->id, > + &capconf->configs[j], > + ref_elem); > + } > + } > + > + return 0; > +} > + > +int tplg_build_pcm_dai(snd_tplg_t *tplg, unsigned int type) > +{ > + struct list_head *base, *pos, *npos; > + struct tplg_elem *elem; > + int err = 0; > + > + switch (type) { > + case PARSER_TYPE_PCM: > + base = &tplg->pcm_list; > + break; > + case PARSER_TYPE_BE: > + base = &tplg->be_list; > + break; > + case PARSER_TYPE_CC: > + base = &tplg->cc_list; > + break; > + default: > + return -EINVAL; > + } > + > + list_for_each_safe(pos, npos, base) { > + > + elem = list_entry(pos, struct tplg_elem, list); > + if (elem->type != type) { > + fprintf(stderr, "error: invalid elem '%s'\n", elem->id); > + return -EINVAL; > + } > + > + err = tplg_build_pcm_cfg_caps(tplg, elem); > + if (err < 0) > + return err; > + } > + > + return 0; > +} > + > +/* PCM stream configuration > + * > + * Describes the PCM configuration for playback and capture streams. > + * > + * config."name" { > + * format "S24_LE" > + * rate "48000" > + * channels "2" > + * tdm_slot "0xf" > + * } > + */ > +static int tplg_parse_stream_cfg(snd_tplg_t *tplg ATTRIBUTE_UNUSED, > + snd_config_t *cfg, void *private) > +{ > + snd_config_iterator_t i, next; > + snd_config_t *n; > + struct snd_soc_tplg_stream_config *sc = private; > + struct snd_soc_tplg_stream *stream; > + const char *id, *val; > + snd_pcm_format_t format; > + int ret; > + > + snd_config_get_id(cfg, &id); > + > + if (strcmp(id, "playback") == 0) > + stream = &sc->playback; > + else if (strcmp(id, "capture") == 0) > + stream = &sc->capture; > + else > + return -EINVAL; > + > + tplg_dbg("\t%s:\n", id); > + > + stream->size = sizeof(*stream); > + > + snd_config_for_each(i, next, cfg) { > + > + n = snd_config_iterator_entry(i); > + > + if (snd_config_get_id(n, &id) < 0) > + return -EINVAL; > + > + if (snd_config_get_string(n, &val) < 0) > + return -EINVAL; > + > + if (strcmp(id, "format") == 0) { > + ret = lookup_pcm_format(val, &format); > + if (ret < 0) { > + fprintf(stderr, "error: unsupported stream format %s\n", > + val); > + return -EINVAL; > + } > + > + stream->format = format; > + tplg_dbg("\t\t%s: %s\n", id, val); > + continue; > + } > + > + if (strcmp(id, "rate") == 0) { > + stream->rate = atoi(val); > + tplg_dbg("\t\t%s: %d\n", id, stream->rate); > + continue; > + } > + > + if (strcmp(id, "channels") == 0) { > + stream->channels = atoi(val); > + tplg_dbg("\t\t%s: %d\n", id, stream->channels); > + continue; > + } > + > + if (strcmp(id, "tdm_slot") == 0) { > + stream->tdm_slot = strtol(val, NULL, 16); > + tplg_dbg("\t\t%s: 0x%x\n", id, stream->tdm_slot); > + continue; > + } > + } > + > + return 0; > +} > + > +/* Parse pcm configuration > + * > + * SectionPCMConfig."PCM config name" { > + * > + * config."playback" { > + * > + * } > + * > + * config."capture" { > + * > + * } > + * } > + */ > +int tplg_parse_pcm_config(snd_tplg_t *tplg, > + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) > +{ > + struct snd_soc_tplg_stream_config *sc; > + 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, PARSER_TYPE_STREAM_CONFIG); > + if (!elem) > + return -ENOMEM; > + > + sc = elem->stream_cfg; > + sc->size = elem->size; > + > + tplg_dbg(" PCM Config: %s\n", 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, "config") == 0) { > + err = tplg_parse_compound(tplg, n, > + tplg_parse_stream_cfg, sc); > + if (err < 0) > + return err; > + continue; > + } > + } > + > + 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, ret; > + > + s = strtok(str, ","); > + while ((s != NULL) && (i < SND_SOC_TPLG_MAX_FORMATS)) { > + ret = lookup_pcm_format(s, &format); > + if (ret < 0) { > + fprintf(stderr, "error: unsupported stream format %s\n", s); > + return -EINVAL; > + } > + > + caps->formats[i] = format; > + s = strtok(NULL, ", "); > + i++; > + } > + > + return 0; > +} > + > +/* Parse pcm Capabilities > + * > + * SectionPCMCapabilities." PCM capabilities name" { > + * > + * formats "S24_LE,S16_LE" > + * rate_min "48000" > + * rate_max "48000" > + * channels_min "2" > + * channels_max "2" > + * } > + */ > +int tplg_parse_pcm_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, PARSER_TYPE_STREAM_CAPS); > + if (!elem) > + return -ENOMEM; > + > + sc = elem->stream_caps; > + sc->size = elem->size; > + strncpy(sc->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); > + > + tplg_dbg(" PCM Capabilities: %s\n", 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 (snd_config_get_string(n, &val) < 0) > + return -EINVAL; > + > + if (strcmp(id, "formats") == 0) { > + 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\n", id, val); > + continue; > + } > + > + if (strcmp(id, "rate_min") == 0) { > + sc->rate_min = atoi(val); > + tplg_dbg("\t\t%s: %d\n", id, sc->rate_min); > + continue; > + } > + > + if (strcmp(id, "rate_max") == 0) { > + sc->rate_max = atoi(val); > + tplg_dbg("\t\t%s: %d\n", id, sc->rate_max); > + continue; > + } > + > + if (strcmp(id, "channels_min") == 0) { > + sc->channels_min = atoi(val); > + tplg_dbg("\t\t%s: %d\n", id, sc->channels_min); > + continue; > + } > + > + if (strcmp(id, "channels_max") == 0) { > + sc->channels_max = atoi(val); > + tplg_dbg("\t\t%s: %d\n", id, sc->channels_max); > + continue; > + } > + } > + > + return 0; > +} > + > +static int tplg_parse_pcm_cfg(snd_tplg_t *tplg ATTRIBUTE_UNUSED, > + snd_config_t *cfg, void *private) > +{ > + struct snd_soc_tplg_pcm_cfg_caps *capconf = private; > + struct snd_soc_tplg_stream_config *configs = capconf->configs; > + unsigned int *num_configs = &capconf->num_configs; > + const char *value; > + > + if (*num_configs == SND_SOC_TPLG_STREAM_CONFIG_MAX) > + return -EINVAL; > + > + if (snd_config_get_string(cfg, &value) < 0) > + return EINVAL; > + > + strncpy(configs[*num_configs].name, value, > + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); > + > + *num_configs += 1; > + > + tplg_dbg("\t\t\t%s\n", value); > + > + return 0; > +} > + > +/* Parse the cap and config of a pcm. > + * > + * pcm."name" { > + * > + * capabilities "System playback" > + * > + * configs [ > + * "PCM 48k Stereo 24bit" > + * "PCM 48k Stereo 16bit" > + * ] > + * } > + */ > +int tplg_parse_pcm_cap_cfg(snd_tplg_t *tplg, 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_dai *pcm_dai; > + const char *id, *value; > + int err, stream; > + > + if (elem->type == PARSER_TYPE_PCM) > + pcm_dai = elem->pcm; > + else if (elem->type == PARSER_TYPE_BE) > + pcm_dai = elem->be; > + else if (elem->type == PARSER_TYPE_CC) > + pcm_dai = elem->cc; > + else > + return -EINVAL; > + > + snd_config_get_id(cfg, &id); > + > + tplg_dbg("\t%s:\n", id); > + > + if (strcmp(id, "playback") == 0) > + stream = SND_SOC_TPLG_STREAM_PLAYBACK; > + else if (strcmp(id, "capture") == 0) > + stream = SND_SOC_TPLG_STREAM_CAPTURE; > + 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; > + > + strncpy(pcm_dai->capconf[stream].caps.name, value, > + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); > + > + tplg_dbg("\t\t%s\n\t\t\t%s\n", id, value); > + continue; > + } > + > + if (strcmp(id, "configs") == 0) { > + tplg_dbg("\t\tconfigs:\n"); > + err = tplg_parse_compound(tplg, n, tplg_parse_pcm_cfg, > + &pcm_dai->capconf[stream]); > + if (err < 0) > + return err; > + continue; > + } > + } > + > + return 0; > +} > + > +/* Parse pcm > + * > + * SectionPCM."System Pin" { > + * > + * index "1" > + * > + * # used for binding to the PCM > + * ID "0" > + * > + * pcm."playback" { > + * capabilities "System Playback" > + * config "PCM 48k Stereo 24bit" > + * config "PCM 48k Stereo 16bit" > + * } > + * > + * pcm."capture" { > + * capabilities "Analog Capture" > + * config "PCM 48k Stereo 24bit" > + * config "PCM 48k Stereo 16bit" > + * config "PCM 48k 2P/4C 16bit" > + * } > + * } > + */ > +int tplg_parse_pcm(snd_tplg_t *tplg, > + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) > +{ > + struct snd_soc_tplg_pcm_dai *pcm_dai; > + 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, PARSER_TYPE_PCM); > + if (!elem) > + return -ENOMEM; > + > + pcm_dai = elem->pcm; > + pcm_dai->size = elem->size; > + strncpy(pcm_dai->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); > + > + tplg_dbg(" PCM: %s\n", 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, "index") == 0) { > + if (snd_config_get_string(n, &val) < 0) > + return -EINVAL; > + > + elem->index = atoi(val); > + tplg_dbg("\t%s: %d\n", id, elem->index); > + continue; > + } > + > + if (strcmp(id, "ID") == 0) { > + if (snd_config_get_string(n, &val) < 0) > + return -EINVAL; > + > + pcm_dai->id = atoi(val); > + tplg_dbg("\t%s: %d\n", id, pcm_dai->id); > + continue; > + } > + > + if (strcmp(id, "pcm") == 0) { > + err = tplg_parse_compound(tplg, n, > + tplg_parse_pcm_cap_cfg, elem); > + if (err < 0) > + return err; > + continue; > + } > + } > + > + return 0; > +} > + > +/* Parse be > + * > + * SectionBE."SSP0-Codec" { > + * > + * index "1" > + * > + * # used for binding to the PCM > + * ID "0" > + * > + * be."playback" { > + * capabilities "System Playback" > + * config "PCM 48k Stereo 24bit" > + * config "PCM 48k Stereo 16bit" > + * } > + * > + * be."capture" { > + * capabilities "Analog Capture" > + * config "PCM 48k Stereo 24bit" > + * config "PCM 48k Stereo 16bit" > + * config "PCM 48k 2P/4C 16bit" > + * } > + * } > + */ > +int tplg_parse_be(snd_tplg_t *tplg, > + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) > +{ > + struct snd_soc_tplg_pcm_dai *pcm_dai; > + 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, PARSER_TYPE_BE); > + if (!elem) > + return -ENOMEM; > + > + pcm_dai = elem->be; > + pcm_dai->size = elem->size; > + strncpy(pcm_dai->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); > + > + tplg_dbg(" BE: %s\n", 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, "index") == 0) { > + if (snd_config_get_string(n, &val) < 0) > + return -EINVAL; > + > + elem->index = atoi(val); > + tplg_dbg("\t%s: %d\n", id, elem->index); > + continue; > + } > + > + if (strcmp(id, "ID") == 0) { > + if (snd_config_get_string(n, &val) < 0) > + return -EINVAL; > + > + pcm_dai->id = atoi(val); > + tplg_dbg("\t%s: %d\n", id, pcm_dai->id); > + continue; > + } > + > + if (strcmp(id, "be") == 0) { > + err = tplg_parse_compound(tplg, n, > + tplg_parse_pcm_cap_cfg, elem); > + if (err < 0) > + return err; > + continue; > + } > + } > + > + return 0; > +} > + > +/* Parse cc > + * > + * SectionCC."FM-Codec" { > + * > + * index "1" > + * > + * # used for binding to the CC link > + * ID "0" > + * > + * # CC DAI link capabilities and supported configs > + * cc."playback" { > + * > + * capabilities "System playback" > + * > + * configs [ > + * "PCM 48k Stereo 16bit" > + * ] > + * } > + * > + * cc."capture" { > + * > + * capabilities "Analog capture" > + * > + * configs [ > + * "PCM 48k Stereo 16bit" > + * ] > + * } > + * } > + */ > +int tplg_parse_cc(snd_tplg_t *tplg, > + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) > +{ > + struct snd_soc_tplg_pcm_dai *pcm_dai; > + 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, PARSER_TYPE_CC); > + if (!elem) > + return -ENOMEM; > + > + pcm_dai = elem->cc; > + pcm_dai->size = elem->size; > + > + tplg_dbg(" CC: %s\n", 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, "index") == 0) { > + if (snd_config_get_string(n, &val) < 0) > + return -EINVAL; > + > + elem->index = atoi(val); > + tplg_dbg("\t%s: %d\n", id, elem->index); > + continue; > + } > + > + if (strcmp(id, "ID") == 0) { > + if (snd_config_get_string(n, &val) < 0) > + return -EINVAL; > + > + pcm_dai->id = atoi(val); > + tplg_dbg("\t%s: %d\n", id, pcm_dai->id); > + continue; > + } > + > + if (strcmp(id, "cc") == 0) { > + err = tplg_parse_compound(tplg, n, > + tplg_parse_pcm_cap_cfg, elem); > + if (err < 0) > + return err; > + continue; > + } > + } > + > + return 0; > +} > -- > 2.1.4 >
diff --git a/src/topology/pcm.c b/src/topology/pcm.c new file mode 100644 index 0000000..476af55 --- /dev/null +++ b/src/topology/pcm.c @@ -0,0 +1,782 @@ +/* + Copyright(c) 2014-2015 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + 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 + 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" + +/* mapping of format text names to types */ +static const struct map_elem pcm_format_map[] = { + {"S16_LE", SNDRV_PCM_FORMAT_S16_LE}, + {"S16_BE", SNDRV_PCM_FORMAT_S16_BE}, + {"U16_LE", SNDRV_PCM_FORMAT_U16_LE}, + {"U16_BE", SNDRV_PCM_FORMAT_U16_BE}, + {"S24_LE", SNDRV_PCM_FORMAT_S24_LE}, + {"S24_BE", SNDRV_PCM_FORMAT_S24_BE}, + {"U24_LE", SNDRV_PCM_FORMAT_U24_LE}, + {"U24_BE", SNDRV_PCM_FORMAT_U24_BE}, + {"S32_LE", SNDRV_PCM_FORMAT_S32_LE}, + {"S32_BE", SNDRV_PCM_FORMAT_S32_BE}, + {"U32_LE", SNDRV_PCM_FORMAT_U32_LE}, + {"U32_BE", SNDRV_PCM_FORMAT_U32_BE}, +}; + +static int lookup_pcm_format(const char *c, snd_pcm_format_t *format) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pcm_format_map); i++) { + if (strcmp(pcm_format_map[i].name, c) == 0) { + *format = pcm_format_map[i].id; + return 0; + } + } + + return -EINVAL; +} + +struct tplg_elem *lookup_pcm_dai_stream(struct list_head *base, const char* id) +{ + struct list_head *pos, *npos; + struct tplg_elem *elem; + struct snd_soc_tplg_pcm_dai *pcm_dai; + + list_for_each_safe(pos, npos, base) { + + elem = list_entry(pos, struct tplg_elem, list); + if (elem->type != PARSER_TYPE_PCM) + return NULL; + + pcm_dai = elem->pcm; + + if (pcm_dai && (!strcmp(pcm_dai->capconf[0].caps.name, id) + || !strcmp(pcm_dai->capconf[1].caps.name, id))) + return elem; + } + + return NULL; +} + +/* copy referenced caps to the pcm */ +static void copy_pcm_caps(const char *id, 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' \n", + sizeof(*caps), ref_elem->id, id); + + memcpy((void*)caps, ref_caps, sizeof(*caps)); +} + +/* copy referenced config to the pcm */ +static void copy_pcm_config(const char *id, + struct snd_soc_tplg_stream_config *cfg, struct tplg_elem *ref_elem) +{ + struct snd_soc_tplg_stream_config *ref_cfg = ref_elem->stream_cfg; + + tplg_dbg("Copy pcm config (%ld bytes) from '%s' to '%s' \n", + sizeof(*cfg), ref_elem->id, id); + + memcpy((void*)cfg, ref_cfg, sizeof(*cfg)); +} + +/* check referenced config and caps for a pcm */ +static int tplg_build_pcm_cfg_caps(snd_tplg_t *tplg, struct tplg_elem *elem) +{ + struct tplg_elem *ref_elem = NULL; + struct snd_soc_tplg_pcm_cfg_caps *capconf; + struct snd_soc_tplg_pcm_dai *pcm_dai; + unsigned int i, j; + + switch (elem->type) { + case PARSER_TYPE_PCM: + pcm_dai = elem->pcm; + break; + case PARSER_TYPE_BE: + pcm_dai = elem->be; + break; + case PARSER_TYPE_CC: + pcm_dai = elem->cc; + break; + default: + return -EINVAL; + } + + for (i = 0; i < 2; i++) { + capconf = &pcm_dai->capconf[i]; + + ref_elem = tplg_elem_lookup(&tplg->pcm_caps_list, + capconf->caps.name, PARSER_TYPE_STREAM_CAPS); + + if (ref_elem != NULL) + copy_pcm_caps(elem->id, &capconf->caps, ref_elem); + + for (j = 0; j < capconf->num_configs; j++) { + ref_elem = tplg_elem_lookup(&tplg->pcm_config_list, + capconf->configs[j].name, + PARSER_TYPE_STREAM_CONFIG); + + if (ref_elem != NULL) + copy_pcm_config(elem->id, + &capconf->configs[j], + ref_elem); + } + } + + return 0; +} + +int tplg_build_pcm_dai(snd_tplg_t *tplg, unsigned int type) +{ + struct list_head *base, *pos, *npos; + struct tplg_elem *elem; + int err = 0; + + switch (type) { + case PARSER_TYPE_PCM: + base = &tplg->pcm_list; + break; + case PARSER_TYPE_BE: + base = &tplg->be_list; + break; + case PARSER_TYPE_CC: + base = &tplg->cc_list; + break; + default: + return -EINVAL; + } + + list_for_each_safe(pos, npos, base) { + + elem = list_entry(pos, struct tplg_elem, list); + if (elem->type != type) { + fprintf(stderr, "error: invalid elem '%s'\n", elem->id); + return -EINVAL; + } + + err = tplg_build_pcm_cfg_caps(tplg, elem); + if (err < 0) + return err; + } + + return 0; +} + +/* PCM stream configuration + * + * Describes the PCM configuration for playback and capture streams. + * + * config."name" { + * format "S24_LE" + * rate "48000" + * channels "2" + * tdm_slot "0xf" + * } + */ +static int tplg_parse_stream_cfg(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + snd_config_t *cfg, void *private) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + struct snd_soc_tplg_stream_config *sc = private; + struct snd_soc_tplg_stream *stream; + const char *id, *val; + snd_pcm_format_t format; + int ret; + + snd_config_get_id(cfg, &id); + + if (strcmp(id, "playback") == 0) + stream = &sc->playback; + else if (strcmp(id, "capture") == 0) + stream = &sc->capture; + else + return -EINVAL; + + tplg_dbg("\t%s:\n", id); + + stream->size = sizeof(*stream); + + snd_config_for_each(i, next, cfg) { + + n = snd_config_iterator_entry(i); + + if (snd_config_get_id(n, &id) < 0) + return -EINVAL; + + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + if (strcmp(id, "format") == 0) { + ret = lookup_pcm_format(val, &format); + if (ret < 0) { + fprintf(stderr, "error: unsupported stream format %s\n", + val); + return -EINVAL; + } + + stream->format = format; + tplg_dbg("\t\t%s: %s\n", id, val); + continue; + } + + if (strcmp(id, "rate") == 0) { + stream->rate = atoi(val); + tplg_dbg("\t\t%s: %d\n", id, stream->rate); + continue; + } + + if (strcmp(id, "channels") == 0) { + stream->channels = atoi(val); + tplg_dbg("\t\t%s: %d\n", id, stream->channels); + continue; + } + + if (strcmp(id, "tdm_slot") == 0) { + stream->tdm_slot = strtol(val, NULL, 16); + tplg_dbg("\t\t%s: 0x%x\n", id, stream->tdm_slot); + continue; + } + } + + return 0; +} + +/* Parse pcm configuration + * + * SectionPCMConfig."PCM config name" { + * + * config."playback" { + * + * } + * + * config."capture" { + * + * } + * } + */ +int tplg_parse_pcm_config(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) +{ + struct snd_soc_tplg_stream_config *sc; + 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, PARSER_TYPE_STREAM_CONFIG); + if (!elem) + return -ENOMEM; + + sc = elem->stream_cfg; + sc->size = elem->size; + + tplg_dbg(" PCM Config: %s\n", 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, "config") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_stream_cfg, sc); + if (err < 0) + return err; + continue; + } + } + + 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, ret; + + s = strtok(str, ","); + while ((s != NULL) && (i < SND_SOC_TPLG_MAX_FORMATS)) { + ret = lookup_pcm_format(s, &format); + if (ret < 0) { + fprintf(stderr, "error: unsupported stream format %s\n", s); + return -EINVAL; + } + + caps->formats[i] = format; + s = strtok(NULL, ", "); + i++; + } + + return 0; +} + +/* Parse pcm Capabilities + * + * SectionPCMCapabilities." PCM capabilities name" { + * + * formats "S24_LE,S16_LE" + * rate_min "48000" + * rate_max "48000" + * channels_min "2" + * channels_max "2" + * } + */ +int tplg_parse_pcm_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, PARSER_TYPE_STREAM_CAPS); + if (!elem) + return -ENOMEM; + + sc = elem->stream_caps; + sc->size = elem->size; + strncpy(sc->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + + tplg_dbg(" PCM Capabilities: %s\n", 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 (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + if (strcmp(id, "formats") == 0) { + 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\n", id, val); + continue; + } + + if (strcmp(id, "rate_min") == 0) { + sc->rate_min = atoi(val); + tplg_dbg("\t\t%s: %d\n", id, sc->rate_min); + continue; + } + + if (strcmp(id, "rate_max") == 0) { + sc->rate_max = atoi(val); + tplg_dbg("\t\t%s: %d\n", id, sc->rate_max); + continue; + } + + if (strcmp(id, "channels_min") == 0) { + sc->channels_min = atoi(val); + tplg_dbg("\t\t%s: %d\n", id, sc->channels_min); + continue; + } + + if (strcmp(id, "channels_max") == 0) { + sc->channels_max = atoi(val); + tplg_dbg("\t\t%s: %d\n", id, sc->channels_max); + continue; + } + } + + return 0; +} + +static int tplg_parse_pcm_cfg(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + snd_config_t *cfg, void *private) +{ + struct snd_soc_tplg_pcm_cfg_caps *capconf = private; + struct snd_soc_tplg_stream_config *configs = capconf->configs; + unsigned int *num_configs = &capconf->num_configs; + const char *value; + + if (*num_configs == SND_SOC_TPLG_STREAM_CONFIG_MAX) + return -EINVAL; + + if (snd_config_get_string(cfg, &value) < 0) + return EINVAL; + + strncpy(configs[*num_configs].name, value, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + + *num_configs += 1; + + tplg_dbg("\t\t\t%s\n", value); + + return 0; +} + +/* Parse the cap and config of a pcm. + * + * pcm."name" { + * + * capabilities "System playback" + * + * configs [ + * "PCM 48k Stereo 24bit" + * "PCM 48k Stereo 16bit" + * ] + * } + */ +int tplg_parse_pcm_cap_cfg(snd_tplg_t *tplg, 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_dai *pcm_dai; + const char *id, *value; + int err, stream; + + if (elem->type == PARSER_TYPE_PCM) + pcm_dai = elem->pcm; + else if (elem->type == PARSER_TYPE_BE) + pcm_dai = elem->be; + else if (elem->type == PARSER_TYPE_CC) + pcm_dai = elem->cc; + else + return -EINVAL; + + snd_config_get_id(cfg, &id); + + tplg_dbg("\t%s:\n", id); + + if (strcmp(id, "playback") == 0) + stream = SND_SOC_TPLG_STREAM_PLAYBACK; + else if (strcmp(id, "capture") == 0) + stream = SND_SOC_TPLG_STREAM_CAPTURE; + 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; + + strncpy(pcm_dai->capconf[stream].caps.name, value, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + + tplg_dbg("\t\t%s\n\t\t\t%s\n", id, value); + continue; + } + + if (strcmp(id, "configs") == 0) { + tplg_dbg("\t\tconfigs:\n"); + err = tplg_parse_compound(tplg, n, tplg_parse_pcm_cfg, + &pcm_dai->capconf[stream]); + if (err < 0) + return err; + continue; + } + } + + return 0; +} + +/* Parse pcm + * + * SectionPCM."System Pin" { + * + * index "1" + * + * # used for binding to the PCM + * ID "0" + * + * pcm."playback" { + * capabilities "System Playback" + * config "PCM 48k Stereo 24bit" + * config "PCM 48k Stereo 16bit" + * } + * + * pcm."capture" { + * capabilities "Analog Capture" + * config "PCM 48k Stereo 24bit" + * config "PCM 48k Stereo 16bit" + * config "PCM 48k 2P/4C 16bit" + * } + * } + */ +int tplg_parse_pcm(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) +{ + struct snd_soc_tplg_pcm_dai *pcm_dai; + 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, PARSER_TYPE_PCM); + if (!elem) + return -ENOMEM; + + pcm_dai = elem->pcm; + pcm_dai->size = elem->size; + strncpy(pcm_dai->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + + tplg_dbg(" PCM: %s\n", 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, "index") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + elem->index = atoi(val); + tplg_dbg("\t%s: %d\n", id, elem->index); + continue; + } + + if (strcmp(id, "ID") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + pcm_dai->id = atoi(val); + tplg_dbg("\t%s: %d\n", id, pcm_dai->id); + continue; + } + + if (strcmp(id, "pcm") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_pcm_cap_cfg, elem); + if (err < 0) + return err; + continue; + } + } + + return 0; +} + +/* Parse be + * + * SectionBE."SSP0-Codec" { + * + * index "1" + * + * # used for binding to the PCM + * ID "0" + * + * be."playback" { + * capabilities "System Playback" + * config "PCM 48k Stereo 24bit" + * config "PCM 48k Stereo 16bit" + * } + * + * be."capture" { + * capabilities "Analog Capture" + * config "PCM 48k Stereo 24bit" + * config "PCM 48k Stereo 16bit" + * config "PCM 48k 2P/4C 16bit" + * } + * } + */ +int tplg_parse_be(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) +{ + struct snd_soc_tplg_pcm_dai *pcm_dai; + 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, PARSER_TYPE_BE); + if (!elem) + return -ENOMEM; + + pcm_dai = elem->be; + pcm_dai->size = elem->size; + strncpy(pcm_dai->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + + tplg_dbg(" BE: %s\n", 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, "index") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + elem->index = atoi(val); + tplg_dbg("\t%s: %d\n", id, elem->index); + continue; + } + + if (strcmp(id, "ID") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + pcm_dai->id = atoi(val); + tplg_dbg("\t%s: %d\n", id, pcm_dai->id); + continue; + } + + if (strcmp(id, "be") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_pcm_cap_cfg, elem); + if (err < 0) + return err; + continue; + } + } + + return 0; +} + +/* Parse cc + * + * SectionCC."FM-Codec" { + * + * index "1" + * + * # used for binding to the CC link + * ID "0" + * + * # CC DAI link capabilities and supported configs + * cc."playback" { + * + * capabilities "System playback" + * + * configs [ + * "PCM 48k Stereo 16bit" + * ] + * } + * + * cc."capture" { + * + * capabilities "Analog capture" + * + * configs [ + * "PCM 48k Stereo 16bit" + * ] + * } + * } + */ +int tplg_parse_cc(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) +{ + struct snd_soc_tplg_pcm_dai *pcm_dai; + 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, PARSER_TYPE_CC); + if (!elem) + return -ENOMEM; + + pcm_dai = elem->cc; + pcm_dai->size = elem->size; + + tplg_dbg(" CC: %s\n", 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, "index") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + elem->index = atoi(val); + tplg_dbg("\t%s: %d\n", id, elem->index); + continue; + } + + if (strcmp(id, "ID") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + pcm_dai->id = atoi(val); + tplg_dbg("\t%s: %d\n", id, pcm_dai->id); + continue; + } + + if (strcmp(id, "cc") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_pcm_cap_cfg, elem); + if (err < 0) + return err; + continue; + } + } + + return 0; +}
Parse PCM configurations and capabilities. These can then be used to define the capabilities and config for FE DAI links, PCM devices and codec <-> codec style links. Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com> --- src/topology/pcm.c | 782 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 782 insertions(+) create mode 100644 src/topology/pcm.c