root/sys/arch/mac68k/nubus/cpi_nubus.c
/*      $NetBSD: cpi_nubus.c,v 1.11 2020/12/19 21:48:04 thorpej Exp $   */

/*-
 * Copyright (c) 2008 Hauke Fath
 *
 * 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 ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,     
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: cpi_nubus.c,v 1.11 2020/12/19 21:48:04 thorpej Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/device.h>
#include <sys/kmem.h>
#include <sys/event.h>
#include <sys/callout.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/uio.h>
#include <sys/ioctl.h>
#include <sys/tty.h>
#include <sys/time.h>
#include <sys/timetc.h>
#include <sys/kernel.h>
#include <sys/syslog.h>
#include <sys/errno.h>

#include <machine/intr.h>
#include <machine/bus.h>
#include <machine/viareg.h>

#include <dev/ic/z8536reg.h>
#include <mac68k/nubus/nubus.h>
#include <mac68k/nubus/cpi_nubusvar.h>

#include "ioconf.h"

#ifdef DEBUG
#define CPI_DEBUG
#endif

/*
 * Stuff taken from Egan/Teixeira ch 8: 'if(TRACE_FOO)' debug output 
 * statements don't break indentation, and when DEBUG is not defined, 
 * the compiler code optimizer drops them as dead code.
 */
#ifdef CPI_DEBUG
#define M_TRACE_CONFIG  0x0001
#define M_TRACE_OPEN    0x0002
#define M_TRACE_CLOSE   0x0004
#define M_TRACE_READ    0x0008
#define M_TRACE_WRITE   0x0010
#define M_TRACE_IOCTL   0x0020
#define M_TRACE_STATUS  0x0040
#define M_TRACE_TCNTR   0x0080
#define M_TRACE_ALL     0xFFFF
#define M_TRACE_NONE    0x0000

#define TRACE_CONFIG    (cpi_debug_mask & M_TRACE_CONFIG)
#define TRACE_OPEN      (cpi_debug_mask & M_TRACE_OPEN)
#define TRACE_CLOSE     (cpi_debug_mask & M_TRACE_CLOSE)
#define TRACE_READ      (cpi_debug_mask & M_TRACE_READ)
#define TRACE_WRITE     (cpi_debug_mask & M_TRACE_WRITE)
#define TRACE_IOCTL     (cpi_debug_mask & M_TRACE_IOCTL)
#define TRACE_STATUS    (cpi_debug_mask & M_TRACE_STATUS)
#define TRACE_TCNTR     (cpi_debug_mask & M_TRACE_TCNTR)
#define TRACE_ALL       (cpi_debug_mask & M_TRACE_ALL)
#define TRACE_NONE      (cpi_debug_mask & M_TRACE_NONE)

uint32_t cpi_debug_mask = M_TRACE_NONE /* | M_TRACE_TCNTR | M_TRACE_WRITE */ ;
#else /* CPI_DEBUG */
#define TRACE_CONFIG    0
#define TRACE_OPEN      0
#define TRACE_CLOSE     0
#define TRACE_READ      0
#define TRACE_WRITE     0
#define TRACE_IOCTL     0
#define TRACE_STATUS    0
#define TRACE_TCNTR     0
#define TRACE_ALL       0
#define TRACE_NONE      0
#endif /* CPI_DEBUG */

/* autoconf interface */
int cpi_nubus_match(device_t, cfdata_t, void *);
void cpi_nubus_attach(device_t, device_t, void *);
void cpi_nubus_intr(void *);

CFATTACH_DECL_NEW(cpi, sizeof(struct cpi_softc),
    cpi_nubus_match, cpi_nubus_attach, NULL, NULL);

dev_type_open(cpi_open);
dev_type_close(cpi_close);
dev_type_read(cpi_read);
dev_type_write(cpi_write);
dev_type_ioctl(cpi_ioctl);

const struct cdevsw cpi_cdevsw = {
        .d_open = cpi_open,
        .d_close = cpi_close,
        .d_read = noread,
        .d_write = cpi_write,
        .d_ioctl = cpi_ioctl,
        .d_stop = nostop,
        .d_tty = notty,
        .d_poll = nopoll,
        .d_mmap = nommap,
        .d_kqfilter = nokqfilter,
        .d_discard = nodiscard,
        .d_flag = D_OTHER
};

/* prototypes */
static void cpi_lpreset(struct cpi_softc *);
static int cpi_notready(struct cpi_softc *);
static void cpi_wakeup(void *);
static int cpi_flush(struct cpi_softc *);
static void cpi_intr(void *);

static void cpi_tc_initclock(struct cpi_softc *);
static uint cpi_get_timecount(struct timecounter *);
static uint z8536_read_counter1(bus_space_tag_t, bus_space_handle_t);
static uint z8536_read_counter2(bus_space_tag_t, bus_space_handle_t);
static void z8536_reg_set(bus_space_tag_t, bus_space_handle_t,
    uint8_t, uint8_t);
static uint8_t z8536_reg_get(bus_space_tag_t, bus_space_handle_t,
    uint8_t);


const uint8_t cio_reset[] = {
        /* register     value */
        Z8536_MICR,     0x00,
        Z8536_MICR,     MICR_RESET,
        Z8536_MICR,     0x00
};

const uint8_t cio_init[] = {
        /* register     value */

        /* Interrupt vectors - clear all */
        Z8536_IVRA,     0x00,
        Z8536_IVRB,     0x00,
        Z8536_IVRCT,    0x20 /* ??? Do we use this? */,

        /*
         * Port A specification - bit port, single buffered,
         * latched output, pulsed handshake, all bits non-inverting
         * non-special I/O
         */
        Z8536_PMSRA,    PMSR_PTS_OUT | PMSR_LPM,
        Z8536_PHSRA,    PHSR_HTS_PUL,
        Z8536_DPPRA,    0x00,
        Z8536_DDRA,     0x00,
        Z8536_SIOCRA,   0x00,
                
        /*
         * Port B specification - bit port, transparent output,
         * pulsed handshake, all bits non-inverting
         * bits 0, 4 output; bits 1-3, 5-8 input,
         * non-special I/O
         * Pattern matching: Bit 6 (BUSY) matching "1"
         * Alternatively: Bit 3 (/ACK) matching "0"
         */
        Z8536_PMSRB,    PMSR_PMS_OR_PEV,
        Z8536_PHSRB,    0x00,
        Z8536_DPPRB,    0x00,
        Z8536_DDRB,     0xee /*11101110b*/,
        Z8536_SIOCRB,   0x00,
        Z8536_PPRB,     0x00,
        Z8536_PTRB,     0x00,
        Z8536_PMRB,     0x40 /*01000000b = PB6 */,
        
        Z8536_PDRB,     0xFE,   /* Assign printer -RESET */
        Z8536_PCSRA,    0x00,   /* Clear port A interrupt bits */
                
        /*
         * Port C specification - bit 3 out, bits 0-2 in,
         * all 4 non-inverting, non-special I/O 
         */
        Z8536_DDRC,     0x07 /*00000111b*/,
        Z8536_DPPRC,    0x00,
        Z8536_SIOCRC,   0x00,

        /*
         * We need Timer 3 for running port A in strobed mode.
         *
         * Counter/Timer 3 specification -- clear IP & IUS, trigger +
         * gate command bit, one-shot operation
         */
        Z8536_CTCSR3,   CTCS_CLR_IP_IUS | CTCS_GCB | CTCS_TCB,
        Z8536_CTMSR3,   CTMS_DCS_ONESHOT,               
        Z8536_CTTCR3_MSB, 0x00,         
        Z8536_CTTCR3_LSB, 0x03,         

        /* Enable ports A+B+C+CT3 */
        Z8536_MCCR,     MCCR_PAE | MCCR_PBE | MCCR_PC_CT3E,
        
        /* Master Interrupt Enable, Disable Lower Chain,
         * No Interrupt Vector, port A+B+CT vectors include status */
        Z8536_MICR,     MICR_MIE | MICR_DLC | MICR_NV | MICR_PAVIS |
        MICR_PBVIS | MICR_CTVIS,
        Z8536_PDRB,     0xFE,   /* Clear printer -RESET */
};

/* CPI default options */
/* int  cpi_options = 0 | CPI_CTC12_IS_TIMECOUNTER; */


/* 
 * Look for Creative Systems Inc. "Hurdler Centronics Parallel Interface"
 */
int
cpi_nubus_match(device_t parent, cfdata_t cf, void *aux)
{
        struct nubus_attach_args *na;

        na = aux;
        if ((na->category == NUBUS_CATEGORY_COMMUNICATIONS) &&
            (na->type == NUBUS_TYPE_CENTRONICS) &&
            (na->drsw == NUBUS_DRSW_CPI) &&
            (na->drhw == NUBUS_DRHW_CPI))
                return 1;
        else
                return 0;
}

void
cpi_nubus_attach(device_t parent, device_t self, void *aux)
{
        struct cpi_softc *sc;
        struct nubus_attach_args *na;
        int err, ii;

        sc = device_private(self);
        sc->sc_options = (device_cfdata(self)->cf_flags & CPI_OPTIONS_MASK);

        na = aux;
        sc->sc_bst = na->na_tag;
        memcpy(&sc->sc_slot, na->fmt, sizeof(nubus_slot));
        sc->sc_basepa = (bus_addr_t)NUBUS_SLOT2PA(na->slot);

        /*
         * The CIO sits eight bit wide on the top byte lane of
         * Nubus, so map 16 byte.
         */
        if (TRACE_CONFIG) {
                printf("\n");
                printf("\tcpi_nubus_attach() mapping 8536 CIO at 0x%lx.\n",
                    sc->sc_basepa + CIO_BASE_OFFSET);
        }

        err = bus_space_map(sc->sc_bst, sc->sc_basepa + CIO_BASE_OFFSET,
            (Z8536_IOSIZE << 4), 0, &sc->sc_bsh);
        if (err) {
                aprint_normal(": failed to map memory space.\n");
                return;
        }
        
        sc->sc_lpstate = LP_INITIAL;
        sc->sc_intcount = 0;
        sc->sc_bytestoport = 0;

        if (TRACE_CONFIG)
                printf("\tcpi_nubus_attach() about to set up 8536 CIO.\n");

        for (ii = 0; ii < sizeof(cio_reset); ii += 2)
                z8536_reg_set(sc->sc_bst, sc->sc_bsh, cio_reset[ii],
                    cio_reset[ii + 1]);
        
        delay(1000);            /* Give the CIO time to set itself up */
        for (ii = 0; ii < sizeof(cio_init); ii += 2) {
                z8536_reg_set(sc->sc_bst, sc->sc_bsh, cio_init[ii],
                    cio_init[ii + 1]);
        }
        
        if (TRACE_CONFIG)
                printf("\tcpi_nubus_attach() done with 8536 CIO setup.\n");
                
        /* XXX Get information strings from the card ROM */
        aprint_normal(": CSI Hurdler II Centronics\n");

        /* Attach CIO timers 1+2 as timecounter */
        if (sc->sc_options & CPI_CTC12_IS_TIMECOUNTER) {
                cpi_tc_initclock(sc);
        }

        callout_init(&sc->sc_wakeupchan, 0);    /* XXX */
        
        /* make sure interrupts are vectored to us */
        add_nubus_intr(na->slot, cpi_nubus_intr, sc);
}

void
cpi_nubus_intr(void *arg)
{
        struct cpi_softc *sc;
        int s;

        sc = (struct cpi_softc *)arg;

        s = spltty();

        sc->sc_intcount++;
        
        /* Check for interrupt source, and clear interrupt */

        /*
         * Clear port A interrupt
         * Interrupt from register A, clear "pending"
         * and set "under service"
         */
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_PCSRA, PCSR_CLR_IE);
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_PCSRA, PCSR_CLR_IP);
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_PCSRA, PCSR_SET_IUS);

        cpi_intr(sc);

        /* Interrupt from register A, mark serviced */
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_PCSRA, PCSR_CLR_IUS);
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_PCSRA, PCSR_SET_IE);

        splx(s);
}


/* cpi nuts and bolts */

int
cpi_open(dev_t device, int flag, int mode, struct lwp *l)
{
        int err, ii, s;
        struct cpi_softc *sc;

        if (TRACE_OPEN)
                printf("\tcpi_open() called...\n");

        /* Consistency checks: Valid unit number, softc, device state */
        sc = device_lookup_private(&cpi_cd, CPI_UNIT(device));
        if (NULL == sc) {
                if (TRACE_OPEN)
                        printf("Tried to cpi_open() with NULL softc\n");
                return ENXIO;
        }
        if (sc->sc_lpstate != LP_INITIAL) {
                if (TRACE_OPEN)
                        printf("Not in initial state (%x).\n",
                            sc->sc_lpstate);
                return EBUSY;
        }
        sc->sc_lpstate = LP_OPENING;

        if (TRACE_OPEN)
                printf("\tcpi_open() resetting the printer...\n");
        cpi_lpreset(sc);

        if (TRACE_OPEN)
                printf("\tcpi_open() waiting for printer ready...\n");
        
        /* Wait max 15 sec for printer to get ready */
        for (ii = 15; cpi_notready(sc); ii--) {
                if (0 == ii) {
                        sc->sc_lpstate = LP_INITIAL;
                        return EBUSY;
                }
                /* sleep for a second, unless we get a signal */
                err = tsleep(sc, PZERO | PCATCH, "cpi_open", hz);
                if (err != EWOULDBLOCK) {
                        sc->sc_lpstate = LP_INITIAL;
                        return err;
                }
        }
        if (TRACE_OPEN)
                printf("\tcpi_open() allocating printer buffer...\n");
        
        /* Allocate the driver's line buffer */
        sc->sc_printbuf = kmem_alloc(CPI_BUFSIZE, KM_SLEEP);
        sc->sc_bufbytes = 0;
        sc->sc_lpstate = LP_OPEN;

        /* Statistics */
        sc->sc_intcount = 0;
        sc->sc_bytestoport = 0;

        /* Kick off transfer */
        cpi_wakeup(sc);

        /*
         * Reset "interrupt {pending, under service}" bits, then
         * enable Port A interrupts
         */
        s = spltty();
        
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_PCSRA, PCSR_CLR_IP_IUS);
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_PCSRA, PCSR_SET_IE);
        splx(s);
        
        if (TRACE_OPEN)
                printf("\tcpi_open() done...\n");

        return 0;
}

int
cpi_close(dev_t device, int flag, int mode, struct lwp *l)
{
        struct cpi_softc *sc;
        
        sc = device_lookup_private(&cpi_cd, CPI_UNIT(device));

        if (TRACE_CLOSE)
                printf("\tcpi_close() called (%lu hard, %lu bytes to port)\n",
                    sc->sc_intcount, sc->sc_bytestoport);

        /* Flush the remaining buffer content, ignoring any errors */
        if (0 < sc->sc_bufbytes)
                (void)cpi_flush(sc);
        
        callout_stop(&sc->sc_wakeupchan);

        /* Disable Port A interrupts */
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_PCSRA, PCSR_CLR_IE);
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_PCSRA, PCSR_CLR_IP_IUS);

        sc->sc_lpstate = LP_INITIAL;
        kmem_free(sc->sc_printbuf, CPI_BUFSIZE);
        
        return 0;
}

int
cpi_write(dev_t device, struct uio *uio, int flags)
{
        int err;
        size_t numbytes;
        struct cpi_softc *sc;

        err = 0;
        
        if (TRACE_WRITE)
                printf("\tcpi_write() called for %u bytes\n", uio->uio_resid);

        sc = device_lookup_private(&cpi_cd, CPI_UNIT(device));

        /* Send data to printer, a line buffer full at a time */
        while (uio->uio_resid > 0) {
                numbytes = uimin(CPI_BUFSIZE, uio->uio_resid);
                sc->sc_cp = sc->sc_printbuf;
                uiomove(sc->sc_cp, numbytes, uio);
                sc->sc_bufbytes = numbytes;

                if (TRACE_WRITE)
                        printf("\tQueuing %u bytes\n", numbytes);
                err = cpi_flush(sc);
                if (err) {
                        /* Failure; adjust residual counter */
                        if (TRACE_WRITE)
                                printf("\tQueuing failed with %d\n", err);
                        uio->uio_resid += sc->sc_bufbytes;
                        sc->sc_bufbytes = 0;
                        break;
                }
        }
        return err;
}

int
cpi_ioctl(dev_t device, unsigned long cmd, void *data,
    int flag, struct lwp *l)
{
        int err;

        err = 0;

        if (TRACE_IOCTL)
                printf("\tcpi_ioctl() called with %ld...\n", cmd);
        
        switch (cmd) {
        default:
                if (TRACE_IOCTL)
                        printf("\tcpi_ioctl() unknown ioctl %ld\n", cmd);
                err = ENODEV;
                break;
        }
        return err;
}

/*
 * Flush the print buffer that our top half uses to provide data to
 * our bottom, interrupt-driven half.
 */
static int
cpi_flush(struct cpi_softc *sc)
{
        int err, s;

        err = 0;
        while (0 < sc->sc_bufbytes) {
                /* Feed the printer a char, if it's ready */
                if ( !cpi_notready(sc)) {
                        if (TRACE_WRITE)
                                printf("\tcpi_flush() writes %u bytes "
                                    "(%lu hard, %lu bytes to port)\n",
                                    sc->sc_bufbytes, sc->sc_intcount,
                                    sc->sc_bytestoport);
                        s = spltty();
                        cpi_intr(sc);
                        splx(s);
                }
                /* XXX Sure we want to wait forever for the printer? */
                err = tsleep((void *)sc, PZERO | PCATCH,
                    "cpi_flush", (60 * hz));
        }
        return err;
}

        
static void
cpi_wakeup(void *param)
{
        struct cpi_softc *sc;
        int s;
        
        sc = param;

        s = spltty();
        cpi_intr(sc);
        splx(s);
        
        callout_reset(&sc->sc_wakeupchan, hz, cpi_wakeup, sc);
}
        

static void
cpi_lpreset(struct cpi_softc *sc)
{
        uint8_t portb;          /* Centronics -RESET is on port B, bit 0 */

        portb = z8536_reg_get(sc->sc_bst, sc->sc_bsh, Z8536_PDRB);
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_PDRB, portb & ~CPI_RESET);
        delay(100);
        portb = z8536_reg_get(sc->sc_bst, sc->sc_bsh, Z8536_PDRB);
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_PDRB, portb | CPI_RESET);
}


/*
 * Centronics BUSY              is on port B, bit 6
 *            SELECT            is on Port B, bit 5
 *            /FAULT            is on Port B, bit 1
 *            PAPER EMPTY       is on Port C, bit 1
 */
static int
cpi_notready(struct cpi_softc *sc)
{
        uint8_t portb, portc;
        int is_busy, is_select, is_fault, is_paper_empty;

        if (TRACE_STATUS)
                printf("\tcpi_notready() checking printer status...\n");
        
        portb = bus_space_read_1(sc->sc_bst, sc->sc_bsh, CIO_PORTB);
        if (TRACE_STATUS)
                printf("\tPort B has 0x0%X\n", portb);
        
        is_busy = CPI_BUSY & portb;
        if (TRACE_STATUS)
                printf("\t\tBUSY = %d\n", is_busy);
        
        is_select = CPI_SELECT & portb;
        if (TRACE_STATUS)
                printf("\t\tSELECT = %d\n", is_select);
        
        is_fault = CPI_FAULT & portb;
        if (TRACE_STATUS)
                printf("\t\t/FAULT = %d\n", is_fault);
        
        portc = bus_space_read_1(sc->sc_bst, sc->sc_bsh, CIO_PORTC);
        if (TRACE_STATUS)
                printf("\tPort C has 0x0%X\n", portc);

        is_paper_empty = CPI_PAPER_EMPTY & portc;
        if (TRACE_STATUS)
                printf("\t\tPAPER EMPTY = %d\n", is_paper_empty);

        return (is_busy || !is_select || !is_fault || is_paper_empty);
}

static void
cpi_intr(void *arg)
{
        struct cpi_softc *sc;
        
        sc = arg;

        /* Printer ready for output? */
        if (cpi_notready(sc))
                return;

        if (0 && TRACE_WRITE)
                printf("\tcpi_soft_intr() has %u bytes.\n", sc->sc_bufbytes);
        
        /* Anything to print? */
        if (sc->sc_bufbytes) {
                /* Data byte */
                bus_space_write_1(sc->sc_bst, sc->sc_bsh,
                    CIO_PORTA, *sc->sc_cp++);
                sc->sc_bufbytes--;
                sc->sc_bytestoport++;
        }
        if (0 == sc->sc_bufbytes)
                /* line buffer empty, wake up our top half */
                wakeup((void *)sc);
}

static void
cpi_tc_initclock(struct cpi_softc *sc)
{
        uint8_t reg;
        
        /*
         * Set up c/t 1 and 2 as a single, free-running 32 bit counter
         */

        /* Disable counters 1 and 2 */
        reg = z8536_reg_get(sc->sc_bst, sc->sc_bsh, Z8536_MCCR);
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_MCCR,
            reg & ~(MCCR_CT1E | MCCR_CT2E));

        /* Make sure interrupt enable bits are cleared */
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_CTCSR1, CTCS_CLR_IE);
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_CTCSR2, CTCS_CLR_IE);
        
        /* Initialise counter start values, and set to continuous cycle */
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_CTMSR1,
            CTMS_CSC | CTMS_DCS_PULSE);
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_CTTCR1_MSB, 0x00);
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_CTTCR1_LSB, 0x00);
        
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_CTMSR2,
            CTMS_CSC | CTMS_DCS_PULSE);
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_CTTCR2_MSB, 0x00);
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_CTTCR2_LSB, 0x00);

        /* Link counters 1 and 2 */
        reg = z8536_reg_get(sc->sc_bst, sc->sc_bsh, Z8536_MCCR);
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_MCCR, reg | MCCR_CT1CT2);

        /* Enable and counter pair */
        reg = z8536_reg_get(sc->sc_bst, sc->sc_bsh, Z8536_MCCR);
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_MCCR,
            reg | (MCCR_CT1E | MCCR_CT2E));

        /* Start c/t 1; c/t 2 gets started by c/t 1 pulse */
        reg = z8536_reg_get(sc->sc_bst, sc->sc_bsh, Z8536_CTCSR1);
        z8536_reg_set(sc->sc_bst, sc->sc_bsh, Z8536_CTCSR1,
            CTCSR_MASK(reg | CTCS_TCB | CTCS_GCB));

        if (TRACE_TCNTR) {
                printf("Before tc_init():\n");
                reg = z8536_reg_get(sc->sc_bst, sc->sc_bsh, Z8536_CTCSR1);
                printf("Counter 1 CTCSR setup bits are 0x%03x\n", reg);
                printf("Counter 1 (LSW) is now 0x%05x\n",
                    z8536_read_counter1(sc->sc_bst, sc->sc_bsh));
                reg = z8536_reg_get(sc->sc_bst, sc->sc_bsh, Z8536_CTCSR2);
                printf("Counter 2 CTCSR setup bits are 0x%03x\n", reg);
                printf("Counter 2 (MSW) is now 0x%05x\n",
                    z8536_read_counter2(sc->sc_bst, sc->sc_bsh));

                delay(1000);
        }
        
        sc->sc_timecounter.tc_get_timecount = cpi_get_timecount;
        sc->sc_timecounter.tc_poll_pps      = 0;
        sc->sc_timecounter.tc_counter_mask  = ~0u;
        sc->sc_timecounter.tc_frequency     = CPI_CLK_FREQ;
        sc->sc_timecounter.tc_name          = "Nubus CPI";
        sc->sc_timecounter.tc_quality       = 1000;
        /*
         * Squirrel away the device's sc so we can talk
         * to the CIO later
         */
        sc->sc_timecounter.tc_priv          = sc;
        sc->sc_timecounter.tc_next          = NULL;
        
        tc_init(&(sc->sc_timecounter));
        
        if (TRACE_TCNTR) {
                delay(1000);

                printf("After tc_init():\n");
                reg = z8536_reg_get(sc->sc_bst, sc->sc_bsh, Z8536_CTCSR1);
                printf("Counter 1 CTCSR setup bits are 0x%03x\n", reg);
                printf("Counter 1 (LSW) is now 0x%05x\n",
                    z8536_read_counter1(sc->sc_bst, sc->sc_bsh));
                reg = z8536_reg_get(sc->sc_bst, sc->sc_bsh, Z8536_CTCSR2);
                printf("Counter 2 CTCSR setup bits are 0x%03x\n", reg);
                printf("Counter 2 (MSW) is now 0x%05x\n",
                    z8536_read_counter2(sc->sc_bst, sc->sc_bsh));
        }
}

static u_int
cpi_get_timecount(struct timecounter *tc)
{
        int s;
        uint msw, msw2, lsw;
        uint8_t reg;
        bus_space_tag_t bst;
        bus_space_handle_t bsh;
        
        bst = ((struct cpi_softc *)tc->tc_priv)->sc_bst;
        bsh = ((struct cpi_softc *)tc->tc_priv)->sc_bsh;
        /*
         * We run CIO counters 1 and 2 in an internally coupled mode,
         * where the output of counter 1 (LSW) clocks counter 2 (MSW).
         * The counters are buffered, and the buffers have to be
         * locked before we can read out a consistent counter
         * value. Reading the LSB releases the buffer lock.
         *
         * Unfortunately, there is no such mechanism between MSW and
         * LSW of the coupled counter. To ensure a consistent
         * read-out, we read the MSW, then the LSW, then re-read the
         * MSW and compare with the old value. If we find that the MSW
         * has just been incremented, we re-read the LSW. This avoids
         * a race that could leave us with a new (just wrapped) LSW
         * and an old MSW value.
         *
         * For simplicity, we roll the procedure into a loop - the
         * rollover case is rare.
         */
        do {
                
#define delay(a)
                
                /* Guard HW timer access */
                s = splhigh();

                /* Lock counter 2 latch in preparation for read-out */
                bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCSR2);
                delay(1);
                reg = bus_space_read_1(bst, bsh, CIO_CTRL);
                bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCSR2);
                bus_space_write_1(bst, bsh, CIO_CTRL, CTCSR_MASK(reg | CTCS_RCC));
                
                /* Read out counter 2 MSB,then LSB (releasing the latch) */
                bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCCR2_MSB);
                delay(1);
                msw = bus_space_read_1(bst, bsh, CIO_CTRL) << 8;
                bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCCR2_LSB);
                delay(1);
                msw |= bus_space_read_1(bst, bsh, CIO_CTRL);
                
                /* Lock counter 1 latch in preparation for read-out */
                bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCSR1);
                delay(1);
                reg = bus_space_read_1(bst, bsh, CIO_CTRL);
                bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCSR1);
                bus_space_write_1(bst, bsh, CIO_CTRL, CTCSR_MASK(reg |CTCS_RCC));
                
                /* Read out counter 1 MSB,then LSB (releasing the latch) */
                bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCCR1_MSB);
                delay(1);
                lsw = bus_space_read_1(bst, bsh, CIO_CTRL) << 8;
                bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCCR1_LSB);
                delay(1);
                lsw |= bus_space_read_1(bst, bsh, CIO_CTRL);
                
                /* Lock counter 2 latch in preparation for read-out */
                bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCSR2);
                delay(1);
                reg = bus_space_read_1(bst, bsh, CIO_CTRL);
                bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCSR2);
                bus_space_write_1(bst, bsh, CIO_CTRL, CTCSR_MASK(reg | CTCS_RCC));
                
                /* Read out counter 2 MSB,then LSB (releasing the latch) */
                bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCCR2_MSB);
                delay(1);
                msw2 = bus_space_read_1(bst, bsh, CIO_CTRL) << 8;
                bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCCR2_LSB);
                delay(1);
                msw2 |= bus_space_read_1(bst, bsh, CIO_CTRL);
                
                splx(s);

        } while (msw2 != msw);

        /* timecounter expects an upward counter */
        return ~0u - ((msw << 16) | lsw);
}

/*
 * Z8536 CIO convenience atomic register getter/setter
 */

static uint
z8536_read_counter1(bus_space_tag_t bst, bus_space_handle_t bsh)
{
        uint8_t reg;
        uint32_t lsw;
        int s;

        s = splhigh();
        
        /* Lock counter 1 latch in preparation for read-out */
        bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCSR1);
        delay(1);
        reg = bus_space_read_1(bst, bsh, CIO_CTRL);
        bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCSR1);
        bus_space_write_1(bst, bsh, CIO_CTRL, CTCSR_MASK(reg | CTCS_RCC));
                
        /* Read out counter 1 MSB,then LSB (releasing the latch) */
        bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCCR1_MSB);
        delay(1);
        lsw = bus_space_read_1(bst, bsh, CIO_CTRL) << 8;
        bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCCR1_LSB);
        delay(1);
        lsw |= bus_space_read_1(bst, bsh, CIO_CTRL);

        splx(s);

        return lsw;
}

static uint
z8536_read_counter2(bus_space_tag_t bst, bus_space_handle_t bsh)
{
        uint8_t reg;
        uint32_t msw;
        int s;

        s = splhigh();

        /* Lock counter 2 latch in preparation for read-out */
        bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCSR2);
        delay(1);
        reg = bus_space_read_1(bst, bsh, CIO_CTRL);
        bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCSR2);
        bus_space_write_1(bst, bsh, CIO_CTRL, CTCSR_MASK(reg | CTCS_RCC));

        /* Read out counter 2 MSB,then LSB (releasing the latch) */
        bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCCR2_MSB);
        delay(1);
        msw = bus_space_read_1(bst, bsh, CIO_CTRL) << 8;
        bus_space_write_1(bst, bsh, CIO_CTRL, Z8536_CTCCR2_LSB);
        delay(1);
        msw |= bus_space_read_1(bst, bsh, CIO_CTRL);

        splx(s);

        return msw;
}

static void
z8536_reg_set(bus_space_tag_t bspace, bus_space_handle_t bhandle,
    uint8_t reg, uint8_t val)
{
        int s;

        s = splhigh();
        bus_space_write_1(bspace, bhandle, CIO_CTRL, reg);
        delay(1);
        bus_space_write_1(bspace, bhandle, CIO_CTRL, val);
        splx(s);
}

static uint8_t
z8536_reg_get(bus_space_tag_t bspace, bus_space_handle_t bhandle, uint8_t reg)
{
        int s;
        uint8_t val;

        s = splhigh();
        bus_space_write_1(bspace, bhandle, CIO_CTRL, reg);
        delay(1);
        val = bus_space_read_1(bspace, bhandle, CIO_CTRL);
        splx(s);
        
        return val;
}