[alsa-devel] [PATCH 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 | 406 +++++++++++++++++++++++++++++ 4 files changed, 412 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..4d63357 --- /dev/null +++ b/src/conf/topology/broadwell/broadwell.conf @@ -0,0 +1,406 @@ +# 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" + ] + } +} + +# Backend DAI links +SectionBE."SSP0-Codec" { + + index "1" + + # used for binding to the BE DAI link + ID "0" + + # BE DAI link capabilities and supported configs + be."playback" { + + capabilities "System Playback" + + configs [ + "PCM 48k Stereo 24bit" + "PCM 48k Stereo 16bit" + ] + } + + be."capture" { + + capabilities "Analog Capture" + + configs [ + "PCM 48k Stereo 24bit" + "PCM 48k Stereo 16bit" + "PCM 48k 2P/4C 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" + ] +}
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 | 185 ++++++++++++++++++++++++ src/topology/parser.c | 357 ++++++++++++++++++++++++++++++++++++++++++++++ src/topology/tplg_local.h | 222 ++++++++++++++++++++++++++++ 4 files changed, 839 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..1da7c60 --- /dev/null +++ b/src/topology/elem.c @@ -0,0 +1,185 @@ +/* + 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->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); + + 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..9c9c490 --- /dev/null +++ b/src/topology/tplg_local.h @@ -0,0 +1,222 @@ +/* + * 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 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);
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 | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 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..4a16b02 --- /dev/null +++ b/src/topology/text.c @@ -0,0 +1,98 @@ +/* + 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); + 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; +}
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 | 782 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 782 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..476af55 --- /dev/null +++ b/src/topology/pcm.c @@ -0,0 +1,782 @@ +/* + 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 format text names to types */ +static const struct map_elem pcm_format_map[] = { + {"S16_LE", SNDRV_PCM_FORMAT_S16_LE}, + {"S16_BE", SNDRV_PCM_FORMAT_S16_BE}, + {"U16_LE", SNDRV_PCM_FORMAT_U16_LE}, + {"U16_BE", SNDRV_PCM_FORMAT_U16_BE}, + {"S24_LE", SNDRV_PCM_FORMAT_S24_LE}, + {"S24_BE", SNDRV_PCM_FORMAT_S24_BE}, + {"U24_LE", SNDRV_PCM_FORMAT_U24_LE}, + {"U24_BE", SNDRV_PCM_FORMAT_U24_BE}, + {"S32_LE", SNDRV_PCM_FORMAT_S32_LE}, + {"S32_BE", SNDRV_PCM_FORMAT_S32_BE}, + {"U32_LE", SNDRV_PCM_FORMAT_U32_LE}, + {"U32_BE", SNDRV_PCM_FORMAT_U32_BE}, +}; + +static int lookup_pcm_format(const char *c, snd_pcm_format_t *format) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pcm_format_map); i++) { + if (strcmp(pcm_format_map[i].name, c) == 0) { + *format = pcm_format_map[i].id; + return 0; + } + } + + return -EINVAL; +} + +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) { + ret = lookup_pcm_format(val, &format); + if (ret < 0) { + 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)) { + ret = lookup_pcm_format(s, &format); + if (ret < 0) { + 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); + + 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); + + *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; + else if (strcmp(id, "capture") == 0) + stream = SND_SOC_TPLG_STREAM_CAPTURE; + 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); + + 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); + + 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); + + 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 Mon, 29 Jun 2015 17:32:50 +0100, Liam Girdwood wrote:
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 | 782 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 782 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..476af55 --- /dev/null +++ b/src/topology/pcm.c @@ -0,0 +1,782 @@ +/*
- 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 format text names to types */ +static const struct map_elem pcm_format_map[] = {
- {"S16_LE", SNDRV_PCM_FORMAT_S16_LE},
- {"S16_BE", SNDRV_PCM_FORMAT_S16_BE},
- {"U16_LE", SNDRV_PCM_FORMAT_U16_LE},
- {"U16_BE", SNDRV_PCM_FORMAT_U16_BE},
- {"S24_LE", SNDRV_PCM_FORMAT_S24_LE},
- {"S24_BE", SNDRV_PCM_FORMAT_S24_BE},
- {"U24_LE", SNDRV_PCM_FORMAT_U24_LE},
- {"U24_BE", SNDRV_PCM_FORMAT_U24_BE},
- {"S32_LE", SNDRV_PCM_FORMAT_S32_LE},
- {"S32_BE", SNDRV_PCM_FORMAT_S32_BE},
- {"U32_LE", SNDRV_PCM_FORMAT_U32_LE},
- {"U32_BE", SNDRV_PCM_FORMAT_U32_BE},
+};
+static int lookup_pcm_format(const char *c, snd_pcm_format_t *format) +{
- unsigned int i;
- for (i = 0; i < ARRAY_SIZE(pcm_format_map); i++) {
if (strcmp(pcm_format_map[i].name, c) == 0) {
*format = pcm_format_map[i].id;
return 0;
}
- }
- return -EINVAL;
+}
You can use snd_pcm_format_value(), I suppose.
Takashi
+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) {
ret = lookup_pcm_format(val, &format);
if (ret < 0) {
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)) {
ret = lookup_pcm_format(s, &format);
if (ret < 0) {
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);
- 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);
- *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;
- else if (strcmp(id, "capture") == 0)
stream = SND_SOC_TPLG_STREAM_CAPTURE;
- 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);
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);
- 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);
- 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;
+}
2.1.4
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 | 348 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 348 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..b579df1 --- /dev/null +++ b/src/topology/data.c @@ -0,0 +1,348 @@ +/* + 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; + } + + 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 (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 Mon, 29 Jun 2015 17:32:52 +0100, Liam Girdwood wrote:
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 | 348 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 348 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..b579df1 --- /dev/null +++ b/src/topology/data.c @@ -0,0 +1,348 @@ +/*
- 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;
- }
- priv = calloc(1, sizeof(*priv) + size);
Isn't it better to have some sane value check before allocation? You may pass a GB size data wrongly.
Takashi
- 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;
This doesn't look portable in regard of endianess.
Takashi
- 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 (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;
+}
2.1.4
Parse DAPM objects including widgets and graph elements.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- src/topology/dapm.c | 560 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 560 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..fafe3cb --- /dev/null +++ b/src/topology/dapm.c @@ -0,0 +1,560 @@ +/* + 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); + + 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->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; +}
At Mon, 29 Jun 2015 17:32:53 +0100, Liam Girdwood wrote:
Parse DAPM objects including widgets and graph elements.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com
src/topology/dapm.c | 560 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 560 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..fafe3cb --- /dev/null +++ b/src/topology/dapm.c @@ -0,0 +1,560 @@ +/*
- 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);
strncpy() doesn't terminate with NUL-character. You have to put manually buf[LINE_SIZE - 1] = 0; after strncpy() call.
Unfortunately there is no strlcpy() in user-space...
Takashi
- 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->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;
+}
2.1.4
Add support to parse mixers, enums and byte controls.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- src/topology/ctl.c | 659 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 659 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..b0ed008 --- /dev/null +++ b/src/topology/ctl.c @@ -0,0 +1,659 @@ +/* + 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; + + 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; + } + } + + 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.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE; + 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.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE; + 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; + + 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 (2)
-
Liam Girdwood
-
Takashi Iwai