shithub: riscv

ref: 6540a9a21ae4f62f512f3e95063561d87e80701c
dir: /sys/src/9/pc/uartaxp.c/

View raw version
/*
 * Avanstar Xp pci uart driver
 */
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"

#include "uartaxp.i"

typedef struct Cc Cc;
typedef struct Ccb Ccb;
typedef struct Ctlr Ctlr;
typedef struct Gcb Gcb;

/*
 * Global Control Block.
 * Service Request fields must be accessed using XCHG.
 */
struct Gcb {
	u16int	gcw;				/* Global Command Word */
	u16int	gsw;				/* Global Status Word */
	u16int	gsr;				/* Global Service Request */
	u16int	abs;				/* Available Buffer Space */
	u16int	bt;				/* Board Type */
	u16int	cpv;				/* Control Program Version */
	u16int	ccbn;				/* Ccb count */
	u16int	ccboff;				/* Ccb offset */
	u16int	ccbsz;				/* Ccb size */
	u16int	gcw2;				/* Global Command Word 2 */
	u16int	gsw2;				/* Global Status Word 2 */
	u16int	esr;				/* Error Service Request */
	u16int	isr;				/* Input Service Request */
	u16int	osr;				/* Output Service Request */
	u16int	msr;				/* Modem Service Request */
	u16int	csr;				/* Command Service Request */
};

/*
 * Channel Control Block.
 */
struct Ccb {
	u16int	br;				/* Baud Rate */
	u16int	df;				/* Data Format */
	u16int	lp;				/* Line Protocol */
	u16int	ibs;				/* Input Buffer Size */
	u16int	obs;				/* Output Buffer Size */
	u16int 	ibtr;				/* Ib Trigger Rate */
	u16int	oblw;				/* Ob Low Watermark */
	u8int	ixon[2];			/* IXON characters */
	u16int	ibhw;				/* Ib High Watermark */
	u16int	iblw;				/* Ib Low Watermark */
	u16int	cc;				/* Channel Command */
	u16int	cs;				/* Channel Status */
	u16int	ibsa;				/* Ib Start Addr */
	u16int 	ibea;				/* Ib Ending Addr */
	u16int	obsa;				/* Ob Start Addr */
	u16int 	obea;				/* Ob Ending Addr */
	u16int	ibwp;				/* Ib write pointer (RO) */
	u16int	ibrp;				/* Ib read pointer (R/W) */
	u16int	obwp;				/* Ob write pointer (R/W) */
	u16int	obrp;				/* Ob read pointer (RO) */
	u16int	ces;				/* Communication Error Status */
	u16int	bcp;				/* Bad Character Pointer */
	u16int	mc;				/* Modem Control */
	u16int	ms;				/* Modem Status */
	u16int	bs;				/* Blocking Status */
	u16int	crf;				/* Character Received Flag */
	u8int	ixoff[2];			/* IXOFF characters */
	u16int	cs2;				/* Channel Status 2 */
	u8int	sec[2];				/* Strip/Error Characters */
};

enum {						/* br */
	Br76800		= 0xFF00,
	Br115200	= 0xFF01,
};

enum {						/* df */
	Db5		= 0x0000,		/* Data Bits - 5 bits/byte */
	Db6		= 0x0001,		/*	6 bits/byte */
	Db7		= 0x0002,		/*	7 bits/byte */
	Db8		= 0x0003,		/*	8 bits/byte */
	DbMASK		= 0x0003,
	Sb1		= 0x0000,		/* 1 Stop Bit */
	Sb2		= 0x0004,		/* 2 Stop Bit */
	SbMASK		= 0x0004,
	Np		= 0x0000,		/* No Parity */
	Op		= 0x0008,		/* Odd Parity */
	Ep		= 0x0010,		/* Even Parity */
	Mp		= 0x0020,		/* Mark Parity */
	Sp		= 0x0030,		/* Space Parity */
	PMASK		= 0x0038,
	Cmn		= 0x0000,		/* Channel Mode Normal */
	Cme		= 0x0040,		/* CM Echo */
	Cmll		= 0x0080,		/* CM Local Loopback */
	Cmrl		= 0x00C0,		/* CM Remote Loopback */
};

enum {						/* lp */
	Ixon		= 0x0001,		/* Obey IXON/IXOFF */
	Ixany		= 0x0002,		/* Any character retarts Tx */
	Ixgen		= 0x0004,		/* Generate IXON/IXOFF  */
	Cts		= 0x0008,		/* CTS controls Tx */
	Dtr		= 0x0010,		/* Rx controls DTR */
	½d		= 0x0020,		/* RTS off during Tx */
	Rts		= 0x0040,		/* generate RTS */
	Emcs		= 0x0080,		/* Enable Modem Control */
	Ecs		= 0x1000,		/* Enable Character Stripping */
	Eia422		= 0x2000,		/* EIA422 */
};

enum {						/* cc */
	Ccu		= 0x0001,		/* Configure Channel and UART */
	Cco		= 0x0002,		/* Configure Channel Only */
	Fib		= 0x0004,		/* Flush Input Buffer */
	Fob		= 0x0008,		/* Flush Output Buffer */
	Er		= 0x0010,		/* Enable Receiver */
	Dr		= 0x0020,		/* Disable Receiver */
	Et		= 0x0040,		/* Enable Transmitter */
	Dt		= 0x0080,		/* Disable Transmitter */
};

enum {						/* ces */
	Oe		= 0x0001,		/* Overrun Error */
	Pe		= 0x0002,		/* Parity Error */
	Fe		= 0x0004,		/* Framing Error */
	Br		= 0x0008,		/* Break Received */
};

enum {						/* mc */
	Adtr		= 0x0001,		/* Assert DTR */
	Arts		= 0x0002,		/* Assert RTS */
	Ab		= 0x0010,		/* Assert BREAK */
};

enum {						/* ms */
	Scts		= 0x0001,		/* Status od CTS */
	Sdsr		= 0x0002,		/* Status of DSR */
	Sri		= 0x0004,		/* Status of RI */
	Sdcd		= 0x0008,		/* Status of DCD */
};

enum {						/* bs */
	Rd		= 0x0001,		/* Receiver Disabled */
	Td		= 0x0002,		/* Transmitter Disabled */
	Tbxoff		= 0x0004,		/* Tx Blocked by XOFF */
	Tbcts		= 0x0008,		/* Tx Blocked by CTS */
	Rbxoff		= 0x0010,		/* Rx Blocked by XOFF */
	Rbrts		= 0x0020,		/* Rx Blocked by RTS */
};

enum {						/* Local Configuration */
	Range		= 0x00,
	Remap		= 0x04,
	Region		= 0x18,
	Mb0		= 0x40,			/* Mailbox 0 */
	Ldb		= 0x60,			/* PCI to Local Doorbell */
	Pdb		= 0x64,			/* Local to PCI Doorbell */
	Ics		= 0x68,			/* Interrupt Control/Status */
	Mcc		= 0x6C,			/* Misc. Command and Control */
};

enum {						/* Mb0 */
	Edcc		= 1,			/* exec. downloaded code cmd */
	Aic		= 0x10,			/* adapter init'zed correctly */
	Cpr		= 1ul << 31,		/* control program ready */
};

enum {						/* Mcc */
	Rcr		= 1ul << 29,		/* reload config. reg.s */
	Asr		= 1ul << 30,		/* pci adapter sw reset */
	Lis		= 1ul << 31,		/* local init status */
};

typedef struct Cc Cc;
typedef struct Ccb Ccb;
typedef struct Ctlr Ctlr;

/*
 * Channel Control, one per uart.
 * Devuart communicates via the PhysUart functions with
 * a Uart* argument. Uart.regs is filled in by this driver
 * to point to a Cc, and Cc.ctlr points to the Axp board
 * controller.
 */
struct Cc {
	int	uartno;
	Ccb*	ccb;
	Ctlr*	ctlr;

	Rendez;

	Uart;
};

typedef struct Ctlr {
	char*	name;
	Pcidev*	pcidev;
	int	ctlrno;
	Ctlr*	next;

	u32int*	reg;
	uchar*	mem;
	Gcb*	gcb;

	int	im;		/* interrupt mask */
	Cc	cc[16];
} Ctlr;

#define csr32r(c, r)	(*((c)->reg+((r)/4)))
#define csr32w(c, r, v)	(*((c)->reg+((r)/4)) = (v))

static Ctlr* axpctlrhead;
static Ctlr* axpctlrtail;

extern PhysUart axpphysuart;

static int
axpccdone(void* ccb)
{
	return !((Ccb*)ccb)->cc;	/* hw sets ccb->cc to zero */
}

static void
axpcc(Cc* cc, int cmd)
{
	Ccb *ccb;
	int timeo;
	u16int cs;

	ccb = cc->ccb;
	ccb->cc = cmd;

	if(!cc->ctlr->im)
		for(timeo = 0; timeo < 1000000; timeo++){
			if(!ccb->cc)
				break;
			microdelay(1);
		}
	else
		tsleep(cc, axpccdone, ccb, 1000);

	cs = ccb->cs;
	if(ccb->cc || cs){
		print("%s: cmd %#ux didn't terminate: %#ux %#ux\n",
			cc->name, cmd, ccb->cc, cs);
		if(cc->ctlr->im)
			error(Eio);
	}
}

static long
axpstatus(Uart* uart, void* buf, long n, long offset)
{
	char *p;
	Ccb *ccb;
	u16int bs, fstat, ms;

	ccb = ((Cc*)(uart->regs))->ccb;

	p = smalloc(READSTR);
	bs = ccb->bs;
	fstat = ccb->df;
	ms = ccb->ms;

	snprint(p, READSTR,
		"b%d c%d d%d e%d l%d m%d p%c r%d s%d i%d\n"
		"dev(%d) type(%d) framing(%d) overruns(%d) "
		"berr(%d) serr(%d)%s%s%s%s\n",

		uart->baud,
		uart->hup_dcd,
		ms & Sdsr,
		uart->hup_dsr,
		(fstat & DbMASK) + 5,
		0,
		(fstat & PMASK) ? ((fstat & Ep) == Ep? 'e': 'o'): 'n',
		(bs & Rbrts) ? 1 : 0,
		(fstat & Sb2) ? 2 : 1,
		0,

		uart->dev,
		uart->type,
		uart->ferr,
		uart->oerr,
		uart->berr,
		uart->serr,
		(ms & Scts) ? " cts"  : "",
		(ms & Sdsr) ? " dsr"  : "",
		(ms & Sdcd) ? " dcd"  : "",
		(ms & Sri) ? " ring" : ""
	);
	n = readstr(offset, buf, n, p);
	free(p);

	return n;
}

static void
axpfifo(Uart*, int)
{
}

static void
axpdtr(Uart* uart, int on)
{
	Ccb *ccb;
	u16int mc;

	ccb = ((Cc*)(uart->regs))->ccb;

	mc = ccb->mc;
	if(on)
		mc |= Adtr;
	else
		mc &= ~Adtr;
	ccb->mc = mc;
}

/*
 * can be called from uartstageinput() during an input interrupt,
 * with uart->rlock ilocked or the uart qlocked, sometimes both.
 */
static void
axprts(Uart* uart, int on)
{
	Ccb *ccb;
	u16int mc;

	ccb = ((Cc*)(uart->regs))->ccb;

	mc = ccb->mc;
	if(on)
		mc |= Arts;
	else
		mc &= ~Arts;
	ccb->mc = mc;
}

static void
axpmodemctl(Uart* uart, int on)
{
	Ccb *ccb;
	u16int lp;

	ccb = ((Cc*)(uart->regs))->ccb;

	ilock(&uart->tlock);
	lp = ccb->lp;
	if(on){
		lp |= Cts|Rts;
		lp &= ~Emcs;
		uart->cts = ccb->ms & Scts;
	}
	else{
		lp &= ~(Cts|Rts);
		lp |= Emcs;
		uart->cts = 1;
	}
	uart->modem = on;
	iunlock(&uart->tlock);

	ccb->lp = lp;
	axpcc(uart->regs, Ccu);
}

static int
axpparity(Uart* uart, int parity)
{
	Ccb *ccb;
	u16int df;

	switch(parity){
	default:
		return -1;
	case 'e':
		parity = Ep;
		break;
	case 'o':
		parity = Op;
		break;
	case 'n':
		parity = Np;
		break;
	}

	ccb = ((Cc*)(uart->regs))->ccb;

	df = ccb->df & ~PMASK;
	ccb->df = df|parity;
	axpcc(uart->regs, Ccu);

	return 0;
}

static int
axpstop(Uart* uart, int stop)
{
	Ccb *ccb;
	u16int df;

	switch(stop){
	default:
		return -1;
	case 1:
		stop = Sb1;
		break;
	case 2:
		stop = Sb2;
		break;
	}

	ccb = ((Cc*)(uart->regs))->ccb;

	df = ccb->df & ~SbMASK;
	ccb->df = df|stop;
	axpcc(uart->regs, Ccu);

	return 0;
}

static int
axpbits(Uart* uart, int bits)
{
	Ccb *ccb;
	u16int df;

	bits -= 5;
	if(bits < 0 || bits > 3)
		return -1;

	ccb = ((Cc*)(uart->regs))->ccb;

	df = ccb->df & ~DbMASK;
	ccb->df = df|bits;
	axpcc(uart->regs, Ccu);

	return 0;
}

static int
axpbaud(Uart* uart, int baud)
{
	Ccb *ccb;
	int i, ibtr;

	/*
	 * Set baud rate (high rates are special - only 16 bits).
	 */
	if(baud <= 0)
		return -1;
	uart->baud = baud;

	ccb = ((Cc*)(uart->regs))->ccb;

	switch(baud){
	default:
		ccb->br = baud;
		break;
	case 76800:
		ccb->br = Br76800;
		break;
	case 115200:
		ccb->br = Br115200;
		break;
	}

	/*
	 * Set trigger level to about 50 per second.
	 */
	ibtr = baud/500;
	i = (ccb->ibea - ccb->ibsa)/2;
	if(ibtr > i)
		ibtr = i;
	ccb->ibtr = ibtr;
	axpcc(uart->regs, Ccu);

	return 0;
}

static void
axpbreak(Uart* uart, int ms)
{
	Ccb *ccb;
	u16int mc;

	/*
	 * Send a break.
	 */
	if(ms <= 0)
		ms = 200;

	ccb = ((Cc*)(uart->regs))->ccb;

	mc = ccb->mc;
	ccb->mc = Ab|mc;
	tsleep(&up->sleep, return0, 0, ms);
	ccb->mc = mc & ~Ab;
}

/* only called from interrupt service */
static void
axpmc(Cc* cc)
{
	int old;
	Ccb *ccb;
	u16int ms;

	ccb = cc->ccb;

	ms = ccb->ms;

	if(ms & Scts){
		ilock(&cc->tlock);
		old = cc->cts;
		cc->cts = ms & Scts;
		if(old == 0 && cc->cts)
			cc->ctsbackoff = 2;
		iunlock(&cc->tlock);
	}
	if(ms & Sdsr){
		old = ms & Sdsr;
		if(cc->hup_dsr && cc->dsr && !old)
			cc->dohup = 1;
		cc->dsr = old;
	}
	if(ms & Sdcd){
		old = ms & Sdcd;
		if(cc->hup_dcd && cc->dcd && !old)
			cc->dohup = 1;
		cc->dcd = old;
	}
}

/* called from uartkick() with uart->tlock ilocked */
static void
axpkick(Uart* uart)
{
	Cc *cc;
	Ccb *ccb;
	uchar *ep, *mem, *rp, *wp, *bp;

	if(uart->cts == 0 || uart->blocked)
		return;

	cc = uart->regs;
	ccb = cc->ccb;

	mem = (uchar*)cc->ctlr->gcb;
	bp = mem + ccb->obsa;
	rp = mem + ccb->obrp;
	wp = mem + ccb->obwp;
	ep = mem + ccb->obea;
	while(wp != rp-1 && (rp != bp || wp != ep)){
		/*
		 * if we've exhausted the uart's output buffer,
		 * ask for more from the output queue, and quit if there
		 * isn't any.
		 */
		if(uart->op >= uart->oe && uartstageoutput(uart) == 0)
			break;
		*wp++ = *(uart->op++);
		if(wp > ep)
			wp = bp;
		ccb->obwp = wp - mem;
	}
}

/* only called from interrupt service */
static void
axprecv(Cc* cc)
{
	Ccb *ccb;
	uchar *ep, *mem, *rp, *wp;

	ccb = cc->ccb;

	mem = (uchar*)cc->ctlr->gcb;
	rp = mem + ccb->ibrp;
	wp = mem + ccb->ibwp;
	ep = mem + ccb->ibea;

	while(rp != wp){
		uartrecv(cc, *rp++);		/* ilocks cc->tlock */
		if(rp > ep)
			rp = mem + ccb->ibsa;
		ccb->ibrp = rp - mem;
	}
}

static void
axpinterrupt(Ureg*, void* arg)
{
	int work;
	Cc *cc;
	Ctlr *ctlr;
	u32int ics;
	u16int r, sr;

	work = 0;
	ctlr = arg;
	ics = csr32r(ctlr, Ics);
	if(ics & 0x0810C000)
		print("%s: unexpected interrupt %#ux\n", ctlr->name, ics);
	if(!(ics & 0x00002000)) {
		/* we get a steady stream of these on consoles */
		// print("%s: non-doorbell interrupt\n", ctlr->name);
		ctlr->gcb->gcw2 = 0x0001;	/* set Gintack */
		return;
	}

//	while(work to do){
		cc = ctlr->cc;
		for(sr = xchgw(&ctlr->gcb->isr, 0); sr != 0; sr >>= 1){
			if(sr & 0x0001)
				work++, axprecv(cc);
			cc++;
		}
		cc = ctlr->cc;
		for(sr = xchgw(&ctlr->gcb->osr, 0); sr != 0; sr >>= 1){
			if(sr & 0x0001)
				work++, uartkick(&cc->Uart);
			cc++;
		}
		cc = ctlr->cc;
		for(sr = xchgw(&ctlr->gcb->csr, 0); sr != 0; sr >>= 1){
			if(sr & 0x0001)
				work++, wakeup(cc);
			cc++;
		}
		cc = ctlr->cc;
		for(sr = xchgw(&ctlr->gcb->msr, 0); sr != 0; sr >>= 1){
			if(sr & 0x0001)
				work++, axpmc(cc);
			cc++;
		}
		cc = ctlr->cc;
		for(sr = xchgw(&ctlr->gcb->esr, 0); sr != 0; sr >>= 1){
			if(sr & 0x0001){
				r = cc->ccb->ms;
				if(r & Oe)
					cc->oerr++;
				if(r & Pe)
					cc->perr++;
				if(r & Fe)
					cc->ferr++;
				if (r & (Oe|Pe|Fe))
					work++;
			}
			cc++;
		}
//	}
	/* only meaningful if we don't share the irq */
	if (0 && !work)
		print("%s: interrupt with no work\n", ctlr->name);
	csr32w(ctlr, Pdb, 1);		/* clear doorbell interrupt */
	ctlr->gcb->gcw2 = 0x0001;	/* set Gintack */
}

static void
axpdisable(Uart* uart)
{
	Cc *cc;
	u16int lp;
	Ctlr *ctlr;

	/*
 	 * Turn off DTR and RTS, disable interrupts.
	 */
	(*uart->phys->dtr)(uart, 0);
	(*uart->phys->rts)(uart, 0);

	cc = uart->regs;
	lp = cc->ccb->lp;
	cc->ccb->lp = Emcs|lp;
	axpcc(cc, Dt|Dr|Fob|Fib|Ccu);

	/*
	 * The Uart is qlocked.
	 */
	ctlr = cc->ctlr;
	ctlr->im &= ~(1<<cc->uartno);
	if(ctlr->im == 0)
		intrdisable(ctlr->pcidev->intl, axpinterrupt, ctlr,
			ctlr->pcidev->tbdf, ctlr->name);
}

static void
axpenable(Uart* uart, int ie)
{
	Cc *cc;
	Ctlr *ctlr;
	u16int lp;

	cc = uart->regs;
	ctlr = cc->ctlr;

	/*
 	 * Enable interrupts and turn on DTR and RTS.
	 * Be careful if this is called to set up a polled serial line
	 * early on not to try to enable interrupts as interrupt-
	 * -enabling mechanisms might not be set up yet.
	 */
	if(ie){
		/*
		 * The Uart is qlocked.
		 */
		if(ctlr->im == 0){
			intrenable(ctlr->pcidev->intl, axpinterrupt, ctlr,
				ctlr->pcidev->tbdf, ctlr->name);
			csr32w(ctlr, Ics, 0x00031F00);
			csr32w(ctlr, Pdb, 1);
			ctlr->gcb->gcw2 = 1;
		}
		ctlr->im |= 1<<cc->uartno;
	}

	(*uart->phys->dtr)(uart, 1);
	(*uart->phys->rts)(uart, 1);

	/*
	 * Make sure we control RTS, DTR and break.
	 */
	lp = cc->ccb->lp;
	cc->ccb->lp = Emcs|lp;
	cc->ccb->oblw = 64;
	axpcc(cc, Et|Er|Ccu);
}

static void*
axpdealloc(Ctlr* ctlr)
{
	int i;

	for(i = 0; i < 16; i++){
		if(ctlr->cc[i].name != nil)
			free(ctlr->cc[i].name);
	}
	if(ctlr->reg != nil)
		vunmap(ctlr->reg, ctlr->pcidev->mem[0].size);
	if(ctlr->mem != nil)
		vunmap(ctlr->mem, ctlr->pcidev->mem[2].size);
	if(ctlr->name != nil)
		free(ctlr->name);
	free(ctlr);

	return nil;
}

static Uart*
axpalloc(int ctlrno, Pcidev* pcidev)
{
	Cc *cc;
	uchar *p;
	Ctlr *ctlr;
	void *addr;
	char name[64];
	u32int bar, r;
	int i, n, timeo;

	ctlr = malloc(sizeof(Ctlr));
	if(ctlr == nil){
		print("uartaxp: can't allocate memory\n");
		return nil;
	}
	seprint(name, name+sizeof(name), "uartaxp%d", ctlrno);
	kstrdup(&ctlr->name, name);
	ctlr->pcidev = pcidev;
	ctlr->ctlrno = ctlrno;

	/*
	 * Access to runtime registers.
	 */
	bar = pcidev->mem[0].bar;
	if((addr = vmap(bar & ~0x0F, pcidev->mem[0].size)) == 0){
		print("%s: can't map registers at %#ux\n", ctlr->name, bar);
		return axpdealloc(ctlr);
	}
	ctlr->reg = addr;
	print("%s: port 0x%ux irq %d ", ctlr->name, bar, pcidev->intl);

	/*
	 * Local address space 0.
	 */
	bar = pcidev->mem[2].bar;
	if((addr = vmap(bar & ~0x0F, pcidev->mem[2].size)) == 0){
		print("%s: can't map memory at %#ux\n", ctlr->name, bar);
		return axpdealloc(ctlr);
	}
	ctlr->mem = addr;
	ctlr->gcb = (Gcb*)(ctlr->mem+0x10000);
	print("mem 0x%ux size %d: ", bar, pcidev->mem[2].size);

	pcienable(pcidev);

	/*
	 * Toggle the software reset and wait for
	 * the adapter local init status to indicate done.
	 *
	 * The two 'delay(100)'s below are important,
	 * without them the board seems to become confused
	 * (perhaps it needs some 'quiet time' because the
	 * timeout loops are not sufficient in themselves).
	 */
	r = csr32r(ctlr, Mcc);
	csr32w(ctlr, Mcc, r|Asr);
	microdelay(1);
	csr32w(ctlr, Mcc, r&~Asr);
	delay(100);

	for(timeo = 0; timeo < 100000; timeo++){
		if(csr32r(ctlr, Mcc) & Lis)
			break;
		microdelay(1);
	}
	if(!(csr32r(ctlr, Mcc) & Lis)){
		print("%s: couldn't reset\n", ctlr->name);
		return axpdealloc(ctlr);
	}
	print("downloading...");
	/*
	 * Copy the control programme to the card memory.
	 * The card's i960 control structures live at 0xD000.
	 */
	if(sizeof(uartaxpcp) > 0xD000){
		print("%s: control programme too big\n", ctlr->name);
		return axpdealloc(ctlr);
	}
	/* TODO: is this right for more than 1 card? devastar does the same */
	csr32w(ctlr, Remap, 0xA0000001);
	for(i = 0; i < sizeof(uartaxpcp); i++)
		ctlr->mem[i] = uartaxpcp[i];
	/*
	 * Execute downloaded code and wait for it
	 * to signal ready.
	 */
	csr32w(ctlr, Mb0, Edcc);
	delay(100);
	/* the manual says to wait for Cpr for 1 second */
	for(timeo = 0; timeo < 10000; timeo++){
		if(csr32r(ctlr, Mb0) & Cpr)
			break;
		microdelay(100);
	}
	if(!(csr32r(ctlr, Mb0) & Cpr)){
		print("control programme not ready; Mb0 %#ux\n",
			csr32r(ctlr, Mb0));
		print("%s: distribution panel not connected or card not fully seated?\n",
			ctlr->name);

		return axpdealloc(ctlr);
	}
	print("\n");

	n = ctlr->gcb->ccbn;
	if(ctlr->gcb->bt != 0x12 || n > 16){
		print("%s: wrong board type %#ux, %d channels\n",
			ctlr->name, ctlr->gcb->bt, ctlr->gcb->ccbn);
		return axpdealloc(ctlr);
	}

	p = ((uchar*)ctlr->gcb) + ctlr->gcb->ccboff;
	for(i = 0; i < n; i++){
		cc = &ctlr->cc[i];
		cc->ccb = (Ccb*)p;
		p += ctlr->gcb->ccbsz;
		cc->uartno = i;
		cc->ctlr = ctlr;

		cc->regs = cc;		/* actually Uart->regs */
		seprint(name, name+sizeof(name), "uartaxp%d%2.2d", ctlrno, i);
		kstrdup(&cc->name, name);
		cc->freq = 0;
		cc->bits = 8;
		cc->stop = 1;
		cc->parity = 'n';
		cc->baud = 9600;
		cc->phys = &axpphysuart;
		cc->console = 0;
		cc->special = 0;

		cc->next = &ctlr->cc[i+1];
	}
	ctlr->cc[n-1].next = nil;

	ctlr->next = nil;
	if(axpctlrhead != nil)
		axpctlrtail->next = ctlr;
	else
		axpctlrhead = ctlr;
	axpctlrtail = ctlr;

	return ctlr->cc;
}

static Uart*
axppnp(void)
{
	Pcidev *p;
	int ctlrno;
	Uart *head, *tail, *uart;

	/*
	 * Loop through all PCI devices looking for simple serial
	 * controllers (ccrb == 0x07) and configure the ones which
	 * are familiar.
	 */
	head = tail = nil;
	ctlrno = 0;
	for(p = pcimatch(nil, 0, 0); p != nil; p = pcimatch(p, 0, 0)){
		if(p->ccrb != 0x07)
			continue;

		switch((p->did<<16)|p->vid){
		default:
			continue;
		case (0x6001<<16)|0x114F:	/* AvanstarXp */
			if((uart = axpalloc(ctlrno, p)) == nil)
				continue;
			break;
		}

		if(head != nil)
			tail->next = uart;
		else
			head = uart;
		for(tail = uart; tail->next != nil; tail = tail->next)
			;
		ctlrno++;
	}

	return head;
}

PhysUart axpphysuart = {
	.name		= "AvanstarXp",
	.pnp		= axppnp,
	.enable		= axpenable,
	.disable	= axpdisable,
	.kick		= axpkick,
	.dobreak	= axpbreak,
	.baud		= axpbaud,
	.bits		= axpbits,
	.stop		= axpstop,
	.parity		= axpparity,
	.modemctl	= axpmodemctl,
	.rts		= axprts,
	.dtr		= axpdtr,
	.status		= axpstatus,
	.fifo		= axpfifo,
	.getc		= nil,
	.putc		= nil,
};