* [PATCH net-next v2 0/4] Support PTP clock for Wangxun NICs
@ 2025-01-06 8:45 Jiawen Wu
2025-01-06 8:45 ` [PATCH net-next v2 1/4] net: wangxun: Add support for PTP clock Jiawen Wu
` (3 more replies)
0 siblings, 4 replies; 17+ messages in thread
From: Jiawen Wu @ 2025-01-06 8:45 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 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: Implement 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 | 38 +
.../net/ethernet/wangxun/libwx/wx_ethtool.h | 2 +
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 | 49 +-
drivers/net/ethernet/wangxun/libwx/wx_ptp.c | 1019 +++++++++++++++++
drivers/net/ethernet/wangxun/libwx/wx_ptp.h | 20 +
drivers/net/ethernet/wangxun/libwx/wx_type.h | 103 ++
.../net/ethernet/wangxun/ngbe/ngbe_ethtool.c | 1 +
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 | 1 +
.../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] 17+ messages in thread
* [PATCH net-next v2 1/4] net: wangxun: Add support for PTP clock
2025-01-06 8:45 [PATCH net-next v2 0/4] Support PTP clock for Wangxun NICs Jiawen Wu
@ 2025-01-06 8:45 ` Jiawen Wu
2025-01-06 10:35 ` Vadim Fedorenko
2025-01-06 8:45 ` [PATCH net-next v2 2/4] net: wangxun: Implement get_ts_info Jiawen Wu
` (2 subsequent siblings)
3 siblings, 1 reply; 17+ messages in thread
From: Jiawen Wu @ 2025-01-06 8:45 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 | 49 +-
drivers/net/ethernet/wangxun/libwx/wx_ptp.c | 711 ++++++++++++++++++
drivers/net/ethernet/wangxun/libwx/wx_ptp.h | 19 +
drivers/net/ethernet/wangxun/libwx/wx_type.h | 67 ++
drivers/net/ethernet/wangxun/ngbe/ngbe_main.c | 8 +
drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c | 10 +
.../net/ethernet/wangxun/txgbe/txgbe_main.c | 11 +
.../net/ethernet/wangxun/txgbe/txgbe_phy.c | 9 +
10 files changed, 883 insertions(+), 6 deletions(-)
create mode 100644 drivers/net/ethernet/wangxun/libwx/wx_ptp.c
create mode 100644 drivers/net/ethernet/wangxun/libwx/wx_ptp.h
diff --git a/drivers/net/ethernet/wangxun/libwx/Makefile b/drivers/net/ethernet/wangxun/libwx/Makefile
index 42ccd6e4052e..e9f0f1f2309b 100644
--- a/drivers/net/ethernet/wangxun/libwx/Makefile
+++ b/drivers/net/ethernet/wangxun/libwx/Makefile
@@ -4,4 +4,4 @@
obj-$(CONFIG_LIBWX) += libwx.o
-libwx-objs := wx_hw.o wx_lib.o wx_ethtool.o
+libwx-objs := wx_hw.o wx_lib.o wx_ethtool.o wx_ptp.o
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
index abe5921dde02..c4b3b00b0926 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
@@ -41,6 +41,9 @@ static const struct wx_stats wx_gstrings_stats[] = {
WX_STAT("rx_csum_offload_good_count", hw_csum_rx_good),
WX_STAT("rx_csum_offload_errors", hw_csum_rx_error),
WX_STAT("alloc_rx_buff_failed", alloc_rx_buff_failed),
+ WX_STAT("tx_hwtstamp_timeouts", tx_hwtstamp_timeouts),
+ WX_STAT("tx_hwtstamp_skipped", tx_hwtstamp_skipped),
+ WX_STAT("rx_hwtstamp_cleared", rx_hwtstamp_cleared),
};
static const struct wx_stats wx_gstrings_fdir_stats[] = {
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
index 2b3d6586f44a..2c266aee667e 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
@@ -13,6 +13,7 @@
#include "wx_type.h"
#include "wx_lib.h"
+#include "wx_ptp.h"
#include "wx_hw.h"
/* Lookup table mapping the HW PTYPE to the bit field for decoding */
@@ -597,8 +598,17 @@ static void wx_process_skb_fields(struct wx_ring *rx_ring,
union wx_rx_desc *rx_desc,
struct sk_buff *skb)
{
+ struct wx *wx = netdev_priv(rx_ring->netdev);
+
wx_rx_hash(rx_ring, rx_desc, skb);
wx_rx_checksum(rx_ring, rx_desc, skb);
+
+ if (unlikely(test_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, wx->flags)) &&
+ unlikely(wx_test_staterr(rx_desc, WX_RXD_STAT_TS))) {
+ wx_ptp_rx_hwtstamp(rx_ring->q_vector->wx, skb);
+ rx_ring->last_rx_timestamp = jiffies;
+ }
+
wx_rx_vlan(rx_ring, rx_desc, skb);
skb_record_rx_queue(skb, rx_ring->queue_index);
skb->protocol = eth_type_trans(skb, rx_ring->netdev);
@@ -932,9 +942,9 @@ static void wx_tx_olinfo_status(union wx_tx_desc *tx_desc,
tx_desc->read.olinfo_status = cpu_to_le32(olinfo_status);
}
-static void wx_tx_map(struct wx_ring *tx_ring,
- struct wx_tx_buffer *first,
- const u8 hdr_len)
+static int wx_tx_map(struct wx_ring *tx_ring,
+ struct wx_tx_buffer *first,
+ const u8 hdr_len)
{
struct sk_buff *skb = first->skb;
struct wx_tx_buffer *tx_buffer;
@@ -1013,6 +1023,8 @@ static void wx_tx_map(struct wx_ring *tx_ring,
netdev_tx_sent_queue(wx_txring_txq(tx_ring), first->bytecount);
+ /* set the timestamp */
+ first->time_stamp = jiffies;
skb_tx_timestamp(skb);
/* Force memory writes to complete before letting h/w know there
@@ -1038,7 +1050,7 @@ static void wx_tx_map(struct wx_ring *tx_ring,
if (netif_xmit_stopped(wx_txring_txq(tx_ring)) || !netdev_xmit_more())
writel(i, tx_ring->tail);
- return;
+ return 0;
dma_error:
dev_err(tx_ring->dev, "TX DMA map failed\n");
@@ -1062,6 +1074,8 @@ static void wx_tx_map(struct wx_ring *tx_ring,
first->skb = NULL;
tx_ring->next_to_use = i;
+
+ return -ENOMEM;
}
static void wx_tx_ctxtdesc(struct wx_ring *tx_ring, u32 vlan_macip_lens,
@@ -1486,6 +1500,23 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb,
tx_flags |= WX_TX_FLAGS_HW_VLAN;
}
+ if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) &&
+ wx->ptp_clock) {
+ if (wx->tstamp_config.tx_type == HWTSTAMP_TX_ON &&
+ !test_and_set_bit_lock(WX_STATE_PTP_TX_IN_PROGRESS,
+ wx->state)) {
+ skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+ tx_flags |= WX_TX_FLAGS_TSTAMP;
+
+ /* schedule check for Tx timestamp */
+ wx->ptp_tx_skb = skb_get(skb);
+ wx->ptp_tx_start = jiffies;
+ schedule_work(&wx->ptp_tx_work);
+ } else {
+ wx->tx_hwtstamp_skipped++;
+ }
+ }
+
/* record initial flags and protocol */
first->tx_flags = tx_flags;
first->protocol = vlan_get_protocol(skb);
@@ -1501,12 +1532,20 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb,
if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags) && tx_ring->atr_sample_rate)
wx->atr(tx_ring, first, ptype);
- wx_tx_map(tx_ring, first, hdr_len);
+ if (wx_tx_map(tx_ring, first, hdr_len))
+ goto cleanup_tx_tstamp;
return NETDEV_TX_OK;
out_drop:
dev_kfree_skb_any(first->skb);
first->skb = NULL;
+cleanup_tx_tstamp:
+ if (unlikely(tx_flags & WX_TX_FLAGS_TSTAMP)) {
+ dev_kfree_skb_any(wx->ptp_tx_skb);
+ wx->ptp_tx_skb = NULL;
+ cancel_work_sync(&wx->ptp_tx_work);
+ clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state);
+ }
return NETDEV_TX_OK;
}
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
new file mode 100644
index 000000000000..0f683e576b29
--- /dev/null
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
@@ -0,0 +1,711 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
+
+#include <linux/ptp_classify.h>
+#include <linux/clocksource.h>
+#include <linux/pci.h>
+
+#include "wx_type.h"
+#include "wx_ptp.h"
+#include "wx_hw.h"
+
+#define WX_INCVAL_10GB 0xCCCCCC
+#define WX_INCVAL_1GB 0x800000
+#define WX_INCVAL_100 0xA00000
+#define WX_INCVAL_10 0xC7F380
+#define WX_INCVAL_EM 0x2000000
+
+#define WX_INCVAL_SHIFT_10GB 20
+#define WX_INCVAL_SHIFT_1GB 18
+#define WX_INCVAL_SHIFT_100 15
+#define WX_INCVAL_SHIFT_10 12
+#define WX_INCVAL_SHIFT_EM 22
+
+#define WX_OVERFLOW_PERIOD (HZ * 30)
+#define WX_PTP_TX_TIMEOUT (HZ)
+
+#define WX_1588_PPS_WIDTH_EM 120
+
+#define WX_NS_PER_SEC 1000000000ULL
+#define WX_NS_PER_MSEC 1000000ULL
+
+/**
+ * wx_ptp_adjfine
+ * @ptp: the ptp clock structure
+ * @ppb: parts per billion adjustment from base
+ * Returns 0 on success
+ *
+ * Adjust the frequency of the ptp cycle counter by the
+ * indicated ppb from the base frequency.
+ */
+static int wx_ptp_adjfine(struct ptp_clock_info *ptp, long ppb)
+{
+ struct wx *wx = container_of(ptp, struct wx, ptp_caps);
+ u64 incval, mask;
+
+ smp_mb(); /* Force any pending update before accessing. */
+ incval = READ_ONCE(wx->base_incval);
+ incval = adjust_by_scaled_ppm(incval, ppb);
+
+ mask = (wx->mac.type == wx_mac_em) ? 0x7FFFFFF : 0xFFFFFF;
+ if (incval > mask)
+ dev_warn(&wx->pdev->dev,
+ "PTP ppb adjusted SYSTIME rate overflowed!\n");
+
+ incval &= mask;
+ if (wx->mac.type != wx_mac_em)
+ incval |= 2 << 24;
+
+ wr32ptp(wx, WX_TSC_1588_INC, incval);
+
+ return 0;
+}
+
+/**
+ * wx_ptp_adjtime
+ * @ptp: the ptp clock structure
+ * @delta: offset to adjust the cycle counter by ns
+ * Returns 0 on success
+ *
+ * Adjust the timer by resetting the timecounter structure.
+ */
+static int wx_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+ struct wx *wx = container_of(ptp, struct wx, ptp_caps);
+ unsigned long flags;
+
+ spin_lock_irqsave(&wx->tmreg_lock, flags);
+ timecounter_adjtime(&wx->hw_tc, delta);
+ spin_unlock_irqrestore(&wx->tmreg_lock, flags);
+
+ return 0;
+}
+
+/**
+ * wx_ptp_gettimex64
+ * @ptp: the ptp clock structure
+ * @ts: timespec structure to hold the current time value
+ * @sts: structure to hold the system time before and after reading the PHC
+ * Returns 0 on success
+ *
+ * Read the timecounter and return the correct value on ns,
+ * after converting it into a struct timespec64.
+ */
+static int wx_ptp_gettimex64(struct ptp_clock_info *ptp,
+ struct timespec64 *ts,
+ struct ptp_system_timestamp *sts)
+{
+ struct wx *wx = container_of(ptp, struct wx, ptp_caps);
+ unsigned long flags;
+ u64 ns, stamp;
+
+ spin_lock_irqsave(&wx->tmreg_lock, flags);
+
+ ptp_read_system_prets(sts);
+ stamp = (u64)rd32ptp(wx, WX_TSC_1588_SYSTIML);
+ ptp_read_system_postts(sts);
+ stamp |= (u64)rd32ptp(wx, WX_TSC_1588_SYSTIMH) << 32;
+ ns = timecounter_cyc2time(&wx->hw_tc, stamp);
+
+ spin_unlock_irqrestore(&wx->tmreg_lock, flags);
+
+ *ts = ns_to_timespec64(ns);
+
+ return 0;
+}
+
+/**
+ * wx_ptp_settime64
+ * @ptp: the ptp clock structure
+ * @ts: the timespec64 containing the new time for the cycle counter
+ * Returns 0 on success
+ *
+ * Reset the timecounter to use a new base value instead of the kernel
+ * wall timer value.
+ */
+static int wx_ptp_settime64(struct ptp_clock_info *ptp,
+ const struct timespec64 *ts)
+{
+ struct wx *wx = container_of(ptp, struct wx, ptp_caps);
+ unsigned long flags;
+ u64 ns;
+
+ ns = timespec64_to_ns(ts);
+
+ /* reset the timecounter */
+ spin_lock_irqsave(&wx->tmreg_lock, flags);
+ timecounter_init(&wx->hw_tc, &wx->hw_cc, ns);
+ spin_unlock_irqrestore(&wx->tmreg_lock, flags);
+
+ return 0;
+}
+
+/**
+ * wx_ptp_clear_tx_timestamp - utility function to clear Tx timestamp state
+ * @wx: the private board structure
+ *
+ * This function should be called whenever the state related to a Tx timestamp
+ * needs to be cleared. This helps ensure that all related bits are reset for
+ * the next Tx timestamp event.
+ */
+static void wx_ptp_clear_tx_timestamp(struct wx *wx)
+{
+ rd32ptp(wx, WX_TSC_1588_STMPH);
+ if (wx->ptp_tx_skb) {
+ dev_kfree_skb_any(wx->ptp_tx_skb);
+ wx->ptp_tx_skb = NULL;
+ }
+ clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state);
+}
+
+/**
+ * wx_ptp_convert_to_hwtstamp - convert register value to hw timestamp
+ * @wx: private board structure
+ * @hwtstamp: stack timestamp structure
+ * @timestamp: unsigned 64bit system time value
+ *
+ * We need to convert the adapter's RX/TXSTMP registers into a hwtstamp value
+ * which can be used by the stack's ptp functions.
+ *
+ * The lock is used to protect consistency of the cyclecounter and the SYSTIME
+ * registers. However, it does not need to protect against the Rx or Tx
+ * timestamp registers, as there can't be a new timestamp until the old one is
+ * unlatched by reading.
+ *
+ * In addition to the timestamp in hardware, some controllers need a software
+ * overflow cyclecounter, and this function takes this into account as well.
+ **/
+static void wx_ptp_convert_to_hwtstamp(struct wx *wx,
+ struct skb_shared_hwtstamps *hwtstamp,
+ u64 timestamp)
+{
+ unsigned long flags;
+ u64 ns;
+
+ memset(hwtstamp, 0, sizeof(*hwtstamp));
+
+ spin_lock_irqsave(&wx->tmreg_lock, flags);
+ ns = timecounter_cyc2time(&wx->hw_tc, timestamp);
+ spin_unlock_irqrestore(&wx->tmreg_lock, flags);
+
+ hwtstamp->hwtstamp = ns_to_ktime(ns);
+}
+
+/**
+ * wx_ptp_tx_hwtstamp - utility function which checks for TX time stamp
+ * @wx: the private board struct
+ *
+ * if the timestamp is valid, we convert it into the timecounter ns
+ * value, then store that result into the shhwtstamps structure which
+ * is passed up the network stack
+ */
+static void wx_ptp_tx_hwtstamp(struct wx *wx)
+{
+ struct skb_shared_hwtstamps shhwtstamps;
+ struct sk_buff *skb = wx->ptp_tx_skb;
+ u64 regval = 0;
+
+ regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPL);
+ regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPH) << 32;
+
+ wx_ptp_convert_to_hwtstamp(wx, &shhwtstamps, regval);
+
+ wx->ptp_tx_skb = NULL;
+ clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state);
+ skb_tstamp_tx(skb, &shhwtstamps);
+ dev_kfree_skb_any(skb);
+}
+
+/**
+ * wx_ptp_tx_hwtstamp_work
+ * @work: pointer to the work struct
+ *
+ * This work item polls TSC_1588_CTL valid bit to determine when a Tx hardware
+ * timestamp has been taken for the current skb. It is necessary, because the
+ * descriptor's "done" bit does not correlate with the timestamp event.
+ */
+static void wx_ptp_tx_hwtstamp_work(struct work_struct *work)
+{
+ struct wx *wx = container_of(work, struct wx, ptp_tx_work);
+ u32 tsynctxctl;
+ bool timeout;
+
+ /* we have to have a valid skb to poll for a timestamp */
+ if (!wx->ptp_tx_skb) {
+ wx_ptp_clear_tx_timestamp(wx);
+ return;
+ }
+
+ /* stop polling once we have a valid timestamp */
+ tsynctxctl = rd32ptp(wx, WX_TSC_1588_CTL);
+ if (tsynctxctl & WX_TSC_1588_CTL_VALID) {
+ wx_ptp_tx_hwtstamp(wx);
+ return;
+ }
+
+ timeout = time_is_before_jiffies(wx->ptp_tx_start + WX_PTP_TX_TIMEOUT);
+ /* check timeout last in case timestamp event just occurred */
+ if (timeout) {
+ wx_ptp_clear_tx_timestamp(wx);
+ wx->tx_hwtstamp_timeouts++;
+ dev_warn(&wx->pdev->dev, "clearing Tx Timestamp hang\n");
+ } else {
+ /* reschedule to keep checking until we timeout */
+ schedule_work(&wx->ptp_tx_work);
+ }
+}
+
+/**
+ * wx_ptp_create_clock
+ * @wx: the private board structure
+ *
+ * Returns 0 on success, negative value on failure
+ *
+ * This function performs setup of the user entry point function table and
+ * initalizes the PTP clock device used by userspace to access the clock-like
+ * features of the PTP core. It will be called by wx_ptp_init, and may
+ * re-use a previously initialized clock (such as during a suspend/resume
+ * cycle).
+ */
+static long wx_ptp_create_clock(struct wx *wx)
+{
+ struct net_device *netdev = wx->netdev;
+ long err;
+
+ /* do nothing if we already have a clock device */
+ if (!IS_ERR_OR_NULL(wx->ptp_clock))
+ return 0;
+
+ snprintf(wx->ptp_caps.name, sizeof(wx->ptp_caps.name),
+ "%s", netdev->name);
+ wx->ptp_caps.owner = THIS_MODULE;
+ wx->ptp_caps.n_alarm = 0;
+ wx->ptp_caps.n_ext_ts = 0;
+ wx->ptp_caps.n_per_out = 0;
+ wx->ptp_caps.pps = 0;
+ wx->ptp_caps.adjfine = wx_ptp_adjfine;
+ wx->ptp_caps.adjtime = wx_ptp_adjtime;
+ wx->ptp_caps.gettimex64 = wx_ptp_gettimex64;
+ wx->ptp_caps.settime64 = wx_ptp_settime64;
+ wx->ptp_caps.do_aux_work = wx_ptp_do_aux_work;
+ if (wx->mac.type == wx_mac_em)
+ wx->ptp_caps.max_adj = 500000000;
+ else
+ wx->ptp_caps.max_adj = 250000000;
+
+ wx->ptp_clock = ptp_clock_register(&wx->ptp_caps, &wx->pdev->dev);
+ if (IS_ERR(wx->ptp_clock)) {
+ err = PTR_ERR(wx->ptp_clock);
+ wx->ptp_clock = NULL;
+ wx_err(wx, "ptp clock register failed\n");
+ return err;
+ } else if (wx->ptp_clock) {
+ dev_info(&wx->pdev->dev, "registered PHC device on %s\n",
+ netdev->name);
+ }
+
+ /* Set the default timestamp mode to disabled here. We do this in
+ * create_clock instead of initialization, because we don't want to
+ * override the previous settings during a suspend/resume cycle.
+ */
+ wx->tstamp_config.rx_filter = HWTSTAMP_FILTER_NONE;
+ wx->tstamp_config.tx_type = HWTSTAMP_TX_OFF;
+
+ return 0;
+}
+
+/**
+ * wx_ptp_set_timestamp_mode - setup the hardware for the requested mode
+ * @wx: the private board structure
+ * @config: the hwtstamp configuration requested
+ *
+ * Returns 0 on success, negative on failure
+ *
+ * Outgoing time stamping can be enabled and disabled. Play nice and
+ * disable it when requested, although it shouldn't cause any overhead
+ * when no packet needs it. At most one packet in the queue may be
+ * marked for time stamping, otherwise it would be impossible to tell
+ * for sure to which packet the hardware time stamp belongs.
+ *
+ * Incoming time stamping has to be configured via the hardware
+ * filters. Not all combinations are supported, in particular event
+ * type has to be specified. Matching the kind of event packet is
+ * not supported, with the exception of "all V2 events regardless of
+ * level 2 or 4".
+ *
+ * Since hardware always timestamps Path delay packets when timestamping V2
+ * packets, regardless of the type specified in the register, only use V2
+ * Event mode. This more accurately tells the user what the hardware is going
+ * to do anyways.
+ *
+ * Note: this may modify the hwtstamp configuration towards a more general
+ * mode, if required to support the specifically requested mode.
+ */
+static int wx_ptp_set_timestamp_mode(struct wx *wx,
+ struct kernel_hwtstamp_config *config)
+{
+ u32 tsync_tx_ctl = WX_TSC_1588_CTL_ENABLED;
+ u32 tsync_rx_ctl = WX_PSR_1588_CTL_ENABLED;
+ DECLARE_BITMAP(flags, WX_PF_FLAGS_NBITS);
+ u32 tsync_rx_mtrl = PTP_EV_PORT << 16;
+ bool is_l2 = false;
+ u32 regval;
+
+ memcpy(flags, wx->flags, sizeof(wx->flags));
+
+ switch (config->tx_type) {
+ case HWTSTAMP_TX_OFF:
+ tsync_tx_ctl = 0;
+ break;
+ case HWTSTAMP_TX_ON:
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ switch (config->rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ tsync_rx_ctl = 0;
+ tsync_rx_mtrl = 0;
+ clear_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags);
+ clear_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags);
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+ tsync_rx_ctl |= WX_PSR_1588_CTL_TYPE_L4_V1;
+ tsync_rx_mtrl |= WX_PSR_1588_MSG_V1_SYNC;
+ set_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags);
+ set_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags);
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+ tsync_rx_ctl |= WX_PSR_1588_CTL_TYPE_L4_V1;
+ tsync_rx_mtrl |= WX_PSR_1588_MSG_V1_DELAY_REQ;
+ set_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags);
+ set_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags);
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+ tsync_rx_ctl |= WX_PSR_1588_CTL_TYPE_EVENT_V2;
+ is_l2 = true;
+ config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
+ set_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags);
+ set_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags);
+ break;
+ default:
+ /* register RXMTRL must be set in order to do V1 packets,
+ * therefore it is not possible to time stamp both V1 Sync and
+ * Delay_Req messages unless hardware supports timestamping all
+ * packets => return error
+ */
+ clear_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, wx->flags);
+ clear_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, wx->flags);
+ config->rx_filter = HWTSTAMP_FILTER_NONE;
+ return -ERANGE;
+ }
+
+ /* define ethertype filter for timestamping L2 packets */
+ if (is_l2)
+ wr32(wx, WX_PSR_ETYPE_SWC(WX_PSR_ETYPE_SWC_FILTER_1588),
+ (WX_PSR_ETYPE_SWC_FILTER_EN | /* enable filter */
+ WX_PSR_ETYPE_SWC_1588 | /* enable timestamping */
+ ETH_P_1588)); /* 1588 eth protocol type */
+ else
+ wr32(wx, WX_PSR_ETYPE_SWC(WX_PSR_ETYPE_SWC_FILTER_1588), 0);
+
+ /* enable/disable TX */
+ regval = rd32ptp(wx, WX_TSC_1588_CTL);
+ regval &= ~WX_TSC_1588_CTL_ENABLED;
+ regval |= tsync_tx_ctl;
+ wr32ptp(wx, WX_TSC_1588_CTL, regval);
+
+ /* enable/disable RX */
+ regval = rd32(wx, WX_PSR_1588_CTL);
+ regval &= ~(WX_PSR_1588_CTL_ENABLED | WX_PSR_1588_CTL_TYPE_MASK);
+ regval |= tsync_rx_ctl;
+ wr32(wx, WX_PSR_1588_CTL, regval);
+
+ /* define which PTP packets are time stamped */
+ wr32(wx, WX_PSR_1588_MSG, tsync_rx_mtrl);
+
+ WX_WRITE_FLUSH(wx);
+
+ /* configure adapter flags only when HW is actually configured */
+ memcpy(wx->flags, flags, sizeof(wx->flags));
+
+ /* clear TX/RX timestamp state, just to be sure */
+ wx_ptp_clear_tx_timestamp(wx);
+ rd32(wx, WX_PSR_1588_STMPH);
+
+ return 0;
+}
+
+/**
+ * wx_ptp_read - read raw cycle counter (to be used by time counter)
+ * @hw_cc: the cyclecounter structure
+ *
+ * this function reads the cyclecounter registers and is called by the
+ * cyclecounter structure used to construct a ns counter from the
+ * arbitrary fixed point registers
+ */
+static u64 wx_ptp_read(const struct cyclecounter *hw_cc)
+{
+ struct wx *wx = container_of(hw_cc, struct wx, hw_cc);
+ u64 stamp = 0;
+
+ stamp |= (u64)rd32ptp(wx, WX_TSC_1588_SYSTIML);
+ stamp |= (u64)rd32ptp(wx, WX_TSC_1588_SYSTIMH) << 32;
+
+ return stamp;
+}
+
+static void wx_ptp_link_speed_adjust(struct wx *wx, u32 *shift, u32 *incval)
+{
+ if (wx->mac.type == wx_mac_em) {
+ *shift = WX_INCVAL_SHIFT_EM;
+ *incval = WX_INCVAL_EM;
+ return;
+ }
+
+ switch (wx->speed) {
+ case SPEED_10:
+ *shift = WX_INCVAL_SHIFT_10;
+ *incval = WX_INCVAL_10;
+ break;
+ case SPEED_100:
+ *shift = WX_INCVAL_SHIFT_100;
+ *incval = WX_INCVAL_100;
+ break;
+ case SPEED_1000:
+ *shift = WX_INCVAL_SHIFT_1GB;
+ *incval = WX_INCVAL_1GB;
+ break;
+ case SPEED_10000:
+ default:
+ *shift = WX_INCVAL_SHIFT_10GB;
+ *incval = WX_INCVAL_10GB;
+ break;
+ }
+}
+
+/**
+ * wx_ptp_reset_cyclecounter - create the cycle counter from hw
+ * @wx: pointer to the wx structure
+ *
+ * This function should be called to set the proper values for the TSC_1588_INC
+ * register and tell the cyclecounter structure what the tick rate of SYSTIME
+ * is. It does not directly modify SYSTIME registers or the timecounter
+ * structure. It should be called whenever a new TSC_1588_INC value is
+ * necessary, such as during initialization or when the link speed changes.
+ */
+void wx_ptp_reset_cyclecounter(struct wx *wx)
+{
+ u32 incval = 0, mask = 0;
+ struct cyclecounter cc;
+ unsigned long flags;
+
+ /* For some of the boards below this mask is technically incorrect.
+ * The timestamp mask overflows at approximately 61bits. However the
+ * particular hardware does not overflow on an even bitmask value.
+ * Instead, it overflows due to conversion of upper 32bits billions of
+ * cycles. Timecounters are not really intended for this purpose so
+ * they do not properly function if the overflow point isn't 2^N-1.
+ * However, the actual SYSTIME values in question take ~138 years to
+ * overflow. In practice this means they won't actually overflow. A
+ * proper fix to this problem would require modification of the
+ * timecounter delta calculations.
+ */
+ cc.mask = CLOCKSOURCE_MASK(64);
+ cc.mult = 1;
+ cc.shift = 0;
+
+ cc.read = wx_ptp_read;
+ wx_ptp_link_speed_adjust(wx, &cc.shift, &incval);
+
+ /* update the base incval used to calculate frequency adjustment */
+ WRITE_ONCE(wx->base_incval, incval);
+
+ mask = (wx->mac.type == wx_mac_em) ? 0x7FFFFFF : 0xFFFFFF;
+ incval &= mask;
+ if (wx->mac.type != wx_mac_em)
+ incval |= 2 << 24;
+ wr32ptp(wx, WX_TSC_1588_INC, incval);
+
+ smp_mb(); /* Force the above update. */
+
+ /* need lock to prevent incorrect read while modifying cyclecounter */
+ spin_lock_irqsave(&wx->tmreg_lock, flags);
+ memcpy(&wx->hw_cc, &cc, sizeof(wx->hw_cc));
+ spin_unlock_irqrestore(&wx->tmreg_lock, flags);
+}
+EXPORT_SYMBOL(wx_ptp_reset_cyclecounter);
+
+/**
+ * wx_ptp_reset
+ * @wx: the private board structure
+ *
+ * When the MAC resets, all of the hardware configuration for timesync is
+ * reset. This function should be called to re-enable the device for PTP,
+ * using the last known settings. However, we do lose the current clock time,
+ * so we fallback to resetting it based on the kernel's realtime clock.
+ *
+ * This function will maintain the hwtstamp_config settings, and it retriggers
+ * the SDP output if it's enabled.
+ */
+void wx_ptp_reset(struct wx *wx)
+{
+ unsigned long flags;
+
+ /* reset the hardware timestamping mode */
+ wx_ptp_set_timestamp_mode(wx, &wx->tstamp_config);
+ wx_ptp_reset_cyclecounter(wx);
+
+ wr32ptp(wx, WX_TSC_1588_SYSTIML, 0);
+ wr32ptp(wx, WX_TSC_1588_SYSTIMH, 0);
+ WX_WRITE_FLUSH(wx);
+
+ spin_lock_irqsave(&wx->tmreg_lock, flags);
+ timecounter_init(&wx->hw_tc, &wx->hw_cc,
+ ktime_to_ns(ktime_get_real()));
+ spin_unlock_irqrestore(&wx->tmreg_lock, flags);
+}
+EXPORT_SYMBOL(wx_ptp_reset);
+
+/**
+ * wx_ptp_init
+ * @wx: the private board structure
+ *
+ * This function performs the required steps for enabling ptp
+ * support. If ptp support has already been loaded it simply calls the
+ * cyclecounter init routine and exits.
+ */
+void wx_ptp_init(struct wx *wx)
+{
+ /* Initialize the spin lock first, since the user might call the clock
+ * functions any time after we've initialized the ptp clock device.
+ */
+ spin_lock_init(&wx->tmreg_lock);
+
+ /* obtain a ptp clock device, or re-use an existing device */
+ if (wx_ptp_create_clock(wx))
+ return;
+
+ /* we have a clock, so we can initialize work for timestamps now */
+ INIT_WORK(&wx->ptp_tx_work, wx_ptp_tx_hwtstamp_work);
+
+ wx->tx_hwtstamp_timeouts = 0;
+ wx->tx_hwtstamp_skipped = 0;
+ wx->rx_hwtstamp_cleared = 0;
+ /* reset the ptp related hardware bits */
+ wx_ptp_reset(wx);
+
+ /* enter the WX_STATE_PTP_RUNNING state */
+ set_bit(WX_STATE_PTP_RUNNING, wx->state);
+}
+EXPORT_SYMBOL(wx_ptp_init);
+
+/**
+ * wx_ptp_suspend - stop ptp work items
+ * @wx: pointer to wx struct
+ *
+ * This function suspends ptp activity, and prevents more work from being
+ * generated, but does not destroy the clock device.
+ */
+void wx_ptp_suspend(struct wx *wx)
+{
+ /* leave the WX_STATE_PTP_RUNNING STATE */
+ if (!test_and_clear_bit(WX_STATE_PTP_RUNNING, wx->state))
+ return;
+
+ cancel_work_sync(&wx->ptp_tx_work);
+ wx_ptp_clear_tx_timestamp(wx);
+}
+EXPORT_SYMBOL(wx_ptp_suspend);
+
+/**
+ * wx_ptp_stop - destroy the ptp_clock device
+ * @wx: pointer to wx struct
+ *
+ * Completely destroy the ptp_clock device, and disable all PTP related
+ * features. Intended to be run when the device is being closed.
+ */
+void wx_ptp_stop(struct wx *wx)
+{
+ /* first, suspend ptp activity */
+ wx_ptp_suspend(wx);
+
+ /* now destroy the ptp clock device */
+ if (wx->ptp_clock) {
+ ptp_clock_unregister(wx->ptp_clock);
+ wx->ptp_clock = NULL;
+ dev_info(&wx->pdev->dev, "removed PHC on %s\n", wx->netdev->name);
+ }
+}
+EXPORT_SYMBOL(wx_ptp_stop);
+
+/**
+ * wx_ptp_rx_hwtstamp - utility function which checks for RX time stamp
+ * @wx: pointer to wx struct
+ * @skb: particular skb to send timestamp with
+ *
+ * if the timestamp is valid, we convert it into the timecounter ns
+ * value, then store that result into the shhwtstamps structure which
+ * is passed up the network stack
+ */
+void wx_ptp_rx_hwtstamp(struct wx *wx, struct sk_buff *skb)
+{
+ u64 regval = 0;
+ u32 tsyncrxctl;
+
+ /* Read the tsyncrxctl register afterwards in order to prevent taking an
+ * I/O hit on every packet.
+ */
+ tsyncrxctl = rd32(wx, WX_PSR_1588_CTL);
+ if (!(tsyncrxctl & WX_PSR_1588_CTL_VALID))
+ return;
+
+ regval |= (u64)rd32(wx, WX_PSR_1588_STMPL);
+ regval |= (u64)rd32(wx, WX_PSR_1588_STMPH) << 32;
+
+ wx_ptp_convert_to_hwtstamp(wx, skb_hwtstamps(skb), regval);
+}
+
+int wx_hwtstamp_get(struct net_device *dev,
+ struct kernel_hwtstamp_config *cfg)
+{
+ struct wx *wx = netdev_priv(dev);
+
+ if (!netif_running(dev))
+ return -EINVAL;
+
+ *cfg = wx->tstamp_config;
+
+ return 0;
+}
+EXPORT_SYMBOL(wx_hwtstamp_get);
+
+int wx_hwtstamp_set(struct net_device *dev,
+ struct kernel_hwtstamp_config *cfg,
+ struct netlink_ext_ack *extack)
+{
+ struct wx *wx = netdev_priv(dev);
+ int err;
+
+ if (!netif_running(dev))
+ return -EINVAL;
+
+ err = wx_ptp_set_timestamp_mode(wx, cfg);
+ if (err)
+ return err;
+
+ /* save these settings for future reference */
+ memcpy(&wx->tstamp_config, cfg, sizeof(wx->tstamp_config));
+
+ return 0;
+}
+EXPORT_SYMBOL(wx_hwtstamp_set);
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.h b/drivers/net/ethernet/wangxun/libwx/wx_ptp.h
new file mode 100644
index 000000000000..8742d2797363
--- /dev/null
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2019 - 2025 Beijing WangXun Technology Co., Ltd. */
+
+#ifndef _WX_PTP_H_
+#define _WX_PTP_H_
+
+void wx_ptp_reset_cyclecounter(struct wx *wx);
+void wx_ptp_reset(struct wx *wx);
+void wx_ptp_init(struct wx *wx);
+void wx_ptp_suspend(struct wx *wx);
+void wx_ptp_stop(struct wx *wx);
+void wx_ptp_rx_hwtstamp(struct wx *wx, struct sk_buff *skb);
+int wx_hwtstamp_get(struct net_device *dev,
+ struct kernel_hwtstamp_config *cfg);
+int wx_hwtstamp_set(struct net_device *dev,
+ struct kernel_hwtstamp_config *cfg,
+ struct netlink_ext_ack *extack);
+
+#endif /* _WX_PTP_H_ */
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h
index b54bffda027b..31b11dba6bf5 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
@@ -4,6 +4,8 @@
#ifndef _WX_TYPE_H_
#define _WX_TYPE_H_
+#include <linux/ptp_clock_kernel.h>
+#include <linux/timecounter.h>
#include <linux/bitfield.h>
#include <linux/netdevice.h>
#include <linux/if_vlan.h>
@@ -180,6 +182,23 @@
#define WX_PSR_VLAN_CTL 0x15088
#define WX_PSR_VLAN_CTL_CFIEN BIT(29) /* bit 29 */
#define WX_PSR_VLAN_CTL_VFE BIT(30) /* bit 30 */
+/* EType Queue Filter */
+#define WX_PSR_ETYPE_SWC(_i) (0x15128 + ((_i) * 4))
+#define WX_PSR_ETYPE_SWC_FILTER_1588 3
+#define WX_PSR_ETYPE_SWC_FILTER_EN BIT(31)
+#define WX_PSR_ETYPE_SWC_1588 BIT(30)
+/* 1588 */
+#define WX_PSR_1588_MSG 0x15120
+#define WX_PSR_1588_MSG_V1_SYNC FIELD_PREP(GENMASK(7, 0), 0)
+#define WX_PSR_1588_MSG_V1_DELAY_REQ FIELD_PREP(GENMASK(7, 0), 1)
+#define WX_PSR_1588_STMPL 0x151E8
+#define WX_PSR_1588_STMPH 0x151A4
+#define WX_PSR_1588_CTL 0x15188
+#define WX_PSR_1588_CTL_ENABLED BIT(4)
+#define WX_PSR_1588_CTL_TYPE_MASK GENMASK(3, 1)
+#define WX_PSR_1588_CTL_TYPE_L4_V1 FIELD_PREP(GENMASK(3, 1), 1)
+#define WX_PSR_1588_CTL_TYPE_EVENT_V2 FIELD_PREP(GENMASK(3, 1), 5)
+#define WX_PSR_1588_CTL_VALID BIT(0)
/* mcasst/ucast overflow tbl */
#define WX_PSR_MC_TBL(_i) (0x15200 + ((_i) * 4))
#define WX_PSR_UC_TBL(_i) (0x15400 + ((_i) * 4))
@@ -253,6 +272,15 @@
#define WX_TSC_ST_SECTX_RDY BIT(0)
#define WX_TSC_BUF_AE 0x1D00C
#define WX_TSC_BUF_AE_THR GENMASK(9, 0)
+/* 1588 */
+#define WX_TSC_1588_CTL 0x11F00
+#define WX_TSC_1588_CTL_ENABLED BIT(4)
+#define WX_TSC_1588_CTL_VALID BIT(0)
+#define WX_TSC_1588_STMPL 0x11F04
+#define WX_TSC_1588_STMPH 0x11F08
+#define WX_TSC_1588_SYSTIML 0x11F0C
+#define WX_TSC_1588_SYSTIMH 0x11F10
+#define WX_TSC_1588_INC 0x11F14
/************************************** MNG ********************************/
#define WX_MNG_SWFW_SYNC 0x1E008
@@ -460,6 +488,7 @@ enum WX_MSCA_CMD_value {
#define WX_RXD_STAT_L4CS BIT(7) /* L4 xsum calculated */
#define WX_RXD_STAT_IPCS BIT(8) /* IP xsum calculated */
#define WX_RXD_STAT_OUTERIPCS BIT(10) /* Cloud IP xsum calculated*/
+#define WX_RXD_STAT_TS BIT(14) /* IEEE1588 Time Stamp */
#define WX_RXD_ERR_OUTERIPER BIT(26) /* CRC IP Header error */
#define WX_RXD_ERR_RXE BIT(29) /* Any MAC Error */
@@ -863,6 +892,7 @@ struct wx_tx_context_desc {
*/
struct wx_tx_buffer {
union wx_tx_desc *next_to_watch;
+ unsigned long time_stamp;
struct sk_buff *skb;
unsigned int bytecount;
unsigned short gso_segs;
@@ -924,6 +954,7 @@ struct wx_ring {
unsigned int size; /* length in bytes */
u16 count; /* amount of descriptors */
+ unsigned long last_rx_timestamp;
u8 queue_index; /* needed for multiqueue queue management */
u8 reg_idx; /* holds the special value that gets
@@ -1026,6 +1057,8 @@ struct wx_hw_stats {
enum wx_state {
WX_STATE_RESETTING,
+ WX_STATE_PTP_RUNNING,
+ WX_STATE_PTP_TX_IN_PROGRESS,
WX_STATE_NBITS, /* must be last */
};
@@ -1033,6 +1066,8 @@ enum wx_pf_flags {
WX_FLAG_FDIR_CAPABLE,
WX_FLAG_FDIR_HASH,
WX_FLAG_FDIR_PERFECT,
+ WX_FLAG_RX_HWTSTAMP_ENABLED,
+ WX_FLAG_RX_HWTSTAMP_IN_REGISTER,
WX_PF_FLAGS_NBITS /* must be last */
};
@@ -1133,6 +1168,20 @@ struct wx {
void (*atr)(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype);
void (*configure_fdir)(struct wx *wx);
void (*do_reset)(struct net_device *netdev);
+
+ u32 base_incval;
+ u32 tx_hwtstamp_timeouts;
+ u32 tx_hwtstamp_skipped;
+ u32 rx_hwtstamp_cleared;
+ unsigned long ptp_tx_start;
+ spinlock_t tmreg_lock; /* spinlock for ptp */
+ struct cyclecounter hw_cc;
+ struct timecounter hw_tc;
+ struct ptp_clock *ptp_clock;
+ struct ptp_clock_info ptp_caps;
+ struct kernel_hwtstamp_config tstamp_config;
+ struct work_struct ptp_tx_work;
+ struct sk_buff *ptp_tx_skb;
};
#define WX_INTR_ALL (~0ULL)
@@ -1177,6 +1226,24 @@ rd64(struct wx *wx, u32 reg)
return (lsb | msb << 32);
}
+static inline u32
+rd32ptp(struct wx *wx, u32 reg)
+{
+ if (wx->mac.type == wx_mac_em)
+ return rd32(wx, reg);
+
+ return rd32(wx, reg + 0xB500);
+}
+
+static inline void
+wr32ptp(struct wx *wx, u32 reg, u32 value)
+{
+ if (wx->mac.type == wx_mac_em)
+ return wr32(wx, reg, value);
+
+ return wr32(wx, reg + 0xB500, value);
+}
+
/* On some domestic CPU platforms, sometimes IO is not synchronized with
* flushing memory, here use readl() to flush PCI read and write.
*/
diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
index 53aeae2f884b..c60a96cc3508 100644
--- a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
+++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
@@ -14,6 +14,7 @@
#include "../libwx/wx_type.h"
#include "../libwx/wx_hw.h"
#include "../libwx/wx_lib.h"
+#include "../libwx/wx_ptp.h"
#include "ngbe_type.h"
#include "ngbe_mdio.h"
#include "ngbe_hw.h"
@@ -317,6 +318,8 @@ void ngbe_down(struct wx *wx)
{
phylink_stop(wx->phylink);
ngbe_disable_device(wx);
+ if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
+ wx_ptp_reset(wx);
wx_clean_all_tx_rings(wx);
wx_clean_all_rx_rings(wx);
}
@@ -379,6 +382,8 @@ static int ngbe_open(struct net_device *netdev)
if (err)
goto err_dis_phy;
+ wx_ptp_init(wx);
+
ngbe_up(wx);
return 0;
@@ -407,6 +412,7 @@ static int ngbe_close(struct net_device *netdev)
{
struct wx *wx = netdev_priv(netdev);
+ wx_ptp_stop(wx);
ngbe_down(wx);
wx_free_irq(wx);
wx_free_isb_resources(wx);
@@ -507,6 +513,8 @@ static const struct net_device_ops ngbe_netdev_ops = {
.ndo_get_stats64 = wx_get_stats64,
.ndo_vlan_rx_add_vid = wx_vlan_rx_add_vid,
.ndo_vlan_rx_kill_vid = wx_vlan_rx_kill_vid,
+ .ndo_hwtstamp_set = wx_hwtstamp_set,
+ .ndo_hwtstamp_get = wx_hwtstamp_get,
};
/**
diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
index a5e9b779c44d..c7944e62838a 100644
--- a/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
+++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
@@ -7,6 +7,7 @@
#include <linux/phy.h>
#include "../libwx/wx_type.h"
+#include "../libwx/wx_ptp.h"
#include "../libwx/wx_hw.h"
#include "ngbe_type.h"
#include "ngbe_mdio.h"
@@ -64,6 +65,11 @@ static void ngbe_mac_config(struct phylink_config *config, unsigned int mode,
static void ngbe_mac_link_down(struct phylink_config *config,
unsigned int mode, phy_interface_t interface)
{
+ struct wx *wx = phylink_to_wx(config);
+
+ wx->speed = SPEED_UNKNOWN;
+ if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
+ wx_ptp_reset_cyclecounter(wx);
}
static void ngbe_mac_link_up(struct phylink_config *config,
@@ -103,6 +109,10 @@ static void ngbe_mac_link_up(struct phylink_config *config,
wr32(wx, WX_MAC_PKT_FLT, WX_MAC_PKT_FLT_PR);
reg = rd32(wx, WX_MAC_WDG_TIMEOUT);
wr32(wx, WX_MAC_WDG_TIMEOUT, reg);
+
+ wx->speed = speed;
+ if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
+ wx_ptp_reset_cyclecounter(wx);
}
static const struct phylink_mac_ops ngbe_mac_ops = {
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
index f77450268036..734450af9a43 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
@@ -13,6 +13,7 @@
#include "../libwx/wx_type.h"
#include "../libwx/wx_lib.h"
+#include "../libwx/wx_ptp.h"
#include "../libwx/wx_hw.h"
#include "txgbe_type.h"
#include "txgbe_hw.h"
@@ -116,6 +117,9 @@ static void txgbe_reset(struct wx *wx)
memcpy(old_addr, &wx->mac_table[0].addr, netdev->addr_len);
wx_flush_sw_mac_table(wx);
wx_mac_set_default_filter(wx, old_addr);
+
+ if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
+ wx_ptp_reset(wx);
}
static void txgbe_disable_device(struct wx *wx)
@@ -176,6 +180,7 @@ void txgbe_down(struct wx *wx)
void txgbe_up(struct wx *wx)
{
wx_configure(wx);
+ wx_ptp_init(wx);
txgbe_up_complete(wx);
}
@@ -321,6 +326,8 @@ static int txgbe_open(struct net_device *netdev)
if (err)
goto err_free_irq;
+ wx_ptp_init(wx);
+
txgbe_up_complete(wx);
return 0;
@@ -344,6 +351,7 @@ static int txgbe_open(struct net_device *netdev)
*/
static void txgbe_close_suspend(struct wx *wx)
{
+ wx_ptp_suspend(wx);
txgbe_disable_device(wx);
wx_free_resources(wx);
}
@@ -363,6 +371,7 @@ static int txgbe_close(struct net_device *netdev)
{
struct wx *wx = netdev_priv(netdev);
+ wx_ptp_stop(wx);
txgbe_down(wx);
wx_free_irq(wx);
wx_free_resources(wx);
@@ -479,6 +488,8 @@ static const struct net_device_ops txgbe_netdev_ops = {
.ndo_get_stats64 = wx_get_stats64,
.ndo_vlan_rx_add_vid = wx_vlan_rx_add_vid,
.ndo_vlan_rx_kill_vid = wx_vlan_rx_kill_vid,
+ .ndo_hwtstamp_set = wx_hwtstamp_set,
+ .ndo_hwtstamp_get = wx_hwtstamp_get,
};
/**
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
index 1ae68f94dd49..60e5f3288ad8 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
@@ -15,6 +15,7 @@
#include "../libwx/wx_type.h"
#include "../libwx/wx_lib.h"
+#include "../libwx/wx_ptp.h"
#include "../libwx/wx_hw.h"
#include "txgbe_type.h"
#include "txgbe_phy.h"
@@ -179,6 +180,10 @@ static void txgbe_mac_link_down(struct phylink_config *config,
struct wx *wx = phylink_to_wx(config);
wr32m(wx, WX_MAC_TX_CFG, WX_MAC_TX_CFG_TE, 0);
+
+ wx->speed = SPEED_UNKNOWN;
+ if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
+ wx_ptp_reset_cyclecounter(wx);
}
static void txgbe_mac_link_up(struct phylink_config *config,
@@ -215,6 +220,10 @@ static void txgbe_mac_link_up(struct phylink_config *config,
wr32(wx, WX_MAC_PKT_FLT, WX_MAC_PKT_FLT_PR);
wdg = rd32(wx, WX_MAC_WDG_TIMEOUT);
wr32(wx, WX_MAC_WDG_TIMEOUT, wdg);
+
+ wx->speed = speed;
+ if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
+ wx_ptp_reset_cyclecounter(wx);
}
static int txgbe_mac_prepare(struct phylink_config *config, unsigned int mode,
--
2.27.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v2 2/4] net: wangxun: Implement get_ts_info
2025-01-06 8:45 [PATCH net-next v2 0/4] Support PTP clock for Wangxun NICs Jiawen Wu
2025-01-06 8:45 ` [PATCH net-next v2 1/4] net: wangxun: Add support for PTP clock Jiawen Wu
@ 2025-01-06 8:45 ` Jiawen Wu
2025-01-06 10:28 ` Vadim Fedorenko
2025-01-06 8:45 ` [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info Jiawen Wu
2025-01-06 8:45 ` [PATCH net-next v2 4/4] net: ngbe: Add support for 1PPS and TOD Jiawen Wu
3 siblings, 1 reply; 17+ messages in thread
From: Jiawen Wu @ 2025-01-06 8:45 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 in ethtool_ops which is needed to get
the HW capabilities for timestamping.
Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
---
.../net/ethernet/wangxun/libwx/wx_ethtool.c | 35 +++++++++++++++++++
.../net/ethernet/wangxun/libwx/wx_ethtool.h | 2 ++
.../net/ethernet/wangxun/ngbe/ngbe_ethtool.c | 1 +
.../ethernet/wangxun/txgbe/txgbe_ethtool.c | 1 +
4 files changed, 39 insertions(+)
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
index c4b3b00b0926..27e6643509f6 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
@@ -455,3 +455,38 @@ 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);
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.h b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.h
index 600c3b597d1a..7c3630e3e187 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.h
@@ -40,4 +40,6 @@ 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);
#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..9270cf8e5bc7 100644
--- a/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c
+++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c
@@ -138,6 +138,7 @@ 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,
};
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..9f8df5b3aee0 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
@@ -529,6 +529,7 @@ 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,
};
void txgbe_set_ethtool_ops(struct net_device *netdev)
--
2.27.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info
2025-01-06 8:45 [PATCH net-next v2 0/4] Support PTP clock for Wangxun NICs Jiawen Wu
2025-01-06 8:45 ` [PATCH net-next v2 1/4] net: wangxun: Add support for PTP clock Jiawen Wu
2025-01-06 8:45 ` [PATCH net-next v2 2/4] net: wangxun: Implement get_ts_info Jiawen Wu
@ 2025-01-06 8:45 ` Jiawen Wu
2025-01-06 10:36 ` Vadim Fedorenko
2025-01-06 15:16 ` Richard Cochran
2025-01-06 8:45 ` [PATCH net-next v2 4/4] net: ngbe: Add support for 1PPS and TOD Jiawen Wu
3 siblings, 2 replies; 17+ messages in thread
From: Jiawen Wu @ 2025-01-06 8:45 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 | 211 ++++++++++++++++++
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, 215 insertions(+)
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
index 0f683e576b29..0071ba929738 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
@@ -255,6 +255,215 @@ 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 */
+ spin_lock_irqsave(&wx->tmreg_lock, flags);
+ timecounter_read(&wx->hw_tc);
+ spin_unlock_irqrestore(&wx->tmreg_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 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_PPS || !wx->ptp_setup_sdp)
+ return -EOPNOTSUPP;
+
+ if (on)
+ set_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
+ else
+ clear_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
+
+ wx->ptp_setup_sdp(wx);
+
+ return 0;
+}
+
+/**
+ * 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)
+{
+ struct cyclecounter *cc = &wx->hw_cc;
+ u32 tsauxc, rem, int_status;
+ u32 trgttiml0, trgttimh0;
+ u32 trgttiml1, trgttimh1;
+ unsigned long flags;
+ u64 ns = 0;
+
+ /* 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);
+
+ 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;
+
+ /* Read the current clock time, and save the cycle counter value */
+ spin_lock_irqsave(&wx->tmreg_lock, flags);
+ ns = timecounter_read(&wx->hw_tc);
+ wx->pps_edge_start = wx->hw_tc.cycle_last;
+ spin_unlock_irqrestore(&wx->tmreg_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);
+ trgttiml0 = (u32)wx->pps_edge_start;
+ trgttimh0 = (u32)(wx->pps_edge_start >> 32);
+
+ rem += WX_1588_PPS_WIDTH_EM * WX_NS_PER_MSEC;
+ wx->pps_edge_end += div_u64(((u64)rem << cc->shift), cc->mult);
+ trgttiml1 = (u32)wx->pps_edge_end;
+ trgttimh1 = (u32)(wx->pps_edge_end >> 32);
+
+ wr32ptp(wx, WX_TSC_1588_TRGT_L(0), trgttiml0);
+ wr32ptp(wx, WX_TSC_1588_TRGT_H(0), trgttimh0);
+ wr32ptp(wx, WX_TSC_1588_TRGT_L(1), trgttiml1);
+ wr32ptp(wx, WX_TSC_1588_TRGT_H(1), trgttimh1);
+ 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
@@ -573,6 +782,8 @@ void wx_ptp_reset(struct wx *wx)
timecounter_init(&wx->hw_tc, &wx->hw_cc,
ktime_to_ns(ktime_get_real()));
spin_unlock_irqrestore(&wx->tmreg_lock, flags);
+
+ wx->last_overflow_check = jiffies;
}
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 31b11dba6bf5..1f9ddddea191 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
@@ -1173,6 +1173,8 @@ struct wx {
u32 tx_hwtstamp_timeouts;
u32 tx_hwtstamp_skipped;
u32 rx_hwtstamp_cleared;
+ unsigned long last_overflow_check;
+ unsigned long last_rx_ptp_check;
unsigned long ptp_tx_start;
spinlock_t tmreg_lock; /* spinlock 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] 17+ messages in thread
* [PATCH net-next v2 4/4] net: ngbe: Add support for 1PPS and TOD
2025-01-06 8:45 [PATCH net-next v2 0/4] Support PTP clock for Wangxun NICs Jiawen Wu
` (2 preceding siblings ...)
2025-01-06 8:45 ` [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info Jiawen Wu
@ 2025-01-06 8:45 ` Jiawen Wu
3 siblings, 0 replies; 17+ messages in thread
From: Jiawen Wu @ 2025-01-06 8:45 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 | 103 +++++++++++++++++-
drivers/net/ethernet/wangxun/libwx/wx_ptp.h | 1 +
drivers/net/ethernet/wangxun/libwx/wx_type.h | 34 ++++++
drivers/net/ethernet/wangxun/ngbe/ngbe_main.c | 12 +-
drivers/net/ethernet/wangxun/ngbe/ngbe_type.h | 5 +
7 files changed, 171 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_hw.c b/drivers/net/ethernet/wangxun/libwx/wx_hw.c
index 1bf9c38e4125..6ba69e41faa6 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_hw.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_hw.c
@@ -395,6 +395,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 0071ba929738..42024028b66c 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
@@ -29,6 +29,82 @@
#define WX_NS_PER_SEC 1000000000ULL
#define WX_NS_PER_MSEC 1000000ULL
+static void wx_ptp_setup_sdp(struct wx *wx)
+{
+ struct cyclecounter *cc = &wx->hw_cc;
+ u32 tsauxc, rem, tssdp, tssdp1;
+ u32 trgttiml0, trgttimh0;
+ u32 trgttiml1, trgttimh1;
+ unsigned long flags;
+ u64 ns = 0;
+
+ if (WX_1588_PPS_WIDTH_EM >= WX_NS_PER_SEC) {
+ wx_err(wx, "PTP pps width cannot be longer than 1s!\n");
+ return;
+ }
+
+ /* 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;
+ }
+
+ wx->pps_enabled = true;
+
+ tssdp = WX_TSC_1588_SDP_FUN_SEL_TT0;
+ tssdp |= WX_TSC_1588_SDP_OUT_LEVEL_H;
+ tssdp1 = WX_TSC_1588_SDP_FUN_SEL_TS0;
+ 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;
+
+ /* Read the current clock time, and save the cycle counter value */
+ spin_lock_irqsave(&wx->tmreg_lock, flags);
+ ns = timecounter_read(&wx->hw_tc);
+ wx->pps_edge_start = wx->hw_tc.cycle_last;
+ spin_unlock_irqrestore(&wx->tmreg_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);
+ trgttiml0 = (u32)wx->pps_edge_start;
+ trgttimh0 = (u32)(wx->pps_edge_start >> 32);
+
+ wx_set_pps(wx, wx->pps_enabled, ns + rem, wx->pps_edge_start);
+
+ rem += WX_1588_PPS_WIDTH_EM * WX_NS_PER_MSEC;
+ wx->pps_edge_end += div_u64(((u64)rem << cc->shift), cc->mult);
+ trgttiml1 = (u32)wx->pps_edge_end;
+ trgttimh1 = (u32)(wx->pps_edge_end >> 32);
+
+ wr32ptp(wx, WX_TSC_1588_TRGT_L(0), trgttiml0);
+ wr32ptp(wx, WX_TSC_1588_TRGT_H(0), trgttimh0);
+ wr32ptp(wx, WX_TSC_1588_TRGT_L(1), trgttiml1);
+ wr32ptp(wx, WX_TSC_1588_TRGT_H(1), trgttimh1);
+ wr32ptp(wx, WX_TSC_1588_SDP(0), tssdp);
+ wr32ptp(wx, WX_TSC_1588_SDP(1), tssdp1);
+ 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);
+
+ rem = WX_NS_PER_SEC;
+ /* Adjust the clock edge to align with the next full second. */
+ wx->sec_to_cc = div_u64(((u64)rem << cc->shift), cc->mult);
+}
+
/**
* wx_ptp_adjfine
* @ptp: the ptp clock structure
@@ -78,6 +154,9 @@ static int wx_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
timecounter_adjtime(&wx->hw_tc, delta);
spin_unlock_irqrestore(&wx->tmreg_lock, flags);
+ if (wx->ptp_setup_sdp)
+ wx->ptp_setup_sdp(wx);
+
return 0;
}
@@ -137,6 +216,9 @@ static int wx_ptp_settime64(struct ptp_clock_info *ptp,
timecounter_init(&wx->hw_tc, &wx->hw_cc, ns);
spin_unlock_irqrestore(&wx->tmreg_lock, flags);
+ if (wx->ptp_setup_sdp)
+ wx->ptp_setup_sdp(wx);
+
return 0;
}
@@ -491,16 +573,21 @@ static long wx_ptp_create_clock(struct wx *wx)
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.pps = 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.pps = 0;
+ wx->ptp_setup_sdp = NULL;
+ }
wx->ptp_clock = ptp_clock_register(&wx->ptp_caps, &wx->pdev->dev);
if (IS_ERR(wx->ptp_clock)) {
@@ -784,6 +871,12 @@ void wx_ptp_reset(struct wx *wx)
spin_unlock_irqrestore(&wx->tmreg_lock, flags);
wx->last_overflow_check = jiffies;
+
+ /* 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);
@@ -833,6 +926,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 1f9ddddea191..2c9c22d9491b 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,12 @@ 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);
+ void (*ptp_setup_sdp)(struct wx *wx);
+ bool pps_enabled;
+ u64 pps_edge_start;
+ u64 pps_edge_end;
+ u64 sec_to_cc;
u32 base_incval;
u32 tx_hwtstamp_timeouts;
u32 tx_hwtstamp_skipped;
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] 17+ messages in thread
* Re: [PATCH net-next v2 2/4] net: wangxun: Implement get_ts_info
2025-01-06 8:45 ` [PATCH net-next v2 2/4] net: wangxun: Implement get_ts_info Jiawen Wu
@ 2025-01-06 10:28 ` Vadim Fedorenko
0 siblings, 0 replies; 17+ messages in thread
From: Vadim Fedorenko @ 2025-01-06 10:28 UTC (permalink / raw)
To: Jiawen Wu, andrew+netdev, davem, edumazet, kuba, pabeni,
richardcochran, linux, horms, jacob.e.keller, netdev
Cc: mengyuanlou
On 06/01/2025 08:45, Jiawen Wu wrote:
> Implement the function get_ts_info in ethtool_ops which is needed to get
> the HW capabilities for timestamping.
>
> Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
> ---
> .../net/ethernet/wangxun/libwx/wx_ethtool.c | 35 +++++++++++++++++++
> .../net/ethernet/wangxun/libwx/wx_ethtool.h | 2 ++
> .../net/ethernet/wangxun/ngbe/ngbe_ethtool.c | 1 +
> .../ethernet/wangxun/txgbe/txgbe_ethtool.c | 1 +
> 4 files changed, 39 insertions(+)
>
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
> index c4b3b00b0926..27e6643509f6 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
> @@ -455,3 +455,38 @@ 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);
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.h b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.h
> index 600c3b597d1a..7c3630e3e187 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.h
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.h
> @@ -40,4 +40,6 @@ 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);
> #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..9270cf8e5bc7 100644
> --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c
> +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c
> @@ -138,6 +138,7 @@ 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,
> };
>
> 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..9f8df5b3aee0 100644
> --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
> +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
> @@ -529,6 +529,7 @@ 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,
> };
>
> void txgbe_set_ethtool_ops(struct net_device *netdev)
Reviewed-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v2 1/4] net: wangxun: Add support for PTP clock
2025-01-06 8:45 ` [PATCH net-next v2 1/4] net: wangxun: Add support for PTP clock Jiawen Wu
@ 2025-01-06 10:35 ` Vadim Fedorenko
2025-01-07 2:01 ` Jiawen Wu
0 siblings, 1 reply; 17+ messages in thread
From: Vadim Fedorenko @ 2025-01-06 10:35 UTC (permalink / raw)
To: Jiawen Wu, andrew+netdev, davem, edumazet, kuba, pabeni,
richardcochran, linux, horms, jacob.e.keller, netdev
Cc: mengyuanlou
On 06/01/2025 08:45, 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 | 49 +-
> drivers/net/ethernet/wangxun/libwx/wx_ptp.c | 711 ++++++++++++++++++
> drivers/net/ethernet/wangxun/libwx/wx_ptp.h | 19 +
> drivers/net/ethernet/wangxun/libwx/wx_type.h | 67 ++
> drivers/net/ethernet/wangxun/ngbe/ngbe_main.c | 8 +
> drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c | 10 +
> .../net/ethernet/wangxun/txgbe/txgbe_main.c | 11 +
> .../net/ethernet/wangxun/txgbe/txgbe_phy.c | 9 +
> 10 files changed, 883 insertions(+), 6 deletions(-)
> create mode 100644 drivers/net/ethernet/wangxun/libwx/wx_ptp.c
> create mode 100644 drivers/net/ethernet/wangxun/libwx/wx_ptp.h
>
> diff --git a/drivers/net/ethernet/wangxun/libwx/Makefile b/drivers/net/ethernet/wangxun/libwx/Makefile
> index 42ccd6e4052e..e9f0f1f2309b 100644
> --- a/drivers/net/ethernet/wangxun/libwx/Makefile
> +++ b/drivers/net/ethernet/wangxun/libwx/Makefile
> @@ -4,4 +4,4 @@
>
> obj-$(CONFIG_LIBWX) += libwx.o
>
> -libwx-objs := wx_hw.o wx_lib.o wx_ethtool.o
> +libwx-objs := wx_hw.o wx_lib.o wx_ethtool.o wx_ptp.o
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
> index abe5921dde02..c4b3b00b0926 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
> @@ -41,6 +41,9 @@ static const struct wx_stats wx_gstrings_stats[] = {
> WX_STAT("rx_csum_offload_good_count", hw_csum_rx_good),
> WX_STAT("rx_csum_offload_errors", hw_csum_rx_error),
> WX_STAT("alloc_rx_buff_failed", alloc_rx_buff_failed),
> + WX_STAT("tx_hwtstamp_timeouts", tx_hwtstamp_timeouts),
> + WX_STAT("tx_hwtstamp_skipped", tx_hwtstamp_skipped),
> + WX_STAT("rx_hwtstamp_cleared", rx_hwtstamp_cleared),
> };
>
> static const struct wx_stats wx_gstrings_fdir_stats[] = {
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
> index 2b3d6586f44a..2c266aee667e 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
> @@ -13,6 +13,7 @@
>
> #include "wx_type.h"
> #include "wx_lib.h"
> +#include "wx_ptp.h"
> #include "wx_hw.h"
>
> /* Lookup table mapping the HW PTYPE to the bit field for decoding */
> @@ -597,8 +598,17 @@ static void wx_process_skb_fields(struct wx_ring *rx_ring,
> union wx_rx_desc *rx_desc,
> struct sk_buff *skb)
> {
> + struct wx *wx = netdev_priv(rx_ring->netdev);
> +
> wx_rx_hash(rx_ring, rx_desc, skb);
> wx_rx_checksum(rx_ring, rx_desc, skb);
> +
> + if (unlikely(test_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, wx->flags)) &&
> + unlikely(wx_test_staterr(rx_desc, WX_RXD_STAT_TS))) {
> + wx_ptp_rx_hwtstamp(rx_ring->q_vector->wx, skb);
> + rx_ring->last_rx_timestamp = jiffies;
> + }
> +
> wx_rx_vlan(rx_ring, rx_desc, skb);
> skb_record_rx_queue(skb, rx_ring->queue_index);
> skb->protocol = eth_type_trans(skb, rx_ring->netdev);
> @@ -932,9 +942,9 @@ static void wx_tx_olinfo_status(union wx_tx_desc *tx_desc,
> tx_desc->read.olinfo_status = cpu_to_le32(olinfo_status);
> }
>
> -static void wx_tx_map(struct wx_ring *tx_ring,
> - struct wx_tx_buffer *first,
> - const u8 hdr_len)
> +static int wx_tx_map(struct wx_ring *tx_ring,
> + struct wx_tx_buffer *first,
> + const u8 hdr_len)
> {
> struct sk_buff *skb = first->skb;
> struct wx_tx_buffer *tx_buffer;
> @@ -1013,6 +1023,8 @@ static void wx_tx_map(struct wx_ring *tx_ring,
>
> netdev_tx_sent_queue(wx_txring_txq(tx_ring), first->bytecount);
>
> + /* set the timestamp */
> + first->time_stamp = jiffies;
> skb_tx_timestamp(skb);
>
> /* Force memory writes to complete before letting h/w know there
> @@ -1038,7 +1050,7 @@ static void wx_tx_map(struct wx_ring *tx_ring,
> if (netif_xmit_stopped(wx_txring_txq(tx_ring)) || !netdev_xmit_more())
> writel(i, tx_ring->tail);
>
> - return;
> + return 0;
> dma_error:
> dev_err(tx_ring->dev, "TX DMA map failed\n");
>
> @@ -1062,6 +1074,8 @@ static void wx_tx_map(struct wx_ring *tx_ring,
> first->skb = NULL;
>
> tx_ring->next_to_use = i;
> +
> + return -ENOMEM;
> }
>
> static void wx_tx_ctxtdesc(struct wx_ring *tx_ring, u32 vlan_macip_lens,
> @@ -1486,6 +1500,23 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb,
> tx_flags |= WX_TX_FLAGS_HW_VLAN;
> }
>
> + if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) &&
> + wx->ptp_clock) {
> + if (wx->tstamp_config.tx_type == HWTSTAMP_TX_ON &&
> + !test_and_set_bit_lock(WX_STATE_PTP_TX_IN_PROGRESS,
> + wx->state)) {
> + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
> + tx_flags |= WX_TX_FLAGS_TSTAMP;
> +
> + /* schedule check for Tx timestamp */
> + wx->ptp_tx_skb = skb_get(skb);
> + wx->ptp_tx_start = jiffies;
> + schedule_work(&wx->ptp_tx_work);
> + } else {
> + wx->tx_hwtstamp_skipped++;
> + }
> + }
> +
> /* record initial flags and protocol */
> first->tx_flags = tx_flags;
> first->protocol = vlan_get_protocol(skb);
> @@ -1501,12 +1532,20 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb,
> if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags) && tx_ring->atr_sample_rate)
> wx->atr(tx_ring, first, ptype);
>
> - wx_tx_map(tx_ring, first, hdr_len);
> + if (wx_tx_map(tx_ring, first, hdr_len))
> + goto cleanup_tx_tstamp;
>
> return NETDEV_TX_OK;
> out_drop:
> dev_kfree_skb_any(first->skb);
> first->skb = NULL;
> +cleanup_tx_tstamp:
> + if (unlikely(tx_flags & WX_TX_FLAGS_TSTAMP)) {
> + dev_kfree_skb_any(wx->ptp_tx_skb);
> + wx->ptp_tx_skb = NULL;
> + cancel_work_sync(&wx->ptp_tx_work);
> + clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state);
> + }
>
> return NETDEV_TX_OK;
> }
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
> new file mode 100644
> index 000000000000..0f683e576b29
> --- /dev/null
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
> @@ -0,0 +1,711 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
> +
> +#include <linux/ptp_classify.h>
> +#include <linux/clocksource.h>
> +#include <linux/pci.h>
> +
> +#include "wx_type.h"
> +#include "wx_ptp.h"
> +#include "wx_hw.h"
> +
> +#define WX_INCVAL_10GB 0xCCCCCC
> +#define WX_INCVAL_1GB 0x800000
> +#define WX_INCVAL_100 0xA00000
> +#define WX_INCVAL_10 0xC7F380
> +#define WX_INCVAL_EM 0x2000000
> +
> +#define WX_INCVAL_SHIFT_10GB 20
> +#define WX_INCVAL_SHIFT_1GB 18
> +#define WX_INCVAL_SHIFT_100 15
> +#define WX_INCVAL_SHIFT_10 12
> +#define WX_INCVAL_SHIFT_EM 22
> +
> +#define WX_OVERFLOW_PERIOD (HZ * 30)
> +#define WX_PTP_TX_TIMEOUT (HZ)
> +
> +#define WX_1588_PPS_WIDTH_EM 120
> +
> +#define WX_NS_PER_SEC 1000000000ULL
> +#define WX_NS_PER_MSEC 1000000ULL
> +
> +/**
> + * wx_ptp_adjfine
> + * @ptp: the ptp clock structure
> + * @ppb: parts per billion adjustment from base
> + * Returns 0 on success
> + *
> + * Adjust the frequency of the ptp cycle counter by the
> + * indicated ppb from the base frequency.
> + */
> +static int wx_ptp_adjfine(struct ptp_clock_info *ptp, long ppb)
> +{
> + struct wx *wx = container_of(ptp, struct wx, ptp_caps);
> + u64 incval, mask;
> +
> + smp_mb(); /* Force any pending update before accessing. */
> + incval = READ_ONCE(wx->base_incval);
> + incval = adjust_by_scaled_ppm(incval, ppb);
> +
> + mask = (wx->mac.type == wx_mac_em) ? 0x7FFFFFF : 0xFFFFFF;
> + if (incval > mask)
> + dev_warn(&wx->pdev->dev,
> + "PTP ppb adjusted SYSTIME rate overflowed!\n");
> +
> + incval &= mask;
> + if (wx->mac.type != wx_mac_em)
> + incval |= 2 << 24;
> +
> + wr32ptp(wx, WX_TSC_1588_INC, incval);
> +
> + return 0;
> +}
> +
> +/**
> + * wx_ptp_adjtime
> + * @ptp: the ptp clock structure
> + * @delta: offset to adjust the cycle counter by ns
> + * Returns 0 on success
> + *
> + * Adjust the timer by resetting the timecounter structure.
> + */
> +static int wx_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
> +{
> + struct wx *wx = container_of(ptp, struct wx, ptp_caps);
> + unsigned long flags;
> +
> + spin_lock_irqsave(&wx->tmreg_lock, flags);
> + timecounter_adjtime(&wx->hw_tc, delta);
> + spin_unlock_irqrestore(&wx->tmreg_lock, flags);
> +
> + return 0;
> +}
> +
> +/**
> + * wx_ptp_gettimex64
> + * @ptp: the ptp clock structure
> + * @ts: timespec structure to hold the current time value
> + * @sts: structure to hold the system time before and after reading the PHC
> + * Returns 0 on success
> + *
> + * Read the timecounter and return the correct value on ns,
> + * after converting it into a struct timespec64.
> + */
> +static int wx_ptp_gettimex64(struct ptp_clock_info *ptp,
> + struct timespec64 *ts,
> + struct ptp_system_timestamp *sts)
> +{
> + struct wx *wx = container_of(ptp, struct wx, ptp_caps);
> + unsigned long flags;
> + u64 ns, stamp;
> +
> + spin_lock_irqsave(&wx->tmreg_lock, flags);
> +
> + ptp_read_system_prets(sts);
> + stamp = (u64)rd32ptp(wx, WX_TSC_1588_SYSTIML);
> + ptp_read_system_postts(sts);
> + stamp |= (u64)rd32ptp(wx, WX_TSC_1588_SYSTIMH) << 32;
> + ns = timecounter_cyc2time(&wx->hw_tc, stamp);
> +
> + spin_unlock_irqrestore(&wx->tmreg_lock, flags);
> +
> + *ts = ns_to_timespec64(ns);
> +
> + return 0;
> +}
> +
> +/**
> + * wx_ptp_settime64
> + * @ptp: the ptp clock structure
> + * @ts: the timespec64 containing the new time for the cycle counter
> + * Returns 0 on success
> + *
> + * Reset the timecounter to use a new base value instead of the kernel
> + * wall timer value.
> + */
> +static int wx_ptp_settime64(struct ptp_clock_info *ptp,
> + const struct timespec64 *ts)
> +{
> + struct wx *wx = container_of(ptp, struct wx, ptp_caps);
> + unsigned long flags;
> + u64 ns;
> +
> + ns = timespec64_to_ns(ts);
> +
> + /* reset the timecounter */
> + spin_lock_irqsave(&wx->tmreg_lock, flags);
> + timecounter_init(&wx->hw_tc, &wx->hw_cc, ns);
> + spin_unlock_irqrestore(&wx->tmreg_lock, flags);
> +
> + return 0;
> +}
> +
> +/**
> + * wx_ptp_clear_tx_timestamp - utility function to clear Tx timestamp state
> + * @wx: the private board structure
> + *
> + * This function should be called whenever the state related to a Tx timestamp
> + * needs to be cleared. This helps ensure that all related bits are reset for
> + * the next Tx timestamp event.
> + */
> +static void wx_ptp_clear_tx_timestamp(struct wx *wx)
> +{
> + rd32ptp(wx, WX_TSC_1588_STMPH);
> + if (wx->ptp_tx_skb) {
> + dev_kfree_skb_any(wx->ptp_tx_skb);
> + wx->ptp_tx_skb = NULL;
> + }
> + clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state);
> +}
> +
> +/**
> + * wx_ptp_convert_to_hwtstamp - convert register value to hw timestamp
> + * @wx: private board structure
> + * @hwtstamp: stack timestamp structure
> + * @timestamp: unsigned 64bit system time value
> + *
> + * We need to convert the adapter's RX/TXSTMP registers into a hwtstamp value
> + * which can be used by the stack's ptp functions.
> + *
> + * The lock is used to protect consistency of the cyclecounter and the SYSTIME
> + * registers. However, it does not need to protect against the Rx or Tx
> + * timestamp registers, as there can't be a new timestamp until the old one is
> + * unlatched by reading.
> + *
> + * In addition to the timestamp in hardware, some controllers need a software
> + * overflow cyclecounter, and this function takes this into account as well.
> + **/
> +static void wx_ptp_convert_to_hwtstamp(struct wx *wx,
> + struct skb_shared_hwtstamps *hwtstamp,
> + u64 timestamp)
> +{
> + unsigned long flags;
> + u64 ns;
> +
> + memset(hwtstamp, 0, sizeof(*hwtstamp));
> +
> + spin_lock_irqsave(&wx->tmreg_lock, flags);
> + ns = timecounter_cyc2time(&wx->hw_tc, timestamp);
> + spin_unlock_irqrestore(&wx->tmreg_lock, flags);
> +
> + hwtstamp->hwtstamp = ns_to_ktime(ns);
> +}
> +
> +/**
> + * wx_ptp_tx_hwtstamp - utility function which checks for TX time stamp
> + * @wx: the private board struct
> + *
> + * if the timestamp is valid, we convert it into the timecounter ns
> + * value, then store that result into the shhwtstamps structure which
> + * is passed up the network stack
> + */
> +static void wx_ptp_tx_hwtstamp(struct wx *wx)
> +{
> + struct skb_shared_hwtstamps shhwtstamps;
> + struct sk_buff *skb = wx->ptp_tx_skb;
> + u64 regval = 0;
> +
> + regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPL);
> + regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPH) << 32;
> +
> + wx_ptp_convert_to_hwtstamp(wx, &shhwtstamps, regval);
> +
> + wx->ptp_tx_skb = NULL;
> + clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state);
> + skb_tstamp_tx(skb, &shhwtstamps);
> + dev_kfree_skb_any(skb);
> +}
> +
> +/**
> + * wx_ptp_tx_hwtstamp_work
> + * @work: pointer to the work struct
> + *
> + * This work item polls TSC_1588_CTL valid bit to determine when a Tx hardware
> + * timestamp has been taken for the current skb. It is necessary, because the
> + * descriptor's "done" bit does not correlate with the timestamp event.
> + */
> +static void wx_ptp_tx_hwtstamp_work(struct work_struct *work)
> +{
> + struct wx *wx = container_of(work, struct wx, ptp_tx_work);
> + u32 tsynctxctl;
> + bool timeout;
> +
> + /* we have to have a valid skb to poll for a timestamp */
> + if (!wx->ptp_tx_skb) {
> + wx_ptp_clear_tx_timestamp(wx);
> + return;
> + }
> +
> + /* stop polling once we have a valid timestamp */
> + tsynctxctl = rd32ptp(wx, WX_TSC_1588_CTL);
> + if (tsynctxctl & WX_TSC_1588_CTL_VALID) {
> + wx_ptp_tx_hwtstamp(wx);
> + return;
> + }
> +
> + timeout = time_is_before_jiffies(wx->ptp_tx_start + WX_PTP_TX_TIMEOUT);
> + /* check timeout last in case timestamp event just occurred */
> + if (timeout) {
> + wx_ptp_clear_tx_timestamp(wx);
> + wx->tx_hwtstamp_timeouts++;
> + dev_warn(&wx->pdev->dev, "clearing Tx Timestamp hang\n");
> + } else {
> + /* reschedule to keep checking until we timeout */
> + schedule_work(&wx->ptp_tx_work);
> + }
> +}
> +
> +/**
> + * wx_ptp_create_clock
> + * @wx: the private board structure
> + *
> + * Returns 0 on success, negative value on failure
> + *
> + * This function performs setup of the user entry point function table and
> + * initalizes the PTP clock device used by userspace to access the clock-like
> + * features of the PTP core. It will be called by wx_ptp_init, and may
> + * re-use a previously initialized clock (such as during a suspend/resume
> + * cycle).
> + */
> +static long wx_ptp_create_clock(struct wx *wx)
> +{
> + struct net_device *netdev = wx->netdev;
> + long err;
> +
> + /* do nothing if we already have a clock device */
> + if (!IS_ERR_OR_NULL(wx->ptp_clock))
> + return 0;
> +
> + snprintf(wx->ptp_caps.name, sizeof(wx->ptp_caps.name),
> + "%s", netdev->name);
> + wx->ptp_caps.owner = THIS_MODULE;
> + wx->ptp_caps.n_alarm = 0;
> + wx->ptp_caps.n_ext_ts = 0;
> + wx->ptp_caps.n_per_out = 0;
> + wx->ptp_caps.pps = 0;
> + wx->ptp_caps.adjfine = wx_ptp_adjfine;
> + wx->ptp_caps.adjtime = wx_ptp_adjtime;
> + wx->ptp_caps.gettimex64 = wx_ptp_gettimex64;
> + wx->ptp_caps.settime64 = wx_ptp_settime64;
> + wx->ptp_caps.do_aux_work = wx_ptp_do_aux_work;
wx_ptp_do_aux_work is not defined in this patch, it appears in patch 3
only. did you compile test your code patch by patch?
> + if (wx->mac.type == wx_mac_em)
> + wx->ptp_caps.max_adj = 500000000;
> + else
> + wx->ptp_caps.max_adj = 250000000;
> +
> + wx->ptp_clock = ptp_clock_register(&wx->ptp_caps, &wx->pdev->dev);
> + if (IS_ERR(wx->ptp_clock)) {
> + err = PTR_ERR(wx->ptp_clock);
> + wx->ptp_clock = NULL;
> + wx_err(wx, "ptp clock register failed\n");
> + return err;
> + } else if (wx->ptp_clock) {
> + dev_info(&wx->pdev->dev, "registered PHC device on %s\n",
> + netdev->name);
> + }
> +
> + /* Set the default timestamp mode to disabled here. We do this in
> + * create_clock instead of initialization, because we don't want to
> + * override the previous settings during a suspend/resume cycle.
> + */
> + wx->tstamp_config.rx_filter = HWTSTAMP_FILTER_NONE;
> + wx->tstamp_config.tx_type = HWTSTAMP_TX_OFF;
> +
> + return 0;
> +}
> +
> +/**
> + * wx_ptp_set_timestamp_mode - setup the hardware for the requested mode
> + * @wx: the private board structure
> + * @config: the hwtstamp configuration requested
> + *
> + * Returns 0 on success, negative on failure
> + *
> + * Outgoing time stamping can be enabled and disabled. Play nice and
> + * disable it when requested, although it shouldn't cause any overhead
> + * when no packet needs it. At most one packet in the queue may be
> + * marked for time stamping, otherwise it would be impossible to tell
> + * for sure to which packet the hardware time stamp belongs.
> + *
> + * Incoming time stamping has to be configured via the hardware
> + * filters. Not all combinations are supported, in particular event
> + * type has to be specified. Matching the kind of event packet is
> + * not supported, with the exception of "all V2 events regardless of
> + * level 2 or 4".
> + *
> + * Since hardware always timestamps Path delay packets when timestamping V2
> + * packets, regardless of the type specified in the register, only use V2
> + * Event mode. This more accurately tells the user what the hardware is going
> + * to do anyways.
> + *
> + * Note: this may modify the hwtstamp configuration towards a more general
> + * mode, if required to support the specifically requested mode.
> + */
> +static int wx_ptp_set_timestamp_mode(struct wx *wx,
> + struct kernel_hwtstamp_config *config)
> +{
> + u32 tsync_tx_ctl = WX_TSC_1588_CTL_ENABLED;
> + u32 tsync_rx_ctl = WX_PSR_1588_CTL_ENABLED;
> + DECLARE_BITMAP(flags, WX_PF_FLAGS_NBITS);
> + u32 tsync_rx_mtrl = PTP_EV_PORT << 16;
> + bool is_l2 = false;
> + u32 regval;
> +
> + memcpy(flags, wx->flags, sizeof(wx->flags));
> +
> + switch (config->tx_type) {
> + case HWTSTAMP_TX_OFF:
> + tsync_tx_ctl = 0;
> + break;
> + case HWTSTAMP_TX_ON:
> + break;
> + default:
> + return -ERANGE;
> + }
> +
> + switch (config->rx_filter) {
> + case HWTSTAMP_FILTER_NONE:
> + tsync_rx_ctl = 0;
> + tsync_rx_mtrl = 0;
> + clear_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags);
> + clear_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags);
> + break;
> + case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
> + tsync_rx_ctl |= WX_PSR_1588_CTL_TYPE_L4_V1;
> + tsync_rx_mtrl |= WX_PSR_1588_MSG_V1_SYNC;
> + set_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags);
> + set_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags);
> + break;
> + case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
> + tsync_rx_ctl |= WX_PSR_1588_CTL_TYPE_L4_V1;
> + tsync_rx_mtrl |= WX_PSR_1588_MSG_V1_DELAY_REQ;
> + set_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags);
> + set_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags);
> + break;
> + case HWTSTAMP_FILTER_PTP_V2_EVENT:
> + case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
> + case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
> + case HWTSTAMP_FILTER_PTP_V2_SYNC:
> + case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
> + case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
> + case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
> + case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
> + case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
> + tsync_rx_ctl |= WX_PSR_1588_CTL_TYPE_EVENT_V2;
> + is_l2 = true;
> + config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
> + set_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags);
> + set_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags);
> + break;
> + default:
> + /* register RXMTRL must be set in order to do V1 packets,
> + * therefore it is not possible to time stamp both V1 Sync and
> + * Delay_Req messages unless hardware supports timestamping all
> + * packets => return error
> + */
> + clear_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, wx->flags);
> + clear_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, wx->flags);
> + config->rx_filter = HWTSTAMP_FILTER_NONE;
> + return -ERANGE;
> + }
> +
> + /* define ethertype filter for timestamping L2 packets */
> + if (is_l2)
> + wr32(wx, WX_PSR_ETYPE_SWC(WX_PSR_ETYPE_SWC_FILTER_1588),
> + (WX_PSR_ETYPE_SWC_FILTER_EN | /* enable filter */
> + WX_PSR_ETYPE_SWC_1588 | /* enable timestamping */
> + ETH_P_1588)); /* 1588 eth protocol type */
> + else
> + wr32(wx, WX_PSR_ETYPE_SWC(WX_PSR_ETYPE_SWC_FILTER_1588), 0);
> +
> + /* enable/disable TX */
> + regval = rd32ptp(wx, WX_TSC_1588_CTL);
> + regval &= ~WX_TSC_1588_CTL_ENABLED;
> + regval |= tsync_tx_ctl;
> + wr32ptp(wx, WX_TSC_1588_CTL, regval);
> +
> + /* enable/disable RX */
> + regval = rd32(wx, WX_PSR_1588_CTL);
> + regval &= ~(WX_PSR_1588_CTL_ENABLED | WX_PSR_1588_CTL_TYPE_MASK);
> + regval |= tsync_rx_ctl;
> + wr32(wx, WX_PSR_1588_CTL, regval);
> +
> + /* define which PTP packets are time stamped */
> + wr32(wx, WX_PSR_1588_MSG, tsync_rx_mtrl);
> +
> + WX_WRITE_FLUSH(wx);
> +
> + /* configure adapter flags only when HW is actually configured */
> + memcpy(wx->flags, flags, sizeof(wx->flags));
> +
> + /* clear TX/RX timestamp state, just to be sure */
> + wx_ptp_clear_tx_timestamp(wx);
> + rd32(wx, WX_PSR_1588_STMPH);
> +
> + return 0;
> +}
> +
> +/**
> + * wx_ptp_read - read raw cycle counter (to be used by time counter)
> + * @hw_cc: the cyclecounter structure
> + *
> + * this function reads the cyclecounter registers and is called by the
> + * cyclecounter structure used to construct a ns counter from the
> + * arbitrary fixed point registers
> + */
> +static u64 wx_ptp_read(const struct cyclecounter *hw_cc)
> +{
> + struct wx *wx = container_of(hw_cc, struct wx, hw_cc);
> + u64 stamp = 0;
> +
> + stamp |= (u64)rd32ptp(wx, WX_TSC_1588_SYSTIML);
> + stamp |= (u64)rd32ptp(wx, WX_TSC_1588_SYSTIMH) << 32;
> +
> + return stamp;
> +}
> +
> +static void wx_ptp_link_speed_adjust(struct wx *wx, u32 *shift, u32 *incval)
> +{
> + if (wx->mac.type == wx_mac_em) {
> + *shift = WX_INCVAL_SHIFT_EM;
> + *incval = WX_INCVAL_EM;
> + return;
> + }
> +
> + switch (wx->speed) {
> + case SPEED_10:
> + *shift = WX_INCVAL_SHIFT_10;
> + *incval = WX_INCVAL_10;
> + break;
> + case SPEED_100:
> + *shift = WX_INCVAL_SHIFT_100;
> + *incval = WX_INCVAL_100;
> + break;
> + case SPEED_1000:
> + *shift = WX_INCVAL_SHIFT_1GB;
> + *incval = WX_INCVAL_1GB;
> + break;
> + case SPEED_10000:
> + default:
> + *shift = WX_INCVAL_SHIFT_10GB;
> + *incval = WX_INCVAL_10GB;
> + break;
> + }
> +}
> +
> +/**
> + * wx_ptp_reset_cyclecounter - create the cycle counter from hw
> + * @wx: pointer to the wx structure
> + *
> + * This function should be called to set the proper values for the TSC_1588_INC
> + * register and tell the cyclecounter structure what the tick rate of SYSTIME
> + * is. It does not directly modify SYSTIME registers or the timecounter
> + * structure. It should be called whenever a new TSC_1588_INC value is
> + * necessary, such as during initialization or when the link speed changes.
> + */
> +void wx_ptp_reset_cyclecounter(struct wx *wx)
> +{
> + u32 incval = 0, mask = 0;
> + struct cyclecounter cc;
> + unsigned long flags;
> +
> + /* For some of the boards below this mask is technically incorrect.
> + * The timestamp mask overflows at approximately 61bits. However the
> + * particular hardware does not overflow on an even bitmask value.
> + * Instead, it overflows due to conversion of upper 32bits billions of
> + * cycles. Timecounters are not really intended for this purpose so
> + * they do not properly function if the overflow point isn't 2^N-1.
> + * However, the actual SYSTIME values in question take ~138 years to
> + * overflow. In practice this means they won't actually overflow. A
> + * proper fix to this problem would require modification of the
> + * timecounter delta calculations.
> + */
> + cc.mask = CLOCKSOURCE_MASK(64);
> + cc.mult = 1;
> + cc.shift = 0;
> +
> + cc.read = wx_ptp_read;
> + wx_ptp_link_speed_adjust(wx, &cc.shift, &incval);
> +
> + /* update the base incval used to calculate frequency adjustment */
> + WRITE_ONCE(wx->base_incval, incval);
> +
> + mask = (wx->mac.type == wx_mac_em) ? 0x7FFFFFF : 0xFFFFFF;
> + incval &= mask;
> + if (wx->mac.type != wx_mac_em)
> + incval |= 2 << 24;
> + wr32ptp(wx, WX_TSC_1588_INC, incval);
> +
> + smp_mb(); /* Force the above update. */
> +
> + /* need lock to prevent incorrect read while modifying cyclecounter */
> + spin_lock_irqsave(&wx->tmreg_lock, flags);
> + memcpy(&wx->hw_cc, &cc, sizeof(wx->hw_cc));
> + spin_unlock_irqrestore(&wx->tmreg_lock, flags);
> +}
> +EXPORT_SYMBOL(wx_ptp_reset_cyclecounter);
> +
> +/**
> + * wx_ptp_reset
> + * @wx: the private board structure
> + *
> + * When the MAC resets, all of the hardware configuration for timesync is
> + * reset. This function should be called to re-enable the device for PTP,
> + * using the last known settings. However, we do lose the current clock time,
> + * so we fallback to resetting it based on the kernel's realtime clock.
> + *
> + * This function will maintain the hwtstamp_config settings, and it retriggers
> + * the SDP output if it's enabled.
> + */
> +void wx_ptp_reset(struct wx *wx)
> +{
> + unsigned long flags;
> +
> + /* reset the hardware timestamping mode */
> + wx_ptp_set_timestamp_mode(wx, &wx->tstamp_config);
> + wx_ptp_reset_cyclecounter(wx);
> +
> + wr32ptp(wx, WX_TSC_1588_SYSTIML, 0);
> + wr32ptp(wx, WX_TSC_1588_SYSTIMH, 0);
> + WX_WRITE_FLUSH(wx);
> +
> + spin_lock_irqsave(&wx->tmreg_lock, flags);
> + timecounter_init(&wx->hw_tc, &wx->hw_cc,
> + ktime_to_ns(ktime_get_real()));
> + spin_unlock_irqrestore(&wx->tmreg_lock, flags);
> +}
> +EXPORT_SYMBOL(wx_ptp_reset);
> +
> +/**
> + * wx_ptp_init
> + * @wx: the private board structure
> + *
> + * This function performs the required steps for enabling ptp
> + * support. If ptp support has already been loaded it simply calls the
> + * cyclecounter init routine and exits.
> + */
> +void wx_ptp_init(struct wx *wx)
> +{
> + /* Initialize the spin lock first, since the user might call the clock
> + * functions any time after we've initialized the ptp clock device.
> + */
> + spin_lock_init(&wx->tmreg_lock);
> +
> + /* obtain a ptp clock device, or re-use an existing device */
> + if (wx_ptp_create_clock(wx))
> + return;
> +
> + /* we have a clock, so we can initialize work for timestamps now */
> + INIT_WORK(&wx->ptp_tx_work, wx_ptp_tx_hwtstamp_work);
> +
> + wx->tx_hwtstamp_timeouts = 0;
> + wx->tx_hwtstamp_skipped = 0;
> + wx->rx_hwtstamp_cleared = 0;
> + /* reset the ptp related hardware bits */
> + wx_ptp_reset(wx);
> +
> + /* enter the WX_STATE_PTP_RUNNING state */
> + set_bit(WX_STATE_PTP_RUNNING, wx->state);
> +}
> +EXPORT_SYMBOL(wx_ptp_init);
> +
> +/**
> + * wx_ptp_suspend - stop ptp work items
> + * @wx: pointer to wx struct
> + *
> + * This function suspends ptp activity, and prevents more work from being
> + * generated, but does not destroy the clock device.
> + */
> +void wx_ptp_suspend(struct wx *wx)
> +{
> + /* leave the WX_STATE_PTP_RUNNING STATE */
> + if (!test_and_clear_bit(WX_STATE_PTP_RUNNING, wx->state))
> + return;
> +
> + cancel_work_sync(&wx->ptp_tx_work);
> + wx_ptp_clear_tx_timestamp(wx);
> +}
> +EXPORT_SYMBOL(wx_ptp_suspend);
> +
> +/**
> + * wx_ptp_stop - destroy the ptp_clock device
> + * @wx: pointer to wx struct
> + *
> + * Completely destroy the ptp_clock device, and disable all PTP related
> + * features. Intended to be run when the device is being closed.
> + */
> +void wx_ptp_stop(struct wx *wx)
> +{
> + /* first, suspend ptp activity */
> + wx_ptp_suspend(wx);
> +
> + /* now destroy the ptp clock device */
> + if (wx->ptp_clock) {
> + ptp_clock_unregister(wx->ptp_clock);
> + wx->ptp_clock = NULL;
> + dev_info(&wx->pdev->dev, "removed PHC on %s\n", wx->netdev->name);
> + }
> +}
> +EXPORT_SYMBOL(wx_ptp_stop);
> +
> +/**
> + * wx_ptp_rx_hwtstamp - utility function which checks for RX time stamp
> + * @wx: pointer to wx struct
> + * @skb: particular skb to send timestamp with
> + *
> + * if the timestamp is valid, we convert it into the timecounter ns
> + * value, then store that result into the shhwtstamps structure which
> + * is passed up the network stack
> + */
> +void wx_ptp_rx_hwtstamp(struct wx *wx, struct sk_buff *skb)
> +{
> + u64 regval = 0;
> + u32 tsyncrxctl;
> +
> + /* Read the tsyncrxctl register afterwards in order to prevent taking an
> + * I/O hit on every packet.
> + */
> + tsyncrxctl = rd32(wx, WX_PSR_1588_CTL);
> + if (!(tsyncrxctl & WX_PSR_1588_CTL_VALID))
> + return;
> +
> + regval |= (u64)rd32(wx, WX_PSR_1588_STMPL);
> + regval |= (u64)rd32(wx, WX_PSR_1588_STMPH) << 32;
> +
> + wx_ptp_convert_to_hwtstamp(wx, skb_hwtstamps(skb), regval);
> +}
> +
> +int wx_hwtstamp_get(struct net_device *dev,
> + struct kernel_hwtstamp_config *cfg)
> +{
> + struct wx *wx = netdev_priv(dev);
> +
> + if (!netif_running(dev))
> + return -EINVAL;
> +
> + *cfg = wx->tstamp_config;
> +
> + return 0;
> +}
> +EXPORT_SYMBOL(wx_hwtstamp_get);
> +
> +int wx_hwtstamp_set(struct net_device *dev,
> + struct kernel_hwtstamp_config *cfg,
> + struct netlink_ext_ack *extack)
> +{
> + struct wx *wx = netdev_priv(dev);
> + int err;
> +
> + if (!netif_running(dev))
> + return -EINVAL;
> +
> + err = wx_ptp_set_timestamp_mode(wx, cfg);
> + if (err)
> + return err;
> +
> + /* save these settings for future reference */
> + memcpy(&wx->tstamp_config, cfg, sizeof(wx->tstamp_config));
> +
> + return 0;
> +}
> +EXPORT_SYMBOL(wx_hwtstamp_set);
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.h b/drivers/net/ethernet/wangxun/libwx/wx_ptp.h
> new file mode 100644
> index 000000000000..8742d2797363
> --- /dev/null
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright (c) 2019 - 2025 Beijing WangXun Technology Co., Ltd. */
> +
> +#ifndef _WX_PTP_H_
> +#define _WX_PTP_H_
> +
> +void wx_ptp_reset_cyclecounter(struct wx *wx);
> +void wx_ptp_reset(struct wx *wx);
> +void wx_ptp_init(struct wx *wx);
> +void wx_ptp_suspend(struct wx *wx);
> +void wx_ptp_stop(struct wx *wx);
> +void wx_ptp_rx_hwtstamp(struct wx *wx, struct sk_buff *skb);
> +int wx_hwtstamp_get(struct net_device *dev,
> + struct kernel_hwtstamp_config *cfg);
> +int wx_hwtstamp_set(struct net_device *dev,
> + struct kernel_hwtstamp_config *cfg,
> + struct netlink_ext_ack *extack);
> +
> +#endif /* _WX_PTP_H_ */
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h
> index b54bffda027b..31b11dba6bf5 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
> @@ -4,6 +4,8 @@
> #ifndef _WX_TYPE_H_
> #define _WX_TYPE_H_
>
> +#include <linux/ptp_clock_kernel.h>
> +#include <linux/timecounter.h>
> #include <linux/bitfield.h>
> #include <linux/netdevice.h>
> #include <linux/if_vlan.h>
> @@ -180,6 +182,23 @@
> #define WX_PSR_VLAN_CTL 0x15088
> #define WX_PSR_VLAN_CTL_CFIEN BIT(29) /* bit 29 */
> #define WX_PSR_VLAN_CTL_VFE BIT(30) /* bit 30 */
> +/* EType Queue Filter */
> +#define WX_PSR_ETYPE_SWC(_i) (0x15128 + ((_i) * 4))
> +#define WX_PSR_ETYPE_SWC_FILTER_1588 3
> +#define WX_PSR_ETYPE_SWC_FILTER_EN BIT(31)
> +#define WX_PSR_ETYPE_SWC_1588 BIT(30)
> +/* 1588 */
> +#define WX_PSR_1588_MSG 0x15120
> +#define WX_PSR_1588_MSG_V1_SYNC FIELD_PREP(GENMASK(7, 0), 0)
> +#define WX_PSR_1588_MSG_V1_DELAY_REQ FIELD_PREP(GENMASK(7, 0), 1)
> +#define WX_PSR_1588_STMPL 0x151E8
> +#define WX_PSR_1588_STMPH 0x151A4
> +#define WX_PSR_1588_CTL 0x15188
> +#define WX_PSR_1588_CTL_ENABLED BIT(4)
> +#define WX_PSR_1588_CTL_TYPE_MASK GENMASK(3, 1)
> +#define WX_PSR_1588_CTL_TYPE_L4_V1 FIELD_PREP(GENMASK(3, 1), 1)
> +#define WX_PSR_1588_CTL_TYPE_EVENT_V2 FIELD_PREP(GENMASK(3, 1), 5)
> +#define WX_PSR_1588_CTL_VALID BIT(0)
> /* mcasst/ucast overflow tbl */
> #define WX_PSR_MC_TBL(_i) (0x15200 + ((_i) * 4))
> #define WX_PSR_UC_TBL(_i) (0x15400 + ((_i) * 4))
> @@ -253,6 +272,15 @@
> #define WX_TSC_ST_SECTX_RDY BIT(0)
> #define WX_TSC_BUF_AE 0x1D00C
> #define WX_TSC_BUF_AE_THR GENMASK(9, 0)
> +/* 1588 */
> +#define WX_TSC_1588_CTL 0x11F00
> +#define WX_TSC_1588_CTL_ENABLED BIT(4)
> +#define WX_TSC_1588_CTL_VALID BIT(0)
> +#define WX_TSC_1588_STMPL 0x11F04
> +#define WX_TSC_1588_STMPH 0x11F08
> +#define WX_TSC_1588_SYSTIML 0x11F0C
> +#define WX_TSC_1588_SYSTIMH 0x11F10
> +#define WX_TSC_1588_INC 0x11F14
>
> /************************************** MNG ********************************/
> #define WX_MNG_SWFW_SYNC 0x1E008
> @@ -460,6 +488,7 @@ enum WX_MSCA_CMD_value {
> #define WX_RXD_STAT_L4CS BIT(7) /* L4 xsum calculated */
> #define WX_RXD_STAT_IPCS BIT(8) /* IP xsum calculated */
> #define WX_RXD_STAT_OUTERIPCS BIT(10) /* Cloud IP xsum calculated*/
> +#define WX_RXD_STAT_TS BIT(14) /* IEEE1588 Time Stamp */
>
> #define WX_RXD_ERR_OUTERIPER BIT(26) /* CRC IP Header error */
> #define WX_RXD_ERR_RXE BIT(29) /* Any MAC Error */
> @@ -863,6 +892,7 @@ struct wx_tx_context_desc {
> */
> struct wx_tx_buffer {
> union wx_tx_desc *next_to_watch;
> + unsigned long time_stamp;
> struct sk_buff *skb;
> unsigned int bytecount;
> unsigned short gso_segs;
> @@ -924,6 +954,7 @@ struct wx_ring {
> unsigned int size; /* length in bytes */
>
> u16 count; /* amount of descriptors */
> + unsigned long last_rx_timestamp;
>
> u8 queue_index; /* needed for multiqueue queue management */
> u8 reg_idx; /* holds the special value that gets
> @@ -1026,6 +1057,8 @@ struct wx_hw_stats {
>
> enum wx_state {
> WX_STATE_RESETTING,
> + WX_STATE_PTP_RUNNING,
> + WX_STATE_PTP_TX_IN_PROGRESS,
> WX_STATE_NBITS, /* must be last */
> };
>
> @@ -1033,6 +1066,8 @@ enum wx_pf_flags {
> WX_FLAG_FDIR_CAPABLE,
> WX_FLAG_FDIR_HASH,
> WX_FLAG_FDIR_PERFECT,
> + WX_FLAG_RX_HWTSTAMP_ENABLED,
> + WX_FLAG_RX_HWTSTAMP_IN_REGISTER,
> WX_PF_FLAGS_NBITS /* must be last */
> };
>
> @@ -1133,6 +1168,20 @@ struct wx {
> void (*atr)(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype);
> void (*configure_fdir)(struct wx *wx);
> void (*do_reset)(struct net_device *netdev);
> +
> + u32 base_incval;
> + u32 tx_hwtstamp_timeouts;
> + u32 tx_hwtstamp_skipped;
> + u32 rx_hwtstamp_cleared;
> + unsigned long ptp_tx_start;
> + spinlock_t tmreg_lock; /* spinlock for ptp */
> + struct cyclecounter hw_cc;
> + struct timecounter hw_tc;
> + struct ptp_clock *ptp_clock;
> + struct ptp_clock_info ptp_caps;
> + struct kernel_hwtstamp_config tstamp_config;
> + struct work_struct ptp_tx_work;
> + struct sk_buff *ptp_tx_skb;
> };
>
> #define WX_INTR_ALL (~0ULL)
> @@ -1177,6 +1226,24 @@ rd64(struct wx *wx, u32 reg)
> return (lsb | msb << 32);
> }
>
> +static inline u32
> +rd32ptp(struct wx *wx, u32 reg)
> +{
> + if (wx->mac.type == wx_mac_em)
> + return rd32(wx, reg);
> +
> + return rd32(wx, reg + 0xB500);
> +}
> +
> +static inline void
> +wr32ptp(struct wx *wx, u32 reg, u32 value)
> +{
> + if (wx->mac.type == wx_mac_em)
> + return wr32(wx, reg, value);
> +
> + return wr32(wx, reg + 0xB500, value);
> +}
> +
> /* On some domestic CPU platforms, sometimes IO is not synchronized with
> * flushing memory, here use readl() to flush PCI read and write.
> */
> diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
> index 53aeae2f884b..c60a96cc3508 100644
> --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
> +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
> @@ -14,6 +14,7 @@
> #include "../libwx/wx_type.h"
> #include "../libwx/wx_hw.h"
> #include "../libwx/wx_lib.h"
> +#include "../libwx/wx_ptp.h"
> #include "ngbe_type.h"
> #include "ngbe_mdio.h"
> #include "ngbe_hw.h"
> @@ -317,6 +318,8 @@ void ngbe_down(struct wx *wx)
> {
> phylink_stop(wx->phylink);
> ngbe_disable_device(wx);
> + if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
> + wx_ptp_reset(wx);
> wx_clean_all_tx_rings(wx);
> wx_clean_all_rx_rings(wx);
> }
> @@ -379,6 +382,8 @@ static int ngbe_open(struct net_device *netdev)
> if (err)
> goto err_dis_phy;
>
> + wx_ptp_init(wx);
> +
> ngbe_up(wx);
>
> return 0;
> @@ -407,6 +412,7 @@ static int ngbe_close(struct net_device *netdev)
> {
> struct wx *wx = netdev_priv(netdev);
>
> + wx_ptp_stop(wx);
> ngbe_down(wx);
> wx_free_irq(wx);
> wx_free_isb_resources(wx);
> @@ -507,6 +513,8 @@ static const struct net_device_ops ngbe_netdev_ops = {
> .ndo_get_stats64 = wx_get_stats64,
> .ndo_vlan_rx_add_vid = wx_vlan_rx_add_vid,
> .ndo_vlan_rx_kill_vid = wx_vlan_rx_kill_vid,
> + .ndo_hwtstamp_set = wx_hwtstamp_set,
> + .ndo_hwtstamp_get = wx_hwtstamp_get,
> };
>
> /**
> diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
> index a5e9b779c44d..c7944e62838a 100644
> --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
> +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
> @@ -7,6 +7,7 @@
> #include <linux/phy.h>
>
> #include "../libwx/wx_type.h"
> +#include "../libwx/wx_ptp.h"
> #include "../libwx/wx_hw.h"
> #include "ngbe_type.h"
> #include "ngbe_mdio.h"
> @@ -64,6 +65,11 @@ static void ngbe_mac_config(struct phylink_config *config, unsigned int mode,
> static void ngbe_mac_link_down(struct phylink_config *config,
> unsigned int mode, phy_interface_t interface)
> {
> + struct wx *wx = phylink_to_wx(config);
> +
> + wx->speed = SPEED_UNKNOWN;
> + if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
> + wx_ptp_reset_cyclecounter(wx);
> }
>
> static void ngbe_mac_link_up(struct phylink_config *config,
> @@ -103,6 +109,10 @@ static void ngbe_mac_link_up(struct phylink_config *config,
> wr32(wx, WX_MAC_PKT_FLT, WX_MAC_PKT_FLT_PR);
> reg = rd32(wx, WX_MAC_WDG_TIMEOUT);
> wr32(wx, WX_MAC_WDG_TIMEOUT, reg);
> +
> + wx->speed = speed;
> + if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
> + wx_ptp_reset_cyclecounter(wx);
> }
>
> static const struct phylink_mac_ops ngbe_mac_ops = {
> diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
> index f77450268036..734450af9a43 100644
> --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
> +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
> @@ -13,6 +13,7 @@
>
> #include "../libwx/wx_type.h"
> #include "../libwx/wx_lib.h"
> +#include "../libwx/wx_ptp.h"
> #include "../libwx/wx_hw.h"
> #include "txgbe_type.h"
> #include "txgbe_hw.h"
> @@ -116,6 +117,9 @@ static void txgbe_reset(struct wx *wx)
> memcpy(old_addr, &wx->mac_table[0].addr, netdev->addr_len);
> wx_flush_sw_mac_table(wx);
> wx_mac_set_default_filter(wx, old_addr);
> +
> + if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
> + wx_ptp_reset(wx);
> }
>
> static void txgbe_disable_device(struct wx *wx)
> @@ -176,6 +180,7 @@ void txgbe_down(struct wx *wx)
> void txgbe_up(struct wx *wx)
> {
> wx_configure(wx);
> + wx_ptp_init(wx);
> txgbe_up_complete(wx);
> }
>
> @@ -321,6 +326,8 @@ static int txgbe_open(struct net_device *netdev)
> if (err)
> goto err_free_irq;
>
> + wx_ptp_init(wx);
> +
> txgbe_up_complete(wx);
>
> return 0;
> @@ -344,6 +351,7 @@ static int txgbe_open(struct net_device *netdev)
> */
> static void txgbe_close_suspend(struct wx *wx)
> {
> + wx_ptp_suspend(wx);
> txgbe_disable_device(wx);
> wx_free_resources(wx);
> }
> @@ -363,6 +371,7 @@ static int txgbe_close(struct net_device *netdev)
> {
> struct wx *wx = netdev_priv(netdev);
>
> + wx_ptp_stop(wx);
> txgbe_down(wx);
> wx_free_irq(wx);
> wx_free_resources(wx);
> @@ -479,6 +488,8 @@ static const struct net_device_ops txgbe_netdev_ops = {
> .ndo_get_stats64 = wx_get_stats64,
> .ndo_vlan_rx_add_vid = wx_vlan_rx_add_vid,
> .ndo_vlan_rx_kill_vid = wx_vlan_rx_kill_vid,
> + .ndo_hwtstamp_set = wx_hwtstamp_set,
> + .ndo_hwtstamp_get = wx_hwtstamp_get,
> };
>
> /**
> diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
> index 1ae68f94dd49..60e5f3288ad8 100644
> --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
> +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
> @@ -15,6 +15,7 @@
>
> #include "../libwx/wx_type.h"
> #include "../libwx/wx_lib.h"
> +#include "../libwx/wx_ptp.h"
> #include "../libwx/wx_hw.h"
> #include "txgbe_type.h"
> #include "txgbe_phy.h"
> @@ -179,6 +180,10 @@ static void txgbe_mac_link_down(struct phylink_config *config,
> struct wx *wx = phylink_to_wx(config);
>
> wr32m(wx, WX_MAC_TX_CFG, WX_MAC_TX_CFG_TE, 0);
> +
> + wx->speed = SPEED_UNKNOWN;
> + if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
> + wx_ptp_reset_cyclecounter(wx);
> }
>
> static void txgbe_mac_link_up(struct phylink_config *config,
> @@ -215,6 +220,10 @@ static void txgbe_mac_link_up(struct phylink_config *config,
> wr32(wx, WX_MAC_PKT_FLT, WX_MAC_PKT_FLT_PR);
> wdg = rd32(wx, WX_MAC_WDG_TIMEOUT);
> wr32(wx, WX_MAC_WDG_TIMEOUT, wdg);
> +
> + wx->speed = speed;
> + if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
> + wx_ptp_reset_cyclecounter(wx);
> }
>
> static int txgbe_mac_prepare(struct phylink_config *config, unsigned int mode,
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info
2025-01-06 8:45 ` [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info Jiawen Wu
@ 2025-01-06 10:36 ` Vadim Fedorenko
2025-01-06 15:16 ` Richard Cochran
1 sibling, 0 replies; 17+ messages in thread
From: Vadim Fedorenko @ 2025-01-06 10:36 UTC (permalink / raw)
To: Jiawen Wu, andrew+netdev, davem, edumazet, kuba, pabeni,
richardcochran, linux, horms, jacob.e.keller, netdev
Cc: mengyuanlou
On 06/01/2025 08:45, 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 | 211 ++++++++++++++++++
> 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, 215 insertions(+)
>
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
> index 0f683e576b29..0071ba929738 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
> @@ -255,6 +255,215 @@ 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 */
> + spin_lock_irqsave(&wx->tmreg_lock, flags);
> + timecounter_read(&wx->hw_tc);
> + spin_unlock_irqrestore(&wx->tmreg_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 0;
This means do_aux_work will run only once. If it's not expected
behavior, you have to return delay value to requeue work.
> +}
> +
> +/**
> + * 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_PPS || !wx->ptp_setup_sdp)
> + return -EOPNOTSUPP;
> +
> + if (on)
> + set_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
> + else
> + clear_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
> +
> + wx->ptp_setup_sdp(wx);
> +
> + return 0;
> +}
> +
> +/**
> + * 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)
> +{
> + struct cyclecounter *cc = &wx->hw_cc;
> + u32 tsauxc, rem, int_status;
> + u32 trgttiml0, trgttimh0;
> + u32 trgttiml1, trgttimh1;
> + unsigned long flags;
> + u64 ns = 0;
> +
> + /* 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);
> +
> + 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;
> +
> + /* Read the current clock time, and save the cycle counter value */
> + spin_lock_irqsave(&wx->tmreg_lock, flags);
> + ns = timecounter_read(&wx->hw_tc);
> + wx->pps_edge_start = wx->hw_tc.cycle_last;
> + spin_unlock_irqrestore(&wx->tmreg_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);
> + trgttiml0 = (u32)wx->pps_edge_start;
> + trgttimh0 = (u32)(wx->pps_edge_start >> 32);
> +
> + rem += WX_1588_PPS_WIDTH_EM * WX_NS_PER_MSEC;
> + wx->pps_edge_end += div_u64(((u64)rem << cc->shift), cc->mult);
> + trgttiml1 = (u32)wx->pps_edge_end;
> + trgttimh1 = (u32)(wx->pps_edge_end >> 32);
> +
> + wr32ptp(wx, WX_TSC_1588_TRGT_L(0), trgttiml0);
> + wr32ptp(wx, WX_TSC_1588_TRGT_H(0), trgttimh0);
> + wr32ptp(wx, WX_TSC_1588_TRGT_L(1), trgttiml1);
> + wr32ptp(wx, WX_TSC_1588_TRGT_H(1), trgttimh1);
> + 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
> @@ -573,6 +782,8 @@ void wx_ptp_reset(struct wx *wx)
> timecounter_init(&wx->hw_tc, &wx->hw_cc,
> ktime_to_ns(ktime_get_real()));
> spin_unlock_irqrestore(&wx->tmreg_lock, flags);
> +
> + wx->last_overflow_check = jiffies;
> }
> 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 31b11dba6bf5..1f9ddddea191 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
> @@ -1173,6 +1173,8 @@ struct wx {
> u32 tx_hwtstamp_timeouts;
> u32 tx_hwtstamp_skipped;
> u32 rx_hwtstamp_cleared;
> + unsigned long last_overflow_check;
> + unsigned long last_rx_ptp_check;
> unsigned long ptp_tx_start;
> spinlock_t tmreg_lock; /* spinlock 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] 17+ messages in thread
* Re: [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info
2025-01-06 8:45 ` [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info Jiawen Wu
2025-01-06 10:36 ` Vadim Fedorenko
@ 2025-01-06 15:16 ` Richard Cochran
2025-01-06 21:35 ` Keller, Jacob E
2025-01-07 1:50 ` Jiawen Wu
1 sibling, 2 replies; 17+ messages in thread
From: Richard Cochran @ 2025-01-06 15:16 UTC (permalink / raw)
To: Jiawen Wu
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, horms,
jacob.e.keller, netdev, vadim.fedorenko, mengyuanlou
On Mon, Jan 06, 2025 at 04:45:05PM +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_PPS || !wx->ptp_setup_sdp)
> + return -EOPNOTSUPP;
NAK.
The logic that you added in patch #4 is a periodic output signal, so
your driver will support PTP_CLK_REQ_PEROUT and not PTP_CLK_REQ_PPS.
Please change the driver to use that instead.
Thanks,
Richard
^ permalink raw reply [flat|nested] 17+ messages in thread
* RE: [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info
2025-01-06 15:16 ` Richard Cochran
@ 2025-01-06 21:35 ` Keller, Jacob E
2025-01-07 5:59 ` Jiawen Wu
2025-01-07 1:50 ` Jiawen Wu
1 sibling, 1 reply; 17+ messages in thread
From: Keller, Jacob E @ 2025-01-06 21:35 UTC (permalink / raw)
To: Richard Cochran, Jiawen Wu
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, linux@armlinux.org.uk,
horms@kernel.org, netdev@vger.kernel.org,
vadim.fedorenko@linux.dev, mengyuanlou@net-swift.com
> -----Original Message-----
> From: Richard Cochran <richardcochran@gmail.com>
> Sent: Monday, January 6, 2025 7:16 AM
> To: Jiawen Wu <jiawenwu@trustnetic.com>
> Cc: andrew+netdev@lunn.ch; davem@davemloft.net; edumazet@google.com;
> kuba@kernel.org; pabeni@redhat.com; linux@armlinux.org.uk;
> horms@kernel.org; Keller, Jacob E <jacob.e.keller@intel.com>;
> netdev@vger.kernel.org; vadim.fedorenko@linux.dev; mengyuanlou@net-
> swift.com
> Subject: Re: [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of
> ptp_clock_info
>
> On Mon, Jan 06, 2025 at 04:45:05PM +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_PPS || !wx->ptp_setup_sdp)
> > + return -EOPNOTSUPP;
>
> NAK.
>
> The logic that you added in patch #4 is a periodic output signal, so
> your driver will support PTP_CLK_REQ_PEROUT and not PTP_CLK_REQ_PPS.
>
> Please change the driver to use that instead.
>
> Thanks,
> Richard
This is a common misconception because the industry lingo uses PPS to mean periodic output. I wonder if there's a place we can put an obvious warning about checking if you meant PEROUT... I've had this issue pop up with colleagues many times.
Thanks,
Jake
^ permalink raw reply [flat|nested] 17+ messages in thread
* RE: [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info
2025-01-06 15:16 ` Richard Cochran
2025-01-06 21:35 ` Keller, Jacob E
@ 2025-01-07 1:50 ` Jiawen Wu
1 sibling, 0 replies; 17+ messages in thread
From: Jiawen Wu @ 2025-01-07 1:50 UTC (permalink / raw)
To: 'Richard Cochran'
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, horms,
jacob.e.keller, netdev, vadim.fedorenko, mengyuanlou
On Mon, Jan 6, 2025 11:16 PM, Richard Cochran wrote:
> On Mon, Jan 06, 2025 at 04:45:05PM +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_PPS || !wx->ptp_setup_sdp)
> > + return -EOPNOTSUPP;
>
> NAK.
>
> The logic that you added in patch #4 is a periodic output signal, so
> your driver will support PTP_CLK_REQ_PEROUT and not PTP_CLK_REQ_PPS.
>
> Please change the driver to use that instead.
Oh sorry, I messed up the patches. These belong to patch 4/4.
^ permalink raw reply [flat|nested] 17+ messages in thread
* RE: [PATCH net-next v2 1/4] net: wangxun: Add support for PTP clock
2025-01-06 10:35 ` Vadim Fedorenko
@ 2025-01-07 2:01 ` Jiawen Wu
0 siblings, 0 replies; 17+ messages in thread
From: Jiawen Wu @ 2025-01-07 2:01 UTC (permalink / raw)
To: 'Vadim Fedorenko', andrew+netdev, davem, edumazet, kuba,
pabeni, richardcochran, linux, horms, jacob.e.keller, netdev
Cc: mengyuanlou
> > +/**
> > + * wx_ptp_create_clock
> > + * @wx: the private board structure
> > + *
> > + * Returns 0 on success, negative value on failure
> > + *
> > + * This function performs setup of the user entry point function table and
> > + * initalizes the PTP clock device used by userspace to access the clock-like
> > + * features of the PTP core. It will be called by wx_ptp_init, and may
> > + * re-use a previously initialized clock (such as during a suspend/resume
> > + * cycle).
> > + */
> > +static long wx_ptp_create_clock(struct wx *wx)
> > +{
> > + struct net_device *netdev = wx->netdev;
> > + long err;
> > +
> > + /* do nothing if we already have a clock device */
> > + if (!IS_ERR_OR_NULL(wx->ptp_clock))
> > + return 0;
> > +
> > + snprintf(wx->ptp_caps.name, sizeof(wx->ptp_caps.name),
> > + "%s", netdev->name);
> > + wx->ptp_caps.owner = THIS_MODULE;
> > + wx->ptp_caps.n_alarm = 0;
> > + wx->ptp_caps.n_ext_ts = 0;
> > + wx->ptp_caps.n_per_out = 0;
> > + wx->ptp_caps.pps = 0;
> > + wx->ptp_caps.adjfine = wx_ptp_adjfine;
> > + wx->ptp_caps.adjtime = wx_ptp_adjtime;
> > + wx->ptp_caps.gettimex64 = wx_ptp_gettimex64;
> > + wx->ptp_caps.settime64 = wx_ptp_settime64;
> > + wx->ptp_caps.do_aux_work = wx_ptp_do_aux_work;
>
> wx_ptp_do_aux_work is not defined in this patch, it appears in patch 3
> only. did you compile test your code patch by patch?
I think I may have created some confusion between the coding and testing
machines...
Thanks for your careful review.
^ permalink raw reply [flat|nested] 17+ messages in thread
* RE: [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info
2025-01-06 21:35 ` Keller, Jacob E
@ 2025-01-07 5:59 ` Jiawen Wu
2025-01-08 0:33 ` Keller, Jacob E
0 siblings, 1 reply; 17+ messages in thread
From: Jiawen Wu @ 2025-01-07 5:59 UTC (permalink / raw)
To: 'Keller, Jacob E', 'Richard Cochran'
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, horms,
netdev, vadim.fedorenko, mengyuanlou
> > > +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_PPS || !wx->ptp_setup_sdp)
> > > + return -EOPNOTSUPP;
> >
> > NAK.
> >
> > The logic that you added in patch #4 is a periodic output signal, so
> > your driver will support PTP_CLK_REQ_PEROUT and not PTP_CLK_REQ_PPS.
> >
> > Please change the driver to use that instead.
> >
> > Thanks,
> > Richard
>
> This is a common misconception because the industry lingo uses PPS to mean
> periodic output. I wonder if there's a place we can put an obvious warning
> about checking if you meant PEROUT... I've had this issue pop up with
> colleagues many times.
Does a periodic output signal mean that a signal is output every second,
whenever the start time is? But I want to implement that a signal is
output when an integer number of seconds for the clock time.
^ permalink raw reply [flat|nested] 17+ messages in thread
* RE: [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info
2025-01-07 5:59 ` Jiawen Wu
@ 2025-01-08 0:33 ` Keller, Jacob E
2025-01-08 2:52 ` Jiawen Wu
0 siblings, 1 reply; 17+ messages in thread
From: Keller, Jacob E @ 2025-01-08 0:33 UTC (permalink / raw)
To: Jiawen Wu, 'Richard Cochran'
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, linux@armlinux.org.uk,
horms@kernel.org, netdev@vger.kernel.org,
vadim.fedorenko@linux.dev, mengyuanlou@net-swift.com
> -----Original Message-----
> From: Jiawen Wu <jiawenwu@trustnetic.com>
> Sent: Monday, January 6, 2025 9:59 PM
> To: Keller, Jacob E <jacob.e.keller@intel.com>; 'Richard Cochran'
> <richardcochran@gmail.com>
> Cc: andrew+netdev@lunn.ch; davem@davemloft.net; edumazet@google.com;
> kuba@kernel.org; pabeni@redhat.com; linux@armlinux.org.uk;
> horms@kernel.org; netdev@vger.kernel.org; vadim.fedorenko@linux.dev;
> mengyuanlou@net-swift.com
> Subject: RE: [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of
> ptp_clock_info
>
> > > > +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_PPS || !wx->ptp_setup_sdp)
> > > > + return -EOPNOTSUPP;
> > >
> > > NAK.
> > >
> > > The logic that you added in patch #4 is a periodic output signal, so
> > > your driver will support PTP_CLK_REQ_PEROUT and not PTP_CLK_REQ_PPS.
> > >
> > > Please change the driver to use that instead.
> > >
> > > Thanks,
> > > Richard
> >
> > This is a common misconception because the industry lingo uses PPS to mean
> > periodic output. I wonder if there's a place we can put an obvious warning
> > about checking if you meant PEROUT... I've had this issue pop up with
> > colleagues many times.
>
> Does a periodic output signal mean that a signal is output every second,
> whenever the start time is? But I want to implement that a signal is
> output when an integer number of seconds for the clock time.
>
The periodic output can be configured in a bunch of ways, including periods that are not a full second, when the signal should start, as well as in "one shot" mode where it will only trigger once. You should check the possible flags in <uapi/linux/ptp_clock.h> for the various options.
Thanks,
Jake
>
^ permalink raw reply [flat|nested] 17+ messages in thread
* RE: [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info
2025-01-08 0:33 ` Keller, Jacob E
@ 2025-01-08 2:52 ` Jiawen Wu
2025-01-08 6:28 ` Jiawen Wu
2025-01-08 18:09 ` Keller, Jacob E
0 siblings, 2 replies; 17+ messages in thread
From: Jiawen Wu @ 2025-01-08 2:52 UTC (permalink / raw)
To: 'Keller, Jacob E', 'Richard Cochran'
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, horms,
netdev, vadim.fedorenko, mengyuanlou, 'linglingzhang'
> > > > > +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_PPS || !wx->ptp_setup_sdp)
> > > > > + return -EOPNOTSUPP;
> > > >
> > > > NAK.
> > > >
> > > > The logic that you added in patch #4 is a periodic output signal, so
> > > > your driver will support PTP_CLK_REQ_PEROUT and not PTP_CLK_REQ_PPS.
> > > >
> > > > Please change the driver to use that instead.
> > > >
> > > > Thanks,
> > > > Richard
> > >
> > > This is a common misconception because the industry lingo uses PPS to mean
> > > periodic output. I wonder if there's a place we can put an obvious warning
> > > about checking if you meant PEROUT... I've had this issue pop up with
> > > colleagues many times.
> >
> > Does a periodic output signal mean that a signal is output every second,
> > whenever the start time is? But I want to implement that a signal is
> > output when an integer number of seconds for the clock time.
> >
>
> The periodic output can be configured in a bunch of ways, including periods that
> are not a full second, when the signal should start, as well as in "one shot" mode
> where it will only trigger once. You should check the possible flags in
> <uapi/linux/ptp_clock.h> for the various options.
Looks like I need to configure perout.phase {0, 0} to output signal at the closest next
second. And configure perout.period {0, 120 * 1000000} to keep the signal 120ms.
But where should I put these configuration? It used to be:
echo 1 > /sys/class/ptp/ptp0/pps_enable
^ permalink raw reply [flat|nested] 17+ messages in thread
* RE: [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info
2025-01-08 2:52 ` Jiawen Wu
@ 2025-01-08 6:28 ` Jiawen Wu
2025-01-08 18:09 ` Keller, Jacob E
1 sibling, 0 replies; 17+ messages in thread
From: Jiawen Wu @ 2025-01-08 6:28 UTC (permalink / raw)
To: 'Keller, Jacob E', 'Richard Cochran'
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, horms,
netdev, vadim.fedorenko, mengyuanlou
> > > > > > +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_PPS || !wx->ptp_setup_sdp)
> > > > > > + return -EOPNOTSUPP;
> > > > >
> > > > > NAK.
> > > > >
> > > > > The logic that you added in patch #4 is a periodic output signal, so
> > > > > your driver will support PTP_CLK_REQ_PEROUT and not PTP_CLK_REQ_PPS.
> > > > >
> > > > > Please change the driver to use that instead.
> > > > >
> > > > > Thanks,
> > > > > Richard
> > > >
> > > > This is a common misconception because the industry lingo uses PPS to mean
> > > > periodic output. I wonder if there's a place we can put an obvious warning
> > > > about checking if you meant PEROUT... I've had this issue pop up with
> > > > colleagues many times.
> > >
> > > Does a periodic output signal mean that a signal is output every second,
> > > whenever the start time is? But I want to implement that a signal is
> > > output when an integer number of seconds for the clock time.
> > >
> >
> > The periodic output can be configured in a bunch of ways, including periods that
> > are not a full second, when the signal should start, as well as in "one shot" mode
> > where it will only trigger once. You should check the possible flags in
> > <uapi/linux/ptp_clock.h> for the various options.
>
> Looks like I need to configure perout.phase {0, 0} to output signal at the closest next
> second. And configure perout.period {0, 120 * 1000000} to keep the signal 120ms.
>
> But where should I put these configuration? It used to be:
>
> echo 1 > /sys/class/ptp/ptp0/pps_enable
I see. Thanks for your suggestion.
^ permalink raw reply [flat|nested] 17+ messages in thread
* RE: [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info
2025-01-08 2:52 ` Jiawen Wu
2025-01-08 6:28 ` Jiawen Wu
@ 2025-01-08 18:09 ` Keller, Jacob E
1 sibling, 0 replies; 17+ messages in thread
From: Keller, Jacob E @ 2025-01-08 18:09 UTC (permalink / raw)
To: Jiawen Wu, 'Richard Cochran'
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, linux@armlinux.org.uk,
horms@kernel.org, netdev@vger.kernel.org,
vadim.fedorenko@linux.dev, mengyuanlou@net-swift.com,
'linglingzhang'
> -----Original Message-----
> From: Jiawen Wu <jiawenwu@trustnetic.com>
> Sent: Tuesday, January 7, 2025 6:52 PM
> To: Keller, Jacob E <jacob.e.keller@intel.com>; 'Richard Cochran'
> <richardcochran@gmail.com>
> Cc: andrew+netdev@lunn.ch; davem@davemloft.net; edumazet@google.com;
> kuba@kernel.org; pabeni@redhat.com; linux@armlinux.org.uk;
> horms@kernel.org; netdev@vger.kernel.org; vadim.fedorenko@linux.dev;
> mengyuanlou@net-swift.com; 'linglingzhang' <linglingzhang@net-swift.com>
> Subject: RE: [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of
> ptp_clock_info
>
> > > > > > +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_PPS || !wx->ptp_setup_sdp)
> > > > > > + return -EOPNOTSUPP;
> > > > >
> > > > > NAK.
> > > > >
> > > > > The logic that you added in patch #4 is a periodic output signal, so
> > > > > your driver will support PTP_CLK_REQ_PEROUT and not
> PTP_CLK_REQ_PPS.
> > > > >
> > > > > Please change the driver to use that instead.
> > > > >
> > > > > Thanks,
> > > > > Richard
> > > >
> > > > This is a common misconception because the industry lingo uses PPS to
> mean
> > > > periodic output. I wonder if there's a place we can put an obvious warning
> > > > about checking if you meant PEROUT... I've had this issue pop up with
> > > > colleagues many times.
> > >
> > > Does a periodic output signal mean that a signal is output every second,
> > > whenever the start time is? But I want to implement that a signal is
> > > output when an integer number of seconds for the clock time.
> > >
> >
> > The periodic output can be configured in a bunch of ways, including periods that
> > are not a full second, when the signal should start, as well as in "one shot"
> mode
> > where it will only trigger once. You should check the possible flags in
> > <uapi/linux/ptp_clock.h> for the various options.
>
> Looks like I need to configure perout.phase {0, 0} to output signal at the closest
> next
> second. And configure perout.period {0, 120 * 1000000} to keep the signal 120ms.
>
> But where should I put these configuration? It used to be:
>
> echo 1 > /sys/class/ptp/ptp0/pps_enable
You can use /sys/class/ptp/ptp0/period (assuming the driver is configured with a periodic output properly). Note that this may not be a fully featured interface. To access the full power of the periodic output support, you need to use the PTP_PEROUT_REQUEST2 ioctl. You can check the code in tools/testing/selftests/ptp/testptp.c or possibly see if there is an example of use in the LinuxPTP project.
Thanks,
Jake
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2025-01-08 18:10 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-01-06 8:45 [PATCH net-next v2 0/4] Support PTP clock for Wangxun NICs Jiawen Wu
2025-01-06 8:45 ` [PATCH net-next v2 1/4] net: wangxun: Add support for PTP clock Jiawen Wu
2025-01-06 10:35 ` Vadim Fedorenko
2025-01-07 2:01 ` Jiawen Wu
2025-01-06 8:45 ` [PATCH net-next v2 2/4] net: wangxun: Implement get_ts_info Jiawen Wu
2025-01-06 10:28 ` Vadim Fedorenko
2025-01-06 8:45 ` [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of ptp_clock_info Jiawen Wu
2025-01-06 10:36 ` Vadim Fedorenko
2025-01-06 15:16 ` Richard Cochran
2025-01-06 21:35 ` Keller, Jacob E
2025-01-07 5:59 ` Jiawen Wu
2025-01-08 0:33 ` Keller, Jacob E
2025-01-08 2:52 ` Jiawen Wu
2025-01-08 6:28 ` Jiawen Wu
2025-01-08 18:09 ` Keller, Jacob E
2025-01-07 1:50 ` Jiawen Wu
2025-01-06 8:45 ` [PATCH net-next v2 4/4] net: ngbe: Add support for 1PPS and TOD Jiawen Wu
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).