diff options
Diffstat (limited to 'sys/dev/etherswitch')
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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + + 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, ®); + + 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 (ðerswitch_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 = ðerswitch_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? */ |