AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/13 13:36 flow patch-triage
3m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch adds PTP clock support to the Microchip Azurite DPLL driver (zl3073x). This is a hardware-specific I2C/SPI driver. During initialization (zl3073x_dev_probe), the driver strictly reads and verifies the hardware chip ID (ZL_REG_ID). Since there is no software emulator for this specific hardware in the kernel, and generic I2C mocks like i2c-stub do not support the 16-bit register addressing required by this driver, the probe function will always fail in virtualized environments (QEMU/GCE). Consequently, the DPLL and PTP devices will never be registered, making the modified code paths impossible to reach and fuzz.",
  "WorthFuzzing": false
}

1/1 2026/07/13 13:36 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 5a600a1bd65a21f474dbecf9ee6d9db73eb82808\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Mon Jul 13 13:36:56 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/drivers/dpll/zl3073x/Kconfig b/drivers/dpll/zl3073x/Kconfig\nindex 5bbca140058131..d0574ad571d3fa 100644\n--- a/drivers/dpll/zl3073x/Kconfig\n+++ b/drivers/dpll/zl3073x/Kconfig\n@@ -2,7 +2,7 @@\n \n config ZL3073X\n \ttristate \"Microchip Azurite DPLL/PTP/SyncE devices\" if COMPILE_TEST\n-\tdepends on NET\n+\tdepends on NET \u0026\u0026 PTP_1588_CLOCK_OPTIONAL\n \tselect DPLL\n \tselect NET_DEVLINK\n \tselect REGMAP\n@@ -16,7 +16,7 @@ config ZL3073X\n \n config ZL3073X_I2C\n \ttristate \"I2C bus implementation for Microchip Azurite devices\"\n-\tdepends on I2C \u0026\u0026 NET\n+\tdepends on I2C \u0026\u0026 NET \u0026\u0026 PTP_1588_CLOCK_OPTIONAL\n \tselect REGMAP_I2C\n \tselect ZL3073X\n \thelp\n@@ -28,7 +28,7 @@ config ZL3073X_I2C\n \n config ZL3073X_SPI\n \ttristate \"SPI bus implementation for Microchip Azurite devices\"\n-\tdepends on NET \u0026\u0026 SPI\n+\tdepends on NET \u0026\u0026 SPI \u0026\u0026 PTP_1588_CLOCK_OPTIONAL\n \tselect REGMAP_SPI\n \tselect ZL3073X\n \thelp\ndiff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c\nindex 4ec2cf53dad468..53032e782fd4af 100644\n--- a/drivers/dpll/zl3073x/chan.c\n+++ b/drivers/dpll/zl3073x/chan.c\n@@ -3,6 +3,7 @@\n #include \u003clinux/cleanup.h\u003e\n #include \u003clinux/delay.h\u003e\n #include \u003clinux/dev_printk.h\u003e\n+#include \u003clinux/ptp_clock_kernel.h\u003e\n #include \u003clinux/string.h\u003e\n #include \u003clinux/types.h\u003e\n \n@@ -202,6 +203,11 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)\n \t\tzl3073x_chan_refsel_state_get(chan),\n \t\tzl3073x_chan_refsel_ref_get(chan));\n \n+\trc = zl3073x_read_u16(zldev, ZL_REG_OUTPUT_STEP_TIME_MASK,\n+\t\t\t      \u0026chan-\u003eout_step_time_mask);\n+\tif (rc)\n+\t\treturn rc;\n+\n \tguard(mutex)(\u0026zldev-\u003emultiop_lock);\n \n \t/* Read DPLL configuration from mailbox */\n@@ -234,6 +240,307 @@ const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,\n \treturn \u0026zldev-\u003echan[index];\n }\n \n+/**\n+ * zl3073x_chan_tod_ready_wait - wait for ToD semaphore to clear\n+ * @zldev: pointer to zl3073x device\n+ * @ch: DPLL channel index\n+ *\n+ * Polls the ToD control register until the semaphore bit is cleared,\n+ * indicating the device has completed the previous ToD operation.\n+ *\n+ * Return: 0 on success, -EBUSY if semaphore not cleared, \u003c0 on error\n+ */\n+static int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch)\n+{\n+\tunsigned int timeout;\n+\tu8 tod_ctrl;\n+\tint rc;\n+\n+\trc = zl3073x_read_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), \u0026tod_ctrl);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tswitch (FIELD_GET(ZL_DPLL_TOD_CTRL_CMD, tod_ctrl)) {\n+\tcase ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ:\n+\t\ttimeout = ZL_POLL_TOD_WR_TIMEOUT_US;\n+\t\tbreak;\n+\tdefault:\n+\t\ttimeout = ZL_POLL_TOD_RD_TIMEOUT_US;\n+\t\tbreak;\n+\t}\n+\n+\trc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch),\n+\t\t\t\t  ZL_DPLL_TOD_CTRL_SEM, timeout);\n+\n+\treturn rc == -ETIMEDOUT ? -EBUSY : rc;\n+}\n+\n+/**\n+ * zl3073x_chan_tod_ctrl - issue ToD command\n+ * @zldev: pointer to zl3073x device\n+ * @ch: DPLL channel index\n+ * @cmd: ToD command to execute\n+ *\n+ * Writes the semaphore and command to dpll_tod_ctrl. The caller must\n+ * ensure the device is ready (semaphore clear) before calling and\n+ * must wait for completion if needed.\n+ *\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+static int zl3073x_chan_tod_ctrl(struct zl3073x_dev *zldev, u8 ch, u8 cmd)\n+{\n+\treturn zl3073x_write_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch),\n+\t\t\t\tZL_DPLL_TOD_CTRL_SEM | cmd);\n+}\n+\n+/**\n+ * zl3073x_chan_tod_read - read ToD registers after issuing a command\n+ * @zldev: pointer to zl3073x device\n+ * @ch: DPLL channel index\n+ * @next_hz: if true, read predicted ToD at next 1 Hz; otherwise read current\n+ * @ts: timespec to store the result\n+ * @sts: optional system timestamp pair for cross-timestamping\n+ *\n+ * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch,\n+\t\t\t  bool next_hz, struct timespec64 *ts,\n+\t\t\t  struct ptp_system_timestamp *sts)\n+{\n+\tu32 nsec;\n+\tu64 sec;\n+\tu8 cmd;\n+\tint rc;\n+\n+\tif (next_hz)\n+\t\tcmd = ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ;\n+\telse\n+\t\tcmd = ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT;\n+\n+\t/* Wait for any previous ToD operation to complete */\n+\trc = zl3073x_chan_tod_ready_wait(zldev, ch);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tptp_read_system_prets(sts);\n+\trc = zl3073x_chan_tod_ctrl(zldev, ch, cmd);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = zl3073x_chan_tod_ready_wait(zldev, ch);\n+\tif (rc)\n+\t\treturn rc;\n+\tptp_read_system_postts(sts);\n+\n+\trc = zl3073x_read_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), \u0026sec);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* HW nanoseconds are always in [0, NSEC_PER_SEC) range */\n+\trc = zl3073x_read_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), \u0026nsec);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tts-\u003etv_sec = sec;\n+\tts-\u003etv_nsec = nsec;\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * zl3073x_chan_tod_write - write ToD registers and trigger 1 Hz update\n+ * @zldev: pointer to zl3073x device\n+ * @ch: DPLL channel index\n+ * @ts: time to set\n+ *\n+ * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch,\n+\t\t\t   struct timespec64 ts)\n+{\n+\tint rc;\n+\n+\t/* Wait for any previous ToD operation to complete */\n+\trc = zl3073x_chan_tod_ready_wait(zldev, ch);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), ts.tv_sec);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = zl3073x_write_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), ts.tv_nsec);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\treturn zl3073x_chan_tod_ctrl(zldev, ch,\n+\t\t\t\t    ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ);\n+}\n+\n+/**\n+ * zl3073x_chan_tod_adjust - atomic ToD read-modify-write with rollover guard\n+ * @zldev: pointer to zl3073x device\n+ * @ch: DPLL channel index\n+ * @delta: time adjustment to apply\n+ *\n+ * Reads the next-Hz ToD and current ToD, then checks whether enough time\n+ * remains before the next 1 Hz rollover to safely complete the write.\n+ * If less than 20 ms remains, waits for the rollover and increments the\n+ * next-Hz seconds by one. Applies @delta and writes the result back.\n+ *\n+ * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch,\n+\t\t\t    struct timespec64 delta)\n+{\n+\tstatic const long threshold_ns = 20 * NSEC_PER_MSEC;\n+\tstruct timespec64 ts_next, ts_cur, diff;\n+\tint rc;\n+\n+\t/* Read predicted ToD at next 1 Hz tick */\n+\trc = zl3073x_chan_tod_read(zldev, ch, true, \u0026ts_next, NULL);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* Read current ToD to determine remaining margin */\n+\trc = zl3073x_chan_tod_read(zldev, ch, false, \u0026ts_cur, NULL);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* If too close to (or past) the next rollover, wait it out */\n+\tdiff = timespec64_sub(ts_next, ts_cur);\n+\tif (diff.tv_sec \u003c 0 || (!diff.tv_sec \u0026\u0026 diff.tv_nsec \u003c threshold_ns)) {\n+\t\tif (!diff.tv_sec \u0026\u0026 diff.tv_nsec)\n+\t\t\tfsleep((unsigned long)diff.tv_nsec / NSEC_PER_USEC + 1);\n+\t\tts_next.tv_sec++;\n+\t}\n+\n+\t/* Apply delta to the next-Hz ToD */\n+\tts_next = timespec64_add(ts_next, delta);\n+\tif (!timespec64_valid(\u0026ts_next))\n+\t\treturn -EINVAL;\n+\n+\t/* Write adjusted ToD back and wait for completion */\n+\trc = zl3073x_chan_tod_write(zldev, ch, ts_next);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\treturn zl3073x_chan_tod_ready_wait(zldev, ch);\n+}\n+\n+/**\n+ * zl3073x_chan_df_offset_set - write delta frequency offset to hardware\n+ * @zldev: pointer to zl3073x device\n+ * @ch: DPLL channel index\n+ * @offset: frequency offset in 2^-48 steps\n+ *\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset)\n+{\n+\tint rc;\n+\n+\trc = zl3073x_write_u48(zldev, ZL_REG_DPLL_DF_OFFSET(ch), offset);\n+\tif (!rc)\n+\t\tzldev-\u003echan[ch].df_offset = offset;\n+\n+\treturn rc;\n+}\n+\n+/**\n+ * zl3073x_chan_tie_write - adjust DPLL phase using TIE write\n+ * @zldev: pointer to zl3073x device\n+ * @ch: DPLL channel index\n+ * @delta_ns: phase adjustment in nanoseconds (must be within +-1s)\n+ *\n+ * Converts nanoseconds to TIE units (0.01 ps) and writes TIE data\n+ * to the specified channel.\n+ *\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns)\n+{\n+\ts64 tie_data;\n+\tint rc;\n+\n+\tguard(mutex)(\u0026zldev-\u003etie_lock);\n+\n+\t/* Wait for any previous TIE operation to complete */\n+\trc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TIE_CTRL,\n+\t\t\t\t  ZL_DPLL_TIE_CTRL_OP,\n+\t\t\t\t  ZL_POLL_TIE_WR_TIMEOUT_US);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* Convert ns to TIE units (0.01 ps = 10^-14 s) */\n+\ttie_data = delta_ns * 100000LL;\n+\n+\trc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TIE_DATA(ch), tie_data);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL_MASK, BIT(ch));\n+\tif (rc)\n+\t\treturn rc;\n+\n+\treturn zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL,\n+\t\t\t\tZL_DPLL_TIE_CTRL_OP_WR);\n+}\n+\n+/**\n+ * zl3073x_chan_phase_step - execute one output phase step operation\n+ * @zldev: pointer to zl3073x device\n+ * @ch: DPLL channel index\n+ * @out_mask: bitmask of outputs to step\n+ * @step_cycles: phase step in synthesizer clock cycles\n+ * @tod_step: also step the ToD counter\n+ *\n+ * All masked outputs must use synthesizers of the same frequency since\n+ * the step value is in synthesizer clock cycles.\n+ *\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch,\n+\t\t\t    u16 out_mask, s32 step_cycles,\n+\t\t\t    bool tod_step)\n+{\n+\tu8 ctrl;\n+\tint rc;\n+\n+\tguard(mutex)(\u0026zldev-\u003ephase_step_lock);\n+\n+\t/* Wait for any previous phase step operation to complete */\n+\trc = zl3073x_poll_zero_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL,\n+\t\t\t\t  ZL_OUTPUT_PHASE_STEP_CTRL_OP,\n+\t\t\t\t  ZL_POLL_PHASE_STEP_TIMEOUT_US);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_STEP_DATA,\n+\t\t\t       step_cycles);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = zl3073x_write_u16(zldev, ZL_REG_OUTPUT_PHASE_STEP_MASK, out_mask);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_NUMBER, 1);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tctrl = FIELD_PREP(ZL_OUTPUT_PHASE_STEP_CTRL_DPLL, ch) |\n+\t       FIELD_PREP(ZL_OUTPUT_PHASE_STEP_CTRL_OP,\n+\t\t\t  ZL_OUTPUT_PHASE_STEP_CTRL_OP_WRITE);\n+\tif (tod_step)\n+\t\tctrl |= ZL_OUTPUT_PHASE_STEP_CTRL_TOD_STEP;\n+\n+\treturn zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL, ctrl);\n+}\n+\n /**\n  * zl3073x_chan_state_set - commit DPLL channel state changes to hardware\n  * @zldev: pointer to zl3073x_dev structure\ndiff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h\nindex dc9c6d95bdee75..9dc5a3f1991f13 100644\n--- a/drivers/dpll/zl3073x/chan.h\n+++ b/drivers/dpll/zl3073x/chan.h\n@@ -5,10 +5,12 @@\n \n #include \u003clinux/bitfield.h\u003e\n #include \u003clinux/stddef.h\u003e\n+#include \u003clinux/time64.h\u003e\n #include \u003clinux/types.h\u003e\n \n #include \"regs.h\"\n \n+struct ptp_system_timestamp;\n struct zl3073x_dev;\n \n /**\n@@ -16,6 +18,7 @@ struct zl3073x_dev;\n  * @ctrl: DPLL control register value\n  * @mode_refsel: mode and reference selection register value\n  * @ref_prio: reference priority registers (4 bits per ref, P/N packed)\n+ * @out_step_time_mask: output step-time mask\n  * @mon_status: monitor status register value\n  * @refsel_status: reference selection status register value\n  * @df_offset: frequency offset vs tracked reference in 2^-48 steps\n@@ -26,6 +29,9 @@ struct zl3073x_chan {\n \t\tu8\tmode_refsel;\n \t\tu8\tref_prio[ZL3073X_NUM_REFS / 2];\n \t);\n+\tstruct_group(inv, /* Invariants */\n+\t\tu16\tout_step_time_mask;\n+\t);\n \tstruct_group(stat,\n \t\tu8\tmon_status;\n \t\tu8\trefsel_status;\n@@ -42,6 +48,20 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,\n int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index);\n int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index);\n \n+int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch,\n+\t\t\t  bool next_hz, struct timespec64 *ts,\n+\t\t\t  struct ptp_system_timestamp *sts);\n+int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch,\n+\t\t\t   struct timespec64 ts);\n+int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch,\n+\t\t\t    struct timespec64 delta);\n+int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch,\n+\t\t\t    u16 out_mask, s32 step_cycles, bool tod_step);\n+\n+int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset);\n+\n+int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns);\n+\n /**\n  * zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference\n  * @chan: pointer to channel state\n@@ -200,6 +220,21 @@ static inline bool zl3073x_chan_mode_is_reflock(const struct zl3073x_chan *chan)\n \treturn zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_REFLOCK;\n }\n \n+/**\n+ * zl3073x_chan_mode_supports_tie - check if channel mode supports TIE write\n+ * @chan: pointer to channel state\n+ *\n+ * TIE write is supported in AUTO and REFLOCK modes regardless of lock state.\n+ *\n+ * Return: true if TIE write is supported, false otherwise\n+ */\n+static inline bool\n+zl3073x_chan_mode_supports_tie(const struct zl3073x_chan *chan)\n+{\n+\treturn zl3073x_chan_mode_is_auto(chan) ||\n+\t\tzl3073x_chan_mode_is_reflock(chan);\n+}\n+\n /**\n  * zl3073x_chan_is_ho_ready - check if holdover is ready\n  * @chan: pointer to channel state\n@@ -233,4 +268,17 @@ static inline u8 zl3073x_chan_refsel_ref_get(const struct zl3073x_chan *chan)\n \treturn FIELD_GET(ZL_DPLL_REFSEL_STATUS_REFSEL, chan-\u003erefsel_status);\n }\n \n+/**\n+ * zl3073x_chan_is_out_stepped - check if output is in step-time mask\n+ * @chan: pointer to channel state\n+ * @out: output index\n+ *\n+ * Return: true if output is affected by step-time operations\n+ */\n+static inline bool\n+zl3073x_chan_is_out_stepped(const struct zl3073x_chan *chan, u8 out)\n+{\n+\treturn !!(chan-\u003eout_step_time_mask \u0026 BIT(out));\n+}\n+\n #endif /* _ZL3073X_CHAN_H */\ndiff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c\nindex 7f5afaaae6342f..b2f9fc97fd1b68 100644\n--- a/drivers/dpll/zl3073x/core.c\n+++ b/drivers/dpll/zl3073x/core.c\n@@ -1033,6 +1033,14 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev)\n \t * and/or polls are required to be done atomically.\n \t */\n \trc = devm_mutex_init(zldev-\u003edev, \u0026zldev-\u003emultiop_lock);\n+\tif (rc)\n+\t\treturn dev_err_probe(zldev-\u003edev, rc,\n+\t\t\t\t     \"Failed to initialize mutex\\n\");\n+\trc = devm_mutex_init(zldev-\u003edev, \u0026zldev-\u003ephase_step_lock);\n+\tif (rc)\n+\t\treturn dev_err_probe(zldev-\u003edev, rc,\n+\t\t\t\t     \"Failed to initialize mutex\\n\");\n+\trc = devm_mutex_init(zldev-\u003edev, \u0026zldev-\u003etie_lock);\n \tif (rc)\n \t\treturn dev_err_probe(zldev-\u003edev, rc,\n \t\t\t\t     \"Failed to initialize mutex\\n\");\ndiff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h\nindex 78dc208f3eea2a..2dc836d0c8f6b3 100644\n--- a/drivers/dpll/zl3073x/core.h\n+++ b/drivers/dpll/zl3073x/core.h\n@@ -26,6 +26,10 @@ struct zl3073x_dpll;\n #define ZL_POLL_HWREG_TIMEOUT_US\t(50 * USEC_PER_MSEC)\n #define ZL_POLL_MB_TIMEOUT_US\t\t(30 * USEC_PER_MSEC)\n #define ZL_POLL_PHASE_ERR_TIMEOUT_US\t(50 * USEC_PER_MSEC)\n+#define ZL_POLL_PHASE_STEP_TIMEOUT_US\t(3000 * USEC_PER_MSEC)\n+#define ZL_POLL_TIE_WR_TIMEOUT_US\t(1000 * USEC_PER_MSEC)\n+#define ZL_POLL_TOD_RD_TIMEOUT_US\t(30 * USEC_PER_MSEC)\n+#define ZL_POLL_TOD_WR_TIMEOUT_US\t(1000 * USEC_PER_MSEC)\n \n enum zl3073x_flags {\n \tZL3073X_FLAG_REF_PHASE_COMP_32_BIT,\n@@ -55,6 +59,8 @@ struct zl3073x_chip_info {\n  * @regmap: regmap to access device registers\n  * @info: detected chip info\n  * @multiop_lock: to serialize multiple register operations\n+ * @tie_lock: to serialize TIE write operations\n+ * @phase_step_lock: to serialize output phase step operations\n  * @ref: array of input references' invariants\n  * @out: array of outs' invariants\n  * @synth: array of synths' invariants\n@@ -71,6 +77,8 @@ struct zl3073x_dev {\n \tstruct regmap\t\t\t*regmap;\n \tconst struct zl3073x_chip_info\t*info;\n \tstruct mutex\t\t\tmultiop_lock;\n+\tstruct mutex\t\t\ttie_lock;\n+\tstruct mutex\t\t\tphase_step_lock;\n \n \t/* Invariants */\n \tstruct zl3073x_ref\tref[ZL3073X_NUM_REFS];\ndiff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c\nindex d91f52b58eae34..7c11de35821aea 100644\n--- a/drivers/dpll/zl3073x/dpll.c\n+++ b/drivers/dpll/zl3073x/dpll.c\n@@ -2,6 +2,7 @@\n \n #include \u003clinux/bits.h\u003e\n #include \u003clinux/bitfield.h\u003e\n+#include \u003clinux/cleanup.h\u003e\n #include \u003clinux/bug.h\u003e\n #include \u003clinux/container_of.h\u003e\n #include \u003clinux/dev_printk.h\u003e\n@@ -14,6 +15,7 @@\n #include \u003clinux/netlink.h\u003e\n #include \u003clinux/platform_device.h\u003e\n #include \u003clinux/property.h\u003e\n+#include \u003clinux/ptp_clock_kernel.h\u003e\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/sprintf.h\u003e\n \n@@ -42,6 +44,7 @@\n  * @phase_offset: last saved pin phase offset\n  * @freq_offset: last saved fractional frequency offset\n  * @measured_freq: last saved measured frequency\n+ * @perout_idx: PTP perout channel index, or -1 if not perout-eligible\n  */\n struct zl3073x_dpll_pin {\n \tstruct list_head\tlist;\n@@ -59,6 +62,7 @@ struct zl3073x_dpll_pin {\n \ts64\t\t\tphase_offset;\n \ts64\t\t\tfreq_offset;\n \tu32\t\t\tmeasured_freq;\n+\ts8\t\t\tperout_idx;\n };\n \n /*\n@@ -936,21 +940,17 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin,\n }\n \n static int\n-zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_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+__zl3073x_dpll_output_pin_frequency_set(struct zl3073x_dpll *zldpll,\n+\t\t\t\t\tstruct zl3073x_dpll_pin *pin,\n+\t\t\t\t\tu64 frequency)\n {\n-\tstruct zl3073x_dpll *zldpll = dpll_priv;\n \tstruct zl3073x_dev *zldev = zldpll-\u003edev;\n-\tstruct zl3073x_dpll_pin *pin = pin_priv;\n \tconst struct zl3073x_synth *synth;\n \tu32 new_div, synth_freq;\n \tstruct zl3073x_out out;\n \tu8 out_id;\n \n-\tguard(mutex)(\u0026zldpll-\u003elock);\n+\tlockdep_assert_held(\u0026zldpll-\u003elock);\n \n \tout_id = zl3073x_output_pin_out_get(pin-\u003eid);\n \tout = *zl3073x_out_state_get(zldev, out_id);\n@@ -1011,6 +1011,21 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,\n \treturn zl3073x_out_state_set(zldev, out_id, \u0026out);\n }\n \n+static int\n+zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_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 zl3073x_dpll *zldpll = dpll_priv;\n+\n+\tguard(mutex)(\u0026zldpll-\u003elock);\n+\n+\treturn __zl3073x_dpll_output_pin_frequency_set(zldpll, pin_priv,\n+\t\t\t\t\t\t       frequency);\n+}\n+\n static int\n zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,\n \t\t\t\t\t void *pin_priv,\n@@ -1608,6 +1623,7 @@ zl3073x_dpll_pin_alloc(struct zl3073x_dpll *zldpll, enum dpll_pin_direction dir,\n \tpin-\u003edpll = zldpll;\n \tpin-\u003edir = dir;\n \tpin-\u003eid = id;\n+\tpin-\u003eperout_idx = -1;\n \n \treturn pin;\n }\n@@ -1626,6 +1642,44 @@ zl3073x_dpll_pin_free(struct zl3073x_dpll_pin *pin)\n \tkfree(pin);\n }\n \n+/**\n+ * zl3073x_dpll_pin_is_perout - check if output pin is perout-eligible\n+ * @pin: DPLL pin to check\n+ * @props: pin properties (already fetched by caller)\n+ *\n+ * An output pin is eligible for PTP periodic output if it is single-ended\n+ * (not differential) and supports 1 Hz in its frequency list.\n+ *\n+ * Return: true if eligible, false otherwise\n+ */\n+static bool\n+zl3073x_dpll_pin_is_perout(struct zl3073x_dpll_pin *pin,\n+\t\t\t   const struct zl3073x_pin_props *props)\n+{\n+\tstruct zl3073x_dpll *zldpll = pin-\u003edpll;\n+\tconst struct zl3073x_chan *chan;\n+\tu8 out_id;\n+\tint i;\n+\n+\tif (zl3073x_dpll_is_input_pin(pin))\n+\t\treturn false;\n+\n+\tout_id = zl3073x_output_pin_out_get(pin-\u003eid);\n+\tif (zl3073x_dev_out_is_diff(zldpll-\u003edev, out_id))\n+\t\treturn false;\n+\n+\tchan = zl3073x_chan_state_get(zldpll-\u003edev, zldpll-\u003eid);\n+\tif (!zl3073x_chan_is_out_stepped(chan, out_id))\n+\t\treturn false;\n+\n+\tfor (i = 0; i \u003c props-\u003edpll_props.freq_supported_num; i++) {\n+\t\tif (props-\u003edpll_props.freq_supported[i].min == 1)\n+\t\t\treturn true;\n+\t}\n+\n+\treturn false;\n+}\n+\n /**\n  * zl3073x_dpll_pin_register - register DPLL pin\n  * @pin: pointer to DPLL pin\n@@ -1667,6 +1721,8 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)\n \t\tif (pin-\u003eprio == ZL_DPLL_REF_PRIO_NONE)\n \t\t\t/* Clamp prio to max value */\n \t\t\tpin-\u003eprio = ZL_DPLL_REF_PRIO_MAX;\n+\t} else if (zl3073x_dpll_pin_is_perout(pin, props)) {\n+\t\tpin-\u003eperout_idx = zldpll-\u003eptp_info.n_per_out++;\n \t}\n \n \t/* Create or get existing DPLL pin */\n@@ -1897,6 +1953,8 @@ zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll)\n \tu8 id, index;\n \tint rc;\n \n+\tzldpll-\u003eptp_info.n_per_out = 0;\n+\n \t/* Process input pins */\n \tfor (index = 0; index \u003c ZL3073X_NUM_PINS; index++) {\n \t\t/* First input pins and then output pins */\n@@ -2280,45 +2338,447 @@ zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev)\n \treturn rc;\n }\n \n+/* Maximum frequency adjustment: +-1% of nominal in ppb */\n+#define ZL3073X_DPLL_PTP_MAX_ADJ\t10000000\n+\n /**\n- * zl3073x_dpll_alloc - allocate DPLL device\n- * @zldev: pointer to zl3073x device\n- * @ch: DPLL channel number\n+ * zl3073x_dpll_ptp_gettimex64 - read current time from ToD counters\n+ * @info: PTP clock info\n+ * @ts: timespec to store current time\n+ * @sts: optional system timestamp pair for cross-timestamping\n  *\n- * Allocates DPLL device structure for given DPLL channel.\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+static int zl3073x_dpll_ptp_gettimex64(struct ptp_clock_info *info,\n+\t\t\t\t       struct timespec64 *ts,\n+\t\t\t\t       struct ptp_system_timestamp *sts)\n+{\n+\tstruct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,\n+\t\t\t\t\t\t   ptp_info);\n+\n+\tguard(mutex)(\u0026zldpll-\u003elock);\n+\n+\treturn zl3073x_chan_tod_read(zldpll-\u003edev, zldpll-\u003eid, false, ts, sts);\n+}\n+\n+/**\n+ * zl3073x_dpll_ptp_settime64 - set ToD counters to given time\n+ * @info: PTP clock info\n+ * @ts: timespec with time to set\n  *\n- * Return: pointer to DPLL device on success, error pointer on error\n+ * Return: 0 on success, \u003c0 on error\n  */\n-struct zl3073x_dpll *\n-zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)\n+static int zl3073x_dpll_ptp_settime64(struct ptp_clock_info *info,\n+\t\t\t\t      const struct timespec64 *ts)\n {\n-\tstruct zl3073x_dpll *zldpll;\n+\tstruct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,\n+\t\t\t\t\t\t   ptp_info);\n \n-\tzldpll = kzalloc_obj(*zldpll);\n-\tif (!zldpll)\n-\t\treturn ERR_PTR(-ENOMEM);\n+\tguard(mutex)(\u0026zldpll-\u003elock);\n \n-\tzldpll-\u003edev = zldev;\n-\tzldpll-\u003eid = ch;\n-\tmutex_init(\u0026zldpll-\u003elock);\n-\tINIT_LIST_HEAD(\u0026zldpll-\u003epins);\n+\treturn zl3073x_chan_tod_write(zldpll-\u003edev, zldpll-\u003eid, *ts);\n+}\n \n-\treturn zldpll;\n+/**\n+ * zl3073x_dpll_ptp_adjtime_phase_step - adjust sub-second time via phase step\n+ * @zldpll: DPLL channel\n+ * @delta: time adjustment in nanoseconds (must be within (-NSEC_PER_SEC,\n+ *         NSEC_PER_SEC))\n+ *\n+ * Uses the output phase step mechanism with tod_step=1 to adjust both\n+ * the output clock phase and the ToD counter simultaneously. This keeps\n+ * outputs and ToD coherent. Only valid for NCO.\n+ *\n+ * Outputs are grouped by synthesizer since the phase step value is in\n+ * synthesizer clock cycles. The first synth group with enabled outputs\n+ * uses tod_step to adjust both outputs and the ToD counter. Remaining\n+ * groups step outputs only. If no synth has enabled outputs, the ToD\n+ * counter is stepped alone using an empty output mask (the FW uses the\n+ * lowest-ID synth's period for the conversion).\n+ *\n+ * Return: 0 on success, -EOPNOTSUPP if no synths available, \u003c0 on error\n+ */\n+static int zl3073x_dpll_ptp_adjtime_phase_step(struct zl3073x_dpll *zldpll,\n+\t\t\t\t\t       s64 delta)\n+{\n+\tu16 synth_mask[ZL3073X_NUM_SYNTHS] = {};\n+\tstruct zl3073x_dev *zldev = zldpll-\u003edev;\n+\tconst struct zl3073x_synth *synth;\n+\tconst struct zl3073x_chan *chan;\n+\tstruct zl3073x_dpll_pin *pin;\n+\tu32 first_synth_freq = 0;\n+\tbool tod_stepped = false;\n+\ts32 step_cycles;\n+\tu32 synth_freq;\n+\tint rc;\n+\tu8 i;\n+\n+\tchan = zl3073x_chan_state_get(zldev, zldpll-\u003eid);\n+\n+\t/* Build per-synth output masks from registered output pins */\n+\tlist_for_each_entry(pin, \u0026zldpll-\u003epins, list) {\n+\t\tu8 out_id, synth_id;\n+\n+\t\tif (zl3073x_dpll_is_input_pin(pin))\n+\t\t\tcontinue;\n+\n+\t\tout_id = zl3073x_output_pin_out_get(pin-\u003eid);\n+\n+\t\tif (!zl3073x_chan_is_out_stepped(chan, out_id))\n+\t\t\tcontinue;\n+\n+\t\tsynth_id = zl3073x_dev_out_synth_get(zldev, out_id);\n+\t\tif (synth_id \u003e= ZL3073X_NUM_SYNTHS) {\n+\t\t\tdev_warn(zldev-\u003edev, \"Unexpected synth id for OUT%u\\n\",\n+\t\t\t\t out_id);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tsynth_mask[synth_id] |= BIT(out_id);\n+\t}\n+\n+\t/* Process each synth group */\n+\tfor (i = 0; i \u003c ZL3073X_NUM_SYNTHS; i++) {\n+\t\tsynth = zl3073x_synth_state_get(zldev, i);\n+\t\tif (!zl3073x_synth_is_enabled(synth) ||\n+\t\t    zl3073x_synth_dpll_get(synth) != zldpll-\u003eid)\n+\t\t\tcontinue;\n+\n+\t\tsynth_freq = zl3073x_synth_freq_get(synth);\n+\n+\t\t/* Remember lowest-ID synth freq for ToD-only fallback */\n+\t\tif (!first_synth_freq)\n+\t\t\tfirst_synth_freq = synth_freq;\n+\n+\t\tif (!synth_mask[i])\n+\t\t\tcontinue;\n+\n+\t\t/* Safe for s32: max synth freq is 750 MHz */\n+\t\tstep_cycles = div_s64(delta * synth_freq, NSEC_PER_SEC);\n+\n+\t\trc = zl3073x_chan_phase_step(zldev, zldpll-\u003eid,\n+\t\t\t\t\t     synth_mask[i], step_cycles,\n+\t\t\t\t\t     !tod_stepped);\n+\t\tif (rc) {\n+\t\t\tif (tod_stepped) {\n+\t\t\t\tdev_warn(zldev-\u003edev,\n+\t\t\t\t\t \"Partial phase step failure\\n\");\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t\treturn rc;\n+\t\t}\n+\t\ttod_stepped = true;\n+\t}\n+\n+\tif (!first_synth_freq)\n+\t\treturn -EOPNOTSUPP;\n+\n+\t/* No enabled outputs found; step ToD counter only using the\n+\t * lowest-ID synth's period (empty output mask).\n+\t */\n+\tif (!tod_stepped) {\n+\t\tstep_cycles = div_s64(delta * first_synth_freq, NSEC_PER_SEC);\n+\t\treturn zl3073x_chan_phase_step(zldev, zldpll-\u003eid, 0,\n+\t\t\t\t\t       step_cycles, true);\n+\t}\n+\n+\treturn 0;\n }\n \n /**\n- * zl3073x_dpll_free - free DPLL device\n- * @zldpll: pointer to zl3073x_dpll structure\n+ * zl3073x_dpll_ptp_adjtime - adjust PTP clock time\n+ * @info: PTP clock info\n+ * @delta: time adjustment in nanoseconds\n  *\n- * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc.\n+ * For NCO, large deltas (\u003e= 1 second) are split into a ToD\n+ * read-modify-write for the seconds part and an output phase step for\n+ * the sub-second remainder. Sub-second deltas use phase step directly,\n+ * falling back to ToD read-modify-write if phase step is unavailable.\n+ * In AUTO/REFLOCK modes, large deltas are split into ToD\n+ * read-modify-write for seconds and TIE write for the sub-second\n+ * remainder. Sub-second deltas use TIE write directly.\n+ *\n+ * Return: 0 on success, \u003c0 on error\n  */\n-void\n-zl3073x_dpll_free(struct zl3073x_dpll *zldpll)\n+static int zl3073x_dpll_ptp_adjtime(struct ptp_clock_info *info, s64 delta)\n {\n-\tWARN(zldpll-\u003edpll_dev, \"DPLL device is still registered\\n\");\n+\tstruct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,\n+\t\t\t\t\t\t   ptp_info);\n+\tstruct zl3073x_dev *zldev = zldpll-\u003edev;\n+\tconst struct zl3073x_chan *chan;\n+\tbool sec_adjusted = false;\n+\tstruct timespec64 ts;\n+\tint rc;\n \n-\tmutex_destroy(\u0026zldpll-\u003elock);\n-\tkfree(zldpll);\n+\tif (!delta)\n+\t\treturn 0;\n+\n+\tguard(mutex)(\u0026zldpll-\u003elock);\n+\n+\t/* Modes without phase step or TIE use plain ToD adjust */\n+\tchan = zl3073x_chan_state_get(zldev, zldpll-\u003eid);\n+\tif (!zl3073x_chan_mode_is_nco(chan) \u0026\u0026\n+\t    !zl3073x_chan_mode_supports_tie(chan))\n+\t\treturn zl3073x_chan_tod_adjust(zldev, zldpll-\u003eid,\n+\t\t\t\t\t       ns_to_timespec64(delta));\n+\n+\t/* Split off seconds via ToD read-modify-write so the sub-second\n+\t * remainder can be applied through the output-coherent mechanism\n+\t * (phase step or TIE write).\n+\t */\n+\tif (abs(delta) \u003e= NSEC_PER_SEC) {\n+\t\ts32 remainder;\n+\n+\t\tts.tv_sec = div_s64_rem(delta, NSEC_PER_SEC, \u0026remainder);\n+\t\tts.tv_nsec = 0;\n+\t\tdelta = remainder;\n+\n+\t\trc = zl3073x_chan_tod_adjust(zldev, zldpll-\u003eid, ts);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\n+\t\tsec_adjusted = true;\n+\n+\t\t/* No sub-second remainder, done */\n+\t\tif (!delta)\n+\t\t\treturn 0;\n+\t}\n+\n+\t/* Apply sub-second delta via phase step (NCO) or TIE write */\n+\tif (zl3073x_chan_mode_is_nco(chan)) {\n+\t\trc = zl3073x_dpll_ptp_adjtime_phase_step(zldpll, delta);\n+\t\tif (!rc)\n+\t\t\treturn 0;\n+\t} else {\n+\t\trc = zl3073x_chan_tie_write(zldev, zldpll-\u003eid, delta);\n+\t\tif (!rc)\n+\t\t\treturn 0;\n+\t}\n+\n+\t/* Phase step or TIE write failed, fall back to ToD adjust */\n+\trc = zl3073x_chan_tod_adjust(zldev, zldpll-\u003eid,\n+\t\t\t\t     ns_to_timespec64(delta));\n+\n+\t/* In the unlikely event that both phase step/TIE write and fallback\n+\t * ToD adjust fail after seconds were already committed, return\n+\t * success to prevent the PTP servo from retrying the full delta and\n+\t * applying seconds again. The sub-second residual will self-correct\n+\t * in the next servo cycle.\n+\t */\n+\tif (rc \u0026\u0026 sec_adjusted) {\n+\t\tdev_warn(zldev-\u003edev,\n+\t\t\t \"Sub-second adjustment failed after seconds applied\\n\");\n+\t\treturn 0;\n+\t}\n+\n+\treturn rc;\n+}\n+\n+/**\n+ * zl3073x_dpll_ptp_adjfine - adjust PTP clock frequency\n+ * @info: PTP clock info\n+ * @scaled_ppm: frequency adjustment in scaled ppm (ppm * 2^16)\n+ *\n+ * Only supported for NCO. Writes the delta frequency offset register.\n+ *\n+ * Return: 0 on success, -EOPNOTSUPP if NCO pin is not connected, \u003c0 on error\n+ */\n+static int\n+zl3073x_dpll_ptp_adjfine(struct ptp_clock_info *info, long scaled_ppm)\n+{\n+\tstruct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,\n+\t\t\t\t\t\t   ptp_info);\n+\tconst struct zl3073x_chan *chan;\n+\ts64 offset;\n+\n+\t/* Convert scaled_ppm to df_offset in 2^-48 steps:\n+\t *   df_offset = -(scaled_ppm * 2^32) / 10^6\n+\t *\n+\t * Simplify to avoid overflow:\n+\t *   df_offset = -(scaled_ppm * 2^26) / 5^6\n+\t *   df_offset = -(scaled_ppm * 67108864) / 15625\n+\t */\n+\toffset = -div_s64((s64)scaled_ppm * 67108864LL, 15625);\n+\n+\tguard(mutex)(\u0026zldpll-\u003elock);\n+\n+\tchan = zl3073x_chan_state_get(zldpll-\u003edev, zldpll-\u003eid);\n+\tif (!zl3073x_chan_mode_is_nco(chan))\n+\t\treturn scaled_ppm ? -EOPNOTSUPP : 0;\n+\tif (offset == chan-\u003edf_offset)\n+\t\treturn 0;\n+\n+\treturn zl3073x_chan_df_offset_set(zldpll-\u003edev, zldpll-\u003eid, offset);\n+}\n+\n+/**\n+ * zl3073x_dpll_ptp_adjphase - adjust PTP clock phase\n+ * @info: PTP clock info\n+ * @delta: phase adjustment in nanoseconds\n+ *\n+ * Only supported in AUTO and REFLOCK modes. Uses TIE write for\n+ * nanosecond resolution phase adjustment.\n+ *\n+ * Return: 0 on success, -EOPNOTSUPP if mode doesn't support TIE, \u003c0 on error\n+ */\n+static int zl3073x_dpll_ptp_adjphase(struct ptp_clock_info *info, s32 delta)\n+{\n+\tstruct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,\n+\t\t\t\t\t\t   ptp_info);\n+\tstruct zl3073x_dev *zldev = zldpll-\u003edev;\n+\tconst struct zl3073x_chan *chan;\n+\n+\tif (!delta)\n+\t\treturn 0;\n+\n+\tguard(mutex)(\u0026zldpll-\u003elock);\n+\n+\tchan = zl3073x_chan_state_get(zldev, zldpll-\u003eid);\n+\n+\tif (!zl3073x_chan_mode_supports_tie(chan))\n+\t\treturn -EOPNOTSUPP;\n+\n+\treturn zl3073x_chan_tie_write(zldev, zldpll-\u003eid, delta);\n+}\n+\n+static s32\n+zl3073x_dpll_ptp_getmaxphase(struct ptp_clock_info *info __always_unused)\n+{\n+\t/* HW limits TIE write to +-1 second. Return the constant HW\n+\t * limit and let adjphase handle mode-specific checks.\n+\t */\n+\treturn NSEC_PER_SEC - 1;\n+}\n+\n+/**\n+ * zl3073x_dpll_ptp_perout_find_pin - find pin by perout channel index\n+ * @zldpll: DPLL channel\n+ * @idx: perout channel index to find\n+ *\n+ * Return: pointer to the pin, or NULL if not found\n+ */\n+static struct zl3073x_dpll_pin *\n+zl3073x_dpll_ptp_perout_find_pin(struct zl3073x_dpll *zldpll, int idx)\n+{\n+\tstruct zl3073x_dpll_pin *pin;\n+\n+\tlist_for_each_entry(pin, \u0026zldpll-\u003epins, list) {\n+\t\tif (pin-\u003eperout_idx == idx)\n+\t\t\treturn pin;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+/**\n+ * zl3073x_dpll_ptp_enable - enable/disable PTP clock functions\n+ * @info: PTP clock info\n+ * @rq: the requested clock function and parameters\n+ * @on: true to enable, false to disable\n+ *\n+ * Handles PTP_CLK_REQ_PEROUT requests. Only 1PPS (period = 1s) is supported.\n+ * On enable, configures the output divider for 1 Hz. On disable, does nothing\n+ * as the signal is not disabled.\n+ *\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+static int zl3073x_dpll_ptp_enable(struct ptp_clock_info *info,\n+\t\t\t\t   struct ptp_clock_request *rq, int on)\n+{\n+\tstruct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,\n+\t\t\t\t\t\t   ptp_info);\n+\tstruct zl3073x_dpll_pin *pin;\n+\tunsigned int idx;\n+\tint rc;\n+\n+\tif (rq-\u003etype != PTP_CLK_REQ_PEROUT)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tidx = rq-\u003eperout.index;\n+\tif (idx \u003e= info-\u003en_per_out)\n+\t\treturn -EINVAL;\n+\n+\t/* Toggling output pins can produce runt pulses, so disable\n+\t * is a no-op.\n+\t */\n+\tif (!on)\n+\t\treturn 0;\n+\n+\t/* Only accept exactly 1PPS (period.sec == 1, nsec == 0) */\n+\tif (rq-\u003eperout.period.sec != 1 || rq-\u003eperout.period.nsec != 0)\n+\t\treturn -EINVAL;\n+\n+\tmutex_lock(\u0026zldpll-\u003elock);\n+\n+\tpin = zl3073x_dpll_ptp_perout_find_pin(zldpll, idx);\n+\tif (!pin) {\n+\t\tmutex_unlock(\u0026zldpll-\u003elock);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* Already at 1 Hz, nothing to do */\n+\tif (zl3073x_dev_output_pin_freq_get(zldpll-\u003edev, pin-\u003eid) == 1) {\n+\t\tmutex_unlock(\u0026zldpll-\u003elock);\n+\t\treturn 0;\n+\t}\n+\n+\trc = __zl3073x_dpll_output_pin_frequency_set(zldpll, pin, 1);\n+\n+\tmutex_unlock(\u0026zldpll-\u003elock);\n+\n+\tif (!rc)\n+\t\tdpll_pin_change_ntf(pin-\u003edpll_pin);\n+\n+\treturn rc;\n+}\n+\n+static const struct ptp_clock_info zl3073x_dpll_ptp_clock_info = {\n+\t.owner\t\t= THIS_MODULE,\n+\t.max_adj\t= ZL3073X_DPLL_PTP_MAX_ADJ,\n+\t.gettimex64\t= zl3073x_dpll_ptp_gettimex64,\n+\t.settime64\t= zl3073x_dpll_ptp_settime64,\n+\t.adjtime\t= zl3073x_dpll_ptp_adjtime,\n+\t.adjfine\t= zl3073x_dpll_ptp_adjfine,\n+\t.adjphase\t= zl3073x_dpll_ptp_adjphase,\n+\t.getmaxphase\t= zl3073x_dpll_ptp_getmaxphase,\n+\t.enable\t\t= zl3073x_dpll_ptp_enable,\n+};\n+\n+/**\n+ * zl3073x_dpll_ptp_register - register PTP clock for a DPLL channel\n+ * @zldpll: DPLL channel to register PTP clock for\n+ *\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+static int zl3073x_dpll_ptp_register(struct zl3073x_dpll *zldpll)\n+{\n+\tstruct zl3073x_dev *zldev = zldpll-\u003edev;\n+\tstruct ptp_clock *ptp_clock;\n+\n+\tsnprintf(zldpll-\u003eptp_info.name, sizeof(zldpll-\u003eptp_info.name),\n+\t\t \"zl3073x-dpll%u\", zldpll-\u003eid);\n+\n+\tptp_clock = ptp_clock_register(\u0026zldpll-\u003eptp_info, zldev-\u003edev);\n+\tif (IS_ERR(ptp_clock)) {\n+\t\tdev_err(zldev-\u003edev, \"Failed to register PTP clock for DPLL%u\\n\",\n+\t\t\tzldpll-\u003eid);\n+\t\treturn PTR_ERR(ptp_clock);\n+\t}\n+\n+\tzldpll-\u003eptp_clock = ptp_clock;\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * zl3073x_dpll_ptp_unregister - unregister PTP clock for a DPLL channel\n+ * @zldpll: DPLL channel to unregister PTP clock for\n+ */\n+static void zl3073x_dpll_ptp_unregister(struct zl3073x_dpll *zldpll)\n+{\n+\tif (!IS_ERR_OR_NULL(zldpll-\u003eptp_clock)) {\n+\t\tptp_clock_unregister(zldpll-\u003eptp_clock);\n+\t\tzldpll-\u003eptp_clock = NULL;\n+\t}\n }\n \n /**\n@@ -2403,6 +2863,48 @@ zl3073x_dpll_ref_sync_pairs_register(struct zl3073x_dpll *zldpll)\n \treturn 0;\n }\n \n+/**\n+ * zl3073x_dpll_alloc - allocate DPLL device\n+ * @zldev: pointer to zl3073x device\n+ * @ch: DPLL channel number\n+ *\n+ * Allocates DPLL device structure for given DPLL channel.\n+ *\n+ * Return: pointer to DPLL device on success, error pointer on error\n+ */\n+struct zl3073x_dpll *\n+zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)\n+{\n+\tstruct zl3073x_dpll *zldpll;\n+\n+\tzldpll = kzalloc_obj(*zldpll);\n+\tif (!zldpll)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tzldpll-\u003edev = zldev;\n+\tzldpll-\u003eid = ch;\n+\tzldpll-\u003eptp_info = zl3073x_dpll_ptp_clock_info;\n+\tmutex_init(\u0026zldpll-\u003elock);\n+\tINIT_LIST_HEAD(\u0026zldpll-\u003epins);\n+\n+\treturn zldpll;\n+}\n+\n+/**\n+ * zl3073x_dpll_free - free DPLL device\n+ * @zldpll: pointer to zl3073x_dpll structure\n+ *\n+ * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc.\n+ */\n+void\n+zl3073x_dpll_free(struct zl3073x_dpll *zldpll)\n+{\n+\tWARN(zldpll-\u003edpll_dev, \"DPLL device is still registered\\n\");\n+\n+\tmutex_destroy(\u0026zldpll-\u003elock);\n+\tkfree(zldpll);\n+}\n+\n /**\n  * zl3073x_dpll_register - register DPLL device and all its pins\n  * @zldpll: pointer to zl3073x_dpll structure\n@@ -2433,6 +2935,13 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll)\n \t\treturn rc;\n \t}\n \n+\trc = zl3073x_dpll_ptp_register(zldpll);\n+\tif (rc) {\n+\t\tzl3073x_dpll_pins_unregister(zldpll);\n+\t\tzl3073x_dpll_device_unregister(zldpll);\n+\t\treturn rc;\n+\t}\n+\n \treturn 0;\n }\n \n@@ -2446,7 +2955,7 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll)\n void\n zl3073x_dpll_unregister(struct zl3073x_dpll *zldpll)\n {\n-\t/* Unregister all pins and dpll */\n+\tzl3073x_dpll_ptp_unregister(zldpll);\n \tzl3073x_dpll_pins_unregister(zldpll);\n \tzl3073x_dpll_device_unregister(zldpll);\n }\ndiff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h\nindex faebc402ba1b70..9b5f72c33849ce 100644\n--- a/drivers/dpll/zl3073x/dpll.h\n+++ b/drivers/dpll/zl3073x/dpll.h\n@@ -5,6 +5,8 @@\n \n #include \u003clinux/dpll.h\u003e\n #include \u003clinux/list.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/ptp_clock_kernel.h\u003e\n \n #include \"core.h\"\n \n@@ -22,6 +24,8 @@\n  * @type: DPLL type (PPS or EEC)\n  * @lock_status: last saved DPLL lock status\n  * @pins: list of pins\n+ * @ptp_info: PTP clock info\n+ * @ptp_clock: registered PTP clock (or NULL)\n  */\n struct zl3073x_dpll {\n \tstruct list_head\t\tlist;\n@@ -36,6 +40,8 @@ struct zl3073x_dpll {\n \tenum dpll_type\t\t\ttype;\n \tenum dpll_lock_status\t\tlock_status;\n \tstruct list_head\t\tpins;\n+\tstruct ptp_clock_info\t\tptp_info;\n+\tstruct ptp_clock\t\t*ptp_clock;\n };\n \n struct zl3073x_dpll *zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch);\ndiff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h\nindex b70ead7d4495b2..447757e99845d8 100644\n--- a/drivers/dpll/zl3073x/regs.h\n+++ b/drivers/dpll/zl3073x/regs.h\n@@ -179,6 +179,20 @@\n #define ZL_DPLL_DF_READ_CMD\t\t\tGENMASK(2, 0)\n #define ZL_DPLL_DF_READ_CMD_ACC_I\t\t4\n \n+#define ZL_REG_DPLL_TIE_CTRL\t\t\tZL_REG(5, 0x30, 1)\n+#define ZL_DPLL_TIE_CTRL_OP\t\t\tGENMASK(2, 0)\n+#define ZL_DPLL_TIE_CTRL_OP_WR\t\t\t4\n+\n+#define ZL_REG_DPLL_TIE_CTRL_MASK\t\tZL_REG(5, 0x31, 1)\n+\n+#define ZL_REG_DPLL_TOD_CTRL(_idx)\t\t\t\t\t\\\n+\tZL_REG_IDX(_idx, 5, 0x38, 1, 8, 1)\n+#define ZL_DPLL_TOD_CTRL_SEM\t\t\tBIT(4)\n+#define ZL_DPLL_TOD_CTRL_CMD\t\t\tGENMASK(3, 0)\n+#define ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ\t1\n+#define ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT\t8\n+#define ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ\t9\n+\n #define ZL_REG_DPLL_MEAS_CTRL\t\t\tZL_REG(5, 0x50, 1)\n #define ZL_DPLL_MEAS_CTRL_EN\t\t\tBIT(0)\n #define ZL_DPLL_MEAS_CTRL_AVG_FACTOR\t\tGENMASK(7, 4)\n@@ -193,6 +207,9 @@\n \n /*******************************\n  * Register Pages 6-7, DPLL Data\n+ *\n+ * Per-channel registers with stride 0x20. Channels 0-3 reside on page 6,\n+ * channel 4 on page 7.\n  *******************************/\n \n #define ZL_REG_DPLL_DF_OFFSET_03(_idx)\t\t\t\t\t\\\n@@ -202,6 +219,24 @@\n \t((_idx) \u003c 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4)\n #define ZL_DPLL_DF_OFFSET_UNKNOWN\tS64_MIN\n \n+#define ZL_REG_DPLL_TIE_DATA_03(_idx)\t\t\t\t\t\\\n+\tZL_REG_IDX(_idx, 6, 0x0C, 6, 4, 0x20)\n+#define ZL_REG_DPLL_TIE_DATA_4\t\t\tZL_REG(7, 0x0C, 6)\n+#define ZL_REG_DPLL_TIE_DATA(_idx)\t\t\t\t\t\\\n+\t((_idx) \u003c 4 ? ZL_REG_DPLL_TIE_DATA_03(_idx) : ZL_REG_DPLL_TIE_DATA_4)\n+\n+#define ZL_REG_DPLL_TOD_SEC_03(_idx)\t\t\t\t\t\\\n+\tZL_REG_IDX(_idx, 6, 0x12, 6, 4, 0x20)\n+#define ZL_REG_DPLL_TOD_SEC_4\t\t\tZL_REG(7, 0x12, 6)\n+#define ZL_REG_DPLL_TOD_SEC(_idx)\t\t\t\t\t\\\n+\t((_idx) \u003c 4 ? ZL_REG_DPLL_TOD_SEC_03(_idx) : ZL_REG_DPLL_TOD_SEC_4)\n+\n+#define ZL_REG_DPLL_TOD_NS_03(_idx)\t\t\t\t\t\\\n+\tZL_REG_IDX(_idx, 6, 0x18, 4, 4, 0x20)\n+#define ZL_REG_DPLL_TOD_NS_4\t\t\tZL_REG(7, 0x18, 4)\n+#define ZL_REG_DPLL_TOD_NS(_idx)\t\t\t\t\t\\\n+\t((_idx) \u003c 4 ? ZL_REG_DPLL_TOD_NS_03(_idx) : ZL_REG_DPLL_TOD_NS_4)\n+\n /***********************************\n  * Register Page 9, Synth and Output\n  ***********************************/\n@@ -221,6 +256,8 @@\n #define ZL_OUTPUT_CTRL_EN\t\t\tBIT(0)\n #define ZL_OUTPUT_CTRL_SYNTH_SEL\t\tGENMASK(6, 4)\n \n+#define ZL_REG_OUTPUT_STEP_TIME_MASK\t\tZL_REG(9, 0x36, 2)\n+\n /*******************************\n  * Register Page 10, Ref Mailbox\n  *******************************/\n@@ -316,6 +353,25 @@\n #define ZL_REG_OUTPUT_ESYNC_WIDTH\t\tZL_REG(14, 0x18, 4)\n #define ZL_REG_OUTPUT_PHASE_COMP\t\tZL_REG(14, 0x20, 4)\n \n+/***********************************\n+ * Register Page 9, Output Phase Step\n+ ***********************************/\n+\n+#define ZL_REG_OUTPUT_PHASE_STEP_CTRL\t\tZL_REG(9, 0x38, 1)\n+#define ZL_OUTPUT_PHASE_STEP_CTRL_DPLL\t\tGENMASK(6, 4)\n+#define ZL_OUTPUT_PHASE_STEP_CTRL_TOD_STEP\tBIT(3)\n+#define ZL_OUTPUT_PHASE_STEP_CTRL_OP\t\tGENMASK(1, 0)\n+#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_NONE\t0\n+#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_RESET\t1\n+#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_READ\t2\n+#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_WRITE\t3\n+\n+#define ZL_REG_OUTPUT_PHASE_STEP_NUMBER\t\tZL_REG(9, 0x39, 1)\n+\n+#define ZL_REG_OUTPUT_PHASE_STEP_MASK\t\tZL_REG(9, 0x3a, 2)\n+\n+#define ZL_REG_OUTPUT_PHASE_STEP_DATA\t\tZL_REG(9, 0x3c, 4)\n+\n /*\n  * Register Page 255 - HW registers access\n  */\n"
}

2/1 2026/07/13 13:36 agent patch-evaluator
3m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch adds PTP clock support to the Microchip Azurite DPLL driver (zl3073x). This is a hardware-specific I2C/SPI driver. During initialization (zl3073x_dev_probe), the driver strictly reads and verifies the hardware chip ID (ZL_REG_ID). Since there is no software emulator for this specific hardware in the kernel, and generic I2C mocks like i2c-stub do not support the 16-bit register addressing required by this driver, the probe function will always fail in virtualized environments (QEMU/GCE). Consequently, the DPLL and PTP devices will never be registered, making the modified code paths impossible to reach and fuzz.",
  "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).

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.
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.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. 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 5a600a1bd65a21f474dbecf9ee6d9db73eb82808
Author: syz-cluster <triage@syzkaller.com>
Date:   Mon Jul 13 13:36:56 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/dpll/zl3073x/Kconfig b/drivers/dpll/zl3073x/Kconfig
index 5bbca140058131..d0574ad571d3fa 100644
--- a/drivers/dpll/zl3073x/Kconfig
+++ b/drivers/dpll/zl3073x/Kconfig
@@ -2,7 +2,7 @@
 
 config ZL3073X
 	tristate "Microchip Azurite DPLL/PTP/SyncE devices" if COMPILE_TEST
-	depends on NET
+	depends on NET && PTP_1588_CLOCK_OPTIONAL
 	select DPLL
 	select NET_DEVLINK
 	select REGMAP
@@ -16,7 +16,7 @@ config ZL3073X
 
 config ZL3073X_I2C
 	tristate "I2C bus implementation for Microchip Azurite devices"
-	depends on I2C && NET
+	depends on I2C && NET && PTP_1588_CLOCK_OPTIONAL
 	select REGMAP_I2C
 	select ZL3073X
 	help
@@ -28,7 +28,7 @@ config ZL3073X_I2C
 
 config ZL3073X_SPI
 	tristate "SPI bus implementation for Microchip Azurite devices"
-	depends on NET && SPI
+	depends on NET && SPI && PTP_1588_CLOCK_OPTIONAL
 	select REGMAP_SPI
 	select ZL3073X
 	help
diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
index 4ec2cf53dad468..53032e782fd4af 100644
--- a/drivers/dpll/zl3073x/chan.c
+++ b/drivers/dpll/zl3073x/chan.c
@@ -3,6 +3,7 @@
 #include <linux/cleanup.h>
 #include <linux/delay.h>
 #include <linux/dev_printk.h>
+#include <linux/ptp_clock_kernel.h>
 #include <linux/string.h>
 #include <linux/types.h>
 
@@ -202,6 +203,11 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
 		zl3073x_chan_refsel_state_get(chan),
 		zl3073x_chan_refsel_ref_get(chan));
 
+	rc = zl3073x_read_u16(zldev, ZL_REG_OUTPUT_STEP_TIME_MASK,
+			      &chan->out_step_time_mask);
+	if (rc)
+		return rc;
+
 	guard(mutex)(&zldev->multiop_lock);
 
 	/* Read DPLL configuration from mailbox */
@@ -234,6 +240,307 @@ const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,
 	return &zldev->chan[index];
 }
 
+/**
+ * zl3073x_chan_tod_ready_wait - wait for ToD semaphore to clear
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ *
+ * Polls the ToD control register until the semaphore bit is cleared,
+ * indicating the device has completed the previous ToD operation.
+ *
+ * Return: 0 on success, -EBUSY if semaphore not cleared, <0 on error
+ */
+static int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch)
+{
+	unsigned int timeout;
+	u8 tod_ctrl;
+	int rc;
+
+	rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), &tod_ctrl);
+	if (rc)
+		return rc;
+
+	switch (FIELD_GET(ZL_DPLL_TOD_CTRL_CMD, tod_ctrl)) {
+	case ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ:
+		timeout = ZL_POLL_TOD_WR_TIMEOUT_US;
+		break;
+	default:
+		timeout = ZL_POLL_TOD_RD_TIMEOUT_US;
+		break;
+	}
+
+	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch),
+				  ZL_DPLL_TOD_CTRL_SEM, timeout);
+
+	return rc == -ETIMEDOUT ? -EBUSY : rc;
+}
+
+/**
+ * zl3073x_chan_tod_ctrl - issue ToD command
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @cmd: ToD command to execute
+ *
+ * Writes the semaphore and command to dpll_tod_ctrl. The caller must
+ * ensure the device is ready (semaphore clear) before calling and
+ * must wait for completion if needed.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int zl3073x_chan_tod_ctrl(struct zl3073x_dev *zldev, u8 ch, u8 cmd)
+{
+	return zl3073x_write_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch),
+				ZL_DPLL_TOD_CTRL_SEM | cmd);
+}
+
+/**
+ * zl3073x_chan_tod_read - read ToD registers after issuing a command
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @next_hz: if true, read predicted ToD at next 1 Hz; otherwise read current
+ * @ts: timespec to store the result
+ * @sts: optional system timestamp pair for cross-timestamping
+ *
+ * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch,
+			  bool next_hz, struct timespec64 *ts,
+			  struct ptp_system_timestamp *sts)
+{
+	u32 nsec;
+	u64 sec;
+	u8 cmd;
+	int rc;
+
+	if (next_hz)
+		cmd = ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ;
+	else
+		cmd = ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT;
+
+	/* Wait for any previous ToD operation to complete */
+	rc = zl3073x_chan_tod_ready_wait(zldev, ch);
+	if (rc)
+		return rc;
+
+	ptp_read_system_prets(sts);
+	rc = zl3073x_chan_tod_ctrl(zldev, ch, cmd);
+	if (rc)
+		return rc;
+
+	rc = zl3073x_chan_tod_ready_wait(zldev, ch);
+	if (rc)
+		return rc;
+	ptp_read_system_postts(sts);
+
+	rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), &sec);
+	if (rc)
+		return rc;
+
+	/* HW nanoseconds are always in [0, NSEC_PER_SEC) range */
+	rc = zl3073x_read_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), &nsec);
+	if (rc)
+		return rc;
+
+	ts->tv_sec = sec;
+	ts->tv_nsec = nsec;
+
+	return 0;
+}
+
+/**
+ * zl3073x_chan_tod_write - write ToD registers and trigger 1 Hz update
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @ts: time to set
+ *
+ * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch,
+			   struct timespec64 ts)
+{
+	int rc;
+
+	/* Wait for any previous ToD operation to complete */
+	rc = zl3073x_chan_tod_ready_wait(zldev, ch);
+	if (rc)
+		return rc;
+
+	rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), ts.tv_sec);
+	if (rc)
+		return rc;
+
+	rc = zl3073x_write_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), ts.tv_nsec);
+	if (rc)
+		return rc;
+
+	return zl3073x_chan_tod_ctrl(zldev, ch,
+				    ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ);
+}
+
+/**
+ * zl3073x_chan_tod_adjust - atomic ToD read-modify-write with rollover guard
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @delta: time adjustment to apply
+ *
+ * Reads the next-Hz ToD and current ToD, then checks whether enough time
+ * remains before the next 1 Hz rollover to safely complete the write.
+ * If less than 20 ms remains, waits for the rollover and increments the
+ * next-Hz seconds by one. Applies @delta and writes the result back.
+ *
+ * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch,
+			    struct timespec64 delta)
+{
+	static const long threshold_ns = 20 * NSEC_PER_MSEC;
+	struct timespec64 ts_next, ts_cur, diff;
+	int rc;
+
+	/* Read predicted ToD at next 1 Hz tick */
+	rc = zl3073x_chan_tod_read(zldev, ch, true, &ts_next, NULL);
+	if (rc)
+		return rc;
+
+	/* Read current ToD to determine remaining margin */
+	rc = zl3073x_chan_tod_read(zldev, ch, false, &ts_cur, NULL);
+	if (rc)
+		return rc;
+
+	/* If too close to (or past) the next rollover, wait it out */
+	diff = timespec64_sub(ts_next, ts_cur);
+	if (diff.tv_sec < 0 || (!diff.tv_sec && diff.tv_nsec < threshold_ns)) {
+		if (!diff.tv_sec && diff.tv_nsec)
+			fsleep((unsigned long)diff.tv_nsec / NSEC_PER_USEC + 1);
+		ts_next.tv_sec++;
+	}
+
+	/* Apply delta to the next-Hz ToD */
+	ts_next = timespec64_add(ts_next, delta);
+	if (!timespec64_valid(&ts_next))
+		return -EINVAL;
+
+	/* Write adjusted ToD back and wait for completion */
+	rc = zl3073x_chan_tod_write(zldev, ch, ts_next);
+	if (rc)
+		return rc;
+
+	return zl3073x_chan_tod_ready_wait(zldev, ch);
+}
+
+/**
+ * zl3073x_chan_df_offset_set - write delta frequency offset to hardware
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @offset: frequency offset in 2^-48 steps
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset)
+{
+	int rc;
+
+	rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_DF_OFFSET(ch), offset);
+	if (!rc)
+		zldev->chan[ch].df_offset = offset;
+
+	return rc;
+}
+
+/**
+ * zl3073x_chan_tie_write - adjust DPLL phase using TIE write
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @delta_ns: phase adjustment in nanoseconds (must be within +-1s)
+ *
+ * Converts nanoseconds to TIE units (0.01 ps) and writes TIE data
+ * to the specified channel.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns)
+{
+	s64 tie_data;
+	int rc;
+
+	guard(mutex)(&zldev->tie_lock);
+
+	/* Wait for any previous TIE operation to complete */
+	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TIE_CTRL,
+				  ZL_DPLL_TIE_CTRL_OP,
+				  ZL_POLL_TIE_WR_TIMEOUT_US);
+	if (rc)
+		return rc;
+
+	/* Convert ns to TIE units (0.01 ps = 10^-14 s) */
+	tie_data = delta_ns * 100000LL;
+
+	rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TIE_DATA(ch), tie_data);
+	if (rc)
+		return rc;
+
+	rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL_MASK, BIT(ch));
+	if (rc)
+		return rc;
+
+	return zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL,
+				ZL_DPLL_TIE_CTRL_OP_WR);
+}
+
+/**
+ * zl3073x_chan_phase_step - execute one output phase step operation
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @out_mask: bitmask of outputs to step
+ * @step_cycles: phase step in synthesizer clock cycles
+ * @tod_step: also step the ToD counter
+ *
+ * All masked outputs must use synthesizers of the same frequency since
+ * the step value is in synthesizer clock cycles.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch,
+			    u16 out_mask, s32 step_cycles,
+			    bool tod_step)
+{
+	u8 ctrl;
+	int rc;
+
+	guard(mutex)(&zldev->phase_step_lock);
+
+	/* Wait for any previous phase step operation to complete */
+	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL,
+				  ZL_OUTPUT_PHASE_STEP_CTRL_OP,
+				  ZL_POLL_PHASE_STEP_TIMEOUT_US);
+	if (rc)
+		return rc;
+
+	rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_STEP_DATA,
+			       step_cycles);
+	if (rc)
+		return rc;
+
+	rc = zl3073x_write_u16(zldev, ZL_REG_OUTPUT_PHASE_STEP_MASK, out_mask);
+	if (rc)
+		return rc;
+
+	rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_NUMBER, 1);
+	if (rc)
+		return rc;
+
+	ctrl = FIELD_PREP(ZL_OUTPUT_PHASE_STEP_CTRL_DPLL, ch) |
+	       FIELD_PREP(ZL_OUTPUT_PHASE_STEP_CTRL_OP,
+			  ZL_OUTPUT_PHASE_STEP_CTRL_OP_WRITE);
+	if (tod_step)
+		ctrl |= ZL_OUTPUT_PHASE_STEP_CTRL_TOD_STEP;
+
+	return zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL, ctrl);
+}
+
 /**
  * zl3073x_chan_state_set - commit DPLL channel state changes to hardware
  * @zldev: pointer to zl3073x_dev structure
diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h
index dc9c6d95bdee75..9dc5a3f1991f13 100644
--- a/drivers/dpll/zl3073x/chan.h
+++ b/drivers/dpll/zl3073x/chan.h
@@ -5,10 +5,12 @@
 
 #include <linux/bitfield.h>
 #include <linux/stddef.h>
+#include <linux/time64.h>
 #include <linux/types.h>
 
 #include "regs.h"
 
+struct ptp_system_timestamp;
 struct zl3073x_dev;
 
 /**
@@ -16,6 +18,7 @@ struct zl3073x_dev;
  * @ctrl: DPLL control register value
  * @mode_refsel: mode and reference selection register value
  * @ref_prio: reference priority registers (4 bits per ref, P/N packed)
+ * @out_step_time_mask: output step-time mask
  * @mon_status: monitor status register value
  * @refsel_status: reference selection status register value
  * @df_offset: frequency offset vs tracked reference in 2^-48 steps
@@ -26,6 +29,9 @@ struct zl3073x_chan {
 		u8	mode_refsel;
 		u8	ref_prio[ZL3073X_NUM_REFS / 2];
 	);
+	struct_group(inv, /* Invariants */
+		u16	out_step_time_mask;
+	);
 	struct_group(stat,
 		u8	mon_status;
 		u8	refsel_status;
@@ -42,6 +48,20 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
 int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index);
 int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index);
 
+int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch,
+			  bool next_hz, struct timespec64 *ts,
+			  struct ptp_system_timestamp *sts);
+int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch,
+			   struct timespec64 ts);
+int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch,
+			    struct timespec64 delta);
+int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch,
+			    u16 out_mask, s32 step_cycles, bool tod_step);
+
+int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset);
+
+int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns);
+
 /**
  * zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference
  * @chan: pointer to channel state
@@ -200,6 +220,21 @@ static inline bool zl3073x_chan_mode_is_reflock(const struct zl3073x_chan *chan)
 	return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_REFLOCK;
 }
 
+/**
+ * zl3073x_chan_mode_supports_tie - check if channel mode supports TIE write
+ * @chan: pointer to channel state
+ *
+ * TIE write is supported in AUTO and REFLOCK modes regardless of lock state.
+ *
+ * Return: true if TIE write is supported, false otherwise
+ */
+static inline bool
+zl3073x_chan_mode_supports_tie(const struct zl3073x_chan *chan)
+{
+	return zl3073x_chan_mode_is_auto(chan) ||
+		zl3073x_chan_mode_is_reflock(chan);
+}
+
 /**
  * zl3073x_chan_is_ho_ready - check if holdover is ready
  * @chan: pointer to channel state
@@ -233,4 +268,17 @@ static inline u8 zl3073x_chan_refsel_ref_get(const struct zl3073x_chan *chan)
 	return FIELD_GET(ZL_DPLL_REFSEL_STATUS_REFSEL, chan->refsel_status);
 }
 
+/**
+ * zl3073x_chan_is_out_stepped - check if output is in step-time mask
+ * @chan: pointer to channel state
+ * @out: output index
+ *
+ * Return: true if output is affected by step-time operations
+ */
+static inline bool
+zl3073x_chan_is_out_stepped(const struct zl3073x_chan *chan, u8 out)
+{
+	return !!(chan->out_step_time_mask & BIT(out));
+}
+
 #endif /* _ZL3073X_CHAN_H */
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 7f5afaaae6342f..b2f9fc97fd1b68 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -1033,6 +1033,14 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev)
 	 * and/or polls are required to be done atomically.
 	 */
 	rc = devm_mutex_init(zldev->dev, &zldev->multiop_lock);
+	if (rc)
+		return dev_err_probe(zldev->dev, rc,
+				     "Failed to initialize mutex\n");
+	rc = devm_mutex_init(zldev->dev, &zldev->phase_step_lock);
+	if (rc)
+		return dev_err_probe(zldev->dev, rc,
+				     "Failed to initialize mutex\n");
+	rc = devm_mutex_init(zldev->dev, &zldev->tie_lock);
 	if (rc)
 		return dev_err_probe(zldev->dev, rc,
 				     "Failed to initialize mutex\n");
diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
index 78dc208f3eea2a..2dc836d0c8f6b3 100644
--- a/drivers/dpll/zl3073x/core.h
+++ b/drivers/dpll/zl3073x/core.h
@@ -26,6 +26,10 @@ struct zl3073x_dpll;
 #define ZL_POLL_HWREG_TIMEOUT_US	(50 * USEC_PER_MSEC)
 #define ZL_POLL_MB_TIMEOUT_US		(30 * USEC_PER_MSEC)
 #define ZL_POLL_PHASE_ERR_TIMEOUT_US	(50 * USEC_PER_MSEC)
+#define ZL_POLL_PHASE_STEP_TIMEOUT_US	(3000 * USEC_PER_MSEC)
+#define ZL_POLL_TIE_WR_TIMEOUT_US	(1000 * USEC_PER_MSEC)
+#define ZL_POLL_TOD_RD_TIMEOUT_US	(30 * USEC_PER_MSEC)
+#define ZL_POLL_TOD_WR_TIMEOUT_US	(1000 * USEC_PER_MSEC)
 
 enum zl3073x_flags {
 	ZL3073X_FLAG_REF_PHASE_COMP_32_BIT,
@@ -55,6 +59,8 @@ struct zl3073x_chip_info {
  * @regmap: regmap to access device registers
  * @info: detected chip info
  * @multiop_lock: to serialize multiple register operations
+ * @tie_lock: to serialize TIE write operations
+ * @phase_step_lock: to serialize output phase step operations
  * @ref: array of input references' invariants
  * @out: array of outs' invariants
  * @synth: array of synths' invariants
@@ -71,6 +77,8 @@ struct zl3073x_dev {
 	struct regmap			*regmap;
 	const struct zl3073x_chip_info	*info;
 	struct mutex			multiop_lock;
+	struct mutex			tie_lock;
+	struct mutex			phase_step_lock;
 
 	/* Invariants */
 	struct zl3073x_ref	ref[ZL3073X_NUM_REFS];
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index d91f52b58eae34..7c11de35821aea 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -2,6 +2,7 @@
 
 #include <linux/bits.h>
 #include <linux/bitfield.h>
+#include <linux/cleanup.h>
 #include <linux/bug.h>
 #include <linux/container_of.h>
 #include <linux/dev_printk.h>
@@ -14,6 +15,7 @@
 #include <linux/netlink.h>
 #include <linux/platform_device.h>
 #include <linux/property.h>
+#include <linux/ptp_clock_kernel.h>
 #include <linux/slab.h>
 #include <linux/sprintf.h>
 
@@ -42,6 +44,7 @@
  * @phase_offset: last saved pin phase offset
  * @freq_offset: last saved fractional frequency offset
  * @measured_freq: last saved measured frequency
+ * @perout_idx: PTP perout channel index, or -1 if not perout-eligible
  */
 struct zl3073x_dpll_pin {
 	struct list_head	list;
@@ -59,6 +62,7 @@ struct zl3073x_dpll_pin {
 	s64			phase_offset;
 	s64			freq_offset;
 	u32			measured_freq;
+	s8			perout_idx;
 };
 
 /*
@@ -936,21 +940,17 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin,
 }
 
 static int
-zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
-				      void *pin_priv,
-				      const struct dpll_device *dpll,
-				      void *dpll_priv, u64 frequency,
-				      struct netlink_ext_ack *extack)
+__zl3073x_dpll_output_pin_frequency_set(struct zl3073x_dpll *zldpll,
+					struct zl3073x_dpll_pin *pin,
+					u64 frequency)
 {
-	struct zl3073x_dpll *zldpll = dpll_priv;
 	struct zl3073x_dev *zldev = zldpll->dev;
-	struct zl3073x_dpll_pin *pin = pin_priv;
 	const struct zl3073x_synth *synth;
 	u32 new_div, synth_freq;
 	struct zl3073x_out out;
 	u8 out_id;
 
-	guard(mutex)(&zldpll->lock);
+	lockdep_assert_held(&zldpll->lock);
 
 	out_id = zl3073x_output_pin_out_get(pin->id);
 	out = *zl3073x_out_state_get(zldev, out_id);
@@ -1011,6 +1011,21 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
 	return zl3073x_out_state_set(zldev, out_id, &out);
 }
 
+static int
+zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
+				      void *pin_priv,
+				      const struct dpll_device *dpll,
+				      void *dpll_priv, u64 frequency,
+				      struct netlink_ext_ack *extack)
+{
+	struct zl3073x_dpll *zldpll = dpll_priv;
+
+	guard(mutex)(&zldpll->lock);
+
+	return __zl3073x_dpll_output_pin_frequency_set(zldpll, pin_priv,
+						       frequency);
+}
+
 static int
 zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
 					 void *pin_priv,
@@ -1608,6 +1623,7 @@ zl3073x_dpll_pin_alloc(struct zl3073x_dpll *zldpll, enum dpll_pin_direction dir,
 	pin->dpll = zldpll;
 	pin->dir = dir;
 	pin->id = id;
+	pin->perout_idx = -1;
 
 	return pin;
 }
@@ -1626,6 +1642,44 @@ zl3073x_dpll_pin_free(struct zl3073x_dpll_pin *pin)
 	kfree(pin);
 }
 
+/**
+ * zl3073x_dpll_pin_is_perout - check if output pin is perout-eligible
+ * @pin: DPLL pin to check
+ * @props: pin properties (already fetched by caller)
+ *
+ * An output pin is eligible for PTP periodic output if it is single-ended
+ * (not differential) and supports 1 Hz in its frequency list.
+ *
+ * Return: true if eligible, false otherwise
+ */
+static bool
+zl3073x_dpll_pin_is_perout(struct zl3073x_dpll_pin *pin,
+			   const struct zl3073x_pin_props *props)
+{
+	struct zl3073x_dpll *zldpll = pin->dpll;
+	const struct zl3073x_chan *chan;
+	u8 out_id;
+	int i;
+
+	if (zl3073x_dpll_is_input_pin(pin))
+		return false;
+
+	out_id = zl3073x_output_pin_out_get(pin->id);
+	if (zl3073x_dev_out_is_diff(zldpll->dev, out_id))
+		return false;
+
+	chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+	if (!zl3073x_chan_is_out_stepped(chan, out_id))
+		return false;
+
+	for (i = 0; i < props->dpll_props.freq_supported_num; i++) {
+		if (props->dpll_props.freq_supported[i].min == 1)
+			return true;
+	}
+
+	return false;
+}
+
 /**
  * zl3073x_dpll_pin_register - register DPLL pin
  * @pin: pointer to DPLL pin
@@ -1667,6 +1721,8 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
 		if (pin->prio == ZL_DPLL_REF_PRIO_NONE)
 			/* Clamp prio to max value */
 			pin->prio = ZL_DPLL_REF_PRIO_MAX;
+	} else if (zl3073x_dpll_pin_is_perout(pin, props)) {
+		pin->perout_idx = zldpll->ptp_info.n_per_out++;
 	}
 
 	/* Create or get existing DPLL pin */
@@ -1897,6 +1953,8 @@ zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll)
 	u8 id, index;
 	int rc;
 
+	zldpll->ptp_info.n_per_out = 0;
+
 	/* Process input pins */
 	for (index = 0; index < ZL3073X_NUM_PINS; index++) {
 		/* First input pins and then output pins */
@@ -2280,45 +2338,447 @@ zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev)
 	return rc;
 }
 
+/* Maximum frequency adjustment: +-1% of nominal in ppb */
+#define ZL3073X_DPLL_PTP_MAX_ADJ	10000000
+
 /**
- * zl3073x_dpll_alloc - allocate DPLL device
- * @zldev: pointer to zl3073x device
- * @ch: DPLL channel number
+ * zl3073x_dpll_ptp_gettimex64 - read current time from ToD counters
+ * @info: PTP clock info
+ * @ts: timespec to store current time
+ * @sts: optional system timestamp pair for cross-timestamping
  *
- * Allocates DPLL device structure for given DPLL channel.
+ * Return: 0 on success, <0 on error
+ */
+static int zl3073x_dpll_ptp_gettimex64(struct ptp_clock_info *info,
+				       struct timespec64 *ts,
+				       struct ptp_system_timestamp *sts)
+{
+	struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+						   ptp_info);
+
+	guard(mutex)(&zldpll->lock);
+
+	return zl3073x_chan_tod_read(zldpll->dev, zldpll->id, false, ts, sts);
+}
+
+/**
+ * zl3073x_dpll_ptp_settime64 - set ToD counters to given time
+ * @info: PTP clock info
+ * @ts: timespec with time to set
  *
- * Return: pointer to DPLL device on success, error pointer on error
+ * Return: 0 on success, <0 on error
  */
-struct zl3073x_dpll *
-zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)
+static int zl3073x_dpll_ptp_settime64(struct ptp_clock_info *info,
+				      const struct timespec64 *ts)
 {
-	struct zl3073x_dpll *zldpll;
+	struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+						   ptp_info);
 
-	zldpll = kzalloc_obj(*zldpll);
-	if (!zldpll)
-		return ERR_PTR(-ENOMEM);
+	guard(mutex)(&zldpll->lock);
 
-	zldpll->dev = zldev;
-	zldpll->id = ch;
-	mutex_init(&zldpll->lock);
-	INIT_LIST_HEAD(&zldpll->pins);
+	return zl3073x_chan_tod_write(zldpll->dev, zldpll->id, *ts);
+}
 
-	return zldpll;
+/**
+ * zl3073x_dpll_ptp_adjtime_phase_step - adjust sub-second time via phase step
+ * @zldpll: DPLL channel
+ * @delta: time adjustment in nanoseconds (must be within (-NSEC_PER_SEC,
+ *         NSEC_PER_SEC))
+ *
+ * Uses the output phase step mechanism with tod_step=1 to adjust both
+ * the output clock phase and the ToD counter simultaneously. This keeps
+ * outputs and ToD coherent. Only valid for NCO.
+ *
+ * Outputs are grouped by synthesizer since the phase step value is in
+ * synthesizer clock cycles. The first synth group with enabled outputs
+ * uses tod_step to adjust both outputs and the ToD counter. Remaining
+ * groups step outputs only. If no synth has enabled outputs, the ToD
+ * counter is stepped alone using an empty output mask (the FW uses the
+ * lowest-ID synth's period for the conversion).
+ *
+ * Return: 0 on success, -EOPNOTSUPP if no synths available, <0 on error
+ */
+static int zl3073x_dpll_ptp_adjtime_phase_step(struct zl3073x_dpll *zldpll,
+					       s64 delta)
+{
+	u16 synth_mask[ZL3073X_NUM_SYNTHS] = {};
+	struct zl3073x_dev *zldev = zldpll->dev;
+	const struct zl3073x_synth *synth;
+	const struct zl3073x_chan *chan;
+	struct zl3073x_dpll_pin *pin;
+	u32 first_synth_freq = 0;
+	bool tod_stepped = false;
+	s32 step_cycles;
+	u32 synth_freq;
+	int rc;
+	u8 i;
+
+	chan = zl3073x_chan_state_get(zldev, zldpll->id);
+
+	/* Build per-synth output masks from registered output pins */
+	list_for_each_entry(pin, &zldpll->pins, list) {
+		u8 out_id, synth_id;
+
+		if (zl3073x_dpll_is_input_pin(pin))
+			continue;
+
+		out_id = zl3073x_output_pin_out_get(pin->id);
+
+		if (!zl3073x_chan_is_out_stepped(chan, out_id))
+			continue;
+
+		synth_id = zl3073x_dev_out_synth_get(zldev, out_id);
+		if (synth_id >= ZL3073X_NUM_SYNTHS) {
+			dev_warn(zldev->dev, "Unexpected synth id for OUT%u\n",
+				 out_id);
+			continue;
+		}
+		synth_mask[synth_id] |= BIT(out_id);
+	}
+
+	/* Process each synth group */
+	for (i = 0; i < ZL3073X_NUM_SYNTHS; i++) {
+		synth = zl3073x_synth_state_get(zldev, i);
+		if (!zl3073x_synth_is_enabled(synth) ||
+		    zl3073x_synth_dpll_get(synth) != zldpll->id)
+			continue;
+
+		synth_freq = zl3073x_synth_freq_get(synth);
+
+		/* Remember lowest-ID synth freq for ToD-only fallback */
+		if (!first_synth_freq)
+			first_synth_freq = synth_freq;
+
+		if (!synth_mask[i])
+			continue;
+
+		/* Safe for s32: max synth freq is 750 MHz */
+		step_cycles = div_s64(delta * synth_freq, NSEC_PER_SEC);
+
+		rc = zl3073x_chan_phase_step(zldev, zldpll->id,
+					     synth_mask[i], step_cycles,
+					     !tod_stepped);
+		if (rc) {
+			if (tod_stepped) {
+				dev_warn(zldev->dev,
+					 "Partial phase step failure\n");
+				return 0;
+			}
+			return rc;
+		}
+		tod_stepped = true;
+	}
+
+	if (!first_synth_freq)
+		return -EOPNOTSUPP;
+
+	/* No enabled outputs found; step ToD counter only using the
+	 * lowest-ID synth's period (empty output mask).
+	 */
+	if (!tod_stepped) {
+		step_cycles = div_s64(delta * first_synth_freq, NSEC_PER_SEC);
+		return zl3073x_chan_phase_step(zldev, zldpll->id, 0,
+					       step_cycles, true);
+	}
+
+	return 0;
 }
 
 /**
- * zl3073x_dpll_free - free DPLL device
- * @zldpll: pointer to zl3073x_dpll structure
+ * zl3073x_dpll_ptp_adjtime - adjust PTP clock time
+ * @info: PTP clock info
+ * @delta: time adjustment in nanoseconds
  *
- * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc.
+ * For NCO, large deltas (>= 1 second) are split into a ToD
+ * read-modify-write for the seconds part and an output phase step for
+ * the sub-second remainder. Sub-second deltas use phase step directly,
+ * falling back to ToD read-modify-write if phase step is unavailable.
+ * In AUTO/REFLOCK modes, large deltas are split into ToD
+ * read-modify-write for seconds and TIE write for the sub-second
+ * remainder. Sub-second deltas use TIE write directly.
+ *
+ * Return: 0 on success, <0 on error
  */
-void
-zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
+static int zl3073x_dpll_ptp_adjtime(struct ptp_clock_info *info, s64 delta)
 {
-	WARN(zldpll->dpll_dev, "DPLL device is still registered\n");
+	struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+						   ptp_info);
+	struct zl3073x_dev *zldev = zldpll->dev;
+	const struct zl3073x_chan *chan;
+	bool sec_adjusted = false;
+	struct timespec64 ts;
+	int rc;
 
-	mutex_destroy(&zldpll->lock);
-	kfree(zldpll);
+	if (!delta)
+		return 0;
+
+	guard(mutex)(&zldpll->lock);
+
+	/* Modes without phase step or TIE use plain ToD adjust */
+	chan = zl3073x_chan_state_get(zldev, zldpll->id);
+	if (!zl3073x_chan_mode_is_nco(chan) &&
+	    !zl3073x_chan_mode_supports_tie(chan))
+		return zl3073x_chan_tod_adjust(zldev, zldpll->id,
+					       ns_to_timespec64(delta));
+
+	/* Split off seconds via ToD read-modify-write so the sub-second
+	 * remainder can be applied through the output-coherent mechanism
+	 * (phase step or TIE write).
+	 */
+	if (abs(delta) >= NSEC_PER_SEC) {
+		s32 remainder;
+
+		ts.tv_sec = div_s64_rem(delta, NSEC_PER_SEC, &remainder);
+		ts.tv_nsec = 0;
+		delta = remainder;
+
+		rc = zl3073x_chan_tod_adjust(zldev, zldpll->id, ts);
+		if (rc)
+			return rc;
+
+		sec_adjusted = true;
+
+		/* No sub-second remainder, done */
+		if (!delta)
+			return 0;
+	}
+
+	/* Apply sub-second delta via phase step (NCO) or TIE write */
+	if (zl3073x_chan_mode_is_nco(chan)) {
+		rc = zl3073x_dpll_ptp_adjtime_phase_step(zldpll, delta);
+		if (!rc)
+			return 0;
+	} else {
+		rc = zl3073x_chan_tie_write(zldev, zldpll->id, delta);
+		if (!rc)
+			return 0;
+	}
+
+	/* Phase step or TIE write failed, fall back to ToD adjust */
+	rc = zl3073x_chan_tod_adjust(zldev, zldpll->id,
+				     ns_to_timespec64(delta));
+
+	/* In the unlikely event that both phase step/TIE write and fallback
+	 * ToD adjust fail after seconds were already committed, return
+	 * success to prevent the PTP servo from retrying the full delta and
+	 * applying seconds again. The sub-second residual will self-correct
+	 * in the next servo cycle.
+	 */
+	if (rc && sec_adjusted) {
+		dev_warn(zldev->dev,
+			 "Sub-second adjustment failed after seconds applied\n");
+		return 0;
+	}
+
+	return rc;
+}
+
+/**
+ * zl3073x_dpll_ptp_adjfine - adjust PTP clock frequency
+ * @info: PTP clock info
+ * @scaled_ppm: frequency adjustment in scaled ppm (ppm * 2^16)
+ *
+ * Only supported for NCO. Writes the delta frequency offset register.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if NCO pin is not connected, <0 on error
+ */
+static int
+zl3073x_dpll_ptp_adjfine(struct ptp_clock_info *info, long scaled_ppm)
+{
+	struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+						   ptp_info);
+	const struct zl3073x_chan *chan;
+	s64 offset;
+
+	/* Convert scaled_ppm to df_offset in 2^-48 steps:
+	 *   df_offset = -(scaled_ppm * 2^32) / 10^6
+	 *
+	 * Simplify to avoid overflow:
+	 *   df_offset = -(scaled_ppm * 2^26) / 5^6
+	 *   df_offset = -(scaled_ppm * 67108864) / 15625
+	 */
+	offset = -div_s64((s64)scaled_ppm * 67108864LL, 15625);
+
+	guard(mutex)(&zldpll->lock);
+
+	chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+	if (!zl3073x_chan_mode_is_nco(chan))
+		return scaled_ppm ? -EOPNOTSUPP : 0;
+	if (offset == chan->df_offset)
+		return 0;
+
+	return zl3073x_chan_df_offset_set(zldpll->dev, zldpll->id, offset);
+}
+
+/**
+ * zl3073x_dpll_ptp_adjphase - adjust PTP clock phase
+ * @info: PTP clock info
+ * @delta: phase adjustment in nanoseconds
+ *
+ * Only supported in AUTO and REFLOCK modes. Uses TIE write for
+ * nanosecond resolution phase adjustment.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if mode doesn't support TIE, <0 on error
+ */
+static int zl3073x_dpll_ptp_adjphase(struct ptp_clock_info *info, s32 delta)
+{
+	struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+						   ptp_info);
+	struct zl3073x_dev *zldev = zldpll->dev;
+	const struct zl3073x_chan *chan;
+
+	if (!delta)
+		return 0;
+
+	guard(mutex)(&zldpll->lock);
+
+	chan = zl3073x_chan_state_get(zldev, zldpll->id);
+
+	if (!zl3073x_chan_mode_supports_tie(chan))
+		return -EOPNOTSUPP;
+
+	return zl3073x_chan_tie_write(zldev, zldpll->id, delta);
+}
+
+static s32
+zl3073x_dpll_ptp_getmaxphase(struct ptp_clock_info *info __always_unused)
+{
+	/* HW limits TIE write to +-1 second. Return the constant HW
+	 * limit and let adjphase handle mode-specific checks.
+	 */
+	return NSEC_PER_SEC - 1;
+}
+
+/**
+ * zl3073x_dpll_ptp_perout_find_pin - find pin by perout channel index
+ * @zldpll: DPLL channel
+ * @idx: perout channel index to find
+ *
+ * Return: pointer to the pin, or NULL if not found
+ */
+static struct zl3073x_dpll_pin *
+zl3073x_dpll_ptp_perout_find_pin(struct zl3073x_dpll *zldpll, int idx)
+{
+	struct zl3073x_dpll_pin *pin;
+
+	list_for_each_entry(pin, &zldpll->pins, list) {
+		if (pin->perout_idx == idx)
+			return pin;
+	}
+
+	return NULL;
+}
+
+/**
+ * zl3073x_dpll_ptp_enable - enable/disable PTP clock functions
+ * @info: PTP clock info
+ * @rq: the requested clock function and parameters
+ * @on: true to enable, false to disable
+ *
+ * Handles PTP_CLK_REQ_PEROUT requests. Only 1PPS (period = 1s) is supported.
+ * On enable, configures the output divider for 1 Hz. On disable, does nothing
+ * as the signal is not disabled.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int zl3073x_dpll_ptp_enable(struct ptp_clock_info *info,
+				   struct ptp_clock_request *rq, int on)
+{
+	struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+						   ptp_info);
+	struct zl3073x_dpll_pin *pin;
+	unsigned int idx;
+	int rc;
+
+	if (rq->type != PTP_CLK_REQ_PEROUT)
+		return -EOPNOTSUPP;
+
+	idx = rq->perout.index;
+	if (idx >= info->n_per_out)
+		return -EINVAL;
+
+	/* Toggling output pins can produce runt pulses, so disable
+	 * is a no-op.
+	 */
+	if (!on)
+		return 0;
+
+	/* Only accept exactly 1PPS (period.sec == 1, nsec == 0) */
+	if (rq->perout.period.sec != 1 || rq->perout.period.nsec != 0)
+		return -EINVAL;
+
+	mutex_lock(&zldpll->lock);
+
+	pin = zl3073x_dpll_ptp_perout_find_pin(zldpll, idx);
+	if (!pin) {
+		mutex_unlock(&zldpll->lock);
+		return -EINVAL;
+	}
+
+	/* Already at 1 Hz, nothing to do */
+	if (zl3073x_dev_output_pin_freq_get(zldpll->dev, pin->id) == 1) {
+		mutex_unlock(&zldpll->lock);
+		return 0;
+	}
+
+	rc = __zl3073x_dpll_output_pin_frequency_set(zldpll, pin, 1);
+
+	mutex_unlock(&zldpll->lock);
+
+	if (!rc)
+		dpll_pin_change_ntf(pin->dpll_pin);
+
+	return rc;
+}
+
+static const struct ptp_clock_info zl3073x_dpll_ptp_clock_info = {
+	.owner		= THIS_MODULE,
+	.max_adj	= ZL3073X_DPLL_PTP_MAX_ADJ,
+	.gettimex64	= zl3073x_dpll_ptp_gettimex64,
+	.settime64	= zl3073x_dpll_ptp_settime64,
+	.adjtime	= zl3073x_dpll_ptp_adjtime,
+	.adjfine	= zl3073x_dpll_ptp_adjfine,
+	.adjphase	= zl3073x_dpll_ptp_adjphase,
+	.getmaxphase	= zl3073x_dpll_ptp_getmaxphase,
+	.enable		= zl3073x_dpll_ptp_enable,
+};
+
+/**
+ * zl3073x_dpll_ptp_register - register PTP clock for a DPLL channel
+ * @zldpll: DPLL channel to register PTP clock for
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int zl3073x_dpll_ptp_register(struct zl3073x_dpll *zldpll)
+{
+	struct zl3073x_dev *zldev = zldpll->dev;
+	struct ptp_clock *ptp_clock;
+
+	snprintf(zldpll->ptp_info.name, sizeof(zldpll->ptp_info.name),
+		 "zl3073x-dpll%u", zldpll->id);
+
+	ptp_clock = ptp_clock_register(&zldpll->ptp_info, zldev->dev);
+	if (IS_ERR(ptp_clock)) {
+		dev_err(zldev->dev, "Failed to register PTP clock for DPLL%u\n",
+			zldpll->id);
+		return PTR_ERR(ptp_clock);
+	}
+
+	zldpll->ptp_clock = ptp_clock;
+
+	return 0;
+}
+
+/**
+ * zl3073x_dpll_ptp_unregister - unregister PTP clock for a DPLL channel
+ * @zldpll: DPLL channel to unregister PTP clock for
+ */
+static void zl3073x_dpll_ptp_unregister(struct zl3073x_dpll *zldpll)
+{
+	if (!IS_ERR_OR_NULL(zldpll->ptp_clock)) {
+		ptp_clock_unregister(zldpll->ptp_clock);
+		zldpll->ptp_clock = NULL;
+	}
 }
 
 /**
@@ -2403,6 +2863,48 @@ zl3073x_dpll_ref_sync_pairs_register(struct zl3073x_dpll *zldpll)
 	return 0;
 }
 
+/**
+ * zl3073x_dpll_alloc - allocate DPLL device
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel number
+ *
+ * Allocates DPLL device structure for given DPLL channel.
+ *
+ * Return: pointer to DPLL device on success, error pointer on error
+ */
+struct zl3073x_dpll *
+zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)
+{
+	struct zl3073x_dpll *zldpll;
+
+	zldpll = kzalloc_obj(*zldpll);
+	if (!zldpll)
+		return ERR_PTR(-ENOMEM);
+
+	zldpll->dev = zldev;
+	zldpll->id = ch;
+	zldpll->ptp_info = zl3073x_dpll_ptp_clock_info;
+	mutex_init(&zldpll->lock);
+	INIT_LIST_HEAD(&zldpll->pins);
+
+	return zldpll;
+}
+
+/**
+ * zl3073x_dpll_free - free DPLL device
+ * @zldpll: pointer to zl3073x_dpll structure
+ *
+ * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc.
+ */
+void
+zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
+{
+	WARN(zldpll->dpll_dev, "DPLL device is still registered\n");
+
+	mutex_destroy(&zldpll->lock);
+	kfree(zldpll);
+}
+
 /**
  * zl3073x_dpll_register - register DPLL device and all its pins
  * @zldpll: pointer to zl3073x_dpll structure
@@ -2433,6 +2935,13 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll)
 		return rc;
 	}
 
+	rc = zl3073x_dpll_ptp_register(zldpll);
+	if (rc) {
+		zl3073x_dpll_pins_unregister(zldpll);
+		zl3073x_dpll_device_unregister(zldpll);
+		return rc;
+	}
+
 	return 0;
 }
 
@@ -2446,7 +2955,7 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll)
 void
 zl3073x_dpll_unregister(struct zl3073x_dpll *zldpll)
 {
-	/* Unregister all pins and dpll */
+	zl3073x_dpll_ptp_unregister(zldpll);
 	zl3073x_dpll_pins_unregister(zldpll);
 	zl3073x_dpll_device_unregister(zldpll);
 }
diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
index faebc402ba1b70..9b5f72c33849ce 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -5,6 +5,8 @@
 
 #include <linux/dpll.h>
 #include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/ptp_clock_kernel.h>
 
 #include "core.h"
 
@@ -22,6 +24,8 @@
  * @type: DPLL type (PPS or EEC)
  * @lock_status: last saved DPLL lock status
  * @pins: list of pins
+ * @ptp_info: PTP clock info
+ * @ptp_clock: registered PTP clock (or NULL)
  */
 struct zl3073x_dpll {
 	struct list_head		list;
@@ -36,6 +40,8 @@ struct zl3073x_dpll {
 	enum dpll_type			type;
 	enum dpll_lock_status		lock_status;
 	struct list_head		pins;
+	struct ptp_clock_info		ptp_info;
+	struct ptp_clock		*ptp_clock;
 };
 
 struct zl3073x_dpll *zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch);
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index b70ead7d4495b2..447757e99845d8 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -179,6 +179,20 @@
 #define ZL_DPLL_DF_READ_CMD			GENMASK(2, 0)
 #define ZL_DPLL_DF_READ_CMD_ACC_I		4
 
+#define ZL_REG_DPLL_TIE_CTRL			ZL_REG(5, 0x30, 1)
+#define ZL_DPLL_TIE_CTRL_OP			GENMASK(2, 0)
+#define ZL_DPLL_TIE_CTRL_OP_WR			4
+
+#define ZL_REG_DPLL_TIE_CTRL_MASK		ZL_REG(5, 0x31, 1)
+
+#define ZL_REG_DPLL_TOD_CTRL(_idx)					\
+	ZL_REG_IDX(_idx, 5, 0x38, 1, 8, 1)
+#define ZL_DPLL_TOD_CTRL_SEM			BIT(4)
+#define ZL_DPLL_TOD_CTRL_CMD			GENMASK(3, 0)
+#define ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ	1
+#define ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT	8
+#define ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ	9
+
 #define ZL_REG_DPLL_MEAS_CTRL			ZL_REG(5, 0x50, 1)
 #define ZL_DPLL_MEAS_CTRL_EN			BIT(0)
 #define ZL_DPLL_MEAS_CTRL_AVG_FACTOR		GENMASK(7, 4)
@@ -193,6 +207,9 @@
 
 /*******************************
  * Register Pages 6-7, DPLL Data
+ *
+ * Per-channel registers with stride 0x20. Channels 0-3 reside on page 6,
+ * channel 4 on page 7.
  *******************************/
 
 #define ZL_REG_DPLL_DF_OFFSET_03(_idx)					\
@@ -202,6 +219,24 @@
 	((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4)
 #define ZL_DPLL_DF_OFFSET_UNKNOWN	S64_MIN
 
+#define ZL_REG_DPLL_TIE_DATA_03(_idx)					\
+	ZL_REG_IDX(_idx, 6, 0x0C, 6, 4, 0x20)
+#define ZL_REG_DPLL_TIE_DATA_4			ZL_REG(7, 0x0C, 6)
+#define ZL_REG_DPLL_TIE_DATA(_idx)					\
+	((_idx) < 4 ? ZL_REG_DPLL_TIE_DATA_03(_idx) : ZL_REG_DPLL_TIE_DATA_4)
+
+#define ZL_REG_DPLL_TOD_SEC_03(_idx)					\
+	ZL_REG_IDX(_idx, 6, 0x12, 6, 4, 0x20)
+#define ZL_REG_DPLL_TOD_SEC_4			ZL_REG(7, 0x12, 6)
+#define ZL_REG_DPLL_TOD_SEC(_idx)					\
+	((_idx) < 4 ? ZL_REG_DPLL_TOD_SEC_03(_idx) : ZL_REG_DPLL_TOD_SEC_4)
+
+#define ZL_REG_DPLL_TOD_NS_03(_idx)					\
+	ZL_REG_IDX(_idx, 6, 0x18, 4, 4, 0x20)
+#define ZL_REG_DPLL_TOD_NS_4			ZL_REG(7, 0x18, 4)
+#define ZL_REG_DPLL_TOD_NS(_idx)					\
+	((_idx) < 4 ? ZL_REG_DPLL_TOD_NS_03(_idx) : ZL_REG_DPLL_TOD_NS_4)
+
 /***********************************
  * Register Page 9, Synth and Output
  ***********************************/
@@ -221,6 +256,8 @@
 #define ZL_OUTPUT_CTRL_EN			BIT(0)
 #define ZL_OUTPUT_CTRL_SYNTH_SEL		GENMASK(6, 4)
 
+#define ZL_REG_OUTPUT_STEP_TIME_MASK		ZL_REG(9, 0x36, 2)
+
 /*******************************
  * Register Page 10, Ref Mailbox
  *******************************/
@@ -316,6 +353,25 @@
 #define ZL_REG_OUTPUT_ESYNC_WIDTH		ZL_REG(14, 0x18, 4)
 #define ZL_REG_OUTPUT_PHASE_COMP		ZL_REG(14, 0x20, 4)
 
+/***********************************
+ * Register Page 9, Output Phase Step
+ ***********************************/
+
+#define ZL_REG_OUTPUT_PHASE_STEP_CTRL		ZL_REG(9, 0x38, 1)
+#define ZL_OUTPUT_PHASE_STEP_CTRL_DPLL		GENMASK(6, 4)
+#define ZL_OUTPUT_PHASE_STEP_CTRL_TOD_STEP	BIT(3)
+#define ZL_OUTPUT_PHASE_STEP_CTRL_OP		GENMASK(1, 0)
+#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_NONE	0
+#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_RESET	1
+#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_READ	2
+#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_WRITE	3
+
+#define ZL_REG_OUTPUT_PHASE_STEP_NUMBER		ZL_REG(9, 0x39, 1)
+
+#define ZL_REG_OUTPUT_PHASE_STEP_MASK		ZL_REG(9, 0x3a, 2)
+
+#define ZL_REG_OUTPUT_PHASE_STEP_DATA		ZL_REG(9, 0x3c, 4)
+
 /*
  * Register Page 255 - HW registers access
  */

3/2 2026/07/13 13:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20137
							output: 26
							thoughts: 582
						

Thoughts:
**Worthiness of Fuzzing the Azurite DPLL Driver**

Okay, so we have this patch touching the `drivers/dpll/zl3073x/` directory, which indicates PTP clock support is being added to the Microchip Azurite DPLL driver. This looks interesting; it involves new features and likely new code paths. The patch introduces a `PTP_1588_CLOCK_OPTIONAL` dependency, registers a PTP clock, and implements the necessary PTP clock operations – `gettimex64`, `settime64`, adjustment functions and enabling/disabling of the clock. This means it creates `/dev/ptpX` devices that can be fuzzed through ioctls, plus, the driver exposes a DPLL device via netlink. The driver uses I2C and SPI, which makes me immediately think about the difficulty of instantiation in a fuzzing context.

Can we actually *reach* this code? That's the key question. We know syzkaller can hit PTP ioctls and DPLL netlink, which is promising. But I2C/SPI devices are usually a pain. Typically, they require device tree, ACPI, or specific board code to be initialized. Can syzkaller instantiate the `zl3073x` device somehow? Does it have a mechanism like `i2c_new_client_device` or anything netlink/sysfs-related for creating an I2C or SPI client? Looking at the tooling, it seems like there is no specific `syz_i2c_setup` equivalent.

But wait, the directive we are working with is about *reachable* core kernel logic, and the critical point is a functional change where we can enable the config, right? So even if it's an I2C/SPI driver, if it's a functional modification, it's often worth fuzzing if we can enable the config option. The main caveat is virtualization. Can we reach this code path in GCE or QEMU? We need to make sure that it's *not* impossible to reach in those environments. We need to check how the `zl3073x` device is probed to see if it's reachable. We need to investigate if it depends on some specific hardware configuration that is unavailable in virtualized environments. If not, the change is WorthFuzzing=true.



4/2 2026/07/13 13:37 tool grepper
0m Args:
{
  "Expression": "zl3073x_i2c_probe"
}

Results:
{
  "Output": "drivers/dpll/zl3073x/i2c.c-10-\ndrivers/dpll/zl3073x/i2c.c:11:static int zl3073x_i2c_probe(struct i2c_client *client)\ndrivers/dpll/zl3073x/i2c.c-12-{\n--\ndrivers/dpll/zl3073x/i2c.c=48=static struct i2c_driver zl3073x_i2c_driver = {\n--\ndrivers/dpll/zl3073x/i2c.c-52-\t},\ndrivers/dpll/zl3073x/i2c.c:53:\t.probe = zl3073x_i2c_probe,\ndrivers/dpll/zl3073x/i2c.c-54-\t.id_table = zl3073x_i2c_id,\n"
}

5/2 2026/07/13 13:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21518
							output: 26
							thoughts: 104
						

Thoughts:
**My Approach to Probing the I2C Device**

Okay, let's think this through. My goal is to see if I can trigger this probe somehow. My usual starting point for these types of investigations with I2C devices is the `/sys/bus/i2c/devices/i2c-X/new_device` interface. If syzkaller has the necessary access to sysfs – which, knowing it, it likely does – then I can probably write to `new_device` to effectively instantiate the I2C device. This should give me a hook to trigger the probe and observe its behavior. Now, let's double-check if the `zl3073x` device has an I2C ID table. This information will inform my next steps.



6/2 2026/07/13 13:37 tool grepper
0m Args:
{
  "Expression": "zl3073x_i2c_id"
}

Results:
{
  "Output": "drivers/dpll/zl3073x/i2c.c=11=static int zl3073x_i2c_probe(struct i2c_client *client)\n--\ndrivers/dpll/zl3073x/i2c.c-27-\ndrivers/dpll/zl3073x/i2c.c:28:static const struct i2c_device_id zl3073x_i2c_id[] = {\ndrivers/dpll/zl3073x/i2c.c-29-\t{ .name = \"zl30731\" },\n--\ndrivers/dpll/zl3073x/i2c.c-35-};\ndrivers/dpll/zl3073x/i2c.c:36:MODULE_DEVICE_TABLE(i2c, zl3073x_i2c_id);\ndrivers/dpll/zl3073x/i2c.c-37-\n--\ndrivers/dpll/zl3073x/i2c.c=48=static struct i2c_driver zl3073x_i2c_driver = {\n--\ndrivers/dpll/zl3073x/i2c.c-53-\t.probe = zl3073x_i2c_probe,\ndrivers/dpll/zl3073x/i2c.c:54:\t.id_table = zl3073x_i2c_id,\ndrivers/dpll/zl3073x/i2c.c-55-};\n"
}

7/2 2026/07/13 13:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22232
							output: 45
							thoughts: 102
						

Thoughts:
**Analyzing the `zl30731` Device Instantiation Process**

Okay, so the device name is definitely "zl30731". Syzkaller can write "zl30731 0x20" to `/sys/bus/i2c/devices/i2c-0/new_device` to trigger its creation. Standard procedure, understood. Now, the real question is, does this instantiation *require* any specific hardware configuration to succeed? I need to be sure the probe doesn't rely on some undocumented register or external signal.

My next step is clear: I need to dive into the probe functions. Let's start with `zl3073x_i2c_probe` and `zl3073x_dev_probe`. Those should reveal the dependencies and any hardware preconditions for the `zl30731` to initialize correctly. That's where the critical logic resides. Understanding those routines will determine if this instantiation is truly clean, or if there are potential hardware dependencies that need further investigation.



8/2 2026/07/13 13:37 tool read-file
0m Args:
{
  "File": "drivers/dpll/zl3073x/i2c.c",
  "FirstLine": 11,
  "LineCount": 20
}

Results:
{
  "Contents": "  11:\tstatic int zl3073x_i2c_probe(struct i2c_client *client)\n  12:\t{\n  13:\t\tstruct device *dev = \u0026client-\u003edev;\n  14:\t\tstruct zl3073x_dev *zldev;\n  15:\t\n  16:\t\tzldev = zl3073x_devm_alloc(dev);\n  17:\t\tif (IS_ERR(zldev))\n  18:\t\t\treturn PTR_ERR(zldev);\n  19:\t\n  20:\t\tzldev-\u003eregmap = devm_regmap_init_i2c(client, \u0026zl3073x_regmap_config);\n  21:\t\tif (IS_ERR(zldev-\u003eregmap))\n  22:\t\t\treturn dev_err_probe(dev, PTR_ERR(zldev-\u003eregmap),\n  23:\t\t\t\t\t     \"Failed to initialize regmap\\n\");\n  24:\t\n  25:\t\treturn zl3073x_dev_probe(zldev);\n  26:\t}\n  27:\t\n  28:\tstatic const struct i2c_device_id zl3073x_i2c_id[] = {\n  29:\t\t{ .name = \"zl30731\" },\n  30:\t\t{ .name = \"zl30732\" },\n"
}

9/2 2026/07/13 13:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22970
							output: 25
							thoughts: 35
						

Thoughts:
Okay, here's my interpretation of that thought process, from an expert's perspective:

**Diving into the zl3073x Driver**

Right, time to dig into the `zl3073x_dev_probe` function. I need to refresh my memory on the initialization sequence for this specific DPLL, and that's the likely starting point. This is probably in the `drivers/dpll/zl3073x/core.c` file, where the core device operations are defined. I'm focusing on the probe function because that's where the driver is first setting things up when the hardware is detected. I need to understand how it's interacting with the register map and if there any obvious gotchas in the initial configuration. Hopefully, it's well-documented, but a quick scan of the code is necessary to understand the low level details, and ensure there aren't any potential issues with clock configurations, or errata handling that could bite me later on. Let's see what's what.



10/2 2026/07/13 13:37 tool grepper
0m Args:
{
  "Expression": "int zl3073x_dev_probe"
}

Results:
{
  "Output": "drivers/dpll/zl3073x/core.c=925=zl3073x_devm_dpll_init(struct zl3073x_dev *zldev)\n--\ndrivers/dpll/zl3073x/core.c-979- */\ndrivers/dpll/zl3073x/core.c:980:int zl3073x_dev_probe(struct zl3073x_dev *zldev)\ndrivers/dpll/zl3073x/core.c-981-{\n--\ndrivers/dpll/zl3073x/core.h=104=struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev);\ndrivers/dpll/zl3073x/core.h:105:int zl3073x_dev_probe(struct zl3073x_dev *zldev);\ndrivers/dpll/zl3073x/core.h-106-\n"
}

11/2 2026/07/13 13:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 23533
							output: 45
							thoughts: 16
						

Thoughts:
Okay, let's dive into this. Here's my take on `zl3073x_dev_probe`, as I understand it:

**Analyzing the `zl3073x_dev_probe` Function**

Alright, so the first thing I see is we're looking at `zl3073x_dev_probe`. That means we're in the realm of device probing, and it's specifically for a `zl3073x` device, which I'm already familiar with the general architecture of. I'm expecting this is where the system attempts to identify, initialize, and register a new `zl3073x` device that's been plugged in or is otherwise becoming available. I'll be looking for things like:

*   **Resource allocation**: How are they getting the necessary resources, like memory regions, IRQs, I2C or SPI buses, etc.? I want to see if they are using any platform-specific mechanisms or if there's a more generic approach. Any unusual approaches here would be interesting.
*   **Device identification**: How is the driver verifying the device is *actually* a `zl3073x`? Are they checking a device ID register? Vendor-specific magic? I'm keen to see the approach and any potential for robustness issues.
*   **Initialization sequence**: What steps are taken to get the device ready for use? This could include power-up sequences, register writes, clock configuration, and possibly firmware loading. I'll be looking at the order of operations, paying attention to any dependencies and potential timing constraints.
*   **Driver registration**: Once the device is ready, I'll be watching to see how the driver registers itself with the kernel. Is it using the standard device driver model? Are there any custom callbacks or structures? Any custom data structures or interactions will be of interest.
*   **Error handling**: Where are the error checks? What happens if any of the critical steps fail? I want to assess the robustness of the probe function and see if there are any obvious weaknesses. I'm expecting thorough error handling, especially when interacting with hardware.
*   **Concurrency**: I'll also glance to see if anything here is susceptible to concurrency issues as I know hardware probing can often have shared resources.

Essentially, I'm expecting this function to be a gateway. It should be making sure that the device is actually the device we're expecting, and then getting it ready to be used by the rest of the system. Let's see how they've done it.



12/2 2026/07/13 13:37 tool read-file
0m Args:
{
  "File": "drivers/dpll/zl3073x/core.c",
  "FirstLine": 980,
  "LineCount": 100
}

Results:
{
  "Contents": " 980:\tint zl3073x_dev_probe(struct zl3073x_dev *zldev)\n 981:\t{\n 982:\t\tu16 id, revision, fw_ver;\n 983:\t\tunsigned int i;\n 984:\t\tu32 cfg_ver;\n 985:\t\tint rc;\n 986:\t\n 987:\t\t/* Read chip ID */\n 988:\t\trc = zl3073x_read_u16(zldev, ZL_REG_ID, \u0026id);\n 989:\t\tif (rc)\n 990:\t\t\treturn rc;\n 991:\t\n 992:\t\t/* Detect chip variant */\n 993:\t\tfor (i = 0; i \u003c ARRAY_SIZE(zl3073x_chip_ids); i++) {\n 994:\t\t\tif (zl3073x_chip_ids[i].id == id)\n 995:\t\t\t\tbreak;\n 996:\t\t}\n 997:\t\n 998:\t\tif (i == ARRAY_SIZE(zl3073x_chip_ids))\n 999:\t\t\treturn dev_err_probe(zldev-\u003edev, -ENODEV,\n1000:\t\t\t\t\t     \"Unknown chip ID: 0x%04x\\n\", id);\n1001:\t\n1002:\t\tzldev-\u003einfo = \u0026zl3073x_chip_ids[i];\n1003:\t\n1004:\t\t/* Read revision, firmware version and custom config version */\n1005:\t\trc = zl3073x_read_u16(zldev, ZL_REG_REVISION, \u0026revision);\n1006:\t\tif (rc)\n1007:\t\t\treturn rc;\n1008:\t\trc = zl3073x_read_u16(zldev, ZL_REG_FW_VER, \u0026fw_ver);\n1009:\t\tif (rc)\n1010:\t\t\treturn rc;\n1011:\t\trc = zl3073x_read_u32(zldev, ZL_REG_CUSTOM_CONFIG_VER, \u0026cfg_ver);\n1012:\t\tif (rc)\n1013:\t\t\treturn rc;\n1014:\t\n1015:\t\tdev_dbg(zldev-\u003edev, \"ChipID(%X), ChipRev(%X), FwVer(%u)\\n\", id,\n1016:\t\t\trevision, fw_ver);\n1017:\t\tdev_dbg(zldev-\u003edev, \"Custom config version: %lu.%lu.%lu.%lu\\n\",\n1018:\t\t\tFIELD_GET(GENMASK(31, 24), cfg_ver),\n1019:\t\t\tFIELD_GET(GENMASK(23, 16), cfg_ver),\n1020:\t\t\tFIELD_GET(GENMASK(15, 8), cfg_ver),\n1021:\t\t\tFIELD_GET(GENMASK(7, 0), cfg_ver));\n1022:\t\n1023:\t\t/* Generate random clock ID as the device has not such property that\n1024:\t\t * could be used for this purpose. A user can later change this value\n1025:\t\t * using devlink.\n1026:\t\t */\n1027:\t\tzldev-\u003eclock_id = get_random_u64();\n1028:\t\n1029:\t\t/* Default phase offset averaging factor */\n1030:\t\tzldev-\u003ephase_avg_factor = 2;\n1031:\t\n1032:\t\t/* Initialize mutex for operations where multiple reads, writes\n1033:\t\t * and/or polls are required to be done atomically.\n1034:\t\t */\n1035:\t\trc = devm_mutex_init(zldev-\u003edev, \u0026zldev-\u003emultiop_lock);\n1036:\t\tif (rc)\n1037:\t\t\treturn dev_err_probe(zldev-\u003edev, rc,\n1038:\t\t\t\t\t     \"Failed to initialize mutex\\n\");\n1039:\t\trc = devm_mutex_init(zldev-\u003edev, \u0026zldev-\u003ephase_step_lock);\n1040:\t\tif (rc)\n1041:\t\t\treturn dev_err_probe(zldev-\u003edev, rc,\n1042:\t\t\t\t\t     \"Failed to initialize mutex\\n\");\n1043:\t\trc = devm_mutex_init(zldev-\u003edev, \u0026zldev-\u003etie_lock);\n1044:\t\tif (rc)\n1045:\t\t\treturn dev_err_probe(zldev-\u003edev, rc,\n1046:\t\t\t\t\t     \"Failed to initialize mutex\\n\");\n1047:\t\n1048:\t\t/* Register DPLL channels */\n1049:\t\trc = zl3073x_devm_dpll_init(zldev);\n1050:\t\tif (rc)\n1051:\t\t\treturn rc;\n1052:\t\n1053:\t\t/* Register the devlink instance and parameters */\n1054:\t\trc = zl3073x_devlink_register(zldev);\n1055:\t\tif (rc)\n1056:\t\t\treturn dev_err_probe(zldev-\u003edev, rc,\n1057:\t\t\t\t\t     \"Failed to register devlink instance\\n\");\n1058:\t\n1059:\t\treturn 0;\n1060:\t}\n1061:\tEXPORT_SYMBOL_NS_GPL(zl3073x_dev_probe, \"ZL3073X\");\n1062:\t\n1063:\tMODULE_AUTHOR(\"Ivan Vecera \u003civecera@redhat.com\u003e\");\n1064:\tMODULE_DESCRIPTION(\"Microchip ZL3073x core driver\");\n1065:\tMODULE_LICENSE(\"GPL\");\n"
}

13/2 2026/07/13 13:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25657
							output: 27
							thoughts: 413
						

Thoughts:
**Analyzing the Device Probe in a Virtualized Environment**

Okay, so I'm looking at this probe function and it's grabbing the `ZL_REG_ID` and comparing it to the known `zl3073x_chip_ids`. The problem, as I see it, is that in a virtualized environment like the one syzkaller uses, these I2C reads are highly likely to fail or return garbage. That means a `-ENODEV` error, and the probe just dies.

Wait a second, doesn’t syzkaller have *some* mechanism to mock I2C devices? I know it mocks other hardware interfaces. No, it doesn't appear to have a generic I2C mock, directly. However, the comments in the code are pointing me towards unreachable code paths in virtualized environments. The code is specifically calling out software-emulated hardware.

So the probe *will* fail because it's expecting a specific chip ID to come back from the I2C bus. But, hold on a minute! Syzkaller has all these different simulators – `vhci`, `mac80211_hwsim`, `vkms`, `vcan`, `uinput`, `snd-dummy`, `netdevsim`, and more. Do they have an I2C equivalent?

Aha! There *is* `i2c-stub`! I can use it to mock I2C devices! Let's see if this `i2c-stub` can actually simulate this specific device.

Okay, so `i2c-stub` lets you specify chip addresses and it’ll act as a dummy I2C device that stores and returns whatever bytes are written to it. But… wait a second… I need to check how `zl3073x` interacts with the registers, because if it uses `regmap` it could be reading 16-bit or 32-bit registers. I need to check `zl3073x_read_u16`.



14/2 2026/07/13 13:37 tool grepper
0m Args:
{
  "Expression": "int zl3073x_read_u16"
}

Results:
{
  "Output": "drivers/dpll/zl3073x/core.c=178=int zl3073x_write_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 val)\n--\ndrivers/dpll/zl3073x/core.c-192- */\ndrivers/dpll/zl3073x/core.c:193:int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val)\ndrivers/dpll/zl3073x/core.c-194-{\n--\ndrivers/dpll/zl3073x/core.h=147=int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val);\ndrivers/dpll/zl3073x/core.h:148:int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val);\ndrivers/dpll/zl3073x/core.h-149-int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val);\n"
}

15/2 2026/07/13 13:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 26886
							output: 44
							thoughts: 22
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Analyzing the zl3073x_read_u16 Implementation**

Okay, let's dive into the `zl3073x_read_u16` function. I need to understand its low-level behavior. Specifically, I want to dissect the critical path; understand how the function is using the hardware. I know these devices pretty well, so I'm focusing on the interaction with the registers and the underlying bus communication protocols – probably I2C, SPI, or potentially some proprietary bus. The key is in figuring out how the 16-bit value is being fetched. Are they using a read transaction with a register address? What are the implications in terms of data sheet latency and timing requirements? Are there any potential issues with atomicity or concurrency? I'll probably start by looking at how the address is translated to an I2C or SPI write. Then follow the transaction to the read side to confirm. Are there any error handling routines? Those are important, too, for understanding robustness. Ultimately, I'm expecting to confirm whether the function performs a simple read of a 16-bit value from a specific address or if it's more complex than that. Are there any hardware or bus constraints?



16/2 2026/07/13 13:38 tool read-file
0m Args:
{
  "File": "drivers/dpll/zl3073x/core.c",
  "FirstLine": 193,
  "LineCount": 20
}

Results:
{
  "Contents": " 193:\tint zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val)\n 194:\t{\n 195:\t\tint rc;\n 196:\t\n 197:\t\trc = zl3073x_read_reg(zldev, reg, val, sizeof(*val));\n 198:\t\tif (!rc)\n 199:\t\t\tbe16_to_cpus(val);\n 200:\t\n 201:\t\treturn rc;\n 202:\t}\n 203:\t\n 204:\t/**\n 205:\t * zl3073x_write_u16 - write value to 16bit register\n 206:\t * @zldev: zl3073x device pointer\n 207:\t * @reg: register to write to\n 208:\t * @val: value to write\n 209:\t *\n 210:\t * Writes value into given 16bit register.\n 211:\t *\n 212:\t * Returns: 0 on success, \u003c0 on error\n"
}

17/2 2026/07/13 13:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 27545
							output: 44
							thoughts: 14
						

Thoughts:
Okay, here's my summary, taking on the persona you've requested, speaking as if these are my own thoughts:

**Analyzing the Code - zl3073x_read_reg**

Alright, let's see, the code's using `zl3073x_read_reg`. Right away, that tells me we're dealing with interacting with a ZL3073x device, likely a timing chip or similar, given the naming convention. The function's name, `_read_reg`, strongly suggests we're reading a register from this specific hardware component. My initial thought is to quickly verify the register map of the ZL3073x. I'll need that to understand *what* data is being requested and why. Knowing the specific register addresses will be key to understanding the code's intent. Is it reading status flags? Configuration values? Or perhaps diagnostic information? The context of where this `zl3073x_read_reg` call appears in the larger code structure is vital to understand the bigger picture. Are we initializing the device, polling for an event, or responding to an interrupt? Now I'm going to look for any comments.



18/2 2026/07/13 13:38 tool read-file
0m Args:
{
  "File": "drivers/dpll/zl3073x/core.c",
  "FirstLine": 140,
  "LineCount": 20
}

Results:
{
  "Contents": " 140:\t\t/* Map the register address to virtual range */\n 141:\t\treg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;\n 142:\t\n 143:\t\trc = regmap_bulk_write(zldev-\u003eregmap, reg, val, size);\n 144:\t\tif (rc) {\n 145:\t\t\tdev_err(zldev-\u003edev, \"Failed to write reg 0x%04x: %pe\\n\", reg,\n 146:\t\t\t\tERR_PTR(rc));\n 147:\t\t\treturn rc;\n 148:\t\t}\n 149:\t\n 150:\t\treturn 0;\n 151:\t}\n 152:\t\n 153:\t/**\n 154:\t * zl3073x_read_u8 - read value from 8bit register\n 155:\t * @zldev: zl3073x device pointer\n 156:\t * @reg: register to write to\n 157:\t * @val: value to write\n 158:\t *\n 159:\t * Reads value from given 8bit register.\n"
}

19/2 2026/07/13 13:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28174
							output: 44
							thoughts: 17
						

Thoughts:
Okay, here's my attempt at summarizing that thought process, tailored for an expert audience:

**Analyzing the Code Flow**

Hmm, I'm tracing through this logic, and I see a call to `zl3073x_read_reg`. Okay, now I've noticed it's positioned above the current line I'm examining. I'm taking a moment to register that, and how that preceding function relates to the subsequent steps. This placement has implications for data dependencies or perhaps register initialization. I'll need to remember that relative ordering as I examine subsequent logic, and understand how values read from registers by `zl3073x_read_reg` are then used. It's a key observation that directs my analysis.



20/2 2026/07/13 13:38 tool read-file
0m Args:
{
  "File": "drivers/dpll/zl3073x/core.c",
  "FirstLine": 100,
  "LineCount": 40
}

Results:
{
  "Contents": " 100:\t\tif (ZL_REG_SIZE(reg) != size) {\n 101:\t\t\tdev_err(zldev-\u003edev, \"Invalid size %zu for reg 0x%04lx\\n\",\n 102:\t\t\t\tsize, ZL_REG_ADDR(reg));\n 103:\t\t\treturn false;\n 104:\t\t}\n 105:\t\n 106:\t\treturn true;\n 107:\t}\n 108:\t\n 109:\tstatic int\n 110:\tzl3073x_read_reg(struct zl3073x_dev *zldev, unsigned int reg, void *val,\n 111:\t\t\t size_t size)\n 112:\t{\n 113:\t\tint rc;\n 114:\t\n 115:\t\tif (!zl3073x_check_reg(zldev, reg, size))\n 116:\t\t\treturn -EINVAL;\n 117:\t\n 118:\t\t/* Map the register address to virtual range */\n 119:\t\treg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;\n 120:\t\n 121:\t\trc = regmap_bulk_read(zldev-\u003eregmap, reg, val, size);\n 122:\t\tif (rc) {\n 123:\t\t\tdev_err(zldev-\u003edev, \"Failed to read reg 0x%04x: %pe\\n\", reg,\n 124:\t\t\t\tERR_PTR(rc));\n 125:\t\t\treturn rc;\n 126:\t\t}\n 127:\t\n 128:\t\treturn 0;\n 129:\t}\n 130:\t\n 131:\tstatic int\n 132:\tzl3073x_write_reg(struct zl3073x_dev *zldev, unsigned int reg, const void *val,\n 133:\t\t\t  size_t size)\n 134:\t{\n 135:\t\tint rc;\n 136:\t\n 137:\t\tif (!zl3073x_check_reg(zldev, reg, size))\n 138:\t\t\treturn -EINVAL;\n 139:\t\n"
}

21/2 2026/07/13 13:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28989
							output: 24
							thoughts: 50
						

Thoughts:
**Analyzing the Probe Function**

Okay, so I'm diving into this probe function, and it's looking for `ZL_REG_ID`. Naturally, the expectation is that this value should correspond to something defined within `zl3073x_chip_ids`. My mind's now running through the possible chip IDs. Let me just quickly check what this `zl3073x_chip_ids` actually contains. Time to pull up the definition and see what we're working with. Understanding those IDs will be key to understanding the expected hardware this probe is designed to work with.



22/2 2026/07/13 13:38 tool grepper
0m Args:
{
  "Expression": "zl3073x_chip_ids"
}

Results:
{
  "Output": "drivers/dpll/zl3073x/core.c-25-\ndrivers/dpll/zl3073x/core.c:26:static const struct zl3073x_chip_info zl3073x_chip_ids[] = {\ndrivers/dpll/zl3073x/core.c-27-\tZL_CHIP_INFO(0x0E30, 2, ZL3073X_FLAG_REF_PHASE_COMP_32),\n--\ndrivers/dpll/zl3073x/core.c=980=int zl3073x_dev_probe(struct zl3073x_dev *zldev)\n--\ndrivers/dpll/zl3073x/core.c-992-\t/* Detect chip variant */\ndrivers/dpll/zl3073x/core.c:993:\tfor (i = 0; i \u003c ARRAY_SIZE(zl3073x_chip_ids); i++) {\ndrivers/dpll/zl3073x/core.c:994:\t\tif (zl3073x_chip_ids[i].id == id)\ndrivers/dpll/zl3073x/core.c-995-\t\t\tbreak;\n--\ndrivers/dpll/zl3073x/core.c-997-\ndrivers/dpll/zl3073x/core.c:998:\tif (i == ARRAY_SIZE(zl3073x_chip_ids))\ndrivers/dpll/zl3073x/core.c-999-\t\treturn dev_err_probe(zldev-\u003edev, -ENODEV,\n--\ndrivers/dpll/zl3073x/core.c-1001-\ndrivers/dpll/zl3073x/core.c:1002:\tzldev-\u003einfo = \u0026zl3073x_chip_ids[i];\ndrivers/dpll/zl3073x/core.c-1003-\n"
}

23/2 2026/07/13 13:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29725
							output: 29
							thoughts: 882
						

Thoughts:
**Fuzzing the zl3073x Driver: A Syzkaller Challenge**

Okay, so I'm thinking about fuzzing the `zl3073x` driver, and I need to figure out how to simulate its hardware dependencies within Syzkaller. The chip ID, let's say `0x0E30`, is the key. Can I get Syzkaller to mock this I2C interaction?

The immediate problem is that Syzkaller doesn't have a built-in, fine-grained I2C mock that can return specific values for specific registers. I know it *does* have some basic `/dev/i2c-X` fuzzing, but that's really for userspace to *talk* to I2C devices, not for *emulating* the devices themselves. If I try to trigger the device instantiation through sysfs's `new_device`, the kernel probe will run I2C reads, and without a real device, those reads will fail. So I need a way to make those reads return what I want. Ideally, there'd be a generic software I2C adapter under Syzkaller's control.

Then I recalled `i2c-stub`. That's a kernel module that makes fake I2C adapter and devices. Let me check if Syzkaller enables it. Usually, `CONFIG_I2C_STUB` is enabled. But I realized `i2c-stub` only supports SMBus commands and `zl3073x` uses regmap over I2C. And, if `i2c-stub` is used, could I load it with `chip_addr=0x20` and then program the registers via `/sys/bus/i2c/devices/i2c-X/0-0020/`? No, `i2c-stub` doesn't expose registers via sysfs. Ah, but wait a second.

Syzkaller *can* write to `/dev/i2c-X`! I could use `/dev/i2c-X` to program the `i2c-stub` registers. And, if userspace writes to `/dev/i2c-X` (the fake adapter), it can write to the fake device's registers. Then, if userspace instantiates `zl3073x` on the same fake adapter at the same address, the `zl3073x` driver *should* read the registers I just programmed. Brilliant!

Now, does Syzkaller *actually* do this? I'm not familiar with any specific examples, and I can't think of anything like `syz_i2c_setup`. I'll have to check the Syzkaller source. Hmm, I just remembered, Syzkaller does have `syz_usb_connect`, `syz_80211_join_ibss`, and so on, but no `syz_i2c_setup`. Let me just assume it doesn't do it directly.

Okay, are there *other* paths to reach this code? What about SPI? The driver has a `zl3073x_spi_probe` function. I am not familiar with Syzkaller's SPI capabilities but I remember it doesn't have a SPI mock either.

Now, I have a bigger problem. I'm operating in a virtualized environment. Are these kinds of I2C/SPI drivers even reachable in a virtualized context? "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)." It's generally impossible to successfully reach the probe function without a real-ish hardware setup or a really clever emulator. Probes always check chip IDs.

I guess I should check one last thing: is there any existing test mock for `zl3073x`? If not, I'm probably looking at a non-trivial amount of work to get this fuzzed.



24/2 2026/07/13 13:38 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "zl3073x",
  "PathPrefix": "tools/testing"
}

Results:
{
  "error": "no matches"
}

25/2 2026/07/13 13:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 31531
							output: 30
							thoughts: 27
						

Thoughts:
Okay, let's see. Here's how I'd approach that line of thought, thinking as a seasoned engineer:

**Checking for Test Mocks in the DPLL Driver Directory**

Alright, before I dive into any heavy-duty debugging or feature implementation within that `drivers/dpll/zl3073x/` directory, I always start with a sanity check. Let's see if we've been diligent with our testing strategy. Specifically, I need to know if there's a test mock available. Having a mock for that DPLL module – the ZL3073x – is absolutely crucial for efficient development and regression testing. It allows for controlled experimentation and isolation of the module from potentially complex hardware interactions or dependencies. Finding a good mock will save a lot of time and effort down the line. It avoids the need to repeatedly boot the system in various configurations just to test a small code change. Plus, it's essential for creating stable unit tests. If there isn't a mock, then it's time to create one.



26/2 2026/07/13 13:38 tool codesearch-dir-index
0m Args:
{
  "Dir": "drivers/dpll/zl3073x"
}

Results:
{
  "Files": [
    "chan.c",
    "chan.h",
    "core.c",
    "core.h",
    "devlink.c",
    "devlink.h",
    "dpll.c",
    "dpll.h",
    "flash.c",
    "flash.h",
    "fw.c",
    "fw.h",
    "i2c.c",
    "out.c",
    "out.h",
    "prop.c",
    "prop.h",
    "ref.c",
    "ref.h",
    "regs.h",
    "spi.c",
    "synth.c",
    "synth.h"
  ],
  "Subdirs": null
}

27/2 2026/07/13 13:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 31971
							output: 28
							thoughts: 1528
						

Thoughts:
**Reaching the Target Code: A Fuzzing Feasibility Analysis**

Okay, so I'm looking at this `zl3073x` driver and trying to figure out if it's fuzzable with syzkaller. My initial thought is, "No test mocks." Since syzkaller runs in environments without the specific hardware, this `zl3073x` device's probe function will almost certainly fail. It explicitly checks the chip ID, and if that doesn't match, it returns `-ENODEV`. I quickly dismissed the bypass, as the code directly prevents that.

Then, I started thinking about `i2c-stub`, could syzkaller leverage that to emulate the device? But, wait, `i2c-stub` only supports 8-bit register addresses, whereas `zl3073x` uses 16-bit registers. My immediate conclusion, then, was that it was impossible. No other I2C mocks are available, not even an `i2c-mock`. The same goes for SPI – no `spi-mock` exists.

So, the probe fails, making the driver unreachable in a virtualized setup. The target code lies within functions like `zl3073x_chan_tod_read`, `zl3073x_chan_tod_write`, etc., all of which are PTP clock operations. These are only registered if `zl3073x_dpll_register` succeeds, which is called from `zl3073x_dev_probe`. Since that probe will fail, those functions will never get executed. Thus, this code path, as far as I can see, is not reachable through standard means.

Now, let me double-check. Syzkaller usually has options, right? Is there anything that could be configured to emulate I2C? I looked for any similar configurations, but, no, it relies on strict chip ID validation. Wait, I had another thought: what about `vhci` for USB-I2C adapter! I can use `syz_usb_connect` to fake an `i2c-tiny-usb` device. That way, syzkaller could intercept the I2C requests and return the expected chip ID when requested.

Yes! I can see that `syz_usb_connect` has that capability! The kernel would then request an I2C read, the USB-I2C adapter would process that, and syzkaller could provide the right chip ID `0x0E30` in its response! The probability of syzkaller generating the correct 16-bit chip ID randomly is, of course, very low: 1/65536. But, wait, syzkaller uses coverage-guided fuzzing, so it *could* detect the comparison and eventually find the chip ID!

So, the I2C read goes through `regmap`, then `i2c`, then USB to syzkaller. Syzkaller's USB fuzzer receives a stream of bytes for USB endpoints. If I can provide `0x0E30`, the probe could continue. The driver would then read `REVISION`, `FW_VER`, `CUSTOM_CONFIG_VER`. It would then register DPLL channels.

Is this practically achievable, though? Usually, these sorts of drivers are considered hard to reach because they depend on hardware interactions. But, if it's a functional change in a driver, we still want to fuzz it. The instructions state clearly to exclude code paths that are impossible to reach in virtualized environments. But is this really impossible?

Syzkaller can create an I2C bus via `i2c-tiny-usb` or `i2c-stub` or `i2c-gpio`. But syzkaller doesn't have an I2C device fuzzer for automatically instantiating all I2C drivers and mocking their registers. Syzkaller has descriptions for `i2c_dev`, but nothing for mocking I2C devices. Ultimately, I need to check if there is an existing syzkaller description for `zl3073x`. That's where I need to start.



28/2 2026/07/13 13:39 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "zl3073x",
  "PathPrefix": "net/"
}

Results:
{
  "error": "no matches"
}

29/2 2026/07/13 13:39 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 34436
							output: 28
							thoughts: 18
						

Thoughts:
Okay, here's my summary, presented as a stream of thought:

**Checking for Existing Syzkaller Coverage**

Alright, before I dive too deep, let's just do a quick scan. My initial thought is, "Do we already have some Syzkaller coverage for this `dpll` thing?" The goal here is efficiency; there's no sense in reinventing the wheel if someone's already done the groundwork. This is a common starting point for me - quickly verifying if existing fuzzing infrastructure handles a target. So, let's check for any existing syzkaller descriptions. It's the most likely tool to have coverage.



30/2 2026/07/13 13:39 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "dpll",
  "PathPrefix": "net/dpll"
}

Results:
{
  "error": "no matches"
}

31/2 2026/07/13 13:39 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 34634
							output: 28
							thoughts: 16
						

Thoughts:
Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to a fellow expert:

**Navigating the Codebase: DPLL Location**

Hmm, let's see... *dpll*... where's that hiding? Right, I should be familiar with this code base. Okay, wait a minute, I remember now. That module, that specific instance of DPLL, I'm pretty sure it's residing in `drivers/dpll`. Yes, that location makes sense given its function within the wider system.



32/2 2026/07/13 13:39 tool grepper
0m Args:
{
  "Expression": "dpll",
  "PathPrefix": "drivers/dpll"
}

Results:
{
  "Output": "\nFull output is too long, showing 500 out of 4647 lines.\nUse more precise expression if possible.\n\ndrivers/dpll/Kconfig=11=config DPLL_REFCNT_TRACKER\n--\ndrivers/dpll/Kconfig-20-\t  The tracking information is exposed via debugfs at:\ndrivers/dpll/Kconfig:21:\t    /sys/kernel/debug/ref_tracker/dpll_device_*\ndrivers/dpll/Kconfig:22:\t    /sys/kernel/debug/ref_tracker/dpll_pin_*\ndrivers/dpll/Kconfig-23-\n--\ndrivers/dpll/Kconfig-25-\ndrivers/dpll/Kconfig:26:source \"drivers/dpll/zl3073x/Kconfig\"\ndrivers/dpll/Kconfig-27-\n--\ndrivers/dpll/Makefile-5-\ndrivers/dpll/Makefile:6:obj-$(CONFIG_DPLL)      += dpll.o\ndrivers/dpll/Makefile:7:dpll-y                  += dpll_core.o\ndrivers/dpll/Makefile:8:dpll-y                  += dpll_netlink.o\ndrivers/dpll/Makefile:9:dpll-y                  += dpll_nl.o\ndrivers/dpll/Makefile-10-\n--\ndrivers/dpll/dpll_core.c-2-/*\ndrivers/dpll/dpll_core.c:3: *  dpll_core.c - DPLL subsystem kernel-space interface implementation.\ndrivers/dpll/dpll_core.c-4- *\n--\ndrivers/dpll/dpll_core.c-18-\ndrivers/dpll/dpll_core.c:19:#include \"dpll_core.h\"\ndrivers/dpll/dpll_core.c:20:#include \"dpll_netlink.h\"\ndrivers/dpll/dpll_core.c-21-\ndrivers/dpll/dpll_core.c-22-/* Mutex lock to protect DPLL subsystem devices and pins */\ndrivers/dpll/dpll_core.c:23:DEFINE_MUTEX(dpll_lock);\ndrivers/dpll/dpll_core.c-24-\ndrivers/dpll/dpll_core.c:25:DEFINE_XARRAY_FLAGS(dpll_device_xa, XA_FLAGS_ALLOC);\ndrivers/dpll/dpll_core.c:26:DEFINE_XARRAY_FLAGS(dpll_pin_xa, XA_FLAGS_ALLOC);\ndrivers/dpll/dpll_core.c-27-\ndrivers/dpll/dpll_core.c:28:static RAW_NOTIFIER_HEAD(dpll_notifier_chain);\ndrivers/dpll/dpll_core.c:29:static DEFINE_IDA(dpll_pin_idx_ida);\ndrivers/dpll/dpll_core.c-30-\ndrivers/dpll/dpll_core.c:31:static u32 dpll_device_xa_id;\ndrivers/dpll/dpll_core.c:32:static u32 dpll_pin_xa_id;\ndrivers/dpll/dpll_core.c-33-\ndrivers/dpll/dpll_core.c-34-#define ASSERT_DPLL_REGISTERED(d)\t\\\ndrivers/dpll/dpll_core.c:35:\tWARN_ON_ONCE(!xa_get_mark(\u0026dpll_device_xa, (d)-\u003eid, DPLL_REGISTERED))\ndrivers/dpll/dpll_core.c-36-#define ASSERT_DPLL_NOT_REGISTERED(d)\t\\\ndrivers/dpll/dpll_core.c:37:\tWARN_ON_ONCE(xa_get_mark(\u0026dpll_device_xa, (d)-\u003eid, DPLL_REGISTERED))\ndrivers/dpll/dpll_core.c-38-#define ASSERT_DPLL_PIN_REGISTERED(p) \\\ndrivers/dpll/dpll_core.c:39:\tWARN_ON_ONCE(!xa_get_mark(\u0026dpll_pin_xa, (p)-\u003eid, DPLL_REGISTERED))\ndrivers/dpll/dpll_core.c-40-\ndrivers/dpll/dpll_core.c:41:struct dpll_device_registration {\ndrivers/dpll/dpll_core.c-42-\tstruct list_head list;\ndrivers/dpll/dpll_core.c:43:\tconst struct dpll_device_ops *ops;\ndrivers/dpll/dpll_core.c-44-\tvoid *priv;\ndrivers/dpll/dpll_core.c:45:\tdpll_tracker tracker;\ndrivers/dpll/dpll_core.c-46-};\ndrivers/dpll/dpll_core.c-47-\ndrivers/dpll/dpll_core.c:48:struct dpll_pin_registration {\ndrivers/dpll/dpll_core.c-49-\tstruct list_head list;\ndrivers/dpll/dpll_core.c:50:\tconst struct dpll_pin_ops *ops;\ndrivers/dpll/dpll_core.c-51-\tvoid *priv;\ndrivers/dpll/dpll_core.c-52-\tvoid *cookie;\ndrivers/dpll/dpll_core.c:53:\tdpll_tracker tracker;\ndrivers/dpll/dpll_core.c-54-};\ndrivers/dpll/dpll_core.c-55-\ndrivers/dpll/dpll_core.c:56:static int call_dpll_notifiers(unsigned long action, void *info)\ndrivers/dpll/dpll_core.c-57-{\ndrivers/dpll/dpll_core.c:58:\tlockdep_assert_held(\u0026dpll_lock);\ndrivers/dpll/dpll_core.c:59:\treturn raw_notifier_call_chain(\u0026dpll_notifier_chain, action, info);\ndrivers/dpll/dpll_core.c-60-}\ndrivers/dpll/dpll_core.c-61-\ndrivers/dpll/dpll_core.c:62:void dpll_device_notify(struct dpll_device *dpll, unsigned long action)\ndrivers/dpll/dpll_core.c-63-{\ndrivers/dpll/dpll_core.c:64:\tstruct dpll_device_notifier_info info = {\ndrivers/dpll/dpll_core.c:65:\t\t.dpll = dpll,\ndrivers/dpll/dpll_core.c:66:\t\t.id = dpll-\u003eid,\ndrivers/dpll/dpll_core.c:67:\t\t.idx = dpll-\u003edevice_idx,\ndrivers/dpll/dpll_core.c:68:\t\t.clock_id = dpll-\u003eclock_id,\ndrivers/dpll/dpll_core.c:69:\t\t.type = dpll-\u003etype,\ndrivers/dpll/dpll_core.c-70-\t};\ndrivers/dpll/dpll_core.c-71-\ndrivers/dpll/dpll_core.c:72:\tcall_dpll_notifiers(action, \u0026info);\ndrivers/dpll/dpll_core.c-73-}\ndrivers/dpll/dpll_core.c-74-\ndrivers/dpll/dpll_core.c:75:void dpll_pin_notify(struct dpll_pin *pin, u64 src_clock_id,\ndrivers/dpll/dpll_core.c-76-\t\t     unsigned long action)\ndrivers/dpll/dpll_core.c-77-{\ndrivers/dpll/dpll_core.c:78:\tstruct dpll_pin_notifier_info info = {\ndrivers/dpll/dpll_core.c-79-\t\t.pin = pin,\n--\ndrivers/dpll/dpll_core.c-87-\ndrivers/dpll/dpll_core.c:88:\tcall_dpll_notifiers(action, \u0026info);\ndrivers/dpll/dpll_core.c-89-}\ndrivers/dpll/dpll_core.c-90-\ndrivers/dpll/dpll_core.c:91:static void dpll_device_tracker_alloc(struct dpll_device *dpll,\ndrivers/dpll/dpll_core.c:92:\t\t\t\t      dpll_tracker *tracker)\ndrivers/dpll/dpll_core.c-93-{\ndrivers/dpll/dpll_core.c-94-#ifdef CONFIG_DPLL_REFCNT_TRACKER\ndrivers/dpll/dpll_core.c:95:\tref_tracker_alloc(\u0026dpll-\u003erefcnt_tracker, tracker, GFP_KERNEL);\ndrivers/dpll/dpll_core.c-96-#endif\n--\ndrivers/dpll/dpll_core.c-98-\ndrivers/dpll/dpll_core.c:99:static void dpll_device_tracker_free(struct dpll_device *dpll,\ndrivers/dpll/dpll_core.c:100:\t\t\t\t     dpll_tracker *tracker)\ndrivers/dpll/dpll_core.c-101-{\ndrivers/dpll/dpll_core.c-102-#ifdef CONFIG_DPLL_REFCNT_TRACKER\ndrivers/dpll/dpll_core.c:103:\tref_tracker_free(\u0026dpll-\u003erefcnt_tracker, tracker);\ndrivers/dpll/dpll_core.c-104-#endif\n--\ndrivers/dpll/dpll_core.c-106-\ndrivers/dpll/dpll_core.c:107:static void __dpll_device_hold(struct dpll_device *dpll, dpll_tracker *tracker)\ndrivers/dpll/dpll_core.c-108-{\ndrivers/dpll/dpll_core.c:109:\tdpll_device_tracker_alloc(dpll, tracker);\ndrivers/dpll/dpll_core.c:110:\trefcount_inc(\u0026dpll-\u003erefcount);\ndrivers/dpll/dpll_core.c-111-}\ndrivers/dpll/dpll_core.c-112-\ndrivers/dpll/dpll_core.c:113:static void __dpll_device_put(struct dpll_device *dpll, dpll_tracker *tracker)\ndrivers/dpll/dpll_core.c-114-{\ndrivers/dpll/dpll_core.c:115:\tdpll_device_tracker_free(dpll, tracker);\ndrivers/dpll/dpll_core.c:116:\tif (refcount_dec_and_test(\u0026dpll-\u003erefcount)) {\ndrivers/dpll/dpll_core.c:117:\t\tASSERT_DPLL_NOT_REGISTERED(dpll);\ndrivers/dpll/dpll_core.c:118:\t\tWARN_ON_ONCE(!xa_empty(\u0026dpll-\u003epin_refs));\ndrivers/dpll/dpll_core.c:119:\t\txa_destroy(\u0026dpll-\u003epin_refs);\ndrivers/dpll/dpll_core.c:120:\t\txa_erase(\u0026dpll_device_xa, dpll-\u003eid);\ndrivers/dpll/dpll_core.c:121:\t\tWARN_ON(!list_empty(\u0026dpll-\u003eregistration_list));\ndrivers/dpll/dpll_core.c:122:\t\tref_tracker_dir_exit(\u0026dpll-\u003erefcnt_tracker);\ndrivers/dpll/dpll_core.c:123:\t\tkfree(dpll);\ndrivers/dpll/dpll_core.c-124-\t}\n--\ndrivers/dpll/dpll_core.c-126-\ndrivers/dpll/dpll_core.c:127:static void dpll_pin_tracker_alloc(struct dpll_pin *pin, dpll_tracker *tracker)\ndrivers/dpll/dpll_core.c-128-{\n--\ndrivers/dpll/dpll_core.c-133-\ndrivers/dpll/dpll_core.c:134:static void dpll_pin_tracker_free(struct dpll_pin *pin, dpll_tracker *tracker)\ndrivers/dpll/dpll_core.c-135-{\n--\ndrivers/dpll/dpll_core.c-140-\ndrivers/dpll/dpll_core.c:141:static void __dpll_pin_hold(struct dpll_pin *pin, dpll_tracker *tracker)\ndrivers/dpll/dpll_core.c-142-{\ndrivers/dpll/dpll_core.c:143:\tdpll_pin_tracker_alloc(pin, tracker);\ndrivers/dpll/dpll_core.c-144-\trefcount_inc(\u0026pin-\u003erefcount);\n--\ndrivers/dpll/dpll_core.c-146-\ndrivers/dpll/dpll_core.c:147:static void dpll_pin_idx_free(u32 pin_idx);\ndrivers/dpll/dpll_core.c:148:static void dpll_pin_prop_free(struct dpll_pin_properties *prop);\ndrivers/dpll/dpll_core.c-149-\ndrivers/dpll/dpll_core.c:150:static void __dpll_pin_put(struct dpll_pin *pin, dpll_tracker *tracker)\ndrivers/dpll/dpll_core.c-151-{\ndrivers/dpll/dpll_core.c:152:\tdpll_pin_tracker_free(pin, tracker);\ndrivers/dpll/dpll_core.c-153-\tif (refcount_dec_and_test(\u0026pin-\u003erefcount)) {\ndrivers/dpll/dpll_core.c:154:\t\txa_erase(\u0026dpll_pin_xa, pin-\u003eid);\ndrivers/dpll/dpll_core.c:155:\t\txa_destroy(\u0026pin-\u003edpll_refs);\ndrivers/dpll/dpll_core.c-156-\t\txa_destroy(\u0026pin-\u003eparent_refs);\ndrivers/dpll/dpll_core.c-157-\t\txa_destroy(\u0026pin-\u003eref_sync_pins);\ndrivers/dpll/dpll_core.c:158:\t\tdpll_pin_prop_free(\u0026pin-\u003eprop);\ndrivers/dpll/dpll_core.c-159-\t\tfwnode_handle_put(pin-\u003efwnode);\ndrivers/dpll/dpll_core.c:160:\t\tdpll_pin_idx_free(pin-\u003epin_idx);\ndrivers/dpll/dpll_core.c-161-\t\tref_tracker_dir_exit(\u0026pin-\u003erefcnt_tracker);\n--\ndrivers/dpll/dpll_core.c-165-\ndrivers/dpll/dpll_core.c:166:struct dpll_device *dpll_device_get_by_id(int id)\ndrivers/dpll/dpll_core.c-167-{\ndrivers/dpll/dpll_core.c:168:\tif (xa_get_mark(\u0026dpll_device_xa, id, DPLL_REGISTERED))\ndrivers/dpll/dpll_core.c:169:\t\treturn xa_load(\u0026dpll_device_xa, id);\ndrivers/dpll/dpll_core.c-170-\n--\ndrivers/dpll/dpll_core.c-173-\ndrivers/dpll/dpll_core.c:174:static struct dpll_pin_registration *\ndrivers/dpll/dpll_core.c:175:dpll_pin_registration_find(struct dpll_pin_ref *ref,\ndrivers/dpll/dpll_core.c:176:\t\t\t   const struct dpll_pin_ops *ops, void *priv,\ndrivers/dpll/dpll_core.c-177-\t\t\t   void *cookie)\ndrivers/dpll/dpll_core.c-178-{\ndrivers/dpll/dpll_core.c:179:\tstruct dpll_pin_registration *reg;\ndrivers/dpll/dpll_core.c-180-\n--\ndrivers/dpll/dpll_core.c=189=static int\ndrivers/dpll/dpll_core.c:190:dpll_xa_ref_pin_add(struct xarray *xa_pins, struct dpll_pin *pin,\ndrivers/dpll/dpll_core.c:191:\t\t    const struct dpll_pin_ops *ops, void *priv,\ndrivers/dpll/dpll_core.c-192-\t\t    void *cookie)\ndrivers/dpll/dpll_core.c-193-{\ndrivers/dpll/dpll_core.c:194:\tstruct dpll_pin_registration *reg;\ndrivers/dpll/dpll_core.c:195:\tstruct dpll_pin_ref *ref;\ndrivers/dpll/dpll_core.c-196-\tbool ref_exists = false;\n--\ndrivers/dpll/dpll_core.c-202-\t\t\tcontinue;\ndrivers/dpll/dpll_core.c:203:\t\treg = dpll_pin_registration_find(ref, ops, priv, cookie);\ndrivers/dpll/dpll_core.c-204-\t\tif (reg)\n--\ndrivers/dpll/dpll_core.c-234-\treg-\u003ecookie = cookie;\ndrivers/dpll/dpll_core.c:235:\t__dpll_pin_hold(pin, \u0026reg-\u003etracker);\ndrivers/dpll/dpll_core.c-236-\tif (ref_exists)\n--\ndrivers/dpll/dpll_core.c-242-\ndrivers/dpll/dpll_core.c:243:static int dpll_xa_ref_pin_del(struct xarray *xa_pins, struct dpll_pin *pin,\ndrivers/dpll/dpll_core.c:244:\t\t\t       const struct dpll_pin_ops *ops, void *priv,\ndrivers/dpll/dpll_core.c-245-\t\t\t       void *cookie)\ndrivers/dpll/dpll_core.c-246-{\ndrivers/dpll/dpll_core.c:247:\tstruct dpll_pin_registration *reg;\ndrivers/dpll/dpll_core.c:248:\tstruct dpll_pin_ref *ref;\ndrivers/dpll/dpll_core.c-249-\tunsigned long i;\n--\ndrivers/dpll/dpll_core.c-253-\t\t\tcontinue;\ndrivers/dpll/dpll_core.c:254:\t\treg = dpll_pin_registration_find(ref, ops, priv, cookie);\ndrivers/dpll/dpll_core.c-255-\t\tif (WARN_ON(!reg))\n--\ndrivers/dpll/dpll_core.c-257-\t\tlist_del(\u0026reg-\u003elist);\ndrivers/dpll/dpll_core.c:258:\t\t__dpll_pin_put(pin, \u0026reg-\u003etracker);\ndrivers/dpll/dpll_core.c-259-\t\tkfree(reg);\n--\ndrivers/dpll/dpll_core.c=271=static int\ndrivers/dpll/dpll_core.c:272:dpll_xa_ref_dpll_add(struct xarray *xa_dplls, struct dpll_device *dpll,\ndrivers/dpll/dpll_core.c:273:\t\t     const struct dpll_pin_ops *ops, void *priv, void *cookie)\ndrivers/dpll/dpll_core.c-274-{\ndrivers/dpll/dpll_core.c:275:\tstruct dpll_pin_registration *reg;\ndrivers/dpll/dpll_core.c:276:\tstruct dpll_pin_ref *ref;\ndrivers/dpll/dpll_core.c-277-\tbool ref_exists = false;\n--\ndrivers/dpll/dpll_core.c-280-\ndrivers/dpll/dpll_core.c:281:\txa_for_each(xa_dplls, i, ref) {\ndrivers/dpll/dpll_core.c:282:\t\tif (ref-\u003edpll != dpll)\ndrivers/dpll/dpll_core.c-283-\t\t\tcontinue;\ndrivers/dpll/dpll_core.c:284:\t\treg = dpll_pin_registration_find(ref, ops, priv, cookie);\ndrivers/dpll/dpll_core.c-285-\t\tif (reg)\n--\ndrivers/dpll/dpll_core.c-294-\t\t\treturn -ENOMEM;\ndrivers/dpll/dpll_core.c:295:\t\tref-\u003edpll = dpll;\ndrivers/dpll/dpll_core.c-296-\t\tINIT_LIST_HEAD(\u0026ref-\u003eregistration_list);\ndrivers/dpll/dpll_core.c:297:\t\tret = xa_insert(xa_dplls, dpll-\u003eid, ref, GFP_KERNEL);\ndrivers/dpll/dpll_core.c-298-\t\tif (ret) {\n--\ndrivers/dpll/dpll_core.c-307-\t\tif (!ref_exists) {\ndrivers/dpll/dpll_core.c:308:\t\t\txa_erase(xa_dplls, dpll-\u003eid);\ndrivers/dpll/dpll_core.c-309-\t\t\tkfree(ref);\n--\ndrivers/dpll/dpll_core.c-315-\treg-\u003ecookie = cookie;\ndrivers/dpll/dpll_core.c:316:\t__dpll_device_hold(dpll, \u0026reg-\u003etracker);\ndrivers/dpll/dpll_core.c-317-\tif (ref_exists)\n--\ndrivers/dpll/dpll_core.c=324=static void\ndrivers/dpll/dpll_core.c:325:dpll_xa_ref_dpll_del(struct xarray *xa_dplls, struct dpll_device *dpll,\ndrivers/dpll/dpll_core.c:326:\t\t     const struct dpll_pin_ops *ops, void *priv, void *cookie)\ndrivers/dpll/dpll_core.c-327-{\ndrivers/dpll/dpll_core.c:328:\tstruct dpll_pin_registration *reg;\ndrivers/dpll/dpll_core.c:329:\tstruct dpll_pin_ref *ref;\ndrivers/dpll/dpll_core.c-330-\tunsigned long i;\ndrivers/dpll/dpll_core.c-331-\ndrivers/dpll/dpll_core.c:332:\txa_for_each(xa_dplls, i, ref) {\ndrivers/dpll/dpll_core.c:333:\t\tif (ref-\u003edpll != dpll)\ndrivers/dpll/dpll_core.c-334-\t\t\tcontinue;\ndrivers/dpll/dpll_core.c:335:\t\treg = dpll_pin_registration_find(ref, ops, priv, cookie);\ndrivers/dpll/dpll_core.c-336-\t\tif (WARN_ON(!reg))\n--\ndrivers/dpll/dpll_core.c-338-\t\tlist_del(\u0026reg-\u003elist);\ndrivers/dpll/dpll_core.c:339:\t\t__dpll_device_put(dpll, \u0026reg-\u003etracker);\ndrivers/dpll/dpll_core.c-340-\t\tkfree(reg);\ndrivers/dpll/dpll_core.c-341-\t\tif (refcount_dec_and_test(\u0026ref-\u003erefcount)) {\ndrivers/dpll/dpll_core.c:342:\t\t\txa_erase(xa_dplls, i);\ndrivers/dpll/dpll_core.c-343-\t\t\tWARN_ON(!list_empty(\u0026ref-\u003eregistration_list));\n--\ndrivers/dpll/dpll_core.c-349-\ndrivers/dpll/dpll_core.c:350:struct dpll_pin_ref *dpll_xa_ref_dpll_first(struct xarray *xa_refs)\ndrivers/dpll/dpll_core.c-351-{\ndrivers/dpll/dpll_core.c:352:\tstruct dpll_pin_ref *ref;\ndrivers/dpll/dpll_core.c-353-\tunsigned long i = 0;\n--\ndrivers/dpll/dpll_core.c-359-\ndrivers/dpll/dpll_core.c:360:static struct dpll_device *\ndrivers/dpll/dpll_core.c:361:dpll_device_alloc(const u64 clock_id, u32 device_idx, struct module *module)\ndrivers/dpll/dpll_core.c-362-{\ndrivers/dpll/dpll_core.c:363:\tstruct dpll_device *dpll;\ndrivers/dpll/dpll_core.c-364-\tint ret;\ndrivers/dpll/dpll_core.c-365-\ndrivers/dpll/dpll_core.c:366:\tdpll = kzalloc_obj(*dpll);\ndrivers/dpll/dpll_core.c:367:\tif (!dpll)\ndrivers/dpll/dpll_core.c-368-\t\treturn ERR_PTR(-ENOMEM);\ndrivers/dpll/dpll_core.c:369:\trefcount_set(\u0026dpll-\u003erefcount, 1);\ndrivers/dpll/dpll_core.c:370:\tINIT_LIST_HEAD(\u0026dpll-\u003eregistration_list);\ndrivers/dpll/dpll_core.c:371:\tdpll-\u003edevice_idx = device_idx;\ndrivers/dpll/dpll_core.c:372:\tdpll-\u003eclock_id = clock_id;\ndrivers/dpll/dpll_core.c:373:\tdpll-\u003emodule = module;\ndrivers/dpll/dpll_core.c:374:\tret = xa_alloc_cyclic(\u0026dpll_device_xa, \u0026dpll-\u003eid, dpll, xa_limit_32b,\ndrivers/dpll/dpll_core.c:375:\t\t\t      \u0026dpll_device_xa_id, GFP_KERNEL);\ndrivers/dpll/dpll_core.c-376-\tif (ret \u003c 0) {\ndrivers/dpll/dpll_core.c:377:\t\tkfree(dpll);\ndrivers/dpll/dpll_core.c-378-\t\treturn ERR_PTR(ret);\ndrivers/dpll/dpll_core.c-379-\t}\ndrivers/dpll/dpll_core.c:380:\txa_init_flags(\u0026dpll-\u003epin_refs, XA_FLAGS_ALLOC);\ndrivers/dpll/dpll_core.c:381:\tref_tracker_dir_init(\u0026dpll-\u003erefcnt_tracker, 128, \"dpll_device\");\ndrivers/dpll/dpll_core.c-382-\ndrivers/dpll/dpll_core.c:383:\treturn dpll;\ndrivers/dpll/dpll_core.c-384-}\n--\ndrivers/dpll/dpll_core.c-386-/**\ndrivers/dpll/dpll_core.c:387: * dpll_device_get - find existing or create new dpll device\ndrivers/dpll/dpll_core.c-388- * @clock_id: clock_id of creator\n--\ndrivers/dpll/dpll_core.c-392- *\ndrivers/dpll/dpll_core.c:393: * Get existing object of a dpll device, unique for given arguments.\ndrivers/dpll/dpll_core.c-394- * Create new if doesn't exist yet.\ndrivers/dpll/dpll_core.c-395- *\ndrivers/dpll/dpll_core.c:396: * Context: Acquires a lock (dpll_lock)\ndrivers/dpll/dpll_core.c-397- * Return:\ndrivers/dpll/dpll_core.c:398: * * valid dpll_device struct pointer if succeeded\ndrivers/dpll/dpll_core.c-399- * * ERR_PTR(X) - error\ndrivers/dpll/dpll_core.c-400- */\ndrivers/dpll/dpll_core.c:401:struct dpll_device *\ndrivers/dpll/dpll_core.c:402:dpll_device_get(u64 clock_id, u32 device_idx, struct module *module,\ndrivers/dpll/dpll_core.c:403:\t\tdpll_tracker *tracker)\ndrivers/dpll/dpll_core.c-404-{\ndrivers/dpll/dpll_core.c:405:\tstruct dpll_device *dpll, *ret = NULL;\ndrivers/dpll/dpll_core.c-406-\tunsigned long index;\ndrivers/dpll/dpll_core.c-407-\ndrivers/dpll/dpll_core.c:408:\tmutex_lock(\u0026dpll_lock);\ndrivers/dpll/dpll_core.c:409:\txa_for_each(\u0026dpll_device_xa, index, dpll) {\ndrivers/dpll/dpll_core.c:410:\t\tif (dpll-\u003eclock_id == clock_id \u0026\u0026\ndrivers/dpll/dpll_core.c:411:\t\t    dpll-\u003edevice_idx == device_idx \u0026\u0026\ndrivers/dpll/dpll_core.c:412:\t\t    dpll-\u003emodule == module) {\ndrivers/dpll/dpll_core.c:413:\t\t\t__dpll_device_hold(dpll, tracker);\ndrivers/dpll/dpll_core.c:414:\t\t\tret = dpll;\ndrivers/dpll/dpll_core.c-415-\t\t\tbreak;\n--\ndrivers/dpll/dpll_core.c-418-\tif (!ret) {\ndrivers/dpll/dpll_core.c:419:\t\tret = dpll_device_alloc(clock_id, device_idx, module);\ndrivers/dpll/dpll_core.c-420-\t\tif (!IS_ERR(ret))\ndrivers/dpll/dpll_core.c:421:\t\t\tdpll_device_tracker_alloc(ret, tracker);\ndrivers/dpll/dpll_core.c-422-\t}\ndrivers/dpll/dpll_core.c-423-\ndrivers/dpll/dpll_core.c:424:\tmutex_unlock(\u0026dpll_lock);\ndrivers/dpll/dpll_core.c-425-\n--\ndrivers/dpll/dpll_core.c-427-}\ndrivers/dpll/dpll_core.c:428:EXPORT_SYMBOL_GPL(dpll_device_get);\ndrivers/dpll/dpll_core.c-429-\ndrivers/dpll/dpll_core.c-430-/**\ndrivers/dpll/dpll_core.c:431: * dpll_device_put - decrease the refcount and free memory if possible\ndrivers/dpll/dpll_core.c:432: * @dpll: dpll_device struct pointer\ndrivers/dpll/dpll_core.c-433- * @tracker: tracking object for the acquired reference\ndrivers/dpll/dpll_core.c-434- *\ndrivers/dpll/dpll_core.c:435: * Context: Acquires a lock (dpll_lock)\ndrivers/dpll/dpll_core.c:436: * Drop reference for a dpll device, if all references are gone, delete\ndrivers/dpll/dpll_core.c:437: * dpll device object.\ndrivers/dpll/dpll_core.c-438- */\ndrivers/dpll/dpll_core.c:439:void dpll_device_put(struct dpll_device *dpll, dpll_tracker *tracker)\ndrivers/dpll/dpll_core.c-440-{\ndrivers/dpll/dpll_core.c:441:\tmutex_lock(\u0026dpll_lock);\ndrivers/dpll/dpll_core.c:442:\t__dpll_device_put(dpll, tracker);\ndrivers/dpll/dpll_core.c:443:\tmutex_unlock(\u0026dpll_lock);\ndrivers/dpll/dpll_core.c-444-}\ndrivers/dpll/dpll_core.c:445:EXPORT_SYMBOL_GPL(dpll_device_put);\ndrivers/dpll/dpll_core.c-446-\ndrivers/dpll/dpll_core.c:447:static struct dpll_device_registration *\ndrivers/dpll/dpll_core.c:448:dpll_device_registration_find(struct dpll_device *dpll,\ndrivers/dpll/dpll_core.c:449:\t\t\t      const struct dpll_device_ops *ops, void *priv)\ndrivers/dpll/dpll_core.c-450-{\ndrivers/dpll/dpll_core.c:451:\tstruct dpll_device_registration *reg;\ndrivers/dpll/dpll_core.c-452-\ndrivers/dpll/dpll_core.c:453:\tlist_for_each_entry(reg, \u0026dpll-\u003eregistration_list, list) {\ndrivers/dpll/dpll_core.c-454-\t\tif (reg-\u003eops == ops \u0026\u0026 reg-\u003epriv == priv)\n--\ndrivers/dpll/dpll_core.c-460-/**\ndrivers/dpll/dpll_core.c:461: * dpll_device_register - register the dpll device in the subsystem\ndrivers/dpll/dpll_core.c:462: * @dpll: pointer to a dpll\ndrivers/dpll/dpll_core.c:463: * @type: type of a dpll\ndrivers/dpll/dpll_core.c:464: * @ops: ops for a dpll device\ndrivers/dpll/dpll_core.c-465- * @priv: pointer to private information of owner\ndrivers/dpll/dpll_core.c-466- *\ndrivers/dpll/dpll_core.c:467: * Make dpll device available for user space.\ndrivers/dpll/dpll_core.c-468- *\ndrivers/dpll/dpll_core.c:469: * Context: Acquires a lock (dpll_lock)\ndrivers/dpll/dpll_core.c-470- * Return:\n--\ndrivers/dpll/dpll_core.c-473- */\ndrivers/dpll/dpll_core.c:474:int dpll_device_register(struct dpll_device *dpll, enum dpll_type type,\ndrivers/dpll/dpll_core.c:475:\t\t\t const struct dpll_device_ops *ops, void *priv)\ndrivers/dpll/dpll_core.c-476-{\ndrivers/dpll/dpll_core.c:477:\tstruct dpll_device_registration *reg;\ndrivers/dpll/dpll_core.c-478-\tbool first_registration = false;\n--\ndrivers/dpll/dpll_core.c-488-\ndrivers/dpll/dpll_core.c:489:\tmutex_lock(\u0026dpll_lock);\ndrivers/dpll/dpll_core.c:490:\treg = dpll_device_registration_find(dpll, ops, priv);\ndrivers/dpll/dpll_core.c-491-\tif (reg) {\ndrivers/dpll/dpll_core.c:492:\t\tmutex_unlock(\u0026dpll_lock);\ndrivers/dpll/dpll_core.c-493-\t\treturn -EEXIST;\n--\ndrivers/dpll/dpll_core.c-497-\tif (!reg) {\ndrivers/dpll/dpll_core.c:498:\t\tmutex_unlock(\u0026dpll_lock);\ndrivers/dpll/dpll_core.c-499-\t\treturn -ENOMEM;\n--\ndrivers/dpll/dpll_core.c-502-\treg-\u003epriv = priv;\ndrivers/dpll/dpll_core.c:503:\tdpll-\u003etype = type;\ndrivers/dpll/dpll_core.c:504:\t__dpll_device_hold(dpll, \u0026reg-\u003etracker);\ndrivers/dpll/dpll_core.c:505:\tfirst_registration = list_empty(\u0026dpll-\u003eregistration_list);\ndrivers/dpll/dpll_core.c:506:\tlist_add_tail(\u0026reg-\u003elist, \u0026dpll-\u003eregistration_list);\ndrivers/dpll/dpll_core.c-507-\tif (!first_registration) {\ndrivers/dpll/dpll_core.c:508:\t\tmutex_unlock(\u0026dpll_lock);\ndrivers/dpll/dpll_core.c-509-\t\treturn 0;\n--\ndrivers/dpll/dpll_core.c-511-\ndrivers/dpll/dpll_core.c:512:\txa_set_mark(\u0026dpll_device_xa, dpll-\u003eid, DPLL_REGISTERED);\ndrivers/dpll/dpll_core.c:513:\tdpll_device_create_ntf(dpll);\ndrivers/dpll/dpll_core.c:514:\tmutex_unlock(\u0026dpll_lock);\ndrivers/dpll/dpll_core.c-515-\n--\ndrivers/dpll/dpll_core.c-517-}\ndrivers/dpll/dpll_core.c:518:EXPORT_SYMBOL_GPL(dpll_device_register);\ndrivers/dpll/dpll_core.c-519-\ndrivers/dpll/dpll_core.c-520-/**\ndrivers/dpll/dpll_core.c:521: * dpll_device_unregister - unregister dpll device\ndrivers/dpll/dpll_core.c:522: * @dpll: registered dpll pointer\ndrivers/dpll/dpll_core.c:523: * @ops: ops for a dpll device\ndrivers/dpll/dpll_core.c-524- * @priv: pointer to private information of owner\n--\ndrivers/dpll/dpll_core.c-527- * Note: It does not free the memory\ndrivers/dpll/dpll_core.c:528: * Context: Acquires a lock (dpll_lock)\ndrivers/dpll/dpll_core.c-529- */\ndrivers/dpll/dpll_core.c:530:void dpll_device_unregister(struct dpll_device *dpll,\ndrivers/dpll/dpll_core.c:531:\t\t\t    const struct dpll_device_ops *ops, void *priv)\ndrivers/dpll/dpll_core.c-532-{\ndrivers/dpll/dpll_core.c:533:\tstruct dpll_device_registration *reg;\ndrivers/dpll/dpll_core.c-534-\ndrivers/dpll/dpll_core.c:535:\tmutex_lock(\u0026dpll_lock);\ndrivers/dpll/dpll_core.c:536:\tASSERT_DPLL_REGISTERED(dpll);\ndrivers/dpll/dpll_core.c:537:\tdpll_device_delete_ntf(dpll);\ndrivers/dpll/dpll_core.c:538:\treg = dpll_device_registration_find(dpll, ops, priv);\ndrivers/dpll/dpll_core.c-539-\tif (WARN_ON(!reg)) {\ndrivers/dpll/dpll_core.c:540:\t\tmutex_unlock(\u0026dpll_lock);\ndrivers/dpll/dpll_core.c-541-\t\treturn;\n--\ndrivers/dpll/dpll_core.c-543-\tlist_del(\u0026reg-\u003elist);\ndrivers/dpll/dpll_core.c:544:\t__dpll_device_put(dpll, \u0026reg-\u003etracker);\ndrivers/dpll/dpll_core.c-545-\tkfree(reg);\ndrivers/dpll/dpll_core.c-546-\ndrivers/dpll/dpll_core.c:547:\tif (!list_empty(\u0026dpll-\u003eregistration_list)) {\ndrivers/dpll/dpll_core.c:548:\t\tmutex_unlock(\u0026dpll_lock);\ndrivers/dpll/dpll_core.c-549-\t\treturn;\ndrivers/dpll/dpll_core.c-550-\t}\ndrivers/dpll/dpll_core.c:551:\txa_clear_mark(\u0026dpll_device_xa, dpll-\u003eid, DPLL_REGISTERED);\ndrivers/dpll/dpll_core.c:552:\tmutex_unlock(\u0026dpll_lock);\ndrivers/dpll/dpll_core.c-553-}\ndrivers/dpll/dpll_core.c:554:EXPORT_SYMBOL_GPL(dpll_device_unregister);\ndrivers/dpll/dpll_core.c-555-\ndrivers/dpll/dpll_core.c:556:static int dpll_pin_idx_alloc(u32 *pin_idx)\ndrivers/dpll/dpll_core.c-557-{\n--\ndrivers/dpll/dpll_core.c-563-\t/* Alloc unique number from IDA. Number belongs to \u003c0, INT_MAX\u003e range */\ndrivers/dpll/dpll_core.c:564:\tret = ida_alloc(\u0026dpll_pin_idx_ida, GFP_KERNEL);\ndrivers/dpll/dpll_core.c-565-\tif (ret \u003c 0)\n--\ndrivers/dpll/dpll_core.c-573-\ndrivers/dpll/dpll_core.c:574:static void dpll_pin_idx_free(u32 pin_idx)\ndrivers/dpll/dpll_core.c-575-{\n--\ndrivers/dpll/dpll_core.c-582-\tpin_idx -= (u32)INT_MAX + 1;\ndrivers/dpll/dpll_core.c:583:\tida_free(\u0026dpll_pin_idx_ida, pin_idx);\ndrivers/dpll/dpll_core.c-584-}\ndrivers/dpll/dpll_core.c-585-\ndrivers/dpll/dpll_core.c:586:static void dpll_pin_prop_free(struct dpll_pin_properties *prop)\ndrivers/dpll/dpll_core.c-587-{\n--\ndrivers/dpll/dpll_core.c-593-\ndrivers/dpll/dpll_core.c:594:static int dpll_pin_prop_dup(const struct dpll_pin_properties *src,\ndrivers/dpll/dpll_core.c:595:\t\t\t     struct dpll_pin_properties *dst)\ndrivers/dpll/dpll_core.c-596-{\n--\ndrivers/dpll/dpll_core.c-635-\ndrivers/dpll/dpll_core.c:636:static struct dpll_pin *\ndrivers/dpll/dpll_core.c:637:dpll_pin_alloc(u64 clock_id, u32 pin_idx, struct module *module,\ndrivers/dpll/dpll_core.c:638:\t       const struct dpll_pin_properties *prop)\ndrivers/dpll/dpll_core.c-639-{\ndrivers/dpll/dpll_core.c:640:\tstruct dpll_pin *pin;\ndrivers/dpll/dpll_core.c-641-\tint ret;\n--\ndrivers/dpll/dpll_core.c-643-\tif (pin_idx == DPLL_PIN_IDX_UNSPEC) {\ndrivers/dpll/dpll_core.c:644:\t\tret = dpll_pin_idx_alloc(\u0026pin_idx);\ndrivers/dpll/dpll_core.c-645-\t\tif (ret)\n--\ndrivers/dpll/dpll_core.c-663-\t}\ndrivers/dpll/dpll_core.c:664:\tret = dpll_pin_prop_dup(prop, \u0026pin-\u003eprop);\ndrivers/dpll/dpll_core.c-665-\tif (ret)\n--\ndrivers/dpll/dpll_core.c-667-\trefcount_set(\u0026pin-\u003erefcount, 1);\ndrivers/dpll/dpll_core.c:668:\txa_init_flags(\u0026pin-\u003edpll_refs, XA_FLAGS_ALLOC);\ndrivers/dpll/dpll_core.c-669-\txa_init_flags(\u0026pin-\u003eparent_refs, XA_FLAGS_ALLOC);\ndrivers/dpll/dpll_core.c-670-\txa_init_flags(\u0026pin-\u003eref_sync_pins, XA_FLAGS_ALLOC);\ndrivers/dpll/dpll_core.c:671:\tret = xa_alloc_cyclic(\u0026dpll_pin_xa, \u0026pin-\u003eid, pin, xa_limit_32b,\ndrivers/dpll/dpll_core.c:672:\t\t\t      \u0026dpll_pin_xa_id, GFP_KERNEL);\ndrivers/dpll/dpll_core.c-673-\tif (ret \u003c 0)\ndrivers/dpll/dpll_core.c-674-\t\tgoto err_xa_alloc;\ndrivers/dpll/dpll_core.c:675:\tref_tracker_dir_init(\u0026pin-\u003erefcnt_tracker, 128, \"dpll_pin\");\ndrivers/dpll/dpll_core.c-676-\treturn pin;\ndrivers/dpll/dpll_core.c-677-err_xa_alloc:\ndrivers/dpll/dpll_core.c:678:\txa_destroy(\u0026pin-\u003edpll_refs);\ndrivers/dpll/dpll_core.c-679-\txa_destroy(\u0026pin-\u003eparent_refs);\ndrivers/dpll/dpll_core.c-680-\txa_destroy(\u0026pin-\u003eref_sync_pins);\ndrivers/dpll/dpll_core.c:681:\tdpll_pin_prop_free(\u0026pin-\u003eprop);\ndrivers/dpll/dpll_core.c-682-err_pin_prop:\n--\ndrivers/dpll/dpll_core.c-684-err_pin_alloc:\ndrivers/dpll/dpll_core.c:685:\tdpll_pin_idx_free(pin_idx);\ndrivers/dpll/dpll_core.c-686-\treturn ERR_PTR(ret);\n--\ndrivers/dpll/dpll_core.c-688-\ndrivers/dpll/dpll_core.c:689:static void dpll_netdev_pin_assign(struct net_device *dev, struct dpll_pin *dpll_pin)\n\n"
}

33/2 2026/07/13 13:39 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 47972
							output: 27
							thoughts: 125
						

Thoughts:
**Investigating DPLL Netlink Support in Syzkaller**

Okay, so I'm focusing on this `dpll` thing. Right, `dpll` is a subsystem, and I recall seeing the core implementation in `drivers/dpll/dpll_core.c`. My immediate question is, does Syzkaller have any descriptions for `dpll` netlink functionality? I need to figure out if it's already covered.

My first thought is to check the typical places. Let's see, maybe it's in `net/dpll` or possibly `drivers/dpll/dpll_nl.c`. Aha! Found `dpll_nl.c`, that's a good sign. Now, the next logical step is to dive into the Syzkaller infrastructure. Does Syzkaller know about this `dpll` netlink interface? I'll search the `sys/linux/` directory within Syzkaller for any relevant definitions, looking for anything named `dpll`. That's where I need to start. Let's see what we can find!



34/2 2026/07/13 13:39 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "dpll",
  "PathPrefix": "sys/linux/"
}

Results:
{
  "error": "no matches"
}

35/2 2026/07/13 13:39 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 48381
							output: 19
							thoughts: 399
						

Thoughts:
**Analyzing the DPLL Fuzzing Reachability**

Okay, I need to recalibrate here. I'm deep in the kernel tree, not the syzkaller repository. That changes things; I don't have direct access to syzkaller's internals right now. The first thing I need to do is see if the `dpll` netlink family is even targeted. I can check this by verifying the existence of a `genl_family` entry for `dpll`. Yup, it's a Generic Netlink family, which means syzkaller's `genetlink` fuzzer *should* automatically pick it up, provided it's registered.

The crucial question is whether the `zl3073x` *driver* is reachable, which is the crux of the issue. If the driver fails to probe successfully, the DPLL device and the PTP clock won't get registered. That means the netlink and PTP ioctls won't even make it to the driver's code, so no fuzzing success.

And here's where it gets tricky. That driver is hardware-specific, requiring specific I2C/SPI hardware. It checks the chip ID, which means it cannot be emulated easily in a virtualized environment. So far, no dice.

Wait, is there *any* remote chance the driver can be probed using `i2c-stub`? Previously, I noted that `i2c-stub` only deals with 8-bit register addresses, while `zl3073x` uses 16-bit registers. Forget `i2c-stub`.

But wait, hold on. Is `i2c-stub` the only option? What about other possibilities? There are no `i2c-mock` or `spi-mock` in the upstream kernel. `vkms` is for DRM; wrong domain. `mac80211_hwsim` is for WiFi, irrelevant. And `netdevsim` is for networking, also not relevant.

What about `dpll` itself? Does the `dpll` part have any kind of simulator or mocking capability? Let's investigate the possibility of a DPLL simulator.



36/2 2026/07/13 13:39 tool grepper
0m Args:
{
  "Expression": "dpll.*sim"
}

Results:
{
  "Output": "Documentation/driver-api/dpll.rst=575=for each call with the same arguments only the object refcount\nDocumentation/driver-api/dpll.rst:576:increases. Also dpll_pin_put() works similarly to dpll_device_put().\nDocumentation/driver-api/dpll.rst-577-\n"
}

37/2 2026/07/13 13:39 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 49372
							output: 35
							thoughts: 127
						

Thoughts:
**Analyzing the Unreachable Code Path**

Okay, so there's no DPLL simulator available, which is immediately a roadblock. This `zl3073x` driver is clearly hardware-specific, meaning I'm not going to get anywhere with probing it without the actual silicon or a highly specific emulator. It's a dead end in environments like Google Compute Engine or QEMU, which is a significant limitation for testing these modifications.

Now, hold on... is there even a *remote* chance that the driver's functions are called directly by something else, some other piece of code that *is* reachable? No, it's pretty clear it’s a standalone driver.

Just to be absolutely certain, I need to double-check if there's any mechanism, even obscure, to bypass that damn chip ID check. Perhaps there's a module parameter hidden somewhere that forces the driver to load regardless. Gotta investigate that possibility.



38/2 2026/07/13 13:39 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "module_param",
  "PathPrefix": "drivers/dpll/zl3073x"
}

Results:
{
  "error": "no matches"
}

39/2 2026/07/13 13:39 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 49746
							output: 185
							thoughts: 803
						

Thoughts:
**WorthFuzzing Analysis - Azurite DPLL Driver**

Okay, no module parameters to manipulate things here. Is there even a debugfs interface, anything, to inject a fake chip ID? Nope. So, the probe will definitely fail if the hardware isn't present.

Wait, the prompt… right, it says "WorthFuzzing=false" if it's just docs, Kconfig, comments, logging, decorative changes, or numeric constants that don't change flow, or… and this is key… *code paths that are impossible to reach in virtualized environments*.

This is a hardware-specific driver for Microchip's Azurite DPLL/PTP/SyncE devices, using I2C/SPI. It *strictly* checks the chip ID during the probe. There's no software emulator for this hardware that I know of, unlike, say, the mac80211 stuff. Generic I2C mocks can't simulate the register addressing either. So, it's *impossible* to reach this code in QEMU, GCE, or anything like that. The probe *will* fail. Therefore, this is a definite WorthFuzzing=false situation.

Let me just double-check, though. Is there *any* other way to get to this? Could the PTP clock somehow be registered *without* the probe succeeding? No. `zl3073x_dpll_ptp_register` is called from `zl3073x_dpll_register`, which is called from `zl3073x_devm_dpll_init`, which is called from `zl3073x_dev_probe`. Hmm. Could `zl3073x_dev_probe` be called from *somewhere else*? Ah, it's exported as `zl3073x_dev_probe`, but it's only called by `zl3073x_i2c_probe` and `zl3073x_spi_probe`. So it's completely unreachable in a virtualized context.

Okay, let me quickly make sure I haven't missed a sneaky little call anywhere else in the code. Let's see...

Files modified:
`drivers/dpll/zl3073x/Kconfig`
`drivers/dpll/zl3073x/chan.c`
`drivers/dpll/zl3073x/chan.h`
`drivers/dpll/zl3073x/core.c`
`drivers/dpll/zl3073x/core.h`
`drivers/dpll/zl3073x/dpll.c`
`drivers/dpll/zl3073x/dpll.h`
`drivers/dpll/zl3073x/regs.h`
All of them are inside `drivers/dpll/zl3073x/`. Good, all neatly contained within that driver.

Alright, I'm setting WorthFuzzing=false and crafting a solid reasoning statement.

The patch adds PTP clock support to the Microchip Azurite DPLL driver (`zl3073x`). It's a hardware-specific I2C/SPI driver. During initialization (`zl3073x_dev_probe`), the driver strictly reads and verifies the hardware chip ID (`ZL_REG_ID`). Since there's *no* software emulator for this specific hardware in the kernel, and the generic I2C mocks don't support the 16-bit addressing, the probe will *always* fail in virtualized environments. Consequently, the DPLL and PTP devices will never be registered. It's impossible to reach and fuzz the modified code.

Now to call the `set-results` tool and move on.



40/2 2026/07/13 13:40 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch adds PTP clock support to the Microchip Azurite DPLL driver (zl3073x). This is a hardware-specific I2C/SPI driver. During initialization (zl3073x_dev_probe), the driver strictly reads and verifies the hardware chip ID (ZL_REG_ID). Since there is no software emulator for this specific hardware in the kernel, and generic I2C mocks like i2c-stub do not support the 16-bit register addressing required by this driver, the probe function will always fail in virtualized environments (QEMU/GCE). Consequently, the DPLL and PTP devices will never be registered, making the modified code paths impossible to reach and fuzz.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch adds PTP clock support to the Microchip Azurite DPLL driver (zl3073x). This is a hardware-specific I2C/SPI driver. During initialization (zl3073x_dev_probe), the driver strictly reads and verifies the hardware chip ID (ZL_REG_ID). Since there is no software emulator for this specific hardware in the kernel, and generic I2C mocks like i2c-stub do not support the 16-bit register addressing required by this driver, the probe function will always fail in virtualized environments (QEMU/GCE). Consequently, the DPLL and PTP devices will never be registered, making the modified code paths impossible to reach and fuzz.",
  "WorthFuzzing": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)