mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Changhuang Liang <changhuang.liang@starfivetech.com>
To: Stephen Boyd <sboyd@kernel.org>,
	Brian Masney <bmasney@redhat.com>,
	Jerome Brunet <jbrunet+clk@baylibre.com>,
	Philipp Zabel <p.zabel@pengutronix.de>,
	Emil Renner Berthing <kernel@esmil.dk>
Cc: Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>,
	linux-clk@vger.kernel.org, linux-kernel@vger.kernel.org,
	devicetree@vger.kernel.org, linux-riscv@lists.infradead.org,
	Changhuang Liang <changhuang.liang@starfivetech.com>,
	Hal Feng <hal.feng@starfivetech.com>
Subject: [PATCH v6 1/5] clk: starfive: Add system-0 domain PLL clock driver
Date: Sat, 10 Oct 2026 03:58:55 -0700	[thread overview]
Message-ID: <20261010105859.19505-2-changhuang.liang@starfivetech.com> (raw)
In-Reply-To: <20261010105859.19505-1-changhuang.liang@starfivetech.com>

Add system-0 domain PLL clock driver for StarFive JHB100 SoC.

The PLL itself is a building block shared by several system controllers
of the SoC, so it is implemented as a library. Each PLL is described with
three clocks matching the hardware topology: the VCO, a power-of-two post
divider and the output selecting one of them.

The VCO multipliers are either taken from a table or computed from the
requested rate. Rates are always derived from the actual parent rate.

The system-0 PLLs are registered by a platform driver bound to the device
created by the owner of the sys0 system controller.

Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com>
---
 drivers/clk/starfive/Kconfig                  |  16 +
 drivers/clk/starfive/Makefile                 |   2 +
 .../clk/starfive/clk-starfive-jhb100-pll.c    | 425 ++++++++++++++++++
 .../clk/starfive/clk-starfive-jhb100-pll.h    |  63 +++
 .../starfive/clk-starfive-jhb100-sys0-pll.c   |  69 +++
 5 files changed, 575 insertions(+)
 create mode 100644 drivers/clk/starfive/clk-starfive-jhb100-pll.c
 create mode 100644 drivers/clk/starfive/clk-starfive-jhb100-pll.h
 create mode 100644 drivers/clk/starfive/clk-starfive-jhb100-sys0-pll.c

diff --git a/drivers/clk/starfive/Kconfig b/drivers/clk/starfive/Kconfig
index 1c9dd5d47cb8..4a7c67445f4c 100644
--- a/drivers/clk/starfive/Kconfig
+++ b/drivers/clk/starfive/Kconfig
@@ -113,6 +113,9 @@ config CLK_STARFIVE_JHB100_PER3
 	  Say yes here to support the peripheral-3 clock controller
 	  on the StarFive JHB100 SoC.
 
+config CLK_STARFIVE_JHB100_PLL
+	bool
+
 config CLK_STARFIVE_JHB100_SYS0
 	bool "StarFive JHB100 system-0 clock support"
 	depends on ARCH_STARFIVE || COMPILE_TEST
@@ -123,6 +126,19 @@ config CLK_STARFIVE_JHB100_SYS0
 	  Say yes here to support the system-0 clock controller on the
 	  StarFive JHB100 SoC.
 
+config CLK_STARFIVE_JHB100_SYS0_PLL
+	bool "StarFive JHB100 system-0 PLL clock support"
+	depends on ARCH_STARFIVE || COMPILE_TEST
+	depends on STARFIVE_JHB100_SOCINFO
+	select CLK_STARFIVE_JHB100_PLL
+	select MFD_SYSCON
+	default ARCH_STARFIVE
+	help
+	  Say yes here to support the PLL2 to PLL5 clock generators found
+	  in the system-0 system controller of the StarFive JHB100 SoC.
+	  The device is instantiated by the JHB100 SoC information driver,
+	  which owns the system-0 system controller.
+
 config CLK_STARFIVE_JHB100_SYS1
 	bool "StarFive JHB100 system-1 clock support"
 	depends on ARCH_STARFIVE || COMPILE_TEST
diff --git a/drivers/clk/starfive/Makefile b/drivers/clk/starfive/Makefile
index 2c4554c2e70e..3a8f75c55c96 100644
--- a/drivers/clk/starfive/Makefile
+++ b/drivers/clk/starfive/Makefile
@@ -15,6 +15,8 @@ obj-$(CONFIG_CLK_STARFIVE_JHB100_PER0)		+= clk-starfive-jhb100-per0.o
 obj-$(CONFIG_CLK_STARFIVE_JHB100_PER1)		+= clk-starfive-jhb100-per1.o
 obj-$(CONFIG_CLK_STARFIVE_JHB100_PER2)		+= clk-starfive-jhb100-per2.o
 obj-$(CONFIG_CLK_STARFIVE_JHB100_PER3)		+= clk-starfive-jhb100-per3.o
+obj-$(CONFIG_CLK_STARFIVE_JHB100_PLL)		+= clk-starfive-jhb100-pll.o
 obj-$(CONFIG_CLK_STARFIVE_JHB100_SYS0)		+= clk-starfive-jhb100-sys0.o
+obj-$(CONFIG_CLK_STARFIVE_JHB100_SYS0_PLL)	+= clk-starfive-jhb100-sys0-pll.o
 obj-$(CONFIG_CLK_STARFIVE_JHB100_SYS1)		+= clk-starfive-jhb100-sys1.o
 obj-$(CONFIG_CLK_STARFIVE_JHB100_SYS2)		+= clk-starfive-jhb100-sys2.o
diff --git a/drivers/clk/starfive/clk-starfive-jhb100-pll.c b/drivers/clk/starfive/clk-starfive-jhb100-pll.c
new file mode 100644
index 000000000000..7be01022b1b8
--- /dev/null
+++ b/drivers/clk/starfive/clk-starfive-jhb100-pll.c
@@ -0,0 +1,425 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * StarFive JHB100 PLL Clock Generator
+ *
+ * Copyright (C) 2024 StarFive Technology Co., Ltd.
+ *
+ * Author: Changhuang Liang <changhuang.liang@starfivetech.com>
+ *
+ * Each PLL is described with three clocks:
+ *
+ *   osc --> vco --+-----------------> |
+ *                 |                   | out
+ *                 +--> postdiv -----> |
+ *
+ * - vco: fractional-N multiplier with a reference divider [1200MHz - 4800MHz]
+ *	  It has no gate: the PLL is only powered down while its multiplier is
+ *	  being changed.
+ * - postdiv: power-of-two divider, from 2 to 16 [75MHz - 4800MHz]
+ * - out: selects either the VCO or the post divider output [75MHz - 4800MHz]
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/math64.h>
+#include <linux/minmax.h>
+#include <linux/overflow.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#include "clk-starfive-jhb100-pll.h"
+
+#define JHB100_PLL_CFG0			0x0
+#define   JHB100_PLL_FBDIV			GENMASK(13, 2)
+#define   JHB100_PLL_FOUTPOSTDIV_EN		BIT(14)
+#define   JHB100_PLL_FOUTVCOP_EN		BIT(16)
+
+#define JHB100_PLL_CFG1			0x4
+#define   JHB100_PLL_FRAC			GENMASK(23, 0)
+#define   JHB100_PLL_LOCK			BIT(24)
+
+#define JHB100_PLL_CFG2			0x8
+#define   JHB100_PLL_POSTDIV			GENMASK(15, 14)
+#define   JHB100_PLL_REFDIV			GENMASK(23, 18)
+
+#define JHB100_PLL_FRAC_BITS		24
+#define JHB100_PLL_FBDIV_MIN		16
+#define JHB100_PLL_FBDIV_MAX		4095
+#define JHB100_PLL_POSTDIV_WIDTH	2
+
+/* VCO operating range */
+#define JHB100_PLL_VCO_MIN		(1200ULL * HZ_PER_MHZ)
+#define JHB100_PLL_VCO_MAX		(4800ULL * HZ_PER_MHZ)
+
+#define JHB100_PLL_OUT_VCO		0
+#define JHB100_PLL_OUT_POSTDIV		1
+
+#define JHB100_PLL_TIMEOUT_US		1000
+#define JHB100_PLL_INTERVAL_US		100
+
+struct jhb100_pll {
+	struct regmap *regmap;
+	const struct jhb100_pll_info *info;
+	struct clk_hw vco;
+	struct clk_hw postdiv;
+	struct clk_hw out;
+};
+
+struct jhb100_pll_priv {
+	unsigned int num;
+	struct jhb100_pll pll[] __counted_by(num);
+};
+
+static const struct clk_div_table jhb100_pll_postdiv_table[] = {
+	{ .val = 0, .div = 2 },
+	{ .val = 1, .div = 4 },
+	{ .val = 2, .div = 8 },
+	{ .val = 3, .div = 16 },
+};
+
+/* VCO */
+
+static unsigned long jhb100_pll_mult_to_rate(unsigned long parent_rate,
+					     const struct jhb100_pll_mult *mult)
+{
+	u64 m = ((u64)mult->fbdiv << JHB100_PLL_FRAC_BITS) | mult->frac;
+
+	if (!mult->refdiv)
+		return 0;
+
+	return div_u64(mul_u64_u32_shr(m, parent_rate, JHB100_PLL_FRAC_BITS),
+		       mult->refdiv);
+}
+
+/*
+ * @round_up selects the direction the multiplier is rounded to:
+ *
+ * - false, to find the multiplier of the highest rate lower or equal to
+ *   @rate. This is what a rate request needs, as a rate higher than the
+ *   requested one is rejected by the dividers down the tree.
+ * - true, to convert a rate returned by jhb100_pll_mult_to_rate(), which
+ *   rounds down, back to the multiplier it was computed from.
+ */
+static int jhb100_pll_rate_to_mult(u64 rate, unsigned long parent_rate,
+				   bool round_up, struct jhb100_pll_mult *mult)
+{
+	u64 m, fbdiv;
+
+	if (!parent_rate)
+		return -EINVAL;
+
+	if (round_up)
+		m = DIV64_U64_ROUND_UP(rate << JHB100_PLL_FRAC_BITS, parent_rate);
+	else
+		m = div64_u64(rate << JHB100_PLL_FRAC_BITS, parent_rate);
+
+	fbdiv = m >> JHB100_PLL_FRAC_BITS;
+	if (fbdiv < JHB100_PLL_FBDIV_MIN || fbdiv > JHB100_PLL_FBDIV_MAX)
+		return -EINVAL;
+
+	mult->fbdiv = fbdiv;
+	mult->frac = m & JHB100_PLL_FRAC;
+	mult->refdiv = 1;
+
+	return 0;
+}
+
+static unsigned long jhb100_pll_vco_recalc_rate(struct clk_hw *hw,
+						unsigned long parent_rate)
+{
+	struct jhb100_pll *pll = container_of(hw, struct jhb100_pll, vco);
+	unsigned int offset = pll->info->offset;
+	struct jhb100_pll_mult mult;
+	u32 val;
+
+	if (regmap_read(pll->regmap, offset + JHB100_PLL_CFG0, &val))
+		return 0;
+	mult.fbdiv = FIELD_GET(JHB100_PLL_FBDIV, val);
+
+	if (regmap_read(pll->regmap, offset + JHB100_PLL_CFG1, &val))
+		return 0;
+	mult.frac = FIELD_GET(JHB100_PLL_FRAC, val);
+
+	if (regmap_read(pll->regmap, offset + JHB100_PLL_CFG2, &val))
+		return 0;
+	mult.refdiv = FIELD_GET(JHB100_PLL_REFDIV, val);
+
+	return jhb100_pll_mult_to_rate(parent_rate, &mult);
+}
+
+static int jhb100_pll_vco_determine_rate(struct clk_hw *hw,
+					 struct clk_rate_request *req)
+{
+	struct jhb100_pll *pll = container_of(hw, struct jhb100_pll, vco);
+	const struct jhb100_pll_info *info = pll->info;
+	unsigned long lowest = ULONG_MAX, best = 0;
+	struct jhb100_pll_mult mult;
+	unsigned int i;
+	int ret;
+
+	if (!info->nmults) {
+		ret = jhb100_pll_rate_to_mult(clamp_t(u64, req->rate,
+						      JHB100_PLL_VCO_MIN,
+						      JHB100_PLL_VCO_MAX),
+					      req->best_parent_rate, false, &mult);
+		if (ret)
+			return ret;
+
+		req->rate = jhb100_pll_mult_to_rate(req->best_parent_rate, &mult);
+		return 0;
+	}
+
+	/* find the highest rate lower or equal to the requested rate */
+	for (i = 0; i < info->nmults; i++) {
+		unsigned long rate = jhb100_pll_mult_to_rate(req->best_parent_rate,
+							     &info->mults[i]);
+
+		if (rate <= req->rate && rate > best)
+			best = rate;
+		if (rate < lowest)
+			lowest = rate;
+	}
+
+	req->rate = best ?: lowest;
+
+	return 0;
+}
+
+static int jhb100_pll_vco_set_rate(struct clk_hw *hw, unsigned long rate,
+				   unsigned long parent_rate)
+{
+	struct jhb100_pll *pll = container_of(hw, struct jhb100_pll, vco);
+	const struct jhb100_pll_info *info = pll->info;
+	const struct jhb100_pll_mult *mult = NULL;
+	struct jhb100_pll_mult calc;
+	unsigned int val, i;
+	int ret;
+
+	if (info->nmults) {
+		for (i = 0; i < info->nmults; i++) {
+			if (jhb100_pll_mult_to_rate(parent_rate, &info->mults[i]) == rate) {
+				mult = &info->mults[i];
+				break;
+			}
+		}
+
+		if (!mult)
+			return -EINVAL;
+	} else {
+		ret = jhb100_pll_rate_to_mult(rate, parent_rate, true, &calc);
+		if (ret)
+			return ret;
+
+		mult = &calc;
+	}
+
+	ret = regmap_update_bits(pll->regmap, info->offset + JHB100_PLL_CFG0,
+				 JHB100_PLL_FBDIV,
+				 FIELD_PREP(JHB100_PLL_FBDIV, mult->fbdiv));
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(pll->regmap, info->offset + JHB100_PLL_CFG1,
+				 JHB100_PLL_FRAC,
+				 FIELD_PREP(JHB100_PLL_FRAC, mult->frac));
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(pll->regmap, info->offset + JHB100_PLL_CFG2,
+				 JHB100_PLL_REFDIV,
+				 FIELD_PREP(JHB100_PLL_REFDIV, mult->refdiv));
+	if (ret)
+		return ret;
+
+	return regmap_read_poll_timeout(pll->regmap,
+					info->offset + JHB100_PLL_CFG1,
+					val, val & JHB100_PLL_LOCK,
+					JHB100_PLL_INTERVAL_US,
+					JHB100_PLL_TIMEOUT_US);
+}
+
+static const struct clk_ops jhb100_pll_vco_ops = {
+	.recalc_rate = jhb100_pll_vco_recalc_rate,
+	.determine_rate = jhb100_pll_vco_determine_rate,
+	.set_rate = jhb100_pll_vco_set_rate,
+};
+
+/* Post divider */
+
+static unsigned long jhb100_pll_postdiv_recalc_rate(struct clk_hw *hw,
+						    unsigned long parent_rate)
+{
+	struct jhb100_pll *pll = container_of(hw, struct jhb100_pll, postdiv);
+	u32 val;
+
+	if (regmap_read(pll->regmap, pll->info->offset + JHB100_PLL_CFG2, &val))
+		return 0;
+
+	return divider_recalc_rate(hw, parent_rate,
+				   FIELD_GET(JHB100_PLL_POSTDIV, val),
+				   jhb100_pll_postdiv_table, 0,
+				   JHB100_PLL_POSTDIV_WIDTH);
+}
+
+static int jhb100_pll_postdiv_determine_rate(struct clk_hw *hw,
+					     struct clk_rate_request *req)
+{
+	return divider_determine_rate(hw, req, jhb100_pll_postdiv_table,
+				      JHB100_PLL_POSTDIV_WIDTH, 0);
+}
+
+static int jhb100_pll_postdiv_set_rate(struct clk_hw *hw, unsigned long rate,
+				       unsigned long parent_rate)
+{
+	struct jhb100_pll *pll = container_of(hw, struct jhb100_pll, postdiv);
+	int val;
+
+	val = divider_get_val(rate, parent_rate, jhb100_pll_postdiv_table,
+			      JHB100_PLL_POSTDIV_WIDTH, 0);
+	if (val < 0)
+		return val;
+
+	return regmap_update_bits(pll->regmap,
+				  pll->info->offset + JHB100_PLL_CFG2,
+				  JHB100_PLL_POSTDIV,
+				  FIELD_PREP(JHB100_PLL_POSTDIV, val));
+}
+
+static const struct clk_ops jhb100_pll_postdiv_ops = {
+	.recalc_rate = jhb100_pll_postdiv_recalc_rate,
+	.determine_rate = jhb100_pll_postdiv_determine_rate,
+	.set_rate = jhb100_pll_postdiv_set_rate,
+};
+
+/* Output selection */
+
+static int jhb100_pll_out_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct jhb100_pll *pll = container_of(hw, struct jhb100_pll, out);
+
+	return regmap_update_bits(pll->regmap,
+				  pll->info->offset + JHB100_PLL_CFG0,
+				  JHB100_PLL_FOUTVCOP_EN | JHB100_PLL_FOUTPOSTDIV_EN,
+				  index == JHB100_PLL_OUT_POSTDIV ?
+				  JHB100_PLL_FOUTPOSTDIV_EN : JHB100_PLL_FOUTVCOP_EN);
+}
+
+static u8 jhb100_pll_out_get_parent(struct clk_hw *hw)
+{
+	struct jhb100_pll *pll = container_of(hw, struct jhb100_pll, out);
+	u32 val;
+
+	if (regmap_read(pll->regmap, pll->info->offset + JHB100_PLL_CFG0, &val))
+		return JHB100_PLL_OUT_POSTDIV;
+
+	return val & JHB100_PLL_FOUTPOSTDIV_EN ? JHB100_PLL_OUT_POSTDIV :
+						 JHB100_PLL_OUT_VCO;
+}
+
+static int jhb100_pll_out_init(struct clk_hw *hw)
+{
+	struct jhb100_pll *pll = container_of(hw, struct jhb100_pll, out);
+	u32 val;
+	int ret;
+
+	ret = regmap_read(pll->regmap, pll->info->offset + JHB100_PLL_CFG0, &val);
+	if (ret)
+		return ret;
+
+	/* Exactly one of the two outputs must be selected */
+	if (!(val & JHB100_PLL_FOUTVCOP_EN) != !(val & JHB100_PLL_FOUTPOSTDIV_EN))
+		return 0;
+
+	return jhb100_pll_out_set_parent(hw, JHB100_PLL_OUT_POSTDIV);
+}
+
+static const struct clk_ops jhb100_pll_out_ops = {
+	.init = jhb100_pll_out_init,
+	.get_parent = jhb100_pll_out_get_parent,
+	.set_parent = jhb100_pll_out_set_parent,
+	.determine_rate = __clk_mux_determine_rate,
+};
+
+static struct clk_hw *jhb100_pll_get(struct of_phandle_args *clkspec, void *data)
+{
+	struct jhb100_pll_priv *priv = data;
+	unsigned int idx = clkspec->args[0];
+
+	if (idx < priv->num)
+		return &priv->pll[idx].out;
+
+	return ERR_PTR(-EINVAL);
+}
+
+/**
+ * devm_jhb100_pll_register - register the PLLs of a system controller
+ * @dev: device providing the clocks. The reference clock is looked up at
+ *	 index 0 of the "clocks" property of its DT node, or of the DT node
+ *	 of its parent if it has none.
+ * @regmap: regmap of the system controller
+ * @info: description of the PLLs, indexed by clock ID
+ * @num: number of PLLs in @info
+ *
+ * Return: 0 on success, a negative error code otherwise.
+ */
+int devm_jhb100_pll_register(struct device *dev, struct regmap *regmap,
+			     const struct jhb100_pll_info *info,
+			     unsigned int num)
+{
+	struct jhb100_pll_priv *priv;
+	unsigned int i;
+	int ret;
+
+	priv = devm_kzalloc(dev, struct_size(priv, pll, num), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->num = num;
+
+	for (i = 0; i < num; i++) {
+		struct jhb100_pll *pll = &priv->pll[i];
+		struct clk_parent_data parents[2] = { };
+		struct clk_init_data init = { };
+
+		pll->regmap = regmap;
+		pll->info = &info[i];
+
+		parents[0].index = 0;
+		init.name = info[i].vco_name;
+		init.ops = &jhb100_pll_vco_ops;
+		init.parent_data = parents;
+		init.num_parents = 1;
+		pll->vco.init = &init;
+
+		ret = devm_clk_hw_register(dev, &pll->vco);
+		if (ret)
+			return ret;
+
+		parents[0].hw = &pll->vco;
+		init.name = info[i].postdiv_name;
+		init.ops = &jhb100_pll_postdiv_ops;
+		init.flags = CLK_SET_RATE_PARENT;
+		pll->postdiv.init = &init;
+
+		ret = devm_clk_hw_register(dev, &pll->postdiv);
+		if (ret)
+			return ret;
+
+		parents[JHB100_PLL_OUT_VCO].hw = &pll->vco;
+		parents[JHB100_PLL_OUT_POSTDIV].hw = &pll->postdiv;
+		init.name = info[i].out_name;
+		init.ops = &jhb100_pll_out_ops;
+		init.num_parents = 2;
+		init.flags = CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT;
+		pll->out.init = &init;
+
+		ret = devm_clk_hw_register(dev, &pll->out);
+		if (ret)
+			return ret;
+	}
+
+	return devm_of_clk_add_hw_provider(dev, jhb100_pll_get, priv);
+}
diff --git a/drivers/clk/starfive/clk-starfive-jhb100-pll.h b/drivers/clk/starfive/clk-starfive-jhb100-pll.h
new file mode 100644
index 000000000000..3b5054f5bc25
--- /dev/null
+++ b/drivers/clk/starfive/clk-starfive-jhb100-pll.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * StarFive JHB100 PLL Clock Generator
+ *
+ * Copyright (C) 2024 StarFive Technology Co., Ltd.
+ */
+
+#ifndef __CLK_STARFIVE_JHB100_PLL_H__
+#define __CLK_STARFIVE_JHB100_PLL_H__
+
+#include <linux/types.h>
+
+struct device;
+struct regmap;
+
+/**
+ * struct jhb100_pll_mult - VCO multiplier setting
+ * @frac: fractional part of the feedback divider, in units of 1 / 2^24
+ * @fbdiv: integer part of the feedback divider
+ * @refdiv: reference divider
+ *
+ * The VCO rate is parent_rate * (fbdiv + frac / 2^24) / refdiv.
+ */
+struct jhb100_pll_mult {
+	u32 frac;
+	u16 fbdiv;
+	u8 refdiv;
+};
+
+/**
+ * struct jhb100_pll_info - description of one PLL instance
+ * @vco_name: name of the VCO clock
+ * @postdiv_name: name of the post divider clock
+ * @out_name: name of the output clock
+ * @mults: multipliers supported by the VCO. If NULL, the multiplier is
+ *	   computed from the requested rate.
+ * @nmults: number of entries in @mults
+ * @offset: offset of the PLL registers in the system controller
+ */
+struct jhb100_pll_info {
+	const char *vco_name;
+	const char *postdiv_name;
+	const char *out_name;
+	const struct jhb100_pll_mult *mults;
+	unsigned int nmults;
+	u8 offset;
+};
+
+#define JHB100_PLL(_idx, _name, _offset, _mults, _nmults)		\
+	[_idx] = {							\
+		.vco_name = _name "_vco",				\
+		.postdiv_name = _name "_postdiv",			\
+		.out_name = _name "_out",				\
+		.mults = _mults,					\
+		.nmults = _nmults,					\
+		.offset = _offset,					\
+	}
+
+int devm_jhb100_pll_register(struct device *dev, struct regmap *regmap,
+			     const struct jhb100_pll_info *info,
+			     unsigned int num);
+
+#endif /* __CLK_STARFIVE_JHB100_PLL_H__ */
diff --git a/drivers/clk/starfive/clk-starfive-jhb100-sys0-pll.c b/drivers/clk/starfive/clk-starfive-jhb100-sys0-pll.c
new file mode 100644
index 000000000000..7ccf66a72c0a
--- /dev/null
+++ b/drivers/clk/starfive/clk-starfive-jhb100-sys0-pll.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * StarFive JHB100 System-0 domain PLL Clock Driver
+ *
+ * Copyright (C) 2024 StarFive Technology Co., Ltd.
+ *
+ * Author: Changhuang Liang <changhuang.liang@starfivetech.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/clock/starfive,jhb100-crg.h>
+
+#include "clk-starfive-jhb100-pll.h"
+
+#define JHB100_PLL2_OFFSET		0x00
+#define JHB100_PLL3_OFFSET		0x0c
+#define JHB100_PLL4_OFFSET		0x18
+#define JHB100_PLL5_OFFSET		0x24
+
+static const struct jhb100_pll_mult jhb100_pll2_mults[] = {
+	{ .fbdiv = 72, .frac = 4252017, .refdiv = 1 },
+};
+
+static const struct jhb100_pll_mult jhb100_pll3_mults[] = {
+	{ .fbdiv = 64, .frac = 0, .refdiv = 1 },
+};
+
+static const struct jhb100_pll_info jhb100_sys0_pll_info[] = {
+	JHB100_PLL(JHB100_SYS0PLL_PLL2_OUT, "pll2", JHB100_PLL2_OFFSET,
+		   jhb100_pll2_mults, ARRAY_SIZE(jhb100_pll2_mults)),
+	JHB100_PLL(JHB100_SYS0PLL_PLL3_OUT, "pll3", JHB100_PLL3_OFFSET,
+		   jhb100_pll3_mults, ARRAY_SIZE(jhb100_pll3_mults)),
+	JHB100_PLL(JHB100_SYS0PLL_PLL4_OUT, "pll4", JHB100_PLL4_OFFSET,
+		   NULL, 0),
+	JHB100_PLL(JHB100_SYS0PLL_PLL5_OUT, "pll5", JHB100_PLL5_OFFSET,
+		   NULL, 0),
+};
+
+static int jhb100_sys0_pll_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct regmap *regmap;
+
+	/*
+	 * This device is instantiated by the sys0 system controller, which
+	 * owns the DT node describing the PLL registers.
+	 */
+	regmap = syscon_node_to_regmap(dev_of_node(dev->parent));
+	if (IS_ERR(regmap))
+		return dev_err_probe(dev, PTR_ERR(regmap),
+				     "failed to get syscon regmap\n");
+
+	return devm_jhb100_pll_register(dev, regmap, jhb100_sys0_pll_info,
+					ARRAY_SIZE(jhb100_sys0_pll_info));
+}
+
+static struct platform_driver jhb100_sys0_pll_driver = {
+	.probe = jhb100_sys0_pll_probe,
+	.driver = {
+		.name = "jhb100-sys0-pll",
+		.suppress_bind_attrs = true,
+	},
+};
+builtin_platform_driver(jhb100_sys0_pll_driver);
-- 
2.25.1


  reply	other threads:[~2026-10-10 12:34 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-10 10:58 [PATCH v6 0/5] Add StarFive JHB100 syscon modules Changhuang Liang
2026-10-10 10:58 ` Changhuang Liang [this message]
2026-10-10 10:58 ` [PATCH v6 2/5] clk: starfive: Add Peripheral-0 domain PLL clock driver Changhuang Liang
2026-10-10 10:58 ` [PATCH v6 3/5] clk: starfive: Add Peripheral-1 " Changhuang Liang
2026-10-10 10:58 ` [PATCH v6 4/5] reset: starfive: Introduce active_low Changhuang Liang
2026-10-10 10:58 ` [PATCH v6 5/5] reset: starfive: Add syscon reset driver support Changhuang Liang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261010105859.19505-2-changhuang.liang@starfivetech.com \
    --to=changhuang.liang@starfivetech.com \
    --cc=bmasney@redhat.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=hal.feng@starfivetech.com \
    --cc=jbrunet+clk@baylibre.com \
    --cc=kernel@esmil.dk \
    --cc=krzk+dt@kernel.org \
    --cc=linux-clk@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-riscv@lists.infradead.org \
    --cc=p.zabel@pengutronix.de \
    --cc=robh@kernel.org \
    --cc=sboyd@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®