[alsa-devel] [PATCH v5] ASoC: cs53l30: Add support for Cirrus Logic CS53L30
From: Tim Howe tim.howe@cirrus.com
Signed-off-by: Tim Howe tim.howe@cirrus.com --- .../devicetree/bindings/sound/cs53l30.txt | 21 + sound/soc/codecs/Kconfig | 6 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/cs53l30.c | 943 +++++++++++++++++++++ sound/soc/codecs/cs53l30.h | 282 ++++++ 5 files changed, 1254 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/cs53l30.txt create mode 100644 sound/soc/codecs/cs53l30.c create mode 100644 sound/soc/codecs/cs53l30.h
diff --git a/Documentation/devicetree/bindings/sound/cs53l30.txt b/Documentation/devicetree/bindings/sound/cs53l30.txt new file mode 100644 index 0000000..a2dff95 +++ b/Documentation/devicetree/bindings/sound/cs53l30.txt @@ -0,0 +1,21 @@ +CS53L30 audio CODEC + +Required properties: + + - compatible : "cirrus,cs53l30" + + - reg : the I2C address of the device + +Optional properties: + + - reset-gpios : a GPIO spec for the reset pin. + + +Example: + +codec: cs53l30@48 { + compatible = "cirrus,cs53l30"; + reg = <0x48>; + reset-gpios = <&gpio 54 0>; +}; + diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index b282169..2dd97d4 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -60,6 +60,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_CS4271_SPI if SPI_MASTER select SND_SOC_CS42XX8_I2C if I2C select SND_SOC_CS4349 if I2C + select SND_SOC_CS53L30 if I2C select SND_SOC_CX20442 if TTY select SND_SOC_DA7210 if SND_SOC_I2C_AND_SPI select SND_SOC_DA7213 if I2C @@ -449,6 +450,11 @@ config SND_SOC_CS4349 tristate "Cirrus Logic CS4349 CODEC" depends on I2C
+# Cirrus Logic Quad-Channel ADC +config SND_SOC_CS53L30 + tristate "Cirrus Logic CS53L30 CODEC" + depends on I2C + config SND_SOC_CX20442 tristate depends on TTY diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 81324bc..88dff59 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -52,6 +52,7 @@ snd-soc-cs4271-spi-objs := cs4271-spi.o snd-soc-cs42xx8-objs := cs42xx8.o snd-soc-cs42xx8-i2c-objs := cs42xx8-i2c.o snd-soc-cs4349-objs := cs4349.o +snd-soc-cs53l30-objs := cs53l30.o snd-soc-cx20442-objs := cx20442.o snd-soc-da7210-objs := da7210.o snd-soc-da7213-objs := da7213.o @@ -252,6 +253,7 @@ obj-$(CONFIG_SND_SOC_CS4271_SPI) += snd-soc-cs4271-spi.o obj-$(CONFIG_SND_SOC_CS42XX8) += snd-soc-cs42xx8.o obj-$(CONFIG_SND_SOC_CS42XX8_I2C) += snd-soc-cs42xx8-i2c.o obj-$(CONFIG_SND_SOC_CS4349) += snd-soc-cs4349.o +obj-$(CONFIG_SND_SOC_CS53L30) += snd-soc-cs53l30.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o obj-$(CONFIG_SND_SOC_DA7213) += snd-soc-da7213.o diff --git a/sound/soc/codecs/cs53l30.c b/sound/soc/codecs/cs53l30.c new file mode 100644 index 0000000..2002ecd +++ b/sound/soc/codecs/cs53l30.c @@ -0,0 +1,943 @@ +/* + * cs53l30.c -- CS53l30 ALSA Soc Audio driver + * + * Copyright 2015 Cirrus Logic, Inc. + * + * Authors: Paul Handrigan Paul.Handrigan@cirrus.com, + * Tim Howe Tim.Howe@cirrus.com + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include "cs53l30.h" + +struct cs53l30_private { + struct regmap *regmap; + struct gpio_desc *reset_gpio; + u8 asp_config_ctl; + u32 mclk; +}; + +static const struct reg_default cs53l30_reg_defaults[] = { + { CS53L30_PWRCTL, CS53L30_THMS_PDN }, + { CS53L30_MCLKCTL, CS53L30_MCLK_DIV_DFLT }, + { CS53L30_INT_SR_CTL, CS53L30_INTRNL_FS_DFLT }, + { CS53L30_MICBIAS_CTL, CS53L30_MIC_BIAS_DFLT }, + { CS53L30_ASPCFG_CTL, CS53L30_ASP_RATE_48K }, + { CS53L30_ASP1_CTL, CS53L30_ASP1_TDM_PDN }, + { CS53L30_ASP1_TDMTX_CTL1, CS53L30_ASP1_CHTX_SLT47 }, + { CS53L30_ASP1_TDMTX_CTL2, CS53L30_ASP1_CHTX_SLT47 }, + { CS53L30_ASP1_TDMTX_CTL3, CS53L30_ASP1_CHTX_SLT47 }, + { CS53L30_ASP1_TDMTX_CTL4, CS53L30_ASP1_CHTX_SLT47 }, + { CS53L30_ASP1_TDMTX_EN1, CS53L30_ASP_TX_DISABLED }, + { CS53L30_ASP1_TDMTX_EN2, CS53L30_ASP_TX_DISABLED }, + { CS53L30_ASP1_TDMTX_EN3, CS53L30_ASP_TX_DISABLED }, + { CS53L30_ASP1_TDMTX_EN4, CS53L30_ASP_TX_DISABLED }, + { CS53L30_ASP1_TDMTX_EN5, CS53L30_ASP_TX_DISABLED }, + { CS53L30_ASP1_TDMTX_EN6, CS53L30_ASP_TX_DISABLED }, + { CS53L30_ASP2_CTL, CS53L30_ASP2_CTRL_DFLT }, + { CS53L30_SFT_RAMP, CS53L30_SFT_RMP_DFLT }, + { CS53L30_LRCLK_CTL1, CS53L30_LRCK_CTLX_DFLT }, + { CS53L30_LRCLK_CTL2, CS53L30_LRCK_CTLX_DFLT }, + { CS53L30_MUTEP_CTL1, CS53L30_MUTE_CTRL1_DFLT }, + { CS53L30_MUTEP_CTL2, CS53L30_MUTE_PDN_ULP }, + { CS53L30_INBIAS_CTL1, CS53L30_INBIAS_X_DFLT }, + { CS53L30_INBIAS_CTL2, CS53L30_INBIAS_X_DFLT }, + { CS53L30_DMIC1_STR_CTL, CS53L30_DMIC1_ST_DFLT }, + { CS53L30_DMIC2_STR_CTL, CS53L30_DMIC2_ST_DFLT }, + { CS53L30_ADCDMIC1_CTL1, CS53L30_ADC1_ON_AB_IN }, + { CS53L30_ADCDMIC1_CTL2, CS53L30_A1_D1_CTL2_DFLT }, + { CS53L30_ADC1_CTL3, CS53L30_ADC1_HPF_EN }, + { CS53L30_ADC1_NG_CTL, CS53L30_ADCX_ZERO_DFLT }, + { CS53L30_ADC1A_AFE_CTL, CS53L30_ADCX_ZERO_DFLT }, + { CS53L30_ADC1B_AFE_CTL, CS53L30_ADCX_ZERO_DFLT }, + { CS53L30_ADC1A_DIG_VOL, CS53L30_ADCX_ZERO_DFLT }, + { CS53L30_ADC1B_DIG_VOL, CS53L30_ADCX_ZERO_DFLT }, + { CS53L30_ADCDMIC2_CTL1, CS53L30_ADC2_ON_AB_IN }, + { CS53L30_ADCDMIC2_CTL2, CS53L30_ADCX_ZERO_DFLT }, + { CS53L30_ADC2_CTL3, CS53L30_ADC2_HPF_EN }, + { CS53L30_ADC2_NG_CTL, CS53L30_ADCX_ZERO_DFLT }, + { CS53L30_ADC2A_AFE_CTL, CS53L30_ADCX_ZERO_DFLT }, + { CS53L30_ADC2B_AFE_CTL, CS53L30_ADCX_ZERO_DFLT }, + { CS53L30_ADC2A_DIG_VOL, CS53L30_ADCX_ZERO_DFLT }, + { CS53L30_ADC2B_DIG_VOL, CS53L30_ADCX_ZERO_DFLT }, + { CS53L30_INT_MASK, CS53L30_DEVICE_INT_MASK }, +}; + +static bool cs53l30_volatile_register(struct device *dev, unsigned int reg) +{ + if (reg == CS53L30_IS) + return true; + else + return false; +} + +static bool cs53l30_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS53L30_DEVID_AB: + case CS53L30_DEVID_CD: + case CS53L30_DEVID_E: + case CS53L30_REVID: + case CS53L30_PWRCTL: + case CS53L30_MCLKCTL: + case CS53L30_INT_SR_CTL: + case CS53L30_MICBIAS_CTL: + case CS53L30_ASPCFG_CTL: + case CS53L30_ASP1_CTL: + case CS53L30_ASP1_TDMTX_CTL1: + case CS53L30_ASP1_TDMTX_CTL2: + case CS53L30_ASP1_TDMTX_CTL3: + case CS53L30_ASP1_TDMTX_CTL4: + case CS53L30_ASP1_TDMTX_EN1: + case CS53L30_ASP1_TDMTX_EN2: + case CS53L30_ASP1_TDMTX_EN3: + case CS53L30_ASP1_TDMTX_EN4: + case CS53L30_ASP1_TDMTX_EN5: + case CS53L30_ASP1_TDMTX_EN6: + case CS53L30_ASP2_CTL: + case CS53L30_SFT_RAMP: + case CS53L30_LRCLK_CTL1: + case CS53L30_LRCLK_CTL2: + case CS53L30_MUTEP_CTL1: + case CS53L30_MUTEP_CTL2: + case CS53L30_INBIAS_CTL1: + case CS53L30_INBIAS_CTL2: + case CS53L30_DMIC1_STR_CTL: + case CS53L30_DMIC2_STR_CTL: + case CS53L30_ADCDMIC1_CTL1: + case CS53L30_ADCDMIC1_CTL2: + case CS53L30_ADC1_CTL3: + case CS53L30_ADC1_NG_CTL: + case CS53L30_ADC1A_AFE_CTL: + case CS53L30_ADC1B_AFE_CTL: + case CS53L30_ADC1A_DIG_VOL: + case CS53L30_ADC1B_DIG_VOL: + case CS53L30_ADCDMIC2_CTL1: + case CS53L30_ADCDMIC2_CTL2: + case CS53L30_ADC2_CTL3: + case CS53L30_ADC2_NG_CTL: + case CS53L30_ADC2A_AFE_CTL: + case CS53L30_ADC2B_AFE_CTL: + case CS53L30_ADC2A_DIG_VOL: + case CS53L30_ADC2B_DIG_VOL: + case CS53L30_INT_MASK: + return true; + default: + return false; + } +} + +static DECLARE_TLV_DB_SCALE(adc_boost_tlv, 0, 2000, 0); +static DECLARE_TLV_DB_SCALE(adc_ng_boost_tlv, 0, 3000, 0); +static DECLARE_TLV_DB_SCALE(pga_tlv, -600, 50, 0); + +static DECLARE_TLV_DB_SCALE(dig_tlv, -9600, 100, 1); + +static const char * const input1_sel_text[] = { "DMIC1 On AB In", + "DMIC1 On A In", "DMIC1 On B In", "ADC1 On AB In", "ADC1 On A In", + "ADC1 On B In", "DMIC1 Off ADC1 Off", }; + +unsigned int const input1_sel_values[] = { CS53L30_DMIC1_ON_AB_IN, + CS53L30_DMIC1_ON_A_IN, CS53L30_DMIC1_ON_B_IN, CS53L30_ADC1_ON_AB_IN, + CS53L30_ADC1_ON_A_IN, CS53L30_ADC1_ON_B_IN, CS53L30_D1_OFF_A1_OFF, }; + +static const char * const input2_sel_text[] = { "DMIC2 On AB In", + "DMIC2 On A In", "DMIC2 On B In", "ADC2 On AB In", "ADC2 On A In", + "ADC2 On B In", "DMIC2 Off ADC2 Off", }; + +unsigned int const input2_sel_values[] = { CS53L30_DMIC2_ON_AB_IN, + CS53L30_DMIC2_ON_A_IN, CS53L30_DMIC2_ON_B_IN, CS53L30_ADC2_ON_AB_IN, + CS53L30_ADC2_ON_A_IN, CS53L30_ADC2_ON_B_IN, CS53L30_D2_OFF_A2_OFF, }; + +static const char * const input1_route_sel_text[] = { "ADC1_SEL", + "DMIC1_SEL" }; + +static const struct soc_enum input1_route_sel_enum = + SOC_ENUM_SINGLE(CS53L30_ADCDMIC1_CTL1, 0, + ARRAY_SIZE(input1_route_sel_text), input1_route_sel_text); + +static SOC_VALUE_ENUM_SINGLE_DECL(input1_sel_enum, CS53L30_ADCDMIC1_CTL1, 0, + CS53L30_A1_D1_PDN_MASK, input1_sel_text, input1_sel_values); + +static const struct snd_kcontrol_new input1_route_sel_mux = + SOC_DAPM_ENUM("Input 1 Route", input1_route_sel_enum); + +static const char * const input2_route_sel_text[] = { "ADC2_SEL", + "DMIC2_SEL" }; + +/* Note: CS53L30_ADCDMIC1_CTL1 CH_TYPE controls inputs 1 and 2 */ +static const struct soc_enum input2_route_sel_enum = + SOC_ENUM_SINGLE(CS53L30_ADCDMIC1_CTL1, 0, + ARRAY_SIZE(input2_route_sel_text), input2_route_sel_text); + +static SOC_VALUE_ENUM_SINGLE_DECL(input2_sel_enum, CS53L30_ADCDMIC2_CTL1, 0, + CS53L30_A1_D1_PDN_MASK, input2_sel_text, input2_sel_values); + +static const struct snd_kcontrol_new input2_route_sel_mux = + SOC_DAPM_ENUM("Input 2 Route", input2_route_sel_enum); + +/* + * TB = 6144*(MCLK(int) scaling factor)/MCLK(internal) + * TB - Time base + * NOTE: If MCLK_INT_SCALE = 0, then TB=1 + */ +static const char * const cs53l30_ng_delay_text[] = { + "TB*50ms", "TB*100ms", "TB*150ms", "TB*200ms" }; + +static const struct soc_enum adc1_ng_delay_enum = + SOC_ENUM_SINGLE(CS53L30_ADC1_NG_CTL, 0, + ARRAY_SIZE(cs53l30_ng_delay_text), cs53l30_ng_delay_text); + +static const struct soc_enum adc2_ng_delay_enum = + SOC_ENUM_SINGLE(CS53L30_ADC2_NG_CTL, 0, + ARRAY_SIZE(cs53l30_ng_delay_text), cs53l30_ng_delay_text); + +/* The noise gate threshold selected will depend on NG Boost */ +static const char * const cs53l30_ng_thres_text[] = { + "-64dB/-34dB", "-66dB/-36dB", "-70dB/-40dB", "-73dB/-43dB", + "-76dB/-46dB", "-82dB/-52dB", "-58dB", "-64dB"}; + +static const struct soc_enum adc1_ng_thres_enum = + SOC_ENUM_SINGLE(CS53L30_ADC1_NG_CTL, 2, + ARRAY_SIZE(cs53l30_ng_thres_text), cs53l30_ng_thres_text); + +static const struct soc_enum adc2_ng_thres_enum = + SOC_ENUM_SINGLE(CS53L30_ADC2_NG_CTL, 2, + ARRAY_SIZE(cs53l30_ng_thres_text), cs53l30_ng_thres_text); + +/* ADC Preamp gain select */ +static const char * const cs53l30_preamp_gain_sel_text[] = { + "0dB", "10dB", "20dB"}; + +static const struct soc_enum adc1a_preamp_gain_enum = + SOC_ENUM_SINGLE(CS53L30_ADC1A_AFE_CTL, 6, + ARRAY_SIZE(cs53l30_preamp_gain_sel_text), + cs53l30_preamp_gain_sel_text); + +static const struct soc_enum adc1b_preamp_gain_enum = + SOC_ENUM_SINGLE(CS53L30_ADC1B_AFE_CTL, 6, + ARRAY_SIZE(cs53l30_preamp_gain_sel_text), + cs53l30_preamp_gain_sel_text); + +static const struct soc_enum adc2a_preamp_gain_enum = + SOC_ENUM_SINGLE(CS53L30_ADC2A_AFE_CTL, 6, + ARRAY_SIZE(cs53l30_preamp_gain_sel_text), + cs53l30_preamp_gain_sel_text); + +static const struct soc_enum adc2b_preamp_gain_enum = + SOC_ENUM_SINGLE(CS53L30_ADC2B_AFE_CTL, 6, + ARRAY_SIZE(cs53l30_preamp_gain_sel_text), + cs53l30_preamp_gain_sel_text); + +/* Set MIC Bias Voltage Control */ +static const char * const cs53l30_micbias_text[] = { + "HiZ", "1.8V", "2.75V"}; + +static const struct soc_enum micbias_enum = + SOC_ENUM_SINGLE(CS53L30_MICBIAS_CTL, 0, + ARRAY_SIZE(cs53l30_micbias_text), + cs53l30_micbias_text); + +/* Corner frequencies are with an Fs of 48kHz. */ +static const char * const hpf_corner_freq_text[] = { + "1.86Hz", "120Hz", "235Hz", "466Hz"}; + +static const struct soc_enum adc1_hpf_enum = + SOC_ENUM_SINGLE(CS53L30_ADC1_CTL3, 1, + ARRAY_SIZE(hpf_corner_freq_text), hpf_corner_freq_text); + +static const struct soc_enum adc2_hpf_enum = + SOC_ENUM_SINGLE(CS53L30_ADC2_CTL3, 1, + ARRAY_SIZE(hpf_corner_freq_text), hpf_corner_freq_text); + +static const struct snd_kcontrol_new cs53l30_snd_controls[] = { + SOC_SINGLE("Digital Soft-Ramp Switch", CS53L30_SFT_RAMP, 5, 1, 0), + SOC_SINGLE("ADC1 Noise Gate Ganging Switch", + CS53L30_ADC1_CTL3, 0, 1, 0), + SOC_SINGLE("ADC2 Noise Gate Ganging Switch", + CS53L30_ADC2_CTL3, 0, 1, 0), + SOC_SINGLE("ADC1A Noise Gate Enable Switch", + CS53L30_ADC1_NG_CTL, 6, 1, 0), + SOC_SINGLE("ADC1B Noise Gate Enable Switch", + CS53L30_ADC1_NG_CTL, 7, 1, 0), + SOC_SINGLE("ADC2A Noise Gate Enable Switch", + CS53L30_ADC2_NG_CTL, 6, 1, 0), + SOC_SINGLE("ADC2B Noise Gate Enable Switch", + CS53L30_ADC2_NG_CTL, 7, 1, 0), + SOC_SINGLE("ADC1 Notch Filter Switch", + CS53L30_ADCDMIC1_CTL2, 7, 1, 1), + SOC_SINGLE("ADC2 Notch Filter Switch", + CS53L30_ADCDMIC2_CTL2, 7, 1, 1), + SOC_SINGLE("ADC1A Invert Switch", + CS53L30_ADCDMIC1_CTL2, 4, 1, 0), + SOC_SINGLE("ADC1B Invert Switch", + CS53L30_ADCDMIC1_CTL2, 5, 1, 0), + SOC_SINGLE("ADC2A Invert Switch", + CS53L30_ADCDMIC2_CTL2, 4, 1, 0), + SOC_SINGLE("ADC2B Invert Switch", + CS53L30_ADCDMIC2_CTL2, 5, 1, 0), + + SOC_SINGLE_TLV("ADC1A Digital Boost Volume", + CS53L30_ADCDMIC1_CTL2, 0, 1, 0, adc_boost_tlv), + SOC_SINGLE_TLV("ADC1B Digital Boost Volume", + CS53L30_ADCDMIC1_CTL2, 1, 1, 0, adc_boost_tlv), + SOC_SINGLE_TLV("ADC2A Digital Boost Volume", + CS53L30_ADCDMIC2_CTL2, 0, 1, 0, adc_boost_tlv), + SOC_SINGLE_TLV("ADC2B Digital Boost Volume", + CS53L30_ADCDMIC2_CTL2, 1, 1, 0, adc_boost_tlv), + SOC_SINGLE_TLV("ADC1 NG Boost Volume", + CS53L30_ADC1_NG_CTL, 5, 1, 0, adc_ng_boost_tlv), + SOC_SINGLE_TLV("ADC2 NG Boost Volume", + CS53L30_ADC2_NG_CTL, 5, 1, 0, adc_ng_boost_tlv), + + SOC_ENUM("Input 1 Channel Select", input1_sel_enum), + SOC_ENUM("Input 2 Channel Select", input2_sel_enum), + + SOC_ENUM("ADC1 HPF Select", adc1_hpf_enum), + SOC_ENUM("ADC2 HPF Select", adc2_hpf_enum), + SOC_ENUM("ADC1 NG Threshold", adc1_ng_thres_enum), + SOC_ENUM("ADC2 NG Threshold", adc2_ng_thres_enum), + SOC_ENUM("ADC1 NG Delay", adc1_ng_delay_enum), + SOC_ENUM("ADC2 NG Delay", adc2_ng_delay_enum), + SOC_ENUM("ADC1A Pre Amp Gain", adc1a_preamp_gain_enum), + SOC_ENUM("ADC1B Pre Amp Gain", adc1b_preamp_gain_enum), + SOC_ENUM("ADC2A Pre Amp Gain", adc2a_preamp_gain_enum), + SOC_ENUM("ADC2B Pre Amp Gain", adc2b_preamp_gain_enum), + SOC_ENUM("Mic Bias Voltage Select", micbias_enum), + + SOC_SINGLE_SX_TLV("ADC1A PGA Volume", + CS53L30_ADC1A_AFE_CTL, 0, 0x34, 0x18, pga_tlv), + SOC_SINGLE_SX_TLV("ADC1B PGA Volume", + CS53L30_ADC1B_AFE_CTL, 0, 0x34, 0x18, pga_tlv), + SOC_SINGLE_SX_TLV("ADC2A PGA Volume", + CS53L30_ADC2A_AFE_CTL, 0, 0x34, 0x18, pga_tlv), + SOC_SINGLE_SX_TLV("ADC2B PGA Volume", + CS53L30_ADC2B_AFE_CTL, 0, 0x34, 0x18, pga_tlv), + + SOC_SINGLE_SX_TLV("ADC1A Digital Volume", + CS53L30_ADC1A_DIG_VOL, 0, 0xA0, 0x0C, dig_tlv), + SOC_SINGLE_SX_TLV("ADC1B Digital Volume", + CS53L30_ADC1B_DIG_VOL, 0, 0xA0, 0x0C, dig_tlv), + SOC_SINGLE_SX_TLV("ADC2A Digital Volume", + CS53L30_ADC2A_DIG_VOL, 0, 0xA0, 0x0C, dig_tlv), + SOC_SINGLE_SX_TLV("ADC2B Digital Volume", + CS53L30_ADC2B_DIG_VOL, 0, 0xA0, 0x0C, dig_tlv), +}; + +static int cs53l30_asp_sdout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(priv->regmap, CS53L30_ASP1_CTL, + CS53L30_ASP1_3ST, 0); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(priv->regmap, CS53L30_ASP1_CTL, + CS53L30_ASP1_3ST, 1); + break; + default: + dev_err(codec->dev, "Invalid event = 0x%x\n", event); + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dapm_widget cs53l30_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("IN1_DMIC1"), + SND_SOC_DAPM_INPUT("IN2"), + SND_SOC_DAPM_INPUT("IN3_DMIC2"), + SND_SOC_DAPM_INPUT("IN4"), + SND_SOC_DAPM_SUPPLY("MIC1 Bias", CS53L30_MICBIAS_CTL, 4, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC2 Bias", CS53L30_MICBIAS_CTL, 5, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC3 Bias", CS53L30_MICBIAS_CTL, 6, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC4 Bias", CS53L30_MICBIAS_CTL, 7, 1, NULL, 0), + + SND_SOC_DAPM_AIF_OUT_E("ASP_SDOUT1", NULL, 0, CS53L30_ASP1_CTL, + CS53L30_ASP1_SDOUT_PDN, 1, + cs53l30_asp_sdout_event, + (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD)), + SND_SOC_DAPM_AIF_OUT_E("ASP_SDOUT2", NULL, 0, CS53L30_ASP2_CTL, + CS53L30_ASP2_SDOUT_PDN, 1, + cs53l30_asp_sdout_event, + (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD)), + + SND_SOC_DAPM_MUX("Input Mux 1", SND_SOC_NOPM, 0, 0, + &input1_route_sel_mux), + SND_SOC_DAPM_MUX("Input Mux 2", SND_SOC_NOPM, 0, 0, + &input2_route_sel_mux), + + SND_SOC_DAPM_ADC("ADC1A", NULL, CS53L30_ADCDMIC1_CTL1, 6, 1), + SND_SOC_DAPM_ADC("ADC1B", NULL, CS53L30_ADCDMIC1_CTL1, 7, 1), + SND_SOC_DAPM_ADC("ADC2A", NULL, CS53L30_ADCDMIC2_CTL1, 6, 1), + SND_SOC_DAPM_ADC("ADC2B", NULL, CS53L30_ADCDMIC2_CTL1, 7, 1), + SND_SOC_DAPM_ADC("DMIC1", NULL, CS53L30_ADCDMIC1_CTL1, 2, 1), + SND_SOC_DAPM_ADC("DMIC2", NULL, CS53L30_ADCDMIC2_CTL1, 2, 1), +}; + +static const struct snd_soc_dapm_route cs53l30_audio_map[] = { + + /* ADC Input Paths */ + {"ADC1A", NULL, "IN1_DMIC1"}, + {"Input Mux 1", "ADC1_SEL", "ADC1A"}, + {"ADC1B", NULL, "IN2"}, + + {"ADC2A", NULL, "IN3_DMIC2"}, + {"Input Mux 2", "ADC2_SEL", "ADC2A"}, + {"ADC2B", NULL, "IN4"}, + + /* MIC Bias Paths */ + {"ADC1A", NULL, "MIC1 Bias"}, + {"ADC1B", NULL, "MIC2 Bias"}, + {"ADC2A", NULL, "MIC3 Bias"}, + {"ADC2B", NULL, "MIC4 Bias"}, + + /* DMIC Paths */ + {"DMIC1", NULL, "IN1_DMIC1"}, + {"Input Mux 1", "DMIC1_SEL", "DMIC1"}, + + {"DMIC2", NULL, "IN3_DMIC2"}, + {"Input Mux 2", "DMIC2_SEL", "DMIC2"}, + + /* Output Paths */ + {"ASP_SDOUT1", NULL, "ADC1A" }, + {"ASP_SDOUT1", NULL, "Input Mux 1"}, + {"ASP_SDOUT1", NULL, "ADC1B"}, + + {"ASP_SDOUT2", NULL, "ADC2A"}, + {"ASP_SDOUT2", NULL, "Input Mux 2"}, + {"ASP_SDOUT2", NULL, "ADC2B"}, + + {"ASP1 Capture", NULL, "ASP_SDOUT1"}, + {"ASP2 Capture", NULL, "ASP_SDOUT2"}, +}; + +struct cs53l30_mclk_div { + u32 mclk; + u32 srate; + u8 asp_rate; + u8 internal_fs_ratio; + u8 mclk_int_scale; +}; + +static struct cs53l30_mclk_div cs53l30_mclk_coeffs[] = { + /* NOTE: Enable MCLK_INT_SCALE to save power. */ + + /* MCLK, Sample Rate, asp_rate, internal_fs_ratio, mclk_int_scale */ + {5644800, 11025, 0x4, 1, 1}, + {5644800, 22050, 0x8, 1, 1}, + {5644800, 44100, 0xC, 1, 1}, + + {6000000, 8000, 0x1, 0, 1}, + {6000000, 11025, 0x2, 0, 1}, + {6000000, 12000, 0x4, 0, 1}, + {6000000, 16000, 0x5, 0, 1}, + {6000000, 22050, 0x6, 0, 1}, + {6000000, 24000, 0x8, 0, 1}, + {6000000, 32000, 0x9, 0, 1}, + {6000000, 44100, 0xA, 0, 1}, + {6000000, 48000, 0xC, 0, 1}, + + {6144000, 8000, 0x1, 1, 1}, + {6144000, 11025, 0x2, 1, 1}, + {6144000, 12000, 0x4, 1, 1}, + {6144000, 16000, 0x5, 1, 1}, + {6144000, 22050, 0x6, 1, 1}, + {6144000, 24000, 0x8, 1, 1}, + {6144000, 32000, 0x9, 1, 1}, + {6144000, 44100, 0xA, 1, 1}, + {6144000, 48000, 0xC, 1, 1}, + + {6400000, 8000, 0x1, 1, 1}, + {6400000, 11025, 0x2, 1, 1}, + {6400000, 12000, 0x4, 1, 1}, + {6400000, 16000, 0x5, 1, 1}, + {6400000, 22050, 0x6, 1, 1}, + {6400000, 24000, 0x8, 1, 1}, + {6400000, 32000, 0x9, 1, 1}, + {6400000, 44100, 0xA, 1, 1}, + {6400000, 48000, 0xC, 1, 1}, +}; + +struct cs53l30_mclkx_div { + u32 mclkx; + u8 ratio; + u8 mclkdiv; +}; + +static struct cs53l30_mclkx_div cs53l30_mclkx_coeffs[] = { + {5644800, 1, 0}, + {6000000, 1, 0}, + {6144000, 1, 0}, + {11289600, 2, 1}, + {12288000, 2, 1}, + {12000000, 2, 1}, + {19200000, 3, 2}, +}; + +static int cs53l30_get_mclkx_coeff(int mclkx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs53l30_mclkx_coeffs); i++) { + if (cs53l30_mclkx_coeffs[i].mclkx == mclkx) + return i; + } + return -EINVAL; +} + +static int cs53l30_get_mclk_coeff(int mclk, int srate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs53l30_mclk_coeffs); i++) { + if (cs53l30_mclk_coeffs[i].mclk == mclk && + cs53l30_mclk_coeffs[i].srate == srate) + return i; + } + return -EINVAL; + +} + +static int cs53l30_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(codec); + + int mclkx_coeff; + u32 mclk; + unsigned int mclk_ctl; + + /* MCLKX -> MCLK */ + mclkx_coeff = cs53l30_get_mclkx_coeff(freq); + if (mclkx_coeff < 0) + return mclkx_coeff; + + mclk = cs53l30_mclkx_coeffs[mclkx_coeff].mclkx / + cs53l30_mclkx_coeffs[mclkx_coeff].ratio; + + regmap_read(priv->regmap, CS53L30_MCLKCTL, &mclk_ctl); + mclk_ctl &= ~CS53L30_MCLK_DIV; + mclk_ctl |= cs53l30_mclkx_coeffs[mclkx_coeff].mclkdiv; + + regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, CS53L30_MCLK_DIV, + mclk_ctl << CS53L30_MCLK_DIV); + priv->mclk = mclk; + return 0; +} + +static int cs53l30_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(codec); + unsigned int asp_config_ctl; + + regmap_read(priv->regmap, CS53L30_ASPCFG_CTL, &asp_config_ctl); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + asp_config_ctl |= CS53L30_ASP_MS; + break; + + case SND_SOC_DAIFMT_CBS_CFS: + asp_config_ctl &= ~CS53L30_ASP_MS; + break; + + default: + return -EINVAL; + } + + /* Check to see if the SCLK is inverted */ + if (fmt & (SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_IB_IF)) + asp_config_ctl |= CS53L30_ASP_SCLK_INV; + else + asp_config_ctl &= ~CS53L30_ASP_SCLK_INV; + + priv->asp_config_ctl = asp_config_ctl; + return 0; +} + +static int cs53l30_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(codec); + int mclk_coeff; + int srate = params_rate(params); + unsigned int int_sr_ctl, mclk_ctl; + + /* MCLK -> srate */ + mclk_coeff = cs53l30_get_mclk_coeff(priv->mclk, srate); + if (mclk_coeff < 0) + return -EINVAL; + + regmap_read(priv->regmap, CS53L30_INT_SR_CTL, &int_sr_ctl); + if (cs53l30_mclk_coeffs[mclk_coeff].internal_fs_ratio) + int_sr_ctl |= CS53L30_INTRNL_FS_RATIO; + else + int_sr_ctl &= ~CS53L30_INTRNL_FS_RATIO; + regmap_update_bits(priv->regmap, CS53L30_INT_SR_CTL, + CS53L30_INTL_FS_RAT_MSK, int_sr_ctl << + CS53L30_INTL_FS_RAT_SFT); + + regmap_read(priv->regmap, CS53L30_MCLKCTL, &mclk_ctl); + if (cs53l30_mclk_coeffs[mclk_coeff].mclk_int_scale) + mclk_ctl |= CS53L30_MCLK_INT_SCALE; + else + mclk_ctl &= ~CS53L30_MCLK_INT_SCALE; + regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, + CS53L30_MCK_INT_SCL_MSK, mclk_ctl << + CS53L30_MCK_INT_SCL_SFT); + + priv->asp_config_ctl &= CS53L30_ASP_CNFG_MASK; + priv->asp_config_ctl |= (cs53l30_mclk_coeffs[mclk_coeff].asp_rate + & CS53L30_ASP_RATE_MASK); + regmap_update_bits(priv->regmap, CS53L30_ASPCFG_CTL, + CS53L30_ASP_RATE_MASK, priv->asp_config_ctl); + + return 0; +} + +static int cs53l30_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(codec); + unsigned int reg; + int i, inter_max_check; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level == SND_SOC_BIAS_STANDBY) + regmap_update_bits(priv->regmap, CS53L30_PWRCTL, + CS53L30_PDN_LP, 0); + break; + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level == SND_SOC_BIAS_OFF) { + regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, + CS53L30_MCLK_DIS, 0); + regmap_update_bits(priv->regmap, CS53L30_PWRCTL, + CS53L30_PDN_ULP, 0); + msleep(50); + } else { + regmap_update_bits(priv->regmap, CS53L30_PWRCTL, + CS53L30_PDN_LP, CS53L30_PDN_LP); + } + break; + + case SND_SOC_BIAS_OFF: + regmap_update_bits(priv->regmap, CS53L30_INT_MASK, + CS53L30_PDN_DONE, 0); + /* If digital softramp is set, the amount of time required + * for power down increases and depends on the digital + * volume setting. + */ + + /* Set the max possible time if digsft is set */ + regmap_read(priv->regmap, CS53L30_SFT_RAMP, ®); + if (reg & CS53L30_DIGSFT) + inter_max_check = CS53L30_PDN_POLL_MAX; + else + inter_max_check = 10; + + regmap_update_bits(priv->regmap, CS53L30_PWRCTL, + CS53L30_PDN_ULP, CS53L30_PDN_ULP); + msleep(20); /* PDN_DONE will take a min of 20ms to be set.*/ + regmap_read(priv->regmap, CS53L30_IS, ®); /* Clr status */ + for (i = 0; i < inter_max_check; i++) { + if (inter_max_check < 10) { + usleep_range(1000, 1100); + regmap_read(priv->regmap, CS53L30_IS, ®); + if (reg & CS53L30_PDN_DONE) + break; + } else { + usleep_range(10000, 10100); + regmap_read(priv->regmap, CS53L30_IS, ®); + if (reg & CS53L30_PDN_DONE) + break; + } + } + /* PDN_DONE is set. We now can disable the MCLK */ + regmap_update_bits(priv->regmap, CS53L30_INT_MASK, + CS53L30_PDN_DONE, CS53L30_PDN_DONE); + regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, + CS53L30_MCLK_DIS, CS53L30_MCLK_DIS); + break; + } + + return 0; +} + +static int cs53l30_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(codec); + + return regmap_update_bits(priv->regmap, CS53L30_ASP1_CTL, + CS53L30_ASP1_3ST, + (CS53L30_ASP1_3ST_VAL(tristate) & + CS53L30_ASP1_3ST)); +} + +unsigned int const cs53l30_src_rates[] = { + 8000, 11025, 12000, 16000, 22050, + 24000, 32000, 44100, 48000 +}; + +static struct snd_pcm_hw_constraint_list src_constraints = { + .count = ARRAY_SIZE(cs53l30_src_rates), + .list = cs53l30_src_rates, +}; + +static int cs53l30_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &src_constraints); + + return 0; +} + +/* SNDRV_PCM_RATE_KNOT -> 12000, 24000 Hz, limit with constraint list */ +#define CS53L30_RATES (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT) + +#define CS53L30_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops cs53l30_ops = { + .startup = cs53l30_pcm_startup, + .hw_params = cs53l30_pcm_hw_params, + .set_fmt = cs53l30_set_dai_fmt, + .set_sysclk = cs53l30_set_sysclk, + .set_tristate = cs53l30_set_tristate, +}; + +static struct snd_soc_dai_driver cs53l30_dai[] = { + { + .name = "cs53l30-asp1", + .id = CS53L30_ASP1, + .capture = { + .stream_name = "ASP1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = CS53L30_RATES, + .formats = CS53L30_FORMATS, + }, + .ops = &cs53l30_ops, + .symmetric_rates = 1, + }, + { + .name = "cs53l30-asp2", + .id = CS53L30_ASP2, + .capture = { + .stream_name = "ASP2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = CS53L30_RATES, + .formats = CS53L30_FORMATS, + }, + .ops = &cs53l30_ops, + .symmetric_rates = 1, + } +}; + +static struct snd_soc_codec_driver soc_codec_dev_cs53l30 = { + .set_bias_level = cs53l30_set_bias_level, + + .dapm_widgets = cs53l30_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs53l30_dapm_widgets), + .dapm_routes = cs53l30_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs53l30_audio_map), + + .controls = cs53l30_snd_controls, + .num_controls = ARRAY_SIZE(cs53l30_snd_controls), +}; + +static struct regmap_config cs53l30_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS53L30_MAX_REGISTER, + .reg_defaults = cs53l30_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs53l30_reg_defaults), + .volatile_reg = cs53l30_volatile_register, + .readable_reg = cs53l30_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int cs53l30_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cs53l30_private *cs53l30; + int ret = 0; + unsigned int devid = 0; + unsigned int reg; + + cs53l30 = devm_kzalloc(&client->dev, + sizeof(struct cs53l30_private), GFP_KERNEL); + if (!cs53l30) { + dev_err(&client->dev, "could not allocate codec\n"); + return -ENOMEM; + } + + /* Reset the Device */ + cs53l30->reset_gpio = devm_gpiod_get_optional(&client->dev, + "reset", GPIOD_OUT_LOW); + if (IS_ERR(cs53l30->reset_gpio)) + return PTR_ERR(cs53l30->reset_gpio); + + if (cs53l30->reset_gpio) + gpiod_set_value_cansleep(cs53l30->reset_gpio, 1); + + i2c_set_clientdata(client, cs53l30); + + cs53l30->mclk = 0; + + cs53l30->regmap = devm_regmap_init_i2c(client, &cs53l30_regmap); + if (IS_ERR(cs53l30->regmap)) { + ret = PTR_ERR(cs53l30->regmap); + dev_err(&client->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + /* initialize codec */ + ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_AB, ®); + devid = reg << 12; + + ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_CD, ®); + devid |= reg << 4; + + ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + + if (devid != CS53L30_DEVID) { + ret = -ENODEV; + dev_err(&client->dev, + "CS53L30 Device ID (%X). Expected %X\n", + devid, CS53L30_DEVID); + return ret; + } + + ret = regmap_read(cs53l30->regmap, CS53L30_REVID, ®); + if (ret < 0) { + dev_err(&client->dev, "Get Revision ID failed\n"); + return ret; + } + + dev_info(&client->dev, + "Cirrus Logic CS53L30, Revision: %02X\n", reg & 0xFF); + + ret = snd_soc_register_codec(&client->dev, + &soc_codec_dev_cs53l30, cs53l30_dai, + ARRAY_SIZE(cs53l30_dai)); + return ret; +} + +static int cs53l30_i2c_remove(struct i2c_client *client) +{ + struct cs53l30_private *cs53l30 = i2c_get_clientdata(client); + + snd_soc_unregister_codec(&client->dev); + + /* Hold down reset */ + if (cs53l30->reset_gpio) + gpiod_set_value_cansleep(cs53l30->reset_gpio, 0); + + return 0; +} + +#ifdef CONFIG_PM +static int cs53l30_runtime_suspend(struct device *dev) +{ + struct cs53l30_private *cs53l30 = dev_get_drvdata(dev); + + regcache_cache_only(cs53l30->regmap, true); + + /* Hold down reset */ + if (cs53l30->reset_gpio) + gpiod_set_value_cansleep(cs53l30->reset_gpio, 0); + + return 0; +} + +static int cs53l30_runtime_resume(struct device *dev) +{ + struct cs53l30_private *cs53l30 = dev_get_drvdata(dev); + + if (cs53l30->reset_gpio) + gpiod_set_value_cansleep(cs53l30->reset_gpio, 1); + + regcache_cache_only(cs53l30->regmap, false); + regcache_sync(cs53l30->regmap); + + return 0; +} +#endif + +static const struct dev_pm_ops cs53l30_runtime_pm = { + SET_RUNTIME_PM_OPS(cs53l30_runtime_suspend, cs53l30_runtime_resume, + NULL) +}; + +static const struct of_device_id cs53l30_of_match[] = { + { .compatible = "cirrus,cs53l30", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, cs53l30_of_match); + +static const struct i2c_device_id cs53l30_id[] = { + {"cs53l30", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs53l30_id); + +static struct i2c_driver cs53l30_i2c_driver = { + .driver = { + .name = "cs53l30", + }, + .id_table = cs53l30_id, + .probe = cs53l30_i2c_probe, + .remove = cs53l30_i2c_remove, + +}; + +module_i2c_driver(cs53l30_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS53L30 driver"); +MODULE_AUTHOR("Paul Handrigan, Cirrus Logic Inc, Paul.Handrigan@cirrus.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs53l30.h b/sound/soc/codecs/cs53l30.h new file mode 100644 index 0000000..116e358 +++ b/sound/soc/codecs/cs53l30.h @@ -0,0 +1,282 @@ +/* + * ALSA SoC CS53L30 codec driver + * + * Copyright 2015 Cirrus Logic, Inc. + * + * Author: Paul Handrigan Paul.Handrigan@cirrus.com, + * Tim Howe Tim.Howe@cirrus.com + * + * 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. + * + */ + +#ifndef __CS53L30_H__ +#define __CS53L30_H__ + +/* I2C Registers */ +#define CS53L30_DEVID_AB 0x01 /* Device ID A & B [RO]. */ +#define CS53L30_DEVID_CD 0x02 /* Device ID C & D [RO]. */ +#define CS53L30_DEVID_E 0x03 /* Device ID E [RO]. */ +#define CS53L30_REVID 0x05 /* Revision ID [RO]. */ +#define CS53L30_PWRCTL 0x06 /* Power Control. */ +#define CS53L30_MCLKCTL 0x07 /* MCLK Control. */ +#define CS53L30_INT_SR_CTL 0x08 /* Internal Sample Rate Control. */ +#define CS53L30_MICBIAS_CTL 0x0A /* Mic Bias Control. */ +#define CS53L30_ASPCFG_CTL 0x0C /* ASP Config Control. */ +#define CS53L30_ASP1_CTL 0x0D /* ASP1 Control. */ +#define CS53L30_ASP1_TDMTX_CTL1 0x0E /* ASP1 TDM TX Control 1 */ +#define CS53L30_ASP1_TDMTX_CTL2 0x0F /* ASP1 TDM TX Control 2 */ +#define CS53L30_ASP1_TDMTX_CTL3 0x10 /* ASP1 TDM TX Control 3 */ +#define CS53L30_ASP1_TDMTX_CTL4 0x11 /* ASP1 TDM TX Control 4 */ +#define CS53L30_ASP1_TDMTX_EN1 0x12 /* ASP1 TDM TX Enable 1 */ +#define CS53L30_ASP1_TDMTX_EN2 0x13 /* ASP1 TDM TX Enable 2 */ +#define CS53L30_ASP1_TDMTX_EN3 0x14 /* ASP1 TDM TX Enable 3 */ +#define CS53L30_ASP1_TDMTX_EN4 0x15 /* ASP1 TDM TX Enable 4 */ +#define CS53L30_ASP1_TDMTX_EN5 0x16 /* ASP1 TDM TX Enable 5 */ +#define CS53L30_ASP1_TDMTX_EN6 0x17 /* ASP1 TDM TX Enable 6 */ +#define CS53L30_ASP2_CTL 0x18 /* ASP2 Control. */ +#define CS53L30_SFT_RAMP 0x1A /* Soft Ramp Control. */ +#define CS53L30_LRCLK_CTL1 0x1B /* LRCLK Control 1. */ +#define CS53L30_LRCLK_CTL2 0x1C /* LRCLK Control 2. */ +#define CS53L30_MUTEP_CTL1 0x1F /* Mute Pin Control 1. */ +#define CS53L30_MUTEP_CTL2 0x20 /* Mute Pin Control 2. */ +#define CS53L30_INBIAS_CTL1 0x21 /* Input Bias Control 1. */ +#define CS53L30_INBIAS_CTL2 0x22 /* Input Bias Control 2. */ +#define CS53L30_DMIC1_STR_CTL 0x23 /* DMIC1 Stereo Control. */ +#define CS53L30_DMIC2_STR_CTL 0x24 /* DMIC2 Stereo Control. */ +#define CS53L30_ADCDMIC1_CTL1 0x25 /* ADC1/DMIC1 Control 1. */ +#define CS53L30_ADCDMIC1_CTL2 0x26 /* ADC1/DMIC1 Control 2. */ +#define CS53L30_ADC1_CTL3 0x27 /* ADC1 Control 3. */ +#define CS53L30_ADC1_NG_CTL 0x28 /* ADC1 Noise Gate Control. */ +#define CS53L30_ADC1A_AFE_CTL 0x29 /* ADC1A AFE Control. */ +#define CS53L30_ADC1B_AFE_CTL 0x2A /* ADC1B AFE Control. */ +#define CS53L30_ADC1A_DIG_VOL 0x2B /* ADC1A Digital Volume. */ +#define CS53L30_ADC1B_DIG_VOL 0x2C /* ADC1B Digital Volume. */ +#define CS53L30_ADCDMIC2_CTL1 0x2D /* ADC2/DMIC2 Control 1. */ +#define CS53L30_ADCDMIC2_CTL2 0x2E /* ADC2/DMIC2 Control 2. */ +#define CS53L30_ADC2_CTL3 0x2F /* ADC2 Control 3. */ +#define CS53L30_ADC2_NG_CTL 0x30 /* ADC2 Noise Gate Control. */ +#define CS53L30_ADC2A_AFE_CTL 0x31 /* ADC2A AFE Control. */ +#define CS53L30_ADC2B_AFE_CTL 0x32 /* ADC2B AFE Control. */ +#define CS53L30_ADC2A_DIG_VOL 0x33 /* ADC2A Digital Volume. */ +#define CS53L30_ADC2B_DIG_VOL 0x34 /* ADC2B Digital Volume. */ +#define CS53L30_INT_MASK 0x35 /* Interrupt Mask. */ +#define CS53L30_IS 0x36 /* Interrupt Status. */ +#define CS53L30_MAX_REGISTER 0x36 + +/* Device ID */ +#define CS53L30_DEVID 0x53A30 + +/* PDN_DONE Poll Maximum + * If soft ramp is set it will take much longer to power down + * the system. + */ +#define CS53L30_PDN_POLL_MAX 90 + +/* Bitfield Definitions */ + +/* CS53L30_PWRCTL */ +#define CS53L30_PDN_ULP (1 << 7) +#define CS53L30_PDN_LP (1 << 6) +#define CS53L30_DISCHARGE_FILT (1 << 5) +#define CS53L30_THMS_PDN (1 << 4) + +/* CS53L30_MCLKCTL */ +#define CS53L30_MCLK_DIS (1 << 7) +#define CS53L30_MCLK_INT_SCALE (1 << 6) +#define CS53L30_DMIC_DRIVE (1 << 5) +#define CS53L30_MCLK_DIV (3 << 2) +#define CS53L30_MCLK_DIV_DFLT (1 << 2) +#define CS53L30_SYNC_EN (1 << 1) +#define CS53L30_MCK_INT_SCL_MSK 0x40 +#define CS53L30_MCK_INT_SCL_SFT 6 + +/* CS53L30_INT_SR_CTL */ +#define CS53L30_INTRNL_FS_RATIO (1 << 4) +#define CS53L30_INTRNL_FS_DFLT (7 << 2) +#define CS53L30_MCLK_19MHZ_EN (1 << 0) +#define CS53L30_INTL_FS_RAT_MSK 0x10 +#define CS53L30_INTL_FS_RAT_SFT 4 + +/* CS53L30_MICBIAS_CTL */ +#define CS53L30_MIC4_BIAS_PDN (1 << 7) +#define CS53L30_MIC3_BIAS_PDN (1 << 6) +#define CS53L30_MIC2_BIAS_PDN (1 << 5) +#define CS53L30_MIC1_BIAS_PDN (1 << 4) +#define CS53L30_VP_MIN (1 << 2) +#define CS53L30_MIC_BIAS_CTRL (3 << 0) +#define CS53L30_BIAS_ALL_PDN 0xF0 +#define CS53L30_MIC_BIAS_DFLT (CS53L30_BIAS_ALL_PDN | CS53L30_VP_MIN) + +/* CS53L30_ASPCFG_CTL */ +#define CS53L30_ASP_MS (1 << 7) +#define CS53L30_ASP_SCLK_INV (1 << 4) +#define CS53L30_ASP_RATE_48K (3 << 2) +#define CS53L30_ASP_RATE_MASK 0x0F +#define CS53L30_ASP_CNFG_MASK 0xF0 + +/* CS53L30_ASP1_CTL */ +#define CS53L30_ASP1_TDM_PDN (1 << 7) +#define CS53L30_ASP1_SDOUT_PDN (1 << 6) +#define CS53L30_ASP1_3ST (1 << 5) +#define CS53L30_SHIFT_LEFT (1 << 4) +#define CS53L30_ASP1_DRIVE (1 << 0) +#define CS53L30_ASP1_3ST_VAL(x) ((x) << 5) + +/* CS53L30_ASP1_TDMTX_CTL */ +#define CS53L30_ASP1_CHX_TX_ST (1 << 7) +#define CS53L30_ASP1_CHX_TX_LOC 0x3F +#define CS53L30_ASP1_CHTX_SLT47 0x2F +#define CS53L30_ASP_TX_DISABLED 0x00 + +/* CS53L30_ASP2_CTL */ +#define CS53L30_ASP2_SDOUT_PDN (1 << 6) +#define CS53L30_ASP2_DRIVE (1 << 0) +#define CS53L30_ASP2_CTRL_DFLT 0x00 + +/* CS53L30_SFT_RAMP */ +#define CS53L30_DIGSFT (1 << 5) +#define CS53L30_SFT_RMP_DFLT 0x00 + +/* CS53L30_LRCLK_CTL2 */ +#define CS53L30_LRCK_50_NPW (1 << 3) +#define CS53L30_LRCK_TPWH (7 << 0) +#define CS53L30_LRCK_CTLX_DFLT 0x00 + +/* CS53L30_MUTEP_CTL */ +#define CS53L30_MUTE_PDN_ULP (1 << 7) +#define CS53L30_MUTE_PDN_LP (1 << 6) +#define CS53L30_MUTE_M4B_PDN (1 << 4) +#define CS53L30_MUTE_M3B_PDN (1 << 3) +#define CS53L30_MUTE_M2B_PDN (1 << 2) +#define CS53L30_MUTE_M1B_PDN (1 << 1) +#define CS53L30_MUTE_MB_ALL_PDN (1 << 0) +#define CS53L30_MUTE_CTRL1_DFLT 0x00 + +/* CS53L30_MUTEP_CTL2 */ +#define CS53L30_MUTE_PIN_PLRTY (1 << 7) +#define CS53L30_MUTE_ASPTDM_PDN (1 << 6) +#define CS53L30_MTE_ASPSDO2_PDN (1 << 5) +#define CS53L30_MTE_ASPSDO1_PDN (1 << 4) +#define CS53L30_MUTE_ADC2B_PDN (1 << 3) +#define CS53L30_MUTE_ADC2A_PDN (1 << 2) +#define CS53L30_MUTE_ADC1B_PDN (1 << 1) +#define CS53L30_MUTE_ADC1A_PDN (1 << 0) + +/* CS53L30_INBIAS_CTL1 */ +#define CS53L30_IN4M_BIAS (3 << 6) +#define CS53L30_IN4P_BIAS (3 << 4) +#define CS53L30_IN3M_BIAS (3 << 2) +#define CS53L30_IN3P_BIAS (3 << 0) + +/* CS53L30_INBIAS_CTL2 */ +#define CS53L30_IN2M_BIAS (3 << 6) +#define CS53L30_IN2P_BIAS (3 << 4) +#define CS53L30_IN1M_BIAS (3 << 2) +#define CS53L30_IN1P_BIAS (3 << 0) +#define CS53L30_INBIAS_X_DFLT 0xAA + +/* CS53L30_DMIC1_STR_CTL */ +#define CS53L30_DMIC1_STERO_EN (1 << 5) +#define CS53L30_DMIC1_ST_DFLT 0xA8 + +/* CS53L30_DMIC2_STR_CTL */ +#define CS53L30_DMIC2_STERO_EN (1 << 5) +#define CS53L30_DMIC2_ST_DFLT 0xEC + +/* CS53L30_ADCDMIC1_CTL1 */ +#define CS53L30_ADC1B_PDN (1 << 7) +#define CS53L30_ADC1A_PDN (1 << 6) +#define CS53L30_DMIC1_PDN (1 << 2) +#define CS53L30_DMIC1_SCLK_DIV (1 << 1) +#define CS53L30_CH_TYPE (1 << 0) +#define CS53L30_DMIC1_ON_AB_IN (CS53L30_CH_TYPE) +#define CS53L30_DMIC1_ON_A_IN (CS53L30_ADC1B_PDN | CS53L30_CH_TYPE) +#define CS53L30_DMIC1_ON_B_IN (CS53L30_ADC1A_PDN | CS53L30_CH_TYPE) +#define CS53L30_ADC1_ON_AB_IN (CS53L30_DMIC1_PDN) +#define CS53L30_ADC1_ON_A_IN (CS53L30_ADC1B_PDN | CS53L30_DMIC1_PDN) +#define CS53L30_ADC1_ON_B_IN (CS53L30_ADC1A_PDN | CS53L30_DMIC1_PDN) +#define CS53L30_D1_OFF_A1_OFF (CS53L30_ADC1A_PDN | \ + CS53L30_ADC1B_PDN | \ + CS53L30_DMIC1_PDN) +#define CS53L30_A1_D1_PDN_MASK 0xFF + +/* CS53L30_ADCDMIC1_CTL2 */ +#define CS53L30_ADC1_NOTCH_DIS (1 << 7) +#define CS53L30_ADC1B_INV (1 << 5) +#define CS53L30_ADC1A_INV (1 << 4) +#define CS53L30_ADC1B_DIG_BOOST (1 << 1) +#define CS53L30_ADC1A_DIG_BOOST (1 << 0) +#define CS53L30_A1_D1_CTL2_DFLT 0x00 + +/* CS53L30_ADC1_CTL3 */ +#define CS53L30_ADC1_HPF_EN (1 << 3) +#define CS53L30_ADC1_HPF_CF (3 << 1) +#define CS53L30_ADC1_NG_ALL (1 << 0) + +/* CS53L30_ADC1_NG_CTL */ +#define CS53L30_ADC1B_NG (1 << 7) +#define CS53L30_ADC1A_NG (1 << 6) +#define CS53L30_ADC1_NG_BOOST (1 << 5) +#define CS53L30_ADC1_NG_THRESH (7 << 2) +#define CS53L30_ADC1_NG_DELAY (3 << 0) +#define CS53L30_ADCX_ZERO_DFLT 0x00 + +/* CS53L30_ADC1A_AFE_CTL */ +#define CS53L30_ADC1A_PREAMP (3 << 6) +#define CS53L30_ADC1A_PGA_VOL 0x3F + +/* CS53L30_ADC1B_AFE_CTL */ +#define CS53L30_ADC1B_PREAMP (3 << 6) +#define CS53L30_ADC1B_PGA_VOL 0x3F + +/* CS53L30_ADCXX_DIG_VOL */ +#define CS53L30_MUTE_DIG_OUT (1 << 7) + +/* CS53L30_ADCDMIC2_CTL1 */ +#define CS53L30_ADC2B_PDN (1 << 7) +#define CS53L30_ADC2A_PDN (1 << 6) +#define CS53L30_DMIC2_PDN (1 << 2) +#define CS53L30_DMIC2_CLKDIV (1 << 1) +#define CS53L30_DMIC2_ON_AB_IN 0x00 /* CH_TYPE must = 1 */ +#define CS53L30_DMIC2_ON_A_IN (CS53L30_ADC2B_PDN) /* CH_TYPE must = 1 */ +#define CS53L30_DMIC2_ON_B_IN (CS53L30_ADC2A_PDN) /* CH_TYPE must = 1 */ +#define CS53L30_ADC2_ON_AB_IN (CS53L30_DMIC2_PDN) /* CH_TYPE must = 0 */ +#define CS53L30_ADC2_ON_A_IN (CS53L30_ADC2B_PDN | \ + CS53L30_DMIC2_PDN) +#define CS53L30_ADC2_ON_B_IN (CS53L30_ADC2A_PDN | \ + CS53L30_DMIC2_PDN) +#define CS53L30_D2_OFF_A2_OFF (CS53L30_ADC2A_PDN | \ + CS53L30_ADC2B_PDN | \ + CS53L30_DMIC2_PDN) + +/* CS53L30_ADCDMIC2_CTL2 */ +#define CS53L30_ADC2_NOTCH_DIS (1 << 7) +#define CS53L30_ADC2B_INV (1 << 5) +#define CS53L30_ADC2A_INV (1 << 4) +#define CS53L30_ADC2B_DIG_BOOST (1 << 1) +#define CS53L30_ADC2A_DIG_BOOST (1 << 0) + +/* CS53L30_ADC2_CTL3 */ +#define CS53L30_ADC2_HPF_EN (1 << 3) +#define CS53L30_ADC2_HPF_CF (3 << 1) +#define CS53L30_ADC2_NG_ALL (1 << 0) + +/* CS53L30_INT */ +#define CS53L30_PDN_DONE (1 << 7) +#define CS53L30_THMS_TRIP (1 << 6) +#define CS53L30_SYNC_DONE (1 << 5) +#define CS53L30_ADC2B_OVFL (1 << 4) +#define CS53L30_ADC2A_OVFL (1 << 3) +#define CS53L30_ADC1B_OVFL (1 << 2) +#define CS53L30_ADC1A_OVFL (1 << 1) +#define CS53L30_MUTE_PIN (1 << 0) +#define CS53L30_DEVICE_INT_MASK 0xFF + +/* Serial Ports */ +#define CS53L30_ASP1 0 +#define CS53L30_ASP2 1 + +#endif /* __CS53L30_H__ */
On Tue, Mar 15, 2016 at 05:47:46PM -0500, tim.howe@cirrus.com wrote:
+/* ADC Preamp gain select */ +static const char * const cs53l30_preamp_gain_sel_text[] = {
- "0dB", "10dB", "20dB"};
+static const struct soc_enum adc1a_preamp_gain_enum =
- SOC_ENUM_SINGLE(CS53L30_ADC1A_AFE_CTL, 6,
ARRAY_SIZE(cs53l30_preamp_gain_sel_text),
cs53l30_preamp_gain_sel_text);
Why are you writing volume controls as enums? Please use normal volume controls with TLV information.
+/* Set MIC Bias Voltage Control */ +static const char * const cs53l30_micbias_text[] = {
- "HiZ", "1.8V", "2.75V"};
Why is this exposed to userspace and not platform data like other bias controls? The voltage to use will normally be determined by the hardware design and the enable/disable should be managed via DAPM.
- case SND_SOC_DAPM_PRE_PMU:
regmap_update_bits(priv->regmap, CS53L30_ASP1_CTL,
CS53L30_ASP1_3ST, 0);
break;
- case SND_SOC_DAPM_POST_PMD:
regmap_update_bits(priv->regmap, CS53L30_ASP1_CTL,
CS53L30_ASP1_3ST, 1);
break;
Could this be a supply widget?
- regmap_read(priv->regmap, CS53L30_MCLKCTL, &mclk_ctl);
- mclk_ctl &= ~CS53L30_MCLK_DIV;
- mclk_ctl |= cs53l30_mclkx_coeffs[mclkx_coeff].mclkdiv;
- regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, CS53L30_MCLK_DIV,
mclk_ctl << CS53L30_MCLK_DIV);
You're using regmap_update_bits() but still implementing a manual read/modify write cycle. Just use regmap_update_bits.
- regmap_read(priv->regmap, CS53L30_ASPCFG_CTL, &asp_config_ctl);
- switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
- case SND_SOC_DAIFMT_CBM_CFM:
asp_config_ctl |= CS53L30_ASP_MS;
- priv->asp_config_ctl = asp_config_ctl;
The driver appears to be mixing use of priv->asp_config_ctl and the physical value, that seems likely to go wrong somewhere along the line. Either the variable is authorative and we should always work from that then write it to the device when needed or the device is authorative but currently we're mixing both.
- regmap_update_bits(priv->regmap, CS53L30_ASPCFG_CTL,
CS53L30_ASP_RATE_MASK, priv->asp_config_ctl);
We also write out via update_bits but don't specify all the bits sometimes...
Hi Mark,
I have two quick questions regarding the DAI and its TDM support.
On Tue, Mar 15, 2016 at 05:47:46PM -0500, tim.howe@cirrus.com wrote:
+static const struct snd_soc_dapm_route cs53l30_audio_map[] = {
- /* ADC Input Paths */
- {"ADC1A", NULL, "IN1_DMIC1"},
- {"Input Mux 1", "ADC1_SEL", "ADC1A"},
- {"ADC1B", NULL, "IN2"},
- {"ADC2A", NULL, "IN3_DMIC2"},
- {"Input Mux 2", "ADC2_SEL", "ADC2A"},
- {"ADC2B", NULL, "IN4"},
- /* MIC Bias Paths */
- {"ADC1A", NULL, "MIC1 Bias"},
- {"ADC1B", NULL, "MIC2 Bias"},
- {"ADC2A", NULL, "MIC3 Bias"},
- {"ADC2B", NULL, "MIC4 Bias"},
- /* DMIC Paths */
- {"DMIC1", NULL, "IN1_DMIC1"},
- {"Input Mux 1", "DMIC1_SEL", "DMIC1"},
- {"DMIC2", NULL, "IN3_DMIC2"},
- {"Input Mux 2", "DMIC2_SEL", "DMIC2"},
- /* Output Paths */
- {"ASP_SDOUT1", NULL, "ADC1A" },
- {"ASP_SDOUT1", NULL, "Input Mux 1"},
- {"ASP_SDOUT1", NULL, "ADC1B"},
- {"ASP_SDOUT2", NULL, "ADC2A"},
- {"ASP_SDOUT2", NULL, "Input Mux 2"},
- {"ASP_SDOUT2", NULL, "ADC2B"},
- {"ASP1 Capture", NULL, "ASP_SDOUT1"},
- {"ASP2 Capture", NULL, "ASP_SDOUT2"},
+};
This codec chip has two SDOUT (Serial Data Output) pins while sharing one serial port -- there's only one pair of bit clock and fsync clock.
+static struct snd_soc_dai_driver cs53l30_dai[] = {
- {
.name = "cs53l30-asp1",
.id = CS53L30_ASP1,
.capture = {
.stream_name = "ASP1 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = CS53L30_RATES,
.formats = CS53L30_FORMATS,
},
.ops = &cs53l30_ops,
.symmetric_rates = 1,
},
- {
.name = "cs53l30-asp2",
.id = CS53L30_ASP2,
.capture = {
.stream_name = "ASP2 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = CS53L30_RATES,
.formats = CS53L30_FORMATS,
},
.ops = &cs53l30_ops,
.symmetric_rates = 1,
}
+};
In this case, would it be more appropriate to create one single DAI with channels_max = 4 over here?
Another question is for its TDM support. This chip outputs 4-channel data on two data output pins (SDOUT1 and SDOUT2) as normal mode; it outputs 4-channel data on one data output pin (SDOUT1) as TDM mode. However, the mode selection for a 4-channel recording should depend on the hardware design: whether the SDOUT2 is connected or not. So I am wondering if there is a common way or existing way to indicate this hardware design. Or just by simply defining a new DT property?
Thank you Nicolin
On Tue, May 17, 2016 at 04:43:33PM -0700, Nicolin Chen wrote:
In this case, would it be more appropriate to create one single DAI with channels_max = 4 over here?
Yes.
Another question is for its TDM support. This chip outputs 4-channel data on two data output pins (SDOUT1 and SDOUT2) as normal mode; it outputs 4-channel data on one data output pin (SDOUT1) as TDM mode. However, the mode selection for a 4-channel recording should depend on the hardware design: whether the SDOUT2 is connected or not. So I am wondering if there is a common way or existing way to indicate this hardware design. Or just by simply defining a new DT property?
That's a really rare thing to have as an option, most things either do TDM or parallel data signals but not both.
On Tue, May 31, 2016 at 9:53 AM, Mark Brown broonie@kernel.org wrote:
On Tue, May 17, 2016 at 04:43:33PM -0700, Nicolin Chen wrote:
In this case, would it be more appropriate to create one single DAI with channels_max = 4 over here?
Yes.
Another question is for its TDM support. This chip outputs 4-channel data on two data output pins (SDOUT1 and SDOUT2) as normal mode; it outputs 4-channel data on one data output pin (SDOUT1) as TDM mode. However, the mode selection for a 4-channel recording should depend on the hardware design: whether the SDOUT2 is connected or not. So I am wondering if there is a common way or existing way to indicate this hardware design. Or just by simply defining a new DT property?
That's a really rare thing to have as an option, most things either do TDM or parallel data signals but not both.
Interesting comment. While I'm sure that's true for the moment, microphone arrays are changing this quickly. I fall into the oddball category that the main chips I use are the TLV320AIC34 and CS53L30, and both of them can switch between TDM mode or dual I2S mode for 4 channel support.
Since I need to get many channels on board, and SoCs (except for TI) rarely have enough parallel I2S ports for mic arrays, I opt for TDM mode often.
If you're using the CS53L30, chances are high that you're building a mic array. Then the question is, how many microphones? up to 4, you could live with dual I2S if your chip supports it. Beyond 4, you're almost certainly talking TDM, unless you have a TI McASP with lots of inputs, or an XMOS part with multiple I2S interfaces.
Of course, it's highly dependent on what the SoC supports. I would say it's just about mandatory for the CS53L30 driver in particular to be able to switch between TDM or I2S mode based on a DT setting because of the huge variability of capabilities on the SoC side. A TI McBSP could support multiple I2S busses, but a freescale SSI must use TDM mode.
-Caleb
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On 5/31/16, 12:10 PM, "Caleb Crome" caleb@crome.org wrote:
On Tue, May 31, 2016 at 9:53 AM, Mark Brown broonie@kernel.org wrote:
On Tue, May 17, 2016 at 04:43:33PM -0700, Nicolin Chen wrote:
In this case, would it be more appropriate to create one single DAI with channels_max = 4 over here?
Yes.
Another question is for its TDM support. This chip outputs 4-channel data on two data output pins (SDOUT1 and SDOUT2) as normal mode; it outputs 4-channel data on one data output pin (SDOUT1) as TDM mode. However, the mode selection for a 4-channel recording should depend on the hardware design: whether the SDOUT2 is connected or not. So I am wondering if there is a common way or existing way to indicate this hardware design. Or just by simply defining a new DT property?
That's a really rare thing to have as an option, most things either do TDM or parallel data signals but not both.
Interesting comment. While I'm sure that's true for the moment, microphone arrays are changing this quickly. I fall into the oddball category that the main chips I use are the TLV320AIC34 and CS53L30, and both of them can switch between TDM mode or dual I2S mode for 4 channel support.
Since I need to get many channels on board, and SoCs (except for TI) rarely have enough parallel I2S ports for mic arrays, I opt for TDM mode often.
If you're using the CS53L30, chances are high that you're building a mic array. Then the question is, how many microphones? up to 4, you could live with dual I2S if your chip supports it. Beyond 4, you're almost certainly talking TDM, unless you have a TI McASP with lots of inputs, or an XMOS part with multiple I2S interfaces.
Of course, it's highly dependent on what the SoC supports. I would say it's just about mandatory for the CS53L30 driver in particular to be able to switch between TDM or I2S mode based on a DT setting because of the huge variability of capabilities on the SoC side. A TI McBSP could support multiple I2S busses, but a freescale SSI must use TDM mode.
-Caleb
For this device you are using either I2S or TDM, therefore, it will depend on the capabilities of the SoC (hence the hardware design). This will not require a DT property since it is configured by the cs53l30_set_dai_fmt callback. As Calib mentioned, you will have to use TDM if you are using greater than channels up to 16 channels for a mic array.
Alsa-devel mailing list Alsa-devel@alsa-project.org
https://urldefense.proofpoint.com/v2/url?u=http-3A__mailman.alsa-2Dprojec t.org_mailman_listinfo_alsa-2Ddevel&d=DQIBaQ&c=O3LcjD-V2Iepl5V0N1424A&r=N JtNI3T_InLOY17xIGk4jdUC7XljFdoy6miaxhGHOOI&m=Qrzd24GLzVm_xD3O3sFLrdCF9bUr dZ_DqkhoIuAy6b0&s=szG5oWmXcuMj-OEhja5V7GMcXXMCfLn9ia0maAr8YG8&e=
On Tue, May 31, 2016 at 10:10:11AM -0700, Caleb Crome wrote:
On Tue, May 31, 2016 at 9:53 AM, Mark Brown broonie@kernel.org wrote:
That's a really rare thing to have as an option, most things either do TDM or parallel data signals but not both.
Interesting comment. While I'm sure that's true for the moment, microphone arrays are changing this quickly. I fall into the oddball category that the main chips I use are the TLV320AIC34 and CS53L30, and both of them can switch between TDM mode or dual I2S mode for 4 channel support.
I'm not sure why mic arrays would drive that, it's not like they're particularly new or innovative technology here and multi channel output has been even more widely available for a long time?
Since I need to get many channels on board, and SoCs (except for TI) rarely have enough parallel I2S ports for mic arrays, I opt for TDM mode often.
Modern systems all use TDM for the most part, the usage of parallel data lines that I've seen has been for surround sound applications where a 5.1 or 7.1 decoder will often be built by taking a bunch of high end stereo CODECs and wiring them up in parallel, partly for performance and physical design reasons and partly because such system designs have often had their roots in very old systems.
This also matches the trend with more modern SoCs to use programmable serial ports rather than dedicated I2S controllers so TDM is very easy to configure, and of course it's fewer signals so it's easier from a board design point of view too.
On Tue, May 31, 2016 at 10:35 AM, Mark Brown broonie@kernel.org wrote:
On Tue, May 31, 2016 at 10:10:11AM -0700, Caleb Crome wrote:
On Tue, May 31, 2016 at 9:53 AM, Mark Brown broonie@kernel.org wrote:
That's a really rare thing to have as an option, most things either do TDM or parallel data signals but not both.
Interesting comment. While I'm sure that's true for the moment, microphone arrays are changing this quickly. I fall into the oddball category that the main chips I use are the TLV320AIC34 and CS53L30, and both of them can switch between TDM mode or dual I2S mode for 4 channel support.
I'm not sure why mic arrays would drive that, it's not like they're particularly new or innovative technology here and multi channel output has been even more widely available for a long time?
Good point. I guess I'm coming from the SoC/mobile world where most SoCs support stereo only, and a couple microphones at most. Now phones have 3-4 or more microphones. Amazon echo has 7 mics. We're working on arrays of 16 and beyond, and getting those channels into linux has proven to be an enormous headache given the hardware and software status of many of the SoCs our customers want to use.
Since I need to get many channels on board, and SoCs (except for TI) rarely have enough parallel I2S ports for mic arrays, I opt for TDM mode often.
Modern systems all use TDM for the most part ...
We must be looking at different sorts of chips :-) Even on new parts like the Snapdragon 410, they have a super inflexible I2S only port. (up to 3 parallel I2S for up to 6 channels, but no TDM).
From my experience, the SoC support for TDM has been essentially
nonexistent, except for TI's McBSP and McASP. I'd sure welcome a Cortex A53 or A57 (or even A15 for that matter!) that has good TDM support.
Here are the chips that we've come across lately from our customers:
MX6 (SSI): hardware supports TDM, but linux didn't until recently (thanks everybody for help with that!) MX6 (ESAI): not available on MX6 solo, nor on any SOM modules I've ever seen, but supported if you can get to the pins. MX7: (SAI): hardware supports TDM, linux BSP driver does not. TI parts (McASP): supported in hardware & software TI parts (McBSP): supported in hardware & software Qualcomm Snapdragon 410c (MI2S): TDM not possible in hardware. Supports multiple parallel I2S channels only. Broadcom: (forgot part number...) hardware supports TDM. software still in the works but apparently somebody's working on it.
A quick grep for channels_max > 2 in sound/soc shows the following few drivers that support channels > 2 (not necessarily even in TDM mode). All the rest are 2 channels max.
pxa/mmp-sspa: 128/2 (playback/capture). pxa/pxa-ssp: 8/8 zx286702-i2s.c: 8/2 fsl_esai: 12/8 fsl_ssi: 32/32 blackfin 5: 8/8 channels blackfin 6: 2/2 channels only. mcbsp: 16/16 mcasp: 512/512
and a few other chips that I'm not familiar with. So there does not seem to be overwhelming support for TDM in Alsa ASoC. To what extent this is a software vs. hardware issue could use a little investigation.
the bummer is that even newer chips like the snapdragon are still using really inflexible ports.
So... if anybody knows of a Cortex A53 or A57 with good TDM support in linux (at least 16 channels in and out), I'd love to know about it :-)
-Caleb
On Tue, May 31, 2016 at 03:32:05PM -0700, Caleb Crome wrote:
On Tue, May 31, 2016 at 10:35 AM, Mark Brown broonie@kernel.org wrote:
I'm not sure why mic arrays would drive that, it's not like they're particularly new or innovative technology here and multi channel output has been even more widely available for a long time?
Good point. I guess I'm coming from the SoC/mobile world where most SoCs support stereo only, and a couple microphones at most. Now phones have 3-4 or more microphones. Amazon echo has 7 mics. We're working on arrays of
That's fairly rare, I'd say more of the vendors are doing programmable serial ports and those that aren't have a different model... see below.
We must be looking at different sorts of chips :-) Even on new parts like the Snapdragon 410, they have a super inflexible I2S only port. (up to 3 parallel I2S for up to 6 channels, but no TDM).
Qualcomm have never really wanted anyone to use I2S in their mobile products for quite some time now - they've always wanted to do a system sale based on either their integrated CODECs or (in the current production designs) their own external CODECs which normally use Slimbus to connect to the CPU (they are working off and on on upstreaming Slimbus, nobody else ever adopted it so there's relatively little pressure).
and a few other chips that I'm not familiar with. So there does not seem to be overwhelming support for TDM in Alsa ASoC. To what extent this is a software vs. hardware issue could use a little investigation.
It's software and legacy hardware.
So... if anybody knows of a Cortex A53 or A57 with good TDM support in linux (at least 16 channels in and out), I'd love to know about it :-)
Well, if you specifically mean TDM as opposed to multi-channel our general support for that isn't good (mainly due to lack of demand). It sounds like you're more interested in multi channel here though.
On Thu, Jun 2, 2016 at 10:17 AM, Mark Brown broonie@kernel.org wrote:
On Tue, May 31, 2016 at 03:32:05PM -0700, Caleb Crome wrote:
On Tue, May 31, 2016 at 10:35 AM, Mark Brown broonie@kernel.org wrote:
I'm not sure why mic arrays would drive that, it's not like they're particularly new or innovative technology here and multi channel output has been even more widely available for a long time?
Good point. I guess I'm coming from the SoC/mobile world where most SoCs support stereo only, and a couple microphones at most. Now phones have 3-4 or more microphones. Amazon echo has 7 mics. We're working on arrays of
That's fairly rare, I'd say more of the vendors are doing programmable serial ports and those that aren't have a different model... see below.
We must be looking at different sorts of chips :-) Even on new parts like the Snapdragon 410, they have a super inflexible I2S only port. (up to 3 parallel I2S for up to 6 channels, but no TDM).
Qualcomm have never really wanted anyone to use I2S in their mobile products for quite some time now - they've always wanted to do a system sale based on either their integrated CODECs or (in the current production designs) their own external CODECs which normally use Slimbus to connect to the CPU (they are working off and on on upstreaming Slimbus, nobody else ever adopted it so there's relatively little pressure).
and a few other chips that I'm not familiar with. So there does not seem to be overwhelming support for TDM in Alsa ASoC. To what extent this is a software vs. hardware issue could use a little investigation.
It's software and legacy hardware.
So... if anybody knows of a Cortex A53 or A57 with good TDM support in linux (at least 16 channels in and out), I'd love to know about it :-)
Well, if you specifically mean TDM as opposed to multi-channel our general support for that isn't good (mainly due to lack of demand). It sounds like you're more interested in multi channel here though.
I must be missing something. To me, TDM is synonymous with multi-channel on a single wire. The datasheets of codecs and SoCs invariably refer to TDM as putting multiple slots onto a single wire, and not necessarily the actual bit-format and clocking of that wire. The actual clocking and bits are generally configurable, but the concept of TDM in the datasheets seems to be universal and have the same meaning -- multi-channel on a wire.
(I recall there is also a specific TDM format with something like 384 slots per frame, which is a well defined thing, but that's not what I'm talking about. That's used just for telephony I beleive).
What's the linuxy name for what the datasheets call TDM format with slots > 2? Do we just call that 'multi-channel', or specifically say, 'channels_max > 2'? I was always under the assumption that's what TDM meant.
Thanks, -Caleb
On Thu, Jun 02, 2016 at 10:40:58AM -0700, Caleb Crome wrote:
On Thu, Jun 2, 2016 at 10:17 AM, Mark Brown broonie@kernel.org wrote:
Well, if you specifically mean TDM as opposed to multi-channel our general support for that isn't good (mainly due to lack of demand). It sounds like you're more interested in multi channel here though.
I must be missing something. To me, TDM is synonymous with multi-channel on a single wire. The datasheets of codecs and SoCs
TDM, at least in the sense Linux is using it, is multiple *unrelated* audio streams on a single wire. Any multi-channel audio stream is TDM in some sense but the trivial extension to add two or more channels isn't really a big deal. This is for things more complex than just stuffing more bytes of data onto a wire where there are going to be some timeslots that the device should ignore as they're going to/from other devices (or at least other streams even if the same chip is handling them).
(I recall there is also a specific TDM format with something like 384 slots per frame, which is a well defined thing, but that's not what I'm talking about. That's used just for telephony I beleive).
You're thinking of H.1x0 CTBus which is genuine TDM, used for routing single channel audio streams between multiple PCI (H.100) or cPCI (H.110) cards in a system. It does way more than 384 slots, 4096 8kHz 8 bit timeslots IIRC.
What's the linuxy name for what the datasheets call TDM format with slots > 2? Do we just call that 'multi-channel', or specifically say, 'channels_max > 2'? I was always under the assumption that's what TDM meant.
We don't particularly call it anything, it's such a trivial extension.
On Thu, Jun 2, 2016 at 11:40 AM, Mark Brown broonie@kernel.org wrote:
On Thu, Jun 02, 2016 at 10:40:58AM -0700, Caleb Crome wrote:
On Thu, Jun 2, 2016 at 10:17 AM, Mark Brown broonie@kernel.org wrote:
Well, if you specifically mean TDM as opposed to multi-channel our general support for that isn't good (mainly due to lack of demand). It sounds like you're more interested in multi channel here though.
I must be missing something. To me, TDM is synonymous with multi-channel on a single wire. The datasheets of codecs and SoCs
TDM, at least in the sense Linux is using it, is multiple *unrelated* audio streams on a single wire. Any multi-channel audio stream is TDM in some sense but the trivial extension to add two or more channels isn't really a big deal. This is for things more complex than just stuffing more bytes of data onto a wire where there are going to be some timeslots that the device should ignore as they're going to/from other devices (or at least other streams even if the same chip is handling them).
Hi Mark, Thanks for taking the time to describe this to me. This definitely comes as a surprise to me. I assumed that TDM mode simply means multi-channels on a single wire (which is what it means in all the datasheets).
Just so I have this straight, TDM in the linux sense is putting say, 6-channels on one wire where the channels are from logically different places? i.e. chanels 0-1 are from bluetooth, 2-3 from analog in, and 4-5 from somewhere else?
So, going back to Nicolin's original question for this email, the CS53L30, what are the proper DT settings for these modes: The device can be configured in what amounts to at least 3 different modes:
1) 4-channel, 5-pin ADC interface: dual I2S: shared BCLK/WCLK/MCLK, separate data pins for channels 0/1 and 2/3 (SDOUT0/SDOUT1). 2) 4-channel, 4-pin ADC interface: MBLCK/BCLK/WCLK/SDOUT0 only. Slots 0/1/2/3 on the SDOUT0 pin (synch master) 3) and finally, a multi-codec configuration, where 4 x CS53L30 are used: 16-channel, 4-pin ADC (what I call TDM) interface, all on the same 4 pins. These are very related data streams -- they are perfectly synchronized in hardware, so perhaps this isn't TDM in the linux-sense.
Nicolin Wrote: Another question is for its TDM support. This chip outputs 4-channel data on two data output pins (SDOUT1 and SDOUT2) as normal mode; it outputs 4-channel data on one data output pin (SDOUT1) as TDM mode. However, the mode selection for a 4-channel recording should depend on the hardware design: whether the SDOUT2 is connected or not. So I am wondering if there is a common way or existing way to indicate this hardware design. Or just by simply defining a new DT property?
As far as I can see he's not trying to define *unrelated* streams in TDM mode, but very related streams, which is TDM in the datasheet-sense. And there not only needs to be a mechanism of choosing the dual I2S mode, but also which TDM slots to drop the data in (which I think already exists, right?)
What's the linuxy name for what the datasheets call TDM format with slots > 2? Do we just call that 'multi-channel', or specifically say, 'channels_max > 2'? I was always under the assumption that's what TDM meant.
We don't particularly call it anything, it's such a trivial extension.
That's not been my experience :-) Getting 16 channels onto a wire has been anything but trivial because of the lack of SoC driver support for it. Perhaps I'm just using the wrong SoCs.
Thanks again, -Caleb
On Sun, Jun 05, 2016 at 09:48:36AM -0700, Caleb Crome wrote:
On Thu, Jun 2, 2016 at 11:40 AM, Mark Brown broonie@kernel.org wrote:
TDM, at least in the sense Linux is using it, is multiple *unrelated* audio streams on a single wire. Any multi-channel audio stream is TDM in some sense but the trivial extension to add two or more channels
Thanks for taking the time to describe this to me. This definitely comes as a surprise to me. I assumed that TDM mode simply means multi-channels on a single wire (which is what it means in all the datasheets).
It *really* depends on the datasheets you're looking at. Things that are doing generic programmable serial ports might have no concept of channels and inflexible devices which just support configurable numbers of channels are just as likely to refer to DSP mode or whatever as anything else.
Just so I have this straight, TDM in the linux sense is putting say, 6-channels on one wire where the channels are from logically different places? i.e. chanels 0-1 are from bluetooth, 2-3 from analog in, and 4-5 from somewhere else?
You also need some separation of devices for us to actually care - if none of the hardware can tell this is going on it's not meaningfully TDM.
As far as I can see he's not trying to define *unrelated* streams in TDM mode, but very related streams, which is TDM in the datasheet-sense. And there not only needs to be a mechanism of choosing the dual I2S mode, but also which TDM slots to drop the data in (which I think already exists, right?)
Yes.
'channels_max > 2'? I was always under the assumption that's what TDM meant.
We don't particularly call it anything, it's such a trivial extension.
That's not been my experience :-) Getting 16 channels onto a wire has been anything but trivial because of the lack of SoC driver support for it. Perhaps I'm just using the wrong SoCs.
Yes, you're using the wrong SoCs - anything with a programmable serial port should cope fine.
participants (5)
-
Caleb Crome
-
Handrigan, Paul
-
Mark Brown
-
Nicolin Chen
-
tim.howe@cirrus.com