[PATCH 15/20] net: dsa: xilinx: register PHC backed by the RTC timer block

Nagadheeraj Rottela <[email protected]>
Newsgroups org.kernel.vger.linux-devicetree,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-kernel,org.kernel.vger.netdev
Message-ID <[email protected]>
Bring the IP-wide RTC timer block up under the switch driver and
expose it as a PTP hardware clock. The timer block sits at a fixed
offset inside MAC1's per-MAC register window, which the switch driver
already owns via the "mac1" reg-name. Map it in probe() and register
the PHC in setup(). A per-switch spinlock serializes concurrent PHC
operations, since gettime/settime/adjtime can run concurrently from
different file descriptors.

Reading the nanoseconds register latches a full RTC snapshot, so
gettime reads nanoseconds before seconds. The timer block fires 128
ticks per second. The ISR delivers PTP_CLOCK_PPS on every 128th tick.

Co-developed-by: Srinivas Neeli <[email protected]>
Signed-off-by: Srinivas Neeli <[email protected]>
Signed-off-by: Nagadheeraj Rottela <[email protected]>
---
 drivers/net/dsa/xilinx/Kconfig          |   1 +
 drivers/net/dsa/xilinx/Makefile         |   2 +-
 drivers/net/dsa/xilinx/xilinx_tsn.c     |   7 +
 drivers/net/dsa/xilinx/xilinx_tsn.h     |  43 +++++
 drivers/net/dsa/xilinx/xilinx_tsn_ptp.c | 235 ++++++++++++++++++++++++
 5 files changed, 287 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/dsa/xilinx/xilinx_tsn_ptp.c

diff --git a/drivers/net/dsa/xilinx/Kconfig b/drivers/net/dsa/xilinx/Kconfig
index 0bd5efe8490b..0e675295914b 100644
--- a/drivers/net/dsa/xilinx/Kconfig
+++ b/drivers/net/dsa/xilinx/Kconfig
@@ -3,6 +3,7 @@ config NET_DSA_XILINX_TSN
 	tristate "AMD/Xilinx TSN Endpoint Ethernet MAC switch support"
 	depends on OF && HAS_IOMEM
 	depends on NET_DSA
+	depends on PTP_1588_CLOCK
 	select NET_DSA_TAG_XLNX_TSN
 	help
 	  This enables DSA switch support for the three-port switch
diff --git a/drivers/net/dsa/xilinx/Makefile b/drivers/net/dsa/xilinx/Makefile
index 334f0f979276..8d63749104a4 100644
--- a/drivers/net/dsa/xilinx/Makefile
+++ b/drivers/net/dsa/xilinx/Makefile
@@ -1,3 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 obj-$(CONFIG_NET_DSA_XILINX_TSN) += xlnx_tsn_dsa.o
-xlnx_tsn_dsa-y := xilinx_tsn.o
+xlnx_tsn_dsa-y := xilinx_tsn.o xilinx_tsn_ptp.o
diff --git a/drivers/net/dsa/xilinx/xilinx_tsn.c b/drivers/net/dsa/xilinx/xilinx_tsn.c
index 9826f006b078..318d8b332208 100644
--- a/drivers/net/dsa/xilinx/xilinx_tsn.c
+++ b/drivers/net/dsa/xilinx/xilinx_tsn.c
@@ -675,8 +675,14 @@ static int xlnx_tsn_setup(struct dsa_switch *ds)
 	if (ret)
 		goto err_mdio;
 
+	ret = xlnx_tsn_ptp_init(sw);
+	if (ret)
+		goto err_nb;
+
 	return 0;
 
+err_nb:
+	unregister_netdevice_notifier(&sw->nb);
 err_mdio:
 	xlnx_tsn_mdio_unregister_all(sw);
 	return ret;
@@ -687,6 +693,7 @@ static void xlnx_tsn_teardown(struct dsa_switch *ds)
 	struct xlnx_tsn *sw = ds->priv;
 	struct dsa_port *dp;
 
+	xlnx_tsn_ptp_exit(sw);
 	unregister_netdevice_notifier(&sw->nb);
 	xlnx_tsn_mdio_unregister_all(sw);
 
diff --git a/drivers/net/dsa/xilinx/xilinx_tsn.h b/drivers/net/dsa/xilinx/xilinx_tsn.h
index a228a7bebd53..6063763b5c17 100644
--- a/drivers/net/dsa/xilinx/xilinx_tsn.h
+++ b/drivers/net/dsa/xilinx/xilinx_tsn.h
@@ -10,6 +10,8 @@
 #include <linux/if_ether.h>
 #include <linux/io.h>
 #include <linux/notifier.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/spinlock.h>
 #include <linux/types.h>
 #include <net/dsa.h>
 
@@ -103,6 +105,28 @@ enum tsn_port_state {
 #define TSN_SPEED_CFG_100		BIT(30)
 #define TSN_SPEED_CFG_1000		BIT(31)
 
+/* PTP RTC timer block: a single block per IP, physically housed
+ * inside MAC1's per-MAC reg window. Owned by the switch driver
+ * because the PHC it backs is IP-wide, not per-MAC.
+ */
+#define TSN_TIMER_RTC_OFFSET_NS		0x00012800
+#define TSN_TIMER_RTC_OFFSET_SEC_L	0x00012808
+#define TSN_TIMER_RTC_OFFSET_SEC_H	0x0001280c
+#define TSN_TIMER_RTC_INCREMENT		0x00012810
+#define TSN_TIMER_CURRENT_RTC_NS	0x00012814
+#define TSN_TIMER_CURRENT_RTC_SEC_L	0x00012818
+#define TSN_TIMER_CURRENT_RTC_SEC_H	0x0001281c
+#define TSN_TIMER_INTERRUPT		0x00012820
+
+#define TSN_TIMER_MAX_NSEC_SIZE		30
+#define TSN_TIMER_MAX_NSEC_MASK		GENMASK_ULL(TSN_TIMER_MAX_NSEC_SIZE - 1, 0)
+#define TSN_TIMER_MAX_SEC_SIZE		48
+#define TSN_TIMER_MAX_SEC_MASK		GENMASK_ULL(TSN_TIMER_MAX_SEC_SIZE - 1, 0)
+#define TSN_TIMER_INT_CLEAR		BIT(0)
+#define TSN_TIMER_RTC_NS_SHIFT		20
+#define TSN_TIMER_PULSES_PER_PPS	128
+#define TSN_TIMER_GTX_CLK_FREQ		125000000U
+
 struct mii_bus;
 struct xlnx_tsn;
 
@@ -134,6 +158,15 @@ struct xlnx_tsn_mac {
  *	to refresh the shared prefix
  * @mac: per-MAC state, indexed by user-port number (index 0 unused;
  *	 MAC1 at [1], MAC2 at [2])
+ * @ptp_timer_irq: 1 PPS / RTC-overflow interrupt
+ * @ptp_clock: registered PHC; NULL until setup() succeeds
+ * @ptp_clock_info: PHC capability + ops descriptor
+ * @reg_lock: serialises RTC offset / increment register accesses
+ *	      from process context and the PHC ops
+ * @rtc_value: base RTC increment word for the GTX clock frequency,
+ *	       used as the starting point for adjust_by_scaled_ppm()
+ * @pps_enable: user requested PPS event delivery
+ * @countpulse: timer-tick counter, reset to zero every TSN_TIMER_PULSES_PER_PPS ticks
  */
 struct xlnx_tsn {
 	struct dsa_switch ds;
@@ -143,6 +176,13 @@ struct xlnx_tsn {
 	u8 mac_prefix[ETH_ALEN];
 	struct notifier_block nb;
 	struct xlnx_tsn_mac mac[XLNX_TSN_NUM_PORTS];
+	int ptp_timer_irq;
+	struct ptp_clock *ptp_clock;
+	struct ptp_clock_info ptp_clock_info;
+	spinlock_t reg_lock; /* serialises RTC offset/increment registers */
+	u64 rtc_value;
+	int pps_enable;
+	int countpulse;
 };
 
 static inline void mac_iow(struct xlnx_tsn_mac *m, u32 off, u32 val)
@@ -155,4 +195,7 @@ static inline u32 mac_ior(struct xlnx_tsn_mac *m, u32 off)
 	return ioread32(m->regs + off);
 }
 
+int xlnx_tsn_ptp_init(struct xlnx_tsn *sw);
+void xlnx_tsn_ptp_exit(struct xlnx_tsn *sw);
+
 #endif /* _XILINX_TSN_H */
diff --git a/drivers/net/dsa/xilinx/xilinx_tsn_ptp.c b/drivers/net/dsa/xilinx/xilinx_tsn_ptp.c
new file mode 100644
index 000000000000..75c177f752f1
--- /dev/null
+++ b/drivers/net/dsa/xilinx/xilinx_tsn_ptp.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD/Xilinx TSN Endpoint Ethernet MAC DSA switch driver:
+ * PTP hardware clock.
+ */
+
+#include <linux/cleanup.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ktime.h>
+#include <linux/math64.h>
+#include <linux/of_irq.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/spinlock.h>
+#include <linux/time64.h>
+#include <net/dsa.h>
+
+#include "xilinx_tsn.h"
+
+/* Reading the nanoseconds register latches a full RTC snapshot
+ * (ns + sec_low + sec_high). The subsequent seconds reads return
+ * those latched values, so nanoseconds must be read first.
+ */
+static void xlnx_tsn_tod_read(struct xlnx_tsn *sw, struct timespec64 *ts)
+{
+	struct xlnx_tsn_mac *m = &sw->mac[XLNX_TSN_PORT_MAC1];
+	u32 secl, sech, nsec;
+
+	nsec = mac_ior(m, TSN_TIMER_CURRENT_RTC_NS);
+	secl = mac_ior(m, TSN_TIMER_CURRENT_RTC_SEC_L);
+	sech = mac_ior(m, TSN_TIMER_CURRENT_RTC_SEC_H);
+
+	ts->tv_sec = (((u64)sech << 32) | secl) & TSN_TIMER_MAX_SEC_MASK;
+	ts->tv_nsec = nsec & TSN_TIMER_MAX_NSEC_MASK;
+}
+
+static void xlnx_tsn_rtc_offset_write(struct xlnx_tsn *sw,
+				      const struct timespec64 *ts)
+{
+	struct xlnx_tsn_mac *m = &sw->mac[XLNX_TSN_PORT_MAC1];
+
+	mac_iow(m, TSN_TIMER_RTC_OFFSET_SEC_H, upper_32_bits(ts->tv_sec));
+	mac_iow(m, TSN_TIMER_RTC_OFFSET_SEC_L, lower_32_bits(ts->tv_sec));
+	mac_iow(m, TSN_TIMER_RTC_OFFSET_NS, ts->tv_nsec);
+}
+
+static void xlnx_tsn_rtc_offset_read(struct xlnx_tsn *sw,
+				     struct timespec64 *ts)
+{
+	struct xlnx_tsn_mac *m = &sw->mac[XLNX_TSN_PORT_MAC1];
+	u32 secl, sech, nsec;
+
+	secl = mac_ior(m, TSN_TIMER_RTC_OFFSET_SEC_L);
+	sech = mac_ior(m, TSN_TIMER_RTC_OFFSET_SEC_H);
+	nsec = mac_ior(m, TSN_TIMER_RTC_OFFSET_NS);
+
+	ts->tv_sec = (((u64)sech << 32) | secl) & TSN_TIMER_MAX_SEC_MASK;
+	ts->tv_nsec = nsec & TSN_TIMER_MAX_NSEC_MASK;
+}
+
+static int xlnx_tsn_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+	struct xlnx_tsn *sw = container_of(ptp, struct xlnx_tsn, ptp_clock_info);
+	u64 incval;
+
+	/* adjust_by_scaled_ppm() returns u64 but the increment register is
+	 * 32 bits, so clamp to U32_MAX to avoid overflow.
+	 */
+	incval = adjust_by_scaled_ppm(sw->rtc_value, scaled_ppm);
+	if (incval > U32_MAX)
+		incval = U32_MAX;
+
+	guard(spinlock_irqsave)(&sw->reg_lock);
+	mac_iow(&sw->mac[XLNX_TSN_PORT_MAC1], TSN_TIMER_RTC_INCREMENT,
+		(u32)incval);
+
+	return 0;
+}
+
+static int xlnx_tsn_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+	struct xlnx_tsn *sw = container_of(ptp, struct xlnx_tsn, ptp_clock_info);
+	struct timespec64 now, then = ns_to_timespec64(delta);
+
+	guard(spinlock_irqsave)(&sw->reg_lock);
+
+	xlnx_tsn_rtc_offset_read(sw, &now);
+	now = timespec64_add(now, then);
+
+	/* Stepping time backwards is fine and just lowers the offset. In
+	 * practice the offset never goes negative. Reject it only as a
+	 * safety net, since the offset register cannot store a negative value.
+	 */
+	if (now.tv_sec < 0)
+		return -ERANGE;
+
+	xlnx_tsn_rtc_offset_write(sw, &now);
+
+	return 0;
+}
+
+static int xlnx_tsn_ptp_gettime(struct ptp_clock_info *ptp,
+				struct timespec64 *ts)
+{
+	struct xlnx_tsn *sw = container_of(ptp, struct xlnx_tsn, ptp_clock_info);
+
+	guard(spinlock_irqsave)(&sw->reg_lock);
+	xlnx_tsn_tod_read(sw, ts);
+
+	return 0;
+}
+
+static int xlnx_tsn_ptp_settime(struct ptp_clock_info *ptp,
+				const struct timespec64 *ts)
+{
+	struct xlnx_tsn *sw = container_of(ptp, struct xlnx_tsn, ptp_clock_info);
+	struct timespec64 delta, tod, offset, counter;
+
+	guard(spinlock_irqsave)(&sw->reg_lock);
+
+	xlnx_tsn_tod_read(sw, &tod);
+	xlnx_tsn_rtc_offset_read(sw, &offset);
+	counter = timespec64_sub(tod, offset);
+
+	delta = timespec64_sub(*ts, counter);
+
+	/* A real wall-clock time is always far above the free-running counter,
+	 * so this never triggers in practice. Reject it only as a safety net,
+	 * since the offset register cannot store a negative value.
+	 */
+	if (delta.tv_sec < 0)
+		return -ERANGE;
+
+	xlnx_tsn_rtc_offset_write(sw, &delta);
+
+	return 0;
+}
+
+static int xlnx_tsn_ptp_enable(struct ptp_clock_info *ptp,
+			       struct ptp_clock_request *rq, int on)
+{
+	struct xlnx_tsn *sw = container_of(ptp, struct xlnx_tsn, ptp_clock_info);
+
+	switch (rq->type) {
+	case PTP_CLK_REQ_PPS:
+		WRITE_ONCE(sw->pps_enable, on ? 1 : 0);
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static irqreturn_t xlnx_tsn_ptp_timer_isr(int irq, void *priv)
+{
+	struct ptp_clock_event event = { .type = PTP_CLOCK_PPS };
+	struct xlnx_tsn *sw = priv;
+
+	sw->countpulse++;
+	if (sw->countpulse >= TSN_TIMER_PULSES_PER_PPS) {
+		sw->countpulse = 0;
+		if (sw->ptp_clock && READ_ONCE(sw->pps_enable))
+			ptp_clock_event(sw->ptp_clock, &event);
+	}
+
+	mac_iow(&sw->mac[XLNX_TSN_PORT_MAC1], TSN_TIMER_INTERRUPT,
+		TSN_TIMER_INT_CLEAR);
+
+	return IRQ_HANDLED;
+}
+
+int xlnx_tsn_ptp_init(struct xlnx_tsn *sw)
+{
+	struct timespec64 ts;
+	int ret;
+
+	spin_lock_init(&sw->reg_lock);
+
+	sw->ptp_timer_irq = of_irq_get_byname(sw->dev->of_node, "ptp_timer");
+	if (sw->ptp_timer_irq <= 0)
+		return dev_err_probe(sw->dev, sw->ptp_timer_irq ? : -ENXIO,
+				     "failed to get ptp_timer IRQ\n");
+
+	sw->ptp_clock_info.owner = THIS_MODULE;
+	snprintf(sw->ptp_clock_info.name, sizeof(sw->ptp_clock_info.name),
+		 "TSN PHC");
+	sw->ptp_clock_info.max_adj = 999999999;
+	sw->ptp_clock_info.pps = 1;
+	sw->ptp_clock_info.adjfine = xlnx_tsn_ptp_adjfine;
+	sw->ptp_clock_info.adjtime = xlnx_tsn_ptp_adjtime;
+	sw->ptp_clock_info.gettime64 = xlnx_tsn_ptp_gettime;
+	sw->ptp_clock_info.settime64 = xlnx_tsn_ptp_settime;
+	sw->ptp_clock_info.enable = xlnx_tsn_ptp_enable;
+
+	sw->ptp_clock = ptp_clock_register(&sw->ptp_clock_info, sw->dev);
+	if (IS_ERR_OR_NULL(sw->ptp_clock)) {
+		ret = sw->ptp_clock ? PTR_ERR(sw->ptp_clock) : -ENODEV;
+		sw->ptp_clock = NULL;
+		return dev_err_probe(sw->dev, ret,
+				     "failed to register PTP clock\n");
+	}
+
+	sw->rtc_value = div_u64(NSEC_PER_SEC, TSN_TIMER_GTX_CLK_FREQ) <<
+			TSN_TIMER_RTC_NS_SHIFT;
+	mac_iow(&sw->mac[XLNX_TSN_PORT_MAC1], TSN_TIMER_RTC_INCREMENT,
+		(u32)sw->rtc_value);
+
+	ts = ktime_to_timespec64(ktime_get_real());
+	xlnx_tsn_ptp_settime(&sw->ptp_clock_info, &ts);
+
+	ret = request_irq(sw->ptp_timer_irq, xlnx_tsn_ptp_timer_isr, 0,
+			  "xlnx-tsn-ptp-timer", sw);
+	if (ret) {
+		dev_err_probe(sw->dev, ret,
+			      "failed to request ptp_timer IRQ %d\n",
+			      sw->ptp_timer_irq);
+		goto err_unregister_clock;
+	}
+
+	return 0;
+
+err_unregister_clock:
+	ptp_clock_unregister(sw->ptp_clock);
+	sw->ptp_clock = NULL;
+	return ret;
+}
+
+void xlnx_tsn_ptp_exit(struct xlnx_tsn *sw)
+{
+	if (!sw->ptp_clock)
+		return;
+
+	free_irq(sw->ptp_timer_irq, sw);
+	ptp_clock_unregister(sw->ptp_clock);
+	sw->ptp_clock = NULL;
+}
-- 
2.34.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.