Commit 712557f2 authored by David S. Miller's avatar David S. Miller

Merge branch 'ptp-adjphase-cleanups'

Rahul Rameshbabu says:

====================
ptp .adjphase cleanups

The goal of this patch series is to improve documentation of .adjphase, add
a new callback .getmaxphase to enable advertising the max phase offset a
device PHC can support, and support invoking .adjphase from the testptp
kselftest.

Changes:
  v2->v1:
    * Removes arbitrary rule that the PHC servo must restore the frequency
      to the value used in the last .adjfine call if any other PHC
      operation is used after a .adjphase operation.
    * Removes a macro introduced in v1 for adding PTP sysfs device
      attribute nodes using a callback for populating the data.

Link: https://lore.kernel.org/netdev/20230120160609.19160723@kernel.org/
Link: https://lore.kernel.org/netdev/20230510205306.136766-1-rrameshbabu@nvidia.com/
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 49310624 d8ee5ca8
......@@ -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
......@@ -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,
......
......@@ -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;
......
......@@ -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) {
......
......@@ -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,
......
......@@ -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)
......
......@@ -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;
......
......@@ -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
*
......
......@@ -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,
......
......@@ -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,
......
......@@ -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,
......
......@@ -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 {
......
......@@ -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");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment