git: f2c300112ea4 - main - if_bnxt: add RX timestamping support

From: Sumit Saxena <ssaxena_at_FreeBSD.org>
Date: Fri, 09 Oct 2026 10:12:31 UTC
The branch main has been updated by ssaxena:

URL: https://cgit.FreeBSD.org/src/commit/?id=f2c300112ea491d69799c5a1085b57c48acef713

commit f2c300112ea491d69799c5a1085b57c48acef713
Author:     Sumit Saxena <sumit.saxena@broadcom.com>
AuthorDate: 2026-08-18 21:00:11 +0000
Commit:     Sumit Saxena <ssaxena@FreeBSD.org>
CommitDate: 2026-10-09 08:24:42 +0000

    if_bnxt: add RX timestamping support
    
    Add PHC register mapping, RTC initialization, refclk read with
    FW-reset serialization, and sbinuptime-based calibration to convert
    the raw hardware timestamp carried in RX completions into an mbuf
    timestamp. Advertise IFCAP_HWRXTSTMP, configure the RX timestamp
    filter in bnxt_init(), and drain the calibration callout on stop.
    Check HWRM_FUNC_QCAPS_OUTPUT_FLAGS_PTP_SUPPORTED in
    bnxt_hwrm_func_qcaps() so the capability bit PTP is gated on actually
    gets set, without which PTP never armed on real hardware.
    
    Reviewed by:    gallatin
    MFC after:      2 weeks
    Sponsored by:   Broadcom Inc.
    Differential Revision:  https://reviews.freebsd.org/D58596
---
 sys/conf/files                    |   1 +
 sys/dev/bnxt/bnxt_en/bnxt.h       |   5 +
 sys/dev/bnxt/bnxt_en/bnxt_hwrm.c  |   2 +
 sys/dev/bnxt/bnxt_en/bnxt_ptp.c   | 392 ++++++++++++++++++++++++++++++++++++++
 sys/dev/bnxt/bnxt_en/bnxt_ptp.h   |  88 +++++++++
 sys/dev/bnxt/bnxt_en/bnxt_txrx.c  |  22 +++
 sys/dev/bnxt/bnxt_en/if_bnxt.c    |  26 +++
 sys/modules/bnxt/bnxt_en/Makefile |   1 +
 8 files changed, 537 insertions(+)

diff --git a/sys/conf/files b/sys/conf/files
index 7450d65bf8b7..73a5d5fa8408 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1332,6 +1332,7 @@ dev/bnxt/bnxt_en/bnxt_ulp.c		optional bnxt iflib pci compile-with "${BNXT_C}"
 dev/bnxt/bnxt_en/bnxt_log.c		optional bnxt iflib pci compile-with "${BNXT_C}"
 dev/bnxt/bnxt_en/bnxt_log_data.c	optional bnxt iflib pci compile-with "${BNXT_C}"
 dev/bnxt/bnxt_en/bnxt_coredump.c	optional bnxt iflib pci compile-with "${BNXT_C}"
+dev/bnxt/bnxt_en/bnxt_ptp.c		optional bnxt iflib pci compile-with "${BNXT_C}"
 dev/bnxt/bnxt_en/if_bnxt.c		optional bnxt iflib pci compile-with "${BNXT_C}"
 dev/bwi/bwimac.c		optional bwi
 dev/bwi/bwiphy.c		optional bwi
diff --git a/sys/dev/bnxt/bnxt_en/bnxt.h b/sys/dev/bnxt/bnxt_en/bnxt.h
index 34afad28377d..98f8c1d3f5fa 100644
--- a/sys/dev/bnxt/bnxt_en/bnxt.h
+++ b/sys/dev/bnxt/bnxt_en/bnxt.h
@@ -1324,6 +1324,7 @@ struct bnxt_softc {
 
 	#define BNXT_FW_CAP_SW_MAX_RESOURCE_LIMITS      BIT_ULL(61)
 	#define BNXT_SW_RES_LMT(bp) ((bp)->fw_cap & BNXT_FW_CAP_SW_MAX_RESOURCE_LIMITS)
+	#define BNXT_FW_CAP_PTP				BIT_ULL(62)
 
 	uint32_t		lpi_tmr_lo;
 	uint32_t		lpi_tmr_hi;
@@ -1419,6 +1420,9 @@ struct bnxt_softc {
 
 #define MAX_NUM_DMA_INDICATIONS 10
 	struct iflib_dma_info	mgmt_dma_data[MAX_NUM_DMA_INDICATIONS];
+
+	struct bnxt_ptp_cfg	*ptp_cfg;
+	bool			rx_ts_enabled;
 };
 
 struct bnxt_filter_info {
@@ -1480,6 +1484,7 @@ int bnxt_alloc_ctx_pg_tbls(struct bnxt_softc *softc,
 			    struct bnxt_ctx_pg_info *ctx_pg,
 			    uint32_t mem_size, uint8_t depth,
 			    struct bnxt_ctx_mem_type *ctxm);
+int bnxt_hwrm_ptp_qcfg(struct bnxt_softc *bp);
 
 static inline u32
 readl_fbsd(struct bnxt_softc *bp, u32 reg_off, u8 bar_idx)
diff --git a/sys/dev/bnxt/bnxt_en/bnxt_hwrm.c b/sys/dev/bnxt/bnxt_en/bnxt_hwrm.c
index a6742b6ca37c..11d40a1a528a 100644
--- a/sys/dev/bnxt/bnxt_en/bnxt_hwrm.c
+++ b/sys/dev/bnxt/bnxt_en/bnxt_hwrm.c
@@ -1265,6 +1265,8 @@ bnxt_hwrm_func_qcaps(struct bnxt_softc *softc)
 		softc->fw_cap |= BNXT_FW_CAP_PTP_PTM;
 	if (flags_ext & HWRM_FUNC_QCAPS_OUTPUT_FLAGS_EXT_PTP_64BIT_RTC_SUPPORTED)
 		softc->fw_cap |= BNXT_FW_CAP_PTP_RTC;
+	if (BNXT_CHIP_P7(softc) && (flags & HWRM_FUNC_QCAPS_OUTPUT_FLAGS_PTP_SUPPORTED))
+		softc->fw_cap |= BNXT_FW_CAP_PTP;
 	if (BNXT_PF(softc) && (flags_ext & HWRM_FUNC_QCAPS_OUTPUT_FLAGS_EXT_HOT_RESET_IF_SUPPORT))
 		softc->fw_cap |= BNXT_FW_CAP_HOT_RESET_IF;
 	if (BNXT_PF(softc) && (flags_ext & HWRM_FUNC_QCAPS_OUTPUT_FLAGS_EXT_FW_LIVEPATCH_SUPPORTED))
diff --git a/sys/dev/bnxt/bnxt_en/bnxt_ptp.c b/sys/dev/bnxt/bnxt_en/bnxt_ptp.c
new file mode 100644
index 000000000000..73d0c24c63f1
--- /dev/null
+++ b/sys/dev/bnxt/bnxt_en/bnxt_ptp.c
@@ -0,0 +1,392 @@
+/*
+ * Broadcom NetXtreme-C/E network driver.
+ *
+ * Copyright (c) 2026 Broadcom, All Rights Reserved.
+ * The term Broadcom refers to Broadcom Limited and/or its subsidiaries
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/clock.h>
+#include <sys/seqc.h>
+#include <machine/atomic.h>
+#include "bnxt_ptp.h"
+#include "bnxt_hwrm.h"
+
+static int
+bnxt_map_ptp_regs(struct bnxt_softc *bp)
+{
+	struct bnxt_ptp_cfg *ptp = bp->ptp_cfg;
+	uint32_t *reg_arr;
+	int i;
+
+	reg_arr = ptp->refclk_regs;
+	for (i = 0; i < 2; i++) {
+		if (reg_arr[i] & BNXT_GRC_BASE_MASK)
+			return (EINVAL);
+		ptp->refclk_mapped_regs[i] = ptp->refclk_regs[i];
+	}
+
+	return (0);
+}
+
+static int
+bnxt_ptp_cfg_settime(struct bnxt_softc *bp, uint64_t time)
+{
+	struct hwrm_func_ptp_cfg_input req = {0};
+
+	bnxt_hwrm_cmd_hdr_init(bp, &req, HWRM_FUNC_PTP_CFG);
+
+	req.enables = htole16(HWRM_FUNC_PTP_CFG_INPUT_ENABLES_PTP_SET_TIME);
+	req.ptp_set_time = htole64(time);
+	return (hwrm_send_message(bp, &req, sizeof(req)));
+}
+
+static int
+bnxt_ptp_init_rtc(struct bnxt_softc *bp, bool phc_cfg)
+{
+	struct timespec ts;
+	uint64_t ns;
+	int rc;
+
+	if (!bp->ptp_cfg || !BNXT_PTP_USE_RTC(bp))
+		return (ENODEV);
+
+	if (!phc_cfg) {
+		nanotime(&ts);
+		ns = (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
+		rc = bnxt_ptp_cfg_settime(bp, ns);
+		if (rc)
+			return (rc);
+	}
+
+	return (0);
+}
+
+static int
+__bnxt_refclk_read(struct bnxt_softc *bp, uint64_t *ns)
+{
+	struct bnxt_ptp_cfg *ptp = bp->ptp_cfg;
+	uint32_t high_before, high_now, low;
+
+	if (test_bit(BNXT_STATE_IN_FW_RESET, &bp->state))
+		return (EIO);
+
+	/* Refclk registers live in the HWRM BAR, not the doorbell BAR. */
+	high_before = readl_fbsd(bp, ptp->refclk_mapped_regs[1], BNXT_HWRM_BAR_IDX);
+	low = readl_fbsd(bp, ptp->refclk_mapped_regs[0], BNXT_HWRM_BAR_IDX);
+	high_now = readl_fbsd(bp, ptp->refclk_mapped_regs[1], BNXT_HWRM_BAR_IDX);
+	if (high_now != high_before)
+		low = readl_fbsd(bp, ptp->refclk_mapped_regs[0], BNXT_HWRM_BAR_IDX);
+	*ns = (((uint64_t)high_now) << 32) | ((uint64_t)low);
+
+	return (0);
+}
+
+static int
+bnxt_refclk_read(struct bnxt_softc *bp, uint64_t *ns)
+{
+	int rc;
+
+	/* Serializes reg access vs FW reset. Must stay a spin mutex: the
+	 * calibration callout runs C_DIRECT_EXEC, sometimes from interrupt context. */
+	mtx_lock_spin(&bp->ptp_cfg->ptp_lock);
+	rc = __bnxt_refclk_read(bp, ns);
+	mtx_unlock_spin(&bp->ptp_cfg->ptp_lock);
+	return (rc);
+}
+
+uint64_t
+bnxt_ptp_hwtstamp_to_ns(struct bnxt_ptp_cfg *priv, uint64_t hw_tstmp)
+{
+	struct bnxt_clbr_point *cp, dcp;
+	sbintime_t sbt_cur_to_prev, sbt;
+	uint64_t hw_clocks;
+	uint64_t hw_clk_div;
+	seqc_t gen;
+
+	for (;;) {
+		cp = &priv->clbr_points[priv->clbr_curr];
+		/* Not seqc_read(): a permanently-invalidated slot stays odd
+		 * forever, which would spin here indefinitely. Bail out instead. */
+		gen = seqc_read_any(&cp->gen_ts);
+		if (seqc_in_modify(gen))
+			return (0);
+		dcp = *cp;
+		if (seqc_consistent(&cp->gen_ts, gen))
+			break;
+	}
+
+	/* Interpolate: (hw_tstmp - hw_prev) * (cur_time - prev_time) /
+	 * (hw_cur - hw_prev) + prev_time, using integer math to avoid float/overflow. */
+	if (hw_tstmp < dcp.hw_prev_ts)
+		return (0);
+
+	hw_clocks = hw_tstmp - dcp.hw_prev_ts;
+	sbt_cur_to_prev = (dcp.sbt_cur_ts - dcp.sbt_prev_ts);
+	hw_clk_div = dcp.hw_curr_ts - dcp.hw_prev_ts;
+
+	if (!hw_clk_div)
+		return (0);
+
+	/* Reject stale calibration intervals; also bounds the math below. */
+	if (hw_clk_div > BNXT_PTP_MAX_CLBR_INTERVAL_NS)
+		return (0);
+
+	/* Allow extrapolating up to one interval past hw_curr_ts; most RX
+	 * timestamps land there, not inside [hw_prev_ts, hw_curr_ts]. */
+	if (hw_clocks > 2 * hw_clk_div)
+		return (0);
+
+	/* Split the divide so hw_clocks * remainder can't overflow 64 bits. */
+	sbt = hw_clocks * (sbt_cur_to_prev / hw_clk_div) +
+	    hw_clocks * (sbt_cur_to_prev % hw_clk_div) / hw_clk_div +
+	    dcp.sbt_prev_ts;
+	return (sbttons(sbt));
+}
+
+static void
+bnxt_ptp_get_current_time(struct bnxt_softc *bp)
+{
+	struct bnxt_ptp_cfg *ptp = bp->ptp_cfg;
+
+	if (!ptp)
+		return;
+
+	atomic_store_64((volatile uint64_t *)&ptp->old_time,
+			ptp->current_time);
+	bnxt_refclk_read(bp, &ptp->current_time);
+}
+
+static void
+bnxt_calibration_callout(void *arg)
+{
+	struct bnxt_ptp_cfg *priv;
+	struct bnxt_clbr_point *next, *curr;
+	int clbr_curr_next;
+	sbintime_t sbt;
+	uint64_t nxt_clbr_hw_curr;
+
+	priv = arg;
+	curr = &priv->clbr_points[priv->clbr_curr];
+	clbr_curr_next = priv->clbr_curr + 1;
+	if (clbr_curr_next >= nitems(priv->clbr_points))
+		clbr_curr_next = 0;
+	next = &priv->clbr_points[clbr_curr_next];
+
+	bnxt_ptp_get_current_time(priv->bp);
+	sbt = sbinuptime();
+	nxt_clbr_hw_curr = priv->current_time;
+
+	if (priv->clbr_ticks &&
+	    ((nxt_clbr_hw_curr - curr->hw_curr_ts) >> BNXT_TSTMP_PREC) == 0) {
+		/* A stalled tick isn't necessarily a dead counter (HW may briefly
+		 * not count right after a reset); only disable after MAX_CLBR_STALLS. */
+		if (++priv->clbr_stall_cnt < BNXT_PTP_MAX_CLBR_STALLS) {
+			bnxt_reset_calibration_callout(priv);
+			return;
+		}
+		device_printf(priv->bp->dev,
+			"HW failed tstmp frozen, current %#jx prev %#jx, disabling\n",
+			(uintmax_t)nxt_clbr_hw_curr, (uintmax_t)curr->hw_curr_ts);
+		priv->clbr_ticks = 0;
+		priv->clbr_stall_cnt = 0;
+		/* Permanently invalidate by leaving gen_ts odd (seqcount's in-modify
+		 * state); not via seqc_write_begin() since we never pair it with _end(). */
+		atomic_store_rel_int(&curr->gen_ts, curr->gen_ts + SEQC_MOD);
+		return;
+	}
+
+	priv->clbr_stall_cnt = 0;
+	seqc_write_begin(&next->gen_ts);
+	next->sbt_prev_ts = curr->sbt_cur_ts;
+	next->hw_prev_ts = curr->hw_curr_ts;
+	next->hw_curr_ts = (priv->current_time) & BNXT_TIMER_MASK;
+	next->sbt_cur_ts = sbt;
+	priv->clbr_curr = clbr_curr_next;
+	seqc_write_end(&next->gen_ts);
+
+	priv->clbr_ticks++;
+	bnxt_reset_calibration_callout(priv);
+}
+
+void
+bnxt_reset_calibration_callout(struct bnxt_ptp_cfg *priv)
+{
+	if (priv->clbr_ticks == 0)
+		bnxt_calibration_callout(priv);
+	else
+		callout_reset_sbt_curcpu(&priv->tstmp_clbr, SBT_1S, 0,
+			bnxt_calibration_callout, priv, C_DIRECT_EXEC);
+}
+
+void
+bnxt_ptp_stop_calibration(struct bnxt_ptp_cfg *priv)
+{
+	/* Reset clbr_ticks so the next bnxt_reset_calibration_callout() calibrates
+	 * immediately; clbr_points is stale after a down/up cycle. */
+	priv->clbr_ticks = 0;
+	priv->clbr_stall_cnt = 0;
+	memset(priv->clbr_points, 0, sizeof(priv->clbr_points));
+}
+
+void
+bnxt_ptp_free(struct bnxt_softc *bp)
+{
+	struct bnxt_ptp_cfg *ptp = bp->ptp_cfg;
+
+	if (!ptp)
+		return;
+
+	/* ptp_lock and tstmp_clbr are set up together, so drain the callout
+	 * before destroying the lock it takes, in case one is still in flight. */
+	if (mtx_initialized(&ptp->ptp_lock)) {
+		callout_drain(&ptp->tstmp_clbr);
+		mtx_destroy(&ptp->ptp_lock);
+	}
+	free(ptp, M_DEVBUF);
+	bp->ptp_cfg = NULL;
+}
+
+static int
+bnxt_ptp_init(struct bnxt_softc *bp)
+{
+	struct bnxt_ptp_cfg *ptp = bp->ptp_cfg;
+	int rc;
+
+	if (!ptp)
+		return (0);
+
+	rc = bnxt_map_ptp_regs(bp);
+	if (rc)
+		return (rc);
+
+	if (BNXT_PTP_USE_RTC(bp)) {
+		rc = bnxt_ptp_init_rtc(bp, ptp->rtc_configured);
+		if (rc)
+			goto out;
+	}
+
+	mtx_init(&ptp->ptp_lock, "BNXT PTP LOCK", NULL,
+	    MTX_SPIN | MTX_NOWITNESS);
+	/* Init unconditionally so bnxt_ptp_free() can always safely drain it
+	 * whenever ptp_lock is initialized. */
+	callout_init(&ptp->tstmp_clbr, 1);
+
+	bnxt_refclk_read(bp, &ptp->current_time);
+	atomic_store_64((volatile uint64_t *)&ptp->old_time, ptp->current_time);
+
+	return (0);
+out:
+	/* Nothing to unmap: bnxt_map_ptp_regs() rejects GRC-window regs before mapping. */
+	return (rc);
+}
+
+int
+bnxt_hwrm_ptp_qcfg(struct bnxt_softc *bp)
+{
+	struct hwrm_port_mac_ptp_qcfg_output *resp =
+	    (void *)bp->hwrm_cmd_resp.idi_vaddr;
+	struct hwrm_port_mac_ptp_qcfg_input req = {0};
+	struct bnxt_ptp_cfg *ptp = bp->ptp_cfg;
+	uint8_t flags;
+	int rc;
+
+	if (!BNXT_CHIP_P7(bp)) {
+		rc = ENODEV;
+		goto no_ptp;
+	}
+
+	bnxt_hwrm_cmd_hdr_init(bp, &req, HWRM_PORT_MAC_PTP_QCFG);
+
+	req.port_id = htole16(bp->pf.port_id);
+	rc = hwrm_send_message(bp, &req, sizeof(req));
+	if (rc)
+		goto exit;
+
+	flags = resp->flags;
+	if (!ptp)
+		ptp = malloc(sizeof(*ptp), M_DEVBUF, M_NOWAIT | M_ZERO);
+
+	if (!ptp) {
+		rc = ENOMEM;
+		goto exit;
+	}
+
+	if (flags &
+	  (HWRM_PORT_MAC_PTP_QCFG_OUTPUT_FLAGS_PARTIAL_DIRECT_ACCESS_REF_CLOCK |
+	  HWRM_PORT_MAC_PTP_QCFG_OUTPUT_FLAGS_64B_PHC_TIME)) {
+		ptp->refclk_regs[0] = le32toh(resp->ts_ref_clock_reg_lower);
+		ptp->refclk_regs[1] = le32toh(resp->ts_ref_clock_reg_upper);
+	}
+
+	ptp->bp = bp;
+	bp->ptp_cfg = ptp;
+
+	ptp->rtc_configured =
+	  (flags & HWRM_PORT_MAC_PTP_QCFG_OUTPUT_FLAGS_RTC_CONFIGURED) != 0;
+	rc = bnxt_ptp_init(bp);
+	if (rc)
+		device_printf(bp->dev, "PTP initialization failed.\n");
+exit:
+	if (!rc)
+		return (0);
+
+no_ptp:
+	/* Use bnxt_ptp_free(), not free(): ptp may already have a mutex/callout
+	 * set up from a prior call, which a plain free() would leak. */
+	bnxt_ptp_free(bp);
+	return (rc);
+}
+
+int
+bnxt_ptp_cfg_tstamp_filters(struct bnxt_softc *bp)
+{
+	struct bnxt_ptp_cfg *ptp = bp->ptp_cfg;
+	struct hwrm_port_mac_cfg_input req = {0};
+	int rc;
+
+	if (!ptp)
+		return (EOPNOTSUPP);
+
+	bnxt_hwrm_cmd_hdr_init(bp, &req, HWRM_PORT_MAC_CFG);
+
+	if (!(bp->fw_cap & BNXT_FW_CAP_RX_ALL_PKT_TS)) {
+		device_printf(bp->dev,
+		    "Unsupported FW for all RX pkts timestamp filter\n");
+		return (EOPNOTSUPP);
+	}
+
+	req.flags =
+	    htole32(HWRM_PORT_MAC_CFG_INPUT_FLAGS_ALL_RX_TS_CAPTURE_ENABLE);
+	req.enables =
+	    htole32(HWRM_PORT_MAC_CFG_INPUT_ENABLES_RX_TS_CAPTURE_PTP_MSG_TYPE);
+
+	rc = hwrm_send_message(bp, &req, sizeof(req));
+	if (rc)
+		device_printf(bp->dev,
+		    "Failed to configure HW packet timestamp filters\n");
+
+	return (rc);
+}
diff --git a/sys/dev/bnxt/bnxt_en/bnxt_ptp.h b/sys/dev/bnxt/bnxt_en/bnxt_ptp.h
new file mode 100644
index 000000000000..81a40800a2a6
--- /dev/null
+++ b/sys/dev/bnxt/bnxt_en/bnxt_ptp.h
@@ -0,0 +1,88 @@
+/*
+ * Broadcom NetXtreme-C/E network driver.
+ *
+ * Copyright (c) 2026 Broadcom, All Rights Reserved.
+ * The term Broadcom refers to Broadcom Limited and/or its subsidiaries
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BNXT_PTP_H
+#define BNXT_PTP_H
+
+#include "bnxt.h"
+
+/*
+ * BNXT_GRCPF_REG_CHIMP_COMM, BNXT_GRCPF_REG_CHIMP_COMM_TRIGGER,
+ * BNXT_GRCPF_REG_WINDOW_BASE_OUT, BNXT_GRC_BASE_MASK, and
+ * BNXT_GRC_OFFSET_MASK are already defined in bnxt.h, included above.
+ */
+
+#define BNXT_TIMER_MASK	0x0000ffffffffffffUL
+
+#define BNXT_LO_TIMER_MASK	0x0000ffffffffUL
+#define BNXT_HI_TIMER_MASK	0xffff00000000UL
+
+#define BNXT_PTP_USE_RTC(bp)	((bp)->fw_cap & BNXT_FW_CAP_PTP_RTC)
+
+#define BNXT_TSTMP_PREC 10
+
+/* 1s cadence plus callout jitter; wider gaps are too stale to use. */
+#define BNXT_PTP_MAX_CLBR_INTERVAL_NS	2000000000ULL
+
+/* Consecutive stalled 1s ticks required before declaring the counter frozen;
+ * a single stall right after a reset isn't necessarily dead. */
+#define BNXT_PTP_MAX_CLBR_STALLS	3
+
+struct bnxt_clbr_point {
+	uint64_t hw_curr_ts;
+	uint64_t hw_prev_ts;
+	sbintime_t sbt_cur_ts;
+	sbintime_t sbt_prev_ts;
+	seqc_t gen_ts;
+};
+
+struct bnxt_ptp_cfg {
+	uint64_t		current_time;
+	uint64_t		old_time;
+	struct bnxt_softc	*bp;
+	uint8_t			rtc_configured:1;
+
+	uint32_t		refclk_regs[2];
+	uint32_t		refclk_mapped_regs[2];
+
+	struct callout		tstmp_clbr;
+	int			clbr_ticks;
+	int			clbr_curr;
+	int			clbr_stall_cnt;
+	struct bnxt_clbr_point	clbr_points[2];
+
+	struct mtx		ptp_lock;
+};
+
+void bnxt_reset_calibration_callout(struct bnxt_ptp_cfg *priv);
+void bnxt_ptp_stop_calibration(struct bnxt_ptp_cfg *priv);
+int bnxt_ptp_cfg_tstamp_filters(struct bnxt_softc *bp);
+uint64_t bnxt_ptp_hwtstamp_to_ns(struct bnxt_ptp_cfg *priv, uint64_t hw_tstmp);
+void bnxt_ptp_free(struct bnxt_softc *bp);
+
+#endif /* BNXT_PTP_H */
diff --git a/sys/dev/bnxt/bnxt_en/bnxt_txrx.c b/sys/dev/bnxt/bnxt_en/bnxt_txrx.c
index 3e867454de8a..8e95cca12a65 100644
--- a/sys/dev/bnxt/bnxt_en/bnxt_txrx.c
+++ b/sys/dev/bnxt/bnxt_en/bnxt_txrx.c
@@ -38,7 +38,9 @@
 #include "opt_inet6.h"
 #include "opt_rss.h"
 
+#include <machine/atomic.h>
 #include "bnxt.h"
+#include "bnxt_ptp.h"
 
 /*
  * Function prototypes
@@ -495,9 +497,11 @@ bnxt_pkt_get_l2(struct bnxt_softc *softc, if_rxd_info_t ri,
 {
 	struct rx_pkt_cmpl *rcp;
 	struct rx_pkt_cmpl_hi *rcph;
+	struct rx_pkt_v2_cmpl_hi *rcph_v2;
 	struct rx_abuf_cmpl *acp;
 	uint32_t flags2;
 	uint32_t errors;
+	uint64_t ts, ts_lo, ts_poll;
 	uint8_t	ags;
 	int i;
 
@@ -551,6 +555,24 @@ bnxt_pkt_get_l2(struct bnxt_softc *softc, if_rxd_info_t ri,
 		}
 	}
 
+	if (softc->rx_ts_enabled && softc->ptp_cfg != NULL) {
+		rcph_v2 = (struct rx_pkt_v2_cmpl_hi *)rcph;
+		ts_poll = atomic_load_acq_64(
+		    (volatile uint64_t *)&softc->ptp_cfg->old_time);
+		ts_lo = le32toh(rcph_v2->timestamp);
+		ts = (ts_poll & BNXT_HI_TIMER_MASK) | ts_lo;
+		if (ts_lo < (ts_poll & BNXT_LO_TIMER_MASK)) {
+			ts += BNXT_LO_TIMER_MASK + 1;
+		}
+		ts = bnxt_ptp_hwtstamp_to_ns(softc->ptp_cfg, ts);
+#ifdef IFLIB_IRI_VALID_FLAGS
+		if (ts != 0) {
+			ri->iri_rcv_tstmp = ts;
+			ri->iri_flags |= M_TSTMP;
+		}
+#endif
+	}
+
 	/* And finally the ag ring stuff. */
 	for (i=1; i < ri->iri_nfrags; i++) {
 		NEXT_CP_CONS_V(&cpr->ring, cpr->cons, cpr->v_bit);
diff --git a/sys/dev/bnxt/bnxt_en/if_bnxt.c b/sys/dev/bnxt/bnxt_en/if_bnxt.c
index 205d98585a1e..044ab619c399 100644
--- a/sys/dev/bnxt/bnxt_en/if_bnxt.c
+++ b/sys/dev/bnxt/bnxt_en/if_bnxt.c
@@ -75,6 +75,7 @@
 #include "bnxt_log.h"
 #include "bnxt_log_data.h"
 #include "bnxt_coredump.h"
+#include "bnxt_ptp.h"
 
 /*
  * PCI Device ID Table
@@ -2901,6 +2902,17 @@ bnxt_attach_pre(if_ctx_t ctx)
 	if (rc)
 		goto failed;
 
+	if (softc->fw_cap & BNXT_FW_CAP_PTP) {
+		rc = bnxt_hwrm_ptp_qcfg(softc);
+		if (!rc) {
+			scctx->isc_capabilities |= IFCAP_HWRXTSTMP;
+			scctx->isc_capenable |= IFCAP_HWRXTSTMP;
+		} else {
+			/* PTP is an optional capability; do not fail attach. */
+			rc = 0;
+		}
+	}
+
 	set_bit(BNXT_STATE_OPEN, &softc->state);
 	INIT_WORK(&softc->sp_task, bnxt_sp_task);
 	INIT_DELAYED_WORK(&softc->fw_reset_task, bnxt_fw_reset_task);
@@ -3005,6 +3017,7 @@ bnxt_detach(if_ctx_t ctx)
 	bnxt_num_pfs--;
 	bnxt_wol_config(ctx);
 	bnxt_do_disable_intr(&softc->def_cp_ring);
+	bnxt_ptp_free(softc);
 	bnxt_free_sysctl_ctx(softc);
 	bnxt_free_crash_dump_mem(softc);
 	bnxt_hwrm_func_reset(softc);
@@ -3313,6 +3326,14 @@ bnxt_init_hw(if_ctx_t ctx)
 	if (rc)
 		goto fail;
 skip_def_cp_ring:
+	if (softc->scctx->isc_capenable & IFCAP_HWRXTSTMP) {
+		if (!bnxt_ptp_cfg_tstamp_filters(softc)) {
+			softc->rx_ts_enabled = true;
+			/* tstmp_clbr is callout_init()'d in bnxt_ptp_init(). */
+			bnxt_reset_calibration_callout(softc->ptp_cfg);
+		}
+	}
+
 	for (i = 0; i < softc->nrxqsets; i++) {
 		/* Allocate the statistics context */
 		rc = bnxt_hwrm_stat_ctx_alloc(softc, &softc->rx_cp_rings[i],
@@ -3480,6 +3501,11 @@ bnxt_stop(if_ctx_t ctx)
 {
 	struct bnxt_softc *softc = iflib_get_softc(ctx);
 
+	if (softc->rx_ts_enabled) {
+		callout_drain(&softc->ptp_cfg->tstmp_clbr);
+		bnxt_ptp_stop_calibration(softc->ptp_cfg);
+		softc->rx_ts_enabled = false;
+	}
 	softc->is_dev_init = false;
 	bnxt_do_disable_intr(&softc->def_cp_ring);
 	bnxt_func_reset(softc);
diff --git a/sys/modules/bnxt/bnxt_en/Makefile b/sys/modules/bnxt/bnxt_en/Makefile
index e3cc3e79377c..67bd2646fa4d 100644
--- a/sys/modules/bnxt/bnxt_en/Makefile
+++ b/sys/modules/bnxt/bnxt_en/Makefile
@@ -14,6 +14,7 @@ SRCS	+= bnxt_ulp.c bnxt_ulp.h
 SRCS	+= bnxt_log.c bnxt_log.h
 SRCS	+= bnxt_log_data.c bnxt_log_data.h
 SRCS	+= bnxt_coredump.c bnxt_coredump.h
+SRCS	+= bnxt_ptp.c bnxt_ptp.h
 SRCS	+= ${LINUXKPI_GENSRCS}
 
 CFLAGS+= -DIFLIB