root/usr/src/uts/common/io/efe/efe.c
/*
 * Copyright (c) 2010 Steven Stallion.  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. Neither the name of the copyright owner nor the names of any
 *        contributors may be used to endorse or promote products derived
 *        from this software without specific prior written permission.
 *
 * 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 COPYRIGHT OWNER 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.
 */

#include <sys/byteorder.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/varargs.h>
#include <sys/cmn_err.h>
#include <sys/note.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/modctl.h>
#include <sys/sysmacros.h>
#include <sys/ddi.h>
#include <sys/ddi_intr.h>
#include <sys/sunddi.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/pci.h>
#include <sys/ethernet.h>
#include <sys/vlan.h>
#include <sys/crc32.h>
#include <sys/mii.h>
#include <sys/mac.h>
#include <sys/mac_ether.h>
#include <sys/mac_provider.h>

#include "efe.h"

/* Autoconfiguration entry points */
static int      efe_attach(dev_info_t *, ddi_attach_cmd_t);
static int      efe_detach(dev_info_t *, ddi_detach_cmd_t);
static int      efe_quiesce(dev_info_t *);

/* MII entry points */
static uint16_t efe_mii_read(void *, uint8_t, uint8_t);
static void     efe_mii_write(void *, uint8_t, uint8_t, uint16_t);
static void     efe_mii_notify(void *, link_state_t);

/* MAC entry points */
static int      efe_m_getstat(void *, uint_t, uint64_t *);
static int      efe_m_start(void *);
static void     efe_m_stop(void *);
static int      efe_m_setpromisc(void *, boolean_t);
static int      efe_m_multicst(void *, boolean_t, const uint8_t *);
static int      efe_m_unicst(void *, const uint8_t *);
static mblk_t   *efe_m_tx(void *, mblk_t *);
static int      efe_m_setprop(void *, const char *, mac_prop_id_t, uint_t,
    const void *);
static int      efe_m_getprop(void *, const char *, mac_prop_id_t, uint_t,
    void *);
static void     efe_m_propinfo(void *, const char *, mac_prop_id_t,
    mac_prop_info_handle_t);

/* ISR/periodic callbacks */
static uint_t   efe_intr(caddr_t, caddr_t);

/* Support functions */
static void             efe_init(efe_t *);
static void             efe_init_rx_ring(efe_t *);
static void             efe_init_tx_ring(efe_t *);
static void             efe_reset(efe_t *);
static void             efe_start(efe_t *);
static void             efe_stop(efe_t *);
static void             efe_stop_dma(efe_t *);
static inline void      efe_restart(efe_t *);
static int              efe_suspend(efe_t *);
static int              efe_resume(efe_t *);

static efe_ring_t       *efe_ring_alloc(dev_info_t *, size_t);
static void             efe_ring_free(efe_ring_t **);
static efe_buf_t        *efe_buf_alloc(dev_info_t *, size_t);
static void             efe_buf_free(efe_buf_t **);

static void             efe_intr_enable(efe_t *);
static void             efe_intr_disable(efe_t *);

static mblk_t           *efe_recv(efe_t *);
static mblk_t           *efe_recv_pkt(efe_t *, efe_desc_t *);

static int              efe_send(efe_t *, mblk_t *);
static void             efe_send_done(efe_t *);

static void             efe_getaddr(efe_t *, uint8_t *);
static void             efe_setaddr(efe_t *, uint8_t *);
static void             efe_setmchash(efe_t *, uint16_t *);

static void             efe_eeprom_read(efe_t *, uint8_t *, size_t, uint8_t);
static uint16_t         efe_eeprom_readw(efe_t *, int, uint8_t);
static inline int       efe_eeprom_readbit(efe_t *);
static inline void      efe_eeprom_writebit(efe_t *, int);

static void             efe_dprintf(dev_info_t *, int, const char *, ...);

#ifdef DEBUG
#define efe_debug(dip, ...) \
        efe_dprintf((dip), CE_CONT, __VA_ARGS__)
#else
#define efe_debug(dip, ...)     /*EMPTY*/
#endif

#define efe_error(dip, ...) \
        efe_dprintf((dip), CE_WARN, __VA_ARGS__)

extern struct mod_ops mod_driverops;

DDI_DEFINE_STREAM_OPS(efe_dev_ops, nulldev, nulldev, efe_attach, efe_detach,
    nodev, NULL, D_MP, NULL, efe_quiesce);

static struct modldrv modldrv = {
        &mod_driverops,                 /* drv_modops */
        "EPIC/100 Fast Ethernet",       /* drv_linkinfo */
        &efe_dev_ops                    /* drv_dev_ops */
};

static struct modlinkage modlinkage = {
        MODREV_1,               /* ml_rev */
        { &modldrv, NULL }      /* ml_linkage */
};

static ddi_device_acc_attr_t efe_regs_acc_attr = {
        DDI_DEVICE_ATTR_V0,     /* devacc_attr_version */
        DDI_STRUCTURE_LE_ACC,   /* devacc_attr_endian_flags */
        DDI_STRICTORDER_ACC     /* devacc_attr_dataorder */
};

static ddi_device_acc_attr_t efe_buf_acc_attr = {
        DDI_DEVICE_ATTR_V0,     /* devacc_attr_version */
        DDI_NEVERSWAP_ACC,      /* devacc_attr_endian_flags */
        DDI_STRICTORDER_ACC     /* devacc_attr_dataorder */
};

static ddi_dma_attr_t efe_dma_attr = {
        DMA_ATTR_V0,            /* dma_attr_version */
        0,                      /* dma_attr_addr_lo */
        0xFFFFFFFFUL,           /* dma_attr_addr_hi */
        0x7FFFFFFFUL,           /* dma_attr_count_max */
        4,                      /* dma_attr_align */
        0x7F,                   /* dma_attr_burstsizes */
        1,                      /* dma_attr_minxfer */
        0xFFFFFFFFUL,           /* dma_attr_maxxfer */
        0xFFFFFFFFUL,           /* dma_attr_seg */
        1,                      /* dma_attr_sgllen */
        1,                      /* dma_attr_granular */
        0                       /* dma_attr_flags */
};

static mii_ops_t efe_mii_ops = {
        MII_OPS_VERSION,        /* mii_version */
        efe_mii_read,           /* mii_read */
        efe_mii_write,          /* mii_write */
        efe_mii_notify          /* mii_notify */
};

static mac_callbacks_t efe_m_callbacks = {
        MC_SETPROP | MC_GETPROP,        /* mc_callbacks */
        efe_m_getstat,                  /* mc_getstat */
        efe_m_start,                    /* mc_start */
        efe_m_stop,                     /* mc_stop */
        efe_m_setpromisc,               /* mc_setpromisc */
        efe_m_multicst,                 /* mc_multicst */
        efe_m_unicst,                   /* mc_unicst */
        efe_m_tx,                       /* mc_tx */
        NULL,                           /* mc_reserved */
        NULL,                           /* mc_ioctl */
        NULL,                           /* mc_getcapab */
        NULL,                           /* mc_open */
        NULL,                           /* mc_close */
        efe_m_setprop,                  /* mc_setprop */
        efe_m_getprop,                  /* mc_getprop */
        efe_m_propinfo                  /* mc_propinfo */
};

static uint8_t efe_broadcast[] = {
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

static uint16_t efe_mchash_promisc[] = {
        0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF
};

/*
 * Loadable module entry points.
 */
int
_init(void)
{
        int error;

        mac_init_ops(&efe_dev_ops, "efe");
        if ((error = mod_install(&modlinkage)) != DDI_SUCCESS) {
                mac_fini_ops(&efe_dev_ops);
        }

        return (error);
}

int
_fini(void)
{
        int error;

        if ((error = mod_remove(&modlinkage)) == DDI_SUCCESS) {
                mac_fini_ops(&efe_dev_ops);
        }

        return (error);
}

int
_info(struct modinfo *modinfop)
{
        return (mod_info(&modlinkage, modinfop));
}

/*
 * Autoconfiguration entry points.
 */
int
efe_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        ddi_acc_handle_t pci;
        int types;
        int count;
        int actual;
        uint_t pri;
        efe_t *efep;
        mac_register_t *macp;

        switch (cmd) {
        case DDI_ATTACH:
                break;

        case DDI_RESUME:
                efep = ddi_get_driver_private(dip);
                return (efe_resume(efep));

        default:
                return (DDI_FAILURE);
        }

        /*
         * PCI configuration.
         */
        if (pci_config_setup(dip, &pci) != DDI_SUCCESS) {
                efe_error(dip, "unable to setup PCI configuration!");
                return (DDI_FAILURE);
        }

        pci_config_put16(pci, PCI_CONF_COMM,
            pci_config_get16(pci, PCI_CONF_COMM) | PCI_COMM_MAE | PCI_COMM_ME);

        pci_config_teardown(&pci);

        if (ddi_intr_get_supported_types(dip, &types)
            != DDI_SUCCESS || !(types & DDI_INTR_TYPE_FIXED)) {
                efe_error(dip, "fixed interrupts not supported!");
                return (DDI_FAILURE);
        }

        if (ddi_intr_get_nintrs(dip, DDI_INTR_TYPE_FIXED, &count)
            != DDI_SUCCESS || count != 1) {
                efe_error(dip, "no fixed interrupts available!");
                return (DDI_FAILURE);
        }

        /*
         * Initialize soft state.
         */
        efep = kmem_zalloc(sizeof (efe_t), KM_SLEEP);
        ddi_set_driver_private(dip, efep);

        efep->efe_dip = dip;

        if (ddi_regs_map_setup(dip, 1, (caddr_t *)&efep->efe_regs, 0, 0,
            &efe_regs_acc_attr, &efep->efe_regs_acch) != DDI_SUCCESS) {
                efe_error(dip, "unable to setup register mapping!");
                goto failure;
        }

        efep->efe_rx_ring = efe_ring_alloc(efep->efe_dip, RXDESCL);
        if (efep->efe_rx_ring == NULL) {
                efe_error(efep->efe_dip, "unable to allocate rx ring!");
                goto failure;
        }

        efep->efe_tx_ring = efe_ring_alloc(efep->efe_dip, TXDESCL);
        if (efep->efe_tx_ring == NULL) {
                efe_error(efep->efe_dip, "unable to allocate tx ring!");
                goto failure;
        }

        if (ddi_intr_alloc(dip, &efep->efe_intrh, DDI_INTR_TYPE_FIXED, 0,
            count, &actual, DDI_INTR_ALLOC_STRICT) != DDI_SUCCESS ||
            actual != count) {
                efe_error(dip, "unable to allocate fixed interrupt!");
                goto failure;
        }

        if (ddi_intr_get_pri(efep->efe_intrh, &pri) != DDI_SUCCESS ||
            pri >= ddi_intr_get_hilevel_pri()) {
                efe_error(dip, "unable to get valid interrupt priority!");
                goto failure;
        }

        mutex_init(&efep->efe_intrlock, NULL, MUTEX_DRIVER,
            DDI_INTR_PRI(pri));

        mutex_init(&efep->efe_txlock, NULL, MUTEX_DRIVER,
            DDI_INTR_PRI(pri));

        /*
         * Initialize device.
         */
        mutex_enter(&efep->efe_intrlock);
        mutex_enter(&efep->efe_txlock);

        efe_reset(efep);

        mutex_exit(&efep->efe_txlock);
        mutex_exit(&efep->efe_intrlock);

        /* Use factory address as default */
        efe_getaddr(efep, efep->efe_macaddr);

        /*
         * Enable the ISR.
         */
        if (ddi_intr_add_handler(efep->efe_intrh, efe_intr, efep, NULL)
            != DDI_SUCCESS) {
                efe_error(dip, "unable to add interrupt handler!");
                goto failure;
        }

        if (ddi_intr_enable(efep->efe_intrh) != DDI_SUCCESS) {
                efe_error(dip, "unable to enable interrupt!");
                goto failure;
        }

        /*
         * Allocate MII resources.
         */
        if ((efep->efe_miih = mii_alloc(efep, dip, &efe_mii_ops)) == NULL) {
                efe_error(dip, "unable to allocate mii resources!");
                goto failure;
        }

        /*
         * Allocate MAC resources.
         */
        if ((macp = mac_alloc(MAC_VERSION)) == NULL) {
                efe_error(dip, "unable to allocate mac resources!");
                goto failure;
        }

        macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER;
        macp->m_driver = efep;
        macp->m_dip = dip;
        macp->m_src_addr = efep->efe_macaddr;
        macp->m_callbacks = &efe_m_callbacks;
        macp->m_min_sdu = 0;
        macp->m_max_sdu = ETHERMTU;
        macp->m_margin = VLAN_TAGSZ;

        if (mac_register(macp, &efep->efe_mh) != 0) {
                efe_error(dip, "unable to register with mac!");
                goto failure;
        }
        mac_free(macp);

        ddi_report_dev(dip);

        return (DDI_SUCCESS);

failure:
        if (macp != NULL) {
                mac_free(macp);
        }

        if (efep->efe_miih != NULL) {
                mii_free(efep->efe_miih);
        }

        if (efep->efe_intrh != NULL) {
                (void) ddi_intr_disable(efep->efe_intrh);
                (void) ddi_intr_remove_handler(efep->efe_intrh);
                (void) ddi_intr_free(efep->efe_intrh);
        }

        mutex_destroy(&efep->efe_txlock);
        mutex_destroy(&efep->efe_intrlock);

        if (efep->efe_tx_ring != NULL) {
                efe_ring_free(&efep->efe_tx_ring);
        }
        if (efep->efe_rx_ring != NULL) {
                efe_ring_free(&efep->efe_rx_ring);
        }

        if (efep->efe_regs_acch != NULL) {
                ddi_regs_map_free(&efep->efe_regs_acch);
        }

        kmem_free(efep, sizeof (efe_t));

        return (DDI_FAILURE);
}

int
efe_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        efe_t *efep = ddi_get_driver_private(dip);

        switch (cmd) {
        case DDI_DETACH:
                break;

        case DDI_SUSPEND:
                return (efe_suspend(efep));

        default:
                return (DDI_FAILURE);
        }

        if (mac_unregister(efep->efe_mh) != 0) {
                efe_error(dip, "unable to unregister from mac!");
                return (DDI_FAILURE);
        }

        mii_free(efep->efe_miih);

        (void) ddi_intr_disable(efep->efe_intrh);
        (void) ddi_intr_remove_handler(efep->efe_intrh);
        (void) ddi_intr_free(efep->efe_intrh);

        mutex_destroy(&efep->efe_txlock);
        mutex_destroy(&efep->efe_intrlock);

        if (efep->efe_tx_ring != NULL) {
                efe_ring_free(&efep->efe_tx_ring);
        }
        if (efep->efe_rx_ring != NULL) {
                efe_ring_free(&efep->efe_rx_ring);
        }

        ddi_regs_map_free(&efep->efe_regs_acch);

        kmem_free(efep, sizeof (efe_t));

        return (DDI_SUCCESS);
}

int
efe_quiesce(dev_info_t *dip)
{
        efe_t *efep = ddi_get_driver_private(dip);

        PUTCSR(efep, CSR_GENCTL, GENCTL_RESET);
        drv_usecwait(RESET_DELAY);

        PUTCSR(efep, CSR_GENCTL, GENCTL_PWRDWN);

        return (DDI_SUCCESS);
}

/*
 * MII entry points.
 */
uint16_t
efe_mii_read(void *arg, uint8_t phy, uint8_t reg)
{
        efe_t *efep = arg;

        PUTCSR(efep, CSR_MMCTL, MMCTL_READ |
            reg << MMCTL_PHYREG | phy << MMCTL_PHYADDR);

        for (int i = 0; i < MII_DELAY_CYCLES; ++i) {
                if (!(GETCSR(efep, CSR_MMCTL) & MMCTL_READ)) {
                        return ((uint16_t)GETCSR(efep, CSR_MMDATA));
                }
                drv_usecwait(MII_DELAY);
        }
        efe_error(efep->efe_dip, "timed out reading from MII!");

        return (0);
}

void
efe_mii_write(void *arg, uint8_t phy, uint8_t reg, uint16_t data)
{
        efe_t *efep = arg;

        PUTCSR(efep, CSR_MMDATA, data);

        PUTCSR(efep, CSR_MMCTL, MMCTL_WRITE |
            reg << MMCTL_PHYREG | phy << MMCTL_PHYADDR);

        for (int i = 0; i < MII_DELAY_CYCLES; ++i) {
                if (!(GETCSR(efep, CSR_MMCTL) & MMCTL_WRITE)) {
                        return;
                }
                drv_usecwait(MII_DELAY);
        }
        efe_error(efep->efe_dip, "timed out writing to MII!");
}

void
efe_mii_notify(void *arg, link_state_t link)
{
        efe_t *efep = arg;

        mac_link_update(efep->efe_mh, link);
}

/*
 * MAC entry points.
 */
int
efe_m_getstat(void *arg, uint_t stat, uint64_t *val)
{
        efe_t *efep = arg;

        if (mii_m_getstat(efep->efe_miih, stat, val) == 0) {
                return (0);
        }

        switch (stat) {
        case MAC_STAT_MULTIRCV:
                *val = efep->efe_multircv;
                break;

        case MAC_STAT_BRDCSTRCV:
                *val = efep->efe_brdcstrcv;
                break;

        case MAC_STAT_MULTIXMT:
                *val = efep->efe_multixmt;
                break;

        case MAC_STAT_BRDCSTXMT:
                *val = efep->efe_brdcstxmt;
                break;

        case MAC_STAT_NORCVBUF:
                *val = efep->efe_norcvbuf;
                break;

        case MAC_STAT_IERRORS:
                *val = efep->efe_ierrors;
                break;

        case MAC_STAT_NOXMTBUF:
                *val = efep->efe_noxmtbuf;
                break;

        case MAC_STAT_OERRORS:
                *val = efep->efe_oerrors;
                break;

        case MAC_STAT_COLLISIONS:
                *val = efep->efe_collisions;
                break;

        case MAC_STAT_RBYTES:
                *val = efep->efe_rbytes;
                break;

        case MAC_STAT_IPACKETS:
                *val = efep->efe_ipackets;
                break;

        case MAC_STAT_OBYTES:
                *val = efep->efe_obytes;
                break;

        case MAC_STAT_OPACKETS:
                *val = efep->efe_opackets;
                break;

        case MAC_STAT_UNDERFLOWS:
                *val = efep->efe_uflo;
                break;

        case MAC_STAT_OVERFLOWS:
                *val = efep->efe_oflo;
                break;

        case ETHER_STAT_ALIGN_ERRORS:
                *val = efep->efe_align_errors;
                break;

        case ETHER_STAT_FCS_ERRORS:
                *val = efep->efe_fcs_errors;
                break;

        case ETHER_STAT_FIRST_COLLISIONS:
                *val = efep->efe_first_collisions;
                break;

        case ETHER_STAT_TX_LATE_COLLISIONS:
                *val = efep->efe_tx_late_collisions;
                break;

        case ETHER_STAT_DEFER_XMTS:
                *val = efep->efe_defer_xmts;
                break;

        case ETHER_STAT_EX_COLLISIONS:
                *val = efep->efe_ex_collisions;
                break;

        case ETHER_STAT_MACXMT_ERRORS:
                *val = efep->efe_macxmt_errors;
                break;

        case ETHER_STAT_CARRIER_ERRORS:
                *val = efep->efe_carrier_errors;
                break;

        case ETHER_STAT_TOOLONG_ERRORS:
                *val = efep->efe_toolong_errors;
                break;

        case ETHER_STAT_MACRCV_ERRORS:
                *val = efep->efe_macrcv_errors;
                break;

        case ETHER_STAT_TOOSHORT_ERRORS:
                *val = efep->efe_runt_errors;
                break;

        case ETHER_STAT_JABBER_ERRORS:
                *val = efep->efe_jabber_errors;
                break;

        default:
                return (ENOTSUP);
        }

        return (0);
}

int
efe_m_start(void *arg)
{
        efe_t *efep = arg;

        mutex_enter(&efep->efe_intrlock);
        mutex_enter(&efep->efe_txlock);

        efe_start(efep);
        efep->efe_flags |= FLAG_RUNNING;

        mutex_exit(&efep->efe_txlock);
        mutex_exit(&efep->efe_intrlock);

        mii_start(efep->efe_miih);

        return (0);
}

void
efe_m_stop(void *arg)
{
        efe_t *efep = arg;

        mutex_enter(&efep->efe_intrlock);
        mutex_enter(&efep->efe_txlock);

        efe_stop(efep);
        efep->efe_flags &= ~FLAG_RUNNING;

        mutex_exit(&efep->efe_txlock);
        mutex_exit(&efep->efe_intrlock);

        mii_stop(efep->efe_miih);
}

int
efe_m_setpromisc(void *arg, boolean_t on)
{
        efe_t *efep = arg;

        mutex_enter(&efep->efe_intrlock);
        mutex_enter(&efep->efe_txlock);

        if (efep->efe_flags & FLAG_SUSPENDED) {
                mutex_exit(&efep->efe_txlock);
                mutex_exit(&efep->efe_intrlock);
                return (0);
        }

        efep->efe_promisc = on;

        if (efep->efe_flags & FLAG_RUNNING) {
                efe_restart(efep);
        }

        mutex_exit(&efep->efe_txlock);
        mutex_exit(&efep->efe_intrlock);

        return (0);
}

int
efe_m_multicst(void *arg, boolean_t add, const uint8_t *macaddr)
{
        efe_t *efep = arg;
        uint32_t val;
        int index;
        int bit;
        boolean_t restart = B_FALSE;

        mutex_enter(&efep->efe_intrlock);
        mutex_enter(&efep->efe_txlock);

        if (efep->efe_flags & FLAG_SUSPENDED) {
                mutex_exit(&efep->efe_txlock);
                mutex_exit(&efep->efe_intrlock);
                return (0);
        }

        CRC32(val, macaddr, ETHERADDRL, -1U, crc32_table);
        val %= MCHASHL;

        index = val / MCHASHSZ;
        bit = 1U << (val % MCHASHSZ);

        if (add) {
                efep->efe_mccount[val]++;
                if (efep->efe_mccount[val] == 1) {
                        efep->efe_mchash[index] |= bit;
                        restart = B_TRUE;
                }

        } else {
                efep->efe_mccount[val]--;
                if (efep->efe_mccount[val] == 0) {
                        efep->efe_mchash[index] &= ~bit;
                        restart = B_TRUE;
                }
        }

        if (restart && efep->efe_flags & FLAG_RUNNING) {
                efe_restart(efep);
        }

        mutex_exit(&efep->efe_txlock);
        mutex_exit(&efep->efe_intrlock);

        return (0);
}

int
efe_m_unicst(void *arg, const uint8_t *macaddr)
{
        efe_t *efep = arg;

        mutex_enter(&efep->efe_intrlock);
        mutex_enter(&efep->efe_txlock);

        if (efep->efe_flags & FLAG_SUSPENDED) {
                mutex_exit(&efep->efe_txlock);
                mutex_exit(&efep->efe_intrlock);
                return (0);
        }

        bcopy(macaddr, efep->efe_macaddr, ETHERADDRL);

        if (efep->efe_flags & FLAG_RUNNING) {
                efe_restart(efep);
        }

        mutex_exit(&efep->efe_txlock);
        mutex_exit(&efep->efe_intrlock);

        return (0);
}

mblk_t *
efe_m_tx(void *arg, mblk_t *mp)
{
        efe_t *efep = arg;

        mutex_enter(&efep->efe_txlock);

        if (efep->efe_flags & FLAG_SUSPENDED) {
                mutex_exit(&efep->efe_txlock);
                return (mp);
        }

        while (mp != NULL) {
                mblk_t *tmp = mp->b_next;
                mp->b_next = NULL;

                if (efe_send(efep, mp) != DDI_SUCCESS) {
                        mp->b_next = tmp;
                        break;
                }
                mp = tmp;
        }

        /* Kick the transmitter */
        PUTCSR(efep, CSR_COMMAND, COMMAND_TXQUEUED);

        mutex_exit(&efep->efe_txlock);

        return (mp);
}

int
efe_m_setprop(void *arg, const char *name, mac_prop_id_t id,
    uint_t valsize, const void *val)
{
        efe_t *efep = arg;

        return (mii_m_setprop(efep->efe_miih, name, id, valsize, val));
}

int
efe_m_getprop(void *arg, const char *name, mac_prop_id_t id,
    uint_t valsize, void *val)
{
        efe_t *efep = arg;

        return (mii_m_getprop(efep->efe_miih, name, id, valsize, val));
}

void
efe_m_propinfo(void *arg, const char *name, mac_prop_id_t id,
    mac_prop_info_handle_t state)
{
        efe_t *efep = arg;

        mii_m_propinfo(efep->efe_miih, name, id, state);
}

/*
 * ISR/periodic callbacks.
 */
uint_t
efe_intr(caddr_t arg1, caddr_t arg2)
{
        efe_t *efep = (void *)arg1;
        uint32_t status;
        mblk_t *mp = NULL;

        _NOTE(ARGUNUSED(arg2));

        mutex_enter(&efep->efe_intrlock);

        if (efep->efe_flags & FLAG_SUSPENDED) {
                mutex_exit(&efep->efe_intrlock);
                return (DDI_INTR_UNCLAIMED);
        }

        status = GETCSR(efep, CSR_INTSTAT);
        if (!(status & INTSTAT_ACTV)) {
                mutex_exit(&efep->efe_intrlock);
                return (DDI_INTR_UNCLAIMED);
        }
        PUTCSR(efep, CSR_INTSTAT, status);

        if (status & INTSTAT_RCC) {
                mp = efe_recv(efep);
        }

        if (status & INTSTAT_RQE) {
                efep->efe_ierrors++;
                efep->efe_macrcv_errors++;

                /* Kick the receiver */
                PUTCSR(efep, CSR_COMMAND, COMMAND_RXQUEUED);
        }

        if (status & INTSTAT_TXC) {
                mutex_enter(&efep->efe_txlock);

                efe_send_done(efep);

                mutex_exit(&efep->efe_txlock);
        }

        if (status & INTSTAT_FATAL) {
                mutex_enter(&efep->efe_txlock);

                efe_error(efep->efe_dip, "bus error; resetting!");
                efe_restart(efep);

                mutex_exit(&efep->efe_txlock);
        }

        mutex_exit(&efep->efe_intrlock);

        if (mp != NULL) {
                mac_rx(efep->efe_mh, NULL, mp);
        }

        if (status & INTSTAT_TXC) {
                mac_tx_update(efep->efe_mh);
        }

        if (status & INTSTAT_FATAL) {
                mii_reset(efep->efe_miih);
        }

        return (DDI_INTR_CLAIMED);
}

/*
 * Support functions.
 */
void
efe_init(efe_t *efep)
{
        uint32_t val;

        ASSERT(mutex_owned(&efep->efe_intrlock));
        ASSERT(mutex_owned(&efep->efe_txlock));

        efe_reset(efep);

        val = GENCTL_ONECOPY | GENCTL_RFT_128 | GENCTL_MRM;
#ifdef _BIG_ENDIAN
        val |= GENCTL_BE;
#endif  /* _BIG_ENDIAN */

        PUTCSR(efep, CSR_GENCTL, val);
        PUTCSR(efep, CSR_PBLCNT, BURSTLEN);

        efe_init_rx_ring(efep);
        efe_init_tx_ring(efep);

        efe_setaddr(efep, efep->efe_macaddr);

        if (efep->efe_promisc) {
                efe_setmchash(efep, efe_mchash_promisc);
        } else {
                efe_setmchash(efep, efep->efe_mchash);
        }
}

void
efe_init_rx_ring(efe_t *efep)
{
        efe_ring_t *rp;

        ASSERT(mutex_owned(&efep->efe_intrlock));

        rp = efep->efe_rx_ring;

        for (int i = 0; i < DESCLEN(rp); ++i) {
                efe_desc_t *dp = GETDESC(rp, i);
                efe_buf_t *bp = GETBUF(rp, i);

                PUTDESC16(rp, &dp->d_status, RXSTAT_OWNER);
                PUTDESC16(rp, &dp->d_len, 0);
                PUTDESC32(rp, &dp->d_bufaddr, BUFADDR(bp));
                PUTDESC16(rp, &dp->d_buflen, BUFLEN(bp));
                PUTDESC16(rp, &dp->d_control, 0);
                PUTDESC32(rp, &dp->d_next, NEXTDESCADDR(rp, i));

                SYNCDESC(rp, i, DDI_DMA_SYNC_FORDEV);
        }

        efep->efe_rx_desc = 0;

        PUTCSR(efep, CSR_PRCDAR, DESCADDR(rp, 0));
}

void
efe_init_tx_ring(efe_t *efep)
{
        efe_ring_t *rp;

        ASSERT(mutex_owned(&efep->efe_txlock));

        rp = efep->efe_tx_ring;

        for (int i = 0; i < DESCLEN(rp); ++i) {
                efe_desc_t *dp = GETDESC(rp, i);
                efe_buf_t *bp = GETBUF(rp, i);

                PUTDESC16(rp, &dp->d_status, 0);
                PUTDESC16(rp, &dp->d_len, 0);
                PUTDESC32(rp, &dp->d_bufaddr, BUFADDR(bp));
                PUTDESC16(rp, &dp->d_buflen, BUFLEN(bp));
                PUTDESC16(rp, &dp->d_control, 0);
                PUTDESC32(rp, &dp->d_next, NEXTDESCADDR(rp, i));

                SYNCDESC(rp, i, DDI_DMA_SYNC_FORDEV);
        }

        efep->efe_tx_desc = 0;
        efep->efe_tx_sent = 0;

        PUTCSR(efep, CSR_PTCDAR, DESCADDR(rp, 0));
}

void
efe_reset(efe_t *efep)
{
        ASSERT(mutex_owned(&efep->efe_intrlock));
        ASSERT(mutex_owned(&efep->efe_txlock));

        PUTCSR(efep, CSR_GENCTL, GENCTL_RESET);
        drv_usecwait(RESET_DELAY);

        /* Assert internal clock source (AN 7.15) */
        for (int i = 0; i < RESET_TEST_CYCLES; ++i) {
                PUTCSR(efep, CSR_TEST, TEST_CLOCK);
        }
}

void
efe_start(efe_t *efep)
{
        ASSERT(mutex_owned(&efep->efe_intrlock));
        ASSERT(mutex_owned(&efep->efe_txlock));

        efe_init(efep);

        PUTCSR(efep, CSR_RXCON,
            RXCON_SEP | RXCON_RRF | RXCON_RBF | RXCON_RMF |
            (efep->efe_promisc ? RXCON_PROMISC : 0));

        PUTCSR(efep, CSR_TXCON, TXCON_LB_3);

        efe_intr_enable(efep);

        SETBIT(efep, CSR_COMMAND,
            COMMAND_START_RX | COMMAND_RXQUEUED);
}

void
efe_stop(efe_t *efep)
{
        ASSERT(mutex_owned(&efep->efe_intrlock));
        ASSERT(mutex_owned(&efep->efe_txlock));

        efe_intr_disable(efep);

        PUTCSR(efep, CSR_COMMAND, COMMAND_STOP_RX);

        efe_stop_dma(efep);

        PUTCSR(efep, CSR_GENCTL, GENCTL_RESET);
        drv_usecwait(RESET_DELAY);

        PUTCSR(efep, CSR_GENCTL, GENCTL_PWRDWN);
}

void
efe_stop_dma(efe_t *efep)
{
        ASSERT(mutex_owned(&efep->efe_intrlock));
        ASSERT(mutex_owned(&efep->efe_txlock));

        PUTCSR(efep, CSR_COMMAND,
            COMMAND_STOP_RDMA | COMMAND_STOP_TDMA);

        for (int i = 0; i < STOP_DELAY_CYCLES; ++i) {
                uint32_t status = GETCSR(efep, CSR_INTSTAT);
                if (status & INTSTAT_RXIDLE &&
                    status & INTSTAT_TXIDLE) {
                        return;
                }
                drv_usecwait(STOP_DELAY);
        }
        efe_error(efep->efe_dip, "timed out stopping DMA engine!");
}

static inline void
efe_restart(efe_t *efep)
{
        efe_stop(efep);
        efe_start(efep);
}

int
efe_suspend(efe_t *efep)
{
        mutex_enter(&efep->efe_intrlock);
        mutex_enter(&efep->efe_txlock);

        if (efep->efe_flags & FLAG_RUNNING) {
                efe_stop(efep);
        }
        efep->efe_flags |= FLAG_SUSPENDED;

        mutex_exit(&efep->efe_txlock);
        mutex_exit(&efep->efe_intrlock);

        mii_suspend(efep->efe_miih);

        return (DDI_SUCCESS);
}

int
efe_resume(efe_t *efep)
{
        mutex_enter(&efep->efe_intrlock);
        mutex_enter(&efep->efe_txlock);

        if (efep->efe_flags & FLAG_RUNNING) {
                efe_start(efep);
        }
        efep->efe_flags &= ~FLAG_SUSPENDED;

        mutex_exit(&efep->efe_txlock);
        mutex_exit(&efep->efe_intrlock);

        mii_resume(efep->efe_miih);

        return (DDI_SUCCESS);
}

efe_ring_t *
efe_ring_alloc(dev_info_t *dip, size_t len)
{
        efe_ring_t *rp;
        size_t rlen;
        uint_t ccount;

        ASSERT(len > 1);

        rp = kmem_zalloc(sizeof (efe_ring_t), KM_SLEEP);
        rp->r_len = len;

        if (ddi_dma_alloc_handle(dip, &efe_dma_attr, DDI_DMA_SLEEP, NULL,
            &rp->r_dmah) != DDI_SUCCESS) {
                efe_error(dip, "unable to allocate DMA handle!");
                goto failure;
        }

        if (ddi_dma_mem_alloc(rp->r_dmah, DESCSZ(len), &efe_buf_acc_attr,
            DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&rp->r_descp,
            &rlen, &rp->r_acch) != DDI_SUCCESS) {
                efe_error(dip, "unable to allocate descriptors!");
                goto failure;
        }

        if (ddi_dma_addr_bind_handle(rp->r_dmah, NULL, (caddr_t)rp->r_descp,
            DESCSZ(len), DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP,
            NULL, &rp->r_dmac, &ccount) != DDI_DMA_MAPPED) {
                efe_error(dip, "unable to bind DMA handle to descriptors!");
                goto failure;
        }

        rp->r_bufpp = kmem_zalloc(BUFPSZ(len), KM_SLEEP);

        for (int i = 0; i < len; ++i) {
                efe_buf_t *bp = efe_buf_alloc(dip, BUFSZ);
                if (bp == NULL) {
                        goto failure;
                }
                rp->r_bufpp[i] = bp;
        }

        return (rp);

failure:
        efe_ring_free(&rp);

        return (NULL);
}

void
efe_ring_free(efe_ring_t **rpp)
{
        efe_ring_t *rp = *rpp;

        ASSERT(rp != NULL);

        for (int i = 0; i < DESCLEN(rp); ++i) {
                efe_buf_t *bp = GETBUF(rp, i);
                if (bp != NULL) {
                        efe_buf_free(&bp);
                }
        }
        kmem_free(rp->r_bufpp, BUFPSZ(DESCLEN(rp)));

        if (rp->r_descp != NULL) {
                (void) ddi_dma_unbind_handle(rp->r_dmah);
        }
        if (rp->r_acch != NULL) {
                ddi_dma_mem_free(&rp->r_acch);
        }
        if (rp->r_dmah != NULL) {
                ddi_dma_free_handle(&rp->r_dmah);
        }
        kmem_free(rp, sizeof (efe_ring_t));

        *rpp = NULL;
}

efe_buf_t *
efe_buf_alloc(dev_info_t *dip, size_t len)
{
        efe_buf_t *bp;
        size_t rlen;
        uint_t ccount;

        bp = kmem_zalloc(sizeof (efe_buf_t), KM_SLEEP);
        bp->b_len = len;

        if (ddi_dma_alloc_handle(dip, &efe_dma_attr, DDI_DMA_SLEEP, NULL,
            &bp->b_dmah) != DDI_SUCCESS) {
                efe_error(dip, "unable to allocate DMA handle!");
                goto failure;
        }

        if (ddi_dma_mem_alloc(bp->b_dmah, len, &efe_buf_acc_attr,
            DDI_DMA_STREAMING, DDI_DMA_SLEEP, NULL, &bp->b_kaddr, &rlen,
            &bp->b_acch) != DDI_SUCCESS) {
                efe_error(dip, "unable to allocate buffer!");
                goto failure;
        }

        if (ddi_dma_addr_bind_handle(bp->b_dmah, NULL, bp->b_kaddr,
            len, DDI_DMA_RDWR | DDI_DMA_STREAMING, DDI_DMA_SLEEP, NULL,
            &bp->b_dmac, &ccount) != DDI_DMA_MAPPED) {
                efe_error(dip, "unable to bind DMA handle to buffer!");
                goto failure;
        }

        return (bp);

failure:
        efe_buf_free(&bp);

        return (NULL);
}

void
efe_buf_free(efe_buf_t **bpp)
{
        efe_buf_t *bp = *bpp;

        ASSERT(bp != NULL);

        if (bp->b_kaddr != NULL) {
                (void) ddi_dma_unbind_handle(bp->b_dmah);
        }
        if (bp->b_acch != NULL) {
                ddi_dma_mem_free(&bp->b_acch);
        }
        if (bp->b_dmah != NULL) {
                ddi_dma_free_handle(&bp->b_dmah);
        }
        kmem_free(bp, sizeof (efe_buf_t));

        *bpp = NULL;
}

void
efe_intr_enable(efe_t *efep)
{
        PUTCSR(efep, CSR_INTMASK,
            INTMASK_RCC | INTMASK_RQE | INTMASK_TXC | INTMASK_FATAL);

        SETBIT(efep, CSR_GENCTL, GENCTL_INT);
}

void
efe_intr_disable(efe_t *efep)
{
        PUTCSR(efep, CSR_INTMASK, 0);

        CLRBIT(efep, CSR_GENCTL, GENCTL_INT);
}

mblk_t *
efe_recv(efe_t *efep)
{
        efe_ring_t *rp;
        mblk_t *mp = NULL;
        mblk_t **mpp = &mp;

        ASSERT(mutex_owned(&efep->efe_intrlock));

        rp = efep->efe_rx_ring;

        for (;;) {
                efe_desc_t *dp;
                uint16_t status;

                dp = GETDESC(rp, efep->efe_rx_desc);
                SYNCDESC(rp, efep->efe_rx_desc, DDI_DMA_SYNC_FORKERNEL);

                status = GETDESC16(rp, &dp->d_status);

                /* Stop if device owns descriptor */
                if (status & RXSTAT_OWNER) {
                        break;
                }

                if (status & RXSTAT_PRI) {
                        mblk_t *tmp = efe_recv_pkt(efep, dp);
                        if (tmp != NULL) {
                                *mpp = tmp;
                                mpp = &tmp->b_next;
                        }

                } else {
                        efep->efe_ierrors++;

                        if (status & RXSTAT_FAE) {
                                efep->efe_align_errors++;
                        }
                        if (status & RXSTAT_CRC) {
                                efep->efe_fcs_errors++;
                        }
                        if (status & RXSTAT_MP) {
                                efep->efe_oflo++;
                        }
                }

                /* Release ownership to device */
                PUTDESC16(rp, &dp->d_status, RXSTAT_OWNER);

                SYNCDESC(rp, efep->efe_rx_desc, DDI_DMA_SYNC_FORDEV);

                efep->efe_rx_desc = NEXTDESC(rp, efep->efe_rx_desc);
        }

        return (mp);
}

mblk_t *
efe_recv_pkt(efe_t *efep, efe_desc_t *dp)
{
        efe_ring_t *rp;
        efe_buf_t *bp;
        uint16_t len;
        mblk_t *mp;
        uint16_t status;

        ASSERT(mutex_owned(&efep->efe_intrlock));

        rp = efep->efe_rx_ring;

        len = GETDESC16(rp, &dp->d_len) - ETHERFCSL;

        if (len < ETHERMIN) {
                efep->efe_ierrors++;
                efep->efe_runt_errors++;
                return (NULL);
        }

        if (len > ETHERMAX + VLAN_TAGSZ) {
                efep->efe_ierrors++;
                efep->efe_toolong_errors++;
                return (NULL);
        }

        mp = allocb(len, 0);
        if (mp == NULL) {
                efep->efe_ierrors++;
                efep->efe_norcvbuf++;
                return (NULL);
        }
        mp->b_wptr = mp->b_rptr + len;

        bp = GETBUF(rp, efep->efe_rx_desc);
        SYNCBUF(bp, DDI_DMA_SYNC_FORKERNEL);

        bcopy(bp->b_kaddr, mp->b_rptr, len);

        efep->efe_ipackets++;
        efep->efe_rbytes += len;

        status = GETDESC16(rp, &dp->d_status);

        if (status & RXSTAT_BAR) {
                efep->efe_brdcstrcv++;

        } else if (status & RXSTAT_MAR) {
                efep->efe_multircv++;
        }

        return (mp);
}

int
efe_send(efe_t *efep, mblk_t *mp)
{
        efe_ring_t *rp;
        uint16_t len;
        efe_desc_t *dp;
        uint16_t status;
        efe_buf_t *bp;

        ASSERT(mutex_owned(&efep->efe_txlock));

        rp = efep->efe_tx_ring;

        len = msgsize(mp);

        if (len > ETHERMAX + VLAN_TAGSZ) {
                efep->efe_oerrors++;
                efep->efe_macxmt_errors++;
                freemsg(mp);
                return (DDI_SUCCESS);
        }

        dp = GETDESC(rp, efep->efe_tx_desc);
        SYNCDESC(rp, efep->efe_tx_desc, DDI_DMA_SYNC_FORKERNEL);

        status = GETDESC16(efep->efe_tx_ring, &dp->d_status);

        /* Stop if device owns descriptor */
        if (status & TXSTAT_OWNER) {
                return (DDI_FAILURE);
        }

        bp = GETBUF(rp, efep->efe_tx_desc);

        mcopymsg(mp, bp->b_kaddr);

        /*
         * Packets must contain at least ETHERMIN octets.
         * Padded octets are zeroed out prior to sending.
         */
        if (len < ETHERMIN) {
                bzero(bp->b_kaddr + len, ETHERMIN - len);
                len = ETHERMIN;
        }

        SYNCBUF(bp, DDI_DMA_SYNC_FORDEV);

        PUTDESC16(rp, &dp->d_status, TXSTAT_OWNER);
        PUTDESC16(rp, &dp->d_len, len);
        PUTDESC16(rp, &dp->d_control, TXCTL_LASTDESCR);

        SYNCDESC(rp, efep->efe_tx_desc, DDI_DMA_SYNC_FORDEV);

        efep->efe_opackets++;
        efep->efe_obytes += len;

        if (*bp->b_kaddr & 0x01) {
                if (bcmp(bp->b_kaddr, efe_broadcast, ETHERADDRL) == 0) {
                        efep->efe_brdcstxmt++;
                } else {
                        efep->efe_multixmt++;
                }
        }

        efep->efe_tx_desc = NEXTDESC(rp, efep->efe_tx_desc);

        return (DDI_SUCCESS);
}

void
efe_send_done(efe_t *efep)
{
        efe_ring_t *rp;

        ASSERT(mutex_owned(&efep->efe_txlock));

        rp = efep->efe_tx_ring;

        for (;;) {
                efe_desc_t *dp;
                uint16_t status;

                dp = GETDESC(rp, efep->efe_tx_sent);
                SYNCDESC(rp, efep->efe_tx_sent, DDI_DMA_SYNC_FORKERNEL);

                status = GETDESC16(rp, &dp->d_status);

                /* Stop if device owns descriptor */
                if (status & TXSTAT_OWNER) {
                        break;
                }

                if (status & TXSTAT_PTX) {
                        if (!(status & TXSTAT_ND)) {
                                efep->efe_defer_xmts++;
                        }
                        if (status & TXSTAT_COLL) {
                                efep->efe_first_collisions++;
                        }

                } else {
                        efep->efe_oerrors++;

                        if (status & TXSTAT_CSL) {
                                efep->efe_carrier_errors++;
                        }
                        if (status & TXSTAT_UFLO) {
                                efep->efe_uflo++;
                        }
                        if (status & TXSTAT_OWC) {
                                efep->efe_tx_late_collisions++;
                        }
                        if (status & TXSTAT_DEFER) {
                                efep->efe_jabber_errors++;
                        }
                        if (status & TXSTAT_EXCOLL) {
                                efep->efe_ex_collisions++;
                        }
                }

                efep->efe_collisions +=
                    (status >> TXSTAT_CCNT) & TXSTAT_CCNTMASK;

                efep->efe_tx_sent = NEXTDESC(rp, efep->efe_tx_sent);
        }
}

void
efe_getaddr(efe_t *efep, uint8_t *macaddr)
{
        efe_eeprom_read(efep, macaddr, ETHERADDRL, 0x0);

        efe_debug(efep->efe_dip,
            "factory address is %02x:%02x:%02x:%02x:%02x:%02x\n",
            macaddr[0], macaddr[1], macaddr[2], macaddr[3],
            macaddr[4], macaddr[5]);
}

void
efe_setaddr(efe_t *efep, uint8_t *macaddr)
{
        uint16_t val;

        bcopy(macaddr, &val, sizeof (uint16_t));
        PUTCSR(efep, CSR_LAN0, val);
        macaddr += sizeof (uint16_t);

        bcopy(macaddr, &val, sizeof (uint16_t));
        PUTCSR(efep, CSR_LAN1, val);
        macaddr += sizeof (uint16_t);

        bcopy(macaddr, &val, sizeof (uint16_t));
        PUTCSR(efep, CSR_LAN2, val);
}

void
efe_setmchash(efe_t *efep, uint16_t *mchash)
{
        PUTCSR(efep, CSR_MC0, mchash[0]);
        PUTCSR(efep, CSR_MC1, mchash[1]);
        PUTCSR(efep, CSR_MC2, mchash[2]);
        PUTCSR(efep, CSR_MC3, mchash[3]);
}

void
efe_eeprom_read(efe_t *efep, uint8_t *buf, size_t len, uint8_t addr)
{
        int addrlen;

        ASSERT(len & ~0x1);     /* non-zero; word-aligned */

        PUTCSR(efep, CSR_EECTL, EECTL_ENABLE | EECTL_EECS);
        drv_usecwait(EEPROM_DELAY);

        addrlen = (GETCSR(efep, CSR_EECTL) & EECTL_SIZE ?
            AT93C46_ADDRLEN : AT93C56_ADDRLEN);

        for (int i = 0; i < len / sizeof (uint16_t); ++i) {
                uint16_t val = efe_eeprom_readw(efep, addrlen, addr + i);
                bcopy(&val, buf, sizeof (uint16_t));
                buf += sizeof (uint16_t);
        }
}

uint16_t
efe_eeprom_readw(efe_t *efep, int addrlen, uint8_t addr)
{
        uint16_t val = 0;

        ASSERT(addrlen > 0);

        /* Write Start Bit (SB) */
        efe_eeprom_writebit(efep, 1);

        /* Write READ instruction */
        efe_eeprom_writebit(efep, 1);
        efe_eeprom_writebit(efep, 0);

        /* Write EEPROM address */
        for (int i = addrlen - 1; i >= 0; --i) {
                efe_eeprom_writebit(efep, addr & 1U << i);
        }

        /* Read EEPROM word */
        for (int i = EEPROM_WORDSZ - 1; i >= 0; --i) {
                val |= efe_eeprom_readbit(efep) << i;
        }

        PUTCSR(efep, CSR_EECTL, EECTL_ENABLE);
        drv_usecwait(EEPROM_DELAY);

        return (val);
}

inline int
efe_eeprom_readbit(efe_t *efep)
{
        PUTCSR(efep, CSR_EECTL, EECTL_ENABLE | EECTL_EECS);
        drv_usecwait(EEPROM_DELAY);

        PUTCSR(efep, CSR_EECTL, EECTL_ENABLE | EECTL_EECS |
            EECTL_EESK);
        drv_usecwait(EEPROM_DELAY);

        PUTCSR(efep, CSR_EECTL, EECTL_ENABLE | EECTL_EECS);
        drv_usecwait(EEPROM_DELAY);

        return (!!(GETCSR(efep, CSR_EECTL) & EECTL_EEDO));
}

inline void
efe_eeprom_writebit(efe_t *efep, int bit)
{
        PUTCSR(efep, CSR_EECTL, EECTL_ENABLE | EECTL_EECS);
        drv_usecwait(EEPROM_DELAY);

        PUTCSR(efep, CSR_EECTL, EECTL_ENABLE | EECTL_EECS |
            EECTL_EESK | (bit ? EECTL_EEDI : 0));
        drv_usecwait(EEPROM_DELAY);

        PUTCSR(efep, CSR_EECTL, EECTL_ENABLE | EECTL_EECS);
        drv_usecwait(EEPROM_DELAY);
}

void
efe_dprintf(dev_info_t *dip, int level, const char *format, ...)
{
        va_list ap;
        char buf[255];

        va_start(ap, format);

        (void) vsnprintf(buf, sizeof (buf), format, ap);

        cmn_err(level, "?%s%d %s", ddi_driver_name(dip),
            ddi_get_instance(dip), buf);

        va_end(ap);
}