diff --git a/drivers/dpll/zl3073x/Kconfig b/drivers/dpll/zl3073x/Kconfig index 5bbca1400581..da1731ace15f 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/dpll.c b/drivers/dpll/zl3073x/dpll.c index 83bd3027dbaa..f2e137475b40 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include #include @@ -2313,45 +2315,384 @@ zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev) return zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_CTRL, 0x01); } +/* Maximum frequency adjustment: +-1% of nominal in ppb */ +#define ZL3073X_DPLL_PTP_MAX_ADJ 10000000 + /** - * zl3073x_dpll_alloc - allocate DPLL device - * @zldev: pointer to zl3073x device - * @ch: DPLL channel number + * zl3073x_dpll_ptp_gettimex64 - read current time from ToD counters + * @info: PTP clock info + * @ts: timespec to store current time + * @sts: optional system timestamp pair for cross-timestamping * - * Allocates DPLL device structure for given DPLL channel. - * - * Return: 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_gettimex64(struct ptp_clock_info *info, + struct timespec64 *ts, + struct ptp_system_timestamp *sts) { - 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 zldpll; + return zl3073x_chan_tod_read(zldpll->dev, zldpll->id, false, ts, sts); } /** - * zl3073x_dpll_free - free DPLL device - * @zldpll: pointer to zl3073x_dpll structure + * zl3073x_dpll_ptp_settime64 - set ToD counters to given time + * @info: PTP clock info + * @ts: timespec with time to set * - * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc. + * Return: 0 on success, <0 on error */ -void -zl3073x_dpll_free(struct zl3073x_dpll *zldpll) +static int zl3073x_dpll_ptp_settime64(struct ptp_clock_info *info, + const struct timespec64 *ts) { - WARN(zldpll->dpll_dev, "DPLL device is still registered\n"); + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); - mutex_destroy(&zldpll->lock); - kfree(zldpll); + guard(mutex)(&zldpll->lock); + + return zl3073x_chan_tod_write(zldpll->dev, zldpll->id, *ts); +} + +/** + * zl3073x_dpll_ptp_adjtime_phase_step - adjust sub-second time via phase step + * @zldpll: DPLL channel + * @delta: time adjustment in nanoseconds (must be within (-NSEC_PER_SEC, + * NSEC_PER_SEC)) + * + * Uses the output phase step mechanism with tod_step=1 to adjust both + * the output clock phase and the ToD counter simultaneously. This keeps + * outputs and ToD coherent. Only valid for NCO. + * + * Outputs are grouped by synthesizer since the phase step value is in + * synthesizer clock cycles. The first synth group with enabled outputs + * uses tod_step to adjust both outputs and the ToD counter. Remaining + * groups step outputs only. If no synth has enabled outputs, the ToD + * counter is stepped alone using an empty output mask (the FW uses + * the first enabled synth's period for the conversion). + * + * Return: + * * %0 - success (or partial success if a later synth group + * failed after the first was already stepped) + * * %-EOPNOTSUPP - no synths available + * * negative - error + */ +static int zl3073x_dpll_ptp_adjtime_phase_step(struct zl3073x_dpll *zldpll, + s64 delta) +{ + u16 synth_mask[ZL3073X_NUM_SYNTHS] = {}; + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_synth *synth; + struct zl3073x_dpll_pin *pin; + u32 first_synth_freq = 0; + bool tod_stepped = false; + s32 step_cycles; + u32 synth_freq; + int rc; + u8 i; + + /* Build per-synth output masks from registered output pins */ + list_for_each_entry(pin, &zldpll->pins, list) { + u8 out_id, synth_id; + + if (zl3073x_dpll_is_input_pin(pin)) + continue; + + out_id = zl3073x_output_pin_out_get(pin->id); + + if (!zl3073x_dev_out_is_stepped(zldev, out_id)) + continue; + + synth_id = zl3073x_dev_out_synth_get(zldev, out_id); + if (synth_id >= ZL3073X_NUM_SYNTHS) { + dev_warn(zldev->dev, "Unexpected synth id for OUT%u\n", + out_id); + continue; + } + synth_mask[synth_id] |= BIT(out_id); + } + + /* Process each synth group */ + for (i = 0; i < ZL3073X_NUM_SYNTHS; i++) { + synth = zl3073x_synth_state_get(zldev, i); + if (!zl3073x_synth_is_enabled(synth) || + zl3073x_synth_dpll_get(synth) != zldpll->id) + continue; + + synth_freq = zl3073x_synth_freq_get(synth); + + /* Remember first enabled synth freq for ToD-only fallback */ + if (!first_synth_freq) + first_synth_freq = synth_freq; + + if (!synth_mask[i]) + continue; + + /* Safe for s32: max synth freq is 750 MHz */ + step_cycles = div_s64(delta * synth_freq, NSEC_PER_SEC); + + rc = zl3073x_chan_phase_step(zldev, zldpll->id, + synth_mask[i], step_cycles, + !tod_stepped); + if (rc) { + if (tod_stepped) { + dev_warn(zldev->dev, + "Partial phase step failure\n"); + return 0; + } + return rc; + } + tod_stepped = true; + } + + if (!first_synth_freq) + return -EOPNOTSUPP; + + /* No enabled outputs found; step ToD counter only using the + * first enabled synth's period (empty output mask). + */ + if (!tod_stepped) { + step_cycles = div_s64(delta * first_synth_freq, NSEC_PER_SEC); + return zl3073x_chan_phase_step(zldev, zldpll->id, 0, + step_cycles, true); + } + + return 0; +} + +/** + * zl3073x_dpll_ptp_adjtime - adjust PTP clock time + * @info: PTP clock info + * @delta: time adjustment in nanoseconds + * + * For NCO, large deltas (>= 1 second) are split into a ToD + * read-modify-write for the seconds part and an output phase step for + * the sub-second remainder. Sub-second deltas use phase step directly, + * falling back to ToD read-modify-write if phase step or TIE write + * fails. In AUTO/REFLOCK modes, large deltas are split into ToD + * read-modify-write for seconds and TIE write for the sub-second + * remainder. Sub-second deltas use TIE write directly. + * + * If the seconds part was already committed when the sub-second + * mechanism fails, returns 0 to prevent the PTP servo from retrying + * the full delta and applying seconds again. + * + * Return: 0 on success (or partial success), <0 on error + */ +static int zl3073x_dpll_ptp_adjtime(struct ptp_clock_info *info, s64 delta) +{ + 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; + + if (!delta) + return 0; + + guard(mutex)(&zldpll->lock); + + /* Modes without phase step or TIE use plain ToD adjust */ + chan = zl3073x_chan_state_get(zldev, zldpll->id); + if (!zl3073x_chan_mode_is_nco(chan) && + !zl3073x_chan_mode_supports_tie(chan)) + return zl3073x_chan_tod_adjust(zldev, zldpll->id, + ns_to_timespec64(delta)); + + /* Split off seconds via ToD read-modify-write so the sub-second + * remainder can be applied through the output-coherent mechanism + * (phase step or TIE write). + */ + if (delta >= NSEC_PER_SEC || delta <= -NSEC_PER_SEC) { + s32 remainder; + + ts.tv_sec = div_s64_rem(delta, NSEC_PER_SEC, &remainder); + ts.tv_nsec = 0; + delta = remainder; + + rc = zl3073x_chan_tod_adjust(zldev, zldpll->id, ts); + if (rc) + return rc; + + /* No sub-second remainder, done */ + if (!delta) + return 0; + + /* Wait for the ToD write to be applied at the 1 Hz edge + * before issuing phase step or TIE write, so the pending + * WR_NEXT_1HZ does not overwrite the sub-second adjustment. + */ + rc = zl3073x_chan_tod_ready_wait(zldev, zldpll->id); + if (rc) + return rc; + + sec_adjusted = true; + } + + /* Apply sub-second delta via phase step (NCO) or TIE write */ + if (zl3073x_chan_mode_is_nco(chan)) { + rc = zl3073x_dpll_ptp_adjtime_phase_step(zldpll, delta); + if (!rc) + return 0; + } else { + rc = zl3073x_chan_tie_write(zldev, zldpll->id, delta); + if (!rc) + return 0; + } + + /* Phase step or TIE write failed, fall back to ToD adjust */ + rc = zl3073x_chan_tod_adjust(zldev, zldpll->id, + ns_to_timespec64(delta)); + + /* In the unlikely event that both phase step/TIE write and fallback + * ToD adjust fail after seconds were already committed, return + * success to prevent the PTP servo from retrying the full delta and + * applying seconds again. The sub-second residual will self-correct + * in the next servo cycle. + */ + if (rc && sec_adjusted) { + dev_warn(zldev->dev, + "Sub-second adjustment failed after seconds applied\n"); + return 0; + } + + return rc; +} + +/** + * zl3073x_dpll_ptp_adjfine - adjust PTP clock frequency + * @info: PTP clock info + * @scaled_ppm: frequency adjustment in scaled ppm (ppm * 2^16) + * + * Only supported for NCO. Writes the delta frequency offset register. + * + * Return: + * * %0 - success or @scaled_ppm is zero (no-op) + * * %-EOPNOTSUPP - NCO pin is not connected and @scaled_ppm is non-zero + * * negative - other error + */ +static int +zl3073x_dpll_ptp_adjfine(struct ptp_clock_info *info, long scaled_ppm) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + const struct zl3073x_chan *chan; + s64 offset; + + /* Convert scaled_ppm to df_offset in 2^-48 steps: + * df_offset = -(scaled_ppm * 2^32) / 10^6 + * + * Simplify to avoid overflow: + * df_offset = -(scaled_ppm * 2^26) / 5^6 + * df_offset = -(scaled_ppm * 67108864) / 15625 + */ + offset = -div_s64((s64)scaled_ppm * 67108864LL, 15625); + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (!zl3073x_chan_mode_is_nco(chan)) + return scaled_ppm ? -EOPNOTSUPP : 0; + if (offset == chan->df_offset) + return 0; + + return zl3073x_chan_df_offset_set(zldpll->dev, zldpll->id, offset); +} + +/** + * zl3073x_dpll_ptp_adjphase - adjust PTP clock phase + * @info: PTP clock info + * @delta: phase adjustment in nanoseconds + * + * Only supported in AUTO and REFLOCK modes. Uses TIE write for + * nanosecond resolution phase adjustment. + * + * Return: + * * %0 - success or @delta is zero (no-op) + * * %-EOPNOTSUPP - mode does not support TIE and @delta is non-zero + * * negative - other error + */ +static int zl3073x_dpll_ptp_adjphase(struct ptp_clock_info *info, s32 delta) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_chan *chan; + + if (!delta) + return 0; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldev, zldpll->id); + + if (!zl3073x_chan_mode_supports_tie(chan)) + return -EOPNOTSUPP; + + return zl3073x_chan_tie_write(zldev, zldpll->id, delta); +} + +static s32 +zl3073x_dpll_ptp_getmaxphase(struct ptp_clock_info *info __always_unused) +{ + /* HW limits TIE write to +-1 second. Return the constant HW + * limit and let adjphase handle mode-specific checks. + */ + return NSEC_PER_SEC - 1; +} + +static const struct ptp_clock_info zl3073x_dpll_ptp_clock_info = { + .owner = THIS_MODULE, + .max_adj = ZL3073X_DPLL_PTP_MAX_ADJ, + .gettimex64 = zl3073x_dpll_ptp_gettimex64, + .settime64 = zl3073x_dpll_ptp_settime64, + .adjtime = zl3073x_dpll_ptp_adjtime, + .adjfine = zl3073x_dpll_ptp_adjfine, + .adjphase = zl3073x_dpll_ptp_adjphase, + .getmaxphase = zl3073x_dpll_ptp_getmaxphase, +}; + +/** + * zl3073x_dpll_ptp_register - register PTP clock for a DPLL channel + * @zldpll: DPLL channel to register PTP clock for + * + * Return: 0 on success, <0 on error + */ +static int zl3073x_dpll_ptp_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dev *zldev = zldpll->dev; + struct ptp_clock *ptp_clock; + + zldpll->ptp_info = zl3073x_dpll_ptp_clock_info; + snprintf(zldpll->ptp_info.name, sizeof(zldpll->ptp_info.name), + "%s-dpll%u", dev_name(zldev->dev), zldpll->id); + + ptp_clock = ptp_clock_register(&zldpll->ptp_info, zldev->dev); + if (IS_ERR(ptp_clock)) { + dev_err(zldev->dev, "Failed to register PTP clock for DPLL%u\n", + zldpll->id); + return PTR_ERR(ptp_clock); + } + + zldpll->ptp_clock = ptp_clock; + + return 0; +} + +/** + * zl3073x_dpll_ptp_unregister - unregister PTP clock for a DPLL channel + * @zldpll: DPLL channel to unregister PTP clock for + */ +static void zl3073x_dpll_ptp_unregister(struct zl3073x_dpll *zldpll) +{ + if (!IS_ERR_OR_NULL(zldpll->ptp_clock)) { + ptp_clock_unregister(zldpll->ptp_clock); + zldpll->ptp_clock = NULL; + } } /** @@ -2436,6 +2777,47 @@ zl3073x_dpll_ref_sync_pairs_register(struct zl3073x_dpll *zldpll) return 0; } +/** + * zl3073x_dpll_alloc - allocate DPLL device + * @zldev: pointer to zl3073x device + * @ch: DPLL channel number + * + * Allocates DPLL device structure for given DPLL channel. + * + * Return: pointer to DPLL device on success, error pointer on error + */ +struct zl3073x_dpll * +zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch) +{ + struct zl3073x_dpll *zldpll; + + zldpll = kzalloc_obj(*zldpll); + if (!zldpll) + return ERR_PTR(-ENOMEM); + + zldpll->dev = zldev; + zldpll->id = ch; + mutex_init(&zldpll->lock); + INIT_LIST_HEAD(&zldpll->pins); + + return zldpll; +} + +/** + * zl3073x_dpll_free - free DPLL device + * @zldpll: pointer to zl3073x_dpll structure + * + * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc. + */ +void +zl3073x_dpll_free(struct zl3073x_dpll *zldpll) +{ + WARN(zldpll->dpll_dev, "DPLL device is still registered\n"); + + mutex_destroy(&zldpll->lock); + kfree(zldpll); +} + /** * zl3073x_dpll_register - register DPLL device and all its pins * @zldpll: pointer to zl3073x_dpll structure @@ -2466,6 +2848,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; } @@ -2479,7 +2868,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 faebc402ba1b..993221dc6324 100644 --- a/drivers/dpll/zl3073x/dpll.h +++ b/drivers/dpll/zl3073x/dpll.h @@ -5,6 +5,7 @@ #include #include +#include #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);