From 50a8db983c682918cd2efe02ede48db93892d52a Mon Sep 17 00:00:00 2001
From: Paolo Sabatino <paolo.sabatino@gmail.com>
Date: Sat, 20 Jul 2024 13:51:55 +0200
Subject: [PATCH] rk3228: add analog audio codec

---
 .../bindings/sound/rockchip,rk3228-codec.txt  |  22 +
 arch/arm/boot/dts/rockchip/rk322x.dtsi        |   9 +
 drivers/clk/rockchip/clk-rk3228.c             |   2 +-
 include/dt-bindings/clock/rk3228-cru.h        |   1 +
 sound/soc/codecs/Kconfig                      |   6 +
 sound/soc/codecs/Makefile                     |   2 +
 sound/soc/codecs/rk3228_codec.c               | 545 ++++++++++++++++++
 sound/soc/codecs/rk3228_codec.h               | 218 +++++++
 8 files changed, 804 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/devicetree/bindings/sound/rockchip,rk3228-codec.txt
 create mode 100644 sound/soc/codecs/rk3228_codec.c
 create mode 100644 sound/soc/codecs/rk3228_codec.h

diff --git a/Documentation/devicetree/bindings/sound/rockchip,rk3228-codec.txt b/Documentation/devicetree/bindings/sound/rockchip,rk3228-codec.txt
new file mode 100644
index 000000000000..9191a8593380
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/rockchip,rk3228-codec.txt
@@ -0,0 +1,22 @@
+* Rockchip Rk3228 internal codec
+
+Required properties:
+
+- compatible: "rockchip,rk3228-codec"
+- reg: physical base address of the controller and length of memory mapped
+  region.
+- clocks: a list of phandle + clock-specifer pairs, one for each entry in clock-names.
+- clock-names: a list of clock names, one for each entry in clocks.
+- spk-en-gpio: speaker enable gpio.
+- spk-depop-time-ms: speaker depop time msec.
+
+Example for rk3228 internal codec:
+
+codec: codec@12010000 {
+	compatible = "rockchip,rk3228-codec";
+	reg = <0x12010000 0x1000>;
+	clocks =  <&cru SCLK_I2S_OUT>, <&cru PCLK_ACODECPHY>, <&cru SCLK_I2S1>;
+	clock-names = "mclk", "pclk", "sclk";
+	spk-en-gpio = <&gpio1 RK_PA3 GPIO_ACTIVE_HIGH>;
+	status = "disabled";
+};
diff --git a/arch/arm/boot/dts/rockchip/rk322x.dtsi b/arch/arm/boot/dts/rockchip/rk322x.dtsi
index cc5a5e609f04..d13dc979a67e 100644
--- a/arch/arm/boot/dts/rockchip/rk322x.dtsi
+++ b/arch/arm/boot/dts/rockchip/rk322x.dtsi
@@ -249,6 +249,15 @@ i2s1: i2s1@100b0000 {
 		status = "disabled";
 	};
 
+	codec: codec@12010000 {
+		compatible = "rockchip,rk3228-codec";
+		reg = <0x12010000 0x1000>;
+		clocks =  <&cru SCLK_I2S_OUT>, <&cru PCLK_ACODECPHY>, <&cru SCLK_I2S1>;
+		clock-names = "mclk", "pclk", "sclk";
+		spk-en-gpio = <&gpio1 RK_PA3 GPIO_ACTIVE_HIGH>;
+		status = "disabled";
+	};
+
 	i2s0: i2s0@100c0000 {
 		compatible = "rockchip,rk3228-i2s", "rockchip,rk3066-i2s";
 		reg = <0x100c0000 0x4000>;
diff --git a/drivers/clk/rockchip/clk-rk3228.c b/drivers/clk/rockchip/clk-rk3228.c
index 0db8a97007f2..0f690dd84650 100644
--- a/drivers/clk/rockchip/clk-rk3228.c
+++ b/drivers/clk/rockchip/clk-rk3228.c
@@ -603,7 +603,7 @@ static struct rockchip_clk_branch rk3228_clk_branches[] __initdata = {
 	GATE(0, "pclk_sim", "pclk_cpu", 0, RK2928_CLKGATE_CON(10), 3, GFLAGS),
 
 	GATE(0, "pclk_ddrphy", "pclk_phy_pre", 0, RK2928_CLKGATE_CON(10), 3, GFLAGS),
-	GATE(0, "pclk_acodecphy", "pclk_phy_pre", 0, RK2928_CLKGATE_CON(10), 5, GFLAGS),
+	GATE(PCLK_ACODECPHY, "pclk_acodecphy", "pclk_phy_pre", 0, RK2928_CLKGATE_CON(10), 5, GFLAGS),
 	GATE(PCLK_HDMI_PHY, "pclk_hdmiphy", "pclk_phy_pre", 0, RK2928_CLKGATE_CON(10), 7, GFLAGS),
 	GATE(0, "pclk_vdacphy", "pclk_phy_pre", 0, RK2928_CLKGATE_CON(10), 8, GFLAGS),
 	GATE(0, "pclk_phy_noc", "pclk_phy_pre", 0, RK2928_CLKGATE_CON(10), 9, GFLAGS),
diff --git a/include/dt-bindings/clock/rk3228-cru.h b/include/dt-bindings/clock/rk3228-cru.h
index 911824731866..9162f9132657 100644
--- a/include/dt-bindings/clock/rk3228-cru.h
+++ b/include/dt-bindings/clock/rk3228-cru.h
@@ -116,6 +116,7 @@
 #define PCLK_HDMI_CTRL		364
 #define PCLK_HDMI_PHY		365
 #define PCLK_GMAC		367
+#define PCLK_ACODECPHY		368
 
 /* hclk gates */
 #define HCLK_I2S0_8CH		442
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 4afc43d3f71f..183a69249160 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -182,6 +182,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_PCM6240
 	imply SND_SOC_PEB2466
 	imply SND_SOC_RK3308
+	imply SND_SOC_RK3228
 	imply SND_SOC_RK3328
 	imply SND_SOC_RK817
 	imply SND_SOC_RT274
@@ -1457,6 +1458,11 @@ config SND_SOC_RK3308
 	  It has 8 24-bit ADCs and 2 24-bit DACs. The maximum supported
 	  sampling rate is 192 kHz.
 
+config SND_SOC_RK3228
+	tristate "Rockchip RK3228 audio CODEC"
+	depends on ARCH_ROCKCHIP || COMPILE_TEST
+	select REGMAP_MMIO
+
 config SND_SOC_RK3328
 	tristate "Rockchip RK3328 audio CODEC"
 	depends on ARCH_ROCKCHIP || COMPILE_TEST
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index b4df22186e25..454179eb7b73 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -207,6 +207,7 @@ snd-soc-pcm512x-spi-y := pcm512x-spi.o
 snd-soc-pcm6240-y := pcm6240.o
 snd-soc-peb2466-y := peb2466.o
 snd-soc-rk3308-y := rk3308_codec.o
+snd-soc-rk3228-y := rk3228_codec.o
 snd-soc-rk3328-y := rk3328_codec.o
 snd-soc-rk817-y := rk817_codec.o
 snd-soc-rl6231-y := rl6231.o
@@ -599,6 +600,7 @@ obj-$(CONFIG_SND_SOC_PCM512x_SPI)	+= snd-soc-pcm512x-spi.o
 obj-$(CONFIG_SND_SOC_PCM6240)	+= snd-soc-pcm6240.o
 obj-$(CONFIG_SND_SOC_PEB2466)	+= snd-soc-peb2466.o
 obj-$(CONFIG_SND_SOC_RK3308)	+= snd-soc-rk3308.o
+obj-$(CONFIG_SND_SOC_RK3228)	+= snd-soc-rk3228.o
 obj-$(CONFIG_SND_SOC_RK3328)	+= snd-soc-rk3328.o
 obj-$(CONFIG_SND_SOC_RK817)	+= snd-soc-rk817.o
 obj-$(CONFIG_SND_SOC_RL6231)	+= snd-soc-rl6231.o
diff --git a/sound/soc/codecs/rk3228_codec.c b/sound/soc/codecs/rk3228_codec.c
new file mode 100644
index 000000000000..197e7e2e0d8b
--- /dev/null
+++ b/sound/soc/codecs/rk3228_codec.c
@@ -0,0 +1,545 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// rk3228_codec.c  --  rk3228 ALSA Soc Audio driver
+//
+// Copyright (c) 2018, Fuzhou Rockchip Electronics Co., Ltd All rights reserved.
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <sound/pcm_params.h>
+#include <sound/dmaengine_pcm.h>
+#include "rk3228_codec.h"
+
+/*
+ * volume setting
+ * 0: -39dB
+ * 26: 0dB
+ * 31: 6dB
+ * Step: 1.5dB
+ */
+#define OUT_VOLUME	(0x18)
+#define INITIAL_FREQ	(11289600)
+
+struct rk3228_codec_priv {
+	struct regmap *regmap;
+	struct clk *mclk;
+	struct clk *pclk;
+	struct clk *sclk;
+	struct gpio_desc *spk_en_gpio;
+	int spk_depop_time; /* msec */
+};
+
+static const struct reg_default rk3228_codec_reg_defaults[] = {
+	{ CODEC_RESET, 0x03 },
+	{ DAC_INIT_CTRL1, 0x00 },
+	{ DAC_INIT_CTRL2, 0x50 },
+	{ DAC_INIT_CTRL3, 0x0e },
+	{ DAC_PRECHARGE_CTRL, 0x01 },
+	{ DAC_PWR_CTRL, 0x00 },
+	{ DAC_CLK_CTRL, 0x00 },
+	{ HPMIX_CTRL, 0x00 },
+	{ HPOUT_CTRL, 0x00 },
+	{ HPOUTL_GAIN_CTRL, 0x00 },
+	{ HPOUTR_GAIN_CTRL, 0x00 },
+	{ HPOUT_POP_CTRL, 0x11 },
+};
+
+static int rk3228_codec_reset(struct snd_soc_component *component)
+{
+	struct rk3228_codec_priv *rk3228 = snd_soc_component_get_drvdata(component);
+
+	regmap_write(rk3228->regmap, CODEC_RESET, 0);
+	mdelay(10);
+	regmap_write(rk3228->regmap, CODEC_RESET, 0x03);
+
+	return 0;
+}
+
+static int rk3228_set_dai_fmt(struct snd_soc_dai *dai,
+			      unsigned int fmt)
+{
+	struct snd_soc_component *component = dai->component;
+	struct rk3228_codec_priv *rk3228 = snd_soc_component_get_drvdata(component);
+	unsigned int val = 0;
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBC_CFC:
+		val |= PIN_DIRECTION_IN | DAC_I2S_MODE_SLAVE;
+		break;
+	case SND_SOC_DAIFMT_CBP_CFP:
+		val |= PIN_DIRECTION_OUT | DAC_I2S_MODE_MASTER;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	regmap_update_bits(rk3228->regmap, DAC_INIT_CTRL1,
+			   PIN_DIRECTION_MASK | DAC_I2S_MODE_MASK, val);
+
+	val = 0;
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+	case SND_SOC_DAIFMT_DSP_B:
+		val |= DAC_MODE_PCM;
+		break;
+	case SND_SOC_DAIFMT_I2S:
+		val |= DAC_MODE_I2S;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		val |= DAC_MODE_RJM;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		val |= DAC_MODE_LJM;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	regmap_update_bits(rk3228->regmap, DAC_INIT_CTRL2,
+			   DAC_MODE_MASK, val);
+	return 0;
+}
+
+static void rk3228_analog_output(struct rk3228_codec_priv *rk3228, int mute)
+{
+	if (rk3228->spk_en_gpio)
+		gpiod_set_value(rk3228->spk_en_gpio, mute);
+}
+
+static int rk3228_mute_stream(struct snd_soc_dai *dai, int mute, int direction)
+{
+	struct snd_soc_component *component = dai->component;
+	struct rk3228_codec_priv *rk3228 = snd_soc_component_get_drvdata(component);
+	unsigned int val = 0;
+
+	if (direction != SNDRV_PCM_STREAM_PLAYBACK)
+		return 0;
+
+	if (mute)
+		val = HPOUTL_MUTE | HPOUTR_MUTE;
+	else
+		val = HPOUTL_UNMUTE | HPOUTR_UNMUTE;
+
+	regmap_update_bits(rk3228->regmap, HPOUT_CTRL,
+			   HPOUTL_MUTE_MASK | HPOUTR_MUTE_MASK, val);
+	return 0;
+}
+
+static int rk3228_codec_power_on(struct snd_soc_component *component, int wait_ms)
+{
+	struct rk3228_codec_priv *rk3228 = snd_soc_component_get_drvdata(component);
+
+	regmap_update_bits(rk3228->regmap, DAC_PRECHARGE_CTRL,
+			   DAC_CHARGE_XCHARGE_MASK, DAC_CHARGE_PRECHARGE);
+	mdelay(10);
+	regmap_update_bits(rk3228->regmap, DAC_PRECHARGE_CTRL,
+			   DAC_CHARGE_CURRENT_ALL_MASK,
+			   DAC_CHARGE_CURRENT_ALL_ON);
+
+	mdelay(wait_ms);
+
+	return 0;
+}
+
+static int rk3228_codec_power_off(struct snd_soc_component *component, int wait_ms)
+{
+	struct rk3228_codec_priv *rk3228 = snd_soc_component_get_drvdata(component);
+
+	regmap_update_bits(rk3228->regmap, DAC_PRECHARGE_CTRL,
+			   DAC_CHARGE_XCHARGE_MASK, DAC_CHARGE_DISCHARGE);
+	mdelay(10);
+	regmap_update_bits(rk3228->regmap, DAC_PRECHARGE_CTRL,
+			   DAC_CHARGE_CURRENT_ALL_MASK,
+			   DAC_CHARGE_CURRENT_ALL_ON);
+
+	mdelay(wait_ms);
+
+	return 0;
+}
+
+static struct rk3228_reg_msk_val playback_open_list[] = {
+	{ DAC_PWR_CTRL, DAC_PWR_MASK, DAC_PWR_ON },
+	{ DAC_PWR_CTRL, DACL_PATH_REFV_MASK | DACR_PATH_REFV_MASK,
+	  DACL_PATH_REFV_ON | DACR_PATH_REFV_ON },
+	{ DAC_PWR_CTRL, HPOUTL_ZERO_CROSSING_ON | HPOUTR_ZERO_CROSSING_ON,
+	  HPOUTL_ZERO_CROSSING_ON | HPOUTR_ZERO_CROSSING_ON },
+	{ HPOUT_POP_CTRL, HPOUTR_POP_MASK | HPOUTL_POP_MASK,
+	  HPOUTR_POP_WORK | HPOUTL_POP_WORK },
+	{ HPMIX_CTRL, HPMIXL_MASK | HPMIXR_MASK, HPMIXL_EN | HPMIXR_EN },
+	{ HPMIX_CTRL, HPMIXL_INIT_MASK | HPMIXR_INIT_MASK,
+	  HPMIXL_INIT_EN | HPMIXR_INIT_EN },
+	{ HPOUT_CTRL, HPOUTL_MASK | HPOUTR_MASK, HPOUTL_EN | HPOUTR_EN },
+	{ HPOUT_CTRL, HPOUTL_INIT_MASK | HPOUTR_INIT_MASK,
+	  HPOUTL_INIT_EN | HPOUTR_INIT_EN },
+	{ DAC_CLK_CTRL, DACL_REFV_MASK | DACR_REFV_MASK,
+	  DACL_REFV_ON | DACR_REFV_ON },
+	{ DAC_CLK_CTRL, DACL_CLK_MASK | DACR_CLK_MASK,
+	  DACL_CLK_ON | DACR_CLK_ON },
+	{ DAC_CLK_CTRL, DACL_MASK | DACR_MASK, DACL_ON | DACR_ON },
+	{ DAC_CLK_CTRL, DACL_INIT_MASK | DACR_INIT_MASK,
+	  DACL_INIT_ON | DACR_INIT_ON },
+	{ DAC_SELECT, DACL_SELECT_MASK | DACR_SELECT_MASK,
+	  DACL_SELECT | DACR_SELECT },
+	{ HPMIX_CTRL, HPMIXL_INIT2_MASK | HPMIXR_INIT2_MASK,
+	  HPMIXL_INIT2_EN | HPMIXR_INIT2_EN },
+	{ HPOUT_CTRL, HPOUTL_MUTE_MASK | HPOUTR_MUTE_MASK,
+	  HPOUTL_UNMUTE | HPOUTR_UNMUTE },
+};
+
+#define PLAYBACK_OPEN_LIST_LEN ARRAY_SIZE(playback_open_list)
+
+static int rk3228_codec_open_playback(struct snd_soc_component *component)
+{
+	struct rk3228_codec_priv *rk3228 = snd_soc_component_get_drvdata(component);
+	int i = 0;
+
+	regmap_update_bits(rk3228->regmap, DAC_PRECHARGE_CTRL,
+			   DAC_CHARGE_CURRENT_ALL_MASK,
+			   DAC_CHARGE_CURRENT_I);
+
+	for (i = 0; i < PLAYBACK_OPEN_LIST_LEN; i++) {
+		regmap_update_bits(rk3228->regmap,
+				   playback_open_list[i].reg,
+				   playback_open_list[i].msk,
+				   playback_open_list[i].val);
+		mdelay(1);
+	}
+
+	msleep(rk3228->spk_depop_time);
+	rk3228_analog_output(rk3228, 1);
+
+	regmap_update_bits(rk3228->regmap, HPOUTL_GAIN_CTRL,
+			   HPOUTL_GAIN_MASK, OUT_VOLUME);
+	regmap_update_bits(rk3228->regmap, HPOUTR_GAIN_CTRL,
+			   HPOUTR_GAIN_MASK, OUT_VOLUME);
+	return 0;
+}
+
+static struct rk3228_reg_msk_val playback_close_list[] = {
+	{ HPMIX_CTRL, HPMIXL_INIT2_MASK | HPMIXR_INIT2_MASK,
+	  HPMIXL_INIT2_DIS | HPMIXR_INIT2_DIS },
+	{ DAC_SELECT, DACL_SELECT_MASK | DACR_SELECT_MASK,
+	  DACL_DESELECT | DACR_DESELECT },
+	{ HPOUT_CTRL, HPOUTL_MUTE_MASK | HPOUTR_MUTE_MASK,
+	  HPOUTL_MUTE | HPOUTR_MUTE },
+	{ HPOUT_CTRL, HPOUTL_INIT_MASK | HPOUTR_INIT_MASK,
+	  HPOUTL_INIT_DIS | HPOUTR_INIT_DIS },
+	{ HPOUT_CTRL, HPOUTL_MASK | HPOUTR_MASK, HPOUTL_DIS | HPOUTR_DIS },
+	{ HPMIX_CTRL, HPMIXL_MASK | HPMIXR_MASK, HPMIXL_DIS | HPMIXR_DIS },
+	{ DAC_CLK_CTRL, DACL_MASK | DACR_MASK, DACL_OFF | DACR_OFF },
+	{ DAC_CLK_CTRL, DACL_CLK_MASK | DACR_CLK_MASK,
+	  DACL_CLK_OFF | DACR_CLK_OFF },
+	{ DAC_CLK_CTRL, DACL_REFV_MASK | DACR_REFV_MASK,
+	  DACL_REFV_OFF | DACR_REFV_OFF },
+	{ HPOUT_POP_CTRL, HPOUTR_POP_MASK | HPOUTL_POP_MASK,
+	  HPOUTR_POP_XCHARGE | HPOUTL_POP_XCHARGE },
+	{ DAC_PWR_CTRL, DACL_PATH_REFV_MASK | DACR_PATH_REFV_MASK,
+	  DACL_PATH_REFV_OFF | DACR_PATH_REFV_OFF },
+	{ DAC_PWR_CTRL, DAC_PWR_MASK, DAC_PWR_OFF },
+	{ HPMIX_CTRL, HPMIXL_INIT_MASK | HPMIXR_INIT_MASK,
+	  HPMIXL_INIT_DIS | HPMIXR_INIT_DIS },
+	{ DAC_CLK_CTRL, DACL_INIT_MASK | DACR_INIT_MASK,
+	  DACL_INIT_OFF | DACR_INIT_OFF },
+};
+
+#define PLAYBACK_CLOSE_LIST_LEN ARRAY_SIZE(playback_close_list)
+
+static int rk3228_codec_close_playback(struct snd_soc_component *component)
+{
+	struct rk3228_codec_priv *rk3228 = snd_soc_component_get_drvdata(component);
+	int i = 0;
+
+	rk3228_analog_output(rk3228, 0);
+
+	regmap_update_bits(rk3228->regmap, HPOUTL_GAIN_CTRL,
+			   HPOUTL_GAIN_MASK, 0);
+	regmap_update_bits(rk3228->regmap, HPOUTR_GAIN_CTRL,
+			   HPOUTR_GAIN_MASK, 0);
+
+	for (i = 0; i < PLAYBACK_CLOSE_LIST_LEN; i++) {
+		regmap_update_bits(rk3228->regmap,
+				   playback_close_list[i].reg,
+				   playback_close_list[i].msk,
+				   playback_close_list[i].val);
+		mdelay(1);
+	}
+
+	regmap_update_bits(rk3228->regmap, DAC_PRECHARGE_CTRL,
+			   DAC_CHARGE_CURRENT_ALL_MASK,
+			   DAC_CHARGE_CURRENT_I);
+	return 0;
+}
+
+static int rk3228_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;
+	struct rk3228_codec_priv *rk3228 = snd_soc_component_get_drvdata(component);
+	unsigned int val = 0;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		val |= DAC_VDL_16BITS;
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		val |= DAC_VDL_20BITS;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		val |= DAC_VDL_24BITS;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		val |= DAC_VDL_32BITS;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	regmap_update_bits(rk3228->regmap, DAC_INIT_CTRL2, DAC_VDL_MASK, val);
+	val = DAC_WL_32BITS | DAC_RST_DIS;
+	regmap_update_bits(rk3228->regmap, DAC_INIT_CTRL3,
+			   DAC_WL_MASK | DAC_RST_MASK, val);
+
+	return 0;
+}
+
+static int rk3228_pcm_startup(struct snd_pcm_substream *substream,
+			      struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+
+	return rk3228_codec_open_playback(component);
+}
+
+static void rk3228_pcm_shutdown(struct snd_pcm_substream *substream,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+
+	rk3228_codec_close_playback(component);
+}
+
+static struct snd_soc_dai_ops rk3228_dai_ops = {
+	.hw_params = rk3228_hw_params,
+	.set_fmt = rk3228_set_dai_fmt,
+	.mute_stream = rk3228_mute_stream,
+	.startup = rk3228_pcm_startup,
+	.shutdown = rk3228_pcm_shutdown,
+};
+
+static struct snd_soc_dai_driver rk3228_dai[] = {
+	{
+		.name = "rk3228-hifi",
+		.id = RK3228_HIFI,
+		.playback = {
+			.stream_name = "HIFI Playback",
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = SNDRV_PCM_RATE_8000_96000,
+			.formats = (SNDRV_PCM_FMTBIT_S16_LE |
+				    SNDRV_PCM_FMTBIT_S20_3LE |
+				    SNDRV_PCM_FMTBIT_S24_LE |
+				    SNDRV_PCM_FMTBIT_S32_LE),
+		},
+		/*.capture = {
+			.stream_name = "HIFI Capture",
+			.channels_min = 2,
+			.channels_max = 8,
+			.rates = SNDRV_PCM_RATE_8000_96000,
+			.formats = (SNDRV_PCM_FMTBIT_S16_LE |
+				    SNDRV_PCM_FMTBIT_S20_3LE |
+				    SNDRV_PCM_FMTBIT_S24_LE |
+				    SNDRV_PCM_FMTBIT_S32_LE),
+		},*/
+		.ops = &rk3228_dai_ops,
+	},
+};
+
+static int rk3228_codec_probe(struct snd_soc_component *component)
+{
+	rk3228_codec_reset(component);
+	rk3228_codec_power_on(component, 0);
+
+	return 0;
+}
+
+static void rk3228_codec_remove(struct snd_soc_component *component)
+{
+	rk3228_codec_close_playback(component);
+	rk3228_codec_power_off(component, 0);
+}
+
+static struct snd_soc_component_driver soc_codec_dev_rk3228 = {
+	.probe = rk3228_codec_probe,
+	.remove = rk3228_codec_remove,
+};
+
+static bool rk3228_codec_write_read_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CODEC_RESET:
+	case DAC_INIT_CTRL1:
+	case DAC_INIT_CTRL2:
+	case DAC_INIT_CTRL3:
+	case DAC_PRECHARGE_CTRL:
+	case DAC_PWR_CTRL:
+	case DAC_CLK_CTRL:
+	case HPMIX_CTRL:
+	case DAC_SELECT:
+	case HPOUT_CTRL:
+	case HPOUTL_GAIN_CTRL:
+	case HPOUTR_GAIN_CTRL:
+	case HPOUT_POP_CTRL:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool rk3228_codec_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CODEC_RESET:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config rk3228_codec_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.max_register = HPOUT_POP_CTRL,
+	.writeable_reg = rk3228_codec_write_read_reg,
+	.readable_reg = rk3228_codec_write_read_reg,
+	.volatile_reg = rk3228_codec_volatile_reg,
+	.reg_defaults = rk3228_codec_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(rk3228_codec_reg_defaults),
+	.cache_type = REGCACHE_FLAT,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id rk3228codec_of_match[] = {
+	{ .compatible = "rockchip,rk3228-codec", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, rk3228codec_of_match);
+#endif
+
+static int rk3228_platform_probe(struct platform_device *pdev)
+{
+	struct device_node *rk3228_np = pdev->dev.of_node;
+	struct rk3228_codec_priv *rk3228;
+	struct resource *res;
+	void __iomem *base;
+	int ret = 0;
+
+	rk3228 = devm_kzalloc(&pdev->dev, sizeof(*rk3228), GFP_KERNEL);
+	if (!rk3228)
+		return -ENOMEM;
+
+	rk3228->mclk = devm_clk_get(&pdev->dev, "mclk");
+	if (PTR_ERR(rk3228->mclk) == -EPROBE_DEFER)
+		return -EPROBE_DEFER;
+
+	rk3228->pclk = devm_clk_get(&pdev->dev, "pclk");
+	if (IS_ERR(rk3228->pclk))
+		return PTR_ERR(rk3228->pclk);
+
+	rk3228->sclk = devm_clk_get(&pdev->dev, "sclk");
+	if (IS_ERR(rk3228->sclk))
+		return PTR_ERR(rk3228->sclk);
+
+	rk3228->spk_en_gpio = devm_gpiod_get_optional(&pdev->dev,
+						      "spk-en",
+						      GPIOD_OUT_LOW);
+	if (IS_ERR(rk3228->spk_en_gpio))
+		return PTR_ERR(rk3228->spk_en_gpio);
+
+	ret = of_property_read_u32(rk3228_np, "spk-depop-time-ms",
+				   &rk3228->spk_depop_time);
+	if (ret < 0) {
+		dev_info(&pdev->dev, "spk_depop_time use default value.\n");
+		rk3228->spk_depop_time = 100;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	ret = clk_prepare_enable(rk3228->mclk);
+	if (ret)
+		return ret;
+
+	ret = clk_prepare_enable(rk3228->pclk);
+	if (ret < 0)
+		goto err_pclk;
+
+	ret = clk_prepare_enable(rk3228->sclk);
+	if (ret)
+		goto err_sclk;
+
+	clk_set_rate(rk3228->sclk, INITIAL_FREQ);
+
+	rk3228->regmap = devm_regmap_init_mmio(&pdev->dev, base,
+					       &rk3228_codec_regmap_config);
+	if (IS_ERR(rk3228->regmap)) {
+		ret = PTR_ERR(rk3228->regmap);
+		goto err_clk;
+	}
+
+	platform_set_drvdata(pdev, rk3228);
+
+	ret = devm_snd_soc_register_component(&pdev->dev, &soc_codec_dev_rk3228,
+					      rk3228_dai, ARRAY_SIZE(rk3228_dai));
+	if (!ret)
+		return 0;
+
+err_clk:
+	clk_disable_unprepare(rk3228->sclk);
+err_sclk:
+	clk_disable_unprepare(rk3228->pclk);
+err_pclk:
+	clk_disable_unprepare(rk3228->mclk);
+
+	return ret;
+}
+
+static void rk3228_platform_remove(struct platform_device *pdev)
+{
+	struct rk3228_codec_priv *rk3228 = platform_get_drvdata(pdev);
+
+	if (!IS_ERR(rk3228->mclk))
+		clk_disable_unprepare(rk3228->mclk);
+
+	if (!IS_ERR(rk3228->pclk))
+		clk_disable_unprepare(rk3228->pclk);
+
+	if (!IS_ERR(rk3228->sclk))
+		clk_disable_unprepare(rk3228->sclk);
+
+	return;
+}
+
+static struct platform_driver rk3228_codec_driver = {
+	.driver = {
+		   .name = "rk3228-codec",
+		   .of_match_table = of_match_ptr(rk3228codec_of_match),
+	},
+	.probe = rk3228_platform_probe,
+	.remove = rk3228_platform_remove,
+};
+module_platform_driver(rk3228_codec_driver);
+
+MODULE_AUTHOR("Sugar Zhang <sugar.zhang@rock-chips.com>");
+MODULE_DESCRIPTION("ASoC rk3228 codec driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/rk3228_codec.h b/sound/soc/codecs/rk3228_codec.h
new file mode 100644
index 000000000000..7283d0ba86e8
--- /dev/null
+++ b/sound/soc/codecs/rk3228_codec.h
@@ -0,0 +1,218 @@
+/*
+ * rk3228_codec.h  --  rk3228 ALSA Soc Audio driver
+ *
+ * Copyright (c) 2018, Fuzhou Rockchip Electronics Co., Ltd All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _RK3228_CODEC_H
+#define _RK3228_CODEC_H
+
+/* codec register */
+#define CODEC_RESET			(0x00 << 2)
+#define DAC_INIT_CTRL1			(0x03 << 2)
+#define DAC_INIT_CTRL2			(0x04 << 2)
+#define DAC_INIT_CTRL3			(0x05 << 2)
+#define DAC_PRECHARGE_CTRL		(0x22 << 2)
+#define DAC_PWR_CTRL			(0x23 << 2)
+#define DAC_CLK_CTRL			(0x24 << 2)
+#define HPMIX_CTRL			(0x25 << 2)
+#define DAC_SELECT			(0x26 << 2)
+#define HPOUT_CTRL			(0x27 << 2)
+#define HPOUTL_GAIN_CTRL		(0x28 << 2)
+#define HPOUTR_GAIN_CTRL		(0x29 << 2)
+#define HPOUT_POP_CTRL			(0x2a << 2)
+
+/* REG00: CODEC_RESET */
+#define PWR_RST_BYPASS_DIS		BIT(6)
+#define PWR_RST_BYPASS_EN		BIT(6)
+#define DIG_CORE_RST			(0 << 1)
+#define DIG_CORE_WORK			BIT(1)
+#define SYS_RST				(0)
+#define SYS_WORK			BIT(0)
+
+/* REG03: DAC_INIT_CTRL1 */
+#define PIN_DIRECTION_MASK		BIT(5)
+#define PIN_DIRECTION_IN		(0 << 5)
+#define PIN_DIRECTION_OUT		BIT(5)
+#define DAC_I2S_MODE_MASK		BIT(4)
+#define DAC_I2S_MODE_SLAVE		(0 << 4)
+#define DAC_I2S_MODE_MASTER		BIT(4)
+
+/* REG04: DAC_INIT_CTRL2 */
+#define DAC_I2S_LRP_MASK		BIT(7)
+#define DAC_I2S_LRP_NORMAL		(0 << 7)
+#define DAC_I2S_LRP_REVERSAL		BIT(7)
+#define DAC_VDL_MASK			(3 << 5)
+#define DAC_VDL_16BITS			(0 << 5)
+#define DAC_VDL_20BITS			BIT(5)
+#define DAC_VDL_24BITS			(2 << 5)
+#define DAC_VDL_32BITS			(3 << 5)
+#define DAC_MODE_MASK			(3 << 3)
+#define DAC_MODE_RJM			(0 << 3)
+#define DAC_MODE_LJM			BIT(3)
+#define DAC_MODE_I2S			(2 << 3)
+#define DAC_MODE_PCM			(3 << 3)
+#define DAC_LR_SWAP_MASK		BIT(2)
+#define DAC_LR_SWAP_DIS			(0 << 2)
+#define DAC_LR_SWAP_EN			BIT(2)
+
+/* REG05: DAC_INIT_CTRL3 */
+#define DAC_WL_MASK			(3 << 2)
+#define DAC_WL_16BITS			(0 << 2)
+#define DAC_WL_20BITS			BIT(2)
+#define DAC_WL_24BITS			(2 << 2)
+#define DAC_WL_32BITS			(3 << 2)
+#define DAC_RST_MASK			BIT(1)
+#define DAC_RST_EN			(0 << 1)
+#define DAC_RST_DIS			BIT(1)
+#define DAC_BCP_MASK			BIT(0)
+#define DAC_BCP_NORMAL			(0 << 0)
+#define DAC_BCP_REVERSAL		BIT(0)
+
+/* REG22: DAC_PRECHARGE_CTRL */
+#define DAC_CHARGE_PRECHARGE		BIT(7)
+#define DAC_CHARGE_DISCHARGE		(0 << 7)
+#define DAC_CHARGE_XCHARGE_MASK		BIT(7)
+#define DAC_CHARGE_CURRENT_64I		BIT(6)
+#define DAC_CHARGE_CURRENT_64I_MASK	BIT(6)
+#define DAC_CHARGE_CURRENT_32I		BIT(5)
+#define DAC_CHARGE_CURRENT_32I_MASK	BIT(5)
+#define DAC_CHARGE_CURRENT_16I		BIT(4)
+#define DAC_CHARGE_CURRENT_16I_MASK	BIT(4)
+#define DAC_CHARGE_CURRENT_08I		BIT(3)
+#define DAC_CHARGE_CURRENT_08I_MASK	BIT(3)
+#define DAC_CHARGE_CURRENT_04I		BIT(2)
+#define DAC_CHARGE_CURRENT_04I_MASK	BIT(2)
+#define DAC_CHARGE_CURRENT_02I		BIT(1)
+#define DAC_CHARGE_CURRENT_02I_MASK	BIT(1)
+#define DAC_CHARGE_CURRENT_I		BIT(0)
+#define DAC_CHARGE_CURRENT_I_MASK	BIT(0)
+#define DAC_CHARGE_CURRENT_ALL_MASK	(0x7f)
+#define DAC_CHARGE_CURRENT_ALL_OFF	(0x0)
+#define DAC_CHARGE_CURRENT_ALL_ON	(0x7f)
+
+/* REG23: DAC_PWR_CTRL */
+#define DAC_PWR_OFF			(0 << 6)
+#define DAC_PWR_ON			BIT(6)
+#define DAC_PWR_MASK			BIT(6)
+#define DACL_PATH_REFV_OFF		(0 << 5)
+#define DACL_PATH_REFV_ON		BIT(5)
+#define DACL_PATH_REFV_MASK		BIT(5)
+#define HPOUTL_ZERO_CROSSING_OFF	(0 << 4)
+#define HPOUTL_ZERO_CROSSING_ON		BIT(4)
+#define DACR_PATH_REFV_OFF		(0 << 1)
+#define DACR_PATH_REFV_ON		BIT(1)
+#define DACR_PATH_REFV_MASK		BIT(1)
+#define HPOUTR_ZERO_CROSSING_OFF	(0 << 0)
+#define HPOUTR_ZERO_CROSSING_ON		BIT(0)
+
+/* REG24: DAC_CLK_CTRL */
+#define DACL_REFV_OFF			(0 << 7)
+#define DACL_REFV_ON			BIT(7)
+#define DACL_REFV_MASK			BIT(7)
+#define DACL_CLK_OFF			(0 << 6)
+#define DACL_CLK_ON			BIT(6)
+#define DACL_CLK_MASK			BIT(6)
+#define DACL_OFF			(0 << 5)
+#define DACL_ON				BIT(5)
+#define DACL_MASK			BIT(5)
+#define DACL_INIT_OFF			(0 << 4)
+#define DACL_INIT_ON			BIT(4)
+#define DACL_INIT_MASK			BIT(4)
+#define DACR_REFV_OFF			(0 << 3)
+#define DACR_REFV_ON			BIT(3)
+#define DACR_REFV_MASK			BIT(3)
+#define DACR_CLK_OFF			(0 << 2)
+#define DACR_CLK_ON			BIT(2)
+#define DACR_CLK_MASK			BIT(2)
+#define DACR_OFF			(0 << 1)
+#define DACR_ON				BIT(1)
+#define DACR_MASK			BIT(1)
+#define DACR_INIT_OFF			(0 << 0)
+#define DACR_INIT_ON			BIT(0)
+#define DACR_INIT_MASK			BIT(0)
+
+/* REG25: HPMIX_CTRL*/
+#define HPMIXL_DIS			(0 << 6)
+#define HPMIXL_EN			BIT(6)
+#define HPMIXL_MASK			BIT(6)
+#define HPMIXL_INIT_DIS			(0 << 5)
+#define HPMIXL_INIT_EN			BIT(5)
+#define HPMIXL_INIT_MASK		BIT(5)
+#define HPMIXL_INIT2_DIS		(0 << 4)
+#define HPMIXL_INIT2_EN			BIT(4)
+#define HPMIXL_INIT2_MASK		BIT(4)
+#define HPMIXR_DIS			(0 << 2)
+#define HPMIXR_EN			BIT(2)
+#define HPMIXR_MASK			BIT(2)
+#define HPMIXR_INIT_DIS			(0 << 1)
+#define HPMIXR_INIT_EN			BIT(1)
+#define HPMIXR_INIT_MASK		BIT(1)
+#define HPMIXR_INIT2_DIS		(0 << 0)
+#define HPMIXR_INIT2_EN			BIT(0)
+#define HPMIXR_INIT2_MASK		BIT(0)
+
+/* REG26: DAC_SELECT */
+#define DACL_SELECT			BIT(4)
+#define DACL_SELECT_MASK		BIT(4)
+#define DACL_DESELECT			(0 << 4)
+#define DACR_SELECT			BIT(0)
+#define DACR_SELECT_MASK		BIT(0)
+#define DACR_DESELECT			(0 << 0)
+
+/* REG27: HPOUT_CTRL */
+#define HPOUTL_DIS			(0 << 7)
+#define HPOUTL_EN			BIT(7)
+#define HPOUTL_MASK			BIT(7)
+#define HPOUTL_INIT_DIS			(0 << 6)
+#define HPOUTL_INIT_EN			BIT(6)
+#define HPOUTL_INIT_MASK		BIT(6)
+#define HPOUTL_MUTE			(0 << 5)
+#define HPOUTL_UNMUTE			BIT(5)
+#define HPOUTL_MUTE_MASK		BIT(5)
+#define HPOUTR_DIS			(0 << 4)
+#define HPOUTR_EN			BIT(4)
+#define HPOUTR_MASK			BIT(4)
+#define HPOUTR_INIT_DIS			(0 << 3)
+#define HPOUTR_INIT_EN			BIT(3)
+#define HPOUTR_INIT_MASK		BIT(3)
+#define HPOUTR_MUTE			(0 << 2)
+#define HPOUTR_UNMUTE			BIT(2)
+#define HPOUTR_MUTE_MASK		BIT(2)
+
+/* REG28: HPOUTL_GAIN_CTRL */
+#define HPOUTL_GAIN_MASK		(0X1f << 0)
+
+/* REG29: HPOUTR_GAIN_CTRL */
+#define HPOUTR_GAIN_MASK		(0X1f << 0)
+
+/* REG2a: HPOUT_POP_CTRL */
+#define HPOUTR_POP_XCHARGE		BIT(4)
+#define HPOUTR_POP_WORK			(2 << 4)
+#define HPOUTR_POP_MASK			(3 << 4)
+#define HPOUTL_POP_XCHARGE		BIT(0)
+#define HPOUTL_POP_WORK			(2 << 0)
+#define HPOUTL_POP_MASK			(3 << 0)
+
+#define RK3228_HIFI			(0)
+
+struct rk3228_reg_msk_val {
+	unsigned int reg;
+	unsigned int msk;
+	unsigned int val;
+};
+
+#endif
-- 
2.34.1

