Subversion Repositories planix.SVN

Rev

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

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