/*- * Copyright (c) 2014 Ruslan Bukin * * This software was developed by SRI International and the University of * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) * ("CTSRD"), as part of the DARPA CRASH research programme. * * 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. */ /* * Ethernet media access controller (EMAC) * Chapter 17, Altera Cyclone V Device Handbook (CV-5V2 2014.07.22) * * EMAC is an instance of the Synopsys DesignWare 3504-0 * Universal 10/100/1000 Ethernet MAC (DWC_gmac). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "if_dwc_if.h" #include "gpio_if.h" #include "miibus_if.h" static struct resource_spec dwc_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; static void dwc_stop_locked(struct dwc_softc *sc); static void dwc_tick(void *arg); /* * Media functions */ static void dwc_media_status(if_t ifp, struct ifmediareq *ifmr) { struct dwc_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = sc->mii_softc; DWC_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; DWC_UNLOCK(sc); } static int dwc_media_change_locked(struct dwc_softc *sc) { return (mii_mediachg(sc->mii_softc)); } static int dwc_media_change(if_t ifp) { struct dwc_softc *sc; int error; sc = if_getsoftc(ifp); DWC_LOCK(sc); error = dwc_media_change_locked(sc); DWC_UNLOCK(sc); return (error); } /* * if_ functions */ static void dwc_txstart_locked(struct dwc_softc *sc) { if_t ifp; DWC_ASSERT_LOCKED(sc); if (!sc->link_is_up) return; ifp = sc->ifp; if ((if_getdrvflags(ifp) & (IFF_DRV_RUNNING|IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING) return; dma1000_txstart(sc); } static void dwc_txstart(if_t ifp) { struct dwc_softc *sc = if_getsoftc(ifp); DWC_LOCK(sc); dwc_txstart_locked(sc); DWC_UNLOCK(sc); } static void dwc_init_locked(struct dwc_softc *sc) { if_t ifp = sc->ifp; DWC_ASSERT_LOCKED(sc); if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) return; /* * Call mii_mediachg() which will call back into dwc1000_miibus_statchg() * to set up the remaining config registers based on current media. */ mii_mediachg(sc->mii_softc); dwc1000_setup_rxfilter(sc); dwc1000_core_setup(sc); dwc1000_enable_mac(sc, true); dwc1000_enable_csum_offload(sc); dma1000_start(sc); if_setdrvflagbits(ifp, IFF_DRV_RUNNING, IFF_DRV_OACTIVE); callout_reset(&sc->dwc_callout, hz, dwc_tick, sc); } static void dwc_init(void *if_softc) { struct dwc_softc *sc = if_softc; DWC_LOCK(sc); dwc_init_locked(sc); DWC_UNLOCK(sc); } static void dwc_stop_locked(struct dwc_softc *sc) { if_t ifp; DWC_ASSERT_LOCKED(sc); ifp = sc->ifp; if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING | IFF_DRV_OACTIVE); sc->tx_watchdog_count = 0; sc->stats_harvest_count = 0; callout_stop(&sc->dwc_callout); dma1000_stop(sc); dwc1000_enable_mac(sc, false); } static int dwc_ioctl(if_t ifp, u_long cmd, caddr_t data) { struct dwc_softc *sc; struct mii_data *mii; struct ifreq *ifr; int flags, mask, error; sc = if_getsoftc(ifp); ifr = (struct ifreq *)data; error = 0; switch (cmd) { case SIOCSIFFLAGS: DWC_LOCK(sc); if (if_getflags(ifp) & IFF_UP) { if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { flags = if_getflags(ifp) ^ sc->if_flags; if ((flags & (IFF_PROMISC|IFF_ALLMULTI)) != 0) dwc1000_setup_rxfilter(sc); } else { if (!sc->is_detaching) dwc_init_locked(sc); } } else { if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) dwc_stop_locked(sc); } sc->if_flags = if_getflags(ifp); DWC_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { DWC_LOCK(sc); dwc1000_setup_rxfilter(sc); DWC_UNLOCK(sc); } break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: mii = sc->mii_softc; error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); break; case SIOCSIFCAP: mask = ifr->ifr_reqcap ^ if_getcapenable(ifp); if (mask & IFCAP_VLAN_MTU) { /* No work to do except acknowledge the change took */ if_togglecapenable(ifp, IFCAP_VLAN_MTU); } if (mask & IFCAP_RXCSUM) if_togglecapenable(ifp, IFCAP_RXCSUM); if (mask & IFCAP_TXCSUM) if_togglecapenable(ifp, IFCAP_TXCSUM); if ((if_getcapenable(ifp) & IFCAP_TXCSUM) != 0) if_sethwassistbits(ifp, CSUM_IP | CSUM_UDP | CSUM_TCP, 0); else if_sethwassistbits(ifp, 0, CSUM_IP | CSUM_UDP | CSUM_TCP); if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { DWC_LOCK(sc); dwc1000_enable_csum_offload(sc); DWC_UNLOCK(sc); } break; default: error = ether_ioctl(ifp, cmd, data); break; } return (error); } /* * Interrupts functions */ static void dwc_intr(void *arg) { struct dwc_softc *sc; int rv; sc = arg; DWC_LOCK(sc); dwc1000_intr(sc); rv = dma1000_intr(sc); if (rv == EIO) { device_printf(sc->dev, "Ethernet DMA error, restarting controller.\n"); dwc_stop_locked(sc); dwc_init_locked(sc); } DWC_UNLOCK(sc); } static void dwc_tick(void *arg) { struct dwc_softc *sc; if_t ifp; int link_was_up; sc = arg; DWC_ASSERT_LOCKED(sc); ifp = sc->ifp; if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0) return; /* * Typical tx watchdog. If this fires it indicates that we enqueued * packets for output and never got a txdone interrupt for them. Maybe * it's a missed interrupt somehow, just pretend we got one. */ if (sc->tx_watchdog_count > 0) { if (--sc->tx_watchdog_count == 0) { dma1000_txfinish_locked(sc); } } /* Gather stats from hardware counters. */ dwc1000_harvest_stats(sc); /* Check the media status. */ link_was_up = sc->link_is_up; mii_tick(sc->mii_softc); if (sc->link_is_up && !link_was_up) dwc_txstart_locked(sc); /* Schedule another check one second from now. */ callout_reset(&sc->dwc_callout, hz, dwc_tick, sc); } static int dwc_reset_phy(struct dwc_softc *sc) { pcell_t gpio_prop[4]; pcell_t delay_prop[3]; phandle_t gpio_node; device_t gpio; uint32_t pin, flags; uint32_t pin_value; /* * All those properties are deprecated but still used in some DTS. * The new way to deal with this is to use the generic bindings * present in the ethernet-phy node. */ if (OF_getencprop(sc->node, "snps,reset-gpio", gpio_prop, sizeof(gpio_prop)) <= 0) return (0); if (OF_getencprop(sc->node, "snps,reset-delays-us", delay_prop, sizeof(delay_prop)) <= 0) { device_printf(sc->dev, "Wrong property for snps,reset-delays-us"); return (ENXIO); } gpio_node = OF_node_from_xref(gpio_prop[0]); if ((gpio = OF_device_from_xref(gpio_prop[0])) == NULL) { device_printf(sc->dev, "Can't find gpio controller for phy reset\n"); return (ENXIO); } if (GPIO_MAP_GPIOS(gpio, sc->node, gpio_node, nitems(gpio_prop) - 1, gpio_prop + 1, &pin, &flags) != 0) { device_printf(sc->dev, "Can't map gpio for phy reset\n"); return (ENXIO); } pin_value = GPIO_PIN_LOW; if (OF_hasprop(sc->node, "snps,reset-active-low")) pin_value = GPIO_PIN_HIGH; GPIO_PIN_SETFLAGS(gpio, pin, GPIO_PIN_OUTPUT); GPIO_PIN_SET(gpio, pin, pin_value); DELAY(delay_prop[0] * 5); GPIO_PIN_SET(gpio, pin, !pin_value); DELAY(delay_prop[1] * 5); GPIO_PIN_SET(gpio, pin, pin_value); DELAY(delay_prop[2] * 5); return (0); } static int dwc_clock_init(struct dwc_softc *sc) { int rv; int64_t freq; /* Required clock */ rv = clk_get_by_ofw_name(sc->dev, 0, "stmmaceth", &sc->clk_stmmaceth); if (rv != 0) { device_printf(sc->dev, "Cannot get GMAC main clock\n"); return (ENXIO); } if ((rv = clk_enable(sc->clk_stmmaceth)) != 0) { device_printf(sc->dev, "could not enable main clock\n"); return (rv); } /* Optional clock */ rv = clk_get_by_ofw_name(sc->dev, 0, "pclk", &sc->clk_pclk); if (rv != 0) return (0); if ((rv = clk_enable(sc->clk_pclk)) != 0) { device_printf(sc->dev, "could not enable peripheral clock\n"); return (rv); } if (bootverbose) { clk_get_freq(sc->clk_stmmaceth, &freq); device_printf(sc->dev, "MAC clock(%s) freq: %jd\n", clk_get_name(sc->clk_stmmaceth), (intmax_t)freq); } return (0); } static int dwc_reset_deassert(struct dwc_softc *sc) { int rv; /* Required reset */ rv = hwreset_get_by_ofw_name(sc->dev, 0, "stmmaceth", &sc->rst_stmmaceth); if (rv != 0) { device_printf(sc->dev, "Cannot get GMAC reset\n"); return (ENXIO); } rv = hwreset_deassert(sc->rst_stmmaceth); if (rv != 0) { device_printf(sc->dev, "could not de-assert GMAC reset\n"); return (rv); } /* Optional reset */ rv = hwreset_get_by_ofw_name(sc->dev, 0, "ahb", &sc->rst_ahb); if (rv != 0) return (0); rv = hwreset_deassert(sc->rst_ahb); if (rv != 0) { device_printf(sc->dev, "could not de-assert AHB reset\n"); return (rv); } return (0); } /* * Probe/Attach functions */ static int dwc_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "snps,dwmac")) return (ENXIO); device_set_desc(dev, "Gigabit Ethernet Controller"); return (BUS_PROBE_DEFAULT); } static int dwc_attach(device_t dev) { uint8_t macaddr[ETHER_ADDR_LEN]; struct dwc_softc *sc; if_t ifp; int error; uint32_t pbl; sc = device_get_softc(dev); sc->dev = dev; sc->rx_idx = 0; sc->tx_desccount = TX_DESC_COUNT; sc->tx_mapcount = 0; sc->node = ofw_bus_get_node(dev); sc->phy_mode = mii_fdt_get_contype(sc->node); switch (sc->phy_mode) { case MII_CONTYPE_RGMII: case MII_CONTYPE_RGMII_ID: case MII_CONTYPE_RGMII_RXID: case MII_CONTYPE_RGMII_TXID: case MII_CONTYPE_RMII: case MII_CONTYPE_MII: break; default: device_printf(dev, "Unsupported MII type\n"); return (ENXIO); } if (OF_getencprop(sc->node, "snps,pbl", &pbl, sizeof(uint32_t)) <= 0) pbl = DMA_DEFAULT_PBL; if (OF_getencprop(sc->node, "snps,txpbl", &sc->txpbl, sizeof(uint32_t)) <= 0) sc->txpbl = pbl; if (OF_getencprop(sc->node, "snps,rxpbl", &sc->rxpbl, sizeof(uint32_t)) <= 0) sc->rxpbl = pbl; if (OF_hasprop(sc->node, "snps,no-pbl-x8") == 1) sc->nopblx8 = true; if (OF_hasprop(sc->node, "snps,fixed-burst") == 1) sc->fixed_burst = true; if (OF_hasprop(sc->node, "snps,mixed-burst") == 1) sc->mixed_burst = true; if (OF_hasprop(sc->node, "snps,aal") == 1) sc->aal = true; error = clk_set_assigned(dev, ofw_bus_get_node(dev)); if (error != 0) { device_printf(dev, "clk_set_assigned failed\n"); return (error); } /* Enable main clock */ if ((error = dwc_clock_init(sc)) != 0) return (error); /* De-assert main reset */ if ((error = dwc_reset_deassert(sc)) != 0) return (error); if (IF_DWC_INIT(dev) != 0) return (ENXIO); if ((sc->mii_clk = IF_DWC_MII_CLK(dev)) < 0) { device_printf(dev, "Cannot get mii clock value %d\n", -sc->mii_clk); return (ENXIO); } if (bus_alloc_resources(dev, dwc_spec, sc->res)) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } /* Read MAC before reset */ dwc1000_get_hwaddr(sc, macaddr); /* Reset the PHY if needed */ if (dwc_reset_phy(sc) != 0) { device_printf(dev, "Can't reset the PHY\n"); bus_release_resources(dev, dwc_spec, sc->res); return (ENXIO); } /* Reset */ if ((error = dma1000_reset(sc)) != 0) { device_printf(sc->dev, "Can't reset DMA controller.\n"); bus_release_resources(sc->dev, dwc_spec, sc->res); return (error); } if (dma1000_init(sc)) { bus_release_resources(dev, dwc_spec, sc->res); return (ENXIO); } mtx_init(&sc->mtx, device_get_nameunit(sc->dev), MTX_NETWORK_LOCK, MTX_DEF); callout_init_mtx(&sc->dwc_callout, &sc->mtx, 0); /* Setup interrupt handler. */ error = bus_setup_intr(dev, sc->res[1], INTR_TYPE_NET | INTR_MPSAFE, NULL, dwc_intr, sc, &sc->intr_cookie); if (error != 0) { device_printf(dev, "could not setup interrupt handler.\n"); bus_release_resources(dev, dwc_spec, sc->res); return (ENXIO); } /* Set up the ethernet interface. */ sc->ifp = ifp = if_alloc(IFT_ETHER); if_setsoftc(ifp, sc); if_initname(ifp, device_get_name(dev), device_get_unit(dev)); if_setflags(sc->ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST); if_setstartfn(ifp, dwc_txstart); if_setioctlfn(ifp, dwc_ioctl); if_setinitfn(ifp, dwc_init); if_setsendqlen(ifp, TX_MAP_COUNT - 1); if_setsendqready(sc->ifp); if_sethwassist(sc->ifp, CSUM_IP | CSUM_UDP | CSUM_TCP); if_setcapabilities(sc->ifp, IFCAP_VLAN_MTU | IFCAP_HWCSUM); if_setcapenable(sc->ifp, if_getcapabilities(sc->ifp)); /* Attach the mii driver. */ error = mii_attach(dev, &sc->miibus, ifp, dwc_media_change, dwc_media_status, BMSR_DEFCAPMASK, MII_PHY_ANY, MII_OFFSET_ANY, 0); if (error != 0) { device_printf(dev, "PHY attach failed\n"); bus_teardown_intr(dev, sc->res[1], sc->intr_cookie); bus_release_resources(dev, dwc_spec, sc->res); return (ENXIO); } sc->mii_softc = device_get_softc(sc->miibus); /* All ready to run, attach the ethernet interface. */ ether_ifattach(ifp, macaddr); sc->is_attached = true; return (0); } static int dwc_detach(device_t dev) { struct dwc_softc *sc; sc = device_get_softc(dev); /* * Disable and tear down interrupts before anything else, so we don't * race with the handler. */ dwc1000_intr_disable(sc); if (sc->intr_cookie != NULL) { bus_teardown_intr(dev, sc->res[1], sc->intr_cookie); } if (sc->is_attached) { DWC_LOCK(sc); sc->is_detaching = true; dwc_stop_locked(sc); DWC_UNLOCK(sc); callout_drain(&sc->dwc_callout); ether_ifdetach(sc->ifp); } if (sc->miibus != NULL) { device_delete_child(dev, sc->miibus); sc->miibus = NULL; } bus_generic_detach(dev); /* Free DMA descriptors */ dma1000_free(sc); if (sc->ifp != NULL) { if_free(sc->ifp); sc->ifp = NULL; } bus_release_resources(dev, dwc_spec, sc->res); mtx_destroy(&sc->mtx); return (0); } static device_method_t dwc_methods[] = { DEVMETHOD(device_probe, dwc_probe), DEVMETHOD(device_attach, dwc_attach), DEVMETHOD(device_detach, dwc_detach), /* MII Interface */ DEVMETHOD(miibus_readreg, dwc1000_miibus_read_reg), DEVMETHOD(miibus_writereg, dwc1000_miibus_write_reg), DEVMETHOD(miibus_statchg, dwc1000_miibus_statchg), { 0, 0 } }; driver_t dwc_driver = { "dwc", dwc_methods, sizeof(struct dwc_softc), }; DRIVER_MODULE(dwc, simplebus, dwc_driver, 0, 0); DRIVER_MODULE(miibus, dwc, miibus_driver, 0, 0); MODULE_DEPEND(dwc, ether, 1, 1, 1); MODULE_DEPEND(dwc, miibus, 1, 1, 1);