root/usr/src/uts/sun4u/serengeti/io/sgsbbc.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*
 * PCI SBBC Device Driver that provides interfaces into
 * EPLD and IO-SRAM
 *
 */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/cmn_err.h>
#include <sys/stropts.h>
#include <sys/kmem.h>
#include <sys/sunndi.h>
#include <sys/conf.h>           /* req. by dev_ops flags MTSAFE etc. */
#include <sys/modctl.h>         /* for modldrv */
#include <sys/promif.h>
#include <sys/stat.h>
#include <sys/ddi.h>

#include <sys/serengeti.h>
#include <sys/sgsbbc_priv.h>
#include <sys/sgsbbc_iosram_priv.h>
#include <sys/sgsbbc_mailbox_priv.h>

#ifdef DEBUG
/* debug flag */
uint_t sgsbbc_debug = 0;
#endif /* DEBUG */

/* driver entry point fn definitions */
static int      sbbc_attach(dev_info_t *, ddi_attach_cmd_t);
static int      sbbc_detach(dev_info_t *, ddi_detach_cmd_t);

/*
 * SBBC soft state hook
 */
static void    *sbbcp;

/*
 * Chosen IOSRAM
 */
struct chosen_iosram *master_iosram = NULL;

/*
 * define new iosram's sbbc and liked list of sbbc.
 */
struct sbbc_softstate *sgsbbc_instances = NULL;

/*
 * At attach time, check if the device is the 'chosen' node
 * if it is, set up the IOSRAM Solaris<->SC Comm tunnel
 * Its like 'Highlander' - there can be only one !
 */
static int      master_chosen = FALSE;
kmutex_t        chosen_lock;

/*
 * Local variable to save intr_in_enabled when the driver is suspended
 */
static uint32_t intr_in_enabled;

/*
 * Local declarations
 */
static void     softsp_init(sbbc_softstate_t *, dev_info_t *);
static void     sbbc_chosen_init(sbbc_softstate_t *);
static void     sbbc_add_instance(sbbc_softstate_t *);
static void     sbbc_remove_instance(sbbc_softstate_t *);
static int      sbbc_find_dip(dev_info_t *, void *);
static void     sbbc_unmap_regs(sbbc_softstate_t *);

/*
 * ops stuff.
 */
static struct cb_ops sbbc_cb_ops = {
        nodev,                                  /* cb_open */
        nodev,                                  /* cb_close */
        nodev,                                  /* cb_strategy */
        nodev,                                  /* cb_print */
        nodev,                                  /* cb_dump */
        nodev,                                  /* cb_read */
        nodev,                                  /* cb_write */
        nodev,                                  /* cb_ioctl */
        nodev,                                  /* cb_devmap */
        nodev,                                  /* cb_mmap */
        nodev,                                  /* cb_segmap */
        nochpoll,                               /* cb_chpoll */
        ddi_prop_op,                            /* cb_prop_op */
        NULL,                                   /* cb_stream */
        D_NEW | D_MP                            /* cb_flag */
};

/*
 * Declare ops vectors for auto configuration.
 */
struct dev_ops  sbbc_ops = {
        DEVO_REV,               /* devo_rev */
        0,                      /* devo_refcnt */
        ddi_getinfo_1to1,       /* devo_getinfo */
        nulldev,                /* devo_identify */
        nulldev,                /* devo_probe */
        sbbc_attach,            /* devo_attach */
        sbbc_detach,            /* devo_detach */
        nodev,                  /* devo_reset */
        &sbbc_cb_ops,           /* devo_cb_ops */
        (struct bus_ops *)NULL, /* devo_bus_ops */
        nulldev,                /* devo_power */
        ddi_quiesce_not_supported,      /* devo_quiesce */
};

/*
 * Loadable module support.
 */
extern struct mod_ops mod_driverops;

static struct modldrv modldrv = {
        &mod_driverops,         /* type of module - driver */
        "PCI SBBC",
        &sbbc_ops,
};

static struct modlinkage modlinkage = {
        MODREV_1,
        (void *)&modldrv,
        NULL
};

int
_init(void)
{
        int    error;

        if ((error = ddi_soft_state_init(&sbbcp,
            sizeof (sbbc_softstate_t), 1)) != 0)
                return (error);

        if ((error = mod_install(&modlinkage)) != 0) {
                ddi_soft_state_fini(&sbbcp);
                return (error);
        }

        /*
         * Initialise the global 'chosen' IOSRAM mutex
         */
        mutex_init(&chosen_lock, NULL, MUTEX_DEFAULT, NULL);

        /*
         * Initialise the iosram driver
         */
        iosram_init();

        /*
         * Initialize the mailbox
         */
        sbbc_mbox_init();

        return (error);

}

int
_fini(void)
{
        int    error;

        if ((error = mod_remove(&modlinkage)) == 0)
                ddi_soft_state_fini(&sbbcp);

        master_chosen = FALSE;

        mutex_destroy(&chosen_lock);

        /*
         * remove the mailbox
         */
        sbbc_mbox_fini();

        /*
         * remove the iosram driver
         */
        iosram_fini();

        return (error);
}

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

static int
sbbc_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
        int                     instance;
        sbbc_softstate_t        *softsp;
        uint32_t                *pci_intr_enable_reg;
        int                     len;
#ifdef  DEBUG
        char                    name[8];
#endif  /* DEBUG */

        instance = ddi_get_instance(devi);

        switch (cmd) {
        case DDI_ATTACH:

                if (ddi_soft_state_zalloc(sbbcp, instance) != 0)
                        return (DDI_FAILURE);

                softsp = ddi_get_soft_state(sbbcp, instance);
                softsp->sbbc_instance = instance;

                /*
                 * Set the dip in the soft state
                 * And get interrupt cookies and initialize the
                 * per instance mutex.
                 */
                softsp_init(softsp, devi);


                /*
                 * Verify that an 'interrupts' property exists for
                 * this device. If not, this instance will be ignored.
                 */
                if (ddi_getproplen(DDI_DEV_T_ANY, softsp->dip,
                    DDI_PROP_DONTPASS, "interrupts",
                    &len) != DDI_PROP_SUCCESS) {
                        SBBC_ERR1(CE_WARN, "No 'interrupts' property for the "
                            "SBBC instance %d\n", instance);
                        return (DDI_FAILURE);
                }
                /*
                 * Add this instance to the sbbc chosen iosram list
                 * so that it can be used for tunnel switch.
                 */
                mutex_enter(&chosen_lock);
                softsp->sbbc_state = SBBC_STATE_INIT;
                sbbc_add_instance(softsp);

                /*
                 * If this is the chosen IOSRAM and there is no master IOSRAM
                 * yet, then let's set this instance as the master.
                 * if there is a master alreay due to the previous tunnel switch
                 * then keep as is even though this is the chosen.
                 */
                if (sgsbbc_iosram_is_chosen(softsp)) {
                        ASSERT(master_iosram);
                        softsp->iosram = master_iosram;
                        master_iosram->sgsbbc = softsp;

                        /* Do 'chosen' init only */
                        sbbc_chosen_init(softsp);
                }

                mutex_exit(&chosen_lock);
#ifdef  DEBUG
                (void) sprintf(name, "sbbc%d", instance);

                if (ddi_create_minor_node(devi, name, S_IFCHR, instance,
                    NULL, NULL) == DDI_FAILURE) {
                        mutex_destroy(&softsp->sbbc_lock);
                        ddi_remove_minor_node(devi, NULL);
                        ddi_soft_state_free(sbbcp, instance);
                        return (DDI_FAILURE);
                }
#endif  /* DEBUG */

                ddi_report_dev(devi);

                return (DDI_SUCCESS);

        case DDI_RESUME:

                if (!(softsp = ddi_get_soft_state(sbbcp, instance)))
                        return (DDI_FAILURE);

                mutex_enter(&softsp->sbbc_lock);
                if ((softsp->suspended == TRUE) && (softsp->chosen == TRUE)) {
                        /*
                         * Enable Interrupts now, turn on both INT#A lines
                         */
                        pci_intr_enable_reg =  (uint32_t *)
                            ((char *)softsp->sbbc_regs +
                            SBBC_PCI_INT_ENABLE);

                        ddi_put32(softsp->sbbc_reg_handle1,
                            pci_intr_enable_reg,
                            (uint32_t)SBBC_PCI_ENABLE_INT_A);

                        /*
                         * Reset intr_in_enabled to the original value
                         * so the SC can send us interrupt.
                         */
                        if (iosram_write(SBBC_SC_INTR_ENABLED_KEY,
                            0, (caddr_t)&intr_in_enabled,
                            sizeof (intr_in_enabled))) {

                                mutex_exit(&softsp->sbbc_lock);
                                return (DDI_FAILURE);
                        }
                }
                softsp->suspended = FALSE;

                mutex_exit(&softsp->sbbc_lock);

                return (DDI_SUCCESS);

        default:
                return (DDI_FAILURE);
        }
}

static int
sbbc_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
        sbbc_softstate_t        *softsp;
        int                     instance;
        uint32_t                *pci_intr_enable_reg;
        int                     rc = DDI_SUCCESS;

        instance = ddi_get_instance(devi);

        if (!(softsp = ddi_get_soft_state(sbbcp, instance)))
                return (DDI_FAILURE);

        switch (cmd) {
        case DDI_DETACH:
                mutex_enter(&chosen_lock);
                softsp->sbbc_state |= SBBC_STATE_DETACH;
                mutex_exit(&chosen_lock);

                /* only tunnel switch the instance with iosram chosen */
                if (softsp->chosen == TRUE) {
                        if (sgsbbc_iosram_switchfrom(softsp) == DDI_FAILURE) {
                                SBBC_ERR(CE_WARN, "Cannot unconfigure: "
                                    "tunnel switch failed\n");
                                return (DDI_FAILURE);
                        }
                }

                /* Adjust linked list */
                mutex_enter(&chosen_lock);
                sbbc_remove_instance(softsp);
                mutex_exit(&chosen_lock);

                sbbc_unmap_regs(softsp);
                mutex_destroy(&softsp->sbbc_lock);
                ddi_soft_state_free(sbbcp, instance);

                return (DDI_SUCCESS);

        case DDI_SUSPEND:

                mutex_enter(&softsp->sbbc_lock);

                if ((softsp->suspended == FALSE) && (softsp->chosen == TRUE)) {
                        uint32_t        tmp_intr_enabled = 0;

                        /*
                         * Disable Interrupts now, turn OFF both INT#A lines
                         */
                        pci_intr_enable_reg =  (uint32_t *)
                            ((char *)softsp->sbbc_regs +
                            SBBC_PCI_INT_ENABLE);

                        ddi_put32(softsp->sbbc_reg_handle1,
                            pci_intr_enable_reg, 0);

                        /*
                         * Set intr_in_enabled to 0 so the SC won't send
                         * us interrupt.
                         */
                        rc = iosram_read(SBBC_SC_INTR_ENABLED_KEY,
                            0, (caddr_t)&intr_in_enabled,
                            sizeof (intr_in_enabled));

                        if (rc) {
                                mutex_exit(&softsp->sbbc_lock);
                                return (DDI_FAILURE);
                        }

                        rc = iosram_write(SBBC_SC_INTR_ENABLED_KEY,
                            0, (caddr_t)&tmp_intr_enabled,
                            sizeof (tmp_intr_enabled));

                        if (rc) {
                                mutex_exit(&softsp->sbbc_lock);
                                return (DDI_FAILURE);
                        }
                }
                softsp->suspended = TRUE;

                mutex_exit(&softsp->sbbc_lock);

                return (DDI_SUCCESS);

        default:
                return (DDI_FAILURE);
        }

}

static void
softsp_init(sbbc_softstate_t *softsp, dev_info_t *devi)
{
        softsp->dip = devi;

        /*
         * XXXX
         * ddi_get_iblock_cookie() here because we need
         * to initialise the mutex regardless of whether
         * or not this SBBC will eventually
         * register an interrupt handler
         */

        (void) ddi_get_iblock_cookie(devi, 0, &softsp->iblock);

        mutex_init(&softsp->sbbc_lock, NULL, MUTEX_DRIVER,
            (void *)softsp->iblock);

        softsp->suspended = FALSE;
        softsp->chosen = FALSE;
}

static int
sbbc_find_dip(dev_info_t *dip, void *arg)
{
        char            *node_name;
        sbbc_find_dip_t *dip_struct = (sbbc_find_dip_t *)arg;
        char            status[OBP_MAXPROPNAME];

        /*
         * Need to find a node named "bootbus-controller" that is neither
         * disabled nor failed.  If a node is not ok, there will be an
         * OBP status property.  Therefore, we will look for a node
         * without the status property.
         */
        node_name = ddi_node_name(dip);
        if (strcmp(node_name, "bootbus-controller") == 0 && DDI_CF2(dip) &&
            (prom_getprop(ddi_get_nodeid(dip),
            "status", (caddr_t)status) == -1) &&
            (prom_getprop(ddi_get_nodeid(ddi_get_parent(dip)),
            "status", (caddr_t)status) == -1)) {

                if (dip != dip_struct->cur_dip) {
                        dip_struct->new_dip = (void *)dip;
                        return (DDI_WALK_TERMINATE);
                }
        }

        return (DDI_WALK_CONTINUE);
}

/*
 * SBBC Interrupt Handler
 *
 * Check the SBBC Port Interrupt Status
 * register to verify that its our interrupt.
 * If yes, clear the register.
 *
 * Then read the 'interrupt reason' field from SRAM,
 * this triggers the appropriate soft_intr handler
 */
uint_t
sbbc_intr_handler(caddr_t arg)
{
        sbbc_softstate_t        *softsp = (sbbc_softstate_t *)arg;
        uint32_t                *port_int_reg;
        volatile uint32_t       port_int_status;
        volatile uint32_t       intr_reason;
        uint32_t                intr_enabled;
        sbbc_intrs_t            *intr;
        int                     i, intr_mask;
        struct tunnel_key       tunnel_key;
        ddi_acc_handle_t        intr_in_handle;
        uint32_t                *intr_in_reason;

        if (softsp == (sbbc_softstate_t *)NULL) {

                return (DDI_INTR_UNCLAIMED);
        }

        mutex_enter(&softsp->sbbc_lock);

        if (softsp->port_int_regs == NULL) {
                mutex_exit(&softsp->sbbc_lock);
                return (DDI_INTR_UNCLAIMED);
        }

        /*
         * Normally if port_int_status is 0, we assume it is not
         * our interrupt.  However, we don't want to miss the
         * ones that come in during tunnel switch.  Therefore,
         * we always check the interrupt reason bits in IOSRAM
         * to be sure.
         */
        port_int_reg = softsp->port_int_regs;

        port_int_status = ddi_get32(softsp->sbbc_reg_handle1, port_int_reg);

        /*
         * Generate a softint for each interrupt
         * bit set in the intr_in_reason field in SRAM
         * that has a corresponding bit set in the
         * intr_in_enabled field in SRAM
         */

        if (iosram_read(SBBC_SC_INTR_ENABLED_KEY, 0,
            (caddr_t)&intr_enabled, sizeof (intr_enabled))) {

                goto intr_handler_exit;
        }

        tunnel_key = master_iosram->tunnel->tunnel_keys[SBBC_SC_INTR_KEY];
        intr_in_reason = (uint32_t *)tunnel_key.base;
        intr_in_handle = tunnel_key.reg_handle;

        intr_reason = ddi_get32(intr_in_handle, intr_in_reason);

        SGSBBC_DBG_INTR(CE_CONT, "intr_reason = %x\n", intr_reason);

        intr_reason &= intr_enabled;

        for (i = 0; i < SBBC_MAX_INTRS; i++) {
                intr_mask = (1 << i);
                if (intr_reason & intr_mask) {
                        intr = &softsp->intr_hdlrs[i];
                        if ((intr != NULL) &&
                            (intr->sbbc_intr_id != 0)) {
                                /*
                                 * XXXX
                                 * The model we agree with a handler
                                 * is that they run until they have
                                 * exhausted all work. To avoid
                                 * triggering them again, they pass
                                 * a state flag and lock when registering.
                                 * We check the flag, if they are idle,
                                 * we trigger.
                                 * The interrupt handler should so
                                 *   intr_func()
                                 *      mutex_enter(sbbc_intr_lock);
                                 *      sbbc_intr_state = RUNNING;
                                 *      mutex_exit(sbbc_intr_lock);
                                 *        ..........
                                 *        ..........
                                 *        ..........
                                 *      mutex_enter(sbbc_intr_lock);
                                 *      sbbc_intr_state = IDLE;
                                 *      mutex_exit(sbbc_intr_lock);
                                 *
                                 * XXXX
                                 */
                                mutex_enter(intr->sbbc_intr_lock);
                                if (*(intr->sbbc_intr_state) ==
                                    SBBC_INTR_IDLE) {
                                        mutex_exit(intr->sbbc_intr_lock);
                                        ddi_trigger_softintr(
                                            intr->sbbc_intr_id);
                                } else {
                                        /*
                                         * The handler is running
                                         */
                                        mutex_exit(intr->sbbc_intr_lock);
                                }
                                intr_reason &= ~intr_mask;
                                /*
                                 * Clear the corresponding reason bit in SRAM
                                 *
                                 * Since there is no interlocking between
                                 * Solaris and the SC when writing to SRAM,
                                 * it is possible for the SC to set another
                                 * bit in the interrupt reason field while
                                 * we are handling the current interrupt.
                                 * To minimize the window in which an
                                 * additional bit can be set, reading
                                 * and writing the interrupt reason
                                 * in SRAM must be as close as possible.
                                 */
                                ddi_put32(intr_in_handle, intr_in_reason,
                                    ddi_get32(intr_in_handle,
                                    intr_in_reason) & ~intr_mask);
                        }
                }
                if (intr_reason == 0)   /* No more interrupts to be processed */
                        break;
        }

        /*
         * Clear the Interrupt Status Register (RW1C)
         */
        ddi_put32(softsp->sbbc_reg_handle1, port_int_reg, port_int_status);

        port_int_status = ddi_get32(softsp->sbbc_reg_handle1, port_int_reg);

intr_handler_exit:

        mutex_exit(&softsp->sbbc_lock);

        return (DDI_INTR_CLAIMED);

}

/*
 * If we don't already have a master SBBC selected,
 * get the <sbbc> property from the /chosen node. If
 * the pathname matches, this is the master SBBC and
 * we set up the console/TOD SRAM mapping here.
 */
static void
sbbc_chosen_init(sbbc_softstate_t *softsp)
{
        char            master_sbbc[MAXNAMELEN];
        char            pn[MAXNAMELEN];
        int             nodeid, len;
        pnode_t         dnode;

        if (master_chosen != FALSE) {
                /*
                 * We've got one already
                 */
                return;
        }

        /*
         * Get /chosen node info. prom interface will handle errors.
         */
        dnode = prom_chosennode();

        /*
         * Look for the "iosram" property on the chosen node with a prom
         * interface as ddi_find_devinfo() couldn't be used (calls
         * ddi_walk_devs() that creates one extra lock on the device tree).
         */
        if (prom_getprop(dnode, IOSRAM_CHOSEN_PROP, (caddr_t)&nodeid) <= 0) {
                /*
                 * No I/O Board SBBC set up as console, what to do ?
                 */
                SBBC_ERR(CE_PANIC, "No SBBC found for Console/TOD \n");
        }

        if (prom_getprop(dnode, IOSRAM_TOC_PROP,
            (caddr_t)&softsp->sram_toc) <= 0) {
                /*
                 * SRAM TOC Offset defaults to 0
                 */
                SBBC_ERR(CE_WARN, "No SBBC TOC Offset found\n");
                softsp->sram_toc = 0;
        }

        /*
         * get the full OBP pathname of this node
         */
        if (prom_phandle_to_path((phandle_t)nodeid, master_sbbc,
            sizeof (master_sbbc)) < 0) {

                SBBC_ERR1(CE_PANIC, "prom_phandle_to_path(%d) failed\n",
                    nodeid);
        }
        SGSBBC_DBG_ALL("chosen pathname : %s\n", master_sbbc);
        SGSBBC_DBG_ALL("device pathname : %s\n", ddi_pathname(softsp->dip, pn));
        if (strcmp(master_sbbc, ddi_pathname(softsp->dip, pn)) == 0) {

                /*
                 * map in the SBBC regs
                 */

                if (sbbc_map_regs(softsp) != DDI_SUCCESS) {
                        SBBC_ERR(CE_PANIC, "Can't map the SBBC regs \n");
                }
                /*
                 * Only the 'chosen' node is used for iosram_read()/_write()
                 * Must initialise the tunnel before the console/tod
                 *
                 */
                if (iosram_tunnel_init(softsp) == DDI_FAILURE) {
                        SBBC_ERR(CE_PANIC, "Can't create the SRAM <-> SC "
                            "comm. tunnel \n");
                }

                master_chosen = TRUE;

                /*
                 * Verify that an 'interrupts' property
                 * exists for this device
                 */

                if (ddi_getproplen(DDI_DEV_T_ANY, softsp->dip,
                    DDI_PROP_DONTPASS, "interrupts",
                    &len) != DDI_PROP_SUCCESS) {

                        SBBC_ERR(CE_PANIC, "No 'interrupts' property for the "
                            "'chosen' SBBC \n");
                }

                /*
                 * add the interrupt handler
                 * NB
                 * should this be a high-level interrupt ?
                 * NB
                 */
                if (sbbc_add_intr(softsp) == DDI_FAILURE) {
                        SBBC_ERR(CE_PANIC, "Can't add interrupt handler for "
                            "'chosen' SBBC \n");
                }

                sbbc_enable_intr(softsp);

                /*
                 * Create the mailbox
                 */
                if (sbbc_mbox_create(softsp) != 0) {
                        cmn_err(CE_WARN, "No IOSRAM MailBox created!\n");
                }

        }
}
/*
 * sbbc_add_instance
 * Must be called to hold chosen_lock.
 */
static void
sbbc_add_instance(sbbc_softstate_t *softsp)
{
#ifdef DEBUG
        struct  sbbc_softstate *sp;
#endif

        ASSERT(mutex_owned(&chosen_lock));

#if defined(DEBUG)
        /* Verify that this instance is not in the list yet */
        for (sp = sgsbbc_instances; sp != NULL; sp = sp->next) {
                ASSERT(sp != softsp);
        }
#endif

        /*
         * Add this instance to the front of the list.
         */
        if (sgsbbc_instances != NULL) {
                sgsbbc_instances->prev = softsp;
        }

        softsp->next = sgsbbc_instances;
        softsp->prev = NULL;
        sgsbbc_instances = softsp;
}

static void
sbbc_remove_instance(sbbc_softstate_t *softsp)
{
        struct sbbc_softstate *sp;

        for (sp = sgsbbc_instances; sp != NULL; sp = sp->next) {
                if (sp == softsp) {
                        if (sp->next != NULL) {
                                sp->next->prev = sp->prev;
                        }
                        if (sp->prev != NULL) {
                                sp->prev->next = sp->next;
                        }
                        if (sgsbbc_instances == softsp) {
                                sgsbbc_instances = sp->next;
                        }
                        break;
                }
        }
}

/*
 * Generate an SBBC interrupt to the SC
 * Called from iosram_send_intr()
 *
 * send_intr == 0, check if EPLD register clear
 *                 for sync'ing SC/OS
 * send_intr == 1, send the interrupt
 */
int
sbbc_send_intr(sbbc_softstate_t *softsp, int send_intr)
{

        uchar_t                 *epld_int;
        volatile uchar_t        epld_status;

        ASSERT(MUTEX_HELD(&master_iosram->iosram_lock));

        if ((softsp == (sbbc_softstate_t *)NULL) ||
            (softsp->epld_regs == (struct sbbc_epld_regs *)NULL))
                return (ENXIO);

        /*
         * Check the L1 EPLD Interrupt register. If the
         * interrupt bit is set, theres an interrupt outstanding
         * (we assume) so return (EBUSY).
         */

        epld_int = &softsp->epld_regs->epld_reg[EPLD_INTERRUPT];

        epld_status = ddi_get8(softsp->sbbc_reg_handle2, epld_int);

        if (epld_status & INTERRUPT_ON)
                return (EBUSY);

        if (send_intr == TRUE)
                ddi_put8(softsp->sbbc_reg_handle2, epld_int,
                    (epld_status | INTERRUPT_ON));

        return (0);
}

/*
 * Map SBBC Internal registers
 *
 * The call to function should be protected by
 * chosen_lock or master_iosram->iosram_lock
 * to make sure a tunnel switch will not occur
 * in a middle of mapping.
 */
int
sbbc_map_regs(sbbc_softstate_t *softsp)
{
        struct ddi_device_acc_attr attr;

        attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
        attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;
        attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;

        /*
         * Map in register set 1, Common Device Regs
         * SBCC offset 0x0
         */
        if (ddi_regs_map_setup(softsp->dip, RNUM_SBBC_REGS,
            (caddr_t *)&softsp->sbbc_regs,
            SBBC_REGS_OFFSET, SBBC_REGS_SIZE,
            &attr, &softsp->sbbc_reg_handle1) != DDI_SUCCESS) {

                cmn_err(CE_WARN, "sbbc%d: unable to map interrupt "
                    "registers", ddi_get_instance(softsp->dip));
                return (DDI_FAILURE);
        }
        /*
         * Map in using register set 1, EPLD
         * SBCC offset 0xe000
         */
        if (ddi_regs_map_setup(softsp->dip, RNUM_SBBC_REGS,
            (caddr_t *)&softsp->epld_regs,
            SBBC_EPLD_OFFSET, SBBC_EPLD_SIZE,
            &attr, &softsp->sbbc_reg_handle2) != DDI_SUCCESS) {

                cmn_err(CE_WARN, "sbbc%d: unable to map EPLD "
                    "registers", ddi_get_instance(softsp->dip));
                return (DDI_FAILURE);
        }

        /*
         * Set up pointers for registers
         */
        softsp->port_int_regs =  (uint32_t *)((char *)softsp->sbbc_regs +
            SBBC_PCI_INT_STATUS);

map_regs_exit:
        return (DDI_SUCCESS);
}


/*
 * Unmap SBBC Internal registers
 */
static void
sbbc_unmap_regs(sbbc_softstate_t *softsp)
{
        if (softsp == NULL)
                return;

        mutex_enter(&master_iosram->iosram_lock);

        if (softsp->sbbc_regs) {
                ddi_regs_map_free(&softsp->sbbc_reg_handle1);
                softsp->sbbc_regs = NULL;
                softsp->port_int_regs = NULL;
        }

        if (softsp->epld_regs) {
                ddi_regs_map_free(&softsp->sbbc_reg_handle2);
                softsp->epld_regs = NULL;
        }

        mutex_exit(&master_iosram->iosram_lock);

        return;

}
/*
 * This is here to allow the IOSRAM driver get the softstate
 * for a chosen node when doing a tunnel switch. Just enables
 * us to avoid exporting the sbbcp softstate hook
 */
sbbc_softstate_t *
sbbc_get_soft_state(int instance)
{
        return (ddi_get_soft_state(sbbcp, instance));
}

/*
 * Add interrupt handlers
 */
int
sbbc_add_intr(sbbc_softstate_t *softsp)
{
        int             rc = DDI_SUCCESS;

        /*
         * map in the SBBC interrupts
         * Note that the iblock_cookie was initialised
         * in the 'attach' routine
         */

        if (ddi_add_intr(softsp->dip, 0, &softsp->iblock,
            &softsp->idevice, sbbc_intr_handler,
            (caddr_t)softsp) != DDI_SUCCESS) {

                cmn_err(CE_WARN, "Can't register SBBC "
                    " interrupt handler\n");
                rc = DDI_FAILURE;
        }

        return (rc);
}

void
sbbc_enable_intr(sbbc_softstate_t *softsp)
{
        uint32_t        *pci_intr_enable_reg;

        /*
         * Enable Interrupts now, turn on both INT#A lines
         */
        pci_intr_enable_reg =  (uint32_t *)((char *)softsp->sbbc_regs +
            SBBC_PCI_INT_ENABLE);
        ddi_put32(softsp->sbbc_reg_handle1, pci_intr_enable_reg,
            (uint32_t)SBBC_PCI_ENABLE_INT_A);
}

void
sbbc_disable_intr(sbbc_softstate_t *softsp)
{
        uint32_t        *pci_intr_enable_reg;

        /*
         * Disable Interrupts now, turn off both INT#A lines
         */
        pci_intr_enable_reg =  (uint32_t *)((char *)softsp->sbbc_regs +
            SBBC_PCI_INT_ENABLE);
        ddi_put32(softsp->sbbc_reg_handle1, pci_intr_enable_reg, 0);
}