diff --git a/Documentation/driver-api/ptp.rst b/Documentation/driver-api/ptp.rst index 664838ae77760..5e033c3b11b32 100644 --- a/Documentation/driver-api/ptp.rst +++ b/Documentation/driver-api/ptp.rst @@ -73,6 +73,22 @@ Writing clock drivers class driver, since the lock may also be needed by the clock driver's interrupt service routine. +PTP hardware clock requirements for '.adjphase' +----------------------------------------------- + + The 'struct ptp_clock_info' interface has a '.adjphase' function. + This function has a set of requirements from the PHC in order to be + implemented. + + * The PHC implements a servo algorithm internally that is used to + correct the offset passed in the '.adjphase' call. + * When other PTP adjustment functions are called, the PHC servo + algorithm is disabled. + + **NOTE:** '.adjphase' is not a simple time adjustment functionality + that 'jumps' the PHC clock time based on the provided offset. It + should correct the offset provided using an internal algorithm. + Supported hardware ================== @@ -106,3 +122,16 @@ Supported hardware - LPF settings (bandwidth, phase limiting, automatic holdover, physical layer assist (per ITU-T G.8273.2)) - Programmable output PTP clocks, any frequency up to 1GHz (to other PHY/MAC time stampers, refclk to ASSPs/SoCs/FPGAs) - Lock to GNSS input, automatic switching between GNSS and user-space PHC control (optional) + + * NVIDIA Mellanox + + - GPIO + - Certain variants of ConnectX-6 Dx and later products support one + GPIO which can time stamp external triggers and one GPIO to produce + periodic signals. + - Certain variants of ConnectX-5 and older products support one GPIO, + configured to either time stamp external triggers or produce + periodic signals. + - PHC instances + - All ConnectX devices have a free-running counter + - ConnectX-6 Dx and later devices have a UTC format counter diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/clock.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/clock.c index 932fbc843c692..973babfaff25c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/clock.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/clock.c @@ -93,17 +93,23 @@ static bool mlx5_modify_mtutc_allowed(struct mlx5_core_dev *mdev) return MLX5_CAP_MCAM_FEATURE(mdev, ptpcyc2realtime_modify); } -static bool mlx5_is_mtutc_time_adj_cap(struct mlx5_core_dev *mdev, s64 delta) +static s32 mlx5_ptp_getmaxphase(struct ptp_clock_info *ptp) { - s64 min = MLX5_MTUTC_OPERATION_ADJUST_TIME_MIN; - s64 max = MLX5_MTUTC_OPERATION_ADJUST_TIME_MAX; + struct mlx5_clock *clock = container_of(ptp, struct mlx5_clock, ptp_info); + struct mlx5_core_dev *mdev; - if (MLX5_CAP_MCAM_FEATURE(mdev, mtutc_time_adjustment_extended_range)) { - min = MLX5_MTUTC_OPERATION_ADJUST_TIME_EXTENDED_MIN; - max = MLX5_MTUTC_OPERATION_ADJUST_TIME_EXTENDED_MAX; - } + mdev = container_of(clock, struct mlx5_core_dev, clock); + + return MLX5_CAP_MCAM_FEATURE(mdev, mtutc_time_adjustment_extended_range) ? + MLX5_MTUTC_OPERATION_ADJUST_TIME_EXTENDED_MAX : + MLX5_MTUTC_OPERATION_ADJUST_TIME_MAX; +} + +static bool mlx5_is_mtutc_time_adj_cap(struct mlx5_core_dev *mdev, s64 delta) +{ + s64 max = mlx5_ptp_getmaxphase(&mdev->clock.ptp_info); - if (delta < min || delta > max) + if (delta < -max || delta > max) return false; return true; @@ -351,14 +357,6 @@ static int mlx5_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) static int mlx5_ptp_adjphase(struct ptp_clock_info *ptp, s32 delta) { - struct mlx5_clock *clock = container_of(ptp, struct mlx5_clock, ptp_info); - struct mlx5_core_dev *mdev; - - mdev = container_of(clock, struct mlx5_core_dev, clock); - - if (!mlx5_is_mtutc_time_adj_cap(mdev, delta)) - return -ERANGE; - return mlx5_ptp_adjtime(ptp, delta); } @@ -734,6 +732,7 @@ static const struct ptp_clock_info mlx5_ptp_clock_info = { .pps = 0, .adjfine = mlx5_ptp_adjfine, .adjphase = mlx5_ptp_adjphase, + .getmaxphase = mlx5_ptp_getmaxphase, .adjtime = mlx5_ptp_adjtime, .gettimex64 = mlx5_ptp_gettimex, .settime64 = mlx5_ptp_settime, diff --git a/drivers/ptp/ptp_chardev.c b/drivers/ptp/ptp_chardev.c index af3bc65c4595d..362bf756e6b78 100644 --- a/drivers/ptp/ptp_chardev.c +++ b/drivers/ptp/ptp_chardev.c @@ -136,7 +136,10 @@ long ptp_ioctl(struct posix_clock *pc, unsigned int cmd, unsigned long arg) caps.pps = ptp->info->pps; caps.n_pins = ptp->info->n_pins; caps.cross_timestamping = ptp->info->getcrosststamp != NULL; - caps.adjust_phase = ptp->info->adjphase != NULL; + caps.adjust_phase = ptp->info->adjphase != NULL && + ptp->info->getmaxphase != NULL; + if (caps.adjust_phase) + caps.max_phase_adj = ptp->info->getmaxphase(ptp->info); if (copy_to_user((void __user *)arg, &caps, sizeof(caps))) err = -EFAULT; break; diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c index 790f9250b3815..80f74e38c2da4 100644 --- a/drivers/ptp/ptp_clock.c +++ b/drivers/ptp/ptp_clock.c @@ -135,11 +135,15 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx) ptp->dialed_frequency = tx->freq; } else if (tx->modes & ADJ_OFFSET) { if (ops->adjphase) { + s32 max_phase_adj = ops->getmaxphase(ops); s32 offset = tx->offset; if (!(tx->modes & ADJ_NANO)) offset *= NSEC_PER_USEC; + if (offset > max_phase_adj || offset < -max_phase_adj) + return -ERANGE; + err = ops->adjphase(ops, offset); } } else if (tx->modes == 0) { diff --git a/drivers/ptp/ptp_clockmatrix.c b/drivers/ptp/ptp_clockmatrix.c index c9d451bf89e2d..f6f9d4adce04a 100644 --- a/drivers/ptp/ptp_clockmatrix.c +++ b/drivers/ptp/ptp_clockmatrix.c @@ -1692,14 +1692,23 @@ static int initialize_dco_operating_mode(struct idtcm_channel *channel) /* PTP Hardware Clock interface */ /* - * Maximum absolute value for write phase offset in picoseconds - * - * @channel: channel - * @delta_ns: delta in nanoseconds + * Maximum absolute value for write phase offset in nanoseconds * * Destination signed register is 32-bit register in resolution of 50ps * - * 0x7fffffff * 50 = 2147483647 * 50 = 107374182350 + * 0x7fffffff * 50 = 2147483647 * 50 = 107374182350 ps + * Represent 107374182350 ps as 107374182 ns + */ +static s32 idtcm_getmaxphase(struct ptp_clock_info *ptp __always_unused) +{ + return MAX_ABS_WRITE_PHASE_NANOSECONDS; +} + +/* + * Internal function for implementing support for write phase offset + * + * @channel: channel + * @delta_ns: delta in nanoseconds */ static int _idtcm_adjphase(struct idtcm_channel *channel, s32 delta_ns) { @@ -1708,7 +1717,6 @@ static int _idtcm_adjphase(struct idtcm_channel *channel, s32 delta_ns) u8 i; u8 buf[4] = {0}; s32 phase_50ps; - s64 offset_ps; if (channel->mode != PTP_PLL_MODE_WRITE_PHASE) { err = channel->configure_write_phase(channel); @@ -1716,19 +1724,7 @@ static int _idtcm_adjphase(struct idtcm_channel *channel, s32 delta_ns) return err; } - offset_ps = (s64)delta_ns * 1000; - - /* - * Check for 32-bit signed max * 50: - * - * 0x7fffffff * 50 = 2147483647 * 50 = 107374182350 - */ - if (offset_ps > MAX_ABS_WRITE_PHASE_PICOSECONDS) - offset_ps = MAX_ABS_WRITE_PHASE_PICOSECONDS; - else if (offset_ps < -MAX_ABS_WRITE_PHASE_PICOSECONDS) - offset_ps = -MAX_ABS_WRITE_PHASE_PICOSECONDS; - - phase_50ps = div_s64(offset_ps, 50); + phase_50ps = div_s64((s64)delta_ns * 1000, 50); for (i = 0; i < 4; i++) { buf[i] = phase_50ps & 0xff; @@ -2048,6 +2044,7 @@ static const struct ptp_clock_info idtcm_caps = { .n_ext_ts = MAX_TOD, .n_pins = MAX_REF_CLK, .adjphase = &idtcm_adjphase, + .getmaxphase = &idtcm_getmaxphase, .adjfine = &idtcm_adjfine, .adjtime = &idtcm_adjtime, .gettime64 = &idtcm_gettime, @@ -2064,6 +2061,7 @@ static const struct ptp_clock_info idtcm_caps_deprecated = { .n_ext_ts = MAX_TOD, .n_pins = MAX_REF_CLK, .adjphase = &idtcm_adjphase, + .getmaxphase = &idtcm_getmaxphase, .adjfine = &idtcm_adjfine, .adjtime = &idtcm_adjtime_deprecated, .gettime64 = &idtcm_gettime, diff --git a/drivers/ptp/ptp_clockmatrix.h b/drivers/ptp/ptp_clockmatrix.h index bf1e494098440..7c17c4f7f5735 100644 --- a/drivers/ptp/ptp_clockmatrix.h +++ b/drivers/ptp/ptp_clockmatrix.h @@ -18,7 +18,7 @@ #define MAX_PLL (8) #define MAX_REF_CLK (16) -#define MAX_ABS_WRITE_PHASE_PICOSECONDS (107374182350LL) +#define MAX_ABS_WRITE_PHASE_NANOSECONDS (107374182L) #define TOD_MASK_ADDR (0xFFA5) #define DEFAULT_TOD_MASK (0x04) diff --git a/drivers/ptp/ptp_idt82p33.c b/drivers/ptp/ptp_idt82p33.c index afc76c22271ad..057190b9cd3d8 100644 --- a/drivers/ptp/ptp_idt82p33.c +++ b/drivers/ptp/ptp_idt82p33.c @@ -978,24 +978,23 @@ static int idt82p33_enable(struct ptp_clock_info *ptp, return err; } +static s32 idt82p33_getmaxphase(__always_unused struct ptp_clock_info *ptp) +{ + return WRITE_PHASE_OFFSET_LIMIT; +} + static int idt82p33_adjwritephase(struct ptp_clock_info *ptp, s32 offset_ns) { struct idt82p33_channel *channel = container_of(ptp, struct idt82p33_channel, caps); struct idt82p33 *idt82p33 = channel->idt82p33; - s64 offset_regval, offset_fs; + s64 offset_regval; u8 val[4] = {0}; int err; - offset_fs = (s64)(-offset_ns) * 1000000; - - if (offset_fs > WRITE_PHASE_OFFSET_LIMIT) - offset_fs = WRITE_PHASE_OFFSET_LIMIT; - else if (offset_fs < -WRITE_PHASE_OFFSET_LIMIT) - offset_fs = -WRITE_PHASE_OFFSET_LIMIT; - /* Convert from phaseoffset_fs to register value */ - offset_regval = div_s64(offset_fs * 1000, IDT_T0DPLL_PHASE_RESOL); + offset_regval = div_s64((s64)(-offset_ns) * 1000000000ll, + IDT_T0DPLL_PHASE_RESOL); val[0] = offset_regval & 0xFF; val[1] = (offset_regval >> 8) & 0xFF; @@ -1175,6 +1174,7 @@ static void idt82p33_caps_init(u32 index, struct ptp_clock_info *caps, caps->n_ext_ts = MAX_PHC_PLL, caps->n_pins = max_pins, caps->adjphase = idt82p33_adjwritephase, + caps->getmaxphase = idt82p33_getmaxphase, caps->adjfine = idt82p33_adjfine; caps->adjtime = idt82p33_adjtime; caps->gettime64 = idt82p33_gettime; diff --git a/drivers/ptp/ptp_idt82p33.h b/drivers/ptp/ptp_idt82p33.h index 8fcb0b17d2078..6a63c14b6966b 100644 --- a/drivers/ptp/ptp_idt82p33.h +++ b/drivers/ptp/ptp_idt82p33.h @@ -43,9 +43,9 @@ #define DEFAULT_OUTPUT_MASK_PLL1 DEFAULT_OUTPUT_MASK_PLL0 /** - * @brief Maximum absolute value for write phase offset in femtoseconds + * @brief Maximum absolute value for write phase offset in nanoseconds */ -#define WRITE_PHASE_OFFSET_LIMIT (20000052084ll) +#define WRITE_PHASE_OFFSET_LIMIT (20000l) /** @brief Phase offset resolution * diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index ab8cab4d1560d..20a974ced8d6c 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -1124,6 +1124,12 @@ ptp_ocp_null_adjfine(struct ptp_clock_info *ptp_info, long scaled_ppm) return -EOPNOTSUPP; } +static s32 +ptp_ocp_null_getmaxphase(struct ptp_clock_info *ptp_info) +{ + return 0; +} + static int ptp_ocp_null_adjphase(struct ptp_clock_info *ptp_info, s32 phase_ns) { @@ -1239,6 +1245,7 @@ static const struct ptp_clock_info ptp_ocp_clock_info = { .adjtime = ptp_ocp_adjtime, .adjfine = ptp_ocp_null_adjfine, .adjphase = ptp_ocp_null_adjphase, + .getmaxphase = ptp_ocp_null_getmaxphase, .enable = ptp_ocp_enable, .verify = ptp_ocp_verify, .pps = true, diff --git a/drivers/ptp/ptp_sysfs.c b/drivers/ptp/ptp_sysfs.c index f30b0a4394705..77219cdcd6831 100644 --- a/drivers/ptp/ptp_sysfs.c +++ b/drivers/ptp/ptp_sysfs.c @@ -18,6 +18,17 @@ static ssize_t clock_name_show(struct device *dev, } static DEVICE_ATTR_RO(clock_name); +static ssize_t max_phase_adjustment_show(struct device *dev, + struct device_attribute *attr, + char *page) +{ + struct ptp_clock *ptp = dev_get_drvdata(dev); + + return snprintf(page, PAGE_SIZE - 1, "%d\n", + ptp->info->getmaxphase(ptp->info)); +} +static DEVICE_ATTR_RO(max_phase_adjustment); + #define PTP_SHOW_INT(name, var) \ static ssize_t var##_show(struct device *dev, \ struct device_attribute *attr, char *page) \ @@ -309,6 +320,7 @@ static struct attribute *ptp_attrs[] = { &dev_attr_clock_name.attr, &dev_attr_max_adjustment.attr, + &dev_attr_max_phase_adjustment.attr, &dev_attr_n_alarms.attr, &dev_attr_n_external_timestamps.attr, &dev_attr_n_periodic_outputs.attr, diff --git a/include/linux/ptp_clock_kernel.h b/include/linux/ptp_clock_kernel.h index fdffa6a98d799..1ef4e0f9bd2a5 100644 --- a/include/linux/ptp_clock_kernel.h +++ b/include/linux/ptp_clock_kernel.h @@ -77,8 +77,14 @@ struct ptp_system_timestamp { * nominal frequency in parts per million, but with a * 16 bit binary fractional field. * - * @adjphase: Adjusts the phase offset of the hardware clock. - * parameter delta: Desired change in nanoseconds. + * @adjphase: Indicates that the PHC should use an internal servo + * algorithm to correct the provided phase offset. + * parameter delta: PHC servo phase adjustment target + * in nanoseconds. + * + * @getmaxphase: Advertises maximum offset that can be provided + * to the hardware clock's phase control functionality + * through adjphase. * * @adjtime: Shifts the time of the hardware clock. * parameter delta: Desired change in nanoseconds. @@ -169,6 +175,7 @@ struct ptp_clock_info { struct ptp_pin_desc *pin_config; int (*adjfine)(struct ptp_clock_info *ptp, long scaled_ppm); int (*adjphase)(struct ptp_clock_info *ptp, s32 phase); + s32 (*getmaxphase)(struct ptp_clock_info *ptp); int (*adjtime)(struct ptp_clock_info *ptp, s64 delta); int (*gettime64)(struct ptp_clock_info *ptp, struct timespec64 *ts); int (*gettimex64)(struct ptp_clock_info *ptp, struct timespec64 *ts, diff --git a/include/uapi/linux/ptp_clock.h b/include/uapi/linux/ptp_clock.h index 1d108d597f66d..05cc35fc94acf 100644 --- a/include/uapi/linux/ptp_clock.h +++ b/include/uapi/linux/ptp_clock.h @@ -95,7 +95,8 @@ struct ptp_clock_caps { int cross_timestamping; /* Whether the clock supports adjust phase */ int adjust_phase; - int rsv[12]; /* Reserved for future use. */ + int max_phase_adj; /* Maximum phase adjustment in nanoseconds. */ + int rsv[11]; /* Reserved for future use. */ }; struct ptp_extts_request { diff --git a/tools/testing/selftests/ptp/testptp.c b/tools/testing/selftests/ptp/testptp.c index cfa9562f3cd83..e9438a1862adf 100644 --- a/tools/testing/selftests/ptp/testptp.c +++ b/tools/testing/selftests/ptp/testptp.c @@ -110,7 +110,7 @@ static long ppb_to_scaled_ppm(int ppb) static int64_t pctns(struct ptp_clock_time *t) { - return t->sec * 1000000000LL + t->nsec; + return t->sec * NSEC_PER_SEC + t->nsec; } static void usage(char *progname) @@ -134,6 +134,7 @@ static void usage(char *progname) " 1 - external time stamp\n" " 2 - periodic output\n" " -n val shift the ptp clock time by 'val' nanoseconds\n" + " -o val phase offset (in nanoseconds) to be provided to the PHC servo\n" " -p val enable output with a period of 'val' nanoseconds\n" " -H val set output phase to 'val' nanoseconds (requires -p)\n" " -w val set output pulse width to 'val' nanoseconds (requires -p)\n" @@ -167,6 +168,7 @@ int main(int argc, char *argv[]) int adjfreq = 0x7fffffff; int adjtime = 0; int adjns = 0; + int adjphase = 0; int capabilities = 0; int extts = 0; int flagtest = 0; @@ -188,7 +190,7 @@ int main(int argc, char *argv[]) progname = strrchr(argv[0], '/'); progname = progname ? 1+progname : argv[0]; - while (EOF != (c = getopt(argc, argv, "cd:e:f:ghH:i:k:lL:n:p:P:sSt:T:w:z"))) { + while (EOF != (c = getopt(argc, argv, "cd:e:f:ghH:i:k:lL:n:o:p:P:sSt:T:w:z"))) { switch (c) { case 'c': capabilities = 1; @@ -228,6 +230,9 @@ int main(int argc, char *argv[]) case 'n': adjns = atoi(optarg); break; + case 'o': + adjphase = atoi(optarg); + break; case 'p': perout = atoll(optarg); break; @@ -287,7 +292,8 @@ int main(int argc, char *argv[]) " %d pulse per second\n" " %d programmable pins\n" " %d cross timestamping\n" - " %d adjust_phase\n", + " %d adjust_phase\n" + " %d maximum phase adjustment (ns)\n", caps.max_adj, caps.n_alarm, caps.n_ext_ts, @@ -295,7 +301,8 @@ int main(int argc, char *argv[]) caps.pps, caps.n_pins, caps.cross_timestamping, - caps.adjust_phase); + caps.adjust_phase, + caps.max_phase_adj); } } @@ -317,7 +324,7 @@ int main(int argc, char *argv[]) tx.time.tv_usec = adjns; while (tx.time.tv_usec < 0) { tx.time.tv_sec -= 1; - tx.time.tv_usec += 1000000000; + tx.time.tv_usec += NSEC_PER_SEC; } if (clock_adjtime(clkid, &tx) < 0) { @@ -327,6 +334,18 @@ int main(int argc, char *argv[]) } } + if (adjphase) { + memset(&tx, 0, sizeof(tx)); + tx.modes = ADJ_OFFSET | ADJ_NANO; + tx.offset = adjphase; + + if (clock_adjtime(clkid, &tx) < 0) { + perror("clock_adjtime"); + } else { + puts("phase adjustment okay"); + } + } + if (gettime) { if (clock_gettime(clkid, &ts)) { perror("clock_gettime");