/*
* Oxford Semiconductor OXPCIe95x 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"
extern PhysUart oxphysuart;
enum {
Ccr = 0x0000/4, /* Class Code and Revision ID */
Nuart = 0x0004/4, /* Decimal Number of UARTs */
Gis = 0x0008/4, /* Global UART IRQ Status */
Gie = 0x000C/4, /* Global UART IRQ Enable */
Gid = 0x0010/4, /* Global UART IRQ Disable */
Gwe = 0x0014/4, /* Global UART Wake Enable */
Gwd = 0x0018/4, /* Global UART Wake Disable */
};
enum {
Thr = 0x00, /* Transmitter Holding */
Rhr = 0x00, /* Receiver Holding */
Ier = 0x01, /* Interrupt Enable */
Fcr = 0x02, /* FIFO Control */
Isr = 0x02, /* Interrupt Status */
Lcr = 0x03, /* Line Control */
Mcr = 0x04, /* Modem Control */
Lsr = 0x05, /* Line Status */
Msr = 0x06, /* Modem Status */
Spr = 0x07, /* Scratch Pad */
Dll = 0x00, /* Divisor Latch LSB */
Dlm = 0x01, /* Divisor Latch MSB */
Efr = 0x02, /* Enhanced Feature */
};
typedef struct Port Port;
typedef struct Ctlr Ctlr;
struct Port {
Uart;
Ctlr *ctlr;
u8int *mem;
int level;
int dtr, rts;
int ri;
};
struct Ctlr {
Lock;
char *name;
Pcidev *pcidev;
u32int *mem;
u32int im;
Port port[0x10];
int nport;
};
static Uart *
oxpnp(void)
{
Pcidev *p;
Ctlr *ctlr;
Port *port;
int i;
char *model;
char name[12+1];
Uart *head, *tail;
static int ctlrno;
p = nil;
head = tail = nil;
while(p = pcimatch(p, 0x1415, 0)){
switch(p->did){
case 0xc101:
case 0xc105:
case 0xc11b:
case 0xc11f:
case 0xc120:
case 0xc124:
case 0xc138:
case 0xc13d:
case 0xc140:
case 0xc141:
case 0xc144:
case 0xc145:
case 0xc158:
case 0xc15d:
model = "OXPCIe952";
break;
case 0xc208:
case 0xc20d:
model = "OXPCIe954";
break;
case 0xc308:
case 0xc30d:
model = "OXPCIe958";
break;
default:
continue;
}
ctlr = malloc(sizeof *ctlr);
if(ctlr == nil){
print("oxpnp: out of memory\n");
continue;
}
ctlr->pcidev = p;
ctlr->mem = vmap(p->mem[0].bar & ~0xf, p->mem[0].size);
if(ctlr->mem == nil){
print("oxpnp: vmap failed\n");
free(ctlr);
continue;
}
snprint(name, sizeof name, "uartox%d", ctlrno);
kstrdup(&ctlr->name, name);
ctlr->nport = ctlr->mem[Nuart] & 0x1f;
for(i = 0; i < ctlr->nport; ++i){
port = &ctlr->port[i];
port->ctlr = ctlr;
port->mem = (u8int *)ctlr->mem + 0x1000 + 0x200*i;
port->regs = port;
snprint(name, sizeof name, "%s.%d", ctlr->name, i);
kstrdup(&port->name, name);
port->phys = &oxphysuart;
if(head == nil)
head = port;
else
tail->next = port;
tail = port;
}
print("%s: %s: %d ports irq %d\n",
ctlr->name, model, ctlr->nport, p->intl);
ctlrno++;
}
return head;
}
static void
oxinterrupt(Ureg *, void *arg)
{
Ctlr *ctlr;
Port *port;
Uart *uart;
int i, old;
u8int val;
char ch;
ctlr = arg;
ilock(ctlr);
if(!(ctlr->im & ctlr->mem[Gis])){
iunlock(ctlr);
return;
}
for(i = 0; i < ctlr->nport; ++i){
if(!(ctlr->im & 1<<i))
continue;
port = &ctlr->port[i];
uart = port; /* "Come Clarity" */
switch(port->mem[Isr] & 0x3f){
case 0x06: /* Receiver status error */
case 0x04: /* Receiver data available */
case 0x0c: /* Receiver time-out */
for(;;){
val = port->mem[Lsr];
if(!(val & 1<<0)) /* RxRDY */
break;
if(val & 1<<1) /* Overrun Error */
uart->oerr++;
if(val & 1<<2) /* Parity Error */
uart->perr++;
if(val & 1<<3) /* Framing Error */
uart->ferr++;
ch = port->mem[Rhr];
if(!(val & 1<<7)) /* Data Error */
uartrecv(uart, ch);
}
break;
case 0x02: /* Transmitter THR empty */
uartkick(uart);
break;
case 0x00: /* Modem status change */
val = port->mem[Msr];
if(val & 1<<0){ /* Delta nCTS */
ilock(&uart->tlock);
old = uart->cts;
uart->cts = val & 1<<4; /* CTS */
if(!old && uart->cts)
uart->ctsbackoff = 2;
iunlock(&uart->tlock);
}
if(val & 1<<1){ /* Delta nDSR */
old = val & 1<<5; /* DSR */
if(!old && uart->dsr && uart->hup_dsr)
uart->dohup = 1;
uart->dsr = old;
}
port->ri = val & 1<<6; /* RI */
if(val & 1<<3){ /* Delta nDCD */
old = val & 1<<7; /* DCD */
if(!old && uart->dcd && uart->hup_dcd)
uart->dohup = 1;
uart->dcd = old;
}
break;
}
}
iunlock(ctlr);
}
#define MASK(p) (1UL<<((p)-(p)->ctlr->port))
static void
oxenable(Uart *uart, int)
{
Ctlr *ctlr;
Port *port;
port = uart->regs;
ctlr = port->ctlr;
ilock(ctlr);
if(ctlr->im == 0)
intrenable(ctlr->pcidev->intl, oxinterrupt, ctlr,
ctlr->pcidev->tbdf, ctlr->name);
ctlr->im |= MASK(port);
iunlock(ctlr);
/* Enable 950 Mode */
port->mem[Lcr] |= 1<<7; /* Divisor latch access */
port->mem[Efr] = 1<<4; /* Enhanced mode */
port->mem[Lcr] &= ~(1<<7);
port->mem[Ier] = 1<<2|1<<1|1<<0; /* Rx Stat, THRE, RxRDY */
(*uart->phys->dtr)(uart, 1);
(*uart->phys->rts)(uart, 1);
/* Enable FIFO */
(*uart->phys->fifo)(uart, ~0);
}
static void
oxdisable(Uart *uart)
{
Ctlr *ctlr;
Port *port;
port = uart->regs;
ctlr = port->ctlr;
(*uart->phys->dtr)(uart, 0);
(*uart->phys->rts)(uart, 0);
(*uart->phys->fifo)(uart, 0);
port->mem[Ier] = 0;
ilock(ctlr);
ctlr->im &= ~MASK(port);
if(ctlr->im == 0)
intrdisable(ctlr->pcidev->intl, oxinterrupt, ctlr,
ctlr->pcidev->tbdf, ctlr->name);
iunlock(ctlr);
}
static void
oxkick(Uart *uart)
{
Port *port;
if(uart->cts == 0 || uart->blocked)
return;
port = uart->regs;
for(;;){
if(!(port->mem[Lsr] & 1<<5)) /* THR Empty */
break;
if(uart->op >= uart->oe && uartstageoutput(uart) == 0)
break;
port->mem[Thr] = *(uart->op++);
}
}
static void
oxdobreak(Uart *uart, int ms)
{
Port *port;
if(ms <= 0)
ms = 200;
port = uart->regs;
port->mem[Lcr] |= 1<<6; /* Transmission break */
if(!waserror()){
tsleep(&up->sleep, return0, nil, ms);
poperror();
}
port->mem[Lcr] &= ~(1<<6);
}
static int
oxbaud(Uart *uart, int baud)
{
Port *port;
u16int val;
if(baud <= 0)
return -1;
port = uart->regs;
/*
* We aren't terribly interested in non-standard baud rates.
* Rather than mess about with generator constants, we instead
* program DLM and DLL according to Table 37 in the datasheet.
*/
switch(baud){
case 1200:
val = 0x0cb6;
break;
case 2400:
val = 0x065b;
break;
case 4800:
val = 0x032d;
break;
case 9600:
val = 0x0196;
break;
case 19200:
val = 0x00cb;
break;
case 38400:
val = 0x0066;
break;
case 57600:
val = 0x0044;
break;
case 115200:
val = 0x0022;
break;
default:
return -1;
}
port->mem[Lcr] |= 1<<7; /* Divisor latch access */
port->mem[Dlm] = val>>8;
port->mem[Dll] = val;
port->mem[Lcr] &= ~(1<<7);
uart->baud = baud;
return 0;
}
static int
oxbits(Uart *uart, int bits)
{
Port *port;
u8int val;
port = uart->regs;
val = port->mem[Lcr] & 0x7c;
switch(bits){
case 8:
val |= 0x3; /* Data length */
break;
case 7:
val |= 0x2;
break;
case 6:
val |= 0x1;
break;
case 5:
break;
default:
return -1;
}
port->mem[Lcr] = val;
uart->bits = bits;
return 0;
}
static int
oxstop(Uart *uart, int stop)
{
Port *port;
u8int val;
port = uart->regs;
val = port->mem[Lcr] & 0x7b;
switch(stop){
case 2:
val |= 1<<2; /* Number of Stop Bits */
break;
case 1:
break;
default:
return -1;
}
port->mem[Lcr] = val;
uart->stop = stop;
return 0;
}
static int
oxparity(Uart *uart, int parity)
{
Port *port;
u8int val;
port = uart->regs;
val = port->mem[Lcr] & 0x67;
switch(parity){
case 'e':
val |= 1<<4; /* Even/Odd Parity */
case 'o':
val |= 1<<3; /* Parity Enable */
break;
case 'n':
break;
default:
return -1;
}
port->mem[Lcr] = val;
uart->parity = parity;
return 0;
}
static void
oxmodemctl(Uart *uart, int on)
{
Ctlr *ctlr;
Port *port;
port = uart->regs;
ctlr = port->ctlr;
ilock(ctlr);
ilock(&uart->tlock);
if(on){
port->mem[Ier] |= 1<<3; /* Modem */
uart->cts = port->mem[Msr] & 1<<4; /* CTS */
}else{
port->mem[Ier] &= ~(1<<3);
uart->cts = 1;
}
uart->modem = on;
iunlock(&uart->tlock);
iunlock(ctlr);
}
static void
oxrts(Uart *uart, int on)
{
Port *port;
port = uart->regs;
if(on)
port->mem[Mcr] |= 1<<1; /* RTS */
else
port->mem[Mcr] &= ~(1<<1);
port->rts = on;
}
static void
oxdtr(Uart *uart, int on)
{
Port *port;
port = uart->regs;
if(on)
port->mem[Mcr] |= 1<<0; /* DTR */
else
port->mem[Mcr] &= ~(1<<0);
port->dtr = on;
}
static long
oxstatus(Uart *uart, void *buf, long n, long offset)
{
Port *port;
if(offset > 0)
return 0;
port = uart->regs;
return snprint(buf, n,
"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,
port->dtr,
uart->hup_dsr,
uart->bits,
uart->modem,
uart->parity,
port->rts,
uart->stop,
port->level,
uart->dev,
uart->type,
uart->ferr,
uart->oerr,
uart->berr,
uart->serr,
uart->cts ? " cts": "",
uart->dsr ? " dsr": "",
port->ri ? " ring": "",
uart->dcd ? " dcd": ""
);
}
static void
oxfifo(Uart *uart, int level)
{
Ctlr *ctlr;
Port *port;
port = uart->regs;
ctlr = port->ctlr;
/*
* 950 Mode FIFOs have a depth of 128 bytes; devuart only
* cares about setting RHR trigger levels. THR trigger
* levels are not supported.
*/
ilock(ctlr);
if(level == 0)
port->mem[Fcr] = 0; /* Disable FIFO */
else{
port->mem[Fcr] = 1<<0; /* Enable FIFO */
switch(level){
default:
level = 112;
case 112:
port->mem[Fcr] = 0x03<<6|1<<0; /* RHR Trigger Level */
break;
case 64:
port->mem[Fcr] = 0x02<<6|1<<0;
break;
case 32:
port->mem[Fcr] = 0x01<<6|1<<0;
break;
case 16:
break;
}
}
port->level = level;
iunlock(ctlr);
}
PhysUart oxphysuart = {
.name = "OXPCIe95x",
.pnp = oxpnp,
.enable = oxenable,
.disable = oxdisable,
.kick = oxkick,
.dobreak = oxdobreak,
.baud = oxbaud,
.bits = oxbits,
.stop = oxstop,
.parity = oxparity,
.modemctl = oxmodemctl,
.rts = oxrts,
.dtr = oxdtr,
.status = oxstatus,
.fifo = oxfifo,
};
|