
The new pci code is moved to port/pci.[hc] and shared by all ports. Each port has its own PCI controller implementation, providing the pcicfgrw*() functions for low level pci config space access. The locking for pcicfgrw*() is now done by the caller (only port/pci.c). Device drivers now need to include "../port/pci.h" in addition to "io.h". The new code now checks bridge windows and membars, while enumerating the bus, giving the pc driver a chance to re-assign them. This is needed because some UEFI implementations fail to assign the bars for some devices, so we need to do it outselfs. (See pcireservemem()). While working on this, it was discovered that the pci code assimed the smallest I/O bar size is 16 (pcibarsize()), which is wrong. I/O bars can be as small as 4 bytes. Bit 1 in an I/O bar is also reserved and should be masked off, making the port mask: port = bar & ~3;
963 lines
19 KiB
C
963 lines
19 KiB
C
/*
|
|
* 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/pci.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 r;
|
|
int i, n, timeo;
|
|
uvlong io;
|
|
|
|
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.
|
|
*/
|
|
io = pcidev->mem[0].bar & ~0xF;
|
|
addr = vmap(io, pcidev->mem[0].size);
|
|
if(addr == nil){
|
|
print("%s: can't map registers at %llux\n", ctlr->name, io);
|
|
return axpdealloc(ctlr);
|
|
}
|
|
ctlr->reg = addr;
|
|
print("%s: port 0x%llux irq %d ", ctlr->name, io, pcidev->intl);
|
|
|
|
/*
|
|
* Local address space 0.
|
|
*/
|
|
io = pcidev->mem[2].bar & ~0xF;
|
|
addr = vmap(io, pcidev->mem[2].size);
|
|
if(addr == nil){
|
|
print("%s: can't map memory at %llux\n", ctlr->name, io);
|
|
return axpdealloc(ctlr);
|
|
}
|
|
ctlr->mem = addr;
|
|
ctlr->gcb = (Gcb*)(ctlr->mem+0x10000);
|
|
print("mem 0x%llux size %d: ", io, 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;
|
|
if((p->mem[0].bar & 1) != 0 || (p->mem[2].bar & 1) != 0)
|
|
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,
|
|
};
|