code:
/*	$OpenBSD: tctrl.c,v 1.21 2011/07/02 22:20:07 nicm Exp $	*/
/*	$NetBSD: tctrl.c,v 1.2 1999/08/11 00:46:06 matt Exp $	*/

/*-
 * Copyright (c) 1998 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Matt Thomas.
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */
/*
 * The /dev/apm{,ctl} interface code falls under the following license
 * terms:
 *
 * Copyright (c) 1998-2001 Michael Shalayeff. All rights reserved.
 * Copyright (c) 1995 John T. Kohl.  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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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 MIND, 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/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/event.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/proc.h>
#include <sys/timeout.h>

#include <machine/apmvar.h>
#include <machine/autoconf.h>
#include <machine/conf.h>
#include <machine/cpu.h>

#include <sparc/sparc/auxioreg.h>

#include <sparc/dev/ts102reg.h>
#include <sparc/dev/tctrlvar.h>

/*
 * Flags to control kernel display
 *	SCFLAG_NOPRINT:		do not output APM power messages due to
 *				a power change event.
 *
 *	SCFLAG_PCTPRINT:	do not output APM power messages due to
 *				to a power change event unless the battery
 *				percentage changes.
 */

#define SCFLAG_NOPRINT	0x0008000
#define SCFLAG_PCTPRINT	0x0004000
#define SCFLAG_PRINT	(SCFLAG_NOPRINT|SCFLAG_PCTPRINT)

const char *tctrl_ext_status[16] = {
	"main power available",
	"internal battery attached",
	"external battery attached",
	"external VGA attached",
	"external keyboard attached",
	"external mouse attached",
	"lid down",
	"internal battery charging",
	"external battery charging",
	"internal battery discharging",
	"external battery discharging",
};

/* Request "packet" */
struct tctrl_req {
	u_int8_t	cmdbuf[16];
	u_int		cmdlen;
	u_int8_t	rspbuf[16];
	u_int		rsplen;
};

struct tctrl_softc {
	struct device sc_dev;
	struct uctrl_regs *sc_regs;
	struct intrhand sc_ih;
	u_int	sc_ext_status;
	u_int	sc_flags;
#define	TCTRL_SEND_REQUEST		0x0001
#define	TCTRL_ISXT			0x0002
	u_int	sc_wantdata;
	enum { TCTRL_IDLE, TCTRL_ARGS,
		TCTRL_ACK, TCTRL_DATA } sc_state;
	u_int8_t sc_cmdbuf[16];
	u_int8_t sc_rspbuf[16];
	u_int8_t sc_tft_on;
	u_int8_t sc_pcmcia_on;
	u_int8_t sc_brightness;
	u_int8_t sc_op;
	u_int	sc_cmdoff;
	u_int	sc_cmdlen;
	u_int	sc_rspoff;
	u_int	sc_rsplen;
	u_int	sc_rspack;
	u_int	sc_bellfreq;
	u_int	sc_bellvol;

	struct timeout sc_tmo;

	/* /dev/apm{,ctl} fields */
	struct klist sc_note;
	u_int	sc_apmflags;

	/* external video control callback */
	void (*sc_evcb)(void *, int);
	void *sc_evdata;
};

int	tctrl_match(struct device *, void *, void *);
void	tctrl_attach(struct device *, struct device *, void *);

void	tctrl_bell(struct tctrl_softc *, int, int);
void	tctrl_brightness(struct tctrl_softc *, int, int);
void	tctrl_init_lcd(struct tctrl_softc *);
int	tctrl_intr(void *);
void	tctrl_lcd(struct tctrl_softc *, int, int);
u_int8_t tctrl_read_data(struct tctrl_softc *);
void	tctrl_read_event_status(void *);
void	tctrl_read_ext_status(struct tctrl_softc *);
int	tctrl_request(struct tctrl_softc *, struct tctrl_req *);
void	tctrl_tft(struct tctrl_softc *);
void	tctrl_write_data(struct tctrl_softc *, u_int8_t);

int	apm_record_event(struct tctrl_softc *, u_int);

struct cfattach tctrl_ca = {
	sizeof(struct tctrl_softc), tctrl_match, tctrl_attach
};

struct cfdriver tctrl_cd = {
	NULL, "tctrl", DV_DULL
};

int
tctrl_match(parent, vcf, aux)
	struct device *parent;
	void *vcf;
	void *aux;
{
	struct confargs *ca = aux;
	struct romaux *ra = &ca->ca_ra;

	/*
	 * Tadpole 3GX/3GS uses "uctrl" for the Tadpole Microcontroller
	 * (which is really part of the TS102 PCMCIA controller, but there
	 * exists a distinct OpenProm node for the microcontroller interface).
	 */
	if (strcmp("uctrl", ra->ra_name))
		return (0);

	return (1);
}

void
tctrl_attach(parent, self, aux)
	struct device *parent, *self;
	void *aux;
{
	struct confargs *ca = aux;
	struct tctrl_softc *sc = (void *)self;
	u_int i, v;
	int pri;

	if (ca->ca_ra.ra_nintr != 1) {
		printf(": expected 1 interrupt, got %d\n",
		    ca->ca_ra.ra_nintr);
		return;
	}
	pri = ca->ca_ra.ra_intr[0].int_pri;

	if (ca->ca_ra.ra_nreg != 1) {
		printf(": expected 1 register, got %d\n",
		    ca->ca_ra.ra_nreg);
		return;
	}
	sc->sc_regs = mapiodev(&(ca->ca_ra.ra_reg[0]), 0,
	    ca->ca_ra.ra_reg[0].rr_len);

	printf(" pri %d\n", pri);

	/*
	 * We need to check if we are running on the SPARCbook S3XT, which
	 * needs extra work to control the TFT power.
	 */
	sc->sc_flags = 0;
	if (strcmp(mainbus_model, "Tadpole_S3000XT") == 0)
		sc->sc_flags |= TCTRL_ISXT;
	sc->sc_tft_on = 1;

	/* clear any pending data */
	for (i = 0; i < 10000; i++) {
		if ((TS102_UCTRL_STS_RXNE_STA & sc->sc_regs->stat) == 0)
			break;
		v = sc->sc_regs->data;
		sc->sc_regs->stat = TS102_UCTRL_STS_RXNE_STA;
	}

	sc->sc_ih.ih_fun = tctrl_intr;
	sc->sc_ih.ih_arg = sc;
	intr_establish(pri, &sc->sc_ih, -1, self->dv_xname);

	timeout_set(&sc->sc_tmo, tctrl_read_event_status, sc);

	/* See what the external status is */
	tctrl_read_ext_status(sc);
	if (sc->sc_ext_status != 0) {
		const char *sep;
		u_int len;

		v = sc->sc_ext_status;
		len = 0;
		sep = "";
		for (i = 0; v != 0; i++, v >>= 1) {
			if ((v & 1) == 0)
				continue;
			/* wrap to next line if necessary */
			if (len != 0 && len + strlen(sep) +
			    strlen(tctrl_ext_status[i]) > 80) {
				printf("\n");
				len = 0;
			}
			if (len == 0) {
				printf("%s: ", sc->sc_dev.dv_xname);
				len = 2 + strlen(sc->sc_dev.dv_xname);
				sep = "";
			}
			printf("%s%s", sep, tctrl_ext_status[i]);
			len += strlen(sep) + strlen(tctrl_ext_status[i]);
			sep = ", ";
		}
		if (len != 0)
			printf("\n");
	}

	/* Get a few status values */
	tctrl_bell(sc, 0xff, 0);
	tctrl_brightness(sc, 0xff, 0);

	/* Blank video if lid is closed during boot */
	if (sc->sc_ext_status & TS102_EXT_STATUS_LID_DOWN)
		tctrl_tft(sc);

	sc->sc_regs->intr = TS102_UCTRL_INT_RXNE_REQ|TS102_UCTRL_INT_RXNE_MSK;

	sc->sc_wantdata = 0;

	/* Initialize the LCD icons */
	tctrl_init_lcd(sc);
}

int
tctrl_intr(void *arg)
{
	struct tctrl_softc *sc = arg;
	unsigned int v, d;
	int progress = 0;

again:
	/* find out the cause(s) of the interrupt */
	v = sc->sc_regs->stat & TS102_UCTRL_STS_MASK;

	/* clear the cause(s) of the interrupt */
	sc->sc_regs->stat = v;

	v &= ~(TS102_UCTRL_STS_RXO_STA|TS102_UCTRL_STS_TXE_STA);
	if (sc->sc_cmdoff >= sc->sc_cmdlen) {
		v &= ~TS102_UCTRL_STS_TXNF_STA;
		if (sc->sc_regs->intr & TS102_UCTRL_INT_TXNF_REQ) {
			sc->sc_regs->intr = 0;
			progress = 1;
		}
	}
	if (v == 0 && ((sc->sc_flags & TCTRL_SEND_REQUEST) == 0 ||
	    sc->sc_state != TCTRL_IDLE)) {
		return (progress);
	}

	progress = 1;
	if (v & TS102_UCTRL_STS_RXNE_STA) {
		d = tctrl_read_data(sc);
		switch (sc->sc_state) {
		case TCTRL_IDLE:
			if (d == TS102_UCTRL_INTR) {
				/* external event */
				timeout_add(&sc->sc_tmo, 1);
			} else {
				printf("%s: (op=0x%02x): unexpected data (0x%02x)\n",
					sc->sc_dev.dv_xname, sc->sc_op, d);
			}
			goto again;
		case TCTRL_ACK:
#ifdef TCTRLDEBUG
			printf(" ack=0x%02x", d);
#endif
			switch (d) {
			case TS102_UCTRL_ACK:
				sc->sc_rspack = 1;
				sc->sc_rsplen--;
				sc->sc_rspoff = 0;
				sc->sc_state =
				    sc->sc_rsplen ? TCTRL_DATA : TCTRL_IDLE;
				sc->sc_wantdata = sc->sc_rsplen ? 1 : 0;
#ifdef TCTRLDEBUG
				if (sc->sc_rsplen > 0) {
					printf(" [data(%u)]", sc->sc_rsplen);
				} else {
					printf(" [idle]\n");
				}
#endif
				goto again;
			default:
				printf("%s: (op=0x%02x): unexpected return value (0x%02x)\n",
					sc->sc_dev.dv_xname, sc->sc_op, d);
				/* FALLTHROUGH */
			case TS102_UCTRL_NACK:
				printf("%s: command %x failed\n",
				    sc->sc_dev.dv_xname, sc->sc_op);
				sc->sc_rspack = 0;
				sc->sc_wantdata = 0;
				sc->sc_state = TCTRL_IDLE;
				break;
			}
			break;
		case TCTRL_DATA:
			sc->sc_rspbuf[sc->sc_rspoff++] = d;
#ifdef TCTRLDEBUG
			printf(" [%d]=0x%02x", sc->sc_rspoff-1, d);
#endif
			if (sc->sc_rspoff == sc->sc_rsplen) {
#ifdef TCTRLDEBUG
				printf(" [idle]\n");
#endif
				sc->sc_state = TCTRL_IDLE;
				sc->sc_wantdata = 0;
			}
			goto again;
		default:
			printf("%s: (op=0x%02x): unexpected data (0x%02x) in state %d\n",
			       sc->sc_dev.dv_xname, sc->sc_op, d, sc->sc_state);
			goto again;
		}
	}
	if ((sc->sc_state == TCTRL_IDLE && sc->sc_wantdata == 0) ||
	    (sc->sc_flags & TCTRL_SEND_REQUEST)) {
		if (sc->sc_flags & TCTRL_SEND_REQUEST) {
			sc->sc_flags &= ~TCTRL_SEND_REQUEST;
			sc->sc_wantdata = 1;
		}
		if (sc->sc_cmdlen > 0) {
			sc->sc_regs->intr =
			    sc->sc_regs->intr | TS102_UCTRL_INT_TXNF_MSK
				|TS102_UCTRL_INT_TXNF_REQ;
			v = sc->sc_regs->stat;
		}
	}
	if ((sc->sc_cmdoff < sc->sc_cmdlen) && (v & TS102_UCTRL_STS_TXNF_STA)) {
		tctrl_write_data(sc, sc->sc_cmdbuf[sc->sc_cmdoff++]);
#ifdef TCTRLDEBUG
		if (sc->sc_cmdoff == 1) {
			printf("%s: op=0x%02x(l=%u)", sc->sc_dev.dv_xname,
				sc->sc_cmdbuf[0], sc->sc_rsplen);
		} else {
			printf(" [%d]=0x%02x", sc->sc_cmdoff-1,
				sc->sc_cmdbuf[sc->sc_cmdoff-1]);
		}
#endif
		if (sc->sc_cmdoff == sc->sc_cmdlen) {
			sc->sc_state = sc->sc_rsplen ? TCTRL_ACK : TCTRL_IDLE;
#ifdef TCTRLDEBUG
			printf(" %s", sc->sc_rsplen ? "[ack]" : "[idle]\n");
#endif
			if (sc->sc_cmdoff == 1) {
				sc->sc_op = sc->sc_cmdbuf[0];
			}
			sc->sc_regs->intr =
			    sc->sc_regs->intr & (~TS102_UCTRL_INT_TXNF_MSK
				   |TS102_UCTRL_INT_TXNF_REQ);
		} else if (sc->sc_state == TCTRL_IDLE) {
			sc->sc_op = sc->sc_cmdbuf[0];
			sc->sc_state = TCTRL_ARGS;
#ifdef TCTRLDEBUG
			printf(" [args]");
#endif
		}
	}
	goto again;
}

/*
 * The Tadpole microcontroller is not preprogrammed with icon
 * representations.  The machine boots with the DC-IN light as
 * a blank (all 0x00) and the other lights, as 4 rows of horizontal
 * bars.  The below code initializes the few icons the system will use
 * to sane values.
 *
 * Programming the icons is simple.  It is a 5x8 matrix, with each row a
 * bitfield in the order 0x10 0x08 0x04 0x02 0x01.
 */

static void tctrl_set_glyph(struct tctrl_softc *, u_int, const u_int8_t *);

static const u_int8_t
    tctrl_glyph_dc[] = { 0x00, 0x00, 0x1f, 0x00, 0x15, 0x00, 0x00, 0x00 },
#if 0
    tctrl_glyph_bs[] = { 0x00, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x00 },
    tctrl_glyph_w1[] = { 0x0c, 0x16, 0x10, 0x15, 0x10, 0x16, 0x0c, 0x00 },
    tctrl_glyph_w2[] = { 0x0c, 0x0d, 0x01, 0x15, 0x01, 0x0d, 0x0c, 0x00 },
    tctrl_glyph_l1[] = { 0x00, 0x04, 0x08, 0x13, 0x08, 0x04, 0x00, 0x00 },
    tctrl_glyph_l2[] = { 0x00, 0x04, 0x02, 0x19, 0x02, 0x04, 0x00, 0x00 },
#endif
    tctrl_glyph_pc[] = { 0x00, 0x0e, 0x0e, 0x1f, 0x1f, 0x1f, 0x1f, 0x00 };

void
tctrl_init_lcd(struct tctrl_softc *sc)
{
	tctrl_set_glyph(sc, TS102_BLK_OFF_DEF_DC_GOOD, tctrl_glyph_dc);
#if 0
	tctrl_set_glyph(sc, TS102_BLK_OFF_DEF_BACKSLASH, tctrl_glyph_bs);
	tctrl_set_glyph(sc, TS102_BLK_OFF_DEF_WAN1, tctrl_glyph_w1);
	tctrl_set_glyph(sc, TS102_BLK_OFF_DEF_WAN2, tctrl_glyph_w2);
	tctrl_set_glyph(sc, TS102_BLK_OFF_DEF_LAN1, tctrl_glyph_l1);
	tctrl_set_glyph(sc, TS102_BLK_OFF_DEF_LAN2, tctrl_glyph_l2);
#endif
	tctrl_set_glyph(sc, TS102_BLK_OFF_DEF_PCMCIA, tctrl_glyph_pc);
}

static void
tctrl_set_glyph(struct tctrl_softc *sc, u_int glyph, const u_int8_t *data)
{
	struct tctrl_req req;

	req.cmdbuf[0] = TS102_OP_BLK_DEF_SPCL_CHAR;
	req.cmdbuf[1] = 8;
	req.cmdbuf[2] = glyph;
	bcopy(data, req.cmdbuf + 3, 8);
	req.cmdlen = 3 + 8;
	req.rsplen = 1;

	tctrl_request(sc, &req);
}

void
tctrl_read_event_status(void *arg)
{
	struct tctrl_softc *sc = (struct tctrl_softc *)arg;
	struct tctrl_req req;
	unsigned int v;

	req.cmdbuf[0] = TS102_OP_RD_EVENT_STATUS;
	req.cmdlen = 1;
	req.rsplen = 3;

	tctrl_request(sc, &req);

	v = req.rspbuf[0] * 256 + req.rspbuf[1];

	/*
	 * Read the new external status value if necessary
	 */
	if (v & (TS102_EVENT_STATUS_DC_STATUS_CHANGE |
	    TS102_EVENT_STATUS_LID_STATUS_CHANGE |
	    TS102_EVENT_STATUS_EXTERNAL_VGA_STATUS_CHANGE))
		tctrl_read_ext_status(sc);

	if (v & TS102_EVENT_STATUS_SHUTDOWN_REQUEST) {
		printf("%s: SHUTDOWN REQUEST!\n", sc->sc_dev.dv_xname);
	}
#ifdef TCTRLDEBUG
	/* Obviously status change */
	if (v & TS102_EVENT_STATUS_VERY_LOW_POWER_WARNING) {
		if (sc->sc_apmflags & SCFLAG_PCTPRINT)
			printf("%s: Battery level change\n",
			    sc->sc_dev.dv_xname);
	}
#endif
	if (v & TS102_EVENT_STATUS_LOW_POWER_WARNING) {
		if ((sc->sc_apmflags & SCFLAG_NOPRINT) == 0)
			printf("%s: LOW POWER WARNING!\n", sc->sc_dev.dv_xname);
		apm_record_event(sc, APM_BATTERY_LOW);
	}
	if (v & TS102_EVENT_STATUS_DC_STATUS_CHANGE) {
		if ((sc->sc_apmflags & SCFLAG_NOPRINT) == 0)
			printf("%s: main power %s\n", sc->sc_dev.dv_xname,
			    (sc->sc_ext_status & TS102_EXT_STATUS_MAIN_POWER_AVAILABLE) ?
			      "restored" : "removed");
		apm_record_event(sc, APM_POWER_CHANGE);
#if 0 /* automatically done for us */
		tctrl_lcd(sc, ~TS102_LCD_DC_OK,
		    sc->sc_ext_status & TS102_EXT_STATUS_MAIN_POWER_AVAILABLE ?
		      TS102_LCD_DC_OK : 0);
#endif
	}
	if (v & TS102_EVENT_STATUS_LID_STATUS_CHANGE) {
		/* blank or restore video if necessary */
		if (sc->sc_tft_on)
			tctrl_tft(sc);
#ifdef TCTRLDEBUG
		printf("%s: lid %s\n", sc->sc_dev.dv_xname,
		    (sc->sc_ext_status & TS102_EXT_STATUS_LID_DOWN) ?
		      "closed" : "opened");
#endif
	}
	if (v & TS102_EVENT_STATUS_EXTERNAL_VGA_STATUS_CHANGE) {
		printf("%s: external vga %s\n", sc->sc_dev.dv_xname,
		    sc->sc_ext_status & TS102_EXT_STATUS_EXTERNAL_VGA_ATTACHED ?
		      "attached" : "detached");
#ifdef TCTRLDEBUG
		req.cmdbuf[0] = TS102_OP_RD_EXT_VGA_PORT;
		req.cmdlen = 1;
		req.rsplen = 2;
		tctrl_request(sc, &req);
		printf("%s: vga status %x\n", sc->sc_dev.dv_xname,
		    req.rspbuf[0]);
#endif
		if (sc->sc_evcb != NULL)
			(*sc->sc_evcb)(sc->sc_evdata, sc->sc_ext_status &
			    TS102_EXT_STATUS_EXTERNAL_VGA_ATTACHED);
	}
}

void
tctrl_read_ext_status(struct tctrl_softc *sc)
{
	struct tctrl_req req;

	req.cmdbuf[0] = TS102_OP_RD_EXT_STATUS;
	req.cmdlen = 1;
	req.rsplen = 3;
#ifdef TCTRLDEBUG
	printf("tctrl_read_ext_status: before, ext_status = %x\n",
	    sc->sc_ext_status);
#endif

	tctrl_request(sc, &req);

	sc->sc_ext_status = req.rspbuf[0] * 256 + req.rspbuf[1];

#ifdef TCTRLDEBUG
	printf("tctrl_read_ext_status: after, ext_status = %x\n",
	    sc->sc_ext_status);
#endif
}

void
tctrl_bell(struct tctrl_softc *sc, int mask, int value)
{
	struct tctrl_req req;

	req.cmdbuf[0] = TS102_OP_CTL_SPEAKER_VOLUME;
	req.cmdbuf[1] = mask;
	req.cmdbuf[2] = value;
	req.cmdlen = 3;
	req.rsplen = 2;

	tctrl_request(sc, &req);

	/*
	 * Note that rspbuf[0] returns the previous value, before any
	 * adjustment happened.
	 */
	if (mask == 0)
		sc->sc_bellvol = value;
	else
		sc->sc_bellvol = req.rspbuf[0];
}

void
tctrl_brightness(struct tctrl_softc *sc, int mask, int value)
{
	struct tctrl_req req;

	req.cmdbuf[0] = TS102_OP_CTL_TFT_BRIGHTNESS;
	req.cmdbuf[1] = mask;
	req.cmdbuf[2] = value;
	req.cmdlen = 3;
	req.rsplen = 2;

	tctrl_request(sc, &req);

	/*
	 * Note that rspbuf[0] returns the previous value, before any
	 * adjustment happened.
	 */
	if (mask == 0)
		sc->sc_brightness = value;
	else
		sc->sc_brightness = req.rspbuf[0];
}

void
tctrl_tft(struct tctrl_softc *sc)
{
	struct tctrl_req req;
	int enable;

	enable = (sc->sc_ext_status & TS102_EXT_STATUS_LID_DOWN) == 0 &&
	    sc->sc_tft_on;

	req.cmdbuf[0] = TS102_OP_CTL_BITPORT;
	req.cmdbuf[1] = ~TS102_BITPORT_TFTPWR;
	req.cmdbuf[2] = enable ? 0 : TS102_BITPORT_TFTPWR;
	req.cmdlen = 3;
	req.rsplen = 2;

	if ((sc->sc_flags & TCTRL_ISXT) != 0 && enable) {
		sb_auxregbisc(0, AUXIO_TFT, 0);
		delay(100000);	/* XXX is such a long delay really necessary? */
	}

	tctrl_request(sc, &req);

	if ((sc->sc_flags & TCTRL_ISXT) != 0 && !enable) {
		delay(100000);	/* XXX is such a long delay really necessary? */
		sb_auxregbisc(0, 0, AUXIO_TFT);
	}
}

void
tctrl_lcd(struct tctrl_softc *sc, int mask, int value)
{
	struct tctrl_req req;

	req.cmdbuf[0] = TS102_OP_CTL_LCD;

	/*
	 * The mask setup for this particular command is *very* bizarre
	 * and totally undocumented.
	 * One would expect the cmdlen and rsplen to be 5 and 3,
	 * respectively, as well.  Though luck, they are not...
	 */

	req.cmdbuf[1] = mask & 0xff;
	req.cmdbuf[4] = (mask >> 8) & 0x01;

	req.cmdbuf[2] = value & 0xff;
	req.cmdbuf[3] = (value >> 8 & 0x01);

	req.cmdlen = 3;
	req.rsplen = 2;

	tctrl_request(sc, &req);
}

int
tctrl_request(struct tctrl_softc *sc, struct tctrl_req *req)
{
	int s, rv;

	while (sc->sc_wantdata != 0) {
		DELAY(1);
	}

	s = splhigh();
	sc->sc_flags |= TCTRL_SEND_REQUEST;
	bcopy(req->cmdbuf, sc->sc_cmdbuf, req->cmdlen);
	sc->sc_wantdata = 1;
	sc->sc_rsplen = req->rsplen;
	sc->sc_cmdlen = req->cmdlen;
	sc->sc_cmdoff = sc->sc_rspoff = 0;

	do {
		tctrl_intr(sc);
	} while (sc->sc_state != TCTRL_IDLE);

	sc->sc_wantdata = 0;	/* just in case... */

	rv = sc->sc_rspack;
	if (rv != 0)
		bcopy(sc->sc_rspbuf, req->rspbuf, sc->sc_rsplen);
	else
		bzero(req->rspbuf, req->rsplen);	/* safety */
	splx(s);

	return (rv);
}

void
tctrl_write_data(sc, v)
	struct tctrl_softc *sc;
	u_int8_t v;
{
	unsigned int i;

	for (i = 0; i < 100; i++)  {
		if (sc->sc_regs->stat & TS102_UCTRL_STS_TXNF_STA)
			break;
	}
	sc->sc_regs->data = v;
}

u_int8_t
tctrl_read_data(sc)
	struct tctrl_softc *sc;
{
	unsigned int i, v;

	for (i = 0; i < 100000; i++) {
		if (sc->sc_regs->stat & TS102_UCTRL_STS_RXNE_STA)
			break;
		DELAY(1);
	}

	v = sc->sc_regs->data;
	sc->sc_regs->stat = TS102_UCTRL_STS_RXNE_STA;
	return v;
}

/*
 * External interfaces, used by the display and pcmcia drivers, as well
 * as the powerdown code.
 */

void
tadpole_powerdown(void)
{
	struct tctrl_softc *sc;
	struct tctrl_req req;

	if (tctrl_cd.cd_ndevs == 0 || tctrl_cd.cd_devs[0] == NULL) {
		return;
	}

	sc = (struct tctrl_softc *)tctrl_cd.cd_devs[0];
	req.cmdbuf[0] = TS102_OP_ADMIN_POWER_OFF;
	req.cmdlen = 1;
	req.rsplen = 1;

	tctrl_request(sc, &req);
}

void
tadpole_set_brightness(int value)
{
	struct tctrl_softc *sc;

	if (tctrl_cd.cd_ndevs == 0 || tctrl_cd.cd_devs[0] == NULL) {
		return;
	}

	sc = (struct tctrl_softc *)tctrl_cd.cd_devs[0];
	if (value != sc->sc_brightness)
		tctrl_brightness(sc, 0, value);
}

int
tadpole_get_brightness()
{
	struct tctrl_softc *sc;

	if (tctrl_cd.cd_ndevs == 0 || tctrl_cd.cd_devs[0] == NULL) {
		return 0;
	}

	sc = (struct tctrl_softc *)tctrl_cd.cd_devs[0];
	return sc->sc_brightness;
}

void
tadpole_set_video(int enabled)
{
	struct tctrl_softc *sc;

	if (tctrl_cd.cd_ndevs == 0 || tctrl_cd.cd_devs[0] == NULL) {
		return;
	}

	sc = (struct tctrl_softc *)tctrl_cd.cd_devs[0];
	if (sc->sc_tft_on ^ enabled) {
		sc->sc_tft_on = enabled;
		/* nothing to do if the lid is down */
		if ((sc->sc_ext_status & TS102_EXT_STATUS_LID_DOWN) == 0)
			tctrl_tft(sc);
	}
}

u_int
tadpole_get_video()
{
	struct tctrl_softc *sc;
	unsigned int status;

	if (tctrl_cd.cd_ndevs == 0 || tctrl_cd.cd_devs[0] == NULL) {
		return 0;
	}

	sc = (struct tctrl_softc *)tctrl_cd.cd_devs[0];
	status = sc->sc_tft_on ? TV_ON : 0;

	return status;
}

void
tadpole_register_extvideo(void (*cb)(void *, int), void *data)
{
	struct tctrl_softc *sc;

	if (tctrl_cd.cd_ndevs == 0 || tctrl_cd.cd_devs[0] == NULL) {
		return;
	}

	sc = (struct tctrl_softc *)tctrl_cd.cd_devs[0];
	sc->sc_evcb = cb;
	sc->sc_evdata = data;

	(*cb)(data, sc->sc_ext_status & TS102_EXT_STATUS_EXTERNAL_VGA_ATTACHED);
}

void
tadpole_set_pcmcia(int slot, int enabled)
{
	struct tctrl_softc *sc;
	int mask;

	if (tctrl_cd.cd_ndevs == 0 || tctrl_cd.cd_devs[0] == NULL) {
		return;
	}

	sc = (struct tctrl_softc *)tctrl_cd.cd_devs[0];
	mask = 1 << slot;
	enabled = enabled ? mask : 0;
	if ((sc->sc_pcmcia_on ^ enabled) & mask) {
		sc->sc_pcmcia_on ^= mask;
		tctrl_lcd(sc, ~TS102_LCD_PCMCIA_ACTIVE,
		    sc->sc_pcmcia_on ? TS102_LCD_PCMCIA_ACTIVE : 0);
	}
}

int
tadpole_bell(u_int duration, u_int freq, u_int volume)
{
	struct tctrl_softc *sc;
	struct tctrl_req req;

	if (tctrl_cd.cd_ndevs == 0 || tctrl_cd.cd_devs[0] == NULL) {
		return (0);
	}

	sc = (struct tctrl_softc *)tctrl_cd.cd_devs[0];

	/* Adjust frequency if necessary (first time or frequence change) */
	if (freq > 0 && freq <= 0xffff && freq != sc->sc_bellfreq) {
		req.cmdbuf[0] = TS102_OP_CMD_SET_BELL_FREQ;
		req.cmdbuf[1] = (freq >> 8) & 0xff;
		req.cmdbuf[2] = freq & 0xff;
		req.cmdlen = 3;
		req.rsplen = 1;

		tctrl_request(sc, &req);

		sc->sc_bellfreq = freq;
	}

	/* Adjust volume if necessary */
	if (volume >= 0 && volume <= 100) {
		volume = (volume * 255) / 100;
		if (volume != sc->sc_bellvol)
			tctrl_bell(sc, 0, volume);

	}

	req.cmdbuf[0] = TS102_OP_CMD_RING_BELL;
	req.cmdbuf[1] = (duration >> 8) & 0xff;
	req.cmdbuf[2] = duration & 0xff;
	req.cmdlen = 3;
	req.rsplen = 1;

	tctrl_request(sc, &req);

	return (1);
}

/*
 * /dev/apm{,ctl} interface code
 */

#define	APMUNIT(dev)	(minor(dev)&0xf0)
#define	APMDEV(dev)	(minor(dev)&0x0f)
#define APMDEV_NORMAL	0
#define APMDEV_CTL	8

int	apmkqfilter(dev_t dev, struct knote *kn);
void	filt_apmrdetach(struct knote *kn);
int	filt_apmread(struct knote *kn, long hint);

struct filterops apmread_filtops =
	{ 1, NULL, filt_apmrdetach, filt_apmread};

#define	SCFLAG_OREAD 	(1 << 0)
#define	SCFLAG_OWRITE	(1 << 1)
#define	SCFLAG_OPEN	(SCFLAG_OREAD|SCFLAG_OWRITE)

int
apmopen(dev_t dev, int flag, int mode, struct proc *p)
{
	struct tctrl_softc *sc;
	int error = 0;

	if (tctrl_cd.cd_ndevs == 0 || tctrl_cd.cd_devs[0] == NULL) {
		return (ENXIO);
	}

	/* apm0 only */
	if (APMUNIT(dev) != 0)
		return (ENODEV);

	sc = (struct tctrl_softc *)tctrl_cd.cd_devs[0];

	switch (APMDEV(dev)) {
	case APMDEV_CTL:
		if (!(flag & FWRITE)) {
			error = EINVAL;
			break;
		}
		if (sc->sc_apmflags & SCFLAG_OWRITE) {
			error = EBUSY;
			break;
		}
		sc->sc_apmflags |= SCFLAG_OWRITE;
		break;
	case APMDEV_NORMAL:
		if (!(flag & FREAD) || (flag & FWRITE)) {
			error = EINVAL;
			break;
		}
		sc->sc_apmflags |= SCFLAG_OREAD;
		break;
	default:
		error = ENXIO;
		break;
	}
	return (error);
}

int
apmclose(dev_t dev, int flag, int mode, struct proc *p)
{
	struct tctrl_softc *sc;

	if (tctrl_cd.cd_ndevs == 0 || tctrl_cd.cd_devs[0] == NULL) {
		return (ENXIO);
	}

	/* apm0 only */
	if (APMUNIT(dev) != 0)
		return (ENODEV);

	sc = (struct tctrl_softc *)tctrl_cd.cd_devs[0];

	switch (APMDEV(dev)) {
	case APMDEV_CTL:
		sc->sc_apmflags &= ~SCFLAG_OWRITE;
		break;
	case APMDEV_NORMAL:
		sc->sc_apmflags &= ~SCFLAG_OREAD;
		break;
	}
	return (0);
}

int
apmioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
{
	struct tctrl_softc *sc;
	struct tctrl_req req;
	struct apm_power_info *power;
	u_int8_t c;
	int error = 0;

	if (tctrl_cd.cd_ndevs == 0 || tctrl_cd.cd_devs[0] == NULL) {
		return (ENXIO);
	}

	/* apm0 only */
	if (APMUNIT(dev) != 0)
		return (ENODEV);

	sc = (struct tctrl_softc *)tctrl_cd.cd_devs[0];

	switch (cmd) {
		/* some ioctl names from linux */
	case APM_IOC_STANDBY:
		if ((flag & FWRITE) == 0)
			error = EBADF;
		else
			error = EOPNOTSUPP;	/* XXX */
		break;
	case APM_IOC_SUSPEND:
		if ((flag & FWRITE) == 0)
			error = EBADF;
		else
			error = EOPNOTSUPP;	/* XXX */
		break;
	case APM_IOC_PRN_CTL:
		if ((flag & FWRITE) == 0)
			error = EBADF;
		else {
			int flag = *(int *)data;
			switch (flag) {
			case APM_PRINT_ON:	/* enable printing */
				sc->sc_apmflags &= ~SCFLAG_PRINT;
				break;
			case APM_PRINT_OFF: /* disable printing */
				sc->sc_apmflags &= ~SCFLAG_PRINT;
				sc->sc_apmflags |= SCFLAG_NOPRINT;
				break;
			case APM_PRINT_PCT: /* disable some printing */
				sc->sc_apmflags &= ~SCFLAG_PRINT;
				sc->sc_apmflags |= SCFLAG_PCTPRINT;
				break;
			default:
				error = EINVAL;
				break;
			}
		}
		break;
	case APM_IOC_GETPOWER:
	        power = (struct apm_power_info *)data;

		if (sc->sc_ext_status &
		    TS102_EXT_STATUS_INTERNAL_BATTERY_ATTACHED) {
			req.cmdbuf[0] = TS102_OP_RD_INT_CHARGE_RATE;
			req.cmdlen = 1;
			req.rsplen = 2;
			tctrl_request(sc, &req);
			if (req.rspbuf[0] != 0)
				power->battery_state = APM_BATT_CHARGING;
			else
				power->battery_state = APM_BATT_UNKNOWN;

			req.cmdbuf[0] = TS102_OP_RD_INT_CHARGE_LEVEL;
			req.cmdlen = 1;
			req.rsplen = 3;
			tctrl_request(sc, &req);

			c = req.rspbuf[0];
			if (c >= TS102_CHARGE_UNKNOWN)
				power->battery_life = 0;
			else {
				power->battery_life = c;
				if (power->battery_state != APM_BATT_CHARGING) {
					if (c < 0x20)
						power->battery_state =
						    APM_BATT_CRITICAL;
					else if (c < 0x40)
						power->battery_state =
						    APM_BATT_HIGH;
					else if (c < 0x66)
						power->battery_state =
						    APM_BATT_HIGH;
				}
			}
		} else {
			power->battery_state = APM_BATTERY_ABSENT;
			power->battery_life = 0;
		}
		power->minutes_left = (u_int)-1;	/* unknown */

		if (sc->sc_ext_status & TS102_EXT_STATUS_MAIN_POWER_AVAILABLE)
			power->ac_state = APM_AC_ON;
		else
			power->ac_state = APM_AC_OFF;
		break;
	case APM_IOC_STANDBY_REQ:
		if ((flag & FWRITE) == 0)
			error = EBADF;
		else
			error = EOPNOTSUPP;	/* XXX */
		break;
	case APM_IOC_SUSPEND_REQ:
		if ((flag & FWRITE) == 0)
			error = EBADF;
		else
			error = EOPNOTSUPP;	/* XXX */
		break;
	default:
		error = ENOTTY;
	}

	return (error);
}

int
apm_record_event(struct tctrl_softc *sc, u_int type)
{
	static int apm_evindex;

	/* skip if no user waiting */
	if ((sc->sc_apmflags & SCFLAG_OPEN) == 0)
		return (1);

	apm_evindex++;
	KNOTE(&sc->sc_note, APM_EVENT_COMPOSE(type, apm_evindex));

	return (0);
}

void
filt_apmrdetach(struct knote *kn)
{
	struct tctrl_softc *sc = (struct tctrl_softc *)kn->kn_hook;

	SLIST_REMOVE(&sc->sc_note, kn, knote, kn_selnext);
}

int
filt_apmread(struct knote *kn, long hint)
{
	/* XXX weird kqueue_scan() semantics */
	if (hint && !kn->kn_data)
		kn->kn_data = (int)hint;

	return (1);
}

int
apmkqfilter(dev_t dev, struct knote *kn)
{
	struct tctrl_softc *sc;

	if (tctrl_cd.cd_ndevs == 0 || tctrl_cd.cd_devs[0] == NULL) {
		return (ENXIO);
	}

	/* apm0 only */
	if (APMUNIT(dev) != 0)
		return (ENODEV);

	sc = (struct tctrl_softc *)tctrl_cd.cd_devs[0];

	switch (kn->kn_filter) {
	case EVFILT_READ:
		kn->kn_fop = &apmread_filtops;
		break;
	default:
		return (EINVAL);
	}

	kn->kn_hook = (caddr_t)sc;
	SLIST_INSERT_HEAD(&sc->sc_note, kn, kn_selnext);

	return (0);
}


code:
/*	$OpenBSD: tctrlvar.h,v 1.7 2008/06/26 05:42:13 ray Exp $	*/
/*	$NetBSD: tctrlvar.h,v 1.1 1999/08/09 18:39:58 matt Exp $ */

/*-
 * Copyright (c) 1998 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Matt Thomas.
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */
#ifndef _SPARC_DEV_TCTRLVAR_H
#define	_SPARC_DEV_TCTRLVAR_H

int	tadpole_bell(u_int, u_int, u_int);
int	tadpole_get_brightness(void);
u_int	tadpole_get_video(void);
void	tadpole_powerdown(void);
void	tadpole_register_extvideo(void (*)(void *, int), void *);
void	tadpole_set_brightness(int);
void	tadpole_set_pcmcia(int, int);
void	tadpole_set_video(int);

/* Values for get_video */
#define	TV_ON		0x01

#endif /* _SPARC_DEV_TCTRLVAR_H */

license: unknown


code:
/*	$OpenBSD: tcx.c,v 1.47 2013/10/20 20:07:26 miod Exp $	*/
/*	$NetBSD: tcx.c,v 1.8 1997/07/29 09:58:14 fair Exp $ */

/*
 * Copyright (c) 2002, 2003, 2008 Miodrag Vallat.  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 ``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.
 *
 *
 *  Copyright (c) 1996 The NetBSD Foundation, Inc.
 *  All rights reserved.
 *
 *  This code is derived from software contributed to The NetBSD Foundation
 *  by Paul Kranenburg.
 *
 *  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 NETBSD FOUNDATION, INC. 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 REGENTS 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.
 */

/*
 * Color display (TCX) driver.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/conf.h>

#include <uvm/uvm_extern.h>

#include <machine/autoconf.h>
#include <machine/pmap.h>
#include <machine/cpu.h>
#include <machine/conf.h>
#include <sparc/sparc/asm.h>
#include <machine/ctlreg.h>

#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wsdisplayvar.h>
#include <dev/rasops/rasops.h>
#include <machine/fbvar.h>

#include <sparc/dev/btreg.h>
#include <sparc/dev/btvar.h>
#include <sparc/dev/tcxreg.h>
#include <dev/ic/bt458reg.h>

#include <dev/cons.h>	/* for prom console hook */

/* per-display variables */
struct tcx_softc {
	struct	sunfb sc_sunfb;		/* common base part */
	struct	rom_reg sc_phys[2];	/* copy of prom ranges for mmap */
	volatile struct bt_regs *sc_bt;	/* Brooktree registers */
	volatile struct tcx_thc *sc_thc;/* THC registers */
	volatile u_int8_t *sc_dfb8;	/* 8 bit plane */
	paddr_t	sc_cplane;		/* S24 control plane PA */
	union	bt_cmap sc_cmap;	/* Brooktree color map */
	struct	intrhand sc_ih;
	int	sc_isconsole;

	/* acceleration parts */
	paddr_t	sc_stipple;		/* Stipple space PA */
	paddr_t	sc_blit;		/* Blitter space PA */
	int	sc_blit_width;		/* maximal blith width */
	int	(*sc_plain_copycols)(void *, int, int, int, int);
};

void	tcx_accel_init(struct tcx_softc *, struct confargs *);
void	tcx_accel_plug(struct tcx_softc *, struct confargs *);
void	tcx_blit(struct tcx_softc *, uint32_t, uint32_t, int);
void	tcx_burner(void *, u_int, u_int);
int	tcx_copyrows(void *, int, int, int);
int	tcx_copycols(void *, int, int, int, int);
int	tcx_do_cursor(struct rasops_info *);
int	tcx_erasecols(void *, int, int, int, long);
int	tcx_eraserows(void *, int, int, long);
int	tcx_intr(void *);
int	tcx_ioctl(void *, u_long, caddr_t, int, struct proc *);
static __inline__
void	tcx_loadcmap_deferred(struct tcx_softc *, u_int, u_int);
paddr_t	tcx_mmap(void *, off_t, int);
void	tcx_prom(struct tcx_softc *);
int	tcx_putchar(void *, int, int, u_int, long);
void	tcx_reset(struct tcx_softc *, int);
void	tcx_s24_reset(struct tcx_softc *, int);
void	tcx_setcolor(void *, u_int, u_int8_t, u_int8_t, u_int8_t);
void	tcx_stipple(struct tcx_softc *, int, int, int, int, int);

struct wsdisplay_accessops tcx_accessops = {
	.ioctl = tcx_ioctl,
	.mmap = tcx_mmap,
	.burn_screen = tcx_burner
};

int	tcxmatch(struct device *, void *, void *);
void	tcxattach(struct device *, struct device *, void *);
int	tcxactivate(struct device *, int);

const struct cfattach tcx_ca = {
	sizeof(struct tcx_softc), tcxmatch, tcxattach,
	NULL, tcxactivate
};

struct cfdriver tcx_cd = {
	NULL, "tcx", DV_DULL
};

/*
 * There are three ways to access the framebuffer memory of the S24:
 * - 26 bits per pixel, in 32-bit words; the low-order 24 bits are blue,
 *   green and red values, and the other two bits select the display modes,
 *   per pixel.
 *   This is the view we'll use to initialize the frame buffer.
 * - 24 bits per pixel, in 32-bit words; the high-order byte reads as zero,
 *   and is ignored on writes (so the mode bits can not be altered).
 *   This is the view available via mmap, for the X server.
 * - 8 bits per pixel, unpadded; writes to this space do not modify the
 *   other 18 bits, which are hidden.
 *   This is the view used for the console emulation mode, as well as for
 *   the X server on 8-bit only devices.
 *
 * The entry-level tcx found on the SPARCstation 4 can only provide the 8-bit
 * mode.
 *
 * Both flavours can be told out by the `tcx-8-bit' property; also, on
 * 8-bit tcx, the 24 bit color regions have a size of zero (or one with
 * the most recent PROM versions).
 */
#define	TCX_CTL_8_MAPPED	0x00000000	/* 8 bits, uses colormap */
#define	TCX_CTL_24_MAPPED	0x01000000	/* 24 bits, uses colormap */
#define	TCX_CTL_24_LEVEL	0x03000000	/* 24 bits, direct color */
#define	TCX_CTL_PIXELMASK	0x00ffffff	/* mask for index/level */

int
tcxmatch(struct device *parent, void *vcf, void *aux)
{
	struct confargs *ca = aux;
	struct romaux *ra = &ca->ca_ra;

	if (strcmp(ra->ra_name, "SUNW,tcx") != 0)
		return (0);

	return (1);
}

void
tcxattach(struct device *parent, struct device *self, void *args)
{
	struct tcx_softc *sc = (struct tcx_softc *)self;
	struct confargs *ca = args;
	int node, pri;
	char *nam = NULL;
	vaddr_t thc_offset;

	pri = ca->ca_ra.ra_intr[0].int_pri;
	printf(" pri %d: ", pri);

	node = ca->ca_ra.ra_node;
	sc->sc_isconsole = node == fbnode;

	if (ca->ca_ra.ra_nreg < TCX_NREG) {
		printf("expected %d registers, got %d\n",
		    TCX_NREG, ca->ca_ra.ra_nreg);
		return;
	}

	nam = getpropstring(node, "model");
	if (*nam != '\0')
		printf("%s, ", nam);

	/*
	 * Copy the register address spaces needed for mmap operation.
	 */
	sc->sc_phys[0] = ca->ca_ra.ra_reg[TCX_REG_DFB8];
	sc->sc_phys[1] = ca->ca_ra.ra_reg[TCX_REG_DFB24];

	/*
	 * Can't trust the PROM range len here, it is only 4 bytes on the
	 * 8-bit model. Not that it matters much anyway since we map in
	 * pages.
	 */
	sc->sc_bt = (volatile struct bt_regs *)
	    mapiodev(&ca->ca_ra.ra_reg[TCX_REG_CMAP], 0, sizeof *sc->sc_bt);

	/*
	 * For some reason S24 PROM sets up TEC and THC ranges at the
	 * right addresses (701000 and 301000), while 8 bit TCX doesn't
	 * (and uses 70000 and 30000) - be sure to only compensate on 8 bit
	 * models.
	 */
	if (((vaddr_t)ca->ca_ra.ra_reg[TCX_REG_THC].rr_paddr & 0x1000) != 0)
		thc_offset = 0;
	else
		thc_offset = 0x1000;
	sc->sc_thc = (volatile struct tcx_thc *)
	    mapiodev(&ca->ca_ra.ra_reg[TCX_REG_THC],
	        thc_offset, sizeof *sc->sc_thc);

	/*
	 * Find out frame buffer geometry, so that we know how much
	 * memory to map.
	 */
	fb_setsize(&sc->sc_sunfb, 8, 1152, 900, node, ca->ca_bustype);

	sc->sc_dfb8 = mapiodev(&ca->ca_ra.ra_reg[TCX_REG_DFB8], 0,
	    round_page(sc->sc_sunfb.sf_fbsize));

	/*
	 * If the frame buffer advertizes itself as the 8 bit model, or
	 * if the PROM ranges are too small, limit ourselves to 8 bit
	 * operations.
	 *
	 * Further code needing to know which capabilities the frame buffer
	 * has will rely on sc_cplane being non-zero if 24 bit operation
	 * is possible.
	 */
	if (!node_has_property(node, "tcx-8-bit") &&
	    ca->ca_ra.ra_reg[TCX_REG_RDFB32].rr_len >=
	      sc->sc_sunfb.sf_fbsize * 4) {
		sc->sc_cplane =
		    (paddr_t)ca->ca_ra.ra_reg[TCX_REG_RDFB32].rr_paddr;
	}

	printf("%dx%dx%d\n",
	    sc->sc_sunfb.sf_width, sc->sc_sunfb.sf_height,
	    sc->sc_cplane == 0 ? 8 : 24);

	/*
	 * Set up mappings for the acceleration code. This may fail.
	 */
	tcx_accel_init(sc, ca);

	/* reset cursor & frame buffer controls */
	tcx_reset(sc, 8);

	/* enable video */
	tcx_burner(sc, 1, 0);

	sc->sc_sunfb.sf_ro.ri_hw = sc;
	sc->sc_sunfb.sf_ro.ri_bits = (void *)sc->sc_dfb8;

	fbwscons_init(&sc->sc_sunfb, sc->sc_isconsole);
	fbwscons_setcolormap(&sc->sc_sunfb, tcx_setcolor);

	/*
	 * Now plug accelerated console routines, if possible.
	 */
	tcx_accel_plug(sc, ca);

	sc->sc_ih.ih_fun = tcx_intr;
	sc->sc_ih.ih_arg = sc;
	intr_establish(pri, &sc->sc_ih, IPL_FB, self->dv_xname);

	if (sc->sc_isconsole)
		fbwscons_console_init(&sc->sc_sunfb, -1);

	fbwscons_attach(&sc->sc_sunfb, &tcx_accessops, sc->sc_isconsole);
}

int
tcxactivate(struct device *self, int act)
{
	struct tcx_softc *sc = (struct tcx_softc *)self;
	int ret = 0;

	switch (act) {
	case DVACT_POWERDOWN:
		if (sc->sc_isconsole)
			tcx_prom(sc);
		break;
	}

	return (ret);
}

int
tcx_ioctl(void *dev, u_long cmd, caddr_t data, int flags, struct proc *p)
{
	struct tcx_softc *sc = dev;
	struct wsdisplay_cmap *cm;
	struct wsdisplay_fbinfo *wdf;
	int error;

	/*
	 * Note that, although the emulation (text) mode is running in 8-bit
	 * mode, if the frame buffer is able to run in 24-bit mode, it will
	 * be advertized as such.
	 */
	switch (cmd) {
	case WSDISPLAYIO_GTYPE:
		*(u_int *)data = WSDISPLAY_TYPE_SUNTCX;
		break;
	case WSDISPLAYIO_GINFO:
		wdf = (struct wsdisplay_fbinfo *)data;
		wdf->height = sc->sc_sunfb.sf_height;
		wdf->width = sc->sc_sunfb.sf_width;
		wdf->depth = sc->sc_sunfb.sf_depth;
		wdf->cmsize = sc->sc_cplane == 0 ? 256 : 0;
		break;
	case WSDISPLAYIO_GETSUPPORTEDDEPTH:
		if (sc->sc_cplane != 0)
			*(u_int *)data = WSDISPLAYIO_DEPTH_24_32;
		else
			return (-1);
		break;
	case WSDISPLAYIO_LINEBYTES:
		if (sc->sc_cplane == 0)
			*(u_int *)data = sc->sc_sunfb.sf_linebytes;
		else
			*(u_int *)data = sc->sc_sunfb.sf_linebytes * 4;
		break;

	case WSDISPLAYIO_GETCMAP:
		if (sc->sc_cplane == 0) {
			cm = (struct wsdisplay_cmap *)data;
			error = bt_getcmap(&sc->sc_cmap, cm);
			if (error)
				return (error);
		}
		break;
	case WSDISPLAYIO_PUTCMAP:
		if (sc->sc_cplane == 0) {
			cm = (struct wsdisplay_cmap *)data;
			error = bt_putcmap(&sc->sc_cmap, cm);
			if (error)
				return (error);
			tcx_loadcmap_deferred(sc, cm->index, cm->count);
		}
		break;

	case WSDISPLAYIO_SMODE:
		if (*(int *)data == WSDISPLAYIO_MODE_EMUL) {
			/* Back from X11 to text mode */
			tcx_reset(sc, 8);
		} else {
			/* Starting X11, try to switch to 24 bit mode */
			if (sc->sc_cplane != 0)
				tcx_reset(sc, 32);
		}
		break;

	case WSDISPLAYIO_SVIDEO:
	case WSDISPLAYIO_GVIDEO:
		break;

	default:
		return (-1);	/* not supported yet */
	}

	return (0);
}

void
tcx_reset(struct tcx_softc *sc, int depth)
{
	volatile struct bt_regs *bt;

	/* Hide the cursor, just in case */
	sc->sc_thc->thc_cursoraddr = THC_CURSOFF;

	/* Enable cursor in Brooktree DAC. */
	bt = sc->sc_bt;
	bt->bt_addr = BT_CR << 24;
	bt->bt_ctrl |= (BTCR_DISPENA_OV1 | BTCR_DISPENA_OV0) << 24;

	/*
	 * Change mode if appropriate
	 */
	if (sc->sc_sunfb.sf_depth != depth) {
		if (sc->sc_cplane != 0) {
			tcx_s24_reset(sc, depth);
		}

		if (depth == 8)
			fbwscons_setcolormap(&sc->sc_sunfb, tcx_setcolor);
	}

	sc->sc_sunfb.sf_depth = depth;
}

void
tcx_prom(struct tcx_softc *sc)
{
	extern struct consdev consdev_prom;

	if (sc->sc_sunfb.sf_depth != 8) {
		/*
	 	 * Select 8-bit mode.
	 	 */
		tcx_reset(sc, 8);

		/*
	 	 * Go back to prom output for the last few messages, so they
	 	 * will be displayed correctly.
	 	 */
		cn_tab = &consdev_prom;
	}
}

void
tcx_burner(void *v, u_int on, u_int flags)
{
	struct tcx_softc *sc = v;
	int s;
	u_int32_t thcm;

	s = splhigh();
	thcm = sc->sc_thc->thc_hcmisc;
	if (on) {
		thcm |= THC_MISC_VIDEN;
		thcm &= ~(THC_MISC_VSYNC_DISABLE | THC_MISC_HSYNC_DISABLE);
	} else {
		thcm &= ~THC_MISC_VIDEN;
		if (flags & WSDISPLAY_BURN_VBLANK)
			thcm |= THC_MISC_VSYNC_DISABLE | THC_MISC_HSYNC_DISABLE;
	}
	sc->sc_thc->thc_hcmisc = thcm;
	splx(s);
}

paddr_t
tcx_mmap(void *v, off_t offset, int prot)
{
	struct tcx_softc *sc = v;
	int regno;

	if (offset & PGOFSET || offset < 0)
		return (-1);

	/* Allow mapping as a dumb framebuffer from offset 0 */
	if (sc->sc_sunfb.sf_depth == 8 && offset < sc->sc_sunfb.sf_fbsize)
		regno = 0;	/* copy of TCX_REG_DFB8 */
	else if (sc->sc_sunfb.sf_depth != 8 &&
	    offset < sc->sc_sunfb.sf_fbsize * 4)
		regno = 1;	/* copy of TCX_REG_RDFB32 */
	else
		return (-1);

	return (REG2PHYS(&sc->sc_phys[regno], offset) | PMAP_NC);
}

void
tcx_setcolor(void *v, u_int index, u_int8_t r, u_int8_t g, u_int8_t b)
{
	struct tcx_softc *sc = v;

	bt_setcolor(&sc->sc_cmap, sc->sc_bt, index, r, g, b, 1);
}

static __inline__ void
tcx_loadcmap_deferred(struct tcx_softc *sc, u_int start, u_int ncolors)
{
	u_int32_t thcm;

	thcm = sc->sc_thc->thc_hcmisc;
	thcm |= THC_MISC_INTEN;
	sc->sc_thc->thc_hcmisc = thcm;
}

int
tcx_intr(void *v)
{
	struct tcx_softc *sc = v;
	u_int32_t thcm;

	thcm = sc->sc_thc->thc_hcmisc;
	if (thcm & THC_MISC_INTEN) {
		thcm &= ~(THC_MISC_INTR | THC_MISC_INTEN);

		/* Acknowledge the interrupt */
		sc->sc_thc->thc_hcmisc = thcm | THC_MISC_INTR;

		bt_loadcmap(&sc->sc_cmap, sc->sc_bt, 0, 256, 1);

		/* Disable further interrupts now */
		sc->sc_thc->thc_hcmisc = thcm;

		return (1);
	}

	return (0);
}

/*
 * Switch video mode and clear screen
 */
void
tcx_s24_reset(struct tcx_softc *sc, int depth)
{
	struct rasops_info *ri = &sc->sc_sunfb.sf_ro;
	uint32_t pixel;
	paddr_t dst;
	int n;

	if (depth == 8)
		pixel = TCX_CTL_8_MAPPED | (ri->ri_devcmap[WSCOL_WHITE] & 0xff);
	else
		pixel = TCX_CTL_24_LEVEL | 0xffffff;

	/*
	 * Set the first 32 pixels as white in the intended mode, using the
	 * control plane.
	 */
	dst = sc->sc_cplane;
	for (n = 32; n != 0; n--) {
		sta(dst, ASI_BYPASS, pixel);
		dst += 4;
	}

	/*
	 * Do the remaining pixels: either with the blitter if we can use it,
	 * or continuing manual writes if we can't.
	 */
	if (sc->sc_blit != 0) {
		dst = sc->sc_blit + (32 << 3);
		pixel = ((sc->sc_blit_width - 1) << 24) | 0;
		for (n = sc->sc_sunfb.sf_fbsize - 32; n != 0;
		    n -= sc->sc_blit_width) {
			stda(dst, ASI_BYPASS, pixel);
			dst += sc->sc_blit_width << 3;
		}
	} else {
		/* this relies on video memory being contiguous */
		for (n = sc->sc_sunfb.sf_fbsize - 32; n != 0; n--) {
			sta(dst, ASI_BYPASS, pixel);
			dst += 4;
		}
	}
}

/*
 * Accelerated console operations
 *
 * Most of the TCX and S24 frame buffers can not perform simple ROPs
 * besides GXcopy. Those which can, have a ``stip-rop'' property,
 * and appear to support at least copy and invert rops.
 *
 * We use the blitter for block moves: copycols, copyrows; and the
 * stipple for solid fills: erasecols, eraserows, putchar.
 *
 * This work has been made possible thanks to the information collected in
 *   http://ftp.rodents-montreal.org/mouse/docs/Sun/S24/memory-map
 * and experiments.
 *
 * It turns out that frame buffer memory is contiguous, so addressing
 * of a given (x,y) pixel is always (y * width + x), regardless of the
 * actual frame buffer resolution.
 */

void
tcx_accel_init(struct tcx_softc *sc, struct confargs *ca)
{
	int stipple_align, regno;

	/*
	 * On S24, try and map raw blit and raw stipple spaces.
	 * We prefer the raw spaces so that we can eventually switch
	 * between 8 bit and 24 bit modes with blitter operations.
	 *
	 * However, on 8-bit TCX, these spaces are missing (and empty!),
	 * so we should fallback to non-raw spaces in this case.
	 *
	 * Since this frame buffer can only exist on SS4 and SS5,
	 * we can rely upon the fact this code will only run on sun4m,
	 * and use stda() bypassing the MMU to access these spaces,
	 * instead of mapping them (8MB KVA each, after all, even more
	 * on an SS4 with the resolution extender VSIMM).
	 */

	sc->sc_blit_width = getpropint(ca->ca_ra.ra_node, "blit-width", 0);
	if (sc->sc_blit_width > 5)
		sc->sc_blit_width = 5;	/* paranoia */
	if (sc->sc_blit_width <= 3)	/* not worth until more than 8 pixels */
		sc->sc_blit_width = 0;
	else {
		sc->sc_blit_width = 1 << sc->sc_blit_width;

		regno = sc->sc_cplane == 0 ? TCX_REG_BLIT : TCX_REG_RBLIT;
		if (ca->ca_ra.ra_reg[regno].rr_len >=
		    sc->sc_sunfb.sf_fbsize * 8)
			sc->sc_blit = (paddr_t)ca->ca_ra.ra_reg[regno].rr_paddr;
	}

	/*
	 * Do not assume the stipple space is usable unless there is a
	 * `stipple-align' property.  Also, don't try to use it if the
	 * alignment is not 32 - our code may not behave correctly.
	 */

	stipple_align = getpropint(ca->ca_ra.ra_node, "stipple-align", 0);
	if (stipple_align == 5) {
		regno = sc->sc_cplane == 0 ? TCX_REG_STIP : TCX_REG_RSTIP;
		if (ca->ca_ra.ra_reg[regno].rr_len >=
		    sc->sc_sunfb.sf_fbsize * 8)
			sc->sc_stipple =
			    (paddr_t)ca->ca_ra.ra_reg[regno].rr_paddr;
	}
}

/*
 * After rasops has initialized, override its basic operations with
 * accelerated routines whenever possible (this will depend on the
 * frame buffer capabilities and our success at using blit and
 * stipple spaces).
 */
void
tcx_accel_plug(struct tcx_softc *sc, struct confargs *ca)
{
	struct rasops_info *ri = &sc->sc_sunfb.sf_ro;

	if (sc->sc_stipple != 0) {
		sc->sc_sunfb.sf_ro.ri_ops.eraserows = tcx_eraserows;
		sc->sc_sunfb.sf_ro.ri_ops.erasecols = tcx_erasecols;
		if (node_has_property(ca->ca_ra.ra_node, "stip-rop")) {
			/* needs GXinvert support */
			sc->sc_sunfb.sf_ro.ri_do_cursor = tcx_do_cursor;
		}
		if (ri->ri_font->fontwidth <= 16) {
			/*
			 * The code in tcx_putchar really can handle up
			 * to 32 bit font width if one extends the font
			 * bits loading part to support more than 16 bits,
			 * but then there is no such font in wsfont at the
			 * moment, so why bother.
			 */
			sc->sc_sunfb.sf_ro.ri_ops.putchar = tcx_putchar;
		}
	}

	if (sc->sc_blit != 0) {
		sc->sc_sunfb.sf_ro.ri_ops.copyrows = tcx_copyrows;
		sc->sc_plain_copycols = sc->sc_sunfb.sf_ro.ri_ops.copycols;
		sc->sc_sunfb.sf_ro.ri_ops.copycols = tcx_copycols;
	}
}

/*
 * Blitter operations
 *
 * Since the blitter only operates on 1 pixel height areas, we need
 * to invoke it many times, and handle overlapping regions ourselves.
 */

int
tcx_copycols(void *cookie, int row, int src, int dst, int n)
{
	struct rasops_info *ri = cookie;
	struct tcx_softc *sc = ri->ri_hw;
	int h;
	uint32_t dstaddr, srcaddr;

	if (dst > src && dst < src + n) {
		/* Areas overlap, do it the slow but safe way */
		return (*sc->sc_plain_copycols)(cookie, row, src, dst, n);
	}

	/* Areas do not overlap dangerously, copy forwards */

	/* actual columns */
	n *= ri->ri_font->fontwidth;
	src *= ri->ri_font->fontwidth;
	dst *= ri->ri_font->fontwidth;

	row *= ri->ri_font->fontheight;
	srcaddr = dstaddr = ri->ri_xorigin +
	    (ri->ri_yorigin + row) * sc->sc_sunfb.sf_width;
	srcaddr += src;
	dstaddr += dst;

	for (h = ri->ri_font->fontheight; h != 0; h--) {
		tcx_blit(sc, dstaddr, srcaddr, n);
		srcaddr += sc->sc_sunfb.sf_width;
		dstaddr += sc->sc_sunfb.sf_width;
	}

	return 0;
}

int
tcx_copyrows(void *cookie, int src, int dst, int n)
{
	struct rasops_info *ri = cookie;
	struct tcx_softc *sc = ri->ri_hw;
	int reverse;
	uint32_t dstaddr, srcaddr;

	/* actual lines */
	src *= ri->ri_font->fontheight;
	dst *= ri->ri_font->fontheight;
	n *= ri->ri_font->fontheight;

	if (dst > src && dst < src + n) {
		/* Areas overlap, copy backwards */
		dst += n - 1;
		src += n - 1;
		reverse = 1;
	} else {
		/* Areas do not overlap dangerously, copy forwards */
		reverse = 0;
	}

	dstaddr = (ri->ri_yorigin + dst) * sc->sc_sunfb.sf_width;
	srcaddr = (ri->ri_yorigin + src) * sc->sc_sunfb.sf_width;

	dstaddr += ri->ri_xorigin;
	srcaddr += ri->ri_xorigin;

	for (; n != 0; n--) {
		tcx_blit(sc, dstaddr, srcaddr, ri->ri_emuwidth);
		if (reverse) {
			dstaddr -= sc->sc_sunfb.sf_width;
			srcaddr -= sc->sc_sunfb.sf_width;
		} else {
			dstaddr += sc->sc_sunfb.sf_width;
			srcaddr += sc->sc_sunfb.sf_width;
		}
	}

	return 0;
}

/*
 * Perform a blit operation, copying the line starting at computed
 * position src to computed position dst, for a length of len pixels.
 */
void
tcx_blit(struct tcx_softc *sc, uint32_t dst, uint32_t src, int len)
{
	int cx;
	uint32_t addr;

	addr = sc->sc_blit + (dst << 3);

	/* do the incomplete chunk first if needed */
	cx = len & (sc->sc_blit_width - 1);
	if (cx == 0)
		cx = sc->sc_blit_width;

	while (len != 0) {
		stda(addr, ASI_BYPASS, ((cx - 1) << 24) | src);
		src += cx;
		addr += cx << 3;
		len -= cx;
		/* and then full steam ahead for the others */
		cx = sc->sc_blit_width;
	}
}

/*
 * Stipple operations
 */

/* canonical rop values */
#define	GXcopy		0x03
#define	GXinvert	0x0a

#define	STIPPLE_ROP_SHIFT	28

/*
 * Perform a stipple operation rop from (x, y) to (x + cnt - 1, y).
 *
 * We probably should honour the stipple alignment property (stipple-align),
 * in case it is different than 32 (1 << 5). However, due to the way
 * the stipple space is accessed, it is not possible to have a stricter
 * alignment requirement, so let's settle for 32. There probably haven't
 * been TCX boards with relaxed alignment rules anyway.
 */
void
tcx_stipple(struct tcx_softc *sc, int x, int y, int cnt, int rop, int bg)
{
	int rx;		/* aligned x */
	int lbcnt;	/* count of untouched pixels on the left */
	int rbcnt;	/* count of untouched pixels on the right */
	uint32_t wmask;	/* write mask */
	uint32_t soffs;	/* stipple offset */
	uint64_t scmd;

	scmd = rop << STIPPLE_ROP_SHIFT;
	scmd |= TCX_CTL_8_MAPPED | bg;	/* pixel bits, here in 8-bit mode */
	scmd <<= 32;

	/*
	 * The first pass needs to align the position to a 32 pixel
	 * boundary, which explains why the loop is a bit unnatural
	 * at first glance.
	 */
	rx = x & ~(32 - 1);
	soffs = sc->sc_stipple + ((y * sc->sc_sunfb.sf_width + rx) << 3);
	lbcnt = x - rx;
	wmask = 0xffffffff >> lbcnt;

	while (cnt != 0) {
		rbcnt = (32 - lbcnt) - cnt;
		if (rbcnt < 0)
			rbcnt = 0;
		if (rbcnt != 0)
			wmask &= ~((1 << rbcnt) - 1);

		stda(soffs, ASI_BYPASS, scmd | wmask);

		cnt -= (32 - lbcnt) - rbcnt;
		soffs += 32 << 3;

		/* further loops are aligned */
		lbcnt = 0;
		wmask = 0xffffffff;
	}
}

int
tcx_erasecols(void *cookie, int row, int col, int n, long attr)
{
	struct rasops_info *ri = cookie;
	struct tcx_softc *sc = ri->ri_hw;
	int fg, bg, h, cury, sx;

	ri->ri_ops.unpack_attr(cookie, attr, &fg, &bg, NULL);
	bg = ri->ri_devcmap[bg] & 0xff;	/* 8 bit palette index */

	n *= ri->ri_font->fontwidth;
	sx = ri->ri_xorigin + col * ri->ri_font->fontwidth;

	cury = ri->ri_yorigin + row * ri->ri_font->fontheight;
	for (h = ri->ri_font->fontheight; h != 0; cury++, h--)
		tcx_stipple(sc, sx, cury, n, GXcopy, bg);

	return 0;
}

int
tcx_eraserows(void *cookie, int row, int n, long attr)
{
	struct rasops_info *ri = cookie;
	struct tcx_softc *sc = ri->ri_hw;
	int fg, bg, x, y, w;

	ri->ri_ops.unpack_attr(cookie, attr, &fg, &bg, NULL);
	bg = ri->ri_devcmap[bg] & 0xff;	/* 8 bit palette index */

	if ((n == ri->ri_rows) && ISSET(ri->ri_flg, RI_FULLCLEAR)) {
		n = ri->ri_height;
		x = y = 0;
		w = ri->ri_width;
	} else {
		n *= ri->ri_font->fontheight;
		x = ri->ri_xorigin;
		y = ri->ri_yorigin + row * ri->ri_font->fontheight;
		w = ri->ri_emuwidth;
	}

	for (; n != 0; y++, n--)
		tcx_stipple(sc, x, y, w, GXcopy, bg);

	return 0;
}

int
tcx_do_cursor(struct rasops_info *ri)
{
	struct tcx_softc *sc = ri->ri_hw;
	int x, y, n;

	x = ri->ri_ccol * ri->ri_font->fontwidth + ri->ri_xorigin;
	y = ri->ri_crow * ri->ri_font->fontheight + ri->ri_yorigin;

	for (n = ri->ri_font->fontheight; n != 0; y++, n--)
		tcx_stipple(sc, x, y, ri->ri_font->fontwidth, GXinvert, 0xff);

	return 0;
}

int
tcx_putchar(void *cookie, int row, int col, u_int uc, long attr)
{
	struct rasops_info *ri = cookie;
	struct tcx_softc *sc = ri->ri_hw;
	struct wsdisplay_font *font = ri->ri_font;
	int fg, bg, ul;
	int h, x, y;
	uint8_t *fontbits;
	uint32_t fgpattern, bgpattern;

	ri->ri_ops.unpack_attr(cookie, attr, &fg, &bg, &ul);
	fg = ri->ri_devcmap[fg] & 0xff;	/* 8 bit palette index */
	bg = ri->ri_devcmap[bg] & 0xff;	/* 8 bit palette index */

	x = ri->ri_xorigin + col * font->fontwidth;
	y = ri->ri_yorigin + row * font->fontheight;

	if (uc == ' ') {
		/* inline tcx_erasecols(cookie, row, col, 1, attr) */
		for (h = font->fontheight; h != 0; y++, h--)
			tcx_stipple(sc, x, y, font->fontwidth, GXcopy,
			    h == 2 && ul != 0 ? fg : bg);
	} else {
		int rx;
		int lbcnt, rbcnt;
		uint32_t soffs;
		uint64_t stmpl, scmd;

		stmpl = (GXcopy << STIPPLE_ROP_SHIFT) | TCX_CTL_8_MAPPED;

		fontbits = (uint8_t *)font->data +
		    (uc - font->firstchar) * ri->ri_fontscale;

		rx = x & ~(32 - 1);
		lbcnt = x - rx;
		rbcnt = (32 - lbcnt) - font->fontwidth; /* may be negative */
		soffs = sc->sc_stipple +
		    ((y * sc->sc_sunfb.sf_width + rx) << 3);

		for (h = font->fontheight; h != 0; y++, h--) {
			if (font->fontwidth <= 8)
				fgpattern = *(uint8_t *)fontbits >>
				    (8 - font->fontwidth);
			else /* if (font->fontwidth <= 16) */
				fgpattern = *(uint16_t *)fontbits >>
				    (16 - font->fontwidth);
			/* see tcx_accel_plug() for the reason why
			   larger font sizes are not supported, yet */
			fontbits += font->stride;

			/* underline */
			if (ul && h == 2)
				fgpattern = 0xffffffff &
				    ((1 << font->fontwidth) - 1);

			bgpattern = ~fgpattern &
			    ((1 << font->fontwidth) - 1);

			/*
			 * We have a pattern of font->fontwidth bits in
			 * the low bits of `fgpattern' and its one-complement
			 * in `bgpattern'. The bgpattern bits need to be
			 * painted with the background colour, while the
			 * fgpattern bits need to be painted with the
			 * foreground colour.
			 *
			 * The particular character cell position might
			 * span two stipple cells, so we have to account
			 * for this.
			 */

			if (rbcnt >= 0) {
				/* everything fits in one stipple cell. */

				/* foreground */
				scmd = (stmpl | fg) << 32;
				stda(soffs, ASI_BYPASS,
				    scmd | (fgpattern << rbcnt));

				/* background */
				scmd = (stmpl | bg) << 32;
				stda(soffs, ASI_BYPASS,
				    scmd | (bgpattern << rbcnt));
			} else {
				/* needs two stipple cells. */

				/* foreground, first stipple cell */
				scmd = (stmpl | fg) << 32;
				stda(soffs, ASI_BYPASS,
				    scmd | (fgpattern >> -rbcnt));

				/* background, first stipple cell */
				scmd = (stmpl | bg) << 32;
				stda(soffs, ASI_BYPASS,
				    scmd | (bgpattern >> -rbcnt));

				/* rotate patterns, relying on 32 bit size */
				fgpattern <<= (32 + rbcnt);
				bgpattern <<= (32 + rbcnt);

				/* foreground, second stipple cell */
				scmd = (stmpl | fg) << 32;
				stda(soffs + (32 << 3), ASI_BYPASS,
				    scmd | fgpattern);

				/* background, second stipple cell */
				scmd = (stmpl | bg) << 32;
				stda(soffs + (32 << 3), ASI_BYPASS,
				    scmd | bgpattern);
			}

			soffs += sc->sc_sunfb.sf_width << 3;
		}
	}

	return 0;
}


code:
/*	$OpenBSD: tcxreg.h,v 1.5 2008/06/26 05:42:13 ray Exp $	*/
/*	$NetBSD: tcxreg.h,v 1.1 1996/06/19 13:17:35 pk Exp $ */

/*
 *  Copyright (c) 1996 The NetBSD Foundation, Inc.
 *  All rights reserved.
 *
 *  This code is derived from software contributed to The NetBSD Foundation
 *  by Paul Kranenburg.
 *
 *  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 NETBSD FOUNDATION, INC. 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 REGENTS 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.
 */

/*
 * A TCX is composed of numerous groups of control registers, all with TLAs:
 *	DHC - ???
 *	TEC - transform engine control?
 *	THC - TEC Hardware Configuration
 *	ROM - a 128Kbyte ROM with who knows what in it.
 *	STIP - ???
 *	RSTIP - Raw ???
 *	BLIT - ???
 *	RBLIT - Raw ???
 *	ALT - ???
 *	colormap - see below
 *	frame buffer memory (video RAM)
 *	possible other stuff
 *
 */
#define TCX_REG_DFB8	0
#define TCX_REG_DFB24	1
#define TCX_REG_STIP	2
#define TCX_REG_BLIT	3
#define TCX_REG_RDFB32	4
#define TCX_REG_RSTIP	5
#define TCX_REG_RBLIT	6
#define TCX_REG_TEC	7
#define TCX_REG_CMAP	8
#define TCX_REG_THC	9
#define TCX_REG_ROM	10
#define TCX_REG_DHC	11
#define TCX_REG_ALT	12

#define TCX_NREG	13

/*
 * The layout of the THC.
 */
struct tcx_thc {
	u_int32_t	thc_config;
	u_int32_t	thc_xxx1[31];
	u_int32_t	thc_sensebus;
	u_int32_t	thc_xxx2[3];
	u_int32_t	thc_delay;
	u_int32_t	thc_strapping;
	u_int32_t	thc_xxx3[1];
	u_int32_t	thc_linecount;
	u_int32_t	thc_xxx4[478];
	u_int32_t	thc_hcmisc;
	u_int32_t	thc_xxx5[56];
	u_int32_t	thc_cursoraddr;
	u_int32_t	thc_cursorAdata[32];
	u_int32_t	thc_cursorBdata[32];

};

/* cursor x/y position for 'off' */
#define	THC_CURSOFF		((65536-32) | ((65536-32) << 16))

/* bits in thc_config ??? */
#define THC_CFG_FBID		0xf0000000	/* id mask */
#define THC_CFG_FBID_SHIFT	28
#define THC_CFG_SENSE		0x07000000	/* sense mask */
#define THC_CFG_SENSE_SHIFT	24
#define THC_CFG_REV		0x00f00000	/* revision mask */
#define THC_CFG_REV_SHIFT	20
#define THC_CFG_RST		0x00008000	/* reset */

/* bits in thc_hcmisc */
#define	THC_MISC_OPENFLG	0x80000000	/* open flag (what's that?) */
#define	THC_MISC_SWERR_EN	0x20000000	/* enable SW error interrupt */
#define	THC_MISC_VSYNC_LEVEL	0x08000000	/* vsync level when disabled */
#define	THC_MISC_HSYNC_LEVEL	0x04000000	/* hsync level when disabled */
#define	THC_MISC_VSYNC_DISABLE	0x02000000	/* vsync disable */
#define	THC_MISC_HSYNC_DISABLE	0x01000000	/* hsync disable */
#define	THC_MISC_RESET		0x00001000	/* reset */
#define	THC_MISC_VIDEN		0x00000400	/* video enable */
#define	THC_MISC_SYNC		0x00000200	/* not sure what ... */
#define	THC_MISC_VSYNC		0x00000100	/* ... these really are */
#define	THC_MISC_SYNCEN		0x00000080	/* sync enable */
#define	THC_MISC_CURSRES	0x00000040	/* cursor resolution */
#define	THC_MISC_INTEN		0x00000020	/* v.retrace intr enable */
#define	THC_MISC_INTR		0x00000010	/* intr pending / ack */
#define	THC_MISC_DACWAIT	0x0000000f	/* cycles before transfer */

/*
 * Partial description of TEC.
 */
struct tcx_tec {
	u_int32_t	tec_config;	/* what's in it? */
	u_int32_t	tec_xxx0[35];
	u_int32_t	tec_delay;	/* */
#define TEC_DELAY_SYNC		0x00000f00
#define TEC_DELAY_WR_F		0x000000c0	/* wr falling */
#define TEC_DELAY_WR_R		0x00000030	/* wr rising */
#define TEC_DELAY_SOE_F		0x0000000c	/* soe falling */
#define TEC_DELAY_SOE_S		0x00000003	/* soe sclk */
	u_int32_t	tec_strapping;	/* */
#define TEC_STRAP_FIFO_LIMIT	0x00f00000
#define TEC_STRAP_CACHE_EN	0x00010000
#define TEC_STRAP_ZERO_OFFSET	0x00008000
#define TEC_STRAP_REFRSH_DIS	0x00004000
#define TEC_STRAP_REF_LOAD	0x00001000
#define TEC_STRAP_REFRSH_PERIOD	0x000003ff
	u_int32_t	tec_hcmisc;	/* */
	u_int32_t	tec_linecount;	/* */
	u_int32_t	tec_hss;	/* */
	u_int32_t	tec_hse;	/* */
	u_int32_t	tec_hds;	/* */
	u_int32_t	tec_hsedvs;	/* */
	u_int32_t	tec_hde;	/* */
	u_int32_t	tec_vss;	/* */
	u_int32_t	tec_vse;	/* */
	u_int32_t	tec_vds;	/* */
	u_int32_t	tec_vde;	/* */
};


code:
/*	$OpenBSD: ts102.c,v 1.21 2015/03/28 19:07:08 miod Exp $	*/
/*
 * Copyright (c) 2003, 2004, Miodrag Vallat.
 *
 * 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 way 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.
 *
 * - IPL for the cards interrupt handlers are not respected. See the stp4020
 *   driver source for comments about this.
 *
 * 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/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/kthread.h>
#include <sys/proc.h>
#include <sys/queue.h>

#include <machine/bus.h>

#include <uvm/uvm_extern.h>

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

#include <sparc/dev/tctrlvar.h>
#include <sparc/dev/ts102reg.h>

#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;

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

	volatile u_int8_t	*td_regs;
	struct rom_reg		td_rr;
	vaddr_t			td_space[TS102_RANGE_CNT];

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

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

struct	tslot_softc {
	struct device	sc_dev;

	struct intrhand	sc_ih;

	pcmcia_chipset_tag_t sc_pct;

	struct proc	*sc_thread;			/* event thread */
	unsigned int	sc_events;	/* sockets with pending events */

	struct tslot_data sc_slot[TS102_NUM_SLOTS];
};

void	tslot_attach(struct device *, struct device *, void *);
void	tslot_create_event_thread(void *);
void	tslot_event_thread(void *);
int	tslot_intr(void *);
void	tslot_intr_disestablish(pcmcia_chipset_handle_t, void *);
void	tslot_intr_dispatch(void *);
void	*tslot_intr_establish(pcmcia_chipset_handle_t, struct pcmcia_function *,
	    int, int (*)(void *), void *, char *);
const char *tslot_intr_string(pcmcia_chipset_handle_t, void *);
int	tslot_io_alloc(pcmcia_chipset_handle_t, bus_addr_t, bus_size_t,
	    bus_size_t, struct pcmcia_io_handle *);
void	tslot_io_free(pcmcia_chipset_handle_t, struct pcmcia_io_handle *);
int	tslot_io_map(pcmcia_chipset_handle_t, int, bus_addr_t, bus_size_t,
	    struct pcmcia_io_handle *, int *);
void	tslot_io_unmap(pcmcia_chipset_handle_t, int);
int	tslot_match(struct device *, void *, void *);
int	tslot_mem_alloc(pcmcia_chipset_handle_t, bus_size_t,
	    struct pcmcia_mem_handle *);
void	tslot_mem_free(pcmcia_chipset_handle_t, struct pcmcia_mem_handle *);
int	tslot_mem_map(pcmcia_chipset_handle_t, int, bus_addr_t, bus_size_t,
	    struct pcmcia_mem_handle *, bus_size_t *, int *);
void	tslot_mem_unmap(pcmcia_chipset_handle_t, int);
int	tslot_print(void *, const char *);
void	tslot_queue_event(struct tslot_softc *, int);
void	tslot_reset(struct tslot_data *, u_int32_t);
void	tslot_slot_disable(pcmcia_chipset_handle_t);
void	tslot_slot_enable(pcmcia_chipset_handle_t);
void	tslot_slot_intr(struct tslot_data *, int);

struct	cfattach tslot_ca = {
	sizeof(struct tslot_softc), tslot_match, tslot_attach
};

struct	cfdriver tslot_cd = {
	NULL, "tslot", DV_DULL
};

/*
 * 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_intr_string,

	tslot_slot_enable,
	tslot_slot_disable
};

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

/*
 * Attachment and initialization
 */

int
tslot_match(struct device *parent, void *vcf, void *aux)
{
	struct confargs *ca = aux;

	return (strcmp("ts102", ca->ca_ra.ra_name) == 0);
}

void
tslot_attach(struct device *parent, struct device *self, void *args)
{
	struct confargs *ca = args;
	struct tslot_softc *sc = (struct tslot_softc *)self;
	struct romaux *ra;
	struct rom_range ranges[TS102_NUM_RANGES], *range;
	struct tslot_data *td;
	volatile u_int8_t *regs;
	int node, nranges, slot, rnum;

	ra = &ca->ca_ra;
	node = ra->ra_node;
	regs = mapiodev(&ra->ra_reg[0], 0, ra->ra_len);

	/*
	 * Find memory ranges
	 */
	nranges = getproplen(node, "ranges") / sizeof(struct rom_range);
	if (nranges < TS102_NUM_RANGES) {
		printf(": expected %d memory ranges, got %d\n",
		    TS102_NUM_RANGES, nranges);
		return;
	}
	getprop(node, "ranges", ranges, sizeof ranges);

	/*
	 * Ranges being relative to this sbus slot, turn them into absolute
	 * addresses.
	 */
	for (rnum = 0; rnum < TS102_NUM_RANGES; rnum++) {
		ranges[rnum].poffset -= TS102_OFFSET_REGISTERS;
	}

	sc->sc_ih.ih_fun = tslot_intr;
	sc->sc_ih.ih_arg = sc;
	intr_establish(ra->ra_intr[0].int_pri, &sc->sc_ih, -1, self->dv_xname);
	printf(" pri %d", ra->ra_intr[0].int_pri);

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

	/*
	 * Setup asynchronous event handler
	 */
	sc->sc_events = 0;
	kthread_create_deferred(tslot_create_event_thread, sc);

	sc->sc_pct = (pcmcia_chipset_tag_t)&tslot_functions;

	/*
	 * Setup slots
	 */
	for (slot = 0; slot < TS102_NUM_SLOTS; slot++) {
		td = &sc->sc_slot[slot];
		for (rnum = 0; rnum < TS102_RANGE_CNT; rnum++) {
			range = ranges + (slot * TS102_RANGE_CNT + rnum);
			td->td_rr = ra->ra_reg[0];
			td->td_rr.rr_iospace = range->pspace;
			td->td_rr.rr_paddr = (void *)
			   ((u_int32_t)td->td_rr.rr_paddr + range->poffset);
			td->td_space[rnum] = (vaddr_t)mapiodev(&td->td_rr, 0,
			    TS102_ARBITRARY_MAP_SIZE);
		}
		td->td_parent = sc;
		td->td_regs = regs +
		    slot * (TS102_REG_CARD_B_INT - TS102_REG_CARD_A_INT);
		td->td_slot = slot;
		SET_TAG_LITTLE_ENDIAN(&td->td_rr);
		tslot_reset(td, TS102_ARBITRARY_MAP_SIZE);
	}
}

void
tslot_reset(struct tslot_data *td, u_int32_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;
	paa.iobase = 0;
	paa.iosize = iosize;

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

	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;
	/* 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);

	/*
	 * 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) {
		tadpole_set_pcmcia(td->td_slot, 1);
		td->td_status = TS_CARD;
		pcmcia_card_attach(td->td_pcmcia);
	} else {
		tadpole_set_pcmcia(td->td_slot, 0);
		td->td_status = 0;
	}
}

/* XXX there ought to be a common function for this... */
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
 */

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-%x]", start, size);
#endif

	pih->iot = &td->td_rr;
	pih->ioh = (bus_space_handle_t)(td->td_space[TS102_RANGE_IO]);
	pih->addr = start;
	pih->size = size;
	pih->flags = 0;

	return (0);
}

void
tslot_io_free(pcmcia_chipset_handle_t pch, struct pcmcia_io_handle *pih)
{
#ifdef TSLOT_DEBUG
	printf("[io free %x-%x]", pih->start, pih->size);
#endif
}

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", offset, size);
#endif

	pih->iot = &td->td_rr;
	bus_space_subregion(&td->td_rr, td->td_space[TS102_RANGE_IO],
	    offset, size, &pih->ioh);
	*windowp = TS102_RANGE_IO;

#ifdef TSLOT_DEBUG
	printf("->%p/%x]", pih->ioh, size);
#endif

	return (0);
}

void
tslot_io_unmap(pcmcia_chipset_handle_t pch, int win)
{
#ifdef TSLOT_DEBUG
	printf("[io unmap]");
#endif
}

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]", size);
#endif
	pmh->memt = &td->td_rr;
	pmh->size = round_page(size);
	pmh->addr = 0;
	pmh->mhandle = 0;
	pmh->realsize = 0;	/* nothing so far! */

	return (0);
}

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

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, addr, size);
#endif

	addr += pmh->addr;

	pmh->memt = &td->td_rr;
	bus_space_subregion(&td->td_rr, td->td_space[slot],
	    addr, size, &pmh->memh);
	pmh->realsize = TS102_ARBITRARY_MAP_SIZE - addr;
	*offsetp = 0;
	*windowp = slot;

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

	return (0);
}

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

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

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

	/*
	 * Disable card access.
	 */
	TSLOT_WRITE(td, TS102_REG_CARD_A_STS,
	    TSLOT_READ(td, TS102_REG_CARD_A_STS) & ~TS102_CARD_STS_ACEN);

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

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",
	    td->td_parent->sc_dev.dv_xname, td->td_slot);
#endif

	/* Power down the socket to reset it */
	status = TSLOT_READ(td, TS102_REG_CARD_A_STS);
	TSLOT_WRITE(td, TS102_REG_CARD_A_STS, status | TS102_CARD_STS_VCCEN);

	/*
	 * wait 300ms until power fails (Tpf).  Then, wait 100ms since we
	 * are changing Vcc (Toff).
	 */
	DELAY((300 + 100) * 1000);

	/*
	 * Power on the card if not already done, and enable card access
	 */
	status |= TS102_CARD_STS_ACEN;
	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)).
	 */
	DELAY((100 + 20) * 1000);

	status &= ~TS102_CARD_STS_VPP1_MASK;
	status |= TS102_CARD_STS_VPP1_VCC;
	TSLOT_WRITE(td, TS102_REG_CARD_A_STS, status);

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

	/* wait 20ms as per pc card standard (r2.01) section 4.3.6 */
	DELAY(20 * 1000);

	/* 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",
		    td->td_parent->sc_dev.dv_xname, td->td_slot,
		    TSLOT_READ(td, TS102_REG_CARD_A_STS));
		return;
	}

	/*
	 * 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!
	 */
	if (pcmcia_card_gettype(td->td_pcmcia) == 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);
	}
}

/*
 * Event management
 */
void
tslot_create_event_thread(void *v)
{
	struct tslot_softc *sc = v;
	const char *name = sc->sc_dev.dv_xname;

	if (kthread_create(tslot_event_thread, sc, &sc->sc_thread,
	    name) != 0) {
		panic("%s: unable to create event kthread", name);
	}
}

void
tslot_event_thread(void *v)
{
	struct tslot_softc *sc = v;
	struct tslot_data *td;
	int s, status;
	unsigned int socket;

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

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

		if (socket >= TS102_NUM_SLOTS) {
#ifdef DEBUG
			printf("%s: invalid slot number %d\n",
			    sc->sc_dev.dv_xname, te->te_slot);
#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;
				tadpole_set_pcmcia(td->td_slot, 1);
				pcmcia_card_attach(td->td_pcmcia);
			}
		} else {
			/* Card removal */
			if ((td->td_status & TS_CARD) != 0) {
				td->td_status &= ~TS_CARD;
				tadpole_set_pcmcia(td->td_slot, 0);
				pcmcia_card_detach(td->td_pcmcia,
				    DETACH_FORCE);
			}
		}
	}
}

/*
 * Interrupt handling
 */

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

	/* protect hardware access against soft interrupts */
	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);
		}
	}

	/*
	 * 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);
}

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);
}

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

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

	sockstat = td->td_status;

	/*
	 * The TS102 queues interrupt requests, 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",
		    sc->sc_dev.dv_xname, 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))
			return;

		if ((sockstat & TS_CARD) == 0) {
			printf("%s: spurious interrupt on slot %d isr %x\n",
			    sc->sc_dev.dv_xname, td->td_slot, intreg);
			return;
		}

		if (td->td_softintr != NULL) {
			/*
			 * Disable this sbus interrupt, until the
			 * softintr handler had a chance to run.
			 */
			TSLOT_WRITE(td, TS102_REG_CARD_A_INT,
			    TSLOT_READ(td, TS102_REG_CARD_A_INT) &
			    ~TS102_CARD_INT_MASK_IRQ);

			softintr_schedule(td->td_softintr);
		}
	}
}

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

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

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

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

	if (td->td_softintr != NULL) {
		softintr_disestablish(td->td_softintr);
		td->td_softintr = NULL;
	}
	td->td_intr = NULL;
	td->td_intrarg = 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 */
}

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

	/*
	 * Note that this code relies on softintr_establish() to be
	 * used with real, hardware ipl values. All platforms with
	 * SBus support support this.
	 */
	td->td_intr = handler;
	td->td_intrarg = arg;
	td->td_softintr = softintr_establish(ipl, tslot_intr_dispatch, td);

	return td->td_softintr != NULL ? td : NULL;
}