[PATCH] ASoC: Add Richtek RT5512 Speaker Amp Driver

From: Jeff Chang
Date: Thu Oct 13 2022 - 04:08:44 EST


From: Jeff <jeff_chang@xxxxxxxxxxx>

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@xxxxxxxxxxx>
---
.../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 --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@xxxxxxxxxxx>
+
+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@xxxxxxxxxxx>");
+MODULE_DESCRIPTION("RT5512 SPKAMP Driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("2.0.5_M");
--
2.17.1