[alsa-devel] [PATCH v2 01/13] topology: uapi: Add UAPI headers for topology ABI
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- include/sound/asoc.h | 387 +++++++++++++++++++++++++++++++++++++++++++++++++++ include/sound/tlv.h | 23 +++ 2 files changed, 410 insertions(+) create mode 100644 include/sound/asoc.h create mode 100644 include/sound/tlv.h
diff --git a/include/sound/asoc.h b/include/sound/asoc.h new file mode 100644 index 0000000..498cd0c --- /dev/null +++ b/include/sound/asoc.h @@ -0,0 +1,387 @@ +/* + * uapi/sound/asoc.h -- ALSA SoC Firmware Controls and DAPM + * + * Copyright (C) 2012 Texas Instruments Inc. + * Copyright (C) 2015 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Simple file API to load FW that includes mixers, coefficients, DAPM graphs, + * algorithms, equalisers, DAIs, widgets etc. +*/ + +#ifndef __LINUX_UAPI_SND_ASOC_H +#define __LINUX_UAPI_SND_ASOC_H + +/* + * Maximum number of channels topology kcontrol can represent. + */ +#define SND_SOC_TPLG_MAX_CHAN 8 + +/* + * Maximum number of PCM formats capability + */ +#define SND_SOC_TPLG_MAX_FORMATS 16 + +/* + * Maximum number of PCM stream configs + */ +#define SND_SOC_TPLG_STREAM_CONFIG_MAX 8 + +/* individual kcontrol info types - can be mixed with other types */ +#define SND_SOC_TPLG_CTL_VOLSW 1 +#define SND_SOC_TPLG_CTL_VOLSW_SX 2 +#define SND_SOC_TPLG_CTL_VOLSW_XR_SX 3 +#define SND_SOC_TPLG_CTL_ENUM 4 +#define SND_SOC_TPLG_CTL_BYTES 5 +#define SND_SOC_TPLG_CTL_ENUM_VALUE 6 +#define SND_SOC_TPLG_CTL_RANGE 7 +#define SND_SOC_TPLG_CTL_STROBE 8 + + +/* individual widget kcontrol info types - can be mixed with other types */ +#define SND_SOC_TPLG_DAPM_CTL_VOLSW 64 +#define SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE 65 +#define SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT 66 +#define SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE 67 +#define SND_SOC_TPLG_DAPM_CTL_PIN 68 + +/* DAPM widget types - add new items to the end */ +#define SND_SOC_TPLG_DAPM_INPUT 0 +#define SND_SOC_TPLG_DAPM_OUTPUT 1 +#define SND_SOC_TPLG_DAPM_MUX 2 +#define SND_SOC_TPLG_DAPM_MIXER 3 +#define SND_SOC_TPLG_DAPM_PGA 4 +#define SND_SOC_TPLG_DAPM_OUT_DRV 5 +#define SND_SOC_TPLG_DAPM_ADC 6 +#define SND_SOC_TPLG_DAPM_DAC 7 +#define SND_SOC_TPLG_DAPM_SWITCH 8 +#define SND_SOC_TPLG_DAPM_PRE 9 +#define SND_SOC_TPLG_DAPM_POST 10 +#define SND_SOC_TPLG_DAPM_AIF_IN 11 +#define SND_SOC_TPLG_DAPM_AIF_OUT 12 +#define SND_SOC_TPLG_DAPM_DAI_IN 13 +#define SND_SOC_TPLG_DAPM_DAI_OUT 14 +#define SND_SOC_TPLG_DAPM_DAI_LINK 15 +#define SND_SOC_TPLG_DAPM_LAST SND_SOC_TPLG_DAPM_DAI_LINK + +/* Header magic number and string sizes */ +#define SND_SOC_TPLG_MAGIC 0x41536F43 /* ASoC */ + +/* string sizes */ +#define SND_SOC_TPLG_NUM_TEXTS 16 + +/* ABI version */ +#define SND_SOC_TPLG_ABI_VERSION 0x2 + +/* Max size of TLV data */ +#define SND_SOC_TPLG_TLV_SIZE 32 + +/* + * File and Block header data types. + * Add new generic and vendor types to end of list. + * Generic types are handled by the core whilst vendors types are passed + * to the component drivers for handling. + */ +#define SND_SOC_TPLG_TYPE_MIXER 1 +#define SND_SOC_TPLG_TYPE_BYTES 2 +#define SND_SOC_TPLG_TYPE_ENUM 3 +#define SND_SOC_TPLG_TYPE_DAPM_GRAPH 4 +#define SND_SOC_TPLG_TYPE_DAPM_WIDGET 5 +#define SND_SOC_TPLG_TYPE_DAI_LINK 6 +#define SND_SOC_TPLG_TYPE_PCM 7 +#define SND_SOC_TPLG_TYPE_MANIFEST 8 +#define SND_SOC_TPLG_TYPE_CODEC_LINK 9 +#define SND_SOC_TPLG_TYPE_MAX SND_SOC_TPLG_TYPE_CODEC_LINK + +/* vendor block IDs - please add new vendor types to end */ +#define SND_SOC_TPLG_TYPE_VENDOR_FW 1000 +#define SND_SOC_TPLG_TYPE_VENDOR_CONFIG 1001 +#define SND_SOC_TPLG_TYPE_VENDOR_COEFF 1002 +#define SND_SOC_TPLG_TYPEVENDOR_CODEC 1003 + +#define SND_SOC_TPLG_STREAM_PLAYBACK 0 +#define SND_SOC_TPLG_STREAM_CAPTURE 1 + +/* + * Block Header. + * This header precedes all object and object arrays below. + */ +struct snd_soc_tplg_hdr { + __le32 magic; /* magic number */ + __le32 abi; /* ABI version */ + __le32 version; /* optional vendor specific version details */ + __le32 type; /* SND_SOC_TPLG_TYPE_ */ + __le32 size; /* size of this structure */ + __le32 vendor_type; /* optional vendor specific type info */ + __le32 payload_size; /* data bytes, excluding this header */ + __le32 index; /* identifier for block */ + __le32 count; /* number of elements in block */ +} __attribute__((packed)); + +/* + * Private data. + * All topology objects may have private data that can be used by the driver or + * firmware. Core will ignore this data. + */ +struct snd_soc_tplg_private { + __le32 size; /* in bytes of private data */ + char data[0]; +} __attribute__((packed)); + +/* + * Kcontrol TLV data. + */ +struct snd_soc_tplg_ctl_tlv { + __le32 size; /* in bytes aligned to 4 */ + __le32 numid; /* control element numeric identification */ + __le32 count; /* number of elem in data array */ + __le32 data[SND_SOC_TPLG_TLV_SIZE]; +} __attribute__((packed)); + +/* + * Kcontrol channel data + */ +struct snd_soc_tplg_channel { + __le32 size; /* in bytes of this structure */ + __le32 reg; + __le32 shift; + __le32 id; /* ID maps to Left, Right, LFE etc */ +} __attribute__((packed)); + +/* + * Kcontrol Operations IDs + */ +struct snd_soc_tplg_kcontrol_ops_id { + __le32 get; + __le32 put; + __le32 info; +} __attribute__((packed)); + +/* + * kcontrol header + */ +struct snd_soc_tplg_ctl_hdr { + __le32 size; /* in bytes of this structure */ + __le32 type; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + __le32 access; + struct snd_soc_tplg_kcontrol_ops_id ops; + __le32 tlv_size; /* non zero means control has TLV data */ +} __attribute__((packed)); + +/* + * Stream Capabilities + */ +struct snd_soc_tplg_stream_caps { + __le32 size; /* in bytes of this structure */ + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + __le64 formats[SND_SOC_TPLG_MAX_FORMATS]; /* supported formats SNDRV_PCM_FMTBIT_* */ + __le32 rates; /* supported rates SNDRV_PCM_RATE_* */ + __le32 rate_min; /* min rate */ + __le32 rate_max; /* max rate */ + __le32 channels_min; /* min channels */ + __le32 channels_max; /* max channels */ + __le32 periods_min; /* min number of periods */ + __le32 periods_max; /* max number of periods */ + __le32 period_size_min; /* min period size bytes */ + __le32 period_size_max; /* max period size bytes */ + __le32 buffer_size_min; /* min buffer size bytes */ + __le32 buffer_size_max; /* max buffer size bytes */ +} __attribute__((packed)); + +/* + * FE or BE Stream configuration supported by SW/FW + */ +struct snd_soc_tplg_stream { + __le32 size; /* in bytes of this structure */ + __le64 format; /* SNDRV_PCM_FMTBIT_* */ + __le32 rate; /* SNDRV_PCM_RATE_* */ + __le32 period_bytes; /* size of period in bytes */ + __le32 buffer_bytes; /* size of buffer in bytes */ + __le32 channels; /* channels */ + __le32 tdm_slot; /* optional BE bitmask of supported TDM slots */ + __le32 dai_fmt; /* SND_SOC_DAIFMT_ */ +} __attribute__((packed)); + +/* + * Duplex stream configuration supported by SW/FW. + */ +struct snd_soc_tplg_stream_config { + __le32 size; /* in bytes of this structure */ + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct snd_soc_tplg_stream playback; + struct snd_soc_tplg_stream capture; +} __attribute__((packed)); + +/* + * Manifest. List totals for each payload type. Not used in parsing, but will + * be passed to the component driver before any other objects in order for any + * global component resource allocations. + * + * File block representation for manifest :- + * +-----------------------------------+----+ + * | struct snd_soc_tplg_hdr | 1 | + * +-----------------------------------+----+ + * | struct snd_soc_tplg_manifest | 1 | + * +-----------------------------------+----+ + */ +struct snd_soc_tplg_manifest { + __le32 size; /* in bytes of this structure */ + __le32 control_elems; /* number of control elements */ + __le32 widget_elems; /* number of widget elements */ + __le32 graph_elems; /* number of graph elements */ + __le32 dai_elems; /* number of DAI elements */ + __le32 dai_link_elems; /* number of DAI link elements */ + struct snd_soc_tplg_private priv; +} __attribute__((packed)); + +/* + * Mixer kcontrol. + * + * File block representation for mixer kcontrol :- + * +-----------------------------------+----+ + * | struct snd_soc_tplg_hdr | 1 | + * +-----------------------------------+----+ + * | struct snd_soc_tplg_mixer_control | N | + * +-----------------------------------+----+ + */ +struct snd_soc_tplg_mixer_control { + struct snd_soc_tplg_ctl_hdr hdr; + __le32 size; /* in bytes of this structure */ + __le32 min; + __le32 max; + __le32 platform_max; + __le32 invert; + __le32 num_channels; + struct snd_soc_tplg_channel channel[SND_SOC_TPLG_MAX_CHAN]; + struct snd_soc_tplg_ctl_tlv tlv; + struct snd_soc_tplg_private priv; +} __attribute__((packed)); + +/* + * Enumerated kcontrol + * + * File block representation for enum kcontrol :- + * +-----------------------------------+----+ + * | struct snd_soc_tplg_hdr | 1 | + * +-----------------------------------+----+ + * | struct snd_soc_tplg_enum_control | N | + * +-----------------------------------+----+ + */ +struct snd_soc_tplg_enum_control { + struct snd_soc_tplg_ctl_hdr hdr; + __le32 size; /* in bytes of this structure */ + __le32 num_channels; + struct snd_soc_tplg_channel channel[SND_SOC_TPLG_MAX_CHAN]; + __le32 items; + __le32 mask; + __le32 count; + char texts[SND_SOC_TPLG_NUM_TEXTS][SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + __le32 values[SND_SOC_TPLG_NUM_TEXTS * SNDRV_CTL_ELEM_ID_NAME_MAXLEN / 4]; + struct snd_soc_tplg_private priv; +} __attribute__((packed)); + +/* + * Bytes kcontrol + * + * File block representation for bytes kcontrol :- + * +-----------------------------------+----+ + * | struct snd_soc_tplg_hdr | 1 | + * +-----------------------------------+----+ + * | struct snd_soc_tplg_bytes_control | N | + * +-----------------------------------+----+ + */ +struct snd_soc_tplg_bytes_control { + struct snd_soc_tplg_ctl_hdr hdr; + __le32 size; /* in bytes of this structure */ + __le32 max; + __le32 mask; + __le32 base; + __le32 num_regs; + struct snd_soc_tplg_private priv; +} __attribute__((packed)); + +/* + * DAPM Graph Element + * + * File block representation for DAPM graph elements :- + * +-------------------------------------+----+ + * | struct snd_soc_tplg_hdr | 1 | + * +-------------------------------------+----+ + * | struct snd_soc_tplg_dapm_graph_elem | N | + * +-------------------------------------+----+ + */ +struct snd_soc_tplg_dapm_graph_elem { + char sink[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + char control[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + char source[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; +} __attribute__((packed)); + +/* + * DAPM Widget. + * + * File block representation for DAPM widget :- + * +-------------------------------------+-----+ + * | struct snd_soc_tplg_hdr | 1 | + * +-------------------------------------+-----+ + * | struct snd_soc_tplg_dapm_widget | N | + * +-------------------------------------+-----+ + * | struct snd_soc_tplg_enum_control | 0|1 | + * | struct snd_soc_tplg_mixer_control | 0|N | + * +-------------------------------------+-----+ + * + * Optional enum or mixer control can be appended to the end of each widget + * in the block. + */ +struct snd_soc_tplg_dapm_widget { + __le32 size; /* in bytes of this structure */ + __le32 id; /* SND_SOC_DAPM_CTL */ + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + char sname[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + __le32 reg; /* negative reg = no direct dapm */ + __le32 shift; /* bits to shift */ + __le32 mask; /* non-shifted mask */ + __le32 subseq; /* sort within widget type */ + __u32 invert; /* invert the power bit */ + __u32 ignore_suspend; /* kept enabled over suspend */ + __u16 event_flags; + __u16 event_type; + __u16 num_kcontrols; + struct snd_soc_tplg_private priv; + /* + * kcontrols that relate to this widget + * follow here after widget private data + */ +} __attribute__((packed)); + +struct snd_soc_tplg_pcm_cfg_caps { + struct snd_soc_tplg_stream_caps caps; + struct snd_soc_tplg_stream_config configs[SND_SOC_TPLG_STREAM_CONFIG_MAX]; + __le32 num_configs; /* number of configs */ +} __attribute__((packed)); + +/* + * Describes SW/FW specific features of PCM or DAI link. + * + * File block representation for PCM/DAI-Link :- + * +-----------------------------------+-----+ + * | struct snd_soc_tplg_hdr | 1 | + * +-----------------------------------+-----+ + * | struct snd_soc_tplg_dapm_pcm_dai | N | + * +-----------------------------------+-----+ + */ +struct snd_soc_tplg_pcm_dai { + __le32 size; /* in bytes of this structure */ + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + __le32 id; /* unique ID - used to match */ + __le32 playback; /* supports playback mode */ + __le32 capture; /* supports capture mode */ + __le32 compress; /* 1 = compressed; 0 = PCM */ + struct snd_soc_tplg_pcm_cfg_caps capconf[2]; /* capabilities and configs */ +} __attribute__((packed)); + +#endif diff --git a/include/sound/tlv.h b/include/sound/tlv.h new file mode 100644 index 0000000..33d747d --- /dev/null +++ b/include/sound/tlv.h @@ -0,0 +1,23 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + */ + +#ifndef __UAPI_SOUND_TLV_H +#define __UAPI_SOUND_TLV_H + +#define SNDRV_CTL_TLVT_CONTAINER 0 /* one level down - group of TLVs */ +#define SNDRV_CTL_TLVT_DB_SCALE 1 /* dB scale */ +#define SNDRV_CTL_TLVT_DB_LINEAR 2 /* linear volume */ +#define SNDRV_CTL_TLVT_DB_RANGE 3 /* dB range container */ +#define SNDRV_CTL_TLVT_DB_MINMAX 4 /* dB scale with min/max */ +#define SNDRV_CTL_TLVT_DB_MINMAX_MUTE 5 /* dB scale with min/max with mute */ + +#endif
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- src/conf/Makefile.am | 2 +- src/conf/topology/Makefile.am | 1 + src/conf/topology/broadwell/Makefile.am | 4 + src/conf/topology/broadwell/broadwell.conf | 375 +++++++++++++++++++++++++++++ 4 files changed, 381 insertions(+), 1 deletion(-) create mode 100644 src/conf/topology/Makefile.am create mode 100644 src/conf/topology/broadwell/Makefile.am create mode 100644 src/conf/topology/broadwell/broadwell.conf
diff --git a/src/conf/Makefile.am b/src/conf/Makefile.am index 948d5a1..a04f73f 100644 --- a/src/conf/Makefile.am +++ b/src/conf/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS=cards pcm alsa.conf.d ucm +SUBDIRS=cards pcm alsa.conf.d ucm topology
cfg_files = alsa.conf if BUILD_ALISP diff --git a/src/conf/topology/Makefile.am b/src/conf/topology/Makefile.am new file mode 100644 index 0000000..f56a96c --- /dev/null +++ b/src/conf/topology/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=broadwell diff --git a/src/conf/topology/broadwell/Makefile.am b/src/conf/topology/broadwell/Makefile.am new file mode 100644 index 0000000..35d1e83 --- /dev/null +++ b/src/conf/topology/broadwell/Makefile.am @@ -0,0 +1,4 @@ +alsaconfigdir = @ALSA_CONFIG_DIR@ +topologydir = $(alsaconfigdir)/topology/broadwell +topology_DATA = broadwell.conf +EXTRA_DIST = $(topology_DATA) diff --git a/src/conf/topology/broadwell/broadwell.conf b/src/conf/topology/broadwell/broadwell.conf new file mode 100644 index 0000000..05b3889 --- /dev/null +++ b/src/conf/topology/broadwell/broadwell.conf @@ -0,0 +1,375 @@ +# Dynamic Firmware Configuration for Broadwell + +# TLV +SectionTLV."hsw_vol_tlv" { + Comment "TLV used by both global and stream volumes" + + scale { + min "-9000" + step "300" + mute "1" + } +} + +# Controls +SectionControlMixer."Master Playback Volume" { + Comment "Global DSP volume" + + # control belongs to this index group + index "1" + + # Channel register and shift for Front Left/Right + channel."FL" { + reg "0" + shift "0" + } + channel."FR" { + reg "0" + shift "8" + } + + # max control value and whether value is inverted + max "31" + invert "false" + + # control uses bespoke driver get/put/info ID 0 + ops."ctl" { + info "volsw" + get "256" + put "256" + } + + # uses TLV data above + tlv "hsw_vol_tlv" +} + +SectionControlMixer."Media0 Playback Volume" { + Comment "Offload 0 volume" + + # control belongs to this index group + index "1" + + # Channel register and shift for Front Left/Right + channel."FL" { + reg "1" + shift "0" + } + channel."FR" { + reg "1" + shift "8" + } + + # max control value and whether value is inverted + max "31" + invert "false" + + # control uses bespoke driver get/put/info ID 0 + ops."ctl" { + info "volsw" + get "257" + put "257" + } + + # uses TLV data above + tlv "hsw_vol_tlv" +} + +SectionControlMixer."Media1 Playback Volume" { + Comment "Offload 1 volume" + + # control belongs to this index group + index "1" + + # Channel register and shift for Front Left/Right + channel."FL" { + reg "2" + shift "0" + } + channel."FR" { + reg "2" + shift "8" + } + + # max control value and whether value is inverted + max "31" + invert "false" + + # control uses bespoke driver get/put/info ID 0 + ops."ctl" { + info "volsw" + get "257" + put "257" + } + + # uses TLV data above + tlv "hsw_vol_tlv" +} + +SectionControlMixer."Mic Capture Volume" { + Comment "Mic Capture volume" + + # control belongs to this index group + index "1" + + # Channel register and shift for Front Left/Right + channel."FL" { + reg "0" + shift "0" + } + channel."FR" { + reg "0" + shift "8" + } + + # max control value and whether value is inverted + max "31" + invert "false" + + # control uses bespoke driver get/put/info ID 0 + ops."ctl" { + info "volsw" + get "257" + put "257" + } + + # uses TLV data above + tlv "hsw_vol_tlv" +} + +SectionWidget."SSP0 CODEC IN" { + + index "1" + type "aif_in" + no_pm "true" + shift "0" + invert "0" +} + +SectionWidget."SSP0 CODEC OUT" { + + index "1" + type "aif_out" + no_pm "true" + shift "0" + invert "0" +} + +SectionWidget."SSP1 BT IN" { + + index "1" + type "aif_in" + no_pm "true" + shift "0" + invert "0" +} + +SectionWidget."SSP1 BT OUT" { + + index "1" + type "aif_out" + no_pm "true" + shift "0" + invert "0" +} + +SectionWidget."Playback VMixer" { + + index "1" + type "mixer" + no_pm "true" + shift "0" + invert "0" +} + +# PCM Configurations supported by FW +SectionPCMConfig."PCM 48k Stereo 24bit" { + + config."playback" { + format "S24_LE" + rate "48000" + channels "2" + tdm_slot "0xf" + } + + config."capture" { + format "S24_LE" + rate "48000" + channels "2" + tdm_slot "0xf" + } +} + +SectionPCMConfig."PCM 48k Stereo 16bit" { + + config."playback" { + format "S16_LE" + rate "48000" + channels "2" + tdm_slot "0xf" + } + + config."capture" { + format "S16_LE" + rate "48000" + channels "2" + tdm_slot "0xf" + } +} + +SectionPCMConfig."PCM 48k 2P/4C 16bit" { + + config."playback" { + format "S16_LE" + rate "48000" + channels "2" + tdm_slot "0xf" + } + + config."capture" { + format "S16_LE" + rate "48000" + channels "4" + tdm_slot "0xf" + } +} + +# PCM capabilities supported by FW +SectionPCMCapabilities."System Playback" { + + formats "S24_LE,S16_LE" + rate_min "48000" + rate_max "48000" + channels_min "2" + channels_max "2" +} + +SectionPCMCapabilities."Analog Capture" { + + formats "S24_LE,S16_LE" + rate_min "48000" + rate_max "48000" + channels_min "2" + channels_max "4" +} + +SectionPCMCapabilities."Loopback Capture" { + + formats "S24_LE,S16_LE" + rate_min "48000" + rate_max "48000" + channels_min "2" + channels_max "2" +} + +SectionPCMCapabilities."Offload0 Playback" { + formats "S24_LE,S16_LE" + rate_min "8000" + rate_max "192000" + channels_min "2" + channels_max "2" +} + +SectionPCMCapabilities."Offload1 Playback" { + formats "S24_LE,S16_LE" + rate_min "8000" + rate_max "48000" + channels_min "2" + channels_max "2" +} + +# PCM devices exported by Firmware +SectionPCM."System Pin" { + + index "1" + + # used for binding to the PCM + ID "0" + + pcm."playback" { + + capabilities "System Playback" + + configs [ + "PCM 48k Stereo 24bit" + "PCM 48k Stereo 16bit" + ] + } + + pcm."capture" { + + capabilities "Analog Capture" + + configs [ + "PCM 48k Stereo 24bit" + "PCM 48k Stereo 16bit" + "PCM 48k 2P/4C 16bit" + ] + } +} + +SectionPCM."Offload0 Pin" { + + index "1" + + # used for binding to the PCM + ID "1" + + pcm."playback" { + + capabilities "Offload0 Playback" + + configs [ + "PCM 48k Stereo 24bit" + "PCM 48k Stereo 16bit" + ] + } +} + +SectionPCM."Offload1 Pin" { + + index "1" + + # used for binding to the PCM + ID "2" + + pcm."playback" { + + capabilities "Offload1 Playback" + + configs [ + "PCM 48k Stereo 24bit" + "PCM 48k Stereo 16bit" + ] + } +} + +SectionPCM."Loopback Pin" { + + index "1" + + # used for binding to the PCM + ID "3" + + pcm."capture" { + + capabilities "Loopback Capture" + + configs [ + "PCM 48k Stereo 24bit" + "PCM 48k Stereo 16bit" + ] + } +} + +SectionGraph."dsp" { + index "1" + + lines [ + "Playback VMixer, , System Playback" + "Playback VMixer, , Offload0 Playback" + "Playback VMixer, , Offload1 Playback" + "SSP0 CODEC OUT, , Playback VMixer" + "Loopback Capture, , Playback VMixer" + "Analog Capture, , SSP0 CODEC IN" + ] +}
At Wed, 1 Jul 2015 14:44:24 +0100, Liam Girdwood wrote:
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com
It's a kind of example, so maybe better to put at the end of the series?
Takashi
src/conf/Makefile.am | 2 +- src/conf/topology/Makefile.am | 1 + src/conf/topology/broadwell/Makefile.am | 4 + src/conf/topology/broadwell/broadwell.conf | 375 +++++++++++++++++++++++++++++ 4 files changed, 381 insertions(+), 1 deletion(-) create mode 100644 src/conf/topology/Makefile.am create mode 100644 src/conf/topology/broadwell/Makefile.am create mode 100644 src/conf/topology/broadwell/broadwell.conf
diff --git a/src/conf/Makefile.am b/src/conf/Makefile.am index 948d5a1..a04f73f 100644 --- a/src/conf/Makefile.am +++ b/src/conf/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS=cards pcm alsa.conf.d ucm +SUBDIRS=cards pcm alsa.conf.d ucm topology
cfg_files = alsa.conf if BUILD_ALISP diff --git a/src/conf/topology/Makefile.am b/src/conf/topology/Makefile.am new file mode 100644 index 0000000..f56a96c --- /dev/null +++ b/src/conf/topology/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=broadwell diff --git a/src/conf/topology/broadwell/Makefile.am b/src/conf/topology/broadwell/Makefile.am new file mode 100644 index 0000000..35d1e83 --- /dev/null +++ b/src/conf/topology/broadwell/Makefile.am @@ -0,0 +1,4 @@ +alsaconfigdir = @ALSA_CONFIG_DIR@ +topologydir = $(alsaconfigdir)/topology/broadwell +topology_DATA = broadwell.conf +EXTRA_DIST = $(topology_DATA) diff --git a/src/conf/topology/broadwell/broadwell.conf b/src/conf/topology/broadwell/broadwell.conf new file mode 100644 index 0000000..05b3889 --- /dev/null +++ b/src/conf/topology/broadwell/broadwell.conf @@ -0,0 +1,375 @@ +# Dynamic Firmware Configuration for Broadwell
+# TLV +SectionTLV."hsw_vol_tlv" {
- Comment "TLV used by both global and stream volumes"
- scale {
min "-9000"
step "300"
mute "1"
- }
+}
+# Controls +SectionControlMixer."Master Playback Volume" {
- Comment "Global DSP volume"
- # control belongs to this index group
- index "1"
- # Channel register and shift for Front Left/Right
- channel."FL" {
reg "0"
shift "0"
- }
- channel."FR" {
reg "0"
shift "8"
- }
- # max control value and whether value is inverted
- max "31"
- invert "false"
- # control uses bespoke driver get/put/info ID 0
- ops."ctl" {
info "volsw"
get "256"
put "256"
- }
- # uses TLV data above
- tlv "hsw_vol_tlv"
+}
+SectionControlMixer."Media0 Playback Volume" {
- Comment "Offload 0 volume"
- # control belongs to this index group
- index "1"
- # Channel register and shift for Front Left/Right
- channel."FL" {
reg "1"
shift "0"
- }
- channel."FR" {
reg "1"
shift "8"
- }
- # max control value and whether value is inverted
- max "31"
- invert "false"
- # control uses bespoke driver get/put/info ID 0
- ops."ctl" {
info "volsw"
get "257"
put "257"
- }
- # uses TLV data above
- tlv "hsw_vol_tlv"
+}
+SectionControlMixer."Media1 Playback Volume" {
- Comment "Offload 1 volume"
- # control belongs to this index group
- index "1"
- # Channel register and shift for Front Left/Right
- channel."FL" {
reg "2"
shift "0"
- }
- channel."FR" {
reg "2"
shift "8"
- }
- # max control value and whether value is inverted
- max "31"
- invert "false"
- # control uses bespoke driver get/put/info ID 0
- ops."ctl" {
info "volsw"
get "257"
put "257"
- }
- # uses TLV data above
- tlv "hsw_vol_tlv"
+}
+SectionControlMixer."Mic Capture Volume" {
- Comment "Mic Capture volume"
- # control belongs to this index group
- index "1"
- # Channel register and shift for Front Left/Right
- channel."FL" {
reg "0"
shift "0"
- }
- channel."FR" {
reg "0"
shift "8"
- }
- # max control value and whether value is inverted
- max "31"
- invert "false"
- # control uses bespoke driver get/put/info ID 0
- ops."ctl" {
info "volsw"
get "257"
put "257"
- }
- # uses TLV data above
- tlv "hsw_vol_tlv"
+}
+SectionWidget."SSP0 CODEC IN" {
- index "1"
- type "aif_in"
- no_pm "true"
- shift "0"
- invert "0"
+}
+SectionWidget."SSP0 CODEC OUT" {
- index "1"
- type "aif_out"
- no_pm "true"
- shift "0"
- invert "0"
+}
+SectionWidget."SSP1 BT IN" {
- index "1"
- type "aif_in"
- no_pm "true"
- shift "0"
- invert "0"
+}
+SectionWidget."SSP1 BT OUT" {
- index "1"
- type "aif_out"
- no_pm "true"
- shift "0"
- invert "0"
+}
+SectionWidget."Playback VMixer" {
- index "1"
- type "mixer"
- no_pm "true"
- shift "0"
- invert "0"
+}
+# PCM Configurations supported by FW +SectionPCMConfig."PCM 48k Stereo 24bit" {
- config."playback" {
format "S24_LE"
rate "48000"
channels "2"
tdm_slot "0xf"
- }
- config."capture" {
format "S24_LE"
rate "48000"
channels "2"
tdm_slot "0xf"
- }
+}
+SectionPCMConfig."PCM 48k Stereo 16bit" {
- config."playback" {
format "S16_LE"
rate "48000"
channels "2"
tdm_slot "0xf"
- }
- config."capture" {
format "S16_LE"
rate "48000"
channels "2"
tdm_slot "0xf"
- }
+}
+SectionPCMConfig."PCM 48k 2P/4C 16bit" {
- config."playback" {
format "S16_LE"
rate "48000"
channels "2"
tdm_slot "0xf"
- }
- config."capture" {
format "S16_LE"
rate "48000"
channels "4"
tdm_slot "0xf"
- }
+}
+# PCM capabilities supported by FW +SectionPCMCapabilities."System Playback" {
- formats "S24_LE,S16_LE"
- rate_min "48000"
- rate_max "48000"
- channels_min "2"
- channels_max "2"
+}
+SectionPCMCapabilities."Analog Capture" {
- formats "S24_LE,S16_LE"
- rate_min "48000"
- rate_max "48000"
- channels_min "2"
- channels_max "4"
+}
+SectionPCMCapabilities."Loopback Capture" {
- formats "S24_LE,S16_LE"
- rate_min "48000"
- rate_max "48000"
- channels_min "2"
- channels_max "2"
+}
+SectionPCMCapabilities."Offload0 Playback" {
- formats "S24_LE,S16_LE"
- rate_min "8000"
- rate_max "192000"
- channels_min "2"
- channels_max "2"
+}
+SectionPCMCapabilities."Offload1 Playback" {
- formats "S24_LE,S16_LE"
- rate_min "8000"
- rate_max "48000"
- channels_min "2"
- channels_max "2"
+}
+# PCM devices exported by Firmware +SectionPCM."System Pin" {
- index "1"
- # used for binding to the PCM
- ID "0"
- pcm."playback" {
capabilities "System Playback"
configs [
"PCM 48k Stereo 24bit"
"PCM 48k Stereo 16bit"
]
- }
- pcm."capture" {
capabilities "Analog Capture"
configs [
"PCM 48k Stereo 24bit"
"PCM 48k Stereo 16bit"
"PCM 48k 2P/4C 16bit"
]
- }
+}
+SectionPCM."Offload0 Pin" {
- index "1"
- # used for binding to the PCM
- ID "1"
- pcm."playback" {
capabilities "Offload0 Playback"
configs [
"PCM 48k Stereo 24bit"
"PCM 48k Stereo 16bit"
]
- }
+}
+SectionPCM."Offload1 Pin" {
- index "1"
- # used for binding to the PCM
- ID "2"
- pcm."playback" {
capabilities "Offload1 Playback"
configs [
"PCM 48k Stereo 24bit"
"PCM 48k Stereo 16bit"
]
- }
+}
+SectionPCM."Loopback Pin" {
- index "1"
- # used for binding to the PCM
- ID "3"
- pcm."capture" {
capabilities "Loopback Capture"
configs [
"PCM 48k Stereo 24bit"
"PCM 48k Stereo 16bit"
]
- }
+}
+SectionGraph."dsp" {
- index "1"
- lines [
"Playback VMixer, , System Playback"
"Playback VMixer, , Offload0 Playback"
"Playback VMixer, , Offload1 Playback"
"SSP0 CODEC OUT, , Playback VMixer"
"Loopback Capture, , Playback VMixer"
"Analog Capture, , SSP0 CODEC IN"
- ]
+}
2.1.4
On Wed, 2015-07-01 at 17:47 +0200, Takashi Iwai wrote:
At Wed, 1 Jul 2015 14:44:24 +0100, Liam Girdwood wrote:
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com
It's a kind of example, so maybe better to put at the end of the series?
I did originally have it at the end, but moved it to show the schema early for anyone reviewing the parser code. I'll stick it back for v3.
Liam
The topology core parses the high level topology file and calls the individual object parsers when any new object element is detected at the high level.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- include/topology.h | 75 ++++++++++ src/topology/elem.c | 187 ++++++++++++++++++++++++ src/topology/parser.c | 357 ++++++++++++++++++++++++++++++++++++++++++++++ src/topology/tplg_local.h | 223 +++++++++++++++++++++++++++++ 4 files changed, 842 insertions(+) create mode 100644 include/topology.h create mode 100644 src/topology/elem.c create mode 100644 src/topology/parser.c create mode 100644 src/topology/tplg_local.h
diff --git a/include/topology.h b/include/topology.h new file mode 100644 index 0000000..d9b223f --- /dev/null +++ b/include/topology.h @@ -0,0 +1,75 @@ +/* + * + * 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Copyright (C) 2015 Intel Corporation + * + */ + +#ifndef __ALSA_TOPOLOGY_H +#define __ALSA_TOPOLOGY_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \defgroup topology Topology Interface + * The topology interface. + * See \ref Topology page for more details. + * { + */ + +/*! \page topology ALSA Topology Interface + * + * ALSA Topology Interface + * + */ + +typedef struct snd_tplg snd_tplg_t; + +/** + * \brief Create a new topology parser instance. + * \return New topology parser instance + */ +snd_tplg_t *snd_tplg_new(void); + +/** + * \brief Free a topology parser instance. + * \param tplg Topology parser instance + */ +void snd_tplg_free(snd_tplg_t *tplg); + +/** + * \brief Parse and build topology text file into binary file. + * \param tplg Topology instance. + * \param infile Topology text input file to be parsed + * \param outfile Binary topology output file. + * \return Zero on sucess, otherwise a negative error code + */ +int snd_tplg_build(snd_tplg_t *tplg, const char *infile, const char *outfile); + +/** + * \brief Enable verbose reporting of binary file output + * \param tplg Topology Instance + * \param verbose Enable verbose output if non zero + */ +void snd_tplg_verbose(snd_tplg_t *tplg, int verbose); + +#ifdef __cplusplus +} +#endif + +#endif /* __ALSA_TOPOLOGY_H */ diff --git a/src/topology/elem.c b/src/topology/elem.c new file mode 100644 index 0000000..0ce406b --- /dev/null +++ b/src/topology/elem.c @@ -0,0 +1,187 @@ +/* + Copyright(c) 2014-2015 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + Authors: Mengdong Lin mengdong.lin@intel.com + Yao Jin yao.jin@intel.com + Liam Girdwood liam.r.girdwood@linux.intel.com +*/ + +#include "list.h" +#include "tplg_local.h" + +int tplg_ref_add(struct tplg_elem *elem, int type, const char* id) +{ + struct tplg_ref *ref; + + ref = calloc(1, sizeof(*ref)); + if (!ref) + return -ENOMEM; + + strncpy(ref->id, id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + ref->id[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0; + ref->type = type; + + list_add_tail(&ref->list, &elem->ref_list); + return 0; +} + +void tplg_ref_free_list(struct list_head *base) +{ + struct list_head *pos, *npos; + struct tplg_ref *ref; + + list_for_each_safe(pos, npos, base) { + ref = list_entry(pos, struct tplg_ref, list); + list_del(&ref->list); + free(ref); + } +} + +struct tplg_elem *tplg_elem_new(void) +{ + struct tplg_elem *elem; + + elem = calloc(1, sizeof(*elem)); + if (!elem) + return NULL; + + INIT_LIST_HEAD(&elem->ref_list); + return elem; +} + +void tplg_elem_free(struct tplg_elem *elem) +{ + tplg_ref_free_list(&elem->ref_list); + + /* free struct snd_tplg_ object, + * the union pointers share the same address + */ + if (elem->mixer_ctrl) + free(elem->mixer_ctrl); + + free(elem); +} + +void tplg_elem_free_list(struct list_head *base) +{ + struct list_head *pos, *npos; + struct tplg_elem *elem; + + list_for_each_safe(pos, npos, base) { + elem = list_entry(pos, struct tplg_elem, list); + list_del(&elem->list); + tplg_elem_free(elem); + } +} + +struct tplg_elem *tplg_elem_lookup(struct list_head *base, const char* id, + unsigned int type) +{ + struct list_head *pos, *npos; + struct tplg_elem *elem; + + list_for_each_safe(pos, npos, base) { + + elem = list_entry(pos, struct tplg_elem, list); + + if (!strcmp(elem->id, id) && elem->type == type) + return elem; + } + + return NULL; +} + +/* create a new common element and object */ +struct tplg_elem* tplg_elem_new_common(snd_tplg_t *tplg, + snd_config_t *cfg, enum parser_type type) +{ + struct tplg_elem *elem; + const char *id; + int obj_size = 0; + void *obj; + + elem = tplg_elem_new(); + if (!elem) + return NULL; + + snd_config_get_id(cfg, &id); + strncpy(elem->id, id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + elem->id[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0; + + switch (type) { + case PARSER_TYPE_DATA: + list_add_tail(&elem->list, &tplg->pdata_list); + break; + case PARSER_TYPE_TEXT: + list_add_tail(&elem->list, &tplg->text_list); + break; + case PARSER_TYPE_TLV: + list_add_tail(&elem->list, &tplg->tlv_list); + elem->size = sizeof(struct snd_soc_tplg_ctl_tlv); + break; + case PARSER_TYPE_BYTES: + list_add_tail(&elem->list, &tplg->bytes_ext_list); + obj_size = sizeof(struct snd_soc_tplg_bytes_control); + break; + case PARSER_TYPE_ENUM: + list_add_tail(&elem->list, &tplg->enum_list); + obj_size = sizeof(struct snd_soc_tplg_enum_control); + break; + case SND_SOC_TPLG_TYPE_MIXER: + list_add_tail(&elem->list, &tplg->mixer_list); + obj_size = sizeof(struct snd_soc_tplg_mixer_control); + break; + case PARSER_TYPE_DAPM_WIDGET: + list_add_tail(&elem->list, &tplg->widget_list); + obj_size = sizeof(struct snd_soc_tplg_dapm_widget); + break; + case PARSER_TYPE_STREAM_CONFIG: + list_add_tail(&elem->list, &tplg->pcm_config_list); + obj_size = sizeof(struct snd_soc_tplg_stream_config); + break; + case PARSER_TYPE_STREAM_CAPS: + list_add_tail(&elem->list, &tplg->pcm_caps_list); + obj_size = sizeof(struct snd_soc_tplg_stream_caps); + break; + case PARSER_TYPE_PCM: + list_add_tail(&elem->list, &tplg->pcm_list); + obj_size = sizeof(struct snd_soc_tplg_pcm_dai); + break; + case PARSER_TYPE_BE: + list_add_tail(&elem->list, &tplg->be_list); + obj_size = sizeof(struct snd_soc_tplg_pcm_dai); + break; + case PARSER_TYPE_CC: + list_add_tail(&elem->list, &tplg->cc_list); + obj_size = sizeof(struct snd_soc_tplg_pcm_dai); + break; + default: + free(elem); + return NULL; + } + + /* create new object too if required */ + if (obj_size > 0) { + obj = calloc(1, obj_size); + if (obj == NULL) { + free(elem); + return NULL; + } + + elem->obj = obj; + elem->size = obj_size; + } + + elem->type = type; + return elem; +} diff --git a/src/topology/parser.c b/src/topology/parser.c new file mode 100644 index 0000000..f813deb --- /dev/null +++ b/src/topology/parser.c @@ -0,0 +1,357 @@ +/* + Copyright(c) 2014-2015 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + Authors: Mengdong Lin mengdong.lin@intel.com + Yao Jin yao.jin@intel.com + Liam Girdwood liam.r.girdwood@linux.intel.com +*/ + +#include "list.h" +#include "tplg_local.h" + +/* + * Parse compound + */ +int tplg_parse_compound(snd_tplg_t *tplg, snd_config_t *cfg, + int (*fcn)(snd_tplg_t *, snd_config_t *, void *), + void *private) +{ + const char *id; + snd_config_iterator_t i, next; + snd_config_t *n; + int err = -EINVAL; + + if (snd_config_get_id(cfg, &id) < 0) + return -EINVAL; + + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { + fprintf(stderr, "error: compound type expected for %s", id); + return -EINVAL; + } + + /* parse compound */ + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { + fprintf(stderr, "error: compound type expected for %s, is %d", + id, snd_config_get_type(cfg)); + return -EINVAL; + } + + err = fcn(tplg, n, private); + if (err < 0) + return err; + } + + return err; +} + +static int tplg_parse_config(snd_tplg_t *tplg, snd_config_t *cfg) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + const char *id; + int err; + + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { + fprintf(stderr, "error: compound type expected at top level"); + return -EINVAL; + } + + /* parse topology config sections */ + 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, "SectionTLV") == 0) { + err = tplg_parse_compound(tplg, n, tplg_parse_tlv, + NULL); + if (err < 0) + return err; + continue; + } + + if (strcmp(id, "SectionControlMixer") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_control_mixer, NULL); + if (err < 0) + return err; + continue; + } + + if (strcmp(id, "SectionControlEnum") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_control_enum, NULL); + if (err < 0) + return err; + continue; + } + + if (strcmp(id, "SectionControlBytes") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_control_bytes, NULL); + if (err < 0) + return err; + continue; + } + + if (strcmp(id, "SectionWidget") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_dapm_widget, NULL); + if (err < 0) + return err; + continue; + } + + if (strcmp(id, "SectionPCMConfig") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_pcm_config, NULL); + if (err < 0) + return err; + continue; + } + + if (strcmp(id, "SectionPCMCapabilities") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_pcm_caps, NULL); + if (err < 0) + return err; + continue; + } + + if (strcmp(id, "SectionPCM") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_pcm, NULL); + if (err < 0) + return err; + continue; + } + + if (strcmp(id, "SectionBE") == 0) { + err = tplg_parse_compound(tplg, n, tplg_parse_be, + NULL); + if (err < 0) + return err; + continue; + } + + if (strcmp(id, "SectionCC") == 0) { + err = tplg_parse_compound(tplg, n, tplg_parse_cc, + NULL); + if (err < 0) + return err; + continue; + } + + if (strcmp(id, "SectionGraph") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_dapm_graph, NULL); + if (err < 0) + return err; + continue; + } + + if (strcmp(id, "SectionText") == 0) { + err = tplg_parse_compound(tplg, n, tplg_parse_text, + NULL); + if (err < 0) + return err; + continue; + } + + if (strcmp(id, "SectionData") == 0) { + err = tplg_parse_compound(tplg, n, tplg_parse_data, + NULL); + if (err < 0) + return err; + continue; + } + + fprintf(stderr, "error: unknown section %s\n", id); + } + return 0; +} + +static int tplg_load_config(const char *file, snd_config_t **cfg) +{ + FILE *fp; + snd_input_t *in; + snd_config_t *top; + int ret; + + fp = fopen(file, "r"); + if (fp == NULL) { + fprintf(stdout, "error: could not open configuration file %s", + file); + return -errno; + } + + ret = snd_input_stdio_attach(&in, fp, 1); + if (ret < 0) { + fprintf(stdout, "error: could not attach stdio %s", file); + goto err; + } + ret = snd_config_top(&top); + if (ret < 0) + goto err; + + ret = snd_config_load(top, in); + if (ret < 0) { + fprintf(stdout, "error: could not load configuration file %s", + file); + goto err_load; + } + + ret = snd_input_close(in); + if (ret < 0) + goto err_load; + + *cfg = top; + return 0; + +err_load: + snd_config_delete(top); +err: + fclose(fp); + return ret; +} + +static int tplg_build_integ(snd_tplg_t *tplg) +{ + int err; + + err = tplg_build_controls(tplg); + if (err < 0) + return err; + + err = tplg_build_widgets(tplg); + if (err < 0) + return err; + + err = tplg_build_pcm_dai(tplg, PARSER_TYPE_PCM); + if (err < 0) + return err; + + err = tplg_build_pcm_dai(tplg, PARSER_TYPE_BE); + if (err < 0) + return err; + + err = tplg_build_pcm_dai(tplg, PARSER_TYPE_CC); + if (err < 0) + return err; + + err = tplg_build_routes(tplg); + if (err < 0) + return err; + + return err; +} + +int snd_tplg_build(snd_tplg_t *tplg, const char *infile, const char *outfile) +{ + snd_config_t *cfg = NULL; + int err = 0; + + /* delete any old output files */ + unlink(outfile); + + tplg->out_fd = + open(outfile, O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO); + if (tplg->out_fd < 0) { + fprintf(stderr, "error: failed to open %s err %d\n", + outfile, -errno); + return -errno; + } + + err = tplg_load_config(infile, &cfg); + if (err < 0) { + fprintf(stderr, "error: failed to load topology file %s\n", + infile); + return err; + } + + err = tplg_parse_config(tplg, cfg); + if (err < 0) { + fprintf(stderr, "error: failed to parse topology\n"); + goto out; + } + + err = tplg_build_integ(tplg); + if (err < 0) { + fprintf(stderr, "error: failed to check topology integrity\n"); + goto out; + } + + err = tplg_write_data(tplg); + if (err < 0) { + fprintf(stderr, "error: failed to write data %d\n", err); + goto out; + } + +out: + snd_config_delete(cfg); + close(tplg->out_fd); + return err; +} + +void snd_tplg_verbose(snd_tplg_t *tplg, int verbose) +{ + tplg->verbose = verbose; +} + +snd_tplg_t *snd_tplg_new(void) +{ + snd_tplg_t *tplg; + + tplg = calloc(1, sizeof(snd_tplg_t)); + if (!tplg) + return NULL; + + INIT_LIST_HEAD(&tplg->tlv_list); + INIT_LIST_HEAD(&tplg->widget_list); + INIT_LIST_HEAD(&tplg->pcm_list); + INIT_LIST_HEAD(&tplg->be_list); + INIT_LIST_HEAD(&tplg->cc_list); + INIT_LIST_HEAD(&tplg->route_list); + INIT_LIST_HEAD(&tplg->pdata_list); + INIT_LIST_HEAD(&tplg->text_list); + INIT_LIST_HEAD(&tplg->pcm_config_list); + INIT_LIST_HEAD(&tplg->pcm_caps_list); + INIT_LIST_HEAD(&tplg->mixer_list); + INIT_LIST_HEAD(&tplg->enum_list); + INIT_LIST_HEAD(&tplg->bytes_ext_list); + + return tplg; +} + +void snd_tplg_free(snd_tplg_t *tplg) +{ + tplg_elem_free_list(&tplg->tlv_list); + tplg_elem_free_list(&tplg->widget_list); + tplg_elem_free_list(&tplg->pcm_list); + tplg_elem_free_list(&tplg->be_list); + tplg_elem_free_list(&tplg->cc_list); + tplg_elem_free_list(&tplg->route_list); + tplg_elem_free_list(&tplg->pdata_list); + tplg_elem_free_list(&tplg->text_list); + tplg_elem_free_list(&tplg->pcm_config_list); + tplg_elem_free_list(&tplg->pcm_caps_list); + tplg_elem_free_list(&tplg->mixer_list); + tplg_elem_free_list(&tplg->enum_list); + tplg_elem_free_list(&tplg->bytes_ext_list); + + free(tplg); +} diff --git a/src/topology/tplg_local.h b/src/topology/tplg_local.h new file mode 100644 index 0000000..ca47879 --- /dev/null +++ b/src/topology/tplg_local.h @@ -0,0 +1,223 @@ +/* + * 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 of the License, or (at your option) any later version. + * + * This library 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. + */ + +#include <limits.h> +#include <stdint.h> +#include <linux/types.h> + +#include "local.h" +#include "list.h" +#include "topology.h" + +#include <sound/asound.h> +#include <sound/asoc.h> +#include <sound/tlv.h> + +#define TPLG_DEBUG +#ifdef TPLG_DEBUG +#define tplg_dbg SNDERR +#else +#define tplg_dbg(fmt, arg...) do { } while (0) +#endif + +#define MAX_FILE 256 +#define TPLG_MAX_PRIV_SIZE (1024 * 128) +#define ALSA_TPLG_DIR ALSA_CONFIG_DIR "/topology" +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +/** The name of the environment variable containing the tplg directory */ +#define ALSA_CONFIG_TPLG_VAR "ALSA_CONFIG_TPLG" + +struct tplg_ref; +struct tplg_elem; + +/* internal topology object type not used by kernel */ +enum parser_type { + PARSER_TYPE_TLV = 0, + PARSER_TYPE_MIXER, + PARSER_TYPE_ENUM, + PARSER_TYPE_TEXT, + PARSER_TYPE_DATA, + PARSER_TYPE_BYTES, + PARSER_TYPE_STREAM_CONFIG, + PARSER_TYPE_STREAM_CAPS, + PARSER_TYPE_PCM, + PARSER_TYPE_DAPM_WIDGET, + PARSER_TYPE_DAPM_GRAPH, + PARSER_TYPE_BE, + PARSER_TYPE_CC, +}; + +struct snd_tplg { + + /* opaque vendor data */ + int vendor_fd; + char *vendor_name; + + /* out file */ + int out_fd; + + int verbose; + unsigned int version; + + /* runtime state */ + unsigned int next_hdr_pos; + int index; + int channel_idx; + + /* list of each element type */ + struct list_head tlv_list; + struct list_head widget_list; + struct list_head pcm_list; + struct list_head be_list; + struct list_head cc_list; + struct list_head route_list; + struct list_head text_list; + struct list_head pdata_list; + struct list_head pcm_config_list; + struct list_head pcm_caps_list; + + /* type-specific control lists */ + struct list_head mixer_list; + struct list_head enum_list; + struct list_head bytes_ext_list; +}; + +/* object text references */ +struct tplg_ref { + unsigned int type; + struct tplg_elem *elem; + char id[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct list_head list; +}; + +/* topology element */ +struct tplg_elem { + + char id[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + /* storage for texts and data if this is text or data elem*/ + char texts[SND_SOC_TPLG_NUM_TEXTS][SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + int index; + enum parser_type type; + + int size; /* total size of this object inc pdata and ref objects */ + int compound_elem; /* dont write this element as individual elem */ + + /* UAPI object for this elem */ + union { + void *obj; + struct snd_soc_tplg_mixer_control *mixer_ctrl; + struct snd_soc_tplg_enum_control *enum_ctrl; + struct snd_soc_tplg_bytes_control *bytes_ext; + struct snd_soc_tplg_dapm_widget *widget; + struct snd_soc_tplg_pcm_dai *pcm; + struct snd_soc_tplg_pcm_dai *be; + struct snd_soc_tplg_pcm_dai *cc; + struct snd_soc_tplg_dapm_graph_elem *route; + struct snd_soc_tplg_stream_config *stream_cfg; + struct snd_soc_tplg_stream_caps *stream_caps; + + /* these do not map to UAPI structs but are internal only */ + struct snd_soc_tplg_ctl_tlv *tlv; + struct snd_soc_tplg_private *data; + }; + + /* an element may refer to other elements: + * a mixer control may refer to a tlv, + * a widget may refer to a mixer control array, + * a graph may refer to some widgets. + */ + struct list_head ref_list; + struct list_head list; /* list of all elements with same type */ +}; + +struct map_elem { + const char *name; + int id; +}; + +int tplg_parse_compound(snd_tplg_t *tplg, snd_config_t *cfg, + int (*fcn)(snd_tplg_t *, snd_config_t *, void *), + void *private); + +int tplg_write_data(snd_tplg_t *tplg); + +int tplg_parse_tlv(snd_tplg_t *tplg, snd_config_t *cfg, + void *private ATTRIBUTE_UNUSED); + +int tplg_parse_text(snd_tplg_t *tplg, snd_config_t *cfg, + void *private ATTRIBUTE_UNUSED); + +int tplg_parse_data(snd_tplg_t *tplg, snd_config_t *cfg, + void *private ATTRIBUTE_UNUSED); + +int tplg_parse_control_bytes(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED); + +int tplg_parse_control_enum(snd_tplg_t *tplg, snd_config_t *cfg, + void *private ATTRIBUTE_UNUSED); + +int tplg_parse_control_mixer(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED); + +int tplg_parse_dapm_graph(snd_tplg_t *tplg, snd_config_t *cfg, + void *private ATTRIBUTE_UNUSED); + +int tplg_parse_dapm_widget(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED); + +int tplg_parse_pcm_config(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED); + +int tplg_parse_pcm_caps(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED); + +int tplg_parse_pcm_cap_cfg(snd_tplg_t *tplg, snd_config_t *cfg, + void *private); + +int tplg_parse_pcm(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED); + +int tplg_parse_be(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED); + +int tplg_parse_cc(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED); + +int tplg_build_controls(snd_tplg_t *tplg); +int tplg_build_widgets(snd_tplg_t *tplg); +int tplg_build_routes(snd_tplg_t *tplg); +int tplg_build_pcm_dai(snd_tplg_t *tplg, unsigned int type); + +int tplg_copy_data(struct tplg_elem *elem, struct tplg_elem *ref); + +int tplg_ref_add(struct tplg_elem *elem, int type, const char* id); + +struct tplg_elem *tplg_elem_new(void); +void tplg_elem_free(struct tplg_elem *elem); +void tplg_elem_free_list(struct list_head *base); +struct tplg_elem *tplg_elem_lookup(struct list_head *base, + const char* id, + unsigned int type); +struct tplg_elem* tplg_elem_new_common(snd_tplg_t *tplg, + snd_config_t *cfg, enum parser_type type); + +int tplg_parse_channel(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + snd_config_t *cfg, void *private); + +int tplg_parse_ops(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + snd_config_t *cfg, void *private); + +struct tplg_elem *lookup_pcm_dai_stream(struct list_head *base, + const char* id);
At Wed, 1 Jul 2015 14:44:25 +0100, Liam Girdwood wrote:
+int tplg_parse_compound(snd_tplg_t *tplg, snd_config_t *cfg,
- int (*fcn)(snd_tplg_t *, snd_config_t *, void *),
- void *private)
+{
- const char *id;
- snd_config_iterator_t i, next;
- snd_config_t *n;
- int err = -EINVAL;
- if (snd_config_get_id(cfg, &id) < 0)
return -EINVAL;
- if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
fprintf(stderr, "error: compound type expected for %s", id);
It's not good to print an error unconditionally from a system library. Better to use SNDERR() macro.
+int snd_tplg_build(snd_tplg_t *tplg, const char *infile, const char *outfile) +{
- snd_config_t *cfg = NULL;
- int err = 0;
- /* delete any old output files */
- unlink(outfile);
- tplg->out_fd =
open(outfile, O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
- if (tplg->out_fd < 0) {
fprintf(stderr, "error: failed to open %s err %d\n",
outfile, -errno);
return -errno;
- }
- err = tplg_load_config(infile, &cfg);
- if (err < 0) {
fprintf(stderr, "error: failed to load topology file %s\n",
infile);
return err;
The outfile is left opened.
Takashi
Parse text lists (like enum values) and store for later attachment to other objects.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- src/topology/text.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/topology/text.c
diff --git a/src/topology/text.c b/src/topology/text.c new file mode 100644 index 0000000..d24d027 --- /dev/null +++ b/src/topology/text.c @@ -0,0 +1,99 @@ +/* + Copyright(c) 2014-2015 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + Authors: Mengdong Lin mengdong.lin@intel.com + Yao Jin yao.jin@intel.com + Liam Girdwood liam.r.girdwood@linux.intel.com + +*/ + +#include "list.h" +#include "tplg_local.h" + +#define TEXT_SIZE_MAX \ + (SND_SOC_TPLG_NUM_TEXTS * SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + +static int parse_text_values(snd_config_t *cfg, struct tplg_elem *elem) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + const char *value = NULL; + int j = 0; + + tplg_dbg(" Text Values: %s\n", elem->id); + + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + + if (j == SND_SOC_TPLG_NUM_TEXTS) { + tplg_dbg("error: text string number exceeds %d\n", j); + return -ENOMEM; + } + + /* get value */ + if (snd_config_get_string(n, &value) < 0) + continue; + + strncpy(&elem->texts[j][0], value, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + elem->texts[j][SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0; + tplg_dbg("\t%s\n", &elem->texts[j][0]); + + j++; + } + + return 0; +} + +/* Parse Text data. + * + * Object text strings. + * + * SectionText."text name" { + * + * Values [ + * + * ] + * } + */ +int tplg_parse_text(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_TEXT); + 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, "values") == 0) { + err = parse_text_values(n, elem); + if (err < 0) { + fprintf(stderr, "error: failed to parse text values"); + return err; + } + continue; + } + } + + return err; +}
At Wed, 1 Jul 2015 14:44:26 +0100, Liam Girdwood wrote:
+static int parse_text_values(snd_config_t *cfg, struct tplg_elem *elem) +{
- snd_config_iterator_t i, next;
- snd_config_t *n;
- const char *value = NULL;
- int j = 0;
- tplg_dbg(" Text Values: %s\n", elem->id);
- snd_config_for_each(i, next, cfg) {
n = snd_config_iterator_entry(i);
if (j == SND_SOC_TPLG_NUM_TEXTS) {
tplg_dbg("error: text string number exceeds %d\n", j);
return -ENOMEM;
}
/* get value */
if (snd_config_get_string(n, &value) < 0)
continue;
strncpy(&elem->texts[j][0], value,
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
elem->texts[j][SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0;
There are multiple calls like this, so maybe it's worth to create a simple helper to copy the ctl element id string instead of open coding at each place.
Takashi
Parse PCM configurations and capabilities. These can then be used to define the capabilities and config for FE DAI links, PCM devices and codec <-> codec style links.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- src/topology/pcm.c | 759 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 759 insertions(+) create mode 100644 src/topology/pcm.c
diff --git a/src/topology/pcm.c b/src/topology/pcm.c new file mode 100644 index 0000000..243472b --- /dev/null +++ b/src/topology/pcm.c @@ -0,0 +1,759 @@ +/* + Copyright(c) 2014-2015 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + Authors: Mengdong Lin mengdong.lin@intel.com + Yao Jin yao.jin@intel.com + Liam Girdwood liam.r.girdwood@linux.intel.com +*/ + +#include "list.h" +#include "tplg_local.h" + +struct tplg_elem *lookup_pcm_dai_stream(struct list_head *base, const char* id) +{ + struct list_head *pos, *npos; + struct tplg_elem *elem; + struct snd_soc_tplg_pcm_dai *pcm_dai; + + list_for_each_safe(pos, npos, base) { + + elem = list_entry(pos, struct tplg_elem, list); + if (elem->type != PARSER_TYPE_PCM) + return NULL; + + pcm_dai = elem->pcm; + + if (pcm_dai && (!strcmp(pcm_dai->capconf[0].caps.name, id) + || !strcmp(pcm_dai->capconf[1].caps.name, id))) + return elem; + } + + return NULL; +} + +/* copy referenced caps to the pcm */ +static void copy_pcm_caps(const char *id, struct snd_soc_tplg_stream_caps *caps, + struct tplg_elem *ref_elem) +{ + struct snd_soc_tplg_stream_caps *ref_caps = ref_elem->stream_caps; + + tplg_dbg("Copy pcm caps (%ld bytes) from '%s' to '%s' \n", + sizeof(*caps), ref_elem->id, id); + + memcpy((void*)caps, ref_caps, sizeof(*caps)); +} + +/* copy referenced config to the pcm */ +static void copy_pcm_config(const char *id, + struct snd_soc_tplg_stream_config *cfg, struct tplg_elem *ref_elem) +{ + struct snd_soc_tplg_stream_config *ref_cfg = ref_elem->stream_cfg; + + tplg_dbg("Copy pcm config (%ld bytes) from '%s' to '%s' \n", + sizeof(*cfg), ref_elem->id, id); + + memcpy((void*)cfg, ref_cfg, sizeof(*cfg)); +} + +/* check referenced config and caps for a pcm */ +static int tplg_build_pcm_cfg_caps(snd_tplg_t *tplg, struct tplg_elem *elem) +{ + struct tplg_elem *ref_elem = NULL; + struct snd_soc_tplg_pcm_cfg_caps *capconf; + struct snd_soc_tplg_pcm_dai *pcm_dai; + unsigned int i, j; + + switch (elem->type) { + case PARSER_TYPE_PCM: + pcm_dai = elem->pcm; + break; + case PARSER_TYPE_BE: + pcm_dai = elem->be; + break; + case PARSER_TYPE_CC: + pcm_dai = elem->cc; + break; + default: + return -EINVAL; + } + + for (i = 0; i < 2; i++) { + capconf = &pcm_dai->capconf[i]; + + ref_elem = tplg_elem_lookup(&tplg->pcm_caps_list, + capconf->caps.name, PARSER_TYPE_STREAM_CAPS); + + if (ref_elem != NULL) + copy_pcm_caps(elem->id, &capconf->caps, ref_elem); + + for (j = 0; j < capconf->num_configs; j++) { + ref_elem = tplg_elem_lookup(&tplg->pcm_config_list, + capconf->configs[j].name, + PARSER_TYPE_STREAM_CONFIG); + + if (ref_elem != NULL) + copy_pcm_config(elem->id, + &capconf->configs[j], + ref_elem); + } + } + + return 0; +} + +int tplg_build_pcm_dai(snd_tplg_t *tplg, unsigned int type) +{ + struct list_head *base, *pos, *npos; + struct tplg_elem *elem; + int err = 0; + + switch (type) { + case PARSER_TYPE_PCM: + base = &tplg->pcm_list; + break; + case PARSER_TYPE_BE: + base = &tplg->be_list; + break; + case PARSER_TYPE_CC: + base = &tplg->cc_list; + break; + default: + return -EINVAL; + } + + list_for_each_safe(pos, npos, base) { + + elem = list_entry(pos, struct tplg_elem, list); + if (elem->type != type) { + fprintf(stderr, "error: invalid elem '%s'\n", elem->id); + return -EINVAL; + } + + err = tplg_build_pcm_cfg_caps(tplg, elem); + if (err < 0) + return err; + } + + return 0; +} + +/* PCM stream configuration + * + * Describes the PCM configuration for playback and capture streams. + * + * config."name" { + * format "S24_LE" + * rate "48000" + * channels "2" + * tdm_slot "0xf" + * } + */ +static int tplg_parse_stream_cfg(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + snd_config_t *cfg, void *private) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + struct snd_soc_tplg_stream_config *sc = private; + struct snd_soc_tplg_stream *stream; + const char *id, *val; + snd_pcm_format_t format; + int ret; + + snd_config_get_id(cfg, &id); + + if (strcmp(id, "playback") == 0) + stream = &sc->playback; + else if (strcmp(id, "capture") == 0) + stream = &sc->capture; + else + return -EINVAL; + + tplg_dbg("\t%s:\n", id); + + stream->size = sizeof(*stream); + + snd_config_for_each(i, next, cfg) { + + n = snd_config_iterator_entry(i); + + if (snd_config_get_id(n, &id) < 0) + return -EINVAL; + + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + if (strcmp(id, "format") == 0) { + format = snd_pcm_format_value(val); + if (format == SND_PCM_FORMAT_UNKNOWN) { + fprintf(stderr, "error: unsupported stream format %s\n", + val); + return -EINVAL; + } + + stream->format = format; + tplg_dbg("\t\t%s: %s\n", id, val); + continue; + } + + if (strcmp(id, "rate") == 0) { + stream->rate = atoi(val); + tplg_dbg("\t\t%s: %d\n", id, stream->rate); + continue; + } + + if (strcmp(id, "channels") == 0) { + stream->channels = atoi(val); + tplg_dbg("\t\t%s: %d\n", id, stream->channels); + continue; + } + + if (strcmp(id, "tdm_slot") == 0) { + stream->tdm_slot = strtol(val, NULL, 16); + tplg_dbg("\t\t%s: 0x%x\n", id, stream->tdm_slot); + continue; + } + } + + return 0; +} + +/* Parse pcm configuration + * + * SectionPCMConfig."PCM config name" { + * + * config."playback" { + * + * } + * + * config."capture" { + * + * } + * } + */ +int tplg_parse_pcm_config(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) +{ + struct snd_soc_tplg_stream_config *sc; + struct tplg_elem *elem; + snd_config_iterator_t i, next; + snd_config_t *n; + const char *id; + int err; + + elem = tplg_elem_new_common(tplg, cfg, PARSER_TYPE_STREAM_CONFIG); + if (!elem) + return -ENOMEM; + + sc = elem->stream_cfg; + sc->size = elem->size; + + tplg_dbg(" PCM Config: %s\n", elem->id); + + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + /* skip comments */ + if (strcmp(id, "comment") == 0) + continue; + if (id[0] == '#') + continue; + + if (strcmp(id, "config") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_stream_cfg, sc); + if (err < 0) + return err; + continue; + } + } + + return 0; +} + +static int split_format(struct snd_soc_tplg_stream_caps *caps, char *str) +{ + char *s = NULL; + snd_pcm_format_t format; + int i = 0, ret; + + s = strtok(str, ","); + while ((s != NULL) && (i < SND_SOC_TPLG_MAX_FORMATS)) { + format = snd_pcm_format_value(s); + if (format == SND_PCM_FORMAT_UNKNOWN) { + fprintf(stderr, "error: unsupported stream format %s\n", s); + return -EINVAL; + } + + caps->formats[i] = format; + s = strtok(NULL, ", "); + i++; + } + + return 0; +} + +/* Parse pcm Capabilities + * + * SectionPCMCapabilities." PCM capabilities name" { + * + * formats "S24_LE,S16_LE" + * rate_min "48000" + * rate_max "48000" + * channels_min "2" + * channels_max "2" + * } + */ +int tplg_parse_pcm_caps(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) +{ + struct snd_soc_tplg_stream_caps *sc; + struct tplg_elem *elem; + snd_config_iterator_t i, next; + snd_config_t *n; + const char *id, *val; + char *s; + int err; + + elem = tplg_elem_new_common(tplg, cfg, PARSER_TYPE_STREAM_CAPS); + if (!elem) + return -ENOMEM; + + sc = elem->stream_caps; + sc->size = elem->size; + strncpy(sc->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + sc->name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0; + + tplg_dbg(" PCM Capabilities: %s\n", elem->id); + + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + /* skip comments */ + if (strcmp(id, "comment") == 0) + continue; + if (id[0] == '#') + continue; + + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + if (strcmp(id, "formats") == 0) { + s = strdup(val); + if (s == NULL) + return -ENOMEM; + + err = split_format(sc, s); + free(s); + + if (err < 0) + return err; + + tplg_dbg("\t\t%s: %s\n", id, val); + continue; + } + + if (strcmp(id, "rate_min") == 0) { + sc->rate_min = atoi(val); + tplg_dbg("\t\t%s: %d\n", id, sc->rate_min); + continue; + } + + if (strcmp(id, "rate_max") == 0) { + sc->rate_max = atoi(val); + tplg_dbg("\t\t%s: %d\n", id, sc->rate_max); + continue; + } + + if (strcmp(id, "channels_min") == 0) { + sc->channels_min = atoi(val); + tplg_dbg("\t\t%s: %d\n", id, sc->channels_min); + continue; + } + + if (strcmp(id, "channels_max") == 0) { + sc->channels_max = atoi(val); + tplg_dbg("\t\t%s: %d\n", id, sc->channels_max); + continue; + } + } + + return 0; +} + +static int tplg_parse_pcm_cfg(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + snd_config_t *cfg, void *private) +{ + struct snd_soc_tplg_pcm_cfg_caps *capconf = private; + struct snd_soc_tplg_stream_config *configs = capconf->configs; + unsigned int *num_configs = &capconf->num_configs; + const char *value; + + if (*num_configs == SND_SOC_TPLG_STREAM_CONFIG_MAX) + return -EINVAL; + + if (snd_config_get_string(cfg, &value) < 0) + return EINVAL; + + strncpy(configs[*num_configs].name, value, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + configs[*num_configs].name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0; + + *num_configs += 1; + + tplg_dbg("\t\t\t%s\n", value); + + return 0; +} + +/* Parse the cap and config of a pcm. + * + * pcm."name" { + * + * capabilities "System playback" + * + * configs [ + * "PCM 48k Stereo 24bit" + * "PCM 48k Stereo 16bit" + * ] + * } + */ +int tplg_parse_pcm_cap_cfg(snd_tplg_t *tplg, snd_config_t *cfg, + void *private) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + struct tplg_elem *elem = private; + struct snd_soc_tplg_pcm_dai *pcm_dai; + const char *id, *value; + int err, stream; + + if (elem->type == PARSER_TYPE_PCM) + pcm_dai = elem->pcm; + else if (elem->type == PARSER_TYPE_BE) + pcm_dai = elem->be; + else if (elem->type == PARSER_TYPE_CC) + pcm_dai = elem->cc; + else + return -EINVAL; + + snd_config_get_id(cfg, &id); + + tplg_dbg("\t%s:\n", id); + + if (strcmp(id, "playback") == 0) { + stream = SND_SOC_TPLG_STREAM_PLAYBACK; + pcm_dai->playback = 1; + } else if (strcmp(id, "capture") == 0) { + stream = SND_SOC_TPLG_STREAM_CAPTURE; + pcm_dai->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; + + strncpy(pcm_dai->capconf[stream].caps.name, value, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + pcm_dai->capconf[stream].caps.name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0; + + tplg_dbg("\t\t%s\n\t\t\t%s\n", id, value); + continue; + } + + if (strcmp(id, "configs") == 0) { + tplg_dbg("\t\tconfigs:\n"); + err = tplg_parse_compound(tplg, n, tplg_parse_pcm_cfg, + &pcm_dai->capconf[stream]); + if (err < 0) + return err; + continue; + } + } + + return 0; +} + +/* Parse pcm + * + * SectionPCM."System Pin" { + * + * index "1" + * + * # used for binding to the PCM + * ID "0" + * + * pcm."playback" { + * capabilities "System Playback" + * config "PCM 48k Stereo 24bit" + * config "PCM 48k Stereo 16bit" + * } + * + * pcm."capture" { + * capabilities "Analog Capture" + * config "PCM 48k Stereo 24bit" + * config "PCM 48k Stereo 16bit" + * config "PCM 48k 2P/4C 16bit" + * } + * } + */ +int tplg_parse_pcm(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) +{ + struct snd_soc_tplg_pcm_dai *pcm_dai; + struct tplg_elem *elem; + snd_config_iterator_t i, next; + snd_config_t *n; + const char *id, *val = NULL; + int err; + + elem = tplg_elem_new_common(tplg, cfg, PARSER_TYPE_PCM); + if (!elem) + return -ENOMEM; + + pcm_dai = elem->pcm; + pcm_dai->size = elem->size; + strncpy(pcm_dai->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + pcm_dai->name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0; + + tplg_dbg(" PCM: %s\n", elem->id); + + snd_config_for_each(i, next, cfg) { + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + /* skip comments */ + if (strcmp(id, "comment") == 0) + continue; + if (id[0] == '#') + continue; + + if (strcmp(id, "index") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + elem->index = atoi(val); + tplg_dbg("\t%s: %d\n", id, elem->index); + continue; + } + + if (strcmp(id, "ID") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + pcm_dai->id = atoi(val); + tplg_dbg("\t%s: %d\n", id, pcm_dai->id); + continue; + } + + if (strcmp(id, "pcm") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_pcm_cap_cfg, elem); + if (err < 0) + return err; + continue; + } + } + + return 0; +} + +/* Parse be + * + * SectionBE."SSP0-Codec" { + * + * index "1" + * + * # used for binding to the PCM + * ID "0" + * + * be."playback" { + * capabilities "System Playback" + * config "PCM 48k Stereo 24bit" + * config "PCM 48k Stereo 16bit" + * } + * + * be."capture" { + * capabilities "Analog Capture" + * config "PCM 48k Stereo 24bit" + * config "PCM 48k Stereo 16bit" + * config "PCM 48k 2P/4C 16bit" + * } + * } + */ +int tplg_parse_be(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) +{ + struct snd_soc_tplg_pcm_dai *pcm_dai; + struct tplg_elem *elem; + snd_config_iterator_t i, next; + snd_config_t *n; + const char *id, *val = NULL; + int err; + + elem = tplg_elem_new_common(tplg, cfg, PARSER_TYPE_BE); + if (!elem) + return -ENOMEM; + + pcm_dai = elem->be; + pcm_dai->size = elem->size; + strncpy(pcm_dai->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + pcm_dai->name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN] = 0; + + tplg_dbg(" BE: %s\n", elem->id); + + snd_config_for_each(i, next, cfg) { + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + /* skip comments */ + if (strcmp(id, "comment") == 0) + continue; + if (id[0] == '#') + continue; + + if (strcmp(id, "index") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + elem->index = atoi(val); + tplg_dbg("\t%s: %d\n", id, elem->index); + continue; + } + + if (strcmp(id, "ID") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + pcm_dai->id = atoi(val); + tplg_dbg("\t%s: %d\n", id, pcm_dai->id); + continue; + } + + if (strcmp(id, "be") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_pcm_cap_cfg, elem); + if (err < 0) + return err; + continue; + } + } + + return 0; +} + +/* Parse cc + * + * SectionCC."FM-Codec" { + * + * index "1" + * + * # used for binding to the CC link + * ID "0" + * + * # CC DAI link capabilities and supported configs + * cc."playback" { + * + * capabilities "System playback" + * + * configs [ + * "PCM 48k Stereo 16bit" + * ] + * } + * + * cc."capture" { + * + * capabilities "Analog capture" + * + * configs [ + * "PCM 48k Stereo 16bit" + * ] + * } + * } + */ +int tplg_parse_cc(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) +{ + struct snd_soc_tplg_pcm_dai *pcm_dai; + struct tplg_elem *elem; + snd_config_iterator_t i, next; + snd_config_t *n; + const char *id, *val = NULL; + int err; + + elem = tplg_elem_new_common(tplg, cfg, PARSER_TYPE_CC); + if (!elem) + return -ENOMEM; + + pcm_dai = elem->cc; + pcm_dai->size = elem->size; + + tplg_dbg(" CC: %s\n", elem->id); + + snd_config_for_each(i, next, cfg) { + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + /* skip comments */ + if (strcmp(id, "comment") == 0) + continue; + if (id[0] == '#') + continue; + + if (strcmp(id, "index") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + elem->index = atoi(val); + tplg_dbg("\t%s: %d\n", id, elem->index); + continue; + } + + if (strcmp(id, "ID") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + pcm_dai->id = atoi(val); + tplg_dbg("\t%s: %d\n", id, pcm_dai->id); + continue; + } + + if (strcmp(id, "cc") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_pcm_cap_cfg, elem); + if (err < 0) + return err; + continue; + } + } + + return 0; +}
At Wed, 1 Jul 2015 14:44:27 +0100, Liam Girdwood wrote:
+/* copy referenced caps to the pcm */ +static void copy_pcm_caps(const char *id, struct snd_soc_tplg_stream_caps *caps,
- struct tplg_elem *ref_elem)
+{
- struct snd_soc_tplg_stream_caps *ref_caps = ref_elem->stream_caps;
- tplg_dbg("Copy pcm caps (%ld bytes) from '%s' to '%s' \n",
sizeof(*caps), ref_elem->id, id);
- memcpy((void*)caps, ref_caps, sizeof(*caps));
This can be simply *caps = *ref_caps;
+/* copy referenced config to the pcm */ +static void copy_pcm_config(const char *id,
- struct snd_soc_tplg_stream_config *cfg, struct tplg_elem *ref_elem)
+{
- struct snd_soc_tplg_stream_config *ref_cfg = ref_elem->stream_cfg;
- tplg_dbg("Copy pcm config (%ld bytes) from '%s' to '%s' \n",
sizeof(*cfg), ref_elem->id, id);
- memcpy((void*)cfg, ref_cfg, sizeof(*cfg));
Ditto.
+int tplg_build_pcm_dai(snd_tplg_t *tplg, unsigned int type) +{
- struct list_head *base, *pos, *npos;
- struct tplg_elem *elem;
- int err = 0;
- switch (type) {
- case PARSER_TYPE_PCM:
base = &tplg->pcm_list;
break;
- case PARSER_TYPE_BE:
base = &tplg->be_list;
break;
- case PARSER_TYPE_CC:
base = &tplg->cc_list;
break;
- default:
return -EINVAL;
- }
- list_for_each_safe(pos, npos, base) {
Does it need to be the safe version?
elem = list_entry(pos, struct tplg_elem, list);
if (elem->type != type) {
fprintf(stderr, "error: invalid elem '%s'\n", elem->id);
return -EINVAL;
}
err = tplg_build_pcm_cfg_caps(tplg, elem);
if (err < 0)
return err;
- }
- return 0;
+}
+/* PCM stream configuration
- Describes the PCM configuration for playback and capture streams.
- config."name" {
format "S24_LE"
rate "48000"
channels "2"
tdm_slot "0xf"
}
Such a description should go to doxygen comment, no? Also, it'd be better to mention more clearly that "name" here corresponds to the stream direction.
+int tplg_parse_pcm(snd_tplg_t *tplg,
- snd_config_t *cfg, void *private ATTRIBUTE_UNUSED)
+{
- struct snd_soc_tplg_pcm_dai *pcm_dai;
- struct tplg_elem *elem;
- snd_config_iterator_t i, next;
- snd_config_t *n;
- const char *id, *val = NULL;
- int err;
- elem = tplg_elem_new_common(tplg, cfg, PARSER_TYPE_PCM);
- if (!elem)
return -ENOMEM;
- pcm_dai = elem->pcm;
- pcm_dai->size = elem->size;
- strncpy(pcm_dai->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
- pcm_dai->name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0;
- tplg_dbg(" PCM: %s\n", elem->id);
- snd_config_for_each(i, next, cfg) {
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
/* skip comments */
if (strcmp(id, "comment") == 0)
continue;
if (id[0] == '#')
continue;
if (strcmp(id, "index") == 0) {
if (snd_config_get_string(n, &val) < 0)
return -EINVAL;
elem->index = atoi(val);
tplg_dbg("\t%s: %d\n", id, elem->index);
continue;
}
if (strcmp(id, "ID") == 0) {
Only this word is capital letters while others are not? I don't mind too much, but just wondered, because currently the parser is case-sensitive.
Takashi
On Wed, 2015-07-01 at 18:14 +0200, Takashi Iwai wrote:
At Wed, 1 Jul 2015 14:44:27 +0100, Liam Girdwood wrote:
inue;
}
if (strcmp(id, "ID") == 0) {
Only this word is capital letters while others are not? I don't mind too much, but just wondered, because currently the parser is case-sensitive.
Oh, I think this is just historical. It's probably likely that one of the other developers had a preference for upper case or because it was the shortened form of the word. I change this to lower case though.
Liam
Parse operations so we can bind them to kcontrols in the kernel.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- src/topology/ops.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/topology/ops.c
diff --git a/src/topology/ops.c b/src/topology/ops.c new file mode 100644 index 0000000..d3f4a28 --- /dev/null +++ b/src/topology/ops.c @@ -0,0 +1,90 @@ +/* + Copyright(c) 2014-2015 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + Authors: Mengdong Lin mengdong.lin@intel.com + Yao Jin yao.jin@intel.com + Liam Girdwood liam.r.girdwood@linux.intel.com +*/ + +#include "list.h" +#include "tplg_local.h" + +/* mapping of kcontrol text names to types */ +static const struct map_elem control_map[] = { + {"volsw", SND_SOC_TPLG_CTL_VOLSW}, + {"volsw_sx", SND_SOC_TPLG_CTL_VOLSW_SX}, + {"volsw_xr_sx", SND_SOC_TPLG_CTL_VOLSW_XR_SX}, + {"enum", SND_SOC_TPLG_CTL_ENUM}, + {"bytes", SND_SOC_TPLG_CTL_BYTES}, + {"enum_value", SND_SOC_TPLG_CTL_ENUM_VALUE}, + {"range", SND_SOC_TPLG_CTL_RANGE}, + {"strobe", SND_SOC_TPLG_CTL_STROBE}, +}; + +static int lookup_ops(const char *c) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(control_map); i++) { + if (strcmp(control_map[i].name, c) == 0) + return control_map[i].id; + } + + /* cant find string name in our table so we use its ID number */ + return atoi(c); +} + +/* Parse Control operations. Ops can come from standard names above or + * bespoke driver controls with numbers >= 256 + * + * ops."name" { + * info "volsw" + * get "256" + * put "256" + * } + */ +int tplg_parse_ops(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + snd_config_t *cfg, void *private) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + struct snd_soc_tplg_ctl_hdr *hdr = private; + const char *id, *value; + + tplg_dbg("\tOps\n"); + hdr->size = sizeof(*hdr); + + snd_config_for_each(i, next, cfg) { + + n = snd_config_iterator_entry(i); + + /* get id */ + if (snd_config_get_id(n, &id) < 0) + continue; + + /* get value - try strings then ints */ + if (snd_config_get_string(n, &value) < 0) + continue; + + if (strcmp(id, "info") == 0) + hdr->ops.info = lookup_ops(value); + else if (strcmp(id, "put") == 0) + hdr->ops.put = lookup_ops(value); + else if (strcmp(id, "get") == 0) + hdr->ops.get = lookup_ops(value); + + tplg_dbg("\t\t%s = %s\n", id, value); + } + + return 0; +}
Parse private data and store for attachment to other objects. Data can come file or be locally defined as bytes, shorts or words.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- src/topology/data.c | 358 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100644 src/topology/data.c
diff --git a/src/topology/data.c b/src/topology/data.c new file mode 100644 index 0000000..7de62b4 --- /dev/null +++ b/src/topology/data.c @@ -0,0 +1,358 @@ +/* + Copyright(c) 2014-2015 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + Authors: Mengdong Lin mengdong.lin@intel.com + Yao Jin yao.jin@intel.com + Liam Girdwood liam.r.girdwood@linux.intel.com +*/ + +#include "list.h" +#include "tplg_local.h" + +/* Get Private data from a file. */ +static int tplg_parse_data_file(snd_config_t *cfg, struct tplg_elem *elem) +{ + struct snd_soc_tplg_private *priv = NULL; + const char *value = NULL; + char filename[MAX_FILE]; + char *env = getenv(ALSA_CONFIG_TPLG_VAR); + FILE *fp; + size_t size, bytes_read; + int ret = 0; + + tplg_dbg("data DataFile: %s\n", elem->id); + + if (snd_config_get_string(cfg, &value) < 0) + return -EINVAL; + + /* prepend alsa config directory to path */ + snprintf(filename, sizeof(filename), "%s/%s", + env ? env : ALSA_TPLG_DIR, value); + filename[sizeof(filename)-1] = '\0'; + + fp = fopen(filename, "r"); + if (fp == NULL) { + fprintf(stderr, "error: invalid data file path '%s'\n", + filename); + ret = -errno; + goto err; + } + + fseek(fp, 0L, SEEK_END); + size = ftell(fp); + fseek(fp, 0L, SEEK_SET); + if (size <= 0) { + fprintf(stderr, "error: invalid data file size %zu\n", size); + ret = -EINVAL; + goto err; + } + if (size > TPLG_MAX_PRIV_SIZE) { + fprintf(stderr, "error: data file too big %zu\n", size); + ret = -EINVAL; + goto err; + } + + priv = calloc(1, sizeof(*priv) + size); + if (!priv) { + ret = -ENOMEM; + goto err; + } + + bytes_read = fread(&priv->data, 1, size, fp); + if (bytes_read != size) { + ret = -errno; + goto err; + } + + elem->data = priv; + priv->size = size; + elem->size = sizeof(*priv) + size; + return 0; + +err: + if (priv) + free(priv); + return ret; +} + +static void dump_priv_data(struct tplg_elem *elem) +{ + struct snd_soc_tplg_private *priv = elem->data; + unsigned char *p = (unsigned char *)priv->data; + unsigned int i, j = 0; + + tplg_dbg(" elem size = %d, priv data size = %d\n", + elem->size, priv->size); + + for (i = 0; i < priv->size; i++) { + if (j++ % 8 == 0) + tplg_dbg("\n"); + + tplg_dbg(" 0x%x", *p++); + } + + tplg_dbg("\n\n"); +} + +static int get_hex_num(const char *str) +{ + char *tmp, *s = NULL; + int i = 0; + + tmp = strdup(str); + if (tmp == NULL) + return -ENOMEM; + + s = strtok(tmp, ","); + while (s != NULL) { + s = strtok(NULL, ","); + i++; + } + + free(tmp); + return i; +} + +static int write_hex(char *buf, char *str, int width) +{ + long val; + void *p = &val; + + errno = 0; + val = strtol(str, NULL, 16); + + if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) + || (errno != 0 && val == 0)) { + return -EINVAL; + } + + switch (width) { + case 1: + *(unsigned char *)buf = *(unsigned char *)p; + break; + case 2: + *(unsigned short *)buf = *(unsigned short *)p; + break; + case 4: + *(unsigned int *)buf = *(unsigned int *)p; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int copy_data_hex(char *data, int off, const char *str, int width) +{ + char *tmp, *s = NULL, *p = data; + int ret; + + tmp = strdup(str); + if (tmp == NULL) + return -ENOMEM; + + p += off; + s = strtok(tmp, ","); + + while (s != NULL) { + ret = write_hex(p, s, width); + if (ret < 0) { + free(tmp); + return ret; + } + + s = strtok(NULL, ","); + p += width; + } + + free(tmp); + return 0; +} + +static int tplg_parse_data_hex(snd_config_t *cfg, struct tplg_elem *elem, + int width) +{ + struct snd_soc_tplg_private *priv; + const char *value = NULL; + int size, esize, off, num; + int ret; + + tplg_dbg(" data: %s\n", elem->id); + + if (snd_config_get_string(cfg, &value) < 0) + return -EINVAL; + + num = get_hex_num(value); + size = num * width; + priv = elem->data; + + if (esize > TPLG_MAX_PRIV_SIZE) { + fprintf(stderr, "error: data too big %d\n", esize); + return -EINVAL; + } + + if (priv != NULL) { + off = priv->size; + esize = elem->size + size; + priv = realloc(priv, esize); + } else { + off = 0; + esize = sizeof(*priv) + size; + priv = calloc(1, esize); + } + + if (!priv) + return -ENOMEM; + + elem->data = priv; + priv->size += size; + elem->size = esize; + + ret = copy_data_hex(priv->data, off, value, width); + + dump_priv_data(elem); + return ret; +} + + +/* Parse Private data. + * + * Object private data can either be from file or defined as bytes, shorts, + * words. + * + * SectionData."data name" { + * + * DataFile "filename" + * bytes "0x12,0x34,0x56,0x78" + * shorts "0x1122,0x3344,0x5566,0x7788" + * words "0xaabbccdd,0x11223344,0x66aa77bb,0xefef1234" + * } + */ +int tplg_parse_data(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_DATA); + 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, "file") == 0) { + err = tplg_parse_data_file(n, elem); + if (err < 0) { + fprintf(stderr, "error: failed to parse data file\n"); + return err; + } + continue; + } + + if (strcmp(id, "bytes") == 0) { + err = tplg_parse_data_hex(n, elem, 1); + if (err < 0) { + fprintf(stderr, "error: failed to parse data bytes\n"); + return err; + } + continue; + } + + if (strcmp(id, "shorts") == 0) { + err = tplg_parse_data_hex(n, elem, 2); + if (err < 0) { + fprintf(stderr, "error: failed to parse data shorts\n"); + return err; + } + continue; + } + + if (strcmp(id, "words") == 0) { + err = tplg_parse_data_hex(n, elem, 4); + if (err < 0) { + fprintf(stderr, "error: failed to parse data words\n"); + return err; + } + continue; + } + } + + return err; +} + +/* copy private data into the bytes extended control */ +int tplg_copy_data(struct tplg_elem *elem, struct tplg_elem *ref) +{ + struct snd_soc_tplg_private *priv; + int priv_data_size; + + if (!ref) + return -EINVAL; + + tplg_dbg("Data '%s' used by '%s'\n", ref->id, elem->id); + priv_data_size = ref->data->size; + + switch (elem->type) { + case PARSER_TYPE_MIXER: + elem->mixer_ctrl = realloc(elem->mixer_ctrl, + elem->size + priv_data_size); + if (!elem->mixer_ctrl) + return -ENOMEM; + priv = &elem->mixer_ctrl->priv; + break; + + case PARSER_TYPE_ENUM: + elem->enum_ctrl = realloc(elem->enum_ctrl, + elem->size + priv_data_size); + if (!elem->enum_ctrl) + return -ENOMEM; + priv = &elem->enum_ctrl->priv; + break; + + case PARSER_TYPE_BYTES: + elem->bytes_ext = realloc(elem->bytes_ext, + elem->size + priv_data_size); + if (!elem->bytes_ext) + return -ENOMEM; + priv = &elem->bytes_ext->priv; + break; + + + case PARSER_TYPE_DAPM_WIDGET: + elem->widget = realloc(elem->widget, + elem->size + priv_data_size); + if (!elem->widget) + return -ENOMEM; + priv = &elem->widget->priv; + break; + + default: + fprintf(stderr, "elem '%s': type %d shall not have private data\n", + elem->id, elem->type); + return -EINVAL; + } + + elem->size += priv_data_size; + priv->size = priv_data_size; + memcpy(priv->data, ref->data->data, priv_data_size); + return 0; +}
At Wed, 1 Jul 2015 14:44:29 +0100, Liam Girdwood wrote:
+static int tplg_parse_data_file(snd_config_t *cfg, struct tplg_elem *elem) +{
- struct snd_soc_tplg_private *priv = NULL;
- const char *value = NULL;
- char filename[MAX_FILE];
- char *env = getenv(ALSA_CONFIG_TPLG_VAR);
- FILE *fp;
- size_t size, bytes_read;
- int ret = 0;
- tplg_dbg("data DataFile: %s\n", elem->id);
- if (snd_config_get_string(cfg, &value) < 0)
return -EINVAL;
- /* prepend alsa config directory to path */
- snprintf(filename, sizeof(filename), "%s/%s",
env ? env : ALSA_TPLG_DIR, value);
- filename[sizeof(filename)-1] = '\0';
Unlike strncpy(), snprintf() puts the NUL-character by itself, so this is superfluous.
+static int get_hex_num(const char *str) +{
- char *tmp, *s = NULL;
- int i = 0;
- tmp = strdup(str);
- if (tmp == NULL)
return -ENOMEM;
- s = strtok(tmp, ",");
- while (s != NULL) {
s = strtok(NULL, ",");
i++;
- }
- free(tmp);
- return i;
Hmm, this just counts the number of comma + 1, so you don't need to duplicate the string?
Takashi
On Wed, 2015-07-01 at 18:20 +0200, Takashi Iwai wrote:
+static int get_hex_num(const char *str) +{
- char *tmp, *s = NULL;
- int i = 0;
- tmp = strdup(str);
- if (tmp == NULL)
return -ENOMEM;
- s = strtok(tmp, ",");
- while (s != NULL) {
s = strtok(NULL, ",");
i++;
- }
- free(tmp);
- return i;
Hmm, this just counts the number of comma + 1, so you don't need to duplicate the string?
The string here is duplicated since strtok is destructive (it overwrites the delimiters with NULL) and the string is being used later on by the calling function.
Liam
On Tue, 07 Jul 2015 17:54:02 +0200, Liam Girdwood wrote:
On Wed, 2015-07-01 at 18:20 +0200, Takashi Iwai wrote:
+static int get_hex_num(const char *str) +{
- char *tmp, *s = NULL;
- int i = 0;
- tmp = strdup(str);
- if (tmp == NULL)
return -ENOMEM;
- s = strtok(tmp, ",");
- while (s != NULL) {
s = strtok(NULL, ",");
i++;
- }
- free(tmp);
- return i;
Hmm, this just counts the number of comma + 1, so you don't need to duplicate the string?
The string here is duplicated since strtok is destructive (it overwrites the delimiters with NULL) and the string is being used later on by the calling function.
Yes, but what I meant is something like below:
static int get_hex_num(const char *str) { int i = 0;
if (!*str) return 0; for (;;) { i++; str = strchr(str, ','); if (!str) return i; str++; } }
... so that it works without strdup().
But it seems that strtok() skips the starting delimiter or handles multiple delimiters as a single, so the result will become inconsistent. That is, all the following strings will give "a" and "b" via strtok(): a,b a,,b ,a,b a,b,
I guess you don't want to have an empty list element, right?
Takashi
On Tue, 2015-07-07 at 18:19 +0200, Takashi Iwai wrote:
On Tue, 07 Jul 2015 17:54:02 +0200, Liam Girdwood wrote:
On Wed, 2015-07-01 at 18:20 +0200, Takashi Iwai wrote:
+static int get_hex_num(const char *str) +{
- char *tmp, *s = NULL;
- int i = 0;
- tmp = strdup(str);
- if (tmp == NULL)
return -ENOMEM;
- s = strtok(tmp, ",");
- while (s != NULL) {
s = strtok(NULL, ",");
i++;
- }
- free(tmp);
- return i;
Hmm, this just counts the number of comma + 1, so you don't need to duplicate the string?
The string here is duplicated since strtok is destructive (it overwrites the delimiters with NULL) and the string is being used later on by the calling function.
Yes, but what I meant is something like below:
static int get_hex_num(const char *str) { int i = 0;
if (!*str) return 0; for (;;) { i++; str = strchr(str, ','); if (!str) return i; str++; } }
... so that it works without strdup().
But it seems that strtok() skips the starting delimiter or handles multiple delimiters as a single, so the result will become inconsistent. That is, all the following strings will give "a" and "b" via strtok(): a,b a,,b ,a,b a,b,
I guess you don't want to have an empty list element, right?
Lets ask the author :) but IMO an empty list should be skipped here.
Yao, what's your rational behind this code ?
Liam
On 2015/7/8 16:57, Liam Girdwood wrote:
On Tue, 2015-07-07 at 18:19 +0200, Takashi Iwai wrote:
On Tue, 07 Jul 2015 17:54:02 +0200, Liam Girdwood wrote:
On Wed, 2015-07-01 at 18:20 +0200, Takashi Iwai wrote:
+static int get_hex_num(const char *str) +{
- char *tmp, *s = NULL;
- int i = 0;
- tmp = strdup(str);
- if (tmp == NULL)
return -ENOMEM;
- s = strtok(tmp, ",");
- while (s != NULL) {
s = strtok(NULL, ",");
i++;
- }
- free(tmp);
- return i;
Hmm, this just counts the number of comma + 1, so you don't need to duplicate the string?
The string here is duplicated since strtok is destructive (it overwrites the delimiters with NULL) and the string is being used later on by the calling function.
Yes, but what I meant is something like below:
static int get_hex_num(const char *str) { int i = 0;
if (!*str) return 0; for (;;) { i++; str = strchr(str, ','); if (!str) return i; str++; } }
... so that it works without strdup().
But it seems that strtok() skips the starting delimiter or handles multiple delimiters as a single, so the result will become inconsistent. That is, all the following strings will give "a" and "b" via strtok(): a,b a,,b ,a,b a,b,
I guess you don't want to have an empty list element, right?
Lets ask the author :) but IMO an empty list should be skipped here.
Yao, what's your rational behind this code ?
Sorry for replying late, I just see this mail.
The get_hex_num() returns the number of hexadecimal in string.
Say the string is "0x12,0x34,0x56,0x78" or "0x12,0x34,0x56,0x78,", the get_hex_num() returns 4.
But if I use strchr, for above string, I get different number (3 or 4). That's the reason I choose the strtok().
I do this is because I think user may append the comma at the end of string.
Liam
On Wed, 08 Jul 2015 15:31:27 +0200, Jin, Yao wrote:
On 2015/7/8 16:57, Liam Girdwood wrote:
On Tue, 2015-07-07 at 18:19 +0200, Takashi Iwai wrote:
On Tue, 07 Jul 2015 17:54:02 +0200, Liam Girdwood wrote:
On Wed, 2015-07-01 at 18:20 +0200, Takashi Iwai wrote:
+static int get_hex_num(const char *str) +{
- char *tmp, *s = NULL;
- int i = 0;
- tmp = strdup(str);
- if (tmp == NULL)
return -ENOMEM;
- s = strtok(tmp, ",");
- while (s != NULL) {
s = strtok(NULL, ",");
i++;
- }
- free(tmp);
- return i;
Hmm, this just counts the number of comma + 1, so you don't need to duplicate the string?
The string here is duplicated since strtok is destructive (it overwrites the delimiters with NULL) and the string is being used later on by the calling function.
Yes, but what I meant is something like below:
static int get_hex_num(const char *str) { int i = 0;
if (!*str) return 0; for (;;) { i++; str = strchr(str, ','); if (!str) return i; str++; } }
... so that it works without strdup().
But it seems that strtok() skips the starting delimiter or handles multiple delimiters as a single, so the result will become inconsistent. That is, all the following strings will give "a" and "b" via strtok(): a,b a,,b ,a,b a,b,
I guess you don't want to have an empty list element, right?
Lets ask the author :) but IMO an empty list should be skipped here.
Yao, what's your rational behind this code ?
Sorry for replying late, I just see this mail.
The get_hex_num() returns the number of hexadecimal in string.
Say the string is "0x12,0x34,0x56,0x78" or "0x12,0x34,0x56,0x78,", the get_hex_num() returns 4.
But if I use strchr, for above string, I get different number (3 or 4). That's the reason I choose the strtok().
I do this is because I think user may append the comma at the end of string.
So, is this allowed intentionally as a valid syntax? As I showed, a string like ",,,0x12,,0x34,,,,0x56,0x78,," would be handled as a valid string.
The above may look strange, but the strtok() behavior is natural if you imagine to replace "," with a space.
Takashi
On Wed, 2015-07-08 at 16:14 +0200, Takashi Iwai wrote:
On Wed, 08 Jul 2015 15:31:27 +0200, Jin, Yao wrote:
On 2015/7/8 16:57, Liam Girdwood wrote:
On Tue, 2015-07-07 at 18:19 +0200, Takashi Iwai wrote:
On Tue, 07 Jul 2015 17:54:02 +0200, Liam Girdwood wrote:
On Wed, 2015-07-01 at 18:20 +0200, Takashi Iwai wrote:
> +static int get_hex_num(const char *str) > +{ > + char *tmp, *s = NULL; > + int i = 0; > + > + tmp = strdup(str); > + if (tmp == NULL) > + return -ENOMEM; > + > + s = strtok(tmp, ","); > + while (s != NULL) { > + s = strtok(NULL, ","); > + i++; > + } > + > + free(tmp); > + return i;
Hmm, this just counts the number of comma + 1, so you don't need to duplicate the string?
The string here is duplicated since strtok is destructive (it overwrites the delimiters with NULL) and the string is being used later on by the calling function.
Yes, but what I meant is something like below:
static int get_hex_num(const char *str) { int i = 0;
if (!*str) return 0; for (;;) { i++; str = strchr(str, ','); if (!str) return i; str++; } }
... so that it works without strdup().
But it seems that strtok() skips the starting delimiter or handles multiple delimiters as a single, so the result will become inconsistent. That is, all the following strings will give "a" and "b" via strtok(): a,b a,,b ,a,b a,b,
I guess you don't want to have an empty list element, right?
Lets ask the author :) but IMO an empty list should be skipped here.
Yao, what's your rational behind this code ?
Sorry for replying late, I just see this mail.
The get_hex_num() returns the number of hexadecimal in string.
Say the string is "0x12,0x34,0x56,0x78" or "0x12,0x34,0x56,0x78,", the get_hex_num() returns 4.
But if I use strchr, for above string, I get different number (3 or 4). That's the reason I choose the strtok().
I do this is because I think user may append the comma at the end of string.
Ok, lets make commas at the end illegal.
So, is this allowed intentionally as a valid syntax? As I showed, a string like ",,,0x12,,0x34,,,,0x56,0x78,," would be handled as a valid string.
The above may look strange, but the strtok() behavior is natural if you imagine to replace "," with a space.
So I'll fix this to be "0x01, 0x02, 0x03" syntax only. i.e. comma between each value and no comma at the end.
Liam
Parse DAPM objects including widgets and graph elements.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- src/topology/dapm.c | 562 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 562 insertions(+) create mode 100644 src/topology/dapm.c
diff --git a/src/topology/dapm.c b/src/topology/dapm.c new file mode 100644 index 0000000..1131f03 --- /dev/null +++ b/src/topology/dapm.c @@ -0,0 +1,562 @@ +/* + Copyright(c) 2014-2015 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + Authors: Mengdong Lin mengdong.lin@intel.com + Yao Jin yao.jin@intel.com + Liam Girdwood liam.r.girdwood@linux.intel.com +*/ + +#include "list.h" +#include "tplg_local.h" + +/* mapping of widget text names to types */ +static const struct map_elem widget_map[] = { + {"input", SND_SOC_TPLG_DAPM_INPUT}, + {"output", SND_SOC_TPLG_DAPM_OUTPUT}, + {"mux", SND_SOC_TPLG_DAPM_MUX}, + {"mixer", SND_SOC_TPLG_DAPM_MIXER}, + {"pga", SND_SOC_TPLG_DAPM_PGA}, + {"out_drv", SND_SOC_TPLG_DAPM_OUT_DRV}, + {"adc", SND_SOC_TPLG_DAPM_ADC}, + {"dac", SND_SOC_TPLG_DAPM_DAC}, + {"switch", SND_SOC_TPLG_DAPM_SWITCH}, + {"pre", SND_SOC_TPLG_DAPM_PRE}, + {"post", SND_SOC_TPLG_DAPM_POST}, + {"aif_in", SND_SOC_TPLG_DAPM_AIF_IN}, + {"aif_out", SND_SOC_TPLG_DAPM_AIF_OUT}, + {"dai_in", SND_SOC_TPLG_DAPM_DAI_IN}, + {"dai_out", SND_SOC_TPLG_DAPM_DAI_OUT}, + {"dai_link", SND_SOC_TPLG_DAPM_DAI_LINK}, +}; + +/* mapping of widget kcontrol text names to types */ +static const struct map_elem widget_control_map[] = { + {"volsw", SND_SOC_TPLG_DAPM_CTL_VOLSW}, + {"enum_double", SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE}, + {"enum_virt", SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT}, + {"enum_value", SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE}, +}; + +static int lookup_widget(const char *w) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(widget_map); i++) { + if (strcmp(widget_map[i].name, w) == 0) + return widget_map[i].id; + } + + return -EINVAL; +} + +static int tplg_parse_dapm_mixers(snd_config_t *cfg, struct tplg_elem *elem) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + const char *value = NULL; + + tplg_dbg(" DAPM Mixer Controls: %s\n", elem->id); + + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + + /* get value */ + if (snd_config_get_string(n, &value) < 0) + continue; + + tplg_ref_add(elem, PARSER_TYPE_MIXER, value); + tplg_dbg("\t\t %s\n", value); + } + + return 0; +} + +static int tplg_parse_dapm_enums(snd_config_t *cfg, struct tplg_elem *elem) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + const char *value = NULL; + + tplg_dbg(" DAPM Enum Controls: %s\n", elem->id); + + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + + /* get value */ + if (snd_config_get_string(n, &value) < 0) + continue; + + tplg_ref_add(elem, PARSER_TYPE_ENUM, value); + tplg_dbg("\t\t %s\n", value); + } + + return 0; +} + +/* move referenced controls to the widget */ +static int copy_dapm_control(struct tplg_elem *elem, struct tplg_elem *ref) +{ + struct snd_soc_tplg_dapm_widget *widget = elem->widget; + struct snd_soc_tplg_mixer_control *mixer_ctrl = ref->mixer_ctrl; + struct snd_soc_tplg_enum_control *enum_ctrl = ref->enum_ctrl; + + tplg_dbg("Control '%s' used by '%s'\n", ref->id, elem->id); + tplg_dbg("\tparent size: %d + %d -> %d, priv size -> %d\n", + elem->size, ref->size, elem->size + ref->size, + widget->priv.size); + + widget = realloc(widget, elem->size + ref->size); + if (!widget) + return -ENOMEM; + + elem->widget = widget; + + /* copy new widget at the end */ + if (ref->type == PARSER_TYPE_MIXER) + memcpy((void*)widget + elem->size, mixer_ctrl, ref->size); + else if (ref->type == PARSER_TYPE_ENUM) + memcpy((void*)widget + elem->size, enum_ctrl, ref->size); + + elem->size += ref->size; + widget->num_kcontrols++; + ref->compound_elem = 1; + return 0; +} + +/* check referenced controls for a widget */ +static int tplg_build_widget(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; + + switch (ref->type) { + case PARSER_TYPE_MIXER: + ref->elem = tplg_elem_lookup(&tplg->mixer_list, + ref->id, PARSER_TYPE_MIXER); + if (ref->elem) + err = copy_dapm_control(elem, ref->elem); + break; + + case PARSER_TYPE_ENUM: + ref->elem = tplg_elem_lookup(&tplg->enum_list, + ref->id, PARSER_TYPE_ENUM); + if (ref->elem) + err = copy_dapm_control(elem, ref->elem); + break; + + case PARSER_TYPE_DATA: + ref->elem = tplg_elem_lookup(&tplg->pdata_list, + ref->id, PARSER_TYPE_DATA); + if (ref->elem) + err = tplg_copy_data(elem, ref->elem); + break; + default: + break; + } + + if (!ref->elem) { + fprintf(stderr, "error: cannot find control '%s'" + " referenced by widget '%s'\n", + ref->id, elem->id); + return -EINVAL; + } + + if (err < 0) + return err; + } + + return 0; +} + +int tplg_build_widgets(snd_tplg_t *tplg) +{ + + struct list_head *base, *pos, *npos; + struct tplg_elem *elem; + int err; + + base = &tplg->widget_list; + list_for_each_safe(pos, npos, base) { + + elem = list_entry(pos, struct tplg_elem, list); + if (!elem->widget || elem->type != PARSER_TYPE_DAPM_WIDGET) { + fprintf(stderr, "error: invalid widget '%s'\n", + elem->id); + return -EINVAL; + } + + err = tplg_build_widget(tplg, elem); + if (err < 0) + return err; + } + + return 0; +} + +int tplg_build_routes(snd_tplg_t *tplg) +{ + struct list_head *base, *pos, *npos; + struct tplg_elem *elem; + struct snd_soc_tplg_dapm_graph_elem *route; + + base = &tplg->route_list; + + list_for_each_safe(pos, npos, base) { + elem = list_entry(pos, struct tplg_elem, list); + + if (!elem->route || elem->type != PARSER_TYPE_DAPM_GRAPH) { + fprintf(stderr, "error: invalid route '%s'\n", + elem->id); + return -EINVAL; + } + + route = elem->route; + tplg_dbg("\nCheck route: sink '%s', control '%s', source '%s'\n", + route->sink, route->control, route->source); + + /* validate sink */ + if (strlen(route->sink) <= 0) { + fprintf(stderr, "error: no sink\n"); + return -EINVAL; + + } + if (!tplg_elem_lookup(&tplg->widget_list, route->sink, + PARSER_TYPE_DAPM_WIDGET)) { + fprintf(stderr, "warning: undefined sink widget/stream '%s'\n", + route->sink); + } + + /* validate control name */ + if (strlen(route->control)) { + if (!tplg_elem_lookup(&tplg->mixer_list, + route->control, PARSER_TYPE_MIXER) && + !tplg_elem_lookup(&tplg->enum_list, + route->control, PARSER_TYPE_ENUM)) { + fprintf(stderr, "warning: Undefined mixer/enum control '%s'\n", + route->control); + } + } + + /* validate source */ + if (strlen(route->source) <= 0) { + fprintf(stderr, "error: no source\n"); + return -EINVAL; + + } + if (!tplg_elem_lookup(&tplg->widget_list, route->source, + PARSER_TYPE_DAPM_WIDGET)) { + fprintf(stderr, "warning: Undefined source widget/stream '%s'\n", + route->source); + } + } + + return 0; +} + +#define LINE_SIZE 1024 + +/* line is defined as '"source, control, sink"' */ +static int tplg_parse_line(const char *text, + struct snd_soc_tplg_dapm_graph_elem *line) +{ + char buf[LINE_SIZE]; + unsigned int len, i; + const char *source = NULL, *sink = NULL, *control = NULL; + + strncpy(buf, text, LINE_SIZE); + buf[LINE_SIZE - 1] = 0; + + len = strlen(buf); + if (len <= 2) { + fprintf(stderr, "error: invalid route "%s"\n", buf); + return -EINVAL; + } + + /* find first , */ + for (i = 1; i < len; i++) { + if (buf[i] == ',') + goto second; + } + fprintf(stderr, "error: invalid route "%s"\n", buf); + return -EINVAL; + +second: + /* find second , */ + sink = buf; + control = &buf[i + 2]; + buf[i] = 0; + + for (; i < len; i++) { + if (buf[i] == ',') + goto done; + } + + fprintf(stderr, "error: invalid route "%s"\n", buf); + return -EINVAL; + +done: + buf[i] = 0; + source = &buf[i + 2]; + + strcpy(line->source, source); + strcpy(line->control, control); + strcpy(line->sink, sink); + return 0; +} + + +static int tplg_parse_routes(snd_tplg_t *tplg, snd_config_t *cfg) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + struct tplg_elem *elem; + struct snd_soc_tplg_dapm_graph_elem *line = NULL; + int err; + + snd_config_for_each(i, next, cfg) { + const char *val; + + n = snd_config_iterator_entry(i); + if (snd_config_get_string(n, &val) < 0) + continue; + + elem = tplg_elem_new(); + if (!elem) + return -ENOMEM; + + list_add_tail(&elem->list, &tplg->route_list); + strcpy(elem->id, "line"); + elem->type = PARSER_TYPE_DAPM_GRAPH; + elem->size = sizeof(*line); + + line = calloc(1, sizeof(*line)); + if (!line) + return -ENOMEM; + + elem->route = line; + + err = tplg_parse_line(val, line); + if (err < 0) + return err; + + tplg_dbg("route: sink '%s', control '%s', source '%s'\n", + line->sink, line->control, line->source); + } + + return 0; +} + +int tplg_parse_dapm_graph(snd_tplg_t *tplg, snd_config_t *cfg, + void *private ATTRIBUTE_UNUSED) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + int err; + const char *graph_id; + + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { + fprintf(stderr, "error: compound is expected for dapm graph definition\n"); + return -EINVAL; + } + + snd_config_get_id(cfg, &graph_id); + + snd_config_for_each(i, next, cfg) { + const char *id; + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) { + continue; + } + + if (strcmp(id, "lines") == 0) { + err = tplg_parse_routes(tplg, n); + if (err < 0) { + fprintf(stderr, "error: failed to parse dapm graph %s\n", + graph_id); + return err; + } + continue; + } + } + + return 0; +} + +/* DAPM Widget. + * + * SectionWidget."widget name" { + * + * index "1" + * type "aif_in" + * no_pm "true" + * shift "0" + * invert "1 + * mixer "name" + * enum "name" + * data "name" + * } + */ +int tplg_parse_dapm_widget(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) +{ + struct snd_soc_tplg_dapm_widget *widget; + struct tplg_elem *elem; + snd_config_iterator_t i, next; + snd_config_t *n; + const char *id, *val = NULL; + int widget_type, err; + + elem = tplg_elem_new_common(tplg, cfg, PARSER_TYPE_DAPM_WIDGET); + if (!elem) + return -ENOMEM; + + tplg_dbg(" Widget: %s\n", elem->id); + + widget = elem->widget; + strncpy(widget->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + widget->name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0; + widget->size = elem->size; + + 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, "type") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + widget_type = lookup_widget(val); + if (widget_type < 0){ + fprintf(stderr, "Widget '%s': Unsupported widget type %s\n", + elem->id, val); + return -EINVAL; + } + + widget->id = widget_type; + tplg_dbg("\t%s: %s\n", id, val); + continue; + } + + if (strcmp(id, "no_pm") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + if (strcmp(val, "true") == 0) + widget->reg = -1; + + tplg_dbg("\t%s: %s\n", id, val); + continue; + } + + if (strcmp(id, "shift") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + widget->shift = atoi(val); + tplg_dbg("\t%s: %d\n", id, widget->shift); + continue; + } + + if (strcmp(id, "invert") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + widget->invert = atoi(val); + tplg_dbg("\t%s: %d\n", id, widget->invert); + continue; + } + + if (strcmp(id, "subseq") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + widget->subseq= atoi(val); + tplg_dbg("\t%s: %d\n", id, widget->subseq); + continue; + } + + if (strcmp(id, "event_type") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + widget->event_type = atoi(val); + tplg_dbg("\t%s: %d\n", id, widget->event_type); + continue; + } + + if (strcmp(id, "event_flags") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + widget->event_flags = atoi(val); + tplg_dbg("\t%s: %d\n", id, widget->event_flags); + continue; + } + + if (strcmp(id, "enum") == 0) { + err = tplg_parse_dapm_enums(n, elem); + if (err < 0) + return err; + + continue; + } + + if (strcmp(id, "mixer") == 0) { + err = tplg_parse_dapm_mixers(n, elem); + 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; +}
Add support to parse mixers, enums and byte controls.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- 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 mengdong.lin@intel.com + Yao Jin yao.jin@intel.com + Liam Girdwood liam.r.girdwood@linux.intel.com +*/ + +#include "list.h" +#include "tplg_local.h" + +/* 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; +}
Add support for parsing channel map to control registers.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- src/topology/channel.c | 128 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 src/topology/channel.c
diff --git a/src/topology/channel.c b/src/topology/channel.c new file mode 100644 index 0000000..e33c70d --- /dev/null +++ b/src/topology/channel.c @@ -0,0 +1,128 @@ +/* + Copyright(c) 2014-2015 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + Authors: Mengdong Lin mengdong.lin@intel.com + Yao Jin yao.jin@intel.com + Liam Girdwood liam.r.girdwood@linux.intel.com +*/ + +#include "list.h" +#include "tplg_local.h" + +/* mapping of channel text names to types */ +static const struct map_elem channel_map[] = { + {"mono", SNDRV_CHMAP_MONO}, /* mono stream */ + {"fl", SNDRV_CHMAP_FL}, /* front left */ + {"fr", SNDRV_CHMAP_FR}, /* front right */ + {"rl", SNDRV_CHMAP_RL}, /* rear left */ + {"rr", SNDRV_CHMAP_RR}, /* rear right */ + {"fc", SNDRV_CHMAP_FC}, /* front center */ + {"lfe", SNDRV_CHMAP_LFE}, /* LFE */ + {"sl", SNDRV_CHMAP_SL}, /* side left */ + {"sr", SNDRV_CHMAP_SR}, /* side right */ + {"rc", SNDRV_CHMAP_RC}, /* rear center */ + {"flc", SNDRV_CHMAP_FLC}, /* front left center */ + {"frc", SNDRV_CHMAP_FRC}, /* front right center */ + {"rlc", SNDRV_CHMAP_RLC}, /* rear left center */ + {"rrc", SNDRV_CHMAP_RRC}, /* rear right center */ + {"flw", SNDRV_CHMAP_FLW}, /* front left wide */ + {"frw", SNDRV_CHMAP_FRW}, /* front right wide */ + {"flh", SNDRV_CHMAP_FLH}, /* front left high */ + {"fch", SNDRV_CHMAP_FCH}, /* front center high */ + {"frh", SNDRV_CHMAP_FRH}, /* front right high */ + {"tc", SNDRV_CHMAP_TC}, /* top center */ + {"tfl", SNDRV_CHMAP_TFL}, /* top front left */ + {"tfr", SNDRV_CHMAP_TFR}, /* top front right */ + {"tfc", SNDRV_CHMAP_TFC}, /* top front center */ + {"trl", SNDRV_CHMAP_TRL}, /* top rear left */ + {"trr", SNDRV_CHMAP_TRR}, /* top rear right */ + {"trc", SNDRV_CHMAP_TRC}, /* top rear center */ + {"tflc", SNDRV_CHMAP_TFLC}, /* top front left center */ + {"tfrc", SNDRV_CHMAP_TFRC}, /* top front right center */ + {"tsl", SNDRV_CHMAP_TSL}, /* top side left */ + {"tsr", SNDRV_CHMAP_TSR}, /* top side right */ + {"llfe", SNDRV_CHMAP_LLFE}, /* left LFE */ + {"rlfe", SNDRV_CHMAP_RLFE}, /* right LFE */ + {"bc", SNDRV_CHMAP_BC}, /* bottom center */ + {"blc", SNDRV_CHMAP_BLC}, /* bottom left center */ + {"brc", SNDRV_CHMAP_BRC}, /* bottom right center */ +}; + + +static int lookup_channel(const char *c) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(channel_map); i++) { + if (strcasecmp(channel_map[i].name, c) == 0) { + return channel_map[i].id; + } + } + + return -EINVAL; +} + +/* Parse a channel. + * + * channel."channel_map.name" { + * reg "0" (register) + * shift "0" (shift) + * } + */ +int tplg_parse_channel(snd_tplg_t *tplg, + snd_config_t *cfg, void *private) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + struct snd_soc_tplg_channel *channel = private; + const char *id, *value; + + if (tplg->channel_idx >= SND_SOC_TPLG_MAX_CHAN) + return -EINVAL; + + channel += tplg->channel_idx; + snd_config_get_id(cfg, &id); + tplg_dbg("\tChannel %s at index %d\n", id, tplg->channel_idx); + + channel->id = lookup_channel(id); + if (channel->id < 0) { + fprintf(stderr, "error: invalid channel %s\n", id); + return -EINVAL; + } + + channel->size = sizeof(*channel); + tplg_dbg("\tChan %s = %d\n", id, channel->id); + + snd_config_for_each(i, next, cfg) { + + n = snd_config_iterator_entry(i); + + /* get id */ + if (snd_config_get_id(n, &id) < 0) + continue; + + /* get value */ + if (snd_config_get_string(n, &value) < 0) + continue; + + if (strcmp(id, "reg") == 0) + channel->reg = atoi(value); + else if (strcmp(id, "shift") == 0) + channel->shift = atoi(value); + + tplg_dbg("\t\t%s = %s\n", id, value); + } + + tplg->channel_idx++; + return 0; +}
Build the binary output file from all the locally parsed objects and elements.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- src/topology/builder.c | 275 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 src/topology/builder.c
diff --git a/src/topology/builder.c b/src/topology/builder.c new file mode 100644 index 0000000..12adc81 --- /dev/null +++ b/src/topology/builder.c @@ -0,0 +1,275 @@ +/* + Copyright(c) 2014-2015 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + Authors: Mengdong Lin mengdong.lin@intel.com + Yao Jin yao.jin@intel.com + Liam Girdwood liam.r.girdwood@linux.intel.com +*/ + +#include "list.h" +#include "tplg_local.h" + +/* verbose output detailing each object size and file position */ +static void verbose(snd_tplg_t *tplg, const char *fmt, ...) +{ + int offset; + va_list va; + + if (!tplg->verbose) + return; + + offset = lseek(tplg->out_fd, 0, SEEK_CUR); + + va_start(va, fmt); + fprintf(stdout, "0x%6.6x/%6.6d -", offset, offset); + vfprintf(stdout, fmt, va); + va_end(va); +} + +/* write out block header to output file */ +static int write_block_header(snd_tplg_t *tplg, unsigned int type, + unsigned int vendor_type, unsigned int version, unsigned int index, + size_t payload_size, int count) +{ + struct snd_soc_tplg_hdr hdr; + size_t bytes; + int offset = lseek(tplg->out_fd, 0, SEEK_CUR); + + memset(&hdr, 0, sizeof(hdr)); + hdr.magic = SND_SOC_TPLG_MAGIC; + hdr.abi = SND_SOC_TPLG_ABI_VERSION; + hdr.type = type; + hdr.vendor_type = vendor_type; + hdr.version = version; + hdr.payload_size = payload_size; + hdr.index = index; + hdr.size = sizeof(hdr); + hdr.count = count; + + /* make sure file offset is aligned with the calculated HDR offset */ + if ((unsigned int)offset != tplg->next_hdr_pos) { + fprintf(stderr, "error: New header is at offset 0x%x but file" + " offset 0x%x is %s by %d bytes\n", + tplg->next_hdr_pos, offset, + (unsigned int)offset > tplg->next_hdr_pos ? "ahead" : "behind", + abs(offset - tplg->next_hdr_pos)); + exit(-EINVAL); + } + + verbose(tplg, " header type %d size 0x%lx/%ld vendor %d " + "version %d\n", type, (long unsigned int)payload_size, + (long int)payload_size, vendor_type, version); + + tplg->next_hdr_pos += hdr.payload_size + sizeof(hdr); + + bytes = write(tplg->out_fd, &hdr, sizeof(hdr)); + if (bytes != sizeof(hdr)) { + fprintf(stderr, "error: can't write section header %lu\n", + (long unsigned int)bytes); + return bytes; + } + + return bytes; +} + +static int write_elem_block(snd_tplg_t *tplg, + struct list_head *base, int size, int tplg_type, const char *obj_name) +{ + struct list_head *pos, *npos; + struct tplg_elem *elem; + int ret, wsize = 0, count = 0; + + /* count number of elements */ + list_for_each_safe(pos, npos, base) + count++; + + /* write the header for this block */ + ret = write_block_header(tplg, tplg_type, 0, + SND_SOC_TPLG_ABI_VERSION, 0, size, count); + if (ret < 0) { + fprintf(stderr, "error: failed to write %s block %d\n", + obj_name, ret); + return ret; + } + + /* write each elem to block */ + list_for_each_safe(pos, npos, base) { + + elem = list_entry(pos, struct tplg_elem, list); + + /* compound elems have already been copied to other elems */ + if (elem->compound_elem) + continue; + + if (elem->type != PARSER_TYPE_DAPM_GRAPH) + verbose(tplg, " %s '%s': write %d bytes\n", + obj_name, elem->id, elem->size); + else + verbose(tplg, " %s '%s': write %d bytes\n", + obj_name, elem->route->source, elem->size); + + count = write(tplg->out_fd, elem->obj, elem->size); + if (count < 0) { + fprintf(stderr, "error: failed to write %s %d\n", + obj_name, ret); + return ret; + } + + wsize += count; + } + + /* make sure we have written the correct size */ + if (wsize != size) { + fprintf(stderr, "error: size mismatch. Expected %d wrote %d\n", + size, wsize); + return -EIO; + } + + return 0; +} + +static int calc_block_size(struct list_head *base) +{ + struct list_head *pos, *npos; + struct tplg_elem *elem; + int size = 0; + + list_for_each_safe(pos, npos, base) { + + elem = list_entry(pos, struct tplg_elem, list); + + /* compound elems have already been copied to other elems */ + if (elem->compound_elem) + continue; + + size += elem->size; + } + + return size; +} + +static int write_block(snd_tplg_t *tplg, struct list_head *base, + int type) +{ + int size; + + /* calculate the block size in bytes for all elems in this list */ + size = calc_block_size(base); + if (size <= 0) + return size; + + verbose(tplg, " block size for type %d is %d\n", type, size); + + /* write each elem for this block */ + switch (type) { + case PARSER_TYPE_MIXER: + return write_elem_block(tplg, base, size, + SND_SOC_TPLG_TYPE_MIXER, "mixer"); + case PARSER_TYPE_BYTES: + return write_elem_block(tplg, base, size, + SND_SOC_TPLG_TYPE_BYTES, "bytes"); + case PARSER_TYPE_ENUM: + return write_elem_block(tplg, base, size, + SND_SOC_TPLG_TYPE_ENUM, "enum"); + case PARSER_TYPE_DAPM_GRAPH: + return write_elem_block(tplg, base, size, + SND_SOC_TPLG_TYPE_DAPM_GRAPH, "route"); + case PARSER_TYPE_DAPM_WIDGET: + return write_elem_block(tplg, base, size, + SND_SOC_TPLG_TYPE_DAPM_WIDGET, "widget"); + case PARSER_TYPE_PCM: + return write_elem_block(tplg, base, size, + SND_SOC_TPLG_TYPE_PCM, "pcm"); + case PARSER_TYPE_BE: + return write_elem_block(tplg, base, size, + SND_SOC_TPLG_TYPE_DAI_LINK, "be"); + case PARSER_TYPE_CC: + return write_elem_block(tplg, base, size, + SND_SOC_TPLG_TYPE_DAI_LINK, "cc"); + default: + return -EINVAL; + } + + return 0; +} + +int tplg_write_data(snd_tplg_t *tplg) +{ + int ret; + + /* write mixer elems. */ + ret = write_block(tplg, &tplg->mixer_list, + PARSER_TYPE_MIXER); + if (ret < 0) { + fprintf(stderr, "failed to write control elems %d\n", ret); + return ret; + } + + /* write enum control elems. */ + ret = write_block(tplg, &tplg->enum_list, + PARSER_TYPE_ENUM); + if (ret < 0) { + fprintf(stderr, "failed to write control elems %d\n", ret); + return ret; + } + + /* write bytes extended control elems. */ + ret = write_block(tplg, &tplg->bytes_ext_list, + PARSER_TYPE_BYTES); + if (ret < 0) { + fprintf(stderr, "failed to write control elems %d\n", ret); + return ret; + } + + /* write widget elems */ + ret = write_block(tplg, &tplg->widget_list, + PARSER_TYPE_DAPM_WIDGET); + if (ret < 0) { + fprintf(stderr, "failed to write widget elems %d\n", ret); + return ret; + } + + /* write pcm elems */ + ret = write_block(tplg, &tplg->pcm_list, + PARSER_TYPE_PCM); + if (ret < 0) { + fprintf(stderr, "failed to write pcm elems %d\n", ret); + return ret; + } + + /* write be elems */ + ret = write_block(tplg, &tplg->be_list, + PARSER_TYPE_BE); + if (ret < 0) { + fprintf(stderr, "failed to write be elems %d\n", ret); + return ret; + } + + /* write cc elems */ + ret = write_block(tplg, &tplg->cc_list, + PARSER_TYPE_CC); + if (ret < 0) { + fprintf(stderr, "failed to write cc elems %d\n", ret); + return ret; + } + + /* write route elems */ + ret = write_block(tplg, &tplg->route_list, + PARSER_TYPE_DAPM_GRAPH); + if (ret < 0) { + fprintf(stderr, "failed to write graph elems %d\n", ret); + return ret; + } + + return 0; +}
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- configure.ac | 11 ++++++++++- include/Makefile.am | 4 ++++ src/Makefile.am | 7 +++++++ src/topology/Makefile.am | 19 +++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/topology/Makefile.am
diff --git a/configure.ac b/configure.ac index 9621d4e..a482b3e 100644 --- a/configure.ac +++ b/configure.ac @@ -380,6 +380,9 @@ AC_ARG_ENABLE(seq, AC_ARG_ENABLE(ucm, AS_HELP_STRING([--disable-ucm], [disable the use-case-manager component]), [build_ucm="$enableval"], [build_ucm="yes"]) +AC_ARG_ENABLE(topology, + AS_HELP_STRING([--disable-topology], [disable the DSP topology component]), + [build_topology="$enableval"], [build_topology="yes"]) AC_ARG_ENABLE(alisp, AS_HELP_STRING([--disable-alisp], [disable the alisp component]), [build_alisp="$enableval"], [build_alisp="yes"]) @@ -422,6 +425,7 @@ AM_CONDITIONAL([BUILD_RAWMIDI], [test x$build_rawmidi = xyes]) AM_CONDITIONAL([BUILD_HWDEP], [test x$build_hwdep = xyes]) AM_CONDITIONAL([BUILD_SEQ], [test x$build_seq = xyes]) AM_CONDITIONAL([BUILD_UCM], [test x$build_ucm = xyes]) +AM_CONDITIONAL([BUILD_TOPOLOGY], [test x$build_topology = xyes]) AM_CONDITIONAL([BUILD_ALISP], [test x$build_alisp = xyes]) AM_CONDITIONAL([BUILD_PYTHON], [test x$build_python = xyes])
@@ -443,6 +447,9 @@ fi if test "$build_ucm" = "yes"; then AC_DEFINE([BUILD_UCM], "1", [Build UCM component]) fi +if test "$build_topology" = "yes"; then + AC_DEFINE([BUILD_TOPOLOGY], "1", [Build DSP Topology component]) +fi
dnl PCM Plugins
@@ -643,7 +650,7 @@ AC_OUTPUT(Makefile doc/Makefile doc/pictures/Makefile doc/doxygen.cfg \ src/pcm/Makefile src/pcm/scopes/Makefile \ src/rawmidi/Makefile src/timer/Makefile \ src/hwdep/Makefile src/seq/Makefile src/ucm/Makefile \ - src/alisp/Makefile \ + src/alisp/Makefile src/topology/Makefile \ src/conf/Makefile src/conf/alsa.conf.d/Makefile \ src/conf/cards/Makefile \ src/conf/pcm/Makefile \ @@ -656,6 +663,8 @@ AC_OUTPUT(Makefile doc/Makefile doc/pictures/Makefile doc/doxygen.cfg \ src/conf/ucm/PAZ00/Makefile \ src/conf/ucm/GoogleNyan/Makefile \ src/conf/ucm/broadwell-rt286/Makefile \ + src/conf/topology/Makefile \ + src/conf/topology/broadwell/Makefile \ modules/Makefile modules/mixer/Makefile modules/mixer/simple/Makefile \ alsalisp/Makefile aserver/Makefile \ test/Makefile test/lsb/Makefile \ diff --git a/include/Makefile.am b/include/Makefile.am index 4baa03a..ff931fd 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -50,6 +50,10 @@ if BUILD_UCM alsainclude_HEADERS += use-case.h endif
+if BUILD_TOPOLOGY +alsainclude_HEADERS += topology.h +endif + if BUILD_ALISP alsainclude_HEADERS += alisp.h endif diff --git a/src/Makefile.am b/src/Makefile.am index fa255ff..57686a6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -42,6 +42,10 @@ if BUILD_UCM SUBDIRS += ucm libasound_la_LIBADD += ucm/libucm.la endif +if BUILD_TOPOLOGY +SUBDIRS += topology +libasound_la_LIBADD += topology/libtopology.la +endif if BUILD_ALISP SUBDIRS += alisp libasound_la_LIBADD += alisp/libalisp.la @@ -81,6 +85,9 @@ seq/libseq.la: ucm/libucm.la: $(MAKE) -C ucm libucm.la
+topology/libtopology.la: + $(MAKE) -C topology libtopology.la + instr/libinstr.la: $(MAKE) -C instr libinstr.la
diff --git a/src/topology/Makefile.am b/src/topology/Makefile.am new file mode 100644 index 0000000..3fb8bf7 --- /dev/null +++ b/src/topology/Makefile.am @@ -0,0 +1,19 @@ +EXTRA_LTLIBRARIES = libtopology.la + +libtopology_la_SOURCES =\ + parser.c \ + builder.c \ + ctl.c \ + dapm.c \ + pcm.c \ + data.c \ + text.c \ + channel.c \ + ops.c \ + elem.c + +noinst_HEADERS = tplg_local.h + +all: libtopology.la + +AM_CPPFLAGS=-I$(top_srcdir)/include
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- doc/doxygen.cfg.in | 7 +++++-- doc/index.doxygen | 1 + 2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/doc/doxygen.cfg.in b/doc/doxygen.cfg.in index 043e75b..92bd52b 100644 --- a/doc/doxygen.cfg.in +++ b/doc/doxygen.cfg.in @@ -29,6 +29,7 @@ INPUT = @top_srcdir@/doc/index.doxygen \ @top_srcdir@/include/control_external.h \ @top_srcdir@/include/mixer.h \ @top_srcdir@/include/use-case.h \ + @top_srcdir@/include/topology.h \ @top_srcdir@/src/error.c \ @top_srcdir@/src/dlmisc.c \ @top_srcdir@/src/async.c \ @@ -78,7 +79,8 @@ INPUT = @top_srcdir@/doc/index.doxygen \ @top_srcdir@/src/timer \ @top_srcdir@/src/hwdep \ @top_srcdir@/src/seq \ - @top_srcdir@/src/ucm + @top_srcdir@/src/ucm \ + @top_srcdir@/src/topology EXCLUDE = @top_srcdir@/src/control/control_local.h \ @top_srcdir@/src/pcm/atomic.h \ @top_srcdir@/src/pcm/interval.h \ @@ -94,7 +96,8 @@ EXCLUDE = @top_srcdir@/src/control/control_local.h \ @top_srcdir@/src/mixer/mixer_local.h \ @top_srcdir@/src/rawmidi/rawmidi_local.h \ @top_srcdir@/src/seq/seq_local.h \ - @top_srcdir@/src/ucm/ucm_local.h + @top_srcdir@/src/ucm/ucm_local.h \ + @top_srcdir@/src/topology/tplg_local.h RECURSIVE = YES FILE_PATTERNS = *.c *.h EXAMPLE_PATH = @top_srcdir@/test diff --git a/doc/index.doxygen b/doc/index.doxygen index 7d049fe..b40c75a 100644 --- a/doc/index.doxygen +++ b/doc/index.doxygen @@ -41,6 +41,7 @@ may be placed in the library code instead of the kernel driver.</P> <LI>Page \ref timer explains the design of the Timer API. <LI>Page \ref seq explains the design of the Sequencer API. <LI>Page \ref ucm explains the use case API. + <LI>Page \ref topology explains the DSP topology API. </UL>
<H2>Configuration</H2>
participants (3)
-
Jin, Yao
-
Liam Girdwood
-
Takashi Iwai