diff mbox series

ASoC: Add Richtek RT5512 Speaker Amp Driver

Message ID 20221013080643.6509-1-richtek.jeff.chang@gmail.com
State New
Headers show
Series ASoC: Add Richtek RT5512 Speaker Amp Driver | expand

Commit Message

Jeff Chang Oct. 13, 2022, 8:06 a.m. UTC
From: Jeff <jeff_chang@richtek.com>

The RT5512 is a boosted class-D amplifier with V/I sensing.
A built-in DC-DC step-up converter is used to provide efficient power for
class-D amplifier with multi-level class-H operation. The digital audio
interface supports I2S, left-justified, right-justified and TDM format for
audio in with a data out used for chip information like voltage sense and
current sense, which are able to be monitored via DATAO pin through proper
register setting.

Signed-off-by: SHIH CHIA CHANG <jeff_chang@richtek.com>
---
 .../bindings/sound/richtek,rt5512.yaml        |  50 +
 sound/soc/codecs/Kconfig                      |  10 +
 sound/soc/codecs/Makefile                     |   2 +
 sound/soc/codecs/rt5512.c                     | 860 ++++++++++++++++++
 4 files changed, 922 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/richtek,rt5512.yaml
 create mode 100644 sound/soc/codecs/rt5512.c
diff mbox series

Patch

diff --git a/Documentation/devicetree/bindings/sound/richtek,rt5512.yaml b/Documentation/devicetree/bindings/sound/richtek,rt5512.yaml
new file mode 100644
index 000000000000..6a21c9d1bd2c
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/richtek,rt5512.yaml
@@ -0,0 +1,50 @@ 
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/richtek,rt5512.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Richtek RT5512 speaker amplifier
+
+maintainers:
+  - SHIH CHIA CHANG <jeff_chang@richtek.com>
+
+description: |
+  The RT5512B is a boosted class-D amplifier with V/I sensing.
+  A built-in DC-DC step-up converter is used to provide efficient power for 
+  class-D amplifier with multi-level class-H operation. The digital audio
+  interface supports I2S, left-justified, right-justified and TDM format for
+  audio in with a data out used for chip information like voltage sense and
+  current sense, which are able to be monitored via DATAO pin through proper
+  register setting.
+
+properties:
+  compatible:
+    enum:
+      - richtek,rt5150
+
+  reg:
+    description: I2C device address
+    maxItems: 1
+
+  '#sound-dai-cells':
+    const: 0
+
+required:
+  - compatible
+  - reg
+  - '#sound-dai-cells'
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+      #address-cells = <1>;
+      #size-cells = <0>;
+      rt5512@5c {
+        compatible = "richtek,rt5512";
+        reg = <0x5c>;
+        #sound-dai-cells = <0>;
+      };
+    };
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index e3b90c425faf..0f2b4c2aded3 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -176,6 +176,7 @@  config SND_SOC_ALL_CODECS
 	imply SND_SOC_RT1019
 	imply SND_SOC_RT1305
 	imply SND_SOC_RT1308
+	imply SND_SOC_RT5512
 	imply SND_SOC_RT5514
 	imply SND_SOC_RT5616
 	imply SND_SOC_RT5631
@@ -1311,6 +1312,15 @@  config SND_SOC_RT1316_SDW
 	depends on SOUNDWIRE
 	select REGMAP_SOUNDWIRE
 
+config SND_SOC_RT5512
+	tristate "Mediatek RT5512 speaker amplifier"
+	depends on I2C
+	help
+	  Config of RT5512. It is a Smart power
+	  amplifier which contain speaker protection,
+	  multi-band DRC, equalizer function. It would
+	  be set to yes if device with RT5512 on it.
+
 config SND_SOC_RT5514
 	tristate
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 9170ee1447dd..13e79d735779 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -199,6 +199,7 @@  snd-soc-rt1316-sdw-objs := rt1316-sdw.o
 snd-soc-rt274-objs := rt274.o
 snd-soc-rt286-objs := rt286.o
 snd-soc-rt298-objs := rt298.o
+snd-soc-rt5512-objs := rt5512.o
 snd-soc-rt5514-objs := rt5514.o
 snd-soc-rt5514-spi-objs := rt5514-spi.o
 snd-soc-rt5616-objs := rt5616.o
@@ -554,6 +555,7 @@  obj-$(CONFIG_SND_SOC_RT1316_SDW)	+= snd-soc-rt1316-sdw.o
 obj-$(CONFIG_SND_SOC_RT274)	+= snd-soc-rt274.o
 obj-$(CONFIG_SND_SOC_RT286)	+= snd-soc-rt286.o
 obj-$(CONFIG_SND_SOC_RT298)	+= snd-soc-rt298.o
+obj-$(CONFIG_SND_SOC_RT5512)	+= snd-soc-rt5512.o
 obj-$(CONFIG_SND_SOC_RT5514)	+= snd-soc-rt5514.o
 obj-$(CONFIG_SND_SOC_RT5514_SPI)	+= snd-soc-rt5514-spi.o
 obj-$(CONFIG_SND_SOC_RT5514_SPI_BUILTIN)	+= snd-soc-rt5514-spi.o
diff --git a/sound/soc/codecs/rt5512.c b/sound/soc/codecs/rt5512.c
new file mode 100644
index 000000000000..a843fd730df3
--- /dev/null
+++ b/sound/soc/codecs/rt5512.c
@@ -0,0 +1,860 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021 Mediatek Inc.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/pm_qos.h>
+#include <linux/pm_runtime.h>
+#include <linux/version.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+
+#define RT5512_REV_A	(2)
+#define RT5512_REV_B	(3)
+
+#define RT5512_REG_DEVID		(0x00)
+#define RT5512_REG_SERIAL_DATA_STATUS	(0x01)
+#define RT5512_REG_SYSTEM_CTRL		(0x03)
+#define RT5512_REG_IRQ_EN		(0x04)
+#define RT5512_REG_IRQ_STATUS1		(0x05)
+#define RT5512_REG_SERIAL_CFG1		(0x10)
+#define RT5512_REG_DATAO_SEL		(0x12)
+#define RT5512_REG_TDM_CFG3		(0x15)
+#define RT5512_REG_HPF_CTRL		(0x16)
+#define RT5512_REG_HPF_COEF_1		(0x19)
+#define RT5512_REG_HPF_COEF_2		(0x1B)
+#define RT5512_REG_PATH_BYPASS		(0x1F)
+#define RT5512_REG_WDT_CTRL		(0x20)
+#define RT5512_REG_HCLIP_CTRL		(0x24)
+#define RT5512_REG_VOL_CTRL		(0x29)
+#define RT5512_REG_CLIP_CTRL		(0x30)
+#define RT5512_REG_CALI_T0		(0x3F)
+#define RT5512_REG_BST_CTRL		(0x40)
+#define RT5512_REG_BST_L1		(0x41)
+#define RT5512_REG_PROTECTION_CFG	(0x46)
+#define RT5512_REG_PLL_CFG1		(0x60)
+#define RT5512_REG_DRE_CTRL		(0x68)
+#define RT5512_REG_DRE_THDMODE		(0x69)
+#define RT5512_REG_FINE_RISING		(0x6C)
+#define RT5512_REG_FINE_FALLING		(0x6D)
+#define RT5512_REG_PWM_CTRL		(0x70)
+#define RT5512_REG_DC_PROTECT_CTRL	(0x74)
+#define RT5512_REG_DITHER_CTRL		(0x78)
+#define RT5512_REG_MISC_CTRL1		(0x98)
+#define RT5512_REG_GVSENSE_CONST	(0x9D)
+#define RT5512_REG_SPK_IB		(0xA2)
+#define RT5512_REG_ANA_SPK_CTRL6	(0xA6)
+#define RT5512_REG_NSW			(0xAD)
+#define RT5512_REG_VBG			(0xAE)
+#define RT5512_REG_BST_BIAS		(0xB0)
+#define RT5512_REG_VSADC_BLKBIAS	(0xB1)
+#define RT5512_REG_CS_CTRL		(0xB3)
+#define RT5512_REG_ANA_TOP_CTRL0	(0xB5)
+#define RT5512_REG_ANA_TOP_CTRL1	(0xB6)
+
+struct rt5512_chip {
+	struct i2c_client *i2c;
+	struct device *dev;
+	struct snd_soc_component *component;
+	struct mutex var_lock;
+	struct regmap *regmap;
+	int t0;
+	int pwr_cnt;
+	unsigned int ff_gain;
+	u16 chip_rev;
+	u8 bst_mode;
+	u8 dev_cnt;
+	struct pm_qos_request rt5512_qos_request;
+};
+
+static struct regmap_config rt5512_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 16,
+	.max_register = 0xff,
+};
+
+static int rt5512_codec_dac_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_component *component =
+		snd_soc_dapm_to_component(w->dapm);
+	int ret = 0;
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		/* un-mute */
+		ret = snd_soc_component_update_bits(component, 0x03, 0x0002, 0);
+		break;
+	case SND_SOC_DAPM_POST_PMU:
+		break;
+	}
+	return ret;
+}
+
+static int rt5512_codec_classd_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_component *component =
+		snd_soc_dapm_to_component(w->dapm);
+	struct rt5512_chip *chip = snd_soc_component_get_drvdata(component);
+	int ret = 0;
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		/*  charge pump disable & disable UVP */
+		ret |= snd_soc_component_update_bits(component, 0xb5, 0xf9fc,
+						     0xf9fc);
+		mdelay(11);
+
+		dev_info(component->dev, "%s rt5512 update bst mode %d\n",
+			 __func__, chip->bst_mode);
+		/* boost config to adaptive mode */
+		ret = snd_soc_component_update_bits(component, 0x40, 0x0003,
+						    chip->bst_mode);
+		mdelay(2);
+
+		ret |= snd_soc_component_update_bits(component, 0x98, 0x0700,
+						     0x0100);
+		/* charge pump enable */
+		ret |= snd_soc_component_update_bits(component, 0xb5, 0x0001,
+						     0x0001);
+		break;
+	case SND_SOC_DAPM_POST_PMU:
+		mdelay(2);
+		ret |= snd_soc_component_update_bits(component, 0x98, 0x0200,
+						     0x0200);
+		dev_info(component->dev, "Amp on\n");
+		cpu_latency_qos_update_request(&chip->rt5512_qos_request,
+					       PM_QOS_DEFAULT_VALUE);
+		break;
+	case SND_SOC_DAPM_PRE_PMD:
+		dev_info(component->dev, "Amp off\n");
+		cpu_latency_qos_update_request(&chip->rt5512_qos_request, 150);
+
+		/* enable mute */
+		ret = snd_soc_component_update_bits(component, 0x03, 0x0002,
+						    0x0002);
+		/* Headroom 1.1V */
+		ret |= snd_soc_component_update_bits(component, 0x41, 0x00ff,
+						     0x002f);
+		ret |= snd_soc_component_update_bits(component, 0x98, 0x0010,
+						     0x0010);
+		break;
+	case SND_SOC_DAPM_POST_PMD:
+		/* un-mute */
+		ret |= snd_soc_component_update_bits(component, 0x03, 0x0002,
+						     0x0000);
+		mdelay(2);
+
+		/* charge pump disable*/
+		ret |= snd_soc_component_update_bits(component, 0xb5, 0x0001,
+						     0x0000);
+
+		mdelay(1);
+		/* set boost to disable mode */
+		ret |= snd_soc_component_update_bits(component, 0x40, 0x0003,
+						     0x0000);
+		/* D_VBG, Bias current disable */
+		ret |= snd_soc_component_update_bits(component, 0xb5, 0xfffe,
+						     0x0000);
+		break;
+	default:
+		break;
+	}
+	return ret;
+}
+
+static const char * const rt5512_i2smux_text[] = { "I2S1", "I2S2"};
+static SOC_ENUM_SINGLE_DECL(rt5512_i2s_muxsel,
+	SND_SOC_NOPM, 0, rt5512_i2smux_text);
+static const struct snd_kcontrol_new rt5512_i2smux_ctrl =
+	SOC_DAPM_ENUM("Switch", rt5512_i2s_muxsel);
+
+static const struct snd_soc_dapm_widget rt5512_component_dapm_widgets[] = {
+	SND_SOC_DAPM_MUX("I2S Mux", SND_SOC_NOPM, 0, 0, &rt5512_i2smux_ctrl),
+	SND_SOC_DAPM_DAC_E("DAC", NULL, RT5512_REG_PLL_CFG1,
+		0, 1, rt5512_codec_dac_event, SND_SOC_DAPM_POST_PMU),
+	SND_SOC_DAPM_ADC("VI ADC", NULL, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_PGA("PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_OUT_DRV_E("ClassD", RT5512_REG_SYSTEM_CTRL, 2, 0,
+			       NULL, 0, rt5512_codec_classd_event,
+			       SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+			       SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_SPK("SPK", NULL),
+};
+
+static const struct snd_soc_dapm_route rt5512_component_dapm_routes[] = {
+	{ "I2S Mux", "I2S1", "AIF1 Playback" },
+	{ "I2S Mux", "I2S2", "AIF2 Playback" },
+	{ "DAC", NULL, "I2S Mux" },
+	{ "PGA", NULL, "DAC" },
+	{ "ClassD", NULL, "PGA" },
+	{ "SPK", NULL, "ClassD" },
+	{ "VI ADC", NULL, "ClassD" },
+	{ "AIF1 Capture", NULL, "VI ADC" },
+};
+
+static int rt5512_component_get_t0(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component =
+		snd_soc_kcontrol_component(kcontrol);
+	struct rt5512_chip *chip = snd_soc_component_get_drvdata(component);
+
+	dev_info(component->dev, "%s, chip->t0 = %d\n", __func__, chip->t0);
+	ucontrol->value.integer.value[0] = chip->t0;
+	return 0;
+}
+
+static int rt5512_get_bstmode(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component =
+		snd_soc_kcontrol_component(kcontrol);
+	struct rt5512_chip *chip = snd_soc_component_get_drvdata(component);
+
+	ucontrol->value.integer.value[0] = chip->bst_mode;
+	return 0;
+}
+
+static int rt5512_set_bstmode(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component =
+		snd_soc_kcontrol_component(kcontrol);
+	struct rt5512_chip *chip = snd_soc_component_get_drvdata(component);
+	int ret = 0;
+
+	chip->bst_mode = ucontrol->value.integer.value[0];
+	dev_info(component->dev, "%s, bst_mode = %d\n", __func__,
+		 chip->bst_mode);
+	return ret;
+}
+
+static int rt5512_codec_get_chiprev(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component =
+		snd_soc_kcontrol_component(kcontrol);
+	struct rt5512_chip *chip = snd_soc_component_get_drvdata(component);
+
+	ucontrol->value.integer.value[0] = chip->chip_rev & 0xf;
+	return 0;
+}
+
+static int rt5512_codec_put_istcbypass(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component =
+		snd_soc_kcontrol_component(kcontrol);
+	int ret;
+
+	pm_runtime_get_sync(component->dev);
+	if (ucontrol->value.integer.value[0]) {
+		ret = snd_soc_component_update_bits(component,
+						    RT5512_REG_PATH_BYPASS,
+						    0x0004, 0x0004);
+	} else {
+		ret = snd_soc_component_update_bits(component,
+						    RT5512_REG_PATH_BYPASS,
+						    0x0004, 0x0000);
+	}
+	if (ret)
+		dev_err(component->dev, "%s set CC Max Failed\n", __func__);
+	pm_runtime_put_sync(component->dev);
+	return ret;
+}
+
+static int rt5512_codec_get_istcbypass(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component =
+		snd_soc_kcontrol_component(kcontrol);
+	int ret;
+
+	pm_runtime_get_sync(component->dev);
+	ret = snd_soc_component_read(component, RT5512_REG_PATH_BYPASS);
+
+	if (ret > 0 && ret&0x0004)
+		ucontrol->value.integer.value[0] = 1;
+	else
+		ucontrol->value.integer.value[0] = 0;
+	pm_runtime_put_sync(component->dev);
+	return 0;
+}
+
+static int rt5512_codec_get_volsw(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component =
+		snd_soc_kcontrol_component(kcontrol);
+	int ret;
+
+	pm_runtime_get_sync(component->dev);
+	ret = snd_soc_get_volsw(kcontrol, ucontrol);
+	if (ret < 0)
+		dev_err(component->dev, "%s get volsw fail\n", __func__);
+	pm_runtime_put_sync(component->dev);
+	return ret;
+}
+
+static int rt5512_codec_put_volsw(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component =
+		snd_soc_kcontrol_component(kcontrol);
+	int  put_ret = 0;
+
+	pm_runtime_get_sync(component->dev);
+	put_ret = snd_soc_put_volsw(kcontrol, ucontrol);
+	if (put_ret < 0)
+		dev_err(component->dev, "%s put volsw fail\n", __func__);
+	pm_runtime_put_sync(component->dev);
+	return put_ret;
+}
+
+static const DECLARE_TLV_DB_SCALE(vol_ctl_tlv, -1155, 5, 0);
+static const struct snd_kcontrol_new rt5512_component_snd_controls[] = {
+	SOC_SINGLE_EXT_TLV("Volume_Ctrl", RT5512_REG_VOL_CTRL, 0, 255,
+			   1, rt5512_codec_get_volsw, rt5512_codec_put_volsw,
+			   vol_ctl_tlv),
+	SOC_SINGLE_EXT("Data Output Left Channel Selection",
+		       RT5512_REG_DATAO_SEL, 3, 7, 0,
+		       rt5512_codec_get_volsw, rt5512_codec_put_volsw),
+	SOC_SINGLE_EXT("Data Output Right Channel Selection",
+		       RT5512_REG_DATAO_SEL, 0, 7, 0,
+		       rt5512_codec_get_volsw, rt5512_codec_put_volsw),
+	SOC_SINGLE_EXT("Audio Input Selection", RT5512_REG_DATAO_SEL,
+		       6, 3, 0,
+		       rt5512_codec_get_volsw, rt5512_codec_put_volsw),
+	SOC_SINGLE_EXT("IDAC Gain Selection", RT5512_REG_ANA_TOP_CTRL1,
+		       0, 31, 0,
+		       rt5512_codec_get_volsw, rt5512_codec_put_volsw),
+	SOC_SINGLE_EXT("VBAT BIT OUT EN", RT5512_REG_TDM_CFG3, 11, 1, 0,
+		       rt5512_codec_get_volsw, rt5512_codec_put_volsw),
+	SOC_SINGLE_EXT("Boost Mode", SND_SOC_NOPM, 0, 3, 0,
+		       rt5512_get_bstmode, rt5512_set_bstmode),
+	SOC_SINGLE_EXT("T0_SEL", SND_SOC_NOPM, 0, 7, 0,
+			rt5512_component_get_t0, NULL),
+	SOC_SINGLE_EXT("Chip_Rev", SND_SOC_NOPM, 0, 16, 0,
+			rt5512_codec_get_chiprev, NULL),
+	SOC_SINGLE_BOOL_EXT("IS_TC_BYPASS", SND_SOC_NOPM,
+		rt5512_codec_get_istcbypass, rt5512_codec_put_istcbypass),
+};
+
+static int rt5512_component_setting(struct snd_soc_component *component)
+{
+	struct rt5512_chip *chip = snd_soc_component_get_drvdata(component);
+	int ret = 0;
+
+	ret = snd_soc_component_read(component, 0x4d);
+	if (ret < 0)
+		return -EIO;
+	chip->ff_gain = ret;
+	if (chip->chip_rev == RT5512_REV_A) {
+		/* RT5512A_RU012B_algorithm_20201110.lua */
+		ret |= snd_soc_component_update_bits(component, 0xA1, 0xff18,
+						     0x5b18);
+		ret |= snd_soc_component_write(component, 0x69, 0x0002);
+		ret |= snd_soc_component_write(component, 0x68, 0x000C);
+		ret |= snd_soc_component_write(component, 0x6C, 0x0010);
+		ret |= snd_soc_component_write(component, 0x6D, 0x0008);
+		ret |= snd_soc_component_write(component, 0x30, 0x0002);
+		ret |= snd_soc_component_write(component, 0xA7, 0x0A84);
+		ret |= snd_soc_component_write(component, 0x20, 0x00A2);
+		ret |= snd_soc_component_write(component, 0x8B, 0x0040);
+
+		/* BST optimizations for efficiency */
+		ret |= snd_soc_component_write(component, 0xAD, 0x40F7);
+		ret |= snd_soc_component_write(component, 0x41, 0x0028);
+		ret |= snd_soc_component_write(component, 0x49, 0x0495);
+		/* 2019.0821 */
+		ret |= snd_soc_component_write(component, 0x46, 0x001D);
+		ret |= snd_soc_component_write(component, 0x45, 0x5292);
+		ret |= snd_soc_component_write(component, 0x4C, 0x0293);
+
+		ret |= snd_soc_component_write(component, 0xA2, 0x355D);
+		ret |= snd_soc_component_update_bits(component, 0xAE, 0x00ff,
+						     0x0056);
+		ret |= snd_soc_component_write(component, 0xA5, 0x6612);
+		ret |= snd_soc_component_write(component, 0x70, 0x0021);
+		ret |= snd_soc_component_write(component, 0xA6, 0x3135);
+
+		/* boost THD performance enhance */
+		ret |= snd_soc_component_write(component, 0x9B, 0x5f37);
+		ret |= snd_soc_component_write(component, 0x4A, 0xD755);
+
+		/* V.I sense performance enhance */
+		ret |= snd_soc_component_write(component, 0xB3, 0x9103);
+		ret |= snd_soc_component_update_bits(component, 0xB1, 0xfff0,
+						     0xA5AA);
+		ret |= snd_soc_component_write(component, 0xB0, 0xD5A5);
+		ret |= snd_soc_component_write(component, 0x98, 0x8B8C);
+		ret |= snd_soc_component_write(component, 0x78, 0x00f2);
+
+		ret |= snd_soc_component_write(component, 0x9D, 0x00FC);
+		ret |= snd_soc_component_write(component, 0x38, 0x9BEB);
+		ret |= snd_soc_component_write(component, 0x39, 0x8BAC);
+		ret |= snd_soc_component_write(component, 0x3A, 0x7E7D);
+		ret |= snd_soc_component_write(component, 0x3B, 0x7395);
+		ret |= snd_soc_component_write(component, 0x3C, 0x6A68);
+		ret |= snd_soc_component_write(component, 0x3D, 0x6295);
+		ret |= snd_soc_component_write(component, 0x3E, 0x5BD4);
+
+		ret |= snd_soc_component_update_bits(component, 0x12, 0x0007, 0x0006);
+		ret |= snd_soc_component_update_bits(component, 0xB6, 0x0400, 0x0000);
+	} else if (chip->chip_rev == RT5512_REV_B) { /* REV_B */
+		/* RT5512B_RU012D_algorithm_20201110.lua */
+		ret |= snd_soc_component_update_bits(component, 0xA1, 0xff18, 0x5b18);
+		ret |= snd_soc_component_write(component, 0x69, 0x0002);
+		ret |= snd_soc_component_write(component, 0x68, 0x000C);
+		ret |= snd_soc_component_write(component, 0x6C, 0x0010);
+		ret |= snd_soc_component_write(component, 0x6D, 0x0008);
+		ret |= snd_soc_component_write(component, 0x30, 0x0002);
+		ret |= snd_soc_component_write(component, 0xA7, 0x0A84);
+		ret |= snd_soc_component_write(component, 0x20, 0x00A2);
+		ret |= snd_soc_component_write(component, 0x8B, 0x0040);
+
+		/* BST optimizations for efficiency */
+		ret |= snd_soc_component_write(component, 0xAD, 0x40F7);
+		ret |= snd_soc_component_write(component, 0x41, 0x0028);
+		ret |= snd_soc_component_write(component, 0x49, 0x0495);
+		/* 2019.0821 */
+		ret |= snd_soc_component_write(component, 0x46, 0x001D);
+		ret |= snd_soc_component_write(component, 0x45, 0x5292);
+		ret |= snd_soc_component_write(component, 0x4C, 0x0293);
+
+		ret |= snd_soc_component_write(component, 0xA2, 0x355D);
+		ret |= snd_soc_component_update_bits(component, 0xAE, 0x00ff, 0x0056);
+		ret |= snd_soc_component_write(component, 0xA5, 0x6612);
+		ret |= snd_soc_component_write(component, 0x70, 0x0021);
+		ret |= snd_soc_component_write(component, 0xA6, 0x3135);
+
+		/* boost THD performance enhance */
+		ret |= snd_soc_component_write(component, 0x9B, 0x5f37);
+		ret |= snd_soc_component_write(component, 0x4A, 0xD755);
+
+		/* V.I sense performance enhance */
+		ret |= snd_soc_component_write(component, 0xB3, 0x9103);
+		ret |= snd_soc_component_update_bits(component, 0xB1, 0xfff0, 0xA5AA);
+		ret |= snd_soc_component_write(component, 0xB0, 0xD5A5);
+		ret |= snd_soc_component_write(component, 0x98, 0x8B8C);
+		ret |= snd_soc_component_write(component, 0x78, 0x00f2);
+
+		ret |= snd_soc_component_write(component, 0x9D, 0x00FC);
+		ret |= snd_soc_component_write(component, 0x38, 0x9BEB);
+		ret |= snd_soc_component_write(component, 0x39, 0x8BAC);
+		ret |= snd_soc_component_write(component, 0x3A, 0x7E7D);
+		ret |= snd_soc_component_write(component, 0x3B, 0x7395);
+		ret |= snd_soc_component_write(component, 0x3C, 0x6A68);
+		ret |= snd_soc_component_write(component, 0x3D, 0x6295);
+		ret |= snd_soc_component_write(component, 0x3E, 0x5BD4);
+
+		ret |= snd_soc_component_update_bits(component, 0x12, 0x0007, 0x0006);
+		ret |= snd_soc_component_update_bits(component, 0xB6, 0x0400, 0x0000);
+	}
+
+	if (ret < 0)
+		return ret;
+	mdelay(5);
+	return 0;
+}
+
+static int rt5512_component_probe(struct snd_soc_component *component)
+{
+	struct rt5512_chip *chip = snd_soc_component_get_drvdata(component);
+	int ret = 0;
+
+	pm_runtime_get_sync(component->dev);
+
+	chip->component = component;
+	snd_soc_component_init_regmap(component, chip->regmap);
+
+	ret = rt5512_component_setting(component);
+	if (ret < 0) {
+		dev_err(chip->dev, "rt5512 component setting failed\n");
+		goto component_probe_fail;
+	}
+
+component_probe_fail:
+	pm_runtime_put_sync(component->dev);
+	return ret;
+}
+
+static void rt5512_component_remove(struct snd_soc_component *component)
+{
+	snd_soc_component_exit_regmap(component);
+}
+
+static const struct snd_soc_component_driver rt5512_component_driver = {
+	.probe = rt5512_component_probe,
+	.remove = rt5512_component_remove,
+
+	.controls = rt5512_component_snd_controls,
+	.num_controls = ARRAY_SIZE(rt5512_component_snd_controls),
+	.dapm_widgets = rt5512_component_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(rt5512_component_dapm_widgets),
+	.dapm_routes = rt5512_component_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(rt5512_component_dapm_routes),
+
+	.idle_bias_on = false,
+};
+
+static int rt5512_component_aif_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai)
+{
+	struct snd_soc_dapm_context *dapm =
+				     snd_soc_component_get_dapm(dai->component);
+	int word_len = params_physical_width(hw_params);
+	int aud_bit = params_width(hw_params);
+	u16 reg_data = 0;
+	int ret = 0;
+	char *tmp = "SPK";
+
+	dev_dbg(dai->dev, "%s: ++\n", __func__);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dev_info(dai->dev, "format: 0x%08x, rate: 0x%08x, word_len: %d, aud_bit: %d\n",
+			 params_format(hw_params), params_rate(hw_params), word_len,
+			 aud_bit);
+	if (word_len > 32 || word_len < 16) {
+		dev_err(dai->dev, "not supported word length\n");
+		return -ENOTSUPP;
+	}
+	switch (aud_bit) {
+	case 16:
+		reg_data = 3;
+		break;
+	case 18:
+		reg_data = 2;
+		break;
+	case 20:
+		reg_data = 1;
+		break;
+	case 24:
+	case 32:
+		reg_data = 0;
+		break;
+	default:
+		return -ENOTSUPP;
+	}
+	ret = snd_soc_component_update_bits(dai->component,
+		RT5512_REG_SERIAL_CFG1, 0x00c0, (reg_data << 6));
+	if (ret < 0) {
+		dev_err(dai->dev, "config aud bit fail\n");
+		return ret;
+	}
+
+	ret = snd_soc_component_update_bits(dai->component,
+		RT5512_REG_TDM_CFG3, 0x03f0, word_len << 4);
+	if (ret < 0) {
+		dev_err(dai->dev, "config word len fail\n");
+		return ret;
+	}
+	dev_dbg(dai->dev, "%s: --\n", __func__);
+	return snd_soc_dapm_enable_pin(dapm, tmp);
+}
+
+static int rt5512_component_aif_hw_free(struct snd_pcm_substream *substream,
+				    struct snd_soc_dai *dai)
+{
+	struct snd_soc_dapm_context *dapm =
+				snd_soc_component_get_dapm(dai->component);
+	int ret = 0;
+	char *tmp = "SPK";
+
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		return 0;
+	dev_info(dai->dev, "%s successfully start\n", __func__);
+
+	ret = snd_soc_dapm_disable_pin(dapm, tmp);
+	if (ret < 0)
+		return ret;
+	return snd_soc_dapm_sync(dapm);
+}
+
+static const struct snd_soc_dai_ops rt5512_component_aif_ops = {
+	.hw_params = rt5512_component_aif_hw_params,
+	.hw_free = rt5512_component_aif_hw_free,
+};
+
+#define STUB_RATES	SNDRV_PCM_RATE_8000_192000
+#define STUB_FORMATS	(SNDRV_PCM_FMTBIT_S16_LE | \
+			SNDRV_PCM_FMTBIT_U16_LE | \
+			SNDRV_PCM_FMTBIT_S24_LE | \
+			SNDRV_PCM_FMTBIT_U24_LE | \
+			SNDRV_PCM_FMTBIT_S32_LE | \
+			SNDRV_PCM_FMTBIT_U32_LE)
+
+static struct snd_soc_dai_driver rt5512_codec_dai[] = {
+	{
+		.name = "rt5512-aif",
+		.playback = {
+			.stream_name	= "AIF1 Playback",
+			.channels_min	= 1,
+			.channels_max	= 2,
+			.rates		= STUB_RATES,
+			.formats	= STUB_FORMATS,
+		},
+		.capture = {
+			.stream_name	= "AIF1 Capture",
+			.channels_min	= 1,
+			.channels_max	= 2,
+			.rates = STUB_RATES,
+			.formats = STUB_FORMATS,
+		},
+		/* dai properties */
+		.symmetric_rate = 1,
+		.symmetric_channels = 1,
+		.symmetric_sample_bits = 1,
+		/* dai operations */
+		.ops = &rt5512_component_aif_ops,
+	},
+	{
+		.name = "rt5512-aif2",
+		.playback = {
+			.stream_name = "AIF2 Playback",
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = STUB_RATES,
+			.formats = STUB_FORMATS,
+		},
+		.ops = &rt5512_component_aif_ops,
+	},
+};
+
+static inline int _rt5512_chip_power_on(struct rt5512_chip *chip, int on_off)
+{
+	int ret = 0;
+
+	dev_info(chip->dev, "%s, %d\n", __func__, on_off);
+	ret =  regmap_write_bits(chip->regmap, RT5512_REG_SYSTEM_CTRL, 0xffff,
+				 on_off ? 0x0000 : 0x0001);
+	if (ret)
+		return ret;
+	return 0;
+}
+
+static inline int _rt5512_chip_sw_reset(struct rt5512_chip *chip)
+{
+	int ret;
+	u8 data[2] = {0x00, 0x00};
+	u8 reg_data[2] = {0x00, 0x80};
+
+	/* turn on main pll first, then trigger reset */
+	ret = i2c_smbus_write_i2c_block_data(chip->i2c, RT5512_REG_SYSTEM_CTRL,
+					     2, data);
+	if (ret < 0)
+		return ret;
+	ret = i2c_smbus_write_i2c_block_data(chip->i2c, RT5512_REG_SYSTEM_CTRL,
+					     2, reg_data);
+	if (ret < 0)
+		return ret;
+	mdelay(14);
+
+	if (chip->chip_rev == RT5512_REV_B) {
+		reg_data[0] = 0;
+		reg_data[1] = 0;
+		ret = i2c_smbus_write_i2c_block_data(chip->i2c, 0x98, 2,
+						     reg_data);
+		if (ret < 0) {
+			dev_err(chip->dev, "%s write 0x98 failed\n", __func__);
+			return ret;
+		}
+		mdelay(10);
+		reg_data[0] = 0xE1;
+		reg_data[1] = 0xFD;
+		ret = i2c_smbus_write_i2c_block_data(chip->i2c, 0xb5, 2,
+						     reg_data);
+		if (ret < 0) {
+			dev_err(chip->dev, "%s write 0xb5 failed\n", __func__);
+			return ret;
+		}
+	}
+	return 0;
+}
+
+static inline int rt5512_chip_id_check(struct rt5512_chip *chip)
+{
+	u8 id[2] = {0};
+	int ret = 0;
+	u8 data[2] = {0x00, 0x00};
+
+	ret = i2c_smbus_write_i2c_block_data(chip->i2c, RT5512_REG_SYSTEM_CTRL,
+					     2, data);
+	if (ret < 0)
+		return ret;
+	ret = i2c_smbus_read_i2c_block_data(chip->i2c, RT5512_REG_DEVID, 2, id);
+	if (ret < 0)
+		return ret;
+	if (id[0] != 0) {
+		dev_err(chip->dev, "%s device id not match, id = %x\n",
+			__func__, id[0]);
+		return -ENODEV;
+	}
+	chip->chip_rev = id[1];
+	if (chip->chip_rev != RT5512_REV_A && chip->chip_rev != RT5512_REV_B) {
+		dev_err(chip->dev, "%s chip rev not match, rev = %d\n",
+			__func__, chip->chip_rev);
+		return -ENODEV;
+	}
+	dev_info(chip->dev, "%s chip_rev = %d\n", __func__, chip->chip_rev);
+
+	ret = i2c_smbus_read_i2c_block_data(chip->i2c, RT5512_REG_CALI_T0, 2,
+					    id);
+	if (ret < 0)
+		return ret;
+	chip->t0 = id[1];
+	dev_info(chip->dev, "%s chip t0 = %d\n", __func__, chip->t0);
+
+	data[1] = 0x01;
+	ret = i2c_smbus_write_i2c_block_data(chip->i2c, RT5512_REG_SYSTEM_CTRL,
+					     2, data);
+	return ret;
+}
+
+static inline int rt5512_component_register(struct rt5512_chip *chip)
+{
+	return devm_snd_soc_register_component(chip->dev,
+					       &rt5512_component_driver,
+					       rt5512_codec_dai,
+					       ARRAY_SIZE(rt5512_codec_dai));
+}
+
+int rt5512_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct rt5512_chip *chip = NULL;
+	int ret = 0;
+	static int dev_cnt;
+
+	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+	chip->i2c = client;
+	chip->dev = &client->dev;
+	chip->dev_cnt = dev_cnt;
+	mutex_init(&chip->var_lock);
+	i2c_set_clientdata(client, chip);
+
+	ret = rt5512_chip_id_check(chip);
+	if (ret < 0) {
+		dev_err(&client->dev, "chip id check fail, ret = %d\n", ret);
+		return -ENODEV;
+	}
+
+	chip->bst_mode = 1; /* Battery Mode */
+
+	/* chip reset first */
+	ret = _rt5512_chip_sw_reset(chip);
+	if (ret < 0) {
+		dev_err(chip->dev, "chip reset fail\n");
+		goto probe_fail;
+	}
+
+	chip->regmap = devm_regmap_init_i2c(client, &rt5512_regmap_config);
+	if (IS_ERR(chip->regmap)) {
+		ret = PTR_ERR(chip->regmap);
+		dev_err(&client->dev, "failed to initialise regmap: %d\n", ret);
+		return ret;
+	}
+
+	/* register qos to prevent deep idle during transfer */
+	cpu_latency_qos_add_request(&chip->rt5512_qos_request,
+				    PM_QOS_DEFAULT_VALUE);
+	ret = rt5512_component_register(chip);
+	if (!ret) {
+		pm_runtime_set_active(chip->dev);
+		pm_runtime_enable(chip->dev);
+	}
+	dev_info(chip->dev, "%s end, ret = %d\n", __func__, ret);
+	return ret;
+probe_fail:
+	_rt5512_chip_power_on(chip, 0);
+	mutex_destroy(&chip->var_lock);
+	return ret;
+}
+
+void rt5512_i2c_remove(struct i2c_client *client)
+{
+	struct rt5512_chip *chip = i2c_get_clientdata(client);
+
+	pm_runtime_disable(chip->dev);
+	pm_runtime_set_suspended(chip->dev);
+	mutex_destroy(&chip->var_lock);
+}
+
+static int __maybe_unused rt5512_i2c_runtime_suspend(struct device *dev)
+{
+	struct rt5512_chip *chip = dev_get_drvdata(dev);
+	int ret = 0;
+
+	dev_info(dev, "enter low power mode\n");
+	ret = regmap_write_bits(chip->regmap, RT5512_REG_SYSTEM_CTRL,
+				0xffff, 0x0001);
+	cpu_latency_qos_update_request(&chip->rt5512_qos_request,
+				       PM_QOS_DEFAULT_VALUE);
+	if (ret < 0)
+		dev_err(dev, "%s ret = %d\n", __func__, ret);
+	return 0;
+}
+
+static int __maybe_unused rt5512_i2c_runtime_resume(struct device *dev)
+{
+	struct rt5512_chip *chip = dev_get_drvdata(dev);
+	int ret = 0;
+
+	/* update qos to prevent deep idle during transfer */
+	cpu_latency_qos_update_request(&chip->rt5512_qos_request, 150);
+	dev_info(dev, "exit low power mode\n");
+	ret = regmap_write_bits(chip->regmap,
+		RT5512_REG_SYSTEM_CTRL, 0xffff, 0x0000);
+	if (ret < 0)
+		dev_err(dev, "%s ret = %d\n", __func__, ret);
+	usleep_range(2000, 2100);
+	return 0;
+}
+
+static const struct dev_pm_ops rt5512_dev_pm_ops = {
+	SET_RUNTIME_PM_OPS(rt5512_i2c_runtime_suspend,
+			   rt5512_i2c_runtime_resume, NULL)
+};
+
+static const struct of_device_id __maybe_unused rt5512_of_id[] = {
+	{ .compatible = "richtek,rt5512",},
+	{},
+};
+MODULE_DEVICE_TABLE(of, rt5512_of_id);
+
+static const struct i2c_device_id rt5512_i2c_id[] = {
+	{"rt5512", 0 },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, rt5512_i2c_id);
+
+static struct i2c_driver rt5512_i2c_driver = {
+	.driver = {
+		.name = "rt5512",
+		.of_match_table = of_match_ptr(rt5512_of_id),
+		.pm = &rt5512_dev_pm_ops,
+	},
+	.probe = rt5512_i2c_probe,
+	.remove = rt5512_i2c_remove,
+	.id_table = rt5512_i2c_id,
+};
+module_i2c_driver(rt5512_i2c_driver);
+
+MODULE_AUTHOR("Jeff Chang <jeff_chang@richtek.com>");
+MODULE_DESCRIPTION("RT5512 SPKAMP Driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("2.0.5_M");