[PATCH v2 6/8] clk: Add Sunplus SP7021 clock driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add clock driver for Sunplus SP7021 SoC.

Signed-off-by: Qin Jian <qinjian@xxxxxxxxxxx>
---
 MAINTAINERS              |   1 +
 drivers/clk/Kconfig      |   8 +
 drivers/clk/Makefile     |   1 +
 drivers/clk/clk-sp7021.c | 770 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 780 insertions(+)
 create mode 100644 drivers/clk/clk-sp7021.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 90ebb823f..5069f552f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2663,6 +2663,7 @@ W:	https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
 F:	Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
 F:	Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
 F:	Documentation/devicetree/bindings/reset/sunplus,reset.yaml
+F:	drivers/clk/clk-sp7021.c
 F:	drivers/reset/reset-sunplus.c
 F:	include/dt-bindings/clock/sp-sp7021.h
 F:	include/dt-bindings/reset/sp-sp7021.h
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index c5b3dc973..e2494c2aa 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -334,6 +334,14 @@ config COMMON_CLK_VC5
 	  This driver supports the IDT VersaClock 5 and VersaClock 6
 	  programmable clock generators.
 
+config COMMON_CLK_SP7021
+	def_bool OF && COMMON_CLK && SOC_SP7021
+	help
+	  This driver supports the Sunplus SP7021 SoC clocks.
+	  It implemented SP7021 PLLs/gate.
+	  Not all features of the PLL are currently supported
+	  by the driver.
+
 config COMMON_CLK_STM32MP157
 	def_bool COMMON_CLK && MACH_STM32MP157
 	help
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index e42312121..f15bb5070 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -60,6 +60,7 @@ obj-$(CONFIG_COMMON_CLK_SI5351)		+= clk-si5351.o
 obj-$(CONFIG_COMMON_CLK_SI514)		+= clk-si514.o
 obj-$(CONFIG_COMMON_CLK_SI544)		+= clk-si544.o
 obj-$(CONFIG_COMMON_CLK_SI570)		+= clk-si570.o
+obj-$(CONFIG_COMMON_CLK_SP7021)		+= clk-sp7021.o
 obj-$(CONFIG_COMMON_CLK_STM32F)		+= clk-stm32f4.o
 obj-$(CONFIG_COMMON_CLK_STM32H7)	+= clk-stm32h7.o
 obj-$(CONFIG_COMMON_CLK_STM32MP157)	+= clk-stm32mp1.o
diff --git a/drivers/clk/clk-sp7021.c b/drivers/clk/clk-sp7021.c
new file mode 100644
index 000000000..39ff84825
--- /dev/null
+++ b/drivers/clk/clk-sp7021.c
@@ -0,0 +1,770 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/*
+ * Copyright (C) Sunplus Technology Co., Ltd.
+ *       All rights reserved.
+ */
+//#define DEBUG
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <dt-bindings/clock/sp-sp7021.h>
+
+//#define TRACE	pr_info("### %s:%d (%d)\n", __func__, __LINE__, (clk->reg - REG(4, 0)) / 4)
+#define TRACE
+
+#ifndef clk_readl
+#define clk_readl  readl
+#define clk_writel writel
+#endif
+
+#define MASK_SET(shift, width, value) \
+({ \
+	u32 m = ((1 << (width)) - 1) << (shift); \
+	(m << 16) | (((value) << (shift)) & m); \
+})
+#define MASK_GET(shift, width, value)	(((value) >> (shift)) & ((1 << (width)) - 1))
+
+#define REG(i)	(pll_regs + (i) * 4)
+
+#define PLLA_CTL	REG(7)
+#define PLLE_CTL	REG(12)
+#define PLLF_CTL	REG(13)
+#define PLLTV_CTL	REG(14)
+#define PLLSYS_CTL	REG(26)
+
+#define EXTCLK		"extclk"
+
+/* speical div_width values for PLLTV/PLLA */
+#define DIV_TV	33
+#define DIV_A	34
+
+/* PLLTV parameters */
+enum {
+	SEL_FRA,
+	SDM_MOD,
+	PH_SEL,
+	NFRA,
+	DIVR,
+	DIVN,
+	DIVM,
+	P_MAX
+};
+
+struct sp_pll {
+	struct clk_hw	hw;
+	void __iomem	*reg;
+	spinlock_t	*lock;
+	int		pd_bit;		/* power down bit idx */
+	int		bp_bit;		/* bypass bit idx */
+	unsigned long	brate;		/* base rate, FIXME: replace brate with muldiv */
+	int		div_shift;
+	int		div_width;
+	u32		p[P_MAX];	/* for hold PLLTV/PLLA parameters */
+};
+#define to_sp_pll(_hw)	container_of(_hw, struct sp_pll, hw)
+
+#define clk_regs	(moon_regs + 0x000) /* G0 ~ CLKEN */
+#define pll_regs	(moon_regs + 0x200) /* G4 ~ PLL */
+static void __iomem *moon_regs;
+
+#define P_EXTCLK	(1 << 16)
+static const char * const parents[] = {
+	"pllsys",
+	"extclk",
+};
+
+/* FIXME: parent clk incorrect cause clk_get_rate got error value */
+static const u32 gates[] = {
+	SYSTEM,
+	RTC,
+	IOCTL,
+	IOP,
+	OTPRX,
+	NOC,
+	BR,
+	RBUS_L00,
+	SPIFL,
+	SDCTRL0,
+	PERI0 | P_EXTCLK,
+	A926,
+	UMCTL2,
+	PERI1 | P_EXTCLK,
+
+	DDR_PHY0,
+	ACHIP,
+	STC0,
+	STC_AV0,
+	STC_AV1,
+	STC_AV2,
+	UA0 | P_EXTCLK,
+	UA1 | P_EXTCLK,
+	UA2 | P_EXTCLK,
+	UA3 | P_EXTCLK,
+	UA4 | P_EXTCLK,
+	HWUA | P_EXTCLK,
+	DDC0,
+	UADMA | P_EXTCLK,
+
+	CBDMA0,
+	CBDMA1,
+	SPI_COMBO_0,
+	SPI_COMBO_1,
+	SPI_COMBO_2,
+	SPI_COMBO_3,
+	AUD,
+	USBC0,
+	USBC1,
+	UPHY0,
+	UPHY1,
+
+	I2CM0,
+	I2CM1,
+	I2CM2,
+	I2CM3,
+	PMC,
+	CARD_CTL0,
+	CARD_CTL1,
+
+	CARD_CTL4,
+	BCH,
+	DDFCH,
+	CSIIW0,
+	CSIIW1,
+	MIPICSI0,
+	MIPICSI1,
+
+	HDMI_TX,
+	VPOST,
+
+	TGEN,
+	DMIX,
+	TCON,
+	INTERRUPT,
+
+	RGST,
+	GPIO,
+	RBUS_TOP,
+
+	MAILBOX,
+	SPIND,
+	I2C2CBUS,
+	SEC,
+	GPOST0,
+	DVE,
+
+	OSD0,
+	DISP_PWM,
+	UADBG,
+	DUMMY_MASTER,
+	FIO_CTL,
+	FPGA,
+	L2SW,
+	ICM,
+	AXI_GLOBAL,
+};
+static struct clk *clks[CLK_MAX];
+static struct clk_onecell_data clk_data;
+
+static DEFINE_SPINLOCK(plla_lock);
+static DEFINE_SPINLOCK(plle_lock);
+static DEFINE_SPINLOCK(pllf_lock);
+static DEFINE_SPINLOCK(pllsys_lock);
+static DEFINE_SPINLOCK(plltv_lock);
+
+#define _M		1000000UL
+#define F_27M		(27 * _M)
+
+/******************************************** PLL_TV *******************************************/
+
+//#define PLLTV_STEP_DIR (?) /* Unit: HZ */
+
+/* TODO: set proper FVCO range */
+#define FVCO_MIN	(100 * _M)
+#define FVCO_MAX	(200 * _M)
+
+#define F_MIN		(FVCO_MIN / 8)
+#define F_MAX		(FVCO_MAX)
+
+static long plltv_integer_div(struct sp_pll *clk, unsigned long freq)
+{
+	/* valid m values: 27M must be divisible by m, 0 means end */
+	static const u32 m_table[] = {
+		1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 25, 27, 30, 32, 0
+	};
+	u32 m, n, r;
+#ifdef PLLTV_STEP_DIR
+	u32 step = (PLLTV_STEP_DIR > 0) ? PLLTV_STEP_DIR : -PLLTV_STEP_DIR;
+	int calc_times = 1000000 / step;
+#endif
+	unsigned long fvco, nf;
+
+	TRACE;
+
+	/* check freq */
+	if (freq < F_MIN) {
+		pr_warn("[%s:%d] freq:%lu < F_MIN:%lu, round up\n",
+			__func__, __LINE__, freq, F_MIN);
+		freq = F_MIN;
+	} else if (freq > F_MAX) {
+		pr_warn("[%s:%d] freq:%lu > F_MAX:%lu, round down\n",
+			__func__, __LINE__, freq, F_MAX);
+		freq = F_MAX;
+	}
+
+#ifdef PLLTV_STEP_DIR
+	if ((freq % step) != 0)
+		freq += step - (freq % step) + ((PLLTV_STEP_DIR > 0) ? 0 : PLLTV_STEP_DIR);
+#endif
+
+#ifdef PLLTV_STEP_DIR
+CALC:
+	if (!calc_times) {
+		pr_err("[%s:%d] freq:%lu out of recalc times\n", __func__, __LINE__, freq);
+		return -ETIMEOUT;
+	}
+#endif
+
+	/* DIVR 0~3 */
+	for (r = 0; r <= 3; r++) {
+		fvco = freq << r;
+		if (fvco <= FVCO_MAX)
+			break;
+	}
+
+	/* DIVM */
+	for (m = 0; m_table[m]; m++) {
+		nf = fvco * m_table[m];
+		n = nf / F_27M;
+		if ((n * F_27M) == nf)
+			break;
+	}
+	m = m_table[m];
+
+	if (!m) {
+#ifdef PLLTV_STEP_DIR
+		freq += PLLTV_STEP_DIR;
+		calc_times--;
+		goto CALC;
+#else
+		pr_err("[%s:%d] freq:%lu not found a valid setting\n", __func__, __LINE__, freq);
+		return -EINVAL;
+#endif
+	}
+
+	/* save parameters */
+	clk->p[SEL_FRA] = 0;
+	clk->p[DIVR]    = r;
+	clk->p[DIVN]    = n;
+	clk->p[DIVM]    = m;
+
+	pr_info("[%s:%d]   M:%u N:%u R:%u   CKREF:%lu  FVCO:%lu  FCKOUT:%lu\n",
+		__func__, __LINE__, m, n, r, (fvco / m), fvco, freq);
+
+	return freq;
+}
+
+/* parameters for PLLTV fractional divider */
+/* FIXME: better parameter naming */
+static const u32 pt[][5] = {
+	/* conventional fractional */
+	{
+		1,			// factor
+		5,			// 5 * p0 (nint)
+		1,			// 1 * p0
+		F_27M,			// F_27M / p0
+		1,			// p0 / p2
+	},
+	/* phase rotation */
+	{
+		10,			// factor
+		54,			// 5.4 * p0 (nint)
+		2,			// 0.2 * p0
+		F_27M / 10,		// F_27M / p0
+		5,			// p0 / p2
+	},
+};
+static const u32 mods[] = { 91, 55 }; /* SDM_MOD mod values */
+
+static long plltv_fractional_div(struct sp_pll *clk, unsigned long freq)
+{
+	u32 m, r;
+	u32 nint, nfra;
+	u32 diff_min_quotient = 210000000, diff_min_remainder = 0;
+	u32 diff_min_sign = 0;
+	unsigned long fvco, nf, f, fout = 0;
+	int sdm, ph;
+
+	TRACE;
+	/* check freq */
+	if (freq < F_MIN) {
+		pr_warn("[%s:%d] freq:%lu < F_MIN:%lu, round up\n",
+			__func__, __LINE__, freq, F_MIN);
+		freq = F_MIN;
+	} else if (freq > F_MAX) {
+		pr_warn("[%s:%d] freq:%lu > F_MAX:%lu, round down\n",
+			__func__, __LINE__, freq, F_MAX);
+		freq = F_MAX;
+	}
+
+	/* DIVR 0~3 */
+	for (r = 0; r <= 3; r++) {
+		fvco = freq << r;
+		if (fvco <= FVCO_MAX)
+			break;
+	}
+	pr_info("freq:%lu fvco:%lu R:%u\n", freq, fvco, r);
+	f = F_27M >> r;
+
+	/* PH_SEL 1/0 */
+	for (ph = 1; ph >= 0; ph--) {
+		const u32 *pp = pt[ph];
+		u32 ms = 1;
+
+		/* SDM_MOD 0/1 */
+		for (sdm = 0; sdm <= 1; sdm++) {
+			u32 mod = mods[sdm];
+
+			/* DIVM 1~32 */
+			for (m = ms; m <= 32; m++) {
+				u32 diff_freq;
+				u32 diff_freq_quotient = 0, diff_freq_remainder = 0;
+				u32 diff_freq_sign = 0; /* 0:Positive number, 1:Negative number */
+
+				nf = fvco * m;
+				nint = nf / pp[3];
+
+				if (nint < pp[1])
+					continue;
+				if (nint > pp[1])
+					break;
+
+				nfra = (((nf % pp[3]) * mod * pp[4]) + (F_27M / 2)) / F_27M;
+				if (nfra)
+					diff_freq = (f * (nint + pp[2]) / pp[0]) -
+								(f * (mod - nfra) / mod / pp[4]);
+				else
+					diff_freq = (f * (nint) / pp[0]);
+
+				diff_freq_quotient  = diff_freq / m;
+				diff_freq_remainder = ((diff_freq % m) * 1000) / m;
+
+				pr_info("m = %d N.f = %2d.%03d%03d, nfra = %d/%d  fout = %u\n",
+					m, nint, (nfra * 1000) / mod,
+					(((nfra * 1000) % mod) * 1000) / mod,
+					nfra, mod, diff_freq_quotient);
+
+				if (freq > diff_freq_quotient) {
+					diff_freq_quotient  = freq - diff_freq_quotient - 1;
+					diff_freq_remainder = 1000 - diff_freq_remainder;
+					diff_freq_sign = 1;
+				} else {
+					diff_freq_quotient = diff_freq_quotient - freq;
+					diff_freq_sign = 0;
+				}
+
+				if ((diff_min_quotient > diff_freq_quotient) ||
+					((diff_min_quotient == diff_freq_quotient) &&
+					(diff_min_remainder > diff_freq_remainder))) {
+
+					/* found a closer freq, save parameters */
+					TRACE;
+					clk->p[SEL_FRA] = 1;
+					clk->p[SDM_MOD] = sdm;
+					clk->p[PH_SEL]  = ph;
+					clk->p[NFRA]    = nfra;
+					clk->p[DIVR]    = r;
+					clk->p[DIVM]    = m;
+
+					fout = diff_freq / m;
+					diff_min_quotient = diff_freq_quotient;
+					diff_min_remainder = diff_freq_remainder;
+					diff_min_sign = diff_freq_sign;
+				}
+			}
+		}
+	}
+
+	if (!fout) {
+		pr_err("[%s:%d] freq:%lu not found a valid setting\n", __func__, __LINE__, freq);
+		return -EINVAL;
+	}
+
+	//pr_info("MOD:%u PH_SEL:%u NFRA:%u M:%u R:%u\n",
+	//	mods[clk->p[SDM_MOD]], clk->p[PH_SEL], clk->p[NFRA], clk->p[DIVM], clk->p[DIVR]);
+
+	pr_info("[%s:%d] real out:%lu/%lu Hz(%u, %u, sign %u)\n",
+		__func__, __LINE__, fout, freq,
+		diff_min_quotient, diff_min_remainder, diff_min_sign);
+
+	return fout;
+}
+
+static long plltv_div(struct sp_pll *clk, unsigned long freq)
+{
+	TRACE;
+	if (freq % 100)
+		return plltv_fractional_div(clk, freq);
+	else
+		return plltv_integer_div(clk, freq);
+}
+
+static void plltv_set_rate(struct sp_pll *clk)
+{
+	u32 reg;
+
+	//pr_info("MOD:%u PH_SEL:%u NFRA:%u M:%u R:%u\n",
+	//	mods[clk->p[SDM_MOD]], clk->p[PH_SEL], clk->p[NFRA], clk->p[DIVM], clk->p[DIVR]);
+	reg  = MASK_SET(1, 1, clk->p[SEL_FRA]);
+	reg |= MASK_SET(2, 1, clk->p[SDM_MOD]);
+	reg |= MASK_SET(4, 1, clk->p[PH_SEL]);
+	reg |= MASK_SET(6, 7, clk->p[NFRA]);
+	clk_writel(reg, clk->reg);
+
+	reg  = MASK_SET(7, 2, clk->p[DIVR]);
+	clk_writel(reg, clk->reg + 4);
+
+	reg  = MASK_SET(0, 8, clk->p[DIVN] - 1);
+	reg |= MASK_SET(8, 7, clk->p[DIVM] - 1);
+	clk_writel(reg, clk->reg + 8);
+}
+
+/******************************************** PLL_A ********************************************/
+
+/* from Q628_PLLs_REG_setting.xlsx */
+struct {
+	u32 rate;
+	u32 regs[5];
+} pa[] = {
+	{
+		.rate = 135475200,
+		.regs = {
+			0x4801,
+			0x02df,
+			0x248f,
+			0x0211,
+			0x33e9
+		}
+	},
+	{
+		.rate = 147456000,
+		.regs = {
+			0x4801,
+			0x1adf,
+			0x2490,
+			0x0349,
+			0x33e9
+		}
+	},
+	{
+		.rate = 196608000,
+		.regs = {
+			0x4801,
+			0x42ef,
+			0x2495,
+			0x01c6,
+			0x33e9
+		}
+	},
+};
+
+static void plla_set_rate(struct sp_pll *clk)
+{
+	const u32 *pp = pa[clk->p[0]].regs;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pa->regs); i++) {
+		clk_writel(0xffff0000 | pp[i], clk->reg + (i * 4));
+		pr_info("%04x\n", pp[i]);
+	}
+}
+
+static long plla_round_rate(struct sp_pll *clk, unsigned long rate)
+{
+	int i = ARRAY_SIZE(pa);
+
+	while (--i) {
+		if (rate >= pa[i].rate)
+			break;
+	}
+	clk->p[0] = i;
+	return pa[i].rate;
+}
+
+/******************************************* SP_PLL ********************************************/
+
+static long sp_pll_calc_div(struct sp_pll *clk, unsigned long rate)
+{
+	u32 fbdiv;
+	u32 max = 1 << clk->div_width;
+
+	fbdiv = DIV_ROUND_CLOSEST(rate, clk->brate);
+	if (fbdiv > max)
+		fbdiv = max;
+	return fbdiv;
+}
+
+static long sp_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+		unsigned long *prate)
+{
+	struct sp_pll *clk = to_sp_pll(hw);
+	long ret;
+
+	TRACE;
+	//pr_info("round_rate: %lu %lu\n", rate, *prate);
+
+	if (rate == *prate)
+		ret = *prate; /* bypass */
+	else if (clk->div_width == DIV_A) {
+		ret = plla_round_rate(clk, rate);
+	} else if (clk->div_width == DIV_TV) {
+		ret = plltv_div(clk, rate);
+		if (ret < 0)
+			ret = *prate;
+	} else
+		ret = sp_pll_calc_div(clk, rate) * clk->brate;
+
+	return ret;
+}
+
+static unsigned long sp_pll_recalc_rate(struct clk_hw *hw,
+		unsigned long prate)
+{
+	struct sp_pll *clk = to_sp_pll(hw);
+	u32 reg = clk_readl(clk->reg);
+	unsigned long ret;
+
+	//TRACE;
+	if (reg & BIT(clk->bp_bit))
+		ret = prate; /* bypass */
+	else if (clk->div_width == DIV_A) {
+		ret = pa[clk->p[0]].rate;
+		//reg = clk_readl(clk->reg + 12); // G4.10 K_SDM_A
+	} else if (clk->div_width == DIV_TV) {
+		u32 m, r, reg2;
+
+		r = MASK_GET(7, 2, clk_readl(clk->reg + 4));
+		reg2 = clk_readl(clk->reg + 8);
+		m = MASK_GET(8, 7, reg2) + 1;
+
+		if (reg & BIT(1)) { /* SEL_FRA */
+			/* fractional divider */
+			u32 sdm  = MASK_GET(2, 1, reg);
+			u32 ph   = MASK_GET(4, 1, reg);
+			u32 nfra = MASK_GET(6, 7, reg);
+			const u32 *pp = pt[ph];
+
+			ret = prate >> r;
+			ret = (ret * (pp[1] + pp[2]) / pp[0]) -
+				  (ret * (mods[sdm] - nfra) / mods[sdm] / pp[4]);
+			ret /= m;
+		} else {
+			/* integer divider */
+			u32 n = MASK_GET(0, 8, reg2) + 1;
+
+			ret = (prate / m * n) >> r;
+		}
+	} else {
+		u32 fbdiv = MASK_GET(clk->div_shift, clk->div_width, reg) + 1;
+
+		ret = clk->brate * fbdiv;
+	}
+	//pr_info("recalc_rate: %lu -> %lu\n", prate, ret);
+
+	return ret;
+}
+
+static int sp_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+		unsigned long prate)
+{
+	struct sp_pll *clk = to_sp_pll(hw);
+	unsigned long flags;
+	u32 reg;
+
+	//TRACE;
+	pr_info("set_rate: %lu -> %lu\n", prate, rate);
+
+	spin_lock_irqsave(clk->lock, flags);
+
+	reg = BIT(clk->bp_bit + 16); /* HIWORD_MASK */
+
+	if (rate == prate)
+		reg |= BIT(clk->bp_bit); /* bypass */
+	else if (clk->div_width == DIV_A)
+		plla_set_rate(clk);
+	else if (clk->div_width == DIV_TV)
+		plltv_set_rate(clk);
+	else if (clk->div_width) {
+		u32 fbdiv = sp_pll_calc_div(clk, rate);
+
+		reg |= MASK_SET(clk->div_shift, clk->div_width, fbdiv - 1);
+	}
+
+	clk_writel(reg, clk->reg);
+
+	spin_unlock_irqrestore(clk->lock, flags);
+
+	return 0;
+}
+
+static int sp_pll_enable(struct clk_hw *hw)
+{
+	struct sp_pll *clk = to_sp_pll(hw);
+	unsigned long flags;
+
+	TRACE;
+	spin_lock_irqsave(clk->lock, flags);
+	clk_writel(BIT(clk->pd_bit + 16) | BIT(clk->pd_bit), clk->reg); /* power up */
+	spin_unlock_irqrestore(clk->lock, flags);
+
+	return 0;
+}
+
+static void sp_pll_disable(struct clk_hw *hw)
+{
+	struct sp_pll *clk = to_sp_pll(hw);
+	unsigned long flags;
+
+	TRACE;
+	spin_lock_irqsave(clk->lock, flags);
+	clk_writel(BIT(clk->pd_bit + 16), clk->reg); /* power down */
+	spin_unlock_irqrestore(clk->lock, flags);
+}
+
+static int sp_pll_is_enabled(struct clk_hw *hw)
+{
+	struct sp_pll *clk = to_sp_pll(hw);
+
+	return clk_readl(clk->reg) & BIT(clk->pd_bit);
+}
+
+static const struct clk_ops sp_pll_ops = {
+	.enable = sp_pll_enable,
+	.disable = sp_pll_disable,
+	.is_enabled = sp_pll_is_enabled,
+	.round_rate = sp_pll_round_rate,
+	.recalc_rate = sp_pll_recalc_rate,
+	.set_rate = sp_pll_set_rate
+};
+
+static const struct clk_ops sp_pll_sub_ops = {
+	.enable = sp_pll_enable,
+	.disable = sp_pll_disable,
+	.is_enabled = sp_pll_is_enabled,
+	.recalc_rate = sp_pll_recalc_rate,
+};
+
+struct clk *clk_register_sp_pll(const char *name, const char *parent,
+		void __iomem *reg, int pd_bit, int bp_bit,
+		unsigned long brate, int shift, int width,
+		spinlock_t *lock)
+{
+	struct sp_pll *pll;
+	struct clk *clk;
+	//unsigned long flags = 0;
+	struct clk_init_data initd = {
+		.name = name,
+		.parent_names = &parent,
+		.ops = (bp_bit >= 0) ? &sp_pll_ops : &sp_pll_sub_ops,
+		.num_parents = 1,
+		.flags = CLK_IGNORE_UNUSED
+	};
+
+	pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+	if (!pll)
+		return ERR_PTR(-ENOMEM);
+
+	if (reg == PLLSYS_CTL)
+		initd.flags |= CLK_IS_CRITICAL;
+
+	pll->hw.init = &initd;
+	pll->reg = reg;
+	pll->pd_bit = pd_bit;
+	pll->bp_bit = bp_bit;
+	pll->brate = brate;
+	pll->div_shift = shift;
+	pll->div_width = width;
+	pll->lock = lock;
+
+	clk = clk_register(NULL, &pll->hw);
+	if (WARN_ON(IS_ERR(clk))) {
+		kfree(pll);
+	} else {
+		pr_info("%-20s%lu\n", name, clk_get_rate(clk));
+		clk_register_clkdev(clk, NULL, name);
+	}
+
+	return clk;
+}
+
+static void __init sp_clk_setup(struct device_node *np)
+{
+	int i, j;
+
+	pr_info("sp-clkc init\n");
+
+	moon_regs = of_iomap(np, 0);
+	if (WARN_ON(!moon_regs)) {
+		pr_warn("sp-clkc regs missing.\n");
+		return; // -EIO
+	}
+
+	/* TODO: PLLs initial */
+
+	/* PLL_A */
+	clks[PLL_A] = clk_register_sp_pll("plla", EXTCLK,
+			PLLA_CTL, 11, 12, 27000000, 0, DIV_A, &plla_lock);
+
+	/* PLL_E */
+	clks[PLL_E] = clk_register_sp_pll("plle", EXTCLK,
+			PLLE_CTL, 6, 2, 50000000, 0, 0, &plle_lock);
+	clks[PLL_E_2P5] = clk_register_sp_pll("plle_2p5", "plle",
+			PLLE_CTL, 13, -1, 2500000, 0, 0, &plle_lock);
+	clks[PLL_E_25] = clk_register_sp_pll("plle_25", "plle",
+			PLLE_CTL, 12, -1, 25000000, 0, 0, &plle_lock);
+	clks[PLL_E_112P5] = clk_register_sp_pll("plle_112p5", "plle",
+			PLLE_CTL, 11, -1, 112500000, 0, 0, &plle_lock);
+
+	/* PLL_F */
+	clks[PLL_F] = clk_register_sp_pll("pllf", EXTCLK,
+			PLLF_CTL, 0, 10, 13500000, 1, 4, &pllf_lock);
+
+	/* PLL_TV */
+	clks[PLL_TV] = clk_register_sp_pll("plltv", EXTCLK,
+			PLLTV_CTL, 0, 15, 27000000, 0, DIV_TV, &plltv_lock);
+	clks[PLL_TV_A] = clk_register_divider(NULL, "plltv_a", "plltv", 0,
+			PLLTV_CTL + 4, 5, 1,
+			CLK_DIVIDER_POWER_OF_TWO, &plltv_lock);
+	clk_register_clkdev(clks[PLL_TV_A], NULL, "plltv_a");
+
+	/* PLL_SYS */
+	clks[PLL_SYS] = clk_register_sp_pll("pllsys", EXTCLK,
+			PLLSYS_CTL, 10, 9, 13500000, 0, 4, &pllsys_lock);
+
+	/* gates */
+	for (i = 0; i < ARRAY_SIZE(gates); i++) {
+		char s[10];
+
+		j = gates[i] & 0xffff;
+		sprintf(s, "clken%02x", j);
+		clks[j] = clk_register_gate(NULL, s, parents[gates[i] >> 16], CLK_IGNORE_UNUSED,
+			clk_regs + (j >> 4) * 4, j & 0x0f,
+			CLK_GATE_HIWORD_MASK, NULL);
+	}
+
+	clk_data.clks = clks;
+	clk_data.clk_num = ARRAY_SIZE(clks);
+	of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data);
+}
+
+CLK_OF_DECLARE(sp_clkc, "sunplus,sp7021-clkc", sp_clk_setup);
+
+MODULE_AUTHOR("Qin Jian <qinjian@xxxxxxxxxxx>");
+MODULE_DESCRIPTION("Sunplus SP7021 Clock Driver");
+MODULE_LICENSE("GPL v2");
-- 
2.33.1




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux