This API was introduced for 2 reasons: 1. Some hardware can emit PPS signals but not starting from arbitrary absolute times, but rather phase-aligned to the beginning of a second. We _could_ patch ts2phc to always specify a start time of 0.000000000 to PTP_PEROUT_REQUEST, but in practice, we would never know whether that would actually work with all in-tree PHC drivers. So there was a need for a new flag that only specifies the phase of the periodic signal, and not the absolute start time. 2. Some hardware can, rather unfortunately, not distinguish between a rising and a falling extts edge. And, since whatever rises also has to fall before rising again, the strategy in ts2phc is to set a 'large' pulse width (half the period) and ignore the extts event corresponding to the mid-way between one second and another. This is all fine, but currently, ts2phc.pulsewidth is a read-only property in the config file. The kernel is not instructed in any way to use this value, it is simply that must be configured based on prior knowledge of the PHC's implementation. This API changes that. The introduction of a phase adjustment for the master PHC means we have to adjust our approximation of the precise perout timestamp. We put that code into a common function and convert all call sites to call that. We also need to do the same thing for the edge ignoring logic. Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
140 lines
3.6 KiB
C
140 lines
3.6 KiB
C
/**
|
|
* @file ts2phc_phc_master.c
|
|
* @note Copyright (C) 2019 Richard Cochran <richardcochran@gmail.com>
|
|
* @note SPDX-License-Identifier: GPL-2.0+
|
|
*/
|
|
#include <linux/ptp_clock.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#include "config.h"
|
|
#include "phc.h"
|
|
#include "print.h"
|
|
#include "missing.h"
|
|
#include "ts2phc.h"
|
|
#include "ts2phc_master_private.h"
|
|
#include "util.h"
|
|
|
|
struct ts2phc_phc_master {
|
|
struct ts2phc_master master;
|
|
struct clock *clock;
|
|
int channel;
|
|
};
|
|
|
|
static int ts2phc_phc_master_activate(struct config *cfg, const char *dev,
|
|
struct ts2phc_phc_master *master)
|
|
{
|
|
struct ptp_perout_request perout_request;
|
|
struct ptp_pin_desc desc;
|
|
int32_t perout_phase;
|
|
int32_t pulsewidth;
|
|
struct timespec ts;
|
|
|
|
memset(&desc, 0, sizeof(desc));
|
|
|
|
master->channel = config_get_int(cfg, dev, "ts2phc.channel");
|
|
|
|
desc.index = config_get_int(cfg, dev, "ts2phc.pin_index");
|
|
desc.func = PTP_PF_PEROUT;
|
|
desc.chan = master->channel;
|
|
|
|
if (phc_pin_setfunc(master->clock->clkid, &desc)) {
|
|
pr_warning("Failed to set the pin. Continuing bravely on...");
|
|
}
|
|
if (clock_gettime(master->clock->clkid, &ts)) {
|
|
perror("clock_gettime");
|
|
return -1;
|
|
}
|
|
perout_phase = config_get_int(cfg, dev, "ts2phc.perout_phase");
|
|
memset(&perout_request, 0, sizeof(perout_request));
|
|
perout_request.index = master->channel;
|
|
perout_request.period.sec = 1;
|
|
perout_request.period.nsec = 0;
|
|
perout_request.flags = 0;
|
|
pulsewidth = config_get_int(cfg, dev, "ts2phc.pulsewidth");
|
|
if (pulsewidth) {
|
|
perout_request.flags |= PTP_PEROUT_DUTY_CYCLE;
|
|
perout_request.on.sec = pulsewidth / NS_PER_SEC;
|
|
perout_request.on.nsec = pulsewidth % NS_PER_SEC;
|
|
}
|
|
if (perout_phase != -1) {
|
|
perout_request.flags |= PTP_PEROUT_PHASE;
|
|
perout_request.phase.sec = perout_phase / NS_PER_SEC;
|
|
perout_request.phase.nsec = perout_phase % NS_PER_SEC;
|
|
} else {
|
|
perout_request.start.sec = ts.tv_sec + 2;
|
|
perout_request.start.nsec = 0;
|
|
}
|
|
|
|
if (ioctl(CLOCKID_TO_FD(master->clock->clkid), PTP_PEROUT_REQUEST2,
|
|
&perout_request)) {
|
|
pr_err(PTP_PEROUT_REQUEST_FAILED);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void ts2phc_phc_master_destroy(struct ts2phc_master *master)
|
|
{
|
|
struct ts2phc_phc_master *m =
|
|
container_of(master, struct ts2phc_phc_master, master);
|
|
struct ptp_perout_request perout_request;
|
|
|
|
memset(&perout_request, 0, sizeof(perout_request));
|
|
perout_request.index = m->channel;
|
|
if (ioctl(CLOCKID_TO_FD(m->clock->clkid), PTP_PEROUT_REQUEST2,
|
|
&perout_request)) {
|
|
pr_err(PTP_PEROUT_REQUEST_FAILED);
|
|
}
|
|
clock_destroy(m->clock);
|
|
free(m);
|
|
}
|
|
|
|
static int ts2phc_phc_master_getppstime(struct ts2phc_master *m,
|
|
struct timespec *ts)
|
|
{
|
|
struct ts2phc_phc_master *master =
|
|
container_of(m, struct ts2phc_phc_master, master);
|
|
return clock_gettime(master->clock->clkid, ts);
|
|
}
|
|
|
|
struct clock *ts2phc_phc_master_get_clock(struct ts2phc_master *m)
|
|
{
|
|
struct ts2phc_phc_master *master =
|
|
container_of(m, struct ts2phc_phc_master, master);
|
|
|
|
return master->clock;
|
|
}
|
|
|
|
struct ts2phc_master *ts2phc_phc_master_create(struct ts2phc_private *priv,
|
|
const char *dev)
|
|
{
|
|
struct ts2phc_phc_master *master;
|
|
|
|
master = calloc(1, sizeof(*master));
|
|
if (!master) {
|
|
return NULL;
|
|
}
|
|
master->master.destroy = ts2phc_phc_master_destroy;
|
|
master->master.getppstime = ts2phc_phc_master_getppstime;
|
|
master->master.get_clock = ts2phc_phc_master_get_clock;
|
|
|
|
master->clock = clock_add(priv, dev);
|
|
if (!master->clock) {
|
|
free(master);
|
|
return NULL;
|
|
}
|
|
master->clock->is_destination = 0;
|
|
|
|
pr_debug("PHC master %s has ptp index %d", dev,
|
|
master->clock->phc_index);
|
|
|
|
if (ts2phc_phc_master_activate(priv->cfg, dev, master)) {
|
|
ts2phc_phc_master_destroy(&master->master);
|
|
return NULL;
|
|
}
|
|
|
|
return &master->master;
|
|
}
|