ref: dd16eef8365cb0b9e4805c23291aadcaa596dd87
dir: /os/mpc/spi.c/
#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;
}