[PATCH 15/20] net: dsa: xilinx: register PHC backed by the RTC timer block
Nagadheeraj Rottela <[email protected]>
| Newsgroups | org.kernel.vger.netdev,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| 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