diff mbox series

[3/6] clk: realtek: add common clock support for Realtek SoCs

Message ID 20191203074513.9416-4-james.tai@realtek.com (mailing list archive)
State Changes Requested, archived
Headers show
Series None | expand

Commit Message

James Tai [戴志峰] Dec. 3, 2019, 7:45 a.m. UTC
From: cylee12 <cylee12@realtek.com>

This patch adds common clock support for Realtek SoCs, including PLLs,
Mux clocks and Gate clocks.

Signed-off-by: Cheng-Yu Lee <cylee12@realtek.com>
Signed-off-by: James Tai <james.tai@realtek.com>
---
 drivers/clk/Kconfig                   |   1 +
 drivers/clk/Makefile                  |   1 +
 drivers/clk/realtek/Kconfig           |  10 +
 drivers/clk/realtek/Makefile          |   9 +
 drivers/clk/realtek/clk-pll-dif.c     |  81 ++++++
 drivers/clk/realtek/clk-pll-psaud.c   | 120 ++++++++
 drivers/clk/realtek/clk-pll.c         | 400 ++++++++++++++++++++++++++
 drivers/clk/realtek/clk-pll.h         | 151 ++++++++++
 drivers/clk/realtek/clk-regmap-gate.c |  89 ++++++
 drivers/clk/realtek/clk-regmap-gate.h |  26 ++
 drivers/clk/realtek/clk-regmap-mux.c  |  63 ++++
 drivers/clk/realtek/clk-regmap-mux.h  |  26 ++
 drivers/clk/realtek/common.c          | 320 +++++++++++++++++++++
 drivers/clk/realtek/common.h          | 123 ++++++++
 14 files changed, 1420 insertions(+)
 create mode 100644 drivers/clk/realtek/Kconfig
 create mode 100644 drivers/clk/realtek/Makefile
 create mode 100644 drivers/clk/realtek/clk-pll-dif.c
 create mode 100644 drivers/clk/realtek/clk-pll-psaud.c
 create mode 100644 drivers/clk/realtek/clk-pll.c
 create mode 100644 drivers/clk/realtek/clk-pll.h
 create mode 100644 drivers/clk/realtek/clk-regmap-gate.c
 create mode 100644 drivers/clk/realtek/clk-regmap-gate.h
 create mode 100644 drivers/clk/realtek/clk-regmap-mux.c
 create mode 100644 drivers/clk/realtek/clk-regmap-mux.h
 create mode 100644 drivers/clk/realtek/common.c
 create mode 100644 drivers/clk/realtek/common.h

Comments

Andreas Färber Dec. 3, 2019, 3:28 p.m. UTC | #1
Hi James,

[dropping Palmer]

Am 03.12.19 um 08:45 schrieb James Tai:
> From: cylee12 <cylee12@realtek.com>

Please fix the author name.

> 
> This patch adds common clock support for Realtek SoCs, including PLLs,
> Mux clocks and Gate clocks.

Can you be more specific here, please? Is this compatible back to
RTD1195 or RTD1295 or just 1619 forward? I only see RTD1619 in 5/6. And
not a single .dtsi patch is included in this series for testing it.

> 
> Signed-off-by: Cheng-Yu Lee <cylee12@realtek.com>
> Signed-off-by: James Tai <james.tai@realtek.com>
> ---
>  drivers/clk/Kconfig                   |   1 +
>  drivers/clk/Makefile                  |   1 +
>  drivers/clk/realtek/Kconfig           |  10 +
>  drivers/clk/realtek/Makefile          |   9 +
>  drivers/clk/realtek/clk-pll-dif.c     |  81 ++++++
>  drivers/clk/realtek/clk-pll-psaud.c   | 120 ++++++++
>  drivers/clk/realtek/clk-pll.c         | 400 ++++++++++++++++++++++++++
>  drivers/clk/realtek/clk-pll.h         | 151 ++++++++++
>  drivers/clk/realtek/clk-regmap-gate.c |  89 ++++++
>  drivers/clk/realtek/clk-regmap-gate.h |  26 ++
>  drivers/clk/realtek/clk-regmap-mux.c  |  63 ++++
>  drivers/clk/realtek/clk-regmap-mux.h  |  26 ++
>  drivers/clk/realtek/common.c          | 320 +++++++++++++++++++++
>  drivers/clk/realtek/common.h          | 123 ++++++++
>  14 files changed, 1420 insertions(+)

This patch is way too large for me to review. Please split this up
further into logically self-contained, compile-testable patches.

>  create mode 100644 drivers/clk/realtek/Kconfig
>  create mode 100644 drivers/clk/realtek/Makefile
>  create mode 100644 drivers/clk/realtek/clk-pll-dif.c
>  create mode 100644 drivers/clk/realtek/clk-pll-psaud.c
>  create mode 100644 drivers/clk/realtek/clk-pll.c
>  create mode 100644 drivers/clk/realtek/clk-pll.h
>  create mode 100644 drivers/clk/realtek/clk-regmap-gate.c
>  create mode 100644 drivers/clk/realtek/clk-regmap-gate.h
>  create mode 100644 drivers/clk/realtek/clk-regmap-mux.c
>  create mode 100644 drivers/clk/realtek/clk-regmap-mux.h
>  create mode 100644 drivers/clk/realtek/common.c
>  create mode 100644 drivers/clk/realtek/common.h
> 
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index c44247d0b83e..8e06487440ce 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -317,6 +317,7 @@ source "drivers/clk/mediatek/Kconfig"
>  source "drivers/clk/meson/Kconfig"
>  source "drivers/clk/mvebu/Kconfig"
>  source "drivers/clk/qcom/Kconfig"
> +source "drivers/clk/realtek/Kconfig"
>  source "drivers/clk/renesas/Kconfig"
>  source "drivers/clk/samsung/Kconfig"
>  source "drivers/clk/sifive/Kconfig"
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 0138fb14e6f8..71ea17f97f7d 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -95,6 +95,7 @@ obj-$(CONFIG_COMMON_CLK_NXP)		+= nxp/
>  obj-$(CONFIG_MACH_PISTACHIO)		+= pistachio/
>  obj-$(CONFIG_COMMON_CLK_PXA)		+= pxa/
>  obj-$(CONFIG_COMMON_CLK_QCOM)		+= qcom/
> +obj-$(CONFIG_COMMON_CLK_REALTEK)	+= realtek/

Should we take the Renesas approach here and allow for COMPILE_TEST?

>  obj-y					+= renesas/
>  obj-$(CONFIG_ARCH_ROCKCHIP)		+= rockchip/
>  obj-$(CONFIG_COMMON_CLK_SAMSUNG)	+= samsung/
> diff --git a/drivers/clk/realtek/Kconfig b/drivers/clk/realtek/Kconfig
> new file mode 100644
> index 000000000000..5bca757dddfa
> --- /dev/null
> +++ b/drivers/clk/realtek/Kconfig
> @@ -0,0 +1,10 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +config COMMON_CLK_REALTEK

I would personally think that it's not necessary to prefix with COMMON_
here, even if based on CONFIG_COMMON_CLK framework, since Realtek is no
longer common. Stephen/Michael?

Which brings us to the next aspect, is this really universally common
for Realtek? Are they compatible with old MIPS SoCs and Arm SoCs from
departments other than DHC? (e.g., Smart TV)
If the answer to any of these is no, then please rename the Kconfig
symbol to the oldest SoC for uniqueness, e.g., RTD1195.

> +	bool "Clock driver for realtek"

Spelling.

> +	select MFD_SYSCON

Please add help text and include SoC names.

> +
> +config CLK_PLL_PSAUD

Too generic name.

> +	bool
> +
> +config CLK_PLL_DIF

Ditto.

> +	bool
> diff --git a/drivers/clk/realtek/Makefile b/drivers/clk/realtek/Makefile
> new file mode 100644
> index 000000000000..050d450db067
> --- /dev/null
> +++ b/drivers/clk/realtek/Makefile
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +obj-$(CONFIG_COMMON_CLK_REALTEK) += clk-rtk.o
> +
> +clk-rtk-y += common.o
> +clk-rtk-y += clk-regmap-mux.o
> +clk-rtk-y += clk-regmap-gate.o
> +clk-rtk-y += clk-pll.o
> +clk-rtk-$(CONFIG_CLK_PLL_PSAUD) += clk-pll-psaud.o
> +clk-rtk-$(CONFIG_CLK_PLL_DIF) += clk-pll-dif.o
> diff --git a/drivers/clk/realtek/clk-pll-dif.c b/drivers/clk/realtek/clk-pll-dif.c
> new file mode 100644
> index 000000000000..d19efef2626e
> --- /dev/null
> +++ b/drivers/clk/realtek/clk-pll-dif.c
> @@ -0,0 +1,81 @@
> +// SPDX-License-Identifier: GPL-2.0-only

Can you relicense this code as GPL-2.0-or-later? For Makefile and
Kconfig it doesn't matter, just for low-level code that could become
relevant for debuggers like OpenOCD, which is licensed GPLv2+.

> +/*

Care to elaborate here what "dif" is? :)

> + * Copyright (c) 2019 Realtek Semiconductor Corporation
> + * Author: Cheng-Yu Lee <cylee12@realtek.com>
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/clk.h>
> +#include <linux/spinlock.h>
> +#include <linux/delay.h>

Insert white line here?

> +#include "common.h"
> +#include "clk-pll.h"
> +
> +static int clk_pll_dif_enable(struct clk_hw *hw)
> +{
> +	struct clk_pll_dif *pll = to_clk_pll_dif(hw);
> +
> +	pr_debug("%pC: %s\n", hw->clk, __func__);

Please review debug and info messages for whether they are still needed
- in particular for info below I assume no.

> +
> +	clk_regmap_write(&pll->clkr, pll->pll_ofs + 0x0C, 0x00000048);
> +	clk_regmap_write(&pll->clkr, pll->pll_ofs + 0x08, 0x00020c00);
> +	clk_regmap_write(&pll->clkr, pll->pll_ofs + 0x04, 0x204004ca);
> +	clk_regmap_write(&pll->clkr, pll->pll_ofs + 0x00, 0x8000a000);

This is way too much dark magic!

Start with giving the offsets symbolic names, please.

Next, construct the value from symbolic constants. You will see me use
BIT() and GENMASK() macros elsewhere; please adopt those conventions.

> +	udelay(100);

Is this from some internal datasheet? Otherwise, from some MCU clocks
that I've previously implemented, I would expect there to be some status
bit indicating readyness that we should poll rather than trusting a
hardcoded delay. I don't see a single read below, nor any explanatory
comment.

> +
> +	clk_regmap_write(&pll->clkr, pll->pll_ofs + 0x08, 0x00420c00);
> +	udelay(50);
> +
> +	clk_regmap_write(&pll->clkr, pll->pll_ofs + 0x08, 0x00420c03);
> +	udelay(200);
> +
> +	clk_regmap_write(&pll->clkr, pll->pll_ofs + 0x0C, 0x00000078);
> +	udelay(100);
> +
> +	clk_regmap_write(&pll->clkr, pll->pll_ofs + 0x04, 0x204084ca);
> +
> +	/* ssc control */

This lonely comment seems kind of redundant with ssc_ofs vs. pll_ofs.

> +	clk_regmap_write(&pll->clkr, pll->ssc_ofs + 0x00, 0x00000004);
> +	clk_regmap_write(&pll->clkr, pll->ssc_ofs + 0x04, 0x00006800);
> +	clk_regmap_write(&pll->clkr, pll->ssc_ofs + 0x0C, 0x00000000);
> +	clk_regmap_write(&pll->clkr, pll->ssc_ofs + 0x10, 0x00000000);
> +	clk_regmap_write(&pll->clkr, pll->ssc_ofs + 0x08, 0x001e1f98);
> +	clk_regmap_write(&pll->clkr, pll->ssc_ofs + 0x00, 0x00000005);
> +	pll->status = 1;
> +
> +	return 0;
> +}
[snip]

I'll stop reviewing here and am waiting for a v2.

Thanks,
Andreas
diff mbox series

Patch

diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index c44247d0b83e..8e06487440ce 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -317,6 +317,7 @@  source "drivers/clk/mediatek/Kconfig"
 source "drivers/clk/meson/Kconfig"
 source "drivers/clk/mvebu/Kconfig"
 source "drivers/clk/qcom/Kconfig"
+source "drivers/clk/realtek/Kconfig"
 source "drivers/clk/renesas/Kconfig"
 source "drivers/clk/samsung/Kconfig"
 source "drivers/clk/sifive/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 0138fb14e6f8..71ea17f97f7d 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -95,6 +95,7 @@  obj-$(CONFIG_COMMON_CLK_NXP)		+= nxp/
 obj-$(CONFIG_MACH_PISTACHIO)		+= pistachio/
 obj-$(CONFIG_COMMON_CLK_PXA)		+= pxa/
 obj-$(CONFIG_COMMON_CLK_QCOM)		+= qcom/
+obj-$(CONFIG_COMMON_CLK_REALTEK)	+= realtek/
 obj-y					+= renesas/
 obj-$(CONFIG_ARCH_ROCKCHIP)		+= rockchip/
 obj-$(CONFIG_COMMON_CLK_SAMSUNG)	+= samsung/
diff --git a/drivers/clk/realtek/Kconfig b/drivers/clk/realtek/Kconfig
new file mode 100644
index 000000000000..5bca757dddfa
--- /dev/null
+++ b/drivers/clk/realtek/Kconfig
@@ -0,0 +1,10 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+config COMMON_CLK_REALTEK
+	bool "Clock driver for realtek"
+	select MFD_SYSCON
+
+config CLK_PLL_PSAUD
+	bool
+
+config CLK_PLL_DIF
+	bool
diff --git a/drivers/clk/realtek/Makefile b/drivers/clk/realtek/Makefile
new file mode 100644
index 000000000000..050d450db067
--- /dev/null
+++ b/drivers/clk/realtek/Makefile
@@ -0,0 +1,9 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_COMMON_CLK_REALTEK) += clk-rtk.o
+
+clk-rtk-y += common.o
+clk-rtk-y += clk-regmap-mux.o
+clk-rtk-y += clk-regmap-gate.o
+clk-rtk-y += clk-pll.o
+clk-rtk-$(CONFIG_CLK_PLL_PSAUD) += clk-pll-psaud.o
+clk-rtk-$(CONFIG_CLK_PLL_DIF) += clk-pll-dif.o
diff --git a/drivers/clk/realtek/clk-pll-dif.c b/drivers/clk/realtek/clk-pll-dif.c
new file mode 100644
index 000000000000..d19efef2626e
--- /dev/null
+++ b/drivers/clk/realtek/clk-pll-dif.c
@@ -0,0 +1,81 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2019 Realtek Semiconductor Corporation
+ * Author: Cheng-Yu Lee <cylee12@realtek.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include "common.h"
+#include "clk-pll.h"
+
+static int clk_pll_dif_enable(struct clk_hw *hw)
+{
+	struct clk_pll_dif *pll = to_clk_pll_dif(hw);
+
+	pr_debug("%pC: %s\n", hw->clk, __func__);
+
+	clk_regmap_write(&pll->clkr, pll->pll_ofs + 0x0C, 0x00000048);
+	clk_regmap_write(&pll->clkr, pll->pll_ofs + 0x08, 0x00020c00);
+	clk_regmap_write(&pll->clkr, pll->pll_ofs + 0x04, 0x204004ca);
+	clk_regmap_write(&pll->clkr, pll->pll_ofs + 0x00, 0x8000a000);
+	udelay(100);
+
+	clk_regmap_write(&pll->clkr, pll->pll_ofs + 0x08, 0x00420c00);
+	udelay(50);
+
+	clk_regmap_write(&pll->clkr, pll->pll_ofs + 0x08, 0x00420c03);
+	udelay(200);
+
+	clk_regmap_write(&pll->clkr, pll->pll_ofs + 0x0C, 0x00000078);
+	udelay(100);
+
+	clk_regmap_write(&pll->clkr, pll->pll_ofs + 0x04, 0x204084ca);
+
+	/* ssc control */
+	clk_regmap_write(&pll->clkr, pll->ssc_ofs + 0x00, 0x00000004);
+	clk_regmap_write(&pll->clkr, pll->ssc_ofs + 0x04, 0x00006800);
+	clk_regmap_write(&pll->clkr, pll->ssc_ofs + 0x0C, 0x00000000);
+	clk_regmap_write(&pll->clkr, pll->ssc_ofs + 0x10, 0x00000000);
+	clk_regmap_write(&pll->clkr, pll->ssc_ofs + 0x08, 0x001e1f98);
+	clk_regmap_write(&pll->clkr, pll->ssc_ofs + 0x00, 0x00000005);
+	pll->status = 1;
+
+	return 0;
+}
+
+static void clk_pll_dif_disable(struct clk_hw *hw)
+{
+	struct clk_pll_dif *pll = to_clk_pll_dif(hw);
+
+	pr_debug("%pC: %s\n", hw->clk, __func__);
+	clk_regmap_update(&pll->clkr, pll->pll_ofs + 0x04, 0x00080000, 0x0);
+	clk_regmap_update(&pll->clkr, pll->pll_ofs + 0x08, 0x00400C03, 0x0);
+	clk_regmap_update(&pll->clkr, pll->pll_ofs + 0x0C, 0x00000038, 0x0);
+
+	clk_regmap_write(&pll->clkr, pll->ssc_ofs + 0x00, 0x00000004);
+	pll->status = 0;
+}
+
+static int clk_pll_dif_is_enabled(struct clk_hw *hw)
+{
+	struct clk_pll_dif *pll = to_clk_pll_dif(hw);
+
+	return pll->status;
+}
+
+static void clk_pll_dif_disable_unused(struct clk_hw *hw)
+{
+	pr_info("%pC: %s\n", hw->clk, __func__);
+	clk_pll_dif_disable(hw);
+}
+
+const struct clk_ops clk_pll_dif_ops = {
+	.enable           = clk_pll_dif_enable,
+	.disable          = clk_pll_dif_disable,
+	.disable_unused   = clk_pll_dif_disable_unused,
+	.is_enabled       = clk_pll_dif_is_enabled,
+};
+
diff --git a/drivers/clk/realtek/clk-pll-psaud.c b/drivers/clk/realtek/clk-pll-psaud.c
new file mode 100644
index 000000000000..757a3320ebb7
--- /dev/null
+++ b/drivers/clk/realtek/clk-pll-psaud.c
@@ -0,0 +1,120 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * clk-pll-psaud.c - PLL_PSAUDXA
+ *
+ * Copyright (c) 2019 Realtek Semiconductor Corporation
+ * Author: Cheng-Yu Lee <cylee12@realtek.com>
+ */
+
+#include <linux/clk-provider.h>
+#include "common.h"
+#include "clk-pll.h"
+
+static int clk_pll_psaud_enable(struct clk_hw *hw)
+{
+	struct clk_pll_psaud *pll = to_clk_pll_psaud(hw);
+	u32 mask = 0, val = 0;
+
+	if (pll->id == CLK_PLL_PSAUD1A) {
+		mask = 0x3;
+		val = 0x1;
+	} else {
+		mask = 0xc;
+		val = 0x4;
+	}
+	clk_regmap_update(&pll->clkr, pll->reg + 4, mask, val);
+	return 0;
+}
+
+static void clk_pll_psaud_disable(struct clk_hw *hw)
+{
+	struct clk_pll_psaud *pll = to_clk_pll_psaud(hw);
+	u32 mask = 0, val = 0;
+
+	if (pll->id == CLK_PLL_PSAUD1A) {
+		mask = 0x3;
+		val = 0x3;
+	} else {
+		mask = 0xc;
+		val = 0xc;
+	}
+	clk_regmap_update(&pll->clkr, pll->reg + 4, mask, val);
+}
+
+static void clk_pll_psaud_disable_unused(struct clk_hw *hw)
+{
+	pr_info("%pC: %s\n", hw->clk, __func__);
+	clk_pll_psaud_disable(hw);
+}
+
+static int clk_pll_psaud_is_enabled(struct clk_hw *hw)
+{
+	struct clk_pll_psaud *pll = to_clk_pll_psaud(hw);
+	u32 val;
+
+	val = clk_regmap_read(&pll->clkr, pll->reg + 4);
+	if (pll->id == CLK_PLL_PSAUD1A)
+		val &= 0x3;
+	else
+		val >>= 2;
+	return val == 0x1;
+}
+
+static long clk_pll_psaud_round_rate(struct clk_hw *hw,
+				     unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	return 49192000;
+}
+
+static int clk_pll_psaud_set_rate(struct clk_hw *hw,
+				  unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct clk_pll_psaud *pll = to_clk_pll_psaud(hw);
+	u32 rsel = 0;
+	u32 mask = 0, val = 0;
+
+	if (WARN_ON_ONCE(rate != 45158400 && rate != 49192000))
+		return -EINVAL;
+
+	if (rate == 45158400)
+		rsel = 1;
+	if (pll->id == CLK_PLL_PSAUD1A) {
+		val  = 0x6a0 | (rsel << 8);
+		mask = 0x7e0;
+	} else {
+		val  = 0x19 | (rsel << 2);
+		mask = 0x1f;
+	}
+	clk_regmap_update(&pll->clkr, pll->reg, mask, val);
+	return 0;
+}
+
+static unsigned long clk_pll_psaud_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct clk_pll_psaud *pll = to_clk_pll_psaud(hw);
+	u32 val;
+	u32 rsel = 0;
+
+	val = clk_regmap_read(&pll->clkr, pll->reg);
+
+	if (pll->id == CLK_PLL_PSAUD1A)
+		rsel = !!(val & BIT(8));
+	else
+		rsel = !!(val & BIT(2));
+
+	return rsel ? 45158400 : 49192000;
+}
+
+const struct clk_ops clk_pll_psaud_ops = {
+	.enable         = clk_pll_psaud_enable,
+	.disable        = clk_pll_psaud_disable,
+	.disable_unused = clk_pll_psaud_disable_unused,
+	.is_enabled     = clk_pll_psaud_is_enabled,
+	.set_rate       = clk_pll_psaud_set_rate,
+	.round_rate     = clk_pll_psaud_round_rate,
+	.recalc_rate    = clk_pll_psaud_recalc_rate,
+};
+
diff --git a/drivers/clk/realtek/clk-pll.c b/drivers/clk/realtek/clk-pll.c
new file mode 100644
index 000000000000..dd90bfc011ec
--- /dev/null
+++ b/drivers/clk/realtek/clk-pll.c
@@ -0,0 +1,400 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2017-2018 Realtek Semiconductor Corporation
+ * Author: Cheng-Yu Lee <cylee12@realtek.com>
+ */
+
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/debugfs.h>
+#include "common.h"
+#include "clk-pll.h"
+
+static int clk_pll_debug_rate_u64_set(void *data, u64 val)
+{
+	struct clk_hw *hw = data;
+
+	clk_set_rate(hw->clk, (unsigned long)(val));
+	return 0;
+}
+
+static int clk_pll_debug_rate_u64_get(void *data, u64 *val)
+{
+	struct clk_hw *hw = data;
+
+	*val = (u64)clk_get_rate(hw->clk);
+	return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(clk_pll_debut_rate_op, clk_pll_debug_rate_u64_get,
+			clk_pll_debug_rate_u64_set, "%llu\n");
+
+static void clk_pll_debug_init(struct clk_hw *hw, struct dentry *d)
+{
+	debugfs_create_file("debug_rate", 0644, d, hw, &clk_pll_debut_rate_op);
+}
+
+static const struct freq_table *ftbl_find_by_rate(const struct freq_table *ftbl,
+						  unsigned long rate)
+{
+	unsigned long best_rate = 0;
+	const struct freq_table *best = NULL;
+
+	for ( ; !IS_FREQ_TABLE_END(ftbl); ftbl++) {
+		if (ftbl->rate == rate)
+			return ftbl;
+
+		if (ftbl->rate > rate)
+			continue;
+
+		if ((rate - best_rate) > (rate - ftbl->rate)) {
+			best_rate = ftbl->rate;
+			best = ftbl;
+		}
+	}
+
+	return best;
+}
+
+static const struct freq_table *ftbl_find_by_val(const struct freq_table *ftbl,
+						 uint32_t value)
+{
+	while (!IS_FREQ_TABLE_END(ftbl)) {
+		if (ftbl->val == (value))
+			return ftbl;
+		ftbl++;
+	}
+	return NULL;
+};
+
+static const struct div_table *dtbl_find_by_rate(const struct div_table *dtbl,
+						 unsigned long rate)
+{
+	while (!IS_DIV_TABLE_END(dtbl)) {
+		if (rate >= dtbl->rate)
+			return dtbl;
+		dtbl++;
+	}
+	return NULL;
+}
+
+static const struct div_table *dtbl_find_by_val(const struct div_table *dtbl,
+						uint32_t val)
+{
+	while (!IS_DIV_TABLE_END(dtbl)) {
+		if (val == dtbl->val)
+			return dtbl;
+		dtbl++;
+	}
+	return NULL;
+}
+
+static void __clk_pll_set_pow_reg(struct clk_pll *clkp, int on)
+{
+	uint32_t pow = (clkp->pow_loc == CLK_PLL_CONF_POW_LOC_CTL3) ? 0x8 : 0x4;
+
+	if (on) {
+		clk_regmap_update(&clkp->clkr, clkp->pll_ofs + pow, 0x7, 0x3);
+		if (clkp->freq_loc == CLK_PLL_CONF_FREQ_LOC_SSC1) {
+			uint32_t val;
+
+			val = clk_regmap_read(&clkp->clkr, clkp->ssc_ofs + 0x0);
+
+			/*
+			 * For those PLL with SCC used only the default
+			 * freq, the oc_en would nerver to be set.
+			 * Help to set it here.
+			 */
+			if ((val & 0x7) != 0x5)
+				clk_regmap_update(&clkp->clkr,
+					clkp->ssc_ofs + 0x0, 0x7, 0x5);
+		}
+		udelay(200);
+	} else {
+		clk_regmap_update(&clkp->clkr, clkp->pll_ofs + pow, 0x7, 0x4);
+	}
+}
+
+static int clk_pll_enable(struct clk_hw *hw)
+{
+	struct clk_pll *clkp = to_clk_pll(hw);
+
+	if (clk_pll_has_pow(clkp))
+		__clk_pll_set_pow_reg(clkp, 1);
+	return 0;
+}
+
+static void clk_pll_disable(struct clk_hw *hw)
+{
+	struct clk_pll *clkp = to_clk_pll(hw);
+
+	if (clk_pll_has_pow(clkp))
+		__clk_pll_set_pow_reg(clkp, 0);
+}
+
+static void clk_pll_disable_unused(struct clk_hw *hw)
+{
+	pr_info("%pC: %s\n", hw->clk, __func__);
+	clk_pll_disable(hw);
+}
+
+static int clk_pll_is_enabled(struct clk_hw *hw)
+{
+	struct clk_pll *clkp = to_clk_pll(hw);
+	uint32_t pow;
+	uint32_t val;
+
+	if (!clk_pll_has_pow(clkp))
+		return -EINVAL;
+
+	pow = (clkp->pow_loc == CLK_PLL_CONF_POW_LOC_CTL3) ? 0x8 : 0x4;
+	val = clk_regmap_read(&clkp->clkr, clkp->pll_ofs + pow);
+	return !!(val & 0x1);
+}
+
+static long clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long *parent_rate)
+{
+	struct clk_pll *clkp = to_clk_pll(hw);
+	const struct freq_table *ftblv = NULL;
+
+	ftblv = ftbl_find_by_rate(clkp->freq_tbl, rate);
+	return ftblv ? ftblv->rate : 0;
+}
+
+static uint32_t __clk_pll_freq_get(struct clk_pll *clkp)
+{
+	uint32_t val = 0;
+
+	switch (clkp->freq_loc) {
+	case CLK_PLL_CONF_FREQ_LOC_CTL1:
+		val = clk_regmap_read(&clkp->clkr, clkp->pll_ofs + 0x0);
+		break;
+
+	case CLK_PLL_CONF_FREQ_LOC_CTL2:
+		val = clk_regmap_read(&clkp->clkr, clkp->pll_ofs + 0x4);
+		break;
+
+	case CLK_PLL_CONF_FREQ_LOC_SSC1:
+		val = clk_regmap_read(&clkp->clkr, clkp->ssc_ofs + 0x4);
+		break;
+
+	default:
+		break;
+	}
+	return val & clkp->freq_mask;
+}
+
+static unsigned long clk_pll_recalc_rate(struct clk_hw *hw,
+					 unsigned long parent_rate)
+{
+	struct clk_pll *clkp = to_clk_pll(hw);
+	unsigned long flags = 0;
+	const struct freq_table *fv;
+	uint32_t val;
+
+	flags = clk_pll_lock(clkp);
+	val = __clk_pll_freq_get(clkp);
+	clk_pll_unlock(clkp, flags);
+
+	fv = ftbl_find_by_val(clkp->freq_tbl, val);
+	return fv ? fv->rate : 0;
+}
+
+static inline int __clk_pll_freq_set(struct clk_pll *clkp, uint32_t val)
+{
+	struct clk_hw *hw = &clkp->clkr.hw;
+	int ret = 0;
+	uint32_t mask = clkp->freq_mask;
+	uint32_t pollval;
+
+	switch (clkp->freq_loc) {
+	case CLK_PLL_CONF_FREQ_LOC_CTL1:
+		clk_regmap_update(&clkp->clkr, clkp->pll_ofs, mask, val);
+		break;
+
+	case CLK_PLL_CONF_FREQ_LOC_CTL2:
+		clk_regmap_update(&clkp->clkr, clkp->pll_ofs + 0x4, mask, val);
+		clk_regmap_update(&clkp->clkr, clkp->pll_ofs + 0x8, 0x1, 0x0);
+		clk_regmap_update(&clkp->clkr, clkp->pll_ofs + 0x8, 0x1, 0x1);
+		break;
+
+	case CLK_PLL_CONF_FREQ_LOC_SSC1:
+		clk_regmap_update(&clkp->clkr, clkp->ssc_ofs + 0x0, 0x7, 0x4);
+		clk_regmap_update(&clkp->clkr, clkp->ssc_ofs + 0x4, mask, val);
+		clk_regmap_update(&clkp->clkr, clkp->ssc_ofs + 0x0, 0x7, 0x5);
+
+		if (clk_pll_has_pow(clkp) && !clk_pll_is_enabled(hw))
+			return 0;
+
+		ret = regmap_read_poll_timeout(clkp->clkr.regmap,
+					       clkp->ssc_ofs + 0x1c,
+					       pollval, pollval & BIT(20),
+					       0, 2000);
+		break;
+
+	default:
+		ret = -EINVAL;
+		break;
+	}
+	return ret;
+}
+
+static int clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			    unsigned long parent_rate)
+{
+	struct clk_pll *clkp = to_clk_pll(hw);
+	unsigned long flags = 0;
+	const struct freq_table *fv;
+	int ret = 0;
+
+	fv = ftbl_find_by_rate(clkp->freq_tbl, rate);
+	if (!fv)
+		return -EINVAL;
+
+	pr_debug("%pC: %s: rate=%ld, val=0x%08x\n", hw->clk, __func__,
+		 fv->rate, fv->val);
+
+	flags = clk_pll_lock(clkp);
+	ret = __clk_pll_freq_set(clkp, fv->val);
+	clk_pll_unlock(clkp, flags);
+	if (ret)
+		pr_warn("%pC %s: failed to set freq: %d\n", hw->clk, __func__,
+			ret);
+	return ret;
+}
+
+static void __clk_pll_div_set(struct clk_pll_div *clkpd, uint32_t val)
+{
+	uint32_t m = (BIT(clkpd->div_width) - 1) << clkpd->div_shift;
+	uint32_t s = clkpd->div_shift;
+
+	clk_regmap_update(&clkpd->clkp.clkr, clkpd->div_ofs, m, val << s);
+}
+
+static uint32_t __clk_pll_div_get(struct clk_pll_div *clkpd)
+{
+	uint32_t m = (BIT(clkpd->div_width) - 1) << clkpd->div_shift;
+	uint32_t s = clkpd->div_shift;
+
+	return (clk_regmap_read(&clkpd->clkp.clkr, clkpd->div_ofs) & m) >> s;
+}
+
+static long clk_pll_div_round_rate(struct clk_hw *hw, unsigned long rate,
+				   unsigned long *parent_rate)
+{
+	struct clk_pll_div *clkpd = to_clk_pll_div(hw);
+	const struct div_table *dv;
+
+	/* lookup div in dtbl */
+	dv = dtbl_find_by_rate(clkpd->div_tbl, rate);
+	if (!dv)
+		return 0;
+
+	rate *= dv->div;
+	rate = clk_pll_round_rate(hw, rate, parent_rate);
+	return rate / dv->div;
+}
+
+static unsigned long clk_pll_div_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	struct clk_pll_div *clkpd = to_clk_pll_div(hw);
+	unsigned long rate;
+	const struct div_table *dv;
+	uint32_t val;
+
+	rate = clk_pll_recalc_rate(hw, parent_rate);
+
+	val = __clk_pll_div_get(clkpd);
+	dv = dtbl_find_by_val(clkpd->div_tbl, val);
+	if (!dv)
+		return 0;
+
+	rate /= dv->div;
+	pr_debug("%pC: %s: current rate=%lu, div=%d, reg_val=0x%x\n",
+		 hw->clk, __func__, rate, dv->div, val);
+
+	return rate;
+}
+
+static int clk_pll_div_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent_rate)
+{
+	struct clk *clk = hw->clk;
+	struct clk_pll_div *clkpd = to_clk_pll_div(hw);
+	unsigned long flags;
+	const struct div_table *ndv, *cdv;
+	unsigned long target;
+	uint32_t cur_d;
+	int ret;
+
+	/* find next in the dtbl */
+	ndv = dtbl_find_by_rate(clkpd->div_tbl, rate);
+	if (!ndv)
+		return -EINVAL;
+
+	target = rate * ndv->div;
+
+	/* find current in the dtbl */
+	cur_d = __clk_pll_div_get(clkpd);
+	cdv = dtbl_find_by_val(clkpd->div_tbl, cur_d);
+	if (!cdv)
+		return -EINVAL;
+
+	pr_debug("%pC: rate=%lu, cdv={%d,0x%x}, ndv={%d,0x%x}\n",
+		 clk, rate, cdv->div, cdv->val, ndv->div, ndv->val);
+
+	flags = clk_pll_div_lock(clkpd);
+
+	/* workaround to prevent glitch */
+#ifdef CONFIG_COMMON_CLK_RTD129X
+	if ((&clkpd->clkp.flags & CLK_PLL_DIV_WORKAROUND) &&
+		ndv->val != cdv->val && (ndv->val == 1 || cdv->val == 1)) {
+
+		pr_debug("%pC: apply rate=%u\n", clk, 1000000000);
+		clk_pll_set_rate(hw, 1000000000, parent_rate);
+
+		pr_debug("%pC: apply dv={%d, 0x%x}\n", clk, ndv->div, ndv->val);
+		__clk_pll_div_set(clkpd, ndv->val);
+		cdv = ndv;
+	}
+#endif
+
+	if (ndv->div > cdv->div)
+		__clk_pll_div_set(clkpd, ndv->val);
+	ret = clk_pll_set_rate(hw, target, parent_rate);
+	if (ndv->div < cdv->div)
+		__clk_pll_div_set(clkpd, ndv->val);
+
+	clk_pll_div_unlock(clkpd, flags);
+
+	return ret;
+}
+
+const struct clk_ops clk_pll_ops = {
+	.debug_init       = clk_pll_debug_init,
+	.round_rate       = clk_pll_round_rate,
+	.recalc_rate      = clk_pll_recalc_rate,
+	.set_rate         = clk_pll_set_rate,
+	.enable           = clk_pll_enable,
+	.disable          = clk_pll_disable,
+	.disable_unused   = clk_pll_disable_unused,
+	.is_enabled       = clk_pll_is_enabled,
+};
+
+const struct clk_ops clk_pll_div_ops = {
+	.debug_init       = clk_pll_debug_init,
+	.round_rate       = clk_pll_div_round_rate,
+	.recalc_rate      = clk_pll_div_recalc_rate,
+	.set_rate         = clk_pll_div_set_rate,
+	.enable           = clk_pll_enable,
+	.disable          = clk_pll_disable,
+	.disable_unused   = clk_pll_disable_unused,
+	.is_enabled       = clk_pll_is_enabled,
+};
+
diff --git a/drivers/clk/realtek/clk-pll.h b/drivers/clk/realtek/clk-pll.h
new file mode 100644
index 000000000000..03b4d0391c31
--- /dev/null
+++ b/drivers/clk/realtek/clk-pll.h
@@ -0,0 +1,151 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2017-2019 Realtek Semiconductor Corporation
+ * Author: Cheng-Yu Lee <cylee12@realtek.com>
+ */
+
+#ifndef __CLK_REALTEK_CLK_PLL_H
+#define __CLK_REALTEK_CLK_PLL_H
+
+#include "common.h"
+
+struct freq_table {
+	uint32_t val;
+	unsigned long rate;
+};
+
+#define FREQ_TABLE_END                  { .rate = 0 }
+#define IS_FREQ_TABLE_END(_f)           ((_f)->rate == 0)
+
+struct div_table {
+	unsigned long rate;
+	uint32_t div;
+	uint32_t val;
+};
+
+#define DIV_TABLE_END                   { .rate = 0 }
+#define IS_DIV_TABLE_END(_d)            ((_d)->rate == 0)
+
+struct clk_pll {
+	struct clk_regmap clkr;
+
+
+	int pll_ofs;
+	int ssc_ofs;
+
+	const struct freq_table *freq_tbl;
+	uint32_t freq_mask;
+	uint32_t freq_loc;
+#define CLK_PLL_CONF_FREQ_LOC_CTL1      1
+#define CLK_PLL_CONF_FREQ_LOC_CTL2      2
+#define CLK_PLL_CONF_FREQ_LOC_SSC1      3
+
+	uint32_t pow_loc;
+#define CLK_PLL_CONF_NO_POW             0
+#define CLK_PLL_CONF_POW_LOC_CTL2       1
+#define CLK_PLL_CONF_POW_LOC_CTL3       2
+
+	spinlock_t *lock;
+
+	uint32_t flags;
+};
+
+#define to_clk_pll(_hw) container_of(to_clk_regmap(_hw), struct clk_pll, clkr)
+#define __clk_pll_hw(_ptr)  __clk_regmap_hw(&(_ptr)->clkr)
+
+/* clk_pll flags */
+#define CLK_PLL_DIV_WORKAROUND          BIT(2)
+
+
+static inline bool clk_pll_has_pow(struct clk_pll *pll)
+{
+	if (pll->pow_loc != CLK_PLL_CONF_NO_POW)
+		return true;
+	return false;
+}
+
+static inline unsigned long clk_pll_lock(struct clk_pll *pll)
+{
+	unsigned long flags = 0;
+
+	if (pll->lock)
+		spin_lock_irqsave(pll->lock, flags);
+	return flags;
+}
+
+static inline void clk_pll_unlock(struct clk_pll *pll, unsigned long flags)
+{
+	if (pll->lock)
+		spin_unlock_irqrestore(pll->lock, flags);
+}
+
+struct clk_pll_div {
+	struct clk_pll clkp;
+	int div_ofs;
+	int div_shift;
+	int div_width;
+	const struct div_table *div_tbl;
+	spinlock_t *lock;
+};
+
+#define to_clk_pll_div(_hw) \
+	container_of(to_clk_pll(_hw), struct clk_pll_div, clkp)
+#define __clk_pll_div_hw(_ptr) __clk_pll_hw(&(_ptr)->clkp)
+
+/* clk_pll_div helper functions */
+static inline unsigned long clk_pll_div_lock(struct clk_pll_div *plld)
+{
+	unsigned long flags = 0;
+
+	if (plld->lock)
+		spin_lock_irqsave(plld->lock, flags);
+	return flags;
+}
+
+static inline void clk_pll_div_unlock(struct clk_pll_div *plld,
+	unsigned long flags)
+{
+	if (plld->lock)
+		spin_unlock_irqrestore(plld->lock, flags);
+}
+
+extern const struct clk_ops clk_pll_ops;
+extern const struct clk_ops clk_pll_div_ops;
+
+#ifdef CONFIG_CLK_PLL_PSAUD
+
+struct clk_pll_psaud {
+	struct clk_regmap clkr;
+	int id;
+	int reg;
+	spinlock_t *lock;
+};
+
+#define to_clk_pll_psaud(_hw) \
+	container_of(to_clk_regmap(_hw), struct clk_pll_psaud, clkr)
+#define __clk_pll_psaud_hw(_ptr)  __clk_regmap_hw(&(_ptr)->clkr)
+extern const struct clk_ops clk_pll_psaud_ops;
+
+#define CLK_PLL_PSAUD1A       (0x1)
+#define CLK_PLL_PSAUD2A       (0x2)
+
+#endif /* CONFIG_CLK_PLL_PSAUD */
+
+#ifdef CONFIG_CLK_PLL_DIF
+
+struct clk_pll_dif {
+	struct clk_regmap clkr;
+	int pll_ofs;
+	int ssc_ofs;
+	uint32_t status;
+	spinlock_t *lock;
+};
+#define to_clk_pll_dif(_hw) \
+	container_of(to_clk_regmap(_hw), struct clk_pll_dif, clkr)
+#define __clk_pll_dif_hw(_ptr)  __clk_regmap_hw(&(_ptr)->clkr)
+
+extern const struct clk_ops clk_pll_dif_ops;
+#endif /* CONFIG_CLK_PLL_DIF */
+
+
+#endif /* __CLK_REALTEK_CLK_PLL_H */
diff --git a/drivers/clk/realtek/clk-regmap-gate.c b/drivers/clk/realtek/clk-regmap-gate.c
new file mode 100644
index 000000000000..5f4a6a98da94
--- /dev/null
+++ b/drivers/clk/realtek/clk-regmap-gate.c
@@ -0,0 +1,89 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2017 Realtek Semiconductor Corporation
+ * Author: Cheng-Yu Lee <cylee12@realtek.com>
+ */
+
+#include <linux/slab.h>
+#include <linux/clk-provider.h>
+#include "clk-regmap-gate.h"
+
+static int clk_regmap_gate_enable(struct clk_hw *hw)
+{
+	struct clk_regmap_gate *clkg = to_clk_regmap_gate(hw);
+	unsigned long flags = 0;
+	unsigned int mask;
+	unsigned int val;
+
+	if (clkg->lock)
+		spin_lock_irqsave(clkg->lock, flags);
+
+	mask = BIT(clkg->bit_idx);
+	val  = BIT(clkg->bit_idx);
+
+	if (clkg->write_en) {
+		mask |= BIT(clkg->bit_idx + 1);
+		val  |= BIT(clkg->bit_idx + 1);
+	}
+
+	clk_regmap_update(&clkg->clkr, clkg->gate_ofs, mask, val);
+
+	if (clkg->lock)
+		spin_unlock_irqrestore(clkg->lock, flags);
+
+	return 0;
+}
+
+static void clk_regmap_gate_disable(struct clk_hw *hw)
+{
+	struct clk_regmap_gate *clkg = to_clk_regmap_gate(hw);
+	unsigned long flags = 0;
+	unsigned int mask;
+	unsigned int val;
+
+	if (clkg->lock)
+		spin_lock_irqsave(clkg->lock, flags);
+
+	mask = BIT(clkg->bit_idx);
+	val  = 0;
+
+	if (clkg->write_en) {
+		mask |= BIT(clkg->bit_idx + 1);
+		val  |= BIT(clkg->bit_idx + 1);
+	}
+
+	clk_regmap_update(&clkg->clkr, clkg->gate_ofs, mask, val);
+
+	if (clkg->lock)
+		spin_unlock_irqrestore(clkg->lock, flags);
+}
+
+static void clk_regmap_gate_disable_unused(struct clk_hw *hw)
+{
+	pr_info("%pC: %s\n", hw->clk, __func__);
+	clk_regmap_gate_disable(hw);
+}
+
+static int clk_regmap_gate_is_enabled(struct clk_hw *hw)
+{
+	struct clk_regmap_gate *clkg = to_clk_regmap_gate(hw);
+	int ret;
+	unsigned long flags = 0;
+
+	if (clkg->lock)
+		spin_lock_irqsave(clkg->lock, flags);
+
+	ret = clk_regmap_read(&clkg->clkr, clkg->gate_ofs) & BIT(clkg->bit_idx);
+
+	if (clkg->lock)
+		spin_unlock_irqrestore(clkg->lock, flags);
+
+	return !!ret;
+}
+
+const struct clk_ops clk_regmap_gate_ops = {
+	.enable = clk_regmap_gate_enable,
+	.disable = clk_regmap_gate_disable,
+	.disable_unused = clk_regmap_gate_disable_unused,
+	.is_enabled = clk_regmap_gate_is_enabled,
+};
diff --git a/drivers/clk/realtek/clk-regmap-gate.h b/drivers/clk/realtek/clk-regmap-gate.h
new file mode 100644
index 000000000000..7643e0193177
--- /dev/null
+++ b/drivers/clk/realtek/clk-regmap-gate.h
@@ -0,0 +1,26 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2017 Realtek Semiconductor Corporation
+ * Author: Cheng-Yu Lee <cylee12@realtek.com>
+ */
+
+#ifndef __CLK_REALTEK_CLK_MMIO_GATE_H
+#define __CLK_REALTEK_CLK_MMIO_GATE_H
+
+#include "common.h"
+
+struct clk_regmap_gate {
+	struct clk_regmap clkr;
+	int gate_ofs;
+	uint8_t bit_idx;
+	spinlock_t *lock;
+	int write_en:1;
+};
+
+#define to_clk_regmap_gate(_hw) \
+	container_of(to_clk_regmap(_hw), struct clk_regmap_gate, clkr)
+#define __clk_regmap_gate_hw(_p) __clk_regmap_hw(&(_p)->clkr)
+
+extern const struct clk_ops clk_regmap_gate_ops;
+
+#endif
diff --git a/drivers/clk/realtek/clk-regmap-mux.c b/drivers/clk/realtek/clk-regmap-mux.c
new file mode 100644
index 000000000000..bd7eb706e12a
--- /dev/null
+++ b/drivers/clk/realtek/clk-regmap-mux.c
@@ -0,0 +1,63 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2017 Realtek Semiconductor Corporation
+ * Author: Cheng-Yu Lee <cylee12@realtek.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include "clk-regmap-mux.h"
+
+static u8 clk_regmap_mux_get_parent(struct clk_hw *hw)
+{
+	struct clk_regmap_mux *clkm = to_clk_regmap_mux(hw);
+	int num_parents = clk_hw_get_num_parents(hw);
+	u32 val;
+	unsigned long flags = 0;
+
+	if (clkm->lock)
+		spin_lock_irqsave(clkm->lock, flags);
+
+	val = clk_regmap_read(&clkm->clkr, clkm->mux_ofs) >> clkm->shift;
+	if (clkm->lock)
+		spin_unlock_irqrestore(clkm->lock, flags);
+
+	val &= clkm->mask;
+	if (val >= num_parents)
+		return -EINVAL;
+
+	return val;
+}
+
+static int clk_regmap_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct clk_regmap_mux *clkm = to_clk_regmap_mux(hw);
+	unsigned long flags = 0;
+
+	if (clkm->lock)
+		spin_lock_irqsave(clkm->lock, flags);
+
+	clk_regmap_update(&clkm->clkr, clkm->mux_ofs, clkm->mask << clkm->shift,
+		index << clkm->shift);
+
+	if (clkm->lock)
+		spin_unlock_irqrestore(clkm->lock, flags);
+	return 0;
+}
+
+const struct clk_ops clk_regmap_mux_ops = {
+	.get_parent = clk_regmap_mux_get_parent,
+	.set_parent = clk_regmap_mux_set_parent,
+	.determine_rate = __clk_mux_determine_rate,
+};
+EXPORT_SYMBOL_GPL(clk_regmap_mux_ops);
+
+const struct clk_ops clk_regmap_mux_ro_ops = {
+	.get_parent = clk_regmap_mux_get_parent,
+};
+EXPORT_SYMBOL_GPL(clk_regmap_mux_ro_ops);
+
diff --git a/drivers/clk/realtek/clk-regmap-mux.h b/drivers/clk/realtek/clk-regmap-mux.h
new file mode 100644
index 000000000000..36895f03c0f4
--- /dev/null
+++ b/drivers/clk/realtek/clk-regmap-mux.h
@@ -0,0 +1,26 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2017 Realtek Semiconductor Corporation
+ * Author: Cheng-Yu Lee <cylee12@realtek.com>
+ */
+
+#ifndef __CLK_REALTEK_CLK_MMIO_MUX_H
+#define __CLK_REALTEK_CLK_MMIO_MUX_H
+
+#include "common.h"
+
+struct clk_regmap_mux {
+	struct clk_regmap clkr;
+	int mux_ofs;
+	unsigned int mask;
+	unsigned int shift;
+	spinlock_t *lock;
+};
+
+#define to_clk_regmap_mux(_hw) \
+	container_of(to_clk_regmap(_hw), struct clk_regmap_mux, clkr)
+#define __clk_regmap_mux_hw(_p) __clk_regmap_hw(&(_p)->clkr)
+
+extern const struct clk_ops clk_regmap_mux_ops;
+
+#endif
diff --git a/drivers/clk/realtek/common.c b/drivers/clk/realtek/common.c
new file mode 100644
index 000000000000..0c5dc5ce4682
--- /dev/null
+++ b/drivers/clk/realtek/common.c
@@ -0,0 +1,320 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2019 Realtek Semiconductor Corporation
+ * Author: Cheng-Yu Lee <cylee12@realtek.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/pm.h>
+#include "common.h"
+#include "clk-pll.h"
+#include "clk-regmap-gate.h"
+#include "clk-regmap-mux.h"
+
+static int rtk_clk_suspend(struct device *dev)
+{
+	struct rtk_clk_data *data = dev_get_drvdata(dev);
+	int i;
+
+	for (i = 0; i < data->pm_data_num; i++) {
+		struct clk_pm_data *pm_data = &data->pm_data[i];
+
+		regmap_read(data->regmap, pm_data->ofs, &pm_data->val);
+	}
+	return 0;
+}
+
+static int rtk_clk_resume(struct device *dev)
+{
+	struct rtk_clk_data *data = dev_get_drvdata(dev);
+	int i;
+
+	for (i = data->pm_data_num - 1; i >= 0; i--) {
+		struct clk_pm_data *pm_data = &data->pm_data[i];
+		uint32_t val = pm_data->val;
+		uint32_t mask = ~0x0;
+
+		if (pm_data->write_en_bits)
+			val |= pm_data->write_en_bits;
+		if (pm_data->ignore_bits)
+			mask &= ~pm_data->ignore_bits;
+		dev_info(dev, "resuming: ofs=%03x, vs=%08x vr=%08x m=%08x\n",
+			pm_data->ofs, pm_data->val, val, mask);
+		regmap_update_bits(data->regmap, pm_data->ofs, mask, val);
+	}
+	return 0;
+}
+
+const struct dev_pm_ops rtk_clk_pm_ops = {
+	.suspend = rtk_clk_suspend,
+	.resume  = rtk_clk_resume,
+};
+
+struct rtk_clk_data *alloc_rtk_clk_data(int clk_num)
+{
+	struct rtk_clk_data *data;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return NULL;
+
+	data->clk_num = clk_num;
+	data->clk_data.clk_num = clk_num;
+	data->clk_data.clks = kcalloc(clk_num, sizeof(*data->clk_data.clks),
+				      GFP_KERNEL);
+	if (!data->clk_data.clks)
+		goto free_data;
+	return data;
+
+free_data:
+	kfree(data->clk_data.clks);
+	kfree(data);
+	return NULL;
+}
+
+void free_rtk_clk_data(struct rtk_clk_data *data)
+{
+	kfree(data->clk_data.clks);
+	kfree(data);
+}
+
+static inline
+int __cell_clk_add(struct clk_onecell_data *clk_data, int i, struct clk *clk)
+{
+	if (clk_data->clks[i]) {
+		pr_err("%s: failed to add %pC, cell%d is used by %pC\n",
+			__func__, clk, i, clk_data->clks[i]);
+		return -EINVAL;
+	}
+	clk_data->clks[i] = clk;
+	return 0;
+}
+
+#define CLK_TYPE_DEFAULT                (0x0)
+#define CLK_TYPE_REGMAP                 (0x8)
+#define CLK_TYPE_REGMAP_PLL             (0x1 | CLK_TYPE_REGMAP)
+#define CLK_TYPE_REGMAP_MUX             (0x2 | CLK_TYPE_REGMAP)
+#define CLK_TYPE_REGMAP_GATE            (0x3 | CLK_TYPE_REGMAP)
+
+static inline int __hw_to_type(struct clk_hw *hw)
+{
+	const struct clk_ops *ops = hw->init->ops;
+
+	if (ops == &clk_pll_ops || ops == &clk_pll_div_ops)
+		return CLK_TYPE_REGMAP_PLL;
+	if (ops == &clk_regmap_mux_ops)
+		return CLK_TYPE_REGMAP_MUX;
+	if (ops == &clk_regmap_gate_ops)
+		return CLK_TYPE_REGMAP_GATE;
+#ifdef CONFIG_CLK_PLL_DIF
+	/*
+	 * clk_pll_dif is not based struct clk_pll, so
+	 * return as CLK_TYPE_REGMAP to setup internal
+	 * clk_reg
+	 */
+	if (ops == &clk_pll_dif_ops)
+		return CLK_TYPE_REGMAP;
+#endif
+#ifdef CONFIG_CLK_PLL_PSAUD
+	/*
+	 * clk_pll_psaud is not based struct clk_pll, so
+	 * return as CLK_TYPE_REGMAP to setup internal
+	 * clk_reg
+	 */
+	if (ops == &clk_pll_psaud_ops)
+		return CLK_TYPE_REGMAP;
+#endif
+
+	return CLK_TYPE_DEFAULT;
+}
+
+static
+struct clk *rtk_clk_register_hw(struct device *dev, struct regmap *regmap,
+				struct clk_hw *hw)
+{
+	int type;
+
+	type = __hw_to_type(hw);
+	if (type & CLK_TYPE_REGMAP) {
+		struct clk_regmap *clkr = to_clk_regmap(hw);
+
+		clkr->regmap = regmap;
+	}
+
+	return clk_register(dev, hw);
+}
+
+int rtk_clk_add_hws(struct device *dev, struct rtk_clk_data *data,
+		    struct clk_hw **hws, int num)
+{
+	struct clk_onecell_data *clk_data = &data->clk_data;
+	struct regmap *regmap = data->regmap;
+	int i;
+
+	for (i = 0; i < num; i++) {
+		struct clk_hw *hw = hws[i];
+		const char *name;
+		struct clk *clk;
+
+		if (IS_ERR(hw))
+			__cell_clk_add(clk_data, i, ERR_CAST(hw));
+		if (IS_ERR_OR_NULL(hw))
+			continue;
+
+		name = hw->init->name;
+		clk = rtk_clk_register_hw(dev, regmap, hw);
+		if (IS_ERR(clk)) {
+			pr_err("%s: failed to add hw%d(%s): %ld\n", __func__,
+				i, name, PTR_ERR(clk));
+			continue;
+		}
+
+		clk_register_clkdev(clk, name, NULL);
+		__cell_clk_add(clk_data, i, clk);
+	}
+	return 0;
+}
+
+static
+struct clk *rtk_clk_register_composite(struct device *dev,
+				       struct regmap *regmap,
+				       struct clk_composite_data *comp)
+{
+	struct clk_regmap_mux *clkm = NULL;
+	const struct clk_ops *mux_op = NULL;
+	struct clk_hw *mux_hw = NULL;
+	struct clk_regmap_gate *clkg = NULL;
+	const struct clk_ops *gate_op = NULL;
+	struct clk_hw *gate_hw = NULL;
+	struct clk *clk;
+
+	if (comp->mux_ofs != CLK_OFS_INVALID) {
+		clkm = kzalloc(sizeof(*clkm), GFP_KERNEL);
+		if (!clkm) {
+			clk = ERR_PTR(-ENOMEM);
+			goto check_err;
+		}
+
+		clkm->mux_ofs     = comp->mux_ofs;
+		clkm->mask        = BIT(comp->mux_width) - 1;
+		clkm->shift       = comp->mux_shift;
+		clkm->clkr.regmap = regmap;
+
+		mux_op = &clk_regmap_mux_ops;
+		mux_hw = &__clk_regmap_mux_hw(clkm);
+	}
+
+	if (comp->gate_ofs != CLK_OFS_INVALID) {
+		clkg = kzalloc(sizeof(*clkg), GFP_KERNEL);
+		if (!clkg) {
+			clk = ERR_PTR(-ENOMEM);
+			goto check_err;
+		}
+
+		clkg->gate_ofs    = comp->gate_ofs;
+		clkg->bit_idx     = comp->gate_shift;
+		clkg->write_en    = comp->gate_write_en;
+		clkg->clkr.regmap = regmap;
+
+		gate_op = &clk_regmap_gate_ops;
+		gate_hw = &__clk_regmap_gate_hw(clkg);
+	}
+
+	clk = clk_register_composite(NULL, comp->name, comp->parent_names,
+				     comp->num_parents, mux_hw, mux_op,
+				     NULL, NULL, gate_hw, gate_op, comp->flags);
+check_err:
+	if (IS_ERR(clk)) {
+		kfree(clkm);
+		kfree(clkg);
+	}
+	return clk;
+}
+
+int rtk_clk_add_composites(struct device *dev, struct rtk_clk_data *data,
+			   struct clk_composite_data *comps, int num)
+{
+	struct clk_onecell_data *clk_data = &data->clk_data;
+	struct regmap *regmap = data->regmap;
+	int i;
+
+	for (i = 0; i < num; i++) {
+		struct clk_composite_data *comp = &comps[i];
+		const char *name = comp->name;
+		struct clk *clk;
+
+		clk = rtk_clk_register_composite(dev, regmap, comp);
+		if (IS_ERR(clk)) {
+			pr_err("%s: failed to add composite%d(%s): %ld\n",
+				__func__, i, name, PTR_ERR(clk));
+			continue;
+		}
+
+		clk_register_clkdev(clk, name, NULL);
+		__cell_clk_add(clk_data, comp->id, clk);
+	}
+
+	return 0;
+}
+
+static
+struct clk *rtk_clk_register_gate(struct device *dev, struct regmap *regmap,
+				  struct clk_gate_data *gate)
+{
+	struct clk_regmap_gate *clkg;
+	struct clk_init_data init = { 0 };
+	struct clk_hw *hw;
+
+	clkg = kzalloc(sizeof(*clkg), GFP_KERNEL);
+	if (!clkg)
+		return ERR_PTR(-ENOMEM);
+
+	clkg->gate_ofs    = gate->gate_ofs;
+	clkg->bit_idx     = gate->gate_shift;
+	clkg->write_en    = gate->gate_write_en;
+	clkg->clkr.regmap = regmap;
+
+	init.name         = gate->name;
+	init.ops          = &clk_regmap_gate_ops;
+	init.flags         = gate->flags;
+	if (gate->parent) {
+		init.parent_names = &gate->parent;
+		init.num_parents  = 1;
+	}
+
+	hw = &__clk_regmap_gate_hw(clkg);
+	hw->init = &init;
+	return clk_register(dev, hw);
+}
+
+int rtk_clk_add_gates(struct device *dev, struct rtk_clk_data *data,
+		      struct clk_gate_data *gates, int num)
+{
+	struct clk_onecell_data *clk_data = &data->clk_data;
+	struct regmap *regmap = data->regmap;
+	int i;
+
+	for (i = 0; i < num; i++) {
+		struct clk_gate_data *gate = &gates[i];
+		const char *name = gate->name;
+		struct clk *clk;
+
+		clk = rtk_clk_register_gate(dev, regmap, gate);
+		if (IS_ERR(clk)) {
+			pr_err("%s: failed to add gate%d(%s): %ld\n", __func__,
+				i, name, PTR_ERR(clk));
+			continue;
+		}
+
+		clk_register_clkdev(clk, name, NULL);
+		__cell_clk_add(clk_data, gate->id, clk);
+	}
+
+	return 0;
+}
+
diff --git a/drivers/clk/realtek/common.h b/drivers/clk/realtek/common.h
new file mode 100644
index 000000000000..c736b254852f
--- /dev/null
+++ b/drivers/clk/realtek/common.h
@@ -0,0 +1,123 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2016-2019 Realtek Semiconductor Corporation
+ * Author: Cheng-Yu Lee <cylee12@realtek.com>
+ */
+
+#ifndef __CLK_REALTEK_COMMON_H
+#define __CLK_REALTEK_COMMON_H
+
+#include <linux/bitops.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+struct device;
+struct platform_device;
+
+struct clk_regmap {
+	struct clk_hw hw;
+	struct regmap *regmap;
+};
+
+#define to_clk_regmap(_hw) container_of(_hw, struct clk_regmap, hw)
+#define __clk_regmap_hw(_p) ((_p)->hw)
+
+static inline
+void clk_regmap_write(struct clk_regmap *clkr, uint32_t ofs, uint32_t val)
+{
+	pr_debug("%s: ofs=%03x, val=%08x\n", __func__, ofs, val);
+	regmap_write(clkr->regmap, ofs, val);
+}
+
+static inline
+uint32_t clk_regmap_read(struct clk_regmap *clkr, uint32_t ofs)
+{
+	uint32_t val = 0;
+
+	regmap_read(clkr->regmap, ofs, &val);
+	pr_debug("%s: ofs=%03x, val=%08x\n", __func__, ofs, val);
+	return val;
+}
+
+static inline void clk_regmap_update(struct clk_regmap *clkr, uint32_t ofs,
+				     uint32_t mask, uint32_t val)
+{
+	pr_debug("%s: ofs=%03x, mask=%08x, val=%08x\n", __func__, ofs,
+		 mask, val);
+	regmap_update_bits(clkr->regmap, ofs, mask, val);
+}
+
+/* ofs check */
+#define CLK_OFS_INVALID                 (-1)
+#define CLK_OFS_IS_VALID(_ofs)          ((_ofs) != CLK_OFS_INVALID)
+
+struct clk_composite_data {
+	int id;
+	const char *name;
+	unsigned long flags;
+	struct clk *clk;
+
+	int gate_ofs;
+	int gate_shift;
+	int gate_write_en;
+
+	int mux_ofs;
+	int mux_width;
+	int mux_shift;
+	const char * const *parent_names;
+	int num_parents;
+};
+
+struct clk_gate_data {
+	int id;
+	const char *name;
+	const char *parent;
+	unsigned long flags;
+	struct clk *clk;
+
+	int gate_ofs;
+	int gate_shift;
+	int gate_write_en;
+};
+
+#define CLK_GATE_DATA(_id, _name, _parent, _flags, _ofs, _shift, _write_en) \
+{ \
+	.id = _id, \
+	.name = _name, \
+	.parent = _parent, \
+	.flags = _flags, \
+	.gate_ofs = _ofs, \
+	.gate_shift = _shift, \
+	.gate_write_en = _write_en, \
+}
+
+struct clk_pm_data {
+	int ofs;
+	uint32_t ignore_bits;
+	uint32_t write_en_bits;
+	uint32_t val;
+};
+
+struct rtk_clk_data {
+	int clk_num;
+	struct regmap *regmap;
+	struct clk_onecell_data clk_data;
+	struct clk_pm_data *pm_data;
+	int pm_data_num;
+};
+
+struct rtk_clk_data *alloc_rtk_clk_data(int clk_num);
+void free_rtk_clk_data(struct rtk_clk_data *data);
+int rtk_clk_add_hws(struct device *dev, struct rtk_clk_data *data,
+		    struct clk_hw **hws, int num);
+int rtk_clk_add_composites(struct device *dev, struct rtk_clk_data *data,
+			   struct clk_composite_data *comps, int num);
+int rtk_clk_add_gates(struct device *dev, struct rtk_clk_data *data,
+		      struct clk_gate_data *gates, int num);
+extern const struct dev_pm_ops rtk_clk_pm_ops;
+
+#endif /* __CLK_REALTEK_COMMON_H */
+