mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Jisheng Zhang <jszhang@kernel.org>
To: Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>,
	Stephen Boyd <sboyd@kernel.org>,
	Brian Masney <bmasney+clk@redhat.com>,
	Jerome Brunet <jbrunet+clk@baylibre.com>,
	Andi Shyti <andi.shyti@kernel.org>,
	Alexandre Belloni <alexandre.belloni@bootlin.com>,
	Frank Li <Frank.Li@nxp.com>, Lee Jones <lee@kernel.org>,
	Linus Walleij <linusw@kernel.org>,
	Liam Girdwood <lgirdwood@gmail.com>,
	Mark Brown <broonie@kernel.org>,
	Philipp Zabel <p.zabel@pengutronix.de>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	Jiri Slaby <jirislaby@kernel.org>,
	Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-clk@vger.kernel.org, linux-i2c@vger.kernel.org,
	linux-i3c@lists.infradead.org, mfd@lists.linux.dev,
	linux-gpio@vger.kernel.org, linux-serial@vger.kernel.org,
	linux-spi@vger.kernel.org, linux-arm-kernel@lists.infradead.org
Subject: [PATCH 16/20] clk: berlin: add Synaptics SL261X SoC clocks and plls
Date: Tue, 29 Sep 2026 14:14:13 +0800	[thread overview]
Message-ID: <20260929061417.20733-17-jszhang@kernel.org> (raw)
In-Reply-To: <20260929061417.20733-1-jszhang@kernel.org>

Add support for clocks, plls in Synaptics SL261X SoCs.

Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
 drivers/clk/Kconfig                |   1 +
 drivers/clk/berlin/Kconfig         |  25 ++
 drivers/clk/berlin/Makefile        |   4 +
 drivers/clk/berlin/sl-clk.c        | 279 +++++++++++++++++++++
 drivers/clk/berlin/sl-clk.h        |  31 +++
 drivers/clk/berlin/sl-sysmgr-clk.c | 250 +++++++++++++++++++
 drivers/clk/berlin/sl-sysmgr-clk.h |  44 ++++
 drivers/clk/berlin/sl261x-clks.c   | 385 +++++++++++++++++++++++++++++
 drivers/clk/berlin/sl261x-pll.c    | 145 +++++++++++
 drivers/clk/berlin/sl261x-pll.h    |  14 ++
 10 files changed, 1178 insertions(+)
 create mode 100644 drivers/clk/berlin/Kconfig
 create mode 100644 drivers/clk/berlin/sl-clk.c
 create mode 100644 drivers/clk/berlin/sl-clk.h
 create mode 100644 drivers/clk/berlin/sl-sysmgr-clk.c
 create mode 100644 drivers/clk/berlin/sl-sysmgr-clk.h
 create mode 100644 drivers/clk/berlin/sl261x-clks.c
 create mode 100644 drivers/clk/berlin/sl261x-pll.c
 create mode 100644 drivers/clk/berlin/sl261x-pll.h

diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index f9592fd9ec2b..8052db52e28f 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -509,6 +509,7 @@ source "drivers/clk/actions/Kconfig"
 source "drivers/clk/analogbits/Kconfig"
 source "drivers/clk/aspeed/Kconfig"
 source "drivers/clk/bcm/Kconfig"
+source "drivers/clk/berlin/Kconfig"
 source "drivers/clk/cix/Kconfig"
 source "drivers/clk/eswin/Kconfig"
 source "drivers/clk/hisilicon/Kconfig"
diff --git a/drivers/clk/berlin/Kconfig b/drivers/clk/berlin/Kconfig
new file mode 100644
index 000000000000..2d5694cd29b7
--- /dev/null
+++ b/drivers/clk/berlin/Kconfig
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config SL_COMMON_CLK
+	bool
+
+config SL_SYSMGR_CLK
+	bool
+
+config SL261X_PLL
+	tristate "Synaptics SL261X PLL Driver"
+	depends on ARCH_BERLIN || COMPILE_TEST
+	default n
+	help
+	  This driver provides support for PLLs on Synaptics SL261X SoC.
+
+config SL261X_CLK
+	tristate "Synaptics SL261X Clock Driver"
+	depends on ARCH_BERLIN || COMPILE_TEST
+	select SL_COMMON_CLK
+	select SL_SYSMGR_CLK
+	default n
+	help
+	  This driver provides support for clock controller on Synaptics SL261X
+	  SoC. The clock controller generates and supplies clocks to various
+	  peripherals within the SoC.
diff --git a/drivers/clk/berlin/Makefile b/drivers/clk/berlin/Makefile
index 3733733a4806..2a664782d8a2 100644
--- a/drivers/clk/berlin/Makefile
+++ b/drivers/clk/berlin/Makefile
@@ -3,3 +3,7 @@ obj-y += berlin2-avpll.o berlin2-pll.o berlin2-div.o
 obj-$(CONFIG_MACH_BERLIN_BG2)	+= bg2.o
 obj-$(CONFIG_MACH_BERLIN_BG2CD)	+= bg2.o
 obj-$(CONFIG_MACH_BERLIN_BG2Q)	+= bg2q.o
+obj-$(CONFIG_SL_COMMON_CLK)	+= sl-clk.o
+obj-$(CONFIG_SL_SYSMGR_CLK)	+= sl-sysmgr-clk.o
+obj-$(CONFIG_SL261X_PLL)	+= sl261x-pll.o
+obj-$(CONFIG_SL261X_CLK)	+= sl261x-clks.o
diff --git a/drivers/clk/berlin/sl-clk.c b/drivers/clk/berlin/sl-clk.c
new file mode 100644
index 000000000000..e212320a0637
--- /dev/null
+++ b/drivers/clk/berlin/sl-clk.c
@@ -0,0 +1,279 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Synaptics Incorporated
+ * Copyright (c) 2015 Marvell Technology Group Ltd.
+ *
+ * Author: Jisheng Zhang <jszhang@kernel.org>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include "sl-clk.h"
+
+#define SL_CLK_EN		BIT(0)
+#define SL_CLK_PLL_SEL		GENMASK(3, 1)
+#define SL_CLK_PLL_SWITCH	BIT(4)
+#define SL_CLK_DIV_SWITCH	BIT(5)
+#define SL_CLK_DIV3_SWITCH	BIT(6)
+#define SL_CLK_DIV_SEL		GENMASK(9, 7)
+#define SL_MAX_PARENTS	6
+
+struct sl_clk {
+	struct clk_hw hw;
+	void __iomem *reg;
+	spinlock_t *lock;
+	struct device *dev;
+};
+
+#define to_sl_clk(_hw) container_of(_hw, struct sl_clk, hw)
+
+static const unsigned int sl_clk_values[] = { 1, 2, 3, 4, 6, 8, 12 };
+
+static unsigned int sl_clk_value(u32 val)
+{
+	static const u8 div_table[] = { 0, 2, 4, 6, 8, 12, 0, 0 };
+
+	if (val & SL_CLK_DIV3_SWITCH)
+		return 3;
+	if (!(val & SL_CLK_DIV_SWITCH))
+		return 1;
+
+	return div_table[FIELD_GET(SL_CLK_DIV_SEL, val)];
+}
+
+static unsigned long sl_clk_recalc_rate(struct clk_hw *hw,
+					unsigned long parent_rate)
+{
+	struct sl_clk *clk = to_sl_clk(hw);
+	unsigned int divisor = sl_clk_value(readl_relaxed(clk->reg));
+
+	if (WARN_ON_ONCE(!divisor))
+		return 0;
+
+	return parent_rate / divisor;
+}
+
+static unsigned int sl_clk_best(unsigned long parent_rate,
+				unsigned long rate)
+{
+	unsigned long best_diff = ULONG_MAX;
+	unsigned int best = 1, i;
+
+	for (i = 0; i < ARRAY_SIZE(sl_clk_values); i++) {
+		unsigned long candidate = parent_rate / sl_clk_values[i];
+		unsigned long diff = candidate > rate ? candidate - rate :
+							     rate - candidate;
+
+		if (diff < best_diff) {
+			best_diff = diff;
+			best = sl_clk_values[i];
+		}
+	}
+
+	return best;
+}
+
+static int sl_clk_determine_rate(struct clk_hw *hw,
+				 struct clk_rate_request *req)
+{
+	struct clk_hw *parent = clk_hw_get_parent(hw);
+
+	if (!parent)
+		return -EINVAL;
+
+	req->best_parent_hw = parent;
+	req->best_parent_rate = clk_hw_get_rate(parent);
+	req->rate = req->best_parent_rate /
+		    sl_clk_best(req->best_parent_rate, req->rate);
+
+	return 0;
+}
+
+static int sl_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+			   unsigned long parent_rate)
+{
+	struct sl_clk *clk = to_sl_clk(hw);
+	unsigned int divisor, select;
+	unsigned long flags;
+	u32 val;
+
+	divisor = sl_clk_best(parent_rate, rate);
+	if (parent_rate / divisor != rate)
+		return -EINVAL;
+
+	spin_lock_irqsave(clk->lock, flags);
+	val = readl_relaxed(clk->reg);
+	val &= ~(SL_CLK_DIV3_SWITCH | SL_CLK_DIV_SWITCH |
+		 SL_CLK_DIV_SEL);
+	if (divisor == 3) {
+		val |= SL_CLK_DIV3_SWITCH;
+	} else if (divisor != 1) {
+		switch (divisor) {
+		case 2:
+			select = 1;
+			break;
+		case 4:
+			select = 2;
+			break;
+		case 6:
+			select = 3;
+			break;
+		case 8:
+			select = 4;
+			break;
+		case 12:
+			select = 5;
+			break;
+		default:
+			spin_unlock_irqrestore(clk->lock, flags);
+			return -EINVAL;
+		}
+		val |= SL_CLK_DIV_SWITCH |
+			FIELD_PREP(SL_CLK_DIV_SEL, select);
+	}
+	writel_relaxed(val, clk->reg);
+	spin_unlock_irqrestore(clk->lock, flags);
+
+	return 0;
+}
+
+static u8 sl_clk_get_parent(struct clk_hw *hw)
+{
+	struct sl_clk *clk = to_sl_clk(hw);
+	u32 val = readl_relaxed(clk->reg);
+	u8 selector;
+	unsigned int index;
+
+	if (!(val & SL_CLK_PLL_SWITCH))
+		return 0;
+
+	selector = FIELD_GET(SL_CLK_PLL_SEL, val);
+	index = selector + 1;
+	if (index < clk_hw_get_num_parents(hw))
+		return index;
+
+	dev_warn(clk->dev, "%s: unsupported parent selector %u\n",
+		 clk_hw_get_name(hw), selector);
+	return 0;
+}
+
+static int sl_clk_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct sl_clk *clk = to_sl_clk(hw);
+	unsigned long flags;
+	u32 val;
+
+	if (index >= clk_hw_get_num_parents(hw) || index >= SL_MAX_PARENTS)
+		return -EINVAL;
+
+	spin_lock_irqsave(clk->lock, flags);
+	val = readl_relaxed(clk->reg);
+	if (!index) {
+		val &= ~SL_CLK_PLL_SWITCH;
+	} else {
+		val &= ~SL_CLK_PLL_SEL;
+		val |= SL_CLK_PLL_SWITCH |
+			FIELD_PREP(SL_CLK_PLL_SEL, index - 1);
+	}
+	writel_relaxed(val, clk->reg);
+	spin_unlock_irqrestore(clk->lock, flags);
+
+	return 0;
+}
+
+static int sl_clk_enable(struct clk_hw *hw)
+{
+	struct sl_clk *clk = to_sl_clk(hw);
+	unsigned long flags;
+	u32 val;
+
+	spin_lock_irqsave(clk->lock, flags);
+	val = readl_relaxed(clk->reg);
+	writel_relaxed(val | SL_CLK_EN, clk->reg);
+	spin_unlock_irqrestore(clk->lock, flags);
+
+	return 0;
+}
+
+static void sl_clk_disable(struct clk_hw *hw)
+{
+	struct sl_clk *clk = to_sl_clk(hw);
+	unsigned long flags;
+	u32 val;
+
+	spin_lock_irqsave(clk->lock, flags);
+	val = readl_relaxed(clk->reg);
+	writel_relaxed(val & ~SL_CLK_EN, clk->reg);
+	spin_unlock_irqrestore(clk->lock, flags);
+}
+
+static int sl_clk_is_enabled(struct clk_hw *hw)
+{
+	struct sl_clk *clk = to_sl_clk(hw);
+
+	return !!(readl_relaxed(clk->reg) & SL_CLK_EN);
+}
+
+static const struct clk_ops sl_clk_ops = {
+	.get_parent = sl_clk_get_parent,
+	.set_parent = sl_clk_set_parent,
+	.recalc_rate = sl_clk_recalc_rate,
+	.determine_rate = sl_clk_determine_rate,
+	.set_rate = sl_clk_set_rate,
+	.enable = sl_clk_enable,
+	.disable = sl_clk_disable,
+	.is_enabled = sl_clk_is_enabled,
+};
+
+struct clk_hw *sl_clk_register(struct device *dev, void __iomem *reg,
+			       const char *name, const char * const *parent_names,
+			       unsigned int num_parents, unsigned long flags,
+			       spinlock_t *lock)
+{
+	struct clk_init_data init = {};
+	struct sl_clk *clk;
+	unsigned int i;
+	int ret;
+
+	if (!dev || !reg || !name || !parent_names || !num_parents ||
+	    num_parents > SL_MAX_PARENTS || !lock)
+		return ERR_PTR(-EINVAL);
+
+	for (i = 0; i < num_parents; i++)
+		if (!parent_names[i])
+			return ERR_PTR(-EINVAL);
+
+	clk = devm_kzalloc(dev, sizeof(*clk), GFP_KERNEL);
+	if (!clk)
+		return ERR_PTR(-ENOMEM);
+
+	clk->dev = dev;
+	clk->reg = reg;
+	clk->lock = lock;
+
+	init.name = name;
+	init.ops = &sl_clk_ops;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	init.flags = flags;
+	clk->hw.init = &init;
+
+	ret = devm_clk_hw_register(dev, &clk->hw);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return &clk->hw;
+}
+EXPORT_SYMBOL_GPL(sl_clk_register);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Synaptics SL261x clock divider support");
diff --git a/drivers/clk/berlin/sl-clk.h b/drivers/clk/berlin/sl-clk.h
new file mode 100644
index 000000000000..7489b26a5e8d
--- /dev/null
+++ b/drivers/clk/berlin/sl-clk.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __SL_CLK_H
+#define __SL_CLK_H
+
+#include <linux/compiler_types.h>
+#include <linux/spinlock_types.h>
+
+struct clk_hw;
+struct device;
+
+struct sl_clk_desc {
+	const char	*name;
+	u32		id;
+	u32		offset;
+	unsigned long	flags;
+};
+
+struct sl_gateclk_desc {
+	const char	*name;
+	const char	*parent_name;
+	u32		id;
+	u8		bit_idx;
+	unsigned long	flags;
+};
+
+struct clk_hw *sl_clk_register(struct device *dev, void __iomem *reg,
+			       const char *name, const char * const *parent_names,
+			       unsigned int num_parents, unsigned long flags,
+			       spinlock_t *lock);
+
+#endif /* __SL_CLK_H */
diff --git a/drivers/clk/berlin/sl-sysmgr-clk.c b/drivers/clk/berlin/sl-sysmgr-clk.c
new file mode 100644
index 000000000000..fba8c22ca830
--- /dev/null
+++ b/drivers/clk/berlin/sl-sysmgr-clk.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Synaptics Incorporated
+ *
+ * Author: Jisheng Zhang <jszhang@kernel.org>
+ *
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/version.h>
+
+#include "sl-sysmgr-clk.h"
+
+#define CLK_EN		BIT(0)
+#define DIV_CHNG_EN	BIT(1)
+
+struct sl_sysmgr_gateclk {
+	struct clk_hw hw;
+	void __iomem *base;
+	u8 bit_idx;
+};
+
+struct sl_sysmgr_clk {
+	struct clk_hw hw;
+	void __iomem *base;
+	u8 div_bits;
+};
+
+struct sl_sysmgr_clk_priv {
+	struct sl_sysmgr_gateclk *gclks;
+	struct sl_sysmgr_clk *clks;
+};
+
+static DEFINE_SPINLOCK(sl_sysmgr_gateclk_lock);
+
+#define to_sl_sysmgr_gateclk(hw)	container_of(hw, struct sl_sysmgr_gateclk, hw)
+#define to_sl_sysmgr_clk(hw)		container_of(hw, struct sl_sysmgr_clk, hw)
+
+static int sl_sysmgr_gateclk_enable(struct clk_hw *hw)
+{
+	u32 val;
+	struct sl_sysmgr_gateclk *clk = to_sl_sysmgr_gateclk(hw);
+
+	guard(spinlock)(&sl_sysmgr_gateclk_lock);
+
+	val = readl_relaxed(clk->base);
+	if (!(val & BIT(clk->bit_idx))) {
+		val |= BIT(clk->bit_idx);
+		writel_relaxed(val, clk->base);
+	}
+
+	return 0;
+}
+
+static int sl_sysmgr_gateclk_is_enabled(struct clk_hw *hw)
+{
+	u32 val;
+	struct sl_sysmgr_gateclk *clk = to_sl_sysmgr_gateclk(hw);
+
+	guard(spinlock)(&sl_sysmgr_gateclk_lock);
+
+	val = readl_relaxed(clk->base);
+	val &= BIT(clk->bit_idx);
+
+	return val ? 1 : 0;
+}
+
+static const struct clk_ops sl_sysmgr_gateclk_ops = {
+	.enable		= sl_sysmgr_gateclk_enable,
+	.is_enabled	= sl_sysmgr_gateclk_is_enabled,
+};
+
+static int
+sl_sysmgr_gateclk_register(struct device *dev, struct sl_sysmgr_gateclk *gclk,
+			   const char *name, const char *parent_names,
+			   u8 bit_idx, void __iomem *base)
+{
+	struct clk_init_data init;
+
+	init.name = name;
+	init.ops = &sl_sysmgr_gateclk_ops;
+	init.parent_names = &parent_names;
+	init.num_parents = 1;
+	init.flags = 0;
+
+	gclk->bit_idx = bit_idx;
+	gclk->base = base;
+	gclk->hw.init = &init;
+
+	return devm_clk_hw_register(dev, &gclk->hw);
+}
+
+static unsigned long
+sl_sysmgr_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+	u32 val, div;
+	struct sl_sysmgr_clk *clk = to_sl_sysmgr_clk(hw);
+
+	val = readl_relaxed(clk->base);
+	div = (val >> 2) & ((1 << clk->div_bits) - 1);
+	++div;
+
+	return parent_rate / div;
+}
+
+static int sl_sysmgr_clk_enable(struct clk_hw *hw)
+{
+	u32 val;
+	struct sl_sysmgr_clk *clk = to_sl_sysmgr_clk(hw);
+
+	val = readl_relaxed(clk->base);
+	if (!(val & CLK_EN)) {
+		val |= CLK_EN;
+		writel_relaxed(val, clk->base);
+	}
+
+	return 0;
+}
+
+static int sl_sysmgr_clk_is_enabled(struct clk_hw *hw)
+{
+	u32 val;
+	struct sl_sysmgr_clk *clk = to_sl_sysmgr_clk(hw);
+
+	val = readl_relaxed(clk->base);
+	val &= CLK_EN;
+
+	return val ? 1 : 0;
+}
+
+static const struct clk_ops sl_sysmgr_clk_ops = {
+	.recalc_rate	= sl_sysmgr_clk_recalc_rate,
+	.enable		= sl_sysmgr_clk_enable,
+	.is_enabled	= sl_sysmgr_clk_is_enabled,
+};
+
+static int
+sl_sysmgr_clk_register(struct device *dev, struct sl_sysmgr_clk *sclk,
+		       const char *name, const char *parent_names,
+		       u8 div_bits, void __iomem *base)
+{
+	struct clk_init_data init;
+
+	init.name = name;
+	init.ops = &sl_sysmgr_clk_ops;
+	init.parent_names = &parent_names;
+	init.num_parents = 1;
+	init.flags = 0;
+
+	sclk->div_bits = div_bits;
+	sclk->base = base;
+	sclk->hw.init = &init;
+
+	return devm_clk_hw_register(dev, &sclk->hw);
+}
+
+static void sl_sysmgr_clk_put_node(void *data)
+{
+	of_node_put(data);
+}
+
+int sl_sysmgr_clk_setup(struct platform_device *pdev,
+			const struct sl_sysmgr_fixedclk_desc *fdescs,
+			int fclk_n,
+			const struct sl_sysmgr_gateclk_desc *gdescs,
+			int gclk_n,
+			u32 gclk_offset,
+			const struct sl_sysmgr_clk_desc *descs,
+			int clk_n)
+{
+	struct device_node *parent_np = of_get_parent(pdev->dev.of_node);
+	int i, ret, num = fclk_n + gclk_n + clk_n;
+	struct clk_hw_onecell_data *clk_data;
+	struct sl_sysmgr_clk_priv *priv;
+	struct clk_hw *clk_hw;
+	void __iomem *base;
+	u32 id;
+
+	ret = devm_add_action_or_reset(&pdev->dev, sl_sysmgr_clk_put_node, parent_np);
+	if (ret)
+		return ret;
+
+	clk_data = devm_kzalloc(&pdev->dev, struct_size(clk_data, hws, num), GFP_KERNEL);
+	if (!clk_data)
+		return -ENOMEM;
+	clk_data->num = num;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->gclks = devm_kcalloc(&pdev->dev, gclk_n, sizeof(*priv->gclks), GFP_KERNEL);
+	if (!priv->gclks)
+		return -ENOMEM;
+
+	priv->clks = devm_kcalloc(&pdev->dev, clk_n, sizeof(*priv->clks), GFP_KERNEL);
+	if (!priv->clks)
+		return -ENOMEM;
+
+	base = devm_of_iomap(&pdev->dev, parent_np, 0, NULL);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	for (i = 0; i < fclk_n; i++) {
+		clk_hw = devm_clk_hw_register_fixed_rate(&pdev->dev, fdescs[i].name,
+							 NULL, 0,
+							 fdescs[i].rate);
+		if (IS_ERR(clk_hw))
+			return PTR_ERR(clk_hw);
+
+		id = fdescs[i].id;
+		clk_data->hws[id] = clk_hw;
+	}
+
+	for (i = 0; i < gclk_n; i++) {
+		ret = sl_sysmgr_gateclk_register(&pdev->dev, &priv->gclks[i],
+						 gdescs[i].name, gdescs[i].parent_name,
+						 gdescs[i].bit_idx, base + gclk_offset);
+		if (ret)
+			return ret;
+
+		id = gdescs[i].id;
+		clk_data->hws[id] = &priv->gclks[i].hw;
+	}
+
+	for (i = 0; i < clk_n; i++) {
+		ret = sl_sysmgr_clk_register(&pdev->dev, &priv->clks[i],
+					     descs[i].name, descs[i].parent_name,
+					     descs[i].div_bits, base + descs[i].offset);
+		if (ret)
+			return ret;
+
+		id = descs[i].id;
+		clk_data->hws[id] = &priv->clks[i].hw;
+	}
+
+	platform_set_drvdata(pdev, priv);
+
+	return devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_onecell_get, clk_data);
+}
+EXPORT_SYMBOL_GPL(sl_sysmgr_clk_setup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>");
+MODULE_DESCRIPTION("SysMgr domain clk driver for Synaptics SL SoCs");
diff --git a/drivers/clk/berlin/sl-sysmgr-clk.h b/drivers/clk/berlin/sl-sysmgr-clk.h
new file mode 100644
index 000000000000..5ccf388bcccb
--- /dev/null
+++ b/drivers/clk/berlin/sl-sysmgr-clk.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2026 Synaptics Incorporated
+ *
+ * Author: Jisheng Zhang <jszhang@kernel.org>
+ *
+ */
+
+#ifndef __SL_SYSMGR_CLK_H
+#define __SL_SYSMGR_CLK_H
+
+struct sl_sysmgr_clk_desc {
+	const char	*name;
+	const char	*parent_name;
+	u32		id;
+	u32		offset;
+	u8		div_bits;
+	unsigned long	flags;
+};
+
+struct sl_sysmgr_gateclk_desc {
+	const char	*name;
+	const char	*parent_name;
+	u32		id;
+	u8		bit_idx;
+	unsigned long	flags;
+};
+
+struct sl_sysmgr_fixedclk_desc {
+	const char	*name;
+	u32		id;
+	unsigned long	rate;
+};
+
+int sl_sysmgr_clk_setup(struct platform_device *pdev,
+			const struct sl_sysmgr_fixedclk_desc *fdescs,
+			int fclk_n,
+			const struct sl_sysmgr_gateclk_desc *gdescs,
+			int gclk_n,
+			u32 gclk_offset,
+			const struct sl_sysmgr_clk_desc *descs,
+			int clk_n);
+
+#endif
diff --git a/drivers/clk/berlin/sl261x-clks.c b/drivers/clk/berlin/sl261x-clks.c
new file mode 100644
index 000000000000..d4e9cee7b685
--- /dev/null
+++ b/drivers/clk/berlin/sl261x-clks.c
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025 Synaptics Incorporated
+ *
+ * Author: Jisheng Zhang <jszhang@kernel.org>
+ *
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+#include <linux/of_device.h>
+#include <linux/module.h>
+
+#include <dt-bindings/clock/syna,sl261x.h>
+
+#include "sl261x-pll.h"
+#include "sl-clk.h"
+#include "sl-sysmgr-clk.h"
+
+#define REG_CLKENABLE		0x0420
+
+#define CLK_GATE_CTRL0		0x0600
+
+#define SYSPLL0_OFFSET		0x0200
+#define SYSPLL1_OFFSET		0x0220
+#define CPUPLL_OFFSET		0x2000
+#define APLL0_OFFSET		0x0008
+#define APLL1_OFFSET		0x0030
+
+static DEFINE_SPINLOCK(gate_lock);
+static DEFINE_SPINLOCK(lock);
+
+enum {
+	SYSPLL0_OUT, SYSPLL0_OUTF,
+	SYSPLL1_OUT, SYSPLL1_OUTF,
+	CPUPLL_OUT, CPUPLL_OUTF,
+	APLL0_OUT, APLL0_OUTF,
+	APLL1_OUT, APLL1_OUTF,
+};
+
+static const char *clk_names[] = {
+	[SYSPLL0_OUT]		= "syspll0_out",
+	[SYSPLL0_OUTF]		= "syspll0_outf",
+	[SYSPLL1_OUT]		= "syspll1_out",
+	[SYSPLL1_OUTF]		= "syspll1_outf",
+	[CPUPLL_OUT]		= "cpupll_out",
+	[CPUPLL_OUTF]		= "cpupll_outf",
+	[APLL0_OUT]		= "apll0_out",
+	[APLL0_OUTF]		= "apll0_outf",
+	[APLL1_OUT]		= "apll1_out",
+	[APLL1_OUTF]		= "apll1_outf",
+};
+
+static const struct sl_gateclk_desc sl261x_gates[] = {
+	{ "usb0coreclk",	"perifsysclk",	CLK_USB0CORE,		0 },
+	{ "sdiosysclk",		"perifsysclk",	CLK_SDIOSYS,		1 },
+	{ "emmcsysclk",		"perifsysclk",	CLK_EMMCSYS,		2 },
+	{ "gpuaxiclk",		"hpcclk",	CLK_GPUAXI,		3 },
+	{ "gethrgmiisysclk",	"perifsysclk",	CLK_GETHRGMIISYS,	4 },
+	{ "sdio1sysclk",	"perifsysclk",	CLK_SDIO1SYS,		5 },
+	{ "usb1coreclk",	"perifsysclk",	CLK_USB1CORE,		6 },
+	{ "gethrgmii1sysclk",	"perifsysclk",	CLK_GETHRGMII1SYS,	7 },
+	{ "usb0phyrefclk",	"perifsysclk",	CLK_USB0PHYREF,		8 },
+	{ "usb1phyrefclk",	"perifsysclk",	CLK_USB1PHYREF,		9 },
+	{ "apbuart0clk",	"apbcoreclk",	CLK_APBUART0,		10 },
+	{ "apbuart1clk",	"apbcoreclk",	CLK_APBUART1,		11 },
+	{ "apbuart2clk",	"apbcoreclk",	CLK_APBUART2,		12 },
+	{ "apbuart3lck",	"apbcoreclk",	CLK_APBUART3,		13 },
+	{ "apbi2c0clk",		"apbcoreclk",	CLK_APBI2C0,		14 },
+	{ "apbi2c1lck",		"apbcoreclk",	CLK_APBI2C1,		15 },
+	{ "apbspi0clk",		"apbcoreclk",	CLK_APBSPI0,		16 },
+	{ "apbspi1clk",		"apbcoreclk",	CLK_APBSPI1,		17 },
+	{ "apbspi2clk",		"apbcoreclk",	CLK_APBSPI2,		18 },
+	{ "apbspi3clk",		"apbcoreclk",	CLK_APBSPI3,		19 },
+	{ "apbgpioclk",		"apbcoreclk",	CLK_APBGPIO,		20 },
+	{ "apbtimersclk",	"apbcoreclk",	CLK_APBTIMERS,		21 },
+	{ "apbsyscntclk",	"apbcoreclk",	CLK_APBSYSCNT,		22 },
+	{ "apbwdtclk",		"apbcoreclk",	CLK_APBWDT,		23 },
+	{ "apbdmaclk",		"apbcoreclk",	CLK_APBDMA,		24 },
+};
+
+static const struct sl_clk_desc sl261x_descs[] = {
+	{ "cpufastrefclk",	CLK_CPUFASTREF,		0x440, CLK_IS_CRITICAL },
+	{ "memfastrefclk",	CLK_MEMFASTREF,		0x444, CLK_IS_CRITICAL },
+	{ "cfgclk",		CLK_CFG,		0x448, CLK_IS_CRITICAL },
+	{ "sysclk",		CLK_SYS,		0x44c, CLK_IS_CRITICAL },
+	{ "perifsysclk",	CLK_PERIFSYS,		0x450, CLK_IS_CRITICAL },
+	{ "apbcoreclk",		CLK_APBCORE,		0x454, CLK_IS_CRITICAL },
+	{ "apbserclk",		CLK_APBSER,		0x458, CLK_IS_CRITICAL },
+	{ "atbclk",		CLK_ATB,		0x45c },
+	{ "hpcclk",		CLK_HPC,		0x460, CLK_IS_CRITICAL },
+	{ "emmcclk",		CLK_EMMC,		0x464 },
+	{ "sd0clk",		CLK_SD0,		0x468 },
+	{ "sd1clk",		CLK_SD1,		0x46c },
+	{ "gethrgmiiclk",	CLK_GETHRGMII,		0x470 },
+	{ "gethrgmii1clk",	CLK_GETHRGMII1,		0x474 },
+	{ "ge0_ptp_refclk",	CLK_GE0_PTP_REF,	0x478 },
+	{ "ge1_ptp_refclk",	CLK_GE1_PTP_REF,	0x47c },
+	{ "usb2testclk",	CLK_USB2TEST,		0x480 },
+	{ "usb2test480mg0clk",	CLK_USB2TEST480MG0,	0x484 },
+	{ "usb2test480mg1clk",	CLK_USB2TEST480MG1,	0x488 },
+	{ "usb2test480mg2clk",	CLK_USB2TEST480MG2,	0x48c },
+	{ "usb2test100mg0clk",	CLK_USB2TEST100MG0,	0x490 },
+	{ "usb2test100mg1clk",	CLK_USB2TEST100MG1,	0x494 },
+	{ "usb2test100mg2clk",	CLK_USB2TEST100MG2,	0x498 },
+	{ "usb2test100mg3clk",	CLK_USB2TEST100MG3,	0x49c },
+	{ "periftest125mg0clk",	CLK_PERIFTEST125MG0,	0x4a0 },
+	{ "periftest200mg0clk",	CLK_PERIFTEST200MG0,	0x4a4 },
+	{ "periftest200mg1clk",	CLK_PERIFTEST200MG1,	0x4a8 },
+	{ "gpuclk",		CLK_GPU,		0x4ac },
+	{ "npuclk",		CLK_NPU,		0x4b0 },
+	{ "aviosysclk",		CLK_AVIOSYS,		0x4b4, CLK_IS_CRITICAL },
+	{ "aiosysclk",		CLK_AIOSYS,		0x4b8, CLK_IS_CRITICAL },
+	{ "avio_lcdc2scanclk",	CLK_AVIO_LCDC2SCAN,	0x4bc },
+	{ "avio_ipiclk",	CLK_AVIO_IPI,		0x4c0 },
+	{ "avio_pclk",		CLK_AVIO_P,		0x4c4 },
+	{ "avio_dphyrxtxescclk", CLK_AVIO_DPHYRXTXESC,	0x4c8 },
+	{ "aviofpllclk",	CLK_AVIOFPLL,		0x4cc },
+	{ "avio_rx_scanbyteclk", CLK_AVIO_RX_SCANBYTE,	0x4d0 },
+	{ "avio_rx_scantestclk", CLK_AVIO_RX_SCANTEST,	0x4d4 },
+};
+
+static const char *default_parent_names[] = {
+	"syspll0_out",
+	"apll0_out",
+	"syspll0_outf",
+	"apll0_outf",
+	"syspll1_out",
+	"syspll1_outf",
+};
+
+static const char *ge_ptp_refclk_parent_names[] = {
+	"osc",
+	"apll0_outf",
+	"syspll1_out",
+	"syspll0_out",
+	"clk_mcu_axi",
+};
+
+static int sl261x_soc_clk_setup(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *parent_np = of_get_parent(np);
+	struct clk_hw_onecell_data *clk_data;
+	const char **parent_names;
+	int num_parents;
+	void __iomem *base;
+	struct clk_hw **hws;
+	int n, ret;
+	u32 id;
+
+	base = devm_of_iomap(dev, parent_np, 0, NULL);
+	of_node_put(parent_np);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, SL261X_CLK_MAX), GFP_KERNEL);
+	if (!clk_data)
+		return -ENOMEM;
+	clk_data->num = SL261X_CLK_MAX;
+	hws = clk_data->hws;
+
+	/* sysplls */
+	ret = sl261x_pll_register(dev, base + SYSPLL0_OFFSET,
+				  clk_names[SYSPLL0_OUT], clk_names[SYSPLL0_OUTF]);
+	if (ret)
+		return ret;
+	ret = sl261x_pll_register(dev, base + SYSPLL1_OFFSET,
+				  clk_names[SYSPLL1_OUT], clk_names[SYSPLL1_OUTF]);
+	if (ret)
+		return ret;
+
+	/* normal clocks */
+	for (n = 0; n < ARRAY_SIZE(sl261x_descs); n++) {
+		const struct sl_clk_desc *cd = &sl261x_descs[n];
+
+		id = cd->id;
+		if (id == CLK_GE0_PTP_REF || id == CLK_GE1_PTP_REF) {
+			parent_names = ge_ptp_refclk_parent_names;
+			num_parents = ARRAY_SIZE(ge_ptp_refclk_parent_names);
+		} else {
+			parent_names = default_parent_names;
+			num_parents = ARRAY_SIZE(default_parent_names);
+		}
+		hws[id] = sl_clk_register(dev, base + cd->offset, cd->name,
+					  parent_names, num_parents, cd->flags, &lock);
+		if (IS_ERR(hws[id]))
+			return PTR_ERR(hws[id]);
+	}
+
+	/* gate clocks */
+	for (n = 0; n < ARRAY_SIZE(sl261x_gates); n++) {
+		const struct sl_gateclk_desc *gd = &sl261x_gates[n];
+
+		id = gd->id;
+		hws[id] = devm_clk_hw_register_gate(dev, gd->name, gd->parent_name,
+						    gd->flags, base + REG_CLKENABLE,
+						    gd->bit_idx, 0, &gate_lock);
+		if (IS_ERR(hws[id]))
+			return PTR_ERR(hws[id]);
+	}
+
+	return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
+}
+
+static int sl261x_cpu_clk_setup(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *parent_np = of_get_parent(np);
+	struct clk_hw_onecell_data *clk_data;
+	struct clk_hw **hws;
+	void __iomem *base;
+	int ret;
+
+	base = devm_of_iomap(dev, parent_np, 0, NULL);
+	of_node_put(parent_np);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, SL261X_CPU_CLK_MAX), GFP_KERNEL);
+	if (!clk_data)
+		return -ENOMEM;
+	clk_data->num = SL261X_CPU_CLK_MAX;
+	hws = clk_data->hws;
+
+	/* cpupll */
+	ret = sl261x_pll_register(dev, base + CPUPLL_OFFSET,
+				  clk_names[CPUPLL_OUT], clk_names[CPUPLL_OUTF]);
+	if (ret)
+		return ret;
+
+	/* TODO: support cpupll bypass */
+	hws[CLK_CPU0] =
+		devm_clk_hw_register_fixed_factor(dev, "cpu0", clk_names[CPUPLL_OUTF], 0, 1, 1);
+	if (IS_ERR(hws[CLK_CPU0]))
+		return PTR_ERR(hws[CLK_CPU0]);
+	hws[CLK_CPU1] =
+		devm_clk_hw_register_fixed_factor(dev, "cpu1", clk_names[CPUPLL_OUTF], 0, 1, 1);
+	if (IS_ERR(hws[CLK_CPU1]))
+		return PTR_ERR(hws[CLK_CPU1]);
+	hws[CLK_DSU] =
+		devm_clk_hw_register_fixed_factor(dev, "dsu", clk_names[CPUPLL_OUT], 0, 1, 1);
+	if (IS_ERR(hws[CLK_DSU]))
+		return PTR_ERR(hws[CLK_DSU]);
+
+	return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
+}
+
+static int sl261x_aio_clk_setup(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *parent_np = of_get_parent(np);
+	void __iomem *base;
+	int ret;
+
+	base = devm_of_iomap(dev, parent_np, 0, NULL);
+	of_node_put(parent_np);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	/* aplls */
+	ret = sl261x_pll_register(dev, base + APLL0_OFFSET,
+				  clk_names[APLL0_OUT], clk_names[APLL0_OUTF]);
+	if (ret)
+		return ret;
+	ret = sl261x_pll_register(dev, base + APLL1_OFFSET,
+				  clk_names[APLL1_OUT], clk_names[APLL1_OUTF]);
+	if (ret)
+		return ret;
+
+	/* TODO aio clks */
+
+	return 0;
+}
+
+static const struct sl_sysmgr_fixedclk_desc sl261x_sysmgr_fixeds[] = {
+	{ "clk_mcu_ref",	CLK_MCU_REF,	25000000 },
+	{ "clk_mcu_pll",	CLK_MCU_PLL,	400000000 },
+	{ "clk_mcu_io_pll",	CLK_MCU_IO_PLL,	300000000 },
+	{ "clk_mcu_apb",	CLK_MCU_APB,	100000000 },
+	{ "clk_mcu_axi",	CLK_MCU_AXI,	200000000 },
+};
+
+static const struct sl_sysmgr_gateclk_desc sl261x_sysmgr_gates[] = {
+	{ "clk_cfg_uart0",	"clk_mcu_apb",	CLK_CFG_UART0,	0 },
+	{ "clk_ser_uart0",	"clk_mcu_ref",	CLK_SER_UART0,	1 },
+	{ "clk_cfg_uart1",	"clk_mcu_apb",	CLK_CFG_UART1,	2 },
+	{ "clk_cfg_uart2",	"clk_mcu_apb",	CLK_CFG_UART2,	3 },
+	{ "clk_cfg_uart3",	"clk_mcu_apb",	CLK_CFG_UART3,	4 },
+	{ "clk_cfg_gpio",	"clk_mcu_apb",	CLK_CFG_GPIO,	5 },
+	{ "clk_cfg_i2cm0",	"clk_mcu_apb",	CLK_CFG_I2CM0,	6 },
+	{ "clk_cfg_i2cm1",	"clk_mcu_apb",	CLK_CFG_I2CM1,	7 },
+	{ "clk_cfg_spim",	"clk_mcu_apb",	CLK_CFG_SPIM,	8 },
+	{ "clk_cfg_spis",	"clk_mcu_apb",	CLK_CFG_SPIS,	9 },
+	{ "clk_cfg_i3c",	"clk_mcu_apb",	CLK_CFG_I3C,	10 },
+	{ "clk_cfg_xspi",	"clk_mcu_apb",	CLK_CFG_XSPI,	11 },
+	{ "clk_axi_xspi",	"clk_mcu_axi",	CLK_AXI_XSPI,	12 },
+	{ "clk_cfg_pvt",	"clk_mcu_apb",	CLK_CFG_PVT,	13 },
+	{ "clk_cfg_adc",	"clk_mcu_apb",	CLK_CFG_ADC,	14 },
+	{ "clk_cfg_pwm",	"clk_mcu_apb",	CLK_CFG_PWM,	15 },
+	{ "clk_bus_can0",	"clk_mcu_axi",	CLK_BUS_CAN0,	16 },
+	{ "clk_bus_can1",	"clk_mcu_axi",	CLK_BUS_CAN1,	17 },
+	{ "clk_cfg_pdm",	"clk_mcu_apb",	CLK_CFG_PDM,	18 },
+};
+
+static const struct sl_sysmgr_clk_desc sl261x_sysmgr_clks[] = {
+	{ "clk_ser_uart1",	"clk_mcu_pll",	CLK_SER_UART1,	0x604,	4 },
+	{ "clk_ser_uart2",	"clk_mcu_pll",	CLK_SER_UART2,	0x608,	4 },
+	{ "clk_ser_uart3",	"clk_mcu_pll",	CLK_SER_UART3,	0x60c,	4 },
+	{ "clk_deb_gpio",	"clk_mcu_pll",	CLK_DEB_GPIO,	0x610,	4 },
+	{ "clk_ser_i2cm0",	"clk_mcu_pll",	CLK_SER_I2CM0,	0x614,	4 },
+	{ "clk_ser_i2cm1",	"clk_mcu_pll",	CLK_SER_I2CM1,	0x618,	4 },
+	{ "clk_ser_spim",	"clk_mcu_pll",	CLK_SER_SPIM,	0x61c,	4 },
+	{ "clk_ser_spis",	"clk_mcu_pll",	CLK_SER_SPIS,	0x620,	4 },
+	{ "clk_ser_i3c",	"clk_mcu_pll",	CLK_SER_I3C,	0x624,	4 },
+	{ "clk_ser_xspi",	"clk_mcu_io_pll", CLK_SER_XSPI,	0x628,	4 },
+	{ "clk_hs_pvt",		"clk_mcu_pll",	CLK_HS_PVT,	0x62c,	4 },
+	{ "clk_core_adc",	"clk_mcu_pll",	CLK_CORE_ADC,	0x630,	8 },
+	{ "clk_ser_pwm",	"clk_mcu_pll",	CLK_SER_PWM,	0x634,	4 },
+	{ "clk_ser_can0",	"clk_mcu_pll",	CLK_SER_CAN0,	0x638,	4 },
+	{ "clk_ser_can1",	"clk_mcu_pll",	CLK_SER_CAN1,	0x63c,	4 },
+	{ "clk_ser_pdm",	"clk_mcu_pll",	CLK_SER_PDM,	0x640,	8 },
+	{ "clk_ref_calib",	"clk_mcu_pll",	CLK_REF_CALIB,	0x644,	8 },
+};
+
+static int sl261x_sysmgr_clk_setup(struct platform_device *pdev)
+{
+	return sl_sysmgr_clk_setup(pdev, sl261x_sysmgr_fixeds, ARRAY_SIZE(sl261x_sysmgr_fixeds),
+				   sl261x_sysmgr_gates, ARRAY_SIZE(sl261x_sysmgr_gates),
+				   CLK_GATE_CTRL0,
+				   sl261x_sysmgr_clks, ARRAY_SIZE(sl261x_sysmgr_clks));
+}
+
+static const struct of_device_id sl261x_clks_match_table[] = {
+	{
+		.compatible = "syna,sl261x-soc-clk",
+		.data = sl261x_soc_clk_setup,
+	},
+	{
+		.compatible = "syna,sl261x-cpu-clk",
+		.data = sl261x_cpu_clk_setup,
+	},
+	{
+		.compatible = "syna,sl261x-aio-clk",
+		.data = sl261x_aio_clk_setup,
+	},
+	{
+		.compatible = "syna,sl261x-system-clk",
+		.data = sl261x_sysmgr_clk_setup,
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, sl261x_clks_match_table);
+
+static int sl261x_clks_probe(struct platform_device *pdev)
+{
+	int (*clk_setup)(struct platform_device *pdev);
+	int ret;
+
+	clk_setup = of_device_get_match_data(&pdev->dev);
+	if (!clk_setup)
+		return -ENODEV;
+
+	ret = clk_setup(pdev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static struct platform_driver sl261x_clks_driver = {
+	.probe		= sl261x_clks_probe,
+	.driver		= {
+		.name	= "syna-sl261x-clks",
+		.of_match_table = sl261x_clks_match_table,
+	},
+};
+module_platform_driver(sl261x_clks_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>");
+MODULE_DESCRIPTION("Synaptics sl261x clks Driver");
diff --git a/drivers/clk/berlin/sl261x-pll.c b/drivers/clk/berlin/sl261x-pll.c
new file mode 100644
index 000000000000..ce19a6043da1
--- /dev/null
+++ b/drivers/clk/berlin/sl261x-pll.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2021 Synaptics Incorporated
+ *
+ * Author: Jisheng Zhang <jszhang@kernel.org>
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_clk.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include "sl261x-pll.h"
+
+#define FRAC_BITS		24
+
+#define CTRLC			0x08
+#define DIVR_MASK		GENMASK(8, 0)
+#define CTRLD			0x0c
+#define DIVF_MASK		GENMASK(8, 0)
+#define CTRLE			0x10
+#define DIVFF_MASK		GENMASK(23, 0)
+#define CTRLF			0x14
+#define DIVQ_MASK		GENMASK(4, 0)
+#define CTRLG			0x18
+#define DIVQF_MASK		GENMASK(2, 0)
+
+#define SL261X_APLL
+struct sl261x_pll {
+	void __iomem *ctrl;
+	struct clk_hw pllout;
+	struct clk_hw plloutf;
+};
+
+#define to_pllout(_hw) container_of(_hw, struct sl261x_pll, pllout)
+#define to_plloutf(_hw) container_of(_hw, struct sl261x_pll, plloutf)
+
+static unsigned long sl261x_pll_recalc_rate(struct sl261x_pll *pll,
+					    unsigned long parent_rate, bool fractional_output)
+{
+	u32 dm, dn, frac, dp;
+	u64 rate;
+
+	dm = (readl_relaxed(pll->ctrl + CTRLC) & DIVR_MASK) + 1;
+	dn = (readl_relaxed(pll->ctrl + CTRLD) & DIVF_MASK) + 1;
+	frac = readl_relaxed(pll->ctrl + CTRLE) & DIVFF_MASK;
+
+	if (fractional_output)
+		dp = (readl_relaxed(pll->ctrl + CTRLG) & DIVQF_MASK) + 1;
+	else
+		dp = ((readl_relaxed(pll->ctrl + CTRLF) & DIVQ_MASK) + 1) * 2;
+
+	rate = (u64)parent_rate * dn;
+	rate += ((u64)parent_rate * frac + BIT_ULL(FRAC_BITS - 1)) >> FRAC_BITS;
+	rate = DIV_ROUND_CLOSEST_ULL(rate * 4, dm * dp);
+
+	return rate;
+}
+
+static unsigned long sl261x_pllout_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	return sl261x_pll_recalc_rate(to_pllout(hw), parent_rate, false);
+}
+
+static unsigned long sl261x_plloutf_recalc_rate(struct clk_hw *hw,
+						unsigned long parent_rate)
+{
+	return sl261x_pll_recalc_rate(to_plloutf(hw), parent_rate, true);
+}
+
+static const struct clk_ops sl261x_pllout_ops = {
+	.recalc_rate = sl261x_pllout_recalc_rate,
+};
+
+static const struct clk_ops sl261x_plloutf_ops = {
+	.recalc_rate = sl261x_plloutf_recalc_rate,
+};
+
+static void sl261x_pll_put_clk(void *data)
+{
+	clk_put(data);
+}
+
+int sl261x_pll_register(struct device *dev, void __iomem *ctrl,
+			const char *out, const char *outf)
+{
+	struct device_node *np = dev->of_node;
+	struct clk_parent_data parent = {};
+	struct clk_init_data init = {};
+	struct sl261x_pll *pll;
+	struct clk *input;
+	int ret;
+
+	if (!dev || !ctrl || !out || !outf)
+		return -EINVAL;
+
+	input = of_clk_get(np, 0);
+	if (IS_ERR(input))
+		return dev_err_probe(dev, PTR_ERR(input),
+				     "failed to get PLL reference clock\n");
+	parent.hw = __clk_get_hw(input);
+	ret = devm_add_action_or_reset(dev, sl261x_pll_put_clk, input);
+	if (ret)
+		return ret;
+
+	pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
+	if (!pll)
+		return -ENOMEM;
+	pll->ctrl = ctrl;
+
+	init.name = out;
+	init.ops = &sl261x_pllout_ops;
+	init.parent_data = &parent;
+	init.num_parents = 1;
+	init.flags = CLK_GET_RATE_NOCACHE;
+	pll->pllout.init = &init;
+
+	ret = devm_clk_hw_register(dev, &pll->pllout);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to register pllout\n");
+
+	init.name = outf;
+	init.ops = &sl261x_plloutf_ops;
+	pll->plloutf.init = &init;
+
+	ret = devm_clk_hw_register(dev, &pll->plloutf);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to register plloutf\n");
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(sl261x_pll_register);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Synaptics SL261x PLL clock driver");
diff --git a/drivers/clk/berlin/sl261x-pll.h b/drivers/clk/berlin/sl261x-pll.h
new file mode 100644
index 000000000000..70788661ff60
--- /dev/null
+++ b/drivers/clk/berlin/sl261x-pll.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __SL261X_PLL_H
+#define __SL261X_PLL_H
+
+#include <linux/io.h>
+
+struct clk_hw_onecell_data;
+struct device;
+struct device_node;
+
+int sl261x_pll_register(struct device *dev, void __iomem *ctrl,
+			const char *out, const char *outf);
+
+#endif /* __SL261X_PLL_H */
-- 
2.53.0


  parent reply	other threads:[~2026-09-29  6:36 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-29  6:13 [PATCH 00/20] arm64: add Synaptics SL261X SoCs and RDK boards Jisheng Zhang
2026-09-29  6:13 ` [PATCH 01/20] dt-bindings: serial: snps-dw-apb-uart: Add Synaptics sl261x uart Jisheng Zhang
2026-09-29  6:13 ` [PATCH 02/20] dt-bindings: i2c: dw: Add Synaptics sl261x i2c Jisheng Zhang
2026-09-29 20:57   ` Andi Shyti
2026-09-29  6:14 ` [PATCH 03/20] spi: dt-bindings: snps,dw-apb-ssi: Add Synaptics sl261x spi Jisheng Zhang
2026-09-29  6:14 ` [PATCH 04/20] dt-bindings: i3c: dw: support up to two reset lines Jisheng Zhang
2026-09-29 19:40   ` Conor Dooley
2026-09-29  6:14 ` [PATCH 05/20] i3c: dw: switch to array-based exclusive reset control Jisheng Zhang
2026-09-29 15:01   ` Frank Li
2026-09-29  6:14 ` [PATCH 06/20] dt-bindings: i3c: Add Synaptics sl261x i3c Jisheng Zhang
2026-09-29  6:14 ` [PATCH 07/20] arm64: kconfig: let ARCH_BERLIN cover Synaptics arm64 SoCs Jisheng Zhang
2026-09-29  6:14 ` [PATCH 08/20] dt-bindings: reset: add Synaptics SL261X SoCs Jisheng Zhang
2026-09-29 19:44   ` Conor Dooley
2026-09-29  6:14 ` [PATCH 09/20] reset: add Synaptics SL261x reset support Jisheng Zhang
2026-09-29  6:14 ` [PATCH 10/20] pinctrl: berlin: use u16 instead of u8 for the offset Jisheng Zhang
2026-09-29  6:14 ` [PATCH 11/20] pinctrl: berlin: enable module build support Jisheng Zhang
2026-09-29  6:14 ` [PATCH 12/20] pinctrl: berlin: add optional pinconf support Jisheng Zhang
2026-09-29  6:14 ` [PATCH 13/20] dt-bindings: pinctrl: berlin: Support Synaptics SL261X SoCs Jisheng Zhang
2026-09-29  6:14 ` [PATCH 14/20] pinctrl: berlin: support " Jisheng Zhang
2026-09-29 15:34   ` Uwe Kleine-König
2026-09-29  6:14 ` [PATCH 15/20] dt-bindings: clock: add Synaptics SL261X clock Jisheng Zhang
2026-09-29 19:46   ` Conor Dooley
2026-09-29 20:49   ` Rob Herring (Arm)
2026-09-29  6:14 ` Jisheng Zhang [this message]
2026-09-29  6:14 ` [PATCH 17/20] dt-bindings: mfd: Add Synaptics SL261x global block binding Jisheng Zhang
2026-09-29 19:55   ` Conor Dooley
2026-09-29  6:14 ` [PATCH 18/20] regulator: dt-bindings: sy8827n: support standard properties Jisheng Zhang
2026-09-29 19:40   ` Conor Dooley
2026-09-29  6:14 ` [PATCH 19/20] dt-bindings: arm: berlin: Add Synaptics SL261X SoC and RDK board Jisheng Zhang
2026-09-29  6:14 ` [PATCH 20/20] arm64: dts: synaptics: " Jisheng Zhang
2026-09-29 19:38 ` [PATCH 00/20] arm64: add Synaptics SL261X SoCs and RDK boards Conor Dooley

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=20260929061417.20733-17-jszhang@kernel.org \
    --to=jszhang@kernel.org \
    --cc=Frank.Li@nxp.com \
    --cc=alexandre.belloni@bootlin.com \
    --cc=andi.shyti@kernel.org \
    --cc=bmasney+clk@redhat.com \
    --cc=broonie@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=jbrunet+clk@baylibre.com \
    --cc=jirislaby@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=lee@kernel.org \
    --cc=lgirdwood@gmail.com \
    --cc=linusw@kernel.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-clk@vger.kernel.org \
    --cc=linux-gpio@vger.kernel.org \
    --cc=linux-i2c@vger.kernel.org \
    --cc=linux-i3c@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-serial@vger.kernel.org \
    --cc=linux-spi@vger.kernel.org \
    --cc=mfd@lists.linux.dev \
    --cc=p.zabel@pengutronix.de \
    --cc=robh@kernel.org \
    --cc=sboyd@kernel.org \
    --cc=sebastian.hesselbarth@gmail.com \
    /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®