diff options
-rw-r--r-- | Documentation/devicetree/bindings/sound/tlv320aic31xx.txt | 1 | ||||
-rw-r--r-- | include/sound/soc-dai.h | 15 | ||||
-rw-r--r-- | include/sound/soc-topology.h | 2 | ||||
-rw-r--r-- | include/sound/soc.h | 3 | ||||
-rw-r--r-- | include/uapi/sound/asoc.h | 90 | ||||
-rw-r--r-- | sound/soc/codecs/Kconfig | 1 | ||||
-rw-r--r-- | sound/soc/codecs/tlv320aic31xx.c | 3 | ||||
-rw-r--r-- | sound/soc/codecs/tlv320aic31xx.h | 1 | ||||
-rw-r--r-- | sound/soc/codecs/uda1380.c | 77 | ||||
-rw-r--r-- | sound/soc/codecs/uda1380.h | 4 | ||||
-rw-r--r-- | sound/soc/codecs/wm2200.c | 4 | ||||
-rw-r--r-- | sound/soc/codecs/wm8523.c | 24 | ||||
-rw-r--r-- | sound/soc/soc-core.c | 42 | ||||
-rw-r--r-- | sound/soc/soc-topology.c | 751 |
14 files changed, 774 insertions, 244 deletions
diff --git a/Documentation/devicetree/bindings/sound/tlv320aic31xx.txt b/Documentation/devicetree/bindings/sound/tlv320aic31xx.txt index 9340d2ddcc54..6fbba562eaa7 100644 --- a/Documentation/devicetree/bindings/sound/tlv320aic31xx.txt +++ b/Documentation/devicetree/bindings/sound/tlv320aic31xx.txt @@ -12,6 +12,7 @@ Required properties: "ti,tlv320aic3120" - TLV320AIC3120 (mono speaker amp, MiniDSP) "ti,tlv320aic3111" - TLV320AIC3111 (stereo speaker amp, MiniDSP) "ti,tlv320dac3100" - TLV320DAC3100 (no ADC, mono speaker amp, no MiniDSP) + "ti,tlv320dac3101" - TLV320DAC3101 (no ADC, stereo speaker amp, no MiniDSP) - reg - <int> - I2C slave address - HPVDD-supply, SPRVDD-supply, SPLVDD-supply, AVDD-supply, IOVDD-supply, diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index b4082454b46f..200e1f04c166 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -15,6 +15,7 @@ #include <linux/list.h> +#include <sound/asoc.h> struct snd_pcm_substream; struct snd_soc_dapm_widget; @@ -26,13 +27,13 @@ struct snd_compr_stream; * Describes the physical PCM data formating and clocking. Add new formats * to the end. */ -#define SND_SOC_DAIFMT_I2S 1 /* I2S mode */ -#define SND_SOC_DAIFMT_RIGHT_J 2 /* Right Justified mode */ -#define SND_SOC_DAIFMT_LEFT_J 3 /* Left Justified mode */ -#define SND_SOC_DAIFMT_DSP_A 4 /* L data MSB after FRM LRC */ -#define SND_SOC_DAIFMT_DSP_B 5 /* L data MSB during FRM LRC */ -#define SND_SOC_DAIFMT_AC97 6 /* AC97 */ -#define SND_SOC_DAIFMT_PDM 7 /* Pulse density modulation */ +#define SND_SOC_DAIFMT_I2S SND_SOC_DAI_FORMAT_I2S +#define SND_SOC_DAIFMT_RIGHT_J SND_SOC_DAI_FORMAT_RIGHT_J +#define SND_SOC_DAIFMT_LEFT_J SND_SOC_DAI_FORMAT_LEFT_J +#define SND_SOC_DAIFMT_DSP_A SND_SOC_DAI_FORMAT_DSP_A +#define SND_SOC_DAIFMT_DSP_B SND_SOC_DAI_FORMAT_DSP_B +#define SND_SOC_DAIFMT_AC97 SND_SOC_DAI_FORMAT_AC97 +#define SND_SOC_DAIFMT_PDM SND_SOC_DAI_FORMAT_PDM /* left and right justified also known as MSB and LSB respectively */ #define SND_SOC_DAIFMT_MSB SND_SOC_DAIFMT_LEFT_J diff --git a/include/sound/soc-topology.h b/include/sound/soc-topology.h index b897b9d63161..f9cc7b9271ac 100644 --- a/include/sound/soc-topology.h +++ b/include/sound/soc-topology.h @@ -53,7 +53,7 @@ struct snd_soc_dobj_control { /* dynamic widget object */ struct snd_soc_dobj_widget { - unsigned int kcontrol_enum:1; /* this widget is an enum kcontrol */ + unsigned int kcontrol_type; /* kcontrol type: mixer, enum, bytes */ }; /* generic dynamic object - all dynamic objects belong to this struct */ diff --git a/include/sound/soc.h b/include/sound/soc.h index 42339c0f3532..2b502f6cc6d0 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1691,6 +1691,9 @@ int snd_soc_add_dai_link(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link); void snd_soc_remove_dai_link(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link); +struct snd_soc_dai_link *snd_soc_find_dai_link(struct snd_soc_card *card, + int id, const char *name, + const char *stream_name); int snd_soc_register_dai(struct snd_soc_component *component, struct snd_soc_dai_driver *dai_drv); diff --git a/include/uapi/sound/asoc.h b/include/uapi/sound/asoc.h index 819d895edfdc..6702533c8bd8 100644 --- a/include/uapi/sound/asoc.h +++ b/include/uapi/sound/asoc.h @@ -33,6 +33,11 @@ */ #define SND_SOC_TPLG_STREAM_CONFIG_MAX 8 +/* + * Maximum number of physical link's hardware configs + */ +#define SND_SOC_TPLG_HW_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 @@ -77,7 +82,8 @@ #define SND_SOC_TPLG_NUM_TEXTS 16 /* ABI version */ -#define SND_SOC_TPLG_ABI_VERSION 0x5 +#define SND_SOC_TPLG_ABI_VERSION 0x5 /* current version */ +#define SND_SOC_TPLG_ABI_VERSION_MIN 0x4 /* oldest version supported */ /* Max size of TLV data */ #define SND_SOC_TPLG_TLV_SIZE 32 @@ -99,8 +105,8 @@ #define SND_SOC_TPLG_TYPE_CODEC_LINK 9 #define SND_SOC_TPLG_TYPE_BACKEND_LINK 10 #define SND_SOC_TPLG_TYPE_PDATA 11 -#define SND_SOC_TPLG_TYPE_BE_DAI 12 -#define SND_SOC_TPLG_TYPE_MAX SND_SOC_TPLG_TYPE_BE_DAI +#define SND_SOC_TPLG_TYPE_DAI 12 +#define SND_SOC_TPLG_TYPE_MAX SND_SOC_TPLG_TYPE_DAI /* vendor block IDs - please add new vendor types to end */ #define SND_SOC_TPLG_TYPE_VENDOR_FW 1000 @@ -119,11 +125,32 @@ #define SND_SOC_TPLG_TUPLE_TYPE_WORD 4 #define SND_SOC_TPLG_TUPLE_TYPE_SHORT 5 -/* BE DAI flags */ +/* DAI flags */ #define SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_RATES (1 << 0) #define SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_CHANNELS (1 << 1) #define SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_SAMPLEBITS (1 << 2) +/* DAI physical PCM data formats. + * Add new formats to the end of the list. + */ +#define SND_SOC_DAI_FORMAT_I2S 1 /* I2S mode */ +#define SND_SOC_DAI_FORMAT_RIGHT_J 2 /* Right Justified mode */ +#define SND_SOC_DAI_FORMAT_LEFT_J 3 /* Left Justified mode */ +#define SND_SOC_DAI_FORMAT_DSP_A 4 /* L data MSB after FRM LRC */ +#define SND_SOC_DAI_FORMAT_DSP_B 5 /* L data MSB during FRM LRC */ +#define SND_SOC_DAI_FORMAT_AC97 6 /* AC97 */ +#define SND_SOC_DAI_FORMAT_PDM 7 /* Pulse density modulation */ + +/* left and right justified also known as MSB and LSB respectively */ +#define SND_SOC_DAI_FORMAT_MSB SND_SOC_DAI_FORMAT_LEFT_J +#define SND_SOC_DAI_FORMAT_LSB SND_SOC_DAI_FORMAT_RIGHT_J + +/* DAI link flags */ +#define SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_RATES (1 << 0) +#define SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_CHANNELS (1 << 1) +#define SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_SAMPLEBITS (1 << 2) +#define SND_SOC_TPLG_LNK_FLGBIT_VOICE_WAKEUP (1 << 3) + /* * Block Header. * This header precedes all object and object arrays below. @@ -267,6 +294,35 @@ struct snd_soc_tplg_stream { __le32 channels; /* channels */ } __attribute__((packed)); + +/* + * Describes a physical link's runtime supported hardware config, + * i.e. hardware audio formats. + */ +struct snd_soc_tplg_hw_config { + __le32 size; /* in bytes of this structure */ + __le32 id; /* unique ID - - used to match */ + __le32 fmt; /* SND_SOC_DAI_FORMAT_ format value */ + __u8 clock_gated; /* 1 if clock can be gated to save power */ + __u8 invert_bclk; /* 1 for inverted BCLK, 0 for normal */ + __u8 invert_fsync; /* 1 for inverted frame clock, 0 for normal */ + __u8 bclk_master; /* 1 for master of BCLK, 0 for slave */ + __u8 fsync_master; /* 1 for master of FSYNC, 0 for slave */ + __u8 mclk_direction; /* 0 for input, 1 for output */ + __le16 reserved; /* for 32bit alignment */ + __le32 mclk_rate; /* MCLK or SYSCLK freqency in Hz */ + __le32 bclk_rate; /* BCLK freqency in Hz */ + __le32 fsync_rate; /* frame clock in Hz */ + __le32 tdm_slots; /* number of TDM slots in use */ + __le32 tdm_slot_width; /* width in bits for each slot */ + __le32 tx_slots; /* bit mask for active Tx slots */ + __le32 rx_slots; /* bit mask for active Rx slots */ + __le32 tx_channels; /* number of Tx channels */ + __le32 tx_chanmap[SND_SOC_TPLG_MAX_CHAN]; /* array of slot number */ + __le32 rx_channels; /* number of Rx channels */ + __le32 rx_chanmap[SND_SOC_TPLG_MAX_CHAN]; /* array of slot number */ +} __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 @@ -286,7 +342,7 @@ struct snd_soc_tplg_manifest { __le32 graph_elems; /* number of graph elements */ __le32 pcm_elems; /* number of PCM elements */ __le32 dai_link_elems; /* number of DAI link elements */ - __le32 be_dai_elems; /* number of BE DAI elements */ + __le32 dai_elems; /* number of physical DAI elements */ __le32 reserved[20]; /* reserved for new ABI element types */ struct snd_soc_tplg_private priv; } __attribute__((packed)); @@ -434,13 +490,16 @@ struct snd_soc_tplg_pcm { struct snd_soc_tplg_stream stream[SND_SOC_TPLG_STREAM_CONFIG_MAX]; /* for DAI link */ __le32 num_streams; /* number of streams */ struct snd_soc_tplg_stream_caps caps[2]; /* playback and capture for DAI */ + __le32 flag_mask; /* bitmask of flags to configure */ + __le32 flags; /* SND_SOC_TPLG_LNK_FLGBIT_* flag value */ + struct snd_soc_tplg_private priv; } __attribute__((packed)); /* - * Describes the BE or CC link runtime supported configs or params + * Describes the physical link runtime supported configs or params * - * File block representation for BE/CC link config :- + * File block representation for physical link config :- * +-----------------------------------+-----+ * | struct snd_soc_tplg_hdr | 1 | * +-----------------------------------+-----+ @@ -450,21 +509,30 @@ struct snd_soc_tplg_pcm { struct snd_soc_tplg_link_config { __le32 size; /* in bytes of this structure */ __le32 id; /* unique ID - used to match */ + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; /* name - used to match */ + char stream_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; /* stream name - used to match */ struct snd_soc_tplg_stream stream[SND_SOC_TPLG_STREAM_CONFIG_MAX]; /* supported configs playback and captrure */ __le32 num_streams; /* number of streams */ + struct snd_soc_tplg_hw_config hw_config[SND_SOC_TPLG_HW_CONFIG_MAX]; /* hw configs */ + __le32 num_hw_configs; /* number of hw configs */ + __le32 default_hw_config_id; /* default hw config ID for init */ + __le32 flag_mask; /* bitmask of flags to configure */ + __le32 flags; /* SND_SOC_TPLG_LNK_FLGBIT_* flag value */ + struct snd_soc_tplg_private priv; } __attribute__((packed)); /* - * Describes SW/FW specific features of BE DAI. + * Describes SW/FW specific features of physical DAI. + * It can be used to configure backend DAIs for DPCM. * - * File block representation for BE DAI :- + * File block representation for physical DAI :- * +-----------------------------------+-----+ * | struct snd_soc_tplg_hdr | 1 | * +-----------------------------------+-----+ - * | struct snd_soc_tplg_be_dai | N | + * | struct snd_soc_tplg_dai | N | * +-----------------------------------+-----+ */ -struct snd_soc_tplg_be_dai { +struct snd_soc_tplg_dai { __le32 size; /* in bytes of this structure */ char dai_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; /* name - used to match */ __le32 dai_id; /* unique ID - used to match */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 23f31e536c11..b4f8dc80105c 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -897,6 +897,7 @@ config SND_SOC_UDA134X config SND_SOC_UDA1380 tristate + depends on I2C config SND_SOC_WL1273 tristate diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c index be1a64bfd320..f8a90ba8cd71 100644 --- a/sound/soc/codecs/tlv320aic31xx.c +++ b/sound/soc/codecs/tlv320aic31xx.c @@ -1253,6 +1253,8 @@ static const struct of_device_id tlv320aic31xx_of_match[] = { { .compatible = "ti,tlv320aic3110" }, { .compatible = "ti,tlv320aic3120" }, { .compatible = "ti,tlv320aic3111" }, + { .compatible = "ti,tlv320dac3100" }, + { .compatible = "ti,tlv320dac3101" }, {}, }; MODULE_DEVICE_TABLE(of, tlv320aic31xx_of_match); @@ -1379,6 +1381,7 @@ static const struct i2c_device_id aic31xx_i2c_id[] = { { "tlv320aic3120", AIC3120 }, { "tlv320aic3111", AIC3111 }, { "tlv320dac3100", DAC3100 }, + { "tlv320dac3101", DAC3101 }, { } }; MODULE_DEVICE_TABLE(i2c, aic31xx_i2c_id); diff --git a/sound/soc/codecs/tlv320aic31xx.h b/sound/soc/codecs/tlv320aic31xx.h index 5acd5b69fb83..730fb2058869 100644 --- a/sound/soc/codecs/tlv320aic31xx.h +++ b/sound/soc/codecs/tlv320aic31xx.h @@ -32,6 +32,7 @@ enum aic31xx_type { AIC3120 = AIC31XX_MINIDSP_BIT, AIC3111 = (AIC31XX_STEREO_CLASS_D_BIT | AIC31XX_MINIDSP_BIT), DAC3100 = DAC31XX_BIT, + DAC3101 = DAC31XX_BIT | AIC31XX_STEREO_CLASS_D_BIT, }; struct aic31xx_pdata { diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index 533e3bb444e4..2918fdb95e58 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -698,25 +698,10 @@ static int uda1380_probe(struct snd_soc_codec *codec) codec->hw_write = (hw_write_t)i2c_master_send; codec->control_data = uda1380->control_data; - if (!pdata) - return -EINVAL; - - if (gpio_is_valid(pdata->gpio_reset)) { - ret = gpio_request_one(pdata->gpio_reset, GPIOF_OUT_INIT_LOW, - "uda1380 reset"); - if (ret) - goto err_out; - } - - if (gpio_is_valid(pdata->gpio_power)) { - ret = gpio_request_one(pdata->gpio_power, GPIOF_OUT_INIT_LOW, - "uda1380 power"); - if (ret) - goto err_free_gpio; - } else { + if (!gpio_is_valid(pdata->gpio_power)) { ret = uda1380_reset(codec); if (ret) - goto err_free_gpio; + return ret; } INIT_WORK(&uda1380->work, uda1380_flush_work); @@ -733,28 +718,10 @@ static int uda1380_probe(struct snd_soc_codec *codec) } return 0; - -err_free_gpio: - if (gpio_is_valid(pdata->gpio_reset)) - gpio_free(pdata->gpio_reset); -err_out: - return ret; -} - -/* power down chip */ -static int uda1380_remove(struct snd_soc_codec *codec) -{ - struct uda1380_platform_data *pdata =codec->dev->platform_data; - - gpio_free(pdata->gpio_reset); - gpio_free(pdata->gpio_power); - - return 0; } static struct snd_soc_codec_driver soc_codec_dev_uda1380 = { .probe = uda1380_probe, - .remove = uda1380_remove, .read = uda1380_read_reg_cache, .write = uda1380_write, .set_bias_level = uda1380_set_bias_level, @@ -775,18 +742,35 @@ static struct snd_soc_codec_driver soc_codec_dev_uda1380 = { }, }; -#if IS_ENABLED(CONFIG_I2C) static int uda1380_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { + struct uda1380_platform_data *pdata = i2c->dev.platform_data; struct uda1380_priv *uda1380; int ret; + if (!pdata) + return -EINVAL; + uda1380 = devm_kzalloc(&i2c->dev, sizeof(struct uda1380_priv), GFP_KERNEL); if (uda1380 == NULL) return -ENOMEM; + if (gpio_is_valid(pdata->gpio_reset)) { + ret = devm_gpio_request_one(&i2c->dev, pdata->gpio_reset, + GPIOF_OUT_INIT_LOW, "uda1380 reset"); + if (ret) + return ret; + } + + if (gpio_is_valid(pdata->gpio_power)) { + ret = devm_gpio_request_one(&i2c->dev, pdata->gpio_power, + GPIOF_OUT_INIT_LOW, "uda1380 power"); + if (ret) + return ret; + } + i2c_set_clientdata(i2c, uda1380); uda1380->control_data = i2c; @@ -815,27 +799,8 @@ static struct i2c_driver uda1380_i2c_driver = { .remove = uda1380_i2c_remove, .id_table = uda1380_i2c_id, }; -#endif -static int __init uda1380_modinit(void) -{ - int ret = 0; -#if IS_ENABLED(CONFIG_I2C) - ret = i2c_add_driver(&uda1380_i2c_driver); - if (ret != 0) - pr_err("Failed to register UDA1380 I2C driver: %d\n", ret); -#endif - return ret; -} -module_init(uda1380_modinit); - -static void __exit uda1380_exit(void) -{ -#if IS_ENABLED(CONFIG_I2C) - i2c_del_driver(&uda1380_i2c_driver); -#endif -} -module_exit(uda1380_exit); +module_i2c_driver(uda1380_i2c_driver); MODULE_AUTHOR("Giorgio Padrin"); MODULE_DESCRIPTION("Audio support for codec Philips UDA1380"); diff --git a/sound/soc/codecs/uda1380.h b/sound/soc/codecs/uda1380.h index 942e3927c72b..69a326ac3c1a 100644 --- a/sound/soc/codecs/uda1380.h +++ b/sound/soc/codecs/uda1380.h @@ -72,8 +72,4 @@ #define R22_SKIP_DCFIL 0x0002 #define R23_AGC_EN 0x0001 -#define UDA1380_DAI_DUPLEX 0 /* playback and capture on single DAI */ -#define UDA1380_DAI_PLAYBACK 1 /* playback DAI */ -#define UDA1380_DAI_CAPTURE 2 /* capture DAI */ - #endif /* _UDA1380_H */ diff --git a/sound/soc/codecs/wm2200.c b/sound/soc/codecs/wm2200.c index 606bf88abfc4..d83dab57a1d1 100644 --- a/sound/soc/codecs/wm2200.c +++ b/sound/soc/codecs/wm2200.c @@ -999,7 +999,7 @@ static DECLARE_TLV_DB_SCALE(in_tlv, -6300, 100, 0); static DECLARE_TLV_DB_SCALE(digital_tlv, -6400, 50, 0); static DECLARE_TLV_DB_SCALE(out_tlv, -6400, 100, 0); -static const char *wm2200_mixer_texts[] = { +static const char * const wm2200_mixer_texts[] = { "None", "Tone Generator", "AEC Loopback", @@ -1033,7 +1033,7 @@ static const char *wm2200_mixer_texts[] = { "DSP2.6", }; -static int wm2200_mixer_values[] = { +static unsigned int wm2200_mixer_values[] = { 0x00, 0x04, /* Tone */ 0x08, /* AEC */ diff --git a/sound/soc/codecs/wm8523.c b/sound/soc/codecs/wm8523.c index deb2e075428e..6d0a2723bfde 100644 --- a/sound/soc/codecs/wm8523.c +++ b/sound/soc/codecs/wm8523.c @@ -446,7 +446,6 @@ static const struct regmap_config wm8523_regmap = { .volatile_reg = wm8523_volatile_register, }; -#if IS_ENABLED(CONFIG_I2C) static int wm8523_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { @@ -543,29 +542,8 @@ static struct i2c_driver wm8523_i2c_driver = { .remove = wm8523_i2c_remove, .id_table = wm8523_i2c_id, }; -#endif -static int __init wm8523_modinit(void) -{ - int ret; -#if IS_ENABLED(CONFIG_I2C) - ret = i2c_add_driver(&wm8523_i2c_driver); - if (ret != 0) { - printk(KERN_ERR "Failed to register WM8523 I2C driver: %d\n", - ret); - } -#endif - return 0; -} -module_init(wm8523_modinit); - -static void __exit wm8523_exit(void) -{ -#if IS_ENABLED(CONFIG_I2C) - i2c_del_driver(&wm8523_i2c_driver); -#endif -} -module_exit(wm8523_exit); +module_i2c_driver(wm8523_i2c_driver); MODULE_DESCRIPTION("ASoC WM8523 driver"); MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index ce8e665a9c89..f1901bb1466e 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -972,6 +972,48 @@ struct snd_soc_dai *snd_soc_find_dai( } EXPORT_SYMBOL_GPL(snd_soc_find_dai); + +/** + * snd_soc_find_dai_link - Find a DAI link + * + * @card: soc card + * @id: DAI link ID to match + * @name: DAI link name to match, optional + * @stream name: DAI link stream name to match, optional + * + * This function will search all existing DAI links of the soc card to + * find the link of the same ID. Since DAI links may not have their + * unique ID, so name and stream name should also match if being + * specified. + * + * Return: pointer of DAI link, or NULL if not found. + */ +struct snd_soc_dai_link *snd_soc_find_dai_link(struct snd_soc_card *card, + int id, const char *name, + const char *stream_name) +{ + struct snd_soc_dai_link *link, *_link; + + lockdep_assert_held(&client_mutex); + + list_for_each_entry_safe(link, _link, &card->dai_link_list, list) { + if (link->id != id) + continue; + + if (name && (!link->name || strcmp(name, link->name))) + continue; + + if (stream_name && (!link->stream_name + || strcmp(stream_name, link->stream_name))) + continue; + + return link; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_find_dai_link); + static bool soc_is_dai_link_bound(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) { diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 6b05047a4134..65670b2b408c 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -49,10 +49,68 @@ #define SOC_TPLG_PASS_GRAPH 5 #define SOC_TPLG_PASS_PINS 6 #define SOC_TPLG_PASS_BE_DAI 7 +#define SOC_TPLG_PASS_LINK 8 #define SOC_TPLG_PASS_START SOC_TPLG_PASS_MANIFEST -#define SOC_TPLG_PASS_END SOC_TPLG_PASS_BE_DAI +#define SOC_TPLG_PASS_END SOC_TPLG_PASS_LINK +/* + * Old version of ABI structs, supported for backward compatibility. + */ + +/* Manifest v4 */ +struct snd_soc_tplg_manifest_v4 { + __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 pcm_elems; /* number of PCM elements */ + __le32 dai_link_elems; /* number of DAI link elements */ + struct snd_soc_tplg_private priv; +} __packed; + +/* Stream Capabilities v4 */ +struct snd_soc_tplg_stream_caps_v4 { + __le32 size; /* in bytes of this structure */ + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + __le64 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 */ +} __packed; + +/* PCM v4 */ +struct snd_soc_tplg_pcm_v4 { + __le32 size; /* in bytes of this structure */ + char pcm_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + char dai_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + __le32 pcm_id; /* unique ID - used to match with DAI link */ + __le32 dai_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_stream stream[SND_SOC_TPLG_STREAM_CONFIG_MAX]; /* for DAI link */ + __le32 num_streams; /* number of streams */ + struct snd_soc_tplg_stream_caps_v4 caps[2]; /* playback and capture for DAI */ +} __packed; + +/* Physical link config v4 */ +struct snd_soc_tplg_link_config_v4 { + __le32 size; /* in bytes of this structure */ + __le32 id; /* unique ID - used to match */ + struct snd_soc_tplg_stream stream[SND_SOC_TPLG_STREAM_CONFIG_MAX]; /* supported configs playback and captrure */ + __le32 num_streams; /* number of streams */ +} __packed; + +/* topology context */ struct soc_tplg { const struct firmware *fw; @@ -428,33 +486,41 @@ static void remove_widget(struct snd_soc_component *comp, dobj->ops->widget_unload(comp, dobj); /* - * Dynamic Widgets either have 1 enum kcontrol or 1..N mixers. + * Dynamic Widgets either have 1..N enum kcontrols or mixers. * The enum may either have an array of values or strings. */ - if (dobj->widget.kcontrol_enum) { + if (dobj->widget.kcontrol_type == SND_SOC_TPLG_TYPE_ENUM) { /* enumerated widget mixer */ - struct soc_enum *se = - (struct soc_enum *)w->kcontrols[0]->private_value; + for (i = 0; i < w->num_kcontrols; i++) { + struct snd_kcontrol *kcontrol = w->kcontrols[i]; + struct soc_enum *se = + (struct soc_enum *)kcontrol->private_value; - snd_ctl_remove(card, w->kcontrols[0]); + snd_ctl_remove(card, kcontrol); - kfree(se->dobj.control.dvalues); - for (i = 0; i < se->items; i++) - kfree(se->dobj.control.dtexts[i]); + kfree(se->dobj.control.dvalues); + for (i = 0; i < se->items; i++) + kfree(se->dobj.control.dtexts[i]); - kfree(se); + kfree(se); + } kfree(w->kcontrol_news); } else { - /* non enumerated widget mixer */ + /* volume mixer or bytes controls */ for (i = 0; i < w->num_kcontrols; i++) { struct snd_kcontrol *kcontrol = w->kcontrols[i]; - struct soc_mixer_control *sm = - (struct soc_mixer_control *) kcontrol->private_value; - kfree(w->kcontrols[i]->tlv.p); + if (dobj->widget.kcontrol_type + == SND_SOC_TPLG_TYPE_MIXER) + kfree(kcontrol->tlv.p); - snd_ctl_remove(card, w->kcontrols[i]); - kfree(sm); + snd_ctl_remove(card, kcontrol); + + /* Private value is used as struct soc_mixer_control + * for volume mixers or soc_bytes_ext for bytes + * controls. + */ + kfree((void *)kcontrol->private_value); } kfree(w->kcontrol_news); } @@ -474,6 +540,7 @@ static void remove_dai(struct snd_soc_component *comp, if (dobj->ops && dobj->ops->dai_unload) dobj->ops->dai_unload(comp, dobj); + kfree(dai_drv->name); list_del(&dobj->list); kfree(dai_drv); } @@ -491,6 +558,10 @@ static void remove_link(struct snd_soc_component *comp, if (dobj->ops && dobj->ops->link_unload) dobj->ops->link_unload(comp, dobj); + kfree(link->name); + kfree(link->stream_name); + kfree(link->cpu_dai_name); + list_del(&dobj->list); snd_soc_remove_dai_link(comp->card, link); kfree(link); @@ -1193,98 +1264,105 @@ err: } static struct snd_kcontrol_new *soc_tplg_dapm_widget_denum_create( - struct soc_tplg *tplg) + struct soc_tplg *tplg, int num_kcontrols) { struct snd_kcontrol_new *kc; struct snd_soc_tplg_enum_control *ec; struct soc_enum *se; - int i, err; - - ec = (struct snd_soc_tplg_enum_control *)tplg->pos; - tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + - ec->priv.size); + int i, j, err; - /* validate kcontrol */ - if (strnlen(ec->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == - SNDRV_CTL_ELEM_ID_NAME_MAXLEN) - return NULL; - - kc = kzalloc(sizeof(*kc), GFP_KERNEL); + kc = kcalloc(num_kcontrols, sizeof(*kc), GFP_KERNEL); if (kc == NULL) return NULL; - se = kzalloc(sizeof(*se), GFP_KERNEL); - if (se == NULL) - goto err; + for (i = 0; i < num_kcontrols; i++) { + ec = (struct snd_soc_tplg_enum_control *)tplg->pos; + /* validate kcontrol */ + if (strnlen(ec->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return NULL; - dev_dbg(tplg->dev, " adding DAPM widget enum control %s\n", - ec->hdr.name); + se = kzalloc(sizeof(*se), GFP_KERNEL); + if (se == NULL) + goto err; - kc->name = ec->hdr.name; - kc->private_value = (long)se; - kc->iface = SNDRV_CTL_ELEM_IFACE_MIXER; - kc->access = ec->hdr.access; + dev_dbg(tplg->dev, " adding DAPM widget enum control %s\n", + ec->hdr.name); + + kc[i].name = ec->hdr.name; + kc[i].private_value = (long)se; + kc[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kc[i].access = ec->hdr.access; - /* we only support FL/FR channel mapping atm */ - se->reg = tplc_chan_get_reg(tplg, ec->channel, SNDRV_CHMAP_FL); - se->shift_l = tplc_chan_get_shift(tplg, ec->channel, SNDRV_CHMAP_FL); - se->shift_r = tplc_chan_get_shift(tplg, ec->channel, SNDRV_CHMAP_FR); + /* we only support FL/FR channel mapping atm */ + se->reg = tplc_chan_get_reg(tplg, ec->channel, SNDRV_CHMAP_FL); + se->shift_l = tplc_chan_get_shift(tplg, ec->channel, + SNDRV_CHMAP_FL); + se->shift_r = tplc_chan_get_shift(tplg, ec->channel, + SNDRV_CHMAP_FR); - se->items = ec->items; - se->mask = ec->mask; - se->dobj.index = tplg->index; + se->items = ec->items; + se->mask = ec->mask; + se->dobj.index = tplg->index; - switch (ec->hdr.ops.info) { - case SND_SOC_TPLG_CTL_ENUM_VALUE: - case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: - err = soc_tplg_denum_create_values(se, ec); - if (err < 0) { - dev_err(tplg->dev, "ASoC: could not create values for %s\n", - ec->hdr.name); + switch (ec->hdr.ops.info) { + case SND_SOC_TPLG_CTL_ENUM_VALUE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: + err = soc_tplg_denum_create_values(se, ec); + if (err < 0) { + dev_err(tplg->dev, "ASoC: could not create values for %s\n", + ec->hdr.name); + goto err_se; + } + /* fall through to create texts */ + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: + err = soc_tplg_denum_create_texts(se, ec); + if (err < 0) { + dev_err(tplg->dev, "ASoC: could not create texts for %s\n", + ec->hdr.name); + goto err_se; + } + break; + default: + dev_err(tplg->dev, "ASoC: invalid enum control type %d for %s\n", + ec->hdr.ops.info, ec->hdr.name); goto err_se; } - /* fall through to create texts */ - case SND_SOC_TPLG_CTL_ENUM: - case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: - case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: - err = soc_tplg_denum_create_texts(se, ec); + + /* map io handlers */ + err = soc_tplg_kcontrol_bind_io(&ec->hdr, &kc[i], tplg); + if (err) { + soc_control_err(tplg, &ec->hdr, ec->hdr.name); + goto err_se; + } + + /* pass control to driver for optional further init */ + err = soc_tplg_init_kcontrol(tplg, &kc[i], + (struct snd_soc_tplg_ctl_hdr *)ec); if (err < 0) { - dev_err(tplg->dev, "ASoC: could not create texts for %s\n", + dev_err(tplg->dev, "ASoC: failed to init %s\n", ec->hdr.name); goto err_se; } - break; - default: - dev_err(tplg->dev, "ASoC: invalid enum control type %d for %s\n", - ec->hdr.ops.info, ec->hdr.name); - goto err_se; - } - /* map io handlers */ - err = soc_tplg_kcontrol_bind_io(&ec->hdr, kc, tplg); - if (err) { - soc_control_err(tplg, &ec->hdr, ec->hdr.name); - goto err_se; - } - - /* pass control to driver for optional further init */ - err = soc_tplg_init_kcontrol(tplg, kc, - (struct snd_soc_tplg_ctl_hdr *)ec); - if (err < 0) { - dev_err(tplg->dev, "ASoC: failed to init %s\n", - ec->hdr.name); - goto err_se; + tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + + ec->priv.size); } return kc; err_se: - /* free values and texts */ - kfree(se->dobj.control.dvalues); - for (i = 0; i < ec->items; i++) - kfree(se->dobj.control.dtexts[i]); + for (; i >= 0; i--) { + /* free values and texts */ + se = (struct soc_enum *)kc[i].private_value; + kfree(se->dobj.control.dvalues); + for (j = 0; j < ec->items; j++) + kfree(se->dobj.control.dtexts[j]); - kfree(se); + kfree(se); + } err: kfree(kc); @@ -1366,6 +1444,7 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, struct snd_soc_dapm_widget template, *widget; struct snd_soc_tplg_ctl_hdr *control_hdr; struct snd_soc_card *card = tplg->comp->card; + unsigned int kcontrol_type; int ret = 0; if (strnlen(w->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == @@ -1406,6 +1485,7 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, tplg->pos += (sizeof(struct snd_soc_tplg_dapm_widget) + w->priv.size); if (w->num_kcontrols == 0) { + kcontrol_type = 0; template.num_kcontrols = 0; goto widget; } @@ -1421,6 +1501,7 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, case SND_SOC_TPLG_CTL_VOLSW_XR_SX: case SND_SOC_TPLG_CTL_RANGE: case SND_SOC_TPLG_DAPM_CTL_VOLSW: + kcontrol_type = SND_SOC_TPLG_TYPE_MIXER; /* volume mixer */ template.num_kcontrols = w->num_kcontrols; template.kcontrol_news = soc_tplg_dapm_widget_dmixer_create(tplg, @@ -1435,16 +1516,18 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: - template.dobj.widget.kcontrol_enum = 1; - template.num_kcontrols = 1; + kcontrol_type = SND_SOC_TPLG_TYPE_ENUM; /* enumerated mixer */ + template.num_kcontrols = w->num_kcontrols; template.kcontrol_news = - soc_tplg_dapm_widget_denum_create(tplg); + soc_tplg_dapm_widget_denum_create(tplg, + template.num_kcontrols); if (!template.kcontrol_news) { ret = -ENOMEM; goto hdr_err; } break; case SND_SOC_TPLG_CTL_BYTES: + kcontrol_type = SND_SOC_TPLG_TYPE_BYTES; /* bytes control */ template.num_kcontrols = w->num_kcontrols; template.kcontrol_news = soc_tplg_dapm_widget_dbytes_create(tplg, @@ -1481,6 +1564,7 @@ widget: } widget->dobj.type = SND_SOC_DOBJ_WIDGET; + widget->dobj.widget.kcontrol_type = kcontrol_type; widget->dobj.ops = tplg->ops; widget->dobj.index = tplg->index; kfree(template.sname); @@ -1589,7 +1673,8 @@ static int soc_tplg_dai_create(struct soc_tplg *tplg, if (dai_drv == NULL) return -ENOMEM; - dai_drv->name = pcm->dai_name; + if (strlen(pcm->dai_name)) + dai_drv->name = kstrdup(pcm->dai_name, GFP_KERNEL); dai_drv->id = pcm->dai_id; if (pcm->playback) { @@ -1621,8 +1706,31 @@ static int soc_tplg_dai_create(struct soc_tplg *tplg, return snd_soc_register_dai(tplg->comp, dai_drv); } +static void set_link_flags(struct snd_soc_dai_link *link, + unsigned int flag_mask, unsigned int flags) +{ + if (flag_mask & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_RATES) + link->symmetric_rates = + flags & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_RATES ? 1 : 0; + + if (flag_mask & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_CHANNELS) + link->symmetric_channels = + flags & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_CHANNELS ? + 1 : 0; + + if (flag_mask & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_SAMPLEBITS) + link->symmetric_samplebits = + flags & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_SAMPLEBITS ? + 1 : 0; + + if (flag_mask & SND_SOC_TPLG_LNK_FLGBIT_VOICE_WAKEUP) + link->ignore_suspend = + flags & SND_SOC_TPLG_LNK_FLGBIT_VOICE_WAKEUP ? + 1 : 0; +} + /* create the FE DAI link */ -static int soc_tplg_link_create(struct soc_tplg *tplg, +static int soc_tplg_fe_link_create(struct soc_tplg *tplg, struct snd_soc_tplg_pcm *pcm) { struct snd_soc_dai_link *link; @@ -1632,11 +1740,15 @@ static int soc_tplg_link_create(struct soc_tplg *tplg, if (link == NULL) return -ENOMEM; - link->name = pcm->pcm_name; - link->stream_name = pcm->pcm_name; + if (strlen(pcm->pcm_name)) { + link->name = kstrdup(pcm->pcm_name, GFP_KERNEL); + link->stream_name = kstrdup(pcm->pcm_name, GFP_KERNEL); + } link->id = pcm->pcm_id; - link->cpu_dai_name = pcm->dai_name; + if (strlen(pcm->dai_name)) + link->cpu_dai_name = kstrdup(pcm->dai_name, GFP_KERNEL); + link->codec_name = "snd-soc-dummy"; link->codec_dai_name = "snd-soc-dummy-dai"; @@ -1644,6 +1756,8 @@ static int soc_tplg_link_create(struct soc_tplg *tplg, link->dynamic = 1; link->dpcm_playback = pcm->playback; link->dpcm_capture = pcm->capture; + if (pcm->flag_mask) + set_link_flags(link, pcm->flag_mask, pcm->flags); /* pass control to component driver for optional further init */ ret = soc_tplg_dai_link_load(tplg, link); @@ -1672,55 +1786,351 @@ static int soc_tplg_pcm_create(struct soc_tplg *tplg, if (ret < 0) return ret; - return soc_tplg_link_create(tplg, pcm); + return soc_tplg_fe_link_create(tplg, pcm); +} + +/* copy stream caps from the old version 4 of source */ +static void stream_caps_new_ver(struct snd_soc_tplg_stream_caps *dest, + struct snd_soc_tplg_stream_caps_v4 *src) +{ + dest->size = sizeof(*dest); + memcpy(dest->name, src->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + dest->formats = src->formats; + dest->rates = src->rates; + dest->rate_min = src->rate_min; + dest->rate_max = src->rate_max; + dest->channels_min = src->channels_min; + dest->channels_max = src->channels_max; + dest->periods_min = src->periods_min; + dest->periods_max = src->periods_max; + dest->period_size_min = src->period_size_min; + dest->period_size_max = src->period_size_max; + dest->buffer_size_min = src->buffer_size_min; + dest->buffer_size_max = src->buffer_size_max; +} + +/** + * pcm_new_ver - Create the new version of PCM from the old version. + * @tplg: topology context + * @src: older version of pcm as a source + * @pcm: latest version of pcm created from the source + * + * Support from vesion 4. User should free the returned pcm manually. + */ +static int pcm_new_ver(struct soc_tplg *tplg, + struct snd_soc_tplg_pcm *src, + struct snd_soc_tplg_pcm **pcm) +{ + struct snd_soc_tplg_pcm *dest; + struct snd_soc_tplg_pcm_v4 *src_v4; + int i; + + *pcm = NULL; + + if (src->size != sizeof(*src_v4)) { + dev_err(tplg->dev, "ASoC: invalid PCM size\n"); + return -EINVAL; + } + + dev_warn(tplg->dev, "ASoC: old version of PCM\n"); + src_v4 = (struct snd_soc_tplg_pcm_v4 *)src; + dest = kzalloc(sizeof(*dest), GFP_KERNEL); + if (!dest) + return -ENOMEM; + + dest->size = sizeof(*dest); /* size of latest abi version */ + memcpy(dest->pcm_name, src_v4->pcm_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + memcpy(dest->dai_name, src_v4->dai_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + dest->pcm_id = src_v4->pcm_id; + dest->dai_id = src_v4->dai_id; + dest->playback = src_v4->playback; + dest->capture = src_v4->capture; + dest->compress = src_v4->compress; + dest->num_streams = src_v4->num_streams; + for (i = 0; i < dest->num_streams; i++) + memcpy(&dest->stream[i], &src_v4->stream[i], + sizeof(struct snd_soc_tplg_stream)); + + for (i = 0; i < 2; i++) + stream_caps_new_ver(&dest->caps[i], &src_v4->caps[i]); + + *pcm = dest; + return 0; } static int soc_tplg_pcm_elems_load(struct soc_tplg *tplg, struct snd_soc_tplg_hdr *hdr) { - struct snd_soc_tplg_pcm *pcm; + struct snd_soc_tplg_pcm *pcm, *_pcm; int count = hdr->count; - int i; + int i, err; + bool abi_match; if (tplg->pass != SOC_TPLG_PASS_PCM_DAI) return 0; + /* check the element size and count */ + pcm = (struct snd_soc_tplg_pcm *)tplg->pos; + if (pcm->size > sizeof(struct snd_soc_tplg_pcm) + || pcm->size < sizeof(struct snd_soc_tplg_pcm_v4)) { + dev_err(tplg->dev, "ASoC: invalid size %d for PCM elems\n", + pcm->size); + return -EINVAL; + } + if (soc_tplg_check_elem_count(tplg, - sizeof(struct snd_soc_tplg_pcm), count, + pcm->size, count, hdr->payload_size, "PCM DAI")) { dev_err(tplg->dev, "ASoC: invalid count %d for PCM DAI elems\n", count); return -EINVAL; } - /* create the FE DAIs and DAI links */ - pcm = (struct snd_soc_tplg_pcm *)tplg->pos; for (i = 0; i < count; i++) { - if (pcm->size != sizeof(*pcm)) { - dev_err(tplg->dev, "ASoC: invalid pcm size\n"); - return -EINVAL; + pcm = (struct snd_soc_tplg_pcm *)tplg->pos; + + /* check ABI version by size, create a new version of pcm + * if abi not match. + */ + if (pcm->size == sizeof(*pcm)) { + abi_match = true; + _pcm = pcm; + } else { + abi_match = false; + err = pcm_new_ver(tplg, pcm, &_pcm); } - soc_tplg_pcm_create(tplg, pcm); - pcm++; + /* create the FE DAIs and DAI links */ + soc_tplg_pcm_create(tplg, _pcm); + + /* offset by version-specific struct size and + * real priv data size + */ + tplg->pos += pcm->size + _pcm->priv.size; + + if (!abi_match) + kfree(_pcm); /* free the duplicated one */ } dev_dbg(tplg->dev, "ASoC: adding %d PCM DAIs\n", count); - tplg->pos += sizeof(struct snd_soc_tplg_pcm) * count; return 0; } -/* * - * soc_tplg_be_dai_config - Find and configure an existing BE DAI. +/** + * set_link_hw_format - Set the HW audio format of the physical DAI link. + * @tplg: topology context + * @cfg: physical link configs. + * + * Topology context contains a list of supported HW formats (configs) and + * a default format ID for the physical link. This function will use this + * default ID to choose the HW format to set the link's DAI format for init. + */ +static void set_link_hw_format(struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg) +{ + struct snd_soc_tplg_hw_config *hw_config; + unsigned char bclk_master, fsync_master; + unsigned char invert_bclk, invert_fsync; + int i; + + for (i = 0; i < cfg->num_hw_configs; i++) { + hw_config = &cfg->hw_config[i]; + if (hw_config->id != cfg->default_hw_config_id) + continue; + + link->dai_fmt = hw_config->fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + /* clock signal polarity */ + invert_bclk = hw_config->invert_bclk; + invert_fsync = hw_config->invert_fsync; + if (!invert_bclk && !invert_fsync) + link->dai_fmt |= SND_SOC_DAIFMT_NB_NF; + else if (!invert_bclk && invert_fsync) + link->dai_fmt |= SND_SOC_DAIFMT_NB_IF; + else if (invert_bclk && !invert_fsync) + link->dai_fmt |= SND_SOC_DAIFMT_IB_NF; + else + link->dai_fmt |= SND_SOC_DAIFMT_IB_IF; + + /* clock masters */ + bclk_master = hw_config->bclk_master; + fsync_master = hw_config->fsync_master; + if (!bclk_master && !fsync_master) + link->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + else if (bclk_master && !fsync_master) + link->dai_fmt |= SND_SOC_DAIFMT_CBS_CFM; + else if (!bclk_master && fsync_master) + link->dai_fmt |= SND_SOC_DAIFMT_CBM_CFS; + else + link->dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; + } +} + +/** + * link_new_ver - Create a new physical link config from the old + * version of source. + * @toplogy: topology context + * @src: old version of phyical link config as a source + * @link: latest version of physical link config created from the source + * + * Support from vesion 4. User need free the returned link config manually. + */ +static int link_new_ver(struct soc_tplg *tplg, + struct snd_soc_tplg_link_config *src, + struct snd_soc_tplg_link_config **link) +{ + struct snd_soc_tplg_link_config *dest; + struct snd_soc_tplg_link_config_v4 *src_v4; + int i; + + *link = NULL; + + if (src->size != sizeof(struct snd_soc_tplg_link_config_v4)) { + dev_err(tplg->dev, "ASoC: invalid physical link config size\n"); + return -EINVAL; + } + + dev_warn(tplg->dev, "ASoC: old version of physical link config\n"); + + src_v4 = (struct snd_soc_tplg_link_config_v4 *)src; + dest = kzalloc(sizeof(*dest), GFP_KERNEL); + if (!dest) + return -ENOMEM; + + dest->size = sizeof(*dest); + dest->id = src_v4->id; + dest->num_streams = src_v4->num_streams; + for (i = 0; i < dest->num_streams; i++) + memcpy(&dest->stream[i], &src_v4->stream[i], + sizeof(struct snd_soc_tplg_stream)); + + *link = dest; + return 0; +} + +/* Find and configure an existing physical DAI link */ +static int soc_tplg_link_config(struct soc_tplg *tplg, + struct snd_soc_tplg_link_config *cfg) +{ + struct snd_soc_dai_link *link; + const char *name, *stream_name; + size_t len; + int ret; + + len = strnlen(cfg->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + if (len == SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + else if (len) + name = cfg->name; + else + name = NULL; + + len = strnlen(cfg->stream_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + if (len == SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + else if (len) + stream_name = cfg->stream_name; + else + stream_name = NULL; + + link = snd_soc_find_dai_link(tplg->comp->card, cfg->id, + name, stream_name); + if (!link) { + dev_err(tplg->dev, "ASoC: physical link %s (id %d) not exist\n", + name, cfg->id); + return -EINVAL; + } + + /* hw format */ + if (cfg->num_hw_configs) + set_link_hw_format(link, cfg); + + /* flags */ + if (cfg->flag_mask) + set_link_flags(link, cfg->flag_mask, cfg->flags); + + /* pass control to component driver for optional further init */ + ret = soc_tplg_dai_link_load(tplg, link); + if (ret < 0) { + dev_err(tplg->dev, "ASoC: physical link loading failed\n"); + return ret; + } + + return 0; +} + + +/* Load physical link config elements from the topology context */ +static int soc_tplg_link_elems_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + struct snd_soc_tplg_link_config *link, *_link; + int count = hdr->count; + int i, ret; + bool abi_match; + + if (tplg->pass != SOC_TPLG_PASS_LINK) { + tplg->pos += hdr->size + hdr->payload_size; + return 0; + }; + + /* check the element size and count */ + link = (struct snd_soc_tplg_link_config *)tplg->pos; + if (link->size > sizeof(struct snd_soc_tplg_link_config) + || link->size < sizeof(struct snd_soc_tplg_link_config_v4)) { + dev_err(tplg->dev, "ASoC: invalid size %d for physical link elems\n", + link->size); + return -EINVAL; + } + + if (soc_tplg_check_elem_count(tplg, + link->size, count, + hdr->payload_size, "physical link config")) { + dev_err(tplg->dev, "ASoC: invalid count %d for physical link elems\n", + count); + return -EINVAL; + } + + /* config physical DAI links */ + for (i = 0; i < count; i++) { + link = (struct snd_soc_tplg_link_config *)tplg->pos; + if (link->size == sizeof(*link)) { + abi_match = true; + _link = link; + } else { + abi_match = false; + ret = link_new_ver(tplg, link, &_link); + if (ret < 0) + return ret; + } + + ret = soc_tplg_link_config(tplg, _link); + if (ret < 0) + return ret; + + /* offset by version-specific struct size and + * real priv data size + */ + tplg->pos += link->size + _link->priv.size; + + if (!abi_match) + kfree(_link); /* free the duplicated one */ + } + + return 0; +} + +/** + * soc_tplg_dai_config - Find and configure an existing physical DAI. * @tplg: topology context - * @be: topology BE DAI configs. + * @d: physical DAI configs. * - * The BE dai should already be registered by the platform driver. The - * platform driver should specify the BE DAI name and ID for matching. + * The physical dai should already be registered by the platform driver. + * The platform driver should specify the DAI name and ID for matching. */ -static int soc_tplg_be_dai_config(struct soc_tplg *tplg, - struct snd_soc_tplg_be_dai *be) +static int soc_tplg_dai_config(struct soc_tplg *tplg, + struct snd_soc_tplg_dai *d) { struct snd_soc_dai_link_component dai_component = {0}; struct snd_soc_dai *dai; @@ -1729,17 +2139,17 @@ static int soc_tplg_be_dai_config(struct soc_tplg *tplg, struct snd_soc_tplg_stream_caps *caps; int ret; - dai_component.dai_name = be->dai_name; + dai_component.dai_name = d->dai_name; dai = snd_soc_find_dai(&dai_component); if (!dai) { - dev_err(tplg->dev, "ASoC: BE DAI %s not registered\n", - be->dai_name); + dev_err(tplg->dev, "ASoC: physical DAI %s not registered\n", + d->dai_name); return -EINVAL; } - if (be->dai_id != dai->id) { - dev_err(tplg->dev, "ASoC: BE DAI %s id mismatch\n", - be->dai_name); + if (d->dai_id != dai->id) { + dev_err(tplg->dev, "ASoC: physical DAI %s id mismatch\n", + d->dai_name); return -EINVAL; } @@ -1747,20 +2157,20 @@ static int soc_tplg_be_dai_config(struct soc_tplg *tplg, if (!dai_drv) return -EINVAL; - if (be->playback) { + if (d->playback) { stream = &dai_drv->playback; - caps = &be->caps[SND_SOC_TPLG_STREAM_PLAYBACK]; + caps = &d->caps[SND_SOC_TPLG_STREAM_PLAYBACK]; set_stream_info(stream, caps); } - if (be->capture) { + if (d->capture) { stream = &dai_drv->capture; - caps = &be->caps[SND_SOC_TPLG_STREAM_CAPTURE]; + caps = &d->caps[SND_SOC_TPLG_STREAM_CAPTURE]; set_stream_info(stream, caps); } - if (be->flag_mask) - set_dai_flags(dai_drv, be->flag_mask, be->flags); + if (d->flag_mask) + set_dai_flags(dai_drv, d->flag_mask, d->flags); /* pass control to component driver for optional further init */ ret = soc_tplg_dai_load(tplg, dai_drv); @@ -1772,10 +2182,11 @@ static int soc_tplg_be_dai_config(struct soc_tplg *tplg, return 0; } -static int soc_tplg_be_dai_elems_load(struct soc_tplg *tplg, - struct snd_soc_tplg_hdr *hdr) +/* load physical DAI elements */ +static int soc_tplg_dai_elems_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) { - struct snd_soc_tplg_be_dai *be; + struct snd_soc_tplg_dai *dai; int count = hdr->count; int i; @@ -1784,41 +2195,95 @@ static int soc_tplg_be_dai_elems_load(struct soc_tplg *tplg, /* config the existing BE DAIs */ for (i = 0; i < count; i++) { - be = (struct snd_soc_tplg_be_dai *)tplg->pos; - if (be->size != sizeof(*be)) { - dev_err(tplg->dev, "ASoC: invalid BE DAI size\n"); + dai = (struct snd_soc_tplg_dai *)tplg->pos; + if (dai->size != sizeof(*dai)) { + dev_err(tplg->dev, "ASoC: invalid physical DAI size\n"); return -EINVAL; } - soc_tplg_be_dai_config(tplg, be); - tplg->pos += (sizeof(*be) + be->priv.size); + soc_tplg_dai_config(tplg, dai); + tplg->pos += (sizeof(*dai) + dai->priv.size); } dev_dbg(tplg->dev, "ASoC: Configure %d BE DAIs\n", count); return 0; } +/** + * manifest_new_ver - Create a new version of manifest from the old version + * of source. + * @toplogy: topology context + * @src: old version of manifest as a source + * @manifest: latest version of manifest created from the source + * + * Support from vesion 4. Users need free the returned manifest manually. + */ +static int manifest_new_ver(struct soc_tplg *tplg, + struct snd_soc_tplg_manifest *src, + struct snd_soc_tplg_manifest **manifest) +{ + struct snd_soc_tplg_manifest *dest; + struct snd_soc_tplg_manifest_v4 *src_v4; + + *manifest = NULL; + + if (src->size != sizeof(*src_v4)) { + dev_err(tplg->dev, "ASoC: invalid manifest size\n"); + return -EINVAL; + } + + dev_warn(tplg->dev, "ASoC: old version of manifest\n"); + + src_v4 = (struct snd_soc_tplg_manifest_v4 *)src; + dest = kzalloc(sizeof(*dest) + src_v4->priv.size, GFP_KERNEL); + if (!dest) + return -ENOMEM; + + dest->size = sizeof(*dest); /* size of latest abi version */ + dest->control_elems = src_v4->control_elems; + dest->widget_elems = src_v4->widget_elems; + dest->graph_elems = src_v4->graph_elems; + dest->pcm_elems = src_v4->pcm_elems; + dest->dai_link_elems = src_v4->dai_link_elems; + dest->priv.size = src_v4->priv.size; + if (dest->priv.size) + memcpy(dest->priv.data, src_v4->priv.data, + src_v4->priv.size); + + *manifest = dest; + return 0; +} static int soc_tplg_manifest_load(struct soc_tplg *tplg, struct snd_soc_tplg_hdr *hdr) { - struct snd_soc_tplg_manifest *manifest; + struct snd_soc_tplg_manifest *manifest, *_manifest; + bool abi_match; + int err; if (tplg->pass != SOC_TPLG_PASS_MANIFEST) return 0; manifest = (struct snd_soc_tplg_manifest *)tplg->pos; - if (manifest->size != sizeof(*manifest)) { - dev_err(tplg->dev, "ASoC: invalid manifest size\n"); - return -EINVAL; - } - tplg->pos += sizeof(struct snd_soc_tplg_manifest); + /* check ABI version by size, create a new manifest if abi not match */ + if (manifest->size == sizeof(*manifest)) { + abi_match = true; + _manifest = manifest; + } else { + abi_match = false; + err = manifest_new_ver(tplg, manifest, &_manifest); + if (err < 0) + return err; + } + /* pass control to component driver for optional further init */ if (tplg->comp && tplg->ops && tplg->ops->manifest) - return tplg->ops->manifest(tplg->comp, manifest); + return tplg->ops->manifest(tplg->comp, _manifest); + + if (!abi_match) /* free the duplicated one */ + kfree(_manifest); - dev_err(tplg->dev, "ASoC: Firmware manifest not supported\n"); return 0; } @@ -1854,7 +2319,9 @@ static int soc_valid_header(struct soc_tplg *tplg, return -EINVAL; } - if (hdr->abi != SND_SOC_TPLG_ABI_VERSION) { + /* Support ABI from version 4 */ + if (hdr->abi > SND_SOC_TPLG_ABI_VERSION + || hdr->abi < SND_SOC_TPLG_ABI_VERSION_MIN) { dev_err(tplg->dev, "ASoC: pass %d invalid ABI version got 0x%x need 0x%x at offset 0x%lx size 0x%zx.\n", tplg->pass, hdr->abi, @@ -1902,8 +2369,12 @@ static int soc_tplg_load_header(struct soc_tplg *tplg, return soc_tplg_dapm_widget_elems_load(tplg, hdr); case SND_SOC_TPLG_TYPE_PCM: return soc_tplg_pcm_elems_load(tplg, hdr); - case SND_SOC_TPLG_TYPE_BE_DAI: - return soc_tplg_be_dai_elems_load(tplg, hdr); + case SND_SOC_TPLG_TYPE_DAI: + return soc_tplg_dai_elems_load(tplg, hdr); + case SND_SOC_TPLG_TYPE_DAI_LINK: + case SND_SOC_TPLG_TYPE_BACKEND_LINK: + /* physical link configurations */ + return soc_tplg_link_elems_load(tplg, hdr); case SND_SOC_TPLG_TYPE_MANIFEST: return soc_tplg_manifest_load(tplg, hdr); default: |