diff options
Diffstat (limited to 'os/mpc/spi.c')
| -rw-r--r-- | os/mpc/spi.c | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/os/mpc/spi.c b/os/mpc/spi.c new file mode 100644 index 00000000..ce662533 --- /dev/null +++ b/os/mpc/spi.c @@ -0,0 +1,243 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../port/error.h" + +/* + * basic read/write interface to mpc8xx Serial Peripheral Interface; + * used by devtouch.c and devspi.c + */ + +typedef struct Ctlr Ctlr; + +enum { + /* spi-specific BD flags */ + BDContin= 1<<9, /* continuous mode */ + RxeOV= 1<<1, /* overrun */ + TxeUN= 1<<1, /* underflow */ + BDme= 1<<0, /* multimaster error */ + BDrxerr= RxeOV|BDme, + BDtxerr= TxeUN|BDme, + + /* spmod */ + MLoop= 1<<14, /* loopback mode */ + MClockInv= 1<<13, /* inactive state of SPICLK is high */ + MClockPhs= 1<<12, /* SPCLK starts toggling at beginning of transfer */ + MDiv16= 1<<11, /* use BRGCLK/16 as input to SPI baud rate */ + MRev= 1<<10, /* normal operation */ + MMaster= 1<<9, + MSlave= 0<<9, + MEnable= 1<<8, + /* LEN, PS fields */ + + /* spcom */ + STR= 1<<7, /* start transmit */ + + /* spie */ + MME = 1<<5, + TXE = 1<<4, + BSY = 1<<2, + TXB = 1<<1, + RXB = 1<<0, + + /* port B bits */ + SPIMISO = IBIT(28), /* master mode input */ + SPIMOSI = IBIT(29), /* master mode output */ + SPICLK = IBIT(30), + + /* maximum SPI I/O (can change) */ + Bufsize = 64, +}; + +/* + * SPI software structures + */ + +struct Ctlr { + Lock; + QLock io; + int init; + SPI* spi; + IOCparam* sp; + + BD* rd; + BD* td; + int phase; + Rendez r; + char* txbuf; + char* rxbuf; +}; + +static Ctlr spictlr[1]; + +/* dcflush isn't needed if rxbuf and txbuf allocated in uncached IMMR memory */ +#define DCFLUSH(a,n) + +static void interrupt(Ureg*, void*); + +/* + * called by the reset routine of any driver using the SPI + */ +void +spireset(void) +{ + IMM *io; + SPI *spi; + IOCparam *sp; + CPMdev *cpm; + Ctlr *ctlr; + + ctlr = spictlr; + if(ctlr->init) + return; + ctlr->init = 1; + cpm = cpmdev(CPspi); + spi = cpm->regs; + ctlr->spi = spi; + sp = cpm->param; + if(sp == nil){ + print("SPI: can't allocate new parameter memory\n"); + return; + } + ctlr->sp = sp; + + if(ctlr->rxbuf == nil) + ctlr->rxbuf = cpmalloc(Bufsize, 2); + if(ctlr->txbuf == nil) + ctlr->txbuf = cpmalloc(Bufsize, 2); + + if(ctlr->rd == nil){ + ctlr->rd = bdalloc(1); + ctlr->rd->addr = PADDR(ctlr->rxbuf); + ctlr->rd->length = 0; + ctlr->rd->status = BDWrap; + } + if(ctlr->td == nil){ + ctlr->td = bdalloc(1); + ctlr->td->addr = PADDR(ctlr->txbuf); + ctlr->td->length = 0; + ctlr->td->status = BDWrap|BDLast; + } + + /* select port pins */ + io = ioplock(); + io->pbdir |= SPICLK | SPIMOSI | SPIMISO; + io->pbpar |= SPICLK | SPIMOSI | SPIMISO; + iopunlock(); + + /* explicitly initialise parameters, because InitRxTx can't be used (see i2c/spi relocation errata) */ + sp = ctlr->sp; + sp->rbase = PADDR(ctlr->rd); + sp->tbase = PADDR(ctlr->td); + sp->rfcr = 0x18; + sp->tfcr = 0x18; + sp->mrblr = Bufsize; + sp->rstate = 0; + sp->rptr = 0; + sp->rbptr = sp->rbase; + sp->rcnt = 0; + sp->tstate = 0; + sp->tbptr = sp->tbase; + sp->tptr = 0; + sp->tcnt = 0; + eieio(); + + spi->spmode = MDiv16 | MRev | MMaster | ((8-1)<<4) | 1; /* 8 bit characters */ + if(0) + spi->spmode |= MLoop; /* internal loop back mode for testing */ + + spi->spie = ~0; /* clear events */ + eieio(); + spi->spim = MME|TXE|BSY|TXB|RXB; + eieio(); + spi->spmode |= MEnable; + + intrenable(VectorCPIC+cpm->irq, interrupt, spictlr, BUSUNKNOWN, "spi"); + +} + +enum { + Idling, + Waitval, + Readyval +}; + +static void +interrupt(Ureg*, void *arg) +{ + int events; + Ctlr *ctlr; + SPI *spi; + + ctlr = arg; + spi = ctlr->spi; + events = spi->spie; + eieio(); + spi->spie = events; + if(events & (MME|BSY|TXE)){ + print("SPI#%x\n", events); + if(ctlr->phase != Idling){ + ctlr->phase = Idling; + wakeup(&ctlr->r); + } + }else if(events & RXB){ + if(ctlr->phase == Waitval){ + ctlr->phase = Readyval; + wakeup(&ctlr->r); + } + } +} + +static int +done(void *a) +{ + return ((Ctlr*)a)->phase != Waitval; +} + +/* + * send `nout' bytes on SPI from `out' and read as many bytes of reply into buffer `in'; + * return the number of bytes received, or -1 on error. + */ +long +spioutin(void *out, long nout, void *in) +{ + Ctlr *ctlr; + int nb, p; + + ctlr = spictlr; + if(nout > Bufsize) + return -1; + qlock(&ctlr->io); + if(waserror()){ + qunlock(&ctlr->io); + return -1; + } + if(ctlr->phase != Idling) + sleep(&ctlr->r, done, ctlr); + memmove(ctlr->txbuf, out, nout); + DCFLUSH(ctlr->txbuf, Bufsize); + DCFLUSH(ctlr->rxbuf, Bufsize); + ilock(ctlr); + ctlr->phase = Waitval; + ctlr->td->length = nout; + ctlr->rd->status = BDEmpty|BDWrap|BDInt; + ctlr->td->status = BDReady|BDWrap|BDLast; + eieio(); + ctlr->spi->spcom = STR; + eieio(); + iunlock(ctlr); + sleep(&ctlr->r, done, ctlr); + nb = ctlr->rd->length; + if(nb > nout) + nb = nout; /* shouldn't happen */ + p = ctlr->phase; + poperror(); + qunlock(&ctlr->io); + if(p != Readyval) + return -1; + memmove(in, ctlr->rxbuf, nb); + return nb; +} |
