diff options
Diffstat (limited to 'sound/soc')
46 files changed, 2637 insertions, 316 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index b3afae9..4d82a58 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -43,6 +43,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK5386 select SND_SOC_ALC5623 if I2C select SND_SOC_ALC5632 if I2C + select SND_SOC_BT_SCO select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC select SND_SOC_CS35L32 if I2C select SND_SOC_CS42L51_I2C if I2C @@ -64,7 +65,6 @@ config SND_SOC_ALL_CODECS select SND_SOC_DA732X if I2C select SND_SOC_DA9055 if I2C select SND_SOC_DMIC - select SND_SOC_BT_SCO select SND_SOC_ES8328_SPI if SPI_MASTER select SND_SOC_ES8328_I2C if I2C select SND_SOC_GTM601 @@ -79,6 +79,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_MAX98090 if I2C select SND_SOC_MAX98095 if I2C select SND_SOC_MAX98357A if GPIOLIB + select SND_SOC_MAX98371 if I2C select SND_SOC_MAX9867 if I2C select SND_SOC_MAX98925 if I2C select SND_SOC_MAX98926 if I2C @@ -126,12 +127,14 @@ config SND_SOC_ALL_CODECS select SND_SOC_TAS2552 if I2C select SND_SOC_TAS5086 if I2C select SND_SOC_TAS571X if I2C + select SND_SOC_TAS5720 if I2C select SND_SOC_TFA9879 if I2C select SND_SOC_TLV320AIC23_I2C if I2C select SND_SOC_TLV320AIC23_SPI if SPI_MASTER select SND_SOC_TLV320AIC26 if SPI_MASTER select SND_SOC_TLV320AIC31XX if I2C - select SND_SOC_TLV320AIC32X4 if I2C + select SND_SOC_TLV320AIC32X4_I2C if I2C + select SND_SOC_TLV320AIC32X4_SPI if SPI_MASTER select SND_SOC_TLV320AIC3X if I2C select SND_SOC_TPA6130A2 if I2C select SND_SOC_TLV320DAC33 if I2C @@ -367,6 +370,9 @@ config SND_SOC_ALC5623 config SND_SOC_ALC5632 tristate +config SND_SOC_BT_SCO + tristate + config SND_SOC_CQ0093VC tristate @@ -473,9 +479,6 @@ config SND_SOC_DA732X config SND_SOC_DA9055 tristate -config SND_SOC_BT_SCO - tristate - config SND_SOC_DMIC tristate @@ -529,6 +532,9 @@ config SND_SOC_MAX98095 config SND_SOC_MAX98357A tristate +config SND_SOC_MAX98371 + tristate + config SND_SOC_MAX9867 tristate @@ -748,9 +754,16 @@ config SND_SOC_TAS5086 depends on I2C config SND_SOC_TAS571X - tristate "Texas Instruments TAS5711/TAS5717/TAS5719 power amplifiers" + tristate "Texas Instruments TAS5711/TAS5717/TAS5719/TAS5721 power amplifiers" depends on I2C +config SND_SOC_TAS5720 + tristate "Texas Instruments TAS5720 Mono Audio amplifier" + depends on I2C + help + Enable support for Texas Instruments TAS5720L/M high-efficiency mono + Class-D audio power amplifiers. + config SND_SOC_TFA9879 tristate "NXP Semiconductors TFA9879 amplifier" depends on I2C @@ -780,6 +793,16 @@ config SND_SOC_TLV320AIC31XX config SND_SOC_TLV320AIC32X4 tristate +config SND_SOC_TLV320AIC32X4_I2C + tristate + depends on I2C + select SND_SOC_TLV320AIC32X4 + +config SND_SOC_TLV320AIC32X4_SPI + tristate + depends on SPI_MASTER + select SND_SOC_TLV320AIC32X4 + config SND_SOC_TLV320AIC3X tristate "Texas Instruments TLV320AIC3x CODECs" depends on I2C @@ -920,7 +943,8 @@ config SND_SOC_WM8955 tristate config SND_SOC_WM8960 - tristate + tristate "Wolfson Microelectronics WM8960 CODEC" + depends on I2C config SND_SOC_WM8961 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index b7b9941..0f548fd3 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -32,6 +32,7 @@ snd-soc-ak4642-objs := ak4642.o snd-soc-ak4671-objs := ak4671.o snd-soc-ak5386-objs := ak5386.o snd-soc-arizona-objs := arizona.o +snd-soc-bt-sco-objs := bt-sco.o snd-soc-cq93vc-objs := cq93vc.o snd-soc-cs35l32-objs := cs35l32.o snd-soc-cs42l51-objs := cs42l51.o @@ -55,7 +56,6 @@ snd-soc-da7218-objs := da7218.o snd-soc-da7219-objs := da7219.o da7219-aad.o snd-soc-da732x-objs := da732x.o snd-soc-da9055-objs := da9055.o -snd-soc-bt-sco-objs := bt-sco.o snd-soc-dmic-objs := dmic.o snd-soc-es8328-objs := es8328.o snd-soc-es8328-i2c-objs := es8328-i2c.o @@ -74,6 +74,7 @@ snd-soc-max98088-objs := max98088.o snd-soc-max98090-objs := max98090.o snd-soc-max98095-objs := max98095.o snd-soc-max98357a-objs := max98357a.o +snd-soc-max98371-objs := max98371.o snd-soc-max9867-objs := max9867.o snd-soc-max98925-objs := max98925.o snd-soc-max98926-objs := max98926.o @@ -131,6 +132,7 @@ snd-soc-stac9766-objs := stac9766.o snd-soc-sti-sas-objs := sti-sas.o snd-soc-tas5086-objs := tas5086.o snd-soc-tas571x-objs := tas571x.o +snd-soc-tas5720-objs := tas5720.o snd-soc-tfa9879-objs := tfa9879.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o @@ -138,6 +140,8 @@ snd-soc-tlv320aic23-spi-objs := tlv320aic23-spi.o snd-soc-tlv320aic26-objs := tlv320aic26.o snd-soc-tlv320aic31xx-objs := tlv320aic31xx.o snd-soc-tlv320aic32x4-objs := tlv320aic32x4.o +snd-soc-tlv320aic32x4-i2c-objs := tlv320aic32x4-i2c.o +snd-soc-tlv320aic32x4-spi-objs := tlv320aic32x4-spi.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o snd-soc-tlv320dac33-objs := tlv320dac33.o snd-soc-ts3a227e-objs := ts3a227e.o @@ -243,6 +247,7 @@ obj-$(CONFIG_SND_SOC_AK5386) += snd-soc-ak5386.o obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o obj-$(CONFIG_SND_SOC_ARIZONA) += snd-soc-arizona.o +obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o obj-$(CONFIG_SND_SOC_CS35L32) += snd-soc-cs35l32.o obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o @@ -266,7 +271,6 @@ obj-$(CONFIG_SND_SOC_DA7218) += snd-soc-da7218.o obj-$(CONFIG_SND_SOC_DA7219) += snd-soc-da7219.o obj-$(CONFIG_SND_SOC_DA732X) += snd-soc-da732x.o obj-$(CONFIG_SND_SOC_DA9055) += snd-soc-da9055.o -obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o @@ -339,6 +343,7 @@ obj-$(CONFIG_SND_SOC_STI_SAS) += snd-soc-sti-sas.o obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o +obj-$(CONFIG_SND_SOC_TAS5720) += snd-soc-tas5720.o obj-$(CONFIG_SND_SOC_TFA9879) += snd-soc-tfa9879.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC23_I2C) += snd-soc-tlv320aic23-i2c.o @@ -346,6 +351,8 @@ obj-$(CONFIG_SND_SOC_TLV320AIC23_SPI) += snd-soc-tlv320aic23-spi.o obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o obj-$(CONFIG_SND_SOC_TLV320AIC31XX) += snd-soc-tlv320aic31xx.o obj-$(CONFIG_SND_SOC_TLV320AIC32X4) += snd-soc-tlv320aic32x4.o +obj-$(CONFIG_SND_SOC_TLV320AIC32X4_I2C) += snd-soc-tlv320aic32x4-i2c.o +obj-$(CONFIG_SND_SOC_TLV320AIC32X4_SPI) += snd-soc-tlv320aic32x4-spi.o obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o obj-$(CONFIG_SND_SOC_TLV320DAC33) += snd-soc-tlv320dac33.o obj-$(CONFIG_SND_SOC_TS3A227E) += snd-soc-ts3a227e.o diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 1ee8506..4d8b9e4 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -560,6 +560,7 @@ static const struct regmap_config ak4642_regmap = { .max_register = FIL1_3, .reg_defaults = ak4642_reg, .num_reg_defaults = NUM_AK4642_REG_DEFAULTS, + .cache_type = REGCACHE_RBTREE, }; static const struct regmap_config ak4643_regmap = { @@ -568,6 +569,7 @@ static const struct regmap_config ak4643_regmap = { .max_register = SPK_MS, .reg_defaults = ak4643_reg, .num_reg_defaults = ARRAY_SIZE(ak4643_reg), + .cache_type = REGCACHE_RBTREE, }; static const struct regmap_config ak4648_regmap = { @@ -576,6 +578,7 @@ static const struct regmap_config ak4648_regmap = { .max_register = EQ_FBEQE, .reg_defaults = ak4648_reg, .num_reg_defaults = ARRAY_SIZE(ak4648_reg), + .cache_type = REGCACHE_RBTREE, }; static const struct ak4642_drvdata ak4642_drvdata = { diff --git a/sound/soc/codecs/max98371.c b/sound/soc/codecs/max98371.c new file mode 100644 index 0000000..cf0a39b --- /dev/null +++ b/sound/soc/codecs/max98371.c @@ -0,0 +1,441 @@ +/* + * max98371.c -- ALSA SoC Stereo MAX98371 driver + * + * Copyright 2015-16 Maxim Integrated Products + * + * 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/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include "max98371.h" + +static const char *const monomix_text[] = { + "Left", "Right", "LeftRightDiv2", +}; + +static const char *const hpf_cutoff_txt[] = { + "Disable", "DC Block", "50Hz", + "100Hz", "200Hz", "400Hz", "800Hz", +}; + +static SOC_ENUM_SINGLE_DECL(max98371_monomix, MAX98371_MONOMIX_CFG, 0, + monomix_text); + +static SOC_ENUM_SINGLE_DECL(max98371_hpf_cutoff, MAX98371_HPF, 0, + hpf_cutoff_txt); + +static const DECLARE_TLV_DB_RANGE(max98371_dht_min_gain, + 0, 1, TLV_DB_SCALE_ITEM(537, 66, 0), + 2, 3, TLV_DB_SCALE_ITEM(677, 82, 0), + 4, 5, TLV_DB_SCALE_ITEM(852, 104, 0), + 6, 7, TLV_DB_SCALE_ITEM(1072, 131, 0), + 8, 9, TLV_DB_SCALE_ITEM(1350, 165, 0), + 10, 11, TLV_DB_SCALE_ITEM(1699, 101, 0), +); + +static const DECLARE_TLV_DB_RANGE(max98371_dht_max_gain, + 0, 1, TLV_DB_SCALE_ITEM(537, 66, 0), + 2, 3, TLV_DB_SCALE_ITEM(677, 82, 0), + 4, 5, TLV_DB_SCALE_ITEM(852, 104, 0), + 6, 7, TLV_DB_SCALE_ITEM(1072, 131, 0), + 8, 9, TLV_DB_SCALE_ITEM(1350, 165, 0), + 10, 11, TLV_DB_SCALE_ITEM(1699, 208, 0), +); + +static const DECLARE_TLV_DB_RANGE(max98371_dht_rot_gain, + 0, 1, TLV_DB_SCALE_ITEM(-50, -50, 0), + 2, 6, TLV_DB_SCALE_ITEM(-100, -100, 0), + 7, 8, TLV_DB_SCALE_ITEM(-800, -200, 0), + 9, 11, TLV_DB_SCALE_ITEM(-1200, -300, 0), + 12, 13, TLV_DB_SCALE_ITEM(-2000, -200, 0), + 14, 15, TLV_DB_SCALE_ITEM(-2500, -500, 0), +); + +static const struct reg_default max98371_reg[] = { + { 0x01, 0x00 }, + { 0x02, 0x00 }, + { 0x03, 0x00 }, + { 0x04, 0x00 }, + { 0x05, 0x00 }, + { 0x06, 0x00 }, + { 0x07, 0x00 }, + { 0x08, 0x00 }, + { 0x09, 0x00 }, + { 0x0A, 0x00 }, + { 0x10, 0x06 }, + { 0x11, 0x08 }, + { 0x14, 0x80 }, + { 0x15, 0x00 }, + { 0x16, 0x00 }, + { 0x18, 0x00 }, + { 0x19, 0x00 }, + { 0x1C, 0x00 }, + { 0x1D, 0x00 }, + { 0x1E, 0x00 }, + { 0x1F, 0x00 }, + { 0x20, 0x00 }, + { 0x21, 0x00 }, + { 0x22, 0x00 }, + { 0x23, 0x00 }, + { 0x24, 0x00 }, + { 0x25, 0x00 }, + { 0x26, 0x00 }, + { 0x27, 0x00 }, + { 0x28, 0x00 }, + { 0x29, 0x00 }, + { 0x2A, 0x00 }, + { 0x2B, 0x00 }, + { 0x2C, 0x00 }, + { 0x2D, 0x00 }, + { 0x2E, 0x0B }, + { 0x31, 0x00 }, + { 0x32, 0x18 }, + { 0x33, 0x00 }, + { 0x34, 0x00 }, + { 0x36, 0x00 }, + { 0x37, 0x00 }, + { 0x38, 0x00 }, + { 0x39, 0x00 }, + { 0x3A, 0x00 }, + { 0x3B, 0x00 }, + { 0x3C, 0x00 }, + { 0x3D, 0x00 }, + { 0x3E, 0x00 }, + { 0x3F, 0x00 }, + { 0x40, 0x00 }, + { 0x41, 0x00 }, + { 0x42, 0x00 }, + { 0x43, 0x00 }, + { 0x4A, 0x00 }, + { 0x4B, 0x00 }, + { 0x4C, 0x00 }, + { 0x4D, 0x00 }, + { 0x4E, 0x00 }, + { 0x50, 0x00 }, + { 0x51, 0x00 }, + { 0x55, 0x00 }, + { 0x58, 0x00 }, + { 0x59, 0x00 }, + { 0x5C, 0x00 }, + { 0xFF, 0x43 }, +}; + +static bool max98371_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98371_IRQ_CLEAR1: + case MAX98371_IRQ_CLEAR2: + case MAX98371_IRQ_CLEAR3: + case MAX98371_VERSION: + return true; + default: + return false; + } +} + +static bool max98371_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98371_SOFT_RESET: + return false; + default: + return true; + } +}; + +static const DECLARE_TLV_DB_RANGE(max98371_gain_tlv, + 0, 7, TLV_DB_SCALE_ITEM(0, 50, 0), + 8, 10, TLV_DB_SCALE_ITEM(400, 100, 0) +); + +static const DECLARE_TLV_DB_RANGE(max98371_noload_gain_tlv, + 0, 11, TLV_DB_SCALE_ITEM(950, 100, 0), +); + +static const DECLARE_TLV_DB_SCALE(digital_tlv, -6300, 50, 1); + +static const struct snd_kcontrol_new max98371_snd_controls[] = { + SOC_SINGLE_TLV("Speaker Volume", MAX98371_GAIN, + MAX98371_GAIN_SHIFT, (1<<MAX98371_GAIN_WIDTH)-1, 0, + max98371_gain_tlv), + SOC_SINGLE_TLV("Digital Volume", MAX98371_DIGITAL_GAIN, 0, + (1<<MAX98371_DIGITAL_GAIN_WIDTH)-1, 1, digital_tlv), + SOC_SINGLE_TLV("Speaker DHT Max Volume", MAX98371_GAIN, + 0, (1<<MAX98371_DHT_MAX_WIDTH)-1, 0, + max98371_dht_max_gain), + SOC_SINGLE_TLV("Speaker DHT Min Volume", MAX98371_DHT_GAIN, + 0, (1<<MAX98371_DHT_GAIN_WIDTH)-1, 0, + max98371_dht_min_gain), + SOC_SINGLE_TLV("Speaker DHT Rotation Volume", MAX98371_DHT_GAIN, + 0, (1<<MAX98371_DHT_ROT_WIDTH)-1, 0, + max98371_dht_rot_gain), + SOC_SINGLE("DHT Attack Step", MAX98371_DHT, MAX98371_DHT_STEP, 3, 0), + SOC_SINGLE("DHT Attack Rate", MAX98371_DHT, 0, 7, 0), + SOC_ENUM("Monomix Select", max98371_monomix), + SOC_ENUM("HPF Cutoff", max98371_hpf_cutoff), +}; + +static int max98371_dai_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct max98371_priv *max98371 = snd_soc_codec_get_drvdata(codec); + unsigned int val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + dev_err(codec->dev, "DAI clock mode unsupported"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + val |= 0; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val |= MAX98371_DAI_RIGHT; + break; + case SND_SOC_DAIFMT_LEFT_J: + val |= MAX98371_DAI_LEFT; + break; + default: + dev_err(codec->dev, "DAI wrong mode unsupported"); + return -EINVAL; + } + regmap_update_bits(max98371->regmap, MAX98371_FMT, + MAX98371_FMT_MODE_MASK, val); + return 0; +} + +static int max98371_dai_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 max98371_priv *max98371 = snd_soc_codec_get_drvdata(codec); + int blr_clk_ratio, ch_size, channels = params_channels(params); + int rate = params_rate(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + regmap_update_bits(max98371->regmap, MAX98371_FMT, + MAX98371_FMT_MASK, MAX98371_DAI_CHANSZ_16); + ch_size = 8; + break; + case SNDRV_PCM_FORMAT_S16_LE: + regmap_update_bits(max98371->regmap, MAX98371_FMT, + MAX98371_FMT_MASK, MAX98371_DAI_CHANSZ_16); + ch_size = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + regmap_update_bits(max98371->regmap, MAX98371_FMT, + MAX98371_FMT_MASK, MAX98371_DAI_CHANSZ_32); + ch_size = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + regmap_update_bits(max98371->regmap, MAX98371_FMT, + MAX98371_FMT_MASK, MAX98371_DAI_CHANSZ_32); + ch_size = 32; + break; + default: + return -EINVAL; + } + + /* BCLK/LRCLK ratio calculation */ + blr_clk_ratio = channels * ch_size; + switch (blr_clk_ratio) { + case 32: + regmap_update_bits(max98371->regmap, + MAX98371_DAI_CLK, + MAX98371_DAI_BSEL_MASK, MAX98371_DAI_BSEL_32); + break; + case 48: + regmap_update_bits(max98371->regmap, + MAX98371_DAI_CLK, + MAX98371_DAI_BSEL_MASK, MAX98371_DAI_BSEL_48); + break; + case 64: + regmap_update_bits(max98371->regmap, + MAX98371_DAI_CLK, + MAX98371_DAI_BSEL_MASK, MAX98371_DAI_BSEL_64); + break; + default: + return -EINVAL; + } + + switch (rate) { + case 32000: + regmap_update_bits(max98371->regmap, + MAX98371_SPK_SR, + MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_32); + break; + case 44100: + regmap_update_bits(max98371->regmap, + MAX98371_SPK_SR, + MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_44); + break; + case 48000: + regmap_update_bits(max98371->regmap, + MAX98371_SPK_SR, + MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_48); + break; + case 88200: + regmap_update_bits(max98371->regmap, + MAX98371_SPK_SR, + MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_88); + break; + case 96000: + regmap_update_bits(max98371->regmap, + MAX98371_SPK_SR, + MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_96); + break; + default: + return -EINVAL; + } + + /* enabling both the RX channels*/ + regmap_update_bits(max98371->regmap, MAX98371_MONOMIX_SRC, + MAX98371_MONOMIX_SRC_MASK, MONOMIX_RX_0_1); + regmap_update_bits(max98371->regmap, MAX98371_DAI_CHANNEL, + MAX98371_CHANNEL_MASK, MAX98371_CHANNEL_MASK); + return 0; +} + +static const struct snd_soc_dapm_widget max98371_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", NULL, MAX98371_SPK_ENABLE, 0, 0), + SND_SOC_DAPM_SUPPLY("Global Enable", MAX98371_GLOBAL_ENABLE, + 0, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("SPK_OUT"), +}; + +static const struct snd_soc_dapm_route max98371_audio_map[] = { + {"DAC", NULL, "HiFi Playback"}, + {"SPK_OUT", NULL, "DAC"}, + {"SPK_OUT", NULL, "Global Enable"}, +}; + +#define MAX98371_RATES SNDRV_PCM_RATE_8000_48000 +#define MAX98371_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) + +static const struct snd_soc_dai_ops max98371_dai_ops = { + .set_fmt = max98371_dai_set_fmt, + .hw_params = max98371_dai_hw_params, +}; + +static struct snd_soc_dai_driver max98371_dai[] = { + { + .name = "max98371-aif1", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = MAX98371_FORMATS, + }, + .ops = &max98371_dai_ops, + } +}; + +static const struct snd_soc_codec_driver max98371_codec = { + .controls = max98371_snd_controls, + .num_controls = ARRAY_SIZE(max98371_snd_controls), + .dapm_routes = max98371_audio_map, + .num_dapm_routes = ARRAY_SIZE(max98371_audio_map), + .dapm_widgets = max98371_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98371_dapm_widgets), +}; + +static const struct regmap_config max98371_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX98371_VERSION, + .reg_defaults = max98371_reg, + .num_reg_defaults = ARRAY_SIZE(max98371_reg), + .volatile_reg = max98371_volatile_register, + .readable_reg = max98371_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int max98371_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max98371_priv *max98371; + int ret, reg; + + max98371 = devm_kzalloc(&i2c->dev, + sizeof(*max98371), GFP_KERNEL); + if (!max98371) + return -ENOMEM; + + i2c_set_clientdata(i2c, max98371); + max98371->regmap = devm_regmap_init_i2c(i2c, &max98371_regmap); + if (IS_ERR(max98371->regmap)) { + ret = PTR_ERR(max98371->regmap); + dev_err(&i2c->dev, + "Failed to allocate regmap: %d\n", ret); + return ret; + } + + ret = regmap_read(max98371->regmap, MAX98371_VERSION, ®); + if (ret < 0) { + dev_info(&i2c->dev, "device error %d\n", ret); + return ret; + } + dev_info(&i2c->dev, "device version %x\n", reg); + + ret = snd_soc_register_codec(&i2c->dev, &max98371_codec, + max98371_dai, ARRAY_SIZE(max98371_dai)); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to register codec: %d\n", ret); + return ret; + } + return ret; +} + +static int max98371_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct i2c_device_id max98371_i2c_id[] = { + { "max98371", 0 }, +}; + +MODULE_DEVICE_TABLE(i2c, max98371_i2c_id); + +static const struct of_device_id max98371_of_match[] = { + { .compatible = "maxim,max98371", }, + { } +}; +MODULE_DEVICE_TABLE(of, max98371_of_match); + +static struct i2c_driver max98371_i2c_driver = { + .driver = { + .name = "max98371", + .owner = THIS_MODULE, + .pm = NULL, + .of_match_table = of_match_ptr(max98371_of_match), + }, + .probe = max98371_i2c_probe, + .remove = max98371_i2c_remove, + .id_table = max98371_i2c_id, +}; + +module_i2c_driver(max98371_i2c_driver); + +MODULE_AUTHOR("anish kumar <yesanishhere@gmail.com>"); +MODULE_DESCRIPTION("ALSA SoC MAX98371 driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98371.h b/sound/soc/codecs/max98371.h new file mode 100644 index 0000000..9f63309 --- /dev/null +++ b/sound/soc/codecs/max98371.h @@ -0,0 +1,67 @@ +/* + * max98371.h -- MAX98371 ALSA SoC Audio driver + * + * Copyright 2011-2012 Maxim Integrated Products + * + * 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 _MAX98371_H +#define _MAX98371_H + +#define MAX98371_IRQ_CLEAR1 0x01 +#define MAX98371_IRQ_CLEAR2 0x02 +#define MAX98371_IRQ_CLEAR3 0x03 +#define MAX98371_DAI_CLK 0x10 +#define MAX98371_DAI_BSEL_MASK 0xF +#define MAX98371_DAI_BSEL_32 2 +#define MAX98371_DAI_BSEL_48 3 +#define MAX98371_DAI_BSEL_64 4 +#define MAX98371_SPK_SR 0x11 +#define MAX98371_SPK_SR_MASK 0xF +#define MAX98371_SPK_SR_32 6 +#define MAX98371_SPK_SR_44 7 +#define MAX98371_SPK_SR_48 8 +#define MAX98371_SPK_SR_88 10 +#define MAX98371_SPK_SR_96 11 +#define MAX98371_DAI_CHANNEL 0x15 +#define MAX98371_CHANNEL_MASK 0x3 +#define MAX98371_MONOMIX_SRC 0x18 +#define MAX98371_MONOMIX_CFG 0x19 +#define MAX98371_HPF 0x1C +#define MAX98371_MONOMIX_SRC_MASK 0xFF +#define MONOMIX_RX_0_1 ((0x1)<<(4)) +#define M98371_DAI_CHANNEL_I2S 0x3 +#define MAX98371_DIGITAL_GAIN 0x2D +#define MAX98371_DIGITAL_GAIN_WIDTH 0x7 +#define MAX98371_GAIN 0x2E +#define MAX98371_GAIN_SHIFT 0x4 +#define MAX98371_GAIN_WIDTH 0x4 +#define MAX98371_DHT_MAX_WIDTH 4 +#define MAX98371_FMT 0x14 +#define MAX98371_CHANSZ_WIDTH 6 +#define MAX98371_FMT_MASK ((0x3)<<(MAX98371_CHANSZ_WIDTH)) +#define MAX98371_FMT_MODE_MASK ((0x7)<<(3)) +#define MAX98371_DAI_LEFT ((0x1)<<(3)) +#define MAX98371_DAI_RIGHT ((0x2)<<(3)) +#define MAX98371_DAI_CHANSZ_16 ((1)<<(MAX98371_CHANSZ_WIDTH)) +#define MAX98371_DAI_CHANSZ_24 ((2)<<(MAX98371_CHANSZ_WIDTH)) +#define MAX98371_DAI_CHANSZ_32 ((3)<<(MAX98371_CHANSZ_WIDTH)) +#define MAX98371_DHT 0x32 +#define MAX98371_DHT_STEP 0x3 +#define MAX98371_DHT_GAIN 0x31 +#define MAX98371_DHT_GAIN_WIDTH 0x4 +#define MAX98371_DHT_ROT_WIDTH 0x4 +#define MAX98371_SPK_ENABLE 0x4A +#define MAX98371_GLOBAL_ENABLE 0x50 +#define MAX98371_SOFT_RESET 0x51 +#define MAX98371_VERSION 0xFF + + +struct max98371_priv { + struct regmap *regmap; + struct snd_soc_codec *codec; +}; +#endif diff --git a/sound/soc/codecs/rt298.c b/sound/soc/codecs/rt298.c index a1aaffc..f80cfe4 100644 --- a/sound/soc/codecs/rt298.c +++ b/sound/soc/codecs/rt298.c @@ -276,6 +276,8 @@ static int rt298_jack_detect(struct rt298_priv *rt298, bool *hp, bool *mic) } else { *mic = false; regmap_write(rt298->regmap, RT298_SET_MIC1, 0x20); + regmap_update_bits(rt298->regmap, + RT298_CBJ_CTRL1, 0x0400, 0x0000); } } else { regmap_read(rt298->regmap, RT298_GET_HP_SENSE, &buf); @@ -482,6 +484,26 @@ static int rt298_adc_event(struct snd_soc_dapm_widget *w, snd_soc_update_bits(codec, VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, nid, 0), 0x7080, 0x7000); + /* If MCLK doesn't exist, reset AD filter */ + if (!(snd_soc_read(codec, RT298_VAD_CTRL) & 0x200)) { + pr_info("NO MCLK\n"); + switch (nid) { + case RT298_ADC_IN1: + snd_soc_update_bits(codec, + RT298_D_FILTER_CTRL, 0x2, 0x2); + mdelay(10); + snd_soc_update_bits(codec, + RT298_D_FILTER_CTRL, 0x2, 0x0); + break; + case RT298_ADC_IN2: + snd_soc_update_bits(codec, + RT298_D_FILTER_CTRL, 0x4, 0x4); + mdelay(10); + snd_soc_update_bits(codec, + RT298_D_FILTER_CTRL, 0x4, 0x0); + break; + } + } break; case SND_SOC_DAPM_PRE_PMD: snd_soc_update_bits(codec, @@ -520,30 +542,12 @@ static int rt298_mic1_event(struct snd_soc_dapm_widget *w, return 0; } -static int rt298_vref_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); - - switch (event) { - case SND_SOC_DAPM_PRE_PMU: - snd_soc_update_bits(codec, - RT298_CBJ_CTRL1, 0x0400, 0x0000); - mdelay(50); - break; - default: - return 0; - } - - return 0; -} - static const struct snd_soc_dapm_widget rt298_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY_S("HV", 1, RT298_POWER_CTRL1, 12, 1, NULL, 0), SND_SOC_DAPM_SUPPLY("VREF", RT298_POWER_CTRL1, - 0, 1, rt298_vref_event, SND_SOC_DAPM_PRE_PMU), + 0, 1, NULL, 0), SND_SOC_DAPM_SUPPLY_S("BG_MBIAS", 1, RT298_POWER_CTRL2, 1, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("LDO1", 1, RT298_POWER_CTRL2, @@ -934,18 +938,9 @@ static int rt298_set_bias_level(struct snd_soc_codec *codec, } break; - case SND_SOC_BIAS_ON: - mdelay(30); - snd_soc_update_bits(codec, - RT298_CBJ_CTRL1, 0x0400, 0x0400); - - break; - case SND_SOC_BIAS_STANDBY: snd_soc_write(codec, RT298_SET_AUDIO_POWER, AC_PWRST_D3); - snd_soc_update_bits(codec, - RT298_CBJ_CTRL1, 0x0400, 0x0000); break; default: diff --git a/sound/soc/codecs/rt298.h b/sound/soc/codecs/rt298.h index d66f884..3638f3d 100644 --- a/sound/soc/codecs/rt298.h +++ b/sound/soc/codecs/rt298.h @@ -137,6 +137,7 @@ #define RT298_A_BIAS_CTRL2 0x02 #define RT298_POWER_CTRL1 0x03 #define RT298_A_BIAS_CTRL3 0x04 +#define RT298_D_FILTER_CTRL 0x05 #define RT298_POWER_CTRL2 0x08 #define RT298_I2S_CTRL1 0x09 #define RT298_I2S_CTRL2 0x0a @@ -148,6 +149,7 @@ #define RT298_IRQ_CTRL 0x33 #define RT298_WIND_FILTER_CTRL 0x46 #define RT298_PLL_CTRL1 0x49 +#define RT298_VAD_CTRL 0x4e #define RT298_CBJ_CTRL1 0x4f #define RT298_CBJ_CTRL2 0x50 #define RT298_PLL_CTRL 0x63 diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 6021226..da9483c 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -1241,60 +1241,46 @@ static int rt5677_dmic_use_asrc(struct snd_soc_dapm_widget *source, regmap_read(rt5677->regmap, RT5677_ASRC_5, &asrc_setting); asrc_setting = (asrc_setting & RT5677_AD_STO1_CLK_SEL_MASK) >> RT5677_AD_STO1_CLK_SEL_SFT; - if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC && - asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC) - return 1; break; case 10: regmap_read(rt5677->regmap, RT5677_ASRC_5, &asrc_setting); asrc_setting = (asrc_setting & RT5677_AD_STO2_CLK_SEL_MASK) >> RT5677_AD_STO2_CLK_SEL_SFT; - if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC && - asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC) - return 1; break; case 9: regmap_read(rt5677->regmap, RT5677_ASRC_5, &asrc_setting); asrc_setting = (asrc_setting & RT5677_AD_STO3_CLK_SEL_MASK) >> RT5677_AD_STO3_CLK_SEL_SFT; - if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC && - asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC) - return 1; break; case 8: regmap_read(rt5677->regmap, RT5677_ASRC_5, &asrc_setting); asrc_setting = (asrc_setting & RT5677_AD_STO4_CLK_SEL_MASK) >> RT5677_AD_STO4_CLK_SEL_SFT; - if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC && - asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC) - return 1; break; case 7: regmap_read(rt5677->regmap, RT5677_ASRC_6, &asrc_setting); asrc_setting = (asrc_setting & RT5677_AD_MONOL_CLK_SEL_MASK) >> RT5677_AD_MONOL_CLK_SEL_SFT; - if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC && - asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC) - return 1; break; case 6: regmap_read(rt5677->regmap, RT5677_ASRC_6, &asrc_setting); asrc_setting = (asrc_setting & RT5677_AD_MONOR_CLK_SEL_MASK) >> RT5677_AD_MONOR_CLK_SEL_SFT; - if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC && - asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC) - return 1; break; default: - break; + return 0; } + if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC && + asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC) + return 1; + return 0; } diff --git a/sound/soc/codecs/tas571x.c b/sound/soc/codecs/tas571x.c index 39307ad..b8d19b7 100644 --- a/sound/soc/codecs/tas571x.c +++ b/sound/soc/codecs/tas571x.c @@ -4,6 +4,9 @@ * Copyright (C) 2015 Google, Inc. * Copyright (c) 2013 Daniel Mack <zonque@gmail.com> * + * TAS5721 support: + * Copyright (C) 2016 Petr Kulhavy, Barix AG <petr@barix.com> + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -57,6 +60,10 @@ static int tas571x_register_size(struct tas571x_private *priv, unsigned int reg) case TAS571X_CH1_VOL_REG: case TAS571X_CH2_VOL_REG: return priv->chip->vol_reg_size; + case TAS571X_INPUT_MUX_REG: + case TAS571X_CH4_SRC_SELECT_REG: + case TAS571X_PWM_MUX_REG: + return 4; default: return 1; } @@ -167,6 +174,23 @@ static int tas571x_hw_params(struct snd_pcm_substream *substream, TAS571X_SDI_FMT_MASK, val); } +static int tas571x_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u8 sysctl2; + int ret; + + sysctl2 = mute ? TAS571X_SYS_CTRL_2_SDN_MASK : 0; + + ret = snd_soc_update_bits(codec, + TAS571X_SYS_CTRL_2_REG, + TAS571X_SYS_CTRL_2_SDN_MASK, + sysctl2); + usleep_range(1000, 2000); + + return ret; +} + static int tas571x_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { @@ -214,6 +238,7 @@ static int tas571x_set_bias_level(struct snd_soc_codec *codec, static const struct snd_soc_dai_ops tas571x_dai_ops = { .set_fmt = tas571x_set_dai_fmt, .hw_params = tas571x_hw_params, + .digital_mute = tas571x_mute, }; static const char *const tas5711_supply_names[] = { @@ -241,6 +266,26 @@ static const struct snd_kcontrol_new tas5711_controls[] = { 1, 1), }; +static const struct regmap_range tas571x_readonly_regs_range[] = { + regmap_reg_range(TAS571X_CLK_CTRL_REG, TAS571X_DEV_ID_REG), +}; + +static const struct regmap_range tas571x_volatile_regs_range[] = { + regmap_reg_range(TAS571X_CLK_CTRL_REG, TAS571X_ERR_STATUS_REG), + regmap_reg_range(TAS571X_OSC_TRIM_REG, TAS571X_OSC_TRIM_REG), +}; + +static const struct regmap_access_table tas571x_write_regs = { + .no_ranges = tas571x_readonly_regs_range, + .n_no_ranges = ARRAY_SIZE(tas571x_readonly_regs_range), +}; + +static const struct regmap_access_table tas571x_volatile_regs = { + .yes_ranges = tas571x_volatile_regs_range, + .n_yes_ranges = ARRAY_SIZE(tas571x_volatile_regs_range), + +}; + static const struct reg_default tas5711_reg_defaults[] = { { 0x04, 0x05 }, { 0x05, 0x40 }, @@ -260,6 +305,8 @@ static const struct regmap_config tas5711_regmap_config = { .reg_defaults = tas5711_reg_defaults, .num_reg_defaults = ARRAY_SIZE(tas5711_reg_defaults), .cache_type = REGCACHE_RBTREE, + .wr_table = &tas571x_write_regs, + .volatile_table = &tas571x_volatile_regs, }; static const struct tas571x_chip tas5711_chip = { @@ -314,6 +361,8 @@ static const struct regmap_config tas5717_regmap_config = { .reg_defaults = tas5717_reg_defaults, .num_reg_defaults = ARRAY_SIZE(tas5717_reg_defaults), .cache_type = REGCACHE_RBTREE, + .wr_table = &tas571x_write_regs, + .volatile_table = &tas571x_volatile_regs, }; /* This entry is reused for tas5719 as the software interface is identical. */ @@ -326,6 +375,77 @@ static const struct tas571x_chip tas5717_chip = { .vol_reg_size = 2, }; +static const char *const tas5721_supply_names[] = { + "AVDD", + "DVDD", + "DRVDD", + "PVDD", +}; + +static const struct snd_kcontrol_new tas5721_controls[] = { + SOC_SINGLE_TLV("Master Volume", + TAS571X_MVOL_REG, + 0, 0xff, 1, tas5711_volume_tlv), + SOC_DOUBLE_R_TLV("Speaker Volume", + TAS571X_CH1_VOL_REG, + TAS571X_CH2_VOL_REG, + 0, 0xff, 1, tas5711_volume_tlv), + SOC_DOUBLE("Speaker Switch", + TAS571X_SOFT_MUTE_REG, + TAS571X_SOFT_MUTE_CH1_SHIFT, TAS571X_SOFT_MUTE_CH2_SHIFT, + 1, 1), +}; + +static const struct reg_default tas5721_reg_defaults[] = { + {TAS571X_CLK_CTRL_REG, 0x6c}, + {TAS571X_DEV_ID_REG, 0x00}, + {TAS571X_ERR_STATUS_REG, 0x00}, + {TAS571X_SYS_CTRL_1_REG, 0xa0}, + {TAS571X_SDI_REG, 0x05}, + {TAS571X_SYS_CTRL_2_REG, 0x40}, + {TAS571X_SOFT_MUTE_REG, 0x00}, + {TAS571X_MVOL_REG, 0xff}, + {TAS571X_CH1_VOL_REG, 0x30}, + {TAS571X_CH2_VOL_REG, 0x30}, + {TAS571X_CH3_VOL_REG, 0x30}, + {TAS571X_VOL_CFG_REG, 0x91}, + {TAS571X_MODULATION_LIMIT_REG, 0x02}, + {TAS571X_IC_DELAY_CH1_REG, 0xac}, + {TAS571X_IC_DELAY_CH2_REG, 0x54}, + {TAS571X_IC_DELAY_CH3_REG, 0xac}, + {TAS571X_IC_DELAY_CH4_REG, 0x54}, + {TAS571X_PWM_CH_SDN_GROUP_REG, 0x30}, + {TAS571X_START_STOP_PERIOD_REG, 0x0f}, + {TAS571X_OSC_TRIM_REG, 0x82}, + {TAS571X_BKND_ERR_REG, 0x02}, + {TAS571X_INPUT_MUX_REG, 0x17772}, + {TAS571X_CH4_SRC_SELECT_REG, 0x4303}, + {TAS571X_PWM_MUX_REG, 0x1021345}, +}; + +static const struct regmap_config tas5721_regmap_config = { + .reg_bits = 8, + .val_bits = 32, + .max_register = 0xff, + .reg_read = tas571x_reg_read, + .reg_write = tas571x_reg_write, + .reg_defaults = tas5721_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tas5721_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .wr_table = &tas571x_write_regs, + .volatile_table = &tas571x_volatile_regs, +}; + + +static const struct tas571x_chip tas5721_chip = { + .supply_names = tas5721_supply_names, + .num_supply_names = ARRAY_SIZE(tas5721_supply_names), + .controls = tas5711_controls, + .num_controls = ARRAY_SIZE(tas5711_controls), + .regmap_config = &tas5721_regmap_config, + .vol_reg_size = 1, +}; + static const struct snd_soc_dapm_widget tas571x_dapm_widgets[] = { SND_SOC_DAPM_DAC("DACL", NULL, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_DAC("DACR", NULL, SND_SOC_NOPM, 0, 0), @@ -386,11 +506,10 @@ static int tas571x_i2c_probe(struct i2c_client *client, i2c_set_clientdata(client, priv); of_id = of_match_device(tas571x_of_match, dev); - if (!of_id) { - dev_err(dev, "Unknown device type\n"); - return -EINVAL; - } - priv->chip = of_id->data; + if (of_id) + priv->chip = of_id->data; + else + priv->chip = (void *) id->driver_data; priv->mclk = devm_clk_get(dev, "mclk"); if (IS_ERR(priv->mclk) && PTR_ERR(priv->mclk) != -ENOENT) { @@ -445,10 +564,6 @@ static int tas571x_i2c_probe(struct i2c_client *client, if (ret) return ret; - ret = regmap_update_bits(priv->regmap, TAS571X_SYS_CTRL_2_REG, - TAS571X_SYS_CTRL_2_SDN_MASK, 0); - if (ret) - return ret; memcpy(&priv->codec_driver, &tas571x_codec, sizeof(priv->codec_driver)); priv->codec_driver.controls = priv->chip->controls; @@ -486,14 +601,16 @@ static const struct of_device_id tas571x_of_match[] = { { .compatible = "ti,tas5711", .data = &tas5711_chip, }, { .compatible = "ti,tas5717", .data = &tas5717_chip, }, { .compatible = "ti,tas5719", .data = &tas5717_chip, }, + { .compatible = "ti,tas5721", .data = &tas5721_chip, }, { } }; MODULE_DEVICE_TABLE(of, tas571x_of_match); static const struct i2c_device_id tas571x_i2c_id[] = { - { "tas5711", 0 }, - { "tas5717", 0 }, - { "tas5719", 0 }, + { "tas5711", (kernel_ulong_t) &tas5711_chip }, + { "tas5717", (kernel_ulong_t) &tas5717_chip }, + { "tas5719", (kernel_ulong_t) &tas5717_chip }, + { "tas5721", (kernel_ulong_t) &tas5721_chip }, { } }; MODULE_DEVICE_TABLE(i2c, tas571x_i2c_id); diff --git a/sound/soc/codecs/tas571x.h b/sound/soc/codecs/tas571x.h index 0aee471..cf800c3 100644 --- a/sound/soc/codecs/tas571x.h +++ b/sound/soc/codecs/tas571x.h @@ -13,6 +13,10 @@ #define _TAS571X_H /* device registers */ +#define TAS571X_CLK_CTRL_REG 0x00 +#define TAS571X_DEV_ID_REG 0x01 +#define TAS571X_ERR_STATUS_REG 0x02 +#define TAS571X_SYS_CTRL_1_REG 0x03 #define TAS571X_SDI_REG 0x04 #define TAS571X_SDI_FMT_MASK 0x0f @@ -27,7 +31,25 @@ #define TAS571X_MVOL_REG 0x07 #define TAS571X_CH1_VOL_REG 0x08 #define TAS571X_CH2_VOL_REG 0x09 +#define TAS571X_CH3_VOL_REG 0x0a +#define TAS571X_VOL_CFG_REG 0x0e +#define TAS571X_MODULATION_LIMIT_REG 0x10 +#define TAS571X_IC_DELAY_CH1_REG 0x11 +#define TAS571X_IC_DELAY_CH2_REG 0x12 +#define TAS571X_IC_DELAY_CH3_REG 0x13 +#define TAS571X_IC_DELAY_CH4_REG 0x14 +#define TAS571X_PWM_CH_SDN_GROUP_REG 0x19 /* N/A on TAS5717, TAS5719 */ +#define TAS571X_PWM_CH1_SDN_MASK (1<<0) +#define TAS571X_PWM_CH2_SDN_SHIFT (1<<1) +#define TAS571X_PWM_CH3_SDN_SHIFT (1<<2) +#define TAS571X_PWM_CH4_SDN_SHIFT (1<<3) + +#define TAS571X_START_STOP_PERIOD_REG 0x1a #define TAS571X_OSC_TRIM_REG 0x1b +#define TAS571X_BKND_ERR_REG 0x1c +#define TAS571X_INPUT_MUX_REG 0x20 +#define TAS571X_CH4_SRC_SELECT_REG 0x21 +#define TAS571X_PWM_MUX_REG 0x25 #endif /* _TAS571X_H */ diff --git a/sound/soc/codecs/tas5720.c b/sound/soc/codecs/tas5720.c new file mode 100644 index 0000000..f54fb46 --- /dev/null +++ b/sound/soc/codecs/tas5720.c @@ -0,0 +1,620 @@ +/* + * tas5720.c - ALSA SoC Texas Instruments TAS5720 Mono Audio Amplifier + * + * Copyright (C)2015-2016 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Andreas Dannenberg <dannenberg@ti.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/regulator/consumer.h> +#include <linux/delay.h> + +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> + +#include "tas5720.h" + +/* Define how often to check (and clear) the fault status register (in ms) */ +#define TAS5720_FAULT_CHECK_INTERVAL 200 + +static const char * const tas5720_supply_names[] = { + "dvdd", /* Digital power supply. Connect to 3.3-V supply. */ + "pvdd", /* Class-D amp and analog power supply (connected). */ +}; + +#define TAS5720_NUM_SUPPLIES ARRAY_SIZE(tas5720_supply_names) + +struct tas5720_data { + struct snd_soc_codec *codec; + struct regmap *regmap; + struct i2c_client *tas5720_client; + struct regulator_bulk_data supplies[TAS5720_NUM_SUPPLIES]; + struct delayed_work fault_check_work; + unsigned int last_fault; +}; + +static int tas5720_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; + unsigned int rate = params_rate(params); + bool ssz_ds; + int ret; + + switch (rate) { + case 44100: + case 48000: + ssz_ds = false; + break; + case 88200: + case 96000: + ssz_ds = true; + break; + default: + dev_err(codec->dev, "unsupported sample rate: %u\n", rate); + return -EINVAL; + } + + ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL1_REG, + TAS5720_SSZ_DS, ssz_ds); + if (ret < 0) { + dev_err(codec->dev, "error setting sample rate: %d\n", ret); + return ret; + } + + return 0; +} + +static int tas5720_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + u8 serial_format; + int ret; + + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) { + dev_vdbg(codec->dev, "DAI Format master is not found\n"); + return -EINVAL; + } + + switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK | + SND_SOC_DAIFMT_INV_MASK)) { + case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF): + /* 1st data bit occur one BCLK cycle after the frame sync */ + serial_format = TAS5720_SAIF_I2S; + break; + case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF): + /* + * Note that although the TAS5720 does not have a dedicated DSP + * mode it doesn't care about the LRCLK duty cycle during TDM + * operation. Therefore we can use the device's I2S mode with + * its delaying of the 1st data bit to receive DSP_A formatted + * data. See device datasheet for additional details. + */ + serial_format = TAS5720_SAIF_I2S; + break; + case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF): + /* + * Similar to DSP_A, we can use the fact that the TAS5720 does + * not care about the LRCLK duty cycle during TDM to receive + * DSP_B formatted data in LEFTJ mode (no delaying of the 1st + * data bit). + */ + serial_format = TAS5720_SAIF_LEFTJ; + break; + case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF): + /* No delay after the frame sync */ + serial_format = TAS5720_SAIF_LEFTJ; + break; + default: + dev_vdbg(codec->dev, "DAI Format is not found\n"); + return -EINVAL; + } + + ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL1_REG, + TAS5720_SAIF_FORMAT_MASK, + serial_format); + if (ret < 0) { + dev_err(codec->dev, "error setting SAIF format: %d\n", ret); + return ret; + } + + return 0; +} + +static int tas5720_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int first_slot; + int ret; + + if (!tx_mask) { + dev_err(codec->dev, "tx masks must not be 0\n"); + return -EINVAL; + } + + /* + * Determine the first slot that is being requested. We will only + * use the first slot that is found since the TAS5720 is a mono + * amplifier. + */ + first_slot = __ffs(tx_mask); + + if (first_slot > 7) { + dev_err(codec->dev, "slot selection out of bounds (%u)\n", + first_slot); + return -EINVAL; + } + + /* Enable manual TDM slot selection (instead of I2C ID based) */ + ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL1_REG, + TAS5720_TDM_CFG_SRC, TAS5720_TDM_CFG_SRC); + if (ret < 0) + goto error_snd_soc_update_bits; + + /* Configure the TDM slot to process audio from */ + ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL2_REG, + TAS5720_TDM_SLOT_SEL_MASK, first_slot); + if (ret < 0) + goto error_snd_soc_update_bits; + + return 0; + +error_snd_soc_update_bits: + dev_err(codec->dev, "error configuring TDM mode: %d\n", ret); + return ret; +} + +static int tas5720_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + int ret; + + ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL2_REG, + TAS5720_MUTE, mute ? TAS5720_MUTE : 0); + if (ret < 0) { + dev_err(codec->dev, "error (un-)muting device: %d\n", ret); + return ret; + } + + return 0; +} + +static void tas5720_fault_check_work(struct work_struct *work) +{ + struct tas5720_data *tas5720 = container_of(work, struct tas5720_data, + fault_check_work.work); + struct device *dev = tas5720->codec->dev; + unsigned int curr_fault; + int ret; + + ret = regmap_read(tas5720->regmap, TAS5720_FAULT_REG, &curr_fault); + if (ret < 0) { + dev_err(dev, "failed to read FAULT register: %d\n", ret); + goto out; + } + + /* Check/handle all errors except SAIF clock errors */ + curr_fault &= TAS5720_OCE | TAS5720_DCE | TAS5720_OTE; + + /* + * Only flag errors once for a given occurrence. This is needed as + * the TAS5720 will take time clearing the fault condition internally + * during which we don't want to bombard the system with the same + * error message over and over. + */ + if ((curr_fault & TAS5720_OCE) && !(tas5720->last_fault & TAS5720_OCE)) + dev_crit(dev, "experienced an over current hardware fault\n"); + + if ((curr_fault & TAS5720_DCE) && !(tas5720->last_fault & TAS5720_DCE)) + dev_crit(dev, "experienced a DC detection fault\n"); + + if ((curr_fault & TAS5720_OTE) && !(tas5720->last_fault & TAS5720_OTE)) + dev_crit(dev, "experienced an over temperature fault\n"); + + /* Store current fault value so we can detect any changes next time */ + tas5720->last_fault = curr_fault; + + if (!curr_fault) + goto out; + + /* + * Periodically toggle SDZ (shutdown bit) H->L->H to clear any latching + * faults as long as a fault condition persists. Always going through + * the full sequence no matter the first return value to minimizes + * chances for the device to end up in shutdown mode. + */ + ret = regmap_write_bits(tas5720->regmap, TAS5720_POWER_CTRL_REG, + TAS5720_SDZ, 0); + if (ret < 0) + dev_err(dev, "failed to write POWER_CTRL register: %d\n", ret); + + ret = regmap_write_bits(tas5720->regmap, TAS5720_POWER_CTRL_REG, + TAS5720_SDZ, TAS5720_SDZ); + if (ret < 0) + dev_err(dev, "failed to write POWER_CTRL register: %d\n", ret); + +out: + /* Schedule the next fault check at the specified interval */ + schedule_delayed_work(&tas5720->fault_check_work, + msecs_to_jiffies(TAS5720_FAULT_CHECK_INTERVAL)); +} + +static int tas5720_codec_probe(struct snd_soc_codec *codec) +{ + struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec); + unsigned int device_id; + int ret; + + tas5720->codec = codec; + + ret = regulator_bulk_enable(ARRAY_SIZE(tas5720->supplies), + tas5720->supplies); + if (ret != 0) { + dev_err(codec->dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + ret = regmap_read(tas5720->regmap, TAS5720_DEVICE_ID_REG, &device_id); + if (ret < 0) { + dev_err(codec->dev, "failed to read device ID register: %d\n", + ret); + goto probe_fail; + } + + if (device_id != TAS5720_DEVICE_ID) { + dev_err(codec->dev, "wrong device ID. expected: %u read: %u\n", + TAS5720_DEVICE_ID, device_id); + ret = -ENODEV; + goto probe_fail; + } + + /* Set device to mute */ + ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL2_REG, + TAS5720_MUTE, TAS5720_MUTE); + if (ret < 0) + goto error_snd_soc_update_bits; + + /* + * Enter shutdown mode - our default when not playing audio - to + * minimize current consumption. On the TAS5720 there is no real down + * side doing so as all device registers are preserved and the wakeup + * of the codec is rather quick which we do using a dapm widget. + */ + ret = snd_soc_update_bits(codec, TAS5720_POWER_CTRL_REG, + TAS5720_SDZ, 0); + if (ret < 0) + goto error_snd_soc_update_bits; + + INIT_DELAYED_WORK(&tas5720->fault_check_work, tas5720_fault_check_work); + + return 0; + +error_snd_soc_update_bits: + dev_err(codec->dev, "error configuring device registers: %d\n", ret); + +probe_fail: + regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), + tas5720->supplies); + return ret; +} + +static int tas5720_codec_remove(struct snd_soc_codec *codec) +{ + struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec); + int ret; + + cancel_delayed_work_sync(&tas5720->fault_check_work); + + ret = regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), + tas5720->supplies); + if (ret < 0) + dev_err(codec->dev, "failed to disable supplies: %d\n", ret); + + return ret; +}; + +static int tas5720_dac_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 tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec); + int ret; + + if (event & SND_SOC_DAPM_POST_PMU) { + /* Take TAS5720 out of shutdown mode */ + ret = snd_soc_update_bits(codec, TAS5720_POWER_CTRL_REG, + TAS5720_SDZ, TAS5720_SDZ); + if (ret < 0) { + dev_err(codec->dev, "error waking codec: %d\n", ret); + return ret; + } + + /* + * Observe codec shutdown-to-active time. The datasheet only + * lists a nominal value however just use-it as-is without + * additional padding to minimize the delay introduced in + * starting to play audio (actually there is other setup done + * by the ASoC framework that will provide additional delays, + * so we should always be safe). + */ + msleep(25); + + /* Turn on TAS5720 periodic fault checking/handling */ + tas5720->last_fault = 0; + schedule_delayed_work(&tas5720->fault_check_work, + msecs_to_jiffies(TAS5720_FAULT_CHECK_INTERVAL)); + } else if (event & SND_SOC_DAPM_PRE_PMD) { + /* Disable TAS5720 periodic fault checking/handling */ + cancel_delayed_work_sync(&tas5720->fault_check_work); + + /* Place TAS5720 in shutdown mode to minimize current draw */ + ret = snd_soc_update_bits(codec, TAS5720_POWER_CTRL_REG, + TAS5720_SDZ, 0); + if (ret < 0) { + dev_err(codec->dev, "error shutting down codec: %d\n", + ret); + return ret; + } + } + + return 0; +} + +#ifdef CONFIG_PM +static int tas5720_suspend(struct snd_soc_codec *codec) +{ + struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec); + int ret; + + regcache_cache_only(tas5720->regmap, true); + regcache_mark_dirty(tas5720->regmap); + + ret = regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), + tas5720->supplies); + if (ret < 0) + dev_err(codec->dev, "failed to disable supplies: %d\n", ret); + + return ret; +} + +static int tas5720_resume(struct snd_soc_codec *codec) +{ + struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(tas5720->supplies), + tas5720->supplies); + if (ret < 0) { + dev_err(codec->dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + regcache_cache_only(tas5720->regmap, false); + + ret = regcache_sync(tas5720->regmap); + if (ret < 0) { + dev_err(codec->dev, "failed to sync regcache: %d\n", ret); + return ret; + } + + return 0; +} +#else +#define tas5720_suspend NULL +#define tas5720_resume NULL +#endif + +static bool tas5720_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TAS5720_DEVICE_ID_REG: + case TAS5720_FAULT_REG: + return true; + default: + return false; + } +} + +static const struct regmap_config tas5720_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = TAS5720_MAX_REG, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = tas5720_is_volatile_reg, +}; + +/* + * DAC analog gain. There are four discrete values to select from, ranging + * from 19.2 dB to 26.3dB. + */ +static const DECLARE_TLV_DB_RANGE(dac_analog_tlv, + 0x0, 0x0, TLV_DB_SCALE_ITEM(1920, 0, 0), + 0x1, 0x1, TLV_DB_SCALE_ITEM(2070, 0, 0), + 0x2, 0x2, TLV_DB_SCALE_ITEM(2350, 0, 0), + 0x3, 0x3, TLV_DB_SCALE_ITEM(2630, 0, 0), +); + +/* + * DAC digital volumes. From -103.5 to 24 dB in 0.5 dB steps. Note that + * setting the gain below -100 dB (register value <0x7) is effectively a MUTE + * as per device datasheet. + */ +static DECLARE_TLV_DB_SCALE(dac_tlv, -10350, 50, 0); + +static const struct snd_kcontrol_new tas5720_snd_controls[] = { + SOC_SINGLE_TLV("Speaker Driver Playback Volume", + TAS5720_VOLUME_CTRL_REG, 0, 0xff, 0, dac_tlv), + SOC_SINGLE_TLV("Speaker Driver Analog Gain", TAS5720_ANALOG_CTRL_REG, + TAS5720_ANALOG_GAIN_SHIFT, 3, 0, dac_analog_tlv), +}; + +static const struct snd_soc_dapm_widget tas5720_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("DAC IN", "Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, tas5720_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_OUTPUT("OUT") +}; + +static const struct snd_soc_dapm_route tas5720_audio_map[] = { + { "DAC", NULL, "DAC IN" }, + { "OUT", NULL, "DAC" }, +}; + +static struct snd_soc_codec_driver soc_codec_dev_tas5720 = { + .probe = tas5720_codec_probe, + .remove = tas5720_codec_remove, + .suspend = tas5720_suspend, + .resume = tas5720_resume, + + .controls = tas5720_snd_controls, + .num_controls = ARRAY_SIZE(tas5720_snd_controls), + .dapm_widgets = tas5720_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tas5720_dapm_widgets), + .dapm_routes = tas5720_audio_map, + .num_dapm_routes = ARRAY_SIZE(tas5720_audio_map), +}; + +/* PCM rates supported by the TAS5720 driver */ +#define TAS5720_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +/* Formats supported by TAS5720 driver */ +#define TAS5720_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE |\ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops tas5720_speaker_dai_ops = { + .hw_params = tas5720_hw_params, + .set_fmt = tas5720_set_dai_fmt, + .set_tdm_slot = tas5720_set_dai_tdm_slot, + .digital_mute = tas5720_mute, +}; + +/* + * TAS5720 DAI structure + * + * Note that were are advertising .playback.channels_max = 2 despite this being + * a mono amplifier. The reason for that is that some serial ports such as TI's + * McASP module have a minimum number of channels (2) that they can output. + * Advertising more channels than we have will allow us to interface with such + * a serial port without really any negative side effects as the TAS5720 will + * simply ignore any extra channel(s) asides from the one channel that is + * configured to be played back. + */ +static struct snd_soc_dai_driver tas5720_dai[] = { + { + .name = "tas5720-amplifier", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = TAS5720_RATES, + .formats = TAS5720_FORMATS, + }, + .ops = &tas5720_speaker_dai_ops, + }, +}; + +static int tas5720_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct tas5720_data *data; + int ret; + int i; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->tas5720_client = client; + data->regmap = devm_regmap_init_i2c(client, &tas5720_regmap_config); + if (IS_ERR(data->regmap)) { + ret = PTR_ERR(data->regmap); + dev_err(dev, "failed to allocate register map: %d\n", ret); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(data->supplies); i++) + data->supplies[i].supply = tas5720_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->supplies), + data->supplies); + if (ret != 0) { + dev_err(dev, "failed to request supplies: %d\n", ret); + return ret; + } + + dev_set_drvdata(dev, data); + + ret = snd_soc_register_codec(&client->dev, + &soc_codec_dev_tas5720, + tas5720_dai, ARRAY_SIZE(tas5720_dai)); + if (ret < 0) { + dev_err(dev, "failed to register codec: %d\n", ret); + return ret; + } + + return 0; +} + +static int tas5720_remove(struct i2c_client *client) +{ + struct device *dev = &client->dev; + + snd_soc_unregister_codec(dev); + + return 0; +} + +static const struct i2c_device_id tas5720_id[] = { + { "tas5720", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tas5720_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id tas5720_of_match[] = { + { .compatible = "ti,tas5720", }, + { }, +}; +MODULE_DEVICE_TABLE(of, tas5720_of_match); +#endif + +static struct i2c_driver tas5720_i2c_driver = { + .driver = { + .name = "tas5720", + .of_match_table = of_match_ptr(tas5720_of_match), + }, + .probe = tas5720_probe, + .remove = tas5720_remove, + .id_table = tas5720_id, +}; + +module_i2c_driver(tas5720_i2c_driver); + +MODULE_AUTHOR("Andreas Dannenberg <dannenberg@ti.com>"); +MODULE_DESCRIPTION("TAS5720 Audio amplifier driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tas5720.h b/sound/soc/codecs/tas5720.h new file mode 100644 index 0000000..3d077c7 --- /dev/null +++ b/sound/soc/codecs/tas5720.h @@ -0,0 +1,90 @@ +/* + * tas5720.h - ALSA SoC Texas Instruments TAS5720 Mono Audio Amplifier + * + * Copyright (C)2015-2016 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Andreas Dannenberg <dannenberg@ti.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#ifndef __TAS5720_H__ +#define __TAS5720_H__ + +/* Register Address Map */ +#define TAS5720_DEVICE_ID_REG 0x00 +#define TAS5720_POWER_CTRL_REG 0x01 +#define TAS5720_DIGITAL_CTRL1_REG 0x02 +#define TAS5720_DIGITAL_CTRL2_REG 0x03 +#define TAS5720_VOLUME_CTRL_REG 0x04 +#define TAS5720_ANALOG_CTRL_REG 0x06 +#define TAS5720_FAULT_REG 0x08 +#define TAS5720_DIGITAL_CLIP2_REG 0x10 +#define TAS5720_DIGITAL_CLIP1_REG 0x11 +#define TAS5720_MAX_REG TAS5720_DIGITAL_CLIP1_REG + +/* TAS5720_DEVICE_ID_REG */ +#define TAS5720_DEVICE_ID 0x01 + +/* TAS5720_POWER_CTRL_REG */ +#define TAS5720_DIG_CLIP_MASK GENMASK(7, 2) +#define TAS5720_SLEEP BIT(1) +#define TAS5720_SDZ BIT(0) + +/* TAS5720_DIGITAL_CTRL1_REG */ +#define TAS5720_HPF_BYPASS BIT(7) +#define TAS5720_TDM_CFG_SRC BIT(6) +#define TAS5720_SSZ_DS BIT(3) +#define TAS5720_SAIF_RIGHTJ_24BIT (0x0) +#define TAS5720_SAIF_RIGHTJ_20BIT (0x1) +#define TAS5720_SAIF_RIGHTJ_18BIT (0x2) +#define TAS5720_SAIF_RIGHTJ_16BIT (0x3) +#define TAS5720_SAIF_I2S (0x4) +#define TAS5720_SAIF_LEFTJ (0x5) +#define TAS5720_SAIF_FORMAT_MASK GENMASK(2, 0) + +/* TAS5720_DIGITAL_CTRL2_REG */ +#define TAS5720_MUTE BIT(4) +#define TAS5720_TDM_SLOT_SEL_MASK GENMASK(2, 0) + +/* TAS5720_ANALOG_CTRL_REG */ +#define TAS5720_PWM_RATE_6_3_FSYNC (0x0 << 4) +#define TAS5720_PWM_RATE_8_4_FSYNC (0x1 << 4) +#define TAS5720_PWM_RATE_10_5_FSYNC (0x2 << 4) +#define TAS5720_PWM_RATE_12_6_FSYNC (0x3 << 4) +#define TAS5720_PWM_RATE_14_7_FSYNC (0x4 << 4) +#define TAS5720_PWM_RATE_16_8_FSYNC (0x5 << 4) +#define TAS5720_PWM_RATE_20_10_FSYNC (0x6 << 4) +#define TAS5720_PWM_RATE_24_12_FSYNC (0x7 << 4) +#define TAS5720_PWM_RATE_MASK GENMASK(6, 4) +#define TAS5720_ANALOG_GAIN_19_2DBV (0x0 << 2) +#define TAS5720_ANALOG_GAIN_20_7DBV (0x1 << 2) +#define TAS5720_ANALOG_GAIN_23_5DBV (0x2 << 2) +#define TAS5720_ANALOG_GAIN_26_3DBV (0x3 << 2) +#define TAS5720_ANALOG_GAIN_MASK GENMASK(3, 2) +#define TAS5720_ANALOG_GAIN_SHIFT (0x2) + +/* TAS5720_FAULT_REG */ +#define TAS5720_OC_THRESH_100PCT (0x0 << 4) +#define TAS5720_OC_THRESH_75PCT (0x1 << 4) +#define TAS5720_OC_THRESH_50PCT (0x2 << 4) +#define TAS5720_OC_THRESH_25PCT (0x3 << 4) +#define TAS5720_OC_THRESH_MASK GENMASK(5, 4) +#define TAS5720_CLKE BIT(3) +#define TAS5720_OCE BIT(2) +#define TAS5720_DCE BIT(1) +#define TAS5720_OTE BIT(0) +#define TAS5720_FAULT_MASK GENMASK(3, 0) + +/* TAS5720_DIGITAL_CLIP1_REG */ +#define TAS5720_CLIP1_MASK GENMASK(7, 2) +#define TAS5720_CLIP1_SHIFT (0x2) + +#endif /* __TAS5720_H__ */ diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c index ee4def4..3c5e1df 100644 --- a/sound/soc/codecs/tlv320aic31xx.c +++ b/sound/soc/codecs/tlv320aic31xx.c @@ -28,6 +28,7 @@ #include <linux/i2c.h> #include <linux/gpio.h> #include <linux/regulator/consumer.h> +#include <linux/acpi.h> #include <linux/of.h> #include <linux/of_gpio.h> #include <linux/slab.h> @@ -1280,10 +1281,19 @@ static const struct i2c_device_id aic31xx_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, aic31xx_i2c_id); +#ifdef CONFIG_ACPI +static const struct acpi_device_id aic31xx_acpi_match[] = { + { "10TI3100", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, aic31xx_acpi_match); +#endif + static struct i2c_driver aic31xx_i2c_driver = { .driver = { .name = "tlv320aic31xx-codec", .of_match_table = of_match_ptr(tlv320aic31xx_of_match), + .acpi_match_table = ACPI_PTR(aic31xx_acpi_match), }, .probe = aic31xx_i2c_probe, .remove = aic31xx_i2c_remove, diff --git a/sound/soc/codecs/tlv320aic32x4-i2c.c b/sound/soc/codecs/tlv320aic32x4-i2c.c new file mode 100644 index 0000000..59606cf --- /dev/null +++ b/sound/soc/codecs/tlv320aic32x4-i2c.c @@ -0,0 +1,74 @@ +/* + * linux/sound/soc/codecs/tlv320aic32x4-i2c.c + * + * Copyright 2011 NW Digital Radio + * + * Author: Jeremy McDermond <nh6z@nh6z.net> + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <sound/soc.h> + +#include "tlv320aic32x4.h" + +static int aic32x4_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + struct regmap_config config; + + config = aic32x4_regmap_config; + config.reg_bits = 8; + config.val_bits = 8; + + regmap = devm_regmap_init_i2c(i2c, &config); + return aic32x4_probe(&i2c->dev, regmap); +} + +static int aic32x4_i2c_remove(struct i2c_client *i2c) +{ + return aic32x4_remove(&i2c->dev); +} + +static const struct i2c_device_id aic32x4_i2c_id[] = { + { "tlv320aic32x4", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, aic32x4_i2c_id); + +static const struct of_device_id aic32x4_of_id[] = { + { .compatible = "ti,tlv320aic32x4", }, + { /* senitel */ } +}; +MODULE_DEVICE_TABLE(of, aic32x4_of_id); + +static struct i2c_driver aic32x4_i2c_driver = { + .driver = { + .name = "tlv320aic32x4", + .of_match_table = aic32x4_of_id, + }, + .probe = aic32x4_i2c_probe, + .remove = aic32x4_i2c_remove, + .id_table = aic32x4_i2c_id, +}; + +module_i2c_driver(aic32x4_i2c_driver); + +MODULE_DESCRIPTION("ASoC TLV320AIC32x4 codec driver I2C"); +MODULE_AUTHOR("Jeremy McDermond <nh6z@nh6z.net>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic32x4-spi.c b/sound/soc/codecs/tlv320aic32x4-spi.c new file mode 100644 index 0000000..724fcdd4 --- /dev/null +++ b/sound/soc/codecs/tlv320aic32x4-spi.c @@ -0,0 +1,76 @@ +/* + * linux/sound/soc/codecs/tlv320aic32x4-spi.c + * + * Copyright 2011 NW Digital Radio + * + * Author: Jeremy McDermond <nh6z@nh6z.net> + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/spi/spi.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <sound/soc.h> + +#include "tlv320aic32x4.h" + +static int aic32x4_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + struct regmap_config config; + + config = aic32x4_regmap_config; + config.reg_bits = 7; + config.pad_bits = 1; + config.val_bits = 8; + config.read_flag_mask = 0x01; + + regmap = devm_regmap_init_spi(spi, &config); + return aic32x4_probe(&spi->dev, regmap); +} + +static int aic32x4_spi_remove(struct spi_device *spi) +{ + return aic32x4_remove(&spi->dev); +} + +static const struct spi_device_id aic32x4_spi_id[] = { + { "tlv320aic32x4", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, aic32x4_spi_id); + +static const struct of_device_id aic32x4_of_id[] = { + { .compatible = "ti,tlv320aic32x4", }, + { /* senitel */ } +}; +MODULE_DEVICE_TABLE(of, aic32x4_of_id); + +static struct spi_driver aic32x4_spi_driver = { + .driver = { + .name = "tlv320aic32x4", + .owner = THIS_MODULE, + .of_match_table = aic32x4_of_id, + }, + .probe = aic32x4_spi_probe, + .remove = aic32x4_spi_remove, + .id_table = aic32x4_spi_id, +}; + +module_spi_driver(aic32x4_spi_driver); + +MODULE_DESCRIPTION("ASoC TLV320AIC32x4 codec driver SPI"); +MODULE_AUTHOR("Jeremy McDermond <nh6z@nh6z.net>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index f2d3191..85d4978 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -30,7 +30,6 @@ #include <linux/pm.h> #include <linux/gpio.h> #include <linux/of_gpio.h> -#include <linux/i2c.h> #include <linux/cdev.h> #include <linux/slab.h> #include <linux/clk.h> @@ -160,7 +159,10 @@ static const struct aic32x4_rate_divs aic32x4_divs[] = { /* 48k rate */ {AIC32X4_FREQ_12000000, 48000, 1, 8, 1920, 128, 2, 8, 128, 2, 8, 4}, {AIC32X4_FREQ_24000000, 48000, 2, 8, 1920, 128, 8, 2, 64, 8, 4, 4}, - {AIC32X4_FREQ_25000000, 48000, 2, 7, 8643, 128, 8, 2, 64, 8, 4, 4} + {AIC32X4_FREQ_25000000, 48000, 2, 7, 8643, 128, 8, 2, 64, 8, 4, 4}, + + /* 96k rate */ + {AIC32X4_FREQ_25000000, 96000, 2, 7, 8643, 64, 4, 4, 64, 4, 4, 1}, }; static const struct snd_kcontrol_new hpl_output_mixer_controls[] = { @@ -181,16 +183,71 @@ static const struct snd_kcontrol_new lor_output_mixer_controls[] = { SOC_DAPM_SINGLE("R_DAC Switch", AIC32X4_LORROUTE, 3, 1, 0), }; -static const struct snd_kcontrol_new left_input_mixer_controls[] = { - SOC_DAPM_SINGLE("IN1_L P Switch", AIC32X4_LMICPGAPIN, 6, 1, 0), - SOC_DAPM_SINGLE("IN2_L P Switch", AIC32X4_LMICPGAPIN, 4, 1, 0), - SOC_DAPM_SINGLE("IN3_L P Switch", AIC32X4_LMICPGAPIN, 2, 1, 0), +static const char * const resistor_text[] = { + "Off", "10 kOhm", "20 kOhm", "40 kOhm", }; -static const struct snd_kcontrol_new right_input_mixer_controls[] = { - SOC_DAPM_SINGLE("IN1_R P Switch", AIC32X4_RMICPGAPIN, 6, 1, 0), - SOC_DAPM_SINGLE("IN2_R P Switch", AIC32X4_RMICPGAPIN, 4, 1, 0), - SOC_DAPM_SINGLE("IN3_R P Switch", AIC32X4_RMICPGAPIN, 2, 1, 0), +/* Left mixer pins */ +static SOC_ENUM_SINGLE_DECL(in1l_lpga_p_enum, AIC32X4_LMICPGAPIN, 6, resistor_text); +static SOC_ENUM_SINGLE_DECL(in2l_lpga_p_enum, AIC32X4_LMICPGAPIN, 4, resistor_text); +static SOC_ENUM_SINGLE_DECL(in3l_lpga_p_enum, AIC32X4_LMICPGAPIN, 2, resistor_text); +static SOC_ENUM_SINGLE_DECL(in1r_lpga_p_enum, AIC32X4_LMICPGAPIN, 0, resistor_text); + +static SOC_ENUM_SINGLE_DECL(cml_lpga_n_enum, AIC32X4_LMICPGANIN, 6, resistor_text); +static SOC_ENUM_SINGLE_DECL(in2r_lpga_n_enum, AIC32X4_LMICPGANIN, 4, resistor_text); +static SOC_ENUM_SINGLE_DECL(in3r_lpga_n_enum, AIC32X4_LMICPGANIN, 2, resistor_text); + +static const struct snd_kcontrol_new in1l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN1_L L+ Switch", in1l_lpga_p_enum), +}; +static const struct snd_kcontrol_new in2l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN2_L L+ Switch", in2l_lpga_p_enum), +}; +static const struct snd_kcontrol_new in3l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN3_L L+ Switch", in3l_lpga_p_enum), +}; +static const struct snd_kcontrol_new in1r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN1_R L+ Switch", in1r_lpga_p_enum), +}; +static const struct snd_kcontrol_new cml_to_lmixer_controls[] = { + SOC_DAPM_ENUM("CM_L L- Switch", cml_lpga_n_enum), +}; +static const struct snd_kcontrol_new in2r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN2_R L- Switch", in2r_lpga_n_enum), +}; +static const struct snd_kcontrol_new in3r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN3_R L- Switch", in3r_lpga_n_enum), +}; + +/* Right mixer pins */ +static SOC_ENUM_SINGLE_DECL(in1r_rpga_p_enum, AIC32X4_RMICPGAPIN, 6, resistor_text); +static SOC_ENUM_SINGLE_DECL(in2r_rpga_p_enum, AIC32X4_RMICPGAPIN, 4, resistor_text); +static SOC_ENUM_SINGLE_DECL(in3r_rpga_p_enum, AIC32X4_RMICPGAPIN, 2, resistor_text); +static SOC_ENUM_SINGLE_DECL(in2l_rpga_p_enum, AIC32X4_RMICPGAPIN, 0, resistor_text); +static SOC_ENUM_SINGLE_DECL(cmr_rpga_n_enum, AIC32X4_RMICPGANIN, 6, resistor_text); +static SOC_ENUM_SINGLE_DECL(in1l_rpga_n_enum, AIC32X4_RMICPGANIN, 4, resistor_text); +static SOC_ENUM_SINGLE_DECL(in3l_rpga_n_enum, AIC32X4_RMICPGANIN, 2, resistor_text); + +static const struct snd_kcontrol_new in1r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN1_R R+ Switch", in1r_rpga_p_enum), +}; +static const struct snd_kcontrol_new in2r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN2_R R+ Switch", in2r_rpga_p_enum), +}; +static const struct snd_kcontrol_new in3r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN3_R R+ Switch", in3r_rpga_p_enum), +}; +static const struct snd_kcontrol_new in2l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN2_L R+ Switch", in2l_rpga_p_enum), +}; +static const struct snd_kcontrol_new cmr_to_rmixer_controls[] = { + SOC_DAPM_ENUM("CM_R R- Switch", cmr_rpga_n_enum), +}; +static const struct snd_kcontrol_new in1l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN1_L R- Switch", in1l_rpga_n_enum), +}; +static const struct snd_kcontrol_new in3l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN3_L R- Switch", in3l_rpga_n_enum), }; static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = { @@ -214,14 +271,39 @@ static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = { &lor_output_mixer_controls[0], ARRAY_SIZE(lor_output_mixer_controls)), SND_SOC_DAPM_PGA("LOR Power", AIC32X4_OUTPWRCTL, 2, 0, NULL, 0), - SND_SOC_DAPM_MIXER("Left Input Mixer", SND_SOC_NOPM, 0, 0, - &left_input_mixer_controls[0], - ARRAY_SIZE(left_input_mixer_controls)), - SND_SOC_DAPM_MIXER("Right Input Mixer", SND_SOC_NOPM, 0, 0, - &right_input_mixer_controls[0], - ARRAY_SIZE(right_input_mixer_controls)), - SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", AIC32X4_ADCSETUP, 6, 0), + SND_SOC_DAPM_MUX("IN1_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in1r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN2_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in2r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN3_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in3r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN2_L to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in2l_to_rmixer_controls), + SND_SOC_DAPM_MUX("CM_R to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + cmr_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN1_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + in1l_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN3_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + in3l_to_rmixer_controls), + + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0), + SND_SOC_DAPM_MUX("IN1_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in1l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN2_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in2l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN3_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in3l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN1_R to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in1r_to_lmixer_controls), + SND_SOC_DAPM_MUX("CM_L to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + cml_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN2_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + in2r_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN3_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + in3r_to_lmixer_controls), + SND_SOC_DAPM_MICBIAS("Mic Bias", AIC32X4_MICBIAS, 6, 0), SND_SOC_DAPM_OUTPUT("HPL"), @@ -261,19 +343,77 @@ static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = { {"LOR Power", NULL, "LOR Output Mixer"}, {"LOR", NULL, "LOR Power"}, - /* Left input */ - {"Left Input Mixer", "IN1_L P Switch", "IN1_L"}, - {"Left Input Mixer", "IN2_L P Switch", "IN2_L"}, - {"Left Input Mixer", "IN3_L P Switch", "IN3_L"}, - - {"Left ADC", NULL, "Left Input Mixer"}, - /* Right Input */ - {"Right Input Mixer", "IN1_R P Switch", "IN1_R"}, - {"Right Input Mixer", "IN2_R P Switch", "IN2_R"}, - {"Right Input Mixer", "IN3_R P Switch", "IN3_R"}, - - {"Right ADC", NULL, "Right Input Mixer"}, + {"Right ADC", NULL, "IN1_R to Right Mixer Positive Resistor"}, + {"IN1_R to Right Mixer Positive Resistor", "10 kOhm", "IN1_R"}, + {"IN1_R to Right Mixer Positive Resistor", "20 kOhm", "IN1_R"}, + {"IN1_R to Right Mixer Positive Resistor", "40 kOhm", "IN1_R"}, + + {"Right ADC", NULL, "IN2_R to Right Mixer Positive Resistor"}, + {"IN2_R to Right Mixer Positive Resistor", "10 kOhm", "IN2_R"}, + {"IN2_R to Right Mixer Positive Resistor", "20 kOhm", "IN2_R"}, + {"IN2_R to Right Mixer Positive Resistor", "40 kOhm", "IN2_R"}, + + {"Right ADC", NULL, "IN3_R to Right Mixer Positive Resistor"}, + {"IN3_R to Right Mixer Positive Resistor", "10 kOhm", "IN3_R"}, + {"IN3_R to Right Mixer Positive Resistor", "20 kOhm", "IN3_R"}, + {"IN3_R to Right Mixer Positive Resistor", "40 kOhm", "IN3_R"}, + + {"Right ADC", NULL, "IN2_L to Right Mixer Positive Resistor"}, + {"IN2_L to Right Mixer Positive Resistor", "10 kOhm", "IN2_L"}, + {"IN2_L to Right Mixer Positive Resistor", "20 kOhm", "IN2_L"}, + {"IN2_L to Right Mixer Positive Resistor", "40 kOhm", "IN2_L"}, + + {"Right ADC", NULL, "CM_R to Right Mixer Negative Resistor"}, + {"CM_R to Right Mixer Negative Resistor", "10 kOhm", "CM_R"}, + {"CM_R to Right Mixer Negative Resistor", "20 kOhm", "CM_R"}, + {"CM_R to Right Mixer Negative Resistor", "40 kOhm", "CM_R"}, + + {"Right ADC", NULL, "IN1_L to Right Mixer Negative Resistor"}, + {"IN1_L to Right Mixer Negative Resistor", "10 kOhm", "IN1_L"}, + {"IN1_L to Right Mixer Negative Resistor", "20 kOhm", "IN1_L"}, + {"IN1_L to Right Mixer Negative Resistor", "40 kOhm", "IN1_L"}, + + {"Right ADC", NULL, "IN3_L to Right Mixer Negative Resistor"}, + {"IN3_L to Right Mixer Negative Resistor", "10 kOhm", "IN3_L"}, + {"IN3_L to Right Mixer Negative Resistor", "20 kOhm", "IN3_L"}, + {"IN3_L to Right Mixer Negative Resistor", "40 kOhm", "IN3_L"}, + + /* Left Input */ + {"Left ADC", NULL, "IN1_L to Left Mixer Positive Resistor"}, + {"IN1_L to Left Mixer Positive Resistor", "10 kOhm", "IN1_L"}, + {"IN1_L to Left Mixer Positive Resistor", "20 kOhm", "IN1_L"}, + {"IN1_L to Left Mixer Positive Resistor", "40 kOhm", "IN1_L"}, + + {"Left ADC", NULL, "IN2_L to Left Mixer Positive Resistor"}, + {"IN2_L to Left Mixer Positive Resistor", "10 kOhm", "IN2_L"}, + {"IN2_L to Left Mixer Positive Resistor", "20 kOhm", "IN2_L"}, + {"IN2_L to Left Mixer Positive Resistor", "40 kOhm", "IN2_L"}, + + {"Left ADC", NULL, "IN3_L to Left Mixer Positive Resistor"}, + {"IN3_L to Left Mixer Positive Resistor", "10 kOhm", "IN3_L"}, + {"IN3_L to Left Mixer Positive Resistor", "20 kOhm", "IN3_L"}, + {"IN3_L to Left Mixer Positive Resistor", "40 kOhm", "IN3_L"}, + + {"Left ADC", NULL, "IN1_R to Left Mixer Positive Resistor"}, + {"IN1_R to Left Mixer Positive Resistor", "10 kOhm", "IN1_R"}, + {"IN1_R to Left Mixer Positive Resistor", "20 kOhm", "IN1_R"}, + {"IN1_R to Left Mixer Positive Resistor", "40 kOhm", "IN1_R"}, + + {"Left ADC", NULL, "CM_L to Left Mixer Negative Resistor"}, + {"CM_L to Left Mixer Negative Resistor", "10 kOhm", "CM_L"}, + {"CM_L to Left Mixer Negative Resistor", "20 kOhm", "CM_L"}, + {"CM_L to Left Mixer Negative Resistor", "40 kOhm", "CM_L"}, + + {"Left ADC", NULL, "IN2_R to Left Mixer Negative Resistor"}, + {"IN2_R to Left Mixer Negative Resistor", "10 kOhm", "IN2_R"}, + {"IN2_R to Left Mixer Negative Resistor", "20 kOhm", "IN2_R"}, + {"IN2_R to Left Mixer Negative Resistor", "40 kOhm", "IN2_R"}, + + {"Left ADC", NULL, "IN3_R to Left Mixer Negative Resistor"}, + {"IN3_R to Left Mixer Negative Resistor", "10 kOhm", "IN3_R"}, + {"IN3_R to Left Mixer Negative Resistor", "20 kOhm", "IN3_R"}, + {"IN3_R to Left Mixer Negative Resistor", "40 kOhm", "IN3_R"}, }; static const struct regmap_range_cfg aic32x4_regmap_pages[] = { @@ -287,14 +427,12 @@ static const struct regmap_range_cfg aic32x4_regmap_pages[] = { }, }; -static const struct regmap_config aic32x4_regmap = { - .reg_bits = 8, - .val_bits = 8, - +const struct regmap_config aic32x4_regmap_config = { .max_register = AIC32X4_RMICPGAVOL, .ranges = aic32x4_regmap_pages, .num_ranges = ARRAY_SIZE(aic32x4_regmap_pages), }; +EXPORT_SYMBOL(aic32x4_regmap_config); static inline int aic32x4_get_divs(int mclk, int rate) { @@ -567,7 +705,7 @@ static int aic32x4_set_bias_level(struct snd_soc_codec *codec, return 0; } -#define AIC32X4_RATES SNDRV_PCM_RATE_8000_48000 +#define AIC32X4_RATES SNDRV_PCM_RATE_8000_96000 #define AIC32X4_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) @@ -596,7 +734,7 @@ static struct snd_soc_dai_driver aic32x4_dai = { .symmetric_rates = 1, }; -static int aic32x4_probe(struct snd_soc_codec *codec) +static int aic32x4_codec_probe(struct snd_soc_codec *codec) { struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); u32 tmp_reg; @@ -655,7 +793,7 @@ static int aic32x4_probe(struct snd_soc_codec *codec) } static struct snd_soc_codec_driver soc_codec_dev_aic32x4 = { - .probe = aic32x4_probe, + .probe = aic32x4_codec_probe, .set_bias_level = aic32x4_set_bias_level, .suspend_bias_off = true, @@ -777,24 +915,22 @@ error_ldo: return ret; } -static int aic32x4_i2c_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) +int aic32x4_probe(struct device *dev, struct regmap *regmap) { - struct aic32x4_pdata *pdata = i2c->dev.platform_data; struct aic32x4_priv *aic32x4; - struct device_node *np = i2c->dev.of_node; + struct aic32x4_pdata *pdata = dev->platform_data; + struct device_node *np = dev->of_node; int ret; - aic32x4 = devm_kzalloc(&i2c->dev, sizeof(struct aic32x4_priv), + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + aic32x4 = devm_kzalloc(dev, sizeof(struct aic32x4_priv), GFP_KERNEL); if (aic32x4 == NULL) return -ENOMEM; - aic32x4->regmap = devm_regmap_init_i2c(i2c, &aic32x4_regmap); - if (IS_ERR(aic32x4->regmap)) - return PTR_ERR(aic32x4->regmap); - - i2c_set_clientdata(i2c, aic32x4); + dev_set_drvdata(dev, aic32x4); if (pdata) { aic32x4->power_cfg = pdata->power_cfg; @@ -804,7 +940,7 @@ static int aic32x4_i2c_probe(struct i2c_client *i2c, } else if (np) { ret = aic32x4_parse_dt(aic32x4, np); if (ret) { - dev_err(&i2c->dev, "Failed to parse DT node\n"); + dev_err(dev, "Failed to parse DT node\n"); return ret; } } else { @@ -814,71 +950,48 @@ static int aic32x4_i2c_probe(struct i2c_client *i2c, aic32x4->rstn_gpio = -1; } - aic32x4->mclk = devm_clk_get(&i2c->dev, "mclk"); + aic32x4->mclk = devm_clk_get(dev, "mclk"); if (IS_ERR(aic32x4->mclk)) { - dev_err(&i2c->dev, "Failed getting the mclk. The current implementation does not support the usage of this codec without mclk\n"); + dev_err(dev, "Failed getting the mclk. The current implementation does not support the usage of this codec without mclk\n"); return PTR_ERR(aic32x4->mclk); } if (gpio_is_valid(aic32x4->rstn_gpio)) { - ret = devm_gpio_request_one(&i2c->dev, aic32x4->rstn_gpio, + ret = devm_gpio_request_one(dev, aic32x4->rstn_gpio, GPIOF_OUT_INIT_LOW, "tlv320aic32x4 rstn"); if (ret != 0) return ret; } - ret = aic32x4_setup_regulators(&i2c->dev, aic32x4); + ret = aic32x4_setup_regulators(dev, aic32x4); if (ret) { - dev_err(&i2c->dev, "Failed to setup regulators\n"); + dev_err(dev, "Failed to setup regulators\n"); return ret; } - ret = snd_soc_register_codec(&i2c->dev, + ret = snd_soc_register_codec(dev, &soc_codec_dev_aic32x4, &aic32x4_dai, 1); if (ret) { - dev_err(&i2c->dev, "Failed to register codec\n"); + dev_err(dev, "Failed to register codec\n"); aic32x4_disable_regulators(aic32x4); return ret; } - i2c_set_clientdata(i2c, aic32x4); - return 0; } +EXPORT_SYMBOL(aic32x4_probe); -static int aic32x4_i2c_remove(struct i2c_client *client) +int aic32x4_remove(struct device *dev) { - struct aic32x4_priv *aic32x4 = i2c_get_clientdata(client); + struct aic32x4_priv *aic32x4 = dev_get_drvdata(dev); aic32x4_disable_regulators(aic32x4); - snd_soc_unregister_codec(&client->dev); + snd_soc_unregister_codec(dev); + return 0; } - -static const struct i2c_device_id aic32x4_i2c_id[] = { - { "tlv320aic32x4", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, aic32x4_i2c_id); - -static const struct of_device_id aic32x4_of_id[] = { - { .compatible = "ti,tlv320aic32x4", }, - { /* senitel */ } -}; -MODULE_DEVICE_TABLE(of, aic32x4_of_id); - -static struct i2c_driver aic32x4_i2c_driver = { - .driver = { - .name = "tlv320aic32x4", - .of_match_table = aic32x4_of_id, - }, - .probe = aic32x4_i2c_probe, - .remove = aic32x4_i2c_remove, - .id_table = aic32x4_i2c_id, -}; - -module_i2c_driver(aic32x4_i2c_driver); +EXPORT_SYMBOL(aic32x4_remove); MODULE_DESCRIPTION("ASoC tlv320aic32x4 codec driver"); MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com>"); diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h index 995f033..a197dd5 100644 --- a/sound/soc/codecs/tlv320aic32x4.h +++ b/sound/soc/codecs/tlv320aic32x4.h @@ -10,6 +10,13 @@ #ifndef _TLV320AIC32X4_H #define _TLV320AIC32X4_H +struct device; +struct regmap_config; + +extern const struct regmap_config aic32x4_regmap_config; +int aic32x4_probe(struct device *dev, struct regmap *regmap); +int aic32x4_remove(struct device *dev); + /* tlv320aic32x4 register space (in decimal to match datasheet) */ #define AIC32X4_PAGE1 128 diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index bc3de2e..1f70810 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -824,7 +824,7 @@ static int twl6040_set_bias_level(struct snd_soc_codec *codec, { struct twl6040 *twl6040 = codec->control_data; struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); - int ret; + int ret = 0; switch (level) { case SND_SOC_BIAS_ON: @@ -832,12 +832,16 @@ static int twl6040_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_PREPARE: break; case SND_SOC_BIAS_STANDBY: - if (priv->codec_powered) + if (priv->codec_powered) { + /* Select low power PLL in standby */ + ret = twl6040_set_pll(twl6040, TWL6040_SYSCLK_SEL_LPPLL, + 32768, 19200000); break; + } ret = twl6040_power(twl6040, 1); if (ret) - return ret; + break; priv->codec_powered = 1; @@ -853,7 +857,7 @@ static int twl6040_set_bias_level(struct snd_soc_codec *codec, break; } - return 0; + return ret; } static int twl6040_startup(struct snd_pcm_substream *substream, @@ -983,9 +987,9 @@ static void twl6040_mute_path(struct snd_soc_codec *codec, enum twl6040_dai_id i if (mute) { /* Power down drivers and DACs */ hflctl &= ~(TWL6040_HFDACENA | TWL6040_HFPGAENA | - TWL6040_HFDRVENA); + TWL6040_HFDRVENA | TWL6040_HFSWENA); hfrctl &= ~(TWL6040_HFDACENA | TWL6040_HFPGAENA | - TWL6040_HFDRVENA); + TWL6040_HFDRVENA | TWL6040_HFSWENA); } twl6040_reg_write(twl6040, TWL6040_REG_HFLCTL, hflctl); diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index fc164d6..f3109da 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -3793,9 +3793,8 @@ static int wm8962_runtime_resume(struct device *dev) ret = regulator_bulk_enable(ARRAY_SIZE(wm8962->supplies), wm8962->supplies); if (ret != 0) { - dev_err(dev, - "Failed to enable supplies: %d\n", ret); - return ret; + dev_err(dev, "Failed to enable supplies: %d\n", ret); + goto disable_clock; } regcache_cache_only(wm8962->regmap, false); @@ -3833,6 +3832,10 @@ static int wm8962_runtime_resume(struct device *dev) msleep(5); return 0; + +disable_clock: + clk_disable_unprepare(wm8962->pdata.mclk); + return ret; } static int wm8962_runtime_suspend(struct device *dev) diff --git a/sound/soc/codecs/wm8962.h b/sound/soc/codecs/wm8962.h index 910aafd..e63a318 100644 --- a/sound/soc/codecs/wm8962.h +++ b/sound/soc/codecs/wm8962.h @@ -16,9 +16,9 @@ #include <asm/types.h> #include <sound/soc.h> -#define WM8962_SYSCLK_MCLK 1 -#define WM8962_SYSCLK_FLL 2 -#define WM8962_SYSCLK_PLL3 3 +#define WM8962_SYSCLK_MCLK 0 +#define WM8962_SYSCLK_FLL 1 +#define WM8962_SYSCLK_PLL3 2 #define WM8962_FLL 1 diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index 2389ab4..466492b 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -643,6 +643,7 @@ MODULE_DEVICE_TABLE(of, asoc_simple_of_match); static struct platform_driver asoc_simple_card = { .driver = { .name = "asoc-simple-card", + .pm = &snd_soc_pm_ops, .of_match_table = asoc_simple_of_match, }, .probe = asoc_simple_card_probe, diff --git a/sound/soc/kirkwood/Kconfig b/sound/soc/kirkwood/Kconfig index 132bb83..bc3c7b5 100644 --- a/sound/soc/kirkwood/Kconfig +++ b/sound/soc/kirkwood/Kconfig @@ -1,6 +1,7 @@ config SND_KIRKWOOD_SOC tristate "SoC Audio for the Marvell Kirkwood and Dove chips" depends on ARCH_DOVE || ARCH_MVEBU || COMPILE_TEST + depends on HAS_DMA help Say Y or M if you want to add support for codecs attached to the Kirkwood I2S interface. You will also need to select the diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index f7e789e..3abf51c 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -43,6 +43,7 @@ config SND_SOC_MT8173_RT5650_RT5676 depends on SND_SOC_MEDIATEK && I2C select SND_SOC_RT5645 select SND_SOC_RT5677 + select SND_SOC_HDMI_CODEC help This adds ASoC driver for Mediatek MT8173 boards with the RT5650 and RT5676 codecs. diff --git a/sound/soc/mediatek/mt8173-rt5650-rt5676.c b/sound/soc/mediatek/mt8173-rt5650-rt5676.c index 5c4c58c..bb59392 100644 --- a/sound/soc/mediatek/mt8173-rt5650-rt5676.c +++ b/sound/soc/mediatek/mt8173-rt5650-rt5676.c @@ -134,7 +134,9 @@ static struct snd_soc_dai_link_component mt8173_rt5650_rt5676_codecs[] = { enum { DAI_LINK_PLAYBACK, DAI_LINK_CAPTURE, + DAI_LINK_HDMI, DAI_LINK_CODEC_I2S, + DAI_LINK_HDMI_I2S, DAI_LINK_INTERCODEC }; @@ -161,6 +163,16 @@ static struct snd_soc_dai_link mt8173_rt5650_rt5676_dais[] = { .dynamic = 1, .dpcm_capture = 1, }, + [DAI_LINK_HDMI] = { + .name = "HDMI", + .stream_name = "HDMI PCM", + .cpu_dai_name = "HDMI", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, /* Back End DAI links */ [DAI_LINK_CODEC_I2S] = { @@ -177,6 +189,13 @@ static struct snd_soc_dai_link mt8173_rt5650_rt5676_dais[] = { .dpcm_playback = 1, .dpcm_capture = 1, }, + [DAI_LINK_HDMI_I2S] = { + .name = "HDMI BE", + .cpu_dai_name = "HDMIO", + .no_pcm = 1, + .codec_dai_name = "i2s-hifi", + .dpcm_playback = 1, + }, /* rt5676 <-> rt5650 intercodec link: Sets rt5676 I2S2 as master */ [DAI_LINK_INTERCODEC] = { .name = "rt5650_rt5676 intercodec", @@ -251,6 +270,14 @@ static int mt8173_rt5650_rt5676_dev_probe(struct platform_device *pdev) mt8173_rt5650_rt5676_dais[DAI_LINK_INTERCODEC].codec_of_node = mt8173_rt5650_rt5676_codecs[1].of_node; + mt8173_rt5650_rt5676_dais[DAI_LINK_HDMI_I2S].codec_of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 2); + if (!mt8173_rt5650_rt5676_dais[DAI_LINK_HDMI_I2S].codec_of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/mediatek/mt8173-rt5650.c b/sound/soc/mediatek/mt8173-rt5650.c index bb09bb1..a27a667 100644 --- a/sound/soc/mediatek/mt8173-rt5650.c +++ b/sound/soc/mediatek/mt8173-rt5650.c @@ -85,12 +85,29 @@ static int mt8173_rt5650_init(struct snd_soc_pcm_runtime *runtime) { struct snd_soc_card *card = runtime->card; struct snd_soc_codec *codec = runtime->codec_dais[0]->codec; + const char *codec_capture_dai = runtime->codec_dais[1]->name; int ret; rt5645_sel_asrc_clk_src(codec, - RT5645_DA_STEREO_FILTER | - RT5645_AD_STEREO_FILTER, + RT5645_DA_STEREO_FILTER, RT5645_CLK_SEL_I2S1_ASRC); + + if (!strcmp(codec_capture_dai, "rt5645-aif1")) { + rt5645_sel_asrc_clk_src(codec, + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + } else if (!strcmp(codec_capture_dai, "rt5645-aif2")) { + rt5645_sel_asrc_clk_src(codec, + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S2_ASRC); + } else { + dev_warn(card->dev, + "Only one dai codec found in DTS, enabled rt5645 AD filter\n"); + rt5645_sel_asrc_clk_src(codec, + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + } + /* enable jack detection */ ret = snd_soc_card_jack_new(card, "Headset Jack", SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | @@ -110,6 +127,11 @@ static int mt8173_rt5650_init(struct snd_soc_pcm_runtime *runtime) static struct snd_soc_dai_link_component mt8173_rt5650_codecs[] = { { + /* Playback */ + .dai_name = "rt5645-aif1", + }, + { + /* Capture */ .dai_name = "rt5645-aif1", }, }; @@ -149,7 +171,7 @@ static struct snd_soc_dai_link mt8173_rt5650_dais[] = { .cpu_dai_name = "I2S", .no_pcm = 1, .codecs = mt8173_rt5650_codecs, - .num_codecs = 1, + .num_codecs = 2, .init = mt8173_rt5650_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS, @@ -177,6 +199,8 @@ static int mt8173_rt5650_dev_probe(struct platform_device *pdev) { struct snd_soc_card *card = &mt8173_rt5650_card; struct device_node *platform_node; + struct device_node *np; + const char *codec_capture_dai; int i, ret; platform_node = of_parse_phandle(pdev->dev.of_node, @@ -199,6 +223,26 @@ static int mt8173_rt5650_dev_probe(struct platform_device *pdev) "Property 'audio-codec' missing or invalid\n"); return -EINVAL; } + mt8173_rt5650_codecs[1].of_node = mt8173_rt5650_codecs[0].of_node; + + if (of_find_node_by_name(platform_node, "codec-capture")) { + np = of_get_child_by_name(pdev->dev.of_node, "codec-capture"); + if (!np) { + dev_err(&pdev->dev, + "%s: Can't find codec-capture DT node\n", + __func__); + return -EINVAL; + } + ret = snd_soc_of_get_dai_name(np, &codec_capture_dai); + if (ret < 0) { + dev_err(&pdev->dev, + "%s codec_capture_dai name fail %d\n", + __func__, ret); + return ret; + } + mt8173_rt5650_codecs[1].dai_name = codec_capture_dai; + } + card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/mediatek/mtk-afe-pcm.c b/sound/soc/mediatek/mtk-afe-pcm.c index f1c58a2..2b5df2e 100644 --- a/sound/soc/mediatek/mtk-afe-pcm.c +++ b/sound/soc/mediatek/mtk-afe-pcm.c @@ -123,6 +123,7 @@ #define AFE_TDM_CON1_WLEN_32BIT (0x2 << 8) #define AFE_TDM_CON1_MSB_ALIGNED (0x1 << 4) #define AFE_TDM_CON1_1_BCK_DELAY (0x1 << 3) +#define AFE_TDM_CON1_LRCK_INV (0x1 << 2) #define AFE_TDM_CON1_BCK_INV (0x1 << 1) #define AFE_TDM_CON1_EN (0x1 << 0) @@ -449,6 +450,7 @@ static int mtk_afe_hdmi_prepare(struct snd_pcm_substream *substream, runtime->rate * runtime->channels * 32); val = AFE_TDM_CON1_BCK_INV | + AFE_TDM_CON1_LRCK_INV | AFE_TDM_CON1_1_BCK_DELAY | AFE_TDM_CON1_MSB_ALIGNED | /* I2S mode */ AFE_TDM_CON1_WLEN_32BIT | diff --git a/sound/soc/omap/mcbsp.c b/sound/soc/omap/mcbsp.c index c7563e2..4a16e77 100644 --- a/sound/soc/omap/mcbsp.c +++ b/sound/soc/omap/mcbsp.c @@ -260,6 +260,10 @@ static void omap_st_on(struct omap_mcbsp *mcbsp) if (mcbsp->pdata->enable_st_clock) mcbsp->pdata->enable_st_clock(mcbsp->id, 1); + /* Disable Sidetone clock auto-gating for normal operation */ + w = MCBSP_ST_READ(mcbsp, SYSCONFIG); + MCBSP_ST_WRITE(mcbsp, SYSCONFIG, w & ~(ST_AUTOIDLE)); + /* Enable McBSP Sidetone */ w = MCBSP_READ(mcbsp, SSELCR); MCBSP_WRITE(mcbsp, SSELCR, w | SIDETONEEN); @@ -279,6 +283,10 @@ static void omap_st_off(struct omap_mcbsp *mcbsp) w = MCBSP_READ(mcbsp, SSELCR); MCBSP_WRITE(mcbsp, SSELCR, w & ~(SIDETONEEN)); + /* Enable Sidetone clock auto-gating to reduce power consumption */ + w = MCBSP_ST_READ(mcbsp, SYSCONFIG); + MCBSP_ST_WRITE(mcbsp, SYSCONFIG, w | ST_AUTOIDLE); + if (mcbsp->pdata->enable_st_clock) mcbsp->pdata->enable_st_clock(mcbsp->id, 0); } diff --git a/sound/soc/omap/omap-pcm.c b/sound/soc/omap/omap-pcm.c index 99381a2..a84f677 100644 --- a/sound/soc/omap/omap-pcm.c +++ b/sound/soc/omap/omap-pcm.c @@ -82,6 +82,8 @@ static int omap_pcm_hw_params(struct snd_pcm_substream *substream, struct dma_chan *chan; int err = 0; + memset(&config, 0x00, sizeof(config)); + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); /* return if this is a bufferless transfer e.g. diff --git a/sound/soc/pxa/brownstone.c b/sound/soc/pxa/brownstone.c index ec522e9..b6cb995 100644 --- a/sound/soc/pxa/brownstone.c +++ b/sound/soc/pxa/brownstone.c @@ -133,3 +133,4 @@ module_platform_driver(mmp_driver); MODULE_AUTHOR("Leo Yan <leoy@marvell.com>"); MODULE_DESCRIPTION("ALSA SoC Brownstone"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:brownstone-audio"); diff --git a/sound/soc/pxa/mioa701_wm9713.c b/sound/soc/pxa/mioa701_wm9713.c index 5c8f9db..d1661fa 100644 --- a/sound/soc/pxa/mioa701_wm9713.c +++ b/sound/soc/pxa/mioa701_wm9713.c @@ -207,3 +207,4 @@ module_platform_driver(mioa701_wm9713_driver); MODULE_AUTHOR("Robert Jarzmik (rjarzmik@free.fr)"); MODULE_DESCRIPTION("ALSA SoC WM9713 MIO A701"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mioa701-wm9713"); diff --git a/sound/soc/pxa/mmp-pcm.c b/sound/soc/pxa/mmp-pcm.c index 51e790d..96df9b2 100644 --- a/sound/soc/pxa/mmp-pcm.c +++ b/sound/soc/pxa/mmp-pcm.c @@ -248,3 +248,4 @@ module_platform_driver(mmp_pcm_driver); MODULE_AUTHOR("Leo Yan <leoy@marvell.com>"); MODULE_DESCRIPTION("MMP Soc Audio DMA module"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mmp-pcm-audio"); diff --git a/sound/soc/pxa/mmp-sspa.c b/sound/soc/pxa/mmp-sspa.c index eca60c2..ca8b23f 100644 --- a/sound/soc/pxa/mmp-sspa.c +++ b/sound/soc/pxa/mmp-sspa.c @@ -482,3 +482,4 @@ module_platform_driver(asoc_mmp_sspa_driver); MODULE_AUTHOR("Leo Yan <leoy@marvell.com>"); MODULE_DESCRIPTION("MMP SSPA SoC Interface"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mmp-sspa-dai"); diff --git a/sound/soc/pxa/palm27x.c b/sound/soc/pxa/palm27x.c index 4e74d95..bcc81e9 100644 --- a/sound/soc/pxa/palm27x.c +++ b/sound/soc/pxa/palm27x.c @@ -161,3 +161,4 @@ module_platform_driver(palm27x_wm9712_driver); MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); MODULE_DESCRIPTION("ALSA SoC Palm T|X, T5 and LifeDrive"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:palm27x-asoc"); diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index da03fad..3cad990 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -833,3 +833,4 @@ module_platform_driver(asoc_ssp_driver); MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); MODULE_DESCRIPTION("PXA SSP/PCM SoC Interface"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pxa-ssp-dai"); diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c index f3de615..9615e6d 100644 --- a/sound/soc/pxa/pxa2xx-ac97.c +++ b/sound/soc/pxa/pxa2xx-ac97.c @@ -287,3 +287,4 @@ module_platform_driver(pxa2xx_ac97_driver); MODULE_AUTHOR("Nicolas Pitre"); MODULE_DESCRIPTION("AC97 driver for the Intel PXA2xx chip"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pxa2xx-ac97"); diff --git a/sound/soc/pxa/pxa2xx-pcm.c b/sound/soc/pxa/pxa2xx-pcm.c index 9f39039..410d48b 100644 --- a/sound/soc/pxa/pxa2xx-pcm.c +++ b/sound/soc/pxa/pxa2xx-pcm.c @@ -117,3 +117,4 @@ module_platform_driver(pxa_pcm_driver); MODULE_AUTHOR("Nicolas Pitre"); MODULE_DESCRIPTION("Intel PXA2xx PCM DMA module"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pxa-pcm-audio"); diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c index ddfe344..db000c6 100644 --- a/sound/soc/qcom/lpass-platform.c +++ b/sound/soc/qcom/lpass-platform.c @@ -474,7 +474,7 @@ static int lpass_platform_pcm_new(struct snd_soc_pcm_runtime *soc_runtime) struct lpass_data *drvdata = snd_soc_platform_get_drvdata(soc_runtime->platform); struct lpass_variant *v = drvdata->variant; - int ret; + int ret = -EINVAL; struct lpass_pcm_data *data; size_t size = lpass_platform_pcm_hardware.buffer_bytes_max; @@ -518,8 +518,10 @@ static int lpass_platform_pcm_new(struct snd_soc_pcm_runtime *soc_runtime) data->wrdma_ch = v->alloc_dma_channel(drvdata, SNDRV_PCM_STREAM_CAPTURE); - if (data->wrdma_ch < 0) + if (data->wrdma_ch < 0) { + ret = data->wrdma_ch; goto capture_alloc_err; + } drvdata->substream[data->wrdma_ch] = csubstream; diff --git a/sound/soc/sh/rcar/adg.c b/sound/soc/sh/rcar/adg.c index 606399d..49354d1 100644 --- a/sound/soc/sh/rcar/adg.c +++ b/sound/soc/sh/rcar/adg.c @@ -492,9 +492,7 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv, */ if (!count) { clk = clk_register_fixed_rate(dev, clkout_name[CLKOUT], - parent_clk_name, - (parent_clk_name) ? - 0 : CLK_IS_ROOT, req_rate); + parent_clk_name, 0, req_rate); if (!IS_ERR(clk)) { adg->clkout[CLKOUT] = clk; of_clk_add_provider(np, of_clk_src_simple_get, clk); @@ -506,9 +504,7 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv, else { for (i = 0; i < CLKOUTMAX; i++) { clk = clk_register_fixed_rate(dev, clkout_name[i], - parent_clk_name, - (parent_clk_name) ? - 0 : CLK_IS_ROOT, + parent_clk_name, 0, req_rate); if (!IS_ERR(clk)) { adg->onecell.clks = adg->clkout; diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c index 7658e8fd7..6bc93cb 100644 --- a/sound/soc/sh/rcar/dma.c +++ b/sound/soc/sh/rcar/dma.c @@ -316,11 +316,15 @@ static u32 rsnd_dmapp_get_id(struct rsnd_dai_stream *io, size = ARRAY_SIZE(gen2_id_table_cmd); } - if (!entry) - return 0xFF; + if ((!entry) || (size <= id)) { + struct device *dev = rsnd_priv_to_dev(rsnd_io_to_priv(io)); - if (size <= id) - return 0xFF; + dev_err(dev, "unknown connection (%s[%d])\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); + + /* use non-prohibited SRS number as error */ + return 0x00; /* SSI00 */ + } return entry[id]; } diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index fc89a67..a8f61d7 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -276,8 +276,9 @@ struct rsnd_mod { /* * status * - * 0xH0000CB0 + * 0xH0000CBA * + * A 0: probe 1: remove * B 0: init 1: quit * C 0: start 1: stop * @@ -287,19 +288,19 @@ struct rsnd_mod { * H 0: fallback * H 0: hw_params */ +#define __rsnd_mod_shift_probe 0 +#define __rsnd_mod_shift_remove 0 #define __rsnd_mod_shift_init 4 #define __rsnd_mod_shift_quit 4 #define __rsnd_mod_shift_start 8 #define __rsnd_mod_shift_stop 8 -#define __rsnd_mod_shift_probe 28 /* always called */ -#define __rsnd_mod_shift_remove 28 /* always called */ #define __rsnd_mod_shift_irq 28 /* always called */ #define __rsnd_mod_shift_pcm_new 28 /* always called */ #define __rsnd_mod_shift_fallback 28 /* always called */ #define __rsnd_mod_shift_hw_params 28 /* always called */ -#define __rsnd_mod_add_probe 0 -#define __rsnd_mod_add_remove 0 +#define __rsnd_mod_add_probe 1 +#define __rsnd_mod_add_remove -1 #define __rsnd_mod_add_init 1 #define __rsnd_mod_add_quit -1 #define __rsnd_mod_add_start 1 @@ -310,7 +311,7 @@ struct rsnd_mod { #define __rsnd_mod_add_hw_params 0 #define __rsnd_mod_call_probe 0 -#define __rsnd_mod_call_remove 0 +#define __rsnd_mod_call_remove 1 #define __rsnd_mod_call_init 0 #define __rsnd_mod_call_quit 1 #define __rsnd_mod_call_start 0 diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index 15d6ffe..e39f916 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -572,6 +572,9 @@ int rsnd_src_probe(struct rsnd_priv *priv) i = 0; for_each_child_of_node(node, np) { + if (!of_device_is_available(np)) + goto skip; + src = rsnd_src_get(priv, i); snprintf(name, RSND_SRC_NAME_SIZE, "%s.%d", @@ -595,6 +598,7 @@ int rsnd_src_probe(struct rsnd_priv *priv) if (ret) goto rsnd_src_probe_done; +skip: i++; } diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 1cf94d7..ee7f15a 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -1023,6 +1023,11 @@ static int soc_tplg_kcontrol_elems_load(struct soc_tplg *tplg, control_hdr = (struct snd_soc_tplg_ctl_hdr *)tplg->pos; + if (control_hdr->size != sizeof(*control_hdr)) { + dev_err(tplg->dev, "ASoC: invalid control size\n"); + return -EINVAL; + } + switch (control_hdr->ops.info) { case SND_SOC_TPLG_CTL_VOLSW: case SND_SOC_TPLG_CTL_STROBE: @@ -1476,6 +1481,8 @@ widget: widget->dobj.type = SND_SOC_DOBJ_WIDGET; widget->dobj.ops = tplg->ops; widget->dobj.index = tplg->index; + kfree(template.sname); + kfree(template.name); list_add(&widget->dobj.list, &tplg->comp->dobj_list); return 0; @@ -1499,10 +1506,17 @@ static int soc_tplg_dapm_widget_elems_load(struct soc_tplg *tplg, for (i = 0; i < count; i++) { widget = (struct snd_soc_tplg_dapm_widget *) tplg->pos; + if (widget->size != sizeof(*widget)) { + dev_err(tplg->dev, "ASoC: invalid widget size\n"); + return -EINVAL; + } + ret = soc_tplg_dapm_widget_create(tplg, widget); - if (ret < 0) + if (ret < 0) { dev_err(tplg->dev, "ASoC: failed to load widget %s\n", widget->name); + return ret; + } } return 0; @@ -1586,6 +1600,7 @@ static int soc_tplg_dai_create(struct soc_tplg *tplg, return snd_soc_register_dai(tplg->comp, dai_drv); } +/* create the FE DAI link */ static int soc_tplg_link_create(struct soc_tplg *tplg, struct snd_soc_tplg_pcm *pcm) { @@ -1598,6 +1613,16 @@ static int soc_tplg_link_create(struct soc_tplg *tplg, link->name = pcm->pcm_name; link->stream_name = pcm->pcm_name; + link->id = pcm->pcm_id; + + link->cpu_dai_name = pcm->dai_name; + link->codec_name = "snd-soc-dummy"; + link->codec_dai_name = "snd-soc-dummy-dai"; + + /* enable DPCM */ + link->dynamic = 1; + link->dpcm_playback = pcm->playback; + link->dpcm_capture = pcm->capture; /* pass control to component driver for optional further init */ ret = soc_tplg_dai_link_load(tplg, link); @@ -1639,8 +1664,6 @@ static int soc_tplg_pcm_elems_load(struct soc_tplg *tplg, if (tplg->pass != SOC_TPLG_PASS_PCM_DAI) return 0; - pcm = (struct snd_soc_tplg_pcm *)tplg->pos; - if (soc_tplg_check_elem_count(tplg, sizeof(struct snd_soc_tplg_pcm), count, hdr->payload_size, "PCM DAI")) { @@ -1650,7 +1673,13 @@ static int soc_tplg_pcm_elems_load(struct soc_tplg *tplg, } /* 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; + } + soc_tplg_pcm_create(tplg, pcm); pcm++; } @@ -1670,6 +1699,11 @@ static int soc_tplg_manifest_load(struct soc_tplg *tplg, 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); if (tplg->comp && tplg->ops && tplg->ops->manifest) @@ -1686,6 +1720,14 @@ static int soc_valid_header(struct soc_tplg *tplg, if (soc_tplg_get_hdr_offset(tplg) >= tplg->fw->size) return 0; + if (hdr->size != sizeof(*hdr)) { + dev_err(tplg->dev, + "ASoC: invalid header size for type %d at offset 0x%lx size 0x%zx.\n", + hdr->type, soc_tplg_get_hdr_offset(tplg), + tplg->fw->size); + return -EINVAL; + } + /* big endian firmware objects not supported atm */ if (hdr->magic == cpu_to_be32(SND_SOC_TPLG_MAGIC)) { dev_err(tplg->dev, diff --git a/sound/soc/sti/sti_uniperif.c b/sound/soc/sti/sti_uniperif.c index 39bcefe..488ef4e 100644 --- a/sound/soc/sti/sti_uniperif.c +++ b/sound/soc/sti/sti_uniperif.c @@ -11,6 +11,142 @@ #include "uniperif.h" /* + * User frame size shall be 2, 4, 6 or 8 32-bits words length + * (i.e. 8, 16, 24 or 32 bytes) + * This constraint comes from allowed values for + * UNIPERIF_I2S_FMT_NUM_CH register + */ +#define UNIPERIF_MAX_FRAME_SZ 0x20 +#define UNIPERIF_ALLOWED_FRAME_SZ (0x08 | 0x10 | 0x18 | UNIPERIF_MAX_FRAME_SZ) + +int sti_uniperiph_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, + int slot_width) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *uni = priv->dai_data.uni; + int i, frame_size, avail_slots; + + if (!UNIPERIF_TYPE_IS_TDM(uni)) { + dev_err(uni->dev, "cpu dai not in tdm mode\n"); + return -EINVAL; + } + + /* store info in unip context */ + uni->tdm_slot.slots = slots; + uni->tdm_slot.slot_width = slot_width; + /* unip is unidirectionnal */ + uni->tdm_slot.mask = (tx_mask != 0) ? tx_mask : rx_mask; + + /* number of available timeslots */ + for (i = 0, avail_slots = 0; i < uni->tdm_slot.slots; i++) { + if ((uni->tdm_slot.mask >> i) & 0x01) + avail_slots++; + } + uni->tdm_slot.avail_slots = avail_slots; + + /* frame size in bytes */ + frame_size = uni->tdm_slot.avail_slots * uni->tdm_slot.slot_width / 8; + + /* check frame size is allowed */ + if ((frame_size > UNIPERIF_MAX_FRAME_SZ) || + (frame_size & ~(int)UNIPERIF_ALLOWED_FRAME_SZ)) { + dev_err(uni->dev, "frame size not allowed: %d bytes\n", + frame_size); + return -EINVAL; + } + + return 0; +} + +int sti_uniperiph_fix_tdm_chan(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct uniperif *uni = rule->private; + struct snd_interval t; + + t.min = uni->tdm_slot.avail_slots; + t.max = uni->tdm_slot.avail_slots; + t.openmin = 0; + t.openmax = 0; + t.integer = 0; + + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +int sti_uniperiph_fix_tdm_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct uniperif *uni = rule->private; + struct snd_mask *maskp = hw_param_mask(params, rule->var); + u64 format; + + switch (uni->tdm_slot.slot_width) { + case 16: + format = SNDRV_PCM_FMTBIT_S16_LE; + break; + case 32: + format = SNDRV_PCM_FMTBIT_S32_LE; + break; + default: + dev_err(uni->dev, "format not supported: %d bits\n", + uni->tdm_slot.slot_width); + return -EINVAL; + } + + maskp->bits[0] &= (u_int32_t)format; + maskp->bits[1] &= (u_int32_t)(format >> 32); + /* clear remaining indexes */ + memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX - 64) / 8); + + if (!maskp->bits[0] && !maskp->bits[1]) + return -EINVAL; + + return 0; +} + +int sti_uniperiph_get_tdm_word_pos(struct uniperif *uni, + unsigned int *word_pos) +{ + int slot_width = uni->tdm_slot.slot_width / 8; + int slots_num = uni->tdm_slot.slots; + unsigned int slots_mask = uni->tdm_slot.mask; + int i, j, k; + unsigned int word16_pos[4]; + + /* word16_pos: + * word16_pos[0] = WORDX_LSB + * word16_pos[1] = WORDX_MSB, + * word16_pos[2] = WORDX+1_LSB + * word16_pos[3] = WORDX+1_MSB + */ + + /* set unip word position */ + for (i = 0, j = 0, k = 0; (i < slots_num) && (k < WORD_MAX); i++) { + if ((slots_mask >> i) & 0x01) { + word16_pos[j] = i * slot_width; + + if (slot_width == 4) { + word16_pos[j + 1] = word16_pos[j] + 2; + j++; + } + j++; + + if (j > 3) { + word_pos[k] = word16_pos[1] | + (word16_pos[0] << 8) | + (word16_pos[3] << 16) | + (word16_pos[2] << 24); + j = 0; + k++; + } + } + } + + return 0; +} + +/* * sti_uniperiph_dai_create_ctrl * This function is used to create Ctrl associated to DAI but also pcm device. * Request is done by front end to associate ctrl with pcm device id @@ -45,10 +181,16 @@ int sti_uniperiph_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *uni = priv->dai_data.uni; struct snd_dmaengine_dai_dma_data *dma_data; int transfer_size; - transfer_size = params_channels(params) * UNIPERIF_FIFO_FRAMES; + if (uni->info->type == SND_ST_UNIPERIF_TYPE_TDM) + /* transfer size = user frame size (in 32-bits FIFO cell) */ + transfer_size = snd_soc_params_to_frame_size(params) / 32; + else + transfer_size = params_channels(params) * UNIPERIF_FIFO_FRAMES; dma_data = snd_soc_dai_get_dma_data(dai, substream); dma_data->maxburst = transfer_size; diff --git a/sound/soc/sti/uniperif.h b/sound/soc/sti/uniperif.h index f0fd5a9..eb9933c 100644 --- a/sound/soc/sti/uniperif.h +++ b/sound/soc/sti/uniperif.h @@ -25,7 +25,7 @@ writel_relaxed((((value) & mask) << shift), ip->base + offset) /* - * AUD_UNIPERIF_SOFT_RST reg + * UNIPERIF_SOFT_RST reg */ #define UNIPERIF_SOFT_RST_OFFSET(ip) 0x0000 @@ -50,7 +50,7 @@ UNIPERIF_SOFT_RST_SOFT_RST_MASK(ip)) /* - * AUD_UNIPERIF_FIFO_DATA reg + * UNIPERIF_FIFO_DATA reg */ #define UNIPERIF_FIFO_DATA_OFFSET(ip) 0x0004 @@ -58,7 +58,7 @@ writel_relaxed(value, ip->base + UNIPERIF_FIFO_DATA_OFFSET(ip)) /* - * AUD_UNIPERIF_CHANNEL_STA_REGN reg + * UNIPERIF_CHANNEL_STA_REGN reg */ #define UNIPERIF_CHANNEL_STA_REGN(ip, n) (0x0060 + (4 * n)) @@ -105,7 +105,7 @@ writel_relaxed(value, ip->base + UNIPERIF_CHANNEL_STA_REG5_OFFSET(ip)) /* - * AUD_UNIPERIF_ITS reg + * UNIPERIF_ITS reg */ #define UNIPERIF_ITS_OFFSET(ip) 0x000C @@ -143,7 +143,7 @@ 0 : (BIT(UNIPERIF_ITS_UNDERFLOW_REC_FAILED_SHIFT(ip)))) /* - * AUD_UNIPERIF_ITS_BCLR reg + * UNIPERIF_ITS_BCLR reg */ /* FIFO_ERROR */ @@ -160,7 +160,7 @@ writel_relaxed(value, ip->base + UNIPERIF_ITS_BCLR_OFFSET(ip)) /* - * AUD_UNIPERIF_ITM reg + * UNIPERIF_ITM reg */ #define UNIPERIF_ITM_OFFSET(ip) 0x0018 @@ -188,7 +188,7 @@ 0 : (BIT(UNIPERIF_ITM_UNDERFLOW_REC_FAILED_SHIFT(ip)))) /* - * AUD_UNIPERIF_ITM_BCLR reg + * UNIPERIF_ITM_BCLR reg */ #define UNIPERIF_ITM_BCLR_OFFSET(ip) 0x001c @@ -213,7 +213,7 @@ UNIPERIF_ITM_BCLR_DMA_ERROR_MASK(ip)) /* - * AUD_UNIPERIF_ITM_BSET reg + * UNIPERIF_ITM_BSET reg */ #define UNIPERIF_ITM_BSET_OFFSET(ip) 0x0020 @@ -767,7 +767,7 @@ SET_UNIPERIF_REG(ip, \ UNIPERIF_CTRL_OFFSET(ip), \ UNIPERIF_CTRL_READER_OUT_SEL_SHIFT(ip), \ - CORAUD_UNIPERIF_CTRL_READER_OUT_SEL_MASK(ip), 1) + UNIPERIF_CTRL_READER_OUT_SEL_MASK(ip), 1) /* UNDERFLOW_REC_WINDOW */ #define UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW_SHIFT(ip) 20 @@ -1046,7 +1046,7 @@ UNIPERIF_STATUS_1_UNDERFLOW_DURATION_MASK(ip), value) /* - * AUD_UNIPERIF_CHANNEL_STA_REGN reg + * UNIPERIF_CHANNEL_STA_REGN reg */ #define UNIPERIF_CHANNEL_STA_REGN(ip, n) (0x0060 + (4 * n)) @@ -1057,7 +1057,7 @@ UNIPERIF_CHANNEL_STA_REGN(ip, n)) /* - * AUD_UNIPERIF_USER_VALIDITY reg + * UNIPERIF_USER_VALIDITY reg */ #define UNIPERIF_USER_VALIDITY_OFFSET(ip) 0x0090 @@ -1101,12 +1101,136 @@ UNIPERIF_DBG_STANDBY_LEFT_SP_MASK(ip), value) /* + * UNIPERIF_TDM_ENABLE + */ +#define UNIPERIF_TDM_ENABLE_OFFSET(ip) 0x0118 +#define GET_UNIPERIF_TDM_ENABLE(ip) \ + readl_relaxed(ip->base + UNIPERIF_TDM_ENABLE_OFFSET(ip)) +#define SET_UNIPERIF_TDM_ENABLE(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_TDM_ENABLE_OFFSET(ip)) + +/* TDM_ENABLE */ +#define UNIPERIF_TDM_ENABLE_EN_TDM_SHIFT(ip) 0x0 +#define UNIPERIF_TDM_ENABLE_EN_TDM_MASK(ip) 0x1 +#define GET_UNIPERIF_TDM_ENABLE_EN_TDM(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_ENABLE_OFFSET(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_SHIFT(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_MASK(ip)) +#define SET_UNIPERIF_TDM_ENABLE_TDM_ENABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_ENABLE_OFFSET(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_SHIFT(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_MASK(ip), 1) +#define SET_UNIPERIF_TDM_ENABLE_TDM_DISABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_ENABLE_OFFSET(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_SHIFT(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_MASK(ip), 0) + +/* + * UNIPERIF_TDM_FS_REF_FREQ + */ +#define UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip) 0x011c +#define GET_UNIPERIF_TDM_FS_REF_FREQ(ip) \ + readl_relaxed(ip->base + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip)) +#define SET_UNIPERIF_TDM_FS_REF_FREQ(ip, value) \ + writel_relaxed(value, ip->base + \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip)) + +/* REF_FREQ */ +#define UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip) 0x0 +#define VALUE_UNIPERIF_TDM_FS_REF_FREQ_8KHZ(ip) 0 +#define VALUE_UNIPERIF_TDM_FS_REF_FREQ_16KHZ(ip) 1 +#define VALUE_UNIPERIF_TDM_FS_REF_FREQ_32KHZ(ip) 2 +#define VALUE_UNIPERIF_TDM_FS_REF_FREQ_48KHZ(ip) 3 +#define UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip) 0x3 +#define GET_UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip)) +#define SET_UNIPERIF_TDM_FS_REF_FREQ_8KHZ(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip), \ + VALUE_UNIPERIF_TDM_FS_REF_FREQ_8KHZ(ip)) +#define SET_UNIPERIF_TDM_FS_REF_FREQ_16KHZ(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip), \ + VALUE_UNIPERIF_TDM_FS_REF_FREQ_16KHZ(ip)) +#define SET_UNIPERIF_TDM_FS_REF_FREQ_32KHZ(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip), \ + VALUE_UNIPERIF_TDM_FS_REF_FREQ_32KHZ(ip)) +#define SET_UNIPERIF_TDM_FS_REF_FREQ_48KHZ(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip), \ + VALUE_UNIPERIF_TDM_FS_REF_FREQ_48KHZ(ip)) + +/* + * UNIPERIF_TDM_FS_REF_DIV + */ +#define UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip) 0x0120 +#define GET_UNIPERIF_TDM_FS_REF_DIV(ip) \ + readl_relaxed(ip->base + UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip)) +#define SET_UNIPERIF_TDM_FS_REF_DIV(ip, value) \ + writel_relaxed(value, ip->base + \ + UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip)) + +/* NUM_TIMESLOT */ +#define UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_SHIFT(ip) 0x0 +#define UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_MASK(ip) 0xff +#define GET_UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_MASK(ip)) +#define SET_UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_MASK(ip), value) + +/* + * UNIPERIF_TDM_WORD_POS_X_Y + * 32 bits of UNIPERIF_TDM_WORD_POS_X_Y register shall be set in 1 shot + */ +#define UNIPERIF_TDM_WORD_POS_1_2_OFFSET(ip) 0x013c +#define UNIPERIF_TDM_WORD_POS_3_4_OFFSET(ip) 0x0140 +#define UNIPERIF_TDM_WORD_POS_5_6_OFFSET(ip) 0x0144 +#define UNIPERIF_TDM_WORD_POS_7_8_OFFSET(ip) 0x0148 +#define GET_UNIPERIF_TDM_WORD_POS(ip, words) \ + readl_relaxed(ip->base + UNIPERIF_TDM_WORD_POS_##words##_OFFSET(ip)) +#define SET_UNIPERIF_TDM_WORD_POS(ip, words, value) \ + writel_relaxed(value, ip->base + \ + UNIPERIF_TDM_WORD_POS_##words##_OFFSET(ip)) +/* * uniperipheral IP capabilities */ #define UNIPERIF_FIFO_SIZE 70 /* FIFO is 70 cells deep */ #define UNIPERIF_FIFO_FRAMES 4 /* FDMA trigger limit in frames */ +#define UNIPERIF_TYPE_IS_HDMI(p) \ + ((p)->info->type == SND_ST_UNIPERIF_TYPE_HDMI) +#define UNIPERIF_TYPE_IS_PCM(p) \ + ((p)->info->type == SND_ST_UNIPERIF_TYPE_PCM) +#define UNIPERIF_TYPE_IS_SPDIF(p) \ + ((p)->info->type == SND_ST_UNIPERIF_TYPE_SPDIF) +#define UNIPERIF_TYPE_IS_IEC958(p) \ + (UNIPERIF_TYPE_IS_HDMI(p) || \ + UNIPERIF_TYPE_IS_SPDIF(p)) +#define UNIPERIF_TYPE_IS_TDM(p) \ + ((p)->info->type == SND_ST_UNIPERIF_TYPE_TDM) + /* * Uniperipheral IP revisions */ @@ -1125,10 +1249,11 @@ enum uniperif_version { }; enum uniperif_type { - SND_ST_UNIPERIF_PLAYER_TYPE_NONE, - SND_ST_UNIPERIF_PLAYER_TYPE_HDMI, - SND_ST_UNIPERIF_PLAYER_TYPE_PCM, - SND_ST_UNIPERIF_PLAYER_TYPE_SPDIF + SND_ST_UNIPERIF_TYPE_NONE, + SND_ST_UNIPERIF_TYPE_HDMI, + SND_ST_UNIPERIF_TYPE_PCM, + SND_ST_UNIPERIF_TYPE_SPDIF, + SND_ST_UNIPERIF_TYPE_TDM }; enum uniperif_state { @@ -1145,9 +1270,17 @@ enum uniperif_iec958_encoding_mode { UNIPERIF_IEC958_ENCODING_MODE_ENCODED }; +enum uniperif_word_pos { + WORD_1_2, + WORD_3_4, + WORD_5_6, + WORD_7_8, + WORD_MAX +}; + struct uniperif_info { int id; /* instance value of the uniperipheral IP */ - enum uniperif_type player_type; + enum uniperif_type type; int underflow_enabled; /* Underflow recovery mode */ }; @@ -1156,12 +1289,20 @@ struct uniperif_iec958_settings { struct snd_aes_iec958 iec958; }; +struct dai_tdm_slot { + unsigned int mask; + int slots; + int slot_width; + unsigned int avail_slots; +}; + struct uniperif { /* System information */ struct uniperif_info *info; struct device *dev; int ver; /* IP version, used by register access macros */ struct regmap_field *clk_sel; + struct regmap_field *valid_sel; /* capabilities */ const struct snd_pcm_hardware *hw; @@ -1192,6 +1333,7 @@ struct uniperif { /* dai properties */ unsigned int daifmt; + struct dai_tdm_slot tdm_slot; /* DAI callbacks */ const struct snd_soc_dai_ops *dai_ops; @@ -1209,6 +1351,28 @@ struct sti_uniperiph_data { struct sti_uniperiph_dai dai_data; }; +static const struct snd_pcm_hardware uni_tdm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE, + + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 48000, + + .channels_min = 1, + .channels_max = 32, + + .periods_min = 2, + .periods_max = 10, + + .period_bytes_min = 128, + .period_bytes_max = 64 * PAGE_SIZE, + .buffer_bytes_max = 256 * PAGE_SIZE +}; + /* uniperiph player*/ int uni_player_init(struct platform_device *pdev, struct uniperif *uni_player); @@ -1226,4 +1390,28 @@ int sti_uniperiph_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai); +static inline int sti_uniperiph_get_user_frame_size( + struct snd_pcm_runtime *runtime) +{ + return (runtime->channels * snd_pcm_format_width(runtime->format) / 8); +} + +static inline int sti_uniperiph_get_unip_tdm_frame_size(struct uniperif *uni) +{ + return (uni->tdm_slot.slots * uni->tdm_slot.slot_width / 8); +} + +int sti_uniperiph_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, + int slot_width); + +int sti_uniperiph_get_tdm_word_pos(struct uniperif *uni, + unsigned int *word_pos); + +int sti_uniperiph_fix_tdm_chan(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule); + +int sti_uniperiph_fix_tdm_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule); + #endif diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c index 7aca6b9..ee1c7c2 100644 --- a/sound/soc/sti/uniperif_player.c +++ b/sound/soc/sti/uniperif_player.c @@ -21,23 +21,14 @@ /* sys config registers definitions */ #define SYS_CFG_AUDIO_GLUE 0xA4 -#define SYS_CFG_AUDI0_GLUE_PCM_CLKX 8 /* * Driver specific types. */ -#define UNIPERIF_PLAYER_TYPE_IS_HDMI(p) \ - ((p)->info->player_type == SND_ST_UNIPERIF_PLAYER_TYPE_HDMI) -#define UNIPERIF_PLAYER_TYPE_IS_PCM(p) \ - ((p)->info->player_type == SND_ST_UNIPERIF_PLAYER_TYPE_PCM) -#define UNIPERIF_PLAYER_TYPE_IS_SPDIF(p) \ - ((p)->info->player_type == SND_ST_UNIPERIF_PLAYER_TYPE_SPDIF) -#define UNIPERIF_PLAYER_TYPE_IS_IEC958(p) \ - (UNIPERIF_PLAYER_TYPE_IS_HDMI(p) || \ - UNIPERIF_PLAYER_TYPE_IS_SPDIF(p)) #define UNIPERIF_PLAYER_CLK_ADJ_MIN -999999 #define UNIPERIF_PLAYER_CLK_ADJ_MAX 1000000 +#define UNIPERIF_PLAYER_I2S_OUT 1 /* player id connected to I2S/TDM TX bus */ /* * Note: snd_pcm_hardware is linked to DMA controller but is declared here to @@ -444,18 +435,11 @@ static int uni_player_prepare_pcm(struct uniperif *player, /* Force slot width to 32 in I2S mode (HW constraint) */ if ((player->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) == - SND_SOC_DAIFMT_I2S) { + SND_SOC_DAIFMT_I2S) slot_width = 32; - } else { - switch (runtime->format) { - case SNDRV_PCM_FORMAT_S16_LE: - slot_width = 16; - break; - default: - slot_width = 32; - break; - } - } + else + slot_width = snd_pcm_format_width(runtime->format); + output_frame_size = slot_width * runtime->channels; clk_div = player->mclk / runtime->rate; @@ -530,7 +514,6 @@ static int uni_player_prepare_pcm(struct uniperif *player, SET_UNIPERIF_CONFIG_ONE_BIT_AUD_DISABLE(player); SET_UNIPERIF_I2S_FMT_ORDER_MSB(player); - SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(player); /* No iec958 formatting as outputting to DAC */ SET_UNIPERIF_CTRL_SPDIF_FMT_OFF(player); @@ -538,6 +521,55 @@ static int uni_player_prepare_pcm(struct uniperif *player, return 0; } +static int uni_player_prepare_tdm(struct uniperif *player, + struct snd_pcm_runtime *runtime) +{ + int tdm_frame_size; /* unip tdm frame size in bytes */ + int user_frame_size; /* user tdm frame size in bytes */ + /* default unip TDM_WORD_POS_X_Y */ + unsigned int word_pos[4] = { + 0x04060002, 0x0C0E080A, 0x14161012, 0x1C1E181A}; + int freq, ret; + + tdm_frame_size = + sti_uniperiph_get_unip_tdm_frame_size(player); + user_frame_size = + sti_uniperiph_get_user_frame_size(runtime); + + /* fix 16/0 format */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_0(player); + SET_UNIPERIF_I2S_FMT_DATA_SIZE_32(player); + + /* number of words inserted on the TDM line */ + SET_UNIPERIF_I2S_FMT_NUM_CH(player, user_frame_size / 4 / 2); + + SET_UNIPERIF_I2S_FMT_ORDER_MSB(player); + SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(player); + + /* Enable the tdm functionality */ + SET_UNIPERIF_TDM_ENABLE_TDM_ENABLE(player); + + /* number of 8 bits timeslots avail in unip tdm frame */ + SET_UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT(player, tdm_frame_size); + + /* set the timeslot allocation for words in FIFO */ + sti_uniperiph_get_tdm_word_pos(player, word_pos); + SET_UNIPERIF_TDM_WORD_POS(player, 1_2, word_pos[WORD_1_2]); + SET_UNIPERIF_TDM_WORD_POS(player, 3_4, word_pos[WORD_3_4]); + SET_UNIPERIF_TDM_WORD_POS(player, 5_6, word_pos[WORD_5_6]); + SET_UNIPERIF_TDM_WORD_POS(player, 7_8, word_pos[WORD_7_8]); + + /* set unip clk rate (not done vai set_sysclk ops) */ + freq = runtime->rate * tdm_frame_size * 8; + mutex_lock(&player->ctrl_lock); + ret = uni_player_clk_set_rate(player, freq); + if (!ret) + player->mclk = freq; + mutex_unlock(&player->ctrl_lock); + + return 0; +} + /* * ALSA uniperipheral iec958 controls */ @@ -668,11 +700,29 @@ static int uni_player_startup(struct snd_pcm_substream *substream, { struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); struct uniperif *player = priv->dai_data.uni; + int ret; + player->substream = substream; player->clk_adj = 0; - return 0; + if (!UNIPERIF_TYPE_IS_TDM(player)) + return 0; + + /* refine hw constraint in tdm mode */ + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + sti_uniperiph_fix_tdm_chan, + player, SNDRV_PCM_HW_PARAM_CHANNELS, + -1); + if (ret < 0) + return ret; + + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_FORMAT, + sti_uniperiph_fix_tdm_format, + player, SNDRV_PCM_HW_PARAM_FORMAT, + -1); } static int uni_player_set_sysclk(struct snd_soc_dai *dai, int clk_id, @@ -682,7 +732,7 @@ static int uni_player_set_sysclk(struct snd_soc_dai *dai, int clk_id, struct uniperif *player = priv->dai_data.uni; int ret; - if (dir == SND_SOC_CLOCK_IN) + if (UNIPERIF_TYPE_IS_TDM(player) || (dir == SND_SOC_CLOCK_IN)) return 0; if (clk_id != 0) @@ -714,7 +764,13 @@ static int uni_player_prepare(struct snd_pcm_substream *substream, } /* Calculate transfer size (in fifo cells and bytes) for frame count */ - transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES; + if (player->info->type == SND_ST_UNIPERIF_TYPE_TDM) { + /* transfer size = user frame size (in 32 bits FIFO cell) */ + transfer_size = + sti_uniperiph_get_user_frame_size(runtime) / 4; + } else { + transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES; + } /* Calculate number of empty cells available before asserting DREQ */ if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) { @@ -738,16 +794,19 @@ static int uni_player_prepare(struct snd_pcm_substream *substream, SET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(player, trigger_limit); /* Uniperipheral setup depends on player type */ - switch (player->info->player_type) { - case SND_ST_UNIPERIF_PLAYER_TYPE_HDMI: + switch (player->info->type) { + case SND_ST_UNIPERIF_TYPE_HDMI: ret = uni_player_prepare_iec958(player, runtime); break; - case SND_ST_UNIPERIF_PLAYER_TYPE_PCM: + case SND_ST_UNIPERIF_TYPE_PCM: ret = uni_player_prepare_pcm(player, runtime); break; - case SND_ST_UNIPERIF_PLAYER_TYPE_SPDIF: + case SND_ST_UNIPERIF_TYPE_SPDIF: ret = uni_player_prepare_iec958(player, runtime); break; + case SND_ST_UNIPERIF_TYPE_TDM: + ret = uni_player_prepare_tdm(player, runtime); + break; default: dev_err(player->dev, "invalid player type"); return -EINVAL; @@ -852,8 +911,8 @@ static int uni_player_start(struct uniperif *player) * will not take affect and hang the player. */ if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) - if (UNIPERIF_PLAYER_TYPE_IS_IEC958(player)) - SET_UNIPERIF_CTRL_SPDIF_FMT_ON(player); + if (UNIPERIF_TYPE_IS_IEC958(player)) + SET_UNIPERIF_CTRL_SPDIF_FMT_ON(player); /* Force channel status update (no update if clk disable) */ if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) @@ -954,27 +1013,30 @@ static void uni_player_shutdown(struct snd_pcm_substream *substream, player->substream = NULL; } -static int uni_player_parse_dt_clk_glue(struct platform_device *pdev, - struct uniperif *player) +static int uni_player_parse_dt_audio_glue(struct platform_device *pdev, + struct uniperif *player) { - int bit_offset; struct device_node *node = pdev->dev.of_node; struct regmap *regmap; - - bit_offset = SYS_CFG_AUDI0_GLUE_PCM_CLKX + player->info->id; + struct reg_field regfield[2] = { + /* PCM_CLK_SEL */ + REG_FIELD(SYS_CFG_AUDIO_GLUE, + 8 + player->info->id, + 8 + player->info->id), + /* PCMP_VALID_SEL */ + REG_FIELD(SYS_CFG_AUDIO_GLUE, 0, 1) + }; regmap = syscon_regmap_lookup_by_phandle(node, "st,syscfg"); - if (regmap) { - struct reg_field regfield = - REG_FIELD(SYS_CFG_AUDIO_GLUE, bit_offset, bit_offset); - - player->clk_sel = regmap_field_alloc(regmap, regfield); - } else { + if (!regmap) { dev_err(&pdev->dev, "sti-audio-clk-glue syscf not found\n"); return -EINVAL; } + player->clk_sel = regmap_field_alloc(regmap, regfield[0]); + player->valid_sel = regmap_field_alloc(regmap, regfield[1]); + return 0; } @@ -1012,19 +1074,21 @@ static int uni_player_parse_dt(struct platform_device *pdev, } if (strcasecmp(mode, "hdmi") == 0) - info->player_type = SND_ST_UNIPERIF_PLAYER_TYPE_HDMI; + info->type = SND_ST_UNIPERIF_TYPE_HDMI; else if (strcasecmp(mode, "pcm") == 0) - info->player_type = SND_ST_UNIPERIF_PLAYER_TYPE_PCM; + info->type = SND_ST_UNIPERIF_TYPE_PCM; else if (strcasecmp(mode, "spdif") == 0) - info->player_type = SND_ST_UNIPERIF_PLAYER_TYPE_SPDIF; + info->type = SND_ST_UNIPERIF_TYPE_SPDIF; + else if (strcasecmp(mode, "tdm") == 0) + info->type = SND_ST_UNIPERIF_TYPE_TDM; else - info->player_type = SND_ST_UNIPERIF_PLAYER_TYPE_NONE; + info->type = SND_ST_UNIPERIF_TYPE_NONE; /* Save the info structure */ player->info = info; - /* Get the PCM_CLK_SEL bit from audio-glue-ctrl SoC register */ - if (uni_player_parse_dt_clk_glue(pdev, player)) + /* Get PCM_CLK_SEL & PCMP_VALID_SEL from audio-glue-ctrl SoC reg */ + if (uni_player_parse_dt_audio_glue(pdev, player)) return -EINVAL; return 0; @@ -1037,7 +1101,8 @@ static const struct snd_soc_dai_ops uni_player_dai_ops = { .trigger = uni_player_trigger, .hw_params = sti_uniperiph_dai_hw_params, .set_fmt = sti_uniperiph_dai_set_fmt, - .set_sysclk = uni_player_set_sysclk + .set_sysclk = uni_player_set_sysclk, + .set_tdm_slot = sti_uniperiph_set_tdm_slot }; int uni_player_init(struct platform_device *pdev, @@ -1047,7 +1112,6 @@ int uni_player_init(struct platform_device *pdev, player->dev = &pdev->dev; player->state = UNIPERIF_STATE_STOPPED; - player->hw = &uni_player_pcm_hw; player->dai_ops = &uni_player_dai_ops; ret = uni_player_parse_dt(pdev, player); @@ -1057,6 +1121,11 @@ int uni_player_init(struct platform_device *pdev, return ret; } + if (UNIPERIF_TYPE_IS_TDM(player)) + player->hw = &uni_tdm_hw; + else + player->hw = &uni_player_pcm_hw; + /* Get uniperif resource */ player->clk = of_clk_get(pdev->dev.of_node, 0); if (IS_ERR(player->clk)) @@ -1073,6 +1142,17 @@ int uni_player_init(struct platform_device *pdev, } } + /* connect to I2S/TDM TX bus */ + if (player->valid_sel && + (player->info->id == UNIPERIF_PLAYER_I2S_OUT)) { + ret = regmap_field_write(player->valid_sel, player->info->id); + if (ret) { + dev_err(player->dev, + "%s: unable to connect to tdm bus", __func__); + return ret; + } + } + ret = devm_request_irq(&pdev->dev, player->irq, uni_player_irq_handler, IRQF_SHARED, dev_name(&pdev->dev), player); @@ -1087,7 +1167,7 @@ int uni_player_init(struct platform_device *pdev, SET_UNIPERIF_CTRL_SPDIF_LAT_OFF(player); SET_UNIPERIF_CONFIG_IDLE_MOD_DISABLE(player); - if (UNIPERIF_PLAYER_TYPE_IS_IEC958(player)) { + if (UNIPERIF_TYPE_IS_IEC958(player)) { /* Set default iec958 status bits */ /* Consumer, PCM, copyright, 2ch, mode 0 */ diff --git a/sound/soc/sti/uniperif_reader.c b/sound/soc/sti/uniperif_reader.c index 8a0eb20..eb74a32 100644 --- a/sound/soc/sti/uniperif_reader.c +++ b/sound/soc/sti/uniperif_reader.c @@ -73,55 +73,10 @@ static irqreturn_t uni_reader_irq_handler(int irq, void *dev_id) return ret; } -static int uni_reader_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int uni_reader_prepare_pcm(struct snd_pcm_runtime *runtime, + struct uniperif *reader) { - struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); - struct uniperif *reader = priv->dai_data.uni; - struct snd_pcm_runtime *runtime = substream->runtime; - int transfer_size, trigger_limit; int slot_width; - int count = 10; - - /* The reader should be stopped */ - if (reader->state != UNIPERIF_STATE_STOPPED) { - dev_err(reader->dev, "%s: invalid reader state %d", __func__, - reader->state); - return -EINVAL; - } - - /* Calculate transfer size (in fifo cells and bytes) for frame count */ - transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES; - - /* Calculate number of empty cells available before asserting DREQ */ - if (reader->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) - trigger_limit = UNIPERIF_FIFO_SIZE - transfer_size; - else - /* - * Since SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 - * FDMA_TRIGGER_LIMIT also controls when the state switches - * from OFF or STANDBY to AUDIO DATA. - */ - trigger_limit = transfer_size; - - /* Trigger limit must be an even number */ - if ((!trigger_limit % 2) || - (trigger_limit != 1 && transfer_size % 2) || - (trigger_limit > UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(reader))) { - dev_err(reader->dev, "invalid trigger limit %d", trigger_limit); - return -EINVAL; - } - - SET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(reader, trigger_limit); - - switch (reader->daifmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_IB_IF: - case SND_SOC_DAIFMT_NB_IF: - SET_UNIPERIF_I2S_FMT_LR_POL_HIG(reader); - break; - default: - SET_UNIPERIF_I2S_FMT_LR_POL_LOW(reader); - } /* Force slot width to 32 in I2S mode */ if ((reader->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) @@ -173,6 +128,109 @@ static int uni_reader_prepare(struct snd_pcm_substream *substream, return -EINVAL; } + /* Number of channels must be even */ + if ((runtime->channels % 2) || (runtime->channels < 2) || + (runtime->channels > 10)) { + dev_err(reader->dev, "%s: invalid nb of channels", __func__); + return -EINVAL; + } + + SET_UNIPERIF_I2S_FMT_NUM_CH(reader, runtime->channels / 2); + SET_UNIPERIF_I2S_FMT_ORDER_MSB(reader); + + return 0; +} + +static int uni_reader_prepare_tdm(struct snd_pcm_runtime *runtime, + struct uniperif *reader) +{ + int frame_size; /* user tdm frame size in bytes */ + /* default unip TDM_WORD_POS_X_Y */ + unsigned int word_pos[4] = { + 0x04060002, 0x0C0E080A, 0x14161012, 0x1C1E181A}; + + frame_size = sti_uniperiph_get_user_frame_size(runtime); + + /* fix 16/0 format */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_0(reader); + SET_UNIPERIF_I2S_FMT_DATA_SIZE_32(reader); + + /* number of words inserted on the TDM line */ + SET_UNIPERIF_I2S_FMT_NUM_CH(reader, frame_size / 4 / 2); + + SET_UNIPERIF_I2S_FMT_ORDER_MSB(reader); + SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(reader); + SET_UNIPERIF_TDM_ENABLE_TDM_ENABLE(reader); + + /* + * set the timeslots allocation for words in FIFO + * + * HW bug: (LSB word < MSB word) => this config is not possible + * So if we want (LSB word < MSB) word, then it shall be + * handled by user + */ + sti_uniperiph_get_tdm_word_pos(reader, word_pos); + SET_UNIPERIF_TDM_WORD_POS(reader, 1_2, word_pos[WORD_1_2]); + SET_UNIPERIF_TDM_WORD_POS(reader, 3_4, word_pos[WORD_3_4]); + SET_UNIPERIF_TDM_WORD_POS(reader, 5_6, word_pos[WORD_5_6]); + SET_UNIPERIF_TDM_WORD_POS(reader, 7_8, word_pos[WORD_7_8]); + + return 0; +} + +static int uni_reader_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *reader = priv->dai_data.uni; + struct snd_pcm_runtime *runtime = substream->runtime; + int transfer_size, trigger_limit, ret; + int count = 10; + + /* The reader should be stopped */ + if (reader->state != UNIPERIF_STATE_STOPPED) { + dev_err(reader->dev, "%s: invalid reader state %d", __func__, + reader->state); + return -EINVAL; + } + + /* Calculate transfer size (in fifo cells and bytes) for frame count */ + if (reader->info->type == SND_ST_UNIPERIF_TYPE_TDM) { + /* transfer size = unip frame size (in 32 bits FIFO cell) */ + transfer_size = + sti_uniperiph_get_user_frame_size(runtime) / 4; + } else { + transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES; + } + + /* Calculate number of empty cells available before asserting DREQ */ + if (reader->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) + trigger_limit = UNIPERIF_FIFO_SIZE - transfer_size; + else + /* + * Since SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 + * FDMA_TRIGGER_LIMIT also controls when the state switches + * from OFF or STANDBY to AUDIO DATA. + */ + trigger_limit = transfer_size; + + /* Trigger limit must be an even number */ + if ((!trigger_limit % 2) || + (trigger_limit != 1 && transfer_size % 2) || + (trigger_limit > UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(reader))) { + dev_err(reader->dev, "invalid trigger limit %d", trigger_limit); + return -EINVAL; + } + + SET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(reader, trigger_limit); + + if (UNIPERIF_TYPE_IS_TDM(reader)) + ret = uni_reader_prepare_tdm(runtime, reader); + else + ret = uni_reader_prepare_pcm(runtime, reader); + if (ret) + return ret; + switch (reader->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(reader); @@ -191,21 +249,26 @@ static int uni_reader_prepare(struct snd_pcm_substream *substream, return -EINVAL; } - SET_UNIPERIF_I2S_FMT_ORDER_MSB(reader); - - /* Data clocking (changing) on the rising edge */ - SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(reader); - - /* Number of channels must be even */ - - if ((runtime->channels % 2) || (runtime->channels < 2) || - (runtime->channels > 10)) { - dev_err(reader->dev, "%s: invalid nb of channels", __func__); - return -EINVAL; + /* Data clocking (changing) on the rising/falling edge */ + switch (reader->daifmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + SET_UNIPERIF_I2S_FMT_LR_POL_LOW(reader); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(reader); + break; + case SND_SOC_DAIFMT_NB_IF: + SET_UNIPERIF_I2S_FMT_LR_POL_HIG(reader); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(reader); + break; + case SND_SOC_DAIFMT_IB_NF: + SET_UNIPERIF_I2S_FMT_LR_POL_LOW(reader); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(reader); + break; + case SND_SOC_DAIFMT_IB_IF: + SET_UNIPERIF_I2S_FMT_LR_POL_HIG(reader); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(reader); + break; } - SET_UNIPERIF_I2S_FMT_NUM_CH(reader, runtime->channels / 2); - /* Clear any pending interrupts */ SET_UNIPERIF_ITS_BCLR(reader, GET_UNIPERIF_ITS(reader)); @@ -293,6 +356,32 @@ static int uni_reader_trigger(struct snd_pcm_substream *substream, } } +static int uni_reader_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *reader = priv->dai_data.uni; + int ret; + + if (!UNIPERIF_TYPE_IS_TDM(reader)) + return 0; + + /* refine hw constraint in tdm mode */ + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + sti_uniperiph_fix_tdm_chan, + reader, SNDRV_PCM_HW_PARAM_CHANNELS, + -1); + if (ret < 0) + return ret; + + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_FORMAT, + sti_uniperiph_fix_tdm_format, + reader, SNDRV_PCM_HW_PARAM_FORMAT, + -1); +} + static void uni_reader_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -310,6 +399,7 @@ static int uni_reader_parse_dt(struct platform_device *pdev, { struct uniperif_info *info; struct device_node *node = pdev->dev.of_node; + const char *mode; /* Allocate memory for the info structure */ info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); @@ -322,6 +412,17 @@ static int uni_reader_parse_dt(struct platform_device *pdev, return -EINVAL; } + /* Read the device mode property */ + if (of_property_read_string(node, "st,mode", &mode)) { + dev_err(&pdev->dev, "uniperipheral mode not defined"); + return -EINVAL; + } + + if (strcasecmp(mode, "tdm") == 0) + info->type = SND_ST_UNIPERIF_TYPE_TDM; + else + info->type = SND_ST_UNIPERIF_TYPE_PCM; + /* Save the info structure */ reader->info = info; @@ -329,11 +430,13 @@ static int uni_reader_parse_dt(struct platform_device *pdev, } static const struct snd_soc_dai_ops uni_reader_dai_ops = { + .startup = uni_reader_startup, .shutdown = uni_reader_shutdown, .prepare = uni_reader_prepare, .trigger = uni_reader_trigger, .hw_params = sti_uniperiph_dai_hw_params, .set_fmt = sti_uniperiph_dai_set_fmt, + .set_tdm_slot = sti_uniperiph_set_tdm_slot }; int uni_reader_init(struct platform_device *pdev, @@ -343,7 +446,6 @@ int uni_reader_init(struct platform_device *pdev, reader->dev = &pdev->dev; reader->state = UNIPERIF_STATE_STOPPED; - reader->hw = &uni_reader_pcm_hw; reader->dai_ops = &uni_reader_dai_ops; ret = uni_reader_parse_dt(pdev, reader); @@ -352,6 +454,11 @@ int uni_reader_init(struct platform_device *pdev, return ret; } + if (UNIPERIF_TYPE_IS_TDM(reader)) + reader->hw = &uni_tdm_hw; + else + reader->hw = &uni_reader_pcm_hw; + ret = devm_request_irq(&pdev->dev, reader->irq, uni_reader_irq_handler, IRQF_SHARED, dev_name(&pdev->dev), reader); |