shithub: riscv

ref: ae1cfc9082b3f56a8502938b892bf0555110a741
dir: /sys/src/9/pc/devpccard.c/

View raw version
/*
     cardbus and pcmcia (grmph) support.
*/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "io.h"
#include "../port/pci.h"

#define DEBUG	0

#pragma	varargck	type	"T"	int

#define MAP(x,o)	(Rmap + (x)*0x8 + o)

enum {
	TI_vid = 0x104c,
	TI_1131_did = 0xAC15,
	TI_1250_did = 0xAC16,
	TI_1450_did = 0xAC1B,
	TI_1251A_did = 0xAC1D,
	TI_1420_did = 0xAC51,

	Ricoh_vid = 0x1180,
	Ricoh_475_did = 0x0475,
	Ricoh_476_did = 0x0476,
	Ricoh_478_did = 0x0478,

	O2_vid = 0x1217,
	O2_OZ711M3_did = 0x7134,

	Nslots = 4,		/* Maximum number of CardBus slots to use */

	K = 1024,
	M = K * K,

	LegacyAddr = 0x3e0,
	NUMEVENTS = 10,

	TI1131xSC = 0x80,		/* system control */
		TI122X_SC_INTRTIE = 1 << 29,
	TI12xxIM = 0x8c,		/*  */
	TI1131xCC = 0x91,		/* card control */
		TI113X_CC_RIENB = 1 << 7,
		TI113X_CC_ZVENABLE = 1 << 6,
		TI113X_CC_PCI_IRQ_ENA = 1 << 5,
		TI113X_CC_PCI_IREQ = 1 << 4,
		TI113X_CC_PCI_CSC = 1 << 3,
		TI113X_CC_SPKROUTEN = 1 << 1,
		TI113X_CC_IFG = 1 << 0,
	TI1131xDC = 0x92,		/* device control */
};

typedef struct Variant Variant;
struct Variant {
	ushort	vid;
	ushort	did;
	char	*name;
};

static Variant variant[] = {
{	Ricoh_vid,	Ricoh_475_did,	"Ricoh 475 PCI/Cardbus bridge",	},
{	Ricoh_vid,	Ricoh_476_did,	"Ricoh 476 PCI/Cardbus bridge",	},
{	Ricoh_vid,	Ricoh_478_did,	"Ricoh 478 PCI/Cardbus bridge",	},
{	TI_vid,		TI_1131_did,	"TI PCI-1131 Cardbus Controller", },
{	TI_vid,		TI_1250_did,	"TI PCI-1250 Cardbus Controller", },
{	TI_vid,		TI_1450_did,	"TI PCI-1450 Cardbus Controller", },
{	TI_vid,		TI_1251A_did,	"TI PCI-1251A Cardbus Controller", },
{	TI_vid,		TI_1420_did,	"TI PCI-1420 Cardbus Controller", },
{	O2_vid,		O2_OZ711M3_did,	"O2Micro OZ711M3 MemoryCardBus", },
};

/* Cardbus registers */
enum {
	SocketEvent = 0,
		SE_CCD = 3 << 1,
		SE_POWER = 1 << 3,
	SocketMask = 1,
	SocketState = 2,
		SS_CCD = 3 << 1,
		SS_POWER = 1 << 3,
		SS_PC16 = 1 << 4,
		SS_CBC = 1 << 5,
		SS_NOTCARD = 1 << 7,
		SS_BADVCC = 1 << 9,
		SS_5V = 1 << 10,
		SS_3V = 1 << 11,
	SocketForce = 3,
	SocketControl = 4,
		SC_5V = 0x22,
		SC_3V = 0x33,
};

enum {
	PciPCR_IO = 1 << 0,
	PciPCR_MEM = 1 << 1,
	PciPCR_Master = 1 << 2,

	PciPMC = 0xa4,

	Ncmd = 10,
	CBIRQ = 9,

	PC16,
	PC32,
};

enum {
	Ti82365,
	Tpd6710,
	Tpd6720,
	Tvg46x,
};

/*
 *  Intel 82365SL PCIC controller for the PCMCIA or
 *  Cirrus Logic PD6710/PD6720 which is mostly register compatible
 */
enum
{
	/*
	 *  registers indices
	 */
	Rid=		0x0,		/* identification and revision */
	Ris=		0x1,		/* interface status */
	Rpc=	 	0x2,		/* power control */
	 Foutena=	 (1<<7),	/*  output enable */
	 Fautopower=	 (1<<5),	/*  automatic power switching */
	 Fcardena=	 (1<<4),	/*  PC card enable */
	Rigc= 		0x3,		/* interrupt and general control */
	 Fiocard=	 (1<<5),	/*  I/O card (vs memory) */
	 Fnotreset=	 (1<<6),	/*  reset if not set */
	 FSMIena=	 (1<<4),	/*  enable change interrupt on SMI */
	Rcsc= 		0x4,		/* card status change */
	Rcscic= 	0x5,		/* card status change interrupt config */
	 Fchangeena=	 (1<<3),	/*  card changed */
	 Fbwarnena=	 (1<<1),	/*  card battery warning */
	 Fbdeadena=	 (1<<0),	/*  card battery dead */
	Rwe= 		0x6,		/* address window enable */
	 Fmem16=	 (1<<5),	/*  use A23-A12 to decode address */
	Rio= 		0x7,		/* I/O control */
	 Fwidth16=	 (1<<0),	/*  16 bit data width */
	 Fiocs16=	 (1<<1),	/*  IOCS16 determines data width */
	 Fzerows=	 (1<<2),	/*  zero wait state */
	 Ftiming=	 (1<<3),	/*  timing register to use */
	Riobtm0lo=	0x8,		/* I/O address 0 start low byte */
	Riobtm0hi=	0x9,		/* I/O address 0 start high byte */
	Riotop0lo=	0xa,		/* I/O address 0 stop low byte */
	Riotop0hi=	0xb,		/* I/O address 0 stop high byte */
	Riobtm1lo=	0xc,		/* I/O address 1 start low byte */
	Riobtm1hi=	0xd,		/* I/O address 1 start high byte */
	Riotop1lo=	0xe,		/* I/O address 1 stop low byte */
	Riotop1hi=	0xf,		/* I/O address 1 stop high byte */
	Rmap=		0x10,		/* map 0 */

	/*
	 *  CL-PD67xx extension registers
	 */
	Rmisc1=		0x16,		/* misc control 1 */
	 F5Vdetect=	 (1<<0),
	 Fvcc3V=	 (1<<1),
	 Fpmint=	 (1<<2),
	 Fpsirq=	 (1<<3),
	 Fspeaker=	 (1<<4),
	 Finpack=	 (1<<7),
	Rfifo=		0x17,		/* fifo control */
	 Fflush=	 (1<<7),	/*  flush fifo */
	Rmisc2=		0x1E,		/* misc control 2 */
	 Flowpow=	 (1<<1),	/*  low power mode */
	Rchipinfo=	0x1F,		/* chip information */
	Ratactl=	0x26,		/* ATA control */

	/*
	 *  offsets into the system memory address maps
	 */
	Mbtmlo=		0x0,		/* System mem addr mapping start low byte */
	Mbtmhi=		0x1,		/* System mem addr mapping start high byte */
	 F16bit=	 (1<<7),	/*  16-bit wide data path */
	Mtoplo=		0x2,		/* System mem addr mapping stop low byte */
	Mtophi=		0x3,		/* System mem addr mapping stop high byte */
	 Ftimer1=	 (1<<6),	/*  timer set 1 */
	Mofflo=		0x4,		/* Card memory offset address low byte */
	Moffhi=		0x5,		/* Card memory offset address high byte */
	 Fregactive=	 (1<<6),	/*  attribute memory */

	/*
	 *  configuration registers - they start at an offset in attribute
	 *  memory found in the CIS.
	 */
	Rconfig=	0,
	 Creset=	 (1<<7),	/*  reset device */
	 Clevel=	 (1<<6),	/*  level sensitive interrupt line */
};

/*
 *  read and crack the card information structure enough to set
 *  important parameters like power
 */
/* cis memory walking */
typedef struct Cisdat Cisdat;
struct Cisdat {
	uchar		*cisbase;
	int		cispos;
	int		cisskip;
	int		cislen;
};

typedef struct Pcminfo Pcminfo;
struct Pcminfo {
	char		verstr[512];		/* Version string */
	PCMmap		mmap[4];		/* maps, last is always for the kernel */
	ulong		conf_addr;		/* Config address */
	uchar		conf_present;		/* Config register present */
	int		nctab;			/* In use configuration tables */
	PCMconftab	ctab[8];		/* Configuration tables */
	PCMconftab	*defctab;		/* Default conftab */

	int		port;			/* Actual port usage */
	int		irq;			/* Actual IRQ usage */
};

typedef struct Cardbus Cardbus;
struct Cardbus {
	Lock;
	Variant		*variant;		/* Which CardBus chipset */
	Pcidev		*pci;			/* The bridge itself */
	ulong		*regs;			/* Cardbus registers */
	int		ltype;			/* Legacy type */
	int		lindex;			/* Legacy port index address */
	int		ldata;			/* Legacy port data address */
	int		lbase;			/* Base register for this socket */

	int		state;			/* Current state of card */
	int		type;			/* Type of card */
	Pcminfo		linfo;			/* PCMCIA slot info */

	int		special;		/* card is allocated to a driver */

	int		refs;			/* Number of refs to slot */
	Lock		refslock;		/* inc/dev ref lock */
};

static int managerstarted;

enum {
	Mshift=	12,
	Mgran=	(1<<Mshift),	/* granularity of maps */
	Mmask=	~(Mgran-1),	/* mask for address bits important to the chip */
};

static Cardbus cbslots[Nslots];
static int nslots;

static ulong exponent[8] = {
	1, 10, 100, 1000, 10000, 100000, 1000000, 10000000,
};

static ulong vmant[16] = {
	10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80, 90,
};

static ulong mantissa[16] = {
	0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80,
};

static char Enocard[] = "No card in slot";

enum
{
	CMdown,
	CMpower,
};

static Cmdtab pccardctlmsg[] =
{
	CMdown,		"down",	2,
	CMpower,	"power",	1,
};

static int powerup(Cardbus *);
static void configure(Cardbus *);
static void powerdown(Cardbus *cb);
static void unconfigure(Cardbus *cb);

static void i82365probe(Cardbus *cb, int lindex, int ldata);
static void i82365configure(Cardbus *cb);
static PCMmap *isamap(Cardbus *cb, ulong offset, int len, int attr);
static void isaunmap(PCMmap* m);
static uchar rdreg(Cardbus *cb, int index);
static void wrreg(Cardbus *cb, int index, uchar val);
static int readc(Cisdat *cis, uchar *x);
static void tvers1(Cardbus *cb, Cisdat *cis, int );
static void tcfig(Cardbus *cb, Cisdat *cis, int );
static void tentry(Cardbus *cb, Cisdat *cis, int );
static int vcode(int volt);
static int pccard_pcmspecial(char *idstr, ISAConf *isa);
static void pccard_pcmspecialclose(int slotno);

enum {
	CardDetected,
	CardPowered,
	CardEjected,
	CardConfigured,
};

static char *messages[] = {
[CardDetected]		"CardDetected",
[CardPowered]		"CardPowered",
[CardEjected]		"CardEjected",
[CardConfigured]	"CardConfigured",
};

enum {
	SlotEmpty,
	SlotFull,
	SlotPowered,
	SlotConfigured,
};

static char *states[] = {
[SlotEmpty]		"SlotEmpty",
[SlotFull]		"SlotFull",
[SlotPowered]		"SlotPowered",
[SlotConfigured]	"SlotConfigured",
};

static void
engine(Cardbus *cb, int message)
{
	if(DEBUG)
		print("engine(%ld): %s(%s)\n", cb - cbslots,
			states[cb->state], messages[message]);
	switch (cb->state) {
	case SlotEmpty:

		switch (message) {
		case CardDetected:
			cb->state = SlotFull;
			powerup(cb);
			break;
		case CardEjected:
			break;
		default:
			if(DEBUG)
				print("#Y%zd: Invalid message %s in SlotEmpty state\n",
					cb - cbslots, messages[message]);
			break;
		}
		break;

	case SlotFull:

		switch (message) {
		case CardPowered:
			cb->state = SlotPowered;
			configure(cb);
			break;
		case CardEjected:
			cb->state = SlotEmpty;
			powerdown(cb);
			break;
		default:
			if(DEBUG)
				print("#Y%zd: Invalid message %s in SlotFull state\n",
					cb - cbslots, messages[message]);
			break;
		}
		break;

	case SlotPowered:

		switch (message) {
		case CardConfigured:
			cb->state = SlotConfigured;
			break;
		case CardEjected:
			cb->state = SlotEmpty;
			unconfigure(cb);
			powerdown(cb);
			break;
		default:
			print("#Y%zd: Invalid message %s in SlotPowered state\n",
				cb - cbslots, messages[message]);
			break;
		}
		break;

	case SlotConfigured:

		switch (message) {
		case CardEjected:
			cb->state = SlotEmpty;
			unconfigure(cb);
			powerdown(cb);
			break;
		default:
			if(DEBUG)
				print("#Y%zd: Invalid message %s in SlotConfigured state\n",
					cb - cbslots, messages[message]);
			break;
		}
		break;
	}
}

static void
qengine(Cardbus *cb, int message)
{
	lock(cb);
	engine(cb, message);
	unlock(cb);
}

typedef struct Events Events;
struct Events {
	Cardbus	*cb;
	int	message;
};

static Lock levents;
static Events events[NUMEVENTS];
static Rendez revents;
static int nevents;

static void
iengine(Cardbus *cb, int message)
{
	if (nevents >= NUMEVENTS) {
		print("#Y: Too many events queued, discarding request\n");
		return;
	}
	ilock(&levents);
	events[nevents].cb = cb;
	events[nevents].message = message;
	nevents++;
	iunlock(&levents);
	wakeup(&revents);
}

static int
eventoccured(void)
{
	return nevents > 0;
}

static void
processevents(void *)
{
	int message;
	Cardbus *cb;

	while(waserror())
		;

	for(;;){
		sleep(&revents, (int (*)(void *))eventoccured, nil);

		cb = nil;
		message = 0;
		ilock(&levents);
		if (nevents > 0) {
			cb = events[0].cb;
			message = events[0].message;
			nevents--;
			if (nevents > 0)
				memmove(events, &events[1], nevents * sizeof(Events));
		}
		iunlock(&levents);

		if (cb)
			qengine(cb, message);
	}
}

static void
cbinterrupt(Ureg *, void *)
{
	int i;

	for (i = 0; i != nslots; i++) {
		Cardbus *cb = &cbslots[i];
		ulong event, state;

		event = cb->regs[SocketEvent];
		if(!(event & (SE_POWER|SE_CCD)))
			continue;
		state = cb->regs[SocketState];
		rdreg(cb, Rcsc);	/* Ack the interrupt */

		if(DEBUG)
			print("#Y%zd: interrupt: event %.8lX, state %.8lX, (%s)\n",
				cb - cbslots, event, state, states[cb->state]);

		if (event & SE_CCD) {
			cb->regs[SocketEvent] |= SE_CCD;	/* Ack interrupt */
			if (state & SE_CCD) {
				if (cb->state != SlotEmpty) {
					print("#Y: take cardejected interrupt\n");
					iengine(cb, CardEjected);
				}
			}
			else
				iengine(cb, CardDetected);
		}

		if (event & SE_POWER) {
			cb->regs[SocketEvent] |= SE_POWER;	/* Ack interrupt */
			iengine(cb, CardPowered);
		}
	}
}

void
devpccardlink(void)
{
	static int initialized;
	Pcidev *pci;
	int i;
	char *p;

	if (initialized)
		return;
	initialized = 1;

	if((p=getconf("pccard0")) && strncmp(p, "disabled", 8)==0)
		return;

	if(_pcmspecial)
		return;

	/* Allocate legacy space */
	if (ioalloc(LegacyAddr, 2, 0, "i82365.0") < 0)
		print("#Y: WARNING: Cannot allocate legacy ports\n");

	/* Find all CardBus controllers */
	pci = nil;
	while ((pci = pcimatch(pci, 0, 0)) != nil) {
		Cardbus *cb;
		uchar pin;

		if(pci->ccrb != 6 || pci->ccru != 7)
			continue;
		for (i = 0; i != nelem(variant); i++)
			if (pci->vid == variant[i].vid && pci->did == variant[i].did)
				break;
		if (i == nelem(variant))
			continue;

		/* initialize this slot */
		cb = &cbslots[nslots];

		cb->pci = pci;
		cb->variant = &variant[i];

		if(pci->mem[0].size == 0 || (pci->mem[0].bar & 0xF) != 0)
			continue;
		cb->regs = (ulong *)vmap(pci->mem[0].bar & ~0xFULL, pci->mem[0].size);
		if(cb->regs == nil)
			continue;

		pcicfgw32(pci, PciCBMBR0, 0xffffffff);
		pcicfgw32(pci, PciCBMLR0, 0);
		pcicfgw32(pci, PciCBMBR1, 0xffffffff);
		pcicfgw32(pci, PciCBMLR1, 0);
		pcicfgw32(pci, PciCBIBR0, 0xffffffff);
		pcicfgw32(pci, PciCBILR0, 0);
		pcicfgw32(pci, PciCBIBR1, 0xffffffff);
		pcicfgw32(pci, PciCBILR1, 0);

		/* Set up PCI bus numbers if needed. */
		if (pcicfgr8(pci, PciSBN) == 0) {
			static int busbase = 0x20;

			pcicfgw8(pci, PciSBN, busbase);
			pcicfgw8(pci, PciUBN, busbase + 2);
			busbase += 3;
		}

		/* Patch up intl if needed. */
		if ((pin = pcicfgr8(pci, PciINTP)) != 0 &&
		    (pci->intl == 0xff || pci->intl == 0)) {
			pci->intl = pciipin(nil, pin);
			pcicfgw8(pci, PciINTL, pci->intl);

			if (pci->intl == 0xff || pci->intl == 0)
				print("#Y%zd: No interrupt?\n", cb - cbslots);
		}

		/* Don't you love standards! */
		if (pci->vid == TI_vid) {
			if (pci->did <= TI_1131_did) {
				uchar cc;

				cc = pcicfgr8(pci, TI1131xCC);
				cc &= ~(TI113X_CC_PCI_IRQ_ENA |
					TI113X_CC_PCI_IREQ |
					TI113X_CC_PCI_CSC |
					TI113X_CC_ZVENABLE);
				cc |= TI113X_CC_PCI_IRQ_ENA |
					TI113X_CC_PCI_IREQ |
					TI113X_CC_SPKROUTEN;
				pcicfgw8(pci, TI1131xCC, cc);

				/* PCI interrupts only */
				pcicfgw8(pci, TI1131xDC,
					pcicfgr8(pci, TI1131xDC) & ~6);

				/* CSC ints to PCI bus. */
				wrreg(cb, Rigc, rdreg(cb, Rigc) | 0x10);
			}
			else if (pci->did == TI_1250_did) {
				print("No support yet for the TI_1250_did, prod pb\n");
			}
			else if (pci->did == TI_1420_did) {
				/* Disable Vcc protection */
				pcicfgw32(cb->pci, 0x80,
					pcicfgr32(cb->pci, 0x80) | (1 << 21));
			}

			pcicfgw16(cb->pci, PciPMC, pcicfgr16(cb->pci, PciPMC) & ~3);
		}
		if (pci->vid == O2_vid) {
			if(DEBUG)
				print("writing O2 config\n");
			pcicfgw8(cb->pci, 0x94, 0xCA);
			pcicfgw8(cb->pci, 0xD4, 0xCA);
		}
		cb->state = SlotEmpty;

		intrenable(pci->intl, cbinterrupt, cb, pci->tbdf, "cardbus");

		/* Don't really know what to do with this... */
		i82365probe(cb, LegacyAddr, LegacyAddr + 1);

		print("#Y%zd: %s, %.8lluX intl %d\n", cb - cbslots,
			 variant[i].name, pci->mem[0].bar, pci->intl);

		nslots++;
	}

	if (nslots == 0){
		iofree(LegacyAddr);
		return;
	}

	_pcmspecial = pccard_pcmspecial;
	_pcmspecialclose = pccard_pcmspecialclose;

	for (i = 0; i != nslots; i++) {
		Cardbus *cb = &cbslots[i];

		if ((cb->regs[SocketState] & SE_CCD) == 0)
			engine(cb, CardDetected);
	}

	delay(500);			/* Allow time for power up */

	for (i = 0; i != nslots; i++) {
		Cardbus *cb = &cbslots[i];

		if (cb->regs[SocketState] & SE_POWER)
			engine(cb, CardPowered);

		/* Ack and enable interrupts on all events */
		// cb->regs[SocketEvent] = cb->regs[SocketEvent];
		cb->regs[SocketMask] |= 0xF;
		wrreg(cb, Rcscic, 0xC);
	}
}

static int
powerup(Cardbus *cb)
{
	ulong state;
	ushort bcr;

	state = cb->regs[SocketState];
	if (state & SS_PC16) {
		if(DEBUG)
			print("#Y%zd: Probed a PC16 card, powering up card\n",
				cb - cbslots);
		cb->type = PC16;
		memset(&cb->linfo, 0, sizeof(Pcminfo));

		/* power up and unreset, wait's are empirical (???) */
		wrreg(cb, Rpc, Fautopower|Foutena|Fcardena);
		delay(300);
		wrreg(cb, Rigc, 0);
		delay(100);
		wrreg(cb, Rigc, Fnotreset);
		delay(500);

//		return 1;
	}

	if (state & SS_CCD)
		return 0;

	if (state & SS_NOTCARD) {
		print("#Y%zd: No card inserted\n", cb - cbslots);
		return 0;
	}

	if ((state & SS_3V) == 0 && (state & SS_5V) == 0) {
		print("#Y%zd: Unsupported voltage, powering down card!\n",
			cb - cbslots);
		cb->regs[SocketControl] = 0;
		return 0;
	}

	if(DEBUG)
		print("#Y%zd: card %spowered at %d volt\n", cb - cbslots,
			(state & SS_POWER)? "": "not ",
			(state & SS_3V)? 3: (state & SS_5V)? 5: -1);

	/* Power up the card
	 * and make sure the secondary bus is not in reset.
	 */
	cb->regs[SocketControl] = (state & SS_5V)? SC_5V: SC_3V;
	delay(50);
	bcr = pcicfgr16(cb->pci, PciBCR);
	bcr &= ~0x40;
	pcicfgw16(cb->pci, PciBCR, bcr);
	delay(100);

	if (state & SS_PC16)
		cb->type = PC16;
	else
		cb->type = PC32;

	return 1;
}

static void
powerdown(Cardbus *cb)
{
	ushort bcr;

	if (cb->type == PC16) {

		wrreg(cb, Rpc, 0);	/* turn off card power */
		wrreg(cb, Rwe, 0);	/* no windows */

		cb->type = -1;
		return;
	}

	bcr = pcicfgr16(cb->pci, PciBCR);
	bcr |= 0x40;
	pcicfgw16(cb->pci, PciBCR, bcr);
	cb->regs[SocketControl] = 0;
	cb->type = -1;
}

static void
configure(Cardbus *cb)
{
	Pcidev *pci;
	ulong iobase, iolen;
	uvlong membase, memlen;

	if(DEBUG)
		print("configuring slot %ld (%s)\n", cb - cbslots, states[cb->state]);
	if (cb->state == SlotConfigured)
		return;
	engine(cb, CardConfigured);

	delay(50);					/* Emperically established */

	if (cb->type == PC16) {
		i82365configure(cb);
		return;
	}

	/* Scan the CardBus for new PCI devices */
	pciscan(pcicfgr8(cb->pci, PciSBN), &cb->pci->bridge, cb->pci);

	/* size the devices on the bus, reserve a minimum for devices arriving later */
	pcibussize(cb->pci->bridge, &memlen, &iolen);

	if(iolen < 512)
		iolen = 512;
	if(memlen < 1*1024*1024)
		memlen = 1*1024*1024;

	print("#Y%zd: iolen=%lud, memlen=%llud\n", cb - cbslots, iolen, memlen);

	if(cb->pci->parent == nil){
		iobase = ioreserve(-1,
			iolen, iolen, "cardbus");
		if(iobase == -1){
NoIO:
			print("#Y%zd: can't allocate io space\n", cb - cbslots);
			return;
		}
		membase = upaalloc(-1ULL,
			memlen, 4*1024*1024);
	} else {
		iobase = ioreservewin(cb->pci->parent->ioa.bar, cb->pci->parent->ioa.size,
			iolen, iolen, "cardbus");
		if(iobase == -1)
			goto NoIO;
		membase = upaallocwin(cb->pci->parent->mema.bar, cb->pci->parent->mema.size,
			memlen, 4*1024*1024);
	}
	if(membase == -1){
		print("#Y%zd: can't allocate memory space\n", cb - cbslots);
		return;
	}

	print("#Y%zd: iobase=%lux, membase=%llux\n", cb - cbslots, iobase, membase);

	pcicfgw32(cb->pci, PciCBIBR0, iobase);
	pcicfgw32(cb->pci, PciCBILR0, iobase + iolen-1);
	pcicfgw32(cb->pci, PciCBIBR1, 0xffffffff);
	pcicfgw32(cb->pci, PciCBILR1, 0);
	cb->pci->ioa.bar = iobase;
	cb->pci->ioa.size = iolen;

	pcicfgw32(cb->pci, PciCBMBR0, (ulong)membase);
	pcicfgw32(cb->pci, PciCBMLR0, (ulong)membase + memlen-1);
	pcicfgw32(cb->pci, PciCBMBR1, 0xffffffff);
	pcicfgw32(cb->pci, PciCBMLR1, 0);
	cb->pci->mema.bar = membase;
	cb->pci->mema.size = memlen;

	/* Route interrupts to INTA#/B#, Disable prefetch for MEM0/1 */
	pcicfgw16(cb->pci, PciBCR, pcicfgr16(cb->pci, PciBCR) & ~(7 << 7));

	/* Assign resources */
	pcibusmap(cb->pci->bridge, &membase, &iobase, 1);

	/* Assign legacy IRQ's */
	for(pci = cb->pci->bridge; pci != nil; pci = pci->link) {
		if(pcicfgr8(pci, PciINTP) != 0) {
			pci->intl = cb->pci->intl;
			pcicfgw8(pci, PciINTL, pci->intl);
		}
	}
}

static void
unconfigure(Cardbus *cb)
{
	if (cb->type == PC16) {
		print("#Y%zd: Don't know how to unconfigure a PC16 card\n", cb - cbslots);
		memset(&cb->linfo, 0, sizeof(Pcminfo));
		return;
	}

	pcicfgw32(cb->pci, PciCBIBR0, 0xffffffff);
	pcicfgw32(cb->pci, PciCBILR0, 0);
	pcicfgw32(cb->pci, PciCBIBR1, 0xffffffff);
	pcicfgw32(cb->pci, PciCBILR1, 0);

	pcicfgw32(cb->pci, PciCBMBR0, 0xffffffff);
	pcicfgw32(cb->pci, PciCBMLR0, 0);
	pcicfgw32(cb->pci, PciCBMBR1, 0xffffffff);
	pcicfgw32(cb->pci, PciCBMLR1, 0);

	/* free i/o space */
	if(cb->pci->ioa.size){
		Pcidev *pci;
		int i;

		for(pci = cb->pci->bridge; pci != nil; pci = pci->link) {
			for(i = 0; i < nelem(pci->mem); i++) {
				if((pci->mem[i].size == 0)
				|| (pci->mem[i].bar & 1) == 0
				|| (pci->mem[i].bar & ~3) == 0)
					continue;
				iofree(pci->mem[i].bar & ~3);
			}
		}
		iofree(cb->pci->ioa.bar);
		cb->pci->ioa.bar = 0;
		cb->pci->ioa.size = 0;
	}

	/* free memory space */
	if(cb->pci->mema.size){
		upafree(cb->pci->mema.bar, cb->pci->mema.size);
		cb->pci->mema.bar = 0;
		cb->pci->mema.size = 0;
	}

	/* remove the devices */
	while(cb->pci->bridge != nil)
		pcidevfree(cb->pci->bridge);
}

static void
i82365configure(Cardbus *cb)
{
	int this;
	Cisdat cis;
	PCMmap *m;
	uchar type, link;

	/*
	 * Read all tuples in attribute space.
	 */
	m = isamap(cb, 0, 0, 1);
	if(m == 0)
		return;

	cis.cisbase = KADDR(m->isa);
	cis.cispos = 0;
	cis.cisskip = 2;
	cis.cislen = m->len;

	/* loop through all the tuples */
	for(;;){
		this = cis.cispos;
		if(readc(&cis, &type) != 1)
			break;
		if(type == 0xFF)
			break;
		if(readc(&cis, &link) != 1)
			break;

		switch(type){
		default:
			break;
		case 0x15:
			tvers1(cb, &cis, type);
			break;
		case 0x1A:
			tcfig(cb, &cis, type);
			break;
		case 0x1B:
			tentry(cb, &cis, type);
			break;
		}

		if(link == 0xFF)
			break;
		cis.cispos = this + (2+link);
	}
	isaunmap(m);
}

/*
 *  look for a card whose version contains 'idstr'
 */
static int
pccard_pcmspecial(char *idstr, ISAConf *isa)
{
	int i, irq;
	PCMconftab *ct, *et;
	Pcminfo *pi;
	Cardbus *cb;
	uchar x, we, *p;

	cb = nil;
	for (i = 0; i != nslots; i++) {
		cb = &cbslots[i];

		lock(cb);
		if (cb->state == SlotConfigured &&
		    cb->type == PC16 &&
		    !cb->special &&
		    strstr(cb->linfo.verstr, idstr))
			break;
		unlock(cb);
	}

	if (i == nslots) {
		if(0 && DEBUG)
			print("#Y: %s not found\n", idstr);
		return -1;
	}

	pi = &cb->linfo;

	/*
 	  *  configure the PCMslot for IO.  We assume very heavily that we can read
 	  *  configuration info from the CIS.  If not, we won't set up correctly.
 	  */
	irq = isa->irq;
	if(irq == 2)
		irq = 9;

	et = &pi->ctab[pi->nctab];
	ct = nil;
	for(i = 0; i < isa->nopt; i++){
		int index;
		char *cp;

		if(strncmp(isa->opt[i], "index=", 6))
			continue;
		index = strtol(&isa->opt[i][6], &cp, 0);
		if(cp == &isa->opt[i][6] || index >= pi->nctab) {
			unlock(cb);
			print("#Y%d: Cannot find index %d in conf table\n",
				 (int)(cb - cbslots), index);
			return -1;
		}
		ct = &pi->ctab[index];
	}

	if(ct == nil){
		PCMconftab *t;

		/* assume default is right */
		if(pi->defctab)
			ct = pi->defctab;
		else
			ct = pi->ctab;

		/* try for best match */
		if(ct->nio == 0
		|| ct->io[0].start != isa->port || ((1<<irq) & ct->irqs) == 0){
			for(t = pi->ctab; t < et; t++)
				if(t->nio
				&& t->io[0].start == isa->port
				&& ((1<<irq) & t->irqs)){
					ct = t;
					break;
				}
		}
		if(ct->nio == 0 || ((1<<irq) & ct->irqs) == 0){
			for(t = pi->ctab; t < et; t++)
				if(t->nio && ((1<<irq) & t->irqs)){
					ct = t;
					break;
				}
		}
		if(ct->nio == 0){
			for(t = pi->ctab; t < et; t++)
				if(t->nio){
					ct = t;
					break;
				}
		}
	}

	if(ct == et || ct->nio == 0) {
		unlock(cb);
		print("#Y%d: No configuration?\n", (int)(cb - cbslots));
		return -1;
	}
	if(isa->port == 0 && ct->io[0].start == 0) {
		unlock(cb);
		print("#Y%d: No part or start address\n", (int)(cb - cbslots));
		return -1;
	}

	cb->special = 1;	/* taken */

	/* route interrupts */
	isa->irq = irq;
	wrreg(cb, Rigc, irq | Fnotreset | Fiocard);

	/* set power and enable device */
	x = vcode(ct->vpp1);
	wrreg(cb, Rpc, x|Fautopower|Foutena|Fcardena);

	/* 16-bit data path */
	if(ct->bit16)
		x = Ftiming|Fiocs16|Fwidth16;
	else
		x = Ftiming;
	if(ct->nio == 2 && ct->io[1].start)
		x |= x<<4;
	wrreg(cb, Rio, x);

	/*
	 * enable io port map 0
	 * the 'top' register value includes the last valid address
	 */
	if(isa->port == 0)
		isa->port = ct->io[0].start;
	we = rdreg(cb, Rwe);
	wrreg(cb, Riobtm0lo, isa->port);
	wrreg(cb, Riobtm0hi, isa->port>>8);
	i = isa->port+ct->io[0].len-1;
	wrreg(cb, Riotop0lo, i);
	wrreg(cb, Riotop0hi, i>>8);
	we |= 1<<6;
	if(ct->nio == 2 && ct->io[1].start){
		wrreg(cb, Riobtm1lo, ct->io[1].start);
		wrreg(cb, Riobtm1hi, ct->io[1].start>>8);
		i = ct->io[1].start+ct->io[1].len-1;
		wrreg(cb, Riotop1lo, i);
		wrreg(cb, Riotop1hi, i>>8);
		we |= 1<<7;
	}
	wrreg(cb, Rwe, we);

	/* only touch Rconfig if it is present */
	if(pi->conf_present & (1<<Rconfig)){
		PCMmap *m;

		/*  Reset adapter */
		m = isamap(cb, pi->conf_addr + Rconfig, 1, 1);
		p = KADDR(m->isa + pi->conf_addr + Rconfig - m->ca);

		/* set configuration and interrupt type */
		x = ct->index;
		if(ct->irqtype & 0x20)
			x |= Clevel;
		*p = x;
		delay(5);

		isaunmap(m);
	}

	pi->port = isa->port;
	pi->irq = isa->irq;
	unlock(cb);

	print("#Y%zd: %s irq %d, port %lluX\n", cb - cbslots, pi->verstr, isa->irq, isa->port);
	return (int)(cb - cbslots);
}

static void
pccard_pcmspecialclose(int slotno)
{
	Cardbus *cb = &cbslots[slotno];

	wrreg(cb, Rwe, 0);	/* no windows */
	cb->special = 0;
}

static Chan*
pccardattach(char *spec)
{
	if (!managerstarted) {
		managerstarted = 1;
		kproc("cardbus", processevents, nil);
	}
	return devattach('Y', spec);
}

enum
{
	Qdir,
	Qctl,

	Nents = 1,
};

#define SLOTNO(c)	((ulong)((c->qid.path>>8)&0xff))
#define TYPE(c)	((ulong)(c->qid.path&0xff))
#define QID(s,t)	(((s)<<8)|(t))

static int
pccardgen(Chan *c, char*, Dirtab *, int , int i, Dir *dp)
{
	int slotno;
	Qid qid;
	long len;
	int entry;

	if(i == DEVDOTDOT){
		mkqid(&qid, Qdir, 0, QTDIR);
		devdir(c, qid, "#Y", 0, eve, 0555, dp);
		return 1;
	}

	len = 0;
	if(i >= Nents * nslots) return -1;
	slotno = i / Nents;
	entry = i % Nents;
	if (entry == 0) {
		qid.path = QID(slotno, Qctl);
		snprint(up->genbuf, sizeof up->genbuf, "cb%dctl", slotno);
	}
	else {
		/* Entries for memory regions.  I'll implement them when
		     needed. (pb) */
	}
	qid.vers = 0;
	qid.type = QTFILE;
	devdir(c, qid, up->genbuf, len, eve, 0660, dp);
	return 1;
}

static Walkqid*
pccardwalk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name, nname, 0, 0, pccardgen);
}

static int
pccardstat(Chan *c, uchar *db, int n)
{
	return devstat(c, db, n, 0, 0, pccardgen);
}

static void
increfp(Cardbus *cb)
{
	lock(&cb->refslock);
	cb->refs++;
	unlock(&cb->refslock);
}

static void
decrefp(Cardbus *cb)
{
	lock(&cb->refslock);
	cb->refs--;
	unlock(&cb->refslock);
}

static Chan*
pccardopen(Chan *c, int omode)
{
	if (c->qid.type & QTDIR){
		if(omode != OREAD)
			error(Eperm);
	} else
		increfp(&cbslots[SLOTNO(c)]);
	c->mode = openmode(omode);
	c->flag |= COPEN;
	c->offset = 0;
	return c;
}

static void
pccardclose(Chan *c)
{
	if(c->flag & COPEN)
		if((c->qid.type & QTDIR) == 0)
			decrefp(&cbslots[SLOTNO(c)]);
}

static long
pccardread(Chan *c, void *a, long n, vlong offset)
{
	Cardbus *cb;
	char *buf, *p, *e;
	int i;

	switch(TYPE(c)){
	case Qdir:
		return devdirread(c, a, n, 0, 0, pccardgen);

	case Qctl:
		buf = p = smalloc(READSTR);
		buf[0] = 0;
		e = p + READSTR;

		cb = &cbslots[SLOTNO(c)];
		lock(cb);
		p = seprint(p, e, "slot %ld: %s; ", cb - cbslots, states[cb->state]);

		switch (cb->type) {
		case -1:
			seprint(p, e, "\n");
			break;

		case PC32:
			if (cb->pci->bridge) {
				Pcidev *pci = cb->pci->bridge;
				int i;

				while (pci != nil) {
					p = seprint(p, e, "%.4uX %.4uX; irq %d\n",
							  pci->vid, pci->did, pci->intl);
					for (i = 0; i < nelem(pci->mem); i++)
						if (pci->mem[i].size)
							p = seprint(p, e,
									  "\tmem[%d] %.8ullX (%.8ullX)\n",
									  i, pci->mem[i].bar,
									  pci->mem[i].size);
					if (pci->rom.size)
						p = seprint(p, e, "\tROM %.8ullX (%.8ullX)\n",
								  pci->rom.bar, pci->rom.size);
					pci = pci->link;
				}
			}
			break;

		case PC16:
			if (cb->state == SlotConfigured) {
				Pcminfo *pi = &cb->linfo;

				p = seprint(p, e, "%s port %X; irq %d;\n",
						  pi->verstr, pi->port,
						  pi->irq);
				for (i = 0; i != pi->nctab; i++) {
					PCMconftab *ct;
					int j;

					ct = &pi->ctab[i];
					p = seprint(p, e,
						"\tconfiguration[%d] irqs %.4uX; vpp %d, %d; %s\n",
							  i, ct->irqs, ct->vpp1, ct->vpp2,
							  (ct == pi->defctab)? "(default);": "");
					for (j = 0; j != ct->nio; j++)
						if (ct->io[j].len > 0)
							p = seprint(p, e, "\t\tio[%d] %.8ulX %uld\n",
									  j, ct->io[j].start, ct->io[j].len);
				}
			}
			break;
		}
		unlock(cb);

		n = readstr(offset, a, n, buf);
		free(buf);
		return n;
	}
	return 0;
}

static long
pccardwrite(Chan *c, void *v, long n, vlong)
{
	Rune r;
	ulong n0;
	char *device;
	Cmdbuf *cbf;
	Cmdtab *ct;
	Cardbus *cb;

	n0 = n;
	switch(TYPE(c)){
	case Qctl:
		cb = &cbslots[SLOTNO(c)];

		cbf = parsecmd(v, n);
		if(waserror()){
			free(cbf);
			nexterror();
		}
		ct = lookupcmd(cbf, pccardctlmsg, nelem(pccardctlmsg));
		switch(ct->index){
		case CMdown:
			device = cbf->f[1];
			device += chartorune(&r, device);
			if ((n = devno(r, 1)) >= 0 && devtab[n]->config)
				devtab[n]->config(0, device, nil);
			qengine(cb, CardEjected);
			break;
		case CMpower:
			if ((cb->regs[SocketState] & SS_CCD) == 0)
				qengine(cb, CardDetected);
			break;
		}
		poperror();
		free(cbf);
		break;
	}
	return n0 - n;
}

Dev pccarddevtab = {
	'Y',
	"cardbus",

	devreset,
	devinit,
	devshutdown,
	pccardattach,
	pccardwalk,
	pccardstat,
	pccardopen,
	devcreate,
	pccardclose,
	pccardread,
	devbread,
	pccardwrite,
	devbwrite,
	devremove,
	devwstat,
};

static PCMmap *
isamap(Cardbus *cb, ulong offset, int len, int attr)
{
	uchar we, bit;
	PCMmap *m, *nm;
	Pcminfo *pi;
	int i;
	ulong e;

	pi = &cb->linfo;

	/* convert offset to granularity */
	if(len <= 0)
		len = 1;
	e = ROUND(offset+len, Mgran);
	offset &= Mmask;
	len = e - offset;

	/* look for a map that covers the right area */
	we = rdreg(cb, Rwe);
	bit = 1;
	nm = 0;
	for(m = pi->mmap; m < &pi->mmap[nelem(pi->mmap)]; m++){
		if((we & bit))
		if(m->attr == attr)
		if(offset >= m->ca && e <= m->cea){
			m->ref++;
			return m;
		}
		bit <<= 1;
		if(nm == 0 && m->ref == 0)
			nm = m;
	}
	m = nm;
	if(m == 0)
		return 0;

	/* if isa space isn't big enough, free it and get more */
	if(m->len < len){
		if(m->len){
			umbfree(m->isa, m->len);
			m->len = 0;
		}
		m->isa = umballoc(-1, len, Mgran);
		if(m->isa == -1){
			m->isa = 0;
			print("isamap: out of isa space\n");
			return 0;
		}
		m->len = len;
	}

	/* set up new map */
	m->ca = offset;
	m->cea = m->ca + m->len;
	m->attr = attr;
	i = m - pi->mmap;
	bit = 1<<i;
	wrreg(cb, Rwe, we & ~bit);		/* disable map before changing it */
	wrreg(cb, MAP(i, Mbtmlo), m->isa>>Mshift);
	wrreg(cb, MAP(i, Mbtmhi), (m->isa>>(Mshift+8)) | F16bit);
	wrreg(cb, MAP(i, Mtoplo), (m->isa+m->len-1)>>Mshift);
	wrreg(cb, MAP(i, Mtophi), ((m->isa+m->len-1)>>(Mshift+8)));
	offset -= m->isa;
	offset &= (1<<25)-1;
	offset >>= Mshift;
	wrreg(cb, MAP(i, Mofflo), offset);
	wrreg(cb, MAP(i, Moffhi), (offset>>8) | (attr ? Fregactive : 0));
	wrreg(cb, Rwe, we | bit);		/* enable map */
	m->ref = 1;

	return m;
}

static void
isaunmap(PCMmap* m)
{
	m->ref--;
}

/*
 *  reading and writing card registers
 */
static uchar
rdreg(Cardbus *cb, int index)
{
	outb(cb->lindex, cb->lbase + index);
	return inb(cb->ldata);
}

static void
wrreg(Cardbus *cb, int index, uchar val)
{
	outb(cb->lindex, cb->lbase + index);
	outb(cb->ldata, val);
}

static int
readc(Cisdat *cis, uchar *x)
{
	if(cis->cispos >= cis->cislen)
		return 0;
	*x = cis->cisbase[cis->cisskip*cis->cispos];
	cis->cispos++;
	return 1;
}

static ulong
getlong(Cisdat *cis, int size)
{
	uchar c;
	int i;
	ulong x;

	x = 0;
	for(i = 0; i < size; i++){
		if(readc(cis, &c) != 1)
			break;
		x |= c<<(i*8);
	}
	return x;
}

static void
tcfig(Cardbus *cb, Cisdat *cis, int )
{
	uchar size, rasize, rmsize;
	uchar last;
	Pcminfo *pi;

	if(readc(cis, &size) != 1)
		return;
	rasize = (size&0x3) + 1;
	rmsize = ((size>>2)&0xf) + 1;
	if(readc(cis, &last) != 1)
		return;

	pi = &cb->linfo;
	pi->conf_addr = getlong(cis, rasize);
	pi->conf_present = getlong(cis, rmsize);
}

static void
tvers1(Cardbus *cb, Cisdat *cis, int )
{
	uchar c, major, minor, last;
	int  i;
	Pcminfo *pi;

	pi = &cb->linfo;
	if(readc(cis, &major) != 1)
		return;
	if(readc(cis, &minor) != 1)
		return;
	last = 0;
	for(i = 0; i < sizeof(pi->verstr) - 1; i++){
		if(readc(cis, &c) != 1)
			return;
		if(c == 0)
			c = ';';
		if(c == '\n')
			c = ';';
		if(c == 0xff)
			break;
		if(c == ';' && last == ';')
			continue;
		pi->verstr[i] = c;
		last = c;
	}
	pi->verstr[i] = 0;
}

static ulong
microvolt(Cisdat *cis)
{
	uchar c;
	ulong microvolts;
	ulong exp;

	if(readc(cis, &c) != 1)
		return 0;
	exp = exponent[c&0x7];
	microvolts = vmant[(c>>3)&0xf]*exp;
	while(c & 0x80){
		if(readc(cis, &c) != 1)
			return 0;
		switch(c){
		case 0x7d:
			break;		/* high impedence when sleeping */
		case 0x7e:
		case 0x7f:
			microvolts = 0;	/* no connection */
			break;
		default:
			exp /= 10;
			microvolts += exp*(c&0x7f);
		}
	}
	return microvolts;
}

static ulong
nanoamps(Cisdat *cis)
{
	uchar c;
	ulong nanoamps;

	if(readc(cis, &c) != 1)
		return 0;
	nanoamps = exponent[c&0x7]*vmant[(c>>3)&0xf];
	while(c & 0x80){
		if(readc(cis, &c) != 1)
			return 0;
		if(c == 0x7d || c == 0x7e || c == 0x7f)
			nanoamps = 0;
	}
	return nanoamps;
}

/*
 * only nominal voltage (feature 1) is important for config,
 * other features must read card to stay in sync.
 */
static ulong
power(Cisdat *cis)
{
	uchar feature;
	ulong mv;

	mv = 0;
	if(readc(cis, &feature) != 1)
		return 0;
	if(feature & 1)
		mv = microvolt(cis);
	if(feature & 2)
		microvolt(cis);
	if(feature & 4)
		microvolt(cis);
	if(feature & 8)
		nanoamps(cis);
	if(feature & 0x10)
		nanoamps(cis);
	if(feature & 0x20)
		nanoamps(cis);
	if(feature & 0x40)
		nanoamps(cis);
	return mv/1000000;
}

static ulong
ttiming(Cisdat *cis, int scale)
{
	uchar unscaled;
	ulong nanosecs;

	if(readc(cis, &unscaled) != 1)
		return 0;
	nanosecs = (mantissa[(unscaled>>3)&0xf]*exponent[unscaled&7])/10;
	nanosecs = nanosecs * exponent[scale];
	return nanosecs;
}

static void
timing(Cisdat *cis, PCMconftab *ct)
{
	uchar c, i;

	if(readc(cis, &c) != 1)
		return;
	i = c&0x3;
	if(i != 3)
		ct->maxwait = ttiming(cis, i);		/* max wait */
	i = (c>>2)&0x7;
	if(i != 7)
		ct->readywait = ttiming(cis, i);	/* max ready/busy wait */
	i = (c>>5)&0x7;
	if(i != 7)
		ct->otherwait = ttiming(cis, i);	/* reserved wait */
}

static void
iospaces(Cisdat *cis, PCMconftab *ct)
{
	uchar c;
	int i, nio;

	ct->nio = 0;
	if(readc(cis, &c) != 1)
		return;

	ct->bit16 = ((c>>5)&3) >= 2;
	if(!(c & 0x80)){
		ct->io[0].start = 0;
		ct->io[0].len = 1<<(c&0x1f);
		ct->nio = 1;
		return;
	}

	if(readc(cis, &c) != 1)
		return;

	/*
	 * For each of the range descriptions read the
	 * start address and the length (value is length-1).
	 */
	nio = (c&0xf)+1;
	for(i = 0; i < nio; i++){
		ct->io[i].start = getlong(cis, (c>>4)&0x3);
		ct->io[i].len = getlong(cis, (c>>6)&0x3)+1;
	}
	ct->nio = nio;
}

static void
irq(Cisdat *cis, PCMconftab *ct)
{
	uchar c;

	if(readc(cis, &c) != 1)
		return;
	ct->irqtype = c & 0xe0;
	if(c & 0x10)
		ct->irqs = getlong(cis, 2);
	else
		ct->irqs = 1<<(c&0xf);
	ct->irqs &= 0xDEB8;		/* levels available to card */
}

static void
memspace(Cisdat *cis, int asize, int lsize, int host)
{
	ulong haddress, address, len;

	len = getlong(cis, lsize)*256;
	address = getlong(cis, asize)*256;
	USED(len, address);
	if(host){
		haddress = getlong(cis, asize)*256;
		USED(haddress);
	}
}

static void
tentry(Cardbus *cb, Cisdat *cis, int )
{
	uchar c, i, feature;
	PCMconftab *ct;
	Pcminfo *pi;

	pi = &cb->linfo;
	if(pi->nctab >= nelem(pi->ctab))
		return;
	if(readc(cis, &c) != 1)
		return;
	ct = &pi->ctab[pi->nctab++];

	/* copy from last default config */
	if(pi->defctab)
		*ct = *pi->defctab;

	ct->index = c & 0x3f;

	/* is this the new default? */
	if(c & 0x40)
		pi->defctab = ct;

	/* memory wait specified? */
	if(c & 0x80){
		if(readc(cis, &i) != 1)
			return;
		if(i&0x80)
			ct->memwait = 1;
	}

	if(readc(cis, &feature) != 1)
		return;
	switch(feature&0x3){
	case 1:
		ct->vpp1 = ct->vpp2 = power(cis);
		break;
	case 2:
		power(cis);
		ct->vpp1 = ct->vpp2 = power(cis);
		break;
	case 3:
		power(cis);
		ct->vpp1 = power(cis);
		ct->vpp2 = power(cis);
		break;
	default:
		break;
	}
	if(feature&0x4)
		timing(cis, ct);
	if(feature&0x8)
		iospaces(cis, ct);
	if(feature&0x10)
		irq(cis, ct);
	switch((feature>>5)&0x3){
	case 1:
		memspace(cis, 0, 2, 0);
		break;
	case 2:
		memspace(cis, 2, 2, 0);
		break;
	case 3:
		if(readc(cis, &c) != 1)
			return;
		for(i = 0; i <= (c&0x7); i++)
			memspace(cis, (c>>5)&0x3, (c>>3)&0x3, c&0x80);
		break;
	}
}

static void
i82365probe(Cardbus *cb, int lindex, int ldata)
{
	uchar c, id;
	int dev = 0;	/* According to the Ricoh spec 00->3F _and_ 80->BF seem
				     to be the same socket A (ditto for B). */

	outb(lindex, Rid + (dev<<7));
	id = inb(ldata);
	if((id & 0xf0) != 0x80)
		return;		/* not a memory & I/O card */
	if((id & 0x0f) == 0x00)
		return;		/* no revision number, not possible */

	cb->lindex = lindex;
	cb->ldata = ldata;
	cb->ltype = Ti82365;
	cb->lbase = (int)(cb - cbslots) * 0x40;

	switch(id){
	case 0x82:
	case 0x83:
	case 0x84:
		/* could be a cirrus */
		outb(cb->lindex, Rchipinfo + (dev<<7));
		outb(cb->ldata, 0);
		c = inb(cb->ldata);
		if((c & 0xc0) != 0xc0)
			break;
		c = inb(cb->ldata);
		if((c & 0xc0) != 0x00)
			break;
		if(c & 0x20){
			cb->ltype = Tpd6720;
		} else {
			cb->ltype = Tpd6710;
		}

		/* low power mode */
		outb(cb->lindex, Rmisc2 + (dev<<7));
		c = inb(cb->ldata);
		outb(cb->ldata, c & ~Flowpow);
		break;
		break;
	}

	/* if it's not a Cirrus, it could be a Vadem... */
	if(cb->ltype == Ti82365){
		/* unlock the Vadem extended regs */
		outb(cb->lindex, 0x0E + (dev<<7));
		outb(cb->lindex, 0x37 + (dev<<7));

		/* make the id register show the Vadem id */
		outb(cb->lindex, 0x3A + (dev<<7));
		c = inb(cb->ldata);
		outb(cb->ldata, c|0xC0);
		outb(cb->lindex, Rid + (dev<<7));
		c = inb(cb->ldata);
		if(c & 0x08)
			cb->ltype = Tvg46x;

		/* go back to Intel compatible id */
		outb(cb->lindex, 0x3A + (dev<<7));
		c = inb(cb->ldata);
		outb(cb->ldata, c & ~0xC0);
	}
}

static int
vcode(int volt)
{
	switch(volt){
	case 5:
		return 1;
	case 12:
		return 2;
	default:
		return 0;
	}
}