Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 19 Jul 2026 16:49:17 +0000
From:      Adrian Chadd <adrian@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Cc:        Nick Price <nick@spun.io>
Subject:   git: dc5c0fc51cf4 - main - aq(4): add Atlantic 2 (AQC113) device support
Message-ID:  <6a5d000d.32586.23fe9733@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by adrian:

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

commit dc5c0fc51cf47d6dfc737343e06805db294258e9
Author:     Nick Price <nick@spun.io>
AuthorDate: 2026-07-19 16:48:05 +0000
Commit:     Adrian Chadd <adrian@FreeBSD.org>
CommitDate: 2026-07-19 16:48:05 +0000

    aq(4): add Atlantic 2 (AQC113) device support
    
    Add support for the Marvell Atlantic 2 (AQC113/114/115/116) controllers,
    a new chip generation that is not register-compatible with the Atlantic 1
    parts aq(4) supports today.  Adapted from the OpenBSD/NetBSD if_aq driver.
    
    Register and device definitions (aq2_hw.h): the firmware handshake
    (MIF_BOOT / MCP_HOST_REQ_INT / MIF_HOST_FINISHED), the 0x12000/0x13000
    firmware interface windows, and the action-resolver table (ART) that
    replaces Atlantic 1's discrete RX filters, plus the Atlantic 2 PCI device
    ids and the aq_is_atlantic2() helper.  Reserve a chip-feature bit
    (AQ_HW_CHIP_ATLANTIC2) and add the aq_hw fields the firmware fills at boot
    (ART base index, statistics interface version A0/B0).  The per-VLAN-filter
    resolver-tag field comes from the Linux driver; the BSD sources never
    write it.
    
    Firmware operations (aq_fwa2.c): Atlantic 2 talks to the management CPU
    through the 0x12000/0x13000 register windows plus the boot handshake,
    rather than Atlantic 1's mailbox in shared RAM.  Implement that as a third
    aq_firmware_ops vtable (reset, set_mode, get_mode, get_mac_addr,
    get_stats); aq_fwa2_reboot() boots the firmware, selects the A2 ops, and
    reads the version and ART base index, failing fast on the
    crash-init / boot-failed bits.  fwa2_set_mode advertises full duplex only
    (the media model exposes no half-duplex types) and writes and acks the
    link options before raising ACTIVE mode, so a forced media change does not
    begin negotiation with a stale rate mask.  enum aq_fw_link_speed gains
    aq_fw_10M, which Atlantic 2 supports and Atlantic 1 does not.
    
    Probe and attach: list the device ids with their media types and link
    speeds (all copper; AQC113* up to 10G, AQC116C to 1G), populate
    hw->device_id, and tag the generation with AQ_HW_CHIP_ATLANTIC2 so
    IS_CHIP_FEATURE() recognises it uniformly.  Branch firmware bring-up and
    reset on the generation: aq_hw_init_ucp() and aq_hw_reset() reboot the MCP
    instead of the Atlantic 1 RBL/FLB reset -- without a real datapath reset
    every stop/init cycle reprograms the rings on a live, desynced RX DMA
    engine and the receive path stays dead.  aq_hw_init() programs the
    Atlantic 2 launch-time clock ratio in place of the Atlantic 1
    MRRS / TX-DMA request-limit clamp.  Add an AQ_LINK_10M capability bit
    (Atlantic 2 links at 10M, Atlantic 1 cannot), offer 10baseT media, and map
    IFM_10_T to aq_fw_10M.
    
    With every supported media type now present, replace the per-speed switch
    statements in aq_media.c with a single {link bit, fw rate, IFM_* subtype,
    Mbit/s} table -- one source of truth for the supported link speeds.
    
    With this an Atlantic 2 card probes, brings up its firmware, reads its
    MAC, and negotiates link; the RX action-resolver datapath comes next.
    
    Reviewed by:    adrian
    Differential Revision:  https://reviews.freebsd.org/D58141
---
 sys/conf/files          |   1 +
 sys/dev/aq/aq2_fw.c     | 447 ++++++++++++++++++++++++++++++++++++++++++++++++
 sys/dev/aq/aq2_hw.h     | 276 ++++++++++++++++++++++++++++++
 sys/dev/aq/aq_device.h  |  20 ++-
 sys/dev/aq/aq_fw.h      |  16 +-
 sys/dev/aq/aq_hw.c      |  49 ++++--
 sys/dev/aq/aq_hw.h      |   7 +
 sys/dev/aq/aq_main.c    |  51 +++++-
 sys/dev/aq/aq_media.c   | 101 +++++------
 sys/modules/aq/Makefile |   2 +-
 10 files changed, 883 insertions(+), 87 deletions(-)

diff --git a/sys/conf/files b/sys/conf/files
index 3b5ba83eda28..fcce58183b9b 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -858,6 +858,7 @@ dev/aq/aq_hw_llh.c		optional aq
 dev/aq/aq_fw.c			optional aq
 dev/aq/aq_fw1x.c		optional aq
 dev/aq/aq_fw2x.c		optional aq
+dev/aq/aq2_fw.c		optional aq
 dev/aq/aq_dbg.c			optional aq
 #
 dev/ata/ata_if.m		optional ata | atacore
diff --git a/sys/dev/aq/aq2_fw.c b/sys/dev/aq/aq2_fw.c
new file mode 100644
index 000000000000..76152a9c07bb
--- /dev/null
+++ b/sys/dev/aq/aq2_fw.c
@@ -0,0 +1,447 @@
+/*-
+ * Atlantic 2 (AQC113/114/115/116) firmware operations for aq(4).
+ *
+ * Adapted from the OpenBSD/NetBSD if_aq driver:
+ *
+ * Copyright (c) 2021 Jonathan Matthew <jonathan@d14n.org>
+ * Copyright (c) 2021 Mike Larkin <mlarkin@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/cdefs.h>
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/errno.h>
+#include <sys/endian.h>
+#include <net/ethernet.h>
+
+#include "aq_common.h"
+#include "aq_hw.h"
+#include "aq_hw_llh.h"
+#include "aq2_hw.h"
+#include "aq_fw.h"
+
+static int aq2_fw_reset(struct aq_hw *hw);
+static int aq2_fw_set_mode(struct aq_hw *hw, enum aq_hw_fw_mpi_state mode,
+    enum aq_fw_link_speed speed);
+static int aq2_fw_get_mode(struct aq_hw *hw, enum aq_hw_fw_mpi_state *mode,
+    enum aq_fw_link_speed *speed, enum aq_fw_link_fc *fc);
+static int aq2_fw_get_mac_addr(struct aq_hw *hw, uint8_t *mac);
+static int aq2_fw_get_stats(struct aq_hw *hw, struct aq_hw_stats *stats);
+
+/* Coherent OUT-window read, bracketed by the transaction id. */
+static int
+aq2_fw_interface_buffer_read(struct aq_hw *hw, uint32_t reg0, uint32_t *data0,
+    uint32_t size0)
+{
+	uint32_t tid0, tid1, reg, *data, size;
+	int timo;
+
+	for (timo = 10000; timo > 0; timo--) {
+		tid0 = AQ_READ_REG(hw, AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_REG);
+		if (((tid0 & AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_A) >>
+		    AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_A_S) !=
+		    ((tid0 & AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_B) >>
+		    AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_B_S)) {
+			DELAY(10);
+			continue;
+		}
+
+		/* size0 is a 4-byte multiple: full register-width reads. */
+		for (reg = reg0, data = data0, size = size0;
+		    size >= 4; reg += 4, data++, size -= 4)
+			*data = AQ_READ_REG(hw, reg);
+
+		tid1 = AQ_READ_REG(hw, AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_REG);
+		if (tid0 == tid1)
+			break;
+	}
+	if (timo == 0) {
+		device_printf(hw->dev, "A2 interface buffer read timeout\n");
+		return (ETIMEDOUT);
+	}
+	return (0);
+}
+
+/* Boot the A2 firmware and select the A2 firmware ops. */
+int
+aq2_fw_reboot(struct aq_hw *hw)
+{
+	uint32_t v, filter_caps[3];
+	int timo, err;
+	const char *iface;
+
+	hw->fw_ops = &aq2_fw_ops;
+
+	AQ_WRITE_REG(hw, AQ2_MCP_HOST_REQ_INT_CLR_REG, 1);
+	AQ_WRITE_REG(hw, AQ2_MIF_BOOT_REG, 1);	/* reboot request */
+	for (timo = 20000; timo > 0; timo--) {
+		v = AQ_READ_REG(hw, AQ2_MIF_BOOT_REG);
+		if ((v & AQ2_MIF_BOOT_BOOT_STARTED) && v != 0xffffffff)
+			break;
+		DELAY(10);
+	}
+	if (timo <= 0) {
+		device_printf(hw->dev, "A2 firmware reboot timeout\n");
+		return (ETIMEDOUT);
+	}
+
+	for (timo = 200000; timo > 0; timo--) {
+		v = AQ_READ_REG(hw, AQ2_MIF_BOOT_REG);
+		if (v & AQ2_MIF_BOOT_COMPLETE)
+			break;
+		v = AQ_READ_REG(hw, AQ2_MCP_HOST_REQ_INT_REG);
+		if (v & AQ2_MCP_HOST_REQ_INT_READY)
+			break;
+		DELAY(10);
+	}
+	if (timo <= 0) {
+		device_printf(hw->dev, "A2 firmware restart timeout\n");
+		return (ETIMEDOUT);
+	}
+
+	v = AQ_READ_REG(hw, AQ2_MIF_BOOT_REG);
+	if (v & AQ2_MIF_BOOT_FAILED) {
+		device_printf(hw->dev, "A2 firmware restart failed\n");
+		return (EIO);
+	}
+	v = AQ_READ_REG(hw, AQ2_MCP_HOST_REQ_INT_REG);
+	if (v & AQ2_MCP_HOST_REQ_INT_READY) {
+		device_printf(hw->dev, "A2 firmware required but not present\n");
+		return (ENXIO);
+	}
+
+	/* Repack into fw_version's major.minor.build layout. */
+	err = aq2_fw_interface_buffer_read(hw,
+	    AQ2_FW_INTERFACE_OUT_VERSION_BUNDLE_REG, &v, sizeof(v));
+	if (err != 0)
+		return (err);
+	hw->fw_version.raw =
+	    (((v & AQ2_FW_INTERFACE_OUT_VERSION_MAJOR) >>
+		AQ2_FW_INTERFACE_OUT_VERSION_MAJOR_S) << 24) |
+	    (((v & AQ2_FW_INTERFACE_OUT_VERSION_MINOR) >>
+		AQ2_FW_INTERFACE_OUT_VERSION_MINOR_S) << 16) |
+	    ((v & AQ2_FW_INTERFACE_OUT_VERSION_BUILD) >>
+		AQ2_FW_INTERFACE_OUT_VERSION_BUILD_S);
+
+	err = aq2_fw_interface_buffer_read(hw,
+	    AQ2_FW_INTERFACE_OUT_VERSION_IFACE_REG, &v, sizeof(v));
+	if (err != 0)
+		return (err);
+	hw->aq2_iface = v & AQ2_FW_INTERFACE_OUT_VERSION_IFACE_VER;
+	switch (hw->aq2_iface) {
+	case AQ2_FW_INTERFACE_OUT_VERSION_IFACE_VER_A0:
+		iface = "A0";
+		break;
+	case AQ2_FW_INTERFACE_OUT_VERSION_IFACE_VER_B0:
+		iface = "B0";
+		break;
+	default:
+		iface = "unknown";
+		break;
+	}
+	device_printf(hw->dev, "Atlantic 2 %s, firmware %u.%u.%u\n", iface,
+	    hw->fw_version.major_version, hw->fw_version.minor_version,
+	    hw->fw_version.build_number);
+
+	/* Base row added to every action-resolver-table index. */
+	err = aq2_fw_interface_buffer_read(hw,
+	    AQ2_FW_INTERFACE_OUT_FILTER_CAPS_REG, filter_caps,
+	    sizeof(filter_caps));
+	if (err != 0)
+		return (err);
+	hw->art_filter_base_index = ((filter_caps[2] &
+	    AQ2_FW_INTERFACE_OUT_FILTER_CAPS3_RESOLVER_BASE_INDEX) >>
+	    AQ2_FW_INTERFACE_OUT_FILTER_CAPS3_RESOLVER_BASE_INDEX_SHIFT) * 8;
+
+	return (0);
+}
+
+/* Commit an IN-window write; wait for the MCP ack. */
+static int
+aq2_fw_wait_shared_ack(struct aq_hw *hw)
+{
+	AQ_WRITE_REG(hw, AQ2_MIF_HOST_FINISHED_STATUS_WRITE_REG,
+	    AQ2_MIF_HOST_FINISHED_STATUS_ACK);
+	return (AQ_HW_WAIT_FOR((AQ_READ_REG(hw,
+	    AQ2_MIF_HOST_FINISHED_STATUS_READ_REG) &
+	    AQ2_MIF_HOST_FINISHED_STATUS_ACK) == 0, 100, 1000));
+}
+
+static int
+aq2_fw_reset(struct aq_hw *hw)
+{
+	uint32_t v;
+	int err;
+
+	AQ_WRITE_REG_BIT(hw, AQ2_FW_INTERFACE_IN_LINK_CONTROL_REG,
+	    AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE, 0,
+	    AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE_ACTIVE);
+
+	AQ_WRITE_REG(hw, AQ2_FW_INTERFACE_IN_MTU_REG, HW_ATL_B0_MTU_JUMBO);
+
+	v = AQ_READ_REG(hw, AQ2_FW_INTERFACE_IN_REQUEST_POLICY_REG);
+	v |= AQ2_FW_INTERFACE_IN_REQUEST_POLICY_MCAST_QUEUE_OR_TC;
+	v &= ~AQ2_FW_INTERFACE_IN_REQUEST_POLICY_MCAST_RX_QUEUE_TC_INDEX;
+	v |= AQ2_FW_INTERFACE_IN_REQUEST_POLICY_MCAST_ACCEPT;
+	v |= AQ2_FW_INTERFACE_IN_REQUEST_POLICY_BCAST_QUEUE_OR_TC;
+	v &= ~AQ2_FW_INTERFACE_IN_REQUEST_POLICY_BCAST_RX_QUEUE_TC_INDEX;
+	v |= AQ2_FW_INTERFACE_IN_REQUEST_POLICY_BCAST_ACCEPT;
+	v |= AQ2_FW_INTERFACE_IN_REQUEST_POLICY_PROMISC_QUEUE_OR_TC;
+	v &= ~AQ2_FW_INTERFACE_IN_REQUEST_POLICY_PROMISC_RX_QUEUE_TX_INDEX;
+	AQ_WRITE_REG(hw, AQ2_FW_INTERFACE_IN_REQUEST_POLICY_REG, v);
+
+	err = aq2_fw_wait_shared_ack(hw);
+	if (err != 0)
+		device_printf(hw->dev, "A2 firmware reset timed out\n");
+	return (err);
+}
+
+static int
+aq2_fw_get_mac_addr(struct aq_hw *hw, uint8_t *mac)
+{
+	uint32_t mac_addr[2];
+
+	mac_addr[0] = AQ_READ_REG(hw, AQ2_FW_INTERFACE_IN_MAC_ADDRESS_REG);
+	mac_addr[1] = AQ_READ_REG(hw, AQ2_FW_INTERFACE_IN_MAC_ADDRESS_REG + 4);
+
+	if (mac_addr[0] == 0 && mac_addr[1] == 0) {
+		device_printf(hw->dev, "A2 mac address not found\n");
+		return (ENXIO);
+	}
+
+	mac_addr[0] = htole32(mac_addr[0]);
+	mac_addr[1] = htole32(mac_addr[1]);
+	memcpy(mac, (uint8_t *)mac_addr, ETHER_ADDR_LEN);
+	return (0);
+}
+
+static int
+aq2_fw_set_mode(struct aq_hw *hw, enum aq_hw_fw_mpi_state mode,
+    enum aq_fw_link_speed speed)
+{
+	uint32_t v;
+	int err;
+
+	v = AQ_READ_REG(hw, AQ2_FW_INTERFACE_IN_LINK_OPTIONS_REG);
+	v &= ~(AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_10G |
+	    AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_N5G |
+	    AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_5G |
+	    AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_N2G5 |
+	    AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_2G5 |
+	    AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_1G |
+	    AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_100M |
+	    AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_10M |
+	    AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_1G_HD |
+	    AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_100M_HD |
+	    AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_10M_HD);
+	v &= ~AQ2_FW_INTERFACE_IN_LINK_OPTIONS_LINK_UP;
+
+	if (mode == MPI_INIT) {
+		if (speed & aq_fw_10G)
+			v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_10G;
+		if (speed & aq_fw_5G)
+			v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_N5G |
+			    AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_5G;
+		if (speed & aq_fw_2G5)
+			v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_N2G5 |
+			    AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_2G5;
+		if (speed & aq_fw_1G)
+			v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_1G;
+		if (speed & aq_fw_100M)
+			v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_100M;
+		if (speed & aq_fw_10M)
+			v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_10M;
+
+		v &= ~(AQ2_FW_INTERFACE_IN_LINK_OPTIONS_PAUSE_TX |
+		    AQ2_FW_INTERFACE_IN_LINK_OPTIONS_PAUSE_RX);
+		if (hw->fc.fc_tx)
+			v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_PAUSE_TX;
+		if (hw->fc.fc_rx)
+			v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_PAUSE_RX;
+
+		v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_LINK_UP;
+	} else {
+		AQ_WRITE_REG_BIT(hw, AQ2_FW_INTERFACE_IN_LINK_CONTROL_REG,
+		    AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE, 0,
+		    AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE_SHUTDOWN);
+	}
+
+	/* Options acked before ACTIVE so bring-up negotiates the new mask. */
+	AQ_WRITE_REG(hw, AQ2_FW_INTERFACE_IN_LINK_OPTIONS_REG, v);
+	if (mode == MPI_INIT) {
+		err = aq2_fw_wait_shared_ack(hw);
+		if (err != 0)
+			return (err);
+		AQ_WRITE_REG_BIT(hw, AQ2_FW_INTERFACE_IN_LINK_CONTROL_REG,
+		    AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE, 0,
+		    AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE_ACTIVE);
+	}
+	return (aq2_fw_wait_shared_ack(hw));
+}
+
+static int
+aq2_fw_get_mode(struct aq_hw *hw, enum aq_hw_fw_mpi_state *modep,
+    enum aq_fw_link_speed *speedp, enum aq_fw_link_fc *fcp)
+{
+	uint32_t v;
+	enum aq_fw_link_speed speed;
+	enum aq_fw_link_fc fc = aq_fw_fc_none;
+
+	if (modep != NULL)
+		*modep = MPI_INIT;
+
+	v = AQ_READ_REG(hw, AQ2_FW_INTERFACE_OUT_LINK_STATUS_REG);
+	switch ((v & AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE) >>
+	    AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_S) {
+	case AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_10G:
+		speed = aq_fw_10G;
+		break;
+	case AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_5G:
+		speed = aq_fw_5G;
+		break;
+	case AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_2G5:
+		speed = aq_fw_2G5;
+		break;
+	case AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_1G:
+		speed = aq_fw_1G;
+		break;
+	case AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_100M:
+		speed = aq_fw_100M;
+		break;
+	case AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_10M:
+		speed = aq_fw_10M;
+		break;
+	default:
+		speed = aq_fw_none;
+		break;
+	}
+	if (speedp != NULL)
+		*speedp = speed;
+
+	if (v & AQ2_FW_INTERFACE_OUT_LINK_STATUS_PAUSE_TX)
+		fc |= aq_fw_fc_ENABLE_TX;
+	if (v & AQ2_FW_INTERFACE_OUT_LINK_STATUS_PAUSE_RX)
+		fc |= aq_fw_fc_ENABLE_RX;
+	if (fcp != NULL)
+		*fcp = fc;
+
+	return (0);
+}
+
+/* A2 firmware stats; layout depends on interface version. */
+struct aq2_fw_statistics_a0 {
+	uint32_t link_up;
+	uint32_t link_down;
+	uint64_t tx_unicast_octets;
+	uint64_t tx_multicast_octets;
+	uint64_t tx_broadcast_octets;
+	uint64_t rx_unicast_octets;
+	uint64_t rx_multicast_octets;
+	uint64_t rx_broadcast_octets;
+	uint32_t tx_unicast_frames;
+	uint32_t tx_multicast_frames;
+	uint32_t tx_broadcast_frames;
+	uint32_t tx_errors;
+	uint32_t rx_unicast_frames;
+	uint32_t rx_multicast_frames;
+	uint32_t rx_broadcast_frames;
+	uint32_t rx_dropped_frames;
+	uint32_t rx_errors;
+	uint32_t tx_good_frames;
+	uint32_t rx_good_frames;
+	uint32_t reserved1;
+	uint32_t main_loop_cycles;
+	uint32_t reserved2;
+};
+
+struct aq2_fw_statistics_b0 {
+	uint64_t rx_good_octets;
+	uint64_t rx_pause_frames;
+	uint64_t rx_good_frames;
+	uint64_t rx_errors;
+	uint64_t rx_unicast_frames;
+	uint64_t rx_multicast_frames;
+	uint64_t rx_broadcast_frames;
+	uint64_t tx_good_octets;
+	uint64_t tx_pause_frames;
+	uint64_t tx_good_frames;
+	uint64_t tx_errors;
+	uint64_t tx_unicast_frames;
+	uint64_t tx_multicast_frames;
+	uint64_t tx_broadcast_frames;
+	uint32_t main_loop_cycles;
+} __packed;
+
+union aq2_fw_statistics {
+	struct aq2_fw_statistics_a0 a0;
+	struct aq2_fw_statistics_b0 b0;
+};
+
+static int
+aq2_fw_get_stats(struct aq_hw *hw, struct aq_hw_stats *stats)
+{
+	union aq2_fw_statistics u;
+	int err;
+
+	memset(stats, 0, sizeof(*stats));
+
+	err = aq2_fw_interface_buffer_read(hw, AQ2_FW_INTERFACE_OUT_STATS_REG,
+	    (uint32_t *)&u, sizeof(u));
+	if (err != 0)
+		return (err);
+
+	if (hw->aq2_iface == AQ2_FW_INTERFACE_OUT_VERSION_IFACE_VER_A0) {
+		stats->uprc = u.a0.rx_unicast_frames;
+		stats->mprc = u.a0.rx_multicast_frames;
+		stats->bprc = u.a0.rx_broadcast_frames;
+		stats->erpr = u.a0.rx_errors;
+		stats->ubrc = u.a0.rx_unicast_octets;
+		stats->mbrc = u.a0.rx_multicast_octets;
+		stats->bbrc = u.a0.rx_broadcast_octets;
+		stats->prc = u.a0.rx_good_frames;
+		stats->uptc = u.a0.tx_unicast_frames;
+		stats->mptc = u.a0.tx_multicast_frames;
+		stats->bptc = u.a0.tx_broadcast_frames;
+		stats->erpt = u.a0.tx_errors;
+		stats->ubtc = u.a0.tx_unicast_octets;
+		stats->mbtc = u.a0.tx_multicast_octets;
+		stats->bbtc = u.a0.tx_broadcast_octets;
+		stats->ptc = u.a0.tx_good_frames;
+	} else if (hw->aq2_iface == AQ2_FW_INTERFACE_OUT_VERSION_IFACE_VER_B0) {
+		/* B0 reports aggregate octets only; per-cast stays zero. */
+		stats->uprc = u.b0.rx_unicast_frames;
+		stats->mprc = u.b0.rx_multicast_frames;
+		stats->bprc = u.b0.rx_broadcast_frames;
+		stats->erpr = u.b0.rx_errors;
+		stats->prc = u.b0.rx_good_frames;
+		stats->uptc = u.b0.tx_unicast_frames;
+		stats->mptc = u.b0.tx_multicast_frames;
+		stats->bptc = u.b0.tx_broadcast_frames;
+		stats->erpt = u.b0.tx_errors;
+		stats->ptc = u.b0.tx_good_frames;
+	}
+
+	return (0);
+}
+
+const struct aq_firmware_ops aq2_fw_ops = {
+	.reset = aq2_fw_reset,
+	.set_mode = aq2_fw_set_mode,
+	.get_mode = aq2_fw_get_mode,
+	.get_mac_addr = aq2_fw_get_mac_addr,
+	.get_stats = aq2_fw_get_stats,
+	.led_control = NULL,
+};
diff --git a/sys/dev/aq/aq2_hw.h b/sys/dev/aq/aq2_hw.h
new file mode 100644
index 000000000000..a2c21162c2d9
--- /dev/null
+++ b/sys/dev/aq/aq2_hw.h
@@ -0,0 +1,276 @@
+/*-
+ * Atlantic 2 (AQC113/114/115/116) register definitions for aq(4).
+ *
+ * Register layout and field definitions adapted from the OpenBSD/NetBSD
+ * if_aq driver:
+ *
+ * Copyright (c) 2021 Jonathan Matthew <jonathan@d14n.org>
+ * Copyright (c) 2021 Mike Larkin <mlarkin@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef AQ2_HW_H
+#define AQ2_HW_H
+
+/* Atlantic 2 PCI device IDs (Aquantia/Marvell). */
+#define AQ_DEVICE_ID_AQC113	0x04C0
+#define AQ_DEVICE_ID_AQC113C	0x14C0
+#define AQ_DEVICE_ID_AQC113CA	0x34C0
+#define AQ_DEVICE_ID_AQC113CS	0x94C0
+#define AQ_DEVICE_ID_AQC114CS	0x93C0
+#define AQ_DEVICE_ID_AQC115C	0x12C0
+#define AQ_DEVICE_ID_AQC116C	0x11C0
+
+static __inline bool
+aq_is_atlantic2(uint16_t device_id)
+{
+	switch (device_id) {
+	case AQ_DEVICE_ID_AQC113:
+	case AQ_DEVICE_ID_AQC113C:
+	case AQ_DEVICE_ID_AQC113CA:
+	case AQ_DEVICE_ID_AQC113CS:
+	case AQ_DEVICE_ID_AQC114CS:
+	case AQ_DEVICE_ID_AQC115C:
+	case AQ_DEVICE_ID_AQC116C:
+		return (true);
+	default:
+		return (false);
+	}
+}
+
+/* Atlantic 2 host packet-buffer sizes (Atlantic 1 uses 160/320). */
+#define AQ2_HW_TXBUF_MAX	128
+#define AQ2_HW_RXBUF_MAX	192
+
+/* A2 firmware handshake registers (host <-> MCP). */
+#define AQ2_MIF_HOST_FINISHED_STATUS_WRITE_REG	0x0e00
+#define AQ2_MIF_HOST_FINISHED_STATUS_READ_REG	0x0e04
+#define  AQ2_MIF_HOST_FINISHED_STATUS_ACK	(1 << 0)
+
+#define AQ2_MCP_HOST_REQ_INT_REG		0x0f00
+#define  AQ2_MCP_HOST_REQ_INT_READY		(1 << 0)
+#define AQ2_MCP_HOST_REQ_INT_SET_REG		0x0f04
+#define AQ2_MCP_HOST_REQ_INT_CLR_REG		0x0f08
+
+#define AQ2_MIF_BOOT_REG			0x3040
+#define  AQ2_MIF_BOOT_HOST_DATA_LOADED		(1 << 16)
+#define  AQ2_MIF_BOOT_BOOT_STARTED		(1 << 24)
+#define  AQ2_MIF_BOOT_CRASH_INIT		(1 << 27)
+#define  AQ2_MIF_BOOT_BOOT_CODE_FAILED		(1 << 28)
+#define  AQ2_MIF_BOOT_FW_INIT_FAILED		(1 << 29)
+#define  AQ2_MIF_BOOT_FW_INIT_COMP_SUCCESS	(1U << 31)
+#define  AQ2_MIF_BOOT_FAILED \
+	(AQ2_MIF_BOOT_CRASH_INIT | AQ2_MIF_BOOT_BOOT_CODE_FAILED | \
+	    AQ2_MIF_BOOT_FW_INIT_FAILED)
+#define  AQ2_MIF_BOOT_COMPLETE \
+	(AQ2_MIF_BOOT_FAILED | AQ2_MIF_BOOT_FW_INIT_COMP_SUCCESS)
+
+/* The ART semaphore is global CPU semaphore index 3. */
+#define AQ2_ART_SEM_INDEX			3
+
+/* Action Resolver Table (replaces Atlantic 1's discrete RPF filters). */
+#define AQ2_ART_ACTION_ACT_SHIFT		8
+#define AQ2_ART_ACTION_RSS			0x0080
+#define AQ2_ART_ACTION_INDEX_SHIFT		2
+#define AQ2_ART_ACTION_ENABLE			0x0001
+#define AQ2_ART_ACTION(act, rss, idx, en)		\
+	(((act) << AQ2_ART_ACTION_ACT_SHIFT) |		\
+	((rss) ? AQ2_ART_ACTION_RSS : 0) |		\
+	((idx) << AQ2_ART_ACTION_INDEX_SHIFT) |		\
+	((en) ? AQ2_ART_ACTION_ENABLE : 0))
+#define AQ2_ART_ACTION_DROP			AQ2_ART_ACTION(0, 0, 0, 1)
+#define AQ2_ART_ACTION_DISABLE			AQ2_ART_ACTION(0, 0, 0, 0)
+#define AQ2_ART_ACTION_ASSIGN_QUEUE(q)		AQ2_ART_ACTION(1, 0, (q), 1)
+#define AQ2_ART_ACTION_ASSIGN_TC(tc)		AQ2_ART_ACTION(1, 1, (tc), 1)
+
+#define AQ2_RPF_TAG_PCP_MASK			0xe0000000
+#define AQ2_RPF_TAG_PCP_SHIFT			29
+#define AQ2_RPF_TAG_FLEX_MASK			0x18000000
+#define AQ2_RPF_TAG_UNKNOWN_MASK		0x07000000
+#define AQ2_RPF_TAG_L4_MASK			0x00e00000
+#define AQ2_RPF_TAG_L3_V6_MASK			0x001c0000
+#define AQ2_RPF_TAG_L3_V4_MASK			0x00038000
+#define AQ2_RPF_TAG_UNTAG_MASK			0x00004000
+#define AQ2_RPF_TAG_VLAN_MASK			0x00003c00
+#define AQ2_RPF_TAG_ET_MASK			0x00000380
+#define AQ2_RPF_TAG_ALLMC_MASK			0x00000040
+#define AQ2_RPF_TAG_UC_MASK			0x0000002f
+
+/* Logical ART row indices (before adding sc_art_filter_base_index). */
+#define AQ2_RPF_INDEX_L2_PROMISC_OFF		0
+#define AQ2_RPF_INDEX_VLAN_PROMISC_OFF		1
+#define AQ2_RPF_INDEX_L3L4_USER			8
+#define AQ2_RPF_INDEX_ET_PCP_USER		24
+#define AQ2_RPF_INDEX_VLAN_USER			40
+#define AQ2_RPF_INDEX_PCP_TO_TC			56
+
+#define AQ2_RPF_L2BC_TAG_REG			0x50f0
+#define  AQ2_RPF_L2BC_TAG_MASK			0x0000003f
+
+#define AQ2_RPF_NEW_CTRL_REG			0x5104
+#define  AQ2_RPF_NEW_CTRL_ENABLE		(1 << 11)
+
+#define AQ2_RPF_REDIR2_REG			0x54c8
+#define  AQ2_RPF_REDIR2_INDEX			(1 << 12)
+#define  AQ2_RPF_REDIR2_HASHTYPE		0x000001FF
+#define  AQ2_RPF_REDIR2_HASHTYPE_NONE		0
+#define  AQ2_RPF_REDIR2_HASHTYPE_ALL		0x000001FF
+
+#define AQ2_RX_Q_TC_MAP_REG(i)			(0x5900 + (i) * 4)
+#define AQ2_TX_Q_TC_MAP_REG(i)			(0x799c + (i) * 4)
+
+#define AQ2_RPF_RSS_REDIR_MAX			64
+#define AQ2_RPF_RSS_REDIR_REG(tc, i)		\
+	 (0x6200 + (0x100 * ((tc) >> 2)) + (i) * 4)
+
+#define AQ2_RPF_REC_TAB_ENABLE_REG		0x6ff0
+#define  AQ2_RPF_REC_TAB_ENABLE_MASK		0x0000ffff
+
+#define AQ2_LAUNCHTIME_CTRL_REG			0x7a1c
+#define  AQ2_LAUNCHTIME_CTRL_RATIO		0x0000ff00
+#define  AQ2_LAUNCHTIME_CTRL_RATIO_SPEED_QUARTER 4
+#define  AQ2_LAUNCHTIME_CTRL_RATIO_SPEED_HALF	2
+#define  AQ2_LAUNCHTIME_CTRL_RATIO_SPEED_FULL	1
+
+#define AQ2_HW_FPGA_VERSION_REG			0x00f4
+
+#define AQ2_TX_INTR_MODERATION_CTL_REG(i)	(0x7c28 + (i) * 0x40)
+#define  AQ2_TX_INTR_MODERATION_CTL_EN		(1 << 1)
+#define  AQ2_TX_INTR_MODERATION_CTL_MIN		0x0000ff00
+#define  AQ2_TX_INTR_MODERATION_CTL_MAX		0x01ff0000
+
+/* Firmware interface IN window (host -> MCP). */
+#define AQ2_FW_INTERFACE_IN_MTU_REG		0x12000
+#define AQ2_FW_INTERFACE_IN_MAC_ADDRESS_REG	0x12008
+
+#define AQ2_FW_INTERFACE_IN_LINK_CONTROL_REG	0x12010
+#define  AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE	0x0000000f
+#define  AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE_INVALID	0
+#define  AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE_ACTIVE	1
+#define  AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE_SLEEP_PROXY 2
+#define  AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE_LOWPOWER	3
+#define  AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE_SHUTDOWN	4
+
+#define AQ2_FW_INTERFACE_IN_LINK_OPTIONS_REG	0x12018
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_DOWNSHIFT	(1 << 27)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_PAUSE_TX	(1 << 25)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_PAUSE_RX	(1 << 24)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_EEE_10G	(1 << 20)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_EEE_5G	(1 << 19)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_EEE_2G5	(1 << 18)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_EEE_1G	(1 << 17)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_EEE_100M	(1 << 16)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_10G	(1 << 15)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_N5G	(1 << 14)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_5G	(1 << 13)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_N2G5	(1 << 12)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_2G5	(1 << 11)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_1G	(1 << 10)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_100M	(1 << 9)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_10M	(1 << 8)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_1G_HD	(1 << 7)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_100M_HD	(1 << 6)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_10M_HD	(1 << 5)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_LINK_RENEGOTIATE (1 << 1)
+#define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_LINK_UP	(1 << 0)
+
+#define AQ2_FW_INTERFACE_IN_REQUEST_POLICY_REG	0x12a58
+#define  AQ2_FW_INTERFACE_IN_REQUEST_POLICY_MCAST_QUEUE_OR_TC	0x00800000
+#define  AQ2_FW_INTERFACE_IN_REQUEST_POLICY_MCAST_RX_QUEUE_TC_INDEX 0x007c0000
+#define  AQ2_FW_INTERFACE_IN_REQUEST_POLICY_MCAST_ACCEPT	0x00010000
+#define  AQ2_FW_INTERFACE_IN_REQUEST_POLICY_BCAST_QUEUE_OR_TC	0x00008000
+#define  AQ2_FW_INTERFACE_IN_REQUEST_POLICY_BCAST_RX_QUEUE_TC_INDEX 0x00007c00
+#define  AQ2_FW_INTERFACE_IN_REQUEST_POLICY_BCAST_ACCEPT	0x00000100
+#define  AQ2_FW_INTERFACE_IN_REQUEST_POLICY_PROMISC_QUEUE_OR_TC	0x00000080
+#define  AQ2_FW_INTERFACE_IN_REQUEST_POLICY_PROMISC_RX_QUEUE_TX_INDEX 0x0000007c
+#define  AQ2_FW_INTERFACE_IN_REQUEST_POLICY_PROMISC_MCAST	0x00000002
+#define  AQ2_FW_INTERFACE_IN_REQUEST_POLICY_PROMISC_ALL		0x00000001
+
+/* Firmware interface OUT window (MCP -> host). */
+#define AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_REG	0x13000
+#define  AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_B	0xffff0000
+#define  AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_B_S 16
+#define  AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_A	0x0000ffff
+#define  AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_A_S 0
+
+#define AQ2_FW_INTERFACE_OUT_VERSION_BUNDLE_REG	0x13004
+#define AQ2_FW_INTERFACE_OUT_VERSION_MAC_REG	0x13008
+
+#define AQ2_FW_INTERFACE_OUT_VERSION_PHY_REG	0x1300c
+#define  AQ2_FW_INTERFACE_OUT_VERSION_BUILD	0xffff0000
+#define  AQ2_FW_INTERFACE_OUT_VERSION_BUILD_S	16
+#define  AQ2_FW_INTERFACE_OUT_VERSION_MINOR	0x0000ff00
+#define  AQ2_FW_INTERFACE_OUT_VERSION_MINOR_S	8
+#define  AQ2_FW_INTERFACE_OUT_VERSION_MAJOR	0x000000ff
+#define  AQ2_FW_INTERFACE_OUT_VERSION_MAJOR_S	0
+
+#define AQ2_FW_INTERFACE_OUT_VERSION_IFACE_REG	0x13010
+#define  AQ2_FW_INTERFACE_OUT_VERSION_IFACE_VER	0x0000000f
+#define  AQ2_FW_INTERFACE_OUT_VERSION_IFACE_VER_A0 0
+#define  AQ2_FW_INTERFACE_OUT_VERSION_IFACE_VER_B0 1
+
+#define AQ2_FW_INTERFACE_OUT_STATS_REG		0x13700
+
+#define AQ2_FW_INTERFACE_OUT_LINK_STATUS_REG	0x13014
+#define  AQ2_FW_INTERFACE_OUT_LINK_STATUS_DUPLEX	(1 << 11)
+#define  AQ2_FW_INTERFACE_OUT_LINK_STATUS_EEE		(1 << 10)
+#define  AQ2_FW_INTERFACE_OUT_LINK_STATUS_PAUSE_RX	(1 << 9)
+#define  AQ2_FW_INTERFACE_OUT_LINK_STATUS_PAUSE_TX	(1 << 8)
+#define  AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE		0x000000f0
+#define  AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_S	4
+#define  AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_10G	6
+#define  AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_5G	5
+#define  AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_2G5	4
+#define  AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_1G	3
+#define  AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_100M	2
+#define  AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_10M	1
+#define  AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_INVALID	0
+#define  AQ2_FW_INTERFACE_OUT_LINK_STATUS_STATE		0x0000000f
+
+#define AQ2_FW_INTERFACE_OUT_FILTER_CAPS_REG	0x13774
+#define  AQ2_FW_INTERFACE_OUT_FILTER_CAPS3_RESOLVER_BASE_INDEX 0x00ff0000
+#define  AQ2_FW_INTERFACE_OUT_FILTER_CAPS3_RESOLVER_BASE_INDEX_SHIFT 16
+
+#define AQ2_RPF_ACT_ART_REQ_TAG_REG(i)		(0x14000 + (i) * 0x10)
+#define AQ2_RPF_ACT_ART_REQ_MASK_REG(i)		(0x14004 + (i) * 0x10)
+#define AQ2_RPF_ACT_ART_REQ_ACTION_REG(i)	(0x14008 + (i) * 0x10)
+
+/*
+ * L2 unicast filter MSW register (shared with Atlantic 1).  Atlantic 2 sets
+ * the TAG field so unicast frames are classified into the resolver table.
+ */
+#define AQ2_RPF_VLAN_FLR_TAG_REG(i)		(0x5290 + (i) * 4)
+#define  AQ2_RPF_VLAN_FLR_TAG			0x0000F000
+#define  AQ2_RPF_VLAN_FLR_TAG_SHIFT		12
+
+#define RPF_L2UC_MSW_REG(i)			(0x5114 + (i) * 8)
+#define  RPF_L2UC_MSW_TAG			0x03c00000
+#define  RPF_L2UC_MSW_TAG_SHIFT			22
+
+/*
+ * Tx packet-scheduler data-TC credit/weight (shared register, but the
+ * Atlantic 2 fields are wider than Atlantic 1's), and the Tx buffer control
+ * register whose randomized ring->TC map Atlantic 2 enables.
+ */
+#define TPS_DATA_TCT_REG(i)			(0x7110 + (i) * 4)
+#define  TPS2_DATA_TCT_CREDIT_MAX		0xffff0000
+#define  TPS2_DATA_TCT_CREDIT_MAX_SHIFT		16
+#define  TPS2_DATA_TCT_WEIGHT			0x00007fff
+#define  TPS2_DATA_TCT_WEIGHT_SHIFT		0
+
+#define TPB_TX_BUF_REG				0x7900
+#define  TPB_TX_BUF_TC_Q_RAND_MAP_EN		(1 << 9)
+#define  TPB_TX_BUF_TC_Q_RAND_MAP_EN_SHIFT	9
+
+#endif /* AQ2_HW_H */
diff --git a/sys/dev/aq/aq_device.h b/sys/dev/aq/aq_device.h
index 23785c1854c5..acdf897562f0 100644
--- a/sys/dev/aq/aq_device.h
+++ b/sys/dev/aq/aq_device.h
@@ -44,14 +44,18 @@ enum aq_media_type {
 };
 
 #define	AQ_LINK_UNKNOWN	0x00000000
-#define	AQ_LINK_100M	0x00000001
-#define	AQ_LINK_1G		0x00000002
-#define	AQ_LINK_2G5		0x00000004
-#define	AQ_LINK_5G		0x00000008
-#define	AQ_LINK_10G		0x00000010
-
-#define	AQ_LINK_ALL	(AQ_LINK_100M | AQ_LINK_1G | AQ_LINK_2G5 | AQ_LINK_5G | \
-					 AQ_LINK_10G )
+#define	AQ_LINK_10M	0x00000001
+#define	AQ_LINK_100M	0x00000002
+#define	AQ_LINK_1G	0x00000004
+#define	AQ_LINK_2G5	0x00000008
+#define	AQ_LINK_5G	0x00000010
+#define	AQ_LINK_10G	0x00000020
+
+#define	AQ_LINK_ALL	(AQ_LINK_10M | AQ_LINK_100M | AQ_LINK_1G | \
+			 AQ_LINK_2G5 | AQ_LINK_5G | AQ_LINK_10G)
+
+/* Atlantic 1 has no 10BASE-T PHY. */
+#define	AQ_LINK_ALL_ATLANTIC1	(AQ_LINK_ALL & ~AQ_LINK_10M)
 
 struct aq_stats {
 	uint64_t prc;
diff --git a/sys/dev/aq/aq_fw.h b/sys/dev/aq/aq_fw.h
index be6661db7d57..7bb35504eab6 100644
--- a/sys/dev/aq/aq_fw.h
+++ b/sys/dev/aq/aq_fw.h
@@ -39,11 +39,12 @@ struct aq_hw;
 enum aq_fw_link_speed
 {
 	aq_fw_none  = 0,
-	aq_fw_100M  = (1 << 0),
-	aq_fw_1G    = (1 << 1),
-	aq_fw_2G5   = (1 << 2),
-	aq_fw_5G    = (1 << 3),
-	aq_fw_10G   = (1 << 4),
+	aq_fw_10M   = (1 << 0),	/* Atlantic 2 only */
+	aq_fw_100M  = (1 << 1),
+	aq_fw_1G    = (1 << 2),
+	aq_fw_2G5   = (1 << 3),
+	aq_fw_5G    = (1 << 4),
+	aq_fw_10G   = (1 << 5),
 };
 
 enum aq_fw_link_fc
@@ -55,7 +56,7 @@ enum aq_fw_link_fc
 };
 
 #define aq_fw_speed_auto \
-    (aq_fw_100M | aq_fw_1G | aq_fw_2G5 | aq_fw_5G | aq_fw_10G)
+    (aq_fw_10M | aq_fw_100M | aq_fw_1G | aq_fw_2G5 | aq_fw_5G | aq_fw_10G)
 
 struct aq_firmware_ops
 {
@@ -70,10 +71,13 @@ struct aq_firmware_ops
 	int (*led_control)(struct aq_hw* hw, uint32_t mode);
 };
 
+/* aq_fw1x/aq_fw2x: Atlantic 1 firmware ABIs; aq2_fw: Atlantic 2 (AQC11x). */
 extern const struct aq_firmware_ops aq_fw1x_ops;
 extern const struct aq_firmware_ops aq_fw2x_ops;
+extern const struct aq_firmware_ops aq2_fw_ops;
 
 int aq_fw_reset(struct aq_hw* hw);
 int aq_fw_ops_init(struct aq_hw* hw);
+int aq2_fw_reboot(struct aq_hw* hw);
 
 #endif // AQ_FW_H
diff --git a/sys/dev/aq/aq_hw.c b/sys/dev/aq/aq_hw.c
index 2c147ee61a4e..bbcc79805a4e 100644
--- a/sys/dev/aq/aq_hw.c
+++ b/sys/dev/aq/aq_hw.c
@@ -39,6 +39,7 @@
 #include <machine/cpu.h>
 
 #include "aq_hw.h"
+#include "aq2_hw.h"
 #include "aq_dbg.h"
 #include "aq_hw_llh.h"
 #include "aq_fw.h"
@@ -151,6 +152,10 @@ aq_hw_init_ucp(struct aq_hw *hw)
 
 	hw->fw_version.raw = 0;
 
+	/* Atlantic 2 uses a different reset/firmware handshake. */
+	if (IS_CHIP_FEATURE(hw, ATLANTIC2))
+		return (aq2_fw_reboot(hw));
+
 	err = aq_fw_reset(hw);
 	if (err != 0) {
 		device_printf(hw->dev, "aq_hw_init_ucp(): F/W reset failed, err %d\n", err);
@@ -280,6 +285,9 @@ aq_hw_get_link_state(struct aq_hw *hw, uint32_t *link_speed, struct aq_hw_fc_inf
 	case aq_fw_100M:
 		*link_speed = 100U;
 		break;
+	case aq_fw_10M:
+		*link_speed = 10U;
+		break;
 	default:
 		*link_speed = 0U;
 		break;
@@ -361,7 +369,11 @@ aq_hw_reset(struct aq_hw *hw)
 
 	AQ_DBG_ENTER();
 
-	err = aq_fw_reset(hw);
+	/* A2 resets by rebooting the MCP; A1 uses the RBL/FLB reset. */
+	if (IS_CHIP_FEATURE(hw, ATLANTIC2))
+		err = aq2_fw_reboot(hw);
+	else
+		err = aq_fw_reset(hw);
 	if (err != 0)
 		goto err_exit;
 
@@ -634,15 +646,32 @@ aq_hw_init(struct aq_hw *hw, uint8_t *mac_addr, uint8_t adm_irq, bool msix)
 
 	AQ_DBG_ENTER();
 
-	/* Force limit MRRS on RDM/TDM to 2K */
-	val = AQ_READ_REG(hw, AQ_HW_PCI_REG_CONTROL_6_ADR);
-	AQ_WRITE_REG(hw, AQ_HW_PCI_REG_CONTROL_6_ADR, (val & ~0x707) | 0x404);
-
-	/* TX DMA total request limit. B0 hardware is not capable to
-	* handle more than (8K-MRRS) incoming DMA data.
-	* Value 24 in 256byte units
-	*/
-	AQ_WRITE_REG(hw, AQ_HW_TX_DMA_TOTAL_REQ_LIMIT_ADR, 24);
+	/* Atlantic 1 only: clamp MRRS and TX DMA total request limit. */
+	if (!IS_CHIP_FEATURE(hw, ATLANTIC2)) {
+		/* Force limit MRRS on RDM/TDM to 2K */
+		val = AQ_READ_REG(hw, AQ_HW_PCI_REG_CONTROL_6_ADR);
+		AQ_WRITE_REG(hw, AQ_HW_PCI_REG_CONTROL_6_ADR,
+		    (val & ~0x707) | 0x404);
+
+		/* TX DMA total request limit. B0 hardware is not capable to
+		* handle more than (8K-MRRS) incoming DMA data.
+		* Value 24 in 256byte units
+		*/
+		AQ_WRITE_REG(hw, AQ_HW_TX_DMA_TOTAL_REQ_LIMIT_ADR, 24);
+	} else {
+		/* Atlantic 2: launch-time clock ratio per FPGA version. */
+		uint32_t fpgaver = AQ_READ_REG(hw, AQ2_HW_FPGA_VERSION_REG);
+		uint32_t ratio;
+
+		if (fpgaver < 0x01000000U)
+			ratio = AQ2_LAUNCHTIME_CTRL_RATIO_SPEED_FULL;
+		else if (fpgaver >= 0x01008502U)
+			ratio = AQ2_LAUNCHTIME_CTRL_RATIO_SPEED_HALF;
+		else
+			ratio = AQ2_LAUNCHTIME_CTRL_RATIO_SPEED_QUARTER;
+		AQ_WRITE_REG_BIT(hw, AQ2_LAUNCHTIME_CTRL_REG,
+		    AQ2_LAUNCHTIME_CTRL_RATIO, 8, ratio);
+	}
 
 	err = aq_hw_init_tx_path(hw);
 	if (err != 0)
diff --git a/sys/dev/aq/aq_hw.h b/sys/dev/aq/aq_hw.h
index 22e10ba72c54..eb46641ef35d 100644
*** 298 LINES SKIPPED ***


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a5d000d.32586.23fe9733>