/* 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 ENUM_VAL_SIZE (SNDRV_CTL_ELEM_ID_NAME_MAXLEN >> 2) struct ctl_access_elem { const char *name; unsigned int value; }; /* CTL access strings and codes */ static const struct ctl_access_elem ctl_access[] = { {"read", SNDRV_CTL_ELEM_ACCESS_READ}, {"write", SNDRV_CTL_ELEM_ACCESS_WRITE}, {"read_write", SNDRV_CTL_ELEM_ACCESS_READWRITE}, {"volatile", SNDRV_CTL_ELEM_ACCESS_VOLATILE}, {"timestamp", SNDRV_CTL_ELEM_ACCESS_TIMESTAMP}, {"tlv_read", SNDRV_CTL_ELEM_ACCESS_TLV_READ}, {"tlv_write", SNDRV_CTL_ELEM_ACCESS_TLV_WRITE}, {"tlv_read_write", SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE}, {"tlv_command", SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND}, {"inactive", SNDRV_CTL_ELEM_ACCESS_INACTIVE}, {"lock", SNDRV_CTL_ELEM_ACCESS_LOCK}, {"owner", SNDRV_CTL_ELEM_ACCESS_OWNER}, {"tlv_callback", SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK}, }; /* find CTL access strings and conver to values */ static int parse_access_values(snd_config_t *cfg, struct snd_soc_tplg_ctl_hdr *hdr) { snd_config_iterator_t i, next; snd_config_t *n; const char *value = NULL; unsigned int j; tplg_dbg(" Access:\n"); snd_config_for_each(i, next, cfg) { n = snd_config_iterator_entry(i); /* get value */ if (snd_config_get_string(n, &value) < 0) continue; /* match access value and set flags */ for (j = 0; j < ARRAY_SIZE(ctl_access); j++) { if (strcmp(value, ctl_access[j].name) == 0) { hdr->access |= ctl_access[j].value; tplg_dbg("\t%s\n", value); break; } } } return 0; } /* Parse Access */ int parse_access(snd_config_t *cfg, struct snd_soc_tplg_ctl_hdr *hdr) { snd_config_iterator_t i, next; snd_config_t *n; const char *id; int err = 0; snd_config_for_each(i, next, cfg) { n = snd_config_iterator_entry(i); if (snd_config_get_id(n, &id) < 0) continue; if (strcmp(id, "access") == 0) { err = parse_access_values(n, hdr); if (err < 0) { SNDERR("error: failed to parse access"); return err; } continue; } } return err; } /* copy referenced TLV to the mixer control */ static int copy_tlv(struct tplg_elem *elem, struct tplg_elem *ref) { struct snd_soc_tplg_mixer_control *mixer_ctrl = elem->mixer_ctrl; struct snd_soc_tplg_ctl_tlv *tlv = ref->tlv; tplg_dbg("TLV '%s' used by '%s\n", ref->id, elem->id); /* TLV has a fixed size */ mixer_ctrl->hdr.tlv = *tlv; return 0; } /* check referenced TLV for a mixer control */ static int tplg_build_mixer_control(snd_tplg_t *tplg, struct tplg_elem *elem) { struct tplg_ref *ref; struct list_head *base, *pos; int err = 0; base = &elem->ref_list; /* for each ref in this control elem */ list_for_each(pos, base) { ref = list_entry(pos, struct tplg_ref, list); if (ref->elem) continue; if (ref->type == SND_TPLG_TYPE_TLV) { ref->elem = tplg_elem_lookup(&tplg->tlv_list, ref->id, SND_TPLG_TYPE_TLV, elem->index); if (ref->elem) err = copy_tlv(elem, ref->elem); } else if (ref->type == SND_TPLG_TYPE_DATA) { err = tplg_copy_data(tplg, elem, ref); if (err < 0) return err; } if (!ref->elem) { SNDERR("error: cannot find '%s' referenced by" " control '%s'\n", ref->id, elem->id); return -EINVAL; } else if (err < 0) return err; } return 0; } static void copy_enum_texts(struct tplg_elem *enum_elem, struct tplg_elem *ref_elem) { struct snd_soc_tplg_enum_control *ec = enum_elem->enum_ctrl; struct tplg_texts *texts = ref_elem->texts; memcpy(ec->texts, texts->items, SND_SOC_TPLG_NUM_TEXTS * SNDRV_CTL_ELEM_ID_NAME_MAXLEN); ec->items += texts->num_items; } /* check referenced text for a enum control */ static int tplg_build_enum_control(snd_tplg_t *tplg, struct tplg_elem *elem) { struct tplg_ref *ref; struct list_head *base, *pos; int err; base = &elem->ref_list; list_for_each(pos, base) { ref = list_entry(pos, struct tplg_ref, list); if (ref->elem) continue; if (ref->type == SND_TPLG_TYPE_TEXT) { ref->elem = tplg_elem_lookup(&tplg->text_list, ref->id, SND_TPLG_TYPE_TEXT, elem->index); if (ref->elem) copy_enum_texts(elem, ref->elem); } else if (ref->type == SND_TPLG_TYPE_DATA) { err = tplg_copy_data(tplg, elem, ref); if (err < 0) return err; } if (!ref->elem) { SNDERR("error: cannot find '%s' referenced by" " control '%s'\n", ref->id, elem->id); return -EINVAL; } } return 0; } /* check referenced private data for a byte control */ static int tplg_build_bytes_control(snd_tplg_t *tplg, struct tplg_elem *elem) { struct tplg_ref *ref; struct list_head *base, *pos; int err; base = &elem->ref_list; list_for_each(pos, base) { ref = list_entry(pos, struct tplg_ref, list); if (ref->elem) continue; if (ref->type == SND_TPLG_TYPE_DATA) { err = tplg_copy_data(tplg, elem, ref); if (err < 0) return err; } } return 0; } int tplg_build_controls(snd_tplg_t *tplg) { struct list_head *base, *pos; struct tplg_elem *elem; int err = 0; base = &tplg->mixer_list; list_for_each(pos, base) { elem = list_entry(pos, struct tplg_elem, list); err = tplg_build_mixer_control(tplg, elem); if (err < 0) return err; /* add control to manifest */ tplg->manifest.control_elems++; } base = &tplg->enum_list; list_for_each(pos, base) { elem = list_entry(pos, struct tplg_elem, list); err = tplg_build_enum_control(tplg, elem); if (err < 0) return err; /* add control to manifest */ tplg->manifest.control_elems++; } base = &tplg->bytes_ext_list; list_for_each(pos, base) { elem = list_entry(pos, struct tplg_elem, list); err = tplg_build_bytes_control(tplg, elem); if (err < 0) return err; /* add control to manifest */ tplg->manifest.control_elems++; } return 0; } /* * Parse TLV of DBScale type. * * Parse DBScale describing min, step, mute in DB. */ static int tplg_parse_tlv_dbscale(snd_config_t *cfg, struct tplg_elem *elem) { snd_config_iterator_t i, next; snd_config_t *n; struct snd_soc_tplg_ctl_tlv *tplg_tlv; struct snd_soc_tplg_tlv_dbscale *scale; const char *id = NULL, *value = NULL; tplg_dbg(" scale: %s\n", elem->id); tplg_tlv = calloc(1, sizeof(*tplg_tlv)); if (!tplg_tlv) return -ENOMEM; elem->tlv = tplg_tlv; tplg_tlv->size = sizeof(struct snd_soc_tplg_ctl_tlv); tplg_tlv->type = SNDRV_CTL_TLVT_DB_SCALE; scale = &tplg_tlv->scale; snd_config_for_each(i, next, cfg) { n = snd_config_iterator_entry(i); /* get ID */ if (snd_config_get_id(n, &id) < 0) { SNDERR("error: cant get ID\n"); return -EINVAL; } /* get value */ if (snd_config_get_string(n, &value) < 0) continue; tplg_dbg("\t%s = %s\n", id, value); /* get TLV data */ if (strcmp(id, "min") == 0) scale->min = atoi(value); else if (strcmp(id, "step") == 0) scale->step = atoi(value); else if (strcmp(id, "mute") == 0) scale->mute = atoi(value); else SNDERR("error: unknown key %s\n", id); } return 0; } /* Parse TLV */ int tplg_parse_tlv(snd_tplg_t *tplg, snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) { snd_config_iterator_t i, next; snd_config_t *n; const char *id; int err = 0; struct tplg_elem *elem; elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_TLV); if (!elem) return -ENOMEM; snd_config_for_each(i, next, cfg) { n = snd_config_iterator_entry(i); if (snd_config_get_id(n, &id) < 0) continue; if (strcmp(id, "scale") == 0) { err = tplg_parse_tlv_dbscale(n, elem); if (err < 0) { SNDERR("error: failed to DBScale"); return err; } continue; } } return err; } /* Parse Control Bytes */ int tplg_parse_control_bytes(snd_tplg_t *tplg, snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) { struct snd_soc_tplg_bytes_control *be; struct tplg_elem *elem; snd_config_iterator_t i, next; snd_config_t *n; const char *id, *val = NULL; int err; bool access_set = false, tlv_set = false; elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_BYTES); if (!elem) return -ENOMEM; be = elem->bytes_ext; be->size = elem->size; snd_strlcpy(be->hdr.name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); be->hdr.type = SND_SOC_TPLG_TYPE_BYTES; tplg_dbg(" Control Bytes: %s\n", elem->id); snd_config_for_each(i, next, cfg) { n = snd_config_iterator_entry(i); if (snd_config_get_id(n, &id) < 0) continue; /* skip comments */ if (strcmp(id, "comment") == 0) continue; if (id[0] == '#') continue; if (strcmp(id, "base") == 0) { if (snd_config_get_string(n, &val) < 0) return -EINVAL; be->base = atoi(val); tplg_dbg("\t%s: %d\n", id, be->base); continue; } if (strcmp(id, "num_regs") == 0) { if (snd_config_get_string(n, &val) < 0) return -EINVAL; be->num_regs = atoi(val); tplg_dbg("\t%s: %d\n", id, be->num_regs); continue; } if (strcmp(id, "max") == 0) { if (snd_config_get_string(n, &val) < 0) return -EINVAL; be->max = atoi(val); tplg_dbg("\t%s: %d\n", id, be->max); continue; } if (strcmp(id, "mask") == 0) { if (snd_config_get_string(n, &val) < 0) return -EINVAL; be->mask = strtol(val, NULL, 16); tplg_dbg("\t%s: %d\n", id, be->mask); continue; } if (strcmp(id, "data") == 0) { err = tplg_parse_data_refs(n, elem); if (err < 0) return err; continue; } if (strcmp(id, "tlv") == 0) { if (snd_config_get_string(n, &val) < 0) return -EINVAL; err = tplg_ref_add(elem, SND_TPLG_TYPE_TLV, val); if (err < 0) return err; tlv_set = true; tplg_dbg("\t%s: %s\n", id, val); continue; } if (strcmp(id, "ops") == 0) { err = tplg_parse_compound(tplg, n, tplg_parse_ops, &be->hdr); if (err < 0) return err; continue; } if (strcmp(id, "extops") == 0) { err = tplg_parse_compound(tplg, n, tplg_parse_ext_ops, be); if (err < 0) return err; continue; } if (strcmp(id, "access") == 0) { err = parse_access(cfg, &be->hdr); if (err < 0) return err; access_set = true; continue; } } /* set CTL access to default values if none are provided */ if (!access_set) { be->hdr.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; if (tlv_set) be->hdr.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; } return 0; } /* Parse Control Enums. */ int tplg_parse_control_enum(snd_tplg_t *tplg, snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) { struct snd_soc_tplg_enum_control *ec; struct tplg_elem *elem; snd_config_iterator_t i, next; snd_config_t *n; const char *id, *val = NULL; int err, j; bool access_set = false; elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_ENUM); if (!elem) return -ENOMEM; ec = elem->enum_ctrl; snd_strlcpy(ec->hdr.name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); ec->hdr.type = SND_SOC_TPLG_TYPE_ENUM; ec->size = elem->size; tplg->channel_idx = 0; /* set channel reg to default state */ for (j = 0; j < SND_SOC_TPLG_MAX_CHAN; j++) ec->channel[j].reg = -1; tplg_dbg(" Control Enum: %s\n", elem->id); snd_config_for_each(i, next, cfg) { n = snd_config_iterator_entry(i); if (snd_config_get_id(n, &id) < 0) continue; /* skip comments */ if (strcmp(id, "comment") == 0) continue; if (id[0] == '#') continue; if (strcmp(id, "texts") == 0) { if (snd_config_get_string(n, &val) < 0) return -EINVAL; tplg_ref_add(elem, SND_TPLG_TYPE_TEXT, val); tplg_dbg("\t%s: %s\n", id, val); continue; } if (strcmp(id, "channel") == 0) { if (ec->num_channels >= SND_SOC_TPLG_MAX_CHAN) { SNDERR("error: too many channels %s\n", elem->id); return -EINVAL; } err = tplg_parse_compound(tplg, n, tplg_parse_channel, ec->channel); if (err < 0) return err; ec->num_channels = tplg->channel_idx; continue; } if (strcmp(id, "ops") == 0) { err = tplg_parse_compound(tplg, n, tplg_parse_ops, &ec->hdr); if (err < 0) return err; continue; } if (strcmp(id, "data") == 0) { err = tplg_parse_data_refs(n, elem); if (err < 0) return err; continue; } if (strcmp(id, "access") == 0) { err = parse_access(cfg, &ec->hdr); if (err < 0) return err; access_set = true; continue; } } /* set CTL access to default values if none are provided */ if (!access_set) { ec->hdr.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; } return 0; } /* Parse Controls. * * Mixer control. Supports multiple channels. */ int tplg_parse_control_mixer(snd_tplg_t *tplg, snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) { struct snd_soc_tplg_mixer_control *mc; struct tplg_elem *elem; snd_config_iterator_t i, next; snd_config_t *n; const char *id, *val = NULL; int err, j; bool access_set = false, tlv_set = false; elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_MIXER); if (!elem) return -ENOMEM; /* init new mixer */ mc = elem->mixer_ctrl; snd_strlcpy(mc->hdr.name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); mc->hdr.type = SND_SOC_TPLG_TYPE_MIXER; mc->size = elem->size; tplg->channel_idx = 0; /* set channel reg to default state */ for (j = 0; j < SND_SOC_TPLG_MAX_CHAN; j++) mc->channel[j].reg = -1; tplg_dbg(" Control Mixer: %s\n", elem->id); /* giterate trough each mixer elment */ 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, "channel") == 0) { if (mc->num_channels >= SND_SOC_TPLG_MAX_CHAN) { SNDERR("error: too many channels %s\n", elem->id); return -EINVAL; } err = tplg_parse_compound(tplg, n, tplg_parse_channel, mc->channel); if (err < 0) return err; mc->num_channels = tplg->channel_idx; continue; } if (strcmp(id, "max") == 0) { if (snd_config_get_string(n, &val) < 0) return -EINVAL; mc->max = atoi(val); tplg_dbg("\t%s: %d\n", id, mc->max); continue; } if (strcmp(id, "invert") == 0) { if (snd_config_get_string(n, &val) < 0) return -EINVAL; if (strcmp(val, "true") == 0) mc->invert = 1; else if (strcmp(val, "false") == 0) mc->invert = 0; tplg_dbg("\t%s: %d\n", id, mc->invert); continue; } if (strcmp(id, "ops") == 0) { err = tplg_parse_compound(tplg, n, tplg_parse_ops, &mc->hdr); if (err < 0) return err; continue; } if (strcmp(id, "tlv") == 0) { if (snd_config_get_string(n, &val) < 0) return -EINVAL; err = tplg_ref_add(elem, SND_TPLG_TYPE_TLV, val); if (err < 0) return err; tlv_set = true; tplg_dbg("\t%s: %s\n", id, val); continue; } if (strcmp(id, "data") == 0) { err = tplg_parse_data_refs(n, elem); if (err < 0) return err; continue; } if (strcmp(id, "access") == 0) { err = parse_access(cfg, &mc->hdr); if (err < 0) return err; access_set = true; continue; } } /* set CTL access to default values if none are provided */ if (!access_set) { mc->hdr.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; if (tlv_set) mc->hdr.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; } return 0; } static int init_ctl_hdr(struct snd_soc_tplg_ctl_hdr *hdr, struct snd_tplg_ctl_template *t) { hdr->size = sizeof(struct snd_soc_tplg_ctl_hdr); hdr->type = t->type; snd_strlcpy(hdr->name, t->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); /* clean up access flag */ if (t->access == 0) t->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; t->access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_INACTIVE | SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND | SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK); hdr->access = t->access; hdr->ops.get = t->ops.get; hdr->ops.put = t->ops.put; hdr->ops.info = t->ops.info; /* TLV */ if (hdr->access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE && !(hdr->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK)) { struct snd_tplg_tlv_template *tlvt = t->tlv; struct snd_soc_tplg_ctl_tlv *tlv = &hdr->tlv; struct snd_tplg_tlv_dbscale_template *scalet; struct snd_soc_tplg_tlv_dbscale *scale; if (!tlvt) { SNDERR("error: missing TLV data\n"); return -EINVAL; } tlv->size = sizeof(struct snd_soc_tplg_ctl_tlv); tlv->type = tlvt->type; switch (tlvt->type) { case SNDRV_CTL_TLVT_DB_SCALE: scalet = container_of(tlvt, struct snd_tplg_tlv_dbscale_template, hdr); scale = &tlv->scale; scale->min = scalet->min; scale->step = scalet->step; scale->mute = scalet->mute; break; /* TODO: add support for other TLV types */ default: SNDERR("error: unsupported TLV type %d\n", tlv->type); break; } } return 0; } int tplg_add_mixer(snd_tplg_t *tplg, struct snd_tplg_mixer_template *mixer, struct tplg_elem **e) { struct snd_soc_tplg_private *priv = mixer->priv; struct snd_soc_tplg_mixer_control *mc; struct tplg_elem *elem; int ret, i, num_channels; tplg_dbg(" Control Mixer: %s\n", mixer->hdr.name); if (mixer->hdr.type != SND_SOC_TPLG_TYPE_MIXER) { SNDERR("error: invalid mixer type %d\n", mixer->hdr.type); return -EINVAL; } elem = tplg_elem_new_common(tplg, NULL, mixer->hdr.name, SND_TPLG_TYPE_MIXER); if (!elem) return -ENOMEM; /* init new mixer */ mc = elem->mixer_ctrl; mc->size = elem->size; ret = init_ctl_hdr(&mc->hdr, &mixer->hdr); if (ret < 0) { tplg_elem_free(elem); return ret; } mc->min = mixer->min; mc->max = mixer->max; mc->platform_max = mixer->platform_max; mc->invert = mixer->invert; /* set channel reg to default state */ for (i = 0; i < SND_SOC_TPLG_MAX_CHAN; i++) mc->channel[i].reg = -1; num_channels = mixer->map ? mixer->map->num_channels : 0; mc->num_channels = num_channels; for (i = 0; i < num_channels; i++) { struct snd_tplg_channel_elem *channel = &mixer->map->channel[i]; mc->channel[i].size = channel->size; mc->channel[i].reg = channel->reg; mc->channel[i].shift = channel->shift; mc->channel[i].id = channel->id; } /* priv data */ if (priv) { mc = realloc(mc, elem->size + priv->size); if (!mc) { tplg_elem_free(elem); return -ENOMEM; } elem->mixer_ctrl = mc; elem->size += priv->size; mc->priv.size = priv->size; memcpy(mc->priv.data, priv->data, priv->size); } if (e) *e = elem; return 0; } int tplg_add_enum(snd_tplg_t *tplg, struct snd_tplg_enum_template *enum_ctl, struct tplg_elem **e) { struct snd_soc_tplg_enum_control *ec; struct tplg_elem *elem; int ret, i, num_items; tplg_dbg(" Control Enum: %s\n", enum_ctl->hdr.name); if (enum_ctl->hdr.type != SND_SOC_TPLG_TYPE_ENUM) { SNDERR("error: invalid enum type %d\n", enum_ctl->hdr.type); return -EINVAL; } elem = tplg_elem_new_common(tplg, NULL, enum_ctl->hdr.name, SND_TPLG_TYPE_ENUM); if (!elem) return -ENOMEM; ec = elem->enum_ctrl; ec->size = elem->size; ret = init_ctl_hdr(&ec->hdr, &enum_ctl->hdr); if (ret < 0) { tplg_elem_free(elem); return ret; } num_items = enum_ctl->items < SND_SOC_TPLG_NUM_TEXTS ? enum_ctl->items : SND_SOC_TPLG_NUM_TEXTS; ec->items = num_items; ec->mask = enum_ctl->mask; ec->count = enum_ctl->items; if (enum_ctl->texts != NULL) { for (i = 0; i < num_items; i++) { if (enum_ctl->texts[i] != NULL) snd_strlcpy(ec->texts[i], enum_ctl->texts[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN); } } if (enum_ctl->values != NULL) { for (i = 0; i < num_items; i++) { if (enum_ctl->values[i] == NULL) continue; memcpy(&ec->values[i * sizeof(int) * ENUM_VAL_SIZE], enum_ctl->values[i], sizeof(int) * ENUM_VAL_SIZE); } } if (enum_ctl->priv != NULL) { ec = realloc(ec, elem->size + enum_ctl->priv->size); if (!ec) { tplg_elem_free(elem); return -ENOMEM; } elem->enum_ctrl = ec; elem->size += enum_ctl->priv->size; memcpy(ec->priv.data, enum_ctl->priv->data, enum_ctl->priv->size); ec->priv.size = enum_ctl->priv->size; } if (e) *e = elem; return 0; } int tplg_add_bytes(snd_tplg_t *tplg, struct snd_tplg_bytes_template *bytes_ctl, struct tplg_elem **e) { struct snd_soc_tplg_bytes_control *be; struct tplg_elem *elem; int ret; tplg_dbg(" Control Bytes: %s\n", bytes_ctl->hdr.name); if (bytes_ctl->hdr.type != SND_SOC_TPLG_TYPE_BYTES) { SNDERR("error: invalid bytes type %d\n", bytes_ctl->hdr.type); return -EINVAL; } elem = tplg_elem_new_common(tplg, NULL, bytes_ctl->hdr.name, SND_TPLG_TYPE_BYTES); if (!elem) return -ENOMEM; be = elem->bytes_ext; be->size = elem->size; ret = init_ctl_hdr(&be->hdr, &bytes_ctl->hdr); if (ret < 0) { tplg_elem_free(elem); return ret; } be->max = bytes_ctl->max; be->mask = bytes_ctl->mask; be->base = bytes_ctl->base; be->num_regs = bytes_ctl->num_regs; be->ext_ops.put = bytes_ctl->ext_ops.put; be->ext_ops.get = bytes_ctl->ext_ops.get; if (bytes_ctl->priv != NULL) { be = realloc(be, elem->size + bytes_ctl->priv->size); if (!be) { tplg_elem_free(elem); return -ENOMEM; } elem->bytes_ext = be; elem->size += bytes_ctl->priv->size; memcpy(be->priv.data, bytes_ctl->priv->data, bytes_ctl->priv->size); be->priv.size = bytes_ctl->priv->size; } /* check on TLV bytes control */ if (be->hdr.access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { if ((be->hdr.access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) != SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) { SNDERR("error: Invalid TLV bytes control access 0x%x\n", be->hdr.access); tplg_elem_free(elem); return -EINVAL; } if (!be->max) { tplg_elem_free(elem); return -EINVAL; } } if (e) *e = elem; return 0; } int tplg_add_mixer_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) { return tplg_add_mixer(tplg, t->mixer, NULL); } int tplg_add_enum_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) { return tplg_add_enum(tplg, t->enum_ctl, NULL); } int tplg_add_bytes_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) { return tplg_add_bytes(tplg, t->bytes_ctl, NULL); }