/*      $NetBSD: if_netdock_nubus.c,v 1.35 2022/09/18 02:45:38 thorpej Exp $    */

/*
* Copyright (C) 2000,2002 Daishi Kato <[email protected]>
* 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. All advertising materials mentioning features or use of this software
*    must display the following acknowledgement:
*      This product includes software developed by Daishi Kato
* 4. 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.
*/

/*
* Asante NetDock (for Duo series) driver
* the chip inside is not known
*/

/*
* The author would like to thank Takeo Kuwata <[email protected]> for
* his help in stabilizing this driver.
*/

/***********************/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_netdock_nubus.c,v 1.35 2022/09/18 02:45:38 thorpej Exp $");

#include <sys/param.h>
#include <sys/device.h>
#include <sys/socket.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/ioctl.h>

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

#include "opt_inet.h"
#ifdef INET
#include <netinet/in.h>
#include <netinet/if_inarp.h>
#endif

#include <net/bpf.h>

#include <machine/bus.h>
#include <machine/viareg.h>
#include <mac68k/nubus/nubus.h>

/***********************/

#define NETDOCK_DEBUG

#define NETDOCK_NUBUS_CATEGORY  0x0020
#define NETDOCK_NUBUS_TYPE      0x0003
#define NETDOCK_NUBUS_DRSW      0x0103
#define NETDOCK_NUBUS_DRHW      0x0100

#define ETHERMICRODOCK_NUBUS_CATEGORY   0x0020
#define ETHERMICRODOCK_NUBUS_TYPE       0x0003
#define ETHERMICRODOCK_NUBUS_DRSW       0x0102
#define ETHERMICRODOCK_NUBUS_DRHW       0x0100

#define REG_ISR         0x000c
#define REG_000E        0x000e
#define REG_0000        0x0000
#define REG_0002        0x0002
#define REG_0004        0x0004
#define REG_0006        0x0006
#define REG_DATA        0x0008
#define REG_EFD00       0xefd00

#define ISR_ALL         0x3300
#define ISR_TX          0x0200
#define ISR_RX          0x0100
#define ISR_READY       0x0800
#define ISR_BIT_0C      0x1000
#define ISR_BIT_0D      0x2000
#define ISR_MASK        0x0033
#define ISR_BIT_03      0x0008

#define REG_0002_BIT_04 0x0010
#define REG_0000_BIT_08 0x0100
#define REG_0004_BIT_0F 0x8000
#define REG_0004_BIT_07 0x0080
#define REG_DATA_BIT_02 0x0004
#define REG_DATA_BIT_03 0x0008
#define REG_DATA_BIT_04 0x0010
#define REG_DATA_BIT_05 0x0020
#define REG_DATA_BIT_08 0x0100
#define REG_DATA_BIT_07 0x0080
#define REG_DATA_BIT_0F 0x8000

/***********************/

typedef struct netdock_softc {
       device_t        sc_dev;
       struct ethercom sc_ethercom;
#define sc_if           sc_ethercom.ec_if

       bus_space_tag_t         sc_regt;
       bus_space_handle_t      sc_regh;

       u_int8_t        sc_enaddr[ETHER_ADDR_LEN];

} netdock_softc_t;

/***********************/

static int      netdock_nubus_match(device_t, cfdata_t, void *);
static void     netdock_nubus_attach(device_t, device_t, void *);
static int      netdock_nb_get_enaddr(bus_space_tag_t, bus_space_handle_t,
                       struct nubus_attach_args *, u_int8_t *);
#ifdef NETDOCK_DEBUG_DRIVER
static void     netdock_print_driver(bus_space_tag_t, bus_space_handle_t,
                       struct nubus_attach_args *);
#endif

int     netdock_setup(struct netdock_softc *, u_int8_t *);
void    netdock_intr(void *);

static void     netdock_watchdog(struct ifnet *);
static int      netdock_init(struct netdock_softc *);
static int      netdock_stop(struct netdock_softc *);
static int      netdock_ioctl(struct ifnet *, u_long, void *);
static void     netdock_start(struct ifnet *);
static void     netdock_reset(struct netdock_softc *);
static void     netdock_txint(struct netdock_softc *);
static void     netdock_rxint(struct netdock_softc *);

static u_int    netdock_put(struct netdock_softc *, struct mbuf *);
static int      netdock_read(struct netdock_softc *, int);
static struct mbuf *netdock_get(struct netdock_softc *, int);

/***********************/

#define NIC_GET_1(sc, o)        (bus_space_read_1((sc)->sc_regt,        \
                                       (sc)->sc_regh, (o)))
#define NIC_PUT_1(sc, o, val)   (bus_space_write_1((sc)->sc_regt,       \
                                       (sc)->sc_regh, (o), (val)))
#define NIC_GET_2(sc, o)        (bus_space_read_2((sc)->sc_regt,        \
                                       (sc)->sc_regh, (o)))
#define NIC_PUT_2(sc, o, val)   (bus_space_write_2((sc)->sc_regt,       \
                                       (sc)->sc_regh, (o), (val)))
#define NIC_GET_4(sc, o)        (bus_space_read_4((sc)->sc_regt,        \
                                       (sc)->sc_regh, (o)))
#define NIC_PUT_4(sc, o, val)   (bus_space_write_4((sc)->sc_regt,       \
                                       (sc)->sc_regh, (o), (val)))

#define NIC_BSET(sc, o, b)                                              \
       __asm volatile("bset %0,%1" : : "di" ((u_short)(b)),    \
               "g" (*(u_int8_t *)((sc)->sc_regh.base + (o))))
#define NIC_BCLR(sc, o, b)                                              \
       __asm volatile("bclr %0,%1" : : "di" ((u_short)(b)),    \
               "g" (*(u_int8_t *)((sc)->sc_regh.base + (o))))
#define NIC_ANDW(sc, o, b)                                              \
       __asm volatile("andw %0,%1" : : "di" ((u_short)(b)),    \
               "g" (*(u_int8_t *)((sc)->sc_regh.base + (o))))
#define NIC_ORW(sc, o, b)                                               \
       __asm volatile("orw %0,%1" : : "di" ((u_short)(b)),     \
               "g" (*(u_int8_t *)((sc)->sc_regh.base + (o))))


/***********************/

CFATTACH_DECL_NEW(netdock_nubus, sizeof(struct netdock_softc),
   netdock_nubus_match, netdock_nubus_attach, NULL, NULL);

/***********************/

static int
netdock_nubus_match(device_t parent, cfdata_t cf, void *aux)
{
       struct nubus_attach_args *na = (struct nubus_attach_args *)aux;
       bus_space_handle_t bsh;
       int rv;

       if (bus_space_map(na->na_tag, NUBUS_SLOT2PA(na->slot),
           NBMEMSIZE, 0, &bsh))
               return (0);

       rv = 0;

       if (na->category == NETDOCK_NUBUS_CATEGORY &&
           na->type == NETDOCK_NUBUS_TYPE &&
           na->drsw == NETDOCK_NUBUS_DRSW) {
               /* assuming this IS Asante NetDock */
               rv = 1;
       }

       if (na->category == ETHERMICRODOCK_NUBUS_CATEGORY &&
           na->type == ETHERMICRODOCK_NUBUS_TYPE &&
           na->drsw == ETHERMICRODOCK_NUBUS_DRSW) {
               /* assuming this IS Newer EtherMicroDock */
               rv = 1;
       }

       bus_space_unmap(na->na_tag, bsh, NBMEMSIZE);

       return rv;
}

static void
netdock_nubus_attach(device_t parent, device_t self, void *aux)
{
       struct netdock_softc *sc = device_private(self);
       struct nubus_attach_args *na = (struct nubus_attach_args *)aux;
       bus_space_tag_t bst;
       bus_space_handle_t bsh;
       u_int8_t enaddr[ETHER_ADDR_LEN];
       const char *cardtype;

       bst = na->na_tag;
       if (bus_space_map(bst, NUBUS_SLOT2PA(na->slot), NBMEMSIZE, 0, &bsh)) {
               printf(": failed to map memory space.\n");
               return;
       }

       sc->sc_regt = bst;
       sc->sc_dev = self;
       cardtype = nubus_get_card_name(bst, bsh, na->fmt);

#ifdef NETDOCK_DEBUG_DRIVER
       netdock_print_driver(bst, bsh, na);
#endif

       if (netdock_nb_get_enaddr(bst, bsh, na, enaddr)) {
               printf(": can't find MAC address.\n");
               bus_space_unmap(bst, bsh, NBMEMSIZE);
               return;
       }

       if (bus_space_subregion(bst, bsh, 0xe00300, 0xf0000, &sc->sc_regh)) {
               printf(": failed to map register space.\n");
               bus_space_unmap(bst, bsh, NBMEMSIZE);
               return;
       }

       printf(": %s\n", cardtype);

       if (netdock_setup(sc, enaddr)) {
               bus_space_unmap(bst, bsh, NBMEMSIZE);
               return;
       }

       add_nubus_intr(na->slot, netdock_intr, (void *)sc);

       return;
}

static int
netdock_nb_get_enaddr(bus_space_tag_t bst, bus_space_handle_t bsh,
   struct nubus_attach_args *na, u_int8_t *ep)
{
       nubus_dir dir;
       nubus_dirent dirent;

       /*
        * these hardwired resource IDs are only for NetDock
        */
       nubus_get_main_dir(na->fmt, &dir);
       if (nubus_find_rsrc(bst, bsh, na->fmt, &dir, 0x81, &dirent) <= 0)
               return 1;
       nubus_get_dir_from_rsrc(na->fmt, &dirent, &dir);
       if (nubus_find_rsrc(bst, bsh, na->fmt, &dir, 0x80, &dirent) <= 0)
               return 1;
       if (nubus_get_ind_data(bst, bsh, na->fmt, &dirent,
               ep, ETHER_ADDR_LEN) <= 0)
               return 1;

       return 0;
}

#ifdef NETDOCK_DEBUG_DRIVER
static void
netdock_print_driver(bus_space_tag_t bst, bus_space_handle_t bsh,
   struct nubus_attach_args *na)
{
#define HEADSIZE        (8+4)
#define CODESIZE        (6759-4)
       unsigned char mydata[HEADSIZE + CODESIZE];
       nubus_dir dir;
       nubus_dirent dirent;
       int i, rv;

       nubus_get_main_dir(na->fmt, &dir);
       rv = nubus_find_rsrc(bst, bsh, na->fmt, &dir, 0x81, &dirent);
       if (rv <= 0) {
               printf(": can't find sResource.\n");
               return;
       }
       nubus_get_dir_from_rsrc(na->fmt, &dirent, &dir);
       if (nubus_find_rsrc(bst, bsh, na->fmt, &dir, NUBUS_RSRC_DRVRDIR,
           &dirent) <= 0) {
               printf(": can't find sResource.\n");
               return;
       }
       if (nubus_get_ind_data(bst, bsh, na->fmt,
               &dirent, mydata, HEADSIZE+CODESIZE) <= 0) {
               printf(": can't find indirect data.\n");
               return;
       }
       printf("\n########## begin driver dir");
       for (i = 0; i < HEADSIZE; i++) {
               if (i % 16 == 0)
                       printf("\n%02x:",i);
               printf(" %02x", mydata[i]);
       }
       printf("\n########## begin driver code");
       for (i = 0; i < CODESIZE; i++) {
               if (i % 16 == 0)
                       printf("\n%02x:",i);
               printf(" %02x", mydata[i + HEADSIZE]);
       }
#if 0
       printf("\n########## begin driver code (partial)\n");
#define PARTSIZE 256
#define OFFSET 0x1568
       for (i = OFFSET; i < OFFSET + PARTSIZE; i++) {
               if ((i - OFFSET) % 16 == 0)
                       printf("\n%02x:",i);
               printf(" %02x", mydata[i + HEADSIZE]);
       }
#endif
       printf("\n########## end\n");
}
#endif


int
netdock_setup(struct netdock_softc *sc, u_int8_t *lladdr)
{
       struct ifnet *ifp = &sc->sc_if;

       memcpy(sc->sc_enaddr, lladdr, ETHER_ADDR_LEN);
       printf("%s: Ethernet address %s\n",
           device_xname(sc->sc_dev), ether_sprintf(lladdr));

       memcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ);
       ifp->if_softc = sc;
       ifp->if_ioctl = netdock_ioctl;
       ifp->if_start = netdock_start;
       ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
       ifp->if_watchdog = netdock_watchdog;

       if_attach(ifp);
       ether_ifattach(ifp, lladdr);

       return (0);
}

static int
netdock_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
       struct ifaddr *ifa;
       struct netdock_softc *sc = ifp->if_softc;
       int s = splnet();
       int err = 0;
       u_short temp;

       switch (cmd) {
       case SIOCINITIFADDR:
               ifa = (struct ifaddr *)data;
               ifp->if_flags |= IFF_UP;
               (void)netdock_init(sc);
               switch (ifa->ifa_addr->sa_family) {
#ifdef INET
               case AF_INET:
                       arp_ifinit(ifp, ifa);
                       break;
#endif
               default:
                       break;
               }
               break;

       case SIOCSIFFLAGS:
               if ((err = ifioctl_common(ifp, cmd, data)) != 0)
                       break;
               /* XXX see the comment in ed_ioctl() about code re-use */
               if ((ifp->if_flags & IFF_UP) == 0 &&
                   (ifp->if_flags & IFF_RUNNING) != 0) {
                       netdock_stop(sc);
                       ifp->if_flags &= ~IFF_RUNNING;
               } else if ((ifp->if_flags & IFF_UP) != 0 &&
                   (ifp->if_flags & IFF_RUNNING) == 0) {
                       (void)netdock_init(sc);
               } else {
                       temp = ifp->if_flags & IFF_UP;
                       netdock_reset(sc);
                       ifp->if_flags |= temp;
                       netdock_start(ifp);
               }
               break;

       case SIOCADDMULTI:
       case SIOCDELMULTI:
               if ((err = ether_ioctl(ifp, cmd, data)) == ENETRESET) {
                       if (ifp->if_flags & IFF_RUNNING) {
                               temp = ifp->if_flags & IFF_UP;
                               netdock_reset(sc);
                               ifp->if_flags |= temp;
                       }
                       err = 0;
               }
               break;
       default:
               err = ether_ioctl(ifp, cmd, data);
               break;
       }
       splx(s);
       return (err);
}

static void
netdock_start(struct ifnet *ifp)
{
       struct netdock_softc *sc = ifp->if_softc;
       struct mbuf *m;

       if ((ifp->if_flags & IFF_RUNNING) == 0)
               return;

       while (1) {
               IF_DEQUEUE(&ifp->if_snd, m);
               if (m == 0)
                       return;

               KASSERT(m->m_flags & M_PKTHDR);

               bpf_mtap(ifp, m, BPF_D_OUT);

               if ((netdock_put(sc, m)) == 0) {
                       IF_PREPEND(&ifp->if_snd, m);
                       return;
               }

               if_statinc(ifp, if_opackets);
       }
}

static void
netdock_reset(struct netdock_softc *sc)
{

       netdock_stop(sc);
       netdock_init(sc);
}

static int
netdock_init(struct netdock_softc *sc)
{
       int s;
       int saveisr;
       int savetmp;

       if (sc->sc_if.if_flags & IFF_RUNNING)
               return (0);

       s = splnet();

       /* 0606 */
       NIC_PUT_2(sc, REG_000E, 0x0200);
       NIC_PUT_2(sc, REG_ISR , 0);
       NIC_PUT_2(sc, REG_000E, 0);

       NIC_PUT_2(sc, REG_0000, 0x8104);
       NIC_PUT_2(sc, REG_0004, 0x0043);
       NIC_PUT_2(sc, REG_000E, 0x0100);
       NIC_PUT_2(sc, REG_0002, 0x6618);
       NIC_PUT_2(sc, REG_0000, 0x8010);

       NIC_PUT_1(sc, REG_0004 + 0, sc->sc_enaddr[0]);
       NIC_PUT_1(sc, REG_0004 + 1, sc->sc_enaddr[1]);
       NIC_PUT_1(sc, REG_0004 + 2, sc->sc_enaddr[2]);
       NIC_PUT_1(sc, REG_0004 + 3, sc->sc_enaddr[3]);
       NIC_PUT_1(sc, REG_0004 + 4, sc->sc_enaddr[4]);
       NIC_PUT_1(sc, REG_0004 + 5, sc->sc_enaddr[5]);

       NIC_PUT_2(sc, REG_ISR , 0x2008);
       NIC_PUT_2(sc, REG_000E, 0x0200);
       NIC_PUT_2(sc, REG_0000, 0x4000);
       NIC_PUT_2(sc, REG_ISR, ISR_MASK);


       /* 1320 */
       saveisr = NIC_GET_2(sc, REG_ISR);
       NIC_PUT_2(sc, REG_ISR , 0);
       savetmp = NIC_GET_2(sc, REG_000E);
       NIC_PUT_2(sc, REG_000E, 0x0100);
       NIC_ANDW(sc, REG_ISR, ~ISR_BIT_03);
       NIC_PUT_2(sc, REG_000E, savetmp);

       /* 1382 */
       savetmp = NIC_GET_2(sc, REG_000E);
       NIC_PUT_2(sc, REG_000E, 0x0100);
       NIC_ORW(sc, REG_ISR, ISR_BIT_03);
       NIC_PUT_2(sc, REG_000E, savetmp);
       NIC_PUT_2(sc, REG_ISR , saveisr);


       sc->sc_if.if_flags |= IFF_RUNNING;

       splx(s);
       return (0);
}

static int
netdock_stop(struct netdock_softc *sc)
{
       int s = splnet();

       sc->sc_if.if_timer = 0;
       sc->sc_if.if_flags &= ~(IFF_RUNNING | IFF_UP);

       splx(s);
       return (0);
}

static void
netdock_watchdog(struct ifnet *ifp)
{
       struct netdock_softc *sc = ifp->if_softc;
       u_short tmp;

       printf("netdock_watchdog: resetting chip\n");
       tmp = ifp->if_flags & IFF_UP;
       netdock_reset(sc);
       ifp->if_flags |= tmp;
}

static u_int
netdock_put(struct netdock_softc *sc, struct mbuf *m0)
{
       struct mbuf *m;
       u_int totlen = 0;
       u_int tmplen;
       int isr;
       int timeout;
       int tmp;
       u_int i;

       for (m = m0; m; m = m->m_next)
               totlen += m->m_len;

       if (totlen >= ETHER_MAX_LEN)
               panic("%s: netdock_put: packet overflow",
                   device_xname(sc->sc_dev));

       totlen += 6;
       tmplen = totlen;
       tmplen &= 0xff00;
       tmplen |= 0x2000;
       NIC_PUT_2(sc, REG_0000, tmplen);

       timeout = 0x3000;
       while ((((isr = NIC_GET_2(sc, REG_ISR)) & ISR_READY) == 0) &&
           timeout--) {
               if (isr & ISR_TX)
                       netdock_txint(sc);
       }
       if (timeout == 0)
               return (0);

       tmp = NIC_GET_2(sc, REG_0002);
       tmp <<= 8;
       NIC_PUT_2(sc, REG_0002, tmp);
       NIC_PUT_2(sc, REG_0006, 0x240);
       NIC_GET_2(sc, REG_ISR);
       tmplen = ((totlen << 8) & 0xfe00) | ((totlen >> 8) & 0x00ff);
       NIC_PUT_2(sc, REG_DATA, tmplen);

       for (m = m0; m; m = m->m_next) {
               u_char *data = mtod(m, u_char *);
               int len = m->m_len;
               int len4 = len >> 2;
               u_int32_t *data4 = (u_int32_t *)data;
               for (i = 0; i < len4; i++)
                       NIC_PUT_4(sc, REG_DATA, data4[i]);
               for (i = len4 << 2; i < len; i++)
                       NIC_PUT_1(sc, REG_DATA, data[i]);
       }

       if (totlen & 0x01)
               NIC_PUT_2(sc, REG_DATA, 0x2020);
       else
               NIC_PUT_2(sc, REG_DATA, 0);

       NIC_PUT_2(sc, REG_0000, 0xc000);

       m_freem(m0);
       /* sc->sc_if.if_timer = 5; */
       return (totlen);
}

void
netdock_intr(void *arg)
{
       struct netdock_softc *sc = (struct netdock_softc *)arg;
       int isr;
       int tmp;

       NIC_PUT_2(sc, REG_ISR, 0);
       while ((isr = (NIC_GET_2(sc, REG_ISR) & ISR_ALL)) != 0) {
               if (isr & ISR_TX)
                       netdock_txint(sc);

               if (isr & ISR_RX)
                       netdock_rxint(sc);

               if (isr & (ISR_BIT_0C | ISR_BIT_0D)) {
                       if (isr & ISR_BIT_0C) {
                               NIC_PUT_2(sc, REG_000E, 0);
                               NIC_BSET(sc, REG_0004, 0x08);
                               NIC_PUT_2(sc, REG_000E, 0x0200);
                       }
                       if (isr & ISR_BIT_0D) {
                               NIC_PUT_2(sc, REG_000E, 0);
                               tmp = NIC_GET_2(sc, REG_0002);
                               if (tmp & REG_0002_BIT_04)
                                       NIC_GET_2(sc, REG_0006);
                               tmp = NIC_GET_2(sc, REG_0000);
                               if (tmp & REG_0000_BIT_08)
                                       NIC_BSET(sc, REG_0000, 0x08);
                               NIC_PUT_2(sc, REG_000E, 0x0200);
                       }
                       NIC_PUT_2(sc, REG_ISR, isr);
               }
       }
       NIC_PUT_2(sc, REG_ISR, ISR_MASK);
}

static void
netdock_txint(struct netdock_softc *sc)
{
       struct ifnet *ifp = &sc->sc_if;
       int savereg0002;
       int reg0004;
       int regdata;

       ifp->if_timer = 0;

       savereg0002 = NIC_GET_2(sc, REG_0002);

       while (((reg0004 = NIC_GET_2(sc, REG_0004)) & REG_0004_BIT_0F) == 0) {
               NIC_PUT_2(sc, REG_0002, reg0004);
               NIC_PUT_2(sc, REG_0006, 0x0060);
               NIC_GET_2(sc, REG_ISR);
               regdata = NIC_GET_2(sc, REG_DATA);
               if ((regdata & REG_DATA_BIT_08) == 0) {
                       /* if_statinc(ifp, if_collisions); */
                       if (regdata & REG_DATA_BIT_07)
                       /* if_statinc(ifp, if_oerrors); */
                       NIC_PUT_2(sc, REG_000E, 0);
                       NIC_ORW(sc, REG_0000, 0x0100);
                       NIC_PUT_2(sc, REG_000E, 0x0200);
               }
               NIC_GET_2(sc ,REG_DATA);

               if (regdata & REG_DATA_BIT_0F)
                       NIC_GET_4(sc, REG_EFD00);
               NIC_PUT_2(sc, REG_0000, 0xa000);
               NIC_PUT_2(sc, REG_ISR, ISR_TX);
       }

       NIC_PUT_2(sc, REG_0002, savereg0002);
       NIC_PUT_2(sc, REG_000E, 0);
       NIC_GET_2(sc, REG_0006);
       NIC_PUT_2(sc, REG_000E, 0x0200);
       NIC_PUT_2(sc, REG_0006, 0);
}

static void
netdock_rxint(struct netdock_softc *sc)
{
       struct ifnet *ifp = &sc->sc_if;
       int regdata1;
       int regdata2;
       u_int len;
       int timeout;

       while ((NIC_GET_2(sc, REG_0004) & REG_0004_BIT_07) == 0) {
               NIC_GET_2(sc, REG_ISR);
               NIC_PUT_2(sc, REG_0006, 0x00e0);
               NIC_GET_2(sc, REG_ISR);
               regdata1 = NIC_GET_2(sc, REG_DATA);
               regdata2 = NIC_GET_2(sc, REG_DATA);
               len = ((regdata2 << 8) & 0x0700) | ((regdata2 >> 8) & 0x00ff);

#if 0
               printf("netdock_rxint: r1=0x%04x, r2=0x%04x, len=%d\n",
                      regdata1, regdata2, len);
#endif

               if ((regdata1 & REG_DATA_BIT_04) == 0)
                       len -= 2;

               /* CRC is included with the packet; trim it off. */
               len -= ETHER_CRC_LEN;

               if ((regdata1 & 0x00ac) == 0) {
                       if (netdock_read(sc, len) == 0)
                               if_statinc(ifp, if_ierrors);
               } else {
                       if_statinc(ifp, if_ierrors);

                       if (regdata1 & REG_DATA_BIT_02)
                               NIC_GET_4(sc, REG_EFD00);
                       if (regdata1 & REG_DATA_BIT_03)
                               ;
                       if (regdata1 & REG_DATA_BIT_05)
                               NIC_GET_4(sc, REG_EFD00);
                       if (regdata1 & REG_DATA_BIT_07)
                               ;
               }

               timeout = 0x14;
               while ((NIC_GET_2(sc, REG_0000) & REG_0000_BIT_08) && timeout--)
                       ;
               if (timeout == 0)
                       ;

               NIC_PUT_2(sc, REG_0000, 0x8000);
       }
       NIC_PUT_2(sc, REG_0006, 0);
}

static int
netdock_read(struct netdock_softc *sc, int len)
{
       struct ifnet *ifp = &sc->sc_if;
       struct mbuf *m;

       m = netdock_get(sc, len);
       if (m == 0)
               return (0);

       if_percpuq_enqueue(ifp->if_percpuq, m);

       return (1);
}

static struct mbuf *
netdock_get(struct netdock_softc *sc, int datalen)
{
       struct mbuf *m, *top, **mp;
       u_char *data;
       int i;
       int len;
       int len4;
       u_int32_t *data4;

       MGETHDR(m, M_DONTWAIT, MT_DATA);
       if (m == NULL)
               return (NULL);
       m_set_rcvif(m, &sc->sc_if);
       m->m_pkthdr.len = datalen;
       len = MHLEN;
       top = NULL;
       mp = &top;

       while (datalen > 0) {
               if (top) {
                       MGET(m, M_DONTWAIT, MT_DATA);
                       if (m == 0) {
                               m_freem(top);
                               return (NULL);
                       }
                       len = MLEN;
               }
               if (datalen >= MINCLSIZE) {
                       MCLGET(m, M_DONTWAIT);
                       if ((m->m_flags & M_EXT) == 0) {
                               if (top)
                                       m_freem(top);
                               else
                                       m_freem(m);
                               return (NULL);
                       }
                       len = MCLBYTES;
               }

               if (mp == &top) {
                       char *newdata = (char *)
                           ALIGN(m->m_data + sizeof(struct ether_header)) -
                           sizeof(struct ether_header);
                       len -= newdata - m->m_data;
                       m->m_data = newdata;
               }

               m->m_len = len = uimin(datalen, len);

               data = mtod(m, u_char *);
               len4 = len >> 2;
               data4 = (u_int32_t *)data;
               for (i = 0; i < len4; i++)
                       data4[i] = NIC_GET_4(sc, REG_DATA);
               for (i = len4 << 2; i < len; i++)
                       data[i] = NIC_GET_1(sc, REG_DATA);

               datalen -= len;
               *mp = m;
               mp = &m->m_next;
       }

       return (top);
}