| /* | |
| Copyright(c) 2014-2015 Intel Corporation | |
| All rights reserved. | |
| This library is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU Lesser General Public License as | |
| published by the Free Software Foundation; either version 2.1 of | |
| the License, or (at your option) any later version. | |
| This program is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU Lesser General Public License for more details. | |
| Authors: Mengdong Lin <mengdong.lin@intel.com> | |
| Yao Jin <yao.jin@intel.com> | |
| Liam Girdwood <liam.r.girdwood@linux.intel.com> | |
| */ | |
| #include "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[4] = { | |
| SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_RATES, | |
| SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_CHANNELS, | |
| SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_SAMPLEBITS, | |
| SND_SOC_TPLG_LNK_FLGBIT_VOICE_WAKEUP, | |
| }; | |
| static const char *flag_ids[4] = { | |
| "symmetric_rates", | |
| "symmetric_channels", | |
| "symmetric_sample_bits", | |
| "ignore_suspend", | |
| }; | |
| 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; | |
| } | |
| if (strcmp(id, "ignore_suspend") == 0) { | |
| err = parse_flag(n, | |
| SND_SOC_TPLG_LNK_FLGBIT_VOICE_WAKEUP, | |
| &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; | |
| } | |
| if (strcmp(id, "ignore_suspend") == 0) { | |
| err = parse_flag(n, | |
| SND_SOC_TPLG_LNK_FLGBIT_VOICE_WAKEUP, | |
| &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; | |
| } | |
| if (strcmp(id, "ignore_suspend") == 0) { | |
| err = parse_flag(n, | |
| SND_SOC_TPLG_LNK_FLGBIT_VOICE_WAKEUP, | |
| &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; | |
| } | |
| #ifndef DOC_HIDDEN | |
| struct audio_hw_format { | |
| unsigned int type; | |
| const char *name; | |
| }; | |
| #endif /* DOC_HIDDEN */ | |
| 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_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'", 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'", 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; | |
| } |