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

From: James Tai <[email-masked]>
Date: Tue, 3 Dec 2019 15:45:10 +0800

From: cylee12 <[email-masked]>

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

Signed-off-by: Cheng-Yu Lee <[email-masked]>
Signed-off-by: James Tai <[email-masked]>
---
 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

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 <[email-masked]>
+ */
+
+#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 <[email-masked]>
+ */
+
+#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 <[email-masked]>
+ */
+
+#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 <[email-masked]>
+ */
+
+#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 <[email-masked]>
+ */
+
+#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 <[email-masked]>
+ */
+
+#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 <[email-masked]>
+ */
+
+#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 <[email-masked]>
+ */
+
+#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 <[email-masked]>
+ */
+
+#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 <[email-masked]>
+ */
+
+#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 */
+
-- 
2.24.0

_______________________________________________
linux-arm-kernel mailing list
[email-masked]
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel