Added PTP Hardware Clock (PHC) support to the Xilinx TSN Ethernet driver by integrating the TSN IP's real-time clock (RTC) and PTP timer logic. A new tsn_ptp_timer abstraction is introduced to manage the PTP timer registers, RTC offset/increment handling, timer interrupts, and PPS generation. The implementation provides full support for PTP clock operations including: - gettime64 / settime64 - adjtime - adjfine - PPS enable/disable - PHC registration via ptp_clock_register() PTP timer interrupt handling is added to generate PPS output based on the TSN RTC pulse counter. The PTP timer is shared globally across the MAC instances and is initialized only for TEMAC1. Support is also added to ethtool -T via get_ts_info to report hardware timestamping capabilities and the PHC index. This enables Linux PTP services such as ptp4l and phc2sys to synchronize time using the TSN hardware clock. Signed-off-by: Srinivas Neeli --- drivers/net/ethernet/xilinx/tsn/Makefile | 2 +- drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h | 59 +++ .../net/ethernet/xilinx/tsn/xilinx_tsn_emac.c | 55 ++- .../xilinx/tsn/xilinx_tsn_ptp_clock.c | 386 ++++++++++++++++++ 4 files changed, 500 insertions(+), 2 deletions(-) create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_clock.c diff --git a/drivers/net/ethernet/xilinx/tsn/Makefile b/drivers/net/ethernet/xilinx/tsn/Makefile index fc1c0cda0843..0faa5233221b 100644 --- a/drivers/net/ethernet/xilinx/tsn/Makefile +++ b/drivers/net/ethernet/xilinx/tsn/Makefile @@ -1,2 +1,2 @@ obj-$(CONFIG_XILINX_TSN) :=xilinx_tsn.o -xilinx_tsn-objs := xilinx_tsn_main.o xilinx_tsn_ep.o xilinx_tsn_emac.o xilinx_tsn_mdio.o xilinx_tsn_switch.o +xilinx_tsn-objs := xilinx_tsn_main.o xilinx_tsn_ep.o xilinx_tsn_emac.o xilinx_tsn_mdio.o xilinx_tsn_switch.o xilinx_tsn_ptp_clock.o diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h index 91d01313aada..0cce916825ea 100644 --- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -25,12 +26,15 @@ #include #include #include +#include #include #include #include #include +#include #include #include +#include #include #define TSN_NUM_CLOCKS 6 @@ -107,6 +111,55 @@ #define TSN_EMMC_LINKSPEED_1000 BIT(31) /* 1000 Mbit */ #define TSN_MAX_EMAC_NO 2 +#define TSN_TEMAC1 1 +#define TSN_TEMAC2 2 + +/* PTP Timer Register Offsets (relative to timer base) */ +#define TSN_TIMER_RTC_OFFSET_NS 0x00000 /* RTC Nanoseconds Offset */ +#define TSN_TIMER_RTC_OFFSET_SEC_L 0x00008 /* RTC Seconds Offset - Low */ +#define TSN_TIMER_RTC_OFFSET_SEC_H 0x0000C /* RTC Seconds Offset - High */ +#define TSN_TIMER_RTC_INCREMENT 0x00010 /* RTC Increment */ +#define TSN_TIMER_CURRENT_RTC_NS 0x00014 /* Current TOD Nanoseconds - RO */ +#define TSN_TIMER_CURRENT_RTC_SEC_L 0x00018 /* Current TOD Seconds Low - RO */ +#define TSN_TIMER_CURRENT_RTC_SEC_H 0x0001C /* Current TOD Seconds High - RO */ +#define TSN_TIMER_INTERRUPT 0x00020 /* Interrupt register */ + +/* PTP Timer bit masks and constants */ +#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_SHIFT 0 +#define TSN_TIMER_RTC_NS_SHIFT 20 +#define PULSESIN1PPS 128 +#define TSN_TIMER_GTX_CLK_FREQ (125 * HZ_PER_MHZ) /* 125 MHz */ + +/* PTP Timer Register Base Offset */ +#define TSN_PTP_TIMER_OFFSET 0x12800 + +/** + * struct tsn_ptp_timer - PTP timer private data + * @dev: Device pointer + * @regs: Base address of PTP timer registers + * @ptp_clock: PTP clock instance + * @ptp_clock_info: PTP clock information + * @reg_lock: Register access spinlock + * @irq: PTP timer interrupt number + * @pps_enable: PPS output enable flag + * @countpulse: Pulse counter for PPS generation + * @rtc_value: RTC increment value + */ +struct tsn_ptp_timer { + struct device *dev; + void __iomem *regs; + struct ptp_clock *ptp_clock; + struct ptp_clock_info ptp_clock_info; + spinlock_t reg_lock; /* Protect ptp register access */ + int irq; + int pps_enable; + int countpulse; + u32 rtc_value; +}; /* * struct tsn_emac - TSN Ethernet MAC configuration structure @@ -218,6 +271,8 @@ struct tsn_endpoint { * @tx_chans: Array of TX DMA channels * @rx_chans: Array of RX DMA channels * @num_emacs: Number of EMAC instances + * @ptp_timer: Global PTP timer shared by both EMACs + * @phc_index: PTP Hardware Clock index */ struct tsn_priv { struct platform_device *pdev; @@ -240,6 +295,8 @@ struct tsn_priv { struct tsn_dma_chan **tx_chans; struct tsn_dma_chan **rx_chans; u32 num_emacs; + struct tsn_ptp_timer ptp_timer; + int phc_index; }; /** @@ -310,4 +367,6 @@ int tsn_mdio_setup(struct tsn_emac *emac, struct device_node *mac_np); void tsn_mdio_teardown(struct tsn_emac *emac); int tsn_switch_init(struct platform_device *pdev); void tsn_switch_exit(struct platform_device *pdev); +int tsn_ptp_timer_init(struct tsn_emac *emac, struct device_node *emac_np); +void tsn_ptp_timer_exit(struct tsn_emac *emac); #endif /* XILINX_TSN_H */ diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c index 26a533e313a2..b7d7ba0de717 100644 --- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c @@ -147,11 +147,46 @@ static void emac_get_drvinfo(struct net_device *ndev, strscpy(ed->version, DRIVER_VERSION, sizeof(ed->version)); } +/** + * emac_get_ts_info - Get timestamping and PTP information + * @ndev: Pointer to net_device structure + * @info: Pointer to ethtool_ts_info structure + * + * This function provides hardware timestamping capabilities and + * PTP hardware clock index for ethtool -T command. + * + * Return: 0 on success + */ +static int emac_get_ts_info(struct net_device *ndev, + struct kernel_ethtool_ts_info *info) +{ + struct tsn_emac *emac = netdev_priv(ndev); + struct tsn_priv *common = emac->common; + + info->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE | + SOF_TIMESTAMPING_RX_HARDWARE | + SOF_TIMESTAMPING_RAW_HARDWARE; + + info->tx_types = BIT(HWTSTAMP_TX_OFF) | + BIT(HWTSTAMP_TX_ON); + + info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) | + BIT(HWTSTAMP_FILTER_ALL); + + if (common->phc_index >= 0) + info->phc_index = common->phc_index; + else + info->phc_index = -1; + + return 0; +} + static const struct ethtool_ops emac_ethtool_ops = { .get_drvinfo = emac_get_drvinfo, .get_link = ethtool_op_get_link, .get_link_ksettings = phy_ethtool_get_link_ksettings, .set_link_ksettings = phy_ethtool_set_link_ksettings, + .get_ts_info = emac_get_ts_info, }; /** @@ -235,10 +270,19 @@ int tsn_emac_init(struct platform_device *pdev) } } + /* PTP timer initialization - ONLY for MAC 1 */ + if (emac->emac_num == TSN_TEMAC1) { + ret = tsn_ptp_timer_init(emac, emac_np); + if (ret) { + dev_err(dev, "Failed to initialize PTP timer for EMAC %d: %d\n", + emac->emac_num, ret); + goto err_teardown_mdio; + } + } ret = register_netdev(ndev); if (ret) { dev_err(dev, "Failed to register net device for MAC %d\n", mac_id); - goto err_teardown_mdio; + goto err_remove_ptp; } common->emacs[array_idx] = emac; @@ -246,6 +290,9 @@ int tsn_emac_init(struct platform_device *pdev) common->num_emacs = array_idx; continue; +err_remove_ptp: + if (emac->emac_num == TSN_TEMAC1) + tsn_ptp_timer_exit(emac); err_teardown_mdio: if (emac->phy_node) tsn_mdio_teardown(emac); @@ -275,6 +322,8 @@ int tsn_emac_init(struct platform_device *pdev) dev_info(dev, "Cleaning up MAC %u (array[%d])\n", old->emac_num, array_idx); unregister_netdev(old->ndev); + if (old->emac_num == TSN_TEMAC1) + tsn_ptp_timer_exit(old); if (old->phy_node) { tsn_mdio_teardown(old); @@ -314,6 +363,10 @@ void tsn_emac_exit(struct platform_device *pdev) dev_info(dev, "Cleaning up MAC %u (array[%d])\n", emac->emac_num, i); unregister_netdev(emac->ndev); + + if (emac->emac_num == TSN_TEMAC1) + tsn_ptp_timer_exit(emac); + if (emac->phy_node) { tsn_mdio_teardown(emac); of_node_put(emac->phy_node); diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_clock.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_clock.c new file mode 100644 index 000000000000..1f6bc932fc6b --- /dev/null +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_clock.c @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "xilinx_tsn.h" + +/** + * rtc_iow - Write to PTP RTC timer register + * @timer: Pointer to TSN PTP timer structure + * @reg: Register offset + * @val: Value to write + * + * This function writes the desired value into the corresponding TSN + * PTP register. + */ +static inline void rtc_iow(struct tsn_ptp_timer *timer, u32 reg, u32 val) +{ + iowrite32(val, timer->regs + reg); +} + +/** + * rtc_ior - Read from PTP RTC timer register + * @timer: Pointer to TSN PTP timer structure + * @reg: Register offset + * + * This function reads a value from the corresponding TSN PTP + * register. + * + * Return: Register value + */ +static inline u32 rtc_ior(struct tsn_ptp_timer *timer, u32 reg) +{ + return ioread32(timer->regs + reg); +} + +/** + * tsn_tod_read - Read current time-of-day from RTC timer + * @timer: Pointer to TSN PTP timer structure + * @ts: Pointer to timespec64 to store current time + * + * Reads the 64-bit seconds (high + low) and nanoseconds from the RTC current + * time registers. Values are masked to valid ranges. + */ +static void tsn_tod_read(struct tsn_ptp_timer *timer, + struct timespec64 *ts) +{ + u32 secl, sech, nsec; + + nsec = rtc_ior(timer, TSN_TIMER_CURRENT_RTC_NS); + secl = rtc_ior(timer, TSN_TIMER_CURRENT_RTC_SEC_L); + sech = rtc_ior(timer, 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; +} + +/** + * tsn_rtc_offset_write - Write time offset to RTC offset registers + * @timer: Pointer to TSN PTP timer structure + * @ts: Pointer to timespec64 with offset value to write + * + */ +static void tsn_rtc_offset_write(struct tsn_ptp_timer *timer, + const struct timespec64 *ts) +{ + rtc_iow(timer, TSN_TIMER_RTC_OFFSET_SEC_H, upper_32_bits(ts->tv_sec)); + rtc_iow(timer, TSN_TIMER_RTC_OFFSET_SEC_L, lower_32_bits(ts->tv_sec)); + rtc_iow(timer, TSN_TIMER_RTC_OFFSET_NS, ts->tv_nsec); +} + +/** + * tsn_rtc_offset_read - Read time offset from RTC offset registers + * @timer: Pointer to TSN PTP timer structure + * @ts: Pointer to timespec64 to store offset value + * + * Reads the current RTC offset value from the offset registers. + * Values are masked to valid ranges. + */ +static void tsn_rtc_offset_read(struct tsn_ptp_timer *timer, + struct timespec64 *ts) +{ + u32 secl, sech, nsec; + + secl = rtc_ior(timer, TSN_TIMER_RTC_OFFSET_SEC_L); + sech = rtc_ior(timer, TSN_TIMER_RTC_OFFSET_SEC_H); + nsec = rtc_ior(timer, 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; +} + +/** + * tsn_ptp_adjfine - Adjust PTP clock frequency + * @ptp: Pointer to PTP clock info structure + * @scaled_ppm: Frequency adjustment in scaled parts-per-million + * + * Adjusts the RTC increment value to fine-tune the clock frequency. + * Uses adjust_by_scaled_ppm() helper to calculate the new increment value + * based on the base RTC value (calculated from 125 MHz GTX clock). + * + * Return: 0 on success + */ +static int tsn_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) +{ + struct tsn_ptp_timer *timer = container_of(ptp, + struct tsn_ptp_timer, + ptp_clock_info); + u32 incval; + + incval = adjust_by_scaled_ppm(timer->rtc_value, scaled_ppm); + rtc_iow(timer, TSN_TIMER_RTC_INCREMENT, incval); + + return 0; +} + +/** + * tsn_ptp_adjtime - Adjust PTP clock time by offset + * @ptp: Pointer to PTP clock info structure + * @delta: Time offset in nanoseconds (positive or negative) + * + * Adjusts the RTC time by adding the specified delta offset. + * Reads the current offset, adds the delta to it, and writes back. + * + * Return: 0 on success + */ +static int tsn_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + struct tsn_ptp_timer *timer = container_of(ptp, struct tsn_ptp_timer, + ptp_clock_info); + struct timespec64 now, then = ns_to_timespec64(delta); + + guard(spinlock_irqsave)(&timer->reg_lock); + + tsn_rtc_offset_read(timer, &now); + now = timespec64_add(now, then); + tsn_rtc_offset_write(timer, &now); + + return 0; +} + +/** + * tsn_ptp_gettime - Get current PTP clock time + * @ptp: Pointer to PTP clock info structure + * @ts: Pointer to timespec64 to receive current time + * + * Reads the current time-of-day from the RTC timer. + * + * Return: 0 on success + */ +static int tsn_ptp_gettime(struct ptp_clock_info *ptp, + struct timespec64 *ts) +{ + struct tsn_ptp_timer *timer = container_of(ptp, struct tsn_ptp_timer, + ptp_clock_info); + + guard(spinlock_irqsave)(&timer->reg_lock); + tsn_tod_read(timer, ts); + + return 0; +} + +/** + * tsn_ptp_settime - Set PTP clock time + * @ptp: Pointer to PTP clock info structure + * @ts: Pointer to timespec64 with new time to set + * + * Return: 0 on success, -EINVAL for invalid timestamp + */ +static int tsn_ptp_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + struct tsn_ptp_timer *timer = container_of(ptp, struct tsn_ptp_timer, + ptp_clock_info); + struct timespec64 delta, tod, offset; + + if (!ts || ts->tv_nsec < 0 || ts->tv_nsec >= NSEC_PER_SEC) + return -EINVAL; + + guard(spinlock_irqsave)(&timer->reg_lock); + + /* Zero the offset first */ + offset.tv_sec = 0; + offset.tv_nsec = 0; + tsn_rtc_offset_write(timer, &offset); + + /* Get current timer value */ + tsn_tod_read(timer, &tod); + + /* Calculate delta */ + delta = timespec64_sub(*ts, tod); + + /* Don't write negative offset */ + if (delta.tv_sec < 0 || (delta.tv_sec == 0 && delta.tv_nsec < 0)) { + delta.tv_sec = 0; + delta.tv_nsec = 0; + } + + tsn_rtc_offset_write(timer, &delta); + + return 0; +} + +/** + * tsn_ptp_enable - Enable or disable PPS output + * @ptp: Pointer to PTP clock info structure + * @rq: Pointer to PTP clock request + * @on: 1 to enable, 0 to disable + * + * Enables or disables the PPS (pulse-per-second) event delivery. + * The TSN IP generates 128 pulses per second, and this function controls + * whether those pulses are reported to the PTP subsystem via ptp_clock_event(). + * Only supports PTP_CLK_REQ_PPS request type. + * + * Return: 0 on success, -EOPNOTSUPP for unsupported request types + */ +static int tsn_ptp_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *rq, int on) +{ + struct tsn_ptp_timer *timer = container_of(ptp, struct tsn_ptp_timer, + ptp_clock_info); + + switch (rq->type) { + case PTP_CLK_REQ_PPS: + timer->pps_enable = on ? 1 : 0; + return 0; + default: + break; + } + + return -EOPNOTSUPP; +} + +/** + * tsn_ptp_timer_isr - PTP timer interrupt handler + * @irq: Interrupt number + * @priv: Pointer to tsn_ptp_timer structure + * + * Handles PTP timer interrupts for PPS (pulse-per-second) events. + * The TSN IP generates 128 pulses per second. This ISR counts those pulses + * and delivers a PTP_CLOCK_PPS event once per second (every 128 pulses) if + * PPS is enabled via tsn_ptp_enable(). + * + * Return: IRQ_HANDLED + */ +static irqreturn_t tsn_ptp_timer_isr(int irq, void *priv) +{ + struct tsn_ptp_timer *timer = priv; + struct ptp_clock_event event; + + event.type = PTP_CLOCK_PPS; + + timer->countpulse++; + if (timer->countpulse >= PULSESIN1PPS) { + timer->countpulse = 0; + if (timer->ptp_clock && timer->pps_enable) + ptp_clock_event(timer->ptp_clock, &event); + } + + /* Clear interrupt */ + rtc_iow(timer, TSN_TIMER_INTERRUPT, BIT(TSN_TIMER_INT_SHIFT)); + + return IRQ_HANDLED; +} + +/** + * tsn_ptp_timer_init - Initialize PTP timer and register PHC + * @emac: Pointer to TSN EMAC structure + * @emac_np: Pointer to EMAC device tree node + * + * The PTP timer is shared globally - only initialized once for TEMAC1. + * TEMAC2 will skip initialization and share the same PHC index. + * + * Return: 0 on success, negative error code on failure + */ +int tsn_ptp_timer_init(struct tsn_emac *emac, struct device_node *emac_np) +{ + struct tsn_priv *common = emac->common; + struct tsn_ptp_timer *timer = &common->ptp_timer; + struct device *dev = common->dev; + struct timespec64 ts; + int ret; + + if (timer->ptp_clock) { + dev_info(dev, "PTP timer already initialized (PHC: %d)\n", + common->phc_index); + return 0; + } + + memset(timer, 0, sizeof(*timer)); + timer->dev = dev; + timer->irq = -1; + + timer->regs = emac->regs + TSN_PTP_TIMER_OFFSET; + + spin_lock_init(&timer->reg_lock); + + timer->irq = of_irq_get_byname(emac_np, "interrupt_ptp_timer"); + if (timer->irq < 0) { + timer->irq = platform_get_irq_byname(common->pdev, "interrupt_ptp_timer"); + if (timer->irq < 0) { + dev_err(dev, "Failed to get PTP timer interrupt: %d\n", + timer->irq); + ret = timer->irq; + goto err_cleanup; + } + } + + ret = devm_request_irq(dev, timer->irq, tsn_ptp_timer_isr, 0, + "tsn_ptp_timer", timer); + if (ret) { + dev_err(dev, "Failed to request PTP timer IRQ %d: %d\n", + timer->irq, ret); + goto err_cleanup; + } + + /* Setup PTP clock info */ + timer->ptp_clock_info.owner = THIS_MODULE; + snprintf(timer->ptp_clock_info.name, + sizeof(timer->ptp_clock_info.name), "TSN PTP"); + timer->ptp_clock_info.max_adj = 999999999; + timer->ptp_clock_info.n_ext_ts = 0; + timer->ptp_clock_info.pps = 1; + timer->ptp_clock_info.adjfine = tsn_ptp_adjfine; + timer->ptp_clock_info.adjtime = tsn_ptp_adjtime; + timer->ptp_clock_info.gettime64 = tsn_ptp_gettime; + timer->ptp_clock_info.settime64 = tsn_ptp_settime; + timer->ptp_clock_info.enable = tsn_ptp_enable; + + /* Register PTP clock */ + timer->ptp_clock = ptp_clock_register(&timer->ptp_clock_info, dev); + if (IS_ERR(timer->ptp_clock)) { + ret = PTR_ERR(timer->ptp_clock); + dev_err(dev, "Failed to register PTP clock: %d\n", ret); + timer->ptp_clock = NULL; + goto err_cleanup; + } + + /* In the TSN IP Core, RTC clock is connected to gtx_clk which is + * 125 MHz. This is specified in the TSN PG and is not configurable. + * + * Calculating the RTC Increment Value once and storing it in + * timer->rtc_value to prevent recalculating it each time the PTP + * frequency is adjusted in xlnx_ptp_adjfine() + */ + timer->rtc_value = (div_u64(NSEC_PER_SEC, TSN_TIMER_GTX_CLK_FREQ) << + TSN_TIMER_RTC_NS_SHIFT); + + rtc_iow(timer, TSN_TIMER_RTC_INCREMENT, timer->rtc_value); + + ts = ktime_to_timespec64(ktime_get_real()); + tsn_ptp_settime(&timer->ptp_clock_info, &ts); + + /* Store PHC index */ + common->phc_index = ptp_clock_index(timer->ptp_clock); + + dev_info(dev, "PTP timer initialized (PHC: %d, IRQ: %d, offset: 0x%x)\n", + common->phc_index, timer->irq, TSN_PTP_TIMER_OFFSET); + + return 0; + +err_cleanup: + timer->irq = -1; + common->phc_index = -1; + return ret; +} + +/** + * tsn_ptp_timer_exit - Cleanup PTP timer and unregister PHC + * @emac: Pointer to TSN EMAC structure + * + * Unregisters the PTP clock from the kernel PTP subsystem and + * cleans up the PTP timer state. Sets phc_index back to -1. + * The interrupt is automatically freed by devm_request_irq(). + */ +void tsn_ptp_timer_exit(struct tsn_emac *emac) +{ + struct tsn_priv *common = emac->common; + struct tsn_ptp_timer *timer = &common->ptp_timer; + + if (!timer->ptp_clock) + return; + + ptp_clock_unregister(timer->ptp_clock); + dev_info(common->dev, "PTP timer unregistered (PHC: %d)\n", + common->phc_index); + + timer->ptp_clock = NULL; + common->phc_index = -1; +} -- 2.25.1