root/sys/arch/sgimips/hpc/pi1ppc.c
/* $NetBSD: pi1ppc.c,v 1.17 2022/11/01 19:45:35 andvar Exp $ */

/*
 * Copyright (c) 2001 Alcove - Nicolas Souchu
 * Copyright (c) 2003, 2004 Gary Thorpe <gathorpe@users.sourceforge.net>
 * Copyright (c) 2005 Joe Britt <britt@danger.com> - SGI PI1 version
 * All rights reserved.
 *
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * FreeBSD: src/sys/isa/ppc.c,v 1.26.2.5 2001/10/02 05:21:45 nsouch Exp
 *
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: pi1ppc.c,v 1.17 2022/11/01 19:45:35 andvar Exp $");

#include "opt_pi1ppc.h"

#include <sys/types.h>
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/device.h>
#include <sys/systm.h>

#include <sys/bus.h>
/*#include <machine/intr.h>*/

#include <dev/ppbus/ppbus_conf.h>
#include <dev/ppbus/ppbus_msq.h>
#include <dev/ppbus/ppbus_io.h>
#include <dev/ppbus/ppbus_var.h>

#include <machine/autoconf.h>
#include <machine/machtype.h>

#include <sgimips/ioc/iocreg.h>

#include <sgimips/hpc/hpcvar.h>
#include <sgimips/hpc/hpcreg.h>

#include <sgimips/hpc/pi1ppcreg.h>
#include <sgimips/hpc/pi1ppcvar.h>

#ifdef PI1PPC_DEBUG
int pi1ppc_debug = 1;
#endif

#ifdef PI1PPC_VERBOSE
int pi1ppc_verbose = 1;
#endif

/* Prototypes for functions. */

/* PC-style register emulation */
static uint8_t r_reg(int reg, struct pi1ppc_softc *pi1ppc);
static void w_reg(int reg, struct pi1ppc_softc *pi1ppc, uint8_t byte);

#define AT_DATA_REG     0
#define AT_STAT_REG     1
#define AT_CTL_REG      2

#define pi1ppc_r_str(_x)        r_reg(AT_STAT_REG,_x)
#define pi1ppc_r_ctr(_x)        r_reg(AT_CTL_REG,_x)
#define pi1ppc_r_dtr(_x)        r_reg(AT_DATA_REG,_x)

#define pi1ppc_w_str(_x,_y)
#define pi1ppc_w_ctr(_x,_y)     w_reg(AT_CTL_REG,_x,_y)
#define pi1ppc_w_dtr(_x,_y)     w_reg(AT_DATA_REG,_x,_y)

/* do we need to do these? */
#define pi1ppc_barrier_r(_x) bus_space_barrier(_x->sc_iot,_x->sc_ioh, \
                                        0,4,BUS_SPACE_BARRIER_READ)
#define pi1ppc_barrier_w(_x) bus_space_barrier(_x->sc_iot,_x->sc_ioh, \
                                        0,4,BUS_SPACE_BARRIER_WRITE)
#define pi1ppc_barrier(_x)  pi1ppc_barrier_r(_x)

/* Print function for config_found() */
static int pi1ppc_print(void *, const char *);

/* Routines for ppbus interface (bus + device) */
static int pi1ppc_read(device_t, char *, int, int, size_t *);
static int pi1ppc_write(device_t, char *, int, int, size_t *);
static int pi1ppc_setmode(device_t, int);
static int pi1ppc_getmode(device_t);
static int pi1ppc_exec_microseq(device_t, struct ppbus_microseq * *);
static uint8_t pi1ppc_io(device_t, int, u_char *, int, u_char);
static int pi1ppc_read_ivar(device_t, int, unsigned int *);
static int pi1ppc_write_ivar(device_t, int, unsigned int *);
static int pi1ppc_add_handler(device_t, void (*)(void *), void *);
static int pi1ppc_remove_handler(device_t, void (*)(void *));

/* no-ops, do any IOC machines have ECP/EPP-capable ports? */
static void pi1ppc_reset_epp_timeout(device_t);
static void pi1ppc_ecp_sync(device_t);

/* Utility functions */

/* Functions to read bytes into device's input buffer */
static void pi1ppc_nibble_read(struct pi1ppc_softc * const);
static void pi1ppc_byte_read(struct pi1ppc_softc * const);

/* Functions to write bytes to device's output buffer */
static void pi1ppc_std_write(struct pi1ppc_softc * const);

/* Miscellaneous */
static void pi1ppc_set_intr_mask(struct pi1ppc_softc * const, uint8_t);
static uint8_t pi1ppc_get_intr_stat(struct pi1ppc_softc * const);

#ifdef USE_INDY_ACK_HACK
static uint8_t pi1ppc_get_intr_mask(struct pi1ppc_softc * const);
#endif

static int pi1ppc_poll_str(struct pi1ppc_softc * const, const uint8_t,
        const uint8_t);
static int pi1ppc_wait_interrupt(struct pi1ppc_softc * const, kcondvar_t *,
        const uint8_t);

static int pi1ppc_poll_interrupt_stat(struct pi1ppc_softc * const, 
        const uint8_t);

static int pi1ppc_match(device_t parent, cfdata_t match, void *aux);
static void pi1ppc_attach(device_t parent, device_t self, void *aux);

CFATTACH_DECL_NEW(pi1ppc, sizeof(struct pi1ppc_softc), 
                                pi1ppc_match, 
                                pi1ppc_attach, 
                                NULL, 
                                NULL);

/* Currently only matching on Indy, though I think the Indigo1 also
   uses PI1.  If it does, then the driver should work (if it is attached
   at the appropriate base addr).
 */

static int
pi1ppc_match(device_t parent, cfdata_t match, void *aux)
{
        struct hpc_attach_args *ha = aux;

        if (strcmp(ha->ha_name, match->cf_name) != 0)
                return 0;

        if (mach_type == MACH_SGI_IP22)
                return 1;

        return 0;
}

static void
pi1ppc_attach(device_t parent, device_t self, void *aux)
{
        struct pi1ppc_softc *sc;
        struct hpc_attach_args *haa;

        sc = device_private(self);
        sc->sc_dev = self;
        haa = aux;
        sc->sc_iot = haa->ha_st;

        if (bus_space_subregion(haa->ha_st, haa->ha_sh, haa->ha_devoff,
                        0x28,           /* # bytes in par port regs */
                        &sc->sc_ioh)) {
                aprint_error(": unable to map control registers\n");
                return;
        }

        pi1ppc_sc_attach(sc);
}

/*
 * Generic attach and detach functions for pi1ppc device. 
 *
 * If sc_dev_ok in soft configuration data is not ATPPC_ATTACHED, these should
 * be skipped altogether.
 */

/* Soft configuration attach for pi1ppc */
void
pi1ppc_sc_attach(struct pi1ppc_softc *lsc)
{
        /* Adapter used to configure ppbus device */
        struct parport_adapter sc_parport_adapter;
        char buf[64];

        /* For a PC, this is where the installed chipset is probed.
         * We *know* what we have, no need to probe.
         */
        lsc->sc_type = PI1PPC_TYPE_INDY;
        lsc->sc_model = GENERIC;

        /* XXX Once we support Interrupts & DMA, update this */
        lsc->sc_has = PI1PPC_HAS_PS2;

        mutex_init(&lsc->sc_lock, MUTEX_DEFAULT, IPL_TTY);
        cv_init(&lsc->sc_in_cv, "pi1ppcin");
        cv_init(&lsc->sc_out_cv, "pi1ppcou");

        /* Print out chipset capabilities */
        snprintb(buf, sizeof(buf), "\20\1INTR\2DMA\3FIFO\4PS2\5ECP\6EPP",
            lsc->sc_has);
        printf("\n%s: capabilities=%s\n", device_xname(lsc->sc_dev), buf);

        /* Initialize device's buffer pointers */
        lsc->sc_outb = lsc->sc_outbstart = lsc->sc_inb = lsc->sc_inbstart
                = NULL;
        lsc->sc_inb_nbytes = lsc->sc_outb_nbytes = 0;

        /* Last configuration step: set mode to standard mode */
        if (pi1ppc_setmode(lsc->sc_dev, PPBUS_COMPATIBLE) != 0) {
                PI1PPC_DPRINTF(("%s: unable to initialize mode.\n",
                   device_xname(lsc->sc_dev)));
        }

        /* Set up parport_adapter structure */

        /* Set capabilities */
        sc_parport_adapter.capabilities = 0;
        if (lsc->sc_has & PI1PPC_HAS_INTR) {
                sc_parport_adapter.capabilities |= PPBUS_HAS_INTR;
        }
        if (lsc->sc_has & PI1PPC_HAS_DMA) {
                sc_parport_adapter.capabilities |= PPBUS_HAS_DMA;
        }
        if (lsc->sc_has & PI1PPC_HAS_FIFO) {
                sc_parport_adapter.capabilities |= PPBUS_HAS_FIFO;
        }
        if (lsc->sc_has & PI1PPC_HAS_PS2) {
                sc_parport_adapter.capabilities |= PPBUS_HAS_PS2;
        }

        /* Set function pointers */
        sc_parport_adapter.parport_io = pi1ppc_io;
        sc_parport_adapter.parport_exec_microseq = pi1ppc_exec_microseq;
        sc_parport_adapter.parport_setmode = pi1ppc_setmode;
        sc_parport_adapter.parport_getmode = pi1ppc_getmode;
        sc_parport_adapter.parport_read = pi1ppc_read;
        sc_parport_adapter.parport_write = pi1ppc_write;
        sc_parport_adapter.parport_read_ivar = pi1ppc_read_ivar;
        sc_parport_adapter.parport_write_ivar = pi1ppc_write_ivar;
        sc_parport_adapter.parport_dma_malloc = lsc->sc_dma_malloc;
        sc_parport_adapter.parport_dma_free = lsc->sc_dma_free;
        sc_parport_adapter.parport_add_handler = pi1ppc_add_handler;
        sc_parport_adapter.parport_remove_handler = pi1ppc_remove_handler;

        /* these are no-ops (does later machines have ECP/EPP support?) */
        sc_parport_adapter.parport_ecp_sync = pi1ppc_ecp_sync;
        sc_parport_adapter.parport_reset_epp_timeout =
                pi1ppc_reset_epp_timeout;

        /* Initialize handler list, may be added to by grandchildren */
        SLIST_INIT(&(lsc->sc_handler_listhead));

        /* Initialize interrupt state */
        lsc->sc_irqstat = PI1PPC_IRQ_NONE;
        lsc->sc_ecr_intr = lsc->sc_ctr_intr = lsc->sc_str_intr = 0;

        /* Disable DMA/interrupts (each ppbus driver selects usage itself) */
        lsc->sc_use = 0;

        /* Configure child of the device. */
        lsc->child = config_found(lsc->sc_dev, &(sc_parport_adapter),
                pi1ppc_print, CFARGS_NONE);

        return;
}

/* Soft configuration detach */
int
pi1ppc_sc_detach(struct pi1ppc_softc *lsc, int flag)
{
        device_t dev = lsc->sc_dev;

        /* Detach children devices */
        if (config_detach(lsc->child, flag) && !(flag & DETACH_QUIET)) {
                printf("%s not able to detach child device, ", device_xname(dev));

                if (!(flag & DETACH_FORCE)) {
                        printf("cannot detach\n");
                        return 1;
                } else {
                        printf("continuing (DETACH_FORCE)\n");
                }
        }
        if (!(flag & DETACH_QUIET))
                printf("%s detached", device_xname(dev));

        mutex_destroy(&lsc->sc_lock);
        cv_destroy(&lsc->sc_in_cv);
        cv_destroy(&lsc->sc_out_cv);
        return 0;
}

/* Used by config_found() to print out device information */
static int
pi1ppc_print(void *aux, const char *name)
{
        /* Print out something on failure. */
        if (name != NULL) {
                printf("%s: child devices", name);
                return UNCONF;
        }

        return QUIET;
}

/* Interrupt handler for pi1ppc device: wakes up read/write functions */
int
pi1ppcintr(void *arg)
{
/* NO INTERRUPTS YET */
#if 0
        device_t dev = arg;
        struct pi1ppc_softc *pi1ppc = device_private(dev);
        int claim = 1;
        enum { NONE, READER, WRITER } wake_up = NONE;

        PI1PPC_LOCK(pi1ppc);

        /* Record registers' status */
        pi1ppc->sc_str_intr = pi1ppc_r_str(pi1ppc);
        pi1ppc->sc_ctr_intr = pi1ppc_r_ctr(pi1ppc);
        pi1ppc_barrier_r(pi1ppc);

        /* Determine cause of interrupt and wake up top half */
        switch (atppc->sc_mode) {
        case ATPPC_MODE_STD:
                /* nAck pulsed for 5 usec, too fast to check reliably, assume */
                atppc->sc_irqstat = ATPPC_IRQ_nACK;
                if (atppc->sc_outb)
                        wake_up = WRITER;
                else
                        claim = 0;
                break;

        case ATPPC_MODE_NIBBLE:
        case ATPPC_MODE_PS2:
                /* nAck is set low by device and then high on ack */
                if (!(atppc->sc_str_intr & nACK)) {
                        claim = 0;
                        break;
                }
                atppc->sc_irqstat = ATPPC_IRQ_nACK;
                if (atppc->sc_inb)
                        wake_up = READER;
                break;

        case ATPPC_MODE_ECP:
        case ATPPC_MODE_FAST:
                /* Confirm interrupt cause: these are not pulsed as in nAck. */
                if (atppc->sc_ecr_intr & ATPPC_SERVICE_INTR) {
                        if (atppc->sc_ecr_intr & ATPPC_ENABLE_DMA)
                                atppc->sc_irqstat |= ATPPC_IRQ_DMA;
                        else
                                atppc->sc_irqstat |= ATPPC_IRQ_FIFO;

                        /* Decide where top half will be waiting */
                        if (atppc->sc_mode & ATPPC_MODE_ECP) {
                                if (atppc->sc_ctr_intr & PCD) {
                                        if (atppc->sc_inb)
                                                wake_up = READER;
                                        else
                                                claim = 0;
                                } else {
                                        if (atppc->sc_outb)
                                                wake_up = WRITER;
                                        else
                                                claim = 0;
                                }
                        } else {
                                if (atppc->sc_outb)
                                        wake_up = WRITER;
                                else
                                        claim = 0;
                        }
                }
                /* Determine if nFault has occurred */
                if ((atppc->sc_mode & ATPPC_MODE_ECP) &&
                        (atppc->sc_ecr_intr & ATPPC_nFAULT_INTR) &&
                        !(atppc->sc_str_intr & nFAULT)) {

                        /* Device is requesting the channel */
                        atppc->sc_irqstat |= ATPPC_IRQ_nFAULT;
                        claim = 1;
                }
                break;

        case ATPPC_MODE_EPP:
                /* nAck pulsed for 5 usec, too fast to check reliably */
                atppc->sc_irqstat = ATPPC_IRQ_nACK;
                if (atppc->sc_inb)
                        wake_up = WRITER;
                else if (atppc->sc_outb)
                        wake_up = READER;
                else
                        claim = 0;
                break;

        default:
                panic("%s: chipset is in invalid mode.", device_xname(dev));
        }

        if (claim) {
                switch (wake_up) {
                case NONE:
                        break;

                case READER:
                        cv_broadcast(atppc->sc_in_cv);
                        break;

                case WRITER:
                        cv_broadcast(atppc->sc_out_cv);
                        break;
                }
        }

        /* Call all of the installed handlers */
        if (claim) {
                struct atppc_handler_node * callback;
                SLIST_FOREACH(callback, &(atppc->sc_handler_listhead),
                        entries) {
                                (*callback->func)(callback->arg);
                }
        }
        PI1PPC_UNLOCK(atppc);

        return claim;
#else
        return 0;               /* NO INTERRUPTS YET */
#endif
}

/* Functions which support ppbus interface */

static void
pi1ppc_reset_epp_timeout(device_t dev)
{
        return;
}

/* Read from pi1ppc device: returns 0 on success. */
static int
pi1ppc_read(device_t dev, char *buf, int len, int ioflag,
        size_t *cnt)
{
        struct pi1ppc_softc *pi1ppc = device_private(dev);
        int error = 0;

        PI1PPC_LOCK(pi1ppc);

        *cnt = 0;

        /* Initialize buffer */
        pi1ppc->sc_inb = pi1ppc->sc_inbstart = buf;
        pi1ppc->sc_inb_nbytes = len;

        /* Initialize device input error state for new operation */
        pi1ppc->sc_inerr = 0;

        /* Call appropriate function to read bytes */
        switch(pi1ppc->sc_mode) {
        case PI1PPC_MODE_STD:
                error = ENODEV;
                break;

        case PI1PPC_MODE_NIBBLE:
                pi1ppc_nibble_read(pi1ppc);
                break;

        case PI1PPC_MODE_PS2:
                pi1ppc_byte_read(pi1ppc);
                break;

        default:
                panic("%s(%s): chipset in invalid mode.\n", __func__,
                      device_xname(dev));
        }

        /* Update counter*/
        *cnt = (pi1ppc->sc_inbstart - pi1ppc->sc_inb);

        /* Reset buffer */
        pi1ppc->sc_inb = pi1ppc->sc_inbstart = NULL;
        pi1ppc->sc_inb_nbytes = 0;

        if (!(error))
                error = pi1ppc->sc_inerr;

        PI1PPC_UNLOCK(pi1ppc);

        return (error);
}

/* Write to pi1ppc device: returns 0 on success. */
static int
pi1ppc_write(device_t dev, char *buf, int len, int ioflag, size_t *cnt)
{
        struct pi1ppc_softc * const pi1ppc = device_private(dev);
        int error = 0;

        *cnt = 0;

        PI1PPC_LOCK(pi1ppc);

        /* Set up line buffer */
        pi1ppc->sc_outb = pi1ppc->sc_outbstart = buf;
        pi1ppc->sc_outb_nbytes = len;

        /* Initialize device output error state for new operation */
        pi1ppc->sc_outerr = 0;

        /* Call appropriate function to write bytes */
        switch (pi1ppc->sc_mode) {
        case PI1PPC_MODE_STD:
                pi1ppc_std_write(pi1ppc);
                break;

        case PI1PPC_MODE_NIBBLE:
        case PI1PPC_MODE_PS2:
                error = ENODEV;
                break;

        default:
                panic("%s(%s): chipset in invalid mode.\n", __func__,
                      device_xname(dev));
        }

        /* Update counter*/
        *cnt = (pi1ppc->sc_outbstart - pi1ppc->sc_outb);

        /* Reset output buffer */
        pi1ppc->sc_outb = pi1ppc->sc_outbstart = NULL;
        pi1ppc->sc_outb_nbytes = 0;

        if (!(error))
                error = pi1ppc->sc_outerr;

        PI1PPC_UNLOCK(pi1ppc);

        return (error);
}

/*
 * Set mode of chipset to mode argument. Modes not supported are ignored. If
 * multiple modes are flagged, the mode is not changed. Modes are those
 * defined for ppbus_softc.sc_mode in ppbus_conf.h. Only ECP-capable chipsets
 * can change their mode of operation. However, ALL operation modes support
 * centronics mode and nibble mode. Modes determine both hardware AND software
 * behaviour.
 * NOTE: the mode for ECP should only be changed when the channel is in
 * forward idle mode. This function does not make sure FIFO's have flushed or
 * any consistency checks.
 */
static int
pi1ppc_setmode(device_t dev, int mode)
{
        struct pi1ppc_softc *pi1ppc = device_private(dev);
        uint8_t ecr;
        uint8_t chipset_mode;
        int rval = 0;

        PI1PPC_LOCK(pi1ppc);

        switch (mode) {
        case PPBUS_PS2:
                /* Indy has this, other PI1 machines do too? */
                chipset_mode = PI1PPC_MODE_PS2;
                break;

        case PPBUS_NIBBLE:
                /* Set nibble mode (virtual) */
                chipset_mode = PI1PPC_MODE_NIBBLE;
                break;

        case PPBUS_COMPATIBLE:
                chipset_mode = PI1PPC_MODE_STD;
                break;

        case PPBUS_ECP:
        case PPBUS_EPP:
                rval = ENODEV;
                goto end;

        default:
                PI1PPC_DPRINTF(("%s(%s): invalid mode passed as "
                                "argument.\n", __func__, device_xname(dev)));
                rval = ENODEV;
                goto end;
        }

        pi1ppc->sc_mode = chipset_mode;
        if (chipset_mode == PI1PPC_MODE_PS2) {
                /* Set direction bit to reverse */
                ecr = pi1ppc_r_ctr(pi1ppc);
                pi1ppc_barrier_r(pi1ppc);
                ecr |= PCD;                     /* data is INPUT */
                pi1ppc_w_ctr(pi1ppc, ecr);
                pi1ppc_barrier_w(pi1ppc);
        }

end:
        PI1PPC_UNLOCK(pi1ppc);

        return rval;
}

/* Get the current mode of chipset */
static int
pi1ppc_getmode(device_t dev)
{
        struct pi1ppc_softc *pi1ppc = device_private(dev);
        int mode;

        PI1PPC_LOCK(pi1ppc);

        /* The chipset can only be in one mode at a time logically */
        switch (pi1ppc->sc_mode) {
        case PI1PPC_MODE_PS2:
                mode = PPBUS_PS2;
                break;

        case PI1PPC_MODE_STD:
                mode = PPBUS_COMPATIBLE;
                break;

        case PI1PPC_MODE_NIBBLE:
                mode = PPBUS_NIBBLE;
                break;

        default:
                panic("%s(%s): device is in invalid mode!", __func__,
                      device_xname(dev));
                break;
        }

        PI1PPC_UNLOCK(pi1ppc);

        return mode;
}


/* Wait for FIFO buffer to empty for ECP-capable chipset */
static void
pi1ppc_ecp_sync(device_t dev)
{
        return;
}

/* Execute a microsequence to handle fast I/O operations. */

/* microsequence registers are equivalent to PC-like port registers */
/* therefore, translate bit positions & polarities */

/* Bit 4 of ctl_reg_int_en is used to emulate the PC's int enable
   bit.  Without it, lpt doesn't like the port.
 */
static uint8_t ctl_reg_int_en = 0;

static uint8_t
r_reg(int reg, struct pi1ppc_softc *pi1ppc)
{
        int val = 0;

        /* if we read the status reg, make it look like the PC */
        if(reg == AT_STAT_REG) {
                val = bus_space_read_4((pi1ppc)->sc_iot, 
                                (pi1ppc)->sc_ioh, IOC_PLP_STAT);
                val &= 0xff;

                /* invert /BUSY */
                val ^= 0x80;

                /* bit 2 reads as '1' on Indy (why?) */
                val &= 0xf8;

                return val;
        }

        /* if we read the ctl reg, make it look like the PC */
        if(reg == AT_CTL_REG) {
                val = bus_space_read_4((pi1ppc)->sc_iot, 
                                (pi1ppc)->sc_ioh, IOC_PLP_CTL);
                val &= 0xff;

                /* get the dir bit in the right place */
                val = ((val >> 1) & 0x20) | (val & 0x0f);

                /* invert /SEL, /AUTOFD, and /STB */
                val ^= 0x0b;
        
                /* emulate the PC's int enable ctl bit */
                val |= (ctl_reg_int_en & 0x10);

                return val;
        }

        if(reg == AT_DATA_REG) {
                val = bus_space_read_4((pi1ppc)->sc_iot, 
                                (pi1ppc)->sc_ioh, IOC_PLP_DATA);
                val &= 0xff;

                return val;
        }
                
        return 0;
}

static void
w_reg(int reg, struct pi1ppc_softc *pi1ppc, uint8_t byte)
{
        /* don't try to write to the status reg */

        /* if we are writing the ctl reg, adjust PC style -> IOC style */
        if(reg == AT_CTL_REG) {
                /* preserve pc-style int enable bit */
                ctl_reg_int_en = (byte & 0x10);

                /* get the dir bit in the right place */
                byte = ((byte << 1) & 0x40) | (byte & 0x0f);

                /* invert /SEL, /AUTOFD, and /STB */
                byte ^= 0x0b;

                bus_space_write_4((pi1ppc)->sc_iot, 
                                (pi1ppc)->sc_ioh, IOC_PLP_CTL, byte);
        }

        if(reg == AT_DATA_REG) {
                bus_space_write_4((pi1ppc)->sc_iot, 
                                (pi1ppc)->sc_ioh, IOC_PLP_DATA, byte);
        }
}

static int
pi1ppc_exec_microseq(device_t dev, struct ppbus_microseq **p_msq)
{
        struct pi1ppc_softc *pi1ppc = device_private(dev);
        struct ppbus_microseq *mi = *p_msq;
        char cc, *p;
        int i, iter, len;
        int error;
        register int reg;
        register unsigned char mask;
        register int accum = 0;
        register char *ptr = NULL;
        struct ppbus_microseq *stack = NULL;

        PI1PPC_LOCK(pi1ppc);

        /* Loop until microsequence execution finishes (ending op code) */
        for (;;) {
                switch (mi->opcode) {
                case MS_OP_RSET:
                        cc = r_reg(mi->arg[0].i, pi1ppc);
                        pi1ppc_barrier_r(pi1ppc);
                        cc &= (char)mi->arg[2].i;       /* clear mask */
                        cc |= (char)mi->arg[1].i;       /* assert mask */
                        w_reg(mi->arg[0].i, pi1ppc, cc);
                        pi1ppc_barrier_w(pi1ppc);
                        mi++;
                        break;

                case MS_OP_RASSERT_P:
                        reg = mi->arg[1].i;
                        ptr = pi1ppc->sc_ptr;

                        if ((len = mi->arg[0].i) == MS_ACCUM) {
                                accum = pi1ppc->sc_accum;
                                for (; accum; accum--) {
                                        w_reg(reg, pi1ppc, *ptr++);
                                        pi1ppc_barrier_w(pi1ppc);
                                }
                                pi1ppc->sc_accum = accum;
                        } else {
                                for (i = 0; i < len; i++) {
                                        w_reg(reg, pi1ppc, *ptr++);
                                        pi1ppc_barrier_w(pi1ppc);
                                }
                        }

                        pi1ppc->sc_ptr = ptr;
                        mi++;
                        break;

                case MS_OP_RFETCH_P:
                        reg = mi->arg[1].i;
                        mask = (char)mi->arg[2].i;
                        ptr = pi1ppc->sc_ptr;

                        if ((len = mi->arg[0].i) == MS_ACCUM) {
                                accum = pi1ppc->sc_accum;
                                for (; accum; accum--) {
                                        *ptr++ = r_reg(reg, pi1ppc) & mask;
                                        pi1ppc_barrier_r(pi1ppc);
                                }
                                pi1ppc->sc_accum = accum;
                        } else {
                                for (i = 0; i < len; i++) {
                                        *ptr++ = r_reg(reg, pi1ppc) & mask;
                                        pi1ppc_barrier_r(pi1ppc);
                                }
                        }

                        pi1ppc->sc_ptr = ptr;
                        mi++;
                        break;

                case MS_OP_RFETCH:
                        *((char *)mi->arg[2].p) = r_reg(mi->arg[0].i, pi1ppc) &
                                (char)mi->arg[1].i;
                        pi1ppc_barrier_r(pi1ppc);
                        mi++;
                        break;

                case MS_OP_RASSERT:
                case MS_OP_DELAY:
                        /* let's suppose the next instr. is the same */
                        do {
                                for (;mi->opcode == MS_OP_RASSERT; mi++) {
                                        w_reg(mi->arg[0].i, pi1ppc,
                                                (char)mi->arg[1].i);
                                        pi1ppc_barrier_w(pi1ppc);
                                }

                                for (;mi->opcode == MS_OP_DELAY; mi++) {
                                        delay(mi->arg[0].i);
                                }
                        } while (mi->opcode == MS_OP_RASSERT);
                        break;

                case MS_OP_ADELAY:
                        if (mi->arg[0].i) {
                                DELAY(mi->arg[0].i * 1000);
                        }
                        mi++;
                        break;

                case MS_OP_TRIG:
                        reg = mi->arg[0].i;
                        iter = mi->arg[1].i;
                        p = (char *)mi->arg[2].p;

                        /* XXX delay limited to 255 us */
                        for (i = 0; i < iter; i++) {
                                w_reg(reg, pi1ppc, *p++);
                                pi1ppc_barrier_w(pi1ppc);
                                delay((unsigned char)*p++);
                        }

                        mi++;
                        break;

                case MS_OP_SET:
                        pi1ppc->sc_accum = mi->arg[0].i;
                        mi++;
                        break;

                case MS_OP_DBRA:
                        if (--pi1ppc->sc_accum > 0) {
                                mi += mi->arg[0].i;
                        }

                        mi++;
                        break;

                case MS_OP_BRSET:
                        cc = pi1ppc_r_str(pi1ppc);
                        pi1ppc_barrier_r(pi1ppc);
                        if ((cc & (char)mi->arg[0].i) == (char)mi->arg[0].i) {
                                mi += mi->arg[1].i;
                        }
                        mi++;
                        break;

                case MS_OP_BRCLEAR:
                        cc = pi1ppc_r_str(pi1ppc);
                        pi1ppc_barrier_r(pi1ppc);
                        if ((cc & (char)mi->arg[0].i) == 0) {
                                mi += mi->arg[1].i;
                        }
                        mi++;
                        break;

                case MS_OP_BRSTAT:
                        cc = pi1ppc_r_str(pi1ppc);
                        pi1ppc_barrier_r(pi1ppc);
                        if ((cc & ((char)mi->arg[0].i | (char)mi->arg[1].i)) ==
                                (char)mi->arg[0].i) {
                                mi += mi->arg[2].i;
                        }
                        mi++;
                        break;

                case MS_OP_C_CALL:
                        /*
                         * If the C call returns !0 then end the microseq.
                         * The current state of ptr is passed to the C function
                         */
                        if ((error = mi->arg[0].f(mi->arg[1].p,
                                pi1ppc->sc_ptr))) {
                                PI1PPC_UNLOCK(pi1ppc);
                                return (error);
                        }
                        mi++;
                        break;

                case MS_OP_PTR:
                        pi1ppc->sc_ptr = (char *)mi->arg[0].p;
                        mi++;
                        break;

                case MS_OP_CALL:
                        if (stack) {
                                panic("%s - %s: too many calls", device_xname(dev),
                                        __func__);
                        }

                        if (mi->arg[0].p) {
                                /* store state of the actual microsequence */
                                stack = mi;

                                /* jump to the new microsequence */
                                mi = (struct ppbus_microseq *)mi->arg[0].p;
                        } else {
                                mi++;
                        }
                        break;

                case MS_OP_SUBRET:
                        /* retrieve microseq and pc state before the call */
                        mi = stack;

                        /* reset the stack */
                        stack = 0;

                        /* XXX return code */

                        mi++;
                        break;

                case MS_OP_PUT:
                case MS_OP_GET:
                case MS_OP_RET:
                        /*
                         * Can't return to pi1ppc level during the execution
                         * of a submicrosequence.
                         */
                        if (stack) {
                                panic("%s: cannot return to pi1ppc level",
                                        __func__);
                        }
                        /* update pc for pi1ppc level of execution */
                        *p_msq = mi;

                        PI1PPC_UNLOCK(pi1ppc);
                        return (0);

                default:
                        panic("%s: unknown microsequence "
                                "opcode 0x%x", __func__, mi->opcode);
                        break;
                }
        }

        /* Should not be reached! */
#ifdef PI1PPC_DEBUG
        panic("%s: unexpected code reached!\n", __func__);
#endif
}

/* General I/O routine */
static uint8_t
pi1ppc_io(device_t dev, int iop, u_char *addr, int cnt, u_char byte)
{
        struct pi1ppc_softc *pi1ppc = device_private(dev);
        uint8_t val = 0;

        PI1PPC_LOCK(pi1ppc);

        switch (iop) {
        case PPBUS_RDTR:
                val = r_reg(AT_DATA_REG, pi1ppc);
                break;
        case PPBUS_RSTR:
                val = r_reg(AT_STAT_REG, pi1ppc);
                break;
        case PPBUS_RCTR:
                val = r_reg(AT_CTL_REG, pi1ppc);
                break;
        case PPBUS_WDTR:
                w_reg(AT_DATA_REG, pi1ppc, byte);
                break;
        case PPBUS_WSTR:
                /* writing to the status register is weird */
                break;
        case PPBUS_WCTR:
                w_reg(AT_CTL_REG, pi1ppc, byte);
                break;
        default:
                panic("%s(%s): unknown I/O operation", device_xname(dev),
                        __func__);
                break;
        }

        pi1ppc_barrier(pi1ppc);

        PI1PPC_UNLOCK(pi1ppc);

        return val;
}

/* Read "instance variables" of pi1ppc device */
static int
pi1ppc_read_ivar(device_t dev, int index, unsigned int *val)
{
        struct pi1ppc_softc *pi1ppc = device_private(dev);
        int rval = 0;

        PI1PPC_LOCK(pi1ppc);

        switch(index) {
        case PPBUS_IVAR_INTR:
                *val = ((pi1ppc->sc_use & PI1PPC_USE_INTR) != 0);
                break;

        case PPBUS_IVAR_DMA:
                *val = ((pi1ppc->sc_use & PI1PPC_USE_DMA) != 0);
                break;

        default:
                rval = ENODEV;
        }

        PI1PPC_UNLOCK(pi1ppc);
        return rval;
}

/* Write "instance variables" of pi1ppc device */
static int
pi1ppc_write_ivar(device_t dev, int index, unsigned int *val)
{
        struct pi1ppc_softc *pi1ppc = device_private(dev);
        int rval = 0;

        PI1PPC_LOCK(pi1ppc);

        switch(index) {
        case PPBUS_IVAR_INTR:
                if (*val == 0)
                        pi1ppc->sc_use &= ~PI1PPC_USE_INTR;
                else if (pi1ppc->sc_has & PI1PPC_HAS_INTR)
                        pi1ppc->sc_use |= PI1PPC_USE_INTR;
                else
                        rval = ENODEV;
                break;

        case PPBUS_IVAR_DMA:
                if (*val == 0)
                        pi1ppc->sc_use &= ~PI1PPC_USE_DMA;
                else if (pi1ppc->sc_has & PI1PPC_HAS_DMA)
                        pi1ppc->sc_use |= PI1PPC_USE_DMA;
                else
                        rval = ENODEV;
                break;

        default:
                rval = ENODEV;
        }

        PI1PPC_UNLOCK(pi1ppc);
        return rval;
}

/* Add a handler routine to be called by the interrupt handler */
static int
pi1ppc_add_handler(device_t dev, void (*handler)(void *), void *arg)
{
        struct pi1ppc_softc *pi1ppc = device_private(dev);
        struct pi1ppc_handler_node *callback;

        if (handler == NULL) {
                PI1PPC_DPRINTF(("%s(%s): attempt to register NULL handler.\n",
                        __func__, device_xname(dev)));
                return EINVAL;
        }
        callback = kmem_alloc(sizeof(struct pi1ppc_handler_node), KM_SLEEP);

        PI1PPC_LOCK(pi1ppc);
        callback->func = handler;
        callback->arg = arg;
        SLIST_INSERT_HEAD(&(pi1ppc->sc_handler_listhead), callback, entries);
        PI1PPC_UNLOCK(pi1ppc);

        return 0;
}

/* Remove a handler added by pi1ppc_add_handler() */
static int
pi1ppc_remove_handler(device_t dev, void (*handler)(void *))
{
        struct pi1ppc_softc *pi1ppc = device_private(dev);
        struct pi1ppc_handler_node *callback;
        int rval;

        PI1PPC_LOCK(pi1ppc);
        KASSERT(!SLIST_EMPTY(&(pi1ppc->sc_handler_listhead)));
        SLIST_FOREACH(callback, &(pi1ppc->sc_handler_listhead), entries) {
                if (callback->func == handler) {
                        SLIST_REMOVE(&(pi1ppc->sc_handler_listhead), callback,
                                pi1ppc_handler_node, entries);
                        
                        break;
                }
        }
        PI1PPC_UNLOCK(pi1ppc);

        if (callback) {
                kmem_free(callback, sizeof(struct pi1ppc_handler_node));
                rval = 0;
        } else {
                rval = EINVAL;
        }
        return rval;
}

/* Utility functions */

/*
 * Functions that read bytes from port into buffer: called from interrupt
 * handler depending on current chipset mode and cause of interrupt. Return
 * value: number of bytes moved.
 */

/* note: BUSY is inverted in the PC world, but not on Indy, but the r_reg()
         and w_reg() functions make the Indy look like the PC. */ 

/* Only the lower 4 bits of the final value are valid */
#define nibble2char(s) ((((s) & ~nACK) >> 3) | (~(s) & nBUSY) >> 4)


/* Read bytes in nibble mode */
static void
pi1ppc_nibble_read(struct pi1ppc_softc *pi1ppc)
{
        int i;
        uint8_t nibble[2];
        uint8_t ctr;
        uint8_t str;

        /* Enable interrupts if needed */
        if (pi1ppc->sc_use & PI1PPC_USE_INTR) {

                /* XXX JOE - need code to enable interrupts 
                                --> emulate PC behavior in r_reg/w_reg
                */
#if 0
                ctr = pi1ppc_r_ctr(pi1ppc);
                pi1ppc_barrier_r(ioppc);
                if (!(ctr & IRQENABLE)) {
                        ctr |= IRQENABLE;
                        pi1ppc_w_ctr(pi1ppc, ctr);
                        pi1ppc_barrier_w(pi1ppc);
                }
#endif
        }

        while (pi1ppc->sc_inbstart < (pi1ppc->sc_inb + pi1ppc->sc_inb_nbytes)) {
                /* Check if device has data to send in idle phase */
                str = pi1ppc_r_str(pi1ppc);
                pi1ppc_barrier_r(pi1ppc);
                if (str & nDATAVAIL) {
                        return;
                }

                /* Nibble-mode handshake transfer */
                for (i = 0; i < 2; i++) {
                        /* Event 7 - ready to take data (HOSTBUSY low) */
                        ctr = pi1ppc_r_ctr(pi1ppc);
                        pi1ppc_barrier_r(pi1ppc);
                        ctr |= HOSTBUSY;
                        pi1ppc_w_ctr(pi1ppc, ctr);
                        pi1ppc_barrier_w(pi1ppc);

                        /* Event 8 - peripheral writes the first nibble */

                        /* Event 9 - peripheral set nAck low */
                        pi1ppc->sc_inerr = pi1ppc_poll_str(pi1ppc, 0, PTRCLK);
                        if (pi1ppc->sc_inerr)
                                return;

                        /* read nibble */
                        nibble[i] = pi1ppc_r_str(pi1ppc);

                        /* Event 10 - ack, nibble received */
                        ctr &= ~HOSTBUSY;
                        pi1ppc_w_ctr(pi1ppc, ctr);

                        /* Event 11 - wait ack from peripheral */
                        if (pi1ppc->sc_use & PI1PPC_USE_INTR)
                                pi1ppc->sc_inerr = pi1ppc_wait_interrupt(pi1ppc,
                                    &pi1ppc->sc_in_cv, PI1PPC_IRQ_nACK);
                        else
                                pi1ppc->sc_inerr = pi1ppc_poll_str(pi1ppc, PTRCLK,
                                        PTRCLK);
                        if (pi1ppc->sc_inerr)
                                return;
                }

                /* Store byte transferred */
                *(pi1ppc->sc_inbstart) = ((nibble2char(nibble[1]) << 4) & 0xf0) |
                        (nibble2char(nibble[0]) & 0x0f);
                pi1ppc->sc_inbstart++;
        }
}

/* Read bytes in bidirectional mode */
static void
pi1ppc_byte_read(struct pi1ppc_softc * const pi1ppc)
{
        uint8_t ctr;
        uint8_t str;

        /* Check direction bit */
        ctr = pi1ppc_r_ctr(pi1ppc);
        pi1ppc_barrier_r(pi1ppc);
        if (!(ctr & PCD)) {
                PI1PPC_DPRINTF(("%s: byte-mode read attempted without direction "
                                "bit set.", device_xname(pi1ppc->sc_dev)));
                pi1ppc->sc_inerr = ENODEV;
                return;
        }
        /* Enable interrupts if needed */

                /* XXX JOE - need code to enable interrupts */
#if 0   
        if (pi1ppc->sc_use & PI1PPC_USE_INTR) {
                if (!(ctr & IRQENABLE)) {
                        ctr |= IRQENABLE;
                        pi1ppc_w_ctr(pi1ppc, ctr);
                        pi1ppc_barrier_w(pi1ppc);
                }
        }
#endif

        /* Byte-mode handshake transfer */
        while (pi1ppc->sc_inbstart < (pi1ppc->sc_inb + pi1ppc->sc_inb_nbytes)) {
                /* Check if device has data to send */
                str = pi1ppc_r_str(pi1ppc);
                pi1ppc_barrier_r(pi1ppc);
                if (str & nDATAVAIL) {
                        return;
                }

                /* Event 7 - ready to take data (nAUTO low) */
                ctr |= HOSTBUSY;
                pi1ppc_w_ctr(pi1ppc, ctr);
                pi1ppc_barrier_w(pi1ppc);

                /* Event 9 - peripheral set nAck low */
                pi1ppc->sc_inerr = pi1ppc_poll_str(pi1ppc, 0, PTRCLK);
                if (pi1ppc->sc_inerr)
                        return;

                /* Store byte transferred */
                *(pi1ppc->sc_inbstart) = pi1ppc_r_dtr(pi1ppc);
                pi1ppc_barrier_r(pi1ppc);

                /* Event 10 - data received, can't accept more */
                ctr &= ~HOSTBUSY;
                pi1ppc_w_ctr(pi1ppc, ctr);
                pi1ppc_barrier_w(pi1ppc);

                /* Event 11 - peripheral ack */
                if (pi1ppc->sc_use & PI1PPC_USE_INTR)
                        pi1ppc->sc_inerr = pi1ppc_wait_interrupt(pi1ppc,
                            &pi1ppc->sc_in_cv, PI1PPC_IRQ_nACK);
                else
                        pi1ppc->sc_inerr = pi1ppc_poll_str(pi1ppc, PTRCLK, PTRCLK);
                if (pi1ppc->sc_inerr)
                        return;

                /* Event 16 - strobe */
                str |= HOSTCLK;
                pi1ppc_w_str(pi1ppc, str);
                pi1ppc_barrier_w(pi1ppc);
                DELAY(1);
                str &= ~HOSTCLK;
                pi1ppc_w_str(pi1ppc, str);
                pi1ppc_barrier_w(pi1ppc);

                /* Update counter */
                pi1ppc->sc_inbstart++;
        }
}

/*
 * Functions that write bytes to port from buffer: called from pi1ppc_write()
 * function depending on current chipset mode. Returns number of bytes moved.
 */

static void
pi1ppc_set_intr_mask(struct pi1ppc_softc * const pi1ppc, uint8_t mask)
{
        /* invert valid bits (0 = enabled) */
        mask = ~mask;
        mask &= 0xfc;

        bus_space_write_4((pi1ppc)->sc_iot, (pi1ppc)->sc_ioh, IOC_PLP_INTMASK, mask);
        pi1ppc_barrier_w(pi1ppc);
}


#ifdef USE_INDY_ACK_HACK
static uint8_t
pi1ppc_get_intr_mask(struct pi1ppc_softc * const pi1ppc)
{
        int val;
        val = bus_space_read_4((pi1ppc)->sc_iot, (pi1ppc)->sc_ioh, IOC_PLP_INTMASK);
        pi1ppc_barrier_r(pi1ppc);

        /* invert (0 = enabled) */
        val = ~val;

        return (val & 0xfc);
}
#endif

static uint8_t
pi1ppc_get_intr_stat(struct pi1ppc_softc * const pi1ppc)
{
        int val;
        val = bus_space_read_4((pi1ppc)->sc_iot, (pi1ppc)->sc_ioh, IOC_PLP_INTSTAT);
        pi1ppc_barrier_r(pi1ppc);

        return (val & 0xfc);
}

/* Write bytes in std/bidirectional mode */
static void
pi1ppc_std_write(struct pi1ppc_softc * const pi1ppc)
{
        unsigned char ctr;

        ctr = pi1ppc_r_ctr(pi1ppc);
        pi1ppc_barrier_r(pi1ppc);

        /* Ensure that the data lines are in OUTPUT mode */
        ctr &= ~PCD;
        pi1ppc_w_ctr(pi1ppc, ctr);
        pi1ppc_barrier_w(pi1ppc);
        
        /* XXX JOE - need code to enable interrupts */
#if 0   
        /* Enable interrupts if needed */
        if (pi1ppc->sc_use & PI1PPC_USE_INTR) {
                if (!(ctr & IRQENABLE)) {
                        ctr |= IRQENABLE;
                        pi1ppc_w_ctr(pi1ppc, ctr);
                        pi1ppc_barrier_w(pi1ppc);
                }
        }
#endif

        while (pi1ppc->sc_outbstart < (pi1ppc->sc_outb + pi1ppc->sc_outb_nbytes)) {

                /* Wait for peripheral to become ready for MAXBUSYWAIT */
                pi1ppc->sc_outerr = pi1ppc_poll_str(pi1ppc, SPP_READY, SPP_MASK);
                if (pi1ppc->sc_outerr) {
                        printf("pi1ppc: timeout waiting for peripheral to become ready\n");
                        return;
                }

                /* Put data in data register */
                pi1ppc_w_dtr(pi1ppc, *(pi1ppc->sc_outbstart));
                pi1ppc_barrier_w(pi1ppc);
                DELAY(1);

                /* If no intr, prepare to catch the rising edge of nACK */
                if (!(pi1ppc->sc_use & PI1PPC_USE_INTR)) {
                        pi1ppc_get_intr_stat(pi1ppc);   /* clear any pending intr */
                        pi1ppc_set_intr_mask(pi1ppc, PI1_PLP_ACK_INTR);
                }

                /* Pulse strobe to indicate valid data on lines */
                ctr |= STROBE;
                pi1ppc_w_ctr(pi1ppc, ctr);
                pi1ppc_barrier_w(pi1ppc);
                DELAY(1);
                ctr &= ~STROBE;
                pi1ppc_w_ctr(pi1ppc, ctr);
                pi1ppc_barrier_w(pi1ppc);

                /* Wait for nACK for MAXBUSYWAIT */
                if (pi1ppc->sc_use & PI1PPC_USE_INTR) {
                        pi1ppc->sc_outerr = pi1ppc_wait_interrupt(pi1ppc,
                            &pi1ppc->sc_out_cv, PI1PPC_IRQ_nACK);
                        if (pi1ppc->sc_outerr)
                                return;
                } else {
                        /* Try to catch the pulsed acknowledgement */
                        pi1ppc->sc_outerr = pi1ppc_poll_interrupt_stat(pi1ppc,
                                PI1_PLP_ACK_INTR);

                        if (pi1ppc->sc_outerr) {
                                printf("pi1ppc: timeout waiting for ACK: %02x\n",pi1ppc_r_str(pi1ppc));
                                return;
                        }
                }

                /* Update buffer position, byte count and counter */
                pi1ppc->sc_outbstart++;
        }
}

/*
 * Poll status register using mask and status for MAXBUSYWAIT.
 * Returns 0 if device ready, error value otherwise.
 */
static int
pi1ppc_poll_str(struct pi1ppc_softc * const pi1ppc, const uint8_t status,
        const uint8_t mask)
{
        unsigned int timecount;
        uint8_t str;
        int error = EIO;

        /* Wait for str to have status for MAXBUSYWAIT */
        for (timecount = 0; timecount < ((MAXBUSYWAIT/hz)*1000000);
                timecount++) {

                str = pi1ppc_r_str(pi1ppc);
                pi1ppc_barrier_r(pi1ppc);
                if ((str & mask) == status) {
                        error = 0;
                        break;
                }
                DELAY(1);
        }

        return error;
}

/* Wait for interrupt for MAXBUSYWAIT: returns 0 if acknowledge received. */
static int
pi1ppc_wait_interrupt(struct pi1ppc_softc * const sc, kcondvar_t *cv,
    const uint8_t irqstat)
{
        int error = EIO;

        sc->sc_irqstat &= ~irqstat;
        error = cv_timedwait_sig(cv, &sc->sc_lock, MAXBUSYWAIT);
        if (!error && (sc->sc_irqstat & irqstat) == 0) {
                sc->sc_irqstat &= ~irqstat;
                error = 0;
        }
        return error;
}

/*
        INDY ACK HACK DESCRIPTION

        There appears to be a bug in the Indy's PI1 hardware - it sometimes 
        *misses* the rising edge of /ACK.  Ugh!

        (Also, unlike the other status bits, /ACK doesn't generate an
         interrupt on its falling edge.)

        So, we do something kind of skanky here.  We use a shorter timeout,
        and, if we timeout, we first check BUSY.  If BUSY is high, we go
        back to waiting for /ACK (because maybe this really is just a slow
        peripheral).

        If it's a normal printer, it will raise BUSY from when it sees our
        /STROBE until it raises its /ACK:  
                _____   _____________________ 
        /STB         \_/
                ________________   __________
        /ACK                    \_/
                       ___________
        BUSY    ______/           \__________

        So, if we time out and see BUSY low, then we probably just missed
        the /ACK.

        In that case, we then check /ERROR and SELECTIN.  If both are hi, 
        (the peripheral thinks it is selected, and is not asserting /ERROR)
        we assume that the Indy's parallel port missed the /ACK, and return
        success.
 */

#ifdef USE_INDY_ACK_HACK
        #define ACK_TIMEOUT_SCALER      1000
#else
        #define ACK_TIMEOUT_SCALER      1000000
#endif

static int
pi1ppc_poll_interrupt_stat(struct pi1ppc_softc * const pi1ppc, 
        const uint8_t match)
{
        unsigned int timecount;
        uint8_t cur;
        int error = EIO;

#ifdef USE_INDY_ACK_HACK
        /* retry 10000x */
        int retry_count = 10000;                

retry:
#endif

        /* Wait for intr status to have match bits set for MAXBUSYWAIT */
        for (timecount = 0; timecount < ((MAXBUSYWAIT/hz)*ACK_TIMEOUT_SCALER); 
                timecount++) {
                cur = pi1ppc_get_intr_stat(pi1ppc);
                if ((cur & match) == match) {
                        error = 0;
                        break;
                }
                DELAY(1);
        }

#ifdef USE_INDY_ACK_HACK
        if(error != 0) {
                cur = pi1ppc_r_str(pi1ppc);

                /* retry if BUSY is hi (inverted, so lo) and we haven't 
                        waited the usual amt */

                if(((cur&nBUSY) == 0) && retry_count) {
                        retry_count--;
                        goto retry;
                }

                /* if /ERROR and SELECT are high, and the peripheral isn't
                        BUSY, assume that we just missed the /ACK.
                        (Remember, we emulate the PC's inverted BUSY!)
                */

                if((cur&(nFAULT|SELECT|nBUSY)) == (nFAULT|SELECT|nBUSY))
                        error = 0;

                /* if things still look bad, print out some info */
                if(error!=0)
                        printf("int mask=%02x, int stat=%02x, str=%02x\n",
                                                pi1ppc_get_intr_mask(pi1ppc),
                                                pi1ppc_get_intr_stat(pi1ppc),
                                                cur);
        }
#endif

        return error;
}