netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH net-next v4 0/4] Support PTP clock for Wangxun NICs
@ 2025-01-14  8:44 Jiawen Wu
  2025-01-14  8:44 ` [PATCH net-next v4 1/4] net: wangxun: Add support for PTP clock Jiawen Wu
                   ` (3 more replies)
  0 siblings, 4 replies; 12+ messages in thread
From: Jiawen Wu @ 2025-01-14  8:44 UTC (permalink / raw)
  To: andrew+netdev, davem, edumazet, kuba, pabeni, richardcochran,
	linux, horms, jacob.e.keller, netdev, vadim.fedorenko
  Cc: mengyuanlou, Jiawen Wu

Implement support for PTP clock on Wangxun NICs.

Changes in v4:
- Link to v3: https://lore.kernel.org/all/20250110031716.2120642-1-jiawenwu@trustnetic.com/
- Add tx_hwtstamp_errors to record errors of DMA mapping
- Remove flag bits clear for default case in setting TS mode
- Change to use seqlock_t hw_tc_lock
- Add ptp_schedule_worker in wx_ptp_reset()
- Remove perout index check
- Refactor the same code into a function

Changes in v3:
- Link to v2: https://lore.kernel.org/all/20250106084506.2042912-1-jiawenwu@trustnetic.com/
- Clean up messy patches
- Return delay value in wx_ptp_do_aux_work()
- Remove dev_warn()
- Implement ethtool get_ts_stats
- Support PTP_CLK_REQ_PEROUT instead of PTP_CLK_REQ_PPS
- Change to start polling Tx timestamp once descriptor done bit is set

Changes in v2:
- Link to v1: https://lore.kernel.org/all/20250102103026.1982137-1-jiawenwu@trustnetic.com/
- Fix build warning
- Convert to .ndo_hwtstamp_get and .ndo_hwtstamp_set
- Remove needless timestamp flags
- Use .do_aux_work instead of driver service task
- Use the better error code
- Rename function wx_ptp_start_cyclecounter()
- Keep the register names consistent between comments and code

Jiawen Wu (4):
  net: wangxun: Add support for PTP clock
  net: wangxun: Support to get ts info
  net: wangxun: Implement do_aux_work of ptp_clock_info
  net: ngbe: Add support for 1PPS and TOD

 drivers/net/ethernet/wangxun/libwx/Makefile   |   2 +-
 .../net/ethernet/wangxun/libwx/wx_ethtool.c   |  53 +
 .../net/ethernet/wangxun/libwx/wx_ethtool.h   |   4 +
 drivers/net/ethernet/wangxun/libwx/wx_hw.c    |  19 +
 drivers/net/ethernet/wangxun/libwx/wx_hw.h    |   1 +
 drivers/net/ethernet/wangxun/libwx/wx_lib.c   |  52 +-
 drivers/net/ethernet/wangxun/libwx/wx_ptp.c   | 994 ++++++++++++++++++
 drivers/net/ethernet/wangxun/libwx/wx_ptp.h   |  20 +
 drivers/net/ethernet/wangxun/libwx/wx_type.h  | 106 ++
 .../net/ethernet/wangxun/ngbe/ngbe_ethtool.c  |   2 +
 drivers/net/ethernet/wangxun/ngbe/ngbe_main.c |  20 +-
 drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c |  11 +
 drivers/net/ethernet/wangxun/ngbe/ngbe_type.h |   5 +
 .../ethernet/wangxun/txgbe/txgbe_ethtool.c    |   2 +
 .../net/ethernet/wangxun/txgbe/txgbe_main.c   |  11 +
 .../net/ethernet/wangxun/txgbe/txgbe_phy.c    |  10 +
 16 files changed, 1305 insertions(+), 7 deletions(-)
 create mode 100644 drivers/net/ethernet/wangxun/libwx/wx_ptp.c
 create mode 100644 drivers/net/ethernet/wangxun/libwx/wx_ptp.h

-- 
2.27.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH net-next v4 1/4] net: wangxun: Add support for PTP clock
  2025-01-14  8:44 [PATCH net-next v4 0/4] Support PTP clock for Wangxun NICs Jiawen Wu
@ 2025-01-14  8:44 ` Jiawen Wu
  2025-01-14  9:27   ` Vadim Fedorenko
  2025-01-14 10:28   ` Vadim Fedorenko
  2025-01-14  8:44 ` [PATCH net-next v4 2/4] net: wangxun: Support to get ts info Jiawen Wu
                   ` (2 subsequent siblings)
  3 siblings, 2 replies; 12+ messages in thread
From: Jiawen Wu @ 2025-01-14  8:44 UTC (permalink / raw)
  To: andrew+netdev, davem, edumazet, kuba, pabeni, richardcochran,
	linux, horms, jacob.e.keller, netdev, vadim.fedorenko
  Cc: mengyuanlou, Jiawen Wu

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


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH net-next v4 2/4] net: wangxun: Support to get ts info
  2025-01-14  8:44 [PATCH net-next v4 0/4] Support PTP clock for Wangxun NICs Jiawen Wu
  2025-01-14  8:44 ` [PATCH net-next v4 1/4] net: wangxun: Add support for PTP clock Jiawen Wu
@ 2025-01-14  8:44 ` Jiawen Wu
  2025-01-14  8:44 ` [PATCH net-next v4 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info Jiawen Wu
  2025-01-14  8:44 ` [PATCH net-next v4 4/4] net: ngbe: Add support for 1PPS and TOD Jiawen Wu
  3 siblings, 0 replies; 12+ messages in thread
From: Jiawen Wu @ 2025-01-14  8:44 UTC (permalink / raw)
  To: andrew+netdev, davem, edumazet, kuba, pabeni, richardcochran,
	linux, horms, jacob.e.keller, netdev, vadim.fedorenko
  Cc: mengyuanlou, Jiawen Wu

Implement the function get_ts_info and get_ts_stats in ethtool_ops to
get the HW capabilities and statistics for timestamping.

Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
Reviewed-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>
---
 .../net/ethernet/wangxun/libwx/wx_ethtool.c   | 50 +++++++++++++++++++
 .../net/ethernet/wangxun/libwx/wx_ethtool.h   |  4 ++
 .../net/ethernet/wangxun/ngbe/ngbe_ethtool.c  |  2 +
 .../ethernet/wangxun/txgbe/txgbe_ethtool.c    |  2 +
 4 files changed, 58 insertions(+)

diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
index c4b3b00b0926..28f982fbc64c 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
@@ -455,3 +455,53 @@ void wx_set_msglevel(struct net_device *netdev, u32 data)
 	wx->msg_enable = data;
 }
 EXPORT_SYMBOL(wx_set_msglevel);
+
+int wx_get_ts_info(struct net_device *dev,
+		   struct kernel_ethtool_ts_info *info)
+{
+	struct wx *wx = netdev_priv(dev);
+
+	info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
+			   BIT(HWTSTAMP_FILTER_PTP_V1_L4_SYNC) |
+			   BIT(HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_L2_EVENT) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_L4_EVENT) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_SYNC) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_L2_SYNC) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_L4_SYNC) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_DELAY_REQ) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_EVENT);
+
+	info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE |
+				SOF_TIMESTAMPING_TX_HARDWARE |
+				SOF_TIMESTAMPING_RX_HARDWARE |
+				SOF_TIMESTAMPING_RAW_HARDWARE;
+
+	if (wx->ptp_clock)
+		info->phc_index = ptp_clock_index(wx->ptp_clock);
+	else
+		info->phc_index = -1;
+
+	info->tx_types = BIT(HWTSTAMP_TX_OFF) |
+			 BIT(HWTSTAMP_TX_ON);
+
+	return 0;
+}
+EXPORT_SYMBOL(wx_get_ts_info);
+
+void wx_get_ptp_stats(struct net_device *dev,
+		      struct ethtool_ts_stats *ts_stats)
+{
+	struct wx *wx = netdev_priv(dev);
+
+	if (wx->ptp_clock) {
+		ts_stats->pkts = wx->tx_hwtstamp_pkts;
+		ts_stats->lost = wx->tx_hwtstamp_timeouts +
+				 wx->tx_hwtstamp_skipped +
+				 wx->rx_hwtstamp_cleared;
+		ts_stats->err = wx->tx_hwtstamp_errors;
+	}
+}
+EXPORT_SYMBOL(wx_get_ptp_stats);
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.h b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.h
index 600c3b597d1a..9e002e699eca 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.h
@@ -40,4 +40,8 @@ int wx_set_channels(struct net_device *dev,
 		    struct ethtool_channels *ch);
 u32 wx_get_msglevel(struct net_device *netdev);
 void wx_set_msglevel(struct net_device *netdev, u32 data);
+int wx_get_ts_info(struct net_device *dev,
+		   struct kernel_ethtool_ts_info *info);
+void wx_get_ptp_stats(struct net_device *dev,
+		      struct ethtool_ts_stats *ts_stats);
 #endif /* _WX_ETHTOOL_H_ */
diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c
index e868f7ef4920..7e2d9ec38a30 100644
--- a/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c
+++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c
@@ -138,6 +138,8 @@ static const struct ethtool_ops ngbe_ethtool_ops = {
 	.set_channels		= ngbe_set_channels,
 	.get_msglevel		= wx_get_msglevel,
 	.set_msglevel		= wx_set_msglevel,
+	.get_ts_info		= wx_get_ts_info,
+	.get_ts_stats		= wx_get_ptp_stats,
 };
 
 void ngbe_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
index d98314b26c19..78999d484f18 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
@@ -529,6 +529,8 @@ static const struct ethtool_ops txgbe_ethtool_ops = {
 	.set_rxnfc		= txgbe_set_rxnfc,
 	.get_msglevel		= wx_get_msglevel,
 	.set_msglevel		= wx_set_msglevel,
+	.get_ts_info		= wx_get_ts_info,
+	.get_ts_stats		= wx_get_ptp_stats,
 };
 
 void txgbe_set_ethtool_ops(struct net_device *netdev)
-- 
2.27.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH net-next v4 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info
  2025-01-14  8:44 [PATCH net-next v4 0/4] Support PTP clock for Wangxun NICs Jiawen Wu
  2025-01-14  8:44 ` [PATCH net-next v4 1/4] net: wangxun: Add support for PTP clock Jiawen Wu
  2025-01-14  8:44 ` [PATCH net-next v4 2/4] net: wangxun: Support to get ts info Jiawen Wu
@ 2025-01-14  8:44 ` Jiawen Wu
  2025-01-14  9:31   ` Vadim Fedorenko
  2025-01-14  8:44 ` [PATCH net-next v4 4/4] net: ngbe: Add support for 1PPS and TOD Jiawen Wu
  3 siblings, 1 reply; 12+ messages in thread
From: Jiawen Wu @ 2025-01-14  8:44 UTC (permalink / raw)
  To: andrew+netdev, davem, edumazet, kuba, pabeni, richardcochran,
	linux, horms, jacob.e.keller, netdev, vadim.fedorenko
  Cc: mengyuanlou, Jiawen Wu

Implement watchdog task to detect SYSTIME overflow and error cases of
Rx/Tx timestamp.

Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
---
 drivers/net/ethernet/wangxun/libwx/wx_ptp.c   | 114 ++++++++++++++++++
 drivers/net/ethernet/wangxun/libwx/wx_type.h  |   2 +
 drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c |   1 +
 .../net/ethernet/wangxun/txgbe/txgbe_phy.c    |   1 +
 4 files changed, 118 insertions(+)

diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
index 97d39e8f02da..2e3d9cfc8aba 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
@@ -251,6 +251,116 @@ static void wx_ptp_tx_hwtstamp_work(struct work_struct *work)
 	}
 }
 
+/**
+ * wx_ptp_overflow_check - watchdog task to detect SYSTIME overflow
+ * @wx: pointer to wx struct
+ *
+ * this watchdog task periodically reads the timecounter
+ * in order to prevent missing when the system time registers wrap
+ * around. This needs to be run approximately twice a minute for the fastest
+ * overflowing hardware. We run it for all hardware since it shouldn't have a
+ * large impact.
+ */
+static void wx_ptp_overflow_check(struct wx *wx)
+{
+	bool timeout = time_is_before_jiffies(wx->last_overflow_check +
+					      WX_OVERFLOW_PERIOD);
+	unsigned long flags;
+
+	if (timeout) {
+		/* Update the timecounter */
+		write_seqlock_irqsave(&wx->hw_tc_lock, flags);
+		timecounter_read(&wx->hw_tc);
+		write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
+
+		wx->last_overflow_check = jiffies;
+	}
+}
+
+/**
+ * wx_ptp_rx_hang - detect error case when Rx timestamp registers latched
+ * @wx: pointer to wx struct
+ *
+ * this watchdog task is scheduled to detect error case where hardware has
+ * dropped an Rx packet that was timestamped when the ring is full. The
+ * particular error is rare but leaves the device in a state unable to
+ * timestamp any future packets.
+ */
+static void wx_ptp_rx_hang(struct wx *wx)
+{
+	struct wx_ring *rx_ring;
+	unsigned long rx_event;
+	u32 tsyncrxctl;
+	int n;
+
+	tsyncrxctl = rd32(wx, WX_PSR_1588_CTL);
+
+	/* if we don't have a valid timestamp in the registers, just update the
+	 * timeout counter and exit
+	 */
+	if (!(tsyncrxctl & WX_PSR_1588_CTL_VALID)) {
+		wx->last_rx_ptp_check = jiffies;
+		return;
+	}
+
+	/* determine the most recent watchdog or rx_timestamp event */
+	rx_event = wx->last_rx_ptp_check;
+	for (n = 0; n < wx->num_rx_queues; n++) {
+		rx_ring = wx->rx_ring[n];
+		if (time_after(rx_ring->last_rx_timestamp, rx_event))
+			rx_event = rx_ring->last_rx_timestamp;
+	}
+
+	/* only need to read the high RXSTMP register to clear the lock */
+	if (time_is_before_jiffies(rx_event + 5 * HZ)) {
+		rd32(wx, WX_PSR_1588_STMPH);
+		wx->last_rx_ptp_check = jiffies;
+
+		wx->rx_hwtstamp_cleared++;
+		dev_warn(&wx->pdev->dev, "clearing RX Timestamp hang");
+	}
+}
+
+/**
+ * wx_ptp_tx_hang - detect error case where Tx timestamp never finishes
+ * @wx: private network wx structure
+ */
+static void wx_ptp_tx_hang(struct wx *wx)
+{
+	bool timeout = time_is_before_jiffies(wx->ptp_tx_start +
+					      WX_PTP_TX_TIMEOUT);
+
+	if (!wx->ptp_tx_skb)
+		return;
+
+	if (!test_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state))
+		return;
+
+	/* If we haven't received a timestamp within the timeout, it is
+	 * reasonable to assume that it will never occur, so we can unlock the
+	 * timestamp bit when this occurs.
+	 */
+	if (timeout) {
+		cancel_work_sync(&wx->ptp_tx_work);
+		wx_ptp_clear_tx_timestamp(wx);
+		wx->tx_hwtstamp_timeouts++;
+		dev_warn(&wx->pdev->dev, "clearing Tx timestamp hang\n");
+	}
+}
+
+static long wx_ptp_do_aux_work(struct ptp_clock_info *ptp)
+{
+	struct wx *wx = container_of(ptp, struct wx, ptp_caps);
+
+	wx_ptp_overflow_check(wx);
+	if (unlikely(test_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER,
+			      wx->flags)))
+		wx_ptp_rx_hang(wx);
+	wx_ptp_tx_hang(wx);
+
+	return HZ;
+}
+
 /**
  * wx_ptp_create_clock
  * @wx: the private board structure
@@ -283,6 +393,7 @@ static long wx_ptp_create_clock(struct wx *wx)
 	wx->ptp_caps.adjtime = wx_ptp_adjtime;
 	wx->ptp_caps.gettimex64 = wx_ptp_gettimex64;
 	wx->ptp_caps.settime64 = wx_ptp_settime64;
+	wx->ptp_caps.do_aux_work = wx_ptp_do_aux_work;
 	if (wx->mac.type == wx_mac_em)
 		wx->ptp_caps.max_adj = 500000000;
 	else
@@ -566,6 +677,9 @@ void wx_ptp_reset(struct wx *wx)
 	timecounter_init(&wx->hw_tc, &wx->hw_cc,
 			 ktime_to_ns(ktime_get_real()));
 	write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
+
+	wx->last_overflow_check = jiffies;
+	ptp_schedule_worker(wx->ptp_clock, HZ);
 }
 EXPORT_SYMBOL(wx_ptp_reset);
 
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h
index 9199317f7175..c2e58de3559a 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
@@ -1175,6 +1175,8 @@ struct wx {
 	u32 tx_hwtstamp_skipped;
 	u32 tx_hwtstamp_errors;
 	u32 rx_hwtstamp_cleared;
+	unsigned long last_overflow_check;
+	unsigned long last_rx_ptp_check;
 	unsigned long ptp_tx_start;
 	seqlock_t hw_tc_lock; /* seqlock for ptp */
 	struct cyclecounter hw_cc;
diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
index c7944e62838a..ea1d7e9a91f3 100644
--- a/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
+++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
@@ -111,6 +111,7 @@ static void ngbe_mac_link_up(struct phylink_config *config,
 	wr32(wx, WX_MAC_WDG_TIMEOUT, reg);
 
 	wx->speed = speed;
+	wx->last_rx_ptp_check = jiffies;
 	if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
 		wx_ptp_reset_cyclecounter(wx);
 }
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
index 60e5f3288ad8..7e17d727c2ba 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
@@ -222,6 +222,7 @@ static void txgbe_mac_link_up(struct phylink_config *config,
 	wr32(wx, WX_MAC_WDG_TIMEOUT, wdg);
 
 	wx->speed = speed;
+	wx->last_rx_ptp_check = jiffies;
 	if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
 		wx_ptp_reset_cyclecounter(wx);
 }
-- 
2.27.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH net-next v4 4/4] net: ngbe: Add support for 1PPS and TOD
  2025-01-14  8:44 [PATCH net-next v4 0/4] Support PTP clock for Wangxun NICs Jiawen Wu
                   ` (2 preceding siblings ...)
  2025-01-14  8:44 ` [PATCH net-next v4 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info Jiawen Wu
@ 2025-01-14  8:44 ` Jiawen Wu
  2025-01-14 16:24   ` Richard Cochran
  3 siblings, 1 reply; 12+ messages in thread
From: Jiawen Wu @ 2025-01-14  8:44 UTC (permalink / raw)
  To: andrew+netdev, davem, edumazet, kuba, pabeni, richardcochran,
	linux, horms, jacob.e.keller, netdev, vadim.fedorenko
  Cc: mengyuanlou, Jiawen Wu

Implement support for generating a 1pps output signal on SDP0.
And support custom firmware to output TOD.

Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
---
 drivers/net/ethernet/wangxun/libwx/wx_hw.c    |  19 ++
 drivers/net/ethernet/wangxun/libwx/wx_hw.h    |   1 +
 drivers/net/ethernet/wangxun/libwx/wx_ptp.c   | 182 +++++++++++++++++-
 drivers/net/ethernet/wangxun/libwx/wx_ptp.h   |   1 +
 drivers/net/ethernet/wangxun/libwx/wx_type.h  |  35 ++++
 drivers/net/ethernet/wangxun/ngbe/ngbe_main.c |  12 +-
 drivers/net/ethernet/wangxun/ngbe/ngbe_type.h |   5 +
 7 files changed, 250 insertions(+), 5 deletions(-)

diff --git a/drivers/net/ethernet/wangxun/libwx/wx_hw.c b/drivers/net/ethernet/wangxun/libwx/wx_hw.c
index deaf670c160e..907d13ade404 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_hw.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_hw.c
@@ -393,6 +393,25 @@ int wx_host_interface_command(struct wx *wx, u32 *buffer,
 }
 EXPORT_SYMBOL(wx_host_interface_command);
 
+int wx_set_pps(struct wx *wx, bool enable, u64 nsec, u64 cycles)
+{
+	struct wx_hic_set_pps pps_cmd;
+
+	pps_cmd.hdr.cmd = FW_PPS_SET_CMD;
+	pps_cmd.hdr.buf_len = FW_PPS_SET_LEN;
+	pps_cmd.hdr.cmd_or_resp.cmd_resv = FW_CEM_CMD_RESERVED;
+	pps_cmd.lan_id = wx->bus.func;
+	pps_cmd.enable = (u8)enable;
+	pps_cmd.nsec = nsec;
+	pps_cmd.cycles = cycles;
+	pps_cmd.hdr.checksum = FW_DEFAULT_CHECKSUM;
+
+	return wx_host_interface_command(wx, (u32 *)&pps_cmd,
+					 sizeof(pps_cmd),
+					 WX_HI_COMMAND_TIMEOUT,
+					 false);
+}
+
 /**
  *  wx_read_ee_hostif_data - Read EEPROM word using a host interface cmd
  *  assuming that the semaphore is already obtained.
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_hw.h b/drivers/net/ethernet/wangxun/libwx/wx_hw.h
index 11fb33349482..b883342bb576 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_hw.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_hw.h
@@ -18,6 +18,7 @@ void wx_control_hw(struct wx *wx, bool drv);
 int wx_mng_present(struct wx *wx);
 int wx_host_interface_command(struct wx *wx, u32 *buffer,
 			      u32 length, u32 timeout, bool return_data);
+int wx_set_pps(struct wx *wx, bool enable, u64 nsec, u64 cycles);
 int wx_read_ee_hostif(struct wx *wx, u16 offset, u16 *data);
 int wx_read_ee_hostif_buffer(struct wx *wx,
 			     u16 offset, u16 words, u16 *data);
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
index 2e3d9cfc8aba..d52bd27c1919 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
@@ -73,6 +73,9 @@ static int wx_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
 	timecounter_adjtime(&wx->hw_tc, delta);
 	write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
 
+	if (wx->ptp_setup_sdp)
+		wx->ptp_setup_sdp(wx);
+
 	return 0;
 }
 
@@ -132,6 +135,9 @@ static int wx_ptp_settime64(struct ptp_clock_info *ptp,
 	timecounter_init(&wx->hw_tc, &wx->hw_cc, ns);
 	write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
 
+	if (wx->ptp_setup_sdp)
+		wx->ptp_setup_sdp(wx);
+
 	return 0;
 }
 
@@ -361,6 +367,160 @@ static long wx_ptp_do_aux_work(struct ptp_clock_info *ptp)
 	return HZ;
 }
 
+static u64 wx_ptp_trigger_calc(struct wx *wx)
+{
+	struct cyclecounter *cc = &wx->hw_cc;
+	unsigned long flags;
+	u64 ns = 0;
+	u32 rem;
+
+	/* Read the current clock time, and save the cycle counter value */
+	write_seqlock_irqsave(&wx->hw_tc_lock, flags);
+	ns = timecounter_read(&wx->hw_tc);
+	wx->pps_edge_start = wx->hw_tc.cycle_last;
+	write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
+	wx->pps_edge_end = wx->pps_edge_start;
+
+	/* Figure out how far past the next second we are */
+	div_u64_rem(ns, WX_NS_PER_SEC, &rem);
+
+	/* Figure out how many nanoseconds to add to round the clock edge up
+	 * to the next full second
+	 */
+	rem = (WX_NS_PER_SEC - rem);
+
+	/* Adjust the clock edge to align with the next full second. */
+	wx->pps_edge_start += div_u64(((u64)rem << cc->shift), cc->mult);
+	wx->pps_edge_end += div_u64(((u64)(rem + wx->pps_width) <<
+				     cc->shift), cc->mult);
+
+	return (ns + rem);
+}
+
+static int wx_ptp_setup_sdp(struct wx *wx)
+{
+	struct cyclecounter *cc = &wx->hw_cc;
+	u32 tsauxc;
+	u64 nsec;
+
+	if (wx->pps_width >= WX_NS_PER_SEC) {
+		wx_err(wx, "PTP pps width cannot be longer than 1s!\n");
+		return -EINVAL;
+	}
+
+	/* disable the pin first */
+	wr32ptp(wx, WX_TSC_1588_AUX_CTL, 0);
+	WX_WRITE_FLUSH(wx);
+
+	if (!test_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags)) {
+		if (wx->pps_enabled) {
+			wx->pps_enabled = false;
+			wx_set_pps(wx, false, 0, 0);
+		}
+		return 0;
+	}
+
+	wx->pps_enabled = true;
+	nsec = wx_ptp_trigger_calc(wx);
+	wx_set_pps(wx, wx->pps_enabled, nsec, wx->pps_edge_start);
+
+	tsauxc = WX_TSC_1588_AUX_CTL_PLSG | WX_TSC_1588_AUX_CTL_EN_TT0 |
+		WX_TSC_1588_AUX_CTL_EN_TT1 | WX_TSC_1588_AUX_CTL_EN_TS0;
+	wr32ptp(wx, WX_TSC_1588_TRGT_L(0), (u32)wx->pps_edge_start);
+	wr32ptp(wx, WX_TSC_1588_TRGT_H(0), (u32)(wx->pps_edge_start >> 32));
+	wr32ptp(wx, WX_TSC_1588_TRGT_L(1), (u32)wx->pps_edge_end);
+	wr32ptp(wx, WX_TSC_1588_TRGT_H(1), (u32)(wx->pps_edge_end >> 32));
+	wr32ptp(wx, WX_TSC_1588_SDP(0),
+		WX_TSC_1588_SDP_FUN_SEL_TT0 | WX_TSC_1588_SDP_OUT_LEVEL_H);
+	wr32ptp(wx, WX_TSC_1588_SDP(1), WX_TSC_1588_SDP_FUN_SEL_TS0);
+	wr32ptp(wx, WX_TSC_1588_AUX_CTL, tsauxc);
+	wr32ptp(wx, WX_TSC_1588_INT_EN, WX_TSC_1588_INT_EN_TT1);
+	WX_WRITE_FLUSH(wx);
+
+	/* Adjust the clock edge to align with the next full second. */
+	wx->sec_to_cc = div_u64(((u64)WX_NS_PER_SEC << cc->shift), cc->mult);
+
+	return 0;
+}
+
+/**
+ * wx_ptp_feature_enable
+ * @ptp: the ptp clock structure
+ * @rq: the requested feature to change
+ * @on: whether to enable or disable the feature
+ *
+ * enable (or disable) ancillary features of the phc subsystem.
+ */
+static int wx_ptp_feature_enable(struct ptp_clock_info *ptp,
+				 struct ptp_clock_request *rq, int on)
+{
+	struct wx *wx = container_of(ptp, struct wx, ptp_caps);
+
+	/**
+	 * When PPS is enabled, unmask the interrupt for the ClockOut
+	 * feature, so that the interrupt handler can send the PPS
+	 * event when the clock SDP triggers. Clear mask when PPS is
+	 * disabled
+	 */
+	if (rq->type != PTP_CLK_REQ_PEROUT || !wx->ptp_setup_sdp)
+		return -EOPNOTSUPP;
+
+	/* Reject requests with unsupported flags */
+	if (rq->perout.flags & ~PTP_PEROUT_PHASE)
+		return -EOPNOTSUPP;
+
+	if (rq->perout.phase.sec || rq->perout.phase.nsec) {
+		wx_err(wx, "Absolute start time not supported.\n");
+		return -EINVAL;
+	}
+
+	if (on)
+		set_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
+	else
+		clear_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
+
+	wx->pps_width = rq->perout.period.nsec;
+	return wx->ptp_setup_sdp(wx);
+}
+
+/**
+ * wx_ptp_check_pps_event
+ * @wx: the private wx structure
+ *
+ * This function is called by the interrupt routine when checking for
+ * interrupts. It will check and handle a pps event.
+ */
+void wx_ptp_check_pps_event(struct wx *wx)
+{
+	u32 tsauxc, int_status;
+
+	/* this check is necessary in case the interrupt was enabled via some
+	 * alternative means (ex. debug_fs). Better to check here than
+	 * everywhere that calls this function.
+	 */
+	if (!wx->ptp_clock)
+		return;
+
+	int_status = rd32ptp(wx, WX_TSC_1588_INT_ST);
+	if (int_status & WX_TSC_1588_INT_ST_TT1) {
+		/* disable the pin first */
+		wr32ptp(wx, WX_TSC_1588_AUX_CTL, 0);
+		WX_WRITE_FLUSH(wx);
+
+		wx_ptp_trigger_calc(wx);
+
+		tsauxc = WX_TSC_1588_AUX_CTL_PLSG | WX_TSC_1588_AUX_CTL_EN_TT0 |
+			 WX_TSC_1588_AUX_CTL_EN_TT1 | WX_TSC_1588_AUX_CTL_EN_TS0;
+		wr32ptp(wx, WX_TSC_1588_TRGT_L(0), (u32)wx->pps_edge_start);
+		wr32ptp(wx, WX_TSC_1588_TRGT_H(0), (u32)(wx->pps_edge_start >> 32));
+		wr32ptp(wx, WX_TSC_1588_TRGT_L(1), (u32)wx->pps_edge_end);
+		wr32ptp(wx, WX_TSC_1588_TRGT_H(1), (u32)(wx->pps_edge_end >> 32));
+		wr32ptp(wx, WX_TSC_1588_AUX_CTL, tsauxc);
+		WX_WRITE_FLUSH(wx);
+	}
+}
+EXPORT_SYMBOL(wx_ptp_check_pps_event);
+
 /**
  * wx_ptp_create_clock
  * @wx: the private board structure
@@ -387,17 +547,22 @@ static long wx_ptp_create_clock(struct wx *wx)
 	wx->ptp_caps.owner = THIS_MODULE;
 	wx->ptp_caps.n_alarm = 0;
 	wx->ptp_caps.n_ext_ts = 0;
-	wx->ptp_caps.n_per_out = 0;
 	wx->ptp_caps.pps = 0;
 	wx->ptp_caps.adjfine = wx_ptp_adjfine;
 	wx->ptp_caps.adjtime = wx_ptp_adjtime;
 	wx->ptp_caps.gettimex64 = wx_ptp_gettimex64;
 	wx->ptp_caps.settime64 = wx_ptp_settime64;
 	wx->ptp_caps.do_aux_work = wx_ptp_do_aux_work;
-	if (wx->mac.type == wx_mac_em)
+	if (wx->mac.type == wx_mac_em) {
 		wx->ptp_caps.max_adj = 500000000;
-	else
+		wx->ptp_caps.n_per_out = 1;
+		wx->ptp_setup_sdp = wx_ptp_setup_sdp;
+		wx->ptp_caps.enable = wx_ptp_feature_enable;
+	} else {
 		wx->ptp_caps.max_adj = 250000000;
+		wx->ptp_caps.n_per_out = 0;
+		wx->ptp_setup_sdp = NULL;
+	}
 
 	wx->ptp_clock = ptp_clock_register(&wx->ptp_caps, &wx->pdev->dev);
 	if (IS_ERR(wx->ptp_clock)) {
@@ -679,7 +844,12 @@ void wx_ptp_reset(struct wx *wx)
 	write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
 
 	wx->last_overflow_check = jiffies;
-	ptp_schedule_worker(wx->ptp_clock, HZ);
+
+	/* Now that the shift has been calculated and the systime
+	 * registers reset, (re-)enable the Clock out feature
+	 */
+	if (wx->ptp_setup_sdp)
+		wx->ptp_setup_sdp(wx);
 }
 EXPORT_SYMBOL(wx_ptp_reset);
 
@@ -731,6 +901,10 @@ void wx_ptp_suspend(struct wx *wx)
 	if (!test_and_clear_bit(WX_STATE_PTP_RUNNING, wx->state))
 		return;
 
+	clear_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
+	if (wx->ptp_setup_sdp)
+		wx->ptp_setup_sdp(wx);
+
 	cancel_work_sync(&wx->ptp_tx_work);
 	wx_ptp_clear_tx_timestamp(wx);
 }
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.h b/drivers/net/ethernet/wangxun/libwx/wx_ptp.h
index 8742d2797363..50db90a6e3ee 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.h
@@ -4,6 +4,7 @@
 #ifndef _WX_PTP_H_
 #define _WX_PTP_H_
 
+void wx_ptp_check_pps_event(struct wx *wx);
 void wx_ptp_reset_cyclecounter(struct wx *wx);
 void wx_ptp_reset(struct wx *wx);
 void wx_ptp_init(struct wx *wx);
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h
index c2e58de3559a..7f6af62f5e94 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
@@ -281,6 +281,23 @@
 #define WX_TSC_1588_SYSTIML          0x11F0C
 #define WX_TSC_1588_SYSTIMH          0x11F10
 #define WX_TSC_1588_INC              0x11F14
+#define WX_TSC_1588_INT_ST           0x11F20
+#define WX_TSC_1588_INT_ST_TT1       BIT(5)
+#define WX_TSC_1588_INT_EN           0x11F24
+#define WX_TSC_1588_INT_EN_TT1       BIT(5)
+#define WX_TSC_1588_AUX_CTL          0x11F28
+#define WX_TSC_1588_AUX_CTL_EN_TS0   BIT(8)
+#define WX_TSC_1588_AUX_CTL_EN_TT1   BIT(2)
+#define WX_TSC_1588_AUX_CTL_PLSG     BIT(1)
+#define WX_TSC_1588_AUX_CTL_EN_TT0   BIT(0)
+#define WX_TSC_1588_TRGT_L(i)        (0x11F2C + ((i) * 8)) /* [0,1] */
+#define WX_TSC_1588_TRGT_H(i)        (0x11F30 + ((i) * 8)) /* [0,1] */
+#define WX_TSC_1588_SDP(i)           (0x11F5C + ((i) * 4)) /* [0,3] */
+#define WX_TSC_1588_SDP_OUT_LEVEL_H  FIELD_PREP(BIT(4), 0)
+#define WX_TSC_1588_SDP_OUT_LEVEL_L  FIELD_PREP(BIT(4), 1)
+#define WX_TSC_1588_SDP_FUN_SEL_MASK GENMASK(2, 0)
+#define WX_TSC_1588_SDP_FUN_SEL_TT0  FIELD_PREP(WX_TSC_1588_SDP_FUN_SEL_MASK, 1)
+#define WX_TSC_1588_SDP_FUN_SEL_TS0  FIELD_PREP(WX_TSC_1588_SDP_FUN_SEL_MASK, 5)
 
 /************************************** MNG ********************************/
 #define WX_MNG_SWFW_SYNC             0x1E008
@@ -410,6 +427,8 @@ enum WX_MSCA_CMD_value {
 #define FW_CEM_CMD_RESERVED          0X0
 #define FW_CEM_MAX_RETRIES           3
 #define FW_CEM_RESP_STATUS_SUCCESS   0x1
+#define FW_PPS_SET_CMD               0xF6
+#define FW_PPS_SET_LEN               0x14
 
 #define WX_SW_REGION_PTR             0x1C
 
@@ -730,6 +749,15 @@ struct wx_hic_reset {
 	u16 reset_type;
 };
 
+struct wx_hic_set_pps {
+	struct wx_hic_hdr hdr;
+	u8 lan_id;
+	u8 enable;
+	u16 pad2;
+	u64 nsec;
+	u64 cycles;
+};
+
 /* Bus parameters */
 struct wx_bus_info {
 	u8 func;
@@ -1068,6 +1096,7 @@ enum wx_pf_flags {
 	WX_FLAG_FDIR_PERFECT,
 	WX_FLAG_RX_HWTSTAMP_ENABLED,
 	WX_FLAG_RX_HWTSTAMP_IN_REGISTER,
+	WX_FLAG_PTP_PPS_ENABLED,
 	WX_PF_FLAGS_NBITS               /* must be last */
 };
 
@@ -1168,7 +1197,13 @@ 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);
+	int (*ptp_setup_sdp)(struct wx *wx);
 
+	bool pps_enabled;
+	u64 pps_width;
+	u64 pps_edge_start;
+	u64 pps_edge_end;
+	u64 sec_to_cc;
 	u32 base_incval;
 	u32 tx_hwtstamp_pkts;
 	u32 tx_hwtstamp_timeouts;
diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
index c60a96cc3508..a6159214ec0a 100644
--- a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
+++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
@@ -168,7 +168,7 @@ static irqreturn_t ngbe_intr(int __always_unused irq, void *data)
 	struct wx_q_vector *q_vector;
 	struct wx *wx  = data;
 	struct pci_dev *pdev;
-	u32 eicr;
+	u32 eicr, eicr_misc;
 
 	q_vector = wx->q_vector[0];
 	pdev = wx->pdev;
@@ -186,6 +186,10 @@ static irqreturn_t ngbe_intr(int __always_unused irq, void *data)
 	if (!(pdev->msi_enabled))
 		wr32(wx, WX_PX_INTA, 1);
 
+	eicr_misc = wx_misc_isb(wx, WX_ISB_MISC);
+	if (unlikely(eicr_misc & NGBE_PX_MISC_IC_TIMESYNC))
+		wx_ptp_check_pps_event(wx);
+
 	wx->isb_mem[WX_ISB_MISC] = 0;
 	/* would disable interrupts here but it is auto disabled */
 	napi_schedule_irqoff(&q_vector->napi);
@@ -199,6 +203,12 @@ static irqreturn_t ngbe_intr(int __always_unused irq, void *data)
 static irqreturn_t ngbe_msix_other(int __always_unused irq, void *data)
 {
 	struct wx *wx = data;
+	u32 eicr;
+
+	eicr = wx_misc_isb(wx, WX_ISB_MISC);
+
+	if (unlikely(eicr & NGBE_PX_MISC_IC_TIMESYNC))
+		wx_ptp_check_pps_event(wx);
 
 	/* re-enable the original interrupt state, no lsc, no queues */
 	if (netif_running(wx->netdev))
diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h b/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h
index f48ed7fc1805..992adbb98c7d 100644
--- a/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h
+++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h
@@ -70,15 +70,20 @@
 
 /* Extended Interrupt Enable Set */
 #define NGBE_PX_MISC_IEN_DEV_RST		BIT(10)
+#define NGBE_PX_MISC_IEN_TIMESYNC		BIT(11)
 #define NGBE_PX_MISC_IEN_ETH_LK			BIT(18)
 #define NGBE_PX_MISC_IEN_INT_ERR		BIT(20)
 #define NGBE_PX_MISC_IEN_GPIO			BIT(26)
 #define NGBE_PX_MISC_IEN_MASK ( \
 				NGBE_PX_MISC_IEN_DEV_RST | \
+				NGBE_PX_MISC_IEN_TIMESYNC | \
 				NGBE_PX_MISC_IEN_ETH_LK | \
 				NGBE_PX_MISC_IEN_INT_ERR | \
 				NGBE_PX_MISC_IEN_GPIO)
 
+/* Extended Interrupt Cause Read */
+#define NGBE_PX_MISC_IC_TIMESYNC		BIT(11) /* time sync */
+
 #define NGBE_INTR_ALL				0x1FF
 #define NGBE_INTR_MISC				BIT(0)
 
-- 
2.27.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH net-next v4 1/4] net: wangxun: Add support for PTP clock
  2025-01-14  8:44 ` [PATCH net-next v4 1/4] net: wangxun: Add support for PTP clock Jiawen Wu
@ 2025-01-14  9:27   ` Vadim Fedorenko
  2025-01-14 10:28   ` Vadim Fedorenko
  1 sibling, 0 replies; 12+ messages in thread
From: Vadim Fedorenko @ 2025-01-14  9:27 UTC (permalink / raw)
  To: Jiawen Wu, andrew+netdev, davem, edumazet, kuba, pabeni,
	richardcochran, linux, horms, jacob.e.keller, netdev
  Cc: mengyuanlou

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,


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH net-next v4 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info
  2025-01-14  8:44 ` [PATCH net-next v4 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info Jiawen Wu
@ 2025-01-14  9:31   ` Vadim Fedorenko
  0 siblings, 0 replies; 12+ messages in thread
From: Vadim Fedorenko @ 2025-01-14  9:31 UTC (permalink / raw)
  To: Jiawen Wu, andrew+netdev, davem, edumazet, kuba, pabeni,
	richardcochran, linux, horms, jacob.e.keller, netdev
  Cc: mengyuanlou

On 14/01/2025 08:44, Jiawen Wu wrote:
> Implement watchdog task to detect SYSTIME overflow and error cases of
> Rx/Tx timestamp.
> 
> Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
> ---
>   drivers/net/ethernet/wangxun/libwx/wx_ptp.c   | 114 ++++++++++++++++++
>   drivers/net/ethernet/wangxun/libwx/wx_type.h  |   2 +
>   drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c |   1 +
>   .../net/ethernet/wangxun/txgbe/txgbe_phy.c    |   1 +
>   4 files changed, 118 insertions(+)

Reviewed-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>


> 
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
> index 97d39e8f02da..2e3d9cfc8aba 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
> @@ -251,6 +251,116 @@ static void wx_ptp_tx_hwtstamp_work(struct work_struct *work)
>   	}
>   }
>   
> +/**
> + * wx_ptp_overflow_check - watchdog task to detect SYSTIME overflow
> + * @wx: pointer to wx struct
> + *
> + * this watchdog task periodically reads the timecounter
> + * in order to prevent missing when the system time registers wrap
> + * around. This needs to be run approximately twice a minute for the fastest
> + * overflowing hardware. We run it for all hardware since it shouldn't have a
> + * large impact.
> + */
> +static void wx_ptp_overflow_check(struct wx *wx)
> +{
> +	bool timeout = time_is_before_jiffies(wx->last_overflow_check +
> +					      WX_OVERFLOW_PERIOD);
> +	unsigned long flags;
> +
> +	if (timeout) {
> +		/* Update the timecounter */
> +		write_seqlock_irqsave(&wx->hw_tc_lock, flags);
> +		timecounter_read(&wx->hw_tc);
> +		write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
> +
> +		wx->last_overflow_check = jiffies;
> +	}
> +}
> +
> +/**
> + * wx_ptp_rx_hang - detect error case when Rx timestamp registers latched
> + * @wx: pointer to wx struct
> + *
> + * this watchdog task is scheduled to detect error case where hardware has
> + * dropped an Rx packet that was timestamped when the ring is full. The
> + * particular error is rare but leaves the device in a state unable to
> + * timestamp any future packets.
> + */
> +static void wx_ptp_rx_hang(struct wx *wx)
> +{
> +	struct wx_ring *rx_ring;
> +	unsigned long rx_event;
> +	u32 tsyncrxctl;
> +	int n;
> +
> +	tsyncrxctl = rd32(wx, WX_PSR_1588_CTL);
> +
> +	/* if we don't have a valid timestamp in the registers, just update the
> +	 * timeout counter and exit
> +	 */
> +	if (!(tsyncrxctl & WX_PSR_1588_CTL_VALID)) {
> +		wx->last_rx_ptp_check = jiffies;
> +		return;
> +	}
> +
> +	/* determine the most recent watchdog or rx_timestamp event */
> +	rx_event = wx->last_rx_ptp_check;
> +	for (n = 0; n < wx->num_rx_queues; n++) {
> +		rx_ring = wx->rx_ring[n];
> +		if (time_after(rx_ring->last_rx_timestamp, rx_event))
> +			rx_event = rx_ring->last_rx_timestamp;
> +	}
> +
> +	/* only need to read the high RXSTMP register to clear the lock */
> +	if (time_is_before_jiffies(rx_event + 5 * HZ)) {
> +		rd32(wx, WX_PSR_1588_STMPH);
> +		wx->last_rx_ptp_check = jiffies;
> +
> +		wx->rx_hwtstamp_cleared++;
> +		dev_warn(&wx->pdev->dev, "clearing RX Timestamp hang");
> +	}
> +}
> +
> +/**
> + * wx_ptp_tx_hang - detect error case where Tx timestamp never finishes
> + * @wx: private network wx structure
> + */
> +static void wx_ptp_tx_hang(struct wx *wx)
> +{
> +	bool timeout = time_is_before_jiffies(wx->ptp_tx_start +
> +					      WX_PTP_TX_TIMEOUT);
> +
> +	if (!wx->ptp_tx_skb)
> +		return;
> +
> +	if (!test_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state))
> +		return;
> +
> +	/* If we haven't received a timestamp within the timeout, it is
> +	 * reasonable to assume that it will never occur, so we can unlock the
> +	 * timestamp bit when this occurs.
> +	 */
> +	if (timeout) {
> +		cancel_work_sync(&wx->ptp_tx_work);
> +		wx_ptp_clear_tx_timestamp(wx);
> +		wx->tx_hwtstamp_timeouts++;
> +		dev_warn(&wx->pdev->dev, "clearing Tx timestamp hang\n");
> +	}
> +}
> +
> +static long wx_ptp_do_aux_work(struct ptp_clock_info *ptp)
> +{
> +	struct wx *wx = container_of(ptp, struct wx, ptp_caps);
> +
> +	wx_ptp_overflow_check(wx);
> +	if (unlikely(test_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER,
> +			      wx->flags)))
> +		wx_ptp_rx_hang(wx);
> +	wx_ptp_tx_hang(wx);
> +
> +	return HZ;
> +}
> +
>   /**
>    * wx_ptp_create_clock
>    * @wx: the private board structure
> @@ -283,6 +393,7 @@ static long wx_ptp_create_clock(struct wx *wx)
>   	wx->ptp_caps.adjtime = wx_ptp_adjtime;
>   	wx->ptp_caps.gettimex64 = wx_ptp_gettimex64;
>   	wx->ptp_caps.settime64 = wx_ptp_settime64;
> +	wx->ptp_caps.do_aux_work = wx_ptp_do_aux_work;
>   	if (wx->mac.type == wx_mac_em)
>   		wx->ptp_caps.max_adj = 500000000;
>   	else
> @@ -566,6 +677,9 @@ void wx_ptp_reset(struct wx *wx)
>   	timecounter_init(&wx->hw_tc, &wx->hw_cc,
>   			 ktime_to_ns(ktime_get_real()));
>   	write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
> +
> +	wx->last_overflow_check = jiffies;
> +	ptp_schedule_worker(wx->ptp_clock, HZ);
>   }
>   EXPORT_SYMBOL(wx_ptp_reset);
>   
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h
> index 9199317f7175..c2e58de3559a 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
> @@ -1175,6 +1175,8 @@ struct wx {
>   	u32 tx_hwtstamp_skipped;
>   	u32 tx_hwtstamp_errors;
>   	u32 rx_hwtstamp_cleared;
> +	unsigned long last_overflow_check;
> +	unsigned long last_rx_ptp_check;
>   	unsigned long ptp_tx_start;
>   	seqlock_t hw_tc_lock; /* seqlock for ptp */
>   	struct cyclecounter hw_cc;
> diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
> index c7944e62838a..ea1d7e9a91f3 100644
> --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
> +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
> @@ -111,6 +111,7 @@ static void ngbe_mac_link_up(struct phylink_config *config,
>   	wr32(wx, WX_MAC_WDG_TIMEOUT, reg);
>   
>   	wx->speed = speed;
> +	wx->last_rx_ptp_check = jiffies;
>   	if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
>   		wx_ptp_reset_cyclecounter(wx);
>   }
> diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
> index 60e5f3288ad8..7e17d727c2ba 100644
> --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
> +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
> @@ -222,6 +222,7 @@ static void txgbe_mac_link_up(struct phylink_config *config,
>   	wr32(wx, WX_MAC_WDG_TIMEOUT, wdg);
>   
>   	wx->speed = speed;
> +	wx->last_rx_ptp_check = jiffies;
>   	if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
>   		wx_ptp_reset_cyclecounter(wx);
>   }


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH net-next v4 1/4] net: wangxun: Add support for PTP clock
  2025-01-14  8:44 ` [PATCH net-next v4 1/4] net: wangxun: Add support for PTP clock Jiawen Wu
  2025-01-14  9:27   ` Vadim Fedorenko
@ 2025-01-14 10:28   ` Vadim Fedorenko
  1 sibling, 0 replies; 12+ messages in thread
From: Vadim Fedorenko @ 2025-01-14 10:28 UTC (permalink / raw)
  To: Jiawen Wu, andrew+netdev, davem, edumazet, kuba, pabeni,
	richardcochran, linux, horms, jacob.e.keller, netdev
  Cc: mengyuanlou

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

[...]

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH net-next v4 4/4] net: ngbe: Add support for 1PPS and TOD
  2025-01-14  8:44 ` [PATCH net-next v4 4/4] net: ngbe: Add support for 1PPS and TOD Jiawen Wu
@ 2025-01-14 16:24   ` Richard Cochran
  2025-01-16  6:24     ` Jiawen Wu
  0 siblings, 1 reply; 12+ messages in thread
From: Richard Cochran @ 2025-01-14 16:24 UTC (permalink / raw)
  To: Jiawen Wu
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, horms,
	jacob.e.keller, netdev, vadim.fedorenko, mengyuanlou

On Tue, Jan 14, 2025 at 04:44:25PM +0800, Jiawen Wu wrote:
> +static int wx_ptp_feature_enable(struct ptp_clock_info *ptp,
> +				 struct ptp_clock_request *rq, int on)
> +{
> +	struct wx *wx = container_of(ptp, struct wx, ptp_caps);
> +
> +	/**
> +	 * When PPS is enabled, unmask the interrupt for the ClockOut
> +	 * feature, so that the interrupt handler can send the PPS
> +	 * event when the clock SDP triggers. Clear mask when PPS is
> +	 * disabled
> +	 */
> +	if (rq->type != PTP_CLK_REQ_PEROUT || !wx->ptp_setup_sdp)
> +		return -EOPNOTSUPP;
> +
> +	/* Reject requests with unsupported flags */
> +	if (rq->perout.flags & ~PTP_PEROUT_PHASE)
> +		return -EOPNOTSUPP;
> +
> +	if (rq->perout.phase.sec || rq->perout.phase.nsec) {
> +		wx_err(wx, "Absolute start time not supported.\n");
> +		return -EINVAL;
> +	}
> +
> +	if (on)
> +		set_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
> +	else
> +		clear_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
> +
> +	wx->pps_width = rq->perout.period.nsec;

This is still wrong.

perout.period specifies the *period* not the pulse width.

Thanks,
Richard

^ permalink raw reply	[flat|nested] 12+ messages in thread

* RE: [PATCH net-next v4 4/4] net: ngbe: Add support for 1PPS and TOD
  2025-01-14 16:24   ` Richard Cochran
@ 2025-01-16  6:24     ` Jiawen Wu
  2025-01-16 17:23       ` Jacob Keller
  0 siblings, 1 reply; 12+ messages in thread
From: Jiawen Wu @ 2025-01-16  6:24 UTC (permalink / raw)
  To: 'Richard Cochran'
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, horms,
	jacob.e.keller, netdev, vadim.fedorenko, mengyuanlou

On Wed, Jan 15, 2025 12:25 AM, Richard Cochran wrote:
> On Tue, Jan 14, 2025 at 04:44:25PM +0800, Jiawen Wu wrote:
> > +static int wx_ptp_feature_enable(struct ptp_clock_info *ptp,
> > +				 struct ptp_clock_request *rq, int on)
> > +{
> > +	struct wx *wx = container_of(ptp, struct wx, ptp_caps);
> > +
> > +	/**
> > +	 * When PPS is enabled, unmask the interrupt for the ClockOut
> > +	 * feature, so that the interrupt handler can send the PPS
> > +	 * event when the clock SDP triggers. Clear mask when PPS is
> > +	 * disabled
> > +	 */
> > +	if (rq->type != PTP_CLK_REQ_PEROUT || !wx->ptp_setup_sdp)
> > +		return -EOPNOTSUPP;
> > +
> > +	/* Reject requests with unsupported flags */
> > +	if (rq->perout.flags & ~PTP_PEROUT_PHASE)
> > +		return -EOPNOTSUPP;
> > +
> > +	if (rq->perout.phase.sec || rq->perout.phase.nsec) {
> > +		wx_err(wx, "Absolute start time not supported.\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (on)
> > +		set_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
> > +	else
> > +		clear_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
> > +
> > +	wx->pps_width = rq->perout.period.nsec;
> 
> This is still wrong.
> 
> perout.period specifies the *period* not the pulse width.

Thanks for the guidance. But what I'm really confused about is how
do I get the duty cycle ("on" in struct ptp_perout_request).
I try this:
	echo "0 0 0 1 0" > /sys/class/ptp/ptp0/period
to pass the period 1s for 1pps. Then where should the duty cycle
values put? Seems "rq->perout.flags & PTP_PEROUT_DUTY_CYCLE"
always be false.

+       /* Reject requests with unsupported flags */
+       if (rq->perout.flags & ~(PTP_PEROUT_DUTY_CYCLE |
+                                PTP_PEROUT_PHASE))
+               return -EOPNOTSUPP;
+
+       if (rq->perout.phase.sec || rq->perout.phase.nsec) {
+               wx_err(wx, "Absolute start time not supported.\n");
+               return -EINVAL;
+       }
+
+       if (rq->perout.period.sec != 1 || rq->perout.period.nsec) {
+               wx_err(wx, "Only 1pps is supported.\n");
+               return -EINVAL;
+       }
+
+       if (rq->perout.flags & PTP_PEROUT_DUTY_CYCLE) {
+               struct timespec64 ts_on;
+
+               ts_on.tv_sec = rq->perout.on.sec;
+               ts_on.tv_nsec = rq->perout.on.nsec;
+               wx->pps_width = timespec64_to_ns(&ts_on);
+       } else {
+               wx->pps_width = 120000000;
+       }



^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH net-next v4 4/4] net: ngbe: Add support for 1PPS and TOD
  2025-01-16  6:24     ` Jiawen Wu
@ 2025-01-16 17:23       ` Jacob Keller
  2025-01-17  6:36         ` Richard Cochran
  0 siblings, 1 reply; 12+ messages in thread
From: Jacob Keller @ 2025-01-16 17:23 UTC (permalink / raw)
  To: Jiawen Wu, 'Richard Cochran'
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, horms,
	netdev, vadim.fedorenko, mengyuanlou



On 1/15/2025 10:24 PM, Jiawen Wu wrote:
> On Wed, Jan 15, 2025 12:25 AM, Richard Cochran wrote:
>> perout.period specifies the *period* not the pulse width.
> 
> Thanks for the guidance. But what I'm really confused about is how
> do I get the duty cycle ("on" in struct ptp_perout_request).
> I try this:
> 	echo "0 0 0 1 0" > /sys/class/ptp/ptp0/period
> to pass the period 1s for 1pps. Then where should the duty cycle
> values put? Seems "rq->perout.flags & PTP_PEROUT_DUTY_CYCLE"
> always be false.
> 

The sysfs interface doesn't expose the full support for the
PTP_PEROUT_REQUEST ioctl.

It only supports setting the period and the start time. The other
features were added later, and the sysfs interface was never extended.

To use the full support, you need to issue the PTP_PEROUT_REQUEST2
ioctl, for example from a C program. Something like the following might
help you on the right path:

> #include <linux/ptp_clock.h>
> #include <sys/ioctl.h>
> #include <time.h>
> 
> int main(void)
> {
> 	struct ptp_perout_request perout_request = {};
> 	int fd, err;
> 	
> 	/* use the appropriate device for your clock */
> 	fd = open("/dev/ptp0", O_RDWR);
> 	
> 	/* fill in perout_request as desired */
> 	
> 	err = ioctl(fd, PTP_PEROUT_REQUEST2, &perout_request);
> 	if (err) {
> 		...
> 	}
> }

Thanks,
Jake

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH net-next v4 4/4] net: ngbe: Add support for 1PPS and TOD
  2025-01-16 17:23       ` Jacob Keller
@ 2025-01-17  6:36         ` Richard Cochran
  0 siblings, 0 replies; 12+ messages in thread
From: Richard Cochran @ 2025-01-17  6:36 UTC (permalink / raw)
  To: Jacob Keller
  Cc: Jiawen Wu, andrew+netdev, davem, edumazet, kuba, pabeni, linux,
	horms, netdev, vadim.fedorenko, mengyuanlou

On Thu, Jan 16, 2025 at 09:23:10AM -0800, Jacob Keller wrote:
> The sysfs interface doesn't expose the full support for the
> PTP_PEROUT_REQUEST ioctl.
> 
> It only supports setting the period and the start time. The other
> features were added later, and the sysfs interface was never extended.

Right.
 
> To use the full support, you need to issue the PTP_PEROUT_REQUEST2
> ioctl, for example from a C program. Something like the following might
> help you on the right path:

Actually, there is no need to write a program, because you can use
linux/tools/testing/selftests/ptp/testptp.c

It has command line options to set the pulse width and phase:

 " -p val     enable output with a period of 'val' nanoseconds\n"
 " -H val     set output phase to 'val' nanoseconds (requires -p)\n"
 " -w val     set output pulse width to 'val' nanoseconds (requires -p)\n"

Thanks,
Richard

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2025-01-17  6:36 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-01-14  8:44 [PATCH net-next v4 0/4] Support PTP clock for Wangxun NICs Jiawen Wu
2025-01-14  8:44 ` [PATCH net-next v4 1/4] net: wangxun: Add support for PTP clock Jiawen Wu
2025-01-14  9:27   ` Vadim Fedorenko
2025-01-14 10:28   ` Vadim Fedorenko
2025-01-14  8:44 ` [PATCH net-next v4 2/4] net: wangxun: Support to get ts info Jiawen Wu
2025-01-14  8:44 ` [PATCH net-next v4 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info Jiawen Wu
2025-01-14  9:31   ` Vadim Fedorenko
2025-01-14  8:44 ` [PATCH net-next v4 4/4] net: ngbe: Add support for 1PPS and TOD Jiawen Wu
2025-01-14 16:24   ` Richard Cochran
2025-01-16  6:24     ` Jiawen Wu
2025-01-16 17:23       ` Jacob Keller
2025-01-17  6:36         ` Richard Cochran

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).