/* $NetBSD: if_bce.c,v 1.65 2024/02/10 09:30:06 andvar Exp $     */

/*
* Copyright (c) 2003 Clifford Wright. 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.
* 3. The name of the author may not be used to endorse or promote products
*    derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
*/

/*
* Broadcom BCM440x 10/100 ethernet (broadcom.com)
* SiliconBackplane is technology from Sonics, Inc.(sonicsinc.com)
*
* Cliff Wright [email protected]
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_bce.c,v 1.65 2024/02/10 09:30:06 andvar Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/callout.h>
#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/socket.h>

#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_ether.h>

#include <net/bpf.h>
#include <sys/rndsource.h>

#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcidevs.h>

#include <dev/mii/mii.h>
#include <dev/mii/miivar.h>

#include <dev/pci/if_bcereg.h>

/* transmit buffer max frags allowed */
#define BCE_NTXFRAGS    16

/* ring descriptor */
struct bce_dma_slot {
       uint32_t ctrl;
       uint32_t addr;
};
#define CTRL_BC_MASK    0x1fff  /* buffer byte count */
#define CTRL_EOT        0x10000000      /* end of descriptor table */
#define CTRL_IOC        0x20000000      /* interrupt on completion */
#define CTRL_EOF        0x40000000      /* end of frame */
#define CTRL_SOF        0x80000000      /* start of frame */

/* Packet status is returned in a pre-packet header */
struct rx_pph {
       uint16_t len;
       uint16_t flags;
       uint16_t pad[12];
};

/* packet status flags bits */
#define RXF_NO                          0x8     /* odd number of nibbles */
#define RXF_RXER                        0x4     /* receive symbol error */
#define RXF_CRC                         0x2     /* crc error */
#define RXF_OV                          0x1     /* fifo overflow */

/* number of descriptors used in a ring */
#define BCE_NRXDESC             128
#define BCE_NTXDESC             128

/*
* Mbuf pointers. We need these to keep track of the virtual addresses
* of our mbuf chains since we can only convert from physical to virtual,
* not the other way around.
*/
struct bce_chain_data {
       struct mbuf    *bce_tx_chain[BCE_NTXDESC];
       struct mbuf    *bce_rx_chain[BCE_NRXDESC];
       bus_dmamap_t    bce_tx_map[BCE_NTXDESC];
       bus_dmamap_t    bce_rx_map[BCE_NRXDESC];
};

#define BCE_TIMEOUT             100     /* # 10us for mii read/write */

struct bce_softc {
       device_t                bce_dev;
       bus_space_tag_t         bce_btag;
       bus_space_handle_t      bce_bhandle;
       bus_dma_tag_t           bce_dmatag;
       struct ethercom         ethercom;       /* interface info */
       void                    *bce_intrhand;
       struct pci_attach_args  bce_pa;
       struct mii_data         bce_mii;
       uint32_t                bce_phy;        /* eeprom indicated phy */
       struct ifmedia          bce_ifmedia;    /* media info *//* Check */
       uint8_t                 enaddr[ETHER_ADDR_LEN];
       struct bce_dma_slot     *bce_rx_ring;   /* receive ring */
       struct bce_dma_slot     *bce_tx_ring;   /* transmit ring */
       struct bce_chain_data   bce_cdata;      /* mbufs */
       bus_dmamap_t            bce_ring_map;
       uint32_t                bce_intmask;    /* current intr mask */
       uint32_t                bce_rxin;       /* last rx descriptor seen */
       uint32_t                bce_txin;       /* last tx descriptor seen */
       int                     bce_txsfree;    /* no. tx slots available */
       int                     bce_txsnext;    /* next available tx slot */
       callout_t               bce_timeout;
       krndsource_t    rnd_source;
};

/* for ring descriptors */
#define BCE_RXBUF_LEN   (MCLBYTES - 4)
#define BCE_INIT_RXDESC(sc, x)                                          \
do {                                                                    \
       struct bce_dma_slot *__bced = &sc->bce_rx_ring[x];              \
                                                                       \
       *mtod(sc->bce_cdata.bce_rx_chain[x], uint32_t *) = 0;           \
       __bced->addr =                                                  \
           htole32(sc->bce_cdata.bce_rx_map[x]->dm_segs[0].ds_addr     \
           + 0x40000000);                                              \
       if (x != (BCE_NRXDESC - 1))                                     \
               __bced->ctrl = htole32(BCE_RXBUF_LEN);                  \
       else                                                            \
               __bced->ctrl = htole32(BCE_RXBUF_LEN | CTRL_EOT);       \
       bus_dmamap_sync(sc->bce_dmatag, sc->bce_ring_map,               \
           sizeof(struct bce_dma_slot) * x,                            \
           sizeof(struct bce_dma_slot),                                \
           BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);                  \
} while (/* CONSTCOND */ 0)

static  int     bce_probe(device_t, cfdata_t, void *);
static  void    bce_attach(device_t, device_t, void *);
static  int     bce_ioctl(struct ifnet *, u_long, void *);
static  void    bce_start(struct ifnet *);
static  void    bce_watchdog(struct ifnet *);
static  int     bce_intr(void *);
static  void    bce_rxintr(struct bce_softc *);
static  void    bce_txintr(struct bce_softc *);
static  int     bce_init(struct ifnet *);
static  void    bce_add_mac(struct bce_softc *, uint8_t *, unsigned long);
static  int     bce_add_rxbuf(struct bce_softc *, int);
static  void    bce_rxdrain(struct bce_softc *);
static  void    bce_stop(struct ifnet *, int);
static  void    bce_reset(struct bce_softc *);
static  bool    bce_resume(device_t, const pmf_qual_t *);
static  void    bce_set_filter(struct ifnet *);
static  int     bce_mii_read(device_t, int, int, uint16_t *);
static  int     bce_mii_write(device_t, int, int, uint16_t);
static  void    bce_statchg(struct ifnet *);
static  void    bce_tick(void *);

CFATTACH_DECL_NEW(bce, sizeof(struct bce_softc),
                 bce_probe, bce_attach, NULL, NULL);

static const struct bce_product {
       pci_vendor_id_t bp_vendor;
       pci_product_id_t bp_product;
       const   char *bp_name;
} bce_products[] = {
       {
               PCI_VENDOR_BROADCOM,
               PCI_PRODUCT_BROADCOM_BCM4401,
               "Broadcom BCM4401 10/100 Ethernet"
       },
       {
               PCI_VENDOR_BROADCOM,
               PCI_PRODUCT_BROADCOM_BCM4401_B0,
               "Broadcom BCM4401-B0 10/100 Ethernet"
       },
       {
               PCI_VENDOR_BROADCOM,
               PCI_PRODUCT_BROADCOM_BCM4401_B1,
               "Broadcom BCM4401-B1 10/100 Ethernet"
       },
       {

               0,
               0,
               NULL
       },
};

static const struct bce_product *
bce_lookup(const struct pci_attach_args * pa)
{
       const struct bce_product *bp;

       for (bp = bce_products; bp->bp_name != NULL; bp++) {
               if (PCI_VENDOR(pa->pa_id) == bp->bp_vendor &&
                   PCI_PRODUCT(pa->pa_id) == bp->bp_product)
                       return (bp);
       }

       return (NULL);
}

/*
* Probe for a Broadcom chip. Check the PCI vendor and device IDs
* against drivers product list, and return its name if a match is found.
*/
static int
bce_probe(device_t parent, cfdata_t match, void *aux)
{
       struct pci_attach_args *pa = (struct pci_attach_args *) aux;

       if (bce_lookup(pa) != NULL)
               return (1);

       return (0);
}

static void
bce_attach(device_t parent, device_t self, void *aux)
{
       struct bce_softc *sc = device_private(self);
       struct pci_attach_args *pa = aux;
       const struct bce_product *bp;
       pci_chipset_tag_t pc = pa->pa_pc;
       pci_intr_handle_t ih;
       const char     *intrstr = NULL;
       uint32_t        command;
       pcireg_t        memtype, pmode;
       bus_addr_t      memaddr;
       bus_size_t      memsize;
       void            *kva;
       bus_dma_segment_t seg;
       int             error, i, pmreg, rseg;
       uint16_t        phyval;
       struct ifnet   *ifp;
       struct mii_data *mii = &sc->bce_mii;
       char intrbuf[PCI_INTRSTR_LEN];

       sc->bce_dev = self;

       bp = bce_lookup(pa);
       KASSERT(bp != NULL);

       sc->bce_pa = *pa;

       /* BCM440x can only address 30 bits (1GB) */
       if (bus_dmatag_subregion(pa->pa_dmat, 0, __MASK(30),
           &(sc->bce_dmatag), BUS_DMA_NOWAIT) != 0) {
               aprint_error_dev(self,
                   "WARNING: failed to restrict dma range,"
                   " falling back to parent bus dma range\n");
               sc->bce_dmatag = pa->pa_dmat;
       }

        aprint_naive(": Ethernet controller\n");
        aprint_normal(": %s\n", bp->bp_name);

       /*
        * Map control/status registers.
        */
       command = pci_conf_read(pc, pa->pa_tag, PCI_COMMAND_STATUS_REG);
       command |= PCI_COMMAND_MEM_ENABLE | PCI_COMMAND_MASTER_ENABLE;
       pci_conf_write(pc, pa->pa_tag, PCI_COMMAND_STATUS_REG, command);
       command = pci_conf_read(pc, pa->pa_tag, PCI_COMMAND_STATUS_REG);

       if (!(command & PCI_COMMAND_MEM_ENABLE)) {
               aprint_error_dev(self, "failed to enable memory mapping!\n");
               return;
       }
       memtype = pci_mapreg_type(pa->pa_pc, pa->pa_tag, BCE_PCI_BAR0);
       switch (memtype) {
       case PCI_MAPREG_TYPE_MEM | PCI_MAPREG_MEM_TYPE_32BIT:
       case PCI_MAPREG_TYPE_MEM | PCI_MAPREG_MEM_TYPE_64BIT:
               if (pci_mapreg_map(pa, BCE_PCI_BAR0, memtype, 0, &sc->bce_btag,
                   &sc->bce_bhandle, &memaddr, &memsize) == 0)
                       break;
               /* FALLTHROUGH */
       default:
               aprint_error_dev(self, "unable to find mem space\n");
               return;
       }

       /* Get it out of power save mode if needed. */
       if (pci_get_capability(pc, pa->pa_tag, PCI_CAP_PWRMGMT, &pmreg, NULL)) {
               pmode = pci_conf_read(pc, pa->pa_tag, pmreg + PCI_PMCSR)
                   & PCI_PMCSR_STATE_MASK;
               if (pmode == PCI_PMCSR_STATE_D3) {
                       /*
                        * The card has lost all configuration data in
                        * this state, so punt.
                        */
                       aprint_error_dev(self,
                           "unable to wake up from power state D3\n");
                       return;
               }
               if (pmode != PCI_PMCSR_STATE_D0) {
                       aprint_normal_dev(self,
                           "waking up from power state D%d\n", pmode);
                       pci_conf_write(pc, pa->pa_tag, pmreg + PCI_PMCSR, 0);
               }
       }
       if (pci_intr_map(pa, &ih)) {
               aprint_error_dev(self, "couldn't map interrupt\n");
               return;
       }
       intrstr = pci_intr_string(pc, ih, intrbuf, sizeof(intrbuf));

       sc->bce_intrhand = pci_intr_establish_xname(pc, ih, IPL_NET, bce_intr,
           sc, device_xname(self));

       if (sc->bce_intrhand == NULL) {
               aprint_error_dev(self, "couldn't establish interrupt\n");
               if (intrstr != NULL)
                       aprint_error(" at %s", intrstr);
               aprint_error("\n");
               return;
       }
       aprint_normal_dev(self, "interrupting at %s\n", intrstr);

       /* reset the chip */
       bce_reset(sc);

       /*
        * Allocate DMA-safe memory for ring descriptors.
        * The receive, and transmit rings can not share the same
        * 4k space, however both are allocated at once here.
        */
       /*
        * XXX PAGE_SIZE is wasteful; we only need 1KB + 1KB, but
        * due to the limitation above. ??
        */
       if ((error = bus_dmamem_alloc(sc->bce_dmatag,
           2 * PAGE_SIZE, PAGE_SIZE, 2 * PAGE_SIZE,
           &seg, 1, &rseg, BUS_DMA_NOWAIT))) {
               aprint_error_dev(self,
                   "unable to alloc space for ring descriptors, error = %d\n",
                   error);
               return;
       }
       /* map ring space to kernel */
       if ((error = bus_dmamem_map(sc->bce_dmatag, &seg, rseg,
           2 * PAGE_SIZE, &kva, BUS_DMA_NOWAIT))) {
               aprint_error_dev(self,
                   "unable to map DMA buffers, error = %d\n", error);
               bus_dmamem_free(sc->bce_dmatag, &seg, rseg);
               return;
       }
       /* create a dma map for the ring */
       if ((error = bus_dmamap_create(sc->bce_dmatag,
           2 * PAGE_SIZE, 1, 2 * PAGE_SIZE, 0, BUS_DMA_NOWAIT,
           &sc->bce_ring_map))) {
               aprint_error_dev(self,
                   "unable to create ring DMA map, error = %d\n", error);
               bus_dmamem_unmap(sc->bce_dmatag, kva, 2 * PAGE_SIZE);
               bus_dmamem_free(sc->bce_dmatag, &seg, rseg);
               return;
       }
       /* connect the ring space to the dma map */
       if (bus_dmamap_load(sc->bce_dmatag, sc->bce_ring_map, kva,
           2 * PAGE_SIZE, NULL, BUS_DMA_NOWAIT)) {
               bus_dmamap_destroy(sc->bce_dmatag, sc->bce_ring_map);
               bus_dmamem_unmap(sc->bce_dmatag, kva, 2 * PAGE_SIZE);
               bus_dmamem_free(sc->bce_dmatag, &seg, rseg);
               return;
       }
       /* save the ring space in softc */
       sc->bce_rx_ring = (struct bce_dma_slot *) kva;
       sc->bce_tx_ring = (struct bce_dma_slot *) ((char *)kva + PAGE_SIZE);

       /* Create the transmit buffer DMA maps. */
       for (i = 0; i < BCE_NTXDESC; i++) {
               if ((error = bus_dmamap_create(sc->bce_dmatag, MCLBYTES,
                   BCE_NTXFRAGS, MCLBYTES, 0, 0, &sc->bce_cdata.bce_tx_map[i])) != 0) {
                       aprint_error_dev(self,
                           "unable to create tx DMA map, error = %d\n", error);
               }
               sc->bce_cdata.bce_tx_chain[i] = NULL;
       }

       /* Create the receive buffer DMA maps. */
       for (i = 0; i < BCE_NRXDESC; i++) {
               if ((error = bus_dmamap_create(sc->bce_dmatag, MCLBYTES, 1,
                   MCLBYTES, 0, 0, &sc->bce_cdata.bce_rx_map[i])) != 0) {
                       aprint_error_dev(self,
                           "unable to create rx DMA map, error = %d\n", error);
               }
               sc->bce_cdata.bce_rx_chain[i] = NULL;
       }

       /* Set up ifnet structure */
       ifp = &sc->ethercom.ec_if;
       strcpy(ifp->if_xname, device_xname(self));
       ifp->if_softc = sc;
       ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
       ifp->if_ioctl = bce_ioctl;
       ifp->if_start = bce_start;
       ifp->if_watchdog = bce_watchdog;
       ifp->if_init = bce_init;
       ifp->if_stop = bce_stop;
       IFQ_SET_READY(&ifp->if_snd);

       sc->ethercom.ec_capabilities |= ETHERCAP_VLAN_MTU;

       /* Initialize our media structures and probe the MII. */

       mii->mii_ifp = ifp;
       mii->mii_readreg = bce_mii_read;
       mii->mii_writereg = bce_mii_write;
       mii->mii_statchg = bce_statchg;

       sc->ethercom.ec_mii = mii;
       ifmedia_init(&mii->mii_media, 0, ether_mediachange, ether_mediastatus);
       mii_attach(sc->bce_dev, mii, 0xffffffff, MII_PHY_ANY,
           MII_OFFSET_ANY, MIIF_FORCEANEG|MIIF_DOPAUSE);
       if (LIST_FIRST(&mii->mii_phys) == NULL) {
               ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_NONE, 0, NULL);
               ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_NONE);
       } else
               ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_AUTO);
       /* get the phy */
       sc->bce_phy = bus_space_read_1(sc->bce_btag, sc->bce_bhandle,
           BCE_MAGIC_PHY) & 0x1f;
       /*
        * Enable activity led.
        * XXX This should be in a phy driver, but not currently.
        */
       bce_mii_read(sc->bce_dev, 1, 26, &phyval);
       bce_mii_write(sc->bce_dev, 1, 26,        /* MAGIC */
           phyval & 0x7fff);    /* MAGIC */
       /* enable traffic meter led mode */
       bce_mii_read(sc->bce_dev, 1, 27, &phyval);
       bce_mii_write(sc->bce_dev, 1, 27,        /* MAGIC */
           phyval | (1 << 6));  /* MAGIC */

       /* Attach the interface */
       if_attach(ifp);
       if_deferred_start_init(ifp, NULL);
       sc->enaddr[0] = bus_space_read_1(sc->bce_btag, sc->bce_bhandle,
           BCE_MAGIC_ENET0);
       sc->enaddr[1] = bus_space_read_1(sc->bce_btag, sc->bce_bhandle,
           BCE_MAGIC_ENET1);
       sc->enaddr[2] = bus_space_read_1(sc->bce_btag, sc->bce_bhandle,
           BCE_MAGIC_ENET2);
       sc->enaddr[3] = bus_space_read_1(sc->bce_btag, sc->bce_bhandle,
           BCE_MAGIC_ENET3);
       sc->enaddr[4] = bus_space_read_1(sc->bce_btag, sc->bce_bhandle,
           BCE_MAGIC_ENET4);
       sc->enaddr[5] = bus_space_read_1(sc->bce_btag, sc->bce_bhandle,
           BCE_MAGIC_ENET5);
       aprint_normal_dev(self, "Ethernet address %s\n",
           ether_sprintf(sc->enaddr));
       ether_ifattach(ifp, sc->enaddr);
       rnd_attach_source(&sc->rnd_source, device_xname(self),
           RND_TYPE_NET, RND_FLAG_DEFAULT);
       callout_init(&sc->bce_timeout, 0);
       callout_setfunc(&sc->bce_timeout, bce_tick, sc);

       if (pmf_device_register(self, NULL, bce_resume))
               pmf_class_network_register(self, ifp);
       else
               aprint_error_dev(self, "couldn't establish power handler\n");
}

/* handle media, and ethernet requests */
static int
bce_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
       int             s, error;

       s = splnet();
       error = ether_ioctl(ifp, cmd, data);
       if (error == ENETRESET) {
               /* change multicast list */
               error = 0;
       }

       /* Try to get more packets going. */
       bce_start(ifp);

       splx(s);
       return error;
}

/* Start packet transmission on the interface. */
static void
bce_start(struct ifnet *ifp)
{
       struct bce_softc *sc = ifp->if_softc;
       struct mbuf    *m0;
       bus_dmamap_t    dmamap;
       int             txstart;
       int             txsfree;
       int             newpkts = 0;
       int             error;

       /*
        * do not start another if currently transmitting, and more
        * descriptors(tx slots) are needed for next packet.
        */
       if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING)
               return;

       /* determine number of descriptors available */
       if (sc->bce_txsnext >= sc->bce_txin)
               txsfree = BCE_NTXDESC - 1 + sc->bce_txin - sc->bce_txsnext;
       else
               txsfree = sc->bce_txin - sc->bce_txsnext - 1;

       /*
        * Loop through the send queue, setting up transmit descriptors
        * until we drain the queue, or use up all available transmit
        * descriptors.
        */
       while (txsfree > 0) {
               int             seg;

               /* Grab a packet off the queue. */
               IFQ_POLL(&ifp->if_snd, m0);
               if (m0 == NULL)
                       break;

               /* get the transmit slot dma map */
               dmamap = sc->bce_cdata.bce_tx_map[sc->bce_txsnext];

               /*
                * Load the DMA map.  If this fails, the packet either
                * didn't fit in the allotted number of segments, or we
                * were short on resources. If the packet will not fit,
                * it will be dropped. If short on resources, it will
                * be tried again later.
                */
               error = bus_dmamap_load_mbuf(sc->bce_dmatag, dmamap, m0,
                   BUS_DMA_WRITE | BUS_DMA_NOWAIT);
               if (error == EFBIG) {
                       aprint_error_dev(sc->bce_dev,
                           "Tx packet consumes too many DMA segments, "
                           "dropping...\n");
                       IFQ_DEQUEUE(&ifp->if_snd, m0);
                       m_freem(m0);
                       if_statinc(ifp, if_oerrors);
                       continue;
               } else if (error) {
                       /* short on resources, come back later */
                       aprint_error_dev(sc->bce_dev,
                           "unable to load Tx buffer, error = %d\n",
                           error);
                       break;
               }
               /* If not enough descriptors available, try again later */
               if (dmamap->dm_nsegs > txsfree) {
                       ifp->if_flags |= IFF_OACTIVE;
                       bus_dmamap_unload(sc->bce_dmatag, dmamap);
                       break;
               }
               /* WE ARE NOW COMMITTED TO TRANSMITTING THE PACKET. */

               /* So take it off the queue */
               IFQ_DEQUEUE(&ifp->if_snd, m0);

               /* save the pointer so it can be freed later */
               sc->bce_cdata.bce_tx_chain[sc->bce_txsnext] = m0;

               /* Sync the data DMA map. */
               bus_dmamap_sync(sc->bce_dmatag, dmamap, 0, dmamap->dm_mapsize,
                               BUS_DMASYNC_PREWRITE);

               /* Initialize the transmit descriptor(s). */
               txstart = sc->bce_txsnext;
               for (seg = 0; seg < dmamap->dm_nsegs; seg++) {
                       uint32_t ctrl;

                       ctrl = dmamap->dm_segs[seg].ds_len & CTRL_BC_MASK;
                       if (seg == 0)
                               ctrl |= CTRL_SOF;
                       if (seg == dmamap->dm_nsegs - 1)
                               ctrl |= CTRL_EOF;
                       if (sc->bce_txsnext == BCE_NTXDESC - 1)
                               ctrl |= CTRL_EOT;
                       ctrl |= CTRL_IOC;
                       sc->bce_tx_ring[sc->bce_txsnext].ctrl = htole32(ctrl);
                       sc->bce_tx_ring[sc->bce_txsnext].addr =
                           htole32(dmamap->dm_segs[seg].ds_addr + 0x40000000); /* MAGIC */
                       if (sc->bce_txsnext + 1 > BCE_NTXDESC - 1)
                               sc->bce_txsnext = 0;
                       else
                               sc->bce_txsnext++;
                       txsfree--;
               }
               /* sync descriptors being used */
               if ( sc->bce_txsnext > txstart ) {
                       bus_dmamap_sync(sc->bce_dmatag, sc->bce_ring_map,
                           PAGE_SIZE + sizeof(struct bce_dma_slot) * txstart,
                           sizeof(struct bce_dma_slot) * dmamap->dm_nsegs,
                           BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
               } else {
                       bus_dmamap_sync(sc->bce_dmatag, sc->bce_ring_map,
                           PAGE_SIZE + sizeof(struct bce_dma_slot) * txstart,
                           sizeof(struct bce_dma_slot) *
                           (BCE_NTXDESC - txstart),
                           BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
                       if ( sc->bce_txsnext != 0 ) {
                               bus_dmamap_sync(sc->bce_dmatag,
                                   sc->bce_ring_map, PAGE_SIZE,
                                   sc->bce_txsnext *
                                   sizeof(struct bce_dma_slot),
                                   BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
                       }
               }

               /* Give the packet to the chip. */
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_DMA_DPTR,
                   sc->bce_txsnext * sizeof(struct bce_dma_slot));

               newpkts++;

               /* Pass the packet to any BPF listeners. */
               bpf_mtap(ifp, m0, BPF_D_OUT);
       }
       if (txsfree == 0) {
               /* No more slots left; notify upper layer. */
               ifp->if_flags |= IFF_OACTIVE;
       }
       if (newpkts) {
               /* Set a watchdog timer in case the chip flakes out. */
               ifp->if_timer = 5;
       }
}

/* Watchdog timer handler. */
static void
bce_watchdog(struct ifnet *ifp)
{
       struct bce_softc *sc = ifp->if_softc;

       device_printf(sc->bce_dev, "device timeout\n");
       if_statinc(ifp, if_oerrors);

       (void) bce_init(ifp);

       /* Try to get more packets going. */
       bce_start(ifp);
}

int
bce_intr(void *xsc)
{
       struct bce_softc *sc;
       struct ifnet   *ifp;
       uint32_t        intstatus;
       int             wantinit;
       int             handled = 0;

       sc = xsc;
       ifp = &sc->ethercom.ec_if;

       for (wantinit = 0; wantinit == 0;) {
               intstatus = bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
                   BCE_INT_STS);

               /* ignore if not ours, or unsolicited interrupts */
               intstatus &= sc->bce_intmask;
               if (intstatus == 0)
                       break;

               handled = 1;

               /* Ack interrupt */
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_INT_STS,
                   intstatus);

               /* Receive interrupts. */
               if (intstatus & I_RI)
                       bce_rxintr(sc);
               /* Transmit interrupts. */
               if (intstatus & I_XI)
                       bce_txintr(sc);
               /* Error interrupts */
               if (intstatus & ~(I_RI | I_XI)) {
                       const char *msg = NULL;
                       if (intstatus & I_XU)
                               msg = "transmit fifo underflow";
                       if (intstatus & I_RO) {
                               msg = "receive fifo overflow";
                               if_statinc(ifp, if_ierrors);
                       }
                       if (intstatus & I_RU)
                               msg = "receive descriptor underflow";
                       if (intstatus & I_DE)
                               msg = "descriptor protocol error";
                       if (intstatus & I_PD)
                               msg = "data error";
                       if (intstatus & I_PC)
                               msg = "descriptor error";
                       if (intstatus & I_TO)
                               msg = "general purpose timeout";
                       if (msg != NULL)
                               aprint_error_dev(sc->bce_dev, "%s\n", msg);
                       wantinit = 1;
               }
       }

       if (handled) {
               if (wantinit)
                       bce_init(ifp);
               rnd_add_uint32(&sc->rnd_source, intstatus);
               /* Try to get more packets going. */
               if_schedule_deferred_start(ifp);
       }
       return (handled);
}

/* Receive interrupt handler */
void
bce_rxintr(struct bce_softc *sc)
{
       struct ifnet   *ifp = &sc->ethercom.ec_if;
       struct rx_pph  *pph;
       struct mbuf    *m;
       int             curr;
       int             len;
       int             i;

       /* get pointer to active receive slot */
       curr = bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_DMA_RXSTATUS)
           & RS_CD_MASK;
       curr = curr / sizeof(struct bce_dma_slot);
       if (curr >= BCE_NRXDESC)
               curr = BCE_NRXDESC - 1;

       /* process packets up to but not current packet being worked on */
       for (i = sc->bce_rxin; i != curr;
           i + 1 > BCE_NRXDESC - 1 ? i = 0 : i++) {
               /* complete any post dma memory ops on packet */
               bus_dmamap_sync(sc->bce_dmatag, sc->bce_cdata.bce_rx_map[i], 0,
                   sc->bce_cdata.bce_rx_map[i]->dm_mapsize,
                   BUS_DMASYNC_POSTREAD);

               /*
                * If the packet had an error, simply recycle the buffer,
                * resetting the len, and flags.
                */
               pph = mtod(sc->bce_cdata.bce_rx_chain[i], struct rx_pph *);
               if (pph->flags & (RXF_NO | RXF_RXER | RXF_CRC | RXF_OV)) {
                       if_statinc(ifp, if_ierrors);
                       pph->len = 0;
                       pph->flags = 0;
                       continue;
               }
               /* receive the packet */
               len = pph->len;
               if (len == 0)
                       continue;       /* no packet if empty */
               pph->len = 0;
               pph->flags = 0;
               /* bump past pre header to packet */
               sc->bce_cdata.bce_rx_chain[i]->m_data += 30;    /* MAGIC */

               /*
                * The chip includes the CRC with every packet.  Trim
                * it off here.
                */
               len -= ETHER_CRC_LEN;

               /*
                * If the packet is small enough to fit in a
                * single header mbuf, allocate one and copy
                * the data into it.  This greatly reduces
                * memory consumption when receiving lots
                * of small packets.
                *
                * Otherwise, add a new buffer to the receive
                * chain.  If this fails, drop the packet and
                * recycle the old buffer.
                */
               if (len <= (MHLEN - 2)) {
                       MGETHDR(m, M_DONTWAIT, MT_DATA);
                       if (m == NULL)
                               goto dropit;
                       m->m_data += 2;
                       memcpy(mtod(m, void *),
                        mtod(sc->bce_cdata.bce_rx_chain[i], void *), len);
                       sc->bce_cdata.bce_rx_chain[i]->m_data -= 30;    /* MAGIC */
               } else {
                       m = sc->bce_cdata.bce_rx_chain[i];
                       if (bce_add_rxbuf(sc, i) != 0) {
               dropit:
                               if_statinc(ifp, if_ierrors);
                               /* continue to use old buffer */
                               sc->bce_cdata.bce_rx_chain[i]->m_data -= 30;
                               bus_dmamap_sync(sc->bce_dmatag,
                                   sc->bce_cdata.bce_rx_map[i], 0,
                                   sc->bce_cdata.bce_rx_map[i]->dm_mapsize,
                                   BUS_DMASYNC_PREREAD);
                               continue;
                       }
               }

               m_set_rcvif(m, ifp);
               m->m_pkthdr.len = m->m_len = len;

               /* Pass it on. */
               if_percpuq_enqueue(ifp->if_percpuq, m);

               /* re-check current in case it changed */
               curr = (bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
                   BCE_DMA_RXSTATUS) & RS_CD_MASK) /
                   sizeof(struct bce_dma_slot);
               if (curr >= BCE_NRXDESC)
                       curr = BCE_NRXDESC - 1;
       }
       sc->bce_rxin = curr;
}

/* Transmit interrupt handler */
void
bce_txintr(struct bce_softc *sc)
{
       struct ifnet   *ifp = &sc->ethercom.ec_if;
       int             curr;
       int             i;

       ifp->if_flags &= ~IFF_OACTIVE;

       /*
        * Go through the Tx list and free mbufs for those
        * frames which have been transmitted.
        */
       curr = bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_DMA_TXSTATUS) &
               RS_CD_MASK;
       curr = curr / sizeof(struct bce_dma_slot);
       if (curr >= BCE_NTXDESC)
               curr = BCE_NTXDESC - 1;
       for (i = sc->bce_txin; i != curr;
           i + 1 > BCE_NTXDESC - 1 ? i = 0 : i++) {
               /* do any post dma memory ops on transmit data */
               if (sc->bce_cdata.bce_tx_chain[i] == NULL)
                       continue;
               bus_dmamap_sync(sc->bce_dmatag, sc->bce_cdata.bce_tx_map[i], 0,
                   sc->bce_cdata.bce_tx_map[i]->dm_mapsize,
                   BUS_DMASYNC_POSTWRITE);
               bus_dmamap_unload(sc->bce_dmatag, sc->bce_cdata.bce_tx_map[i]);
               m_freem(sc->bce_cdata.bce_tx_chain[i]);
               sc->bce_cdata.bce_tx_chain[i] = NULL;
               if_statinc(ifp, if_opackets);
       }
       sc->bce_txin = curr;

       /*
        * If there are no more pending transmissions, cancel the watchdog
        * timer
        */
       if (sc->bce_txsnext == sc->bce_txin)
               ifp->if_timer = 0;
}

/* initialize the interface */
static int
bce_init(struct ifnet *ifp)
{
       struct bce_softc *sc = ifp->if_softc;
       uint32_t        reg_win;
       int             error;
       int             i;

       /* Cancel any pending I/O. */
       bce_stop(ifp, 0);

       /* enable pci interrupts, bursts, and prefetch */

       /* remap the pci registers to the Sonics config registers */

       /* save the current map, so it can be restored */
       reg_win = pci_conf_read(sc->bce_pa.pa_pc, sc->bce_pa.pa_tag,
           BCE_REG_WIN);

       /* set register window to Sonics registers */
       pci_conf_write(sc->bce_pa.pa_pc, sc->bce_pa.pa_tag, BCE_REG_WIN,
           BCE_SONICS_WIN);

       /* enable SB to PCI interrupt */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_SBINTVEC,
           bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_SBINTVEC) |
           SBIV_ENET0);

       /* enable prefetch and bursts for sonics-to-pci translation 2 */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_SPCI_TR2,
           bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_SPCI_TR2) |
           SBTOPCI_PREF | SBTOPCI_BURST);

       /* restore to ethernet register space */
       pci_conf_write(sc->bce_pa.pa_pc, sc->bce_pa.pa_tag, BCE_REG_WIN,
           reg_win);

       /* Reset the chip to a known state. */
       bce_reset(sc);

       /* Initialize transmit descriptors */
       memset(sc->bce_tx_ring, 0, BCE_NTXDESC * sizeof(struct bce_dma_slot));
       sc->bce_txsnext = 0;
       sc->bce_txin = 0;

       /* enable crc32 generation and set proper LED modes */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_MACCTL,
           bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_MACCTL) |
           BCE_EMC_CRC32_ENAB | BCE_EMC_LED);

       /* reset or clear powerdown control bit  */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_MACCTL,
           bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_MACCTL) &
           ~BCE_EMC_PDOWN);

       /* recv coalesce; 31:24 frame upper bound, 23:0 guard period */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_DMAI_CTL, 1 << 24);        /* MAGIC */

       /* setup packet filter */
       bce_set_filter(ifp);

       /* set max frame length, account for possible vlan tag */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_RX_MAX,
           ETHER_MAX_LEN + 32);
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_TX_MAX,
           ETHER_MAX_LEN + 32);

       /* set tx watermark */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_TX_WATER, 56);

       /* enable transmit */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_DMA_TXCTL, XC_XE);
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_DMA_TXADDR,
           sc->bce_ring_map->dm_segs[0].ds_addr + PAGE_SIZE + 0x40000000);     /* MAGIC */

       /*
        * Give the receive ring to the chip, and
        * start the receive DMA engine.
        */
       sc->bce_rxin = 0;

       /* clear the rx descriptor ring */
       memset(sc->bce_rx_ring, 0, BCE_NRXDESC * sizeof(struct bce_dma_slot));
       /* enable receive */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_DMA_RXCTL,
           30 << 1 | 1);       /* MAGIC */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_DMA_RXADDR,
           sc->bce_ring_map->dm_segs[0].ds_addr + 0x40000000);         /* MAGIC */

       /* Initialize receive descriptors */
       for (i = 0; i < BCE_NRXDESC; i++) {
               if (sc->bce_cdata.bce_rx_chain[i] == NULL) {
                       if ((error = bce_add_rxbuf(sc, i)) != 0) {
                               aprint_error_dev(sc->bce_dev,
                                   "unable to allocate or map rx(%d) "
                                   "mbuf, error = %d\n", i, error);
                               bce_rxdrain(sc);
                               return (error);
                       }
               } else
                       BCE_INIT_RXDESC(sc, i);
       }

       /* Enable interrupts */
       sc->bce_intmask =
           I_XI | I_RI | I_XU | I_RO | I_RU | I_DE | I_PD | I_PC | I_TO;
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_INT_MASK,
           sc->bce_intmask);

       /* start the receive dma */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_DMA_RXDPTR,
           BCE_NRXDESC * sizeof(struct bce_dma_slot));

       /* set media */
       if ((error = ether_mediachange(ifp)) != 0)
               return error;

       /* turn on the ethernet mac */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_ENET_CTL,
           bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
           BCE_ENET_CTL) | EC_EE);

       /* start timer */
       callout_schedule(&sc->bce_timeout, hz);

       /* mark as running, and no outputs active */
       ifp->if_flags |= IFF_RUNNING;
       ifp->if_flags &= ~IFF_OACTIVE;

       return 0;
}

/* add a mac address to packet filter */
void
bce_add_mac(struct bce_softc *sc, uint8_t *mac, u_long idx)
{
       int             i;
       uint32_t        rval;

       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_FILT_LOW,
           (uint32_t)mac[2] << 24 | mac[3] << 16 | mac[4] << 8 | mac[5]);
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_FILT_HI,
           mac[0] << 8 | mac[1] | 0x10000);    /* MAGIC */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_FILT_CTL,
           idx << 16 | 8);     /* MAGIC */
       /* wait for write to complete */
       for (i = 0; i < 100; i++) {
               rval = bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
                   BCE_FILT_CTL);
               if (!(rval & 0x80000000))       /* MAGIC */
                       break;
               delay(10);
       }
       if (i == 100) {
               aprint_error_dev(sc->bce_dev,
                   "timed out writing pkt filter ctl\n");
       }
}

/* Add a receive buffer to the indicated descriptor. */
static int
bce_add_rxbuf(struct bce_softc *sc, int idx)
{
       struct mbuf    *m;
       int             error;

       MGETHDR(m, M_DONTWAIT, MT_DATA);
       if (m == NULL)
               return (ENOBUFS);

       MCLGET(m, M_DONTWAIT);
       if ((m->m_flags & M_EXT) == 0) {
               m_freem(m);
               return (ENOBUFS);
       }
       if (sc->bce_cdata.bce_rx_chain[idx] != NULL)
               bus_dmamap_unload(sc->bce_dmatag,
                   sc->bce_cdata.bce_rx_map[idx]);

       sc->bce_cdata.bce_rx_chain[idx] = m;

       error = bus_dmamap_load(sc->bce_dmatag, sc->bce_cdata.bce_rx_map[idx],
           m->m_ext.ext_buf, m->m_ext.ext_size, NULL,
           BUS_DMA_READ | BUS_DMA_NOWAIT);
       if (error)
               return (error);

       bus_dmamap_sync(sc->bce_dmatag, sc->bce_cdata.bce_rx_map[idx], 0,
           sc->bce_cdata.bce_rx_map[idx]->dm_mapsize, BUS_DMASYNC_PREREAD);

       BCE_INIT_RXDESC(sc, idx);

       return (0);

}

/* Drain the receive queue. */
static void
bce_rxdrain(struct bce_softc *sc)
{
       int             i;

       for (i = 0; i < BCE_NRXDESC; i++) {
               if (sc->bce_cdata.bce_rx_chain[i] != NULL) {
                       bus_dmamap_unload(sc->bce_dmatag,
                           sc->bce_cdata.bce_rx_map[i]);
                       m_freem(sc->bce_cdata.bce_rx_chain[i]);
                       sc->bce_cdata.bce_rx_chain[i] = NULL;
               }
       }
}

/* Stop transmission on the interface */
static void
bce_stop(struct ifnet *ifp, int disable)
{
       struct bce_softc *sc = ifp->if_softc;
       int             i;
       uint32_t        val;

       /* Stop the 1 second timer */
       callout_stop(&sc->bce_timeout);

       /* Down the MII. */
       mii_down(&sc->bce_mii);

       /* Disable interrupts. */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_INT_MASK, 0);
       sc->bce_intmask = 0;
       delay(10);

       /* Disable emac */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_ENET_CTL, EC_ED);
       for (i = 0; i < 200; i++) {
               val = bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
                   BCE_ENET_CTL);
               if (!(val & EC_ED))
                       break;
               delay(10);
       }

       /* Stop the DMA */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_DMA_RXCTL, 0);
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_DMA_TXCTL, 0);
       delay(10);

       /* Release any queued transmit buffers. */
       for (i = 0; i < BCE_NTXDESC; i++) {
               if (sc->bce_cdata.bce_tx_chain[i] != NULL) {
                       bus_dmamap_unload(sc->bce_dmatag,
                           sc->bce_cdata.bce_tx_map[i]);
                       m_freem(sc->bce_cdata.bce_tx_chain[i]);
                       sc->bce_cdata.bce_tx_chain[i] = NULL;
               }
       }

       /* Mark the interface down and cancel the watchdog timer. */
       ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
       ifp->if_timer = 0;

       /* drain receive queue */
       if (disable)
               bce_rxdrain(sc);
}

/* reset the chip */
static void
bce_reset(struct bce_softc *sc)
{
       uint32_t        val;
       uint32_t        sbval;
       int             i;

       /* if SB core is up */
       sbval = bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
           BCE_SBTMSTATELOW);
       if ((sbval & (SBTML_RESET | SBTML_REJ | SBTML_CLK)) == SBTML_CLK) {
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_DMAI_CTL,
                   0);

               /* disable emac */
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_ENET_CTL,
                   EC_ED);
               for (i = 0; i < 200; i++) {
                       val = bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
                           BCE_ENET_CTL);
                       if (!(val & EC_ED))
                               break;
                       delay(10);
               }
               if (i == 200) {
                       aprint_error_dev(sc->bce_dev,
                           "timed out disabling ethernet mac\n");
               }

               /* reset the dma engines */
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_DMA_TXCTL, 0);
               val = bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_DMA_RXSTATUS);
               /* if error on receive, wait to go idle */
               if (val & RS_ERROR) {
                       for (i = 0; i < 100; i++) {
                               val = bus_space_read_4(sc->bce_btag,
                                   sc->bce_bhandle, BCE_DMA_RXSTATUS);
                               if (val & RS_DMA_IDLE)
                                       break;
                               delay(10);
                       }
                       if (i == 100) {
                               aprint_error_dev(sc->bce_dev,
                                   "receive dma did not go idle after"
                                   " error\n");
                       }
               }
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle,
                  BCE_DMA_RXSTATUS, 0);

               /* reset ethernet mac */
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_ENET_CTL,
                   EC_ES);
               for (i = 0; i < 200; i++) {
                       val = bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
                           BCE_ENET_CTL);
                       if (!(val & EC_ES))
                               break;
                       delay(10);
               }
               if (i == 200) {
                       aprint_error_dev(sc->bce_dev,
                           "timed out resetting ethernet mac\n");
               }
       } else {
               uint32_t reg_win;

               /* remap the pci registers to the Sonics config registers */

               /* save the current map, so it can be restored */
               reg_win = pci_conf_read(sc->bce_pa.pa_pc, sc->bce_pa.pa_tag,
                   BCE_REG_WIN);
               /* set register window to Sonics registers */
               pci_conf_write(sc->bce_pa.pa_pc, sc->bce_pa.pa_tag,
                   BCE_REG_WIN, BCE_SONICS_WIN);

               /* enable SB to PCI interrupt */
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_SBINTVEC,
                   bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
                       BCE_SBINTVEC) |
                   SBIV_ENET0);

               /* enable prefetch and bursts for sonics-to-pci translation 2 */
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_SPCI_TR2,
                   bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
                       BCE_SPCI_TR2) |
                   SBTOPCI_PREF | SBTOPCI_BURST);

               /* restore to ethernet register space */
               pci_conf_write(sc->bce_pa.pa_pc, sc->bce_pa.pa_tag, BCE_REG_WIN,
                   reg_win);
       }

       /* disable SB core if not in reset */
       if (!(sbval & SBTML_RESET)) {

               /* set the reject bit */
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle,
                   BCE_SBTMSTATELOW, SBTML_REJ | SBTML_CLK);
               for (i = 0; i < 200; i++) {
                       val = bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
                           BCE_SBTMSTATELOW);
                       if (val & SBTML_REJ)
                               break;
                       delay(1);
               }
               if (i == 200) {
                       aprint_error_dev(sc->bce_dev,
                           "while resetting core, reject did not set\n");
               }
               /* wait until busy is clear */
               for (i = 0; i < 200; i++) {
                       val = bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
                           BCE_SBTMSTATEHI);
                       if (!(val & 0x4))
                               break;
                       delay(1);
               }
               if (i == 200) {
                       aprint_error_dev(sc->bce_dev,
                           "while resetting core, busy did not clear\n");
               }
               /* set reset and reject while enabling the clocks */
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle,
                   BCE_SBTMSTATELOW,
                   SBTML_FGC | SBTML_CLK | SBTML_REJ | SBTML_RESET);
               val = bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
                   BCE_SBTMSTATELOW);
               delay(10);
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle,
                   BCE_SBTMSTATELOW, SBTML_REJ | SBTML_RESET);
               delay(1);
       }
       /* enable clock */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_SBTMSTATELOW,
           SBTML_FGC | SBTML_CLK | SBTML_RESET);
       val = bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_SBTMSTATELOW);
       delay(1);

       /* clear any error bits that may be on */
       val = bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_SBTMSTATEHI);
       if (val & 1)
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_SBTMSTATEHI,
                   0);
       val = bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_SBIMSTATE);
       if (val & SBIM_MAGIC_ERRORBITS)
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_SBIMSTATE,
                   val & ~SBIM_MAGIC_ERRORBITS);

       /* clear reset and allow it to propagate throughout the core */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_SBTMSTATELOW,
           SBTML_FGC | SBTML_CLK);
       val = bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_SBTMSTATELOW);
       delay(1);

       /* leave clock enabled */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_SBTMSTATELOW,
           SBTML_CLK);
       val = bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_SBTMSTATELOW);
       delay(1);

       /* initialize MDC preamble, frequency */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_MI_CTL, 0x8d);     /* MAGIC */

       /* enable phy, differs for internal, and external */
       val = bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_DEVCTL);
       if (!(val & BCE_DC_IP)) {
               /* select external phy */
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_ENET_CTL, EC_EP);
       } else if (val & BCE_DC_ER) {   /* internal, clear reset bit if on */
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_DEVCTL,
                   val & ~BCE_DC_ER);
               delay(100);
       }
}

/* Set up the receive filter. */
void
bce_set_filter(struct ifnet *ifp)
{
       struct bce_softc *sc = ifp->if_softc;

       if (ifp->if_flags & IFF_PROMISC) {
               ifp->if_flags |= IFF_ALLMULTI;
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_RX_CTL,
                   bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_RX_CTL)
                   | ERC_PE);
       } else {
               ifp->if_flags &= ~IFF_ALLMULTI;

               /* turn off promiscuous */
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_RX_CTL,
                   bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
                   BCE_RX_CTL) & ~ERC_PE);

               /* enable/disable broadcast */
               if (ifp->if_flags & IFF_BROADCAST)
                       bus_space_write_4(sc->bce_btag, sc->bce_bhandle,
                           BCE_RX_CTL, bus_space_read_4(sc->bce_btag,
                           sc->bce_bhandle, BCE_RX_CTL) & ~ERC_DB);
               else
                       bus_space_write_4(sc->bce_btag, sc->bce_bhandle,
                           BCE_RX_CTL, bus_space_read_4(sc->bce_btag,
                           sc->bce_bhandle, BCE_RX_CTL) | ERC_DB);

               /* disable the filter */
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_FILT_CTL,
                   0);

               /* add our own address */
               bce_add_mac(sc, sc->enaddr, 0);

               /* for now accept all multicast */
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_RX_CTL,
               bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_RX_CTL) |
                   ERC_AM);
               ifp->if_flags |= IFF_ALLMULTI;

               /* enable the filter */
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_FILT_CTL,
                   bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
                   BCE_FILT_CTL) | 1);
       }
}

static bool
bce_resume(device_t self, const pmf_qual_t *qual)
{
       struct bce_softc *sc = device_private(self);

       bce_reset(sc);

       return true;
}

/* Read a PHY register on the MII. */
int
bce_mii_read(device_t self, int phy, int reg, uint16_t *val)
{
       struct bce_softc *sc = device_private(self);
       int             i;
       uint32_t        data;

       /* clear mii_int */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_MI_STS, BCE_MIINTR);

       /* Read the PHY register */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_MI_COMM,
           (MII_COMMAND_READ << 28) | (MII_COMMAND_START << 30) |      /* MAGIC */
           (MII_COMMAND_ACK << 16) | BCE_MIPHY(phy) | BCE_MIREG(reg)); /* MAGIC */

       for (i = 0; i < BCE_TIMEOUT; i++) {
               data = bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
                   BCE_MI_STS);
               if (data & BCE_MIINTR)
                       break;
               delay(10);
       }
       data = bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_MI_COMM);
       if (i == BCE_TIMEOUT) {
               aprint_error_dev(sc->bce_dev,
                   "PHY read timed out reading phy %d, reg %d, val = "
                   "0x%08x\n", phy, reg, data);
               return ETIMEDOUT;
       }
       *val = data & BCE_MICOMM_DATA;
       return 0;
}

/* Write a PHY register on the MII */
int
bce_mii_write(device_t self, int phy, int reg, uint16_t val)
{
       struct bce_softc *sc = device_private(self);
       int             i;
       uint32_t        data;

       /* clear mii_int */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_MI_STS,
           BCE_MIINTR);

       /* Write the PHY register */
       bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_MI_COMM,
           (MII_COMMAND_WRITE << 28) | (MII_COMMAND_START << 30) |     /* MAGIC */
           (MII_COMMAND_ACK << 16) | (val & BCE_MICOMM_DATA) | /* MAGIC */
           BCE_MIPHY(phy) | BCE_MIREG(reg));

       /* wait for write to complete */
       for (i = 0; i < BCE_TIMEOUT; i++) {
               data = bus_space_read_4(sc->bce_btag, sc->bce_bhandle,
                   BCE_MI_STS);
               if (data & BCE_MIINTR)
                       break;
               delay(10);
       }
       if (i == BCE_TIMEOUT) {
               aprint_error_dev(sc->bce_dev,
                   "PHY timed out writing phy %d, reg %d, val = 0x%04hx\n",
                   phy, reg, val);
               return ETIMEDOUT;
       }

       return 0;
}

/* sync hardware duplex mode to software state */
void
bce_statchg(struct ifnet *ifp)
{
       struct bce_softc *sc = ifp->if_softc;
       uint32_t        reg;
       uint16_t        phyval;

       /* if needed, change register to match duplex mode */
       reg = bus_space_read_4(sc->bce_btag, sc->bce_bhandle, BCE_TX_CTL);
       if (sc->bce_mii.mii_media_active & IFM_FDX && !(reg & EXC_FD))
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_TX_CTL,
                   reg | EXC_FD);
       else if (!(sc->bce_mii.mii_media_active & IFM_FDX) && reg & EXC_FD)
               bus_space_write_4(sc->bce_btag, sc->bce_bhandle, BCE_TX_CTL,
                   reg & ~EXC_FD);

       /*
        * Enable activity led.
        * XXX This should be in a phy driver, but not currently.
        */
       bce_mii_read(sc->bce_dev, 1, 26, &phyval);
       bce_mii_write(sc->bce_dev, 1, 26,       /* MAGIC */
           phyval & 0x7fff);   /* MAGIC */
       /* enable traffic meter led mode */
       bce_mii_read(sc->bce_dev, 1, 27, &phyval);
       bce_mii_write(sc->bce_dev, 1, 26,       /* MAGIC */
           phyval | (1 << 6)); /* MAGIC */
}

/* One second timer, checks link status */
static void
bce_tick(void *v)
{
       struct bce_softc *sc = v;
       int s;

       s = splnet();
       mii_tick(&sc->bce_mii);
       splx(s);

       callout_schedule(&sc->bce_timeout, hz);
}