Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

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

        p = malloc(READSTR);
        if(p == nil)
                error(Enomem);
        ccb = ((Cc*)(uart->regs))->ccb;
        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)
                error(Enomem);
        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);

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