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: David S. Miller <davem@davemloft.net>
This commit is contained in:
@@ -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 s32 mlx5_ptp_getmaxphase(struct ptp_clock_info *ptp)
|
||||
{
|
||||
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);
|
||||
|
||||
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 min = MLX5_MTUTC_OPERATION_ADJUST_TIME_MIN;
|
||||
s64 max = MLX5_MTUTC_OPERATION_ADJUST_TIME_MAX;
|
||||
s64 max = mlx5_ptp_getmaxphase(&mdev->clock.ptp_info);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user