Index: sys/dev/mge/if_mge.c =================================================================== --- sys/dev/mge/if_mge.c +++ sys/dev/mge/if_mge.c @@ -1,5 +1,6 @@ /*- * Copyright (C) 2008 MARVELL INTERNATIONAL LTD. + * Copyright (C) 2009-2015 Semihalf. * All rights reserved. * * Developed by Semihalf. @@ -50,7 +51,6 @@ #include #include #include -#include #include #include #include @@ -73,12 +73,16 @@ #include #include #include +#include #include #include #include #include "miibus_if.h" +#include "mdio_if.h" + +#define MGE_DELAY(x) pause("SMI access sleep", (x) / tick_sbt) static int mge_probe(device_t dev); static int mge_attach(device_t dev); @@ -90,6 +94,9 @@ static int mge_miibus_readreg(device_t dev, int phy, int reg); static int mge_miibus_writereg(device_t dev, int phy, int reg, int value); +static int mge_mdio_readreg(device_t dev, int phy, int reg); +static int mge_mdio_writereg(device_t dev, int phy, int reg, int value); + static int mge_ifmedia_upd(struct ifnet *ifp); static void mge_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr); @@ -154,21 +161,23 @@ /* MII interface */ DEVMETHOD(miibus_readreg, mge_miibus_readreg), DEVMETHOD(miibus_writereg, mge_miibus_writereg), + /* MDIO interface */ + DEVMETHOD(mdio_readreg, mge_mdio_readreg), + DEVMETHOD(mdio_writereg, mge_mdio_writereg), { 0, 0 } }; -static driver_t mge_driver = { - "mge", - mge_methods, - sizeof(struct mge_softc), -}; +DEFINE_CLASS_0(mge, mge_driver, mge_methods, sizeof(struct mge_softc)); static devclass_t mge_devclass; +static int switch_attached = 0; DRIVER_MODULE(mge, simplebus, mge_driver, mge_devclass, 0, 0); DRIVER_MODULE(miibus, mge, miibus_driver, miibus_devclass, 0, 0); +DRIVER_MODULE(mdio, mge, mdio_driver, mdio_devclass, 0, 0); MODULE_DEPEND(mge, ether, 1, 1, 1); MODULE_DEPEND(mge, miibus, 1, 1, 1); +MODULE_DEPEND(mge, mdio, 1, 1, 1); static struct resource_spec res_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, @@ -190,6 +199,133 @@ { mge_intr_err, "GbE error interrupt" }, }; +/* SMI access interlock */ +static struct sx sx_smi; + +static uint32_t +mv_read_ge_smi(device_t dev, int phy, int reg) +{ + uint32_t timeout; + uint32_t ret; + struct mge_softc *sc; + + sc = device_get_softc(dev); + KASSERT(sc != NULL, ("NULL softc ptr!")); + timeout = MGE_SMI_WRITE_RETRIES; + + MGE_SMI_LOCK(); + while (--timeout && + (MGE_READ(sc, MGE_REG_SMI) & MGE_SMI_BUSY)) + MGE_DELAY(MGE_SMI_WRITE_DELAY); + + if (timeout == 0) { + device_printf(dev, "SMI write timeout.\n"); + ret = ~0U; + goto out; + } + + MGE_WRITE(sc, MGE_REG_SMI, MGE_SMI_MASK & + (MGE_SMI_READ | (reg << 21) | (phy << 16))); + + /* Wait till finished. */ + timeout = MGE_SMI_WRITE_RETRIES; + while (--timeout && + !((MGE_READ(sc, MGE_REG_SMI) & MGE_SMI_READVALID))) + MGE_DELAY(MGE_SMI_WRITE_DELAY); + + if (timeout == 0) { + device_printf(dev, "SMI write validation timeout.\n"); + ret = ~0U; + goto out; + } + + /* Wait for the data to update in the SMI register */ + MGE_DELAY(MGE_SMI_DELAY); + ret = MGE_READ(sc, MGE_REG_SMI) & MGE_SMI_DATA_MASK; + +out: + MGE_SMI_UNLOCK(); + return (ret); + +} + +static void +mv_write_ge_smi(device_t dev, int phy, int reg, uint32_t value) +{ + uint32_t timeout; + struct mge_softc *sc; + + sc = device_get_softc(dev); + KASSERT(sc != NULL, ("NULL softc ptr!")); + + MGE_SMI_LOCK(); + timeout = MGE_SMI_READ_RETRIES; + while (--timeout && + (MGE_READ(sc, MGE_REG_SMI) & MGE_SMI_BUSY)) + MGE_DELAY(MGE_SMI_READ_DELAY); + + if (timeout == 0) { + device_printf(dev, "SMI read timeout.\n"); + goto out; + } + + MGE_WRITE(sc, MGE_REG_SMI, MGE_SMI_MASK & + (MGE_SMI_WRITE | (reg << 21) | (phy << 16) | + (value & MGE_SMI_DATA_MASK))); + +out: + MGE_SMI_UNLOCK(); +} + +static int +mv_read_ext_phy(device_t dev, int phy, int reg) +{ + uint32_t retries; + struct mge_softc *sc; + uint32_t ret; + + sc = device_get_softc(dev); + + MGE_SMI_LOCK(); + MGE_WRITE(sc->phy_sc, MGE_REG_SMI, MGE_SMI_MASK & + (MGE_SMI_READ | (reg << 21) | (phy << 16))); + + retries = MGE_SMI_READ_RETRIES; + while (--retries && + !(MGE_READ(sc->phy_sc, MGE_REG_SMI) & MGE_SMI_READVALID)) + DELAY(MGE_SMI_READ_DELAY); + + if (retries == 0) + device_printf(dev, "Timeout while reading from PHY\n"); + + ret = MGE_READ(sc->phy_sc, MGE_REG_SMI) & MGE_SMI_DATA_MASK; + MGE_SMI_UNLOCK(); + + return (ret); +} + +static void +mv_write_ext_phy(device_t dev, int phy, int reg, int value) +{ + uint32_t retries; + struct mge_softc *sc; + + sc = device_get_softc(dev); + + MGE_SMI_LOCK(); + MGE_WRITE(sc->phy_sc, MGE_REG_SMI, MGE_SMI_MASK & + (MGE_SMI_WRITE | (reg << 21) | (phy << 16) | + (value & MGE_SMI_DATA_MASK))); + + retries = MGE_SMI_WRITE_RETRIES; + while (--retries && MGE_READ(sc->phy_sc, MGE_REG_SMI) & MGE_SMI_BUSY) + DELAY(MGE_SMI_WRITE_DELAY); + + if (retries == 0) + device_printf(dev, "Timeout while writing to PHY\n"); + MGE_SMI_UNLOCK(); +} + static void mge_get_mac_address(struct mge_softc *sc, uint8_t *addr) { @@ -605,10 +741,10 @@ uint32_t int_cause, int_cause_ext; int rx_npkts = 0; - MGE_GLOBAL_LOCK(sc); + MGE_RECEIVE_LOCK(sc); if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { - MGE_GLOBAL_UNLOCK(sc); + MGE_RECEIVE_UNLOCK(sc); return (rx_npkts); } @@ -626,10 +762,13 @@ } } - mge_intr_tx_locked(sc); + rx_npkts = mge_intr_rx_locked(sc, count); - MGE_GLOBAL_UNLOCK(sc); + MGE_RECEIVE_UNLOCK(sc); + MGE_TRANSMIT_LOCK(sc); + mge_intr_tx_locked(sc); + MGE_TRANSMIT_UNLOCK(sc); return (rx_npkts); } #endif /* DEVICE_POLLING */ @@ -646,17 +785,39 @@ sc = device_get_softc(dev); sc->dev = dev; sc->node = ofw_bus_get_node(dev); + phy = 0; + + if (fdt_get_phyaddr(sc->node, sc->dev, &phy, (void **)&sc->phy_sc) == 0) { + device_printf(dev, "PHY%i attached, phy_sc points to %s\n", phy, + device_get_nameunit(sc->phy_sc->dev)); + sc->phy_attached = 1; + } else { + device_printf(dev, "PHY not attached.\n"); + sc->phy_attached = 0; + sc->phy_sc = sc; + } + + if (fdt_find_compatible(sc->node, "mrvl,sw", 1) != 0) { + device_printf(dev, "Switch attached.\n"); + sc->switch_attached = 1; + /* additional variable available across instances */ + switch_attached = 1; + } else { + sc->switch_attached = 0; + } + + if (device_get_unit(dev) == 0) { + sx_init(&sx_smi, "mge_tick() SMI access threads interlock"); + } /* Set chip version-dependent parameters */ mge_ver_params(sc); - /* Get phy address and used softc from fdt */ - if (fdt_get_phyaddr(sc->node, sc->dev, &phy, (void **)&sc->phy_sc) != 0) - return (ENXIO); - /* Initialize mutexes */ - mtx_init(&sc->transmit_lock, device_get_nameunit(dev), "mge TX lock", MTX_DEF); - mtx_init(&sc->receive_lock, device_get_nameunit(dev), "mge RX lock", MTX_DEF); + mtx_init(&sc->transmit_lock, device_get_nameunit(dev), "mge TX lock", + MTX_DEF); + mtx_init(&sc->receive_lock, device_get_nameunit(dev), "mge RX lock", + MTX_DEF); /* Allocate IO and IRQ resources */ error = bus_alloc_resources(dev, res_spec, sc->res); @@ -719,18 +880,32 @@ callout_init(&sc->wd_callout, 0); /* Attach PHY(s) */ - error = mii_attach(dev, &sc->miibus, ifp, mge_ifmedia_upd, - mge_ifmedia_sts, BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0); - if (error) { - device_printf(dev, "attaching PHYs failed\n"); - mge_detach(dev); - return (error); - } - sc->mii = device_get_softc(sc->miibus); + if (sc->phy_attached) { + error = mii_attach(dev, &sc->miibus, ifp, mge_ifmedia_upd, + mge_ifmedia_sts, BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0); + if (error) { + device_printf(dev, "MII failed to find PHY\n"); + if_free(ifp); + sc->ifp = NULL; + mge_detach(dev); + return (error); + } + sc->mii = device_get_softc(sc->miibus); - /* Tell the MAC where to find the PHY so autoneg works */ - miisc = LIST_FIRST(&sc->mii->mii_phys); - MGE_WRITE(sc, MGE_REG_PHYDEV, miisc->mii_phy); + /* Tell the MAC where to find the PHY so autoneg works */ + miisc = LIST_FIRST(&sc->mii->mii_phys); + MGE_WRITE(sc, MGE_REG_PHYDEV, miisc->mii_phy); + } else { + /* no PHY, so use hard-coded values */ + ifmedia_init(&sc->mge_ifmedia, 0, + mge_ifmedia_upd, + mge_ifmedia_sts); + ifmedia_add(&sc->mge_ifmedia, + IFM_ETHER | IFM_1000_T | IFM_FDX, + 0, NULL); + ifmedia_set(&sc->mge_ifmedia, + IFM_ETHER | IFM_1000_T | IFM_FDX); + } /* Attach interrupt handlers */ /* TODO: review flags, in part. mark RX as INTR_ENTROPY ? */ @@ -747,6 +922,13 @@ } } + if (sc->switch_attached) { + device_t child; + MGE_WRITE(sc, MGE_REG_PHYDEV, MGE_SWITCH_PHYDEV); + child = device_add_child(dev, "mdio", -1); + bus_generic_attach(dev); + } + return (0); } @@ -792,6 +974,9 @@ mtx_destroy(&sc->receive_lock); mtx_destroy(&sc->transmit_lock); + if (device_get_unit(dev) == 0) + sx_destroy(&sx_smi); + return (0); } @@ -801,7 +986,13 @@ struct mge_softc *sc = ifp->if_softc; struct mii_data *mii; - MGE_TRANSMIT_LOCK(sc); + MGE_GLOBAL_LOCK(sc); + + if (!sc->phy_attached) { + ifmr->ifm_active = IFM_1000_T | IFM_FDX | IFM_ETHER; + ifmr->ifm_status = IFM_AVALID | IFM_ACTIVE; + goto out_unlock; + } mii = sc->mii; mii_pollstat(mii); @@ -809,7 +1000,8 @@ ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; - MGE_TRANSMIT_UNLOCK(sc); +out_unlock: + MGE_GLOBAL_UNLOCK(sc); } static uint32_t @@ -826,13 +1018,13 @@ break; case IFM_1000_T: port_config |= (PORT_SERIAL_GMII_SPEED_1000 | - PORT_SERIAL_AUTONEG | PORT_SERIAL_AUTONEG_FC | - PORT_SERIAL_SPEED_AUTONEG); + PORT_SERIAL_AUTONEG | PORT_SERIAL_AUTONEG_FC + | PORT_SERIAL_SPEED_AUTONEG); break; case IFM_100_TX: port_config |= (PORT_SERIAL_MII_SPEED_100 | - PORT_SERIAL_AUTONEG | PORT_SERIAL_AUTONEG_FC | - PORT_SERIAL_SPEED_AUTONEG); + PORT_SERIAL_AUTONEG | PORT_SERIAL_AUTONEG_FC + | PORT_SERIAL_SPEED_AUTONEG); break; case IFM_10_T: port_config |= (PORT_SERIAL_AUTONEG | @@ -851,13 +1043,21 @@ { struct mge_softc *sc = ifp->if_softc; - if (ifp->if_flags & IFF_UP) { + /* + * Do not do anything for switch here, as updating media between + * MGE MAC and switch MAC is hardcoded in PCB. Changing it here would + * break the link. + */ + if (sc->phy_attached) { MGE_GLOBAL_LOCK(sc); + if (ifp->if_flags & IFF_UP) { + sc->mge_media_status = sc->mii->mii_media.ifm_media; + mii_mediachg(sc->mii); - sc->mge_media_status = sc->mii->mii_media.ifm_media; - mii_mediachg(sc->mii); - mge_init_locked(sc); + /* MGE MAC needs to be reinitialized. */ + mge_init_locked(sc); + } MGE_GLOBAL_UNLOCK(sc); } @@ -883,6 +1083,7 @@ struct mge_desc_wrapper *dw; volatile uint32_t reg_val; int i, count; + uint32_t media_status; MGE_GLOBAL_LOCK_ASSERT(sc); @@ -925,8 +1126,17 @@ PORT_CONFIG_ARO_RXQ(0)); MGE_WRITE(sc, MGE_PORT_EXT_CONFIG , 0x0); + /* Configure promisc mode */ + mge_set_prom_mode(sc, MGE_RX_DEFAULT_QUEUE); + + media_status = sc->mge_media_status; + if (sc->switch_attached) { + media_status &= ~IFM_TMASK; + media_status |= IFM_1000_T; + } + /* Setup port configuration */ - reg_val = mge_set_port_serial_control(sc->mge_media_status); + reg_val = mge_set_port_serial_control(media_status); MGE_WRITE(sc, MGE_PORT_SERIAL_CTRL, reg_val); /* Setup SDMA configuration */ @@ -997,7 +1207,8 @@ sc->wd_timer = 0; /* Schedule watchdog timeout */ - callout_reset(&sc->wd_callout, hz, mge_tick, sc); + if (sc->phy_attached) + callout_reset(&sc->wd_callout, hz, mge_tick, sc); } static void @@ -1331,6 +1542,18 @@ break; case SIOCGIFMEDIA: /* fall through */ case SIOCSIFMEDIA: + /* + * Setting up media type via ioctls is *not* supported for MAC + * which is connected to switch. Use etherswitchcfg. + */ + if (!sc->phy_attached && (command == SIOCSIFMEDIA)) + return (0); + else if (!sc->phy_attached) { + error = ifmedia_ioctl(ifp, ifr, &sc->mge_ifmedia, + command); + break; + } + if (IFM_SUBTYPE(ifr->ifr_media) == IFM_1000_T && !(ifr->ifr_media & IFM_FDX)) { device_printf(sc->dev, @@ -1349,41 +1572,23 @@ mge_miibus_readreg(device_t dev, int phy, int reg) { struct mge_softc *sc; - uint32_t retries; - sc = device_get_softc(dev); - MGE_WRITE(sc->phy_sc, MGE_REG_SMI, 0x1fffffff & - (MGE_SMI_READ | (reg << 21) | (phy << 16))); - - retries = MGE_SMI_READ_RETRIES; - while (--retries && - !(MGE_READ(sc->phy_sc, MGE_REG_SMI) & MGE_SMI_READVALID)) - DELAY(MGE_SMI_READ_DELAY); + KASSERT(!switch_attached, ("miibus used with switch attached")); - if (retries == 0) - device_printf(dev, "Timeout while reading from PHY\n"); - - return (MGE_READ(sc->phy_sc, MGE_REG_SMI) & 0xffff); + return (mv_read_ext_phy(dev, phy, reg)); } static int mge_miibus_writereg(device_t dev, int phy, int reg, int value) { struct mge_softc *sc; - uint32_t retries; - sc = device_get_softc(dev); - MGE_WRITE(sc->phy_sc, MGE_REG_SMI, 0x1fffffff & - (MGE_SMI_WRITE | (reg << 21) | (phy << 16) | (value & 0xffff))); + KASSERT(!switch_attached, ("miibus used with switch attached")); - retries = MGE_SMI_WRITE_RETRIES; - while (--retries && MGE_READ(sc->phy_sc, MGE_REG_SMI) & MGE_SMI_BUSY) - DELAY(MGE_SMI_WRITE_DELAY); + mv_write_ext_phy(dev, phy, reg, value); - if (retries == 0) - device_printf(dev, "Timeout while writing to PHY\n"); return (0); } @@ -1489,6 +1694,10 @@ { struct mge_softc *sc = msc; + KASSERT(sc->phy_attached == 1, ("mge_tick while PHY not attached")); + + MGE_GLOBAL_LOCK(sc); + /* Check for TX timeout */ mge_watchdog(sc); @@ -1498,8 +1707,12 @@ if(sc->mge_media_status != sc->mii->mii_media.ifm_media) mge_ifmedia_upd(sc->ifp); + MGE_GLOBAL_UNLOCK(sc); + /* Schedule another timeout one second from now */ callout_reset(&sc->wd_callout, hz, mge_tick, sc); + + return; } static void @@ -1509,10 +1722,7 @@ ifp = sc->ifp; - MGE_GLOBAL_LOCK(sc); - if (sc->wd_timer == 0 || --sc->wd_timer) { - MGE_GLOBAL_UNLOCK(sc); return; } @@ -1521,8 +1731,6 @@ mge_stop(sc); mge_init_locked(sc); - - MGE_GLOBAL_UNLOCK(sc); } static void @@ -1711,7 +1919,7 @@ struct ip *ip; int ehlen, etype; - if (csum_flags) { + if (csum_flags != 0) { if (eh->evl_encap_proto == htons(ETHERTYPE_VLAN)) { etype = ntohs(eh->evl_proto); ehlen = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN; @@ -1927,3 +2135,23 @@ CTLTYPE_UINT | CTLFLAG_RW, sc, MGE_IC_TX, mge_sysctl_ic, "I", "IC TX time threshold"); } + +static int +mge_mdio_writereg(device_t dev, int phy, int reg, int value) +{ + + mv_write_ge_smi(dev, phy, reg, value); + + return (0); +} + + +static int +mge_mdio_readreg(device_t dev, int phy, int reg) +{ + int ret; + + ret = mv_read_ge_smi(dev, phy, reg); + + return (ret); +} Index: sys/dev/mge/if_mgevar.h =================================================================== --- sys/dev/mge/if_mgevar.h +++ sys/dev/mge/if_mgevar.h @@ -34,6 +34,8 @@ #ifndef __IF_MGE_H__ #define __IF_MGE_H__ +#include + #define MGE_INTR_COUNT 5 /* ETH controller occupies 5 IRQ lines */ #define MGE_TX_DESC_NUM 256 #define MGE_RX_DESC_NUM 256 @@ -71,6 +73,7 @@ device_t miibus; struct mii_data *mii; + struct ifmedia mge_ifmedia; struct resource *res[1 + MGE_INTR_COUNT]; /* resources */ void *ih_cookie[MGE_INTR_COUNT]; /* interrupt handlers cookies */ struct mtx transmit_lock; /* transmitter lock */ @@ -106,6 +109,8 @@ int mge_intr_cnt; uint8_t mge_hw_csum; + int phy_attached; + int switch_attached; struct mge_softc *phy_sc; }; @@ -150,6 +155,14 @@ MGE_RECEIVE_LOCK_ASSERT(sc); \ } while (0) +#define MGE_SMI_LOCK() do { \ + sx_assert(&sx_smi, SA_UNLOCKED); \ + sx_xlock(&sx_smi); \ +} while (0) + +#define MGE_SMI_UNLOCK() sx_unlock(&sx_smi) +#define MGE_SMI_LOCK_ASSERT() sx_assert(&sx_smi, SA_XLOCKED) + /* SMI-related macros */ #define MGE_REG_PHYDEV 0x000 #define MGE_REG_SMI 0x004 @@ -158,6 +171,17 @@ #define MGE_SMI_READVALID (1 << 27) #define MGE_SMI_BUSY (1 << 28) +#define MGE_SMI_MASK 0x1fffffff +#define MGE_SMI_DATA_MASK 0xffff +#define MGE_SMI_DELAY 1000 + +#define MGE_SWITCH_PHYDEV 6 + +/* Internal Switch SMI Command */ + +#define SW_SMI_READ_CMD(phy, reg) ((1 << 15) | (1 << 12) | (1 << 11) | (phy << 5) | reg) +#define SW_SMI_WRITE_CMD(phy, reg) ((1 << 15) | (1 << 12) | (1 << 10) | (phy << 5) | reg) + /* TODO verify the timings and retries count w/specs */ #define MGE_SMI_READ_RETRIES 1000 #define MGE_SMI_READ_DELAY 100