[DO NOT MERGE v8 15/36] clk: renesas: Add SH7750/7751 CPG Driver

Yoshinori Sato <[email protected]>
Newsgroups gmane.linux.kernel.renesas-soc,gmane.linux.ports.sh.devel,gmane.linux.ide,gmane.linux.drivers.devicetree,gmane.linux.kernel,gmane.linux.kernel.clk,gmane.comp.video.dri.devel,gmane.linux.kernel.pci,gmane.linux.serial
Message-ID <3d4f043db8f4827c6bcf63c23cba00e03a8a2721.1716965617.git.ysato@users.sourceforge.jp>
Renesas SH7750 and SH7751 series CPG driver.
This driver supported frequency control and clock gating.

Signed-off-by: Yoshinori Sato <[email protected]>
---
 drivers/clk/renesas/Kconfig      |  13 +-
 drivers/clk/renesas/Makefile     |   1 +
 drivers/clk/renesas/clk-sh7750.c | 480 +++++++++++++++++++++++++++++++
 3 files changed, 491 insertions(+), 3 deletions(-)
 create mode 100644 drivers/clk/renesas/clk-sh7750.c

diff --git a/drivers/clk/renesas/Kconfig b/drivers/clk/renesas/Kconfig
index d252150402e8..482efcb6e76e 100644
--- a/drivers/clk/renesas/Kconfig
+++ b/drivers/clk/renesas/Kconfig
@@ -1,8 +1,8 @@
 # SPDX-License-Identifier: GPL-2.0
 
 config CLK_RENESAS
-	bool "Renesas SoC clock support" if COMPILE_TEST && !ARCH_RENESAS
-	default y if ARCH_RENESAS
+	bool "Renesas SoC clock support" if COMPILE_TEST && !ARCH_RENESAS && !SUPERH
+	default y if ARCH_RENESAS || SUPERH
 	select CLK_EMEV2 if ARCH_EMEV2
 	select CLK_RZA1 if ARCH_R7S72100
 	select CLK_R7S9210 if ARCH_R7S9210
@@ -41,6 +41,9 @@ config CLK_RENESAS
 	select CLK_R9A08G045 if ARCH_R9A08G045
 	select CLK_R9A09G011 if ARCH_R9A09G011
 	select CLK_SH73A0 if ARCH_SH73A0
+	select CLK_SH7750 if CPU_SUBTYPE_SH7750 || CPU_SUBTYPE_SH7750S || \
+	                     CPU_SUBTYPE_SH7750R || CPU_SUBTYPE_SH7751 || \
+			     CPU_SUBTYPE_SH7751R
 
 if CLK_RENESAS
 
@@ -198,7 +201,6 @@ config CLK_SH73A0
 	select CLK_RENESAS_CPG_MSTP
 	select CLK_RENESAS_DIV6
 
-
 # Family
 config CLK_RCAR_CPG_LIB
 	bool "CPG/MSSR library functions" if COMPILE_TEST
@@ -228,6 +230,11 @@ config CLK_RZG2L
 	bool "Renesas RZ/{G2L,G2UL,G3S,V2L} family clock support" if COMPILE_TEST
 	select RESET_CONTROLLER
 
+config CLK_SH7750
+	bool "Renesas SH7750/7751 family clock support" if COMPILE_TEST
+	help
+	  This is a driver for SH7750 / SH7751 CPG.
+
 # Generic
 config CLK_RENESAS_CPG_MSSR
 	bool "CPG/MSSR clock support" if COMPILE_TEST
diff --git a/drivers/clk/renesas/Makefile b/drivers/clk/renesas/Makefile
index f7e18679c3b8..ea0ffa8d59c4 100644
--- a/drivers/clk/renesas/Makefile
+++ b/drivers/clk/renesas/Makefile
@@ -38,6 +38,7 @@ obj-$(CONFIG_CLK_R9A07G054)		+= r9a07g044-cpg.o
 obj-$(CONFIG_CLK_R9A08G045)		+= r9a08g045-cpg.o
 obj-$(CONFIG_CLK_R9A09G011)		+= r9a09g011-cpg.o
 obj-$(CONFIG_CLK_SH73A0)		+= clk-sh73a0.o
+obj-$(CONFIG_CLK_SH7750)		+= clk-sh7750.o
 
 # Family
 obj-$(CONFIG_CLK_RCAR_CPG_LIB)		+= rcar-cpg-lib.o
diff --git a/drivers/clk/renesas/clk-sh7750.c b/drivers/clk/renesas/clk-sh7750.c
new file mode 100644
index 000000000000..6f1580ce96c5
--- /dev/null
+++ b/drivers/clk/renesas/clk-sh7750.c
@@ -0,0 +1,480 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas SH7750/51 CPG driver
+ *
+ * Copyright 2023 Yoshinori Sato <[email protected]>
+ */
+
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+
+/* PCLK divide rate selector */
+static const struct clk_div_table pdiv_table[] = {
+	{ .val = 0, .div = 2, },
+	{ .val = 1, .div = 3, },
+	{ .val = 2, .div = 4, },
+	{ .val = 3, .div = 6, },
+	{ .val = 4, .div = 8, },
+	{ }
+};
+
+/* ICLK and BCLK divide rate selector */
+static const struct clk_div_table div_table[] = {
+	{ .val = 0, .div = 1, },
+	{ .val = 1, .div = 2, },
+	{ .val = 2, .div = 3, },
+	{ .val = 3, .div = 4, },
+	{ .val = 4, .div = 6, },
+	{ .val = 5, .div = 8, },
+	{ }
+};
+
+struct cpg_priv {
+	struct clk_hw hw;
+	spinlock_t clklock;
+	void __iomem *frqcr;
+	void __iomem *clkstp00;
+	u32 mode;
+	u32 feat;
+};
+
+/* CPG feature flag */
+#define CPG_DIV1	BIT(0)	/* 7750, 7750S, 7751 */
+#define MSTP_CR2	BIT(1)	/* 7750S, 7750R, 7751, 7751R */
+#define MSTP_CLKSTP	BIT(2)	/* 7750R, 7751, 7751R */
+#define MSTP_CSTP2	BIT(3)	/* 7751, 7751R */
+
+enum {
+	CPG_SH7750,
+	CPG_SH7750S,
+	CPG_SH7750R,
+	CPG_SH7751,
+	CPG_SH7751R,
+};
+
+static const u32 cpg_feature[] = {
+	[CPG_SH7750]  = CPG_DIV1,
+	[CPG_SH7750S] = CPG_DIV1 | MSTP_CR2,
+	[CPG_SH7750R] = MSTP_CR2 | MSTP_CLKSTP,
+	[CPG_SH7751]  = CPG_DIV1 | MSTP_CR2 | MSTP_CLKSTP | MSTP_CSTP2,
+	[CPG_SH7751R] = MSTP_CR2 | MSTP_CLKSTP | MSTP_CSTP2,
+};
+
+enum clk_type {CLK_DIV, CLK_STBCR, CLK_STBCR2, CLK_CLKSTP00};
+
+enum {
+	FRQCR = 0,
+	STBCR = 4,
+	WTCNT = 8,
+	WTCSR = 12,
+	STBCR2 = 16,
+	CLKSTP00 = 0,
+	CLKSTPCLR00 = 8,
+};
+
+static struct cpg_priv *cpg_data;
+
+#define to_priv(_hw) container_of(_hw, struct cpg_priv, hw)
+
+#define FRQCR_PLL1EN BIT(10)
+static const unsigned int pll1mult[] = { 12, 12, 6, 12, 6, 12, 1};
+
+static unsigned long pll_recalc_rate(struct clk_hw *hw,
+				      unsigned long parent_rate)
+{
+	struct cpg_priv *cpg = to_priv(hw);
+	unsigned long rate = parent_rate;
+	u16 frqcr;
+
+	frqcr = ioread16(cpg->frqcr);
+	if (frqcr & FRQCR_PLL1EN) {
+		rate *= pll1mult[cpg->mode];
+		if (cpg->mode < 6 && (cpg->feat & CPG_DIV1))
+			rate /= 2;
+	}
+	return rate;
+}
+
+static void get_round_rate(struct cpg_priv *cpg,
+			   unsigned long *out, bool *pllen,
+			   unsigned long rate, unsigned long prate)
+{
+	long pllout, res;
+	bool pll;
+
+	if (cpg->mode < 6 && (cpg->feat & CPG_DIV1))
+		prate /= 2;
+
+	pllout = prate * pll1mult[cpg->mode];
+	if (abs(pllout - rate) > abs(prate - rate)) {
+		res = prate;
+		pll = false;
+	} else {
+		res = pllout;
+		pll = true;
+	}
+	if (out)
+		*out = res;
+	if (pllen)
+		*pllen = pll;
+}
+
+static int pll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
+{
+	struct cpg_priv *cpg = to_priv(hw);
+
+	get_round_rate(cpg, &req->rate, NULL, req->rate, req->best_parent_rate);
+	return 0;
+}
+
+static int pll_set_rate(struct clk_hw *hw,
+			unsigned long rate, unsigned long prate)
+{
+	struct cpg_priv *cpg = to_priv(hw);
+	bool oldpll, newpll;
+	u16 frqcr;
+
+	frqcr = ioread16(cpg->frqcr);
+	get_round_rate(cpg, NULL, &newpll, rate, prate);
+	oldpll = frqcr & FRQCR_PLL1EN;
+	frqcr &= ~FRQCR_PLL1EN;
+
+	if (newpll) {
+		frqcr |= FRQCR_PLL1EN;
+		if (!oldpll) {
+			/* set PLL wakeup delay time */
+			iowrite16(0xa500, cpg->frqcr + WTCNT);
+			iowrite16(0xa507, cpg->frqcr + WTCNT);
+			iowrite16(0x5a00, cpg->frqcr + WTCSR);
+		}
+	}
+	iowrite16(frqcr, cpg->frqcr);
+
+	/* Test for new PLL state */
+	frqcr = ioread16(cpg->frqcr);
+	oldpll = frqcr & FRQCR_PLL1EN;
+	return !(oldpll == newpll);
+}
+
+static const struct clk_ops pll_ops = {
+	.recalc_rate = pll_recalc_rate,
+	.determine_rate = pll_determine_rate,
+	.set_rate = pll_set_rate,
+};
+
+#define PLLOUT "pllout"
+
+static int register_pll(struct device_node *node, struct cpg_priv *cpg)
+{
+	const char *clk_name = node->name;
+	const char *parent_name;
+	struct clk_init_data init = {
+		.name = PLLOUT,
+		.ops = &pll_ops,
+		.flags = 0,
+		.num_parents = 1,
+	};
+	int ret;
+
+	parent_name = of_clk_get_parent_name(node, 0);
+	init.parent_names = &parent_name;
+	cpg->hw.init = &init;
+
+	ret = of_clk_hw_register(node, &cpg->hw);
+	if (ret < 0)
+		pr_err("%pOF: failed to register pll clock %s (%d)\n",
+		       node, clk_name, ret);
+	return ret;
+}
+
+static void clkstp00_sw(struct clk_hw *hw, bool on)
+{
+	u32 val;
+	struct clk_gate *gate = to_clk_gate(hw);
+
+	val = BIT(gate->bit_idx);
+	if (on)
+		writel(val, gate->reg + CLKSTPCLR00);
+	else
+		writel(val, gate->reg);
+}
+
+static int clkstp00_enable(struct clk_hw *hw)
+{
+	clkstp00_sw(hw, true);
+	return 0;
+}
+
+static void clkstp00_disable(struct clk_hw *hw)
+{
+	clkstp00_sw(hw, false);
+}
+
+static int clkstp00_is_enabled(struct clk_hw *hw)
+{
+	u8 val;
+	struct clk_gate *gate = to_clk_gate(hw);
+
+	val = readb(gate->reg);
+	val &= 1 << gate->bit_idx;
+	return val == 0;
+}
+
+static const struct clk_ops gate_clkstp00_ops = {
+	.enable = clkstp00_enable,
+	.disable = clkstp00_disable,
+	.is_enabled = clkstp00_is_enabled,
+};
+
+static struct clk_hw *clk_hw_register_clkstp(struct device_node *node,
+					     const char *name,
+					     const char *parent,
+					     void __iomem *reg, int bit,
+					     spinlock_t *lock)
+{
+	struct clk_gate *gate;
+	struct clk_init_data init = {
+		.name = name,
+		.ops = &gate_clkstp00_ops,
+		.flags = 0,
+		.parent_names = &parent,
+		.num_parents = 1,
+	};
+	struct clk_hw *hw;
+	int ret;
+
+	gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+	if (gate == NULL)
+		goto error;
+	gate->reg = reg;
+	gate->bit_idx = bit;
+	gate->flags = 0;
+	gate->lock = lock;
+	gate->hw.init = &init;
+	hw = &gate->hw;
+	ret = of_clk_hw_register(node, hw);
+	if (ret < 0)
+		goto error;
+	return hw;
+error:
+	kfree(gate);
+	return ERR_PTR(ret);
+}
+
+#define STBCR_BASE 5
+#define CLKSTP_BASE 2
+static int register_div(struct device_node *node, struct cpg_priv *cpg)
+{
+	static const char * const divout[] = {
+		"fck", "bck", "ick",
+	};
+	static const char * const stbcrout[] = {
+		"sci_clk", "rtc_clk", "tmu012_clk",	/* STBCR */
+		"scif_clk", "dmac_clk",			/* STBCR */
+		"ubc_clk", "sq_clk",			/* STBCR2 */
+	};
+	static const char * const clkstpout[] = {
+		"intc_clk", "tmu34_clk", "pcic_clk",	/* CLKSTP00 */
+	};
+
+	int num_clk = ARRAY_SIZE(divout) + ARRAY_SIZE(stbcrout) + ARRAY_SIZE(clkstpout);
+	struct clk_hw_onecell_data *data;
+	struct clk_hw *reg_hw;
+	unsigned int i, n;
+	int ret;
+
+	data = kzalloc(struct_size(data, hws, num_clk + 1), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	num_clk = 0;
+	for (i = 0; i < ARRAY_SIZE(divout); i++) {
+		reg_hw = __clk_hw_register_divider(NULL, node, divout[i],
+						   PLLOUT, NULL, NULL,
+						   0, cpg->frqcr, i * 3, 3,
+						   CLK_DIVIDER_REG_16BIT,
+						   (i == 0) ? pdiv_table : div_table,
+						   &cpg->clklock);
+		if (IS_ERR(reg_hw)) {
+			ret = PTR_ERR(reg_hw);
+			goto error;
+		}
+		data->hws[num_clk++] = reg_hw;
+	}
+
+	n = (cpg->feat & MSTP_CR2) ? ARRAY_SIZE(stbcrout) : STBCR_BASE;
+	for (i = 0; i < n; i++) {
+		u32 off =  (i < (ARRAY_SIZE(stbcrout) - 2)) ? STBCR : STBCR2;
+
+		reg_hw = __clk_hw_register_gate(NULL, node, stbcrout[i],
+						divout[0], NULL, NULL,
+						0, cpg->frqcr + off, i % STBCR_BASE,
+						CLK_GATE_REG_8BIT | CLK_GATE_SET_TO_DISABLE,
+						&cpg->clklock);
+		if (IS_ERR(reg_hw)) {
+			ret = PTR_ERR(reg_hw);
+			goto error;
+		}
+		data->hws[num_clk++] = reg_hw;
+	}
+
+	if (cpg->feat & MSTP_CLKSTP) {
+		n = (cpg->feat & MSTP_CSTP2) ? ARRAY_SIZE(clkstpout) : CLKSTP_BASE;
+		for (i = 0; i < n; i++) {
+			reg_hw = clk_hw_register_clkstp(node, clkstpout[i],
+							divout[0], cpg->clkstp00,
+							i, &cpg->clklock);
+			if (IS_ERR(reg_hw)) {
+				ret = PTR_ERR(reg_hw);
+				goto error;
+			}
+			data->hws[num_clk++] = reg_hw;
+		}
+	}
+
+	data->num = num_clk;
+	ret = of_clk_add_hw_provider(node, of_clk_hw_onecell_get, data);
+	if (ret < 0)
+		goto error;
+	return 0;
+
+error:
+	pr_err("%pOF: failed to register clock divider (%d)\n",
+	       node, ret);
+	for (num_clk--; num_clk >= 0; num_clk--)
+		kfree(data->hws[num_clk]);
+	kfree(data);
+	return ret;
+}
+
+#define NR_CLKMODE 7
+static int sh7750_cpg_setup(struct device_node *node, u32 feat)
+{
+	struct cpg_priv *cpg;
+	u32 mode = NR_CLKMODE;
+	int ret = 0;
+
+	cpg_data = NULL;
+
+	of_property_read_u32_index(node, "renesas,mode", 0, &mode);
+	if (mode >= NR_CLKMODE) {
+		pr_err("%s: Invalid clock mode setting (%u)\n",
+		       node->name, mode);
+		return -EINVAL;
+	}
+
+	cpg = kzalloc(sizeof(struct cpg_priv), GFP_KERNEL);
+	if (!cpg)
+		return -ENOMEM;
+
+	cpg->frqcr = of_iomap(node, 0);
+	if (cpg->frqcr == NULL) {
+		pr_err("%pOF: failed to map divide register", node);
+		ret = -ENODEV;
+		goto cpg_free;
+	}
+
+	if (feat & MSTP_CLKSTP) {
+		cpg->clkstp00 = of_iomap(node, 1);
+		if (cpg->clkstp00 == NULL) {
+			pr_err("%pOF: failed to map clkstp00 register", node);
+			ret = -ENODEV;
+			goto unmap_frqcr;
+		}
+	}
+	cpg->feat = feat;
+	cpg->mode = mode;
+
+	ret = register_pll(node, cpg);
+	if (ret < 0)
+		goto unmap_clkstp00;
+
+	ret = register_div(node, cpg);
+	if (ret < 0)
+		goto unmap_clkstp00;
+
+	cpg_data = cpg;
+	return 0;
+
+unmap_clkstp00:
+	iounmap(cpg->clkstp00);
+unmap_frqcr:
+	iounmap(cpg->frqcr);
+cpg_free:
+	kfree(cpg);
+	return ret;
+}
+
+static void __init sh7750_cpg_init(struct device_node *node)
+{
+	sh7750_cpg_setup(node, cpg_feature[CPG_SH7750]);
+}
+
+static void __init sh7750s_cpg_init(struct device_node *node)
+{
+	sh7750_cpg_setup(node, cpg_feature[CPG_SH7750S]);
+}
+
+static void __init sh7750r_cpg_init(struct device_node *node)
+{
+	sh7750_cpg_setup(node, cpg_feature[CPG_SH7750R]);
+}
+
+static void __init sh7751_cpg_init(struct device_node *node)
+{
+	sh7750_cpg_setup(node, cpg_feature[CPG_SH7751]);
+}
+
+static void __init sh7751r_cpg_init(struct device_node *node)
+{
+	sh7750_cpg_setup(node, cpg_feature[CPG_SH7751R]);
+}
+
+CLK_OF_DECLARE_DRIVER(sh7750_cpg, "renesas,sh7750-cpg",
+		      sh7750_cpg_init);
+CLK_OF_DECLARE_DRIVER(sh7750s_cpg, "renesas,sh7750s-cpg",
+		      sh7750s_cpg_init);
+CLK_OF_DECLARE_DRIVER(sh7750r_cpg, "renesas,sh7750r-cpg",
+		      sh7750r_cpg_init);
+CLK_OF_DECLARE_DRIVER(sh7751_cpg, "renesas,sh7751-cpg",
+		      sh7751_cpg_init);
+CLK_OF_DECLARE_DRIVER(sh7751r_cpg, "renesas,sh7751r-cpg",
+		      sh7751r_cpg_init);
+
+static int sh7750_cpg_probe(struct platform_device *pdev)
+{
+	u32 feature;
+
+	if (cpg_data)
+		return 0;
+	feature = *(u32 *)of_device_get_match_data(&pdev->dev);
+	return sh7750_cpg_setup(pdev->dev.of_node, feature);
+}
+
+static const struct of_device_id sh7750_cpg_of_match[] = {
+	{ .compatible = "renesas,sh7750-cpg",
+	  .data = &cpg_feature[CPG_SH7750] },
+	{ .compatible = "renesas,sh7750s-cpg",
+	  .data = &cpg_feature[CPG_SH7750S] },
+	{ .compatible = "renesas,sh7750r-cpg",
+	  .data = &cpg_feature[CPG_SH7750R] },
+	{ .compatible = "renesas,sh7751-cpg",
+	  .data = &cpg_feature[CPG_SH7751] },
+	{ .compatible = "renesas,sh7751r-cpg",
+	  .data = &cpg_feature[CPG_SH7751R] },
+	{ }
+};
+
+static struct platform_driver sh7750_cpg_driver = {
+	.probe  = sh7750_cpg_probe,
+	.driver = {
+		.name = "sh7750-cpg",
+		.of_match_table = sh7750_cpg_of_match,
+	},
+};
+builtin_platform_driver(sh7750_cpg_driver);
-- 
2.39.2
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.