diff options
author | Takashi Iwai <tiwai@suse.de> | 2016-05-27 17:16:53 +0200 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2016-05-27 17:16:53 +0200 |
commit | eb4606e64a7d548f5d60a9583baa8104890b2c6e (patch) | |
tree | 2dc8e587fc79c207dab2fb74830963cc84dcf801 /sound | |
parent | 86c72d1ce91d804e4fa8d90b316a89597dd220f1 (diff) | |
parent | bf65921380cd50b87618df550398e06581f4a361 (diff) |
Merge tag 'asoc-v4.7-2' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound into for-linussound-4.7-rc1-2
ASoC: Updates for v4.7 part 2
Really sorry about this late pull request. It looks like at the time I
sent my pull request for v4.7 there was some conflict or other issue
which caused my script to stop merging the ASoC branches at some point
after the HDMI changes.
It's all specific driver updates, including:
- New drivers for MAX98371 and TAS5720.
- SPI support for TLV320AIC32x4.
- TDM support for STI Uniperf IPs.
This code should all have been in -next prior to the merge window apart
from some fixes, it dropped out on the 18th.
Diffstat (limited to 'sound')
46 files changed, 2638 insertions, 317 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index b3afae990e39..4d82a58ff6b0 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 b7b99416537f..0f548fd34ca3 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 1ee8506c06c7..4d8b9e49e8d6 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 000000000000..cf0a39bb631a --- /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 000000000000..9f6330964d98 --- /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 a1aaffc20862..f80cfe4d2ef2 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 d66f8847b676..3638f3d61209 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 33e290b703df..b3f1db5bae4a 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 39307ad41a34..b8d19b77bde9 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 0aee471232cd..cf800c364f0f 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 000000000000..f54fb46b77c2 --- /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 000000000000..3d077c779b12 --- /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 ee4def4f819f..3c5e1df01c19 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 000000000000..59606cf3008f --- /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 000000000000..724fcdd491b2 --- /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 f2d3191961e1..85d4978d0384 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 995f033a855d..a197dd51addc 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 bc3de2e844e6..1f7081043566 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 720a14e0687d..0100e28f40dc 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -3798,9 +3798,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); @@ -3838,6 +3837,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 910aafd09d21..e63a318a3015 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 2389ab47e25f..466492b7d4f5 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 132bb83f8e99..bc3c7b5ac752 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 f7e789e97fbc..3abf51c07851 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 5c4c58c69c51..bb593926c62d 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 bb09bb1b7f1c..a27a6673dbe3 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 f1c58a2c12fb..2b5df2ef51a3 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 c7563e230c7d..4a16e778966b 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 99381a27295b..a84f677234f0 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 ec522e94b0e2..b6cb9950f05d 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 5c8f9db50a47..d1661fa6ee08 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 51e790d006f5..96df9b2d8fc4 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 eca60c29791a..ca8b23f8c525 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 4e74d9573f03..bcc81e920a67 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 da03fad1b9cd..3cad990dad2c 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 f3de615aacd7..9615e6de1306 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 9f390398d518..410d48b93031 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 6e8665430bd5..db000c6987a1 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; @@ -491,7 +491,7 @@ static int lpass_platform_pcm_new(struct snd_soc_pcm_runtime *soc_runtime) data->rdma_ch = v->alloc_dma_channel(drvdata, SNDRV_PCM_STREAM_PLAYBACK); - if (IS_ERR_VALUE(data->rdma_ch)) + if (data->rdma_ch < 0) return data->rdma_ch; drvdata->substream[data->rdma_ch] = psubstream; @@ -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 (IS_ERR_VALUE(data->wrdma_ch)) + 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 606399de684d..49354d17ea55 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 7658e8fd7bdc..6bc93cbb3049 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 fc89a67258ca..a8f61d79333b 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 15d6ffe8be74..e39f916d0f2f 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 1cf94d7fb9f4..ee7f15aa46fc 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 39bcefe5eea0..488ef4ed8fba 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 f0fd5a9944e9..eb9933c62ad6 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 7aca6b92f718..ee1c7c245bc7 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 8a0eb2050169..eb74a328c928 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); |