clk: sophgo: cv1800b: Add clock controller driver for cv1800b SoC

Add clock controller driver for sophgo cv1800b SoC

Signed-off-by: Kongyang Liu <seashell11234455@gmail.com>
Reviewed-by: Leo Yu-Chi Liang <ycliang@andestech.com>
diff --git a/drivers/clk/sophgo/clk-pll.c b/drivers/clk/sophgo/clk-pll.c
new file mode 100644
index 0000000..c99aa0b
--- /dev/null
+++ b/drivers/clk/sophgo/clk-pll.c
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2024, Kongyang Liu <seashell11234455@gmail.com>
+ */
+
+#include <clk-uclass.h>
+#include <dm.h>
+#include <div64.h>
+#include <linux/bitfield.h>
+#include <linux/clk-provider.h>
+#include <linux/kernel.h>
+
+#include "clk-common.h"
+#include "clk-pll.h"
+
+#define PLL_PRE_DIV_MIN      1
+#define PLL_PRE_DIV_MAX      127
+#define PLL_POST_DIV_MIN     1
+#define PLL_POST_DIV_MAX     127
+#define PLL_DIV_MIN          6
+#define PLL_DIV_MAX          127
+#define PLL_ICTRL_MIN        0
+#define PLL_ICTRL_MAX        7
+#define PLL_MODE_MIN         0
+#define PLL_MODE_MAX         3
+#define FOR_RANGE(x, RANGE) for (x = RANGE##_MIN; x <= RANGE##_MAX; x++)
+
+#define PLL_ICTRL GENMASK(26, 24)
+#define PLL_DIV_SEL GENMASK(23, 17)
+#define PLL_SEL_MODE GENMASK(16, 15)
+#define PLL_POST_DIV_SEL GENMASK(14, 8)
+#define PLL_PRE_DIV_SEL GENMASK(6, 0)
+#define PLL_MASK_ALL (PLL_ICTRL | PLL_DIV_SEL | PLL_SEL_MODE | PLL_POST_DIV_SEL | PLL_PRE_DIV_SEL)
+
+/* IPLL */
+#define to_clk_ipll(dev) container_of(dev, struct cv1800b_clk_ipll, clk)
+
+static int cv1800b_ipll_enable(struct clk *clk)
+{
+	struct cv1800b_clk_ipll *pll = to_clk_ipll(clk);
+
+	cv1800b_clk_clrbit(pll->base, &pll->pll_pwd);
+	return 0;
+}
+
+static int cv1800b_ipll_disable(struct clk *clk)
+{
+	struct cv1800b_clk_ipll *pll = to_clk_ipll(clk);
+
+	cv1800b_clk_setbit(pll->base, &pll->pll_pwd);
+	return 0;
+}
+
+static ulong cv1800b_ipll_get_rate(struct clk *clk)
+{
+	struct cv1800b_clk_ipll *pll = to_clk_ipll(clk);
+
+	ulong parent_rate = clk_get_parent_rate(clk);
+	u32 reg = readl(pll->base + pll->pll_reg);
+	u32 pre_div = FIELD_GET(PLL_PRE_DIV_SEL, reg);
+	u32 post_div = FIELD_GET(PLL_POST_DIV_SEL, reg);
+	u32 div = FIELD_GET(PLL_DIV_SEL, reg);
+
+	return DIV_ROUND_DOWN_ULL(parent_rate * div, pre_div * post_div);
+}
+
+static ulong cv1800b_ipll_set_rate(struct clk *clk, ulong rate)
+{
+	struct cv1800b_clk_ipll *pll = to_clk_ipll(clk);
+	ulong parent_rate = clk_get_parent_rate(clk);
+	u32 pre_div, post_div, div;
+	u32 pre_div_sel, post_div_sel, div_sel;
+	ulong new_rate, best_rate = 0;
+	u32 mode, ictrl;
+	u32 test, val;
+
+	FOR_RANGE(pre_div, PLL_PRE_DIV)
+	{
+		FOR_RANGE(post_div, PLL_POST_DIV)
+		{
+			FOR_RANGE(div, PLL_DIV)
+			{
+				new_rate =
+					DIV_ROUND_DOWN_ULL(parent_rate * div, pre_div * post_div);
+				if (rate - new_rate < rate - best_rate) {
+					best_rate = new_rate;
+					pre_div_sel = pre_div;
+					post_div_sel = post_div;
+					div_sel = div;
+				}
+			}
+		}
+	}
+
+	FOR_RANGE(mode, PLL_MODE)
+	{
+		FOR_RANGE(ictrl, PLL_ICTRL)
+		{
+			test = 184 * (1 + mode) * (1 + ictrl) / 2;
+			if (test > 20 * div_sel && test < 35 * div_sel) {
+				val = FIELD_PREP(PLL_PRE_DIV_SEL, pre_div_sel) |
+				      FIELD_PREP(PLL_POST_DIV_SEL, post_div_sel) |
+				      FIELD_PREP(PLL_DIV_SEL, div_sel) |
+				      FIELD_PREP(PLL_ICTRL, ictrl) |
+				      FIELD_PREP(PLL_SEL_MODE, mode);
+				clrsetbits_le32(pll->base + pll->pll_reg, PLL_MASK_ALL, val);
+				return best_rate;
+			}
+		}
+	}
+
+	return -EINVAL;
+}
+
+const struct clk_ops cv1800b_ipll_ops = {
+	.enable = cv1800b_ipll_enable,
+	.disable = cv1800b_ipll_disable,
+	.get_rate = cv1800b_ipll_get_rate,
+	.set_rate = cv1800b_ipll_set_rate,
+};
+
+U_BOOT_DRIVER(cv1800b_clk_ipll) = {
+	.name = "cv1800b_clk_ipll",
+	.id = UCLASS_CLK,
+	.ops = &cv1800b_ipll_ops,
+	.flags = DM_FLAG_PRE_RELOC,
+};
+
+/* FPLL */
+#define to_clk_fpll(dev) container_of(dev, struct cv1800b_clk_fpll, ipll.clk)
+
+static ulong cv1800b_fpll_get_rate(struct clk *clk)
+{
+	struct cv1800b_clk_fpll *pll = to_clk_fpll(clk);
+	u32 val, syn_set;
+	u32 pre_div, post_div, div;
+	u8 mult = 1;
+	ulong divisor, remainder, rate;
+
+	if (!cv1800b_clk_getbit(pll->ipll.base, &pll->syn.en))
+		return cv1800b_ipll_get_rate(clk);
+
+	syn_set = readl(pll->ipll.base + pll->syn.set);
+	if (syn_set == 0)
+		return 0;
+
+	val = readl(pll->ipll.base + pll->ipll.pll_reg);
+	pre_div = FIELD_GET(PLL_PRE_DIV_SEL, val);
+	post_div = FIELD_GET(PLL_POST_DIV_SEL, val);
+	div = FIELD_GET(PLL_DIV_SEL, val);
+
+	if (cv1800b_clk_getbit(pll->ipll.base, &pll->syn.clk_half))
+		mult = 2;
+
+	divisor = (ulong)pre_div * post_div * syn_set;
+	rate = (clk_get_parent_rate(clk) * div) << 25;
+	remainder = rate % divisor;
+	rate /= divisor;
+	return rate * mult + DIV_ROUND_CLOSEST_ULL(remainder * mult, divisor);
+}
+
+static ulong cv1800b_find_syn(ulong rate, ulong parent_rate, ulong pre_div, ulong post_div,
+			      ulong div, u32 *syn)
+{
+	u32 syn_min = (4 << 26) + 1;
+	u32 syn_max = U32_MAX;
+	u32 mid;
+	ulong new_rate;
+	u32 mult = 1;
+	ulong divisor, remainder;
+
+	while (syn_min < syn_max) {
+		mid = ((ulong)syn_min + syn_max) / 2;
+		divisor = pre_div * post_div * mid;
+		new_rate = (parent_rate * div) << 25;
+		remainder = do_div(new_rate, divisor);
+		new_rate = new_rate * mult + DIV_ROUND_CLOSEST_ULL(remainder * mult, divisor);
+		if (new_rate > rate) {
+			syn_max = mid + 1;
+		} else if (new_rate < rate) {
+			syn_min = mid - 1;
+		} else {
+			syn_min = mid;
+			break;
+		}
+	}
+	*syn = syn_min;
+	return new_rate;
+}
+
+static ulong cv1800b_fpll_set_rate(struct clk *clk, ulong rate)
+{
+	struct cv1800b_clk_fpll *pll = to_clk_fpll(clk);
+	ulong parent_rate = clk_get_parent_rate(clk);
+	u32 pre_div, post_div, div;
+	u32 pre_div_sel, post_div_sel, div_sel;
+	u32 syn, syn_sel;
+	ulong new_rate, best_rate = 0;
+	u32 mult = 1;
+	u32 mode, ictrl;
+
+	if (!cv1800b_clk_getbit(pll->ipll.base, &pll->syn.en))
+		return cv1800b_ipll_set_rate(clk, rate);
+
+	if (cv1800b_clk_getbit(pll->ipll.base, &pll->syn.clk_half))
+		mult = 2;
+
+	FOR_RANGE(pre_div, PLL_PRE_DIV)
+	{
+		FOR_RANGE(post_div, PLL_POST_DIV)
+		{
+			FOR_RANGE(div, PLL_DIV)
+			{
+				new_rate = cv1800b_find_syn(rate, parent_rate, pre_div, post_div,
+							    div, &syn);
+				if (rate - new_rate < rate - best_rate) {
+					best_rate = new_rate;
+					pre_div_sel = pre_div;
+					post_div_sel = post_div;
+					div_sel = div;
+					syn_sel = syn;
+				}
+			}
+		}
+	}
+
+	FOR_RANGE(mode, PLL_MODE)
+	{
+		FOR_RANGE(ictrl, PLL_ICTRL)
+		{
+			u32 test = 184 * (1 + mode) * (1 + ictrl) / 2;
+
+			if (test > 10 * div_sel && test <= 24 * div_sel) {
+				u32 val = FIELD_PREP(PLL_PRE_DIV_SEL, pre_div_sel) |
+					  FIELD_PREP(PLL_POST_DIV_SEL, post_div_sel) |
+					  FIELD_PREP(PLL_DIV_SEL, div_sel) |
+					  FIELD_PREP(PLL_ICTRL, ictrl) |
+					  FIELD_PREP(PLL_SEL_MODE, mode);
+				clrsetbits_le32(pll->ipll.base + pll->ipll.pll_reg, PLL_MASK_ALL,
+						val);
+				writel(syn_sel, pll->ipll.base + pll->syn.set);
+				return best_rate;
+			}
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int cv1800b_fpll_set_parent(struct clk *clk, struct clk *parent)
+{
+	struct cv1800b_clk_fpll *pll = to_clk_fpll(clk);
+
+	if (parent->id == CV1800B_CLK_BYPASS)
+		cv1800b_clk_setbit(pll->ipll.base, &pll->syn.en);
+	else
+		cv1800b_clk_clrbit(pll->ipll.base, &pll->syn.en);
+
+	return 0;
+}
+
+const struct clk_ops cv1800b_fpll_ops = {
+	.enable = cv1800b_ipll_enable,
+	.disable = cv1800b_ipll_disable,
+	.get_rate = cv1800b_fpll_get_rate,
+	.set_rate = cv1800b_fpll_set_rate,
+	.set_parent = cv1800b_fpll_set_parent,
+};
+
+U_BOOT_DRIVER(cv1800b_clk_fpll) = {
+	.name = "cv1800b_clk_fpll",
+	.id = UCLASS_CLK,
+	.ops = &cv1800b_fpll_ops,
+	.flags = DM_FLAG_PRE_RELOC,
+};