/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2013 Ganbold Tsagaankhuu * 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. */ /* A10/A20 EMAC driver */ #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 #ifdef INET #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include "miibus_if.h" #include "gpio_if.h" #include "a10_sramc.h" struct emac_softc { if_t emac_ifp; device_t emac_dev; device_t emac_miibus; bus_space_handle_t emac_handle; bus_space_tag_t emac_tag; struct resource *emac_res; struct resource *emac_irq; void *emac_intrhand; clk_t emac_clk; int emac_if_flags; struct mtx emac_mtx; struct callout emac_tick_ch; int emac_watchdog_timer; int emac_rx_process_limit; int emac_link; uint32_t emac_fifo_mask; }; static int emac_probe(device_t); static int emac_attach(device_t); static int emac_detach(device_t); static int emac_shutdown(device_t); static int emac_suspend(device_t); static int emac_resume(device_t); static int emac_sys_setup(struct emac_softc *); static void emac_reset(struct emac_softc *); static void emac_init_locked(struct emac_softc *); static void emac_start_locked(if_t); static void emac_init(void *); static void emac_stop_locked(struct emac_softc *); static void emac_intr(void *); static int emac_ioctl(if_t, u_long, caddr_t); static void emac_rxeof(struct emac_softc *, int); static void emac_txeof(struct emac_softc *, uint32_t); static int emac_miibus_readreg(device_t, int, int); static int emac_miibus_writereg(device_t, int, int, int); static void emac_miibus_statchg(device_t); static int emac_ifmedia_upd(if_t); static void emac_ifmedia_sts(if_t, struct ifmediareq *); static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int, int); static int sysctl_hw_emac_proc_limit(SYSCTL_HANDLER_ARGS); #define EMAC_READ_REG(sc, reg) \ bus_space_read_4(sc->emac_tag, sc->emac_handle, reg) #define EMAC_WRITE_REG(sc, reg, val) \ bus_space_write_4(sc->emac_tag, sc->emac_handle, reg, val) static int emac_sys_setup(struct emac_softc *sc) { int error; /* Activate EMAC clock. */ error = clk_get_by_ofw_index(sc->emac_dev, 0, 0, &sc->emac_clk); if (error != 0) { device_printf(sc->emac_dev, "cannot get clock\n"); return (error); } error = clk_enable(sc->emac_clk); if (error != 0) { device_printf(sc->emac_dev, "cannot enable clock\n"); return (error); } /* Map sram. */ a10_map_to_emac(); return (0); } static void emac_get_hwaddr(struct emac_softc *sc, uint8_t *hwaddr) { uint32_t val0, val1, rnd; u_char rootkey[16]; size_t rootkey_size; /* * Try to get MAC address from running hardware. * If there is something non-zero there just use it. * * Otherwise set the address to a convenient locally assigned address, * using the SID rootkey. * This is was uboot does so we end up with the same mac as if uboot * did set it. * If we can't get the root key, generate a random one, * 'bsd' + random 24 low-order bits. 'b' is 0x62, which has the locally * assigned bit set, and the broadcast/multicast bit clear. */ val0 = EMAC_READ_REG(sc, EMAC_MAC_A0); val1 = EMAC_READ_REG(sc, EMAC_MAC_A1); if ((val0 | val1) != 0 && (val0 | val1) != 0xffffff) { hwaddr[0] = (val1 >> 16) & 0xff; hwaddr[1] = (val1 >> 8) & 0xff; hwaddr[2] = (val1 >> 0) & 0xff; hwaddr[3] = (val0 >> 16) & 0xff; hwaddr[4] = (val0 >> 8) & 0xff; hwaddr[5] = (val0 >> 0) & 0xff; } else { rootkey_size = sizeof(rootkey); if (aw_sid_get_fuse(AW_SID_FUSE_ROOTKEY, rootkey, &rootkey_size) == 0) { hwaddr[0] = 0x2; hwaddr[1] = rootkey[3]; hwaddr[2] = rootkey[12]; hwaddr[3] = rootkey[13]; hwaddr[4] = rootkey[14]; hwaddr[5] = rootkey[15]; } else { rnd = arc4random() & 0x00ffffff; hwaddr[0] = 'b'; hwaddr[1] = 's'; hwaddr[2] = 'd'; hwaddr[3] = (rnd >> 16) & 0xff; hwaddr[4] = (rnd >> 8) & 0xff; hwaddr[5] = (rnd >> 0) & 0xff; } } if (bootverbose) printf("MAC address: %s\n", ether_sprintf(hwaddr)); } static u_int emac_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { uint32_t h, *hashes = arg; h = ether_crc32_be(LLADDR(sdl), ETHER_ADDR_LEN) >> 26; hashes[h >> 5] |= 1 << (h & 0x1f); return (1); } static void emac_set_rx_mode(struct emac_softc *sc) { if_t ifp; uint32_t hashes[2]; uint32_t rcr = 0; EMAC_ASSERT_LOCKED(sc); ifp = sc->emac_ifp; rcr = EMAC_READ_REG(sc, EMAC_RX_CTL); /* Unicast packet and DA filtering */ rcr |= EMAC_RX_UCAD; rcr |= EMAC_RX_DAF; hashes[0] = 0; hashes[1] = 0; if (if_getflags(ifp) & IFF_ALLMULTI) { hashes[0] = 0xffffffff; hashes[1] = 0xffffffff; } else if_foreach_llmaddr(ifp, emac_hash_maddr, hashes); rcr |= EMAC_RX_MCO; rcr |= EMAC_RX_MHF; EMAC_WRITE_REG(sc, EMAC_RX_HASH0, hashes[0]); EMAC_WRITE_REG(sc, EMAC_RX_HASH1, hashes[1]); if (if_getflags(ifp) & IFF_BROADCAST) { rcr |= EMAC_RX_BCO; rcr |= EMAC_RX_MCO; } if (if_getflags(ifp) & IFF_PROMISC) rcr |= EMAC_RX_PA; else rcr |= EMAC_RX_UCAD; EMAC_WRITE_REG(sc, EMAC_RX_CTL, rcr); } static void emac_reset(struct emac_softc *sc) { EMAC_WRITE_REG(sc, EMAC_CTL, 0); DELAY(200); EMAC_WRITE_REG(sc, EMAC_CTL, 1); DELAY(200); } static void emac_drain_rxfifo(struct emac_softc *sc) { while (EMAC_READ_REG(sc, EMAC_RX_FBC) > 0) (void)EMAC_READ_REG(sc, EMAC_RX_IO_DATA); } static void emac_txeof(struct emac_softc *sc, uint32_t status) { if_t ifp; EMAC_ASSERT_LOCKED(sc); ifp = sc->emac_ifp; status &= (EMAC_TX_FIFO0 | EMAC_TX_FIFO1); sc->emac_fifo_mask &= ~status; if (status == (EMAC_TX_FIFO0 | EMAC_TX_FIFO1)) if_inc_counter(ifp, IFCOUNTER_OPACKETS, 2); else if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE); /* Unarm watchdog timer if no TX */ sc->emac_watchdog_timer = 0; } static void emac_rxeof(struct emac_softc *sc, int count) { if_t ifp; struct mbuf *m, *m0; uint32_t reg_val, rxcount; int16_t len; uint16_t status; int i; ifp = sc->emac_ifp; for (; count > 0 && (if_getdrvflags(ifp) & IFF_DRV_RUNNING) != 0; count--) { /* * Race warning: The first packet might arrive with * the interrupts disabled, but the second will fix */ rxcount = EMAC_READ_REG(sc, EMAC_RX_FBC); if (!rxcount) { /* Had one stuck? */ rxcount = EMAC_READ_REG(sc, EMAC_RX_FBC); if (!rxcount) return; } /* Check packet header */ reg_val = EMAC_READ_REG(sc, EMAC_RX_IO_DATA); if (reg_val != EMAC_PACKET_HEADER) { /* Packet header is wrong */ if (bootverbose) if_printf(ifp, "wrong packet header\n"); /* Disable RX */ reg_val = EMAC_READ_REG(sc, EMAC_CTL); reg_val &= ~EMAC_CTL_RX_EN; EMAC_WRITE_REG(sc, EMAC_CTL, reg_val); /* Flush RX FIFO */ reg_val = EMAC_READ_REG(sc, EMAC_RX_CTL); reg_val |= EMAC_RX_FLUSH_FIFO; EMAC_WRITE_REG(sc, EMAC_RX_CTL, reg_val); for (i = 100; i > 0; i--) { DELAY(100); if ((EMAC_READ_REG(sc, EMAC_RX_CTL) & EMAC_RX_FLUSH_FIFO) == 0) break; } if (i == 0) { device_printf(sc->emac_dev, "flush FIFO timeout\n"); /* Reinitialize controller */ emac_init_locked(sc); return; } /* Enable RX */ reg_val = EMAC_READ_REG(sc, EMAC_CTL); reg_val |= EMAC_CTL_RX_EN; EMAC_WRITE_REG(sc, EMAC_CTL, reg_val); return; } /* Get packet size and status */ reg_val = EMAC_READ_REG(sc, EMAC_RX_IO_DATA); len = reg_val & 0xffff; status = (reg_val >> 16) & 0xffff; if (len < 64 || (status & EMAC_PKT_OK) == 0) { if (bootverbose) if_printf(ifp, "bad packet: len = %i status = %i\n", len, status); if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); emac_drain_rxfifo(sc); continue; } #if 0 if (status & (EMAC_CRCERR | EMAC_LENERR)) { good_packet = 0; if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); if (status & EMAC_CRCERR) if_printf(ifp, "crc error\n"); if (status & EMAC_LENERR) if_printf(ifp, "length error\n"); } #endif m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) { emac_drain_rxfifo(sc); return; } m->m_len = m->m_pkthdr.len = MCLBYTES; /* Copy entire frame to mbuf first. */ bus_space_read_multi_4(sc->emac_tag, sc->emac_handle, EMAC_RX_IO_DATA, mtod(m, uint32_t *), roundup2(len, 4) / 4); m->m_pkthdr.rcvif = ifp; m->m_len = m->m_pkthdr.len = len - ETHER_CRC_LEN; /* * Emac controller needs strict alignment, so to avoid * copying over an entire frame to align, we allocate * a new mbuf and copy ethernet header + IP header to * the new mbuf. The new mbuf is prepended into the * existing mbuf chain. */ if (m->m_len <= (MHLEN - ETHER_HDR_LEN)) { bcopy(m->m_data, m->m_data + ETHER_HDR_LEN, m->m_len); m->m_data += ETHER_HDR_LEN; } else if (m->m_len <= (MCLBYTES - ETHER_HDR_LEN) && m->m_len > (MHLEN - ETHER_HDR_LEN)) { MGETHDR(m0, M_NOWAIT, MT_DATA); if (m0 != NULL) { len = ETHER_HDR_LEN + m->m_pkthdr.l2hlen; bcopy(m->m_data, m0->m_data, len); m->m_data += len; m->m_len -= len; m0->m_len = len; M_MOVE_PKTHDR(m0, m); m0->m_next = m; m = m0; } else { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); m_freem(m); m = NULL; continue; } } else if (m->m_len > EMAC_MAC_MAXF) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); m_freem(m); m = NULL; continue; } if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); EMAC_UNLOCK(sc); if_input(ifp, m); EMAC_LOCK(sc); } } static void emac_watchdog(struct emac_softc *sc) { if_t ifp; EMAC_ASSERT_LOCKED(sc); if (sc->emac_watchdog_timer == 0 || --sc->emac_watchdog_timer) return; ifp = sc->emac_ifp; if (sc->emac_link == 0) { if (bootverbose) if_printf(sc->emac_ifp, "watchdog timeout " "(missed link)\n"); } else if_printf(sc->emac_ifp, "watchdog timeout -- resetting\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING); emac_init_locked(sc); if (!if_sendq_empty(ifp)) emac_start_locked(ifp); } static void emac_tick(void *arg) { struct emac_softc *sc; struct mii_data *mii; sc = (struct emac_softc *)arg; mii = device_get_softc(sc->emac_miibus); mii_tick(mii); emac_watchdog(sc); callout_reset(&sc->emac_tick_ch, hz, emac_tick, sc); } static void emac_init(void *xcs) { struct emac_softc *sc; sc = (struct emac_softc *)xcs; EMAC_LOCK(sc); emac_init_locked(sc); EMAC_UNLOCK(sc); } static void emac_init_locked(struct emac_softc *sc) { if_t ifp; struct mii_data *mii; uint32_t reg_val; uint8_t *eaddr; EMAC_ASSERT_LOCKED(sc); ifp = sc->emac_ifp; if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) != 0) return; /* Flush RX FIFO */ reg_val = EMAC_READ_REG(sc, EMAC_RX_CTL); reg_val |= EMAC_RX_FLUSH_FIFO; EMAC_WRITE_REG(sc, EMAC_RX_CTL, reg_val); DELAY(1); /* Soft reset MAC */ reg_val = EMAC_READ_REG(sc, EMAC_MAC_CTL0); reg_val &= (~EMAC_MAC_CTL0_SOFT_RST); EMAC_WRITE_REG(sc, EMAC_MAC_CTL0, reg_val); /* Set MII clock */ reg_val = EMAC_READ_REG(sc, EMAC_MAC_MCFG); reg_val &= (~(0xf << 2)); reg_val |= (0xd << 2); EMAC_WRITE_REG(sc, EMAC_MAC_MCFG, reg_val); /* Clear RX counter */ EMAC_WRITE_REG(sc, EMAC_RX_FBC, 0); /* Disable all interrupt and clear interrupt status */ EMAC_WRITE_REG(sc, EMAC_INT_CTL, 0); reg_val = EMAC_READ_REG(sc, EMAC_INT_STA); EMAC_WRITE_REG(sc, EMAC_INT_STA, reg_val); DELAY(1); /* Set up TX */ reg_val = EMAC_READ_REG(sc, EMAC_TX_MODE); reg_val |= EMAC_TX_AB_M; reg_val &= EMAC_TX_TM; EMAC_WRITE_REG(sc, EMAC_TX_MODE, reg_val); /* Set up RX */ reg_val = EMAC_READ_REG(sc, EMAC_RX_CTL); reg_val |= EMAC_RX_SETUP; reg_val &= EMAC_RX_TM; EMAC_WRITE_REG(sc, EMAC_RX_CTL, reg_val); /* Set up MAC CTL0. */ reg_val = EMAC_READ_REG(sc, EMAC_MAC_CTL0); reg_val |= EMAC_MAC_CTL0_SETUP; EMAC_WRITE_REG(sc, EMAC_MAC_CTL0, reg_val); /* Set up MAC CTL1. */ reg_val = EMAC_READ_REG(sc, EMAC_MAC_CTL1); reg_val |= EMAC_MAC_CTL1_SETUP; EMAC_WRITE_REG(sc, EMAC_MAC_CTL1, reg_val); /* Set up IPGT */ EMAC_WRITE_REG(sc, EMAC_MAC_IPGT, EMAC_MAC_IPGT_FD); /* Set up IPGR */ EMAC_WRITE_REG(sc, EMAC_MAC_IPGR, EMAC_MAC_NBTB_IPG2 | (EMAC_MAC_NBTB_IPG1 << 8)); /* Set up Collison window */ EMAC_WRITE_REG(sc, EMAC_MAC_CLRT, EMAC_MAC_RM | (EMAC_MAC_CW << 8)); /* Set up Max Frame Length */ EMAC_WRITE_REG(sc, EMAC_MAC_MAXF, EMAC_MAC_MFL); /* Setup ethernet address */ eaddr = if_getlladdr(ifp); EMAC_WRITE_REG(sc, EMAC_MAC_A1, eaddr[0] << 16 | eaddr[1] << 8 | eaddr[2]); EMAC_WRITE_REG(sc, EMAC_MAC_A0, eaddr[3] << 16 | eaddr[4] << 8 | eaddr[5]); /* Setup rx filter */ emac_set_rx_mode(sc); /* Enable RX/TX0/RX Hlevel interrupt */ reg_val = EMAC_READ_REG(sc, EMAC_INT_CTL); reg_val |= EMAC_INT_EN; EMAC_WRITE_REG(sc, EMAC_INT_CTL, reg_val); if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0); if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE); sc->emac_link = 0; /* Switch to the current media. */ mii = device_get_softc(sc->emac_miibus); mii_mediachg(mii); callout_reset(&sc->emac_tick_ch, hz, emac_tick, sc); } static void emac_start(if_t ifp) { struct emac_softc *sc; sc = if_getsoftc(ifp); EMAC_LOCK(sc); emac_start_locked(ifp); EMAC_UNLOCK(sc); } static void emac_start_locked(if_t ifp) { struct emac_softc *sc; struct mbuf *m, *m0; uint32_t fifo, reg; sc = if_getsoftc(ifp); if (if_getdrvflags(ifp) & IFF_DRV_OACTIVE) return; if (sc->emac_fifo_mask == (EMAC_TX_FIFO0 | EMAC_TX_FIFO1)) return; if (sc->emac_link == 0) return; m = if_dequeue(ifp); if (m == NULL) return; /* Select channel */ if (sc->emac_fifo_mask & EMAC_TX_FIFO0) fifo = 1; else fifo = 0; sc->emac_fifo_mask |= (1 << fifo); if (sc->emac_fifo_mask == (EMAC_TX_FIFO0 | EMAC_TX_FIFO1)) if_setdrvflagbits(ifp, IFF_DRV_OACTIVE, 0); EMAC_WRITE_REG(sc, EMAC_TX_INS, fifo); /* * Emac controller wants 4 byte aligned TX buffers. * We have to copy pretty much all the time. */ if (m->m_next != NULL || (mtod(m, uintptr_t) & 3) != 0) { m0 = m_defrag(m, M_NOWAIT); if (m0 == NULL) { m_freem(m); m = NULL; return; } m = m0; } /* Write data */ bus_space_write_multi_4(sc->emac_tag, sc->emac_handle, EMAC_TX_IO_DATA, mtod(m, uint32_t *), roundup2(m->m_len, 4) / 4); /* Send the data lengh. */ reg = (fifo == 0) ? EMAC_TX_PL0 : EMAC_TX_PL1; EMAC_WRITE_REG(sc, reg, m->m_len); /* Start translate from fifo to phy. */ reg = (fifo == 0) ? EMAC_TX_CTL0 : EMAC_TX_CTL1; EMAC_WRITE_REG(sc, reg, EMAC_READ_REG(sc, reg) | 1); /* Set timeout */ sc->emac_watchdog_timer = 5; /* Data have been sent to hardware, it is okay to free the mbuf now. */ BPF_MTAP(ifp, m); m_freem(m); } static void emac_stop_locked(struct emac_softc *sc) { if_t ifp; uint32_t reg_val; EMAC_ASSERT_LOCKED(sc); ifp = sc->emac_ifp; if_setdrvflagbits(ifp, 0, (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)); sc->emac_link = 0; /* Disable all interrupt and clear interrupt status */ EMAC_WRITE_REG(sc, EMAC_INT_CTL, 0); reg_val = EMAC_READ_REG(sc, EMAC_INT_STA); EMAC_WRITE_REG(sc, EMAC_INT_STA, reg_val); /* Disable RX/TX */ reg_val = EMAC_READ_REG(sc, EMAC_CTL); reg_val &= ~(EMAC_CTL_RST | EMAC_CTL_TX_EN | EMAC_CTL_RX_EN); EMAC_WRITE_REG(sc, EMAC_CTL, reg_val); callout_stop(&sc->emac_tick_ch); } static void emac_intr(void *arg) { struct emac_softc *sc; if_t ifp; uint32_t reg_val; sc = (struct emac_softc *)arg; EMAC_LOCK(sc); /* Disable all interrupts */ EMAC_WRITE_REG(sc, EMAC_INT_CTL, 0); /* Get EMAC interrupt status */ reg_val = EMAC_READ_REG(sc, EMAC_INT_STA); /* Clear ISR status */ EMAC_WRITE_REG(sc, EMAC_INT_STA, reg_val); /* Received incoming packet */ if (reg_val & EMAC_INT_STA_RX) emac_rxeof(sc, sc->emac_rx_process_limit); /* Transmit Interrupt check */ if (reg_val & EMAC_INT_STA_TX) { emac_txeof(sc, reg_val); ifp = sc->emac_ifp; if (!if_sendq_empty(ifp)) emac_start_locked(ifp); } /* Re-enable interrupt mask */ reg_val = EMAC_READ_REG(sc, EMAC_INT_CTL); reg_val |= EMAC_INT_EN; EMAC_WRITE_REG(sc, EMAC_INT_CTL, reg_val); EMAC_UNLOCK(sc); } static int emac_ioctl(if_t ifp, u_long command, caddr_t data) { struct emac_softc *sc; struct mii_data *mii; struct ifreq *ifr; int error = 0; sc = if_getsoftc(ifp); ifr = (struct ifreq *)data; switch (command) { case SIOCSIFFLAGS: EMAC_LOCK(sc); if (if_getflags(ifp) & IFF_UP) { if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) != 0) { if ((if_getflags(ifp) ^ sc->emac_if_flags) & (IFF_PROMISC | IFF_ALLMULTI)) emac_set_rx_mode(sc); } else emac_init_locked(sc); } else { if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) != 0) emac_stop_locked(sc); } sc->emac_if_flags = if_getflags(ifp); EMAC_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: EMAC_LOCK(sc); if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { emac_set_rx_mode(sc); } EMAC_UNLOCK(sc); break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: mii = device_get_softc(sc->emac_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command); break; default: error = ether_ioctl(ifp, command, data); break; } return (error); } static int emac_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "allwinner,sun4i-a10-emac")) return (ENXIO); device_set_desc(dev, "A10/A20 EMAC ethernet controller"); return (BUS_PROBE_DEFAULT); } static int emac_detach(device_t dev) { struct emac_softc *sc; sc = device_get_softc(dev); if_setdrvflagbits(sc->emac_ifp, 0, IFF_DRV_RUNNING); if (device_is_attached(dev)) { ether_ifdetach(sc->emac_ifp); EMAC_LOCK(sc); emac_stop_locked(sc); EMAC_UNLOCK(sc); callout_drain(&sc->emac_tick_ch); } if (sc->emac_intrhand != NULL) bus_teardown_intr(sc->emac_dev, sc->emac_irq, sc->emac_intrhand); if (sc->emac_miibus != NULL) { device_delete_child(sc->emac_dev, sc->emac_miibus); bus_generic_detach(sc->emac_dev); } if (sc->emac_clk != NULL) clk_disable(sc->emac_clk); if (sc->emac_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->emac_res); if (sc->emac_irq != NULL) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->emac_irq); if (sc->emac_ifp != NULL) if_free(sc->emac_ifp); if (mtx_initialized(&sc->emac_mtx)) mtx_destroy(&sc->emac_mtx); return (0); } static int emac_shutdown(device_t dev) { return (emac_suspend(dev)); } static int emac_suspend(device_t dev) { struct emac_softc *sc; if_t ifp; sc = device_get_softc(dev); EMAC_LOCK(sc); ifp = sc->emac_ifp; if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) != 0) emac_stop_locked(sc); EMAC_UNLOCK(sc); return (0); } static int emac_resume(device_t dev) { struct emac_softc *sc; if_t ifp; sc = device_get_softc(dev); EMAC_LOCK(sc); ifp = sc->emac_ifp; if ((if_getflags(ifp) & IFF_UP) != 0) { if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING); emac_init_locked(sc); } EMAC_UNLOCK(sc); return (0); } static int emac_attach(device_t dev) { struct emac_softc *sc; if_t ifp; int error, rid; uint8_t eaddr[ETHER_ADDR_LEN]; sc = device_get_softc(dev); sc->emac_dev = dev; error = 0; mtx_init(&sc->emac_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF); callout_init_mtx(&sc->emac_tick_ch, &sc->emac_mtx, 0); rid = 0; sc->emac_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->emac_res == NULL) { device_printf(dev, "unable to map memory\n"); error = ENXIO; goto fail; } sc->emac_tag = rman_get_bustag(sc->emac_res); sc->emac_handle = rman_get_bushandle(sc->emac_res); rid = 0; sc->emac_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->emac_irq == NULL) { device_printf(dev, "cannot allocate IRQ resources.\n"); error = ENXIO; goto fail; } /* Create device sysctl node. */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "process_limit", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, &sc->emac_rx_process_limit, 0, sysctl_hw_emac_proc_limit, "I", "max number of Rx events to process"); sc->emac_rx_process_limit = EMAC_PROC_DEFAULT; error = resource_int_value(device_get_name(dev), device_get_unit(dev), "process_limit", &sc->emac_rx_process_limit); if (error == 0) { if (sc->emac_rx_process_limit < EMAC_PROC_MIN || sc->emac_rx_process_limit > EMAC_PROC_MAX) { device_printf(dev, "process_limit value out of range; " "using default: %d\n", EMAC_PROC_DEFAULT); sc->emac_rx_process_limit = EMAC_PROC_DEFAULT; } } /* Setup EMAC */ error = emac_sys_setup(sc); if (error != 0) goto fail; emac_reset(sc); ifp = sc->emac_ifp = if_alloc(IFT_ETHER); if_setsoftc(ifp, sc); /* Setup MII */ error = mii_attach(dev, &sc->emac_miibus, ifp, emac_ifmedia_upd, emac_ifmedia_sts, BMSR_DEFCAPMASK, MII_PHY_ANY, MII_OFFSET_ANY, 0); if (error != 0) { device_printf(dev, "PHY probe failed\n"); goto fail; } if_initname(ifp, device_get_name(dev), device_get_unit(dev)); if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST); if_setstartfn(ifp, emac_start); if_setioctlfn(ifp, emac_ioctl); if_setinitfn(ifp, emac_init); if_setsendqlen(ifp, IFQ_MAXLEN); /* Get MAC address */ emac_get_hwaddr(sc, eaddr); ether_ifattach(ifp, eaddr); /* VLAN capability setup. */ if_setcapabilitiesbit(ifp, IFCAP_VLAN_MTU, 0); if_setcapenable(ifp, if_getcapabilities(ifp)); /* Tell the upper layer we support VLAN over-sized frames. */ if_setifheaderlen(ifp, sizeof(struct ether_vlan_header)); error = bus_setup_intr(dev, sc->emac_irq, INTR_TYPE_NET | INTR_MPSAFE, NULL, emac_intr, sc, &sc->emac_intrhand); if (error != 0) { device_printf(dev, "could not set up interrupt handler.\n"); ether_ifdetach(ifp); goto fail; } fail: if (error != 0) emac_detach(dev); return (error); } static bool emac_miibus_iowait(struct emac_softc *sc) { uint32_t timeout; for (timeout = 100; timeout != 0; --timeout) { DELAY(100); if ((EMAC_READ_REG(sc, EMAC_MAC_MIND) & 0x1) == 0) return (true); } return (false); } /* * The MII bus interface */ static int emac_miibus_readreg(device_t dev, int phy, int reg) { struct emac_softc *sc; int rval; sc = device_get_softc(dev); /* Issue phy address and reg */ EMAC_WRITE_REG(sc, EMAC_MAC_MADR, (phy << 8) | reg); /* Pull up the phy io line */ EMAC_WRITE_REG(sc, EMAC_MAC_MCMD, 0x1); if (!emac_miibus_iowait(sc)) { device_printf(dev, "timeout waiting for mii read\n"); return (0); } /* Push down the phy io line */ EMAC_WRITE_REG(sc, EMAC_MAC_MCMD, 0x0); /* Read data */ rval = EMAC_READ_REG(sc, EMAC_MAC_MRDD); return (rval); } static int emac_miibus_writereg(device_t dev, int phy, int reg, int data) { struct emac_softc *sc; sc = device_get_softc(dev); /* Issue phy address and reg */ EMAC_WRITE_REG(sc, EMAC_MAC_MADR, (phy << 8) | reg); /* Write data */ EMAC_WRITE_REG(sc, EMAC_MAC_MWTD, data); /* Pull up the phy io line */ EMAC_WRITE_REG(sc, EMAC_MAC_MCMD, 0x1); if (!emac_miibus_iowait(sc)) { device_printf(dev, "timeout waiting for mii write\n"); return (0); } /* Push down the phy io line */ EMAC_WRITE_REG(sc, EMAC_MAC_MCMD, 0x0); return (0); } static void emac_miibus_statchg(device_t dev) { struct emac_softc *sc; struct mii_data *mii; if_t ifp; uint32_t reg_val; sc = device_get_softc(dev); mii = device_get_softc(sc->emac_miibus); ifp = sc->emac_ifp; if (mii == NULL || ifp == NULL || (if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0) return; sc->emac_link = 0; if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == (IFM_ACTIVE | IFM_AVALID)) { switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: sc->emac_link = 1; break; default: break; } } /* Program MACs with resolved speed/duplex. */ if (sc->emac_link != 0) { reg_val = EMAC_READ_REG(sc, EMAC_MAC_IPGT); if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) { reg_val &= ~EMAC_MAC_IPGT_HD; reg_val |= EMAC_MAC_IPGT_FD; } else { reg_val &= ~EMAC_MAC_IPGT_FD; reg_val |= EMAC_MAC_IPGT_HD; } EMAC_WRITE_REG(sc, EMAC_MAC_IPGT, reg_val); /* Enable RX/TX */ reg_val = EMAC_READ_REG(sc, EMAC_CTL); reg_val |= EMAC_CTL_RST | EMAC_CTL_TX_EN | EMAC_CTL_RX_EN; EMAC_WRITE_REG(sc, EMAC_CTL, reg_val); } else { /* Disable RX/TX */ reg_val = EMAC_READ_REG(sc, EMAC_CTL); reg_val &= ~(EMAC_CTL_RST | EMAC_CTL_TX_EN | EMAC_CTL_RX_EN); EMAC_WRITE_REG(sc, EMAC_CTL, reg_val); } } static int emac_ifmedia_upd(if_t ifp) { struct emac_softc *sc; struct mii_data *mii; struct mii_softc *miisc; int error; sc = if_getsoftc(ifp); mii = device_get_softc(sc->emac_miibus); EMAC_LOCK(sc); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); error = mii_mediachg(mii); EMAC_UNLOCK(sc); return (error); } static void emac_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) { struct emac_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = device_get_softc(sc->emac_miibus); EMAC_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; EMAC_UNLOCK(sc); } static device_method_t emac_methods[] = { /* Device interface */ DEVMETHOD(device_probe, emac_probe), DEVMETHOD(device_attach, emac_attach), DEVMETHOD(device_detach, emac_detach), DEVMETHOD(device_shutdown, emac_shutdown), DEVMETHOD(device_suspend, emac_suspend), DEVMETHOD(device_resume, emac_resume), /* bus interface, for miibus */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD(bus_driver_added, bus_generic_driver_added), /* MII interface */ DEVMETHOD(miibus_readreg, emac_miibus_readreg), DEVMETHOD(miibus_writereg, emac_miibus_writereg), DEVMETHOD(miibus_statchg, emac_miibus_statchg), DEVMETHOD_END }; static driver_t emac_driver = { "emac", emac_methods, sizeof(struct emac_softc) }; DRIVER_MODULE(emac, simplebus, emac_driver, 0, 0); DRIVER_MODULE(miibus, emac, miibus_driver, 0, 0); MODULE_DEPEND(emac, miibus, 1, 1, 1); MODULE_DEPEND(emac, ether, 1, 1, 1); static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int low, int high) { int error, value; if (arg1 == NULL) return (EINVAL); value = *(int *)arg1; error = sysctl_handle_int(oidp, &value, 0, req); if (error || req->newptr == NULL) return (error); if (value < low || value > high) return (EINVAL); *(int *)arg1 = value; return (0); } static int sysctl_hw_emac_proc_limit(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, EMAC_PROC_MIN, EMAC_PROC_MAX)); }