diff mbox series

[net-next,v4,1/4] net: wangxun: Add support for PTP clock

Message ID 20250114084425.2203428-2-jiawenwu@trustnetic.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series Support PTP clock for Wangxun NICs | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net-next, async
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1 this patch: 1
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers warning 1 maintainers not CCed: ecree.xilinx@gmail.com
netdev/build_clang fail Errors and warnings before: 46 this patch: 47
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 5 this patch: 5
netdev/checkpatch warning WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: line length of 81 exceeds 80 columns WARNING: line length of 82 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc fail Errors and warnings before: 28 this patch: 51
netdev/source_inline success Was 0 now: 0

Commit Message

Jiawen Wu Jan. 14, 2025, 8:44 a.m. UTC
Implement support for PTP clock on Wangxun NICs.

Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
---
 drivers/net/ethernet/wangxun/libwx/Makefile   |   2 +-
 .../net/ethernet/wangxun/libwx/wx_ethtool.c   |   3 +
 drivers/net/ethernet/wangxun/libwx/wx_lib.c   |  52 +-
 drivers/net/ethernet/wangxun/libwx/wx_ptp.c   | 706 ++++++++++++++++++
 drivers/net/ethernet/wangxun/libwx/wx_ptp.h   |  19 +
 drivers/net/ethernet/wangxun/libwx/wx_type.h  |  69 ++
 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

Comments

Vadim Fedorenko Jan. 14, 2025, 9:27 a.m. UTC | #1
On 14/01/2025 08:44, Jiawen Wu wrote:
> Implement support for PTP clock on Wangxun NICs.
> 
> Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
> ---
>   drivers/net/ethernet/wangxun/libwx/Makefile   |   2 +-
>   .../net/ethernet/wangxun/libwx/wx_ethtool.c   |   3 +
>   drivers/net/ethernet/wangxun/libwx/wx_lib.c   |  52 +-
>   drivers/net/ethernet/wangxun/libwx/wx_ptp.c   | 706 ++++++++++++++++++
>   drivers/net/ethernet/wangxun/libwx/wx_ptp.h   |  19 +
>   drivers/net/ethernet/wangxun/libwx/wx_type.h  |  69 ++
>   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..62c837cd3fb7 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);
> @@ -705,6 +715,7 @@ static bool wx_clean_tx_irq(struct wx_q_vector *q_vector,
>   {
>   	unsigned int budget = q_vector->wx->tx_work_limit;
>   	unsigned int total_bytes = 0, total_packets = 0;
> +	struct wx *wx = netdev_priv(tx_ring->netdev);
>   	unsigned int i = tx_ring->next_to_clean;
>   	struct wx_tx_buffer *tx_buffer;
>   	union wx_tx_desc *tx_desc;
> @@ -737,6 +748,11 @@ static bool wx_clean_tx_irq(struct wx_q_vector *q_vector,
>   		total_bytes += tx_buffer->bytecount;
>   		total_packets += tx_buffer->gso_segs;
>   
> +		/* schedule check for Tx timestamp */
> +		if (unlikely(test_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state)) &&
> +		    skb_shinfo(tx_buffer->skb)->tx_flags & SKBTX_IN_PROGRESS)
> +			schedule_work(&wx->ptp_tx_work);
> +
>   		/* free the skb */
>   		napi_consume_skb(tx_buffer->skb, napi_budget);
>   
> @@ -932,9 +948,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 +1029,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 +1056,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 +1080,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 +1506,20 @@ 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;
> +			wx->ptp_tx_skb = skb_get(skb);
> +			wx->ptp_tx_start = jiffies;
> +		} else {
> +			wx->tx_hwtstamp_skipped++;
> +		}
> +	}
> +
>   	/* record initial flags and protocol */
>   	first->tx_flags = tx_flags;
>   	first->protocol = vlan_get_protocol(skb);
> @@ -1501,12 +1535,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;
> +		wx->tx_hwtstamp_errors++;
> +		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..97d39e8f02da
> --- /dev/null
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
> @@ -0,0 +1,706 @@
> +// 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
> +
> +/**
> + * 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;
> +	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;
> +
> +	write_seqlock_irqsave(&wx->hw_tc_lock, flags);
> +	timecounter_adjtime(&wx->hw_tc, delta);
> +	write_sequnlock_irqrestore(&wx->hw_tc_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;
> +
> +	write_seqlock_irqsave(&wx->hw_tc_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);
> +
> +	write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);

According to your previous comments, there is no need to lock anything
to read PTP registers. the only thing needs to be serialized is the
access to timecounter. Instead of using write lock, it logically better
to use reading template:

         do {
                 seq = read_seqbegin(&wx->hw_tc_lock);
                 ns = timecounter_cyc2time(&wx->hw_tc, stamp);
         } while (read_seqretry(&wx->hw_tc_lock, seq));

This will not spin on lock under the hood of seqlock and will give you
lockless access to timecounter.

> +
> +	*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 */
> +	write_seqlock_irqsave(&wx->hw_tc_lock, flags);
> +	timecounter_init(&wx->hw_tc, &wx->hw_cc, ns);
> +	write_sequnlock_irqrestore(&wx->hw_tc_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));
> +
> +	write_seqlock_irqsave(&wx->hw_tc_lock, flags);
> +	ns = timecounter_cyc2time(&wx->hw_tc, timestamp);
> +	write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);

Same comment here - use seqlock read template to access timecounter.

The best would be to move this access to the function, there are several
places of copy-pasted code..

> +
> +	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->tx_hwtstamp_pkts++;
> +}
> +
> +/**
> + * 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;
> +	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 PSR_1588_MSG 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
> +		 */
> +		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 */
> +	write_seqlock_irqsave(&wx->hw_tc_lock, flags);
> +	memcpy(&wx->hw_cc, &cc, sizeof(wx->hw_cc));
> +	write_sequnlock_irqrestore(&wx->hw_tc_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);
> +
> +	write_seqlock_irqsave(&wx->hw_tc_lock, flags);
> +	timecounter_init(&wx->hw_tc, &wx->hw_cc,
> +			 ktime_to_ns(ktime_get_real()));
> +	write_sequnlock_irqrestore(&wx->hw_tc_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 seqlock_t first, since the user might call the clock
> +	 * functions any time after we've initialized the ptp clock device.
> +	 */
> +	seqlock_init(&wx->hw_tc_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_pkts = 0;
> +	wx->tx_hwtstamp_timeouts = 0;
> +	wx->tx_hwtstamp_skipped = 0;
> +	wx->tx_hwtstamp_errors = 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..9199317f7175 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,22 @@ 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_pkts;
> +	u32 tx_hwtstamp_timeouts;
> +	u32 tx_hwtstamp_skipped;
> +	u32 tx_hwtstamp_errors;
> +	u32 rx_hwtstamp_cleared;
> +	unsigned long ptp_tx_start;
> +	seqlock_t hw_tc_lock; /* seqlock 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 +1228,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,
Vadim Fedorenko Jan. 14, 2025, 10:28 a.m. UTC | #2
On 14/01/2025 08:44, Jiawen Wu wrote:
> Implement support for PTP clock on Wangxun NICs.
> 
> Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
> ---
>   drivers/net/ethernet/wangxun/libwx/Makefile   |   2 +-
>   .../net/ethernet/wangxun/libwx/wx_ethtool.c   |   3 +
>   drivers/net/ethernet/wangxun/libwx/wx_lib.c   |  52 +-
>   drivers/net/ethernet/wangxun/libwx/wx_ptp.c   | 706 ++++++++++++++++++
>   drivers/net/ethernet/wangxun/libwx/wx_ptp.h   |  19 +
>   drivers/net/ethernet/wangxun/libwx/wx_type.h  |  69 ++
>   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..62c837cd3fb7 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);
> @@ -705,6 +715,7 @@ static bool wx_clean_tx_irq(struct wx_q_vector *q_vector,
>   {
>   	unsigned int budget = q_vector->wx->tx_work_limit;
>   	unsigned int total_bytes = 0, total_packets = 0;
> +	struct wx *wx = netdev_priv(tx_ring->netdev);
>   	unsigned int i = tx_ring->next_to_clean;
>   	struct wx_tx_buffer *tx_buffer;
>   	union wx_tx_desc *tx_desc;
> @@ -737,6 +748,11 @@ static bool wx_clean_tx_irq(struct wx_q_vector *q_vector,
>   		total_bytes += tx_buffer->bytecount;
>   		total_packets += tx_buffer->gso_segs;
>   
> +		/* schedule check for Tx timestamp */
> +		if (unlikely(test_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state)) &&
> +		    skb_shinfo(tx_buffer->skb)->tx_flags & SKBTX_IN_PROGRESS)
> +			schedule_work(&wx->ptp_tx_work);
> +
>   		/* free the skb */
>   		napi_consume_skb(tx_buffer->skb, napi_budget);
>   
> @@ -932,9 +948,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 +1029,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 +1056,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 +1080,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 +1506,20 @@ 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;
> +			wx->ptp_tx_skb = skb_get(skb);
> +			wx->ptp_tx_start = jiffies;
> +		} else {
> +			wx->tx_hwtstamp_skipped++;
> +		}
> +	}
> +
>   	/* record initial flags and protocol */
>   	first->tx_flags = tx_flags;
>   	first->protocol = vlan_get_protocol(skb);
> @@ -1501,12 +1535,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;
> +		wx->tx_hwtstamp_errors++;
> +		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..97d39e8f02da
> --- /dev/null
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
> @@ -0,0 +1,706 @@
> +// 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
> +
> +/**
> + * 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;
> +	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;
> +
> +	write_seqlock_irqsave(&wx->hw_tc_lock, flags);
> +	timecounter_adjtime(&wx->hw_tc, delta);
> +	write_sequnlock_irqrestore(&wx->hw_tc_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;
> +
> +	write_seqlock_irqsave(&wx->hw_tc_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);

Another thing here - you don't check for the overflow. This is unusual
pattern. What will happen if WX_TSC_1588_SYSTIMH value will change
between these 2 calls?

Usual pattern for this is to read WX_TSC_1588_SYSTIMH, then read
WX_TSC_1588_SYSTIML, and then read WX_TSC_1588_SYSTIMH again. If 2 reads
of WX_TSC_1588_SYSTIMH are the same, then the value is correct,
otherwise re-read WX_TSC_1588_SYSTIML again. You can find examples of
this template in the other drivers, like mlx5.

> +
> +	write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
> +
> +	*ts = ns_to_timespec64(ns);
> +
> +	return 0;
> +}

[...]

> +/**
> + * 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));

no need to zero out it, it will be completely overwritten later

> +
> +	write_seqlock_irqsave(&wx->hw_tc_lock, flags);
> +	ns = timecounter_cyc2time(&wx->hw_tc, timestamp);
> +	write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
> +
> +	hwtstamp->hwtstamp = ns_to_ktime(ns);
> +}
> +

[...]

> +/**
> + * 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;

The same reading sequence should be here...

> +
> +	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;
> +	}
> +}
> +

[...]
diff mbox series

Patch

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..62c837cd3fb7 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);
@@ -705,6 +715,7 @@  static bool wx_clean_tx_irq(struct wx_q_vector *q_vector,
 {
 	unsigned int budget = q_vector->wx->tx_work_limit;
 	unsigned int total_bytes = 0, total_packets = 0;
+	struct wx *wx = netdev_priv(tx_ring->netdev);
 	unsigned int i = tx_ring->next_to_clean;
 	struct wx_tx_buffer *tx_buffer;
 	union wx_tx_desc *tx_desc;
@@ -737,6 +748,11 @@  static bool wx_clean_tx_irq(struct wx_q_vector *q_vector,
 		total_bytes += tx_buffer->bytecount;
 		total_packets += tx_buffer->gso_segs;
 
+		/* schedule check for Tx timestamp */
+		if (unlikely(test_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state)) &&
+		    skb_shinfo(tx_buffer->skb)->tx_flags & SKBTX_IN_PROGRESS)
+			schedule_work(&wx->ptp_tx_work);
+
 		/* free the skb */
 		napi_consume_skb(tx_buffer->skb, napi_budget);
 
@@ -932,9 +948,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 +1029,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 +1056,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 +1080,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 +1506,20 @@  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;
+			wx->ptp_tx_skb = skb_get(skb);
+			wx->ptp_tx_start = jiffies;
+		} else {
+			wx->tx_hwtstamp_skipped++;
+		}
+	}
+
 	/* record initial flags and protocol */
 	first->tx_flags = tx_flags;
 	first->protocol = vlan_get_protocol(skb);
@@ -1501,12 +1535,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;
+		wx->tx_hwtstamp_errors++;
+		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..97d39e8f02da
--- /dev/null
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
@@ -0,0 +1,706 @@ 
+// 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
+
+/**
+ * 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;
+	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;
+
+	write_seqlock_irqsave(&wx->hw_tc_lock, flags);
+	timecounter_adjtime(&wx->hw_tc, delta);
+	write_sequnlock_irqrestore(&wx->hw_tc_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;
+
+	write_seqlock_irqsave(&wx->hw_tc_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);
+
+	write_sequnlock_irqrestore(&wx->hw_tc_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 */
+	write_seqlock_irqsave(&wx->hw_tc_lock, flags);
+	timecounter_init(&wx->hw_tc, &wx->hw_cc, ns);
+	write_sequnlock_irqrestore(&wx->hw_tc_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));
+
+	write_seqlock_irqsave(&wx->hw_tc_lock, flags);
+	ns = timecounter_cyc2time(&wx->hw_tc, timestamp);
+	write_sequnlock_irqrestore(&wx->hw_tc_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->tx_hwtstamp_pkts++;
+}
+
+/**
+ * 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;
+	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 PSR_1588_MSG 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
+		 */
+		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 */
+	write_seqlock_irqsave(&wx->hw_tc_lock, flags);
+	memcpy(&wx->hw_cc, &cc, sizeof(wx->hw_cc));
+	write_sequnlock_irqrestore(&wx->hw_tc_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);
+
+	write_seqlock_irqsave(&wx->hw_tc_lock, flags);
+	timecounter_init(&wx->hw_tc, &wx->hw_cc,
+			 ktime_to_ns(ktime_get_real()));
+	write_sequnlock_irqrestore(&wx->hw_tc_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 seqlock_t first, since the user might call the clock
+	 * functions any time after we've initialized the ptp clock device.
+	 */
+	seqlock_init(&wx->hw_tc_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_pkts = 0;
+	wx->tx_hwtstamp_timeouts = 0;
+	wx->tx_hwtstamp_skipped = 0;
+	wx->tx_hwtstamp_errors = 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..9199317f7175 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,22 @@  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_pkts;
+	u32 tx_hwtstamp_timeouts;
+	u32 tx_hwtstamp_skipped;
+	u32 tx_hwtstamp_errors;
+	u32 rx_hwtstamp_cleared;
+	unsigned long ptp_tx_start;
+	seqlock_t hw_tc_lock; /* seqlock 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 +1228,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,