From owner-svn-src-all@FreeBSD.ORG Mon Mar 3 11:32:56 2014 Return-Path: Delivered-To: svn-src-all@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [8.8.178.115]) (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id 3B87EF3D; Mon, 3 Mar 2014 11:32:56 +0000 (UTC) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.freebsd.org (Postfix) with ESMTPS id 1C827CF9; Mon, 3 Mar 2014 11:32:56 +0000 (UTC) Received: from svn.freebsd.org ([127.0.1.70]) by svn.freebsd.org (8.14.8/8.14.8) with ESMTP id s23BWuII079090; Mon, 3 Mar 2014 11:32:56 GMT (envelope-from ganbold@svn.freebsd.org) Received: (from ganbold@localhost) by svn.freebsd.org (8.14.8/8.14.8/Submit) id s23BWu4Q079088; Mon, 3 Mar 2014 11:32:56 GMT (envelope-from ganbold@svn.freebsd.org) Message-Id: <201403031132.s23BWu4Q079088@svn.freebsd.org> From: Ganbold Tsagaankhuu Date: Mon, 3 Mar 2014 11:32:56 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r262710 - head/sys/arm/allwinner X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-all@freebsd.org X-Mailman-Version: 2.1.17 Precedence: list List-Id: "SVN commit messages for the entire src tree \(except for " user" and " projects" \)" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 03 Mar 2014 11:32:56 -0000 Author: ganbold Date: Mon Mar 3 11:32:55 2014 New Revision: 262710 URL: http://svnweb.freebsd.org/changeset/base/262710 Log: Add EMAC 10/100 Ethernet controller driver for A10/A20. It is available mostly in A10 devices like Hackberry, Marsboard, Mele A1000, A2000, A100 HTPC, cubieboard1 and A20 device like cubieboard2. TX performance can be improved using both channels 0 and 1. RX performance is poor and needs improvement with the assistance of external DMA controller in case there is bulk TCP receiver. Reviewed by: yongari@ Approved by: stas (mentor) Added: head/sys/arm/allwinner/if_emac.c (contents, props changed) head/sys/arm/allwinner/if_emacreg.h (contents, props changed) Added: head/sys/arm/allwinner/if_emac.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/arm/allwinner/if_emac.c Mon Mar 3 11:32:55 2014 (r262710) @@ -0,0 +1,1152 @@ +/*- + * 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. + * + * $FreeBSD$ + */ + +/* A10/A20 EMAC driver */ + +#include +__FBSDID("$FreeBSD$"); + +#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 "miibus_if.h" + +#include "gpio_if.h" + +#include "a10_clk.h" +#include "a10_sramc.h" +#include "a10_gpio.h" + +struct emac_softc { + struct ifnet *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; + 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; +}; + +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 void emac_sys_setup(void); +static void emac_reset(struct emac_softc *); + +static void emac_init_locked(struct emac_softc *); +static void emac_start_locked(struct ifnet *); +static void emac_init(void *); +static void emac_stop_locked(struct emac_softc *); +static void emac_intr(void *); +static int emac_ioctl(struct ifnet *, u_long, caddr_t); + +static void emac_rxeof(struct emac_softc *, int); +static void emac_txeof(struct emac_softc *); + +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(struct ifnet *); +static void emac_ifmedia_sts(struct ifnet *, 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 void +emac_sys_setup(void) +{ + int i; + + a10_clk_emac_activate(); + + /* + * Configure pin mux settings for MII. + * Pins PA0 from PA17. + */ + for (i = 0; i <= 17; i++) + a10_emac_gpio_config(i); + /* Map sram */ + a10_map_to_emac(); +} + +static void +emac_get_hwaddr(struct emac_softc *sc, uint8_t *hwaddr) +{ + uint32_t val0, val1, rnd; + + /* + * 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, + * '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 { + 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 void +emac_set_rx_mode(struct emac_softc *sc) +{ + struct ifnet *ifp; + struct ifmultiaddr *ifma; + uint32_t h, 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 (ifp->if_flags & IFF_ALLMULTI) { + hashes[0] = 0xffffffff; + hashes[1] = 0xffffffff; + } else { + if_maddr_rlock(ifp); + TAILQ_FOREACH(ifma, &sc->emac_ifp->if_multiaddrs, ifma_link) { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + h = ether_crc32_be(LLADDR((struct sockaddr_dl *) + ifma->ifma_addr), ETHER_ADDR_LEN) >> 26; + hashes[h >> 5] |= 1 << (h & 0x1f); + } + if_maddr_runlock(ifp); + } + 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 (ifp->if_flags & IFF_BROADCAST) { + rcr |= EMAC_RX_BCO; + rcr |= EMAC_RX_MCO; + } + + if (ifp->if_flags & 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_txeof(struct emac_softc *sc) +{ + struct ifnet *ifp; + + EMAC_ASSERT_LOCKED(sc); + + ifp = sc->emac_ifp; + ifp->if_opackets++; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + /* Unarm watchdog timer if no TX */ + sc->emac_watchdog_timer = 0; +} + +static void +emac_rxeof(struct emac_softc *sc, int count) +{ + struct ifnet *ifp; + struct mbuf *m, *m0; + uint32_t reg_val, rxcount; + int16_t len; + uint16_t status; + int good_packet, i; + + ifp = sc->emac_ifp; + for (; count > 0 && + (ifp->if_drv_flags & 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; + } + + good_packet = 1; + + /* 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) { + good_packet = 0; + if (bootverbose) + if_printf(ifp, + "bad packet: len = %i status = %i\n", + len, status); + ifp->if_ierrors++; + } +#if 0 + if (status & (EMAC_CRCERR | EMAC_LENERR)) { + good_packet = 0; + ifp->if_ierrors++; + if (status & EMAC_CRCERR) + if_printf(ifp, "crc error\n"); + if (status & EMAC_LENERR) + if_printf(ifp, "length error\n"); + } +#endif + if (good_packet) { + m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); + if (m == NULL) + return; + m->m_len = m->m_pkthdr.len = MCLBYTES; + + len -= ETHER_CRC_LEN; + + /* 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; + + /* + * Emac controller needs strict aligment, 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 { + ifp->if_ierrors++; + m_freem(m); + m = NULL; + continue; + } + } else if (m->m_len > EMAC_MAC_MAXF) { + ifp->if_ierrors++; + m_freem(m); + m = NULL; + continue; + } + ifp->if_ipackets++; + EMAC_UNLOCK(sc); + (*ifp->if_input)(ifp, m); + EMAC_LOCK(sc); + } + } +} + +static void +emac_watchdog(struct emac_softc *sc) +{ + struct ifnet *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"); + + ifp->if_oerrors++; + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + emac_init_locked(sc); + if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) + 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) +{ + struct ifnet *ifp; + struct mii_data *mii; + uint32_t reg_val; + uint8_t *eaddr; + + EMAC_ASSERT_LOCKED(sc); + + ifp = sc->emac_ifp; + if ((ifp->if_drv_flags & 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_LLADDR(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); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + ifp->if_drv_flags &= ~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(struct ifnet *ifp) +{ + struct emac_softc *sc; + + sc = ifp->if_softc; + EMAC_LOCK(sc); + emac_start_locked(ifp); + EMAC_UNLOCK(sc); +} + +static void +emac_start_locked(struct ifnet *ifp) +{ + struct emac_softc *sc; + struct mbuf *m, *m0; + uint32_t reg_val; + + sc = ifp->if_softc; + if (ifp->if_drv_flags & IFF_DRV_OACTIVE) + return; + if (sc->emac_link == 0) + return; + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + return; + + /* Select channel */ + EMAC_WRITE_REG(sc, EMAC_TX_INS, 0); + + /* + * 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. */ + EMAC_WRITE_REG(sc, EMAC_TX_PL0, m->m_len); + + /* Start translate from fifo to phy. */ + reg_val = EMAC_READ_REG(sc, EMAC_TX_CTL0); + reg_val |= 1; + EMAC_WRITE_REG(sc, EMAC_TX_CTL0, reg_val); + + /* Set timeout */ + sc->emac_watchdog_timer = 5; + + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + BPF_MTAP(ifp, m); + m_freem(m); +} + +static void +emac_stop_locked(struct emac_softc *sc) +{ + struct ifnet *ifp; + uint32_t reg_val; + + EMAC_ASSERT_LOCKED(sc); + + ifp = sc->emac_ifp; + ifp->if_drv_flags &= ~(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; + struct ifnet *ifp; + uint32_t reg_val; + + sc = (struct emac_softc *)arg; + EMAC_LOCK(sc); + ifp = sc->emac_ifp; + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return; + + /* 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); + if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) + emac_start_locked(ifp); + } + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { + /* 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(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct emac_softc *sc; + struct mii_data *mii; + struct ifreq *ifr; + int error = 0; + + sc = ifp->if_softc; + ifr = (struct ifreq *)data; + + switch (command) { + case SIOCSIFFLAGS: + EMAC_LOCK(sc); + if (ifp->if_flags & IFF_UP) { + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { + if ((ifp->if_flags ^ sc->emac_if_flags) & + (IFF_PROMISC | IFF_ALLMULTI)) + emac_set_rx_mode(sc); + } else + emac_init_locked(sc); + } else { + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) + emac_stop_locked(sc); + } + sc->emac_if_flags = ifp->if_flags; + EMAC_UNLOCK(sc); + break; + case SIOCADDMULTI: + case SIOCDELMULTI: + EMAC_LOCK(sc); + if (ifp->if_drv_flags & 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_is_compatible(dev, "allwinner,sun4i-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); + sc->emac_ifp->if_drv_flags &= ~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_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; + struct ifnet *ifp; + + sc = device_get_softc(dev); + + EMAC_LOCK(sc); + ifp = sc->emac_ifp; + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) + emac_stop_locked(sc); + EMAC_UNLOCK(sc); + + return (0); +} + +static int +emac_resume(device_t dev) +{ + struct emac_softc *sc; + struct ifnet *ifp; + + sc = device_get_softc(dev); + + EMAC_LOCK(sc); + ifp = sc->emac_ifp; + if ((ifp->if_flags & IFF_UP) != 0) { + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + emac_init_locked(sc); + } + EMAC_UNLOCK(sc); + + return (0); +} + +static int +emac_attach(device_t dev) +{ + struct emac_softc *sc; + struct ifnet *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, + &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 */ + emac_sys_setup(); + emac_reset(sc); + + ifp = sc->emac_ifp = if_alloc(IFT_ETHER); + if (ifp == NULL) { + device_printf(dev, "unable to allocate ifp\n"); + error = ENOSPC; + goto fail; + } + ifp->if_softc = 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)); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_start = emac_start; + ifp->if_ioctl = emac_ioctl; + ifp->if_init = emac_init; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + + /* Get MAC address */ + emac_get_hwaddr(sc, eaddr); + ether_ifattach(ifp, eaddr); + + /* VLAN capability setup. */ + ifp->if_capabilities |= IFCAP_VLAN_MTU; + ifp->if_capenable = ifp->if_capabilities; + /* Tell the upper layer we support VLAN over-sized frames. */ + ifp->if_hdrlen = 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 boolean_t +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); *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***