AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/11 14:02 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "ZL3073X_I2C",
    "ZL3073X_SPI",
    "PTP_1588_CLOCK"
  ],
  "FocusSymbols": [
    "zl3073x_chan_tod_read",
    "zl3073x_chan_tod_write",
    "zl3073x_chan_tod_adjust",
    "zl3073x_chan_df_offset_set",
    "zl3073x_chan_tie_write",
    "zl3073x_chan_phase_step",
    "zl3073x_dpll_ptp_gettimex64",
    "zl3073x_dpll_ptp_settime64",
    "zl3073x_dpll_ptp_adjtime",
    "zl3073x_dpll_ptp_adjfine",
    "zl3073x_dpll_ptp_adjphase"
  ],
  "KMSANReasoning": "The patch adds PTP clock support to the Microchip Azurite DPLL/PTP/SyncE devices driver. It introduces functions to read/write Time of Day (ToD), adjust phase, adjust frequency, and register a PTP clock device. The driver interacts with the PTP subsystem by providing callbacks (`gettimex64`, `settime64`, `adjtime`, `adjfine`, `adjphase`). All structures allocated dynamically are zero-initialized using `kzalloc_obj`. The `ptp_clock_info` structure is copied from a static const template, ensuring all uninitialized fields are zeroed by the compiler before `snprintf` modifies the `name` field. The `struct timespec64` populated by the driver is fully initialized on success, and errors are properly propagated to the PTP subsystem, preventing uninitialized data from being used or copied to userspace. No new uninitialized memory risks or info-leaks are introduced.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch adds PTP clock support to the Microchip Azurite DPLL driver (zl3073x). It implements PTP clock operations like gettimex64, settime64, adjtime, adjfine, and adjphase. The driver can be instantiated via I2C or SPI in QEMU, and the PTP operations are reachable from userspace via PTP ioctls.",
  "WorthFuzzing": true
}

1/1 2026/08/11 14:02 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 957dcfd671e8bc597719c12cb2b301d2f749fed1\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Aug 11 14:02:22 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/drivers/dpll/zl3073x/Kconfig b/drivers/dpll/zl3073x/Kconfig\nindex 5bbca14005813..da1731ace15f0 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\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\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\n \tselect REGMAP_SPI\n \tselect ZL3073X\n \thelp\ndiff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c\nindex 4ec2cf53dad46..bc6d70d25d791 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@@ -162,8 +163,8 @@ int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index)\n  * @zldev: pointer to zl3073x_dev structure\n  * @index: DPLL channel index to fetch state for\n  *\n- * Reads the mode_refsel register and reference priority registers for\n- * the given DPLL channel and stores the raw values for later use.\n+ * Reads the mode_refsel, status and reference priority registers for\n+ * the given DPLL channel and stores the values for later use.\n  *\n  * Return: 0 on success, \u003c0 on error\n  */\n@@ -234,6 +235,326 @@ 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+ * Checks the ToD control register semaphore bit. If clear, returns\n+ * immediately. Otherwise polls until the bit is cleared by the device.\n+ *\n+ * Return:\n+ * * 0\t\t\t- success\n+ * * %-EBUSY\t\t- timeout\n+ * * %-EOPNOTSUPP\t- unknown command detected\n+ * * negative\t\t- other error\n+ */\n+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+\tif (!(tod_ctrl \u0026 ZL_DPLL_TOD_CTRL_SEM))\n+\t\treturn 0;\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+\tcase ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT:\n+\tcase ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ:\n+\t\ttimeout = ZL_POLL_TOD_RD_TIMEOUT_US;\n+\t\tbreak;\n+\tdefault:\n+\t\tWARN_ON(1);\n+\t\treturn -EOPNOTSUPP;\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+ * Re-reads if the 1 Hz tick crossed between the two reads or if less\n+ * than 20 ms remains before the next rollover. Applies @delta and writes\n+ * 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+#define ZL_TOD_MAX_RETRIES\t20\n+\tstatic const long threshold_ns = 20 * NSEC_PER_MSEC;\n+\tstruct timespec64 ts_next, ts_cur, diff;\n+\tint rc, i;\n+\n+\tfor (i = 0; i \u003c ZL_TOD_MAX_RETRIES; i++) {\n+\t\trc = zl3073x_chan_tod_read(zldev, ch, true, \u0026ts_next, NULL);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\n+\t\trc = zl3073x_chan_tod_read(zldev, ch, false, \u0026ts_cur, NULL);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\n+\t\t/* Ensure the 1 Hz tick did not cross between the two reads\n+\t\t * and that enough margin remains to complete the write.\n+\t\t */\n+\t\tdiff = timespec64_sub(ts_next, ts_cur);\n+\t\tif (diff.tv_sec \u003e 0 ||\n+\t\t    (!diff.tv_sec \u0026\u0026 diff.tv_nsec \u003e= threshold_ns))\n+\t\t\tbreak;\n+\t}\n+\tif (i == ZL_TOD_MAX_RETRIES) {\n+\t\tdev_warn(zldev-\u003edev,\n+\t\t\t \"DPLL%u ToD adjust failed to get stable margin\\n\",\n+\t\t\t ch);\n+\t\treturn -EBUSY;\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+\treturn zl3073x_chan_tod_write(zldev, ch, ts_next);\n+#undef ZL_TOD_MAX_RETRIES\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+ * Context: Caller must hold the per-DPLL lock.\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 in (-1s, 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+\tif (WARN_ON(delta_ns \u003c= -NSEC_PER_SEC || delta_ns \u003e= NSEC_PER_SEC))\n+\t\treturn -ERANGE;\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 dc9c6d95bdee7..99c3b7088f678 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@@ -42,6 +44,21 @@ 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_ready_wait(struct zl3073x_dev *zldev, u8 ch);\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 +217,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\ndiff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c\nindex 5b2d77f2c2288..230df08e27cd1 100644\n--- a/drivers/dpll/zl3073x/core.c\n+++ b/drivers/dpll/zl3073x/core.c\n@@ -322,7 +322,7 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val)\n int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,\n \t\t\t u8 mask, unsigned int timeout_us)\n {\n-#define ZL_POLL_SLEEP_US 10\n+\tunsigned int sleep_us = timeout_us / 50;\n \tunsigned int val;\n \n \t/* Check the register is 8bit */\n@@ -336,7 +336,7 @@ int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,\n \treg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;\n \n \treturn regmap_read_poll_timeout(zldev-\u003eregmap, reg, val, !(val \u0026 mask),\n-\t\t\t\t\tZL_POLL_SLEEP_US, timeout_us);\n+\t\t\t\t\tsleep_us, timeout_us);\n }\n \n int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,\n@@ -511,6 +511,11 @@ zl3073x_dev_state_fetch(struct zl3073x_dev *zldev)\n \tint rc;\n \tu8 i;\n \n+\trc = zl3073x_read_u16(zldev, ZL_REG_OUTPUT_STEP_TIME_MASK,\n+\t\t\t      \u0026zldev-\u003eout_step_time_mask);\n+\tif (rc)\n+\t\treturn rc;\n+\n \tfor (i = 0; i \u003c ZL3073X_NUM_REFS; i++) {\n \t\trc = zl3073x_ref_state_fetch(zldev, i);\n \t\tif (rc) {\n@@ -1034,6 +1039,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 78dc208f3eea2..67c10e2595118 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@@ -63,6 +69,7 @@ struct zl3073x_chip_info {\n  * @kworker: thread for periodic work\n  * @work: periodic work\n  * @clock_id: clock id of the device\n+ * @out_step_time_mask: output step-time mask (device-global)\n  * @phase_avg_factor: phase offset measurement averaging factor\n  * @freq_monitor: is frequency monitor enabled\n  */\n@@ -71,6 +78,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];\n@@ -87,6 +96,7 @@ struct zl3073x_dev {\n \n \t/* Per-chip parameters */\n \tu64\t\t\tclock_id;\n+\tu16\t\t\tout_step_time_mask;\n \tu8\t\t\tphase_avg_factor;\n \tbool\t\t\tfreq_monitor;\n };\n@@ -308,6 +318,19 @@ zl3073x_dev_out_is_enabled(struct zl3073x_dev *zldev, u8 index)\n \treturn zl3073x_synth_is_enabled(synth) \u0026\u0026 zl3073x_out_is_enabled(out);\n }\n \n+/**\n+ * zl3073x_dev_out_is_stepped - check if output is in step-time mask\n+ * @zldev: pointer to zl3073x device\n+ * @index: output index\n+ *\n+ * Return: true if output is affected by step-time operations\n+ */\n+static inline bool\n+zl3073x_dev_out_is_stepped(struct zl3073x_dev *zldev, u8 index)\n+{\n+\treturn !!(zldev-\u003eout_step_time_mask \u0026 BIT(index));\n+}\n+\n /**\n  * zl3073x_dev_out_dpll_get - get DPLL ID the output is driven by\n  * @zldev: pointer to zl3073x device\ndiff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c\nindex 0488ae6ac486c..8301ad343658a 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@@ -13,6 +14,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@@ -2275,45 +2277,384 @@ zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev)\n \treturn zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_CTRL, 0x01);\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\n+ * the first enabled synth's period for the conversion).\n+ *\n+ * Return:\n+ * * %0\t\t\t- success (or partial success if a later synth group\n+ *\t\t\t  failed after the first was already stepped)\n+ * * %-EOPNOTSUPP\t- no synths available\n+ * * negative\t\t- 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+\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+\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_dev_out_is_stepped(zldev, 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 first enabled 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 * first enabled 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 or TIE write\n+ * fails. 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+ * If the seconds part was already committed when the sub-second\n+ * mechanism fails, returns 0 to prevent the PTP servo from retrying\n+ * the full delta and applying seconds again.\n+ *\n+ * Return: 0 on success (or partial 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 (delta \u003e= NSEC_PER_SEC || delta \u003c= -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\t/* No sub-second remainder, done */\n+\t\tif (!delta)\n+\t\t\treturn 0;\n+\n+\t\t/* Wait for the ToD write to be applied at the 1 Hz edge\n+\t\t * before issuing phase step or TIE write, so the pending\n+\t\t * WR_NEXT_1HZ does not overwrite the sub-second adjustment.\n+\t\t */\n+\t\trc = zl3073x_chan_tod_ready_wait(zldev, zldpll-\u003eid);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\n+\t\tsec_adjusted = true;\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:\n+ * * %0\t\t\t- success or @scaled_ppm is zero (no-op)\n+ * * %-EOPNOTSUPP\t- NCO pin is not connected and @scaled_ppm is non-zero\n+ * * negative\t\t- other 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:\n+ * * %0\t\t\t- success or @delta is zero (no-op)\n+ * * %-EOPNOTSUPP\t- mode does not support TIE and @delta is non-zero\n+ * * negative\t\t- other 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+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+};\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+\tzldpll-\u003eptp_info = zl3073x_dpll_ptp_clock_info;\n+\tsnprintf(zldpll-\u003eptp_info.name, sizeof(zldpll-\u003eptp_info.name),\n+\t\t \"%s-dpll%u\", dev_name(zldev-\u003edev), 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@@ -2398,6 +2739,47 @@ 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+\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@@ -2428,6 +2810,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@@ -2441,7 +2830,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 faebc402ba1b7..993221dc63249 100644\n--- a/drivers/dpll/zl3073x/dpll.h\n+++ b/drivers/dpll/zl3073x/dpll.h\n@@ -5,6 +5,7 @@\n \n #include \u003clinux/dpll.h\u003e\n #include \u003clinux/list.h\u003e\n+#include \u003clinux/ptp_clock_kernel.h\u003e\n \n #include \"core.h\"\n \n@@ -22,6 +23,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 +39,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 b70ead7d4495b..f3a5e1215aa36 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, ZL3073X_MAX_CHANNELS, 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\t\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,23 @@\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+#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 10, Ref Mailbox\n  *******************************/\n"
}

2/1 2026/08/11 14:02 agent patch-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "ZL3073X_I2C",
    "ZL3073X_SPI",
    "PTP_1588_CLOCK"
  ],
  "FocusSymbols": [
    "zl3073x_chan_tod_read",
    "zl3073x_chan_tod_write",
    "zl3073x_chan_tod_adjust",
    "zl3073x_chan_df_offset_set",
    "zl3073x_chan_tie_write",
    "zl3073x_chan_phase_step",
    "zl3073x_dpll_ptp_gettimex64",
    "zl3073x_dpll_ptp_settime64",
    "zl3073x_dpll_ptp_adjtime",
    "zl3073x_dpll_ptp_adjfine",
    "zl3073x_dpll_ptp_adjphase"
  ],
  "Reasoning": "The patch adds PTP clock support to the Microchip Azurite DPLL driver (zl3073x). It implements PTP clock operations like gettimex64, settime64, adjtime, adjfine, and adjphase. The driver can be instantiated via I2C or SPI in QEMU, and the PTP operations are reachable from userspace via PTP ioctls.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 957dcfd671e8bc597719c12cb2b301d2f749fed1
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Aug 11 14:02:22 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/dpll/zl3073x/Kconfig b/drivers/dpll/zl3073x/Kconfig
index 5bbca14005813..da1731ace15f0 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
 	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
 	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
 	select REGMAP_SPI
 	select ZL3073X
 	help
diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
index 4ec2cf53dad46..bc6d70d25d791 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>
 
@@ -162,8 +163,8 @@ int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index)
  * @zldev: pointer to zl3073x_dev structure
  * @index: DPLL channel index to fetch state for
  *
- * Reads the mode_refsel register and reference priority registers for
- * the given DPLL channel and stores the raw values for later use.
+ * Reads the mode_refsel, status and reference priority registers for
+ * the given DPLL channel and stores the values for later use.
  *
  * Return: 0 on success, <0 on error
  */
@@ -234,6 +235,326 @@ 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
+ *
+ * Checks the ToD control register semaphore bit. If clear, returns
+ * immediately. Otherwise polls until the bit is cleared by the device.
+ *
+ * Return:
+ * * 0			- success
+ * * %-EBUSY		- timeout
+ * * %-EOPNOTSUPP	- unknown command detected
+ * * negative		- other error
+ */
+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;
+
+	if (!(tod_ctrl & ZL_DPLL_TOD_CTRL_SEM))
+		return 0;
+
+	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;
+	case ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT:
+	case ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ:
+		timeout = ZL_POLL_TOD_RD_TIMEOUT_US;
+		break;
+	default:
+		WARN_ON(1);
+		return -EOPNOTSUPP;
+	}
+
+	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.
+ * Re-reads if the 1 Hz tick crossed between the two reads or if less
+ * than 20 ms remains before the next rollover. 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)
+{
+#define ZL_TOD_MAX_RETRIES	20
+	static const long threshold_ns = 20 * NSEC_PER_MSEC;
+	struct timespec64 ts_next, ts_cur, diff;
+	int rc, i;
+
+	for (i = 0; i < ZL_TOD_MAX_RETRIES; i++) {
+		rc = zl3073x_chan_tod_read(zldev, ch, true, &ts_next, NULL);
+		if (rc)
+			return rc;
+
+		rc = zl3073x_chan_tod_read(zldev, ch, false, &ts_cur, NULL);
+		if (rc)
+			return rc;
+
+		/* Ensure the 1 Hz tick did not cross between the two reads
+		 * and that enough margin remains to complete the write.
+		 */
+		diff = timespec64_sub(ts_next, ts_cur);
+		if (diff.tv_sec > 0 ||
+		    (!diff.tv_sec && diff.tv_nsec >= threshold_ns))
+			break;
+	}
+	if (i == ZL_TOD_MAX_RETRIES) {
+		dev_warn(zldev->dev,
+			 "DPLL%u ToD adjust failed to get stable margin\n",
+			 ch);
+		return -EBUSY;
+	}
+
+	/* Apply delta to the next-Hz ToD */
+	ts_next = timespec64_add(ts_next, delta);
+	if (!timespec64_valid(&ts_next))
+		return -EINVAL;
+
+	return zl3073x_chan_tod_write(zldev, ch, ts_next);
+#undef ZL_TOD_MAX_RETRIES
+}
+
+/**
+ * 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
+ *
+ * Context: Caller must hold the per-DPLL lock.
+ * 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 in (-1s, 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;
+
+	if (WARN_ON(delta_ns <= -NSEC_PER_SEC || delta_ns >= NSEC_PER_SEC))
+		return -ERANGE;
+
+	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 dc9c6d95bdee7..99c3b7088f678 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;
 
 /**
@@ -42,6 +44,21 @@ 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_ready_wait(struct zl3073x_dev *zldev, u8 ch);
+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 +217,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
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 5b2d77f2c2288..230df08e27cd1 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -322,7 +322,7 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val)
 int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
 			 u8 mask, unsigned int timeout_us)
 {
-#define ZL_POLL_SLEEP_US 10
+	unsigned int sleep_us = timeout_us / 50;
 	unsigned int val;
 
 	/* Check the register is 8bit */
@@ -336,7 +336,7 @@ int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
 	reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;
 
 	return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask),
-					ZL_POLL_SLEEP_US, timeout_us);
+					sleep_us, timeout_us);
 }
 
 int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
@@ -511,6 +511,11 @@ zl3073x_dev_state_fetch(struct zl3073x_dev *zldev)
 	int rc;
 	u8 i;
 
+	rc = zl3073x_read_u16(zldev, ZL_REG_OUTPUT_STEP_TIME_MASK,
+			      &zldev->out_step_time_mask);
+	if (rc)
+		return rc;
+
 	for (i = 0; i < ZL3073X_NUM_REFS; i++) {
 		rc = zl3073x_ref_state_fetch(zldev, i);
 		if (rc) {
@@ -1034,6 +1039,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 78dc208f3eea2..67c10e2595118 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
@@ -63,6 +69,7 @@ struct zl3073x_chip_info {
  * @kworker: thread for periodic work
  * @work: periodic work
  * @clock_id: clock id of the device
+ * @out_step_time_mask: output step-time mask (device-global)
  * @phase_avg_factor: phase offset measurement averaging factor
  * @freq_monitor: is frequency monitor enabled
  */
@@ -71,6 +78,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];
@@ -87,6 +96,7 @@ struct zl3073x_dev {
 
 	/* Per-chip parameters */
 	u64			clock_id;
+	u16			out_step_time_mask;
 	u8			phase_avg_factor;
 	bool			freq_monitor;
 };
@@ -308,6 +318,19 @@ zl3073x_dev_out_is_enabled(struct zl3073x_dev *zldev, u8 index)
 	return zl3073x_synth_is_enabled(synth) && zl3073x_out_is_enabled(out);
 }
 
+/**
+ * zl3073x_dev_out_is_stepped - check if output is in step-time mask
+ * @zldev: pointer to zl3073x device
+ * @index: output index
+ *
+ * Return: true if output is affected by step-time operations
+ */
+static inline bool
+zl3073x_dev_out_is_stepped(struct zl3073x_dev *zldev, u8 index)
+{
+	return !!(zldev->out_step_time_mask & BIT(index));
+}
+
 /**
  * zl3073x_dev_out_dpll_get - get DPLL ID the output is driven by
  * @zldev: pointer to zl3073x device
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 0488ae6ac486c..8301ad343658a 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>
@@ -13,6 +14,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>
 
@@ -2275,45 +2277,384 @@ zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev)
 	return zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_CTRL, 0x01);
 }
 
+/* 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 first enabled synth's period for the conversion).
+ *
+ * Return:
+ * * %0			- success (or partial success if a later synth group
+ *			  failed after the first was already stepped)
+ * * %-EOPNOTSUPP	- no synths available
+ * * negative		- 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;
+	struct zl3073x_dpll_pin *pin;
+	u32 first_synth_freq = 0;
+	bool tod_stepped = false;
+	s32 step_cycles;
+	u32 synth_freq;
+	int rc;
+	u8 i;
+
+	/* 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_dev_out_is_stepped(zldev, 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 first enabled 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
+	 * first enabled 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 or TIE write
+ * fails. 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.
+ *
+ * If the seconds part was already committed when the sub-second
+ * mechanism fails, returns 0 to prevent the PTP servo from retrying
+ * the full delta and applying seconds again.
+ *
+ * Return: 0 on success (or partial 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 (delta >= NSEC_PER_SEC || 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;
+
+		/* No sub-second remainder, done */
+		if (!delta)
+			return 0;
+
+		/* Wait for the ToD write to be applied at the 1 Hz edge
+		 * before issuing phase step or TIE write, so the pending
+		 * WR_NEXT_1HZ does not overwrite the sub-second adjustment.
+		 */
+		rc = zl3073x_chan_tod_ready_wait(zldev, zldpll->id);
+		if (rc)
+			return rc;
+
+		sec_adjusted = true;
+	}
+
+	/* 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			- success or @scaled_ppm is zero (no-op)
+ * * %-EOPNOTSUPP	- NCO pin is not connected and @scaled_ppm is non-zero
+ * * negative		- other 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			- success or @delta is zero (no-op)
+ * * %-EOPNOTSUPP	- mode does not support TIE and @delta is non-zero
+ * * negative		- other 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;
+}
+
+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,
+};
+
+/**
+ * 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;
+
+	zldpll->ptp_info = zl3073x_dpll_ptp_clock_info;
+	snprintf(zldpll->ptp_info.name, sizeof(zldpll->ptp_info.name),
+		 "%s-dpll%u", dev_name(zldev->dev), 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;
+	}
 }
 
 /**
@@ -2398,6 +2739,47 @@ 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;
+	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
@@ -2428,6 +2810,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;
 }
 
@@ -2441,7 +2830,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 faebc402ba1b7..993221dc63249 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -5,6 +5,7 @@
 
 #include <linux/dpll.h>
 #include <linux/list.h>
+#include <linux/ptp_clock_kernel.h>
 
 #include "core.h"
 
@@ -22,6 +23,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 +39,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 b70ead7d4495b..f3a5e1215aa36 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, ZL3073X_MAX_CHANNELS, 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,23 @@
 #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)
+
+#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 10, Ref Mailbox
  *******************************/

3/2 2026/08/11 14:02 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18231
							output: 26
							thoughts: 788
						

Thoughts:
**Reaching the PTP Clock in QEMU**

Okay, so I'm looking at this patch for the `zl3073x` DPLL driver, and it's adding PTP clock support. It seems pretty straightforward: it modifies `Kconfig` to add a dependency on `PTP_1588_CLOCK`, and then adds all these PTP-related functions: read/write/adjust TOD, set frequency offsets, control tie and phase steps, and finally it registers the PTP clock itself. The standard operations like getting and setting the time, and adjusting time/phase are also there.

My immediate question, as always, is: can I actually *reach* this in QEMU? This driver is for a Microchip Azurite DPLL/PTP/SyncE device, and I recall it has both I2C and SPI bus implementations. So, that's good; I2C and SPI devices are *usually* reachable in a virtualized environment. I can typically instantiate them through syzkaller's pseudo-syscalls or via sysfs/device tree, or by directly fuzzing the I2C or SPI interfaces.

Wait, syzkaller does support I2C and SPI fuzzing. I need to see if there are any specific syzkaller descriptions for `zl3073x`, but if not, I can just rely on the generic I2C/SPI fuzzing mechanisms. I think there is `i2c_new_client_device` in kernel.

Hang on… the prompt specifies code unreachable in virtualized environments. It mentions PCIe hardware, which is usually a pain. But this is an I2C/SPI driver! That's good, because I2C and SPI are definitely emulatable or fuzzeable in QEMU. Syzkaller has stuff like `syz_i2c_device` or can fuzz through `/dev/i2c-*` and `/dev/spidev*`, so that's all good.

The key question is, how is this driver probed? I need to check how the driver is registered with the I2C or SPI subsystem. That's the critical step to make sure I can even get to the PTP functions.



4/2 2026/08/11 14:02 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/08/11 14:02 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19783
							output: 26
							thoughts: 18
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Investigating Potential Data Sheet Mismatches**

Right, let's get down to brass tacks. First, my immediate concern is a potential mismatch between the data sheet and the actual hardware. I need to quickly determine if there’s a comprehensive "OF Match Table" or an "I2C ID Table" that clearly outlines the expected device identities and their corresponding Open Firmware (OF) bindings or I2C addresses. This is standard preliminary due diligence. A comprehensive table streamlines any debugging effort, offering a single source of truth for device identification and resource allocation. If it's there, great! It’ll save some time. If not, it means I need to manually correlate data sheet information to actual on-board responses – and that’ll add complexity to the process. Time to dig in and find those answers.



6/2 2026/08/11 14:02 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/08/11 14:02 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20434
							output: 41
							thoughts: 552
						

Thoughts:
**Analyzing the Integration of PTP Clock Support in the ZL3073X Driver**

Okay, so I see that this patch adds PTP clock functionality to the ZL3073X driver. The key here is that I2C devices can be created in userspace using `/sys/bus/i2c/devices/i2c-*/new_device`, allowing for probing the driver within QEMU, which is great for testing. Once probed, the driver registers a PTP clock, which is crucial. These PTP clocks are then exposed via `/dev/ptp*` and controlled via standard `ioctl`s like `PTP_CLOCK_GETCAPS`, `PTP_EXTTS_REQUEST`, `PTP_PEROUT_REQUEST`, and the like.

The patch itself specifically implements these user-accessible functions: `gettimex64`, `settime64`, `adjtime`, `adjfine`, and `adjphase`. These map directly to PTP `ioctl`s. We have to make sure the user space applications are configured to use these commands.

Looking at the specific functions added, namely `zl3073x_chan_tod_read`, `zl3073x_chan_tod_write`, `zl3073x_chan_tod_adjust`, `zl3073x_chan_df_offset_set`, `zl3073x_chan_tie_write`, `zl3073x_chan_phase_step`, `zl3073x_dpll_ptp_gettimex64`, `zl3073x_dpll_ptp_settime64`, `zl3073x_dpll_ptp_adjtime`, `zl3073x_dpll_ptp_adjfine`, and `zl3073x_dpll_ptp_adjphase`, they are clearly part of the PTP clock interface. This confirms the functional changes to the kernel.

The driver is `ZL3073X`, and it relies on both `NET` and `PTP_1588_CLOCK`. The config options I need to verify are: `ZL3073X`, `ZL3073X_I2C`, `ZL3073X_SPI`, and `PTP_1588_CLOCK`. Let me just double-check that `ZL3073X` correctly depends on `NET` and `PTP_1588_CLOCK`. Time to check `drivers/dpll/zl3073x/Kconfig` for the dependencies.



8/2 2026/08/11 14:02 tool read-file
0m Args:
{
  "File": "drivers/dpll/zl3073x/Kconfig",
  "FirstLine": 1,
  "LineCount": 50
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0-only\n   2:\t\n   3:\tconfig ZL3073X\n   4:\t\ttristate \"Microchip Azurite DPLL/PTP/SyncE devices\" if COMPILE_TEST\n   5:\t\tdepends on NET \u0026\u0026 PTP_1588_CLOCK\n   6:\t\tselect DPLL\n   7:\t\tselect NET_DEVLINK\n   8:\t\tselect REGMAP\n   9:\t\thelp\n  10:\t\t  This driver supports Microchip Azurite family DPLL/PTP/SyncE\n  11:\t\t  devices that support up to 5 independent DPLL channels,\n  12:\t\t  10 input pins and up to 20 output pins.\n  13:\t\n  14:\t\t  To compile this driver as a module, choose M here. The module\n  15:\t\t  will be called zl3073x.\n  16:\t\n  17:\tconfig ZL3073X_I2C\n  18:\t\ttristate \"I2C bus implementation for Microchip Azurite devices\"\n  19:\t\tdepends on I2C \u0026\u0026 NET \u0026\u0026 PTP_1588_CLOCK\n  20:\t\tselect REGMAP_I2C\n  21:\t\tselect ZL3073X\n  22:\t\thelp\n  23:\t\t  This is I2C bus implementation for Microchip Azurite DPLL/PTP/SyncE\n  24:\t\t  devices.\n  25:\t\n  26:\t\t  To compile this driver as a module, choose M here: the module will\n  27:\t\t  be called zl3073x_i2c.\n  28:\t\n  29:\tconfig ZL3073X_SPI\n  30:\t\ttristate \"SPI bus implementation for Microchip Azurite devices\"\n  31:\t\tdepends on NET \u0026\u0026 SPI \u0026\u0026 PTP_1588_CLOCK\n  32:\t\tselect REGMAP_SPI\n  33:\t\tselect ZL3073X\n  34:\t\thelp\n  35:\t\t  This is SPI bus implementation for Microchip Azurite DPLL/PTP/SyncE\n  36:\t\t  devices.\n  37:\t\n  38:\t\t  To compile this driver as a module, choose M here: the module will\n  39:\t\t  be called zl3073x_spi.\n"
}

9/2 2026/08/11 14:02 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22216
							output: 343
							thoughts: 1458
						

Thoughts:
**Analyzing the Microchip Azurite Driver Configuration and API**

Okay, let's break this down. First, I need to figure out which kernel configurations are essential for this Azurite DPLL/PTP/SyncE device support. Initially, I thought it was a straightforward list: `ZL3073X`, `ZL3073X_I2C`, `ZL3073X_SPI`, `PTP_1588_CLOCK`, `DPLL`, `NET_DEVLINK`, `REGMAP`, `REGMAP_I2C`, and `REGMAP_SPI`. But wait, `ZL3073X` is actually selected by both `ZL3073X_I2C` and `ZL3073X_SPI`. So, directly enabling `ZL3073X` might be unnecessary; enabling the I2C and SPI configurations should suffice, right?

Then I notice `ZL3073X` is tristate and only directly enabled if `COMPILE_TEST` is set. Given this, directly enabling `ZL3073X` seems to be off the table, as `ZL3073X_I2C` already selects it and I want to avoid the `COMPILE_TEST` requirement. So, I will focus on enabling `ZL3073X_I2C` and `ZL3073X_SPI`. And, of course, the `PTP_1588_CLOCK` is a must-have.

Next, I need to identify the crucial API functions I should focus on. I've got a list here: `zl3073x_chan_tod_read`, `zl3073x_chan_tod_write`, `zl3073x_chan_tod_adjust`, `zl3073x_chan_df_offset_set`, `zl3073x_chan_tie_write`, `zl3073x_chan_phase_step`, `zl3073x_dpll_ptp_gettimex64`, `zl3073x_dpll_ptp_settime64`, `zl3073x_dpll_ptp_adjtime`, `zl3073x_dpll_ptp_adjfine`, and `zl3073x_dpll_ptp_adjphase`.

I need to confirm if these functions are static or not. Examining them, it turns out that `zl3073x_chan_tod_read`, `zl3073x_chan_tod_write`, `zl3073x_chan_tod_adjust`, `zl3073x_chan_df_offset_set`, `zl3073x_chan_tie_write`, and `zl3073x_chan_phase_step` are *not* static, while the `zl3073x_dpll_*` functions are all static.

Wait a minute, since the `zl3073x_dpll_*` functions are used as function pointers within `ptp_clock_info`, they *cannot* be inlined, so the compiler *must* have generated individual symbols for these functions. They are entry points for PTP subsystem, so I will include them.

So, the definitive list of functions I will include is: `zl3073x_chan_tod_read`, `zl3073x_chan_tod_write`, `zl3073x_chan_tod_adjust`, `zl3073x_chan_df_offset_set`, `zl3073x_chan_tie_write`, `zl3073x_chan_phase_step`, `zl3073x_dpll_ptp_gettimex64`, `zl3073x_dpll_ptp_settime64`, `zl3073x_dpll_ptp_adjtime`, `zl3073x_dpll_ptp_adjfine`, and `zl3073x_dpll_ptp_adjphase`.

Now, regarding the configurations, I'm confident about: `ZL3073X_I2C`, `ZL3073X_SPI`, and `PTP_1588_CLOCK`.

I'll quickly check for dependencies. `ZL3073X_I2C` requires `I2C`, `NET`, and `PTP_1588


10/2 2026/08/11 14:03 tool set-results
0m Args:
{
  "EnableConfigs": [
    "ZL3073X_I2C",
    "ZL3073X_SPI",
    "PTP_1588_CLOCK"
  ],
  "FocusSymbols": [
    "zl3073x_chan_tod_read",
    "zl3073x_chan_tod_write",
    "zl3073x_chan_tod_adjust",
    "zl3073x_chan_df_offset_set",
    "zl3073x_chan_tie_write",
    "zl3073x_chan_phase_step",
    "zl3073x_dpll_ptp_gettimex64",
    "zl3073x_dpll_ptp_settime64",
    "zl3073x_dpll_ptp_adjtime",
    "zl3073x_dpll_ptp_adjfine",
    "zl3073x_dpll_ptp_adjphase"
  ],
  "Reasoning": "The patch adds PTP clock support to the Microchip Azurite DPLL driver (zl3073x). It implements PTP clock operations like gettimex64, settime64, adjtime, adjfine, and adjphase. The driver can be instantiated via I2C or SPI in QEMU, and the PTP operations are reachable from userspace via PTP ioctls.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "ZL3073X_I2C",
    "ZL3073X_SPI",
    "PTP_1588_CLOCK"
  ],
  "FocusSymbols": [
    "zl3073x_chan_tod_read",
    "zl3073x_chan_tod_write",
    "zl3073x_chan_tod_adjust",
    "zl3073x_chan_df_offset_set",
    "zl3073x_chan_tie_write",
    "zl3073x_chan_phase_step",
    "zl3073x_dpll_ptp_gettimex64",
    "zl3073x_dpll_ptp_settime64",
    "zl3073x_dpll_ptp_adjtime",
    "zl3073x_dpll_ptp_adjfine",
    "zl3073x_dpll_ptp_adjphase"
  ],
  "Reasoning": "The patch adds PTP clock support to the Microchip Azurite DPLL driver (zl3073x). It implements PTP clock operations like gettimex64, settime64, adjtime, adjfine, and adjphase. The driver can be instantiated via I2C or SPI in QEMU, and the PTP operations are reachable from userspace via PTP ioctls.",
  "WorthFuzzing": true
}

11/1 2026/08/11 14:03 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

12/2 2026/08/11 14:03 agent kmsan-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "KMSANReasoning": "The patch adds PTP clock support to the Microchip Azurite DPLL/PTP/SyncE devices driver. It introduces functions to read/write Time of Day (ToD), adjust phase, adjust frequency, and register a PTP clock device. The driver interacts with the PTP subsystem by providing callbacks (`gettimex64`, `settime64`, `adjtime`, `adjfine`, `adjphase`). All structures allocated dynamically are zero-initialized using `kzalloc_obj`. The `ptp_clock_info` structure is copied from a static const template, ensuring all uninitialized fields are zeroed by the compiler before `snprintf` modifies the `name` field. The `struct timespec64` populated by the driver is fully initialized on success, and errors are properly propagated to the PTP subsystem, preventing uninitialized data from being used or copied to userspace. No new uninitialized memory risks or info-leaks are introduced.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
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 957dcfd671e8bc597719c12cb2b301d2f749fed1
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Aug 11 14:02:22 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/dpll/zl3073x/Kconfig b/drivers/dpll/zl3073x/Kconfig
index 5bbca14005813..da1731ace15f0 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
 	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
 	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
 	select REGMAP_SPI
 	select ZL3073X
 	help
diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
index 4ec2cf53dad46..bc6d70d25d791 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>
 
@@ -162,8 +163,8 @@ int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index)
  * @zldev: pointer to zl3073x_dev structure
  * @index: DPLL channel index to fetch state for
  *
- * Reads the mode_refsel register and reference priority registers for
- * the given DPLL channel and stores the raw values for later use.
+ * Reads the mode_refsel, status and reference priority registers for
+ * the given DPLL channel and stores the values for later use.
  *
  * Return: 0 on success, <0 on error
  */
@@ -234,6 +235,326 @@ 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
+ *
+ * Checks the ToD control register semaphore bit. If clear, returns
+ * immediately. Otherwise polls until the bit is cleared by the device.
+ *
+ * Return:
+ * * 0			- success
+ * * %-EBUSY		- timeout
+ * * %-EOPNOTSUPP	- unknown command detected
+ * * negative		- other error
+ */
+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;
+
+	if (!(tod_ctrl & ZL_DPLL_TOD_CTRL_SEM))
+		return 0;
+
+	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;
+	case ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT:
+	case ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ:
+		timeout = ZL_POLL_TOD_RD_TIMEOUT_US;
+		break;
+	default:
+		WARN_ON(1);
+		return -EOPNOTSUPP;
+	}
+
+	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.
+ * Re-reads if the 1 Hz tick crossed between the two reads or if less
+ * than 20 ms remains before the next rollover. 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)
+{
+#define ZL_TOD_MAX_RETRIES	20
+	static const long threshold_ns = 20 * NSEC_PER_MSEC;
+	struct timespec64 ts_next, ts_cur, diff;
+	int rc, i;
+
+	for (i = 0; i < ZL_TOD_MAX_RETRIES; i++) {
+		rc = zl3073x_chan_tod_read(zldev, ch, true, &ts_next, NULL);
+		if (rc)
+			return rc;
+
+		rc = zl3073x_chan_tod_read(zldev, ch, false, &ts_cur, NULL);
+		if (rc)
+			return rc;
+
+		/* Ensure the 1 Hz tick did not cross between the two reads
+		 * and that enough margin remains to complete the write.
+		 */
+		diff = timespec64_sub(ts_next, ts_cur);
+		if (diff.tv_sec > 0 ||
+		    (!diff.tv_sec && diff.tv_nsec >= threshold_ns))
+			break;
+	}
+	if (i == ZL_TOD_MAX_RETRIES) {
+		dev_warn(zldev->dev,
+			 "DPLL%u ToD adjust failed to get stable margin\n",
+			 ch);
+		return -EBUSY;
+	}
+
+	/* Apply delta to the next-Hz ToD */
+	ts_next = timespec64_add(ts_next, delta);
+	if (!timespec64_valid(&ts_next))
+		return -EINVAL;
+
+	return zl3073x_chan_tod_write(zldev, ch, ts_next);
+#undef ZL_TOD_MAX_RETRIES
+}
+
+/**
+ * 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
+ *
+ * Context: Caller must hold the per-DPLL lock.
+ * 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 in (-1s, 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;
+
+	if (WARN_ON(delta_ns <= -NSEC_PER_SEC || delta_ns >= NSEC_PER_SEC))
+		return -ERANGE;
+
+	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 dc9c6d95bdee7..99c3b7088f678 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;
 
 /**
@@ -42,6 +44,21 @@ 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_ready_wait(struct zl3073x_dev *zldev, u8 ch);
+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 +217,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
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 5b2d77f2c2288..230df08e27cd1 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -322,7 +322,7 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val)
 int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
 			 u8 mask, unsigned int timeout_us)
 {
-#define ZL_POLL_SLEEP_US 10
+	unsigned int sleep_us = timeout_us / 50;
 	unsigned int val;
 
 	/* Check the register is 8bit */
@@ -336,7 +336,7 @@ int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
 	reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;
 
 	return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask),
-					ZL_POLL_SLEEP_US, timeout_us);
+					sleep_us, timeout_us);
 }
 
 int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
@@ -511,6 +511,11 @@ zl3073x_dev_state_fetch(struct zl3073x_dev *zldev)
 	int rc;
 	u8 i;
 
+	rc = zl3073x_read_u16(zldev, ZL_REG_OUTPUT_STEP_TIME_MASK,
+			      &zldev->out_step_time_mask);
+	if (rc)
+		return rc;
+
 	for (i = 0; i < ZL3073X_NUM_REFS; i++) {
 		rc = zl3073x_ref_state_fetch(zldev, i);
 		if (rc) {
@@ -1034,6 +1039,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 78dc208f3eea2..67c10e2595118 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
@@ -63,6 +69,7 @@ struct zl3073x_chip_info {
  * @kworker: thread for periodic work
  * @work: periodic work
  * @clock_id: clock id of the device
+ * @out_step_time_mask: output step-time mask (device-global)
  * @phase_avg_factor: phase offset measurement averaging factor
  * @freq_monitor: is frequency monitor enabled
  */
@@ -71,6 +78,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];
@@ -87,6 +96,7 @@ struct zl3073x_dev {
 
 	/* Per-chip parameters */
 	u64			clock_id;
+	u16			out_step_time_mask;
 	u8			phase_avg_factor;
 	bool			freq_monitor;
 };
@@ -308,6 +318,19 @@ zl3073x_dev_out_is_enabled(struct zl3073x_dev *zldev, u8 index)
 	return zl3073x_synth_is_enabled(synth) && zl3073x_out_is_enabled(out);
 }
 
+/**
+ * zl3073x_dev_out_is_stepped - check if output is in step-time mask
+ * @zldev: pointer to zl3073x device
+ * @index: output index
+ *
+ * Return: true if output is affected by step-time operations
+ */
+static inline bool
+zl3073x_dev_out_is_stepped(struct zl3073x_dev *zldev, u8 index)
+{
+	return !!(zldev->out_step_time_mask & BIT(index));
+}
+
 /**
  * zl3073x_dev_out_dpll_get - get DPLL ID the output is driven by
  * @zldev: pointer to zl3073x device
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 0488ae6ac486c..8301ad343658a 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>
@@ -13,6 +14,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>
 
@@ -2275,45 +2277,384 @@ zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev)
 	return zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_CTRL, 0x01);
 }
 
+/* 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 first enabled synth's period for the conversion).
+ *
+ * Return:
+ * * %0			- success (or partial success if a later synth group
+ *			  failed after the first was already stepped)
+ * * %-EOPNOTSUPP	- no synths available
+ * * negative		- 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;
+	struct zl3073x_dpll_pin *pin;
+	u32 first_synth_freq = 0;
+	bool tod_stepped = false;
+	s32 step_cycles;
+	u32 synth_freq;
+	int rc;
+	u8 i;
+
+	/* 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_dev_out_is_stepped(zldev, 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 first enabled 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
+	 * first enabled 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 or TIE write
+ * fails. 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.
+ *
+ * If the seconds part was already committed when the sub-second
+ * mechanism fails, returns 0 to prevent the PTP servo from retrying
+ * the full delta and applying seconds again.
+ *
+ * Return: 0 on success (or partial 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 (delta >= NSEC_PER_SEC || 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;
+
+		/* No sub-second remainder, done */
+		if (!delta)
+			return 0;
+
+		/* Wait for the ToD write to be applied at the 1 Hz edge
+		 * before issuing phase step or TIE write, so the pending
+		 * WR_NEXT_1HZ does not overwrite the sub-second adjustment.
+		 */
+		rc = zl3073x_chan_tod_ready_wait(zldev, zldpll->id);
+		if (rc)
+			return rc;
+
+		sec_adjusted = true;
+	}
+
+	/* 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			- success or @scaled_ppm is zero (no-op)
+ * * %-EOPNOTSUPP	- NCO pin is not connected and @scaled_ppm is non-zero
+ * * negative		- other 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			- success or @delta is zero (no-op)
+ * * %-EOPNOTSUPP	- mode does not support TIE and @delta is non-zero
+ * * negative		- other 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;
+}
+
+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,
+};
+
+/**
+ * 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;
+
+	zldpll->ptp_info = zl3073x_dpll_ptp_clock_info;
+	snprintf(zldpll->ptp_info.name, sizeof(zldpll->ptp_info.name),
+		 "%s-dpll%u", dev_name(zldev->dev), 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;
+	}
 }
 
 /**
@@ -2398,6 +2739,47 @@ 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;
+	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
@@ -2428,6 +2810,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;
 }
 
@@ -2441,7 +2830,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 faebc402ba1b7..993221dc63249 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -5,6 +5,7 @@
 
 #include <linux/dpll.h>
 #include <linux/list.h>
+#include <linux/ptp_clock_kernel.h>
 
 #include "core.h"
 
@@ -22,6 +23,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 +39,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 b70ead7d4495b..f3a5e1215aa36 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, ZL3073X_MAX_CHANNELS, 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,23 @@
 #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)
+
+#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 10, Ref Mailbox
  *******************************/

13/3 2026/08/11 14:03 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18228
							output: 18
							thoughts: 1883
						

Thoughts:
**PTP Clock Support in the Azurite DPLL Driver: A Memory Safety Review**

Alright, let's dissect this patch adding PTP clock support for the Microchip Azurite DPLL/PTP/SyncE devices. The core idea is to introduce functions for reading and writing Time of Day (ToD), adjusting phase and frequency, and registering a PTP clock device. The interaction with the hardware is standard fare: reading and writing device registers via I2C or SPI using the `regmap` framework. The patch leverages the existing PTP clock subsystem, passing data through it, so the driver only needs to worry about the callbacks. The subsystem handles the `ioctl` calls and translates them into `timespec64` or similar structures that the driver can understand.

My immediate focus is on memory safety; specifically, I need to make sure we're not inadvertently leaking uninitialized memory. Let's start with `zl3073x_chan_tod_read`. This function populates a `struct timespec64` pointed to by `ts`. The function reads seconds and nanoseconds from device registers. If the read operations, `zl3073x_read_u48` or `zl3073x_read_u32`, fail, it returns an error, and the `ts` is not fully initialized. However, the calling function `zl3073x_dpll_ptp_gettimex64` propagates the error. The PTP subsystem handles the error and doesn't pass a partially initialized `ts` to user space, which is good. No uninitialized memory leak in that case.

Then, I'm checking `zl3073x_dpll_ptp_adjtime_phase_step`. Here, `synth_mask` is initialized to zero, which is good. It looks like it is safe.

Next I review `zl3073x_chan_tod_adjust`. The function reads `ts_next` and `ts_cur` with `zl3073x_chan_tod_read`. The variables are fully initialized on success. So it looks safe.

The registration of the PTP clock device is an important area to review for memory safety. The patch defines `zl3073x_dpll_ptp_clock_info`, a `ptp_clock_info` struct. The driver copies the contents of `zl3073x_dpll_ptp_clock_info` into a local `ptp_info` struct, and then overwrites the name field with a call to `snprintf`. I need to ensure there are no uninitialized memory leaks. Looking at the definition, the `name` field is a `char name[16]` array. Does `snprintf` zero-pad the rest of the array? I remember that `snprintf` will not. If `zl3073x_dpll_ptp_clock_info.name` wasn't fully initialized, the uninitialized memory could be leaked. However, `zl3073x_dpll_ptp_clock_info` is `static const`, so it is zero initialized by the compiler. It is zero-padded, and then `snprintf` overwrites some of the zero values, but the remaining part is zero, so no leak.

Finally, `zl3073x_dpll_alloc`. The code uses `kzalloc_obj`, which looks suspicious. Is it a kernel macro, or a typo? I suspect that it is a macro for `kzalloc(sizeof(*zldpll), GFP_KERNEL)`. Since the macro name contains `zalloc`, it should zero the memory. I need to make sure this is the case. Let me check the definition of `kzalloc_obj`.

Overall, this looks like a well-structured patch with attention to memory safety. My spot checks haven't revealed any immediate uninitialized memory risks, but I will investigate `kzalloc_obj` further.



14/3 2026/08/11 14:03 tool grepper
0m Args:
{
  "Expression": "kzalloc_obj"
}

Results:
{
  "Output": "\nFull output is too long, showing 500 out of 79279 lines.\nUse more precise expression if possible.\n\nDocumentation/core-api/list.rst=103=list:\n--\nDocumentation/core-api/list.rst-114-\nDocumentation/core-api/list.rst:115:          grock = kzalloc_obj(*grock);\nDocumentation/core-api/list.rst-116-          if (!grock)\n--\nDocumentation/core-api/list.rst-125-\nDocumentation/core-api/list.rst:126:          dimitri = kzalloc_obj(*dimitri);\nDocumentation/core-api/list.rst-127-          if (!dimitri)\n--\nDocumentation/driver-api/mailbox.rst=45=a message and a callback function to the API and return immediately).\n--\nDocumentation/driver-api/mailbox.rst-89-\nDocumentation/driver-api/mailbox.rst:90:\t\tdc_sync = kzalloc_obj(*dc_sync);\nDocumentation/driver-api/mailbox.rst:91:\t\tdc_async = kzalloc_obj(*dc_async);\nDocumentation/driver-api/mailbox.rst-92-\n--\nDocumentation/driver-api/media/v4l2-fh.rst=26=Example:\n--\nDocumentation/driver-api/media/v4l2-fh.rst-44-\nDocumentation/driver-api/media/v4l2-fh.rst:45:\t\tmy_fh = kzalloc_obj(*my_fh);\nDocumentation/driver-api/media/v4l2-fh.rst-46-\n--\nDocumentation/process/coding-style.rst=938=The kernel provides the following general purpose memory allocators:\nDocumentation/process/coding-style.rst:939:kmalloc(), kzalloc(), kmalloc_objs(), kzalloc_objs(), vmalloc(), and\nDocumentation/process/coding-style.rst-940-vzalloc().  Please refer to the API documentation for further information\n--\nDocumentation/process/coding-style.rst=964=The preferred form for allocating a zeroed array is the following:\n--\nDocumentation/process/coding-style.rst-967-\nDocumentation/process/coding-style.rst:968:\tp = kzalloc_objs(*p, n, ...);\nDocumentation/process/coding-style.rst-969-\n--\nDocumentation/process/deprecated.rst=398=become, respectively::\n--\nDocumentation/process/deprecated.rst-400-\tptr = kmalloc_obj(*ptr [, gfp] );\nDocumentation/process/deprecated.rst:401:\tptr = kzalloc_obj(*ptr [, gfp] );\nDocumentation/process/deprecated.rst-402-\tptr = kmalloc_objs(*ptr, count [, gfp] );\nDocumentation/process/deprecated.rst:403:\tptr = kzalloc_objs(*ptr, count [, gfp] );\nDocumentation/process/deprecated.rst-404-\tptr = kmalloc_flex(*ptr, flex_member, count [, gfp] );\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=208=to details explained in the following section.\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-268-              /* allocate a chip-specific data with zero filled */\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:269:              chip = kzalloc_obj(*chip);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-270-              if (chip == NULL)\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=623=After allocating a card instance via :c:func:`snd_card_new()`\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-630-  .....\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:631:  chip = kzalloc_obj(*chip);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-632-\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=698=destructor and PCI entries. Example code is shown first, below::\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-749-\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:750:              chip = kzalloc_obj(*chip);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-751-              if (chip == NULL) {\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=3823=chip data individually::\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-3835-          ....\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:3836:          chip = kzalloc_obj(*chip);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-3837-          ....\n--\nDocumentation/translations/zh_CN/video4linux/v4l2-framework.txt=794=int my_open(struct file *file)\n--\nDocumentation/translations/zh_CN/video4linux/v4l2-framework.txt-801-\nDocumentation/translations/zh_CN/video4linux/v4l2-framework.txt:802:\tmy_fh = kzalloc_obj(*my_fh);\nDocumentation/translations/zh_CN/video4linux/v4l2-framework.txt-803-\n--\narch/alpha/kernel/module.c=64=module_frob_arch_sections(Elf64_Ehdr *hdr, Elf64_Shdr *sechdrs,\n--\narch/alpha/kernel/module.c-95-\tnsyms = symtab-\u003esh_size / sizeof(Elf64_Sym);\narch/alpha/kernel/module.c:96:\tchains = kzalloc_objs(struct got_entry, nsyms);\narch/alpha/kernel/module.c-97-\tif (!chains) {\n--\narch/alpha/kernel/setup.c=390=register_cpus(void)\n--\narch/alpha/kernel/setup.c-394-\tfor_each_possible_cpu(i) {\narch/alpha/kernel/setup.c:395:\t\tstruct cpu *p = kzalloc_obj(*p);\narch/alpha/kernel/setup.c-396-\t\tif (!p)\n--\narch/arc/net/bpf_jit_core.c=1120=static int jit_prepare_final_mem_alloc(struct jit_context *ctx)\n--\narch/arc/net/bpf_jit_core.c-1131-\tif (ctx-\u003eneed_extra_pass) {\narch/arc/net/bpf_jit_core.c:1132:\t\tctx-\u003ejit_data = kzalloc_obj(*ctx-\u003ejit_data);\narch/arc/net/bpf_jit_core.c-1133-\t\tif (!ctx-\u003ejit_data)\n--\narch/arm/common/locomo.c=220=locomo_init_one_child(struct locomo *lchip, struct locomo_dev_info *info)\n--\narch/arm/common/locomo.c-224-\narch/arm/common/locomo.c:225:\tdev = kzalloc_obj(struct locomo_dev);\narch/arm/common/locomo.c-226-\tif (!dev) {\n--\narch/arm/common/locomo.c=356=__locomo_probe(struct device *me, struct resource *mem, int irq)\n--\narch/arm/common/locomo.c-362-\narch/arm/common/locomo.c:363:\tlchip = kzalloc_obj(struct locomo);\narch/arm/common/locomo.c-364-\tif (!lchip)\n--\narch/arm/common/sa1111.c=733=sa1111_init_one_child(struct sa1111 *sachip, struct resource *parent,\n--\narch/arm/common/sa1111.c-739-\narch/arm/common/sa1111.c:740:\tdev = kzalloc_obj(struct sa1111_dev);\narch/arm/common/sa1111.c-741-\tif (!dev) {\n--\narch/arm/common/scoop.c=178=static int scoop_probe(struct platform_device *pdev)\n--\narch/arm/common/scoop.c-187-\narch/arm/common/scoop.c:188:\tdevptr = kzalloc_obj(struct scoop_dev);\narch/arm/common/scoop.c-189-\tif (!devptr)\n--\narch/arm/kernel/smp.c=108=static int secondary_biglittle_prepare(unsigned int cpu)\n--\narch/arm/kernel/smp.c-110-\tif (!cpu_vtable[cpu])\narch/arm/kernel/smp.c:111:\t\tcpu_vtable[cpu] = kzalloc_obj(*cpu_vtable[cpu]);\narch/arm/kernel/smp.c-112-\n--\narch/arm/kernel/vdso.c=169=static int __init vdso_init(void)\n--\narch/arm/kernel/vdso.c-181-\t/* Allocate the VDSO text pagelist */\narch/arm/kernel/vdso.c:182:\tvdso_text_pagelist = kzalloc_objs(struct page *, text_pages);\narch/arm/kernel/vdso.c-183-\tif (vdso_text_pagelist == NULL)\n--\narch/arm/mach-footbridge/dc21285.c=261=int __init dc21285_setup(int nr, struct pci_sys_data *sys)\n--\narch/arm/mach-footbridge/dc21285.c-264-\narch/arm/mach-footbridge/dc21285.c:265:\tres = kzalloc_objs(struct resource, 2);\narch/arm/mach-footbridge/dc21285.c-266-\tif (!res) {\n--\narch/arm/mach-footbridge/ebsa285.c=69=static int __init ebsa285_leds_init(void)\n--\narch/arm/mach-footbridge/ebsa285.c-86-\narch/arm/mach-footbridge/ebsa285.c:87:\t\tled = kzalloc_obj(*led);\narch/arm/mach-footbridge/ebsa285.c-88-\t\tif (!led)\n--\narch/arm/mach-footbridge/netwinder-hw.c=720=static int __init netwinder_leds_init(void)\n--\narch/arm/mach-footbridge/netwinder-hw.c-729-\narch/arm/mach-footbridge/netwinder-hw.c:730:\t\tled = kzalloc_obj(*led);\narch/arm/mach-footbridge/netwinder-hw.c-731-\t\tif (!led)\n--\narch/arm/mach-imx/mmdc.c=473=static int imx_mmdc_perf_init(struct platform_device *pdev, void __iomem *mmdc_base,\n--\narch/arm/mach-imx/mmdc.c-479-\narch/arm/mach-imx/mmdc.c:480:\tpmu_mmdc = kzalloc_obj(*pmu_mmdc);\narch/arm/mach-imx/mmdc.c-481-\tif (!pmu_mmdc) {\n--\narch/arm/mach-mvebu/board-v7.c=114=static void __init i2c_quirk(void)\n--\narch/arm/mach-mvebu/board-v7.c-129-\narch/arm/mach-mvebu/board-v7.c:130:\t\tnew_compat = kzalloc_obj(*new_compat);\narch/arm/mach-mvebu/board-v7.c-131-\n--\narch/arm/mach-mvebu/coherency.c=163=static void __init armada_375_380_coherency_init(struct device_node *np)\n--\narch/arm/mach-mvebu/coherency.c-187-\narch/arm/mach-mvebu/coherency.c:188:\t\tp = kzalloc_obj(*p);\narch/arm/mach-mvebu/coherency.c-189-\t\tp-\u003ename = kstrdup(\"arm,io-coherent\", GFP_KERNEL);\n--\narch/arm/mach-mvebu/mvebu-soc-id.c=148=static int __init mvebu_soc_device(void)\n--\narch/arm/mach-mvebu/mvebu-soc-id.c-156-\narch/arm/mach-mvebu/mvebu-soc-id.c:157:\tsoc_dev_attr = kzalloc_obj(*soc_dev_attr);\narch/arm/mach-mvebu/mvebu-soc-id.c-158-\tif (!soc_dev_attr)\n--\narch/arm/mach-mxs/mach-mxs.c=380=static void __init mxs_machine_init(void)\n--\narch/arm/mach-mxs/mach-mxs.c-389-\narch/arm/mach-mxs/mach-mxs.c:390:\tsoc_dev_attr = kzalloc_obj(*soc_dev_attr);\narch/arm/mach-mxs/mach-mxs.c-391-\tif (!soc_dev_attr)\n--\narch/arm/mach-omap1/dma.c=294=static int __init omap1_system_dma_init(void)\n--\narch/arm/mach-omap1/dma.c-321-\narch/arm/mach-omap1/dma.c:322:\td = kzalloc_obj(*d);\narch/arm/mach-omap1/dma.c-323-\tif (!d) {\n--\narch/arm/mach-omap1/mcbsp.c=292=static void omap_mcbsp_register_board_cfg(struct resource *res, int res_count,\n--\narch/arm/mach-omap1/mcbsp.c-296-\narch/arm/mach-omap1/mcbsp.c:297:\tomap_mcbsp_devices = kzalloc_objs(struct platform_device *, size);\narch/arm/mach-omap1/mcbsp.c-298-\tif (!omap_mcbsp_devices) {\n--\narch/arm/mach-omap1/timer.c=51=static int __init omap1_dm_timer_init(void)\n--\narch/arm/mach-omap1/timer.c-127-\narch/arm/mach-omap1/timer.c:128:\t\tpdata = kzalloc_obj(*pdata);\narch/arm/mach-omap1/timer.c-129-\t\tif (!pdata) {\n--\narch/arm/mach-omap2/clkt2xxx_virt_prcm_set.c=230=void omap2xxx_clkt_vps_init(void)\n--\narch/arm/mach-omap2/clkt2xxx_virt_prcm_set.c-239-\narch/arm/mach-omap2/clkt2xxx_virt_prcm_set.c:240:\thw = kzalloc_obj(*hw);\narch/arm/mach-omap2/clkt2xxx_virt_prcm_set.c-241-\tif (!hw)\n--\narch/arm/mach-omap2/id.c=786=void __init omap_soc_device_init(void)\n--\narch/arm/mach-omap2/id.c-790-\narch/arm/mach-omap2/id.c:791:\tsoc_dev_attr = kzalloc_obj(*soc_dev_attr);\narch/arm/mach-omap2/id.c-792-\tif (!soc_dev_attr)\n--\narch/arm/mach-omap2/omap_device.c=131=static int omap_device_build_from_dt(struct platform_device *pdev)\n--\narch/arm/mach-omap2/omap_device.c-158-\narch/arm/mach-omap2/omap_device.c:159:\thwmods = kzalloc_objs(struct omap_hwmod *, oh_cnt);\narch/arm/mach-omap2/omap_device.c-160-\tif (!hwmods) {\n--\narch/arm/mach-omap2/omap_hwmod.c=3381=static int omap_hwmod_allocate_module(struct device *dev, struct omap_hwmod *oh,\n--\narch/arm/mach-omap2/omap_hwmod.c-3394-\narch/arm/mach-omap2/omap_hwmod.c:3395:\tsysc = kzalloc_obj(*sysc);\narch/arm/mach-omap2/omap_hwmod.c-3396-\tif (!sysc)\n--\narch/arm/mach-omap2/omap_hwmod.c-3424-\tif (list_empty(\u0026oh-\u003eslave_ports)) {\narch/arm/mach-omap2/omap_hwmod.c:3425:\t\toi = kzalloc_obj(*oi);\narch/arm/mach-omap2/omap_hwmod.c-3426-\t\tif (!oi)\n--\narch/arm/mach-omap2/omap_hwmod.c=3513=int omap_hwmod_init_module(struct device *dev,\n--\narch/arm/mach-omap2/omap_hwmod.c-3527-\tif (!oh) {\narch/arm/mach-omap2/omap_hwmod.c:3528:\t\toh = kzalloc_obj(*oh);\narch/arm/mach-omap2/omap_hwmod.c-3529-\t\tif (!oh)\n--\narch/arm/mach-omap2/omap_hwmod.c-3538-\narch/arm/mach-omap2/omap_hwmod.c:3539:\t\toh-\u003eclass = kzalloc_obj(*oh-\u003eclass);\narch/arm/mach-omap2/omap_hwmod.c-3540-\t\tif (!oh-\u003eclass) {\n--\narch/arm/mach-omap2/pm33xx-core.c=379=static int __init amx3_idle_init(struct device_node *cpu_node, int cpu)\n--\narch/arm/mach-omap2/pm33xx-core.c-412-\narch/arm/mach-omap2/pm33xx-core.c:413:\tidle_states = kzalloc_objs(*idle_states, state_count);\narch/arm/mach-omap2/pm33xx-core.c-414-\tif (!idle_states)\n--\narch/arm/mach-omap2/sr_device.c=30=static void __init sr_set_nvalues(struct omap_volt_data *volt_data,\n--\narch/arm/mach-omap2/sr_device.c-41-\narch/arm/mach-omap2/sr_device.c:42:\tnvalue_table = kzalloc_objs(*nvalue_table, count);\narch/arm/mach-omap2/sr_device.c-43-\tif (!nvalue_table)\n--\narch/arm/mach-orion5x/pci.c=139=static int __init pcie_setup(struct pci_sys_data *sys)\n--\narch/arm/mach-orion5x/pci.c-171-\t */\narch/arm/mach-orion5x/pci.c:172:\tres = kzalloc_obj(struct resource);\narch/arm/mach-orion5x/pci.c-173-\tif (!res)\n--\narch/arm/mach-orion5x/pci.c=466=static int __init pci_setup(struct pci_sys_data *sys)\n--\narch/arm/mach-orion5x/pci.c-492-\t */\narch/arm/mach-orion5x/pci.c:493:\tres = kzalloc_obj(struct resource);\narch/arm/mach-orion5x/pci.c-494-\tif (!res)\n--\narch/arm/mach-rpc/ecard.c=689=static struct expansion_card *__init ecard_alloc_card(int type, int slot)\n--\narch/arm/mach-rpc/ecard.c-694-\narch/arm/mach-rpc/ecard.c:695:\tec = kzalloc_obj(ecard_t);\narch/arm/mach-rpc/ecard.c-696-\tif (!ec) {\n--\narch/arm/mach-sa1100/clock.c=93=int __init sa11xx_clk_init(void)\n--\narch/arm/mach-sa1100/clock.c-109-\narch/arm/mach-sa1100/clock.c:110:\thw = kzalloc_obj(*hw);\narch/arm/mach-sa1100/clock.c-111-\tif (!hw)\n--\narch/arm/mach-sa1100/clock.c-131-\narch/arm/mach-sa1100/clock.c:132:\thw = kzalloc_obj(*hw);\narch/arm/mach-sa1100/clock.c-133-\tif (!hw)\n--\narch/arm/mach-sa1100/generic.c=317=int __init sa11x0_register_fixed_regulator(int n,\n--\narch/arm/mach-sa1100/generic.c-323-\narch/arm/mach-sa1100/generic.c:324:\tcfg-\u003einit_data = id = kzalloc_obj(*cfg-\u003einit_data);\narch/arm/mach-sa1100/generic.c-325-\tif (!cfg-\u003einit_data)\n--\narch/arm/mach-sa1100/neponset.c=225=static int neponset_probe(struct platform_device *dev)\n--\narch/arm/mach-sa1100/neponset.c-278-\narch/arm/mach-sa1100/neponset.c:279:\td = kzalloc_obj(*d);\narch/arm/mach-sa1100/neponset.c-280-\tif (!d) {\n--\narch/arm/mach-shmobile/regulator-quirk-rcar-gen2.c=141=static int __init rcar_gen2_regulator_quirk(void)\n--\narch/arm/mach-shmobile/regulator-quirk-rcar-gen2.c-166-\narch/arm/mach-shmobile/regulator-quirk-rcar-gen2.c:167:\t\tquirk = kzalloc_obj(*quirk);\narch/arm/mach-shmobile/regulator-quirk-rcar-gen2.c-168-\t\tif (!quirk) {\n--\narch/arm/mach-versatile/spc.c=393=static int ve_spc_populate_opps(uint32_t cluster)\n--\narch/arm/mach-versatile/spc.c-397-\narch/arm/mach-versatile/spc.c:398:\topps = kzalloc_objs(*opps, MAX_OPPS);\narch/arm/mach-versatile/spc.c-399-\tif (!opps)\n--\narch/arm/mach-versatile/spc.c=442=int __init ve_spc_init(void __iomem *baseaddr, u32 a15_clusid, int irq)\n--\narch/arm/mach-versatile/spc.c-444-\tint ret;\narch/arm/mach-versatile/spc.c:445:\tinfo = kzalloc_obj(*info);\narch/arm/mach-versatile/spc.c-446-\tif (!info)\n--\narch/arm/mach-versatile/spc.c=523=static struct clk *ve_spc_clk_register(struct device *cpu_dev)\n--\narch/arm/mach-versatile/spc.c-527-\narch/arm/mach-versatile/spc.c:528:\tspc = kzalloc_obj(*spc);\narch/arm/mach-versatile/spc.c-529-\tif (!spc)\n--\narch/arm/mach-versatile/versatile.c=123=static void __init versatile_dt_pci_init(void)\n--\narch/arm/mach-versatile/versatile.c-144-\narch/arm/mach-versatile/versatile.c:145:\tnewprop = kzalloc_obj(*newprop);\narch/arm/mach-versatile/versatile.c-146-\tif (!newprop)\n--\narch/arm/mach-zynq/common.c=105=static void __init zynq_init_machine(void)\n--\narch/arm/mach-zynq/common.c-110-\narch/arm/mach-zynq/common.c:111:\tsoc_dev_attr = kzalloc_obj(*soc_dev_attr);\narch/arm/mach-zynq/common.c-112-\tif (!soc_dev_attr)\n--\narch/arm/mm/cache-l2x0-pmu.c=503=static __init int l2x0_pmu_init(void)\n--\narch/arm/mm/cache-l2x0-pmu.c-509-\narch/arm/mm/cache-l2x0-pmu.c:510:\tl2x0_pmu = kzalloc_obj(*l2x0_pmu);\narch/arm/mm/cache-l2x0-pmu.c-511-\tif (!l2x0_pmu) {\n--\narch/arm/mm/cache-uniphier.c=315=static int __init __uniphier_cache_init(struct device_node *np,\n--\narch/arm/mm/cache-uniphier.c-344-\narch/arm/mm/cache-uniphier.c:345:\tdata = kzalloc_obj(*data);\narch/arm/mm/cache-uniphier.c-346-\tif (!data)\n--\narch/arm/mm/dma-mapping.c=533=static void *__dma_alloc(struct device *dev, size_t size, dma_addr_t *handle,\n--\narch/arm/mm/dma-mapping.c-560-\narch/arm/mm/dma-mapping.c:561:\tbuf = kzalloc_obj(*buf,\narch/arm/mm/dma-mapping.c-562-\t\t\t  gfp \u0026 ~(__GFP_DMA | __GFP_DMA32 | __GFP_HIGHMEM));\n--\narch/arm/mm/dma-mapping.c=1487=arm_iommu_create_mapping(struct device *dev, dma_addr_t base, u64 size)\n--\narch/arm/mm/dma-mapping.c-1506-\narch/arm/mm/dma-mapping.c:1507:\tmapping = kzalloc_obj(struct dma_iommu_mapping);\narch/arm/mm/dma-mapping.c-1508-\tif (!mapping)\n--\narch/arm/xen/enlighten.c=316=int __init arch_xen_unpopulated_init(struct resource **res)\n--\narch/arm/xen/enlighten.c-343-\narch/arm/xen/enlighten.c:344:\tregs = kzalloc_objs(*regs, nr_reg);\narch/arm/xen/enlighten.c-345-\tif (!regs) {\n--\narch/arm/xen/enlighten.c-387-\narch/arm/xen/enlighten.c:388:\t\ttmp_res = kzalloc_obj(*tmp_res);\narch/arm/xen/enlighten.c-389-\t\tif (!tmp_res) {\n--\narch/arm/xen/p2m.c=150=bool __set_phys_to_machine_multi(unsigned long pfn,\n--\narch/arm/xen/p2m.c-178-\narch/arm/xen/p2m.c:179:\tp2m_entry = kzalloc_obj(*p2m_entry, GFP_NOWAIT);\narch/arm/xen/p2m.c-180-\tif (!p2m_entry)\n--\narch/arm64/kernel/vdso.c=68=static int __init __vdso_init(enum vdso_abi abi)\n--\narch/arm64/kernel/vdso.c-83-\narch/arm64/kernel/vdso.c:84:\tvdso_pagelist = kzalloc_objs(struct page *, vdso_info[abi].vdso_pages);\narch/arm64/kernel/vdso.c-85-\tif (vdso_pagelist == NULL)\n--\narch/arm64/kvm/mmu.c=480=static int share_pfn_hyp(u64 pfn)\n--\narch/arm64/kvm/mmu.c-492-\narch/arm64/kvm/mmu.c:493:\tthis = kzalloc_obj(*this);\narch/arm64/kvm/mmu.c-494-\tif (!this) {\n--\narch/arm64/kvm/mmu.c=981=int kvm_init_stage2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu, unsigned long type)\n--\narch/arm64/kvm/mmu.c-1007-\narch/arm64/kvm/mmu.c:1008:\tpgt = kzalloc_obj(*pgt, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/mmu.c-1009-\tif (!pgt)\n--\narch/arm64/kvm/mmu.c=1180=int topup_hyp_memcache(struct kvm_hyp_memcache *mc, unsigned long min_pages)\n--\narch/arm64/kvm/mmu.c-1185-\tif (!mc-\u003emapping) {\narch/arm64/kvm/mmu.c:1186:\t\tmc-\u003emapping = kzalloc_obj(struct pkvm_mapping,\narch/arm64/kvm/mmu.c-1187-\t\t\t\t\t  GFP_KERNEL_ACCOUNT);\n--\narch/arm64/kvm/mmu.c=2510=int __init kvm_mmu_init(u32 hyp_va_bits)\n--\narch/arm64/kvm/mmu.c-2543-\narch/arm64/kvm/mmu.c:2544:\thyp_pgtable = kzalloc_obj(*hyp_pgtable);\narch/arm64/kvm/mmu.c-2545-\tif (!hyp_pgtable) {\n--\narch/arm64/kvm/nested.c=1328=int kvm_vcpu_allocate_vncr_tlb(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/nested.c-1333-\tif (!vcpu-\u003earch.vncr_tlb) {\narch/arm64/kvm/nested.c:1334:\t\tstruct vncr_tlb *vt = kzalloc_obj(*vcpu-\u003earch.vncr_tlb,\narch/arm64/kvm/nested.c-1335-\t\t\t\t\t\t  GFP_KERNEL_ACCOUNT);\n--\narch/arm64/kvm/nested.c=1793=int kvm_init_nv_sysregs(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/nested.c-1802-\narch/arm64/kvm/nested.c:1803:\tkvm-\u003earch.sysreg_masks = kzalloc_obj(*(kvm-\u003earch.sysreg_masks),\narch/arm64/kvm/nested.c-1804-\t\t\t\t\t     GFP_KERNEL_ACCOUNT);\n--\narch/arm64/kvm/ptdump.c=116=static struct kvm_ptdump_guest_state *kvm_ptdump_parser_create(struct kvm_s2_mmu *mmu)\n--\narch/arm64/kvm/ptdump.c-121-\narch/arm64/kvm/ptdump.c:122:\tst = kzalloc_obj(struct kvm_ptdump_guest_state, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/ptdump.c-123-\tif (!st)\n--\narch/arm64/kvm/vgic/vgic-init.c=207=static int kvm_vgic_dist_init(struct kvm *kvm, unsigned int nr_spis)\n--\narch/arm64/kvm/vgic/vgic-init.c-213-\tdist-\u003eactive_spis = (atomic_t)ATOMIC_INIT(0);\narch/arm64/kvm/vgic/vgic-init.c:214:\tdist-\u003espis = kzalloc_objs(struct vgic_irq, nr_spis, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-init.c-215-\tif (!dist-\u003espis)\n--\narch/arm64/kvm/vgic/vgic-init.c=316=static int vgic_allocate_private_irqs_locked(struct kvm_vcpu *vcpu, u32 type)\n--\narch/arm64/kvm/vgic/vgic-init.c-331-\narch/arm64/kvm/vgic/vgic-init.c:332:\tvgic_cpu-\u003eprivate_irqs = kzalloc_objs(struct vgic_irq,\narch/arm64/kvm/vgic/vgic-init.c-333-\t\t\t\t\t      num_private_irqs,\n--\narch/arm64/kvm/vgic/vgic-irqfd.c=142=int kvm_vgic_setup_default_irq_routing(struct kvm *kvm)\n--\narch/arm64/kvm/vgic/vgic-irqfd.c-148-\narch/arm64/kvm/vgic/vgic-irqfd.c:149:\tentries = kzalloc_objs(*entries, nr, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-irqfd.c-150-\tif (!entries)\n--\narch/arm64/kvm/vgic/vgic-its.c=76=static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid,\n--\narch/arm64/kvm/vgic/vgic-its.c-87-\narch/arm64/kvm/vgic/vgic-its.c:88:\tirq = kzalloc_obj(struct vgic_irq, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-its.c-89-\tif (!irq)\n--\narch/arm64/kvm/vgic/vgic-its.c=971=static int vgic_its_alloc_collection(struct vgic_its *its,\n--\narch/arm64/kvm/vgic/vgic-its.c-976-\narch/arm64/kvm/vgic/vgic-its.c:977:\tcollection = kzalloc_obj(*collection, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-its.c-978-\tif (!collection)\n--\narch/arm64/kvm/vgic/vgic-its.c=1015=static struct its_ite *vgic_its_alloc_ite(struct its_device *device,\n--\narch/arm64/kvm/vgic/vgic-its.c-1020-\narch/arm64/kvm/vgic/vgic-its.c:1021:\tite = kzalloc_obj(*ite, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-its.c-1022-\tif (!ite)\n--\narch/arm64/kvm/vgic/vgic-its.c=1142=static struct its_device *vgic_its_alloc_device(struct vgic_its *its,\n--\narch/arm64/kvm/vgic/vgic-its.c-1147-\narch/arm64/kvm/vgic/vgic-its.c:1148:\tdevice = kzalloc_obj(*device, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-its.c-1149-\tif (!device)\n--\narch/arm64/kvm/vgic/vgic-its.c=1855=static int vgic_its_create(struct kvm_device *dev, u32 type)\n--\narch/arm64/kvm/vgic/vgic-its.c-1862-\narch/arm64/kvm/vgic/vgic-its.c:1863:\tits = kzalloc_obj(struct vgic_its, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-its.c-1864-\tif (!its)\n--\narch/arm64/kvm/vgic/vgic-mmio-v3.c=886=static int vgic_v3_alloc_redist_region(struct kvm *kvm, uint32_t index,\n--\narch/arm64/kvm/vgic/vgic-mmio-v3.c-931-\narch/arm64/kvm/vgic/vgic-mmio-v3.c:932:\trdreg = kzalloc_obj(*rdreg, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-mmio-v3.c-933-\tif (!rdreg)\n--\narch/arm64/kvm/vgic/vgic-v4.c=242=int vgic_v4_init(struct kvm *kvm)\n--\narch/arm64/kvm/vgic/vgic-v4.c-258-\narch/arm64/kvm/vgic/vgic-v4.c:259:\tdist-\u003eits_vm.vpes = kzalloc_objs(*dist-\u003eits_vm.vpes, nr_vcpus,\narch/arm64/kvm/vgic/vgic-v4.c-260-\t\t\t\t\t GFP_KERNEL_ACCOUNT);\n--\narch/arm64/net/bpf_jit_comp.c=2080=struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)\n--\narch/arm64/net/bpf_jit_comp.c-2101-\tif (!jit_data) {\narch/arm64/net/bpf_jit_comp.c:2102:\t\tjit_data = kzalloc_obj(*jit_data);\narch/arm64/net/bpf_jit_comp.c-2103-\t\tif (!jit_data)\n--\narch/csky/kernel/vdso.c=17=static int __init vdso_init(void)\n--\narch/csky/kernel/vdso.c-22-\tvdso_pagelist =\narch/csky/kernel/vdso.c:23:\t\tkzalloc_objs(struct page *, vdso_pages);\narch/csky/kernel/vdso.c-24-\tif (unlikely(vdso_pagelist == NULL)) {\n--\narch/loongarch/kernel/setup.c=466=static int __init add_legacy_isa_io(struct fwnode_handle *fwnode,\n--\narch/loongarch/kernel/setup.c-472-\narch/loongarch/kernel/setup.c:473:\trange = kzalloc_obj(*range, GFP_ATOMIC);\narch/loongarch/kernel/setup.c-474-\tif (!range)\n--\narch/loongarch/kernel/vdso.c=45=static int __init init_vdso(void)\n--\narch/loongarch/kernel/vdso.c-55-\tvdso_info.code_mapping.pages =\narch/loongarch/kernel/vdso.c:56:\t\tkzalloc_objs(struct page *, vdso_info.size / PAGE_SIZE);\narch/loongarch/kernel/vdso.c-57-\n--\narch/loongarch/kvm/intc/eiointc.c=614=static int kvm_eiointc_create(struct kvm_device *dev, u32 type)\n--\narch/loongarch/kvm/intc/eiointc.c-624-\narch/loongarch/kvm/intc/eiointc.c:625:\ts = kzalloc_obj(struct loongarch_eiointc);\narch/loongarch/kvm/intc/eiointc.c-626-\tif (!s)\n--\narch/loongarch/kvm/intc/ipi.c=394=static int kvm_ipi_create(struct kvm_device *dev, u32 type)\n--\narch/loongarch/kvm/intc/ipi.c-411-\narch/loongarch/kvm/intc/ipi.c:412:\ts = kzalloc_obj(struct loongarch_ipi);\narch/loongarch/kvm/intc/ipi.c-413-\tif (!s)\n\n"
}

15/3 2026/08/11 14:03 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 31917
							output: 227
							thoughts: 2699
						

Thoughts:
**Analysis of Initialization in the zl3073x Driver**

Okay, let's break this down systematically. It seems like the core concern is about ensuring variables and data structures are properly initialized before use, especially when dealing with PTP (Precision Time Protocol) operations within this `zl3073x` driver. I'm focusing on potential uninitialized memory reads.

First, I see the shift to using `kzalloc_obj` and `kzalloc_objs` kernel macros. That's good; these are now the preferred methods to allocate and zero memory. Specifically, allocating and zeroing the `zldpll` struct with `kzalloc_obj(*zldpll)` is a solid start for avoiding uninitialized values.

Moving through the code, I examined the `zl3073x_dpll_ptp_register` function and the usage of `zldpll->ptp_info`. This struct gets initialized by a copy from a `static const` structure, `zl3073x_dpll_ptp_clock_info`. Because it's `static const`, the compiler handles the zero-initialization of all uninitialized fields, ensuring that the PTP information is fully initialized before use. The `snprintf` function will write to the `name` field, but since the rest is already zeroed, there are no uninitialized reads.

Then I considered `zl3073x_chan_tod_read`. The `ts` struct, used to store the read time, seems to be fully initialized on a successful return. In the event of an error, it is returned, and since the PTP subsystem will not use it, there are no uninitialized reads.

In `zl3073x_dpll_ptp_adjtime_phase_step` I saw that the `synth_mask` array is initialized to zero, which is a good practice.

Next, I looked at how `zl3073x_dpll_ptp_adjtime` handles `ts`. The relevant code indicates that `ts` is fully initialized before it's passed on to other functions.

`zl3073x_chan_tod_adjust` calls `zl3073x_chan_tod_read`, and it appears that `ts_next` and `ts_cur` will also be fully initialized due to the return value of `zl3073x_chan_tod_read` in case of success.

For `zl3073x_chan_tod_write`, I confirmed that the fields of the input `ts` are properly populated before being used.

Then I analyzed `zl3073x_chan_df_offset_set`, `zl3073x_chan_tie_write`, `zl3073x_chan_phase_step`, and `zl3073x_dev_state_fetch`. It seems that these functions all use initialized data.

Finally, the analysis of `zl3073x_dpll_ptp_adjfine` and `zl3073x_dpll_ptp_adjphase` did not present any obvious cases of uninitialized memory usage.

In summary, there is no evidence of uninitialized reads being introduced by this patch. The driver uses appropriate initialization techniques, like `kzalloc_obj`, and the PTP subsystem seems to handle data transfer to userspace safely. Therefore, I'm confident in setting `NeedsKMSAN=false`.



16/3 2026/08/11 14:03 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch adds PTP clock support to the Microchip Azurite DPLL/PTP/SyncE devices driver. It introduces functions to read/write Time of Day (ToD), adjust phase, adjust frequency, and register a PTP clock device. The driver interacts with the PTP subsystem by providing callbacks (`gettimex64`, `settime64`, `adjtime`, `adjfine`, `adjphase`). All structures allocated dynamically are zero-initialized using `kzalloc_obj`. The `ptp_clock_info` structure is copied from a static const template, ensuring all uninitialized fields are zeroed by the compiler before `snprintf` modifies the `name` field. The `struct timespec64` populated by the driver is fully initialized on success, and errors are properly propagated to the PTP subsystem, preventing uninitialized data from being used or copied to userspace. No new uninitialized memory risks or info-leaks are introduced.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch adds PTP clock support to the Microchip Azurite DPLL/PTP/SyncE devices driver. It introduces functions to read/write Time of Day (ToD), adjust phase, adjust frequency, and register a PTP clock device. The driver interacts with the PTP subsystem by providing callbacks (`gettimex64`, `settime64`, `adjtime`, `adjfine`, `adjphase`). All structures allocated dynamically are zero-initialized using `kzalloc_obj`. The `ptp_clock_info` structure is copied from a static const template, ensuring all uninitialized fields are zeroed by the compiler before `snprintf` modifies the `name` field. The `struct timespec64` populated by the driver is fully initialized on success, and errors are properly propagated to the PTP subsystem, preventing uninitialized data from being used or copied to userspace. No new uninitialized memory risks or info-leaks are introduced.",
  "NeedsKMSAN": 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)