/*      $OpenBSD: ts102.c,v 1.14 2005/01/27 17:03:23 millert Exp $      */
/*      $NetBSD: ts102.c,v 1.22 2023/12/20 05:33:18 thorpej Exp $ */
/*
* Copyright (c) 2003, 2004, Miodrag Vallat.
* Copyright (c) 2005, Michael Lorenz.
*
* 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.
*/

/*
* Driver for the PCMCIA controller found in Tadpole SPARCbook 3 series
* notebooks.
*
* Based on the information provided in the SPARCbook 3 Technical Reference
* Manual (s3gxtrmb.pdf), chapter 7.  A few ramblings against this document
* and/or the chip itself are scattered across this file.
*
* Implementation notes:
*
* - The TS102 exports its PCMCIA windows as SBus memory ranges: 64MB for
*   the common memory window, and 16MB for the attribute and I/O windows.
*
*   Mapping the whole windows would consume 192MB of address space, which
*   is much more that what the iospace can offer.
*
*   A best-effort solution would be to map the windows on demand. However,
*   due to the wap mapdev() works, the va used for the mappings would be
*   lost after unmapping (although using an extent to register iospace memory
*   usage would fix this). So, instead, we will do a fixed mapping of a subset
*   of each window upon attach - this is similar to what the stp4020 driver
*   does.
*
* Endianness farce:
*
* - The documentation pretends that the endianness settings only affect the
*   common memory window. Gee, thanks a lot. What about other windows, then?
*   As a result, this driver runs with endianness conversions turned off.
*
* - One of the little-endian SBus and big-endian PCMCIA flags has the reverse
*   meaning, actually. To achieve a ``no endianness conversion'' status,
*   one has to be set and the other unset. It does not matter which one,
*   though.
*/

#include <sys/cdefs.h>

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/extent.h>
#include <sys/proc.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/device.h>

#include <dev/pcmcia/pcmciareg.h>
#include <dev/pcmcia/pcmciavar.h>
#include <dev/pcmcia/pcmciachip.h>

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

#include <dev/sbus/sbusvar.h>
#include <sparc/dev/ts102reg.h>

#include "tctrl.h"

#if NTCTRL > 0
#include <machine/tctrl.h>
#include <sparc/dev/tctrlvar.h>
#endif

#define TS102_NUM_SLOTS         2

/*
* Memory ranges
*/
#define TS102_RANGE_COMMON      0
#define TS102_RANGE_ATTR        1
#define TS102_RANGE_IO          2

#define TS102_RANGE_CNT         3
#define TS102_NUM_RANGES        (TS102_RANGE_CNT * TS102_NUM_SLOTS)

#define TS102_ARBITRARY_MAP_SIZE        (1 * 1024 * 1024)

struct  tslot_softc;

#ifdef TSLOT_DEBUG
#define TSPRINTF        printf
#else
#define TSPRINTF        while (0) printf
#endif

/*
* Per-slot data
*/
struct  tslot_data {
       struct tslot_softc      *td_parent;
       device_t                td_pcmcia;

       volatile uint8_t        *td_regs;
       bus_addr_t              td_space[TS102_RANGE_CNT];
       bus_space_tag_t         td_pcmciat;     /* for accessing cards */

       /* Interrupt handler */
       int                     (*td_intr)(void *);
       void                    *td_intrarg;
       void                    *td_softint;

       /* Socket status */
       int                     td_slot;
       int                     td_status;
#define TS_CARD                 0x0001
};

struct  tslot_softc {
       device_t        sc_dev;

       bus_space_tag_t sc_bustag;              /* socket control io    */
       bus_space_handle_t      sc_regh;        /*  space               */

       pcmcia_chipset_tag_t sc_pct;

       lwp_t           *sc_thread;     /* event thread */
       uint32_t        sc_events;      /* sockets with pending events */

       /* bits 0 and 1 are set according to card presence in slot 0 and 1 */
       uint32_t        sc_active;

       struct tslot_data sc_slot[TS102_NUM_SLOTS];
};

static void tslot_attach(device_t, device_t, void *);
static void tslot_event_thread(void *);
static int  tslot_intr(void *);
static void tslot_intr_disestablish(pcmcia_chipset_handle_t, void *);
static void *tslot_intr_establish(pcmcia_chipset_handle_t,
   struct pcmcia_function *, int, int (*)(void *), void *);

const char  *tslot_intr_string(pcmcia_chipset_handle_t, void *);
static int  tslot_io_alloc(pcmcia_chipset_handle_t, bus_addr_t, bus_size_t,
   bus_size_t, struct pcmcia_io_handle *);
static void tslot_io_free(pcmcia_chipset_handle_t, struct pcmcia_io_handle *);
static int  tslot_io_map(pcmcia_chipset_handle_t, int, bus_addr_t, bus_size_t,
   struct pcmcia_io_handle *, int *);
static void tslot_io_unmap(pcmcia_chipset_handle_t, int);
static int  tslot_match(device_t, struct cfdata *, void *);
static int  tslot_mem_alloc(pcmcia_chipset_handle_t, bus_size_t,
   struct pcmcia_mem_handle *);
static void tslot_mem_free(pcmcia_chipset_handle_t, struct pcmcia_mem_handle *);
static int  tslot_mem_map(pcmcia_chipset_handle_t, int, bus_addr_t, bus_size_t,
   struct pcmcia_mem_handle *, bus_size_t *, int *);
static void tslot_mem_unmap(pcmcia_chipset_handle_t, int);
static int  tslot_print(void *, const char *);
static void tslot_queue_event(struct tslot_softc *, int);
static void tslot_reset(struct tslot_data *, uint32_t);
static void tslot_slot_disable(pcmcia_chipset_handle_t);
static void tslot_slot_enable(pcmcia_chipset_handle_t);
static void tslot_slot_intr(struct tslot_data *, int);
static void tslot_slot_settype(pcmcia_chipset_handle_t, int);
static void tslot_update_lcd(struct tslot_softc *, int, int);
static void tslot_intr_dispatch(void *arg);
void tslot_delay(struct tslot_softc *sc, unsigned int ms);

CFATTACH_DECL_NEW(tslot, sizeof(struct tslot_softc),
   tslot_match, tslot_attach, NULL, NULL);

extern struct cfdriver tslot_cd;

/*
* PCMCIA chipset methods
*/
struct  pcmcia_chip_functions tslot_functions = {
       tslot_mem_alloc,
       tslot_mem_free,
       tslot_mem_map,
       tslot_mem_unmap,

       tslot_io_alloc,
       tslot_io_free,
       tslot_io_map,
       tslot_io_unmap,

       tslot_intr_establish,
       tslot_intr_disestablish,

       tslot_slot_enable,
       tslot_slot_disable,
       tslot_slot_settype
};

static  uint16_t ts102_read_2(bus_space_tag_t,
                                bus_space_handle_t,
                                bus_size_t);
static  uint32_t ts102_read_4(bus_space_tag_t,
                                bus_space_handle_t,
                                bus_size_t);
static  uint64_t ts102_read_8(bus_space_tag_t,
                                bus_space_handle_t,
                                bus_size_t);
static  void    ts102_write_2(bus_space_tag_t,
                               bus_space_handle_t,
                               bus_size_t,
                               uint16_t);
static  void    ts102_write_4(bus_space_tag_t,
                               bus_space_handle_t,
                               bus_size_t,
                               uint32_t);
static  void    ts102_write_8(bus_space_tag_t,
                               bus_space_handle_t,
                               bus_size_t,
                               uint64_t);

static uint16_t
ts102_read_2(bus_space_tag_t space, bus_space_handle_t handle,
   bus_size_t offset)
{
       return (le16toh(*(volatile uint16_t *)(handle +
           offset)));
}

static uint32_t
ts102_read_4(bus_space_tag_t space, bus_space_handle_t handle,
   bus_size_t offset)
{
       return (le32toh(*(volatile uint32_t *)(handle +
           offset)));
}

static uint64_t
ts102_read_8(bus_space_tag_t space, bus_space_handle_t handle,
   bus_size_t offset)
{
       return (le64toh(*(volatile uint64_t *)(handle +
           offset)));
}

static void
ts102_write_2(bus_space_tag_t space, bus_space_handle_t handle,
   bus_size_t offset, uint16_t value)
{
       (*(volatile uint16_t *)(handle + offset)) =
           htole16(value);
}

static void
ts102_write_4(bus_space_tag_t space, bus_space_handle_t handle,
   bus_size_t offset, uint32_t value)
{
       (*(volatile uint32_t *)(handle + offset)) =
           htole32(value);
}

static void
ts102_write_8(bus_space_tag_t space, bus_space_handle_t handle,
   bus_size_t offset, uint64_t value)
{
       (*(volatile uint64_t *)(handle + offset)) =
           htole64(value);
}


#define TSLOT_READ(slot, offset) \
       *(volatile uint16_t *)((slot)->td_regs + (offset))
#define TSLOT_WRITE(slot, offset, value) \
       *(volatile uint16_t *)((slot)->td_regs + (offset)) = (value)

/*
* Attachment and initialization
*/

static int
tslot_match(device_t parent, struct cfdata *vcf, void *aux)
{
       struct sbus_attach_args *sa = aux;

       return (strcmp("ts102", sa->sa_name) == 0);
}

static void
tslot_attach(device_t parent, device_t self, void *args)
{
       struct sbus_attach_args *sa = args;
       struct tslot_softc *sc = device_private(self);
       struct tslot_data *td;
       volatile uint8_t *regs;
       int node, slot, rnum, base, size;
       uint32_t ranges[30];
       void *rptr = ranges;
       bus_space_handle_t hrang = 0;
       bus_space_tag_t tag;

       sc->sc_dev = self;
       node = sa->sa_node;
       sc->sc_bustag=sa->sa_bustag;
       if (sbus_bus_map(sa->sa_bustag,
                        sa->sa_slot,
                        sa->sa_offset,
                        sa->sa_size,
                        0, &sc->sc_regh) != 0) {
               printf("%s: cannot map registers\n", device_xname(self));
               return;
       }
       regs = (uint8_t *)bus_space_vaddr(sa->sa_bustag, sc->sc_regh);

       tag = bus_space_tag_alloc(sa->sa_bustag, sc);
       if (tag == NULL) {
               printf("%s: attach: out of memory\n", device_xname(self));
               return;
       }
       tag->sparc_read_2 = ts102_read_2;
       tag->sparc_read_4 = ts102_read_4;
       tag->sparc_read_8 = ts102_read_8;
       tag->sparc_write_2 = ts102_write_2;
       tag->sparc_write_4 = ts102_write_4;
       tag->sparc_write_8 = ts102_write_8;

       bus_intr_establish(sa->sa_bustag, sa->sa_intr[0].oi_pri,
           IPL_NONE, tslot_intr, sc);

       printf(": %d slots\n", TS102_NUM_SLOTS);

       size = sizeof(ranges);
       if (prom_getprop(node, "ranges", 4, &size, &rptr) != 0) {
               printf("couldn't read ranges\n");
               return;
       }

       /*
        * Setup asynchronous event handler
        */
       sc->sc_events = 0;

       TSPRINTF("starting event thread...\n");
       if (kthread_create(PRI_NONE, 0, NULL, tslot_event_thread, sc,
           &sc->sc_thread, "%s", device_xname(self)) != 0) {
               panic("%s: unable to create event kthread",
                   device_xname(self));
       }

       sc->sc_pct = (pcmcia_chipset_tag_t)&tslot_functions;
       sc->sc_active = 0;

       /*
        * Setup slots
        */
       TSPRINTF("mapping resources...\n");
       for (slot = 0; slot < TS102_NUM_SLOTS; slot++) {
               td = &sc->sc_slot[slot];
               TSPRINTF("slot %d, ",slot);
               for (rnum = 0; rnum < TS102_RANGE_CNT; rnum++) {
                       base = (slot * TS102_RANGE_CNT + rnum) * 5;
                       TSPRINTF("%d: %08x %08x ",rnum,ranges[base + 3],
                           ranges[base + 4]);
                       if(sbus_bus_map(sc->sc_bustag,
                                       sa->sa_slot,
                                       ranges[base+3],
                                       TS102_ARBITRARY_MAP_SIZE,
                                       0, &hrang) != 0) {
                               printf("%s: cannot map registers\n",
                                   device_xname(self));
                               return;
                       }
                       TSPRINTF("%08x: %08x ",(uint32_t)ranges[base + 3],
                           (uint32_t)hrang);
                       td->td_space[rnum] = hrang;
               }
               td->td_parent = sc;
               td->td_pcmciat = tag;
               td->td_softint = NULL;
               td->td_regs = regs + slot * (TS102_REG_CARD_B_INT -
                   TS102_REG_CARD_A_INT);
               td->td_slot = slot;

               TSPRINTF("resetting slot %d %d\n", slot, (int)td->td_regs);
               tslot_reset(td, TS102_ARBITRARY_MAP_SIZE);
       }
}

static void
tslot_reset(struct tslot_data *td, uint32_t iosize)
{
       struct pcmciabus_attach_args paa;
       int ctl, status;

       paa.paa_busname = "pcmcia";
       paa.pct = (pcmcia_chipset_tag_t)td->td_parent->sc_pct;
       paa.pch = (pcmcia_chipset_handle_t)td;

       td->td_pcmcia = config_found(td->td_parent->sc_dev, &paa, tslot_print,
           CFARGS_NONE);

       if (td->td_pcmcia == NULL) {
               /*
                * If no pcmcia attachment, power down the slot.
                */
               tslot_slot_disable((pcmcia_chipset_handle_t)td);
               return;
       }

       /*
        * Initialize the slot
        */

       ctl = TSLOT_READ(td, TS102_REG_CARD_A_CTL);

       /* force low addresses */
       ctl &= ~(TS102_CARD_CTL_AA_MASK | TS102_CARD_CTL_IA_MASK);

       /* Put SBus and PCMCIA in their respective endian mode */
       ctl |= TS102_CARD_CTL_SBLE;     /* this is not what it looks like! */
       ctl &= ~TS102_CARD_CTL_PCMBE;   /* default */

       /* disable read ahead and address increment */
       ctl &= ~TS102_CARD_CTL_RAHD;
       ctl |= TS102_CARD_CTL_INCDIS;

       /* power on */
       ctl &= ~TS102_CARD_CTL_PWRD;
       TSLOT_WRITE(td, TS102_REG_CARD_A_CTL, ctl);
       TSPRINTF("ctl: %x\n", ctl);

       /*
        * Enable interrupt upon insertion/removal
        */

       TSLOT_WRITE(td, TS102_REG_CARD_A_INT,
           TS102_CARD_INT_MASK_CARDDETECT_STATUS);

       status = TSLOT_READ(td, TS102_REG_CARD_A_STS);
       if (status & TS102_CARD_STS_PRES) {
               td->td_status = TS_CARD;
               pcmcia_card_attach(td->td_pcmcia);
       } else
               td->td_status = 0;
}

/* XXX there ought to be a common function for this... */
static int
tslot_print(void *aux, const char *description)
{
       struct pcmciabus_attach_args *paa = aux;
       struct tslot_data *td = (struct tslot_data *)paa->pch;

       printf(" socket %d", td->td_slot);
       return (UNCONF);
}

/*
* PCMCIA Helpers
*/

static int
tslot_io_alloc(pcmcia_chipset_handle_t pch, bus_addr_t start, bus_size_t size,
   bus_size_t align, struct pcmcia_io_handle *pih)
{
       struct tslot_data *td = (struct tslot_data *)pch;

#ifdef TSLOT_DEBUG
       printf("[io alloc %x]", (uint32_t)size);
#endif

       pih->iot = td->td_pcmciat;
       pih->ioh = td->td_space[TS102_RANGE_IO];
       pih->addr = start;
       pih->size = size;
       pih->flags = 0;

       return (0);
}

static void
tslot_io_free(pcmcia_chipset_handle_t pch, struct pcmcia_io_handle *pih)
{
#ifdef TSLOT_DEBUG
       printf("[io free]");
#endif
}

static int
tslot_io_map(pcmcia_chipset_handle_t pch, int width, bus_addr_t offset,
   bus_size_t size, struct pcmcia_io_handle *pih, int *windowp)
{
       struct tslot_data *td = (struct tslot_data *)pch;

#ifdef TSLOT_DEBUG
       printf("[io map %x/%x", (uint32_t)offset, (uint32_t)size);
#endif

       pih->iot = td->td_pcmciat;
       if (bus_space_subregion(pih->iot, td->td_space[TS102_RANGE_IO],
           offset, size, &pih->ioh) != 0)
               printf("io_map failed, offset %x\n", (uint32_t)offset);
       *windowp = 0; /* TS102_RANGE_IO */

#ifdef TSLOT_DEBUG
       printf("->%x/%x]", (uint32_t)pih->ioh, (uint32_t)size);
       {
               int addr, line;
               for( addr = offset; addr < (offset + size); addr += 16) {
                       printf("%04x:", addr);
                       for(line = addr; line < (addr + 16); line += 2) {
                               printf(" %04x", bus_space_read_2(pih->iot,
                                   pih->ioh, line));
                       }
                       printf("\n");
               }
       }
#endif

       return (0);
}

static void
tslot_io_unmap(pcmcia_chipset_handle_t pch, int win)
{
#ifdef TSLOT_DEBUG
       struct tslot_data *td = (struct tslot_data *)pch;

       printf("[io unmap]");
       {
               int addr, line, offset = 0, size = 0x80;
               for (addr = offset; addr < (offset + size); addr += 16) {
                       printf("%04x:", addr);
                       for (line = addr; line < (addr + 16); line += 2){
                               printf(" %04x", bus_space_read_2(td->td_pcmciat,
                                   td->td_space[2], line));
                       }
                       printf("\n");
               }
       }
#endif
}

static int
tslot_mem_alloc(pcmcia_chipset_handle_t pch, bus_size_t size,
   struct pcmcia_mem_handle *pmh)
{
       struct tslot_data *td = (struct tslot_data *)pch;

#ifdef TSLOT_DEBUG
       printf("[mem alloc %x]", (uint32_t)size);
#endif
       pmh->memt = td->td_pcmciat;
       pmh->size = size;
       pmh->addr = 0;
       pmh->mhandle = 0;
       pmh->realsize = size;   /* nothing so far! */

       return (0);
}

static void
tslot_mem_free(pcmcia_chipset_handle_t pch, struct pcmcia_mem_handle *pmh)
{
#ifdef TSLOT_DEBUG
       printf("[mem free]");
#endif
}

static int
tslot_mem_map(pcmcia_chipset_handle_t pch, int kind, bus_addr_t addr,
   bus_size_t size, struct pcmcia_mem_handle *pmh, bus_size_t *offsetp,
   int *windowp)
{
       struct tslot_data *td = (struct tslot_data *)pch;
       int slot;

       slot = kind & PCMCIA_MEM_ATTR ? TS102_RANGE_ATTR : TS102_RANGE_COMMON;
#ifdef TSLOT_DEBUG
       printf("[mem map %d %x/%x", slot, (uint32_t)addr, (uint32_t)size);
#endif

       pmh->memt = td->td_parent->sc_bustag;
       if (bus_space_subregion(pmh->memt, td->td_space[slot],
           addr, size, &pmh->memh) != 0)
               printf("mem_map failed, offset %x\n", (uint32_t)addr);
       pmh->realsize = TS102_ARBITRARY_MAP_SIZE - addr;
       pmh->size = size;
       *offsetp = 0;
       *windowp = 0;

#ifdef TSLOT_DEBUG
       printf("->%x/%x]", (uint32_t)pmh->memh, (uint32_t)size);
#endif

       return (0);
}

static void
tslot_mem_unmap(pcmcia_chipset_handle_t pch, int win)
{
#ifdef TSLOT_DEBUG
       printf("[mem unmap %d]", win);
#endif
}

static void
tslot_slot_disable(pcmcia_chipset_handle_t pch)
{
       struct tslot_data *td = (struct tslot_data *)pch;
       int status;

#ifdef TSLOT_DEBUG
       printf("%s: disable slot %d\n",
           device_xname(td->td_parent->sc_dev), td->td_slot);
#endif

       status = TSLOT_READ(td, TS102_REG_CARD_A_STS);

       status &= ~TS102_CARD_STS_ACEN;

       /*
        * Disable interrupts, except for insertion.
        */
       TSLOT_WRITE(td, TS102_REG_CARD_A_INT,
           TS102_CARD_INT_MASK_CARDDETECT_STATUS);

       /*
        * Power down the socket and disable access
        */
       status &= ~TS102_CARD_STS_ACEN;
       status &= ~(TS102_CARD_STS_VPP1_MASK | TS102_CARD_STS_VPP2_MASK);
       status |= TS102_CARD_STS_VCCEN;
       TSLOT_WRITE(td, TS102_REG_CARD_A_STS, status);

       /*
        * wait 300ms until power fails (Tpf).
        */
       tslot_delay(td->td_parent, 300);
}

static void
tslot_slot_enable(pcmcia_chipset_handle_t pch)
{
       struct tslot_data *td = (struct tslot_data *)pch;
       int status, intr, i;

#ifdef TSLOT_DEBUG
       printf("%s: enable slot %d\n",
           device_xname(td->td_parent->sc_dev), td->td_slot);
#endif

       /* Power down the socket to reset it */
       status = TSLOT_READ(td, TS102_REG_CARD_A_STS);
       TSPRINTF("status: %x\n", status);

       status &= ~TS102_CARD_STS_ACEN;
       status &= ~(TS102_CARD_STS_VPP1_MASK | TS102_CARD_STS_VPP2_MASK);
       status |= TS102_CARD_STS_VCCEN;
       TSLOT_WRITE(td, TS102_REG_CARD_A_STS, status);

       /*
        * wait 300ms until power fails (Tpf).  Then, wait 100ms since we
        * are changing Vcc (Toff).
        */
       tslot_delay(td->td_parent, 300 + 100);

       /*
        * Power on the card if not already done, and enable card access
        */
       status |= TS102_CARD_STS_ACEN;
       status |= TS102_CARD_STS_VPP1_VCC;
       status &= ~TS102_CARD_STS_VCCEN;
       TSLOT_WRITE(td, TS102_REG_CARD_A_STS, status);

       /*
        * wait 100ms until power raise (Tpr) and 20ms to become
        * stable (Tsu(Vcc)).
        */
       tslot_delay(td->td_parent, 100 + 20);

       /*
        * hold RESET at least 20us.
        */
       intr = TSLOT_READ(td, TS102_REG_CARD_A_INT);
       delay(20);
       TSLOT_WRITE(td, TS102_REG_CARD_A_INT,
           intr & ~TS102_CARD_INT_SOFT_RESET);

       /* wait 20ms as per pc card standard (r2.01) section 4.3.6 */
       tslot_delay(td->td_parent, 20);

       /* We need level-triggered interrupts for PC Card hardware */
       TSLOT_WRITE(td, TS102_REG_CARD_A_STS,
               TSLOT_READ(td, TS102_REG_CARD_A_STS) | TS102_CARD_STS_LVL);

       /*
        * Wait until the card is unbusy. If it is still busy after 3 seconds,
        * give up. We could enable card interrupts and wait for the interrupt
        * to happen when BUSY is released, but the interrupt could also be
        * triggered by the card itself if it's an I/O card, so better poll
        * here.
        */
       for (i = 30000; i != 0; i--) {
               status = TSLOT_READ(td, TS102_REG_CARD_A_STS);
               /* If the card has been removed, abort */
               if ((status & TS102_CARD_STS_PRES) == 0) {
                       tslot_slot_disable(pch);
                       return;
               }
               if (status & TS102_CARD_STS_RDY)
                       break;
               else
                       delay(100);
       }

       if (i == 0) {
               printf("%s: slot %d still busy after 3 seconds, status 0x%x\n",
                   device_xname(td->td_parent->sc_dev), td->td_slot,
                   TSLOT_READ(td, TS102_REG_CARD_A_STS));
               return;
       }
}
static void
tslot_event_thread(void *v)
{
       struct tslot_softc *sc = v;
       struct tslot_data *td;
       int s, status;
       unsigned int socket;

#if NTCTRL > 0
       int i;

       /*
        * First-time setup of our LCD symbol. When a card is present at boot
        * time we won't detect a change here and therefore the LCD symbol won't
        * light up.
        */
       for (i = 0; i < TS102_NUM_SLOTS; i++) {
               td = &sc->sc_slot[i];
               status = TSLOT_READ(td, TS102_REG_CARD_A_STS);
               tslot_update_lcd(sc, i, status & TS102_CARD_STS_PRES);
       }
#endif

       for (;;) {
               s = splhigh();

               if ((socket = ffs(sc->sc_events)) == 0) {
                       splx(s);
                       tsleep(&sc->sc_events, PWAIT, "tslot_event", hz * 30);
                       continue;
               }
               socket--;
               sc->sc_events &= ~(1 << socket);
               splx(s);

               if (socket >= TS102_NUM_SLOTS) {
#ifdef DEBUG
                       printf("%s: invalid slot number %d\n",
                           device_xname(sc->sc_dev), socket);
#endif
                       continue;
               }

               td = &sc->sc_slot[socket];
               status = TSLOT_READ(td, TS102_REG_CARD_A_STS);

               if (status & TS102_CARD_STS_PRES) {
                       /* Card insertion */
                       if ((td->td_status & TS_CARD) == 0) {
                               td->td_status |= TS_CARD;
                               tslot_update_lcd(sc, socket, 1);
                               pcmcia_card_attach(td->td_pcmcia);
                       }
               } else {
                       /* Card removal */
                       if ((td->td_status & TS_CARD) != 0) {
                               tslot_update_lcd(sc, socket, 0);
                               td->td_status &= ~TS_CARD;
                               pcmcia_card_detach(td->td_pcmcia,
                                   DETACH_FORCE);
                       }
               }
       }
}

/*
* Interrupt handling
*/

static int
tslot_intr(void *v)
{
       struct tslot_softc *sc = v;
       struct tslot_data *td;
       int intregs[TS102_NUM_SLOTS], *intreg;
       int i, s, rc = 0;

       s = splhigh();

       /*
        * Scan slots, and acknowledge the interrupt if necessary first
        */
       for (i = 0; i < TS102_NUM_SLOTS; i++) {
               td = &sc->sc_slot[i];
               intreg = &intregs[i];
               *intreg = TSLOT_READ(td, TS102_REG_CARD_A_INT);

               /*
                * Acknowledge all interrupt situations at once, even if they
                * did not occur.
                */
               if ((*intreg & (TS102_CARD_INT_STATUS_IRQ |
                   TS102_CARD_INT_STATUS_WP_STATUS_CHANGED |
                   TS102_CARD_INT_STATUS_BATTERY_STATUS_CHANGED |
                   TS102_CARD_INT_STATUS_CARDDETECT_STATUS_CHANGED)) != 0) {
                       rc = 1;
                       TSLOT_WRITE(td, TS102_REG_CARD_A_INT, *intreg |
                           TS102_CARD_INT_RQST_IRQ |
                           TS102_CARD_INT_RQST_WP_STATUS_CHANGED |
                           TS102_CARD_INT_RQST_BATTERY_STATUS_CHANGED |
                           TS102_CARD_INT_RQST_CARDDETECT_STATUS_CHANGED);
               }
       }

#ifdef TSLOT_DEBUG
       printf("tslot_intr: %x %x\n", intregs[0], intregs[1]);
#endif

       /*
        * Invoke the interrupt handler for each slot
        */
       for (i = 0; i < TS102_NUM_SLOTS; i++) {
               td = &sc->sc_slot[i];
               intreg = &intregs[i];

               if ((*intreg & (TS102_CARD_INT_STATUS_IRQ |
                   TS102_CARD_INT_STATUS_WP_STATUS_CHANGED |
                   TS102_CARD_INT_STATUS_BATTERY_STATUS_CHANGED |
                   TS102_CARD_INT_STATUS_CARDDETECT_STATUS_CHANGED)) != 0)
                       tslot_slot_intr(td, *intreg);
       }
       splx(s);

       return (rc);
}

static void
tslot_queue_event(struct tslot_softc *sc, int slot)
{
       int s;

       s = splhigh();
       sc->sc_events |= (1 << slot);
       splx(s);
       wakeup(&sc->sc_events);
}

static void
tslot_slot_intr(struct tslot_data *td, int intreg)
{
       struct tslot_softc *sc = td->td_parent;
       int status, sockstat;
       uint32_t ireg;

       status = TSLOT_READ(td, TS102_REG_CARD_A_STS);
#ifdef TSLOT_DEBUG
       printf("%s: interrupt on socket %d ir %x sts %x\n",
           device_xname(sc->sc_dev), td->td_slot, intreg, status);
#else
       __USE(status);
#endif

       sockstat = td->td_status;

       /*
        * The TS102 queues interrupt request, and may trigger an interrupt
        * for a condition the driver does not want to receive anymore (for
        * example, after a card gets removed).
        * Thus, only proceed if the driver is currently allowing a particular
        * condition.
        */

       if ((intreg & TS102_CARD_INT_STATUS_CARDDETECT_STATUS_CHANGED) != 0 &&
           (intreg & TS102_CARD_INT_MASK_CARDDETECT_STATUS) != 0) {
               tslot_queue_event(sc, td->td_slot);
#ifdef TSLOT_DEBUG
               printf("%s: slot %d status changed from %d to %d\n",
                   device_xname(sc->sc_dev), td->td_slot, sockstat,
                   td->td_status);
#endif
               /*
                * Ignore extra interrupt bits, they are part of the change.
                */
               return;
       }

       if ((intreg & TS102_CARD_INT_STATUS_IRQ) != 0 &&
           (intreg & TS102_CARD_INT_MASK_IRQ) != 0) {
               /* ignore interrupts if we have a pending state change */
               if (sc->sc_events & (1 << td->td_slot))
               {
                       TSPRINTF("ev: %d\n", sc->sc_events);
                       return;
               }
               if ((sockstat & TS_CARD) == 0) {
                       printf("%s: spurious interrupt on slot %d isr %x\n",
                           device_xname(sc->sc_dev), td->td_slot, intreg);
                       return;
               }

               if (td->td_intr != NULL) {

                       if (td->td_softint != NULL)
                               sparc_softintr_schedule(td->td_softint);
                       /*
                        * Disable this sbus interrupt, until the soft-int
                        * handler had a chance to run
                        */
                       ireg = TSLOT_READ(td, TS102_REG_CARD_A_INT);
                       TSLOT_WRITE(td, TS102_REG_CARD_A_INT, ireg &
                           ~TS102_CARD_INT_MASK_IRQ);
               }
       }
}

static void
tslot_intr_disestablish(pcmcia_chipset_handle_t pch, void *ih)
{
       struct tslot_data *td = (struct tslot_data *)pch;

       td->td_intr = NULL;
       td->td_intrarg = NULL;
       if (td->td_softint) {
               sparc_softintr_disestablish(td->td_softint);
               td->td_softint = NULL;
       }
}

const char *
tslot_intr_string(pcmcia_chipset_handle_t pch, void *ih)
{
       if (ih == NULL)
               return ("couldn't establish interrupt");
       else
               return ("");    /* nothing for now */
}

static void *
tslot_intr_establish(pcmcia_chipset_handle_t pch, struct pcmcia_function *pf,
   int ipl, int (*handler)(void *), void *arg)
{
       struct tslot_data *td = (struct tslot_data *)pch;

       td->td_intr = handler;
       td->td_intrarg = arg;
       td->td_softint = sparc_softintr_establish(ipl, tslot_intr_dispatch, td);

       return (td);
}

/*
* Softinterrupt called to invoke the real driver interrupt handler.
*/
static void
tslot_intr_dispatch(void *arg)
{
       struct tslot_data *td = arg;
       int s;
       uint32_t ireg;

       /* invoke driver handler */
       td->td_intr(td->td_intrarg);

       /* enable SBUS interrupts for pcmcia interrupts again */
       s = splhigh();
       ireg = TSLOT_READ(td, TS102_REG_CARD_A_INT);
       TSLOT_WRITE(td, TS102_REG_CARD_A_INT, ireg | TS102_CARD_INT_MASK_IRQ);
       splx(s);
}

static void
tslot_slot_settype(pcmcia_chipset_handle_t pch, int type)
{
       struct tslot_data *td = (struct tslot_data *)pch;
       uint32_t reg;

       /*
        * Enable the card interrupts if this is an I/O card.
        * Note that the TS102_CARD_STS_IO bit in the status register will
        * never get set, despite what the documentation says!
        */
       TSPRINTF("tslot_slot_settype(%d)\n",type);
       if (type == PCMCIA_IFTYPE_IO) {
               TSLOT_WRITE(td, TS102_REG_CARD_A_STS,
                   TSLOT_READ(td, TS102_REG_CARD_A_STS) | TS102_CARD_STS_IO);
               TSLOT_WRITE(td, TS102_REG_CARD_A_INT,
                   TS102_CARD_INT_MASK_CARDDETECT_STATUS |
                   TS102_CARD_INT_MASK_IRQ);
               reg=TSLOT_READ(td, TS102_REG_CARD_A_STS);
               TSPRINTF("status: %x\n", reg);
       }
}

static void
tslot_update_lcd(struct tslot_softc *sc, int socket, int status)
{
#if NTCTRL > 0
       int was = (sc->sc_active != 0), is;
       int mask = 1 << socket;

       if (status > 0) {
               sc->sc_active |= mask;
       } else {
               sc->sc_active &= (mask ^ 3);
       }
       is = (sc->sc_active != 0);
       if (was != is) {
               tadpole_set_lcd(is, 0x40);
       }
#endif
}

/*
* Delay and possibly yield CPU.
* XXX - assumes a context
*/
void
tslot_delay(struct tslot_softc *sc, unsigned int ms)
{
       unsigned int ticks = mstohz(ms);

       if (cold || ticks == 0) {
               delay(ms);
               return;
       }

#ifdef DIAGNOSTIC
       if (ticks > 60*hz)
               panic("tslot: preposterous delay: %u", ticks);
#endif
       tsleep(sc, 0, "tslotdel", ticks);
}