aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/etherswitch
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/etherswitch')
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_debug.h44
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_hw.c357
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_hw.h42
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_hw_atu.c216
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_hw_atu.h37
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_hw_mdio.c129
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_hw_mdio.h40
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_hw_mib.c194
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_hw_mib.h36
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_hw_mirror.c132
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_hw_mirror.h33
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_hw_port.c287
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_hw_port.h42
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_hw_psgmii.c437
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_hw_psgmii.h41
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_hw_vtu.c196
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_hw_vtu.h39
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_main.c968
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_phy.c244
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_phy.h39
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_reg.h348
-rw-r--r--sys/dev/etherswitch/ar40xx/ar40xx_var.h135
-rw-r--r--sys/dev/etherswitch/arswitch/arswitch.c1313
-rw-r--r--sys/dev/etherswitch/arswitch/arswitch_8216.c94
-rw-r--r--sys/dev/etherswitch/arswitch/arswitch_8216.h34
-rw-r--r--sys/dev/etherswitch/arswitch/arswitch_8226.c94
-rw-r--r--sys/dev/etherswitch/arswitch/arswitch_8226.h34
-rw-r--r--sys/dev/etherswitch/arswitch/arswitch_8316.c171
-rw-r--r--sys/dev/etherswitch/arswitch/arswitch_8316.h34
-rw-r--r--sys/dev/etherswitch/arswitch/arswitch_8327.c1310
-rw-r--r--sys/dev/etherswitch/arswitch/arswitch_8327.h96
-rw-r--r--sys/dev/etherswitch/arswitch/arswitch_phy.c216
-rw-r--r--sys/dev/etherswitch/arswitch/arswitch_phy.h37
-rw-r--r--sys/dev/etherswitch/arswitch/arswitch_reg.c264
-rw-r--r--sys/dev/etherswitch/arswitch/arswitch_reg.h47
-rw-r--r--sys/dev/etherswitch/arswitch/arswitch_vlans.c384
-rw-r--r--sys/dev/etherswitch/arswitch/arswitch_vlans.h47
-rw-r--r--sys/dev/etherswitch/arswitch/arswitchreg.h582
-rw-r--r--sys/dev/etherswitch/arswitch/arswitchvar.h185
-rw-r--r--sys/dev/etherswitch/e6000sw/e6000sw.c1834
-rw-r--r--sys/dev/etherswitch/e6000sw/e6000swreg.h304
-rw-r--r--sys/dev/etherswitch/e6000sw/e6060sw.c1021
-rw-r--r--sys/dev/etherswitch/etherswitch.c226
-rw-r--r--sys/dev/etherswitch/etherswitch.h147
-rw-r--r--sys/dev/etherswitch/etherswitch_if.m220
-rw-r--r--sys/dev/etherswitch/felix/felix.c1005
-rw-r--r--sys/dev/etherswitch/felix/felix_reg.h102
-rw-r--r--sys/dev/etherswitch/felix/felix_var.h109
-rw-r--r--sys/dev/etherswitch/infineon/adm6996fc.c839
-rw-r--r--sys/dev/etherswitch/ip17x/ip175c.c256
-rw-r--r--sys/dev/etherswitch/ip17x/ip175c.h46
-rw-r--r--sys/dev/etherswitch/ip17x/ip175d.c221
-rw-r--r--sys/dev/etherswitch/ip17x/ip175d.h43
-rw-r--r--sys/dev/etherswitch/ip17x/ip17x.c652
-rw-r--r--sys/dev/etherswitch/ip17x/ip17x_phy.c104
-rw-r--r--sys/dev/etherswitch/ip17x/ip17x_phy.h38
-rw-r--r--sys/dev/etherswitch/ip17x/ip17x_reg.h44
-rw-r--r--sys/dev/etherswitch/ip17x/ip17x_var.h95
-rw-r--r--sys/dev/etherswitch/ip17x/ip17x_vlans.c187
-rw-r--r--sys/dev/etherswitch/ip17x/ip17x_vlans.h36
-rw-r--r--sys/dev/etherswitch/micrel/ksz8995ma.c941
-rw-r--r--sys/dev/etherswitch/miiproxy.c434
-rw-r--r--sys/dev/etherswitch/miiproxy.h36
-rw-r--r--sys/dev/etherswitch/mtkswitch/mtkswitch.c663
-rw-r--r--sys/dev/etherswitch/mtkswitch/mtkswitch_mt7620.c562
-rw-r--r--sys/dev/etherswitch/mtkswitch/mtkswitch_mt7620.h128
-rw-r--r--sys/dev/etherswitch/mtkswitch/mtkswitch_rt3050.c523
-rw-r--r--sys/dev/etherswitch/mtkswitch/mtkswitch_rt3050.h86
-rw-r--r--sys/dev/etherswitch/mtkswitch/mtkswitchvar.h162
-rw-r--r--sys/dev/etherswitch/rtl8366/rtl8366rb.c959
-rw-r--r--sys/dev/etherswitch/rtl8366/rtl8366rbvar.h183
-rw-r--r--sys/dev/etherswitch/ukswitch/ukswitch.c574
72 files changed, 21758 insertions, 0 deletions
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_debug.h b/sys/dev/etherswitch/ar40xx/ar40xx_debug.h
new file mode 100644
index 000000000000..78661a749c3d
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_debug.h
@@ -0,0 +1,44 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.c b/sys/dev/etherswitch/ar40xx/ar40xx_hw.c
new file mode 100644
index 000000000000..cdc13366eef3
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw.c
@@ -0,0 +1,357 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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/clk/clk.h>
+#include <dev/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.h b/sys/dev/etherswitch/ar40xx/ar40xx_hw.h
new file mode 100644
index 000000000000..cd38ac1c31aa
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw.h
@@ -0,0 +1,42 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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_atu.c b/sys/dev/etherswitch/ar40xx/ar40xx_hw_atu.c
new file mode 100644
index 000000000000..4ddcc58a2cfc
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_atu.c
@@ -0,0 +1,216 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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/clk/clk.h>
+#include <dev/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_atu.h b/sys/dev/etherswitch/ar40xx/ar40xx_hw_atu.h
new file mode 100644
index 000000000000..edb01133431c
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_atu.h
@@ -0,0 +1,37 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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_mdio.c b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mdio.c
new file mode 100644
index 000000000000..793507b9aaa2
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mdio.c
@@ -0,0 +1,129 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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/clk/clk.h>
+#include <dev/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_mdio.h b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mdio.h
new file mode 100644
index 000000000000..c4f82a2836cc
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mdio.h
@@ -0,0 +1,40 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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_mib.c b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mib.c
new file mode 100644
index 000000000000..73110753c915
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mib.c
@@ -0,0 +1,194 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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/clk/clk.h>
+#include <dev/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_mib.h b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mib.h
new file mode 100644
index 000000000000..17797d28fa7b
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mib.h
@@ -0,0 +1,36 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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_mirror.c b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mirror.c
new file mode 100644
index 000000000000..fff97147d878
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mirror.c
@@ -0,0 +1,132 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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/clk/clk.h>
+#include <dev/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_mirror.h b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mirror.h
new file mode 100644
index 000000000000..46877fe7e1b9
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_mirror.h
@@ -0,0 +1,33 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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_port.c b/sys/dev/etherswitch/ar40xx/ar40xx_hw_port.c
new file mode 100644
index 000000000000..a540f9b7498e
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_port.c
@@ -0,0 +1,287 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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/clk/clk.h>
+#include <dev/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_port.h b/sys/dev/etherswitch/ar40xx/ar40xx_hw_port.h
new file mode 100644
index 000000000000..428cd34b954a
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_port.h
@@ -0,0 +1,42 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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_psgmii.c b/sys/dev/etherswitch/ar40xx/ar40xx_hw_psgmii.c
new file mode 100644
index 000000000000..67a2bcbc7a6c
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_psgmii.c
@@ -0,0 +1,437 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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/clk/clk.h>
+#include <dev/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_psgmii.h b/sys/dev/etherswitch/ar40xx/ar40xx_hw_psgmii.h
new file mode 100644
index 000000000000..38f1a0e61b03
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_psgmii.h
@@ -0,0 +1,41 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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_vtu.c b/sys/dev/etherswitch/ar40xx/ar40xx_hw_vtu.c
new file mode 100644
index 000000000000..15f5f61f8b2d
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_vtu.c
@@ -0,0 +1,196 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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/clk/clk.h>
+#include <dev/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_hw_vtu.h b/sys/dev/etherswitch/ar40xx/ar40xx_hw_vtu.h
new file mode 100644
index 000000000000..35e1de98c6f9
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_hw_vtu.h
@@ -0,0 +1,39 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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_main.c b/sys/dev/etherswitch/ar40xx/ar40xx_main.c
new file mode 100644
index 000000000000..d5636d26120b
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_main.c
@@ -0,0 +1,968 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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/clk/clk.h>
+#include <dev/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 error, i;
+
+ device_printf(sc->sc_dev, "%s: called\n", __func__);
+
+ error = bus_generic_detach(dev);
+ if (error != 0)
+ return (error);
+
+ callout_drain(&sc->sc_phy_callout);
+
+ /* Free PHYs */
+ for (i = 0; i < AR40XX_NUM_PHYS; i++) {
+ if (sc->sc_phys.ifp[i] != NULL)
+ if_free(sc->sc_phys.ifp[i]);
+ free(sc->sc_phys.ifname[i], M_DEVBUF);
+ }
+
+ 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);
+
+ bus_identify_children(dev);
+ bus_enumerate_hinted_children(dev);
+ bus_attach_children(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;
+ if_t 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));
+
+DRIVER_MODULE(ar40xx, simplebus, ar40xx_driver, 0, 0);
+DRIVER_MODULE(ar40xx, ofwbus, ar40xx_driver, 0, 0);
+DRIVER_MODULE(miibus, ar40xx, miibus_driver, 0, 0);
+DRIVER_MODULE(mdio, ar40xx, mdio_driver, 0, 0);
+DRIVER_MODULE(etherswitch, ar40xx, etherswitch_driver, 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.c b/sys/dev/etherswitch/ar40xx/ar40xx_phy.c
new file mode 100644
index 000000000000..aa02ef25ac7b
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_phy.c
@@ -0,0 +1,244 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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/clk/clk.h>
+#include <dev/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]));
+}
+
+if_t
+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(if_t ifp)
+{
+ struct ar40xx_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = ar40xx_phy_miiforport(sc, if_getdunit(ifp));
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_PORT_STATUS, "%s: called, PHY %d\n",
+ __func__, if_getdunit(ifp));
+
+ if (mii == NULL)
+ return (ENXIO);
+ mii_mediachg(mii);
+ return (0);
+}
+
+static void
+ar40xx_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct ar40xx_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = ar40xx_phy_miiforport(sc, if_getdunit(ifp));
+
+ AR40XX_DPRINTF(sc, AR40XX_DBG_PORT_STATUS, "%s: called, PHY %d\n",
+ __func__, if_getdunit(ifp));
+
+ 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_setsoftc(sc->sc_phys.ifp[phy], sc);
+ if_setflagbits(sc->sc_phys.ifp[phy], IFF_UP | IFF_BROADCAST |
+ IFF_DRV_RUNNING | IFF_SIMPLEX, 0);
+ 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]),
+ if_name(sc->sc_phys.ifp[phy]));
+ 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_phy.h b/sys/dev/etherswitch/ar40xx/ar40xx_phy.h
new file mode 100644
index 000000000000..cc0a827afa4f
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_phy.h
@@ -0,0 +1,39 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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 if_t ar40xx_phy_ifpforport(struct ar40xx_softc *sc,
+ int port);
+
+#endif /* __AR40XX_PHY_H__ */
+
diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_reg.h b/sys/dev/etherswitch/ar40xx/ar40xx_reg.h
new file mode 100644
index 000000000000..913040d3aba5
--- /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
index 000000000000..10ecdc40689e
--- /dev/null
+++ b/sys/dev/etherswitch/ar40xx/ar40xx_var.h
@@ -0,0 +1,135 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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];
+ if_t 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__ */
+
diff --git a/sys/dev/etherswitch/arswitch/arswitch.c b/sys/dev/etherswitch/arswitch/arswitch.c
new file mode 100644
index 000000000000..c53c82f1750c
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitch.c
@@ -0,0 +1,1313 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/arswitch/arswitchreg.h>
+#include <dev/etherswitch/arswitch/arswitchvar.h>
+#include <dev/etherswitch/arswitch/arswitch_reg.h>
+#include <dev/etherswitch/arswitch/arswitch_phy.h>
+#include <dev/etherswitch/arswitch/arswitch_vlans.h>
+
+#include <dev/etherswitch/arswitch/arswitch_8216.h>
+#include <dev/etherswitch/arswitch/arswitch_8226.h>
+#include <dev/etherswitch/arswitch/arswitch_8316.h>
+#include <dev/etherswitch/arswitch/arswitch_8327.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+/* Map ETHERSWITCH_PORT_LED_* to Atheros pattern codes */
+static int led_pattern_table[] = {
+ [ETHERSWITCH_PORT_LED_DEFAULT] = 0x3,
+ [ETHERSWITCH_PORT_LED_ON] = 0x2,
+ [ETHERSWITCH_PORT_LED_OFF] = 0x0,
+ [ETHERSWITCH_PORT_LED_BLINK] = 0x1
+};
+
+static inline int arswitch_portforphy(int phy);
+static void arswitch_tick(void *arg);
+static int arswitch_ifmedia_upd(if_t);
+static void arswitch_ifmedia_sts(if_t, struct ifmediareq *);
+static int ar8xxx_port_vlan_setup(struct arswitch_softc *sc,
+ etherswitch_port_t *p);
+static int ar8xxx_port_vlan_get(struct arswitch_softc *sc,
+ etherswitch_port_t *p);
+static int arswitch_setled(struct arswitch_softc *sc, int phy, int led,
+ int style);
+
+static int
+arswitch_probe(device_t dev)
+{
+ struct arswitch_softc *sc;
+ uint32_t id;
+ char *chipname;
+
+ sc = device_get_softc(dev);
+ bzero(sc, sizeof(*sc));
+ sc->page = -1;
+
+ /* AR8xxx probe */
+ id = arswitch_readreg(dev, AR8X16_REG_MASK_CTRL);
+ sc->chip_rev = (id & AR8X16_MASK_CTRL_REV_MASK);
+ sc->chip_ver = (id & AR8X16_MASK_CTRL_VER_MASK) >> AR8X16_MASK_CTRL_VER_SHIFT;
+ switch (id & (AR8X16_MASK_CTRL_VER_MASK | AR8X16_MASK_CTRL_REV_MASK)) {
+ case 0x0101:
+ chipname = "AR8216";
+ sc->sc_switchtype = AR8X16_SWITCH_AR8216;
+ break;
+ case 0x0201:
+ chipname = "AR8226";
+ sc->sc_switchtype = AR8X16_SWITCH_AR8226;
+ break;
+ /* 0x0301 - AR8236 */
+ case 0x1000:
+ case 0x1001:
+ chipname = "AR8316";
+ sc->sc_switchtype = AR8X16_SWITCH_AR8316;
+ break;
+ case 0x1202:
+ case 0x1204:
+ chipname = "AR8327";
+ sc->sc_switchtype = AR8X16_SWITCH_AR8327;
+ sc->mii_lo_first = 1;
+ break;
+ default:
+ chipname = NULL;
+ }
+
+ DPRINTF(sc, ARSWITCH_DBG_ANY, "chipname=%s, id=%08x\n", chipname, id);
+ if (chipname != NULL) {
+ device_set_descf(dev,
+ "Atheros %s Ethernet Switch (ver %d rev %d)",
+ chipname, sc->chip_ver, sc->chip_rev);
+ return (BUS_PROBE_DEFAULT);
+ }
+ return (ENXIO);
+}
+
+static int
+arswitch_attach_phys(struct arswitch_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 < sc->numphys; phy++) {
+ sc->ifp[phy] = if_alloc(IFT_ETHER);
+ if_setsoftc(sc->ifp[phy], sc);
+ if_setflagbits(sc->ifp[phy], IFF_UP | IFF_BROADCAST |
+ IFF_DRV_RUNNING | IFF_SIMPLEX, 0);
+ sc->ifname[phy] = malloc(strlen(name)+1, M_DEVBUF, M_WAITOK);
+ bcopy(name, sc->ifname[phy], strlen(name)+1);
+ if_initname(sc->ifp[phy], sc->ifname[phy],
+ arswitch_portforphy(phy));
+ err = mii_attach(sc->sc_dev, &sc->miibus[phy], sc->ifp[phy],
+ arswitch_ifmedia_upd, arswitch_ifmedia_sts, \
+ BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0);
+#if 0
+ DPRINTF(sc->sc_dev, "%s attached to pseudo interface %s\n",
+ device_get_nameunit(sc->miibus[phy]),
+ if_name(sc->ifp[phy]));
+#endif
+ if (err != 0) {
+ device_printf(sc->sc_dev,
+ "attaching PHY %d failed\n",
+ phy);
+ return (err);
+ }
+
+ if (AR8X16_IS_SWITCH(sc, AR8327)) {
+ int led;
+ char ledname[IFNAMSIZ+4];
+
+ for (led = 0; led < 3; led++) {
+ sprintf(ledname, "%s%dled%d", name,
+ arswitch_portforphy(phy), led+1);
+ sc->dev_led[phy][led].sc = sc;
+ sc->dev_led[phy][led].phy = phy;
+ sc->dev_led[phy][led].lednum = led;
+ }
+ }
+ }
+ return (0);
+}
+
+static int
+arswitch_reset(device_t dev)
+{
+
+ arswitch_writereg(dev, AR8X16_REG_MASK_CTRL,
+ AR8X16_MASK_CTRL_SOFT_RESET);
+ DELAY(1000);
+ if (arswitch_readreg(dev, AR8X16_REG_MASK_CTRL) &
+ AR8X16_MASK_CTRL_SOFT_RESET) {
+ device_printf(dev, "unable to reset switch\n");
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+arswitch_set_vlan_mode(struct arswitch_softc *sc, uint32_t mode)
+{
+
+ /* Check for invalid modes. */
+ if ((mode & sc->info.es_vlan_caps) != mode)
+ return (EINVAL);
+
+ switch (mode) {
+ case ETHERSWITCH_VLAN_DOT1Q:
+ sc->vlan_mode = ETHERSWITCH_VLAN_DOT1Q;
+ break;
+ case ETHERSWITCH_VLAN_PORT:
+ sc->vlan_mode = ETHERSWITCH_VLAN_PORT;
+ break;
+ default:
+ sc->vlan_mode = 0;
+ }
+
+ /* Reset VLANs. */
+ sc->hal.arswitch_vlan_init_hw(sc);
+
+ return (0);
+}
+
+static void
+ar8xxx_port_init(struct arswitch_softc *sc, int port)
+{
+
+ /* Port0 - CPU */
+ if (port == AR8X16_PORT_CPU) {
+ arswitch_writereg(sc->sc_dev, AR8X16_REG_PORT_STS(0),
+ (AR8X16_IS_SWITCH(sc, AR8216) ?
+ AR8X16_PORT_STS_SPEED_100 : AR8X16_PORT_STS_SPEED_1000) |
+ (AR8X16_IS_SWITCH(sc, AR8216) ? 0 : AR8X16_PORT_STS_RXFLOW) |
+ (AR8X16_IS_SWITCH(sc, AR8216) ? 0 : AR8X16_PORT_STS_TXFLOW) |
+ AR8X16_PORT_STS_RXMAC |
+ AR8X16_PORT_STS_TXMAC |
+ AR8X16_PORT_STS_DUPLEX);
+ arswitch_writereg(sc->sc_dev, AR8X16_REG_PORT_CTRL(0),
+ arswitch_readreg(sc->sc_dev, AR8X16_REG_PORT_CTRL(0)) &
+ ~AR8X16_PORT_CTRL_HEADER);
+ } else {
+ /* Set ports to auto negotiation. */
+ arswitch_writereg(sc->sc_dev, AR8X16_REG_PORT_STS(port),
+ AR8X16_PORT_STS_LINK_AUTO);
+ arswitch_writereg(sc->sc_dev, AR8X16_REG_PORT_CTRL(port),
+ arswitch_readreg(sc->sc_dev, AR8X16_REG_PORT_CTRL(port)) &
+ ~AR8X16_PORT_CTRL_HEADER);
+ }
+}
+
+static int
+ar8xxx_atu_wait_ready(struct arswitch_softc *sc)
+{
+ int ret;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ ret = arswitch_waitreg(sc->sc_dev,
+ AR8216_REG_ATU,
+ AR8216_ATU_ACTIVE,
+ 0,
+ 1000);
+
+ return (ret);
+}
+
+/*
+ * Flush all ATU entries.
+ */
+static int
+ar8xxx_atu_flush(struct arswitch_softc *sc)
+{
+ int ret;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: flushing all ports\n", __func__);
+
+ ret = ar8xxx_atu_wait_ready(sc);
+ if (ret)
+ device_printf(sc->sc_dev, "%s: waitreg failed\n", __func__);
+
+ if (!ret)
+ arswitch_writereg(sc->sc_dev,
+ AR8216_REG_ATU,
+ AR8216_ATU_OP_FLUSH | AR8216_ATU_ACTIVE);
+
+ return (ret);
+}
+
+/*
+ * Flush ATU entries for a single port.
+ */
+static int
+ar8xxx_atu_flush_port(struct arswitch_softc *sc, int port)
+{
+ int ret, val;
+
+ DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: flushing port %d\n", __func__,
+ port);
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* Flush unicast entries on port */
+ val = AR8216_ATU_OP_FLUSH_UNICAST;
+
+ /* TODO: bit 4 indicates whether to flush dynamic (0) or static (1) */
+
+ /* Which port */
+ val |= SM(port, AR8216_ATU_PORT_NUM);
+
+ ret = ar8xxx_atu_wait_ready(sc);
+ if (ret)
+ device_printf(sc->sc_dev, "%s: waitreg failed\n", __func__);
+
+ if (!ret)
+ arswitch_writereg(sc->sc_dev,
+ AR8216_REG_ATU,
+ val | AR8216_ATU_ACTIVE);
+
+ return (ret);
+}
+
+/*
+ * XXX TODO: flush a single MAC address.
+ */
+
+/*
+ * Fetch a single entry from the ATU.
+ */
+static int
+ar8xxx_atu_fetch_table(struct arswitch_softc *sc, etherswitch_atu_entry_t *e,
+ int atu_fetch_op)
+{
+ uint32_t ret0, ret1, ret2, val;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ switch (atu_fetch_op) {
+ case 0:
+ /* Initialise things for the first fetch */
+
+ DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: initializing\n", __func__);
+ (void) ar8xxx_atu_wait_ready(sc);
+
+ arswitch_writereg(sc->sc_dev,
+ AR8216_REG_ATU, AR8216_ATU_OP_GET_NEXT);
+ arswitch_writereg(sc->sc_dev,
+ AR8216_REG_ATU_DATA, 0);
+ arswitch_writereg(sc->sc_dev,
+ AR8216_REG_ATU_CTRL2, 0);
+
+ return (0);
+ case 1:
+ DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: reading next\n", __func__);
+ /*
+ * Attempt to read the next address entry; don't modify what
+ * is there in AT_ADDR{4,5} as its used for the next fetch
+ */
+ (void) ar8xxx_atu_wait_ready(sc);
+
+ /* Begin the next read event; not modifying anything */
+ val = arswitch_readreg(sc->sc_dev, AR8216_REG_ATU);
+ val |= AR8216_ATU_ACTIVE;
+ arswitch_writereg(sc->sc_dev, AR8216_REG_ATU, val);
+
+ /* Wait for it to complete */
+ (void) ar8xxx_atu_wait_ready(sc);
+
+ /* Fetch the ethernet address and ATU status */
+ ret0 = arswitch_readreg(sc->sc_dev, AR8216_REG_ATU);
+ ret1 = arswitch_readreg(sc->sc_dev, AR8216_REG_ATU_DATA);
+ ret2 = arswitch_readreg(sc->sc_dev, AR8216_REG_ATU_CTRL2);
+
+ /* If the status is zero, then we're done */
+ if (MS(ret2, AR8216_ATU_CTRL2_AT_STATUS) == 0)
+ return (-1);
+
+ /* MAC address */
+ e->es_macaddr[5] = MS(ret0, AR8216_ATU_ADDR5);
+ e->es_macaddr[4] = MS(ret0, AR8216_ATU_ADDR4);
+ e->es_macaddr[3] = MS(ret1, AR8216_ATU_ADDR3);
+ e->es_macaddr[2] = MS(ret1, AR8216_ATU_ADDR2);
+ e->es_macaddr[1] = MS(ret1, AR8216_ATU_ADDR1);
+ e->es_macaddr[0] = MS(ret1, AR8216_ATU_ADDR0);
+
+ /* Bitmask of ports this entry is for */
+ e->es_portmask = MS(ret2, AR8216_ATU_CTRL2_DESPORT);
+
+ /* TODO: other flags that are interesting */
+
+ DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: MAC %6D portmask 0x%08x\n",
+ __func__,
+ e->es_macaddr, ":", e->es_portmask);
+ return (0);
+ default:
+ return (-1);
+ }
+ return (-1);
+}
+
+/*
+ * Configure aging register defaults.
+ */
+static int
+ar8xxx_atu_learn_default(struct arswitch_softc *sc)
+{
+ int ret;
+ uint32_t val;
+
+ DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: resetting learning\n", __func__);
+
+ /*
+ * For now, configure the aging defaults:
+ *
+ * + ARP_EN - enable "acknowledgement" of ARP frames - they are
+ * forwarded to the CPU port
+ * + LEARN_CHANGE_EN - hash table violations when learning MAC addresses
+ * will force an entry to be expired/updated and a new one to be
+ * programmed in.
+ * + AGE_EN - enable address table aging
+ * + AGE_TIME - set to 5 minutes
+ */
+ val = 0;
+ val |= AR8216_ATU_CTRL_ARP_EN;
+ val |= AR8216_ATU_CTRL_LEARN_CHANGE;
+ val |= AR8216_ATU_CTRL_AGE_EN;
+ val |= 0x2b; /* 5 minutes; bits 15:0 */
+
+ ret = arswitch_writereg(sc->sc_dev,
+ AR8216_REG_ATU_CTRL,
+ val);
+
+ if (ret)
+ device_printf(sc->sc_dev, "%s: writereg failed\n", __func__);
+
+ return (ret);
+}
+
+/*
+ * XXX TODO: add another routine to configure the leaky behaviour
+ * when unknown frames are received. These must be consistent
+ * between ethernet switches.
+ */
+
+/*
+ * Fetch the configured switch MAC address.
+ */
+static int
+ar8xxx_hw_get_switch_macaddr(struct arswitch_softc *sc, struct ether_addr *ea)
+{
+ uint32_t ret0, ret1;
+ char *s;
+
+ s = (void *) ea;
+
+ ret0 = arswitch_readreg(sc->sc_dev, AR8X16_REG_SW_MAC_ADDR0);
+ ret1 = arswitch_readreg(sc->sc_dev, AR8X16_REG_SW_MAC_ADDR1);
+
+ s[5] = MS(ret0, AR8X16_REG_SW_MAC_ADDR0_BYTE5);
+ s[4] = MS(ret0, AR8X16_REG_SW_MAC_ADDR0_BYTE4);
+ s[3] = MS(ret1, AR8X16_REG_SW_MAC_ADDR1_BYTE3);
+ s[2] = MS(ret1, AR8X16_REG_SW_MAC_ADDR1_BYTE2);
+ s[1] = MS(ret1, AR8X16_REG_SW_MAC_ADDR1_BYTE1);
+ s[0] = MS(ret1, AR8X16_REG_SW_MAC_ADDR1_BYTE0);
+
+ return (0);
+}
+
+/*
+ * Set the switch mac address.
+ */
+static int
+ar8xxx_hw_set_switch_macaddr(struct arswitch_softc *sc,
+ const struct ether_addr *ea)
+{
+
+ return (ENXIO);
+}
+
+/*
+ * XXX TODO: this attach routine does NOT free all memory, resources
+ * upon failure!
+ */
+static int
+arswitch_attach(device_t dev)
+{
+ struct arswitch_softc *sc = device_get_softc(dev);
+ struct sysctl_ctx_list *ctx;
+ struct sysctl_oid *tree;
+ int err = 0;
+ int port;
+
+ /* sc->sc_switchtype is already decided in arswitch_probe() */
+ sc->sc_dev = dev;
+ mtx_init(&sc->sc_mtx, "arswitch", NULL, MTX_DEF);
+ sc->page = -1;
+ strlcpy(sc->info.es_name, device_get_desc(dev),
+ sizeof(sc->info.es_name));
+
+ /* Debugging */
+ ctx = device_get_sysctl_ctx(sc->sc_dev);
+ tree = device_get_sysctl_tree(sc->sc_dev);
+ SYSCTL_ADD_UINT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+ "debug", CTLFLAG_RW, &sc->sc_debug, 0,
+ "control debugging printfs");
+
+ /* Allocate a 128 entry ATU table; hopefully its big enough! */
+ /* XXX TODO: make this per chip */
+ sc->atu.entries = malloc(sizeof(etherswitch_atu_entry_t) * 128,
+ M_DEVBUF, M_NOWAIT);
+ if (sc->atu.entries == NULL) {
+ device_printf(sc->sc_dev, "%s: failed to allocate ATU table\n",
+ __func__);
+ return (ENXIO);
+ }
+ sc->atu.count = 0;
+ sc->atu.size = 128;
+
+ /* Default HAL methods */
+ sc->hal.arswitch_port_init = ar8xxx_port_init;
+ sc->hal.arswitch_port_vlan_setup = ar8xxx_port_vlan_setup;
+ sc->hal.arswitch_port_vlan_get = ar8xxx_port_vlan_get;
+ sc->hal.arswitch_vlan_init_hw = ar8xxx_reset_vlans;
+ sc->hal.arswitch_hw_get_switch_macaddr = ar8xxx_hw_get_switch_macaddr;
+ sc->hal.arswitch_hw_set_switch_macaddr = ar8xxx_hw_set_switch_macaddr;
+
+ sc->hal.arswitch_vlan_getvgroup = ar8xxx_getvgroup;
+ sc->hal.arswitch_vlan_setvgroup = ar8xxx_setvgroup;
+
+ sc->hal.arswitch_vlan_get_pvid = ar8xxx_get_pvid;
+ sc->hal.arswitch_vlan_set_pvid = ar8xxx_set_pvid;
+
+ sc->hal.arswitch_get_dot1q_vlan = ar8xxx_get_dot1q_vlan;
+ sc->hal.arswitch_set_dot1q_vlan = ar8xxx_set_dot1q_vlan;
+ sc->hal.arswitch_flush_dot1q_vlan = ar8xxx_flush_dot1q_vlan;
+ sc->hal.arswitch_purge_dot1q_vlan = ar8xxx_purge_dot1q_vlan;
+ sc->hal.arswitch_get_port_vlan = ar8xxx_get_port_vlan;
+ sc->hal.arswitch_set_port_vlan = ar8xxx_set_port_vlan;
+
+ sc->hal.arswitch_atu_flush = ar8xxx_atu_flush;
+ sc->hal.arswitch_atu_flush_port = ar8xxx_atu_flush_port;
+ sc->hal.arswitch_atu_learn_default = ar8xxx_atu_learn_default;
+ sc->hal.arswitch_atu_fetch_table = ar8xxx_atu_fetch_table;
+
+ sc->hal.arswitch_phy_read = arswitch_readphy_internal;
+ sc->hal.arswitch_phy_write = arswitch_writephy_internal;
+
+ /*
+ * Attach switch related functions
+ */
+ if (AR8X16_IS_SWITCH(sc, AR8216))
+ ar8216_attach(sc);
+ else if (AR8X16_IS_SWITCH(sc, AR8226))
+ ar8226_attach(sc);
+ else if (AR8X16_IS_SWITCH(sc, AR8316))
+ ar8316_attach(sc);
+ else if (AR8X16_IS_SWITCH(sc, AR8327))
+ ar8327_attach(sc);
+ else {
+ DPRINTF(sc, ARSWITCH_DBG_ANY,
+ "%s: unknown switch (%d)?\n", __func__, sc->sc_switchtype);
+ return (ENXIO);
+ }
+
+ /* Common defaults. */
+ sc->info.es_nports = 5; /* XXX technically 6, but 6th not used */
+
+ /* XXX Defaults for externally connected AR8316 */
+ sc->numphys = 4;
+ sc->phy4cpu = 1;
+ sc->is_rgmii = 1;
+ sc->is_gmii = 0;
+ sc->is_mii = 0;
+
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "numphys", &sc->numphys);
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "phy4cpu", &sc->phy4cpu);
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "is_rgmii", &sc->is_rgmii);
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "is_gmii", &sc->is_gmii);
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "is_mii", &sc->is_mii);
+
+ if (sc->numphys > AR8X16_NUM_PHYS)
+ sc->numphys = AR8X16_NUM_PHYS;
+
+ /* Reset the switch. */
+ if (arswitch_reset(dev)) {
+ DPRINTF(sc, ARSWITCH_DBG_ANY,
+ "%s: arswitch_reset: failed\n", __func__);
+ return (ENXIO);
+ }
+
+ err = sc->hal.arswitch_hw_setup(sc);
+ if (err != 0) {
+ DPRINTF(sc, ARSWITCH_DBG_ANY,
+ "%s: hw_setup: err=%d\n", __func__, err);
+ return (err);
+ }
+
+ err = sc->hal.arswitch_hw_global_setup(sc);
+ if (err != 0) {
+ DPRINTF(sc, ARSWITCH_DBG_ANY,
+ "%s: hw_global_setup: err=%d\n", __func__, err);
+ return (err);
+ }
+
+ /*
+ * Configure the default address table learning parameters for this
+ * switch.
+ */
+ err = sc->hal.arswitch_atu_learn_default(sc);
+ if (err != 0) {
+ DPRINTF(sc, ARSWITCH_DBG_ANY,
+ "%s: atu_learn_default: err=%d\n", __func__, err);
+ return (err);
+ }
+
+ /* Initialize the switch ports. */
+ for (port = 0; port <= sc->numphys; port++) {
+ sc->hal.arswitch_port_init(sc, port);
+ }
+
+ /*
+ * Attach the PHYs and complete the bus enumeration.
+ */
+ err = arswitch_attach_phys(sc);
+ if (err != 0) {
+ DPRINTF(sc, ARSWITCH_DBG_ANY,
+ "%s: attach_phys: err=%d\n", __func__, err);
+ return (err);
+ }
+
+ /* Default to ingress filters off. */
+ err = arswitch_set_vlan_mode(sc, 0);
+ if (err != 0) {
+ DPRINTF(sc, ARSWITCH_DBG_ANY,
+ "%s: set_vlan_mode: err=%d\n", __func__, err);
+ return (err);
+ }
+
+ bus_identify_children(dev);
+ bus_enumerate_hinted_children(dev);
+ bus_attach_children(dev);
+
+ callout_init_mtx(&sc->callout_tick, &sc->sc_mtx, 0);
+
+ ARSWITCH_LOCK(sc);
+ arswitch_tick(sc);
+ ARSWITCH_UNLOCK(sc);
+
+ return (err);
+}
+
+static int
+arswitch_detach(device_t dev)
+{
+ struct arswitch_softc *sc = device_get_softc(dev);
+ int error, i;
+
+ callout_drain(&sc->callout_tick);
+
+ error = bus_generic_detach(dev);
+ if (error != 0)
+ return (error);
+
+ for (i=0; i < sc->numphys; i++) {
+ if (sc->ifp[i] != NULL)
+ if_free(sc->ifp[i]);
+ free(sc->ifname[i], M_DEVBUF);
+ }
+
+ free(sc->atu.entries, M_DEVBUF);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+/*
+ * Convert PHY number to port number. PHY0 is connected to port 1, PHY1 to
+ * port 2, etc.
+ */
+static inline int
+arswitch_portforphy(int phy)
+{
+ return (phy+1);
+}
+
+static inline struct mii_data *
+arswitch_miiforport(struct arswitch_softc *sc, int port)
+{
+ int phy = port-1;
+
+ if (phy < 0 || phy >= sc->numphys)
+ return (NULL);
+ return (device_get_softc(sc->miibus[phy]));
+}
+
+static inline if_t
+arswitch_ifpforport(struct arswitch_softc *sc, int port)
+{
+ int phy = port-1;
+
+ if (phy < 0 || phy >= sc->numphys)
+ return (NULL);
+ return (sc->ifp[phy]);
+}
+
+/*
+ * Convert port status to ifmedia.
+ */
+static void
+arswitch_update_ifmedia(int portstatus, u_int *media_status, u_int *media_active)
+{
+ *media_active = IFM_ETHER;
+ *media_status = IFM_AVALID;
+
+ if ((portstatus & AR8X16_PORT_STS_LINK_UP) != 0)
+ *media_status |= IFM_ACTIVE;
+ else {
+ *media_active |= IFM_NONE;
+ return;
+ }
+ switch (portstatus & AR8X16_PORT_STS_SPEED_MASK) {
+ case AR8X16_PORT_STS_SPEED_10:
+ *media_active |= IFM_10_T;
+ break;
+ case AR8X16_PORT_STS_SPEED_100:
+ *media_active |= IFM_100_TX;
+ break;
+ case AR8X16_PORT_STS_SPEED_1000:
+ *media_active |= IFM_1000_T;
+ break;
+ }
+ if ((portstatus & AR8X16_PORT_STS_DUPLEX) == 0)
+ *media_active |= IFM_FDX;
+ else
+ *media_active |= IFM_HDX;
+ if ((portstatus & AR8X16_PORT_STS_TXFLOW) != 0)
+ *media_active |= IFM_ETH_TXPAUSE;
+ if ((portstatus & AR8X16_PORT_STS_RXFLOW) != 0)
+ *media_active |= IFM_ETH_RXPAUSE;
+}
+
+/*
+ * Poll the status for all PHYs. We're using the switch port status because
+ * thats a lot quicker to read than talking to all the PHYs. Care must be
+ * taken that the resulting ifmedia_active is identical to what the PHY will
+ * compute, or gratuitous link status changes will occur whenever the PHYs
+ * update function is called.
+ */
+static void
+arswitch_miipollstat(struct arswitch_softc *sc)
+{
+ int i;
+ struct mii_data *mii;
+ struct mii_softc *miisc;
+ int portstatus;
+ int port_flap = 0;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ for (i = 0; i < sc->numphys; i++) {
+ if (sc->miibus[i] == NULL)
+ continue;
+ mii = device_get_softc(sc->miibus[i]);
+ /* XXX This would be nice to have abstracted out to be per-chip */
+ /* AR8327/AR8337 has a different register base */
+ if (AR8X16_IS_SWITCH(sc, AR8327))
+ portstatus = arswitch_readreg(sc->sc_dev,
+ AR8327_REG_PORT_STATUS(arswitch_portforphy(i)));
+ else
+ portstatus = arswitch_readreg(sc->sc_dev,
+ AR8X16_REG_PORT_STS(arswitch_portforphy(i)));
+#if 1
+ DPRINTF(sc, ARSWITCH_DBG_POLL, "p[%d]=0x%08x (%b)\n",
+ i,
+ portstatus,
+ portstatus,
+ "\20\3TXMAC\4RXMAC\5TXFLOW\6RXFLOW\7"
+ "DUPLEX\11LINK_UP\12LINK_AUTO\13LINK_PAUSE");
+#endif
+ /*
+ * If the current status is down, but we have a link
+ * status showing up, we need to do an ATU flush.
+ */
+ if ((mii->mii_media_status & IFM_ACTIVE) == 0 &&
+ (portstatus & AR8X16_PORT_STS_LINK_UP) != 0) {
+ device_printf(sc->sc_dev, "%s: port %d: port -> UP\n",
+ __func__,
+ i);
+ port_flap = 1;
+ }
+ /*
+ * and maybe if a port goes up->down?
+ */
+ if ((mii->mii_media_status & IFM_ACTIVE) != 0 &&
+ (portstatus & AR8X16_PORT_STS_LINK_UP) == 0) {
+ device_printf(sc->sc_dev, "%s: port %d: port -> DOWN\n",
+ __func__,
+ i);
+ port_flap = 1;
+ }
+ arswitch_update_ifmedia(portstatus, &mii->mii_media_status,
+ &mii->mii_media_active);
+ LIST_FOREACH(miisc, &mii->mii_phys, mii_list) {
+ if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) !=
+ miisc->mii_inst)
+ continue;
+ mii_phy_update(miisc, MII_POLLSTAT);
+ }
+ }
+
+ /* If a port went from down->up, flush the ATU */
+ if (port_flap)
+ sc->hal.arswitch_atu_flush(sc);
+}
+
+static void
+arswitch_tick(void *arg)
+{
+ struct arswitch_softc *sc = arg;
+
+ arswitch_miipollstat(sc);
+ callout_reset(&sc->callout_tick, hz, arswitch_tick, sc);
+}
+
+static void
+arswitch_lock(device_t dev)
+{
+ struct arswitch_softc *sc = device_get_softc(dev);
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+ ARSWITCH_LOCK(sc);
+}
+
+static void
+arswitch_unlock(device_t dev)
+{
+ struct arswitch_softc *sc = device_get_softc(dev);
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ ARSWITCH_UNLOCK(sc);
+}
+
+static etherswitch_info_t *
+arswitch_getinfo(device_t dev)
+{
+ struct arswitch_softc *sc = device_get_softc(dev);
+
+ return (&sc->info);
+}
+
+static int
+ar8xxx_port_vlan_get(struct arswitch_softc *sc, etherswitch_port_t *p)
+{
+ uint32_t reg;
+
+ ARSWITCH_LOCK(sc);
+
+ /* Retrieve the PVID. */
+ sc->hal.arswitch_vlan_get_pvid(sc, p->es_port, &p->es_pvid);
+
+ /* Port flags. */
+ reg = arswitch_readreg(sc->sc_dev, AR8X16_REG_PORT_CTRL(p->es_port));
+ if (reg & AR8X16_PORT_CTRL_DOUBLE_TAG)
+ p->es_flags |= ETHERSWITCH_PORT_DOUBLE_TAG;
+ reg >>= AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_SHIFT;
+ if ((reg & 0x3) == AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_ADD)
+ p->es_flags |= ETHERSWITCH_PORT_ADDTAG;
+ if ((reg & 0x3) == AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_STRIP)
+ p->es_flags |= ETHERSWITCH_PORT_STRIPTAG;
+ ARSWITCH_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+arswitch_is_cpuport(struct arswitch_softc *sc, int port)
+{
+
+ return ((port == AR8X16_PORT_CPU) ||
+ ((AR8X16_IS_SWITCH(sc, AR8327) &&
+ port == AR8327_PORT_GMAC6)));
+}
+
+static int
+arswitch_getport(device_t dev, etherswitch_port_t *p)
+{
+ struct arswitch_softc *sc;
+ struct mii_data *mii;
+ struct ifmediareq *ifmr;
+ int err;
+
+ sc = device_get_softc(dev);
+ /* XXX +1 is for AR8327; should make this configurable! */
+ if (p->es_port < 0 || p->es_port > sc->info.es_nports)
+ return (ENXIO);
+
+ err = sc->hal.arswitch_port_vlan_get(sc, p);
+ if (err != 0)
+ return (err);
+
+ mii = arswitch_miiforport(sc, p->es_port);
+ if (arswitch_is_cpuport(sc, p->es_port)) {
+ /* fill in fixed values for CPU port */
+ /* XXX is this valid in all cases? */
+ 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) {
+ err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr,
+ &mii->mii_media, SIOCGIFMEDIA);
+ if (err)
+ return (err);
+ } else {
+ return (ENXIO);
+ }
+
+ if (!arswitch_is_cpuport(sc, p->es_port) &&
+ AR8X16_IS_SWITCH(sc, AR8327)) {
+ int led;
+ p->es_nleds = 3;
+
+ for (led = 0; led < p->es_nleds; led++)
+ {
+ int style;
+ uint32_t val;
+
+ /* Find the right style enum for our pattern */
+ val = arswitch_readreg(dev,
+ ar8327_led_mapping[p->es_port-1][led].reg);
+ val = (val>>ar8327_led_mapping[p->es_port-1][led].shift)&0x03;
+
+ for (style = 0; style < ETHERSWITCH_PORT_LED_MAX; style++)
+ {
+ if (led_pattern_table[style] == val) break;
+ }
+
+ /* can't happen */
+ if (style == ETHERSWITCH_PORT_LED_MAX)
+ style = ETHERSWITCH_PORT_LED_DEFAULT;
+
+ p->es_led[led] = style;
+ }
+ } else
+ {
+ p->es_nleds = 0;
+ }
+
+ return (0);
+}
+
+static int
+ar8xxx_port_vlan_setup(struct arswitch_softc *sc, etherswitch_port_t *p)
+{
+ uint32_t reg;
+ int err;
+
+ ARSWITCH_LOCK(sc);
+
+ /* Set the PVID. */
+ if (p->es_pvid != 0)
+ sc->hal.arswitch_vlan_set_pvid(sc, p->es_port, p->es_pvid);
+
+ /* Mutually exclusive. */
+ if (p->es_flags & ETHERSWITCH_PORT_ADDTAG &&
+ p->es_flags & ETHERSWITCH_PORT_STRIPTAG) {
+ ARSWITCH_UNLOCK(sc);
+ return (EINVAL);
+ }
+
+ reg = 0;
+ if (p->es_flags & ETHERSWITCH_PORT_DOUBLE_TAG)
+ reg |= AR8X16_PORT_CTRL_DOUBLE_TAG;
+ if (p->es_flags & ETHERSWITCH_PORT_ADDTAG)
+ reg |= AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_ADD <<
+ AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_SHIFT;
+ if (p->es_flags & ETHERSWITCH_PORT_STRIPTAG)
+ reg |= AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_STRIP <<
+ AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_SHIFT;
+
+ err = arswitch_modifyreg(sc->sc_dev,
+ AR8X16_REG_PORT_CTRL(p->es_port),
+ 0x3 << AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_SHIFT |
+ AR8X16_PORT_CTRL_DOUBLE_TAG, reg);
+
+ ARSWITCH_UNLOCK(sc);
+ return (err);
+}
+
+static int
+arswitch_setport(device_t dev, etherswitch_port_t *p)
+{
+ int err, i;
+ struct arswitch_softc *sc;
+ struct ifmedia *ifm;
+ struct mii_data *mii;
+ if_t ifp;
+
+ sc = device_get_softc(dev);
+ if (p->es_port < 0 || p->es_port > sc->info.es_nports)
+ return (ENXIO);
+
+ /* Port flags. */
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ err = sc->hal.arswitch_port_vlan_setup(sc, p);
+ if (err)
+ return (err);
+ }
+
+ /* Do not allow media or led changes on CPU port. */
+ if (arswitch_is_cpuport(sc, p->es_port))
+ return (0);
+
+ if (AR8X16_IS_SWITCH(sc, AR8327))
+ {
+ for (i = 0; i < 3; i++)
+ {
+ int err;
+ err = arswitch_setled(sc, p->es_port-1, i, p->es_led[i]);
+ if (err)
+ return (err);
+ }
+ }
+
+ mii = arswitch_miiforport(sc, p->es_port);
+ if (mii == NULL)
+ return (ENXIO);
+
+ ifp = arswitch_ifpforport(sc, p->es_port);
+
+ ifm = &mii->mii_media;
+ return (ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA));
+}
+
+static int
+arswitch_setled(struct arswitch_softc *sc, int phy, int led, int style)
+{
+ int shift;
+ int err;
+
+ if (phy < 0 || phy > sc->numphys)
+ return EINVAL;
+
+ if (style < 0 || style > ETHERSWITCH_PORT_LED_MAX)
+ return (EINVAL);
+
+ ARSWITCH_LOCK(sc);
+
+ shift = ar8327_led_mapping[phy][led].shift;
+ err = (arswitch_modifyreg(sc->sc_dev,
+ ar8327_led_mapping[phy][led].reg,
+ 0x03 << shift, led_pattern_table[style] << shift));
+ ARSWITCH_UNLOCK(sc);
+
+ return (err);
+}
+
+static void
+arswitch_statchg(device_t dev)
+{
+ struct arswitch_softc *sc = device_get_softc(dev);
+
+ DPRINTF(sc, ARSWITCH_DBG_POLL, "%s\n", __func__);
+}
+
+static int
+arswitch_ifmedia_upd(if_t ifp)
+{
+ struct arswitch_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = arswitch_miiforport(sc, if_getdunit(ifp));
+
+ if (mii == NULL)
+ return (ENXIO);
+ mii_mediachg(mii);
+ return (0);
+}
+
+static void
+arswitch_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct arswitch_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = arswitch_miiforport(sc, if_getdunit(ifp));
+
+ DPRINTF(sc, ARSWITCH_DBG_POLL, "%s\n", __func__);
+
+ if (mii == NULL)
+ return;
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+}
+
+static int
+arswitch_getconf(device_t dev, etherswitch_conf_t *conf)
+{
+ struct arswitch_softc *sc;
+ int ret;
+
+ sc = device_get_softc(dev);
+
+ /* Return the VLAN mode. */
+ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE;
+ conf->vlan_mode = sc->vlan_mode;
+
+ /* Return the switch ethernet address. */
+ ret = sc->hal.arswitch_hw_get_switch_macaddr(sc,
+ &conf->switch_macaddr);
+ if (ret == 0) {
+ conf->cmd |= ETHERSWITCH_CONF_SWITCH_MACADDR;
+ }
+
+ return (0);
+}
+
+static int
+arswitch_setconf(device_t dev, etherswitch_conf_t *conf)
+{
+ struct arswitch_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+
+ /* Set the VLAN mode. */
+ if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) {
+ err = arswitch_set_vlan_mode(sc, conf->vlan_mode);
+ if (err != 0)
+ return (err);
+ }
+
+ /* TODO: Set the switch ethernet address. */
+
+ return (0);
+}
+
+static int
+arswitch_atu_flush_all(device_t dev)
+{
+ struct arswitch_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+ ARSWITCH_LOCK(sc);
+ err = sc->hal.arswitch_atu_flush(sc);
+ /* Invalidate cached ATU */
+ sc->atu.count = 0;
+ ARSWITCH_UNLOCK(sc);
+ return (err);
+}
+
+static int
+arswitch_atu_flush_port(device_t dev, int port)
+{
+ struct arswitch_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+ ARSWITCH_LOCK(sc);
+ err = sc->hal.arswitch_atu_flush_port(sc, port);
+ /* Invalidate cached ATU */
+ sc->atu.count = 0;
+ ARSWITCH_UNLOCK(sc);
+ return (err);
+}
+
+static int
+arswitch_atu_fetch_table(device_t dev, etherswitch_atu_table_t *table)
+{
+ struct arswitch_softc *sc;
+ int err, nitems;
+
+ sc = device_get_softc(dev);
+
+ ARSWITCH_LOCK(sc);
+ /* Initial setup */
+ nitems = 0;
+ err = sc->hal.arswitch_atu_fetch_table(sc, NULL, 0);
+
+ /* fetch - ideally yes we'd fetch into a separate table then switch */
+ while (err == 0 && nitems < sc->atu.size) {
+ err = sc->hal.arswitch_atu_fetch_table(sc,
+ &sc->atu.entries[nitems], 1);
+ if (err == 0) {
+ sc->atu.entries[nitems].id = nitems;
+ nitems++;
+ }
+ }
+ sc->atu.count = nitems;
+ ARSWITCH_UNLOCK(sc);
+
+ table->es_nitems = nitems;
+
+ return (0);
+}
+
+static int
+arswitch_atu_fetch_table_entry(device_t dev, etherswitch_atu_entry_t *e)
+{
+ struct arswitch_softc *sc;
+ int id;
+
+ sc = device_get_softc(dev);
+ id = e->id;
+
+ ARSWITCH_LOCK(sc);
+ if (id > sc->atu.count) {
+ ARSWITCH_UNLOCK(sc);
+ return (ENOENT);
+ }
+
+ memcpy(e, &sc->atu.entries[id], sizeof(*e));
+ ARSWITCH_UNLOCK(sc);
+ return (0);
+}
+
+static int
+arswitch_getvgroup(device_t dev, etherswitch_vlangroup_t *e)
+{
+ struct arswitch_softc *sc = device_get_softc(dev);
+
+ return (sc->hal.arswitch_vlan_getvgroup(sc, e));
+}
+
+static int
+arswitch_setvgroup(device_t dev, etherswitch_vlangroup_t *e)
+{
+ struct arswitch_softc *sc = device_get_softc(dev);
+
+ return (sc->hal.arswitch_vlan_setvgroup(sc, e));
+}
+
+static int
+arswitch_readphy(device_t dev, int phy, int reg)
+{
+ struct arswitch_softc *sc = device_get_softc(dev);
+
+ return (sc->hal.arswitch_phy_read(dev, phy, reg));
+}
+
+static int
+arswitch_writephy(device_t dev, int phy, int reg, int val)
+{
+ struct arswitch_softc *sc = device_get_softc(dev);
+
+ return (sc->hal.arswitch_phy_write(dev, phy, reg, val));
+}
+
+static device_method_t arswitch_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, arswitch_probe),
+ DEVMETHOD(device_attach, arswitch_attach),
+ DEVMETHOD(device_detach, arswitch_detach),
+
+ /* bus interface */
+ DEVMETHOD(bus_add_child, device_add_child_ordered),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, arswitch_readphy),
+ DEVMETHOD(miibus_writereg, arswitch_writephy),
+ DEVMETHOD(miibus_statchg, arswitch_statchg),
+
+ /* MDIO interface */
+ DEVMETHOD(mdio_readreg, arswitch_readphy),
+ DEVMETHOD(mdio_writereg, arswitch_writephy),
+
+ /* etherswitch interface */
+ DEVMETHOD(etherswitch_lock, arswitch_lock),
+ DEVMETHOD(etherswitch_unlock, arswitch_unlock),
+ DEVMETHOD(etherswitch_getinfo, arswitch_getinfo),
+ DEVMETHOD(etherswitch_readreg, arswitch_readreg),
+ DEVMETHOD(etherswitch_writereg, arswitch_writereg),
+ DEVMETHOD(etherswitch_readphyreg, arswitch_readphy),
+ DEVMETHOD(etherswitch_writephyreg, arswitch_writephy),
+ DEVMETHOD(etherswitch_getport, arswitch_getport),
+ DEVMETHOD(etherswitch_setport, arswitch_setport),
+ DEVMETHOD(etherswitch_getvgroup, arswitch_getvgroup),
+ DEVMETHOD(etherswitch_setvgroup, arswitch_setvgroup),
+ DEVMETHOD(etherswitch_getconf, arswitch_getconf),
+ DEVMETHOD(etherswitch_setconf, arswitch_setconf),
+ DEVMETHOD(etherswitch_flush_all, arswitch_atu_flush_all),
+ DEVMETHOD(etherswitch_flush_port, arswitch_atu_flush_port),
+ DEVMETHOD(etherswitch_fetch_table, arswitch_atu_fetch_table),
+ DEVMETHOD(etherswitch_fetch_table_entry, arswitch_atu_fetch_table_entry),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(arswitch, arswitch_driver, arswitch_methods,
+ sizeof(struct arswitch_softc));
+
+DRIVER_MODULE(arswitch, mdio, arswitch_driver, 0, 0);
+DRIVER_MODULE(miibus, arswitch, miibus_driver, 0, 0);
+DRIVER_MODULE(mdio, arswitch, mdio_driver, 0, 0);
+DRIVER_MODULE(etherswitch, arswitch, etherswitch_driver, 0, 0);
+MODULE_VERSION(arswitch, 1);
+MODULE_DEPEND(arswitch, miibus, 1, 1, 1); /* XXX which versions? */
+MODULE_DEPEND(arswitch, etherswitch, 1, 1, 1); /* XXX which versions? */
diff --git a/sys/dev/etherswitch/arswitch/arswitch_8216.c b/sys/dev/etherswitch/arswitch/arswitch_8216.c
new file mode 100644
index 000000000000..cb3192c8dfd4
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitch_8216.c
@@ -0,0 +1,94 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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/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_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/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/arswitch/arswitchreg.h>
+#include <dev/etherswitch/arswitch/arswitchvar.h>
+#include <dev/etherswitch/arswitch/arswitch_reg.h>
+#include <dev/etherswitch/arswitch/arswitch_8216.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+/*
+ * AR8216 specific functions
+ */
+static int
+ar8216_hw_setup(struct arswitch_softc *sc)
+{
+
+ return (0);
+}
+
+/*
+ * Initialise other global values, for the AR8216.
+ */
+static int
+ar8216_hw_global_setup(struct arswitch_softc *sc)
+{
+
+ return (0);
+}
+
+void
+ar8216_attach(struct arswitch_softc *sc)
+{
+
+ sc->hal.arswitch_hw_setup = ar8216_hw_setup;
+ sc->hal.arswitch_hw_global_setup = ar8216_hw_global_setup;
+
+ sc->info.es_nvlangroups = 0;
+}
diff --git a/sys/dev/etherswitch/arswitch/arswitch_8216.h b/sys/dev/etherswitch/arswitch/arswitch_8216.h
new file mode 100644
index 000000000000..d1d859897787
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitch_8216.h
@@ -0,0 +1,34 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * 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 __ARSWITCH_8216_H__
+#define __ARSWITCH_8216_H__
+
+extern void ar8216_attach(struct arswitch_softc *sc);
+
+#endif /* __ARSWITCH_8216_H__ */
+
diff --git a/sys/dev/etherswitch/arswitch/arswitch_8226.c b/sys/dev/etherswitch/arswitch/arswitch_8226.c
new file mode 100644
index 000000000000..af11812d1ca8
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitch_8226.c
@@ -0,0 +1,94 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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/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_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/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/arswitch/arswitchreg.h>
+#include <dev/etherswitch/arswitch/arswitchvar.h>
+#include <dev/etherswitch/arswitch/arswitch_phy.h>
+#include <dev/etherswitch/arswitch/arswitch_8226.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+/*
+ * AR8226 specific functions
+ */
+static int
+ar8226_hw_setup(struct arswitch_softc *sc)
+{
+
+ return (0);
+}
+
+/*
+ * Initialise other global values, for the AR8226.
+ */
+static int
+ar8226_hw_global_setup(struct arswitch_softc *sc)
+{
+
+ return (0);
+}
+
+void
+ar8226_attach(struct arswitch_softc *sc)
+{
+
+ sc->hal.arswitch_hw_setup = ar8226_hw_setup;
+ sc->hal.arswitch_hw_global_setup = ar8226_hw_global_setup;
+
+ sc->info.es_nvlangroups = 0;
+}
diff --git a/sys/dev/etherswitch/arswitch/arswitch_8226.h b/sys/dev/etherswitch/arswitch/arswitch_8226.h
new file mode 100644
index 000000000000..5f10193ffcd7
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitch_8226.h
@@ -0,0 +1,34 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * 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 __ARSWITCH_8226_H__
+#define __ARSWITCH_8226_H__
+
+extern void ar8226_attach(struct arswitch_softc *sc);
+
+#endif /* __ARSWITCH_8226_H__ */
+
diff --git a/sys/dev/etherswitch/arswitch/arswitch_8316.c b/sys/dev/etherswitch/arswitch/arswitch_8316.c
new file mode 100644
index 000000000000..48db6394fa00
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitch_8316.c
@@ -0,0 +1,171 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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/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_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/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/arswitch/arswitchreg.h>
+#include <dev/etherswitch/arswitch/arswitchvar.h>
+#include <dev/etherswitch/arswitch/arswitch_reg.h>
+#include <dev/etherswitch/arswitch/arswitch_8316.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+/*
+ * AR8316 specific functions
+ */
+static int
+ar8316_hw_setup(struct arswitch_softc *sc)
+{
+
+ /*
+ * Configure the switch mode based on whether:
+ *
+ * + The switch port is GMII/RGMII;
+ * + Port 4 is either connected to the CPU or to the internal switch.
+ */
+ if (sc->is_rgmii && sc->phy4cpu) {
+ arswitch_writereg(sc->sc_dev, AR8X16_REG_MODE,
+ AR8X16_MODE_RGMII_PORT4_ISO);
+ device_printf(sc->sc_dev,
+ "%s: MAC port == RGMII, port 4 = dedicated PHY\n",
+ __func__);
+ } else if (sc->is_rgmii) {
+ arswitch_writereg(sc->sc_dev, AR8X16_REG_MODE,
+ AR8X16_MODE_RGMII_PORT4_SWITCH);
+ device_printf(sc->sc_dev,
+ "%s: MAC port == RGMII, port 4 = switch port\n",
+ __func__);
+ } else if (sc->is_gmii) {
+ arswitch_writereg(sc->sc_dev, AR8X16_REG_MODE,
+ AR8X16_MODE_GMII);
+ device_printf(sc->sc_dev, "%s: MAC port == GMII\n", __func__);
+ } else {
+ device_printf(sc->sc_dev, "%s: unknown switch PHY config\n",
+ __func__);
+ return (ENXIO);
+ }
+
+ DELAY(1000); /* 1ms wait for things to settle */
+
+ /*
+ * If port 4 is RGMII, force workaround
+ */
+ if (sc->is_rgmii && sc->phy4cpu) {
+ device_printf(sc->sc_dev,
+ "%s: port 4 RGMII workaround\n",
+ __func__);
+
+ /* work around for phy4 rgmii mode */
+ arswitch_writedbg(sc->sc_dev, 4, 0x12, 0x480c);
+ /* rx delay */
+ arswitch_writedbg(sc->sc_dev, 4, 0x0, 0x824e);
+ /* tx delay */
+ arswitch_writedbg(sc->sc_dev, 4, 0x5, 0x3d47);
+ DELAY(1000); /* 1ms, again to let things settle */
+ }
+
+ return (0);
+}
+
+/*
+ * Initialise other global values, for the AR8316.
+ */
+static int
+ar8316_hw_global_setup(struct arswitch_softc *sc)
+{
+
+ ARSWITCH_LOCK(sc);
+
+ arswitch_writereg(sc->sc_dev, 0x38, AR8X16_MAGIC);
+
+ /* Enable CPU port and disable mirror port. */
+ arswitch_writereg(sc->sc_dev, AR8X16_REG_CPU_PORT,
+ AR8X16_CPU_PORT_EN | AR8X16_CPU_MIRROR_DIS);
+
+ /* Setup TAG priority mapping. */
+ arswitch_writereg(sc->sc_dev, AR8X16_REG_TAG_PRIO, 0xfa50);
+
+ /*
+ * Flood address table misses to all ports, and enable forwarding of
+ * broadcasts to the cpu port.
+ */
+ arswitch_writereg(sc->sc_dev, AR8X16_REG_FLOOD_MASK,
+ AR8X16_FLOOD_MASK_BCAST_TO_CPU | 0x003f003f);
+
+ /* Enable jumbo frames. */
+ arswitch_modifyreg(sc->sc_dev, AR8X16_REG_GLOBAL_CTRL,
+ AR8316_GLOBAL_CTRL_MTU_MASK, 9018 + 8 + 2);
+
+ /* Setup service TAG. */
+ arswitch_modifyreg(sc->sc_dev, AR8X16_REG_SERVICE_TAG,
+ AR8X16_SERVICE_TAG_MASK, 0);
+
+ ARSWITCH_UNLOCK(sc);
+ return (0);
+}
+
+void
+ar8316_attach(struct arswitch_softc *sc)
+{
+
+ sc->hal.arswitch_hw_setup = ar8316_hw_setup;
+ sc->hal.arswitch_hw_global_setup = ar8316_hw_global_setup;
+
+ /* Set the switch vlan capabilities. */
+ sc->info.es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q |
+ ETHERSWITCH_VLAN_PORT | ETHERSWITCH_VLAN_DOUBLE_TAG;
+ sc->info.es_nvlangroups = AR8X16_MAX_VLANS;
+}
diff --git a/sys/dev/etherswitch/arswitch/arswitch_8316.h b/sys/dev/etherswitch/arswitch/arswitch_8316.h
new file mode 100644
index 000000000000..7a3b8140b950
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitch_8316.h
@@ -0,0 +1,34 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * 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 __ARSWITCH_8316_H__
+#define __ARSWITCH_8316_H__
+
+extern void ar8316_attach(struct arswitch_softc *sc);
+
+#endif /* __ARSWITCH_8316_H__ */
+
diff --git a/sys/dev/etherswitch/arswitch/arswitch_8327.c b/sys/dev/etherswitch/arswitch/arswitch_8327.c
new file mode 100644
index 000000000000..6901143466a4
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitch_8327.c
@@ -0,0 +1,1310 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2014 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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/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_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/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/arswitch/arswitchreg.h>
+#include <dev/etherswitch/arswitch/arswitchvar.h>
+#include <dev/etherswitch/arswitch/arswitch_reg.h>
+#include <dev/etherswitch/arswitch/arswitch_phy.h>
+#include <dev/etherswitch/arswitch/arswitch_vlans.h>
+
+#include <dev/etherswitch/arswitch/arswitch_8327.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+/*
+ * AR8327 TODO:
+ *
+ * There should be a default hardware setup hint set for the default
+ * switch config. Otherwise the default is "all ports in one vlangroup",
+ * which means both CPU ports can see each other and that will quickly
+ * lead to traffic storms/loops.
+ */
+
+/* Map port+led to register+shift */
+struct ar8327_led_mapping ar8327_led_mapping[AR8327_NUM_PHYS][ETHERSWITCH_PORT_MAX_LEDS] =
+{
+ { /* PHY0 */
+ {AR8327_REG_LED_CTRL0, 14 },
+ {AR8327_REG_LED_CTRL1, 14 },
+ {AR8327_REG_LED_CTRL2, 14 }
+ },
+ { /* PHY1 */
+ {AR8327_REG_LED_CTRL3, 8 },
+ {AR8327_REG_LED_CTRL3, 10 },
+ {AR8327_REG_LED_CTRL3, 12 }
+ },
+ { /* PHY2 */
+ {AR8327_REG_LED_CTRL3, 14 },
+ {AR8327_REG_LED_CTRL3, 16 },
+ {AR8327_REG_LED_CTRL3, 18 }
+ },
+ { /* PHY3 */
+ {AR8327_REG_LED_CTRL3, 20 },
+ {AR8327_REG_LED_CTRL3, 22 },
+ {AR8327_REG_LED_CTRL3, 24 }
+ },
+ { /* PHY4 */
+ {AR8327_REG_LED_CTRL0, 30 },
+ {AR8327_REG_LED_CTRL1, 30 },
+ {AR8327_REG_LED_CTRL2, 30 }
+ }
+};
+
+static int
+ar8327_vlan_op(struct arswitch_softc *sc, uint32_t op, uint32_t vid,
+ uint32_t data)
+{
+ int err;
+
+ /*
+ * Wait for the "done" bit to finish.
+ */
+ if (arswitch_waitreg(sc->sc_dev, AR8327_REG_VTU_FUNC1,
+ AR8327_VTU_FUNC1_BUSY, 0, 5))
+ return (EBUSY);
+
+ /*
+ * If it's a "load" operation, then ensure 'data' is loaded
+ * in first.
+ */
+ if ((op & AR8327_VTU_FUNC1_OP) == AR8327_VTU_FUNC1_OP_LOAD) {
+ err = arswitch_writereg(sc->sc_dev, AR8327_REG_VTU_FUNC0, data);
+ if (err)
+ return (err);
+ }
+
+ /*
+ * Set the VID.
+ */
+ op |= ((vid & 0xfff) << AR8327_VTU_FUNC1_VID_S);
+
+ /*
+ * Set busy bit to start loading in the command.
+ */
+ op |= AR8327_VTU_FUNC1_BUSY;
+ arswitch_writereg(sc->sc_dev, AR8327_REG_VTU_FUNC1, op);
+
+ /*
+ * Finally - wait for it to load.
+ */
+ if (arswitch_waitreg(sc->sc_dev, AR8327_REG_VTU_FUNC1,
+ AR8327_VTU_FUNC1_BUSY, 0, 5))
+ return (EBUSY);
+
+ return (0);
+}
+
+static void
+ar8327_phy_fixup(struct arswitch_softc *sc, int phy)
+{
+ if (bootverbose)
+ device_printf(sc->sc_dev,
+ "%s: called; phy=%d; chiprev=%d\n", __func__,
+ phy,
+ sc->chip_rev);
+ switch (sc->chip_rev) {
+ case 1:
+ /* For 100M waveform */
+ arswitch_writedbg(sc->sc_dev, phy, 0, 0x02ea);
+ /* Turn on Gigabit clock */
+ arswitch_writedbg(sc->sc_dev, phy, 0x3d, 0x68a0);
+ break;
+
+ case 2:
+ arswitch_writemmd(sc->sc_dev, phy, 0x7, 0x3c);
+ arswitch_writemmd(sc->sc_dev, phy, 0x4007, 0x0);
+ /* fallthrough */
+ case 4:
+ arswitch_writemmd(sc->sc_dev, phy, 0x3, 0x800d);
+ arswitch_writemmd(sc->sc_dev, phy, 0x4003, 0x803f);
+
+ arswitch_writedbg(sc->sc_dev, phy, 0x3d, 0x6860);
+ arswitch_writedbg(sc->sc_dev, phy, 0x5, 0x2c46);
+ arswitch_writedbg(sc->sc_dev, phy, 0x3c, 0x6000);
+ break;
+ }
+}
+
+static uint32_t
+ar8327_get_pad_cfg(struct ar8327_pad_cfg *cfg)
+{
+ uint32_t t;
+
+ if (!cfg)
+ return (0);
+
+ t = 0;
+ switch (cfg->mode) {
+ case AR8327_PAD_NC:
+ break;
+
+ case AR8327_PAD_MAC2MAC_MII:
+ t = AR8327_PAD_MAC_MII_EN;
+ if (cfg->rxclk_sel)
+ t |= AR8327_PAD_MAC_MII_RXCLK_SEL;
+ if (cfg->txclk_sel)
+ t |= AR8327_PAD_MAC_MII_TXCLK_SEL;
+ break;
+
+ case AR8327_PAD_MAC2MAC_GMII:
+ t = AR8327_PAD_MAC_GMII_EN;
+ if (cfg->rxclk_sel)
+ t |= AR8327_PAD_MAC_GMII_RXCLK_SEL;
+ if (cfg->txclk_sel)
+ t |= AR8327_PAD_MAC_GMII_TXCLK_SEL;
+ break;
+
+ case AR8327_PAD_MAC_SGMII:
+ t = AR8327_PAD_SGMII_EN;
+
+ /*
+ * WAR for the Qualcomm Atheros AP136 board.
+ * It seems that RGMII TX/RX delay settings needs to be
+ * applied for SGMII mode as well, The ethernet is not
+ * reliable without this.
+ */
+ t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S;
+ t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S;
+ if (cfg->rxclk_delay_en)
+ t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN;
+ if (cfg->txclk_delay_en)
+ t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN;
+
+ if (cfg->sgmii_delay_en)
+ t |= AR8327_PAD_SGMII_DELAY_EN;
+
+ break;
+
+ case AR8327_PAD_MAC2PHY_MII:
+ t = AR8327_PAD_PHY_MII_EN;
+ if (cfg->rxclk_sel)
+ t |= AR8327_PAD_PHY_MII_RXCLK_SEL;
+ if (cfg->txclk_sel)
+ t |= AR8327_PAD_PHY_MII_TXCLK_SEL;
+ break;
+
+ case AR8327_PAD_MAC2PHY_GMII:
+ t = AR8327_PAD_PHY_GMII_EN;
+ if (cfg->pipe_rxclk_sel)
+ t |= AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL;
+ if (cfg->rxclk_sel)
+ t |= AR8327_PAD_PHY_GMII_RXCLK_SEL;
+ if (cfg->txclk_sel)
+ t |= AR8327_PAD_PHY_GMII_TXCLK_SEL;
+ break;
+
+ case AR8327_PAD_MAC_RGMII:
+ t = AR8327_PAD_RGMII_EN;
+ t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S;
+ t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S;
+ if (cfg->rxclk_delay_en)
+ t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN;
+ if (cfg->txclk_delay_en)
+ t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN;
+ break;
+
+ case AR8327_PAD_PHY_GMII:
+ t = AR8327_PAD_PHYX_GMII_EN;
+ break;
+
+ case AR8327_PAD_PHY_RGMII:
+ t = AR8327_PAD_PHYX_RGMII_EN;
+ break;
+
+ case AR8327_PAD_PHY_MII:
+ t = AR8327_PAD_PHYX_MII_EN;
+ break;
+ }
+
+ return (t);
+}
+
+/*
+ * Map the hard-coded port config from the switch setup to
+ * the chipset port config (status, duplex, flow, etc.)
+ */
+static uint32_t
+ar8327_get_port_init_status(struct ar8327_port_cfg *cfg)
+{
+ uint32_t t;
+
+ if (!cfg->force_link)
+ return (AR8X16_PORT_STS_LINK_AUTO);
+
+ t = AR8X16_PORT_STS_TXMAC | AR8X16_PORT_STS_RXMAC;
+ t |= cfg->duplex ? AR8X16_PORT_STS_DUPLEX : 0;
+ t |= cfg->rxpause ? AR8X16_PORT_STS_RXFLOW : 0;
+ t |= cfg->txpause ? AR8X16_PORT_STS_TXFLOW : 0;
+
+ switch (cfg->speed) {
+ case AR8327_PORT_SPEED_10:
+ t |= AR8X16_PORT_STS_SPEED_10;
+ break;
+ case AR8327_PORT_SPEED_100:
+ t |= AR8X16_PORT_STS_SPEED_100;
+ break;
+ case AR8327_PORT_SPEED_1000:
+ t |= AR8X16_PORT_STS_SPEED_1000;
+ break;
+ }
+
+ return (t);
+}
+
+/*
+ * Fetch the port data for the given port.
+ *
+ * This goes and does dirty things with the hints space
+ * to determine what the configuration parameters should be.
+ *
+ * Returns 1 if the structure was successfully parsed and
+ * the contents are valid; 0 otherwise.
+ */
+static int
+ar8327_fetch_pdata_port(struct arswitch_softc *sc,
+ struct ar8327_port_cfg *pcfg,
+ int port)
+{
+ int val;
+ char sbuf[128];
+
+ /* Check if force_link exists */
+ val = 0;
+ snprintf(sbuf, 128, "port.%d.force_link", port);
+ (void) resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ sbuf, &val);
+ if (val != 1)
+ return (0);
+ pcfg->force_link = 1;
+
+ /* force_link is set; let's parse the rest of the fields */
+ snprintf(sbuf, 128, "port.%d.speed", port);
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ sbuf, &val) == 0) {
+ switch (val) {
+ case 10:
+ pcfg->speed = AR8327_PORT_SPEED_10;
+ break;
+ case 100:
+ pcfg->speed = AR8327_PORT_SPEED_100;
+ break;
+ case 1000:
+ pcfg->speed = AR8327_PORT_SPEED_1000;
+ break;
+ default:
+ device_printf(sc->sc_dev,
+ "%s: invalid port %d duplex value (%d)\n",
+ __func__,
+ port,
+ val);
+ return (0);
+ }
+ }
+
+ snprintf(sbuf, 128, "port.%d.duplex", port);
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ sbuf, &val) == 0)
+ pcfg->duplex = val;
+
+ snprintf(sbuf, 128, "port.%d.txpause", port);
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ sbuf, &val) == 0)
+ pcfg->txpause = val;
+
+ snprintf(sbuf, 128, "port.%d.rxpause", port);
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ sbuf, &val) == 0)
+ pcfg->rxpause = val;
+
+#if 1
+ device_printf(sc->sc_dev,
+ "%s: port %d: speed=%d, duplex=%d, txpause=%d, rxpause=%d\n",
+ __func__,
+ port,
+ pcfg->speed,
+ pcfg->duplex,
+ pcfg->txpause,
+ pcfg->rxpause);
+#endif
+
+ return (1);
+}
+
+/*
+ * Parse the pad configuration from the boot hints.
+ *
+ * The (mostly optional) fields are:
+ *
+ * uint32_t mode;
+ * uint32_t rxclk_sel;
+ * uint32_t txclk_sel;
+ * uint32_t txclk_delay_sel;
+ * uint32_t rxclk_delay_sel;
+ * uint32_t txclk_delay_en;
+ * uint32_t rxclk_delay_en;
+ * uint32_t sgmii_delay_en;
+ * uint32_t pipe_rxclk_sel;
+ *
+ * If mode isn't in the hints, 0 is returned.
+ * Else the structure is fleshed out and 1 is returned.
+ */
+static int
+ar8327_fetch_pdata_pad(struct arswitch_softc *sc,
+ struct ar8327_pad_cfg *pc,
+ int pad)
+{
+ int val;
+ char sbuf[128];
+
+ /* Check if mode exists */
+ val = 0;
+ snprintf(sbuf, 128, "pad.%d.mode", pad);
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ sbuf, &val) != 0)
+ return (0);
+
+ /* assume that 'mode' exists and was found */
+ pc->mode = val;
+
+ snprintf(sbuf, 128, "pad.%d.rxclk_sel", pad);
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ sbuf, &val) == 0)
+ pc->rxclk_sel = val;
+
+ snprintf(sbuf, 128, "pad.%d.txclk_sel", pad);
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ sbuf, &val) == 0)
+ pc->txclk_sel = val;
+
+ snprintf(sbuf, 128, "pad.%d.txclk_delay_sel", pad);
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ sbuf, &val) == 0)
+ pc->txclk_delay_sel = val;
+
+ snprintf(sbuf, 128, "pad.%d.rxclk_delay_sel", pad);
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ sbuf, &val) == 0)
+ pc->rxclk_delay_sel = val;
+
+ snprintf(sbuf, 128, "pad.%d.txclk_delay_en", pad);
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ sbuf, &val) == 0)
+ pc->txclk_delay_en = val;
+
+ snprintf(sbuf, 128, "pad.%d.rxclk_delay_en", pad);
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ sbuf, &val) == 0)
+ pc->rxclk_delay_en = val;
+
+ snprintf(sbuf, 128, "pad.%d.sgmii_delay_en", pad);
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ sbuf, &val) == 0)
+ pc->sgmii_delay_en = val;
+
+ snprintf(sbuf, 128, "pad.%d.pipe_rxclk_sel", pad);
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ sbuf, &val) == 0)
+ pc->pipe_rxclk_sel = val;
+
+ if (bootverbose) {
+ device_printf(sc->sc_dev,
+ "%s: pad %d: mode=%d, rxclk_sel=%d, txclk_sel=%d, "
+ "txclk_delay_sel=%d, rxclk_delay_sel=%d, txclk_delay_en=%d, "
+ "rxclk_enable_en=%d, sgmii_delay_en=%d, pipe_rxclk_sel=%d\n",
+ __func__,
+ pad,
+ pc->mode,
+ pc->rxclk_sel,
+ pc->txclk_sel,
+ pc->txclk_delay_sel,
+ pc->rxclk_delay_sel,
+ pc->txclk_delay_en,
+ pc->rxclk_delay_en,
+ pc->sgmii_delay_en,
+ pc->pipe_rxclk_sel);
+ }
+
+ return (1);
+}
+
+/*
+ * Fetch the SGMII configuration block from the boot hints.
+ */
+static int
+ar8327_fetch_pdata_sgmii(struct arswitch_softc *sc,
+ struct ar8327_sgmii_cfg *scfg)
+{
+ int val;
+
+ /* sgmii_ctrl */
+ val = 0;
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ "sgmii.ctrl", &val) != 0)
+ return (0);
+ scfg->sgmii_ctrl = val;
+
+ /* serdes_aen */
+ val = 0;
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ "sgmii.serdes_aen", &val) != 0)
+ return (0);
+ scfg->serdes_aen = val;
+
+ return (1);
+}
+
+/*
+ * Fetch the LED configuration from the boot hints.
+ */
+static int
+ar8327_fetch_pdata_led(struct arswitch_softc *sc,
+ struct ar8327_led_cfg *lcfg)
+{
+ int val;
+
+ val = 0;
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ "led.ctrl0", &val) != 0)
+ return (0);
+ lcfg->led_ctrl0 = val;
+
+ val = 0;
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ "led.ctrl1", &val) != 0)
+ return (0);
+ lcfg->led_ctrl1 = val;
+
+ val = 0;
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ "led.ctrl2", &val) != 0)
+ return (0);
+ lcfg->led_ctrl2 = val;
+
+ val = 0;
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ "led.ctrl3", &val) != 0)
+ return (0);
+ lcfg->led_ctrl3 = val;
+
+ val = 0;
+ if (resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ "led.open_drain", &val) != 0)
+ return (0);
+ lcfg->open_drain = val;
+
+ return (1);
+}
+
+/*
+ * Initialise the ar8327 specific hardware features from
+ * the hints provided in the boot environment.
+ */
+static int
+ar8327_init_pdata(struct arswitch_softc *sc)
+{
+ struct ar8327_pad_cfg pc;
+ struct ar8327_port_cfg port_cfg;
+ struct ar8327_sgmii_cfg scfg;
+ struct ar8327_led_cfg lcfg;
+ uint32_t t, new_pos, pos;
+
+ /* Port 0 */
+ bzero(&port_cfg, sizeof(port_cfg));
+ sc->ar8327.port0_status = 0;
+ if (ar8327_fetch_pdata_port(sc, &port_cfg, 0))
+ sc->ar8327.port0_status = ar8327_get_port_init_status(&port_cfg);
+
+ /* Port 6 */
+ bzero(&port_cfg, sizeof(port_cfg));
+ sc->ar8327.port6_status = 0;
+ if (ar8327_fetch_pdata_port(sc, &port_cfg, 6))
+ sc->ar8327.port6_status = ar8327_get_port_init_status(&port_cfg);
+
+ /* Pad 0 */
+ bzero(&pc, sizeof(pc));
+ t = 0;
+ if (ar8327_fetch_pdata_pad(sc, &pc, 0))
+ t = ar8327_get_pad_cfg(&pc);
+#if 0
+ if (AR8X16_IS_SWITCH(sc, AR8337))
+ t |= AR8337_PAD_MAC06_EXCHANGE_EN;
+#endif
+ arswitch_writereg(sc->sc_dev, AR8327_REG_PAD0_MODE, t);
+
+ /* Pad 5 */
+ bzero(&pc, sizeof(pc));
+ t = 0;
+ if (ar8327_fetch_pdata_pad(sc, &pc, 5))
+ t = ar8327_get_pad_cfg(&pc);
+ arswitch_writereg(sc->sc_dev, AR8327_REG_PAD5_MODE, t);
+
+ /* Pad 6 */
+ bzero(&pc, sizeof(pc));
+ t = 0;
+ if (ar8327_fetch_pdata_pad(sc, &pc, 6))
+ t = ar8327_get_pad_cfg(&pc);
+ arswitch_writereg(sc->sc_dev, AR8327_REG_PAD6_MODE, t);
+
+ pos = arswitch_readreg(sc->sc_dev, AR8327_REG_POWER_ON_STRIP);
+ new_pos = pos;
+
+ /* XXX LED config */
+ bzero(&lcfg, sizeof(lcfg));
+ if (ar8327_fetch_pdata_led(sc, &lcfg)) {
+ if (lcfg.open_drain)
+ new_pos |= AR8327_POWER_ON_STRIP_LED_OPEN_EN;
+ else
+ new_pos &= ~AR8327_POWER_ON_STRIP_LED_OPEN_EN;
+
+ arswitch_writereg(sc->sc_dev, AR8327_REG_LED_CTRL0,
+ lcfg.led_ctrl0);
+ arswitch_writereg(sc->sc_dev, AR8327_REG_LED_CTRL1,
+ lcfg.led_ctrl1);
+ arswitch_writereg(sc->sc_dev, AR8327_REG_LED_CTRL2,
+ lcfg.led_ctrl2);
+ arswitch_writereg(sc->sc_dev, AR8327_REG_LED_CTRL3,
+ lcfg.led_ctrl3);
+
+ if (new_pos != pos)
+ new_pos |= AR8327_POWER_ON_STRIP_POWER_ON_SEL;
+ }
+
+ /* SGMII config */
+ bzero(&scfg, sizeof(scfg));
+ if (ar8327_fetch_pdata_sgmii(sc, &scfg)) {
+ device_printf(sc->sc_dev, "%s: SGMII cfg?\n", __func__);
+ t = scfg.sgmii_ctrl;
+ if (sc->chip_rev == 1)
+ t |= AR8327_SGMII_CTRL_EN_PLL |
+ AR8327_SGMII_CTRL_EN_RX |
+ AR8327_SGMII_CTRL_EN_TX;
+ else
+ t &= ~(AR8327_SGMII_CTRL_EN_PLL |
+ AR8327_SGMII_CTRL_EN_RX |
+ AR8327_SGMII_CTRL_EN_TX);
+
+ arswitch_writereg(sc->sc_dev, AR8327_REG_SGMII_CTRL, t);
+
+ if (scfg.serdes_aen)
+ new_pos &= ~AR8327_POWER_ON_STRIP_SERDES_AEN;
+ else
+ new_pos |= AR8327_POWER_ON_STRIP_SERDES_AEN;
+ }
+
+ arswitch_writereg(sc->sc_dev, AR8327_REG_POWER_ON_STRIP, new_pos);
+
+ return (0);
+}
+
+static int
+ar8327_hw_setup(struct arswitch_softc *sc)
+{
+ int i;
+ int err;
+
+ /* pdata fetch and setup */
+ err = ar8327_init_pdata(sc);
+ if (err != 0)
+ return (err);
+
+ /* XXX init leds */
+
+ for (i = 0; i < AR8327_NUM_PHYS; i++) {
+ /* phy fixup */
+ ar8327_phy_fixup(sc, i);
+
+ /* start PHY autonegotiation? */
+ /* XXX is this done as part of the normal PHY setup? */
+
+ }
+
+ /* Let things settle */
+ DELAY(1000);
+
+ return (0);
+}
+
+static int
+ar8327_atu_learn_default(struct arswitch_softc *sc)
+{
+
+ device_printf(sc->sc_dev, "%s: TODO!\n", __func__);
+ return (0);
+}
+
+/*
+ * Initialise other global values, for the AR8327.
+ */
+static int
+ar8327_hw_global_setup(struct arswitch_softc *sc)
+{
+ uint32_t t;
+
+ ARSWITCH_LOCK(sc);
+
+ /* enable CPU port and disable mirror port */
+ t = AR8327_FWD_CTRL0_CPU_PORT_EN |
+ AR8327_FWD_CTRL0_MIRROR_PORT;
+ arswitch_writereg(sc->sc_dev, AR8327_REG_FWD_CTRL0, t);
+
+ /* forward multicast and broadcast frames to CPU */
+ t = (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_UC_FLOOD_S) |
+ (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_MC_FLOOD_S) |
+ (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_BC_FLOOD_S);
+ arswitch_writereg(sc->sc_dev, AR8327_REG_FWD_CTRL1, t);
+
+ /* enable jumbo frames */
+ /* XXX need to macro-shift the value! */
+ arswitch_modifyreg(sc->sc_dev, AR8327_REG_MAX_FRAME_SIZE,
+ AR8327_MAX_FRAME_SIZE_MTU, 9018 + 8 + 2);
+
+ /* Enable MIB counters */
+ arswitch_modifyreg(sc->sc_dev, AR8327_REG_MODULE_EN,
+ AR8327_MODULE_EN_MIB, AR8327_MODULE_EN_MIB);
+
+ /* Disable EEE on all ports due to stability issues */
+ t = arswitch_readreg(sc->sc_dev, AR8327_REG_EEE_CTRL);
+ t |= AR8327_EEE_CTRL_DISABLE_PHY(0) |
+ AR8327_EEE_CTRL_DISABLE_PHY(1) |
+ AR8327_EEE_CTRL_DISABLE_PHY(2) |
+ AR8327_EEE_CTRL_DISABLE_PHY(3) |
+ AR8327_EEE_CTRL_DISABLE_PHY(4);
+ arswitch_writereg(sc->sc_dev, AR8327_REG_EEE_CTRL, t);
+
+ /* Set the right number of ports */
+ /* GMAC0 (CPU), GMAC1..5 (PHYs), GMAC6 (CPU) */
+ sc->info.es_nports = 7;
+
+ ARSWITCH_UNLOCK(sc);
+ return (0);
+}
+
+/*
+ * Port setup. Called at attach time.
+ */
+static void
+ar8327_port_init(struct arswitch_softc *sc, int port)
+{
+ uint32_t t;
+ int ports;
+
+ /* For now, port can see all other ports */
+ ports = 0x7f;
+
+ if (port == AR8X16_PORT_CPU)
+ t = sc->ar8327.port0_status;
+ else if (port == 6)
+ t = sc->ar8327.port6_status;
+ else
+ t = AR8X16_PORT_STS_LINK_AUTO;
+
+ arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_STATUS(port), t);
+ arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_HEADER(port), 0);
+
+ /*
+ * Default to 1 port group.
+ */
+ t = 1 << AR8327_PORT_VLAN0_DEF_SVID_S;
+ t |= 1 << AR8327_PORT_VLAN0_DEF_CVID_S;
+ arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_VLAN0(port), t);
+
+ t = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH << AR8327_PORT_VLAN1_OUT_MODE_S;
+ arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_VLAN1(port), t);
+
+ /*
+ * This doesn't configure any ports which this port can "see".
+ * bits 0-6 control which ports a frame coming into this port
+ * can be sent out to.
+ *
+ * So by doing this, we're making it impossible to send frames out
+ * to that port.
+ */
+ t = AR8327_PORT_LOOKUP_LEARN;
+ t |= AR8X16_PORT_CTRL_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S;
+
+ /* So this allows traffic to any port except ourselves */
+ t |= (ports & ~(1 << port));
+ arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_LOOKUP(port), t);
+}
+
+static int
+ar8327_port_vlan_setup(struct arswitch_softc *sc, etherswitch_port_t *p)
+{
+
+ /* Check: ADDTAG/STRIPTAG - exclusive */
+
+ ARSWITCH_LOCK(sc);
+
+ /* Set the PVID. */
+ if (p->es_pvid != 0)
+ sc->hal.arswitch_vlan_set_pvid(sc, p->es_port, p->es_pvid);
+
+ /*
+ * DOUBLE_TAG
+ * VLAN_MODE_ADD
+ * VLAN_MODE_STRIP
+ */
+ ARSWITCH_UNLOCK(sc);
+ return (0);
+}
+
+/*
+ * Get the port VLAN configuration.
+ */
+static int
+ar8327_port_vlan_get(struct arswitch_softc *sc, etherswitch_port_t *p)
+{
+
+ ARSWITCH_LOCK(sc);
+
+ /* Retrieve the PVID */
+ sc->hal.arswitch_vlan_get_pvid(sc, p->es_port, &p->es_pvid);
+
+ /* Retrieve the current port configuration from the VTU */
+ /*
+ * DOUBLE_TAG
+ * VLAN_MODE_ADD
+ * VLAN_MODE_STRIP
+ */
+
+ ARSWITCH_UNLOCK(sc);
+ return (0);
+}
+
+static void
+ar8327_port_disable_mirror(struct arswitch_softc *sc, int port)
+{
+
+ arswitch_modifyreg(sc->sc_dev,
+ AR8327_REG_PORT_LOOKUP(port),
+ AR8327_PORT_LOOKUP_ING_MIRROR_EN,
+ 0);
+ arswitch_modifyreg(sc->sc_dev,
+ AR8327_REG_PORT_HOL_CTRL1(port),
+ AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN,
+ 0);
+}
+
+static void
+ar8327_reset_vlans(struct arswitch_softc *sc)
+{
+ int i;
+ uint32_t t;
+ int ports;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+ ARSWITCH_LOCK(sc);
+
+ /* Clear the existing VLAN configuration */
+ memset(sc->vid, 0, sizeof(sc->vid));
+
+ /*
+ * Disable mirroring.
+ */
+ arswitch_modifyreg(sc->sc_dev, AR8327_REG_FWD_CTRL0,
+ AR8327_FWD_CTRL0_MIRROR_PORT,
+ (0xF << AR8327_FWD_CTRL0_MIRROR_PORT_S));
+
+ /*
+ * XXX TODO: disable any Q-in-Q port configuration,
+ * tagging, egress filters, etc.
+ */
+
+ /*
+ * For now, let's default to one portgroup, just so traffic
+ * flows. All ports can see other ports. There are two CPU GMACs
+ * (GMAC0, GMAC6), GMAC1..GMAC5 are external PHYs.
+ *
+ * (ETHERSWITCH_VLAN_PORT)
+ */
+ ports = 0x7f;
+
+ /*
+ * XXX TODO: set things up correctly for vlans!
+ */
+ for (i = 0; i < AR8327_NUM_PORTS; i++) {
+ int egress, ingress;
+
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) {
+ sc->vid[i] = i | ETHERSWITCH_VID_VALID;
+ /* set egress == out_keep */
+ ingress = AR8X16_PORT_VLAN_MODE_PORT_ONLY;
+ /* in_port_only, forward */
+ egress = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH;
+ } else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ ingress = AR8X16_PORT_VLAN_MODE_SECURE;
+ egress = AR8327_PORT_VLAN1_OUT_MODE_UNMOD;
+ } else {
+ /* set egress == out_keep */
+ ingress = AR8X16_PORT_VLAN_MODE_PORT_ONLY;
+ /* in_port_only, forward */
+ egress = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH;
+ }
+
+ /* set pvid = 1; there's only one vlangroup to start with */
+ t = 1 << AR8327_PORT_VLAN0_DEF_SVID_S;
+ t |= 1 << AR8327_PORT_VLAN0_DEF_CVID_S;
+ arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_VLAN0(i), t);
+
+ t = AR8327_PORT_VLAN1_PORT_VLAN_PROP;
+ t |= egress << AR8327_PORT_VLAN1_OUT_MODE_S;
+ arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_VLAN1(i), t);
+
+ /* Ports can see other ports */
+ /* XXX not entirely true for dot1q? */
+ t = (ports & ~(1 << i)); /* all ports besides us */
+ t |= AR8327_PORT_LOOKUP_LEARN;
+
+ t |= ingress << AR8327_PORT_LOOKUP_IN_MODE_S;
+ t |= AR8X16_PORT_CTRL_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S;
+ arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_LOOKUP(i), t);
+ }
+
+ /*
+ * Disable port mirroring entirely.
+ */
+ for (i = 0; i < AR8327_NUM_PORTS; i++) {
+ ar8327_port_disable_mirror(sc, i);
+ }
+
+ /*
+ * If dot1q - set pvid; dot1q, etc.
+ */
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ sc->vid[0] = 1;
+ for (i = 0; i < AR8327_NUM_PORTS; i++) {
+ /* Each port - pvid 1 */
+ sc->hal.arswitch_vlan_set_pvid(sc, i, sc->vid[0]);
+ }
+ /* Initialise vlan1 - all ports, untagged */
+ sc->hal.arswitch_set_dot1q_vlan(sc, ports, ports, sc->vid[0]);
+ sc->vid[0] |= ETHERSWITCH_VID_VALID;
+ }
+
+ ARSWITCH_UNLOCK(sc);
+}
+
+static int
+ar8327_vlan_get_port(struct arswitch_softc *sc, uint32_t *ports, int vid)
+{
+ int port;
+ uint32_t reg;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* For port based vlans the vlanid is the same as the port index. */
+ port = vid & ETHERSWITCH_VID_MASK;
+ reg = arswitch_readreg(sc->sc_dev, AR8327_REG_PORT_LOOKUP(port));
+ *ports = reg & 0x7f;
+ return (0);
+}
+
+static int
+ar8327_vlan_set_port(struct arswitch_softc *sc, uint32_t ports, int vid)
+{
+ int err, port;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* For port based vlans the vlanid is the same as the port index. */
+ port = vid & ETHERSWITCH_VID_MASK;
+
+ err = arswitch_modifyreg(sc->sc_dev, AR8327_REG_PORT_LOOKUP(port),
+ 0x7f, /* vlan membership mask */
+ (ports & 0x7f));
+
+ if (err)
+ return (err);
+ return (0);
+}
+
+static int
+ar8327_vlan_getvgroup(struct arswitch_softc *sc, etherswitch_vlangroup_t *vg)
+{
+
+ return (ar8xxx_getvgroup(sc, vg));
+}
+
+static int
+ar8327_vlan_setvgroup(struct arswitch_softc *sc, etherswitch_vlangroup_t *vg)
+{
+
+ return (ar8xxx_setvgroup(sc, vg));
+}
+
+static int
+ar8327_get_pvid(struct arswitch_softc *sc, int port, int *pvid)
+{
+ uint32_t reg;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ /*
+ * XXX for now, assuming it's CVID; likely very wrong!
+ */
+ port = port & ETHERSWITCH_VID_MASK;
+ reg = arswitch_readreg(sc->sc_dev, AR8327_REG_PORT_VLAN0(port));
+ reg = reg >> AR8327_PORT_VLAN0_DEF_CVID_S;
+ reg = reg & 0xfff;
+
+ *pvid = reg;
+ return (0);
+}
+
+static int
+ar8327_set_pvid(struct arswitch_softc *sc, int port, int pvid)
+{
+ uint32_t t;
+
+ /* Limit pvid to valid values */
+ pvid &= 0x7f;
+
+ t = pvid << AR8327_PORT_VLAN0_DEF_SVID_S;
+ t |= pvid << AR8327_PORT_VLAN0_DEF_CVID_S;
+ arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_VLAN0(port), t);
+
+ return (0);
+}
+
+static int
+ar8327_atu_wait_ready(struct arswitch_softc *sc)
+{
+ int ret;
+
+ ret = arswitch_waitreg(sc->sc_dev,
+ AR8327_REG_ATU_FUNC,
+ AR8327_ATU_FUNC_BUSY,
+ 0,
+ 1000);
+
+ return (ret);
+}
+
+static int
+ar8327_atu_flush(struct arswitch_softc *sc)
+{
+
+ int ret;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ ret = ar8327_atu_wait_ready(sc);
+ if (ret)
+ device_printf(sc->sc_dev, "%s: waitreg failed\n", __func__);
+
+ if (!ret)
+ arswitch_writereg(sc->sc_dev,
+ AR8327_REG_ATU_FUNC,
+ AR8327_ATU_FUNC_OP_FLUSH | AR8327_ATU_FUNC_BUSY);
+ return (ret);
+}
+
+static int
+ar8327_atu_flush_port(struct arswitch_softc *sc, int port)
+{
+ int ret;
+ uint32_t val;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ ret = ar8327_atu_wait_ready(sc);
+ if (ret)
+ device_printf(sc->sc_dev, "%s: waitreg failed\n", __func__);
+
+ val = AR8327_ATU_FUNC_OP_FLUSH_UNICAST;
+ val |= SM(port, AR8327_ATU_FUNC_PORT_NUM);
+
+ if (!ret)
+ arswitch_writereg(sc->sc_dev,
+ AR8327_REG_ATU_FUNC,
+ val | AR8327_ATU_FUNC_BUSY);
+
+ return (ret);
+}
+
+/*
+ * Fetch a single entry from the ATU.
+ */
+static int
+ar8327_atu_fetch_table(struct arswitch_softc *sc, etherswitch_atu_entry_t *e,
+ int atu_fetch_op)
+{
+ uint32_t ret0, ret1, ret2, val;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ switch (atu_fetch_op) {
+ case 0:
+ /* Initialise things for the first fetch */
+
+ DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: initializing\n", __func__);
+ (void) ar8327_atu_wait_ready(sc);
+
+ arswitch_writereg(sc->sc_dev,
+ AR8327_REG_ATU_FUNC, AR8327_ATU_FUNC_OP_GET_NEXT);
+ arswitch_writereg(sc->sc_dev, AR8327_REG_ATU_DATA0, 0);
+ arswitch_writereg(sc->sc_dev, AR8327_REG_ATU_DATA1, 0);
+ arswitch_writereg(sc->sc_dev, AR8327_REG_ATU_DATA2, 0);
+
+ return (0);
+ case 1:
+ DPRINTF(sc, ARSWITCH_DBG_ATU, "%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
+ */
+ (void) ar8327_atu_wait_ready(sc);
+
+ /* Begin the next read event; not modifying anything */
+ val = arswitch_readreg(sc->sc_dev, AR8327_REG_ATU_FUNC);
+ val |= AR8327_ATU_FUNC_BUSY;
+ arswitch_writereg(sc->sc_dev, AR8327_REG_ATU_FUNC, val);
+
+ /* Wait for it to complete */
+ (void) ar8327_atu_wait_ready(sc);
+
+ /* Fetch the ethernet address and ATU status */
+ ret0 = arswitch_readreg(sc->sc_dev, AR8327_REG_ATU_DATA0);
+ ret1 = arswitch_readreg(sc->sc_dev, AR8327_REG_ATU_DATA1);
+ ret2 = arswitch_readreg(sc->sc_dev, AR8327_REG_ATU_DATA2);
+
+ /* If the status is zero, then we're done */
+ if (MS(ret2, AR8327_ATU_FUNC_DATA2_STATUS) == 0)
+ return (-1);
+
+ /* MAC address */
+ e->es_macaddr[5] = MS(ret0, AR8327_ATU_DATA0_MAC_ADDR3);
+ e->es_macaddr[4] = MS(ret0, AR8327_ATU_DATA0_MAC_ADDR2);
+ e->es_macaddr[3] = MS(ret0, AR8327_ATU_DATA0_MAC_ADDR1);
+ e->es_macaddr[2] = MS(ret0, AR8327_ATU_DATA0_MAC_ADDR0);
+ e->es_macaddr[0] = MS(ret1, AR8327_ATU_DATA1_MAC_ADDR5);
+ e->es_macaddr[1] = MS(ret1, AR8327_ATU_DATA1_MAC_ADDR4);
+
+ /* Bitmask of ports this entry is for */
+ e->es_portmask = MS(ret1, AR8327_ATU_DATA1_DEST_PORT);
+
+ /* TODO: other flags that are interesting */
+
+ DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: MAC %6D portmask 0x%08x\n",
+ __func__,
+ e->es_macaddr, ":", e->es_portmask);
+ return (0);
+ default:
+ return (-1);
+ }
+ return (-1);
+}
+static int
+ar8327_flush_dot1q_vlan(struct arswitch_softc *sc)
+{
+
+ return (ar8327_vlan_op(sc, AR8327_VTU_FUNC1_OP_FLUSH, 0, 0));
+}
+
+static int
+ar8327_purge_dot1q_vlan(struct arswitch_softc *sc, int vid)
+{
+
+ return (ar8327_vlan_op(sc, AR8327_VTU_FUNC1_OP_PURGE, vid, 0));
+}
+
+static int
+ar8327_get_dot1q_vlan(struct arswitch_softc *sc, uint32_t *ports,
+ uint32_t *untagged_ports, int vid)
+{
+ int i, r;
+ uint32_t op, reg, val;
+
+ op = AR8327_VTU_FUNC1_OP_GET_ONE;
+
+ /* Filter out the vid flags; only grab the VLAN ID */
+ vid &= 0xfff;
+
+ /* XXX TODO: the VTU here stores egress mode - keep, tag, untagged, none */
+ r = ar8327_vlan_op(sc, op, vid, 0);
+ if (r != 0) {
+ device_printf(sc->sc_dev, "%s: %d: op failed\n", __func__, vid);
+ }
+
+ reg = arswitch_readreg(sc->sc_dev, AR8327_REG_VTU_FUNC0);
+ DPRINTF(sc, ARSWITCH_DBG_REGIO, "%s: %d: reg=0x%08x\n", __func__, vid, reg);
+
+ /*
+ * If any of the bits are set, update the port mask.
+ * Worry about the port config itself when getport() is called.
+ */
+ *ports = 0;
+ for (i = 0; i < AR8327_NUM_PORTS; i++) {
+ val = reg >> AR8327_VTU_FUNC0_EG_MODE_S(i);
+ val = val & 0x3;
+ /* XXX KEEP (unmodified?) */
+ if (val == AR8327_VTU_FUNC0_EG_MODE_TAG) {
+ *ports |= (1 << i);
+ } else if (val == AR8327_VTU_FUNC0_EG_MODE_UNTAG) {
+ *ports |= (1 << i);
+ *untagged_ports |= (1 << i);
+ }
+ }
+
+ return (0);
+}
+
+static int
+ar8327_set_dot1q_vlan(struct arswitch_softc *sc, uint32_t ports,
+ uint32_t untagged_ports, int vid)
+{
+ int i;
+ uint32_t op, val, mode;
+
+ op = AR8327_VTU_FUNC1_OP_LOAD;
+ vid &= 0xfff;
+
+ DPRINTF(sc, ARSWITCH_DBG_VLAN,
+ "%s: vid: %d, ports=0x%08x, untagged_ports=0x%08x\n",
+ __func__,
+ vid,
+ ports,
+ untagged_ports);
+
+ /*
+ * Mark it as valid; and that it should use per-VLAN MAC table,
+ * not VID=0 when doing MAC lookups
+ */
+ val = AR8327_VTU_FUNC0_VALID | AR8327_VTU_FUNC0_IVL;
+
+ for (i = 0; i < AR8327_NUM_PORTS; i++) {
+ if ((ports & BIT(i)) == 0)
+ mode = AR8327_VTU_FUNC0_EG_MODE_NOT;
+ else if (untagged_ports & BIT(i))
+ mode = AR8327_VTU_FUNC0_EG_MODE_UNTAG;
+ else
+ mode = AR8327_VTU_FUNC0_EG_MODE_TAG;
+
+ val |= mode << AR8327_VTU_FUNC0_EG_MODE_S(i);
+ }
+
+ return (ar8327_vlan_op(sc, op, vid, val));
+}
+
+void
+ar8327_attach(struct arswitch_softc *sc)
+{
+
+ sc->hal.arswitch_hw_setup = ar8327_hw_setup;
+ sc->hal.arswitch_hw_global_setup = ar8327_hw_global_setup;
+
+ sc->hal.arswitch_port_init = ar8327_port_init;
+
+ sc->hal.arswitch_vlan_getvgroup = ar8327_vlan_getvgroup;
+ sc->hal.arswitch_vlan_setvgroup = ar8327_vlan_setvgroup;
+ sc->hal.arswitch_port_vlan_setup = ar8327_port_vlan_setup;
+ sc->hal.arswitch_port_vlan_get = ar8327_port_vlan_get;
+ sc->hal.arswitch_flush_dot1q_vlan = ar8327_flush_dot1q_vlan;
+ sc->hal.arswitch_purge_dot1q_vlan = ar8327_purge_dot1q_vlan;
+ sc->hal.arswitch_set_dot1q_vlan = ar8327_set_dot1q_vlan;
+ sc->hal.arswitch_get_dot1q_vlan = ar8327_get_dot1q_vlan;
+
+ sc->hal.arswitch_vlan_init_hw = ar8327_reset_vlans;
+ sc->hal.arswitch_vlan_get_pvid = ar8327_get_pvid;
+ sc->hal.arswitch_vlan_set_pvid = ar8327_set_pvid;
+
+ sc->hal.arswitch_get_port_vlan = ar8327_vlan_get_port;
+ sc->hal.arswitch_set_port_vlan = ar8327_vlan_set_port;
+
+ sc->hal.arswitch_atu_learn_default = ar8327_atu_learn_default;
+ sc->hal.arswitch_atu_flush = ar8327_atu_flush;
+ sc->hal.arswitch_atu_flush_port = ar8327_atu_flush_port;
+ sc->hal.arswitch_atu_fetch_table = ar8327_atu_fetch_table;
+
+ /*
+ * Reading the PHY via the MDIO interface currently doesn't
+ * work correctly.
+ *
+ * So for now, just go direct to the PHY registers themselves.
+ * This has always worked on external devices, but not internal
+ * devices (AR934x, AR724x, AR933x.)
+ */
+ sc->hal.arswitch_phy_read = arswitch_readphy_external;
+ sc->hal.arswitch_phy_write = arswitch_writephy_external;
+
+ /* Set the switch vlan capabilities. */
+ sc->info.es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q |
+ ETHERSWITCH_VLAN_PORT | ETHERSWITCH_VLAN_DOUBLE_TAG;
+ sc->info.es_nvlangroups = AR8X16_MAX_VLANS;
+}
diff --git a/sys/dev/etherswitch/arswitch/arswitch_8327.h b/sys/dev/etherswitch/arswitch/arswitch_8327.h
new file mode 100644
index 000000000000..7ba0e71204db
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitch_8327.h
@@ -0,0 +1,96 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2014 Adrian Chadd <adrian@FreeBSD.org>
+ * All rights reserved.
+ *
+ * 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 __ARSWITCH_8327_H__
+#define __ARSWITCH_8327_H__
+
+enum ar8327_pad_mode {
+ AR8327_PAD_NC = 0,
+ AR8327_PAD_MAC2MAC_MII,
+ AR8327_PAD_MAC2MAC_GMII,
+ AR8327_PAD_MAC_SGMII,
+ AR8327_PAD_MAC2PHY_MII,
+ AR8327_PAD_MAC2PHY_GMII,
+ AR8327_PAD_MAC_RGMII,
+ AR8327_PAD_PHY_GMII,
+ AR8327_PAD_PHY_RGMII,
+ AR8327_PAD_PHY_MII,
+};
+
+enum ar8327_clk_delay_sel {
+ AR8327_CLK_DELAY_SEL0 = 0,
+ AR8327_CLK_DELAY_SEL1,
+ AR8327_CLK_DELAY_SEL2,
+ AR8327_CLK_DELAY_SEL3,
+};
+
+/* XXX update the field types */
+struct ar8327_pad_cfg {
+ uint32_t mode;
+ uint32_t rxclk_sel;
+ uint32_t txclk_sel;
+ uint32_t txclk_delay_sel;
+ uint32_t rxclk_delay_sel;
+ uint32_t txclk_delay_en;
+ uint32_t rxclk_delay_en;
+ uint32_t sgmii_delay_en;
+ uint32_t pipe_rxclk_sel;
+};
+
+struct ar8327_sgmii_cfg {
+ uint32_t sgmii_ctrl;
+ uint32_t serdes_aen;
+};
+
+struct ar8327_led_cfg {
+ uint32_t led_ctrl0;
+ uint32_t led_ctrl1;
+ uint32_t led_ctrl2;
+ uint32_t led_ctrl3;
+ uint32_t open_drain;
+};
+
+struct ar8327_port_cfg {
+#define AR8327_PORT_SPEED_10 1
+#define AR8327_PORT_SPEED_100 2
+#define AR8327_PORT_SPEED_1000 3
+ uint32_t speed;
+ uint32_t force_link;
+ uint32_t duplex;
+ uint32_t txpause;
+ uint32_t rxpause;
+};
+
+extern struct ar8327_led_mapping {
+ int reg;
+ int shift;
+} ar8327_led_mapping[AR8327_NUM_PHYS][ETHERSWITCH_PORT_MAX_LEDS];
+
+extern void ar8327_attach(struct arswitch_softc *sc);
+
+#endif /* __ARSWITCH_8327_H__ */
+
diff --git a/sys/dev/etherswitch/arswitch/arswitch_phy.c b/sys/dev/etherswitch/arswitch/arswitch_phy.c
new file mode 100644
index 000000000000..68cf365fd0f5
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitch_phy.c
@@ -0,0 +1,216 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_media.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/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/arswitch/arswitchreg.h>
+#include <dev/etherswitch/arswitch/arswitchvar.h>
+
+#include <dev/etherswitch/arswitch/arswitch_reg.h>
+#include <dev/etherswitch/arswitch/arswitch_phy.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+/*
+ * Access PHYs integrated into the switch by going direct
+ * to the PHY space itself, rather than through the switch
+ * MDIO register.
+ */
+int
+arswitch_readphy_external(device_t dev, int phy, int reg)
+{
+ int ret;
+ struct arswitch_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ ARSWITCH_LOCK(sc);
+ ret = (MDIO_READREG(device_get_parent(dev), phy, reg));
+ DPRINTF(sc, ARSWITCH_DBG_PHYIO,
+ "%s: phy=0x%08x, reg=0x%08x, ret=0x%08x\n",
+ __func__, phy, reg, ret);
+ ARSWITCH_UNLOCK(sc);
+
+ return (ret);
+}
+
+int
+arswitch_writephy_external(device_t dev, int phy, int reg, int data)
+{
+ struct arswitch_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ ARSWITCH_LOCK(sc);
+ (void) MDIO_WRITEREG(device_get_parent(dev), phy,
+ reg, data);
+ DPRINTF(sc, ARSWITCH_DBG_PHYIO,
+ "%s: phy=0x%08x, reg=0x%08x, data=0x%08x\n",
+ __func__, phy, reg, data);
+ ARSWITCH_UNLOCK(sc);
+
+ return (0);
+}
+
+/*
+ * Access PHYs integrated into the switch chip through the switch's MDIO
+ * control register.
+ */
+int
+arswitch_readphy_internal(device_t dev, int phy, int reg)
+{
+ struct arswitch_softc *sc;
+ uint32_t data = 0, ctrl;
+ int err, timeout;
+ uint32_t a;
+
+ sc = device_get_softc(dev);
+ ARSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ if (phy < 0 || phy >= 32)
+ return (ENXIO);
+ if (reg < 0 || reg >= 32)
+ return (ENXIO);
+
+ if (AR8X16_IS_SWITCH(sc, AR8327))
+ a = AR8327_REG_MDIO_CTRL;
+ else
+ a = AR8X16_REG_MDIO_CTRL;
+
+ ARSWITCH_LOCK(sc);
+ err = arswitch_writereg_msb(dev, a,
+ AR8X16_MDIO_CTRL_BUSY | AR8X16_MDIO_CTRL_MASTER_EN |
+ AR8X16_MDIO_CTRL_CMD_READ |
+ (phy << AR8X16_MDIO_CTRL_PHY_ADDR_SHIFT) |
+ (reg << AR8X16_MDIO_CTRL_REG_ADDR_SHIFT));
+ DEVERR(dev, err, "arswitch_readphy()=%d: phy=%d.%02x\n", phy, reg);
+ if (err != 0)
+ goto fail;
+ for (timeout = 100; timeout--; ) {
+ ctrl = arswitch_readreg_msb(dev, a);
+ if ((ctrl & AR8X16_MDIO_CTRL_BUSY) == 0)
+ break;
+ }
+ if (timeout < 0) {
+ DPRINTF(sc, ARSWITCH_DBG_ANY,
+ "arswitch_readphy(): phy=%d.%02x; timeout=%d\n",
+ phy, reg, timeout);
+ goto fail;
+ }
+ data = arswitch_readreg_lsb(dev, a) &
+ AR8X16_MDIO_CTRL_DATA_MASK;
+ ARSWITCH_UNLOCK(sc);
+
+ DPRINTF(sc, ARSWITCH_DBG_PHYIO,
+ "%s: phy=0x%08x, reg=0x%08x, ret=0x%08x\n",
+ __func__, phy, reg, data);
+
+ return (data);
+
+fail:
+ ARSWITCH_UNLOCK(sc);
+
+ DPRINTF(sc, ARSWITCH_DBG_PHYIO,
+ "%s: phy=0x%08x, reg=0x%08x, fail; err=%d\n",
+ __func__, phy, reg, err);
+
+ return (-1);
+}
+
+int
+arswitch_writephy_internal(device_t dev, int phy, int reg, int data)
+{
+ struct arswitch_softc *sc;
+ uint32_t ctrl;
+ int err, timeout;
+ uint32_t a;
+
+ sc = device_get_softc(dev);
+ ARSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ if (reg < 0 || reg >= 32)
+ return (ENXIO);
+
+ if (AR8X16_IS_SWITCH(sc, AR8327))
+ a = AR8327_REG_MDIO_CTRL;
+ else
+ a = AR8X16_REG_MDIO_CTRL;
+
+ ARSWITCH_LOCK(sc);
+ err = arswitch_writereg(dev, a,
+ AR8X16_MDIO_CTRL_BUSY |
+ AR8X16_MDIO_CTRL_MASTER_EN |
+ AR8X16_MDIO_CTRL_CMD_WRITE |
+ (phy << AR8X16_MDIO_CTRL_PHY_ADDR_SHIFT) |
+ (reg << AR8X16_MDIO_CTRL_REG_ADDR_SHIFT) |
+ (data & AR8X16_MDIO_CTRL_DATA_MASK));
+ if (err != 0)
+ goto out;
+ for (timeout = 100; timeout--; ) {
+ ctrl = arswitch_readreg(dev, a);
+ if ((ctrl & AR8X16_MDIO_CTRL_BUSY) == 0)
+ break;
+ }
+ if (timeout < 0)
+ err = EIO;
+
+ DPRINTF(sc, ARSWITCH_DBG_PHYIO,
+ "%s: phy=0x%08x, reg=0x%08x, data=0x%08x, err=%d\n",
+ __func__, phy, reg, data, err);
+
+out:
+ DEVERR(dev, err, "arswitch_writephy()=%d: phy=%d.%02x\n", phy, reg);
+ ARSWITCH_UNLOCK(sc);
+ return (err);
+}
diff --git a/sys/dev/etherswitch/arswitch/arswitch_phy.h b/sys/dev/etherswitch/arswitch/arswitch_phy.h
new file mode 100644
index 000000000000..f2eaf07eb942
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitch_phy.h
@@ -0,0 +1,37 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * 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 __ARSWITCH_PHY_H__
+#define __ARSWITCH_PHY_H__
+
+extern int arswitch_readphy_external(device_t dev, int phy, int reg);
+extern int arswitch_writephy_external(device_t dev, int phy, int reg, int data);
+
+extern int arswitch_readphy_internal(device_t dev, int phy, int reg);
+extern int arswitch_writephy_internal(device_t dev, int phy, int reg, int data);
+
+#endif /* __ARSWITCH_PHY_H__ */
diff --git a/sys/dev/etherswitch/arswitch/arswitch_reg.c b/sys/dev/etherswitch/arswitch/arswitch_reg.c
new file mode 100644
index 000000000000..9608b08688ef
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitch_reg.c
@@ -0,0 +1,264 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * 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/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_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/etherswitch/etherswitch.h>
+
+#include <dev/etherswitch/arswitch/arswitchreg.h>
+#include <dev/etherswitch/arswitch/arswitchvar.h>
+#include <dev/etherswitch/arswitch/arswitch_reg.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+static inline void
+arswitch_split_setpage(device_t dev, uint32_t addr, uint16_t *phy,
+ uint16_t *reg)
+{
+ struct arswitch_softc *sc = device_get_softc(dev);
+ uint16_t page;
+
+ page = (addr >> 9) & 0x1ff;
+ *phy = (addr >> 6) & 0x7;
+ *reg = (addr >> 1) & 0x1f;
+
+ if (sc->page != page) {
+ MDIO_WRITEREG(device_get_parent(dev), 0x18, 0, page);
+ DELAY(2000);
+ sc->page = page;
+ }
+}
+
+/*
+ * Read half a register. Some of the registers define control bits, and
+ * the sequence of half-word accesses matters. The register addresses
+ * are word-even (mod 4).
+ */
+static inline int
+arswitch_readreg16(device_t dev, int addr)
+{
+ uint16_t phy, reg;
+
+ arswitch_split_setpage(dev, addr, &phy, &reg);
+ return (MDIO_READREG(device_get_parent(dev), 0x10 | phy, reg));
+}
+
+/*
+ * Write half a register. See above!
+ */
+static inline int
+arswitch_writereg16(device_t dev, int addr, int data)
+{
+ uint16_t phy, reg;
+
+ arswitch_split_setpage(dev, addr, &phy, &reg);
+ return (MDIO_WRITEREG(device_get_parent(dev), 0x10 | phy, reg, data));
+}
+
+void
+arswitch_writedbg(device_t dev, int phy, uint16_t dbg_addr,
+ uint16_t dbg_data)
+{
+ (void) MDIO_WRITEREG(device_get_parent(dev), phy,
+ MII_ATH_DBG_ADDR, dbg_addr);
+ (void) MDIO_WRITEREG(device_get_parent(dev), phy,
+ MII_ATH_DBG_DATA, dbg_data);
+}
+
+void
+arswitch_writemmd(device_t dev, int phy, uint16_t dbg_addr,
+ uint16_t dbg_data)
+{
+ (void) MDIO_WRITEREG(device_get_parent(dev), phy,
+ MII_ATH_MMD_ADDR, dbg_addr);
+ (void) MDIO_WRITEREG(device_get_parent(dev), phy,
+ MII_ATH_MMD_DATA, dbg_data);
+}
+
+static uint32_t
+arswitch_reg_read32(device_t dev, int phy, int reg)
+{
+ uint16_t lo, hi;
+ lo = MDIO_READREG(device_get_parent(dev), phy, reg);
+ hi = MDIO_READREG(device_get_parent(dev), phy, reg + 1);
+
+ return (hi << 16) | lo;
+}
+
+static int
+arswitch_reg_write32(device_t dev, int phy, int reg, uint32_t value)
+{
+ struct arswitch_softc *sc;
+ int r;
+ uint16_t lo, hi;
+
+ sc = device_get_softc(dev);
+ lo = value & 0xffff;
+ hi = (uint16_t) (value >> 16);
+
+ if (sc->mii_lo_first) {
+ r = MDIO_WRITEREG(device_get_parent(dev),
+ phy, reg, lo);
+ r |= MDIO_WRITEREG(device_get_parent(dev),
+ phy, reg + 1, hi);
+ } else {
+ r = MDIO_WRITEREG(device_get_parent(dev),
+ phy, reg + 1, hi);
+ r |= MDIO_WRITEREG(device_get_parent(dev),
+ phy, reg, lo);
+ }
+
+ return r;
+}
+
+int
+arswitch_readreg(device_t dev, int addr)
+{
+ uint16_t phy, reg;
+
+ arswitch_split_setpage(dev, addr, &phy, &reg);
+ return arswitch_reg_read32(dev, 0x10 | phy, reg);
+}
+
+int
+arswitch_writereg(device_t dev, int addr, int value)
+{
+ uint16_t phy, reg;
+
+ arswitch_split_setpage(dev, addr, &phy, &reg);
+ return (arswitch_reg_write32(dev, 0x10 | phy, reg, value));
+}
+
+/*
+ * Read/write 16 bit values in the switch register space.
+ *
+ * Some of the registers are control registers (eg the MDIO
+ * data versus control space) and so need to be treated
+ * differently.
+ */
+int
+arswitch_readreg_lsb(device_t dev, int addr)
+{
+
+ return (arswitch_readreg16(dev, addr));
+}
+
+int
+arswitch_readreg_msb(device_t dev, int addr)
+{
+
+ return (arswitch_readreg16(dev, addr + 2) << 16);
+}
+
+int
+arswitch_writereg_lsb(device_t dev, int addr, int data)
+{
+
+ return (arswitch_writereg16(dev, addr, data & 0xffff));
+}
+
+int
+arswitch_writereg_msb(device_t dev, int addr, int data)
+{
+
+ return (arswitch_writereg16(dev, addr + 2, (data >> 16) & 0xffff));
+}
+
+int
+arswitch_modifyreg(device_t dev, int addr, int mask, int set)
+{
+ int value;
+ uint16_t phy, reg;
+
+ ARSWITCH_LOCK_ASSERT((struct arswitch_softc *)device_get_softc(dev),
+ MA_OWNED);
+
+ arswitch_split_setpage(dev, addr, &phy, &reg);
+
+ value = arswitch_reg_read32(dev, 0x10 | phy, reg);
+ value &= ~mask;
+ value |= set;
+ return (arswitch_reg_write32(dev, 0x10 | phy, reg, value));
+}
+
+int
+arswitch_waitreg(device_t dev, int addr, int mask, int val, int timeout)
+{
+ struct arswitch_softc *sc = device_get_softc(dev);
+ int err, v;
+ uint16_t phy, reg;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ arswitch_split_setpage(dev, addr, &phy, &reg);
+
+ err = -1;
+ while (1) {
+ v = arswitch_reg_read32(dev, 0x10 | phy, reg);
+ v &= mask;
+ if (v == val) {
+ err = 0;
+ break;
+ }
+ if (!timeout)
+ break;
+ DELAY(1);
+ timeout--;
+ }
+ if (err != 0) {
+ DPRINTF(sc, ARSWITCH_DBG_ANY,
+ "%s: waitreg failed; addr=0x%08x, mask=0x%08x, val=0x%08x\n",
+ __func__, addr, mask, val);
+ }
+ return (err);
+}
diff --git a/sys/dev/etherswitch/arswitch/arswitch_reg.h b/sys/dev/etherswitch/arswitch/arswitch_reg.h
new file mode 100644
index 000000000000..41816065794b
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitch_reg.h
@@ -0,0 +1,47 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * 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 __ARSWITCH_REG_H__
+#define __ARSWITCH_REG_H__
+
+extern void arswitch_writedbg(device_t dev, int phy, uint16_t dbg_addr,
+ uint16_t dbg_data);
+extern void arswitch_writemmd(device_t dev, int phy, uint16_t dbg_addr,
+ uint16_t dbg_data);
+
+extern int arswitch_readreg(device_t dev, int addr);
+extern int arswitch_writereg(device_t dev, int addr, int value);
+extern int arswitch_modifyreg(device_t dev, int addr, int mask, int set);
+extern int arswitch_waitreg(device_t, int, int, int, int);
+
+extern int arswitch_readreg_lsb(device_t dev, int addr);
+extern int arswitch_readreg_msb(device_t dev, int addr);
+
+extern int arswitch_writereg_lsb(device_t dev, int addr, int data);
+extern int arswitch_writereg_msb(device_t dev, int addr, int data);
+
+#endif /* __ARSWITCH_REG_H__ */
diff --git a/sys/dev/etherswitch/arswitch/arswitch_vlans.c b/sys/dev/etherswitch/arswitch/arswitch_vlans.c
new file mode 100644
index 000000000000..06111560a98e
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitch_vlans.c
@@ -0,0 +1,384 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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/lock.h>
+#include <sys/kernel.h>
+#include <sys/mutex.h>
+#include <sys/systm.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <dev/mii/mii.h>
+
+#include <dev/etherswitch/etherswitch.h>
+#include <dev/etherswitch/arswitch/arswitchreg.h>
+#include <dev/etherswitch/arswitch/arswitchvar.h>
+#include <dev/etherswitch/arswitch/arswitch_reg.h>
+#include <dev/etherswitch/arswitch/arswitch_vlans.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+/*
+ * XXX TODO: teach about the AR934x SoC switch
+ */
+
+static int
+ar8xxx_vlan_op(struct arswitch_softc *sc, uint32_t op, uint32_t vid,
+ uint32_t data)
+{
+ int err;
+
+ if (arswitch_waitreg(sc->sc_dev, AR8X16_REG_VLAN_CTRL,
+ AR8X16_VLAN_ACTIVE, 0, 5))
+ return (EBUSY);
+
+ /* Load the vlan data if needed. */
+ if (op == AR8X16_VLAN_OP_LOAD) {
+ err = arswitch_writereg(sc->sc_dev, AR8X16_REG_VLAN_DATA,
+ (data & AR8X16_VLAN_MEMBER) | AR8X16_VLAN_VALID);
+ if (err)
+ return (err);
+ }
+
+ if (vid != 0)
+ op |= ((vid & ETHERSWITCH_VID_MASK) << AR8X16_VLAN_VID_SHIFT);
+ op |= AR8X16_VLAN_ACTIVE;
+ arswitch_writereg(sc->sc_dev, AR8X16_REG_VLAN_CTRL, op);
+
+ /* Wait for command processing. */
+ if (arswitch_waitreg(sc->sc_dev, AR8X16_REG_VLAN_CTRL,
+ AR8X16_VLAN_ACTIVE, 0, 5))
+ return (EBUSY);
+
+ return (0);
+}
+
+int
+ar8xxx_flush_dot1q_vlan(struct arswitch_softc *sc)
+{
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ return (ar8xxx_vlan_op(sc, AR8X16_VLAN_OP_FLUSH, 0, 0));
+}
+
+int
+ar8xxx_purge_dot1q_vlan(struct arswitch_softc *sc, int vid)
+{
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ return (ar8xxx_vlan_op(sc, AR8X16_VLAN_OP_PURGE, vid, 0));
+}
+
+int
+ar8xxx_get_dot1q_vlan(struct arswitch_softc *sc, uint32_t *ports,
+ uint32_t *untagged_ports, int vid)
+{
+ uint32_t reg;
+ int err;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ err = ar8xxx_vlan_op(sc, AR8X16_VLAN_OP_GET, vid, 0);
+ if (err)
+ return (err);
+
+ reg = arswitch_readreg(sc->sc_dev, AR8X16_REG_VLAN_DATA);
+ if ((reg & AR8X16_VLAN_VALID) == 0) {
+ *ports = 0;
+ return (EINVAL);
+ }
+ reg &= ((1 << (sc->numphys + 1)) - 1);
+ *ports = reg;
+ *untagged_ports = reg;
+ return (0);
+}
+
+int
+ar8xxx_set_dot1q_vlan(struct arswitch_softc *sc, uint32_t ports,
+ uint32_t untagged_ports, int vid)
+{
+ int err;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ err = ar8xxx_vlan_op(sc, AR8X16_VLAN_OP_LOAD, vid, ports);
+ if (err)
+ return (err);
+ return (0);
+}
+
+int
+ar8xxx_get_port_vlan(struct arswitch_softc *sc, uint32_t *ports, int vid)
+{
+ int port;
+ uint32_t reg;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ /* For port based vlans the vlanid is the same as the port index. */
+ port = vid & ETHERSWITCH_VID_MASK;
+ reg = arswitch_readreg(sc->sc_dev, AR8X16_REG_PORT_VLAN(port));
+ *ports = (reg >> AR8X16_PORT_VLAN_DEST_PORTS_SHIFT);
+ *ports &= AR8X16_VLAN_MEMBER;
+ return (0);
+}
+
+int
+ar8xxx_set_port_vlan(struct arswitch_softc *sc, uint32_t ports, int vid)
+{
+ int err, port;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ /* For port based vlans the vlanid is the same as the port index. */
+ port = vid & ETHERSWITCH_VID_MASK;
+ err = arswitch_modifyreg(sc->sc_dev, AR8X16_REG_PORT_VLAN(port),
+ AR8X16_VLAN_MEMBER << AR8X16_PORT_VLAN_DEST_PORTS_SHIFT,
+ (ports & AR8X16_VLAN_MEMBER) << AR8X16_PORT_VLAN_DEST_PORTS_SHIFT);
+ if (err)
+ return (err);
+ return (0);
+}
+
+/*
+ * Reset vlans to default state.
+ */
+void
+ar8xxx_reset_vlans(struct arswitch_softc *sc)
+{
+ uint32_t ports;
+ int i, j;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ ARSWITCH_LOCK(sc);
+
+ /* Reset all vlan data. */
+ memset(sc->vid, 0, sizeof(sc->vid));
+
+ /* Disable the QinQ and egress filters for all ports. */
+ for (i = 0; i <= sc->numphys; i++) {
+ if (arswitch_modifyreg(sc->sc_dev, AR8X16_REG_PORT_CTRL(i),
+ 0x3 << AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_SHIFT |
+ AR8X16_PORT_CTRL_DOUBLE_TAG, 0)) {
+ ARSWITCH_UNLOCK(sc);
+ return;
+ }
+ }
+
+ if (sc->hal.arswitch_flush_dot1q_vlan(sc)) {
+ ARSWITCH_UNLOCK(sc);
+ return;
+ }
+
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ /*
+ * Reset the port based vlan settings and turn on the
+ * ingress filter for all ports.
+ */
+ ports = 0;
+ for (i = 0; i <= sc->numphys; i++)
+ arswitch_modifyreg(sc->sc_dev,
+ AR8X16_REG_PORT_VLAN(i),
+ AR8X16_PORT_VLAN_MODE_MASK |
+ AR8X16_VLAN_MEMBER <<
+ AR8X16_PORT_VLAN_DEST_PORTS_SHIFT,
+ AR8X16_PORT_VLAN_MODE_SECURE <<
+ AR8X16_PORT_VLAN_MODE_SHIFT);
+
+ /*
+ * Setup vlan 1 as PVID for all switch ports. Add all ports
+ * as members of vlan 1.
+ */
+ sc->vid[0] = 1;
+ /* Set PVID for everyone. */
+ for (i = 0; i <= sc->numphys; i++)
+ sc->hal.arswitch_vlan_set_pvid(sc, i, sc->vid[0]);
+ ports = 0;
+ for (i = 0; i <= sc->numphys; i++)
+ ports |= (1 << i);
+ sc->hal.arswitch_set_dot1q_vlan(sc, ports, sc->vid[0], sc->vid[0]);
+ sc->vid[0] |= ETHERSWITCH_VID_VALID;
+ } else if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) {
+ /* Initialize the port based vlans. */
+ for (i = 0; i <= sc->numphys; i++) {
+ sc->vid[i] = i | ETHERSWITCH_VID_VALID;
+ ports = 0;
+ for (j = 0; j <= sc->numphys; j++)
+ ports |= (1 << j);
+ arswitch_modifyreg(sc->sc_dev,
+ AR8X16_REG_PORT_VLAN(i),
+ AR8X16_PORT_VLAN_MODE_MASK |
+ AR8X16_VLAN_MEMBER <<
+ AR8X16_PORT_VLAN_DEST_PORTS_SHIFT,
+ ports << AR8X16_PORT_VLAN_DEST_PORTS_SHIFT |
+ AR8X16_PORT_VLAN_MODE_SECURE <<
+ AR8X16_PORT_VLAN_MODE_PORT_ONLY);
+ /* XXX TODO: SECURE / PORT_ONLY is wrong? */
+ }
+ } else {
+ /* Disable the ingress filter and get everyone on all vlans. */
+ for (i = 0; i <= sc->numphys; i++)
+ arswitch_modifyreg(sc->sc_dev,
+ AR8X16_REG_PORT_VLAN(i),
+ AR8X16_PORT_VLAN_MODE_MASK |
+ AR8X16_VLAN_MEMBER <<
+ AR8X16_PORT_VLAN_DEST_PORTS_SHIFT,
+ AR8X16_VLAN_MEMBER <<
+ AR8X16_PORT_VLAN_DEST_PORTS_SHIFT |
+ AR8X16_PORT_VLAN_MODE_SECURE <<
+ AR8X16_PORT_VLAN_MODE_PORT_ONLY);
+ }
+ ARSWITCH_UNLOCK(sc);
+}
+
+int
+ar8xxx_getvgroup(struct arswitch_softc *sc, etherswitch_vlangroup_t *vg)
+{
+ int err;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ if (vg->es_vlangroup > sc->info.es_nvlangroups)
+ return (EINVAL);
+
+ /* Reset the members ports. */
+ vg->es_untagged_ports = 0;
+ vg->es_member_ports = 0;
+
+ /* Not supported. */
+ vg->es_fid = 0;
+
+ /* Vlan ID. */
+ ARSWITCH_LOCK(sc);
+ vg->es_vid = sc->vid[vg->es_vlangroup];
+ if ((vg->es_vid & ETHERSWITCH_VID_VALID) == 0) {
+ ARSWITCH_UNLOCK(sc);
+ return (0);
+ }
+
+ /* Member Ports. */
+ switch (sc->vlan_mode) {
+ case ETHERSWITCH_VLAN_DOT1Q:
+ err = sc->hal.arswitch_get_dot1q_vlan(sc, &vg->es_member_ports,
+ &vg->es_untagged_ports,
+ vg->es_vid);
+ break;
+ case ETHERSWITCH_VLAN_PORT:
+ err = sc->hal.arswitch_get_port_vlan(sc, &vg->es_member_ports,
+ vg->es_vid);
+ vg->es_untagged_ports = vg->es_member_ports;
+ break;
+ default:
+ vg->es_member_ports = 0;
+ vg->es_untagged_ports = 0;
+ err = -1;
+ }
+ ARSWITCH_UNLOCK(sc);
+
+ return (err);
+}
+
+int
+ar8xxx_setvgroup(struct arswitch_softc *sc, etherswitch_vlangroup_t *vg)
+{
+ int err, vid;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ /* Check VLAN mode. */
+ if (sc->vlan_mode == 0)
+ return (EINVAL);
+
+ /*
+ * Check if we are changing the vlanid for an already used vtu entry.
+ * Then purge the entry first.
+ */
+ ARSWITCH_LOCK(sc);
+ vid = sc->vid[vg->es_vlangroup];
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q &&
+ (vid & ETHERSWITCH_VID_VALID) != 0 &&
+ (vid & ETHERSWITCH_VID_MASK) !=
+ (vg->es_vid & ETHERSWITCH_VID_MASK)) {
+ err = sc->hal.arswitch_purge_dot1q_vlan(sc, vid);
+ if (err) {
+ ARSWITCH_UNLOCK(sc);
+ return (err);
+ }
+ }
+
+ /* Vlan ID. */
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ sc->vid[vg->es_vlangroup] = vg->es_vid & ETHERSWITCH_VID_MASK;
+ /* Setting the vlanid to zero disables the vlangroup. */
+ if (sc->vid[vg->es_vlangroup] == 0) {
+ ARSWITCH_UNLOCK(sc);
+ return (0);
+ }
+ sc->vid[vg->es_vlangroup] |= ETHERSWITCH_VID_VALID;
+ vid = sc->vid[vg->es_vlangroup];
+ }
+
+ /* Member Ports. */
+ switch (sc->vlan_mode) {
+ case ETHERSWITCH_VLAN_DOT1Q:
+ err = sc->hal.arswitch_set_dot1q_vlan(sc, vg->es_member_ports,
+ vg->es_untagged_ports, vid);
+ break;
+ case ETHERSWITCH_VLAN_PORT:
+ err = sc->hal.arswitch_set_port_vlan(sc, vg->es_member_ports, vid);
+ break;
+ default:
+ err = -1;
+ }
+ ARSWITCH_UNLOCK(sc);
+ return (err);
+}
+
+int
+ar8xxx_get_pvid(struct arswitch_softc *sc, int port, int *pvid)
+{
+ uint32_t reg;
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ reg = arswitch_readreg(sc->sc_dev, AR8X16_REG_PORT_VLAN(port));
+ *pvid = reg & 0xfff;
+ return (0);
+}
+
+int
+ar8xxx_set_pvid(struct arswitch_softc *sc, int port, int pvid)
+{
+
+ ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ return (arswitch_modifyreg(sc->sc_dev,
+ AR8X16_REG_PORT_VLAN(port), 0xfff, pvid));
+}
diff --git a/sys/dev/etherswitch/arswitch/arswitch_vlans.h b/sys/dev/etherswitch/arswitch/arswitch_vlans.h
new file mode 100644
index 000000000000..39011dd8a771
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitch_vlans.h
@@ -0,0 +1,47 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * 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 __ARSWITCH_VLANS_H__
+#define __ARSWITCH_VLANS_H__
+
+void ar8xxx_reset_vlans(struct arswitch_softc *);
+int ar8xxx_getvgroup(struct arswitch_softc *, etherswitch_vlangroup_t *);
+int ar8xxx_setvgroup(struct arswitch_softc *, etherswitch_vlangroup_t *);
+int ar8xxx_get_pvid(struct arswitch_softc *, int, int *);
+int ar8xxx_set_pvid(struct arswitch_softc *, int, int);
+
+int ar8xxx_flush_dot1q_vlan(struct arswitch_softc *sc);
+int ar8xxx_purge_dot1q_vlan(struct arswitch_softc *sc, int vid);
+int ar8xxx_get_dot1q_vlan(struct arswitch_softc *sc, uint32_t *ports,
+ uint32_t *untagged_ports, int vid);
+int ar8xxx_set_dot1q_vlan(struct arswitch_softc *sc, uint32_t ports,
+ uint32_t untagged_ports, int vid);
+int ar8xxx_get_port_vlan(struct arswitch_softc *sc, uint32_t *ports, int vid);
+int ar8xxx_set_port_vlan(struct arswitch_softc *sc, uint32_t ports, int vid);
+
+#endif /* __ARSWITCH_VLANS_H__ */
diff --git a/sys/dev/etherswitch/arswitch/arswitchreg.h b/sys/dev/etherswitch/arswitch/arswitchreg.h
new file mode 100644
index 000000000000..2ec3b7012eba
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitchreg.h
@@ -0,0 +1,582 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011 Aleksandr Rybalko.
+ * All rights reserved.
+ *
+ * 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 __AR8X16_SWITCHREG_H__
+#define __AR8X16_SWITCHREG_H__
+
+/* XXX doesn't belong here; stolen shamelessly from ath_hal/ah_internal.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)
+
+/* XXX Linux define compatibility stuff */
+#define BIT(_m) (1UL << (_m))
+#define BITM(_count) ((1UL << (_count)) - 1)
+#define BITS(_shift, _count) (BITM(_count) << (_shift))
+
+/* Atheros specific MII registers */
+#define MII_ATH_MMD_ADDR 0x0d
+#define MII_ATH_MMD_DATA 0x0e
+#define MII_ATH_DBG_ADDR 0x1d
+#define MII_ATH_DBG_DATA 0x1e
+
+#define AR8X16_REG_MASK_CTRL 0x0000
+#define AR8X16_MASK_CTRL_REV_MASK 0x000000ff
+#define AR8X16_MASK_CTRL_VER_MASK 0x0000ff00
+#define AR8X16_MASK_CTRL_VER_SHIFT 8
+#define AR8X16_MASK_CTRL_SOFT_RESET (1U << 31)
+
+#define AR8X16_REG_MODE 0x0008
+/* DIR-615 E4 U-Boot */
+#define AR8X16_MODE_DIR_615_UBOOT 0x8d1003e0
+/* From Ubiquiti RSPRO */
+#define AR8X16_MODE_RGMII_PORT4_ISO 0x81461bea
+#define AR8X16_MODE_RGMII_PORT4_SWITCH 0x01261be2
+/* AVM Fritz!Box 7390 */
+#define AR8X16_MODE_GMII 0x010e5b71
+/* from avm_cpmac/linux_ar_reg.h */
+#define AR8X16_MODE_RESERVED 0x000e1b20
+#define AR8X16_MODE_MAC0_GMII_EN (1u << 0)
+#define AR8X16_MODE_MAC0_RGMII_EN (1u << 1)
+#define AR8X16_MODE_PHY4_GMII_EN (1u << 2)
+#define AR8X16_MODE_PHY4_RGMII_EN (1u << 3)
+#define AR8X16_MODE_MAC0_MAC_MODE (1u << 4)
+#define AR8X16_MODE_RGMII_RXCLK_DELAY_EN (1u << 6)
+#define AR8X16_MODE_RGMII_TXCLK_DELAY_EN (1u << 7)
+#define AR8X16_MODE_MAC5_MAC_MODE (1u << 14)
+#define AR8X16_MODE_MAC5_PHY_MODE (1u << 15)
+#define AR8X16_MODE_TXDELAY_S0 (1u << 21)
+#define AR8X16_MODE_TXDELAY_S1 (1u << 22)
+#define AR8X16_MODE_RXDELAY_S0 (1u << 23)
+#define AR8X16_MODE_LED_OPEN_EN (1u << 24)
+#define AR8X16_MODE_SPI_EN (1u << 25)
+#define AR8X16_MODE_RXDELAY_S1 (1u << 26)
+#define AR8X16_MODE_POWER_ON_SEL (1u << 31)
+
+#define AR8X16_REG_ISR 0x0010
+#define AR8X16_REG_IMR 0x0014
+
+#define AR8X16_REG_SW_MAC_ADDR0 0x0020
+#define AR8X16_REG_SW_MAC_ADDR0_BYTE4 BITS(8, 8)
+#define AR8X16_REG_SW_MAC_ADDR0_BYTE4_S 8
+#define AR8X16_REG_SW_MAC_ADDR0_BYTE5 BITS(0, 8)
+#define AR8X16_REG_SW_MAC_ADDR0_BYTE5_S 0
+
+#define AR8X16_REG_SW_MAC_ADDR1 0x0024
+#define AR8X16_REG_SW_MAC_ADDR1_BYTE0 BITS(24, 8)
+#define AR8X16_REG_SW_MAC_ADDR1_BYTE0_S 24
+#define AR8X16_REG_SW_MAC_ADDR1_BYTE1 BITS(16, 8)
+#define AR8X16_REG_SW_MAC_ADDR1_BYTE1_S 16
+#define AR8X16_REG_SW_MAC_ADDR1_BYTE2 BITS(8, 8)
+#define AR8X16_REG_SW_MAC_ADDR1_BYTE2_S 8
+#define AR8X16_REG_SW_MAC_ADDR1_BYTE3 BITS(0, 8)
+#define AR8X16_REG_SW_MAC_ADDR1_BYTE3_S 0
+
+#define AR8X16_REG_FLOOD_MASK 0x002c
+#define AR8X16_FLOOD_MASK_BCAST_TO_CPU (1 << 26)
+
+#define AR8X16_REG_GLOBAL_CTRL 0x0030
+#define AR8216_GLOBAL_CTRL_MTU_MASK 0x00000fff
+#define AR8216_GLOBAL_CTRL_MTU_MASK_S 0
+#define AR8316_GLOBAL_CTRL_MTU_MASK 0x00007fff
+#define AR8316_GLOBAL_CTRL_MTU_MASK_S 0
+#define AR8236_GLOBAL_CTRL_MTU_MASK 0x00007fff
+#define AR8236_GLOBAL_CTRL_MTU_MASK_S 0
+
+#define AR8X16_REG_VLAN_CTRL 0x0040
+#define AR8X16_VLAN_OP 0x00000007
+#define AR8X16_VLAN_OP_NOOP 0x0
+#define AR8X16_VLAN_OP_FLUSH 0x1
+#define AR8X16_VLAN_OP_LOAD 0x2
+#define AR8X16_VLAN_OP_PURGE 0x3
+#define AR8X16_VLAN_OP_REMOVE_PORT 0x4
+#define AR8X16_VLAN_OP_GET_NEXT 0x5
+#define AR8X16_VLAN_OP_GET 0x6
+#define AR8X16_VLAN_ACTIVE (1 << 3)
+#define AR8X16_VLAN_FULL (1 << 4)
+#define AR8X16_VLAN_PORT 0x00000f00
+#define AR8X16_VLAN_PORT_SHIFT 8
+#define AR8X16_VLAN_VID 0x0fff0000
+#define AR8X16_VLAN_VID_SHIFT 16
+#define AR8X16_VLAN_PRIO 0x70000000
+#define AR8X16_VLAN_PRIO_SHIFT 28
+#define AR8X16_VLAN_PRIO_EN (1U << 31)
+
+#define AR8X16_REG_VLAN_DATA 0x0044
+#define AR8X16_VLAN_MEMBER 0x0000003f
+#define AR8X16_VLAN_VALID (1 << 11)
+
+#define AR8216_REG_ATU 0x0050
+#define AR8216_ATU_OP BITS(0, 3)
+#define AR8216_ATU_OP_NOOP 0x0
+#define AR8216_ATU_OP_FLUSH 0x1
+#define AR8216_ATU_OP_LOAD 0x2
+#define AR8216_ATU_OP_PURGE 0x3
+#define AR8216_ATU_OP_FLUSH_LOCKED 0x4
+#define AR8216_ATU_OP_FLUSH_UNICAST 0x5
+#define AR8216_ATU_OP_GET_NEXT 0x6
+#define AR8216_ATU_ACTIVE BIT(3)
+#define AR8216_ATU_PORT_NUM BITS(8, 4)
+#define AR8216_ATU_PORT_NUM_S 8
+#define AR8216_ATU_FULL_VIO BIT(12)
+#define AR8216_ATU_ADDR5 BITS(16, 8)
+#define AR8216_ATU_ADDR5_S 16
+#define AR8216_ATU_ADDR4 BITS(24, 8)
+#define AR8216_ATU_ADDR4_S 24
+
+#define AR8216_REG_ATU_DATA 0x0054
+#define AR8216_ATU_ADDR3 BITS(0, 8)
+#define AR8216_ATU_ADDR3_S 0
+#define AR8216_ATU_ADDR2 BITS(8, 8)
+#define AR8216_ATU_ADDR2_S 8
+#define AR8216_ATU_ADDR1 BITS(16, 8)
+#define AR8216_ATU_ADDR1_S 16
+#define AR8216_ATU_ADDR0 BITS(24, 8)
+#define AR8216_ATU_ADDR0_S 24
+
+#define AR8216_REG_ATU_CTRL2 0x0058
+#define AR8216_ATU_CTRL2_DESPORT BITS(0, 5)
+#define AR8216_ATU_CTRL2_DESPORT_S 0
+#define AR934X_ATU_CROSS_STATE_PORT_EN BIT(8)
+#define AR934X_ATU_HASH_HIGH_ADDR BIT(9) /* Used for CPU_FUNC (get_next_valid) */
+#define AR8216_ATU_CTRL2_AT_PRIORITY BITS(10, 2)
+#define AR8216_ATU_CTRL2_AT_PRIORITY_EN BIT(12)
+#define AR8216_ATU_CTRL2_MIRROR_EN BIT(13)
+#define AR8216_ATU_CTRL2_SA_DROP_EN BIT(14)
+#define AR934X_ATU_CTRL2_MAC_CLONE BIT(15)
+#define AR8216_ATU_CTRL2_AT_STATUS BITS(16, 4)
+#define AR8216_ATU_CTRL2_AT_STATUS_S 16
+/*
+ * For at least the AR9340 -
+ * 0: empty
+ * 1-7: dynamic, valid
+ * 15: static, won't be aged
+ */
+#define AR8216_ATU_CTRL2_VLAN_LEAKY_EN BIT(24)
+/*
+ * This defines whether this MAC will leak between VLANs;
+ * controlled by ARL_UNI_LEAKY_EN and ARL_MULTI_LEAKY_EN.
+ */
+#define AR8216_ATU_CTRL2_REDIRECT2CPU BIT(25)
+#define AR8216_ATU_CTRL2_COPY2CPU BIT(26)
+
+#define AR8216_REG_ATU_CTRL 0x005C
+#define AR8216_ATU_CTRL_AGE_TIME BITS(0, 16)
+#define AR8216_ATU_CTRL_AGE_TIME_S 0
+#define AR8216_ATU_CTRL_AGE_EN BIT(17)
+#define AR8216_ATU_CTRL_LEARN_CHANGE BIT(18)
+#define AR8216_ATU_CTRL_ARP_EN BIT(20)
+
+#define AR8X16_REG_IP_PRIORITY_1 0x0060
+#define AR8X16_REG_IP_PRIORITY_2 0x0064
+#define AR8X16_REG_IP_PRIORITY_3 0x0068
+#define AR8X16_REG_IP_PRIORITY_4 0x006C
+
+#define AR8X16_REG_TAG_PRIO 0x0070
+
+#define AR8X16_REG_SERVICE_TAG 0x0074
+#define AR8X16_SERVICE_TAG_MASK 0x0000ffff
+
+#define AR8X16_REG_CPU_PORT 0x0078
+#define AR8X16_MIRROR_PORT_SHIFT 4
+#define AR8X16_MIRROR_PORT_MASK (0xf << AR8X16_MIRROR_PORT_SHIFT)
+#define AR8X16_CPU_MIRROR_PORT(_p) ((_p) << AR8X16_MIRROR_PORT_SHIFT)
+#define AR8X16_CPU_MIRROR_DIS AR8X16_CPU_MIRROR_PORT(0xf)
+#define AR8X16_CPU_PORT_EN (1 << 8)
+
+#define AR8X16_REG_MIB_FUNC0 0x0080
+#define AR8X16_MIB_TIMER_MASK 0x0000ffff
+#define AR8X16_MIB_AT_HALF_EN (1 << 16)
+#define AR8X16_MIB_BUSY (1 << 17)
+#define AR8X16_MIB_FUNC_SHIFT 24
+#define AR8X16_MIB_FUNC_NO_OP 0x0
+#define AR8X16_MIB_FUNC_FLUSH 0x1
+#define AR8X16_MIB_FUNC_CAPTURE 0x3
+#define AR8X16_MIB_FUNC_XXX (1 << 30) /* 0x40000000 */
+
+#define AR934X_MIB_ENABLE (1 << 30)
+
+#define AR8X16_REG_MDIO_HIGH_ADDR 0x0094
+
+#define AR8X16_REG_MDIO_CTRL 0x0098
+#define AR8X16_MDIO_CTRL_DATA_MASK 0x0000ffff
+#define AR8X16_MDIO_CTRL_REG_ADDR_SHIFT 16
+#define AR8X16_MDIO_CTRL_PHY_ADDR_SHIFT 21
+#define AR8X16_MDIO_CTRL_CMD_WRITE 0
+#define AR8X16_MDIO_CTRL_CMD_READ (1 << 27)
+#define AR8X16_MDIO_CTRL_MASTER_EN (1 << 30)
+#define AR8X16_MDIO_CTRL_BUSY (1U << 31)
+
+#define AR8X16_REG_PORT_BASE(_p) (0x0100 + (_p) * 0x0100)
+
+#define AR8X16_REG_PORT_STS(_p) (AR8X16_REG_PORT_BASE((_p)) + 0x0000)
+#define AR8X16_PORT_STS_SPEED_MASK 0x00000003
+#define AR8X16_PORT_STS_SPEED_10 0
+#define AR8X16_PORT_STS_SPEED_100 1
+#define AR8X16_PORT_STS_SPEED_1000 2
+#define AR8X16_PORT_STS_TXMAC (1 << 2)
+#define AR8X16_PORT_STS_RXMAC (1 << 3)
+#define AR8X16_PORT_STS_TXFLOW (1 << 4)
+#define AR8X16_PORT_STS_RXFLOW (1 << 5)
+#define AR8X16_PORT_STS_DUPLEX (1 << 6)
+#define AR8X16_PORT_STS_LINK_UP (1 << 8)
+#define AR8X16_PORT_STS_LINK_AUTO (1 << 9)
+#define AR8X16_PORT_STS_LINK_PAUSE (1 << 10)
+
+#define AR8X16_REG_PORT_CTRL(_p) (AR8X16_REG_PORT_BASE((_p)) + 0x0004)
+#define AR8X16_PORT_CTRL_STATE_MASK 0x00000007
+#define AR8X16_PORT_CTRL_STATE_DISABLED 0
+#define AR8X16_PORT_CTRL_STATE_BLOCK 1
+#define AR8X16_PORT_CTRL_STATE_LISTEN 2
+#define AR8X16_PORT_CTRL_STATE_LEARN 3
+#define AR8X16_PORT_CTRL_STATE_FORWARD 4
+#define AR8X16_PORT_CTRL_LEARN_LOCK (1 << 7)
+#define AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_SHIFT 8
+#define AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_KEEP 0
+#define AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_STRIP 1
+#define AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_ADD 2
+#define AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_DOUBLE_TAG 3
+#define AR8X16_PORT_CTRL_IGMP_SNOOP (1 << 10)
+#define AR8X16_PORT_CTRL_HEADER (1 << 11)
+#define AR8X16_PORT_CTRL_MAC_LOOP (1 << 12)
+#define AR8X16_PORT_CTRL_SINGLE_VLAN (1 << 13)
+#define AR8X16_PORT_CTRL_LEARN (1 << 14)
+#define AR8X16_PORT_CTRL_DOUBLE_TAG (1 << 15)
+#define AR8X16_PORT_CTRL_MIRROR_TX (1 << 16)
+#define AR8X16_PORT_CTRL_MIRROR_RX (1 << 17)
+
+#define AR8X16_REG_PORT_VLAN(_p) (AR8X16_REG_PORT_BASE((_p)) + 0x0008)
+
+#define AR8X16_PORT_VLAN_DEFAULT_ID_SHIFT 0
+#define AR8X16_PORT_VLAN_DEST_PORTS_SHIFT 16
+#define AR8X16_PORT_VLAN_MODE_MASK 0xc0000000
+#define AR8X16_PORT_VLAN_MODE_SHIFT 30
+#define AR8X16_PORT_VLAN_MODE_PORT_ONLY 0
+#define AR8X16_PORT_VLAN_MODE_PORT_FALLBACK 1
+#define AR8X16_PORT_VLAN_MODE_VLAN_ONLY 2
+#define AR8X16_PORT_VLAN_MODE_SECURE 3
+
+#define AR8X16_REG_PORT_RATE_LIM(_p) (AR8X16_REG_PORT_BASE((_p)) + 0x000c)
+#define AR8X16_PORT_RATE_LIM_128KB 0
+#define AR8X16_PORT_RATE_LIM_256KB 1
+#define AR8X16_PORT_RATE_LIM_512KB 2
+#define AR8X16_PORT_RATE_LIM_1MB 3
+#define AR8X16_PORT_RATE_LIM_2MB 4
+#define AR8X16_PORT_RATE_LIM_4MB 5
+#define AR8X16_PORT_RATE_LIM_8MB 6
+#define AR8X16_PORT_RATE_LIM_16MB 7
+#define AR8X16_PORT_RATE_LIM_32MB 8
+#define AR8X16_PORT_RATE_LIM_64MB 9
+#define AR8X16_PORT_RATE_LIM_IN_EN (1 << 24)
+#define AR8X16_PORT_RATE_LIM_OUT_EN (1 << 23)
+#define AR8X16_PORT_RATE_LIM_IN_MASK 0x000f0000
+#define AR8X16_PORT_RATE_LIM_IN_SHIFT 16
+#define AR8X16_PORT_RATE_LIM_OUT_MASK 0x0000000f
+#define AR8X16_PORT_RATE_LIM_OUT_SHIFT 0
+
+#define AR8X16_REG_PORT_PRIORITY(_p) (AR8X16_REG_PORT_BASE((_p)) + 0x0010)
+
+#define AR8X16_REG_STATS_BASE(_p) (0x20000 + (_p) * 0x100)
+
+#define AR8X16_STATS_RXBROAD 0x0000
+#define AR8X16_STATS_RXPAUSE 0x0004
+#define AR8X16_STATS_RXMULTI 0x0008
+#define AR8X16_STATS_RXFCSERR 0x000c
+#define AR8X16_STATS_RXALIGNERR 0x0010
+#define AR8X16_STATS_RXRUNT 0x0014
+#define AR8X16_STATS_RXFRAGMENT 0x0018
+#define AR8X16_STATS_RX64BYTE 0x001c
+#define AR8X16_STATS_RX128BYTE 0x0020
+#define AR8X16_STATS_RX256BYTE 0x0024
+#define AR8X16_STATS_RX512BYTE 0x0028
+#define AR8X16_STATS_RX1024BYTE 0x002c
+#define AR8X16_STATS_RX1518BYTE 0x0030
+#define AR8X16_STATS_RXMAXBYTE 0x0034
+#define AR8X16_STATS_RXTOOLONG 0x0038
+#define AR8X16_STATS_RXGOODBYTE 0x003c
+#define AR8X16_STATS_RXBADBYTE 0x0044
+#define AR8X16_STATS_RXOVERFLOW 0x004c
+#define AR8X16_STATS_FILTERED 0x0050
+#define AR8X16_STATS_TXBROAD 0x0054
+#define AR8X16_STATS_TXPAUSE 0x0058
+#define AR8X16_STATS_TXMULTI 0x005c
+#define AR8X16_STATS_TXUNDERRUN 0x0060
+#define AR8X16_STATS_TX64BYTE 0x0064
+#define AR8X16_STATS_TX128BYTE 0x0068
+#define AR8X16_STATS_TX256BYTE 0x006c
+#define AR8X16_STATS_TX512BYTE 0x0070
+#define AR8X16_STATS_TX1024BYTE 0x0074
+#define AR8X16_STATS_TX1518BYTE 0x0078
+#define AR8X16_STATS_TXMAXBYTE 0x007c
+#define AR8X16_STATS_TXOVERSIZE 0x0080
+#define AR8X16_STATS_TXBYTE 0x0084
+#define AR8X16_STATS_TXCOLLISION 0x008c
+#define AR8X16_STATS_TXABORTCOL 0x0090
+#define AR8X16_STATS_TXMULTICOL 0x0094
+#define AR8X16_STATS_TXSINGLECOL 0x0098
+#define AR8X16_STATS_TXEXCDEFER 0x009c
+#define AR8X16_STATS_TXDEFER 0x00a0
+#define AR8X16_STATS_TXLATECOL 0x00a4
+
+#define AR8X16_PORT_CPU 0
+#define AR8X16_NUM_PORTS 6
+#define AR8X16_NUM_PHYS 5
+#define AR8X16_MAGIC 0xc000050e
+
+#define AR8X16_PHY_ID1 0x004d
+#define AR8X16_PHY_ID2 0xd041
+
+#define AR8X16_PORT_MASK(_port) (1 << (_port))
+#define AR8X16_PORT_MASK_ALL ((1<<AR8X16_NUM_PORTS)-1)
+#define AR8X16_PORT_MASK_BUT(_port) (AR8X16_PORT_MASK_ALL & ~(1 << (_port)))
+
+#define AR8X16_MAX_VLANS 16
+
+/*
+ * AR8327 specific registers
+ */
+#define AR8327_NUM_PORTS 7
+#define AR8327_NUM_PHYS 5
+#define AR8327_PORTS_ALL 0x7f
+
+#define AR8327_PORT_GMAC0 0
+#define AR8327_PORT_GMAC6 6
+
+#define AR8327_REG_MASK 0x000
+
+#define AR8327_REG_PAD0_MODE 0x004
+#define AR8327_REG_PAD5_MODE 0x008
+#define AR8327_REG_PAD6_MODE 0x00c
+
+#define AR8327_PAD_MAC_MII_RXCLK_SEL (1 << 0)
+#define AR8327_PAD_MAC_MII_TXCLK_SEL (1 << 1)
+#define AR8327_PAD_MAC_MII_EN (1 << 2)
+#define AR8327_PAD_MAC_GMII_RXCLK_SEL (1 << 4)
+#define AR8327_PAD_MAC_GMII_TXCLK_SEL (1 << 5)
+#define AR8327_PAD_MAC_GMII_EN (1 << 6)
+#define AR8327_PAD_SGMII_EN (1 << 7)
+#define AR8327_PAD_PHY_MII_RXCLK_SEL (1 << 8)
+#define AR8327_PAD_PHY_MII_TXCLK_SEL (1 << 9)
+#define AR8327_PAD_PHY_MII_EN (1 << 10)
+#define AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL (1 << 11)
+#define AR8327_PAD_PHY_GMII_RXCLK_SEL (1 << 12)
+#define AR8327_PAD_PHY_GMII_TXCLK_SEL (1 << 13)
+#define AR8327_PAD_PHY_GMII_EN (1 << 14)
+#define AR8327_PAD_PHYX_GMII_EN (1 << 16)
+#define AR8327_PAD_PHYX_RGMII_EN (1 << 17)
+#define AR8327_PAD_PHYX_MII_EN (1 << 18)
+#define AR8327_PAD_SGMII_DELAY_EN (1 << 19)
+#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL BITS(20, 2)
+#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S 20
+#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL BITS(22, 2)
+#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S 22
+#define AR8327_PAD_RGMII_RXCLK_DELAY_EN (1 << 24)
+#define AR8327_PAD_RGMII_TXCLK_DELAY_EN (1 << 25)
+#define AR8327_PAD_RGMII_EN (1 << 26)
+
+#define AR8327_REG_POWER_ON_STRIP 0x010
+#define AR8327_POWER_ON_STRIP_POWER_ON_SEL (1U << 31)
+#define AR8327_POWER_ON_STRIP_LED_OPEN_EN (1 << 24)
+#define AR8327_POWER_ON_STRIP_SERDES_AEN (1 << 7)
+
+#define AR8327_REG_INT_STATUS0 0x020
+#define AR8327_INT0_VT_DONE (1 << 20)
+
+#define AR8327_REG_INT_STATUS1 0x024
+#define AR8327_REG_INT_MASK0 0x028
+#define AR8327_REG_INT_MASK1 0x02c
+
+#define AR8327_REG_MODULE_EN 0x030
+#define AR8327_MODULE_EN_MIB (1 << 0)
+
+#define AR8327_REG_MIB_FUNC 0x034
+#define AR8327_MIB_CPU_KEEP (1 << 20)
+
+#define AR8327_REG_MDIO_CTRL 0x03c
+
+#define AR8327_REG_SERVICE_TAG 0x048
+#define AR8327_REG_LED_CTRL0 0x050
+#define AR8327_REG_LED_CTRL1 0x054
+#define AR8327_REG_LED_CTRL2 0x058
+#define AR8327_REG_LED_CTRL3 0x05c
+#define AR8327_REG_MAC_ADDR0 0x060
+#define AR8327_REG_MAC_ADDR1 0x064
+
+#define AR8327_REG_MAX_FRAME_SIZE 0x078
+#define AR8327_MAX_FRAME_SIZE_MTU BITS(0, 14)
+
+#define AR8327_REG_PORT_STATUS(_i) (0x07c + (_i) * 4)
+
+#define AR8327_REG_HEADER_CTRL 0x098
+#define AR8327_REG_PORT_HEADER(_i) (0x09c + (_i) * 4)
+
+#define AR8327_REG_SGMII_CTRL 0x0e0
+#define AR8327_SGMII_CTRL_EN_PLL (1 << 1)
+#define AR8327_SGMII_CTRL_EN_RX (1 << 2)
+#define AR8327_SGMII_CTRL_EN_TX (1 << 3)
+
+#define AR8327_REG_EEE_CTRL 0x100
+#define AR8327_EEE_CTRL_DISABLE_PHY(_i) BIT(4 + (_i) * 2)
+
+#define AR8327_REG_PORT_VLAN0(_i) (0x420 + (_i) * 0x8)
+#define AR8327_PORT_VLAN0_DEF_SVID BITS(0, 12)
+#define AR8327_PORT_VLAN0_DEF_SVID_S 0
+#define AR8327_PORT_VLAN0_DEF_CVID BITS(16, 12)
+#define AR8327_PORT_VLAN0_DEF_CVID_S 16
+
+#define AR8327_REG_PORT_VLAN1(_i) (0x424 + (_i) * 0x8)
+#define AR8327_PORT_VLAN1_PORT_VLAN_PROP (1 << 6)
+#define AR8327_PORT_VLAN1_OUT_MODE BITS(12, 2)
+#define AR8327_PORT_VLAN1_OUT_MODE_S 12
+#define AR8327_PORT_VLAN1_OUT_MODE_UNMOD 0
+#define AR8327_PORT_VLAN1_OUT_MODE_UNTAG 1
+#define AR8327_PORT_VLAN1_OUT_MODE_TAG 2
+#define AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH 3
+
+#define AR8327_REG_ATU_DATA0 0x600
+#define AR8327_ATU_DATA0_MAC_ADDR3 BITS(0, 8)
+#define AR8327_ATU_DATA0_MAC_ADDR3_S 0
+#define AR8327_ATU_DATA0_MAC_ADDR2 BITS(8, 8)
+#define AR8327_ATU_DATA0_MAC_ADDR2_S 8
+#define AR8327_ATU_DATA0_MAC_ADDR1 BITS(16, 8)
+#define AR8327_ATU_DATA0_MAC_ADDR1_S 16
+#define AR8327_ATU_DATA0_MAC_ADDR0 BITS(24, 8)
+#define AR8327_ATU_DATA0_MAC_ADDR0_S 24
+
+#define AR8327_REG_ATU_DATA1 0x604
+#define AR8327_ATU_DATA1_MAC_ADDR4 BITS(0, 8)
+#define AR8327_ATU_DATA1_MAC_ADDR4_S 0
+#define AR8327_ATU_DATA1_MAC_ADDR5 BITS(8, 8)
+#define AR8327_ATU_DATA1_MAC_ADDR5_S 8
+#define AR8327_ATU_DATA1_DEST_PORT BITS(16, 7)
+#define AR8327_ATU_DATA1_DEST_PORT_S 16
+#define AR8327_ATU_DATA1_CROSS_PORT_STATE_EN BIT(23)
+#define AR8327_ATU_DATA1_PRI BITS(24, 3)
+#define AR8327_ATU_DATA1_SVL_ENTRY BIT(27)
+#define AR8327_ATU_DATA1_PRI_OVER_EN BIT(28)
+#define AR8327_ATU_DATA1_MIRROR_EN BIT(29)
+#define AR8327_ATU_DATA1_SA_DROP_EN BIT(30)
+#define AR8327_ATU_DATA1_HASH_HIGH_ADDR BIT(31)
+
+#define AR8327_REG_ATU_DATA2 0x608
+#define AR8327_ATU_FUNC_DATA2_STATUS BITS(0, 4)
+#define AR8327_ATU_FUNC_DATA2_STATUS_S 0
+#define AR8327_ATU_FUNC_DATA2_VLAN_LEAKY_EN BIT(4)
+#define AR8327_ATU_FUNC_DATA2_REDIRECT_TO_CPU BIT(5)
+#define AR8327_ATU_FUNC_DATA2_COPY_TO_CPU BIT(6)
+#define AR8327_ATU_FUNC_DATA2_SHORT_LOOP BIT(7)
+#define AR8327_ATU_FUNC_DATA2_ATU_VID BITS(8, 12)
+#define AR8327_ATU_FUNC_DATA2_ATU_VID_S 8
+
+#define AR8327_REG_ATU_FUNC 0x60c
+#define AR8327_ATU_FUNC_OP BITS(0, 4)
+#define AR8327_ATU_FUNC_OP_NOOP 0x0
+#define AR8327_ATU_FUNC_OP_FLUSH 0x1
+#define AR8327_ATU_FUNC_OP_LOAD 0x2
+#define AR8327_ATU_FUNC_OP_PURGE 0x3
+#define AR8327_ATU_FUNC_OP_FLUSH_LOCKED 0x4
+#define AR8327_ATU_FUNC_OP_FLUSH_UNICAST 0x5
+#define AR8327_ATU_FUNC_OP_GET_NEXT 0x6
+#define AR8327_ATU_FUNC_OP_SEARCH_MAC 0x7
+#define AR8327_ATU_FUNC_OP_CHANGE_TRUNK 0x8
+#define AR8327_ATU_FUNC_FLUSH_STATIC_EN BIT(4)
+#define AR8327_ATU_FUNC_ENTRY_TYPE BIT(5)
+#define AR8327_ATU_FUNC_PORT_NUM BITS(8, 4)
+#define AR8327_ATU_FUNC_PORT_NUM_S 8
+#define AR8327_ATU_FUNC_FULL_VIOLATION BIT(12)
+#define AR8327_ATU_FUNC_MULTI_EN BIT(13) /* for GET_NEXT */
+#define AR8327_ATU_FUNC_PORT_EN BIT(14) /* for GET_NEXT */
+#define AR8327_ATU_FUNC_VID_EN BIT(15) /* for GET_NEXT */
+#define AR8327_ATU_FUNC_ATU_INDEX BITS(16, 5)
+#define AR8327_ATU_FUNC_ATU_INDEX_S 16
+#define AR8327_ATU_FUNC_TRUNK_PORT_NUM BITS(22, 3) /* for CHANGE_TRUNK */
+#define AR8327_ATU_FUNC_TRUNK_PORT_NUM_S 22
+#define AR8327_ATU_FUNC_BUSY BIT(31)
+
+#define AR8327_REG_VTU_FUNC0 0x0610
+#define AR8327_VTU_FUNC0_EG_MODE BITS(4, 14)
+#define AR8327_VTU_FUNC0_EG_MODE_S(_i) (4 + (_i) * 2)
+#define AR8327_VTU_FUNC0_EG_MODE_KEEP 0
+#define AR8327_VTU_FUNC0_EG_MODE_UNTAG 1
+#define AR8327_VTU_FUNC0_EG_MODE_TAG 2
+#define AR8327_VTU_FUNC0_EG_MODE_NOT 3
+#define AR8327_VTU_FUNC0_IVL (1 << 19)
+#define AR8327_VTU_FUNC0_VALID (1 << 20)
+
+#define AR8327_REG_VTU_FUNC1 0x0614
+#define AR8327_VTU_FUNC1_OP BITS(0, 3)
+#define AR8327_VTU_FUNC1_OP_NOOP 0
+#define AR8327_VTU_FUNC1_OP_FLUSH 1
+#define AR8327_VTU_FUNC1_OP_LOAD 2
+#define AR8327_VTU_FUNC1_OP_PURGE 3
+#define AR8327_VTU_FUNC1_OP_REMOVE_PORT 4
+#define AR8327_VTU_FUNC1_OP_GET_NEXT 5
+#define AR8327_VTU_FUNC1_OP_GET_ONE 6
+#define AR8327_VTU_FUNC1_FULL (1 << 4)
+#define AR8327_VTU_FUNC1_PORT (1 << 8, 4)
+#define AR8327_VTU_FUNC1_PORT_S 8
+#define AR8327_VTU_FUNC1_VID (1 << 16, 12)
+#define AR8327_VTU_FUNC1_VID_S 16
+#define AR8327_VTU_FUNC1_BUSY (1U << 31)
+
+#define AR8327_REG_FWD_CTRL0 0x620
+#define AR8327_FWD_CTRL0_CPU_PORT_EN (1 << 10)
+#define AR8327_FWD_CTRL0_MIRROR_PORT BITS(4, 4)
+#define AR8327_FWD_CTRL0_MIRROR_PORT_S 4
+
+#define AR8327_REG_FWD_CTRL1 0x624
+#define AR8327_FWD_CTRL1_UC_FLOOD BITS(0, 7)
+#define AR8327_FWD_CTRL1_UC_FLOOD_S 0
+#define AR8327_FWD_CTRL1_MC_FLOOD BITS(8, 7)
+#define AR8327_FWD_CTRL1_MC_FLOOD_S 8
+#define AR8327_FWD_CTRL1_BC_FLOOD BITS(16, 7)
+#define AR8327_FWD_CTRL1_BC_FLOOD_S 16
+#define AR8327_FWD_CTRL1_IGMP BITS(24, 7)
+#define AR8327_FWD_CTRL1_IGMP_S 24
+
+#define AR8327_REG_PORT_LOOKUP(_i) (0x660 + (_i) * 0xc)
+#define AR8327_PORT_LOOKUP_MEMBER BITS(0, 7)
+#define AR8327_PORT_LOOKUP_IN_MODE BITS(8, 2)
+#define AR8327_PORT_LOOKUP_IN_MODE_S 8
+#define AR8327_PORT_LOOKUP_STATE BITS(16, 3)
+#define AR8327_PORT_LOOKUP_STATE_S 16
+#define AR8327_PORT_LOOKUP_LEARN (1 << 20)
+#define AR8327_PORT_LOOKUP_ING_MIRROR_EN (1 << 25)
+
+#define AR8327_REG_PORT_PRIO(_i) (0x664 + (_i) * 0xc)
+
+#define AR8327_REG_PORT_HOL_CTRL1(_i) (0x974 + (_i) * 0x8)
+#define AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN (1 << 16)
+
+#define AR8327_REG_PORT_STATS_BASE(_i) (0x1000 + (_i) * 0x100)
+
+#endif /* __AR8X16_SWITCHREG_H__ */
diff --git a/sys/dev/etherswitch/arswitch/arswitchvar.h b/sys/dev/etherswitch/arswitch/arswitchvar.h
new file mode 100644
index 000000000000..30d5680c6c77
--- /dev/null
+++ b/sys/dev/etherswitch/arswitch/arswitchvar.h
@@ -0,0 +1,185 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * 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 __ARSWITCHVAR_H__
+#define __ARSWITCHVAR_H__
+
+typedef enum {
+ AR8X16_SWITCH_NONE,
+ AR8X16_SWITCH_AR8216,
+ AR8X16_SWITCH_AR8226,
+ AR8X16_SWITCH_AR8316,
+ AR8X16_SWITCH_AR8327,
+ AR8X16_SWITCH_AR8337,
+} ar8x16_switch_type;
+
+/*
+ * XXX TODO: start using this where required
+ */
+#define AR8X16_IS_SWITCH(_sc, _type) \
+ (!!((_sc)->sc_switchtype == AR8X16_SWITCH_ ## _type))
+
+#define ARSWITCH_NUM_PORTS MAX(AR8327_NUM_PORTS, AR8X16_NUM_PORTS)
+#define ARSWITCH_NUM_PHYS MAX(AR8327_NUM_PHYS, AR8X16_NUM_PHYS)
+
+#define ARSWITCH_NUM_LEDS 3
+
+struct arswitch_dev_led {
+ struct arswitch_softc *sc;
+ struct cdev *led;
+ int phy;
+ int lednum;
+};
+
+struct arswitch_softc {
+ struct mtx sc_mtx; /* serialize access to softc */
+ device_t sc_dev;
+ int phy4cpu; /* PHY4 is connected to the CPU */
+ int numphys; /* PHYs we manage */
+ int is_rgmii; /* PHY mode is RGMII (XXX which PHY?) */
+ int is_gmii; /* PHY mode is GMII (XXX which PHY?) */
+ int is_mii; /* PHY mode is MII (XXX which PHY?) */
+ int page;
+ int chip_ver;
+ int chip_rev;
+ int mii_lo_first; /* Send low data DWORD before high */
+ ar8x16_switch_type sc_switchtype;
+ /* should be the max of both pre-AR8327 and AR8327 ports */
+ char *ifname[ARSWITCH_NUM_PHYS];
+ device_t miibus[ARSWITCH_NUM_PHYS];
+ if_t ifp[ARSWITCH_NUM_PHYS];
+ struct arswitch_dev_led dev_led[ARSWITCH_NUM_PHYS][ARSWITCH_NUM_LEDS];
+ struct callout callout_tick;
+ etherswitch_info_t info;
+
+ uint32_t sc_debug;
+
+ /* VLANs support */
+ int vid[AR8X16_MAX_VLANS];
+ uint32_t vlan_mode;
+
+ /* ATU (address table unit) support */
+ struct {
+ int count;
+ int size;
+ etherswitch_atu_entry_t *entries;
+ } atu;
+
+ struct {
+ /* Global setup */
+ int (* arswitch_hw_setup) (struct arswitch_softc *);
+ int (* arswitch_hw_global_setup) (struct arswitch_softc *);
+
+ int (* arswitch_hw_get_switch_macaddr) (struct arswitch_softc *,
+ struct ether_addr *sa);
+ int (* arswitch_hw_set_switch_macaddr) (struct arswitch_softc *,
+ const struct ether_addr *sa);
+
+ /* Port functions */
+ void (* arswitch_port_init) (struct arswitch_softc *, int);
+
+ /* ATU functions */
+ int (* arswitch_atu_flush) (struct arswitch_softc *);
+ int (* arswitch_atu_flush_port) (struct arswitch_softc *, int);
+ int (* arswitch_atu_learn_default) (struct arswitch_softc *);
+ int (* arswitch_atu_fetch_table) (struct arswitch_softc *,
+ etherswitch_atu_entry_t *, int atu_fetch_op);
+
+ /* VLAN functions */
+ int (* arswitch_port_vlan_setup) (struct arswitch_softc *,
+ etherswitch_port_t *);
+ int (* arswitch_port_vlan_get) (struct arswitch_softc *,
+ etherswitch_port_t *);
+ void (* arswitch_vlan_init_hw) (struct arswitch_softc *);
+ int (* arswitch_vlan_getvgroup) (struct arswitch_softc *,
+ etherswitch_vlangroup_t *);
+ int (* arswitch_vlan_setvgroup) (struct arswitch_softc *,
+ etherswitch_vlangroup_t *);
+ int (* arswitch_vlan_get_pvid) (struct arswitch_softc *, int,
+ int *);
+ int (* arswitch_vlan_set_pvid) (struct arswitch_softc *, int,
+ int);
+
+ int (* arswitch_flush_dot1q_vlan) (struct arswitch_softc *sc);
+ int (* arswitch_purge_dot1q_vlan) (struct arswitch_softc *sc,
+ int vid);
+ int (* arswitch_get_dot1q_vlan) (struct arswitch_softc *,
+ uint32_t *ports, uint32_t *untagged_ports, int vid);
+ int (* arswitch_set_dot1q_vlan) (struct arswitch_softc *sc,
+ uint32_t ports, uint32_t untagged_ports, int vid);
+ int (* arswitch_get_port_vlan) (struct arswitch_softc *sc,
+ uint32_t *ports, int vid);
+ int (* arswitch_set_port_vlan) (struct arswitch_softc *sc,
+ uint32_t ports, int vid);
+
+ /* PHY functions */
+ int (* arswitch_phy_read) (device_t, int, int);
+ int (* arswitch_phy_write) (device_t, int, int, int);
+ } hal;
+
+ struct {
+ uint32_t port0_status;
+ uint32_t port5_status;
+ uint32_t port6_status;
+ } ar8327;
+};
+
+#define ARSWITCH_LOCK(_sc) \
+ mtx_lock(&(_sc)->sc_mtx)
+#define ARSWITCH_UNLOCK(_sc) \
+ mtx_unlock(&(_sc)->sc_mtx)
+#define ARSWITCH_LOCK_ASSERT(_sc, _what) \
+ mtx_assert(&(_sc)->sc_mtx, (_what))
+#define ARSWITCH_TRYLOCK(_sc) \
+ mtx_trylock(&(_sc)->sc_mtx)
+
+#define ARSWITCH_DBG_RESET 0x00000001
+#define ARSWITCH_DBG_REGIO 0x00000002
+#define ARSWITCH_DBG_PHYIO 0x00000004
+#define ARSWITCH_DBG_POLL 0x00000008
+#define ARSWITCH_DBG_VLAN 0x00000010
+#define ARSWITCH_DBG_ATU 0x00000020
+#define ARSWITCH_DBG_ANY 0xffffffff
+
+#if 1
+#define DPRINTF(sc, dbg, args...) \
+ do { \
+ if (((sc)->sc_debug & (dbg)) || \
+ ((sc)->sc_debug == ARSWITCH_DBG_ANY)) { \
+ device_printf((sc)->sc_dev, args); \
+ } \
+ } while (0)
+#define DEVERR(dev, err, fmt, args...) do { \
+ if (err != 0) device_printf(dev, fmt, err, args); \
+ } while (0)
+#else
+#define DPRINTF(dev, dbg, args...)
+#define DEVERR(dev, err, fmt, args...)
+#endif
+
+#endif /* __ARSWITCHVAR_H__ */
+
diff --git a/sys/dev/etherswitch/e6000sw/e6000sw.c b/sys/dev/etherswitch/e6000sw/e6000sw.c
new file mode 100644
index 000000000000..7e9193f4ba47
--- /dev/null
+++ b/sys/dev/etherswitch/e6000sw/e6000sw.c
@@ -0,0 +1,1834 @@
+/*-
+ * Copyright (c) 2015 Semihalf
+ * Copyright (c) 2015 Stormshield
+ * Copyright (c) 2018-2019, Rubicon Communications, LLC (Netgate)
+ * All rights reserved.
+ *
+ * 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/cdefs.h>
+#include "opt_platform.h"
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/kthread.h>
+#include <sys/module.h>
+#include <sys/taskqueue.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+
+#include <net/if.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <dev/etherswitch/etherswitch.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#ifdef FDT
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#else
+#include <sys/stdarg.h>
+#endif
+
+#include "e6000swreg.h"
+#include "etherswitch_if.h"
+#include "miibus_if.h"
+#include "mdio_if.h"
+
+MALLOC_DECLARE(M_E6000SW);
+MALLOC_DEFINE(M_E6000SW, "e6000sw", "e6000sw switch");
+
+#define E6000SW_LOCK(_sc) sx_xlock(&(_sc)->sx)
+#define E6000SW_UNLOCK(_sc) sx_unlock(&(_sc)->sx)
+#define E6000SW_LOCK_ASSERT(_sc, _what) sx_assert(&(_sc)->sx, (_what))
+#define E6000SW_TRYLOCK(_sc) sx_tryxlock(&(_sc)->sx)
+#define E6000SW_LOCKED(_sc) sx_xlocked(&(_sc)->sx)
+#define E6000SW_WAITREADY(_sc, _reg, _bit) \
+ e6000sw_waitready((_sc), REG_GLOBAL, (_reg), (_bit))
+#define E6000SW_WAITREADY2(_sc, _reg, _bit) \
+ e6000sw_waitready((_sc), REG_GLOBAL2, (_reg), (_bit))
+#define MDIO_READ(dev, addr, reg) \
+ MDIO_READREG(device_get_parent(dev), (addr), (reg))
+#define MDIO_WRITE(dev, addr, reg, val) \
+ MDIO_WRITEREG(device_get_parent(dev), (addr), (reg), (val))
+
+
+typedef struct e6000sw_softc {
+ device_t dev;
+#ifdef FDT
+ phandle_t node;
+#endif
+
+ struct sx sx;
+ if_t ifp[E6000SW_MAX_PORTS];
+ char *ifname[E6000SW_MAX_PORTS];
+ device_t miibus[E6000SW_MAX_PORTS];
+ struct taskqueue *sc_tq;
+ struct timeout_task sc_tt;
+ bool is_shutdown;
+
+ int vlans[E6000SW_NUM_VLANS];
+ uint32_t swid;
+ uint32_t vlan_mode;
+ uint32_t cpuports_mask;
+ uint32_t fixed_mask;
+ uint32_t fixed25_mask;
+ uint32_t ports_mask;
+ int phy_base;
+ int sw_addr;
+ int num_ports;
+} e6000sw_softc_t;
+
+static etherswitch_info_t etherswitch_info = {
+ .es_nports = 0,
+ .es_nvlangroups = 0,
+ .es_vlan_caps = ETHERSWITCH_VLAN_PORT | ETHERSWITCH_VLAN_DOT1Q,
+ .es_name = "Marvell 6000 series switch"
+};
+
+static void e6000sw_identify(driver_t *, device_t);
+static int e6000sw_probe(device_t);
+#ifdef FDT
+static int e6000sw_parse_fixed_link(e6000sw_softc_t *, phandle_t, uint32_t);
+static int e6000sw_parse_ethernet(e6000sw_softc_t *, phandle_t, uint32_t);
+#endif
+static int e6000sw_attach(device_t);
+static int e6000sw_detach(device_t);
+static int e6000sw_read_xmdio(device_t, int, int, int);
+static int e6000sw_write_xmdio(device_t, int, int, int, int);
+static int e6000sw_readphy(device_t, int, int);
+static int e6000sw_writephy(device_t, int, int, int);
+static int e6000sw_readphy_locked(device_t, int, int);
+static int e6000sw_writephy_locked(device_t, int, int, int);
+static etherswitch_info_t* e6000sw_getinfo(device_t);
+static int e6000sw_getconf(device_t, etherswitch_conf_t *);
+static int e6000sw_setconf(device_t, etherswitch_conf_t *);
+static void e6000sw_lock(device_t);
+static void e6000sw_unlock(device_t);
+static int e6000sw_getport(device_t, etherswitch_port_t *);
+static int e6000sw_setport(device_t, etherswitch_port_t *);
+static int e6000sw_set_vlan_mode(e6000sw_softc_t *, uint32_t);
+static int e6000sw_readreg_wrapper(device_t, int);
+static int e6000sw_writereg_wrapper(device_t, int, int);
+static int e6000sw_getvgroup_wrapper(device_t, etherswitch_vlangroup_t *);
+static int e6000sw_setvgroup_wrapper(device_t, etherswitch_vlangroup_t *);
+static int e6000sw_setvgroup(device_t, etherswitch_vlangroup_t *);
+static int e6000sw_getvgroup(device_t, etherswitch_vlangroup_t *);
+static void e6000sw_setup(device_t, e6000sw_softc_t *);
+static void e6000sw_tick(void *, int);
+static void e6000sw_set_atustat(device_t, e6000sw_softc_t *, int, int);
+static int e6000sw_atu_flush(device_t, e6000sw_softc_t *, int);
+static int e6000sw_vtu_flush(e6000sw_softc_t *);
+static int e6000sw_vtu_update(e6000sw_softc_t *, int, int, int, int, int);
+static __inline void e6000sw_writereg(e6000sw_softc_t *, int, int, int);
+static __inline uint32_t e6000sw_readreg(e6000sw_softc_t *, int, int);
+static int e6000sw_ifmedia_upd(if_t);
+static void e6000sw_ifmedia_sts(if_t, struct ifmediareq *);
+static int e6000sw_atu_mac_table(device_t, e6000sw_softc_t *, struct atu_opt *,
+ int);
+static int e6000sw_get_pvid(e6000sw_softc_t *, int, int *);
+static void e6000sw_set_pvid(e6000sw_softc_t *, int, int);
+static __inline bool e6000sw_is_cpuport(e6000sw_softc_t *, int);
+static __inline bool e6000sw_is_fixedport(e6000sw_softc_t *, int);
+static __inline bool e6000sw_is_fixed25port(e6000sw_softc_t *, int);
+static __inline bool e6000sw_is_phyport(e6000sw_softc_t *, int);
+static __inline bool e6000sw_is_portenabled(e6000sw_softc_t *, int);
+static __inline struct mii_data *e6000sw_miiforphy(e6000sw_softc_t *,
+ unsigned int);
+
+static device_method_t e6000sw_methods[] = {
+ /* device interface */
+ DEVMETHOD(device_identify, e6000sw_identify),
+ DEVMETHOD(device_probe, e6000sw_probe),
+ DEVMETHOD(device_attach, e6000sw_attach),
+ DEVMETHOD(device_detach, e6000sw_detach),
+
+ /* bus interface */
+ DEVMETHOD(bus_add_child, device_add_child_ordered),
+
+ /* mii interface */
+ DEVMETHOD(miibus_readreg, e6000sw_readphy),
+ DEVMETHOD(miibus_writereg, e6000sw_writephy),
+
+ /* etherswitch interface */
+ DEVMETHOD(etherswitch_getinfo, e6000sw_getinfo),
+ DEVMETHOD(etherswitch_getconf, e6000sw_getconf),
+ DEVMETHOD(etherswitch_setconf, e6000sw_setconf),
+ DEVMETHOD(etherswitch_lock, e6000sw_lock),
+ DEVMETHOD(etherswitch_unlock, e6000sw_unlock),
+ DEVMETHOD(etherswitch_getport, e6000sw_getport),
+ DEVMETHOD(etherswitch_setport, e6000sw_setport),
+ DEVMETHOD(etherswitch_readreg, e6000sw_readreg_wrapper),
+ DEVMETHOD(etherswitch_writereg, e6000sw_writereg_wrapper),
+ DEVMETHOD(etherswitch_readphyreg, e6000sw_readphy),
+ DEVMETHOD(etherswitch_writephyreg, e6000sw_writephy),
+ DEVMETHOD(etherswitch_setvgroup, e6000sw_setvgroup_wrapper),
+ DEVMETHOD(etherswitch_getvgroup, e6000sw_getvgroup_wrapper),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(e6000sw, e6000sw_driver, e6000sw_methods,
+ sizeof(e6000sw_softc_t));
+
+DRIVER_MODULE(e6000sw, mdio, e6000sw_driver, 0, 0);
+DRIVER_MODULE(miibus, e6000sw, miibus_driver, 0, 0);
+DRIVER_MODULE_ORDERED(etherswitch, e6000sw, etherswitch_driver, 0, 0, SI_ORDER_ANY);
+MODULE_DEPEND(e6000sw, mdio, 1, 1, 1);
+MODULE_DEPEND(e6000sw, etherswitch, 1, 1, 1);
+
+static void
+e6000sw_identify(driver_t *driver, device_t parent)
+{
+
+ if (device_find_child(parent, "e6000sw", DEVICE_UNIT_ANY) == NULL)
+ BUS_ADD_CHILD(parent, 0, "e6000sw", DEVICE_UNIT_ANY);
+}
+
+static int
+e6000sw_probe(device_t dev)
+{
+ e6000sw_softc_t *sc;
+ const char *description;
+#ifdef FDT
+ phandle_t switch_node;
+#else
+ int is_6190 = 0;
+ int is_6190x = 0;
+#endif
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+
+#ifdef FDT
+ switch_node = ofw_bus_find_compatible(OF_finddevice("/"),
+ "marvell,mv88e6085");
+ if (switch_node == 0) {
+ switch_node = ofw_bus_find_compatible(OF_finddevice("/"),
+ "marvell,mv88e6190");
+
+ if (switch_node == 0)
+ return (ENXIO);
+
+ /*
+ * Trust DTS and fix the port register offset for the MV88E6190
+ * detection bellow.
+ */
+ sc->swid = MV88E6190;
+ }
+
+ if (bootverbose)
+ device_printf(dev, "Found switch_node: 0x%x\n", switch_node);
+
+ sc->node = switch_node;
+
+ if (OF_getencprop(sc->node, "reg", &sc->sw_addr,
+ sizeof(sc->sw_addr)) < 0)
+ return (ENXIO);
+#else
+ if (resource_int_value(device_get_name(sc->dev),
+ device_get_unit(sc->dev), "addr", &sc->sw_addr) != 0)
+ return (ENXIO);
+ if (resource_int_value(device_get_name(sc->dev),
+ device_get_unit(sc->dev), "is6190", &is_6190) != 0) {
+ /*
+ * Check "is8190" to keep backward compatibility with
+ * older setups.
+ */
+ resource_int_value(device_get_name(sc->dev),
+ device_get_unit(sc->dev), "is8190", &is_6190);
+ }
+ resource_int_value(device_get_name(sc->dev),
+ device_get_unit(sc->dev), "is6190x", &is_6190x);
+ if (is_6190 != 0 && is_6190x != 0) {
+ device_printf(dev,
+ "Cannot configure conflicting variants (6190 / 6190x)\n");
+ return (ENXIO);
+ }
+ if (is_6190 != 0)
+ sc->swid = MV88E6190;
+ else if (is_6190x != 0)
+ sc->swid = MV88E6190X;
+#endif
+ if (sc->sw_addr < 0 || sc->sw_addr > 32)
+ return (ENXIO);
+
+ /*
+ * Create temporary lock, just to satisfy assertions,
+ * when obtaining the switch ID. Destroy immediately afterwards.
+ */
+ sx_init(&sc->sx, "e6000sw_tmp");
+ E6000SW_LOCK(sc);
+ sc->swid = e6000sw_readreg(sc, REG_PORT(sc, 0), SWITCH_ID) & 0xfff0;
+ E6000SW_UNLOCK(sc);
+ sx_destroy(&sc->sx);
+
+ switch (sc->swid) {
+ case MV88E6141:
+ description = "Marvell 88E6141";
+ sc->phy_base = 0x10;
+ sc->num_ports = 6;
+ break;
+ case MV88E6341:
+ description = "Marvell 88E6341";
+ sc->phy_base = 0x10;
+ sc->num_ports = 6;
+ break;
+ case MV88E6352:
+ description = "Marvell 88E6352";
+ sc->num_ports = 7;
+ break;
+ case MV88E6172:
+ description = "Marvell 88E6172";
+ sc->num_ports = 7;
+ break;
+ case MV88E6176:
+ description = "Marvell 88E6176";
+ sc->num_ports = 7;
+ break;
+ case MV88E6190:
+ description = "Marvell 88E6190";
+ sc->num_ports = 11;
+ break;
+ case MV88E6190X:
+ description = "Marvell 88E6190X";
+ sc->num_ports = 11;
+ break;
+ default:
+ device_printf(dev, "Unrecognized device, id 0x%x.\n", sc->swid);
+ return (ENXIO);
+ }
+
+ device_set_desc(dev, description);
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+#ifdef FDT
+static int
+e6000sw_parse_fixed_link(e6000sw_softc_t *sc, phandle_t node, uint32_t port)
+{
+ int speed;
+ phandle_t fixed_link;
+
+ fixed_link = ofw_bus_find_child(node, "fixed-link");
+
+ if (fixed_link != 0) {
+ sc->fixed_mask |= (1 << port);
+
+ if (OF_getencprop(fixed_link,
+ "speed", &speed, sizeof(speed)) < 0) {
+ device_printf(sc->dev,
+ "Port %d has a fixed-link node without a speed "
+ "property\n", port);
+ return (ENXIO);
+ }
+ if (speed == 2500 && (MVSWITCH(sc, MV88E6141) ||
+ MVSWITCH(sc, MV88E6341) || MVSWITCH(sc, MV88E6190) || MVSWITCH(sc, MV88E6190X)))
+ sc->fixed25_mask |= (1 << port);
+ }
+
+ return (0);
+}
+
+static int
+e6000sw_parse_ethernet(e6000sw_softc_t *sc, phandle_t port_handle, uint32_t port) {
+ phandle_t switch_eth, switch_eth_handle;
+
+ if (OF_getencprop(port_handle, "ethernet", (void*)&switch_eth_handle,
+ sizeof(switch_eth_handle)) > 0) {
+ if (switch_eth_handle > 0) {
+ switch_eth = OF_node_from_xref(switch_eth_handle);
+
+ device_printf(sc->dev, "CPU port at %d\n", port);
+ sc->cpuports_mask |= (1 << port);
+
+ return (e6000sw_parse_fixed_link(sc, switch_eth, port));
+ } else
+ device_printf(sc->dev,
+ "Port %d has ethernet property but it points "
+ "to an invalid location\n", port);
+ }
+
+ return (0);
+}
+
+static int
+e6000sw_parse_child_fdt(e6000sw_softc_t *sc, phandle_t child, int *pport)
+{
+ uint32_t port;
+
+ if (pport == NULL)
+ return (ENXIO);
+
+ if (OF_getencprop(child, "reg", (void *)&port, sizeof(port)) < 0)
+ return (ENXIO);
+ if (port >= sc->num_ports)
+ return (ENXIO);
+ *pport = port;
+
+ if (e6000sw_parse_fixed_link(sc, child, port) != 0)
+ return (ENXIO);
+
+ if (e6000sw_parse_ethernet(sc, child, port) != 0)
+ return (ENXIO);
+
+ if ((sc->fixed_mask & (1 << port)) != 0)
+ device_printf(sc->dev, "fixed port at %d\n", port);
+ else
+ device_printf(sc->dev, "PHY at port %d\n", port);
+
+ return (0);
+}
+#else
+
+static int
+e6000sw_check_hint_val(device_t dev, int *val, char *fmt, ...)
+{
+ char *resname;
+ int err, len;
+ va_list ap;
+
+ len = min(strlen(fmt) * 2, 128);
+ if (len == 0)
+ return (-1);
+ resname = malloc(len, M_E6000SW, M_WAITOK);
+ memset(resname, 0, len);
+ va_start(ap, fmt);
+ vsnprintf(resname, len - 1, fmt, ap);
+ va_end(ap);
+ err = resource_int_value(device_get_name(dev), device_get_unit(dev),
+ resname, val);
+ free(resname, M_E6000SW);
+
+ return (err);
+}
+
+static int
+e6000sw_parse_hinted_port(e6000sw_softc_t *sc, int port)
+{
+ int err, val;
+
+ err = e6000sw_check_hint_val(sc->dev, &val, "port%ddisabled", port);
+ if (err == 0 && val != 0)
+ return (1);
+
+ err = e6000sw_check_hint_val(sc->dev, &val, "port%dcpu", port);
+ if (err == 0 && val != 0) {
+ sc->cpuports_mask |= (1 << port);
+ sc->fixed_mask |= (1 << port);
+ if (bootverbose)
+ device_printf(sc->dev, "CPU port at %d\n", port);
+ }
+ err = e6000sw_check_hint_val(sc->dev, &val, "port%dspeed", port);
+ if (err == 0 && val != 0) {
+ sc->fixed_mask |= (1 << port);
+ if (val == 2500)
+ sc->fixed25_mask |= (1 << port);
+ }
+
+ if (bootverbose) {
+ if ((sc->fixed_mask & (1 << port)) != 0)
+ device_printf(sc->dev, "fixed port at %d\n", port);
+ else
+ device_printf(sc->dev, "PHY at port %d\n", port);
+ }
+
+ return (0);
+}
+#endif
+
+static int
+e6000sw_init_interface(e6000sw_softc_t *sc, int port)
+{
+ char name[IFNAMSIZ];
+
+ snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->dev));
+
+ sc->ifp[port] = if_alloc(IFT_ETHER);
+ if_setsoftc(sc->ifp[port], sc);
+ if_setflagbits(sc->ifp[port], IFF_UP | IFF_BROADCAST |
+ IFF_DRV_RUNNING | IFF_SIMPLEX, 0);
+ sc->ifname[port] = malloc(strlen(name) + 1, M_E6000SW, M_NOWAIT);
+ if (sc->ifname[port] == NULL) {
+ if_free(sc->ifp[port]);
+ return (ENOMEM);
+ }
+ memcpy(sc->ifname[port], name, strlen(name) + 1);
+ if_initname(sc->ifp[port], sc->ifname[port], port);
+
+ return (0);
+}
+
+static int
+e6000sw_attach_miibus(e6000sw_softc_t *sc, int port)
+{
+ int err;
+
+ err = mii_attach(sc->dev, &sc->miibus[port], sc->ifp[port],
+ e6000sw_ifmedia_upd, e6000sw_ifmedia_sts, BMSR_DEFCAPMASK,
+ port + sc->phy_base, MII_OFFSET_ANY, 0);
+ if (err != 0)
+ return (err);
+
+ return (0);
+}
+
+static void
+e6000sw_serdes_power(device_t dev, int port, bool sgmii)
+{
+ uint32_t reg;
+
+ /* SGMII */
+ reg = e6000sw_read_xmdio(dev, port, E6000SW_SERDES_DEV,
+ E6000SW_SERDES_SGMII_CTL);
+ if (sgmii)
+ reg &= ~E6000SW_SERDES_PDOWN;
+ else
+ reg |= E6000SW_SERDES_PDOWN;
+ e6000sw_write_xmdio(dev, port, E6000SW_SERDES_DEV,
+ E6000SW_SERDES_SGMII_CTL, reg);
+
+ /* 10GBASE-R/10GBASE-X4/X2 */
+ reg = e6000sw_read_xmdio(dev, port, E6000SW_SERDES_DEV,
+ E6000SW_SERDES_PCS_CTL1);
+ if (sgmii)
+ reg |= E6000SW_SERDES_PDOWN;
+ else
+ reg &= ~E6000SW_SERDES_PDOWN;
+ e6000sw_write_xmdio(dev, port, E6000SW_SERDES_DEV,
+ E6000SW_SERDES_PCS_CTL1, reg);
+}
+
+static int
+e6000sw_attach(device_t dev)
+{
+ bool sgmii;
+ e6000sw_softc_t *sc;
+#ifdef FDT
+ phandle_t child, ports;
+#endif
+ int err, port;
+ uint32_t reg;
+
+ err = 0;
+ sc = device_get_softc(dev);
+
+ /*
+ * According to the Linux source code, all of the Switch IDs we support
+ * are multi_chip capable, and should go into multi-chip mode if the
+ * sw_addr != 0.
+ */
+ if (MVSWITCH_MULTICHIP(sc))
+ device_printf(dev, "multi-chip addressing mode (%#x)\n",
+ sc->sw_addr);
+ else
+ device_printf(dev, "single-chip addressing mode\n");
+
+ sx_init(&sc->sx, "e6000sw");
+
+ E6000SW_LOCK(sc);
+ e6000sw_setup(dev, sc);
+
+ sc->sc_tq = taskqueue_create("e6000sw_taskq", M_NOWAIT,
+ taskqueue_thread_enqueue, &sc->sc_tq);
+
+ TIMEOUT_TASK_INIT(sc->sc_tq, &sc->sc_tt, 0, e6000sw_tick, sc);
+ taskqueue_start_threads(&sc->sc_tq, 1, PI_NET, "%s taskq",
+ device_get_nameunit(dev));
+
+#ifdef FDT
+ ports = ofw_bus_find_child(sc->node, "ports");
+ if (ports == 0) {
+ device_printf(dev, "failed to parse DTS: no ports found for "
+ "switch\n");
+ E6000SW_UNLOCK(sc);
+ return (ENXIO);
+ }
+
+ for (child = OF_child(ports); child != 0; child = OF_peer(child)) {
+ err = e6000sw_parse_child_fdt(sc, child, &port);
+ if (err != 0) {
+ device_printf(sc->dev, "failed to parse DTS\n");
+ goto out_fail;
+ }
+#else
+ for (port = 0; port < sc->num_ports; port++) {
+ err = e6000sw_parse_hinted_port(sc, port);
+ if (err != 0)
+ continue;
+#endif
+
+ /* Port is in use. */
+ sc->ports_mask |= (1 << port);
+
+ err = e6000sw_init_interface(sc, port);
+ if (err != 0) {
+ device_printf(sc->dev, "failed to init interface\n");
+ goto out_fail;
+ }
+
+ if (e6000sw_is_fixedport(sc, port)) {
+ /* Link must be down to change speed force value. */
+ reg = e6000sw_readreg(sc, REG_PORT(sc, port),
+ PSC_CONTROL);
+ reg &= ~PSC_CONTROL_LINK_UP;
+ reg |= PSC_CONTROL_FORCED_LINK;
+ e6000sw_writereg(sc, REG_PORT(sc, port), PSC_CONTROL,
+ reg);
+
+ /*
+ * Force speed, full-duplex, EEE off and flow-control
+ * on.
+ */
+ reg &= ~(PSC_CONTROL_SPD2500 | PSC_CONTROL_ALT_SPD |
+ PSC_CONTROL_FORCED_FC | PSC_CONTROL_FC_ON |
+ PSC_CONTROL_FORCED_EEE);
+ if (e6000sw_is_fixed25port(sc, port))
+ reg |= PSC_CONTROL_SPD2500;
+ else
+ reg |= PSC_CONTROL_SPD1000;
+ if ((MVSWITCH(sc, MV88E6190) ||
+ MVSWITCH(sc, MV88E6190X)) &&
+ e6000sw_is_fixed25port(sc, port))
+ reg |= PSC_CONTROL_ALT_SPD;
+ reg |= PSC_CONTROL_FORCED_DPX | PSC_CONTROL_FULLDPX |
+ PSC_CONTROL_FORCED_LINK | PSC_CONTROL_LINK_UP |
+ PSC_CONTROL_FORCED_SPD;
+ if (!MVSWITCH(sc, MV88E6190) &&
+ !MVSWITCH(sc, MV88E6190X))
+ reg |= PSC_CONTROL_FORCED_FC | PSC_CONTROL_FC_ON;
+ if (MVSWITCH(sc, MV88E6141) ||
+ MVSWITCH(sc, MV88E6341) ||
+ MVSWITCH(sc, MV88E6190) ||
+ MVSWITCH(sc, MV88E6190X))
+ reg |= PSC_CONTROL_FORCED_EEE;
+ e6000sw_writereg(sc, REG_PORT(sc, port), PSC_CONTROL,
+ reg);
+ /* Power on the SERDES interfaces. */
+ if ((MVSWITCH(sc, MV88E6190) ||
+ MVSWITCH(sc, MV88E6190X)) &&
+ (port == 9 || port == 10)) {
+ if (e6000sw_is_fixed25port(sc, port))
+ sgmii = false;
+ else
+ sgmii = true;
+ e6000sw_serdes_power(sc->dev, port, sgmii);
+ }
+ }
+
+ /* Don't attach miibus at CPU/fixed ports */
+ if (!e6000sw_is_phyport(sc, port))
+ continue;
+
+ err = e6000sw_attach_miibus(sc, port);
+ if (err != 0) {
+ device_printf(sc->dev, "failed to attach miibus\n");
+ goto out_fail;
+ }
+ }
+
+ etherswitch_info.es_nports = sc->num_ports;
+
+ /* Default to port vlan. */
+ e6000sw_set_vlan_mode(sc, ETHERSWITCH_VLAN_PORT);
+
+ reg = e6000sw_readreg(sc, REG_GLOBAL, SWITCH_GLOBAL_STATUS);
+ if (reg & SWITCH_GLOBAL_STATUS_IR)
+ device_printf(dev, "switch is ready.\n");
+ E6000SW_UNLOCK(sc);
+
+ bus_identify_children(dev);
+ bus_attach_children(dev);
+
+ taskqueue_enqueue_timeout(sc->sc_tq, &sc->sc_tt, hz);
+
+ return (0);
+
+out_fail:
+ E6000SW_UNLOCK(sc);
+ e6000sw_detach(dev);
+
+ return (err);
+}
+
+static int
+e6000sw_waitready(e6000sw_softc_t *sc, uint32_t phy, uint32_t reg,
+ uint32_t busybit)
+{
+ int i;
+
+ for (i = 0; i < E6000SW_RETRIES; i++) {
+ if ((e6000sw_readreg(sc, phy, reg) & busybit) == 0)
+ return (0);
+ DELAY(1);
+ }
+
+ return (1);
+}
+
+/* XMDIO/Clause 45 access. */
+static int
+e6000sw_read_xmdio(device_t dev, int phy, int devaddr, int devreg)
+{
+ e6000sw_softc_t *sc;
+ uint32_t reg;
+
+ sc = device_get_softc(dev);
+ E6000SW_LOCK_ASSERT(sc, SA_XLOCKED);
+ if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) {
+ device_printf(dev, "Timeout while waiting for switch\n");
+ return (ETIMEDOUT);
+ }
+
+ reg = devaddr & SMI_CMD_REG_ADDR_MASK;
+ reg |= (phy << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK;
+
+ /* Load C45 register address. */
+ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG, devreg);
+ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG,
+ reg | SMI_CMD_OP_C45_ADDR);
+ if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) {
+ device_printf(dev, "Timeout while waiting for switch\n");
+ return (ETIMEDOUT);
+ }
+
+ /* Start C45 read operation. */
+ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG,
+ reg | SMI_CMD_OP_C45_READ);
+ if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) {
+ device_printf(dev, "Timeout while waiting for switch\n");
+ return (ETIMEDOUT);
+ }
+
+ /* Read C45 data. */
+ reg = e6000sw_readreg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG);
+
+ return (reg & PHY_DATA_MASK);
+}
+
+static int
+e6000sw_write_xmdio(device_t dev, int phy, int devaddr, int devreg, int val)
+{
+ e6000sw_softc_t *sc;
+ uint32_t reg;
+
+ sc = device_get_softc(dev);
+ E6000SW_LOCK_ASSERT(sc, SA_XLOCKED);
+ if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) {
+ device_printf(dev, "Timeout while waiting for switch\n");
+ return (ETIMEDOUT);
+ }
+
+ reg = devaddr & SMI_CMD_REG_ADDR_MASK;
+ reg |= (phy << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK;
+
+ /* Load C45 register address. */
+ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG, devreg);
+ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG,
+ reg | SMI_CMD_OP_C45_ADDR);
+ if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) {
+ device_printf(dev, "Timeout while waiting for switch\n");
+ return (ETIMEDOUT);
+ }
+
+ /* Load data and start the C45 write operation. */
+ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG, devreg);
+ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG,
+ reg | SMI_CMD_OP_C45_WRITE);
+
+ return (0);
+}
+
+static int
+e6000sw_readphy(device_t dev, int phy, int reg)
+{
+ e6000sw_softc_t *sc;
+ int locked, ret;
+
+ sc = device_get_softc(dev);
+
+ locked = E6000SW_LOCKED(sc);
+ if (!locked)
+ E6000SW_LOCK(sc);
+ ret = e6000sw_readphy_locked(dev, phy, reg);
+ if (!locked)
+ E6000SW_UNLOCK(sc);
+
+ return (ret);
+}
+
+/*
+ * PHY registers are paged. Put page index in reg 22 (accessible from every
+ * page), then access specific register.
+ */
+static int
+e6000sw_readphy_locked(device_t dev, int phy, int reg)
+{
+ e6000sw_softc_t *sc;
+ uint32_t val;
+
+ sc = device_get_softc(dev);
+ E6000SW_LOCK_ASSERT(sc, SA_XLOCKED);
+
+ if (!e6000sw_is_phyport(sc, phy) || reg >= E6000SW_NUM_PHY_REGS) {
+ device_printf(dev, "Wrong register address.\n");
+ return (EINVAL);
+ }
+
+ if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) {
+ device_printf(dev, "Timeout while waiting for switch\n");
+ return (ETIMEDOUT);
+ }
+
+ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG,
+ SMI_CMD_OP_C22_READ | (reg & SMI_CMD_REG_ADDR_MASK) |
+ ((phy << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK));
+ if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) {
+ device_printf(dev, "Timeout while waiting for switch\n");
+ return (ETIMEDOUT);
+ }
+
+ val = e6000sw_readreg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG);
+
+ return (val & PHY_DATA_MASK);
+}
+
+static int
+e6000sw_writephy(device_t dev, int phy, int reg, int data)
+{
+ e6000sw_softc_t *sc;
+ int locked, ret;
+
+ sc = device_get_softc(dev);
+
+ locked = E6000SW_LOCKED(sc);
+ if (!locked)
+ E6000SW_LOCK(sc);
+ ret = e6000sw_writephy_locked(dev, phy, reg, data);
+ if (!locked)
+ E6000SW_UNLOCK(sc);
+
+ return (ret);
+}
+
+static int
+e6000sw_writephy_locked(device_t dev, int phy, int reg, int data)
+{
+ e6000sw_softc_t *sc;
+
+ sc = device_get_softc(dev);
+ E6000SW_LOCK_ASSERT(sc, SA_XLOCKED);
+
+ if (!e6000sw_is_phyport(sc, phy) || reg >= E6000SW_NUM_PHY_REGS) {
+ device_printf(dev, "Wrong register address.\n");
+ return (EINVAL);
+ }
+
+ if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) {
+ device_printf(dev, "Timeout while waiting for switch\n");
+ return (ETIMEDOUT);
+ }
+
+ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG,
+ data & PHY_DATA_MASK);
+ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG,
+ SMI_CMD_OP_C22_WRITE | (reg & SMI_CMD_REG_ADDR_MASK) |
+ ((phy << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK));
+
+ return (0);
+}
+
+static int
+e6000sw_detach(device_t dev)
+{
+ int error, phy;
+ e6000sw_softc_t *sc;
+
+ sc = device_get_softc(dev);
+
+ E6000SW_LOCK(sc);
+ sc->is_shutdown = true;
+ if (sc->sc_tq != NULL) {
+ while (taskqueue_cancel_timeout(sc->sc_tq, &sc->sc_tt, NULL) != 0)
+ taskqueue_drain_timeout(sc->sc_tq, &sc->sc_tt);
+ }
+ E6000SW_UNLOCK(sc);
+
+ error = bus_generic_detach(dev);
+ if (error != 0)
+ return (error);
+
+ if (sc->sc_tq != NULL)
+ taskqueue_free(sc->sc_tq);
+
+ sx_destroy(&sc->sx);
+ for (phy = 0; phy < sc->num_ports; phy++) {
+ if (sc->ifp[phy] != NULL)
+ if_free(sc->ifp[phy]);
+ if (sc->ifname[phy] != NULL)
+ free(sc->ifname[phy], M_E6000SW);
+ }
+
+ return (0);
+}
+
+static etherswitch_info_t*
+e6000sw_getinfo(device_t dev)
+{
+
+ return (&etherswitch_info);
+}
+
+static int
+e6000sw_getconf(device_t dev, etherswitch_conf_t *conf)
+{
+ struct e6000sw_softc *sc;
+
+ /* Return the VLAN mode. */
+ sc = device_get_softc(dev);
+ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE;
+ conf->vlan_mode = sc->vlan_mode;
+
+ return (0);
+}
+
+static int
+e6000sw_setconf(device_t dev, etherswitch_conf_t *conf)
+{
+ struct e6000sw_softc *sc;
+
+ /* Set the VLAN mode. */
+ sc = device_get_softc(dev);
+ if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) {
+ E6000SW_LOCK(sc);
+ e6000sw_set_vlan_mode(sc, conf->vlan_mode);
+ E6000SW_UNLOCK(sc);
+ }
+
+ return (0);
+}
+
+static void
+e6000sw_lock(device_t dev)
+{
+ struct e6000sw_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED);
+ E6000SW_LOCK(sc);
+}
+
+static void
+e6000sw_unlock(device_t dev)
+{
+ struct e6000sw_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ E6000SW_LOCK_ASSERT(sc, SA_XLOCKED);
+ E6000SW_UNLOCK(sc);
+}
+
+static int
+e6000sw_getport(device_t dev, etherswitch_port_t *p)
+{
+ struct mii_data *mii;
+ int err;
+ struct ifmediareq *ifmr;
+ uint32_t reg;
+
+ e6000sw_softc_t *sc = device_get_softc(dev);
+ E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED);
+
+ if (p->es_port >= sc->num_ports || p->es_port < 0)
+ return (EINVAL);
+ if (!e6000sw_is_portenabled(sc, p->es_port))
+ return (0);
+
+ E6000SW_LOCK(sc);
+ e6000sw_get_pvid(sc, p->es_port, &p->es_pvid);
+
+ /* Port flags. */
+ reg = e6000sw_readreg(sc, REG_PORT(sc, p->es_port), PORT_CONTROL2);
+ if (reg & PORT_CONTROL2_DISC_TAGGED)
+ p->es_flags |= ETHERSWITCH_PORT_DROPTAGGED;
+ if (reg & PORT_CONTROL2_DISC_UNTAGGED)
+ p->es_flags |= ETHERSWITCH_PORT_DROPUNTAGGED;
+
+ err = 0;
+ if (e6000sw_is_fixedport(sc, p->es_port)) {
+ if (e6000sw_is_cpuport(sc, p->es_port))
+ p->es_flags |= ETHERSWITCH_PORT_CPU;
+ ifmr = &p->es_ifmr;
+ ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID;
+ ifmr->ifm_count = 0;
+ if (e6000sw_is_fixed25port(sc, p->es_port))
+ ifmr->ifm_active = IFM_2500_T;
+ else
+ ifmr->ifm_active = IFM_1000_T;
+ ifmr->ifm_active |= IFM_ETHER | IFM_FDX;
+ ifmr->ifm_current = ifmr->ifm_active;
+ ifmr->ifm_mask = 0;
+ } else {
+ mii = e6000sw_miiforphy(sc, p->es_port);
+ err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr,
+ &mii->mii_media, SIOCGIFMEDIA);
+ }
+ E6000SW_UNLOCK(sc);
+
+ return (err);
+}
+
+static int
+e6000sw_setport(device_t dev, etherswitch_port_t *p)
+{
+ e6000sw_softc_t *sc;
+ int err;
+ struct mii_data *mii;
+ uint32_t reg;
+
+ sc = device_get_softc(dev);
+ E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED);
+
+ if (p->es_port >= sc->num_ports || p->es_port < 0)
+ return (EINVAL);
+ if (!e6000sw_is_portenabled(sc, p->es_port))
+ return (0);
+
+ E6000SW_LOCK(sc);
+
+ /* Port flags. */
+ reg = e6000sw_readreg(sc, REG_PORT(sc, p->es_port), PORT_CONTROL2);
+ if (p->es_flags & ETHERSWITCH_PORT_DROPTAGGED)
+ reg |= PORT_CONTROL2_DISC_TAGGED;
+ else
+ reg &= ~PORT_CONTROL2_DISC_TAGGED;
+ if (p->es_flags & ETHERSWITCH_PORT_DROPUNTAGGED)
+ reg |= PORT_CONTROL2_DISC_UNTAGGED;
+ else
+ reg &= ~PORT_CONTROL2_DISC_UNTAGGED;
+ e6000sw_writereg(sc, REG_PORT(sc, p->es_port), PORT_CONTROL2, reg);
+
+ err = 0;
+ if (p->es_pvid != 0)
+ e6000sw_set_pvid(sc, p->es_port, p->es_pvid);
+ if (e6000sw_is_phyport(sc, p->es_port)) {
+ mii = e6000sw_miiforphy(sc, p->es_port);
+ err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media,
+ SIOCSIFMEDIA);
+ }
+ E6000SW_UNLOCK(sc);
+
+ return (err);
+}
+
+static __inline void
+e6000sw_port_vlan_assign(e6000sw_softc_t *sc, int port, uint32_t fid,
+ uint32_t members)
+{
+ uint32_t reg;
+
+ reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VLAN_MAP);
+ reg &= ~(PORT_MASK(sc) | PORT_VLAN_MAP_FID_MASK);
+ reg |= members & PORT_MASK(sc) & ~(1 << port);
+ reg |= (fid << PORT_VLAN_MAP_FID) & PORT_VLAN_MAP_FID_MASK;
+ e6000sw_writereg(sc, REG_PORT(sc, port), PORT_VLAN_MAP, reg);
+ reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL1);
+ reg &= ~PORT_CONTROL1_FID_MASK;
+ reg |= (fid >> 4) & PORT_CONTROL1_FID_MASK;
+ e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL1, reg);
+}
+
+static int
+e6000sw_init_vlan(struct e6000sw_softc *sc)
+{
+ int i, port, ret;
+ uint32_t members;
+
+ /* Disable all ports */
+ for (port = 0; port < sc->num_ports; port++) {
+ ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL);
+ e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL,
+ (ret & ~PORT_CONTROL_ENABLE));
+ }
+
+ /* Flush VTU. */
+ e6000sw_vtu_flush(sc);
+
+ for (port = 0; port < sc->num_ports; port++) {
+ /* Reset the egress and frame mode. */
+ ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL);
+ ret &= ~(PORT_CONTROL_EGRESS | PORT_CONTROL_FRAME);
+ e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL, ret);
+
+ /* Set the 802.1q mode. */
+ ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL2);
+ ret &= ~PORT_CONTROL2_DOT1Q;
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q)
+ ret |= PORT_CONTROL2_DOT1Q;
+ e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL2, ret);
+ }
+
+ for (port = 0; port < sc->num_ports; port++) {
+ if (!e6000sw_is_portenabled(sc, port))
+ continue;
+
+ ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VID);
+
+ /* Set port priority */
+ ret &= ~PORT_VID_PRIORITY_MASK;
+
+ /* Set VID map */
+ ret &= ~PORT_VID_DEF_VID_MASK;
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q)
+ ret |= 1;
+ else
+ ret |= (port + 1);
+ e6000sw_writereg(sc, REG_PORT(sc, port), PORT_VID, ret);
+ }
+
+ /* Assign the member ports to each origin port. */
+ for (port = 0; port < sc->num_ports; port++) {
+ members = 0;
+ if (e6000sw_is_portenabled(sc, port)) {
+ for (i = 0; i < sc->num_ports; i++) {
+ if (i == port || !e6000sw_is_portenabled(sc, i))
+ continue;
+ members |= (1 << i);
+ }
+ }
+ /* Default to FID 0. */
+ e6000sw_port_vlan_assign(sc, port, 0, members);
+ }
+
+ /* Reset internal VLAN table. */
+ for (i = 0; i < nitems(sc->vlans); i++)
+ sc->vlans[i] = 0;
+
+ /* Create default VLAN (1). */
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ sc->vlans[0] = 1;
+ e6000sw_vtu_update(sc, 0, sc->vlans[0], 1, 0, sc->ports_mask);
+ }
+
+ /* Enable all ports */
+ for (port = 0; port < sc->num_ports; port++) {
+ if (!e6000sw_is_portenabled(sc, port))
+ continue;
+ ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL);
+ e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL,
+ (ret | PORT_CONTROL_ENABLE));
+ }
+
+ return (0);
+}
+
+static int
+e6000sw_set_vlan_mode(struct e6000sw_softc *sc, uint32_t mode)
+{
+
+ E6000SW_LOCK_ASSERT(sc, SA_XLOCKED);
+ switch (mode) {
+ case ETHERSWITCH_VLAN_PORT:
+ sc->vlan_mode = ETHERSWITCH_VLAN_PORT;
+ etherswitch_info.es_nvlangroups = sc->num_ports;
+ return (e6000sw_init_vlan(sc));
+ break;
+ case ETHERSWITCH_VLAN_DOT1Q:
+ sc->vlan_mode = ETHERSWITCH_VLAN_DOT1Q;
+ etherswitch_info.es_nvlangroups = E6000SW_NUM_VLANS;
+ return (e6000sw_init_vlan(sc));
+ break;
+ default:
+ return (EINVAL);
+ }
+}
+
+/*
+ * Registers in this switch are divided into sections, specified in
+ * documentation. So as to access any of them, section index and reg index
+ * is necessary. etherswitchcfg uses only one variable, so indexes were
+ * compressed into addr_reg: 32 * section_index + reg_index.
+ */
+static int
+e6000sw_readreg_wrapper(device_t dev, int addr_reg)
+{
+ e6000sw_softc_t *sc;
+
+ sc = device_get_softc(dev);
+ if ((addr_reg > (REG_GLOBAL2 * 32 + REG_NUM_MAX)) ||
+ (addr_reg < (REG_PORT(sc, 0) * 32))) {
+ device_printf(dev, "Wrong register address.\n");
+ return (EINVAL);
+ }
+
+ return (e6000sw_readreg(device_get_softc(dev), addr_reg / 32,
+ addr_reg % 32));
+}
+
+static int
+e6000sw_writereg_wrapper(device_t dev, int addr_reg, int val)
+{
+ e6000sw_softc_t *sc;
+
+ sc = device_get_softc(dev);
+ if ((addr_reg > (REG_GLOBAL2 * 32 + REG_NUM_MAX)) ||
+ (addr_reg < (REG_PORT(sc, 0) * 32))) {
+ device_printf(dev, "Wrong register address.\n");
+ return (EINVAL);
+ }
+ e6000sw_writereg(device_get_softc(dev), addr_reg / 32,
+ addr_reg % 32, val);
+
+ return (0);
+}
+
+/*
+ * setvgroup/getvgroup called from etherswitchfcg need to be locked,
+ * while internal calls do not.
+ */
+static int
+e6000sw_setvgroup_wrapper(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ e6000sw_softc_t *sc;
+ int ret;
+
+ sc = device_get_softc(dev);
+ E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED);
+
+ E6000SW_LOCK(sc);
+ ret = e6000sw_setvgroup(dev, vg);
+ E6000SW_UNLOCK(sc);
+
+ return (ret);
+}
+
+static int
+e6000sw_getvgroup_wrapper(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ e6000sw_softc_t *sc;
+ int ret;
+
+ sc = device_get_softc(dev);
+ E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED);
+
+ E6000SW_LOCK(sc);
+ ret = e6000sw_getvgroup(dev, vg);
+ E6000SW_UNLOCK(sc);
+
+ return (ret);
+}
+
+static int
+e6000sw_set_port_vlan(e6000sw_softc_t *sc, etherswitch_vlangroup_t *vg)
+{
+ uint32_t port;
+
+ port = vg->es_vlangroup;
+ if (port > sc->num_ports)
+ return (EINVAL);
+
+ if (vg->es_member_ports != vg->es_untagged_ports) {
+ device_printf(sc->dev, "Tagged ports not supported.\n");
+ return (EINVAL);
+ }
+
+ e6000sw_port_vlan_assign(sc, port, 0, vg->es_untagged_ports);
+ vg->es_vid = port | ETHERSWITCH_VID_VALID;
+
+ return (0);
+}
+
+static int
+e6000sw_set_dot1q_vlan(e6000sw_softc_t *sc, etherswitch_vlangroup_t *vg)
+{
+ int i, vlan;
+
+ vlan = vg->es_vid & ETHERSWITCH_VID_MASK;
+
+ /* Set VLAN to '0' removes it from table. */
+ if (vlan == 0) {
+ e6000sw_vtu_update(sc, VTU_PURGE,
+ sc->vlans[vg->es_vlangroup], 0, 0, 0);
+ sc->vlans[vg->es_vlangroup] = 0;
+ return (0);
+ }
+
+ /* Is this VLAN already in table ? */
+ for (i = 0; i < etherswitch_info.es_nvlangroups; i++)
+ if (i != vg->es_vlangroup && vlan == sc->vlans[i])
+ return (EINVAL);
+
+ sc->vlans[vg->es_vlangroup] = vlan;
+ e6000sw_vtu_update(sc, 0, vlan, vg->es_vlangroup + 1,
+ vg->es_member_ports & sc->ports_mask,
+ vg->es_untagged_ports & sc->ports_mask);
+
+ return (0);
+}
+
+static int
+e6000sw_setvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ e6000sw_softc_t *sc;
+
+ sc = device_get_softc(dev);
+ E6000SW_LOCK_ASSERT(sc, SA_XLOCKED);
+
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT)
+ return (e6000sw_set_port_vlan(sc, vg));
+ else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q)
+ return (e6000sw_set_dot1q_vlan(sc, vg));
+
+ return (EINVAL);
+}
+
+static int
+e6000sw_get_port_vlan(e6000sw_softc_t *sc, etherswitch_vlangroup_t *vg)
+{
+ uint32_t port, reg;
+
+ port = vg->es_vlangroup;
+ if (port > sc->num_ports)
+ return (EINVAL);
+
+ if (!e6000sw_is_portenabled(sc, port)) {
+ vg->es_vid = port;
+ return (0);
+ }
+
+ reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VLAN_MAP);
+ vg->es_untagged_ports = vg->es_member_ports = reg & PORT_MASK(sc);
+ vg->es_vid = port | ETHERSWITCH_VID_VALID;
+ vg->es_fid = (reg & PORT_VLAN_MAP_FID_MASK) >> PORT_VLAN_MAP_FID;
+ reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL1);
+ vg->es_fid |= (reg & PORT_CONTROL1_FID_MASK) << 4;
+
+ return (0);
+}
+
+static int
+e6000sw_get_dot1q_vlan(e6000sw_softc_t *sc, etherswitch_vlangroup_t *vg)
+{
+ int i, port;
+ uint32_t reg;
+
+ vg->es_fid = 0;
+ vg->es_vid = sc->vlans[vg->es_vlangroup];
+ vg->es_untagged_ports = vg->es_member_ports = 0;
+ if (vg->es_vid == 0)
+ return (0);
+
+ if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) {
+ device_printf(sc->dev, "VTU unit is busy, cannot access\n");
+ return (EBUSY);
+ }
+
+ e6000sw_writereg(sc, REG_GLOBAL, VTU_VID, vg->es_vid - 1);
+
+ reg = e6000sw_readreg(sc, REG_GLOBAL, VTU_OPERATION);
+ reg &= ~VTU_OP_MASK;
+ reg |= VTU_GET_NEXT | VTU_BUSY;
+ e6000sw_writereg(sc, REG_GLOBAL, VTU_OPERATION, reg);
+ if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) {
+ device_printf(sc->dev, "Timeout while reading\n");
+ return (EBUSY);
+ }
+
+ reg = e6000sw_readreg(sc, REG_GLOBAL, VTU_VID);
+ if (reg == VTU_VID_MASK || (reg & VTU_VID_VALID) == 0)
+ return (EINVAL);
+ if ((reg & VTU_VID_MASK) != vg->es_vid)
+ return (EINVAL);
+
+ vg->es_vid |= ETHERSWITCH_VID_VALID;
+ reg = e6000sw_readreg(sc, REG_GLOBAL, VTU_DATA);
+ for (i = 0; i < sc->num_ports; i++) {
+ if (i == VTU_PPREG(sc))
+ reg = e6000sw_readreg(sc, REG_GLOBAL, VTU_DATA2);
+ port = (reg >> VTU_PORT(sc, i)) & VTU_PORT_MASK;
+ if (port == VTU_PORT_UNTAGGED) {
+ vg->es_untagged_ports |= (1 << i);
+ vg->es_member_ports |= (1 << i);
+ } else if (port == VTU_PORT_TAGGED)
+ vg->es_member_ports |= (1 << i);
+ }
+
+ return (0);
+}
+
+static int
+e6000sw_getvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ e6000sw_softc_t *sc;
+
+ sc = device_get_softc(dev);
+ E6000SW_LOCK_ASSERT(sc, SA_XLOCKED);
+
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT)
+ return (e6000sw_get_port_vlan(sc, vg));
+ else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q)
+ return (e6000sw_get_dot1q_vlan(sc, vg));
+
+ return (EINVAL);
+}
+
+static __inline struct mii_data*
+e6000sw_miiforphy(e6000sw_softc_t *sc, unsigned int phy)
+{
+ device_t mii_dev;
+
+ if (!e6000sw_is_phyport(sc, phy))
+ return (NULL);
+ mii_dev = sc->miibus[phy];
+ if (mii_dev == NULL)
+ return (NULL);
+ if (device_get_state(mii_dev) != DS_ATTACHED)
+ return (NULL);
+
+ return (device_get_softc(mii_dev));
+}
+
+static int
+e6000sw_ifmedia_upd(if_t ifp)
+{
+ e6000sw_softc_t *sc;
+ struct mii_data *mii;
+
+ sc = if_getsoftc(ifp);
+ mii = e6000sw_miiforphy(sc, if_getdunit(ifp));
+ if (mii == NULL)
+ return (ENXIO);
+ mii_mediachg(mii);
+
+ return (0);
+}
+
+static void
+e6000sw_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ e6000sw_softc_t *sc;
+ struct mii_data *mii;
+
+ sc = if_getsoftc(ifp);
+ mii = e6000sw_miiforphy(sc, if_getdunit(ifp));
+
+ if (mii == NULL)
+ return;
+
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+}
+
+static int
+e6000sw_smi_waitready(e6000sw_softc_t *sc, int phy)
+{
+ int i;
+
+ for (i = 0; i < E6000SW_SMI_TIMEOUT; i++) {
+ if ((MDIO_READ(sc->dev, phy, SMI_CMD) & SMI_CMD_BUSY) == 0)
+ return (0);
+ DELAY(1);
+ }
+
+ return (1);
+}
+
+static __inline uint32_t
+e6000sw_readreg(e6000sw_softc_t *sc, int addr, int reg)
+{
+
+ E6000SW_LOCK_ASSERT(sc, SA_XLOCKED);
+
+ if (!MVSWITCH_MULTICHIP(sc))
+ return (MDIO_READ(sc->dev, addr, reg) & 0xffff);
+
+ if (e6000sw_smi_waitready(sc, sc->sw_addr)) {
+ printf("e6000sw: readreg timeout\n");
+ return (0xffff);
+ }
+ MDIO_WRITE(sc->dev, sc->sw_addr, SMI_CMD,
+ SMI_CMD_OP_C22_READ | (reg & SMI_CMD_REG_ADDR_MASK) |
+ ((addr << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK));
+ if (e6000sw_smi_waitready(sc, sc->sw_addr)) {
+ printf("e6000sw: readreg timeout\n");
+ return (0xffff);
+ }
+
+ return (MDIO_READ(sc->dev, sc->sw_addr, SMI_DATA) & 0xffff);
+}
+
+static __inline void
+e6000sw_writereg(e6000sw_softc_t *sc, int addr, int reg, int val)
+{
+
+ E6000SW_LOCK_ASSERT(sc, SA_XLOCKED);
+
+ if (!MVSWITCH_MULTICHIP(sc)) {
+ MDIO_WRITE(sc->dev, addr, reg, val);
+ return;
+ }
+
+ if (e6000sw_smi_waitready(sc, sc->sw_addr)) {
+ printf("e6000sw: readreg timeout\n");
+ return;
+ }
+ MDIO_WRITE(sc->dev, sc->sw_addr, SMI_DATA, val);
+ MDIO_WRITE(sc->dev, sc->sw_addr, SMI_CMD,
+ SMI_CMD_OP_C22_WRITE | (reg & SMI_CMD_REG_ADDR_MASK) |
+ ((addr << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK));
+}
+
+static __inline bool
+e6000sw_is_cpuport(e6000sw_softc_t *sc, int port)
+{
+
+ return ((sc->cpuports_mask & (1 << port)) ? true : false);
+}
+
+static __inline bool
+e6000sw_is_fixedport(e6000sw_softc_t *sc, int port)
+{
+
+ return ((sc->fixed_mask & (1 << port)) ? true : false);
+}
+
+static __inline bool
+e6000sw_is_fixed25port(e6000sw_softc_t *sc, int port)
+{
+
+ return ((sc->fixed25_mask & (1 << port)) ? true : false);
+}
+
+static __inline bool
+e6000sw_is_phyport(e6000sw_softc_t *sc, int port)
+{
+ uint32_t phy_mask;
+ phy_mask = ~(sc->fixed_mask | sc->cpuports_mask);
+
+ return ((phy_mask & (1 << port)) ? true : false);
+}
+
+static __inline bool
+e6000sw_is_portenabled(e6000sw_softc_t *sc, int port)
+{
+
+ return ((sc->ports_mask & (1 << port)) ? true : false);
+}
+
+static __inline void
+e6000sw_set_pvid(e6000sw_softc_t *sc, int port, int pvid)
+{
+ uint32_t reg;
+
+ reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VID);
+ reg &= ~PORT_VID_DEF_VID_MASK;
+ reg |= (pvid & PORT_VID_DEF_VID_MASK);
+ e6000sw_writereg(sc, REG_PORT(sc, port), PORT_VID, reg);
+}
+
+static __inline int
+e6000sw_get_pvid(e6000sw_softc_t *sc, int port, int *pvid)
+{
+
+ if (pvid == NULL)
+ return (ENXIO);
+
+ *pvid = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VID) &
+ PORT_VID_DEF_VID_MASK;
+
+ return (0);
+}
+
+/*
+ * Convert port status to ifmedia.
+ */
+static void
+e6000sw_update_ifmedia(uint16_t portstatus, u_int *media_status, u_int *media_active)
+{
+ *media_active = IFM_ETHER;
+ *media_status = IFM_AVALID;
+
+ if ((portstatus & PORT_STATUS_LINK_MASK) != 0)
+ *media_status |= IFM_ACTIVE;
+ else {
+ *media_active |= IFM_NONE;
+ return;
+ }
+
+ switch (portstatus & PORT_STATUS_SPEED_MASK) {
+ case PORT_STATUS_SPEED_10:
+ *media_active |= IFM_10_T;
+ break;
+ case PORT_STATUS_SPEED_100:
+ *media_active |= IFM_100_TX;
+ break;
+ case PORT_STATUS_SPEED_1000:
+ *media_active |= IFM_1000_T;
+ break;
+ }
+
+ if ((portstatus & PORT_STATUS_DUPLEX_MASK) == 0)
+ *media_active |= IFM_FDX;
+ else
+ *media_active |= IFM_HDX;
+}
+
+static void
+e6000sw_tick(void *arg, int p __unused)
+{
+ e6000sw_softc_t *sc;
+ struct mii_data *mii;
+ struct mii_softc *miisc;
+ uint16_t portstatus;
+ int port;
+
+ sc = arg;
+
+ E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED);
+
+ E6000SW_LOCK(sc);
+
+ if (sc->is_shutdown) {
+ E6000SW_UNLOCK(sc);
+ return;
+ }
+
+ for (port = 0; port < sc->num_ports; port++) {
+ /* Tick only on PHY ports */
+ if (!e6000sw_is_portenabled(sc, port) ||
+ !e6000sw_is_phyport(sc, port))
+ continue;
+
+ mii = e6000sw_miiforphy(sc, port);
+ if (mii == NULL)
+ continue;
+
+ portstatus = e6000sw_readreg(sc, REG_PORT(sc, port),
+ PORT_STATUS);
+
+ e6000sw_update_ifmedia(portstatus,
+ &mii->mii_media_status, &mii->mii_media_active);
+
+ LIST_FOREACH(miisc, &mii->mii_phys, mii_list) {
+ /*
+ * Note: this is sometimes NULL during PHY
+ * enumeration, although that shouldn't be
+ * happening /after/ tick runs. To work
+ * around this whilst the problem is being
+ * debugged, just do a NULL check here and
+ * continue.
+ */
+ if (mii->mii_media.ifm_cur == NULL)
+ continue;
+
+ if (IFM_INST(mii->mii_media.ifm_cur->ifm_media)
+ != miisc->mii_inst)
+ continue;
+ mii_phy_update(miisc, MII_POLLSTAT);
+ }
+ }
+ E6000SW_UNLOCK(sc);
+ taskqueue_enqueue_timeout(sc->sc_tq, &sc->sc_tt, hz);
+}
+
+static void
+e6000sw_setup(device_t dev, e6000sw_softc_t *sc)
+{
+ uint32_t atu_ctrl;
+
+ /* Set aging time. */
+ atu_ctrl = e6000sw_readreg(sc, REG_GLOBAL, ATU_CONTROL);
+ atu_ctrl &= ~ATU_CONTROL_AGETIME_MASK;
+ atu_ctrl |= E6000SW_DEFAULT_AGETIME << ATU_CONTROL_AGETIME;
+ e6000sw_writereg(sc, REG_GLOBAL, ATU_CONTROL, atu_ctrl);
+
+ /* Send all with specific mac address to cpu port */
+ e6000sw_writereg(sc, REG_GLOBAL2, MGMT_EN_2x, MGMT_EN_ALL);
+ e6000sw_writereg(sc, REG_GLOBAL2, MGMT_EN_0x, MGMT_EN_ALL);
+
+ /* Disable Remote Management */
+ e6000sw_writereg(sc, REG_GLOBAL, SWITCH_GLOBAL_CONTROL2, 0);
+
+ /* Disable loopback filter and flow control messages */
+ e6000sw_writereg(sc, REG_GLOBAL2, SWITCH_MGMT,
+ SWITCH_MGMT_PRI_MASK |
+ (1 << SWITCH_MGMT_RSVD2CPU) |
+ SWITCH_MGMT_FC_PRI_MASK |
+ (1 << SWITCH_MGMT_FORCEFLOW));
+
+ e6000sw_atu_flush(dev, sc, NO_OPERATION);
+ e6000sw_atu_mac_table(dev, sc, NULL, NO_OPERATION);
+ e6000sw_set_atustat(dev, sc, 0, COUNT_ALL);
+}
+
+static void
+e6000sw_set_atustat(device_t dev, e6000sw_softc_t *sc, int bin, int flag)
+{
+
+ e6000sw_readreg(sc, REG_GLOBAL2, ATU_STATS);
+ e6000sw_writereg(sc, REG_GLOBAL2, ATU_STATS, (bin << ATU_STATS_BIN ) |
+ (flag << ATU_STATS_FLAG));
+}
+
+static int
+e6000sw_atu_mac_table(device_t dev, e6000sw_softc_t *sc, struct atu_opt *atu,
+ int flag)
+{
+ uint16_t ret_opt;
+ uint16_t ret_data;
+
+ if (flag == NO_OPERATION)
+ return (0);
+ else if ((flag & (LOAD_FROM_FIB | PURGE_FROM_FIB | GET_NEXT_IN_FIB |
+ GET_VIOLATION_DATA | CLEAR_VIOLATION_DATA)) == 0) {
+ device_printf(dev, "Wrong Opcode for ATU operation\n");
+ return (EINVAL);
+ }
+
+ if (E6000SW_WAITREADY(sc, ATU_OPERATION, ATU_UNIT_BUSY)) {
+ device_printf(dev, "ATU unit is busy, cannot access\n");
+ return (EBUSY);
+ }
+
+ ret_opt = e6000sw_readreg(sc, REG_GLOBAL, ATU_OPERATION);
+ if (flag & LOAD_FROM_FIB) {
+ ret_data = e6000sw_readreg(sc, REG_GLOBAL, ATU_DATA);
+ e6000sw_writereg(sc, REG_GLOBAL2, ATU_DATA, (ret_data &
+ ~ENTRY_STATE));
+ }
+ e6000sw_writereg(sc, REG_GLOBAL, ATU_MAC_ADDR01, atu->mac_01);
+ e6000sw_writereg(sc, REG_GLOBAL, ATU_MAC_ADDR23, atu->mac_23);
+ e6000sw_writereg(sc, REG_GLOBAL, ATU_MAC_ADDR45, atu->mac_45);
+ e6000sw_writereg(sc, REG_GLOBAL, ATU_FID, atu->fid);
+
+ e6000sw_writereg(sc, REG_GLOBAL, ATU_OPERATION,
+ (ret_opt | ATU_UNIT_BUSY | flag));
+
+ if (E6000SW_WAITREADY(sc, ATU_OPERATION, ATU_UNIT_BUSY))
+ device_printf(dev, "Timeout while waiting ATU\n");
+ else if (flag & GET_NEXT_IN_FIB) {
+ atu->mac_01 = e6000sw_readreg(sc, REG_GLOBAL,
+ ATU_MAC_ADDR01);
+ atu->mac_23 = e6000sw_readreg(sc, REG_GLOBAL,
+ ATU_MAC_ADDR23);
+ atu->mac_45 = e6000sw_readreg(sc, REG_GLOBAL,
+ ATU_MAC_ADDR45);
+ }
+
+ return (0);
+}
+
+static int
+e6000sw_atu_flush(device_t dev, e6000sw_softc_t *sc, int flag)
+{
+ uint32_t reg;
+
+ if (flag == NO_OPERATION)
+ return (0);
+
+ if (E6000SW_WAITREADY(sc, ATU_OPERATION, ATU_UNIT_BUSY)) {
+ device_printf(dev, "ATU unit is busy, cannot access\n");
+ return (EBUSY);
+ }
+ reg = e6000sw_readreg(sc, REG_GLOBAL, ATU_OPERATION);
+ e6000sw_writereg(sc, REG_GLOBAL, ATU_OPERATION,
+ (reg | ATU_UNIT_BUSY | flag));
+ if (E6000SW_WAITREADY(sc, ATU_OPERATION, ATU_UNIT_BUSY))
+ device_printf(dev, "Timeout while flushing ATU\n");
+
+ return (0);
+}
+
+static int
+e6000sw_vtu_flush(e6000sw_softc_t *sc)
+{
+
+ if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) {
+ device_printf(sc->dev, "VTU unit is busy, cannot access\n");
+ return (EBUSY);
+ }
+
+ e6000sw_writereg(sc, REG_GLOBAL, VTU_OPERATION, VTU_FLUSH | VTU_BUSY);
+ if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) {
+ device_printf(sc->dev, "Timeout while flushing VTU\n");
+ return (ETIMEDOUT);
+ }
+
+ return (0);
+}
+
+static int
+e6000sw_vtu_update(e6000sw_softc_t *sc, int purge, int vid, int fid,
+ int members, int untagged)
+{
+ int i, op;
+ uint32_t data[2];
+
+ if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) {
+ device_printf(sc->dev, "VTU unit is busy, cannot access\n");
+ return (EBUSY);
+ }
+
+ *data = (vid & VTU_VID_MASK);
+ if (purge == 0)
+ *data |= VTU_VID_VALID;
+ e6000sw_writereg(sc, REG_GLOBAL, VTU_VID, *data);
+
+ if (purge == 0) {
+ data[0] = 0;
+ data[1] = 0;
+ for (i = 0; i < sc->num_ports; i++) {
+ if ((untagged & (1 << i)) != 0)
+ data[i / VTU_PPREG(sc)] |=
+ VTU_PORT_UNTAGGED << VTU_PORT(sc, i);
+ else if ((members & (1 << i)) != 0)
+ data[i / VTU_PPREG(sc)] |=
+ VTU_PORT_TAGGED << VTU_PORT(sc, i);
+ else
+ data[i / VTU_PPREG(sc)] |=
+ VTU_PORT_DISCARD << VTU_PORT(sc, i);
+ }
+ e6000sw_writereg(sc, REG_GLOBAL, VTU_DATA, data[0]);
+ e6000sw_writereg(sc, REG_GLOBAL, VTU_DATA2, data[1]);
+ e6000sw_writereg(sc, REG_GLOBAL, VTU_FID,
+ fid & VTU_FID_MASK(sc));
+ op = VTU_LOAD;
+ } else
+ op = VTU_PURGE;
+
+ e6000sw_writereg(sc, REG_GLOBAL, VTU_OPERATION, op | VTU_BUSY);
+ if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) {
+ device_printf(sc->dev, "Timeout while flushing VTU\n");
+ return (ETIMEDOUT);
+ }
+
+ return (0);
+}
diff --git a/sys/dev/etherswitch/e6000sw/e6000swreg.h b/sys/dev/etherswitch/e6000sw/e6000swreg.h
new file mode 100644
index 000000000000..ec4503faeec5
--- /dev/null
+++ b/sys/dev/etherswitch/e6000sw/e6000swreg.h
@@ -0,0 +1,304 @@
+/*-
+ * Copyright (c) 2015 Semihalf
+ * Copyright (c) 2015 Stormshield
+ * All rights reserved.
+ *
+ * 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 _E6000SWREG_H_
+#define _E6000SWREG_H_
+
+struct atu_opt {
+ uint16_t mac_01;
+ uint16_t mac_23;
+ uint16_t mac_45;
+ uint16_t fid;
+};
+
+/*
+ * Definitions for the Marvell 88E6000 series Ethernet Switch.
+ */
+
+/* Switch IDs. */
+#define MV88E6141 0x3400
+#define MV88E6341 0x3410
+#define MV88E6352 0x3520
+#define MV88E6172 0x1720
+#define MV88E6176 0x1760
+#define MV88E6190 0x1900
+#define MV88E6190X 0x0a00
+
+#define MVSWITCH(_sc, id) ((_sc)->swid == (id))
+#define MVSWITCH_MULTICHIP(_sc) ((_sc)->sw_addr != 0)
+
+/*
+ * Switch Registers
+ */
+#define REG_GLOBAL 0x1b
+#define REG_GLOBAL2 0x1c
+#define REG_PORT(_sc, p) (((MVSWITCH((_sc), MV88E6190) || MVSWITCH((_sc), MV88E6190X)) ? 0 : 0x10) + (p))
+
+#define REG_NUM_MAX 31
+
+/*
+ * Per-Port Switch Registers
+ */
+#define PORT_STATUS 0x0
+#define PORT_STATUS_SPEED_MASK 0x300
+#define PORT_STATUS_SPEED_10 0
+#define PORT_STATUS_SPEED_100 1
+#define PORT_STATUS_SPEED_1000 2
+#define PORT_STATUS_DUPLEX_MASK (1 << 10)
+#define PORT_STATUS_LINK_MASK (1 << 11)
+#define PORT_STATUS_PHY_DETECT_MASK (1 << 12)
+
+#define PSC_CONTROL 0x1
+#define PSC_CONTROL_FORCED_SPD (1 << 13)
+#define PSC_CONTROL_ALT_SPD (1 << 12)
+#define PSC_CONTROL_EEE_ON (1 << 9)
+#define PSC_CONTROL_FORCED_EEE (1 << 8)
+#define PSC_CONTROL_FC_ON (1 << 7)
+#define PSC_CONTROL_FORCED_FC (1 << 6)
+#define PSC_CONTROL_LINK_UP (1 << 5)
+#define PSC_CONTROL_FORCED_LINK (1 << 4)
+#define PSC_CONTROL_FULLDPX (1 << 3)
+#define PSC_CONTROL_FORCED_DPX (1 << 2)
+#define PSC_CONTROL_SPD10G 0x3
+#define PSC_CONTROL_SPD2500 PSC_CONTROL_SPD10G
+#define PSC_CONTROL_SPD1000 0x2
+#define SWITCH_ID 0x3
+#define PORT_CONTROL 0x4
+#define PORT_CONTROL1 0x5
+#define PORT_CONTROL1_LAG_PORT (1 << 14)
+#define PORT_CONTROL1_LAG_ID_MASK 0xf
+#define PORT_CONTROL1_LAG_ID_SHIFT 8
+#define PORT_CONTROL1_FID_MASK 0xf
+#define PORT_VLAN_MAP 0x6
+#define PORT_VID 0x7
+#define PORT_CONTROL2 0x8
+#define PORT_ASSOCIATION_VECTOR 0xb
+#define PORT_ATU_CTRL 0xc
+#define RX_COUNTER 0x12
+#define TX_COUNTER 0x13
+
+#define PORT_MASK(_sc) 0x7f
+#define PORT_VID_DEF_VID 0
+#define PORT_VID_DEF_VID_MASK 0xfff
+#define PORT_VID_PRIORITY_MASK 0xc00
+
+#define PORT_CONTROL_DISABLED 0
+#define PORT_CONTROL_BLOCKING 1
+#define PORT_CONTROL_LEARNING 2
+#define PORT_CONTROL_FORWARDING 3
+#define PORT_CONTROL_ENABLE 3
+#define PORT_CONTROL_FRAME 0x0300
+#define PORT_CONTROL_EGRESS 0x3000
+#define PORT_CONTROL2_DOT1Q 0x0c00
+#define PORT_CONTROL2_DISC_TAGGED (1 << 9)
+#define PORT_CONTROL2_DISC_UNTAGGED (1 << 8)
+
+/* PORT_VLAN fields */
+#define PORT_VLAN_MAP_FID 12
+#define PORT_VLAN_MAP_FID_MASK 0xf000
+
+/*
+ * Switch Global Register 1 accessed via REG_GLOBAL_ADDR
+ */
+#define SWITCH_GLOBAL_STATUS 0
+#define SWITCH_GLOBAL_STATUS_IR (1 << 11)
+#define SWITCH_GLOBAL_CONTROL 4
+#define SWITCH_GLOBAL_CONTROL2 28
+
+#define MONITOR_CONTROL 26
+
+/* VTU operation */
+#define VTU_FID 2
+#define VTU_OPERATION 5
+#define VTU_VID 6
+#define VTU_DATA 7
+#define VTU_DATA2 8
+
+#define VTU_FID_MASK(_sc) ((MVSWITCH((_sc), MV88E6190) || MVSWITCH((_sc), MV88E6190X)) ? 0xfff : 0xff)
+#define VTU_FID_POLICY (1 << 12)
+#define VTU_PORT_UNMODIFIED 0
+#define VTU_PORT_UNTAGGED 1
+#define VTU_PORT_TAGGED 2
+#define VTU_PORT_DISCARD 3
+#define VTU_PPREG(_sc) ((MVSWITCH((_sc), MV88E6190) || MVSWITCH((_sc), MV88E6190X)) ? 8 : 4)
+#define VTU_PORT(_sc, p) (((p) % VTU_PPREG(_sc)) * (16 / VTU_PPREG(_sc)))
+#define VTU_PORT_MASK 3
+#define VTU_BUSY (1 << 15)
+#define VTU_VID_VALID (1 << 12)
+#define VTU_VID_MASK 0xfff
+
+/* VTU opcodes */
+#define VTU_OP_MASK (7 << 12)
+#define VTU_NOP (0 << 12)
+#define VTU_FLUSH (1 << 12)
+#define VTU_LOAD (3 << 12)
+#define VTU_PURGE (3 << 12)
+#define VTU_GET_NEXT (4 << 12)
+#define STU_LOAD (5 << 12)
+#define STU_PURGE (5 << 12)
+#define STU_GET_NEXT (6 << 12)
+#define VTU_GET_VIOLATION_DATA (7 << 12)
+#define VTU_CLEAR_VIOLATION_DATA (7 << 12)
+
+/* ATU operation */
+#define ATU_FID 1
+#define ATU_CONTROL 10
+#define ATU_OPERATION 11
+#define ATU_DATA 12
+#define ATU_MAC_ADDR01 13
+#define ATU_MAC_ADDR23 14
+#define ATU_MAC_ADDR45 15
+
+#define ATU_DATA_LAG (1 << 15)
+#define ATU_PORT_MASK(_sc) ((MVSWITCH((_sc), MV88E6190) || MVSWITCH((_sc), MV88E6190X)) ? 0xfff0 : 0xff0)
+#define ATU_PORT_SHIFT 4
+#define ATU_LAG_MASK 0xf0
+#define ATU_LAG_SHIFT 4
+#define ATU_STATE_MASK 0xf
+#define ATU_UNIT_BUSY (1 << 15)
+#define ENTRY_STATE 0xf
+
+/* ATU_CONTROL fields */
+#define ATU_CONTROL_AGETIME 4
+#define ATU_CONTROL_AGETIME_MASK 0xff0
+#define ATU_CONTROL_LEARN2ALL 3
+
+/* ATU opcode */
+#define ATU_OP_MASK (7 << 12)
+#define NO_OPERATION (0 << 12)
+#define FLUSH_ALL (1 << 12)
+#define FLUSH_NON_STATIC (2 << 12)
+#define LOAD_FROM_FIB (3 << 12)
+#define PURGE_FROM_FIB (3 << 12)
+#define GET_NEXT_IN_FIB (4 << 12)
+#define FLUSH_ALL_IN_FIB (5 << 12)
+#define FLUSH_NON_STATIC_IN_FIB (6 << 12)
+#define GET_VIOLATION_DATA (7 << 12)
+#define CLEAR_VIOLATION_DATA (7 << 12)
+
+/* ATU Stats */
+#define COUNT_ALL (0 << 0)
+
+/*
+ * Switch Global Register 2 accessed via REG_GLOBAL2_ADDR
+ */
+#define MGMT_EN_2x 2
+#define MGMT_EN_0x 3
+#define SWITCH_MGMT 5
+#define LAG_MASK 7
+#define LAG_MAPPING 8
+#define ATU_STATS 14
+
+#define MGMT_EN_ALL 0xffff
+#define LAG_UPDATE (1 << 15)
+#define LAG_MASKNUM_SHIFT 12
+#define LAGID_SHIFT 11
+
+/* SWITCH_MGMT fields */
+
+#define SWITCH_MGMT_PRI 0
+#define SWITCH_MGMT_PRI_MASK 7
+#define SWITCH_MGMT_RSVD2CPU 3
+#define SWITCH_MGMT_FC_PRI 4
+#define SWITCH_MGMT_FC_PRI_MASK (7 << 4)
+#define SWITCH_MGMT_FORCEFLOW 7
+
+/* ATU_STATS fields */
+
+#define ATU_STATS_BIN 14
+#define ATU_STATS_FLAG 12
+
+/* Offset of SMI registers in multi-chip setup. */
+#define SMI_CMD 0
+#define SMI_DATA 1
+
+/*
+ * 'Switch Global Registers 2' (REG_GLOBAL2).
+ */
+
+/* EEPROM registers */
+#define EEPROM_CMD 0x14
+#define EEPROM_BUSY (1 << 15)
+#define EEPROM_READ_CMD (4 << 12)
+#define EEPROM_WRITE_CMD (3 << 12)
+#define EEPROM_WRITE_EN (1 << 10)
+#define EEPROM_DATA_MASK 0xff
+#define EEPROM_ADDR 0x15
+
+/* PHY registers */
+#define SMI_PHY_CMD_REG 0x18
+#define SMI_CMD_BUSY (1 << 15)
+#define SMI_CMD_MODE_C22 (1 << 12)
+#define SMI_CMD_C22_WRITE (1 << 10)
+#define SMI_CMD_C22_READ (2 << 10)
+#define SMI_CMD_OP_C22_WRITE \
+ (SMI_CMD_C22_WRITE | SMI_CMD_BUSY | SMI_CMD_MODE_C22)
+#define SMI_CMD_OP_C22_READ \
+ (SMI_CMD_C22_READ | SMI_CMD_BUSY | SMI_CMD_MODE_C22)
+#define SMI_CMD_C45 (0 << 12)
+#define SMI_CMD_C45_ADDR (0 << 10)
+#define SMI_CMD_C45_WRITE (1 << 10)
+#define SMI_CMD_C45_READ (3 << 10)
+#define SMI_CMD_OP_C45_ADDR \
+ (SMI_CMD_C45_ADDR | SMI_CMD_BUSY | SMI_CMD_C45)
+#define SMI_CMD_OP_C45_WRITE \
+ (SMI_CMD_C45_WRITE | SMI_CMD_BUSY | SMI_CMD_C45)
+#define SMI_CMD_OP_C45_READ \
+ (SMI_CMD_C45_READ | SMI_CMD_BUSY | SMI_CMD_C45)
+#define SMI_CMD_DEV_ADDR 5
+#define SMI_CMD_DEV_ADDR_MASK 0x3e0
+#define SMI_CMD_REG_ADDR_MASK 0x1f
+#define SMI_PHY_DATA_REG 0x19
+#define PHY_DATA_MASK 0xffff
+
+#define PHY_PAGE_REG 22
+
+/*
+ * Scratch and Misc register accessed via
+ * 'Switch Global Registers' (REG_GLOBAL2)
+ */
+#define SCR_AND_MISC_REG 0x1a
+
+#define SCR_AND_MISC_PTR_CFG 0x7000
+#define SCR_AND_MISC_DATA_CFG_MASK 0xf0
+
+/* SERDES registers. */
+#define E6000SW_SERDES_DEV 4
+#define E6000SW_SERDES_PCS_CTL1 0x1000
+#define E6000SW_SERDES_SGMII_CTL 0x2000
+#define E6000SW_SERDES_PDOWN (1 << 11)
+
+#define E6000SW_NUM_VLANS 128
+#define E6000SW_NUM_LAGMASK 8
+#define E6000SW_NUM_PHY_REGS 29
+#define E6000SW_MAX_PORTS 11
+#define E6000SW_DEFAULT_AGETIME 20
+#define E6000SW_RETRIES 100
+#define E6000SW_SMI_TIMEOUT 16
+
+#endif /* _E6000SWREG_H_ */
diff --git a/sys/dev/etherswitch/e6000sw/e6060sw.c b/sys/dev/etherswitch/e6000sw/e6060sw.c
new file mode 100644
index 000000000000..0af71692091c
--- /dev/null
+++ b/sys/dev/etherswitch/e6000sw/e6060sw.c
@@ -0,0 +1,1021 @@
+/*-
+ * Copyright (c) 2016-2017 Hiroki Mori
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+/*
+ * This code is Marvell 88E6060 ethernet switch support code on etherswitch
+ * framework.
+ * 88E6060 support is only port vlan support. Not support ingress/egress
+ * trailer.
+ * 88E6065 support is port and dot1q vlan. Also group base tag support.
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.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/ethernet.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+#define CORE_REGISTER 0x8
+#define SWITCH_ID 3
+
+#define PORT_CONTROL 4
+#define ENGRESSFSHIFT 2
+#define ENGRESSFMASK 3
+#define ENGRESSTAGSHIFT 12
+#define ENGRESSTAGMASK 3
+
+#define PORT_VLAN_MAP 6
+#define FORCEMAPSHIFT 8
+#define FORCEMAPMASK 1
+
+#define PORT_DEFVLAN 7
+#define DEFVIDMASK 0xfff
+#define DEFPRIMASK 7
+
+#define PORT_CONTROL2 8
+#define DOT1QMODESHIFT 10
+#define DOT1QMODEMASK 3
+#define DOT1QNONE 0
+#define DOT1QFALLBACK 1
+#define DOT1QCHECK 2
+#define DOT1QSECURE 3
+
+#define GLOBAL_REGISTER 0xf
+
+#define VTU_OPERATION 5
+#define VTU_VID_REG 6
+#define VTU_DATA1_REG 7
+#define VTU_DATA2_REG 8
+#define VTU_DATA3_REG 9
+#define VTU_BUSY 0x8000
+#define VTU_FLASH 1
+#define VTU_LOAD_PURGE 3
+#define VTU_GET_NEXT 4
+#define VTU_VIOLATION 7
+
+MALLOC_DECLARE(M_E6060SW);
+MALLOC_DEFINE(M_E6060SW, "e6060sw", "e6060sw data structures");
+
+struct e6060sw_softc {
+ struct mtx sc_mtx; /* serialize access to softc */
+ device_t sc_dev;
+ int vlan_mode;
+ int media; /* cpu port media */
+ int cpuport; /* which PHY is connected to the CPU */
+ int phymask; /* PHYs we manage */
+ int numports; /* number of ports */
+ int ifpport[MII_NPHY];
+ int *portphy;
+ char **ifname;
+ device_t **miibus;
+ if_t *ifp;
+ struct callout callout_tick;
+ etherswitch_info_t info;
+ int smi_offset;
+ int sw_model;
+};
+
+/* Switch Identifier DeviceID */
+
+#define E6060 0x60
+#define E6063 0x63
+#define E6065 0x65
+
+#define E6060SW_LOCK(_sc) \
+ mtx_lock(&(_sc)->sc_mtx)
+#define E6060SW_UNLOCK(_sc) \
+ mtx_unlock(&(_sc)->sc_mtx)
+#define E6060SW_LOCK_ASSERT(_sc, _what) \
+ mtx_assert(&(_sc)->sc_mtx, (_what))
+#define E6060SW_TRYLOCK(_sc) \
+ mtx_trylock(&(_sc)->sc_mtx)
+
+#if defined(DEBUG)
+#define DPRINTF(dev, args...) device_printf(dev, args)
+#else
+#define DPRINTF(dev, args...)
+#endif
+
+static inline int e6060sw_portforphy(struct e6060sw_softc *, int);
+static void e6060sw_tick(void *);
+static int e6060sw_ifmedia_upd(if_t);
+static void e6060sw_ifmedia_sts(if_t, struct ifmediareq *);
+
+static void e6060sw_setup(device_t dev);
+static int e6060sw_read_vtu(device_t dev, int num, int *data1, int *data2);
+static void e6060sw_set_vtu(device_t dev, int num, int data1, int data2);
+
+static int
+e6060sw_probe(device_t dev)
+{
+ int data;
+ struct e6060sw_softc *sc;
+ int devid, i;
+ char *devname;
+
+ sc = device_get_softc(dev);
+ bzero(sc, sizeof(*sc));
+
+ devid = 0;
+ for (i = 0; i < 2; ++i) {
+ data = MDIO_READREG(device_get_parent(dev),
+ CORE_REGISTER + i * 0x10, SWITCH_ID);
+ if (bootverbose)
+ device_printf(dev,"Switch Identifier Register %x\n",
+ data);
+
+ devid = data >> 4;
+ if (devid == E6060 ||
+ devid == E6063 || devid == E6065) {
+ sc->sw_model = devid;
+ sc->smi_offset = i * 0x10;
+ break;
+ }
+ }
+
+ if (devid == E6060)
+ devname = "88E6060";
+ else if (devid == E6063)
+ devname = "88E6063";
+ else if (devid == E6065)
+ devname = "88E6065";
+ else
+ return (ENXIO);
+
+ device_set_descf(dev, "Marvell %s MDIO switch driver at 0x%02x",
+ devname, sc->smi_offset);
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+e6060sw_attach_phys(struct e6060sw_softc *sc)
+{
+ int phy, port, err;
+ char name[IFNAMSIZ];
+
+ port = 0;
+ err = 0;
+ /* PHYs need an interface, so we generate a dummy one */
+ snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->sc_dev));
+ for (phy = 0; phy < sc->numports; phy++) {
+ if (((1 << phy) & sc->phymask) == 0)
+ continue;
+ sc->ifpport[phy] = port;
+ sc->portphy[port] = phy;
+ sc->ifp[port] = if_alloc(IFT_ETHER);
+ if_setsoftc(sc->ifp[port], sc);
+ if_setflagbits(sc->ifp[port], IFF_UP | IFF_BROADCAST |
+ IFF_DRV_RUNNING | IFF_SIMPLEX, 0);
+ if_initname(sc->ifp[port], name, port);
+ sc->miibus[port] = malloc(sizeof(device_t), M_E6060SW,
+ M_WAITOK | M_ZERO);
+ err = mii_attach(sc->sc_dev, sc->miibus[port], sc->ifp[port],
+ e6060sw_ifmedia_upd, e6060sw_ifmedia_sts, \
+ BMSR_DEFCAPMASK, phy + sc->smi_offset, MII_OFFSET_ANY, 0);
+ DPRINTF(sc->sc_dev, "%s attached to pseudo interface %s\n",
+ device_get_nameunit(*sc->miibus[port]),
+ if_name(sc->ifp[port]));
+ if (err != 0) {
+ device_printf(sc->sc_dev,
+ "attaching PHY %d failed\n",
+ phy);
+ break;
+ }
+ ++port;
+ }
+ sc->info.es_nports = port;
+ if (sc->cpuport != -1) {
+ /* assume cpuport is last one */
+ sc->ifpport[sc->cpuport] = port;
+ sc->portphy[port] = sc->cpuport;
+ ++sc->info.es_nports;
+ }
+ return (err);
+}
+
+static int
+e6060sw_attach(device_t dev)
+{
+ struct e6060sw_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+ err = 0;
+
+ sc->sc_dev = dev;
+ mtx_init(&sc->sc_mtx, "e6060sw", NULL, MTX_DEF);
+ strlcpy(sc->info.es_name, device_get_desc(dev),
+ sizeof(sc->info.es_name));
+
+ /* XXX Defaults */
+ if (sc->sw_model == E6063) {
+ sc->numports = 3;
+ sc->phymask = 0x07;
+ sc->cpuport = 2;
+ } else {
+ sc->numports = 6;
+ sc->phymask = 0x1f;
+ sc->cpuport = 5;
+ }
+ sc->media = 100;
+
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "numports", &sc->numports);
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "phymask", &sc->phymask);
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "cpuport", &sc->cpuport);
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "media", &sc->media);
+
+ if (sc->sw_model == E6060) {
+ sc->info.es_nvlangroups = sc->numports;
+ sc->info.es_vlan_caps = ETHERSWITCH_VLAN_PORT;
+ } else {
+ sc->info.es_nvlangroups = 64;
+ sc->info.es_vlan_caps = ETHERSWITCH_VLAN_PORT |
+ ETHERSWITCH_VLAN_DOT1Q;
+ }
+
+ e6060sw_setup(dev);
+
+ sc->ifp = malloc(sizeof(if_t) * sc->numports, M_E6060SW,
+ M_WAITOK | M_ZERO);
+ sc->ifname = malloc(sizeof(char *) * sc->numports, M_E6060SW,
+ M_WAITOK | M_ZERO);
+ sc->miibus = malloc(sizeof(device_t *) * sc->numports, M_E6060SW,
+ M_WAITOK | M_ZERO);
+ sc->portphy = malloc(sizeof(int) * sc->numports, M_E6060SW,
+ M_WAITOK | M_ZERO);
+
+ /*
+ * Attach the PHYs and complete the bus enumeration.
+ */
+ err = e6060sw_attach_phys(sc);
+ if (err != 0)
+ return (err);
+
+ bus_identify_children(dev);
+ bus_enumerate_hinted_children(dev);
+ bus_attach_children(dev);
+
+ callout_init(&sc->callout_tick, 0);
+
+ e6060sw_tick(sc);
+
+ return (err);
+}
+
+static int
+e6060sw_detach(device_t dev)
+{
+ struct e6060sw_softc *sc;
+ int error, i, port;
+
+ sc = device_get_softc(dev);
+
+ error = bus_generic_detach(dev);
+ if (error != 0)
+ return (error);
+
+ callout_drain(&sc->callout_tick);
+
+ for (i = 0; i < MII_NPHY; i++) {
+ if (((1 << i) & sc->phymask) == 0)
+ continue;
+ port = e6060sw_portforphy(sc, i);
+ if (sc->ifp[port] != NULL)
+ if_free(sc->ifp[port]);
+ free(sc->ifname[port], M_E6060SW);
+ free(sc->miibus[port], M_E6060SW);
+ }
+
+ free(sc->portphy, M_E6060SW);
+ free(sc->miibus, M_E6060SW);
+ free(sc->ifname, M_E6060SW);
+ free(sc->ifp, M_E6060SW);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+/*
+ * Convert PHY number to port number.
+ */
+static inline int
+e6060sw_portforphy(struct e6060sw_softc *sc, int phy)
+{
+
+ return (sc->ifpport[phy]);
+}
+
+static inline struct mii_data *
+e6060sw_miiforport(struct e6060sw_softc *sc, int port)
+{
+
+ if (port < 0 || port > sc->numports)
+ return (NULL);
+ if (port == sc->cpuport)
+ return (NULL);
+ return (device_get_softc(*sc->miibus[port]));
+}
+
+static inline if_t
+e6060sw_ifpforport(struct e6060sw_softc *sc, int port)
+{
+
+ if (port < 0 || port > sc->numports)
+ return (NULL);
+ return (sc->ifp[port]);
+}
+
+/*
+ * Poll the status for all PHYs.
+ */
+static void
+e6060sw_miipollstat(struct e6060sw_softc *sc)
+{
+ int i, port;
+ struct mii_data *mii;
+ struct mii_softc *miisc;
+
+ E6060SW_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ for (i = 0; i < MII_NPHY; i++) {
+ if (((1 << i) & sc->phymask) == 0)
+ continue;
+ port = e6060sw_portforphy(sc, i);
+ if ((*sc->miibus[port]) == NULL)
+ continue;
+ mii = device_get_softc(*sc->miibus[port]);
+ 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);
+ }
+ }
+}
+
+static void
+e6060sw_tick(void *arg)
+{
+ struct e6060sw_softc *sc;
+
+ sc = arg;
+
+ e6060sw_miipollstat(sc);
+ callout_reset(&sc->callout_tick, hz, e6060sw_tick, sc);
+}
+
+static void
+e6060sw_lock(device_t dev)
+{
+ struct e6060sw_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ E6060SW_LOCK_ASSERT(sc, MA_NOTOWNED);
+ E6060SW_LOCK(sc);
+}
+
+static void
+e6060sw_unlock(device_t dev)
+{
+ struct e6060sw_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ E6060SW_LOCK_ASSERT(sc, MA_OWNED);
+ E6060SW_UNLOCK(sc);
+}
+
+static etherswitch_info_t *
+e6060sw_getinfo(device_t dev)
+{
+ struct e6060sw_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ return (&sc->info);
+}
+
+static int
+e6060sw_getport(device_t dev, etherswitch_port_t *p)
+{
+ struct e6060sw_softc *sc;
+ struct mii_data *mii;
+ struct ifmediareq *ifmr;
+ int err, phy;
+
+ sc = device_get_softc(dev);
+ ifmr = &p->es_ifmr;
+
+ if (p->es_port < 0 || p->es_port >= sc->numports)
+ return (ENXIO);
+
+ p->es_pvid = 0;
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ p->es_pvid = MDIO_READREG(device_get_parent(dev),
+ CORE_REGISTER + sc->smi_offset + p->es_port,
+ PORT_DEFVLAN) & 0xfff;
+ }
+
+ phy = sc->portphy[p->es_port];
+ mii = e6060sw_miiforport(sc, p->es_port);
+ if (sc->cpuport != -1 && phy == sc->cpuport) {
+ /* fill in fixed values for CPU port */
+ p->es_flags |= ETHERSWITCH_PORT_CPU;
+ ifmr->ifm_count = 0;
+ if (sc->media == 100)
+ ifmr->ifm_current = ifmr->ifm_active =
+ IFM_ETHER | IFM_100_TX | IFM_FDX;
+ else
+ 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) {
+ err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr,
+ &mii->mii_media, SIOCGIFMEDIA);
+ if (err)
+ return (err);
+ } else {
+ return (ENXIO);
+ }
+ return (0);
+}
+
+static int
+e6060sw_setport(device_t dev, etherswitch_port_t *p)
+{
+ struct e6060sw_softc *sc;
+ struct ifmedia *ifm;
+ struct mii_data *mii;
+ if_t ifp;
+ int err;
+ int data;
+
+ sc = device_get_softc(dev);
+
+ if (p->es_port < 0 || p->es_port >= sc->numports)
+ return (ENXIO);
+
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ data = MDIO_READREG(device_get_parent(dev),
+ CORE_REGISTER + sc->smi_offset + p->es_port,
+ PORT_DEFVLAN);
+ data &= ~0xfff;
+ data |= p->es_pvid;
+ data |= 1 << 12;
+ MDIO_WRITEREG(device_get_parent(dev),
+ CORE_REGISTER + sc->smi_offset + p->es_port,
+ PORT_DEFVLAN, data);
+ }
+
+ if (sc->portphy[p->es_port] == sc->cpuport)
+ return(0);
+
+ mii = e6060sw_miiforport(sc, p->es_port);
+ if (mii == NULL)
+ return (ENXIO);
+
+ ifp = e6060sw_ifpforport(sc, p->es_port);
+
+ ifm = &mii->mii_media;
+ err = ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA);
+ return (err);
+}
+
+static int
+e6060sw_getvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ struct e6060sw_softc *sc;
+ int data1, data2;
+ int vid;
+ int i, tag;
+
+ sc = device_get_softc(dev);
+
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) {
+ vg->es_vid = ETHERSWITCH_VID_VALID;
+ vg->es_vid |= vg->es_vlangroup;
+ data1 = MDIO_READREG(device_get_parent(dev),
+ CORE_REGISTER + sc->smi_offset + vg->es_vlangroup,
+ PORT_VLAN_MAP);
+ vg->es_member_ports = data1 & 0x3f;
+ vg->es_untagged_ports = vg->es_member_ports;
+ vg->es_fid = 0;
+ } else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ if (vg->es_vlangroup == 0)
+ return (0);
+ vid = e6060sw_read_vtu(dev, vg->es_vlangroup, &data1, &data2);
+ if (vid > 0) {
+ vg->es_vid = ETHERSWITCH_VID_VALID;
+ vg->es_vid |= vid;
+ vg->es_member_ports = 0;
+ vg->es_untagged_ports = 0;
+ for (i = 0; i < 4; ++i) {
+ tag = data1 >> (i * 4) & 3;
+ if (tag == 0 || tag == 1) {
+ vg->es_member_ports |= 1 << i;
+ vg->es_untagged_ports |= 1 << i;
+ } else if (tag == 2) {
+ vg->es_member_ports |= 1 << i;
+ }
+ }
+ for (i = 0; i < 2; ++i) {
+ tag = data2 >> (i * 4) & 3;
+ if (tag == 0 || tag == 1) {
+ vg->es_member_ports |= 1 << (i + 4);
+ vg->es_untagged_ports |= 1 << (i + 4);
+ } else if (tag == 2) {
+ vg->es_member_ports |= 1 << (i + 4);
+ }
+ }
+
+ }
+ } else {
+ vg->es_vid = 0;
+ }
+ return (0);
+}
+
+static int
+e6060sw_setvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ struct e6060sw_softc *sc;
+ int data1, data2;
+ int i;
+
+ sc = device_get_softc(dev);
+
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) {
+ data1 = MDIO_READREG(device_get_parent(dev),
+ CORE_REGISTER + sc->smi_offset + vg->es_vlangroup,
+ PORT_VLAN_MAP);
+ data1 &= ~0x3f;
+ data1 |= vg->es_member_ports;
+ MDIO_WRITEREG(device_get_parent(dev),
+ CORE_REGISTER + sc->smi_offset + vg->es_vlangroup,
+ PORT_VLAN_MAP, data1);
+ } else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ if (vg->es_vlangroup == 0)
+ return (0);
+ data1 = 0;
+ data2 = 0;
+ for (i = 0; i < 6; ++i) {
+ if (vg->es_member_ports &
+ vg->es_untagged_ports & (1 << i)) {
+ if (i < 4) {
+ data1 |= (0xd << i * 4);
+ } else {
+ data2 |= (0xd << (i - 4) * 4);
+ }
+ } else if (vg->es_member_ports & (1 << i)) {
+ if (i < 4) {
+ data1 |= (0xe << i * 4);
+ } else {
+ data2 |= (0xe << (i - 4) * 4);
+ }
+ } else {
+ if (i < 4) {
+ data1 |= (0x3 << i * 4);
+ } else {
+ data2 |= (0x3 << (i - 4) * 4);
+ }
+ }
+ }
+ e6060sw_set_vtu(dev, vg->es_vlangroup, data1, data2);
+ }
+ return (0);
+}
+
+static void
+e6060sw_reset_vlans(device_t dev)
+{
+ struct e6060sw_softc *sc;
+ uint32_t ports;
+ int i;
+ int data;
+
+ sc = device_get_softc(dev);
+
+ for (i = 0; i <= sc->numports; i++) {
+ ports = (1 << (sc->numports + 1)) - 1;
+ ports &= ~(1 << i);
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) {
+ data = i << 12;
+ } else if (sc->vlan_mode == 0) {
+ data = 1 << 8;
+ } else {
+ data = 0;
+ }
+ data |= ports;
+ MDIO_WRITEREG(device_get_parent(dev),
+ CORE_REGISTER + sc->smi_offset + i, PORT_VLAN_MAP, data);
+ }
+}
+
+static void
+e6060sw_setup(device_t dev)
+{
+ struct e6060sw_softc *sc;
+ int i;
+ int data;
+
+ sc = device_get_softc(dev);
+
+ for (i = 0; i <= sc->numports; i++) {
+ if (sc->sw_model == E6063 || sc->sw_model == E6065) {
+ data = MDIO_READREG(device_get_parent(dev),
+ CORE_REGISTER + sc->smi_offset + i, PORT_VLAN_MAP);
+ data &= ~(FORCEMAPMASK << FORCEMAPSHIFT);
+ MDIO_WRITEREG(device_get_parent(dev),
+ CORE_REGISTER + sc->smi_offset + i,
+ PORT_VLAN_MAP, data);
+
+ data = MDIO_READREG(device_get_parent(dev),
+ CORE_REGISTER + sc->smi_offset + i, PORT_CONTROL);
+ data |= 3 << ENGRESSFSHIFT;
+ MDIO_WRITEREG(device_get_parent(dev),
+ CORE_REGISTER + sc->smi_offset + i,
+ PORT_CONTROL, data);
+ }
+ }
+}
+
+static void
+e6060sw_dot1q_mode(device_t dev, int mode)
+{
+ struct e6060sw_softc *sc;
+ int i;
+ int data;
+
+ sc = device_get_softc(dev);
+
+ for (i = 0; i <= sc->numports; i++) {
+ data = MDIO_READREG(device_get_parent(dev),
+ CORE_REGISTER + sc->smi_offset + i, PORT_CONTROL2);
+ data &= ~(DOT1QMODEMASK << DOT1QMODESHIFT);
+ data |= mode << DOT1QMODESHIFT;
+ MDIO_WRITEREG(device_get_parent(dev),
+ CORE_REGISTER + sc->smi_offset + i, PORT_CONTROL2, data);
+
+ data = MDIO_READREG(device_get_parent(dev),
+ CORE_REGISTER + sc->smi_offset + i,
+ PORT_DEFVLAN);
+ data &= ~0xfff;
+ data |= 1;
+ MDIO_WRITEREG(device_get_parent(dev),
+ CORE_REGISTER + sc->smi_offset + i,
+ PORT_DEFVLAN, data);
+ }
+}
+
+static int
+e6060sw_getconf(device_t dev, etherswitch_conf_t *conf)
+{
+ struct e6060sw_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ /* Return the VLAN mode. */
+ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE;
+ conf->vlan_mode = sc->vlan_mode;
+
+ return (0);
+}
+
+static void
+e6060sw_init_vtu(device_t dev)
+{
+ struct e6060sw_softc *sc;
+ int busy;
+
+ sc = device_get_softc(dev);
+
+ MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset,
+ VTU_OPERATION, VTU_BUSY | (VTU_FLASH << 12));
+ while (1) {
+ busy = MDIO_READREG(device_get_parent(dev),
+ GLOBAL_REGISTER + sc->smi_offset, VTU_OPERATION);
+ if ((busy & VTU_BUSY) == 0)
+ break;
+ }
+
+ /* initial member set at vlan 1*/
+ MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset,
+ VTU_DATA1_REG, 0xcccc);
+ MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset,
+ VTU_DATA2_REG, 0x00cc);
+ MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset,
+ VTU_VID_REG, 0x1000 | 1);
+ MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset,
+ VTU_OPERATION, VTU_BUSY | (VTU_LOAD_PURGE << 12) | 1);
+ while (1) {
+ busy = MDIO_READREG(device_get_parent(dev),
+ GLOBAL_REGISTER + sc->smi_offset, VTU_OPERATION);
+ if ((busy & VTU_BUSY) == 0)
+ break;
+ }
+}
+
+static void
+e6060sw_set_vtu(device_t dev, int num, int data1, int data2)
+{
+ struct e6060sw_softc *sc;
+ int busy;
+
+ sc = device_get_softc(dev);
+
+ MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset,
+ VTU_DATA1_REG, data1);
+ MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset,
+ VTU_DATA2_REG, data2);
+ MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset,
+ VTU_VID_REG, 0x1000 | num);
+ MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset,
+ VTU_OPERATION, VTU_BUSY | (VTU_LOAD_PURGE << 12) | num);
+ while (1) {
+ busy = MDIO_READREG(device_get_parent(dev),
+ GLOBAL_REGISTER + sc->smi_offset, VTU_OPERATION);
+ if ((busy & VTU_BUSY) == 0)
+ break;
+ }
+
+}
+
+static int
+e6060sw_read_vtu(device_t dev, int num, int *data1, int *data2)
+{
+ struct e6060sw_softc *sc;
+ int busy;
+
+ sc = device_get_softc(dev);
+
+ num = num - 1;
+
+ MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset,
+ VTU_VID_REG, num & 0xfff);
+ /* Get Next */
+ MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset,
+ VTU_OPERATION, VTU_BUSY | (VTU_GET_NEXT << 12));
+ while (1) {
+ busy = MDIO_READREG(device_get_parent(dev),
+ GLOBAL_REGISTER + sc->smi_offset, VTU_OPERATION);
+ if ((busy & VTU_BUSY) == 0)
+ break;
+ }
+
+ int vid = MDIO_READREG(device_get_parent(dev),
+ GLOBAL_REGISTER + sc->smi_offset, VTU_VID_REG);
+ if (vid & 0x1000) {
+ *data1 = MDIO_READREG(device_get_parent(dev),
+ GLOBAL_REGISTER + sc->smi_offset, VTU_DATA1_REG);
+ *data2 = MDIO_READREG(device_get_parent(dev),
+ GLOBAL_REGISTER + sc->smi_offset, VTU_DATA2_REG);
+
+ return (vid & 0xfff);
+ }
+
+ return (-1);
+}
+
+static int
+e6060sw_setconf(device_t dev, etherswitch_conf_t *conf)
+{
+ struct e6060sw_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ /* Set the VLAN mode. */
+ if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) {
+ if (conf->vlan_mode == ETHERSWITCH_VLAN_PORT) {
+ sc->vlan_mode = ETHERSWITCH_VLAN_PORT;
+ e6060sw_dot1q_mode(dev, DOT1QNONE);
+ e6060sw_reset_vlans(dev);
+ } else if ((sc->sw_model == E6063 || sc->sw_model == E6065) &&
+ conf->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ sc->vlan_mode = ETHERSWITCH_VLAN_DOT1Q;
+ e6060sw_dot1q_mode(dev, DOT1QSECURE);
+ e6060sw_init_vtu(dev);
+ } else {
+ sc->vlan_mode = 0;
+ /* Reset VLANs. */
+ e6060sw_dot1q_mode(dev, DOT1QNONE);
+ e6060sw_reset_vlans(dev);
+ }
+ }
+
+ return (0);
+}
+
+static void
+e6060sw_statchg(device_t dev)
+{
+
+ DPRINTF(dev, "%s\n", __func__);
+}
+
+static int
+e6060sw_ifmedia_upd(if_t ifp)
+{
+ struct e6060sw_softc *sc;
+ struct mii_data *mii;
+
+ sc = if_getsoftc(ifp);
+ mii = e6060sw_miiforport(sc, if_getdunit(ifp));
+
+ DPRINTF(sc->sc_dev, "%s\n", __func__);
+ if (mii == NULL)
+ return (ENXIO);
+ mii_mediachg(mii);
+ return (0);
+}
+
+static void
+e6060sw_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct e6060sw_softc *sc;
+ struct mii_data *mii;
+
+ sc = if_getsoftc(ifp);
+ mii = e6060sw_miiforport(sc, if_getdunit(ifp));
+
+ DPRINTF(sc->sc_dev, "%s\n", __func__);
+
+ if (mii == NULL)
+ return;
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+}
+
+static int
+e6060sw_readphy(device_t dev, int phy, int reg)
+{
+ struct e6060sw_softc *sc;
+ int data;
+
+ sc = device_get_softc(dev);
+ E6060SW_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ if (phy < 0 || phy >= 32)
+ return (ENXIO);
+ if (reg < 0 || reg >= 32)
+ return (ENXIO);
+
+ E6060SW_LOCK(sc);
+ data = MDIO_READREG(device_get_parent(dev), phy, reg);
+ E6060SW_UNLOCK(sc);
+
+ return (data);
+}
+
+static int
+e6060sw_writephy(device_t dev, int phy, int reg, int data)
+{
+ struct e6060sw_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+ E6060SW_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ if (phy < 0 || phy >= 32)
+ return (ENXIO);
+ if (reg < 0 || reg >= 32)
+ return (ENXIO);
+
+ E6060SW_LOCK(sc);
+ err = MDIO_WRITEREG(device_get_parent(dev), phy, reg, data);
+ E6060SW_UNLOCK(sc);
+
+ return (err);
+}
+
+/* addr is 5-8 bit is SMI Device Addres, 0-4 bit is SMI Register Address */
+
+static int
+e6060sw_readreg(device_t dev, int addr)
+{
+ int devaddr, regaddr;
+
+ devaddr = (addr >> 5) & 0x1f;
+ regaddr = addr & 0x1f;
+
+ return MDIO_READREG(device_get_parent(dev), devaddr, regaddr);
+}
+
+/* addr is 5-8 bit is SMI Device Addres, 0-4 bit is SMI Register Address */
+
+static int
+e6060sw_writereg(device_t dev, int addr, int value)
+{
+ int devaddr, regaddr;
+
+ devaddr = (addr >> 5) & 0x1f;
+ regaddr = addr & 0x1f;
+
+ return (MDIO_WRITEREG(device_get_parent(dev), devaddr, regaddr, value));
+}
+
+static device_method_t e6060sw_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, e6060sw_probe),
+ DEVMETHOD(device_attach, e6060sw_attach),
+ DEVMETHOD(device_detach, e6060sw_detach),
+
+ /* bus interface */
+ DEVMETHOD(bus_add_child, device_add_child_ordered),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, e6060sw_readphy),
+ DEVMETHOD(miibus_writereg, e6060sw_writephy),
+ DEVMETHOD(miibus_statchg, e6060sw_statchg),
+
+ /* MDIO interface */
+ DEVMETHOD(mdio_readreg, e6060sw_readphy),
+ DEVMETHOD(mdio_writereg, e6060sw_writephy),
+
+ /* etherswitch interface */
+ DEVMETHOD(etherswitch_lock, e6060sw_lock),
+ DEVMETHOD(etherswitch_unlock, e6060sw_unlock),
+ DEVMETHOD(etherswitch_getinfo, e6060sw_getinfo),
+ DEVMETHOD(etherswitch_readreg, e6060sw_readreg),
+ DEVMETHOD(etherswitch_writereg, e6060sw_writereg),
+ DEVMETHOD(etherswitch_readphyreg, e6060sw_readphy),
+ DEVMETHOD(etherswitch_writephyreg, e6060sw_writephy),
+ DEVMETHOD(etherswitch_getport, e6060sw_getport),
+ DEVMETHOD(etherswitch_setport, e6060sw_setport),
+ DEVMETHOD(etherswitch_getvgroup, e6060sw_getvgroup),
+ DEVMETHOD(etherswitch_setvgroup, e6060sw_setvgroup),
+ DEVMETHOD(etherswitch_setconf, e6060sw_setconf),
+ DEVMETHOD(etherswitch_getconf, e6060sw_getconf),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(e6060sw, e6060sw_driver, e6060sw_methods,
+ sizeof(struct e6060sw_softc));
+
+DRIVER_MODULE(e6060sw, mdio, e6060sw_driver, 0, 0);
+DRIVER_MODULE(miibus, e6060sw, miibus_driver, 0, 0);
+DRIVER_MODULE(mdio, e6060sw, mdio_driver, 0, 0);
+DRIVER_MODULE(etherswitch, e6060sw, etherswitch_driver, 0, 0);
+MODULE_VERSION(e6060sw, 1);
+MODULE_DEPEND(e6060sw, miibus, 1, 1, 1); /* XXX which versions? */
+MODULE_DEPEND(e6060sw, etherswitch, 1, 1, 1); /* XXX which versions? */
diff --git a/sys/dev/etherswitch/etherswitch.c b/sys/dev/etherswitch/etherswitch.c
new file mode 100644
index 000000000000..ba46d8b2299d
--- /dev/null
+++ b/sys/dev/etherswitch/etherswitch.c
@@ -0,0 +1,226 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * 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/conf.h>
+#include <sys/fcntl.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sx.h>
+#include <sys/systm.h>
+#include <sys/uio.h>
+
+#include <net/if.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+#include "etherswitch_if.h"
+
+struct etherswitch_softc {
+ device_t sc_dev;
+ struct cdev *sc_devnode;
+};
+
+static int etherswitch_probe(device_t);
+static int etherswitch_attach(device_t);
+static int etherswitch_detach(device_t);
+static void etherswitch_identify(driver_t *driver, device_t parent);
+
+static device_method_t etherswitch_methods[] = {
+ /* device interface */
+ DEVMETHOD(device_identify, etherswitch_identify),
+ DEVMETHOD(device_probe, etherswitch_probe),
+ DEVMETHOD(device_attach, etherswitch_attach),
+ DEVMETHOD(device_detach, etherswitch_detach),
+
+ DEVMETHOD_END
+};
+
+driver_t etherswitch_driver = {
+ "etherswitch",
+ etherswitch_methods,
+ sizeof(struct etherswitch_softc),
+};
+
+static d_ioctl_t etherswitchioctl;
+
+static struct cdevsw etherswitch_cdevsw = {
+ .d_version = D_VERSION,
+ .d_flags = D_TRACKCLOSE,
+ .d_ioctl = etherswitchioctl,
+ .d_name = "etherswitch",
+};
+
+static void
+etherswitch_identify(driver_t *driver, device_t parent)
+{
+ if (device_find_child(parent, "etherswitch", DEVICE_UNIT_ANY) == NULL)
+ BUS_ADD_CHILD(parent, 0, "etherswitch", DEVICE_UNIT_ANY);
+}
+
+static int
+etherswitch_probe(device_t dev)
+{
+ device_set_desc(dev, "Switch controller");
+
+ return (0);
+}
+
+static int
+etherswitch_attach(device_t dev)
+{
+ int err;
+ struct etherswitch_softc *sc;
+ struct make_dev_args devargs;
+
+ sc = device_get_softc(dev);
+ sc->sc_dev = dev;
+ make_dev_args_init(&devargs);
+ devargs.mda_devsw = &etherswitch_cdevsw;
+ devargs.mda_uid = UID_ROOT;
+ devargs.mda_gid = GID_WHEEL;
+ devargs.mda_mode = 0600;
+ devargs.mda_si_drv1 = sc;
+ err = make_dev_s(&devargs, &sc->sc_devnode, "etherswitch%d",
+ device_get_unit(dev));
+ if (err != 0) {
+ device_printf(dev, "failed to create character device\n");
+ return (ENXIO);
+ }
+
+ return (0);
+}
+
+static int
+etherswitch_detach(device_t dev)
+{
+ struct etherswitch_softc *sc = (struct etherswitch_softc *)device_get_softc(dev);
+
+ if (sc->sc_devnode)
+ destroy_dev(sc->sc_devnode);
+
+ return (0);
+}
+
+static int
+etherswitchioctl(struct cdev *cdev, u_long cmd, caddr_t data, int flags, struct thread *td)
+{
+ struct etherswitch_softc *sc = cdev->si_drv1;
+ device_t dev = sc->sc_dev;
+ device_t etherswitch = device_get_parent(dev);
+ etherswitch_conf_t conf;
+ etherswitch_info_t *info;
+ etherswitch_reg_t *reg;
+ etherswitch_phyreg_t *phyreg;
+ etherswitch_portid_t *portid;
+ int error = 0;
+
+ switch (cmd) {
+ case IOETHERSWITCHGETINFO:
+ info = ETHERSWITCH_GETINFO(etherswitch);
+ bcopy(info, data, sizeof(etherswitch_info_t));
+ break;
+
+ case IOETHERSWITCHGETREG:
+ reg = (etherswitch_reg_t *)data;
+ ETHERSWITCH_LOCK(etherswitch);
+ reg->val = ETHERSWITCH_READREG(etherswitch, reg->reg);
+ ETHERSWITCH_UNLOCK(etherswitch);
+ break;
+
+ case IOETHERSWITCHSETREG:
+ reg = (etherswitch_reg_t *)data;
+ ETHERSWITCH_LOCK(etherswitch);
+ error = ETHERSWITCH_WRITEREG(etherswitch, reg->reg, reg->val);
+ ETHERSWITCH_UNLOCK(etherswitch);
+ break;
+
+ case IOETHERSWITCHGETPORT:
+ error = ETHERSWITCH_GETPORT(etherswitch, (etherswitch_port_t *)data);
+ break;
+
+ case IOETHERSWITCHSETPORT:
+ error = ETHERSWITCH_SETPORT(etherswitch, (etherswitch_port_t *)data);
+ break;
+
+ case IOETHERSWITCHGETVLANGROUP:
+ error = ETHERSWITCH_GETVGROUP(etherswitch, (etherswitch_vlangroup_t *)data);
+ break;
+
+ case IOETHERSWITCHSETVLANGROUP:
+ error = ETHERSWITCH_SETVGROUP(etherswitch, (etherswitch_vlangroup_t *)data);
+ break;
+
+ case IOETHERSWITCHGETPHYREG:
+ phyreg = (etherswitch_phyreg_t *)data;
+ phyreg->val = ETHERSWITCH_READPHYREG(etherswitch, phyreg->phy, phyreg->reg);
+ break;
+
+ case IOETHERSWITCHSETPHYREG:
+ phyreg = (etherswitch_phyreg_t *)data;
+ error = ETHERSWITCH_WRITEPHYREG(etherswitch, phyreg->phy, phyreg->reg, phyreg->val);
+ break;
+
+ case IOETHERSWITCHGETCONF:
+ bzero(&conf, sizeof(etherswitch_conf_t));
+ error = ETHERSWITCH_GETCONF(etherswitch, &conf);
+ bcopy(&conf, data, sizeof(etherswitch_conf_t));
+ break;
+
+ case IOETHERSWITCHSETCONF:
+ error = ETHERSWITCH_SETCONF(etherswitch, (etherswitch_conf_t *)data);
+ break;
+
+ case IOETHERSWITCHFLUSHALL:
+ error = ETHERSWITCH_FLUSH_ALL(etherswitch);
+ break;
+
+ case IOETHERSWITCHFLUSHPORT:
+ portid = (etherswitch_portid_t *)data;
+ error = ETHERSWITCH_FLUSH_PORT(etherswitch, portid->es_port);
+ break;
+
+ case IOETHERSWITCHGETTABLE:
+ error = ETHERSWITCH_FETCH_TABLE(etherswitch, (void *) data);
+ break;
+
+ case IOETHERSWITCHGETTABLEENTRY:
+ error = ETHERSWITCH_FETCH_TABLE_ENTRY(etherswitch, (void *) data);
+ break;
+
+ default:
+ error = ENOTTY;
+ }
+
+ return (error);
+}
+
+MODULE_VERSION(etherswitch, 1);
diff --git a/sys/dev/etherswitch/etherswitch.h b/sys/dev/etherswitch/etherswitch.h
new file mode 100644
index 000000000000..9eec1ea781fc
--- /dev/null
+++ b/sys/dev/etherswitch/etherswitch.h
@@ -0,0 +1,147 @@
+/*
+ */
+
+#ifndef __SYS_DEV_ETHERSWITCH_ETHERSWITCH_H
+#define __SYS_DEV_ETHERSWITCH_ETHERSWITCH_H
+
+#include <sys/ioccom.h>
+#include <net/ethernet.h>
+
+#ifdef _KERNEL
+extern driver_t etherswitch_driver;
+#endif /* _KERNEL */
+
+struct etherswitch_reg {
+ uint32_t reg;
+ uint32_t val;
+};
+typedef struct etherswitch_reg etherswitch_reg_t;
+
+struct etherswitch_phyreg {
+ uint16_t phy;
+ uint16_t reg;
+ uint16_t val;
+};
+typedef struct etherswitch_phyreg etherswitch_phyreg_t;
+
+#define ETHERSWITCH_NAMEMAX 64
+#define ETHERSWITCH_VID_MASK 0xfff
+#define ETHERSWITCH_VID_VALID (1 << 12)
+#define ETHERSWITCH_VLAN_ISL (1 << 0) /* ISL */
+#define ETHERSWITCH_VLAN_PORT (1 << 1) /* Port based vlan */
+#define ETHERSWITCH_VLAN_DOT1Q (1 << 2) /* 802.1q */
+#define ETHERSWITCH_VLAN_DOT1Q_4K (1 << 3) /* 4k support on 802.1q */
+#define ETHERSWITCH_VLAN_DOUBLE_TAG (1 << 4) /* Q-in-Q */
+#define ETHERSWITCH_VLAN_CAPS_BITS \
+"\020\1ISL\2PORT\3DOT1Q\4DOT1Q4K\5QinQ"
+
+struct etherswitch_info {
+ int es_nports;
+ int es_nvlangroups;
+ char es_name[ETHERSWITCH_NAMEMAX];
+ uint32_t es_vlan_caps;
+};
+typedef struct etherswitch_info etherswitch_info_t;
+
+#define ETHERSWITCH_CONF_FLAGS (1 << 0)
+#define ETHERSWITCH_CONF_MIRROR (1 << 1)
+#define ETHERSWITCH_CONF_VLAN_MODE (1 << 2)
+#define ETHERSWITCH_CONF_SWITCH_MACADDR (1 << 3)
+
+struct etherswitch_conf {
+ uint32_t cmd; /* What to configure */
+ uint32_t vlan_mode; /* Switch VLAN mode */
+ struct ether_addr switch_macaddr; /* Switch MAC address */
+};
+typedef struct etherswitch_conf etherswitch_conf_t;
+
+#define ETHERSWITCH_PORT_CPU (1 << 0)
+#define ETHERSWITCH_PORT_STRIPTAG (1 << 1)
+#define ETHERSWITCH_PORT_ADDTAG (1 << 2)
+#define ETHERSWITCH_PORT_FIRSTLOCK (1 << 3)
+#define ETHERSWITCH_PORT_DROPUNTAGGED (1 << 4)
+#define ETHERSWITCH_PORT_DOUBLE_TAG (1 << 5)
+#define ETHERSWITCH_PORT_INGRESS (1 << 6)
+#define ETHERSWITCH_PORT_DROPTAGGED (1 << 7)
+#define ETHERSWITCH_PORT_STRIPTAGINGRESS (1 << 8)
+#define ETHERSWITCH_PORT_FLAGS_BITS \
+"\020\1CPUPORT\2STRIPTAG\3ADDTAG\4FIRSTLOCK\5DROPUNTAGGED\6QinQ\7INGRESS" \
+"\10DROPTAGGED\11STRIPTAGINGRESS"
+
+#define ETHERSWITCH_PORT_MAX_LEDS 3
+
+enum etherswitch_port_led {
+ ETHERSWITCH_PORT_LED_DEFAULT,
+ ETHERSWITCH_PORT_LED_ON,
+ ETHERSWITCH_PORT_LED_OFF,
+ ETHERSWITCH_PORT_LED_BLINK,
+ ETHERSWITCH_PORT_LED_MAX
+};
+typedef enum etherswitch_port_led etherswitch_port_led_t;
+
+struct etherswitch_port {
+ int es_port;
+ int es_pvid;
+ int es_nleds;
+ uint32_t es_flags;
+ etherswitch_port_led_t es_led[ETHERSWITCH_PORT_MAX_LEDS];
+ union {
+ struct ifreq es_uifr;
+ struct ifmediareq es_uifmr;
+ } es_ifu;
+#define es_ifr es_ifu.es_uifr
+#define es_ifmr es_ifu.es_uifmr
+};
+typedef struct etherswitch_port etherswitch_port_t;
+
+struct etherswitch_vlangroup {
+ int es_vlangroup;
+ int es_vid;
+ int es_member_ports;
+ int es_untagged_ports;
+ int es_fid;
+};
+typedef struct etherswitch_vlangroup etherswitch_vlangroup_t;
+
+#define ETHERSWITCH_PORTMASK(_port) (1 << (_port))
+
+struct etherswitch_portid {
+ int es_port;
+};
+typedef struct etherswitch_portid etherswitch_portid_t;
+
+struct etherswitch_atu_entry {
+ int id;
+ int es_portmask;
+ uint8_t es_macaddr[ETHER_ADDR_LEN];
+};
+typedef struct etherswitch_atu_entry etherswitch_atu_entry_t;
+
+struct etherswitch_atu_table {
+ uint32_t es_nitems;
+};
+typedef struct etherswitch_atu_table etherswitch_atu_table_t;
+
+struct etherswitch_atu_flush_macentry {
+ uint8_t es_macaddr[ETHER_ADDR_LEN];
+};
+typedef struct etherswitch_atu_flush_macentry etherswitch_atu_flush_macentry_t;
+
+#define IOETHERSWITCHGETINFO _IOR('i', 1, etherswitch_info_t)
+#define IOETHERSWITCHGETREG _IOWR('i', 2, etherswitch_reg_t)
+#define IOETHERSWITCHSETREG _IOW('i', 3, etherswitch_reg_t)
+#define IOETHERSWITCHGETPORT _IOWR('i', 4, etherswitch_port_t)
+#define IOETHERSWITCHSETPORT _IOW('i', 5, etherswitch_port_t)
+#define IOETHERSWITCHGETVLANGROUP _IOWR('i', 6, etherswitch_vlangroup_t)
+#define IOETHERSWITCHSETVLANGROUP _IOW('i', 7, etherswitch_vlangroup_t)
+#define IOETHERSWITCHGETPHYREG _IOWR('i', 8, etherswitch_phyreg_t)
+#define IOETHERSWITCHSETPHYREG _IOW('i', 9, etherswitch_phyreg_t)
+#define IOETHERSWITCHGETCONF _IOR('i', 10, etherswitch_conf_t)
+#define IOETHERSWITCHSETCONF _IOW('i', 11, etherswitch_conf_t)
+#define IOETHERSWITCHFLUSHALL _IOW('i', 12, etherswitch_portid_t) /* Dummy */
+#define IOETHERSWITCHFLUSHPORT _IOW('i', 13, etherswitch_portid_t)
+#define IOETHERSWITCHFLUSHMAC _IOW('i', 14, etherswitch_atu_flush_macentry_t)
+#define IOETHERSWITCHGETTABLE _IOWR('i', 15, etherswitch_atu_table_t)
+#define IOETHERSWITCHGETTABLEENTRY _IOWR('i', 16, etherswitch_atu_entry_t)
+
+#endif
diff --git a/sys/dev/etherswitch/etherswitch_if.m b/sys/dev/etherswitch/etherswitch_if.m
new file mode 100644
index 000000000000..f8e43a277210
--- /dev/null
+++ b/sys/dev/etherswitch/etherswitch_if.m
@@ -0,0 +1,220 @@
+
+#include <sys/bus.h>
+
+# Needed for ifreq/ifmediareq
+#include <sys/socket.h>
+#include <net/if.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+INTERFACE etherswitch;
+
+#
+# Default implementation
+#
+CODE {
+ static void
+ null_etherswitch_lock(device_t dev)
+ {
+ }
+
+ static void
+ null_etherswitch_unlock(device_t dev)
+ {
+ }
+
+ static int
+ null_etherswitch_getconf(device_t dev, etherswitch_conf_t *conf)
+ {
+ return (0);
+ }
+
+ static int
+ null_etherswitch_setconf(device_t dev, etherswitch_conf_t *conf)
+ {
+ return (0);
+ }
+
+ static int
+ null_etherswitch_flush_all(device_t dev)
+ {
+
+ return (ENXIO);
+ }
+
+ static int
+ null_etherswitch_flush_port(device_t dev, int port)
+ {
+
+ return (ENXIO);
+ }
+
+ static int
+ null_etherswitch_flush_mac(device_t dev,
+ etherswitch_atu_flush_macentry_t *e)
+ {
+
+ return (ENXIO);
+ }
+
+ static int
+ null_etherswitch_fetch_table(device_t dev,
+ etherswitch_atu_table_t *table)
+ {
+
+ table->es_nitems = 0;
+ return (ENXIO);
+ }
+
+ static int
+ null_etherswitch_fetch_entry(device_t dev,
+ etherswitch_atu_entry_t *e)
+ {
+
+ return (ENXIO);
+ }
+};
+
+#
+# Return device info
+#
+METHOD etherswitch_info_t* getinfo {
+ device_t dev;
+}
+
+#
+# Lock access to switch registers
+#
+METHOD void lock {
+ device_t dev;
+} DEFAULT null_etherswitch_lock;
+
+#
+# Unlock access to switch registers
+#
+METHOD void unlock {
+ device_t dev;
+} DEFAULT null_etherswitch_unlock;
+
+#
+# Read switch register
+#
+METHOD int readreg {
+ device_t dev;
+ int reg;
+};
+
+#
+# Write switch register
+#
+METHOD int writereg {
+ device_t dev;
+ int reg;
+ int value;
+};
+
+#
+# Read PHY register
+#
+METHOD int readphyreg {
+ device_t dev;
+ int phy;
+ int reg;
+};
+
+#
+# Write PHY register
+#
+METHOD int writephyreg {
+ device_t dev;
+ int phy;
+ int reg;
+ int value;
+};
+
+#
+# Get port configuration
+#
+METHOD int getport {
+ device_t dev;
+ etherswitch_port_t *vg;
+}
+
+#
+# Set port configuration
+#
+METHOD int setport {
+ device_t dev;
+ etherswitch_port_t *vg;
+}
+
+#
+# Get VLAN group configuration
+#
+METHOD int getvgroup {
+ device_t dev;
+ etherswitch_vlangroup_t *vg;
+}
+
+#
+# Set VLAN group configuration
+#
+METHOD int setvgroup {
+ device_t dev;
+ etherswitch_vlangroup_t *vg;
+}
+
+#
+# Get the Switch configuration
+#
+METHOD int getconf {
+ device_t dev;
+ etherswitch_conf_t *conf;
+} DEFAULT null_etherswitch_getconf;
+
+#
+# Set the Switch configuration
+#
+METHOD int setconf {
+ device_t dev;
+ etherswitch_conf_t *conf;
+} DEFAULT null_etherswitch_setconf;
+
+#
+# Flush all of the programmed/learnt MAC addresses
+#
+METHOD int flush_all {
+ device_t dev;
+} DEFAULT null_etherswitch_flush_all;
+
+#
+# Flush a single MAC address entry
+#
+METHOD int flush_mac {
+ device_t dev;
+ etherswitch_atu_flush_macentry_t *entry;
+} DEFAULT null_etherswitch_flush_mac;
+
+#
+# Flush all of the dynamic MAC addresses on a given port
+#
+METHOD int flush_port {
+ device_t dev;
+ int port;
+} DEFAULT null_etherswitch_flush_port;
+
+#
+# Fetch the address table from the ethernet switch.
+#
+METHOD int fetch_table {
+ device_t dev;
+ etherswitch_atu_table_t *table;
+} DEFAULT null_etherswitch_fetch_table;
+
+#
+# Fetch a single entry from the ethernet switch table.
+#
+METHOD int fetch_table_entry {
+ device_t dev;
+ etherswitch_atu_entry_t *entry;
+} DEFAULT null_etherswitch_fetch_entry;
diff --git a/sys/dev/etherswitch/felix/felix.c b/sys/dev/etherswitch/felix/felix.c
new file mode 100644
index 000000000000..098767ee063e
--- /dev/null
+++ b/sys/dev/etherswitch/felix/felix.c
@@ -0,0 +1,1005 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2021 Alstom Group.
+ * Copyright (c) 2021 Semihalf.
+ *
+ * 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/kernel.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/rman.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+
+#include <net/if.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <dev/enetc/enetc_mdio.h>
+
+#include <dev/etherswitch/etherswitch.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/etherswitch/felix/felix_var.h>
+#include <dev/etherswitch/felix/felix_reg.h>
+
+#include "etherswitch_if.h"
+#include "miibus_if.h"
+
+MALLOC_DECLARE(M_FELIX);
+MALLOC_DEFINE(M_FELIX, "felix", "felix switch");
+
+static device_probe_t felix_probe;
+static device_attach_t felix_attach;
+static device_detach_t felix_detach;
+
+static etherswitch_info_t* felix_getinfo(device_t);
+static int felix_getconf(device_t, etherswitch_conf_t *);
+static int felix_setconf(device_t, etherswitch_conf_t *);
+static void felix_lock(device_t);
+static void felix_unlock(device_t);
+static int felix_getport(device_t, etherswitch_port_t *);
+static int felix_setport(device_t, etherswitch_port_t *);
+static int felix_readreg_wrapper(device_t, int);
+static int felix_writereg_wrapper(device_t, int, int);
+static int felix_readphy(device_t, int, int);
+static int felix_writephy(device_t, int, int, int);
+static int felix_setvgroup(device_t, etherswitch_vlangroup_t *);
+static int felix_getvgroup(device_t, etherswitch_vlangroup_t *);
+
+static int felix_parse_port_fdt(felix_softc_t, phandle_t, int *);
+static int felix_setup(felix_softc_t);
+static void felix_setup_port(felix_softc_t, int);
+
+static void felix_tick(void *);
+static int felix_ifmedia_upd(if_t);
+static void felix_ifmedia_sts(if_t, struct ifmediareq *);
+
+static void felix_get_port_cfg(felix_softc_t, etherswitch_port_t *);
+static void felix_set_port_cfg(felix_softc_t, etherswitch_port_t *);
+
+static bool felix_is_phyport(felix_softc_t, int);
+static struct mii_data *felix_miiforport(felix_softc_t, unsigned int);
+
+static struct felix_pci_id felix_pci_ids[] = {
+ {PCI_VENDOR_FREESCALE, FELIX_DEV_ID, FELIX_DEV_NAME},
+ {0, 0, NULL}
+};
+
+static device_method_t felix_methods[] = {
+ /* device interface */
+ DEVMETHOD(device_probe, felix_probe),
+ DEVMETHOD(device_attach, felix_attach),
+ DEVMETHOD(device_detach, felix_detach),
+
+ /* bus interface */
+ DEVMETHOD(bus_add_child, device_add_child_ordered),
+ DEVMETHOD(bus_setup_intr, bus_generic_setup_intr),
+ DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr),
+ DEVMETHOD(bus_release_resource, bus_generic_release_resource),
+ DEVMETHOD(bus_activate_resource, bus_generic_activate_resource),
+ DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource),
+ DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource),
+ DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource),
+
+ /* etherswitch interface */
+ DEVMETHOD(etherswitch_getinfo, felix_getinfo),
+ DEVMETHOD(etherswitch_getconf, felix_getconf),
+ DEVMETHOD(etherswitch_setconf, felix_setconf),
+ DEVMETHOD(etherswitch_lock, felix_lock),
+ DEVMETHOD(etherswitch_unlock, felix_unlock),
+ DEVMETHOD(etherswitch_getport, felix_getport),
+ DEVMETHOD(etherswitch_setport, felix_setport),
+ DEVMETHOD(etherswitch_readreg, felix_readreg_wrapper),
+ DEVMETHOD(etherswitch_writereg, felix_writereg_wrapper),
+ DEVMETHOD(etherswitch_readphyreg, felix_readphy),
+ DEVMETHOD(etherswitch_writephyreg, felix_writephy),
+ DEVMETHOD(etherswitch_setvgroup, felix_setvgroup),
+ DEVMETHOD(etherswitch_getvgroup, felix_getvgroup),
+
+ /* miibus interface */
+ DEVMETHOD(miibus_readreg, felix_readphy),
+ DEVMETHOD(miibus_writereg, felix_writephy),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(felix, felix_driver, felix_methods,
+ sizeof(struct felix_softc));
+
+DRIVER_MODULE_ORDERED(felix, pci, felix_driver, NULL, NULL, SI_ORDER_ANY);
+DRIVER_MODULE(miibus, felix, miibus_fdt_driver, NULL, NULL);
+DRIVER_MODULE(etherswitch, felix, etherswitch_driver, NULL, NULL);
+MODULE_VERSION(felix, 1);
+MODULE_PNP_INFO("U16:vendor;U16:device;D:#", pci, felix,
+ felix_pci_ids, nitems(felix_pci_ids) - 1);
+
+static int
+felix_probe(device_t dev)
+{
+ struct felix_pci_id *id;
+ felix_softc_t sc;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+
+ for (id = felix_pci_ids; id->vendor != 0; ++id) {
+ if (pci_get_device(dev) != id->device ||
+ pci_get_vendor(dev) != id->vendor)
+ continue;
+
+ device_set_desc(dev, id->desc);
+
+ return (BUS_PROBE_DEFAULT);
+ }
+
+ return (ENXIO);
+}
+
+static int
+felix_parse_port_fdt(felix_softc_t sc, phandle_t child, int *pport)
+{
+ uint32_t port, status;
+ phandle_t node;
+
+ if (OF_getencprop(child, "reg", (void *)&port, sizeof(port)) < 0) {
+ device_printf(sc->dev, "Port node doesn't have reg property\n");
+ return (ENXIO);
+ }
+
+ *pport = port;
+
+ node = OF_getproplen(child, "ethernet");
+ if (node <= 0)
+ sc->ports[port].cpu_port = false;
+ else
+ sc->ports[port].cpu_port = true;
+
+ node = ofw_bus_find_child(child, "fixed-link");
+ if (node <= 0) {
+ sc->ports[port].fixed_port = false;
+ return (0);
+ }
+
+ sc->ports[port].fixed_port = true;
+
+ if (OF_getencprop(node, "speed", &status, sizeof(status)) <= 0) {
+ device_printf(sc->dev,
+ "Port has fixed-link node without link speed specified\n");
+ return (ENXIO);
+ }
+
+ switch (status) {
+ case 2500:
+ status = IFM_2500_T;
+ break;
+ case 1000:
+ status = IFM_1000_T;
+ break;
+ case 100:
+ status = IFM_100_T;
+ break;
+ case 10:
+ status = IFM_10_T;
+ break;
+ default:
+ device_printf(sc->dev,
+ "Unsupported link speed value of %d\n",
+ status);
+ return (ENXIO);
+ }
+
+ if (OF_hasprop(node, "full-duplex"))
+ status |= IFM_FDX;
+ else
+ status |= IFM_HDX;
+
+ status |= IFM_ETHER;
+ sc->ports[port].fixed_link_status = status;
+ return (0);
+}
+
+static int
+felix_init_interface(felix_softc_t sc, int port)
+{
+ char name[IFNAMSIZ];
+
+ snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->dev));
+
+ sc->ports[port].ifp = if_alloc(IFT_ETHER);
+ if_setsoftc(sc->ports[port].ifp, sc);
+ if_setflags(sc->ports[port].ifp, IFF_UP | IFF_BROADCAST | IFF_MULTICAST |
+ IFF_DRV_RUNNING | IFF_SIMPLEX);
+ sc->ports[port].ifname = malloc(strlen(name) + 1, M_FELIX, M_NOWAIT);
+ if (sc->ports[port].ifname == NULL) {
+ if_free(sc->ports[port].ifp);
+ return (ENOMEM);
+ }
+
+ memcpy(sc->ports[port].ifname, name, strlen(name) + 1);
+ if_initname(sc->ports[port].ifp, sc->ports[port].ifname, port);
+ return (0);
+}
+
+static void
+felix_setup_port(felix_softc_t sc, int port)
+{
+
+ /* Link speed has to be always set to 1000 in the clock register. */
+ FELIX_DEVGMII_PORT_WR4(sc, port, FELIX_DEVGMII_CLK_CFG,
+ FELIX_DEVGMII_CLK_CFG_SPEED_1000);
+ FELIX_DEVGMII_PORT_WR4(sc, port, FELIX_DEVGMII_MAC_CFG,
+ FELIX_DEVGMII_MAC_CFG_TX_ENA | FELIX_DEVGMII_MAC_CFG_RX_ENA);
+ FELIX_WR4(sc, FELIX_QSYS_PORT_MODE(port),
+ FELIX_QSYS_PORT_MODE_PORT_ENA);
+
+ /*
+ * Enable "VLANMTU". Each port has a configurable MTU.
+ * Accept frames that are 8 and 4 bytes longer than it
+ * for double and single tagged frames respectively.
+ * Since etherswitch API doesn't provide an option to change
+ * MTU don't touch it for now.
+ */
+ FELIX_DEVGMII_PORT_WR4(sc, port, FELIX_DEVGMII_VLAN_CFG,
+ FELIX_DEVGMII_VLAN_CFG_ENA |
+ FELIX_DEVGMII_VLAN_CFG_LEN_ENA |
+ FELIX_DEVGMII_VLAN_CFG_DOUBLE_ENA);
+}
+
+static int
+felix_setup(felix_softc_t sc)
+{
+ int timeout, i;
+ uint32_t reg;
+
+ /* Trigger soft reset, bit is self-clearing, with 5s timeout. */
+ FELIX_WR4(sc, FELIX_DEVCPU_GCB_RST, FELIX_DEVCPU_GCB_RST_EN);
+ timeout = FELIX_INIT_TIMEOUT;
+ do {
+ DELAY(1000);
+ reg = FELIX_RD4(sc, FELIX_DEVCPU_GCB_RST);
+ if ((reg & FELIX_DEVCPU_GCB_RST_EN) == 0)
+ break;
+ } while (timeout-- > 0);
+ if (timeout == 0) {
+ device_printf(sc->dev,
+ "Timeout while waiting for switch to reset\n");
+ return (ETIMEDOUT);
+ }
+
+ FELIX_WR4(sc, FELIX_SYS_RAM_CTRL, FELIX_SYS_RAM_CTRL_INIT);
+ timeout = FELIX_INIT_TIMEOUT;
+ do {
+ DELAY(1000);
+ reg = FELIX_RD4(sc, FELIX_SYS_RAM_CTRL);
+ if ((reg & FELIX_SYS_RAM_CTRL_INIT) == 0)
+ break;
+ } while (timeout-- > 0);
+ if (timeout == 0) {
+ device_printf(sc->dev,
+ "Timeout while waiting for switch RAM init.\n");
+ return (ETIMEDOUT);
+ }
+
+ FELIX_WR4(sc, FELIX_SYS_CFG, FELIX_SYS_CFG_CORE_EN);
+
+ for (i = 0; i < sc->info.es_nports; i++)
+ felix_setup_port(sc, i);
+
+ return (0);
+}
+
+static int
+felix_timer_rate(SYSCTL_HANDLER_ARGS)
+{
+ felix_softc_t sc;
+ int error, value, old;
+
+ sc = arg1;
+
+ old = value = sc->timer_ticks;
+ error = sysctl_handle_int(oidp, &value, 0, req);
+ if (error != 0 || req->newptr == NULL)
+ return (error);
+
+ if (value < 0)
+ return (EINVAL);
+
+ if (value == old)
+ return (0);
+
+ FELIX_LOCK(sc);
+ sc->timer_ticks = value;
+ callout_reset(&sc->tick_callout, sc->timer_ticks, felix_tick, sc);
+ FELIX_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+felix_attach(device_t dev)
+{
+ phandle_t child, ports, node;
+ int error, port, rid;
+ felix_softc_t sc;
+ uint32_t phy_addr;
+ ssize_t size;
+
+ sc = device_get_softc(dev);
+ sc->info.es_nports = 0;
+ sc->info.es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q;
+ strlcpy(sc->info.es_name, "Felix TSN Switch", sizeof(sc->info.es_name));
+
+ rid = PCIR_BAR(FELIX_BAR_MDIO);
+ sc->mdio = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (sc->mdio == NULL) {
+ device_printf(dev, "Failed to allocate MDIO registers.\n");
+ return (ENXIO);
+ }
+
+ rid = PCIR_BAR(FELIX_BAR_REGS);
+ sc->regs = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (sc->regs == NULL) {
+ device_printf(dev, "Failed to allocate registers BAR.\n");
+ error = ENXIO;
+ goto out_fail;
+ }
+
+ mtx_init(&sc->mtx, "felix lock", NULL, MTX_DEF);
+ callout_init_mtx(&sc->tick_callout, &sc->mtx, 0);
+
+ node = ofw_bus_get_node(dev);
+ if (node <= 0) {
+ error = ENXIO;
+ goto out_fail;
+ }
+
+ ports = ofw_bus_find_child(node, "ports");
+ if (ports == 0) {
+ device_printf(dev,
+ "Failed to find \"ports\" property in DTS.\n");
+ error = ENXIO;
+ goto out_fail;
+ }
+
+ for (child = OF_child(ports); child != 0; child = OF_peer(child)) {
+ /* Do not parse disabled ports. */
+ if (ofw_bus_node_status_okay(child) == 0)
+ continue;
+
+ error = felix_parse_port_fdt(sc, child, &port);
+ if (error != 0)
+ goto out_fail;
+
+ error = felix_init_interface(sc, port);
+ if (error != 0) {
+ device_printf(sc->dev,
+ "Failed to initialize interface.\n");
+ goto out_fail;
+ }
+
+ if (sc->ports[port].fixed_port) {
+ sc->info.es_nports++;
+ continue;
+ }
+
+ size = OF_getencprop(child, "phy-handle", &node, sizeof(node));
+ if (size <= 0) {
+ device_printf(sc->dev,
+ "Failed to acquire PHY handle from FDT.\n");
+ error = ENXIO;
+ goto out_fail;
+ }
+
+ node = OF_node_from_xref(node);
+ size = OF_getencprop(node, "reg", &phy_addr, sizeof(phy_addr));
+ if (size <= 0) {
+ device_printf(sc->dev,
+ "Failed to obtain PHY address.\n");
+ error = ENXIO;
+ goto out_fail;
+ }
+
+ sc->ports[port].phyaddr = phy_addr;
+ sc->ports[port].miibus = NULL;
+ error = mii_attach(dev, &sc->ports[port].miibus, sc->ports[port].ifp,
+ felix_ifmedia_upd, felix_ifmedia_sts, BMSR_DEFCAPMASK,
+ phy_addr, MII_OFFSET_ANY, 0);
+ if (error != 0)
+ goto out_fail;
+
+ sc->info.es_nports++;
+ }
+
+ error = felix_setup(sc);
+ if (error != 0)
+ goto out_fail;
+
+ sc->timer_ticks = hz; /* Default to 1s. */
+ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
+ OID_AUTO, "timer_ticks", CTLTYPE_INT | CTLFLAG_RW,
+ sc, 0, felix_timer_rate, "I",
+ "Number of ticks between timer invocations");
+
+ /* The tick routine has to be called with the lock held. */
+ FELIX_LOCK(sc);
+ felix_tick(sc);
+ FELIX_UNLOCK(sc);
+
+ /* Allow etherswitch to attach as our child. */
+ bus_identify_children(dev);
+ bus_attach_children(dev);
+
+ return (0);
+
+out_fail:
+ felix_detach(dev);
+ return (error);
+}
+
+static int
+felix_detach(device_t dev)
+{
+ felix_softc_t sc;
+ int error;
+ int i;
+
+ sc = device_get_softc(dev);
+ error = bus_generic_detach(dev);
+ if (error != 0)
+ return (error);
+
+ mtx_lock(&sc->mtx);
+ callout_stop(&sc->tick_callout);
+ mtx_unlock(&sc->mtx);
+ mtx_destroy(&sc->mtx);
+
+ /*
+ * If we have been fully attached do a soft reset.
+ * This way after when driver is unloaded switch is left in unmanaged mode.
+ */
+ if (device_is_attached(dev))
+ felix_setup(sc);
+
+ for (i = 0; i < sc->info.es_nports; i++) {
+ if (sc->ports[i].ifp != NULL)
+ if_free(sc->ports[i].ifp);
+ if (sc->ports[i].ifname != NULL)
+ free(sc->ports[i].ifname, M_FELIX);
+ }
+
+ if (sc->regs != NULL)
+ error = bus_release_resource(sc->dev, SYS_RES_MEMORY,
+ rman_get_rid(sc->regs), sc->regs);
+
+ if (sc->mdio != NULL)
+ error = bus_release_resource(sc->dev, SYS_RES_MEMORY,
+ rman_get_rid(sc->mdio), sc->mdio);
+
+ return (error);
+}
+
+static etherswitch_info_t*
+felix_getinfo(device_t dev)
+{
+ felix_softc_t sc;
+
+ sc = device_get_softc(dev);
+ return (&sc->info);
+}
+
+static int
+felix_getconf(device_t dev, etherswitch_conf_t *conf)
+{
+ felix_softc_t sc;
+
+ sc = device_get_softc(dev);
+
+ /* Return the VLAN mode. */
+ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE;
+ conf->vlan_mode = sc->vlan_mode;
+ return (0);
+}
+
+static int
+felix_init_vlan(felix_softc_t sc)
+{
+ int timeout = FELIX_INIT_TIMEOUT;
+ uint32_t reg;
+ int i;
+
+ /* Flush VLAN table in hardware. */
+ FELIX_WR4(sc, FELIX_ANA_VT, FELIX_ANA_VT_RESET);
+ do {
+ DELAY(1000);
+ reg = FELIX_RD4(sc, FELIX_ANA_VT);
+ if ((reg & FELIX_ANA_VT_STS) == FELIX_ANA_VT_IDLE)
+ break;
+ } while (timeout-- > 0);
+ if (timeout == 0) {
+ device_printf(sc->dev,
+ "Timeout during VLAN table reset.\n");
+ return (ETIMEDOUT);
+ }
+
+ /* Flush VLAN table in sc. */
+ for (i = 0; i < sc->info.es_nvlangroups; i++)
+ sc->vlans[i] = 0;
+
+ /*
+ * Make all ports VLAN aware.
+ * Read VID from incoming frames and use it for port grouping
+ * purposes.
+ * Don't set this if pvid is set.
+ */
+ for (i = 0; i < sc->info.es_nports; i++) {
+ reg = FELIX_ANA_PORT_RD4(sc, i, FELIX_ANA_PORT_VLAN_CFG);
+ if ((reg & FELIX_ANA_PORT_VLAN_CFG_VID_MASK) != 0)
+ continue;
+
+ reg |= FELIX_ANA_PORT_VLAN_CFG_VID_AWARE;
+ FELIX_ANA_PORT_WR4(sc, i, FELIX_ANA_PORT_VLAN_CFG, reg);
+ }
+ return (0);
+}
+
+static int
+felix_setconf(device_t dev, etherswitch_conf_t *conf)
+{
+ felix_softc_t sc;
+ int error;
+
+ error = 0;
+ /* Set the VLAN mode. */
+ sc = device_get_softc(dev);
+ FELIX_LOCK(sc);
+ if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) {
+ switch (conf->vlan_mode) {
+ case ETHERSWITCH_VLAN_DOT1Q:
+ sc->vlan_mode = ETHERSWITCH_VLAN_DOT1Q;
+ sc->info.es_nvlangroups = FELIX_NUM_VLANS;
+ error = felix_init_vlan(sc);
+ break;
+ default:
+ error = EINVAL;
+ }
+ }
+ FELIX_UNLOCK(sc);
+ return (error);
+}
+
+static void
+felix_lock(device_t dev)
+{
+ felix_softc_t sc;
+
+ sc = device_get_softc(dev);
+ FELIX_LOCK_ASSERT(sc, MA_NOTOWNED);
+ FELIX_LOCK(sc);
+}
+
+static void
+felix_unlock(device_t dev)
+{
+ felix_softc_t sc;
+
+ sc = device_get_softc(dev);
+ FELIX_LOCK_ASSERT(sc, MA_OWNED);
+ FELIX_UNLOCK(sc);
+}
+
+static void
+felix_get_port_cfg(felix_softc_t sc, etherswitch_port_t *p)
+{
+ uint32_t reg;
+
+ p->es_flags = 0;
+
+ reg = FELIX_ANA_PORT_RD4(sc, p->es_port, FELIX_ANA_PORT_DROP_CFG);
+ if (reg & FELIX_ANA_PORT_DROP_CFG_TAGGED)
+ p->es_flags |= ETHERSWITCH_PORT_DROPTAGGED;
+
+ if (reg & FELIX_ANA_PORT_DROP_CFG_UNTAGGED)
+ p->es_flags |= ETHERSWITCH_PORT_DROPUNTAGGED;
+
+ reg = FELIX_DEVGMII_PORT_RD4(sc, p->es_port, FELIX_DEVGMII_VLAN_CFG);
+ if (reg & FELIX_DEVGMII_VLAN_CFG_DOUBLE_ENA)
+ p->es_flags |= ETHERSWITCH_PORT_DOUBLE_TAG;
+
+ reg = FELIX_REW_PORT_RD4(sc, p->es_port, FELIX_REW_PORT_TAG_CFG);
+ if (reg & FELIX_REW_PORT_TAG_CFG_ALL)
+ p->es_flags |= ETHERSWITCH_PORT_ADDTAG;
+
+ reg = FELIX_ANA_PORT_RD4(sc, p->es_port, FELIX_ANA_PORT_VLAN_CFG);
+ if (reg & FELIX_ANA_PORT_VLAN_CFG_POP)
+ p->es_flags |= ETHERSWITCH_PORT_STRIPTAGINGRESS;
+
+ p->es_pvid = reg & FELIX_ANA_PORT_VLAN_CFG_VID_MASK;
+}
+
+static int
+felix_getport(device_t dev, etherswitch_port_t *p)
+{
+ struct ifmediareq *ifmr;
+ struct mii_data *mii;
+ felix_softc_t sc;
+ int error;
+
+ error = 0;
+ sc = device_get_softc(dev);
+ FELIX_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ if (p->es_port >= sc->info.es_nports || p->es_port < 0)
+ return (EINVAL);
+
+ FELIX_LOCK(sc);
+ felix_get_port_cfg(sc, p);
+ if (sc->ports[p->es_port].fixed_port) {
+ ifmr = &p->es_ifmr;
+ ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID;
+ ifmr->ifm_count = 0;
+ ifmr->ifm_active = sc->ports[p->es_port].fixed_link_status;
+ ifmr->ifm_current = ifmr->ifm_active;
+ ifmr->ifm_mask = 0;
+ } else {
+ mii = felix_miiforport(sc, p->es_port);
+ error = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr,
+ &mii->mii_media, SIOCGIFMEDIA);
+ }
+ FELIX_UNLOCK(sc);
+ return (error);
+}
+
+static void
+felix_set_port_cfg(felix_softc_t sc, etherswitch_port_t *p)
+{
+ uint32_t reg;
+
+ reg = FELIX_ANA_PORT_RD4(sc, p->es_port, FELIX_ANA_PORT_DROP_CFG);
+ if (p->es_flags & ETHERSWITCH_PORT_DROPTAGGED)
+ reg |= FELIX_ANA_PORT_DROP_CFG_TAGGED;
+ else
+ reg &= ~FELIX_ANA_PORT_DROP_CFG_TAGGED;
+
+ if (p->es_flags & ETHERSWITCH_PORT_DROPUNTAGGED)
+ reg |= FELIX_ANA_PORT_DROP_CFG_UNTAGGED;
+ else
+ reg &= ~FELIX_ANA_PORT_DROP_CFG_UNTAGGED;
+
+ FELIX_ANA_PORT_WR4(sc, p->es_port, FELIX_ANA_PORT_DROP_CFG, reg);
+
+ reg = FELIX_REW_PORT_RD4(sc, p->es_port, FELIX_REW_PORT_TAG_CFG);
+ if (p->es_flags & ETHERSWITCH_PORT_ADDTAG)
+ reg |= FELIX_REW_PORT_TAG_CFG_ALL;
+ else
+ reg &= ~FELIX_REW_PORT_TAG_CFG_ALL;
+
+ FELIX_REW_PORT_WR4(sc, p->es_port, FELIX_REW_PORT_TAG_CFG, reg);
+
+ reg = FELIX_ANA_PORT_RD4(sc, p->es_port, FELIX_ANA_PORT_VLAN_CFG);
+ if (p->es_flags & ETHERSWITCH_PORT_STRIPTAGINGRESS)
+ reg |= FELIX_ANA_PORT_VLAN_CFG_POP;
+ else
+ reg &= ~FELIX_ANA_PORT_VLAN_CFG_POP;
+
+ reg &= ~FELIX_ANA_PORT_VLAN_CFG_VID_MASK;
+ reg |= p->es_pvid & FELIX_ANA_PORT_VLAN_CFG_VID_MASK;
+ /*
+ * If port VID is set use it for VLAN classification,
+ * instead of frame VID.
+ * By default the frame tag takes precedence.
+ * Force the switch to ignore it.
+ */
+ if (p->es_pvid != 0)
+ reg &= ~FELIX_ANA_PORT_VLAN_CFG_VID_AWARE;
+ else
+ reg |= FELIX_ANA_PORT_VLAN_CFG_VID_AWARE;
+
+ FELIX_ANA_PORT_WR4(sc, p->es_port, FELIX_ANA_PORT_VLAN_CFG, reg);
+}
+
+static int
+felix_setport(device_t dev, etherswitch_port_t *p)
+{
+ felix_softc_t sc;
+ struct mii_data *mii;
+ int error;
+
+ error = 0;
+ sc = device_get_softc(dev);
+ FELIX_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ if (p->es_port >= sc->info.es_nports || p->es_port < 0)
+ return (EINVAL);
+
+ FELIX_LOCK(sc);
+ felix_set_port_cfg(sc, p);
+ if (felix_is_phyport(sc, p->es_port)) {
+ mii = felix_miiforport(sc, p->es_port);
+ error = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media,
+ SIOCSIFMEDIA);
+ }
+ FELIX_UNLOCK(sc);
+
+ return (error);
+}
+
+static int
+felix_readreg_wrapper(device_t dev, int addr_reg)
+{
+ felix_softc_t sc;
+
+ sc = device_get_softc(dev);
+ if (addr_reg > rman_get_size(sc->regs))
+ return (UINT32_MAX); /* Can't return errors here. */
+
+ return (FELIX_RD4(sc, addr_reg));
+}
+
+static int
+felix_writereg_wrapper(device_t dev, int addr_reg, int val)
+{
+ felix_softc_t sc;
+
+ sc = device_get_softc(dev);
+ if (addr_reg > rman_get_size(sc->regs))
+ return (EINVAL);
+
+ FELIX_WR4(sc, addr_reg, val);
+ return (0);
+}
+
+static int
+felix_readphy(device_t dev, int phy, int reg)
+{
+ felix_softc_t sc;
+
+ sc = device_get_softc(dev);
+
+ return (enetc_mdio_read(sc->mdio, FELIX_MDIO_BASE, phy, reg));
+}
+
+static int
+felix_writephy(device_t dev, int phy, int reg, int data)
+{
+ felix_softc_t sc;
+
+ sc = device_get_softc(dev);
+
+ return (enetc_mdio_write(sc->mdio, FELIX_MDIO_BASE, phy, reg, data));
+}
+
+static int
+felix_set_dot1q_vlan(felix_softc_t sc, etherswitch_vlangroup_t *vg)
+{
+ uint32_t reg;
+ int i, vid;
+
+ vid = vg->es_vid & ETHERSWITCH_VID_MASK;
+
+ /* Tagged mode is not supported. */
+ if (vg->es_member_ports != vg->es_untagged_ports)
+ return (EINVAL);
+
+ /*
+ * Hardware support 4096 groups, but we can't do group_id == vid.
+ * Note that hw_group_id == vid.
+ */
+ if (vid == 0) {
+ /* Clear VLAN table entry using old VID. */
+ FELIX_WR4(sc, FELIX_ANA_VTIDX, sc->vlans[vg->es_vlangroup]);
+ FELIX_WR4(sc, FELIX_ANA_VT, FELIX_ANA_VT_WRITE);
+ sc->vlans[vg->es_vlangroup] = 0;
+ return (0);
+ }
+
+ /* The VID is already used in a different group. */
+ for (i = 0; i < sc->info.es_nvlangroups; i++)
+ if (i != vg->es_vlangroup && vid == sc->vlans[i])
+ return (EINVAL);
+
+ /* This group already uses a different VID. */
+ if (sc->vlans[vg->es_vlangroup] != 0 &&
+ sc->vlans[vg->es_vlangroup] != vid)
+ return (EINVAL);
+
+ sc->vlans[vg->es_vlangroup] = vid;
+
+ /* Assign members to the given group. */
+ reg = vg->es_member_ports & FELIX_ANA_VT_PORTMASK_MASK;
+ reg <<= FELIX_ANA_VT_PORTMASK_SHIFT;
+ reg |= FELIX_ANA_VT_WRITE;
+
+ FELIX_WR4(sc, FELIX_ANA_VTIDX, vid);
+ FELIX_WR4(sc, FELIX_ANA_VT, reg);
+
+ /*
+ * According to documentation read and write commands
+ * are instant.
+ * Add a small delay just to be safe.
+ */
+ mb();
+ DELAY(100);
+ reg = FELIX_RD4(sc, FELIX_ANA_VT);
+ if ((reg & FELIX_ANA_VT_STS) != FELIX_ANA_VT_IDLE)
+ return (ENXIO);
+
+ return (0);
+}
+
+static int
+felix_setvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ felix_softc_t sc;
+ int error;
+
+ sc = device_get_softc(dev);
+
+ FELIX_LOCK(sc);
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q)
+ error = felix_set_dot1q_vlan(sc, vg);
+ else
+ error = EINVAL;
+
+ FELIX_UNLOCK(sc);
+ return (error);
+}
+
+static int
+felix_get_dot1q_vlan(felix_softc_t sc, etherswitch_vlangroup_t *vg)
+{
+ uint32_t reg;
+ int vid;
+
+ vid = sc->vlans[vg->es_vlangroup];
+
+ if (vid == 0)
+ return (0);
+
+ FELIX_WR4(sc, FELIX_ANA_VTIDX, vid);
+ FELIX_WR4(sc, FELIX_ANA_VT, FELIX_ANA_VT_READ);
+
+ /*
+ * According to documentation read and write commands
+ * are instant.
+ * Add a small delay just to be safe.
+ */
+ mb();
+ DELAY(100);
+ reg = FELIX_RD4(sc, FELIX_ANA_VT);
+ if ((reg & FELIX_ANA_VT_STS) != FELIX_ANA_VT_IDLE)
+ return (ENXIO);
+
+ reg >>= FELIX_ANA_VT_PORTMASK_SHIFT;
+ reg &= FELIX_ANA_VT_PORTMASK_MASK;
+
+ vg->es_untagged_ports = vg->es_member_ports = reg;
+ vg->es_fid = 0;
+ vg->es_vid = vid | ETHERSWITCH_VID_VALID;
+ return (0);
+}
+
+static int
+felix_getvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ felix_softc_t sc;
+ int error;
+
+ sc = device_get_softc(dev);
+
+ FELIX_LOCK(sc);
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q)
+ error = felix_get_dot1q_vlan(sc, vg);
+ else
+ error = EINVAL;
+
+ FELIX_UNLOCK(sc);
+ return (error);
+}
+
+static void
+felix_tick(void *arg)
+{
+ struct mii_data *mii;
+ felix_softc_t sc;
+ int port;
+
+ sc = arg;
+
+ FELIX_LOCK_ASSERT(sc, MA_OWNED);
+
+ for (port = 0; port < sc->info.es_nports; port++) {
+ if (!felix_is_phyport(sc, port))
+ continue;
+
+ mii = felix_miiforport(sc, port);
+ MPASS(mii != NULL);
+ mii_tick(mii);
+ }
+ if (sc->timer_ticks != 0)
+ callout_reset(&sc->tick_callout, sc->timer_ticks, felix_tick, sc);
+}
+
+static int
+felix_ifmedia_upd(if_t ifp)
+{
+ struct mii_data *mii;
+ felix_softc_t sc;
+
+ sc = if_getsoftc(ifp);
+ mii = felix_miiforport(sc, if_getdunit(ifp));
+ if (mii == NULL)
+ return (ENXIO);
+
+ mii_mediachg(mii);
+ return (0);
+}
+
+static void
+felix_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ felix_softc_t sc;
+ struct mii_data *mii;
+
+ sc = if_getsoftc(ifp);
+ mii = felix_miiforport(sc, if_getdunit(ifp));
+ if (mii == NULL)
+ return;
+
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+}
+
+static bool
+felix_is_phyport(felix_softc_t sc, int port)
+{
+
+ return (!sc->ports[port].fixed_port);
+}
+
+static struct mii_data*
+felix_miiforport(felix_softc_t sc, unsigned int port)
+{
+
+ if (!felix_is_phyport(sc, port))
+ return (NULL);
+
+ return (device_get_softc(sc->ports[port].miibus));
+}
diff --git a/sys/dev/etherswitch/felix/felix_reg.h b/sys/dev/etherswitch/felix/felix_reg.h
new file mode 100644
index 000000000000..4e416ca1e65d
--- /dev/null
+++ b/sys/dev/etherswitch/felix/felix_reg.h
@@ -0,0 +1,102 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2021 Alstom Group.
+ * Copyright (c) 2021 Semihalf.
+ *
+ * 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 _FELIX_REG_H_
+#define _FELIX_REG_H_
+
+#define BIT(x) (1UL << (x))
+
+#define FELIX_MDIO_BASE 0x1C00
+
+#define FELIX_DEVCPU_GCB_RST 0x70004
+#define FELIX_DEVCPU_GCB_RST_EN BIT(0)
+
+#define FELIX_ANA_VT 0x287F34
+#define FELIX_ANA_VT_PORTMASK_SHIFT 2
+#define FELIX_ANA_VT_PORTMASK_MASK 0x7F
+#define FELIX_ANA_VT_STS (BIT(0) | BIT(1))
+#define FELIX_ANA_VT_RESET (BIT(0) | BIT(1))
+#define FELIX_ANA_VT_WRITE BIT(1)
+#define FELIX_ANA_VT_READ BIT(0)
+#define FELIX_ANA_VT_IDLE 0
+#define FELIX_ANA_VTIDX 0x287F38
+
+#define FELIX_ANA_PORT_BASE 0x287800
+#define FELIX_ANA_PORT_OFFSET 0x100
+#define FELIX_ANA_PORT_VLAN_CFG 0x0
+#define FELIX_ANA_PORT_VLAN_CFG_VID_MASK 0xFFF
+#define FELIX_ANA_PORT_VLAN_CFG_POP BIT(18)
+#define FELIX_ANA_PORT_VLAN_CFG_VID_AWARE BIT(20)
+#define FELIX_ANA_PORT_DROP_CFG 0x4
+#define FELIX_ANA_PORT_DROP_CFG_MULTI BIT(0)
+#define FELIX_ANA_PORT_DROP_CFG_NULL BIT(1) /* SRC, or DST MAC == 0 */
+#define FELIX_ANA_PORT_DROP_CFG_CTAGGED_PRIO BIT(2) /* 0x8100, VID == 0 */
+#define FELIX_ANA_PORT_DROP_CFG_STAGGED_PRIO BIT(3) /* 0x88A8, VID == 0 */
+#define FELIX_ANA_PORT_DROP_CFG_CTAGGED BIT(4) /* 0x8100 */
+#define FELIX_ANA_PORT_DROP_CFG_STAGGED BIT(5) /* 0x88A8 */
+#define FELIX_ANA_PORT_DROP_CFG_UNTAGGED BIT(6)
+#define FELIX_ANA_PORT_DROP_CFG_TAGGED \
+ (FELIX_ANA_PORT_DROP_CFG_CTAGGED_PRIO | \
+ FELIX_ANA_PORT_DROP_CFG_STAGGED_PRIO | \
+ FELIX_ANA_PORT_DROP_CFG_CTAGGED | \
+ FELIX_ANA_PORT_DROP_CFG_STAGGED)
+
+#define FELIX_DEVGMII_BASE 0x100000
+#define FELIX_DEVGMII_PORT_OFFSET 0x010000
+
+#define FELIX_DEVGMII_CLK_CFG 0x0
+#define FELIX_DEVGMII_CLK_CFG_SPEED_1000 1
+#define FELIX_DEVGMII_CLK_CFG_SPEED_100 2
+#define FELIX_DEVGMII_CLK_CFG_SPEED_10 3
+
+#define FELIX_DEVGMII_MAC_CFG 0x1c
+#define FELIX_DEVGMII_MAC_CFG_TX_ENA BIT(0)
+#define FELIX_DEVGMII_MAC_CFG_RX_ENA BIT(4)
+
+#define FELIX_DEVGMII_VLAN_CFG 0x28
+#define FELIX_DEVGMII_VLAN_CFG_ENA BIT(0) /* Accept 0x8100 only. */
+#define FELIX_DEVGMII_VLAN_CFG_DOUBLE_ENA BIT(1) /* Inner tag can only be 0x8100. */
+#define FELIX_DEVGMII_VLAN_CFG_LEN_ENA BIT(2) /* Enable VLANMTU. */
+
+#define FELIX_REW_PORT_BASE 0x030000
+#define FELIX_REW_PORT_OFFSET 0x80
+#define FELIX_REW_PORT_TAG_CFG 0x4
+#define FELIX_REW_PORT_TAG_CFG_MASK (BIT(7) | BIT(8))
+#define FELIX_REW_PORT_TAG_CFG_DIS (0 << 7) /* Port tagging disabled */
+#define FELIX_REW_PORT_TAG_CFG_ALL (2 << 7) /* Tag frames if pvid != 0 */
+
+#define FELIX_SYS_RAM_CTRL 0x10F24
+#define FELIX_SYS_RAM_CTRL_INIT BIT(1)
+
+#define FELIX_SYS_CFG 0x10E00
+#define FELIX_SYS_CFG_CORE_EN BIT(0)
+
+#define FELIX_QSYS_PORT_MODE(port) (0x20F480 + 4*(port))
+#define FELIX_QSYS_PORT_MODE_PORT_ENA BIT(14)
+
+#endif
diff --git a/sys/dev/etherswitch/felix/felix_var.h b/sys/dev/etherswitch/felix/felix_var.h
new file mode 100644
index 000000000000..40ef1067a51b
--- /dev/null
+++ b/sys/dev/etherswitch/felix/felix_var.h
@@ -0,0 +1,109 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2021 Alstom Group.
+ * Copyright (c) 2021 Semihalf.
+ *
+ * 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 _FELIX_VAR_H_
+#define _FELIX_VAR_H_
+
+#define FELIX_INIT_TIMEOUT 5000 /* msec */
+
+#define FELIX_DEV_NAME "Felix TSN Switch driver"
+#define FELIX_MAX_PORTS 6
+#define FELIX_NUM_VLANS 4096
+
+#define PCI_VENDOR_FREESCALE 0x1957
+#define FELIX_DEV_ID 0xEEF0
+
+#define FELIX_BAR_MDIO 0
+#define FELIX_BAR_REGS 4
+
+#define FELIX_LOCK(_sc) mtx_lock(&(_sc)->mtx)
+#define FELIX_UNLOCK(_sc) mtx_unlock(&(_sc)->mtx)
+#define FELIX_LOCK_ASSERT(_sc, _what) mtx_assert(&(_sc)->mtx, (_what))
+
+#define FELIX_RD4(sc, reg) bus_read_4((sc)->regs, reg)
+#define FELIX_WR4(sc, reg, value) bus_write_4((sc)->regs, reg, value)
+
+#define FELIX_DEVGMII_PORT_RD4(sc, port, reg) \
+ FELIX_RD4(sc, \
+ FELIX_DEVGMII_BASE + (FELIX_DEVGMII_PORT_OFFSET * (port)) + reg)
+#define FELIX_DEVGMII_PORT_WR4(sc, port, reg, value) \
+ FELIX_WR4(sc, \
+ FELIX_DEVGMII_BASE + (FELIX_DEVGMII_PORT_OFFSET * (port)) + reg, \
+ value)
+
+#define FELIX_ANA_PORT_RD4(sc, port, reg) \
+ FELIX_RD4(sc, \
+ FELIX_ANA_PORT_BASE + (FELIX_ANA_PORT_OFFSET * (port)) + reg)
+#define FELIX_ANA_PORT_WR4(sc, port, reg, value) \
+ FELIX_WR4(sc, \
+ FELIX_ANA_PORT_BASE + (FELIX_ANA_PORT_OFFSET * (port)) + reg, \
+ value)
+
+#define FELIX_REW_PORT_RD4(sc, port, reg) \
+ FELIX_RD4(sc, \
+ FELIX_REW_PORT_BASE + (FELIX_REW_PORT_OFFSET * (port)) + reg)
+#define FELIX_REW_PORT_WR4(sc, port, reg, value) \
+ FELIX_WR4(sc, \
+ FELIX_REW_PORT_BASE + (FELIX_REW_PORT_OFFSET * (port)) + reg, \
+ value)
+
+struct felix_pci_id {
+ uint16_t vendor;
+ uint16_t device;
+ const char *desc;
+};
+
+struct felix_port {
+ if_t ifp;
+ device_t miibus;
+ char *ifname;
+
+ uint32_t phyaddr;
+
+ int fixed_link_status;
+ bool fixed_port;
+ bool cpu_port;
+};
+
+typedef struct felix_softc {
+ device_t dev;
+ struct resource *regs;
+ struct resource *mdio;
+
+ etherswitch_info_t info;
+ struct callout tick_callout;
+ struct mtx mtx;
+ struct felix_port ports[FELIX_MAX_PORTS];
+
+ int vlan_mode;
+ int vlans[FELIX_NUM_VLANS];
+
+ uint32_t timer_ticks;
+} *felix_softc_t;
+
+#endif
diff --git a/sys/dev/etherswitch/infineon/adm6996fc.c b/sys/dev/etherswitch/infineon/adm6996fc.c
new file mode 100644
index 000000000000..fedab27c2610
--- /dev/null
+++ b/sys/dev/etherswitch/infineon/adm6996fc.c
@@ -0,0 +1,839 @@
+/*-
+ * Copyright (c) 2016 Hiroki Mori
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+/*
+ * This is Infineon ADM6996FC/M/MX driver code on etherswitch framework.
+ * Support PORT and DOT1Q VLAN.
+ * This code suppose ADM6996FC SDC/SDIO connect to SOC network interface
+ * MDC/MDIO.
+ * This code development on Netgear WGR614Cv7.
+ * etherswitchcfg command port option support addtag.
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.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/ethernet.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+#define ADM6996FC_PRODUCT_CODE 0x7102
+
+#define ADM6996FC_SC3 0x11
+#define ADM6996FC_VF0L 0x40
+#define ADM6996FC_VF0H 0x41
+#define ADM6996FC_CI0 0xa0
+#define ADM6996FC_CI1 0xa1
+#define ADM6996FC_PHY_C0 0x200
+
+#define ADM6996FC_PC_SHIFT 4
+#define ADM6996FC_TBV_SHIFT 5
+#define ADM6996FC_PVID_SHIFT 10
+#define ADM6996FC_OPTE_SHIFT 4
+#define ADM6996FC_VV_SHIFT 15
+
+#define ADM6996FC_PHY_SIZE 0x20
+
+MALLOC_DECLARE(M_ADM6996FC);
+MALLOC_DEFINE(M_ADM6996FC, "adm6996fc", "adm6996fc data structures");
+
+struct adm6996fc_softc {
+ struct mtx sc_mtx; /* serialize access to softc */
+ device_t sc_dev;
+ int vlan_mode;
+ int media; /* cpu port media */
+ int cpuport; /* which PHY is connected to the CPU */
+ int phymask; /* PHYs we manage */
+ int numports; /* number of ports */
+ int ifpport[MII_NPHY];
+ int *portphy;
+ char **ifname;
+ device_t **miibus;
+ if_t *ifp;
+ struct callout callout_tick;
+ etherswitch_info_t info;
+};
+
+#define ADM6996FC_LOCK(_sc) \
+ mtx_lock(&(_sc)->sc_mtx)
+#define ADM6996FC_UNLOCK(_sc) \
+ mtx_unlock(&(_sc)->sc_mtx)
+#define ADM6996FC_LOCK_ASSERT(_sc, _what) \
+ mtx_assert(&(_sc)->sc_mtx, (_what))
+#define ADM6996FC_TRYLOCK(_sc) \
+ mtx_trylock(&(_sc)->sc_mtx)
+
+#if defined(DEBUG)
+#define DPRINTF(dev, args...) device_printf(dev, args)
+#else
+#define DPRINTF(dev, args...)
+#endif
+
+static inline int adm6996fc_portforphy(struct adm6996fc_softc *, int);
+static void adm6996fc_tick(void *);
+static int adm6996fc_ifmedia_upd(if_t);
+static void adm6996fc_ifmedia_sts(if_t, struct ifmediareq *);
+
+#define ADM6996FC_READREG(dev, x) \
+ MDIO_READREG(dev, ((x) >> 5), ((x) & 0x1f));
+#define ADM6996FC_WRITEREG(dev, x, v) \
+ MDIO_WRITEREG(dev, ((x) >> 5), ((x) & 0x1f), v);
+
+#define ADM6996FC_PVIDBYDATA(data1, data2) \
+ ((((data1) >> ADM6996FC_PVID_SHIFT) & 0x0f) | ((data2) << 4))
+
+static int
+adm6996fc_probe(device_t dev)
+{
+ int data1, data2;
+ int pc;
+ struct adm6996fc_softc *sc;
+
+ sc = device_get_softc(dev);
+ bzero(sc, sizeof(*sc));
+
+ data1 = ADM6996FC_READREG(device_get_parent(dev), ADM6996FC_CI0);
+ data2 = ADM6996FC_READREG(device_get_parent(dev), ADM6996FC_CI1);
+ pc = ((data2 << 16) | data1) >> ADM6996FC_PC_SHIFT;
+ if (bootverbose)
+ device_printf(dev,"Chip Identifier Register %x %x\n", data1,
+ data2);
+
+ /* check Product Code */
+ if (pc != ADM6996FC_PRODUCT_CODE) {
+ return (ENXIO);
+ }
+
+ device_set_desc(dev, "Infineon ADM6996FC/M/MX MDIO switch driver");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+adm6996fc_attach_phys(struct adm6996fc_softc *sc)
+{
+ int phy, port, err;
+ char name[IFNAMSIZ];
+
+ port = 0;
+ err = 0;
+ /* PHYs need an interface, so we generate a dummy one */
+ snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->sc_dev));
+ for (phy = 0; phy < sc->numports; phy++) {
+ if (((1 << phy) & sc->phymask) == 0)
+ continue;
+ sc->ifpport[phy] = port;
+ sc->portphy[port] = phy;
+ sc->ifp[port] = if_alloc(IFT_ETHER);
+ if_setsoftc(sc->ifp[port], sc);
+ if_setflagbits(sc->ifp[port], IFF_UP | IFF_BROADCAST |
+ IFF_DRV_RUNNING | IFF_SIMPLEX, 0);
+ if_initname(sc->ifp[port], name, port);
+ sc->miibus[port] = malloc(sizeof(device_t), M_ADM6996FC,
+ M_WAITOK | M_ZERO);
+ err = mii_attach(sc->sc_dev, sc->miibus[port], sc->ifp[port],
+ adm6996fc_ifmedia_upd, adm6996fc_ifmedia_sts, \
+ BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0);
+ DPRINTF(sc->sc_dev, "%s attached to pseudo interface %s\n",
+ device_get_nameunit(*sc->miibus[port]),
+ if_name(sc->ifp[port]));
+ if (err != 0) {
+ device_printf(sc->sc_dev,
+ "attaching PHY %d failed\n",
+ phy);
+ goto failed;
+ }
+ ++port;
+ }
+ sc->info.es_nports = port;
+ if (sc->cpuport != -1) {
+ /* assume cpuport is last one */
+ sc->ifpport[sc->cpuport] = port;
+ sc->portphy[port] = sc->cpuport;
+ ++sc->info.es_nports;
+ }
+ return (0);
+
+failed:
+ for (phy = 0; phy < sc->numports; phy++) {
+ if (((1 << phy) & sc->phymask) == 0)
+ continue;
+ port = adm6996fc_portforphy(sc, phy);
+ if (sc->miibus[port] != NULL)
+ device_delete_child(sc->sc_dev, (*sc->miibus[port]));
+ if (sc->ifp[port] != NULL)
+ if_free(sc->ifp[port]);
+ if (sc->ifname[port] != NULL)
+ free(sc->ifname[port], M_ADM6996FC);
+ if (sc->miibus[port] != NULL)
+ free(sc->miibus[port], M_ADM6996FC);
+ }
+ return (err);
+}
+
+static int
+adm6996fc_attach(device_t dev)
+{
+ struct adm6996fc_softc *sc;
+ int err;
+
+ err = 0;
+ sc = device_get_softc(dev);
+
+ sc->sc_dev = dev;
+ mtx_init(&sc->sc_mtx, "adm6996fc", NULL, MTX_DEF);
+ strlcpy(sc->info.es_name, device_get_desc(dev),
+ sizeof(sc->info.es_name));
+
+ /* ADM6996FC Defaults */
+ sc->numports = 6;
+ sc->phymask = 0x1f;
+ sc->cpuport = 5;
+ sc->media = 100;
+
+ sc->info.es_nvlangroups = 16;
+ sc->info.es_vlan_caps = ETHERSWITCH_VLAN_PORT | ETHERSWITCH_VLAN_DOT1Q;
+
+ sc->ifp = malloc(sizeof(if_t) * sc->numports, M_ADM6996FC,
+ M_WAITOK | M_ZERO);
+ sc->ifname = malloc(sizeof(char *) * sc->numports, M_ADM6996FC,
+ M_WAITOK | M_ZERO);
+ sc->miibus = malloc(sizeof(device_t *) * sc->numports, M_ADM6996FC,
+ M_WAITOK | M_ZERO);
+ sc->portphy = malloc(sizeof(int) * sc->numports, M_ADM6996FC,
+ M_WAITOK | M_ZERO);
+
+ /*
+ * Attach the PHYs and complete the bus enumeration.
+ */
+ err = adm6996fc_attach_phys(sc);
+ if (err != 0)
+ goto failed;
+
+ bus_identify_children(dev);
+ bus_enumerate_hinted_children(dev);
+ bus_attach_children(dev);
+
+ callout_init(&sc->callout_tick, 0);
+
+ adm6996fc_tick(sc);
+
+ return (0);
+
+failed:
+ free(sc->portphy, M_ADM6996FC);
+ free(sc->miibus, M_ADM6996FC);
+ free(sc->ifname, M_ADM6996FC);
+ free(sc->ifp, M_ADM6996FC);
+
+ return (err);
+}
+
+static int
+adm6996fc_detach(device_t dev)
+{
+ struct adm6996fc_softc *sc;
+ int error, i, port;
+
+ sc = device_get_softc(dev);
+
+ error = bus_generic_detach(dev);
+ if (error != 0)
+ return (error);
+
+ callout_drain(&sc->callout_tick);
+
+ for (i = 0; i < MII_NPHY; i++) {
+ if (((1 << i) & sc->phymask) == 0)
+ continue;
+ port = adm6996fc_portforphy(sc, i);
+ if (sc->ifp[port] != NULL)
+ if_free(sc->ifp[port]);
+ free(sc->ifname[port], M_ADM6996FC);
+ free(sc->miibus[port], M_ADM6996FC);
+ }
+
+ free(sc->portphy, M_ADM6996FC);
+ free(sc->miibus, M_ADM6996FC);
+ free(sc->ifname, M_ADM6996FC);
+ free(sc->ifp, M_ADM6996FC);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+/*
+ * Convert PHY number to port number.
+ */
+static inline int
+adm6996fc_portforphy(struct adm6996fc_softc *sc, int phy)
+{
+
+ return (sc->ifpport[phy]);
+}
+
+static inline struct mii_data *
+adm6996fc_miiforport(struct adm6996fc_softc *sc, int port)
+{
+
+ if (port < 0 || port > sc->numports)
+ return (NULL);
+ if (port == sc->cpuport)
+ return (NULL);
+ return (device_get_softc(*sc->miibus[port]));
+}
+
+static inline if_t
+adm6996fc_ifpforport(struct adm6996fc_softc *sc, int port)
+{
+
+ if (port < 0 || port > sc->numports)
+ return (NULL);
+ return (sc->ifp[port]);
+}
+
+/*
+ * Poll the status for all PHYs.
+ */
+static void
+adm6996fc_miipollstat(struct adm6996fc_softc *sc)
+{
+ int i, port;
+ struct mii_data *mii;
+ struct mii_softc *miisc;
+
+ ADM6996FC_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ for (i = 0; i < MII_NPHY; i++) {
+ if (((1 << i) & sc->phymask) == 0)
+ continue;
+ port = adm6996fc_portforphy(sc, i);
+ if ((*sc->miibus[port]) == NULL)
+ continue;
+ mii = device_get_softc(*sc->miibus[port]);
+ 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);
+ }
+ }
+}
+
+static void
+adm6996fc_tick(void *arg)
+{
+ struct adm6996fc_softc *sc;
+
+ sc = arg;
+
+ adm6996fc_miipollstat(sc);
+ callout_reset(&sc->callout_tick, hz, adm6996fc_tick, sc);
+}
+
+static void
+adm6996fc_lock(device_t dev)
+{
+ struct adm6996fc_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ ADM6996FC_LOCK_ASSERT(sc, MA_NOTOWNED);
+ ADM6996FC_LOCK(sc);
+}
+
+static void
+adm6996fc_unlock(device_t dev)
+{
+ struct adm6996fc_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ ADM6996FC_LOCK_ASSERT(sc, MA_OWNED);
+ ADM6996FC_UNLOCK(sc);
+}
+
+static etherswitch_info_t *
+adm6996fc_getinfo(device_t dev)
+{
+ struct adm6996fc_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ return (&sc->info);
+}
+
+static int
+adm6996fc_getport(device_t dev, etherswitch_port_t *p)
+{
+ struct adm6996fc_softc *sc;
+ struct mii_data *mii;
+ struct ifmediareq *ifmr;
+ device_t parent;
+ int err, phy;
+ int data1, data2;
+
+ int bcaddr[6] = {0x01, 0x03, 0x05, 0x07, 0x08, 0x09};
+ int vidaddr[6] = {0x28, 0x29, 0x2a, 0x2b, 0x2b, 0x2c};
+
+ sc = device_get_softc(dev);
+ ifmr = &p->es_ifmr;
+
+ if (p->es_port < 0 || p->es_port >= sc->numports)
+ return (ENXIO);
+
+ parent = device_get_parent(dev);
+
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ data1 = ADM6996FC_READREG(parent, bcaddr[p->es_port]);
+ data2 = ADM6996FC_READREG(parent, vidaddr[p->es_port]);
+ /* only port 4 is hi bit */
+ if (p->es_port == 4)
+ data2 = (data2 >> 8) & 0xff;
+ else
+ data2 = data2 & 0xff;
+
+ p->es_pvid = ADM6996FC_PVIDBYDATA(data1, data2);
+ if (((data1 >> ADM6996FC_OPTE_SHIFT) & 0x01) == 1)
+ p->es_flags |= ETHERSWITCH_PORT_ADDTAG;
+ } else {
+ p->es_pvid = 0;
+ }
+
+ phy = sc->portphy[p->es_port];
+ mii = adm6996fc_miiforport(sc, p->es_port);
+ if (sc->cpuport != -1 && phy == sc->cpuport) {
+ /* fill in fixed values for CPU port */
+ p->es_flags |= ETHERSWITCH_PORT_CPU;
+ ifmr->ifm_count = 0;
+ if (sc->media == 100)
+ ifmr->ifm_current = ifmr->ifm_active =
+ IFM_ETHER | IFM_100_TX | IFM_FDX;
+ else
+ 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) {
+ err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr,
+ &mii->mii_media, SIOCGIFMEDIA);
+ if (err)
+ return (err);
+ } else {
+ return (ENXIO);
+ }
+ return (0);
+}
+
+static int
+adm6996fc_setport(device_t dev, etherswitch_port_t *p)
+{
+ struct adm6996fc_softc *sc;
+ struct ifmedia *ifm;
+ struct mii_data *mii;
+ if_t ifp;
+ device_t parent;
+ int err;
+ int data;
+
+ int bcaddr[6] = {0x01, 0x03, 0x05, 0x07, 0x08, 0x09};
+ int vidaddr[6] = {0x28, 0x29, 0x2a, 0x2b, 0x2b, 0x2c};
+
+ sc = device_get_softc(dev);
+ parent = device_get_parent(dev);
+
+ if (p->es_port < 0 || p->es_port >= sc->numports)
+ return (ENXIO);
+
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ data = ADM6996FC_READREG(parent, bcaddr[p->es_port]);
+ data &= ~(0xf << 10);
+ data |= (p->es_pvid & 0xf) << ADM6996FC_PVID_SHIFT;
+ if (p->es_flags & ETHERSWITCH_PORT_ADDTAG)
+ data |= 1 << ADM6996FC_OPTE_SHIFT;
+ else
+ data &= ~(1 << ADM6996FC_OPTE_SHIFT);
+ ADM6996FC_WRITEREG(parent, bcaddr[p->es_port], data);
+ data = ADM6996FC_READREG(parent, vidaddr[p->es_port]);
+ /* only port 4 is hi bit */
+ if (p->es_port == 4) {
+ data &= ~(0xff << 8);
+ data = data | (((p->es_pvid >> 4) & 0xff) << 8);
+ } else {
+ data &= ~0xff;
+ data = data | ((p->es_pvid >> 4) & 0xff);
+ }
+ ADM6996FC_WRITEREG(parent, vidaddr[p->es_port], data);
+ err = 0;
+ } else {
+ if (sc->portphy[p->es_port] == sc->cpuport)
+ return (ENXIO);
+ }
+
+ if (sc->portphy[p->es_port] != sc->cpuport) {
+ mii = adm6996fc_miiforport(sc, p->es_port);
+ if (mii == NULL)
+ return (ENXIO);
+
+ ifp = adm6996fc_ifpforport(sc, p->es_port);
+
+ ifm = &mii->mii_media;
+ err = ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA);
+ }
+ return (err);
+}
+
+static int
+adm6996fc_getvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ struct adm6996fc_softc *sc;
+ device_t parent;
+ int datahi, datalo;
+
+ sc = device_get_softc(dev);
+ parent = device_get_parent(dev);
+
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) {
+ if (vg->es_vlangroup <= 5) {
+ vg->es_vid = ETHERSWITCH_VID_VALID;
+ vg->es_vid |= vg->es_vlangroup;
+ datalo = ADM6996FC_READREG(parent,
+ ADM6996FC_VF0L + 2 * vg->es_vlangroup);
+ datahi = ADM6996FC_READREG(parent,
+ ADM6996FC_VF0H + 2 * vg->es_vlangroup);
+
+ vg->es_member_ports = datalo & 0x3f;
+ vg->es_untagged_ports = vg->es_member_ports;
+ vg->es_fid = 0;
+ } else {
+ vg->es_vid = 0;
+ }
+ } else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ datalo = ADM6996FC_READREG(parent,
+ ADM6996FC_VF0L + 2 * vg->es_vlangroup);
+ datahi = ADM6996FC_READREG(parent,
+ ADM6996FC_VF0H + 2 * vg->es_vlangroup);
+
+ if (datahi & (1 << ADM6996FC_VV_SHIFT)) {
+ vg->es_vid = ETHERSWITCH_VID_VALID;
+ vg->es_vid |= datahi & 0xfff;
+ vg->es_member_ports = datalo & 0x3f;
+ vg->es_untagged_ports = (~datalo >> 6) & 0x3f;
+ vg->es_fid = 0;
+ } else {
+ vg->es_fid = 0;
+ }
+ } else {
+ vg->es_fid = 0;
+ }
+
+ return (0);
+}
+
+static int
+adm6996fc_setvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ struct adm6996fc_softc *sc;
+ device_t parent;
+
+ sc = device_get_softc(dev);
+ parent = device_get_parent(dev);
+
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) {
+ ADM6996FC_WRITEREG(parent, ADM6996FC_VF0L + 2 * vg->es_vlangroup,
+ vg->es_member_ports);
+ } else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ ADM6996FC_WRITEREG(parent, ADM6996FC_VF0L + 2 * vg->es_vlangroup,
+ vg->es_member_ports | ((~vg->es_untagged_ports & 0x3f)<< 6));
+ ADM6996FC_WRITEREG(parent, ADM6996FC_VF0H + 2 * vg->es_vlangroup,
+ (1 << ADM6996FC_VV_SHIFT) | vg->es_vid);
+ }
+
+ return (0);
+}
+
+static int
+adm6996fc_getconf(device_t dev, etherswitch_conf_t *conf)
+{
+ struct adm6996fc_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ /* Return the VLAN mode. */
+ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE;
+ conf->vlan_mode = sc->vlan_mode;
+
+ return (0);
+}
+
+static int
+adm6996fc_setconf(device_t dev, etherswitch_conf_t *conf)
+{
+ struct adm6996fc_softc *sc;
+ device_t parent;
+ int i;
+ int data;
+ int bcaddr[6] = {0x01, 0x03, 0x05, 0x07, 0x08, 0x09};
+
+ sc = device_get_softc(dev);
+ parent = device_get_parent(dev);
+
+ if ((conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) == 0)
+ return (0);
+
+ if (conf->vlan_mode == ETHERSWITCH_VLAN_PORT) {
+ sc->vlan_mode = ETHERSWITCH_VLAN_PORT;
+ data = ADM6996FC_READREG(parent, ADM6996FC_SC3);
+ data &= ~(1 << ADM6996FC_TBV_SHIFT);
+ ADM6996FC_WRITEREG(parent, ADM6996FC_SC3, data);
+ for (i = 0;i <= 5; ++i) {
+ data = ADM6996FC_READREG(parent, bcaddr[i]);
+ data &= ~(0xf << 10);
+ data |= (i << 10);
+ ADM6996FC_WRITEREG(parent, bcaddr[i], data);
+ ADM6996FC_WRITEREG(parent, ADM6996FC_VF0L + 2 * i,
+ 0x003f);
+ ADM6996FC_WRITEREG(parent, ADM6996FC_VF0H + 2 * i,
+ (1 << ADM6996FC_VV_SHIFT) | 1);
+ }
+ } else if (conf->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ sc->vlan_mode = ETHERSWITCH_VLAN_DOT1Q;
+ data = ADM6996FC_READREG(parent, ADM6996FC_SC3);
+ data |= (1 << ADM6996FC_TBV_SHIFT);
+ ADM6996FC_WRITEREG(parent, ADM6996FC_SC3, data);
+ for (i = 0;i <= 5; ++i) {
+ data = ADM6996FC_READREG(parent, bcaddr[i]);
+ /* Private VID set 1 */
+ data &= ~(0xf << 10);
+ data |= (1 << 10);
+ ADM6996FC_WRITEREG(parent, bcaddr[i], data);
+ }
+ for (i = 2;i <= 15; ++i) {
+ ADM6996FC_WRITEREG(parent, ADM6996FC_VF0H + 2 * i,
+ 0x0000);
+ }
+ } else {
+ /*
+ ADM6996FC have no VLAN off. Then set Port base and
+ add all port to member. Use VLAN Filter 1 is reset
+ default.
+ */
+ sc->vlan_mode = 0;
+ data = ADM6996FC_READREG(parent, ADM6996FC_SC3);
+ data &= ~(1 << ADM6996FC_TBV_SHIFT);
+ ADM6996FC_WRITEREG(parent, ADM6996FC_SC3, data);
+ for (i = 0;i <= 5; ++i) {
+ data = ADM6996FC_READREG(parent, bcaddr[i]);
+ data &= ~(0xf << 10);
+ data |= (1 << 10);
+ if (i == 5)
+ data &= ~(1 << 4);
+ ADM6996FC_WRITEREG(parent, bcaddr[i], data);
+ }
+ /* default setting */
+ ADM6996FC_WRITEREG(parent, ADM6996FC_VF0L + 2, 0x003f);
+ ADM6996FC_WRITEREG(parent, ADM6996FC_VF0H + 2,
+ (1 << ADM6996FC_VV_SHIFT) | 1);
+ }
+
+
+ return (0);
+}
+
+static void
+adm6996fc_statchg(device_t dev)
+{
+
+ DPRINTF(dev, "%s\n", __func__);
+}
+
+static int
+adm6996fc_ifmedia_upd(if_t ifp)
+{
+ struct adm6996fc_softc *sc;
+ struct mii_data *mii;
+
+ sc = if_getsoftc(ifp);
+ mii = adm6996fc_miiforport(sc, if_getdunit(ifp));
+
+ DPRINTF(sc->sc_dev, "%s\n", __func__);
+ if (mii == NULL)
+ return (ENXIO);
+ mii_mediachg(mii);
+ return (0);
+}
+
+static void
+adm6996fc_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct adm6996fc_softc *sc;
+ struct mii_data *mii;
+
+ sc = if_getsoftc(ifp);
+ mii = adm6996fc_miiforport(sc, if_getdunit(ifp));
+
+ DPRINTF(sc->sc_dev, "%s\n", __func__);
+
+ if (mii == NULL)
+ return;
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+}
+
+static int
+adm6996fc_readphy(device_t dev, int phy, int reg)
+{
+ struct adm6996fc_softc *sc;
+ int data;
+
+ sc = device_get_softc(dev);
+ ADM6996FC_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ if (phy < 0 || phy >= 32)
+ return (ENXIO);
+ if (reg < 0 || reg >= 32)
+ return (ENXIO);
+
+ ADM6996FC_LOCK(sc);
+ data = ADM6996FC_READREG(device_get_parent(dev),
+ (ADM6996FC_PHY_C0 + ADM6996FC_PHY_SIZE * phy) + reg);
+ ADM6996FC_UNLOCK(sc);
+
+ return (data);
+}
+
+static int
+adm6996fc_writephy(device_t dev, int phy, int reg, int data)
+{
+ struct adm6996fc_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+ ADM6996FC_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ if (phy < 0 || phy >= 32)
+ return (ENXIO);
+ if (reg < 0 || reg >= 32)
+ return (ENXIO);
+
+ ADM6996FC_LOCK(sc);
+ err = ADM6996FC_WRITEREG(device_get_parent(dev),
+ (ADM6996FC_PHY_C0 + ADM6996FC_PHY_SIZE * phy) + reg, data);
+ ADM6996FC_UNLOCK(sc);
+
+ return (err);
+}
+
+static int
+adm6996fc_readreg(device_t dev, int addr)
+{
+
+ return ADM6996FC_READREG(device_get_parent(dev), addr);
+}
+
+static int
+adm6996fc_writereg(device_t dev, int addr, int value)
+{
+ int err;
+
+ err = ADM6996FC_WRITEREG(device_get_parent(dev), addr, value);
+ return (err);
+}
+
+static device_method_t adm6996fc_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, adm6996fc_probe),
+ DEVMETHOD(device_attach, adm6996fc_attach),
+ DEVMETHOD(device_detach, adm6996fc_detach),
+
+ /* bus interface */
+ DEVMETHOD(bus_add_child, device_add_child_ordered),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, adm6996fc_readphy),
+ DEVMETHOD(miibus_writereg, adm6996fc_writephy),
+ DEVMETHOD(miibus_statchg, adm6996fc_statchg),
+
+ /* MDIO interface */
+ DEVMETHOD(mdio_readreg, adm6996fc_readphy),
+ DEVMETHOD(mdio_writereg, adm6996fc_writephy),
+
+ /* etherswitch interface */
+ DEVMETHOD(etherswitch_lock, adm6996fc_lock),
+ DEVMETHOD(etherswitch_unlock, adm6996fc_unlock),
+ DEVMETHOD(etherswitch_getinfo, adm6996fc_getinfo),
+ DEVMETHOD(etherswitch_readreg, adm6996fc_readreg),
+ DEVMETHOD(etherswitch_writereg, adm6996fc_writereg),
+ DEVMETHOD(etherswitch_readphyreg, adm6996fc_readphy),
+ DEVMETHOD(etherswitch_writephyreg, adm6996fc_writephy),
+ DEVMETHOD(etherswitch_getport, adm6996fc_getport),
+ DEVMETHOD(etherswitch_setport, adm6996fc_setport),
+ DEVMETHOD(etherswitch_getvgroup, adm6996fc_getvgroup),
+ DEVMETHOD(etherswitch_setvgroup, adm6996fc_setvgroup),
+ DEVMETHOD(etherswitch_setconf, adm6996fc_setconf),
+ DEVMETHOD(etherswitch_getconf, adm6996fc_getconf),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(adm6996fc, adm6996fc_driver, adm6996fc_methods,
+ sizeof(struct adm6996fc_softc));
+
+DRIVER_MODULE(adm6996fc, mdio, adm6996fc_driver, 0, 0);
+DRIVER_MODULE(miibus, adm6996fc, miibus_driver, 0, 0);
+DRIVER_MODULE(mdio, adm6996fc, mdio_driver, 0, 0);
+DRIVER_MODULE(etherswitch, adm6996fc, etherswitch_driver, 0, 0);
+MODULE_VERSION(adm6996fc, 1);
+MODULE_DEPEND(adm6996fc, miibus, 1, 1, 1); /* XXX which versions? */
+MODULE_DEPEND(adm6996fc, etherswitch, 1, 1, 1); /* XXX which versions? */
diff --git a/sys/dev/etherswitch/ip17x/ip175c.c b/sys/dev/etherswitch/ip17x/ip175c.c
new file mode 100644
index 000000000000..81b6deea6bb9
--- /dev/null
+++ b/sys/dev/etherswitch/ip17x/ip175c.c
@@ -0,0 +1,256 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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/kernel.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/systm.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+
+#include <dev/mii/mii.h>
+
+#include <dev/etherswitch/etherswitch.h>
+#include <dev/etherswitch/ip17x/ip17x_phy.h>
+#include <dev/etherswitch/ip17x/ip17x_reg.h>
+#include <dev/etherswitch/ip17x/ip17x_var.h>
+#include <dev/etherswitch/ip17x/ip17x_vlans.h>
+#include <dev/etherswitch/ip17x/ip175c.h>
+
+/*
+ * IP175C specific functions.
+ */
+
+/*
+ * Reset the switch.
+ */
+static int
+ip175c_reset(struct ip17x_softc *sc)
+{
+ uint32_t data;
+
+ /* Reset all the switch settings. */
+ if (ip17x_writephy(sc->sc_dev, IP175C_RESET_PHY, IP175C_RESET_REG,
+ 0x175c))
+ return (-1);
+ DELAY(2000);
+
+ /* Force IP175C mode. */
+ data = ip17x_readphy(sc->sc_dev, IP175C_MODE_PHY, IP175C_MODE_REG);
+ if (data == 0x175a) {
+ if (ip17x_writephy(sc->sc_dev, IP175C_MODE_PHY, IP175C_MODE_REG,
+ 0x175c))
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+ip175c_port_vlan_setup(struct ip17x_softc *sc)
+{
+ struct ip17x_vlan *v;
+ uint32_t ports[IP175X_NUM_PORTS], reg[IP175X_NUM_PORTS/2];
+ int i, err, phy;
+
+ KASSERT(sc->cpuport == 5, ("cpuport != 5 not supported for IP175C"));
+ KASSERT(sc->numports == 6, ("numports != 6 not supported for IP175C"));
+
+ /* Build the port access masks. */
+ memset(ports, 0, sizeof(ports));
+ for (i = 0; i < sc->info.es_nports; i++) {
+ phy = sc->portphy[i];
+ v = &sc->vlan[i];
+ ports[phy] = v->ports;
+ }
+
+ /* Move the cpuport bit to its correct place. */
+ for (i = 0; i < sc->numports; i++) {
+ if (ports[i] & (1 << sc->cpuport)) {
+ ports[i] |= (1 << 7);
+ ports[i] &= ~(1 << sc->cpuport);
+ }
+ }
+
+ /* And now build the switch register data. */
+ memset(reg, 0, sizeof(reg));
+ for (i = 0; i < (sc->numports / 2); i++)
+ reg[i] = ports[i * 2] << 8 | ports[i * 2 + 1];
+
+ /* Update the switch resgisters. */
+ err = ip17x_writephy(sc->sc_dev, 29, 19, reg[0]);
+ if (err == 0)
+ err = ip17x_writephy(sc->sc_dev, 29, 20, reg[1]);
+ if (err == 0)
+ err = ip17x_updatephy(sc->sc_dev, 29, 21, 0xff00, reg[2]);
+ if (err == 0)
+ err = ip17x_updatephy(sc->sc_dev, 30, 18, 0x00ff, reg[2]);
+ return (err);
+}
+
+static int
+ip175c_dot1q_vlan_setup(struct ip17x_softc *sc)
+{
+ struct ip17x_vlan *v;
+ uint32_t data;
+ uint32_t vlans[IP17X_MAX_VLANS];
+ int i, j;
+
+ KASSERT(sc->cpuport == 5, ("cpuport != 5 not supported for IP175C"));
+ KASSERT(sc->numports == 6, ("numports != 6 not supported for IP175C"));
+
+ /* Add and strip VLAN tags. */
+ data = (sc->addtag & ~(1 << IP175X_CPU_PORT)) << 11;
+ data |= (sc->striptag & ~(1 << IP175X_CPU_PORT)) << 6;
+ if (sc->addtag & (1 << IP175X_CPU_PORT))
+ data |= (1 << 1);
+ if (sc->striptag & (1 << IP175X_CPU_PORT))
+ data |= (1 << 0);
+ if (ip17x_writephy(sc->sc_dev, 29, 23, data))
+ return (-1);
+
+ /* Set the VID_IDX_SEL to 0. */
+ if (ip17x_updatephy(sc->sc_dev, 30, 9, 0x70, 0))
+ return (-1);
+
+ /* Calculate the port masks. */
+ memset(vlans, 0, sizeof(vlans));
+ for (i = 0; i < IP17X_MAX_VLANS; i++) {
+ v = &sc->vlan[i];
+ if ((v->vlanid & ETHERSWITCH_VID_VALID) == 0)
+ continue;
+ vlans[v->vlanid & ETHERSWITCH_VID_MASK] = v->ports;
+ }
+
+ for (j = 0, i = 1; i <= IP17X_MAX_VLANS / 2; i++) {
+ data = vlans[j++] & 0x3f;
+ data |= (vlans[j++] & 0x3f) << 8;
+ if (ip17x_writephy(sc->sc_dev, 30, i, data))
+ return (-1);
+ }
+
+ /* Port default VLAN ID. */
+ for (i = 0; i < sc->numports; i++) {
+ if (i == IP175X_CPU_PORT) {
+ if (ip17x_writephy(sc->sc_dev, 29, 30, sc->pvid[i]))
+ return (-1);
+ } else {
+ if (ip17x_writephy(sc->sc_dev, 29, 24 + i, sc->pvid[i]))
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * Set the Switch configuration.
+ */
+static int
+ip175c_hw_setup(struct ip17x_softc *sc)
+{
+
+ switch (sc->vlan_mode) {
+ case ETHERSWITCH_VLAN_PORT:
+ return (ip175c_port_vlan_setup(sc));
+ break;
+ case ETHERSWITCH_VLAN_DOT1Q:
+ return (ip175c_dot1q_vlan_setup(sc));
+ break;
+ }
+ return (-1);
+}
+
+/*
+ * Set the switch VLAN mode.
+ */
+static int
+ip175c_set_vlan_mode(struct ip17x_softc *sc, uint32_t mode)
+{
+
+ switch (mode) {
+ case ETHERSWITCH_VLAN_DOT1Q:
+ /* Enable VLAN tag processing. */
+ ip17x_updatephy(sc->sc_dev, 30, 9, 0x80, 0x80);
+ sc->vlan_mode = mode;
+ break;
+ case ETHERSWITCH_VLAN_PORT:
+ default:
+ /* Disable VLAN tag processing. */
+ ip17x_updatephy(sc->sc_dev, 30, 9, 0x80, 0);
+ sc->vlan_mode = ETHERSWITCH_VLAN_PORT;
+ break;
+ }
+
+ /* Reset vlans. */
+ ip17x_reset_vlans(sc, sc->vlan_mode);
+
+ /* Update switch configuration. */
+ ip175c_hw_setup(sc);
+
+ return (0);
+}
+
+/*
+ * Get the switch VLAN mode.
+ */
+static int
+ip175c_get_vlan_mode(struct ip17x_softc *sc)
+{
+
+ return (sc->vlan_mode);
+}
+
+void
+ip175c_attach(struct ip17x_softc *sc)
+{
+ uint32_t data;
+
+ data = ip17x_readphy(sc->sc_dev, IP175C_MII_PHY, IP175C_MII_CTL_REG);
+ device_printf(sc->sc_dev, "MII: %x\n", data);
+ /* check mii1 interface if disabled then phy4 and mac4 hold on switch */
+ if((data & (1 << IP175C_MII_MII1_RMII_EN)) == 0)
+ sc->phymask |= 0x10;
+
+ sc->hal.ip17x_reset = ip175c_reset;
+ sc->hal.ip17x_hw_setup = ip175c_hw_setup;
+ sc->hal.ip17x_get_vlan_mode = ip175c_get_vlan_mode;
+ sc->hal.ip17x_set_vlan_mode = ip175c_set_vlan_mode;
+
+ /* Defaults for IP175C. */
+ sc->cpuport = IP175X_CPU_PORT;
+ sc->numports = IP175X_NUM_PORTS;
+ sc->info.es_vlan_caps = ETHERSWITCH_VLAN_PORT | ETHERSWITCH_VLAN_DOT1Q;
+
+ device_printf(sc->sc_dev, "type: IP175C\n");
+}
diff --git a/sys/dev/etherswitch/ip17x/ip175c.h b/sys/dev/etherswitch/ip17x/ip175c.h
new file mode 100644
index 000000000000..374f6ce20a58
--- /dev/null
+++ b/sys/dev/etherswitch/ip17x/ip175c.h
@@ -0,0 +1,46 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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 __IP175C_H__
+#define __IP175C_H__
+
+#define IP175C_MODE_PHY 29
+#define IP175C_MODE_REG 31
+#define IP175C_RESET_PHY 30
+#define IP175C_RESET_REG 0
+#define IP175C_MII_PHY 31
+#define IP175C_MII_CTL_REG 5
+#define IP175C_MII_MII1_RMII_EN 8
+
+#define IP175C_LAST_VLAN 15
+
+void ip175c_attach(struct ip17x_softc *sc);
+
+#endif /* __IP175C_H__ */
diff --git a/sys/dev/etherswitch/ip17x/ip175d.c b/sys/dev/etherswitch/ip17x/ip175d.c
new file mode 100644
index 000000000000..c789cb7ed689
--- /dev/null
+++ b/sys/dev/etherswitch/ip17x/ip175d.c
@@ -0,0 +1,221 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * Copyright (C) 2008 Patrick Horn.
+ * Copyright (C) 2008, 2010 Martin Mares.
+ * All rights reserved.
+ *
+ * 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/kernel.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/systm.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+
+#include <dev/mii/mii.h>
+
+#include <dev/etherswitch/etherswitch.h>
+#include <dev/etherswitch/ip17x/ip17x_phy.h>
+#include <dev/etherswitch/ip17x/ip17x_reg.h>
+#include <dev/etherswitch/ip17x/ip17x_var.h>
+#include <dev/etherswitch/ip17x/ip17x_vlans.h>
+#include <dev/etherswitch/ip17x/ip175d.h>
+
+/*
+ * IP175D specific functions.
+ */
+
+/*
+ * Reset the switch to default state.
+ */
+static int
+ip175d_reset(struct ip17x_softc *sc)
+{
+
+ /* Reset all the switch settings. */
+ ip17x_writephy(sc->sc_dev, IP175D_RESET_PHY, IP175D_RESET_REG, 0x175d);
+ DELAY(2000);
+
+ /* Disable the special tagging mode. */
+ ip17x_updatephy(sc->sc_dev, 21, 22, 0x3, 0x0);
+
+ /* Set 802.1q protocol type. */
+ ip17x_writephy(sc->sc_dev, 22, 3, 0x8100);
+
+ return (0);
+}
+
+/*
+ * Set the Switch configuration.
+ */
+static int
+ip175d_hw_setup(struct ip17x_softc *sc)
+{
+ struct ip17x_vlan *v;
+ uint32_t ports[IP17X_MAX_VLANS];
+ uint32_t addtag[IP17X_MAX_VLANS];
+ uint32_t striptag[IP17X_MAX_VLANS];
+ uint32_t vlan_mask;
+ int i, j;
+
+ vlan_mask = 0;
+ for (i = 0; i < IP17X_MAX_VLANS; i++) {
+
+ ports[i] = 0;
+ addtag[i] = 0;
+ striptag[i] = 0;
+
+ v = &sc->vlan[i];
+ if ((v->vlanid & ETHERSWITCH_VID_VALID) == 0 ||
+ sc->vlan_mode == 0) {
+ /* Vlangroup disabled. Reset the filter. */
+ ip17x_writephy(sc->sc_dev, 22, 14 + i, i + 1);
+ ports[i] = 0x3f;
+ continue;
+ }
+
+ vlan_mask |= (1 << i);
+ ports[i] = v->ports;
+
+ /* Setup the filter, write the VLAN id. */
+ ip17x_writephy(sc->sc_dev, 22, 14 + i,
+ v->vlanid & ETHERSWITCH_VID_MASK);
+
+ for (j = 0; j < MII_NPHY; j++) {
+ if ((ports[i] & (1 << j)) == 0)
+ continue;
+ if (sc->addtag & (1 << j))
+ addtag[i] |= (1 << j);
+ if (sc->striptag & (1 << j))
+ striptag[i] |= (1 << j);
+ }
+ }
+
+ /* Write the port masks, tag adds and removals. */
+ for (i = 0; i < IP17X_MAX_VLANS / 2; i++) {
+ ip17x_writephy(sc->sc_dev, 23, i,
+ ports[2 * i] | (ports[2 * i + 1] << 8));
+ ip17x_writephy(sc->sc_dev, 23, i + 8,
+ addtag[2 * i] | (addtag[2 * i + 1] << 8));
+ ip17x_writephy(sc->sc_dev, 23, i + 16,
+ striptag[2 * i] | (striptag[2 * i + 1] << 8));
+ }
+
+ /* Write the in use vlan mask. */
+ ip17x_writephy(sc->sc_dev, 22, 10, vlan_mask);
+
+ /* Write the PVID of each port. */
+ for (i = 0; i < sc->numports; i++)
+ ip17x_writephy(sc->sc_dev, 22, 4 + i, sc->pvid[i]);
+
+ return (0);
+}
+
+/*
+ * Set the switch VLAN mode.
+ */
+static int
+ip175d_set_vlan_mode(struct ip17x_softc *sc, uint32_t mode)
+{
+
+ switch (mode) {
+ case ETHERSWITCH_VLAN_DOT1Q:
+ /*
+ * VLAN classification rules: tag-based VLANs,
+ * use VID to classify, drop packets that cannot
+ * be classified.
+ */
+ ip17x_updatephy(sc->sc_dev, 22, 0, 0x3fff, 0x003f);
+ sc->vlan_mode = mode;
+ break;
+ case ETHERSWITCH_VLAN_PORT:
+ sc->vlan_mode = mode;
+ /* fallthrough */
+ default:
+ /*
+ * VLAN classification rules: everything off &
+ * clear table.
+ */
+ ip17x_updatephy(sc->sc_dev, 22, 0, 0xbfff, 0x8000);
+ sc->vlan_mode = 0;
+ break;
+ }
+
+ if (sc->vlan_mode != 0) {
+ /*
+ * Ingress rules: CFI=1 dropped, null VID is untagged, VID=1 passed,
+ * VID=0xfff discarded, admin both tagged and untagged, ingress
+ * filters enabled.
+ */
+ ip17x_updatephy(sc->sc_dev, 22, 1, 0x0fff, 0x0c3f);
+
+ /* Egress rules: IGMP processing off, keep VLAN header off. */
+ ip17x_updatephy(sc->sc_dev, 22, 2, 0x0fff, 0x0000);
+ } else {
+ ip17x_updatephy(sc->sc_dev, 22, 1, 0x0fff, 0x043f);
+ ip17x_updatephy(sc->sc_dev, 22, 2, 0x0fff, 0x0020);
+ }
+
+ /* Reset vlans. */
+ ip17x_reset_vlans(sc, sc->vlan_mode);
+
+ /* Update switch configuration. */
+ ip175d_hw_setup(sc);
+
+ return (0);
+}
+
+/*
+ * Get the switch VLAN mode.
+ */
+static int
+ip175d_get_vlan_mode(struct ip17x_softc *sc)
+{
+
+ return (sc->vlan_mode);
+}
+
+void
+ip175d_attach(struct ip17x_softc *sc)
+{
+
+ sc->hal.ip17x_reset = ip175d_reset;
+ sc->hal.ip17x_hw_setup = ip175d_hw_setup;
+ sc->hal.ip17x_get_vlan_mode = ip175d_get_vlan_mode;
+ sc->hal.ip17x_set_vlan_mode = ip175d_set_vlan_mode;
+
+ /* Defaults for IP175C. */
+ sc->cpuport = IP175X_CPU_PORT;
+ sc->numports = IP175X_NUM_PORTS;
+ sc->info.es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q;
+
+ device_printf(sc->sc_dev, "type: IP175D\n");
+}
diff --git a/sys/dev/etherswitch/ip17x/ip175d.h b/sys/dev/etherswitch/ip17x/ip175d.h
new file mode 100644
index 000000000000..b009298e81c4
--- /dev/null
+++ b/sys/dev/etherswitch/ip17x/ip175d.h
@@ -0,0 +1,43 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * Copyright (C) 2008 Patrick Horn.
+ * Copyright (C) 2008, 2010 Martin Mares.
+ * All rights reserved.
+ *
+ * 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 __IP175D_H__
+#define __IP175D_H__
+
+#define IP175D_ID_PHY 20
+#define IP175D_ID_REG 0
+#define IP175D_RESET_PHY 20
+#define IP175D_RESET_REG 2
+
+void ip175d_attach(struct ip17x_softc *sc);
+
+#endif /* __IP175D_H__ */
diff --git a/sys/dev/etherswitch/ip17x/ip17x.c b/sys/dev/etherswitch/ip17x/ip17x.c
new file mode 100644
index 000000000000..42d3bf990c0e
--- /dev/null
+++ b/sys/dev/etherswitch/ip17x/ip17x.c
@@ -0,0 +1,652 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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 "opt_platform.h"
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+#include <sys/types.h>
+
+#include <net/if.h>
+#include <net/ethernet.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+#include <net/if_var.h>
+
+#include <machine/bus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+
+#include <dev/etherswitch/etherswitch.h>
+#include <dev/etherswitch/ip17x/ip17x_phy.h>
+#include <dev/etherswitch/ip17x/ip17x_reg.h>
+#include <dev/etherswitch/ip17x/ip17x_var.h>
+#include <dev/etherswitch/ip17x/ip17x_vlans.h>
+#include <dev/etherswitch/ip17x/ip175c.h>
+#include <dev/etherswitch/ip17x/ip175d.h>
+
+#ifdef FDT
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#endif
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+MALLOC_DECLARE(M_IP17X);
+MALLOC_DEFINE(M_IP17X, "ip17x", "ip17x data structures");
+
+static void ip17x_tick(void *);
+static int ip17x_ifmedia_upd(if_t);
+static void ip17x_ifmedia_sts(if_t, struct ifmediareq *);
+
+static void
+ip17x_identify(driver_t *driver, device_t parent)
+{
+ if (device_find_child(parent, "ip17x", DEVICE_UNIT_ANY) == NULL)
+ BUS_ADD_CHILD(parent, 0, "ip17x", DEVICE_UNIT_ANY);
+}
+
+static int
+ip17x_probe(device_t dev)
+{
+ struct ip17x_softc *sc;
+ uint32_t oui, model, phy_id1, phy_id2;
+#ifdef FDT
+ phandle_t ip17x_node;
+ pcell_t cell;
+
+ ip17x_node = fdt_find_compatible(OF_finddevice("/"),
+ "icplus,ip17x", 0);
+
+ if (ip17x_node == 0)
+ return (ENXIO);
+#endif
+
+ sc = device_get_softc(dev);
+
+ /* Read ID from PHY 0. */
+ phy_id1 = MDIO_READREG(device_get_parent(dev), 0, MII_PHYIDR1);
+ phy_id2 = MDIO_READREG(device_get_parent(dev), 0, MII_PHYIDR2);
+
+ oui = MII_OUI(phy_id1, phy_id2);
+ model = MII_MODEL(phy_id2);
+ /* We only care about IC+ devices. */
+ if (oui != IP17X_OUI) {
+ device_printf(dev,
+ "Unsupported IC+ switch. Unknown OUI: %#x\n", oui);
+ return (ENXIO);
+ }
+
+ switch (model) {
+ case IP17X_IP175A:
+ sc->sc_switchtype = IP17X_SWITCH_IP175A;
+ break;
+ case IP17X_IP175C:
+ sc->sc_switchtype = IP17X_SWITCH_IP175C;
+ break;
+ default:
+ device_printf(dev, "Unsupported IC+ switch model: %#x\n",
+ model);
+ return (ENXIO);
+ }
+
+ /* IP175D has a specific ID register. */
+ model = MDIO_READREG(device_get_parent(dev), IP175D_ID_PHY,
+ IP175D_ID_REG);
+ if (model == 0x175d)
+ sc->sc_switchtype = IP17X_SWITCH_IP175D;
+ else {
+ /* IP178 has more PHYs. Try it. */
+ model = MDIO_READREG(device_get_parent(dev), 5, MII_PHYIDR1);
+ if (phy_id1 == model)
+ sc->sc_switchtype = IP17X_SWITCH_IP178C;
+ }
+
+ sc->miipoll = 1;
+#ifdef FDT
+ if ((OF_getencprop(ip17x_node, "mii-poll",
+ &cell, sizeof(cell))) > 0)
+ sc->miipoll = cell ? 1 : 0;
+#else
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "mii-poll", &sc->miipoll);
+#endif
+ device_set_desc(dev, "IC+ IP17x switch driver");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+ip17x_attach_phys(struct ip17x_softc *sc)
+{
+ int err, phy, port;
+ char name[IFNAMSIZ];
+
+ port = err = 0;
+
+ /* PHYs need an interface, so we generate a dummy one */
+ snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->sc_dev));
+ for (phy = 0; phy < MII_NPHY; phy++) {
+ if (((1 << phy) & sc->phymask) == 0)
+ continue;
+ sc->phyport[phy] = port;
+ sc->portphy[port] = phy;
+ sc->ifp[port] = if_alloc(IFT_ETHER);
+ if_setsoftc(sc->ifp[port], sc);
+ if_setflags(sc->ifp[port], IFF_UP | IFF_BROADCAST |
+ IFF_DRV_RUNNING | IFF_SIMPLEX);
+ if_initname(sc->ifp[port], name, port);
+ sc->miibus[port] = malloc(sizeof(device_t), M_IP17X,
+ M_WAITOK | M_ZERO);
+ err = mii_attach(sc->sc_dev, sc->miibus[port], sc->ifp[port],
+ ip17x_ifmedia_upd, ip17x_ifmedia_sts, \
+ BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0);
+ DPRINTF(sc->sc_dev, "%s attached to pseudo interface %s\n",
+ device_get_nameunit(*sc->miibus[port]),
+ if_name(sc->ifp[port]));
+ if (err != 0) {
+ device_printf(sc->sc_dev,
+ "attaching PHY %d failed\n",
+ phy);
+ break;
+ }
+ sc->info.es_nports = port + 1;
+ if (++port >= sc->numports)
+ break;
+ }
+ return (err);
+}
+
+static int
+ip17x_attach(device_t dev)
+{
+ struct ip17x_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+
+ sc->sc_dev = dev;
+ mtx_init(&sc->sc_mtx, "ip17x", NULL, MTX_DEF);
+ strlcpy(sc->info.es_name, device_get_desc(dev),
+ sizeof(sc->info.es_name));
+
+ /* XXX Defaults */
+ sc->phymask = 0x0f;
+ sc->media = 100;
+
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "phymask", &sc->phymask);
+
+ /* Number of vlans supported by the switch. */
+ sc->info.es_nvlangroups = IP17X_MAX_VLANS;
+
+ /* Attach the switch related functions. */
+ if (IP17X_IS_SWITCH(sc, IP175C))
+ ip175c_attach(sc);
+ else if (IP17X_IS_SWITCH(sc, IP175D))
+ ip175d_attach(sc);
+ else
+ /* We don't have support to all the models yet :-/ */
+ return (ENXIO);
+
+ /* Always attach the cpu port. */
+ sc->phymask |= (1 << sc->cpuport);
+
+ sc->ifp = malloc(sizeof(if_t) * sc->numports, M_IP17X,
+ M_WAITOK | M_ZERO);
+ sc->pvid = malloc(sizeof(uint32_t) * sc->numports, M_IP17X,
+ M_WAITOK | M_ZERO);
+ sc->miibus = malloc(sizeof(device_t *) * sc->numports, M_IP17X,
+ M_WAITOK | M_ZERO);
+ sc->portphy = malloc(sizeof(int) * sc->numports, M_IP17X,
+ M_WAITOK | M_ZERO);
+
+ /* Initialize the switch. */
+ sc->hal.ip17x_reset(sc);
+
+ /*
+ * Attach the PHYs and complete the bus enumeration.
+ */
+ err = ip17x_attach_phys(sc);
+ if (err != 0)
+ return (err);
+
+ /*
+ * Set the switch to port based vlans or disabled (if not supported
+ * on this model).
+ */
+ sc->hal.ip17x_set_vlan_mode(sc, ETHERSWITCH_VLAN_PORT);
+
+ bus_identify_children(dev);
+ bus_enumerate_hinted_children(dev);
+ bus_attach_children(dev);
+
+ if (sc->miipoll) {
+ callout_init(&sc->callout_tick, 0);
+
+ ip17x_tick(sc);
+ }
+
+ return (0);
+}
+
+static int
+ip17x_detach(device_t dev)
+{
+ struct ip17x_softc *sc;
+ int error, i, port;
+
+ error = bus_generic_detach(dev);
+ if (error != 0)
+ return (error);
+
+ sc = device_get_softc(dev);
+ if (sc->miipoll)
+ callout_drain(&sc->callout_tick);
+
+ for (i=0; i < MII_NPHY; i++) {
+ if (((1 << i) & sc->phymask) == 0)
+ continue;
+ port = sc->phyport[i];
+ if (sc->ifp[port] != NULL)
+ if_free(sc->ifp[port]);
+ free(sc->miibus[port], M_IP17X);
+ }
+
+ free(sc->portphy, M_IP17X);
+ free(sc->miibus, M_IP17X);
+ free(sc->pvid, M_IP17X);
+ free(sc->ifp, M_IP17X);
+
+ /* Reset the switch. */
+ sc->hal.ip17x_reset(sc);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static inline struct mii_data *
+ip17x_miiforport(struct ip17x_softc *sc, int port)
+{
+
+ if (port < 0 || port > sc->numports)
+ return (NULL);
+ return (device_get_softc(*sc->miibus[port]));
+}
+
+static inline if_t
+ip17x_ifpforport(struct ip17x_softc *sc, int port)
+{
+
+ if (port < 0 || port > sc->numports)
+ return (NULL);
+ return (sc->ifp[port]);
+}
+
+/*
+ * Poll the status for all PHYs.
+ */
+static void
+ip17x_miipollstat(struct ip17x_softc *sc)
+{
+ struct mii_softc *miisc;
+ struct mii_data *mii;
+ int i, port;
+
+ IP17X_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ for (i = 0; i < MII_NPHY; i++) {
+ if (((1 << i) & sc->phymask) == 0)
+ continue;
+ port = sc->phyport[i];
+ if ((*sc->miibus[port]) == NULL)
+ continue;
+ mii = device_get_softc(*sc->miibus[port]);
+ 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);
+ }
+ }
+}
+
+static void
+ip17x_tick(void *arg)
+{
+ struct ip17x_softc *sc;
+
+ sc = arg;
+ ip17x_miipollstat(sc);
+ callout_reset(&sc->callout_tick, hz, ip17x_tick, sc);
+}
+
+static void
+ip17x_lock(device_t dev)
+{
+ struct ip17x_softc *sc;
+
+ sc = device_get_softc(dev);
+ IP17X_LOCK_ASSERT(sc, MA_NOTOWNED);
+ IP17X_LOCK(sc);
+}
+
+static void
+ip17x_unlock(device_t dev)
+{
+ struct ip17x_softc *sc;
+
+ sc = device_get_softc(dev);
+ IP17X_LOCK_ASSERT(sc, MA_OWNED);
+ IP17X_UNLOCK(sc);
+}
+
+static etherswitch_info_t *
+ip17x_getinfo(device_t dev)
+{
+ struct ip17x_softc *sc;
+
+ sc = device_get_softc(dev);
+ return (&sc->info);
+}
+
+static int
+ip17x_getport(device_t dev, etherswitch_port_t *p)
+{
+ struct ip17x_softc *sc;
+ struct ifmediareq *ifmr;
+ struct mii_data *mii;
+ int err, phy;
+
+ sc = device_get_softc(dev);
+ if (p->es_port < 0 || p->es_port >= sc->numports)
+ return (ENXIO);
+
+ phy = sc->portphy[p->es_port];
+
+ /* Retrieve the PVID. */
+ p->es_pvid = sc->pvid[phy];
+
+ /* Port flags. */
+ if (sc->addtag & (1 << phy))
+ p->es_flags |= ETHERSWITCH_PORT_ADDTAG;
+ if (sc->striptag & (1 << phy))
+ p->es_flags |= ETHERSWITCH_PORT_STRIPTAG;
+
+ ifmr = &p->es_ifmr;
+
+ /* No media settings ? */
+ if (p->es_ifmr.ifm_count == 0)
+ return (0);
+
+ mii = ip17x_miiforport(sc, p->es_port);
+ if (mii == NULL)
+ return (ENXIO);
+ if (phy == sc->cpuport) {
+ /* fill in fixed values for CPU port */
+ p->es_flags |= ETHERSWITCH_PORT_CPU;
+ ifmr->ifm_count = 0;
+ if (sc->media == 100)
+ ifmr->ifm_current = ifmr->ifm_active =
+ IFM_ETHER | IFM_100_TX | IFM_FDX;
+ else
+ 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 {
+ err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr,
+ &mii->mii_media, SIOCGIFMEDIA);
+ if (err)
+ return (err);
+ }
+ return (0);
+}
+
+static int
+ip17x_setport(device_t dev, etherswitch_port_t *p)
+{
+ struct ip17x_softc *sc;
+ struct ifmedia *ifm;
+ if_t ifp;
+ struct mii_data *mii;
+ int phy;
+
+ sc = device_get_softc(dev);
+ if (p->es_port < 0 || p->es_port >= sc->numports)
+ return (ENXIO);
+
+ phy = sc->portphy[p->es_port];
+ ifp = ip17x_ifpforport(sc, p->es_port);
+ mii = ip17x_miiforport(sc, p->es_port);
+ if (ifp == NULL || mii == NULL)
+ return (ENXIO);
+
+ /* Port flags. */
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+
+ /* Set the PVID. */
+ if (p->es_pvid != 0) {
+ if (IP17X_IS_SWITCH(sc, IP175C) &&
+ p->es_pvid > IP175C_LAST_VLAN)
+ return (ENXIO);
+ sc->pvid[phy] = p->es_pvid;
+ }
+
+ /* Mutually exclusive. */
+ if (p->es_flags & ETHERSWITCH_PORT_ADDTAG &&
+ p->es_flags & ETHERSWITCH_PORT_STRIPTAG)
+ return (EINVAL);
+
+ /* Reset the settings for this port. */
+ sc->addtag &= ~(1 << phy);
+ sc->striptag &= ~(1 << phy);
+
+ /* And then set it to the new value. */
+ if (p->es_flags & ETHERSWITCH_PORT_ADDTAG)
+ sc->addtag |= (1 << phy);
+ if (p->es_flags & ETHERSWITCH_PORT_STRIPTAG)
+ sc->striptag |= (1 << phy);
+ }
+
+ /* Update the switch configuration. */
+ if (sc->hal.ip17x_hw_setup(sc))
+ return (ENXIO);
+
+ /* Do not allow media changes on CPU port. */
+ if (phy == sc->cpuport)
+ return (0);
+
+ /* No media settings ? */
+ if (p->es_ifmr.ifm_count == 0)
+ return (0);
+
+ ifm = &mii->mii_media;
+ return (ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA));
+}
+
+static void
+ip17x_statchg(device_t dev)
+{
+
+ DPRINTF(dev, "%s\n", __func__);
+}
+
+static int
+ip17x_ifmedia_upd(if_t ifp)
+{
+ struct ip17x_softc *sc;
+ struct mii_data *mii;
+
+ sc = if_getsoftc(ifp);
+ DPRINTF(sc->sc_dev, "%s\n", __func__);
+ mii = ip17x_miiforport(sc, if_getdunit(ifp));
+ if (mii == NULL)
+ return (ENXIO);
+ mii_mediachg(mii);
+
+ return (0);
+}
+
+static void
+ip17x_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct ip17x_softc *sc;
+ struct mii_data *mii;
+
+ sc = if_getsoftc(ifp);
+ DPRINTF(sc->sc_dev, "%s\n", __func__);
+ mii = ip17x_miiforport(sc, if_getdunit(ifp));
+ if (mii == NULL)
+ return;
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+}
+
+static int
+ip17x_readreg(device_t dev, int addr)
+{
+ struct ip17x_softc *sc __diagused;
+
+ sc = device_get_softc(dev);
+ IP17X_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* Not supported. */
+ return (0);
+}
+
+static int
+ip17x_writereg(device_t dev, int addr, int value)
+{
+ struct ip17x_softc *sc __diagused;
+
+ sc = device_get_softc(dev);
+ IP17X_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* Not supported. */
+ return (0);
+}
+
+static int
+ip17x_getconf(device_t dev, etherswitch_conf_t *conf)
+{
+ struct ip17x_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ /* Return the VLAN mode. */
+ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE;
+ conf->vlan_mode = sc->hal.ip17x_get_vlan_mode(sc);
+
+ return (0);
+}
+
+static int
+ip17x_setconf(device_t dev, etherswitch_conf_t *conf)
+{
+ struct ip17x_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ /* Set the VLAN mode. */
+ if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE)
+ sc->hal.ip17x_set_vlan_mode(sc, conf->vlan_mode);
+
+ return (0);
+}
+
+static device_method_t ip17x_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_identify, ip17x_identify),
+ DEVMETHOD(device_probe, ip17x_probe),
+ DEVMETHOD(device_attach, ip17x_attach),
+ DEVMETHOD(device_detach, ip17x_detach),
+
+ /* bus interface */
+ DEVMETHOD(bus_add_child, device_add_child_ordered),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, ip17x_readphy),
+ DEVMETHOD(miibus_writereg, ip17x_writephy),
+ DEVMETHOD(miibus_statchg, ip17x_statchg),
+
+ /* MDIO interface */
+ DEVMETHOD(mdio_readreg, ip17x_readphy),
+ DEVMETHOD(mdio_writereg, ip17x_writephy),
+
+ /* etherswitch interface */
+ DEVMETHOD(etherswitch_lock, ip17x_lock),
+ DEVMETHOD(etherswitch_unlock, ip17x_unlock),
+ DEVMETHOD(etherswitch_getinfo, ip17x_getinfo),
+ DEVMETHOD(etherswitch_readreg, ip17x_readreg),
+ DEVMETHOD(etherswitch_writereg, ip17x_writereg),
+ DEVMETHOD(etherswitch_readphyreg, ip17x_readphy),
+ DEVMETHOD(etherswitch_writephyreg, ip17x_writephy),
+ DEVMETHOD(etherswitch_getport, ip17x_getport),
+ DEVMETHOD(etherswitch_setport, ip17x_setport),
+ DEVMETHOD(etherswitch_getvgroup, ip17x_getvgroup),
+ DEVMETHOD(etherswitch_setvgroup, ip17x_setvgroup),
+ DEVMETHOD(etherswitch_getconf, ip17x_getconf),
+ DEVMETHOD(etherswitch_setconf, ip17x_setconf),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(ip17x, ip17x_driver, ip17x_methods,
+ sizeof(struct ip17x_softc));
+
+DRIVER_MODULE(ip17x, mdio, ip17x_driver, 0, 0);
+DRIVER_MODULE(miibus, ip17x, miibus_driver, 0, 0);
+DRIVER_MODULE(etherswitch, ip17x, etherswitch_driver, 0, 0);
+MODULE_VERSION(ip17x, 1);
+
+#ifdef FDT
+MODULE_DEPEND(ip17x, mdio, 1, 1, 1); /* XXX which versions? */
+#else
+DRIVER_MODULE(mdio, ip17x, mdio_driver, 0, 0);
+MODULE_DEPEND(ip17x, miibus, 1, 1, 1); /* XXX which versions? */
+MODULE_DEPEND(ip17x, etherswitch, 1, 1, 1); /* XXX which versions? */
+#endif
diff --git a/sys/dev/etherswitch/ip17x/ip17x_phy.c b/sys/dev/etherswitch/ip17x/ip17x_phy.c
new file mode 100644
index 000000000000..6565e978fb82
--- /dev/null
+++ b/sys/dev/etherswitch/ip17x/ip17x_phy.c
@@ -0,0 +1,104 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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/lock.h>
+#include <sys/mutex.h>
+#include <sys/systm.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+
+#include <dev/mii/mii.h>
+
+#include <dev/etherswitch/etherswitch.h>
+#include <dev/etherswitch/ip17x/ip17x_phy.h>
+#include <dev/etherswitch/ip17x/ip17x_reg.h>
+#include <dev/etherswitch/ip17x/ip17x_var.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+int
+ip17x_readphy(device_t dev, int phy, int reg)
+{
+ struct ip17x_softc *sc;
+ int data;
+
+ sc = device_get_softc(dev);
+ IP17X_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ if (phy < 0 || phy >= 32)
+ return (ENXIO);
+ if (reg < 0 || reg >= 32)
+ return (ENXIO);
+
+ IP17X_LOCK(sc);
+ data = MDIO_READREG(device_get_parent(dev), phy, reg);
+ IP17X_UNLOCK(sc);
+
+ return (data);
+}
+
+int
+ip17x_writephy(device_t dev, int phy, int reg, int data)
+{
+ struct ip17x_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+ IP17X_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ if (phy < 0 || phy >= 32)
+ return (ENXIO);
+ if (reg < 0 || reg >= 32)
+ return (ENXIO);
+
+ IP17X_LOCK(sc);
+ err = MDIO_WRITEREG(device_get_parent(dev), phy, reg, data);
+ IP17X_UNLOCK(sc);
+
+ return (err);
+}
+
+int
+ip17x_updatephy(device_t dev, int phy, int reg, int mask, int value)
+{
+ int val;
+
+ val = ip17x_readphy(dev, phy, reg);
+ val &= ~mask;
+ val |= value;
+ return (ip17x_writephy(dev, phy, reg, val));
+}
diff --git a/sys/dev/etherswitch/ip17x/ip17x_phy.h b/sys/dev/etherswitch/ip17x/ip17x_phy.h
new file mode 100644
index 000000000000..17970f79d497
--- /dev/null
+++ b/sys/dev/etherswitch/ip17x/ip17x_phy.h
@@ -0,0 +1,38 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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 __IP17X_PHY_H__
+#define __IP17X_PHY_H__
+
+int ip17x_readphy(device_t, int, int);
+int ip17x_writephy(device_t, int, int, int);
+int ip17x_updatephy(device_t, int, int, int, int);
+
+#endif /* __IP17X_PHY_H__ */
diff --git a/sys/dev/etherswitch/ip17x/ip17x_reg.h b/sys/dev/etherswitch/ip17x/ip17x_reg.h
new file mode 100644
index 000000000000..3e82226dd279
--- /dev/null
+++ b/sys/dev/etherswitch/ip17x/ip17x_reg.h
@@ -0,0 +1,44 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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 __IP17X_REG_H__
+#define __IP17X_REG_H__
+
+/* IP175X */
+#define IP17X_OUI 0x9c3
+#define IP17X_IP175A 0x05
+#define IP17X_IP175C 0x18
+
+#define IP17X_MAX_VLANS 16
+
+#define IP175X_CPU_PORT 5
+#define IP175X_NUM_PORTS 6
+
+#endif /* __IP17X_REG_H__ */
diff --git a/sys/dev/etherswitch/ip17x/ip17x_var.h b/sys/dev/etherswitch/ip17x/ip17x_var.h
new file mode 100644
index 000000000000..0daf36b9a486
--- /dev/null
+++ b/sys/dev/etherswitch/ip17x/ip17x_var.h
@@ -0,0 +1,95 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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 __IP17X_VAR_H__
+#define __IP17X_VAR_H__
+
+typedef enum {
+ IP17X_SWITCH_NONE,
+ IP17X_SWITCH_IP175A,
+ IP17X_SWITCH_IP175C,
+ IP17X_SWITCH_IP175D,
+ IP17X_SWITCH_IP178C,
+} ip17x_switch_type;
+
+struct ip17x_vlan {
+ uint32_t ports;
+ int vlanid;
+};
+
+struct ip17x_softc {
+ device_t sc_dev;
+ int media; /* cpu port media */
+ int cpuport; /* which PHY is connected to the CPU */
+ int phymask; /* PHYs we manage */
+ int phyport[MII_NPHY];
+ int numports; /* number of ports */
+ int *portphy;
+ device_t **miibus;
+ int miipoll;
+ etherswitch_info_t info;
+ ip17x_switch_type sc_switchtype;
+ struct callout callout_tick;
+ if_t *ifp;
+ struct mtx sc_mtx; /* serialize access to softc */
+
+ struct ip17x_vlan vlan[IP17X_MAX_VLANS];
+ uint32_t *pvid; /* PVID */
+ uint32_t addtag; /* per port add tag flag */
+ uint32_t striptag; /* per port strip tag flag */
+ uint32_t vlan_mode; /* VLAN mode */
+
+ struct {
+ int (* ip17x_reset) (struct ip17x_softc *);
+ int (* ip17x_hw_setup) (struct ip17x_softc *);
+ int (* ip17x_get_vlan_mode) (struct ip17x_softc *);
+ int (* ip17x_set_vlan_mode) (struct ip17x_softc *, uint32_t);
+ } hal;
+};
+
+#define IP17X_IS_SWITCH(_sc, _type) \
+ (!!((_sc)->sc_switchtype == IP17X_SWITCH_ ## _type))
+
+#define IP17X_LOCK(_sc) \
+ mtx_lock(&(_sc)->sc_mtx)
+#define IP17X_UNLOCK(_sc) \
+ mtx_unlock(&(_sc)->sc_mtx)
+#define IP17X_LOCK_ASSERT(_sc, _what) \
+ mtx_assert(&(_sc)->sc_mtx, (_what))
+#define IP17X_TRYLOCK(_sc) \
+ mtx_trylock(&(_sc)->sc_mtx)
+
+#if defined(DEBUG)
+#define DPRINTF(dev, args...) device_printf(dev, args)
+#else
+#define DPRINTF(dev, args...)
+#endif
+
+#endif /* __IP17X_VAR_H__ */
diff --git a/sys/dev/etherswitch/ip17x/ip17x_vlans.c b/sys/dev/etherswitch/ip17x/ip17x_vlans.c
new file mode 100644
index 000000000000..4b583714beeb
--- /dev/null
+++ b/sys/dev/etherswitch/ip17x/ip17x_vlans.c
@@ -0,0 +1,187 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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/lock.h>
+#include <sys/mutex.h>
+#include <sys/systm.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+
+#include <dev/mii/mii.h>
+
+#include <dev/etherswitch/etherswitch.h>
+#include <dev/etherswitch/ip17x/ip17x_phy.h>
+#include <dev/etherswitch/ip17x/ip17x_reg.h>
+#include <dev/etherswitch/ip17x/ip17x_var.h>
+#include <dev/etherswitch/ip17x/ip17x_vlans.h>
+#include <dev/etherswitch/ip17x/ip175c.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+/*
+ * Reset vlans to default state.
+ */
+int
+ip17x_reset_vlans(struct ip17x_softc *sc, uint32_t vlan_mode)
+{
+ struct ip17x_vlan *v;
+ int i, j, phy;
+
+ /* Do not add or strip vlan tags on any port. */
+ sc->addtag = 0;
+ sc->striptag = 0;
+
+ /* Reset all vlan data. */
+ memset(sc->vlan, 0, sizeof(sc->vlan));
+ memset(sc->pvid, 0, sizeof(uint32_t) * sc->numports);
+
+ if (vlan_mode == ETHERSWITCH_VLAN_PORT) {
+
+ /* Initialize port based vlans. */
+ for (i = 0, phy = 0; phy < MII_NPHY; phy++) {
+ if (((1 << phy) & sc->phymask) == 0)
+ continue;
+ v = &sc->vlan[i];
+ v->vlanid = i++ | ETHERSWITCH_VID_VALID;
+ v->ports = (1 << sc->cpuport);
+ for (j = 0; j < MII_NPHY; j++) {
+ if (((1 << j) & sc->phymask) == 0)
+ continue;
+ v->ports |= (1 << j);
+ }
+ }
+
+ } else if (vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+
+ /*
+ * Setup vlan 1 as PVID for all switch ports. Add all ports as
+ * members of vlan 1.
+ */
+ v = &sc->vlan[0];
+ v->vlanid = 1 | ETHERSWITCH_VID_VALID;
+ /* Set PVID to 1 for everyone. */
+ for (i = 0; i < sc->numports; i++)
+ sc->pvid[i] = 1;
+ for (i = 0; i < MII_NPHY; i++) {
+ if ((sc->phymask & (1 << i)) == 0)
+ continue;
+ v->ports |= (1 << i);
+ }
+ }
+
+ return (0);
+}
+
+int
+ip17x_getvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ struct ip17x_softc *sc;
+ uint32_t port;
+ int i;
+
+ sc = device_get_softc(dev);
+
+ /* Vlan ID. */
+ vg->es_vid = sc->vlan[vg->es_vlangroup].vlanid;
+
+ /* Member Ports. */
+ vg->es_member_ports = 0;
+ for (i = 0; i < MII_NPHY; i++) {
+ if ((sc->phymask & (1 << i)) == 0)
+ continue;
+ if ((sc->vlan[vg->es_vlangroup].ports & (1 << i)) == 0)
+ continue;
+ port = sc->phyport[i];
+ vg->es_member_ports |= (1 << port);
+ }
+
+ /* Not supported. */
+ vg->es_untagged_ports = vg->es_member_ports;
+ vg->es_fid = 0;
+
+ return (0);
+}
+
+int
+ip17x_setvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ struct ip17x_softc *sc;
+ uint32_t phy;
+ int i;
+
+ sc = device_get_softc(dev);
+
+ /* Check VLAN mode. */
+ if (sc->vlan_mode == 0)
+ return (EINVAL);
+
+ /* IP175C don't support VLAN IDs > 15. */
+ if (IP17X_IS_SWITCH(sc, IP175C) &&
+ (vg->es_vid & ETHERSWITCH_VID_MASK) > IP175C_LAST_VLAN)
+ return (EINVAL);
+
+ /* Vlan ID. */
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ for (i = 0; i < sc->info.es_nvlangroups; i++) {
+ /* Is this Vlan ID already set in another vlangroup ? */
+ if (i != vg->es_vlangroup &&
+ sc->vlan[i].vlanid & ETHERSWITCH_VID_VALID &&
+ (sc->vlan[i].vlanid & ETHERSWITCH_VID_MASK) ==
+ (vg->es_vid & ETHERSWITCH_VID_MASK))
+ return (EINVAL);
+ }
+ sc->vlan[vg->es_vlangroup].vlanid = vg->es_vid &
+ ETHERSWITCH_VID_MASK;
+ /* Setting the vlanid to zero disables the vlangroup. */
+ if (sc->vlan[vg->es_vlangroup].vlanid == 0) {
+ sc->vlan[vg->es_vlangroup].ports = 0;
+ return (sc->hal.ip17x_hw_setup(sc));
+ }
+ sc->vlan[vg->es_vlangroup].vlanid |= ETHERSWITCH_VID_VALID;
+ }
+
+ /* Member Ports. */
+ sc->vlan[vg->es_vlangroup].ports = 0;
+ for (i = 0; i < sc->numports; i++) {
+ if ((vg->es_member_ports & (1 << i)) == 0)
+ continue;
+ phy = sc->portphy[i];
+ sc->vlan[vg->es_vlangroup].ports |= (1 << phy);
+ }
+
+ return (sc->hal.ip17x_hw_setup(sc));
+}
diff --git a/sys/dev/etherswitch/ip17x/ip17x_vlans.h b/sys/dev/etherswitch/ip17x/ip17x_vlans.h
new file mode 100644
index 000000000000..6d2ba0e339ce
--- /dev/null
+++ b/sys/dev/etherswitch/ip17x/ip17x_vlans.h
@@ -0,0 +1,36 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * All rights reserved.
+ *
+ * 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 __IP17X_VLANS_H__
+#define __IP17X_VLANS_H__
+
+int ip17x_reset_vlans(struct ip17x_softc *, uint32_t);
+int ip17x_getvgroup(device_t, etherswitch_vlangroup_t *);
+int ip17x_setvgroup(device_t, etherswitch_vlangroup_t *);
+
+#endif /* __IP17X_VLANS_H__ */
diff --git a/sys/dev/etherswitch/micrel/ksz8995ma.c b/sys/dev/etherswitch/micrel/ksz8995ma.c
new file mode 100644
index 000000000000..cbffd5e39f49
--- /dev/null
+++ b/sys/dev/etherswitch/micrel/ksz8995ma.c
@@ -0,0 +1,941 @@
+/*-
+ * Copyright (c) 2016 Hiroki Mori
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+/*
+ * This is Micrel KSZ8995MA driver code. KSZ8995MA use SPI bus on control.
+ * This code development on @SRCHACK's ksz8995ma board and FON2100 with
+ * gpiospi.
+ * etherswitchcfg command port option support addtag, ingress, striptag,
+ * dropuntagged.
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.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/ethernet.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+#include <dev/spibus/spi.h>
+
+#include "spibus_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+#define KSZ8995MA_SPI_READ 0x03
+#define KSZ8995MA_SPI_WRITE 0x02
+
+#define KSZ8995MA_CID0 0x00
+#define KSZ8995MA_CID1 0x01
+
+#define KSZ8995MA_GC0 0x02
+#define KSZ8995MA_GC1 0x03
+#define KSZ8995MA_GC2 0x04
+#define KSZ8995MA_GC3 0x05
+
+#define KSZ8995MA_PORT_SIZE 0x10
+
+#define KSZ8995MA_PC0_BASE 0x10
+#define KSZ8995MA_PC1_BASE 0x11
+#define KSZ8995MA_PC2_BASE 0x12
+#define KSZ8995MA_PC3_BASE 0x13
+#define KSZ8995MA_PC4_BASE 0x14
+#define KSZ8995MA_PC5_BASE 0x15
+#define KSZ8995MA_PC6_BASE 0x16
+#define KSZ8995MA_PC7_BASE 0x17
+#define KSZ8995MA_PC8_BASE 0x18
+#define KSZ8995MA_PC9_BASE 0x19
+#define KSZ8995MA_PC10_BASE 0x1a
+#define KSZ8995MA_PC11_BASE 0x1b
+#define KSZ8995MA_PC12_BASE 0x1c
+#define KSZ8995MA_PC13_BASE 0x1d
+
+#define KSZ8995MA_PS0_BASE 0x1e
+
+#define KSZ8995MA_PC14_BASE 0x1f
+
+#define KSZ8995MA_IAC0 0x6e
+#define KSZ8995MA_IAC1 0x6f
+#define KSZ8995MA_IDR8 0x70
+#define KSZ8995MA_IDR7 0x71
+#define KSZ8995MA_IDR6 0x72
+#define KSZ8995MA_IDR5 0x73
+#define KSZ8995MA_IDR4 0x74
+#define KSZ8995MA_IDR3 0x75
+#define KSZ8995MA_IDR2 0x76
+#define KSZ8995MA_IDR1 0x77
+#define KSZ8995MA_IDR0 0x78
+
+#define KSZ8995MA_FAMILI_ID 0x95
+#define KSZ8995MA_CHIP_ID 0x00
+#define KSZ8995MA_CHIP_ID_MASK 0xf0
+#define KSZ8995MA_START 0x01
+#define KSZ8995MA_VLAN_ENABLE 0x80
+#define KSZ8995MA_TAG_INS 0x04
+#define KSZ8995MA_TAG_RM 0x02
+#define KSZ8995MA_INGR_FILT 0x40
+#define KSZ8995MA_DROP_NONPVID 0x20
+
+#define KSZ8995MA_PDOWN 0x08
+#define KSZ8995MA_STARTNEG 0x20
+
+#define KSZ8995MA_MII_STAT 0x7808
+#define KSZ8995MA_MII_PHYID_H 0x0022
+#define KSZ8995MA_MII_PHYID_L 0x1450
+#define KSZ8995MA_MII_AA 0x0401
+
+#define KSZ8995MA_VLAN_TABLE_VALID 0x20
+#define KSZ8995MA_VLAN_TABLE_READ 0x14
+#define KSZ8995MA_VLAN_TABLE_WRITE 0x04
+
+#define KSZ8995MA_MAX_PORT 5
+
+MALLOC_DECLARE(M_KSZ8995MA);
+MALLOC_DEFINE(M_KSZ8995MA, "ksz8995ma", "ksz8995ma data structures");
+
+struct ksz8995ma_softc {
+ struct mtx sc_mtx; /* serialize access to softc */
+ device_t sc_dev;
+ int vlan_mode;
+ int media; /* cpu port media */
+ int cpuport; /* which PHY is connected to the CPU */
+ int phymask; /* PHYs we manage */
+ int numports; /* number of ports */
+ int ifpport[KSZ8995MA_MAX_PORT];
+ int *portphy;
+ char **ifname;
+ device_t **miibus;
+ if_t *ifp;
+ struct callout callout_tick;
+ etherswitch_info_t info;
+};
+
+#define KSZ8995MA_LOCK(_sc) \
+ mtx_lock(&(_sc)->sc_mtx)
+#define KSZ8995MA_UNLOCK(_sc) \
+ mtx_unlock(&(_sc)->sc_mtx)
+#define KSZ8995MA_LOCK_ASSERT(_sc, _what) \
+ mtx_assert(&(_sc)->sc_mtx, (_what))
+#define KSZ8995MA_TRYLOCK(_sc) \
+ mtx_trylock(&(_sc)->sc_mtx)
+
+#if defined(DEBUG)
+#define DPRINTF(dev, args...) device_printf(dev, args)
+#else
+#define DPRINTF(dev, args...)
+#endif
+
+static inline int ksz8995ma_portforphy(struct ksz8995ma_softc *, int);
+static void ksz8995ma_tick(void *);
+static int ksz8995ma_ifmedia_upd(if_t);
+static void ksz8995ma_ifmedia_sts(if_t, struct ifmediareq *);
+static int ksz8995ma_readreg(device_t dev, int addr);
+static int ksz8995ma_writereg(device_t dev, int addr, int value);
+static void ksz8995ma_portvlanreset(device_t dev);
+
+static int
+ksz8995ma_probe(device_t dev)
+{
+ int id0, id1;
+ struct ksz8995ma_softc *sc;
+
+ sc = device_get_softc(dev);
+ bzero(sc, sizeof(*sc));
+
+ id0 = ksz8995ma_readreg(dev, KSZ8995MA_CID0);
+ id1 = ksz8995ma_readreg(dev, KSZ8995MA_CID1);
+ if (bootverbose)
+ device_printf(dev,"Chip Identifier Register %x %x\n", id0, id1);
+
+ /* check Product Code */
+ if (id0 != KSZ8995MA_FAMILI_ID || (id1 & KSZ8995MA_CHIP_ID_MASK) !=
+ KSZ8995MA_CHIP_ID) {
+ return (ENXIO);
+ }
+
+ device_set_desc(dev, "Micrel KSZ8995MA SPI switch driver");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+ksz8995ma_attach_phys(struct ksz8995ma_softc *sc)
+{
+ int phy, port, err;
+ char name[IFNAMSIZ];
+
+ port = 0;
+ err = 0;
+ /* PHYs need an interface, so we generate a dummy one */
+ snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->sc_dev));
+ for (phy = 0; phy < sc->numports; phy++) {
+ if (phy == sc->cpuport)
+ continue;
+ if (((1 << phy) & sc->phymask) == 0)
+ continue;
+ sc->ifpport[phy] = port;
+ sc->portphy[port] = phy;
+ sc->ifp[port] = if_alloc(IFT_ETHER);
+ if_setsoftc(sc->ifp[port], sc);
+ if_setflagbits(sc->ifp[port], IFF_UP | IFF_BROADCAST |
+ IFF_DRV_RUNNING | IFF_SIMPLEX, 0);
+ if_initname(sc->ifp[port], name, port);
+ sc->miibus[port] = malloc(sizeof(device_t), M_KSZ8995MA,
+ M_WAITOK | M_ZERO);
+ err = mii_attach(sc->sc_dev, sc->miibus[port], sc->ifp[port],
+ ksz8995ma_ifmedia_upd, ksz8995ma_ifmedia_sts, \
+ BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0);
+ DPRINTF(sc->sc_dev, "%s attached to pseudo interface %s\n",
+ device_get_nameunit(*sc->miibus[port]),
+ if_name(sc->ifp[port]));
+ if (err != 0) {
+ device_printf(sc->sc_dev,
+ "attaching PHY %d failed\n",
+ phy);
+ goto failed;
+ }
+ ++port;
+ }
+ sc->info.es_nports = port;
+ if (sc->cpuport != -1) {
+ /* cpu port is MAC5 on ksz8995ma */
+ sc->ifpport[sc->cpuport] = port;
+ sc->portphy[port] = sc->cpuport;
+ ++sc->info.es_nports;
+ }
+
+ return (0);
+
+failed:
+ for (phy = 0; phy < sc->numports; phy++) {
+ if (((1 << phy) & sc->phymask) == 0)
+ continue;
+ port = ksz8995ma_portforphy(sc, phy);
+ if (sc->miibus[port] != NULL)
+ device_delete_child(sc->sc_dev, (*sc->miibus[port]));
+ if (sc->ifp[port] != NULL)
+ if_free(sc->ifp[port]);
+ if (sc->ifname[port] != NULL)
+ free(sc->ifname[port], M_KSZ8995MA);
+ if (sc->miibus[port] != NULL)
+ free(sc->miibus[port], M_KSZ8995MA);
+ }
+ return (err);
+}
+
+static int
+ksz8995ma_attach(device_t dev)
+{
+ struct ksz8995ma_softc *sc;
+ int err, reg;
+
+ err = 0;
+ sc = device_get_softc(dev);
+
+ sc->sc_dev = dev;
+ mtx_init(&sc->sc_mtx, "ksz8995ma", NULL, MTX_DEF);
+ strlcpy(sc->info.es_name, device_get_desc(dev),
+ sizeof(sc->info.es_name));
+
+ /* KSZ8995MA Defaults */
+ sc->numports = KSZ8995MA_MAX_PORT;
+ sc->phymask = (1 << (KSZ8995MA_MAX_PORT + 1)) - 1;
+ sc->cpuport = -1;
+ sc->media = 100;
+
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "cpuport", &sc->cpuport);
+
+ sc->info.es_nvlangroups = 16;
+ sc->info.es_vlan_caps = ETHERSWITCH_VLAN_PORT | ETHERSWITCH_VLAN_DOT1Q;
+
+ sc->ifp = malloc(sizeof(if_t) * sc->numports, M_KSZ8995MA,
+ M_WAITOK | M_ZERO);
+ sc->ifname = malloc(sizeof(char *) * sc->numports, M_KSZ8995MA,
+ M_WAITOK | M_ZERO);
+ sc->miibus = malloc(sizeof(device_t *) * sc->numports, M_KSZ8995MA,
+ M_WAITOK | M_ZERO);
+ sc->portphy = malloc(sizeof(int) * sc->numports, M_KSZ8995MA,
+ M_WAITOK | M_ZERO);
+
+ /*
+ * Attach the PHYs and complete the bus enumeration.
+ */
+ err = ksz8995ma_attach_phys(sc);
+ if (err != 0)
+ goto failed;
+
+ bus_identify_children(dev);
+ bus_enumerate_hinted_children(dev);
+ bus_attach_children(dev);
+
+ callout_init(&sc->callout_tick, 0);
+
+ ksz8995ma_tick(sc);
+
+ /* start switch */
+ sc->vlan_mode = 0;
+ reg = ksz8995ma_readreg(dev, KSZ8995MA_GC3);
+ ksz8995ma_writereg(dev, KSZ8995MA_GC3,
+ reg & ~KSZ8995MA_VLAN_ENABLE);
+ ksz8995ma_portvlanreset(dev);
+ ksz8995ma_writereg(dev, KSZ8995MA_CID1, KSZ8995MA_START);
+
+ return (0);
+
+failed:
+ free(sc->portphy, M_KSZ8995MA);
+ free(sc->miibus, M_KSZ8995MA);
+ free(sc->ifname, M_KSZ8995MA);
+ free(sc->ifp, M_KSZ8995MA);
+
+ return (err);
+}
+
+static int
+ksz8995ma_detach(device_t dev)
+{
+ struct ksz8995ma_softc *sc;
+ int error, i, port;
+
+ error = bus_generic_detach(dev);
+ if (error != 0)
+ return (error);
+
+ sc = device_get_softc(dev);
+
+ callout_drain(&sc->callout_tick);
+
+ for (i = 0; i < KSZ8995MA_MAX_PORT; i++) {
+ if (((1 << i) & sc->phymask) == 0)
+ continue;
+ port = ksz8995ma_portforphy(sc, i);
+ if (sc->ifp[port] != NULL)
+ if_free(sc->ifp[port]);
+ free(sc->ifname[port], M_KSZ8995MA);
+ free(sc->miibus[port], M_KSZ8995MA);
+ }
+
+ free(sc->portphy, M_KSZ8995MA);
+ free(sc->miibus, M_KSZ8995MA);
+ free(sc->ifname, M_KSZ8995MA);
+ free(sc->ifp, M_KSZ8995MA);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+/*
+ * Convert PHY number to port number.
+ */
+static inline int
+ksz8995ma_portforphy(struct ksz8995ma_softc *sc, int phy)
+{
+
+ return (sc->ifpport[phy]);
+}
+
+static inline struct mii_data *
+ksz8995ma_miiforport(struct ksz8995ma_softc *sc, int port)
+{
+
+ if (port < 0 || port > sc->numports)
+ return (NULL);
+ if (port == sc->cpuport)
+ return (NULL);
+ return (device_get_softc(*sc->miibus[port]));
+}
+
+static inline if_t
+ksz8995ma_ifpforport(struct ksz8995ma_softc *sc, int port)
+{
+
+ if (port < 0 || port > sc->numports)
+ return (NULL);
+ return (sc->ifp[port]);
+}
+
+/*
+ * Poll the status for all PHYs.
+ */
+static void
+ksz8995ma_miipollstat(struct ksz8995ma_softc *sc)
+{
+ int i, port;
+ struct mii_data *mii;
+ struct mii_softc *miisc;
+
+ KSZ8995MA_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ for (i = 0; i < KSZ8995MA_MAX_PORT; i++) {
+ if (i == sc->cpuport)
+ continue;
+ if (((1 << i) & sc->phymask) == 0)
+ continue;
+ port = ksz8995ma_portforphy(sc, i);
+ if ((*sc->miibus[port]) == NULL)
+ continue;
+ mii = device_get_softc(*sc->miibus[port]);
+ 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);
+ }
+ }
+}
+
+static void
+ksz8995ma_tick(void *arg)
+{
+ struct ksz8995ma_softc *sc;
+
+ sc = arg;
+
+ ksz8995ma_miipollstat(sc);
+ callout_reset(&sc->callout_tick, hz, ksz8995ma_tick, sc);
+}
+
+static void
+ksz8995ma_lock(device_t dev)
+{
+ struct ksz8995ma_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ KSZ8995MA_LOCK_ASSERT(sc, MA_NOTOWNED);
+ KSZ8995MA_LOCK(sc);
+}
+
+static void
+ksz8995ma_unlock(device_t dev)
+{
+ struct ksz8995ma_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ KSZ8995MA_LOCK_ASSERT(sc, MA_OWNED);
+ KSZ8995MA_UNLOCK(sc);
+}
+
+static etherswitch_info_t *
+ksz8995ma_getinfo(device_t dev)
+{
+ struct ksz8995ma_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ return (&sc->info);
+}
+
+static int
+ksz8995ma_getport(device_t dev, etherswitch_port_t *p)
+{
+ struct ksz8995ma_softc *sc;
+ struct mii_data *mii;
+ struct ifmediareq *ifmr;
+ int phy, err;
+ int tag1, tag2, portreg;
+
+ sc = device_get_softc(dev);
+ ifmr = &p->es_ifmr;
+
+ if (p->es_port < 0 || p->es_port >= sc->numports)
+ return (ENXIO);
+
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ tag1 = ksz8995ma_readreg(dev, KSZ8995MA_PC3_BASE +
+ KSZ8995MA_PORT_SIZE * p->es_port);
+ tag2 = ksz8995ma_readreg(dev, KSZ8995MA_PC4_BASE +
+ KSZ8995MA_PORT_SIZE * p->es_port);
+ p->es_pvid = (tag1 & 0x0f) << 8 | tag2;
+
+ portreg = ksz8995ma_readreg(dev, KSZ8995MA_PC0_BASE +
+ KSZ8995MA_PORT_SIZE * p->es_port);
+ if (portreg & KSZ8995MA_TAG_INS)
+ p->es_flags |= ETHERSWITCH_PORT_ADDTAG;
+ if (portreg & KSZ8995MA_TAG_RM)
+ p->es_flags |= ETHERSWITCH_PORT_STRIPTAG;
+
+ portreg = ksz8995ma_readreg(dev, KSZ8995MA_PC2_BASE +
+ KSZ8995MA_PORT_SIZE * p->es_port);
+ if (portreg & KSZ8995MA_DROP_NONPVID)
+ p->es_flags |= ETHERSWITCH_PORT_DROPUNTAGGED;
+ if (portreg & KSZ8995MA_INGR_FILT)
+ p->es_flags |= ETHERSWITCH_PORT_INGRESS;
+ }
+
+ phy = sc->portphy[p->es_port];
+ mii = ksz8995ma_miiforport(sc, p->es_port);
+ if (sc->cpuport != -1 && phy == sc->cpuport) {
+ /* fill in fixed values for CPU port */
+ p->es_flags |= ETHERSWITCH_PORT_CPU;
+ ifmr->ifm_count = 0;
+ if (sc->media == 100)
+ ifmr->ifm_current = ifmr->ifm_active =
+ IFM_ETHER | IFM_100_TX | IFM_FDX;
+ else
+ 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) {
+ err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr,
+ &mii->mii_media, SIOCGIFMEDIA);
+ if (err)
+ return (err);
+ } else {
+ return (ENXIO);
+ }
+
+ return (0);
+}
+
+static int
+ksz8995ma_setport(device_t dev, etherswitch_port_t *p)
+{
+ struct ksz8995ma_softc *sc;
+ struct mii_data *mii;
+ struct ifmedia *ifm;
+ if_t ifp;
+ int phy, err;
+ int portreg;
+
+ sc = device_get_softc(dev);
+
+ if (p->es_port < 0 || p->es_port >= sc->numports)
+ return (ENXIO);
+
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ ksz8995ma_writereg(dev, KSZ8995MA_PC4_BASE +
+ KSZ8995MA_PORT_SIZE * p->es_port, p->es_pvid & 0xff);
+ portreg = ksz8995ma_readreg(dev, KSZ8995MA_PC3_BASE +
+ KSZ8995MA_PORT_SIZE * p->es_port);
+ ksz8995ma_writereg(dev, KSZ8995MA_PC3_BASE +
+ KSZ8995MA_PORT_SIZE * p->es_port,
+ (portreg & 0xf0) | ((p->es_pvid >> 8) & 0x0f));
+
+ portreg = ksz8995ma_readreg(dev, KSZ8995MA_PC0_BASE +
+ KSZ8995MA_PORT_SIZE * p->es_port);
+ if (p->es_flags & ETHERSWITCH_PORT_ADDTAG)
+ portreg |= KSZ8995MA_TAG_INS;
+ else
+ portreg &= ~KSZ8995MA_TAG_INS;
+ if (p->es_flags & ETHERSWITCH_PORT_STRIPTAG)
+ portreg |= KSZ8995MA_TAG_RM;
+ else
+ portreg &= ~KSZ8995MA_TAG_RM;
+ ksz8995ma_writereg(dev, KSZ8995MA_PC0_BASE +
+ KSZ8995MA_PORT_SIZE * p->es_port, portreg);
+
+ portreg = ksz8995ma_readreg(dev, KSZ8995MA_PC2_BASE +
+ KSZ8995MA_PORT_SIZE * p->es_port);
+ if (p->es_flags & ETHERSWITCH_PORT_DROPUNTAGGED)
+ portreg |= KSZ8995MA_DROP_NONPVID;
+ else
+ portreg &= ~KSZ8995MA_DROP_NONPVID;
+ if (p->es_flags & ETHERSWITCH_PORT_INGRESS)
+ portreg |= KSZ8995MA_INGR_FILT;
+ else
+ portreg &= ~KSZ8995MA_INGR_FILT;
+ ksz8995ma_writereg(dev, KSZ8995MA_PC2_BASE +
+ KSZ8995MA_PORT_SIZE * p->es_port, portreg);
+ }
+
+ phy = sc->portphy[p->es_port];
+ mii = ksz8995ma_miiforport(sc, p->es_port);
+ if (phy != sc->cpuport) {
+ if (mii == NULL)
+ return (ENXIO);
+ ifp = ksz8995ma_ifpforport(sc, p->es_port);
+ ifm = &mii->mii_media;
+ err = ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA);
+ }
+ return (0);
+}
+
+static int
+ksz8995ma_getvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ int data0, data1, data2;
+ int vlantab;
+ struct ksz8995ma_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) {
+ if (vg->es_vlangroup < sc->numports) {
+ vg->es_vid = ETHERSWITCH_VID_VALID;
+ vg->es_vid |= vg->es_vlangroup;
+ data0 = ksz8995ma_readreg(dev, KSZ8995MA_PC1_BASE +
+ KSZ8995MA_PORT_SIZE * vg->es_vlangroup);
+ vg->es_member_ports = data0 & 0x1f;
+ vg->es_untagged_ports = vg->es_member_ports;
+ vg->es_fid = 0;
+ } else {
+ vg->es_vid = 0;
+ }
+ } else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ ksz8995ma_writereg(dev, KSZ8995MA_IAC0,
+ KSZ8995MA_VLAN_TABLE_READ);
+ ksz8995ma_writereg(dev, KSZ8995MA_IAC1, vg->es_vlangroup);
+ data2 = ksz8995ma_readreg(dev, KSZ8995MA_IDR2);
+ data1 = ksz8995ma_readreg(dev, KSZ8995MA_IDR1);
+ data0 = ksz8995ma_readreg(dev, KSZ8995MA_IDR0);
+ vlantab = data2 << 16 | data1 << 8 | data0;
+ if (data2 & KSZ8995MA_VLAN_TABLE_VALID) {
+ vg->es_vid = ETHERSWITCH_VID_VALID;
+ vg->es_vid |= vlantab & 0xfff;
+ vg->es_member_ports = (vlantab >> 16) & 0x1f;
+ vg->es_untagged_ports = vg->es_member_ports;
+ vg->es_fid = (vlantab >> 12) & 0x0f;
+ } else {
+ vg->es_fid = 0;
+ }
+ }
+
+ return (0);
+}
+
+static int
+ksz8995ma_setvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ struct ksz8995ma_softc *sc;
+ int data0;
+
+ sc = device_get_softc(dev);
+
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) {
+ data0 = ksz8995ma_readreg(dev, KSZ8995MA_PC1_BASE +
+ KSZ8995MA_PORT_SIZE * vg->es_vlangroup);
+ ksz8995ma_writereg(dev, KSZ8995MA_PC1_BASE +
+ KSZ8995MA_PORT_SIZE * vg->es_vlangroup,
+ (data0 & 0xe0) | (vg->es_member_ports & 0x1f));
+ } else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ if (vg->es_member_ports != 0) {
+ ksz8995ma_writereg(dev, KSZ8995MA_IDR2,
+ KSZ8995MA_VLAN_TABLE_VALID |
+ (vg->es_member_ports & 0x1f));
+ ksz8995ma_writereg(dev, KSZ8995MA_IDR1,
+ vg->es_fid << 4 | vg->es_vid >> 8);
+ ksz8995ma_writereg(dev, KSZ8995MA_IDR0,
+ vg->es_vid & 0xff);
+ } else {
+ ksz8995ma_writereg(dev, KSZ8995MA_IDR2, 0);
+ ksz8995ma_writereg(dev, KSZ8995MA_IDR1, 0);
+ ksz8995ma_writereg(dev, KSZ8995MA_IDR0, 0);
+ }
+ ksz8995ma_writereg(dev, KSZ8995MA_IAC0,
+ KSZ8995MA_VLAN_TABLE_WRITE);
+ ksz8995ma_writereg(dev, KSZ8995MA_IAC1, vg->es_vlangroup);
+ }
+
+ return (0);
+}
+
+static int
+ksz8995ma_getconf(device_t dev, etherswitch_conf_t *conf)
+{
+ struct ksz8995ma_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ /* Return the VLAN mode. */
+ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE;
+ conf->vlan_mode = sc->vlan_mode;
+
+ return (0);
+}
+
+static void
+ksz8995ma_portvlanreset(device_t dev)
+{
+ int i, data;
+ struct ksz8995ma_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ for (i = 0; i < sc->numports; ++i) {
+ data = ksz8995ma_readreg(dev, KSZ8995MA_PC1_BASE +
+ KSZ8995MA_PORT_SIZE * i);
+ ksz8995ma_writereg(dev, KSZ8995MA_PC1_BASE +
+ KSZ8995MA_PORT_SIZE * i, (data & 0xe0) | 0x1f);
+ }
+}
+
+static int
+ksz8995ma_setconf(device_t dev, etherswitch_conf_t *conf)
+{
+ int reg;
+ struct ksz8995ma_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ if ((conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) == 0)
+ return (0);
+
+ if (conf->vlan_mode == ETHERSWITCH_VLAN_PORT) {
+ sc->vlan_mode = ETHERSWITCH_VLAN_PORT;
+ reg = ksz8995ma_readreg(dev, KSZ8995MA_GC3);
+ ksz8995ma_writereg(dev, KSZ8995MA_GC3,
+ reg & ~KSZ8995MA_VLAN_ENABLE);
+ ksz8995ma_portvlanreset(dev);
+ } else if (conf->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ sc->vlan_mode = ETHERSWITCH_VLAN_DOT1Q;
+ reg = ksz8995ma_readreg(dev, KSZ8995MA_GC3);
+ ksz8995ma_writereg(dev, KSZ8995MA_GC3,
+ reg | KSZ8995MA_VLAN_ENABLE);
+ } else {
+ sc->vlan_mode = 0;
+ reg = ksz8995ma_readreg(dev, KSZ8995MA_GC3);
+ ksz8995ma_writereg(dev, KSZ8995MA_GC3,
+ reg & ~KSZ8995MA_VLAN_ENABLE);
+ ksz8995ma_portvlanreset(dev);
+ }
+ return (0);
+}
+
+static void
+ksz8995ma_statchg(device_t dev)
+{
+
+ DPRINTF(dev, "%s\n", __func__);
+}
+
+static int
+ksz8995ma_ifmedia_upd(if_t ifp)
+{
+ struct ksz8995ma_softc *sc;
+ struct mii_data *mii;
+
+ sc = if_getsoftc(ifp);
+ mii = ksz8995ma_miiforport(sc, if_getdunit(ifp));
+
+ DPRINTF(sc->sc_dev, "%s\n", __func__);
+ if (mii == NULL)
+ return (ENXIO);
+ mii_mediachg(mii);
+ return (0);
+}
+
+static void
+ksz8995ma_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct ksz8995ma_softc *sc;
+ struct mii_data *mii;
+
+ sc = if_getsoftc(ifp);
+ mii = ksz8995ma_miiforport(sc, if_getdunit(ifp));
+
+ DPRINTF(sc->sc_dev, "%s\n", __func__);
+
+ if (mii == NULL)
+ return;
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+}
+
+static int
+ksz8995ma_readphy(device_t dev, int phy, int reg)
+{
+int portreg;
+
+ /*
+ * This is no mdio/mdc connection code.
+ * simulate MIIM Registers via the SPI interface
+ */
+ if (reg == MII_BMSR) {
+ portreg = ksz8995ma_readreg(dev, KSZ8995MA_PS0_BASE +
+ KSZ8995MA_PORT_SIZE * phy);
+ return (KSZ8995MA_MII_STAT |
+ (portreg & 0x20 ? BMSR_LINK : 0x00) |
+ (portreg & 0x40 ? BMSR_ACOMP : 0x00));
+ } else if (reg == MII_PHYIDR1) {
+ return (KSZ8995MA_MII_PHYID_H);
+ } else if (reg == MII_PHYIDR2) {
+ return (KSZ8995MA_MII_PHYID_L);
+ } else if (reg == MII_ANAR) {
+ portreg = ksz8995ma_readreg(dev, KSZ8995MA_PC12_BASE +
+ KSZ8995MA_PORT_SIZE * phy);
+ return (KSZ8995MA_MII_AA | (portreg & 0x0f) << 5);
+ } else if (reg == MII_ANLPAR) {
+ portreg = ksz8995ma_readreg(dev, KSZ8995MA_PS0_BASE +
+ KSZ8995MA_PORT_SIZE * phy);
+ return (((portreg & 0x0f) << 5) | 0x01);
+ }
+
+ return (0);
+}
+
+static int
+ksz8995ma_writephy(device_t dev, int phy, int reg, int data)
+{
+int portreg;
+
+ /*
+ * This is no mdio/mdc connection code.
+ * simulate MIIM Registers via the SPI interface
+ */
+ if (reg == MII_BMCR) {
+ portreg = ksz8995ma_readreg(dev, KSZ8995MA_PC13_BASE +
+ KSZ8995MA_PORT_SIZE * phy);
+ if (data & BMCR_PDOWN)
+ portreg |= KSZ8995MA_PDOWN;
+ else
+ portreg &= ~KSZ8995MA_PDOWN;
+ if (data & BMCR_STARTNEG)
+ portreg |= KSZ8995MA_STARTNEG;
+ else
+ portreg &= ~KSZ8995MA_STARTNEG;
+ ksz8995ma_writereg(dev, KSZ8995MA_PC13_BASE +
+ KSZ8995MA_PORT_SIZE * phy, portreg);
+ } else if (reg == MII_ANAR) {
+ portreg = ksz8995ma_readreg(dev, KSZ8995MA_PC12_BASE +
+ KSZ8995MA_PORT_SIZE * phy);
+ portreg &= 0xf;
+ portreg |= ((data >> 5) & 0x0f);
+ ksz8995ma_writereg(dev, KSZ8995MA_PC12_BASE +
+ KSZ8995MA_PORT_SIZE * phy, portreg);
+ }
+ return (0);
+}
+
+static int
+ksz8995ma_readreg(device_t dev, int addr)
+{
+ uint8_t txBuf[8], rxBuf[8];
+ struct spi_command cmd;
+ int err;
+
+ memset(&cmd, 0, sizeof(cmd));
+ memset(txBuf, 0, sizeof(txBuf));
+ memset(rxBuf, 0, sizeof(rxBuf));
+
+ /* read spi */
+ txBuf[0] = KSZ8995MA_SPI_READ;
+ txBuf[1] = addr;
+ cmd.tx_cmd = &txBuf;
+ cmd.rx_cmd = &rxBuf;
+ cmd.tx_cmd_sz = 3;
+ cmd.rx_cmd_sz = 3;
+ err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
+ if (err)
+ return(0);
+
+ return (rxBuf[2]);
+}
+
+static int
+ksz8995ma_writereg(device_t dev, int addr, int value)
+{
+ uint8_t txBuf[8], rxBuf[8];
+ struct spi_command cmd;
+ int err;
+
+ memset(&cmd, 0, sizeof(cmd));
+ memset(txBuf, 0, sizeof(txBuf));
+ memset(rxBuf, 0, sizeof(rxBuf));
+
+ /* write spi */
+ txBuf[0] = KSZ8995MA_SPI_WRITE;
+ txBuf[1] = addr;
+ txBuf[2] = value;
+ cmd.tx_cmd = &txBuf;
+ cmd.rx_cmd = &rxBuf;
+ cmd.tx_cmd_sz = 3;
+ cmd.rx_cmd_sz = 3;
+ err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
+ if (err)
+ return(0);
+
+ return (0);
+}
+
+static device_method_t ksz8995ma_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, ksz8995ma_probe),
+ DEVMETHOD(device_attach, ksz8995ma_attach),
+ DEVMETHOD(device_detach, ksz8995ma_detach),
+
+ /* bus interface */
+ DEVMETHOD(bus_add_child, device_add_child_ordered),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, ksz8995ma_readphy),
+ DEVMETHOD(miibus_writereg, ksz8995ma_writephy),
+ DEVMETHOD(miibus_statchg, ksz8995ma_statchg),
+
+ /* etherswitch interface */
+ DEVMETHOD(etherswitch_lock, ksz8995ma_lock),
+ DEVMETHOD(etherswitch_unlock, ksz8995ma_unlock),
+ DEVMETHOD(etherswitch_getinfo, ksz8995ma_getinfo),
+ DEVMETHOD(etherswitch_readreg, ksz8995ma_readreg),
+ DEVMETHOD(etherswitch_writereg, ksz8995ma_writereg),
+ DEVMETHOD(etherswitch_readphyreg, ksz8995ma_readphy),
+ DEVMETHOD(etherswitch_writephyreg, ksz8995ma_writephy),
+ DEVMETHOD(etherswitch_getport, ksz8995ma_getport),
+ DEVMETHOD(etherswitch_setport, ksz8995ma_setport),
+ DEVMETHOD(etherswitch_getvgroup, ksz8995ma_getvgroup),
+ DEVMETHOD(etherswitch_setvgroup, ksz8995ma_setvgroup),
+ DEVMETHOD(etherswitch_setconf, ksz8995ma_setconf),
+ DEVMETHOD(etherswitch_getconf, ksz8995ma_getconf),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(ksz8995ma, ksz8995ma_driver, ksz8995ma_methods,
+ sizeof(struct ksz8995ma_softc));
+
+DRIVER_MODULE(ksz8995ma, spibus, ksz8995ma_driver, 0, 0);
+DRIVER_MODULE(miibus, ksz8995ma, miibus_driver, 0, 0);
+DRIVER_MODULE(etherswitch, ksz8995ma, etherswitch_driver, 0, 0);
+MODULE_VERSION(ksz8995ma, 1);
+MODULE_DEPEND(ksz8995ma, spibus, 1, 1, 1); /* XXX which versions? */
+MODULE_DEPEND(ksz8995ma, miibus, 1, 1, 1); /* XXX which versions? */
+MODULE_DEPEND(ksz8995ma, etherswitch, 1, 1, 1); /* XXX which versions? */
diff --git a/sys/dev/etherswitch/miiproxy.c b/sys/dev/etherswitch/miiproxy.c
new file mode 100644
index 000000000000..79342a9e8e03
--- /dev/null
+++ b/sys/dev/etherswitch/miiproxy.c
@@ -0,0 +1,434 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * 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/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_media.h>
+
+#include <dev/etherswitch/miiproxy.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+
+
+MALLOC_DECLARE(M_MIIPROXY);
+MALLOC_DEFINE(M_MIIPROXY, "miiproxy", "miiproxy data structures");
+
+driver_t miiproxy_driver;
+driver_t mdioproxy_driver;
+
+struct miiproxy_softc {
+ device_t parent;
+ device_t proxy;
+ device_t mdio;
+};
+
+struct mdioproxy_softc {
+};
+
+/*
+ * The rendezvous data structures and functions allow two device endpoints to
+ * match up, so that the proxy endpoint can be associated with a target
+ * endpoint. The proxy has to know the device name of the target that it
+ * wants to associate with, for example through a hint. The rendezvous code
+ * makes no assumptions about the devices that want to meet.
+ */
+struct rendezvous_entry;
+
+enum rendezvous_op {
+ RENDEZVOUS_ATTACH,
+ RENDEZVOUS_DETACH
+};
+
+typedef int (*rendezvous_callback_t)(enum rendezvous_op,
+ struct rendezvous_entry *);
+
+static SLIST_HEAD(rendezvoushead, rendezvous_entry) rendezvoushead =
+ SLIST_HEAD_INITIALIZER(rendezvoushead);
+
+struct rendezvous_endpoint {
+ device_t device;
+ const char *name;
+ rendezvous_callback_t callback;
+};
+
+struct rendezvous_entry {
+ SLIST_ENTRY(rendezvous_entry) entries;
+ struct rendezvous_endpoint proxy;
+ struct rendezvous_endpoint target;
+};
+
+/*
+ * Call the callback routines for both the proxy and the target. If either
+ * returns an error, undo the attachment.
+ */
+static int
+rendezvous_attach(struct rendezvous_entry *e, struct rendezvous_endpoint *ep)
+{
+ int error;
+
+ error = e->proxy.callback(RENDEZVOUS_ATTACH, e);
+ if (error == 0) {
+ error = e->target.callback(RENDEZVOUS_ATTACH, e);
+ if (error != 0) {
+ e->proxy.callback(RENDEZVOUS_DETACH, e);
+ ep->device = NULL;
+ ep->callback = NULL;
+ }
+ }
+ return (error);
+}
+
+/*
+ * Create an entry for the proxy in the rendezvous list. The name parameter
+ * indicates the name of the device that is the target endpoint for this
+ * rendezvous. The callback will be invoked as soon as the target is
+ * registered: either immediately if the target registered itself earlier,
+ * or once the target registers. Returns ENXIO if the target has not yet
+ * registered.
+ */
+static int
+rendezvous_register_proxy(device_t dev, const char *name,
+ rendezvous_callback_t callback)
+{
+ struct rendezvous_entry *e;
+
+ KASSERT(callback != NULL, ("callback must be set"));
+ SLIST_FOREACH(e, &rendezvoushead, entries) {
+ if (strcmp(name, e->target.name) == 0) {
+ /* the target is already attached */
+ e->proxy.name = device_get_nameunit(dev);
+ e->proxy.device = dev;
+ e->proxy.callback = callback;
+ return (rendezvous_attach(e, &e->proxy));
+ }
+ }
+ e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO);
+ e->proxy.name = device_get_nameunit(dev);
+ e->proxy.device = dev;
+ e->proxy.callback = callback;
+ e->target.name = name;
+ SLIST_INSERT_HEAD(&rendezvoushead, e, entries);
+ return (ENXIO);
+}
+
+/*
+ * Create an entry in the rendezvous list for the target.
+ * Returns ENXIO if the proxy has not yet registered.
+ */
+static int
+rendezvous_register_target(device_t dev, rendezvous_callback_t callback)
+{
+ struct rendezvous_entry *e;
+ const char *name;
+
+ KASSERT(callback != NULL, ("callback must be set"));
+ name = device_get_nameunit(dev);
+ SLIST_FOREACH(e, &rendezvoushead, entries) {
+ if (strcmp(name, e->target.name) == 0) {
+ e->target.device = dev;
+ e->target.callback = callback;
+ return (rendezvous_attach(e, &e->target));
+ }
+ }
+ e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO);
+ e->target.name = name;
+ e->target.device = dev;
+ e->target.callback = callback;
+ SLIST_INSERT_HEAD(&rendezvoushead, e, entries);
+ return (ENXIO);
+}
+
+/*
+ * Remove the registration for the proxy.
+ */
+static int
+rendezvous_unregister_proxy(device_t dev)
+{
+ struct rendezvous_entry *e;
+ int error = 0;
+
+ SLIST_FOREACH(e, &rendezvoushead, entries) {
+ if (e->proxy.device == dev) {
+ if (e->target.device == NULL) {
+ SLIST_REMOVE(&rendezvoushead, e, rendezvous_entry, entries);
+ free(e, M_MIIPROXY);
+ return (0);
+ } else {
+ e->proxy.callback(RENDEZVOUS_DETACH, e);
+ e->target.callback(RENDEZVOUS_DETACH, e);
+ }
+ e->proxy.device = NULL;
+ e->proxy.callback = NULL;
+ return (error);
+ }
+ }
+ return (ENOENT);
+}
+
+/*
+ * Remove the registration for the target.
+ */
+static int
+rendezvous_unregister_target(device_t dev)
+{
+ struct rendezvous_entry *e;
+ int error = 0;
+
+ SLIST_FOREACH(e, &rendezvoushead, entries) {
+ if (e->target.device == dev) {
+ if (e->proxy.device == NULL) {
+ SLIST_REMOVE(&rendezvoushead, e, rendezvous_entry, entries);
+ free(e, M_MIIPROXY);
+ return (0);
+ } else {
+ e->proxy.callback(RENDEZVOUS_DETACH, e);
+ e->target.callback(RENDEZVOUS_DETACH, e);
+ }
+ e->target.device = NULL;
+ e->target.callback = NULL;
+ return (error);
+ }
+ }
+ return (ENOENT);
+}
+
+/*
+ * Functions of the proxy that is interposed between the ethernet interface
+ * driver and the miibus device.
+ */
+
+static int
+miiproxy_rendezvous_callback(enum rendezvous_op op, struct rendezvous_entry *rendezvous)
+{
+ struct miiproxy_softc *sc = device_get_softc(rendezvous->proxy.device);
+
+ switch (op) {
+ case RENDEZVOUS_ATTACH:
+ sc->mdio = device_get_parent(rendezvous->target.device);
+ break;
+ case RENDEZVOUS_DETACH:
+ sc->mdio = NULL;
+ break;
+ }
+ return (0);
+}
+
+static int
+miiproxy_probe(device_t dev)
+{
+ device_set_desc(dev, "MII/MDIO proxy, MII side");
+
+ return (BUS_PROBE_SPECIFIC);
+}
+
+static int
+miiproxy_attach(device_t dev)
+{
+
+ /*
+ * The ethernet interface needs to call mii_attach_proxy() to pass
+ * the relevant parameters for rendezvous with the MDIO target.
+ */
+ bus_attach_children(dev);
+ return (0);
+}
+
+static int
+miiproxy_detach(device_t dev)
+{
+
+ rendezvous_unregister_proxy(dev);
+ bus_generic_detach(dev);
+ return (0);
+}
+
+static int
+miiproxy_readreg(device_t dev, int phy, int reg)
+{
+ struct miiproxy_softc *sc = device_get_softc(dev);
+
+ if (sc->mdio != NULL)
+ return (MDIO_READREG(sc->mdio, phy, reg));
+ return (-1);
+}
+
+static int
+miiproxy_writereg(device_t dev, int phy, int reg, int val)
+{
+ struct miiproxy_softc *sc = device_get_softc(dev);
+
+ if (sc->mdio != NULL)
+ return (MDIO_WRITEREG(sc->mdio, phy, reg, val));
+ return (-1);
+}
+
+static void
+miiproxy_statchg(device_t dev)
+{
+
+ MIIBUS_STATCHG(device_get_parent(dev));
+}
+
+static void
+miiproxy_linkchg(device_t dev)
+{
+
+ MIIBUS_LINKCHG(device_get_parent(dev));
+}
+
+static void
+miiproxy_mediainit(device_t dev)
+{
+
+ MIIBUS_MEDIAINIT(device_get_parent(dev));
+}
+
+/*
+ * Functions for the MDIO target device driver.
+ */
+static int
+mdioproxy_rendezvous_callback(enum rendezvous_op op, struct rendezvous_entry *rendezvous)
+{
+ return (0);
+}
+
+static void
+mdioproxy_identify(driver_t *driver, device_t parent)
+{
+ if (device_find_child(parent, driver->name, DEVICE_UNIT_ANY) == NULL) {
+ BUS_ADD_CHILD(parent, 0, driver->name, DEVICE_UNIT_ANY);
+ }
+}
+
+static int
+mdioproxy_probe(device_t dev)
+{
+ device_set_desc(dev, "MII/MDIO proxy, MDIO side");
+
+ return (BUS_PROBE_SPECIFIC);
+}
+
+static int
+mdioproxy_attach(device_t dev)
+{
+
+ rendezvous_register_target(dev, mdioproxy_rendezvous_callback);
+ bus_attach_children(dev);
+ return (0);
+}
+
+static int
+mdioproxy_detach(device_t dev)
+{
+
+ rendezvous_unregister_target(dev);
+ bus_generic_detach(dev);
+ return (0);
+}
+
+/*
+ * Attach this proxy in place of miibus. The target MDIO must be attached
+ * already. Returns NULL on error.
+ */
+device_t
+mii_attach_proxy(device_t dev)
+{
+ struct miiproxy_softc *sc;
+ const char *name;
+ device_t miiproxy;
+
+ if (resource_string_value(device_get_name(dev),
+ device_get_unit(dev), "mdio", &name) != 0) {
+ if (bootverbose)
+ printf("mii_attach_proxy: not attaching, no mdio"
+ " device hint for %s\n", device_get_nameunit(dev));
+ return (NULL);
+ }
+
+ miiproxy = device_add_child(dev, miiproxy_driver.name, DEVICE_UNIT_ANY);
+ bus_attach_children(dev);
+ sc = device_get_softc(miiproxy);
+ sc->parent = dev;
+ sc->proxy = miiproxy;
+ if (rendezvous_register_proxy(miiproxy, name, miiproxy_rendezvous_callback) != 0) {
+ device_printf(dev, "can't attach proxy\n");
+ return (NULL);
+ }
+ device_printf(miiproxy, "attached to target %s\n", device_get_nameunit(sc->mdio));
+ return (miiproxy);
+}
+
+static device_method_t miiproxy_methods[] = {
+ /* device interface */
+ DEVMETHOD(device_probe, miiproxy_probe),
+ DEVMETHOD(device_attach, miiproxy_attach),
+ DEVMETHOD(device_detach, miiproxy_detach),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, miiproxy_readreg),
+ DEVMETHOD(miibus_writereg, miiproxy_writereg),
+ DEVMETHOD(miibus_statchg, miiproxy_statchg),
+ DEVMETHOD(miibus_linkchg, miiproxy_linkchg),
+ DEVMETHOD(miibus_mediainit, miiproxy_mediainit),
+
+ DEVMETHOD_END
+};
+
+static device_method_t mdioproxy_methods[] = {
+ /* device interface */
+ DEVMETHOD(device_identify, mdioproxy_identify),
+ DEVMETHOD(device_probe, mdioproxy_probe),
+ DEVMETHOD(device_attach, mdioproxy_attach),
+ DEVMETHOD(device_detach, mdioproxy_detach),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(miiproxy, miiproxy_driver, miiproxy_methods,
+ sizeof(struct miiproxy_softc));
+DEFINE_CLASS_0(mdioproxy, mdioproxy_driver, mdioproxy_methods,
+ sizeof(struct mdioproxy_softc));
+
+DRIVER_MODULE(mdioproxy, mdio, mdioproxy_driver, 0, 0);
+DRIVER_MODULE(miibus, miiproxy, miibus_driver, 0, 0);
+MODULE_VERSION(miiproxy, 1);
+MODULE_DEPEND(miiproxy, miibus, 1, 1, 1);
+MODULE_DEPEND(miiproxy, mdio, 1, 1, 1);
diff --git a/sys/dev/etherswitch/miiproxy.h b/sys/dev/etherswitch/miiproxy.h
new file mode 100644
index 000000000000..5cb05fb91df7
--- /dev/null
+++ b/sys/dev/etherswitch/miiproxy.h
@@ -0,0 +1,36 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * 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 __DEV_ETHERSWITCH_MIIPROXY_H__
+#define __DEV_ETHERSWITCH_MIIPROXY_H__
+
+extern driver_t miiproxy_driver;
+
+device_t mii_attach_proxy(device_t dev);
+
+#endif /* __DEV_ETHERSWITCH_MIIPROXY_H__ */
diff --git a/sys/dev/etherswitch/mtkswitch/mtkswitch.c b/sys/dev/etherswitch/mtkswitch/mtkswitch.c
new file mode 100644
index 000000000000..ff7aee22398f
--- /dev/null
+++ b/sys/dev/etherswitch/mtkswitch/mtkswitch.c
@@ -0,0 +1,663 @@
+/*-
+ * Copyright (c) 2016 Stanislav Galabov.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.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/ethernet.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+
+#include <dev/etherswitch/etherswitch.h>
+#include <dev/etherswitch/mtkswitch/mtkswitchvar.h>
+
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+#define DEBUG
+
+#if defined(DEBUG)
+static SYSCTL_NODE(_debug, OID_AUTO, mtkswitch, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
+ "mtkswitch");
+#endif
+
+static inline int mtkswitch_portforphy(int phy);
+static int mtkswitch_ifmedia_upd(if_t ifp);
+static void mtkswitch_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr);
+static void mtkswitch_tick(void *arg);
+
+static const struct ofw_compat_data compat_data[] = {
+ { "ralink,rt3050-esw", MTK_SWITCH_RT3050 },
+ { "ralink,rt3352-esw", MTK_SWITCH_RT3352 },
+ { "ralink,rt5350-esw", MTK_SWITCH_RT5350 },
+ { "mediatek,mt7620-gsw", MTK_SWITCH_MT7620 },
+ { "mediatek,mt7621-gsw", MTK_SWITCH_MT7621 },
+ { "mediatek,mt7628-esw", MTK_SWITCH_MT7628 },
+
+ /* Sentinel */
+ { NULL, MTK_SWITCH_NONE }
+};
+
+static int
+mtkswitch_probe(device_t dev)
+{
+ struct mtkswitch_softc *sc;
+ mtk_switch_type switch_type;
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ switch_type = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
+ if (switch_type == MTK_SWITCH_NONE)
+ return (ENXIO);
+
+ sc = device_get_softc(dev);
+ bzero(sc, sizeof(*sc));
+ sc->sc_switchtype = switch_type;
+
+ device_set_desc(dev, "MTK Switch Driver");
+
+ return (0);
+}
+
+static int
+mtkswitch_attach_phys(struct mtkswitch_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 < sc->numphys; phy++) {
+ if ((sc->phymap & (1u << phy)) == 0) {
+ sc->ifp[phy] = NULL;
+ sc->ifname[phy] = NULL;
+ sc->miibus[phy] = NULL;
+ continue;
+ }
+ sc->ifp[phy] = if_alloc(IFT_ETHER);
+ if_setsoftc(sc->ifp[phy], sc);
+ if_setflagbits(sc->ifp[phy], IFF_UP | IFF_BROADCAST |
+ IFF_DRV_RUNNING | IFF_SIMPLEX, 0);
+ sc->ifname[phy] = malloc(strlen(name) + 1, M_DEVBUF, M_WAITOK);
+ bcopy(name, sc->ifname[phy], strlen(name) + 1);
+ if_initname(sc->ifp[phy], sc->ifname[phy],
+ mtkswitch_portforphy(phy));
+ err = mii_attach(sc->sc_dev, &sc->miibus[phy], sc->ifp[phy],
+ mtkswitch_ifmedia_upd, mtkswitch_ifmedia_sts,
+ BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0);
+ if (err != 0) {
+ device_printf(sc->sc_dev,
+ "attaching PHY %d failed\n",
+ phy);
+ } else {
+ DPRINTF(sc->sc_dev, "%s attached to pseudo interface "
+ "%s\n", device_get_nameunit(sc->miibus[phy]),
+ if_name(sc->ifp[phy]));
+ }
+ }
+ return (err);
+}
+
+static int
+mtkswitch_set_vlan_mode(struct mtkswitch_softc *sc, uint32_t mode)
+{
+
+ /* Check for invalid modes. */
+ if ((mode & sc->info.es_vlan_caps) != mode)
+ return (EINVAL);
+
+ sc->vlan_mode = mode;
+
+ /* Reset VLANs. */
+ sc->hal.mtkswitch_vlan_init_hw(sc);
+
+ return (0);
+}
+
+static int
+mtkswitch_attach(device_t dev)
+{
+ struct mtkswitch_softc *sc;
+ int err = 0;
+ int port, rid;
+
+ sc = device_get_softc(dev);
+
+ /* sc->sc_switchtype is already decided in mtkswitch_probe() */
+ sc->numports = MTKSWITCH_MAX_PORTS;
+ sc->numphys = MTKSWITCH_MAX_PHYS;
+ sc->cpuport = MTKSWITCH_CPU_PORT;
+ sc->sc_dev = dev;
+
+ /* Attach switch related functions */
+ if (sc->sc_switchtype == MTK_SWITCH_NONE) {
+ device_printf(dev, "Unknown switch type\n");
+ return (ENXIO);
+ }
+
+ if (sc->sc_switchtype == MTK_SWITCH_MT7620 ||
+ sc->sc_switchtype == MTK_SWITCH_MT7621)
+ mtk_attach_switch_mt7620(sc);
+ else
+ mtk_attach_switch_rt3050(sc);
+
+ /* Allocate resources */
+ rid = 0;
+ sc->sc_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (sc->sc_res == NULL) {
+ device_printf(dev, "could not map memory\n");
+ return (ENXIO);
+ }
+
+ mtx_init(&sc->sc_mtx, "mtkswitch", NULL, MTX_DEF);
+
+ /* Reset the switch */
+ if (sc->hal.mtkswitch_reset(sc)) {
+ DPRINTF(dev, "%s: mtkswitch_reset: failed\n", __func__);
+ return (ENXIO);
+ }
+
+ err = sc->hal.mtkswitch_hw_setup(sc);
+ DPRINTF(dev, "%s: hw_setup: err=%d\n", __func__, err);
+ if (err != 0)
+ return (err);
+
+ err = sc->hal.mtkswitch_hw_global_setup(sc);
+ DPRINTF(dev, "%s: hw_global_setup: err=%d\n", __func__, err);
+ if (err != 0)
+ return (err);
+
+ /* Initialize the switch ports */
+ for (port = 0; port < sc->numports; port++) {
+ sc->hal.mtkswitch_port_init(sc, port);
+ }
+
+ /* Attach the PHYs and complete the bus enumeration */
+ err = mtkswitch_attach_phys(sc);
+ DPRINTF(dev, "%s: attach_phys: err=%d\n", __func__, err);
+ if (err != 0)
+ return (err);
+
+ /* Default to ingress filters off. */
+ err = mtkswitch_set_vlan_mode(sc, ETHERSWITCH_VLAN_DOT1Q);
+ DPRINTF(dev, "%s: set_vlan_mode: err=%d\n", __func__, err);
+ if (err != 0)
+ return (err);
+
+ bus_identify_children(dev);
+ bus_enumerate_hinted_children(dev);
+ bus_attach_children(dev);
+
+ callout_init_mtx(&sc->callout_tick, &sc->sc_mtx, 0);
+
+ MTKSWITCH_LOCK(sc);
+ mtkswitch_tick(sc);
+ MTKSWITCH_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+mtkswitch_detach(device_t dev)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+ int error, phy;
+
+ error = bus_generic_detach(dev);
+ if (error != 0)
+ return (error);
+
+ callout_drain(&sc->callout_tick);
+
+ for (phy = 0; phy < MTKSWITCH_MAX_PHYS; phy++) {
+ if (sc->ifp[phy] != NULL)
+ if_free(sc->ifp[phy]);
+ free(sc->ifname[phy], M_DEVBUF);
+ }
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+/* PHY <-> port mapping is currently 1:1 */
+static inline int
+mtkswitch_portforphy(int phy)
+{
+
+ return (phy);
+}
+
+static inline int
+mtkswitch_phyforport(int port)
+{
+
+ return (port);
+}
+
+static inline struct mii_data *
+mtkswitch_miiforport(struct mtkswitch_softc *sc, int port)
+{
+ int phy = mtkswitch_phyforport(port);
+
+ if (phy < 0 || phy >= MTKSWITCH_MAX_PHYS || sc->miibus[phy] == NULL)
+ return (NULL);
+
+ return (device_get_softc(sc->miibus[phy]));
+}
+
+static inline if_t
+mtkswitch_ifpforport(struct mtkswitch_softc *sc, int port)
+{
+ int phy = mtkswitch_phyforport(port);
+
+ if (phy < 0 || phy >= MTKSWITCH_MAX_PHYS)
+ return (NULL);
+
+ return (sc->ifp[phy]);
+}
+
+/*
+ * Convert port status to ifmedia.
+ */
+static void
+mtkswitch_update_ifmedia(uint32_t portstatus, u_int *media_status,
+ u_int *media_active)
+{
+ *media_active = IFM_ETHER;
+ *media_status = IFM_AVALID;
+
+ if ((portstatus & MTKSWITCH_LINK_UP) != 0)
+ *media_status |= IFM_ACTIVE;
+ else {
+ *media_active |= IFM_NONE;
+ return;
+ }
+
+ switch (portstatus & MTKSWITCH_SPEED_MASK) {
+ case MTKSWITCH_SPEED_10:
+ *media_active |= IFM_10_T;
+ break;
+ case MTKSWITCH_SPEED_100:
+ *media_active |= IFM_100_TX;
+ break;
+ case MTKSWITCH_SPEED_1000:
+ *media_active |= IFM_1000_T;
+ break;
+ }
+
+ if ((portstatus & MTKSWITCH_DUPLEX) != 0)
+ *media_active |= IFM_FDX;
+ else
+ *media_active |= IFM_HDX;
+
+ if ((portstatus & MTKSWITCH_TXFLOW) != 0)
+ *media_active |= IFM_ETH_TXPAUSE;
+ if ((portstatus & MTKSWITCH_RXFLOW) != 0)
+ *media_active |= IFM_ETH_RXPAUSE;
+}
+
+static void
+mtkswitch_miipollstat(struct mtkswitch_softc *sc)
+{
+ struct mii_data *mii;
+ struct mii_softc *miisc;
+ uint32_t portstatus;
+ int i, port_flap = 0;
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ for (i = 0; i < sc->numphys; i++) {
+ if (sc->miibus[i] == NULL)
+ continue;
+ mii = device_get_softc(sc->miibus[i]);
+ portstatus = sc->hal.mtkswitch_get_port_status(sc,
+ mtkswitch_portforphy(i));
+
+ /* If a port has flapped - mark it so we can flush the ATU */
+ if (((mii->mii_media_status & IFM_ACTIVE) == 0 &&
+ (portstatus & MTKSWITCH_LINK_UP) != 0) ||
+ ((mii->mii_media_status & IFM_ACTIVE) != 0 &&
+ (portstatus & MTKSWITCH_LINK_UP) == 0)) {
+ port_flap = 1;
+ }
+
+ mtkswitch_update_ifmedia(portstatus, &mii->mii_media_status,
+ &mii->mii_media_active);
+ LIST_FOREACH(miisc, &mii->mii_phys, mii_list) {
+ if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) !=
+ miisc->mii_inst)
+ continue;
+ mii_phy_update(miisc, MII_POLLSTAT);
+ }
+ }
+
+ if (port_flap)
+ sc->hal.mtkswitch_atu_flush(sc);
+}
+
+static void
+mtkswitch_tick(void *arg)
+{
+ struct mtkswitch_softc *sc = arg;
+
+ mtkswitch_miipollstat(sc);
+ callout_reset(&sc->callout_tick, hz, mtkswitch_tick, sc);
+}
+
+static void
+mtkswitch_lock(device_t dev)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+ MTKSWITCH_LOCK(sc);
+}
+
+static void
+mtkswitch_unlock(device_t dev)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ MTKSWITCH_UNLOCK(sc);
+}
+
+static etherswitch_info_t *
+mtkswitch_getinfo(device_t dev)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+
+ return (&sc->info);
+}
+
+static inline int
+mtkswitch_is_cpuport(struct mtkswitch_softc *sc, int port)
+{
+
+ return (sc->cpuport == port);
+}
+
+static int
+mtkswitch_getport(device_t dev, etherswitch_port_t *p)
+{
+ struct mtkswitch_softc *sc;
+ struct mii_data *mii;
+ struct ifmediareq *ifmr;
+ int err;
+
+ sc = device_get_softc(dev);
+ if (p->es_port < 0 || p->es_port > sc->info.es_nports)
+ return (ENXIO);
+
+ err = sc->hal.mtkswitch_port_vlan_get(sc, p);
+ if (err != 0)
+ return (err);
+
+ mii = mtkswitch_miiforport(sc, p->es_port);
+ if (mtkswitch_is_cpuport(sc, p->es_port)) {
+ /* fill in fixed values for CPU port */
+ /* XXX is this valid in all cases? */
+ 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) {
+ err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr,
+ &mii->mii_media, SIOCGIFMEDIA);
+ if (err)
+ return (err);
+ } else {
+ ifmr = &p->es_ifmr;
+ ifmr->ifm_count = 0;
+ ifmr->ifm_current = ifmr->ifm_active = IFM_NONE;
+ ifmr->ifm_mask = 0;
+ ifmr->ifm_status = 0;
+ }
+ return (0);
+}
+
+static int
+mtkswitch_setport(device_t dev, etherswitch_port_t *p)
+{
+ int err;
+ struct mtkswitch_softc *sc;
+ struct ifmedia *ifm;
+ struct mii_data *mii;
+ if_t ifp;
+
+ sc = device_get_softc(dev);
+ if (p->es_port < 0 || p->es_port > sc->info.es_nports)
+ return (ENXIO);
+
+ /* Port flags. */
+ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
+ err = sc->hal.mtkswitch_port_vlan_setup(sc, p);
+ if (err)
+ return (err);
+ }
+
+ /* Do not allow media changes on CPU port. */
+ if (mtkswitch_is_cpuport(sc, p->es_port))
+ return (0);
+
+ mii = mtkswitch_miiforport(sc, p->es_port);
+ if (mii == NULL)
+ return (ENXIO);
+
+ ifp = mtkswitch_ifpforport(sc, p->es_port);
+
+ ifm = &mii->mii_media;
+ return (ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA));
+}
+
+static void
+mtkswitch_statchg(device_t dev)
+{
+
+ DPRINTF(dev, "%s\n", __func__);
+}
+
+static int
+mtkswitch_ifmedia_upd(if_t ifp)
+{
+ struct mtkswitch_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = mtkswitch_miiforport(sc, if_getdunit(ifp));
+
+ if (mii == NULL)
+ return (ENXIO);
+ mii_mediachg(mii);
+ return (0);
+}
+
+static void
+mtkswitch_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct mtkswitch_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = mtkswitch_miiforport(sc, if_getdunit(ifp));
+
+ DPRINTF(sc->sc_dev, "%s\n", __func__);
+
+ if (mii == NULL)
+ return;
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+}
+
+static int
+mtkswitch_getconf(device_t dev, etherswitch_conf_t *conf)
+{
+ struct mtkswitch_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ /* Return the VLAN mode. */
+ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE;
+ conf->vlan_mode = sc->vlan_mode;
+
+ return (0);
+}
+
+static int
+mtkswitch_setconf(device_t dev, etherswitch_conf_t *conf)
+{
+ struct mtkswitch_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+
+ /* Set the VLAN mode. */
+ if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) {
+ err = mtkswitch_set_vlan_mode(sc, conf->vlan_mode);
+ if (err != 0)
+ return (err);
+ }
+
+ return (0);
+}
+
+static int
+mtkswitch_getvgroup(device_t dev, etherswitch_vlangroup_t *e)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+
+ return (sc->hal.mtkswitch_vlan_getvgroup(sc, e));
+}
+
+static int
+mtkswitch_setvgroup(device_t dev, etherswitch_vlangroup_t *e)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+
+ return (sc->hal.mtkswitch_vlan_setvgroup(sc, e));
+}
+
+static int
+mtkswitch_readphy(device_t dev, int phy, int reg)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+
+ return (sc->hal.mtkswitch_phy_read(dev, phy, reg));
+}
+
+static int
+mtkswitch_writephy(device_t dev, int phy, int reg, int val)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+
+ return (sc->hal.mtkswitch_phy_write(dev, phy, reg, val));
+}
+
+static int
+mtkswitch_readreg(device_t dev, int addr)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+
+ return (sc->hal.mtkswitch_reg_read(dev, addr));
+}
+
+static int
+mtkswitch_writereg(device_t dev, int addr, int value)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+
+ return (sc->hal.mtkswitch_reg_write(dev, addr, value));
+}
+
+static device_method_t mtkswitch_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, mtkswitch_probe),
+ DEVMETHOD(device_attach, mtkswitch_attach),
+ DEVMETHOD(device_detach, mtkswitch_detach),
+
+ /* bus interface */
+ DEVMETHOD(bus_add_child, device_add_child_ordered),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, mtkswitch_readphy),
+ DEVMETHOD(miibus_writereg, mtkswitch_writephy),
+ DEVMETHOD(miibus_statchg, mtkswitch_statchg),
+
+ /* MDIO interface */
+ DEVMETHOD(mdio_readreg, mtkswitch_readphy),
+ DEVMETHOD(mdio_writereg, mtkswitch_writephy),
+
+ /* ehterswitch interface */
+ DEVMETHOD(etherswitch_lock, mtkswitch_lock),
+ DEVMETHOD(etherswitch_unlock, mtkswitch_unlock),
+ DEVMETHOD(etherswitch_getinfo, mtkswitch_getinfo),
+ DEVMETHOD(etherswitch_readreg, mtkswitch_readreg),
+ DEVMETHOD(etherswitch_writereg, mtkswitch_writereg),
+ DEVMETHOD(etherswitch_readphyreg, mtkswitch_readphy),
+ DEVMETHOD(etherswitch_writephyreg, mtkswitch_writephy),
+ DEVMETHOD(etherswitch_getport, mtkswitch_getport),
+ DEVMETHOD(etherswitch_setport, mtkswitch_setport),
+ DEVMETHOD(etherswitch_getvgroup, mtkswitch_getvgroup),
+ DEVMETHOD(etherswitch_setvgroup, mtkswitch_setvgroup),
+ DEVMETHOD(etherswitch_getconf, mtkswitch_getconf),
+ DEVMETHOD(etherswitch_setconf, mtkswitch_setconf),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(mtkswitch, mtkswitch_driver, mtkswitch_methods,
+ sizeof(struct mtkswitch_softc));
+
+DRIVER_MODULE(mtkswitch, simplebus, mtkswitch_driver, 0, 0);
+DRIVER_MODULE(miibus, mtkswitch, miibus_driver, 0, 0);
+DRIVER_MODULE(mdio, mtkswitch, mdio_driver, 0, 0);
+DRIVER_MODULE(etherswitch, mtkswitch, etherswitch_driver, 0, 0);
+MODULE_VERSION(mtkswitch, 1);
+MODULE_DEPEND(mtkswitch, miibus, 1, 1, 1);
+MODULE_DEPEND(mtkswitch, etherswitch, 1, 1, 1);
diff --git a/sys/dev/etherswitch/mtkswitch/mtkswitch_mt7620.c b/sys/dev/etherswitch/mtkswitch/mtkswitch_mt7620.c
new file mode 100644
index 000000000000..1d5a43712288
--- /dev/null
+++ b/sys/dev/etherswitch/mtkswitch/mtkswitch_mt7620.c
@@ -0,0 +1,562 @@
+/*-
+ * Copyright (c) 2016 Stanislav Galabov.
+ * All rights reserved.
+ *
+ * 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/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/rman.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/ethernet.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+
+#include <dev/etherswitch/etherswitch.h>
+#include <dev/etherswitch/mtkswitch/mtkswitchvar.h>
+#include <dev/etherswitch/mtkswitch/mtkswitch_mt7620.h>
+
+static int
+mtkswitch_phy_read_locked(struct mtkswitch_softc *sc, int phy, int reg)
+{
+ uint32_t data;
+
+ MTKSWITCH_WRITE(sc, MTKSWITCH_PIAC, PIAC_PHY_ACS_ST | PIAC_MDIO_ST |
+ (reg << PIAC_MDIO_REG_ADDR_OFF) | (phy << PIAC_MDIO_PHY_ADDR_OFF) |
+ PIAC_MDIO_CMD_READ);
+ while ((data = MTKSWITCH_READ(sc, MTKSWITCH_PIAC)) & PIAC_PHY_ACS_ST);
+
+ return ((int)(data & PIAC_MDIO_RW_DATA_MASK));
+}
+
+static int
+mtkswitch_phy_read(device_t dev, int phy, int reg)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+ int data;
+
+ if ((phy < 0 || phy >= 32) || (reg < 0 || reg >= 32))
+ return (ENXIO);
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+ MTKSWITCH_LOCK(sc);
+ data = mtkswitch_phy_read_locked(sc, phy, reg);
+ MTKSWITCH_UNLOCK(sc);
+
+ return (data);
+}
+
+static int
+mtkswitch_phy_write_locked(struct mtkswitch_softc *sc, int phy, int reg,
+ int val)
+{
+
+ MTKSWITCH_WRITE(sc, MTKSWITCH_PIAC, PIAC_PHY_ACS_ST | PIAC_MDIO_ST |
+ (reg << PIAC_MDIO_REG_ADDR_OFF) | (phy << PIAC_MDIO_PHY_ADDR_OFF) |
+ (val & PIAC_MDIO_RW_DATA_MASK) | PIAC_MDIO_CMD_WRITE);
+ while (MTKSWITCH_READ(sc, MTKSWITCH_PIAC) & PIAC_PHY_ACS_ST);
+
+ return (0);
+}
+
+static int
+mtkswitch_phy_write(device_t dev, int phy, int reg, int val)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+ int res;
+
+ if ((phy < 0 || phy >= 32) || (reg < 0 || reg >= 32))
+ return (ENXIO);
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+ MTKSWITCH_LOCK(sc);
+ res = mtkswitch_phy_write_locked(sc, phy, reg, val);
+ MTKSWITCH_UNLOCK(sc);
+
+ return (res);
+}
+
+static uint32_t
+mtkswitch_reg_read32(struct mtkswitch_softc *sc, int reg)
+{
+
+ return (MTKSWITCH_READ(sc, reg));
+}
+
+static uint32_t
+mtkswitch_reg_write32(struct mtkswitch_softc *sc, int reg, uint32_t val)
+{
+
+ MTKSWITCH_WRITE(sc, reg, val);
+ return (0);
+}
+
+static uint32_t
+mtkswitch_reg_read32_mt7621(struct mtkswitch_softc *sc, int reg)
+{
+ uint32_t low, hi;
+
+ mtkswitch_phy_write_locked(sc, MTKSWITCH_GLOBAL_PHY,
+ MTKSWITCH_GLOBAL_REG, MTKSWITCH_REG_ADDR(reg));
+ low = mtkswitch_phy_read_locked(sc, MTKSWITCH_GLOBAL_PHY,
+ MTKSWITCH_REG_LO(reg));
+ hi = mtkswitch_phy_read_locked(sc, MTKSWITCH_GLOBAL_PHY,
+ MTKSWITCH_REG_HI(reg));
+ return (low | (hi << 16));
+}
+
+static uint32_t
+mtkswitch_reg_write32_mt7621(struct mtkswitch_softc *sc, int reg, uint32_t val)
+{
+
+ mtkswitch_phy_write_locked(sc, MTKSWITCH_GLOBAL_PHY,
+ MTKSWITCH_GLOBAL_REG, MTKSWITCH_REG_ADDR(reg));
+ mtkswitch_phy_write_locked(sc, MTKSWITCH_GLOBAL_PHY,
+ MTKSWITCH_REG_LO(reg), MTKSWITCH_VAL_LO(val));
+ mtkswitch_phy_write_locked(sc, MTKSWITCH_GLOBAL_PHY,
+ MTKSWITCH_REG_HI(reg), MTKSWITCH_VAL_HI(val));
+ return (0);
+}
+
+static int
+mtkswitch_reg_read(device_t dev, int reg)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+ uint32_t val;
+
+ val = sc->hal.mtkswitch_read(sc, MTKSWITCH_REG32(reg));
+ if (MTKSWITCH_IS_HI16(reg))
+ return (MTKSWITCH_HI16(val));
+ return (MTKSWITCH_LO16(val));
+}
+
+static int
+mtkswitch_reg_write(device_t dev, int reg, int val)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+ uint32_t tmp;
+
+ tmp = sc->hal.mtkswitch_read(sc, MTKSWITCH_REG32(reg));
+ if (MTKSWITCH_IS_HI16(reg)) {
+ tmp &= MTKSWITCH_LO16_MSK;
+ tmp |= MTKSWITCH_TO_HI16(val);
+ } else {
+ tmp &= MTKSWITCH_HI16_MSK;
+ tmp |= MTKSWITCH_TO_LO16(val);
+ }
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_REG32(reg), tmp);
+
+ return (0);
+}
+
+static int
+mtkswitch_reset(struct mtkswitch_softc *sc)
+{
+
+ /* We don't reset the switch for now */
+ return (0);
+}
+
+static int
+mtkswitch_hw_setup(struct mtkswitch_softc *sc)
+{
+
+ /*
+ * TODO: parse the device tree and see if we need to configure
+ * ports, etc. differently. For now we fallback to defaults.
+ */
+
+ /* Called early and hence unlocked */
+ return (0);
+}
+
+static int
+mtkswitch_hw_global_setup(struct mtkswitch_softc *sc)
+{
+ /* Currently does nothing */
+
+ /* Called early and hence unlocked */
+ return (0);
+}
+
+static void
+mtkswitch_port_init(struct mtkswitch_softc *sc, int port)
+{
+ uint32_t val;
+
+ /* Called early and hence unlocked */
+
+ /* Set the port to secure mode */
+ val = sc->hal.mtkswitch_read(sc, MTKSWITCH_PCR(port));
+ val |= PCR_PORT_VLAN_SECURE;
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_PCR(port), val);
+
+ /* Set port's vlan_attr to user port */
+ val = sc->hal.mtkswitch_read(sc, MTKSWITCH_PVC(port));
+ val &= ~PVC_VLAN_ATTR_MASK;
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_PVC(port), val);
+
+ val = PMCR_CFG_DEFAULT;
+ if (port == sc->cpuport)
+ val |= PMCR_FORCE_LINK | PMCR_FORCE_DPX | PMCR_FORCE_SPD_1000 |
+ PMCR_FORCE_MODE;
+ /* Set port's MAC to default settings */
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_PMCR(port), val);
+}
+
+static uint32_t
+mtkswitch_get_port_status(struct mtkswitch_softc *sc, int port)
+{
+ uint32_t val, res, tmp;
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ res = 0;
+ val = sc->hal.mtkswitch_read(sc, MTKSWITCH_PMSR(port));
+
+ if (val & PMSR_MAC_LINK_STS)
+ res |= MTKSWITCH_LINK_UP;
+ if (val & PMSR_MAC_DPX_STS)
+ res |= MTKSWITCH_DUPLEX;
+ tmp = PMSR_MAC_SPD(val);
+ if (tmp == 0)
+ res |= MTKSWITCH_SPEED_10;
+ else if (tmp == 1)
+ res |= MTKSWITCH_SPEED_100;
+ else if (tmp == 2)
+ res |= MTKSWITCH_SPEED_1000;
+ if (val & PMSR_TX_FC_STS)
+ res |= MTKSWITCH_TXFLOW;
+ if (val & PMSR_RX_FC_STS)
+ res |= MTKSWITCH_RXFLOW;
+
+ return (res);
+}
+
+static int
+mtkswitch_atu_flush(struct mtkswitch_softc *sc)
+{
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* Flush all non-static MAC addresses */
+ while (sc->hal.mtkswitch_read(sc, MTKSWITCH_ATC) & ATC_BUSY);
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_ATC, ATC_BUSY |
+ ATC_AC_MAT_NON_STATIC_MACS | ATC_AC_CMD_CLEAN);
+ while (sc->hal.mtkswitch_read(sc, MTKSWITCH_ATC) & ATC_BUSY);
+
+ return (0);
+}
+
+static int
+mtkswitch_port_vlan_setup(struct mtkswitch_softc *sc, etherswitch_port_t *p)
+{
+ int err;
+
+ /*
+ * Port behaviour wrt tag/untag/stack is currently defined per-VLAN.
+ * So we say we don't support it here.
+ */
+ if ((p->es_flags & (ETHERSWITCH_PORT_DOUBLE_TAG |
+ ETHERSWITCH_PORT_ADDTAG | ETHERSWITCH_PORT_STRIPTAG)) != 0)
+ return (ENOTSUP);
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+ MTKSWITCH_LOCK(sc);
+
+ /* Set the PVID */
+ if (p->es_pvid != 0) {
+ err = sc->hal.mtkswitch_vlan_set_pvid(sc, p->es_port,
+ p->es_pvid);
+ if (err != 0) {
+ MTKSWITCH_UNLOCK(sc);
+ return (err);
+ }
+ }
+
+ MTKSWITCH_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+mtkswitch_port_vlan_get(struct mtkswitch_softc *sc, etherswitch_port_t *p)
+{
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+ MTKSWITCH_LOCK(sc);
+
+ /* Retrieve the PVID */
+ sc->hal.mtkswitch_vlan_get_pvid(sc, p->es_port, &p->es_pvid);
+
+ /*
+ * Port flags are not supported at the moment.
+ * Port's tag/untag/stack behaviour is defined per-VLAN.
+ */
+ p->es_flags = 0;
+
+ MTKSWITCH_UNLOCK(sc);
+
+ return (0);
+}
+
+static void
+mtkswitch_invalidate_vlan(struct mtkswitch_softc *sc, uint32_t vid)
+{
+
+ while (sc->hal.mtkswitch_read(sc, MTKSWITCH_VTCR) & VTCR_BUSY);
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_VTCR, VTCR_BUSY |
+ VTCR_FUNC_VID_INVALID | (vid & VTCR_VID_MASK));
+ while (sc->hal.mtkswitch_read(sc, MTKSWITCH_VTCR) & VTCR_BUSY);
+}
+
+static void
+mtkswitch_vlan_init_hw(struct mtkswitch_softc *sc)
+{
+ uint32_t val, vid, i;
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+ MTKSWITCH_LOCK(sc);
+ /* Reset all VLANs to defaults first */
+ for (i = 0; i < sc->info.es_nvlangroups; i++) {
+ mtkswitch_invalidate_vlan(sc, i);
+ if (sc->sc_switchtype == MTK_SWITCH_MT7620) {
+ val = sc->hal.mtkswitch_read(sc, MTKSWITCH_VTIM(i));
+ val &= ~(VTIM_MASK << VTIM_OFF(i));
+ val |= ((i + 1) << VTIM_OFF(i));
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_VTIM(i), val);
+ }
+ }
+
+ /* Now, add all ports as untagged members of VLAN 1 */
+ if (sc->sc_switchtype == MTK_SWITCH_MT7620) {
+ /* MT7620 uses vid index instead of actual vid */
+ vid = 0;
+ } else {
+ /* MT7621 uses the vid itself */
+ vid = 1;
+ }
+ val = VAWD1_IVL_MAC | VAWD1_VTAG_EN | VAWD1_VALID;
+ for (i = 0; i < sc->info.es_nports; i++)
+ val |= VAWD1_PORT_MEMBER(i);
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_VAWD1, val);
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_VAWD2, 0);
+ val = VTCR_BUSY | VTCR_FUNC_VID_WRITE | vid;
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_VTCR, val);
+
+ /* Set all port PVIDs to 1 */
+ for (i = 0; i < sc->info.es_nports; i++) {
+ sc->hal.mtkswitch_vlan_set_pvid(sc, i, 1);
+ }
+
+ MTKSWITCH_UNLOCK(sc);
+}
+
+static int
+mtkswitch_vlan_getvgroup(struct mtkswitch_softc *sc, etherswitch_vlangroup_t *v)
+{
+ uint32_t val, i;
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ if ((sc->vlan_mode != ETHERSWITCH_VLAN_DOT1Q) ||
+ (v->es_vlangroup > sc->info.es_nvlangroups))
+ return (EINVAL);
+
+ /* Reset the member ports. */
+ v->es_untagged_ports = 0;
+ v->es_member_ports = 0;
+
+ /* Not supported for now */
+ v->es_fid = 0;
+
+ MTKSWITCH_LOCK(sc);
+ if (sc->sc_switchtype == MTK_SWITCH_MT7620) {
+ v->es_vid = (sc->hal.mtkswitch_read(sc,
+ MTKSWITCH_VTIM(v->es_vlangroup)) >>
+ VTIM_OFF(v->es_vlangroup)) & VTIM_MASK;
+ } else {
+ v->es_vid = v->es_vlangroup;
+ }
+
+ while (sc->hal.mtkswitch_read(sc, MTKSWITCH_VTCR) & VTCR_BUSY);
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_VTCR, VTCR_BUSY |
+ VTCR_FUNC_VID_READ | (v->es_vlangroup & VTCR_VID_MASK));
+ while ((val = sc->hal.mtkswitch_read(sc, MTKSWITCH_VTCR)) & VTCR_BUSY);
+ if (val & VTCR_IDX_INVALID) {
+ MTKSWITCH_UNLOCK(sc);
+ return (0);
+ }
+
+ val = sc->hal.mtkswitch_read(sc, MTKSWITCH_VAWD1);
+ if (val & VAWD1_VALID)
+ v->es_vid |= ETHERSWITCH_VID_VALID;
+ else {
+ MTKSWITCH_UNLOCK(sc);
+ return (0);
+ }
+ v->es_member_ports = (val >> VAWD1_MEMBER_OFF) & VAWD1_MEMBER_MASK;
+
+ val = sc->hal.mtkswitch_read(sc, MTKSWITCH_VAWD2);
+ for (i = 0; i < sc->info.es_nports; i++) {
+ if ((val & VAWD2_PORT_MASK(i)) == VAWD2_PORT_UNTAGGED(i))
+ v->es_untagged_ports |= (1<<i);
+ }
+
+ MTKSWITCH_UNLOCK(sc);
+ return (0);
+}
+
+static int
+mtkswitch_vlan_setvgroup(struct mtkswitch_softc *sc, etherswitch_vlangroup_t *v)
+{
+ uint32_t val, i, vid;
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ if ((sc->vlan_mode != ETHERSWITCH_VLAN_DOT1Q) ||
+ (v->es_vlangroup > sc->info.es_nvlangroups))
+ return (EINVAL);
+
+ /* We currently don't support FID */
+ if (v->es_fid != 0)
+ return (EINVAL);
+
+ MTKSWITCH_LOCK(sc);
+ while (sc->hal.mtkswitch_read(sc, MTKSWITCH_VTCR) & VTCR_BUSY);
+ if (sc->sc_switchtype == MTK_SWITCH_MT7620) {
+ val = sc->hal.mtkswitch_read(sc,
+ MTKSWITCH_VTIM(v->es_vlangroup));
+ val &= ~(VTIM_MASK << VTIM_OFF(v->es_vlangroup));
+ val |= ((v->es_vid & VTIM_MASK) << VTIM_OFF(v->es_vlangroup));
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_VTIM(v->es_vlangroup),
+ val);
+ vid = v->es_vlangroup;
+ } else
+ vid = v->es_vid;
+
+ /* We use FID 0 */
+ val = VAWD1_IVL_MAC | VAWD1_VTAG_EN | VAWD1_VALID;
+ val |= ((v->es_member_ports & VAWD1_MEMBER_MASK) << VAWD1_MEMBER_OFF);
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_VAWD1, val);
+
+ /* Set tagged ports */
+ val = 0;
+ for (i = 0; i < sc->info.es_nports; i++)
+ if (((1<<i) & v->es_untagged_ports) == 0)
+ val |= VAWD2_PORT_TAGGED(i);
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_VAWD2, val);
+
+ /* Write the VLAN entry */
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_VTCR, VTCR_BUSY |
+ VTCR_FUNC_VID_WRITE | (vid & VTCR_VID_MASK));
+ while ((val = sc->hal.mtkswitch_read(sc, MTKSWITCH_VTCR)) & VTCR_BUSY);
+
+ MTKSWITCH_UNLOCK(sc);
+
+ if (val & VTCR_IDX_INVALID)
+ return (EINVAL);
+
+ return (0);
+}
+
+static int
+mtkswitch_vlan_get_pvid(struct mtkswitch_softc *sc, int port, int *pvid)
+{
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ *pvid = sc->hal.mtkswitch_read(sc, MTKSWITCH_PPBV1(port));
+ *pvid = PPBV_VID_FROM_REG(*pvid);
+
+ return (0);
+}
+
+static int
+mtkswitch_vlan_set_pvid(struct mtkswitch_softc *sc, int port, int pvid)
+{
+ uint32_t val;
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ val = PPBV_VID(pvid & PPBV_VID_MASK);
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_PPBV1(port), val);
+ sc->hal.mtkswitch_write(sc, MTKSWITCH_PPBV2(port), val);
+
+ return (0);
+}
+
+extern void
+mtk_attach_switch_mt7620(struct mtkswitch_softc *sc)
+{
+
+ sc->portmap = 0x7f;
+ sc->phymap = 0x1f;
+
+ sc->info.es_nports = 7;
+ sc->info.es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q;
+ sc->info.es_nvlangroups = 16;
+ sprintf(sc->info.es_name, "Mediatek GSW");
+
+ if (sc->sc_switchtype == MTK_SWITCH_MT7621) {
+ sc->hal.mtkswitch_read = mtkswitch_reg_read32_mt7621;
+ sc->hal.mtkswitch_write = mtkswitch_reg_write32_mt7621;
+ sc->info.es_nvlangroups = 4096;
+ } else {
+ sc->hal.mtkswitch_read = mtkswitch_reg_read32;
+ sc->hal.mtkswitch_write = mtkswitch_reg_write32;
+ }
+
+ sc->hal.mtkswitch_reset = mtkswitch_reset;
+ sc->hal.mtkswitch_hw_setup = mtkswitch_hw_setup;
+ sc->hal.mtkswitch_hw_global_setup = mtkswitch_hw_global_setup;
+ sc->hal.mtkswitch_port_init = mtkswitch_port_init;
+ sc->hal.mtkswitch_get_port_status = mtkswitch_get_port_status;
+ sc->hal.mtkswitch_atu_flush = mtkswitch_atu_flush;
+ sc->hal.mtkswitch_port_vlan_setup = mtkswitch_port_vlan_setup;
+ sc->hal.mtkswitch_port_vlan_get = mtkswitch_port_vlan_get;
+ sc->hal.mtkswitch_vlan_init_hw = mtkswitch_vlan_init_hw;
+ sc->hal.mtkswitch_vlan_getvgroup = mtkswitch_vlan_getvgroup;
+ sc->hal.mtkswitch_vlan_setvgroup = mtkswitch_vlan_setvgroup;
+ sc->hal.mtkswitch_vlan_get_pvid = mtkswitch_vlan_get_pvid;
+ sc->hal.mtkswitch_vlan_set_pvid = mtkswitch_vlan_set_pvid;
+ sc->hal.mtkswitch_phy_read = mtkswitch_phy_read;
+ sc->hal.mtkswitch_phy_write = mtkswitch_phy_write;
+ sc->hal.mtkswitch_reg_read = mtkswitch_reg_read;
+ sc->hal.mtkswitch_reg_write = mtkswitch_reg_write;
+}
diff --git a/sys/dev/etherswitch/mtkswitch/mtkswitch_mt7620.h b/sys/dev/etherswitch/mtkswitch/mtkswitch_mt7620.h
new file mode 100644
index 000000000000..a7575938a2ed
--- /dev/null
+++ b/sys/dev/etherswitch/mtkswitch/mtkswitch_mt7620.h
@@ -0,0 +1,128 @@
+/*-
+ * Copyright (c) 2016 Stanislav Galabov.
+ * All rights reserved.
+ *
+ * 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 __MTKSWITCH_MT7620_H__
+#define __MTKSWITCH_MT7620_H__
+
+#define MTKSWITCH_ATC 0x0080
+#define ATC_BUSY (1u<<15)
+#define ATC_AC_MAT_NON_STATIC_MACS (4u<<8)
+#define ATC_AC_CMD_CLEAN (2u<<0)
+
+#define MTKSWITCH_VTCR 0x0090
+#define VTCR_BUSY (1u<<31)
+#define VTCR_FUNC_VID_READ (0u<<12)
+#define VTCR_FUNC_VID_WRITE (1u<<12)
+#define VTCR_FUNC_VID_INVALID (2u<<12)
+#define VTCR_FUNC_VID_VALID (3u<<12)
+#define VTCR_IDX_INVALID (1u<<16)
+#define VTCR_VID_MASK 0xfff
+
+#define MTKSWITCH_VAWD1 0x0094
+#define VAWD1_IVL_MAC (1u<<30)
+#define VAWD1_VTAG_EN (1u<<28)
+#define VAWD1_PORT_MEMBER(p) ((1u<<16)<<(p))
+#define VAWD1_MEMBER_OFF 16
+#define VAWD1_MEMBER_MASK 0xff
+#define VAWD1_FID_OFFSET 1
+#define VAWD1_VALID (1u<<0)
+
+#define MTKSWITCH_VAWD2 0x0098
+#define VAWD2_PORT_UNTAGGED(p) (0u<<((p)*2))
+#define VAWD2_PORT_TAGGED(p) (2u<<((p)*2))
+#define VAWD2_PORT_MASK(p) (3u<<((p)*2))
+
+#define MTKSWITCH_VTIM(v) ((((v) >> 1) * 4) + 0x100)
+#define VTIM_OFF(v) (((v) & 1) ? 12 : 0)
+#define VTIM_MASK 0xfff
+
+#define MTKSWITCH_PIAC 0x7004
+#define PIAC_PHY_ACS_ST (1u<<31)
+#define PIAC_MDIO_REG_ADDR_OFF 25
+#define PIAC_MDIO_PHY_ADDR_OFF 20
+#define PIAC_MDIO_CMD_WRITE (1u<<18)
+#define PIAC_MDIO_CMD_READ (2u<<18)
+#define PIAC_MDIO_ST (1u<<16)
+#define PIAC_MDIO_RW_DATA_MASK 0xffff
+
+#define MTKSWITCH_PORTREG(r, p) ((r) + ((p) * 0x100))
+
+#define MTKSWITCH_PCR(x) MTKSWITCH_PORTREG(0x2004, (x))
+#define PCR_PORT_VLAN_SECURE (3u<<0)
+
+#define MTKSWITCH_PVC(x) MTKSWITCH_PORTREG(0x2010, (x))
+#define PVC_VLAN_ATTR_MASK (3u<<6)
+
+#define MTKSWITCH_PPBV1(x) MTKSWITCH_PORTREG(0x2014, (x))
+#define MTKSWITCH_PPBV2(x) MTKSWITCH_PORTREG(0x2018, (x))
+#define PPBV_VID(v) (((v)<<16) | (v))
+#define PPBV_VID_FROM_REG(x) ((x) & 0xfff)
+#define PPBV_VID_MASK 0xfff
+
+#define MTKSWITCH_PMCR(x) MTKSWITCH_PORTREG(0x3000, (x))
+#define PMCR_FORCE_LINK (1u<<0)
+#define PMCR_FORCE_DPX (1u<<1)
+#define PMCR_FORCE_SPD_1000 (2u<<2)
+#define PMCR_FORCE_TX_FC (1u<<4)
+#define PMCR_FORCE_RX_FC (1u<<5)
+#define PMCR_BACKPR_EN (1u<<8)
+#define PMCR_BKOFF_EN (1u<<9)
+#define PMCR_MAC_RX_EN (1u<<13)
+#define PMCR_MAC_TX_EN (1u<<14)
+#define PMCR_FORCE_MODE (1u<<15)
+#define PMCR_RES_1 (1u<<16)
+#define PMCR_IPG_CFG_RND (1u<<18)
+#define PMCR_CFG_DEFAULT (PMCR_BACKPR_EN | PMCR_BKOFF_EN | \
+ PMCR_MAC_RX_EN | PMCR_MAC_TX_EN | PMCR_IPG_CFG_RND | \
+ PMCR_FORCE_RX_FC | PMCR_FORCE_TX_FC | PMCR_RES_1)
+
+#define MTKSWITCH_PMSR(x) MTKSWITCH_PORTREG(0x3008, (x))
+#define PMSR_MAC_LINK_STS (1u<<0)
+#define PMSR_MAC_DPX_STS (1u<<1)
+#define PMSR_MAC_SPD_STS (3u<<2)
+#define PMSR_MAC_SPD(x) (((x)>>2) & 0x3)
+#define PMSR_MAC_SPD_10 0
+#define PMSR_MAC_SPD_100 1
+#define PMSR_MAC_SPD_1000 2
+#define PMSR_TX_FC_STS (1u<<4)
+#define PMSR_RX_FC_STS (1u<<5)
+
+#define MTKSWITCH_REG_ADDR(r) (((r) >> 6) & 0x3ff)
+#define MTKSWITCH_REG_LO(r) (((r) >> 2) & 0xf)
+#define MTKSWITCH_REG_HI(r) (1 << 4)
+#define MTKSWITCH_VAL_LO(v) ((v) & 0xffff)
+#define MTKSWITCH_VAL_HI(v) (((v) >> 16) & 0xffff)
+#define MTKSWITCH_GLOBAL_PHY 31
+#define MTKSWITCH_GLOBAL_REG 31
+
+#define MTKSWITCH_LAN_VID 0x001
+#define MTKSWITCH_WAN_VID 0x002
+#define MTKSWITCH_INVALID_VID 0xfff
+
+#define MTKSWITCH_LAN_FID 1
+#define MTKSWITCH_WAN_FID 2
+
+#endif /* __MTKSWITCH_MT7620_H__ */
diff --git a/sys/dev/etherswitch/mtkswitch/mtkswitch_rt3050.c b/sys/dev/etherswitch/mtkswitch/mtkswitch_rt3050.c
new file mode 100644
index 000000000000..4cf54bc9f729
--- /dev/null
+++ b/sys/dev/etherswitch/mtkswitch/mtkswitch_rt3050.c
@@ -0,0 +1,523 @@
+/*-
+ * Copyright (c) 2016 Stanislav Galabov.
+ * All rights reserved.
+ *
+ * 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/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/rman.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/ethernet.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+
+#include <dev/etherswitch/etherswitch.h>
+#include <dev/etherswitch/mtkswitch/mtkswitchvar.h>
+#include <dev/etherswitch/mtkswitch/mtkswitch_rt3050.h>
+
+static int
+mtkswitch_reg_read(device_t dev, int reg)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+ uint32_t val;
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ val = MTKSWITCH_READ(sc, MTKSWITCH_REG32(reg));
+ if (MTKSWITCH_IS_HI16(reg))
+ return (MTKSWITCH_HI16(val));
+ return (MTKSWITCH_LO16(val));
+}
+
+static int
+mtkswitch_reg_write(device_t dev, int reg, int val)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+ uint32_t tmp;
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ tmp = MTKSWITCH_READ(sc, MTKSWITCH_REG32(reg));
+ if (MTKSWITCH_IS_HI16(reg)) {
+ tmp &= MTKSWITCH_LO16_MSK;
+ tmp |= MTKSWITCH_TO_HI16(val);
+ } else {
+ tmp &= MTKSWITCH_HI16_MSK;
+ tmp |= MTKSWITCH_TO_LO16(val);
+ }
+ MTKSWITCH_WRITE(sc, MTKSWITCH_REG32(reg), tmp);
+
+ return (0);
+}
+
+static int
+mtkswitch_phy_read(device_t dev, int phy, int reg)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+ int val;
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+ MTKSWITCH_LOCK(sc);
+ while (MTKSWITCH_READ(sc, MTKSWITCH_PCR0) & PCR0_ACTIVE);
+ MTKSWITCH_WRITE(sc, MTKSWITCH_PCR0, PCR0_READ | PCR0_REG(reg) |
+ PCR0_PHY(phy));
+ while (MTKSWITCH_READ(sc, MTKSWITCH_PCR0) & PCR0_ACTIVE);
+ val = (MTKSWITCH_READ(sc, MTKSWITCH_PCR1) >> PCR1_DATA_OFF) &
+ PCR1_DATA_MASK;
+ MTKSWITCH_UNLOCK(sc);
+ return (val);
+}
+
+static int
+mtkswitch_phy_write(device_t dev, int phy, int reg, int val)
+{
+ struct mtkswitch_softc *sc = device_get_softc(dev);
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+ MTKSWITCH_LOCK(sc);
+ while (MTKSWITCH_READ(sc, MTKSWITCH_PCR0) & PCR0_ACTIVE);
+ MTKSWITCH_WRITE(sc, MTKSWITCH_PCR0, PCR0_WRITE | PCR0_REG(reg) |
+ PCR0_PHY(phy) | PCR0_DATA(val));
+ while (MTKSWITCH_READ(sc, MTKSWITCH_PCR0) & PCR0_ACTIVE);
+ MTKSWITCH_UNLOCK(sc);
+ return (0);
+}
+
+static int
+mtkswitch_reset(struct mtkswitch_softc *sc)
+{
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+ MTKSWITCH_LOCK(sc);
+ MTKSWITCH_WRITE(sc, MTKSWITCH_STRT, STRT_RESET);
+ while (MTKSWITCH_READ(sc, MTKSWITCH_STRT) != 0);
+ MTKSWITCH_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+mtkswitch_hw_setup(struct mtkswitch_softc *sc)
+{
+
+ /*
+ * TODO: parse the device tree and see if we need to configure
+ * ports, etc. differently. For now we fallback to defaults.
+ */
+
+ /* Called early and hence unlocked */
+ /* Set ports 0-4 to auto negotiation */
+ MTKSWITCH_WRITE(sc, MTKSWITCH_FPA, FPA_ALL_AUTO);
+
+ return (0);
+}
+
+static int
+mtkswitch_hw_global_setup(struct mtkswitch_softc *sc)
+{
+
+ /* Called early and hence unlocked */
+ return (0);
+}
+
+static void
+mtkswitch_port_init(struct mtkswitch_softc *sc, int port)
+{
+ /* Called early and hence unlocked */
+ /* Do nothing - ports are set to auto negotiation in hw_setup */
+}
+
+static uint32_t
+mtkswitch_get_port_status(struct mtkswitch_softc *sc, int port)
+{
+ uint32_t val, res;
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ res = 0;
+ val = MTKSWITCH_READ(sc, MTKSWITCH_POA);
+
+ if (val & POA_PRT_LINK(port))
+ res |= MTKSWITCH_LINK_UP;
+ if (val & POA_PRT_DPX(port))
+ res |= MTKSWITCH_DUPLEX;
+
+ if (MTKSWITCH_PORT_IS_100M(port)) {
+ if (val & POA_FE_SPEED(port))
+ res |= MTKSWITCH_SPEED_100;
+ if (val & POA_FE_XFC(port))
+ res |= (MTKSWITCH_TXFLOW | MTKSWITCH_RXFLOW);
+ } else {
+ switch (POA_GE_SPEED(val, port)) {
+ case POA_GE_SPEED_10:
+ res |= MTKSWITCH_SPEED_10;
+ break;
+ case POA_GE_SPEED_100:
+ res |= MTKSWITCH_SPEED_100;
+ break;
+ case POA_GE_SPEED_1000:
+ res |= MTKSWITCH_SPEED_1000;
+ break;
+ }
+
+ val = POA_GE_XFC(val, port);
+ if (val & POA_GE_XFC_TX_MSK)
+ res |= MTKSWITCH_TXFLOW;
+ if (val & POA_GE_XFC_RX_MSK)
+ res |= MTKSWITCH_RXFLOW;
+ }
+
+ return (res);
+}
+
+static int
+mtkswitch_atu_flush(struct mtkswitch_softc *sc)
+{
+ return (0);
+}
+
+static int
+mtkswitch_port_vlan_setup(struct mtkswitch_softc *sc, etherswitch_port_t *p)
+{
+ uint32_t val;
+ int err, invert = 0;
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+ MTKSWITCH_LOCK(sc);
+ /* Set the PVID. */
+ if (p->es_pvid != 0) {
+ err = sc->hal.mtkswitch_vlan_set_pvid(sc, p->es_port,
+ p->es_pvid);
+ if (err != 0) {
+ MTKSWITCH_UNLOCK(sc);
+ return (err);
+ }
+ }
+
+ /* Mutually exclusive */
+ if (p->es_flags & ETHERSWITCH_PORT_ADDTAG &&
+ p->es_flags & ETHERSWITCH_PORT_STRIPTAG) {
+ invert = 1;
+ }
+
+ val = MTKSWITCH_READ(sc, MTKSWITCH_SGC2);
+ if (p->es_flags & ETHERSWITCH_PORT_DOUBLE_TAG)
+ val |= SGC2_DOUBLE_TAG_PORT(p->es_port);
+ else
+ val &= ~SGC2_DOUBLE_TAG_PORT(p->es_port);
+ MTKSWITCH_WRITE(sc, MTKSWITCH_SGC2, val);
+
+ val = MTKSWITCH_READ(sc, MTKSWITCH_POC2);
+ if (invert) {
+ if (val & POC2_UNTAG_PORT(p->es_port))
+ val &= ~POC2_UNTAG_PORT(p->es_port);
+ else
+ val |= POC2_UNTAG_PORT(p->es_port);
+ } else if (p->es_flags & ETHERSWITCH_PORT_STRIPTAG)
+ val |= POC2_UNTAG_PORT(p->es_port);
+ else
+ val &= ~POC2_UNTAG_PORT(p->es_port);
+ MTKSWITCH_WRITE(sc, MTKSWITCH_POC2, val);
+ MTKSWITCH_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+mtkswitch_port_vlan_get(struct mtkswitch_softc *sc, etherswitch_port_t *p)
+{
+ uint32_t val;
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+ MTKSWITCH_LOCK(sc);
+
+ /* Retrieve the PVID */
+ sc->hal.mtkswitch_vlan_get_pvid(sc, p->es_port, &p->es_pvid);
+
+ /* Port flags */
+ p->es_flags = 0;
+ val = MTKSWITCH_READ(sc, MTKSWITCH_SGC2);
+ if (val & SGC2_DOUBLE_TAG_PORT(p->es_port))
+ p->es_flags |= ETHERSWITCH_PORT_DOUBLE_TAG;
+
+ val = MTKSWITCH_READ(sc, MTKSWITCH_POC2);
+ if (val & POC2_UNTAG_PORT(p->es_port))
+ p->es_flags |= ETHERSWITCH_PORT_STRIPTAG;
+ else
+ p->es_flags |= ETHERSWITCH_PORT_ADDTAG;
+
+ MTKSWITCH_UNLOCK(sc);
+
+ return (0);
+}
+
+static void
+mtkswitch_vlan_init_hw(struct mtkswitch_softc *sc)
+{
+ uint32_t val, vid;
+ int i;
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+ MTKSWITCH_LOCK(sc);
+
+ /* Reset everything to defaults first */
+ for (i = 0; i < sc->info.es_nvlangroups; i++) {
+ /* Remove all VLAN members and untag info, if any */
+ if (i % 4 == 0) {
+ MTKSWITCH_WRITE(sc, MTKSWITCH_VMSC(i), 0);
+ if (sc->sc_switchtype != MTK_SWITCH_RT3050)
+ MTKSWITCH_WRITE(sc, MTKSWITCH_VUB(i), 0);
+ }
+ /* Reset to default VIDs */
+ val = MTKSWITCH_READ(sc, MTKSWITCH_VLANI(i));
+ val &= ~(VLANI_MASK << VLANI_OFF(i));
+ val |= ((i + 1) << VLANI_OFF(i));
+ MTKSWITCH_WRITE(sc, MTKSWITCH_VLANI(i), val);
+ }
+
+ /* Now, add all ports as untagged members to VLAN1 */
+ vid = 0;
+ val = MTKSWITCH_READ(sc, MTKSWITCH_VMSC(vid));
+ val &= ~(VMSC_MASK << VMSC_OFF(vid));
+ val |= (((1<<sc->numports)-1) << VMSC_OFF(vid));
+ MTKSWITCH_WRITE(sc, MTKSWITCH_VMSC(vid), val);
+ if (sc->sc_switchtype != MTK_SWITCH_RT3050) {
+ val = MTKSWITCH_READ(sc, MTKSWITCH_VUB(vid));
+ val &= ~(VUB_MASK << VUB_OFF(vid));
+ val |= (((1<<sc->numports)-1) << VUB_OFF(vid));
+ MTKSWITCH_WRITE(sc, MTKSWITCH_VUB(vid), val);
+ }
+ val = MTKSWITCH_READ(sc, MTKSWITCH_POC2);
+ if (sc->sc_switchtype != MTK_SWITCH_RT3050)
+ val |= POC2_UNTAG_VLAN;
+ val |= ((1<<sc->numports)-1);
+ MTKSWITCH_WRITE(sc, MTKSWITCH_POC2, val);
+
+ /* only the first vlangroup is valid */
+ sc->valid_vlans = (1<<0);
+
+ /* Set all port PVIDs to 1 */
+ vid = 1;
+ for (i = 0; i < sc->info.es_nports; i++) {
+ val = MTKSWITCH_READ(sc, MTKSWITCH_PVID(i));
+ val &= ~(PVID_MASK << PVID_OFF(i));
+ val |= (vid << PVID_OFF(i));
+ MTKSWITCH_WRITE(sc, MTKSWITCH_PVID(i), val);
+ }
+
+ MTKSWITCH_UNLOCK(sc);
+}
+
+static int
+mtkswitch_vlan_getvgroup(struct mtkswitch_softc *sc, etherswitch_vlangroup_t *v)
+{
+ uint32_t val;
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ if ((sc->vlan_mode != ETHERSWITCH_VLAN_DOT1Q) ||
+ (v->es_vlangroup > sc->info.es_nvlangroups))
+ return (EINVAL);
+
+ /* Reset the member ports. */
+ v->es_untagged_ports = 0;
+ v->es_member_ports = 0;
+
+ /* Not supported */
+ v->es_fid = 0;
+
+ /* Vlan ID */
+ v->es_vid = 0;
+ if ((sc->valid_vlans & (1<<v->es_vlangroup)) == 0)
+ return (0);
+
+ MTKSWITCH_LOCK(sc);
+ v->es_vid = (MTKSWITCH_READ(sc, MTKSWITCH_VLANI(v->es_vlangroup)) >>
+ VLANI_OFF(v->es_vlangroup)) & VLANI_MASK;
+ v->es_vid |= ETHERSWITCH_VID_VALID;
+
+ /* Member ports */
+ v->es_member_ports = v->es_untagged_ports =
+ (MTKSWITCH_READ(sc, MTKSWITCH_VMSC(v->es_vlangroup)) >>
+ VMSC_OFF(v->es_vlangroup)) & VMSC_MASK;
+
+ val = MTKSWITCH_READ(sc, MTKSWITCH_POC2);
+
+ if ((val & POC2_UNTAG_VLAN) && sc->sc_switchtype != MTK_SWITCH_RT3050) {
+ val = (MTKSWITCH_READ(sc, MTKSWITCH_VUB(v->es_vlangroup)) >>
+ VUB_OFF(v->es_vlangroup)) & VUB_MASK;
+ } else {
+ val &= VUB_MASK;
+ }
+ v->es_untagged_ports &= val;
+
+ MTKSWITCH_UNLOCK(sc);
+ return (0);
+}
+
+static int
+mtkswitch_vlan_setvgroup(struct mtkswitch_softc *sc, etherswitch_vlangroup_t *v)
+{
+ uint32_t val, tmp;
+
+ if ((sc->vlan_mode != ETHERSWITCH_VLAN_DOT1Q) ||
+ (v->es_vlangroup > sc->info.es_nvlangroups))
+ return (EINVAL);
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+ MTKSWITCH_LOCK(sc);
+ /* First, see if we can accommodate the request at all */
+ val = MTKSWITCH_READ(sc, MTKSWITCH_POC2);
+ if (sc->sc_switchtype == MTK_SWITCH_RT3050 ||
+ (val & POC2_UNTAG_VLAN) == 0) {
+ /*
+ * There are 2 things we can't support in per-port untagging
+ * mode:
+ * 1. Adding a port as an untagged member if the port is not
+ * set up to do untagging.
+ * 2. Adding a port as a tagged member if the port is set up
+ * to do untagging.
+ */
+ val &= VUB_MASK;
+
+ /* get all untagged members from the member list */
+ tmp = v->es_untagged_ports & v->es_member_ports;
+ /* fail if untagged members are not a subset of all members */
+ if (tmp != v->es_untagged_ports) {
+ /* Cannot accommodate request */
+ MTKSWITCH_UNLOCK(sc);
+ return (ENOTSUP);
+ }
+
+ /* fail if any untagged member is set up to do tagging */
+ if ((tmp & val) != tmp) {
+ /* Cannot accommodate request */
+ MTKSWITCH_UNLOCK(sc);
+ return (ENOTSUP);
+ }
+
+ /* now, get the list of all tagged members */
+ tmp = v->es_member_ports & ~tmp;
+ /* fail if any tagged member is set up to do untagging */
+ if ((tmp & val) != 0) {
+ /* Cannot accommodate request */
+ MTKSWITCH_UNLOCK(sc);
+ return (ENOTSUP);
+ }
+ } else {
+ /* Prefer per-Vlan untag and set its members */
+ val = MTKSWITCH_READ(sc, MTKSWITCH_VUB(v->es_vlangroup));
+ val &= ~(VUB_MASK << VUB_OFF(v->es_vlangroup));
+ val |= (((v->es_untagged_ports) & VUB_MASK) <<
+ VUB_OFF(v->es_vlangroup));
+ MTKSWITCH_WRITE(sc, MTKSWITCH_VUB(v->es_vlangroup), val);
+ }
+
+ /* Set VID */
+ val = MTKSWITCH_READ(sc, MTKSWITCH_VLANI(v->es_vlangroup));
+ val &= ~(VLANI_MASK << VLANI_OFF(v->es_vlangroup));
+ val |= (v->es_vid & VLANI_MASK) << VLANI_OFF(v->es_vlangroup);
+ MTKSWITCH_WRITE(sc, MTKSWITCH_VLANI(v->es_vlangroup), val);
+
+ /* Set members */
+ val = MTKSWITCH_READ(sc, MTKSWITCH_VMSC(v->es_vlangroup));
+ val &= ~(VMSC_MASK << VMSC_OFF(v->es_vlangroup));
+ val |= (v->es_member_ports << VMSC_OFF(v->es_vlangroup));
+ MTKSWITCH_WRITE(sc, MTKSWITCH_VMSC(v->es_vlangroup), val);
+
+ sc->valid_vlans |= (1<<v->es_vlangroup);
+
+ MTKSWITCH_UNLOCK(sc);
+ return (0);
+}
+
+static int
+mtkswitch_vlan_get_pvid(struct mtkswitch_softc *sc, int port, int *pvid)
+{
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ *pvid = (MTKSWITCH_READ(sc, MTKSWITCH_PVID(port)) >> PVID_OFF(port)) &
+ PVID_MASK;
+
+ return (0);
+}
+
+static int
+mtkswitch_vlan_set_pvid(struct mtkswitch_softc *sc, int port, int pvid)
+{
+ uint32_t val;
+
+ MTKSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ val = MTKSWITCH_READ(sc, MTKSWITCH_PVID(port));
+ val &= ~(PVID_MASK << PVID_OFF(port));
+ val |= (pvid & PVID_MASK) << PVID_OFF(port);
+ MTKSWITCH_WRITE(sc, MTKSWITCH_PVID(port), val);
+
+ return (0);
+}
+
+extern void
+mtk_attach_switch_rt3050(struct mtkswitch_softc *sc)
+{
+
+ sc->portmap = 0x7f;
+ sc->phymap = 0x1f;
+
+ sc->info.es_nports = 7;
+ sc->info.es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q;
+ sc->info.es_nvlangroups = 16;
+ sprintf(sc->info.es_name, "Ralink ESW");
+
+ sc->hal.mtkswitch_reset = mtkswitch_reset;
+ sc->hal.mtkswitch_hw_setup = mtkswitch_hw_setup;
+ sc->hal.mtkswitch_hw_global_setup = mtkswitch_hw_global_setup;
+ sc->hal.mtkswitch_port_init = mtkswitch_port_init;
+ sc->hal.mtkswitch_get_port_status = mtkswitch_get_port_status;
+ sc->hal.mtkswitch_atu_flush = mtkswitch_atu_flush;
+ sc->hal.mtkswitch_port_vlan_setup = mtkswitch_port_vlan_setup;
+ sc->hal.mtkswitch_port_vlan_get = mtkswitch_port_vlan_get;
+ sc->hal.mtkswitch_vlan_init_hw = mtkswitch_vlan_init_hw;
+ sc->hal.mtkswitch_vlan_getvgroup = mtkswitch_vlan_getvgroup;
+ sc->hal.mtkswitch_vlan_setvgroup = mtkswitch_vlan_setvgroup;
+ sc->hal.mtkswitch_vlan_get_pvid = mtkswitch_vlan_get_pvid;
+ sc->hal.mtkswitch_vlan_set_pvid = mtkswitch_vlan_set_pvid;
+ sc->hal.mtkswitch_phy_read = mtkswitch_phy_read;
+ sc->hal.mtkswitch_phy_write = mtkswitch_phy_write;
+ sc->hal.mtkswitch_reg_read = mtkswitch_reg_read;
+ sc->hal.mtkswitch_reg_write = mtkswitch_reg_write;
+}
diff --git a/sys/dev/etherswitch/mtkswitch/mtkswitch_rt3050.h b/sys/dev/etherswitch/mtkswitch/mtkswitch_rt3050.h
new file mode 100644
index 000000000000..8c74512bb65b
--- /dev/null
+++ b/sys/dev/etherswitch/mtkswitch/mtkswitch_rt3050.h
@@ -0,0 +1,86 @@
+/*-
+ * Copyright (c) 2016 Stanislav Galabov.
+ * All rights reserved.
+ *
+ * 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 __MTKSWITCH_RT3050_H__
+#define __MTKSWITCH_RT3050_H__
+
+#define MTKSWITCH_PVID(p) ((((p) >> 1) * 4) + 0x40)
+#define PVID_OFF(p) (((p) & 1) ? 12 : 0)
+#define PVID_MASK 0xfff
+
+#define MTKSWITCH_VLANI(v) ((((v) >> 1) * 4) + 0x50)
+#define VLANI_OFF(v) (((v) & 1) ? 12 : 0)
+#define VLANI_MASK 0xfff
+
+#define MTKSWITCH_VMSC(x) ((((x) >> 2) * 4) + 0x70)
+#define VMSC_OFF(x) ((x & 3) * 8)
+#define VMSC_MASK 0xff
+
+#define MTKSWITCH_POA 0x0080
+#define POA_PRT_DPX(x) ((1<<9)<<(x))
+#define POA_FE_SPEED(x) ((1<<0)<<(x))
+#define POA_GE_SPEED(v, x) ((((v)>>5)>>(((x)-5)*2)) & 0x3)
+#define POA_FE_XFC(x) ((1<<16)<<(x))
+#define POA_GE_XFC(v, x) ((((v)>>21)>>(((x)-5)*2)) & 0x3)
+#define POA_PRT_LINK(x) ((1<<25)<<(x))
+#define POA_GE_XFC_TX_MSK 0x2
+#define POA_GE_XFC_RX_MSK 0x1
+#define POA_GE_SPEED_10 0x0
+#define POA_GE_SPEED_100 0x1
+#define POA_GE_SPEED_1000 0x2
+
+#define MTKSWITCH_FPA 0x0084
+#define FPA_ALL_AUTO 0x00000000
+
+#define MTKSWITCH_POC2 0x0098
+#define POC2_UNTAG_PORT(x) (1 << (x))
+#define POC2_UNTAG_VLAN (1 << 15)
+
+#define MTKSWITCH_STRT 0x00a0
+#define STRT_RESET 0xffffffff
+
+#define MTKSWITCH_PCR0 0x00c0
+#define PCR0_WRITE (1<<13)
+#define PCR0_READ (1<<14)
+#define PCR0_ACTIVE (PCR0_WRITE | PCR0_READ)
+#define PCR0_REG(x) (((x) & 0x1f) << 8)
+#define PCR0_PHY(x) ((x) & 0x1f)
+#define PCR0_DATA(x) (((x) & 0xffff) << 16)
+
+#define MTKSWITCH_PCR1 0x00c4
+#define PCR1_DATA_OFF 16
+#define PCR1_DATA_MASK 0xffff
+
+#define MTKSWITCH_SGC2 0x00e4
+#define SGC2_DOUBLE_TAG_PORT(x) (1 << (x))
+
+#define MTKSWITCH_VUB(x) ((((x) >> 2) * 4) + 0x100)
+#define VUB_OFF(x) ((x & 3) * 7)
+#define VUB_MASK 0x7f
+
+#define MTKSWITCH_PORT_IS_100M(x) ((x) < 5)
+
+#endif /* __MTKSWITCH_RT3050_H__ */
diff --git a/sys/dev/etherswitch/mtkswitch/mtkswitchvar.h b/sys/dev/etherswitch/mtkswitch/mtkswitchvar.h
new file mode 100644
index 000000000000..45e729e4ea7d
--- /dev/null
+++ b/sys/dev/etherswitch/mtkswitch/mtkswitchvar.h
@@ -0,0 +1,162 @@
+/*-
+ * Copyright (c) 2016 Stanislav Galabov.
+ * All rights reserved.
+ *
+ * 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 __MTKSWITCHVAR_H__
+#define __MTKSWITCHVAR_H__
+
+typedef enum {
+ MTK_SWITCH_NONE,
+ MTK_SWITCH_RT3050,
+ MTK_SWITCH_RT3352,
+ MTK_SWITCH_RT5350,
+ MTK_SWITCH_MT7620,
+ MTK_SWITCH_MT7621,
+ MTK_SWITCH_MT7628,
+} mtk_switch_type;
+
+#define MTK_IS_SWITCH(_sc, _type) \
+ (!!((_sc)->sc_switchtype == MTK_SWITCH_ ## _type))
+
+#define MTKSWITCH_MAX_PORTS 7
+#define MTKSWITCH_MAX_PHYS 7
+#define MTKSWITCH_CPU_PORT 6
+
+#define MTKSWITCH_LINK_UP (1<<0)
+#define MTKSWITCH_SPEED_MASK (3<<1)
+#define MTKSWITCH_SPEED_10 (0<<1)
+#define MTKSWITCH_SPEED_100 (1<<1)
+#define MTKSWITCH_SPEED_1000 (2<<1)
+#define MTKSWITCH_DUPLEX (1<<3)
+#define MTKSWITCH_TXFLOW (1<<4)
+#define MTKSWITCH_RXFLOW (1<<5)
+
+struct mtkswitch_softc {
+ struct mtx sc_mtx;
+ device_t sc_dev;
+ struct resource *sc_res;
+ int numphys;
+ uint32_t phymap;
+ int numports;
+ uint32_t portmap;
+ int cpuport;
+ uint32_t valid_vlans;
+ mtk_switch_type sc_switchtype;
+ char *ifname[MTKSWITCH_MAX_PHYS];
+ device_t miibus[MTKSWITCH_MAX_PHYS];
+ if_t ifp[MTKSWITCH_MAX_PHYS];
+ struct callout callout_tick;
+ etherswitch_info_t info;
+
+ uint32_t vlan_mode;
+
+ struct {
+ /* Global setup */
+ int (* mtkswitch_reset) (struct mtkswitch_softc *);
+ int (* mtkswitch_hw_setup) (struct mtkswitch_softc *);
+ int (* mtkswitch_hw_global_setup) (struct mtkswitch_softc *);
+
+ /* Port functions */
+ void (* mtkswitch_port_init) (struct mtkswitch_softc *, int);
+ uint32_t (* mtkswitch_get_port_status)
+ (struct mtkswitch_softc *, int);
+
+ /* ATU functions */
+ int (* mtkswitch_atu_flush) (struct mtkswitch_softc *);
+
+ /* VLAN functions */
+ int (* mtkswitch_port_vlan_setup) (struct mtkswitch_softc *,
+ etherswitch_port_t *);
+ int (* mtkswitch_port_vlan_get) (struct mtkswitch_softc *,
+ etherswitch_port_t *);
+ void (* mtkswitch_vlan_init_hw) (struct mtkswitch_softc *);
+ int (* mtkswitch_vlan_getvgroup) (struct mtkswitch_softc *,
+ etherswitch_vlangroup_t *);
+ int (* mtkswitch_vlan_setvgroup) (struct mtkswitch_softc *,
+ etherswitch_vlangroup_t *);
+ int (* mtkswitch_vlan_get_pvid) (struct mtkswitch_softc *,
+ int, int *);
+ int (* mtkswitch_vlan_set_pvid) (struct mtkswitch_softc *,
+ int, int);
+
+ /* PHY functions */
+ int (* mtkswitch_phy_read) (device_t, int, int);
+ int (* mtkswitch_phy_write) (device_t, int, int, int);
+
+ /* Register functions */
+ int (* mtkswitch_reg_read) (device_t, int);
+ int (* mtkswitch_reg_write) (device_t, int, int);
+
+ /* Internal register access functions */
+ uint32_t (* mtkswitch_read) (struct mtkswitch_softc *, int);
+ uint32_t (* mtkswitch_write) (struct mtkswitch_softc *, int,
+ uint32_t);
+ } hal;
+};
+
+#define MTKSWITCH_LOCK(_sc) \
+ mtx_lock(&(_sc)->sc_mtx)
+#define MTKSWITCH_UNLOCK(_sc) \
+ mtx_unlock(&(_sc)->sc_mtx)
+#define MTKSWITCH_LOCK_ASSERT(_sc, _what) \
+ mtx_assert(&(_sc)->sc_mtx, (_what))
+#define MTKSWITCH_TRYLOCK(_sc) \
+ mtx_trylock(&(_sc)->sc_mtx)
+
+#define MTKSWITCH_READ(_sc, _reg) \
+ bus_read_4((_sc)->sc_res, (_reg))
+#define MTKSWITCH_WRITE(_sc, _reg, _val) \
+ bus_write_4((_sc)->sc_res, (_reg), (_val))
+#define MTKSWITCH_MOD(_sc, _reg, _clr, _set) \
+ MTKSWITCH_WRITE((_sc), (_reg), \
+ ((MTKSWITCH_READ((_sc), (_reg)) & ~(_clr)) | (_set))
+
+#define MTKSWITCH_REG32(addr) ((addr) & ~(0x3))
+#define MTKSWITCH_IS_HI16(addr) (((addr) & 0x3) > 0x1)
+#define MTKSWITCH_HI16(x) (((x) >> 16) & 0xffff)
+#define MTKSWITCH_LO16(x) ((x) & 0xffff)
+#define MTKSWITCH_TO_HI16(x) (((x) & 0xffff) << 16)
+#define MTKSWITCH_TO_LO16(x) ((x) & 0xffff)
+#define MTKSWITCH_HI16_MSK 0xffff0000
+#define MTKSWITCH_LO16_MSK 0x0000ffff
+
+#if defined(DEBUG)
+#define DPRINTF(dev, args...) device_printf(dev, args)
+#define DEVERR(dev, err, fmt, args...) do { \
+ if (err != 0) device_printf(dev, fmt, err, args); \
+ } while (0)
+#define DEBUG_INCRVAR(var) do { \
+ var++; \
+ } while (0)
+#else
+#define DPRINTF(dev, args...)
+#define DEVERR(dev, err, fmt, args...)
+#define DEBUG_INCRVAR(var)
+#endif
+
+extern void mtk_attach_switch_rt3050(struct mtkswitch_softc *);
+extern void mtk_attach_switch_mt7620(struct mtkswitch_softc *);
+
+#endif /* __MTKSWITCHVAR_H__ */
diff --git a/sys/dev/etherswitch/rtl8366/rtl8366rb.c b/sys/dev/etherswitch/rtl8366/rtl8366rb.c
new file mode 100644
index 000000000000..9000061ae138
--- /dev/null
+++ b/sys/dev/etherswitch/rtl8366/rtl8366rb.c
@@ -0,0 +1,959 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2015-2016 Hiroki Mori.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * 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 "opt_etherswitch.h"
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.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/ethernet.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/etherswitch/etherswitch.h>
+#include <dev/etherswitch/rtl8366/rtl8366rbvar.h>
+
+#include "mdio_if.h"
+#include "iicbus_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+
+struct rtl8366rb_softc {
+ struct mtx sc_mtx; /* serialize access to softc */
+ int smi_acquired; /* serialize access to SMI/I2C bus */
+ struct mtx callout_mtx; /* serialize callout */
+ device_t dev;
+ int vid[RTL8366_NUM_VLANS];
+ char *ifname[RTL8366_NUM_PHYS];
+ device_t miibus[RTL8366_NUM_PHYS];
+ if_t ifp[RTL8366_NUM_PHYS];
+ struct callout callout_tick;
+ etherswitch_info_t info;
+ int chip_type;
+ int phy4cpu;
+ int numphys;
+};
+
+#define RTL_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define RTL_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define RTL_LOCK_ASSERT(_sc, _what) mtx_assert(&(_s)c->sc_mtx, (_what))
+#define RTL_TRYLOCK(_sc) mtx_trylock(&(_sc)->sc_mtx)
+
+#define RTL_WAITOK 0
+#define RTL_NOWAIT 1
+
+#define RTL_SMI_ACQUIRED 1
+#define RTL_SMI_ACQUIRED_ASSERT(_sc) \
+ KASSERT((_sc)->smi_acquired == RTL_SMI_ACQUIRED, ("smi must be acquired @%s", __FUNCTION__))
+
+#if defined(DEBUG)
+#define DPRINTF(dev, args...) device_printf(dev, args)
+#define DEVERR(dev, err, fmt, args...) do { \
+ if (err != 0) device_printf(dev, fmt, err, args); \
+ } while (0)
+#define DEBUG_INCRVAR(var) do { \
+ var++; \
+ } while (0)
+
+static int callout_blocked = 0;
+static int iic_select_retries = 0;
+static int phy_access_retries = 0;
+static SYSCTL_NODE(_debug, OID_AUTO, rtl8366rb, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
+ "rtl8366rb");
+SYSCTL_INT(_debug_rtl8366rb, OID_AUTO, callout_blocked, CTLFLAG_RW, &callout_blocked, 0,
+ "number of times the callout couldn't acquire the bus");
+SYSCTL_INT(_debug_rtl8366rb, OID_AUTO, iic_select_retries, CTLFLAG_RW, &iic_select_retries, 0,
+ "number of times the I2C bus selection had to be retried");
+SYSCTL_INT(_debug_rtl8366rb, OID_AUTO, phy_access_retries, CTLFLAG_RW, &phy_access_retries, 0,
+ "number of times PHY register access had to be retried");
+#else
+#define DPRINTF(dev, args...)
+#define DEVERR(dev, err, fmt, args...)
+#define DEBUG_INCRVAR(var)
+#endif
+
+static int smi_probe(device_t dev);
+static int smi_read(device_t dev, uint16_t addr, uint16_t *data, int sleep);
+static int smi_write(device_t dev, uint16_t addr, uint16_t data, int sleep);
+static int smi_rmw(device_t dev, uint16_t addr, uint16_t mask, uint16_t data, int sleep);
+static void rtl8366rb_tick(void *arg);
+static int rtl8366rb_ifmedia_upd(if_t);
+static void rtl8366rb_ifmedia_sts(if_t, struct ifmediareq *);
+
+static void
+rtl8366rb_identify(driver_t *driver, device_t parent)
+{
+ device_t child;
+ struct iicbus_ivar *devi;
+
+ if (device_find_child(parent, "rtl8366rb", DEVICE_UNIT_ANY) == NULL) {
+ child = BUS_ADD_CHILD(parent, 0, "rtl8366rb", DEVICE_UNIT_ANY);
+ devi = IICBUS_IVAR(child);
+ devi->addr = RTL8366_IIC_ADDR;
+ }
+}
+
+static int
+rtl8366rb_probe(device_t dev)
+{
+ struct rtl8366rb_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ bzero(sc, sizeof(*sc));
+ if (smi_probe(dev) != 0)
+ return (ENXIO);
+ if (sc->chip_type == RTL8366RB)
+ device_set_desc(dev, "RTL8366RB Ethernet Switch Controller");
+ else
+ device_set_desc(dev, "RTL8366SR Ethernet Switch Controller");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static void
+rtl8366rb_init(device_t dev)
+{
+ struct rtl8366rb_softc *sc;
+ int i;
+
+ sc = device_get_softc(dev);
+
+ /* Initialisation for TL-WR1043ND */
+#ifdef RTL8366_SOFT_RESET
+ smi_rmw(dev, RTL8366_RCR,
+ RTL8366_RCR_SOFT_RESET,
+ RTL8366_RCR_SOFT_RESET, RTL_WAITOK);
+#else
+ smi_rmw(dev, RTL8366_RCR,
+ RTL8366_RCR_HARD_RESET,
+ RTL8366_RCR_HARD_RESET, RTL_WAITOK);
+#endif
+ /* hard reset not return ack */
+ DELAY(100000);
+ /* Enable 16 VLAN mode */
+ smi_rmw(dev, RTL8366_SGCR,
+ RTL8366_SGCR_EN_VLAN | RTL8366_SGCR_EN_VLAN_4KTB,
+ RTL8366_SGCR_EN_VLAN, RTL_WAITOK);
+ /* Initialize our vlan table. */
+ for (i = 0; i <= 1; i++)
+ sc->vid[i] = (i + 1) | ETHERSWITCH_VID_VALID;
+ /* Remove port 0 from VLAN 1. */
+ smi_rmw(dev, RTL8366_VMCR(RTL8366_VMCR_MU_REG, 0),
+ (1 << 0), 0, RTL_WAITOK);
+ /* Add port 0 untagged and port 5 tagged to VLAN 2. */
+ smi_rmw(dev, RTL8366_VMCR(RTL8366_VMCR_MU_REG, 1),
+ ((1 << 5 | 1 << 0) << RTL8366_VMCR_MU_MEMBER_SHIFT)
+ | ((1 << 5 | 1 << 0) << RTL8366_VMCR_MU_UNTAG_SHIFT),
+ ((1 << 5 | 1 << 0) << RTL8366_VMCR_MU_MEMBER_SHIFT
+ | ((1 << 0) << RTL8366_VMCR_MU_UNTAG_SHIFT)),
+ RTL_WAITOK);
+ /* Set PVID 2 for port 0. */
+ smi_rmw(dev, RTL8366_PVCR_REG(0),
+ RTL8366_PVCR_VAL(0, RTL8366_PVCR_PORT_MASK),
+ RTL8366_PVCR_VAL(0, 1), RTL_WAITOK);
+}
+
+static int
+rtl8366rb_attach(device_t dev)
+{
+ struct rtl8366rb_softc *sc;
+ uint16_t rev = 0;
+ char name[IFNAMSIZ];
+ int err = 0;
+ int i;
+
+ sc = device_get_softc(dev);
+
+ sc->dev = dev;
+ mtx_init(&sc->sc_mtx, "rtl8366rb", NULL, MTX_DEF);
+ sc->smi_acquired = 0;
+ mtx_init(&sc->callout_mtx, "rtl8366rbcallout", NULL, MTX_DEF);
+
+ rtl8366rb_init(dev);
+ smi_read(dev, RTL8366_CVCR, &rev, RTL_WAITOK);
+ device_printf(dev, "rev. %d\n", rev & 0x000f);
+
+ sc->phy4cpu = 0;
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "phy4cpu", &sc->phy4cpu);
+
+ sc->numphys = sc->phy4cpu ? RTL8366_NUM_PHYS - 1 : RTL8366_NUM_PHYS;
+
+ sc->info.es_nports = sc->numphys + 1;
+ sc->info.es_nvlangroups = RTL8366_NUM_VLANS;
+ sc->info.es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q;
+ if (sc->chip_type == RTL8366RB)
+ sprintf(sc->info.es_name, "Realtek RTL8366RB");
+ else
+ sprintf(sc->info.es_name, "Realtek RTL8366SR");
+
+ /* attach miibus and phys */
+ /* PHYs need an interface, so we generate a dummy one */
+ for (i = 0; i < sc->numphys; i++) {
+ sc->ifp[i] = if_alloc(IFT_ETHER);
+ if_setsoftc(sc->ifp[i], sc);
+ if_setflagbits(sc->ifp[i], IFF_UP | IFF_BROADCAST | IFF_DRV_RUNNING
+ | IFF_SIMPLEX, 0);
+ snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(dev));
+ sc->ifname[i] = malloc(strlen(name)+1, M_DEVBUF, M_WAITOK);
+ bcopy(name, sc->ifname[i], strlen(name)+1);
+ if_initname(sc->ifp[i], sc->ifname[i], i);
+ err = mii_attach(dev, &sc->miibus[i], sc->ifp[i], rtl8366rb_ifmedia_upd, \
+ rtl8366rb_ifmedia_sts, BMSR_DEFCAPMASK, \
+ i, MII_OFFSET_ANY, 0);
+ if (err != 0) {
+ device_printf(dev, "attaching PHY %d failed\n", i);
+ return (err);
+ }
+ }
+
+ bus_identify_children(dev);
+ bus_enumerate_hinted_children(dev);
+ bus_attach_children(dev);
+
+ callout_init_mtx(&sc->callout_tick, &sc->callout_mtx, 0);
+ rtl8366rb_tick(sc);
+
+ return (err);
+}
+
+static int
+rtl8366rb_detach(device_t dev)
+{
+ struct rtl8366rb_softc *sc;
+ int error, i;
+
+ error = bus_generic_detach(dev);
+ if (error != 0)
+ return (error);
+
+ sc = device_get_softc(dev);
+
+ for (i=0; i < sc->numphys; i++) {
+ if (sc->ifp[i] != NULL)
+ if_free(sc->ifp[i]);
+ free(sc->ifname[i], M_DEVBUF);
+ }
+ callout_drain(&sc->callout_tick);
+ mtx_destroy(&sc->callout_mtx);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static void
+rtl8366rb_update_ifmedia(int portstatus, u_int *media_status, u_int *media_active)
+{
+ *media_active = IFM_ETHER;
+ *media_status = IFM_AVALID;
+ if ((portstatus & RTL8366_PLSR_LINK) != 0)
+ *media_status |= IFM_ACTIVE;
+ else {
+ *media_active |= IFM_NONE;
+ return;
+ }
+ switch (portstatus & RTL8366_PLSR_SPEED_MASK) {
+ case RTL8366_PLSR_SPEED_10:
+ *media_active |= IFM_10_T;
+ break;
+ case RTL8366_PLSR_SPEED_100:
+ *media_active |= IFM_100_TX;
+ break;
+ case RTL8366_PLSR_SPEED_1000:
+ *media_active |= IFM_1000_T;
+ break;
+ }
+ if ((portstatus & RTL8366_PLSR_FULLDUPLEX) != 0)
+ *media_active |= IFM_FDX;
+ else
+ *media_active |= IFM_HDX;
+ if ((portstatus & RTL8366_PLSR_TXPAUSE) != 0)
+ *media_active |= IFM_ETH_TXPAUSE;
+ if ((portstatus & RTL8366_PLSR_RXPAUSE) != 0)
+ *media_active |= IFM_ETH_RXPAUSE;
+}
+
+static void
+rtl833rb_miipollstat(struct rtl8366rb_softc *sc)
+{
+ int i;
+ struct mii_data *mii;
+ struct mii_softc *miisc;
+ uint16_t value;
+ int portstatus;
+
+ for (i = 0; i < sc->numphys; i++) {
+ mii = device_get_softc(sc->miibus[i]);
+ if ((i % 2) == 0) {
+ if (smi_read(sc->dev, RTL8366_PLSR_BASE + i/2, &value, RTL_NOWAIT) != 0) {
+ DEBUG_INCRVAR(callout_blocked);
+ return;
+ }
+ portstatus = value & 0xff;
+ } else {
+ portstatus = (value >> 8) & 0xff;
+ }
+ rtl8366rb_update_ifmedia(portstatus, &mii->mii_media_status, &mii->mii_media_active);
+ LIST_FOREACH(miisc, &mii->mii_phys, mii_list) {
+ if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != miisc->mii_inst)
+ continue;
+ mii_phy_update(miisc, MII_POLLSTAT);
+ }
+ }
+}
+
+static void
+rtl8366rb_tick(void *arg)
+{
+ struct rtl8366rb_softc *sc;
+
+ sc = arg;
+
+ rtl833rb_miipollstat(sc);
+ callout_reset(&sc->callout_tick, hz, rtl8366rb_tick, sc);
+}
+
+static int
+smi_probe(device_t dev)
+{
+ struct rtl8366rb_softc *sc;
+ device_t iicbus, iicha;
+ int err, i, j;
+ uint16_t chipid;
+ char bytes[2];
+ int xferd;
+
+ sc = device_get_softc(dev);
+
+ iicbus = device_get_parent(dev);
+ iicha = device_get_parent(iicbus);
+
+ for (i = 0; i < 2; ++i) {
+ iicbus_reset(iicbus, IIC_FASTEST, RTL8366_IIC_ADDR, NULL);
+ for (j=3; j--; ) {
+ IICBUS_STOP(iicha);
+ /*
+ * we go directly to the host adapter because iicbus.c
+ * only issues a stop on a bus that was successfully started.
+ */
+ }
+ err = iicbus_request_bus(iicbus, dev, IIC_WAIT);
+ if (err != 0)
+ goto out;
+ err = iicbus_start(iicbus, RTL8366_IIC_ADDR | RTL_IICBUS_READ, RTL_IICBUS_TIMEOUT);
+ if (err != 0)
+ goto out;
+ if (i == 0) {
+ bytes[0] = RTL8366RB_CIR & 0xff;
+ bytes[1] = (RTL8366RB_CIR >> 8) & 0xff;
+ } else {
+ bytes[0] = RTL8366SR_CIR & 0xff;
+ bytes[1] = (RTL8366SR_CIR >> 8) & 0xff;
+ }
+ err = iicbus_write(iicbus, bytes, 2, &xferd, RTL_IICBUS_TIMEOUT);
+ if (err != 0)
+ goto out;
+ err = iicbus_read(iicbus, bytes, 2, &xferd, IIC_LAST_READ, 0);
+ if (err != 0)
+ goto out;
+ chipid = ((bytes[1] & 0xff) << 8) | (bytes[0] & 0xff);
+ if (i == 0 && chipid == RTL8366RB_CIR_ID8366RB) {
+ DPRINTF(dev, "chip id 0x%04x\n", chipid);
+ sc->chip_type = RTL8366RB;
+ err = 0;
+ break;
+ }
+ if (i == 1 && chipid == RTL8366SR_CIR_ID8366SR) {
+ DPRINTF(dev, "chip id 0x%04x\n", chipid);
+ sc->chip_type = RTL8366SR;
+ err = 0;
+ break;
+ }
+ if (i == 0) {
+ iicbus_stop(iicbus);
+ iicbus_release_bus(iicbus, dev);
+ }
+ }
+ if (i == 2)
+ err = ENXIO;
+out:
+ iicbus_stop(iicbus);
+ iicbus_release_bus(iicbus, dev);
+ return (err == 0 ? 0 : ENXIO);
+}
+
+static int
+smi_acquire(struct rtl8366rb_softc *sc, int sleep)
+{
+ int r = 0;
+ if (sleep == RTL_WAITOK)
+ RTL_LOCK(sc);
+ else
+ if (RTL_TRYLOCK(sc) == 0)
+ return (EWOULDBLOCK);
+ if (sc->smi_acquired == RTL_SMI_ACQUIRED)
+ r = EBUSY;
+ else {
+ r = iicbus_request_bus(device_get_parent(sc->dev), sc->dev, \
+ sleep == RTL_WAITOK ? IIC_WAIT : IIC_DONTWAIT);
+ if (r == 0)
+ sc->smi_acquired = RTL_SMI_ACQUIRED;
+ }
+ RTL_UNLOCK(sc);
+ return (r);
+}
+
+static int
+smi_release(struct rtl8366rb_softc *sc, int sleep)
+{
+ if (sleep == RTL_WAITOK)
+ RTL_LOCK(sc);
+ else
+ if (RTL_TRYLOCK(sc) == 0)
+ return (EWOULDBLOCK);
+ RTL_SMI_ACQUIRED_ASSERT(sc);
+ iicbus_release_bus(device_get_parent(sc->dev), sc->dev);
+ sc->smi_acquired = 0;
+ RTL_UNLOCK(sc);
+ return (0);
+}
+
+static int
+smi_select(device_t dev, int op, int sleep)
+{
+ struct rtl8366rb_softc *sc;
+ int err, i;
+ device_t iicbus;
+ struct iicbus_ivar *devi;
+ int slave;
+
+ sc = device_get_softc(dev);
+
+ iicbus = device_get_parent(dev);
+ devi = IICBUS_IVAR(dev);
+ slave = devi->addr;
+
+ RTL_SMI_ACQUIRED_ASSERT((struct rtl8366rb_softc *)device_get_softc(dev));
+
+ if (sc->chip_type == RTL8366SR) { // RTL8366SR work around
+ // this is same work around at probe
+ for (int i=3; i--; )
+ IICBUS_STOP(device_get_parent(device_get_parent(dev)));
+ }
+ /*
+ * The chip does not use clock stretching when it is busy,
+ * instead ignoring the command. Retry a few times.
+ */
+ for (i = RTL_IICBUS_RETRIES; i--; ) {
+ err = iicbus_start(iicbus, slave | op, RTL_IICBUS_TIMEOUT);
+ if (err != IIC_ENOACK)
+ break;
+ if (sleep == RTL_WAITOK) {
+ DEBUG_INCRVAR(iic_select_retries);
+ pause("smi_select", RTL_IICBUS_RETRY_SLEEP);
+ } else
+ break;
+ }
+ return (err);
+}
+
+static int
+smi_read_locked(struct rtl8366rb_softc *sc, uint16_t addr, uint16_t *data, int sleep)
+{
+ int err;
+ device_t iicbus;
+ char bytes[2];
+ int xferd;
+
+ iicbus = device_get_parent(sc->dev);
+
+ RTL_SMI_ACQUIRED_ASSERT(sc);
+ bytes[0] = addr & 0xff;
+ bytes[1] = (addr >> 8) & 0xff;
+ err = smi_select(sc->dev, RTL_IICBUS_READ, sleep);
+ if (err != 0)
+ goto out;
+ err = iicbus_write(iicbus, bytes, 2, &xferd, RTL_IICBUS_TIMEOUT);
+ if (err != 0)
+ goto out;
+ err = iicbus_read(iicbus, bytes, 2, &xferd, IIC_LAST_READ, 0);
+ if (err != 0)
+ goto out;
+ *data = ((bytes[1] & 0xff) << 8) | (bytes[0] & 0xff);
+
+out:
+ iicbus_stop(iicbus);
+ return (err);
+}
+
+static int
+smi_write_locked(struct rtl8366rb_softc *sc, uint16_t addr, uint16_t data, int sleep)
+{
+ int err;
+ device_t iicbus;
+ char bytes[4];
+ int xferd;
+
+ iicbus = device_get_parent(sc->dev);
+
+ RTL_SMI_ACQUIRED_ASSERT(sc);
+ bytes[0] = addr & 0xff;
+ bytes[1] = (addr >> 8) & 0xff;
+ bytes[2] = data & 0xff;
+ bytes[3] = (data >> 8) & 0xff;
+
+ err = smi_select(sc->dev, RTL_IICBUS_WRITE, sleep);
+ if (err == 0)
+ err = iicbus_write(iicbus, bytes, 4, &xferd, RTL_IICBUS_TIMEOUT);
+ iicbus_stop(iicbus);
+
+ return (err);
+}
+
+static int
+smi_read(device_t dev, uint16_t addr, uint16_t *data, int sleep)
+{
+ struct rtl8366rb_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+
+ err = smi_acquire(sc, sleep);
+ if (err != 0)
+ return (EBUSY);
+ err = smi_read_locked(sc, addr, data, sleep);
+ smi_release(sc, sleep);
+ DEVERR(dev, err, "smi_read()=%d: addr=%04x\n", addr);
+ return (err == 0 ? 0 : EIO);
+}
+
+static int
+smi_write(device_t dev, uint16_t addr, uint16_t data, int sleep)
+{
+ struct rtl8366rb_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+
+ err = smi_acquire(sc, sleep);
+ if (err != 0)
+ return (EBUSY);
+ err = smi_write_locked(sc, addr, data, sleep);
+ smi_release(sc, sleep);
+ DEVERR(dev, err, "smi_write()=%d: addr=%04x\n", addr);
+ return (err == 0 ? 0 : EIO);
+}
+
+static int
+smi_rmw(device_t dev, uint16_t addr, uint16_t mask, uint16_t data, int sleep)
+{
+ struct rtl8366rb_softc *sc;
+ int err;
+ uint16_t oldv, newv;
+
+ sc = device_get_softc(dev);
+
+ err = smi_acquire(sc, sleep);
+ if (err != 0)
+ return (EBUSY);
+ if (err == 0) {
+ err = smi_read_locked(sc, addr, &oldv, sleep);
+ if (err == 0) {
+ newv = oldv & ~mask;
+ newv |= data & mask;
+ if (newv != oldv)
+ err = smi_write_locked(sc, addr, newv, sleep);
+ }
+ }
+ smi_release(sc, sleep);
+ DEVERR(dev, err, "smi_rmw()=%d: addr=%04x\n", addr);
+ return (err == 0 ? 0 : EIO);
+}
+
+static etherswitch_info_t *
+rtl_getinfo(device_t dev)
+{
+ struct rtl8366rb_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ return (&sc->info);
+}
+
+static int
+rtl_readreg(device_t dev, int reg)
+{
+ uint16_t data;
+
+ data = 0;
+
+ smi_read(dev, reg, &data, RTL_WAITOK);
+ return (data);
+}
+
+static int
+rtl_writereg(device_t dev, int reg, int value)
+{
+ return (smi_write(dev, reg, value, RTL_WAITOK));
+}
+
+static int
+rtl_getport(device_t dev, etherswitch_port_t *p)
+{
+ struct rtl8366rb_softc *sc;
+ struct ifmedia *ifm;
+ struct mii_data *mii;
+ struct ifmediareq *ifmr;
+ uint16_t v;
+ int err, vlangroup;
+
+ sc = device_get_softc(dev);
+
+ ifmr = &p->es_ifmr;
+
+ if (p->es_port < 0 || p->es_port >= (sc->numphys + 1))
+ return (ENXIO);
+ if (sc->phy4cpu && p->es_port == sc->numphys) {
+ vlangroup = RTL8366_PVCR_GET(p->es_port + 1,
+ rtl_readreg(dev, RTL8366_PVCR_REG(p->es_port + 1)));
+ } else {
+ vlangroup = RTL8366_PVCR_GET(p->es_port,
+ rtl_readreg(dev, RTL8366_PVCR_REG(p->es_port)));
+ }
+ p->es_pvid = sc->vid[vlangroup] & ETHERSWITCH_VID_MASK;
+
+ if (p->es_port < sc->numphys) {
+ mii = device_get_softc(sc->miibus[p->es_port]);
+ ifm = &mii->mii_media;
+ err = ifmedia_ioctl(sc->ifp[p->es_port], &p->es_ifr, ifm, SIOCGIFMEDIA);
+ if (err)
+ return (err);
+ } else {
+ /* fill in fixed values for CPU port */
+ p->es_flags |= ETHERSWITCH_PORT_CPU;
+ smi_read(dev, RTL8366_PLSR_BASE + (RTL8366_NUM_PHYS)/2, &v, RTL_WAITOK);
+ v = v >> (8 * ((RTL8366_NUM_PHYS) % 2));
+ rtl8366rb_update_ifmedia(v, &ifmr->ifm_status, &ifmr->ifm_active);
+ ifmr->ifm_current = ifmr->ifm_active;
+ ifmr->ifm_mask = 0;
+ ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID;
+ /* Return our static media list. */
+ if (ifmr->ifm_count > 0) {
+ ifmr->ifm_count = 1;
+ ifmr->ifm_ulist[0] = IFM_MAKEWORD(IFM_ETHER, IFM_1000_T,
+ IFM_FDX, 0);
+ } else
+ ifmr->ifm_count = 0;
+ }
+ return (0);
+}
+
+static int
+rtl_setport(device_t dev, etherswitch_port_t *p)
+{
+ struct rtl8366rb_softc *sc;
+ int i, err, vlangroup;
+ struct ifmedia *ifm;
+ struct mii_data *mii;
+ int port;
+
+ sc = device_get_softc(dev);
+
+ if (p->es_port < 0 || p->es_port >= (sc->numphys + 1))
+ return (ENXIO);
+ vlangroup = -1;
+ for (i = 0; i < RTL8366_NUM_VLANS; i++) {
+ if ((sc->vid[i] & ETHERSWITCH_VID_MASK) == p->es_pvid) {
+ vlangroup = i;
+ break;
+ }
+ }
+ if (vlangroup == -1)
+ return (ENXIO);
+ if (sc->phy4cpu && p->es_port == sc->numphys) {
+ port = p->es_port + 1;
+ } else {
+ port = p->es_port;
+ }
+ err = smi_rmw(dev, RTL8366_PVCR_REG(port),
+ RTL8366_PVCR_VAL(port, RTL8366_PVCR_PORT_MASK),
+ RTL8366_PVCR_VAL(port, vlangroup), RTL_WAITOK);
+ if (err)
+ return (err);
+ /* CPU Port */
+ if (p->es_port == sc->numphys)
+ return (0);
+ mii = device_get_softc(sc->miibus[p->es_port]);
+ ifm = &mii->mii_media;
+ err = ifmedia_ioctl(sc->ifp[p->es_port], &p->es_ifr, ifm, SIOCSIFMEDIA);
+ return (err);
+}
+
+static int
+rtl_getvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ struct rtl8366rb_softc *sc;
+ uint16_t vmcr[3];
+ int i;
+ int member, untagged;
+
+ sc = device_get_softc(dev);
+
+ for (i=0; i<RTL8366_VMCR_MULT; i++)
+ vmcr[i] = rtl_readreg(dev, RTL8366_VMCR(i, vg->es_vlangroup));
+
+ vg->es_vid = sc->vid[vg->es_vlangroup];
+ member = RTL8366_VMCR_MEMBER(vmcr);
+ untagged = RTL8366_VMCR_UNTAG(vmcr);
+ if (sc->phy4cpu) {
+ vg->es_member_ports = ((member & 0x20) >> 1) | (member & 0x0f);
+ vg->es_untagged_ports = ((untagged & 0x20) >> 1) | (untagged & 0x0f);
+ } else {
+ vg->es_member_ports = member;
+ vg->es_untagged_ports = untagged;
+ }
+ vg->es_fid = RTL8366_VMCR_FID(vmcr);
+ return (0);
+}
+
+static int
+rtl_setvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+ struct rtl8366rb_softc *sc;
+ int g;
+ int member, untagged;
+
+ sc = device_get_softc(dev);
+
+ g = vg->es_vlangroup;
+
+ sc->vid[g] = vg->es_vid;
+ /* VLAN group disabled ? */
+ if (vg->es_member_ports == 0 && vg->es_untagged_ports == 0 && vg->es_vid == 0)
+ return (0);
+ sc->vid[g] |= ETHERSWITCH_VID_VALID;
+ rtl_writereg(dev, RTL8366_VMCR(RTL8366_VMCR_DOT1Q_REG, g),
+ (vg->es_vid << RTL8366_VMCR_DOT1Q_VID_SHIFT) & RTL8366_VMCR_DOT1Q_VID_MASK);
+ if (sc->phy4cpu) {
+ /* add space at phy4 */
+ member = (vg->es_member_ports & 0x0f) |
+ ((vg->es_member_ports & 0x10) << 1);
+ untagged = (vg->es_untagged_ports & 0x0f) |
+ ((vg->es_untagged_ports & 0x10) << 1);
+ } else {
+ member = vg->es_member_ports;
+ untagged = vg->es_untagged_ports;
+ }
+ if (sc->chip_type == RTL8366RB) {
+ rtl_writereg(dev, RTL8366_VMCR(RTL8366_VMCR_MU_REG, g),
+ ((member << RTL8366_VMCR_MU_MEMBER_SHIFT) & RTL8366_VMCR_MU_MEMBER_MASK) |
+ ((untagged << RTL8366_VMCR_MU_UNTAG_SHIFT) & RTL8366_VMCR_MU_UNTAG_MASK));
+ rtl_writereg(dev, RTL8366_VMCR(RTL8366_VMCR_FID_REG, g),
+ vg->es_fid);
+ } else {
+ rtl_writereg(dev, RTL8366_VMCR(RTL8366_VMCR_MU_REG, g),
+ ((member << RTL8366_VMCR_MU_MEMBER_SHIFT) & RTL8366_VMCR_MU_MEMBER_MASK) |
+ ((untagged << RTL8366_VMCR_MU_UNTAG_SHIFT) & RTL8366_VMCR_MU_UNTAG_MASK) |
+ ((vg->es_fid << RTL8366_VMCR_FID_FID_SHIFT) & RTL8366_VMCR_FID_FID_MASK));
+ }
+ return (0);
+}
+
+static int
+rtl_getconf(device_t dev, etherswitch_conf_t *conf)
+{
+
+ /* Return the VLAN mode. */
+ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE;
+ conf->vlan_mode = ETHERSWITCH_VLAN_DOT1Q;
+
+ return (0);
+}
+
+static int
+rtl_readphy(device_t dev, int phy, int reg)
+{
+ struct rtl8366rb_softc *sc;
+ uint16_t data;
+ int err, i, sleep;
+
+ sc = device_get_softc(dev);
+
+ data = 0;
+
+ if (phy < 0 || phy >= RTL8366_NUM_PHYS)
+ return (ENXIO);
+ if (reg < 0 || reg >= RTL8366_NUM_PHY_REG)
+ return (ENXIO);
+ sleep = RTL_WAITOK;
+ err = smi_acquire(sc, sleep);
+ if (err != 0)
+ return (EBUSY);
+ for (i = RTL_IICBUS_RETRIES; i--; ) {
+ err = smi_write_locked(sc, RTL8366_PACR, RTL8366_PACR_READ, sleep);
+ if (err == 0)
+ err = smi_write_locked(sc, RTL8366_PHYREG(phy, 0, reg), 0, sleep);
+ if (err == 0) {
+ err = smi_read_locked(sc, RTL8366_PADR, &data, sleep);
+ break;
+ }
+ DEBUG_INCRVAR(phy_access_retries);
+ DPRINTF(dev, "rtl_readphy(): chip not responsive, retrying %d more times\n", i);
+ pause("rtl_readphy", RTL_IICBUS_RETRY_SLEEP);
+ }
+ smi_release(sc, sleep);
+ DEVERR(dev, err, "rtl_readphy()=%d: phy=%d.%02x\n", phy, reg);
+ return (data);
+}
+
+static int
+rtl_writephy(device_t dev, int phy, int reg, int data)
+{
+ struct rtl8366rb_softc *sc;
+ int err, i, sleep;
+
+ sc = device_get_softc(dev);
+
+ if (phy < 0 || phy >= RTL8366_NUM_PHYS)
+ return (ENXIO);
+ if (reg < 0 || reg >= RTL8366_NUM_PHY_REG)
+ return (ENXIO);
+ sleep = RTL_WAITOK;
+ err = smi_acquire(sc, sleep);
+ if (err != 0)
+ return (EBUSY);
+ for (i = RTL_IICBUS_RETRIES; i--; ) {
+ err = smi_write_locked(sc, RTL8366_PACR, RTL8366_PACR_WRITE, sleep);
+ if (err == 0)
+ err = smi_write_locked(sc, RTL8366_PHYREG(phy, 0, reg), data, sleep);
+ if (err == 0) {
+ break;
+ }
+ DEBUG_INCRVAR(phy_access_retries);
+ DPRINTF(dev, "rtl_writephy(): chip not responsive, retrying %d more tiems\n", i);
+ pause("rtl_writephy", RTL_IICBUS_RETRY_SLEEP);
+ }
+ smi_release(sc, sleep);
+ DEVERR(dev, err, "rtl_writephy()=%d: phy=%d.%02x\n", phy, reg);
+ return (err == 0 ? 0 : EIO);
+}
+
+static int
+rtl8366rb_ifmedia_upd(if_t ifp)
+{
+ struct rtl8366rb_softc *sc;
+ struct mii_data *mii;
+
+ sc = if_getsoftc(ifp);
+ mii = device_get_softc(sc->miibus[if_getdunit(ifp)]);
+
+ mii_mediachg(mii);
+ return (0);
+}
+
+static void
+rtl8366rb_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct rtl8366rb_softc *sc;
+ struct mii_data *mii;
+
+ sc = if_getsoftc(ifp);
+ mii = device_get_softc(sc->miibus[if_getdunit(ifp)]);
+
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+}
+
+
+static device_method_t rtl8366rb_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_identify, rtl8366rb_identify),
+ DEVMETHOD(device_probe, rtl8366rb_probe),
+ DEVMETHOD(device_attach, rtl8366rb_attach),
+ DEVMETHOD(device_detach, rtl8366rb_detach),
+
+ /* bus interface */
+ DEVMETHOD(bus_add_child, device_add_child_ordered),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, rtl_readphy),
+ DEVMETHOD(miibus_writereg, rtl_writephy),
+
+ /* MDIO interface */
+ DEVMETHOD(mdio_readreg, rtl_readphy),
+ DEVMETHOD(mdio_writereg, rtl_writephy),
+
+ /* etherswitch interface */
+ DEVMETHOD(etherswitch_getconf, rtl_getconf),
+ DEVMETHOD(etherswitch_getinfo, rtl_getinfo),
+ DEVMETHOD(etherswitch_readreg, rtl_readreg),
+ DEVMETHOD(etherswitch_writereg, rtl_writereg),
+ DEVMETHOD(etherswitch_readphyreg, rtl_readphy),
+ DEVMETHOD(etherswitch_writephyreg, rtl_writephy),
+ DEVMETHOD(etherswitch_getport, rtl_getport),
+ DEVMETHOD(etherswitch_setport, rtl_setport),
+ DEVMETHOD(etherswitch_getvgroup, rtl_getvgroup),
+ DEVMETHOD(etherswitch_setvgroup, rtl_setvgroup),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(rtl8366rb, rtl8366rb_driver, rtl8366rb_methods,
+ sizeof(struct rtl8366rb_softc));
+
+DRIVER_MODULE(rtl8366rb, iicbus, rtl8366rb_driver, 0, 0);
+DRIVER_MODULE(miibus, rtl8366rb, miibus_driver, 0, 0);
+DRIVER_MODULE(mdio, rtl8366rb, mdio_driver, 0, 0);
+DRIVER_MODULE(etherswitch, rtl8366rb, etherswitch_driver, 0, 0);
+MODULE_VERSION(rtl8366rb, 1);
+MODULE_DEPEND(rtl8366rb, iicbus, 1, 1, 1); /* XXX which versions? */
+MODULE_DEPEND(rtl8366rb, miibus, 1, 1, 1); /* XXX which versions? */
+MODULE_DEPEND(rtl8366rb, etherswitch, 1, 1, 1); /* XXX which versions? */
diff --git a/sys/dev/etherswitch/rtl8366/rtl8366rbvar.h b/sys/dev/etherswitch/rtl8366/rtl8366rbvar.h
new file mode 100644
index 000000000000..2ce532d998ac
--- /dev/null
+++ b/sys/dev/etherswitch/rtl8366/rtl8366rbvar.h
@@ -0,0 +1,183 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2015-2016 Hiroki Mori.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * All rights reserved.
+ *
+ * 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 _DEV_ETHERSWITCH_RTL8366RBVAR_H_
+#define _DEV_ETHERSWITCH_RTL8366RBVAR_H_
+
+#define RTL8366RB 0
+#define RTL8366SR 1
+
+#define RTL8366_IIC_ADDR 0xa8
+#define RTL_IICBUS_TIMEOUT 100 /* us */
+#define RTL_IICBUS_READ 1
+#define RTL_IICBUS_WRITE 0
+/* number of times to try and select the chip on the I2C bus */
+#define RTL_IICBUS_RETRIES 3
+#define RTL_IICBUS_RETRY_SLEEP (hz/1000)
+
+/* Register definitions */
+
+/* Switch Global Configuration */
+#define RTL8366_SGCR 0x0000
+#define RTL8366_SGCR_EN_BC_STORM_CTRL 0x0001
+#define RTL8366_SGCR_MAX_LENGTH_MASK 0x0030
+#define RTL8366_SGCR_MAX_LENGTH_1522 0x0000
+#define RTL8366_SGCR_MAX_LENGTH_1536 0x0010
+#define RTL8366_SGCR_MAX_LENGTH_1552 0x0020
+#define RTL8366_SGCR_MAX_LENGTH_9216 0x0030
+#define RTL8366_SGCR_EN_VLAN 0x2000
+#define RTL8366_SGCR_EN_VLAN_4KTB 0x4000
+#define RTL8366_SGCR_EN_QOS 0x8000
+
+/* Port Enable Control: DISABLE_PORT[5:0] */
+#define RTL8366_PECR 0x0001
+
+/* Switch Security Control 0: DIS_LEARN[5:0] */
+#define RTL8366_SSCR0 0x0002
+
+/* Switch Security Control 1: DIS_AGE[5:0] */
+#define RTL8366_SSCR1 0x0003
+
+/* Switch Security Control 2 */
+#define RTL8366_SSCR2 0x0004
+#define RTL8366_SSCR2_DROP_UNKNOWN_DA 0x0001
+
+/* Port Link Status: two ports per register */
+#define RTL8366_PLSR_BASE (sc->chip_type == 0 ? 0x0014 : 0x0060)
+#define RTL8366_PLSR_SPEED_MASK 0x03
+#define RTL8366_PLSR_SPEED_10 0x00
+#define RTL8366_PLSR_SPEED_100 0x01
+#define RTL8366_PLSR_SPEED_1000 0x02
+#define RTL8366_PLSR_FULLDUPLEX 0x04
+#define RTL8366_PLSR_LINK 0x10
+#define RTL8366_PLSR_TXPAUSE 0x20
+#define RTL8366_PLSR_RXPAUSE 0x40
+#define RTL8366_PLSR_NO_AUTO 0x80
+
+/* VLAN Member Configuration, 3 or 2 registers per VLAN */
+#define RTL8366_VMCR_BASE (sc->chip_type == 0 ? 0x0020 : 0x0016)
+#define RTL8366_VMCR_MULT (sc->chip_type == 0 ? 3 : 2)
+#define RTL8366_VMCR_DOT1Q_REG 0
+#define RTL8366_VMCR_DOT1Q_VID_SHIFT 0
+#define RTL8366_VMCR_DOT1Q_VID_MASK 0x0fff
+#define RTL8366_VMCR_DOT1Q_PCP_SHIFT 12
+#define RTL8366_VMCR_DOT1Q_PCP_MASK 0x7000
+#define RTL8366_VMCR_MU_REG 1
+#define RTL8366_VMCR_MU_MEMBER_SHIFT 0
+#define RTL8366_VMCR_MU_MEMBER_MASK (sc->chip_type == 0 ? 0x00ff : 0x003f)
+#define RTL8366_VMCR_MU_UNTAG_SHIFT (sc->chip_type == 0 ? 8 : 6)
+#define RTL8366_VMCR_MU_UNTAG_MASK (sc->chip_type == 0 ? 0xff00 : 0x0fc0)
+#define RTL8366_VMCR_FID_REG (sc->chip_type == 0 ? 2 : 1)
+#define RTL8366_VMCR_FID_FID_SHIFT (sc->chip_type == 0 ? 0 : 12)
+#define RTL8366_VMCR_FID_FID_MASK (sc->chip_type == 0 ? 0x0007 : 0x7000)
+#define RTL8366_VMCR(_reg, _vlan) \
+ (RTL8366_VMCR_BASE + _reg + _vlan * RTL8366_VMCR_MULT)
+/* VLAN Identifier */
+#define RTL8366_VMCR_VID(_r) \
+ (_r[RTL8366_VMCR_DOT1Q_REG] & RTL8366_VMCR_DOT1Q_VID_MASK)
+/* Priority Code Point */
+#define RTL8366_VMCR_PCP(_r) \
+ ((_r[RTL8366_VMCR_DOT1Q_REG] & RTL8366_VMCR_DOT1Q_PCP_MASK) \
+ >> RTL8366_VMCR_DOT1Q_PCP_SHIFT)
+/* Member ports */
+#define RTL8366_VMCR_MEMBER(_r) \
+ (_r[RTL8366_VMCR_MU_REG] & RTL8366_VMCR_MU_MEMBER_MASK)
+/* Untagged ports */
+#define RTL8366_VMCR_UNTAG(_r) \
+ ((_r[RTL8366_VMCR_MU_REG] & RTL8366_VMCR_MU_UNTAG_MASK) \
+ >> RTL8366_VMCR_MU_UNTAG_SHIFT)
+/* Forwarding ID */
+#define RTL8366_VMCR_FID(_r) \
+ (sc->chip_type == 0 ? (_r[RTL8366_VMCR_FID_REG] & RTL8366_VMCR_FID_FID_MASK) : \
+ ((_r[RTL8366_VMCR_FID_REG] & RTL8366_VMCR_FID_FID_MASK) \
+ >> RTL8366_VMCR_FID_FID_SHIFT))
+
+/*
+ * Port VLAN Control, 4 ports per register
+ * Determines the VID for untagged ingress frames through
+ * index into VMC.
+ */
+#define RTL8366_PVCR_BASE (sc->chip_type == 0 ? 0x0063 : 0x0058)
+#define RTL8366_PVCR_PORT_SHIFT 4
+#define RTL8366_PVCR_PORT_PERREG (16 / RTL8366_PVCR_PORT_SHIFT)
+#define RTL8366_PVCR_PORT_MASK 0x000f
+#define RTL8366_PVCR_REG(_port) \
+ (RTL8366_PVCR_BASE + _port / (RTL8366_PVCR_PORT_PERREG))
+#define RTL8366_PVCR_VAL(_port, _pvlan) \
+ ((_pvlan & RTL8366_PVCR_PORT_MASK) << \
+ ((_port % RTL8366_PVCR_PORT_PERREG) * RTL8366_PVCR_PORT_SHIFT))
+#define RTL8366_PVCR_GET(_port, _val) \
+ (((_val) >> ((_port % RTL8366_PVCR_PORT_PERREG) * RTL8366_PVCR_PORT_SHIFT)) & RTL8366_PVCR_PORT_MASK)
+
+/* Reset Control */
+#define RTL8366_RCR 0x0100
+#define RTL8366_RCR_HARD_RESET 0x0001
+#define RTL8366_RCR_SOFT_RESET 0x0002
+
+/* Chip Version Control: CHIP_VER[3:0] */
+#define RTL8366_CVCR (sc->chip_type == 0 ? 0x050A : 0x0104)
+/* Chip Identifier */
+#define RTL8366RB_CIR 0x0509
+#define RTL8366RB_CIR_ID8366RB 0x5937
+#define RTL8366SR_CIR 0x0105
+#define RTL8366SR_CIR_ID8366SR 0x8366
+
+/* VLAN Ingress Control 2: [5:0] */
+#define RTL8366_VIC2R 0x037f
+
+/* MIB registers */
+#define RTL8366_MCNT_BASE 0x1000
+#define RTL8366_MCTLR (sc->chip_type == 0 ? 0x13f0 : 0x11F0)
+#define RTL8366_MCTLR_BUSY 0x0001
+#define RTL8366_MCTLR_RESET 0x0002
+#define RTL8366_MCTLR_RESET_PORT_MASK 0x00fc
+#define RTL8366_MCTLR_RESET_ALL 0x0800
+
+#define RTL8366_MCNT(_port, _r) \
+ (RTL8366_MCNT_BASE + 0x50 * (_port) + (_r))
+#define RTL8366_MCTLR_RESET_PORT(_p) \
+ (1 << ((_p) + 2))
+
+/* PHY Access Control */
+#define RTL8366_PACR (sc->chip_type == 0 ? 0x8000 : 0x8028)
+#define RTL8366_PACR_WRITE 0x0000
+#define RTL8366_PACR_READ 0x0001
+
+/* PHY Access Data */
+#define RTL8366_PADR (sc->chip_type == 0 ? 0x8002 : 0x8029)
+
+#define RTL8366_PHYREG(phy, page, reg) \
+ (0x8000 | (1 << (((phy) & 0x1f) + 9)) | (((page) & (sc->chip_type == 0 ? 0xf : 0x7)) << 5) | ((reg) & 0x1f))
+
+/* general characteristics of the chip */
+#define RTL8366_NUM_PHYS 5
+#define RTL8366_NUM_VLANS 16
+#define RTL8366_NUM_PHY_REG 32
+
+#endif
diff --git a/sys/dev/etherswitch/ukswitch/ukswitch.c b/sys/dev/etherswitch/ukswitch/ukswitch.c
new file mode 100644
index 000000000000..a2e30c3af8a1
--- /dev/null
+++ b/sys/dev/etherswitch/ukswitch/ukswitch.c
@@ -0,0 +1,574 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Luiz Otavio O Souza.
+ * Copyright (c) 2011-2012 Stefan Bethke.
+ * Copyright (c) 2012 Adrian Chadd.
+ * All rights reserved.
+ *
+ * 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/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.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/ethernet.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <machine/bus.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include <dev/mdio/mdio.h>
+
+#include <dev/etherswitch/etherswitch.h>
+
+#include "mdio_if.h"
+#include "miibus_if.h"
+#include "etherswitch_if.h"
+
+MALLOC_DECLARE(M_UKSWITCH);
+MALLOC_DEFINE(M_UKSWITCH, "ukswitch", "ukswitch data structures");
+
+struct ukswitch_softc {
+ struct mtx sc_mtx; /* serialize access to softc */
+ device_t sc_dev;
+ int media; /* cpu port media */
+ int cpuport; /* which PHY is connected to the CPU */
+ int phymask; /* PHYs we manage */
+ int phyoffset; /* PHYs register offset */
+ int numports; /* number of ports */
+ int ifpport[MII_NPHY];
+ int *portphy;
+ char **ifname;
+ device_t **miibus;
+ if_t *ifp;
+ struct callout callout_tick;
+ etherswitch_info_t info;
+};
+
+#define UKSWITCH_LOCK(_sc) \
+ mtx_lock(&(_sc)->sc_mtx)
+#define UKSWITCH_UNLOCK(_sc) \
+ mtx_unlock(&(_sc)->sc_mtx)
+#define UKSWITCH_LOCK_ASSERT(_sc, _what) \
+ mtx_assert(&(_sc)->sc_mtx, (_what))
+#define UKSWITCH_TRYLOCK(_sc) \
+ mtx_trylock(&(_sc)->sc_mtx)
+
+#if defined(DEBUG)
+#define DPRINTF(dev, args...) device_printf(dev, args)
+#else
+#define DPRINTF(dev, args...)
+#endif
+
+static inline int ukswitch_portforphy(struct ukswitch_softc *, int);
+static void ukswitch_tick(void *);
+static int ukswitch_ifmedia_upd(if_t);
+static void ukswitch_ifmedia_sts(if_t, struct ifmediareq *);
+
+static int
+ukswitch_probe(device_t dev)
+{
+ struct ukswitch_softc *sc;
+
+ sc = device_get_softc(dev);
+ bzero(sc, sizeof(*sc));
+
+ device_set_desc(dev, "Generic MDIO switch driver");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+ukswitch_attach_phys(struct ukswitch_softc *sc)
+{
+ int phy, port = 0, 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 < MII_NPHY; phy++) {
+ if (((1 << phy) & sc->phymask) == 0)
+ continue;
+ sc->ifpport[phy] = port;
+ sc->portphy[port] = phy;
+ sc->ifp[port] = if_alloc(IFT_ETHER);
+ if_setsoftc(sc->ifp[port], sc);
+ if_setflags(sc->ifp[port], IFF_UP | IFF_BROADCAST |
+ IFF_DRV_RUNNING | IFF_SIMPLEX);
+ sc->ifname[port] = malloc(strlen(name)+1, M_UKSWITCH, M_WAITOK);
+ bcopy(name, sc->ifname[port], strlen(name)+1);
+ if_initname(sc->ifp[port], sc->ifname[port], port);
+ sc->miibus[port] = malloc(sizeof(device_t), M_UKSWITCH,
+ M_WAITOK | M_ZERO);
+ err = mii_attach(sc->sc_dev, sc->miibus[port], sc->ifp[port],
+ ukswitch_ifmedia_upd, ukswitch_ifmedia_sts, \
+ BMSR_DEFCAPMASK, phy + sc->phyoffset, MII_OFFSET_ANY, 0);
+ DPRINTF(sc->sc_dev, "%s attached to pseudo interface %s\n",
+ device_get_nameunit(*sc->miibus[port]),
+ if_name(sc->ifp[port]));
+ if (err != 0) {
+ device_printf(sc->sc_dev,
+ "attaching PHY %d failed\n",
+ phy);
+ break;
+ }
+ sc->info.es_nports = port + 1;
+ if (++port >= sc->numports)
+ break;
+ }
+ return (err);
+}
+
+static int
+ukswitch_attach(device_t dev)
+{
+ struct ukswitch_softc *sc;
+ int err = 0;
+
+ sc = device_get_softc(dev);
+
+ sc->sc_dev = dev;
+ mtx_init(&sc->sc_mtx, "ukswitch", NULL, MTX_DEF);
+ strlcpy(sc->info.es_name, device_get_desc(dev),
+ sizeof(sc->info.es_name));
+
+ /* XXX Defaults */
+ sc->numports = 6;
+ sc->phymask = 0x0f;
+ sc->phyoffset = 0;
+ sc->cpuport = -1;
+ sc->media = 100;
+
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "numports", &sc->numports);
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "phymask", &sc->phymask);
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "phyoffset", &sc->phyoffset);
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "cpuport", &sc->cpuport);
+ (void) resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "media", &sc->media);
+
+ /* Support only fast and giga ethernet. */
+ if (sc->media != 100 && sc->media != 1000)
+ sc->media = 100;
+
+ if (sc->cpuport != -1)
+ /* Always attach the cpu port. */
+ sc->phymask |= (1 << sc->cpuport);
+
+ /* We do not support any vlan groups. */
+ sc->info.es_nvlangroups = 0;
+
+ sc->ifp = malloc(sizeof(if_t) * sc->numports, M_UKSWITCH,
+ M_WAITOK | M_ZERO);
+ sc->ifname = malloc(sizeof(char *) * sc->numports, M_UKSWITCH,
+ M_WAITOK | M_ZERO);
+ sc->miibus = malloc(sizeof(device_t *) * sc->numports, M_UKSWITCH,
+ M_WAITOK | M_ZERO);
+ sc->portphy = malloc(sizeof(int) * sc->numports, M_UKSWITCH,
+ M_WAITOK | M_ZERO);
+
+ /*
+ * Attach the PHYs and complete the bus enumeration.
+ */
+ err = ukswitch_attach_phys(sc);
+ if (err != 0)
+ return (err);
+
+ bus_identify_children(dev);
+ bus_enumerate_hinted_children(dev);
+ bus_attach_children(dev);
+
+ callout_init(&sc->callout_tick, 0);
+
+ ukswitch_tick(sc);
+
+ return (err);
+}
+
+static int
+ukswitch_detach(device_t dev)
+{
+ struct ukswitch_softc *sc = device_get_softc(dev);
+ int error, i, port;
+
+ error = bus_generic_detach(dev);
+ if (error != 0)
+ return (error);
+
+ callout_drain(&sc->callout_tick);
+
+ for (i=0; i < MII_NPHY; i++) {
+ if (((1 << i) & sc->phymask) == 0)
+ continue;
+ port = ukswitch_portforphy(sc, i);
+ if (sc->ifp[port] != NULL)
+ if_free(sc->ifp[port]);
+ free(sc->ifname[port], M_UKSWITCH);
+ free(sc->miibus[port], M_UKSWITCH);
+ }
+
+ free(sc->portphy, M_UKSWITCH);
+ free(sc->miibus, M_UKSWITCH);
+ free(sc->ifname, M_UKSWITCH);
+ free(sc->ifp, M_UKSWITCH);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+/*
+ * Convert PHY number to port number.
+ */
+static inline int
+ukswitch_portforphy(struct ukswitch_softc *sc, int phy)
+{
+
+ return (sc->ifpport[phy]);
+}
+
+static inline struct mii_data *
+ukswitch_miiforport(struct ukswitch_softc *sc, int port)
+{
+
+ if (port < 0 || port > sc->numports)
+ return (NULL);
+ return (device_get_softc(*sc->miibus[port]));
+}
+
+static inline if_t
+ukswitch_ifpforport(struct ukswitch_softc *sc, int port)
+{
+
+ if (port < 0 || port > sc->numports)
+ return (NULL);
+ return (sc->ifp[port]);
+}
+
+/*
+ * Poll the status for all PHYs.
+ */
+static void
+ukswitch_miipollstat(struct ukswitch_softc *sc)
+{
+ int i, port;
+ struct mii_data *mii;
+ struct mii_softc *miisc;
+
+ UKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ for (i = 0; i < MII_NPHY; i++) {
+ if (((1 << i) & sc->phymask) == 0)
+ continue;
+ port = ukswitch_portforphy(sc, i);
+ if ((*sc->miibus[port]) == NULL)
+ continue;
+ mii = device_get_softc(*sc->miibus[port]);
+ 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);
+ }
+ }
+}
+
+static void
+ukswitch_tick(void *arg)
+{
+ struct ukswitch_softc *sc = arg;
+
+ ukswitch_miipollstat(sc);
+ callout_reset(&sc->callout_tick, hz, ukswitch_tick, sc);
+}
+
+static void
+ukswitch_lock(device_t dev)
+{
+ struct ukswitch_softc *sc = device_get_softc(dev);
+
+ UKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+ UKSWITCH_LOCK(sc);
+}
+
+static void
+ukswitch_unlock(device_t dev)
+{
+ struct ukswitch_softc *sc = device_get_softc(dev);
+
+ UKSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+ UKSWITCH_UNLOCK(sc);
+}
+
+static etherswitch_info_t *
+ukswitch_getinfo(device_t dev)
+{
+ struct ukswitch_softc *sc = device_get_softc(dev);
+
+ return (&sc->info);
+}
+
+static int
+ukswitch_getport(device_t dev, etherswitch_port_t *p)
+{
+ struct ukswitch_softc *sc = device_get_softc(dev);
+ struct mii_data *mii;
+ struct ifmediareq *ifmr = &p->es_ifmr;
+ int err, phy;
+
+ if (p->es_port < 0 || p->es_port >= sc->numports)
+ return (ENXIO);
+ p->es_pvid = 0;
+
+ phy = sc->portphy[p->es_port];
+ mii = ukswitch_miiforport(sc, p->es_port);
+ if (sc->cpuport != -1 && phy == sc->cpuport) {
+ /* fill in fixed values for CPU port */
+ p->es_flags |= ETHERSWITCH_PORT_CPU;
+ ifmr->ifm_count = 0;
+ if (sc->media == 100)
+ ifmr->ifm_current = ifmr->ifm_active =
+ IFM_ETHER | IFM_100_TX | IFM_FDX;
+ else
+ 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) {
+ err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr,
+ &mii->mii_media, SIOCGIFMEDIA);
+ if (err)
+ return (err);
+ } else {
+ return (ENXIO);
+ }
+ return (0);
+}
+
+static int
+ukswitch_setport(device_t dev, etherswitch_port_t *p)
+{
+ struct ukswitch_softc *sc = device_get_softc(dev);
+ struct ifmedia *ifm;
+ struct mii_data *mii;
+ if_t ifp;
+ int err;
+
+ if (p->es_port < 0 || p->es_port >= sc->numports)
+ return (ENXIO);
+
+ if (sc->portphy[p->es_port] == sc->cpuport)
+ return (ENXIO);
+
+ mii = ukswitch_miiforport(sc, p->es_port);
+ if (mii == NULL)
+ return (ENXIO);
+
+ ifp = ukswitch_ifpforport(sc, p->es_port);
+
+ ifm = &mii->mii_media;
+ err = ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA);
+ return (err);
+}
+
+static int
+ukswitch_getvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+
+ /* Not supported. */
+ vg->es_vid = 0;
+ vg->es_member_ports = 0;
+ vg->es_untagged_ports = 0;
+ vg->es_fid = 0;
+ return (0);
+}
+
+static int
+ukswitch_setvgroup(device_t dev, etherswitch_vlangroup_t *vg)
+{
+
+ /* Not supported. */
+ return (0);
+}
+
+static void
+ukswitch_statchg(device_t dev)
+{
+
+ DPRINTF(dev, "%s\n", __func__);
+}
+
+static int
+ukswitch_ifmedia_upd(if_t ifp)
+{
+ struct ukswitch_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = ukswitch_miiforport(sc, if_getdunit(ifp));
+
+ DPRINTF(sc->sc_dev, "%s\n", __func__);
+ if (mii == NULL)
+ return (ENXIO);
+ mii_mediachg(mii);
+ return (0);
+}
+
+static void
+ukswitch_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct ukswitch_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = ukswitch_miiforport(sc, if_getdunit(ifp));
+
+ DPRINTF(sc->sc_dev, "%s\n", __func__);
+
+ if (mii == NULL)
+ return;
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+}
+
+static int
+ukswitch_readphy(device_t dev, int phy, int reg)
+{
+ struct ukswitch_softc *sc;
+ int data;
+
+ sc = device_get_softc(dev);
+ UKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ if (phy < 0 || phy >= 32)
+ return (ENXIO);
+ if (reg < 0 || reg >= 32)
+ return (ENXIO);
+
+ UKSWITCH_LOCK(sc);
+ data = MDIO_READREG(device_get_parent(dev), phy, reg);
+ UKSWITCH_UNLOCK(sc);
+
+ return (data);
+}
+
+static int
+ukswitch_writephy(device_t dev, int phy, int reg, int data)
+{
+ struct ukswitch_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+ UKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
+
+ if (phy < 0 || phy >= 32)
+ return (ENXIO);
+ if (reg < 0 || reg >= 32)
+ return (ENXIO);
+
+ UKSWITCH_LOCK(sc);
+ err = MDIO_WRITEREG(device_get_parent(dev), phy, reg, data);
+ UKSWITCH_UNLOCK(sc);
+
+ return (err);
+}
+
+static int
+ukswitch_readreg(device_t dev, int addr)
+{
+ struct ukswitch_softc *sc __diagused;
+
+ sc = device_get_softc(dev);
+ UKSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* Not supported. */
+ return (0);
+}
+
+static int
+ukswitch_writereg(device_t dev, int addr, int value)
+{
+ struct ukswitch_softc *sc __diagused;
+
+ sc = device_get_softc(dev);
+ UKSWITCH_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* Not supported. */
+ return (0);
+}
+
+static device_method_t ukswitch_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, ukswitch_probe),
+ DEVMETHOD(device_attach, ukswitch_attach),
+ DEVMETHOD(device_detach, ukswitch_detach),
+
+ /* bus interface */
+ DEVMETHOD(bus_add_child, device_add_child_ordered),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, ukswitch_readphy),
+ DEVMETHOD(miibus_writereg, ukswitch_writephy),
+ DEVMETHOD(miibus_statchg, ukswitch_statchg),
+
+ /* MDIO interface */
+ DEVMETHOD(mdio_readreg, ukswitch_readphy),
+ DEVMETHOD(mdio_writereg, ukswitch_writephy),
+
+ /* etherswitch interface */
+ DEVMETHOD(etherswitch_lock, ukswitch_lock),
+ DEVMETHOD(etherswitch_unlock, ukswitch_unlock),
+ DEVMETHOD(etherswitch_getinfo, ukswitch_getinfo),
+ DEVMETHOD(etherswitch_readreg, ukswitch_readreg),
+ DEVMETHOD(etherswitch_writereg, ukswitch_writereg),
+ DEVMETHOD(etherswitch_readphyreg, ukswitch_readphy),
+ DEVMETHOD(etherswitch_writephyreg, ukswitch_writephy),
+ DEVMETHOD(etherswitch_getport, ukswitch_getport),
+ DEVMETHOD(etherswitch_setport, ukswitch_setport),
+ DEVMETHOD(etherswitch_getvgroup, ukswitch_getvgroup),
+ DEVMETHOD(etherswitch_setvgroup, ukswitch_setvgroup),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(ukswitch, ukswitch_driver, ukswitch_methods,
+ sizeof(struct ukswitch_softc));
+
+DRIVER_MODULE(ukswitch, mdio, ukswitch_driver, 0, 0);
+DRIVER_MODULE(miibus, ukswitch, miibus_driver, 0, 0);
+DRIVER_MODULE(mdio, ukswitch, mdio_driver, 0, 0);
+DRIVER_MODULE(etherswitch, ukswitch, etherswitch_driver, 0, 0);
+MODULE_VERSION(ukswitch, 1);
+MODULE_DEPEND(ukswitch, miibus, 1, 1, 1); /* XXX which versions? */
+MODULE_DEPEND(ukswitch, etherswitch, 1, 1, 1); /* XXX which versions? */