[05/13] topology: Add PCM parser.
diff mbox

Message ID 1435595578-6899-5-git-send-email-liam.r.girdwood@linux.intel.com
State New
Headers show

Commit Message

Liam Girdwood June 29, 2015, 4:32 p.m. UTC
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

Comments

Takashi Iwai June 29, 2015, 6:37 p.m. UTC | #1
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
>

Patch
diff mbox

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