/* 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 Yao Jin Liam Girdwood */ #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; }