/*	$OpenBSD: ts102.c,v 1.14 2005/01/27 17:03:23 millert Exp $	*/
/*	$NetBSD: ts102.c,v 1.21 2021/08/07 16:19:05 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/malloc.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);
}