[PATCH 1/6 V3] ASoC: add TAS5756m driver
Charles-Antoine Couret
charles-antoine.couret at mind.be
Sun Mar 8 15:25:04 CET 2020
You can read datasheets there:
http://www.ti.com/lit/ds/symlink/tas5754m.pdf
http://www.ti.com/lit/ds/symlink/tas5756m.pdf
TAS5754M datasheet has a more complete datasheet about register mapping
which is common with TAS5756M.
Those devices have a programmable DSP whith several modes named hybridflow.
Currently only Hybridflow 3, 4, 6 and 7 are supported (out of 9).
Retro engenerring is required to map coefficients registers to registers
for each Hybridflow.
More details about it there: http://www.ti.com/lit/ug/slau577a/slau577a.pdf
Signed-off-by: Charles-Antoine Couret <charles-antoine.couret at mind.be>
---
sound/soc/codecs/Kconfig | 8 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/tas5756m.c | 2165 +++++++++++++++++++++++++++++++++++
sound/soc/codecs/tas5756m.h | 351 ++++++
4 files changed, 2526 insertions(+)
create mode 100644 sound/soc/codecs/tas5756m.c
create mode 100644 sound/soc/codecs/tas5756m.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index ea912439e446..5e3afb4fb68d 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -193,6 +193,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_TAS5086 if I2C
select SND_SOC_TAS571X if I2C
select SND_SOC_TAS5720 if I2C
+ select SND_SOC_TAS5756M if I2C
select SND_SOC_TAS6424 if I2C
select SND_SOC_TDA7419 if I2C
select SND_SOC_TFA9879 if I2C
@@ -1221,6 +1222,13 @@ config SND_SOC_TAS5720
Enable support for Texas Instruments TAS5720L/M high-efficiency mono
Class-D audio power amplifiers.
+config SND_SOC_TAS5756M
+ tristate "Texas Instruments TAS5756M Audio amplifier"
+ depends on I2C
+ help
+ Enable support for Texas Instruments TAS5756M and TAS5754
+ audio power amplifiers.
+
config SND_SOC_TAS6424
tristate "Texas Instruments TAS6424 Quad-Channel Audio amplifier"
depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index ba1b4b3fa2da..39dbaad4af70 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -204,6 +204,7 @@ 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-tas5756m-objs := tas5756m.o
snd-soc-tas6424-objs := tas6424.o
snd-soc-tda7419-objs := tda7419.o
snd-soc-tas2770-objs := tas2770.o
@@ -502,6 +503,7 @@ obj-$(CONFIG_SND_SOC_TAS2562) += snd-soc-tas2562.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_TAS5756M) += snd-soc-tas5756m.o
obj-$(CONFIG_SND_SOC_TAS6424) += snd-soc-tas6424.o
obj-$(CONFIG_SND_SOC_TDA7419) += snd-soc-tda7419.o
obj-$(CONFIG_SND_SOC_TAS2770) += snd-soc-tas2770.o
diff --git a/sound/soc/codecs/tas5756m.c b/sound/soc/codecs/tas5756m.c
new file mode 100644
index 000000000000..67a03ac337ef
--- /dev/null
+++ b/sound/soc/codecs/tas5756m.c
@@ -0,0 +1,2165 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * tas5756m.h - ALSA SoC Texas Instruments TAS5756M Audio Amplifier
+ *
+ * Copyright (C)2018-2020 House of Music NV - https://www.homa.be
+ *
+ * Authors: Charles-Antoine Couret <charles-antoine.couret at mind.be>
+ * : Thomas Brijs <thomas.brijs at houseofmusic.be>
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+
+#include <linux/regulator/consumer.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-component.h>
+#include <sound/tlv.h>
+
+#include "tas5756m.h"
+#if 0
+#include "tas5756m_hf3_cfg.h"
+#include "tas5756m_hf4_cfg.h"
+#include "tas5756m_hf6_cfg.h"
+#include "tas5756m_hf7_cfg.h"
+#endif
+
+/* Define how often to check (and clear) the fault status register (in ms) */
+#define TAS5756M_FAULT_CHECK_INTERVAL 200
+
+static struct reg_default tas5756m_reg_defaults[] = {
+ { TAS5756M_RESET, 0x00 },
+ { TAS5756M_PDN_STBY, 0x00 },
+ { TAS5756M_MUTE_L_R, 0x00 },
+ { TAS5756M_PLL_EN_STA, 0x01 },
+ { TAS5756M_SPI_MISO_SEL, 0x01 },
+ { TAS5756M_SDOUT_DEEMPH, 0x00 },
+ { TAS5756M_GPIO_OUT_EN, 0x00 },
+ { TAS5756M_BCK_LRCK_CFG, 0x00 },
+ { TAS5756M_DSP_GPIO_IN, 0x00 },
+ { TAS5756M_MASTER_B_LRCK_RST, 0x7c },
+ { TAS5756M_PLL_CLK_SRC_SEL, 0x00 },
+ { TAS5756M_DAC_CLOCK_SRC, 0x00 },
+ { TAS5756M_PLL_GPIO_REF_SEL, 0x00 },
+ { TAS5756M_SYNC_REQ, 0x10 },
+ { TAS5756M_PLL_P_VALUE, 0x00 },
+ { TAS5756M_PLL_J_VALUE, 0x00 },
+ { TAS5756M_PLL_D_VALUE_MSB, 0x00 },
+ { TAS5756M_PLL_D_VALUE_LSB, 0x00 },
+ { TAS5756M_PLL_R_VALUE, 0x00 },
+ { TAS5756M_DSP_CLKDIV, 0x00 },
+ { TAS5756M_DAC_CLKDIV, 0x00 },
+ { TAS5756M_NCP_CLKDIV, 0x00 },
+ { TAS5756M_OSR_CLKDIV, 0x00 },
+ { TAS5756M_MM_BCK_CLKDIV, 0x00 },
+ { TAS5756M_MM_LRCK_CLKDIV, 0x00 },
+ { TAS5756M_FS_SPEED_MODE, 0x00 },
+ { TAS5756M_IDAC_MSB, 0x01 },
+ { TAS5756M_IDAC_LSB, 0x00 },
+ { TAS5756M_IGN_ERRORS, 0x00 },
+ { TAS5756M_I2S_CONFIG, 0x10 },
+ { TAS5756M_I2S_SHIFT, 0x00 },
+ { TAS5756M_DAC_DATA_PATH, 0x01 },
+ { TAS5756M_DSP_PROG_SEL, 0x01 },
+ { TAS5756M_CLK_MISS_DET, 0x00 },
+ { TAS5756M_AUTO_MUTE_TIME, 0x00 },
+ { TAS5756M_DIGITAL_VOLUME, 0x00 },
+ { TAS5756M_LEFT_DVOL, 0x30 },
+ { TAS5756M_RIGHT_DVOL, 0x30 },
+ { TAS5756M_DVOL_RAMP_NORMAL, 0x22 },
+ { TAS5756M_DVOL_RAMP_EMRGNCY, 0x02 },
+ { TAS5756M_AUTO_MUTE, 0x04 },
+ { TAS5756M_GPIO1_OUTPUT_SEL, 0x00 },
+ { TAS5756M_GPIO2_OUTPUT_SEL, 0x00 },
+ { TAS5756M_GPIO3_OUTPUT_SEL, 0x00 },
+ { TAS5756M_GPIO4_OUTPUT_SEL, 0x00 },
+ { TAS5756M_GPIO5_OUTPUT_SEL, 0x00 },
+ { TAS5756M_GPIO6_OUTPUT_SEL, 0x00 },
+ { TAS5756M_DAC_MODE, 0x00 },
+ { TAS5756M_MCM_MODE, 0x00 },
+ { TAS5756M_MCM_OUT_GPIO_1_2, 0x00 },
+ { TAS5756M_MCM_OUT_GPIO_3_4, 0x00 },
+ { TAS5756M_MCM_OUT_GPIO_5_6, 0x00 },
+ { TAS5756M_ANLG_GAIN, 0x00 },
+ { TAS5756M_ANLG_BOOST, 0x00 },
+};
+
+static bool tas5756m_volatile(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TAS5756M_PAGE_SEL: /* regmap implementation requires this */
+ case TAS5756M_CHAN_OVRFLOW:
+ case TAS5756M_DET_FS_MCLK:
+ case TAS5756M_DET_SCLK:
+ case TAS5756M_DET_SCLK_DESC:
+ case TAS5756M_CLK_DET_STATUS:
+ case TAS5756M_CLK_STATUS:
+ case TAS5756M_ANLG_MUTE_MON:
+ case TAS5756M_SHORT_DETECT:
+ case TAS5756M_SPK_MUTE_DEC:
+ case TAS5756M_FS_SPEED_MON:
+ case TAS5756M_DAC_PWR_STA:
+ case TAS5756M_GPIO012_STATE:
+ case TAS5756M_AUTO_MUTE_FLAG:
+ return true;
+ }
+
+ return false;
+}
+
+static bool tas5756m_writeable(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TAS5756M_CHAN_OVRFLOW:
+ case TAS5756M_DET_FS_MCLK:
+ case TAS5756M_DET_SCLK:
+ case TAS5756M_DET_SCLK_DESC:
+ case TAS5756M_CLK_DET_STATUS:
+ case TAS5756M_CLK_STATUS:
+ case TAS5756M_ANLG_MUTE_MON:
+ case TAS5756M_SHORT_DETECT:
+ case TAS5756M_SPK_MUTE_DEC:
+ case TAS5756M_FS_SPEED_MON:
+ case TAS5756M_DAC_PWR_STA:
+ case TAS5756M_GPIO012_STATE:
+ case TAS5756M_AUTO_MUTE_FLAG:
+ return false;
+ }
+
+ return true;
+}
+
+static int tas5756m_route_channels(struct tas5756m_data *tas5756m,
+ enum channel_mixer channel)
+{
+ struct device *dev = &tas5756m->tas5756m_client->dev;
+ const int ADDR = PAGE_NR(0) + TAS5756M_DAC_DATA_PATH;
+ unsigned char value;
+ int ret;
+
+ switch (channel) {
+ case CHANNEL_MIXER_RIGHT:
+ value =
+ DAC_PATH_DEFAULT + (DAC_PATH_OPPOSITE << DAC_PATH_B_SHIFT);
+ break;
+ case CHANNEL_MIXER_LEFT:
+ value =
+ DAC_PATH_OPPOSITE + (DAC_PATH_DEFAULT << DAC_PATH_B_SHIFT);
+ break;
+ default:
+ value =
+ DAC_PATH_DEFAULT + (DAC_PATH_DEFAULT << DAC_PATH_B_SHIFT);
+ break;
+ }
+
+ ret = regmap_bulk_write(tas5756m->regmap, ADDR, &value, 1);
+ if (ret < 0) {
+ dev_err(dev, "failed to write default channels route: %d\n",
+ ret);
+ return ret;
+ }
+
+ tas5756m->channel = channel;
+ return ret;
+}
+
+static int tas5756m_resume(struct tas5756m_data *tas5756m)
+{
+ struct device *dev = &tas5756m->tas5756m_client->dev;
+ int ret;
+
+ ret =
+ regulator_bulk_enable(ARRAY_SIZE(tas5756m->supplies),
+ tas5756m->supplies);
+ if (ret < 0)
+ dev_err(dev, "failed to enable regulators %d\n", ret);
+
+ regcache_cache_only(tas5756m->regmap, false);
+
+ ret = regcache_sync(tas5756m->regmap);
+ if (ret < 0) {
+ dev_err(dev, "failed to sync regcache: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tas5756m_shutdown(struct tas5756m_data *tas5756m)
+{
+ struct device *dev = &tas5756m->tas5756m_client->dev;
+ int ret, reg_val, is_shutdown;
+
+ ret = regmap_read(tas5756m->regmap, TAS5756M_PDN_STBY, ®_val);
+ if (ret < 0) {
+ dev_err(dev, "failed to read DAC power state: %d\n", ret);
+ return ret;
+ }
+
+ is_shutdown = reg_val & PDN_MASK;
+ if (!is_shutdown) {
+ dev_info(dev, "codec power down - PDN: %u\n", is_shutdown);
+ regmap_update_bits(tas5756m->regmap, TAS5756M_PDN_STBY,
+ PDN_MASK, PDN_MASK);
+ }
+
+ return 0;
+}
+
+static bool tas5756m_is_running(struct tas5756m_data *tas5756m)
+{
+ struct device *dev = &tas5756m->tas5756m_client->dev;
+ bool running = false;
+ int err;
+ int dac_power_state;
+
+ err =
+ regmap_read(tas5756m->regmap, TAS5756M_DAC_PWR_STA,
+ &dac_power_state);
+ if (err < 0) {
+ dev_err(dev, "failed to read DAC power state: %d\n", err);
+ return running;
+ }
+
+ if ((dac_power_state & DAC_POWER_STATE_MASK) == DAC_RUNNING)
+ running = true;
+
+ return running;
+}
+
+static int tas5756m_get_coef_reg_offset(struct tas5756m_data *tas5756m,
+ enum hybridflow_features feature)
+{
+ int reg = -1;
+
+ if (tas5756m->hybridflow == HYBRIDFLOW_3) {
+ switch (feature) {
+ case TAS5756_REG_ADD_DELAY:
+ reg = PAGE_NR(50) + 72;
+ break;
+ case TAS5756_REG_CHAN_MIXER_HIGH:
+ reg = PAGE_NR(50) + 92;
+ break;
+ case TAS5756_REG_CHAN_MIXER_LOW:
+ reg = PAGE_NR(50) + 112;
+ break;
+ case TAS5756_REG_FILTER_HIGH_BIQUAD_1:
+ reg = PAGE_NR(46) + 56;
+ break;
+ case TAS5756_REG_FILTER_HIGH_BIQUAD_2:
+ reg = PAGE_NR(46) + 76;
+ break;
+ case TAS5756_REG_FILTER_HIGH_BIQUAD_3:
+ reg = PAGE_NR(46) + 96;
+ break;
+ case TAS5756_REG_FILTER_HIGH_BIQUAD_4:
+ reg = PAGE_NR(46) + 116;
+ break;
+ case TAS5756_REG_FILTER_HIGH_BIQUAD_5:
+ reg = PAGE_NR(47) + 16;
+ break;
+ case TAS5756_REG_FILTER_LOW_BIQUAD_1:
+ reg = PAGE_NR(45) + 60;
+ break;
+ case TAS5756_REG_FILTER_LOW_BIQUAD_2:
+ reg = PAGE_NR(45) + 80;
+ break;
+ case TAS5756_REG_FILTER_LOW_BIQUAD_3:
+ reg = PAGE_NR(45) + 100;
+ break;
+ case TAS5756_REG_FILTER_LOW_BIQUAD_4:
+ reg = PAGE_NR(45) + 120;
+ break;
+ case TAS5756_REG_FILTER_LOW_BIQUAD_5:
+ reg = PAGE_NR(46) + 20;
+ break;
+ case TAS5756_REG_DBE_EQ_HIGH_BIQUAD_1:
+ reg = PAGE_NR(47) + 80;
+ break;
+ case TAS5756_REG_DBE_EQ_HIGH_BIQUAD_2:
+ reg = PAGE_NR(47) + 100;
+ break;
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_1:
+ reg = PAGE_NR(51) + 16;
+ break;
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_2:
+ reg = PAGE_NR(51) + 36;
+ break;
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_3:
+ reg = PAGE_NR(51) + 56;
+ break;
+ case TAS5756M_REG_DBE_MIXING_HIGH:
+ reg = PAGE_NR(46) + 44;
+ break;
+ case TAS5756M_REG_DBE_MIXING_LOW:
+ reg = PAGE_NR(46) + 48;
+ break;
+ case TAS5756M_REG_DBE_SENSING_HIGH:
+ reg = PAGE_NR(47) + 60;
+ break;
+ case TAS5756M_REG_DBE_SENSING_LOW:
+ reg = PAGE_NR(47) + 40;
+ break;
+ case TAS5756M_REG_DBE_WINDOW:
+ reg = PAGE_NR(46) + 52;
+ break;
+ case TAS5756M_REG_PBE_BYPASS:
+ reg = PAGE_NR(51) + 76;
+ break;
+ case TAS5756M_REG_PBE_HARMONIC:
+ reg = PAGE_NR(44) + 16;
+ break;
+ case TAS5756M_REG_PBE_EFFECT:
+ case TAS5756M_REG_PBE_HPF:
+ reg = PAGE_NR(44) + 20;
+ break;
+ default:
+ reg = -1;
+ break;
+ }
+ } else if (tas5756m->hybridflow == HYBRIDFLOW_4) {
+ switch (feature) {
+ case TAS5756_REG_CHAN_MIXER:
+ case TAS5756_REG_CHAN_MIXER_HIGH:
+ case TAS5756_REG_CHAN_MIXER_LOW:
+ reg = PAGE_NR(51) + 28;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_PRE_DBE_1:
+ reg = PAGE_NR(51) + 48;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_PRE_DBE_2:
+ reg = PAGE_NR(51) + 68;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_PRE_DBE_3:
+ reg = PAGE_NR(51) + 88;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_POST_DBE_1:
+ reg = PAGE_NR(46) + 108;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_POST_DBE_2:
+ reg = PAGE_NR(47) + 8;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_POST_DBE_3:
+ reg = PAGE_NR(47) + 28;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_POST_DBE_4:
+ reg = PAGE_NR(47) + 48;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_POST_DBE_5:
+ reg = PAGE_NR(47) + 68;
+ break;
+ case TAS5756_REG_DBE_EQ_HIGH_BIQUAD_1:
+ reg = PAGE_NR(46) + 16;
+ break;
+ case TAS5756_REG_DBE_EQ_HIGH_BIQUAD_2:
+ reg = PAGE_NR(46) + 36;
+ break;
+ case TAS5756_REG_DBE_EQ_HIGH_BIQUAD_3:
+ reg = PAGE_NR(46) + 56;
+ break;
+ case TAS5756_REG_DBE_EQ_HIGH_BIQUAD_4:
+ reg = PAGE_NR(46) + 76;
+ break;
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_1:
+ reg = PAGE_NR(50) + 52;
+ break;
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_2:
+ reg = PAGE_NR(50) + 72;
+ break;
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_3:
+ reg = PAGE_NR(50) + 92;
+ break;
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_4:
+ reg = PAGE_NR(50) + 112;
+ break;
+ case TAS5756M_REG_DBE_MIXING_HIGH:
+ reg = PAGE_NR(46) + 100;
+ break;
+ case TAS5756M_REG_DBE_MIXING_LOW:
+ reg = PAGE_NR(46) + 96;
+ break;
+ case TAS5756M_REG_DBE_SENSING_HIGH:
+ reg = PAGE_NR(45) + 8;
+ break;
+ case TAS5756M_REG_DBE_SENSING_LOW:
+ reg = PAGE_NR(44) + 108;
+ break;
+ case TAS5756M_REG_DBE_WINDOW:
+ reg = PAGE_NR(46) + 104;
+ break;
+ case TAS5756M_REG_PBE_BYPASS:
+ reg = PAGE_NR(51) + 108;
+ break;
+ case TAS5756M_REG_PBE_HARMONIC:
+ reg = PAGE_NR(45) + 28;
+ break;
+ case TAS5756M_REG_PBE_EFFECT:
+ case TAS5756M_REG_PBE_HPF:
+ reg = PAGE_NR(45) + 32;
+ break;
+ default:
+ reg = -1;
+ break;
+ }
+ } else if (tas5756m->hybridflow == HYBRIDFLOW_6) {
+ switch (feature) {
+ case TAS5756_REG_FILTER_BIQUAD_1:
+ reg = PAGE_NR(47) + 32;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_2:
+ reg = PAGE_NR(47) + 52;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_3:
+ reg = PAGE_NR(47) + 72;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_4:
+ reg = PAGE_NR(47) + 92;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_5:
+ reg = PAGE_NR(47) + 112;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_6:
+ reg = PAGE_NR(48) + 12;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_7:
+ reg = PAGE_NR(48) + 32;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_8:
+ reg = PAGE_NR(48) + 52;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_9:
+ reg = PAGE_NR(48) + 72;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_10:
+ reg = PAGE_NR(48) + 92;
+ break;
+ case TAS5756_REG_DBE_EQ_HIGH_BIQUAD_1:
+ reg = PAGE_NR(45) + 32;
+ break;
+ case TAS5756_REG_DBE_EQ_HIGH_BIQUAD_2:
+ reg = PAGE_NR(45) + 52;
+ break;
+ case TAS5756_REG_DBE_EQ_HIGH_BIQUAD_3:
+ reg = PAGE_NR(45) + 72;
+ break;
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_1:
+ reg = PAGE_NR(46) + 16;
+ break;
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_2:
+ reg = PAGE_NR(46) + 36;
+ break;
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_3:
+ reg = PAGE_NR(46) + 56;
+ break;
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_4:
+ reg = PAGE_NR(46) + 76;
+ break;
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_5:
+ reg = PAGE_NR(46) + 96;
+ break;
+ case TAS5756M_REG_DBE_MIXING_HIGH:
+ reg = PAGE_NR(44) + 100;
+ break;
+ case TAS5756M_REG_DBE_MIXING_LOW:
+ reg = PAGE_NR(44) + 96;
+ break;
+ case TAS5756M_REG_DBE_SENSING_HIGH:
+ case TAS5756M_REG_DBE_SENSING_LOW:
+ reg = PAGE_NR(44) + 108;
+ break;
+ case TAS5756M_REG_DBE_WINDOW:
+ reg = PAGE_NR(44) + 104;
+ break;
+ default:
+ reg = -1;
+ break;
+ }
+ } else if (tas5756m->hybridflow == HYBRIDFLOW_7) {
+ switch (feature) {
+ case TAS5756_REG_FILTER_BIQUAD_1:
+ reg = PAGE_NR(45) + 48;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_2:
+ reg = PAGE_NR(45) + 76;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_3:
+ reg = PAGE_NR(45) + 96;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_4:
+ reg = PAGE_NR(45) + 116;
+ break;
+ case TAS5756_REG_FILTER_BIQUAD_5:
+ reg = PAGE_NR(46) + 16;
+ break;
+ default:
+ reg = -1;
+ break;
+ }
+ }
+
+ return CRAM_BUFFER_OFFSET(reg);
+}
+
+static int tas5756m_get_nb_coef(struct tas5756m_data *tas5756m,
+ enum hybridflow_features feature)
+{
+ int nb_coefs;
+
+ switch (feature) {
+ case TAS5756_REG_FILTER_BIQUAD_1:
+ case TAS5756_REG_FILTER_BIQUAD_2:
+ case TAS5756_REG_FILTER_BIQUAD_3:
+ case TAS5756_REG_FILTER_BIQUAD_4:
+ case TAS5756_REG_FILTER_BIQUAD_5:
+ case TAS5756_REG_FILTER_BIQUAD_6:
+ case TAS5756_REG_FILTER_BIQUAD_7:
+ case TAS5756_REG_FILTER_BIQUAD_8:
+ case TAS5756_REG_FILTER_BIQUAD_9:
+ case TAS5756_REG_FILTER_BIQUAD_10:
+
+ case TAS5756_REG_FILTER_BIQUAD_PRE_DBE_1:
+ case TAS5756_REG_FILTER_BIQUAD_PRE_DBE_2:
+ case TAS5756_REG_FILTER_BIQUAD_PRE_DBE_3:
+
+ case TAS5756_REG_FILTER_BIQUAD_POST_DBE_1:
+ case TAS5756_REG_FILTER_BIQUAD_POST_DBE_2:
+ case TAS5756_REG_FILTER_BIQUAD_POST_DBE_3:
+ case TAS5756_REG_FILTER_BIQUAD_POST_DBE_4:
+ case TAS5756_REG_FILTER_BIQUAD_POST_DBE_5:
+
+ case TAS5756_REG_FILTER_HIGH_BIQUAD_1:
+ case TAS5756_REG_FILTER_HIGH_BIQUAD_2:
+ case TAS5756_REG_FILTER_HIGH_BIQUAD_3:
+ case TAS5756_REG_FILTER_HIGH_BIQUAD_4:
+ case TAS5756_REG_FILTER_HIGH_BIQUAD_5:
+
+ case TAS5756_REG_FILTER_LOW_BIQUAD_1:
+ case TAS5756_REG_FILTER_LOW_BIQUAD_2:
+ case TAS5756_REG_FILTER_LOW_BIQUAD_3:
+ case TAS5756_REG_FILTER_LOW_BIQUAD_4:
+ case TAS5756_REG_FILTER_LOW_BIQUAD_5:
+
+ case TAS5756_REG_DBE_EQ_HIGH_BIQUAD_1:
+ case TAS5756_REG_DBE_EQ_HIGH_BIQUAD_2:
+ case TAS5756_REG_DBE_EQ_HIGH_BIQUAD_3:
+ case TAS5756_REG_DBE_EQ_HIGH_BIQUAD_4:
+ case TAS5756_REG_DBE_EQ_HIGH_BIQUAD_5:
+
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_1:
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_2:
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_3:
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_4:
+ case TAS5756_REG_DBE_EQ_LOW_BIQUAD_5:
+
+ case TAS5756M_REG_DBE_SENSING_HIGH:
+ case TAS5756M_REG_DBE_SENSING_LOW:
+ nb_coefs = CRAM_BIQUAD_NB_COEF;
+ break;
+
+ case TAS5756_REG_CHAN_MIXER:
+ case TAS5756_REG_CHAN_MIXER_HIGH:
+ case TAS5756_REG_CHAN_MIXER_LOW:
+ case TAS5756M_REG_PBE_BYPASS:
+ nb_coefs = 2;
+ break;
+
+ case TAS5756M_REG_PBE_EFFECT:
+ case TAS5756M_REG_PBE_HPF:
+ nb_coefs = CRAM_HPF_EFFECT_NB_COEF;
+ break;
+
+ case TAS5756_REG_ADD_DELAY:
+ nb_coefs = DELAY_SAMPLES_MAX;
+ break;
+
+ case TAS5756M_REG_DBE_WINDOW:
+ case TAS5756M_REG_DBE_MIXING_HIGH:
+ case TAS5756M_REG_DBE_MIXING_LOW:
+ case TAS5756M_REG_PBE_HARMONIC:
+ default:
+ nb_coefs = 1;
+ break;
+ }
+
+ return nb_coefs;
+}
+
+static int tas5756m_get_coef(struct tas5756m_data *tas5756m,
+ enum hybridflow_features feature)
+{
+ int reg = tas5756m_get_coef_reg_offset(tas5756m, feature);
+ int page_offset = ADDR_TO_PAGE_NR(reg);
+ int reg_offset = (reg & PAGE_ADDRESS_MASK) - CRAM_BUFFER_PAGE_OFFSET;
+
+ if (reg < 0)
+ return -EINVAL;
+
+ return page_offset * CRAM_NB_COEF_PER_PAGE +
+ reg_offset / CRAM_COEF_NB_REGS;
+}
+
+static int tas5756m_enable_adaptive_mode(struct tas5756m_data *tas5756m,
+ bool enable)
+{
+ struct device *dev = &tas5756m->tas5756m_client->dev;
+ int mask = CRAM_BUFFER_CRAM_ADAP_MODE_MASK;
+ int value = (enable ? 1 : 0) << 2;
+ int ret;
+
+ ret =
+ regmap_update_bits(tas5756m->regmap, CRAM_BUFFER_SWITCH_REG, mask,
+ value);
+ if (ret < 0)
+ dev_err(dev, "failed to enable adaptive mode: %d\n", ret);
+
+ return ret;
+}
+
+static bool tas5756m_adaptive_mode_is_enabled(struct tas5756m_data *tas5756m)
+{
+ struct device *dev = &tas5756m->tas5756m_client->dev;
+ int ret;
+ bool enabled = false;
+ int value;
+
+ ret = regmap_read(tas5756m->regmap, CRAM_BUFFER_SWITCH_REG, &value);
+ if (ret < 0)
+ dev_err(dev, "failed to read adaptive mode value: %d\n", ret);
+ else
+ enabled =
+ value & CRAM_BUFFER_CRAM_ADAP_MODE_MASK ? true : false;
+
+ return enabled;
+}
+
+static enum cram_buffer tas5756m_get_current_cram_buffer(struct tas5756m_data
+ *tas5756m)
+{
+ struct device *dev = &tas5756m->tas5756m_client->dev;
+ enum cram_buffer buffer = CRAM_BUFFER_A;
+ int ret, used_buffer;
+ int mask = CRAM_BUFFER_CRAM_BUFFER_USED_NON_ADAPT_MASK;
+
+ ret =
+ regmap_read(tas5756m->regmap, CRAM_BUFFER_SWITCH_REG, &used_buffer);
+ if (ret < 0) {
+ dev_err(dev,
+ "failed to read which buffer is currently used: %d\n",
+ ret);
+ return buffer;
+ }
+
+ if (tas5756m_adaptive_mode_is_enabled(tas5756m))
+ mask = CRAM_BUFFER_CRAM_BUFFER_USED_MASK;
+
+ if ((used_buffer & mask) == 0)
+ buffer = CRAM_BUFFER_A;
+ else
+ buffer = CRAM_BUFFER_B;
+
+ return buffer;
+}
+
+static int tas5756m_cram_to_virt_buffer(struct tas5756m_data *tas5756m,
+ enum cram_buffer src)
+{
+ struct regmap *regmap = tas5756m->regmap;
+ struct device *dev = &tas5756m->tas5756m_client->dev;
+ const int SRC_PAGE =
+ src ==
+ CRAM_BUFFER_A ? CRAM_FIRST_PAGE : CRAM_FIRST_PAGE_SECOND_BANK;
+ const int NB_PAGES = CRAM_BUFFER_NB_PAGES;
+ const int OFFSET = CRAM_BUFFER_PAGE_OFFSET;
+ int i, ret;
+
+ for (i = 0; i < NB_PAGES; i++) {
+ ret = regmap_bulk_read(regmap, PAGE_NR(SRC_PAGE + i) + OFFSET,
+ tas5756m->cram_buffer +
+ CRAM_NB_COEF_PER_PAGE * i,
+ CRAM_REGS_PER_PAGES);
+ if (ret < 0) {
+ dev_err(dev,
+ "failed to copy from CRAM page %d registers to virtual buffer: %d\n",
+ SRC_PAGE + i, ret);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static int tas5756m_virt_buffer_to_cram(struct tas5756m_data *tas5756m,
+ enum cram_buffer src)
+{
+ struct regmap *regmap = tas5756m->regmap;
+ struct device *dev = &tas5756m->tas5756m_client->dev;
+ const int SRC_PAGE =
+ src ==
+ CRAM_BUFFER_A ? CRAM_FIRST_PAGE : CRAM_FIRST_PAGE_SECOND_BANK;
+ const int NB_PAGES = CRAM_BUFFER_NB_PAGES;
+ const int OFFSET = CRAM_BUFFER_PAGE_OFFSET;
+ int i, ret;
+
+ for (i = 0; i < NB_PAGES; i++) {
+ ret = regmap_bulk_write(regmap, PAGE_NR(SRC_PAGE + i) + OFFSET,
+ tas5756m->cram_buffer +
+ CRAM_NB_COEF_PER_PAGE * i,
+ CRAM_REGS_PER_PAGES);
+ if (ret < 0) {
+ dev_err(dev,
+ "failed to copy from virt buffer to CRAM page %d registers: %d\n",
+ SRC_PAGE + i, ret);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static int tas5756m_info_cram_buffer(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+
+ return 0;
+}
+
+static int tas5756m_get_current_buffer(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ ucontrol->value.integer.value[0] = tas5756m->buffer;
+ return 0;
+}
+
+static int tas5756m_apply_config(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+ struct device *dev = &tas5756m->tas5756m_client->dev;
+
+ const int MAX_TRY_CHECK_STATUS = 10;
+ int ret = 0, try = 0;
+ unsigned int switch_status = 1;
+
+ /* Without adaptive mode, the chip must be suspended
+ * before writing CRAM buffer then enable it again.
+ */
+ if (!tas5756m_adaptive_mode_is_enabled(tas5756m)) {
+ tas5756m_shutdown(tas5756m);
+ tas5756m_virt_buffer_to_cram(tas5756m, tas5756m->buffer);
+ tas5756m_resume(tas5756m);
+ } else {
+ tas5756m_virt_buffer_to_cram(tas5756m, CRAM_BUFFER_A);
+ tas5756m_virt_buffer_to_cram(tas5756m, CRAM_BUFFER_B);
+
+ ret =
+ regmap_update_bits(tas5756m->regmap, CRAM_BUFFER_SWITCH_REG,
+ CRAM_BUFFER_SWITCH_MASK, switch_status);
+ if (ret < 0) {
+ dev_err(dev,
+ "failed to write CRAM switch register: %d\n",
+ ret);
+ return ret;
+ }
+
+ /* CRAM switching is done on next audio frame.
+ * When the chip is running,
+ * the driver is able to check if the operation succeed.
+ * Otherwise, this checking step is skipped.
+ */
+ if (tas5756m_is_running(tas5756m)) {
+ while (switch_status && try < MAX_TRY_CHECK_STATUS) {
+ ret =
+ regmap_read(tas5756m->regmap,
+ CRAM_BUFFER_SWITCH_REG,
+ &switch_status);
+ if (ret < 0) {
+ dev_err(dev,
+ "failed to read CRAM switch register: %d\n",
+ ret);
+ return ret;
+ }
+
+ switch_status &= CRAM_BUFFER_SWITCH_MASK;
+ try++;
+ usleep_range(100, 200);
+ }
+
+ if (switch_status) {
+ dev_err(dev,
+ "failed to switch CRAM: %d tries.\n",
+ try);
+ return ret;
+ }
+ }
+ }
+
+ tas5756m->buffer = tas5756m_get_current_cram_buffer(tas5756m);
+ return ret;
+}
+
+static int tas5756m_coef_get(struct tas5756m_data *tas5756m, int coef,
+ long *val)
+{
+ if (coef < 0 || coef >= CRAM_BUFFER_SIZE)
+ return -EINVAL;
+
+ *val = be32_to_cpu(tas5756m->cram_buffer[coef]) >> 8;
+ return 0;
+}
+
+static int tas5756m_coef_set(struct tas5756m_data *tas5756m, int coef, long val)
+{
+ if (coef < 0 || coef >= CRAM_BUFFER_SIZE)
+ return -EINVAL;
+
+ tas5756m->cram_buffer[coef] = cpu_to_be32(val << 8);
+ return 0;
+}
+
+static int tas5756m_coef_set_mask(struct tas5756m_data *tas5756m, size_t coef,
+ int mask, long val)
+{
+ long old_val;
+ int ret;
+
+ ret = tas5756m_coef_get(tas5756m, coef, &old_val);
+ if (ret < 0)
+ return ret;
+
+ return tas5756m_coef_set(tas5756m, coef,
+ (val & mask) | (old_val & ~mask));
+}
+
+/*
+ * Integer array controls for setting biquad, mixer, DRC coefficients.
+ * According to the datasheet each coefficient is effectively 24 bits,
+ * i.e. stored as 32bits, where bits [31:24] are ignored.
+ * TI's TAS57xx Graphical Development Environment tool however produces
+ * coefficients with more than 24 bits. For this reason we allow values
+ * in the full 32-bits reange.
+ * The coefficients are ordered as given in the TAS575x data sheet:
+ * b0, b1, b2, a1, a2.
+ * To compute valid values:
+ * http://www.ti.com/lit/an/slaa447/slaa447.pdf
+ * In this document, Peak EQ is miscomputed:
+ * https://e2e.ti.com/support/audio/f/6/p/656270/2415402
+ */
+static int tas5756m_coefficient_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ int nb_coefs = tas5756m_get_nb_coef(tas5756m, kcontrol->private_value);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = nb_coefs;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 0x00ffffff;
+
+ return 0;
+}
+
+static int tas5756m_coefficient_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ int coef = tas5756m_get_coef(tas5756m, kcontrol->private_value);
+ int nb_coefs = tas5756m_get_nb_coef(tas5756m, kcontrol->private_value);
+ int i, ret;
+
+ for (i = 0; i < nb_coefs; i++) {
+ ret = tas5756m_coef_get(tas5756m, coef + i,
+ &(ucontrol->value.integer.value[i]));
+ if (ret < 0)
+ return ret;
+ }
+
+ return i;
+}
+
+static int tas5756m_coefficient_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ int coef = tas5756m_get_coef(tas5756m, kcontrol->private_value);
+ int nb_coefs = tas5756m_get_nb_coef(tas5756m, kcontrol->private_value);
+ int i, ret;
+
+ for (i = 0; i < nb_coefs; i++) {
+ ret = tas5756m_coef_set(tas5756m, coef + i,
+ ucontrol->value.integer.value[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ return i;
+}
+
+#define CRAM_COEFS(xname, feature) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = tas5756m_coefficient_info, \
+ .get = tas5756m_coefficient_get,\
+ .put = tas5756m_coefficient_put, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .private_value = feature \
+}
+
+const char *mixer_mode_text[] = {
+ "Right",
+ "Left",
+ "Mono",
+};
+
+static int tas5756m_channel_mixer_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ return snd_ctl_enum_info(uinfo, 1, CHANNEL_MIXER_MAX, mixer_mode_text);
+}
+
+static int tas5756m_channel_mixer_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ ucontrol->value.enumerated.item[0] = tas5756m->channel;
+ return 1;
+}
+
+static int tas5756m_channel_mixer_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ const int default_gain_one = 0x7FFFFF, default_gain_both = 0x400000;
+ long gains[2];
+ int coef = tas5756m_get_coef(tas5756m, kcontrol->private_value);
+ int nb_coefs = tas5756m_get_nb_coef(tas5756m, kcontrol->private_value);
+ int i, ret;
+
+ switch (ucontrol->value.enumerated.item[0]) {
+ case CHANNEL_MIXER_MONO:
+ gains[0] = default_gain_both;
+ gains[1] = default_gain_both;
+ break;
+ case CHANNEL_MIXER_LEFT:
+ gains[0] = default_gain_one;
+ gains[1] = 0;
+ break;
+ case CHANNEL_MIXER_RIGHT:
+ gains[0] = 0;
+ gains[1] = default_gain_one;
+ break;
+ default:
+ gains[0] = 0;
+ gains[1] = 0;
+ break;
+ }
+
+ tas5756m_route_channels(tas5756m, ucontrol->value.enumerated.item[0]);
+
+ for (i = 0; i < nb_coefs; i++) {
+ ret = tas5756m_coef_set_mask(tas5756m, coef + i,
+ MIXER_GAIN_MASK_MASK, gains[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 1;
+}
+
+#define CHANNEL_MIXER_COEFS(xname, feature) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = tas5756m_channel_mixer_info, \
+ .get = tas5756m_channel_mixer_get,\
+ .put = tas5756m_channel_mixer_put, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .private_value = feature \
+}
+
+static int tas5756m_channel_phase_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ int nb_coefs = tas5756m_get_nb_coef(tas5756m, kcontrol->private_value);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = nb_coefs;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+
+ return 0;
+}
+
+static int tas5756m_channel_phase_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ long phases[2];
+ int coef = tas5756m_get_coef(tas5756m, kcontrol->private_value);
+ int nb_coefs = tas5756m_get_nb_coef(tas5756m, kcontrol->private_value);
+ int i, ret;
+
+ for (i = 0; i < nb_coefs; i++) {
+ ret = tas5756m_coef_get(tas5756m, coef + i, &(phases[i]));
+ if (ret < 0)
+ return ret;
+
+ phases[i] = phases[i] & MIXER_PHASE_INVERSION_MASK;
+ }
+
+ if (phases[0] || phases[1])
+ ucontrol->value.integer.value[0] = PHASE_INVERSION;
+ else
+ ucontrol->value.integer.value[0] = PHASE_NO_INVERSION;
+
+ return 1;
+}
+
+static int tas5756m_channel_phase_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ int phase =
+ ucontrol->value.integer.value[0] ==
+ 1 ? MIXER_PHASE_INVERSION_MASK : 0;
+ int coef = tas5756m_get_coef(tas5756m, kcontrol->private_value);
+ int nb_coefs = tas5756m_get_nb_coef(tas5756m, kcontrol->private_value);
+ int i, ret;
+
+ for (i = 0; i < nb_coefs; i++) {
+ ret = tas5756m_coef_set_mask(tas5756m, coef + i,
+ MIXER_PHASE_INVERSION_MASK, phase);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 1;
+}
+
+#define CHANNEL_PHASE_COEFS(xname, feature) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = tas5756m_channel_phase_info, \
+ .get = tas5756m_channel_phase_get,\
+ .put = tas5756m_channel_phase_put, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .private_value = feature \
+}
+
+static int tas5756m_bypass_coef_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ int nb_coefs = tas5756m_get_nb_coef(tas5756m, kcontrol->private_value);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = nb_coefs;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+
+ return 0;
+}
+
+static int tas5756m_bypass_coef_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ int coef = tas5756m_get_coef(tas5756m, kcontrol->private_value);
+ int nb_coefs = tas5756m_get_nb_coef(tas5756m, kcontrol->private_value);
+ long value[2];
+ int i, ret;
+
+ for (i = 0; i < nb_coefs; i++) {
+ ret = tas5756m_coef_get(tas5756m, coef + i, &value[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (value[0] == 0 && value[1] == COEF_ENABLE_FEATURE_VALUE)
+ ucontrol->value.integer.value[0] = 1;
+ else
+ ucontrol->value.integer.value[0] = 0;
+
+ return 1;
+}
+
+static int tas5756m_bypass_coef_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ int coef = tas5756m_get_coef(tas5756m, kcontrol->private_value);
+ int nb_coefs = tas5756m_get_nb_coef(tas5756m, kcontrol->private_value);
+ long value[2];
+ int i, ret;
+
+ if (ucontrol->value.integer.value[0] == 1) {
+ value[0] = 0;
+ value[1] = COEF_ENABLE_FEATURE_VALUE;
+ } else {
+ value[0] = COEF_ENABLE_FEATURE_VALUE;
+ value[1] = 0;
+ }
+
+ for (i = 0; i < nb_coefs; i++) {
+ ret = tas5756m_coef_set(tas5756m, coef + i, value[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 1;
+}
+
+#define BYPASS_COEFS(xname, feature) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = tas5756m_bypass_coef_info, \
+ .get = tas5756m_bypass_coef_get,\
+ .put = tas5756m_bypass_coef_put, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .private_value = feature \
+}
+
+const char *delay_samples_text[] = {
+ "0",
+ "4",
+ "8",
+ "12",
+ "16",
+};
+
+static int tas5756m_delay_samples_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ return snd_ctl_enum_info(uinfo, 1, DELAY_SAMPLES_MAX,
+ delay_samples_text);
+}
+
+static int tas5756m_delay_samples_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ int coef = tas5756m_get_coef(tas5756m, kcontrol->private_value);
+ int nb_coefs = tas5756m_get_nb_coef(tas5756m, kcontrol->private_value);
+ long value;
+ enum delay_samples delay = DELAY_SAMPLES_0;
+ int i, ret;
+
+ for (i = 0; i < nb_coefs; i++) {
+ ret = tas5756m_coef_get(tas5756m, coef + i, &value);
+ if (ret < 0)
+ return ret;
+
+ if (value != 0)
+ delay = DELAY_SAMPLES_MAX - i - 1;
+ }
+
+ ucontrol->value.enumerated.item[0] = delay;
+ return 1;
+}
+
+static int tas5756m_delay_samples_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ const int default_value = 0x7FFFFF;
+ int coef = tas5756m_get_coef(tas5756m, kcontrol->private_value);
+ int nb_coefs = tas5756m_get_nb_coef(tas5756m, kcontrol->private_value);
+ int coef_index, value;
+ int i, ret;
+
+ coef_index = nb_coefs - ucontrol->value.enumerated.item[0] - 1;
+
+ for (i = 0; i < nb_coefs; i++) {
+ value = coef_index == i ? default_value : 0;
+ ret = tas5756m_coef_set(tas5756m, coef + i, value);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 1;
+}
+
+#define DELAY_SAMPLES_COEFS(xname, feature) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = tas5756m_delay_samples_info, \
+ .get = tas5756m_delay_samples_get,\
+ .put = tas5756m_delay_samples_put, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .private_value = feature \
+}
+
+#define APPLY_CONFIG \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = "Apply config", \
+ .put = tas5756m_apply_config, \
+ .get = tas5756m_get_current_buffer, \
+ .info = tas5756m_info_cram_buffer, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+}
+
+static const char *const tas5756m_supply_names[] = {
+ "dvdd", /* Digital power supply. Connect to 3.3-V supply. */
+ "pvdd", /* Class-D amp and analog power supply (connected). */
+};
+
+static int tas5756m_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ u16 iface_reg;
+ int ret;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ switch (params_rate(params)) {
+ case 8000:
+ case 11025:
+ case 16000:
+ case 22050:
+ case 32000:
+ case 44100:
+ case 48000:
+ case 96000:
+ dev_info(component->dev, "sample rate: %u\n",
+ params_rate(params));
+ break;
+
+ default:
+ dev_err(component->dev, "invalid sample rate: %u\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+
+ switch (params_width(params)) {
+ case 16:
+ iface_reg = RES_16BIT;
+ break;
+ case 20:
+ iface_reg = RES_20BIT;
+ break;
+ case 24:
+ iface_reg = RES_24BIT;
+ break;
+ case 32:
+ default:
+ iface_reg = RES_32BIT;
+ break;
+ }
+
+ dev_info(component->dev, "bit depth: %u\n",
+ params_width(params));
+
+ ret =
+ snd_soc_component_write(component, TAS5756M_I2S_CONFIG,
+ iface_reg);
+ if (ret < 0) {
+ dev_err(component->dev, "error setting width: %d\n",
+ ret);
+ return ret;
+ }
+
+ }
+
+ return 0;
+}
+
+static int tas5756m_pcm_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ int ret = 0;
+ int val;
+ unsigned int rate = substream->runtime->rate; /* rate in Hz */
+ unsigned int channels = substream->runtime->channels;
+ unsigned int frame_bits = substream->runtime->frame_bits;
+
+ dev_info(component->dev,
+ "runtime data: sample rate: %u - channels: %u - bitrate: %u\n",
+ rate, channels, frame_bits);
+
+ ret = snd_soc_component_read(component, TAS5756M_PDN_STBY, &val);
+ if (!ret) {
+ val &= PDN_STBY_MASK;
+ if (val) {
+ dev_info(component->dev,
+ "activating codec - PDN/STBY: %u\n", val);
+ ret =
+ snd_soc_component_write(component,
+ TAS5756M_PDN_STBY, 0x00);
+ }
+ }
+
+ return ret;
+}
+
+static void tas5756m_shutdown_dai(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct tas5756m_data *tas5756m_data =
+ snd_soc_component_get_drvdata(component);
+
+ tas5756m_shutdown(tas5756m_data);
+}
+
+static void tas5756m_fault_check_work(struct work_struct *work)
+{
+ struct tas5756m_data *tas5756m =
+ container_of(work, struct tas5756m_data, fault_check_work.work);
+ struct i2c_client *tas5756m_client = tas5756m->tas5756m_client;
+ struct device *dev = &tas5756m_client->dev;
+
+ unsigned int curr_fault;
+ int ret;
+
+ ret = regmap_read(tas5756m->regmap, TAS5756M_SHORT_DETECT, &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 &= (SHORT_BUSY_MASK | SHORT_MASK);
+
+ /* Only flag errors once for a given occurrence. This is needed as
+ * the TAS5756M 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 && !tas5756m->last_fault)
+ dev_crit(dev, "error: short detected\n");
+
+ /* Store current fault value so we can detect any changes next time */
+ tas5756m->last_fault = curr_fault;
+
+out:
+ schedule_delayed_work(&tas5756m->fault_check_work,
+ msecs_to_jiffies(TAS5756M_FAULT_CHECK_INTERVAL));
+}
+
+static int tas5756m_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_component *component = dai->component;
+ u16 iface_reg;
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ tas5756m->fmt = fmt;
+
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) {
+ dev_info(component->dev, "master mode not supported\n");
+ return -EINVAL;
+ }
+
+ /* Interface format. Always normal I²S.
+ * Also assumed when setting resolution (bits).
+ */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case (SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF):
+ iface_reg = FMT_RTJ;
+ dev_info(component->dev, "fmt: rightj\n");
+ break;
+ case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF):
+ case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF):
+ dev_info(component->dev, "fmt: leftj\n");
+ iface_reg = FMT_LTJ;
+ break;
+ default:
+ case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF):
+ case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF):
+ dev_info(component->dev, "fmt: i2s / dsp_a\n");
+ iface_reg = FMT_I2S;
+ break;
+ }
+
+ return snd_soc_component_write(component, TAS5756M_I2S_CONFIG,
+ (iface_reg << 4));
+}
+
+static int tas5756m_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_component *component = dai->component;
+ int ret;
+ int reg;
+
+ ret = snd_soc_component_read(component, TAS5756M_MUTE_L_R, ®);
+ if (ret) {
+ dev_err(component->dev, "unable to read mute status: %d\n",
+ ret);
+ return ret;
+ }
+
+ if (mute) {
+ dev_info(component->dev, "mute requested\n");
+ reg |= MUTE_ALL_MASK;
+ } else {
+ dev_info(component->dev, "unmute requested\n");
+ reg &= ~(MUTE_ALL_MASK);
+ }
+
+ return snd_soc_component_write(component, TAS5756M_MUTE_L_R,
+ (reg & MUTE_ALL_MASK));
+}
+
+static int tas5756m_codec_probe(struct snd_soc_component *component)
+{
+ int ret;
+ unsigned int dac_power_state;
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ tas5756m->component = component;
+
+#ifdef CONFIG_OF
+ tas5756m->gpio_mute =
+ of_get_named_gpio(component->dev->of_node, "mute-gpios", 0);
+#endif
+
+ /* Enable the amplifier */
+ if (gpio_is_valid(tas5756m->gpio_mute)) {
+ ret =
+ gpio_request_one(tas5756m->gpio_mute, GPIOF_OUT_INIT_LOW,
+ "TAS5756M mute GPIO");
+
+ if (ret < 0) {
+ dev_warn(component->dev,
+ "failed to request mute gpio: %d\n", ret);
+ } else {
+ gpio_set_value(tas5756m->gpio_mute, 1);
+ dev_info(component->dev, "unmuted\n");
+ }
+ }
+
+ /* Set L / R volume to same value (are always differing on first boot
+ * when there're no saved state and messes up the mixer element).
+ */
+ ret =
+ regmap_write(tas5756m->regmap, TAS5756M_RIGHT_DVOL,
+ VOL_CH1_2_DEFAULT);
+ if (ret < 0)
+ dev_warn(component->dev, "failed to set volume: %d\n", ret);
+
+ ret =
+ regmap_write(tas5756m->regmap, TAS5756M_LEFT_DVOL,
+ VOL_CH1_2_DEFAULT);
+ if (ret < 0)
+ dev_warn(component->dev, "failed to set volume: %d\n", ret);
+
+ ret =
+ regmap_update_bits(tas5756m->regmap, TAS5756M_DAC_PWR_STA,
+ DAC_ENABLED_MASK, DAC_ENABLED_MASK);
+
+ dev_info(component->dev, "checking DAC booting register\n");
+ ret =
+ regmap_read(tas5756m->regmap, TAS5756M_DAC_PWR_STA,
+ &dac_power_state);
+ if (ret < 0) {
+ dev_err(component->dev, "failed to read DAC power state: %d\n",
+ ret);
+ goto probe_fail;
+ }
+
+ if (!dac_power_state) {
+ dev_info(component->dev, "not ready. retrying..");
+
+ ret =
+ regmap_read(tas5756m->regmap, TAS5756M_DAC_PWR_STA,
+ &dac_power_state);
+ if (ret < 0) {
+ dev_err(component->dev,
+ "failed to read DAC power state: %d\n", ret);
+ goto probe_fail;
+ }
+ }
+
+ /* Only D7 matters, lower nibble used for RO status */
+ dac_power_state &= 0x80;
+
+ if (dac_power_state) {
+ dev_info(component->dev, "DAC enabled\n");
+ } else {
+ dev_err(component->dev, "DAC disabled\n");
+ ret = -ENODEV;
+ goto probe_fail;
+ }
+
+ /* Set device to mute */
+ dev_info(component->dev, "muting TAS5756M\n");
+
+ ret =
+ snd_soc_component_write(component, TAS5756M_MUTE_L_R,
+ MUTE_ALL_MASK);
+ if (ret < 0) {
+ dev_info(component->dev, "failed to mute\n");
+ goto error_snd_soc_update_bits;
+ }
+
+ dev_info(component->dev, "entering standby mode\n");
+ ret = snd_soc_component_write(component, TAS5756M_PDN_STBY, STBY_MASK);
+ if (ret < 0)
+ goto error_snd_soc_update_bits;
+
+ INIT_DELAYED_WORK(&tas5756m->fault_check_work,
+ tas5756m_fault_check_work);
+ return 0;
+
+error_snd_soc_update_bits:
+ dev_err(component->dev, "error configuring device registers: %d\n",
+ ret);
+
+probe_fail:
+ dev_err(component->dev, "probe failed\n");
+ regulator_bulk_disable(ARRAY_SIZE(tas5756m->supplies),
+ tas5756m->supplies);
+ return ret;
+}
+
+static void tas5756m_codec_remove(struct snd_soc_component *component)
+{
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+ int ret;
+
+ cancel_delayed_work_sync(&tas5756m->fault_check_work);
+
+ ret =
+ regulator_bulk_disable(ARRAY_SIZE(tas5756m->supplies),
+ tas5756m->supplies);
+ if (ret < 0)
+ dev_err(component->dev, "failed to disable supplies: %d\n",
+ ret);
+}
+
+static int tas5756m_suspend(struct snd_soc_component *component)
+{
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+ int ret;
+
+ regcache_cache_only(tas5756m->regmap, true);
+ regcache_mark_dirty(tas5756m->regmap);
+
+ ret =
+ regulator_bulk_disable(ARRAY_SIZE(tas5756m->supplies),
+ tas5756m->supplies);
+ if (ret < 0)
+ dev_err(component->dev, "failed to disable regulators %d\n",
+ ret);
+
+ return ret;
+}
+
+static int tas5756m_resume_codec(struct snd_soc_component *component)
+{
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ return tas5756m_resume(tas5756m);
+}
+
+static const struct regmap_range_cfg tas5756m_regmap_pages[] = {
+ {
+ .selector_reg = TAS5756M_PAGE_SEL,
+ .selector_mask = 0xff,
+ .window_start = 0,
+ .window_len = 128,
+ .range_min = 0,
+ .range_max = TAS5756M_MAX_REG,
+ },
+};
+
+static const struct regmap_config tas5756m_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .read_flag_mask = 0x80,
+ .write_flag_mask = 0x80,
+
+ .reg_defaults = tas5756m_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(tas5756m_reg_defaults),
+ .writeable_reg = tas5756m_writeable,
+ .volatile_reg = tas5756m_volatile,
+ .max_register = TAS5756M_MAX_REG,
+ .ranges = tas5756m_regmap_pages,
+ .num_ranges = ARRAY_SIZE(tas5756m_regmap_pages),
+ .cache_type = REGCACHE_RBTREE,
+};
+
+/* Right/Left Digital volume -103 to 24 dB */
+static const DECLARE_TLV_DB_LINEAR(out_volume_tlv, -10350, 25);
+
+#define KCONTROLS_COMMON \
+ SOC_SINGLE("Bass Boost", TAS5756M_ANLG_GAIN, 0, 0x01, 1), \
+ SOC_DOUBLE_R_RANGE_TLV("Volume", TAS5756M_LEFT_DVOL, \
+ TAS5756M_RIGHT_DVOL, 0x00, 0x00, 0xff, 1, \
+ out_volume_tlv), \
+ APPLY_CONFIG
+
+#define KCONTROLS_DBE_COMMON \
+ CRAM_COEFS("Filter DBE biquad high 1", TAS5756_REG_DBE_EQ_HIGH_BIQUAD_1), \
+ CRAM_COEFS("Filter DBE biquad high 2", TAS5756_REG_DBE_EQ_HIGH_BIQUAD_2), \
+ CRAM_COEFS("Filter DBE biquad low 1", TAS5756_REG_DBE_EQ_LOW_BIQUAD_1), \
+ CRAM_COEFS("Filter DBE biquad low 2", TAS5756_REG_DBE_EQ_LOW_BIQUAD_2), \
+ CRAM_COEFS("Filter DBE biquad low 3", TAS5756_REG_DBE_EQ_LOW_BIQUAD_3), \
+ \
+ /* First order lowpass and highpass only => b2 = 0 and a2 = 0*/ \
+ CRAM_COEFS("Filter DBE biquad energy lowpass", TAS5756M_REG_DBE_SENSING_HIGH), \
+ CRAM_COEFS("Filter DBE biquad energy highpass", TAS5756M_REG_DBE_SENSING_LOW), \
+ \
+ CRAM_COEFS("DBE energy window", TAS5756M_REG_DBE_WINDOW), \
+ CRAM_COEFS("DBE mixing high threshold", TAS5756M_REG_DBE_MIXING_HIGH), \
+ CRAM_COEFS("DBE mixing low threshold", TAS5756M_REG_DBE_MIXING_LOW)
+
+#define KCONTROLS_PBE_COMMON \
+ BYPASS_COEFS("PBE bypass", TAS5756M_REG_PBE_BYPASS), \
+ CRAM_COEFS("PBE harmonic", TAS5756M_REG_PBE_HARMONIC), \
+ CRAM_COEFS("PBE HPF and effect", TAS5756M_REG_PBE_HPF)
+
+static const struct snd_kcontrol_new tas5756m_snd_controls_no_hf[] = {
+ KCONTROLS_COMMON,
+};
+
+static const struct snd_kcontrol_new tas5756m_snd_controls_hf3[] = {
+ KCONTROLS_COMMON,
+ KCONTROLS_DBE_COMMON,
+ KCONTROLS_PBE_COMMON,
+
+ CRAM_COEFS("Filter high biquad 1", TAS5756_REG_FILTER_HIGH_BIQUAD_1),
+ CRAM_COEFS("Filter high biquad 2", TAS5756_REG_FILTER_HIGH_BIQUAD_2),
+ CRAM_COEFS("Filter high biquad 3", TAS5756_REG_FILTER_HIGH_BIQUAD_3),
+ CRAM_COEFS("Filter high biquad 4", TAS5756_REG_FILTER_HIGH_BIQUAD_4),
+ CRAM_COEFS("Filter high biquad 5", TAS5756_REG_FILTER_HIGH_BIQUAD_5),
+
+ CRAM_COEFS("Filter low biquad 1", TAS5756_REG_FILTER_LOW_BIQUAD_1),
+ CRAM_COEFS("Filter low biquad 2", TAS5756_REG_FILTER_LOW_BIQUAD_2),
+ CRAM_COEFS("Filter low biquad 3", TAS5756_REG_FILTER_LOW_BIQUAD_3),
+ CRAM_COEFS("Filter low biquad 4", TAS5756_REG_FILTER_LOW_BIQUAD_4),
+ CRAM_COEFS("Filter low biquad 5", TAS5756_REG_FILTER_LOW_BIQUAD_5),
+
+ CHANNEL_MIXER_COEFS("Channel mixer high", TAS5756_REG_CHAN_MIXER_HIGH),
+ CHANNEL_MIXER_COEFS("Channel mixer low", TAS5756_REG_CHAN_MIXER_LOW),
+
+ CHANNEL_PHASE_COEFS("Phase inversion mixer high",
+ TAS5756_REG_CHAN_MIXER_HIGH),
+ CHANNEL_PHASE_COEFS("Phase inversion mixer low",
+ TAS5756_REG_CHAN_MIXER_LOW),
+
+ DELAY_SAMPLES_COEFS("Delay samples high/mid", TAS5756_REG_ADD_DELAY),
+};
+
+static const struct snd_kcontrol_new tas5756m_snd_controls_hf4[] = {
+ KCONTROLS_COMMON,
+ KCONTROLS_DBE_COMMON,
+ KCONTROLS_PBE_COMMON,
+
+ CRAM_COEFS("Filter pre DBE biquad 1",
+ TAS5756_REG_FILTER_BIQUAD_PRE_DBE_1),
+ CRAM_COEFS("Filter pre DBE biquad 2",
+ TAS5756_REG_FILTER_BIQUAD_PRE_DBE_2),
+ CRAM_COEFS("Filter pre DBE biquad 3",
+ TAS5756_REG_FILTER_BIQUAD_PRE_DBE_3),
+
+ CRAM_COEFS("Filter post DBE biquad 1",
+ TAS5756_REG_FILTER_BIQUAD_POST_DBE_1),
+ CRAM_COEFS("Filter post DBE biquad 2",
+ TAS5756_REG_FILTER_BIQUAD_POST_DBE_2),
+ CRAM_COEFS("Filter post DBE biquad 3",
+ TAS5756_REG_FILTER_BIQUAD_POST_DBE_3),
+ CRAM_COEFS("Filter post DBE biquad 4",
+ TAS5756_REG_FILTER_BIQUAD_POST_DBE_4),
+ CRAM_COEFS("Filter post DBE biquad 5",
+ TAS5756_REG_FILTER_BIQUAD_POST_DBE_5),
+
+ CRAM_COEFS("Filter DBE biquad high 3",
+ TAS5756_REG_DBE_EQ_HIGH_BIQUAD_3),
+ CRAM_COEFS("Filter DBE biquad high 4",
+ TAS5756_REG_DBE_EQ_HIGH_BIQUAD_4),
+
+ CRAM_COEFS("Filter DBE biquad low 4", TAS5756_REG_DBE_EQ_LOW_BIQUAD_4),
+
+ CHANNEL_MIXER_COEFS("Channel mixer", TAS5756_REG_CHAN_MIXER),
+ CHANNEL_PHASE_COEFS("Phase inversion mixer", TAS5756_REG_CHAN_MIXER),
+};
+
+static const struct snd_kcontrol_new tas5756m_snd_controls_hf6[] = {
+ KCONTROLS_COMMON,
+ KCONTROLS_DBE_COMMON,
+ KCONTROLS_PBE_COMMON,
+
+ CRAM_COEFS("Filter biquad 1", TAS5756_REG_FILTER_BIQUAD_1),
+ CRAM_COEFS("Filter biquad 2", TAS5756_REG_FILTER_BIQUAD_2),
+ CRAM_COEFS("Filter biquad 3", TAS5756_REG_FILTER_BIQUAD_3),
+ CRAM_COEFS("Filter biquad 4", TAS5756_REG_FILTER_BIQUAD_4),
+ CRAM_COEFS("Filter biquad 5", TAS5756_REG_FILTER_BIQUAD_5),
+ CRAM_COEFS("Filter biquad 6", TAS5756_REG_FILTER_BIQUAD_6),
+ CRAM_COEFS("Filter biquad 7", TAS5756_REG_FILTER_BIQUAD_7),
+ CRAM_COEFS("Filter biquad 8", TAS5756_REG_FILTER_BIQUAD_8),
+ CRAM_COEFS("Filter biquad 9", TAS5756_REG_FILTER_BIQUAD_9),
+ CRAM_COEFS("Filter biquad 10", TAS5756_REG_FILTER_BIQUAD_10),
+
+ CRAM_COEFS("Filter DBE biquad high 3",
+ TAS5756_REG_DBE_EQ_HIGH_BIQUAD_3),
+
+ CRAM_COEFS("Filter DBE biquad low 4", TAS5756_REG_DBE_EQ_LOW_BIQUAD_4),
+ CRAM_COEFS("Filter DBE biquad low 5", TAS5756_REG_DBE_EQ_LOW_BIQUAD_5),
+
+ CHANNEL_MIXER_COEFS("Channel mixer", TAS5756_REG_CHAN_MIXER),
+};
+
+static const struct snd_kcontrol_new tas5756m_snd_controls_hf7[] = {
+ KCONTROLS_COMMON,
+
+ CHANNEL_MIXER_COEFS("Channel mixer", TAS5756_REG_CHAN_MIXER),
+
+ CRAM_COEFS("Filter biquad 1", TAS5756_REG_FILTER_BIQUAD_1),
+ CRAM_COEFS("Filter biquad 2", TAS5756_REG_FILTER_BIQUAD_2),
+ CRAM_COEFS("Filter biquad 3", TAS5756_REG_FILTER_BIQUAD_3),
+ CRAM_COEFS("Filter biquad 4", TAS5756_REG_FILTER_BIQUAD_4),
+ CRAM_COEFS("Filter biquad 5", TAS5756_REG_FILTER_BIQUAD_5),
+};
+
+static const struct snd_soc_dapm_widget tas5756mw_dapm_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("DAC IN", "Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_OUTPUT("OUT"),
+};
+
+static const struct snd_soc_dapm_route tas5756mw_audio_map[] = {
+ { "DAC", NULL, "DAC IN" },
+ { "OUT", NULL, "DAC" },
+};
+
+static const struct snd_soc_dapm_widget tas5756m_dapm_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("DAC IN", "Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_OUTPUT("OUT"),
+};
+
+static const struct snd_soc_dapm_route tas5756m_audio_map[] = {
+ { "DAC", NULL, "DAC IN" },
+ { "OUT", NULL, "DAC" },
+};
+
+static struct snd_soc_component_driver soc_component_dev_tas5756m = {
+ .probe = tas5756m_codec_probe,
+ .remove = tas5756m_codec_remove,
+ .suspend = tas5756m_suspend,
+ .resume = tas5756m_resume_codec,
+};
+
+#define TAS5756M_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 \
+ | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 \
+ | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 \
+ | SNDRV_PCM_RATE_96000)
+
+#define TAS5756M_FORMATS (SNDRV_PCM_FMTBIT_S16_LE \
+ | SNDRV_PCM_FMTBIT_S20_3LE \
+ | SNDRV_PCM_FMTBIT_S24_3LE \
+ | SNDRV_PCM_FMTBIT_S32_LE)
+
+static const u32 tas5756m_dai_rates[] = {
+ 8000, 16000, 32000, 44100, 48000, 88200, 96000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_slave = {
+ .count = ARRAY_SIZE(tas5756m_dai_rates),
+ .list = tas5756m_dai_rates,
+};
+
+static int tas5756m_dai_startup_slave(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct device *dev = dai->dev;
+
+ dev_info(dev, "setting pcm hw constraints\n");
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &constraints_slave);
+}
+
+static int tas5756m_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct tas5756m_data *tas5756m =
+ snd_soc_component_get_drvdata(component);
+
+ switch (tas5756m->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ dev_info(component->dev, "setting DAI slave mode\n");
+ return tas5756m_dai_startup_slave(substream, dai);
+ case SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ default:
+ dev_info(component->dev, "cant set DAI master mode\n");
+ return -EINVAL;
+ }
+}
+
+static const struct snd_soc_dai_ops tas5756m_dai_ops = {
+ .startup = tas5756m_dai_startup,
+ .prepare = tas5756m_pcm_prepare,
+ .hw_params = tas5756m_hw_params,
+ .shutdown = tas5756m_shutdown_dai,
+ .set_fmt = tas5756m_set_dai_fmt,
+ .digital_mute = tas5756m_mute,
+};
+
+static struct snd_soc_dai_driver tas5756m_dai[] = {
+ {
+ .name = "tas5756m-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = TAS5756M_RATES,
+ .formats = TAS5756M_FORMATS,
+ },
+ .ops = &tas5756m_dai_ops,
+ },
+};
+
+/* To send default values of registers generated by
+ * Purepath tool from Texas Instruments
+ */
+static int tas5756m_transmit_registers(struct i2c_client *client, cfg_reg *r,
+ int n)
+{
+ unsigned int i = 0;
+ int ret = 0;
+
+ while (i < n) {
+ switch (r[i].command) {
+ case CFG_META_DELAY:
+ usleep_range(1000ul, r[i].param * 1000ul);
+ break;
+ case CFG_META_BURST:
+ ret =
+ i2c_master_send(client, (unsigned char *)&r[i + 1],
+ r[i].param);
+ i += (r[i].param + 1) / 2;
+ break;
+ default:
+ ret =
+ i2c_master_send(client, (unsigned char *)&r[i], 2);
+ break;
+ }
+
+ if (ret)
+ return ret;
+
+ i++;
+ }
+
+ return ret;
+}
+
+static int tas5756m_setup_hybridflow(struct tas5756m_data *tas5756m)
+{
+ struct i2c_client *client = tas5756m->tas5756m_client;
+ size_t array_reg_size = 0;
+ cfg_reg *array_reg = NULL;
+ int ret;
+
+ ret =
+ tas5756m_transmit_registers(client, &tas5756m_startup_registers[0],
+ ARRAY_SIZE(tas5756m_startup_registers));
+ if (!ret) {
+ dev_err(&client->dev, "failed to transmit init registers: %d\n",
+ ret);
+ return ret;
+ }
+
+ /* No all hybridflow are currently supported.
+ * If not supported, Hybridflow is not used.
+ */
+ switch (tas5756m->hybridflow) {
+#if 0
+ case HYBRIDFLOW_3:
+ soc_component_dev_tas5756m.controls = tas5756m_snd_controls_hf3;
+ soc_component_dev_tas5756m.num_controls =
+ ARRAY_SIZE(tas5756m_snd_controls_hf3);
+ array_reg = tas5756m_init_hf3;
+ array_reg_size = ARRAY_SIZE(tas5756m_init_hf3);
+ break;
+ case HYBRIDFLOW_4:
+ soc_component_dev_tas5756m.controls = tas5756m_snd_controls_hf4;
+ soc_component_dev_tas5756m.num_controls =
+ ARRAY_SIZE(tas5756m_snd_controls_hf4);
+ array_reg = tas5756m_init_hf4;
+ array_reg_size = ARRAY_SIZE(tas5756m_init_hf4);
+ break;
+ case HYBRIDFLOW_6:
+ soc_component_dev_tas5756m.controls = tas5756m_snd_controls_hf6;
+ soc_component_dev_tas5756m.num_controls =
+ ARRAY_SIZE(tas5756m_snd_controls_hf6);
+ array_reg = tas5756m_init_hf6;
+ array_reg_size = ARRAY_SIZE(tas5756m_init_hf6);
+ break;
+ case HYBRIDFLOW_7:
+ soc_component_dev_tas5756m.controls = tas5756m_snd_controls_hf7;
+ soc_component_dev_tas5756m.num_controls =
+ ARRAY_SIZE(tas5756m_snd_controls_hf7);
+ array_reg = tas5756m_init_hf7;
+ array_reg_size = ARRAY_SIZE(tas5756m_init_hf7);
+ break;
+#endif
+ default:
+ soc_component_dev_tas5756m.controls =
+ tas5756m_snd_controls_no_hf;
+ soc_component_dev_tas5756m.num_controls =
+ ARRAY_SIZE(tas5756m_snd_controls_no_hf);
+ tas5756m->hybridflow = NO_HYBRIDFLOW;
+ break;
+ }
+
+ if (array_reg && array_reg_size) {
+ ret =
+ tas5756m_transmit_registers(client, array_reg,
+ array_reg_size);
+ if (!ret) {
+ dev_err(&client->dev,
+ "failed to transmit hybridflow registers: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ ret =
+ snd_soc_register_component(&client->dev,
+ &soc_component_dev_tas5756m,
+ &tas5756m_dai[0],
+ ARRAY_SIZE(tas5756m_dai));
+ if (ret < 0) {
+ dev_err(&client->dev, "failed to register component: %d\n",
+ ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int tas5756m_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *i2c_id)
+{
+ struct device *dev = &client->dev;
+ struct tas5756m_data *tas5756m;
+ int ret;
+ int i;
+#ifdef CONFIG_OF
+ int hybridflow;
+ struct device_node *np;
+#endif
+
+ tas5756m = devm_kzalloc(dev, sizeof(struct tas5756m_data), GFP_KERNEL);
+ if (!tas5756m)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, tas5756m);
+ tas5756m->tas5756m_client = client;
+ tas5756m->hybridflow = NO_HYBRIDFLOW;
+
+ /* When adaptive mode is enabled, pages from CRAM A or B are referring
+ * to the same memory location.
+ * So, the driver has to enable a virtual buffer
+ * to track current settings and be able to apply them to both bank
+ * when it is relevant.
+ * Because we are not able to copy from A to B directly.
+ * Source: http://www.ti.com/lit/an/slaa425d/slaa425d.pdf
+ */
+ tas5756m->cram_buffer = vmalloc(CRAM_BUFFER_SIZE);
+ if (!tas5756m->cram_buffer)
+ return -ENOMEM;
+
+#ifdef CONFIG_OF
+ of_node_get(np);
+
+ if (of_property_read_u32(dev->of_node, "ti,hybridflow", &hybridflow)) {
+ dev_info(dev,
+ "no hybridflow property. Use default DSP program.\n");
+ } else {
+ dev_info(dev, "read property hybridflow: %u\n", hybridflow);
+ tas5756m->hybridflow = hybridflow;
+ }
+#endif
+
+ dev_info(dev, "## %s: %s codec_type = %d\n", __func__, i2c_id->name,
+ (int)i2c_id->driver_data);
+
+ tas5756m->regmap =
+ devm_regmap_init_i2c(client, &tas5756m_regmap_config);
+ if (IS_ERR(tas5756m->regmap)) {
+ ret = PTR_ERR(tas5756m->regmap);
+ dev_err(dev, "failed to allocate register map: %d\n", ret);
+ return ret;
+ }
+
+ regcache_cache_only(tas5756m->regmap, false);
+ regcache_sync(tas5756m->regmap);
+
+ tas5756m_enable_adaptive_mode(tas5756m, true);
+ tas5756m->buffer = tas5756m_get_current_cram_buffer(tas5756m);
+
+ for (i = 0; i < ARRAY_SIZE(tas5756m->supplies); i++)
+ tas5756m->supplies[i].supply = tas5756m_supply_names[i];
+
+ ret =
+ devm_regulator_bulk_get(dev, ARRAY_SIZE(tas5756m->supplies),
+ tas5756m->supplies);
+ if (ret != 0) {
+ dev_err(dev, "failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(tas5756m->supplies); i++)
+ tas5756m->supplies[i].supply = tas5756m_supply_names[i];
+
+ ret =
+ regulator_bulk_enable(ARRAY_SIZE(tas5756m->supplies),
+ tas5756m->supplies);
+ if (ret != 0) {
+ dev_err(dev, "failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = tas5756m_setup_hybridflow(tas5756m);
+ if (ret) {
+ dev_err(dev, "failed to setup hybridflow: %d\n", ret);
+ goto err;
+ }
+
+ /*
+ * By default, hybridflow mutes both channels when
+ * Purepath Console is dumping memory.
+ * We must configure them after hybridflow init to get sound back.
+ * Source: https://e2e.ti.com/support/audio/f/6/p/523251/1919266?tisearch=e2e-sitesearch&keymatch=audio&pi316677=2&pi320995=3
+ */
+ tas5756m_route_channels(tas5756m, CHANNEL_MIXER_MONO);
+
+ tas5756m_cram_to_virt_buffer(tas5756m, CRAM_BUFFER_A);
+ return 0;
+
+err:
+ dev_err(dev, "probe error\n");
+ regulator_bulk_disable(ARRAY_SIZE(tas5756m->supplies),
+ tas5756m->supplies);
+ return ret;
+}
+
+static int tas5756m_i2c_remove(struct i2c_client *i2c)
+{
+ struct device *dev = &i2c->dev;
+ struct tas5756m_data *tas5756m = i2c_get_clientdata(i2c);
+
+ vfree(tas5756m->cram_buffer);
+ snd_soc_unregister_component(dev);
+ return 0;
+}
+
+static const struct i2c_device_id tas5756m_id[] = {
+ { "tas5754m", },
+ { "tas5756m", },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, tas5756m_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id tas5756m_of_match[] = {
+ {.compatible = "ti,tas5754m" },
+ {.compatible = "ti,tas5756m" },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, tas5756m_of_match);
+#endif
+
+static struct i2c_driver tas5756m_i2c_driver = {
+ .driver = {
+ .name = "tas5756m-codec",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(tas5756m_of_match),
+ },
+
+ .probe = tas5756m_i2c_probe,
+ .remove = tas5756m_i2c_remove,
+ .id_table = tas5756m_id,
+};
+
+module_i2c_driver(tas5756m_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC TAS5756M codec / amplifier driver");
+MODULE_AUTHOR("Thomas Brijs <thomas.brijs at houseofmusic.be>");
+MODULE_AUTHOR("Charles-Antoine Couret <charles-antoine.couret at essensium.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tas5756m.h b/sound/soc/codecs/tas5756m.h
new file mode 100644
index 000000000000..2b45eb8f0fe2
--- /dev/null
+++ b/sound/soc/codecs/tas5756m.h
@@ -0,0 +1,351 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * tas5756m.h - ALSA SoC Texas Instruments TAS5756M Audio Amplifier
+ *
+ * Copyright (C)2018-2020 House of Music NV - https://www.homa.be
+ *
+ * Authors: Charles-Antoine Couret <charles-antoine.couret at mind.be>
+ * : Thomas Brijs <thomas.brijs at houseofmusic.be>
+ */
+
+#ifndef _TAS5756M_H
+#define _TAS5756M_H
+
+enum purepath_hybridflow {
+ NO_HYBRIDFLOW = 0,
+ HYBRIDFLOW_1,
+ HYBRIDFLOW_2,
+ HYBRIDFLOW_3,
+ HYBRIDFLOW_4,
+ HYBRIDFLOW_5,
+ HYBRIDFLOW_6,
+ HYBRIDFLOW_7,
+};
+
+enum channel_mixer {
+ CHANNEL_MIXER_RIGHT = 0,
+ CHANNEL_MIXER_LEFT,
+ CHANNEL_MIXER_MONO,
+ CHANNEL_MIXER_MAX,
+};
+
+enum delay_samples {
+ DELAY_SAMPLES_0 = 0,
+ DELAY_SAMPLES_4,
+ DELAY_SAMPLES_8,
+ DELAY_SAMPLES_12,
+ DELAY_SAMPLES_16,
+ DELAY_SAMPLES_MAX,
+};
+
+#define MIXER_PHASE_INVERSION_MASK BIT(23)
+#define MIXER_GAIN_MASK_MASK 0x7FFFFF
+#define COEF_ENABLE_FEATURE_VALUE 0x7FFFFF
+
+enum phase_inversion {
+ PHASE_NO_INVERSION = 0,
+ PHASE_INVERSION,
+};
+
+#define TAS5756M_NUM_SUPPLIES 2
+
+#define CFG_META_SWITCH (255)
+#define CFG_META_DELAY (254)
+#define CFG_META_BURST (253)
+
+#define TAS5756M_PAGE_SIZE 128
+#define PAGE_NR(x) (TAS5756M_PAGE_SIZE * (x))
+#define PAGE_ADDRESS_MASK 0x7F
+#define ADDR_TO_PAGE_NR(x) ((x) >> 7)
+#define TAS5756M_PAGE_SEL 0x00
+
+/* Register Address Map */
+#define TAS5756M_RESET 0x01
+#define RESET_REGISTER_MASK 0x01
+#define RESET_MODULES_MASK 0x10
+#define RESET_ALL_MASK 0x11
+
+#define TAS5756M_PDN_STBY 0x02
+#define PDN_MASK 0x01
+#define STBY_MASK 0x10
+#define PDN_STBY_MASK 0x11
+
+#define TAS5756M_MUTE_L_R 0x03
+#define MUTE_LEFT_MASK 0x10
+#define MUTE_RIGHT_MASK 0x01
+#define MUTE_ALL_MASK (MUTE_LEFT_MASK | MUTE_RIGHT_MASK)
+
+#define TAS5756M_PLL_EN_STA 0x04
+#define TAS5756M_SPI_MISO_SEL 0x06
+#define TAS5756M_SDOUT_DEEMPH 0x07
+#define TAS5756M_GPIO_OUT_EN 0x08
+#define TAS5756M_BCK_LRCK_CFG 0x09
+#define TAS5756M_DSP_GPIO_IN 0x0a
+#define TAS5756M_MASTER_B_LRCK_RST 0x0c
+
+/* PLL Regs */
+#define TAS5756M_PLL_CLK_SRC_SEL 0x0d
+#define TAS5756M_DAC_CLOCK_SRC 0x0e
+#define TAS5756M_PLL_GPIO_REF_SEL 0x12
+#define TAS5756M_SYNC_REQ 0x13
+#define TAS5756M_PLL_P_VALUE 0x14
+#define TAS5756M_PLL_J_VALUE 0x15
+#define TAS5756M_PLL_D_VALUE_MSB 0x16
+#define TAS5756M_PLL_D_VALUE_LSB 0x17
+#define TAS5756M_PLL_R_VALUE 0x18
+
+/* Clock dividers */
+#define TAS5756M_DSP_CLKDIV 0x1b
+#define TAS5756M_DAC_CLKDIV 0x1c
+#define TAS5756M_NCP_CLKDIV 0x1d
+#define TAS5756M_OSR_CLKDIV 0x1e
+#define TAS5756M_MM_BCK_CLKDIV 0x20
+#define TAS5756M_MM_LRCK_CLKDIV 0x21
+
+#define TAS5756M_FS_SPEED_MODE 0x22
+#define TAS5756M_IDAC_MSB 0x23
+#define TAS5756M_IDAC_LSB 0x24
+#define TAS5756M_IGN_ERRORS 0x25
+
+/* I2S config */
+#define TAS5756M_I2S_CONFIG 0x28
+#define FMT_MASK 0x30
+#define FMT_I2S (0x00 << 4)
+#define FMT_DSP (0x01 << 4)
+#define FMT_RTJ (0x02 << 4)
+#define FMT_LTJ (0x03 << 4)
+
+#define RES_MASK 0x03
+#define RES_16BIT (0x00)
+#define RES_20BIT (0x01)
+#define RES_24BIT (0x02)
+#define RES_32BIT (0x03)
+
+#define TAS5756M_I2S_SHIFT 0x29
+
+#define TAS5756M_DAC_DATA_PATH 0x2a
+#define DAC_PATH_DEFAULT 0x01
+#define DAC_PATH_OPPOSITE 0x02
+#define DAC_PATH_B_SHIFT 4
+
+#define TAS5756M_DSP_PROG_SEL 0x2b
+#define TAS5756M_DSP_HYBRIDFLOW 0x1f
+#define TAS5756M_CLK_MISS_DET 0x2c
+#define TAS5756M_AUTO_MUTE_TIME 0x3b
+#define TAS5756M_DIGITAL_VOLUME 0x3c
+#define VOL_MASTER_DEFAULT 0x00
+
+#define TAS5756M_RIGHT_DVOL 0x3d
+#define TAS5756M_LEFT_DVOL 0x3e
+#define VOL_CH1_2_DEFAULT 0x74
+
+#define TAS5756M_DVOL_RAMP_NORMAL 0x3f
+#define TAS5756M_DVOL_RAMP_EMRGNCY 0x40
+#define TAS5756M_AUTO_MUTE 0x41
+
+/* GPIO ouputs */
+#define TAS5756M_GPIO1_OUTPUT_SEL 0x50
+#define TAS5756M_GPIO2_OUTPUT_SEL 0x51
+#define TAS5756M_GPIO3_OUTPUT_SEL 0x52
+#define TAS5756M_GPIO4_OUTPUT_SEL 0x53
+#define TAS5756M_GPIO5_OUTPUT_SEL 0x54
+#define TAS5756M_GPIO6_OUTPUT_SEL 0x55
+#define TAS5756M_GPIO_OUTPUT_CTRL 0x56
+/* Read only */
+#define TAS5756M_CHAN_OVRFLOW 0x5a
+#define TAS5756M_DET_FS_MCLK 0x5b
+#define TAS5756M_DET_SCLK 0x5c
+#define TAS5756M_DET_SCLK_DESC 0x5d
+#define TAS5756M_CLK_DET_STATUS 0x5e
+#define TAS5756M_CLK_STATUS 0x5f
+
+#define TAS5756M_ANLG_MUTE_MON 0x6c
+
+#define TAS5756M_SHORT_DETECT 0x6d
+#define SHORT_MASK 0x01
+#define SHORT_BUSY_MASK 0x10
+
+#define TAS5756M_SPK_MUTE_DEC 0x72
+#define TAS5756M_FS_SPEED_MON 0x73
+
+/* Datasheet is wrong, R118 is the right register to get DSP and DAC status
+ * Source: https://e2e.ti.com/support/audio/f/6/t/390728?TAS5756M-experiences
+ */
+#define TAS5756M_DAC_PWR_STA 0x76
+#define DAC_ENABLED_MASK 0x80
+#define DAC_POWER_STATE_MASK 0x0F
+#define DAC_RUNNING 0x05
+
+#define TAS5756M_GPIO012_STATE 0x77
+#define TAS5756M_AUTO_MUTE_FLAG 0x78
+
+#define TAS5756M_DAC_MODE 0x79
+#define TAS5756M_MCM_MODE 0x7a
+#define TAS5756M_MCM_OUT_GPIO_1_2 0x7b
+#define TAS5756M_MCM_OUT_GPIO_3_4 0x7c
+#define TAS5756M_MCM_OUT_GPIO_5_6 0x7d
+
+/* End Page 0 */
+
+#define TAS5756M_ANLG_GAIN (PAGE_NR(1) + 0x02)
+#define CHANNEL_A_GAIN 0x01
+#define CHANNEL_B_GAIN 0x10
+
+#define TAS5756M_PWR_DET_CTRL (PAGE_NR(1) + 0x05)
+#define EXT_UVLO_PROTECT 0x02
+#define INT_UVLO_PROTECT 0x01
+
+#define TAS5756M_ANLG_MUTE (PAGE_NR(1) + 0x06)
+#define ANLG_MUTE_DIS 0x01
+
+#define TAS5756M_ANLG_BOOST (PAGE_NR(1) + 0x07)
+#define ANLG_CHA_BOOST 0x01
+#define ANLG_CHB_BOOST 0x10
+
+#define TAS5756M_VCOM_REF_RAMP (PAGE_NR(1) + 0x08)
+#define FAST_RAMP_EN 0x01
+
+#define TAS5756M_VCOM_PDN_SWITCH (PAGE_NR(1) + 0x09)
+#define VCOM_POWER_DN 0x01
+
+/* End page 1 */
+
+enum cram_buffer {
+ CRAM_BUFFER_A = 0,
+ CRAM_BUFFER_B,
+};
+
+#define CRAM_FIRST_PAGE 44
+#define CRAM_FIRST_PAGE_SECOND_BANK 62
+
+#define TAS5756M_ACTIVE_CRAM_MON (PAGE_NR(CRAM_FIRST_PAGE) + 1)
+#define CRAM_BUFFER_SWITCH_MASK BIT(0)
+#define CRAM_BUFFER_CRAM_BUFFER_USED_MASK BIT(1)
+#define CRAM_BUFFER_CRAM_ADAP_MODE_MASK BIT(2)
+#define CRAM_BUFFER_CRAM_BUFFER_USED_NON_ADAPT_MASK BIT(3)
+
+#define CRAM_BUFFER_SWITCH_REG TAS5756M_ACTIVE_CRAM_MON
+#define CRAM_BUFFER_PAGE_OFFSET 8
+#define CRAM_BUFFER_A_BASE_ADDR (PAGE_NR(CRAM_FIRST_PAGE) + CRAM_BUFFER_PAGE_OFFSET)
+#define CRAM_BUFFER_B_BASE_ADDR (PAGE_NR(CRAM_FIRST_PAGE_SECOND_BANK) + CRAM_BUFFER_PAGE_OFFSET)
+#define CRAM_NB_COEF 256
+#define CRAM_COEF_NB_REGS 4
+#define CRAM_BUFFER_SIZE (CRAM_COEF_NB_REGS * CRAM_NB_COEF)
+#define CRAM_BUFFER_OFFSET(reg) (reg - PAGE_NR(CRAM_FIRST_PAGE))
+#define CRAM_BUFFER_NB_PAGES (53 - CRAM_FIRST_PAGE)
+#define CRAM_REGS_PER_PAGES (TAS5756M_PAGE_SIZE - CRAM_BUFFER_PAGE_OFFSET)
+#define CRAM_NB_COEF_PER_PAGE (CRAM_REGS_PER_PAGES / CRAM_COEF_NB_REGS)
+#define CRAM_BIQUAD_NB_COEF 5
+#define CRAM_BIQUAD_SIZE (CRAM_COEF_NB_REGS * CRAM_BIQUAD_NB_COEF)
+#define CRAM_HPF_EFFECT_NB_COEF 24
+#define CRAM_HPF_EFFECT_SIZE (CRAM_HPF_EFFECT_NB_COEF * CRAM_BIQUAD_NB_COEF)
+
+enum hybridflow_features {
+ TAS5756_REG_FILTER_BIQUAD_1,
+ TAS5756_REG_FILTER_BIQUAD_2,
+ TAS5756_REG_FILTER_BIQUAD_3,
+ TAS5756_REG_FILTER_BIQUAD_4,
+ TAS5756_REG_FILTER_BIQUAD_5,
+ TAS5756_REG_FILTER_BIQUAD_6,
+ TAS5756_REG_FILTER_BIQUAD_7,
+ TAS5756_REG_FILTER_BIQUAD_8,
+ TAS5756_REG_FILTER_BIQUAD_9,
+ TAS5756_REG_FILTER_BIQUAD_10,
+
+ TAS5756_REG_FILTER_BIQUAD_PRE_DBE_1,
+ TAS5756_REG_FILTER_BIQUAD_PRE_DBE_2,
+ TAS5756_REG_FILTER_BIQUAD_PRE_DBE_3,
+
+ TAS5756_REG_FILTER_BIQUAD_POST_DBE_1,
+ TAS5756_REG_FILTER_BIQUAD_POST_DBE_2,
+ TAS5756_REG_FILTER_BIQUAD_POST_DBE_3,
+ TAS5756_REG_FILTER_BIQUAD_POST_DBE_4,
+ TAS5756_REG_FILTER_BIQUAD_POST_DBE_5,
+
+ TAS5756_REG_FILTER_HIGH_BIQUAD_1,
+ TAS5756_REG_FILTER_HIGH_BIQUAD_2,
+ TAS5756_REG_FILTER_HIGH_BIQUAD_3,
+ TAS5756_REG_FILTER_HIGH_BIQUAD_4,
+ TAS5756_REG_FILTER_HIGH_BIQUAD_5,
+
+ TAS5756_REG_FILTER_LOW_BIQUAD_1,
+ TAS5756_REG_FILTER_LOW_BIQUAD_2,
+ TAS5756_REG_FILTER_LOW_BIQUAD_3,
+ TAS5756_REG_FILTER_LOW_BIQUAD_4,
+ TAS5756_REG_FILTER_LOW_BIQUAD_5,
+
+ TAS5756_REG_DBE_EQ_HIGH_BIQUAD_1,
+ TAS5756_REG_DBE_EQ_HIGH_BIQUAD_2,
+ TAS5756_REG_DBE_EQ_HIGH_BIQUAD_3,
+ TAS5756_REG_DBE_EQ_HIGH_BIQUAD_4,
+ TAS5756_REG_DBE_EQ_HIGH_BIQUAD_5,
+
+ TAS5756_REG_DBE_EQ_LOW_BIQUAD_1,
+ TAS5756_REG_DBE_EQ_LOW_BIQUAD_2,
+ TAS5756_REG_DBE_EQ_LOW_BIQUAD_3,
+ TAS5756_REG_DBE_EQ_LOW_BIQUAD_4,
+ TAS5756_REG_DBE_EQ_LOW_BIQUAD_5,
+
+ TAS5756M_REG_DBE_MIXING_HIGH,
+ TAS5756M_REG_DBE_MIXING_LOW,
+ TAS5756M_REG_DBE_SENSING_HIGH,
+ TAS5756M_REG_DBE_SENSING_LOW,
+ TAS5756M_REG_DBE_WINDOW,
+
+ TAS5756M_REG_PBE_BYPASS,
+ TAS5756M_REG_PBE_EFFECT,
+ TAS5756M_REG_PBE_HARMONIC,
+ TAS5756M_REG_PBE_HPF,
+
+ TAS5756_REG_CHAN_MIXER,
+ TAS5756_REG_CHAN_MIXER_HIGH,
+ TAS5756_REG_CHAN_MIXER_LOW,
+
+ TAS5756_REG_ADD_DELAY,
+};
+
+#define TAS5756M_MAX_REG (PAGE_NR(71))
+
+/* To support registers arrays generated by Purepath tool */
+typedef unsigned char cfg_u8;
+typedef union {
+ struct {
+ cfg_u8 offset;
+ cfg_u8 value;
+ };
+
+ struct {
+ cfg_u8 command;
+ cfg_u8 param;
+ };
+} cfg_reg;
+
+static void tas5756m_fault_check_work(struct work_struct *work);
+static cfg_reg tas5756m_startup_registers[] = {
+ { { TAS5756M_PAGE_SEL, 0x00} },
+ { { TAS5756M_PDN_STBY, STBY_MASK} },
+ { { TAS5756M_RESET, RESET_ALL_MASK} },
+ { { TAS5756M_LEFT_DVOL, 0x40} },
+ { { TAS5756M_RIGHT_DVOL, 0x40} },
+ { { TAS5756M_PDN_STBY, STBY_MASK} },
+};
+
+struct tas5756m_data {
+ struct snd_soc_component *component;
+ struct regmap *regmap;
+ struct i2c_client *tas5756m_client;
+ unsigned int fmt;
+ struct regulator_bulk_data supplies[TAS5756M_NUM_SUPPLIES];
+
+ struct delayed_work fault_check_work;
+
+ unsigned int gpio_mute;
+ unsigned int last_fault;
+ unsigned int power_state;
+ enum purepath_hybridflow hybridflow;
+ enum cram_buffer buffer;
+ uint32_t *cram_buffer;
+ enum channel_mixer channel;
+};
+
+#endif /* _TAS5756M_H */
--
2.24.1
More information about the Alsa-devel
mailing list