From patchwork Wed Jul 1 13:44:31 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Liam Girdwood X-Patchwork-Id: 6703771 Return-Path: X-Original-To: patchwork-alsa-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 951149F2F0 for ; Wed, 1 Jul 2015 13:49:51 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 2903D203A1 for ; Wed, 1 Jul 2015 13:49:50 +0000 (UTC) Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.kernel.org (Postfix) with ESMTP id 12B3D201C8 for ; Wed, 1 Jul 2015 13:49:48 +0000 (UTC) Received: by alsa0.perex.cz (Postfix, from userid 1000) id 54AE22658C2; Wed, 1 Jul 2015 15:49:47 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 Received: from alsa0.perex.cz (localhost [IPv6:::1]) by alsa0.perex.cz (Postfix) with ESMTP id 3144B2606A2; Wed, 1 Jul 2015 15:46:20 +0200 (CEST) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 277FD2606D6; Wed, 1 Jul 2015 15:46:19 +0200 (CEST) Received: from mga03.intel.com (mga03.intel.com [134.134.136.65]) by alsa0.perex.cz (Postfix) with ESMTP id 81CED260670 for ; Wed, 1 Jul 2015 15:45:55 +0200 (CEST) Received: from orsmga002.jf.intel.com ([10.7.209.21]) by orsmga103.jf.intel.com with ESMTP; 01 Jul 2015 06:45:55 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.15,386,1432623600"; d="scan'208";a="756534085" Received: from twhit3x-mobl1.ger.corp.intel.com (HELO loki.ger.corp.intel.com) ([10.252.31.19]) by orsmga002.jf.intel.com with ESMTP; 01 Jul 2015 06:45:53 -0700 From: Liam Girdwood To: Date: Wed, 1 Jul 2015 14:44:31 +0100 Message-Id: <1435758275-4047-9-git-send-email-liam.r.girdwood@linux.intel.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1435758275-4047-1-git-send-email-liam.r.girdwood@linux.intel.com> References: <1435758275-4047-1-git-send-email-liam.r.girdwood@linux.intel.com> Cc: Takashi Iwai , Vinod Koul , Mark Brown , Liam Girdwood Subject: [alsa-devel] [PATCH v2 09/13] topology: Add CTL parser X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP Add support to parse mixers, enums and byte controls. Signed-off-by: Liam Girdwood --- src/topology/ctl.c | 674 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 src/topology/ctl.c diff --git a/src/topology/ctl.c b/src/topology/ctl.c new file mode 100644 index 0000000..5a797d1 --- /dev/null +++ b/src/topology/ctl.c @@ -0,0 +1,674 @@ +/* + Copyright(c) 2014-2015 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + Authors: Mengdong Lin + Yao Jin + Liam Girdwood +*/ + +#include "list.h" +#include "tplg_local.h" + +/* 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 */ + memcpy(&mixer_ctrl->tlv, tlv, sizeof(*tlv)); + + /* set size of TLV data */ + mixer_ctrl->hdr.tlv_size = tlv->count * sizeof(uint32_t); + 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, *npos; + int err = 0; + + base = &elem->ref_list; + + /* for each ref in this control elem */ + list_for_each_safe(pos, npos, base) { + + ref = list_entry(pos, struct tplg_ref, list); + if (ref->id == NULL || ref->elem) + continue; + + if (ref->type == PARSER_TYPE_TLV) { + ref->elem = tplg_elem_lookup(&tplg->tlv_list, + ref->id, PARSER_TYPE_TLV); + if (ref->elem) + err = copy_tlv(elem, ref->elem); + + } else if (ref->type == PARSER_TYPE_DATA) { + ref->elem = tplg_elem_lookup(&tplg->pdata_list, + ref->id, PARSER_TYPE_DATA); + err = tplg_copy_data(elem, ref->elem); + } + + if (!ref->elem) { + fprintf(stderr, "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; + + memcpy(ec->texts, ref_elem->texts, + SND_SOC_TPLG_NUM_TEXTS * SNDRV_CTL_ELEM_ID_NAME_MAXLEN); +} + +/* 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, *npos; + int err = 0; + + base = &elem->ref_list; + + list_for_each_safe(pos, npos, base) { + + ref = list_entry(pos, struct tplg_ref, list); + if (ref->id == NULL || ref->elem) + continue; + + if (ref->type == PARSER_TYPE_TEXT) { + ref->elem = tplg_elem_lookup(&tplg->text_list, + ref->id, PARSER_TYPE_TEXT); + if (ref->elem) + copy_enum_texts(elem, ref->elem); + + } else if (ref->type == PARSER_TYPE_DATA) { + ref->elem = tplg_elem_lookup(&tplg->pdata_list, + ref->id, PARSER_TYPE_DATA); + err = tplg_copy_data(elem, ref->elem); + } + if (!ref->elem) { + fprintf(stderr, "error: cannot find '%s' referenced by" + " control '%s'\n", ref->id, elem->id); + return -EINVAL; + } else if (err < 0) + return err; + } + + 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, *npos; + + base = &elem->ref_list; + + list_for_each_safe(pos, npos, base) { + + ref = list_entry(pos, struct tplg_ref, list); + if (ref->id == NULL || ref->elem) + continue; + + /* bytes control only reference one private data section */ + ref->elem = tplg_elem_lookup(&tplg->pdata_list, + ref->id, PARSER_TYPE_DATA); + if (!ref->elem) { + fprintf(stderr, "error: cannot find data '%s'" + " referenced by control '%s'\n", + ref->id, elem->id); + return -EINVAL; + } + + /* copy texts to enum elem */ + return tplg_copy_data(elem, ref->elem); + } + + return 0; +} + +int tplg_build_controls(snd_tplg_t *tplg) +{ + struct list_head *base, *pos, *npos; + struct tplg_elem *elem; + int err = 0; + + base = &tplg->mixer_list; + list_for_each_safe(pos, npos, base) { + + elem = list_entry(pos, struct tplg_elem, list); + err = tplg_build_mixer_control(tplg, elem); + if (err < 0) + return err; + } + + base = &tplg->enum_list; + list_for_each_safe(pos, npos, base) { + + elem = list_entry(pos, struct tplg_elem, list); + err = tplg_build_enum_control(tplg, elem); + if (err < 0) + return err; + } + + base = &tplg->bytes_ext_list; + list_for_each_safe(pos, npos, base) { + + elem = list_entry(pos, struct tplg_elem, list); + err = tplg_build_bytes_control(tplg, elem); + if (err < 0) + return err; + } + + return 0; +} + + +/* + * Parse TLV of DBScale type. + * + * Parse DBScale describing min, step, mute in DB. + * + * scale { + * min "-9000" + * step "300" + * mute "1" + * } + */ +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; + const char *id = NULL, *value = NULL; + int *data; + + tplg_dbg(" scale: %s\n", elem->id); + + tplg_tlv = calloc(1, sizeof(*tplg_tlv)); + if (!tplg_tlv) + return -ENOMEM; + data = (int*)(tplg_tlv->data); + + elem->tlv = tplg_tlv; + tplg_tlv->numid = SNDRV_CTL_TLVT_DB_SCALE; + tplg_tlv->count = 8; + tplg_tlv->size = sizeof(*tplg_tlv); + + snd_config_for_each(i, next, cfg) { + + n = snd_config_iterator_entry(i); + + /* get ID */ + if (snd_config_get_id(n, &id) < 0) { + fprintf(stderr, "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) + data[0] = atoi(value); + else if (strcmp(id, "step") == 0) + data[1] = atoi(value); + else if (strcmp(id, "mute") == 0) + data[2] = atoi(value); + else + fprintf(stderr, "error: unknown key %s\n", id); + } + + return 0; +} + +/* Parse TLV. + * + * Each TLV is described in new section + * Supported TLV types: scale. + * + * SectionTLV."tlv name" { + * type { + * + * } + * } + */ +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, PARSER_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) { + fprintf(stderr, "error: failed to DBScale"); + return err; + } + continue; + } + } + + return err; +} + +/* Parse Control Bytes + * + * Each Control is described in new section + * Supported control types: Byte + * + * SectionControlBytes."control name" { + * comment "optional comments" + * + * index "1" + * base "0" + * num_regs "16" + * mask "0xff" + * max "255" + * } + */ +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; + + elem = tplg_elem_new_common(tplg, cfg, PARSER_TYPE_BYTES); + if (!elem) + return -ENOMEM; + + be = elem->bytes_ext; + be->size = elem->size; + strncpy(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, "index") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + elem->index = atoi(val); + tplg_dbg("\t%s: %d\n", id, elem->index); + continue; + } + + if (strcmp(id, "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->num_regs); + 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) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + tplg_ref_add(elem, PARSER_TYPE_DATA, val); + tplg_dbg("\t%s: %s\n", id, val); + continue; + } + + if (strcmp(id, "tlv") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + err = tplg_ref_add(elem, PARSER_TYPE_TLV, val); + if (err < 0) + return err; + + be->hdr.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE; + tplg_dbg("\t%s: %s\n", id, val); + continue; + } + } + + return 0; +} + +/* Parse Control Enums. + * + * Enumerated control. Supports mutiple channels. + * + * SectionControlMixer."control name" { + * comment "optional comments" + * + * index "1" + * texts "EQU1" + * + * channel."name" { + * } + * + * ops."ctl" { + * } + * + * tlv "hsw_vol_tlv" + * } + */ +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; + + elem = tplg_elem_new_common(tplg, cfg, PARSER_TYPE_ENUM); + if (!elem) + return -ENOMEM; + + /* init new mixer */ + ec = elem->enum_ctrl; + strncpy(ec->hdr.name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + ec->hdr.name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0; + 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, "index") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + elem->index = atoi(val); + tplg_dbg("\t%s: %d\n", id, elem->index); + continue; + } + + if (strcmp(id, "texts") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + tplg_ref_add(elem, PARSER_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) { + fprintf(stderr, "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) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + tplg_ref_add(elem, PARSER_TYPE_DATA, val); + tplg_dbg("\t%s: %s\n", id, val); + continue; + } + } + + return 0; +} + +/* Parse Controls. + * + * Mixer control. Supports multiple channels. + * + * SectionControlMixer."control name" { + * comment "optional comments" + * + * index "1" + * + * channel."name" { + * } + * + * ops."ctl" { + * } + * + * max "32" + * invert "0" + * + * ops."ctl" { + * } + * + * tlv "hsw_vol_tlv" + * } + */ +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; + + elem = tplg_elem_new_common(tplg, cfg, PARSER_TYPE_MIXER); + if (!elem) + return -ENOMEM; + + /* init new mixer */ + mc = elem->mixer_ctrl; + strncpy(mc->hdr.name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + mc->hdr.name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0; + 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, "index") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + elem->index = atoi(val); + tplg_dbg("\t%s: %d\n", id, elem->index); + continue; + } + + if (strcmp(id, "channel") == 0) { + if (mc->num_channels >= SND_SOC_TPLG_MAX_CHAN) { + fprintf(stderr, "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, PARSER_TYPE_TLV, val); + if (err < 0) + return err; + + mc->hdr.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE; + tplg_dbg("\t%s: %s\n", id, val); + continue; + } + + if (strcmp(id, "data") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + tplg_ref_add(elem, PARSER_TYPE_DATA, val); + tplg_dbg("\t%s: %s\n", id, val); + continue; + } + } + + return 0; +}