| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/03 18:36 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch modifies the Microchip Azurite DPLL driver (zl3073x) to add PTP clock support. This is a hardware-specific driver that communicates over I2C/SPI. During probe, it reads a hardware chip ID and fails if it doesn't match known values. Since this specific hardware is not emulated in standard QEMU, the probe will fail, making the newly added PTP clock operations unreachable in virtualized fuzzing environments.",
"WorthFuzzing": false
} |
| 1/1 | 2026/08/03 18:36 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 65bc9df9d47d2b32c89455269b93bb538fa9fe1d\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Mon Aug 3 18:36:02 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..79874a9fdb496 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,311 @@ const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,\n \treturn \u0026zldev-\u003echan[index];\n }\n \n+/**\n+ * zl3073x_chan_tod_ready_wait - wait for ToD semaphore to clear\n+ * @zldev: pointer to zl3073x device\n+ * @ch: DPLL channel index\n+ *\n+ * Polls the ToD control register until the semaphore bit is cleared,\n+ * indicating the device has completed the previous ToD operation.\n+ *\n+ * Return: 0 on success, -EBUSY if semaphore not cleared, \u003c0 on error\n+ */\n+int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch)\n+{\n+\tunsigned int timeout;\n+\tu8 tod_ctrl;\n+\tint rc;\n+\n+\trc = zl3073x_read_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), \u0026tod_ctrl);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tswitch (FIELD_GET(ZL_DPLL_TOD_CTRL_CMD, tod_ctrl)) {\n+\tcase ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ:\n+\t\ttimeout = ZL_POLL_TOD_WR_TIMEOUT_US;\n+\t\tbreak;\n+\tdefault:\n+\t\ttimeout = ZL_POLL_TOD_RD_TIMEOUT_US;\n+\t\tbreak;\n+\t}\n+\n+\trc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch),\n+\t\t\t\t ZL_DPLL_TOD_CTRL_SEM, timeout);\n+\n+\treturn rc == -ETIMEDOUT ? -EBUSY : rc;\n+}\n+\n+/**\n+ * zl3073x_chan_tod_ctrl - issue ToD command\n+ * @zldev: pointer to zl3073x device\n+ * @ch: DPLL channel index\n+ * @cmd: ToD command to execute\n+ *\n+ * Writes the semaphore and command to dpll_tod_ctrl. The caller must\n+ * ensure the device is ready (semaphore clear) before calling and\n+ * must wait for completion if needed.\n+ *\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+static int zl3073x_chan_tod_ctrl(struct zl3073x_dev *zldev, u8 ch, u8 cmd)\n+{\n+\treturn zl3073x_write_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch),\n+\t\t\t\tZL_DPLL_TOD_CTRL_SEM | cmd);\n+}\n+\n+/**\n+ * zl3073x_chan_tod_read - read ToD registers after issuing a command\n+ * @zldev: pointer to zl3073x device\n+ * @ch: DPLL channel index\n+ * @next_hz: if true, read predicted ToD at next 1 Hz; otherwise read current\n+ * @ts: timespec to store the result\n+ * @sts: optional system timestamp pair for cross-timestamping\n+ *\n+ * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch,\n+\t\t\t bool next_hz, struct timespec64 *ts,\n+\t\t\t struct ptp_system_timestamp *sts)\n+{\n+\tu32 nsec;\n+\tu64 sec;\n+\tu8 cmd;\n+\tint rc;\n+\n+\tif (next_hz)\n+\t\tcmd = ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ;\n+\telse\n+\t\tcmd = ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT;\n+\n+\t/* Wait for any previous ToD operation to complete */\n+\trc = zl3073x_chan_tod_ready_wait(zldev, ch);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tptp_read_system_prets(sts);\n+\trc = zl3073x_chan_tod_ctrl(zldev, ch, cmd);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = zl3073x_chan_tod_ready_wait(zldev, ch);\n+\tif (rc)\n+\t\treturn rc;\n+\tptp_read_system_postts(sts);\n+\n+\trc = zl3073x_read_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), \u0026sec);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* HW nanoseconds are always in [0, NSEC_PER_SEC) range */\n+\trc = zl3073x_read_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), \u0026nsec);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tts-\u003etv_sec = sec;\n+\tts-\u003etv_nsec = nsec;\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * zl3073x_chan_tod_write - write ToD registers and trigger 1 Hz update\n+ * @zldev: pointer to zl3073x device\n+ * @ch: DPLL channel index\n+ * @ts: time to set\n+ *\n+ * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch,\n+\t\t\t struct timespec64 ts)\n+{\n+\tint rc;\n+\n+\t/* Wait for any previous ToD operation to complete */\n+\trc = zl3073x_chan_tod_ready_wait(zldev, ch);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), ts.tv_sec);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\trc = zl3073x_write_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), ts.tv_nsec);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\treturn zl3073x_chan_tod_ctrl(zldev, ch,\n+\t\t\t\t ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ);\n+}\n+\n+/**\n+ * zl3073x_chan_tod_adjust - atomic ToD read-modify-write with rollover guard\n+ * @zldev: pointer to zl3073x device\n+ * @ch: DPLL channel index\n+ * @delta: time adjustment to apply\n+ *\n+ * Reads the next-Hz ToD and current ToD, then checks whether enough time\n+ * remains before the next 1 Hz rollover to safely complete the write.\n+ * If less than 20 ms remains, waits for the rollover and increments the\n+ * next-Hz seconds by one. If the tick crossed between the two reads,\n+ * normalizes by the number of elapsed ticks. 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+\tstatic const long threshold_ns = 20 * NSEC_PER_MSEC;\n+\tstruct timespec64 ts_next, ts_cur, diff;\n+\tint rc;\n+\n+\t/* Read predicted ToD at next 1 Hz tick */\n+\trc = zl3073x_chan_tod_read(zldev, ch, true, \u0026ts_next, NULL);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* Read current ToD to determine remaining margin */\n+\trc = zl3073x_chan_tod_read(zldev, ch, false, \u0026ts_cur, NULL);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\t/* If too close to the next rollover, wait it out. If the 1 Hz tick\n+\t * crossed between the two reads, normalize by the elapsed ticks.\n+\t */\n+\tdiff = timespec64_sub(ts_next, ts_cur);\n+\tif (!diff.tv_sec \u0026\u0026 diff.tv_nsec \u003c threshold_ns) {\n+\t\tfsleep((unsigned long)diff.tv_nsec / NSEC_PER_USEC + 1);\n+\t\tts_next.tv_sec++;\n+\t} else if (diff.tv_sec \u003c 0) {\n+\t\tts_next.tv_sec -= diff.tv_sec;\n+\t}\n+\n+\t/* Apply delta to the next-Hz ToD */\n+\tts_next = timespec64_add(ts_next, delta);\n+\tif (!timespec64_valid(\u0026ts_next))\n+\t\treturn -EINVAL;\n+\n+\treturn zl3073x_chan_tod_write(zldev, ch, ts_next);\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 within +-1s)\n+ *\n+ * Converts nanoseconds to TIE units (0.01 ps) and writes TIE data\n+ * to the specified channel.\n+ *\n+ * Return: 0 on success, \u003c0 on error\n+ */\n+int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns)\n+{\n+\ts64 tie_data;\n+\tint rc;\n+\n+\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 7f5afaaae6342..8a8c27a06ac9d 100644\n--- a/drivers/dpll/zl3073x/core.c\n+++ b/drivers/dpll/zl3073x/core.c\n@@ -510,6 +510,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@@ -1033,6 +1038,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..5e294f190505f 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,369 @@ 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 the\n+ * lowest-ID synth's period for the conversion).\n+ *\n+ * Return: 0 on success, -EOPNOTSUPP if no synths available, \u003c0 on error\n+ */\n+static int zl3073x_dpll_ptp_adjtime_phase_step(struct zl3073x_dpll *zldpll,\n+\t\t\t\t\t s64 delta)\n+{\n+\tu16 synth_mask[ZL3073X_NUM_SYNTHS] = {};\n+\tstruct zl3073x_dev *zldev = zldpll-\u003edev;\n+\tconst struct zl3073x_synth *synth;\n+\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 lowest-ID synth freq for ToD-only fallback */\n+\t\tif (!first_synth_freq)\n+\t\t\tfirst_synth_freq = synth_freq;\n+\n+\t\tif (!synth_mask[i])\n+\t\t\tcontinue;\n+\n+\t\t/* Safe for s32: max synth freq is 750 MHz */\n+\t\tstep_cycles = div_s64(delta * synth_freq, NSEC_PER_SEC);\n+\n+\t\trc = zl3073x_chan_phase_step(zldev, zldpll-\u003eid,\n+\t\t\t\t\t synth_mask[i], step_cycles,\n+\t\t\t\t\t !tod_stepped);\n+\t\tif (rc) {\n+\t\t\tif (tod_stepped) {\n+\t\t\t\tdev_warn(zldev-\u003edev,\n+\t\t\t\t\t \"Partial phase step failure\\n\");\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t\treturn rc;\n+\t\t}\n+\t\ttod_stepped = true;\n+\t}\n+\n+\tif (!first_synth_freq)\n+\t\treturn -EOPNOTSUPP;\n+\n+\t/* No enabled outputs found; step ToD counter only using the\n+\t * lowest-ID synth's period (empty output mask).\n+\t */\n+\tif (!tod_stepped) {\n+\t\tstep_cycles = div_s64(delta * first_synth_freq, NSEC_PER_SEC);\n+\t\treturn zl3073x_chan_phase_step(zldev, zldpll-\u003eid, 0,\n+\t\t\t\t\t step_cycles, true);\n+\t}\n+\n+\treturn 0;\n }\n \n /**\n- * zl3073x_dpll_free - free DPLL device\n- * @zldpll: pointer to zl3073x_dpll structure\n+ * zl3073x_dpll_ptp_adjtime - adjust PTP clock time\n+ * @info: PTP clock info\n+ * @delta: time adjustment in nanoseconds\n *\n- * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc.\n+ * For NCO, large deltas (\u003e= 1 second) are split into a ToD\n+ * read-modify-write for the seconds part and an output phase step for\n+ * the sub-second remainder. Sub-second deltas use phase step directly,\n+ * falling back to ToD read-modify-write if phase step is unavailable.\n+ * In AUTO/REFLOCK modes, large deltas are split into ToD\n+ * read-modify-write for seconds and TIE write for the sub-second\n+ * remainder. Sub-second deltas use TIE write directly.\n+ *\n+ * Return: 0 on success, \u003c0 on error\n */\n-void\n-zl3073x_dpll_free(struct zl3073x_dpll *zldpll)\n+static int zl3073x_dpll_ptp_adjtime(struct ptp_clock_info *info, s64 delta)\n {\n-\tWARN(zldpll-\u003edpll_dev, \"DPLL device is still registered\\n\");\n+\tstruct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,\n+\t\t\t\t\t\t ptp_info);\n+\tstruct zl3073x_dev *zldev = zldpll-\u003edev;\n+\tconst struct zl3073x_chan *chan;\n+\tbool sec_adjusted = false;\n+\tstruct timespec64 ts;\n+\tint rc;\n \n-\tmutex_destroy(\u0026zldpll-\u003elock);\n-\tkfree(zldpll);\n+\tif (!delta)\n+\t\treturn 0;\n+\n+\tguard(mutex)(\u0026zldpll-\u003elock);\n+\n+\t/* Modes without phase step or TIE use plain ToD adjust */\n+\tchan = zl3073x_chan_state_get(zldev, zldpll-\u003eid);\n+\tif (!zl3073x_chan_mode_is_nco(chan) \u0026\u0026\n+\t !zl3073x_chan_mode_supports_tie(chan))\n+\t\treturn zl3073x_chan_tod_adjust(zldev, zldpll-\u003eid,\n+\t\t\t\t\t ns_to_timespec64(delta));\n+\n+\t/* Split off seconds via ToD read-modify-write so the sub-second\n+\t * remainder can be applied through the output-coherent mechanism\n+\t * (phase step or TIE write).\n+\t */\n+\tif (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: 0 on success, -EOPNOTSUPP if NCO pin is not connected, \u003c0 on error\n+ */\n+static int\n+zl3073x_dpll_ptp_adjfine(struct ptp_clock_info *info, long scaled_ppm)\n+{\n+\tstruct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,\n+\t\t\t\t\t\t ptp_info);\n+\tconst struct zl3073x_chan *chan;\n+\ts64 offset;\n+\n+\t/* Convert scaled_ppm to df_offset in 2^-48 steps:\n+\t * df_offset = -(scaled_ppm * 2^32) / 10^6\n+\t *\n+\t * Simplify to avoid overflow:\n+\t * df_offset = -(scaled_ppm * 2^26) / 5^6\n+\t * df_offset = -(scaled_ppm * 67108864) / 15625\n+\t */\n+\toffset = -div_s64((s64)scaled_ppm * 67108864LL, 15625);\n+\n+\tguard(mutex)(\u0026zldpll-\u003elock);\n+\n+\tchan = zl3073x_chan_state_get(zldpll-\u003edev, zldpll-\u003eid);\n+\tif (!zl3073x_chan_mode_is_nco(chan))\n+\t\treturn scaled_ppm ? -EOPNOTSUPP : 0;\n+\tif (offset == chan-\u003edf_offset)\n+\t\treturn 0;\n+\n+\treturn zl3073x_chan_df_offset_set(zldpll-\u003edev, zldpll-\u003eid, offset);\n+}\n+\n+/**\n+ * zl3073x_dpll_ptp_adjphase - adjust PTP clock phase\n+ * @info: PTP clock info\n+ * @delta: phase adjustment in nanoseconds\n+ *\n+ * Only supported in AUTO and REFLOCK modes. Uses TIE write for\n+ * nanosecond resolution phase adjustment.\n+ *\n+ * Return: 0 on success, -EOPNOTSUPP if mode doesn't support TIE, \u003c0 on error\n+ */\n+static int zl3073x_dpll_ptp_adjphase(struct ptp_clock_info *info, s32 delta)\n+{\n+\tstruct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,\n+\t\t\t\t\t\t ptp_info);\n+\tstruct zl3073x_dev *zldev = zldpll-\u003edev;\n+\tconst struct zl3073x_chan *chan;\n+\n+\tif (!delta)\n+\t\treturn 0;\n+\n+\tguard(mutex)(\u0026zldpll-\u003elock);\n+\n+\tchan = zl3073x_chan_state_get(zldev, zldpll-\u003eid);\n+\n+\tif (!zl3073x_chan_mode_supports_tie(chan))\n+\t\treturn -EOPNOTSUPP;\n+\n+\treturn zl3073x_chan_tie_write(zldev, zldpll-\u003eid, delta);\n+}\n+\n+static s32\n+zl3073x_dpll_ptp_getmaxphase(struct ptp_clock_info *info __always_unused)\n+{\n+\t/* HW limits TIE write to +-1 second. Return the constant HW\n+\t * limit and let adjphase handle mode-specific checks.\n+\t */\n+\treturn NSEC_PER_SEC - 1;\n+}\n+\n+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+\tsnprintf(zldpll-\u003eptp_info.name, sizeof(zldpll-\u003eptp_info.name),\n+\t\t \"zl3073x-dpll%u\", zldpll-\u003eid);\n+\n+\tptp_clock = ptp_clock_register(\u0026zldpll-\u003eptp_info, zldev-\u003edev);\n+\tif (IS_ERR(ptp_clock)) {\n+\t\tdev_err(zldev-\u003edev, \"Failed to register PTP clock for DPLL%u\\n\",\n+\t\t\tzldpll-\u003eid);\n+\t\treturn PTR_ERR(ptp_clock);\n+\t}\n+\n+\tzldpll-\u003eptp_clock = ptp_clock;\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * zl3073x_dpll_ptp_unregister - unregister PTP clock for a DPLL channel\n+ * @zldpll: DPLL channel to unregister PTP clock for\n+ */\n+static void zl3073x_dpll_ptp_unregister(struct zl3073x_dpll *zldpll)\n+{\n+\tif (!IS_ERR_OR_NULL(zldpll-\u003eptp_clock)) {\n+\t\tptp_clock_unregister(zldpll-\u003eptp_clock);\n+\t\tzldpll-\u003eptp_clock = NULL;\n+\t}\n }\n \n /**\n@@ -2398,6 +2724,48 @@ zl3073x_dpll_ref_sync_pairs_register(struct zl3073x_dpll *zldpll)\n \treturn 0;\n }\n \n+/**\n+ * zl3073x_dpll_alloc - allocate DPLL device\n+ * @zldev: pointer to zl3073x device\n+ * @ch: DPLL channel number\n+ *\n+ * Allocates DPLL device structure for given DPLL channel.\n+ *\n+ * Return: pointer to DPLL device on success, error pointer on error\n+ */\n+struct zl3073x_dpll *\n+zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)\n+{\n+\tstruct zl3073x_dpll *zldpll;\n+\n+\tzldpll = kzalloc_obj(*zldpll);\n+\tif (!zldpll)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tzldpll-\u003edev = zldev;\n+\tzldpll-\u003eid = ch;\n+\tzldpll-\u003eptp_info = zl3073x_dpll_ptp_clock_info;\n+\tmutex_init(\u0026zldpll-\u003elock);\n+\tINIT_LIST_HEAD(\u0026zldpll-\u003epins);\n+\n+\treturn zldpll;\n+}\n+\n+/**\n+ * zl3073x_dpll_free - free DPLL device\n+ * @zldpll: pointer to zl3073x_dpll structure\n+ *\n+ * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc.\n+ */\n+void\n+zl3073x_dpll_free(struct zl3073x_dpll *zldpll)\n+{\n+\tWARN(zldpll-\u003edpll_dev, \"DPLL device is still registered\\n\");\n+\n+\tmutex_destroy(\u0026zldpll-\u003elock);\n+\tkfree(zldpll);\n+}\n+\n /**\n * zl3073x_dpll_register - register DPLL device and all its pins\n * @zldpll: pointer to zl3073x_dpll structure\n@@ -2428,6 +2796,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 +2816,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/03 18:36 | agent | patch-evaluator |
1mModel:best-expensive Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch modifies the Microchip Azurite DPLL driver (zl3073x) to add PTP clock support. This is a hardware-specific driver that communicates over I2C/SPI. During probe, it reads a hardware chip ID and fails if it doesn't match known values. Since this specific hardware is not emulated in standard QEMU, the probe will fail, making the newly added PTP clock operations unreachable in virtualized fuzzing environments.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 65bc9df9d47d2b32c89455269b93bb538fa9fe1d
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Aug 3 18:36:02 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..79874a9fdb496 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,311 @@ const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,
return &zldev->chan[index];
}
+/**
+ * zl3073x_chan_tod_ready_wait - wait for ToD semaphore to clear
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ *
+ * Polls the ToD control register until the semaphore bit is cleared,
+ * indicating the device has completed the previous ToD operation.
+ *
+ * Return: 0 on success, -EBUSY if semaphore not cleared, <0 on error
+ */
+int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch)
+{
+ unsigned int timeout;
+ u8 tod_ctrl;
+ int rc;
+
+ rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), &tod_ctrl);
+ if (rc)
+ return rc;
+
+ switch (FIELD_GET(ZL_DPLL_TOD_CTRL_CMD, tod_ctrl)) {
+ case ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ:
+ timeout = ZL_POLL_TOD_WR_TIMEOUT_US;
+ break;
+ default:
+ timeout = ZL_POLL_TOD_RD_TIMEOUT_US;
+ break;
+ }
+
+ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch),
+ ZL_DPLL_TOD_CTRL_SEM, timeout);
+
+ return rc == -ETIMEDOUT ? -EBUSY : rc;
+}
+
+/**
+ * zl3073x_chan_tod_ctrl - issue ToD command
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @cmd: ToD command to execute
+ *
+ * Writes the semaphore and command to dpll_tod_ctrl. The caller must
+ * ensure the device is ready (semaphore clear) before calling and
+ * must wait for completion if needed.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int zl3073x_chan_tod_ctrl(struct zl3073x_dev *zldev, u8 ch, u8 cmd)
+{
+ return zl3073x_write_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch),
+ ZL_DPLL_TOD_CTRL_SEM | cmd);
+}
+
+/**
+ * zl3073x_chan_tod_read - read ToD registers after issuing a command
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @next_hz: if true, read predicted ToD at next 1 Hz; otherwise read current
+ * @ts: timespec to store the result
+ * @sts: optional system timestamp pair for cross-timestamping
+ *
+ * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch,
+ bool next_hz, struct timespec64 *ts,
+ struct ptp_system_timestamp *sts)
+{
+ u32 nsec;
+ u64 sec;
+ u8 cmd;
+ int rc;
+
+ if (next_hz)
+ cmd = ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ;
+ else
+ cmd = ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT;
+
+ /* Wait for any previous ToD operation to complete */
+ rc = zl3073x_chan_tod_ready_wait(zldev, ch);
+ if (rc)
+ return rc;
+
+ ptp_read_system_prets(sts);
+ rc = zl3073x_chan_tod_ctrl(zldev, ch, cmd);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_chan_tod_ready_wait(zldev, ch);
+ if (rc)
+ return rc;
+ ptp_read_system_postts(sts);
+
+ rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), &sec);
+ if (rc)
+ return rc;
+
+ /* HW nanoseconds are always in [0, NSEC_PER_SEC) range */
+ rc = zl3073x_read_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), &nsec);
+ if (rc)
+ return rc;
+
+ ts->tv_sec = sec;
+ ts->tv_nsec = nsec;
+
+ return 0;
+}
+
+/**
+ * zl3073x_chan_tod_write - write ToD registers and trigger 1 Hz update
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @ts: time to set
+ *
+ * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch,
+ struct timespec64 ts)
+{
+ int rc;
+
+ /* Wait for any previous ToD operation to complete */
+ rc = zl3073x_chan_tod_ready_wait(zldev, ch);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), ts.tv_sec);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_write_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), ts.tv_nsec);
+ if (rc)
+ return rc;
+
+ return zl3073x_chan_tod_ctrl(zldev, ch,
+ ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ);
+}
+
+/**
+ * zl3073x_chan_tod_adjust - atomic ToD read-modify-write with rollover guard
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @delta: time adjustment to apply
+ *
+ * Reads the next-Hz ToD and current ToD, then checks whether enough time
+ * remains before the next 1 Hz rollover to safely complete the write.
+ * If less than 20 ms remains, waits for the rollover and increments the
+ * next-Hz seconds by one. If the tick crossed between the two reads,
+ * normalizes by the number of elapsed ticks. Applies @delta and writes
+ * the result back.
+ *
+ * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch,
+ struct timespec64 delta)
+{
+ static const long threshold_ns = 20 * NSEC_PER_MSEC;
+ struct timespec64 ts_next, ts_cur, diff;
+ int rc;
+
+ /* Read predicted ToD at next 1 Hz tick */
+ rc = zl3073x_chan_tod_read(zldev, ch, true, &ts_next, NULL);
+ if (rc)
+ return rc;
+
+ /* Read current ToD to determine remaining margin */
+ rc = zl3073x_chan_tod_read(zldev, ch, false, &ts_cur, NULL);
+ if (rc)
+ return rc;
+
+ /* If too close to the next rollover, wait it out. If the 1 Hz tick
+ * crossed between the two reads, normalize by the elapsed ticks.
+ */
+ diff = timespec64_sub(ts_next, ts_cur);
+ if (!diff.tv_sec && diff.tv_nsec < threshold_ns) {
+ fsleep((unsigned long)diff.tv_nsec / NSEC_PER_USEC + 1);
+ ts_next.tv_sec++;
+ } else if (diff.tv_sec < 0) {
+ ts_next.tv_sec -= diff.tv_sec;
+ }
+
+ /* 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);
+}
+
+/**
+ * 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 within +-1s)
+ *
+ * Converts nanoseconds to TIE units (0.01 ps) and writes TIE data
+ * to the specified channel.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns)
+{
+ s64 tie_data;
+ int rc;
+
+ 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 7f5afaaae6342..8a8c27a06ac9d 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -510,6 +510,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) {
@@ -1033,6 +1038,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..5e294f190505f 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,369 @@ 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
+ * lowest-ID synth's period for the conversion).
+ *
+ * Return: 0 on success, -EOPNOTSUPP if no synths available, <0 on error
+ */
+static int zl3073x_dpll_ptp_adjtime_phase_step(struct zl3073x_dpll *zldpll,
+ s64 delta)
+{
+ u16 synth_mask[ZL3073X_NUM_SYNTHS] = {};
+ struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_synth *synth;
+ 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 lowest-ID synth freq for ToD-only fallback */
+ if (!first_synth_freq)
+ first_synth_freq = synth_freq;
+
+ if (!synth_mask[i])
+ continue;
+
+ /* Safe for s32: max synth freq is 750 MHz */
+ step_cycles = div_s64(delta * synth_freq, NSEC_PER_SEC);
+
+ rc = zl3073x_chan_phase_step(zldev, zldpll->id,
+ synth_mask[i], step_cycles,
+ !tod_stepped);
+ if (rc) {
+ if (tod_stepped) {
+ dev_warn(zldev->dev,
+ "Partial phase step failure\n");
+ return 0;
+ }
+ return rc;
+ }
+ tod_stepped = true;
+ }
+
+ if (!first_synth_freq)
+ return -EOPNOTSUPP;
+
+ /* No enabled outputs found; step ToD counter only using the
+ * lowest-ID synth's period (empty output mask).
+ */
+ if (!tod_stepped) {
+ step_cycles = div_s64(delta * first_synth_freq, NSEC_PER_SEC);
+ return zl3073x_chan_phase_step(zldev, zldpll->id, 0,
+ step_cycles, true);
+ }
+
+ return 0;
}
/**
- * zl3073x_dpll_free - free DPLL device
- * @zldpll: pointer to zl3073x_dpll structure
+ * zl3073x_dpll_ptp_adjtime - adjust PTP clock time
+ * @info: PTP clock info
+ * @delta: time adjustment in nanoseconds
*
- * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc.
+ * For NCO, large deltas (>= 1 second) are split into a ToD
+ * read-modify-write for the seconds part and an output phase step for
+ * the sub-second remainder. Sub-second deltas use phase step directly,
+ * falling back to ToD read-modify-write if phase step is unavailable.
+ * In AUTO/REFLOCK modes, large deltas are split into ToD
+ * read-modify-write for seconds and TIE write for the sub-second
+ * remainder. Sub-second deltas use TIE write directly.
+ *
+ * Return: 0 on success, <0 on error
*/
-void
-zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
+static int zl3073x_dpll_ptp_adjtime(struct ptp_clock_info *info, s64 delta)
{
- WARN(zldpll->dpll_dev, "DPLL device is still registered\n");
+ struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+ ptp_info);
+ struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_chan *chan;
+ bool sec_adjusted = false;
+ struct timespec64 ts;
+ int rc;
- mutex_destroy(&zldpll->lock);
- kfree(zldpll);
+ if (!delta)
+ return 0;
+
+ guard(mutex)(&zldpll->lock);
+
+ /* Modes without phase step or TIE use plain ToD adjust */
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
+ if (!zl3073x_chan_mode_is_nco(chan) &&
+ !zl3073x_chan_mode_supports_tie(chan))
+ return zl3073x_chan_tod_adjust(zldev, zldpll->id,
+ ns_to_timespec64(delta));
+
+ /* Split off seconds via ToD read-modify-write so the sub-second
+ * remainder can be applied through the output-coherent mechanism
+ * (phase step or TIE write).
+ */
+ if (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 on success, -EOPNOTSUPP if NCO pin is not connected, <0 on error
+ */
+static int
+zl3073x_dpll_ptp_adjfine(struct ptp_clock_info *info, long scaled_ppm)
+{
+ struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+ ptp_info);
+ const struct zl3073x_chan *chan;
+ s64 offset;
+
+ /* Convert scaled_ppm to df_offset in 2^-48 steps:
+ * df_offset = -(scaled_ppm * 2^32) / 10^6
+ *
+ * Simplify to avoid overflow:
+ * df_offset = -(scaled_ppm * 2^26) / 5^6
+ * df_offset = -(scaled_ppm * 67108864) / 15625
+ */
+ offset = -div_s64((s64)scaled_ppm * 67108864LL, 15625);
+
+ guard(mutex)(&zldpll->lock);
+
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ if (!zl3073x_chan_mode_is_nco(chan))
+ return scaled_ppm ? -EOPNOTSUPP : 0;
+ if (offset == chan->df_offset)
+ return 0;
+
+ return zl3073x_chan_df_offset_set(zldpll->dev, zldpll->id, offset);
+}
+
+/**
+ * zl3073x_dpll_ptp_adjphase - adjust PTP clock phase
+ * @info: PTP clock info
+ * @delta: phase adjustment in nanoseconds
+ *
+ * Only supported in AUTO and REFLOCK modes. Uses TIE write for
+ * nanosecond resolution phase adjustment.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if mode doesn't support TIE, <0 on error
+ */
+static int zl3073x_dpll_ptp_adjphase(struct ptp_clock_info *info, s32 delta)
+{
+ struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+ ptp_info);
+ struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_chan *chan;
+
+ if (!delta)
+ return 0;
+
+ guard(mutex)(&zldpll->lock);
+
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
+
+ if (!zl3073x_chan_mode_supports_tie(chan))
+ return -EOPNOTSUPP;
+
+ return zl3073x_chan_tie_write(zldev, zldpll->id, delta);
+}
+
+static s32
+zl3073x_dpll_ptp_getmaxphase(struct ptp_clock_info *info __always_unused)
+{
+ /* HW limits TIE write to +-1 second. Return the constant HW
+ * limit and let adjphase handle mode-specific checks.
+ */
+ return NSEC_PER_SEC - 1;
+}
+
+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;
+
+ snprintf(zldpll->ptp_info.name, sizeof(zldpll->ptp_info.name),
+ "zl3073x-dpll%u", zldpll->id);
+
+ ptp_clock = ptp_clock_register(&zldpll->ptp_info, zldev->dev);
+ if (IS_ERR(ptp_clock)) {
+ dev_err(zldev->dev, "Failed to register PTP clock for DPLL%u\n",
+ zldpll->id);
+ return PTR_ERR(ptp_clock);
+ }
+
+ zldpll->ptp_clock = ptp_clock;
+
+ return 0;
+}
+
+/**
+ * zl3073x_dpll_ptp_unregister - unregister PTP clock for a DPLL channel
+ * @zldpll: DPLL channel to unregister PTP clock for
+ */
+static void zl3073x_dpll_ptp_unregister(struct zl3073x_dpll *zldpll)
+{
+ if (!IS_ERR_OR_NULL(zldpll->ptp_clock)) {
+ ptp_clock_unregister(zldpll->ptp_clock);
+ zldpll->ptp_clock = NULL;
+ }
}
/**
@@ -2398,6 +2724,48 @@ zl3073x_dpll_ref_sync_pairs_register(struct zl3073x_dpll *zldpll)
return 0;
}
+/**
+ * zl3073x_dpll_alloc - allocate DPLL device
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel number
+ *
+ * Allocates DPLL device structure for given DPLL channel.
+ *
+ * Return: pointer to DPLL device on success, error pointer on error
+ */
+struct zl3073x_dpll *
+zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)
+{
+ struct zl3073x_dpll *zldpll;
+
+ zldpll = kzalloc_obj(*zldpll);
+ if (!zldpll)
+ return ERR_PTR(-ENOMEM);
+
+ zldpll->dev = zldev;
+ zldpll->id = ch;
+ zldpll->ptp_info = zl3073x_dpll_ptp_clock_info;
+ mutex_init(&zldpll->lock);
+ INIT_LIST_HEAD(&zldpll->pins);
+
+ return zldpll;
+}
+
+/**
+ * zl3073x_dpll_free - free DPLL device
+ * @zldpll: pointer to zl3073x_dpll structure
+ *
+ * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc.
+ */
+void
+zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
+{
+ WARN(zldpll->dpll_dev, "DPLL device is still registered\n");
+
+ mutex_destroy(&zldpll->lock);
+ kfree(zldpll);
+}
+
/**
* zl3073x_dpll_register - register DPLL device and all its pins
* @zldpll: pointer to zl3073x_dpll structure
@@ -2428,6 +2796,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 +2816,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/03 18:36 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 17668 output: 39 thoughts: 500 Thoughts: **Fuzzing the Azurite DPLL Driver: A Worthwhile Endeavor?** Okay, so I'm looking at this patch series adding PTP clock support to the Microchip Azurite DPLL driver (`drivers/dpll/zl3073x/`). It looks like a solid functional change, which immediately makes me consider fuzzing. My first thought is, is this worth the effort? I'm thinking, this is a DPLL driver, so it's probably I2C or SPI, and sure enough, `ZL3073X_I2C` and `ZL3073X_SPI` confirm that. That's good, because that opens up possibilities for emulation or at least access within QEMU. Now the question is, *can* syzkaller reach it? Syzkaller can definitely fuzz I2C and SPI devices. I know it can inject devices via sysfs, use virtual buses, or just directly interact with `/dev/i2c-X` or `/dev/spiX`. But wait, this is a *kernel driver*, so can syzkaller reach it *after* it's loaded? Syzkaller has some support for I2C/SPI device instantiation already if it knows how, or if it's part of the descriptions. But hang on, there's a constraint: the prompt specifically says "Return WorthFuzzing=false if ... PCIe hardware cards not emulated in standard QEMU." This is I2C/SPI, not PCIe, so that shouldn't be a problem. That's a huge relief. Usually, I2C/SPI drivers are fine as long as they're in the device tree or can be created through sysfs. Syzkaller even uses `new_device` to probe I2C devices! I need to confirm that syzkaller can probe this driver. The driver uses regmap over I2C/SPI so let's check `drivers/dpll/zl3073x/` in the kernel to confirm. I think it looks promising, so let's dig into that directory to check if this is fuzzable. |
| 4/2 | 2026/08/03 18:36 | tool | grepper |
0mArgs:{
"Expression": "zl3073x",
"PathPrefix": "drivers/dpll/zl3073x/"
}Results: {
"Output": "\nFull output is too long, showing 500 out of 4487 lines.\nUse more precise expression if possible.\n\ndrivers/dpll/zl3073x/Kconfig=3=config ZL3073X\n--\ndrivers/dpll/zl3073x/Kconfig-14-\t To compile this driver as a module, choose M here. The module\ndrivers/dpll/zl3073x/Kconfig:15:\t will be called zl3073x.\ndrivers/dpll/zl3073x/Kconfig-16-\ndrivers/dpll/zl3073x/Kconfig=17=config ZL3073X_I2C\n--\ndrivers/dpll/zl3073x/Kconfig-26-\t To compile this driver as a module, choose M here: the module will\ndrivers/dpll/zl3073x/Kconfig:27:\t be called zl3073x_i2c.\ndrivers/dpll/zl3073x/Kconfig-28-\ndrivers/dpll/zl3073x/Kconfig=29=config ZL3073X_SPI\n--\ndrivers/dpll/zl3073x/Kconfig-38-\t To compile this driver as a module, choose M here: the module will\ndrivers/dpll/zl3073x/Kconfig:39:\t be called zl3073x_spi.\n--\ndrivers/dpll/zl3073x/Makefile-2-\ndrivers/dpll/zl3073x/Makefile:3:obj-$(CONFIG_ZL3073X)\t\t+= zl3073x.o\ndrivers/dpll/zl3073x/Makefile:4:zl3073x-objs\t\t\t:= chan.o core.o devlink.o dpll.o\t\\\ndrivers/dpll/zl3073x/Makefile-5-\t\t\t\t flash.o fw.o out.o prop.o ref.o synth.o\ndrivers/dpll/zl3073x/Makefile-6-\ndrivers/dpll/zl3073x/Makefile:7:obj-$(CONFIG_ZL3073X_I2C)\t+= zl3073x_i2c.o\ndrivers/dpll/zl3073x/Makefile:8:zl3073x_i2c-objs\t\t:= i2c.o\ndrivers/dpll/zl3073x/Makefile-9-\ndrivers/dpll/zl3073x/Makefile:10:obj-$(CONFIG_ZL3073X_SPI)\t+= zl3073x_spi.o\ndrivers/dpll/zl3073x/Makefile:11:zl3073x_spi-objs\t\t:= spi.o\n--\ndrivers/dpll/zl3073x/chan.c-13-/**\ndrivers/dpll/zl3073x/chan.c:14: * zl3073x_chan_state_update - update DPLL channel status from HW\ndrivers/dpll/zl3073x/chan.c:15: * @zldev: pointer to zl3073x_dev structure\ndrivers/dpll/zl3073x/chan.c-16- * @index: DPLL channel index\n--\ndrivers/dpll/zl3073x/chan.c-19- */\ndrivers/dpll/zl3073x/chan.c:20:int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)\ndrivers/dpll/zl3073x/chan.c-21-{\ndrivers/dpll/zl3073x/chan.c:22:\tstruct zl3073x_chan *chan = \u0026zldev-\u003echan[index];\ndrivers/dpll/zl3073x/chan.c-23-\tu64 val;\n--\ndrivers/dpll/zl3073x/chan.c-25-\ndrivers/dpll/zl3073x/chan.c:26:\trc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(index),\ndrivers/dpll/zl3073x/chan.c-27-\t\t\t \u0026chan-\u003emon_status);\n--\ndrivers/dpll/zl3073x/chan.c-30-\ndrivers/dpll/zl3073x/chan.c:31:\trc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),\ndrivers/dpll/zl3073x/chan.c-32-\t\t\t \u0026chan-\u003erefsel_status);\n--\ndrivers/dpll/zl3073x/chan.c-38-\t */\ndrivers/dpll/zl3073x/chan.c:39:\tif (!zl3073x_chan_is_locked(chan)) {\ndrivers/dpll/zl3073x/chan.c:40:\t\tif (!zl3073x_chan_mode_is_nco(chan))\ndrivers/dpll/zl3073x/chan.c-41-\t\t\tchan-\u003edf_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;\n--\ndrivers/dpll/zl3073x/chan.c-44-\ndrivers/dpll/zl3073x/chan.c:45:\trc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),\ndrivers/dpll/zl3073x/chan.c-46-\t\t\t\t ZL_DPLL_DF_READ_SEM,\n--\ndrivers/dpll/zl3073x/chan.c-50-\ndrivers/dpll/zl3073x/chan.c:51:\trc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index),\ndrivers/dpll/zl3073x/chan.c-52-\t\t\t ZL_DPLL_DF_READ_SEM | ZL_DPLL_DF_READ_REF_OFST);\n--\ndrivers/dpll/zl3073x/chan.c-55-\ndrivers/dpll/zl3073x/chan.c:56:\trc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),\ndrivers/dpll/zl3073x/chan.c-57-\t\t\t\t ZL_DPLL_DF_READ_SEM,\n--\ndrivers/dpll/zl3073x/chan.c-61-\ndrivers/dpll/zl3073x/chan.c:62:\trc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), \u0026val);\ndrivers/dpll/zl3073x/chan.c-63-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-71-/**\ndrivers/dpll/zl3073x/chan.c:72: * zl3073x_chan_nco_mode_set - switch DPLL channel to NCO mode\ndrivers/dpll/zl3073x/chan.c:73: * @zldev: pointer to zl3073x_dev structure\ndrivers/dpll/zl3073x/chan.c-74- * @index: DPLL channel index\n--\ndrivers/dpll/zl3073x/chan.c-82- */\ndrivers/dpll/zl3073x/chan.c:83:int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index)\ndrivers/dpll/zl3073x/chan.c-84-{\ndrivers/dpll/zl3073x/chan.c:85:\tstruct zl3073x_chan *chan = \u0026zldev-\u003echan[index];\ndrivers/dpll/zl3073x/chan.c-86-\tu8 prev_mode, df_read;\n--\ndrivers/dpll/zl3073x/chan.c-89-\ndrivers/dpll/zl3073x/chan.c:90:\tprev_mode = zl3073x_chan_mode_get(chan);\ndrivers/dpll/zl3073x/chan.c-91-\n--\ndrivers/dpll/zl3073x/chan.c-96-\tif (prev_mode == ZL_DPLL_MODE_REFSEL_MODE_FREERUN) {\ndrivers/dpll/zl3073x/chan.c:97:\t\tzl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO);\ndrivers/dpll/zl3073x/chan.c-98-\ndrivers/dpll/zl3073x/chan.c:99:\t\trc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),\ndrivers/dpll/zl3073x/chan.c-100-\t\t\t\t chan-\u003emode_refsel);\ndrivers/dpll/zl3073x/chan.c-101-\t\tif (rc) {\ndrivers/dpll/zl3073x/chan.c:102:\t\t\tzl3073x_chan_mode_set(chan, prev_mode);\ndrivers/dpll/zl3073x/chan.c-103-\t\t\treturn rc;\n--\ndrivers/dpll/zl3073x/chan.c-120-\t\t FIELD_PREP(ZL_DPLL_DF_READ_CMD, ZL_DPLL_DF_READ_CMD_ACC_I);\ndrivers/dpll/zl3073x/chan.c:121:\trc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index), df_read);\ndrivers/dpll/zl3073x/chan.c-122-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-130-\ndrivers/dpll/zl3073x/chan.c:131:\tzl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO);\ndrivers/dpll/zl3073x/chan.c:132:\trc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),\ndrivers/dpll/zl3073x/chan.c-133-\t\t\t chan-\u003emode_refsel);\ndrivers/dpll/zl3073x/chan.c-134-\tif (rc) {\ndrivers/dpll/zl3073x/chan.c:135:\t\tzl3073x_chan_mode_set(chan, prev_mode);\ndrivers/dpll/zl3073x/chan.c-136-\t\treturn rc;\n--\ndrivers/dpll/zl3073x/chan.c-147-\t */\ndrivers/dpll/zl3073x/chan.c:148:\trc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), \u0026val);\ndrivers/dpll/zl3073x/chan.c-149-\tif (rc) {\n--\ndrivers/dpll/zl3073x/chan.c-161-/**\ndrivers/dpll/zl3073x/chan.c:162: * zl3073x_chan_state_fetch - fetch DPLL channel state from hardware\ndrivers/dpll/zl3073x/chan.c:163: * @zldev: pointer to zl3073x_dev structure\ndrivers/dpll/zl3073x/chan.c-164- * @index: DPLL channel index to fetch state for\n--\ndrivers/dpll/zl3073x/chan.c-170- */\ndrivers/dpll/zl3073x/chan.c:171:int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)\ndrivers/dpll/zl3073x/chan.c-172-{\ndrivers/dpll/zl3073x/chan.c:173:\tstruct zl3073x_chan *chan = \u0026zldev-\u003echan[index];\ndrivers/dpll/zl3073x/chan.c-174-\tint rc, i;\ndrivers/dpll/zl3073x/chan.c-175-\ndrivers/dpll/zl3073x/chan.c:176:\trc = zl3073x_read_u8(zldev, ZL_REG_DPLL_CTRL(index), \u0026chan-\u003ectrl);\ndrivers/dpll/zl3073x/chan.c-177-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-179-\ndrivers/dpll/zl3073x/chan.c:180:\trc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),\ndrivers/dpll/zl3073x/chan.c-181-\t\t\t \u0026chan-\u003emode_refsel);\n--\ndrivers/dpll/zl3073x/chan.c-185-\tdev_dbg(zldev-\u003edev, \"DPLL%u mode: %u, ref: %u\\n\", index,\ndrivers/dpll/zl3073x/chan.c:186:\t\tzl3073x_chan_mode_get(chan), zl3073x_chan_ref_get(chan));\ndrivers/dpll/zl3073x/chan.c-187-\ndrivers/dpll/zl3073x/chan.c:188:\trc = zl3073x_chan_state_update(zldev, index);\ndrivers/dpll/zl3073x/chan.c-189-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-195-\t */\ndrivers/dpll/zl3073x/chan.c:196:\tif (zl3073x_chan_mode_is_nco(chan))\ndrivers/dpll/zl3073x/chan.c-197-\t\tchan-\u003edf_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;\n--\ndrivers/dpll/zl3073x/chan.c-200-\t\t\"DPLL%u lock_state: %u, ho: %u, sel_state: %u, sel_ref: %u\\n\",\ndrivers/dpll/zl3073x/chan.c:201:\t\tindex, zl3073x_chan_lock_state_get(chan),\ndrivers/dpll/zl3073x/chan.c:202:\t\tzl3073x_chan_is_ho_ready(chan) ? 1 : 0,\ndrivers/dpll/zl3073x/chan.c:203:\t\tzl3073x_chan_refsel_state_get(chan),\ndrivers/dpll/zl3073x/chan.c:204:\t\tzl3073x_chan_refsel_ref_get(chan));\ndrivers/dpll/zl3073x/chan.c-205-\n--\ndrivers/dpll/zl3073x/chan.c-208-\t/* Read DPLL configuration from mailbox */\ndrivers/dpll/zl3073x/chan.c:209:\trc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD,\ndrivers/dpll/zl3073x/chan.c-210-\t\t\t ZL_REG_DPLL_MB_MASK, BIT(index));\n--\ndrivers/dpll/zl3073x/chan.c-215-\tfor (i = 0; i \u003c ARRAY_SIZE(chan-\u003eref_prio); i++) {\ndrivers/dpll/zl3073x/chan.c:216:\t\trc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(i),\ndrivers/dpll/zl3073x/chan.c-217-\t\t\t\t \u0026chan-\u003eref_prio[i]);\n--\ndrivers/dpll/zl3073x/chan.c-225-/**\ndrivers/dpll/zl3073x/chan.c:226: * zl3073x_chan_state_get - get current DPLL channel state\ndrivers/dpll/zl3073x/chan.c:227: * @zldev: pointer to zl3073x_dev structure\ndrivers/dpll/zl3073x/chan.c-228- * @index: DPLL channel index to get state for\n--\ndrivers/dpll/zl3073x/chan.c-231- */\ndrivers/dpll/zl3073x/chan.c:232:const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,\ndrivers/dpll/zl3073x/chan.c-233-\t\t\t\t\t\t u8 index)\n--\ndrivers/dpll/zl3073x/chan.c-238-/**\ndrivers/dpll/zl3073x/chan.c:239: * zl3073x_chan_tod_ready_wait - wait for ToD semaphore to clear\ndrivers/dpll/zl3073x/chan.c:240: * @zldev: pointer to zl3073x device\ndrivers/dpll/zl3073x/chan.c-241- * @ch: DPLL channel index\n--\ndrivers/dpll/zl3073x/chan.c-247- */\ndrivers/dpll/zl3073x/chan.c:248:int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch)\ndrivers/dpll/zl3073x/chan.c-249-{\n--\ndrivers/dpll/zl3073x/chan.c-253-\ndrivers/dpll/zl3073x/chan.c:254:\trc = zl3073x_read_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), \u0026tod_ctrl);\ndrivers/dpll/zl3073x/chan.c-255-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-266-\ndrivers/dpll/zl3073x/chan.c:267:\trc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch),\ndrivers/dpll/zl3073x/chan.c-268-\t\t\t\t ZL_DPLL_TOD_CTRL_SEM, timeout);\n--\ndrivers/dpll/zl3073x/chan.c-273-/**\ndrivers/dpll/zl3073x/chan.c:274: * zl3073x_chan_tod_ctrl - issue ToD command\ndrivers/dpll/zl3073x/chan.c:275: * @zldev: pointer to zl3073x device\ndrivers/dpll/zl3073x/chan.c-276- * @ch: DPLL channel index\n--\ndrivers/dpll/zl3073x/chan.c-284- */\ndrivers/dpll/zl3073x/chan.c:285:static int zl3073x_chan_tod_ctrl(struct zl3073x_dev *zldev, u8 ch, u8 cmd)\ndrivers/dpll/zl3073x/chan.c-286-{\ndrivers/dpll/zl3073x/chan.c:287:\treturn zl3073x_write_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch),\ndrivers/dpll/zl3073x/chan.c-288-\t\t\t\tZL_DPLL_TOD_CTRL_SEM | cmd);\n--\ndrivers/dpll/zl3073x/chan.c-291-/**\ndrivers/dpll/zl3073x/chan.c:292: * zl3073x_chan_tod_read - read ToD registers after issuing a command\ndrivers/dpll/zl3073x/chan.c:293: * @zldev: pointer to zl3073x device\ndrivers/dpll/zl3073x/chan.c-294- * @ch: DPLL channel index\n--\ndrivers/dpll/zl3073x/chan.c-298- *\ndrivers/dpll/zl3073x/chan.c:299: * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.\ndrivers/dpll/zl3073x/chan.c-300- * Return: 0 on success, \u003c0 on error\ndrivers/dpll/zl3073x/chan.c-301- */\ndrivers/dpll/zl3073x/chan.c:302:int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch,\ndrivers/dpll/zl3073x/chan.c-303-\t\t\t bool next_hz, struct timespec64 *ts,\n--\ndrivers/dpll/zl3073x/chan.c-316-\t/* Wait for any previous ToD operation to complete */\ndrivers/dpll/zl3073x/chan.c:317:\trc = zl3073x_chan_tod_ready_wait(zldev, ch);\ndrivers/dpll/zl3073x/chan.c-318-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-321-\tptp_read_system_prets(sts);\ndrivers/dpll/zl3073x/chan.c:322:\trc = zl3073x_chan_tod_ctrl(zldev, ch, cmd);\ndrivers/dpll/zl3073x/chan.c-323-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-325-\ndrivers/dpll/zl3073x/chan.c:326:\trc = zl3073x_chan_tod_ready_wait(zldev, ch);\ndrivers/dpll/zl3073x/chan.c-327-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-330-\ndrivers/dpll/zl3073x/chan.c:331:\trc = zl3073x_read_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), \u0026sec);\ndrivers/dpll/zl3073x/chan.c-332-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-335-\t/* HW nanoseconds are always in [0, NSEC_PER_SEC) range */\ndrivers/dpll/zl3073x/chan.c:336:\trc = zl3073x_read_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), \u0026nsec);\ndrivers/dpll/zl3073x/chan.c-337-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-346-/**\ndrivers/dpll/zl3073x/chan.c:347: * zl3073x_chan_tod_write - write ToD registers and trigger 1 Hz update\ndrivers/dpll/zl3073x/chan.c:348: * @zldev: pointer to zl3073x device\ndrivers/dpll/zl3073x/chan.c-349- * @ch: DPLL channel index\n--\ndrivers/dpll/zl3073x/chan.c-351- *\ndrivers/dpll/zl3073x/chan.c:352: * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.\ndrivers/dpll/zl3073x/chan.c-353- * Return: 0 on success, \u003c0 on error\ndrivers/dpll/zl3073x/chan.c-354- */\ndrivers/dpll/zl3073x/chan.c:355:int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch,\ndrivers/dpll/zl3073x/chan.c-356-\t\t\t struct timespec64 ts)\n--\ndrivers/dpll/zl3073x/chan.c-360-\t/* Wait for any previous ToD operation to complete */\ndrivers/dpll/zl3073x/chan.c:361:\trc = zl3073x_chan_tod_ready_wait(zldev, ch);\ndrivers/dpll/zl3073x/chan.c-362-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-364-\ndrivers/dpll/zl3073x/chan.c:365:\trc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), ts.tv_sec);\ndrivers/dpll/zl3073x/chan.c-366-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-368-\ndrivers/dpll/zl3073x/chan.c:369:\trc = zl3073x_write_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), ts.tv_nsec);\ndrivers/dpll/zl3073x/chan.c-370-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-372-\ndrivers/dpll/zl3073x/chan.c:373:\treturn zl3073x_chan_tod_ctrl(zldev, ch,\ndrivers/dpll/zl3073x/chan.c-374-\t\t\t\t ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ);\n--\ndrivers/dpll/zl3073x/chan.c-377-/**\ndrivers/dpll/zl3073x/chan.c:378: * zl3073x_chan_tod_adjust - atomic ToD read-modify-write with rollover guard\ndrivers/dpll/zl3073x/chan.c:379: * @zldev: pointer to zl3073x device\ndrivers/dpll/zl3073x/chan.c-380- * @ch: DPLL channel index\n--\ndrivers/dpll/zl3073x/chan.c-389- *\ndrivers/dpll/zl3073x/chan.c:390: * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.\ndrivers/dpll/zl3073x/chan.c-391- * Return: 0 on success, \u003c0 on error\ndrivers/dpll/zl3073x/chan.c-392- */\ndrivers/dpll/zl3073x/chan.c:393:int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch,\ndrivers/dpll/zl3073x/chan.c-394-\t\t\t struct timespec64 delta)\n--\ndrivers/dpll/zl3073x/chan.c-400-\t/* Read predicted ToD at next 1 Hz tick */\ndrivers/dpll/zl3073x/chan.c:401:\trc = zl3073x_chan_tod_read(zldev, ch, true, \u0026ts_next, NULL);\ndrivers/dpll/zl3073x/chan.c-402-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-405-\t/* Read current ToD to determine remaining margin */\ndrivers/dpll/zl3073x/chan.c:406:\trc = zl3073x_chan_tod_read(zldev, ch, false, \u0026ts_cur, NULL);\ndrivers/dpll/zl3073x/chan.c-407-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-425-\ndrivers/dpll/zl3073x/chan.c:426:\treturn zl3073x_chan_tod_write(zldev, ch, ts_next);\ndrivers/dpll/zl3073x/chan.c-427-}\n--\ndrivers/dpll/zl3073x/chan.c-429-/**\ndrivers/dpll/zl3073x/chan.c:430: * zl3073x_chan_df_offset_set - write delta frequency offset to hardware\ndrivers/dpll/zl3073x/chan.c:431: * @zldev: pointer to zl3073x device\ndrivers/dpll/zl3073x/chan.c-432- * @ch: DPLL channel index\n--\ndrivers/dpll/zl3073x/chan.c-437- */\ndrivers/dpll/zl3073x/chan.c:438:int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset)\ndrivers/dpll/zl3073x/chan.c-439-{\n--\ndrivers/dpll/zl3073x/chan.c-441-\ndrivers/dpll/zl3073x/chan.c:442:\trc = zl3073x_write_u48(zldev, ZL_REG_DPLL_DF_OFFSET(ch), offset);\ndrivers/dpll/zl3073x/chan.c-443-\tif (!rc)\n--\ndrivers/dpll/zl3073x/chan.c-449-/**\ndrivers/dpll/zl3073x/chan.c:450: * zl3073x_chan_tie_write - adjust DPLL phase using TIE write\ndrivers/dpll/zl3073x/chan.c:451: * @zldev: pointer to zl3073x device\ndrivers/dpll/zl3073x/chan.c-452- * @ch: DPLL channel index\n--\ndrivers/dpll/zl3073x/chan.c-459- */\ndrivers/dpll/zl3073x/chan.c:460:int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns)\ndrivers/dpll/zl3073x/chan.c-461-{\n--\ndrivers/dpll/zl3073x/chan.c-470-\t/* Wait for any previous TIE operation to complete */\ndrivers/dpll/zl3073x/chan.c:471:\trc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TIE_CTRL,\ndrivers/dpll/zl3073x/chan.c-472-\t\t\t\t ZL_DPLL_TIE_CTRL_OP,\n--\ndrivers/dpll/zl3073x/chan.c-479-\ndrivers/dpll/zl3073x/chan.c:480:\trc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TIE_DATA(ch), tie_data);\ndrivers/dpll/zl3073x/chan.c-481-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-483-\ndrivers/dpll/zl3073x/chan.c:484:\trc = zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL_MASK, BIT(ch));\ndrivers/dpll/zl3073x/chan.c-485-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-487-\ndrivers/dpll/zl3073x/chan.c:488:\treturn zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL,\ndrivers/dpll/zl3073x/chan.c-489-\t\t\t\tZL_DPLL_TIE_CTRL_OP_WR);\n--\ndrivers/dpll/zl3073x/chan.c-492-/**\ndrivers/dpll/zl3073x/chan.c:493: * zl3073x_chan_phase_step - execute one output phase step operation\ndrivers/dpll/zl3073x/chan.c:494: * @zldev: pointer to zl3073x device\ndrivers/dpll/zl3073x/chan.c-495- * @ch: DPLL channel index\n--\ndrivers/dpll/zl3073x/chan.c-504- */\ndrivers/dpll/zl3073x/chan.c:505:int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch,\ndrivers/dpll/zl3073x/chan.c-506-\t\t\t u16 out_mask, s32 step_cycles,\n--\ndrivers/dpll/zl3073x/chan.c-514-\t/* Wait for any previous phase step operation to complete */\ndrivers/dpll/zl3073x/chan.c:515:\trc = zl3073x_poll_zero_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL,\ndrivers/dpll/zl3073x/chan.c-516-\t\t\t\t ZL_OUTPUT_PHASE_STEP_CTRL_OP,\n--\ndrivers/dpll/zl3073x/chan.c-520-\ndrivers/dpll/zl3073x/chan.c:521:\trc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_STEP_DATA,\ndrivers/dpll/zl3073x/chan.c-522-\t\t\t step_cycles);\n--\ndrivers/dpll/zl3073x/chan.c-525-\ndrivers/dpll/zl3073x/chan.c:526:\trc = zl3073x_write_u16(zldev, ZL_REG_OUTPUT_PHASE_STEP_MASK, out_mask);\ndrivers/dpll/zl3073x/chan.c-527-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-529-\ndrivers/dpll/zl3073x/chan.c:530:\trc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_NUMBER, 1);\ndrivers/dpll/zl3073x/chan.c-531-\tif (rc)\n--\ndrivers/dpll/zl3073x/chan.c-539-\ndrivers/dpll/zl3073x/chan.c:540:\treturn zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL, ctrl);\ndrivers/dpll/zl3073x/chan.c-541-}\n--\ndrivers/dpll/zl3073x/chan.c-543-/**\ndrivers/dpll/zl3073x/chan.c:544: * zl3073x_chan_state_set - commit DPLL channel state changes to hardware\ndrivers/dpll/zl3073x/chan.c:545: * @zldev: pointer to zl3073x_dev structure\ndrivers/dpll/zl3073x/chan.c-546- * @index: DPLL channel index to set state for\n--\ndrivers/dpll/zl3073x/chan.c-555- */\ndrivers/dpll/zl3073x/chan.c:556:int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,\ndrivers/dpll/zl3073x/chan.c:557:\t\t\t const struct zl3073x_chan *chan)\ndrivers/dpll/zl3073x/chan.c-558-{\ndrivers/dpll/zl3073x/chan.c:559:\tstruct zl3073x_chan *dchan = \u0026zldev-\u003echan[index];\ndrivers/dpll/zl3073x/chan.c-560-\tint rc, i;\n--\ndrivers/dpll/zl3073x/chan.c-567-\tif (dchan-\u003ectrl != chan-\u003ectrl) {\ndrivers/dpll/zl3073x/chan.c:568:\t\trc = zl3073x_write_u8(zldev, ZL_REG_DPLL_CTRL(index),\ndrivers/dpll/zl3073x/chan.c-569-\t\t\t\t chan-\u003ectrl);\n--\ndrivers/dpll/zl3073x/chan.c-575-\tif (dchan-\u003emode_refsel != chan-\u003emode_refsel) {\ndrivers/dpll/zl3073x/chan.c:576:\t\trc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),\ndrivers/dpll/zl3073x/chan.c-577-\t\t\t\t chan-\u003emode_refsel);\n--\ndrivers/dpll/zl3073x/chan.c-591-\t/* Read DPLL configuration into mailbox */\ndrivers/dpll/zl3073x/chan.c:592:\trc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD,\ndrivers/dpll/zl3073x/chan.c-593-\t\t\t ZL_REG_DPLL_MB_MASK, BIT(index));\n--\ndrivers/dpll/zl3073x/chan.c-599-\t\tif (dchan-\u003eref_prio[i] != chan-\u003eref_prio[i]) {\ndrivers/dpll/zl3073x/chan.c:600:\t\t\trc = zl3073x_write_u8(zldev,\ndrivers/dpll/zl3073x/chan.c-601-\t\t\t\t\t ZL_REG_DPLL_REF_PRIO(i),\n--\ndrivers/dpll/zl3073x/chan.c-608-\t/* Commit DPLL configuration */\ndrivers/dpll/zl3073x/chan.c:609:\trc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_WR,\ndrivers/dpll/zl3073x/chan.c-610-\t\t\t ZL_REG_DPLL_MB_MASK, BIT(index));\n--\ndrivers/dpll/zl3073x/chan.h=13=struct ptp_system_timestamp;\ndrivers/dpll/zl3073x/chan.h:14:struct zl3073x_dev;\ndrivers/dpll/zl3073x/chan.h-15-\ndrivers/dpll/zl3073x/chan.h-16-/**\ndrivers/dpll/zl3073x/chan.h:17: * struct zl3073x_chan - DPLL channel state\ndrivers/dpll/zl3073x/chan.h-18- * @ctrl: DPLL control register value\n--\ndrivers/dpll/zl3073x/chan.h-24- */\ndrivers/dpll/zl3073x/chan.h:25:struct zl3073x_chan {\ndrivers/dpll/zl3073x/chan.h-26-\tstruct_group(cfg,\n--\ndrivers/dpll/zl3073x/chan.h-37-\ndrivers/dpll/zl3073x/chan.h:38:int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index);\ndrivers/dpll/zl3073x/chan.h:39:const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,\ndrivers/dpll/zl3073x/chan.h-40-\t\t\t\t\t\t u8 index);\ndrivers/dpll/zl3073x/chan.h:41:int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,\ndrivers/dpll/zl3073x/chan.h:42:\t\t\t const struct zl3073x_chan *chan);\ndrivers/dpll/zl3073x/chan.h-43-\ndrivers/dpll/zl3073x/chan.h:44:int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index);\ndrivers/dpll/zl3073x/chan.h:45:int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index);\ndrivers/dpll/zl3073x/chan.h-46-\ndrivers/dpll/zl3073x/chan.h:47:int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch);\ndrivers/dpll/zl3073x/chan.h:48:int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch,\ndrivers/dpll/zl3073x/chan.h-49-\t\t\t bool next_hz, struct timespec64 *ts,\ndrivers/dpll/zl3073x/chan.h-50-\t\t\t struct ptp_system_timestamp *sts);\ndrivers/dpll/zl3073x/chan.h:51:int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch,\ndrivers/dpll/zl3073x/chan.h-52-\t\t\t struct timespec64 ts);\ndrivers/dpll/zl3073x/chan.h:53:int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch,\ndrivers/dpll/zl3073x/chan.h-54-\t\t\t struct timespec64 delta);\ndrivers/dpll/zl3073x/chan.h:55:int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch,\ndrivers/dpll/zl3073x/chan.h-56-\t\t\t u16 out_mask, s32 step_cycles, bool tod_step);\ndrivers/dpll/zl3073x/chan.h-57-\ndrivers/dpll/zl3073x/chan.h:58:int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset);\ndrivers/dpll/zl3073x/chan.h-59-\ndrivers/dpll/zl3073x/chan.h:60:int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns);\ndrivers/dpll/zl3073x/chan.h-61-\ndrivers/dpll/zl3073x/chan.h-62-/**\ndrivers/dpll/zl3073x/chan.h:63: * zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference\ndrivers/dpll/zl3073x/chan.h-64- * @chan: pointer to channel state\n--\ndrivers/dpll/zl3073x/chan.h=68=static inline s64\ndrivers/dpll/zl3073x/chan.h:69:zl3073x_chan_df_offset_get(const struct zl3073x_chan *chan)\ndrivers/dpll/zl3073x/chan.h-70-{\n--\ndrivers/dpll/zl3073x/chan.h-74-/**\ndrivers/dpll/zl3073x/chan.h:75: * zl3073x_chan_mode_get - get DPLL channel operating mode\ndrivers/dpll/zl3073x/chan.h-76- * @chan: pointer to channel state\n--\ndrivers/dpll/zl3073x/chan.h-79- */\ndrivers/dpll/zl3073x/chan.h:80:static inline u8 zl3073x_chan_mode_get(const struct zl3073x_chan *chan)\ndrivers/dpll/zl3073x/chan.h-81-{\n--\ndrivers/dpll/zl3073x/chan.h-85-/**\ndrivers/dpll/zl3073x/chan.h:86: * zl3073x_chan_ref_get - get manually selected reference\ndrivers/dpll/zl3073x/chan.h-87- * @chan: pointer to channel state\n--\ndrivers/dpll/zl3073x/chan.h-90- */\ndrivers/dpll/zl3073x/chan.h:91:static inline u8 zl3073x_chan_ref_get(const struct zl3073x_chan *chan)\ndrivers/dpll/zl3073x/chan.h-92-{\n--\ndrivers/dpll/zl3073x/chan.h-96-/**\ndrivers/dpll/zl3073x/chan.h:97: * zl3073x_chan_mode_set - set DPLL channel operating mode\ndrivers/dpll/zl3073x/chan.h-98- * @chan: pointer to channel state\n--\ndrivers/dpll/zl3073x/chan.h-100- */\ndrivers/dpll/zl3073x/chan.h:101:static inline void zl3073x_chan_mode_set(struct zl3073x_chan *chan, u8 mode)\ndrivers/dpll/zl3073x/chan.h-102-{\n--\ndrivers/dpll/zl3073x/chan.h-106-/**\ndrivers/dpll/zl3073x/chan.h:107: * zl3073x_chan_ref_set - set manually selected reference\ndrivers/dpll/zl3073x/chan.h-108- * @chan: pointer to channel state\n--\ndrivers/dpll/zl3073x/chan.h-110- */\ndrivers/dpll/zl3073x/chan.h:111:static inline void zl3073x_chan_ref_set(struct zl3073x_chan *chan, u8 ref)\ndrivers/dpll/zl3073x/chan.h-112-{\n--\ndrivers/dpll/zl3073x/chan.h-116-/**\ndrivers/dpll/zl3073x/chan.h:117: * zl3073x_chan_ref_prio_get - get reference priority\ndrivers/dpll/zl3073x/chan.h-118- * @chan: pointer to channel state\n--\ndrivers/dpll/zl3073x/chan.h=123=static inline u8\ndrivers/dpll/zl3073x/chan.h:124:zl3073x_chan_ref_prio_get(const struct zl3073x_chan *chan, u8 ref)\ndrivers/dpll/zl3073x/chan.h-125-{\n--\ndrivers/dpll/zl3073x/chan.h-134-/**\ndrivers/dpll/zl3073x/chan.h:135: * zl3073x_chan_ref_prio_set - set reference priority\ndrivers/dpll/zl3073x/chan.h-136- * @chan: pointer to channel state\n--\ndrivers/dpll/zl3073x/chan.h=140=static inline void\ndrivers/dpll/zl3073x/chan.h:141:zl3073x_chan_ref_prio_set(struct zl3073x_chan *chan, u8 ref, u8 prio)\ndrivers/dpll/zl3073x/chan.h-142-{\n--\ndrivers/dpll/zl3073x/chan.h-151-/**\ndrivers/dpll/zl3073x/chan.h:152: * zl3073x_chan_ref_is_selectable - check if reference is selectable\ndrivers/dpll/zl3073x/chan.h-153- * @chan: pointer to channel state\n--\ndrivers/dpll/zl3073x/chan.h=158=static inline bool\ndrivers/dpll/zl3073x/chan.h:159:zl3073x_chan_ref_is_selectable(const struct zl3073x_chan *chan, u8 ref)\ndrivers/dpll/zl3073x/chan.h-160-{\ndrivers/dpll/zl3073x/chan.h:161:\treturn zl3073x_chan_ref_prio_get(chan, ref) != ZL_DPLL_REF_PRIO_NONE;\ndrivers/dpll/zl3073x/chan.h-162-}\n--\ndrivers/dpll/zl3073x/chan.h-164-/**\ndrivers/dpll/zl3073x/chan.h:165: * zl3073x_chan_lock_state_get - get DPLL channel lock state\ndrivers/dpll/zl3073x/chan.h-166- * @chan: pointer to channel state\n--\ndrivers/dpll/zl3073x/chan.h-169- */\ndrivers/dpll/zl3073x/chan.h:170:static inline u8 zl3073x_chan_lock_state_get(const struct zl3073x_chan *chan)\ndrivers/dpll/zl3073x/chan.h-171-{\n--\ndrivers/dpll/zl3073x/chan.h-175-/**\ndrivers/dpll/zl3073x/chan.h:176: * zl3073x_chan_is_locked - check if channel is locked to a reference\ndrivers/dpll/zl3073x/chan.h-177- * @chan: pointer to channel state\n--\ndrivers/dpll/zl3073x/chan.h-180- */\ndrivers/dpll/zl3073x/chan.h:181:static inline bool zl3073x_chan_is_locked(const struct zl3073x_chan *chan)\ndrivers/dpll/zl3073x/chan.h-182-{\ndrivers/dpll/zl3073x/chan.h:183:\tu8 lock_state = zl3073x_chan_lock_state_get(chan);\ndrivers/dpll/zl3073x/chan.h-184-\treturn lock_state == ZL_DPLL_MON_STATUS_STATE_LOCK;\n--\ndrivers/dpll/zl3073x/chan.h-187-/**\ndrivers/dpll/zl3073x/chan.h:188: * zl3073x_chan_mode_is_auto - check if channel is in automatic mode\ndrivers/dpll/zl3073x/chan.h-189- * @chan: pointer to channel state\n--\ndrivers/dpll/zl3073x/chan.h-192- */\ndrivers/dpll/zl3073x/chan.h:193:static inline bool zl3073x_chan_mode_is_auto(const struct zl3073x_chan *chan)\ndrivers/dpll/zl3073x/chan.h-194-{\ndrivers/dpll/zl3073x/chan.h:195:\treturn zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_AUTO;\ndrivers/dpll/zl3073x/chan.h-196-}\n--\ndrivers/dpll/zl3073x/chan.h-198-/**\n\n"
} |
| 5/2 | 2026/08/03 18:36 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33121 output: 40 thoughts: 350 Thoughts: **Fuzzing the Microchip Azurite DPLL Driver** Okay, so I'm looking at this driver for Microchip Azurite DPLL, PTP, and SyncE devices. It registers a PTP clock, which is interesting. My first thought was, is this thing emulated in QEMU? But no, it's a dedicated Microchip Azurite DPLL device, not something generic. Hmm, but it's an I2C or SPI device, right? Now, the next question is can Syzkaller, my favorite fuzzing tool, even *handle* I2C or SPI devices? The good news is, yes, it can! Syzkaller is capable of injecting I2C/SPI devices if it knows the specifics, or even just opening `/dev/i2c-X` and talking to the bus. However, a critical point comes to mind: if the driver isn't loaded in the first place, Syzkaller can't fuzz it. The immediate thought is, can Syzkaller load the driver? It appears Syzkaller *can* load the driver if it's compiled directly into the kernel, using `CONFIG_ZL3073X_I2C=y`, which looks like an option here. Then Syzkaller can try to instantiate it via sysfs `new_device` for I2C. Wait a second. The original prompt talks about "vendor-specific PCIe switch, SmartNIC, or GPU drivers" needing physical PCIe hardware. This is *not* a PCIe card; it's I2C/SPI, so it's a different use case. Okay, is it *worth* fuzzing? Given that it's an I2C device, and Syzkaller can instantiate it using `echo zl3073x > /sys/bus/i2c/devices/i2c-0/new_device`, the answer is probably yes. Let's start by checking if this driver has an I2C ID table. That's the first step to confirm Syzkaller can actually instantiate it. |
| 6/2 | 2026/08/03 18:36 | tool | grepper |
0mArgs:{
"Expression": "i2c_device_id",
"PathPrefix": "drivers/dpll/zl3073x/"
}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"
} |
| 7/2 | 2026/08/03 18:36 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 34130 output: 45 thoughts: 74 Thoughts: **Analyzing the I2C Device Probe Process** Okay, so I see this device has an I2C ID table, which is good. Syzkaller should be able to handle instantiation using `new_device`. That should trigger the driver's probe function. Now I need to consider the probe function itself. Does the probe actually interact with the hardware? Specifically, does it read any registers or exchange any data on the bus? My main concern is, if it does, will it fail gracefully if the expected response isn't received? This is crucial for avoiding any unexpected hangs or errors during fuzzing. I need to make sure the fuzzing process can continue, so I better check the `zl3073x_i2c_probe` function to understand the interaction. |
| 8/2 | 2026/08/03 18:36 | tool | grepper |
0mArgs:{
"Expression": "zl3073x_i2c_probe",
"PathPrefix": "drivers/dpll/zl3073x/"
}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"
} |
| 9/2 | 2026/08/03 18:36 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 34672 output: 45 thoughts: 31 Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person: **Diving into the ZL3073x Probe Functions** Alright, time to dig into the heart of this driver. I need to understand the device initialization and connection process, so I'm thinking I'll start by examining the `zl3073x_i2c_probe` and `zl3073x_dev_probe` functions. I'm assuming that the I2C version is the most generic, as I2C provides the least overhead to the system. From there, I expect `zl3073x_dev_probe` to be the actual main entry point for the device, and potentially the place where other methods of connection are supported. Let's see how the device is enumerated, what resources are requested, and how the driver interacts with the hardware initially. The naming suggests a hierarchical structure, with the I2C-specific probe handling bus initialization and the `dev_probe` taking care of the actual device setup. I'm looking for the standard pattern of resource allocation, device identification, and potential platform-specific initialization. Are they using the Device Tree or direct board definitions for configuration? What about error handling and fallback mechanisms? I'm anticipating that `zl3073x_dev_probe` is where the core functionality, like clock configuration and data path setup, begins. This is where the interesting stuff lies! |
| 10/2 | 2026/08/03 18:36 | tool | read-file |
0mArgs:{
"File": "drivers/dpll/zl3073x/i2c.c",
"FirstLine": 11,
"LineCount": 20
}Results: {
"Contents": " 11:\tstatic int zl3073x_i2c_probe(struct i2c_client *client)\n 12:\t{\n 13:\t\tstruct device *dev = \u0026client-\u003edev;\n 14:\t\tstruct zl3073x_dev *zldev;\n 15:\t\n 16:\t\tzldev = zl3073x_devm_alloc(dev);\n 17:\t\tif (IS_ERR(zldev))\n 18:\t\t\treturn PTR_ERR(zldev);\n 19:\t\n 20:\t\tzldev-\u003eregmap = devm_regmap_init_i2c(client, \u0026zl3073x_regmap_config);\n 21:\t\tif (IS_ERR(zldev-\u003eregmap))\n 22:\t\t\treturn dev_err_probe(dev, PTR_ERR(zldev-\u003eregmap),\n 23:\t\t\t\t\t \"Failed to initialize regmap\\n\");\n 24:\t\n 25:\t\treturn zl3073x_dev_probe(zldev);\n 26:\t}\n 27:\t\n 28:\tstatic const struct i2c_device_id zl3073x_i2c_id[] = {\n 29:\t\t{ .name = \"zl30731\" },\n 30:\t\t{ .name = \"zl30732\" },\n"
} |
| 11/2 | 2026/08/03 18:36 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 35426 output: 43 thoughts: 13 Thoughts: Okay, here's my interpretation of those thoughts, summarized from a professional's perspective: **zl3073x_dev_probe: Deconstructing the Device Probe** Alright, let's break this down. When I see mention of `zl3073x_dev_probe`, my immediate thoughts are around driver initialization and hardware interaction. This suggests a probing function specifically designed to detect and configure a device, likely a ZL3073x series device. My mind immediately starts running through the typical probe sequence: resource allocation, hardware register interrogation, potential power-on sequencing, and finally, registration with the system. I'm mentally stepping through potential failure points. What are the expected hardware responses during this probe? Are there known issues with power-up timing or register access on this particular platform? Is there any documentation readily available for the ZL3073x series or relevant associated device configurations? I’m thinking about the I2C or SPI communication that's likely involved, and mentally verifying that the bus initialization is robust. I'm also considering the implications of this probe's success or failure. What errors could it return? What are the implications for the rest of the system if this probe doesn't succeed? Does it gracefully handle errors? And I'm checking to make sure it reports informative error messages, so debugging is easy. We'd hate to be scratching our heads down the line. I'm checking for dependencies; are there any prerequisite modules or configurations required before this probe can run correctly? What about clock configuration? Does this probe address any clock-related initialization that's critical for the device's functionality? Finally, my thoughts are on scalability and robustness. How is this probe designed to handle different hardware revisions or configurations of the ZL3073x device? Will this device work reliably with the product as it exists? Does it include appropriate error handling to protect the system from unpredictable hardware behavior? In short, my mind is immediately dissecting the `zl3073x_dev_probe` function from a system-level perspective, seeking to understand its purpose, its potential pitfalls, and its integration within the larger architecture. |
| 12/2 | 2026/08/03 18:36 | tool | grepper |
0mArgs:{
"Expression": "zl3073x_dev_probe",
"PathPrefix": "drivers/dpll/zl3073x/"
}Results: {
"Output": "drivers/dpll/zl3073x/core.c=930=zl3073x_devm_dpll_init(struct zl3073x_dev *zldev)\n--\ndrivers/dpll/zl3073x/core.c-977-/**\ndrivers/dpll/zl3073x/core.c:978: * zl3073x_dev_probe - initialize zl3073x device\ndrivers/dpll/zl3073x/core.c-979- * @zldev: pointer to zl3073x device\n--\ndrivers/dpll/zl3073x/core.c-984- */\ndrivers/dpll/zl3073x/core.c:985:int zl3073x_dev_probe(struct zl3073x_dev *zldev)\ndrivers/dpll/zl3073x/core.c-986-{\n--\ndrivers/dpll/zl3073x/core.c-1065-}\ndrivers/dpll/zl3073x/core.c:1066:EXPORT_SYMBOL_NS_GPL(zl3073x_dev_probe, \"ZL3073X\");\ndrivers/dpll/zl3073x/core.c-1067-\n--\ndrivers/dpll/zl3073x/core.h=106=struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev);\ndrivers/dpll/zl3073x/core.h:107:int zl3073x_dev_probe(struct zl3073x_dev *zldev);\ndrivers/dpll/zl3073x/core.h-108-\n--\ndrivers/dpll/zl3073x/i2c.c=11=static int zl3073x_i2c_probe(struct i2c_client *client)\n--\ndrivers/dpll/zl3073x/i2c.c-24-\ndrivers/dpll/zl3073x/i2c.c:25:\treturn zl3073x_dev_probe(zldev);\ndrivers/dpll/zl3073x/i2c.c-26-}\n--\ndrivers/dpll/zl3073x/spi.c=11=static int zl3073x_spi_probe(struct spi_device *spi)\n--\ndrivers/dpll/zl3073x/spi.c-24-\ndrivers/dpll/zl3073x/spi.c:25:\treturn zl3073x_dev_probe(zldev);\ndrivers/dpll/zl3073x/spi.c-26-}\n"
} |
| 13/2 | 2026/08/03 18:36 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 36686 output: 45 thoughts: 16 Thoughts: Okay, let's dive into this. Here's my take on `zl3073x_dev_probe`, as I understand it: **Analyzing the `zl3073x_dev_probe` Function** Alright, so the first thing I see is we're looking at `zl3073x_dev_probe`. That means we're in the realm of device probing, and it's specifically for a `zl3073x` device, which I'm already familiar with the general architecture of. I'm expecting this is where the system attempts to identify, initialize, and register a new `zl3073x` device that's been plugged in or is otherwise becoming available. I'll be looking for things like: * **Resource allocation**: How are they getting the necessary resources, like memory regions, IRQs, I2C or SPI buses, etc.? I want to see if they are using any platform-specific mechanisms or if there's a more generic approach. Any unusual approaches here would be interesting. * **Device identification**: How is the driver verifying the device is *actually* a `zl3073x`? Are they checking a device ID register? Vendor-specific magic? I'm keen to see the approach and any potential for robustness issues. * **Initialization sequence**: What steps are taken to get the device ready for use? This could include power-up sequences, register writes, clock configuration, and possibly firmware loading. I'll be looking at the order of operations, paying attention to any dependencies and potential timing constraints. * **Driver registration**: Once the device is ready, I'll be watching to see how the driver registers itself with the kernel. Is it using the standard device driver model? Are there any custom callbacks or structures? Any custom data structures or interactions will be of interest. * **Error handling**: Where are the error checks? What happens if any of the critical steps fail? I want to assess the robustness of the probe function and see if there are any obvious weaknesses. I'm expecting thorough error handling, especially when interacting with hardware. * **Concurrency**: I'll also glance to see if anything here is susceptible to concurrency issues as I know hardware probing can often have shared resources. Essentially, I'm expecting this function to be a gateway. It should be making sure that the device is actually the device we're expecting, and then getting it ready to be used by the rest of the system. Let's see how they've done it. |
| 14/2 | 2026/08/03 18:36 | tool | read-file |
0mArgs:{
"File": "drivers/dpll/zl3073x/core.c",
"FirstLine": 985,
"LineCount": 100
}Results: {
"Contents": " 985:\tint zl3073x_dev_probe(struct zl3073x_dev *zldev)\n 986:\t{\n 987:\t\tu16 id, revision, fw_ver;\n 988:\t\tunsigned int i;\n 989:\t\tu32 cfg_ver;\n 990:\t\tint rc;\n 991:\t\n 992:\t\t/* Read chip ID */\n 993:\t\trc = zl3073x_read_u16(zldev, ZL_REG_ID, \u0026id);\n 994:\t\tif (rc)\n 995:\t\t\treturn rc;\n 996:\t\n 997:\t\t/* Detect chip variant */\n 998:\t\tfor (i = 0; i \u003c ARRAY_SIZE(zl3073x_chip_ids); i++) {\n 999:\t\t\tif (zl3073x_chip_ids[i].id == id)\n1000:\t\t\t\tbreak;\n1001:\t\t}\n1002:\t\n1003:\t\tif (i == ARRAY_SIZE(zl3073x_chip_ids))\n1004:\t\t\treturn dev_err_probe(zldev-\u003edev, -ENODEV,\n1005:\t\t\t\t\t \"Unknown chip ID: 0x%04x\\n\", id);\n1006:\t\n1007:\t\tzldev-\u003einfo = \u0026zl3073x_chip_ids[i];\n1008:\t\n1009:\t\t/* Read revision, firmware version and custom config version */\n1010:\t\trc = zl3073x_read_u16(zldev, ZL_REG_REVISION, \u0026revision);\n1011:\t\tif (rc)\n1012:\t\t\treturn rc;\n1013:\t\trc = zl3073x_read_u16(zldev, ZL_REG_FW_VER, \u0026fw_ver);\n1014:\t\tif (rc)\n1015:\t\t\treturn rc;\n1016:\t\trc = zl3073x_read_u32(zldev, ZL_REG_CUSTOM_CONFIG_VER, \u0026cfg_ver);\n1017:\t\tif (rc)\n1018:\t\t\treturn rc;\n1019:\t\n1020:\t\tdev_dbg(zldev-\u003edev, \"ChipID(%X), ChipRev(%X), FwVer(%u)\\n\", id,\n1021:\t\t\trevision, fw_ver);\n1022:\t\tdev_dbg(zldev-\u003edev, \"Custom config version: %lu.%lu.%lu.%lu\\n\",\n1023:\t\t\tFIELD_GET(GENMASK(31, 24), cfg_ver),\n1024:\t\t\tFIELD_GET(GENMASK(23, 16), cfg_ver),\n1025:\t\t\tFIELD_GET(GENMASK(15, 8), cfg_ver),\n1026:\t\t\tFIELD_GET(GENMASK(7, 0), cfg_ver));\n1027:\t\n1028:\t\t/* Generate random clock ID as the device has not such property that\n1029:\t\t * could be used for this purpose. A user can later change this value\n1030:\t\t * using devlink.\n1031:\t\t */\n1032:\t\tzldev-\u003eclock_id = get_random_u64();\n1033:\t\n1034:\t\t/* Default phase offset averaging factor */\n1035:\t\tzldev-\u003ephase_avg_factor = 2;\n1036:\t\n1037:\t\t/* Initialize mutex for operations where multiple reads, writes\n1038:\t\t * and/or polls are required to be done atomically.\n1039:\t\t */\n1040:\t\trc = devm_mutex_init(zldev-\u003edev, \u0026zldev-\u003emultiop_lock);\n1041:\t\tif (rc)\n1042:\t\t\treturn dev_err_probe(zldev-\u003edev, rc,\n1043:\t\t\t\t\t \"Failed to initialize mutex\\n\");\n1044:\t\trc = devm_mutex_init(zldev-\u003edev, \u0026zldev-\u003ephase_step_lock);\n1045:\t\tif (rc)\n1046:\t\t\treturn dev_err_probe(zldev-\u003edev, rc,\n1047:\t\t\t\t\t \"Failed to initialize mutex\\n\");\n1048:\t\trc = devm_mutex_init(zldev-\u003edev, \u0026zldev-\u003etie_lock);\n1049:\t\tif (rc)\n1050:\t\t\treturn dev_err_probe(zldev-\u003edev, rc,\n1051:\t\t\t\t\t \"Failed to initialize mutex\\n\");\n1052:\t\n1053:\t\t/* Register DPLL channels */\n1054:\t\trc = zl3073x_devm_dpll_init(zldev);\n1055:\t\tif (rc)\n1056:\t\t\treturn rc;\n1057:\t\n1058:\t\t/* Register the devlink instance and parameters */\n1059:\t\trc = zl3073x_devlink_register(zldev);\n1060:\t\tif (rc)\n1061:\t\t\treturn dev_err_probe(zldev-\u003edev, rc,\n1062:\t\t\t\t\t \"Failed to register devlink instance\\n\");\n1063:\t\n1064:\t\treturn 0;\n1065:\t}\n1066:\tEXPORT_SYMBOL_NS_GPL(zl3073x_dev_probe, \"ZL3073X\");\n1067:\t\n1068:\tMODULE_AUTHOR(\"Ivan Vecera \u003civecera@redhat.com\u003e\");\n1069:\tMODULE_DESCRIPTION(\"Microchip ZL3073x core driver\");\n1070:\tMODULE_LICENSE(\"GPL\");\n"
} |
| 15/2 | 2026/08/03 18:36 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 38810 output: 28 thoughts: 972 Thoughts: **Unreachable Code in a Virtualized Environment** Okay, so I'm looking at this driver for the Microchip ZL3073x DPLL/PTP/SyncE device, and the critical part is that probe function. It starts with a simple `zl3073x_read_u16` call to get the device ID via I2C. That's the crux of it. If that read fails, or if the ID doesn't match a known value, the probe function immediately returns `-ENODEV`. Now, the question is, can Syzkaller emulate this? It doesn't look like Syzkaller has a specific emulation for the ZL3073x, which makes sense; it's a specialized piece of hardware. I know Syzkaller *can* inject I2C devices, but it's not the same as having a proper emulator with a defined register map. It *might* intercept the I2C reads with some kind of fault injection, or maybe a mock, but I don't think Syzkaller has a generic I2C mock that can reply with arbitrary data unless it's using a virtual I2C bus that it controls. I remember that Syzkaller uses `vhci` for USB, `mac80211_hwsim` for wifi, and `vkms` for DRM etc, does it have a virtual I2C bus? There's `i2c-stub`, but Syzkaller doesn't typically use it to mock arbitrary devices with specific register maps unless there's a syzlang description for it. I remember there used to be a `syz_i2c_setup` or something similar, but no. If we're dealing with just an I2C device driver and there's no real hardware, the probe will fail. The I2C read will fail or return garbage. That means the driver won't be instantiated, and, crucially, the new PTP clock code like `zl3073x_dpll_ptp_register` and `zl3073x_dpll_ptp_adjtime` is completely unreachable in a virtualized environment like GCE or QEMU without that physical hardware or an extremely specific emulator. Let's make sure I'm right about this. According to the prompt, code paths that are impossible to reach in virtualized environments like GCE or QEMU are things that require special hardware. It does explicitly call out things like vendor-specific PCIe switch, SmartNIC, or GPU drivers that require physical PCIe cards not emulated in standard QEMU. That's exactly this scenario. This driver targets the ZL3073x, a Microchip Azurite DPLL/PTP/SyncE device. It uses I2C or SPI. In QEMU, unless there's some device model for the ZL3073x (which is highly unlikely—it's a telecom timing chip!), the I2C or SPI reads will always fail or time out. Even if Syzkaller creates an I2C device via sysfs, the I2C transfers will go to a dummy bus (like `i2c-stub` or something similar) which has no such device attached. The I2C controller will NACK, the read will fail, and `zl3073x_read_u16` will return an error. Specifically, the failure will happen in `zl3073x_dev_probe` during the `zl3073x_read_u16` call, or even if the read somehow returns garbage, it will fail at the chip ID check. The bottom line is that Syzkaller cannot bypass this hardware check without an explicit emulator, which means the whole driver, including those PTP functions, is inaccessible. It makes sense to check if there are any syzkaller pseudo-syscalls for I2C. And finally, let's check if there are any syzkaller descriptions for this. |
| 16/2 | 2026/08/03 18:36 | tool | grepper |
0mError:no matches Args: {
"Expression": "zl3073x",
"PathPrefix": "sys/"
}Results: {
"error": "no matches"
} |
| 17/2 | 2026/08/03 18:36 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 40692 output: 129 thoughts: 791 Thoughts: **WorthFuzzing Analysis** Okay, let's break this down. First, I noticed the "sys/" directory search yielded no results, but the prompt flagged that as expected, saying to use `read-syz-spec` and `syz-grepper` instead, though I don't see `syz-grepper` in my tool list. But wait, I don't *need* those tools, because the code seems to be for a standard kernel driver, specifically for a Microchip Azurite DPLL hardware chip. Given that it's hardware-specific and requires I2C/SPI physical hardware, which isn't emulated in standard QEMU, and critically, there's a strict chip ID check in the probe function. The implication is that reaching the PTP clock operations within this is simply impossible in any virtualized environment. So, that's it: `WorthFuzzing` should be `false`. Just to be thorough, let me double-check. Is there some kind of virtual or mock driver that could be used? Nope. Any other entry point? Let's see... The patch touches `drivers/dpll/zl3073x/`. The functions added are all related to time of day (TOD), like `zl3073x_chan_tod_ready_wait`, etc. These TOD functions are called by things like `zl3073x_dpll_ptp_adjtime` - time adjustment, etc. That means these are PTP clock callbacks. Which is registered in `zl3073x_dpll_ptp_register`, which is then called from `zl3073x_dpll_register`, which is then called by `zl3073x_devm_dpll_init`, which in turn, is from `zl3073x_dev_probe`. The problem is `zl3073x_dev_probe` fails early when reading the chip ID. So, none of this is reachable. Therefore, I'm setting `WorthFuzzing=false`. The rationale is straightforward: it's a hardware-specific driver for Microchip Azurite DPLL devices that requires physical I2C/SPI hardware. It has a chip ID check in `probe` that's not emulated in standard QEMU, rendering the modified code unreachable in any virtualized environment. Now, let's review the prompt requirements again, just to be sure. "Return WorthFuzzing=false if the patch only contains: ... Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware ... Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers ... that require physical PCIe hardware cards not emulated in standard QEMU." Perfect match. I'm confident in this conclusion. Okay, time to call `set-results`. |
| 18/2 | 2026/08/03 18:37 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch modifies the Microchip Azurite DPLL driver (zl3073x) to add PTP clock support. This is a hardware-specific driver that communicates over I2C/SPI. During probe, it reads a hardware chip ID and fails if it doesn't match known values. Since this specific hardware is not emulated in standard QEMU, the probe will fail, making the newly added PTP clock operations unreachable in virtualized fuzzing environments.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch modifies the Microchip Azurite DPLL driver (zl3073x) to add PTP clock support. This is a hardware-specific driver that communicates over I2C/SPI. During probe, it reads a hardware chip ID and fails if it doesn't match known values. Since this specific hardware is not emulated in standard QEMU, the probe will fail, making the newly added PTP clock operations unreachable in virtualized fuzzing environments.",
"WorthFuzzing": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|