Subject: Re: [PATCH net-next v2 1/4] net: wangxun: Add support for PTP clock

From: Vadim Fedorenko <[email-masked]>
Date: Mon, 6 Jan 2025 10:35:13 +0000

On 06/01/2025 08:45, Jiawen Wu wrote:
> Implement support for PTP clock on Wangxun NICs.
> 
> Signed-off-by: Jiawen Wu <[email-masked]>
> ---
>   drivers/net/ethernet/wangxun/libwx/Makefile   |   2 +-
>   .../net/ethernet/wangxun/libwx/wx_ethtool.c   |   3 +
>   drivers/net/ethernet/wangxun/libwx/wx_lib.c   |  49 +-
>   drivers/net/ethernet/wangxun/libwx/wx_ptp.c   | 711 ++++++++++++++++++
>   drivers/net/ethernet/wangxun/libwx/wx_ptp.h   |  19 +
>   drivers/net/ethernet/wangxun/libwx/wx_type.h  |  67 ++
>   drivers/net/ethernet/wangxun/ngbe/ngbe_main.c |   8 +
>   drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c |  10 +
>   .../net/ethernet/wangxun/txgbe/txgbe_main.c   |  11 +
>   .../net/ethernet/wangxun/txgbe/txgbe_phy.c    |   9 +
>   10 files changed, 883 insertions(+), 6 deletions(-)
>   create mode 100644 drivers/net/ethernet/wangxun/libwx/wx_ptp.c
>   create mode 100644 drivers/net/ethernet/wangxun/libwx/wx_ptp.h
> 
> diff --git a/drivers/net/ethernet/wangxun/libwx/Makefile b/drivers/net/ethernet/wangxun/libwx/Makefile
> index 42ccd6e4052e..e9f0f1f2309b 100644
> --- a/drivers/net/ethernet/wangxun/libwx/Makefile
> +++ b/drivers/net/ethernet/wangxun/libwx/Makefile
> @@ -4,4 +4,4 @@
>   
>   obj-$(CONFIG_LIBWX) += libwx.o
>   
> -libwx-objs := wx_hw.o wx_lib.o wx_ethtool.o
> +libwx-objs := wx_hw.o wx_lib.o wx_ethtool.o wx_ptp.o
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
> index abe5921dde02..c4b3b00b0926 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
> @@ -41,6 +41,9 @@ static const struct wx_stats wx_gstrings_stats[] = {
>   	WX_STAT("rx_csum_offload_good_count", hw_csum_rx_good),
>   	WX_STAT("rx_csum_offload_errors", hw_csum_rx_error),
>   	WX_STAT("alloc_rx_buff_failed", alloc_rx_buff_failed),
> +	WX_STAT("tx_hwtstamp_timeouts", tx_hwtstamp_timeouts),
> +	WX_STAT("tx_hwtstamp_skipped", tx_hwtstamp_skipped),
> +	WX_STAT("rx_hwtstamp_cleared", rx_hwtstamp_cleared),
>   };
>   
>   static const struct wx_stats wx_gstrings_fdir_stats[] = {
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
> index 2b3d6586f44a..2c266aee667e 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
> @@ -13,6 +13,7 @@
>   
>   #include "wx_type.h"
>   #include "wx_lib.h"
> +#include "wx_ptp.h"
>   #include "wx_hw.h"
>   
>   /* Lookup table mapping the HW PTYPE to the bit field for decoding */
> @@ -597,8 +598,17 @@ static void wx_process_skb_fields(struct wx_ring *rx_ring,
>   				  union wx_rx_desc *rx_desc,
>   				  struct sk_buff *skb)
>   {
> +	struct wx *wx = netdev_priv(rx_ring->netdev);
> +
>   	wx_rx_hash(rx_ring, rx_desc, skb);
>   	wx_rx_checksum(rx_ring, rx_desc, skb);
> +
> +	if (unlikely(test_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, wx->flags)) &&
> +	    unlikely(wx_test_staterr(rx_desc, WX_RXD_STAT_TS))) {
> +		wx_ptp_rx_hwtstamp(rx_ring->q_vector->wx, skb);
> +		rx_ring->last_rx_timestamp = jiffies;
> +	}
> +
>   	wx_rx_vlan(rx_ring, rx_desc, skb);
>   	skb_record_rx_queue(skb, rx_ring->queue_index);
>   	skb->protocol = eth_type_trans(skb, rx_ring->netdev);
> @@ -932,9 +942,9 @@ static void wx_tx_olinfo_status(union wx_tx_desc *tx_desc,
>   	tx_desc->read.olinfo_status = cpu_to_le32(olinfo_status);
>   }
>   
> -static void wx_tx_map(struct wx_ring *tx_ring,
> -		      struct wx_tx_buffer *first,
> -		      const u8 hdr_len)
> +static int wx_tx_map(struct wx_ring *tx_ring,
> +		     struct wx_tx_buffer *first,
> +		     const u8 hdr_len)
>   {
>   	struct sk_buff *skb = first->skb;
>   	struct wx_tx_buffer *tx_buffer;
> @@ -1013,6 +1023,8 @@ static void wx_tx_map(struct wx_ring *tx_ring,
>   
>   	netdev_tx_sent_queue(wx_txring_txq(tx_ring), first->bytecount);
>   
> +	/* set the timestamp */
> +	first->time_stamp = jiffies;
>   	skb_tx_timestamp(skb);
>   
>   	/* Force memory writes to complete before letting h/w know there
> @@ -1038,7 +1050,7 @@ static void wx_tx_map(struct wx_ring *tx_ring,
>   	if (netif_xmit_stopped(wx_txring_txq(tx_ring)) || !netdev_xmit_more())
>   		writel(i, tx_ring->tail);
>   
> -	return;
> +	return 0;
>   dma_error:
>   	dev_err(tx_ring->dev, "TX DMA map failed\n");
>   
> @@ -1062,6 +1074,8 @@ static void wx_tx_map(struct wx_ring *tx_ring,
>   	first->skb = NULL;
>   
>   	tx_ring->next_to_use = i;
> +
> +	return -ENOMEM;
>   }
>   
>   static void wx_tx_ctxtdesc(struct wx_ring *tx_ring, u32 vlan_macip_lens,
> @@ -1486,6 +1500,23 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb,
>   		tx_flags |= WX_TX_FLAGS_HW_VLAN;
>   	}
>   
> +	if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) &&
> +	    wx->ptp_clock) {
> +		if (wx->tstamp_config.tx_type == HWTSTAMP_TX_ON &&
> +		    !test_and_set_bit_lock(WX_STATE_PTP_TX_IN_PROGRESS,
> +					   wx->state)) {
> +			skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
> +			tx_flags |= WX_TX_FLAGS_TSTAMP;
> +
> +			/* schedule check for Tx timestamp */
> +			wx->ptp_tx_skb = skb_get(skb);
> +			wx->ptp_tx_start = jiffies;
> +			schedule_work(&wx->ptp_tx_work);
> +		} else {
> +			wx->tx_hwtstamp_skipped++;
> +		}
> +	}
> +
>   	/* record initial flags and protocol */
>   	first->tx_flags = tx_flags;
>   	first->protocol = vlan_get_protocol(skb);
> @@ -1501,12 +1532,20 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb,
>   	if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags) && tx_ring->atr_sample_rate)
>   		wx->atr(tx_ring, first, ptype);
>   
> -	wx_tx_map(tx_ring, first, hdr_len);
> +	if (wx_tx_map(tx_ring, first, hdr_len))
> +		goto cleanup_tx_tstamp;
>   
>   	return NETDEV_TX_OK;
>   out_drop:
>   	dev_kfree_skb_any(first->skb);
>   	first->skb = NULL;
> +cleanup_tx_tstamp:
> +	if (unlikely(tx_flags & WX_TX_FLAGS_TSTAMP)) {
> +		dev_kfree_skb_any(wx->ptp_tx_skb);
> +		wx->ptp_tx_skb = NULL;
> +		cancel_work_sync(&wx->ptp_tx_work);
> +		clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state);
> +	}
>   
>   	return NETDEV_TX_OK;
>   }
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
> new file mode 100644
> index 000000000000..0f683e576b29
> --- /dev/null
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
> @@ -0,0 +1,711 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
> +
> +#include <linux/ptp_classify.h>
> +#include <linux/clocksource.h>
> +#include <linux/pci.h>
> +
> +#include "wx_type.h"
> +#include "wx_ptp.h"
> +#include "wx_hw.h"
> +
> +#define WX_INCVAL_10GB        0xCCCCCC
> +#define WX_INCVAL_1GB         0x800000
> +#define WX_INCVAL_100         0xA00000
> +#define WX_INCVAL_10          0xC7F380
> +#define WX_INCVAL_EM          0x2000000
> +
> +#define WX_INCVAL_SHIFT_10GB  20
> +#define WX_INCVAL_SHIFT_1GB   18
> +#define WX_INCVAL_SHIFT_100   15
> +#define WX_INCVAL_SHIFT_10    12
> +#define WX_INCVAL_SHIFT_EM    22
> +
> +#define WX_OVERFLOW_PERIOD    (HZ * 30)
> +#define WX_PTP_TX_TIMEOUT     (HZ)
> +
> +#define WX_1588_PPS_WIDTH_EM  120
> +
> +#define WX_NS_PER_SEC         1000000000ULL
> +#define WX_NS_PER_MSEC        1000000ULL
> +
> +/**
> + * wx_ptp_adjfine
> + * @ptp: the ptp clock structure
> + * @ppb: parts per billion adjustment from base
> + * Returns 0 on success
> + *
> + * Adjust the frequency of the ptp cycle counter by the
> + * indicated ppb from the base frequency.
> + */
> +static int wx_ptp_adjfine(struct ptp_clock_info *ptp, long ppb)
> +{
> +	struct wx *wx = container_of(ptp, struct wx, ptp_caps);
> +	u64 incval, mask;
> +
> +	smp_mb(); /* Force any pending update before accessing. */
> +	incval = READ_ONCE(wx->base_incval);
> +	incval = adjust_by_scaled_ppm(incval, ppb);
> +
> +	mask = (wx->mac.type == wx_mac_em) ? 0x7FFFFFF : 0xFFFFFF;
> +	if (incval > mask)
> +		dev_warn(&wx->pdev->dev,
> +			 "PTP ppb adjusted SYSTIME rate overflowed!\n");
> +
> +	incval &= mask;
> +	if (wx->mac.type != wx_mac_em)
> +		incval |= 2 << 24;
> +
> +	wr32ptp(wx, WX_TSC_1588_INC, incval);
> +
> +	return 0;
> +}
> +
> +/**
> + * wx_ptp_adjtime
> + * @ptp: the ptp clock structure
> + * @delta: offset to adjust the cycle counter by ns
> + * Returns 0 on success
> + *
> + * Adjust the timer by resetting the timecounter structure.
> + */
> +static int wx_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
> +{
> +	struct wx *wx = container_of(ptp, struct wx, ptp_caps);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&wx->tmreg_lock, flags);
> +	timecounter_adjtime(&wx->hw_tc, delta);
> +	spin_unlock_irqrestore(&wx->tmreg_lock, flags);
> +
> +	return 0;
> +}
> +
> +/**
> + * wx_ptp_gettimex64
> + * @ptp: the ptp clock structure
> + * @ts: timespec structure to hold the current time value
> + * @sts: structure to hold the system time before and after reading the PHC
> + * Returns 0 on success
> + *
> + * Read the timecounter and return the correct value on ns,
> + * after converting it into a struct timespec64.
> + */
> +static int wx_ptp_gettimex64(struct ptp_clock_info *ptp,
> +			     struct timespec64 *ts,
> +			     struct ptp_system_timestamp *sts)
> +{
> +	struct wx *wx = container_of(ptp, struct wx, ptp_caps);
> +	unsigned long flags;
> +	u64 ns, stamp;
> +
> +	spin_lock_irqsave(&wx->tmreg_lock, flags);
> +
> +	ptp_read_system_prets(sts);
> +	stamp = (u64)rd32ptp(wx, WX_TSC_1588_SYSTIML);
> +	ptp_read_system_postts(sts);
> +	stamp |= (u64)rd32ptp(wx, WX_TSC_1588_SYSTIMH) << 32;
> +	ns = timecounter_cyc2time(&wx->hw_tc, stamp);
> +
> +	spin_unlock_irqrestore(&wx->tmreg_lock, flags);
> +
> +	*ts = ns_to_timespec64(ns);
> +
> +	return 0;
> +}
> +
> +/**
> + * wx_ptp_settime64
> + * @ptp: the ptp clock structure
> + * @ts: the timespec64 containing the new time for the cycle counter
> + * Returns 0 on success
> + *
> + * Reset the timecounter to use a new base value instead of the kernel
> + * wall timer value.
> + */
> +static int wx_ptp_settime64(struct ptp_clock_info *ptp,
> +			    const struct timespec64 *ts)
> +{
> +	struct wx *wx = container_of(ptp, struct wx, ptp_caps);
> +	unsigned long flags;
> +	u64 ns;
> +
> +	ns = timespec64_to_ns(ts);
> +
> +	/* reset the timecounter */
> +	spin_lock_irqsave(&wx->tmreg_lock, flags);
> +	timecounter_init(&wx->hw_tc, &wx->hw_cc, ns);
> +	spin_unlock_irqrestore(&wx->tmreg_lock, flags);
> +
> +	return 0;
> +}
> +
> +/**
> + * wx_ptp_clear_tx_timestamp - utility function to clear Tx timestamp state
> + * @wx: the private board structure
> + *
> + * This function should be called whenever the state related to a Tx timestamp
> + * needs to be cleared. This helps ensure that all related bits are reset for
> + * the next Tx timestamp event.
> + */
> +static void wx_ptp_clear_tx_timestamp(struct wx *wx)
> +{
> +	rd32ptp(wx, WX_TSC_1588_STMPH);
> +	if (wx->ptp_tx_skb) {
> +		dev_kfree_skb_any(wx->ptp_tx_skb);
> +		wx->ptp_tx_skb = NULL;
> +	}
> +	clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state);
> +}
> +
> +/**
> + * wx_ptp_convert_to_hwtstamp - convert register value to hw timestamp
> + * @wx: private board structure
> + * @hwtstamp: stack timestamp structure
> + * @timestamp: unsigned 64bit system time value
> + *
> + * We need to convert the adapter's RX/TXSTMP registers into a hwtstamp value
> + * which can be used by the stack's ptp functions.
> + *
> + * The lock is used to protect consistency of the cyclecounter and the SYSTIME
> + * registers. However, it does not need to protect against the Rx or Tx
> + * timestamp registers, as there can't be a new timestamp until the old one is
> + * unlatched by reading.
> + *
> + * In addition to the timestamp in hardware, some controllers need a software
> + * overflow cyclecounter, and this function takes this into account as well.
> + **/
> +static void wx_ptp_convert_to_hwtstamp(struct wx *wx,
> +				       struct skb_shared_hwtstamps *hwtstamp,
> +				       u64 timestamp)
> +{
> +	unsigned long flags;
> +	u64 ns;
> +
> +	memset(hwtstamp, 0, sizeof(*hwtstamp));
> +
> +	spin_lock_irqsave(&wx->tmreg_lock, flags);
> +	ns = timecounter_cyc2time(&wx->hw_tc, timestamp);
> +	spin_unlock_irqrestore(&wx->tmreg_lock, flags);
> +
> +	hwtstamp->hwtstamp = ns_to_ktime(ns);
> +}
> +
> +/**
> + * wx_ptp_tx_hwtstamp - utility function which checks for TX time stamp
> + * @wx: the private board struct
> + *
> + * if the timestamp is valid, we convert it into the timecounter ns
> + * value, then store that result into the shhwtstamps structure which
> + * is passed up the network stack
> + */
> +static void wx_ptp_tx_hwtstamp(struct wx *wx)
> +{
> +	struct skb_shared_hwtstamps shhwtstamps;
> +	struct sk_buff *skb = wx->ptp_tx_skb;
> +	u64 regval = 0;
> +
> +	regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPL);
> +	regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPH) << 32;
> +
> +	wx_ptp_convert_to_hwtstamp(wx, &shhwtstamps, regval);
> +
> +	wx->ptp_tx_skb = NULL;
> +	clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state);
> +	skb_tstamp_tx(skb, &shhwtstamps);
> +	dev_kfree_skb_any(skb);
> +}
> +
> +/**
> + * wx_ptp_tx_hwtstamp_work
> + * @work: pointer to the work struct
> + *
> + * This work item polls TSC_1588_CTL valid bit to determine when a Tx hardware
> + * timestamp has been taken for the current skb. It is necessary, because the
> + * descriptor's "done" bit does not correlate with the timestamp event.
> + */
> +static void wx_ptp_tx_hwtstamp_work(struct work_struct *work)
> +{
> +	struct wx *wx = container_of(work, struct wx, ptp_tx_work);
> +	u32 tsynctxctl;
> +	bool timeout;
> +
> +	/* we have to have a valid skb to poll for a timestamp */
> +	if (!wx->ptp_tx_skb) {
> +		wx_ptp_clear_tx_timestamp(wx);
> +		return;
> +	}
> +
> +	/* stop polling once we have a valid timestamp */
> +	tsynctxctl = rd32ptp(wx, WX_TSC_1588_CTL);
> +	if (tsynctxctl & WX_TSC_1588_CTL_VALID) {
> +		wx_ptp_tx_hwtstamp(wx);
> +		return;
> +	}
> +
> +	timeout = time_is_before_jiffies(wx->ptp_tx_start + WX_PTP_TX_TIMEOUT);
> +	/* check timeout last in case timestamp event just occurred */
> +	if (timeout) {
> +		wx_ptp_clear_tx_timestamp(wx);
> +		wx->tx_hwtstamp_timeouts++;
> +		dev_warn(&wx->pdev->dev, "clearing Tx Timestamp hang\n");
> +	} else {
> +		/* reschedule to keep checking until we timeout */
> +		schedule_work(&wx->ptp_tx_work);
> +	}
> +}
> +
> +/**
> + * wx_ptp_create_clock
> + * @wx: the private board structure
> + *
> + * Returns 0 on success, negative value on failure
> + *
> + * This function performs setup of the user entry point function table and
> + * initalizes the PTP clock device used by userspace to access the clock-like
> + * features of the PTP core. It will be called by wx_ptp_init, and may
> + * re-use a previously initialized clock (such as during a suspend/resume
> + * cycle).
> + */
> +static long wx_ptp_create_clock(struct wx *wx)
> +{
> +	struct net_device *netdev = wx->netdev;
> +	long err;
> +
> +	/* do nothing if we already have a clock device */
> +	if (!IS_ERR_OR_NULL(wx->ptp_clock))
> +		return 0;
> +
> +	snprintf(wx->ptp_caps.name, sizeof(wx->ptp_caps.name),
> +		 "%s", netdev->name);
> +	wx->ptp_caps.owner = THIS_MODULE;
> +	wx->ptp_caps.n_alarm = 0;
> +	wx->ptp_caps.n_ext_ts = 0;
> +	wx->ptp_caps.n_per_out = 0;
> +	wx->ptp_caps.pps = 0;
> +	wx->ptp_caps.adjfine = wx_ptp_adjfine;
> +	wx->ptp_caps.adjtime = wx_ptp_adjtime;
> +	wx->ptp_caps.gettimex64 = wx_ptp_gettimex64;
> +	wx->ptp_caps.settime64 = wx_ptp_settime64;
> +	wx->ptp_caps.do_aux_work = wx_ptp_do_aux_work;

wx_ptp_do_aux_work is not defined in this patch, it appears in patch 3
only. did you compile test your code patch by patch?

> +	if (wx->mac.type == wx_mac_em)
> +		wx->ptp_caps.max_adj = 500000000;
> +	else
> +		wx->ptp_caps.max_adj = 250000000;
> +
> +	wx->ptp_clock = ptp_clock_register(&wx->ptp_caps, &wx->pdev->dev);
> +	if (IS_ERR(wx->ptp_clock)) {
> +		err = PTR_ERR(wx->ptp_clock);
> +		wx->ptp_clock = NULL;
> +		wx_err(wx, "ptp clock register failed\n");
> +		return err;
> +	} else if (wx->ptp_clock) {
> +		dev_info(&wx->pdev->dev, "registered PHC device on %s\n",
> +			 netdev->name);
> +	}
> +
> +	/* Set the default timestamp mode to disabled here. We do this in
> +	 * create_clock instead of initialization, because we don't want to
> +	 * override the previous settings during a suspend/resume cycle.
> +	 */
> +	wx->tstamp_config.rx_filter = HWTSTAMP_FILTER_NONE;
> +	wx->tstamp_config.tx_type = HWTSTAMP_TX_OFF;
> +
> +	return 0;
> +}
> +
> +/**
> + * wx_ptp_set_timestamp_mode - setup the hardware for the requested mode
> + * @wx: the private board structure
> + * @config: the hwtstamp configuration requested
> + *
> + * Returns 0 on success, negative on failure
> + *
> + * Outgoing time stamping can be enabled and disabled. Play nice and
> + * disable it when requested, although it shouldn't cause any overhead
> + * when no packet needs it. At most one packet in the queue may be
> + * marked for time stamping, otherwise it would be impossible to tell
> + * for sure to which packet the hardware time stamp belongs.
> + *
> + * Incoming time stamping has to be configured via the hardware
> + * filters. Not all combinations are supported, in particular event
> + * type has to be specified. Matching the kind of event packet is
> + * not supported, with the exception of "all V2 events regardless of
> + * level 2 or 4".
> + *
> + * Since hardware always timestamps Path delay packets when timestamping V2
> + * packets, regardless of the type specified in the register, only use V2
> + * Event mode. This more accurately tells the user what the hardware is going
> + * to do anyways.
> + *
> + * Note: this may modify the hwtstamp configuration towards a more general
> + * mode, if required to support the specifically requested mode.
> + */
> +static int wx_ptp_set_timestamp_mode(struct wx *wx,
> +				     struct kernel_hwtstamp_config *config)
> +{
> +	u32 tsync_tx_ctl = WX_TSC_1588_CTL_ENABLED;
> +	u32 tsync_rx_ctl = WX_PSR_1588_CTL_ENABLED;
> +	DECLARE_BITMAP(flags, WX_PF_FLAGS_NBITS);
> +	u32 tsync_rx_mtrl = PTP_EV_PORT << 16;
> +	bool is_l2 = false;
> +	u32 regval;
> +
> +	memcpy(flags, wx->flags, sizeof(wx->flags));
> +
> +	switch (config->tx_type) {
> +	case HWTSTAMP_TX_OFF:
> +		tsync_tx_ctl = 0;
> +		break;
> +	case HWTSTAMP_TX_ON:
> +		break;
> +	default:
> +		return -ERANGE;
> +	}
> +
> +	switch (config->rx_filter) {
> +	case HWTSTAMP_FILTER_NONE:
> +		tsync_rx_ctl = 0;
> +		tsync_rx_mtrl = 0;
> +		clear_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags);
> +		clear_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags);
> +		break;
> +	case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
> +		tsync_rx_ctl |= WX_PSR_1588_CTL_TYPE_L4_V1;
> +		tsync_rx_mtrl |= WX_PSR_1588_MSG_V1_SYNC;
> +		set_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags);
> +		set_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags);
> +		break;
> +	case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
> +		tsync_rx_ctl |= WX_PSR_1588_CTL_TYPE_L4_V1;
> +		tsync_rx_mtrl |= WX_PSR_1588_MSG_V1_DELAY_REQ;
> +		set_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags);
> +		set_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags);
> +		break;
> +	case HWTSTAMP_FILTER_PTP_V2_EVENT:
> +	case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
> +	case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
> +	case HWTSTAMP_FILTER_PTP_V2_SYNC:
> +	case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
> +	case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
> +	case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
> +	case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
> +	case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
> +		tsync_rx_ctl |= WX_PSR_1588_CTL_TYPE_EVENT_V2;
> +		is_l2 = true;
> +		config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
> +		set_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags);
> +		set_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags);
> +		break;
> +	default:
> +		/* register RXMTRL must be set in order to do V1 packets,
> +		 * therefore it is not possible to time stamp both V1 Sync and
> +		 * Delay_Req messages unless hardware supports timestamping all
> +		 * packets => return error
> +		 */
> +		clear_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, wx->flags);
> +		clear_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, wx->flags);
> +		config->rx_filter = HWTSTAMP_FILTER_NONE;
> +		return -ERANGE;
> +	}
> +
> +	/* define ethertype filter for timestamping L2 packets */
> +	if (is_l2)
> +		wr32(wx, WX_PSR_ETYPE_SWC(WX_PSR_ETYPE_SWC_FILTER_1588),
> +		     (WX_PSR_ETYPE_SWC_FILTER_EN | /* enable filter */
> +		      WX_PSR_ETYPE_SWC_1588 | /* enable timestamping */
> +		      ETH_P_1588)); /* 1588 eth protocol type */
> +	else
> +		wr32(wx, WX_PSR_ETYPE_SWC(WX_PSR_ETYPE_SWC_FILTER_1588), 0);
> +
> +	/* enable/disable TX */
> +	regval = rd32ptp(wx, WX_TSC_1588_CTL);
> +	regval &= ~WX_TSC_1588_CTL_ENABLED;
> +	regval |= tsync_tx_ctl;
> +	wr32ptp(wx, WX_TSC_1588_CTL, regval);
> +
> +	/* enable/disable RX */
> +	regval = rd32(wx, WX_PSR_1588_CTL);
> +	regval &= ~(WX_PSR_1588_CTL_ENABLED | WX_PSR_1588_CTL_TYPE_MASK);
> +	regval |= tsync_rx_ctl;
> +	wr32(wx, WX_PSR_1588_CTL, regval);
> +
> +	/* define which PTP packets are time stamped */
> +	wr32(wx, WX_PSR_1588_MSG, tsync_rx_mtrl);
> +
> +	WX_WRITE_FLUSH(wx);
> +
> +	/* configure adapter flags only when HW is actually configured */
> +	memcpy(wx->flags, flags, sizeof(wx->flags));
> +
> +	/* clear TX/RX timestamp state, just to be sure */
> +	wx_ptp_clear_tx_timestamp(wx);
> +	rd32(wx, WX_PSR_1588_STMPH);
> +
> +	return 0;
> +}
> +
> +/**
> + * wx_ptp_read - read raw cycle counter (to be used by time counter)
> + * @hw_cc: the cyclecounter structure
> + *
> + * this function reads the cyclecounter registers and is called by the
> + * cyclecounter structure used to construct a ns counter from the
> + * arbitrary fixed point registers
> + */
> +static u64 wx_ptp_read(const struct cyclecounter *hw_cc)
> +{
> +	struct wx *wx = container_of(hw_cc, struct wx, hw_cc);
> +	u64 stamp = 0;
> +
> +	stamp |= (u64)rd32ptp(wx, WX_TSC_1588_SYSTIML);
> +	stamp |= (u64)rd32ptp(wx, WX_TSC_1588_SYSTIMH) << 32;
> +
> +	return stamp;
> +}
> +
> +static void wx_ptp_link_speed_adjust(struct wx *wx, u32 *shift, u32 *incval)
> +{
> +	if (wx->mac.type == wx_mac_em) {
> +		*shift = WX_INCVAL_SHIFT_EM;
> +		*incval = WX_INCVAL_EM;
> +		return;
> +	}
> +
> +	switch (wx->speed) {
> +	case SPEED_10:
> +		*shift = WX_INCVAL_SHIFT_10;
> +		*incval = WX_INCVAL_10;
> +		break;
> +	case SPEED_100:
> +		*shift = WX_INCVAL_SHIFT_100;
> +		*incval = WX_INCVAL_100;
> +		break;
> +	case SPEED_1000:
> +		*shift = WX_INCVAL_SHIFT_1GB;
> +		*incval = WX_INCVAL_1GB;
> +		break;
> +	case SPEED_10000:
> +	default:
> +		*shift = WX_INCVAL_SHIFT_10GB;
> +		*incval = WX_INCVAL_10GB;
> +		break;
> +	}
> +}
> +
> +/**
> + * wx_ptp_reset_cyclecounter - create the cycle counter from hw
> + * @wx: pointer to the wx structure
> + *
> + * This function should be called to set the proper values for the TSC_1588_INC
> + * register and tell the cyclecounter structure what the tick rate of SYSTIME
> + * is. It does not directly modify SYSTIME registers or the timecounter
> + * structure. It should be called whenever a new TSC_1588_INC value is
> + * necessary, such as during initialization or when the link speed changes.
> + */
> +void wx_ptp_reset_cyclecounter(struct wx *wx)
> +{
> +	u32 incval = 0, mask = 0;
> +	struct cyclecounter cc;
> +	unsigned long flags;
> +
> +	/* For some of the boards below this mask is technically incorrect.
> +	 * The timestamp mask overflows at approximately 61bits. However the
> +	 * particular hardware does not overflow on an even bitmask value.
> +	 * Instead, it overflows due to conversion of upper 32bits billions of
> +	 * cycles. Timecounters are not really intended for this purpose so
> +	 * they do not properly function if the overflow point isn't 2^N-1.
> +	 * However, the actual SYSTIME values in question take ~138 years to
> +	 * overflow. In practice this means they won't actually overflow. A
> +	 * proper fix to this problem would require modification of the
> +	 * timecounter delta calculations.
> +	 */
> +	cc.mask = CLOCKSOURCE_MASK(64);
> +	cc.mult = 1;
> +	cc.shift = 0;
> +
> +	cc.read = wx_ptp_read;
> +	wx_ptp_link_speed_adjust(wx, &cc.shift, &incval);
> +
> +	/* update the base incval used to calculate frequency adjustment */
> +	WRITE_ONCE(wx->base_incval, incval);
> +
> +	mask = (wx->mac.type == wx_mac_em) ? 0x7FFFFFF : 0xFFFFFF;
> +	incval &= mask;
> +	if (wx->mac.type != wx_mac_em)
> +		incval |= 2 << 24;
> +	wr32ptp(wx, WX_TSC_1588_INC, incval);
> +
> +	smp_mb(); /* Force the above update. */
> +
> +	/* need lock to prevent incorrect read while modifying cyclecounter */
> +	spin_lock_irqsave(&wx->tmreg_lock, flags);
> +	memcpy(&wx->hw_cc, &cc, sizeof(wx->hw_cc));
> +	spin_unlock_irqrestore(&wx->tmreg_lock, flags);
> +}
> +EXPORT_SYMBOL(wx_ptp_reset_cyclecounter);
> +
> +/**
> + * wx_ptp_reset
> + * @wx: the private board structure
> + *
> + * When the MAC resets, all of the hardware configuration for timesync is
> + * reset. This function should be called to re-enable the device for PTP,
> + * using the last known settings. However, we do lose the current clock time,
> + * so we fallback to resetting it based on the kernel's realtime clock.
> + *
> + * This function will maintain the hwtstamp_config settings, and it retriggers
> + * the SDP output if it's enabled.
> + */
> +void wx_ptp_reset(struct wx *wx)
> +{
> +	unsigned long flags;
> +
> +	/* reset the hardware timestamping mode */
> +	wx_ptp_set_timestamp_mode(wx, &wx->tstamp_config);
> +	wx_ptp_reset_cyclecounter(wx);
> +
> +	wr32ptp(wx, WX_TSC_1588_SYSTIML, 0);
> +	wr32ptp(wx, WX_TSC_1588_SYSTIMH, 0);
> +	WX_WRITE_FLUSH(wx);
> +
> +	spin_lock_irqsave(&wx->tmreg_lock, flags);
> +	timecounter_init(&wx->hw_tc, &wx->hw_cc,
> +			 ktime_to_ns(ktime_get_real()));
> +	spin_unlock_irqrestore(&wx->tmreg_lock, flags);
> +}
> +EXPORT_SYMBOL(wx_ptp_reset);
> +
> +/**
> + * wx_ptp_init
> + * @wx: the private board structure
> + *
> + * This function performs the required steps for enabling ptp
> + * support. If ptp support has already been loaded it simply calls the
> + * cyclecounter init routine and exits.
> + */
> +void wx_ptp_init(struct wx *wx)
> +{
> +	/* Initialize the spin lock first, since the user might call the clock
> +	 * functions any time after we've initialized the ptp clock device.
> +	 */
> +	spin_lock_init(&wx->tmreg_lock);
> +
> +	/* obtain a ptp clock device, or re-use an existing device */
> +	if (wx_ptp_create_clock(wx))
> +		return;
> +
> +	/* we have a clock, so we can initialize work for timestamps now */
> +	INIT_WORK(&wx->ptp_tx_work, wx_ptp_tx_hwtstamp_work);
> +
> +	wx->tx_hwtstamp_timeouts = 0;
> +	wx->tx_hwtstamp_skipped = 0;
> +	wx->rx_hwtstamp_cleared = 0;
> +	/* reset the ptp related hardware bits */
> +	wx_ptp_reset(wx);
> +
> +	/* enter the WX_STATE_PTP_RUNNING state */
> +	set_bit(WX_STATE_PTP_RUNNING, wx->state);
> +}
> +EXPORT_SYMBOL(wx_ptp_init);
> +
> +/**
> + * wx_ptp_suspend - stop ptp work items
> + * @wx: pointer to wx struct
> + *
> + * This function suspends ptp activity, and prevents more work from being
> + * generated, but does not destroy the clock device.
> + */
> +void wx_ptp_suspend(struct wx *wx)
> +{
> +	/* leave the WX_STATE_PTP_RUNNING STATE */
> +	if (!test_and_clear_bit(WX_STATE_PTP_RUNNING, wx->state))
> +		return;
> +
> +	cancel_work_sync(&wx->ptp_tx_work);
> +	wx_ptp_clear_tx_timestamp(wx);
> +}
> +EXPORT_SYMBOL(wx_ptp_suspend);
> +
> +/**
> + * wx_ptp_stop - destroy the ptp_clock device
> + * @wx: pointer to wx struct
> + *
> + * Completely destroy the ptp_clock device, and disable all PTP related
> + * features. Intended to be run when the device is being closed.
> + */
> +void wx_ptp_stop(struct wx *wx)
> +{
> +	/* first, suspend ptp activity */
> +	wx_ptp_suspend(wx);
> +
> +	/* now destroy the ptp clock device */
> +	if (wx->ptp_clock) {
> +		ptp_clock_unregister(wx->ptp_clock);
> +		wx->ptp_clock = NULL;
> +		dev_info(&wx->pdev->dev, "removed PHC on %s\n", wx->netdev->name);
> +	}
> +}
> +EXPORT_SYMBOL(wx_ptp_stop);
> +
> +/**
> + * wx_ptp_rx_hwtstamp - utility function which checks for RX time stamp
> + * @wx: pointer to wx struct
> + * @skb: particular skb to send timestamp with
> + *
> + * if the timestamp is valid, we convert it into the timecounter ns
> + * value, then store that result into the shhwtstamps structure which
> + * is passed up the network stack
> + */
> +void wx_ptp_rx_hwtstamp(struct wx *wx, struct sk_buff *skb)
> +{
> +	u64 regval = 0;
> +	u32 tsyncrxctl;
> +
> +	/* Read the tsyncrxctl register afterwards in order to prevent taking an
> +	 * I/O hit on every packet.
> +	 */
> +	tsyncrxctl = rd32(wx, WX_PSR_1588_CTL);
> +	if (!(tsyncrxctl & WX_PSR_1588_CTL_VALID))
> +		return;
> +
> +	regval |= (u64)rd32(wx, WX_PSR_1588_STMPL);
> +	regval |= (u64)rd32(wx, WX_PSR_1588_STMPH) << 32;
> +
> +	wx_ptp_convert_to_hwtstamp(wx, skb_hwtstamps(skb), regval);
> +}
> +
> +int wx_hwtstamp_get(struct net_device *dev,
> +		    struct kernel_hwtstamp_config *cfg)
> +{
> +	struct wx *wx = netdev_priv(dev);
> +
> +	if (!netif_running(dev))
> +		return -EINVAL;
> +
> +	*cfg = wx->tstamp_config;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(wx_hwtstamp_get);
> +
> +int wx_hwtstamp_set(struct net_device *dev,
> +		    struct kernel_hwtstamp_config *cfg,
> +		    struct netlink_ext_ack *extack)
> +{
> +	struct wx *wx = netdev_priv(dev);
> +	int err;
> +
> +	if (!netif_running(dev))
> +		return -EINVAL;
> +
> +	err = wx_ptp_set_timestamp_mode(wx, cfg);
> +	if (err)
> +		return err;
> +
> +	/* save these settings for future reference */
> +	memcpy(&wx->tstamp_config, cfg, sizeof(wx->tstamp_config));
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(wx_hwtstamp_set);
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.h b/drivers/net/ethernet/wangxun/libwx/wx_ptp.h
> new file mode 100644
> index 000000000000..8742d2797363
> --- /dev/null
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright (c) 2019 - 2025 Beijing WangXun Technology Co., Ltd. */
> +
> +#ifndef _WX_PTP_H_
> +#define _WX_PTP_H_
> +
> +void wx_ptp_reset_cyclecounter(struct wx *wx);
> +void wx_ptp_reset(struct wx *wx);
> +void wx_ptp_init(struct wx *wx);
> +void wx_ptp_suspend(struct wx *wx);
> +void wx_ptp_stop(struct wx *wx);
> +void wx_ptp_rx_hwtstamp(struct wx *wx, struct sk_buff *skb);
> +int wx_hwtstamp_get(struct net_device *dev,
> +		    struct kernel_hwtstamp_config *cfg);
> +int wx_hwtstamp_set(struct net_device *dev,
> +		    struct kernel_hwtstamp_config *cfg,
> +		    struct netlink_ext_ack *extack);
> +
> +#endif /* _WX_PTP_H_ */
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h
> index b54bffda027b..31b11dba6bf5 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
> @@ -4,6 +4,8 @@
>   #ifndef _WX_TYPE_H_
>   #define _WX_TYPE_H_
>   
> +#include <linux/ptp_clock_kernel.h>
> +#include <linux/timecounter.h>
>   #include <linux/bitfield.h>
>   #include <linux/netdevice.h>
>   #include <linux/if_vlan.h>
> @@ -180,6 +182,23 @@
>   #define WX_PSR_VLAN_CTL              0x15088
>   #define WX_PSR_VLAN_CTL_CFIEN        BIT(29)  /* bit 29 */
>   #define WX_PSR_VLAN_CTL_VFE          BIT(30)  /* bit 30 */
> +/* EType Queue Filter */
> +#define WX_PSR_ETYPE_SWC(_i)         (0x15128 + ((_i) * 4))
> +#define WX_PSR_ETYPE_SWC_FILTER_1588 3
> +#define WX_PSR_ETYPE_SWC_FILTER_EN   BIT(31)
> +#define WX_PSR_ETYPE_SWC_1588        BIT(30)
> +/* 1588 */
> +#define WX_PSR_1588_MSG                 0x15120
> +#define WX_PSR_1588_MSG_V1_SYNC         FIELD_PREP(GENMASK(7, 0), 0)
> +#define WX_PSR_1588_MSG_V1_DELAY_REQ    FIELD_PREP(GENMASK(7, 0), 1)
> +#define WX_PSR_1588_STMPL               0x151E8
> +#define WX_PSR_1588_STMPH               0x151A4
> +#define WX_PSR_1588_CTL                 0x15188
> +#define WX_PSR_1588_CTL_ENABLED         BIT(4)
> +#define WX_PSR_1588_CTL_TYPE_MASK       GENMASK(3, 1)
> +#define WX_PSR_1588_CTL_TYPE_L4_V1      FIELD_PREP(GENMASK(3, 1), 1)
> +#define WX_PSR_1588_CTL_TYPE_EVENT_V2   FIELD_PREP(GENMASK(3, 1), 5)
> +#define WX_PSR_1588_CTL_VALID           BIT(0)
>   /* mcasst/ucast overflow tbl */
>   #define WX_PSR_MC_TBL(_i)            (0x15200  + ((_i) * 4))
>   #define WX_PSR_UC_TBL(_i)            (0x15400 + ((_i) * 4))
> @@ -253,6 +272,15 @@
>   #define WX_TSC_ST_SECTX_RDY          BIT(0)
>   #define WX_TSC_BUF_AE                0x1D00C
>   #define WX_TSC_BUF_AE_THR            GENMASK(9, 0)
> +/* 1588 */
> +#define WX_TSC_1588_CTL              0x11F00
> +#define WX_TSC_1588_CTL_ENABLED      BIT(4)
> +#define WX_TSC_1588_CTL_VALID        BIT(0)
> +#define WX_TSC_1588_STMPL            0x11F04
> +#define WX_TSC_1588_STMPH            0x11F08
> +#define WX_TSC_1588_SYSTIML          0x11F0C
> +#define WX_TSC_1588_SYSTIMH          0x11F10
> +#define WX_TSC_1588_INC              0x11F14
>   
>   /************************************** MNG ********************************/
>   #define WX_MNG_SWFW_SYNC             0x1E008
> @@ -460,6 +488,7 @@ enum WX_MSCA_CMD_value {
>   #define WX_RXD_STAT_L4CS             BIT(7) /* L4 xsum calculated */
>   #define WX_RXD_STAT_IPCS             BIT(8) /* IP xsum calculated */
>   #define WX_RXD_STAT_OUTERIPCS        BIT(10) /* Cloud IP xsum calculated*/
> +#define WX_RXD_STAT_TS               BIT(14) /* IEEE1588 Time Stamp */
>   
>   #define WX_RXD_ERR_OUTERIPER         BIT(26) /* CRC IP Header error */
>   #define WX_RXD_ERR_RXE               BIT(29) /* Any MAC Error */
> @@ -863,6 +892,7 @@ struct wx_tx_context_desc {
>    */
>   struct wx_tx_buffer {
>   	union wx_tx_desc *next_to_watch;
> +	unsigned long time_stamp;
>   	struct sk_buff *skb;
>   	unsigned int bytecount;
>   	unsigned short gso_segs;
> @@ -924,6 +954,7 @@ struct wx_ring {
>   	unsigned int size;              /* length in bytes */
>   
>   	u16 count;                      /* amount of descriptors */
> +	unsigned long last_rx_timestamp;
>   
>   	u8 queue_index; /* needed for multiqueue queue management */
>   	u8 reg_idx;                     /* holds the special value that gets
> @@ -1026,6 +1057,8 @@ struct wx_hw_stats {
>   
>   enum wx_state {
>   	WX_STATE_RESETTING,
> +	WX_STATE_PTP_RUNNING,
> +	WX_STATE_PTP_TX_IN_PROGRESS,
>   	WX_STATE_NBITS,		/* must be last */
>   };
>   
> @@ -1033,6 +1066,8 @@ enum wx_pf_flags {
>   	WX_FLAG_FDIR_CAPABLE,
>   	WX_FLAG_FDIR_HASH,
>   	WX_FLAG_FDIR_PERFECT,
> +	WX_FLAG_RX_HWTSTAMP_ENABLED,
> +	WX_FLAG_RX_HWTSTAMP_IN_REGISTER,
>   	WX_PF_FLAGS_NBITS               /* must be last */
>   };
>   
> @@ -1133,6 +1168,20 @@ struct wx {
>   	void (*atr)(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype);
>   	void (*configure_fdir)(struct wx *wx);
>   	void (*do_reset)(struct net_device *netdev);
> +
> +	u32 base_incval;
> +	u32 tx_hwtstamp_timeouts;
> +	u32 tx_hwtstamp_skipped;
> +	u32 rx_hwtstamp_cleared;
> +	unsigned long ptp_tx_start;
> +	spinlock_t tmreg_lock; /* spinlock for ptp */
> +	struct cyclecounter hw_cc;
> +	struct timecounter hw_tc;
> +	struct ptp_clock *ptp_clock;
> +	struct ptp_clock_info ptp_caps;
> +	struct kernel_hwtstamp_config tstamp_config;
> +	struct work_struct ptp_tx_work;
> +	struct sk_buff *ptp_tx_skb;
>   };
>   
>   #define WX_INTR_ALL (~0ULL)
> @@ -1177,6 +1226,24 @@ rd64(struct wx *wx, u32 reg)
>   	return (lsb | msb << 32);
>   }
>   
> +static inline u32
> +rd32ptp(struct wx *wx, u32 reg)
> +{
> +	if (wx->mac.type == wx_mac_em)
> +		return rd32(wx, reg);
> +
> +	return rd32(wx, reg + 0xB500);
> +}
> +
> +static inline void
> +wr32ptp(struct wx *wx, u32 reg, u32 value)
> +{
> +	if (wx->mac.type == wx_mac_em)
> +		return wr32(wx, reg, value);
> +
> +	return wr32(wx, reg + 0xB500, value);
> +}
> +
>   /* On some domestic CPU platforms, sometimes IO is not synchronized with
>    * flushing memory, here use readl() to flush PCI read and write.
>    */
> diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
> index 53aeae2f884b..c60a96cc3508 100644
> --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
> +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
> @@ -14,6 +14,7 @@
>   #include "../libwx/wx_type.h"
>   #include "../libwx/wx_hw.h"
>   #include "../libwx/wx_lib.h"
> +#include "../libwx/wx_ptp.h"
>   #include "ngbe_type.h"
>   #include "ngbe_mdio.h"
>   #include "ngbe_hw.h"
> @@ -317,6 +318,8 @@ void ngbe_down(struct wx *wx)
>   {
>   	phylink_stop(wx->phylink);
>   	ngbe_disable_device(wx);
> +	if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
> +		wx_ptp_reset(wx);
>   	wx_clean_all_tx_rings(wx);
>   	wx_clean_all_rx_rings(wx);
>   }
> @@ -379,6 +382,8 @@ static int ngbe_open(struct net_device *netdev)
>   	if (err)
>   		goto err_dis_phy;
>   
> +	wx_ptp_init(wx);
> +
>   	ngbe_up(wx);
>   
>   	return 0;
> @@ -407,6 +412,7 @@ static int ngbe_close(struct net_device *netdev)
>   {
>   	struct wx *wx = netdev_priv(netdev);
>   
> +	wx_ptp_stop(wx);
>   	ngbe_down(wx);
>   	wx_free_irq(wx);
>   	wx_free_isb_resources(wx);
> @@ -507,6 +513,8 @@ static const struct net_device_ops ngbe_netdev_ops = {
>   	.ndo_get_stats64        = wx_get_stats64,
>   	.ndo_vlan_rx_add_vid    = wx_vlan_rx_add_vid,
>   	.ndo_vlan_rx_kill_vid   = wx_vlan_rx_kill_vid,
> +	.ndo_hwtstamp_set       = wx_hwtstamp_set,
> +	.ndo_hwtstamp_get       = wx_hwtstamp_get,
>   };
>   
>   /**
> diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
> index a5e9b779c44d..c7944e62838a 100644
> --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
> +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
> @@ -7,6 +7,7 @@
>   #include <linux/phy.h>
>   
>   #include "../libwx/wx_type.h"
> +#include "../libwx/wx_ptp.h"
>   #include "../libwx/wx_hw.h"
>   #include "ngbe_type.h"
>   #include "ngbe_mdio.h"
> @@ -64,6 +65,11 @@ static void ngbe_mac_config(struct phylink_config *config, unsigned int mode,
>   static void ngbe_mac_link_down(struct phylink_config *config,
>   			       unsigned int mode, phy_interface_t interface)
>   {
> +	struct wx *wx = phylink_to_wx(config);
> +
> +	wx->speed = SPEED_UNKNOWN;
> +	if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
> +		wx_ptp_reset_cyclecounter(wx);
>   }
>   
>   static void ngbe_mac_link_up(struct phylink_config *config,
> @@ -103,6 +109,10 @@ static void ngbe_mac_link_up(struct phylink_config *config,
>   	wr32(wx, WX_MAC_PKT_FLT, WX_MAC_PKT_FLT_PR);
>   	reg = rd32(wx, WX_MAC_WDG_TIMEOUT);
>   	wr32(wx, WX_MAC_WDG_TIMEOUT, reg);
> +
> +	wx->speed = speed;
> +	if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
> +		wx_ptp_reset_cyclecounter(wx);
>   }
>   
>   static const struct phylink_mac_ops ngbe_mac_ops = {
> diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
> index f77450268036..734450af9a43 100644
> --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
> +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
> @@ -13,6 +13,7 @@
>   
>   #include "../libwx/wx_type.h"
>   #include "../libwx/wx_lib.h"
> +#include "../libwx/wx_ptp.h"
>   #include "../libwx/wx_hw.h"
>   #include "txgbe_type.h"
>   #include "txgbe_hw.h"
> @@ -116,6 +117,9 @@ static void txgbe_reset(struct wx *wx)
>   	memcpy(old_addr, &wx->mac_table[0].addr, netdev->addr_len);
>   	wx_flush_sw_mac_table(wx);
>   	wx_mac_set_default_filter(wx, old_addr);
> +
> +	if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
> +		wx_ptp_reset(wx);
>   }
>   
>   static void txgbe_disable_device(struct wx *wx)
> @@ -176,6 +180,7 @@ void txgbe_down(struct wx *wx)
>   void txgbe_up(struct wx *wx)
>   {
>   	wx_configure(wx);
> +	wx_ptp_init(wx);
>   	txgbe_up_complete(wx);
>   }
>   
> @@ -321,6 +326,8 @@ static int txgbe_open(struct net_device *netdev)
>   	if (err)
>   		goto err_free_irq;
>   
> +	wx_ptp_init(wx);
> +
>   	txgbe_up_complete(wx);
>   
>   	return 0;
> @@ -344,6 +351,7 @@ static int txgbe_open(struct net_device *netdev)
>    */
>   static void txgbe_close_suspend(struct wx *wx)
>   {
> +	wx_ptp_suspend(wx);
>   	txgbe_disable_device(wx);
>   	wx_free_resources(wx);
>   }
> @@ -363,6 +371,7 @@ static int txgbe_close(struct net_device *netdev)
>   {
>   	struct wx *wx = netdev_priv(netdev);
>   
> +	wx_ptp_stop(wx);
>   	txgbe_down(wx);
>   	wx_free_irq(wx);
>   	wx_free_resources(wx);
> @@ -479,6 +488,8 @@ static const struct net_device_ops txgbe_netdev_ops = {
>   	.ndo_get_stats64        = wx_get_stats64,
>   	.ndo_vlan_rx_add_vid    = wx_vlan_rx_add_vid,
>   	.ndo_vlan_rx_kill_vid   = wx_vlan_rx_kill_vid,
> +	.ndo_hwtstamp_set       = wx_hwtstamp_set,
> +	.ndo_hwtstamp_get       = wx_hwtstamp_get,
>   };
>   
>   /**
> diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
> index 1ae68f94dd49..60e5f3288ad8 100644
> --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
> +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
> @@ -15,6 +15,7 @@
>   
>   #include "../libwx/wx_type.h"
>   #include "../libwx/wx_lib.h"
> +#include "../libwx/wx_ptp.h"
>   #include "../libwx/wx_hw.h"
>   #include "txgbe_type.h"
>   #include "txgbe_phy.h"
> @@ -179,6 +180,10 @@ static void txgbe_mac_link_down(struct phylink_config *config,
>   	struct wx *wx = phylink_to_wx(config);
>   
>   	wr32m(wx, WX_MAC_TX_CFG, WX_MAC_TX_CFG_TE, 0);
> +
> +	wx->speed = SPEED_UNKNOWN;
> +	if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
> +		wx_ptp_reset_cyclecounter(wx);
>   }
>   
>   static void txgbe_mac_link_up(struct phylink_config *config,
> @@ -215,6 +220,10 @@ static void txgbe_mac_link_up(struct phylink_config *config,
>   	wr32(wx, WX_MAC_PKT_FLT, WX_MAC_PKT_FLT_PR);
>   	wdg = rd32(wx, WX_MAC_WDG_TIMEOUT);
>   	wr32(wx, WX_MAC_WDG_TIMEOUT, wdg);
> +
> +	wx->speed = speed;
> +	if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
> +		wx_ptp_reset_cyclecounter(wx);
>   }
>   
>   static int txgbe_mac_prepare(struct phylink_config *config, unsigned int mode,