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
next prev 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®