[PATCH 15/20] net: dsa: xilinx: register PHC backed by the RTC timer block
Nagadheeraj Rottela
nagadheeraj.rottela at amd.com
Fri Aug 7 03:44:26 PDT 2026
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 <srinivas.neeli at amd.com>
Signed-off-by: Srinivas Neeli <srinivas.neeli at amd.com>
Signed-off-by: Nagadheeraj Rottela <nagadheeraj.rottela at amd.com>
---
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
More information about the linux-arm-kernel
mailing list