[PATCH 4/6] net: ethernet: ti: cpts: add ptp pps support

From: Grygorii Strashko
Date: Mon Nov 28 2016 - 18:06:11 EST


The TS_COMP output in the CPSW CPTS module is asserted for
ts_comp_length[15:0] RCLK periods when the time_stamp value compares
with the ts_comp_val[31:0] and the length value is non-zero. The
TS_COMP pulse edge occurs three RCLK periods after the values
compare. A timestamp compare event is pushed into the event FIFO when
TS_COMP is asserted.

This patch adds support of Pulse-Per-Second (PPS) by using the
timestamp compare output. The CPTS driver adds one second of counter
value to the ts_comp_val register after each assertion of the TS_COMP
output. The TS_COMP pulse polarity and width are configurable in DT.

Signed-off-by: WingMan Kwok <w-kwok2@xxxxxx>
Signed-off-by: Grygorii Strashko <grygorii.strashko@xxxxxx>
---
.../devicetree/bindings/net/keystone-netcp.txt | 10 +
drivers/net/ethernet/ti/cpts.c | 237 ++++++++++++++++++++-
drivers/net/ethernet/ti/cpts.h | 14 +-
3 files changed, 251 insertions(+), 10 deletions(-)

diff --git a/Documentation/devicetree/bindings/net/keystone-netcp.txt b/Documentation/devicetree/bindings/net/keystone-netcp.txt
index 1c805319..060af96 100644
--- a/Documentation/devicetree/bindings/net/keystone-netcp.txt
+++ b/Documentation/devicetree/bindings/net/keystone-netcp.txt
@@ -127,6 +127,16 @@ Optional properties:
The number of external time stamp channels.
The different CPTS versions might support up 8
external time stamp channels. if absent - unsupported.
+ - cpts-ts-comp-length:
+ Enable time stamp comparison event and TS_COMP signal output
+ generation when CPTS counter reaches a value written to
+ the TS_COMP_VAL register.
+ The generated pulse width is 3 refclk cycles if this property
+ has no value (empty) or, otherwise, it should specify desired
+ pulse width in number of refclk periods - max value 2^16.
+ TS_COMP functionality will be disabled if not present.
+ - cpts-ts-comp-polarity-low:
+ Set polarity of TS_COMP signal to low. Default is hight.

NetCP interface properties: Interface specification for NetCP sub-modules.
Required properties:
diff --git a/drivers/net/ethernet/ti/cpts.c b/drivers/net/ethernet/ti/cpts.c
index 2f7641a..8ff70cc 100644
--- a/drivers/net/ethernet/ti/cpts.c
+++ b/drivers/net/ethernet/ti/cpts.c
@@ -31,9 +31,13 @@

#include "cpts.h"

+#define CPTS_TS_COMP_PULSE_LENGTH_DEF 3
+
#define cpts_read32(c, r) readl_relaxed(&c->reg->r)
#define cpts_write32(c, v, r) writel_relaxed(v, &c->reg->r)

+static int cpts_report_ts_events(struct cpts *cpts, bool pps_reload);
+
static int cpts_event_port(struct cpts_event *event)
{
return (event->high >> PORT_NUMBER_SHIFT) & PORT_NUMBER_MASK;
@@ -108,6 +112,7 @@ static int cpts_fifo_read(struct cpts *cpts, int match)
type = event_type(event);
switch (type) {
case CPTS_EV_HW:
+ case CPTS_EV_COMP:
event->tmo +=
msecs_to_jiffies(CPTS_EVENT_HWSTAMP_TIMEOUT);
case CPTS_EV_PUSH:
@@ -153,6 +158,60 @@ static cycle_t cpts_systim_read(const struct cyclecounter *cc)
return val;
}

+static cycle_t cpts_cc_ns2cyc(struct cpts *cpts, u64 nsecs)
+{
+ cycle_t cyc = (nsecs << cpts->cc.shift) + nsecs;
+
+ do_div(cyc, cpts->cc.mult);
+
+ return cyc;
+}
+
+static void cpts_ts_comp_disable(struct cpts *cpts)
+{
+ cpts_write32(cpts, 0, ts_comp_length);
+}
+
+static void cpts_ts_comp_enable(struct cpts *cpts)
+{
+ /* TS_COMP_LENGTH should be 0 while the TS_COMP_VAL value is
+ * being written
+ */
+ cpts_write32(cpts, 0, ts_comp_length);
+ cpts_write32(cpts, cpts->ts_comp_next, ts_comp_val);
+ cpts_write32(cpts, cpts->ts_comp_length, ts_comp_length);
+}
+
+static void cpts_ts_comp_add_ns(struct cpts *cpts, s64 add_ns)
+{
+ cycle_t cyc_next;
+
+ if (add_ns == NSEC_PER_SEC)
+ /* avoid calculation */
+ cyc_next = cpts->ts_comp_one_sec_cycs;
+ else
+ cyc_next = cpts_cc_ns2cyc(cpts, add_ns);
+
+ cyc_next += cpts->ts_comp_next;
+ cpts->ts_comp_next = cyc_next & cpts->cc.mask;
+ pr_debug("cpts comp ts_comp_next: %u\n", cpts->ts_comp_next);
+}
+
+static void cpts_ts_comp_settime(struct cpts *cpts, s64 now_ns)
+{
+ struct timespec64 ts;
+
+ if (cpts->ts_comp_enabled) {
+ ts = ns_to_timespec64(now_ns);
+
+ /* align pulse to next sec boundary and add one sec */
+ cpts_ts_comp_add_ns(cpts, NSEC_PER_SEC - ts.tv_nsec);
+
+ /* enable ts_comp pulse */
+ cpts_ts_comp_enable(cpts);
+ }
+}
+
/* PTP clock operations */

static int cpts_ptp_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
@@ -162,6 +221,7 @@ static int cpts_ptp_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
int neg_adj = 0;
unsigned long flags;
struct cpts *cpts = container_of(ptp, struct cpts, info);
+ u64 ns;

if (ppb < 0) {
neg_adj = 1;
@@ -172,14 +232,31 @@ static int cpts_ptp_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
adj *= ppb;
diff = div_u64(adj, 1000000000ULL);

+ mutex_lock(&cpts->ptp_clk_mutex);
+
spin_lock_irqsave(&cpts->lock, flags);
+ if (cpts->ts_comp_enabled) {
+ cpts_ts_comp_disable(cpts);
+ /* if any, report existing pulse before adj */
+ cpts_fifo_read(cpts, CPTS_EV_COMP);
+ /* if any, report existing pulse before adj */
+ cpts_report_ts_events(cpts, false);
+ }

timecounter_read(&cpts->tc);

cpts->cc.mult = neg_adj ? mult - diff : mult + diff;
-
+ /* get updated time with adj */
+ ns = timecounter_read(&cpts->tc);
+ cpts->ts_comp_next = cpts->tc.cycle_last;
spin_unlock_irqrestore(&cpts->lock, flags);

+ if (cpts->ts_comp_enabled)
+ cpts->ts_comp_one_sec_cycs = cpts_cc_ns2cyc(cpts, NSEC_PER_SEC);
+ cpts_ts_comp_settime(cpts, ns);
+
+ mutex_unlock(&cpts->ptp_clk_mutex);
+
return 0;
}

@@ -187,11 +264,28 @@ static int cpts_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
unsigned long flags;
struct cpts *cpts = container_of(ptp, struct cpts, info);
+ u64 ns;
+
+ mutex_lock(&cpts->ptp_clk_mutex);

spin_lock_irqsave(&cpts->lock, flags);
+ if (cpts->ts_comp_enabled) {
+ cpts_ts_comp_disable(cpts);
+ /* if any, report existing pulse before adj */
+ cpts_fifo_read(cpts, CPTS_EV_COMP);
+ /* if any, report existing pulse before adj */
+ cpts_report_ts_events(cpts, false);
+ }
+
timecounter_adjtime(&cpts->tc, delta);
+ ns = timecounter_read(&cpts->tc);
+ cpts->ts_comp_next = cpts->tc.cycle_last;
spin_unlock_irqrestore(&cpts->lock, flags);

+ cpts_ts_comp_settime(cpts, ns);
+
+ mutex_unlock(&cpts->ptp_clk_mutex);
+
return 0;
}

@@ -213,25 +307,90 @@ static int cpts_ptp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts)
static int cpts_ptp_settime(struct ptp_clock_info *ptp,
const struct timespec64 *ts)
{
- u64 ns;
- unsigned long flags;
struct cpts *cpts = container_of(ptp, struct cpts, info);
+ unsigned long flags;
+ u64 ns;

ns = timespec64_to_ns(ts);

+ mutex_lock(&cpts->ptp_clk_mutex);
+
spin_lock_irqsave(&cpts->lock, flags);
+ if (cpts->ts_comp_enabled) {
+ cpts_ts_comp_disable(cpts);
+ /* if any, get existing pulse event before adj */
+ cpts_fifo_read(cpts, CPTS_EV_COMP);
+ /* if any, report existing pulse before adj */
+ cpts_report_ts_events(cpts, false);
+ }
+
timecounter_init(&cpts->tc, &cpts->cc, ns);
+ cpts->ts_comp_next = cpts->tc.cycle_last;
spin_unlock_irqrestore(&cpts->lock, flags);

+ cpts_ts_comp_settime(cpts, ns);
+
+ mutex_unlock(&cpts->ptp_clk_mutex);
+
return 0;
}

-static int cpts_report_ts_events(struct cpts *cpts)
+static int cpts_pps_enable(struct cpts *cpts, int on)
+{
+ struct timespec64 ts;
+ unsigned long flags;
+ u64 ns;
+
+ if (cpts->ts_comp_enabled == on)
+ return 0;
+
+ mutex_lock(&cpts->ptp_clk_mutex);
+ cpts->ts_comp_enabled = on;
+
+ if (!on) {
+ cpts_ts_comp_disable(cpts);
+ if (!cpts->hw_ts_enable)
+ cpts->ov_check_period = cpts->ov_check_period_slow;
+ mutex_unlock(&cpts->ptp_clk_mutex);
+ return 0;
+ }
+
+ /* get current counter value */
+ spin_lock_irqsave(&cpts->lock, flags);
+ ns = timecounter_read(&cpts->tc);
+ cpts->ts_comp_next = cpts->tc.cycle_last;
+ spin_unlock_irqrestore(&cpts->lock, flags);
+
+ ts = ns_to_timespec64(ns);
+ cpts->ts_comp_one_sec_cycs = cpts_cc_ns2cyc(cpts, NSEC_PER_SEC);
+ /* align to next sec boundary and add one sec to avoid the situation
+ * when the current time is very close to the next second point and
+ * it might be possible that ts_comp_val will be configured to
+ * the time in the past.
+ */
+ cpts_ts_comp_add_ns(cpts, 2 * NSEC_PER_SEC - ts.tv_nsec);
+
+ /* enable ts_comp pulse */
+ cpts_ts_comp_enable(cpts);
+
+ /* poll for events faster - evry 200 ms */
+ cpts->ov_check_period = msecs_to_jiffies(CPTS_EVENT_HWSTAMP_TIMEOUT);
+
+ mod_delayed_work(system_wq, &cpts->overflow_work,
+ cpts->ov_check_period);
+
+ mutex_unlock(&cpts->ptp_clk_mutex);
+
+ return 0;
+}
+
+static int cpts_report_ts_events(struct cpts *cpts, bool pps_reload)
{
struct list_head *this, *next;
struct ptp_clock_event pevent;
struct cpts_event *event;
int reported = 0, ev;
+ u64 ns;

list_for_each_safe(this, next, &cpts->events) {
event = list_entry(this, struct cpts_event, list);
@@ -248,6 +407,33 @@ static int cpts_report_ts_events(struct cpts *cpts)
++reported;
continue;
}
+
+ if (event_type(event) == CPTS_EV_COMP) {
+ list_del_init(&event->list);
+ list_add(&event->list, &cpts->pool);
+ if (cpts->ts_comp_next != event->low) {
+ pr_err("cpts ts_comp mismatch: %08x %08x\n",
+ cpts->ts_comp_next, event->low);
+ continue;
+ } else
+ pr_debug("cpts comp ev tstamp: %u\n",
+ event->low);
+
+ /* report the event */
+ ns = timecounter_cyc2time(&cpts->tc, event->low);
+ pevent.type = PTP_CLOCK_PPSUSR;
+ pevent.pps_times.ts_real = ns_to_timespec64(ns);
+ ptp_clock_event(cpts->clock, &pevent);
+
+ if (pps_reload) {
+ /* reload: add ns to ts_comp */
+ cpts_ts_comp_add_ns(cpts, NSEC_PER_SEC);
+ /* enable ts_comp pulse with new val */
+ cpts_ts_comp_enable(cpts);
+ }
+ ++reported;
+ continue;
+ }
}
return reported;
}
@@ -264,6 +450,8 @@ static int cpts_extts_enable(struct cpts *cpts, u32 index, int on)
if (((cpts->hw_ts_enable & BIT(index)) >> index) == on)
return 0;

+ mutex_lock(&cpts->ptp_clk_mutex);
+
spin_lock_irqsave(&cpts->lock, flags);

v = cpts_read32(cpts, control);
@@ -282,12 +470,12 @@ static int cpts_extts_enable(struct cpts *cpts, u32 index, int on)
/* poll for events faster - evry 200 ms */
cpts->ov_check_period =
msecs_to_jiffies(CPTS_EVENT_HWSTAMP_TIMEOUT);
- else
+ else if (!cpts->ts_comp_enabled)
cpts->ov_check_period = cpts->ov_check_period_slow;

mod_delayed_work(system_wq, &cpts->overflow_work,
cpts->ov_check_period);
-
+ mutex_unlock(&cpts->ptp_clk_mutex);
return 0;
}

@@ -299,6 +487,8 @@ static int cpts_ptp_enable(struct ptp_clock_info *ptp,
switch (rq->type) {
case PTP_CLK_REQ_EXTTS:
return cpts_extts_enable(cpts, rq->extts.index, on);
+ case PTP_CLK_REQ_PPS:
+ return cpts_pps_enable(cpts, on);
default:
break;
}
@@ -326,12 +516,15 @@ static void cpts_overflow_check(struct work_struct *work)
struct timespec64 ts;
unsigned long flags;

+ mutex_lock(&cpts->ptp_clk_mutex);
spin_lock_irqsave(&cpts->lock, flags);
ts = ns_to_timespec64(timecounter_read(&cpts->tc));
spin_unlock_irqrestore(&cpts->lock, flags);

- if (cpts->hw_ts_enable)
- cpts_report_ts_events(cpts);
+ if (cpts->hw_ts_enable || cpts->ts_comp_enabled)
+ cpts_report_ts_events(cpts, true);
+ mutex_unlock(&cpts->ptp_clk_mutex);
+
pr_debug("cpts overflow check at %lld.%09lu\n", ts.tv_sec, ts.tv_nsec);
schedule_delayed_work(&cpts->overflow_work, cpts->ov_check_period);
}
@@ -445,6 +638,7 @@ EXPORT_SYMBOL_GPL(cpts_tx_timestamp);
int cpts_register(struct cpts *cpts)
{
int err, i;
+ u32 control;

INIT_LIST_HEAD(&cpts->events);
INIT_LIST_HEAD(&cpts->pool);
@@ -453,7 +647,14 @@ int cpts_register(struct cpts *cpts)

clk_enable(cpts->refclk);

- cpts_write32(cpts, CPTS_EN, control);
+ control = CPTS_EN;
+ if (cpts->caps & CPTS_CAP_TS_COMP_EN) {
+ if (cpts->caps & CPTS_CAP_TS_COMP_POL_LOW_SEL)
+ control &= ~TS_COMP_POL;
+ else
+ control |= TS_COMP_POL;
+ }
+ cpts_write32(cpts, control, control);
cpts_write32(cpts, TS_PEND_EN, int_enable);

cpts->cc.mult = cpts->cc_mult;
@@ -558,6 +759,20 @@ static int cpts_of_parse(struct cpts *cpts, struct device_node *node)
cpts->rftclk_sel = prop & CPTS_RFTCLK_SEL_MASK;
}

+ if (of_property_read_bool(node, "cpts-ts-comp-length")) {
+ cpts->caps |= CPTS_CAP_TS_COMP_EN;
+ cpts->ts_comp_length = CPTS_TS_COMP_PULSE_LENGTH_DEF;
+ }
+
+ if (cpts->caps & CPTS_CAP_TS_COMP_EN) {
+ ret = of_property_read_u32(node, "cpts-ts-comp-length", &prop);
+ if (!ret)
+ cpts->ts_comp_length = prop;
+
+ if (of_property_read_bool(node, "cpts-ts-comp-polarity-low"))
+ cpts->caps |= CPTS_CAP_TS_COMP_POL_LOW_SEL;
+ }
+
if (!of_property_read_u32(node, "cpts-ext-ts-inputs", &prop))
cpts->ext_ts_inputs = prop;

@@ -584,6 +799,7 @@ struct cpts *cpts_create(struct device *dev, void __iomem *regs,
cpts->dev = dev;
cpts->reg = (struct cpsw_cpts __iomem *)regs;
spin_lock_init(&cpts->lock);
+ mutex_init(&cpts->ptp_clk_mutex);
INIT_DELAYED_WORK(&cpts->overflow_work, cpts_overflow_check);

ret = cpts_of_parse(cpts, node);
@@ -608,6 +824,9 @@ struct cpts *cpts_create(struct device *dev, void __iomem *regs,
if (cpts->ext_ts_inputs)
cpts->info.n_ext_ts = cpts->ext_ts_inputs;

+ if (cpts->caps & CPTS_CAP_TS_COMP_EN)
+ cpts->info.pps = 1;
+
cpts_calc_mult_shift(cpts);

return cpts;
diff --git a/drivers/net/ethernet/ti/cpts.h b/drivers/net/ethernet/ti/cpts.h
index ad80c95..a82520d 100644
--- a/drivers/net/ethernet/ti/cpts.h
+++ b/drivers/net/ethernet/ti/cpts.h
@@ -39,7 +39,8 @@ struct cpsw_cpts {
u32 ts_push; /* Time stamp event push */
u32 ts_load_val; /* Time stamp load value */
u32 ts_load_en; /* Time stamp load enable */
- u32 res2[2];
+ u32 ts_comp_val; /* Time stamp comparison value, v1.5 & up */
+ u32 ts_comp_length; /* Time stamp comp assert len, v1.5 & up */
u32 intstat_raw; /* Time sync interrupt status raw */
u32 intstat_masked; /* Time sync interrupt status masked */
u32 int_enable; /* Time sync interrupt enable */
@@ -64,11 +65,14 @@ struct cpsw_cpts {
#define HW3_TS_PUSH_EN (1<<10) /* Hardware push 3 enable */
#define HW2_TS_PUSH_EN (1<<9) /* Hardware push 2 enable */
#define HW1_TS_PUSH_EN (1<<8) /* Hardware push 1 enable */
+#define TS_COMP_POL BIT(2) /* TS_COMP Polarity */
#define INT_TEST (1<<1) /* Interrupt Test */
#define CPTS_EN (1<<0) /* Time Sync Enable */

#define CPTS_RFTCLK_SEL_MASK 0x1f

+#define CPTS_TS_COMP_LENGTH_MASK 0xffff
+
/*
* Definitions for the single bit resisters:
* TS_PUSH TS_LOAD_EN INTSTAT_RAW INTSTAT_MASKED INT_ENABLE EVENT_POP
@@ -97,6 +101,7 @@ enum {
CPTS_EV_HW, /* Hardware Time Stamp Push Event */
CPTS_EV_RX, /* Ethernet Receive Event */
CPTS_EV_TX, /* Ethernet Transmit Event */
+ CPTS_EV_COMP, /* Time Stamp Compare Event */
};

#define CPTS_FIFO_DEPTH 16
@@ -113,6 +118,8 @@ struct cpts_event {
};

#define CPTS_CAP_RFTCLK_SEL BIT(0)
+#define CPTS_CAP_TS_COMP_EN BIT(1)
+#define CPTS_CAP_TS_COMP_POL_LOW_SEL BIT(2)

struct cpts {
struct device *dev;
@@ -137,6 +144,11 @@ struct cpts {
u32 ext_ts_inputs;
u32 hw_ts_enable;
u32 caps;
+ u32 ts_comp_next; /* next time_stamp value to compare with */
+ u32 ts_comp_length; /* TS_COMP Output pulse width */
+ u32 ts_comp_one_sec_cycs; /* number of counter cycles in one sec */
+ int ts_comp_enabled;
+ struct mutex ptp_clk_mutex; /* sync PTP interface with overflow_work */
};

void cpts_rx_timestamp(struct cpts *cpts, struct sk_buff *skb);
--
2.10.1