[PATCH net-next v15 04/13] dpll: add basic SiTime SiT9531x support
From: Ali Rouhi
Date: Tue Nov 10 2026 - 16:49:13 EST
From: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@xxxxxxxxxxxxxxxxx>
Add the I2C probe for the SiTime SiT9531x family: four PLLs, up to eight
inputs and up to twelve outputs, configured from efuse or from a blob
applied before the driver binds. Later patches register the PLLs with
the DPLL subsystem.
The register space is 32 pages of 256 registers, selected through offset
0xFF of any page. The regmap is the flat 256-byte window, and the
accessors select the page under a lock.
Probe takes the optional reset line and the XO rate, waits the boot time
with reset deasserted so the configuration the part holds is kept, and
identifies the variant from the ID register.
Suggested-by: Ivan Vecera <ivecera@xxxxxxxxxx>
Signed-off-by: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@xxxxxxxxxxxxxxxxx>
Signed-off-by: Ali Rouhi <arouhi@xxxxxxxxxx>
---
MAINTAINERS | 1 +
drivers/dpll/Kconfig | 2 +
drivers/dpll/Makefile | 1 +
drivers/dpll/sit9531x/Kconfig | 13 ++
drivers/dpll/sit9531x/Makefile | 4 +
drivers/dpll/sit9531x/core.c | 308 +++++++++++++++++++++++++++++++++
drivers/dpll/sit9531x/core.h | 88 ++++++++++
drivers/dpll/sit9531x/regs.h | 36 ++++
8 files changed, 453 insertions(+)
create mode 100644 drivers/dpll/sit9531x/Kconfig
create mode 100644 drivers/dpll/sit9531x/Makefile
create mode 100644 drivers/dpll/sit9531x/core.c
create mode 100644 drivers/dpll/sit9531x/core.h
create mode 100644 drivers/dpll/sit9531x/regs.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 6c831bf9e812..936996cef03a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25875,6 +25875,7 @@ M: Ali Rouhi <arouhi@xxxxxxxxxx>
L: netdev@xxxxxxxxxxxxxxx
S: Maintained
F: Documentation/devicetree/bindings/dpll/sitime,sit95316.yaml
+F: drivers/dpll/sit9531x/
SL28 CPLD MFD DRIVER
M: Michael Walle <mwalle@xxxxxxxxxx>
diff --git a/drivers/dpll/Kconfig b/drivers/dpll/Kconfig
index be98969f040a..f8f7ca121b93 100644
--- a/drivers/dpll/Kconfig
+++ b/drivers/dpll/Kconfig
@@ -23,6 +23,8 @@ config DPLL_REFCNT_TRACKER
If unsure, say N.
+source "drivers/dpll/sit9531x/Kconfig"
+
source "drivers/dpll/zl3073x/Kconfig"
endmenu
diff --git a/drivers/dpll/Makefile b/drivers/dpll/Makefile
index 9e7a3a3e592e..4adc50d748d4 100644
--- a/drivers/dpll/Makefile
+++ b/drivers/dpll/Makefile
@@ -8,4 +8,5 @@ dpll-y += dpll_core.o
dpll-y += dpll_netlink.o
dpll-y += dpll_nl.o
+obj-$(CONFIG_SIT9531X_DPLL) += sit9531x/
obj-$(CONFIG_ZL3073X) += zl3073x/
diff --git a/drivers/dpll/sit9531x/Kconfig b/drivers/dpll/sit9531x/Kconfig
new file mode 100644
index 000000000000..ac5b7f31a034
--- /dev/null
+++ b/drivers/dpll/sit9531x/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config SIT9531X_DPLL
+ tristate "SiTime SiT9531x DPLL driver"
+ depends on I2C && NET
+ select DPLL
+ select REGMAP_I2C
+ help
+ Driver for SiTime SiT9531x family clock generators
+ (SiT95317, SiT95316).
+
+ To compile this driver as a module, choose M here: the
+ module will be called sit9531x.
diff --git a/drivers/dpll/sit9531x/Makefile b/drivers/dpll/sit9531x/Makefile
new file mode 100644
index 000000000000..a221fe55386a
--- /dev/null
+++ b/drivers/dpll/sit9531x/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_SIT9531X_DPLL) += sit9531x.o
+sit9531x-y := core.o
diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c
new file mode 100644
index 000000000000..3ec1ab5aa56c
--- /dev/null
+++ b/drivers/dpll/sit9531x/core.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SiTime SiT9531x DPLL core driver
+ *
+ * Copyright (C) 2026 SiTime Corp.
+ * Author: Ali Rouhi <arouhi@xxxxxxxxxx>
+ * Author: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@xxxxxxxxxxxxxxxxx>
+ *
+ * I2C probe, paged regmap configuration and register access helpers.
+ */
+
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include "core.h"
+#include "regs.h"
+
+#define SIT9531X_CHIP(_id, _nin, _nout, _name, _map) \
+ { .id = (_id), .num_inputs = (_nin), .num_outputs = (_nout), \
+ .name = (_name), .clkout_map = (_map) }
+
+/* Per-variant output index -> physical slot mapping */
+static const u8 clkout_map_95317[] = {0, 3, 4, 5, 7, 8, 9, 11};
+static const u8 clkout_map_95316[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+
+static const struct sit9531x_chip_info sit9531x_chip_ids[] = {
+ SIT9531X_CHIP(SIT9531X_VARIANT_ID_95317, 8, 8, "SiT95317",
+ clkout_map_95317),
+ SIT9531X_CHIP(SIT9531X_VARIANT_ID_95316, 8, 12, "SiT95316",
+ clkout_map_95316),
+};
+
+/*
+ * One 256-register page is visible at a time, selected by offset 0xFF on every
+ * page. The map covers that window, uncached; the accessors select the page.
+ */
+static const struct regmap_config sit9531x_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = SIT9531X_PAGE_SEL,
+ .cache_type = REGCACHE_NONE,
+};
+
+/* Caller holds page_lock. */
+static int sit9531x_page_select(struct sit9531x_dev *sitdev, int page)
+{
+ int rc;
+
+ if (sitdev->page == page)
+ return 0;
+
+ sitdev->page = -1;
+ rc = regmap_write(sitdev->regmap, SIT9531X_PAGE_SEL, page);
+ if (rc)
+ return rc;
+ sitdev->page = page;
+
+ return 0;
+}
+
+/* Caller holds page_lock. */
+static int sit9531x_read_locked(struct sit9531x_dev *sitdev, unsigned int reg,
+ unsigned int *val)
+{
+ int rc;
+
+ rc = sit9531x_page_select(sitdev, SIT9531X_REG_PAGE(reg));
+ if (rc)
+ return rc;
+ rc = regmap_read(sitdev->regmap, SIT9531X_REG_OFFSET(reg), val);
+ if (rc)
+ sitdev->page = -1;
+
+ return rc;
+}
+
+/* Caller holds page_lock. */
+static int sit9531x_write_locked(struct sit9531x_dev *sitdev,
+ unsigned int reg, u8 val)
+{
+ int rc;
+
+ rc = sit9531x_page_select(sitdev, SIT9531X_REG_PAGE(reg));
+ if (rc)
+ return rc;
+ rc = regmap_write(sitdev->regmap, SIT9531X_REG_OFFSET(reg), val);
+ if (rc)
+ sitdev->page = -1;
+ else if (SIT9531X_REG_OFFSET(reg) == SIT9531X_PAGE_SEL)
+ sitdev->page = val;
+
+ return rc;
+}
+
+/* @reg is a SIT9531X_REG(page, offset) value. */
+int sit9531x_read_u8(struct sit9531x_dev *sitdev, unsigned int reg,
+ u8 *val)
+{
+ unsigned int tmp;
+ int rc;
+
+ mutex_lock(&sitdev->page_lock);
+ rc = sit9531x_read_locked(sitdev, reg, &tmp);
+ mutex_unlock(&sitdev->page_lock);
+ if (rc) {
+ dev_err_ratelimited(sitdev->dev,
+ "Failed to read page 0x%02x reg 0x%02x: %d\n",
+ SIT9531X_REG_PAGE(reg),
+ SIT9531X_REG_OFFSET(reg), rc);
+ return rc;
+ }
+ *val = (u8)tmp;
+
+ return 0;
+}
+
+/* @reg is a SIT9531X_REG(page, offset) value. */
+int sit9531x_write_u8(struct sit9531x_dev *sitdev, unsigned int reg,
+ u8 val)
+{
+ int rc;
+
+ mutex_lock(&sitdev->page_lock);
+ rc = sit9531x_write_locked(sitdev, reg, val);
+ mutex_unlock(&sitdev->page_lock);
+ if (rc)
+ dev_err_ratelimited(sitdev->dev,
+ "Failed to write page 0x%02x reg 0x%02x: %d\n",
+ SIT9531X_REG_PAGE(reg),
+ SIT9531X_REG_OFFSET(reg), rc);
+
+ return rc;
+}
+
+int sit9531x_read_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 offset, u8 *val)
+{
+ u8 page = sit9531x_pll_page(pll_idx);
+
+ return sit9531x_read_u8(sitdev, SIT9531X_REG(page, offset), val);
+}
+
+int sit9531x_write_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 offset, u8 val)
+{
+ u8 page = sit9531x_pll_page(pll_idx);
+
+ return sit9531x_write_u8(sitdev, SIT9531X_REG(page, offset), val);
+}
+
+int sit9531x_update_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 offset, u8 mask, u8 val)
+{
+ u8 page = sit9531x_pll_page(pll_idx);
+ unsigned int reg = SIT9531X_REG(page, offset);
+ unsigned int orig, tmp;
+ int rc;
+
+ mutex_lock(&sitdev->page_lock);
+ rc = sit9531x_read_locked(sitdev, reg, &orig);
+ if (!rc) {
+ tmp = (orig & ~mask) | (val & mask);
+ if (tmp != orig)
+ rc = sit9531x_write_locked(sitdev, reg, tmp);
+ }
+ mutex_unlock(&sitdev->page_lock);
+ if (rc)
+ dev_err_ratelimited(sitdev->dev,
+ "Failed to update page 0x%02x reg 0x%02x: %d\n",
+ page, offset, rc);
+
+ return rc;
+}
+
+static int sit9531x_read_variant_id(struct sit9531x_dev *sitdev, u8 *id)
+{
+ return sit9531x_read_u8(sitdev, SIT9531X_REG_VARIANT_ID, id);
+}
+
+static const struct sit9531x_chip_info *sit9531x_match_variant(u8 id)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(sit9531x_chip_ids); i++) {
+ if (sit9531x_chip_ids[i].id == id)
+ return &sit9531x_chip_ids[i];
+ }
+
+ return NULL;
+}
+
+static int sit9531x_dev_probe(struct sit9531x_dev *sitdev)
+{
+ struct clk *xtal_clk;
+ u8 variant_id;
+ int rc;
+
+ /*
+ * Fvco = Fref * (DIVN + frac/2^32); Fref is the rate of the XO feeding
+ * XIN/XO_CLK.
+ */
+ xtal_clk = devm_clk_get_optional_enabled(sitdev->dev, "xtal");
+ if (IS_ERR(xtal_clk))
+ return dev_err_probe(sitdev->dev, PTR_ERR(xtal_clk),
+ "Failed to get xtal clock\n");
+ sitdev->xtal_freq = clk_get_rate(xtal_clk);
+ if (!sitdev->xtal_freq) {
+ u32 freq;
+
+ if (!device_property_read_u32(sitdev->dev, "clock-frequency",
+ &freq))
+ sitdev->xtal_freq = freq;
+ }
+ if (!sitdev->xtal_freq)
+ return dev_err_probe(sitdev->dev, -EINVAL,
+ "no xtal rate: provide clocks=<&xo> + clock-names=\"xtal\", or a clock-frequency property\n");
+
+ /*
+ * Keep reset deasserted, never pulse it: a reset discards the
+ * configuration from efuse or a pre-probe NVM blob. Release it before
+ * the first I2C access.
+ */
+ sitdev->reset_gpio = devm_gpiod_get_optional(sitdev->dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(sitdev->reset_gpio))
+ return dev_err_probe(sitdev->dev, PTR_ERR(sitdev->reset_gpio),
+ "Failed to request reset gpio\n");
+ if (sitdev->reset_gpio)
+ fsleep(10000); /* internal boot after release */
+
+ rc = sit9531x_read_variant_id(sitdev, &variant_id);
+ if (rc)
+ return rc;
+
+ sitdev->info = sit9531x_match_variant(variant_id);
+ if (!sitdev->info)
+ return dev_err_probe(sitdev->dev, -ENODEV,
+ "Unknown variant ID: 0x%02x\n",
+ variant_id);
+
+ rc = devm_mutex_init(sitdev->dev, &sitdev->multiop_lock);
+ if (rc)
+ return dev_err_probe(sitdev->dev, rc,
+ "Failed to initialize mutex\n");
+
+ dev_info(sitdev->dev, "%s detected, %u inputs, %u outputs\n",
+ sitdev->info->name, sitdev->info->num_inputs,
+ sitdev->info->num_outputs);
+
+ return 0;
+}
+
+static int sit9531x_i2c_probe(struct i2c_client *client)
+{
+ struct sit9531x_dev *sitdev;
+ struct regmap *regmap;
+ int rc;
+
+ regmap = devm_regmap_init_i2c(client, &sit9531x_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(&client->dev, PTR_ERR(regmap),
+ "Failed to initialize regmap\n");
+
+ sitdev = devm_kzalloc(&client->dev, sizeof(*sitdev), GFP_KERNEL);
+ if (!sitdev)
+ return -ENOMEM;
+
+ sitdev->dev = &client->dev;
+ sitdev->client = client;
+ sitdev->regmap = regmap;
+ sitdev->page = -1;
+ rc = devm_mutex_init(&client->dev, &sitdev->page_lock);
+ if (rc)
+ return rc;
+ i2c_set_clientdata(client, sitdev);
+
+ return sit9531x_dev_probe(sitdev);
+}
+
+static const struct of_device_id sit9531x_of_match[] = {
+ { .compatible = "sitime,sit95316" },
+ { .compatible = "sitime,sit95317" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sit9531x_of_match);
+
+static struct i2c_driver sit9531x_i2c_driver = {
+ .driver = {
+ .name = "sit9531x",
+ .of_match_table = sit9531x_of_match,
+ },
+ .probe = sit9531x_i2c_probe,
+};
+module_i2c_driver(sit9531x_i2c_driver);
+
+MODULE_AUTHOR("Ali Rouhi <arouhi@xxxxxxxxxx>");
+MODULE_AUTHOR("Oleg Zadorozhnyi <Oleg.Zadorozhnyi@xxxxxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("SiTime SiT9531x DPLL subsystem driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/dpll/sit9531x/core.h b/drivers/dpll/sit9531x/core.h
new file mode 100644
index 000000000000..0f826313eff8
--- /dev/null
+++ b/drivers/dpll/sit9531x/core.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SiTime SiT9531x DPLL core driver
+ *
+ * Copyright (C) 2026 SiTime Corp.
+ * Author: Ali Rouhi <arouhi@xxxxxxxxxx>
+ * Author: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@xxxxxxxxxxxxxxxxx>
+ *
+ * Device structure, register access helpers, and core function
+ * declarations.
+ */
+
+#ifndef _SIT9531X_CORE_H
+#define _SIT9531X_CORE_H
+
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include "regs.h"
+
+#define SIT9531X_NUM_PLLS 4
+#define SIT9531X_MAX_INPUTS 8
+#define SIT9531X_MAX_OUTPUTS 12
+
+/*
+ * struct sit9531x_chip_info - chip variant identification
+ * @id: variant ID byte read from the device
+ * @num_inputs: number of input clock pins
+ * @num_outputs: number of output clock pins
+ * @name: variant name
+ * @clkout_map: physical slot of each output index
+ */
+struct sit9531x_chip_info {
+ u8 id;
+ u8 num_inputs;
+ u8 num_outputs;
+ const char *name;
+ const u8 *clkout_map;
+};
+
+/*
+ * struct sit9531x_dev - SiT9531x device instance
+ * @dev: parent device
+ * @client: I2C client backing @regmap
+ * @regmap: the 256-byte register window
+ * @info: chip variant info
+ * @multiop_lock: serializes multi-register operations
+ * @page_lock: held across a page select and its transfer; nests inside
+ * @multiop_lock
+ * @page: current page, -1 when unknown
+ * @xtal_freq: crystal frequency in Hz
+ * @reset_gpio: optional reset line, NULL if absent
+ */
+struct sit9531x_dev {
+ struct device *dev;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ const struct sit9531x_chip_info *info;
+ /* Serializes multi-step register sequences */
+ struct mutex multiop_lock;
+ /* Serializes a page select and the transfer it is for */
+ struct mutex page_lock;
+ int page;
+
+ u32 xtal_freq;
+
+ struct gpio_desc *reset_gpio;
+};
+
+/* Register page of PLL @pll_idx (0 = PLLA, 3 = PLLD). */
+static inline u8 sit9531x_pll_page(u8 pll_idx)
+{
+ return SIT9531X_PAGE_PLLA + pll_idx;
+}
+
+int sit9531x_read_u8(struct sit9531x_dev *sitdev, unsigned int reg, u8 *val);
+int sit9531x_write_u8(struct sit9531x_dev *sitdev, unsigned int reg, u8 val);
+int sit9531x_read_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx, u8 offset,
+ u8 *val);
+int sit9531x_write_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx, u8 offset,
+ u8 val);
+int sit9531x_update_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx, u8 offset,
+ u8 mask, u8 val);
+
+#endif /* _SIT9531X_CORE_H */
diff --git a/drivers/dpll/sit9531x/regs.h b/drivers/dpll/sit9531x/regs.h
new file mode 100644
index 000000000000..3a6151f44d61
--- /dev/null
+++ b/drivers/dpll/sit9531x/regs.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SiTime SiT9531x register definitions
+ *
+ * Copyright (C) 2026 SiTime Corp.
+ * Author: Ali Rouhi <arouhi@xxxxxxxxxx>
+ * Author: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@xxxxxxxxxxxxxxxxx>
+ */
+
+#ifndef _SIT9531X_REGS_H
+#define _SIT9531X_REGS_H
+
+/*
+ * The page select is at offset 0xFF in every page; some pages are paired, e.g.
+ * 0x0A/0x1A for PLLA.
+ */
+#define SIT9531X_PAGE_SEL 0xFF
+#define SIT9531X_PAGE_SIZE 0x100
+#define SIT9531X_NUM_PAGES 32
+
+#define SIT9531X_REG(_page, _offset) (((_page) << 8) | (_offset))
+#define SIT9531X_REG_PAGE(_reg) ((_reg) >> 8)
+#define SIT9531X_REG_OFFSET(_reg) ((_reg) & 0xFF)
+
+#define SIT9531X_PAGE_OUTSYS0 0x03
+#define SIT9531X_PAGE_OUTSYS1 0x04
+#define SIT9531X_PAGE_PLLA 0x0A
+#define SIT9531X_PAGE_PLLA_EXT 0x1A
+
+/* Reg 0x03 holds an unrelated revision byte, not part of the variant ID */
+#define SIT9531X_REG_VARIANT_ID SIT9531X_REG(0x00, 0x02)
+
+#define SIT9531X_VARIANT_ID_95317 0x17
+#define SIT9531X_VARIANT_ID_95316 0x31
+
+#endif /* _SIT9531X_REGS_H */
--
2.39.2 (Apple Git-143)