diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 21:39:35 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 21:39:35 +0000 |
| commit | 74a4d8c26dd3c1e9febcb717cfd6cb6512991a7a (patch) | |
| tree | c6e220ba61db3a6ea4052e6841296d829654e664 /os/boot/mpc/i2c.c | |
| parent | 46439007cf417cbd9ac8049bb4122c890097a0fa (diff) | |
20060303
Diffstat (limited to 'os/boot/mpc/i2c.c')
| -rw-r--r-- | os/boot/mpc/i2c.c | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/os/boot/mpc/i2c.c b/os/boot/mpc/i2c.c new file mode 100644 index 00000000..1cf9243c --- /dev/null +++ b/os/boot/mpc/i2c.c @@ -0,0 +1,351 @@ +#include "boot.h" + +/* + * basic read/write interface to mpc8xx I2C bus (master mode) + */ + +typedef struct I2C I2C; + +struct I2C { + uchar i2mod; + uchar rsv12a[3]; + uchar i2add; + uchar rsv12b[3]; + uchar i2brg; + uchar rsv12c[3]; + uchar i2com; + uchar rsv12d[3]; + uchar i2cer; + uchar rsv12e[3]; + uchar i2cmr; +}; + +enum { + /* i2c-specific BD flags */ + RxeOV= 1<<1, /* overrun */ + TxS= 1<<10, /* transmit start condition */ + TxeNAK= 1<<2, /* last transmitted byte not acknowledged */ + TxeUN= 1<<1, /* underflow */ + TxeCL= 1<<0, /* collision */ + TxERR= (TxeNAK|TxeUN|TxeCL), + + /* i2cmod */ + REVD= 1<<5, /* =1, LSB first */ + GCD= 1<<4, /* =1, general call address disabled */ + FLT= 1<<3, /* =0, not filtered; =1, filtered */ + PDIV= 3<<1, /* predivisor field */ + EN= 1<<0, /* enable */ + + /* i2com */ + STR= 1<<7, /* start transmit */ + I2CM= 1<<0, /* master */ + I2CS= 0<<0, /* slave */ + + /* i2cer */ + TXE = 1<<4, + BSY = 1<<2, + TXB = 1<<1, + RXB = 1<<0, + + /* port B bits */ + I2CSDA = IBIT(27), + I2CSCL = IBIT(26), + + Rbit = 1<<0, /* bit in address byte denoting read */ + + /* maximum I2C I/O (can change) */ + Bufsize = 64, + Tbuflen= Bufsize+4, /* extra address bytes and alignment */ + Freq = 100000, + I2CTimeout = 250, /* msec */ +}; + +/* data cache needn't be flushed if buffers allocated in uncached INTMEM */ +#define DCFLUSH(a,n) + +/* + * I2C software structures + */ + +struct Ctlr { + Lock; + QLock io; + int init; + I2C* i2c; + IOCparam* sp; + + BD* rd; + BD* td; + int phase; + char* addr; + char* txbuf; + char* rxbuf; +}; +typedef struct Ctlr Ctlr; + +static Ctlr i2ctlr[1]; +extern int predawn; + +static void interrupt(Ureg*, void*); + +static void +enable(void) +{ + I2C *i2c; + + i2c = i2ctlr->i2c; + i2c->i2cer = ~0; /* clear events */ + eieio(); + i2c->i2mod |= EN; + eieio(); + i2c->i2cmr = TXE|BSY|TXB|RXB; /* enable all interrupts */ + eieio(); +} + +static void +disable(void) +{ + I2C *i2c; + + i2c = i2ctlr->i2c; + i2c->i2cmr = 0; /* mask all interrupts */ + i2c->i2mod &= ~EN; +} + +/* + * called by the reset routine of any driver using the I2C + */ +void +i2csetup(void) +{ + IMM *io; + I2C *i2c; + IOCparam *sp; + Ctlr *ctlr; + long f, e, emin; + int p, d, dmax; + + ctlr = i2ctlr; + if(ctlr->init) + return; + print("i2c setup...\n"); + ctlr->init = 1; + i2c = KADDR(INTMEM+0x860); + ctlr->i2c = i2c; + sp = KADDR(INTMEM+0x3c80); + ctlr->sp = sp; + disable(); + + if(ctlr->txbuf == nil){ + ctlr->txbuf = ialloc(Tbuflen, 2); + ctlr->addr = ctlr->txbuf+Bufsize; + } + if(ctlr->rxbuf == nil) + ctlr->rxbuf = ialloc(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(2); + ctlr->td->addr = PADDR(ctlr->txbuf); + ctlr->td->length = 0; + ctlr->td->status = BDWrap|BDLast; + } + + /* select port pins */ + io = ioplock(); + io->pbdir |= I2CSDA | I2CSCL; + io->pbodr |= I2CSDA | I2CSCL; + io->pbpar |= I2CSDA | I2CSCL; + 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(); + + i2c->i2com = I2CM; + i2c->i2mod = 0; /* normal mode */ + i2c->i2add = 0; + + emin = Freq; + dmax = (m->cpuhz/Freq)/2-3; + for(d=0; d < dmax; d++){ + for(p=3; p>=0; p--){ + f = (m->cpuhz>>(p+2))/(2*(d+3)); + e = Freq - f; + if(e < 0) + e = -e; + if(e < emin){ + emin = e; + i2c->i2brg = d; + i2c->i2mod = (i2c->i2mod&~PDIV)|((3-p)<<1); /* set PDIV */ + } + } + } + //print("i2brg=%d i2mod=#%2.2ux\n", i2c->i2brg, i2c->i2mod); + setvec(VectorCPIC+0x10, interrupt, i2ctlr); +} + +enum { + Idling, + Done, + Busy, + Sending, + Recving, +}; + +static void +interrupt(Ureg*, void *arg) +{ + int events; + Ctlr *ctlr; + I2C *i2c; + + ctlr = arg; + i2c = ctlr->i2c; + events = i2c->i2cer; + eieio(); + i2c->i2cer = events; + if(events & (BSY|TXE)){ + //print("I2C#%x\n", events); + if(ctlr->phase != Idling){ + ctlr->phase = Idling; + } + }else{ + if(events & TXB){ + //print("i2c: xmt %d %4.4ux %4.4ux\n", ctlr->phase, ctlr->td->status, ctlr->td[1].status); + if(ctlr->phase == Sending){ + ctlr->phase = Done; + } + } + if(events & RXB){ + //print("i2c: rcv %d %4.4ux %d\n", ctlr->phase, ctlr->rd->status, ctlr->rd->length); + if(ctlr->phase == Recving){ + ctlr->phase = Done; + } + } + } +} + +static int +done(void *a) +{ + return ((Ctlr*)a)->phase < Busy; +} + +static void +i2cwait(Ctlr *ctlr) +{ + /* TO DO: timeout */ + while(!done(ctlr)){ + if(predawn) + interrupt(nil, ctlr); + } +} + +long +i2csend(int addr, void *buf, long n) +{ + Ctlr *ctlr; + int i, p, s; + + ctlr = i2ctlr; + if(n > Bufsize) + return -1; + i = 1; + ctlr->txbuf[0] = addr & ~1; + if(addr & 1){ + ctlr->txbuf[1] = addr>>8; + i++; + } + memmove(ctlr->txbuf+i, buf, n); + DCFLUSH(ctlr->txbuf, Tbuflen); + ctlr->phase = Sending; + ctlr->rd->status = BDEmpty|BDWrap|BDInt; + ctlr->td->addr = PADDR(ctlr->txbuf); + ctlr->td->length = n+i; + ctlr->td->status = BDReady|BDWrap|BDLast|BDInt; + enable(); + ctlr->i2c->i2com = STR|I2CM; + eieio(); + i2cwait(ctlr); + disable(); + p = ctlr->phase; + s = ctlr->td->status; + if(s & BDReady || s & TxERR || p != Done) + return -1; + return n; +} + +long +i2crecv(int addr, void *buf, long n) +{ + Ctlr *ctlr; + int p, s, flag; + BD *td; + long nr; + + ctlr = i2ctlr; + if(n > Bufsize) + return -1; + ctlr->txbuf[0] = addr|Rbit; + if(addr & 1){ /* special select sequence */ + ctlr->addr[0] = addr &~ 1; + ctlr->addr[1] = addr>>8; + } + DCFLUSH(ctlr->txbuf, Tbuflen); + DCFLUSH(ctlr->rxbuf, Bufsize); + ctlr->phase = Recving; + ctlr->rd->addr = PADDR(ctlr->rxbuf); + ctlr->rd->status = BDEmpty|BDWrap|BDInt; + flag = 0; + td = ctlr->td; + td[1].status = 0; + if(addr & 1){ + /* special select sequence */ + td->addr = PADDR(ctlr->addr); + td->length = 2; + /* td->status made BDReady below */ + td++; + flag = TxS; + } + td->addr = PADDR(ctlr->txbuf); + td->length = n+1; + td->status = BDReady|BDWrap|BDLast | flag; /* not BDInt: leave that to receive */ + if(flag) + ctlr->td->status = BDReady; + enable(); + ctlr->i2c->i2com = STR|I2CM; + eieio(); + i2cwait(ctlr); + disable(); + p = ctlr->phase; + s = ctlr->td->status; + if(flag) + s |= ctlr->td[1].status; + nr = ctlr->rd->length; + if(nr > n) + nr = n; /* shouldn't happen */ + if(s & TxERR || s & BDReady || ctlr->rd->status & BDEmpty) + return -1; + if(p != Done) + return -1; + memmove(buf, ctlr->rxbuf, nr); + return nr; +} |
