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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




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

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux