Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F110678834
D34112.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
128 KB
Referenced Files
None
Subscribers
None
D34112.diff
View Options
diff --git a/sys/arm/conf/std.qca b/sys/arm/conf/std.qca
--- a/sys/arm/conf/std.qca
+++ b/sys/arm/conf/std.qca
@@ -78,3 +78,5 @@
device mii
device miibus
device qcom_mdio_ipq4018
+device etherswitch
+device ar40xx_switch
diff --git a/sys/arm/qualcomm/std.ipq4018 b/sys/arm/qualcomm/std.ipq4018
--- a/sys/arm/qualcomm/std.ipq4018
+++ b/sys/arm/qualcomm/std.ipq4018
@@ -6,6 +6,27 @@
arm/qualcomm/ipq4018_usb_hs_phy.c optional qcom_ipq4018_hs_usbphy
arm/qualcomm/ipq4018_usb_ss_phy.c optional qcom_ipq4018_ss_usbphy
+dev/etherswitch/ar40xx/ar40xx_main.c \
+ optional mdio etherswitch ar40xx_switch
+dev/etherswitch/ar40xx/ar40xx_phy.c \
+ optional mdio etherswitch ar40xx_switch
+dev/etherswitch/ar40xx/ar40xx_hw.c \
+ optional mdio etherswitch ar40xx_switch
+dev/etherswitch/ar40xx/ar40xx_hw_atu.c \
+ optional mdio etherswitch ar40xx_switch
+dev/etherswitch/ar40xx/ar40xx_hw_port.c \
+ optional mdio etherswitch ar40xx_switch
+dev/etherswitch/ar40xx/ar40xx_hw_psgmii.c \
+ optional mdio etherswitch ar40xx_switch
+dev/etherswitch/ar40xx/ar40xx_hw_mib.c \
+ optional mdio etherswitch ar40xx_switch
+dev/etherswitch/ar40xx/ar40xx_hw_mirror.c \
+ optional mdio etherswitch ar40xx_switch
+dev/etherswitch/ar40xx/ar40xx_hw_vtu.c \
+ optional mdio etherswitch ar40xx_switch
+dev/etherswitch/ar40xx/ar40xx_hw_mdio.c \
+ optional mdio etherswitch ar40xx_switch
+
dev/qcom_dwc3/qcom_dwc3.c optional qcom_dwc3
dev/qcom_rnd/qcom_rnd.c optional qcom_rnd
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_debug.h b/sys/dev/etherswitch/ar40xx/ar40xx_debug.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_debug.h
@@ -0,0 +1,44 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>
+ *
+ * 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 AUTHOR 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 AUTHOR 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 __AR40XX_DEBUG_H__
+#define __AR40XX_DEBUG_H__
+
+#define AR40XX_DBG_HW_INIT 0x00000001
+#define AR40XX_DBG_HW_RESET 0x00000002
+#define AR40XX_DBG_HW_PORT_INIT 0x00000004
+#define AR40XX_DBG_VTU_OP 0x00000008
+#define AR40XX_DBG_ATU_OP 0x00000010
+#define AR40XX_DBG_PORT_STATUS 0x00000020
+
+#define AR40XX_DPRINTF(sc, flags, ...) \
+ do { \
+ if ((sc)->sc_debug & (flags)) \
+ device_printf((sc)->sc_dev, __VA_ARGS__); \
+ } while (0)
+
+#endif /* __AR40XX_DEBUG_H__ */
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_hw.h b/sys/dev/etherswitch/ar40xx/ar40xx_hw.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw.h
@@ -0,0 +1,42 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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 __AR40XX_HW_H__
+#define __AR40XX_HW_H__
+
+extern int ar40xx_hw_ess_reset(struct ar40xx_softc *sc);
+extern int ar40xx_hw_init_globals(struct ar40xx_softc *sc);
+extern int ar40xx_hw_vlan_init(struct ar40xx_softc *sc);
+extern int ar40xx_hw_sw_hw_apply(struct ar40xx_softc *sc);
+extern int ar40xx_hw_wait_bit(struct ar40xx_softc *sc, int reg,
+ uint32_t mask, uint32_t val);
+extern int ar40xx_hw_read_switch_mac_address(struct ar40xx_softc *sc,
+ struct ether_addr *ea);
+extern int ar40xx_hw_write_switch_mac_address(struct ar40xx_softc *sc,
+ struct ether_addr *ea);
+
+#endif /* __AR40XX_HW_H__ */
+
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_hw.c b/sys/dev/etherswitch/ar40xx/ar40xx_hw.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw.c
@@ -0,0 +1,357 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/iicbus/iic.h>
+#include <dev/iicbus/iiconf.h>
+#include <dev/iicbus/iicbus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/ar40xx/ar40xx_var.h>
+#include <dev/etherswitch/ar40xx/ar40xx_reg.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw.h>
+#include <dev/etherswitch/ar40xx/ar40xx_debug.h>
+
+/*
+ * XXX these are here for now; move the code using these
+ * into main.c once this is all done!
+ */
+#include <dev/etherswitch/ar40xx/ar40xx_hw_vtu.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_port.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_mirror.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+/*
+ * Reset the ESS switch. This also resets the ESS ethernet
+ * and PSGMII block.
+ */
+int
+ar40xx_hw_ess_reset(struct ar40xx_softc *sc)
+{
+ int ret;
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_HW_RESET, "%s: called\n", __func__);
+
+ ret = hwreset_assert(sc->sc_ess_rst);
+ if (ret != 0) {
+ device_printf(sc->sc_dev, "ERROR: failed to assert reset\n");
+ return ret;
+ }
+ DELAY(10*1000);
+
+ ret = hwreset_deassert(sc->sc_ess_rst);
+ if (ret != 0) {
+ device_printf(sc->sc_dev,
+ "ERROR: failed to deassert reset\n");
+ return ret;
+ }
+
+ DELAY(10*1000);
+
+ return (0);
+}
+
+int
+ar40xx_hw_init_globals(struct ar40xx_softc *sc)
+{
+ uint32_t reg;
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_HW_INIT, "%s: called\n", __func__);
+
+ /* enable CPU port and disable mirror port */
+ reg = AR40XX_FWD_CTRL0_CPU_PORT_EN
+ | AR40XX_FWD_CTRL0_MIRROR_PORT;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_FWD_CTRL0, reg);
+
+ /* forward multicast and broadcast frames to CPU */
+ reg = (AR40XX_PORTS_ALL << AR40XX_FWD_CTRL1_UC_FLOOD_S)
+ | (AR40XX_PORTS_ALL << AR40XX_FWD_CTRL1_MC_FLOOD_S)
+ | (AR40XX_PORTS_ALL << AR40XX_FWD_CTRL1_BC_FLOOD_S);
+ AR40XX_REG_WRITE(sc, AR40XX_REG_FWD_CTRL1, reg);
+
+ /* enable jumbo frames */
+ reg = AR40XX_REG_READ(sc, AR40XX_REG_MAX_FRAME_SIZE);
+ reg &= ~AR40XX_MAX_FRAME_SIZE_MTU;
+ reg |= 9018 + 8 + 2;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_MAX_FRAME_SIZE, reg);
+
+ /* Enable MIB counters */
+ reg = AR40XX_REG_READ(sc, AR40XX_REG_MODULE_EN);
+ reg |= AR40XX_MODULE_EN_MIB;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_MODULE_EN, reg);
+
+ /* Disable AZ */
+ AR40XX_REG_WRITE(sc, AR40XX_REG_EEE_CTRL, 0);
+
+ /* set flowctrl thershold for cpu port */
+ reg = (AR40XX_PORT0_FC_THRESH_ON_DFLT << 16)
+ | AR40XX_PORT0_FC_THRESH_OFF_DFLT;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_FLOWCTRL_THRESH(0), reg);
+
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ return (0);
+}
+
+int
+ar40xx_hw_vlan_init(struct ar40xx_softc *sc)
+{
+ int i;
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_HW_INIT, "%s: called\n", __func__);
+
+ /* Enable VLANs by default */
+ sc->sc_vlan.vlan = 1;
+
+ /* Configure initial LAN/WAN bitmap and include CPU port as tagged */
+ sc->sc_vlan.vlan_id[AR40XX_LAN_VLAN] = AR40XX_LAN_VLAN
+ | ETHERSWITCH_VID_VALID;
+ sc->sc_vlan.vlan_id[AR40XX_WAN_VLAN] = AR40XX_WAN_VLAN
+ | ETHERSWITCH_VID_VALID;
+
+ sc->sc_vlan.vlan_ports[AR40XX_LAN_VLAN] =
+ sc->sc_config.switch_cpu_bmp | sc->sc_config.switch_lan_bmp;
+ sc->sc_vlan.vlan_untagged[AR40XX_LAN_VLAN] =
+ sc->sc_config.switch_lan_bmp;
+
+ sc->sc_vlan.vlan_ports[AR40XX_WAN_VLAN] =
+ sc->sc_config.switch_cpu_bmp | sc->sc_config.switch_wan_bmp;
+ sc->sc_vlan.vlan_untagged[AR40XX_WAN_VLAN] =
+ sc->sc_config.switch_wan_bmp;
+
+ /* Populate the per-port PVID - pvid[] is an index into vlan_id[] */
+ for (i = 0; i < AR40XX_NUM_PORTS; i++) {
+ if (sc->sc_config.switch_lan_bmp & (1U << i))
+ sc->sc_vlan.pvid[i] = AR40XX_LAN_VLAN;
+ if (sc->sc_config.switch_wan_bmp & (1U << i))
+ sc->sc_vlan.pvid[i] = AR40XX_WAN_VLAN;
+ }
+
+ return (0);
+}
+
+/*
+ * Apply the per-port and global configuration from software.
+ *
+ * This is useful if we ever start doing the linux switch framework
+ * thing of updating the config in one hit and pushing it to the
+ * hardware. For now it's just used in the reset path.
+ */
+int
+ar40xx_hw_sw_hw_apply(struct ar40xx_softc *sc)
+{
+ uint8_t portmask[AR40XX_NUM_PORTS];
+ int i, j, ret;
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_HW_INIT, "%s: called\n", __func__);
+
+ /*
+ * Flush the VTU configuration.
+ */
+ ret = ar40xx_hw_vtu_flush(sc);
+ if (ret != 0) {
+ device_printf(sc->sc_dev,
+ "ERROR: couldn't apply config; vtu flush failed (%d)\n",
+ ret);
+ return (ret);
+ }
+
+ memset(portmask, 0, sizeof(portmask));
+
+ /*
+ * Configure the ports based on whether it's 802.1q
+ * VLANs, or just straight up per-port VLANs.
+ */
+ if (sc->sc_vlan.vlan) {
+ device_printf(sc->sc_dev, "%s: configuring 802.1q VLANs\n",
+ __func__);
+ for (j = 0; j < AR40XX_NUM_VTU_ENTRIES; j++) {
+ uint8_t vp = sc->sc_vlan.vlan_ports[j];
+
+ if (!vp)
+ continue;
+ if ((sc->sc_vlan.vlan_id[j]
+ & ETHERSWITCH_VID_VALID) == 0)
+ continue;
+
+ for (i = 0; i < AR40XX_NUM_PORTS; i++) {
+ uint8_t mask = (1U << i);
+
+ if (vp & mask)
+ portmask[i] |= vp & ~mask;
+ }
+
+ ar40xx_hw_vtu_load_vlan(sc,
+ sc->sc_vlan.vlan_id[j] & ETHERSWITCH_VID_MASK,
+ sc->sc_vlan.vlan_ports[j],
+ sc->sc_vlan.vlan_untagged[j]);
+ }
+ } else {
+ device_printf(sc->sc_dev, "%s: configuring per-port VLANs\n",
+ __func__);
+ for (i = 0; i < AR40XX_NUM_PORTS; i++) {
+ if (i == AR40XX_PORT_CPU)
+ continue;
+
+ portmask[i] = (1U << AR40XX_PORT_CPU);
+ portmask[AR40XX_PORT_CPU] |= (1U << i);
+ }
+ }
+
+ /*
+ * Update per-port destination mask, vlan tag settings
+ */
+ for (i = 0; i < AR40XX_NUM_PORTS; i++)
+ (void) ar40xx_hw_port_setup(sc, i, portmask[i]);
+
+ /* Set the mirror register config */
+ ret = ar40xx_hw_mirror_set_registers(sc);
+ if (ret != 0) {
+ device_printf(sc->sc_dev,
+ "ERROR: couldn't apply config; mirror config failed"
+ " (%d)\n",
+ ret);
+ return (ret);
+ }
+
+ return (0);
+}
+
+int
+ar40xx_hw_wait_bit(struct ar40xx_softc *sc, int reg, uint32_t mask,
+ uint32_t val)
+{
+ int timeout = 20;
+ uint32_t t;
+
+ while (true) {
+ AR40XX_REG_BARRIER_READ(sc);
+ t = AR40XX_REG_READ(sc, reg);
+ if ((t & mask) == val)
+ return 0;
+
+ if (timeout-- <= 0)
+ break;
+
+ DELAY(20);
+ }
+
+ device_printf(sc->sc_dev, "ERROR: timeout for reg "
+ "%08x: %08x & %08x != %08x\n",
+ (unsigned int)reg, t, mask, val);
+ return (ETIMEDOUT);
+}
+
+/*
+ * Read the switch MAC address.
+ */
+int
+ar40xx_hw_read_switch_mac_address(struct ar40xx_softc *sc,
+ struct ether_addr *ea)
+{
+ uint32_t ret0, ret1;
+ char *s;
+
+ s = (void *) ea;
+
+ AR40XX_LOCK_ASSERT(sc);
+
+ AR40XX_REG_BARRIER_READ(sc);
+ ret0 = AR40XX_REG_READ(sc, AR40XX_REG_SW_MAC_ADDR0);
+ ret1 = AR40XX_REG_READ(sc, AR40XX_REG_SW_MAC_ADDR1);
+
+ s[5] = MS(ret0, AR40XX_REG_SW_MAC_ADDR0_BYTE5);
+ s[4] = MS(ret0, AR40XX_REG_SW_MAC_ADDR0_BYTE4);
+ s[3] = MS(ret1, AR40XX_REG_SW_MAC_ADDR1_BYTE3);
+ s[2] = MS(ret1, AR40XX_REG_SW_MAC_ADDR1_BYTE2);
+ s[1] = MS(ret1, AR40XX_REG_SW_MAC_ADDR1_BYTE1);
+ s[0] = MS(ret1, AR40XX_REG_SW_MAC_ADDR1_BYTE0);
+
+ return (0);
+}
+
+/*
+ * Set the switch MAC address.
+ */
+int
+ar40xx_hw_write_switch_mac_address(struct ar40xx_softc *sc,
+ struct ether_addr *ea)
+{
+ uint32_t ret0 = 0, ret1 = 0;
+ char *s;
+
+ s = (void *) ea;
+
+ AR40XX_LOCK_ASSERT(sc);
+
+ ret0 |= SM(s[5], AR40XX_REG_SW_MAC_ADDR0_BYTE5);
+ ret0 |= SM(s[4], AR40XX_REG_SW_MAC_ADDR0_BYTE4);
+
+ ret1 |= SM(s[3], AR40XX_REG_SW_MAC_ADDR1_BYTE3);
+ ret1 |= SM(s[2], AR40XX_REG_SW_MAC_ADDR1_BYTE2);
+ ret1 |= SM(s[1], AR40XX_REG_SW_MAC_ADDR1_BYTE1);
+ ret1 |= SM(s[0], AR40XX_REG_SW_MAC_ADDR1_BYTE0);
+
+ AR40XX_REG_WRITE(sc, AR40XX_REG_SW_MAC_ADDR0, ret0);
+ AR40XX_REG_WRITE(sc, AR40XX_REG_SW_MAC_ADDR1, ret1);
+
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ return (0);
+}
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_hw_atu.h b/sys/dev/etherswitch/ar40xx/ar40xx_hw_atu.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_atu.h
@@ -0,0 +1,37 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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 __AR40XX_HW_ATU_H__
+#define __AR40XX_HW_ATU_H__
+
+extern int ar40xx_hw_atu_wait_busy(struct ar40xx_softc *sc);
+extern int ar40xx_hw_atu_flush_all(struct ar40xx_softc *sc);
+extern int ar40xx_hw_atu_flush_port(struct ar40xx_softc *sc, int port);
+extern int ar40xx_hw_atu_fetch_entry(struct ar40xx_softc *sc,
+ etherswitch_atu_entry_t *e, int atu_fetch_op);
+
+#endif /* __AR40XX_HW_ATU_H__ */
+
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_hw_atu.c b/sys/dev/etherswitch/ar40xx/ar40xx_hw_atu.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_atu.c
@@ -0,0 +1,216 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/iicbus/iic.h>
+#include <dev/iicbus/iiconf.h>
+#include <dev/iicbus/iicbus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/ar40xx/ar40xx_var.h>
+#include <dev/etherswitch/ar40xx/ar40xx_reg.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_atu.h>
+#include <dev/etherswitch/ar40xx/ar40xx_debug.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+int
+ar40xx_hw_atu_wait_busy(struct ar40xx_softc *sc)
+{
+ int ret;
+
+ ret = ar40xx_hw_wait_bit(sc, AR40XX_REG_ATU_FUNC,
+ AR40XX_ATU_FUNC_BUSY, 0);
+ return (ret);
+}
+
+int
+ar40xx_hw_atu_flush_all(struct ar40xx_softc *sc)
+{
+ int ret;
+
+ AR40XX_LOCK_ASSERT(sc);
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_ATU_OP, "%s: called\n", __func__);
+ ret = ar40xx_hw_atu_wait_busy(sc);
+ if (ret != 0)
+ return (ret);
+
+ AR40XX_REG_WRITE(sc, AR40XX_REG_ATU_FUNC,
+ AR40XX_ATU_FUNC_OP_FLUSH
+ | AR40XX_ATU_FUNC_BUSY);
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ return (ret);
+}
+
+int
+ar40xx_hw_atu_flush_port(struct ar40xx_softc *sc, int port)
+{
+ uint32_t val;
+ int ret;
+
+ AR40XX_LOCK_ASSERT(sc);
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_ATU_OP, "%s: called, port=%d\n",
+ __func__, port);
+
+ if (port >= AR40XX_NUM_PORTS) {
+ return (EINVAL);
+ }
+
+ ret = ar40xx_hw_atu_wait_busy(sc);
+ if (ret != 0)
+ return (ret);
+
+ val = AR40XX_ATU_FUNC_OP_FLUSH_UNICAST;
+ val |= (port << AR40XX_ATU_FUNC_PORT_NUM_S)
+ & AR40XX_ATU_FUNC_PORT_NUM;
+
+ AR40XX_REG_WRITE(sc, AR40XX_REG_ATU_FUNC,
+ val | AR40XX_ATU_FUNC_BUSY);
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ return (0);
+}
+
+int
+ar40xx_hw_atu_fetch_entry(struct ar40xx_softc *sc, etherswitch_atu_entry_t *e,
+ int atu_fetch_op)
+{
+ uint32_t ret0, ret1, ret2, val;
+ int ret;
+
+ AR40XX_LOCK_ASSERT(sc);
+
+ switch (atu_fetch_op) {
+ case 0:
+ /* Initialise things for the first fetch */
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_ATU_OP,
+ "%s: initializing\n", __func__);
+
+ ret = ar40xx_hw_atu_wait_busy(sc);
+ if (ret != 0)
+ return (ret);
+
+ AR40XX_REG_WRITE(sc, AR40XX_REG_ATU_FUNC,
+ AR40XX_ATU_FUNC_OP_GET_NEXT);
+ AR40XX_REG_WRITE(sc, AR40XX_REG_ATU_DATA0, 0);
+ AR40XX_REG_WRITE(sc, AR40XX_REG_ATU_DATA1, 0);
+ AR40XX_REG_WRITE(sc, AR40XX_REG_ATU_DATA2, 0);
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ return (0);
+ case 1:
+ AR40XX_DPRINTF(sc, AR40XX_DBG_ATU_OP,
+ "%s: reading next\n", __func__);
+ /*
+ * Attempt to read the next address entry; don't modify what
+ * is there in these registers as its used for the next fetch
+ */
+ ret = ar40xx_hw_atu_wait_busy(sc);
+ if (ret != 0)
+ return (ret);
+
+ /* Begin the next read event; not modifying anything */
+ AR40XX_REG_BARRIER_READ(sc);
+ val = AR40XX_REG_READ(sc, AR40XX_REG_ATU_FUNC);
+ val |= AR40XX_ATU_FUNC_BUSY;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_ATU_FUNC, val);
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ /* Wait for it to complete */
+ ret = ar40xx_hw_atu_wait_busy(sc);
+ if (ret != 0)
+ return (ret);
+
+ /* Fetch the ethernet address and ATU status */
+ AR40XX_REG_BARRIER_READ(sc);
+ ret0 = AR40XX_REG_READ(sc, AR40XX_REG_ATU_DATA0);
+ ret1 = AR40XX_REG_READ(sc, AR40XX_REG_ATU_DATA1);
+ ret2 = AR40XX_REG_READ(sc, AR40XX_REG_ATU_DATA2);
+
+ /* If the status is zero, then we're done */
+ if (MS(ret2, AR40XX_ATU_FUNC_DATA2_STATUS) == 0)
+ return (ENOENT);
+
+ /* MAC address */
+ e->es_macaddr[5] = MS(ret0, AR40XX_ATU_DATA0_MAC_ADDR3);
+ e->es_macaddr[4] = MS(ret0, AR40XX_ATU_DATA0_MAC_ADDR2);
+ e->es_macaddr[3] = MS(ret0, AR40XX_ATU_DATA0_MAC_ADDR1);
+ e->es_macaddr[2] = MS(ret0, AR40XX_ATU_DATA0_MAC_ADDR0);
+ e->es_macaddr[0] = MS(ret1, AR40XX_ATU_DATA1_MAC_ADDR5);
+ e->es_macaddr[1] = MS(ret1, AR40XX_ATU_DATA1_MAC_ADDR4);
+
+ /* Bitmask of ports this entry is for */
+ e->es_portmask = MS(ret1, AR40XX_ATU_DATA1_DEST_PORT);
+
+ /* TODO: other flags that are interesting */
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_ATU_OP,
+ "%s: MAC %6D portmask 0x%08x\n",
+ __func__,
+ e->es_macaddr, ":", e->es_portmask);
+ return (0);
+ default:
+ return (EINVAL);
+ }
+ return (EINVAL);
+}
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_hw_mdio.h b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mdio.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mdio.h
@@ -0,0 +1,40 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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 __AR40XX_HW_MDIO_H__
+#define __AR40XX_HW_MDIO_H__
+
+extern int ar40xx_hw_phy_dbg_write(struct ar40xx_softc *sc, int phy,
+ uint16_t dbg, uint16_t data);
+extern int ar40xx_hw_phy_dbg_read(struct ar40xx_softc *sc, int phy,
+ uint16_t dbg);
+extern int ar40xx_hw_phy_mmd_write(struct ar40xx_softc *sc, uint32_t phy_id,
+ uint16_t mmd_num, uint16_t reg_id, uint16_t reg_val);
+extern int ar40xx_hw_phy_mmd_read(struct ar40xx_softc *sc, uint32_t phy_id,
+ uint16_t mmd_num, uint16_t reg_id);
+
+#endif /* __AR40XX_HW_MDIO_H__ */
+
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_hw_mdio.c b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mdio.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mdio.c
@@ -0,0 +1,129 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/iicbus/iic.h>
+#include <dev/iicbus/iiconf.h>
+#include <dev/iicbus/iicbus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/ar40xx/ar40xx_var.h>
+#include <dev/etherswitch/ar40xx/ar40xx_reg.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw.h>
+
+#include <dev/etherswitch/ar40xx/ar40xx_hw_mdio.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+int
+ar40xx_hw_phy_dbg_write(struct ar40xx_softc *sc, int phy, uint16_t dbg,
+ uint16_t data)
+{
+ AR40XX_LOCK_ASSERT(sc);
+ device_printf(sc->sc_dev, "%s: TODO\n", __func__);
+ return (0);
+}
+
+int
+ar40xx_hw_phy_dbg_read(struct ar40xx_softc *sc, int phy, uint16_t dbg)
+{
+ AR40XX_LOCK_ASSERT(sc);
+ device_printf(sc->sc_dev, "%s: TODO\n", __func__);
+ return (-1);
+}
+
+int
+ar40xx_hw_phy_mmd_write(struct ar40xx_softc *sc, uint32_t phy_id,
+ uint16_t mmd_num, uint16_t reg_id, uint16_t reg_val)
+{
+
+ AR40XX_LOCK_ASSERT(sc);
+
+ MDIO_WRITEREG(sc->sc_mdio_dev, phy_id, AR40XX_MII_ATH_MMD_ADDR,
+ mmd_num);
+ MDIO_WRITEREG(sc->sc_mdio_dev, phy_id, AR40XX_MII_ATH_MMD_DATA,
+ reg_id);
+ MDIO_WRITEREG(sc->sc_mdio_dev, phy_id, AR40XX_MII_ATH_MMD_ADDR,
+ 0x4000 | mmd_num);
+ MDIO_WRITEREG(sc->sc_mdio_dev, phy_id, AR40XX_MII_ATH_MMD_DATA,
+ reg_val);
+
+ return (0);
+}
+
+int
+ar40xx_hw_phy_mmd_read(struct ar40xx_softc *sc, uint32_t phy_id,
+ uint16_t mmd_num, uint16_t reg_id)
+{
+ uint16_t value;
+
+ AR40XX_LOCK_ASSERT(sc);
+
+ MDIO_WRITEREG(sc->sc_mdio_dev, phy_id, AR40XX_MII_ATH_MMD_ADDR,
+ mmd_num);
+ MDIO_WRITEREG(sc->sc_mdio_dev, phy_id, AR40XX_MII_ATH_MMD_DATA,
+ reg_id);
+ MDIO_WRITEREG(sc->sc_mdio_dev, phy_id, AR40XX_MII_ATH_MMD_ADDR,
+ 0x4000 | mmd_num);
+
+ value = MDIO_READREG(sc->sc_mdio_dev, phy_id,
+ AR40XX_MII_ATH_MMD_DATA);
+
+ return value;
+}
+
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_hw_mib.h b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mib.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mib.h
@@ -0,0 +1,36 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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 __AR40XX_HW_MIB_H__
+#define __AR40XX_HW_MIB_H__
+
+extern int ar40xx_hw_mib_op(struct ar40xx_softc *sc, uint32_t op);
+extern int ar40xx_hw_mib_capture(struct ar40xx_softc *sc);
+extern int ar40xx_hw_mib_flush(struct ar40xx_softc *sc);
+
+extern int ar40xx_hw_mib_fetch(struct ar40xx_softc *sc, int port);
+
+#endif /* __AR40XX_HW_MIB_H__ */
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_hw_mib.c b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mib.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mib.c
@@ -0,0 +1,194 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/iicbus/iic.h>
+#include <dev/iicbus/iiconf.h>
+#include <dev/iicbus/iicbus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/ar40xx/ar40xx_var.h>
+#include <dev/etherswitch/ar40xx/ar40xx_reg.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_mib.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+
+#define MIB_DESC(_s , _o, _n) \
+ { \
+ .size = (_s), \
+ .offset = (_o), \
+ .name = (_n), \
+ }
+
+static const struct ar40xx_mib_desc ar40xx_mibs[] = {
+ MIB_DESC(1, AR40XX_STATS_RXBROAD, "RxBroad"),
+ MIB_DESC(1, AR40XX_STATS_RXPAUSE, "RxPause"),
+ MIB_DESC(1, AR40XX_STATS_RXMULTI, "RxMulti"),
+ MIB_DESC(1, AR40XX_STATS_RXFCSERR, "RxFcsErr"),
+ MIB_DESC(1, AR40XX_STATS_RXALIGNERR, "RxAlignErr"),
+ MIB_DESC(1, AR40XX_STATS_RXRUNT, "RxRunt"),
+ MIB_DESC(1, AR40XX_STATS_RXFRAGMENT, "RxFragment"),
+ MIB_DESC(1, AR40XX_STATS_RX64BYTE, "Rx64Byte"),
+ MIB_DESC(1, AR40XX_STATS_RX128BYTE, "Rx128Byte"),
+ MIB_DESC(1, AR40XX_STATS_RX256BYTE, "Rx256Byte"),
+ MIB_DESC(1, AR40XX_STATS_RX512BYTE, "Rx512Byte"),
+ MIB_DESC(1, AR40XX_STATS_RX1024BYTE, "Rx1024Byte"),
+ MIB_DESC(1, AR40XX_STATS_RX1518BYTE, "Rx1518Byte"),
+ MIB_DESC(1, AR40XX_STATS_RXMAXBYTE, "RxMaxByte"),
+ MIB_DESC(1, AR40XX_STATS_RXTOOLONG, "RxTooLong"),
+ MIB_DESC(2, AR40XX_STATS_RXGOODBYTE, "RxGoodByte"),
+ MIB_DESC(2, AR40XX_STATS_RXBADBYTE, "RxBadByte"),
+ MIB_DESC(1, AR40XX_STATS_RXOVERFLOW, "RxOverFlow"),
+ MIB_DESC(1, AR40XX_STATS_FILTERED, "Filtered"),
+ MIB_DESC(1, AR40XX_STATS_TXBROAD, "TxBroad"),
+ MIB_DESC(1, AR40XX_STATS_TXPAUSE, "TxPause"),
+ MIB_DESC(1, AR40XX_STATS_TXMULTI, "TxMulti"),
+ MIB_DESC(1, AR40XX_STATS_TXUNDERRUN, "TxUnderRun"),
+ MIB_DESC(1, AR40XX_STATS_TX64BYTE, "Tx64Byte"),
+ MIB_DESC(1, AR40XX_STATS_TX128BYTE, "Tx128Byte"),
+ MIB_DESC(1, AR40XX_STATS_TX256BYTE, "Tx256Byte"),
+ MIB_DESC(1, AR40XX_STATS_TX512BYTE, "Tx512Byte"),
+ MIB_DESC(1, AR40XX_STATS_TX1024BYTE, "Tx1024Byte"),
+ MIB_DESC(1, AR40XX_STATS_TX1518BYTE, "Tx1518Byte"),
+ MIB_DESC(1, AR40XX_STATS_TXMAXBYTE, "TxMaxByte"),
+ MIB_DESC(1, AR40XX_STATS_TXOVERSIZE, "TxOverSize"),
+ MIB_DESC(2, AR40XX_STATS_TXBYTE, "TxByte"),
+ MIB_DESC(1, AR40XX_STATS_TXCOLLISION, "TxCollision"),
+ MIB_DESC(1, AR40XX_STATS_TXABORTCOL, "TxAbortCol"),
+ MIB_DESC(1, AR40XX_STATS_TXMULTICOL, "TxMultiCol"),
+ MIB_DESC(1, AR40XX_STATS_TXSINGLECOL, "TxSingleCol"),
+ MIB_DESC(1, AR40XX_STATS_TXEXCDEFER, "TxExcDefer"),
+ MIB_DESC(1, AR40XX_STATS_TXDEFER, "TxDefer"),
+ MIB_DESC(1, AR40XX_STATS_TXLATECOL, "TxLateCol"),
+};
+
+
+int
+ar40xx_hw_mib_op(struct ar40xx_softc *sc, uint32_t op)
+{
+ uint32_t reg;
+ int ret;
+
+ AR40XX_LOCK_ASSERT(sc);
+
+ /* Trigger capturing statistics on all ports */
+ AR40XX_REG_BARRIER_READ(sc);
+ reg = AR40XX_REG_READ(sc, AR40XX_REG_MIB_FUNC);
+ reg &= ~AR40XX_MIB_FUNC;
+ reg |= (op << AR40XX_MIB_FUNC_S);
+ AR40XX_REG_WRITE(sc, AR40XX_REG_MIB_FUNC, reg);
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ /* Now wait */
+ ret = ar40xx_hw_wait_bit(sc, AR40XX_REG_MIB_FUNC,
+ AR40XX_MIB_BUSY, 0);
+ if (ret != 0) {
+ device_printf(sc->sc_dev,
+ "%s: ERROR: timeout waiting for MIB load\n",
+ __func__);
+ }
+
+ return ret;
+}
+
+int
+ar40xx_hw_mib_capture(struct ar40xx_softc *sc)
+{
+ int ret;
+
+ ret = ar40xx_hw_mib_op(sc, AR40XX_MIB_FUNC_CAPTURE);
+ return (ret);
+}
+
+int
+ar40xx_hw_mib_flush(struct ar40xx_softc *sc)
+{
+ int ret;
+
+ ret = ar40xx_hw_mib_op(sc, AR40XX_MIB_FUNC_FLUSH);
+ return (ret);
+}
+
+int
+ar40xx_hw_mib_fetch(struct ar40xx_softc *sc, int port)
+{
+ uint64_t val;
+ uint32_t base, reg;
+ int i;
+
+ base = AR40XX_REG_PORT_STATS_START
+ + (AR40XX_REG_PORT_STATS_LEN * port);
+
+ /* For now just print them out, we'll store them later */
+ AR40XX_REG_BARRIER_READ(sc);
+ for (i = 0; i < nitems(ar40xx_mibs); i++) {
+ val = 0;
+
+ val = AR40XX_REG_READ(sc, base + ar40xx_mibs[i].offset);
+ if (ar40xx_mibs[i].size == 2) {
+ reg = AR40XX_REG_READ(sc, base + ar40xx_mibs[i].offset + 4);
+ val |= ((uint64_t) reg << 32);
+ }
+
+ device_printf(sc->sc_dev, "%s[%d] = %llu\n", ar40xx_mibs[i].name, port, val);
+ }
+
+ return (0);
+}
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_hw_mirror.h b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mirror.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mirror.h
@@ -0,0 +1,33 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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 __AR40XX_HW_MIRROR_H__
+#define __AR40XX_HW_MIRROR_H__
+
+extern int ar40xx_hw_mirror_set_registers(struct ar40xx_softc *sc);
+
+#endif /* __AR40XX_HW_H__ */
+
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_hw_mirror.c b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mirror.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mirror.c
@@ -0,0 +1,132 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/iicbus/iic.h>
+#include <dev/iicbus/iiconf.h>
+#include <dev/iicbus/iicbus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/ar40xx/ar40xx_var.h>
+#include <dev/etherswitch/ar40xx/ar40xx_reg.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_mirror.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+
+int
+ar40xx_hw_mirror_set_registers(struct ar40xx_softc *sc)
+{
+ uint32_t reg;
+ int port;
+
+ /* Reset the mirror registers before configuring */
+ reg = AR40XX_REG_READ(sc, AR40XX_REG_FWD_CTRL0);
+ reg &= ~(AR40XX_FWD_CTRL0_MIRROR_PORT);
+ reg |= (0xF << AR40XX_FWD_CTRL0_MIRROR_PORT_S);
+ AR40XX_REG_WRITE(sc, AR40XX_REG_FWD_CTRL0, reg);
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ for (port = 0; port < AR40XX_NUM_PORTS; port++) {
+ reg = AR40XX_REG_READ(sc, AR40XX_REG_PORT_LOOKUP(port));
+ reg &= ~AR40XX_PORT_LOOKUP_ING_MIRROR_EN;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_LOOKUP(port), reg);
+
+ reg = AR40XX_REG_READ(sc, AR40XX_REG_PORT_HOL_CTRL1(port));
+ reg &= ~AR40XX_PORT_HOL_CTRL1_EG_MIRROR_EN;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_HOL_CTRL1(port), reg);
+
+ AR40XX_REG_BARRIER_WRITE(sc);
+ }
+
+ /* Now, enable mirroring if requested */
+ if (sc->sc_monitor.source_port >= AR40XX_NUM_PORTS
+ || sc->sc_monitor.monitor_port >= AR40XX_NUM_PORTS
+ || sc->sc_monitor.source_port == sc->sc_monitor.monitor_port) {
+ return (0);
+ }
+
+ reg = AR40XX_REG_READ(sc, AR40XX_REG_FWD_CTRL0);
+ reg &= ~AR40XX_FWD_CTRL0_MIRROR_PORT;
+ reg |=
+ (sc->sc_monitor.monitor_port << AR40XX_FWD_CTRL0_MIRROR_PORT_S);
+ AR40XX_REG_WRITE(sc, AR40XX_REG_FWD_CTRL0, reg);
+
+ if (sc->sc_monitor.mirror_rx) {
+ reg = AR40XX_REG_READ(sc,
+ AR40XX_REG_PORT_LOOKUP(sc->sc_monitor.source_port));
+ reg |= AR40XX_PORT_LOOKUP_ING_MIRROR_EN;
+ AR40XX_REG_WRITE(sc,
+ AR40XX_REG_PORT_LOOKUP(sc->sc_monitor.source_port),
+ reg);
+ AR40XX_REG_BARRIER_WRITE(sc);
+ }
+
+ if (sc->sc_monitor.mirror_tx) {
+ reg = AR40XX_REG_READ(sc,
+ AR40XX_REG_PORT_HOL_CTRL1(sc->sc_monitor.source_port));
+ reg |= AR40XX_PORT_HOL_CTRL1_EG_MIRROR_EN;
+ AR40XX_REG_WRITE(sc,
+ AR40XX_REG_PORT_HOL_CTRL1(sc->sc_monitor.source_port),
+ reg);
+ AR40XX_REG_BARRIER_WRITE(sc);
+ }
+
+ return (0);
+}
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_hw_port.h b/sys/dev/etherswitch/ar40xx/ar40xx_hw_port.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_port.h
@@ -0,0 +1,42 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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 __AR40XX_HW_PORT_H__
+#define __AR40XX_HW_PORT_H__
+
+extern int ar40xx_hw_port_init(struct ar40xx_softc *sc, int port);
+extern int ar40xx_hw_port_cpuport_setup(struct ar40xx_softc *sc);
+extern int ar40xx_hw_port_link_up(struct ar40xx_softc *sc, int port);
+extern int ar40xx_hw_port_link_down(struct ar40xx_softc *sc, int port);
+extern int ar40xx_hw_get_port_pvid(struct ar40xx_softc *sc, int port,
+ int *pvid);
+extern int ar40xx_hw_set_port_pvid(struct ar40xx_softc *sc, int port,
+ int pvid);
+extern int ar40xx_hw_port_setup(struct ar40xx_softc *sc, int port,
+ uint32_t members);
+
+#endif /* __AR40XX_HW_PORT_H__ */
+
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_hw_port.c b/sys/dev/etherswitch/ar40xx/ar40xx_hw_port.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_port.c
@@ -0,0 +1,287 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/iicbus/iic.h>
+#include <dev/iicbus/iiconf.h>
+#include <dev/iicbus/iicbus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/ar40xx/ar40xx_var.h>
+#include <dev/etherswitch/ar40xx/ar40xx_reg.h>
+#include <dev/etherswitch/ar40xx/ar40xx_debug.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_port.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+
+int
+ar40xx_hw_port_init(struct ar40xx_softc *sc, int port)
+{
+ uint32_t reg;
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_HW_PORT_INIT,
+ "%s: called; port %d\n", __func__, port);
+
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_STATUS(port), 0);
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_HEADER(port), 0);
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_VLAN0(port), 0);
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ DELAY(20);
+
+ /*
+ * Ok! Here is where things get super fun in the AR40xx
+ * driver in uboot/linux.
+ *
+ * The earlier chipset switch drivers enable auto link enable here.
+ * The switch will poll the PHYs too, and configure appropriately.
+ *
+ * The ar40xx code in linux/u-boot instead has a whole workaround
+ * path that polls things directly and does some weird hijinx.
+ * NOTABLY - they do NOT enable the TX/RX MAC here or autoneg -
+ * it's done in the work around path.
+ *
+ * SO - for now the port is left off until the PHY state changes.
+ * And then we flip it on and off based on the PHY state.
+ */
+#if 0
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_STATUS(port),
+ AR40XX_PORT_AUTO_LINK_EN);
+#endif
+
+ /*
+ * Configure the VLAN egress mode (don't touch them) and
+ * learning state for STP/ATU. This isn't currently
+ * configurable so it's just nailed up here and left alone.
+ */
+ reg = AR40XX_PORT_VLAN1_OUT_MODE_UNTOUCH
+ << AR40XX_PORT_VLAN1_OUT_MODE_S;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_VLAN1(port), reg);
+
+ reg = AR40XX_PORT_LOOKUP_LEARN;
+ reg |= AR40XX_PORT_STATE_FORWARD << AR40XX_PORT_LOOKUP_STATE_S;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_LOOKUP(port), reg);
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ return (0);
+}
+
+/*
+ * Call when the link for a non-CPU port is down.
+ *
+ * This will turn off the MAC/forwarding path for this port.
+ */
+int
+ar40xx_hw_port_link_down(struct ar40xx_softc *sc, int port)
+{
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_HW_PORT_INIT,
+ "%s: called; port %d\n", __func__, port);
+
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_STATUS(port), 0);
+
+ return (0);
+}
+
+/*
+ * Call when the link for a non-CPU port is up.
+ *
+ * This will turn on the default auto-link checking and
+ * eventually enable the TX/RX MAC.
+ */
+int
+ar40xx_hw_port_link_up(struct ar40xx_softc *sc, int port)
+{
+ uint32_t reg;
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_HW_PORT_INIT,
+ "%s: called; port %d\n", __func__, port);
+
+ /* Auto-link enable */
+ AR40XX_REG_BARRIER_READ(sc);
+ reg = AR40XX_REG_READ(sc, AR40XX_REG_PORT_STATUS(port));
+ reg |= AR40XX_PORT_AUTO_LINK_EN;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_STATUS(port), reg);
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ return (0);
+}
+
+/*
+ * Setup the CPU facing port. For this device it'll only
+ * be port 0.
+ */
+int
+ar40xx_hw_port_cpuport_setup(struct ar40xx_softc *sc)
+{
+ uint32_t reg;
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_HW_PORT_INIT, "%s: called\n",
+ __func__);
+
+ reg = AR40XX_PORT_STATUS_TXFLOW
+ | AR40XX_PORT_STATUS_RXFLOW
+ | AR40XX_PORT_TXHALF_FLOW
+ | AR40XX_PORT_DUPLEX
+ | AR40XX_PORT_SPEED_1000M;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_STATUS(0), reg);
+ DELAY(20);
+
+ reg |= AR40XX_PORT_TX_EN | AR40XX_PORT_RX_EN;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_STATUS(0), reg);
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ return (0);
+}
+
+/*
+ * Fetch the port PVID.
+ *
+ * For 802.1q mode this is the default VLAN ID for the port.
+ * Frames without an 802.1q VLAN will assume this VLAN ID for
+ * transmit/receive.
+ */
+int
+ar40xx_hw_get_port_pvid(struct ar40xx_softc *sc, int port, int *pvid)
+{
+ uint32_t reg;
+
+ AR40XX_LOCK_ASSERT(sc);
+
+ AR40XX_REG_BARRIER_READ(sc);
+ reg = AR40XX_REG_READ(sc, AR40XX_REG_PORT_VLAN0(port));
+
+ reg = reg >> AR40XX_PORT_VLAN0_DEF_CVID_S;
+ reg = reg & 0x0fff; /* XXX */
+
+ *pvid = reg;
+ return (0);
+}
+
+/*
+ * Set the port PVID.
+ *
+ * For now, since double-tagged frames aren't currently supported,
+ * CVID=SVID here.
+ */
+int
+ar40xx_hw_set_port_pvid(struct ar40xx_softc *sc, int port, int pvid)
+{
+ uint32_t reg;
+
+ AR40XX_LOCK_ASSERT(sc);
+
+ pvid &= ETHERSWITCH_VID_MASK;
+
+ reg = pvid << AR40XX_PORT_VLAN0_DEF_SVID_S;
+ reg |= pvid << AR40XX_PORT_VLAN0_DEF_CVID_S;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_VLAN0(port), reg);
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ return (0);
+}
+
+/*
+ * Setup the default port membership configuration.
+ *
+ * This configures the PVID for the port in the sc_vlan config,
+ * along with a set of ports that constitute the "membership"
+ * of this particular VID.
+ *
+ * For 802.1q mode the membership can be viewed as the default
+ * learning port group, but this can be added to via VLAN membership.
+ * (Eg you could in theory split two LAN ports into separate "member"
+ * groups and they'd not learn MAC addresses from each other even
+ * inside a VLAN; you'd then end up with the traffic being flooded to
+ * the CPU port.)
+ */
+int
+ar40xx_hw_port_setup(struct ar40xx_softc *sc, int port, uint32_t members)
+{
+ uint32_t egress, ingress, reg;
+ uint32_t pvid = sc->sc_vlan.vlan_id[sc->sc_vlan.pvid[port]]
+ & ETHERSWITCH_VID_MASK;
+
+ if (sc->sc_vlan.vlan) {
+ egress = AR40XX_PORT_VLAN1_OUT_MODE_UNMOD;
+ ingress = AR40XX_IN_SECURE;
+ } else {
+ egress = AR40XX_PORT_VLAN1_OUT_MODE_UNTOUCH;
+ ingress = AR40XX_IN_PORT_ONLY;
+ }
+
+ reg = pvid << AR40XX_PORT_VLAN0_DEF_SVID_S;
+ reg |= pvid << AR40XX_PORT_VLAN0_DEF_CVID_S;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_VLAN0(port), reg);
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ reg = AR40XX_PORT_VLAN1_PORT_VLAN_PROP;
+ reg |= egress << AR40XX_PORT_VLAN1_OUT_MODE_S;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_VLAN1(port), reg);
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ reg = members;
+ reg |= AR40XX_PORT_LOOKUP_LEARN;
+ reg |= ingress << AR40XX_PORT_LOOKUP_IN_MODE_S;
+ reg |= AR40XX_PORT_STATE_FORWARD << AR40XX_PORT_LOOKUP_STATE_S;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_LOOKUP(port), reg);
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ return (0);
+}
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_hw_psgmii.h b/sys/dev/etherswitch/ar40xx/ar40xx_hw_psgmii.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_psgmii.h
@@ -0,0 +1,41 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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 __AR40XX_HW_PSGMII_H__
+#define __AR40XX_HW_PSGMII_H__
+
+extern int ar40xx_hw_psgmii_set_mac_mode(struct ar40xx_softc *sc,
+ uint32_t mac_mode);
+extern int ar40xx_hw_psgmii_self_test(struct ar40xx_softc *sc);
+extern int ar40xx_hw_psgmii_self_test_clean(struct ar40xx_softc *sc);
+extern int ar40xx_hw_psgmii_single_phy_testing(struct ar40xx_softc *sc,
+ int phy);
+extern int ar40xx_hw_malibu_psgmii_ess_reset(struct ar40xx_softc *sc);
+extern int ar40xx_hw_psgmii_all_phy_testing(struct ar40xx_softc *sc);
+extern int ar40xx_hw_psgmii_init_config(struct ar40xx_softc *sc);
+
+#endif /* __AR40XX_HW_PSGMII_H__ */
+
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_hw_psgmii.c b/sys/dev/etherswitch/ar40xx/ar40xx_hw_psgmii.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_psgmii.c
@@ -0,0 +1,437 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/iicbus/iic.h>
+#include <dev/iicbus/iiconf.h>
+#include <dev/iicbus/iicbus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/ar40xx/ar40xx_var.h>
+#include <dev/etherswitch/ar40xx/ar40xx_reg.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw.h>
+#include <dev/etherswitch/ar40xx/ar40xx_phy.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_atu.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_mdio.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_psgmii.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+/*
+ * Routines that control the ess-psgmii block - the interconnect
+ * between the ess-switch and the external multi-port PHY
+ * (eg Maple.)
+ */
+
+static void
+ar40xx_hw_psgmii_reg_write(struct ar40xx_softc *sc, uint32_t reg,
+ uint32_t val)
+{
+ bus_space_write_4(sc->sc_psgmii_mem_tag, sc->sc_psgmii_mem_handle,
+ reg, val);
+ bus_space_barrier(sc->sc_psgmii_mem_tag, sc->sc_psgmii_mem_handle,
+ 0, sc->sc_psgmii_mem_size, BUS_SPACE_BARRIER_WRITE);
+}
+
+static int
+ar40xx_hw_psgmii_reg_read(struct ar40xx_softc *sc, uint32_t reg)
+{
+ int ret;
+
+ bus_space_barrier(sc->sc_psgmii_mem_tag, sc->sc_psgmii_mem_handle,
+ 0, sc->sc_psgmii_mem_size, BUS_SPACE_BARRIER_READ);
+ ret = bus_space_read_4(sc->sc_psgmii_mem_tag, sc->sc_psgmii_mem_handle,
+ reg);
+
+ return (ret);
+}
+
+int
+ar40xx_hw_psgmii_set_mac_mode(struct ar40xx_softc *sc, uint32_t mac_mode)
+{
+ if (mac_mode == PORT_WRAPPER_PSGMII) {
+ ar40xx_hw_psgmii_reg_write(sc, AR40XX_PSGMII_MODE_CONTROL,
+ 0x2200);
+ ar40xx_hw_psgmii_reg_write(sc, AR40XX_PSGMIIPHY_TX_CONTROL,
+ 0x8380);
+ } else {
+ device_printf(sc->sc_dev, "WARNING: unknown MAC_MODE=%u\n",
+ mac_mode);
+ }
+
+ return (0);
+}
+
+int
+ar40xx_hw_psgmii_single_phy_testing(struct ar40xx_softc *sc, int phy)
+{
+ int j;
+ uint32_t tx_ok, tx_error;
+ uint32_t rx_ok, rx_error;
+ uint32_t tx_ok_high16;
+ uint32_t rx_ok_high16;
+ uint32_t tx_all_ok, rx_all_ok;
+
+ MDIO_WRITEREG(sc->sc_mdio_dev, phy, 0x0, 0x9000);
+ MDIO_WRITEREG(sc->sc_mdio_dev, phy, 0x0, 0x4140);
+
+ for (j = 0; j < AR40XX_PSGMII_CALB_NUM; j++) {
+ uint16_t status;
+
+ status = MDIO_READREG(sc->sc_mdio_dev, phy, 0x11);
+ if (status & AR40XX_PHY_SPEC_STATUS_LINK)
+ break;
+ /*
+ * the polling interval to check if the PHY link up
+ * or not
+ * maxwait_timer: 750 ms +/-10 ms
+ * minwait_timer : 1 us +/- 0.1us
+ * time resides in minwait_timer ~ maxwait_timer
+ * see IEEE 802.3 section 40.4.5.2
+ */
+ DELAY(8 * 1000);
+ }
+
+ /* enable check */
+ ar40xx_hw_phy_mmd_write(sc, phy, 7, 0x8029, 0x0000);
+ ar40xx_hw_phy_mmd_write(sc, phy, 7, 0x8029, 0x0003);
+
+ /* start traffic */
+ ar40xx_hw_phy_mmd_write(sc, phy, 7, 0x8020, 0xa000);
+ /*
+ *wait for all traffic end
+ * 4096(pkt num)*1524(size)*8ns(125MHz)=49.9ms
+ */
+ DELAY(60 * 1000);
+
+ /* check counter */
+ tx_ok = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802e);
+ tx_ok_high16 = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802d);
+ tx_error = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802f);
+ rx_ok = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802b);
+ rx_ok_high16 = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802a);
+ rx_error = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802c);
+ tx_all_ok = tx_ok + (tx_ok_high16 << 16);
+ rx_all_ok = rx_ok + (rx_ok_high16 << 16);
+
+ if (tx_all_ok == 0x1000 && tx_error == 0) {
+ /* success */
+ sc->sc_psgmii.phy_t_status &= ~(1U << phy);
+ } else {
+ device_printf(sc->sc_dev, "TX_OK=%d, tx_error=%d RX_OK=%d"
+ " rx_error=%d\n",
+ tx_all_ok, tx_error, rx_all_ok, rx_error);
+ device_printf(sc->sc_dev,
+ "PHY %d single test PSGMII issue happen!\n", phy);
+ sc->sc_psgmii.phy_t_status |= BIT(phy);
+ }
+
+ MDIO_WRITEREG(sc->sc_mdio_dev, phy, 0x0, 0x1840);
+ return (0);
+}
+
+int
+ar40xx_hw_psgmii_all_phy_testing(struct ar40xx_softc *sc)
+{
+ int phy, j;
+
+ MDIO_WRITEREG(sc->sc_mdio_dev, 0x1f, 0x0, 0x9000);
+ MDIO_WRITEREG(sc->sc_mdio_dev, 0x1f, 0x0, 0x4140);
+
+ for (j = 0; j < AR40XX_PSGMII_CALB_NUM; j++) {
+ for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++) {
+ uint16_t status;
+
+ status = MDIO_READREG(sc->sc_mdio_dev, phy, 0x11);
+ if (!(status & (1U << 10)))
+ break;
+ }
+
+ if (phy >= (AR40XX_NUM_PORTS - 1))
+ break;
+ /* The polling interval to check if the PHY link up or not */
+ DELAY(8*1000);
+ }
+
+ /* enable check */
+ ar40xx_hw_phy_mmd_write(sc, 0x1f, 7, 0x8029, 0x0000);
+ ar40xx_hw_phy_mmd_write(sc, 0x1f, 7, 0x8029, 0x0003);
+
+ /* start traffic */
+ ar40xx_hw_phy_mmd_write(sc, 0x1f, 7, 0x8020, 0xa000);
+ /*
+ * wait for all traffic end
+ * 4096(pkt num)*1524(size)*8ns(125MHz)=49.9ms
+ */
+ DELAY(60*1000); /* was 50ms */
+
+ for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++) {
+ uint32_t tx_ok, tx_error;
+ uint32_t rx_ok, rx_error;
+ uint32_t tx_ok_high16;
+ uint32_t rx_ok_high16;
+ uint32_t tx_all_ok, rx_all_ok;
+
+ /* check counter */
+ tx_ok = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802e);
+ tx_ok_high16 = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802d);
+ tx_error = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802f);
+ rx_ok = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802b);
+ rx_ok_high16 = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802a);
+ rx_error = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802c);
+
+ tx_all_ok = tx_ok + (tx_ok_high16<<16);
+ rx_all_ok = rx_ok + (rx_ok_high16<<16);
+ if (tx_all_ok == 0x1000 && tx_error == 0) {
+ /* success */
+ sc->sc_psgmii.phy_t_status &= ~(1U << (phy + 8));
+ } else {
+ device_printf(sc->sc_dev,
+ "PHY%d test see issue! (tx_all_ok=%u,"
+ " rx_all_ok=%u, tx_error=%u, rx_error=%u)\n",
+ phy, tx_all_ok, rx_all_ok, tx_error, rx_error);
+ sc->sc_psgmii.phy_t_status |= (1U << (phy + 8));
+ }
+ }
+
+ device_printf(sc->sc_dev, "PHY all test 0x%x\n",
+ sc->sc_psgmii.phy_t_status);
+ return (0);
+}
+
+/*
+ * Reset PSGMII in the Malibu PHY.
+ */
+int
+ar40xx_hw_malibu_psgmii_ess_reset(struct ar40xx_softc *sc)
+{
+ device_printf(sc->sc_dev, "%s: called\n", __func__);
+ uint32_t i;
+
+ /* reset phy psgmii */
+ /* fix phy psgmii RX 20bit */
+ MDIO_WRITEREG(sc->sc_mdio_dev, 5, 0x0, 0x005b);
+ /* reset phy psgmii */
+ MDIO_WRITEREG(sc->sc_mdio_dev, 5, 0x0, 0x001b);
+ /* release reset phy psgmii */
+ MDIO_WRITEREG(sc->sc_mdio_dev, 5, 0x0, 0x005b);
+
+ for (i = 0; i < AR40XX_PSGMII_CALB_NUM; i++) {
+ uint32_t status;
+
+ status = ar40xx_hw_phy_mmd_read(sc, 5, 1, 0x28);
+ if (status & (1U << 0))
+ break;
+ /*
+ * Polling interval to check PSGMII PLL in malibu is ready
+ * the worst time is 8.67ms
+ * for 25MHz reference clock
+ * [512+(128+2048)*49]*80ns+100us
+ */
+ DELAY(2000);
+ }
+ /* XXX TODO ;see if it timed out? */
+
+ /*check malibu psgmii calibration done end..*/
+
+ /*freeze phy psgmii RX CDR*/
+ MDIO_WRITEREG(sc->sc_mdio_dev, 5, 0x1a, 0x2230);
+
+ ar40xx_hw_ess_reset(sc);
+
+ /*check psgmii calibration done start*/
+ for (i = 0; i < AR40XX_PSGMII_CALB_NUM; i++) {
+ uint32_t status;
+
+ status = ar40xx_hw_psgmii_reg_read(sc, 0xa0);
+ if (status & (1U << 0))
+ break;
+ /* Polling interval to check PSGMII PLL in ESS is ready */
+ DELAY(2000);
+ }
+ /* XXX TODO ;see if it timed out? */
+
+ /* check dakota psgmii calibration done end..*/
+
+ /* release phy psgmii RX CDR */
+ MDIO_WRITEREG(sc->sc_mdio_dev, 5, 0x1a, 0x3230);
+ /* release phy psgmii RX 20bit */
+ MDIO_WRITEREG(sc->sc_mdio_dev, 5, 0x0, 0x005f);
+
+ return (0);
+}
+
+int
+ar40xx_hw_psgmii_self_test(struct ar40xx_softc *sc)
+{
+ uint32_t i, phy, reg;
+
+ device_printf(sc->sc_dev, "%s: called\n", __func__);
+
+ ar40xx_hw_malibu_psgmii_ess_reset(sc);
+
+ /* switch to access MII reg for copper */
+ MDIO_WRITEREG(sc->sc_mdio_dev, 4, 0x1f, 0x8500);
+ for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++) {
+ /*enable phy mdio broadcast write*/
+ ar40xx_hw_phy_mmd_write(sc, phy, 7, 0x8028, 0x801f);
+ }
+
+ /* force no link by power down */
+ MDIO_WRITEREG(sc->sc_mdio_dev, 0x1f, 0x0, 0x1840);
+
+ /* packet number*/
+ ar40xx_hw_phy_mmd_write(sc, 0x1f, 7, 0x8021, 0x1000);
+ ar40xx_hw_phy_mmd_write(sc, 0x1f, 7, 0x8062, 0x05e0);
+
+ /* fix mdi status */
+ MDIO_WRITEREG(sc->sc_mdio_dev, 0x1f, 0x10, 0x6800);
+ for (i = 0; i < AR40XX_PSGMII_CALB_NUM; i++) {
+ sc->sc_psgmii.phy_t_status = 0;
+
+ for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++) {
+ /* Enable port loopback for testing */
+ AR40XX_REG_BARRIER_READ(sc);
+ reg = AR40XX_REG_READ(sc,
+ AR40XX_REG_PORT_LOOKUP(phy + 1));
+ reg |= AR40XX_PORT_LOOKUP_LOOPBACK;
+ AR40XX_REG_WRITE(sc,
+ AR40XX_REG_PORT_LOOKUP(phy + 1), reg);
+ AR40XX_REG_BARRIER_WRITE(sc);
+ }
+
+ for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++)
+ ar40xx_hw_psgmii_single_phy_testing(sc, phy);
+
+ ar40xx_hw_psgmii_all_phy_testing(sc);
+
+ if (sc->sc_psgmii.phy_t_status)
+ ar40xx_hw_malibu_psgmii_ess_reset(sc);
+ else
+ break;
+ }
+
+ if (i >= AR40XX_PSGMII_CALB_NUM)
+ device_printf(sc->sc_dev, "PSGMII cannot recover\n");
+ else
+ device_printf(sc->sc_dev,
+ "PSGMII recovered after %d times reset\n", i);
+
+ /* configuration recover */
+ /* packet number */
+ ar40xx_hw_phy_mmd_write(sc, 0x1f, 7, 0x8021, 0x0);
+ /* disable check */
+ ar40xx_hw_phy_mmd_write(sc, 0x1f, 7, 0x8029, 0x0);
+ /* disable traffic */
+ ar40xx_hw_phy_mmd_write(sc, 0x1f, 7, 0x8020, 0x0);
+
+ return (0);
+}
+
+int
+ar40xx_hw_psgmii_self_test_clean(struct ar40xx_softc *sc)
+{
+ uint32_t reg;
+ int phy;
+
+ device_printf(sc->sc_dev, "%s: called\n", __func__);
+
+ /* disable phy internal loopback */
+ MDIO_WRITEREG(sc->sc_mdio_dev, 0x1f, 0x10, 0x6860);
+ MDIO_WRITEREG(sc->sc_mdio_dev, 0x1f, 0x0, 0x9040);
+
+ for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++) {
+ /* disable mac loop back */
+ reg = AR40XX_REG_READ(sc, AR40XX_REG_PORT_LOOKUP(phy + 1));
+ reg &= ~AR40XX_PORT_LOOKUP_LOOPBACK;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_LOOKUP(phy + 1), reg);
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ /* disable phy mdio broadcast write */
+ ar40xx_hw_phy_mmd_write(sc, phy, 7, 0x8028, 0x001f);
+ }
+
+ /* clear fdb entry */
+ ar40xx_hw_atu_flush_all(sc);
+
+ return (0);
+}
+
+int
+ar40xx_hw_psgmii_init_config(struct ar40xx_softc *sc)
+{
+ uint32_t reg;
+
+ /*
+ * This is based on what I found in uboot - it configures
+ * the initial ESS interconnect to either be PSGMII
+ * or RGMII.
+ */
+
+ /* For now, just assume PSGMII and fix it in post. */
+ /* PSGMIIPHY_PLL_VCO_RELATED_CTRL */
+ reg = ar40xx_hw_psgmii_reg_read(sc, 0x78c);
+ device_printf(sc->sc_dev,
+ "%s: PSGMIIPHY_PLL_VCO_RELATED_CTRL=0x%08x\n", __func__, reg);
+ /* PSGMIIPHY_VCO_CALIBRATION_CTRL */
+ reg = ar40xx_hw_psgmii_reg_read(sc, 0x09c);
+ device_printf(sc->sc_dev,
+ "%s: PSGMIIPHY_VCO_CALIBRATION_CTRL=0x%08x\n", __func__, reg);
+
+ return (0);
+}
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_hw_vtu.h b/sys/dev/etherswitch/ar40xx/ar40xx_hw_vtu.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_vtu.h
@@ -0,0 +1,39 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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 __AR40XX_HW_VTU_H__
+#define __AR40XX_HW_VTU_H__
+
+extern int ar40xx_hw_vtu_op(struct ar40xx_softc *sc, uint32_t op,
+ uint32_t val);
+extern int ar40xx_hw_vtu_load_vlan(struct ar40xx_softc *sc, uint32_t vid,
+ uint32_t port_mask, uint32_t untagged_mask);
+extern int ar40xx_hw_vtu_flush(struct ar40xx_softc *sc);
+extern int ar40xx_hw_vtu_get_vlan(struct ar40xx_softc *sc, int vid,
+ uint32_t *ports, uint32_t *untagged_ports);
+
+#endif /* __AR40XX_HW_VTU_H__ */
+
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_hw_vtu.c b/sys/dev/etherswitch/ar40xx/ar40xx_hw_vtu.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_vtu.c
@@ -0,0 +1,196 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/iicbus/iic.h>
+#include <dev/iicbus/iiconf.h>
+#include <dev/iicbus/iicbus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/ar40xx/ar40xx_var.h>
+#include <dev/etherswitch/ar40xx/ar40xx_reg.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_vtu.h>
+#include <dev/etherswitch/ar40xx/ar40xx_debug.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+
+/*
+ * Perform a VTU (vlan table unit) operation.
+ */
+int
+ar40xx_hw_vtu_op(struct ar40xx_softc *sc, uint32_t op, uint32_t val)
+{
+ int ret;
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_VTU_OP,
+ "%s: called; op=0x%08x, val=0x%08x\n",
+ __func__, op, val);
+
+ ret = (ar40xx_hw_wait_bit(sc, AR40XX_REG_VTU_FUNC1,
+ AR40XX_VTU_FUNC1_BUSY, 0));
+ if (ret != 0)
+ return (ret);
+
+ if ((op & AR40XX_VTU_FUNC1_OP) == AR40XX_VTU_FUNC1_OP_LOAD) {
+ AR40XX_REG_WRITE(sc, AR40XX_REG_VTU_FUNC0, val);
+ AR40XX_REG_BARRIER_WRITE(sc);
+ }
+
+ op |= AR40XX_VTU_FUNC1_BUSY;
+ AR40XX_REG_WRITE(sc, AR40XX_REG_VTU_FUNC1, op);
+ AR40XX_REG_BARRIER_WRITE(sc);
+
+ return (0);
+}
+
+/*
+ * Load in a VLAN table map / port configuration for the given
+ * vlan ID.
+ */
+int
+ar40xx_hw_vtu_load_vlan(struct ar40xx_softc *sc, uint32_t vid,
+ uint32_t port_mask, uint32_t untagged_mask)
+{
+
+ uint32_t op, val, mode;
+ int i, ret;
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_VTU_OP,
+ "%s: called; vid=%d port_mask=0x%08x, untagged_mask=0x%08x\n",
+ __func__, vid, port_mask, untagged_mask);
+
+ op = AR40XX_VTU_FUNC1_OP_LOAD | (vid << AR40XX_VTU_FUNC1_VID_S);
+ val = AR40XX_VTU_FUNC0_VALID | AR40XX_VTU_FUNC0_IVL;
+ for (i = 0; i < AR40XX_NUM_PORTS; i++) {
+ if ((port_mask & (1U << i)) == 0)
+ /* Not in the VLAN at all */
+ mode = AR40XX_VTU_FUNC0_EG_MODE_NOT;
+ else if (sc->sc_vlan.vlan == 0)
+ /* VLAN mode disabled; keep the provided VLAN tag */
+ mode = AR40XX_VTU_FUNC0_EG_MODE_KEEP;
+ else if (untagged_mask & (1U << i))
+ /* Port in the VLAN; is untagged */
+ mode = AR40XX_VTU_FUNC0_EG_MODE_UNTAG;
+ else
+ /* Port is in the VLAN; is tagged */
+ mode = AR40XX_VTU_FUNC0_EG_MODE_TAG;
+ val |= mode << AR40XX_VTU_FUNC0_EG_MODE_S(i);
+ }
+ ret = ar40xx_hw_vtu_op(sc, op, val);
+
+ return (ret);
+}
+
+/*
+ * Flush all VLAN port entries.
+ */
+int
+ar40xx_hw_vtu_flush(struct ar40xx_softc *sc)
+{
+ int ret;
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_VTU_OP, "%s: called\n", __func__);
+
+ ret = ar40xx_hw_vtu_op(sc, AR40XX_VTU_FUNC1_OP_FLUSH, 0);
+ return (ret);
+}
+
+/*
+ * Get the VLAN port map for the given vlan ID.
+ */
+int
+ar40xx_hw_vtu_get_vlan(struct ar40xx_softc *sc, int vid, uint32_t *ports,
+ uint32_t *untagged_ports)
+{
+ uint32_t op, reg, val;
+ int i, r;
+
+ op = AR40XX_VTU_FUNC1_OP_GET_ONE;
+
+ /* Filter out any etherswitch VID flags; only grab the VLAN ID */
+ vid &= ETHERSWITCH_VID_MASK;
+
+ /* XXX TODO: the VTU here stores egress mode - keep, tag, untagged, none */
+ op |= (vid << AR40XX_VTU_FUNC1_VID_S);
+ r = ar40xx_hw_vtu_op(sc, op, 0);
+ if (r != 0) {
+ device_printf(sc->sc_dev, "%s: %d: op failed\n", __func__, vid);
+ return (r);
+ }
+
+ AR40XX_REG_BARRIER_READ(sc);
+ reg = AR40XX_REG_READ(sc, AR40XX_REG_VTU_FUNC0);
+
+ *ports = 0;
+ for (i = 0; i < AR40XX_NUM_PORTS; i++) {
+ val = reg >> AR40XX_VTU_FUNC0_EG_MODE_S(i);
+ val = val & 0x3;
+ /* XXX KEEP (unmodified? For non-dot1q operation?) */
+ if (val == AR40XX_VTU_FUNC0_EG_MODE_TAG) {
+ *ports |= (1 << i);
+ } else if (val == AR40XX_VTU_FUNC0_EG_MODE_UNTAG) {
+ *ports |= (1 << i);
+ *untagged_ports |= (1 << i);
+ }
+ }
+
+ return (0);
+}
+
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_main.c b/sys/dev/etherswitch/ar40xx/ar40xx_main.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_main.c
@@ -0,0 +1,968 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/iicbus/iic.h>
+#include <dev/iicbus/iiconf.h>
+#include <dev/iicbus/iicbus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/ar40xx/ar40xx_var.h>
+#include <dev/etherswitch/ar40xx/ar40xx_reg.h>
+#include <dev/etherswitch/ar40xx/ar40xx_phy.h>
+#include <dev/etherswitch/ar40xx/ar40xx_debug.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_psgmii.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_port.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_mib.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_vtu.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_atu.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+static struct ofw_compat_data compat_data[] = {
+ { "qcom,ess-switch", 1 },
+ { NULL, 0 },
+};
+
+static int
+ar40xx_probe(device_t dev)
+{
+
+ if (! ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
+ return (ENXIO);
+
+ device_set_desc(dev, "IPQ4018 ESS Switch fabric / PSGMII PHY");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static void
+ar40xx_tick(void *arg)
+{
+ struct ar40xx_softc *sc = arg;
+
+ (void) ar40xx_phy_tick(sc);
+ callout_reset(&sc->sc_phy_callout, hz, ar40xx_tick, sc);
+}
+
+static void
+ar40xx_statchg(device_t dev)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_PORT_STATUS, "%s\n", __func__);
+}
+
+static int
+ar40xx_readphy(device_t dev, int phy, int reg)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+
+ return MDIO_READREG(sc->sc_mdio_dev, phy, reg);
+}
+
+static int
+ar40xx_writephy(device_t dev, int phy, int reg, int val)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+
+ return MDIO_WRITEREG(sc->sc_mdio_dev, phy, reg, val);
+}
+
+/*
+ * Do the initial switch configuration.
+ */
+static int
+ar40xx_reset_switch(struct ar40xx_softc *sc)
+{
+ int ret, i;
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_HW_INIT, "%s: called\n", __func__);
+
+ /* blank the VLAN config */
+ memset(&sc->sc_vlan, 0, sizeof(sc->sc_vlan));
+
+ /* initial vlan port mapping */
+ for (i = 0; i < AR40XX_NUM_VTU_ENTRIES; i++)
+ sc->sc_vlan.vlan_id[i] = 0;
+
+ /* init vlan config */
+ ret = ar40xx_hw_vlan_init(sc);
+
+ /* init monitor config */
+ sc->sc_monitor.mirror_tx = false;
+ sc->sc_monitor.mirror_rx = false;
+ sc->sc_monitor.source_port = 0;
+ sc->sc_monitor.monitor_port = 0;
+
+ /* apply switch config */
+ ret = ar40xx_hw_sw_hw_apply(sc);
+
+ return (ret);
+}
+
+static int
+ar40xx_sysctl_dump_port_state(SYSCTL_HANDLER_ARGS)
+{
+ struct ar40xx_softc *sc = arg1;
+ int val = 0;
+ int error;
+ int i;
+
+ (void) i; (void) sc;
+
+ error = sysctl_handle_int(oidp, &val, 0, req);
+ if (error || !req->newptr)
+ return (error);
+
+ if (val < 0 || val > 5) {
+ return (EINVAL);
+ }
+
+ AR40XX_LOCK(sc);
+
+ device_printf(sc->sc_dev, "port %d: PORT_STATUS=0x%08x\n", val,
+ AR40XX_REG_READ(sc, AR40XX_REG_PORT_STATUS(val)));
+ device_printf(sc->sc_dev, "port %d: PORT_HEADER=0x%08x\n", val,
+ AR40XX_REG_READ(sc, AR40XX_REG_PORT_HEADER(val)));
+ device_printf(sc->sc_dev, "port %d: PORT_VLAN0=0x%08x\n", val,
+ AR40XX_REG_READ(sc, AR40XX_REG_PORT_VLAN0(val)));
+ device_printf(sc->sc_dev, "port %d: PORT_VLAN1=0x%08x\n", val,
+ AR40XX_REG_READ(sc, AR40XX_REG_PORT_VLAN1(val)));
+ device_printf(sc->sc_dev, "port %d: PORT_LOOKUP=0x%08x\n", val,
+ AR40XX_REG_READ(sc, AR40XX_REG_PORT_LOOKUP(val)));
+ device_printf(sc->sc_dev, "port %d: PORT_HOL_CTRL1=0x%08x\n", val,
+ AR40XX_REG_READ(sc, AR40XX_REG_PORT_HOL_CTRL1(val)));
+ device_printf(sc->sc_dev, "port %d: PORT_FLOWCTRL_THRESH=0x%08x\n",
+ val, AR40XX_REG_READ(sc, AR40XX_REG_PORT_FLOWCTRL_THRESH(val)));
+
+ AR40XX_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+ar40xx_sysctl_dump_port_mibstats(SYSCTL_HANDLER_ARGS)
+{
+ struct ar40xx_softc *sc = arg1;
+ int val = 0;
+ int error;
+ int i;
+
+ (void) i; (void) sc;
+
+ error = sysctl_handle_int(oidp, &val, 0, req);
+ if (error || !req->newptr)
+ return (error);
+
+ if (val < 0 || val > 5) {
+ return (EINVAL);
+ }
+
+ AR40XX_LOCK(sc);
+
+ /* Yes, this snapshots all ports */
+ (void) ar40xx_hw_mib_capture(sc);
+ (void) ar40xx_hw_mib_fetch(sc, val);
+
+ AR40XX_UNLOCK(sc);
+
+ return (0);
+}
+
+
+static int
+ar40xx_sysctl_attach(struct ar40xx_softc *sc)
+{
+ struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev);
+ struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev);
+
+ SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+ "debug", CTLFLAG_RW, &sc->sc_debug, 0,
+ "debugging flags");
+
+ SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+ "port_state", CTLTYPE_INT | CTLFLAG_RW, sc,
+ 0, ar40xx_sysctl_dump_port_state, "I", "");
+
+ SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+ "port_mibstats", CTLTYPE_INT | CTLFLAG_RW, sc,
+ 0, ar40xx_sysctl_dump_port_mibstats, "I", "");
+
+ return (0);
+}
+
+static int
+ar40xx_detach(device_t dev)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+ int i;
+
+ device_printf(sc->sc_dev, "%s: called\n", __func__);
+
+ callout_drain(&sc->sc_phy_callout);
+
+ /* Free PHYs */
+ for (i = 0; i < AR40XX_NUM_PHYS; i++) {
+ if (sc->sc_phys.miibus[i] != NULL)
+ device_delete_child(dev, sc->sc_phys.miibus[i]);
+ if (sc->sc_phys.ifp[i] != NULL)
+ if_free(sc->sc_phys.ifp[i]);
+ free(sc->sc_phys.ifname[i], M_DEVBUF);
+ }
+
+ bus_generic_detach(dev);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static int
+ar40xx_attach(device_t dev)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+ phandle_t psgmii_p, root_p, mdio_p;
+ int ret, i;
+
+ sc->sc_dev = dev;
+ mtx_init(&sc->sc_mtx, "ar40xx_switch", NULL, MTX_DEF);
+
+ psgmii_p = OF_finddevice("/soc/ess-psgmii");
+ if (psgmii_p == -1) {
+ device_printf(dev,
+ "%s: couldn't find /soc/ess-psgmii DT node\n",
+ __func__);
+ goto error;
+ }
+
+ /*
+ * Get the ipq4019-mdio node here, to talk to our local PHYs
+ * if needed
+ */
+ root_p = OF_finddevice("/soc");
+ mdio_p = ofw_bus_find_compatible(root_p, "qcom,ipq4019-mdio");
+ if (mdio_p == -1) {
+ device_printf(dev, "%s: couldn't find ipq4019-mdio DT node\n",
+ __func__);
+ goto error;
+ }
+ sc->sc_mdio_phandle = mdio_p;
+ sc->sc_mdio_dev = OF_device_from_xref(OF_xref_from_node(mdio_p));
+ if (sc->sc_mdio_dev == NULL) {
+ device_printf(dev,
+ "%s: couldn't get mdio device (mdio_p=%u)\n",
+ __func__, mdio_p);
+ goto error;
+ }
+
+ /* get psgmii base address from psgmii node */
+ ret = OF_decode_addr(psgmii_p, 0, &sc->sc_psgmii_mem_tag,
+ &sc->sc_psgmii_mem_handle,
+ &sc->sc_psgmii_mem_size);
+ if (ret != 0) {
+ device_printf(dev, "%s: couldn't map psgmii mem (%d)\n",
+ __func__, ret);
+ goto error;
+ }
+
+ /* get switch base address */
+ sc->sc_ess_mem_rid = 0;
+ sc->sc_ess_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
+ &sc->sc_ess_mem_rid, RF_ACTIVE);
+ if (sc->sc_ess_mem_res == NULL) {
+ device_printf(dev, "%s: failed to find memory resource\n",
+ __func__);
+ goto error;
+ }
+ sc->sc_ess_mem_size = (size_t) bus_get_resource_count(dev,
+ SYS_RES_MEMORY, sc->sc_ess_mem_rid);
+ if (sc->sc_ess_mem_size == 0) {
+ device_printf(dev, "%s: failed to get device memory size\n",
+ __func__);
+ goto error;
+ }
+
+ ret = OF_getencprop(ofw_bus_get_node(dev), "switch_mac_mode",
+ &sc->sc_config.switch_mac_mode,
+ sizeof(sc->sc_config.switch_mac_mode));
+ if (ret < 0) {
+ device_printf(dev, "%s: missing switch_mac_mode property\n",
+ __func__);
+ goto error;
+ }
+
+ ret = OF_getencprop(ofw_bus_get_node(dev), "switch_cpu_bmp",
+ &sc->sc_config.switch_cpu_bmp,
+ sizeof(sc->sc_config.switch_cpu_bmp));
+ if (ret < 0) {
+ device_printf(dev, "%s: missing switch_cpu_bmp property\n",
+ __func__);
+ goto error;
+ }
+
+ ret = OF_getencprop(ofw_bus_get_node(dev), "switch_lan_bmp",
+ &sc->sc_config.switch_lan_bmp,
+ sizeof(sc->sc_config.switch_lan_bmp));
+ if (ret < 0) {
+ device_printf(dev, "%s: missing switch_lan_bmp property\n",
+ __func__);
+ goto error;
+ }
+
+ ret = OF_getencprop(ofw_bus_get_node(dev), "switch_wan_bmp",
+ &sc->sc_config.switch_wan_bmp,
+ sizeof(sc->sc_config.switch_wan_bmp));
+ if (ret < 0) {
+ device_printf(dev, "%s: missing switch_wan_bmp property\n",
+ __func__);
+ goto error;
+ }
+
+ ret = clk_get_by_ofw_name(dev, 0, "ess_clk", &sc->sc_ess_clk);
+ if (ret != 0) {
+ device_printf(dev, "%s: failed to find ess_clk (%d)\n",
+ __func__, ret);
+ goto error;
+ }
+ ret = clk_enable(sc->sc_ess_clk);
+ if (ret != 0) {
+ device_printf(dev, "%s: failed to enable clock (%d)\n",
+ __func__, ret);
+ goto error;
+ }
+
+ ret = hwreset_get_by_ofw_name(dev, 0, "ess_rst", &sc->sc_ess_rst);
+ if (ret != 0) {
+ device_printf(dev, "%s: failed to find ess_rst (%d)\n",
+ __func__, ret);
+ goto error;
+ }
+
+ /*
+ * Ok, at this point we have enough resources to do an initial
+ * reset and configuration.
+ */
+
+ AR40XX_LOCK(sc);
+
+ /* Initial PSGMII/RGMII port configuration */
+ ret = ar40xx_hw_psgmii_init_config(sc);
+ if (ret != 0) {
+ device_printf(sc->sc_dev,
+ "ERROR: failed to init PSGMII (%d)\n", ret);
+ goto error_locked;
+ }
+
+ /*
+ * ESS reset - this resets both the ethernet switch
+ * AND the ethernet block.
+ */
+ ret = ar40xx_hw_ess_reset(sc);
+ if (ret != 0) {
+ device_printf(sc->sc_dev,
+ "ERROR: failed to reset ESS block (%d)\n", ret);
+ goto error_locked;
+ }
+
+ /*
+ * Check the PHY IDs for each of the PHYs from 0..4;
+ * this is useful to make sure that we can SEE the external
+ * PHY(s).
+ */
+ if (bootverbose) {
+ ret = ar40xx_hw_phy_get_ids(sc);
+ if (ret != 0) {
+ device_printf(sc->sc_dev,
+ "ERROR: failed to check PHY IDs (%d)\n", ret);
+ goto error_locked;
+ }
+ }
+
+ /*
+ * Do PSGMII PHY self-test; work-around issues.
+ */
+ ret = ar40xx_hw_psgmii_self_test(sc);
+ if (ret != 0) {
+ device_printf(sc->sc_dev,
+ "ERROR: failed to do PSGMII self-test (%d)\n", ret);
+ goto error_locked;
+ }
+
+ /* Return port config to runtime state */
+ ret = ar40xx_hw_psgmii_self_test_clean(sc);
+ if (ret != 0) {
+ device_printf(sc->sc_dev,
+ "ERROR: failed to do PSGMII runtime config (%d)\n", ret);
+ goto error_locked;
+ }
+
+ /* mac_mode_init */
+ ret = ar40xx_hw_psgmii_set_mac_mode(sc,
+ sc->sc_config.switch_mac_mode);
+
+ /* Initialise each hardware port */
+ for (i = 0; i < AR40XX_NUM_PORTS; i++) {
+ ret = ar40xx_hw_port_init(sc, i);
+ }
+
+ /* initialise the global switch configuration */
+ ret = ar40xx_hw_init_globals(sc);
+
+ /* reset the switch vlan/port learning config */
+ ret = ar40xx_reset_switch(sc);
+
+ /* cpuport setup */
+ ret = ar40xx_hw_port_cpuport_setup(sc);
+
+ AR40XX_UNLOCK(sc);
+
+#if 0
+ /* We may end up needing the QM workaround code here.. */
+ device_printf(dev, "%s: TODO: QM error check\n", __func__);
+#endif
+
+ /* Attach PHYs */
+ ret = ar40xx_attach_phys(sc);
+
+ ret = bus_generic_probe(dev);
+ bus_enumerate_hinted_children(dev);
+ ret = bus_generic_attach(dev);
+
+ /* Start timer */
+ callout_init_mtx(&sc->sc_phy_callout, &sc->sc_mtx, 0);
+
+ /*
+ * Setup the etherswitch info block.
+ */
+ strlcpy(sc->sc_info.es_name, device_get_desc(dev),
+ sizeof(sc->sc_info.es_name));
+ sc->sc_info.es_nports = AR40XX_NUM_PORTS;
+ sc->sc_info.es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q;
+ /* XXX TODO: double-tag / 802.1ad */
+ sc->sc_info.es_nvlangroups = AR40XX_NUM_VTU_ENTRIES;
+
+ /*
+ * Fetch the initial port configuration.
+ */
+ AR40XX_LOCK(sc);
+ ar40xx_tick(sc);
+ AR40XX_UNLOCK(sc);
+
+ ar40xx_sysctl_attach(sc);
+
+ return (0);
+error_locked:
+ AR40XX_UNLOCK(sc);
+error:
+ ar40xx_detach(dev);
+ return (ENXIO);
+}
+
+static void
+ar40xx_lock(device_t dev)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+
+ AR40XX_LOCK(sc);
+}
+
+static void
+ar40xx_unlock(device_t dev)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+
+ AR40XX_LOCK_ASSERT(sc);
+ AR40XX_UNLOCK(sc);
+}
+
+static etherswitch_info_t *
+ar40xx_getinfo(device_t dev)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+
+ return (&sc->sc_info);
+}
+
+static int
+ar40xx_readreg(device_t dev, int addr)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+
+ if (addr >= sc->sc_ess_mem_size - 1)
+ return (-1);
+
+ AR40XX_REG_BARRIER_READ(sc);
+
+ return AR40XX_REG_READ(sc, addr);
+}
+
+static int
+ar40xx_writereg(device_t dev, int addr, int value)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+
+ if (addr >= sc->sc_ess_mem_size - 1)
+ return (-1);
+
+ AR40XX_REG_WRITE(sc, addr, value);
+ AR40XX_REG_BARRIER_WRITE(sc);
+ return (0);
+}
+
+/*
+ * Get the port configuration and status.
+ */
+static int
+ar40xx_getport(device_t dev, etherswitch_port_t *p)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+ struct mii_data *mii = NULL;
+ struct ifmediareq *ifmr;
+ int err;
+
+ if (p->es_port < 0 || p->es_port > sc->sc_info.es_nports)
+ return (ENXIO);
+
+ AR40XX_LOCK(sc);
+ /* Fetch the current VLAN configuration for this port */
+ /* PVID */
+ ar40xx_hw_get_port_pvid(sc, p->es_port, &p->es_pvid);
+
+ /*
+ * The VLAN egress aren't appropriate to the ports;
+ * instead it's part of the VLAN group config.
+ */
+
+ /* Get MII config */
+ mii = ar40xx_phy_miiforport(sc, p->es_port);
+
+ AR40XX_UNLOCK(sc);
+
+ if (p->es_port == 0) {
+ /* CPU port */
+ p->es_flags |= ETHERSWITCH_PORT_CPU;
+ ifmr = &p->es_ifmr;
+ ifmr->ifm_count = 0;
+ ifmr->ifm_current = ifmr->ifm_active =
+ IFM_ETHER | IFM_1000_T | IFM_FDX;
+ ifmr->ifm_mask = 0;
+ ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID;
+ } else if (mii != NULL) {
+ /* non-CPU port */
+ err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr,
+ &mii->mii_media, SIOCGIFMEDIA);
+ if (err)
+ return (err);
+ } else {
+ return (ENXIO);
+ }
+
+ return (0);
+}
+
+/*
+ * Set the port configuration and status.
+ */
+static int
+ar40xx_setport(device_t dev, etherswitch_port_t *p)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+ struct ifmedia *ifm;
+ struct mii_data *mii;
+ struct ifnet *ifp;
+ int ret;
+
+ if (p->es_port < 0 || p->es_port > sc->sc_info.es_nports)
+ return (EINVAL);
+
+ /* Port flags */
+ AR40XX_LOCK(sc);
+ ret = ar40xx_hw_set_port_pvid(sc, p->es_port, p->es_pvid);
+ if (ret != 0) {
+ AR40XX_UNLOCK(sc);
+ return (ret);
+ }
+ /* XXX TODO: tag strip/unstrip, double-tag, etc */
+ AR40XX_UNLOCK(sc);
+
+ /* Don't change media config on CPU port */
+ if (p->es_port == 0)
+ return (0);
+
+ mii = ar40xx_phy_miiforport(sc, p->es_port);
+ if (mii == NULL)
+ return (ENXIO);
+
+ ifp = ar40xx_phy_ifpforport(sc, p->es_port);
+
+ ifm = &mii->mii_media;
+ return (ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA));
+
+ return (0);
+}
+
+/*
+ * Get the current VLAN group (per-port, ISL, dot1q) configuration.
+ *
+ * For now the only supported operating mode is dot1q.
+ */
+static int
+ar40xx_getvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+ int vid, ret;
+
+ if (vg->es_vlangroup > sc->sc_info.es_nvlangroups)
+ return (EINVAL);
+
+ vg->es_untagged_ports = 0;
+ vg->es_member_ports = 0;
+ vg->es_fid = 0;
+
+ AR40XX_LOCK(sc);
+
+ /* Note: only supporting 802.1q VLAN config for now */
+ if (sc->sc_vlan.vlan != 1) {
+ vg->es_member_ports = 0;
+ vg->es_untagged_ports = 0;
+ AR40XX_UNLOCK(sc);
+ return (-1);
+ }
+
+ /* Get vlangroup mapping to VLAN id */
+ vid = sc->sc_vlan.vlan_id[vg->es_vlangroup];
+ if ((vid & ETHERSWITCH_VID_VALID) == 0) {
+ /* Not an active vgroup; bail */
+ AR40XX_UNLOCK(sc);
+ return (0);
+ }
+ vg->es_vid = vid;
+
+ ret = ar40xx_hw_vtu_get_vlan(sc, vid, &vg->es_member_ports,
+ &vg->es_untagged_ports);
+
+ AR40XX_UNLOCK(sc);
+
+ if (ret == 0) {
+ vg->es_vid |= ETHERSWITCH_VID_VALID;
+ }
+
+ return (ret);
+}
+
+/*
+ * Set the current VLAN group (per-port, ISL, dot1q) configuration.
+ *
+ * For now the only supported operating mode is dot1q.
+ */
+static int
+ar40xx_setvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+ int err, vid;
+
+ /* For now we only support 802.1q mode */
+ if (sc->sc_vlan.vlan == 0)
+ return (EINVAL);
+
+ AR40XX_LOCK(sc);
+ vid = sc->sc_vlan.vlan_id[vg->es_vlangroup];
+ /*
+ * If we have an 802.1q VID and it's different to the current one,
+ * purge the current VTU entry.
+ */
+ if ((vid != 0) &&
+ ((vid & ETHERSWITCH_VID_VALID) != 0) &&
+ ((vid & ETHERSWITCH_VID_MASK) !=
+ (vg->es_vid & ETHERSWITCH_VID_MASK))) {
+ AR40XX_DPRINTF(sc, AR40XX_DBG_VTU_OP,
+ "%s: purging VID %d first\n", __func__, vid);
+ err = ar40xx_hw_vtu_flush(sc);
+ if (err != 0) {
+ AR40XX_UNLOCK(sc);
+ return (err);
+ }
+ }
+
+ /* Update VLAN ID */
+ vid = vg->es_vid & ETHERSWITCH_VID_MASK;
+ sc->sc_vlan.vlan_id[vg->es_vlangroup] = vid;
+ if (vid == 0) {
+ /* Setting it to 0 disables the group */
+ AR40XX_UNLOCK(sc);
+ return (0);
+ }
+ /* Add valid bit for this entry */
+ sc->sc_vlan.vlan_id[vg->es_vlangroup] = vid | ETHERSWITCH_VID_VALID;
+
+ /* Update hardware */
+ err = ar40xx_hw_vtu_load_vlan(sc, vid, vg->es_member_ports,
+ vg->es_untagged_ports);
+ if (err != 0) {
+ AR40XX_UNLOCK(sc);
+ return (err);
+ }
+
+ /* Update the config for the given entry */
+ sc->sc_vlan.vlan_ports[vg->es_vlangroup] = vg->es_member_ports;
+ sc->sc_vlan.vlan_untagged[vg->es_vlangroup] = vg->es_untagged_ports;
+
+ AR40XX_UNLOCK(sc);
+
+ return (0);
+}
+
+/*
+ * Get the current configuration mode.
+ */
+static int
+ar40xx_getconf(device_t dev, etherswitch_conf_t *conf)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+ int ret;
+
+ AR40XX_LOCK(sc);
+
+ /* Only support dot1q VLAN for now */
+ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE;
+ conf->vlan_mode = ETHERSWITCH_VLAN_DOT1Q;
+
+ /* Switch MAC address */
+ ret = ar40xx_hw_read_switch_mac_address(sc, &conf->switch_macaddr);
+ if (ret == 0)
+ conf->cmd |= ETHERSWITCH_CONF_SWITCH_MACADDR;
+
+ AR40XX_UNLOCK(sc);
+
+ return (0);
+}
+
+/*
+ * Set the current configuration and do a switch reset.
+ *
+ * For now the only supported operating mode is dot1q, don't
+ * allow it to be set to non-dot1q.
+ */
+static int
+ar40xx_setconf(device_t dev, etherswitch_conf_t *conf)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+ int ret = 0;
+
+ if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) {
+ /* Only support dot1q VLAN for now */
+ if (conf->vlan_mode != ETHERSWITCH_VLAN_DOT1Q)
+ return (EINVAL);
+ }
+
+ if (conf->cmd & ETHERSWITCH_CONF_SWITCH_MACADDR) {
+ AR40XX_LOCK(sc);
+ ret = ar40xx_hw_read_switch_mac_address(sc,
+ &conf->switch_macaddr);
+ AR40XX_UNLOCK(sc);
+ }
+
+ return (ret);
+}
+
+/*
+ * Flush all ATU entries.
+ */
+static int
+ar40xx_atu_flush_all(device_t dev)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+ int ret;
+
+ AR40XX_LOCK(sc);
+ ret = ar40xx_hw_atu_flush_all(sc);
+ AR40XX_UNLOCK(sc);
+ return (ret);
+}
+
+/*
+ * Flush all ATU entries for the given port.
+ */
+static int
+ar40xx_atu_flush_port(device_t dev, int port)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+ int ret;
+
+ AR40XX_LOCK(sc);
+ ret = ar40xx_hw_atu_flush_port(sc, port);
+ AR40XX_UNLOCK(sc);
+ return (ret);
+}
+
+/*
+ * Load the ATU table into local storage so it can be iterated
+ * over.
+ */
+static int
+ar40xx_atu_fetch_table(device_t dev, etherswitch_atu_table_t *table)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+ int err, nitems;
+
+ memset(&sc->atu.entries, 0, sizeof(sc->atu.entries));
+
+ table->es_nitems = 0;
+ nitems = 0;
+
+ AR40XX_LOCK(sc);
+ sc->atu.count = 0;
+ err = ar40xx_hw_atu_fetch_entry(sc, NULL, 0);
+ if (err != 0)
+ goto done;
+
+ while (nitems < AR40XX_NUM_ATU_ENTRIES) {
+ err = ar40xx_hw_atu_fetch_entry(sc,
+ &sc->atu.entries[nitems], 1);
+ if (err != 0)
+ goto done;
+ sc->atu.entries[nitems].id = nitems;
+ nitems++;
+ }
+done:
+ sc->atu.count = nitems;
+ table->es_nitems = nitems;
+ AR40XX_UNLOCK(sc);
+
+ return (0);
+}
+
+/*
+ * Iterate over the ATU table entries that have been previously
+ * fetched.
+ */
+static int
+ar40xx_atu_fetch_table_entry(device_t dev, etherswitch_atu_entry_t *e)
+{
+ struct ar40xx_softc *sc = device_get_softc(dev);
+ int id, err = 0;
+
+ id = e->id;
+ AR40XX_LOCK(sc);
+ if (id > sc->atu.count) {
+ err = ENOENT;
+ goto done;
+ }
+ memcpy(e, &sc->atu.entries[id], sizeof(*e));
+done:
+ AR40XX_UNLOCK(sc);
+ return (err);
+}
+
+static device_method_t ar40xx_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, ar40xx_probe),
+ DEVMETHOD(device_attach, ar40xx_attach),
+ DEVMETHOD(device_detach, ar40xx_detach),
+
+ /* bus interface */
+ DEVMETHOD(bus_add_child, device_add_child_ordered),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, ar40xx_readphy),
+ DEVMETHOD(miibus_writereg, ar40xx_writephy),
+ DEVMETHOD(miibus_statchg, ar40xx_statchg),
+
+ /* MDIO interface */
+ DEVMETHOD(mdio_readreg, ar40xx_readphy),
+ DEVMETHOD(mdio_writereg, ar40xx_writephy),
+
+ /* etherswitch interface */
+ DEVMETHOD(etherswitch_lock, ar40xx_lock),
+ DEVMETHOD(etherswitch_unlock, ar40xx_unlock),
+ DEVMETHOD(etherswitch_getinfo, ar40xx_getinfo),
+ DEVMETHOD(etherswitch_readreg, ar40xx_readreg),
+ DEVMETHOD(etherswitch_writereg, ar40xx_writereg),
+ DEVMETHOD(etherswitch_readphyreg, ar40xx_readphy),
+ DEVMETHOD(etherswitch_writephyreg, ar40xx_writephy),
+ DEVMETHOD(etherswitch_getport, ar40xx_getport),
+ DEVMETHOD(etherswitch_setport, ar40xx_setport),
+ DEVMETHOD(etherswitch_getvgroup, ar40xx_getvgroup),
+ DEVMETHOD(etherswitch_setvgroup, ar40xx_setvgroup),
+ DEVMETHOD(etherswitch_getconf, ar40xx_getconf),
+ DEVMETHOD(etherswitch_setconf, ar40xx_setconf),
+ DEVMETHOD(etherswitch_flush_all, ar40xx_atu_flush_all),
+ DEVMETHOD(etherswitch_flush_port, ar40xx_atu_flush_port),
+ DEVMETHOD(etherswitch_fetch_table, ar40xx_atu_fetch_table),
+ DEVMETHOD(etherswitch_fetch_table_entry,
+ ar40xx_atu_fetch_table_entry),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(ar40xx, ar40xx_driver, ar40xx_methods,
+ sizeof(struct ar40xx_softc));
+static devclass_t ar40xx_devclass;
+
+DRIVER_MODULE(ar40xx, simplebus, ar40xx_driver, ar40xx_devclass, 0, 0);
+DRIVER_MODULE(ar40xx, ofwbus, ar40xx_driver, ar40xx_devclass, 0, 0);
+DRIVER_MODULE(miibus, ar40xx, miibus_driver, miibus_devclass, 0, 0);
+DRIVER_MODULE(mdio, ar40xx, mdio_driver, mdio_devclass, 0, 0);
+DRIVER_MODULE(etherswitch, ar40xx, etherswitch_driver, etherswitch_devclass, 0, 0);
+MODULE_DEPEND(ar40xx, mdio, 1, 1, 1);
+MODULE_DEPEND(ar40xx, miibus, 1, 1, 1);
+MODULE_DEPEND(ar40xx, etherswitch, 1, 1, 1);
+MODULE_VERSION(ar40xx, 1);
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_phy.h b/sys/dev/etherswitch/ar40xx/ar40xx_phy.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_phy.h
@@ -0,0 +1,39 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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 __AR40XX_PHY_H__
+#define __AR40XX_PHY_H__
+
+extern int ar40xx_phy_tick(struct ar40xx_softc *sc);
+extern int ar40xx_attach_phys(struct ar40xx_softc *sc);
+extern int ar40xx_hw_phy_get_ids(struct ar40xx_softc *sc);
+extern struct mii_data * ar40xx_phy_miiforport(struct ar40xx_softc *sc,
+ int port);
+extern struct ifnet * ar40xx_phy_ifpforport(struct ar40xx_softc *sc,
+ int port);
+
+#endif /* __AR40XX_PHY_H__ */
+
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_phy.c b/sys/dev/etherswitch/ar40xx/ar40xx_phy.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_phy.c
@@ -0,0 +1,252 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/iicbus/iic.h>
+#include <dev/iicbus/iiconf.h>
+#include <dev/iicbus/iicbus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/ar40xx/ar40xx_var.h>
+#include <dev/etherswitch/ar40xx/ar40xx_reg.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_mdio.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_port.h>
+#include <dev/etherswitch/ar40xx/ar40xx_hw_atu.h>
+#include <dev/etherswitch/ar40xx/ar40xx_phy.h>
+#include <dev/etherswitch/ar40xx/ar40xx_debug.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+
+int
+ar40xx_phy_tick(struct ar40xx_softc *sc)
+{
+ struct mii_softc *miisc;
+ struct mii_data *mii;
+ int phy;
+ uint32_t reg;
+
+ AR40XX_LOCK_ASSERT(sc);
+
+ AR40XX_REG_BARRIER_READ(sc);
+ /*
+ * Loop over; update phy port status here
+ */
+ for (phy = 0; phy < AR40XX_NUM_PHYS; phy++) {
+ /*
+ * Port here is PHY, not port!
+ */
+ reg = AR40XX_REG_READ(sc, AR40XX_REG_PORT_STATUS(phy + 1));
+
+ mii = device_get_softc(sc->sc_phys.miibus[phy]);
+
+ /*
+ * Compare the current link status to the previous link
+ * status. We may need to clear ATU / change phy config.
+ */
+ if (((reg & AR40XX_PORT_STATUS_LINK_UP) != 0) &&
+ (mii->mii_media_status & IFM_ACTIVE) == 0) {
+ AR40XX_DPRINTF(sc, AR40XX_DBG_PORT_STATUS,
+ "%s: PHY %d: down -> up\n", __func__, phy);
+ ar40xx_hw_port_link_up(sc, phy + 1);
+ ar40xx_hw_atu_flush_port(sc, phy + 1);
+ }
+ if (((reg & AR40XX_PORT_STATUS_LINK_UP) == 0) &&
+ (mii->mii_media_status & IFM_ACTIVE) != 0) {
+ AR40XX_DPRINTF(sc, AR40XX_DBG_PORT_STATUS,
+ "%s: PHY %d: up -> down\n", __func__, phy);
+ ar40xx_hw_port_link_down(sc, phy + 1);
+ ar40xx_hw_atu_flush_port(sc, phy + 1);
+ }
+
+ mii_tick(mii);
+ LIST_FOREACH(miisc, &mii->mii_phys, mii_list) {
+ if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) !=
+ miisc->mii_inst)
+ continue;
+ ukphy_status(miisc);
+ mii_phy_update(miisc, MII_POLLSTAT);
+ }
+ }
+
+ return (0);
+}
+
+static inline int
+ar40xx_portforphy(int phy)
+{
+
+ return (phy+1);
+}
+
+struct mii_data *
+ar40xx_phy_miiforport(struct ar40xx_softc *sc, int port)
+{
+ int phy;
+
+ phy = port-1;
+
+ if (phy < 0 || phy >= AR40XX_NUM_PHYS)
+ return (NULL);
+ return (device_get_softc(sc->sc_phys.miibus[phy]));
+}
+
+struct ifnet *
+ar40xx_phy_ifpforport(struct ar40xx_softc *sc, int port)
+{
+ int phy;
+
+ phy = port-1;
+ if (phy < 0 || phy >= AR40XX_NUM_PHYS)
+ return (NULL);
+ return (sc->sc_phys.ifp[phy]);
+}
+
+static int
+ar40xx_ifmedia_upd(struct ifnet *ifp)
+{
+ struct ar40xx_softc *sc = ifp->if_softc;
+ struct mii_data *mii = ar40xx_phy_miiforport(sc, ifp->if_dunit);
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_PORT_STATUS, "%s: called, PHY %d\n",
+ __func__, ifp->if_dunit);
+
+ if (mii == NULL)
+ return (ENXIO);
+ mii_mediachg(mii);
+ return (0);
+}
+
+static void
+ar40xx_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr)
+{
+ struct ar40xx_softc *sc = ifp->if_softc;
+ struct mii_data *mii = ar40xx_phy_miiforport(sc, ifp->if_dunit);
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_PORT_STATUS, "%s: called, PHY %d\n",
+ __func__, ifp->if_dunit);
+
+ if (mii == NULL)
+ return;
+ mii_pollstat(mii);
+
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+}
+
+int
+ar40xx_attach_phys(struct ar40xx_softc *sc)
+{
+ int phy, err = 0;
+ char name[IFNAMSIZ];
+
+ /* PHYs need an interface, so we generate a dummy one */
+ snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->sc_dev));
+ for (phy = 0; phy < AR40XX_NUM_PHYS; phy++) {
+ sc->sc_phys.ifp[phy] = if_alloc(IFT_ETHER);
+ if (sc->sc_phys.ifp[phy] == NULL) {
+ device_printf(sc->sc_dev,
+ "PHY %d: couldn't allocate ifnet structure\n",
+ phy);
+ err = ENOMEM;
+ break;
+ }
+
+ sc->sc_phys.ifp[phy]->if_softc = sc;
+ sc->sc_phys.ifp[phy]->if_flags |= IFF_UP | IFF_BROADCAST |
+ IFF_DRV_RUNNING | IFF_SIMPLEX;
+ sc->sc_phys.ifname[phy] = malloc(strlen(name)+1, M_DEVBUF,
+ M_WAITOK);
+ bcopy(name, sc->sc_phys.ifname[phy], strlen(name)+1);
+ if_initname(sc->sc_phys.ifp[phy], sc->sc_phys.ifname[phy],
+ ar40xx_portforphy(phy));
+ err = mii_attach(sc->sc_dev, &sc->sc_phys.miibus[phy],
+ sc->sc_phys.ifp[phy], ar40xx_ifmedia_upd,
+ ar40xx_ifmedia_sts, BMSR_DEFCAPMASK,
+ phy, MII_OFFSET_ANY, 0);
+ device_printf(sc->sc_dev,
+ "%s attached to pseudo interface %s\n",
+ device_get_nameunit(sc->sc_phys.miibus[phy]),
+ sc->sc_phys.ifp[phy]->if_xname);
+ if (err != 0) {
+ device_printf(sc->sc_dev,
+ "attaching PHY %d failed\n",
+ phy);
+ return (err);
+ }
+ }
+ return (0);
+}
+
+int
+ar40xx_hw_phy_get_ids(struct ar40xx_softc *sc)
+{
+ int phy;
+ uint32_t id1, id2;
+
+ for (phy = 0; phy < AR40XX_NUM_PHYS; phy++) {
+ id1 = MDIO_READREG(sc->sc_mdio_dev, phy, 2);
+ id2 = MDIO_READREG(sc->sc_mdio_dev, phy, 3);
+ device_printf(sc->sc_dev,
+ "%s: PHY %d: ID1=0x%04x, ID2=0x%04x\n",
+ __func__, phy, id1, id2);
+ }
+
+ return (0);
+}
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_reg.h b/sys/dev/etherswitch/ar40xx/ar40xx_reg.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_reg.h
@@ -0,0 +1,348 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or 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 __AR40XX_REG_H__
+#define __AR40XX_REG_H__
+
+/*
+ * Register manipulation macros that expect bit field defines
+ * to follow the convention that an _S suffix is appended for
+ * a shift count, while the field mask has no suffix.
+ */
+#define SM(_v, _f) (((_v) << _f##_S) & (_f))
+#define MS(_v, _f) (((_v) & (_f)) >> _f##_S)
+
+#define BITS(_s, _n) (((1UL << (_n)) - 1) << _s)
+#define BIT(_n) (1UL << (_n))
+
+#define AR40XX_PORT_LINK_UP 1
+#define AR40XX_PORT_LINK_DOWN 0
+#define AR40XX_QM_NOT_EMPTY 1
+#define AR40XX_QM_EMPTY 0
+
+#define AR40XX_LAN_VLAN 1
+#define AR40XX_WAN_VLAN 2
+
+enum ar40xx_port_wrapper_cfg {
+ PORT_WRAPPER_PSGMII = 0,
+};
+
+struct ar40xx_mib_desc {
+ uint32_t size;
+ uint32_t offset;
+ const char *name;
+};
+
+#define AR40XX_PORT_CPU 0
+
+#define AR40XX_PSGMII_MODE_CONTROL 0x1b4
+#define AR40XX_PSGMII_ATHR_CSCO_MODE_25M BIT(0)
+
+#define AR40XX_PSGMIIPHY_TX_CONTROL 0x288
+
+#define AR40XX_MII_ATH_MMD_ADDR 0x0d
+#define AR40XX_MII_ATH_MMD_DATA 0x0e
+#define AR40XX_MII_ATH_DBG_ADDR 0x1d
+#define AR40XX_MII_ATH_DBG_DATA 0x1e
+
+#define AR40XX_STATS_RXBROAD 0x00
+#define AR40XX_STATS_RXPAUSE 0x04
+#define AR40XX_STATS_RXMULTI 0x08
+#define AR40XX_STATS_RXFCSERR 0x0c
+#define AR40XX_STATS_RXALIGNERR 0x10
+#define AR40XX_STATS_RXRUNT 0x14
+#define AR40XX_STATS_RXFRAGMENT 0x18
+#define AR40XX_STATS_RX64BYTE 0x1c
+#define AR40XX_STATS_RX128BYTE 0x20
+#define AR40XX_STATS_RX256BYTE 0x24
+#define AR40XX_STATS_RX512BYTE 0x28
+#define AR40XX_STATS_RX1024BYTE 0x2c
+#define AR40XX_STATS_RX1518BYTE 0x30
+#define AR40XX_STATS_RXMAXBYTE 0x34
+#define AR40XX_STATS_RXTOOLONG 0x38
+#define AR40XX_STATS_RXGOODBYTE 0x3c
+#define AR40XX_STATS_RXBADBYTE 0x44
+#define AR40XX_STATS_RXOVERFLOW 0x4c
+#define AR40XX_STATS_FILTERED 0x50
+#define AR40XX_STATS_TXBROAD 0x54
+#define AR40XX_STATS_TXPAUSE 0x58
+#define AR40XX_STATS_TXMULTI 0x5c
+#define AR40XX_STATS_TXUNDERRUN 0x60
+#define AR40XX_STATS_TX64BYTE 0x64
+#define AR40XX_STATS_TX128BYTE 0x68
+#define AR40XX_STATS_TX256BYTE 0x6c
+#define AR40XX_STATS_TX512BYTE 0x70
+#define AR40XX_STATS_TX1024BYTE 0x74
+#define AR40XX_STATS_TX1518BYTE 0x78
+#define AR40XX_STATS_TXMAXBYTE 0x7c
+#define AR40XX_STATS_TXOVERSIZE 0x80
+#define AR40XX_STATS_TXBYTE 0x84
+#define AR40XX_STATS_TXCOLLISION 0x8c
+#define AR40XX_STATS_TXABORTCOL 0x90
+#define AR40XX_STATS_TXMULTICOL 0x94
+#define AR40XX_STATS_TXSINGLECOL 0x98
+#define AR40XX_STATS_TXEXCDEFER 0x9c
+#define AR40XX_STATS_TXDEFER 0xa0
+#define AR40XX_STATS_TXLATECOL 0xa4
+
+#define AR40XX_REG_MODULE_EN 0x030
+#define AR40XX_MODULE_EN_MIB BIT(0)
+
+#define AR40XX_REG_MIB_FUNC 0x034
+#define AR40XX_MIB_BUSY BIT(17)
+#define AR40XX_MIB_CPU_KEEP BIT(20)
+#define AR40XX_MIB_FUNC BITS(24, 3)
+#define AR40XX_MIB_FUNC_S 24
+#define AR40XX_MIB_FUNC_NO_OP 0x0
+#define AR40XX_MIB_FUNC_FLUSH 0x1
+
+#define AR40XX_ESS_SERVICE_TAG 0x48
+#define AR40XX_ESS_SERVICE_TAG_STAG BIT(17)
+
+#define AR40XX_REG_SW_MAC_ADDR0 0x60
+#define AR40XX_REG_SW_MAC_ADDR0_BYTE4 BITS(8, 8)
+#define AR40XX_REG_SW_MAC_ADDR0_BYTE4_S 8
+#define AR40XX_REG_SW_MAC_ADDR0_BYTE5 BITS(0, 8)
+#define AR40XX_REG_SW_MAC_ADDR0_BYTE5_S 0
+
+#define AR40XX_REG_SW_MAC_ADDR1 0x64
+#define AR40XX_REG_SW_MAC_ADDR1_BYTE0 BITS(24, 8)
+#define AR40XX_REG_SW_MAC_ADDR1_BYTE0_S 24
+#define AR40XX_REG_SW_MAC_ADDR1_BYTE1 BITS(16, 8)
+#define AR40XX_REG_SW_MAC_ADDR1_BYTE1_S 16
+#define AR40XX_REG_SW_MAC_ADDR1_BYTE2 BITS(8, 8)
+#define AR40XX_REG_SW_MAC_ADDR1_BYTE2_S 8
+#define AR40XX_REG_SW_MAC_ADDR1_BYTE3 BITS(0, 8)
+#define AR40XX_REG_SW_MAC_ADDR1_BYTE3_S 0
+
+#define AR40XX_REG_MAX_FRAME_SIZE 0x078
+#define AR40XX_MAX_FRAME_SIZE_MTU BITS(0, 14)
+
+#define AR40XX_REG_PORT_STATUS(_i) (0x07c + (_i) * 4)
+#define AR40XX_PORT_SPEED BITS(0, 2)
+#define AR40XX_PORT_STATUS_SPEED_S 0
+#define AR40XX_PORT_TX_EN BIT(2)
+#define AR40XX_PORT_RX_EN BIT(3)
+#define AR40XX_PORT_STATUS_TXFLOW BIT(4)
+#define AR40XX_PORT_STATUS_RXFLOW BIT(5)
+#define AR40XX_PORT_DUPLEX BIT(6)
+#define AR40XX_PORT_TXHALF_FLOW BIT(7)
+#define AR40XX_PORT_STATUS_LINK_UP BIT(8)
+#define AR40XX_PORT_AUTO_LINK_EN BIT(9)
+#define AR40XX_PORT_STATUS_FLOW_CONTROL BIT(12)
+
+#define AR40XX_REG_PORT_HEADER(_i) (0x09c + (_i) * 4)
+
+#define AR40XX_REG_EEE_CTRL 0x100
+#define AR40XX_EEE_CTRL_DISABLE_PHY(_i) BIT(4 + (_i) * 2)
+
+#define AR40XX_REG_PORT_VLAN0(_i) (0x420 + (_i) * 0x8)
+#define AR40XX_PORT_VLAN0_DEF_SVID BITS(0, 12)
+#define AR40XX_PORT_VLAN0_DEF_SVID_S 0
+#define AR40XX_PORT_VLAN0_DEF_CVID BITS(16, 12)
+#define AR40XX_PORT_VLAN0_DEF_CVID_S 16
+
+#define AR40XX_REG_PORT_VLAN1(_i) (0x424 + (_i) * 0x8)
+#define AR40XX_PORT_VLAN1_CORE_PORT BIT(9)
+#define AR40XX_PORT_VLAN1_PORT_TLS_MODE BIT(7)
+#define AR40XX_PORT_VLAN1_PORT_VLAN_PROP BIT(6)
+#define AR40XX_PORT_VLAN1_OUT_MODE BITS(12, 2)
+#define AR40XX_PORT_VLAN1_OUT_MODE_S 12
+#define AR40XX_PORT_VLAN1_OUT_MODE_UNMOD 0
+#define AR40XX_PORT_VLAN1_OUT_MODE_UNTAG 1
+#define AR40XX_PORT_VLAN1_OUT_MODE_TAG 2
+#define AR40XX_PORT_VLAN1_OUT_MODE_UNTOUCH 3
+
+#define AR40XX_REG_ATU_DATA0 0x600
+#define AR40XX_ATU_DATA0_MAC_ADDR3 BITS(0, 8)
+#define AR40XX_ATU_DATA0_MAC_ADDR3_S 0
+#define AR40XX_ATU_DATA0_MAC_ADDR2 BITS(8, 8)
+#define AR40XX_ATU_DATA0_MAC_ADDR2_S 8
+#define AR40XX_ATU_DATA0_MAC_ADDR1 BITS(16, 8)
+#define AR40XX_ATU_DATA0_MAC_ADDR1_S 16
+#define AR40XX_ATU_DATA0_MAC_ADDR0 BITS(24, 8)
+#define AR40XX_ATU_DATA0_MAC_ADDR0_S 24
+
+#define AR40XX_REG_ATU_DATA1 0x604
+#define AR40XX_ATU_DATA1_MAC_ADDR4 BITS(0, 8)
+#define AR40XX_ATU_DATA1_MAC_ADDR4_S 0
+#define AR40XX_ATU_DATA1_MAC_ADDR5 BITS(8, 8)
+#define AR40XX_ATU_DATA1_MAC_ADDR5_S 8
+#define AR40XX_ATU_DATA1_DEST_PORT BITS(16, 7)
+#define AR40XX_ATU_DATA1_DEST_PORT_S 16
+#define AR40XX_ATU_DATA1_CROSS_PORT_STATE_EN BIT(23)
+#define AR40XX_ATU_DATA1_PRI BITS(24, 3)
+#define AR40XX_ATU_DATA1_SVL_ENTRY BIT(27)
+#define AR40XX_ATU_DATA1_PRI_OVER_EN BIT(28)
+#define AR40XX_ATU_DATA1_MIRROR_EN BIT(29)
+#define AR40XX_ATU_DATA1_SA_DROP_EN BIT(30)
+#define AR40XX_ATU_DATA1_HASH_HIGH_ADDR BIT(31)
+
+#define AR40XX_REG_ATU_DATA2 0x608
+#define AR40XX_ATU_FUNC_DATA2_STATUS BITS(0, 4)
+#define AR40XX_ATU_FUNC_DATA2_STATUS_S 0
+#define AR40XX_ATU_FUNC_DATA2_VLAN_LEAKY_EN BIT(4)
+#define AR40XX_ATU_FUNC_DATA2_REDIRECT_TO_CPU BIT(5)
+#define AR40XX_ATU_FUNC_DATA2_COPY_TO_CPU BIT(6)
+#define AR40XX_ATU_FUNC_DATA2_SHORT_LOOP BIT(7)
+#define AR40XX_ATU_FUNC_DATA2_ATU_VID BITS(8, 12)
+#define AR40XX_ATU_FUNC_DATA2_ATU_VID_S 8
+
+#define AR40XX_REG_ATU_FUNC 0x60c
+#define AR40XX_ATU_FUNC_OP BITS(0, 4)
+#define AR40XX_ATU_FUNC_OP_NOOP 0x0
+#define AR40XX_ATU_FUNC_OP_FLUSH 0x1
+#define AR40XX_ATU_FUNC_OP_LOAD 0x2
+#define AR40XX_ATU_FUNC_OP_PURGE 0x3
+#define AR40XX_ATU_FUNC_OP_FLUSH_LOCKED 0x4
+#define AR40XX_ATU_FUNC_OP_FLUSH_UNICAST 0x5
+#define AR40XX_ATU_FUNC_OP_GET_NEXT 0x6
+#define AR40XX_ATU_FUNC_OP_SEARCH_MAC 0x7
+#define AR40XX_ATU_FUNC_OP_CHANGE_TRUNK 0x8
+#define AR40XX_ATU_FUNC_PORT_NUM BITS(8, 4)
+#define AR40XX_ATU_FUNC_PORT_NUM_S 8
+#define AR40XX_ATU_FUNC_BUSY BIT(31)
+
+
+
+#define AR40XX_REG_VTU_FUNC0 0x0610
+#define AR40XX_VTU_FUNC0_EG_MODE BITS(4, 14)
+#define AR40XX_VTU_FUNC0_EG_MODE_S(_i) (4 + (_i) * 2)
+#define AR40XX_VTU_FUNC0_EG_MODE_KEEP 0
+#define AR40XX_VTU_FUNC0_EG_MODE_UNTAG 1
+#define AR40XX_VTU_FUNC0_EG_MODE_TAG 2
+#define AR40XX_VTU_FUNC0_EG_MODE_NOT 3
+#define AR40XX_VTU_FUNC0_IVL BIT(19)
+#define AR40XX_VTU_FUNC0_VALID BIT(20)
+
+#define AR40XX_REG_VTU_FUNC1 0x0614
+#define AR40XX_VTU_FUNC1_OP BITS(0, 3)
+#define AR40XX_VTU_FUNC1_OP_NOOP 0
+#define AR40XX_VTU_FUNC1_OP_FLUSH 1
+#define AR40XX_VTU_FUNC1_OP_LOAD 2
+#define AR40XX_VTU_FUNC1_OP_PURGE 3
+#define AR40XX_VTU_FUNC1_OP_REMOVE_PORT 4
+#define AR40XX_VTU_FUNC1_OP_GET_NEXT 5
+#define AR40XX_VTU_FUNC1_OP_GET_ONE 6
+#define AR40XX_VTU_FUNC1_FULL BIT(4)
+#define AR40XX_VTU_FUNC1_PORT BIT(8, 4)
+#define AR40XX_VTU_FUNC1_PORT_S 8
+#define AR40XX_VTU_FUNC1_VID BIT(16, 12)
+#define AR40XX_VTU_FUNC1_VID_S 16
+#define AR40XX_VTU_FUNC1_BUSY BIT(31)
+
+#define AR40XX_REG_FWD_CTRL0 0x620
+#define AR40XX_FWD_CTRL0_CPU_PORT_EN BIT(10)
+#define AR40XX_FWD_CTRL0_MIRROR_PORT BITS(4, 4)
+#define AR40XX_FWD_CTRL0_MIRROR_PORT_S 4
+
+#define AR40XX_REG_FWD_CTRL1 0x624
+#define AR40XX_FWD_CTRL1_UC_FLOOD BITS(0, 7)
+#define AR40XX_FWD_CTRL1_UC_FLOOD_S 0
+#define AR40XX_FWD_CTRL1_MC_FLOOD BITS(8, 7)
+#define AR40XX_FWD_CTRL1_MC_FLOOD_S 8
+#define AR40XX_FWD_CTRL1_BC_FLOOD BITS(16, 7)
+#define AR40XX_FWD_CTRL1_BC_FLOOD_S 16
+#define AR40XX_FWD_CTRL1_IGMP BITS(24, 7)
+#define AR40XX_FWD_CTRL1_IGMP_S 24
+
+#define AR40XX_REG_PORT_LOOKUP(_i) (0x660 + (_i) * 0xc)
+#define AR40XX_PORT_LOOKUP_MEMBER BITS(0, 7)
+#define AR40XX_PORT_LOOKUP_IN_MODE BITS(8, 2)
+#define AR40XX_PORT_LOOKUP_IN_MODE_S 8
+#define AR40XX_PORT_LOOKUP_STATE BITS(16, 3)
+#define AR40XX_PORT_LOOKUP_STATE_S 16
+#define AR40XX_PORT_LOOKUP_LEARN BIT(20)
+#define AR40XX_PORT_LOOKUP_LOOPBACK BIT(21)
+#define AR40XX_PORT_LOOKUP_ING_MIRROR_EN BIT(25)
+
+#define AR40XX_REG_QM_DEBUG_ADDR 0x820
+#define AR40XX_REG_QM_DEBUG_VALUE 0x824
+#define AR40XX_REG_QM_PORT0_3_QNUM 0x1d
+#define AR40XX_REG_QM_PORT4_6_QNUM 0x1e
+
+#define AR40XX_REG_PORT_HOL_CTRL1(_i) (0x974 + (_i) * 0x8)
+#define AR40XX_PORT_HOL_CTRL1_EG_MIRROR_EN BIT(16)
+
+#define AR40XX_REG_PORT_FLOWCTRL_THRESH(_i) (0x9b0 + (_i) * 0x4)
+#define AR40XX_PORT0_FC_THRESH_ON_DFLT 0x60
+#define AR40XX_PORT0_FC_THRESH_OFF_DFLT 0x90
+
+#define AR40XX_PHY_DEBUG_0 0
+#define AR40XX_PHY_MANU_CTRL_EN BIT(12)
+
+#define AR40XX_PHY_DEBUG_2 2
+
+#define AR40XX_PHY_SPEC_STATUS 0x11
+#define AR40XX_PHY_SPEC_STATUS_LINK BIT(10)
+#define AR40XX_PHY_SPEC_STATUS_DUPLEX BIT(13)
+#define AR40XX_PHY_SPEC_STATUS_SPEED BITS(14, 2)
+
+/* port forwarding state */
+enum {
+ AR40XX_PORT_STATE_DISABLED = 0,
+ AR40XX_PORT_STATE_BLOCK = 1,
+ AR40XX_PORT_STATE_LISTEN = 2,
+ AR40XX_PORT_STATE_LEARN = 3,
+ AR40XX_PORT_STATE_FORWARD = 4
+};
+
+/* ingress 802.1q mode */
+enum {
+ AR40XX_IN_PORT_ONLY = 0,
+ AR40XX_IN_PORT_FALLBACK = 1,
+ AR40XX_IN_VLAN_ONLY = 2,
+ AR40XX_IN_SECURE = 3
+};
+
+/* egress 802.1q mode */
+enum {
+ AR40XX_OUT_KEEP = 0,
+ AR40XX_OUT_STRIP_VLAN = 1,
+ AR40XX_OUT_ADD_VLAN = 2
+};
+
+/* port speed */
+enum {
+ AR40XX_PORT_SPEED_10M = 0,
+ AR40XX_PORT_SPEED_100M = 1,
+ AR40XX_PORT_SPEED_1000M = 2,
+ AR40XX_PORT_SPEED_ERR = 3,
+};
+
+#define AR40XX_MIB_WORK_DELAY 2000 /* msecs */
+
+#define AR40XX_QM_WORK_DELAY 100
+
+#define AR40XX_MIB_FUNC_CAPTURE 0x3
+
+#define AR40XX_REG_PORT_STATS_START 0x1000
+#define AR40XX_REG_PORT_STATS_LEN 0x100
+
+#define AR40XX_PORTS_ALL 0x3f
+
+#define AR40XX_PSGMII_ID 5
+#define AR40XX_PSGMII_CALB_NUM 100
+#define AR40XX_MALIBU_PSGMII_MODE_CTRL 0x6d
+#define AR40XX_MALIBU_PHY_PSGMII_MODE_CTRL_ADJUST_VAL 0x220c
+#define AR40XX_MALIBU_PHY_MMD7_DAC_CTRL 0x801a
+#define AR40XX_MALIBU_DAC_CTRL_MASK 0x380
+#define AR40XX_MALIBU_DAC_CTRL_VALUE 0x280
+#define AR40XX_MALIBU_PHY_RLP_CTRL 0x805a
+#define AR40XX_PSGMII_TX_DRIVER_1_CTRL 0xb
+#define AR40XX_MALIBU_PHY_PSGMII_REDUCE_SERDES_TX_AMP 0x8a
+#define AR40XX_MALIBU_PHY_LAST_ADDR 4
+
+#endif /* __AR40XX_REG_H__ */
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_var.h b/sys/dev/etherswitch/ar40xx/ar40xx_var.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_var.h
@@ -0,0 +1,135 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * 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 AUTHOR 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 AUTHOR 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 __AR40XX_VAR_H__
+#define __AR40XX_VAR_H__
+
+#define AR40XX_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define AR40XX_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define AR40XX_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED)
+
+/*
+ * register space access macros
+ */
+#define AR40XX_REG_WRITE(sc, reg, val) do { \
+ bus_write_4(sc->sc_ess_mem_res, (reg), (val)); \
+ } while (0)
+
+#define AR40XX_REG_READ(sc, reg) bus_read_4(sc->sc_ess_mem_res, (reg))
+
+#define AR40XX_REG_BARRIER_WRITE(sc) bus_barrier((sc)->sc_ess_mem_res, \
+ 0, (sc)->sc_ess_mem_size, BUS_SPACE_BARRIER_WRITE)
+#define AR40XX_REG_BARRIER_READ(sc) bus_barrier((sc)->sc_ess_mem_res, \
+ 0, (sc)->sc_ess_mem_size, BUS_SPACE_BARRIER_READ)
+#define AR40XX_REG_BARRIER_RW(sc) bus_barrier((sc)->sc_ess_mem_res, \
+ 0, (sc)->sc_ess_mem_size, \
+ BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE)
+
+/* Size of the VLAN table itself in hardware */
+#define AR40XX_NUM_VTU_ENTRIES 64
+#define AR40XX_NUM_PORTS 6
+#define AR40XX_NUM_PHYS 5
+/* Size of the ATU table in hardware */
+#define AR40XX_NUM_ATU_ENTRIES 2048
+
+struct ar40xx_softc {
+ struct mtx sc_mtx; /* serialize access to softc */
+ device_t sc_dev;
+ uint32_t sc_debug;
+
+ /* ess-switch memory resource */
+ struct resource *sc_ess_mem_res;
+ int sc_ess_mem_rid;
+ size_t sc_ess_mem_size;
+
+ /* ess-switch clock resource */
+ clk_t sc_ess_clk;
+
+ /* ess-switch reset resource */
+ hwreset_t sc_ess_rst;
+
+ /* phy update callout timer */
+ struct callout sc_phy_callout;
+
+ /* memory for the ess-psgmii config interface */
+ bus_space_tag_t sc_psgmii_mem_tag;
+ bus_space_handle_t sc_psgmii_mem_handle;
+ bus_size_t sc_psgmii_mem_size;
+
+ /* reference to the ipq4019-mdio interface */
+ phandle_t sc_mdio_phandle;
+ device_t sc_mdio_dev;
+
+ etherswitch_info_t sc_info;
+
+ struct {
+ uint32_t phy_t_status;
+ } sc_psgmii;
+
+ struct {
+ uint32_t switch_mac_mode;
+ uint32_t switch_cpu_bmp;
+ uint32_t switch_lan_bmp;
+ uint32_t switch_wan_bmp;
+ } sc_config;
+
+ /* VLAN table configuration */
+ struct {
+ /* Whether 802.1q VLANs are enabled or not */
+ bool vlan;
+ /* Map etherswitch vgroup to 802.1q vlan */
+ uint16_t vlan_id[AR40XX_NUM_VTU_ENTRIES];
+ /* VLAN port membership */
+ uint8_t vlan_ports[AR40XX_NUM_VTU_ENTRIES];
+ /* VLAN port membership - untagged ports */
+ uint16_t vlan_untagged[AR40XX_NUM_VTU_ENTRIES];
+ /* PVID for each port - index into vlan_id[] */
+ uint16_t pvid[AR40XX_NUM_PORTS];
+ } sc_vlan;
+
+ struct {
+ bool mirror_rx;
+ bool mirror_tx;
+ int source_port;
+ int monitor_port;
+ } sc_monitor;
+
+ struct {
+ char *ifname[AR40XX_NUM_PHYS];
+ device_t miibus[AR40XX_NUM_PHYS];
+ struct ifnet *ifp[AR40XX_NUM_PHYS];
+ } sc_phys;
+
+ /* ATU (address table unit) support */
+ struct {
+ int count;
+ int size;
+ etherswitch_atu_entry_t entries[AR40XX_NUM_ATU_ENTRIES];
+ } atu;
+};
+
+#endif /* __AR40XX_VAR_H__ */
+
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Feb 22, 7:52 PM (9 h, 40 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
16778721
Default Alt Text
D34112.diff (128 KB)
Attached To
Mode
D34112: ar40xx_switch: add initial switch for the IPQ4018/IPQ4019.
Attached
Detach File
Event Timeline
Log In to Comment