| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/12 19:57 | flow | patch-triage |
8mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"KMSANReasoning": "",
"NeedsKMSAN": false,
"Reasoning": "The driver requires a mandatory 'xtal' clock via devm_clk_get_enabled() during probe. In generic virtualized environments (QEMU/GCE), dynamically instantiating this I2C device via sysfs will lack the necessary device tree node or clkdev lookup to satisfy this clock requirement. The probe will always fail with -ENOENT, making the rest of the driver's code paths unreachable for fuzzing.",
"WorthFuzzing": false
} |
| 1/1 | 2026/08/12 19:57 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 6d074aed00584446dfd11f27a835d4654aa9eaea\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Aug 12 19:57:20 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/devicetree/bindings/dpll/sitime,sit95316.yaml b/Documentation/devicetree/bindings/dpll/sitime,sit95316.yaml\nnew file mode 100644\nindex 0000000000000..591a8c0d7bbf2\n--- /dev/null\n+++ b/Documentation/devicetree/bindings/dpll/sitime,sit95316.yaml\n@@ -0,0 +1,171 @@\n+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)\n+%YAML 1.2\n+---\n+$id: http://devicetree.org/schemas/dpll/sitime,sit95316.yaml#\n+$schema: http://devicetree.org/meta-schemas/core.yaml#\n+\n+title: SiTime SiT95316/SiT95317 DPLL Clock Generator\n+\n+maintainers:\n+ - Ali Rouhi \u003carouhi@sitime.com\u003e\n+\n+description: |\n+ SiTime SiT95316 and SiT95317 are I2C-controlled programmable clock\n+ generators with integrated DPLL for synchronization applications. Both\n+ variants contain four PLLs with automatic/manual reference selection,\n+ DCO frequency adjustment, and phase offset measurement via an on-chip\n+ TDC (Time-to-Digital Converter).\n+\n+ Both parts have 4 differential input pairs whose lanes can also be\n+ driven independently as single-ended references, so 8 inputs are\n+ individually selectable. SiT95317 drives 8 outputs, SiT95316\n+ drives 12.\n+\n+properties:\n+ compatible:\n+ enum:\n+ - sitime,sit95316\n+ - sitime,sit95317\n+\n+ reg:\n+ maxItems: 1\n+\n+ clocks:\n+ maxItems: 1\n+\n+ clock-names:\n+ items:\n+ - const: xtal\n+\n+ reset-gpios:\n+ maxItems: 1\n+ description:\n+ GPIO connected to the chip's active-low reset pin (RESETB).\n+\n+ interrupts:\n+ maxItems: 1\n+ description:\n+ Interrupt from the chip's active-low INTRB output. Asserted when\n+ the device detects a status change such as lock acquisition or loss.\n+\n+ sitime,pll-fvco:\n+ $ref: /schemas/types.yaml#/definitions/uint64-array\n+ minItems: 4\n+ maxItems: 4\n+ description:\n+ Per-PLL VCO frequency in Hz for PLLA, PLLB, PLLC, PLLD. Neither\n+ standard frequency suffix fits. The VCO bands are 4915.2 MHz to\n+ 5898.24 MHz and 6875 MHz to 7812.5 MHz, so every valid value\n+ exceeds the 32 bits \"-hz\" allows, and the band edges are not\n+ whole megahertz either, so \"-mhz\" cannot express them. Hence a\n+ plain uint64-array. Override where the standard\n+ Fvco = Fref * DIVN derivation does not match the running VCO --\n+ for example a PLL operating in INTSYNC mode. A value of 0 keeps\n+ the register-derived computation for that PLL.\n+\n+ sitime,output-pll-map:\n+ $ref: /schemas/types.yaml#/definitions/uint32-array\n+ minItems: 8\n+ maxItems: 12\n+ items:\n+ enum: [0, 1, 2, 3, 255]\n+ description:\n+ Source PLL index (0=PLLA .. 3=PLLD) for each output 0..11. The\n+ value 255 (0xff) marks an output as unmapped and prevents the\n+ driver from registering it as a DPLL pin. Override for\n+ configurations where the chip's per-PLL OUTPUT_ENABLE bitmaps do\n+ not unambiguously describe output-to-PLL routing.\n+\n+required:\n+ - compatible\n+ - reg\n+ - clocks\n+ - clock-names\n+\n+allOf:\n+ - $ref: /schemas/dpll/dpll-device.yaml#\n+ # SiT95317 exposes 8 outputs, SiT95316 exposes 12. Bound the\n+ # output-pll-map length to the variant so a SiT95317 node cannot\n+ # describe more outputs than the part has.\n+ - if:\n+ properties:\n+ compatible:\n+ contains:\n+ const: sitime,sit95317\n+ then:\n+ properties:\n+ sitime,output-pll-map:\n+ maxItems: 8\n+ # SiT95316 has 12 outputs. When the map is supplied it must describe\n+ # all of them, otherwise the trailing outputs are left ambiguous.\n+ - if:\n+ properties:\n+ compatible:\n+ contains:\n+ const: sitime,sit95316\n+ then:\n+ properties:\n+ sitime,output-pll-map:\n+ minItems: 12\n+\n+unevaluatedProperties: false\n+\n+examples:\n+ - |\n+ #include \u003cdt-bindings/gpio/gpio.h\u003e\n+ #include \u003cdt-bindings/interrupt-controller/irq.h\u003e\n+\n+ i2c {\n+ #address-cells = \u003c1\u003e;\n+ #size-cells = \u003c0\u003e;\n+\n+ dpll@68 {\n+ compatible = \"sitime,sit95316\";\n+ reg = \u003c0x68\u003e;\n+ clocks = \u003c\u0026xo2\u003e;\n+ clock-names = \"xtal\";\n+ reset-gpios = \u003c\u0026gpio 78 GPIO_ACTIVE_LOW\u003e;\n+ interrupts = \u003c12 IRQ_TYPE_LEVEL_LOW\u003e;\n+ dpll-types = \"eec\", \"eec\", \"eec\", \"eec\";\n+ sitime,pll-fvco = /bits/ 64 \u003c6251500000 0 0 0\u003e;\n+ sitime,output-pll-map = \u003c0 0 0 0 0 0 0 0 0 0 0 0\u003e;\n+\n+ input-pins {\n+ #address-cells = \u003c1\u003e;\n+ #size-cells = \u003c0\u003e;\n+\n+ pin@0 {\n+ reg = \u003c0\u003e;\n+ label = \"clkin0\";\n+ connection-type = \"ext\";\n+ supported-frequencies-hz = /bits/ 64 \u003c10000000\u003e;\n+ };\n+\n+ pin@1 {\n+ reg = \u003c1\u003e;\n+ label = \"clkin1\";\n+ connection-type = \"synce\";\n+ supported-frequencies-hz = /bits/ 64 \u003c156250000\u003e;\n+ };\n+ };\n+\n+ output-pins {\n+ #address-cells = \u003c1\u003e;\n+ #size-cells = \u003c0\u003e;\n+\n+ pin@0 {\n+ reg = \u003c0\u003e;\n+ label = \"clkout0\";\n+ esync-control;\n+ supported-frequencies-hz = /bits/ 64 \u003c156250000\u003e;\n+ };\n+\n+ pin@1 {\n+ reg = \u003c1\u003e;\n+ label = \"clkout1\";\n+ supported-frequencies-hz = /bits/ 64 \u003c25000000\u003e;\n+ };\n+ };\n+ };\n+ };\n+...\ndiff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml\nindex 396044f368e7c..1921718a5f82c 100644\n--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml\n+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml\n@@ -1535,6 +1535,8 @@ patternProperties:\n description: SiRF Technology, Inc.\n \"^sis,.*\":\n description: Silicon Integrated Systems Corp.\n+ \"^sitime,.*\":\n+ description: SiTime Corporation\n \"^sitronix,.*\":\n description: Sitronix Technology Corporation\n \"^skov,.*\":\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex a82b6ec9c5674..8dfab3ac6a17f 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -24905,6 +24905,13 @@ S:\tMaintained\n W:\thttp://www.winischhofer.at/linuxsisusbvga.shtml\n F:\tdrivers/usb/misc/sisusbvga/\n \n+SITIME SIT9531X DPLL DRIVER\n+M:\tAli Rouhi \u003carouhi@sitime.com\u003e\n+L:\tnetdev@vger.kernel.org\n+S:\tMaintained\n+F:\tDocumentation/devicetree/bindings/dpll/sitime,sit95316.yaml\n+F:\tdrivers/dpll/sit9531x/\n+\n SL28 CPLD MFD DRIVER\n M:\tMichael Walle \u003cmwalle@kernel.org\u003e\n S:\tMaintained\ndiff --git a/drivers/dpll/Kconfig b/drivers/dpll/Kconfig\nindex be98969f040ab..ea77b9c11ab1d 100644\n--- a/drivers/dpll/Kconfig\n+++ b/drivers/dpll/Kconfig\n@@ -23,6 +23,7 @@ config DPLL_REFCNT_TRACKER\n \n \t If unsure, say N.\n \n+source \"drivers/dpll/sit9531x/Kconfig\"\n source \"drivers/dpll/zl3073x/Kconfig\"\n \n endmenu\ndiff --git a/drivers/dpll/Makefile b/drivers/dpll/Makefile\nindex 9e7a3a3e592e8..4adc50d748d4d 100644\n--- a/drivers/dpll/Makefile\n+++ b/drivers/dpll/Makefile\n@@ -8,4 +8,5 @@ dpll-y += dpll_core.o\n dpll-y += dpll_netlink.o\n dpll-y += dpll_nl.o\n \n+obj-$(CONFIG_SIT9531X_DPLL)\t+= sit9531x/\n obj-$(CONFIG_ZL3073X)\t+= zl3073x/\ndiff --git a/drivers/dpll/sit9531x/Kconfig b/drivers/dpll/sit9531x/Kconfig\nnew file mode 100644\nindex 0000000000000..47aea8674327c\n--- /dev/null\n+++ b/drivers/dpll/sit9531x/Kconfig\n@@ -0,0 +1,17 @@\n+# SPDX-License-Identifier: GPL-2.0-only\n+\n+config SIT9531X_DPLL\n+\ttristate \"SiTime SiT9531x DPLL driver\"\n+\tdepends on I2C \u0026\u0026 NET\n+\tselect DPLL\n+\tselect REGMAP_I2C\n+\thelp\n+\t Driver for SiTime SiT9531x family clock generators\n+\t (SiT95317, SiT95316).\n+\n+\t This driver registers each on-chip PLL as a DPLL device\n+\t and exposes input/output clocks as DPLL pins, providing\n+\t runtime configuration via Generic Netlink.\n+\n+\t To compile this driver as a module, choose M here: the\n+\t module will be called sit9531x.\ndiff --git a/drivers/dpll/sit9531x/Makefile b/drivers/dpll/sit9531x/Makefile\nnew file mode 100644\nindex 0000000000000..b97d2656a4603\n--- /dev/null\n+++ b/drivers/dpll/sit9531x/Makefile\n@@ -0,0 +1,4 @@\n+# SPDX-License-Identifier: GPL-2.0-only\n+\n+obj-$(CONFIG_SIT9531X_DPLL) += sit9531x.o\n+sit9531x-y := core.o dpll.o prop.o\ndiff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c\nnew file mode 100644\nindex 0000000000000..711ab16b0897a\n--- /dev/null\n+++ b/drivers/dpll/sit9531x/core.c\n@@ -0,0 +1,3120 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * SiTime SiT9531x DPLL core driver\n+ *\n+ * Copyright (C) 2026 SiTime Corp.\n+ * Author: Ali Rouhi \u003carouhi@sitime.com\u003e\n+ * Author: Oleg Zadorozhnyi \u003cOleg.Zadorozhnyi@devoxsoftware.com\u003e\n+ *\n+ * Core I2C probe, regmap configuration, hardware state management,\n+ * and periodic work thread.\n+ */\n+\n+#include \u003clinux/bitfield.h\u003e\n+#include \u003clinux/bits.h\u003e\n+#include \u003clinux/clk.h\u003e\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/dev_printk.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/gpio/consumer.h\u003e\n+#include \u003clinux/i2c.h\u003e\n+#include \u003clinux/interrupt.h\u003e\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/kthread.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/property.h\u003e\n+#include \u003clinux/regmap.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/string.h\u003e\n+\n+#include \"core.h\"\n+#include \"dpll.h\"\n+#include \"prop.h\"\n+#include \"regs.h\"\n+\n+/* Number of input + output pin positions for pin index allocation */\n+#define SIT9531X_NUM_INPUT_PINS\t\t(SIT9531X_MAX_INPUTS + 2) /* +xtal +INTSYNC */\n+#define SIT9531X_NUM_OUTPUT_PINS\t(SIT9531X_MAX_OUTPUTS + 1) /* +INTSYNC src */\n+#define SIT9531X_NUM_PINS_TOTAL\t\t(SIT9531X_NUM_INPUT_PINS + SIT9531X_NUM_OUTPUT_PINS)\n+\n+#define SIT9531X_CHIP(_id, _nin, _nout, _name, _map) \\\n+\t{ .id = (_id), .num_inputs = (_nin), .num_outputs = (_nout), \\\n+\t .name = (_name), .clkout_map = (_map) }\n+\n+/* Per-variant output index -\u003e physical slot mapping */\n+static const u8 clkout_map_95317[] = {0, 3, 4, 5, 7, 8, 9, 11};\n+static const u8 clkout_map_95316[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};\n+\n+static const struct sit9531x_chip_info sit9531x_chip_ids[] = {\n+\tSIT9531X_CHIP(SIT9531X_VARIANT_ID_95317, 8, 8, \"SiT95317\", clkout_map_95317),\n+\tSIT9531X_CHIP(SIT9531X_VARIANT_ID_95316, 8, 12, \"SiT95316\", clkout_map_95316),\n+};\n+\n+#define SIT9531X_RANGE_OFFSET\tSIT9531X_PAGE_SIZE\n+\n+static const struct regmap_range_cfg sit9531x_regmap_range = {\n+\t.range_min\t= SIT9531X_RANGE_OFFSET,\n+\t.range_max\t= SIT9531X_RANGE_OFFSET +\n+\t\t\t (SIT9531X_NUM_PAGES * SIT9531X_PAGE_SIZE) - 1,\n+\t.selector_reg\t= SIT9531X_PAGE_SEL,\n+\t.selector_mask\t= GENMASK(7, 0),\n+\t.selector_shift\t= 0,\n+\t.window_start\t= 0,\n+\t.window_len\t= SIT9531X_PAGE_SIZE,\n+};\n+\n+const struct regmap_config sit9531x_regmap_config = {\n+\t.reg_bits\t= 8,\n+\t.val_bits\t= 8,\n+\t.max_register\t= SIT9531X_RANGE_OFFSET +\n+\t\t\t (SIT9531X_NUM_PAGES * SIT9531X_PAGE_SIZE) - 1,\n+\t.ranges\t\t= \u0026sit9531x_regmap_range,\n+\t.num_ranges\t= 1,\n+\t.cache_type\t= REGCACHE_NONE,\n+};\n+\n+/*\n+ * sit9531x_read_u8 - read an 8-bit register\n+ * @reg:\tregister in SIT9531X_REG(page, offset) form\n+ * @val:\toutput value\n+ */\n+int sit9531x_read_u8(struct sit9531x_dev *sitdev, unsigned int reg,\n+\t\t u8 *val)\n+{\n+\tunsigned int tmp;\n+\tint rc;\n+\n+\treg = (SIT9531X_REG_PAGE(reg) * SIT9531X_PAGE_SIZE) +\n+\t SIT9531X_REG_OFFSET(reg) + SIT9531X_RANGE_OFFSET;\n+\n+\trc = regmap_read(sitdev-\u003eregmap, reg, \u0026tmp);\n+\tif (rc)\n+\t\tdev_err(sitdev-\u003edev, \"Failed to read reg 0x%04x: %d\\n\",\n+\t\t\treg, rc);\n+\telse\n+\t\t*val = (u8)tmp;\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * sit9531x_write_u8 - write an 8-bit register\n+ * @reg:\tregister in SIT9531X_REG(page, offset) form\n+ * @val:\tvalue to write\n+ */\n+int sit9531x_write_u8(struct sit9531x_dev *sitdev, unsigned int reg,\n+\t\t u8 val)\n+{\n+\tint rc;\n+\n+\treg = (SIT9531X_REG_PAGE(reg) * SIT9531X_PAGE_SIZE) +\n+\t SIT9531X_REG_OFFSET(reg) + SIT9531X_RANGE_OFFSET;\n+\n+\trc = regmap_write(sitdev-\u003eregmap, reg, val);\n+\tif (rc)\n+\t\tdev_err(sitdev-\u003edev, \"Failed to write reg 0x%04x: %d\\n\",\n+\t\t\treg, rc);\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * sit9531x_read_pll_u8 - read a register on a PLL page\n+ * @val:\toutput value\n+ */\n+int sit9531x_read_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t u8 offset, u8 *val)\n+{\n+\treturn sit9531x_read_u8(sitdev,\n+\t\t\t\tSIT9531X_REG(sit9531x_pll_page(pll_idx), offset),\n+\t\t\t\tval);\n+}\n+\n+/*\n+ * sit9531x_write_pll_u8 - write a register on a PLL page\n+ * @val:\tvalue to write\n+ */\n+int sit9531x_write_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t u8 offset, u8 val)\n+{\n+\treturn sit9531x_write_u8(sitdev,\n+\t\t\t\t SIT9531X_REG(sit9531x_pll_page(pll_idx), offset),\n+\t\t\t\t val);\n+}\n+\n+/*\n+ * sit9531x_update_pll_u8 - read-modify-write a register on a PLL page\n+ * @mask:\tbits to modify\n+ * @val:\tnew value for masked bits\n+ */\n+int sit9531x_update_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t u8 offset, u8 mask, u8 val)\n+{\n+\tunsigned int reg;\n+\n+\treg = (sit9531x_pll_page(pll_idx) * SIT9531X_PAGE_SIZE) +\n+\t offset + SIT9531X_RANGE_OFFSET;\n+\n+\treturn regmap_update_bits(sitdev-\u003eregmap, reg, mask, val);\n+}\n+\n+/*\n+ * sit9531x_input_get_regs - get force mask and state register addresses\n+ * @index:\tlogical input index\n+ * @force_reg:\toutput force mask register address\n+ * @state_reg:\toutput state register address\n+ *\n+ * Selects the correct Page 0x02 register pair based on the pair's\n+ * signal mode and the lane (P/N) the index refers to.\n+ */\n+static void sit9531x_input_get_regs(const struct sit9531x_dev *sitdev,\n+\t\t\t\t u8 index,\n+\t\t\t\t unsigned int *force_reg,\n+\t\t\t\t unsigned int *state_reg)\n+{\n+\tif (sitdev-\u003eref[index].sig_mode == SIT9531X_MODE_DE) {\n+\t\t*force_reg = SIT9531X_REG_IN_DE_FORCE;\n+\t\t*state_reg = SIT9531X_REG_IN_DE_STATE;\n+\t} else if (sit9531x_input_is_n(index)) {\n+\t\t*force_reg = SIT9531X_REG_IN_SEN_FORCE;\n+\t\t*state_reg = SIT9531X_REG_IN_SEN_STATE;\n+\t} else {\n+\t\t*force_reg = SIT9531X_REG_IN_SEP_FORCE;\n+\t\t*state_reg = SIT9531X_REG_IN_SEP_STATE;\n+\t}\n+}\n+\n+/*\n+ * sit9531x_input_disable - disable an input reference\n+ * @index:\tlogical input index (0-N)\n+ *\n+ * Sets the force mask bit and clears the state bit for the given\n+ * input, effectively disabling it. Register selection depends on\n+ * the pair's signal mode (SE/DE) and the lane (P/N); the bit within\n+ * each register addresses the input pair.\n+ */\n+int sit9531x_input_disable(struct sit9531x_dev *sitdev, u8 index)\n+{\n+\tstruct sit9531x_ref *ref = \u0026sitdev-\u003eref[index];\n+\tunsigned int force_reg, state_reg;\n+\tu8 pair = sit9531x_input_pair(index);\n+\tu8 val;\n+\tint rc;\n+\n+\tsit9531x_input_get_regs(sitdev, index, \u0026force_reg, \u0026state_reg);\n+\n+\trc = sit9531x_read_u8(sitdev, force_reg, \u0026val);\n+\tif (rc)\n+\t\treturn rc;\n+\trc = sit9531x_write_u8(sitdev, force_reg, val | BIT(pair));\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = sit9531x_read_u8(sitdev, state_reg, \u0026val);\n+\tif (rc)\n+\t\treturn rc;\n+\trc = sit9531x_write_u8(sitdev, state_reg, val \u0026 ~BIT(pair));\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tref-\u003eenabled = false;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_input_enable - enable an input reference\n+ * @index:\tlogical input index (0-N)\n+ *\n+ * Clears the force mask bit for the given input, returning it to\n+ * hardware default (enabled).\n+ */\n+int sit9531x_input_enable(struct sit9531x_dev *sitdev, u8 index)\n+{\n+\tstruct sit9531x_ref *ref = \u0026sitdev-\u003eref[index];\n+\tunsigned int force_reg, state_reg;\n+\tu8 pair = sit9531x_input_pair(index);\n+\tu8 val;\n+\tint rc;\n+\n+\tsit9531x_input_get_regs(sitdev, index, \u0026force_reg, \u0026state_reg);\n+\n+\trc = sit9531x_read_u8(sitdev, force_reg, \u0026val);\n+\tif (rc)\n+\t\treturn rc;\n+\trc = sit9531x_write_u8(sitdev, force_reg, val \u0026 ~BIT(pair));\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tref-\u003eenabled = true;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Output enable / disable (Hi-Z control)\n+ *\n+ * SiT9531x outputs can be configured as differential (DIFF) or\n+ * single-ended (SE) depending on the factory blob. Each output slot\n+ * has TWO Hi-Z force/state register pairs on Page 0x03 -- one for the\n+ * DIFF path, one for the SE path.\n+ *\n+ * We write to BOTH pairs so the function mutes the output regardless\n+ * of whether it's been configured DIFF or SE on this board.\n+ *\n+ * slot 0-7 :\n+ * DIFF mask=0xF2 state=0xF3 SE mask=0xF8 state=0xF9\n+ * slot 8-11:\n+ * DIFF mask=0xF4 state=0xF5 SE mask=0xFA state=0xFB\n+ *\n+ * MASK bit = 1 -\u003e driver takes control of that output's Hi-Z state\n+ * STATE bit = 0 -\u003e output is forced to Hi-Z (muted)\n+ * STATE bit = 1 -\u003e output is driven (active)\n+ *\n+ * The output \"index\" in the driver is logical; the physical slot comes\n+ * from info-\u003eclkout_map[].\n+ */\n+\n+struct sit9531x_hiz_regs {\n+\tunsigned int diff_mask;\n+\tunsigned int diff_state;\n+\tunsigned int se_mask;\n+\tunsigned int se_state;\n+\tu8 bit;\n+};\n+\n+static void sit9531x_output_get_hiz_regs(u8 slot,\n+\t\t\t\t\t struct sit9531x_hiz_regs *r)\n+{\n+\tif (slot \u003c= 7) {\n+\t\tr-\u003ediff_mask = SIT9531X_REG_HIZ_DIFF_07_MASK;\n+\t\tr-\u003ediff_state = SIT9531X_REG_HIZ_DIFF_07_STATE;\n+\t\tr-\u003ese_mask = SIT9531X_REG_HIZ_SE_07_MASK;\n+\t\tr-\u003ese_state = SIT9531X_REG_HIZ_SE_07_STATE;\n+\t\tr-\u003ebit = slot;\n+\t} else {\n+\t\tr-\u003ediff_mask = SIT9531X_REG_HIZ_DIFF_811_MASK;\n+\t\tr-\u003ediff_state = SIT9531X_REG_HIZ_DIFF_811_STATE;\n+\t\tr-\u003ese_mask = SIT9531X_REG_HIZ_SE_811_MASK;\n+\t\tr-\u003ese_state = SIT9531X_REG_HIZ_SE_811_STATE;\n+\t\tr-\u003ebit = slot - 8;\n+\t}\n+}\n+\n+/*\n+ * Report whether a slot is currently forced into Hi-Z, i.e. the driver\n+ * (or the blob) took control of the Hi-Z state (MASK bit set) and drives\n+ * it low (STATE bit clear). Either register pair muting the slot counts,\n+ * mirroring what sit9531x_output_disable() programs.\n+ */\n+static int sit9531x_output_forced_hiz(struct sit9531x_dev *sitdev, u8 slot,\n+\t\t\t\t bool *muted)\n+{\n+\tstruct sit9531x_hiz_regs r;\n+\tu8 mask, state;\n+\tint rc;\n+\n+\tsit9531x_output_get_hiz_regs(slot, \u0026r);\n+\n+\trc = sit9531x_read_u8(sitdev, r.diff_mask, \u0026mask);\n+\tif (rc)\n+\t\treturn rc;\n+\trc = sit9531x_read_u8(sitdev, r.diff_state, \u0026state);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t*muted = (mask \u0026 BIT(r.bit)) \u0026\u0026 !(state \u0026 BIT(r.bit));\n+\tif (*muted)\n+\t\treturn 0;\n+\n+\trc = sit9531x_read_u8(sitdev, r.se_mask, \u0026mask);\n+\tif (rc)\n+\t\treturn rc;\n+\trc = sit9531x_read_u8(sitdev, r.se_state, \u0026state);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t*muted = (mask \u0026 BIT(r.bit)) \u0026\u0026 !(state \u0026 BIT(r.bit));\n+\n+\treturn 0;\n+}\n+\n+static int sit9531x_hiz_set_bit(struct sit9531x_dev *sitdev,\n+\t\t\t\tunsigned int reg, u8 bit, bool set)\n+{\n+\tu8 cur, new_val;\n+\tint rc;\n+\n+\trc = sit9531x_read_u8(sitdev, reg, \u0026cur);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tnew_val = set ? (cur | BIT(bit)) : (cur \u0026 ~BIT(bit));\n+\n+\treturn sit9531x_write_u8(sitdev, reg, new_val);\n+}\n+\n+/*\n+ * Enter the output-system programming state: unlock the debug\n+ * registers on Page 3 and issue the PRG_CMD state command. Register\n+ * writes that reconfigure the output system only take effect when\n+ * they are made inside this state.\n+ */\n+static int sit9531x_prg_enter(struct sit9531x_dev *sitdev)\n+{\n+\tint rc;\n+\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG_OUTSYS_DEBUG,\n+\t\t\t SIT9531X_DEBUG_UNLOCK_VAL);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\treturn sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN,\n+\t\t\t\t SIT9531X_PRG_CMD_STATE);\n+}\n+\n+/*\n+ * Commit a programming sequence started by sit9531x_prg_enter():\n+ * update the NVM shadow and re-lock the loops. The sleep gives the\n+ * hardware its required settling time after the loop-lock command;\n+ * it is intentional despite the caller holding multiop_lock, as the\n+ * whole NVM + lock sequence must be atomic.\n+ */\n+static int sit9531x_prg_commit(struct sit9531x_dev *sitdev)\n+{\n+\tint rc;\n+\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN,\n+\t\t\t SIT9531X_UPDATE_NVM);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN,\n+\t\t\t SIT9531X_LOOP_LOCK);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tmsleep(100);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_output_disable - mute an output (force Hi-Z)\n+ * @index:\tlogical output index (0..info-\u003enum_outputs-1)\n+ *\n+ * Sets MASK+STATE on BOTH the DIFF and SE register pairs so that the\n+ * output is muted regardless of its electrical configuration. The\n+ * writes are wrapped in the PRG_CMD / NVM update / loop lock sequence\n+ * so the new state is applied by the hardware.\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ */\n+int sit9531x_output_disable(struct sit9531x_dev *sitdev, u8 index)\n+{\n+\tconst struct sit9531x_chip_info *info = sitdev-\u003einfo;\n+\tstruct sit9531x_hiz_regs r;\n+\tu8 slot;\n+\tint rc, ret;\n+\n+\tlockdep_assert_held(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (index \u003e= info-\u003enum_outputs)\n+\t\treturn -EINVAL;\n+\n+\tslot = info-\u003eclkout_map[index];\n+\tsit9531x_output_get_hiz_regs(slot, \u0026r);\n+\n+\trc = sit9531x_prg_enter(sitdev);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* Take control (MASK=1) and mute (STATE=0) on both DIFF and SE */\n+\trc = sit9531x_hiz_set_bit(sitdev, r.diff_mask, r.bit, true);\n+\tif (rc)\n+\t\tgoto commit;\n+\trc = sit9531x_hiz_set_bit(sitdev, r.diff_state, r.bit, false);\n+\tif (rc)\n+\t\tgoto commit;\n+\trc = sit9531x_hiz_set_bit(sitdev, r.se_mask, r.bit, true);\n+\tif (rc)\n+\t\tgoto commit;\n+\trc = sit9531x_hiz_set_bit(sitdev, r.se_state, r.bit, false);\n+\n+commit:\n+\t/*\n+\t * Always leave the PRG_CMD programming state, even on a mid-sequence\n+\t * write failure: prg_enter() unlocked the output loops, so returning\n+\t * without prg_commit() would strand the chip in the programming state\n+\t * with the loops unlocked. Best effort -- keep the first error.\n+\t */\n+\tret = sit9531x_prg_commit(sitdev);\n+\tif (ret \u0026\u0026 !rc)\n+\t\trc = ret;\n+\tif (!rc)\n+\t\tsitdev-\u003eout[index].enabled = false;\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * sit9531x_output_enable - un-mute an output (active state)\n+ * @index:\tlogical output index (0..info-\u003enum_outputs-1)\n+ *\n+ * Releases MASK on BOTH register pairs so the output returns to\n+ * whatever the initial_config blob programmed. The writes are wrapped\n+ * in the PRG_CMD / NVM update / loop lock sequence so the new state is\n+ * applied by the hardware.\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ */\n+int sit9531x_output_enable(struct sit9531x_dev *sitdev, u8 index)\n+{\n+\tconst struct sit9531x_chip_info *info = sitdev-\u003einfo;\n+\tstruct sit9531x_hiz_regs r;\n+\tu8 slot;\n+\tint rc, ret;\n+\n+\tlockdep_assert_held(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (index \u003e= info-\u003enum_outputs)\n+\t\treturn -EINVAL;\n+\n+\tslot = info-\u003eclkout_map[index];\n+\tsit9531x_output_get_hiz_regs(slot, \u0026r);\n+\n+\trc = sit9531x_prg_enter(sitdev);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = sit9531x_hiz_set_bit(sitdev, r.diff_mask, r.bit, false);\n+\tif (rc)\n+\t\tgoto commit;\n+\trc = sit9531x_hiz_set_bit(sitdev, r.se_mask, r.bit, false);\n+\n+commit:\n+\t/*\n+\t * Always leave the PRG_CMD programming state, even on a mid-sequence\n+\t * write failure: prg_enter() unlocked the output loops, so returning\n+\t * without prg_commit() would strand the chip in the programming state\n+\t * with the loops unlocked. Best effort -- keep the first error.\n+\t */\n+\tret = sit9531x_prg_commit(sitdev);\n+\tif (ret \u0026\u0026 !rc)\n+\t\trc = ret;\n+\tif (!rc)\n+\t\tsitdev-\u003eout[index].enabled = true;\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * Input priority selection\n+ *\n+ * The SiT9531x has a 12-slot priority table per PLL on Page 1. Each\n+ * register holds two slots nibble-packed (even slot in [3:0], odd slot\n+ * in [7:4]).\n+ *\n+ * The procedure:\n+ * 1. Force PLL into holdover (PLL page reg 0x6F bit 4)\n+ * 2. Write priority slots on Page 1\n+ * 3. Small change update (Page 0 reg 0x0F bit 1)\n+ * 4. Release holdover\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ */\n+\n+/* Page-1 register holding priority slot @slot of @pll_idx. */\n+static u16 sit9531x_prio_reg(u8 pll_idx, u8 slot)\n+{\n+\treturn SIT9531X_REG(SIT9531X_PAGE_PRIOSYS,\n+\t\t\t SIT9531X_PRIO_BASE_REG +\n+\t\t\t SIT9531X_PRIO_REGS_PER_PLL * pll_idx +\n+\t\t\t slot / SIT9531X_PRIO_SLOTS_PER_REG);\n+}\n+\n+/*\n+ * Extract priority slot @slot from its register value. The register\n+ * holding slots 2n and 2n+1 keeps the earlier slot in the high nibble\n+ * (CLK_SPARE\u003c2n\u003eSEL) and the later one in the low nibble.\n+ */\n+static u8 sit9531x_prio_slot_get(u8 val, u8 slot)\n+{\n+\tif (slot \u0026 1)\n+\t\treturn val \u0026 SIT9531X_PRIO_NIBBLE_MASK;\n+\n+\treturn val \u003e\u003e SIT9531X_PRIO_HI_SHIFT;\n+}\n+\n+/* Place source @src in priority slot @slot of a register value. */\n+static u8 sit9531x_prio_slot_set(u8 val, u8 slot, u8 src)\n+{\n+\tif (slot \u0026 1)\n+\t\treturn (val \u0026 (SIT9531X_PRIO_NIBBLE_MASK \u003c\u003c\n+\t\t\t SIT9531X_PRIO_HI_SHIFT)) |\n+\t\t (src \u0026 SIT9531X_PRIO_NIBBLE_MASK);\n+\n+\treturn (val \u0026 SIT9531X_PRIO_NIBBLE_MASK) |\n+\t ((src \u0026 SIT9531X_PRIO_NIBBLE_MASK) \u003c\u003c\n+\t\tSIT9531X_PRIO_HI_SHIFT);\n+}\n+\n+/*\n+ * Commit a priority-table programming sequence through the Page-0\n+ * programming directive register.\n+ *\n+ * A small change update is all the table needs. The NVM-bank and\n+ * loop-lock directives that the output system issues do not belong\n+ * here: the former programs non-volatile storage from the efuse and\n+ * the latter only means anything after an escape to the PRG_CMD\n+ * state. This matches the vendor input_priority_sel() procedure.\n+ */\n+static int sit9531x_prio_prg_commit(struct sit9531x_dev *sitdev)\n+{\n+\tint rc;\n+\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG_GLOBAL_UPDATE,\n+\t\t\t SIT9531X_SMALL_UPDATE_CMD);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tusleep_range(1000, 2000);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_input_prio_set - set input priority for a PLL\n+ * @input_idx:\tinput source in hardware encoding (0-11, see\n+ *\t\tsit9531x_input_hw_src())\n+ * @prio:\tpriority slot position (0 = highest)\n+ *\n+ * Writes a single priority slot assignment. The input source is\n+ * placed at the requested slot, and all lower-priority (higher-numbered)\n+ * slots are filled with the same source to avoid stale entries.\n+ */\n+int sit9531x_input_prio_set(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t u8 input_idx, u8 prio)\n+{\n+\tu8 val, slot;\n+\tint rc, prg_rc, ho_rc;\n+\tu16 reg;\n+\n+\tlockdep_assert_held(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (pll_idx \u003e= SIT9531X_NUM_PLLS)\n+\t\treturn -EINVAL;\n+\tif (input_idx \u003e= SIT9531X_PRIO_NUM_SRC)\n+\t\treturn -EINVAL;\n+\tif (prio \u003e= SIT9531X_PRIO_MAX_SLOTS)\n+\t\treturn -EINVAL;\n+\n+\t/* Force PLL into holdover. */\n+\trc = sit9531x_update_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_HO_CTRL,\n+\t\t\t\t BIT(SIT9531X_PLL_HO_FORCE_BIT),\n+\t\t\t\t BIT(SIT9531X_PLL_HO_FORCE_BIT));\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tusleep_range(10000, 12000);\n+\n+\t/*\n+\t * Write the requested slot on Page 1, then fill the remaining\n+\t * slots with the same source, matching the procedure script's\n+\t * backfill behaviour.\n+\t */\n+\tfor (slot = prio; slot \u003c SIT9531X_PRIO_MAX_SLOTS; slot++) {\n+\t\treg = sit9531x_prio_reg(pll_idx, slot);\n+\n+\t\trc = sit9531x_read_u8(sitdev, reg, \u0026val);\n+\t\tif (rc)\n+\t\t\tgoto commit;\n+\n+\t\tval = sit9531x_prio_slot_set(val, slot, input_idx);\n+\n+\t\trc = sit9531x_write_u8(sitdev, reg, val);\n+\t\tif (rc)\n+\t\t\tgoto commit;\n+\t}\n+\n+commit:\n+\t/*\n+\t * Latch the table even when a slot write failed, so a partially\n+\t * written table takes effect as a whole rather than waiting for an\n+\t * unrelated update to latch the leftovers later. Best effort --\n+\t * keep the first error.\n+\t */\n+\tprg_rc = sit9531x_prio_prg_commit(sitdev);\n+\tif (prg_rc \u0026\u0026 !rc)\n+\t\trc = prg_rc;\n+\n+\t/*\n+\t * Release holdover. This must run even when an earlier step\n+\t * failed, so its own error is only surfaced when it would not mask a\n+\t * prior failure -- otherwise a failing release could leave the PLL\n+\t * stuck in forced holdover while the function reports success.\n+\t */\n+\tho_rc = sit9531x_update_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_HO_CTRL,\n+\t\t\t\t BIT(SIT9531X_PLL_HO_FORCE_BIT), 0);\n+\tif (ho_rc \u0026\u0026 !rc)\n+\t\trc = ho_rc;\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * sit9531x_input_prio_get - read an input's priority slot for a PLL\n+ * @input_idx:\tinput source in hardware encoding (see\n+ *\t\tsit9531x_input_hw_src())\n+ * @prio:\toutput slot position (0 = highest); set to\n+ *\t\tSIT9531X_PRIO_MAX_SLOTS when the source is not in the table\n+ *\n+ * Scans the PLL's 12-slot priority table on Page 1 and returns the\n+ * highest-priority (lowest-numbered) slot that references the source.\n+ * This reads the value the chip actually holds rather than a cached\n+ * default, so pin-get reflects the real hardware priority.\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ */\n+int sit9531x_input_prio_get(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t u8 input_idx, u8 *prio)\n+{\n+\tu8 val, slot, src;\n+\tint rc;\n+\n+\tlockdep_assert_held(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (pll_idx \u003e= SIT9531X_NUM_PLLS)\n+\t\treturn -EINVAL;\n+\n+\tfor (slot = 0; slot \u003c SIT9531X_PRIO_MAX_SLOTS; slot++) {\n+\t\trc = sit9531x_read_u8(sitdev,\n+\t\t\t\t sit9531x_prio_reg(pll_idx, slot), \u0026val);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\n+\t\tsrc = sit9531x_prio_slot_get(val, slot);\n+\n+\t\tif (src == input_idx) {\n+\t\t\t*prio = slot;\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\t*prio = SIT9531X_PRIO_MAX_SLOTS;\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_prio_table_commit - write a full priority table for a PLL\n+ * @srcs:\tarray of SIT9531X_PRIO_MAX_SLOTS source codes, slot 0 first\n+ *\n+ * Programs all priority slots (nibble-packed, two per register) for\n+ * the PLL using the same holdover / small-update sequence as\n+ * sit9531x_input_prio_set(). Caller must hold sitdev-\u003emultiop_lock.\n+ */\n+static int sit9531x_prio_table_commit(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t\t const u8 *srcs)\n+{\n+\tu8 val, slot;\n+\tint rc, prg_rc, ho_rc;\n+\tu16 reg;\n+\n+\trc = sit9531x_update_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_HO_CTRL,\n+\t\t\t\t BIT(SIT9531X_PLL_HO_FORCE_BIT),\n+\t\t\t\t BIT(SIT9531X_PLL_HO_FORCE_BIT));\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tusleep_range(10000, 12000);\n+\n+\tfor (slot = 0; slot \u003c SIT9531X_PRIO_MAX_SLOTS; slot++) {\n+\t\treg = sit9531x_prio_reg(pll_idx, slot);\n+\n+\t\trc = sit9531x_read_u8(sitdev, reg, \u0026val);\n+\t\tif (rc)\n+\t\t\tgoto commit;\n+\n+\t\tval = sit9531x_prio_slot_set(val, slot, srcs[slot]);\n+\n+\t\trc = sit9531x_write_u8(sitdev, reg, val);\n+\t\tif (rc)\n+\t\t\tgoto commit;\n+\t}\n+\n+commit:\n+\t/* Latch unconditionally, as in sit9531x_input_prio_set(). */\n+\tprg_rc = sit9531x_prio_prg_commit(sitdev);\n+\tif (prg_rc \u0026\u0026 !rc)\n+\t\trc = prg_rc;\n+\n+\tho_rc = sit9531x_update_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_HO_CTRL,\n+\t\t\t\t BIT(SIT9531X_PLL_HO_FORCE_BIT), 0);\n+\tif (ho_rc \u0026\u0026 !rc)\n+\t\trc = ho_rc;\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * sit9531x_prio_table_read - read a PLL's priority-table source codes\n+ * @srcs:\toutput array of SIT9531X_PRIO_MAX_SLOTS source codes\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ */\n+static int sit9531x_prio_table_read(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t\t u8 *srcs)\n+{\n+\tu8 val, slot;\n+\tint rc;\n+\n+\tfor (slot = 0; slot \u003c SIT9531X_PRIO_MAX_SLOTS; slot++) {\n+\t\trc = sit9531x_read_u8(sitdev,\n+\t\t\t\t sit9531x_prio_reg(pll_idx, slot), \u0026val);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\n+\t\tsrcs[slot] = sit9531x_prio_slot_get(val, slot);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_input_prio_remove - drop an input from a PLL's priority table\n+ * @input_idx:\tinput source in hardware encoding\n+ *\n+ * Rewrites the priority table with the source removed: the remaining\n+ * sources are compacted toward the highest-priority slots and the freed\n+ * tail slots are backfilled with the lowest-priority remaining source\n+ * (matching sit9531x_input_prio_set()). This makes a disconnected\n+ * input ineligible for automatic reference selection, not just gated at\n+ * the input buffer. It is a no-op when the source is absent, or when it\n+ * is the only source (the buffer disable then drives the PLL to\n+ * holdover and the table must not be left empty).\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ */\n+int sit9531x_input_prio_remove(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t u8 input_idx)\n+{\n+\tu8 srcs[SIT9531X_PRIO_MAX_SLOTS];\n+\tu8 kept[SIT9531X_PRIO_MAX_SLOTS];\n+\tu8 slot, count = 0;\n+\tbool found = false;\n+\tint rc;\n+\n+\tlockdep_assert_held(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (pll_idx \u003e= SIT9531X_NUM_PLLS)\n+\t\treturn -EINVAL;\n+\n+\trc = sit9531x_prio_table_read(sitdev, pll_idx, srcs);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tfor (slot = 0; slot \u003c SIT9531X_PRIO_MAX_SLOTS; slot++) {\n+\t\tif (srcs[slot] == input_idx)\n+\t\t\tfound = true;\n+\t\telse\n+\t\t\tkept[count++] = srcs[slot];\n+\t}\n+\n+\tif (!found || count == 0)\n+\t\treturn 0;\n+\n+\t/* Backfill freed tail slots with the lowest-priority remaining src */\n+\twhile (count \u003c SIT9531X_PRIO_MAX_SLOTS) {\n+\t\tkept[count] = kept[count - 1];\n+\t\tcount++;\n+\t}\n+\n+\treturn sit9531x_prio_table_commit(sitdev, pll_idx, kept);\n+}\n+\n+/*\n+ * sit9531x_input_prio_add - make an input eligible in a PLL's table\n+ * @input_idx:\tinput source in hardware encoding\n+ *\n+ * Ensures the source appears in the priority table so it can be picked\n+ * by automatic reference selection again after a disconnect. If the\n+ * source is already listed the table is left untouched; otherwise it is\n+ * placed in the lowest-priority slot. The original priority is not\n+ * restored -- use sit9531x_input_prio_set() to reassign it.\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ */\n+int sit9531x_input_prio_add(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t u8 input_idx)\n+{\n+\tu8 srcs[SIT9531X_PRIO_MAX_SLOTS];\n+\tu8 slot;\n+\tint rc;\n+\n+\tlockdep_assert_held(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (pll_idx \u003e= SIT9531X_NUM_PLLS)\n+\t\treturn -EINVAL;\n+\n+\trc = sit9531x_prio_table_read(sitdev, pll_idx, srcs);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tfor (slot = 0; slot \u003c SIT9531X_PRIO_MAX_SLOTS; slot++)\n+\t\tif (srcs[slot] == input_idx)\n+\t\t\treturn 0;\n+\n+\tsrcs[SIT9531X_PRIO_MAX_SLOTS - 1] = input_idx;\n+\n+\treturn sit9531x_prio_table_commit(sitdev, pll_idx, srcs);\n+}\n+\n+/* Per-slot DIVO base register offsets (6 slots per page) */\n+static const u8 clkout_odr_divn_base[] = {\n+\t0x14, 0x24, 0x34, 0x44, 0x54, 0x64\n+};\n+\n+/* XO doubler register */\n+#define SIT9531X_REG_XO2_GENERIC\t\tSIT9531X_REG(0x00, 0x2D)\n+#define SIT9531X_XO_DOUBLER_ENB_BIT\t\t7 /* inverted: 0 = enabled */\n+\n+/* VCO frequency bands (Hz) */\n+#define SIT9531X_FVCO_LOWBAND_MIN\t\t4915200000ULL\n+#define SIT9531X_FVCO_LOWBAND_MAX\t\t5898240000ULL\n+#define SIT9531X_FVCO_HIGHBAND_MIN\t\t6875000000ULL\n+#define SIT9531X_FVCO_HIGHBAND_MAX\t\t7812500000ULL\n+\n+/*\n+ * sit9531x_is_xo_doubler_enabled - check if Fref doubler is active\n+ *\n+ * Register 0x2D bit 7 is active-low: 0 = doubler enabled, 1 = disabled.\n+ *\n+ * Return: 1 if enabled, 0 if disabled, \u003c0 on error\n+ */\n+static int sit9531x_is_xo_doubler_enabled(struct sit9531x_dev *sitdev)\n+{\n+\tu8 val;\n+\tint rc;\n+\n+\trc = sit9531x_read_u8(sitdev, SIT9531X_REG_XO2_GENERIC, \u0026val);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\treturn (~val \u003e\u003e SIT9531X_XO_DOUBLER_ENB_BIT) \u0026 1u;\n+}\n+\n+/*\n+ * sit9531x_dbg_sample - latch and read a signal pathway debug sample\n+ * @sitdev:\tdevice pointer\n+ * @pll_idx:\tPLL index (0-3)\n+ * @read_code:\twhich tap of the pathway to sample\n+ * @buf:\tresult, least significant byte first\n+ * @len:\tbytes to read, at most SIT9531X_DBG_DATA_BYTES\n+ *\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+static int sit9531x_dbg_sample(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t u8 read_code, u8 *buf, unsigned int len)\n+{\n+\tunsigned int i;\n+\tint rc;\n+\tu8 v;\n+\n+\tif (len \u003e SIT9531X_DBG_DATA_BYTES)\n+\t\treturn -EINVAL;\n+\n+\trc = sit9531x_write_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_DEBUG,\n+\t\t\t\t SIT9531X_PLL_DEBUG_UNLOCK);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = sit9531x_write_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_DBG_READ_CODE, read_code);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* Reading the trigger latches a fresh sample of the selected tap. */\n+\trc = sit9531x_read_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_DBG_TRIGGER, \u0026v);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tfor (i = 0; i \u003c len; i++) {\n+\t\trc = sit9531x_read_pll_u8(sitdev, pll_idx,\n+\t\t\t\t\t SIT9531X_PLL_REG_DBG_DATA_0 + i,\n+\t\t\t\t\t \u0026buf[i]);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * DIVN as a fixed-point value: int_part plus fracn/fracd, carried with\n+ * SIT9531X_DIVN_SCALE steps per unit. The scale keeps a whole DIVN\n+ * well inside s64 while resolving far below the parts-per-trillion the\n+ * frequency offset is reported in.\n+ */\n+static s64 sit9531x_divn_fixed(u32 int_part, s64 fracn, u64 fracd)\n+{\n+\ts64 whole = (s64)int_part * SIT9531X_DIVN_SCALE;\n+\tu64 frac;\n+\n+\tif (!fracd)\n+\t\treturn whole;\n+\n+\tfrac = mul_u64_u64_div_u64(abs(fracn), SIT9531X_DIVN_SCALE, fracd);\n+\n+\treturn fracn \u003c 0 ? whole - (s64)frac : whole + (s64)frac;\n+}\n+\n+/*\n+ * sit9531x_divn_static - read the configured DIVN of a PLL\n+ * @sitdev:\tdevice pointer\n+ * @pll_idx:\tPLL index (0-3)\n+ * @divn:\tresult, fixed point as per sit9531x_divn_fixed()\n+ *\n+ * Reads PLL page regs 0x30 (integer part), 0x32-0x35 (numerator) and\n+ * 0x38-0x3B (denominator). The numerator is a two's complement 32-bit\n+ * value, so DIVN can sit below the integer part, and the denominator\n+ * register holds the divisor minus one.\n+ *\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+static int sit9531x_divn_static(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t\ts64 *divn)\n+{\n+\tu32 int_part, fracn_raw = 0, fracd_raw = 0;\n+\tint rc, i;\n+\tu8 v;\n+\n+\trc = sit9531x_read_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_DIVN_INT, \u0026v);\n+\tif (rc)\n+\t\treturn rc;\n+\tint_part = v;\n+\n+\tfor (i = 3; i \u003e= 0; i--) {\n+\t\trc = sit9531x_read_pll_u8(sitdev, pll_idx,\n+\t\t\t\t\t SIT9531X_PLL_REG_DIVN_NUM + i, \u0026v);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\t\tfracn_raw = (fracn_raw \u003c\u003c 8) | v;\n+\t}\n+\n+\tfor (i = 3; i \u003e= 0; i--) {\n+\t\trc = sit9531x_read_pll_u8(sitdev, pll_idx,\n+\t\t\t\t\t SIT9531X_PLL_REG_DIVN_DEN + i, \u0026v);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\t\tfracd_raw = (fracd_raw \u003c\u003c 8) | v;\n+\t}\n+\n+\t*divn = sit9531x_divn_fixed(int_part, (s32)fracn_raw,\n+\t\t\t\t (u64)fracd_raw + 1);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_divn_runtime - read the DIVN the digital loop is commanding\n+ * @sitdev:\tdevice pointer\n+ * @pll_idx:\tPLL index (0-3)\n+ * @divn:\tresult, fixed point as per sit9531x_divn_fixed()\n+ *\n+ * Same quantity as sit9531x_divn_static(), but sampled from the running\n+ * loop rather than from the configuration registers, and carried at a\n+ * wider precision: the numerator is 48 bits, two's complement, the\n+ * denominator 49. The integer part shares its tap with the numerator.\n+ *\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+static int sit9531x_divn_runtime(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t\t s64 *divn)\n+{\n+\tu8 buf[SIT9531X_DBG_DATA_BYTES];\n+\tu64 fracn_raw = 0, fracd = 0;\n+\tu32 int_part;\n+\tint rc, i;\n+\n+\trc = sit9531x_dbg_sample(sitdev, pll_idx, SIT9531X_DBG_READ_CODE_DIVN,\n+\t\t\t\t buf, SIT9531X_DBG_DATA_BYTES);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tfor (i = 5; i \u003e= 0; i--)\n+\t\tfracn_raw = (fracn_raw \u003c\u003c 8) | buf[i];\n+\n+\tint_part = buf[6] | ((u32)(buf[7] \u0026 SIT9531X_DIVN_RT_INT_HI_BIT) \u003c\u003c 8);\n+\n+\trc = sit9531x_dbg_sample(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_DBG_READ_CODE_DIVN_DEN, buf,\n+\t\t\t\t SIT9531X_DBG_DATA_BYTES);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tfor (i = 5; i \u003e= 0; i--)\n+\t\tfracd = (fracd \u003c\u003c 8) | buf[i];\n+\n+\tfracd |= (u64)(buf[6] \u0026 SIT9531X_DIVN_RT_INT_HI_BIT) \u003c\u003c 48;\n+\n+\t*divn = sit9531x_divn_fixed(int_part,\n+\t\t\t\t sign_extend64(fracn_raw,\n+\t\t\t\t\t\t SIT9531X_DIVN_RT_NUM_BITS - 1),\n+\t\t\t\t fracd);\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * sit9531x_pll_ffo_ppt - fractional frequency offset of a PLL's reference\n+ * @sitdev:\tdevice pointer\n+ * @pll_idx:\tPLL index (0-3)\n+ * @ffo:\tresult in parts per trillion\n+ *\n+ * A locked PLL commands whatever DIVN keeps its VCO tracking the\n+ * reference. How far that sits from the configured DIVN is how far the\n+ * reference sits from the local oscillator, which is the fractional\n+ * frequency offset the DPLL ABI reports for the pin feeding the device.\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ *\n+ * Return: 0 on success, -ENODATA when DIVN is not programmed, \u003c0 on\n+ * error.\n+ */\n+int sit9531x_pll_ffo_ppt(struct sit9531x_dev *sitdev, u8 pll_idx, s64 *ffo)\n+{\n+\ts64 configured, running, delta;\n+\tu64 magnitude;\n+\tint rc;\n+\n+\tlockdep_assert_held(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (pll_idx \u003e= SIT9531X_NUM_PLLS)\n+\t\treturn -EINVAL;\n+\n+\trc = sit9531x_divn_static(sitdev, pll_idx, \u0026configured);\n+\tif (rc)\n+\t\treturn rc;\n+\tif (configured \u003c= 0)\n+\t\treturn -ENODATA;\n+\n+\trc = sit9531x_divn_runtime(sitdev, pll_idx, \u0026running);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tdelta = running - configured;\n+\tmagnitude = mul_u64_u64_div_u64(abs(delta), SIT9531X_PPT_PER_UNIT,\n+\t\t\t\t\t(u64)configured);\n+\n+\t*ffo = delta \u003c 0 ? -(s64)magnitude : (s64)magnitude;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_get_fvco - read VCO frequency from chip's DIVN registers\n+ *\n+ * Fvco = Fref * DIVN, where DIVN comes from sit9531x_divn_static() and\n+ * Fref = xtal_freq \u003c\u003c doubler. DIVN is the\n+ * steady-state Fvco/Fref target programmed by the NVM blob and is\n+ * authoritative in both free-run and sync modes; the previous split\n+ * between free-run and sync formulas returned 0 on chips that didn't\n+ * have a sync input populated, which broke the TDC phase readback.\n+ *\n+ * Return: Fvco in Hz, or 0 on error\n+ */\n+static u64 sit9531x_get_fvco(struct sit9531x_dev *sitdev, u8 pll_idx)\n+{\n+\tint doubler, rc;\n+\ts64 divn;\n+\tu64 fref;\n+\n+\t/*\n+\t * DT board-config override: some configs (e.g. an INTSYNC PLL)\n+\t * run a VCO that Fref*DIVN does not reproduce. When the board\n+\t * supplies the measured VCO, use it verbatim.\n+\t */\n+\tif (pll_idx \u003c SIT9531X_NUM_PLLS \u0026\u0026 sitdev-\u003epll_fvco[pll_idx])\n+\t\treturn sitdev-\u003epll_fvco[pll_idx];\n+\n+\trc = sit9531x_divn_static(sitdev, pll_idx, \u0026divn);\n+\tif (rc || divn \u003c= 0)\n+\t\treturn 0;\n+\n+\tdoubler = sit9531x_is_xo_doubler_enabled(sitdev);\n+\tif (doubler \u003c 0)\n+\t\treturn 0;\n+\n+\tfref = (u64)sitdev-\u003extal_freq \u003c\u003c doubler;\n+\n+\treturn mul_u64_u64_div_u64(fref, (u64)divn, SIT9531X_DIVN_SCALE);\n+}\n+\n+/*\n+ * sit9531x_output_phase_flush - flush the output phase of a PLL\n+ *\n+ * Fires the chip's on-demand phase-flush (PHFL) so every output divider\n+ * of @pll_idx restarts aligned to the PLL phase. Without it a rewritten\n+ * DIVO keeps counting from an arbitrary point and the output edge lands\n+ * with a persistent offset against the tracked reference (only a power\n+ * cycle realigned it).\n+ *\n+ * The sequence mirrors the vendor procedure: arm the on-demand PHFL and\n+ * latch it with the PLL-page small-change update, then select the\n+ * in-register phase trigger on Page 0 and pulse it. The Page 0 trigger\n+ * register is touched read-modify-write so the unrelated OEb bits are\n+ * preserved.\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ */\n+static int sit9531x_output_phase_flush(struct sit9531x_dev *sitdev, u8 pll_idx)\n+{\n+\tu8 ctrl, orig;\n+\tint rc, ret;\n+\n+\t/* Arm the on-demand phase-flush on the PLL page. */\n+\trc = sit9531x_update_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_PHFL_CTRL,\n+\t\t\t\t SIT9531X_PLL_PHFL_ON_DEMAND_EN,\n+\t\t\t\t SIT9531X_PLL_PHFL_ON_DEMAND_EN);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* Latch it with the PLL small-change update. */\n+\trc = sit9531x_update_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_SMALL_UPDATE,\n+\t\t\t\t SIT9531X_SMALL_UPDATE_CMD,\n+\t\t\t\t SIT9531X_SMALL_UPDATE_CMD);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/*\n+\t * Select the in-register phase trigger, preserving the OEb bits.\n+\t * Remember the original register value (with the trigger de-asserted)\n+\t * so the trigger-source select can be restored once the pulse has\n+\t * fired.\n+\t */\n+\trc = sit9531x_read_u8(sitdev, SIT9531X_REG_GPIO_FUNC_CTRL1, \u0026ctrl);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\torig = ctrl \u0026 ~SIT9531X_DIVO_PHASE_TRIG;\n+\tctrl = orig | SIT9531X_DIVO_PHASE_SEL_REG;\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG_GPIO_FUNC_CTRL1, ctrl);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/*\n+\t * Pulse the phase trigger. No explicit delay is needed between the\n+\t * set and clear writes: each I2C transaction takes far longer than\n+\t * any minimum pulse width.\n+\t */\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG_GPIO_FUNC_CTRL1,\n+\t\t\t ctrl | SIT9531X_DIVO_PHASE_TRIG);\n+\n+\t/*\n+\t * Restore the original trigger-source select. The pulse above has\n+\t * already latched the flush, so a one-shot flush must not leave the\n+\t * phase trigger permanently pinned to the in-register source. This\n+\t * runs even when the pulse write failed, otherwise a failed flush\n+\t * would keep a hardware trigger source hijacked; the restore error\n+\t * is only surfaced when it would not mask the pulse failure.\n+\t */\n+\tret = sit9531x_write_u8(sitdev, SIT9531X_REG_GPIO_FUNC_CTRL1, orig);\n+\tif (ret \u0026\u0026 !rc)\n+\t\trc = ret;\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * sit9531x_output_freq_set - set output clock frequency\n+ * @out_idx:\toutput index (0-N for this chip variant)\n+ * @pll_idx:\tPLL driving this output (0-3)\n+ * @frequency:\tdesired output frequency in Hz\n+ *\n+ * Computes DIVO = Fvco / frequency and writes the 34-bit output divider\n+ * to the output system registers on Pages 3/4. The write sequence is:\n+ * 1. Unlock debug registers (Page 3)\n+ * 2. Enter PRG_CMD state\n+ * 3. Write 5-byte DIVO to the correct page/slot\n+ * 4. NVM update\n+ * 5. Loop lock\n+ * 6. Wait for lock to settle\n+ * 7. Flush the output phase so the new divider starts aligned\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ *\n+ * Return: 0 on success, \u003c0 on error. Actual frequency may differ\n+ * due to integer division; the output state is updated with\n+ * the effective frequency (Fvco / DIVO).\n+ */\n+int sit9531x_output_freq_set(struct sit9531x_dev *sitdev, u8 out_idx,\n+\t\t\t u8 pll_idx, u64 frequency)\n+{\n+\tconst struct sit9531x_chip_info *info = sitdev-\u003einfo;\n+\tu8 slot, page, base_reg, divo_bytes[5], msb_old;\n+\tu64 fvco, divo, fvco_min, fvco_max;\n+\tint rc, j, ret;\n+\n+\tlockdep_assert_held(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (out_idx \u003e= info-\u003enum_outputs || pll_idx \u003e= SIT9531X_NUM_PLLS)\n+\t\treturn -EINVAL;\n+\n+\tif (!frequency)\n+\t\treturn -EINVAL;\n+\n+\t/* Determine VCO frequency band limits */\n+\tif (pll_idx == 1 || pll_idx == 3) {\n+\t\t/* PLLB, PLLD: high band */\n+\t\tfvco_min = SIT9531X_FVCO_HIGHBAND_MIN;\n+\t\tfvco_max = SIT9531X_FVCO_HIGHBAND_MAX;\n+\t} else {\n+\t\t/* PLLA, PLLC: low band */\n+\t\tfvco_min = SIT9531X_FVCO_LOWBAND_MIN;\n+\t\tfvco_max = SIT9531X_FVCO_LOWBAND_MAX;\n+\t}\n+\n+\t/*\n+\t * Read current VCO frequency. When the board supplies an explicit\n+\t * Fvco via \"sitime,pll-fvco\" the override is the source of truth\n+\t * (e.g. a chip variant that runs out of the documented band, or a\n+\t * mode like INTSYNC where Fref*DIVN does not reproduce the VCO), so\n+\t * skip the band clamp in that case.\n+\t */\n+\tfvco = sit9531x_get_fvco(sitdev, pll_idx);\n+\tif (!fvco) {\n+\t\tfvco = fvco_min;\n+\t} else if (!sitdev-\u003epll_fvco[pll_idx]) {\n+\t\tif (fvco \u003c fvco_min)\n+\t\t\tfvco = fvco_min;\n+\t\telse if (fvco \u003e fvco_max)\n+\t\t\tfvco = fvco_max;\n+\t}\n+\n+\tdivo = div64_u64(fvco, frequency);\n+\tif (!divo)\n+\t\treturn -EINVAL;\n+\n+\tdev_dbg(sitdev-\u003edev,\n+\t\t\"out%u: Fvco=%llu freq=%llu DIVO=%llu (effective %llu Hz)\\n\",\n+\t\tout_idx, fvco, frequency, divo, div64_u64(fvco, divo));\n+\n+\t/* Map output index to physical slot */\n+\tslot = info-\u003eclkout_map[out_idx];\n+\n+\t/* Determine page and per-page slot register */\n+\tif (slot \u003e SIT9531X_PAGE_OUTSYS0_SLOT_MAX)\n+\t\tpage = SIT9531X_PAGE_OUTSYS1;\n+\telse\n+\t\tpage = SIT9531X_PAGE_OUTSYS0;\n+\tbase_reg = clkout_odr_divn_base[slot % 6];\n+\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG_OUTSYS_DEBUG,\n+\t\t\t SIT9531X_DEBUG_UNLOCK_VAL);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN,\n+\t\t\t SIT9531X_PRG_CMD_STATE);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tdivo_bytes[0] = (divo \u003e\u003e 0) \u0026 0xFF;\n+\tdivo_bytes[1] = (divo \u003e\u003e 8) \u0026 0xFF;\n+\tdivo_bytes[2] = (divo \u003e\u003e 16) \u0026 0xFF;\n+\tdivo_bytes[3] = (divo \u003e\u003e 24) \u0026 0xFF;\n+\tdivo_bytes[4] = (divo \u003e\u003e 32) \u0026 0x03; /* only bits [1:0] */\n+\n+\trc = sit9531x_read_u8(sitdev,\n+\t\t\t SIT9531X_REG(page, base_reg - 4), \u0026msb_old);\n+\tif (rc)\n+\t\tgoto commit;\n+\tdivo_bytes[4] |= msb_old \u0026 0xFC;\n+\n+\tfor (j = 0; j \u003c 5; j++) {\n+\t\trc = sit9531x_write_u8(sitdev,\n+\t\t\t\t SIT9531X_REG(page, base_reg - j),\n+\t\t\t\t divo_bytes[j]);\n+\t\tif (rc)\n+\t\t\tgoto commit;\n+\t}\n+\n+commit:\n+\t/*\n+\t * Step 4: NVM update + loop lock. Always run prg_commit() so the chip\n+\t * leaves the PRG_CMD state with the output loops re-locked, even when a\n+\t * write above failed; keep the first error to return. It also carries\n+\t * the required post-lock settling sleep.\n+\t */\n+\tret = sit9531x_prg_commit(sitdev);\n+\tif (ret \u0026\u0026 !rc)\n+\t\trc = ret;\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/*\n+\t * Step 5: flush the PLL's output phase so the new DIVO starts\n+\t * aligned instead of keeping the arbitrary phase the divider\n+\t * happened to be at.\n+\t */\n+\trc = sit9531x_output_phase_flush(sitdev, pll_idx);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tsitdev-\u003eout[out_idx].freq = (u32)div64_u64(fvco, divo);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_output_freq_get - read output clock frequency from hardware\n+ * @out_idx:\toutput index (0-N for this chip variant)\n+ * @frequency:\toutput frequency in Hz\n+ *\n+ * Reads the 34-bit DIVO divider back from the output system registers\n+ * and computes the live output frequency as Fvco / DIVO. This stays\n+ * correct even when the divider was reprogrammed behind the driver's\n+ * back (e.g. by a direct-I2C userspace tool), where the cached value\n+ * would be stale.\n+ *\n+ * The cached output state is refreshed with the computed value.\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ *\n+ * Return: 0 on success, -ENODEV when the output divider or VCO rate\n+ *\t is not resolvable, \u003c0 on register access error\n+ */\n+int sit9531x_output_freq_get(struct sit9531x_dev *sitdev, u8 out_idx,\n+\t\t\t u64 *frequency)\n+{\n+\tconst struct sit9531x_chip_info *info = sitdev-\u003einfo;\n+\tu8 slot, page, base_reg, pll_idx, v;\n+\tu64 fvco, divo = 0;\n+\tint rc, j;\n+\n+\tlockdep_assert_held(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (out_idx \u003e= info-\u003enum_outputs)\n+\t\treturn -EINVAL;\n+\n+\tpll_idx = sitdev-\u003eout[out_idx].pll_idx;\n+\tif (pll_idx \u003e= SIT9531X_NUM_PLLS)\n+\t\treturn -ENODEV;\n+\n+\tfvco = sit9531x_get_fvco(sitdev, pll_idx);\n+\tif (!fvco)\n+\t\treturn -ENODEV;\n+\n+\tslot = info-\u003eclkout_map[out_idx];\n+\tif (slot \u003e SIT9531X_PAGE_OUTSYS0_SLOT_MAX)\n+\t\tpage = SIT9531X_PAGE_OUTSYS1;\n+\telse\n+\t\tpage = SIT9531X_PAGE_OUTSYS0;\n+\tbase_reg = clkout_odr_divn_base[slot % 6];\n+\n+\tfor (j = 4; j \u003e= 0; j--) {\n+\t\trc = sit9531x_read_u8(sitdev,\n+\t\t\t\t SIT9531X_REG(page, base_reg - j), \u0026v);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\t\tif (j == 4)\n+\t\t\tv \u0026= 0x03;\n+\t\tdivo = (divo \u003c\u003c 8) | v;\n+\t}\n+\n+\tif (!divo)\n+\t\treturn -ENODEV;\n+\n+\t*frequency = div64_u64(fvco, divo);\n+\tsitdev-\u003eout[out_idx].freq = (u32)*frequency;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Phase adjust (PRG_RST_DELAY register-based).\n+ *\n+ * The chip exposes a per-output 34-bit coarse delay measured in VCO\n+ * clock periods plus a 3-bit fine delay in fixed 30 ps steps. The\n+ * five bytes PROG6..PROG2 hold the field across registers:\n+ * base + 0 PROG6 [7:5] OPSTG_VCASC_BUMP (preserved via RMW)\n+ * [4:2] PRG_RST_FINE_DELAY\n+ * [1:0] PRG_RST_DELAY[33:32]\n+ * base + 1 PROG5 PRG_RST_DELAY[31:24]\n+ * base + 2 PROG4 PRG_RST_DELAY[23:16]\n+ * base + 3 PROG3 PRG_RST_DELAY[15:8]\n+ * base + 4 PROG2 PRG_RST_DELAY[7:0]\n+ *\n+ * Outputs 0-5 live on Page 3, outputs 6-11 on Page 4, with each\n+ * output's block at base = 0x15 + 16 * (out_idx % 6).\n+ *\n+ * The chip only supports unsigned positive delay. A negative phase\n+ * adjustment (advance) is wrapped to (T_out - |phase|) modulo one\n+ * output period, which is identical for a periodic signal.\n+ */\n+\n+int sit9531x_output_phase_adjust_set(struct sit9531x_dev *sitdev,\n+\t\t\t\t u8 out_idx, s32 phase_ps)\n+{\n+\tconst struct sit9531x_chip_info *info = sitdev-\u003einfo;\n+\tu64 abs_ps, fvco, coarse, coarse_ps, rem_ps;\n+\tu8 page, base, prog6_val, fine = 0;\n+\tu8 pll_idx, slot;\n+\tu32 freq;\n+\tint rc, ret;\n+\n+\tif (out_idx \u003e= info-\u003enum_outputs)\n+\t\treturn -EINVAL;\n+\n+\tpll_idx = sitdev-\u003eout[out_idx].pll_idx;\n+\tif (pll_idx \u003e= SIT9531X_NUM_PLLS)\n+\t\treturn -EINVAL;\n+\n+\tfreq = sitdev-\u003eout[out_idx].freq;\n+\tif (!freq)\n+\t\treturn -EINVAL;\n+\n+\tfvco = sit9531x_get_fvco(sitdev, pll_idx);\n+\tif (!fvco)\n+\t\treturn -EIO;\n+\n+\t/*\n+\t * Convert to unsigned absolute delay. Negative phase (advance)\n+\t * is rendered as T_out - |phase|, modulo the output period.\n+\t */\n+\tif (phase_ps == 0) {\n+\t\tabs_ps = 0;\n+\t} else if (phase_ps \u003e 0) {\n+\t\tabs_ps = (u64)phase_ps;\n+\t} else {\n+\t\tu64 t_out_ps = div64_u64(1000000000000ULL, freq);\n+\t\tu64 advance = (u64)(-(s64)phase_ps);\n+\n+\t\tif (t_out_ps == 0)\n+\t\t\treturn -EINVAL;\n+\t\tadvance %= t_out_ps;\n+\t\tabs_ps = (advance == 0) ? 0 : (t_out_ps - advance);\n+\t}\n+\n+\t/*\n+\t * coarse_cycles = abs_ps * Fvco / 1e12 ps/s.\n+\t * mul_u64_u64_div_u64() avoids overflow when abs_ps approaches\n+\t * one second of 1 PPS wrap-around.\n+\t */\n+\tcoarse = mul_u64_u64_div_u64(abs_ps, fvco, 1000000000000ULL);\n+\tif (coarse \u003e= (1ULL \u003c\u003c SIT9531X_OUT_PRG_COARSE_BITS))\n+\t\treturn -ERANGE;\n+\n+\t/* Fine delay = round((abs_ps - coarse * vco_period_ps) / 30 ps) */\n+\tcoarse_ps = mul_u64_u64_div_u64(coarse, 1000000000000ULL, fvco);\n+\trem_ps = (abs_ps \u003e coarse_ps) ? (abs_ps - coarse_ps) : 0;\n+\tif (rem_ps) {\n+\t\tu64 steps;\n+\n+\t\tsteps = div64_u64(rem_ps + SIT9531X_OUT_PRG_FINE_STEP_PS / 2,\n+\t\t\t\t SIT9531X_OUT_PRG_FINE_STEP_PS);\n+\t\tif (steps \u003e SIT9531X_OUT_PRG_FINE_MAX)\n+\t\t\tsteps = SIT9531X_OUT_PRG_FINE_MAX;\n+\t\tfine = (u8)steps;\n+\t}\n+\n+\t/*\n+\t * Map logical output index to the chip's physical output slot.\n+\t * On SiT95317 the eight logical outputs land on chip slots\n+\t * {0, 3, 4, 5, 7, 8, 9, 11}; on SiT95316 the map is identity.\n+\t * Page/base must address the slot, not the logical index.\n+\t */\n+\tslot = info-\u003eclkout_map[out_idx];\n+\tpage = (slot \u003e SIT9531X_PAGE_OUTSYS0_SLOT_MAX) ?\n+\t SIT9531X_PAGE_OUTSYS1 : SIT9531X_PAGE_OUTSYS0;\n+\tbase = SIT9531X_OUT_PRG_DELAY_BASE +\n+\t SIT9531X_OUT_PRG_SLOT_STRIDE * (slot % 6);\n+\n+\t/* Caller (dpll.c) holds multiop_lock around the whole sequence. */\n+\tlockdep_assert_held(\u0026sitdev-\u003emultiop_lock);\n+\n+\t/*\n+\t * The PRG_RST_DELAY bytes live in the output system, so the writes\n+\t * only take effect when made inside the PRG_CMD programming state and\n+\t * committed to the NVM shadow, exactly like sit9531x_output_freq_set().\n+\t */\n+\trc = sit9531x_prg_enter(sitdev);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* PROG6 RMW: preserve OPSTG_VCASC_BUMP in [7:5] */\n+\trc = sit9531x_read_u8(sitdev, SIT9531X_REG(page, base),\n+\t\t\t \u0026prog6_val);\n+\tif (rc)\n+\t\tgoto commit;\n+\n+\tprog6_val \u0026= SIT9531X_OUT_PRG_OPSTG_MASK;\n+\tprog6_val |= (fine \u003c\u003c SIT9531X_OUT_PRG_FINE_SHIFT) \u0026\n+\t\t SIT9531X_OUT_PRG_FINE_MASK;\n+\tprog6_val |= (u8)((coarse \u003e\u003e 32) \u0026 SIT9531X_OUT_PRG_COARSE_HI_MASK);\n+\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG(page, base),\n+\t\t\t prog6_val);\n+\tif (rc)\n+\t\tgoto commit;\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG(page, base + 1),\n+\t\t\t (u8)((coarse \u003e\u003e 24) \u0026 0xFF));\n+\tif (rc)\n+\t\tgoto commit;\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG(page, base + 2),\n+\t\t\t (u8)((coarse \u003e\u003e 16) \u0026 0xFF));\n+\tif (rc)\n+\t\tgoto commit;\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG(page, base + 3),\n+\t\t\t (u8)((coarse \u003e\u003e 8) \u0026 0xFF));\n+\tif (rc)\n+\t\tgoto commit;\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG(page, base + 4),\n+\t\t\t (u8)(coarse \u0026 0xFF));\n+\n+commit:\n+\t/*\n+\t * Always leave the PRG_CMD state via prg_commit(), even on a\n+\t * mid-sequence write failure, so the output loops are re-locked rather\n+\t * than stranded unlocked; keep the first error.\n+\t */\n+\tret = sit9531x_prg_commit(sitdev);\n+\tif (ret \u0026\u0026 !rc)\n+\t\trc = ret;\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/*\n+\t * Restart the output divider phase so the freshly programmed delay is\n+\t * applied against a known edge instead of the divider's arbitrary\n+\t * running phase.\n+\t */\n+\treturn sit9531x_output_phase_flush(sitdev, pll_idx);\n+}\n+\n+/*\n+ * sit9531x_clear_notifications - clear all notification registers\n+ *\n+ * Clears all write-1-to-clear notification registers:\n+ * - PLL outer LOL notification (Page 0, reg 0x07)\n+ * - PLL holdover freeze notification (Page 0, reg 0x0B)\n+ * - PLL inner LOL notification (Page 0, reg 0x93)\n+ * - Clock monitor XO/PLL notification (Page 0, reg 0x9E)\n+ * - Clock input notifications (Page 6, regs 0x03/0x07/0x93/0x97)\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ */\n+int sit9531x_clear_notifications(struct sit9531x_dev *sitdev)\n+{\n+\tint rc;\n+\n+\tlockdep_assert_held(\u0026sitdev-\u003emultiop_lock);\n+\n+\t/* Page 0x00 W1C notification registers */\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG_OUTER_LOL_NOTIF, 0xFF);\n+\tif (rc)\n+\t\treturn rc;\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG_HO_FREEZE_NOTIF, 0xFF);\n+\tif (rc)\n+\t\treturn rc;\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG_PLL_INNER_LOL_NOTIF, 0xFF);\n+\tif (rc)\n+\t\treturn rc;\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG_CMON_NOTIF, 0xFF);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* Page 0x06 clock input monitor notifications */\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_CLKMON_P_NOTIF_01, 0xFF);\n+\tif (rc)\n+\t\treturn rc;\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_CLKMON_P_NOTIF_23, 0xFF);\n+\tif (rc)\n+\t\treturn rc;\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_CLKMON_N_NOTIF_01, 0xFF);\n+\tif (rc)\n+\t\treturn rc;\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_CLKMON_N_NOTIF_23, 0xFF);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tdev_dbg(sitdev-\u003edev, \"All notification registers cleared\\n\");\n+\treturn 0;\n+}\n+\n+/*\n+ * INTSYNC configuration register values.\n+ * These are written to the source PLL's EXT page to enable/disable\n+ * inter-PLL synchronization (lock frequency PLL to phase PLL).\n+ */\n+struct sit9531x_intsync_reg {\n+\tu8 offset;\n+\tu8 en_val;\n+\tu8 dis_val;\n+};\n+\n+static const struct sit9531x_intsync_reg intsync_config[] = {\n+\t{ 0x2D, 0x02, 0x00 },\n+\t{ 0x50, 0x08, 0x00 },\n+\t{ 0x51, 0x04, 0x00 },\n+\t{ 0x54, 0x02, 0x00 },\n+\t{ 0x55, 0x28, 0x20 },\n+\t{ 0x5C, 0x0F, 0x00 },\n+\t{ 0x5D, 0xFF, 0x00 },\n+\t{ 0x6C, 0xDD, 0x00 },\n+};\n+\n+/*\n+ * sit9531x_intsync_enable - enable inter-PLL synchronization\n+ * @src_pll_idx: source (frequency) PLL index (0-3)\n+ *\n+ * Enables INTSYNC global bit, unlocks the source PLL's EXT page\n+ * debug registers, writes configuration, and triggers a small\n+ * update on the source PLL.\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ */\n+int sit9531x_intsync_enable(struct sit9531x_dev *sitdev, u8 src_pll_idx)\n+{\n+\tu8 ext_page, val;\n+\tint rc, i;\n+\n+\tlockdep_assert_held(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (src_pll_idx \u003e= SIT9531X_NUM_PLLS)\n+\t\treturn -EINVAL;\n+\n+\text_page = SIT9531X_PLL_EXT_PAGE(src_pll_idx);\n+\n+\trc = sit9531x_read_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL, \u0026val);\n+\tif (rc)\n+\t\treturn rc;\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL,\n+\t\t\t val | BIT(SIT9531X_INTSYNC_EN_BIT));\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* Small update on Page 0 */\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG_GLOBAL_UPDATE,\n+\t\t\t SIT9531X_SMALL_UPDATE_CMD);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* Unlock debug on EXT page */\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG(ext_page, SIT9531X_PLL_REG_DEBUG),\n+\t\t\t SIT9531X_PLL_DEBUG_UNLOCK);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(intsync_config); i++) {\n+\t\trc = sit9531x_write_u8(sitdev,\n+\t\t\t\t SIT9531X_REG(ext_page,\n+\t\t\t\t\t\t intsync_config[i].offset),\n+\t\t\t\t intsync_config[i].en_val);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\t}\n+\n+\t/* Small update on source PLL */\n+\trc = sit9531x_write_pll_u8(sitdev, src_pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_SMALL_UPDATE,\n+\t\t\t\t SIT9531X_SMALL_UPDATE_CMD);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_intsync_disable - disable inter-PLL synchronization\n+ * @src_pll_idx: source (frequency) PLL index (0-3)\n+ *\n+ * Clears INTSYNC global bit, writes disable values to the source\n+ * PLL's EXT page, and triggers a small update.\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ */\n+int sit9531x_intsync_disable(struct sit9531x_dev *sitdev, u8 src_pll_idx)\n+{\n+\tu8 ext_page, val;\n+\tint rc, i;\n+\n+\tlockdep_assert_held(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (src_pll_idx \u003e= SIT9531X_NUM_PLLS)\n+\t\treturn -EINVAL;\n+\n+\text_page = SIT9531X_PLL_EXT_PAGE(src_pll_idx);\n+\n+\trc = sit9531x_read_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL, \u0026val);\n+\tif (rc)\n+\t\treturn rc;\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL,\n+\t\t\t val \u0026 ~BIT(SIT9531X_INTSYNC_EN_BIT));\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* Small update on Page 0 */\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG_GLOBAL_UPDATE,\n+\t\t\t SIT9531X_SMALL_UPDATE_CMD);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* Unlock debug on EXT page */\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG(ext_page, SIT9531X_PLL_REG_DEBUG),\n+\t\t\t SIT9531X_PLL_DEBUG_UNLOCK);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(intsync_config); i++) {\n+\t\trc = sit9531x_write_u8(sitdev,\n+\t\t\t\t SIT9531X_REG(ext_page,\n+\t\t\t\t\t\t intsync_config[i].offset),\n+\t\t\t\t intsync_config[i].dis_val);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\t}\n+\n+\t/* Small update on source PLL */\n+\trc = sit9531x_write_pll_u8(sitdev, src_pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_SMALL_UPDATE,\n+\t\t\t\t SIT9531X_SMALL_UPDATE_CMD);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_output_pulse_ctrl_set - program per-output PULSE_CTRL byte\n+ * @out_idx:\tlogical output index (translated to chip slot internally)\n+ * @pulse_ctrl:\t8-bit PULSE_CTRL value (PROG0)\n+ *\n+ * Writes ODRn_PROG0 on the output page (Page 3 for slots 0..5,\n+ * Page 4 for slots 6..11) at offset 0x1B + 16 * (slot % 6).\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ */\n+int sit9531x_output_pulse_ctrl_set(struct sit9531x_dev *sitdev,\n+\t\t\t\t u8 out_idx, u8 pulse_ctrl)\n+{\n+\tconst struct sit9531x_chip_info *info = sitdev-\u003einfo;\n+\tu8 slot, page, reg;\n+\tint rc, ret;\n+\n+\tlockdep_assert_held(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (out_idx \u003e= info-\u003enum_outputs)\n+\t\treturn -EINVAL;\n+\n+\tslot = info-\u003eclkout_map[out_idx];\n+\tpage = (slot \u003e SIT9531X_PAGE_OUTSYS0_SLOT_MAX) ?\n+\t\tSIT9531X_PAGE_OUTSYS1 : SIT9531X_PAGE_OUTSYS0;\n+\treg = SIT9531X_OUT_PROG0_BASE +\n+\t SIT9531X_OUT_PRG_SLOT_STRIDE * (slot % 6);\n+\n+\t/*\n+\t * PROG0 lives in the output system, so like the DIVO and\n+\t * PRG_RST_DELAY writes it only takes effect inside the PRG_CMD\n+\t * programming state committed to the NVM shadow.\n+\t */\n+\trc = sit9531x_prg_enter(sitdev);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = sit9531x_write_u8(sitdev, SIT9531X_REG(page, reg), pulse_ctrl);\n+\n+\t/*\n+\t * Always leave the PRG_CMD state via prg_commit(), even if the write\n+\t * failed, so the output loops are re-locked rather than stranded\n+\t * unlocked; keep the first error.\n+\t */\n+\tret = sit9531x_prg_commit(sitdev);\n+\tif (ret \u0026\u0026 !rc)\n+\t\trc = ret;\n+\n+\treturn rc;\n+}\n+\n+/* Retries (at 1 s each) to wait for a PLL to reach its active state. */\n+#define SIT9531X_PLL_ACTIVE_RETRIES\t90\n+\n+/*\n+ * sit9531x_phase_offset_read - read phase difference via TDC\n+ * @phase_ps:\toutput phase difference in picoseconds\n+ *\n+ * Reads the Time-to-Digital Converter (TDC) 40-bit code from the\n+ * PLL page registers, then converts to picoseconds using the VCO\n+ * frequency: phase_diff = tdc_code / fvco.\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ */\n+int sit9531x_phase_offset_read(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t s64 *phase_ps)\n+{\n+\tu64 fvco, fvco_mhz;\n+\ts64 tdc_signed;\n+\tu64 tdc_raw;\n+\tint rc, i;\n+\tbool sign;\n+\tu8 v;\n+\n+\tlockdep_assert_held(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (pll_idx \u003e= SIT9531X_NUM_PLLS)\n+\t\treturn -EINVAL;\n+\n+\t/* Unlock the debug page so the TDC registers are accessible. */\n+\trc = sit9531x_write_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_DEBUG,\n+\t\t\t\t SIT9531X_PLL_DEBUG_UNLOCK);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/*\n+\t * Select the debug clock for taps below 200 kHz, then point the\n+\t * readback at the TDC. Only the one bit is touched: writing the\n+\t * modifier register whole would clear the fields belonging to\n+\t * other taps.\n+\t */\n+\trc = sit9531x_update_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_DBG_WRITE_CODE,\n+\t\t\t\t SIT9531X_DBG_LOW_FREQ_CLK_BIT,\n+\t\t\t\t SIT9531X_DBG_LOW_FREQ_CLK_BIT);\n+\tif (rc)\n+\t\treturn rc;\n+\trc = sit9531x_write_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_DBG_READ_CODE,\n+\t\t\t\t SIT9531X_DBG_READ_CODE_TDC);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* Latch a sample by reading the trigger register 3 times. */\n+\tfor (i = 0; i \u003c 3; i++) {\n+\t\trc = sit9531x_read_pll_u8(sitdev, pll_idx,\n+\t\t\t\t\t SIT9531X_PLL_REG_DBG_TRIGGER, \u0026v);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\t}\n+\n+\ttdc_raw = 0;\n+\n+\trc = sit9531x_read_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_DBG_DATA_4, \u0026v);\n+\tif (rc)\n+\t\treturn rc;\n+\tsign = !!(v \u0026 BIT(SIT9531X_TDC_SIGN_BIT));\n+\ttdc_raw = (u64)(v \u0026 0x07) \u003c\u003c 32;\n+\n+\trc = sit9531x_read_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_DBG_DATA_3, \u0026v);\n+\tif (rc)\n+\t\treturn rc;\n+\ttdc_raw |= (u64)v \u003c\u003c 24;\n+\n+\trc = sit9531x_read_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_DBG_DATA_2, \u0026v);\n+\tif (rc)\n+\t\treturn rc;\n+\ttdc_raw |= (u64)v \u003c\u003c 16;\n+\n+\trc = sit9531x_read_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_DBG_DATA_1, \u0026v);\n+\tif (rc)\n+\t\treturn rc;\n+\ttdc_raw |= (u64)v \u003c\u003c 8;\n+\n+\trc = sit9531x_read_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_DBG_DATA_0, \u0026v);\n+\tif (rc)\n+\t\treturn rc;\n+\ttdc_raw |= v;\n+\n+\t/*\n+\t * Apply sign. Per the vendor reference the sign bit is active-high\n+\t * for a positive offset: bit set -\u003e +code, bit clear -\u003e -code.\n+\t */\n+\ttdc_signed = sign ? (s64)tdc_raw : -(s64)tdc_raw;\n+\n+\t/*\n+\t * Get VCO frequency for conversion. Fvco==0 means DIVN is not\n+\t * programmed (PLL unused on this board) -- skip silently rather\n+\t * than spamming the log on every poll cycle.\n+\t */\n+\tfvco = sit9531x_get_fvco(sitdev, pll_idx);\n+\tif (!fvco) {\n+\t\tdev_dbg(sitdev-\u003edev, \"PLL%c: Fvco unknown, skip TDC\\n\",\n+\t\t\t'A' + pll_idx);\n+\t\treturn -ENODEV;\n+\t}\n+\n+\t/*\n+\t * phase_diff (seconds) = tdc_code / fvco\n+\t * phase_diff (ps) = tdc_code * 1e12 / fvco\n+\t *\n+\t * To avoid 64-bit overflow:\n+\t * phase_ps = tdc_code * 1e6 / (fvco / 1e6)\n+\t */\n+\tfvco_mhz = div64_u64(fvco, 1000000ULL);\n+\tif (!fvco_mhz)\n+\t\treturn -EIO;\n+\n+\t*phase_ps = div64_s64(tdc_signed * 1000000LL, (s64)fvco_mhz);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_ref_state_fetch - read input reference status from hardware\n+ * @index:\tlogical input index\n+ *\n+ * Reads LOS and OOF status for the given input lane from the Page 0x06\n+ * clock monitor registers. P and N lanes have separate register banks;\n+ * each register carries two input pairs nibble-packed (even pair in\n+ * bits [3:0], odd pair in [7:4]).\n+ */\n+static int sit9531x_ref_state_fetch(struct sit9531x_dev *sitdev, u8 index)\n+{\n+\tunsigned int reg, force_reg, state_reg;\n+\tu8 pair, status, force, state;\n+\tstruct sit9531x_ref *ref;\n+\tint rc;\n+\n+\t/*\n+\t * The XTAL/XO reference (index SIT9531X_MAX_INPUTS) is the on-chip\n+\t * oscillator that feeds every PLL. It cannot be routed or deselected,\n+\t * so its pin is modelled as permanently connected (see\n+\t * sit9531x_dpll_xo_pin_ops) and its LOS/OOF flags are never consulted.\n+\t * Only the routable per-lane inputs (0..num_inputs-1) are polled here.\n+\t */\n+\tif (index \u003e= SIT9531X_MAX_INPUTS)\n+\t\treturn -EINVAL;\n+\n+\tref = \u0026sitdev-\u003eref[index];\n+\tpair = sit9531x_input_pair(index);\n+\n+\tif (sit9531x_input_is_n(index))\n+\t\treg = pair \u003c 2 ? SIT9531X_CLKMON_N_STATUS_01\n+\t\t\t : SIT9531X_CLKMON_N_STATUS_23;\n+\telse\n+\t\treg = pair \u003c 2 ? SIT9531X_CLKMON_P_STATUS_01\n+\t\t\t : SIT9531X_CLKMON_P_STATUS_23;\n+\n+\trc = sit9531x_read_u8(sitdev, reg, \u0026status);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tif (pair \u0026 1)\n+\t\tstatus \u003e\u003e= 4;\n+\n+\tref-\u003elos = !!(status \u0026 (BIT(SIT9531X_CLKMON_CLK_LOSS) |\n+\t\t\t\tBIT(SIT9531X_CLKMON_CLK_LOSS_FD)));\n+\tref-\u003eoof = !!(status \u0026 (BIT(SIT9531X_CLKMON_FREQ_FINE) |\n+\t\t\t\tBIT(SIT9531X_CLKMON_FREQ_COARSE)));\n+\n+\t/*\n+\t * Whether the receiver is on. This has to come from the chip: it\n+\t * is the loaded configuration that decides, and without reading it\n+\t * back every input would look disabled until something called\n+\t * sit9531x_input_enable(). A lane counts as disabled only while\n+\t * the force bit overrides it to the off state; with the force bit\n+\t * clear it follows the configuration, which is the enabled case.\n+\t */\n+\tsit9531x_input_get_regs(sitdev, index, \u0026force_reg, \u0026state_reg);\n+\n+\trc = sit9531x_read_u8(sitdev, force_reg, \u0026force);\n+\tif (rc)\n+\t\treturn rc;\n+\trc = sit9531x_read_u8(sitdev, state_reg, \u0026state);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tref-\u003eenabled = !((force \u0026 BIT(pair)) \u0026\u0026 !(state \u0026 BIT(pair)));\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_input_mode_fetch - detect SE/DE configuration of an input pair\n+ * @pair:\tinput pair number (0-3)\n+ *\n+ * Reads CLKINx_INPUT_MODE and stores the detected signal mode on both\n+ * lanes of the pair. A pair with neither SE lane enabled is running\n+ * differential.\n+ */\n+static int sit9531x_input_mode_fetch(struct sit9531x_dev *sitdev, u8 pair)\n+{\n+\tenum sit9531x_signal_mode sig_mode;\n+\tu8 mode;\n+\tint rc;\n+\n+\trc = sit9531x_read_u8(sitdev, SIT9531X_REG_IN_MODE(pair), \u0026mode);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tif (mode \u0026 (SIT9531X_IN_MODE_SE_P_EN | SIT9531X_IN_MODE_SE_N_EN))\n+\t\tsig_mode = SIT9531X_MODE_SE;\n+\telse\n+\t\tsig_mode = SIT9531X_MODE_DE;\n+\n+\tsitdev-\u003eref[pair * 2].sig_mode = sig_mode;\n+\tsitdev-\u003eref[pair * 2 + 1].sig_mode = sig_mode;\n+\n+\tdev_dbg(sitdev-\u003edev, \"CLKIN%u mode reg 0x%02x -\u003e %s\\n\", pair, mode,\n+\t\tsig_mode == SIT9531X_MODE_DE ? \"differential\" : \"single-ended\");\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_chan_state_fetch - read PLL channel status from hardware\n+ *\n+ * Reads lock status and mode from the PLL status register.\n+ */\n+/* Read the PLL active-state bit (PLL page reg 0x02 bit 0). */\n+static int sit9531x_pll_is_active(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t\t bool *active)\n+{\n+\tu8 v;\n+\tint rc;\n+\n+\trc = sit9531x_read_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_ACTIVE, \u0026v);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t*active = !!(v \u0026 SIT9531X_PLL_ACTIVE_BIT);\n+\n+\treturn 0;\n+}\n+\n+static int sit9531x_chan_state_fetch(struct sit9531x_dev *sitdev, u8 pll_idx)\n+{\n+\tu8 status, outer_lol, input_sel, inner_lol, ho_freeze, activesel_reg;\n+\tstruct sit9531x_chan *chan = \u0026sitdev-\u003echan[pll_idx];\n+\tbool active;\n+\tint rc;\n+\n+\t/*\n+\t * Whether the PLL is running at all. The loss-of-lock bit read\n+\t * below is driven by the PLL itself, so on one the loaded\n+\t * configuration leaves unused it simply stays clear and would\n+\t * otherwise read as a lock.\n+\t */\n+\trc = sit9531x_pll_is_active(sitdev, pll_idx, \u0026active);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = sit9531x_read_pll_u8(sitdev, pll_idx,\n+\t\t\t\t SIT9531X_PLL_REG_STATUS, \u0026status);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = sit9531x_read_u8(sitdev, SIT9531X_REG_OUTER_LOL_STATUS,\n+\t\t\t \u0026outer_lol);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/*\n+\t * Read the input source the PLL has currently selected as its\n+\t * active reference. This lives in the low nibble of the last\n+\t * register of the PLL's page-1 priority block (CLK_ACTIVESEL_PLL),\n+\t * not on the PLL page -- PLL-page 0x29 is a config register.\n+\t */\n+\tactivesel_reg = SIT9531X_PRIO_BASE_REG +\n+\t\t\tSIT9531X_PRIO_REGS_PER_PLL * pll_idx +\n+\t\t\tSIT9531X_PRIO_ACTIVESEL_OFF;\n+\trc = sit9531x_read_u8(sitdev,\n+\t\t\t SIT9531X_REG(SIT9531X_PAGE_PRIOSYS,\n+\t\t\t\t\t activesel_reg),\n+\t\t\t \u0026input_sel);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = sit9531x_read_u8(sitdev, SIT9531X_REG_PLL_INNER_LOL_STATUS,\n+\t\t\t \u0026inner_lol);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = sit9531x_read_u8(sitdev, SIT9531X_REG_HO_FREEZE_STATUS, \u0026ho_freeze);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* STATUS_1_GENERIC reports loss of lock, so invert it. */\n+\tchan-\u003eactive = active;\n+\tchan-\u003elocked = active \u0026\u0026 !(outer_lol \u0026 BIT(pll_idx));\n+\tchan-\u003emode = !!(status \u0026 SIT9531X_PLL_STATUS_OUTER_DIS);\n+\tchan-\u003eselected_ref =\n+\t\tsit9531x_hw_src_input(input_sel \u0026 SIT9531X_PRIO_NIBBLE_MASK);\n+\tchan-\u003einner_lol = !!(inner_lol \u0026 BIT(pll_idx));\n+\tchan-\u003eho_freeze = !!(ho_freeze \u0026 BIT(pll_idx));\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_out_state_fetch - read output status from hardware\n+ *\n+ * Reads the output PLL association from the PLL page output map\n+ * registers into out-\u003erouted / out-\u003epll_idx, and the current drive\n+ * state from the Hi-Z force bits into out-\u003eenabled. The two are\n+ * separate: routing decides whether the output gets a DPLL pin at all,\n+ * while a muted but routed output keeps its pin and reports\n+ * DPLL_PIN_STATE_DISCONNECTED until it is un-muted.\n+ */\n+static int sit9531x_out_state_fetch(struct sit9531x_dev *sitdev, u8 index)\n+{\n+\tstruct sit9531x_out *out = \u0026sitdev-\u003eout[index];\n+\tu8 map_lo, map_hi, slot;\n+\tint pll_idx;\n+\tbool muted;\n+\tint rc;\n+\n+\tslot = sitdev-\u003einfo-\u003eclkout_map[index];\n+\n+\trc = sit9531x_output_forced_hiz(sitdev, slot, \u0026muted);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/*\n+\t * DT board-config override: the per-PLL OUTPUT_ENABLE bitmaps\n+\t * (0x27/0x28) do not unambiguously express output-\u003ePLL routing on\n+\t * every config (overlaps, and some outputs routed outside that\n+\t * path). When the board supplies an explicit map, trust it.\n+\t */\n+\tif (sitdev-\u003eout_pll_map_valid) {\n+\t\tu8 m = sitdev-\u003eout_pll_map[index];\n+\n+\t\tif (m \u003c SIT9531X_NUM_PLLS) {\n+\t\t\tout-\u003epll_idx = m;\n+\t\t\tout-\u003erouted = true;\n+\t\t\tout-\u003eenabled = !muted;\n+\t\t} else {\n+\t\t\tout-\u003epll_idx = 0;\n+\t\t\tout-\u003erouted = false;\n+\t\t\tout-\u003eenabled = false;\n+\t\t}\n+\t\treturn 0;\n+\t}\n+\n+\t/*\n+\t * The OUT_MAP_LO/HI bitmaps are indexed by the physical slot the\n+\t * output occupies on the chip, not by the driver's logical output\n+\t * index (translated above via the chip-info clkout_map[]: identity\n+\t * on SiT95316, non-contiguous on SiT95317).\n+\t *\n+\t * Determine which PLL drives this output by checking each PLL's\n+\t * output map registers (0x27 = slots 8-11, 0x28 = slots 0-7).\n+\t */\n+\tfor (pll_idx = 0; pll_idx \u003c SIT9531X_NUM_PLLS; pll_idx++) {\n+\t\trc = sit9531x_read_pll_u8(sitdev, pll_idx,\n+\t\t\t\t\t SIT9531X_PLL_REG_OUT_MAP_LO, \u0026map_lo);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\n+\t\trc = sit9531x_read_pll_u8(sitdev, pll_idx,\n+\t\t\t\t\t SIT9531X_PLL_REG_OUT_MAP_HI, \u0026map_hi);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\n+\t\tif (slot \u003c 8) {\n+\t\t\tif (map_lo \u0026 BIT(slot)) {\n+\t\t\t\tout-\u003epll_idx = pll_idx;\n+\t\t\t\tout-\u003erouted = true;\n+\t\t\t\tout-\u003eenabled = !muted;\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t} else {\n+\t\t\tif (map_hi \u0026 BIT(slot - 8)) {\n+\t\t\t\tout-\u003epll_idx = pll_idx;\n+\t\t\t\tout-\u003erouted = true;\n+\t\t\t\tout-\u003eenabled = !muted;\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t/* Output not mapped to any PLL */\n+\tout-\u003epll_idx = 0;\n+\tout-\u003erouted = false;\n+\tout-\u003eenabled = false;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_ref_pll_mask_fetch - seed the input-to-PLL usage masks\n+ *\n+ * ref-\u003epll_mask is the refcount the disconnect path uses to decide when\n+ * an input receiver may be powered down: the physical input is only\n+ * disabled once the last DPLL has released it. It therefore has to\n+ * start out matching the hardware. Without this pass every mask starts\n+ * at zero, and disconnecting an input from one DPLL drops the mask to\n+ * zero and disables a receiver the other DPLLs are still locked to.\n+ *\n+ * An input is counted for a PLL when it appears in that PLL's Page-1\n+ * priority table, which is exactly the condition the connect and\n+ * disconnect callbacks maintain. Sources that are not physical inputs\n+ * (OCXO, INTSYNC) and reserved codes are skipped.\n+ *\n+ * Caller must hold sitdev-\u003emultiop_lock.\n+ */\n+static int sit9531x_ref_pll_mask_fetch(struct sit9531x_dev *sitdev)\n+{\n+\tu8 srcs[SIT9531X_PRIO_MAX_SLOTS];\n+\tu8 pll_idx, slot, index;\n+\tint rc;\n+\n+\tfor (pll_idx = 0; pll_idx \u003c SIT9531X_NUM_PLLS; pll_idx++) {\n+\t\trc = sit9531x_prio_table_read(sitdev, pll_idx, srcs);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\n+\t\tfor (slot = 0; slot \u003c SIT9531X_PRIO_MAX_SLOTS; slot++) {\n+\t\t\tindex = sit9531x_hw_src_input(srcs[slot]);\n+\t\t\tif (index \u003e= sitdev-\u003einfo-\u003enum_inputs)\n+\t\t\t\tcontinue;\n+\n+\t\t\t/*\n+\t\t\t * On a differentially configured pair only the P lane\n+\t\t\t * has a DPLL pin, so that is the lane the connect and\n+\t\t\t * disconnect callbacks account for. Fold an N-lane\n+\t\t\t * table entry onto its P lane, otherwise the count\n+\t\t\t * would land on a lane nothing ever releases. The\n+\t\t\t * signalling mode is already known here:\n+\t\t\t * sit9531x_input_mode_fetch() runs first.\n+\t\t\t */\n+\t\t\tif (sit9531x_input_is_n(index) \u0026\u0026\n+\t\t\t sitdev-\u003eref[index].sig_mode == SIT9531X_MODE_DE)\n+\t\t\t\tindex--;\n+\n+\t\t\tsitdev-\u003eref[index].pll_mask |= BIT(pll_idx);\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_dev_state_fetch - read all hardware state at startup\n+ *\n+ * Called once during probe to populate the initial state cache.\n+ */\n+static int sit9531x_dev_state_fetch(struct sit9531x_dev *sitdev)\n+{\n+\tint rc;\n+\tu8 i;\n+\n+\t/* Detect SE/DE configuration before any per-lane access */\n+\tfor (i = 0; i \u003c sitdev-\u003einfo-\u003enum_inputs / 2; i++) {\n+\t\trc = sit9531x_input_mode_fetch(sitdev, i);\n+\t\tif (rc) {\n+\t\t\tdev_err(sitdev-\u003edev,\n+\t\t\t\t\"Failed to fetch CLKIN%u mode: %d\\n\", i, rc);\n+\t\t\treturn rc;\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i \u003c sitdev-\u003einfo-\u003enum_inputs; i++) {\n+\t\trc = sit9531x_ref_state_fetch(sitdev, i);\n+\t\tif (rc) {\n+\t\t\tdev_err(sitdev-\u003edev,\n+\t\t\t\t\"Failed to fetch input %u state: %d\\n\", i, rc);\n+\t\t\treturn rc;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * The priority-table read walks the Page-1 registers, so it runs\n+\t * with multiop_lock held like every other multi-register sequence.\n+\t * Nothing can race with it here -- the DPLLs are not registered and\n+\t * the monitor is not running yet -- but the page handling stays\n+\t * serialized the same way as at runtime.\n+\t */\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\trc = sit9531x_ref_pll_mask_fetch(sitdev);\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\tif (rc) {\n+\t\tdev_err(sitdev-\u003edev,\n+\t\t\t\"Failed to fetch input priority tables: %d\\n\", rc);\n+\t\treturn rc;\n+\t}\n+\n+\tfor (i = 0; i \u003c sitdev-\u003einfo-\u003enum_outputs; i++) {\n+\t\trc = sit9531x_out_state_fetch(sitdev, i);\n+\t\tif (rc) {\n+\t\t\tdev_err(sitdev-\u003edev,\n+\t\t\t\t\"Failed to fetch output %u state: %d\\n\", i, rc);\n+\t\t\treturn rc;\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i \u003c SIT9531X_NUM_PLLS; i++) {\n+\t\trc = sit9531x_chan_state_fetch(sitdev, i);\n+\t\tif (rc) {\n+\t\t\tdev_err(sitdev-\u003edev,\n+\t\t\t\t\"Failed to fetch PLL%c state: %d\\n\",\n+\t\t\t\t'A' + i, rc);\n+\t\t\treturn rc;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void sit9531x_dev_ref_states_update(struct sit9531x_dev *sitdev)\n+{\n+\tint i, rc;\n+\n+\tfor (i = 0; i \u003c sitdev-\u003einfo-\u003enum_inputs; i++) {\n+\t\trc = sit9531x_ref_state_fetch(sitdev, i);\n+\t\tif (rc)\n+\t\t\tdev_warn(sitdev-\u003edev,\n+\t\t\t\t \"Failed to get REF%u status: %d\\n\", i, rc);\n+\t}\n+}\n+\n+static void sit9531x_dev_chan_states_update(struct sit9531x_dev *sitdev)\n+{\n+\tint i, rc;\n+\n+\tfor (i = 0; i \u003c SIT9531X_NUM_PLLS; i++) {\n+\t\trc = sit9531x_chan_state_fetch(sitdev, i);\n+\t\tif (rc)\n+\t\t\tdev_warn(sitdev-\u003edev,\n+\t\t\t\t \"Failed to get PLL%c state: %d\\n\",\n+\t\t\t\t 'A' + i, rc);\n+\t}\n+}\n+\n+/*\n+ * sit9531x_dev_periodic_work - periodic hardware state polling\n+ * @work:\tkthread_work pointer\n+ *\n+ * Polls hardware state at SIT9531X_STATUS_POLL_MS intervals.\n+ * Updates reference and channel states, then delegates change\n+ * detection to sit9531x_dpll_changes_check() for each registered DPLL.\n+ */\n+static void sit9531x_dev_periodic_work(struct kthread_work *work)\n+{\n+\tstruct sit9531x_dev *sitdev = container_of(work, struct sit9531x_dev,\n+\t\t\t\t\t\t work.work);\n+\tstruct sit9531x_dpll *sitdpll;\n+\tint rc;\n+\n+\t/*\n+\t * Update the cached ref[]/chan[] arrays under multiop_lock so the\n+\t * fetches are serialized against the DPLL callbacks that read\n+\t * these fields and against the chip's page selector.\n+\t *\n+\t * The lock is then dropped before sit9531x_dpll_changes_check(),\n+\t * which calls dpll_pin_change_ntf() / dpll_device_change_ntf().\n+\t * Those notification helpers take DPLL-subsystem locks that are\n+\t * already held when our callbacks are invoked from netlink\n+\t * context, and nesting multiop_lock around them would invert the\n+\t * lock order. changes_check() reads the cache published above,\n+\t * which is already consistent.\n+\t */\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\tsit9531x_dev_ref_states_update(sitdev);\n+\tsit9531x_dev_chan_states_update(sitdev);\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\tlist_for_each_entry(sitdpll, \u0026sitdev-\u003edplls, list)\n+\t\tsit9531x_dpll_changes_check(sitdpll);\n+\n+\t/*\n+\t * Acknowledge the chip's notification latches after the tick has\n+\t * read and acted on them. Without this, the W1C bits remain set\n+\t * and -- on boards that wire INTRB -- the line stays asserted,\n+\t * re-firing the threaded handler back to back. The helper writes\n+\t * W1C bits across page 0 and page 6 and must run under\n+\t * multiop_lock to serialize the page selector against userspace\n+\t * dpll ops. Failure is non-fatal: status was already consumed\n+\t * for this tick and the next tick re-processes whatever stayed\n+\t * latched.\n+\t */\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\trc = sit9531x_clear_notifications(sitdev);\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\tif (rc)\n+\t\tdev_warn_ratelimited(sitdev-\u003edev,\n+\t\t\t\t \"Failed to clear notifications: %d\\n\",\n+\t\t\t\t rc);\n+\n+\t/* Run twice a second */\n+\tkthread_queue_delayed_work(sitdev-\u003ekworker, \u0026sitdev-\u003ework,\n+\t\t\t\t msecs_to_jiffies(SIT9531X_STATUS_POLL_MS));\n+}\n+\n+/*\n+ * sit9531x_irq_thread_fn - threaded IRQ handler for the chip's INTRB line\n+ *\n+ * Triggered when the chip asserts INTRB (and only when DT wires up the\n+ * client interrupt; absent property == handler never installed). The\n+ * action mirrors a periodic-work tick: queue an immediate run so status\n+ * registers are read and DPLL changes_check fires without waiting for\n+ * the next poll deadline. Polling continues to run as a fallback.\n+ */\n+static irqreturn_t sit9531x_irq_thread_fn(int irq, void *data)\n+{\n+\tstruct sit9531x_dev *sitdev = data;\n+\tint rc;\n+\n+\t/*\n+\t * Acknowledge the chip's notification latches from the threaded\n+\t * handler itself. With IRQF_ONESHOT the line is unmasked on\n+\t * return, so deferring the W1C clear to the async kworker would\n+\t * let a still-asserted INTRB re-fire immediately (interrupt storm).\n+\t * Clear here, then kick the poll worker to read state and run\n+\t * changes_check.\n+\t */\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\trc = sit9531x_clear_notifications(sitdev);\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\tif (rc)\n+\t\tdev_warn_ratelimited(sitdev-\u003edev,\n+\t\t\t\t \"IRQ: failed to clear notifications: %d\\n\",\n+\t\t\t\t rc);\n+\n+\tkthread_mod_delayed_work(sitdev-\u003ekworker, \u0026sitdev-\u003ework, 0);\n+\treturn IRQ_HANDLED;\n+}\n+\n+/*\n+ * sit9531x_dev_start - start normal operation\n+ *\n+ * Fetches initial hardware state, registers all DPLL devices and\n+ * their pins, and starts the periodic monitoring thread.\n+ */\n+int sit9531x_dev_start(struct sit9531x_dev *sitdev)\n+{\n+\tstruct sit9531x_dpll *sitdpll;\n+\tint rc;\n+\n+\t/* Fetch device state */\n+\trc = sit9531x_dev_state_fetch(sitdev);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tlist_for_each_entry(sitdpll, \u0026sitdev-\u003edplls, list) {\n+\t\trc = sit9531x_dpll_register(sitdpll);\n+\t\tif (rc) {\n+\t\t\tdev_err_probe(sitdev-\u003edev, rc,\n+\t\t\t\t \"Failed to register DPLL%u\\n\",\n+\t\t\t\t sitdpll-\u003eid);\n+\t\t\treturn rc;\n+\t\t}\n+\t}\n+\n+\tkthread_queue_delayed_work(sitdev-\u003ekworker, \u0026sitdev-\u003ework, 0);\n+\n+\t/* Development/test knobs (best-effort) */\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_dev_stop - stop normal operation\n+ *\n+ * Cancels the monitoring thread and unregisters all DPLL devices\n+ * and their pins.\n+ */\n+void sit9531x_dev_stop(struct sit9531x_dev *sitdev)\n+{\n+\tstruct sit9531x_dpll *sitdpll;\n+\n+\tkthread_cancel_delayed_work_sync(\u0026sitdev-\u003ework);\n+\n+\tlist_for_each_entry(sitdpll, \u0026sitdev-\u003edplls, list) {\n+\t\tif (sitdpll-\u003edpll_dev)\n+\t\t\tsit9531x_dpll_unregister(sitdpll);\n+\t}\n+}\n+\n+static struct sit9531x_dpll_pin *\n+sit9531x_dpll_pin_alloc(struct sit9531x_dpll *sitdpll,\n+\t\t\tenum dpll_pin_direction dir, u8 id)\n+{\n+\tstruct sit9531x_dpll_pin *pin;\n+\n+\tpin = kzalloc_obj(*pin, GFP_KERNEL);\n+\tif (!pin)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tpin-\u003edpll = sitdpll;\n+\tpin-\u003edir = dir;\n+\tpin-\u003eid = id;\n+\n+\treturn pin;\n+}\n+\n+/*\n+ * sit9531x_dpll_pin_register - register a DPLL pin with the subsystem\n+ * @index:\tabsolute pin index for clock_id namespace\n+ *\n+ * Gets pin properties from firmware, creates or gets a dpll_pin,\n+ * and registers it with the parent DPLL device.\n+ */\n+static int sit9531x_dpll_pin_register(struct sit9531x_dpll_pin *pin,\n+\t\t\t\t u32 index)\n+{\n+\tstruct sit9531x_dpll *sitdpll = pin-\u003edpll;\n+\tstruct sit9531x_pin_props *props;\n+\tconst struct dpll_pin_ops *ops;\n+\tint rc;\n+\n+\tprops = sit9531x_pin_props_get(sitdpll-\u003edev, pin-\u003edir, pin-\u003eid);\n+\tif (IS_ERR(props))\n+\t\treturn PTR_ERR(props);\n+\n+\tstrscpy(pin-\u003elabel, props-\u003epackage_label, sizeof(pin-\u003elabel));\n+\tpin-\u003efwnode = fwnode_handle_get(props-\u003efwnode);\n+\tpin-\u003eesync_control = props-\u003eesync_control;\n+\n+\tpin-\u003edpll_pin = dpll_pin_get(sitdpll-\u003edev-\u003eclock_id, index,\n+\t\t\t\t THIS_MODULE, \u0026props-\u003edpll_props,\n+\t\t\t\t \u0026pin-\u003etracker);\n+\tif (IS_ERR(pin-\u003edpll_pin)) {\n+\t\trc = PTR_ERR(pin-\u003edpll_pin);\n+\t\tgoto err_pin_get;\n+\t}\n+\tdpll_pin_fwnode_set(pin-\u003edpll_pin, props-\u003efwnode);\n+\n+\tops = sit9531x_dpll_pin_ops_get(pin);\n+\n+\trc = dpll_pin_register(sitdpll-\u003edpll_dev, pin-\u003edpll_pin, ops, pin);\n+\tif (rc)\n+\t\tgoto err_register;\n+\n+\tsit9531x_pin_props_put(props);\n+\n+\treturn 0;\n+\n+err_register:\n+\tdpll_pin_put(pin-\u003edpll_pin, \u0026pin-\u003etracker);\n+err_pin_get:\n+\t/* dpll_pin_get() left an ERR_PTR here. */\n+\tpin-\u003edpll_pin = NULL;\n+\tfwnode_handle_put(pin-\u003efwnode);\n+\tpin-\u003efwnode = NULL;\n+\tsit9531x_pin_props_put(props);\n+\n+\treturn rc;\n+}\n+\n+static void sit9531x_dpll_pin_unregister(struct sit9531x_dpll_pin *pin)\n+{\n+\tstruct sit9531x_dpll *sitdpll = pin-\u003edpll;\n+\tconst struct dpll_pin_ops *ops;\n+\n+\tops = sit9531x_dpll_pin_ops_get(pin);\n+\n+\tdpll_pin_unregister(sitdpll-\u003edpll_dev, pin-\u003edpll_pin, ops, pin);\n+\tdpll_pin_put(pin-\u003edpll_pin, \u0026pin-\u003etracker);\n+\tpin-\u003edpll_pin = NULL;\n+\n+\tfwnode_handle_put(pin-\u003efwnode);\n+\tpin-\u003efwnode = NULL;\n+}\n+\n+static void sit9531x_dpll_pins_unregister(struct sit9531x_dpll *sitdpll)\n+{\n+\tstruct sit9531x_dpll_pin *pin, *next;\n+\n+\tlist_for_each_entry_safe(pin, next, \u0026sitdpll-\u003epins, list) {\n+\t\tsit9531x_dpll_pin_unregister(pin);\n+\t\tlist_del(\u0026pin-\u003elist);\n+\t\tkfree(pin);\n+\t}\n+}\n+\n+/*\n+ * sit9531x_input_pin_is_registrable - check if an input pin is registrable\n+ *\n+ * Split out so input-model changes stay local to this helper.\n+ *\n+ * Return: true if the input pin should be registered, false otherwise\n+ */\n+static bool sit9531x_input_pin_is_registrable(struct sit9531x_dev *sitdev,\n+\t\t\t\t\t u8 index)\n+{\n+\tif (index \u003e= sitdev-\u003einfo-\u003enum_inputs)\n+\t\treturn false;\n+\n+\t/*\n+\t * The N lane of a differentially-configured pair is not a\n+\t * standalone input and is skipped (zl3073x model).\n+\t */\n+\tif (sit9531x_input_is_n(index) \u0026\u0026\n+\t sitdev-\u003eref[index].sig_mode == SIT9531X_MODE_DE)\n+\t\treturn false;\n+\n+\treturn true;\n+}\n+\n+/*\n+ * sit9531x_dpll_pin_is_registrable - check if a pin should be registered\n+ * @dir:\tpin direction\n+ * @index:\tpin hardware index\n+ *\n+ * For input pins: delegate to sit9531x_input_pin_is_registrable().\n+ * For output pins: the pin is registrable if this DPLL is routed to it,\n+ * whether or not it is currently driving.\n+ *\n+ * Return: true if pin should be registered, false otherwise\n+ */\n+static bool sit9531x_dpll_pin_is_registrable(struct sit9531x_dpll *sitdpll,\n+\t\t\t\t\t enum dpll_pin_direction dir,\n+\t\t\t\t\t u8 index)\n+{\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\n+\tif (dir == DPLL_PIN_DIRECTION_INPUT) {\n+\t\t/* The internal INTSYNC and XO pins are always registrable */\n+\t\tif (index == SIT9531X_INTSYNC_PIN_ID ||\n+\t\t index == SIT9531X_MAX_INPUTS)\n+\t\t\treturn true;\n+\n+\t\treturn sit9531x_input_pin_is_registrable(sitdev, index);\n+\t}\n+\n+\t/* The internal INTSYNC source pin is always registrable */\n+\tif (index == SIT9531X_INTSYNC_OUT_PIN_ID)\n+\t\treturn true;\n+\n+\t/* Output -- check if driven by this DPLL */\n+\tif (index \u003e= sitdev-\u003einfo-\u003enum_outputs)\n+\t\treturn false;\n+\n+\t/*\n+\t * Routing, not the current drive state: an output the initial\n+\t * configuration muted still gets a pin, so userspace can see it and\n+\t * un-mute it. Only outputs no PLL drives are left unregistered.\n+\t */\n+\treturn sitdev-\u003eout[index].pll_idx == sitdpll-\u003eid \u0026\u0026\n+\t sitdev-\u003eout[index].routed;\n+}\n+\n+/*\n+ * sit9531x_dpll_pins_register - register all registrable pins\n+ *\n+ * Enumerates all possible input and output pins, checks registrability,\n+ * and registers each one. Input pins come first, then output pins,\n+ * with input pins first, then output pins.\n+ */\n+static int sit9531x_dpll_pins_register(struct sit9531x_dpll *sitdpll)\n+{\n+\tstruct sit9531x_dpll_pin *pin;\n+\tenum dpll_pin_direction dir;\n+\tu8 id, index;\n+\tint rc;\n+\n+\tfor (index = 0; index \u003c SIT9531X_NUM_PINS_TOTAL; index++) {\n+\t\tif (index \u003c SIT9531X_NUM_INPUT_PINS) {\n+\t\t\tid = index;\n+\t\t\tdir = DPLL_PIN_DIRECTION_INPUT;\n+\t\t} else {\n+\t\t\tid = index - SIT9531X_NUM_INPUT_PINS;\n+\t\t\tdir = DPLL_PIN_DIRECTION_OUTPUT;\n+\t\t}\n+\n+\t\tif (!sit9531x_dpll_pin_is_registrable(sitdpll, dir, id))\n+\t\t\tcontinue;\n+\n+\t\tpin = sit9531x_dpll_pin_alloc(sitdpll, dir, id);\n+\t\tif (IS_ERR(pin)) {\n+\t\t\trc = PTR_ERR(pin);\n+\t\t\tgoto error;\n+\t\t}\n+\n+\t\trc = sit9531x_dpll_pin_register(pin, index);\n+\t\tif (rc) {\n+\t\t\tkfree(pin);\n+\t\t\tgoto error;\n+\t\t}\n+\n+\t\tlist_add(\u0026pin-\u003elist, \u0026sitdpll-\u003epins);\n+\t}\n+\n+\treturn 0;\n+\n+error:\n+\tsit9531x_dpll_pins_unregister(sitdpll);\n+\treturn rc;\n+}\n+\n+static int sit9531x_dpll_device_register(struct sit9531x_dpll *sitdpll)\n+{\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tint rc;\n+\n+\tsitdpll-\u003eops = sit9531x_dpll_device_ops;\n+\n+\tsitdpll-\u003edpll_dev = dpll_device_get(sitdev-\u003eclock_id, sitdpll-\u003eid,\n+\t\t\t\t\t THIS_MODULE, \u0026sitdpll-\u003etracker);\n+\tif (IS_ERR(sitdpll-\u003edpll_dev)) {\n+\t\trc = PTR_ERR(sitdpll-\u003edpll_dev);\n+\t\tsitdpll-\u003edpll_dev = NULL;\n+\t\treturn rc;\n+\t}\n+\n+\trc = dpll_device_register(sitdpll-\u003edpll_dev,\n+\t\t\t\t sit9531x_prop_dpll_type_get(sitdev,\n+\t\t\t\t\t\t\t sitdpll-\u003eid),\n+\t\t\t\t \u0026sitdpll-\u003eops, sitdpll);\n+\tif (rc) {\n+\t\tdpll_device_put(sitdpll-\u003edpll_dev, \u0026sitdpll-\u003etracker);\n+\t\tsitdpll-\u003edpll_dev = NULL;\n+\t}\n+\n+\treturn rc;\n+}\n+\n+static void sit9531x_dpll_device_unregister(struct sit9531x_dpll *sitdpll)\n+{\n+\tdpll_device_unregister(sitdpll-\u003edpll_dev, \u0026sitdpll-\u003eops, sitdpll);\n+\tdpll_device_put(sitdpll-\u003edpll_dev, \u0026sitdpll-\u003etracker);\n+\tsitdpll-\u003edpll_dev = NULL;\n+}\n+\n+/*\n+ * sit9531x_dpll_alloc - allocate a DPLL device structure\n+ * @sitdev:\tparent device\n+ * @ch:\t\tPLL channel number (0-3)\n+ *\n+ * Return: pointer to allocated DPLL on success, error pointer on error\n+ */\n+struct sit9531x_dpll *sit9531x_dpll_alloc(struct sit9531x_dev *sitdev, u8 ch)\n+{\n+\tstruct sit9531x_dpll *sitdpll;\n+\n+\tsitdpll = kzalloc_obj(*sitdpll, GFP_KERNEL);\n+\tif (!sitdpll)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tsitdpll-\u003edev = sitdev;\n+\tsitdpll-\u003eid = ch;\n+\tsitdpll-\u003elock_status = DPLL_LOCK_STATUS_UNLOCKED;\n+\tINIT_LIST_HEAD(\u0026sitdpll-\u003epins);\n+\n+\treturn sitdpll;\n+}\n+\n+/*\n+ * sit9531x_dpll_free - deallocate a DPLL device structure\n+ * @sitdpll:\tDPLL to free\n+ */\n+void sit9531x_dpll_free(struct sit9531x_dpll *sitdpll)\n+{\n+\tkfree(sitdpll);\n+}\n+\n+/*\n+ * sit9531x_dpll_register - register DPLL device and all its pins\n+ *\n+ * Registers the DPLL device with the subsystem and then registers\n+ * all input and output pins that are connected to this PLL.\n+ */\n+int sit9531x_dpll_register(struct sit9531x_dpll *sitdpll)\n+{\n+\tint rc;\n+\n+\trc = sit9531x_dpll_device_register(sitdpll);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = sit9531x_dpll_pins_register(sitdpll);\n+\tif (rc) {\n+\t\tsit9531x_dpll_device_unregister(sitdpll);\n+\t\treturn rc;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* sit9531x_dpll_unregister - unregister DPLL device and its pins */\n+void sit9531x_dpll_unregister(struct sit9531x_dpll *sitdpll)\n+{\n+\tsit9531x_dpll_pins_unregister(sitdpll);\n+\tsit9531x_dpll_device_unregister(sitdpll);\n+}\n+\n+static void sit9531x_dpll_list_free(struct sit9531x_dev *sitdev)\n+{\n+\tstruct sit9531x_dpll *sitdpll, *next;\n+\n+\tlist_for_each_entry_safe(sitdpll, next, \u0026sitdev-\u003edplls, list) {\n+\t\tlist_del(\u0026sitdpll-\u003elist);\n+\t\tsit9531x_dpll_free(sitdpll);\n+\t}\n+}\n+\n+/* Runs only once the device is fully started, see the caller. */\n+static void sit9531x_dev_dpll_fini(void *ptr)\n+{\n+\tstruct sit9531x_dev *sitdev = ptr;\n+\n+\tsit9531x_dev_stop(sitdev);\n+\tkthread_destroy_worker(sitdev-\u003ekworker);\n+\tsit9531x_dpll_list_free(sitdev);\n+}\n+\n+static int sit9531x_devm_dpll_init(struct sit9531x_dev *sitdev)\n+{\n+\tstruct kthread_worker *kworker;\n+\tstruct sit9531x_dpll *sitdpll;\n+\tunsigned int i;\n+\tint rc;\n+\n+\tINIT_LIST_HEAD(\u0026sitdev-\u003edplls);\n+\tkthread_init_delayed_work(\u0026sitdev-\u003ework, sit9531x_dev_periodic_work);\n+\n+\tfor (i = 0; i \u003c SIT9531X_NUM_PLLS; i++) {\n+\t\tsitdpll = sit9531x_dpll_alloc(sitdev, i);\n+\t\tif (IS_ERR(sitdpll)) {\n+\t\t\trc = dev_err_probe(sitdev-\u003edev, PTR_ERR(sitdpll),\n+\t\t\t\t\t \"Failed to alloc DPLL%u\\n\", i);\n+\t\t\tgoto err_free_dplls;\n+\t\t}\n+\n+\t\tlist_add_tail(\u0026sitdpll-\u003elist, \u0026sitdev-\u003edplls);\n+\t}\n+\n+\tkworker = kthread_run_worker(0, \"sit9531x-%s\", dev_name(sitdev-\u003edev));\n+\tif (IS_ERR(kworker)) {\n+\t\trc = PTR_ERR(kworker);\n+\t\tgoto err_free_dplls;\n+\t}\n+\tsitdev-\u003ekworker = kworker;\n+\n+\trc = sit9531x_dev_start(sitdev);\n+\tif (rc) {\n+\t\trc = dev_err_probe(sitdev-\u003edev, rc, \"Failed to start device\\n\");\n+\t\tgoto err_destroy_worker;\n+\t}\n+\n+\t/*\n+\t * Only now is every field the cleanup touches valid, so this is the\n+\t * first point at which the action may be registered. On failure it\n+\t * runs the action itself, which is correct here and only here.\n+\t */\n+\treturn devm_add_action_or_reset(sitdev-\u003edev, sit9531x_dev_dpll_fini,\n+\t\t\t\t\tsitdev);\n+\n+err_destroy_worker:\n+\tkthread_destroy_worker(sitdev-\u003ekworker);\n+err_free_dplls:\n+\tsit9531x_dpll_list_free(sitdev);\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * sit9531x_read_variant_id - read chip variant ID byte from hardware\n+ * @id:\t\toutput variant ID byte\n+ *\n+ * Reads the single-byte variant identification register from Page 0\n+ * reg 0x02 (95317 = 0x17, 95316 = 0x31). Reg 0x03 holds a separate\n+ * revision byte and is intentionally not consumed here.\n+ */\n+static int sit9531x_read_variant_id(struct sit9531x_dev *sitdev, u8 *id)\n+{\n+\treturn sit9531x_read_u8(sitdev, SIT9531X_REG_VARIANT_ID, id);\n+}\n+\n+static const struct sit9531x_chip_info *sit9531x_match_variant(u8 id)\n+{\n+\tunsigned int i;\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(sit9531x_chip_ids); i++) {\n+\t\tif (sit9531x_chip_ids[i].id == id)\n+\t\t\treturn \u0026sit9531x_chip_ids[i];\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+/*\n+ * sit9531x_derive_clock_id - build EUI-64 clock identifier\n+ *\n+ * Generates a deterministic 64-bit identifier from the SiTime OUI,\n+ * the chip ID, and the I2C address. This provides a stable clock_id\n+ * across reboots.\n+ *\n+ * Return: 64-bit clock identifier\n+ */\n+static u64 sit9531x_derive_clock_id(struct sit9531x_dev *sitdev)\n+{\n+\tu64 clkid;\n+\n+\tclkid = SIT9531X_OUI \u003c\u003c 24;\n+\tclkid |= (u64)sitdev-\u003einfo-\u003eid \u003c\u003c 8;\n+\tclkid |= (u64)sitdev-\u003eclient-\u003eaddr;\n+\n+\treturn clkid;\n+}\n+\n+/*\n+ * Board-config overrides for fixed efuse/blob routing the chip registers do\n+ * not describe unambiguously. Absent properties leave pll_fvco[] zeroed\n+ * (derive from DIVN) and out_pll_map_valid false (use the OUT_MAP registers).\n+ */\n+static void sit9531x_parse_board_config(struct sit9531x_dev *sitdev)\n+{\n+\tu32 map[SIT9531X_MAX_OUTPUTS];\n+\tint n, i;\n+\n+\tdevice_property_read_u64_array(sitdev-\u003edev, \"sitime,pll-fvco\",\n+\t\t\t\t sitdev-\u003epll_fvco, SIT9531X_NUM_PLLS);\n+\n+\tif (!device_property_present(sitdev-\u003edev, \"sitime,output-pll-map\"))\n+\t\treturn;\n+\n+\t/*\n+\t * Any 1..MAX_OUTPUTS length is accepted so the 8-output SiT95317 need\n+\t * not pad to 12; variant detection has not run yet and entries past\n+\t * the detected num_outputs are never indexed. Trailing entries of a\n+\t * short map must read as unmapped rather than 0 (== PLLA), which\n+\t * would mark unrouted outputs active in sit9531x_out_state_fetch().\n+\t */\n+\tmemset(sitdev-\u003eout_pll_map, SIT9531X_OUT_PLL_UNMAPPED,\n+\t sizeof(sitdev-\u003eout_pll_map));\n+\n+\tn = device_property_count_u32(sitdev-\u003edev, \"sitime,output-pll-map\");\n+\tif (n \u003c= 0 || n \u003e SIT9531X_MAX_OUTPUTS ||\n+\t device_property_read_u32_array(sitdev-\u003edev, \"sitime,output-pll-map\",\n+\t\t\t\t\t map, n))\n+\t\treturn;\n+\n+\tfor (i = 0; i \u003c n; i++)\n+\t\tsitdev-\u003eout_pll_map[i] = map[i];\n+\tsitdev-\u003eout_pll_map_valid = true;\n+}\n+\n+int sit9531x_dev_probe(struct sit9531x_dev *sitdev)\n+{\n+\tstruct clk *xtal_clk;\n+\tu8 variant_id;\n+\tint rc;\n+\n+\t/*\n+\t * Fvco = Fref * (DIVN + frac/2^32) with Fref = xtal_freq \u003c\u003c doubler,\n+\t * so every freq_set and phase_adjust path divides by a rate derived\n+\t * from the XO feeding XIN/XO_CLK.\n+\t */\n+\txtal_clk = devm_clk_get_enabled(sitdev-\u003edev, \"xtal\");\n+\tif (IS_ERR(xtal_clk))\n+\t\treturn dev_err_probe(sitdev-\u003edev, PTR_ERR(xtal_clk),\n+\t\t\t\t \"Failed to get xtal clock\\n\");\n+\tsitdev-\u003extal_freq = clk_get_rate(xtal_clk);\n+\tif (!sitdev-\u003extal_freq)\n+\t\treturn dev_err_probe(sitdev-\u003edev, -EINVAL,\n+\t\t\t\t \"xtal clock has no rate\\n\");\n+\n+\t/*\n+\t * Held deasserted, never pulsed: the chip configuration comes from\n+\t * efuse or an NVM blob applied before probe, and a reset would\n+\t * discard it. Must precede the first I2C access, as a board that\n+\t * powers up asserted keeps the chip unreachable until released.\n+\t */\n+\tsitdev-\u003ereset_gpio = devm_gpiod_get_optional(sitdev-\u003edev, \"reset\",\n+\t\t\t\t\t\t GPIOD_OUT_LOW);\n+\tif (IS_ERR(sitdev-\u003ereset_gpio))\n+\t\treturn dev_err_probe(sitdev-\u003edev, PTR_ERR(sitdev-\u003ereset_gpio),\n+\t\t\t\t \"Failed to request reset gpio\\n\");\n+\tif (sitdev-\u003ereset_gpio)\n+\t\tfsleep(10000);\t/* internal boot after release */\n+\n+\tsit9531x_parse_board_config(sitdev);\n+\n+\trc = sit9531x_read_variant_id(sitdev, \u0026variant_id);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tsitdev-\u003einfo = sit9531x_match_variant(variant_id);\n+\tif (!sitdev-\u003einfo)\n+\t\treturn dev_err_probe(sitdev-\u003edev, -ENODEV,\n+\t\t\t\t \"Unknown variant ID: 0x%02x\\n\", variant_id);\n+\n+\tsitdev-\u003eclock_id = sit9531x_derive_clock_id(sitdev);\n+\tsitdev-\u003eintsync_src = -1;\n+\n+\trc = devm_mutex_init(sitdev-\u003edev, \u0026sitdev-\u003emultiop_lock);\n+\tif (rc)\n+\t\treturn dev_err_probe(sitdev-\u003edev, rc,\n+\t\t\t\t \"Failed to initialize mutex\\n\");\n+\n+\t/*\n+\t * Before the IRQ: the handler reaches sitdev-\u003ekworker through\n+\t * kthread_mod_delayed_work(), so the worker has to exist before an\n+\t * INTRB assertion can land.\n+\t */\n+\trc = sit9531x_devm_dpll_init(sitdev);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* Absent \"interrupts\" leaves client-\u003eirq 0 and the poll in charge. */\n+\tsitdev-\u003eirq = sitdev-\u003eclient ? sitdev-\u003eclient-\u003eirq : 0;\n+\tif (sitdev-\u003eirq \u003e 0) {\n+\t\trc = devm_request_threaded_irq(sitdev-\u003edev, sitdev-\u003eirq,\n+\t\t\t\t\t NULL, sit9531x_irq_thread_fn,\n+\t\t\t\t\t IRQF_ONESHOT,\n+\t\t\t\t\t dev_name(sitdev-\u003edev), sitdev);\n+\t\tif (rc)\n+\t\t\treturn dev_err_probe(sitdev-\u003edev, rc,\n+\t\t\t\t\t \"Failed to request IRQ %d\\n\",\n+\t\t\t\t\t sitdev-\u003eirq);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int sit9531x_i2c_probe(struct i2c_client *client)\n+{\n+\tstruct sit9531x_dev *sitdev;\n+\tstruct regmap *regmap;\n+\n+\tregmap = devm_regmap_init_i2c(client, \u0026sit9531x_regmap_config);\n+\tif (IS_ERR(regmap))\n+\t\treturn dev_err_probe(\u0026client-\u003edev, PTR_ERR(regmap),\n+\t\t\t\t \"Failed to initialize regmap\\n\");\n+\n+\tsitdev = devm_kzalloc(\u0026client-\u003edev, sizeof(*sitdev), GFP_KERNEL);\n+\tif (!sitdev)\n+\t\treturn -ENOMEM;\n+\n+\tsitdev-\u003edev = \u0026client-\u003edev;\n+\tsitdev-\u003eclient = client;\n+\tsitdev-\u003eregmap = regmap;\n+\ti2c_set_clientdata(client, sitdev);\n+\n+\treturn sit9531x_dev_probe(sitdev);\n+}\n+\n+static const struct of_device_id sit9531x_of_match[] = {\n+\t{ .compatible = \"sitime,sit95316\" },\n+\t{ .compatible = \"sitime,sit95317\" },\n+\t{ }\n+};\n+MODULE_DEVICE_TABLE(of, sit9531x_of_match);\n+\n+static struct i2c_driver sit9531x_i2c_driver = {\n+\t.driver = {\n+\t\t.name\t\t= \"sit9531x\",\n+\t\t.of_match_table\t= sit9531x_of_match,\n+\t},\n+\t.probe\t\t= sit9531x_i2c_probe,\n+};\n+module_i2c_driver(sit9531x_i2c_driver);\n+\n+MODULE_AUTHOR(\"Ali Rouhi \u003carouhi@sitime.com\u003e\");\n+MODULE_AUTHOR(\"Oleg Zadorozhnyi \u003cOleg.Zadorozhnyi@devoxsoftware.com\u003e\");\n+MODULE_DESCRIPTION(\"SiTime SiT9531x DPLL subsystem driver\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/dpll/sit9531x/core.h b/drivers/dpll/sit9531x/core.h\nnew file mode 100644\nindex 0000000000000..05d4ee0c712ed\n--- /dev/null\n+++ b/drivers/dpll/sit9531x/core.h\n@@ -0,0 +1,372 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * SiTime SiT9531x DPLL core driver\n+ *\n+ * Copyright (C) 2026 SiTime Corp.\n+ * Author: Ali Rouhi \u003carouhi@sitime.com\u003e\n+ * Author: Oleg Zadorozhnyi \u003cOleg.Zadorozhnyi@devoxsoftware.com\u003e\n+ *\n+ * Device structure, register access helpers, and core function\n+ * declarations.\n+ */\n+\n+#ifndef _SIT9531X_CORE_H\n+#define _SIT9531X_CORE_H\n+\n+#include \u003clinux/gpio/consumer.h\u003e\n+#include \u003clinux/i2c.h\u003e\n+#include \u003clinux/kthread.h\u003e\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/regmap.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+#include \"regs.h\"\n+\n+#define SIT9531X_NUM_PLLS\t\t4\n+#define SIT9531X_MAX_INPUTS\t\t8\n+#define SIT9531X_NUM_INPUT_PAIRS\t(SIT9531X_MAX_INPUTS / 2)\n+#define SIT9531X_MAX_OUTPUTS\t\t12\n+/* out_pll_map[] entry meaning \"this output is not routed to any PLL\" */\n+#define SIT9531X_OUT_PLL_UNMAPPED\t0xFF\n+/*\n+ * INTSYNC (the inter-PLL sync net) is modelled as two pins. The\n+ * destination PLL that locks to INTSYNC sees an input pin\n+ * (SIT9531X_INTSYNC_PIN_ID, in the input id namespace after the physical\n+ * inputs and the xtal); the source PLL that drives INTSYNC sees an output\n+ * pin (SIT9531X_INTSYNC_OUT_PIN_ID, appended after the physical outputs).\n+ */\n+#define SIT9531X_INTSYNC_PIN_ID\t\t(SIT9531X_MAX_INPUTS + 1)\n+#define SIT9531X_INTSYNC_OUT_PIN_ID\tSIT9531X_MAX_OUTPUTS\n+#define SIT9531X_NUM_PINS\t\t(SIT9531X_MAX_INPUTS + 2 + SIT9531X_MAX_OUTPUTS + 1)\n+#define SIT9531X_STATUS_POLL_MS\t\t500\n+\n+/* selected_ref value when the active source is not a registered input */\n+#define SIT9531X_REF_INVALID\t\t0xFF\n+\n+/* SiTime IEEE OUI for EUI-64 generation */\n+#define SIT9531X_OUI\t\t\t0x0090C2FFFEULL\n+\n+struct sit9531x_dpll;\n+\n+/*\n+ * struct sit9531x_chip_info - chip variant identification\n+ * @id:\t\tvariant ID byte read from register\n+ * @num_inputs:\tnumber of input clock pins\n+ * @num_outputs: number of output clock pins\n+ * @name:\thuman-readable variant name\n+ * @clkout_map:\tper-output slot mapping (output index -\u003e physical slot)\n+ */\n+struct sit9531x_chip_info {\n+\tu8\t\tid;\n+\tu8\t\tnum_inputs;\n+\tu8\t\tnum_outputs;\n+\tconst char\t*name;\n+\tconst u8\t*clkout_map;\n+};\n+\n+/*\n+ * enum sit9531x_signal_mode - input signal electrical mode\n+ * @SIT9531X_MODE_SE: single-ended\n+ * @SIT9531X_MODE_DE: differential\n+ */\n+enum sit9531x_signal_mode {\n+\tSIT9531X_MODE_SE = 0,\n+\tSIT9531X_MODE_DE,\n+};\n+\n+/*\n+ * struct sit9531x_ref - input reference state\n+ * @freq:\t\tconfigured frequency in Hz\n+ * @enabled:\t\treference is enabled for monitoring\n+ * @los:\t\tloss-of-signal detected\n+ * @oof:\t\tout-of-frequency detected\n+ * @pll_mask:\t\tbitmask of PLLs this input feeds (bit 0 = PLLA)\n+ * @label:\t\tboard label from DT or default\n+ * @sig_mode:\t\tsignal mode of the pair this lane belongs to\n+ *\t\t\t(detected from CLKINx_INPUT_MODE at probe)\n+ */\n+struct sit9531x_ref {\n+\tu32\t\tfreq;\n+\tbool\t\tenabled;\n+\tbool\t\tlos;\n+\tbool\t\toof;\n+\tu8\t\tpll_mask;\n+\tconst char\t*label;\n+\tenum sit9531x_signal_mode\tsig_mode;\n+};\n+\n+/*\n+ * struct sit9531x_out - output state\n+ * @enabled:\t\toutput is driving, i.e. not forced into Hi-Z\n+ * @routed:\t\toutput is mapped to @pll_idx by the initial\n+ *\t\t\tconfiguration; an unrouted output has no DPLL pin\n+ * @pll_idx:\t\tPLL driving this output (0-3)\n+ * @label:\t\tboard label from DT or default\n+ */\n+struct sit9531x_out {\n+\tu32\t\tfreq;\n+\tbool\t\tenabled;\n+\tbool\t\trouted;\n+\tu8\t\tpll_idx;\n+\tconst char\t*label;\n+};\n+\n+/*\n+ * struct sit9531x_chan - per-PLL channel state\n+ * @active:\t\tPLL has reached its active state; a PLL the loaded\n+ *\t\t\tconfiguration leaves unused never does, and its\n+ *\t\t\tloss-of-lock bit stays clear because nothing drives it\n+ * @locked:\t\tPLL is locked (raw status register bit)\n+ * @mode:\t\t0 = sync (outer loop enabled), 1 = free-run\n+ * @selected_ref:\tlogical input index of the currently selected\n+ *\t\t\treference (the INTSYNC net maps to\n+ *\t\t\tSIT9531X_INTSYNC_PIN_ID), or SIT9531X_REF_INVALID\n+ *\t\t\twhen the hardware source encoding is reserved\n+ * @inner_lol:\t\tPLL inner loop loss-of-lock detected\n+ * @ho_freeze:\t\tholdover freeze active\n+ */\n+struct sit9531x_chan {\n+\tbool\t\tactive;\n+\tbool\t\tlocked;\n+\tu8\t\tmode;\n+\tu8\t\tselected_ref;\n+\tbool\t\tinner_lol;\n+\tbool\t\tho_freeze;\n+};\n+\n+/*\n+ * struct sit9531x_dev - SiT9531x device instance\n+ * @info:\t\tdetected chip variant info\n+ * @multiop_lock:\tmutex for multi-register atomic operations\n+ * @ref:\t\tarray of input reference states\n+ * @out:\t\tarray of output states\n+ * @chan:\t\tarray of per-PLL channel states\n+ * @xtal_freq:\t\tcrystal oscillator frequency in Hz\n+ * @kworker:\t\tkthread worker for periodic polling\n+ * @work:\t\tdelayed work for periodic state checks\n+ * @clock_id:\t\tIEEE 1588 EUI-64 clock identifier\n+ * @reset_gpio:\t\toptional reset line (DT \"reset-gpios\"), NULL if absent\n+ * @irq:\t\toptional INTRB IRQ number (from DT \"interrupts\" via the\n+ *\t\t\tI2C client), 0 if no IRQ is wired\n+ * @pll_fvco:\t\toptional per-PLL VCO in Hz from DT\n+ *\t\t\t\"sitime,pll-fvco\"; 0 means derive from DIVN\n+ * @out_pll_map:\toptional per-output source PLL (0-3, 0xff =\n+ *\t\t\tunmapped) from DT \"sitime,output-pll-map\"\n+ * @out_pll_map_valid:\ttrue when out_pll_map[] was populated from DT;\n+ *\t\t\tfalse means use the chip's OUT_MAP registers\n+ * @intsync_src:\tPLL index currently sourcing inter-PLL\n+ *\t\t\tsynchronization (INTSYNC), or -1 when disabled\n+ */\n+struct sit9531x_dev {\n+\tstruct device\t\t\t*dev;\n+\tstruct i2c_client\t\t*client;\n+\tstruct regmap\t\t\t*regmap;\n+\tconst struct sit9531x_chip_info\t*info;\n+\t/* Serializes multi-step register sequences */\n+\tstruct mutex\t\t\tmultiop_lock;\n+\n+\t/* Hardware state */\n+\tstruct sit9531x_ref\tref[SIT9531X_MAX_INPUTS + 1]; /* +1 for xtal */\n+\tstruct sit9531x_out\tout[SIT9531X_MAX_OUTPUTS];\n+\tstruct sit9531x_chan\tchan[SIT9531X_NUM_PLLS];\n+\tu32\t\t\txtal_freq;\n+\n+\t/* DPLL channels */\n+\tstruct list_head\tdplls;\n+\n+\t/* Monitor */\n+\tstruct kthread_worker\t\t*kworker;\n+\tstruct kthread_delayed_work\twork;\n+\n+\t/* Device identity */\n+\tu64\t\t\tclock_id;\n+\n+\t/* Optional DT-described GPIO / IRQ lines */\n+\tstruct gpio_desc\t*reset_gpio;\n+\tint\t\t\tirq;\n+\n+\t/* Optional DT board-config overrides */\n+\tu64\t\t\tpll_fvco[SIT9531X_NUM_PLLS];\n+\tu8\t\t\tout_pll_map[SIT9531X_MAX_OUTPUTS];\n+\tbool\t\t\tout_pll_map_valid;\n+\n+\t/* Inter-PLL synchronization state */\n+\ts8\t\t\tintsync_src;\n+\n+};\n+\n+extern const struct regmap_config sit9531x_regmap_config;\n+\n+/* ---- Core lifecycle ---- */\n+int sit9531x_dev_probe(struct sit9531x_dev *sitdev);\n+int sit9531x_dev_start(struct sit9531x_dev *sitdev);\n+void sit9531x_dev_stop(struct sit9531x_dev *sitdev);\n+\n+/* ---- Register access ---- */\n+int sit9531x_read_u8(struct sit9531x_dev *sitdev, unsigned int reg,\n+\t\t u8 *val);\n+int sit9531x_write_u8(struct sit9531x_dev *sitdev, unsigned int reg,\n+\t\t u8 val);\n+int sit9531x_read_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t u8 offset, u8 *val);\n+int sit9531x_write_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t u8 offset, u8 val);\n+int sit9531x_update_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t u8 offset, u8 mask, u8 val);\n+\n+/* ---- Input enable/disable ---- */\n+int sit9531x_input_disable(struct sit9531x_dev *sitdev, u8 index);\n+int sit9531x_input_enable(struct sit9531x_dev *sitdev, u8 index);\n+\n+/* ---- Input priority ---- */\n+int sit9531x_input_prio_set(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t u8 input_idx, u8 prio);\n+int sit9531x_input_prio_get(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t u8 input_idx, u8 *prio);\n+int sit9531x_input_prio_remove(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t u8 input_idx);\n+int sit9531x_input_prio_add(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t u8 input_idx);\n+\n+/* ---- Output enable/disable (Hi-Z control) ---- */\n+int sit9531x_output_disable(struct sit9531x_dev *sitdev, u8 index);\n+int sit9531x_output_enable(struct sit9531x_dev *sitdev, u8 index);\n+\n+/* ---- Output frequency ---- */\n+int sit9531x_output_freq_set(struct sit9531x_dev *sitdev, u8 out_idx,\n+\t\t\t u8 pll_idx, u64 frequency);\n+int sit9531x_output_freq_get(struct sit9531x_dev *sitdev, u8 out_idx,\n+\t\t\t u64 *frequency);\n+\n+/* ---- Output phase adjust (PRG_RST_DELAY register-based) ---- */\n+int sit9531x_output_phase_adjust_set(struct sit9531x_dev *sitdev,\n+\t\t\t\t u8 out_idx, s32 phase_ps);\n+\n+/* ---- Notification clear ---- */\n+int sit9531x_clear_notifications(struct sit9531x_dev *sitdev);\n+\n+/* ---- INTSYNC (inter-PLL synchronization) ---- */\n+int sit9531x_intsync_enable(struct sit9531x_dev *sitdev, u8 src_pll_idx);\n+int sit9531x_intsync_disable(struct sit9531x_dev *sitdev, u8 src_pll_idx);\n+\n+/* ---- Output pulse control ---- */\n+int sit9531x_output_pulse_ctrl_set(struct sit9531x_dev *sitdev,\n+\t\t\t\t u8 out_idx, u8 pulse_ctrl);\n+\n+/* ---- Phase offset (TDC readback) ---- */\n+int sit9531x_pll_ffo_ppt(struct sit9531x_dev *sitdev, u8 pll_idx, s64 *ffo);\n+int sit9531x_phase_offset_read(struct sit9531x_dev *sitdev, u8 pll_idx,\n+\t\t\t s64 *phase_ps);\n+\n+/* ---- State helpers ---- */\n+\n+/*\n+ * sit9531x_pll_page - get register page for PLL index\n+ * @pll_idx: PLL index (0 = PLLA, 3 = PLLD)\n+ */\n+static inline u8 sit9531x_pll_page(u8 pll_idx)\n+{\n+\treturn SIT9531X_PAGE_PLLA + pll_idx;\n+}\n+\n+/*\n+ * Logical input pins are interleaved: even index = P lane, odd\n+ * index = N lane of pair index/2 (IN0P, IN0N, IN1P, IN1N, ...).\n+ * Index SIT9531X_MAX_INPUTS is the XO input.\n+ */\n+\n+/*\n+ * sit9531x_input_pair - get input pair number for a logical input index\n+ * @index: logical input pin index\n+ */\n+static inline u8 sit9531x_input_pair(u8 index)\n+{\n+\treturn index \u003e\u003e 1;\n+}\n+\n+/*\n+ * sit9531x_input_is_n - check if a logical input index is an N lane\n+ * @index: logical input pin index\n+ */\n+static inline bool sit9531x_input_is_n(u8 index)\n+{\n+\treturn index \u0026 1;\n+}\n+\n+/*\n+ * sit9531x_input_hw_src - translate logical input index to source encoding\n+ * @index: logical input pin index\n+ *\n+ * The priority table and CLK_ACTIVESEL registers use a non-contiguous\n+ * source encoding: 0-3 = CLK0P..CLK3P, 5 = OCXO, 6 = INTSYNC,\n+ * 7-10 = CLK0N..CLK3N.\n+ */\n+static inline u8 sit9531x_input_hw_src(u8 index)\n+{\n+\tif (index == SIT9531X_MAX_INPUTS)\n+\t\treturn SIT9531X_PRIO_SRC_OCXO;\n+\tif (index == SIT9531X_INTSYNC_PIN_ID)\n+\t\treturn SIT9531X_PRIO_SRC_INTSYNC;\n+\tif (sit9531x_input_is_n(index))\n+\t\treturn SIT9531X_PRIO_SRC_N_BASE + sit9531x_input_pair(index);\n+\treturn sit9531x_input_pair(index);\n+}\n+\n+/*\n+ * sit9531x_hw_src_input - translate source encoding to logical input index\n+ * @src: 4-bit hardware source encoding\n+ *\n+ * Return: logical input index (INTSYNC maps to SIT9531X_INTSYNC_PIN_ID),\n+ * or SIT9531X_REF_INVALID if @src is a reserved value\n+ */\n+static inline u8 sit9531x_hw_src_input(u8 src)\n+{\n+\tif (src \u003c SIT9531X_NUM_INPUT_PAIRS)\n+\t\treturn src * 2;\n+\tif (src == SIT9531X_PRIO_SRC_OCXO)\n+\t\treturn SIT9531X_MAX_INPUTS;\n+\tif (src == SIT9531X_PRIO_SRC_INTSYNC)\n+\t\treturn SIT9531X_INTSYNC_PIN_ID;\n+\tif (src \u003e= SIT9531X_PRIO_SRC_N_BASE \u0026\u0026\n+\t src \u003c SIT9531X_PRIO_SRC_N_BASE + SIT9531X_NUM_INPUT_PAIRS)\n+\t\treturn (src - SIT9531X_PRIO_SRC_N_BASE) * 2 + 1;\n+\treturn SIT9531X_REF_INVALID;\n+}\n+\n+/*\n+ * sit9531x_ref_state_get - get reference state by index\n+ * @index:\tlogical input index\n+ *\n+ * Return: pointer to the cached input reference state\n+ */\n+static inline const struct sit9531x_ref *\n+sit9531x_ref_state_get(const struct sit9531x_dev *sitdev, u8 index)\n+{\n+\treturn \u0026sitdev-\u003eref[index];\n+}\n+\n+/*\n+ * sit9531x_out_state_get - get output state by index\n+ * @index:\tlogical output index\n+ *\n+ * Return: pointer to the cached output state\n+ */\n+static inline const struct sit9531x_out *\n+sit9531x_out_state_get(const struct sit9531x_dev *sitdev, u8 index)\n+{\n+\treturn \u0026sitdev-\u003eout[index];\n+}\n+\n+/*\n+ * sit9531x_chan_state_get - get channel state by PLL index\n+ *\n+ * Return: pointer to the cached per-PLL channel state\n+ */\n+static inline const struct sit9531x_chan *\n+sit9531x_chan_state_get(const struct sit9531x_dev *sitdev, u8 pll_idx)\n+{\n+\treturn \u0026sitdev-\u003echan[pll_idx];\n+}\n+\n+#endif /* _SIT9531X_CORE_H */\ndiff --git a/drivers/dpll/sit9531x/dpll.c b/drivers/dpll/sit9531x/dpll.c\nnew file mode 100644\nindex 0000000000000..364f870ac3041\n--- /dev/null\n+++ b/drivers/dpll/sit9531x/dpll.c\n@@ -0,0 +1,1202 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * SiTime SiT9531x DPLL subsystem callbacks and registration\n+ *\n+ * Copyright (C) 2026 SiTime Corp.\n+ * Author: Ali Rouhi \u003carouhi@sitime.com\u003e\n+ * Author: Oleg Zadorozhnyi \u003cOleg.Zadorozhnyi@devoxsoftware.com\u003e\n+ *\n+ * DPLL device ops, pin ops (separate input/output), pin registration,\n+ * and periodic change detection.\n+ */\n+\n+#include \u003clinux/dpll.h\u003e\n+#include \u003clinux/err.h\u003e\n+#include \u003clinux/kthread.h\u003e\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/netlink.h\u003e\n+#include \u003clinux/slab.h\u003e\n+\n+#include \"core.h\"\n+#include \"dpll.h\"\n+#include \"prop.h\"\n+#include \"regs.h\"\n+\n+#define SIT9531X_ESYNC_FREQ_10MHZ\t10000000ULL\n+#define SIT9531X_ESYNC_PULSE_DEFAULT\t50\n+\n+static const struct dpll_pin_frequency sit9531x_esync_ranges[] = {\n+\tDPLL_PIN_FREQUENCY(0),\n+\tDPLL_PIN_FREQUENCY(SIT9531X_ESYNC_FREQ_10MHZ),\n+};\n+\n+static inline bool\n+sit9531x_dpll_esync_pin_supported(const struct sit9531x_dpll_pin *dpin)\n+{\n+\treturn dpin-\u003eesync_control;\n+}\n+\n+static inline bool sit9531x_dpll_is_input_pin(const struct sit9531x_dpll_pin *pin)\n+{\n+\treturn pin-\u003edir == DPLL_PIN_DIRECTION_INPUT;\n+}\n+\n+static inline bool\n+sit9531x_dpll_is_intsync_pin(const struct sit9531x_dpll_pin *pin)\n+{\n+\treturn sit9531x_dpll_is_input_pin(pin) \u0026\u0026\n+\t pin-\u003eid == SIT9531X_INTSYNC_PIN_ID;\n+}\n+\n+static inline bool\n+sit9531x_dpll_is_intsync_src_pin(const struct sit9531x_dpll_pin *pin)\n+{\n+\treturn !sit9531x_dpll_is_input_pin(pin) \u0026\u0026\n+\t pin-\u003eid == SIT9531X_INTSYNC_OUT_PIN_ID;\n+}\n+\n+static inline bool\n+sit9531x_dpll_is_xo_pin(const struct sit9531x_dpll_pin *pin)\n+{\n+\treturn sit9531x_dpll_is_input_pin(pin) \u0026\u0026\n+\t pin-\u003eid == SIT9531X_MAX_INPUTS;\n+}\n+\n+/*\n+ * The cached state this reports comes from the outer loss-of-lock byte\n+ * (page 0, reg 0x06), the PLL mode bit (PLL page, reg 0x31), inner LOL\n+ * (reg 0x92) and the holdover freeze byte (reg 0x0A).\n+ */\n+static int\n+sit9531x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,\n+\t\t\t enum dpll_lock_status *status,\n+\t\t\t enum dpll_lock_status_error *status_error,\n+\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tconst struct sit9531x_chan *chan;\n+\n+\tif (status_error)\n+\t\t*status_error = DPLL_LOCK_STATUS_ERROR_NONE;\n+\n+\tchan = sit9531x_chan_state_get(sitdev, sitdpll-\u003eid);\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (!chan-\u003eactive) {\n+\t\t/*\n+\t\t * A PLL the loaded configuration leaves unused never reaches\n+\t\t * its active state. Nothing drives its loss-of-lock bit, so\n+\t\t * without this it would report a lock it does not have.\n+\t\t */\n+\t\t*status = DPLL_LOCK_STATUS_UNLOCKED;\n+\t} else if (chan-\u003elocked) {\n+\t\tif (chan-\u003emode)\n+\t\t\t*status = DPLL_LOCK_STATUS_LOCKED; /* free-run */\n+\t\telse\n+\t\t\t*status = DPLL_LOCK_STATUS_LOCKED_HO_ACQ; /* sync */\n+\t} else if (chan-\u003eho_freeze) {\n+\t\t*status = DPLL_LOCK_STATUS_HOLDOVER;\n+\t} else {\n+\t\t*status = DPLL_LOCK_STATUS_UNLOCKED;\n+\t}\n+\n+\t/* Report inner LOL as an error condition */\n+\tif (status_error \u0026\u0026 chan-\u003einner_lol)\n+\t\t*status_error = DPLL_LOCK_STATUS_ERROR_UNDEFINED;\n+\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_dpll_mode_get - report current PLL operating mode\n+ *\n+ * reads outer loop disable bit (PLL page reg 0x31[5]).\n+ * Free-run -\u003e MANUAL, sync -\u003e AUTOMATIC.\n+ */\n+static int\n+sit9531x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,\n+\t\t enum dpll_mode *mode, struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tconst struct sit9531x_chan *chan;\n+\n+\tchan = sit9531x_chan_state_get(sitdev, sitdpll-\u003eid);\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\t*mode = chan-\u003emode ? DPLL_MODE_MANUAL : DPLL_MODE_AUTOMATIC;\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_dpll_mode_set - switch PLL between free-run and sync mode\n+ *\n+ * writes PLL page reg 0x31[5] to enable/disable the\n+ * outer loop, then triggers a small update via reg 0x0F.\n+ *\n+ * The driver maps AUTOMATIC to sync mode (outer loop enabled; the PLL\n+ * selects its reference automatically from the priority table) and\n+ * MANUAL to free-run (outer loop disabled via bit 0x31[5]). The chip\n+ * also has a separate manual-active-select path (MANUAL_ACTIVE_SEL_PLL\n+ * plus the per-PLL man_in_sel registers) that can pin one reference\n+ * while still syncing; that path is not wired to a DPLL callback here.\n+ */\n+static int\n+sit9531x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,\n+\t\t enum dpll_mode mode, struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tu8 val;\n+\tint rc;\n+\n+\t/*\n+\t * Outer loop disable bit:\n+\t * 0 = sync mode (outer loop enabled) -\u003e AUTOMATIC\n+\t * 1 = free-run (outer loop disabled) -\u003e MANUAL\n+\t */\n+\tval = (mode == DPLL_MODE_MANUAL) ? SIT9531X_PLL_STATUS_OUTER_DIS : 0;\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\n+\trc = sit9531x_update_pll_u8(sitdev, sitdpll-\u003eid,\n+\t\t\t\t SIT9531X_PLL_REG_STATUS,\n+\t\t\t\t SIT9531X_PLL_STATUS_OUTER_DIS, val);\n+\tif (rc) {\n+\t\tNL_SET_ERR_MSG(extack, \"Failed to write PLL mode register\");\n+\t\tgoto unlock;\n+\t}\n+\n+\t/* Trigger small update to apply without full NVM cycle */\n+\trc = sit9531x_write_pll_u8(sitdev, sitdpll-\u003eid,\n+\t\t\t\t SIT9531X_PLL_REG_SMALL_UPDATE,\n+\t\t\t\t SIT9531X_SMALL_UPDATE_CMD);\n+\tif (rc) {\n+\t\tNL_SET_ERR_MSG(extack, \"Failed to trigger small update\");\n+\t\tgoto unlock;\n+\t}\n+\n+\t/*\n+\t * Keep the cached mode in step with the register. The periodic\n+\t * monitor refreshes it too, but mode_get and the reference-selection\n+\t * path read this cache and would otherwise report or act on the\n+\t * pre-switch mode until the next poll.\n+\t */\n+\tsitdev-\u003echan[sitdpll-\u003eid].mode = !!val;\n+\n+unlock:\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\treturn rc;\n+}\n+\n+static int\n+sit9531x_dpll_supported_modes_get(const struct dpll_device *dpll,\n+\t\t\t\t void *dpll_priv, unsigned long *modes,\n+\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\t__set_bit(DPLL_MODE_AUTOMATIC, modes);\n+\t__set_bit(DPLL_MODE_MANUAL, modes);\n+\n+\treturn 0;\n+}\n+\n+const struct dpll_device_ops sit9531x_dpll_device_ops = {\n+\t.lock_status_get\t= sit9531x_dpll_lock_status_get,\n+\t.mode_get\t\t= sit9531x_dpll_mode_get,\n+\t.mode_set\t\t= sit9531x_dpll_mode_set,\n+\t.supported_modes_get\t= sit9531x_dpll_supported_modes_get,\n+\t/* temp_get not available -- SiT9531x has no on-die temp sensor */\n+};\n+\n+static int\n+sit9531x_dpll_input_pin_direction_get(const struct dpll_pin *pin,\n+\t\t\t\t void *pin_priv,\n+\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t void *dpll_priv,\n+\t\t\t\t enum dpll_pin_direction *direction,\n+\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\t*direction = DPLL_PIN_DIRECTION_INPUT;\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_dpll_input_pin_frequency_get - read input pin frequency\n+ *\n+ * returns cached frequency from DT or last set.\n+ */\n+static int\n+sit9531x_dpll_input_pin_frequency_get(const struct dpll_pin *pin,\n+\t\t\t\t void *pin_priv,\n+\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t void *dpll_priv, u64 *frequency,\n+\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll_pin *dpin = pin_priv;\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tconst struct sit9531x_ref *ref;\n+\n+\tref = sit9531x_ref_state_get(sitdpll-\u003edev, dpin-\u003eid);\n+\t*frequency = ref-\u003efreq;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_dpll_input_pin_state_on_dpll_get - get input pin DPLL state\n+ *\n+ * determines pin state from channel state: connected\n+ * if this input is the selected reference on a locked PLL, selectable\n+ * if enabled in automatic mode, disconnected otherwise.\n+ */\n+static int\n+sit9531x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *pin,\n+\t\t\t\t\t void *pin_priv,\n+\t\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t\t void *dpll_priv,\n+\t\t\t\t\t enum dpll_pin_state *state,\n+\t\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll_pin *dpin = pin_priv;\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tconst struct sit9531x_chan *chan;\n+\tconst struct sit9531x_ref *ref;\n+\n+\tchan = sit9531x_chan_state_get(sitdev, sitdpll-\u003eid);\n+\tref = sit9531x_ref_state_get(sitdev, dpin-\u003eid);\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\n+\t/*\n+\t * Report CONNECTED only when the PLL is genuinely locked to this\n+\u003c\u003c\u003c\u003c\u003c\u003c\u003c HEAD\n+\t * input: outer loop locked AND inner loop locked. Gating on the\n+\t * outer bit alone let an idle PLL whose outer loop happens to read\n+\t * locked (e.g. an unused PLLD) falsely claim its default source\n+\t * (IN0P) as connected right after probe.\n+=======\n+\t * input. chan-\u003elocked already excludes a PLL the configuration\n+\t * leaves unused, which would otherwise claim its default source as\n+\t * connected right after probe; the inner loop has to be locked as\n+\t * well before the reference can be called connected.\n+\u003e\u003e\u003e\u003e\u003e\u003e\u003e 7117164f1c60 (dpll: sit9531x: do not report an unused PLL as locked)\n+\t */\n+\tif (chan-\u003elocked \u0026\u0026 !chan-\u003einner_lol \u0026\u0026\n+\t chan-\u003eselected_ref == dpin-\u003eid)\n+\t\t*state = DPLL_PIN_STATE_CONNECTED;\n+\t/* In auto mode, enabled inputs with good signal are selectable */\n+\telse if (!chan-\u003emode \u0026\u0026 ref-\u003eenabled \u0026\u0026 !ref-\u003elos \u0026\u0026 !ref-\u003eoof)\n+\t\t*state = DPLL_PIN_STATE_SELECTABLE;\n+\telse\n+\t\t*state = DPLL_PIN_STATE_DISCONNECTED;\n+\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_dpll_input_pin_state_on_dpll_set - set input pin DPLL state\n+ *\n+ * Enables or disables the physical input receiver via Page 0x02\n+ * force/state registers (sit9531x_input_disable/enable()) and updates\n+ * this DPLL's Page 1 priority table so the state is honoured by the\n+ * PLL's automatic reference selection, not just at the input buffer.\n+ * DISCONNECTED -\u003e drop the input from this DPLL's priority table and\n+ * release this DPLL's claim on the input\n+ * SELECTABLE -\u003e claim the input for this DPLL and make it eligible\n+ * again in the priority table\n+ * CONNECTED -\u003e same as SELECTABLE (the PLL auto-switch logic does\n+ * the actual selection)\n+ *\n+ * The priority table is per PLL, so it is always updated for this DPLL.\n+ * A single physical input feeds every DPLL, so the hardware receiver is\n+ * only cut off once the last DPLL has released it: ref-\u003epll_mask tracks\n+ * which DPLLs currently claim the input, and the physical disable\n+ * happens on the transition to an empty mask.\n+ */\n+static int\n+sit9531x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *pin,\n+\t\t\t\t\t void *pin_priv,\n+\t\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t\t void *dpll_priv,\n+\t\t\t\t\t enum dpll_pin_state state,\n+\t\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll_pin *dpin = pin_priv;\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tstruct sit9531x_ref *ref = \u0026sitdev-\u003eref[dpin-\u003eid];\n+\tu8 hw_src = sit9531x_input_hw_src(dpin-\u003eid);\n+\tu8 pll_bit = BIT(sitdpll-\u003eid);\n+\tint rc;\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\n+\tswitch (state) {\n+\tcase DPLL_PIN_STATE_DISCONNECTED:\n+\t\trc = sit9531x_input_prio_remove(sitdev, sitdpll-\u003eid, hw_src);\n+\t\tif (rc)\n+\t\t\tbreak;\n+\t\tref-\u003epll_mask \u0026= ~pll_bit;\n+\t\tif (ref-\u003epll_mask)\n+\t\t\trc = 0;\t/* another DPLL still uses this input */\n+\t\telse\n+\t\t\trc = sit9531x_input_disable(sitdev, dpin-\u003eid);\n+\t\tbreak;\n+\tcase DPLL_PIN_STATE_SELECTABLE:\n+\tcase DPLL_PIN_STATE_CONNECTED:\n+\t\trc = sit9531x_input_enable(sitdev, dpin-\u003eid);\n+\t\tif (rc)\n+\t\t\tbreak;\n+\t\trc = sit9531x_input_prio_add(sitdev, sitdpll-\u003eid, hw_src);\n+\t\tif (rc)\n+\t\t\tbreak;\n+\t\t/*\n+\t\t * Claim the input for this DPLL only once it is both enabled\n+\t\t * and present in the priority table. Setting the mask before\n+\t\t * prio_add would leak the claim if prio_add failed, keeping the\n+\t\t * shared input receiver powered even after every DPLL released\n+\t\t * it.\n+\t\t */\n+\t\tref-\u003epll_mask |= pll_bit;\n+\t\tbreak;\n+\tdefault:\n+\t\trc = -EINVAL;\n+\t\tbreak;\n+\t}\n+\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (rc)\n+\t\tNL_SET_ERR_MSG(extack, \"Failed to set input pin state\");\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * sit9531x_dpll_input_pin_prio_get - read input pin priority\n+ *\n+ * reads the PLL's priority table on Page 1 (via\n+ * sit9531x_input_prio_get()) and returns the slot the input\n+ * occupies, so pin-get reports the real hardware priority rather\n+ * than a software default.\n+ */\n+static int\n+sit9531x_dpll_input_pin_prio_get(const struct dpll_pin *pin, void *pin_priv,\n+\t\t\t\t const struct dpll_device *dpll, void *dpll_priv,\n+\t\t\t\t u32 *prio, struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll_pin *dpin = pin_priv;\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tu8 slot;\n+\tint rc;\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\trc = sit9531x_input_prio_get(sitdev, sitdpll-\u003eid,\n+\t\t\t\t sit9531x_input_hw_src(dpin-\u003eid), \u0026slot);\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tdpin-\u003eprio = slot;\n+\t*prio = slot;\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_dpll_input_pin_prio_set - set input pin priority\n+ *\n+ * writes input priority table on Page 1 via\n+ * core.c sit9531x_input_prio_set(). Forces holdover during update.\n+ */\n+static int\n+sit9531x_dpll_input_pin_prio_set(const struct dpll_pin *pin, void *pin_priv,\n+\t\t\t\t const struct dpll_device *dpll, void *dpll_priv,\n+\t\t\t\t u32 prio, struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll_pin *dpin = pin_priv;\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tint rc;\n+\n+\tif (dpin-\u003edir != DPLL_PIN_DIRECTION_INPUT) {\n+\t\tNL_SET_ERR_MSG(extack, \"Priority applies only to input pins\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (prio \u003e= SIT9531X_PRIO_MAX_SLOTS) {\n+\t\tNL_SET_ERR_MSG(extack, \"Priority out of range (0-10)\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\trc = sit9531x_input_prio_set(sitdev, sitdpll-\u003eid,\n+\t\t\t\t sit9531x_input_hw_src(dpin-\u003eid),\n+\t\t\t\t (u8)prio);\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (rc) {\n+\t\tNL_SET_ERR_MSG(extack, \"Failed to set input priority\");\n+\t\treturn rc;\n+\t}\n+\n+\tdpin-\u003eprio = (u8)prio;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_dpll_input_pin_phase_adjust_get - read phase adjustment\n+ *\n+ * returns cached phase adjustment value (in ps).\n+ */\n+static int\n+sit9531x_dpll_input_pin_phase_adjust_get(const struct dpll_pin *pin,\n+\t\t\t\t\t void *pin_priv,\n+\t\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t\t void *dpll_priv, s32 *phase_adjust,\n+\t\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll_pin *dpin = pin_priv;\n+\n+\t*phase_adjust = dpin-\u003ephase_adjust;\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_dpll_input_pin_phase_offset_get - read phase offset\n+ *\n+ * reads the TDC (Time-to-Digital Converter) hardware\n+ * to measure the phase difference in picoseconds via\n+ * sit9531x_phase_offset_read().\n+ */\n+/*\n+ * sit9531x_dpll_input_pin_ffo_get - read the input's frequency offset\n+ *\n+ * The offset is derived from how far the PLL's running DIVN sits from\n+ * its configured one, which only says something about the reference the\n+ * PLL is actually tracking. For every other input there is no\n+ * measurement, and -ENODATA leaves the attribute out rather than\n+ * reporting the active reference's figure against the wrong pin.\n+ */\n+static int\n+sit9531x_dpll_input_pin_ffo_get(const struct dpll_pin *pin, void *pin_priv,\n+\t\t\t\tconst struct dpll_device *dpll, void *dpll_priv,\n+\t\t\t\tstruct dpll_ffo_param *ffo,\n+\t\t\t\tstruct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll_pin *dpin = pin_priv;\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tint rc;\n+\n+\tif (sitdev-\u003echan[sitdpll-\u003eid].selected_ref != dpin-\u003eid)\n+\t\treturn -ENODATA;\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\trc = sit9531x_pll_ffo_ppt(sitdev, sitdpll-\u003eid, \u0026ffo-\u003effo);\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\treturn rc;\n+}\n+\n+static int\n+sit9531x_dpll_input_pin_phase_offset_get(const struct dpll_pin *pin,\n+\t\t\t\t\t void *pin_priv,\n+\t\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t\t void *dpll_priv, s64 *phase_offset,\n+\t\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll_pin *dpin = pin_priv;\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\ts64 offset;\n+\tint rc;\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\n+\t/*\n+\t * The on-chip TDC is a per-PLL resource that always measures the\n+\t * phase difference between the VCO and the PLL's currently\n+\t * selected reference; it cannot be pointed at an arbitrary input.\n+\t * For any input that is not the active reference there is no\n+\t * meaningful per-pin phase offset, so report 0 instead of the\n+\t * active reference's value.\n+\t */\n+\tif (sitdev-\u003echan[sitdpll-\u003eid].selected_ref != dpin-\u003eid) {\n+\t\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\t\tdpin-\u003ephase_offset = 0;\n+\t\t*phase_offset = 0;\n+\t\treturn 0;\n+\t}\n+\n+\trc = sit9531x_phase_offset_read(sitdev, sitdpll-\u003eid, \u0026offset);\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\t/*\n+\t * -ENODEV means the PLL has no programmed DIVN (unused on this\n+\t * board); report phase_offset = 0 so a full pin-get dump does not\n+\t * fail just because one DPLL is dormant.\n+\t */\n+\tif (rc == -ENODEV) {\n+\t\tdpin-\u003ephase_offset = 0;\n+\t\t*phase_offset = 0;\n+\t\treturn 0;\n+\t}\n+\tif (rc) {\n+\t\tNL_SET_ERR_MSG(extack, \"TDC phase readback failed\");\n+\t\treturn rc;\n+\t}\n+\n+\t/*\n+\t * The ABI reports phase offset in units of 1/DPLL_PHASE_OFFSET_DIVIDER\n+\t * picoseconds: the integer part of the attribute is the value divided\n+\t * by the divider, the remainder is the fraction. The TDC resolves one\n+\t * VCO period (hundreds of picoseconds), so the fractional digits are\n+\t * always zero here, but the magnitude still has to be scaled or every\n+\t * reading would be reported a thousand times too small.\n+\t */\n+\toffset *= DPLL_PHASE_OFFSET_DIVIDER;\n+\n+\tdpin-\u003ephase_offset = offset;\n+\t*phase_offset = offset;\n+\treturn 0;\n+}\n+\n+static const struct dpll_pin_ops sit9531x_dpll_input_pin_ops = {\n+\t.direction_get\t\t= sit9531x_dpll_input_pin_direction_get,\n+\t.frequency_get\t\t= sit9531x_dpll_input_pin_frequency_get,\n+\t.state_on_dpll_get\t= sit9531x_dpll_input_pin_state_on_dpll_get,\n+\t.state_on_dpll_set\t= sit9531x_dpll_input_pin_state_on_dpll_set,\n+\t.prio_get\t\t= sit9531x_dpll_input_pin_prio_get,\n+\t.prio_set\t\t= sit9531x_dpll_input_pin_prio_set,\n+\t.phase_adjust_get\t= sit9531x_dpll_input_pin_phase_adjust_get,\n+\t.phase_offset_get\t= sit9531x_dpll_input_pin_phase_offset_get,\n+\t/*\n+\t * The measurement compares the PLL's running feedback divider with\n+\t * its configured one, so it describes the device's own reference\n+\t * rather than a port rate.\n+\t */\n+\t.supported_ffo\t\t= BIT(DPLL_FFO_PIN_DEVICE),\n+\t.ffo_get\t\t= sit9531x_dpll_input_pin_ffo_get,\n+};\n+\n+/*\n+ * INTSYNC pin ops\n+ *\n+ * INTSYNC is the chip's inter-PLL sync net: one PLL drives it and other\n+ * PLLs may lock to it instead of to an external reference. The two\n+ * roles are exposed as two separate pins so neither overloads the other:\n+ *\n+ * - a source (output) pin registered on every DPLL. Connecting it on a\n+ * DPLL makes that DPLL drive INTSYNC; only one DPLL may drive it at a\n+ * time. It has no priority ops -- driving the net is not a reference\n+ * selection.\n+ * - a destination (input) pin registered on every DPLL. Connecting it\n+ * on a DPLL makes that DPLL eligible to lock to INTSYNC as a\n+ * reference, so it carries the priority ops.\n+ */\n+\n+/* ---- INTSYNC source (output) pin ---- */\n+\n+/* The INTSYNC source pin is an output; its direction_get is defined below. */\n+static int\n+sit9531x_dpll_output_pin_direction_get(const struct dpll_pin *pin,\n+\t\t\t\t void *pin_priv,\n+\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t void *dpll_priv,\n+\t\t\t\t enum dpll_pin_direction *direction,\n+\t\t\t\t struct netlink_ext_ack *extack);\n+\n+static int\n+sit9531x_dpll_intsync_src_state_on_dpll_get(const struct dpll_pin *pin,\n+\t\t\t\t\t void *pin_priv,\n+\t\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t\t void *dpll_priv,\n+\t\t\t\t\t enum dpll_pin_state *state,\n+\t\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\tif (sitdev-\u003eintsync_src == sitdpll-\u003eid)\n+\t\t*state = DPLL_PIN_STATE_CONNECTED;\n+\telse\n+\t\t*state = DPLL_PIN_STATE_DISCONNECTED;\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_dpll_intsync_src_state_on_dpll_set - drive INTSYNC from a PLL\n+ *\n+ * CONNECTED -\u003e this PLL drives the INTSYNC net\n+ * DISCONNECTED -\u003e stop driving INTSYNC if this PLL drives it\n+ *\n+ * SELECTABLE is rejected: driving the net is an explicit output routing,\n+ * not an automatic-selection candidate, matching the regular output pin.\n+ */\n+static int\n+sit9531x_dpll_intsync_src_state_on_dpll_set(const struct dpll_pin *pin,\n+\t\t\t\t\t void *pin_priv,\n+\t\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t\t void *dpll_priv,\n+\t\t\t\t\t enum dpll_pin_state state,\n+\t\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tint rc = 0;\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\n+\tswitch (state) {\n+\tcase DPLL_PIN_STATE_CONNECTED:\n+\t\tif (sitdev-\u003eintsync_src == sitdpll-\u003eid)\n+\t\t\tbreak;\n+\t\tif (sitdev-\u003eintsync_src \u003e= 0) {\n+\t\t\tNL_SET_ERR_MSG(extack,\n+\t\t\t\t \"INTSYNC is already sourced by another PLL\");\n+\t\t\trc = -EBUSY;\n+\t\t\tbreak;\n+\t\t}\n+\t\trc = sit9531x_intsync_enable(sitdev, sitdpll-\u003eid);\n+\t\tif (!rc)\n+\t\t\tsitdev-\u003eintsync_src = sitdpll-\u003eid;\n+\t\tbreak;\n+\tcase DPLL_PIN_STATE_DISCONNECTED:\n+\t\tif (sitdev-\u003eintsync_src != sitdpll-\u003eid)\n+\t\t\tbreak;\n+\t\trc = sit9531x_intsync_disable(sitdev, sitdpll-\u003eid);\n+\t\tif (!rc)\n+\t\t\tsitdev-\u003eintsync_src = -1;\n+\t\tbreak;\n+\tdefault:\n+\t\trc = -EINVAL;\n+\t\tbreak;\n+\t}\n+\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (rc \u0026\u0026 rc != -EBUSY)\n+\t\tNL_SET_ERR_MSG(extack, \"Failed to set INTSYNC source state\");\n+\n+\treturn rc;\n+}\n+\n+static const struct dpll_pin_ops sit9531x_dpll_intsync_src_pin_ops = {\n+\t.direction_get\t\t= sit9531x_dpll_output_pin_direction_get,\n+\t.state_on_dpll_get\t= sit9531x_dpll_intsync_src_state_on_dpll_get,\n+\t.state_on_dpll_set\t= sit9531x_dpll_intsync_src_state_on_dpll_set,\n+};\n+\n+/* ---- INTSYNC destination (input) pin ---- */\n+\n+/*\n+ * sit9531x_dpll_intsync_dst_state_on_dpll_get - INTSYNC reference state\n+ *\n+ * CONNECTED when this PLL is locked to INTSYNC as its reference,\n+ * SELECTABLE when it runs in automatic mode and a source PLL is driving\n+ * INTSYNC, DISCONNECTED otherwise. The PLL that drives INTSYNC can never\n+ * be its own destination.\n+ */\n+static int\n+sit9531x_dpll_intsync_dst_state_on_dpll_get(const struct dpll_pin *pin,\n+\t\t\t\t\t void *pin_priv,\n+\t\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t\t void *dpll_priv,\n+\t\t\t\t\t enum dpll_pin_state *state,\n+\t\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tconst struct sit9531x_chan *chan;\n+\n+\tchan = sit9531x_chan_state_get(sitdev, sitdpll-\u003eid);\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\tif (sitdev-\u003eintsync_src == sitdpll-\u003eid)\n+\t\t*state = DPLL_PIN_STATE_DISCONNECTED;\n+\telse if (chan-\u003elocked \u0026\u0026 !chan-\u003einner_lol \u0026\u0026\n+\t\t chan-\u003eselected_ref == SIT9531X_INTSYNC_PIN_ID)\n+\t\t*state = DPLL_PIN_STATE_CONNECTED;\n+\telse if (!chan-\u003emode \u0026\u0026 sitdev-\u003eintsync_src \u003e= 0)\n+\t\t*state = DPLL_PIN_STATE_SELECTABLE;\n+\telse\n+\t\t*state = DPLL_PIN_STATE_DISCONNECTED;\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_dpll_intsync_dst_state_on_dpll_set - lock a PLL to INTSYNC\n+ *\n+ * CONNECTED/SELECTABLE -\u003e add INTSYNC to this PLL's priority table\n+ * DISCONNECTED -\u003e drop INTSYNC from this PLL's priority table\n+ *\n+ * INTSYNC is an internal net with no physical receiver, so only the\n+ * per-PLL priority table is touched; the source pin controls generation.\n+ */\n+static int\n+sit9531x_dpll_intsync_dst_state_on_dpll_set(const struct dpll_pin *pin,\n+\t\t\t\t\t void *pin_priv,\n+\t\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t\t void *dpll_priv,\n+\t\t\t\t\t enum dpll_pin_state state,\n+\t\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tu8 hw_src = sit9531x_input_hw_src(SIT9531X_INTSYNC_PIN_ID);\n+\tint rc;\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\n+\tswitch (state) {\n+\tcase DPLL_PIN_STATE_DISCONNECTED:\n+\t\trc = sit9531x_input_prio_remove(sitdev, sitdpll-\u003eid, hw_src);\n+\t\tbreak;\n+\tcase DPLL_PIN_STATE_SELECTABLE:\n+\tcase DPLL_PIN_STATE_CONNECTED:\n+\t\tif (sitdev-\u003eintsync_src == sitdpll-\u003eid) {\n+\t\t\tNL_SET_ERR_MSG(extack,\n+\t\t\t\t \"PLL cannot lock to the INTSYNC it drives\");\n+\t\t\trc = -EINVAL;\n+\t\t\tbreak;\n+\t\t}\n+\t\trc = sit9531x_input_prio_add(sitdev, sitdpll-\u003eid, hw_src);\n+\t\tbreak;\n+\tdefault:\n+\t\trc = -EINVAL;\n+\t\tbreak;\n+\t}\n+\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (rc \u0026\u0026 rc != -EINVAL)\n+\t\tNL_SET_ERR_MSG(extack, \"Failed to set INTSYNC input state\");\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * Do not add .frequency_get / the generic input state getter here: the\n+ * destination pin id is SIT9531X_INTSYNC_PIN_ID, one past the end of the\n+ * ref[] array (INTSYNC is an internal net with no ref[] entry). The ops\n+ * below only ever key on chan[] and the priority table, never ref[id].\n+ */\n+static const struct dpll_pin_ops sit9531x_dpll_intsync_dst_pin_ops = {\n+\t.direction_get\t\t= sit9531x_dpll_input_pin_direction_get,\n+\t.state_on_dpll_get\t= sit9531x_dpll_intsync_dst_state_on_dpll_get,\n+\t.state_on_dpll_set\t= sit9531x_dpll_intsync_dst_state_on_dpll_set,\n+\t.prio_get\t\t= sit9531x_dpll_input_pin_prio_get,\n+\t.prio_set\t\t= sit9531x_dpll_input_pin_prio_set,\n+};\n+\n+/*\n+ * XO (crystal oscillator) pin ops\n+ *\n+ * The XO is the chip's internal reference oscillator that feeds every\n+ * PLL. It is exposed so userspace can see the on-chip reference, but it\n+ * cannot be routed or disconnected, so it is reported permanently\n+ * connected and offers no state_on_dpll_set / prio ops.\n+ */\n+\n+static int\n+sit9531x_dpll_xo_pin_state_on_dpll_get(const struct dpll_pin *pin,\n+\t\t\t\t void *pin_priv,\n+\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t void *dpll_priv,\n+\t\t\t\t enum dpll_pin_state *state,\n+\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\t*state = DPLL_PIN_STATE_CONNECTED;\n+\treturn 0;\n+}\n+\n+static const struct dpll_pin_ops sit9531x_dpll_xo_pin_ops = {\n+\t.direction_get\t\t= sit9531x_dpll_input_pin_direction_get,\n+\t.frequency_get\t\t= sit9531x_dpll_input_pin_frequency_get,\n+\t.state_on_dpll_get\t= sit9531x_dpll_xo_pin_state_on_dpll_get,\n+};\n+\n+static int\n+sit9531x_dpll_output_pin_direction_get(const struct dpll_pin *pin,\n+\t\t\t\t void *pin_priv,\n+\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t void *dpll_priv,\n+\t\t\t\t enum dpll_pin_direction *direction,\n+\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\t*direction = DPLL_PIN_DIRECTION_OUTPUT;\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_dpll_output_pin_frequency_get - read output pin frequency\n+ *\n+ * Reads the DIVO divider back from the chip and computes the live\n+ * frequency as Fvco / DIVO. Falls back to the cached value when the\n+ * output is not resolvable through the divider chain (e.g. not mapped\n+ * to a PLL), so a netlink dump never fails on such pins.\n+ */\n+static int\n+sit9531x_dpll_output_pin_frequency_get(const struct dpll_pin *pin,\n+\t\t\t\t void *pin_priv,\n+\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t void *dpll_priv, u64 *frequency,\n+\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll_pin *dpin = pin_priv;\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tint rc;\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\trc = sit9531x_output_freq_get(sitdev, dpin-\u003eid, frequency);\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (rc)\n+\t\t*frequency = sit9531x_out_state_get(sitdev, dpin-\u003eid)-\u003efreq;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_dpll_output_pin_frequency_set - set output pin frequency\n+ *\n+ * computes DIVO = Fvco / frequency and writes the\n+ * 34-bit output divider to the output system registers via\n+ * sit9531x_output_freq_set().\n+ */\n+static int\n+sit9531x_dpll_output_pin_frequency_set(const struct dpll_pin *pin,\n+\t\t\t\t void *pin_priv,\n+\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t void *dpll_priv, u64 frequency,\n+\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll_pin *dpin = pin_priv;\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tu8 actual_pll;\n+\tint rc;\n+\n+\t/*\n+\t * Read the PLL that drives this output from its OUT_MAP state\n+\t * (populated by out_state_fetch from the chip's OUT_MAP registers).\n+\t * That is the index the output register programming below is keyed\n+\t * by; the output is registered under the DPLL matching this PLL.\n+\t */\n+\tactual_pll = sitdev-\u003eout[dpin-\u003eid].pll_idx;\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\trc = sit9531x_output_freq_set(sitdev, dpin-\u003eid, actual_pll,\n+\t\t\t\t frequency);\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (rc)\n+\t\tNL_SET_ERR_MSG(extack, \"Output frequency set failed\");\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * sit9531x_dpll_output_pin_state_on_dpll_get - get output pin state\n+ *\n+ * reports CONNECTED when the output is driven and\n+ * DISCONNECTED when it has been muted via sit9531x_output_disable().\n+ */\n+static int\n+sit9531x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *pin,\n+\t\t\t\t\t void *pin_priv,\n+\t\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t\t void *dpll_priv,\n+\t\t\t\t\t enum dpll_pin_state *state,\n+\t\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll_pin *dpin = pin_priv;\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tconst struct sit9531x_out *out;\n+\n+\tout = sit9531x_out_state_get(sitdpll-\u003edev, dpin-\u003eid);\n+\t*state = out-\u003eenabled ? DPLL_PIN_STATE_CONNECTED\n+\t\t\t : DPLL_PIN_STATE_DISCONNECTED;\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_dpll_output_pin_state_on_dpll_set - mute/un-mute an output\n+ *\n+ * forces Hi-Z on the output pin via the Page 0x03\n+ * force/state register pair.\n+ * CONNECTED -\u003e enable (release force, back to factory default)\n+ * DISCONNECTED -\u003e disable (force Hi-Z)\n+ */\n+static int\n+sit9531x_dpll_output_pin_state_on_dpll_set(const struct dpll_pin *pin,\n+\t\t\t\t\t void *pin_priv,\n+\t\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t\t void *dpll_priv,\n+\t\t\t\t\t enum dpll_pin_state state,\n+\t\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll_pin *dpin = pin_priv;\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tint rc;\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\n+\tswitch (state) {\n+\tcase DPLL_PIN_STATE_CONNECTED:\n+\t\trc = sit9531x_output_enable(sitdev, dpin-\u003eid);\n+\t\tbreak;\n+\tcase DPLL_PIN_STATE_DISCONNECTED:\n+\t\trc = sit9531x_output_disable(sitdev, dpin-\u003eid);\n+\t\tbreak;\n+\tdefault:\n+\t\trc = -EINVAL;\n+\t\tbreak;\n+\t}\n+\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (rc)\n+\t\tNL_SET_ERR_MSG(extack, \"Failed to set output pin state\");\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * sit9531x_dpll_output_pin_phase_adjust_get - read output phase adjustment\n+ *\n+ * returns cached value.\n+ */\n+static int\n+sit9531x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *pin,\n+\t\t\t\t\t void *pin_priv,\n+\t\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t\t void *dpll_priv, s32 *phase_adjust,\n+\t\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll_pin *dpin = pin_priv;\n+\n+\t*phase_adjust = dpin-\u003ephase_adjust;\n+\treturn 0;\n+}\n+\n+/*\n+ * sit9531x_dpll_output_pin_phase_adjust_set - set output phase adjustment\n+ *\n+ * Programs the per-output PRG_RST_DELAY registers for deterministic\n+ * phase offset; see sit9531x_output_phase_adjust_set() in core.c.\n+ */\n+static int\n+sit9531x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *pin,\n+\t\t\t\t\t void *pin_priv,\n+\t\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t\t void *dpll_priv, s32 phase_adjust,\n+\t\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll_pin *dpin = pin_priv;\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tint rc;\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\trc = sit9531x_output_phase_adjust_set(sitdev, dpin-\u003eid, phase_adjust);\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (rc) {\n+\t\tNL_SET_ERR_MSG(extack, \"Phase adjust failed\");\n+\t\treturn rc;\n+\t}\n+\n+\tdpin-\u003ephase_adjust = phase_adjust;\n+\treturn 0;\n+}\n+\n+static int\n+sit9531x_dpll_output_pin_esync_get(const struct dpll_pin *pin,\n+\t\t\t\t void *pin_priv,\n+\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t void *dpll_priv,\n+\t\t\t\t struct dpll_pin_esync *esync,\n+\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll_pin *dpin = pin_priv;\n+\n+\tif (!sit9531x_dpll_esync_pin_supported(dpin))\n+\t\treturn -EOPNOTSUPP;\n+\n+\tesync-\u003erange = sit9531x_esync_ranges;\n+\tesync-\u003erange_num = ARRAY_SIZE(sit9531x_esync_ranges);\n+\tesync-\u003epulse = SIT9531X_ESYNC_PULSE_DEFAULT;\n+\tesync-\u003efreq = dpin-\u003eesync_freq;\n+\n+\treturn 0;\n+}\n+\n+static int\n+sit9531x_dpll_output_pin_esync_set(const struct dpll_pin *pin,\n+\t\t\t\t void *pin_priv,\n+\t\t\t\t const struct dpll_device *dpll,\n+\t\t\t\t void *dpll_priv,\n+\t\t\t\t u64 freq,\n+\t\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct sit9531x_dpll_pin *dpin = pin_priv;\n+\tstruct sit9531x_dpll *sitdpll = dpll_priv;\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tu8 actual_pll;\n+\tint rc;\n+\n+\tif (!sit9531x_dpll_esync_pin_supported(dpin)) {\n+\t\tNL_SET_ERR_MSG(extack,\n+\t\t\t \"Embedded sync not enabled for this pin\");\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tactual_pll = sitdev-\u003eout[dpin-\u003eid].pll_idx;\n+\n+\tmutex_lock(\u0026sitdev-\u003emultiop_lock);\n+\n+\t/*\n+\t * This output is a dedicated embedded-sync pin.\n+\t * Treat freq=0 as a request to disable the entire output.\n+\t */\n+\tif (!freq) {\n+\t\trc = sit9531x_output_disable(sitdev, dpin-\u003eid);\n+\t\tif (!rc)\n+\t\t\tdpin-\u003eesync_freq = 0;\n+\t\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\t\treturn rc;\n+\t}\n+\n+\tif (freq != SIT9531X_ESYNC_FREQ_10MHZ) {\n+\t\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\t\tNL_SET_ERR_MSG(extack,\n+\t\t\t \"Only 10 MHz esync frequency is supported\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\trc = sit9531x_output_freq_set(sitdev, dpin-\u003eid, actual_pll,\n+\t\t\t\t SIT9531X_ESYNC_FREQ_10MHZ);\n+\t/*\n+\t * Program the pulse generator (PROG0 PULSE_CTRL) so the embedded-sync\n+\t * pulse is actually emitted; without it the output carries the clock\n+\t * but no esync marker. SIT9531X_ESYNC_PULSE_DEFAULT is the same duty\n+\t * the esync_get callback advertises.\n+\t */\n+\tif (!rc)\n+\t\trc = sit9531x_output_pulse_ctrl_set(sitdev, dpin-\u003eid,\n+\t\t\t\t\t\t SIT9531X_ESYNC_PULSE_DEFAULT);\n+\tif (!rc)\n+\t\trc = sit9531x_output_enable(sitdev, dpin-\u003eid);\n+\n+\tmutex_unlock(\u0026sitdev-\u003emultiop_lock);\n+\n+\tif (!rc)\n+\t\tdpin-\u003eesync_freq = SIT9531X_ESYNC_FREQ_10MHZ;\n+\n+\treturn rc;\n+}\n+\n+static const struct dpll_pin_ops sit9531x_dpll_output_pin_ops = {\n+\t.direction_get\t\t= sit9531x_dpll_output_pin_direction_get,\n+\t.frequency_get\t\t= sit9531x_dpll_output_pin_frequency_get,\n+\t.frequency_set\t\t= sit9531x_dpll_output_pin_frequency_set,\n+\t.state_on_dpll_get\t= sit9531x_dpll_output_pin_state_on_dpll_get,\n+\t.state_on_dpll_set\t= sit9531x_dpll_output_pin_state_on_dpll_set,\n+\t.phase_adjust_get\t= sit9531x_dpll_output_pin_phase_adjust_get,\n+\t.phase_adjust_set\t= sit9531x_dpll_output_pin_phase_adjust_set,\n+\t.esync_get\t\t= sit9531x_dpll_output_pin_esync_get,\n+\t.esync_set\t\t= sit9531x_dpll_output_pin_esync_set,\n+};\n+\n+const struct dpll_pin_ops *\n+sit9531x_dpll_pin_ops_get(const struct sit9531x_dpll_pin *pin)\n+{\n+\tif (!sit9531x_dpll_is_input_pin(pin)) {\n+\t\tif (sit9531x_dpll_is_intsync_src_pin(pin))\n+\t\t\treturn \u0026sit9531x_dpll_intsync_src_pin_ops;\n+\t\treturn \u0026sit9531x_dpll_output_pin_ops;\n+\t}\n+\tif (sit9531x_dpll_is_intsync_pin(pin))\n+\t\treturn \u0026sit9531x_dpll_intsync_dst_pin_ops;\n+\tif (sit9531x_dpll_is_xo_pin(pin))\n+\t\treturn \u0026sit9531x_dpll_xo_pin_ops;\n+\treturn \u0026sit9531x_dpll_input_pin_ops;\n+}\n+\n+/*\n+ * sit9531x_dpll_changes_check - check for state changes and notify\n+ *\n+ * Called from sit9531x_dev_periodic_work(). Compares current hardware\n+ * state against cached values and sends netlink notifications on changes.\n+ */\n+void sit9531x_dpll_changes_check(struct sit9531x_dpll *sitdpll)\n+{\n+\tstruct sit9531x_dev *sitdev = sitdpll-\u003edev;\n+\tenum dpll_lock_status lock_status;\n+\tstruct sit9531x_dpll_pin *pin;\n+\tint rc;\n+\n+\trc = sit9531x_dpll_lock_status_get(sitdpll-\u003edpll_dev, sitdpll,\n+\t\t\t\t\t \u0026lock_status, NULL, NULL);\n+\tif (rc) {\n+\t\tdev_err(sitdev-\u003edev, \"Failed to get DPLL%u lock status: %d\\n\",\n+\t\t\tsitdpll-\u003eid, rc);\n+\t\treturn;\n+\t}\n+\n+\t/* If lock status changed, notify DPLL core */\n+\tif (sitdpll-\u003elock_status != lock_status) {\n+\t\tsitdpll-\u003elock_status = lock_status;\n+\t\tdpll_device_change_ntf(sitdpll-\u003edpll_dev);\n+\t}\n+\n+\tlist_for_each_entry(pin, \u0026sitdpll-\u003epins, list) {\n+\t\tconst struct dpll_pin_ops *ops;\n+\t\tenum dpll_pin_state state;\n+\n+\t\t/*\n+\t\t * Poll input pins whose state can change autonomously: regular\n+\t\t * references and the INTSYNC destination pin. Outputs (incl.\n+\t\t * the INTSYNC source) change only through their own set\n+\t\t * callback and the XO is permanently connected, so skip those.\n+\t\t * Each pin's own state_on_dpll_get resolves to the right getter.\n+\t\t */\n+\t\tif (!sit9531x_dpll_is_input_pin(pin) ||\n+\t\t sit9531x_dpll_is_xo_pin(pin))\n+\t\t\tcontinue;\n+\n+\t\tops = sit9531x_dpll_pin_ops_get(pin);\n+\t\trc = ops-\u003estate_on_dpll_get(pin-\u003edpll_pin, pin,\n+\t\t\t\t\t sitdpll-\u003edpll_dev, sitdpll,\n+\t\t\t\t\t \u0026state, NULL);\n+\t\tif (rc)\n+\t\t\tcontinue;\n+\n+\t\tif (state != pin-\u003epin_state) {\n+\t\t\tdev_dbg(sitdev-\u003edev, \"%s state changed: %u-\u003e%u\\n\",\n+\t\t\t\tpin-\u003elabel, pin-\u003epin_state, state);\n+\t\t\tpin-\u003epin_state = state;\n+\t\t\tdpll_pin_change_ntf(pin-\u003edpll_pin);\n+\t\t}\n+\t}\n+}\ndiff --git a/drivers/dpll/sit9531x/dpll.h b/drivers/dpll/sit9531x/dpll.h\nnew file mode 100644\nindex 0000000000000..e5eef4514bcdd\n--- /dev/null\n+++ b/drivers/dpll/sit9531x/dpll.h\n@@ -0,0 +1,69 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * SiTime SiT9531x DPLL subsystem interface\n+ *\n+ * Copyright (C) 2026 SiTime Corp.\n+ * Author: Ali Rouhi \u003carouhi@sitime.com\u003e\n+ * Author: Oleg Zadorozhnyi \u003cOleg.Zadorozhnyi@devoxsoftware.com\u003e\n+ *\n+ * DPLL device and pin structures, and function declarations for\n+ * the DPLL registration and callback layer.\n+ */\n+\n+#ifndef _SIT9531X_DPLL_H\n+#define _SIT9531X_DPLL_H\n+\n+#include \u003clinux/dpll.h\u003e\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+struct sit9531x_dev;\n+\n+/* Per-pin DPLL state. */\n+struct sit9531x_dpll_pin {\n+\tstruct list_head\t\tlist;\n+\tstruct sit9531x_dpll\t\t*dpll;\n+\tstruct dpll_pin\t\t\t*dpll_pin;\n+\tdpll_tracker\t\t\ttracker;\n+\tstruct fwnode_handle\t\t*fwnode;\n+\tchar\t\t\t\tlabel[8];\t/* \"IN0\", \"OUT3\" */\n+\tenum dpll_pin_direction\t\tdir;\n+\tu8\t\t\t\tid;\t\t/* hardware index */\n+\tu8\t\t\t\tprio;\n+\tenum dpll_pin_state\t\tpin_state;\n+\ts32\t\t\t\tphase_adjust;\t/* picoseconds */\n+\ts64\t\t\t\tphase_offset;\t/* picoseconds */\n+\tbool\t\t\t\tesync_control;\n+\tu64\t\t\t\tesync_freq;\t/* 0 == disabled */\n+};\n+\n+/* Per-PLL DPLL device state. */\n+struct sit9531x_dpll {\n+\tstruct list_head\t\tlist;\n+\tstruct sit9531x_dev\t\t*dev;\n+\tstruct dpll_device\t\t*dpll_dev;\n+\tdpll_tracker\t\t\ttracker;\n+\tstruct dpll_device_ops\t\tops;\t/* per-instance copy */\n+\tstruct list_head\t\tpins;\n+\tu8\t\t\t\tid;\t/* 0 = PLLA .. 3 = PLLD */\n+\tenum dpll_lock_status\t\tlock_status;\n+};\n+\n+/* ---- DPLL allocation and registration ---- */\n+/*\n+ * The callback tables stay with the callbacks; the registration code that\n+ * hands them to the subsystem lives next to probe() in core.c.\n+ */\n+extern const struct dpll_device_ops sit9531x_dpll_device_ops;\n+const struct dpll_pin_ops *\n+sit9531x_dpll_pin_ops_get(const struct sit9531x_dpll_pin *pin);\n+\n+struct sit9531x_dpll *sit9531x_dpll_alloc(struct sit9531x_dev *sitdev, u8 ch);\n+void sit9531x_dpll_free(struct sit9531x_dpll *sitdpll);\n+int sit9531x_dpll_register(struct sit9531x_dpll *sitdpll);\n+void sit9531x_dpll_unregister(struct sit9531x_dpll *sitdpll);\n+\n+/* ---- Periodic change detection ---- */\n+void sit9531x_dpll_changes_check(struct sit9531x_dpll *sitdpll);\n+\n+#endif /* _SIT9531X_DPLL_H */\ndiff --git a/drivers/dpll/sit9531x/prop.c b/drivers/dpll/sit9531x/prop.c\nnew file mode 100644\nindex 0000000000000..8a0105c8c6472\n--- /dev/null\n+++ b/drivers/dpll/sit9531x/prop.c\n@@ -0,0 +1,397 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * SiTime SiT9531x firmware node property parsing\n+ *\n+ * Copyright (C) 2026 SiTime Corp.\n+ * Author: Ali Rouhi \u003carouhi@sitime.com\u003e\n+ * Author: Oleg Zadorozhnyi \u003cOleg.Zadorozhnyi@devoxsoftware.com\u003e\n+ *\n+ * Retrieves pin properties from Device Tree firmware nodes (or\n+ * applies defaults when no firmware node exists).\n+ */\n+\n+#include \u003clinux/dev_printk.h\u003e\n+#include \u003clinux/dpll.h\u003e\n+#include \u003clinux/err.h\u003e\n+#include \u003clinux/fwnode.h\u003e\n+#include \u003clinux/property.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/string.h\u003e\n+\n+#include \"core.h\"\n+#include \"prop.h\"\n+\n+/*\n+ * sit9531x_input_pin_label - fill the package label for an input pin\n+ *\n+ * Split out so input-naming changes stay local to this helper.\n+ */\n+static void sit9531x_input_pin_label(struct sit9531x_dev *sitdev,\n+\t\t\t\t struct sit9531x_pin_props *props, u8 id)\n+{\n+\tu8 pair = sit9531x_input_pair(id);\n+\n+\tif (sitdev-\u003eref[id].sig_mode == SIT9531X_MODE_DE)\n+\t\tsnprintf(props-\u003epackage_label,\n+\t\t\t sizeof(props-\u003epackage_label), \"IN%u\", pair);\n+\telse\n+\t\tsnprintf(props-\u003epackage_label,\n+\t\t\t sizeof(props-\u003epackage_label), \"IN%u%c\", pair,\n+\t\t\t sit9531x_input_is_n(id) ? 'N' : 'P');\n+}\n+\n+/*\n+ * sit9531x_prop_pin_package_label_set - generate package label\n+ * @dir:\tpin direction\n+ * @id:\t\tpin index\n+ *\n+ * Generates a package label string. Output pins are named \"OUT0\",\n+ * \"OUT1\", ... Input pins are named after the physical pair and lane:\n+ * \"IN0P\", \"IN0N\", \"IN1P\", ... for single-ended lanes, or \"IN0\",\n+ * \"IN1\", ... when the pair is configured differential (the N lane is\n+ * not registered in that case).\n+ */\n+static void\n+sit9531x_prop_pin_package_label_set(struct sit9531x_dev *sitdev,\n+\t\t\t\t struct sit9531x_pin_props *props,\n+\t\t\t\t enum dpll_pin_direction dir, u8 id)\n+{\n+\t/* The internal INTSYNC pin has a fixed label */\n+\tif (dir == DPLL_PIN_DIRECTION_INPUT \u0026\u0026\n+\t id == SIT9531X_INTSYNC_PIN_ID) {\n+\t\tstrscpy(props-\u003epackage_label, \"INTSYNC\",\n+\t\t\tsizeof(props-\u003epackage_label));\n+\t\tprops-\u003edpll_props.package_label = props-\u003epackage_label;\n+\t\treturn;\n+\t}\n+\n+\t/* The internal XO reference has a fixed label */\n+\tif (dir == DPLL_PIN_DIRECTION_INPUT \u0026\u0026 id == SIT9531X_MAX_INPUTS) {\n+\t\tstrscpy(props-\u003epackage_label, \"XO\",\n+\t\t\tsizeof(props-\u003epackage_label));\n+\t\tprops-\u003edpll_props.package_label = props-\u003epackage_label;\n+\t\treturn;\n+\t}\n+\n+\t/* The internal INTSYNC source (output) pin has a fixed label */\n+\tif (dir == DPLL_PIN_DIRECTION_OUTPUT \u0026\u0026\n+\t id == SIT9531X_INTSYNC_OUT_PIN_ID) {\n+\t\tstrscpy(props-\u003epackage_label, \"SYNCOUT\",\n+\t\t\tsizeof(props-\u003epackage_label));\n+\t\tprops-\u003edpll_props.package_label = props-\u003epackage_label;\n+\t\treturn;\n+\t}\n+\n+\tif (dir == DPLL_PIN_DIRECTION_INPUT)\n+\t\tsit9531x_input_pin_label(sitdev, props, id);\n+\telse\n+\t\tsnprintf(props-\u003epackage_label, sizeof(props-\u003epackage_label),\n+\t\t\t \"OUT%u\", id);\n+\n+\tprops-\u003edpll_props.package_label = props-\u003epackage_label;\n+}\n+\n+/*\n+ * sit9531x_prop_pin_fwnode_get - find firmware node for a pin\n+ * @dir:\tpin direction\n+ * @id:\t\tpin index\n+ *\n+ * Searches for input-pins/output-pins child nodes in DT, looking\n+ * for a child whose \"reg\" property matches @id.\n+ *\n+ * Return: 0 on success, -ENOENT if no firmware node exists\n+ */\n+static int\n+sit9531x_prop_pin_fwnode_get(struct sit9531x_dev *sitdev,\n+\t\t\t struct sit9531x_pin_props *props,\n+\t\t\t enum dpll_pin_direction dir, u8 id)\n+{\n+\tstruct fwnode_handle *pins_node, *pin_node;\n+\tconst char *node_name;\n+\n+\tif (dir == DPLL_PIN_DIRECTION_INPUT)\n+\t\tnode_name = \"input-pins\";\n+\telse\n+\t\tnode_name = \"output-pins\";\n+\n+\tpins_node = device_get_named_child_node(sitdev-\u003edev, node_name);\n+\tif (!pins_node) {\n+\t\tdev_dbg(sitdev-\u003edev, \"'%s' sub-node is missing\\n\", node_name);\n+\t\treturn -ENOENT;\n+\t}\n+\n+\t/* Enumerate child pin nodes and find the requested one */\n+\tfwnode_for_each_child_node(pins_node, pin_node) {\n+\t\tu32 reg;\n+\n+\t\tif (fwnode_property_read_u32(pin_node, \"reg\", \u0026reg))\n+\t\t\tcontinue;\n+\n+\t\tif (id == reg)\n+\t\t\tbreak;\n+\t}\n+\n+\tfwnode_handle_put(pins_node);\n+\n+\tprops-\u003efwnode = pin_node;\n+\n+\tdev_dbg(sitdev-\u003edev, \"Firmware node for %s %sfound\\n\",\n+\t\tprops-\u003epackage_label, pin_node ? \"\" : \"NOT \");\n+\n+\treturn pin_node ? 0 : -ENOENT;\n+}\n+\n+/*\n+ * sit9531x_pin_props_get - get pin properties for a given pin\n+ * @dir:\tpin direction (INPUT or OUTPUT)\n+ * @index:\tpin index\n+ *\n+ * Allocates a pin properties structure, generates a package label,\n+ * looks up the firmware node if available, and reads optional\n+ * properties (label, connection-type, supported-frequencies-hz,\n+ * esync-control).\n+ *\n+ * Call sit9531x_pin_props_put() to free the returned structure.\n+ *\n+ * Return: pointer to pin properties on success, error pointer on error\n+ */\n+struct sit9531x_pin_props *\n+sit9531x_pin_props_get(struct sit9531x_dev *sitdev,\n+\t\t enum dpll_pin_direction dir, u8 index)\n+{\n+\tstruct dpll_pin_frequency *ranges;\n+\tstruct sit9531x_pin_props *props;\n+\tint i, j, num_freqs = 0, rc;\n+\tu64 *freqs = NULL;\n+\tconst char *type;\n+\tu32 curr_freq;\n+\n+\tprops = kzalloc_obj(*props, GFP_KERNEL);\n+\tif (!props)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tif (dir == DPLL_PIN_DIRECTION_INPUT \u0026\u0026\n+\t index == SIT9531X_INTSYNC_PIN_ID) {\n+\t\t/*\n+\t\t * INTSYNC destination pin: a PLL locks to the INTSYNC net as a\n+\t\t * reference, so it can be connected and re-prioritised.\n+\t\t */\n+\t\tprops-\u003edpll_props.type = DPLL_PIN_TYPE_INT_OSCILLATOR;\n+\t\tprops-\u003edpll_props.capabilities =\n+\t\t\tDPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE |\n+\t\t\tDPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;\n+\t\tcurr_freq = 0;\n+\t} else if (dir == DPLL_PIN_DIRECTION_OUTPUT \u0026\u0026\n+\t\t index == SIT9531X_INTSYNC_OUT_PIN_ID) {\n+\t\t/*\n+\t\t * INTSYNC source pin: a PLL drives the INTSYNC net. It can be\n+\t\t * connected/disconnected but carries no priority (driving the\n+\t\t * net is not a reference selection) and no frequency.\n+\t\t */\n+\t\tprops-\u003edpll_props.type = DPLL_PIN_TYPE_INT_OSCILLATOR;\n+\t\tprops-\u003edpll_props.capabilities =\n+\t\t\tDPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;\n+\t\tcurr_freq = 0;\n+\t} else if (dir == DPLL_PIN_DIRECTION_INPUT \u0026\u0026\n+\t\t index == SIT9531X_MAX_INPUTS) {\n+\t\t/* The XO reference is fixed: no state or priority control. */\n+\t\tprops-\u003edpll_props.type = DPLL_PIN_TYPE_INT_OSCILLATOR;\n+\t\tprops-\u003edpll_props.capabilities = 0;\n+\t\tsitdev-\u003eref[index].freq = sitdev-\u003extal_freq;\n+\t\tcurr_freq = sitdev-\u003extal_freq;\n+\t} else if (dir == DPLL_PIN_DIRECTION_INPUT) {\n+\t\tprops-\u003edpll_props.type = DPLL_PIN_TYPE_EXT;\n+\t\tprops-\u003edpll_props.capabilities =\n+\t\t\tDPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE |\n+\t\t\tDPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;\n+\t\tcurr_freq = sitdev-\u003eref[index].freq;\n+\t} else {\n+\t\tprops-\u003edpll_props.type = DPLL_PIN_TYPE_GNSS;\n+\t\tprops-\u003edpll_props.capabilities =\n+\t\t\tDPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;\n+\t\tcurr_freq = sitdev-\u003eout[index].freq;\n+\t}\n+\n+\t/* Allow phase-adjust over +/-1 ms window. The subsystem rejects\n+\t * pin_set(phase-adjust, X) when X falls outside [min, max], so\n+\t * leaving these at 0 silently blocks every netlink call. 1 ms is\n+\t * well beyond the DCO dynamic range but costs nothing.\n+\t */\n+\tprops-\u003edpll_props.phase_range.min = -1000000000; /* -1 ms in ps */\n+\tprops-\u003edpll_props.phase_range.max = 1000000000; /* +1 ms in ps */\n+\n+\t/* Generate package label */\n+\tsit9531x_prop_pin_package_label_set(sitdev, props, dir, index);\n+\n+\t/*\n+\t * Both INTSYNC pins are internal to the chip and have no board-level\n+\t * wiring, so they take no properties from the firmware node.\n+\t */\n+\tif (dir == DPLL_PIN_DIRECTION_INPUT \u0026\u0026\n+\t index == SIT9531X_INTSYNC_PIN_ID)\n+\t\tgoto skip_fwnode_props;\n+\tif (dir == DPLL_PIN_DIRECTION_OUTPUT \u0026\u0026\n+\t index == SIT9531X_INTSYNC_OUT_PIN_ID)\n+\t\tgoto skip_fwnode_props;\n+\n+\trc = sit9531x_prop_pin_fwnode_get(sitdev, props, dir, index);\n+\tif (rc)\n+\t\tgoto skip_fwnode_props;\n+\n+\t/* Look for \"label\" property -\u003e board label */\n+\tfwnode_property_read_string(props-\u003efwnode, \"label\",\n+\t\t\t\t \u0026props-\u003edpll_props.board_label);\n+\n+\t/* Look for \"connection-type\" property -\u003e pin type enum */\n+\tif (!fwnode_property_read_string(props-\u003efwnode, \"connection-type\",\n+\t\t\t\t\t \u0026type)) {\n+\t\tif (!strcmp(type, \"ext\"))\n+\t\t\tprops-\u003edpll_props.type = DPLL_PIN_TYPE_EXT;\n+\t\telse if (!strcmp(type, \"gnss\"))\n+\t\t\tprops-\u003edpll_props.type = DPLL_PIN_TYPE_GNSS;\n+\t\telse if (!strcmp(type, \"int\") ||\n+\t\t\t !strcmp(type, \"int-oscillator\"))\n+\t\t\tprops-\u003edpll_props.type = DPLL_PIN_TYPE_INT_OSCILLATOR;\n+\t\telse if (!strcmp(type, \"synce\") ||\n+\t\t\t !strcmp(type, \"synce-eth-port\"))\n+\t\t\tprops-\u003edpll_props.type = DPLL_PIN_TYPE_SYNCE_ETH_PORT;\n+\t\telse if (!strcmp(type, \"mux\"))\n+\t\t\tprops-\u003edpll_props.type = DPLL_PIN_TYPE_MUX;\n+\t\telse\n+\t\t\tdev_warn(sitdev-\u003edev,\n+\t\t\t\t \"Unknown pin type '%s'\\n\", type);\n+\t}\n+\n+\tprops-\u003eesync_control =\n+\t\tfwnode_property_read_bool(props-\u003efwnode, \"esync-control\");\n+\n+\tnum_freqs = fwnode_property_count_u64(props-\u003efwnode,\n+\t\t\t\t\t \"supported-frequencies-hz\");\n+\tif (num_freqs \u003c= 0) {\n+\t\tnum_freqs = 0;\n+\t\tgoto skip_fwnode_props;\n+\t}\n+\n+\tfreqs = kcalloc(num_freqs, sizeof(*freqs), GFP_KERNEL);\n+\tif (!freqs) {\n+\t\trc = -ENOMEM;\n+\t\tgoto err_alloc_freqs;\n+\t}\n+\n+\tfwnode_property_read_u64_array(props-\u003efwnode,\n+\t\t\t\t \"supported-frequencies-hz\",\n+\t\t\t\t freqs, num_freqs);\n+\n+\t/*\n+\t * Seed the runtime ref-\u003efreq / out-\u003efreq with the first DT-listed\n+\t * supported frequency so the netlink frequency_get callback reports\n+\t * a sane initial value before any pin_set occurs. DT lists the\n+\t * physically-wired reference frequency for each input pin and the\n+\t * default output frequency for each output pin.\n+\t */\n+\tif (num_freqs \u003e 0) {\n+\t\tif (dir == DPLL_PIN_DIRECTION_INPUT)\n+\t\t\tsitdev-\u003eref[index].freq = (u32)freqs[0];\n+\t\telse\n+\t\t\tsitdev-\u003eout[index].freq = (u32)freqs[0];\n+\t\tcurr_freq = (u32)freqs[0];\n+\t}\n+\n+skip_fwnode_props:\n+\t/* Neither INTSYNC pin carries a frequency attribute */\n+\tif (dir == DPLL_PIN_DIRECTION_INPUT \u0026\u0026\n+\t index == SIT9531X_INTSYNC_PIN_ID)\n+\t\treturn props;\n+\tif (dir == DPLL_PIN_DIRECTION_OUTPUT \u0026\u0026\n+\t index == SIT9531X_INTSYNC_OUT_PIN_ID)\n+\t\treturn props;\n+\n+\t/* Allocate frequency ranges list -- DT discrete entries + current\n+\t * freq + one catch-all wide range so the subsystem never pre-\n+\t * rejects a frequency_set call. The chip's real admissible set\n+\t * is bounded by VCO / divider math in sit9531x_output_freq_set().\n+\t */\n+\tranges = kcalloc(num_freqs + 2, sizeof(*ranges), GFP_KERNEL);\n+\tif (!ranges) {\n+\t\trc = -ENOMEM;\n+\t\tgoto err_alloc_ranges;\n+\t}\n+\n+\t/* Current freq as first entry */\n+\tranges[0] = (struct dpll_pin_frequency)DPLL_PIN_FREQUENCY(curr_freq);\n+\tj = 1;\n+\n+\tfor (i = 0; i \u003c num_freqs; i++) {\n+\t\tstruct dpll_pin_frequency freq = DPLL_PIN_FREQUENCY(freqs[i]);\n+\n+\t\tif (freqs[i] == curr_freq)\n+\t\t\tcontinue;\n+\t\tranges[j++] = freq;\n+\t}\n+\n+\t/* Always append a wide catch-all range */\n+\tranges[j].min = 1;\n+\tranges[j].max = 1000000000ULL; /* 1 GHz */\n+\tj++;\n+\n+\tprops-\u003edpll_props.freq_supported = ranges;\n+\tprops-\u003edpll_props.freq_supported_num = j;\n+\n+\tkfree(freqs);\n+\n+\treturn props;\n+\n+err_alloc_ranges:\n+\tkfree(freqs);\n+err_alloc_freqs:\n+\tfwnode_handle_put(props-\u003efwnode);\n+\tkfree(props);\n+\n+\treturn ERR_PTR(rc);\n+}\n+\n+/*\n+ * sit9531x_pin_props_put - release pin properties\n+ * @props:\tpin properties to free\n+ */\n+void sit9531x_pin_props_put(struct sit9531x_pin_props *props)\n+{\n+\tkfree(props-\u003edpll_props.freq_supported);\n+\n+\tif (props-\u003efwnode)\n+\t\tfwnode_handle_put(props-\u003efwnode);\n+\n+\tkfree(props);\n+}\n+\n+/*\n+ * sit9531x_prop_dpll_type_get - get DPLL channel type from firmware\n+ * @index:\tDPLL channel index (0-3)\n+ *\n+ * Reads the \"dpll-types\" string array property from the firmware node\n+ * and returns the corresponding DPLL type enum.\n+ *\n+ * Return: DPLL type for the given channel (default: DPLL_TYPE_PPS)\n+ */\n+enum dpll_type\n+sit9531x_prop_dpll_type_get(struct sit9531x_dev *sitdev, u8 index)\n+{\n+\tconst char *types[SIT9531X_NUM_PLLS];\n+\tint count;\n+\n+\tcount = device_property_read_string_array(sitdev-\u003edev, \"dpll-types\",\n+\t\t\t\t\t\t types, ARRAY_SIZE(types));\n+\n+\tif (index \u003e= count)\n+\t\treturn DPLL_TYPE_PPS;\n+\n+\tif (!strcmp(types[index], \"pps\"))\n+\t\treturn DPLL_TYPE_PPS;\n+\telse if (!strcmp(types[index], \"eec\"))\n+\t\treturn DPLL_TYPE_EEC;\n+\n+\tdev_warn(sitdev-\u003edev, \"Unknown DPLL type '%s', using default\\n\",\n+\t\t types[index]);\n+\n+\treturn DPLL_TYPE_PPS;\n+}\ndiff --git a/drivers/dpll/sit9531x/prop.h b/drivers/dpll/sit9531x/prop.h\nnew file mode 100644\nindex 0000000000000..f7f1c854b9558\n--- /dev/null\n+++ b/drivers/dpll/sit9531x/prop.h\n@@ -0,0 +1,39 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * SiTime SiT9531x firmware node property parsing\n+ *\n+ * Copyright (C) 2026 SiTime Corp.\n+ * Author: Ali Rouhi \u003carouhi@sitime.com\u003e\n+ * Author: Oleg Zadorozhnyi \u003cOleg.Zadorozhnyi@devoxsoftware.com\u003e\n+ */\n+\n+#ifndef _SIT9531X_PROP_H\n+#define _SIT9531X_PROP_H\n+\n+#include \u003clinux/dpll.h\u003e\n+#include \u003clinux/fwnode.h\u003e\n+\n+struct sit9531x_dev;\n+\n+/*\n+ * struct sit9531x_pin_props - pin properties from firmware\n+ * @fwnode:\t\tfirmware node handle (NULL if no DT node)\n+ * @dpll_props:\t\tDPLL core pin properties\n+ * @package_label:\tpin package label (e.g. \"IN0\", \"OUT3\")\n+ * @esync_control:\tembedded sync is controllable\n+ */\n+struct sit9531x_pin_props {\n+\tstruct fwnode_handle\t\t*fwnode;\n+\tstruct dpll_pin_properties\tdpll_props;\n+\tchar\t\t\t\tpackage_label[8];\n+\tbool\t\t\t\tesync_control;\n+};\n+\n+enum dpll_type sit9531x_prop_dpll_type_get(struct sit9531x_dev *sitdev,\n+\t\t\t\t\t u8 index);\n+struct sit9531x_pin_props *sit9531x_pin_props_get(struct sit9531x_dev *sitdev,\n+\t\t\t\t\t\t enum dpll_pin_direction dir,\n+\t\t\t\t\t\t u8 index);\n+void sit9531x_pin_props_put(struct sit9531x_pin_props *props);\n+\n+#endif /* _SIT9531X_PROP_H */\ndiff --git a/drivers/dpll/sit9531x/regs.h b/drivers/dpll/sit9531x/regs.h\nnew file mode 100644\nindex 0000000000000..5c3dbeefd86b6\n--- /dev/null\n+++ b/drivers/dpll/sit9531x/regs.h\n@@ -0,0 +1,371 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * SiTime SiT9531x register definitions\n+ *\n+ * Copyright (C) 2026 SiTime Corp.\n+ * Author: Ali Rouhi \u003carouhi@sitime.com\u003e\n+ * Author: Oleg Zadorozhnyi \u003cOleg.Zadorozhnyi@devoxsoftware.com\u003e\n+ */\n+\n+#ifndef _SIT9531X_REGS_H\n+#define _SIT9531X_REGS_H\n+\n+/*\n+ * I2C register model:\n+ * - Page select register at offset 0x01\n+ * - Each page has 128 registers (0x00-0x7F)\n+ * - Some pages are paired (e.g. 0x0A/0x1A for PLLA)\n+ */\n+#define SIT9531X_PAGE_SEL\t\t0xFF\n+#define SIT9531X_PAGE_SIZE\t\t0x100\n+#define SIT9531X_NUM_PAGES\t\t32\n+\n+/* Helper macros for page:offset addressing */\n+#define SIT9531X_REG(_page, _offset)\t\t(((_page) \u003c\u003c 8) | (_offset))\n+#define SIT9531X_REG_PAGE(_reg)\t\t((_reg) \u003e\u003e 8)\n+#define SIT9531X_REG_OFFSET(_reg)\t\t((_reg) \u0026 0xFF)\n+\n+/* ---- Page definitions ---- */\n+#define SIT9531X_PAGE_MAINSYS0\t\t0x00\n+#define SIT9531X_PAGE_MAINSYS1\t\t0x01\n+#define SIT9531X_PAGE_INPUTSYS\t\t0x02\n+#define SIT9531X_PAGE_OUTSYS0\t\t0x03\n+#define SIT9531X_PAGE_OUTSYS1\t\t0x04\n+#define SIT9531X_PAGE_CLKMON0\t\t0x06\n+#define SIT9531X_PAGE_CLKMON1\t\t0x07\n+#define SIT9531X_PAGE_PLLA\t\t\t0x0A\n+#define SIT9531X_PAGE_PLLA_EXT\t\t0x1A\n+#define SIT9531X_PAGE_PLLB\t\t\t0x0B\n+#define SIT9531X_PAGE_PLLB_EXT\t\t0x1B\n+#define SIT9531X_PAGE_PLLC\t\t\t0x0C\n+#define SIT9531X_PAGE_PLLC_EXT\t\t0x1C\n+#define SIT9531X_PAGE_PLLD\t\t\t0x0D\n+#define SIT9531X_PAGE_PLLD_EXT\t\t0x1D\n+\n+/* PLL index to page mapping */\n+#define SIT9531X_PLL_PAGE(_idx) \\\n+\t(SIT9531X_PAGE_PLLA + (_idx))\n+\n+/*\n+ * VARIANT_ID is a single byte at page 0 reg 0x02 (95317 = 0x17, 95316 = 0x31).\n+ * Reg 0x03 carries an unrelated revision byte and must not be combined into\n+ * the variant identifier.\n+ */\n+#define SIT9531X_REG_VARIANT_ID\t\tSIT9531X_REG(0x00, 0x02)\n+\n+/* DCO trigger pulse timing: minimum 6 ns required by hardware */\n+\n+#define SIT9531X_REG_HOLDOVER_HISTORY\tSIT9531X_REG(0x00, 0x58)\n+\n+/* Page 0 -- PLL inner loop loss-of-lock */\n+#define SIT9531X_REG_PLL_INNER_LOL_STATUS\tSIT9531X_REG(0x00, 0x92)\n+#define SIT9531X_REG_PLL_INNER_LOL_NOTIF\tSIT9531X_REG(0x00, 0x93)\n+\n+/* Page 0 -- Clock monitor PLL / XO status */\n+#define SIT9531X_REG_CMON_NOTIF\t\tSIT9531X_REG(0x00, 0x9E)\n+\n+/* Page 0 -- PLL outer-loop loss-of-lock */\n+#define SIT9531X_REG_OUTER_LOL_STATUS\tSIT9531X_REG(0x00, 0x06)\n+#define SIT9531X_REG_OUTER_LOL_NOTIF\t\tSIT9531X_REG(0x00, 0x07)\n+\n+/* Page 0 -- PLL holdover freeze status */\n+#define SIT9531X_REG_HO_FREEZE_STATUS\tSIT9531X_REG(0x00, 0x0A)\n+#define SIT9531X_REG_HO_FREEZE_NOTIF\tSIT9531X_REG(0x00, 0x0B)\n+\n+/* Page 0 -- INTSYNC (inter-PLL synchronization) global enable */\n+#define SIT9531X_REG_INTSYNC_GLOBAL\t\tSIT9531X_REG(0x00, 0x40)\n+#define SIT9531X_INTSYNC_EN_BIT\t\t6\n+\n+/*\n+ * Priority table: 6 registers per PLL, each holds two priority slots\n+ * nibble-packed. The register holding slots 2n and 2n+1 keeps the\n+ * earlier slot (CLK_SPARE\u003c2n\u003eSEL_PLL) in [7:4] and the later one in\n+ * [3:0].\n+ *\n+ * Base registers for PLLA: 0x16-0x1B (slots 0-10 plus the\n+ * active-reference nibble).\n+ * For PLL N: base + 6 * N (e.g. PLLB starts at 0x1C).\n+ *\n+ * Input source encoding (4-bit value):\n+ * 0=IN0P, 1=IN1P, 2=IN2P, 3=IN3P, 4=IN4P,\n+ * 5=OCXO, 6=INTSYNC,\n+ * 7=IN0N, 8=IN1N, 9=IN2N, 10=IN3N, 11=IN4N\n+ */\n+#define SIT9531X_PAGE_PRIOSYS\t\t0x01\n+#define SIT9531X_PRIO_BASE_REG\t\t0x16\n+#define SIT9531X_PRIO_REGS_PER_PLL\t\t6\n+#define SIT9531X_PRIO_SLOTS_PER_REG\t\t2\n+/*\n+ * 11 priority slots, CLK_SPARE0SEL_PLL through CLK_SPARE10SEL_PLL.\n+ * The twelfth nibble of the block is not a slot: it is\n+ * CLK_ACTIVESEL_PLL, see SIT9531X_PRIO_ACTIVESEL_OFF below.\n+ */\n+#define SIT9531X_PRIO_MAX_SLOTS\t\t11\n+/* Number of source encodings (0-11), unrelated to the slot count */\n+#define SIT9531X_PRIO_NUM_SRC\t\t12\n+#define SIT9531X_PRIO_NIBBLE_MASK\t\t0x0F\n+#define SIT9531X_PRIO_HI_SHIFT\t\t4\n+/* Input source encoding values (see table above) */\n+#define SIT9531X_PRIO_SRC_OCXO\t\t5\n+#define SIT9531X_PRIO_SRC_INTSYNC\t\t6\n+#define SIT9531X_PRIO_SRC_N_BASE\t\t7\n+/*\n+ * The last register of each PLL's priority block holds, in its low\n+ * nibble, the input source the PLL has currently selected as its\n+ * active reference (CLK_ACTIVESEL_PLL, same 4-bit encoding as above).\n+ */\n+#define SIT9531X_PRIO_ACTIVESEL_OFF\t\t5\n+\n+/*\n+ * Page 0 -- PRG_Directives_GENERIC_0, the main system's programming\n+ * directive register. Every page carries its own copy of this\n+ * register at offset 0x0F with the same bit layout:\n+ *\n+ * bit 6 proceed to loop lock / active state from the PRG_CMD state\n+ * bit 4 update the NVM bank from the efuse contents\n+ * bit 3 read the efuse\n+ * bit 2 program the efuse\n+ * bit 1 small change update (SIT9531X_SMALL_UPDATE_CMD)\n+ * bit 0 escape to the PRG_CMD state\n+ *\n+ * Only the small change update belongs in a runtime path; the efuse\n+ * and NVM directives touch non-volatile storage.\n+ */\n+#define SIT9531X_REG_GLOBAL_UPDATE\t\tSIT9531X_REG(0x00, 0x0F)\n+#define SIT9531X_SMALL_UPDATE_CMD\t\t0x02\n+\n+/* PLL holdover control (PLL page offset) */\n+#define SIT9531X_PLL_REG_HO_CTRL\t\t0x6F\n+#define SIT9531X_PLL_HO_FORCE_BIT\t\t4\n+\n+/* One bit per input PAIR (bit 0 = CLKIN0, ..., bit 3 = CLKIN3) */\n+#define SIT9531X_REG_IN_DE_FORCE\t\tSIT9531X_REG(0x02, 0xE8)\n+#define SIT9531X_REG_IN_DE_STATE\t\tSIT9531X_REG(0x02, 0xE9)\n+#define SIT9531X_REG_IN_SEP_FORCE\t\tSIT9531X_REG(0x02, 0xEA)\n+#define SIT9531X_REG_IN_SEP_STATE\t\tSIT9531X_REG(0x02, 0xEB)\n+#define SIT9531X_REG_IN_SEN_FORCE\t\tSIT9531X_REG(0x02, 0xF2)\n+#define SIT9531X_REG_IN_SEN_STATE\t\tSIT9531X_REG(0x02, 0xF3)\n+\n+/*\n+ * One register per input pair at 0x1B + 0x10 * pair\n+ * (CLKIN0 = 0x1B, CLKIN1 = 0x2B, CLKIN2 = 0x3B, CLKIN3 = 0x4B).\n+ * SE_P_EN/SE_N_EN set means the corresponding lane is configured\n+ * single-ended; both clear means the pair runs differential.\n+ */\n+#define SIT9531X_REG_IN_MODE(_pair)\t\t\\\n+\tSIT9531X_REG(0x02, 0x1B + 0x10 * (_pair))\n+#define SIT9531X_IN_MODE_SE_P_EN\t\tBIT(0)\n+#define SIT9531X_IN_MODE_SE_N_EN\t\tBIT(1)\n+\n+/* ---- Page 0x03 (Output System) registers -- Hi-Z control ---- */\n+#define SIT9531X_REG_HIZ_DIFF_07_MASK\tSIT9531X_REG(0x03, 0xF2)\n+#define SIT9531X_REG_HIZ_DIFF_07_STATE\tSIT9531X_REG(0x03, 0xF3)\n+#define SIT9531X_REG_HIZ_DIFF_811_MASK\tSIT9531X_REG(0x03, 0xF4)\n+#define SIT9531X_REG_HIZ_DIFF_811_STATE\tSIT9531X_REG(0x03, 0xF5)\n+#define SIT9531X_REG_HIZ_SE_07_MASK\t\tSIT9531X_REG(0x03, 0xF8)\n+#define SIT9531X_REG_HIZ_SE_07_STATE\t\tSIT9531X_REG(0x03, 0xF9)\n+#define SIT9531X_REG_HIZ_SE_811_MASK\t\tSIT9531X_REG(0x03, 0xFA)\n+#define SIT9531X_REG_HIZ_SE_811_STATE\tSIT9531X_REG(0x03, 0xFB)\n+\n+/*\n+ * Output divider registers in Pages 3/4. Each output has a 34-bit\n+ * integer divider mapped to 5 bytes (LSB at base reg, MSB at base-4).\n+ * Outputs 0-5 are on Page 3, outputs 6-11 are on Page 4.\n+ *\n+ * The base register for slot N within a page is:\n+ * clkout_odr_divn_base[slot] = { 0x14, 0x24, 0x34, 0x44, 0x54, 0x64 }\n+ *\n+ * Layout: base=LSB, base-1, base-2, base-3, base-4[1:0]=MSB.\n+ *\n+ * Per-chip clkout_map[] translates output index to slot position.\n+ */\n+#define SIT9531X_PAGE_OUTSYS0_SLOT_MAX\t5 /* slots 0-5 on Page 0x03 */\n+\n+/* Misc output system registers */\n+#define SIT9531X_REG_PRG_DIR_GEN\t\tSIT9531X_REG(0x03, 0x0F)\n+#define SIT9531X_PRG_CMD_STATE\t\t0x01\n+#define SIT9531X_UPDATE_NVM\t\t\t0x10\n+#define SIT9531X_LOOP_LOCK\t\t\t0x40\n+\n+/* Debug register (same offset, per-page) */\n+#define SIT9531X_REG_OUTSYS_DEBUG\t\tSIT9531X_REG(0x03, 0xBD)\n+#define SIT9531X_DEBUG_UNLOCK_VAL\t\t0xC3\n+\n+/*\n+ * Per-output programmable phase delay: 34-bit coarse (in VCO clock\n+ * cycles) plus a 3-bit fine field with fixed 30 ps steps. Each output\n+ * has a five-byte block PROG6..PROG2:\n+ *\n+ * base + 0 PROG6 [7:5] OPSTG_VCASC_BUMP (preserve via RMW)\n+ * [4:2] PRG_RST_FINE_DELAY[2:0]\n+ * [1:0] PRG_RST_DELAY[33:32]\n+ * base + 1 PROG5 [7:0] PRG_RST_DELAY[31:24]\n+ * base + 2 PROG4 [7:0] PRG_RST_DELAY[23:16]\n+ * base + 3 PROG3 [7:0] PRG_RST_DELAY[15:8]\n+ * base + 4 PROG2 [7:0] PRG_RST_DELAY[7:0]\n+ *\n+ * Outputs 0-5 are on Page 3, outputs 6-11 on Page 4. The block base\n+ * within a page is 0x15 + 16 * (out_idx % 6).\n+ */\n+#define SIT9531X_OUT_PRG_DELAY_BASE\t\t0x15\n+#define SIT9531X_OUT_PRG_SLOT_STRIDE\t\t0x10\n+#define SIT9531X_OUT_PRG_OPSTG_MASK\t\t0xE0\t/* bits [7:5], preserve */\n+#define SIT9531X_OUT_PRG_FINE_SHIFT\t\t2\n+#define SIT9531X_OUT_PRG_FINE_MASK\t\t0x1C\t/* bits [4:2] */\n+#define SIT9531X_OUT_PRG_COARSE_HI_MASK\t\t0x03\t/* bits [1:0] */\n+#define SIT9531X_OUT_PRG_FINE_STEP_PS\t\t30\n+#define SIT9531X_OUT_PRG_FINE_MAX\t\t7\t/* 3-bit field */\n+#define SIT9531X_OUT_PRG_COARSE_BITS\t\t34\n+\n+/*\n+ * Per-output pulse-count control byte used in SYSREF / SYNCB modes.\n+ * Slot N within a page sits at 0x1B + 16 * (slot % 6). Same page\n+ * mapping as PRG_RST_DELAY: slots 0-5 on Page 3, slots 6-11 on Page 4.\n+ */\n+#define SIT9531X_OUT_PROG0_BASE\t\t0x1B\n+\n+/*\n+ * On-demand phase-flush fired from a register rather than a GPIO pin.\n+ * DIVO_PHASE_SEL_REG selects the in-register trigger source and\n+ * DIVO_PHASE_TRIG flushes the output phase when pulsed high then low.\n+ * The unrelated OEb trigger pair in bits [7:6] must be preserved.\n+ */\n+#define SIT9531X_REG_GPIO_FUNC_CTRL1\tSIT9531X_REG(0x00, 0x65)\n+#define SIT9531X_DIVO_PHASE_SEL_REG\tBIT(5)\n+#define SIT9531X_DIVO_PHASE_TRIG\tBIT(4)\n+\n+/* ---- PLL page registers (apply to pages 0x0A-0x0D) ---- */\n+#define SIT9531X_PLL_REG_SMALL_UPDATE\t0x0F\n+\n+/* On-demand phase-flush enable (PLL page reg 0x3D bit 7) */\n+#define SIT9531X_PLL_REG_PHFL_CTRL\t0x3D\n+#define SIT9531X_PLL_PHFL_ON_DEMAND_EN\tBIT(7)\n+\n+/*\n+ * Loop-filter coefficients on PLL_PAGE regs 0x10-0x15 (3 normal +\n+ * 3 fast-lock) are GUI/NVM-generated by the timing configurator and must not be\n+ * reprogrammed at runtime; the register map flags them as\n+ * \"GUI generated configuration should not change manually\".\n+ */\n+\n+#define SIT9531X_PLL_REG_OUT_MAP_HI\t\t0x27\n+#define SIT9531X_PLL_REG_OUT_MAP_LO\t\t0x28\n+#define SIT9531X_PLL_REG_STATUS\t\t0x31\n+#define SIT9531X_PLL_REG_NVM_UPDATE\t\t0x3F\n+\n+/* DIVN registers (free-run divider readback) */\n+#define SIT9531X_PLL_REG_DIVN_INT\t\t0x30\n+#define SIT9531X_PLL_REG_DIVN_NUM\t\t0x32 /* 4 bytes (0x32-0x35) */\n+#define SIT9531X_PLL_REG_DIVN_DEN\t\t0x38 /* 4 bytes (0x38-0x3B) */\n+\n+/* DIVN2 registers (sync divider readback) */\n+#define SIT9531X_PLL_REG_DIVN2_INT\t\t0x3E /* 5 bytes (0x3E-0x42) */\n+#define SIT9531X_PLL_REG_DIVN2_FRAC_NUM\t0x43 /* 4 bytes (0x43-0x46) */\n+#define SIT9531X_PLL_REG_DIVN2_FRAC_DEN\t0x49 /* 4 bytes (0x49-0x4C) */\n+\n+/* Debug register unlock */\n+#define SIT9531X_PLL_REG_DEBUG\t\t0xBD\n+#define SIT9531X_PLL_DEBUG_UNLOCK\t\t0xC3\n+\n+/*\n+ * Signal pathway debug readback -- PLL page. Dig_Sys_ReadCode selects\n+ * which point of the pathway is tapped, Dig_Sys_WriteCode carries the\n+ * modifiers for that read, Dig_Sys_read7..read0 hold the sampled bytes\n+ * and the trigger register latches a sample. The TDC phase\n+ * measurement is one tap among several, reached through read code 69.\n+ */\n+#define SIT9531X_PLL_REG_DBG_READ_CODE\t0xB3\n+#define SIT9531X_PLL_REG_DBG_WRITE_CODE\t0xB4\n+#define SIT9531X_DBG_LOW_FREQ_CLK_BIT\tBIT(7)\n+#define SIT9531X_PLL_REG_DBG_DATA_0\t\t0xB5 /* [7:0] */\n+#define SIT9531X_PLL_REG_DBG_DATA_1\t\t0xB6 /* [15:8] */\n+#define SIT9531X_PLL_REG_DBG_DATA_2\t\t0xB7 /* [23:16] */\n+#define SIT9531X_PLL_REG_DBG_DATA_3\t\t0xB8 /* [31:24] */\n+#define SIT9531X_PLL_REG_DBG_DATA_4\t\t0xB9 /* [39:32] + sign */\n+#define SIT9531X_PLL_REG_DBG_DATA_5\t\t0xBA /* [47:40] */\n+#define SIT9531X_PLL_REG_DBG_DATA_6\t\t0xBB\n+#define SIT9531X_PLL_REG_DBG_DATA_7\t\t0xBC\n+#define SIT9531X_PLL_REG_DBG_TRIGGER\t\t0xD0 /* read to latch a sample */\n+#define SIT9531X_DBG_DATA_BYTES\t\t8\n+\n+/* Read code of the TDC phase tap, and the sign bit of its sample */\n+#define SIT9531X_DBG_READ_CODE_TDC\t\t69\n+#define SIT9531X_TDC_SIGN_BIT\t\t3\n+\n+/*\n+ * Read codes of the running DIVN taps. Unlike the configuration\n+ * registers these report what the digital loop currently commands, so\n+ * they carry the correction the loop applies to track its reference.\n+ * The integer part and the numerator share one tap, the denominator\n+ * has its own.\n+ */\n+#define SIT9531X_DBG_READ_CODE_DIVN\t\t0x57\n+#define SIT9531X_DBG_READ_CODE_DIVN_DEN\t0x56\n+#define SIT9531X_DIVN_RT_NUM_BITS\t\t48\n+#define SIT9531X_DIVN_RT_INT_HI_BIT\t\tBIT(0)\n+\n+/*\n+ * DIVN carried as fixed point, and the unit the DPLL ABI wants the\n+ * fractional frequency offset in. Equal in value, distinct in meaning.\n+ */\n+#define SIT9531X_DIVN_SCALE\t\t1000000000000ULL\n+#define SIT9531X_PPT_PER_UNIT\t\t1000000000000ULL\n+\n+/*\n+ * TDC phase-difference measurement setup (PLL page). Per the vendor\n+ * PhaseDifferenceReader reference procedure, a valid TDC reading needs\n+ * the digital loop filter frozen (so the VCO does not track out the\n+ * measured offset mid-sample) and, on PLLA (the 1PPS phase DPLL), the\n+ * automatic phase/frequency-lock helpers held off.\n+ */\n+#define SIT9531X_PLL_REG_DLPF_FREEZE0\t0x8A\n+#define SIT9531X_PLL_REG_DLPF_FREEZE1\t0x8B\n+#define SIT9531X_PLL_DLPF_FREEZE_BIT\tBIT(5) /* freeze DLPF (hitless HO) */\n+#define SIT9531X_PLL_REG_APL_CTRL\t\t0x4D\n+#define SIT9531X_PLL_APL_WAKEUP_DIS_BIT\tBIT(7) /* disable APL at wake-up */\n+#define SIT9531X_PLL_APL_SWITCH_EN_BIT\tBIT(5) /* APL on switchover */\n+#define SIT9531X_PLL_REG_AFL_CTRL\t\t0x4E\n+#define SIT9531X_PLL_AFL_WAKEUP_DIS_BIT\tBIT(5) /* disable AFL at wake-up */\n+#define SIT9531X_PLL_CONFIG47_PHFL_BIT\tBIT(7) /* PHFL enable (reg 0x47) */\n+#define SIT9531X_PLL_REG_ACTIVE\t\t0x02\n+#define SIT9531X_PLL_ACTIVE_BIT\t\tBIT(0) /* PLL reached active state */\n+#define SIT9531X_PLL_REG_RESTART\t\t0x05\n+#define SIT9531X_PLL_RESTART_BIT\t\tBIT(0) /* restart the PLL */\n+#define SIT9531X_PLL_REG_ZDB0\t\t0x2B\n+#define SIT9531X_PLL_REG_ZDB1\t\t0x1E\n+#define SIT9531X_PLL_ZDB_EN_BIT\t\tBIT(4) /* zero-delay buffer enabled */\n+\n+/* PLL EXT page INTSYNC configuration registers */\n+#define SIT9531X_PLL_EXT_PAGE(_idx)\t\t(SIT9531X_PAGE_PLLA_EXT + (_idx))\n+\n+/* PLL STATUS register bits */\n+#define SIT9531X_PLL_STATUS_LOCK\t\tBIT(0)\n+#define SIT9531X_PLL_STATUS_OUTER_DIS\tBIT(5)\n+\n+/* P-polarity status registers */\n+#define SIT9531X_CLKMON_P_STATUS_01\t\tSIT9531X_REG(0x06, 0x02) /* inputs 0,1 */\n+#define SIT9531X_CLKMON_P_NOTIF_01\t\tSIT9531X_REG(0x06, 0x03)\n+#define SIT9531X_CLKMON_P_STATUS_23\t\tSIT9531X_REG(0x06, 0x06) /* inputs 2,3 */\n+#define SIT9531X_CLKMON_P_NOTIF_23\t\tSIT9531X_REG(0x06, 0x07)\n+\n+/* N-polarity status registers */\n+#define SIT9531X_CLKMON_N_STATUS_01\t\tSIT9531X_REG(0x06, 0x92) /* inputs 0,1 */\n+#define SIT9531X_CLKMON_N_NOTIF_01\t\tSIT9531X_REG(0x06, 0x93)\n+#define SIT9531X_CLKMON_N_STATUS_23\t\tSIT9531X_REG(0x06, 0x96) /* inputs 2,3 */\n+#define SIT9531X_CLKMON_N_NOTIF_23\t\tSIT9531X_REG(0x06, 0x97)\n+\n+/* Per-input bit offsets within clock monitor nibble */\n+#define SIT9531X_CLKMON_FREQ_FINE\t\t0 /* bit 0 / bit 4 */\n+#define SIT9531X_CLKMON_FREQ_COARSE\t\t1 /* bit 1 / bit 5 */\n+#define SIT9531X_CLKMON_CLK_LOSS\t\t2 /* bit 2 / bit 6 */\n+#define SIT9531X_CLKMON_CLK_LOSS_FD\t\t3 /* bit 3 / bit 7 */\n+\n+/* ---- Debug / NVM unlock registers ---- */\n+#define SIT9531X_REG_DBG_UNLOCK1\t\t0x24\n+#define SIT9531X_REG_DBG_UNLOCK2\t\t0x25\n+\n+/* ---- Variant ID values (single byte read from SIT9531X_REG_VARIANT_ID) ---- */\n+#define SIT9531X_VARIANT_ID_95317\t0x17\n+#define SIT9531X_VARIANT_ID_95316\t0x31\n+\n+#endif /* _SIT9531X_REGS_H */\n"
} |
| 2/1 | 2026/08/12 19:57 | agent | patch-evaluator |
8mModel:best-expensive Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The driver requires a mandatory 'xtal' clock via devm_clk_get_enabled() during probe. In generic virtualized environments (QEMU/GCE), dynamically instantiating this I2C device via sysfs will lack the necessary device tree node or clkdev lookup to satisfy this clock requirement. The probe will always fail with -ENOENT, making the rest of the driver's code paths unreachable for fuzzing.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 6d074aed00584446dfd11f27a835d4654aa9eaea
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Aug 12 19:57:20 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/devicetree/bindings/dpll/sitime,sit95316.yaml b/Documentation/devicetree/bindings/dpll/sitime,sit95316.yaml
new file mode 100644
index 0000000000000..591a8c0d7bbf2
--- /dev/null
+++ b/Documentation/devicetree/bindings/dpll/sitime,sit95316.yaml
@@ -0,0 +1,171 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/dpll/sitime,sit95316.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: SiTime SiT95316/SiT95317 DPLL Clock Generator
+
+maintainers:
+ - Ali Rouhi <arouhi@sitime.com>
+
+description: |
+ SiTime SiT95316 and SiT95317 are I2C-controlled programmable clock
+ generators with integrated DPLL for synchronization applications. Both
+ variants contain four PLLs with automatic/manual reference selection,
+ DCO frequency adjustment, and phase offset measurement via an on-chip
+ TDC (Time-to-Digital Converter).
+
+ Both parts have 4 differential input pairs whose lanes can also be
+ driven independently as single-ended references, so 8 inputs are
+ individually selectable. SiT95317 drives 8 outputs, SiT95316
+ drives 12.
+
+properties:
+ compatible:
+ enum:
+ - sitime,sit95316
+ - sitime,sit95317
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ items:
+ - const: xtal
+
+ reset-gpios:
+ maxItems: 1
+ description:
+ GPIO connected to the chip's active-low reset pin (RESETB).
+
+ interrupts:
+ maxItems: 1
+ description:
+ Interrupt from the chip's active-low INTRB output. Asserted when
+ the device detects a status change such as lock acquisition or loss.
+
+ sitime,pll-fvco:
+ $ref: /schemas/types.yaml#/definitions/uint64-array
+ minItems: 4
+ maxItems: 4
+ description:
+ Per-PLL VCO frequency in Hz for PLLA, PLLB, PLLC, PLLD. Neither
+ standard frequency suffix fits. The VCO bands are 4915.2 MHz to
+ 5898.24 MHz and 6875 MHz to 7812.5 MHz, so every valid value
+ exceeds the 32 bits "-hz" allows, and the band edges are not
+ whole megahertz either, so "-mhz" cannot express them. Hence a
+ plain uint64-array. Override where the standard
+ Fvco = Fref * DIVN derivation does not match the running VCO --
+ for example a PLL operating in INTSYNC mode. A value of 0 keeps
+ the register-derived computation for that PLL.
+
+ sitime,output-pll-map:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ minItems: 8
+ maxItems: 12
+ items:
+ enum: [0, 1, 2, 3, 255]
+ description:
+ Source PLL index (0=PLLA .. 3=PLLD) for each output 0..11. The
+ value 255 (0xff) marks an output as unmapped and prevents the
+ driver from registering it as a DPLL pin. Override for
+ configurations where the chip's per-PLL OUTPUT_ENABLE bitmaps do
+ not unambiguously describe output-to-PLL routing.
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+
+allOf:
+ - $ref: /schemas/dpll/dpll-device.yaml#
+ # SiT95317 exposes 8 outputs, SiT95316 exposes 12. Bound the
+ # output-pll-map length to the variant so a SiT95317 node cannot
+ # describe more outputs than the part has.
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: sitime,sit95317
+ then:
+ properties:
+ sitime,output-pll-map:
+ maxItems: 8
+ # SiT95316 has 12 outputs. When the map is supplied it must describe
+ # all of them, otherwise the trailing outputs are left ambiguous.
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: sitime,sit95316
+ then:
+ properties:
+ sitime,output-pll-map:
+ minItems: 12
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ dpll@68 {
+ compatible = "sitime,sit95316";
+ reg = <0x68>;
+ clocks = <&xo2>;
+ clock-names = "xtal";
+ reset-gpios = <&gpio 78 GPIO_ACTIVE_LOW>;
+ interrupts = <12 IRQ_TYPE_LEVEL_LOW>;
+ dpll-types = "eec", "eec", "eec", "eec";
+ sitime,pll-fvco = /bits/ 64 <6251500000 0 0 0>;
+ sitime,output-pll-map = <0 0 0 0 0 0 0 0 0 0 0 0>;
+
+ input-pins {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pin@0 {
+ reg = <0>;
+ label = "clkin0";
+ connection-type = "ext";
+ supported-frequencies-hz = /bits/ 64 <10000000>;
+ };
+
+ pin@1 {
+ reg = <1>;
+ label = "clkin1";
+ connection-type = "synce";
+ supported-frequencies-hz = /bits/ 64 <156250000>;
+ };
+ };
+
+ output-pins {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pin@0 {
+ reg = <0>;
+ label = "clkout0";
+ esync-control;
+ supported-frequencies-hz = /bits/ 64 <156250000>;
+ };
+
+ pin@1 {
+ reg = <1>;
+ label = "clkout1";
+ supported-frequencies-hz = /bits/ 64 <25000000>;
+ };
+ };
+ };
+ };
+...
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 396044f368e7c..1921718a5f82c 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1535,6 +1535,8 @@ patternProperties:
description: SiRF Technology, Inc.
"^sis,.*":
description: Silicon Integrated Systems Corp.
+ "^sitime,.*":
+ description: SiTime Corporation
"^sitronix,.*":
description: Sitronix Technology Corporation
"^skov,.*":
diff --git a/MAINTAINERS b/MAINTAINERS
index a82b6ec9c5674..8dfab3ac6a17f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -24905,6 +24905,13 @@ S: Maintained
W: http://www.winischhofer.at/linuxsisusbvga.shtml
F: drivers/usb/misc/sisusbvga/
+SITIME SIT9531X DPLL DRIVER
+M: Ali Rouhi <arouhi@sitime.com>
+L: netdev@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/dpll/sitime,sit95316.yaml
+F: drivers/dpll/sit9531x/
+
SL28 CPLD MFD DRIVER
M: Michael Walle <mwalle@kernel.org>
S: Maintained
diff --git a/drivers/dpll/Kconfig b/drivers/dpll/Kconfig
index be98969f040ab..ea77b9c11ab1d 100644
--- a/drivers/dpll/Kconfig
+++ b/drivers/dpll/Kconfig
@@ -23,6 +23,7 @@ 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 9e7a3a3e592e8..4adc50d748d4d 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 0000000000000..47aea8674327c
--- /dev/null
+++ b/drivers/dpll/sit9531x/Kconfig
@@ -0,0 +1,17 @@
+# 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).
+
+ This driver registers each on-chip PLL as a DPLL device
+ and exposes input/output clocks as DPLL pins, providing
+ runtime configuration via Generic Netlink.
+
+ 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 0000000000000..b97d2656a4603
--- /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 dpll.o prop.o
diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c
new file mode 100644
index 0000000000000..711ab16b0897a
--- /dev/null
+++ b/drivers/dpll/sit9531x/core.c
@@ -0,0 +1,3120 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SiTime SiT9531x DPLL core driver
+ *
+ * Copyright (C) 2026 SiTime Corp.
+ * Author: Ali Rouhi <arouhi@sitime.com>
+ * Author: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>
+ *
+ * Core I2C probe, regmap configuration, hardware state management,
+ * and periodic work thread.
+ */
+
+#include <linux/bitfield.h>
+#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/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "core.h"
+#include "dpll.h"
+#include "prop.h"
+#include "regs.h"
+
+/* Number of input + output pin positions for pin index allocation */
+#define SIT9531X_NUM_INPUT_PINS (SIT9531X_MAX_INPUTS + 2) /* +xtal +INTSYNC */
+#define SIT9531X_NUM_OUTPUT_PINS (SIT9531X_MAX_OUTPUTS + 1) /* +INTSYNC src */
+#define SIT9531X_NUM_PINS_TOTAL (SIT9531X_NUM_INPUT_PINS + SIT9531X_NUM_OUTPUT_PINS)
+
+#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),
+};
+
+#define SIT9531X_RANGE_OFFSET SIT9531X_PAGE_SIZE
+
+static const struct regmap_range_cfg sit9531x_regmap_range = {
+ .range_min = SIT9531X_RANGE_OFFSET,
+ .range_max = SIT9531X_RANGE_OFFSET +
+ (SIT9531X_NUM_PAGES * SIT9531X_PAGE_SIZE) - 1,
+ .selector_reg = SIT9531X_PAGE_SEL,
+ .selector_mask = GENMASK(7, 0),
+ .selector_shift = 0,
+ .window_start = 0,
+ .window_len = SIT9531X_PAGE_SIZE,
+};
+
+const struct regmap_config sit9531x_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = SIT9531X_RANGE_OFFSET +
+ (SIT9531X_NUM_PAGES * SIT9531X_PAGE_SIZE) - 1,
+ .ranges = &sit9531x_regmap_range,
+ .num_ranges = 1,
+ .cache_type = REGCACHE_NONE,
+};
+
+/*
+ * sit9531x_read_u8 - read an 8-bit register
+ * @reg: register in SIT9531X_REG(page, offset) form
+ * @val: output value
+ */
+int sit9531x_read_u8(struct sit9531x_dev *sitdev, unsigned int reg,
+ u8 *val)
+{
+ unsigned int tmp;
+ int rc;
+
+ reg = (SIT9531X_REG_PAGE(reg) * SIT9531X_PAGE_SIZE) +
+ SIT9531X_REG_OFFSET(reg) + SIT9531X_RANGE_OFFSET;
+
+ rc = regmap_read(sitdev->regmap, reg, &tmp);
+ if (rc)
+ dev_err(sitdev->dev, "Failed to read reg 0x%04x: %d\n",
+ reg, rc);
+ else
+ *val = (u8)tmp;
+
+ return rc;
+}
+
+/*
+ * sit9531x_write_u8 - write an 8-bit register
+ * @reg: register in SIT9531X_REG(page, offset) form
+ * @val: value to write
+ */
+int sit9531x_write_u8(struct sit9531x_dev *sitdev, unsigned int reg,
+ u8 val)
+{
+ int rc;
+
+ reg = (SIT9531X_REG_PAGE(reg) * SIT9531X_PAGE_SIZE) +
+ SIT9531X_REG_OFFSET(reg) + SIT9531X_RANGE_OFFSET;
+
+ rc = regmap_write(sitdev->regmap, reg, val);
+ if (rc)
+ dev_err(sitdev->dev, "Failed to write reg 0x%04x: %d\n",
+ reg, rc);
+
+ return rc;
+}
+
+/*
+ * sit9531x_read_pll_u8 - read a register on a PLL page
+ * @val: output value
+ */
+int sit9531x_read_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 offset, u8 *val)
+{
+ return sit9531x_read_u8(sitdev,
+ SIT9531X_REG(sit9531x_pll_page(pll_idx), offset),
+ val);
+}
+
+/*
+ * sit9531x_write_pll_u8 - write a register on a PLL page
+ * @val: value to write
+ */
+int sit9531x_write_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 offset, u8 val)
+{
+ return sit9531x_write_u8(sitdev,
+ SIT9531X_REG(sit9531x_pll_page(pll_idx), offset),
+ val);
+}
+
+/*
+ * sit9531x_update_pll_u8 - read-modify-write a register on a PLL page
+ * @mask: bits to modify
+ * @val: new value for masked bits
+ */
+int sit9531x_update_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 offset, u8 mask, u8 val)
+{
+ unsigned int reg;
+
+ reg = (sit9531x_pll_page(pll_idx) * SIT9531X_PAGE_SIZE) +
+ offset + SIT9531X_RANGE_OFFSET;
+
+ return regmap_update_bits(sitdev->regmap, reg, mask, val);
+}
+
+/*
+ * sit9531x_input_get_regs - get force mask and state register addresses
+ * @index: logical input index
+ * @force_reg: output force mask register address
+ * @state_reg: output state register address
+ *
+ * Selects the correct Page 0x02 register pair based on the pair's
+ * signal mode and the lane (P/N) the index refers to.
+ */
+static void sit9531x_input_get_regs(const struct sit9531x_dev *sitdev,
+ u8 index,
+ unsigned int *force_reg,
+ unsigned int *state_reg)
+{
+ if (sitdev->ref[index].sig_mode == SIT9531X_MODE_DE) {
+ *force_reg = SIT9531X_REG_IN_DE_FORCE;
+ *state_reg = SIT9531X_REG_IN_DE_STATE;
+ } else if (sit9531x_input_is_n(index)) {
+ *force_reg = SIT9531X_REG_IN_SEN_FORCE;
+ *state_reg = SIT9531X_REG_IN_SEN_STATE;
+ } else {
+ *force_reg = SIT9531X_REG_IN_SEP_FORCE;
+ *state_reg = SIT9531X_REG_IN_SEP_STATE;
+ }
+}
+
+/*
+ * sit9531x_input_disable - disable an input reference
+ * @index: logical input index (0-N)
+ *
+ * Sets the force mask bit and clears the state bit for the given
+ * input, effectively disabling it. Register selection depends on
+ * the pair's signal mode (SE/DE) and the lane (P/N); the bit within
+ * each register addresses the input pair.
+ */
+int sit9531x_input_disable(struct sit9531x_dev *sitdev, u8 index)
+{
+ struct sit9531x_ref *ref = &sitdev->ref[index];
+ unsigned int force_reg, state_reg;
+ u8 pair = sit9531x_input_pair(index);
+ u8 val;
+ int rc;
+
+ sit9531x_input_get_regs(sitdev, index, &force_reg, &state_reg);
+
+ rc = sit9531x_read_u8(sitdev, force_reg, &val);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, force_reg, val | BIT(pair));
+ if (rc)
+ return rc;
+
+ rc = sit9531x_read_u8(sitdev, state_reg, &val);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, state_reg, val & ~BIT(pair));
+ if (rc)
+ return rc;
+
+ ref->enabled = false;
+
+ return 0;
+}
+
+/*
+ * sit9531x_input_enable - enable an input reference
+ * @index: logical input index (0-N)
+ *
+ * Clears the force mask bit for the given input, returning it to
+ * hardware default (enabled).
+ */
+int sit9531x_input_enable(struct sit9531x_dev *sitdev, u8 index)
+{
+ struct sit9531x_ref *ref = &sitdev->ref[index];
+ unsigned int force_reg, state_reg;
+ u8 pair = sit9531x_input_pair(index);
+ u8 val;
+ int rc;
+
+ sit9531x_input_get_regs(sitdev, index, &force_reg, &state_reg);
+
+ rc = sit9531x_read_u8(sitdev, force_reg, &val);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, force_reg, val & ~BIT(pair));
+ if (rc)
+ return rc;
+
+ ref->enabled = true;
+
+ return 0;
+}
+
+/*
+ * Output enable / disable (Hi-Z control)
+ *
+ * SiT9531x outputs can be configured as differential (DIFF) or
+ * single-ended (SE) depending on the factory blob. Each output slot
+ * has TWO Hi-Z force/state register pairs on Page 0x03 -- one for the
+ * DIFF path, one for the SE path.
+ *
+ * We write to BOTH pairs so the function mutes the output regardless
+ * of whether it's been configured DIFF or SE on this board.
+ *
+ * slot 0-7 :
+ * DIFF mask=0xF2 state=0xF3 SE mask=0xF8 state=0xF9
+ * slot 8-11:
+ * DIFF mask=0xF4 state=0xF5 SE mask=0xFA state=0xFB
+ *
+ * MASK bit = 1 -> driver takes control of that output's Hi-Z state
+ * STATE bit = 0 -> output is forced to Hi-Z (muted)
+ * STATE bit = 1 -> output is driven (active)
+ *
+ * The output "index" in the driver is logical; the physical slot comes
+ * from info->clkout_map[].
+ */
+
+struct sit9531x_hiz_regs {
+ unsigned int diff_mask;
+ unsigned int diff_state;
+ unsigned int se_mask;
+ unsigned int se_state;
+ u8 bit;
+};
+
+static void sit9531x_output_get_hiz_regs(u8 slot,
+ struct sit9531x_hiz_regs *r)
+{
+ if (slot <= 7) {
+ r->diff_mask = SIT9531X_REG_HIZ_DIFF_07_MASK;
+ r->diff_state = SIT9531X_REG_HIZ_DIFF_07_STATE;
+ r->se_mask = SIT9531X_REG_HIZ_SE_07_MASK;
+ r->se_state = SIT9531X_REG_HIZ_SE_07_STATE;
+ r->bit = slot;
+ } else {
+ r->diff_mask = SIT9531X_REG_HIZ_DIFF_811_MASK;
+ r->diff_state = SIT9531X_REG_HIZ_DIFF_811_STATE;
+ r->se_mask = SIT9531X_REG_HIZ_SE_811_MASK;
+ r->se_state = SIT9531X_REG_HIZ_SE_811_STATE;
+ r->bit = slot - 8;
+ }
+}
+
+/*
+ * Report whether a slot is currently forced into Hi-Z, i.e. the driver
+ * (or the blob) took control of the Hi-Z state (MASK bit set) and drives
+ * it low (STATE bit clear). Either register pair muting the slot counts,
+ * mirroring what sit9531x_output_disable() programs.
+ */
+static int sit9531x_output_forced_hiz(struct sit9531x_dev *sitdev, u8 slot,
+ bool *muted)
+{
+ struct sit9531x_hiz_regs r;
+ u8 mask, state;
+ int rc;
+
+ sit9531x_output_get_hiz_regs(slot, &r);
+
+ rc = sit9531x_read_u8(sitdev, r.diff_mask, &mask);
+ if (rc)
+ return rc;
+ rc = sit9531x_read_u8(sitdev, r.diff_state, &state);
+ if (rc)
+ return rc;
+
+ *muted = (mask & BIT(r.bit)) && !(state & BIT(r.bit));
+ if (*muted)
+ return 0;
+
+ rc = sit9531x_read_u8(sitdev, r.se_mask, &mask);
+ if (rc)
+ return rc;
+ rc = sit9531x_read_u8(sitdev, r.se_state, &state);
+ if (rc)
+ return rc;
+
+ *muted = (mask & BIT(r.bit)) && !(state & BIT(r.bit));
+
+ return 0;
+}
+
+static int sit9531x_hiz_set_bit(struct sit9531x_dev *sitdev,
+ unsigned int reg, u8 bit, bool set)
+{
+ u8 cur, new_val;
+ int rc;
+
+ rc = sit9531x_read_u8(sitdev, reg, &cur);
+ if (rc)
+ return rc;
+
+ new_val = set ? (cur | BIT(bit)) : (cur & ~BIT(bit));
+
+ return sit9531x_write_u8(sitdev, reg, new_val);
+}
+
+/*
+ * Enter the output-system programming state: unlock the debug
+ * registers on Page 3 and issue the PRG_CMD state command. Register
+ * writes that reconfigure the output system only take effect when
+ * they are made inside this state.
+ */
+static int sit9531x_prg_enter(struct sit9531x_dev *sitdev)
+{
+ int rc;
+
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_OUTSYS_DEBUG,
+ SIT9531X_DEBUG_UNLOCK_VAL);
+ if (rc)
+ return rc;
+
+ return sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN,
+ SIT9531X_PRG_CMD_STATE);
+}
+
+/*
+ * Commit a programming sequence started by sit9531x_prg_enter():
+ * update the NVM shadow and re-lock the loops. The sleep gives the
+ * hardware its required settling time after the loop-lock command;
+ * it is intentional despite the caller holding multiop_lock, as the
+ * whole NVM + lock sequence must be atomic.
+ */
+static int sit9531x_prg_commit(struct sit9531x_dev *sitdev)
+{
+ int rc;
+
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN,
+ SIT9531X_UPDATE_NVM);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN,
+ SIT9531X_LOOP_LOCK);
+ if (rc)
+ return rc;
+
+ msleep(100);
+
+ return 0;
+}
+
+/*
+ * sit9531x_output_disable - mute an output (force Hi-Z)
+ * @index: logical output index (0..info->num_outputs-1)
+ *
+ * Sets MASK+STATE on BOTH the DIFF and SE register pairs so that the
+ * output is muted regardless of its electrical configuration. The
+ * writes are wrapped in the PRG_CMD / NVM update / loop lock sequence
+ * so the new state is applied by the hardware.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+int sit9531x_output_disable(struct sit9531x_dev *sitdev, u8 index)
+{
+ const struct sit9531x_chip_info *info = sitdev->info;
+ struct sit9531x_hiz_regs r;
+ u8 slot;
+ int rc, ret;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (index >= info->num_outputs)
+ return -EINVAL;
+
+ slot = info->clkout_map[index];
+ sit9531x_output_get_hiz_regs(slot, &r);
+
+ rc = sit9531x_prg_enter(sitdev);
+ if (rc)
+ return rc;
+
+ /* Take control (MASK=1) and mute (STATE=0) on both DIFF and SE */
+ rc = sit9531x_hiz_set_bit(sitdev, r.diff_mask, r.bit, true);
+ if (rc)
+ goto commit;
+ rc = sit9531x_hiz_set_bit(sitdev, r.diff_state, r.bit, false);
+ if (rc)
+ goto commit;
+ rc = sit9531x_hiz_set_bit(sitdev, r.se_mask, r.bit, true);
+ if (rc)
+ goto commit;
+ rc = sit9531x_hiz_set_bit(sitdev, r.se_state, r.bit, false);
+
+commit:
+ /*
+ * Always leave the PRG_CMD programming state, even on a mid-sequence
+ * write failure: prg_enter() unlocked the output loops, so returning
+ * without prg_commit() would strand the chip in the programming state
+ * with the loops unlocked. Best effort -- keep the first error.
+ */
+ ret = sit9531x_prg_commit(sitdev);
+ if (ret && !rc)
+ rc = ret;
+ if (!rc)
+ sitdev->out[index].enabled = false;
+
+ return rc;
+}
+
+/*
+ * sit9531x_output_enable - un-mute an output (active state)
+ * @index: logical output index (0..info->num_outputs-1)
+ *
+ * Releases MASK on BOTH register pairs so the output returns to
+ * whatever the initial_config blob programmed. The writes are wrapped
+ * in the PRG_CMD / NVM update / loop lock sequence so the new state is
+ * applied by the hardware.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+int sit9531x_output_enable(struct sit9531x_dev *sitdev, u8 index)
+{
+ const struct sit9531x_chip_info *info = sitdev->info;
+ struct sit9531x_hiz_regs r;
+ u8 slot;
+ int rc, ret;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (index >= info->num_outputs)
+ return -EINVAL;
+
+ slot = info->clkout_map[index];
+ sit9531x_output_get_hiz_regs(slot, &r);
+
+ rc = sit9531x_prg_enter(sitdev);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_hiz_set_bit(sitdev, r.diff_mask, r.bit, false);
+ if (rc)
+ goto commit;
+ rc = sit9531x_hiz_set_bit(sitdev, r.se_mask, r.bit, false);
+
+commit:
+ /*
+ * Always leave the PRG_CMD programming state, even on a mid-sequence
+ * write failure: prg_enter() unlocked the output loops, so returning
+ * without prg_commit() would strand the chip in the programming state
+ * with the loops unlocked. Best effort -- keep the first error.
+ */
+ ret = sit9531x_prg_commit(sitdev);
+ if (ret && !rc)
+ rc = ret;
+ if (!rc)
+ sitdev->out[index].enabled = true;
+
+ return rc;
+}
+
+/*
+ * Input priority selection
+ *
+ * The SiT9531x has a 12-slot priority table per PLL on Page 1. Each
+ * register holds two slots nibble-packed (even slot in [3:0], odd slot
+ * in [7:4]).
+ *
+ * The procedure:
+ * 1. Force PLL into holdover (PLL page reg 0x6F bit 4)
+ * 2. Write priority slots on Page 1
+ * 3. Small change update (Page 0 reg 0x0F bit 1)
+ * 4. Release holdover
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+
+/* Page-1 register holding priority slot @slot of @pll_idx. */
+static u16 sit9531x_prio_reg(u8 pll_idx, u8 slot)
+{
+ return SIT9531X_REG(SIT9531X_PAGE_PRIOSYS,
+ SIT9531X_PRIO_BASE_REG +
+ SIT9531X_PRIO_REGS_PER_PLL * pll_idx +
+ slot / SIT9531X_PRIO_SLOTS_PER_REG);
+}
+
+/*
+ * Extract priority slot @slot from its register value. The register
+ * holding slots 2n and 2n+1 keeps the earlier slot in the high nibble
+ * (CLK_SPARE<2n>SEL) and the later one in the low nibble.
+ */
+static u8 sit9531x_prio_slot_get(u8 val, u8 slot)
+{
+ if (slot & 1)
+ return val & SIT9531X_PRIO_NIBBLE_MASK;
+
+ return val >> SIT9531X_PRIO_HI_SHIFT;
+}
+
+/* Place source @src in priority slot @slot of a register value. */
+static u8 sit9531x_prio_slot_set(u8 val, u8 slot, u8 src)
+{
+ if (slot & 1)
+ return (val & (SIT9531X_PRIO_NIBBLE_MASK <<
+ SIT9531X_PRIO_HI_SHIFT)) |
+ (src & SIT9531X_PRIO_NIBBLE_MASK);
+
+ return (val & SIT9531X_PRIO_NIBBLE_MASK) |
+ ((src & SIT9531X_PRIO_NIBBLE_MASK) <<
+ SIT9531X_PRIO_HI_SHIFT);
+}
+
+/*
+ * Commit a priority-table programming sequence through the Page-0
+ * programming directive register.
+ *
+ * A small change update is all the table needs. The NVM-bank and
+ * loop-lock directives that the output system issues do not belong
+ * here: the former programs non-volatile storage from the efuse and
+ * the latter only means anything after an escape to the PRG_CMD
+ * state. This matches the vendor input_priority_sel() procedure.
+ */
+static int sit9531x_prio_prg_commit(struct sit9531x_dev *sitdev)
+{
+ int rc;
+
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_GLOBAL_UPDATE,
+ SIT9531X_SMALL_UPDATE_CMD);
+ if (rc)
+ return rc;
+
+ usleep_range(1000, 2000);
+
+ return 0;
+}
+
+/*
+ * sit9531x_input_prio_set - set input priority for a PLL
+ * @input_idx: input source in hardware encoding (0-11, see
+ * sit9531x_input_hw_src())
+ * @prio: priority slot position (0 = highest)
+ *
+ * Writes a single priority slot assignment. The input source is
+ * placed at the requested slot, and all lower-priority (higher-numbered)
+ * slots are filled with the same source to avoid stale entries.
+ */
+int sit9531x_input_prio_set(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx, u8 prio)
+{
+ u8 val, slot;
+ int rc, prg_rc, ho_rc;
+ u16 reg;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (pll_idx >= SIT9531X_NUM_PLLS)
+ return -EINVAL;
+ if (input_idx >= SIT9531X_PRIO_NUM_SRC)
+ return -EINVAL;
+ if (prio >= SIT9531X_PRIO_MAX_SLOTS)
+ return -EINVAL;
+
+ /* Force PLL into holdover. */
+ rc = sit9531x_update_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_HO_CTRL,
+ BIT(SIT9531X_PLL_HO_FORCE_BIT),
+ BIT(SIT9531X_PLL_HO_FORCE_BIT));
+ if (rc)
+ return rc;
+
+ usleep_range(10000, 12000);
+
+ /*
+ * Write the requested slot on Page 1, then fill the remaining
+ * slots with the same source, matching the procedure script's
+ * backfill behaviour.
+ */
+ for (slot = prio; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) {
+ reg = sit9531x_prio_reg(pll_idx, slot);
+
+ rc = sit9531x_read_u8(sitdev, reg, &val);
+ if (rc)
+ goto commit;
+
+ val = sit9531x_prio_slot_set(val, slot, input_idx);
+
+ rc = sit9531x_write_u8(sitdev, reg, val);
+ if (rc)
+ goto commit;
+ }
+
+commit:
+ /*
+ * Latch the table even when a slot write failed, so a partially
+ * written table takes effect as a whole rather than waiting for an
+ * unrelated update to latch the leftovers later. Best effort --
+ * keep the first error.
+ */
+ prg_rc = sit9531x_prio_prg_commit(sitdev);
+ if (prg_rc && !rc)
+ rc = prg_rc;
+
+ /*
+ * Release holdover. This must run even when an earlier step
+ * failed, so its own error is only surfaced when it would not mask a
+ * prior failure -- otherwise a failing release could leave the PLL
+ * stuck in forced holdover while the function reports success.
+ */
+ ho_rc = sit9531x_update_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_HO_CTRL,
+ BIT(SIT9531X_PLL_HO_FORCE_BIT), 0);
+ if (ho_rc && !rc)
+ rc = ho_rc;
+
+ return rc;
+}
+
+/*
+ * sit9531x_input_prio_get - read an input's priority slot for a PLL
+ * @input_idx: input source in hardware encoding (see
+ * sit9531x_input_hw_src())
+ * @prio: output slot position (0 = highest); set to
+ * SIT9531X_PRIO_MAX_SLOTS when the source is not in the table
+ *
+ * Scans the PLL's 12-slot priority table on Page 1 and returns the
+ * highest-priority (lowest-numbered) slot that references the source.
+ * This reads the value the chip actually holds rather than a cached
+ * default, so pin-get reflects the real hardware priority.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+int sit9531x_input_prio_get(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx, u8 *prio)
+{
+ u8 val, slot, src;
+ int rc;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (pll_idx >= SIT9531X_NUM_PLLS)
+ return -EINVAL;
+
+ for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) {
+ rc = sit9531x_read_u8(sitdev,
+ sit9531x_prio_reg(pll_idx, slot), &val);
+ if (rc)
+ return rc;
+
+ src = sit9531x_prio_slot_get(val, slot);
+
+ if (src == input_idx) {
+ *prio = slot;
+ return 0;
+ }
+ }
+
+ *prio = SIT9531X_PRIO_MAX_SLOTS;
+ return 0;
+}
+
+/*
+ * sit9531x_prio_table_commit - write a full priority table for a PLL
+ * @srcs: array of SIT9531X_PRIO_MAX_SLOTS source codes, slot 0 first
+ *
+ * Programs all priority slots (nibble-packed, two per register) for
+ * the PLL using the same holdover / small-update sequence as
+ * sit9531x_input_prio_set(). Caller must hold sitdev->multiop_lock.
+ */
+static int sit9531x_prio_table_commit(struct sit9531x_dev *sitdev, u8 pll_idx,
+ const u8 *srcs)
+{
+ u8 val, slot;
+ int rc, prg_rc, ho_rc;
+ u16 reg;
+
+ rc = sit9531x_update_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_HO_CTRL,
+ BIT(SIT9531X_PLL_HO_FORCE_BIT),
+ BIT(SIT9531X_PLL_HO_FORCE_BIT));
+ if (rc)
+ return rc;
+
+ usleep_range(10000, 12000);
+
+ for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) {
+ reg = sit9531x_prio_reg(pll_idx, slot);
+
+ rc = sit9531x_read_u8(sitdev, reg, &val);
+ if (rc)
+ goto commit;
+
+ val = sit9531x_prio_slot_set(val, slot, srcs[slot]);
+
+ rc = sit9531x_write_u8(sitdev, reg, val);
+ if (rc)
+ goto commit;
+ }
+
+commit:
+ /* Latch unconditionally, as in sit9531x_input_prio_set(). */
+ prg_rc = sit9531x_prio_prg_commit(sitdev);
+ if (prg_rc && !rc)
+ rc = prg_rc;
+
+ ho_rc = sit9531x_update_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_HO_CTRL,
+ BIT(SIT9531X_PLL_HO_FORCE_BIT), 0);
+ if (ho_rc && !rc)
+ rc = ho_rc;
+
+ return rc;
+}
+
+/*
+ * sit9531x_prio_table_read - read a PLL's priority-table source codes
+ * @srcs: output array of SIT9531X_PRIO_MAX_SLOTS source codes
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+static int sit9531x_prio_table_read(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 *srcs)
+{
+ u8 val, slot;
+ int rc;
+
+ for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) {
+ rc = sit9531x_read_u8(sitdev,
+ sit9531x_prio_reg(pll_idx, slot), &val);
+ if (rc)
+ return rc;
+
+ srcs[slot] = sit9531x_prio_slot_get(val, slot);
+ }
+
+ return 0;
+}
+
+/*
+ * sit9531x_input_prio_remove - drop an input from a PLL's priority table
+ * @input_idx: input source in hardware encoding
+ *
+ * Rewrites the priority table with the source removed: the remaining
+ * sources are compacted toward the highest-priority slots and the freed
+ * tail slots are backfilled with the lowest-priority remaining source
+ * (matching sit9531x_input_prio_set()). This makes a disconnected
+ * input ineligible for automatic reference selection, not just gated at
+ * the input buffer. It is a no-op when the source is absent, or when it
+ * is the only source (the buffer disable then drives the PLL to
+ * holdover and the table must not be left empty).
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+int sit9531x_input_prio_remove(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx)
+{
+ u8 srcs[SIT9531X_PRIO_MAX_SLOTS];
+ u8 kept[SIT9531X_PRIO_MAX_SLOTS];
+ u8 slot, count = 0;
+ bool found = false;
+ int rc;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (pll_idx >= SIT9531X_NUM_PLLS)
+ return -EINVAL;
+
+ rc = sit9531x_prio_table_read(sitdev, pll_idx, srcs);
+ if (rc)
+ return rc;
+
+ for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) {
+ if (srcs[slot] == input_idx)
+ found = true;
+ else
+ kept[count++] = srcs[slot];
+ }
+
+ if (!found || count == 0)
+ return 0;
+
+ /* Backfill freed tail slots with the lowest-priority remaining src */
+ while (count < SIT9531X_PRIO_MAX_SLOTS) {
+ kept[count] = kept[count - 1];
+ count++;
+ }
+
+ return sit9531x_prio_table_commit(sitdev, pll_idx, kept);
+}
+
+/*
+ * sit9531x_input_prio_add - make an input eligible in a PLL's table
+ * @input_idx: input source in hardware encoding
+ *
+ * Ensures the source appears in the priority table so it can be picked
+ * by automatic reference selection again after a disconnect. If the
+ * source is already listed the table is left untouched; otherwise it is
+ * placed in the lowest-priority slot. The original priority is not
+ * restored -- use sit9531x_input_prio_set() to reassign it.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+int sit9531x_input_prio_add(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx)
+{
+ u8 srcs[SIT9531X_PRIO_MAX_SLOTS];
+ u8 slot;
+ int rc;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (pll_idx >= SIT9531X_NUM_PLLS)
+ return -EINVAL;
+
+ rc = sit9531x_prio_table_read(sitdev, pll_idx, srcs);
+ if (rc)
+ return rc;
+
+ for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++)
+ if (srcs[slot] == input_idx)
+ return 0;
+
+ srcs[SIT9531X_PRIO_MAX_SLOTS - 1] = input_idx;
+
+ return sit9531x_prio_table_commit(sitdev, pll_idx, srcs);
+}
+
+/* Per-slot DIVO base register offsets (6 slots per page) */
+static const u8 clkout_odr_divn_base[] = {
+ 0x14, 0x24, 0x34, 0x44, 0x54, 0x64
+};
+
+/* XO doubler register */
+#define SIT9531X_REG_XO2_GENERIC SIT9531X_REG(0x00, 0x2D)
+#define SIT9531X_XO_DOUBLER_ENB_BIT 7 /* inverted: 0 = enabled */
+
+/* VCO frequency bands (Hz) */
+#define SIT9531X_FVCO_LOWBAND_MIN 4915200000ULL
+#define SIT9531X_FVCO_LOWBAND_MAX 5898240000ULL
+#define SIT9531X_FVCO_HIGHBAND_MIN 6875000000ULL
+#define SIT9531X_FVCO_HIGHBAND_MAX 7812500000ULL
+
+/*
+ * sit9531x_is_xo_doubler_enabled - check if Fref doubler is active
+ *
+ * Register 0x2D bit 7 is active-low: 0 = doubler enabled, 1 = disabled.
+ *
+ * Return: 1 if enabled, 0 if disabled, <0 on error
+ */
+static int sit9531x_is_xo_doubler_enabled(struct sit9531x_dev *sitdev)
+{
+ u8 val;
+ int rc;
+
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG_XO2_GENERIC, &val);
+ if (rc)
+ return rc;
+
+ return (~val >> SIT9531X_XO_DOUBLER_ENB_BIT) & 1u;
+}
+
+/*
+ * sit9531x_dbg_sample - latch and read a signal pathway debug sample
+ * @sitdev: device pointer
+ * @pll_idx: PLL index (0-3)
+ * @read_code: which tap of the pathway to sample
+ * @buf: result, least significant byte first
+ * @len: bytes to read, at most SIT9531X_DBG_DATA_BYTES
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int sit9531x_dbg_sample(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 read_code, u8 *buf, unsigned int len)
+{
+ unsigned int i;
+ int rc;
+ u8 v;
+
+ if (len > SIT9531X_DBG_DATA_BYTES)
+ return -EINVAL;
+
+ rc = sit9531x_write_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_DEBUG,
+ SIT9531X_PLL_DEBUG_UNLOCK);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_write_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DBG_READ_CODE, read_code);
+ if (rc)
+ return rc;
+
+ /* Reading the trigger latches a fresh sample of the selected tap. */
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DBG_TRIGGER, &v);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < len; i++) {
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DBG_DATA_0 + i,
+ &buf[i]);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+/*
+ * DIVN as a fixed-point value: int_part plus fracn/fracd, carried with
+ * SIT9531X_DIVN_SCALE steps per unit. The scale keeps a whole DIVN
+ * well inside s64 while resolving far below the parts-per-trillion the
+ * frequency offset is reported in.
+ */
+static s64 sit9531x_divn_fixed(u32 int_part, s64 fracn, u64 fracd)
+{
+ s64 whole = (s64)int_part * SIT9531X_DIVN_SCALE;
+ u64 frac;
+
+ if (!fracd)
+ return whole;
+
+ frac = mul_u64_u64_div_u64(abs(fracn), SIT9531X_DIVN_SCALE, fracd);
+
+ return fracn < 0 ? whole - (s64)frac : whole + (s64)frac;
+}
+
+/*
+ * sit9531x_divn_static - read the configured DIVN of a PLL
+ * @sitdev: device pointer
+ * @pll_idx: PLL index (0-3)
+ * @divn: result, fixed point as per sit9531x_divn_fixed()
+ *
+ * Reads PLL page regs 0x30 (integer part), 0x32-0x35 (numerator) and
+ * 0x38-0x3B (denominator). The numerator is a two's complement 32-bit
+ * value, so DIVN can sit below the integer part, and the denominator
+ * register holds the divisor minus one.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int sit9531x_divn_static(struct sit9531x_dev *sitdev, u8 pll_idx,
+ s64 *divn)
+{
+ u32 int_part, fracn_raw = 0, fracd_raw = 0;
+ int rc, i;
+ u8 v;
+
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DIVN_INT, &v);
+ if (rc)
+ return rc;
+ int_part = v;
+
+ for (i = 3; i >= 0; i--) {
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DIVN_NUM + i, &v);
+ if (rc)
+ return rc;
+ fracn_raw = (fracn_raw << 8) | v;
+ }
+
+ for (i = 3; i >= 0; i--) {
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DIVN_DEN + i, &v);
+ if (rc)
+ return rc;
+ fracd_raw = (fracd_raw << 8) | v;
+ }
+
+ *divn = sit9531x_divn_fixed(int_part, (s32)fracn_raw,
+ (u64)fracd_raw + 1);
+
+ return 0;
+}
+
+/*
+ * sit9531x_divn_runtime - read the DIVN the digital loop is commanding
+ * @sitdev: device pointer
+ * @pll_idx: PLL index (0-3)
+ * @divn: result, fixed point as per sit9531x_divn_fixed()
+ *
+ * Same quantity as sit9531x_divn_static(), but sampled from the running
+ * loop rather than from the configuration registers, and carried at a
+ * wider precision: the numerator is 48 bits, two's complement, the
+ * denominator 49. The integer part shares its tap with the numerator.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int sit9531x_divn_runtime(struct sit9531x_dev *sitdev, u8 pll_idx,
+ s64 *divn)
+{
+ u8 buf[SIT9531X_DBG_DATA_BYTES];
+ u64 fracn_raw = 0, fracd = 0;
+ u32 int_part;
+ int rc, i;
+
+ rc = sit9531x_dbg_sample(sitdev, pll_idx, SIT9531X_DBG_READ_CODE_DIVN,
+ buf, SIT9531X_DBG_DATA_BYTES);
+ if (rc)
+ return rc;
+
+ for (i = 5; i >= 0; i--)
+ fracn_raw = (fracn_raw << 8) | buf[i];
+
+ int_part = buf[6] | ((u32)(buf[7] & SIT9531X_DIVN_RT_INT_HI_BIT) << 8);
+
+ rc = sit9531x_dbg_sample(sitdev, pll_idx,
+ SIT9531X_DBG_READ_CODE_DIVN_DEN, buf,
+ SIT9531X_DBG_DATA_BYTES);
+ if (rc)
+ return rc;
+
+ for (i = 5; i >= 0; i--)
+ fracd = (fracd << 8) | buf[i];
+
+ fracd |= (u64)(buf[6] & SIT9531X_DIVN_RT_INT_HI_BIT) << 48;
+
+ *divn = sit9531x_divn_fixed(int_part,
+ sign_extend64(fracn_raw,
+ SIT9531X_DIVN_RT_NUM_BITS - 1),
+ fracd);
+
+ return 0;
+}
+
+/**
+ * sit9531x_pll_ffo_ppt - fractional frequency offset of a PLL's reference
+ * @sitdev: device pointer
+ * @pll_idx: PLL index (0-3)
+ * @ffo: result in parts per trillion
+ *
+ * A locked PLL commands whatever DIVN keeps its VCO tracking the
+ * reference. How far that sits from the configured DIVN is how far the
+ * reference sits from the local oscillator, which is the fractional
+ * frequency offset the DPLL ABI reports for the pin feeding the device.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ *
+ * Return: 0 on success, -ENODATA when DIVN is not programmed, <0 on
+ * error.
+ */
+int sit9531x_pll_ffo_ppt(struct sit9531x_dev *sitdev, u8 pll_idx, s64 *ffo)
+{
+ s64 configured, running, delta;
+ u64 magnitude;
+ int rc;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (pll_idx >= SIT9531X_NUM_PLLS)
+ return -EINVAL;
+
+ rc = sit9531x_divn_static(sitdev, pll_idx, &configured);
+ if (rc)
+ return rc;
+ if (configured <= 0)
+ return -ENODATA;
+
+ rc = sit9531x_divn_runtime(sitdev, pll_idx, &running);
+ if (rc)
+ return rc;
+
+ delta = running - configured;
+ magnitude = mul_u64_u64_div_u64(abs(delta), SIT9531X_PPT_PER_UNIT,
+ (u64)configured);
+
+ *ffo = delta < 0 ? -(s64)magnitude : (s64)magnitude;
+
+ return 0;
+}
+
+/*
+ * sit9531x_get_fvco - read VCO frequency from chip's DIVN registers
+ *
+ * Fvco = Fref * DIVN, where DIVN comes from sit9531x_divn_static() and
+ * Fref = xtal_freq << doubler. DIVN is the
+ * steady-state Fvco/Fref target programmed by the NVM blob and is
+ * authoritative in both free-run and sync modes; the previous split
+ * between free-run and sync formulas returned 0 on chips that didn't
+ * have a sync input populated, which broke the TDC phase readback.
+ *
+ * Return: Fvco in Hz, or 0 on error
+ */
+static u64 sit9531x_get_fvco(struct sit9531x_dev *sitdev, u8 pll_idx)
+{
+ int doubler, rc;
+ s64 divn;
+ u64 fref;
+
+ /*
+ * DT board-config override: some configs (e.g. an INTSYNC PLL)
+ * run a VCO that Fref*DIVN does not reproduce. When the board
+ * supplies the measured VCO, use it verbatim.
+ */
+ if (pll_idx < SIT9531X_NUM_PLLS && sitdev->pll_fvco[pll_idx])
+ return sitdev->pll_fvco[pll_idx];
+
+ rc = sit9531x_divn_static(sitdev, pll_idx, &divn);
+ if (rc || divn <= 0)
+ return 0;
+
+ doubler = sit9531x_is_xo_doubler_enabled(sitdev);
+ if (doubler < 0)
+ return 0;
+
+ fref = (u64)sitdev->xtal_freq << doubler;
+
+ return mul_u64_u64_div_u64(fref, (u64)divn, SIT9531X_DIVN_SCALE);
+}
+
+/*
+ * sit9531x_output_phase_flush - flush the output phase of a PLL
+ *
+ * Fires the chip's on-demand phase-flush (PHFL) so every output divider
+ * of @pll_idx restarts aligned to the PLL phase. Without it a rewritten
+ * DIVO keeps counting from an arbitrary point and the output edge lands
+ * with a persistent offset against the tracked reference (only a power
+ * cycle realigned it).
+ *
+ * The sequence mirrors the vendor procedure: arm the on-demand PHFL and
+ * latch it with the PLL-page small-change update, then select the
+ * in-register phase trigger on Page 0 and pulse it. The Page 0 trigger
+ * register is touched read-modify-write so the unrelated OEb bits are
+ * preserved.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+static int sit9531x_output_phase_flush(struct sit9531x_dev *sitdev, u8 pll_idx)
+{
+ u8 ctrl, orig;
+ int rc, ret;
+
+ /* Arm the on-demand phase-flush on the PLL page. */
+ rc = sit9531x_update_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_PHFL_CTRL,
+ SIT9531X_PLL_PHFL_ON_DEMAND_EN,
+ SIT9531X_PLL_PHFL_ON_DEMAND_EN);
+ if (rc)
+ return rc;
+
+ /* Latch it with the PLL small-change update. */
+ rc = sit9531x_update_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_SMALL_UPDATE,
+ SIT9531X_SMALL_UPDATE_CMD,
+ SIT9531X_SMALL_UPDATE_CMD);
+ if (rc)
+ return rc;
+
+ /*
+ * Select the in-register phase trigger, preserving the OEb bits.
+ * Remember the original register value (with the trigger de-asserted)
+ * so the trigger-source select can be restored once the pulse has
+ * fired.
+ */
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG_GPIO_FUNC_CTRL1, &ctrl);
+ if (rc)
+ return rc;
+
+ orig = ctrl & ~SIT9531X_DIVO_PHASE_TRIG;
+ ctrl = orig | SIT9531X_DIVO_PHASE_SEL_REG;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_GPIO_FUNC_CTRL1, ctrl);
+ if (rc)
+ return rc;
+
+ /*
+ * Pulse the phase trigger. No explicit delay is needed between the
+ * set and clear writes: each I2C transaction takes far longer than
+ * any minimum pulse width.
+ */
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_GPIO_FUNC_CTRL1,
+ ctrl | SIT9531X_DIVO_PHASE_TRIG);
+
+ /*
+ * Restore the original trigger-source select. The pulse above has
+ * already latched the flush, so a one-shot flush must not leave the
+ * phase trigger permanently pinned to the in-register source. This
+ * runs even when the pulse write failed, otherwise a failed flush
+ * would keep a hardware trigger source hijacked; the restore error
+ * is only surfaced when it would not mask the pulse failure.
+ */
+ ret = sit9531x_write_u8(sitdev, SIT9531X_REG_GPIO_FUNC_CTRL1, orig);
+ if (ret && !rc)
+ rc = ret;
+
+ return rc;
+}
+
+/*
+ * sit9531x_output_freq_set - set output clock frequency
+ * @out_idx: output index (0-N for this chip variant)
+ * @pll_idx: PLL driving this output (0-3)
+ * @frequency: desired output frequency in Hz
+ *
+ * Computes DIVO = Fvco / frequency and writes the 34-bit output divider
+ * to the output system registers on Pages 3/4. The write sequence is:
+ * 1. Unlock debug registers (Page 3)
+ * 2. Enter PRG_CMD state
+ * 3. Write 5-byte DIVO to the correct page/slot
+ * 4. NVM update
+ * 5. Loop lock
+ * 6. Wait for lock to settle
+ * 7. Flush the output phase so the new divider starts aligned
+ *
+ * Caller must hold sitdev->multiop_lock.
+ *
+ * Return: 0 on success, <0 on error. Actual frequency may differ
+ * due to integer division; the output state is updated with
+ * the effective frequency (Fvco / DIVO).
+ */
+int sit9531x_output_freq_set(struct sit9531x_dev *sitdev, u8 out_idx,
+ u8 pll_idx, u64 frequency)
+{
+ const struct sit9531x_chip_info *info = sitdev->info;
+ u8 slot, page, base_reg, divo_bytes[5], msb_old;
+ u64 fvco, divo, fvco_min, fvco_max;
+ int rc, j, ret;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (out_idx >= info->num_outputs || pll_idx >= SIT9531X_NUM_PLLS)
+ return -EINVAL;
+
+ if (!frequency)
+ return -EINVAL;
+
+ /* Determine VCO frequency band limits */
+ if (pll_idx == 1 || pll_idx == 3) {
+ /* PLLB, PLLD: high band */
+ fvco_min = SIT9531X_FVCO_HIGHBAND_MIN;
+ fvco_max = SIT9531X_FVCO_HIGHBAND_MAX;
+ } else {
+ /* PLLA, PLLC: low band */
+ fvco_min = SIT9531X_FVCO_LOWBAND_MIN;
+ fvco_max = SIT9531X_FVCO_LOWBAND_MAX;
+ }
+
+ /*
+ * Read current VCO frequency. When the board supplies an explicit
+ * Fvco via "sitime,pll-fvco" the override is the source of truth
+ * (e.g. a chip variant that runs out of the documented band, or a
+ * mode like INTSYNC where Fref*DIVN does not reproduce the VCO), so
+ * skip the band clamp in that case.
+ */
+ fvco = sit9531x_get_fvco(sitdev, pll_idx);
+ if (!fvco) {
+ fvco = fvco_min;
+ } else if (!sitdev->pll_fvco[pll_idx]) {
+ if (fvco < fvco_min)
+ fvco = fvco_min;
+ else if (fvco > fvco_max)
+ fvco = fvco_max;
+ }
+
+ divo = div64_u64(fvco, frequency);
+ if (!divo)
+ return -EINVAL;
+
+ dev_dbg(sitdev->dev,
+ "out%u: Fvco=%llu freq=%llu DIVO=%llu (effective %llu Hz)\n",
+ out_idx, fvco, frequency, divo, div64_u64(fvco, divo));
+
+ /* Map output index to physical slot */
+ slot = info->clkout_map[out_idx];
+
+ /* Determine page and per-page slot register */
+ if (slot > SIT9531X_PAGE_OUTSYS0_SLOT_MAX)
+ page = SIT9531X_PAGE_OUTSYS1;
+ else
+ page = SIT9531X_PAGE_OUTSYS0;
+ base_reg = clkout_odr_divn_base[slot % 6];
+
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_OUTSYS_DEBUG,
+ SIT9531X_DEBUG_UNLOCK_VAL);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN,
+ SIT9531X_PRG_CMD_STATE);
+ if (rc)
+ return rc;
+
+ divo_bytes[0] = (divo >> 0) & 0xFF;
+ divo_bytes[1] = (divo >> 8) & 0xFF;
+ divo_bytes[2] = (divo >> 16) & 0xFF;
+ divo_bytes[3] = (divo >> 24) & 0xFF;
+ divo_bytes[4] = (divo >> 32) & 0x03; /* only bits [1:0] */
+
+ rc = sit9531x_read_u8(sitdev,
+ SIT9531X_REG(page, base_reg - 4), &msb_old);
+ if (rc)
+ goto commit;
+ divo_bytes[4] |= msb_old & 0xFC;
+
+ for (j = 0; j < 5; j++) {
+ rc = sit9531x_write_u8(sitdev,
+ SIT9531X_REG(page, base_reg - j),
+ divo_bytes[j]);
+ if (rc)
+ goto commit;
+ }
+
+commit:
+ /*
+ * Step 4: NVM update + loop lock. Always run prg_commit() so the chip
+ * leaves the PRG_CMD state with the output loops re-locked, even when a
+ * write above failed; keep the first error to return. It also carries
+ * the required post-lock settling sleep.
+ */
+ ret = sit9531x_prg_commit(sitdev);
+ if (ret && !rc)
+ rc = ret;
+ if (rc)
+ return rc;
+
+ /*
+ * Step 5: flush the PLL's output phase so the new DIVO starts
+ * aligned instead of keeping the arbitrary phase the divider
+ * happened to be at.
+ */
+ rc = sit9531x_output_phase_flush(sitdev, pll_idx);
+ if (rc)
+ return rc;
+
+ sitdev->out[out_idx].freq = (u32)div64_u64(fvco, divo);
+
+ return 0;
+}
+
+/*
+ * sit9531x_output_freq_get - read output clock frequency from hardware
+ * @out_idx: output index (0-N for this chip variant)
+ * @frequency: output frequency in Hz
+ *
+ * Reads the 34-bit DIVO divider back from the output system registers
+ * and computes the live output frequency as Fvco / DIVO. This stays
+ * correct even when the divider was reprogrammed behind the driver's
+ * back (e.g. by a direct-I2C userspace tool), where the cached value
+ * would be stale.
+ *
+ * The cached output state is refreshed with the computed value.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ *
+ * Return: 0 on success, -ENODEV when the output divider or VCO rate
+ * is not resolvable, <0 on register access error
+ */
+int sit9531x_output_freq_get(struct sit9531x_dev *sitdev, u8 out_idx,
+ u64 *frequency)
+{
+ const struct sit9531x_chip_info *info = sitdev->info;
+ u8 slot, page, base_reg, pll_idx, v;
+ u64 fvco, divo = 0;
+ int rc, j;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (out_idx >= info->num_outputs)
+ return -EINVAL;
+
+ pll_idx = sitdev->out[out_idx].pll_idx;
+ if (pll_idx >= SIT9531X_NUM_PLLS)
+ return -ENODEV;
+
+ fvco = sit9531x_get_fvco(sitdev, pll_idx);
+ if (!fvco)
+ return -ENODEV;
+
+ slot = info->clkout_map[out_idx];
+ if (slot > SIT9531X_PAGE_OUTSYS0_SLOT_MAX)
+ page = SIT9531X_PAGE_OUTSYS1;
+ else
+ page = SIT9531X_PAGE_OUTSYS0;
+ base_reg = clkout_odr_divn_base[slot % 6];
+
+ for (j = 4; j >= 0; j--) {
+ rc = sit9531x_read_u8(sitdev,
+ SIT9531X_REG(page, base_reg - j), &v);
+ if (rc)
+ return rc;
+ if (j == 4)
+ v &= 0x03;
+ divo = (divo << 8) | v;
+ }
+
+ if (!divo)
+ return -ENODEV;
+
+ *frequency = div64_u64(fvco, divo);
+ sitdev->out[out_idx].freq = (u32)*frequency;
+
+ return 0;
+}
+
+/*
+ * Phase adjust (PRG_RST_DELAY register-based).
+ *
+ * The chip exposes a per-output 34-bit coarse delay measured in VCO
+ * clock periods plus a 3-bit fine delay in fixed 30 ps steps. The
+ * five bytes PROG6..PROG2 hold the field across registers:
+ * base + 0 PROG6 [7:5] OPSTG_VCASC_BUMP (preserved via RMW)
+ * [4:2] PRG_RST_FINE_DELAY
+ * [1:0] PRG_RST_DELAY[33:32]
+ * base + 1 PROG5 PRG_RST_DELAY[31:24]
+ * base + 2 PROG4 PRG_RST_DELAY[23:16]
+ * base + 3 PROG3 PRG_RST_DELAY[15:8]
+ * base + 4 PROG2 PRG_RST_DELAY[7:0]
+ *
+ * Outputs 0-5 live on Page 3, outputs 6-11 on Page 4, with each
+ * output's block at base = 0x15 + 16 * (out_idx % 6).
+ *
+ * The chip only supports unsigned positive delay. A negative phase
+ * adjustment (advance) is wrapped to (T_out - |phase|) modulo one
+ * output period, which is identical for a periodic signal.
+ */
+
+int sit9531x_output_phase_adjust_set(struct sit9531x_dev *sitdev,
+ u8 out_idx, s32 phase_ps)
+{
+ const struct sit9531x_chip_info *info = sitdev->info;
+ u64 abs_ps, fvco, coarse, coarse_ps, rem_ps;
+ u8 page, base, prog6_val, fine = 0;
+ u8 pll_idx, slot;
+ u32 freq;
+ int rc, ret;
+
+ if (out_idx >= info->num_outputs)
+ return -EINVAL;
+
+ pll_idx = sitdev->out[out_idx].pll_idx;
+ if (pll_idx >= SIT9531X_NUM_PLLS)
+ return -EINVAL;
+
+ freq = sitdev->out[out_idx].freq;
+ if (!freq)
+ return -EINVAL;
+
+ fvco = sit9531x_get_fvco(sitdev, pll_idx);
+ if (!fvco)
+ return -EIO;
+
+ /*
+ * Convert to unsigned absolute delay. Negative phase (advance)
+ * is rendered as T_out - |phase|, modulo the output period.
+ */
+ if (phase_ps == 0) {
+ abs_ps = 0;
+ } else if (phase_ps > 0) {
+ abs_ps = (u64)phase_ps;
+ } else {
+ u64 t_out_ps = div64_u64(1000000000000ULL, freq);
+ u64 advance = (u64)(-(s64)phase_ps);
+
+ if (t_out_ps == 0)
+ return -EINVAL;
+ advance %= t_out_ps;
+ abs_ps = (advance == 0) ? 0 : (t_out_ps - advance);
+ }
+
+ /*
+ * coarse_cycles = abs_ps * Fvco / 1e12 ps/s.
+ * mul_u64_u64_div_u64() avoids overflow when abs_ps approaches
+ * one second of 1 PPS wrap-around.
+ */
+ coarse = mul_u64_u64_div_u64(abs_ps, fvco, 1000000000000ULL);
+ if (coarse >= (1ULL << SIT9531X_OUT_PRG_COARSE_BITS))
+ return -ERANGE;
+
+ /* Fine delay = round((abs_ps - coarse * vco_period_ps) / 30 ps) */
+ coarse_ps = mul_u64_u64_div_u64(coarse, 1000000000000ULL, fvco);
+ rem_ps = (abs_ps > coarse_ps) ? (abs_ps - coarse_ps) : 0;
+ if (rem_ps) {
+ u64 steps;
+
+ steps = div64_u64(rem_ps + SIT9531X_OUT_PRG_FINE_STEP_PS / 2,
+ SIT9531X_OUT_PRG_FINE_STEP_PS);
+ if (steps > SIT9531X_OUT_PRG_FINE_MAX)
+ steps = SIT9531X_OUT_PRG_FINE_MAX;
+ fine = (u8)steps;
+ }
+
+ /*
+ * Map logical output index to the chip's physical output slot.
+ * On SiT95317 the eight logical outputs land on chip slots
+ * {0, 3, 4, 5, 7, 8, 9, 11}; on SiT95316 the map is identity.
+ * Page/base must address the slot, not the logical index.
+ */
+ slot = info->clkout_map[out_idx];
+ page = (slot > SIT9531X_PAGE_OUTSYS0_SLOT_MAX) ?
+ SIT9531X_PAGE_OUTSYS1 : SIT9531X_PAGE_OUTSYS0;
+ base = SIT9531X_OUT_PRG_DELAY_BASE +
+ SIT9531X_OUT_PRG_SLOT_STRIDE * (slot % 6);
+
+ /* Caller (dpll.c) holds multiop_lock around the whole sequence. */
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ /*
+ * The PRG_RST_DELAY bytes live in the output system, so the writes
+ * only take effect when made inside the PRG_CMD programming state and
+ * committed to the NVM shadow, exactly like sit9531x_output_freq_set().
+ */
+ rc = sit9531x_prg_enter(sitdev);
+ if (rc)
+ return rc;
+
+ /* PROG6 RMW: preserve OPSTG_VCASC_BUMP in [7:5] */
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG(page, base),
+ &prog6_val);
+ if (rc)
+ goto commit;
+
+ prog6_val &= SIT9531X_OUT_PRG_OPSTG_MASK;
+ prog6_val |= (fine << SIT9531X_OUT_PRG_FINE_SHIFT) &
+ SIT9531X_OUT_PRG_FINE_MASK;
+ prog6_val |= (u8)((coarse >> 32) & SIT9531X_OUT_PRG_COARSE_HI_MASK);
+
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG(page, base),
+ prog6_val);
+ if (rc)
+ goto commit;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG(page, base + 1),
+ (u8)((coarse >> 24) & 0xFF));
+ if (rc)
+ goto commit;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG(page, base + 2),
+ (u8)((coarse >> 16) & 0xFF));
+ if (rc)
+ goto commit;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG(page, base + 3),
+ (u8)((coarse >> 8) & 0xFF));
+ if (rc)
+ goto commit;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG(page, base + 4),
+ (u8)(coarse & 0xFF));
+
+commit:
+ /*
+ * Always leave the PRG_CMD state via prg_commit(), even on a
+ * mid-sequence write failure, so the output loops are re-locked rather
+ * than stranded unlocked; keep the first error.
+ */
+ ret = sit9531x_prg_commit(sitdev);
+ if (ret && !rc)
+ rc = ret;
+ if (rc)
+ return rc;
+
+ /*
+ * Restart the output divider phase so the freshly programmed delay is
+ * applied against a known edge instead of the divider's arbitrary
+ * running phase.
+ */
+ return sit9531x_output_phase_flush(sitdev, pll_idx);
+}
+
+/*
+ * sit9531x_clear_notifications - clear all notification registers
+ *
+ * Clears all write-1-to-clear notification registers:
+ * - PLL outer LOL notification (Page 0, reg 0x07)
+ * - PLL holdover freeze notification (Page 0, reg 0x0B)
+ * - PLL inner LOL notification (Page 0, reg 0x93)
+ * - Clock monitor XO/PLL notification (Page 0, reg 0x9E)
+ * - Clock input notifications (Page 6, regs 0x03/0x07/0x93/0x97)
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+int sit9531x_clear_notifications(struct sit9531x_dev *sitdev)
+{
+ int rc;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ /* Page 0x00 W1C notification registers */
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_OUTER_LOL_NOTIF, 0xFF);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_HO_FREEZE_NOTIF, 0xFF);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_PLL_INNER_LOL_NOTIF, 0xFF);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_CMON_NOTIF, 0xFF);
+ if (rc)
+ return rc;
+
+ /* Page 0x06 clock input monitor notifications */
+ rc = sit9531x_write_u8(sitdev, SIT9531X_CLKMON_P_NOTIF_01, 0xFF);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_CLKMON_P_NOTIF_23, 0xFF);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_CLKMON_N_NOTIF_01, 0xFF);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_CLKMON_N_NOTIF_23, 0xFF);
+ if (rc)
+ return rc;
+
+ dev_dbg(sitdev->dev, "All notification registers cleared\n");
+ return 0;
+}
+
+/*
+ * INTSYNC configuration register values.
+ * These are written to the source PLL's EXT page to enable/disable
+ * inter-PLL synchronization (lock frequency PLL to phase PLL).
+ */
+struct sit9531x_intsync_reg {
+ u8 offset;
+ u8 en_val;
+ u8 dis_val;
+};
+
+static const struct sit9531x_intsync_reg intsync_config[] = {
+ { 0x2D, 0x02, 0x00 },
+ { 0x50, 0x08, 0x00 },
+ { 0x51, 0x04, 0x00 },
+ { 0x54, 0x02, 0x00 },
+ { 0x55, 0x28, 0x20 },
+ { 0x5C, 0x0F, 0x00 },
+ { 0x5D, 0xFF, 0x00 },
+ { 0x6C, 0xDD, 0x00 },
+};
+
+/*
+ * sit9531x_intsync_enable - enable inter-PLL synchronization
+ * @src_pll_idx: source (frequency) PLL index (0-3)
+ *
+ * Enables INTSYNC global bit, unlocks the source PLL's EXT page
+ * debug registers, writes configuration, and triggers a small
+ * update on the source PLL.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+int sit9531x_intsync_enable(struct sit9531x_dev *sitdev, u8 src_pll_idx)
+{
+ u8 ext_page, val;
+ int rc, i;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (src_pll_idx >= SIT9531X_NUM_PLLS)
+ return -EINVAL;
+
+ ext_page = SIT9531X_PLL_EXT_PAGE(src_pll_idx);
+
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL, &val);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL,
+ val | BIT(SIT9531X_INTSYNC_EN_BIT));
+ if (rc)
+ return rc;
+
+ /* Small update on Page 0 */
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_GLOBAL_UPDATE,
+ SIT9531X_SMALL_UPDATE_CMD);
+ if (rc)
+ return rc;
+
+ /* Unlock debug on EXT page */
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG(ext_page, SIT9531X_PLL_REG_DEBUG),
+ SIT9531X_PLL_DEBUG_UNLOCK);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < ARRAY_SIZE(intsync_config); i++) {
+ rc = sit9531x_write_u8(sitdev,
+ SIT9531X_REG(ext_page,
+ intsync_config[i].offset),
+ intsync_config[i].en_val);
+ if (rc)
+ return rc;
+ }
+
+ /* Small update on source PLL */
+ rc = sit9531x_write_pll_u8(sitdev, src_pll_idx,
+ SIT9531X_PLL_REG_SMALL_UPDATE,
+ SIT9531X_SMALL_UPDATE_CMD);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+/*
+ * sit9531x_intsync_disable - disable inter-PLL synchronization
+ * @src_pll_idx: source (frequency) PLL index (0-3)
+ *
+ * Clears INTSYNC global bit, writes disable values to the source
+ * PLL's EXT page, and triggers a small update.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+int sit9531x_intsync_disable(struct sit9531x_dev *sitdev, u8 src_pll_idx)
+{
+ u8 ext_page, val;
+ int rc, i;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (src_pll_idx >= SIT9531X_NUM_PLLS)
+ return -EINVAL;
+
+ ext_page = SIT9531X_PLL_EXT_PAGE(src_pll_idx);
+
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL, &val);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL,
+ val & ~BIT(SIT9531X_INTSYNC_EN_BIT));
+ if (rc)
+ return rc;
+
+ /* Small update on Page 0 */
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_GLOBAL_UPDATE,
+ SIT9531X_SMALL_UPDATE_CMD);
+ if (rc)
+ return rc;
+
+ /* Unlock debug on EXT page */
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG(ext_page, SIT9531X_PLL_REG_DEBUG),
+ SIT9531X_PLL_DEBUG_UNLOCK);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < ARRAY_SIZE(intsync_config); i++) {
+ rc = sit9531x_write_u8(sitdev,
+ SIT9531X_REG(ext_page,
+ intsync_config[i].offset),
+ intsync_config[i].dis_val);
+ if (rc)
+ return rc;
+ }
+
+ /* Small update on source PLL */
+ rc = sit9531x_write_pll_u8(sitdev, src_pll_idx,
+ SIT9531X_PLL_REG_SMALL_UPDATE,
+ SIT9531X_SMALL_UPDATE_CMD);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+/*
+ * sit9531x_output_pulse_ctrl_set - program per-output PULSE_CTRL byte
+ * @out_idx: logical output index (translated to chip slot internally)
+ * @pulse_ctrl: 8-bit PULSE_CTRL value (PROG0)
+ *
+ * Writes ODRn_PROG0 on the output page (Page 3 for slots 0..5,
+ * Page 4 for slots 6..11) at offset 0x1B + 16 * (slot % 6).
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+int sit9531x_output_pulse_ctrl_set(struct sit9531x_dev *sitdev,
+ u8 out_idx, u8 pulse_ctrl)
+{
+ const struct sit9531x_chip_info *info = sitdev->info;
+ u8 slot, page, reg;
+ int rc, ret;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (out_idx >= info->num_outputs)
+ return -EINVAL;
+
+ slot = info->clkout_map[out_idx];
+ page = (slot > SIT9531X_PAGE_OUTSYS0_SLOT_MAX) ?
+ SIT9531X_PAGE_OUTSYS1 : SIT9531X_PAGE_OUTSYS0;
+ reg = SIT9531X_OUT_PROG0_BASE +
+ SIT9531X_OUT_PRG_SLOT_STRIDE * (slot % 6);
+
+ /*
+ * PROG0 lives in the output system, so like the DIVO and
+ * PRG_RST_DELAY writes it only takes effect inside the PRG_CMD
+ * programming state committed to the NVM shadow.
+ */
+ rc = sit9531x_prg_enter(sitdev);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG(page, reg), pulse_ctrl);
+
+ /*
+ * Always leave the PRG_CMD state via prg_commit(), even if the write
+ * failed, so the output loops are re-locked rather than stranded
+ * unlocked; keep the first error.
+ */
+ ret = sit9531x_prg_commit(sitdev);
+ if (ret && !rc)
+ rc = ret;
+
+ return rc;
+}
+
+/* Retries (at 1 s each) to wait for a PLL to reach its active state. */
+#define SIT9531X_PLL_ACTIVE_RETRIES 90
+
+/*
+ * sit9531x_phase_offset_read - read phase difference via TDC
+ * @phase_ps: output phase difference in picoseconds
+ *
+ * Reads the Time-to-Digital Converter (TDC) 40-bit code from the
+ * PLL page registers, then converts to picoseconds using the VCO
+ * frequency: phase_diff = tdc_code / fvco.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+int sit9531x_phase_offset_read(struct sit9531x_dev *sitdev, u8 pll_idx,
+ s64 *phase_ps)
+{
+ u64 fvco, fvco_mhz;
+ s64 tdc_signed;
+ u64 tdc_raw;
+ int rc, i;
+ bool sign;
+ u8 v;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (pll_idx >= SIT9531X_NUM_PLLS)
+ return -EINVAL;
+
+ /* Unlock the debug page so the TDC registers are accessible. */
+ rc = sit9531x_write_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DEBUG,
+ SIT9531X_PLL_DEBUG_UNLOCK);
+ if (rc)
+ return rc;
+
+ /*
+ * Select the debug clock for taps below 200 kHz, then point the
+ * readback at the TDC. Only the one bit is touched: writing the
+ * modifier register whole would clear the fields belonging to
+ * other taps.
+ */
+ rc = sit9531x_update_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DBG_WRITE_CODE,
+ SIT9531X_DBG_LOW_FREQ_CLK_BIT,
+ SIT9531X_DBG_LOW_FREQ_CLK_BIT);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DBG_READ_CODE,
+ SIT9531X_DBG_READ_CODE_TDC);
+ if (rc)
+ return rc;
+
+ /* Latch a sample by reading the trigger register 3 times. */
+ for (i = 0; i < 3; i++) {
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DBG_TRIGGER, &v);
+ if (rc)
+ return rc;
+ }
+
+ tdc_raw = 0;
+
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DBG_DATA_4, &v);
+ if (rc)
+ return rc;
+ sign = !!(v & BIT(SIT9531X_TDC_SIGN_BIT));
+ tdc_raw = (u64)(v & 0x07) << 32;
+
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DBG_DATA_3, &v);
+ if (rc)
+ return rc;
+ tdc_raw |= (u64)v << 24;
+
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DBG_DATA_2, &v);
+ if (rc)
+ return rc;
+ tdc_raw |= (u64)v << 16;
+
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DBG_DATA_1, &v);
+ if (rc)
+ return rc;
+ tdc_raw |= (u64)v << 8;
+
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DBG_DATA_0, &v);
+ if (rc)
+ return rc;
+ tdc_raw |= v;
+
+ /*
+ * Apply sign. Per the vendor reference the sign bit is active-high
+ * for a positive offset: bit set -> +code, bit clear -> -code.
+ */
+ tdc_signed = sign ? (s64)tdc_raw : -(s64)tdc_raw;
+
+ /*
+ * Get VCO frequency for conversion. Fvco==0 means DIVN is not
+ * programmed (PLL unused on this board) -- skip silently rather
+ * than spamming the log on every poll cycle.
+ */
+ fvco = sit9531x_get_fvco(sitdev, pll_idx);
+ if (!fvco) {
+ dev_dbg(sitdev->dev, "PLL%c: Fvco unknown, skip TDC\n",
+ 'A' + pll_idx);
+ return -ENODEV;
+ }
+
+ /*
+ * phase_diff (seconds) = tdc_code / fvco
+ * phase_diff (ps) = tdc_code * 1e12 / fvco
+ *
+ * To avoid 64-bit overflow:
+ * phase_ps = tdc_code * 1e6 / (fvco / 1e6)
+ */
+ fvco_mhz = div64_u64(fvco, 1000000ULL);
+ if (!fvco_mhz)
+ return -EIO;
+
+ *phase_ps = div64_s64(tdc_signed * 1000000LL, (s64)fvco_mhz);
+
+ return 0;
+}
+
+/*
+ * sit9531x_ref_state_fetch - read input reference status from hardware
+ * @index: logical input index
+ *
+ * Reads LOS and OOF status for the given input lane from the Page 0x06
+ * clock monitor registers. P and N lanes have separate register banks;
+ * each register carries two input pairs nibble-packed (even pair in
+ * bits [3:0], odd pair in [7:4]).
+ */
+static int sit9531x_ref_state_fetch(struct sit9531x_dev *sitdev, u8 index)
+{
+ unsigned int reg, force_reg, state_reg;
+ u8 pair, status, force, state;
+ struct sit9531x_ref *ref;
+ int rc;
+
+ /*
+ * The XTAL/XO reference (index SIT9531X_MAX_INPUTS) is the on-chip
+ * oscillator that feeds every PLL. It cannot be routed or deselected,
+ * so its pin is modelled as permanently connected (see
+ * sit9531x_dpll_xo_pin_ops) and its LOS/OOF flags are never consulted.
+ * Only the routable per-lane inputs (0..num_inputs-1) are polled here.
+ */
+ if (index >= SIT9531X_MAX_INPUTS)
+ return -EINVAL;
+
+ ref = &sitdev->ref[index];
+ pair = sit9531x_input_pair(index);
+
+ if (sit9531x_input_is_n(index))
+ reg = pair < 2 ? SIT9531X_CLKMON_N_STATUS_01
+ : SIT9531X_CLKMON_N_STATUS_23;
+ else
+ reg = pair < 2 ? SIT9531X_CLKMON_P_STATUS_01
+ : SIT9531X_CLKMON_P_STATUS_23;
+
+ rc = sit9531x_read_u8(sitdev, reg, &status);
+ if (rc)
+ return rc;
+
+ if (pair & 1)
+ status >>= 4;
+
+ ref->los = !!(status & (BIT(SIT9531X_CLKMON_CLK_LOSS) |
+ BIT(SIT9531X_CLKMON_CLK_LOSS_FD)));
+ ref->oof = !!(status & (BIT(SIT9531X_CLKMON_FREQ_FINE) |
+ BIT(SIT9531X_CLKMON_FREQ_COARSE)));
+
+ /*
+ * Whether the receiver is on. This has to come from the chip: it
+ * is the loaded configuration that decides, and without reading it
+ * back every input would look disabled until something called
+ * sit9531x_input_enable(). A lane counts as disabled only while
+ * the force bit overrides it to the off state; with the force bit
+ * clear it follows the configuration, which is the enabled case.
+ */
+ sit9531x_input_get_regs(sitdev, index, &force_reg, &state_reg);
+
+ rc = sit9531x_read_u8(sitdev, force_reg, &force);
+ if (rc)
+ return rc;
+ rc = sit9531x_read_u8(sitdev, state_reg, &state);
+ if (rc)
+ return rc;
+
+ ref->enabled = !((force & BIT(pair)) && !(state & BIT(pair)));
+
+ return 0;
+}
+
+/*
+ * sit9531x_input_mode_fetch - detect SE/DE configuration of an input pair
+ * @pair: input pair number (0-3)
+ *
+ * Reads CLKINx_INPUT_MODE and stores the detected signal mode on both
+ * lanes of the pair. A pair with neither SE lane enabled is running
+ * differential.
+ */
+static int sit9531x_input_mode_fetch(struct sit9531x_dev *sitdev, u8 pair)
+{
+ enum sit9531x_signal_mode sig_mode;
+ u8 mode;
+ int rc;
+
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG_IN_MODE(pair), &mode);
+ if (rc)
+ return rc;
+
+ if (mode & (SIT9531X_IN_MODE_SE_P_EN | SIT9531X_IN_MODE_SE_N_EN))
+ sig_mode = SIT9531X_MODE_SE;
+ else
+ sig_mode = SIT9531X_MODE_DE;
+
+ sitdev->ref[pair * 2].sig_mode = sig_mode;
+ sitdev->ref[pair * 2 + 1].sig_mode = sig_mode;
+
+ dev_dbg(sitdev->dev, "CLKIN%u mode reg 0x%02x -> %s\n", pair, mode,
+ sig_mode == SIT9531X_MODE_DE ? "differential" : "single-ended");
+
+ return 0;
+}
+
+/*
+ * sit9531x_chan_state_fetch - read PLL channel status from hardware
+ *
+ * Reads lock status and mode from the PLL status register.
+ */
+/* Read the PLL active-state bit (PLL page reg 0x02 bit 0). */
+static int sit9531x_pll_is_active(struct sit9531x_dev *sitdev, u8 pll_idx,
+ bool *active)
+{
+ u8 v;
+ int rc;
+
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_ACTIVE, &v);
+ if (rc)
+ return rc;
+
+ *active = !!(v & SIT9531X_PLL_ACTIVE_BIT);
+
+ return 0;
+}
+
+static int sit9531x_chan_state_fetch(struct sit9531x_dev *sitdev, u8 pll_idx)
+{
+ u8 status, outer_lol, input_sel, inner_lol, ho_freeze, activesel_reg;
+ struct sit9531x_chan *chan = &sitdev->chan[pll_idx];
+ bool active;
+ int rc;
+
+ /*
+ * Whether the PLL is running at all. The loss-of-lock bit read
+ * below is driven by the PLL itself, so on one the loaded
+ * configuration leaves unused it simply stays clear and would
+ * otherwise read as a lock.
+ */
+ rc = sit9531x_pll_is_active(sitdev, pll_idx, &active);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_STATUS, &status);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG_OUTER_LOL_STATUS,
+ &outer_lol);
+ if (rc)
+ return rc;
+
+ /*
+ * Read the input source the PLL has currently selected as its
+ * active reference. This lives in the low nibble of the last
+ * register of the PLL's page-1 priority block (CLK_ACTIVESEL_PLL),
+ * not on the PLL page -- PLL-page 0x29 is a config register.
+ */
+ activesel_reg = SIT9531X_PRIO_BASE_REG +
+ SIT9531X_PRIO_REGS_PER_PLL * pll_idx +
+ SIT9531X_PRIO_ACTIVESEL_OFF;
+ rc = sit9531x_read_u8(sitdev,
+ SIT9531X_REG(SIT9531X_PAGE_PRIOSYS,
+ activesel_reg),
+ &input_sel);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG_PLL_INNER_LOL_STATUS,
+ &inner_lol);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG_HO_FREEZE_STATUS, &ho_freeze);
+ if (rc)
+ return rc;
+
+ /* STATUS_1_GENERIC reports loss of lock, so invert it. */
+ chan->active = active;
+ chan->locked = active && !(outer_lol & BIT(pll_idx));
+ chan->mode = !!(status & SIT9531X_PLL_STATUS_OUTER_DIS);
+ chan->selected_ref =
+ sit9531x_hw_src_input(input_sel & SIT9531X_PRIO_NIBBLE_MASK);
+ chan->inner_lol = !!(inner_lol & BIT(pll_idx));
+ chan->ho_freeze = !!(ho_freeze & BIT(pll_idx));
+
+ return 0;
+}
+
+/*
+ * sit9531x_out_state_fetch - read output status from hardware
+ *
+ * Reads the output PLL association from the PLL page output map
+ * registers into out->routed / out->pll_idx, and the current drive
+ * state from the Hi-Z force bits into out->enabled. The two are
+ * separate: routing decides whether the output gets a DPLL pin at all,
+ * while a muted but routed output keeps its pin and reports
+ * DPLL_PIN_STATE_DISCONNECTED until it is un-muted.
+ */
+static int sit9531x_out_state_fetch(struct sit9531x_dev *sitdev, u8 index)
+{
+ struct sit9531x_out *out = &sitdev->out[index];
+ u8 map_lo, map_hi, slot;
+ int pll_idx;
+ bool muted;
+ int rc;
+
+ slot = sitdev->info->clkout_map[index];
+
+ rc = sit9531x_output_forced_hiz(sitdev, slot, &muted);
+ if (rc)
+ return rc;
+
+ /*
+ * DT board-config override: the per-PLL OUTPUT_ENABLE bitmaps
+ * (0x27/0x28) do not unambiguously express output->PLL routing on
+ * every config (overlaps, and some outputs routed outside that
+ * path). When the board supplies an explicit map, trust it.
+ */
+ if (sitdev->out_pll_map_valid) {
+ u8 m = sitdev->out_pll_map[index];
+
+ if (m < SIT9531X_NUM_PLLS) {
+ out->pll_idx = m;
+ out->routed = true;
+ out->enabled = !muted;
+ } else {
+ out->pll_idx = 0;
+ out->routed = false;
+ out->enabled = false;
+ }
+ return 0;
+ }
+
+ /*
+ * The OUT_MAP_LO/HI bitmaps are indexed by the physical slot the
+ * output occupies on the chip, not by the driver's logical output
+ * index (translated above via the chip-info clkout_map[]: identity
+ * on SiT95316, non-contiguous on SiT95317).
+ *
+ * Determine which PLL drives this output by checking each PLL's
+ * output map registers (0x27 = slots 8-11, 0x28 = slots 0-7).
+ */
+ for (pll_idx = 0; pll_idx < SIT9531X_NUM_PLLS; pll_idx++) {
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_OUT_MAP_LO, &map_lo);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_OUT_MAP_HI, &map_hi);
+ if (rc)
+ return rc;
+
+ if (slot < 8) {
+ if (map_lo & BIT(slot)) {
+ out->pll_idx = pll_idx;
+ out->routed = true;
+ out->enabled = !muted;
+ return 0;
+ }
+ } else {
+ if (map_hi & BIT(slot - 8)) {
+ out->pll_idx = pll_idx;
+ out->routed = true;
+ out->enabled = !muted;
+ return 0;
+ }
+ }
+ }
+
+ /* Output not mapped to any PLL */
+ out->pll_idx = 0;
+ out->routed = false;
+ out->enabled = false;
+
+ return 0;
+}
+
+/*
+ * sit9531x_ref_pll_mask_fetch - seed the input-to-PLL usage masks
+ *
+ * ref->pll_mask is the refcount the disconnect path uses to decide when
+ * an input receiver may be powered down: the physical input is only
+ * disabled once the last DPLL has released it. It therefore has to
+ * start out matching the hardware. Without this pass every mask starts
+ * at zero, and disconnecting an input from one DPLL drops the mask to
+ * zero and disables a receiver the other DPLLs are still locked to.
+ *
+ * An input is counted for a PLL when it appears in that PLL's Page-1
+ * priority table, which is exactly the condition the connect and
+ * disconnect callbacks maintain. Sources that are not physical inputs
+ * (OCXO, INTSYNC) and reserved codes are skipped.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+static int sit9531x_ref_pll_mask_fetch(struct sit9531x_dev *sitdev)
+{
+ u8 srcs[SIT9531X_PRIO_MAX_SLOTS];
+ u8 pll_idx, slot, index;
+ int rc;
+
+ for (pll_idx = 0; pll_idx < SIT9531X_NUM_PLLS; pll_idx++) {
+ rc = sit9531x_prio_table_read(sitdev, pll_idx, srcs);
+ if (rc)
+ return rc;
+
+ for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) {
+ index = sit9531x_hw_src_input(srcs[slot]);
+ if (index >= sitdev->info->num_inputs)
+ continue;
+
+ /*
+ * On a differentially configured pair only the P lane
+ * has a DPLL pin, so that is the lane the connect and
+ * disconnect callbacks account for. Fold an N-lane
+ * table entry onto its P lane, otherwise the count
+ * would land on a lane nothing ever releases. The
+ * signalling mode is already known here:
+ * sit9531x_input_mode_fetch() runs first.
+ */
+ if (sit9531x_input_is_n(index) &&
+ sitdev->ref[index].sig_mode == SIT9531X_MODE_DE)
+ index--;
+
+ sitdev->ref[index].pll_mask |= BIT(pll_idx);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * sit9531x_dev_state_fetch - read all hardware state at startup
+ *
+ * Called once during probe to populate the initial state cache.
+ */
+static int sit9531x_dev_state_fetch(struct sit9531x_dev *sitdev)
+{
+ int rc;
+ u8 i;
+
+ /* Detect SE/DE configuration before any per-lane access */
+ for (i = 0; i < sitdev->info->num_inputs / 2; i++) {
+ rc = sit9531x_input_mode_fetch(sitdev, i);
+ if (rc) {
+ dev_err(sitdev->dev,
+ "Failed to fetch CLKIN%u mode: %d\n", i, rc);
+ return rc;
+ }
+ }
+
+ for (i = 0; i < sitdev->info->num_inputs; i++) {
+ rc = sit9531x_ref_state_fetch(sitdev, i);
+ if (rc) {
+ dev_err(sitdev->dev,
+ "Failed to fetch input %u state: %d\n", i, rc);
+ return rc;
+ }
+ }
+
+ /*
+ * The priority-table read walks the Page-1 registers, so it runs
+ * with multiop_lock held like every other multi-register sequence.
+ * Nothing can race with it here -- the DPLLs are not registered and
+ * the monitor is not running yet -- but the page handling stays
+ * serialized the same way as at runtime.
+ */
+ mutex_lock(&sitdev->multiop_lock);
+ rc = sit9531x_ref_pll_mask_fetch(sitdev);
+ mutex_unlock(&sitdev->multiop_lock);
+ if (rc) {
+ dev_err(sitdev->dev,
+ "Failed to fetch input priority tables: %d\n", rc);
+ return rc;
+ }
+
+ for (i = 0; i < sitdev->info->num_outputs; i++) {
+ rc = sit9531x_out_state_fetch(sitdev, i);
+ if (rc) {
+ dev_err(sitdev->dev,
+ "Failed to fetch output %u state: %d\n", i, rc);
+ return rc;
+ }
+ }
+
+ for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
+ rc = sit9531x_chan_state_fetch(sitdev, i);
+ if (rc) {
+ dev_err(sitdev->dev,
+ "Failed to fetch PLL%c state: %d\n",
+ 'A' + i, rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static void sit9531x_dev_ref_states_update(struct sit9531x_dev *sitdev)
+{
+ int i, rc;
+
+ for (i = 0; i < sitdev->info->num_inputs; i++) {
+ rc = sit9531x_ref_state_fetch(sitdev, i);
+ if (rc)
+ dev_warn(sitdev->dev,
+ "Failed to get REF%u status: %d\n", i, rc);
+ }
+}
+
+static void sit9531x_dev_chan_states_update(struct sit9531x_dev *sitdev)
+{
+ int i, rc;
+
+ for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
+ rc = sit9531x_chan_state_fetch(sitdev, i);
+ if (rc)
+ dev_warn(sitdev->dev,
+ "Failed to get PLL%c state: %d\n",
+ 'A' + i, rc);
+ }
+}
+
+/*
+ * sit9531x_dev_periodic_work - periodic hardware state polling
+ * @work: kthread_work pointer
+ *
+ * Polls hardware state at SIT9531X_STATUS_POLL_MS intervals.
+ * Updates reference and channel states, then delegates change
+ * detection to sit9531x_dpll_changes_check() for each registered DPLL.
+ */
+static void sit9531x_dev_periodic_work(struct kthread_work *work)
+{
+ struct sit9531x_dev *sitdev = container_of(work, struct sit9531x_dev,
+ work.work);
+ struct sit9531x_dpll *sitdpll;
+ int rc;
+
+ /*
+ * Update the cached ref[]/chan[] arrays under multiop_lock so the
+ * fetches are serialized against the DPLL callbacks that read
+ * these fields and against the chip's page selector.
+ *
+ * The lock is then dropped before sit9531x_dpll_changes_check(),
+ * which calls dpll_pin_change_ntf() / dpll_device_change_ntf().
+ * Those notification helpers take DPLL-subsystem locks that are
+ * already held when our callbacks are invoked from netlink
+ * context, and nesting multiop_lock around them would invert the
+ * lock order. changes_check() reads the cache published above,
+ * which is already consistent.
+ */
+ mutex_lock(&sitdev->multiop_lock);
+ sit9531x_dev_ref_states_update(sitdev);
+ sit9531x_dev_chan_states_update(sitdev);
+ mutex_unlock(&sitdev->multiop_lock);
+
+ list_for_each_entry(sitdpll, &sitdev->dplls, list)
+ sit9531x_dpll_changes_check(sitdpll);
+
+ /*
+ * Acknowledge the chip's notification latches after the tick has
+ * read and acted on them. Without this, the W1C bits remain set
+ * and -- on boards that wire INTRB -- the line stays asserted,
+ * re-firing the threaded handler back to back. The helper writes
+ * W1C bits across page 0 and page 6 and must run under
+ * multiop_lock to serialize the page selector against userspace
+ * dpll ops. Failure is non-fatal: status was already consumed
+ * for this tick and the next tick re-processes whatever stayed
+ * latched.
+ */
+ mutex_lock(&sitdev->multiop_lock);
+ rc = sit9531x_clear_notifications(sitdev);
+ mutex_unlock(&sitdev->multiop_lock);
+ if (rc)
+ dev_warn_ratelimited(sitdev->dev,
+ "Failed to clear notifications: %d\n",
+ rc);
+
+ /* Run twice a second */
+ kthread_queue_delayed_work(sitdev->kworker, &sitdev->work,
+ msecs_to_jiffies(SIT9531X_STATUS_POLL_MS));
+}
+
+/*
+ * sit9531x_irq_thread_fn - threaded IRQ handler for the chip's INTRB line
+ *
+ * Triggered when the chip asserts INTRB (and only when DT wires up the
+ * client interrupt; absent property == handler never installed). The
+ * action mirrors a periodic-work tick: queue an immediate run so status
+ * registers are read and DPLL changes_check fires without waiting for
+ * the next poll deadline. Polling continues to run as a fallback.
+ */
+static irqreturn_t sit9531x_irq_thread_fn(int irq, void *data)
+{
+ struct sit9531x_dev *sitdev = data;
+ int rc;
+
+ /*
+ * Acknowledge the chip's notification latches from the threaded
+ * handler itself. With IRQF_ONESHOT the line is unmasked on
+ * return, so deferring the W1C clear to the async kworker would
+ * let a still-asserted INTRB re-fire immediately (interrupt storm).
+ * Clear here, then kick the poll worker to read state and run
+ * changes_check.
+ */
+ mutex_lock(&sitdev->multiop_lock);
+ rc = sit9531x_clear_notifications(sitdev);
+ mutex_unlock(&sitdev->multiop_lock);
+ if (rc)
+ dev_warn_ratelimited(sitdev->dev,
+ "IRQ: failed to clear notifications: %d\n",
+ rc);
+
+ kthread_mod_delayed_work(sitdev->kworker, &sitdev->work, 0);
+ return IRQ_HANDLED;
+}
+
+/*
+ * sit9531x_dev_start - start normal operation
+ *
+ * Fetches initial hardware state, registers all DPLL devices and
+ * their pins, and starts the periodic monitoring thread.
+ */
+int sit9531x_dev_start(struct sit9531x_dev *sitdev)
+{
+ struct sit9531x_dpll *sitdpll;
+ int rc;
+
+ /* Fetch device state */
+ rc = sit9531x_dev_state_fetch(sitdev);
+ if (rc)
+ return rc;
+
+ list_for_each_entry(sitdpll, &sitdev->dplls, list) {
+ rc = sit9531x_dpll_register(sitdpll);
+ if (rc) {
+ dev_err_probe(sitdev->dev, rc,
+ "Failed to register DPLL%u\n",
+ sitdpll->id);
+ return rc;
+ }
+ }
+
+ kthread_queue_delayed_work(sitdev->kworker, &sitdev->work, 0);
+
+ /* Development/test knobs (best-effort) */
+
+ return 0;
+}
+
+/*
+ * sit9531x_dev_stop - stop normal operation
+ *
+ * Cancels the monitoring thread and unregisters all DPLL devices
+ * and their pins.
+ */
+void sit9531x_dev_stop(struct sit9531x_dev *sitdev)
+{
+ struct sit9531x_dpll *sitdpll;
+
+ kthread_cancel_delayed_work_sync(&sitdev->work);
+
+ list_for_each_entry(sitdpll, &sitdev->dplls, list) {
+ if (sitdpll->dpll_dev)
+ sit9531x_dpll_unregister(sitdpll);
+ }
+}
+
+static struct sit9531x_dpll_pin *
+sit9531x_dpll_pin_alloc(struct sit9531x_dpll *sitdpll,
+ enum dpll_pin_direction dir, u8 id)
+{
+ struct sit9531x_dpll_pin *pin;
+
+ pin = kzalloc_obj(*pin, GFP_KERNEL);
+ if (!pin)
+ return ERR_PTR(-ENOMEM);
+
+ pin->dpll = sitdpll;
+ pin->dir = dir;
+ pin->id = id;
+
+ return pin;
+}
+
+/*
+ * sit9531x_dpll_pin_register - register a DPLL pin with the subsystem
+ * @index: absolute pin index for clock_id namespace
+ *
+ * Gets pin properties from firmware, creates or gets a dpll_pin,
+ * and registers it with the parent DPLL device.
+ */
+static int sit9531x_dpll_pin_register(struct sit9531x_dpll_pin *pin,
+ u32 index)
+{
+ struct sit9531x_dpll *sitdpll = pin->dpll;
+ struct sit9531x_pin_props *props;
+ const struct dpll_pin_ops *ops;
+ int rc;
+
+ props = sit9531x_pin_props_get(sitdpll->dev, pin->dir, pin->id);
+ if (IS_ERR(props))
+ return PTR_ERR(props);
+
+ strscpy(pin->label, props->package_label, sizeof(pin->label));
+ pin->fwnode = fwnode_handle_get(props->fwnode);
+ pin->esync_control = props->esync_control;
+
+ pin->dpll_pin = dpll_pin_get(sitdpll->dev->clock_id, index,
+ THIS_MODULE, &props->dpll_props,
+ &pin->tracker);
+ if (IS_ERR(pin->dpll_pin)) {
+ rc = PTR_ERR(pin->dpll_pin);
+ goto err_pin_get;
+ }
+ dpll_pin_fwnode_set(pin->dpll_pin, props->fwnode);
+
+ ops = sit9531x_dpll_pin_ops_get(pin);
+
+ rc = dpll_pin_register(sitdpll->dpll_dev, pin->dpll_pin, ops, pin);
+ if (rc)
+ goto err_register;
+
+ sit9531x_pin_props_put(props);
+
+ return 0;
+
+err_register:
+ dpll_pin_put(pin->dpll_pin, &pin->tracker);
+err_pin_get:
+ /* dpll_pin_get() left an ERR_PTR here. */
+ pin->dpll_pin = NULL;
+ fwnode_handle_put(pin->fwnode);
+ pin->fwnode = NULL;
+ sit9531x_pin_props_put(props);
+
+ return rc;
+}
+
+static void sit9531x_dpll_pin_unregister(struct sit9531x_dpll_pin *pin)
+{
+ struct sit9531x_dpll *sitdpll = pin->dpll;
+ const struct dpll_pin_ops *ops;
+
+ ops = sit9531x_dpll_pin_ops_get(pin);
+
+ dpll_pin_unregister(sitdpll->dpll_dev, pin->dpll_pin, ops, pin);
+ dpll_pin_put(pin->dpll_pin, &pin->tracker);
+ pin->dpll_pin = NULL;
+
+ fwnode_handle_put(pin->fwnode);
+ pin->fwnode = NULL;
+}
+
+static void sit9531x_dpll_pins_unregister(struct sit9531x_dpll *sitdpll)
+{
+ struct sit9531x_dpll_pin *pin, *next;
+
+ list_for_each_entry_safe(pin, next, &sitdpll->pins, list) {
+ sit9531x_dpll_pin_unregister(pin);
+ list_del(&pin->list);
+ kfree(pin);
+ }
+}
+
+/*
+ * sit9531x_input_pin_is_registrable - check if an input pin is registrable
+ *
+ * Split out so input-model changes stay local to this helper.
+ *
+ * Return: true if the input pin should be registered, false otherwise
+ */
+static bool sit9531x_input_pin_is_registrable(struct sit9531x_dev *sitdev,
+ u8 index)
+{
+ if (index >= sitdev->info->num_inputs)
+ return false;
+
+ /*
+ * The N lane of a differentially-configured pair is not a
+ * standalone input and is skipped (zl3073x model).
+ */
+ if (sit9531x_input_is_n(index) &&
+ sitdev->ref[index].sig_mode == SIT9531X_MODE_DE)
+ return false;
+
+ return true;
+}
+
+/*
+ * sit9531x_dpll_pin_is_registrable - check if a pin should be registered
+ * @dir: pin direction
+ * @index: pin hardware index
+ *
+ * For input pins: delegate to sit9531x_input_pin_is_registrable().
+ * For output pins: the pin is registrable if this DPLL is routed to it,
+ * whether or not it is currently driving.
+ *
+ * Return: true if pin should be registered, false otherwise
+ */
+static bool sit9531x_dpll_pin_is_registrable(struct sit9531x_dpll *sitdpll,
+ enum dpll_pin_direction dir,
+ u8 index)
+{
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+
+ if (dir == DPLL_PIN_DIRECTION_INPUT) {
+ /* The internal INTSYNC and XO pins are always registrable */
+ if (index == SIT9531X_INTSYNC_PIN_ID ||
+ index == SIT9531X_MAX_INPUTS)
+ return true;
+
+ return sit9531x_input_pin_is_registrable(sitdev, index);
+ }
+
+ /* The internal INTSYNC source pin is always registrable */
+ if (index == SIT9531X_INTSYNC_OUT_PIN_ID)
+ return true;
+
+ /* Output -- check if driven by this DPLL */
+ if (index >= sitdev->info->num_outputs)
+ return false;
+
+ /*
+ * Routing, not the current drive state: an output the initial
+ * configuration muted still gets a pin, so userspace can see it and
+ * un-mute it. Only outputs no PLL drives are left unregistered.
+ */
+ return sitdev->out[index].pll_idx == sitdpll->id &&
+ sitdev->out[index].routed;
+}
+
+/*
+ * sit9531x_dpll_pins_register - register all registrable pins
+ *
+ * Enumerates all possible input and output pins, checks registrability,
+ * and registers each one. Input pins come first, then output pins,
+ * with input pins first, then output pins.
+ */
+static int sit9531x_dpll_pins_register(struct sit9531x_dpll *sitdpll)
+{
+ struct sit9531x_dpll_pin *pin;
+ enum dpll_pin_direction dir;
+ u8 id, index;
+ int rc;
+
+ for (index = 0; index < SIT9531X_NUM_PINS_TOTAL; index++) {
+ if (index < SIT9531X_NUM_INPUT_PINS) {
+ id = index;
+ dir = DPLL_PIN_DIRECTION_INPUT;
+ } else {
+ id = index - SIT9531X_NUM_INPUT_PINS;
+ dir = DPLL_PIN_DIRECTION_OUTPUT;
+ }
+
+ if (!sit9531x_dpll_pin_is_registrable(sitdpll, dir, id))
+ continue;
+
+ pin = sit9531x_dpll_pin_alloc(sitdpll, dir, id);
+ if (IS_ERR(pin)) {
+ rc = PTR_ERR(pin);
+ goto error;
+ }
+
+ rc = sit9531x_dpll_pin_register(pin, index);
+ if (rc) {
+ kfree(pin);
+ goto error;
+ }
+
+ list_add(&pin->list, &sitdpll->pins);
+ }
+
+ return 0;
+
+error:
+ sit9531x_dpll_pins_unregister(sitdpll);
+ return rc;
+}
+
+static int sit9531x_dpll_device_register(struct sit9531x_dpll *sitdpll)
+{
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ int rc;
+
+ sitdpll->ops = sit9531x_dpll_device_ops;
+
+ sitdpll->dpll_dev = dpll_device_get(sitdev->clock_id, sitdpll->id,
+ THIS_MODULE, &sitdpll->tracker);
+ if (IS_ERR(sitdpll->dpll_dev)) {
+ rc = PTR_ERR(sitdpll->dpll_dev);
+ sitdpll->dpll_dev = NULL;
+ return rc;
+ }
+
+ rc = dpll_device_register(sitdpll->dpll_dev,
+ sit9531x_prop_dpll_type_get(sitdev,
+ sitdpll->id),
+ &sitdpll->ops, sitdpll);
+ if (rc) {
+ dpll_device_put(sitdpll->dpll_dev, &sitdpll->tracker);
+ sitdpll->dpll_dev = NULL;
+ }
+
+ return rc;
+}
+
+static void sit9531x_dpll_device_unregister(struct sit9531x_dpll *sitdpll)
+{
+ dpll_device_unregister(sitdpll->dpll_dev, &sitdpll->ops, sitdpll);
+ dpll_device_put(sitdpll->dpll_dev, &sitdpll->tracker);
+ sitdpll->dpll_dev = NULL;
+}
+
+/*
+ * sit9531x_dpll_alloc - allocate a DPLL device structure
+ * @sitdev: parent device
+ * @ch: PLL channel number (0-3)
+ *
+ * Return: pointer to allocated DPLL on success, error pointer on error
+ */
+struct sit9531x_dpll *sit9531x_dpll_alloc(struct sit9531x_dev *sitdev, u8 ch)
+{
+ struct sit9531x_dpll *sitdpll;
+
+ sitdpll = kzalloc_obj(*sitdpll, GFP_KERNEL);
+ if (!sitdpll)
+ return ERR_PTR(-ENOMEM);
+
+ sitdpll->dev = sitdev;
+ sitdpll->id = ch;
+ sitdpll->lock_status = DPLL_LOCK_STATUS_UNLOCKED;
+ INIT_LIST_HEAD(&sitdpll->pins);
+
+ return sitdpll;
+}
+
+/*
+ * sit9531x_dpll_free - deallocate a DPLL device structure
+ * @sitdpll: DPLL to free
+ */
+void sit9531x_dpll_free(struct sit9531x_dpll *sitdpll)
+{
+ kfree(sitdpll);
+}
+
+/*
+ * sit9531x_dpll_register - register DPLL device and all its pins
+ *
+ * Registers the DPLL device with the subsystem and then registers
+ * all input and output pins that are connected to this PLL.
+ */
+int sit9531x_dpll_register(struct sit9531x_dpll *sitdpll)
+{
+ int rc;
+
+ rc = sit9531x_dpll_device_register(sitdpll);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_dpll_pins_register(sitdpll);
+ if (rc) {
+ sit9531x_dpll_device_unregister(sitdpll);
+ return rc;
+ }
+
+ return 0;
+}
+
+/* sit9531x_dpll_unregister - unregister DPLL device and its pins */
+void sit9531x_dpll_unregister(struct sit9531x_dpll *sitdpll)
+{
+ sit9531x_dpll_pins_unregister(sitdpll);
+ sit9531x_dpll_device_unregister(sitdpll);
+}
+
+static void sit9531x_dpll_list_free(struct sit9531x_dev *sitdev)
+{
+ struct sit9531x_dpll *sitdpll, *next;
+
+ list_for_each_entry_safe(sitdpll, next, &sitdev->dplls, list) {
+ list_del(&sitdpll->list);
+ sit9531x_dpll_free(sitdpll);
+ }
+}
+
+/* Runs only once the device is fully started, see the caller. */
+static void sit9531x_dev_dpll_fini(void *ptr)
+{
+ struct sit9531x_dev *sitdev = ptr;
+
+ sit9531x_dev_stop(sitdev);
+ kthread_destroy_worker(sitdev->kworker);
+ sit9531x_dpll_list_free(sitdev);
+}
+
+static int sit9531x_devm_dpll_init(struct sit9531x_dev *sitdev)
+{
+ struct kthread_worker *kworker;
+ struct sit9531x_dpll *sitdpll;
+ unsigned int i;
+ int rc;
+
+ INIT_LIST_HEAD(&sitdev->dplls);
+ kthread_init_delayed_work(&sitdev->work, sit9531x_dev_periodic_work);
+
+ for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
+ sitdpll = sit9531x_dpll_alloc(sitdev, i);
+ if (IS_ERR(sitdpll)) {
+ rc = dev_err_probe(sitdev->dev, PTR_ERR(sitdpll),
+ "Failed to alloc DPLL%u\n", i);
+ goto err_free_dplls;
+ }
+
+ list_add_tail(&sitdpll->list, &sitdev->dplls);
+ }
+
+ kworker = kthread_run_worker(0, "sit9531x-%s", dev_name(sitdev->dev));
+ if (IS_ERR(kworker)) {
+ rc = PTR_ERR(kworker);
+ goto err_free_dplls;
+ }
+ sitdev->kworker = kworker;
+
+ rc = sit9531x_dev_start(sitdev);
+ if (rc) {
+ rc = dev_err_probe(sitdev->dev, rc, "Failed to start device\n");
+ goto err_destroy_worker;
+ }
+
+ /*
+ * Only now is every field the cleanup touches valid, so this is the
+ * first point at which the action may be registered. On failure it
+ * runs the action itself, which is correct here and only here.
+ */
+ return devm_add_action_or_reset(sitdev->dev, sit9531x_dev_dpll_fini,
+ sitdev);
+
+err_destroy_worker:
+ kthread_destroy_worker(sitdev->kworker);
+err_free_dplls:
+ sit9531x_dpll_list_free(sitdev);
+
+ return rc;
+}
+
+/*
+ * sit9531x_read_variant_id - read chip variant ID byte from hardware
+ * @id: output variant ID byte
+ *
+ * Reads the single-byte variant identification register from Page 0
+ * reg 0x02 (95317 = 0x17, 95316 = 0x31). Reg 0x03 holds a separate
+ * revision byte and is intentionally not consumed here.
+ */
+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;
+}
+
+/*
+ * sit9531x_derive_clock_id - build EUI-64 clock identifier
+ *
+ * Generates a deterministic 64-bit identifier from the SiTime OUI,
+ * the chip ID, and the I2C address. This provides a stable clock_id
+ * across reboots.
+ *
+ * Return: 64-bit clock identifier
+ */
+static u64 sit9531x_derive_clock_id(struct sit9531x_dev *sitdev)
+{
+ u64 clkid;
+
+ clkid = SIT9531X_OUI << 24;
+ clkid |= (u64)sitdev->info->id << 8;
+ clkid |= (u64)sitdev->client->addr;
+
+ return clkid;
+}
+
+/*
+ * Board-config overrides for fixed efuse/blob routing the chip registers do
+ * not describe unambiguously. Absent properties leave pll_fvco[] zeroed
+ * (derive from DIVN) and out_pll_map_valid false (use the OUT_MAP registers).
+ */
+static void sit9531x_parse_board_config(struct sit9531x_dev *sitdev)
+{
+ u32 map[SIT9531X_MAX_OUTPUTS];
+ int n, i;
+
+ device_property_read_u64_array(sitdev->dev, "sitime,pll-fvco",
+ sitdev->pll_fvco, SIT9531X_NUM_PLLS);
+
+ if (!device_property_present(sitdev->dev, "sitime,output-pll-map"))
+ return;
+
+ /*
+ * Any 1..MAX_OUTPUTS length is accepted so the 8-output SiT95317 need
+ * not pad to 12; variant detection has not run yet and entries past
+ * the detected num_outputs are never indexed. Trailing entries of a
+ * short map must read as unmapped rather than 0 (== PLLA), which
+ * would mark unrouted outputs active in sit9531x_out_state_fetch().
+ */
+ memset(sitdev->out_pll_map, SIT9531X_OUT_PLL_UNMAPPED,
+ sizeof(sitdev->out_pll_map));
+
+ n = device_property_count_u32(sitdev->dev, "sitime,output-pll-map");
+ if (n <= 0 || n > SIT9531X_MAX_OUTPUTS ||
+ device_property_read_u32_array(sitdev->dev, "sitime,output-pll-map",
+ map, n))
+ return;
+
+ for (i = 0; i < n; i++)
+ sitdev->out_pll_map[i] = map[i];
+ sitdev->out_pll_map_valid = true;
+}
+
+int sit9531x_dev_probe(struct sit9531x_dev *sitdev)
+{
+ struct clk *xtal_clk;
+ u8 variant_id;
+ int rc;
+
+ /*
+ * Fvco = Fref * (DIVN + frac/2^32) with Fref = xtal_freq << doubler,
+ * so every freq_set and phase_adjust path divides by a rate derived
+ * from the XO feeding XIN/XO_CLK.
+ */
+ xtal_clk = devm_clk_get_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)
+ return dev_err_probe(sitdev->dev, -EINVAL,
+ "xtal clock has no rate\n");
+
+ /*
+ * Held deasserted, never pulsed: the chip configuration comes from
+ * efuse or an NVM blob applied before probe, and a reset would
+ * discard it. Must precede the first I2C access, as a board that
+ * powers up asserted keeps the chip unreachable until released.
+ */
+ 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 */
+
+ sit9531x_parse_board_config(sitdev);
+
+ 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);
+
+ sitdev->clock_id = sit9531x_derive_clock_id(sitdev);
+ sitdev->intsync_src = -1;
+
+ rc = devm_mutex_init(sitdev->dev, &sitdev->multiop_lock);
+ if (rc)
+ return dev_err_probe(sitdev->dev, rc,
+ "Failed to initialize mutex\n");
+
+ /*
+ * Before the IRQ: the handler reaches sitdev->kworker through
+ * kthread_mod_delayed_work(), so the worker has to exist before an
+ * INTRB assertion can land.
+ */
+ rc = sit9531x_devm_dpll_init(sitdev);
+ if (rc)
+ return rc;
+
+ /* Absent "interrupts" leaves client->irq 0 and the poll in charge. */
+ sitdev->irq = sitdev->client ? sitdev->client->irq : 0;
+ if (sitdev->irq > 0) {
+ rc = devm_request_threaded_irq(sitdev->dev, sitdev->irq,
+ NULL, sit9531x_irq_thread_fn,
+ IRQF_ONESHOT,
+ dev_name(sitdev->dev), sitdev);
+ if (rc)
+ return dev_err_probe(sitdev->dev, rc,
+ "Failed to request IRQ %d\n",
+ sitdev->irq);
+ }
+
+ return 0;
+}
+
+static int sit9531x_i2c_probe(struct i2c_client *client)
+{
+ struct sit9531x_dev *sitdev;
+ struct regmap *regmap;
+
+ 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;
+ 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@sitime.com>");
+MODULE_AUTHOR("Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>");
+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 0000000000000..05d4ee0c712ed
--- /dev/null
+++ b/drivers/dpll/sit9531x/core.h
@@ -0,0 +1,372 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SiTime SiT9531x DPLL core driver
+ *
+ * Copyright (C) 2026 SiTime Corp.
+ * Author: Ali Rouhi <arouhi@sitime.com>
+ * Author: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>
+ *
+ * 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/kthread.h>
+#include <linux/list.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_NUM_INPUT_PAIRS (SIT9531X_MAX_INPUTS / 2)
+#define SIT9531X_MAX_OUTPUTS 12
+/* out_pll_map[] entry meaning "this output is not routed to any PLL" */
+#define SIT9531X_OUT_PLL_UNMAPPED 0xFF
+/*
+ * INTSYNC (the inter-PLL sync net) is modelled as two pins. The
+ * destination PLL that locks to INTSYNC sees an input pin
+ * (SIT9531X_INTSYNC_PIN_ID, in the input id namespace after the physical
+ * inputs and the xtal); the source PLL that drives INTSYNC sees an output
+ * pin (SIT9531X_INTSYNC_OUT_PIN_ID, appended after the physical outputs).
+ */
+#define SIT9531X_INTSYNC_PIN_ID (SIT9531X_MAX_INPUTS + 1)
+#define SIT9531X_INTSYNC_OUT_PIN_ID SIT9531X_MAX_OUTPUTS
+#define SIT9531X_NUM_PINS (SIT9531X_MAX_INPUTS + 2 + SIT9531X_MAX_OUTPUTS + 1)
+#define SIT9531X_STATUS_POLL_MS 500
+
+/* selected_ref value when the active source is not a registered input */
+#define SIT9531X_REF_INVALID 0xFF
+
+/* SiTime IEEE OUI for EUI-64 generation */
+#define SIT9531X_OUI 0x0090C2FFFEULL
+
+struct sit9531x_dpll;
+
+/*
+ * struct sit9531x_chip_info - chip variant identification
+ * @id: variant ID byte read from register
+ * @num_inputs: number of input clock pins
+ * @num_outputs: number of output clock pins
+ * @name: human-readable variant name
+ * @clkout_map: per-output slot mapping (output index -> physical slot)
+ */
+struct sit9531x_chip_info {
+ u8 id;
+ u8 num_inputs;
+ u8 num_outputs;
+ const char *name;
+ const u8 *clkout_map;
+};
+
+/*
+ * enum sit9531x_signal_mode - input signal electrical mode
+ * @SIT9531X_MODE_SE: single-ended
+ * @SIT9531X_MODE_DE: differential
+ */
+enum sit9531x_signal_mode {
+ SIT9531X_MODE_SE = 0,
+ SIT9531X_MODE_DE,
+};
+
+/*
+ * struct sit9531x_ref - input reference state
+ * @freq: configured frequency in Hz
+ * @enabled: reference is enabled for monitoring
+ * @los: loss-of-signal detected
+ * @oof: out-of-frequency detected
+ * @pll_mask: bitmask of PLLs this input feeds (bit 0 = PLLA)
+ * @label: board label from DT or default
+ * @sig_mode: signal mode of the pair this lane belongs to
+ * (detected from CLKINx_INPUT_MODE at probe)
+ */
+struct sit9531x_ref {
+ u32 freq;
+ bool enabled;
+ bool los;
+ bool oof;
+ u8 pll_mask;
+ const char *label;
+ enum sit9531x_signal_mode sig_mode;
+};
+
+/*
+ * struct sit9531x_out - output state
+ * @enabled: output is driving, i.e. not forced into Hi-Z
+ * @routed: output is mapped to @pll_idx by the initial
+ * configuration; an unrouted output has no DPLL pin
+ * @pll_idx: PLL driving this output (0-3)
+ * @label: board label from DT or default
+ */
+struct sit9531x_out {
+ u32 freq;
+ bool enabled;
+ bool routed;
+ u8 pll_idx;
+ const char *label;
+};
+
+/*
+ * struct sit9531x_chan - per-PLL channel state
+ * @active: PLL has reached its active state; a PLL the loaded
+ * configuration leaves unused never does, and its
+ * loss-of-lock bit stays clear because nothing drives it
+ * @locked: PLL is locked (raw status register bit)
+ * @mode: 0 = sync (outer loop enabled), 1 = free-run
+ * @selected_ref: logical input index of the currently selected
+ * reference (the INTSYNC net maps to
+ * SIT9531X_INTSYNC_PIN_ID), or SIT9531X_REF_INVALID
+ * when the hardware source encoding is reserved
+ * @inner_lol: PLL inner loop loss-of-lock detected
+ * @ho_freeze: holdover freeze active
+ */
+struct sit9531x_chan {
+ bool active;
+ bool locked;
+ u8 mode;
+ u8 selected_ref;
+ bool inner_lol;
+ bool ho_freeze;
+};
+
+/*
+ * struct sit9531x_dev - SiT9531x device instance
+ * @info: detected chip variant info
+ * @multiop_lock: mutex for multi-register atomic operations
+ * @ref: array of input reference states
+ * @out: array of output states
+ * @chan: array of per-PLL channel states
+ * @xtal_freq: crystal oscillator frequency in Hz
+ * @kworker: kthread worker for periodic polling
+ * @work: delayed work for periodic state checks
+ * @clock_id: IEEE 1588 EUI-64 clock identifier
+ * @reset_gpio: optional reset line (DT "reset-gpios"), NULL if absent
+ * @irq: optional INTRB IRQ number (from DT "interrupts" via the
+ * I2C client), 0 if no IRQ is wired
+ * @pll_fvco: optional per-PLL VCO in Hz from DT
+ * "sitime,pll-fvco"; 0 means derive from DIVN
+ * @out_pll_map: optional per-output source PLL (0-3, 0xff =
+ * unmapped) from DT "sitime,output-pll-map"
+ * @out_pll_map_valid: true when out_pll_map[] was populated from DT;
+ * false means use the chip's OUT_MAP registers
+ * @intsync_src: PLL index currently sourcing inter-PLL
+ * synchronization (INTSYNC), or -1 when disabled
+ */
+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;
+
+ /* Hardware state */
+ struct sit9531x_ref ref[SIT9531X_MAX_INPUTS + 1]; /* +1 for xtal */
+ struct sit9531x_out out[SIT9531X_MAX_OUTPUTS];
+ struct sit9531x_chan chan[SIT9531X_NUM_PLLS];
+ u32 xtal_freq;
+
+ /* DPLL channels */
+ struct list_head dplls;
+
+ /* Monitor */
+ struct kthread_worker *kworker;
+ struct kthread_delayed_work work;
+
+ /* Device identity */
+ u64 clock_id;
+
+ /* Optional DT-described GPIO / IRQ lines */
+ struct gpio_desc *reset_gpio;
+ int irq;
+
+ /* Optional DT board-config overrides */
+ u64 pll_fvco[SIT9531X_NUM_PLLS];
+ u8 out_pll_map[SIT9531X_MAX_OUTPUTS];
+ bool out_pll_map_valid;
+
+ /* Inter-PLL synchronization state */
+ s8 intsync_src;
+
+};
+
+extern const struct regmap_config sit9531x_regmap_config;
+
+/* ---- Core lifecycle ---- */
+int sit9531x_dev_probe(struct sit9531x_dev *sitdev);
+int sit9531x_dev_start(struct sit9531x_dev *sitdev);
+void sit9531x_dev_stop(struct sit9531x_dev *sitdev);
+
+/* ---- Register access ---- */
+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);
+
+/* ---- Input enable/disable ---- */
+int sit9531x_input_disable(struct sit9531x_dev *sitdev, u8 index);
+int sit9531x_input_enable(struct sit9531x_dev *sitdev, u8 index);
+
+/* ---- Input priority ---- */
+int sit9531x_input_prio_set(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx, u8 prio);
+int sit9531x_input_prio_get(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx, u8 *prio);
+int sit9531x_input_prio_remove(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx);
+int sit9531x_input_prio_add(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx);
+
+/* ---- Output enable/disable (Hi-Z control) ---- */
+int sit9531x_output_disable(struct sit9531x_dev *sitdev, u8 index);
+int sit9531x_output_enable(struct sit9531x_dev *sitdev, u8 index);
+
+/* ---- Output frequency ---- */
+int sit9531x_output_freq_set(struct sit9531x_dev *sitdev, u8 out_idx,
+ u8 pll_idx, u64 frequency);
+int sit9531x_output_freq_get(struct sit9531x_dev *sitdev, u8 out_idx,
+ u64 *frequency);
+
+/* ---- Output phase adjust (PRG_RST_DELAY register-based) ---- */
+int sit9531x_output_phase_adjust_set(struct sit9531x_dev *sitdev,
+ u8 out_idx, s32 phase_ps);
+
+/* ---- Notification clear ---- */
+int sit9531x_clear_notifications(struct sit9531x_dev *sitdev);
+
+/* ---- INTSYNC (inter-PLL synchronization) ---- */
+int sit9531x_intsync_enable(struct sit9531x_dev *sitdev, u8 src_pll_idx);
+int sit9531x_intsync_disable(struct sit9531x_dev *sitdev, u8 src_pll_idx);
+
+/* ---- Output pulse control ---- */
+int sit9531x_output_pulse_ctrl_set(struct sit9531x_dev *sitdev,
+ u8 out_idx, u8 pulse_ctrl);
+
+/* ---- Phase offset (TDC readback) ---- */
+int sit9531x_pll_ffo_ppt(struct sit9531x_dev *sitdev, u8 pll_idx, s64 *ffo);
+int sit9531x_phase_offset_read(struct sit9531x_dev *sitdev, u8 pll_idx,
+ s64 *phase_ps);
+
+/* ---- State helpers ---- */
+
+/*
+ * sit9531x_pll_page - get register page for PLL index
+ * @pll_idx: PLL index (0 = PLLA, 3 = PLLD)
+ */
+static inline u8 sit9531x_pll_page(u8 pll_idx)
+{
+ return SIT9531X_PAGE_PLLA + pll_idx;
+}
+
+/*
+ * Logical input pins are interleaved: even index = P lane, odd
+ * index = N lane of pair index/2 (IN0P, IN0N, IN1P, IN1N, ...).
+ * Index SIT9531X_MAX_INPUTS is the XO input.
+ */
+
+/*
+ * sit9531x_input_pair - get input pair number for a logical input index
+ * @index: logical input pin index
+ */
+static inline u8 sit9531x_input_pair(u8 index)
+{
+ return index >> 1;
+}
+
+/*
+ * sit9531x_input_is_n - check if a logical input index is an N lane
+ * @index: logical input pin index
+ */
+static inline bool sit9531x_input_is_n(u8 index)
+{
+ return index & 1;
+}
+
+/*
+ * sit9531x_input_hw_src - translate logical input index to source encoding
+ * @index: logical input pin index
+ *
+ * The priority table and CLK_ACTIVESEL registers use a non-contiguous
+ * source encoding: 0-3 = CLK0P..CLK3P, 5 = OCXO, 6 = INTSYNC,
+ * 7-10 = CLK0N..CLK3N.
+ */
+static inline u8 sit9531x_input_hw_src(u8 index)
+{
+ if (index == SIT9531X_MAX_INPUTS)
+ return SIT9531X_PRIO_SRC_OCXO;
+ if (index == SIT9531X_INTSYNC_PIN_ID)
+ return SIT9531X_PRIO_SRC_INTSYNC;
+ if (sit9531x_input_is_n(index))
+ return SIT9531X_PRIO_SRC_N_BASE + sit9531x_input_pair(index);
+ return sit9531x_input_pair(index);
+}
+
+/*
+ * sit9531x_hw_src_input - translate source encoding to logical input index
+ * @src: 4-bit hardware source encoding
+ *
+ * Return: logical input index (INTSYNC maps to SIT9531X_INTSYNC_PIN_ID),
+ * or SIT9531X_REF_INVALID if @src is a reserved value
+ */
+static inline u8 sit9531x_hw_src_input(u8 src)
+{
+ if (src < SIT9531X_NUM_INPUT_PAIRS)
+ return src * 2;
+ if (src == SIT9531X_PRIO_SRC_OCXO)
+ return SIT9531X_MAX_INPUTS;
+ if (src == SIT9531X_PRIO_SRC_INTSYNC)
+ return SIT9531X_INTSYNC_PIN_ID;
+ if (src >= SIT9531X_PRIO_SRC_N_BASE &&
+ src < SIT9531X_PRIO_SRC_N_BASE + SIT9531X_NUM_INPUT_PAIRS)
+ return (src - SIT9531X_PRIO_SRC_N_BASE) * 2 + 1;
+ return SIT9531X_REF_INVALID;
+}
+
+/*
+ * sit9531x_ref_state_get - get reference state by index
+ * @index: logical input index
+ *
+ * Return: pointer to the cached input reference state
+ */
+static inline const struct sit9531x_ref *
+sit9531x_ref_state_get(const struct sit9531x_dev *sitdev, u8 index)
+{
+ return &sitdev->ref[index];
+}
+
+/*
+ * sit9531x_out_state_get - get output state by index
+ * @index: logical output index
+ *
+ * Return: pointer to the cached output state
+ */
+static inline const struct sit9531x_out *
+sit9531x_out_state_get(const struct sit9531x_dev *sitdev, u8 index)
+{
+ return &sitdev->out[index];
+}
+
+/*
+ * sit9531x_chan_state_get - get channel state by PLL index
+ *
+ * Return: pointer to the cached per-PLL channel state
+ */
+static inline const struct sit9531x_chan *
+sit9531x_chan_state_get(const struct sit9531x_dev *sitdev, u8 pll_idx)
+{
+ return &sitdev->chan[pll_idx];
+}
+
+#endif /* _SIT9531X_CORE_H */
diff --git a/drivers/dpll/sit9531x/dpll.c b/drivers/dpll/sit9531x/dpll.c
new file mode 100644
index 0000000000000..364f870ac3041
--- /dev/null
+++ b/drivers/dpll/sit9531x/dpll.c
@@ -0,0 +1,1202 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SiTime SiT9531x DPLL subsystem callbacks and registration
+ *
+ * Copyright (C) 2026 SiTime Corp.
+ * Author: Ali Rouhi <arouhi@sitime.com>
+ * Author: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>
+ *
+ * DPLL device ops, pin ops (separate input/output), pin registration,
+ * and periodic change detection.
+ */
+
+#include <linux/dpll.h>
+#include <linux/err.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/netlink.h>
+#include <linux/slab.h>
+
+#include "core.h"
+#include "dpll.h"
+#include "prop.h"
+#include "regs.h"
+
+#define SIT9531X_ESYNC_FREQ_10MHZ 10000000ULL
+#define SIT9531X_ESYNC_PULSE_DEFAULT 50
+
+static const struct dpll_pin_frequency sit9531x_esync_ranges[] = {
+ DPLL_PIN_FREQUENCY(0),
+ DPLL_PIN_FREQUENCY(SIT9531X_ESYNC_FREQ_10MHZ),
+};
+
+static inline bool
+sit9531x_dpll_esync_pin_supported(const struct sit9531x_dpll_pin *dpin)
+{
+ return dpin->esync_control;
+}
+
+static inline bool sit9531x_dpll_is_input_pin(const struct sit9531x_dpll_pin *pin)
+{
+ return pin->dir == DPLL_PIN_DIRECTION_INPUT;
+}
+
+static inline bool
+sit9531x_dpll_is_intsync_pin(const struct sit9531x_dpll_pin *pin)
+{
+ return sit9531x_dpll_is_input_pin(pin) &&
+ pin->id == SIT9531X_INTSYNC_PIN_ID;
+}
+
+static inline bool
+sit9531x_dpll_is_intsync_src_pin(const struct sit9531x_dpll_pin *pin)
+{
+ return !sit9531x_dpll_is_input_pin(pin) &&
+ pin->id == SIT9531X_INTSYNC_OUT_PIN_ID;
+}
+
+static inline bool
+sit9531x_dpll_is_xo_pin(const struct sit9531x_dpll_pin *pin)
+{
+ return sit9531x_dpll_is_input_pin(pin) &&
+ pin->id == SIT9531X_MAX_INPUTS;
+}
+
+/*
+ * The cached state this reports comes from the outer loss-of-lock byte
+ * (page 0, reg 0x06), the PLL mode bit (PLL page, reg 0x31), inner LOL
+ * (reg 0x92) and the holdover freeze byte (reg 0x0A).
+ */
+static int
+sit9531x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
+ enum dpll_lock_status *status,
+ enum dpll_lock_status_error *status_error,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ const struct sit9531x_chan *chan;
+
+ if (status_error)
+ *status_error = DPLL_LOCK_STATUS_ERROR_NONE;
+
+ chan = sit9531x_chan_state_get(sitdev, sitdpll->id);
+
+ mutex_lock(&sitdev->multiop_lock);
+
+ if (!chan->active) {
+ /*
+ * A PLL the loaded configuration leaves unused never reaches
+ * its active state. Nothing drives its loss-of-lock bit, so
+ * without this it would report a lock it does not have.
+ */
+ *status = DPLL_LOCK_STATUS_UNLOCKED;
+ } else if (chan->locked) {
+ if (chan->mode)
+ *status = DPLL_LOCK_STATUS_LOCKED; /* free-run */
+ else
+ *status = DPLL_LOCK_STATUS_LOCKED_HO_ACQ; /* sync */
+ } else if (chan->ho_freeze) {
+ *status = DPLL_LOCK_STATUS_HOLDOVER;
+ } else {
+ *status = DPLL_LOCK_STATUS_UNLOCKED;
+ }
+
+ /* Report inner LOL as an error condition */
+ if (status_error && chan->inner_lol)
+ *status_error = DPLL_LOCK_STATUS_ERROR_UNDEFINED;
+
+ mutex_unlock(&sitdev->multiop_lock);
+
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_mode_get - report current PLL operating mode
+ *
+ * reads outer loop disable bit (PLL page reg 0x31[5]).
+ * Free-run -> MANUAL, sync -> AUTOMATIC.
+ */
+static int
+sit9531x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
+ enum dpll_mode *mode, struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ const struct sit9531x_chan *chan;
+
+ chan = sit9531x_chan_state_get(sitdev, sitdpll->id);
+
+ mutex_lock(&sitdev->multiop_lock);
+ *mode = chan->mode ? DPLL_MODE_MANUAL : DPLL_MODE_AUTOMATIC;
+ mutex_unlock(&sitdev->multiop_lock);
+
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_mode_set - switch PLL between free-run and sync mode
+ *
+ * writes PLL page reg 0x31[5] to enable/disable the
+ * outer loop, then triggers a small update via reg 0x0F.
+ *
+ * The driver maps AUTOMATIC to sync mode (outer loop enabled; the PLL
+ * selects its reference automatically from the priority table) and
+ * MANUAL to free-run (outer loop disabled via bit 0x31[5]). The chip
+ * also has a separate manual-active-select path (MANUAL_ACTIVE_SEL_PLL
+ * plus the per-PLL man_in_sel registers) that can pin one reference
+ * while still syncing; that path is not wired to a DPLL callback here.
+ */
+static int
+sit9531x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
+ enum dpll_mode mode, struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ u8 val;
+ int rc;
+
+ /*
+ * Outer loop disable bit:
+ * 0 = sync mode (outer loop enabled) -> AUTOMATIC
+ * 1 = free-run (outer loop disabled) -> MANUAL
+ */
+ val = (mode == DPLL_MODE_MANUAL) ? SIT9531X_PLL_STATUS_OUTER_DIS : 0;
+
+ mutex_lock(&sitdev->multiop_lock);
+
+ rc = sit9531x_update_pll_u8(sitdev, sitdpll->id,
+ SIT9531X_PLL_REG_STATUS,
+ SIT9531X_PLL_STATUS_OUTER_DIS, val);
+ if (rc) {
+ NL_SET_ERR_MSG(extack, "Failed to write PLL mode register");
+ goto unlock;
+ }
+
+ /* Trigger small update to apply without full NVM cycle */
+ rc = sit9531x_write_pll_u8(sitdev, sitdpll->id,
+ SIT9531X_PLL_REG_SMALL_UPDATE,
+ SIT9531X_SMALL_UPDATE_CMD);
+ if (rc) {
+ NL_SET_ERR_MSG(extack, "Failed to trigger small update");
+ goto unlock;
+ }
+
+ /*
+ * Keep the cached mode in step with the register. The periodic
+ * monitor refreshes it too, but mode_get and the reference-selection
+ * path read this cache and would otherwise report or act on the
+ * pre-switch mode until the next poll.
+ */
+ sitdev->chan[sitdpll->id].mode = !!val;
+
+unlock:
+ mutex_unlock(&sitdev->multiop_lock);
+
+ return rc;
+}
+
+static int
+sit9531x_dpll_supported_modes_get(const struct dpll_device *dpll,
+ void *dpll_priv, unsigned long *modes,
+ struct netlink_ext_ack *extack)
+{
+ __set_bit(DPLL_MODE_AUTOMATIC, modes);
+ __set_bit(DPLL_MODE_MANUAL, modes);
+
+ return 0;
+}
+
+const struct dpll_device_ops sit9531x_dpll_device_ops = {
+ .lock_status_get = sit9531x_dpll_lock_status_get,
+ .mode_get = sit9531x_dpll_mode_get,
+ .mode_set = sit9531x_dpll_mode_set,
+ .supported_modes_get = sit9531x_dpll_supported_modes_get,
+ /* temp_get not available -- SiT9531x has no on-die temp sensor */
+};
+
+static int
+sit9531x_dpll_input_pin_direction_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_direction *direction,
+ struct netlink_ext_ack *extack)
+{
+ *direction = DPLL_PIN_DIRECTION_INPUT;
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_input_pin_frequency_get - read input pin frequency
+ *
+ * returns cached frequency from DT or last set.
+ */
+static int
+sit9531x_dpll_input_pin_frequency_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, u64 *frequency,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ const struct sit9531x_ref *ref;
+
+ ref = sit9531x_ref_state_get(sitdpll->dev, dpin->id);
+ *frequency = ref->freq;
+
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_input_pin_state_on_dpll_get - get input pin DPLL state
+ *
+ * determines pin state from channel state: connected
+ * if this input is the selected reference on a locked PLL, selectable
+ * if enabled in automatic mode, disconnected otherwise.
+ */
+static int
+sit9531x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state *state,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ const struct sit9531x_chan *chan;
+ const struct sit9531x_ref *ref;
+
+ chan = sit9531x_chan_state_get(sitdev, sitdpll->id);
+ ref = sit9531x_ref_state_get(sitdev, dpin->id);
+
+ mutex_lock(&sitdev->multiop_lock);
+
+ /*
+ * Report CONNECTED only when the PLL is genuinely locked to this
+<<<<<<< HEAD
+ * input: outer loop locked AND inner loop locked. Gating on the
+ * outer bit alone let an idle PLL whose outer loop happens to read
+ * locked (e.g. an unused PLLD) falsely claim its default source
+ * (IN0P) as connected right after probe.
+=======
+ * input. chan->locked already excludes a PLL the configuration
+ * leaves unused, which would otherwise claim its default source as
+ * connected right after probe; the inner loop has to be locked as
+ * well before the reference can be called connected.
+>>>>>>> 7117164f1c60 (dpll: sit9531x: do not report an unused PLL as locked)
+ */
+ if (chan->locked && !chan->inner_lol &&
+ chan->selected_ref == dpin->id)
+ *state = DPLL_PIN_STATE_CONNECTED;
+ /* In auto mode, enabled inputs with good signal are selectable */
+ else if (!chan->mode && ref->enabled && !ref->los && !ref->oof)
+ *state = DPLL_PIN_STATE_SELECTABLE;
+ else
+ *state = DPLL_PIN_STATE_DISCONNECTED;
+
+ mutex_unlock(&sitdev->multiop_lock);
+
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_input_pin_state_on_dpll_set - set input pin DPLL state
+ *
+ * Enables or disables the physical input receiver via Page 0x02
+ * force/state registers (sit9531x_input_disable/enable()) and updates
+ * this DPLL's Page 1 priority table so the state is honoured by the
+ * PLL's automatic reference selection, not just at the input buffer.
+ * DISCONNECTED -> drop the input from this DPLL's priority table and
+ * release this DPLL's claim on the input
+ * SELECTABLE -> claim the input for this DPLL and make it eligible
+ * again in the priority table
+ * CONNECTED -> same as SELECTABLE (the PLL auto-switch logic does
+ * the actual selection)
+ *
+ * The priority table is per PLL, so it is always updated for this DPLL.
+ * A single physical input feeds every DPLL, so the hardware receiver is
+ * only cut off once the last DPLL has released it: ref->pll_mask tracks
+ * which DPLLs currently claim the input, and the physical disable
+ * happens on the transition to an empty mask.
+ */
+static int
+sit9531x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state state,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ struct sit9531x_ref *ref = &sitdev->ref[dpin->id];
+ u8 hw_src = sit9531x_input_hw_src(dpin->id);
+ u8 pll_bit = BIT(sitdpll->id);
+ int rc;
+
+ mutex_lock(&sitdev->multiop_lock);
+
+ switch (state) {
+ case DPLL_PIN_STATE_DISCONNECTED:
+ rc = sit9531x_input_prio_remove(sitdev, sitdpll->id, hw_src);
+ if (rc)
+ break;
+ ref->pll_mask &= ~pll_bit;
+ if (ref->pll_mask)
+ rc = 0; /* another DPLL still uses this input */
+ else
+ rc = sit9531x_input_disable(sitdev, dpin->id);
+ break;
+ case DPLL_PIN_STATE_SELECTABLE:
+ case DPLL_PIN_STATE_CONNECTED:
+ rc = sit9531x_input_enable(sitdev, dpin->id);
+ if (rc)
+ break;
+ rc = sit9531x_input_prio_add(sitdev, sitdpll->id, hw_src);
+ if (rc)
+ break;
+ /*
+ * Claim the input for this DPLL only once it is both enabled
+ * and present in the priority table. Setting the mask before
+ * prio_add would leak the claim if prio_add failed, keeping the
+ * shared input receiver powered even after every DPLL released
+ * it.
+ */
+ ref->pll_mask |= pll_bit;
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&sitdev->multiop_lock);
+
+ if (rc)
+ NL_SET_ERR_MSG(extack, "Failed to set input pin state");
+
+ return rc;
+}
+
+/*
+ * sit9531x_dpll_input_pin_prio_get - read input pin priority
+ *
+ * reads the PLL's priority table on Page 1 (via
+ * sit9531x_input_prio_get()) and returns the slot the input
+ * occupies, so pin-get reports the real hardware priority rather
+ * than a software default.
+ */
+static int
+sit9531x_dpll_input_pin_prio_get(const struct dpll_pin *pin, void *pin_priv,
+ const struct dpll_device *dpll, void *dpll_priv,
+ u32 *prio, struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ u8 slot;
+ int rc;
+
+ mutex_lock(&sitdev->multiop_lock);
+ rc = sit9531x_input_prio_get(sitdev, sitdpll->id,
+ sit9531x_input_hw_src(dpin->id), &slot);
+ mutex_unlock(&sitdev->multiop_lock);
+ if (rc)
+ return rc;
+
+ dpin->prio = slot;
+ *prio = slot;
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_input_pin_prio_set - set input pin priority
+ *
+ * writes input priority table on Page 1 via
+ * core.c sit9531x_input_prio_set(). Forces holdover during update.
+ */
+static int
+sit9531x_dpll_input_pin_prio_set(const struct dpll_pin *pin, void *pin_priv,
+ const struct dpll_device *dpll, void *dpll_priv,
+ u32 prio, struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ int rc;
+
+ if (dpin->dir != DPLL_PIN_DIRECTION_INPUT) {
+ NL_SET_ERR_MSG(extack, "Priority applies only to input pins");
+ return -EINVAL;
+ }
+
+ if (prio >= SIT9531X_PRIO_MAX_SLOTS) {
+ NL_SET_ERR_MSG(extack, "Priority out of range (0-10)");
+ return -EINVAL;
+ }
+
+ mutex_lock(&sitdev->multiop_lock);
+ rc = sit9531x_input_prio_set(sitdev, sitdpll->id,
+ sit9531x_input_hw_src(dpin->id),
+ (u8)prio);
+ mutex_unlock(&sitdev->multiop_lock);
+
+ if (rc) {
+ NL_SET_ERR_MSG(extack, "Failed to set input priority");
+ return rc;
+ }
+
+ dpin->prio = (u8)prio;
+
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_input_pin_phase_adjust_get - read phase adjustment
+ *
+ * returns cached phase adjustment value (in ps).
+ */
+static int
+sit9531x_dpll_input_pin_phase_adjust_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, s32 *phase_adjust,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+
+ *phase_adjust = dpin->phase_adjust;
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_input_pin_phase_offset_get - read phase offset
+ *
+ * reads the TDC (Time-to-Digital Converter) hardware
+ * to measure the phase difference in picoseconds via
+ * sit9531x_phase_offset_read().
+ */
+/*
+ * sit9531x_dpll_input_pin_ffo_get - read the input's frequency offset
+ *
+ * The offset is derived from how far the PLL's running DIVN sits from
+ * its configured one, which only says something about the reference the
+ * PLL is actually tracking. For every other input there is no
+ * measurement, and -ENODATA leaves the attribute out rather than
+ * reporting the active reference's figure against the wrong pin.
+ */
+static int
+sit9531x_dpll_input_pin_ffo_get(const struct dpll_pin *pin, void *pin_priv,
+ const struct dpll_device *dpll, void *dpll_priv,
+ struct dpll_ffo_param *ffo,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ int rc;
+
+ if (sitdev->chan[sitdpll->id].selected_ref != dpin->id)
+ return -ENODATA;
+
+ mutex_lock(&sitdev->multiop_lock);
+ rc = sit9531x_pll_ffo_ppt(sitdev, sitdpll->id, &ffo->ffo);
+ mutex_unlock(&sitdev->multiop_lock);
+
+ return rc;
+}
+
+static int
+sit9531x_dpll_input_pin_phase_offset_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, s64 *phase_offset,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ s64 offset;
+ int rc;
+
+ mutex_lock(&sitdev->multiop_lock);
+
+ /*
+ * The on-chip TDC is a per-PLL resource that always measures the
+ * phase difference between the VCO and the PLL's currently
+ * selected reference; it cannot be pointed at an arbitrary input.
+ * For any input that is not the active reference there is no
+ * meaningful per-pin phase offset, so report 0 instead of the
+ * active reference's value.
+ */
+ if (sitdev->chan[sitdpll->id].selected_ref != dpin->id) {
+ mutex_unlock(&sitdev->multiop_lock);
+ dpin->phase_offset = 0;
+ *phase_offset = 0;
+ return 0;
+ }
+
+ rc = sit9531x_phase_offset_read(sitdev, sitdpll->id, &offset);
+ mutex_unlock(&sitdev->multiop_lock);
+
+ /*
+ * -ENODEV means the PLL has no programmed DIVN (unused on this
+ * board); report phase_offset = 0 so a full pin-get dump does not
+ * fail just because one DPLL is dormant.
+ */
+ if (rc == -ENODEV) {
+ dpin->phase_offset = 0;
+ *phase_offset = 0;
+ return 0;
+ }
+ if (rc) {
+ NL_SET_ERR_MSG(extack, "TDC phase readback failed");
+ return rc;
+ }
+
+ /*
+ * The ABI reports phase offset in units of 1/DPLL_PHASE_OFFSET_DIVIDER
+ * picoseconds: the integer part of the attribute is the value divided
+ * by the divider, the remainder is the fraction. The TDC resolves one
+ * VCO period (hundreds of picoseconds), so the fractional digits are
+ * always zero here, but the magnitude still has to be scaled or every
+ * reading would be reported a thousand times too small.
+ */
+ offset *= DPLL_PHASE_OFFSET_DIVIDER;
+
+ dpin->phase_offset = offset;
+ *phase_offset = offset;
+ return 0;
+}
+
+static const struct dpll_pin_ops sit9531x_dpll_input_pin_ops = {
+ .direction_get = sit9531x_dpll_input_pin_direction_get,
+ .frequency_get = sit9531x_dpll_input_pin_frequency_get,
+ .state_on_dpll_get = sit9531x_dpll_input_pin_state_on_dpll_get,
+ .state_on_dpll_set = sit9531x_dpll_input_pin_state_on_dpll_set,
+ .prio_get = sit9531x_dpll_input_pin_prio_get,
+ .prio_set = sit9531x_dpll_input_pin_prio_set,
+ .phase_adjust_get = sit9531x_dpll_input_pin_phase_adjust_get,
+ .phase_offset_get = sit9531x_dpll_input_pin_phase_offset_get,
+ /*
+ * The measurement compares the PLL's running feedback divider with
+ * its configured one, so it describes the device's own reference
+ * rather than a port rate.
+ */
+ .supported_ffo = BIT(DPLL_FFO_PIN_DEVICE),
+ .ffo_get = sit9531x_dpll_input_pin_ffo_get,
+};
+
+/*
+ * INTSYNC pin ops
+ *
+ * INTSYNC is the chip's inter-PLL sync net: one PLL drives it and other
+ * PLLs may lock to it instead of to an external reference. The two
+ * roles are exposed as two separate pins so neither overloads the other:
+ *
+ * - a source (output) pin registered on every DPLL. Connecting it on a
+ * DPLL makes that DPLL drive INTSYNC; only one DPLL may drive it at a
+ * time. It has no priority ops -- driving the net is not a reference
+ * selection.
+ * - a destination (input) pin registered on every DPLL. Connecting it
+ * on a DPLL makes that DPLL eligible to lock to INTSYNC as a
+ * reference, so it carries the priority ops.
+ */
+
+/* ---- INTSYNC source (output) pin ---- */
+
+/* The INTSYNC source pin is an output; its direction_get is defined below. */
+static int
+sit9531x_dpll_output_pin_direction_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_direction *direction,
+ struct netlink_ext_ack *extack);
+
+static int
+sit9531x_dpll_intsync_src_state_on_dpll_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state *state,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+
+ mutex_lock(&sitdev->multiop_lock);
+ if (sitdev->intsync_src == sitdpll->id)
+ *state = DPLL_PIN_STATE_CONNECTED;
+ else
+ *state = DPLL_PIN_STATE_DISCONNECTED;
+ mutex_unlock(&sitdev->multiop_lock);
+
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_intsync_src_state_on_dpll_set - drive INTSYNC from a PLL
+ *
+ * CONNECTED -> this PLL drives the INTSYNC net
+ * DISCONNECTED -> stop driving INTSYNC if this PLL drives it
+ *
+ * SELECTABLE is rejected: driving the net is an explicit output routing,
+ * not an automatic-selection candidate, matching the regular output pin.
+ */
+static int
+sit9531x_dpll_intsync_src_state_on_dpll_set(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state state,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ int rc = 0;
+
+ mutex_lock(&sitdev->multiop_lock);
+
+ switch (state) {
+ case DPLL_PIN_STATE_CONNECTED:
+ if (sitdev->intsync_src == sitdpll->id)
+ break;
+ if (sitdev->intsync_src >= 0) {
+ NL_SET_ERR_MSG(extack,
+ "INTSYNC is already sourced by another PLL");
+ rc = -EBUSY;
+ break;
+ }
+ rc = sit9531x_intsync_enable(sitdev, sitdpll->id);
+ if (!rc)
+ sitdev->intsync_src = sitdpll->id;
+ break;
+ case DPLL_PIN_STATE_DISCONNECTED:
+ if (sitdev->intsync_src != sitdpll->id)
+ break;
+ rc = sit9531x_intsync_disable(sitdev, sitdpll->id);
+ if (!rc)
+ sitdev->intsync_src = -1;
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&sitdev->multiop_lock);
+
+ if (rc && rc != -EBUSY)
+ NL_SET_ERR_MSG(extack, "Failed to set INTSYNC source state");
+
+ return rc;
+}
+
+static const struct dpll_pin_ops sit9531x_dpll_intsync_src_pin_ops = {
+ .direction_get = sit9531x_dpll_output_pin_direction_get,
+ .state_on_dpll_get = sit9531x_dpll_intsync_src_state_on_dpll_get,
+ .state_on_dpll_set = sit9531x_dpll_intsync_src_state_on_dpll_set,
+};
+
+/* ---- INTSYNC destination (input) pin ---- */
+
+/*
+ * sit9531x_dpll_intsync_dst_state_on_dpll_get - INTSYNC reference state
+ *
+ * CONNECTED when this PLL is locked to INTSYNC as its reference,
+ * SELECTABLE when it runs in automatic mode and a source PLL is driving
+ * INTSYNC, DISCONNECTED otherwise. The PLL that drives INTSYNC can never
+ * be its own destination.
+ */
+static int
+sit9531x_dpll_intsync_dst_state_on_dpll_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state *state,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ const struct sit9531x_chan *chan;
+
+ chan = sit9531x_chan_state_get(sitdev, sitdpll->id);
+
+ mutex_lock(&sitdev->multiop_lock);
+ if (sitdev->intsync_src == sitdpll->id)
+ *state = DPLL_PIN_STATE_DISCONNECTED;
+ else if (chan->locked && !chan->inner_lol &&
+ chan->selected_ref == SIT9531X_INTSYNC_PIN_ID)
+ *state = DPLL_PIN_STATE_CONNECTED;
+ else if (!chan->mode && sitdev->intsync_src >= 0)
+ *state = DPLL_PIN_STATE_SELECTABLE;
+ else
+ *state = DPLL_PIN_STATE_DISCONNECTED;
+ mutex_unlock(&sitdev->multiop_lock);
+
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_intsync_dst_state_on_dpll_set - lock a PLL to INTSYNC
+ *
+ * CONNECTED/SELECTABLE -> add INTSYNC to this PLL's priority table
+ * DISCONNECTED -> drop INTSYNC from this PLL's priority table
+ *
+ * INTSYNC is an internal net with no physical receiver, so only the
+ * per-PLL priority table is touched; the source pin controls generation.
+ */
+static int
+sit9531x_dpll_intsync_dst_state_on_dpll_set(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state state,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ u8 hw_src = sit9531x_input_hw_src(SIT9531X_INTSYNC_PIN_ID);
+ int rc;
+
+ mutex_lock(&sitdev->multiop_lock);
+
+ switch (state) {
+ case DPLL_PIN_STATE_DISCONNECTED:
+ rc = sit9531x_input_prio_remove(sitdev, sitdpll->id, hw_src);
+ break;
+ case DPLL_PIN_STATE_SELECTABLE:
+ case DPLL_PIN_STATE_CONNECTED:
+ if (sitdev->intsync_src == sitdpll->id) {
+ NL_SET_ERR_MSG(extack,
+ "PLL cannot lock to the INTSYNC it drives");
+ rc = -EINVAL;
+ break;
+ }
+ rc = sit9531x_input_prio_add(sitdev, sitdpll->id, hw_src);
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&sitdev->multiop_lock);
+
+ if (rc && rc != -EINVAL)
+ NL_SET_ERR_MSG(extack, "Failed to set INTSYNC input state");
+
+ return rc;
+}
+
+/*
+ * Do not add .frequency_get / the generic input state getter here: the
+ * destination pin id is SIT9531X_INTSYNC_PIN_ID, one past the end of the
+ * ref[] array (INTSYNC is an internal net with no ref[] entry). The ops
+ * below only ever key on chan[] and the priority table, never ref[id].
+ */
+static const struct dpll_pin_ops sit9531x_dpll_intsync_dst_pin_ops = {
+ .direction_get = sit9531x_dpll_input_pin_direction_get,
+ .state_on_dpll_get = sit9531x_dpll_intsync_dst_state_on_dpll_get,
+ .state_on_dpll_set = sit9531x_dpll_intsync_dst_state_on_dpll_set,
+ .prio_get = sit9531x_dpll_input_pin_prio_get,
+ .prio_set = sit9531x_dpll_input_pin_prio_set,
+};
+
+/*
+ * XO (crystal oscillator) pin ops
+ *
+ * The XO is the chip's internal reference oscillator that feeds every
+ * PLL. It is exposed so userspace can see the on-chip reference, but it
+ * cannot be routed or disconnected, so it is reported permanently
+ * connected and offers no state_on_dpll_set / prio ops.
+ */
+
+static int
+sit9531x_dpll_xo_pin_state_on_dpll_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state *state,
+ struct netlink_ext_ack *extack)
+{
+ *state = DPLL_PIN_STATE_CONNECTED;
+ return 0;
+}
+
+static const struct dpll_pin_ops sit9531x_dpll_xo_pin_ops = {
+ .direction_get = sit9531x_dpll_input_pin_direction_get,
+ .frequency_get = sit9531x_dpll_input_pin_frequency_get,
+ .state_on_dpll_get = sit9531x_dpll_xo_pin_state_on_dpll_get,
+};
+
+static int
+sit9531x_dpll_output_pin_direction_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_direction *direction,
+ struct netlink_ext_ack *extack)
+{
+ *direction = DPLL_PIN_DIRECTION_OUTPUT;
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_output_pin_frequency_get - read output pin frequency
+ *
+ * Reads the DIVO divider back from the chip and computes the live
+ * frequency as Fvco / DIVO. Falls back to the cached value when the
+ * output is not resolvable through the divider chain (e.g. not mapped
+ * to a PLL), so a netlink dump never fails on such pins.
+ */
+static int
+sit9531x_dpll_output_pin_frequency_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, u64 *frequency,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ int rc;
+
+ mutex_lock(&sitdev->multiop_lock);
+ rc = sit9531x_output_freq_get(sitdev, dpin->id, frequency);
+ mutex_unlock(&sitdev->multiop_lock);
+
+ if (rc)
+ *frequency = sit9531x_out_state_get(sitdev, dpin->id)->freq;
+
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_output_pin_frequency_set - set output pin frequency
+ *
+ * computes DIVO = Fvco / frequency and writes the
+ * 34-bit output divider to the output system registers via
+ * sit9531x_output_freq_set().
+ */
+static int
+sit9531x_dpll_output_pin_frequency_set(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, u64 frequency,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ u8 actual_pll;
+ int rc;
+
+ /*
+ * Read the PLL that drives this output from its OUT_MAP state
+ * (populated by out_state_fetch from the chip's OUT_MAP registers).
+ * That is the index the output register programming below is keyed
+ * by; the output is registered under the DPLL matching this PLL.
+ */
+ actual_pll = sitdev->out[dpin->id].pll_idx;
+
+ mutex_lock(&sitdev->multiop_lock);
+ rc = sit9531x_output_freq_set(sitdev, dpin->id, actual_pll,
+ frequency);
+ mutex_unlock(&sitdev->multiop_lock);
+
+ if (rc)
+ NL_SET_ERR_MSG(extack, "Output frequency set failed");
+
+ return rc;
+}
+
+/*
+ * sit9531x_dpll_output_pin_state_on_dpll_get - get output pin state
+ *
+ * reports CONNECTED when the output is driven and
+ * DISCONNECTED when it has been muted via sit9531x_output_disable().
+ */
+static int
+sit9531x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state *state,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ const struct sit9531x_out *out;
+
+ out = sit9531x_out_state_get(sitdpll->dev, dpin->id);
+ *state = out->enabled ? DPLL_PIN_STATE_CONNECTED
+ : DPLL_PIN_STATE_DISCONNECTED;
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_output_pin_state_on_dpll_set - mute/un-mute an output
+ *
+ * forces Hi-Z on the output pin via the Page 0x03
+ * force/state register pair.
+ * CONNECTED -> enable (release force, back to factory default)
+ * DISCONNECTED -> disable (force Hi-Z)
+ */
+static int
+sit9531x_dpll_output_pin_state_on_dpll_set(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state state,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ int rc;
+
+ mutex_lock(&sitdev->multiop_lock);
+
+ switch (state) {
+ case DPLL_PIN_STATE_CONNECTED:
+ rc = sit9531x_output_enable(sitdev, dpin->id);
+ break;
+ case DPLL_PIN_STATE_DISCONNECTED:
+ rc = sit9531x_output_disable(sitdev, dpin->id);
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&sitdev->multiop_lock);
+
+ if (rc)
+ NL_SET_ERR_MSG(extack, "Failed to set output pin state");
+
+ return rc;
+}
+
+/*
+ * sit9531x_dpll_output_pin_phase_adjust_get - read output phase adjustment
+ *
+ * returns cached value.
+ */
+static int
+sit9531x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, s32 *phase_adjust,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+
+ *phase_adjust = dpin->phase_adjust;
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_output_pin_phase_adjust_set - set output phase adjustment
+ *
+ * Programs the per-output PRG_RST_DELAY registers for deterministic
+ * phase offset; see sit9531x_output_phase_adjust_set() in core.c.
+ */
+static int
+sit9531x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, s32 phase_adjust,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ int rc;
+
+ mutex_lock(&sitdev->multiop_lock);
+ rc = sit9531x_output_phase_adjust_set(sitdev, dpin->id, phase_adjust);
+ mutex_unlock(&sitdev->multiop_lock);
+
+ if (rc) {
+ NL_SET_ERR_MSG(extack, "Phase adjust failed");
+ return rc;
+ }
+
+ dpin->phase_adjust = phase_adjust;
+ return 0;
+}
+
+static int
+sit9531x_dpll_output_pin_esync_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ struct dpll_pin_esync *esync,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+
+ if (!sit9531x_dpll_esync_pin_supported(dpin))
+ return -EOPNOTSUPP;
+
+ esync->range = sit9531x_esync_ranges;
+ esync->range_num = ARRAY_SIZE(sit9531x_esync_ranges);
+ esync->pulse = SIT9531X_ESYNC_PULSE_DEFAULT;
+ esync->freq = dpin->esync_freq;
+
+ return 0;
+}
+
+static int
+sit9531x_dpll_output_pin_esync_set(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ u64 freq,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ u8 actual_pll;
+ int rc;
+
+ if (!sit9531x_dpll_esync_pin_supported(dpin)) {
+ NL_SET_ERR_MSG(extack,
+ "Embedded sync not enabled for this pin");
+ return -EOPNOTSUPP;
+ }
+
+ actual_pll = sitdev->out[dpin->id].pll_idx;
+
+ mutex_lock(&sitdev->multiop_lock);
+
+ /*
+ * This output is a dedicated embedded-sync pin.
+ * Treat freq=0 as a request to disable the entire output.
+ */
+ if (!freq) {
+ rc = sit9531x_output_disable(sitdev, dpin->id);
+ if (!rc)
+ dpin->esync_freq = 0;
+ mutex_unlock(&sitdev->multiop_lock);
+ return rc;
+ }
+
+ if (freq != SIT9531X_ESYNC_FREQ_10MHZ) {
+ mutex_unlock(&sitdev->multiop_lock);
+ NL_SET_ERR_MSG(extack,
+ "Only 10 MHz esync frequency is supported");
+ return -EINVAL;
+ }
+
+ rc = sit9531x_output_freq_set(sitdev, dpin->id, actual_pll,
+ SIT9531X_ESYNC_FREQ_10MHZ);
+ /*
+ * Program the pulse generator (PROG0 PULSE_CTRL) so the embedded-sync
+ * pulse is actually emitted; without it the output carries the clock
+ * but no esync marker. SIT9531X_ESYNC_PULSE_DEFAULT is the same duty
+ * the esync_get callback advertises.
+ */
+ if (!rc)
+ rc = sit9531x_output_pulse_ctrl_set(sitdev, dpin->id,
+ SIT9531X_ESYNC_PULSE_DEFAULT);
+ if (!rc)
+ rc = sit9531x_output_enable(sitdev, dpin->id);
+
+ mutex_unlock(&sitdev->multiop_lock);
+
+ if (!rc)
+ dpin->esync_freq = SIT9531X_ESYNC_FREQ_10MHZ;
+
+ return rc;
+}
+
+static const struct dpll_pin_ops sit9531x_dpll_output_pin_ops = {
+ .direction_get = sit9531x_dpll_output_pin_direction_get,
+ .frequency_get = sit9531x_dpll_output_pin_frequency_get,
+ .frequency_set = sit9531x_dpll_output_pin_frequency_set,
+ .state_on_dpll_get = sit9531x_dpll_output_pin_state_on_dpll_get,
+ .state_on_dpll_set = sit9531x_dpll_output_pin_state_on_dpll_set,
+ .phase_adjust_get = sit9531x_dpll_output_pin_phase_adjust_get,
+ .phase_adjust_set = sit9531x_dpll_output_pin_phase_adjust_set,
+ .esync_get = sit9531x_dpll_output_pin_esync_get,
+ .esync_set = sit9531x_dpll_output_pin_esync_set,
+};
+
+const struct dpll_pin_ops *
+sit9531x_dpll_pin_ops_get(const struct sit9531x_dpll_pin *pin)
+{
+ if (!sit9531x_dpll_is_input_pin(pin)) {
+ if (sit9531x_dpll_is_intsync_src_pin(pin))
+ return &sit9531x_dpll_intsync_src_pin_ops;
+ return &sit9531x_dpll_output_pin_ops;
+ }
+ if (sit9531x_dpll_is_intsync_pin(pin))
+ return &sit9531x_dpll_intsync_dst_pin_ops;
+ if (sit9531x_dpll_is_xo_pin(pin))
+ return &sit9531x_dpll_xo_pin_ops;
+ return &sit9531x_dpll_input_pin_ops;
+}
+
+/*
+ * sit9531x_dpll_changes_check - check for state changes and notify
+ *
+ * Called from sit9531x_dev_periodic_work(). Compares current hardware
+ * state against cached values and sends netlink notifications on changes.
+ */
+void sit9531x_dpll_changes_check(struct sit9531x_dpll *sitdpll)
+{
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ enum dpll_lock_status lock_status;
+ struct sit9531x_dpll_pin *pin;
+ int rc;
+
+ rc = sit9531x_dpll_lock_status_get(sitdpll->dpll_dev, sitdpll,
+ &lock_status, NULL, NULL);
+ if (rc) {
+ dev_err(sitdev->dev, "Failed to get DPLL%u lock status: %d\n",
+ sitdpll->id, rc);
+ return;
+ }
+
+ /* If lock status changed, notify DPLL core */
+ if (sitdpll->lock_status != lock_status) {
+ sitdpll->lock_status = lock_status;
+ dpll_device_change_ntf(sitdpll->dpll_dev);
+ }
+
+ list_for_each_entry(pin, &sitdpll->pins, list) {
+ const struct dpll_pin_ops *ops;
+ enum dpll_pin_state state;
+
+ /*
+ * Poll input pins whose state can change autonomously: regular
+ * references and the INTSYNC destination pin. Outputs (incl.
+ * the INTSYNC source) change only through their own set
+ * callback and the XO is permanently connected, so skip those.
+ * Each pin's own state_on_dpll_get resolves to the right getter.
+ */
+ if (!sit9531x_dpll_is_input_pin(pin) ||
+ sit9531x_dpll_is_xo_pin(pin))
+ continue;
+
+ ops = sit9531x_dpll_pin_ops_get(pin);
+ rc = ops->state_on_dpll_get(pin->dpll_pin, pin,
+ sitdpll->dpll_dev, sitdpll,
+ &state, NULL);
+ if (rc)
+ continue;
+
+ if (state != pin->pin_state) {
+ dev_dbg(sitdev->dev, "%s state changed: %u->%u\n",
+ pin->label, pin->pin_state, state);
+ pin->pin_state = state;
+ dpll_pin_change_ntf(pin->dpll_pin);
+ }
+ }
+}
diff --git a/drivers/dpll/sit9531x/dpll.h b/drivers/dpll/sit9531x/dpll.h
new file mode 100644
index 0000000000000..e5eef4514bcdd
--- /dev/null
+++ b/drivers/dpll/sit9531x/dpll.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SiTime SiT9531x DPLL subsystem interface
+ *
+ * Copyright (C) 2026 SiTime Corp.
+ * Author: Ali Rouhi <arouhi@sitime.com>
+ * Author: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>
+ *
+ * DPLL device and pin structures, and function declarations for
+ * the DPLL registration and callback layer.
+ */
+
+#ifndef _SIT9531X_DPLL_H
+#define _SIT9531X_DPLL_H
+
+#include <linux/dpll.h>
+#include <linux/list.h>
+#include <linux/types.h>
+
+struct sit9531x_dev;
+
+/* Per-pin DPLL state. */
+struct sit9531x_dpll_pin {
+ struct list_head list;
+ struct sit9531x_dpll *dpll;
+ struct dpll_pin *dpll_pin;
+ dpll_tracker tracker;
+ struct fwnode_handle *fwnode;
+ char label[8]; /* "IN0", "OUT3" */
+ enum dpll_pin_direction dir;
+ u8 id; /* hardware index */
+ u8 prio;
+ enum dpll_pin_state pin_state;
+ s32 phase_adjust; /* picoseconds */
+ s64 phase_offset; /* picoseconds */
+ bool esync_control;
+ u64 esync_freq; /* 0 == disabled */
+};
+
+/* Per-PLL DPLL device state. */
+struct sit9531x_dpll {
+ struct list_head list;
+ struct sit9531x_dev *dev;
+ struct dpll_device *dpll_dev;
+ dpll_tracker tracker;
+ struct dpll_device_ops ops; /* per-instance copy */
+ struct list_head pins;
+ u8 id; /* 0 = PLLA .. 3 = PLLD */
+ enum dpll_lock_status lock_status;
+};
+
+/* ---- DPLL allocation and registration ---- */
+/*
+ * The callback tables stay with the callbacks; the registration code that
+ * hands them to the subsystem lives next to probe() in core.c.
+ */
+extern const struct dpll_device_ops sit9531x_dpll_device_ops;
+const struct dpll_pin_ops *
+sit9531x_dpll_pin_ops_get(const struct sit9531x_dpll_pin *pin);
+
+struct sit9531x_dpll *sit9531x_dpll_alloc(struct sit9531x_dev *sitdev, u8 ch);
+void sit9531x_dpll_free(struct sit9531x_dpll *sitdpll);
+int sit9531x_dpll_register(struct sit9531x_dpll *sitdpll);
+void sit9531x_dpll_unregister(struct sit9531x_dpll *sitdpll);
+
+/* ---- Periodic change detection ---- */
+void sit9531x_dpll_changes_check(struct sit9531x_dpll *sitdpll);
+
+#endif /* _SIT9531X_DPLL_H */
diff --git a/drivers/dpll/sit9531x/prop.c b/drivers/dpll/sit9531x/prop.c
new file mode 100644
index 0000000000000..8a0105c8c6472
--- /dev/null
+++ b/drivers/dpll/sit9531x/prop.c
@@ -0,0 +1,397 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SiTime SiT9531x firmware node property parsing
+ *
+ * Copyright (C) 2026 SiTime Corp.
+ * Author: Ali Rouhi <arouhi@sitime.com>
+ * Author: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>
+ *
+ * Retrieves pin properties from Device Tree firmware nodes (or
+ * applies defaults when no firmware node exists).
+ */
+
+#include <linux/dev_printk.h>
+#include <linux/dpll.h>
+#include <linux/err.h>
+#include <linux/fwnode.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "core.h"
+#include "prop.h"
+
+/*
+ * sit9531x_input_pin_label - fill the package label for an input pin
+ *
+ * Split out so input-naming changes stay local to this helper.
+ */
+static void sit9531x_input_pin_label(struct sit9531x_dev *sitdev,
+ struct sit9531x_pin_props *props, u8 id)
+{
+ u8 pair = sit9531x_input_pair(id);
+
+ if (sitdev->ref[id].sig_mode == SIT9531X_MODE_DE)
+ snprintf(props->package_label,
+ sizeof(props->package_label), "IN%u", pair);
+ else
+ snprintf(props->package_label,
+ sizeof(props->package_label), "IN%u%c", pair,
+ sit9531x_input_is_n(id) ? 'N' : 'P');
+}
+
+/*
+ * sit9531x_prop_pin_package_label_set - generate package label
+ * @dir: pin direction
+ * @id: pin index
+ *
+ * Generates a package label string. Output pins are named "OUT0",
+ * "OUT1", ... Input pins are named after the physical pair and lane:
+ * "IN0P", "IN0N", "IN1P", ... for single-ended lanes, or "IN0",
+ * "IN1", ... when the pair is configured differential (the N lane is
+ * not registered in that case).
+ */
+static void
+sit9531x_prop_pin_package_label_set(struct sit9531x_dev *sitdev,
+ struct sit9531x_pin_props *props,
+ enum dpll_pin_direction dir, u8 id)
+{
+ /* The internal INTSYNC pin has a fixed label */
+ if (dir == DPLL_PIN_DIRECTION_INPUT &&
+ id == SIT9531X_INTSYNC_PIN_ID) {
+ strscpy(props->package_label, "INTSYNC",
+ sizeof(props->package_label));
+ props->dpll_props.package_label = props->package_label;
+ return;
+ }
+
+ /* The internal XO reference has a fixed label */
+ if (dir == DPLL_PIN_DIRECTION_INPUT && id == SIT9531X_MAX_INPUTS) {
+ strscpy(props->package_label, "XO",
+ sizeof(props->package_label));
+ props->dpll_props.package_label = props->package_label;
+ return;
+ }
+
+ /* The internal INTSYNC source (output) pin has a fixed label */
+ if (dir == DPLL_PIN_DIRECTION_OUTPUT &&
+ id == SIT9531X_INTSYNC_OUT_PIN_ID) {
+ strscpy(props->package_label, "SYNCOUT",
+ sizeof(props->package_label));
+ props->dpll_props.package_label = props->package_label;
+ return;
+ }
+
+ if (dir == DPLL_PIN_DIRECTION_INPUT)
+ sit9531x_input_pin_label(sitdev, props, id);
+ else
+ snprintf(props->package_label, sizeof(props->package_label),
+ "OUT%u", id);
+
+ props->dpll_props.package_label = props->package_label;
+}
+
+/*
+ * sit9531x_prop_pin_fwnode_get - find firmware node for a pin
+ * @dir: pin direction
+ * @id: pin index
+ *
+ * Searches for input-pins/output-pins child nodes in DT, looking
+ * for a child whose "reg" property matches @id.
+ *
+ * Return: 0 on success, -ENOENT if no firmware node exists
+ */
+static int
+sit9531x_prop_pin_fwnode_get(struct sit9531x_dev *sitdev,
+ struct sit9531x_pin_props *props,
+ enum dpll_pin_direction dir, u8 id)
+{
+ struct fwnode_handle *pins_node, *pin_node;
+ const char *node_name;
+
+ if (dir == DPLL_PIN_DIRECTION_INPUT)
+ node_name = "input-pins";
+ else
+ node_name = "output-pins";
+
+ pins_node = device_get_named_child_node(sitdev->dev, node_name);
+ if (!pins_node) {
+ dev_dbg(sitdev->dev, "'%s' sub-node is missing\n", node_name);
+ return -ENOENT;
+ }
+
+ /* Enumerate child pin nodes and find the requested one */
+ fwnode_for_each_child_node(pins_node, pin_node) {
+ u32 reg;
+
+ if (fwnode_property_read_u32(pin_node, "reg", ®))
+ continue;
+
+ if (id == reg)
+ break;
+ }
+
+ fwnode_handle_put(pins_node);
+
+ props->fwnode = pin_node;
+
+ dev_dbg(sitdev->dev, "Firmware node for %s %sfound\n",
+ props->package_label, pin_node ? "" : "NOT ");
+
+ return pin_node ? 0 : -ENOENT;
+}
+
+/*
+ * sit9531x_pin_props_get - get pin properties for a given pin
+ * @dir: pin direction (INPUT or OUTPUT)
+ * @index: pin index
+ *
+ * Allocates a pin properties structure, generates a package label,
+ * looks up the firmware node if available, and reads optional
+ * properties (label, connection-type, supported-frequencies-hz,
+ * esync-control).
+ *
+ * Call sit9531x_pin_props_put() to free the returned structure.
+ *
+ * Return: pointer to pin properties on success, error pointer on error
+ */
+struct sit9531x_pin_props *
+sit9531x_pin_props_get(struct sit9531x_dev *sitdev,
+ enum dpll_pin_direction dir, u8 index)
+{
+ struct dpll_pin_frequency *ranges;
+ struct sit9531x_pin_props *props;
+ int i, j, num_freqs = 0, rc;
+ u64 *freqs = NULL;
+ const char *type;
+ u32 curr_freq;
+
+ props = kzalloc_obj(*props, GFP_KERNEL);
+ if (!props)
+ return ERR_PTR(-ENOMEM);
+
+ if (dir == DPLL_PIN_DIRECTION_INPUT &&
+ index == SIT9531X_INTSYNC_PIN_ID) {
+ /*
+ * INTSYNC destination pin: a PLL locks to the INTSYNC net as a
+ * reference, so it can be connected and re-prioritised.
+ */
+ props->dpll_props.type = DPLL_PIN_TYPE_INT_OSCILLATOR;
+ props->dpll_props.capabilities =
+ DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE |
+ DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;
+ curr_freq = 0;
+ } else if (dir == DPLL_PIN_DIRECTION_OUTPUT &&
+ index == SIT9531X_INTSYNC_OUT_PIN_ID) {
+ /*
+ * INTSYNC source pin: a PLL drives the INTSYNC net. It can be
+ * connected/disconnected but carries no priority (driving the
+ * net is not a reference selection) and no frequency.
+ */
+ props->dpll_props.type = DPLL_PIN_TYPE_INT_OSCILLATOR;
+ props->dpll_props.capabilities =
+ DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;
+ curr_freq = 0;
+ } else if (dir == DPLL_PIN_DIRECTION_INPUT &&
+ index == SIT9531X_MAX_INPUTS) {
+ /* The XO reference is fixed: no state or priority control. */
+ props->dpll_props.type = DPLL_PIN_TYPE_INT_OSCILLATOR;
+ props->dpll_props.capabilities = 0;
+ sitdev->ref[index].freq = sitdev->xtal_freq;
+ curr_freq = sitdev->xtal_freq;
+ } else if (dir == DPLL_PIN_DIRECTION_INPUT) {
+ props->dpll_props.type = DPLL_PIN_TYPE_EXT;
+ props->dpll_props.capabilities =
+ DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE |
+ DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;
+ curr_freq = sitdev->ref[index].freq;
+ } else {
+ props->dpll_props.type = DPLL_PIN_TYPE_GNSS;
+ props->dpll_props.capabilities =
+ DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;
+ curr_freq = sitdev->out[index].freq;
+ }
+
+ /* Allow phase-adjust over +/-1 ms window. The subsystem rejects
+ * pin_set(phase-adjust, X) when X falls outside [min, max], so
+ * leaving these at 0 silently blocks every netlink call. 1 ms is
+ * well beyond the DCO dynamic range but costs nothing.
+ */
+ props->dpll_props.phase_range.min = -1000000000; /* -1 ms in ps */
+ props->dpll_props.phase_range.max = 1000000000; /* +1 ms in ps */
+
+ /* Generate package label */
+ sit9531x_prop_pin_package_label_set(sitdev, props, dir, index);
+
+ /*
+ * Both INTSYNC pins are internal to the chip and have no board-level
+ * wiring, so they take no properties from the firmware node.
+ */
+ if (dir == DPLL_PIN_DIRECTION_INPUT &&
+ index == SIT9531X_INTSYNC_PIN_ID)
+ goto skip_fwnode_props;
+ if (dir == DPLL_PIN_DIRECTION_OUTPUT &&
+ index == SIT9531X_INTSYNC_OUT_PIN_ID)
+ goto skip_fwnode_props;
+
+ rc = sit9531x_prop_pin_fwnode_get(sitdev, props, dir, index);
+ if (rc)
+ goto skip_fwnode_props;
+
+ /* Look for "label" property -> board label */
+ fwnode_property_read_string(props->fwnode, "label",
+ &props->dpll_props.board_label);
+
+ /* Look for "connection-type" property -> pin type enum */
+ if (!fwnode_property_read_string(props->fwnode, "connection-type",
+ &type)) {
+ if (!strcmp(type, "ext"))
+ props->dpll_props.type = DPLL_PIN_TYPE_EXT;
+ else if (!strcmp(type, "gnss"))
+ props->dpll_props.type = DPLL_PIN_TYPE_GNSS;
+ else if (!strcmp(type, "int") ||
+ !strcmp(type, "int-oscillator"))
+ props->dpll_props.type = DPLL_PIN_TYPE_INT_OSCILLATOR;
+ else if (!strcmp(type, "synce") ||
+ !strcmp(type, "synce-eth-port"))
+ props->dpll_props.type = DPLL_PIN_TYPE_SYNCE_ETH_PORT;
+ else if (!strcmp(type, "mux"))
+ props->dpll_props.type = DPLL_PIN_TYPE_MUX;
+ else
+ dev_warn(sitdev->dev,
+ "Unknown pin type '%s'\n", type);
+ }
+
+ props->esync_control =
+ fwnode_property_read_bool(props->fwnode, "esync-control");
+
+ num_freqs = fwnode_property_count_u64(props->fwnode,
+ "supported-frequencies-hz");
+ if (num_freqs <= 0) {
+ num_freqs = 0;
+ goto skip_fwnode_props;
+ }
+
+ freqs = kcalloc(num_freqs, sizeof(*freqs), GFP_KERNEL);
+ if (!freqs) {
+ rc = -ENOMEM;
+ goto err_alloc_freqs;
+ }
+
+ fwnode_property_read_u64_array(props->fwnode,
+ "supported-frequencies-hz",
+ freqs, num_freqs);
+
+ /*
+ * Seed the runtime ref->freq / out->freq with the first DT-listed
+ * supported frequency so the netlink frequency_get callback reports
+ * a sane initial value before any pin_set occurs. DT lists the
+ * physically-wired reference frequency for each input pin and the
+ * default output frequency for each output pin.
+ */
+ if (num_freqs > 0) {
+ if (dir == DPLL_PIN_DIRECTION_INPUT)
+ sitdev->ref[index].freq = (u32)freqs[0];
+ else
+ sitdev->out[index].freq = (u32)freqs[0];
+ curr_freq = (u32)freqs[0];
+ }
+
+skip_fwnode_props:
+ /* Neither INTSYNC pin carries a frequency attribute */
+ if (dir == DPLL_PIN_DIRECTION_INPUT &&
+ index == SIT9531X_INTSYNC_PIN_ID)
+ return props;
+ if (dir == DPLL_PIN_DIRECTION_OUTPUT &&
+ index == SIT9531X_INTSYNC_OUT_PIN_ID)
+ return props;
+
+ /* Allocate frequency ranges list -- DT discrete entries + current
+ * freq + one catch-all wide range so the subsystem never pre-
+ * rejects a frequency_set call. The chip's real admissible set
+ * is bounded by VCO / divider math in sit9531x_output_freq_set().
+ */
+ ranges = kcalloc(num_freqs + 2, sizeof(*ranges), GFP_KERNEL);
+ if (!ranges) {
+ rc = -ENOMEM;
+ goto err_alloc_ranges;
+ }
+
+ /* Current freq as first entry */
+ ranges[0] = (struct dpll_pin_frequency)DPLL_PIN_FREQUENCY(curr_freq);
+ j = 1;
+
+ for (i = 0; i < num_freqs; i++) {
+ struct dpll_pin_frequency freq = DPLL_PIN_FREQUENCY(freqs[i]);
+
+ if (freqs[i] == curr_freq)
+ continue;
+ ranges[j++] = freq;
+ }
+
+ /* Always append a wide catch-all range */
+ ranges[j].min = 1;
+ ranges[j].max = 1000000000ULL; /* 1 GHz */
+ j++;
+
+ props->dpll_props.freq_supported = ranges;
+ props->dpll_props.freq_supported_num = j;
+
+ kfree(freqs);
+
+ return props;
+
+err_alloc_ranges:
+ kfree(freqs);
+err_alloc_freqs:
+ fwnode_handle_put(props->fwnode);
+ kfree(props);
+
+ return ERR_PTR(rc);
+}
+
+/*
+ * sit9531x_pin_props_put - release pin properties
+ * @props: pin properties to free
+ */
+void sit9531x_pin_props_put(struct sit9531x_pin_props *props)
+{
+ kfree(props->dpll_props.freq_supported);
+
+ if (props->fwnode)
+ fwnode_handle_put(props->fwnode);
+
+ kfree(props);
+}
+
+/*
+ * sit9531x_prop_dpll_type_get - get DPLL channel type from firmware
+ * @index: DPLL channel index (0-3)
+ *
+ * Reads the "dpll-types" string array property from the firmware node
+ * and returns the corresponding DPLL type enum.
+ *
+ * Return: DPLL type for the given channel (default: DPLL_TYPE_PPS)
+ */
+enum dpll_type
+sit9531x_prop_dpll_type_get(struct sit9531x_dev *sitdev, u8 index)
+{
+ const char *types[SIT9531X_NUM_PLLS];
+ int count;
+
+ count = device_property_read_string_array(sitdev->dev, "dpll-types",
+ types, ARRAY_SIZE(types));
+
+ if (index >= count)
+ return DPLL_TYPE_PPS;
+
+ if (!strcmp(types[index], "pps"))
+ return DPLL_TYPE_PPS;
+ else if (!strcmp(types[index], "eec"))
+ return DPLL_TYPE_EEC;
+
+ dev_warn(sitdev->dev, "Unknown DPLL type '%s', using default\n",
+ types[index]);
+
+ return DPLL_TYPE_PPS;
+}
diff --git a/drivers/dpll/sit9531x/prop.h b/drivers/dpll/sit9531x/prop.h
new file mode 100644
index 0000000000000..f7f1c854b9558
--- /dev/null
+++ b/drivers/dpll/sit9531x/prop.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SiTime SiT9531x firmware node property parsing
+ *
+ * Copyright (C) 2026 SiTime Corp.
+ * Author: Ali Rouhi <arouhi@sitime.com>
+ * Author: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>
+ */
+
+#ifndef _SIT9531X_PROP_H
+#define _SIT9531X_PROP_H
+
+#include <linux/dpll.h>
+#include <linux/fwnode.h>
+
+struct sit9531x_dev;
+
+/*
+ * struct sit9531x_pin_props - pin properties from firmware
+ * @fwnode: firmware node handle (NULL if no DT node)
+ * @dpll_props: DPLL core pin properties
+ * @package_label: pin package label (e.g. "IN0", "OUT3")
+ * @esync_control: embedded sync is controllable
+ */
+struct sit9531x_pin_props {
+ struct fwnode_handle *fwnode;
+ struct dpll_pin_properties dpll_props;
+ char package_label[8];
+ bool esync_control;
+};
+
+enum dpll_type sit9531x_prop_dpll_type_get(struct sit9531x_dev *sitdev,
+ u8 index);
+struct sit9531x_pin_props *sit9531x_pin_props_get(struct sit9531x_dev *sitdev,
+ enum dpll_pin_direction dir,
+ u8 index);
+void sit9531x_pin_props_put(struct sit9531x_pin_props *props);
+
+#endif /* _SIT9531X_PROP_H */
diff --git a/drivers/dpll/sit9531x/regs.h b/drivers/dpll/sit9531x/regs.h
new file mode 100644
index 0000000000000..5c3dbeefd86b6
--- /dev/null
+++ b/drivers/dpll/sit9531x/regs.h
@@ -0,0 +1,371 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SiTime SiT9531x register definitions
+ *
+ * Copyright (C) 2026 SiTime Corp.
+ * Author: Ali Rouhi <arouhi@sitime.com>
+ * Author: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>
+ */
+
+#ifndef _SIT9531X_REGS_H
+#define _SIT9531X_REGS_H
+
+/*
+ * I2C register model:
+ * - Page select register at offset 0x01
+ * - Each page has 128 registers (0x00-0x7F)
+ * - 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
+
+/* Helper macros for page:offset addressing */
+#define SIT9531X_REG(_page, _offset) (((_page) << 8) | (_offset))
+#define SIT9531X_REG_PAGE(_reg) ((_reg) >> 8)
+#define SIT9531X_REG_OFFSET(_reg) ((_reg) & 0xFF)
+
+/* ---- Page definitions ---- */
+#define SIT9531X_PAGE_MAINSYS0 0x00
+#define SIT9531X_PAGE_MAINSYS1 0x01
+#define SIT9531X_PAGE_INPUTSYS 0x02
+#define SIT9531X_PAGE_OUTSYS0 0x03
+#define SIT9531X_PAGE_OUTSYS1 0x04
+#define SIT9531X_PAGE_CLKMON0 0x06
+#define SIT9531X_PAGE_CLKMON1 0x07
+#define SIT9531X_PAGE_PLLA 0x0A
+#define SIT9531X_PAGE_PLLA_EXT 0x1A
+#define SIT9531X_PAGE_PLLB 0x0B
+#define SIT9531X_PAGE_PLLB_EXT 0x1B
+#define SIT9531X_PAGE_PLLC 0x0C
+#define SIT9531X_PAGE_PLLC_EXT 0x1C
+#define SIT9531X_PAGE_PLLD 0x0D
+#define SIT9531X_PAGE_PLLD_EXT 0x1D
+
+/* PLL index to page mapping */
+#define SIT9531X_PLL_PAGE(_idx) \
+ (SIT9531X_PAGE_PLLA + (_idx))
+
+/*
+ * VARIANT_ID is a single byte at page 0 reg 0x02 (95317 = 0x17, 95316 = 0x31).
+ * Reg 0x03 carries an unrelated revision byte and must not be combined into
+ * the variant identifier.
+ */
+#define SIT9531X_REG_VARIANT_ID SIT9531X_REG(0x00, 0x02)
+
+/* DCO trigger pulse timing: minimum 6 ns required by hardware */
+
+#define SIT9531X_REG_HOLDOVER_HISTORY SIT9531X_REG(0x00, 0x58)
+
+/* Page 0 -- PLL inner loop loss-of-lock */
+#define SIT9531X_REG_PLL_INNER_LOL_STATUS SIT9531X_REG(0x00, 0x92)
+#define SIT9531X_REG_PLL_INNER_LOL_NOTIF SIT9531X_REG(0x00, 0x93)
+
+/* Page 0 -- Clock monitor PLL / XO status */
+#define SIT9531X_REG_CMON_NOTIF SIT9531X_REG(0x00, 0x9E)
+
+/* Page 0 -- PLL outer-loop loss-of-lock */
+#define SIT9531X_REG_OUTER_LOL_STATUS SIT9531X_REG(0x00, 0x06)
+#define SIT9531X_REG_OUTER_LOL_NOTIF SIT9531X_REG(0x00, 0x07)
+
+/* Page 0 -- PLL holdover freeze status */
+#define SIT9531X_REG_HO_FREEZE_STATUS SIT9531X_REG(0x00, 0x0A)
+#define SIT9531X_REG_HO_FREEZE_NOTIF SIT9531X_REG(0x00, 0x0B)
+
+/* Page 0 -- INTSYNC (inter-PLL synchronization) global enable */
+#define SIT9531X_REG_INTSYNC_GLOBAL SIT9531X_REG(0x00, 0x40)
+#define SIT9531X_INTSYNC_EN_BIT 6
+
+/*
+ * Priority table: 6 registers per PLL, each holds two priority slots
+ * nibble-packed. The register holding slots 2n and 2n+1 keeps the
+ * earlier slot (CLK_SPARE<2n>SEL_PLL) in [7:4] and the later one in
+ * [3:0].
+ *
+ * Base registers for PLLA: 0x16-0x1B (slots 0-10 plus the
+ * active-reference nibble).
+ * For PLL N: base + 6 * N (e.g. PLLB starts at 0x1C).
+ *
+ * Input source encoding (4-bit value):
+ * 0=IN0P, 1=IN1P, 2=IN2P, 3=IN3P, 4=IN4P,
+ * 5=OCXO, 6=INTSYNC,
+ * 7=IN0N, 8=IN1N, 9=IN2N, 10=IN3N, 11=IN4N
+ */
+#define SIT9531X_PAGE_PRIOSYS 0x01
+#define SIT9531X_PRIO_BASE_REG 0x16
+#define SIT9531X_PRIO_REGS_PER_PLL 6
+#define SIT9531X_PRIO_SLOTS_PER_REG 2
+/*
+ * 11 priority slots, CLK_SPARE0SEL_PLL through CLK_SPARE10SEL_PLL.
+ * The twelfth nibble of the block is not a slot: it is
+ * CLK_ACTIVESEL_PLL, see SIT9531X_PRIO_ACTIVESEL_OFF below.
+ */
+#define SIT9531X_PRIO_MAX_SLOTS 11
+/* Number of source encodings (0-11), unrelated to the slot count */
+#define SIT9531X_PRIO_NUM_SRC 12
+#define SIT9531X_PRIO_NIBBLE_MASK 0x0F
+#define SIT9531X_PRIO_HI_SHIFT 4
+/* Input source encoding values (see table above) */
+#define SIT9531X_PRIO_SRC_OCXO 5
+#define SIT9531X_PRIO_SRC_INTSYNC 6
+#define SIT9531X_PRIO_SRC_N_BASE 7
+/*
+ * The last register of each PLL's priority block holds, in its low
+ * nibble, the input source the PLL has currently selected as its
+ * active reference (CLK_ACTIVESEL_PLL, same 4-bit encoding as above).
+ */
+#define SIT9531X_PRIO_ACTIVESEL_OFF 5
+
+/*
+ * Page 0 -- PRG_Directives_GENERIC_0, the main system's programming
+ * directive register. Every page carries its own copy of this
+ * register at offset 0x0F with the same bit layout:
+ *
+ * bit 6 proceed to loop lock / active state from the PRG_CMD state
+ * bit 4 update the NVM bank from the efuse contents
+ * bit 3 read the efuse
+ * bit 2 program the efuse
+ * bit 1 small change update (SIT9531X_SMALL_UPDATE_CMD)
+ * bit 0 escape to the PRG_CMD state
+ *
+ * Only the small change update belongs in a runtime path; the efuse
+ * and NVM directives touch non-volatile storage.
+ */
+#define SIT9531X_REG_GLOBAL_UPDATE SIT9531X_REG(0x00, 0x0F)
+#define SIT9531X_SMALL_UPDATE_CMD 0x02
+
+/* PLL holdover control (PLL page offset) */
+#define SIT9531X_PLL_REG_HO_CTRL 0x6F
+#define SIT9531X_PLL_HO_FORCE_BIT 4
+
+/* One bit per input PAIR (bit 0 = CLKIN0, ..., bit 3 = CLKIN3) */
+#define SIT9531X_REG_IN_DE_FORCE SIT9531X_REG(0x02, 0xE8)
+#define SIT9531X_REG_IN_DE_STATE SIT9531X_REG(0x02, 0xE9)
+#define SIT9531X_REG_IN_SEP_FORCE SIT9531X_REG(0x02, 0xEA)
+#define SIT9531X_REG_IN_SEP_STATE SIT9531X_REG(0x02, 0xEB)
+#define SIT9531X_REG_IN_SEN_FORCE SIT9531X_REG(0x02, 0xF2)
+#define SIT9531X_REG_IN_SEN_STATE SIT9531X_REG(0x02, 0xF3)
+
+/*
+ * One register per input pair at 0x1B + 0x10 * pair
+ * (CLKIN0 = 0x1B, CLKIN1 = 0x2B, CLKIN2 = 0x3B, CLKIN3 = 0x4B).
+ * SE_P_EN/SE_N_EN set means the corresponding lane is configured
+ * single-ended; both clear means the pair runs differential.
+ */
+#define SIT9531X_REG_IN_MODE(_pair) \
+ SIT9531X_REG(0x02, 0x1B + 0x10 * (_pair))
+#define SIT9531X_IN_MODE_SE_P_EN BIT(0)
+#define SIT9531X_IN_MODE_SE_N_EN BIT(1)
+
+/* ---- Page 0x03 (Output System) registers -- Hi-Z control ---- */
+#define SIT9531X_REG_HIZ_DIFF_07_MASK SIT9531X_REG(0x03, 0xF2)
+#define SIT9531X_REG_HIZ_DIFF_07_STATE SIT9531X_REG(0x03, 0xF3)
+#define SIT9531X_REG_HIZ_DIFF_811_MASK SIT9531X_REG(0x03, 0xF4)
+#define SIT9531X_REG_HIZ_DIFF_811_STATE SIT9531X_REG(0x03, 0xF5)
+#define SIT9531X_REG_HIZ_SE_07_MASK SIT9531X_REG(0x03, 0xF8)
+#define SIT9531X_REG_HIZ_SE_07_STATE SIT9531X_REG(0x03, 0xF9)
+#define SIT9531X_REG_HIZ_SE_811_MASK SIT9531X_REG(0x03, 0xFA)
+#define SIT9531X_REG_HIZ_SE_811_STATE SIT9531X_REG(0x03, 0xFB)
+
+/*
+ * Output divider registers in Pages 3/4. Each output has a 34-bit
+ * integer divider mapped to 5 bytes (LSB at base reg, MSB at base-4).
+ * Outputs 0-5 are on Page 3, outputs 6-11 are on Page 4.
+ *
+ * The base register for slot N within a page is:
+ * clkout_odr_divn_base[slot] = { 0x14, 0x24, 0x34, 0x44, 0x54, 0x64 }
+ *
+ * Layout: base=LSB, base-1, base-2, base-3, base-4[1:0]=MSB.
+ *
+ * Per-chip clkout_map[] translates output index to slot position.
+ */
+#define SIT9531X_PAGE_OUTSYS0_SLOT_MAX 5 /* slots 0-5 on Page 0x03 */
+
+/* Misc output system registers */
+#define SIT9531X_REG_PRG_DIR_GEN SIT9531X_REG(0x03, 0x0F)
+#define SIT9531X_PRG_CMD_STATE 0x01
+#define SIT9531X_UPDATE_NVM 0x10
+#define SIT9531X_LOOP_LOCK 0x40
+
+/* Debug register (same offset, per-page) */
+#define SIT9531X_REG_OUTSYS_DEBUG SIT9531X_REG(0x03, 0xBD)
+#define SIT9531X_DEBUG_UNLOCK_VAL 0xC3
+
+/*
+ * Per-output programmable phase delay: 34-bit coarse (in VCO clock
+ * cycles) plus a 3-bit fine field with fixed 30 ps steps. Each output
+ * has a five-byte block PROG6..PROG2:
+ *
+ * base + 0 PROG6 [7:5] OPSTG_VCASC_BUMP (preserve via RMW)
+ * [4:2] PRG_RST_FINE_DELAY[2:0]
+ * [1:0] PRG_RST_DELAY[33:32]
+ * base + 1 PROG5 [7:0] PRG_RST_DELAY[31:24]
+ * base + 2 PROG4 [7:0] PRG_RST_DELAY[23:16]
+ * base + 3 PROG3 [7:0] PRG_RST_DELAY[15:8]
+ * base + 4 PROG2 [7:0] PRG_RST_DELAY[7:0]
+ *
+ * Outputs 0-5 are on Page 3, outputs 6-11 on Page 4. The block base
+ * within a page is 0x15 + 16 * (out_idx % 6).
+ */
+#define SIT9531X_OUT_PRG_DELAY_BASE 0x15
+#define SIT9531X_OUT_PRG_SLOT_STRIDE 0x10
+#define SIT9531X_OUT_PRG_OPSTG_MASK 0xE0 /* bits [7:5], preserve */
+#define SIT9531X_OUT_PRG_FINE_SHIFT 2
+#define SIT9531X_OUT_PRG_FINE_MASK 0x1C /* bits [4:2] */
+#define SIT9531X_OUT_PRG_COARSE_HI_MASK 0x03 /* bits [1:0] */
+#define SIT9531X_OUT_PRG_FINE_STEP_PS 30
+#define SIT9531X_OUT_PRG_FINE_MAX 7 /* 3-bit field */
+#define SIT9531X_OUT_PRG_COARSE_BITS 34
+
+/*
+ * Per-output pulse-count control byte used in SYSREF / SYNCB modes.
+ * Slot N within a page sits at 0x1B + 16 * (slot % 6). Same page
+ * mapping as PRG_RST_DELAY: slots 0-5 on Page 3, slots 6-11 on Page 4.
+ */
+#define SIT9531X_OUT_PROG0_BASE 0x1B
+
+/*
+ * On-demand phase-flush fired from a register rather than a GPIO pin.
+ * DIVO_PHASE_SEL_REG selects the in-register trigger source and
+ * DIVO_PHASE_TRIG flushes the output phase when pulsed high then low.
+ * The unrelated OEb trigger pair in bits [7:6] must be preserved.
+ */
+#define SIT9531X_REG_GPIO_FUNC_CTRL1 SIT9531X_REG(0x00, 0x65)
+#define SIT9531X_DIVO_PHASE_SEL_REG BIT(5)
+#define SIT9531X_DIVO_PHASE_TRIG BIT(4)
+
+/* ---- PLL page registers (apply to pages 0x0A-0x0D) ---- */
+#define SIT9531X_PLL_REG_SMALL_UPDATE 0x0F
+
+/* On-demand phase-flush enable (PLL page reg 0x3D bit 7) */
+#define SIT9531X_PLL_REG_PHFL_CTRL 0x3D
+#define SIT9531X_PLL_PHFL_ON_DEMAND_EN BIT(7)
+
+/*
+ * Loop-filter coefficients on PLL_PAGE regs 0x10-0x15 (3 normal +
+ * 3 fast-lock) are GUI/NVM-generated by the timing configurator and must not be
+ * reprogrammed at runtime; the register map flags them as
+ * "GUI generated configuration should not change manually".
+ */
+
+#define SIT9531X_PLL_REG_OUT_MAP_HI 0x27
+#define SIT9531X_PLL_REG_OUT_MAP_LO 0x28
+#define SIT9531X_PLL_REG_STATUS 0x31
+#define SIT9531X_PLL_REG_NVM_UPDATE 0x3F
+
+/* DIVN registers (free-run divider readback) */
+#define SIT9531X_PLL_REG_DIVN_INT 0x30
+#define SIT9531X_PLL_REG_DIVN_NUM 0x32 /* 4 bytes (0x32-0x35) */
+#define SIT9531X_PLL_REG_DIVN_DEN 0x38 /* 4 bytes (0x38-0x3B) */
+
+/* DIVN2 registers (sync divider readback) */
+#define SIT9531X_PLL_REG_DIVN2_INT 0x3E /* 5 bytes (0x3E-0x42) */
+#define SIT9531X_PLL_REG_DIVN2_FRAC_NUM 0x43 /* 4 bytes (0x43-0x46) */
+#define SIT9531X_PLL_REG_DIVN2_FRAC_DEN 0x49 /* 4 bytes (0x49-0x4C) */
+
+/* Debug register unlock */
+#define SIT9531X_PLL_REG_DEBUG 0xBD
+#define SIT9531X_PLL_DEBUG_UNLOCK 0xC3
+
+/*
+ * Signal pathway debug readback -- PLL page. Dig_Sys_ReadCode selects
+ * which point of the pathway is tapped, Dig_Sys_WriteCode carries the
+ * modifiers for that read, Dig_Sys_read7..read0 hold the sampled bytes
+ * and the trigger register latches a sample. The TDC phase
+ * measurement is one tap among several, reached through read code 69.
+ */
+#define SIT9531X_PLL_REG_DBG_READ_CODE 0xB3
+#define SIT9531X_PLL_REG_DBG_WRITE_CODE 0xB4
+#define SIT9531X_DBG_LOW_FREQ_CLK_BIT BIT(7)
+#define SIT9531X_PLL_REG_DBG_DATA_0 0xB5 /* [7:0] */
+#define SIT9531X_PLL_REG_DBG_DATA_1 0xB6 /* [15:8] */
+#define SIT9531X_PLL_REG_DBG_DATA_2 0xB7 /* [23:16] */
+#define SIT9531X_PLL_REG_DBG_DATA_3 0xB8 /* [31:24] */
+#define SIT9531X_PLL_REG_DBG_DATA_4 0xB9 /* [39:32] + sign */
+#define SIT9531X_PLL_REG_DBG_DATA_5 0xBA /* [47:40] */
+#define SIT9531X_PLL_REG_DBG_DATA_6 0xBB
+#define SIT9531X_PLL_REG_DBG_DATA_7 0xBC
+#define SIT9531X_PLL_REG_DBG_TRIGGER 0xD0 /* read to latch a sample */
+#define SIT9531X_DBG_DATA_BYTES 8
+
+/* Read code of the TDC phase tap, and the sign bit of its sample */
+#define SIT9531X_DBG_READ_CODE_TDC 69
+#define SIT9531X_TDC_SIGN_BIT 3
+
+/*
+ * Read codes of the running DIVN taps. Unlike the configuration
+ * registers these report what the digital loop currently commands, so
+ * they carry the correction the loop applies to track its reference.
+ * The integer part and the numerator share one tap, the denominator
+ * has its own.
+ */
+#define SIT9531X_DBG_READ_CODE_DIVN 0x57
+#define SIT9531X_DBG_READ_CODE_DIVN_DEN 0x56
+#define SIT9531X_DIVN_RT_NUM_BITS 48
+#define SIT9531X_DIVN_RT_INT_HI_BIT BIT(0)
+
+/*
+ * DIVN carried as fixed point, and the unit the DPLL ABI wants the
+ * fractional frequency offset in. Equal in value, distinct in meaning.
+ */
+#define SIT9531X_DIVN_SCALE 1000000000000ULL
+#define SIT9531X_PPT_PER_UNIT 1000000000000ULL
+
+/*
+ * TDC phase-difference measurement setup (PLL page). Per the vendor
+ * PhaseDifferenceReader reference procedure, a valid TDC reading needs
+ * the digital loop filter frozen (so the VCO does not track out the
+ * measured offset mid-sample) and, on PLLA (the 1PPS phase DPLL), the
+ * automatic phase/frequency-lock helpers held off.
+ */
+#define SIT9531X_PLL_REG_DLPF_FREEZE0 0x8A
+#define SIT9531X_PLL_REG_DLPF_FREEZE1 0x8B
+#define SIT9531X_PLL_DLPF_FREEZE_BIT BIT(5) /* freeze DLPF (hitless HO) */
+#define SIT9531X_PLL_REG_APL_CTRL 0x4D
+#define SIT9531X_PLL_APL_WAKEUP_DIS_BIT BIT(7) /* disable APL at wake-up */
+#define SIT9531X_PLL_APL_SWITCH_EN_BIT BIT(5) /* APL on switchover */
+#define SIT9531X_PLL_REG_AFL_CTRL 0x4E
+#define SIT9531X_PLL_AFL_WAKEUP_DIS_BIT BIT(5) /* disable AFL at wake-up */
+#define SIT9531X_PLL_CONFIG47_PHFL_BIT BIT(7) /* PHFL enable (reg 0x47) */
+#define SIT9531X_PLL_REG_ACTIVE 0x02
+#define SIT9531X_PLL_ACTIVE_BIT BIT(0) /* PLL reached active state */
+#define SIT9531X_PLL_REG_RESTART 0x05
+#define SIT9531X_PLL_RESTART_BIT BIT(0) /* restart the PLL */
+#define SIT9531X_PLL_REG_ZDB0 0x2B
+#define SIT9531X_PLL_REG_ZDB1 0x1E
+#define SIT9531X_PLL_ZDB_EN_BIT BIT(4) /* zero-delay buffer enabled */
+
+/* PLL EXT page INTSYNC configuration registers */
+#define SIT9531X_PLL_EXT_PAGE(_idx) (SIT9531X_PAGE_PLLA_EXT + (_idx))
+
+/* PLL STATUS register bits */
+#define SIT9531X_PLL_STATUS_LOCK BIT(0)
+#define SIT9531X_PLL_STATUS_OUTER_DIS BIT(5)
+
+/* P-polarity status registers */
+#define SIT9531X_CLKMON_P_STATUS_01 SIT9531X_REG(0x06, 0x02) /* inputs 0,1 */
+#define SIT9531X_CLKMON_P_NOTIF_01 SIT9531X_REG(0x06, 0x03)
+#define SIT9531X_CLKMON_P_STATUS_23 SIT9531X_REG(0x06, 0x06) /* inputs 2,3 */
+#define SIT9531X_CLKMON_P_NOTIF_23 SIT9531X_REG(0x06, 0x07)
+
+/* N-polarity status registers */
+#define SIT9531X_CLKMON_N_STATUS_01 SIT9531X_REG(0x06, 0x92) /* inputs 0,1 */
+#define SIT9531X_CLKMON_N_NOTIF_01 SIT9531X_REG(0x06, 0x93)
+#define SIT9531X_CLKMON_N_STATUS_23 SIT9531X_REG(0x06, 0x96) /* inputs 2,3 */
+#define SIT9531X_CLKMON_N_NOTIF_23 SIT9531X_REG(0x06, 0x97)
+
+/* Per-input bit offsets within clock monitor nibble */
+#define SIT9531X_CLKMON_FREQ_FINE 0 /* bit 0 / bit 4 */
+#define SIT9531X_CLKMON_FREQ_COARSE 1 /* bit 1 / bit 5 */
+#define SIT9531X_CLKMON_CLK_LOSS 2 /* bit 2 / bit 6 */
+#define SIT9531X_CLKMON_CLK_LOSS_FD 3 /* bit 3 / bit 7 */
+
+/* ---- Debug / NVM unlock registers ---- */
+#define SIT9531X_REG_DBG_UNLOCK1 0x24
+#define SIT9531X_REG_DBG_UNLOCK2 0x25
+
+/* ---- Variant ID values (single byte read from SIT9531X_REG_VARIANT_ID) ---- */
+#define SIT9531X_VARIANT_ID_95317 0x17
+#define SIT9531X_VARIANT_ID_95316 0x31
+
+#endif /* _SIT9531X_REGS_H */
|
| 3/2 | 2026/08/12 19:57 | llm | patch-evaluator |
1mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 80034 output: 22 thoughts: 1058 Thoughts: **Reaching the SiTime SiT9531x DPLL Driver in a Virtualized Environment** Okay, so I have this new driver for the SiTime SiT9531x DPLL clock generators. It's an I2C driver, located in `drivers/dpll/sit9531x/`, and registers DPLL devices and pins. It uses regmap, I2C, and has a threaded IRQ handler, delayed work for polling, and supports DPLL subsystem callbacks. Now, the key question is, can I reach this driver in a virtualized environment like QEMU or GCE? These environments often lack the hardware resources that real hardware relies on. Since it's an I2C driver, the typical approach would be instantiating it through `/sys/bus/i2c/devices/i2c-*/new_device`, or potentially through device tree overlays, or even through syzkaller injection if syzkaller has support. Syzkaller usually employs `i2c-stub` or similar or uses `/sys/bus/i2c/devices/i2c-*/new_device`. To use the latter, I'd need the driver's name, which in this case is "sit9531x" from `sit9531x_i2c_driver`. Therefore, a command like `echo sit9531x 0x68 > /sys/bus/i2c/devices/i2c-0/new_device` should, in theory, trigger the probe. However, the probe function, `sit9531x_dev_probe`, immediately calls `devm_clk_get_enabled(sitdev->dev, "xtal")`. This is where the issue lies. If the "xtal" clock isn't provided, `devm_clk_get_enabled` will return an error, likely `-ENOENT`, and the probe will fail. The C code confirms this. In a virtualized environment where I can't rely on a specific device tree, it's very unlikely the "xtal" clock will be defined. It is important to know that I2C devices instantiated via sysfs `new_device` don't have an OF node. I don't think there's a generic dummy clock that it could fall back to. `devm_clk_get_enabled` usually fails without a clock entry. So, no luck with `CONFIG_COMMON_CLK_DUMMY` or the like. In the common case, without the clock, it just returns `-ENOENT`. So the question is: can syzkaller provide this clock through a device tree overlay? I know syzkaller has DT overlay support but not for I2C and clocks as often. There are no other ways like `syz_genetlink` that would help, because this is an I2C device. How about trying to make `devm_clk_get_enabled` return a valid clock even without a DT node? Maybe using `i2c_new_client_device` from another driver? But that would require some other driver already probing and I'm unsure if that is possible. The prompt says that such unreachable code paths should be avoided. In a lot of these cases, I2C drivers that depend on specific DT resources (like clocks, GPIOs, and regulators) are difficult to probe via `new_device` because those resources are absent in the virtualized setup. Although, sometimes, there are dummy clocks... So, I need to look closely at the behavior of `devm_clk_get_enabled` and how it handles the absence of a clock to determine if this driver is truly reachable. |
| 4/2 | 2026/08/12 19:58 | tool | grepper |
0mArgs:{
"Expression": "devm_clk_get_enabled"
}Results: {
"Output": "\nFull output is too long, showing 500 out of 2432 lines.\nUse more precise expression if possible.\n\ndrivers/ata/pata_imx.c=125=static int pata_imx_probe(struct platform_device *pdev)\n--\ndrivers/ata/pata_imx.c-142-\ndrivers/ata/pata_imx.c:143:\tpriv-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/ata/pata_imx.c-144-\tif (IS_ERR(priv-\u003eclk)) {\n--\ndrivers/bus/imx-weim.c=265=static int weim_probe(struct platform_device *pdev)\n--\ndrivers/bus/imx-weim.c-284-\t/* get the clock */\ndrivers/bus/imx-weim.c:285:\tclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/bus/imx-weim.c-286-\tif (IS_ERR(clk))\n--\ndrivers/char/hw_random/exynos-trng.c=183=static int exynos_trng_probe(struct platform_device *pdev)\n--\ndrivers/char/hw_random/exynos-trng.c-222-\ndrivers/char/hw_random/exynos-trng.c:223:\ttrng-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev, \"secss\");\ndrivers/char/hw_random/exynos-trng.c-224-\tif (IS_ERR(trng-\u003eclk)) {\n--\ndrivers/char/hw_random/ingenic-trng.c=75=static int ingenic_trng_probe(struct platform_device *pdev)\n--\ndrivers/char/hw_random/ingenic-trng.c-89-\ndrivers/char/hw_random/ingenic-trng.c:90:\tclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/char/hw_random/ingenic-trng.c-91-\tif (IS_ERR(clk))\n--\ndrivers/char/hw_random/ks-sa-rng.c=205=static int ks_sa_rng_probe(struct platform_device *pdev)\n--\ndrivers/char/hw_random/ks-sa-rng.c-233-\ndrivers/char/hw_random/ks-sa-rng.c:234:\tks_sa_rng-\u003eclk = devm_clk_get_enabled(dev, NULL);\ndrivers/char/hw_random/ks-sa-rng.c-235-\tif (IS_ERR(ks_sa_rng-\u003eclk))\n--\ndrivers/char/hw_random/mxc-rnga.c=134=static int mxc_rnga_probe(struct platform_device *pdev)\n--\ndrivers/char/hw_random/mxc-rnga.c-149-\ndrivers/char/hw_random/mxc-rnga.c:150:\tmxc_rng-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/char/hw_random/mxc-rnga.c-151-\tif (IS_ERR(mxc_rng-\u003eclk)) {\n--\ndrivers/char/hw_random/nomadik-rng.c=35=static int nmk_rng_probe(struct amba_device *dev, const struct amba_id *id)\n--\ndrivers/char/hw_random/nomadik-rng.c-40-\ndrivers/char/hw_random/nomadik-rng.c:41:\trng_clk = devm_clk_get_enabled(\u0026dev-\u003edev, NULL);\ndrivers/char/hw_random/nomadik-rng.c-42-\tif (IS_ERR(rng_clk))\n--\ndrivers/char/hw_random/pic32-rng.c=76=static int pic32_rng_probe(struct platform_device *pdev)\n--\ndrivers/char/hw_random/pic32-rng.c-88-\ndrivers/char/hw_random/pic32-rng.c:89:\tclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/char/hw_random/pic32-rng.c-90-\tif (IS_ERR(clk))\n--\ndrivers/char/hw_random/st-rng.c=72=static int st_rng_probe(struct platform_device *pdev)\n--\ndrivers/char/hw_random/st-rng.c-86-\ndrivers/char/hw_random/st-rng.c:87:\tclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/char/hw_random/st-rng.c-88-\tif (IS_ERR(clk))\n--\ndrivers/clk/clk-axi-clkgen.c=566=static int axi_clkgen_probe(struct platform_device *pdev)\n--\ndrivers/clk/clk-axi-clkgen.c-591-\ndrivers/clk/clk-axi-clkgen.c:592:\taxi_clk = devm_clk_get_enabled(\u0026pdev-\u003edev, \"s_axi_aclk\");\ndrivers/clk/clk-axi-clkgen.c-593-\tif (!IS_ERR(axi_clk)) {\n--\ndrivers/clk/clk-devres.c=73=EXPORT_SYMBOL_GPL(devm_clk_get_prepared);\ndrivers/clk/clk-devres.c-74-\ndrivers/clk/clk-devres.c:75:struct clk *devm_clk_get_enabled(struct device *dev, const char *id)\ndrivers/clk/clk-devres.c-76-{\n--\ndrivers/clk/clk-devres.c-79-}\ndrivers/clk/clk-devres.c:80:EXPORT_SYMBOL_GPL(devm_clk_get_enabled);\ndrivers/clk/clk-devres.c-81-\n--\ndrivers/clk/clk-lmk04832.c=1399=static int lmk04832_probe(struct spi_device *spi)\n--\ndrivers/clk/clk-lmk04832.c-1415-\ndrivers/clk/clk-lmk04832.c:1416:\tlmk-\u003eoscin = devm_clk_get_enabled(lmk-\u003edev, \"oscin\");\ndrivers/clk/clk-lmk04832.c-1417-\tif (IS_ERR(lmk-\u003eoscin)) {\n--\ndrivers/clk/meson/axg-audio.c=1326=static int axg_audio_clkc_probe(struct platform_device *pdev)\n--\ndrivers/clk/meson/axg-audio.c-1352-\t/* Get the mandatory peripheral clock */\ndrivers/clk/meson/axg-audio.c:1353:\tclk = devm_clk_get_enabled(dev, \"pclk\");\ndrivers/clk/meson/axg-audio.c-1354-\tif (IS_ERR(clk))\n--\ndrivers/clk/qcom/nsscc-qca8k.c=2168=static int nss_cc_qca8k_clock_enable_and_reset(struct device *dev)\n--\ndrivers/clk/qcom/nsscc-qca8k.c-2172-\ndrivers/clk/qcom/nsscc-qca8k.c:2173:\tclk = devm_clk_get_enabled(dev, NULL);\ndrivers/clk/qcom/nsscc-qca8k.c-2174-\tif (IS_ERR(clk))\n--\ndrivers/clk/xilinx/clk-xlnx-clock-wizard.c=1153=static int clk_wzrd_probe(struct platform_device *pdev)\n--\ndrivers/clk/xilinx/clk-xlnx-clock-wizard.c-1174-\ndrivers/clk/xilinx/clk-xlnx-clock-wizard.c:1175:\tclk_wzrd-\u003eaxi_clk = devm_clk_get_enabled(\u0026pdev-\u003edev, \"s_axi_aclk\");\ndrivers/clk/xilinx/clk-xlnx-clock-wizard.c-1176-\tif (IS_ERR(clk_wzrd-\u003eaxi_clk))\n--\ndrivers/clocksource/ingenic-ost.c=66=static int __init ingenic_ost_probe(struct platform_device *pdev)\n--\ndrivers/clocksource/ingenic-ost.c-95-\ndrivers/clocksource/ingenic-ost.c:96:\tost-\u003eclk = devm_clk_get_enabled(dev, \"ost\");\ndrivers/clocksource/ingenic-ost.c-97-\tif (IS_ERR(ost-\u003eclk))\n--\ndrivers/clocksource/timer-nxp-stm.c=389=static int nxp_stm_timer_probe(struct platform_device *pdev)\n--\ndrivers/clocksource/timer-nxp-stm.c-432-\ndrivers/clocksource/timer-nxp-stm.c:433:\tclk = devm_clk_get_enabled(dev, NULL);\ndrivers/clocksource/timer-nxp-stm.c-434-\tif (IS_ERR(clk))\n--\ndrivers/clocksource/timer-sun5i.c=258=static int sun5i_timer_probe(struct platform_device *pdev)\n--\ndrivers/clocksource/timer-sun5i.c-284-\ndrivers/clocksource/timer-sun5i.c:285:\tclk = devm_clk_get_enabled(dev, NULL);\ndrivers/clocksource/timer-sun5i.c-286-\tif (IS_ERR(clk)) {\n--\ndrivers/counter/ti-ecap-capture.c=467=static int ecap_cnt_probe(struct platform_device *pdev)\n--\ndrivers/counter/ti-ecap-capture.c-491-\ndrivers/counter/ti-ecap-capture.c:492:\tecap_dev-\u003eclk = devm_clk_get_enabled(dev, \"fck\");\ndrivers/counter/ti-ecap-capture.c-493-\tif (IS_ERR(ecap_dev-\u003eclk))\n--\ndrivers/counter/ti-eqep.c=494=static int ti_eqep_probe(struct platform_device *pdev)\n--\ndrivers/counter/ti-eqep.c-548-\ndrivers/counter/ti-eqep.c:549:\tclk = devm_clk_get_enabled(dev, NULL);\ndrivers/counter/ti-eqep.c-550-\tif (IS_ERR(clk))\n--\ndrivers/crypto/aspeed/aspeed-acry.c=705=static int aspeed_acry_probe(struct platform_device *pdev)\n--\ndrivers/crypto/aspeed/aspeed-acry.c-739-\ndrivers/crypto/aspeed/aspeed-acry.c:740:\tacry_dev-\u003eclk = devm_clk_get_enabled(dev, NULL);\ndrivers/crypto/aspeed/aspeed-acry.c-741-\tif (IS_ERR(acry_dev-\u003eclk)) {\n--\ndrivers/crypto/img-hash.c=921=static int img_hash_probe(struct platform_device *pdev)\n--\ndrivers/crypto/img-hash.c-974-\ndrivers/crypto/img-hash.c:975:\thdev-\u003ehash_clk = devm_clk_get_enabled(\u0026pdev-\u003edev, \"hash\");\ndrivers/crypto/img-hash.c-976-\tif (IS_ERR(hdev-\u003ehash_clk)) {\n--\ndrivers/crypto/img-hash.c-981-\ndrivers/crypto/img-hash.c:982:\thdev-\u003esys_clk = devm_clk_get_enabled(\u0026pdev-\u003edev, \"sys\");\ndrivers/crypto/img-hash.c-983-\tif (IS_ERR(hdev-\u003esys_clk)) {\n--\ndrivers/crypto/sahara.c=1280=static int sahara_probe(struct platform_device *pdev)\n--\ndrivers/crypto/sahara.c-1311-\t/* clocks */\ndrivers/crypto/sahara.c:1312:\tdev-\u003eclk_ipg = devm_clk_get_enabled(\u0026pdev-\u003edev, \"ipg\");\ndrivers/crypto/sahara.c-1313-\tif (IS_ERR(dev-\u003eclk_ipg))\n--\ndrivers/crypto/sahara.c-1316-\ndrivers/crypto/sahara.c:1317:\tdev-\u003eclk_ahb = devm_clk_get_enabled(\u0026pdev-\u003edev, \"ahb\");\ndrivers/crypto/sahara.c-1318-\tif (IS_ERR(dev-\u003eclk_ahb))\n--\ndrivers/crypto/tegra/tegra-se-main.c=277=static int tegra_se_probe(struct platform_device *pdev)\n--\ndrivers/crypto/tegra/tegra-se-main.c-297-\ndrivers/crypto/tegra/tegra-se-main.c:298:\tse-\u003eclk = devm_clk_get_enabled(se-\u003edev, NULL);\ndrivers/crypto/tegra/tegra-se-main.c-299-\tif (IS_ERR(se-\u003eclk))\n--\ndrivers/devfreq/exynos-bus.c=239=static int exynos_bus_parse_of(struct exynos_bus *bus)\n--\ndrivers/devfreq/exynos-bus.c-246-\t/* Get the clock to provide each bus with source clock */\ndrivers/devfreq/exynos-bus.c:247:\tbus-\u003eclk = devm_clk_get_enabled(dev, \"bus\");\ndrivers/devfreq/exynos-bus.c-248-\tif (IS_ERR(bus-\u003eclk))\n--\ndrivers/devfreq/sun8i-a33-mbus.c=331=static int sun8i_a33_mbus_probe(struct platform_device *pdev)\n--\ndrivers/devfreq/sun8i-a33-mbus.c-362-\ndrivers/devfreq/sun8i-a33-mbus.c:363:\tpriv-\u003eclk_bus = devm_clk_get_enabled(dev, \"bus\");\ndrivers/devfreq/sun8i-a33-mbus.c-364-\tif (IS_ERR(priv-\u003eclk_bus))\n--\ndrivers/dma/dma-axi-dmac.c=1208=static int axi_dmac_probe(struct platform_device *pdev)\n--\ndrivers/dma/dma-axi-dmac.c-1231-\ndrivers/dma/dma-axi-dmac.c:1232:\tclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/dma/dma-axi-dmac.c-1233-\tif (IS_ERR(clk))\n--\ndrivers/dma/fsl-edma-main.c=696=static int fsl_edma_probe(struct platform_device *pdev)\n--\ndrivers/dma/fsl-edma-main.c-737-\tif (drvdata-\u003eflags \u0026 FSL_EDMA_DRV_HAS_DMACLK) {\ndrivers/dma/fsl-edma-main.c:738:\t\tfsl_edma-\u003edmaclk = devm_clk_get_enabled(\u0026pdev-\u003edev, \"dma\");\ndrivers/dma/fsl-edma-main.c-739-\t\tif (IS_ERR(fsl_edma-\u003edmaclk))\n--\ndrivers/dma/fsl-edma-main.c-764-\t\tsprintf(clkname, \"dmamux%d\", i);\ndrivers/dma/fsl-edma-main.c:765:\t\tfsl_edma-\u003emuxclk[i] = devm_clk_get_enabled(\u0026pdev-\u003edev, clkname);\ndrivers/dma/fsl-edma-main.c-766-\t\tif (IS_ERR(fsl_edma-\u003emuxclk[i]))\n--\ndrivers/dma/fsl-edma-main.c-813-\t\t\tsnprintf(clk_name, sizeof(clk_name), \"ch%02d\", i);\ndrivers/dma/fsl-edma-main.c:814:\t\t\tfsl_chan-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev,\ndrivers/dma/fsl-edma-main.c-815-\t\t\t\t\t\t\t (const char *)clk_name);\n--\ndrivers/dma/loongson/loongson2-apb-dma.c=599=static int ls2x_dma_probe(struct platform_device *pdev)\n--\ndrivers/dma/loongson/loongson2-apb-dma.c-614-\ndrivers/dma/loongson/loongson2-apb-dma.c:615:\tpriv-\u003edma_clk = devm_clk_get_enabled(dev, NULL);\ndrivers/dma/loongson/loongson2-apb-dma.c-616-\tif (IS_ERR(priv-\u003edma_clk))\n--\ndrivers/dma/mv_xor_v2.c=714=static int mv_xor_v2_probe(struct platform_device *pdev)\n--\ndrivers/dma/mv_xor_v2.c-745-\ndrivers/dma/mv_xor_v2.c:746:\txor_dev-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/dma/mv_xor_v2.c-747-\tif (IS_ERR(xor_dev-\u003eclk))\n--\ndrivers/dma/ste_dma40.c=3123=static int __init d40_hw_detect_init(struct platform_device *pdev,\n--\ndrivers/dma/ste_dma40.c-3140-\ndrivers/dma/ste_dma40.c:3141:\tclk = devm_clk_get_enabled(dev, NULL);\ndrivers/dma/ste_dma40.c-3142-\tif (IS_ERR(clk))\n--\ndrivers/dma/sun4i-dma.c=1231=static int sun4i_dma_probe(struct platform_device *pdev)\n--\ndrivers/dma/sun4i-dma.c-1251-\ndrivers/dma/sun4i-dma.c:1252:\tpriv-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/dma/sun4i-dma.c-1253-\tif (IS_ERR(priv-\u003eclk))\n--\ndrivers/dpll/sit9531x/core.c=3001=int sit9531x_dev_probe(struct sit9531x_dev *sitdev)\n--\ndrivers/dpll/sit9531x/core.c-3011-\t */\ndrivers/dpll/sit9531x/core.c:3012:\txtal_clk = devm_clk_get_enabled(sitdev-\u003edev, \"xtal\");\ndrivers/dpll/sit9531x/core.c-3013-\tif (IS_ERR(xtal_clk))\n--\ndrivers/gpio/gpio-aspeed.c=1303=static int aspeed_gpio_probe(struct platform_device *pdev)\n--\ndrivers/gpio/gpio-aspeed.c-1325-\ndrivers/gpio/gpio-aspeed.c:1326:\tgpio-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/gpio/gpio-aspeed.c-1327-\tif (IS_ERR(gpio-\u003eclk)) {\n--\ndrivers/gpio/gpio-cadence.c=183=static int cdns_gpio_probe(struct platform_device *pdev)\n--\ndrivers/gpio/gpio-cadence.c-250-\ndrivers/gpio/gpio-cadence.c:251:\tclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/gpio/gpio-cadence.c-252-\tif (IS_ERR(clk)) {\n--\ndrivers/gpio/gpio-davinci.c=462=static int davinci_gpio_irq_setup(struct platform_device *pdev)\n--\ndrivers/gpio/gpio-davinci.c-485-\ndrivers/gpio/gpio-davinci.c:486:\tclk = devm_clk_get_enabled(dev, \"gpio\");\ndrivers/gpio/gpio-davinci.c-487-\tif (IS_ERR(clk)) {\n--\ndrivers/gpio/gpio-ftgpio010.c=236=static int ftgpio_gpio_probe(struct platform_device *pdev)\n--\ndrivers/gpio/gpio-ftgpio010.c-258-\ndrivers/gpio/gpio-ftgpio010.c:259:\tg-\u003eclk = devm_clk_get_enabled(dev, NULL);\ndrivers/gpio/gpio-ftgpio010.c-260-\tif (IS_ERR(g-\u003eclk) \u0026\u0026 PTR_ERR(g-\u003eclk) == -EPROBE_DEFER)\n--\ndrivers/gpio/gpio-lpc18xx.c=336=static int lpc18xx_gpio_probe(struct platform_device *pdev)\n--\ndrivers/gpio/gpio-lpc18xx.c-365-\ndrivers/gpio/gpio-lpc18xx.c:366:\tclk = devm_clk_get_enabled(dev, NULL);\ndrivers/gpio/gpio-lpc18xx.c-367-\tif (IS_ERR(clk)) {\n--\ndrivers/gpio/gpio-mpfs.c=201=static int mpfs_gpio_probe(struct platform_device *pdev)\n--\ndrivers/gpio/gpio-mpfs.c-225-\ndrivers/gpio/gpio-mpfs.c:226:\tclk = devm_clk_get_enabled(dev, NULL);\ndrivers/gpio/gpio-mpfs.c-227-\tif (IS_ERR(clk))\n--\ndrivers/gpio/gpio-pxa.c=610=static int pxa_gpio_probe(struct platform_device *pdev)\n--\ndrivers/gpio/gpio-pxa.c-659-\ndrivers/gpio/gpio-pxa.c:660:\tclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/gpio/gpio-pxa.c-661-\tif (IS_ERR(clk)) {\n--\ndrivers/gpio/gpio-rockchip.c=648=static int rockchip_get_bank_data(struct rockchip_pin_bank *bank)\n--\ndrivers/gpio/gpio-rockchip.c-665-\ndrivers/gpio/gpio-rockchip.c:666:\tbank-\u003eclk = devm_clk_get_enabled(bank-\u003edev, NULL);\ndrivers/gpio/gpio-rockchip.c-667-\tif (IS_ERR(bank-\u003eclk))\n--\ndrivers/gpio/gpio-spacemit-k1.c=269=static int spacemit_gpio_probe(struct platform_device *pdev)\n--\ndrivers/gpio/gpio-spacemit-k1.c-294-\ndrivers/gpio/gpio-spacemit-k1.c:295:\tcore_clk = devm_clk_get_enabled(dev, \"core\");\ndrivers/gpio/gpio-spacemit-k1.c-296-\tif (IS_ERR(core_clk))\n--\ndrivers/gpio/gpio-spacemit-k1.c-298-\ndrivers/gpio/gpio-spacemit-k1.c:299:\tbus_clk = devm_clk_get_enabled(dev, \"bus\");\ndrivers/gpio/gpio-spacemit-k1.c-300-\tif (IS_ERR(bus_clk))\n--\ndrivers/gpio/gpio-stp-xway.c=233=static int xway_stp_probe(struct platform_device *pdev)\n--\ndrivers/gpio/gpio-stp-xway.c-298-\ndrivers/gpio/gpio-stp-xway.c:299:\tclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/gpio/gpio-stp-xway.c-300-\tif (IS_ERR(clk)) {\n--\ndrivers/gpio/gpio-zynq.c=912=static int zynq_gpio_probe(struct platform_device *pdev)\n--\ndrivers/gpio/gpio-zynq.c-953-\t/* Retrieve GPIO clock */\ndrivers/gpio/gpio-zynq.c:954:\tgpio-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/gpio/gpio-zynq.c-955-\tif (IS_ERR(gpio-\u003eclk))\n--\ndrivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c=2261=static int cdns_mhdp_probe(struct platform_device *pdev)\n--\ndrivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c-2274-\ndrivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c:2275:\tclk = devm_clk_get_enabled(dev, NULL);\ndrivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c-2276-\tif (IS_ERR(clk)) {\n--\ndrivers/gpu/drm/bridge/inno-hdmi.c=1064=struct inno_hdmi *inno_hdmi_bind(struct device *dev,\n--\ndrivers/gpu/drm/bridge/inno-hdmi.c-1088-\ndrivers/gpu/drm/bridge/inno-hdmi.c:1089:\thdmi-\u003epclk = devm_clk_get_enabled(hdmi-\u003edev, \"pclk\");\ndrivers/gpu/drm/bridge/inno-hdmi.c-1090-\tif (IS_ERR(hdmi-\u003epclk)) {\n--\ndrivers/gpu/drm/bridge/synopsys/dw-dp.c=1964=struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,\n--\ndrivers/gpu/drm/bridge/synopsys/dw-dp.c-2001-\ndrivers/gpu/drm/bridge/synopsys/dw-dp.c:2002:\tdp-\u003eapb_clk = devm_clk_get_enabled(dev, \"apb\");\ndrivers/gpu/drm/bridge/synopsys/dw-dp.c-2003-\tif (IS_ERR(dp-\u003eapb_clk)) {\n--\ndrivers/gpu/drm/bridge/synopsys/dw-dp.c-2007-\ndrivers/gpu/drm/bridge/synopsys/dw-dp.c:2008:\tdp-\u003eaux_clk = devm_clk_get_enabled(dev, \"aux\");\ndrivers/gpu/drm/bridge/synopsys/dw-dp.c-2009-\tif (IS_ERR(dp-\u003eaux_clk)) {\n--\ndrivers/gpu/drm/bridge/synopsys/dw-hdmi.c=3327=struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,\n--\ndrivers/gpu/drm/bridge/synopsys/dw-hdmi.c-3411-\ndrivers/gpu/drm/bridge/synopsys/dw-hdmi.c:3412:\tclk = devm_clk_get_enabled(hdmi-\u003edev, \"isfr\");\ndrivers/gpu/drm/bridge/synopsys/dw-hdmi.c-3413-\tif (IS_ERR(clk)) {\n--\ndrivers/gpu/drm/bridge/synopsys/dw-hdmi.c-3418-\ndrivers/gpu/drm/bridge/synopsys/dw-hdmi.c:3419:\tclk = devm_clk_get_enabled(hdmi-\u003edev, \"iahb\");\ndrivers/gpu/drm/bridge/synopsys/dw-hdmi.c-3420-\tif (IS_ERR(clk)) {\n--\ndrivers/gpu/drm/bridge/tc358767.c=2456=static int tc_probe(struct i2c_client *client)\n--\ndrivers/gpu/drm/bridge/tc358767.c-2476-\ndrivers/gpu/drm/bridge/tc358767.c:2477:\ttc-\u003erefclk = devm_clk_get_enabled(dev, \"ref\");\ndrivers/gpu/drm/bridge/tc358767.c-2478-\tif (IS_ERR(tc-\u003erefclk))\n--\ndrivers/gpu/drm/bridge/th1520-dw-hdmi.c=105=static int th1520_dw_hdmi_probe(struct platform_device *pdev)\n--\ndrivers/gpu/drm/bridge/th1520-dw-hdmi.c-116-\ndrivers/gpu/drm/bridge/th1520-dw-hdmi.c:117:\thdmi-\u003epixclk = devm_clk_get_enabled(dev, \"pix\");\ndrivers/gpu/drm/bridge/th1520-dw-hdmi.c-118-\tif (IS_ERR(hdmi-\u003epixclk))\n--\ndrivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c=343=static int mtk_hdmi_ddc_v2_probe(struct platform_device *pdev)\n--\ndrivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c-359-\ndrivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c:360:\tddc-\u003eclk = devm_clk_get_enabled(dev, NULL);\ndrivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c-361-\tif (IS_ERR(ddc-\u003eclk))\n--\ndrivers/gpu/drm/meson/meson_dw_mipi_dsi.c=265=static int meson_dw_mipi_dsi_probe(struct platform_device *pdev)\n--\ndrivers/gpu/drm/meson/meson_dw_mipi_dsi.c-282-\ndrivers/gpu/drm/meson/meson_dw_mipi_dsi.c:283:\tmipi_dsi-\u003ebit_clk = devm_clk_get_enabled(dev, \"bit\");\ndrivers/gpu/drm/meson/meson_dw_mipi_dsi.c-284-\tif (IS_ERR(mipi_dsi-\u003ebit_clk)) {\n--\ndrivers/gpu/drm/meson/meson_dw_mipi_dsi.c-293-\ndrivers/gpu/drm/meson/meson_dw_mipi_dsi.c:294:\tmipi_dsi-\u003epx_clk = devm_clk_get_enabled(dev, \"px\");\ndrivers/gpu/drm/meson/meson_dw_mipi_dsi.c-295-\tif (IS_ERR(mipi_dsi-\u003epx_clk))\n--\ndrivers/gpu/drm/sun4i/sun4i_tcon.c=784=static int sun4i_tcon_init_clocks(struct device *dev,\n--\ndrivers/gpu/drm/sun4i/sun4i_tcon.c-786-{\ndrivers/gpu/drm/sun4i/sun4i_tcon.c:787:\ttcon-\u003eclk = devm_clk_get_enabled(dev, \"ahb\");\ndrivers/gpu/drm/sun4i/sun4i_tcon.c-788-\tif (IS_ERR(tcon-\u003eclk)) {\n--\ndrivers/gpu/drm/sun4i/sun4i_tcon.c-793-\tif (tcon-\u003equirks-\u003ehas_channel_0) {\ndrivers/gpu/drm/sun4i/sun4i_tcon.c:794:\t\ttcon-\u003esclk0 = devm_clk_get_enabled(dev, \"tcon-ch0\");\ndrivers/gpu/drm/sun4i/sun4i_tcon.c-795-\t\tif (IS_ERR(tcon-\u003esclk0)) {\n--\ndrivers/gpu/drm/verisilicon/vs_dc.c=43=static int vs_dc_probe(struct platform_device *pdev)\n--\ndrivers/gpu/drm/verisilicon/vs_dc.c-88-\ndrivers/gpu/drm/verisilicon/vs_dc.c:89:\tdc-\u003ecore_clk = devm_clk_get_enabled(dev, \"core\");\ndrivers/gpu/drm/verisilicon/vs_dc.c-90-\tif (IS_ERR(dc-\u003ecore_clk)) {\n--\ndrivers/gpu/drm/verisilicon/vs_dc.c-94-\ndrivers/gpu/drm/verisilicon/vs_dc.c:95:\tdc-\u003eaxi_clk = devm_clk_get_enabled(dev, \"axi\");\ndrivers/gpu/drm/verisilicon/vs_dc.c-96-\tif (IS_ERR(dc-\u003eaxi_clk)) {\n--\ndrivers/gpu/drm/verisilicon/vs_dc.c-100-\ndrivers/gpu/drm/verisilicon/vs_dc.c:101:\tdc-\u003eahb_clk = devm_clk_get_enabled(dev, \"ahb\");\ndrivers/gpu/drm/verisilicon/vs_dc.c-102-\tif (IS_ERR(dc-\u003eahb_clk)) {\n--\ndrivers/hwmon/aspeed-g6-pwm-tach.c=458=static int aspeed_pwm_tach_probe(struct platform_device *pdev)\n--\ndrivers/hwmon/aspeed-g6-pwm-tach.c-472-\ndrivers/hwmon/aspeed-g6-pwm-tach.c:473:\tpriv-\u003eclk = devm_clk_get_enabled(dev, NULL);\ndrivers/hwmon/aspeed-g6-pwm-tach.c-474-\tif (IS_ERR(priv-\u003eclk))\n--\ndrivers/hwmon/axi-fan-control.c=446=static int axi_fan_control_probe(struct platform_device *pdev)\n--\ndrivers/hwmon/axi-fan-control.c-466-\ndrivers/hwmon/axi-fan-control.c:467:\tclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/hwmon/axi-fan-control.c-468-\tif (IS_ERR(clk))\n--\ndrivers/hwmon/lan966x-hwmon.c=336=static int lan966x_hwmon_probe(struct platform_device *pdev)\n--\ndrivers/hwmon/lan966x-hwmon.c-346-\ndrivers/hwmon/lan966x-hwmon.c:347:\thwmon-\u003eclk = devm_clk_get_enabled(dev, NULL);\ndrivers/hwmon/lan966x-hwmon.c-348-\tif (IS_ERR(hwmon-\u003eclk))\n--\ndrivers/hwmon/mr75203.c=765=static int mr75203_probe(struct platform_device *pdev)\n--\ndrivers/hwmon/mr75203.c-782-\ndrivers/hwmon/mr75203.c:783:\tpvt-\u003eclk = devm_clk_get_enabled(dev, NULL);\ndrivers/hwmon/mr75203.c-784-\tif (IS_ERR(pvt-\u003eclk))\n--\ndrivers/hwmon/sparx5-temp.c=104=static int s5_temp_probe(struct platform_device *pdev)\n--\ndrivers/hwmon/sparx5-temp.c-116-\ndrivers/hwmon/sparx5-temp.c:117:\thwmon-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/hwmon/sparx5-temp.c-118-\tif (IS_ERR(hwmon-\u003eclk))\n--\ndrivers/i2c/busses/i2c-at91-core.c=196=static int at91_twi_probe(struct platform_device *pdev)\n--\ndrivers/i2c/busses/i2c-at91-core.c-223-\ndrivers/i2c/busses/i2c-at91-core.c:224:\tdev-\u003eclk = devm_clk_get_enabled(dev-\u003edev, NULL);\ndrivers/i2c/busses/i2c-at91-core.c-225-\tif (IS_ERR(dev-\u003eclk))\n--\ndrivers/i2c/busses/i2c-cadence.c=1493=static int cdns_i2c_probe(struct platform_device *pdev)\n--\ndrivers/i2c/busses/i2c-cadence.c-1542-\ndrivers/i2c/busses/i2c-cadence.c:1543:\tid-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/i2c/busses/i2c-cadence.c-1544-\tif (IS_ERR(id-\u003eclk))\n--\ndrivers/i2c/busses/i2c-emev2.c=360=static int em_i2c_probe(struct platform_device *pdev)\n--\ndrivers/i2c/busses/i2c-emev2.c-375-\ndrivers/i2c/busses/i2c-emev2.c:376:\tsclk = devm_clk_get_enabled(\u0026pdev-\u003edev, \"sclk\");\ndrivers/i2c/busses/i2c-emev2.c-377-\tif (IS_ERR(sclk))\n--\ndrivers/i2c/busses/i2c-hix5hd2.c=390=static int hix5hd2_i2c_probe(struct platform_device *pdev)\n--\ndrivers/i2c/busses/i2c-hix5hd2.c-421-\ndrivers/i2c/busses/i2c-hix5hd2.c:422:\tpriv-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/i2c/busses/i2c-hix5hd2.c-423-\tif (IS_ERR(priv-\u003eclk)) {\n--\ndrivers/i2c/busses/i2c-imx.c=1744=static int i2c_imx_probe(struct platform_device *pdev)\n--\ndrivers/i2c/busses/i2c-imx.c-1788-\t/* Get I2C clock */\ndrivers/i2c/busses/i2c-imx.c:1789:\ti2c_imx-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/i2c/busses/i2c-imx.c-1790-\tif (IS_ERR(i2c_imx-\u003eclk))\n--\ndrivers/i2c/busses/i2c-jz4780.c=762=static int jz4780_i2c_probe(struct platform_device *pdev)\n--\ndrivers/i2c/busses/i2c-jz4780.c-795-\ndrivers/i2c/busses/i2c-jz4780.c:796:\ti2c-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/i2c/busses/i2c-jz4780.c-797-\tif (IS_ERR(i2c-\u003eclk))\n--\ndrivers/i2c/busses/i2c-k1.c=688=static int spacemit_i2c_probe(struct platform_device *pdev)\n--\ndrivers/i2c/busses/i2c-k1.c-725-\ndrivers/i2c/busses/i2c-k1.c:726:\tclk = devm_clk_get_enabled(dev, \"func\");\ndrivers/i2c/busses/i2c-k1.c-727-\tif (IS_ERR(clk))\n--\ndrivers/i2c/busses/i2c-k1.c-729-\ndrivers/i2c/busses/i2c-k1.c:730:\tclk = devm_clk_get_enabled(dev, \"bus\");\ndrivers/i2c/busses/i2c-k1.c-731-\tif (IS_ERR(clk))\n--\ndrivers/i2c/busses/i2c-lpc2k.c=345=static int i2c_lpc2k_probe(struct platform_device *pdev)\n--\ndrivers/i2c/busses/i2c-lpc2k.c-366-\ndrivers/i2c/busses/i2c-lpc2k.c:367:\ti2c-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/i2c/busses/i2c-lpc2k.c-368-\tif (IS_ERR(i2c-\u003eclk)) {\n--\ndrivers/i2c/busses/i2c-ls2x-v2.c=475=static int loongson2_i2c_probe(struct platform_device *pdev)\n--\ndrivers/i2c/busses/i2c-ls2x-v2.c-494-\ndrivers/i2c/busses/i2c-ls2x-v2.c:495:\tpriv-\u003eclk = devm_clk_get_enabled(dev, NULL);\ndrivers/i2c/busses/i2c-ls2x-v2.c-496-\tif (IS_ERR(priv-\u003eclk))\n--\ndrivers/i2c/busses/i2c-mt7621.c=268=static int mtk_i2c_probe(struct platform_device *pdev)\n--\ndrivers/i2c/busses/i2c-mt7621.c-281-\ndrivers/i2c/busses/i2c-mt7621.c:282:\ti2c-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/i2c/busses/i2c-mt7621.c-283-\tif (IS_ERR(i2c-\u003eclk)) {\n--\ndrivers/i2c/busses/i2c-nomadik.c=1082=static int nmk_i2c_probe(struct amba_device *adev, const struct amba_id *id)\n--\ndrivers/i2c/busses/i2c-nomadik.c-1142-\ndrivers/i2c/busses/i2c-nomadik.c:1143:\tpriv-\u003eclk = devm_clk_get_enabled(dev, NULL);\ndrivers/i2c/busses/i2c-nomadik.c-1144-\tif (IS_ERR(priv-\u003eclk))\n--\ndrivers/i2c/busses/i2c-owl.c=436=static int owl_i2c_probe(struct platform_device *pdev)\n--\ndrivers/i2c/busses/i2c-owl.c-464-\ndrivers/i2c/busses/i2c-owl.c:465:\ti2c_dev-\u003eclk = devm_clk_get_enabled(dev, NULL);\ndrivers/i2c/busses/i2c-owl.c-466-\tif (IS_ERR(i2c_dev-\u003eclk)) {\n--\ndrivers/i2c/busses/i2c-pasemi-platform.c=45=static int pasemi_platform_i2c_probe(struct platform_device *pdev)\n--\ndrivers/i2c/busses/i2c-pasemi-platform.c-68-\ndrivers/i2c/busses/i2c-pasemi-platform.c:69:\tdata-\u003eclk_ref = devm_clk_get_enabled(dev, NULL);\ndrivers/i2c/busses/i2c-pasemi-platform.c-70-\tif (IS_ERR(data-\u003eclk_ref))\n--\ndrivers/i2c/busses/i2c-stm32f4.c=752=static int stm32f4_i2c_probe(struct platform_device *pdev)\n--\ndrivers/i2c/busses/i2c-stm32f4.c-779-\ndrivers/i2c/busses/i2c-stm32f4.c:780:\ti2c_dev-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/i2c/busses/i2c-stm32f4.c-781-\tif (IS_ERR(i2c_dev-\u003eclk))\n--\ndrivers/i2c/busses/i2c-stm32f7.c=2154=static int stm32f7_i2c_probe(struct platform_device *pdev)\n--\ndrivers/i2c/busses/i2c-stm32f7.c-2184-\ndrivers/i2c/busses/i2c-stm32f7.c:2185:\ti2c_dev-\u003eclk = devm_clk_get_enabled(\u0026pdev-\u003edev, NULL);\ndrivers/i2c/busses/i2c-stm32f7.c-2186-\tif (IS_ERR(i2c_dev-\u003eclk))\n--\ndrivers/i2c/busses/i2c-sun6i-p2wi.c=183=static int p2wi_probe(struct platform_device *pdev)\n--\ndrivers/i2c/busses/i2c-sun6i-p2wi.c-238-\ndrivers/i2c/busses/i2c-sun6i-p2wi.c:239:\tp2wi-\u003eclk = devm_clk_get_enabled(dev, NULL);\ndrivers/i2c/busses/i2c-sun6i-p2wi.c-240-\tif (IS_ERR(p2wi-\u003eclk))\n\n"
} |
| 5/2 | 2026/08/12 19:58 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 93886 output: 18 thoughts: 452 Thoughts: **Clock Retrieval on I2C Devices in Virtualized Environments** Okay, so `devm_clk_get_enabled` is returning `-ENOENT`. That makes sense; if the clock isn't present, it's going to fail. Now, the real issue is that I2C devices can be created in various ways, like through a board file or another driver using `i2c_new_client_device`. In those cases, the clock might be entirely absent. I thought maybe `CONFIG_COMMON_CLK_DUMMY` or something similar might solve it, but there isn't a generic dummy clock that would help. The interesting angle is Syzkaller. It can inject I2C devices, and the question is, *does* Syzkaller inject them with clocks? Then I re-read the prompt and it's pointing out this problem is difficult to reproduce in virtualized environments. Is this truly an unreachable code path? If I use `i2c-mock` or `i2c-stub`, I can create a device, but the probe will still fail because there's no "xtal" clock attached to the device. Can I attach a clock to an I2C device created via sysfs? I don't think so. Now, the Syzkaller solution! Can I use a device tree overlay in Syzkaller to define a clock? That's the key. Syzkaller has pseudo-syscalls like `syz_init_net_socket`, `syz_mount_image`, and can load device tree overlays if `CONFIG_OF_OVERLAY` is enabled. Syzkaller doesn't have a generic syscall for DT overlays, but there are other options like `syz_genetlink`. Wait, I need to check the prompt! The prompt says I can't search for "syz_" directly because those are Syzkaller-specific pseudo-syscalls. So I need to use `read-syz-spec` and `syz-grepper` instead, but I can use Syzkaller to load DT overlays! Okay, let me confirm if Syzkaller *can* load DT overlays. |
| 6/2 | 2026/08/12 19:59 | tool | syz-grepper |
0mError:tool "syz-grepper" does not exist, please correct the name Args: {
"Expression": "overlay"
}Results: {
"error": "tool \"syz-grepper\" does not exist, please correct the name"
} |
| 7/2 | 2026/08/12 19:59 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 94847 output: 28 thoughts: 509 Thoughts: **Reaching the Probe: A Thought Process** Okay, so I need to figure out if this driver's probe function is reachable. The text mentions `syz-grepper`, but I quickly realize that's not actually a tool I have available. Instead, I need to focus on just *determining reachability*. This isn't a Syzkaller debugging task. The prompt highlights a potential scenario with a `devm_clk_get_enabled(..., "xtal")` call. This immediately flags a dependency on a clock named "xtal". My first thought is: can a user-space process register a clock lookup? The answer is no, this is impossible. Then: Can this I2C driver be bound to a platform device that *does* have a clock? No, it's an I2C driver. So, I switch gears. The context mentions virtualized environments like GCE or QEMU. Can I emulate a board with this specific I2C device? QEMU can emulate boards, but likely not this particular device because it's I2C. I briefly consider Syzkaller's device-adding capabilities, but that's not the correct path here. Then I think: can the driver be instantiated via `i2c_new_client_device` from *another* reachable driver, like a USB I2C adapter? That's possible, but the adapter wouldn't be able to provide the clock. Then I recall the existence of potentially helpful 'dummy' clocks like `clk-dummy` or `clk-mock`. Could one of those be used, or is one of those named "xtal"? The critical point here is: how does the system find a clock named "xtal"? The function `devm_clk_get_enabled` looks up the clock by name. If not found, it returns an error. Finally, I remember a more fundamental consideration: what if `CONFIG_HAVE_CLK` isn't enabled? If it's *not* enabled, `devm_clk_get_enabled` won't return an error, but rather return `NULL` to indicate a dummy clock and `clk_get_rate` will return 0. This is the key. Now I will check `include/linux/clk.h` to confirm the behavior. |
| 8/2 | 2026/08/12 19:59 | tool | grepper |
0mArgs:{
"Expression": "static inline struct clk \\*devm_clk_get_enabled"
}Results: {
"Output": "include/linux/clk.h=1006=static inline struct clk *devm_clk_get_prepared(struct device *dev,\n--\ninclude/linux/clk.h-1011-\ninclude/linux/clk.h:1012:static inline struct clk *devm_clk_get_enabled(struct device *dev,\ninclude/linux/clk.h-1013-\t\t\t\t\t const char *id)\n"
} |
| 9/2 | 2026/08/12 19:59 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 95997 output: 36 thoughts: 21 Thoughts: Here's a first-person summary, tailored for an expert audience, as requested: **Clock Framework Deep Dive** Okay, time to dig into the heart of the clock framework. Specifically, I need to revisit some of the core definitions and structures, so let's jump straight to `include/linux/clk.h` and take a look around line 1012. I'm focusing on the low-level details, expecting to find the nitty-gritty of how clock sources, parent clocks, rate changes, and enable/disable operations are handled. I'm particularly interested in seeing how they've structured the data in relation to how it's used. I'm thinking I might find some of the critical `struct clk` member definitions, perhaps the definitions for clock provider or consumer interfaces. This line number should put me right around the heart of the core clock operations structures. Hopefully, it's well-commented because I'm looking for the canonical definition and use pattern so I can compare it against other implementations. I'm also preparing myself to spot some of the common pitfalls in clock management that I've seen in other projects. I hope this spot is a good place to start. Let's see what's there... |
| 10/2 | 2026/08/12 19:59 | tool | read-file |
0mArgs:{
"File": "include/linux/clk.h",
"FirstLine": 1000,
"LineCount": 30
}Results: {
"Contents": "1000:\t\n1001:\tstatic inline struct clk *devm_clk_get(struct device *dev, const char *id)\n1002:\t{\n1003:\t\treturn NULL;\n1004:\t}\n1005:\t\n1006:\tstatic inline struct clk *devm_clk_get_prepared(struct device *dev,\n1007:\t\t\t\t\t\t\tconst char *id)\n1008:\t{\n1009:\t\treturn NULL;\n1010:\t}\n1011:\t\n1012:\tstatic inline struct clk *devm_clk_get_enabled(struct device *dev,\n1013:\t\t\t\t\t\t const char *id)\n1014:\t{\n1015:\t\treturn NULL;\n1016:\t}\n1017:\t\n1018:\tstatic inline struct clk *devm_clk_get_optional(struct device *dev,\n1019:\t\t\t\t\t\t\tconst char *id)\n1020:\t{\n1021:\t\treturn NULL;\n1022:\t}\n1023:\t\n1024:\tstatic inline struct clk *devm_clk_get_optional_prepared(struct device *dev,\n1025:\t\t\t\t\t\t\t\t const char *id)\n1026:\t{\n1027:\t\treturn NULL;\n1028:\t}\n1029:\t\n"
} |
| 11/2 | 2026/08/12 19:59 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 96693 output: 25 thoughts: 1233 Thoughts: **Analyzing the Sit9531x DPLL Driver's Fuzzability** Okay, so I'm digging into this `sit9531x` DPLL driver and its potential for fuzzing with syzkaller. The initial issue jumps out: it's using `devm_clk_get_enabled` to acquire an "xtal" clock. If `CONFIG_HAVE_CLK` is disabled, this returns `NULL`, which, while not a direct error, leads to a subsequent `clk_get_rate(NULL)` call, which returns 0. That causes a -EINVAL, causing a probe failure. So, no clock support equals no driver. Even if `CONFIG_HAVE_CLK` is enabled, the probe likely fails if there's no clock named "xtal" in the device tree, with -ENOENT. Sysfs-based device creation (`/sys/bus/i2c/devices/i2c-X/new_device`) is the standard way syzkaller interacts with I2C, but it doesn't provide any mechanism to supply clock information. So, no sysfs, no probe. The only other way I can think of is if the driver is instantiated via a device tree overlay, but that won't work either. I know that syzkaller has added support for device tree overlays in *some* contexts, but I doubt this particular situation would be handled. There's no global dummy clock mechanism for this specific situation, and I don't see any obvious way a `clk_register_clkdev` call could be triggered to create the necessary "xtal" clock. So, the immediate problem is this: If the driver doesn't probe, syzkaller can't fuzz it. I need to figure out if syzkaller can trigger a probe. This driver, being for a DPLL, registers a DPLL device via `dpll_device_register`. And the DPLL subsystem is accessible via netlink (`GENL_ID_DPLL`). If it *can* be probed, syzkaller has a clear netlink interface to fuzz. Looking at the Kconfig, this driver depends on I2C and NET. So it's tied to I2C. Syzkaller generally fuzzes I2C drivers by writing to `/sys/bus/i2c/devices/i2c-X/new_device`. But as established, no clocks are provided that way. The problem comes down to the mandatory clock dependency via `devm_clk_get_enabled`. I need to check if there's any mechanism syzkaller could leverage to inject a clock, short of a full DT overlay mechanism. No `i2c-stub` or `i2c-mock` I2C drivers provide clocks. My next step is to look for how *other* I2C drivers are fuzzed. Many I2C drivers rely on `devm_clk_get_optional`, which would be great because it's non-fatal if the clock isn't present. Here, though, it's `devm_clk_get_enabled`. And I'm checking if CONFIG_OF is available, but that's a dead end because syzkaller doesn't have a generic DT overlay pseudo-syscall. Also, CONFIG_I2C_STUB doesn't provide clocks. If this driver is impossible to probe, maybe it's not worth fuzzing. The key is to find out if `devm_clk_get_enabled` is EVER successful in any scenario. Because otherwise, this driver is unreachable. If it doesn't succeed by default, or with some specific driver features, it's unlikely that the driver is fuzzed. The only possible remaining thing is some sort of global `xtal` clock that is somehow registered, but I don't see it. The fact that the architecture is `x86` is not very important here since clocks are handled through configuration. |
| 12/2 | 2026/08/12 19:59 | tool | grepper |
0mError:no matches Args: {
"Expression": "clk_register_clkdev.*\"xtal\""
}Results: {
"error": "no matches"
} |
| 13/2 | 2026/08/12 19:59 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 98824 output: 26 thoughts: 17 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Checking for Global "xtal" Registrations** Alright, let's get down to it. My initial thought is to quickly verify if there's any global registration – probably in some kind of naming service, configuration store, or a system-wide registry – for a component or object identified as "xtal." I'm mentally parsing the possibilities. I'm thinking about the common pitfalls of name collisions and unexpected overrides. Given my experience, I'd anticipate the code to have been well-structured to avoid these. I need to know if I'm stepping on someone else's toes or if "xtal" is already a reserved or used term in this context. It's a quick sanity check before I proceed, particularly if I'm about to work with or implement something new related to it. |
| 14/2 | 2026/08/12 19:59 | tool | grepper |
0mError:no matches Args: {
"Expression": "clk_register_fixed_rate.*\"xtal\""
}Results: {
"error": "no matches"
} |
| 15/2 | 2026/08/12 19:59 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 99069 output: 23 thoughts: 260 Thoughts: **My Analysis of the Driver Probe Failure** Okay, so I'm digging into this probe failure, and it's looking like it's all about how the clock is being acquired. Let's see... the `devm_clk_get` function is key here. If there's no device tree node for the clock, it falls back to `clk_get_sys`. In this particular scenario, the `dev_id` is something like `0-0068` – a specific I2C device address – and the `con_id` is "xtal". The thing is, in a standard x86 QEMU environment, there's absolutely no way that a clock registered with those parameters exists. No such "xtal" clock will be registered in this generic x86 QEMU VM. Therefore, when the driver calls `devm_clk_get_enabled`, it's going to hit a `-ENOENT` error. That means the probe is going to fail right at the beginning. As a consequence, the DPLL registration and all of the netlink callbacks will never even get a chance to execute. That’s a bit of a problem. Now, a thought occurs... Is there any other path the driver can be probed? I have to consider other scenarios. What if this driver is compiled for a specific ARM board that happens to have a clock called "xtal"? In that case, even if an "xtal" clock exists, `clk_get_sys` matches by both `dev_id` and `con_id`. If `dev_id` is NULL, it would match with any device. So, maybe there's a global "xtal" clock with `dev_id == NULL`? I should check if there is such a clock defined, just to be sure. |
| 16/2 | 2026/08/12 20:00 | tool | grepper |
0mError:no matches Args: {
"Expression": "clkdev_create.*\"xtal\""
}Results: {
"error": "no matches"
} |
| 17/2 | 2026/08/12 20:00 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 99755 output: 22 thoughts: 20 Thoughts: Here's a summary of my thought process: **Diving into `clk_register_clkdev` with a `NULL` dev_id** Okay, so I'm thinking about the implications of passing a `NULL` `dev_id` to the `clk_register_clkdev` function. This is a common situation for me, I understand this code very well. I immediately want to understand how the driver handles this edge case. What are the expected behaviors? Does it check for the `NULL` explicitly? And if so, how? Where? The core question driving my thought process is: what happens when a clock device is registered without an associated device identifier? Does this cause a crash? Does it result in an unexpected behavior that may lead to complications later? Does the code even allow for such a condition? I need to analyze the code to check and see what the expected behaviour is. I know from experience that kernel drivers often rely on the device ID for initialization or for associating clock operations with specific hardware instances. Therefore, a `NULL` `dev_id` seems like a potential source of trouble, or at the very least, a situation that needs careful examination. My next step will be to dive deeper into the code to determine what the function actually does, and specifically, how it handles the parameter, if at all. |
| 18/2 | 2026/08/12 20:00 | tool | grepper |
0mArgs:{
"Expression": "clk_register_clkdev.*NULL"
}Results: {
"Output": "\nFull output is too long, showing 500 out of 2042 lines.\nUse more precise expression if possible.\n\narch/arm/mach-versatile/spc.c=545=static int __init ve_spc_clk_init(void)\n--\narch/arm/mach-versatile/spc.c-569-\t\t}\narch/arm/mach-versatile/spc.c:570:\t\tif (clk_register_clkdev(clk, NULL, dev_name(cpu_dev))) {\narch/arm/mach-versatile/spc.c-571-\t\t\tpr_warn(\"failed to register cpu%d clock lookup\\n\", cpu);\n--\narch/mips/alchemy/common/clock.c=244=static struct clk __init *alchemy_clk_setup_aux(const char *parent_name,\n--\narch/mips/alchemy/common/clock.c-267-\tif (!IS_ERR(c))\narch/mips/alchemy/common/clock.c:268:\t\tclk_register_clkdev(c, name, NULL);\narch/mips/alchemy/common/clock.c-269-\telse\n--\narch/mips/alchemy/common/clock.c=277=static struct clk __init *alchemy_clk_setup_sysbus(const char *pn)\n--\narch/mips/alchemy/common/clock.c-284-\tif (!IS_ERR(c))\narch/mips/alchemy/common/clock.c:285:\t\tclk_register_clkdev(c, ALCHEMY_SYSBUS_CLK, NULL);\narch/mips/alchemy/common/clock.c-286-\treturn c;\n--\narch/mips/alchemy/common/clock.c=291=static struct clk __init *alchemy_clk_setup_periph(const char *pn)\n--\narch/mips/alchemy/common/clock.c-298-\tif (!IS_ERR(c))\narch/mips/alchemy/common/clock.c:299:\t\tclk_register_clkdev(c, ALCHEMY_PERIPH_CLK, NULL);\narch/mips/alchemy/common/clock.c-300-\treturn c;\n--\narch/mips/alchemy/common/clock.c=305=static struct clk __init *alchemy_clk_setup_mem(const char *pn, int ct)\n--\narch/mips/alchemy/common/clock.c-332-\tif (!IS_ERR(c))\narch/mips/alchemy/common/clock.c:333:\t\tclk_register_clkdev(c, ALCHEMY_MEM_CLK, NULL);\narch/mips/alchemy/common/clock.c-334-\treturn c;\n--\narch/mips/alchemy/common/clock.c=339=static struct clk __init *alchemy_clk_setup_lrclk(const char *pn, int t)\n--\narch/mips/alchemy/common/clock.c-361-\tif (!IS_ERR(c))\narch/mips/alchemy/common/clock.c:362:\t\tclk_register_clkdev(c, ALCHEMY_LR_CLK, NULL);\narch/mips/alchemy/common/clock.c-363-\treturn c;\n--\narch/mips/alchemy/common/clock.c=754=static int __init alchemy_clk_init_fgens(int ctype)\n--\narch/mips/alchemy/common/clock.c-813-\t\telse\narch/mips/alchemy/common/clock.c:814:\t\t\tclk_register_clkdev(c, id.name, NULL);\narch/mips/alchemy/common/clock.c-815-\t\ta++;\n--\narch/mips/alchemy/common/clock.c=960=static int __init alchemy_clk_setup_imux(int ctype)\n--\narch/mips/alchemy/common/clock.c-1030-\t\telse\narch/mips/alchemy/common/clock.c:1031:\t\t\tclk_register_clkdev(c, id.name, NULL);\narch/mips/alchemy/common/clock.c-1032-next:\n--\narch/mips/ath79/clock.c=51=static void __init __ath79_set_clk(int type, const char *name, struct clk *clk)\n--\narch/mips/ath79/clock.c-56-\tclks[type] = clk;\narch/mips/ath79/clock.c:57:\tclk_register_clkdev(clk, name, NULL);\narch/mips/ath79/clock.c-58-}\n--\narch/powerpc/platforms/512x/clock-commonclk.c=1014=static void __init mpc5121_clk_provide_backwards_compat(void)\n--\narch/powerpc/platforms/512x/clock-commonclk.c-1068-\tif (did_register \u0026 DID_REG_CAN) {\narch/powerpc/platforms/512x/clock-commonclk.c:1069:\t\tclk_register_clkdev(clks[MPC512x_CLK_IPS], \"ips\", NULL);\narch/powerpc/platforms/512x/clock-commonclk.c:1070:\t\tclk_register_clkdev(clks[MPC512x_CLK_SYS], \"sys\", NULL);\narch/powerpc/platforms/512x/clock-commonclk.c:1071:\t\tclk_register_clkdev(clks[MPC512x_CLK_REF], \"ref\", NULL);\narch/powerpc/platforms/512x/clock-commonclk.c-1072-\t}\n--\ndrivers/acpi/acpi_apd.c=46=static int acpi_apd_setup(struct apd_private_data *pdata)\n--\ndrivers/acpi/acpi_apd.c-54-\t\t\t\t\tNULL, 0, dev_desc-\u003efixed_clk_rate);\ndrivers/acpi/acpi_apd.c:55:\t\tclk_register_clkdev(clk, NULL, dev_name(\u0026pdata-\u003eadev-\u003edev));\ndrivers/acpi/acpi_apd.c-56-\t\tpdata-\u003eclk = clk;\n--\ndrivers/acpi/arm64/amba.c=28=static void amba_register_dummy_clk(void)\n--\ndrivers/acpi/arm64/amba.c-32-\tamba_dummy_clk = clk_register_fixed_rate(NULL, \"apb_pclk\", NULL, 0, 0);\ndrivers/acpi/arm64/amba.c:33:\tclk_register_clkdev(amba_dummy_clk, \"apb_pclk\", NULL);\ndrivers/acpi/arm64/amba.c-34-}\n--\ndrivers/clk/clk-qoriq.c=1210=static void __init create_one_pll(struct clockgen *cg, int idx)\n--\ndrivers/clk/clk-qoriq.c-1293-\t\tpll-\u003ediv[i].clk = clk;\ndrivers/clk/clk-qoriq.c:1294:\t\tret = clk_register_clkdev(clk, pll-\u003ediv[i].name, NULL);\ndrivers/clk/clk-qoriq.c-1295-\t\tif (ret != 0)\n--\ndrivers/clk/clk-xgene.c=170=static void xgene_pllclk_init(struct device_node *np, enum xgene_pll_type pll_type)\n--\ndrivers/clk/clk-xgene.c-188-\t\tof_clk_add_provider(np, of_clk_src_simple_get, clk);\ndrivers/clk/clk-xgene.c:189:\t\tclk_register_clkdev(clk, clk_name, NULL);\ndrivers/clk/clk-xgene.c-190-\t\tpr_debug(\"Add %s clock PLL\\n\", clk_name);\n--\ndrivers/clk/clk-xgene.c=385=static void xgene_pmdclk_init(struct device_node *np)\n--\ndrivers/clk/clk-xgene.c-421-\t\tof_clk_add_provider(np, of_clk_src_simple_get, clk);\ndrivers/clk/clk-xgene.c:422:\t\tclk_register_clkdev(clk, clk_name, NULL);\ndrivers/clk/clk-xgene.c-423-\t\tpr_debug(\"Add %s clock\\n\", clk_name);\n--\ndrivers/clk/clk-xgene.c=633=static struct clk *xgene_register_clk(struct device *dev,\n--\ndrivers/clk/clk-xgene.c-665-\t/* Register the clock for lookup */\ndrivers/clk/clk-xgene.c:666:\trc = clk_register_clkdev(clk, name, NULL);\ndrivers/clk/clk-xgene.c-667-\tif (rc != 0) {\n--\ndrivers/clk/clkdev.c=319=static int do_clk_register_clkdev(struct clk_hw *hw,\n--\ndrivers/clk/clkdev.c-330-\telse\ndrivers/clk/clkdev.c:331:\t\t*cl = __clk_register_clkdev(hw, con_id, NULL);\ndrivers/clk/clkdev.c-332-\n--\ndrivers/clk/davinci/pll-da850.c=89=int da850_pll0_init(struct device *dev, void __iomem *base, struct regmap *cfgchip)\n--\ndrivers/clk/davinci/pll-da850.c-121-\ndrivers/clk/davinci/pll-da850.c:122:\tclk_register_clkdev(clk, NULL, \"i2c_davinci.1\");\ndrivers/clk/davinci/pll-da850.c:123:\tclk_register_clkdev(clk, \"timer0\", NULL);\ndrivers/clk/davinci/pll-da850.c:124:\tclk_register_clkdev(clk, NULL, \"davinci-wdt\");\ndrivers/clk/davinci/pll-da850.c-125-\n--\ndrivers/clk/hisilicon/clk.c=152=int hisi_clk_register_mux(const struct hisi_mux_clock *clks,\n--\ndrivers/clk/hisilicon/clk.c-174-\t\tif (clks[i].alias)\ndrivers/clk/hisilicon/clk.c:175:\t\t\tclk_register_clkdev(clk, clks[i].alias, NULL);\ndrivers/clk/hisilicon/clk.c-176-\n--\ndrivers/clk/hisilicon/clk.c=214=int hisi_clk_register_divider(const struct hisi_divider_clock *clks,\n--\ndrivers/clk/hisilicon/clk.c-236-\t\tif (clks[i].alias)\ndrivers/clk/hisilicon/clk.c:237:\t\t\tclk_register_clkdev(clk, clks[i].alias, NULL);\ndrivers/clk/hisilicon/clk.c-238-\n--\ndrivers/clk/hisilicon/clk.c=252=int hisi_clk_register_gate(const struct hisi_gate_clock *clks,\n--\ndrivers/clk/hisilicon/clk.c-273-\t\tif (clks[i].alias)\ndrivers/clk/hisilicon/clk.c:274:\t\t\tclk_register_clkdev(clk, clks[i].alias, NULL);\ndrivers/clk/hisilicon/clk.c-275-\n--\ndrivers/clk/hisilicon/clk.c=289=void hisi_clk_register_gate_sep(const struct hisi_gate_clock *clks,\n--\ndrivers/clk/hisilicon/clk.c-310-\t\tif (clks[i].alias)\ndrivers/clk/hisilicon/clk.c:311:\t\t\tclk_register_clkdev(clk, clks[i].alias, NULL);\ndrivers/clk/hisilicon/clk.c-312-\n--\ndrivers/clk/hisilicon/clk.c=318=void __init hi6220_clk_register_divider(const struct hi6220_divider_clock *clks,\n--\ndrivers/clk/hisilicon/clk.c-340-\t\tif (clks[i].alias)\ndrivers/clk/hisilicon/clk.c:341:\t\t\tclk_register_clkdev(clk, clks[i].alias, NULL);\ndrivers/clk/hisilicon/clk.c-342-\n--\ndrivers/clk/imx/clk-imx27.c=51=static void __init _mx27_clocks_init(unsigned long fref)\n--\ndrivers/clk/imx/clk-imx27.c-162-\ndrivers/clk/imx/clk-imx27.c:163:\tclk_register_clkdev(clk[IMX27_CLK_CPU_DIV], NULL, \"cpu0\");\ndrivers/clk/imx/clk-imx27.c-164-\n--\ndrivers/clk/imx/clk-imx5.c=131=static void __init mx5_clocks_common_init(void __iomem *ccm_base)\n--\ndrivers/clk/imx/clk-imx5.c-272-\ndrivers/clk/imx/clk-imx5.c:273:\tclk_register_clkdev(clk[IMX5_CLK_CPU_PODF], NULL, \"cpu0\");\ndrivers/clk/imx/clk-imx5.c:274:\tclk_register_clkdev(clk[IMX5_CLK_GPC_DVFS], \"gpc_dvfs\", NULL);\ndrivers/clk/imx/clk-imx5.c-275-\n--\ndrivers/clk/ingenic/cgu.c=644=static int ingenic_register_clock(struct ingenic_cgu *cgu, unsigned idx)\n--\ndrivers/clk/ingenic/cgu.c-663-\t\t}\ndrivers/clk/ingenic/cgu.c:664:\t\terr = clk_register_clkdev(clk, clk_info-\u003ename, NULL);\ndrivers/clk/ingenic/cgu.c-665-\t\tif (err) {\n--\ndrivers/clk/ingenic/cgu.c-775-\ndrivers/clk/ingenic/cgu.c:776:\terr = clk_register_clkdev(clk, clk_info-\u003ename, NULL);\ndrivers/clk/ingenic/cgu.c-777-\tif (err)\n--\ndrivers/clk/microchip/clk-pic32mzda.c=150=static int pic32mzda_clk_probe(struct platform_device *pdev)\n--\ndrivers/clk/microchip/clk-pic32mzda.c-222-\t\t\tcontinue;\ndrivers/clk/microchip/clk-pic32mzda.c:223:\t\tclk_register_clkdev(clks[i], NULL, __clk_get_name(clks[i]));\ndrivers/clk/microchip/clk-pic32mzda.c-224-\t}\n--\ndrivers/clk/mxs/clk-imx28.c=152=static void __init mx28_clocks_init(struct device_node *np)\n--\ndrivers/clk/mxs/clk-imx28.c-243-\ndrivers/clk/mxs/clk-imx28.c:244:\tclk_register_clkdev(clks[enet_out], NULL, \"enet_out\");\ndrivers/clk/mxs/clk-imx28.c-245-\n--\ndrivers/clk/pxa/clk-pxa3xx.c=422=static void __init pxa3xx_base_clocks_init(void __iomem *oscc_reg)\n--\ndrivers/clk/pxa/clk-pxa3xx.c-432-\t\t\t\t\"osc_13mhz\", 0, oscc_reg, 11, 0, NULL);\ndrivers/clk/pxa/clk-pxa3xx.c:433:\tclk_register_clkdev(clk, \"CLK_POUT\", NULL);\ndrivers/clk/pxa/clk-pxa3xx.c-434-\tclkdev_pxa_register(CLK_OSTIMER, \"OSTIMER0\", NULL,\n--\ndrivers/clk/spear/spear1310_clock.c=382=void __init spear1310_clk_init(void __iomem *misc_base, void __iomem *ras_base)\n--\ndrivers/clk/spear/spear1310_clock.c-386-\tclk = clk_register_fixed_rate(NULL, \"osc_32k_clk\", NULL, 0, 32000);\ndrivers/clk/spear/spear1310_clock.c:387:\tclk_register_clkdev(clk, \"osc_32k_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-388-\ndrivers/clk/spear/spear1310_clock.c-389-\tclk = clk_register_fixed_rate(NULL, \"osc_24m_clk\", NULL, 0, 24000000);\ndrivers/clk/spear/spear1310_clock.c:390:\tclk_register_clkdev(clk, \"osc_24m_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-391-\ndrivers/clk/spear/spear1310_clock.c-392-\tclk = clk_register_fixed_rate(NULL, \"osc_25m_clk\", NULL, 0, 25000000);\ndrivers/clk/spear/spear1310_clock.c:393:\tclk_register_clkdev(clk, \"osc_25m_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-394-\ndrivers/clk/spear/spear1310_clock.c-395-\tclk = clk_register_fixed_rate(NULL, \"gmii_pad_clk\", NULL, 0, 125000000);\ndrivers/clk/spear/spear1310_clock.c:396:\tclk_register_clkdev(clk, \"gmii_pad_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-397-\n--\ndrivers/clk/spear/spear1310_clock.c-399-\t\t\t\t 12288000);\ndrivers/clk/spear/spear1310_clock.c:400:\tclk_register_clkdev(clk, \"i2s_src_pad_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-401-\n--\ndrivers/clk/spear/spear1310_clock.c-405-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:406:\tclk_register_clkdev(clk, NULL, \"e0580000.rtc\");\ndrivers/clk/spear/spear1310_clock.c-407-\n--\ndrivers/clk/spear/spear1310_clock.c-413-\t\t\tSPEAR1310_PLL_CLK_MASK, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:414:\tclk_register_clkdev(clk, \"vco1_mclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-415-\tclk = clk_register_vco_pll(\"vco1_clk\", \"pll1_clk\", NULL, \"vco1_mclk\",\n--\ndrivers/clk/spear/spear1310_clock.c-417-\t\t\tARRAY_SIZE(pll_rtbl), \u0026_lock, \u0026clk1, NULL);\ndrivers/clk/spear/spear1310_clock.c:418:\tclk_register_clkdev(clk, \"vco1_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c:419:\tclk_register_clkdev(clk1, \"pll1_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-420-\n--\ndrivers/clk/spear/spear1310_clock.c-424-\t\t\tSPEAR1310_PLL_CLK_MASK, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:425:\tclk_register_clkdev(clk, \"vco2_mclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-426-\tclk = clk_register_vco_pll(\"vco2_clk\", \"pll2_clk\", NULL, \"vco2_mclk\",\n--\ndrivers/clk/spear/spear1310_clock.c-428-\t\t\tARRAY_SIZE(pll_rtbl), \u0026_lock, \u0026clk1, NULL);\ndrivers/clk/spear/spear1310_clock.c:429:\tclk_register_clkdev(clk, \"vco2_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c:430:\tclk_register_clkdev(clk1, \"pll2_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-431-\n--\ndrivers/clk/spear/spear1310_clock.c-435-\t\t\tSPEAR1310_PLL_CLK_MASK, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:436:\tclk_register_clkdev(clk, \"vco3_mclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-437-\tclk = clk_register_vco_pll(\"vco3_clk\", \"pll3_clk\", NULL, \"vco3_mclk\",\n--\ndrivers/clk/spear/spear1310_clock.c-439-\t\t\tARRAY_SIZE(pll_rtbl), \u0026_lock, \u0026clk1, NULL);\ndrivers/clk/spear/spear1310_clock.c:440:\tclk_register_clkdev(clk, \"vco3_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c:441:\tclk_register_clkdev(clk1, \"pll3_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-442-\n--\ndrivers/clk/spear/spear1310_clock.c-445-\t\t\tARRAY_SIZE(pll4_rtbl), \u0026_lock, \u0026clk1, NULL);\ndrivers/clk/spear/spear1310_clock.c:446:\tclk_register_clkdev(clk, \"vco4_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c:447:\tclk_register_clkdev(clk1, \"pll4_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-448-\n--\ndrivers/clk/spear/spear1310_clock.c-450-\t\t\t48000000);\ndrivers/clk/spear/spear1310_clock.c:451:\tclk_register_clkdev(clk, \"pll5_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-452-\n--\ndrivers/clk/spear/spear1310_clock.c-454-\t\t\t25000000);\ndrivers/clk/spear/spear1310_clock.c:455:\tclk_register_clkdev(clk, \"pll6_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-456-\n--\ndrivers/clk/spear/spear1310_clock.c-459-\t\t\t2);\ndrivers/clk/spear/spear1310_clock.c:460:\tclk_register_clkdev(clk, \"vco1div2_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-461-\n--\ndrivers/clk/spear/spear1310_clock.c-463-\t\t\t4);\ndrivers/clk/spear/spear1310_clock.c:464:\tclk_register_clkdev(clk, \"vco1div4_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-465-\n--\ndrivers/clk/spear/spear1310_clock.c-467-\t\t\t2);\ndrivers/clk/spear/spear1310_clock.c:468:\tclk_register_clkdev(clk, \"vco2div2_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-469-\n--\ndrivers/clk/spear/spear1310_clock.c-471-\t\t\t2);\ndrivers/clk/spear/spear1310_clock.c:472:\tclk_register_clkdev(clk, \"vco3div2_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-473-\n--\ndrivers/clk/spear/spear1310_clock.c-479-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:480:\tclk_register_clkdev(clk, NULL, \"spear_thermal\");\ndrivers/clk/spear/spear1310_clock.c-481-\n--\ndrivers/clk/spear/spear1310_clock.c-484-\t\t\t1);\ndrivers/clk/spear/spear1310_clock.c:485:\tclk_register_clkdev(clk, \"ddr_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-486-\n--\ndrivers/clk/spear/spear1310_clock.c-489-\t\t\tCLK_SET_RATE_PARENT, 1, 2);\ndrivers/clk/spear/spear1310_clock.c:490:\tclk_register_clkdev(clk, \"cpu_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-491-\n--\ndrivers/clk/spear/spear1310_clock.c-493-\t\t\t2);\ndrivers/clk/spear/spear1310_clock.c:494:\tclk_register_clkdev(clk, NULL, \"ec800620.wdt\");\ndrivers/clk/spear/spear1310_clock.c-495-\n--\ndrivers/clk/spear/spear1310_clock.c-497-\t\t\t2);\ndrivers/clk/spear/spear1310_clock.c:498:\tclk_register_clkdev(clk, NULL, \"smp_twd\");\ndrivers/clk/spear/spear1310_clock.c-499-\n--\ndrivers/clk/spear/spear1310_clock.c-501-\t\t\t6);\ndrivers/clk/spear/spear1310_clock.c:502:\tclk_register_clkdev(clk, \"ahb_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-503-\n--\ndrivers/clk/spear/spear1310_clock.c-505-\t\t\t12);\ndrivers/clk/spear/spear1310_clock.c:506:\tclk_register_clkdev(clk, \"apb_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-507-\n--\ndrivers/clk/spear/spear1310_clock.c-512-\t\t\tSPEAR1310_GPT_CLK_MASK, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:513:\tclk_register_clkdev(clk, \"gpt0_mclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-514-\tclk = clk_register_gate(NULL, \"gpt0_clk\", \"gpt0_mclk\", 0,\n--\ndrivers/clk/spear/spear1310_clock.c-516-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:517:\tclk_register_clkdev(clk, NULL, \"gpt0\");\ndrivers/clk/spear/spear1310_clock.c-518-\n--\ndrivers/clk/spear/spear1310_clock.c-522-\t\t\tSPEAR1310_GPT_CLK_MASK, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:523:\tclk_register_clkdev(clk, \"gpt1_mclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-524-\tclk = clk_register_gate(NULL, \"gpt1_clk\", \"gpt1_mclk\", 0,\n--\ndrivers/clk/spear/spear1310_clock.c-526-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:527:\tclk_register_clkdev(clk, NULL, \"gpt1\");\ndrivers/clk/spear/spear1310_clock.c-528-\n--\ndrivers/clk/spear/spear1310_clock.c-532-\t\t\tSPEAR1310_GPT_CLK_MASK, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:533:\tclk_register_clkdev(clk, \"gpt2_mclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-534-\tclk = clk_register_gate(NULL, \"gpt2_clk\", \"gpt2_mclk\", 0,\n--\ndrivers/clk/spear/spear1310_clock.c-536-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:537:\tclk_register_clkdev(clk, NULL, \"gpt2\");\ndrivers/clk/spear/spear1310_clock.c-538-\n--\ndrivers/clk/spear/spear1310_clock.c-542-\t\t\tSPEAR1310_GPT_CLK_MASK, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:543:\tclk_register_clkdev(clk, \"gpt3_mclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-544-\tclk = clk_register_gate(NULL, \"gpt3_clk\", \"gpt3_mclk\", 0,\n--\ndrivers/clk/spear/spear1310_clock.c-546-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:547:\tclk_register_clkdev(clk, NULL, \"gpt3\");\ndrivers/clk/spear/spear1310_clock.c-548-\n--\ndrivers/clk/spear/spear1310_clock.c-552-\t\t\tARRAY_SIZE(aux_rtbl), \u0026_lock, \u0026clk1);\ndrivers/clk/spear/spear1310_clock.c:553:\tclk_register_clkdev(clk, \"uart_syn_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c:554:\tclk_register_clkdev(clk1, \"uart_syn_gclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-555-\n--\ndrivers/clk/spear/spear1310_clock.c-560-\t\t\tSPEAR1310_UART_CLK_MASK, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:561:\tclk_register_clkdev(clk, \"uart0_mclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-562-\n--\ndrivers/clk/spear/spear1310_clock.c-565-\t\t\tSPEAR1310_UART_CLK_ENB, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:566:\tclk_register_clkdev(clk, NULL, \"e0000000.serial\");\ndrivers/clk/spear/spear1310_clock.c-567-\n--\ndrivers/clk/spear/spear1310_clock.c-570-\t\t\taux_rtbl, ARRAY_SIZE(aux_rtbl), \u0026_lock, \u0026clk1);\ndrivers/clk/spear/spear1310_clock.c:571:\tclk_register_clkdev(clk, \"sdhci_syn_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c:572:\tclk_register_clkdev(clk1, \"sdhci_syn_gclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-573-\n--\ndrivers/clk/spear/spear1310_clock.c-576-\t\t\tSPEAR1310_SDHCI_CLK_ENB, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:577:\tclk_register_clkdev(clk, NULL, \"b3000000.sdhci\");\ndrivers/clk/spear/spear1310_clock.c-578-\n--\ndrivers/clk/spear/spear1310_clock.c-581-\t\t\tARRAY_SIZE(aux_rtbl), \u0026_lock, \u0026clk1);\ndrivers/clk/spear/spear1310_clock.c:582:\tclk_register_clkdev(clk, \"cfxd_syn_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c:583:\tclk_register_clkdev(clk1, \"cfxd_syn_gclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-584-\n--\ndrivers/clk/spear/spear1310_clock.c-587-\t\t\tSPEAR1310_CFXD_CLK_ENB, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:588:\tclk_register_clkdev(clk, NULL, \"b2800000.cf\");\ndrivers/clk/spear/spear1310_clock.c:589:\tclk_register_clkdev(clk, NULL, \"arasan_xd\");\ndrivers/clk/spear/spear1310_clock.c-590-\n--\ndrivers/clk/spear/spear1310_clock.c-593-\t\t\tARRAY_SIZE(aux_rtbl), \u0026_lock, \u0026clk1);\ndrivers/clk/spear/spear1310_clock.c:594:\tclk_register_clkdev(clk, \"c3_syn_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c:595:\tclk_register_clkdev(clk1, \"c3_syn_gclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-596-\n--\ndrivers/clk/spear/spear1310_clock.c-601-\t\t\tSPEAR1310_C3_CLK_MASK, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:602:\tclk_register_clkdev(clk, \"c3_mclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-603-\n--\ndrivers/clk/spear/spear1310_clock.c-606-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:607:\tclk_register_clkdev(clk, NULL, \"c3\");\ndrivers/clk/spear/spear1310_clock.c-608-\n--\ndrivers/clk/spear/spear1310_clock.c-614-\t\t\tSPEAR1310_GMAC_PHY_INPUT_CLK_MASK, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:615:\tclk_register_clkdev(clk, \"phy_input_mclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-616-\n--\ndrivers/clk/spear/spear1310_clock.c-619-\t\t\tARRAY_SIZE(gmac_rtbl), \u0026_lock, \u0026clk1);\ndrivers/clk/spear/spear1310_clock.c:620:\tclk_register_clkdev(clk, \"phy_syn_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c:621:\tclk_register_clkdev(clk1, \"phy_syn_gclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-622-\n--\ndrivers/clk/spear/spear1310_clock.c-626-\t\t\tSPEAR1310_GMAC_PHY_CLK_MASK, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:627:\tclk_register_clkdev(clk, \"stmmacphy.0\", NULL);\ndrivers/clk/spear/spear1310_clock.c-628-\n--\ndrivers/clk/spear/spear1310_clock.c-634-\t\t\tSPEAR1310_CLCD_SYNT_CLK_MASK, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:635:\tclk_register_clkdev(clk, \"clcd_syn_mclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-636-\n--\ndrivers/clk/spear/spear1310_clock.c-639-\t\t\tARRAY_SIZE(clcd_rtbl), \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:640:\tclk_register_clkdev(clk, \"clcd_syn_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-641-\n--\ndrivers/clk/spear/spear1310_clock.c-646-\t\t\tSPEAR1310_CLCD_CLK_MASK, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:647:\tclk_register_clkdev(clk, \"clcd_pixel_mclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-648-\n--\ndrivers/clk/spear/spear1310_clock.c-651-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:652:\tclk_register_clkdev(clk, NULL, \"e1000000.clcd\");\ndrivers/clk/spear/spear1310_clock.c-653-\n--\ndrivers/clk/spear/spear1310_clock.c-658-\t\t\tSPEAR1310_I2S_SRC_CLK_MASK, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:659:\tclk_register_clkdev(clk, \"i2s_src_mclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-660-\n--\ndrivers/clk/spear/spear1310_clock.c-663-\t\t\tARRAY_SIZE(i2s_prs1_rtbl), \u0026_lock, NULL);\ndrivers/clk/spear/spear1310_clock.c:664:\tclk_register_clkdev(clk, \"i2s_prs1_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-665-\n--\ndrivers/clk/spear/spear1310_clock.c-670-\t\t\tSPEAR1310_I2S_REF_SEL_MASK, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:671:\tclk_register_clkdev(clk, \"i2s_ref_mclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-672-\n--\ndrivers/clk/spear/spear1310_clock.c-675-\t\t\t0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:676:\tclk_register_clkdev(clk, \"i2s_ref_pad_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-677-\n--\ndrivers/clk/spear/spear1310_clock.c-681-\t\t\tARRAY_SIZE(i2s_sclk_rtbl), \u0026_lock, \u0026clk1);\ndrivers/clk/spear/spear1310_clock.c:682:\tclk_register_clkdev(clk, \"i2s_sclk_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c:683:\tclk_register_clkdev(clk1, \"i2s_sclk_gclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-684-\n--\ndrivers/clk/spear/spear1310_clock.c-688-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:689:\tclk_register_clkdev(clk, NULL, \"e0280000.i2c\");\ndrivers/clk/spear/spear1310_clock.c-690-\n--\ndrivers/clk/spear/spear1310_clock.c-693-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:694:\tclk_register_clkdev(clk, NULL, \"ea800000.dma\");\ndrivers/clk/spear/spear1310_clock.c:695:\tclk_register_clkdev(clk, NULL, \"eb000000.dma\");\ndrivers/clk/spear/spear1310_clock.c-696-\n--\ndrivers/clk/spear/spear1310_clock.c-699-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:700:\tclk_register_clkdev(clk, NULL, \"b2000000.jpeg\");\ndrivers/clk/spear/spear1310_clock.c-701-\n--\ndrivers/clk/spear/spear1310_clock.c-704-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:705:\tclk_register_clkdev(clk, NULL, \"e2000000.eth\");\ndrivers/clk/spear/spear1310_clock.c-706-\n--\ndrivers/clk/spear/spear1310_clock.c-709-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:710:\tclk_register_clkdev(clk, NULL, \"b0000000.flash\");\ndrivers/clk/spear/spear1310_clock.c-711-\n--\ndrivers/clk/spear/spear1310_clock.c-714-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:715:\tclk_register_clkdev(clk, NULL, \"ea000000.flash\");\ndrivers/clk/spear/spear1310_clock.c-716-\n--\ndrivers/clk/spear/spear1310_clock.c-719-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:720:\tclk_register_clkdev(clk, NULL, \"e4000000.ohci\");\ndrivers/clk/spear/spear1310_clock.c:721:\tclk_register_clkdev(clk, NULL, \"e4800000.ehci\");\ndrivers/clk/spear/spear1310_clock.c-722-\n--\ndrivers/clk/spear/spear1310_clock.c-725-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:726:\tclk_register_clkdev(clk, NULL, \"e5000000.ohci\");\ndrivers/clk/spear/spear1310_clock.c:727:\tclk_register_clkdev(clk, NULL, \"e5800000.ehci\");\ndrivers/clk/spear/spear1310_clock.c-728-\n--\ndrivers/clk/spear/spear1310_clock.c-731-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:732:\tclk_register_clkdev(clk, NULL, \"e3800000.otg\");\ndrivers/clk/spear/spear1310_clock.c-733-\n--\ndrivers/clk/spear/spear1310_clock.c-736-\t\t\t0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:737:\tclk_register_clkdev(clk, NULL, \"b1000000.pcie\");\ndrivers/clk/spear/spear1310_clock.c:738:\tclk_register_clkdev(clk, NULL, \"b1000000.ahci\");\ndrivers/clk/spear/spear1310_clock.c-739-\n--\ndrivers/clk/spear/spear1310_clock.c-742-\t\t\t0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:743:\tclk_register_clkdev(clk, NULL, \"b1800000.pcie\");\ndrivers/clk/spear/spear1310_clock.c:744:\tclk_register_clkdev(clk, NULL, \"b1800000.ahci\");\ndrivers/clk/spear/spear1310_clock.c-745-\n--\ndrivers/clk/spear/spear1310_clock.c-748-\t\t\t0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:749:\tclk_register_clkdev(clk, NULL, \"b4000000.pcie\");\ndrivers/clk/spear/spear1310_clock.c:750:\tclk_register_clkdev(clk, NULL, \"b4000000.ahci\");\ndrivers/clk/spear/spear1310_clock.c-751-\n--\ndrivers/clk/spear/spear1310_clock.c-754-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:755:\tclk_register_clkdev(clk, \"sysram0_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-756-\n--\ndrivers/clk/spear/spear1310_clock.c-759-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:760:\tclk_register_clkdev(clk, \"sysram1_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-761-\n--\ndrivers/clk/spear/spear1310_clock.c-764-\t\t\tARRAY_SIZE(adc_rtbl), \u0026_lock, \u0026clk1);\ndrivers/clk/spear/spear1310_clock.c:765:\tclk_register_clkdev(clk, \"adc_syn_clk\", NULL);\ndrivers/clk/spear/spear1310_clock.c:766:\tclk_register_clkdev(clk1, \"adc_syn_gclk\", NULL);\ndrivers/clk/spear/spear1310_clock.c-767-\n--\ndrivers/clk/spear/spear1310_clock.c-770-\t\t\tSPEAR1310_ADC_CLK_ENB, 0, \u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:771:\tclk_register_clkdev(clk, NULL, \"e0080000.adc\");\ndrivers/clk/spear/spear1310_clock.c-772-\n--\ndrivers/clk/spear/spear1310_clock.c-776-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:777:\tclk_register_clkdev(clk, NULL, \"e0100000.spi\");\ndrivers/clk/spear/spear1310_clock.c-778-\n--\ndrivers/clk/spear/spear1310_clock.c-781-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:782:\tclk_register_clkdev(clk, NULL, \"e0600000.gpio\");\ndrivers/clk/spear/spear1310_clock.c-783-\n--\ndrivers/clk/spear/spear1310_clock.c-786-\t\t\t\u0026_lock);\ndrivers/clk/spear/spear1310_clock.c:787:\tclk_register_clkdev(clk, NULL, \"e0680000.gpio\");\ndrivers/clk/spear/spear1310_clock.c-788-\n--\n\n"
} |
| 19/2 | 2026/08/12 20:00 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 113292 output: 27 thoughts: 402 Thoughts: **Analyzing the "xtal" Clock Dependency in a Virtualized Environment** Okay, so I'm looking at this `clk_register_clkdev` function, and it's clear that it's used on some platforms, but there isn't a universally registered "xtal" clock available across all devices. The question now becomes: what if this driver, the one I'm examining, is actually instantiated on a platform that *does* have an "xtal" clock? Some ARM boards, for example, might. But, and this is the crux of the matter given the prompt, we are specifically dealing with scenarios that are impossible in virtualized environments, like GCE or QEMU, even with emulated hardware. So, is there *any* QEMU machine that could have both an "xtal" clock *and* an I2C bus that would let syzkaller instantiate this particular device? Now, if syzkaller is using `i2c-stub`, it creates an I2C adapter on a virtual bus. Then, I can use syzkaller to write to the `/sys/bus/i2c/devices/i2c-X/new_device` to instantiate the `sit9531x` device. This would likely result in a device named something like `0-0068`. The trouble arises when `devm_clk_get_enabled(dev, "xtal")` is called. It will be looking for a clock associated with `dev` and a "xtal" clock connection identifier. Because `i2c-stub` doesn't attach clocks to the devices it creates, and there's no global "xtal" clock (where `dev_id=NULL` and `con_id="xtal"`), this function will fail. But wait, hold on, is there a global clock named "xtal" anyway? I need to check if there are any calls to `clk_register_clkdev` that use `"xtal"` as `con_id` and `NULL` as `dev_id`. That's where I need to focus my investigation. |
| 20/2 | 2026/08/12 20:00 | tool | grepper |
0mError:no matches Args: {
"Expression": "clk_register_clkdev.*\"xtal\".*NULL"
}Results: {
"error": "no matches"
} |
| 21/2 | 2026/08/12 20:00 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 114192 output: 27 thoughts: 17 Thoughts: Here's my attempt at summarizing that thought process, tailored for an expert audience:
**Considering Clock Device Registration**
Okay, so I'm digging through this clock driver code, and I'm focusing on registration. Specifically, I'm keying in on potential issues or areas for review related to clock device registration, and the function `clk_register_clkdev`. Now, I'm immediately thinking, "What about that '.*NULL.*"xtal"' pattern?" It's jumping out at me as a potential point of interest. I'm wondering if there are any specific registration scenarios where a NULL pointer might appear in the context of crystal clocks ("xtal"). It could be a place to investigate potential edge cases, error handling, or even subtle issues related to how the clock framework is interfacing with hardware at system boot or during device probe. A null pointer is a great place to start looking for potential bugs. It's time to dig into the surrounding code and see what scenarios could lead to a NULL pointer.
|
| 22/2 | 2026/08/12 20:00 | tool | grepper |
0mError:no matches Args: {
"Expression": "clk_register_clkdev.*NULL.*\"xtal\""
}Results: {
"error": "no matches"
} |
| 23/2 | 2026/08/12 20:00 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 114463 output: 19 thoughts: 250 Thoughts: **Device Tree Deep Dive** Okay, so I'm thinking about how to handle this I2C device with syzkaller. Device tree seems like the natural approach here. If I can get syzkaller to load a device tree overlay, I can just create a new node for the device and attach a clock to it, problem solved! But does syzkaller *actually* support device tree overlays? I remember there's the `syz_mount_image` pseudo-syscall, but I don't recall it doing any DT loading. Hold on…wait, I think it *does*! I need to be sure. Let's dig into the details... I can't use `syz-grepper` right now, but I can search the kernel code for `of_overlay_fdt_apply` or something similar, and see if it's called from some syzkaller test module or if it's enabled under the configs that syzkaller uses. Thinking through it a bit, I remember several pseudo-syscalls like `syz_init_net_socket`, `syz_genetlink`, and `syz_open_dev`, but none are obviously for DT overlays. Aha! Wait a minute... there are kernel configs that seem relevant here: `CONFIG_OF_OVERLAY` and `CONFIG_OF_CONFIGFS`. If `CONFIG_OF_CONFIGFS` is enabled, userspace can load DT overlays through configfs! That could be the key! Now, the question is, does `CONFIG_OF_CONFIGFS` actually exist? I need to check the kernel's configuration options. |
| 24/2 | 2026/08/12 20:00 | tool | grepper |
0mError:no matches Args: {
"Expression": "config OF_CONFIGFS"
}Results: {
"error": "no matches"
} |
| 25/2 | 2026/08/12 20:00 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 115097 output: 19 thoughts: 27 Thoughts: Okay, here's my interpretation of those thoughts, crafted with the expectation that I'm discussing this with a fellow expert: **Kernel Overlay Investigation** Right, hold on a second. Overlay support... isn't this supposed to be integrated into the mainline kernel at this point? I thought it was baked in a while ago. Let me double-check. Okay, I'm thinking, where would I *expect* to find the Kconfig entries related to this functionality, based on established kernel organizational patterns? My first instinct is to dive into `drivers/of/Kconfig`. That location is the most logical place to begin digging around for configuration options related to device tree overlays. Let's see what's in there. I'm expecting to see the dependencies, maybe even a basic description or hints. |
| 26/2 | 2026/08/12 20:01 | tool | grepper |
0mArgs:{
"Expression": "config OF_OVERLAY"
}Results: {
"Output": "drivers/of/Kconfig=102=config OF_RESOLVE\n--\ndrivers/of/Kconfig-104-\ndrivers/of/Kconfig:105:config OF_OVERLAY\ndrivers/of/Kconfig-106-\tbool \"Device Tree overlays\"\n--\ndrivers/of/Kconfig-115-\ndrivers/of/Kconfig:116:config OF_OVERLAY_KUNIT_TEST\ndrivers/of/Kconfig-117-\ttristate \"Device Tree overlay KUnit tests\" if !KUNIT_ALL_TESTS\n"
} |
| 27/2 | 2026/08/12 20:01 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 115460 output: 26 thoughts: 432 Thoughts: **Analyzing the DT Overlay and Clock Initialization**
Okay, so I'm trying to figure out how this driver is failing in syzkaller. The initial thought is whether we can even load overlays from userspace in a vanilla Linux kernel. No, that's not possible. `OF_OVERLAY` is strictly an in-kernel thing used by drivers like FPGA managers, no generic userspace interface. Forget about using that with syzkaller.
Right, so if syzkaller can't load these DT overlays, and there's no "xtal" clock globally, the `devm_clk_get_enabled` call *will* always fail with a `new_device` instantiation. But wait, is there *any* way `devm_clk_get_enabled` can succeed in this scenario?
Ah, what if `CONFIG_HAVE_CLK` is disabled in the kernel configuration? If it is, `devm_clk_get_enabled` will return `NULL`. Which, in turn, means that calling `clk_get_rate(NULL)` will return 0. The driver code then tries to grab the rate:
```c
sitdev->xtal_freq = clk_get_rate(xtal_clk);
if (!sitdev->xtal_freq)
return dev_err_probe(sitdev->dev, -EINVAL,
"xtal clock has no rate\n");
```
So it *should* fail with an `-EINVAL` error in that case. That's good to know. But is there a crazy corner case, like, is there *any* architecture where `clk_get_rate(NULL)` returns something other than zero if `CONFIG_HAVE_CLK` is disabled? No, looking at the code it's defined like this when `CONFIG_HAVE_CLK` is disabled:
```c
static inline unsigned long clk_get_rate(struct clk *clk)
{
return 0;
}
```
So the probe will ALWAYS fail if there is no clock, which the absence of the DT overlay guarantees. Okay. I think I'm following it.
But hold on, is there a chance that a different driver might register a clock globally, maybe even named "xtal", so it is available? I should search the `drivers/clk/` directory for any references to "xtal". Let's look and make sure.
|
| 28/2 | 2026/08/12 20:01 | tool | grepper |
0mError:no matches Args: {
"Expression": "clk_register_fixed_rate.*\"xtal\""
}Results: {
"error": "no matches"
} |
| 29/2 | 2026/08/12 20:01 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 116461 output: 28 thoughts: 19 Thoughts: Here's my thought process, as if I were the one thinking it: **Digging into Clock Driver Code** Okay, so I'm trying to track down something related to "xtal" – probably a crystal oscillator – and its usage in the clock driver subsystem. It's a common term in embedded systems, especially when dealing with hardware-level clock sources. I need to figure out how it's being handled within the kernel. My first thought is to broaden the search. I'm going to run a search for `"xtal"` within the `drivers/clk/` directory. This is where the clock driver code typically resides, so it's the logical place to start looking for references to crystal oscillators and how the clock framework manages them. I need to see what pops up. I'm expecting to find defines, function calls, or data structures that are related to the crystal oscillator. This should hopefully point me in the right direction to understand the clock's configuration and control within this driver. |
| 30/2 | 2026/08/12 20:01 | tool | grepper |
0mArgs:{
"Expression": "\"xtal\"",
"PathPrefix": "drivers/clk/"
}Results: {
"Output": "\nFull output is too long, showing 500 out of 882 lines.\nUse more precise expression if possible.\n\ndrivers/clk/clk-gemini.c=268=static int gemini_clk_probe(struct platform_device *pdev)\n--\ndrivers/clk/clk-gemini.c-361-\tdiv += 1;\ndrivers/clk/clk-gemini.c:362:\thw = clk_hw_register_fixed_rate(NULL, \"tvcdiv\", \"xtal\", 0, 27000000);\ndrivers/clk/clk-gemini.c-363-\tgemini_clk_data-\u003ehws[GEMINI_CLK_TVC] = hw;\n--\ndrivers/clk/clk-gemini.c-365-\t/* FIXME: very unclear what the parent is */\ndrivers/clk/clk-gemini.c:366:\thw = gemini_pci_clk_setup(\"PCI\", \"xtal\", map);\ndrivers/clk/clk-gemini.c-367-\tgemini_clk_data-\u003ehws[GEMINI_CLK_PCI] = hw;\n--\ndrivers/clk/clk-gemini.c-369-\t/* FIXME: very unclear what the parent is */\ndrivers/clk/clk-gemini.c:370:\thw = clk_hw_register_fixed_rate(NULL, \"uart\", \"xtal\", 0, 48000000);\ndrivers/clk/clk-gemini.c-371-\tgemini_clk_data-\u003ehws[GEMINI_CLK_UART] = hw;\n--\ndrivers/clk/clk-gemini.c=391=static void __init gemini_cc_init(struct device_node *np)\n--\ndrivers/clk/clk-gemini.c-437-\t\tfreq = 60000000;\ndrivers/clk/clk-gemini.c:438:\thw = clk_hw_register_fixed_rate(NULL, \"xtal\", NULL, 0, freq);\ndrivers/clk/clk-gemini.c-439-\tpr_debug(\"main crystal @%lu MHz\\n\", freq / 1000000);\n--\ndrivers/clk/clk-gemini.c-446-\t\tmult *= 2;\ndrivers/clk/clk-gemini.c:447:\thw = clk_hw_register_fixed_factor(NULL, \"vco\", \"xtal\", 0, mult, div);\ndrivers/clk/clk-gemini.c-448-\n--\ndrivers/clk/clk-loongson1.c=154=struct ls1x_clk _name = {\t\t\t\t\t\t\\\n--\ndrivers/clk/clk-loongson1.c-167-\t\t.parent_data = \u0026(const struct clk_parent_data) {\t\\\ndrivers/clk/clk-loongson1.c:168:\t\t\t.fw_name = \"xtal\",\t\t\t\t\\\ndrivers/clk/clk-loongson1.c:169:\t\t\t.name = \"xtal\",\t\t\t\t\t\\\ndrivers/clk/clk-loongson1.c-170-\t\t\t.index = -1,\t\t\t\t\t\\\n--\ndrivers/clk/clk-si5341.c=171=static const char * const si5341_input_clock_names[] = {\ndrivers/clk/clk-si5341.c:172:\t\"in0\", \"in1\", \"in2\", \"xtal\"\ndrivers/clk/clk-si5341.c-173-};\n--\ndrivers/clk/clk-si5351.c=67=static const char * const si5351_input_names[] = {\ndrivers/clk/clk-si5351.c:68:\t\"xtal\", \"clkin\"\ndrivers/clk/clk-si5351.c-69-};\n--\ndrivers/clk/clk-si5351.c=1436=static int si5351_i2c_probe(struct i2c_client *client)\n--\ndrivers/clk/clk-si5351.c-1461-\tdrvdata-\u003evariant = variant;\ndrivers/clk/clk-si5351.c:1462:\tdrvdata-\u003epxtal = devm_clk_get(\u0026client-\u003edev, \"xtal\");\ndrivers/clk/clk-si5351.c-1463-\tdrvdata-\u003epclkin = devm_clk_get(\u0026client-\u003edev, \"clkin\");\n--\ndrivers/clk/clk-wm831x.c=50=static const struct clk_init_data wm831x_xtal_init = {\ndrivers/clk/clk-wm831x.c:51:\t.name = \"xtal\",\ndrivers/clk/clk-wm831x.c-52-\t.ops = \u0026wm831x_xtal_ops,\n--\ndrivers/clk/clk-wm831x.c=173=static const char *wm831x_fll_parents[] = {\ndrivers/clk/clk-wm831x.c:174:\t\"xtal\",\ndrivers/clk/clk-wm831x.c-175-\t\"clkin\",\n--\ndrivers/clk/clk-wm831x.c=294=static const char *wm831x_clkout_parents[] = {\ndrivers/clk/clk-wm831x.c-295-\t\"fll\",\ndrivers/clk/clk-wm831x.c:296:\t\"xtal\",\ndrivers/clk/clk-wm831x.c-297-};\n--\ndrivers/clk/clk.c=436=of_clk_get_hw_from_clkspec(struct of_phandle_args *clkspec)\n--\ndrivers/clk/clk.c-467- * clocks = \u003c\u0026parent\u003e;\ndrivers/clk/clk.c:468: * clock-names = \"xtal\";\ndrivers/clk/clk.c-469- * #clock-cells = \u003c1\u003e;\n--\ndrivers/clk/meson/a1-peripherals.c=48=static struct clk_regmap a1_xtal_in = {\n--\ndrivers/clk/meson/a1-peripherals.c-56-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/a1-peripherals.c:57:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/a1-peripherals.c-58-\t\t},\n--\ndrivers/clk/meson/a1-peripherals.c=63=static struct clk_regmap a1_fixpll_in = {\n--\ndrivers/clk/meson/a1-peripherals.c-71-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/a1-peripherals.c:72:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/a1-peripherals.c-73-\t\t},\n--\ndrivers/clk/meson/a1-peripherals.c=78=static struct clk_regmap a1_usb_phy_in = {\n--\ndrivers/clk/meson/a1-peripherals.c-86-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/a1-peripherals.c:87:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/a1-peripherals.c-88-\t\t},\n--\ndrivers/clk/meson/a1-peripherals.c=93=static struct clk_regmap a1_usb_ctrl_in = {\n--\ndrivers/clk/meson/a1-peripherals.c-101-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/a1-peripherals.c:102:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/a1-peripherals.c-103-\t\t},\n--\ndrivers/clk/meson/a1-peripherals.c=108=static struct clk_regmap a1_hifipll_in = {\n--\ndrivers/clk/meson/a1-peripherals.c-116-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/a1-peripherals.c:117:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/a1-peripherals.c-118-\t\t},\n--\ndrivers/clk/meson/a1-peripherals.c=123=static struct clk_regmap a1_syspll_in = {\n--\ndrivers/clk/meson/a1-peripherals.c-131-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/a1-peripherals.c:132:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/a1-peripherals.c-133-\t\t},\n--\ndrivers/clk/meson/a1-peripherals.c=138=static struct clk_regmap a1_dds_in = {\n--\ndrivers/clk/meson/a1-peripherals.c-146-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/a1-peripherals.c:147:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/a1-peripherals.c-148-\t\t},\n--\ndrivers/clk/meson/a1-peripherals.c=153=static struct clk_regmap a1_rtc_32k_in = {\n--\ndrivers/clk/meson/a1-peripherals.c-161-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/a1-peripherals.c:162:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/a1-peripherals.c-163-\t\t},\n--\ndrivers/clk/meson/a1-peripherals.c=269=static const struct clk_parent_data a1_sys_parents[] = {\ndrivers/clk/meson/a1-peripherals.c:270:\t{ .fw_name = \"xtal\" },\ndrivers/clk/meson/a1-peripherals.c-271-\t{ .fw_name = \"fclk_div2\" },\n--\ndrivers/clk/meson/a1-peripherals.c=401=static const struct clk_parent_data a1_dsp_parents[] = {\ndrivers/clk/meson/a1-peripherals.c:402:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/a1-peripherals.c-403-\t{ .fw_name = \"fclk_div2\", },\n--\ndrivers/clk/meson/a1-peripherals.c=702=static struct clk_regmap a1_24m = {\n--\ndrivers/clk/meson/a1-peripherals.c-710-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/a1-peripherals.c:711:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/a1-peripherals.c-712-\t\t},\n--\ndrivers/clk/meson/a1-peripherals.c=782=static const struct clk_parent_data a1_gen_parents[] = {\ndrivers/clk/meson/a1-peripherals.c:783:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/a1-peripherals.c-784-\t{ .hw = \u0026a1_rtc.hw },\n--\ndrivers/clk/meson/a1-peripherals.c=848=static struct clk_regmap a1_saradc_sel = {\n--\ndrivers/clk/meson/a1-peripherals.c-857-\t\t.parent_data = (const struct clk_parent_data []) {\ndrivers/clk/meson/a1-peripherals.c:858:\t\t\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/a1-peripherals.c-859-\t\t\t{ .hw = \u0026a1_sys.hw, },\n--\ndrivers/clk/meson/a1-peripherals.c=898=static const struct clk_parent_data a1_pwm_abcd_parents[] = {\ndrivers/clk/meson/a1-peripherals.c:899:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/a1-peripherals.c-900-\t{ .hw = \u0026a1_sys.hw },\n--\ndrivers/clk/meson/a1-peripherals.c=1092=static const struct clk_parent_data a1_pwm_ef_parents[] = {\ndrivers/clk/meson/a1-peripherals.c:1093:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/a1-peripherals.c-1094-\t{ .hw = \u0026a1_sys.hw },\n--\ndrivers/clk/meson/a1-peripherals.c=1240=static struct clk_regmap a1_spicc_sel2 = {\n--\ndrivers/clk/meson/a1-peripherals.c-1250-\t\t\t{ .hw = \u0026a1_spicc_div.hw },\ndrivers/clk/meson/a1-peripherals.c:1251:\t\t\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/a1-peripherals.c-1252-\t\t},\n--\ndrivers/clk/meson/a1-peripherals.c=1274=static struct clk_regmap a1_ts_div = {\n--\ndrivers/clk/meson/a1-peripherals.c-1283-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/a1-peripherals.c:1284:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/a1-peripherals.c-1285-\t\t},\n--\ndrivers/clk/meson/a1-peripherals.c=1337=static struct clk_regmap a1_spifc_sel2 = {\n--\ndrivers/clk/meson/a1-peripherals.c-1347-\t\t\t{ .hw = \u0026a1_spifc_div.hw },\ndrivers/clk/meson/a1-peripherals.c:1348:\t\t\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/a1-peripherals.c-1349-\t\t},\n--\ndrivers/clk/meson/a1-peripherals.c=1371=static const struct clk_parent_data a1_usb_bus_parents[] = {\ndrivers/clk/meson/a1-peripherals.c:1372:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/a1-peripherals.c-1373-\t{ .hw = \u0026a1_sys.hw },\n--\ndrivers/clk/meson/a1-peripherals.c=1464=static struct clk_regmap a1_sd_emmc_sel2 = {\n--\ndrivers/clk/meson/a1-peripherals.c-1474-\t\t\t{ .hw = \u0026a1_sd_emmc_div.hw },\ndrivers/clk/meson/a1-peripherals.c:1475:\t\t\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/a1-peripherals.c-1476-\t\t},\n--\ndrivers/clk/meson/a1-peripherals.c=1529=static struct clk_regmap a1_psram_sel2 = {\n--\ndrivers/clk/meson/a1-peripherals.c-1539-\t\t\t{ .hw = \u0026a1_psram_div.hw },\ndrivers/clk/meson/a1-peripherals.c:1540:\t\t\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/a1-peripherals.c-1541-\t\t},\n--\ndrivers/clk/meson/a1-peripherals.c=1594=static struct clk_regmap a1_dmc_sel2 = {\n--\ndrivers/clk/meson/a1-peripherals.c-1604-\t\t\t{ .hw = \u0026a1_dmc_div.hw },\ndrivers/clk/meson/a1-peripherals.c:1605:\t\t\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/a1-peripherals.c-1606-\t\t},\n--\ndrivers/clk/meson/a1-peripherals.c=1628=static struct clk_regmap a1_ceca_32k_in = {\n--\ndrivers/clk/meson/a1-peripherals.c-1636-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/a1-peripherals.c:1637:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/a1-peripherals.c-1638-\t\t},\n--\ndrivers/clk/meson/a1-peripherals.c=1735=static struct clk_regmap a1_cecb_32k_in = {\n--\ndrivers/clk/meson/a1-peripherals.c-1743-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/a1-peripherals.c:1744:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/a1-peripherals.c-1745-\t\t},\n--\ndrivers/clk/meson/axg-aoclk.c=51=static struct clk_regmap axg_ao_cts_oscin = {\n--\ndrivers/clk/meson/axg-aoclk.c-59-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/axg-aoclk.c:60:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/axg-aoclk.c-61-\t\t},\n--\ndrivers/clk/meson/axg-aoclk.c=209=static struct clk_regmap axg_ao_saradc_mux = {\n--\ndrivers/clk/meson/axg-aoclk.c-218-\t\t.parent_data = (const struct clk_parent_data []) {\ndrivers/clk/meson/axg-aoclk.c:219:\t\t\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/axg-aoclk.c-220-\t\t\t{ .hw = \u0026axg_ao_clk81.hw },\n--\ndrivers/clk/meson/axg.c=110=static struct clk_regmap axg_fixed_pll_dco = {\n--\ndrivers/clk/meson/axg.c-146-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/axg.c:147:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/axg.c-148-\t\t},\n--\ndrivers/clk/meson/axg.c=174=static struct clk_regmap axg_sys_pll_dco = {\n--\ndrivers/clk/meson/axg.c-205-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/axg.c:206:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/axg.c-207-\t\t},\n--\ndrivers/clk/meson/axg.c=271=static struct clk_regmap axg_gp0_pll_dco = {\n--\ndrivers/clk/meson/axg.c-310-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/axg.c:311:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/axg.c-312-\t\t},\n--\ndrivers/clk/meson/axg.c=343=static struct clk_regmap axg_hifi_pll_dco = {\n--\ndrivers/clk/meson/axg.c-383-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/axg.c:384:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/axg.c-385-\t\t},\n--\ndrivers/clk/meson/axg.c=792=static struct clk_regmap axg_pcie_pll_dco = {\n--\ndrivers/clk/meson/axg.c-831-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/axg.c:832:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/axg.c-833-\t\t},\n--\ndrivers/clk/meson/axg.c=939=static const struct clk_parent_data clk81_parents[] = {\ndrivers/clk/meson/axg.c:940:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/axg.c-941-\t{ .hw = \u0026axg_fclk_div7.hw },\n--\ndrivers/clk/meson/axg.c=997=static const struct clk_parent_data axg_sd_emmc_clk0_parents[] = {\ndrivers/clk/meson/axg.c:998:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/axg.c-999-\t{ .hw = \u0026axg_fclk_div2.hw },\n--\ndrivers/clk/meson/axg.c=1791=static const struct clk_parent_data axg_vdin_meas_parents[] = {\ndrivers/clk/meson/axg.c:1792:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/axg.c-1793-\t{ .hw = \u0026axg_fclk_div4.hw },\n--\ndrivers/clk/meson/axg.c=1849=static const struct clk_parent_data gen_clk_parents[] = {\ndrivers/clk/meson/axg.c:1850:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/axg.c-1851-\t{ .hw = \u0026axg_hifi_pll.hw },\n--\ndrivers/clk/meson/g12a-aoclk.c=71=static struct clk_regmap g12a_ao_cts_oscin = {\n--\ndrivers/clk/meson/g12a-aoclk.c-79-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/g12a-aoclk.c:80:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/g12a-aoclk.c-81-\t\t},\n--\ndrivers/clk/meson/g12a-aoclk.c=322=static struct clk_regmap g12a_ao_saradc_mux = {\n--\ndrivers/clk/meson/g12a-aoclk.c-331-\t\t.parent_data = (const struct clk_parent_data []) {\ndrivers/clk/meson/g12a-aoclk.c:332:\t\t\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/g12a-aoclk.c-333-\t\t\t{ .hw = \u0026g12a_ao_clk81.hw },\n--\ndrivers/clk/meson/g12a.c=140=static struct clk_regmap g12a_fixed_pll_dco = {\n--\ndrivers/clk/meson/g12a.c-176-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/g12a.c:177:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/g12a.c-178-\t\t},\n--\ndrivers/clk/meson/g12a.c=209=static struct clk_regmap g12a_sys_pll_dco = {\n--\ndrivers/clk/meson/g12a.c-241-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/g12a.c:242:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/g12a.c-243-\t\t},\n--\ndrivers/clk/meson/g12a.c=268=static struct clk_regmap g12b_sys1_pll_dco = {\n--\ndrivers/clk/meson/g12a.c-300-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/g12a.c:301:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/g12a.c-302-\t\t},\n--\ndrivers/clk/meson/g12a.c=406=static struct clk_regmap g12a_gp0_pll_dco = {\n--\ndrivers/clk/meson/g12a.c-445-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/g12a.c:446:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/g12a.c-447-\t\t},\n--\ndrivers/clk/meson/g12a.c=471=static struct clk_regmap sm1_gp1_pll_dco = {\n--\ndrivers/clk/meson/g12a.c-507-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/g12a.c:508:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/g12a.c-509-\t\t},\n--\ndrivers/clk/meson/g12a.c=546=static struct clk_regmap g12a_hifi_pll_dco = {\n--\ndrivers/clk/meson/g12a.c-586-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/g12a.c:587:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/g12a.c-588-\t\t},\n--\ndrivers/clk/meson/g12a.c=638=static struct clk_regmap g12a_pcie_pll_dco = {\n--\ndrivers/clk/meson/g12a.c-677-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/g12a.c:678:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/g12a.c-679-\t\t},\n--\ndrivers/clk/meson/g12a.c=732=static struct clk_regmap g12a_hdmi_pll_dco = {\n--\ndrivers/clk/meson/g12a.c-768-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/g12a.c:769:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/g12a.c-770-\t\t},\n--\ndrivers/clk/meson/g12a.c=1038=static struct clk_regmap g12a_mpll_50m = {\n--\ndrivers/clk/meson/g12a.c-1047-\t\t.parent_data = (const struct clk_parent_data []) {\ndrivers/clk/meson/g12a.c:1048:\t\t\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/g12a.c-1049-\t\t\t{ .hw = \u0026g12a_mpll_50m_div.hw },\n--\ndrivers/clk/meson/g12a.c=1069=static struct clk_regmap g12a_cpu_clk_dyn0_sel = {\n--\ndrivers/clk/meson/g12a.c-1079-\t\t.parent_data = (const struct clk_parent_data []) {\ndrivers/clk/meson/g12a.c:1080:\t\t\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/g12a.c-1081-\t\t\t{ .hw = \u0026g12a_fclk_div2.hw },\n--\ndrivers/clk/meson/g12a.c=1135=static struct clk_regmap g12a_cpu_clk_dyn1_sel = {\n--\ndrivers/clk/meson/g12a.c-1144-\t\t.parent_data = (const struct clk_parent_data []) {\ndrivers/clk/meson/g12a.c:1145:\t\t\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/g12a.c-1146-\t\t\t{ .hw = \u0026g12a_fclk_div2.hw },\n--\ndrivers/clk/meson/g12a.c=1253=static struct clk_regmap g12b_cpub_clk_dyn0_sel = {\n--\ndrivers/clk/meson/g12a.c-1263-\t\t.parent_data = (const struct clk_parent_data []) {\ndrivers/clk/meson/g12a.c:1264:\t\t\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/g12a.c-1265-\t\t\t{ .hw = \u0026g12a_fclk_div2.hw },\n--\ndrivers/clk/meson/g12a.c=1319=static struct clk_regmap g12b_cpub_clk_dyn1_sel = {\n--\ndrivers/clk/meson/g12a.c-1328-\t\t.parent_data = (const struct clk_parent_data []) {\ndrivers/clk/meson/g12a.c:1329:\t\t\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/g12a.c-1330-\t\t\t{ .hw = \u0026g12a_fclk_div2.hw },\n--\ndrivers/clk/meson/g12a.c=1417=static struct clk_regmap sm1_dsu_clk_dyn0_sel = {\n--\ndrivers/clk/meson/g12a.c-1426-\t\t.parent_data = (const struct clk_parent_data []) {\ndrivers/clk/meson/g12a.c:1427:\t\t\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/g12a.c-1428-\t\t\t{ .hw = \u0026g12a_fclk_div2.hw },\n--\ndrivers/clk/meson/g12a.c=1472=static struct clk_regmap sm1_dsu_clk_dyn1_sel = {\n--\ndrivers/clk/meson/g12a.c-1481-\t\t.parent_data = (const struct clk_parent_data []) {\ndrivers/clk/meson/g12a.c:1482:\t\t\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/g12a.c-1483-\t\t\t{ .hw = \u0026g12a_fclk_div2.hw },\n--\ndrivers/clk/meson/g12a.c=2523=static const struct clk_parent_data g12a_clk81_parents[] = {\ndrivers/clk/meson/g12a.c:2524:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/g12a.c-2525-\t{ .hw = \u0026g12a_fclk_div7.hw },\n--\ndrivers/clk/meson/g12a.c=2581=static const struct clk_parent_data g12a_sd_emmc_clk0_parents[] = {\ndrivers/clk/meson/g12a.c:2582:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/g12a.c-2583-\t{ .hw = \u0026g12a_fclk_div2.hw },\n--\ndrivers/clk/meson/g12a.c=3899=static const struct clk_parent_data g12b_mipi_isp_parents[] = {\ndrivers/clk/meson/g12a.c:3900:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/g12a.c-3901-\t{ .hw = \u0026g12a_gp0_pll.hw },\n--\ndrivers/clk/meson/g12a.c=3959=static const struct clk_parent_data g12a_hdmi_parents[] = {\ndrivers/clk/meson/g12a.c:3960:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/g12a.c-3961-\t{ .hw = \u0026g12a_fclk_div4.hw },\n--\ndrivers/clk/meson/g12a.c=4017=static const struct clk_parent_data g12a_mali_parents[] = {\ndrivers/clk/meson/g12a.c:4018:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/g12a.c-4019-\t{ .hw = \u0026g12a_gp0_pll.hw },\n--\ndrivers/clk/meson/g12a.c=4154=static struct clk_regmap g12a_ts_div = {\n--\ndrivers/clk/meson/g12a.c-4163-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/g12a.c:4164:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/g12a.c-4165-\t\t},\n--\ndrivers/clk/meson/g12a.c=4187=static const struct clk_parent_data g12a_spicc_sclk_parents[] = {\ndrivers/clk/meson/g12a.c:4188:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/g12a.c-4189-\t{ .hw = \u0026g12a_clk81.hw },\n--\ndrivers/clk/meson/g12a.c=4293=static const struct clk_parent_data sm1_nna_clk_parents[] = {\ndrivers/clk/meson/g12a.c:4294:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/g12a.c-4295-\t{ .hw = \u0026g12a_gp0_pll.hw, },\n--\ndrivers/clk/meson/gxbb-aoclk.c=39=static struct clk_regmap gxbb_ao_cts_oscin = {\n--\ndrivers/clk/meson/gxbb-aoclk.c-47-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/gxbb-aoclk.c:48:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/gxbb-aoclk.c-49-\t\t},\n--\ndrivers/clk/meson/gxbb.c=118=static struct clk_regmap gxbb_fixed_pll_dco = {\n--\ndrivers/clk/meson/gxbb.c-154-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/gxbb.c:155:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/gxbb.c-156-\t\t},\n--\ndrivers/clk/meson/gxbb.c=182=static struct clk_fixed_factor gxbb_hdmi_pll_pre_mult = {\n--\ndrivers/clk/meson/gxbb.c-188-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/gxbb.c:189:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/gxbb.c-190-\t\t},\n--\ndrivers/clk/meson/gxbb.c=243=static struct clk_regmap gxl_hdmi_pll_dco = {\n--\ndrivers/clk/meson/gxbb.c-285-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/gxbb.c:286:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/gxbb.c-287-\t\t},\n--\ndrivers/clk/meson/gxbb.c=416=static struct clk_regmap gxbb_sys_pll_dco = {\n--\ndrivers/clk/meson/gxbb.c-447-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/gxbb.c:448:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/gxbb.c-449-\t\t},\n--\ndrivers/clk/meson/gxbb.c=513=static struct clk_regmap gxbb_gp0_pll_dco = {\n--\ndrivers/clk/meson/gxbb.c-547-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/gxbb.c:548:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/gxbb.c-549-\t\t},\n--\ndrivers/clk/meson/gxbb.c=591=static struct clk_regmap gxl_gp0_pll_dco = {\n--\ndrivers/clk/meson/gxbb.c-630-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/gxbb.c:631:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/gxbb.c-632-\t\t},\n--\ndrivers/clk/meson/gxbb.c=987=static const struct clk_parent_data clk81_parents[] = {\ndrivers/clk/meson/gxbb.c:988:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/gxbb.c-989-\t{ .hw = \u0026gxbb_fclk_div7.hw },\n--\ndrivers/clk/meson/gxbb.c=1050=static struct clk_regmap gxbb_sar_adc_clk_sel = {\n--\ndrivers/clk/meson/gxbb.c-1060-\t\t.parent_data = (const struct clk_parent_data []) {\ndrivers/clk/meson/gxbb.c:1061:\t\t\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/gxbb.c-1062-\t\t\t{ .hw = \u0026gxbb_clk81.hw },\n--\ndrivers/clk/meson/gxbb.c=1108=static const struct clk_parent_data gxbb_mali_parents[] = {\ndrivers/clk/meson/gxbb.c:1109:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/gxbb.c-1110-\t{ .hw = \u0026gxbb_gp0_pll.hw },\n--\ndrivers/clk/meson/gxbb.c=1379=static const struct clk_parent_data gxbb_32k_clk_parents[] = {\ndrivers/clk/meson/gxbb.c:1380:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/gxbb.c-1381-\t{ .hw = \u0026gxbb_fclk_div3.hw },\n--\ndrivers/clk/meson/gxbb.c=1434=static const struct clk_parent_data gxbb_sd_emmc_clk0_parents[] = {\ndrivers/clk/meson/gxbb.c:1435:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/gxbb.c-1436-\t{ .hw = \u0026gxbb_fclk_div2.hw },\n--\ndrivers/clk/meson/gxbb.c=2504=static const struct clk_parent_data gxbb_hdmi_parents[] = {\ndrivers/clk/meson/gxbb.c:2505:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/gxbb.c-2506-\t{ .hw = \u0026gxbb_fclk_div4.hw },\n--\ndrivers/clk/meson/gxbb.c=2666=static const struct clk_parent_data gxbb_gen_clk_parents[] = {\ndrivers/clk/meson/gxbb.c:2667:\t{ .fw_name = \"xtal\", },\ndrivers/clk/meson/gxbb.c-2668-\t{ .hw = \u0026gxbb_vdec_1.hw },\n--\ndrivers/clk/meson/meson8-ddr.c=26=static struct clk_regmap meson8_ddr_pll_dco = {\n--\ndrivers/clk/meson/meson8-ddr.c-57-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/meson8-ddr.c:58:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/meson8-ddr.c-59-\t\t},\n--\ndrivers/clk/meson/meson8b.c=122=static struct clk_regmap meson8b_fixed_pll_dco = {\n--\ndrivers/clk/meson/meson8b.c-158-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/meson8b.c:159:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/meson8b.c:160:\t\t\t.name = \"xtal\",\ndrivers/clk/meson/meson8b.c-161-\t\t\t.index = -1,\n--\ndrivers/clk/meson/meson8b.c=188=static struct clk_fixed_factor hdmi_pll_dco_in = {\n--\ndrivers/clk/meson/meson8b.c-194-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/meson8b.c:195:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/meson8b.c-196-\t\t\t.index = -1,\n--\ndrivers/clk/meson/meson8b.c=321=static struct clk_regmap meson8b_sys_pll_dco = {\n--\ndrivers/clk/meson/meson8b.c-353-\t\t.parent_data = \u0026(const struct clk_parent_data) {\ndrivers/clk/meson/meson8b.c:354:\t\t\t.fw_name = \"xtal\",\ndrivers/clk/meson/meson8b.c:355:\t\t\t.name = \"xtal\",\ndrivers/clk/meson/meson8b.c-356-\t\t\t.index = -1,\n--\ndrivers/clk/meson/meson8b.c=731=static struct clk_regmap meson8b_cpu_in_sel = {\n--\ndrivers/clk/meson/meson8b.c-740-\t\t.parent_data = (const struct clk_parent_data[]) {\ndrivers/clk/meson/meson8b.c:741:\t\t\t{ .fw_name = \"xtal\", .name = \"xtal\", .index = -1, },\ndrivers/clk/meson/meson8b.c-742-\t\t\t{ .hw = \u0026meson8b_sys_pll.hw, },\n--\ndrivers/clk/meson/meson8b.c=836=static struct clk_regmap meson8b_cpu_clk = {\n--\ndrivers/clk/meson/meson8b.c-845-\t\t.parent_data = (const struct clk_parent_data[]) {\ndrivers/clk/meson/meson8b.c:846:\t\t\t{ .fw_name = \"xtal\", .name = \"xtal\", .index = -1, },\ndrivers/clk/meson/meson8b.c-847-\t\t\t{ .hw = \u0026meson8b_cpu_scale_out_sel.hw, },\n--\n\n"
} |
| 31/2 | 2026/08/12 20:01 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 128173 output: 19 thoughts: 456 Thoughts: **Clock Confusion: Dissecting the Kernel's "xtal" Clock Handling**
Okay, so there are a *lot* of "xtal" clocks kicking around in the kernel, and I need to figure out how they're being managed. Let's see... `devm_clk_get_enabled(dev, "xtal")`. Right, that's trying to grab a clock associated with a device and a connection ID of "xtal." So, if my device is an I2C device, say, `0-0068`, it'll first look for a clock directly linked to *that* device ID ("0-0068") and the "xtal" connection. If that fails, it'll try again, but this time with a null device ID and "xtal" connection.
Hang on... does `clk_get` really fallback like that? Yes, I should check `clk_find` in `drivers/clk/clkdev.c`. I believe it searches the `clocks` list. The preferred match is when both device ID and connection ID line up perfectly. But if there isn't an exact match, it *can* match using a NULL device ID and the "xtal" connection ID. Okay, let's see if there is a clock registered as such.
Ah, here in `drivers/clk/clk-gemini.c`, I see:
```c
hw = clk_hw_register_fixed_rate(NULL, "xtal", NULL, 0, freq);
```
Wait a second. Now that I look at it, this is registering a clock with the *name* "xtal", but I need to use `clk_get` with `con_id`. `devm_clk_get`'s second argument is the connection ID (con_id)! That means it's matching against the `con_id` in the `clk_lookup` table, NOT the clock name. That is important.
Hmm... Does `CONFIG_COMMON_CLK` change anything? Maybe if the connection ID lookup fails, `clk_get` will match by clock *name* if `dev_id` and `con_id` lookup fails? I need to double-check the `clk_get` implementation. Let me go look at `drivers/clk/clk.c` or `drivers/clk/clkdev.c` again.
|
| 32/2 | 2026/08/12 20:01 | tool | grepper |
0mArgs:{
"Expression": "clk_get_sys"
}Results: {
"Output": "arch/arm/kernel/smp_twd.c=191=static void twd_get_clock(struct device_node *np)\n--\narch/arm/kernel/smp_twd.c-197-\telse\narch/arm/kernel/smp_twd.c:198:\t\ttwd_clk = clk_get_sys(\"smp_twd\", NULL);\narch/arm/kernel/smp_twd.c-199-\n--\narch/arm/mach-dove/pcie.c=192=static void __init add_pcie_port(int index, void __iomem *base)\n--\narch/arm/mach-dove/pcie.c-197-\t\tstruct pcie_port *pp = \u0026pcie_port[num_pcie_ports++];\narch/arm/mach-dove/pcie.c:198:\t\tstruct clk *clk = clk_get_sys(\"pcie\", (index ? \"1\" : \"0\"));\narch/arm/mach-dove/pcie.c-199-\n--\narch/arm/mach-imx/mach-imx6q.c=79=static void __init imx6q_1588_init(void)\n--\narch/arm/mach-imx/mach-imx6q.c-106-\narch/arm/mach-imx/mach-imx6q.c:107:\tenet_ref = clk_get_sys(NULL, \"enet_ref\");\narch/arm/mach-imx/mach-imx6q.c-108-\tif (IS_ERR(enet_ref)) {\n--\narch/arm/mach-imx/system.c=66=void __init mxc_arch_reset_init(void __iomem *base)\n--\narch/arm/mach-imx/system.c-69-\narch/arm/mach-imx/system.c:70:\twdog_clk = clk_get_sys(\"imx2-wdt.0\", NULL);\narch/arm/mach-imx/system.c-71-\tif (IS_ERR(wdog_clk))\n--\narch/arm/mach-mxs/mach-mxs.c=232=static inline void enable_clk_enet_out(void)\narch/arm/mach-mxs/mach-mxs.c-233-{\narch/arm/mach-mxs/mach-mxs.c:234:\tstruct clk *clk = clk_get_sys(\"enet_out\", NULL);\narch/arm/mach-mxs/mach-mxs.c-235-\n--\narch/arm/mach-omap1/clock.c=821=void propagate_rate(struct omap1_clk *tclk)\n--\narch/arm/mach-omap1/clock.c-828-\narch/arm/mach-omap1/clock.c:829:\tclkp = clk_get_sys(NULL, clk_hw_get_name(\u0026tclk-\u003ehw));\narch/arm/mach-omap1/clock.c-830-\tif (WARN_ON(!clkp))\n--\narch/arm/mach-omap2/omap_device.c=50=static void _add_clkdev(struct omap_device *od, const char *clk_alias,\n--\narch/arm/mach-omap2/omap_device.c-60-\narch/arm/mach-omap2/omap_device.c:61:\tr = clk_get_sys(dev_name(\u0026od-\u003epdev-\u003edev), clk_alias);\narch/arm/mach-omap2/omap_device.c-62-\tif (!IS_ERR(r)) {\n--\narch/arm/mach-omap2/omap_device.c-68-\narch/arm/mach-omap2/omap_device.c:69:\tr = clk_get_sys(NULL, clk_name);\narch/arm/mach-omap2/omap_device.c-70-\n--\narch/arm/mach-spear/spear13xx.c=98=void __init spear13xx_timer_init(void)\n--\narch/arm/mach-spear/spear13xx.c-105-\t/* get the system timer clock */\narch/arm/mach-spear/spear13xx.c:106:\tgpt_clk = clk_get_sys(\"gpt0\", NULL);\narch/arm/mach-spear/spear13xx.c-107-\tif (IS_ERR(gpt_clk)) {\n--\narch/arm/mach-spear/spear3xx.c=71=void __init spear3xx_timer_init(void)\n--\narch/arm/mach-spear/spear3xx.c-78-\t/* get the system timer clock */\narch/arm/mach-spear/spear3xx.c:79:\tgpt_clk = clk_get_sys(\"gpt0\", NULL);\narch/arm/mach-spear/spear3xx.c-80-\tif (IS_ERR(gpt_clk)) {\n--\narch/arm/mach-spear/spear6xx.c=368=static void __init spear6xx_timer_init(void)\n--\narch/arm/mach-spear/spear6xx.c-375-\t/* get the system timer clock */\narch/arm/mach-spear/spear6xx.c:376:\tgpt_clk = clk_get_sys(\"gpt0\", NULL);\narch/arm/mach-spear/spear6xx.c-377-\tif (IS_ERR(gpt_clk)) {\n--\narch/arm/mach-spear/time.c=204=void __init spear_setup_of_timer(void)\n--\narch/arm/mach-spear/time.c-226-\narch/arm/mach-spear/time.c:227:\tgpt_clk = clk_get_sys(\"gpt0\", NULL);\narch/arm/mach-spear/time.c-228-\tif (IS_ERR(gpt_clk)) {\n--\narch/powerpc/platforms/512x/mpc512x_shared.c=55=static void mpc512x_set_pixel_clock(unsigned int pixclock)\n--\narch/powerpc/platforms/512x/mpc512x_shared.c-70-\t\t/* backwards compat with device trees that lack clock specs */\narch/powerpc/platforms/512x/mpc512x_shared.c:71:\t\tclk_diu = clk_get_sys(np-\u003ename, \"ipg\");\narch/powerpc/platforms/512x/mpc512x_shared.c-72-\t}\n--\ndrivers/clk/clkdev.c=72=struct clk_hw *clk_find_hw(const char *dev_id, const char *con_id)\n--\ndrivers/clk/clkdev.c-85-\ndrivers/clk/clkdev.c:86:static struct clk *__clk_get_sys(struct device *dev, const char *dev_id,\ndrivers/clk/clkdev.c-87-\t\t\t\t const char *con_id)\n--\ndrivers/clk/clkdev.c-93-\ndrivers/clk/clkdev.c:94:struct clk *clk_get_sys(const char *dev_id, const char *con_id)\ndrivers/clk/clkdev.c-95-{\ndrivers/clk/clkdev.c:96:\treturn __clk_get_sys(NULL, dev_id, con_id);\ndrivers/clk/clkdev.c-97-}\ndrivers/clk/clkdev.c:98:EXPORT_SYMBOL(clk_get_sys);\ndrivers/clk/clkdev.c-99-\ndrivers/clk/clkdev.c=100=struct clk *clk_get(struct device *dev, const char *con_id)\n--\ndrivers/clk/clkdev.c-110-\ndrivers/clk/clkdev.c:111:\treturn __clk_get_sys(dev, dev_id, con_id);\ndrivers/clk/clkdev.c-112-}\n--\ndrivers/clk/davinci/psc.c=176=static int davinci_psc_genpd_attach_dev(struct generic_pm_domain *pm_domain,\n--\ndrivers/clk/davinci/psc.c-186-\t */\ndrivers/clk/davinci/psc.c:187:\tclk = clk_get_sys(best_dev_name(lpsc-\u003edev), clk_hw_get_name(\u0026lpsc-\u003ehw));\ndrivers/clk/davinci/psc.c-188-\tif (IS_ERR(clk))\n--\ndrivers/clk/ti/clk-2xxx.c=217=static int __init omap2xxx_dt_clk_init(int soc_type)\n--\ndrivers/clk/ti/clk-2xxx.c-233-\tpr_info(\"Clocking rate (Crystal/DPLL/MPU): %ld.%01ld/%ld/%ld MHz\\n\",\ndrivers/clk/ti/clk-2xxx.c:234:\t\t(clk_get_rate(clk_get_sys(NULL, \"sys_ck\")) / 1000000),\ndrivers/clk/ti/clk-2xxx.c:235:\t\t(clk_get_rate(clk_get_sys(NULL, \"sys_ck\")) / 100000) % 10,\ndrivers/clk/ti/clk-2xxx.c:236:\t\t(clk_get_rate(clk_get_sys(NULL, \"dpll_ck\")) / 1000000),\ndrivers/clk/ti/clk-2xxx.c:237:\t\t(clk_get_rate(clk_get_sys(NULL, \"mpu_ck\")) / 1000000));\ndrivers/clk/ti/clk-2xxx.c-238-\n--\ndrivers/clk/ti/clk-33xx.c=272=int __init am33xx_dt_clk_init(void)\n--\ndrivers/clk/ti/clk-33xx.c-293-\ndrivers/clk/ti/clk-33xx.c:294:\tclk1 = clk_get_sys(NULL, \"sys_clkin_ck\");\ndrivers/clk/ti/clk-33xx.c:295:\tclk2 = clk_get_sys(NULL, \"timer3_fck\");\ndrivers/clk/ti/clk-33xx.c-296-\tclk_set_parent(clk2, clk1);\ndrivers/clk/ti/clk-33xx.c-297-\ndrivers/clk/ti/clk-33xx.c:298:\tclk2 = clk_get_sys(NULL, \"timer6_fck\");\ndrivers/clk/ti/clk-33xx.c-299-\tclk_set_parent(clk2, clk1);\n--\ndrivers/clk/ti/clk-33xx.c-306-\t */\ndrivers/clk/ti/clk-33xx.c:307:\tclk1 = clk_get_sys(NULL, \"wdt1_fck\");\ndrivers/clk/ti/clk-33xx.c:308:\tclk2 = clk_get_sys(NULL, \"clkdiv32k_ick\");\ndrivers/clk/ti/clk-33xx.c-309-\tclk_set_parent(clk1, clk2);\n--\ndrivers/clk/ti/clk-3xxx.c=300=static int __init omap3xxx_dt_clk_init(int soc_type)\n--\ndrivers/clk/ti/clk-3xxx.c-328-\tpr_info(\"Clocking rate (Crystal/Core/MPU): %ld.%01ld/%ld/%ld MHz\\n\",\ndrivers/clk/ti/clk-3xxx.c:329:\t\t(clk_get_rate(clk_get_sys(NULL, \"osc_sys_ck\")) / 1000000),\ndrivers/clk/ti/clk-3xxx.c:330:\t\t(clk_get_rate(clk_get_sys(NULL, \"osc_sys_ck\")) / 100000) % 10,\ndrivers/clk/ti/clk-3xxx.c:331:\t\t(clk_get_rate(clk_get_sys(NULL, \"core_ck\")) / 1000000),\ndrivers/clk/ti/clk-3xxx.c:332:\t\t(clk_get_rate(clk_get_sys(NULL, \"arm_fck\")) / 1000000));\ndrivers/clk/ti/clk-3xxx.c-333-\n--\ndrivers/clk/ti/clk-43xx.c=273=int __init am43xx_dt_clk_init(void)\n--\ndrivers/clk/ti/clk-43xx.c-295-\t */\ndrivers/clk/ti/clk-43xx.c:296:\tclk1 = clk_get_sys(NULL, \"cpsw_cpts_rft_clk\");\ndrivers/clk/ti/clk-43xx.c:297:\tclk2 = clk_get_sys(NULL, \"dpll_core_m5_ck\");\ndrivers/clk/ti/clk-43xx.c-298-\tclk_set_parent(clk1, clk2);\n--\ndrivers/clk/ti/clk-44xx.c=794=int __init omap4xxx_dt_clk_init(void)\n--\ndrivers/clk/ti/clk-44xx.c-808-\t */\ndrivers/clk/ti/clk-44xx.c:809:\tusb_dpll = clk_get_sys(NULL, \"dpll_usb_ck\");\ndrivers/clk/ti/clk-44xx.c-810-\trc = clk_set_rate(usb_dpll, OMAP4_DPLL_USB_DEFFREQ);\n--\ndrivers/clk/ti/clk-44xx.c-819-\t */\ndrivers/clk/ti/clk-44xx.c:820:\tabe_dpll_ref = clk_get_sys(NULL, \"abe_dpll_refclk_mux_ck\");\ndrivers/clk/ti/clk-44xx.c:821:\tsys_32k_ck = clk_get_sys(NULL, \"sys_32k_ck\");\ndrivers/clk/ti/clk-44xx.c-822-\trc = clk_set_parent(abe_dpll_ref, sys_32k_ck);\ndrivers/clk/ti/clk-44xx.c:823:\tabe_dpll = clk_get_sys(NULL, \"dpll_abe_ck\");\ndrivers/clk/ti/clk-44xx.c-824-\tif (!rc)\n--\ndrivers/clk/ti/clk-54xx.c=611=int __init omap5xxx_dt_clk_init(void)\n--\ndrivers/clk/ti/clk-54xx.c-621-\ndrivers/clk/ti/clk-54xx.c:622:\tabe_dpll_ref = clk_get_sys(NULL, \"abe_dpll_clk_mux\");\ndrivers/clk/ti/clk-54xx.c:623:\tsys_32k_ck = clk_get_sys(NULL, \"sys_32k_ck\");\ndrivers/clk/ti/clk-54xx.c-624-\trc = clk_set_parent(abe_dpll_ref, sys_32k_ck);\n--\ndrivers/clk/ti/clk-54xx.c-630-\t */\ndrivers/clk/ti/clk-54xx.c:631:\tabe_dpll_byp = clk_get_sys(NULL, \"abe_dpll_bypass_clk_mux\");\ndrivers/clk/ti/clk-54xx.c-632-\tif (!rc)\n--\ndrivers/clk/ti/clk-54xx.c-634-\ndrivers/clk/ti/clk-54xx.c:635:\tabe_dpll = clk_get_sys(NULL, \"dpll_abe_ck\");\ndrivers/clk/ti/clk-54xx.c-636-\tif (!rc)\n--\ndrivers/clk/ti/clk-54xx.c-640-\ndrivers/clk/ti/clk-54xx.c:641:\tabe_dpll = clk_get_sys(NULL, \"dpll_abe_m2x2_ck\");\ndrivers/clk/ti/clk-54xx.c-642-\tif (!rc)\n--\ndrivers/clk/ti/clk-54xx.c-646-\ndrivers/clk/ti/clk-54xx.c:647:\tusb_dpll = clk_get_sys(NULL, \"dpll_usb_ck\");\ndrivers/clk/ti/clk-54xx.c-648-\trc = clk_set_rate(usb_dpll, OMAP5_DPLL_USB_DEFFREQ);\n--\ndrivers/clk/ti/clk-54xx.c-651-\ndrivers/clk/ti/clk-54xx.c:652:\tusb_dpll = clk_get_sys(NULL, \"dpll_usb_m2_ck\");\ndrivers/clk/ti/clk-54xx.c-653-\trc = clk_set_rate(usb_dpll, OMAP5_DPLL_USB_DEFFREQ/2);\n--\ndrivers/clk/ti/clk-7xx.c=944=int __init dra7xx_dt_clk_init(void)\n--\ndrivers/clk/ti/clk-7xx.c-954-\ndrivers/clk/ti/clk-7xx.c:955:\tdpll_ck = clk_get_sys(NULL, \"dpll_gmac_ck\");\ndrivers/clk/ti/clk-7xx.c-956-\trc = clk_set_rate(dpll_ck, DRA7_DPLL_GMAC_DEFFREQ);\n--\ndrivers/clk/ti/clk-7xx.c-959-\ndrivers/clk/ti/clk-7xx.c:960:\tdpll_ck = clk_get_sys(NULL, \"dpll_usb_ck\");\ndrivers/clk/ti/clk-7xx.c-961-\trc = clk_set_rate(dpll_ck, DRA7_DPLL_USB_DEFFREQ);\n--\ndrivers/clk/ti/clk-7xx.c-964-\ndrivers/clk/ti/clk-7xx.c:965:\tdpll_ck = clk_get_sys(NULL, \"dpll_usb_m2_ck\");\ndrivers/clk/ti/clk-7xx.c-966-\trc = clk_set_rate(dpll_ck, DRA7_DPLL_USB_DEFFREQ/2);\n--\ndrivers/clk/ti/clk-7xx.c-969-\ndrivers/clk/ti/clk-7xx.c:970:\thdcp_ck = clk_get_sys(NULL, \"dss_deshdcp_clk\");\ndrivers/clk/ti/clk-7xx.c-971-\trc = clk_prepare_enable(hdcp_ck);\n--\ndrivers/clocksource/timer-sp804.c=66=static long __init sp804_get_clock_rate(struct clk *clk, const char *name)\n--\ndrivers/clocksource/timer-sp804.c-70-\tif (!clk)\ndrivers/clocksource/timer-sp804.c:71:\t\tclk = clk_get_sys(\"sp804\", name);\ndrivers/clocksource/timer-sp804.c-72-\tif (IS_ERR(clk)) {\n--\ndrivers/cpufreq/pxa2xx-cpufreq.c=298=static int __init pxa_cpu_init(void)\n--\ndrivers/cpufreq/pxa2xx-cpufreq.c-301-\ndrivers/cpufreq/pxa2xx-cpufreq.c:302:\tpxa_cpufreq_data.clk_core = clk_get_sys(NULL, \"core\");\ndrivers/cpufreq/pxa2xx-cpufreq.c-303-\tif (IS_ERR(pxa_cpufreq_data.clk_core))\n--\ndrivers/gpu/drm/tegra/rgb.c=208=int tegra_dc_rgb_probe(struct tegra_dc *dc)\n--\ndrivers/gpu/drm/tegra/rgb.c-256-\ndrivers/gpu/drm/tegra/rgb.c:257:\trgb-\u003epll_d_out0 = clk_get_sys(NULL, \"pll_d_out0\");\ndrivers/gpu/drm/tegra/rgb.c-258-\tif (IS_ERR(rgb-\u003epll_d_out0)) {\n--\ndrivers/gpu/drm/tegra/rgb.c-264-\tif (dc-\u003esoc-\u003ehas_pll_d2_out0) {\ndrivers/gpu/drm/tegra/rgb.c:265:\t\trgb-\u003epll_d2_out0 = clk_get_sys(NULL, \"pll_d2_out0\");\ndrivers/gpu/drm/tegra/rgb.c-266-\t\tif (IS_ERR(rgb-\u003epll_d2_out0)) {\n--\ndrivers/net/wireless/ralink/rt2x00/rt2800lib.c=5836=static int rt2800_init_registers(struct rt2x00_dev *rt2x00dev)\n--\ndrivers/net/wireless/ralink/rt2x00/rt2800lib.c-6273-\t} else if (rt2x00_is_soc(rt2x00dev)) {\ndrivers/net/wireless/ralink/rt2x00/rt2800lib.c:6274:\t\tstruct clk *clk = clk_get_sys(\"bus\", NULL);\ndrivers/net/wireless/ralink/rt2x00/rt2800lib.c-6275-\t\tint rate;\n--\ndrivers/net/wireless/ralink/rt2x00/rt2800lib.c-6277-\t\tif (IS_ERR(clk)) {\ndrivers/net/wireless/ralink/rt2x00/rt2800lib.c:6278:\t\t\tclk = clk_get_sys(\"cpu\", NULL);\ndrivers/net/wireless/ralink/rt2x00/rt2800lib.c-6279-\n--\ndrivers/tty/serial/mpc52xx_uart.c=566=static int __init mpc512x_psc_fifoc_init(void)\n--\ndrivers/tty/serial/mpc52xx_uart.c-584-\t\t/* backwards compat with device trees that lack clock specs */\ndrivers/tty/serial/mpc52xx_uart.c:585:\t\tclk = clk_get_sys(np-\u003ename, \"ipg\");\ndrivers/tty/serial/mpc52xx_uart.c-586-\t}\n--\ninclude/linux/clk.h=957=struct clk *clk_get_parent(struct clk *clk);\n--\ninclude/linux/clk.h-959-/**\ninclude/linux/clk.h:960: * clk_get_sys - get a clock based upon the device name\ninclude/linux/clk.h-961- * @dev_id: device name\n--\ninclude/linux/clk.h-971- *\ninclude/linux/clk.h:972: * clk_get_sys should not be called from within interrupt context.\ninclude/linux/clk.h-973- */\ninclude/linux/clk.h:974:struct clk *clk_get_sys(const char *dev_id, const char *con_id);\ninclude/linux/clk.h-975-\n--\ninclude/linux/clk.h=1152=static inline struct clk *clk_get_parent(struct clk *clk)\n--\ninclude/linux/clk.h-1156-\ninclude/linux/clk.h:1157:static inline struct clk *clk_get_sys(const char *dev_id, const char *con_id)\ninclude/linux/clk.h-1158-{\n"
} |
| 33/2 | 2026/08/12 20:01 | llm | patch-evaluator |
1mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 135469 output: 27 thoughts: 122 Thoughts: **Clock Registration Analysis** Okay, so I'm trying to figure out how this global clock named "xtal" is being accessed. My initial thought was that `clk_get_sys(NULL, "xtal")` would be the function to retrieve it, where "xtal" is the name. But first, I need to confirm if such a global clock even exists. My next step was to search the code for any registration of the "xtal" clock using a `NULL` device ID. I specifically looked for patterns like `clk_register_clkdev.*NULL.*"xtal"` - something that would link the global clock to the string "xtal" and not be device-specific. Nothing came up. Then it hit me! The registration function `clk_register_clkdev(clk, "xtal", NULL)` uses "xtal" as the connection ID (the second argument), and sets the device ID to `NULL` (the third argument), indicating a global clock. So, now my search needs to be for `clk_register_clkdev.*"xtal".*NULL`. That should reveal how this global clock is actually being registered, allowing me to fully understand its usage and potential conflicts. |
| 34/2 | 2026/08/12 20:03 | tool | grepper |
0mError:no matches Args: {
"Expression": "clk_register_clkdev.*\"xtal\".*NULL"
}Results: {
"error": "no matches"
} |
| 35/2 | 2026/08/12 20:03 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 135891 output: 25 thoughts: 15 Thoughts: Okay, here's my interpretation of that thought as a summary, written in the first person, assuming the user is an expert. **Considering Clock Device Registration for a Crystal Oscillator** Right, so I'm deep in this clock management code, and my mind keeps circling back to that crystal oscillator. I'm wondering if I can leverage that `clk_register_clkdev.*"xtal"` mechanism somehow. It seems like a potential fit, but I need to quickly evaluate the specifics. Could that family of functions and structures give me the hooks I need to properly register, manage, and control the crystal's clock output? I have to look more closely at that code and see if the abstraction level aligns with my needs. I'm especially interested in the registration aspect... I don't want to reinvent the wheel if it is already there. I'm thinking about the low-level details of clock dev registration and will have to see if the available options are granular enough to provide me the level of control I need. Ultimately, I need to figure out if it is the right approach to use what's already there or implement something custom. |
| 36/2 | 2026/08/12 20:04 | tool | grepper |
0mError:no matches Args: {
"Expression": "clk_register_clkdev.*\"xtal\""
}Results: {
"error": "no matches"
} |
| 37/2 | 2026/08/12 20:04 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 136183 output: 23 thoughts: 13 Thoughts: Okay, here's my interpretation of that thought as a summary, written in the first person, assuming an expert user:
**Investigating Clock Device Creation and "xtal"**
Hmm, so I'm thinking about clock device creation, specifically the `clkdev_create.*` family of functions. My mind immediately jumped to the "xtal" substring. It's making me wonder: what's the typical handling within these `clkdev_create` calls when dealing with a crystal oscillator ("xtal")? Are there specific flags, configurations, or device types associated with it that I should be mindful of during initialization or probe? I need to refresh my understanding of how the clock framework manages and represents external crystal oscillators and their impact on system clock trees. This might relate to a recent issue, or something I am planning to implement, or something I have debugged.
|
| 38/2 | 2026/08/12 20:04 | tool | grepper |
0mError:no matches Args: {
"Expression": "clkdev_create.*\"xtal\""
}Results: {
"error": "no matches"
} |
| 39/2 | 2026/08/12 20:04 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 136415 output: 18 thoughts: 9 Thoughts: Here's my thinking process: **Diving into Clock Device Management** Okay, so I'm thinking about clock device management, and specifically, the function `clkdev_add`. It's a key function in a system that relies on a clock framework, isn't it? I'm picturing it as the core mechanism for registering a clock device within the system. It's the point where the framework gets to know about a new clock source or consumer. I need to refresh my memory on the details here. What are the key parameters? What data structures are involved? What locking is required, and what's the typical flow of operations around this call? How does it interact with the probe functions? Is it synchronous, or is there some deferred initialization involved? I need to understand how `clkdev_add` handles the relationships between different clock domains. I'm going to dig a little deeper on this. |
| 40/2 | 2026/08/12 20:04 | tool | grepper |
0mArgs:{
"Expression": "clkdev_add"
}Results: {
"Output": "\nFull output is too long, showing 500 out of 569 lines.\nUse more precise expression if possible.\n\narch/arm/mach-dove/common.c=81=static void __init dove_clk_init(void)\n--\narch/arm/mach-dove/common.c-107-\narch/arm/mach-dove/common.c:108:\torion_clkdev_add(NULL, \"orion_spi.0\", tclk);\narch/arm/mach-dove/common.c:109:\torion_clkdev_add(NULL, \"orion_spi.1\", tclk);\narch/arm/mach-dove/common.c:110:\torion_clkdev_add(NULL, \"orion_wdt\", tclk);\narch/arm/mach-dove/common.c:111:\torion_clkdev_add(NULL, \"mv64xxx_i2c.0\", tclk);\narch/arm/mach-dove/common.c-112-\narch/arm/mach-dove/common.c:113:\torion_clkdev_add(NULL, \"orion-ehci.0\", usb0);\narch/arm/mach-dove/common.c:114:\torion_clkdev_add(NULL, \"orion-ehci.1\", usb1);\narch/arm/mach-dove/common.c:115:\torion_clkdev_add(NULL, \"mv643xx_eth_port.0\", ge);\narch/arm/mach-dove/common.c:116:\torion_clkdev_add(NULL, \"sata_mv.0\", sata);\narch/arm/mach-dove/common.c:117:\torion_clkdev_add(\"0\", \"pcie\", pex0);\narch/arm/mach-dove/common.c:118:\torion_clkdev_add(\"1\", \"pcie\", pex1);\narch/arm/mach-dove/common.c:119:\torion_clkdev_add(NULL, \"sdhci-dove.0\", sdio0);\narch/arm/mach-dove/common.c:120:\torion_clkdev_add(NULL, \"sdhci-dove.1\", sdio1);\narch/arm/mach-dove/common.c:121:\torion_clkdev_add(NULL, \"orion_nand\", nand);\narch/arm/mach-dove/common.c:122:\torion_clkdev_add(NULL, \"cafe1000-ccic.0\", camera);\narch/arm/mach-dove/common.c:123:\torion_clkdev_add(NULL, \"mvebu-audio.0\", i2s0);\narch/arm/mach-dove/common.c:124:\torion_clkdev_add(NULL, \"mvebu-audio.1\", i2s1);\narch/arm/mach-dove/common.c:125:\torion_clkdev_add(NULL, \"mv_crypto\", crypto);\narch/arm/mach-dove/common.c:126:\torion_clkdev_add(NULL, \"dove-ac97\", ac97);\narch/arm/mach-dove/common.c:127:\torion_clkdev_add(NULL, \"dove-pdma\", pdma);\narch/arm/mach-dove/common.c:128:\torion_clkdev_add(NULL, MV_XOR_NAME \".0\", xor0);\narch/arm/mach-dove/common.c:129:\torion_clkdev_add(NULL, MV_XOR_NAME \".1\", xor1);\narch/arm/mach-dove/common.c-130-}\n--\narch/arm/plat-orion/common.c-26-/* Create a clkdev entry for a given device/clk */\narch/arm/plat-orion/common.c:27:void __init orion_clkdev_add(const char *con_id, const char *dev_id,\narch/arm/plat-orion/common.c-28-\t\t\t struct clk *clk)\n--\narch/arm/plat-orion/common.c=37=void __init orion_clkdev_init(struct clk *tclk)\narch/arm/plat-orion/common.c-38-{\narch/arm/plat-orion/common.c:39:\torion_clkdev_add(NULL, \"orion_spi.0\", tclk);\narch/arm/plat-orion/common.c:40:\torion_clkdev_add(NULL, \"orion_spi.1\", tclk);\narch/arm/plat-orion/common.c:41:\torion_clkdev_add(NULL, MV643XX_ETH_NAME \".0\", tclk);\narch/arm/plat-orion/common.c:42:\torion_clkdev_add(NULL, MV643XX_ETH_NAME \".1\", tclk);\narch/arm/plat-orion/common.c:43:\torion_clkdev_add(NULL, MV643XX_ETH_NAME \".2\", tclk);\narch/arm/plat-orion/common.c:44:\torion_clkdev_add(NULL, MV643XX_ETH_NAME \".3\", tclk);\narch/arm/plat-orion/common.c:45:\torion_clkdev_add(NULL, \"orion_wdt\", tclk);\narch/arm/plat-orion/common.c:46:\torion_clkdev_add(NULL, MV64XXX_I2C_CTLR_NAME \".0\", tclk);\narch/arm/plat-orion/common.c-47-}\n--\narch/arm/plat-orion/include/plat/common.h=95=void __init orion_crypto_init(unsigned long mapbase,\n--\narch/arm/plat-orion/include/plat/common.h-99-\narch/arm/plat-orion/include/plat/common.h:100:void __init orion_clkdev_add(const char *con_id, const char *dev_id,\narch/arm/plat-orion/include/plat/common.h-101-\t\t\t struct clk *clk);\n--\narch/m68k/coldfire/m5206.c=49=void __init config_BSP(char *commandp, int size)\n--\narch/m68k/coldfire/m5206.c-64-\narch/m68k/coldfire/m5206.c:65:\tclkdev_add_table(m5206_clk_lookup, ARRAY_SIZE(m5206_clk_lookup));\narch/m68k/coldfire/m5206.c-66-}\n--\narch/m68k/coldfire/m520x.c=107=static void __init m520x_clk_init(void)\n--\narch/m68k/coldfire/m520x.c-117-\narch/m68k/coldfire/m520x.c:118:\tclkdev_add_table(m520x_clk_lookup, ARRAY_SIZE(m520x_clk_lookup));\narch/m68k/coldfire/m520x.c-119-}\n--\narch/m68k/coldfire/m523x.c=87=void __init config_BSP(char *commandp, int size)\n--\narch/m68k/coldfire/m523x.c-93-\narch/m68k/coldfire/m523x.c:94:\tclkdev_add_table(m523x_clk_lookup, ARRAY_SIZE(m523x_clk_lookup));\narch/m68k/coldfire/m523x.c-95-}\n--\narch/m68k/coldfire/m5249.c=124=void __init config_BSP(char *commandp, int size)\n--\narch/m68k/coldfire/m5249.c-133-\narch/m68k/coldfire/m5249.c:134:\tclkdev_add_table(m5249_clk_lookup, ARRAY_SIZE(m5249_clk_lookup));\narch/m68k/coldfire/m5249.c-135-}\n--\narch/m68k/coldfire/m525x.c=78=void __init config_BSP(char *commandp, int size)\n--\narch/m68k/coldfire/m525x.c-84-\narch/m68k/coldfire/m525x.c:85:\tclkdev_add_table(m525x_clk_lookup, ARRAY_SIZE(m525x_clk_lookup));\narch/m68k/coldfire/m525x.c-86-}\n--\narch/m68k/coldfire/m5272.c=105=static int __init init_BSP(void)\n--\narch/m68k/coldfire/m5272.c-107-\tm5272_uarts_init();\narch/m68k/coldfire/m5272.c:108:\tclkdev_add_table(m5272_clk_lookup, ARRAY_SIZE(m5272_clk_lookup));\narch/m68k/coldfire/m5272.c-109-\treturn 0;\n--\narch/m68k/coldfire/m527x.c=136=void __init config_BSP(char *commandp, int size)\n--\narch/m68k/coldfire/m527x.c-142-\tm527x_i2c_init();\narch/m68k/coldfire/m527x.c:143:\tclkdev_add_table(m527x_clk_lookup, ARRAY_SIZE(m527x_clk_lookup));\narch/m68k/coldfire/m527x.c-144-}\n--\narch/m68k/coldfire/m528x.c=126=void __init config_BSP(char *commandp, int size)\n--\narch/m68k/coldfire/m528x.c-139-\narch/m68k/coldfire/m528x.c:140:\tclkdev_add_table(m528x_clk_lookup, ARRAY_SIZE(m528x_clk_lookup));\narch/m68k/coldfire/m528x.c-141-}\n--\narch/m68k/coldfire/m5307.c=60=void __init config_BSP(char *commandp, int size)\n--\narch/m68k/coldfire/m5307.c-86-\narch/m68k/coldfire/m5307.c:87:\tclkdev_add_table(m5307_clk_lookup, ARRAY_SIZE(m5307_clk_lookup));\narch/m68k/coldfire/m5307.c-88-}\n--\narch/m68k/coldfire/m53xx.c=149=static void __init m53xx_clk_init(void)\n--\narch/m68k/coldfire/m53xx.c-159-\narch/m68k/coldfire/m53xx.c:160:\tclkdev_add_table(m53xx_clk_lookup, ARRAY_SIZE(m53xx_clk_lookup));\narch/m68k/coldfire/m53xx.c-161-}\n--\narch/m68k/coldfire/m5407.c=51=void __init config_BSP(char *commandp, int size)\n--\narch/m68k/coldfire/m5407.c-61-\narch/m68k/coldfire/m5407.c:62:\tclkdev_add_table(m5407_clk_lookup, ARRAY_SIZE(m5407_clk_lookup));\narch/m68k/coldfire/m5407.c-63-}\n--\narch/m68k/coldfire/m5441x.c=220=static void __init m5441x_clk_init(void)\n--\narch/m68k/coldfire/m5441x.c-229-\narch/m68k/coldfire/m5441x.c:230:\tclkdev_add_table(m5411x_clk_lookup, ARRAY_SIZE(m5411x_clk_lookup));\narch/m68k/coldfire/m5441x.c-231-}\n--\narch/m68k/coldfire/m54xx.c=90=void __init config_BSP(char *commandp, int size)\n--\narch/m68k/coldfire/m54xx.c-96-\narch/m68k/coldfire/m54xx.c:97:\tclkdev_add_table(m54xx_clk_lookup, ARRAY_SIZE(m54xx_clk_lookup));\narch/m68k/coldfire/m54xx.c-98-}\n--\narch/mips/bcm63xx/clk.c=546=static int __init bcm63xx_clk_init(void)\n--\narch/mips/bcm63xx/clk.c-549-\tcase BCM3368_CPU_ID:\narch/mips/bcm63xx/clk.c:550:\t\tclkdev_add_table(bcm3368_clks, ARRAY_SIZE(bcm3368_clks));\narch/mips/bcm63xx/clk.c-551-\t\tbreak;\n--\narch/mips/bcm63xx/clk.c-553-\t\tclk_hsspi_pll.rate = HSSPI_PLL_HZ_6328;\narch/mips/bcm63xx/clk.c:554:\t\tclkdev_add_table(bcm6328_clks, ARRAY_SIZE(bcm6328_clks));\narch/mips/bcm63xx/clk.c-555-\t\tbreak;\narch/mips/bcm63xx/clk.c-556-\tcase BCM6338_CPU_ID:\narch/mips/bcm63xx/clk.c:557:\t\tclkdev_add_table(bcm6338_clks, ARRAY_SIZE(bcm6338_clks));\narch/mips/bcm63xx/clk.c-558-\t\tbreak;\narch/mips/bcm63xx/clk.c-559-\tcase BCM6345_CPU_ID:\narch/mips/bcm63xx/clk.c:560:\t\tclkdev_add_table(bcm6345_clks, ARRAY_SIZE(bcm6345_clks));\narch/mips/bcm63xx/clk.c-561-\t\tbreak;\narch/mips/bcm63xx/clk.c-562-\tcase BCM6348_CPU_ID:\narch/mips/bcm63xx/clk.c:563:\t\tclkdev_add_table(bcm6348_clks, ARRAY_SIZE(bcm6348_clks));\narch/mips/bcm63xx/clk.c-564-\t\tbreak;\narch/mips/bcm63xx/clk.c-565-\tcase BCM6358_CPU_ID:\narch/mips/bcm63xx/clk.c:566:\t\tclkdev_add_table(bcm6358_clks, ARRAY_SIZE(bcm6358_clks));\narch/mips/bcm63xx/clk.c-567-\t\tbreak;\n--\narch/mips/bcm63xx/clk.c-569-\t\tclk_hsspi_pll.rate = HSSPI_PLL_HZ_6362;\narch/mips/bcm63xx/clk.c:570:\t\tclkdev_add_table(bcm6362_clks, ARRAY_SIZE(bcm6362_clks));\narch/mips/bcm63xx/clk.c-571-\t\tbreak;\narch/mips/bcm63xx/clk.c-572-\tcase BCM6368_CPU_ID:\narch/mips/bcm63xx/clk.c:573:\t\tclkdev_add_table(bcm6368_clks, ARRAY_SIZE(bcm6368_clks));\narch/mips/bcm63xx/clk.c-574-\t\tbreak;\n--\narch/mips/lantiq/clk.c=27=static struct clk cpu_clk_generic[4];\narch/mips/lantiq/clk.c-28-\narch/mips/lantiq/clk.c:29:void clkdev_add_static(unsigned long cpu, unsigned long fpi,\narch/mips/lantiq/clk.c-30-\t\t\tunsigned long io, unsigned long ppe)\n--\narch/mips/lantiq/clk.h=58=struct clk {\n--\narch/mips/lantiq/clk.h-71-\narch/mips/lantiq/clk.h:72:extern void clkdev_add_static(unsigned long cpu, unsigned long fpi,\narch/mips/lantiq/clk.h-73-\t\t\t\tunsigned long io, unsigned long ppe);\n--\narch/mips/lantiq/falcon/sysctrl.c=135=static void falcon_gpe_enable(void)\n--\narch/mips/lantiq/falcon/sysctrl.c-160-\narch/mips/lantiq/falcon/sysctrl.c:161:static inline void clkdev_add_sys(const char *dev, unsigned int module,\narch/mips/lantiq/falcon/sysctrl.c-162-\t\t\t\t\tunsigned int bits)\n--\narch/mips/lantiq/falcon/sysctrl.c-177-\tclk-\u003ereboot = sysctl_reboot;\narch/mips/lantiq/falcon/sysctrl.c:178:\tclkdev_add(\u0026clk-\u003ecl);\narch/mips/lantiq/falcon/sysctrl.c-179-}\n--\narch/mips/lantiq/falcon/sysctrl.c=181=void __init ltq_soc_init(void)\n--\narch/mips/lantiq/falcon/sysctrl.c-244-\tif (ltq_sys1_r32(SYS1_CPU0CC) \u0026 CPU0CC_CPUDIV)\narch/mips/lantiq/falcon/sysctrl.c:245:\t\tclkdev_add_static(CLOCK_200M, CLOCK_100M, CLOCK_200M, 0);\narch/mips/lantiq/falcon/sysctrl.c-246-\telse\narch/mips/lantiq/falcon/sysctrl.c:247:\t\tclkdev_add_static(CLOCK_400M, CLOCK_100M, CLOCK_200M, 0);\narch/mips/lantiq/falcon/sysctrl.c-248-\narch/mips/lantiq/falcon/sysctrl.c-249-\t/* add our clock domains */\narch/mips/lantiq/falcon/sysctrl.c:250:\tclkdev_add_sys(\"1d810000.gpio\", SYSCTL_SYSETH, ACTS_P0);\narch/mips/lantiq/falcon/sysctrl.c:251:\tclkdev_add_sys(\"1d810100.gpio\", SYSCTL_SYSETH, ACTS_P2);\narch/mips/lantiq/falcon/sysctrl.c:252:\tclkdev_add_sys(\"1e800100.gpio\", SYSCTL_SYS1, ACTS_P1);\narch/mips/lantiq/falcon/sysctrl.c:253:\tclkdev_add_sys(\"1e800200.gpio\", SYSCTL_SYS1, ACTS_P3);\narch/mips/lantiq/falcon/sysctrl.c:254:\tclkdev_add_sys(\"1e800300.gpio\", SYSCTL_SYS1, ACTS_P4);\narch/mips/lantiq/falcon/sysctrl.c:255:\tclkdev_add_sys(\"1db01000.pad\", SYSCTL_SYSETH, ACTS_PADCTRL0);\narch/mips/lantiq/falcon/sysctrl.c:256:\tclkdev_add_sys(\"1db02000.pad\", SYSCTL_SYSETH, ACTS_PADCTRL2);\narch/mips/lantiq/falcon/sysctrl.c:257:\tclkdev_add_sys(\"1e800400.pad\", SYSCTL_SYS1, ACTS_PADCTRL1);\narch/mips/lantiq/falcon/sysctrl.c:258:\tclkdev_add_sys(\"1e800500.pad\", SYSCTL_SYS1, ACTS_PADCTRL3);\narch/mips/lantiq/falcon/sysctrl.c:259:\tclkdev_add_sys(\"1e800600.pad\", SYSCTL_SYS1, ACTS_PADCTRL4);\narch/mips/lantiq/falcon/sysctrl.c:260:\tclkdev_add_sys(\"1e100b00.serial\", SYSCTL_SYS1, ACTS_ASC1_ACT);\narch/mips/lantiq/falcon/sysctrl.c:261:\tclkdev_add_sys(\"1e100c00.serial\", SYSCTL_SYS1, ACTS_ASC0_ACT);\narch/mips/lantiq/falcon/sysctrl.c:262:\tclkdev_add_sys(\"1e100d00.spi\", SYSCTL_SYS1, ACTS_SSC0);\narch/mips/lantiq/falcon/sysctrl.c:263:\tclkdev_add_sys(\"1e200000.i2c\", SYSCTL_SYS1, ACTS_I2C_ACT);\narch/mips/lantiq/falcon/sysctrl.c-264-}\n--\narch/mips/lantiq/xway/gptu.c=111=static void gptu_disable(struct clk *clk)\n--\narch/mips/lantiq/xway/gptu.c-119-\narch/mips/lantiq/xway/gptu.c:120:static inline void clkdev_add_gptu(struct device *dev, const char *con,\narch/mips/lantiq/xway/gptu.c-121-\t\t\t\t\t\t\tunsigned int timer)\n--\narch/mips/lantiq/xway/gptu.c-132-\tclk-\u003ebits = timer;\narch/mips/lantiq/xway/gptu.c:133:\tclkdev_add(\u0026clk-\u003ecl);\narch/mips/lantiq/xway/gptu.c-134-}\n--\narch/mips/lantiq/xway/gptu.c=136=static int gptu_probe(struct platform_device *pdev)\n--\narch/mips/lantiq/xway/gptu.c-170-\t/* register the clocks */\narch/mips/lantiq/xway/gptu.c:171:\tclkdev_add_gptu(\u0026pdev-\u003edev, \"timer1a\", TIMER1A);\narch/mips/lantiq/xway/gptu.c:172:\tclkdev_add_gptu(\u0026pdev-\u003edev, \"timer1b\", TIMER1B);\narch/mips/lantiq/xway/gptu.c:173:\tclkdev_add_gptu(\u0026pdev-\u003edev, \"timer2a\", TIMER2A);\narch/mips/lantiq/xway/gptu.c:174:\tclkdev_add_gptu(\u0026pdev-\u003edev, \"timer2b\", TIMER2B);\narch/mips/lantiq/xway/gptu.c:175:\tclkdev_add_gptu(\u0026pdev-\u003edev, \"timer3a\", TIMER3A);\narch/mips/lantiq/xway/gptu.c:176:\tclkdev_add_gptu(\u0026pdev-\u003edev, \"timer3b\", TIMER3B);\narch/mips/lantiq/xway/gptu.c-177-\n--\narch/mips/lantiq/xway/sysctrl.c=309=static int clkout_enable(struct clk *clk)\n--\narch/mips/lantiq/xway/sysctrl.c-330-/* manage the clock gates via PMU */\narch/mips/lantiq/xway/sysctrl.c:331:static void clkdev_add_pmu(const char *dev, const char *con, bool deactivate,\narch/mips/lantiq/xway/sysctrl.c-332-\t\t\t unsigned int module, unsigned int bits)\n--\narch/mips/lantiq/xway/sysctrl.c-351-\t}\narch/mips/lantiq/xway/sysctrl.c:352:\tclkdev_add(\u0026clk-\u003ecl);\narch/mips/lantiq/xway/sysctrl.c-353-}\n--\narch/mips/lantiq/xway/sysctrl.c-355-/* manage the clock generator */\narch/mips/lantiq/xway/sysctrl.c:356:static void clkdev_add_cgu(const char *dev, const char *con,\narch/mips/lantiq/xway/sysctrl.c-357-\t\t\t\t\tunsigned int bits)\n--\narch/mips/lantiq/xway/sysctrl.c-368-\tclk-\u003ebits = bits;\narch/mips/lantiq/xway/sysctrl.c:369:\tclkdev_add(\u0026clk-\u003ecl);\narch/mips/lantiq/xway/sysctrl.c-370-}\n--\narch/mips/lantiq/xway/sysctrl.c=373=static unsigned long valid_pci_rates[] = {CLOCK_33M, CLOCK_62_5M, 0};\narch/mips/lantiq/xway/sysctrl.c-374-\narch/mips/lantiq/xway/sysctrl.c:375:static void clkdev_add_pci(void)\narch/mips/lantiq/xway/sysctrl.c-376-{\n--\narch/mips/lantiq/xway/sysctrl.c-390-\t\tclk-\u003ebits = PMU_PCI;\narch/mips/lantiq/xway/sysctrl.c:391:\t\tclkdev_add(\u0026clk-\u003ecl);\narch/mips/lantiq/xway/sysctrl.c-392-\t}\n--\narch/mips/lantiq/xway/sysctrl.c-400-\t\tclk_ext-\u003edisable = pci_ext_disable;\narch/mips/lantiq/xway/sysctrl.c:401:\t\tclkdev_add(\u0026clk_ext-\u003ecl);\narch/mips/lantiq/xway/sysctrl.c-402-\t}\n--\narch/mips/lantiq/xway/sysctrl.c=406=static unsigned long valid_clkout_rates[4][5] = {\n--\narch/mips/lantiq/xway/sysctrl.c-412-\narch/mips/lantiq/xway/sysctrl.c:413:static void clkdev_add_clkout(void)\narch/mips/lantiq/xway/sysctrl.c-414-{\n--\narch/mips/lantiq/xway/sysctrl.c-437-\t\tclk-\u003emodule = i;\narch/mips/lantiq/xway/sysctrl.c:438:\t\tclkdev_add(\u0026clk-\u003ecl);\narch/mips/lantiq/xway/sysctrl.c-439-\t}\n--\narch/mips/lantiq/xway/sysctrl.c=443=void __init ltq_soc_init(void)\n--\narch/mips/lantiq/xway/sysctrl.c-485-\t/* add our generic xway clocks */\narch/mips/lantiq/xway/sysctrl.c:486:\tclkdev_add_pmu(\"10000000.fpi\", NULL, 0, 0, PMU_FPI);\narch/mips/lantiq/xway/sysctrl.c:487:\tclkdev_add_pmu(\"1e100a00.gptu\", NULL, 1, 0, PMU_GPT);\narch/mips/lantiq/xway/sysctrl.c:488:\tclkdev_add_pmu(\"1e100bb0.gpio\", NULL, 1, 0, PMU_STP);\narch/mips/lantiq/xway/sysctrl.c:489:\tclkdev_add_pmu(\"1e100c00.serial\", NULL, 0, 0, PMU_ASC1);\narch/mips/lantiq/xway/sysctrl.c:490:\tclkdev_add_pmu(\"1e104100.dma\", NULL, 1, 0, PMU_DMA);\narch/mips/lantiq/xway/sysctrl.c:491:\tclkdev_add_pmu(\"1e100800.spi\", NULL, 1, 0, PMU_SPI);\narch/mips/lantiq/xway/sysctrl.c:492:\tclkdev_add_pmu(\"1e105300.ebu\", NULL, 0, 0, PMU_EBU);\narch/mips/lantiq/xway/sysctrl.c:493:\tclkdev_add_clkout();\narch/mips/lantiq/xway/sysctrl.c-494-\n--\narch/mips/lantiq/xway/sysctrl.c-499-\t} else {\narch/mips/lantiq/xway/sysctrl.c:500:\t\tclkdev_add_pmu(\"1e180000.ethernet\", NULL, 1, 0, PMU_PPE);\narch/mips/lantiq/xway/sysctrl.c-501-\t}\n--\narch/mips/lantiq/xway/sysctrl.c-503-\tif (!of_machine_is_compatible(\"lantiq,ase\"))\narch/mips/lantiq/xway/sysctrl.c:504:\t\tclkdev_add_pci();\narch/mips/lantiq/xway/sysctrl.c-505-\n--\narch/mips/lantiq/xway/sysctrl.c-507-\t of_machine_is_compatible(\"lantiq,ar10\")) {\narch/mips/lantiq/xway/sysctrl.c:508:\t\tclkdev_add_pmu(\"1e108000.switch\", \"gphy0\", 0, 0, PMU_GPHY0);\narch/mips/lantiq/xway/sysctrl.c:509:\t\tclkdev_add_pmu(\"1e108000.switch\", \"gphy1\", 0, 0, PMU_GPHY1);\narch/mips/lantiq/xway/sysctrl.c:510:\t\tclkdev_add_pmu(\"1e108000.switch\", \"gphy2\", 0, 0, PMU_GPHY2);\narch/mips/lantiq/xway/sysctrl.c:511:\t\tclkdev_add_pmu(\"1f203018.usb2-phy\", \"phy\", 1, 2, PMU_ANALOG_USB0_P);\narch/mips/lantiq/xway/sysctrl.c:512:\t\tclkdev_add_pmu(\"1f203034.usb2-phy\", \"phy\", 1, 2, PMU_ANALOG_USB1_P);\narch/mips/lantiq/xway/sysctrl.c-513-\t\t/* rc 0 */\narch/mips/lantiq/xway/sysctrl.c:514:\t\tclkdev_add_pmu(\"1f106800.phy\", \"phy\", 1, 2, PMU_ANALOG_PCIE0_P);\narch/mips/lantiq/xway/sysctrl.c:515:\t\tclkdev_add_pmu(\"1d900000.pcie\", \"msi\", 1, 1, PMU1_PCIE_MSI);\narch/mips/lantiq/xway/sysctrl.c:516:\t\tclkdev_add_pmu(\"1f106800.phy\", \"pdi\", 1, 1, PMU1_PCIE_PDI);\narch/mips/lantiq/xway/sysctrl.c:517:\t\tclkdev_add_pmu(\"1d900000.pcie\", \"ctl\", 1, 1, PMU1_PCIE_CTL);\narch/mips/lantiq/xway/sysctrl.c-518-\t\t/* rc 1 */\narch/mips/lantiq/xway/sysctrl.c:519:\t\tclkdev_add_pmu(\"1f700400.phy\", \"phy\", 1, 2, PMU_ANALOG_PCIE1_P);\narch/mips/lantiq/xway/sysctrl.c:520:\t\tclkdev_add_pmu(\"19000000.pcie\", \"msi\", 1, 1, PMU1_PCIE1_MSI);\narch/mips/lantiq/xway/sysctrl.c:521:\t\tclkdev_add_pmu(\"1f700400.phy\", \"pdi\", 1, 1, PMU1_PCIE1_PDI);\narch/mips/lantiq/xway/sysctrl.c:522:\t\tclkdev_add_pmu(\"19000000.pcie\", \"ctl\", 1, 1, PMU1_PCIE1_CTL);\narch/mips/lantiq/xway/sysctrl.c-523-\t}\n--\narch/mips/lantiq/xway/sysctrl.c-526-\t\tif (ltq_cgu_r32(CGU_SYS) \u0026 (1 \u003c\u003c 5))\narch/mips/lantiq/xway/sysctrl.c:527:\t\t\tclkdev_add_static(CLOCK_266M, CLOCK_133M,\narch/mips/lantiq/xway/sysctrl.c-528-\t\t\t\t\t\tCLOCK_133M, CLOCK_266M);\narch/mips/lantiq/xway/sysctrl.c-529-\t\telse\narch/mips/lantiq/xway/sysctrl.c:530:\t\t\tclkdev_add_static(CLOCK_133M, CLOCK_133M,\narch/mips/lantiq/xway/sysctrl.c-531-\t\t\t\t\t\tCLOCK_133M, CLOCK_133M);\narch/mips/lantiq/xway/sysctrl.c:532:\t\tclkdev_add_pmu(\"1e101000.usb\", \"otg\", 1, 0, PMU_USB0);\narch/mips/lantiq/xway/sysctrl.c:533:\t\tclkdev_add_pmu(\"1f203018.usb2-phy\", \"phy\", 1, 0, PMU_USB0_P);\narch/mips/lantiq/xway/sysctrl.c:534:\t\tclkdev_add_pmu(\"1e180000.ethernet\", \"ppe\", 1, 0, PMU_PPE);\narch/mips/lantiq/xway/sysctrl.c:535:\t\tclkdev_add_cgu(\"1e180000.ethernet\", \"ephycgu\", CGU_EPHY);\narch/mips/lantiq/xway/sysctrl.c:536:\t\tclkdev_add_pmu(\"1e180000.ethernet\", \"ephy\", 1, 0, PMU_EPHY);\narch/mips/lantiq/xway/sysctrl.c:537:\t\tclkdev_add_pmu(\"1e103000.sdio\", NULL, 1, 0, PMU_ASE_SDIO);\narch/mips/lantiq/xway/sysctrl.c:538:\t\tclkdev_add_pmu(\"1e116000.mei\", \"dfe\", 1, 0, PMU_DFE);\narch/mips/lantiq/xway/sysctrl.c-539-\t} else if (of_machine_is_compatible(\"lantiq,grx390\")) {\narch/mips/lantiq/xway/sysctrl.c:540:\t\tclkdev_add_static(ltq_grx390_cpu_hz(), ltq_grx390_fpi_hz(),\narch/mips/lantiq/xway/sysctrl.c-541-\t\t\t\t ltq_grx390_fpi_hz(), ltq_grx390_pp32_hz());\narch/mips/lantiq/xway/sysctrl.c:542:\t\tclkdev_add_pmu(\"1e108000.switch\", \"gphy3\", 0, 0, PMU_GPHY3);\narch/mips/lantiq/xway/sysctrl.c:543:\t\tclkdev_add_pmu(\"1e101000.usb\", \"otg\", 1, 0, PMU_USB0);\narch/mips/lantiq/xway/sysctrl.c:544:\t\tclkdev_add_pmu(\"1e106000.usb\", \"otg\", 1, 0, PMU_USB1);\narch/mips/lantiq/xway/sysctrl.c-545-\t\t/* rc 2 */\narch/mips/lantiq/xway/sysctrl.c:546:\t\tclkdev_add_pmu(\"1f106a00.pcie\", \"phy\", 1, 2, PMU_ANALOG_PCIE2_P);\narch/mips/lantiq/xway/sysctrl.c:547:\t\tclkdev_add_pmu(\"1a800000.pcie\", \"msi\", 1, 1, PMU1_PCIE2_MSI);\narch/mips/lantiq/xway/sysctrl.c:548:\t\tclkdev_add_pmu(\"1f106a00.pcie\", \"pdi\", 1, 1, PMU1_PCIE2_PDI);\narch/mips/lantiq/xway/sysctrl.c:549:\t\tclkdev_add_pmu(\"1a800000.pcie\", \"ctl\", 1, 1, PMU1_PCIE2_CTL);\narch/mips/lantiq/xway/sysctrl.c:550:\t\tclkdev_add_pmu(\"1e10b308.eth\", NULL, 0, 0, PMU_SWITCH | PMU_PPE_DP);\narch/mips/lantiq/xway/sysctrl.c:551:\t\tclkdev_add_pmu(\"1da00000.usif\", \"NULL\", 1, 0, PMU_USIF);\narch/mips/lantiq/xway/sysctrl.c:552:\t\tclkdev_add_pmu(\"1e103100.deu\", NULL, 1, 0, PMU_DEU);\narch/mips/lantiq/xway/sysctrl.c-553-\t} else if (of_machine_is_compatible(\"lantiq,ar10\")) {\narch/mips/lantiq/xway/sysctrl.c:554:\t\tclkdev_add_static(ltq_ar10_cpu_hz(), ltq_ar10_fpi_hz(),\narch/mips/lantiq/xway/sysctrl.c-555-\t\t\t\t ltq_ar10_fpi_hz(), ltq_ar10_pp32_hz());\narch/mips/lantiq/xway/sysctrl.c:556:\t\tclkdev_add_pmu(\"1e101000.usb\", \"otg\", 1, 0, PMU_USB0);\narch/mips/lantiq/xway/sysctrl.c:557:\t\tclkdev_add_pmu(\"1e106000.usb\", \"otg\", 1, 0, PMU_USB1);\narch/mips/lantiq/xway/sysctrl.c:558:\t\tclkdev_add_pmu(\"1e10b308.eth\", NULL, 0, 0, PMU_SWITCH |\narch/mips/lantiq/xway/sysctrl.c-559-\t\t\t PMU_PPE_DP | PMU_PPE_TC);\narch/mips/lantiq/xway/sysctrl.c:560:\t\tclkdev_add_pmu(\"1da00000.usif\", \"NULL\", 1, 0, PMU_USIF);\narch/mips/lantiq/xway/sysctrl.c:561:\t\tclkdev_add_pmu(\"1e103100.deu\", NULL, 1, 0, PMU_DEU);\narch/mips/lantiq/xway/sysctrl.c:562:\t\tclkdev_add_pmu(\"1e116000.mei\", \"afe\", 1, 2, PMU_ANALOG_DSL_AFE);\narch/mips/lantiq/xway/sysctrl.c:563:\t\tclkdev_add_pmu(\"1e116000.mei\", \"dfe\", 1, 0, PMU_DFE);\narch/mips/lantiq/xway/sysctrl.c-564-\t} else if (of_machine_is_compatible(\"lantiq,vr9\")) {\narch/mips/lantiq/xway/sysctrl.c:565:\t\tclkdev_add_static(ltq_vr9_cpu_hz(), ltq_vr9_fpi_hz(),\narch/mips/lantiq/xway/sysctrl.c-566-\t\t\t\tltq_vr9_fpi_hz(), ltq_vr9_pp32_hz());\narch/mips/lantiq/xway/sysctrl.c:567:\t\tclkdev_add_pmu(\"1f203018.usb2-phy\", \"phy\", 1, 0, PMU_USB0_P);\narch/mips/lantiq/xway/sysctrl.c:568:\t\tclkdev_add_pmu(\"1e101000.usb\", \"otg\", 1, 0, PMU_USB0 | PMU_AHBM);\narch/mips/lantiq/xway/sysctrl.c:569:\t\tclkdev_add_pmu(\"1f203034.usb2-phy\", \"phy\", 1, 0, PMU_USB1_P);\narch/mips/lantiq/xway/sysctrl.c:570:\t\tclkdev_add_pmu(\"1e106000.usb\", \"otg\", 1, 0, PMU_USB1 | PMU_AHBM);\narch/mips/lantiq/xway/sysctrl.c:571:\t\tclkdev_add_pmu(\"1f106800.phy\", \"phy\", 1, 1, PMU1_PCIE_PHY);\narch/mips/lantiq/xway/sysctrl.c:572:\t\tclkdev_add_pmu(\"1d900000.pcie\", \"bus\", 1, 0, PMU_PCIE_CLK);\narch/mips/lantiq/xway/sysctrl.c:573:\t\tclkdev_add_pmu(\"1d900000.pcie\", \"msi\", 1, 1, PMU1_PCIE_MSI);\narch/mips/lantiq/xway/sysctrl.c:574:\t\tclkdev_add_pmu(\"1f106800.phy\", \"pdi\", 1, 1, PMU1_PCIE_PDI);\narch/mips/lantiq/xway/sysctrl.c:575:\t\tclkdev_add_pmu(\"1d900000.pcie\", \"ctl\", 1, 1, PMU1_PCIE_CTL);\narch/mips/lantiq/xway/sysctrl.c:576:\t\tclkdev_add_pmu(NULL, \"ahb\", 1, 0, PMU_AHBM | PMU_AHBS);\narch/mips/lantiq/xway/sysctrl.c-577-\narch/mips/lantiq/xway/sysctrl.c:578:\t\tclkdev_add_pmu(\"1da00000.usif\", \"NULL\", 1, 0, PMU_USIF);\narch/mips/lantiq/xway/sysctrl.c:579:\t\tclkdev_add_pmu(\"1e10b308.eth\", NULL, 0, 0,\narch/mips/lantiq/xway/sysctrl.c-580-\t\t\t\tPMU_SWITCH | PMU_PPE_DPLUS | PMU_PPE_DPLUM |\n--\narch/mips/lantiq/xway/sysctrl.c-582-\t\t\t\tPMU_PPE_QSB | PMU_PPE_TOP);\narch/mips/lantiq/xway/sysctrl.c:583:\t\tclkdev_add_pmu(\"1e108000.switch\", \"gphy0\", 0, 0, PMU_GPHY);\narch/mips/lantiq/xway/sysctrl.c:584:\t\tclkdev_add_pmu(\"1e108000.switch\", \"gphy1\", 0, 0, PMU_GPHY);\narch/mips/lantiq/xway/sysctrl.c:585:\t\tclkdev_add_pmu(\"1e103000.sdio\", NULL, 1, 0, PMU_SDIO);\narch/mips/lantiq/xway/sysctrl.c:586:\t\tclkdev_add_pmu(\"1e103100.deu\", NULL, 1, 0, PMU_DEU);\narch/mips/lantiq/xway/sysctrl.c:587:\t\tclkdev_add_pmu(\"1e116000.mei\", \"dfe\", 1, 0, PMU_DFE);\narch/mips/lantiq/xway/sysctrl.c-588-\t} else if (of_machine_is_compatible(\"lantiq,ar9\")) {\narch/mips/lantiq/xway/sysctrl.c:589:\t\tclkdev_add_static(ltq_ar9_cpu_hz(), ltq_ar9_fpi_hz(),\narch/mips/lantiq/xway/sysctrl.c-590-\t\t\t\tltq_ar9_fpi_hz(), CLOCK_250M);\narch/mips/lantiq/xway/sysctrl.c:591:\t\tclkdev_add_pmu(\"1f203018.usb2-phy\", \"phy\", 1, 0, PMU_USB0_P);\narch/mips/lantiq/xway/sysctrl.c:592:\t\tclkdev_add_pmu(\"1e101000.usb\", \"otg\", 1, 0, PMU_USB0 | PMU_AHBM);\narch/mips/lantiq/xway/sysctrl.c:593:\t\tclkdev_add_pmu(\"1f203034.usb2-phy\", \"phy\", 1, 0, PMU_USB1_P);\narch/mips/lantiq/xway/sysctrl.c:594:\t\tclkdev_add_pmu(\"1e106000.usb\", \"otg\", 1, 0, PMU_USB1 | PMU_AHBM);\narch/mips/lantiq/xway/sysctrl.c:595:\t\tclkdev_add_pmu(\"1e180000.ethernet\", \"switch\", 1, 0, PMU_SWITCH);\narch/mips/lantiq/xway/sysctrl.c:596:\t\tclkdev_add_pmu(\"1e103000.sdio\", NULL, 1, 0, PMU_SDIO);\narch/mips/lantiq/xway/sysctrl.c:597:\t\tclkdev_add_pmu(\"1e103100.deu\", NULL, 1, 0, PMU_DEU);\narch/mips/lantiq/xway/sysctrl.c:598:\t\tclkdev_add_pmu(\"1e116000.mei\", \"dfe\", 1, 0, PMU_DFE);\narch/mips/lantiq/xway/sysctrl.c:599:\t\tclkdev_add_pmu(\"1e100400.serial\", NULL, 1, 0, PMU_ASC0);\narch/mips/lantiq/xway/sysctrl.c-600-\t} else {\narch/mips/lantiq/xway/sysctrl.c:601:\t\tclkdev_add_static(ltq_danube_cpu_hz(), ltq_danube_fpi_hz(),\narch/mips/lantiq/xway/sysctrl.c-602-\t\t\t\tltq_danube_fpi_hz(), ltq_danube_pp32_hz());\narch/mips/lantiq/xway/sysctrl.c:603:\t\tclkdev_add_pmu(\"1e101000.usb\", \"otg\", 1, 0, PMU_USB0 | PMU_AHBM);\narch/mips/lantiq/xway/sysctrl.c:604:\t\tclkdev_add_pmu(\"1f203018.usb2-phy\", \"phy\", 1, 0, PMU_USB0_P);\narch/mips/lantiq/xway/sysctrl.c:605:\t\tclkdev_add_pmu(\"1e103000.sdio\", NULL, 1, 0, PMU_SDIO);\narch/mips/lantiq/xway/sysctrl.c:606:\t\tclkdev_add_pmu(\"1e103100.deu\", NULL, 1, 0, PMU_DEU);\narch/mips/lantiq/xway/sysctrl.c:607:\t\tclkdev_add_pmu(\"1e116000.mei\", \"dfe\", 1, 0, PMU_DFE);\narch/mips/lantiq/xway/sysctrl.c:608:\t\tclkdev_add_pmu(\"1e100400.serial\", NULL, 1, 0, PMU_ASC0);\narch/mips/lantiq/xway/sysctrl.c-609-\t}\n--\narch/sh/boards/mach-highlander/setup.c=349=static void __init highlander_setup(char **cmdline_p)\n--\narch/sh/boards/mach-highlander/setup.c-375-\narch/sh/boards/mach-highlander/setup.c:376:\tclkdev_add_table(lookups, ARRAY_SIZE(lookups));\narch/sh/boards/mach-highlander/setup.c-377-\n--\narch/sh/boards/mach-sdk7786/setup.c=189=static int sdk7786_clk_init(void)\n--\narch/sh/boards/mach-sdk7786/setup.c-215-\narch/sh/boards/mach-sdk7786/setup.c:216:\tclkdev_add(\u0026sdk7786_pcie_cl);\narch/sh/boards/mach-sdk7786/setup.c-217-\n--\narch/sh/kernel/cpu/clock-cpg.c=47=int __init __deprecated cpg_clk_init(void)\n--\narch/sh/kernel/cpu/clock-cpg.c-57-\narch/sh/kernel/cpu/clock-cpg.c:58:\tclkdev_add_table(lookups, ARRAY_SIZE(lookups));\narch/sh/kernel/cpu/clock-cpg.c-59-\n--\narch/sh/kernel/cpu/sh2a/clock-sh7264.c=133=int __init arch_clk_init(void)\n--\narch/sh/kernel/cpu/sh2a/clock-sh7264.c-147-\narch/sh/kernel/cpu/sh2a/clock-sh7264.c:148:\tclkdev_add_table(lookups, ARRAY_SIZE(lookups));\narch/sh/kernel/cpu/sh2a/clock-sh7264.c-149-\n--\narch/sh/kernel/cpu/sh2a/clock-sh7269.c=165=int __init arch_clk_init(void)\n--\narch/sh/kernel/cpu/sh2a/clock-sh7269.c-171-\narch/sh/kernel/cpu/sh2a/clock-sh7269.c:172:\tclkdev_add_table(lookups, ARRAY_SIZE(lookups));\narch/sh/kernel/cpu/sh2a/clock-sh7269.c-173-\n--\narch/sh/kernel/cpu/sh4a/clock-sh7343.c=252=int __init arch_clk_init(void)\n--\narch/sh/kernel/cpu/sh4a/clock-sh7343.c-264-\narch/sh/kernel/cpu/sh4a/clock-sh7343.c:265:\tclkdev_add_table(lookups, ARRAY_SIZE(lookups));\narch/sh/kernel/cpu/sh4a/clock-sh7343.c-266-\n--\narch/sh/kernel/cpu/sh4a/clock-sh7366.c=245=int __init arch_clk_init(void)\n--\narch/sh/kernel/cpu/sh4a/clock-sh7366.c-257-\narch/sh/kernel/cpu/sh4a/clock-sh7366.c:258:\tclkdev_add_table(lookups, ARRAY_SIZE(lookups));\narch/sh/kernel/cpu/sh4a/clock-sh7366.c-259-\n--\narch/sh/kernel/cpu/sh4a/clock-sh7722.c=220=int __init arch_clk_init(void)\n--\narch/sh/kernel/cpu/sh4a/clock-sh7722.c-232-\narch/sh/kernel/cpu/sh4a/clock-sh7722.c:233:\tclkdev_add_table(lookups, ARRAY_SIZE(lookups));\narch/sh/kernel/cpu/sh4a/clock-sh7722.c-234-\n--\narch/sh/kernel/cpu/sh4a/clock-sh7723.c=268=int __init arch_clk_init(void)\n--\narch/sh/kernel/cpu/sh4a/clock-sh7723.c-280-\narch/sh/kernel/cpu/sh4a/clock-sh7723.c:281:\tclkdev_add_table(lookups, ARRAY_SIZE(lookups));\narch/sh/kernel/cpu/sh4a/clock-sh7723.c-282-\n--\narch/sh/kernel/cpu/sh4a/clock-sh7724.c=342=int __init arch_clk_init(void)\n--\narch/sh/kernel/cpu/sh4a/clock-sh7724.c-354-\narch/sh/kernel/cpu/sh4a/clock-sh7724.c:355:\tclkdev_add_table(lookups, ARRAY_SIZE(lookups));\narch/sh/kernel/cpu/sh4a/clock-sh7724.c-356-\n--\narch/sh/kernel/cpu/sh4a/clock-sh7734.c=239=int __init arch_clk_init(void)\n--\narch/sh/kernel/cpu/sh4a/clock-sh7734.c-245-\narch/sh/kernel/cpu/sh4a/clock-sh7734.c:246:\tclkdev_add_table(lookups, ARRAY_SIZE(lookups));\narch/sh/kernel/cpu/sh4a/clock-sh7734.c-247-\n--\narch/sh/kernel/cpu/sh4a/clock-sh7757.c=135=int __init arch_clk_init(void)\n--\narch/sh/kernel/cpu/sh4a/clock-sh7757.c-141-\narch/sh/kernel/cpu/sh4a/clock-sh7757.c:142:\tclkdev_add_table(lookups, ARRAY_SIZE(lookups));\narch/sh/kernel/cpu/sh4a/clock-sh7757.c-143-\n--\narch/sh/kernel/cpu/sh4a/clock-sh7763.c=96=int __init arch_clk_init(void)\n--\narch/sh/kernel/cpu/sh4a/clock-sh7763.c-112-\narch/sh/kernel/cpu/sh4a/clock-sh7763.c:113:\tclkdev_add_table(lookups, ARRAY_SIZE(lookups));\narch/sh/kernel/cpu/sh4a/clock-sh7763.c-114-\n--\narch/sh/kernel/cpu/sh4a/clock-sh7780.c=102=int __init arch_clk_init(void)\n--\narch/sh/kernel/cpu/sh4a/clock-sh7780.c-118-\narch/sh/kernel/cpu/sh4a/clock-sh7780.c:119:\tclkdev_add_table(lookups, ARRAY_SIZE(lookups));\narch/sh/kernel/cpu/sh4a/clock-sh7780.c-120-\n--\narch/sh/kernel/cpu/sh4a/clock-sh7785.c=158=int __init arch_clk_init(void)\n--\narch/sh/kernel/cpu/sh4a/clock-sh7785.c-164-\narch/sh/kernel/cpu/sh4a/clock-sh7785.c:165:\tclkdev_add_table(lookups, ARRAY_SIZE(lookups));\narch/sh/kernel/cpu/sh4a/clock-sh7785.c-166-\n--\narch/sh/kernel/cpu/sh4a/clock-sh7786.c=173=int __init arch_clk_init(void)\n--\narch/sh/kernel/cpu/sh4a/clock-sh7786.c-179-\narch/sh/kernel/cpu/sh4a/clock-sh7786.c:180:\tclkdev_add_table(lookups, ARRAY_SIZE(lookups));\narch/sh/kernel/cpu/sh4a/clock-sh7786.c-181-\n--\narch/sh/kernel/cpu/sh4a/clock-shx3.c=132=int __init arch_clk_init(void)\n--\narch/sh/kernel/cpu/sh4a/clock-shx3.c-138-\narch/sh/kernel/cpu/sh4a/clock-shx3.c:139:\tclkdev_add_table(lookups, ARRAY_SIZE(lookups));\narch/sh/kernel/cpu/sh4a/clock-shx3.c-140-\n--\ndrivers/bus/ti-sysc.c=321=static int sysc_add_named_clock_from_child(struct sysc *ddata,\n--\ndrivers/bus/ti-sysc.c-352-\t/*\ndrivers/bus/ti-sysc.c:353:\t * Use clkdev_add() instead of clkdev_alloc() to avoid the MAX_DEV_ID\ndrivers/bus/ti-sysc.c-354-\t * limit for clk_get(). If cl ever needs to be freed, it should be done\n--\ndrivers/bus/ti-sysc.c-363-\tcl-\u003eclk = clock;\ndrivers/bus/ti-sysc.c:364:\tclkdev_add(cl);\ndrivers/bus/ti-sysc.c-365-\n--\ndrivers/clk/clkdev.c=119=EXPORT_SYMBOL(clk_put);\n\n"
} |
| 41/2 | 2026/08/12 20:04 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 152645 output: 122 thoughts: 4141 Thoughts: **Analysis of the SiT9531x I2C Driver's Fuzzing Potential** Okay, so I'm digging into this new I2C driver for the SiTime SiT9531x DPLL. My initial impression is that fuzzing it in a typical environment is going to be a no-go. The critical stumbling block is that `sit9531x_dev_probe` calls `devm_clk_get_enabled(sitdev->dev, "xtal")`. This is a hard dependency on a clock named "xtal". If that call fails, which it almost certainly will, the probe function returns an error, and the driver never gets registered. That's the end of the line. The vast majority of the code is unreachable. Now, how can I be so sure this clock will fail to be found? Well, it's an I2C driver, which means it will likely be instantiated dynamically, likely through the `/sys/bus/i2c/devices/i2c-X/new_device` interface. That method of device creation doesn't provide a device tree node (`of_node`), so we're relying on a `clkdev` lookup. In a generic QEMU or GCE environment, there's no way that I'm aware of for a clock named "xtal" to be available through `clkdev_add` (I've searched). Could another driver provide the clock? I've scanned the kernel, and there aren't any drivers that instantiate `sit9531x` and provide the clock. Syzkaller can't inject clocks directly via `bpf`, `configfs`, or pseudo-syscalls. Even if I try to use `i2c-stub` and hope for a dummy clock, `devm_clk_get_enabled` won't fall back to a dummy clock. That function demands the clock be present. `devm_clk_get_optional` would allow for a NULL clock. So, this driver is strictly dependent on the "xtal" clock. What about ACPI? Perhaps Syzkaller could leverage ACPI overlays to inject the device and a clock? It turns out Syzkaller CAN inject devices via ACPI, but successfully injecting a clock via ACPI that will match a specific `con_id` like "xtal" seems extremely difficult and unlikely. It's a complex process to create and inject the necessary ACPI table with the clock definitions, especially with a specific naming scheme. There's no straightforward mechanism for syzkaller to generate such complex structures. What if the kernel is compiled for a specific board? Then it has a device tree that has the device, right? Syzkaller's typical environments (the upstream instances) use generic QEMU images, so no custom device trees are involved. Is there any chance this is an x86 system? On x86, `CONFIG_HAVE_CLK` is enabled, but if the clock isn't found, it still returns `-ENOENT`. The probe will fail. There's no easy way around this hard requirement. So, since the probe function is guaranteed to fail in these typical environments because of the clock dependency, I'm confident that the rest of the driver's functionality is unreachable. Even if `CONFIG_SIT9531X_DPLL` is enabled, it’s not relevant because no code will be reached. I've checked for any sort of clock mocking, like a dummy clock or some special mechanism to bypass the check, but I can't find anything. Syzkaller is unlikely to use a mock clock unless it is using `devm_clk_get_optional`. Because there is no other entry point, `WorthFuzzing=false` seems appropriate because the device cannot be reached, according to the criteria. Therefore, since the probe will fail due to the missing clock, `WorthFuzzing=false`, with empty `FocusSymbols` and `EnableConfigs` is the conclusion. |
| 42/2 | 2026/08/12 20:05 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The driver requires a mandatory 'xtal' clock via devm_clk_get_enabled() during probe. In generic virtualized environments (QEMU/GCE), dynamically instantiating this I2C device via sysfs will lack the necessary device tree node or clkdev lookup to satisfy this clock requirement. The probe will always fail with -ENOENT, making the rest of the driver's code paths unreachable for fuzzing.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The driver requires a mandatory 'xtal' clock via devm_clk_get_enabled() during probe. In generic virtualized environments (QEMU/GCE), dynamically instantiating this I2C device via sysfs will lack the necessary device tree node or clkdev lookup to satisfy this clock requirement. The probe will always fail with -ENOENT, making the rest of the driver's code paths unreachable for fuzzing.",
"WorthFuzzing": false
} |
| 43/1 | 2026/08/12 20:05 | action | If |
0mArgs:{
"WorthFuzzing": false
}Results: null |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|