summaryrefslogtreecommitdiff
path: root/os/mpc/i2c.c
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 21:39:35 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 21:39:35 +0000
commit74a4d8c26dd3c1e9febcb717cfd6cb6512991a7a (patch)
treec6e220ba61db3a6ea4052e6841296d829654e664 /os/mpc/i2c.c
parent46439007cf417cbd9ac8049bb4122c890097a0fa (diff)
20060303
Diffstat (limited to 'os/mpc/i2c.c')
-rw-r--r--os/mpc/i2c.c439
1 files changed, 439 insertions, 0 deletions
diff --git a/os/mpc/i2c.c b/os/mpc/i2c.c
new file mode 100644
index 00000000..5218b1c0
--- /dev/null
+++ b/os/mpc/i2c.c
@@ -0,0 +1,439 @@
+#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 I2C bus (master mode)
+ */
+
+typedef struct Ctlr Ctlr;
+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) */
+ MaxIO = 128,
+ MaxSA = 2, /* longest subaddress */
+ Bufsize = (MaxIO+MaxSA+1+4)&~3, /* extra space for subaddress/clock bytes and alignment */
+ Freq = 100000,
+ I2CTimeout = 250, /* msec */
+
+ Chatty = 0,
+};
+
+#define DPRINT if(Chatty)print
+
+/* data cache needn't be flushed if buffers allocated in uncached PHYSIMM */
+#define DCFLUSH(a,n)
+
+/*
+ * I2C software structures
+ */
+
+struct Ctlr {
+ Lock;
+ QLock io;
+ int init;
+ int busywait; /* running before system set up */
+ I2C* i2c;
+ IOCparam* sp;
+
+ BD* rd;
+ BD* td;
+ int phase;
+ Rendez r;
+ char* addr;
+ char* txbuf;
+ char* rxbuf;
+};
+
+static Ctlr i2ctlr[1];
+
+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(int busywait)
+{
+ IMM *io;
+ I2C *i2c;
+ IOCparam *sp;
+ CPMdev *cpm;
+ Ctlr *ctlr;
+ long f, e, emin;
+ int p, d, dmax;
+
+ ctlr = i2ctlr;
+ ctlr->busywait = busywait;
+ if(ctlr->init)
+ return;
+ print("i2c setup...\n");
+ ctlr->init = 1;
+ cpm = cpmdev(CPi2c);
+ i2c = cpm->regs;
+ ctlr->i2c = i2c;
+ sp = cpm->param;
+ if(sp == nil)
+ panic("I2C: can't allocate new parameter memory\n");
+ ctlr->sp = sp;
+ disable();
+
+ if(ctlr->txbuf == nil){
+ ctlr->txbuf = cpmalloc(Bufsize, 2);
+ ctlr->addr = ctlr->txbuf+MaxIO;
+ }
+ if(ctlr->rxbuf == nil)
+ ctlr->rxbuf = 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(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);
+ intrenable(VectorCPIC+cpm->irq, interrupt, i2ctlr, BUSUNKNOWN, "i2c");
+}
+
+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;
+ wakeup(&ctlr->r);
+ }
+ }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;
+ wakeup(&ctlr->r);
+ }
+ }
+ 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;
+ wakeup(&ctlr->r);
+ }
+ }
+ }
+}
+
+static int
+done(void *a)
+{
+ return ((Ctlr*)a)->phase < Busy;
+}
+
+static void
+i2cwait(Ctlr *ctlr)
+{
+ int i;
+
+ if(up == nil || ctlr->busywait){
+ for(i=0; i < 5 && !done(ctlr); i++){
+ delay(2);
+ interrupt(nil, ctlr);
+ }
+ }else
+ tsleep(&ctlr->r, done, ctlr, I2CTimeout);
+}
+
+static int
+i2cerror(char *s)
+{
+ if(up)
+ error(s);
+ /* no current process, don't call error */
+ DPRINT("i2c error: %s\n", s);
+ return -1;
+}
+
+long
+i2csend(I2Cdev *d, void *buf, long n, ulong offset)
+{
+ Ctlr *ctlr;
+ int i, p, s;
+
+ ctlr = i2ctlr;
+ if(up){
+ if(n > MaxIO)
+ error(Etoobig);
+ qlock(&ctlr->io);
+ if(waserror()){
+ qunlock(&ctlr->io);
+ nexterror();
+ }
+ }
+ ctlr->txbuf[0] = d->addr<<1;
+ i = 1;
+ if(d->salen > 1)
+ ctlr->txbuf[i++] = offset>>8;
+ if(d->salen)
+ ctlr->txbuf[i++] = offset;
+ memmove(ctlr->txbuf+i, buf, n);
+ if(Chatty){
+ print("tx: %8.8lux: ", PADDR(ctlr->txbuf));
+ for(s=0; s<n+i; s++)
+ print(" %.2ux", ctlr->txbuf[s]&0xFF);
+ print("\n");
+ }
+ DCFLUSH(ctlr->txbuf, Bufsize);
+ ilock(ctlr);
+ 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();
+ iunlock(ctlr);
+ i2cwait(ctlr);
+ disable();
+ p = ctlr->phase;
+ s = ctlr->td->status;
+ if(up){
+ poperror();
+ qunlock(&ctlr->io);
+ }
+ if(s & BDReady)
+ return i2cerror("timed out");
+ if(s & TxERR){
+ sprint(up->genbuf, "write error: status %.4ux", s);
+ return i2cerror(up->genbuf);
+ }
+ if(p != Done)
+ return i2cerror("phase error");
+ return n;
+}
+
+long
+i2crecv(I2Cdev *d, void *buf, long n, ulong offset)
+{
+ Ctlr *ctlr;
+ int p, s, flag, i;
+ BD *td;
+ long nr;
+
+ ctlr = i2ctlr;
+ if(up){
+ if(n > MaxIO)
+ error(Etoobig);
+ qlock(&ctlr->io);
+ if(waserror()){
+ qunlock(&ctlr->io);
+ nexterror();
+ }
+ }
+ ctlr->txbuf[0] = (d->addr<<1)|Rbit;
+ if(d->salen){ /* special write to set address */
+ ctlr->addr[0] = d->addr<<1;
+ i = 1;
+ if(d->salen > 1)
+ ctlr->addr[i++] = offset >> 8;
+ ctlr->addr[i] = offset;
+ }
+ DCFLUSH(ctlr->txbuf, Bufsize);
+ DCFLUSH(ctlr->rxbuf, Bufsize);
+ ilock(ctlr);
+ 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(d->salen){
+ /* special select sequence */
+ td->addr = PADDR(ctlr->addr);
+ i = d->salen+1;
+ if(i > 3)
+ i = 3;
+ td->length = i;
+ /* 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();
+ iunlock(ctlr);
+ i2cwait(ctlr);
+ disable();
+ p = ctlr->phase;
+ s = ctlr->td->status;
+ if(flag)
+ s |= ctlr->td[1].status;
+ nr = ctlr->rd->length;
+ if(up){
+ poperror();
+ qunlock(&ctlr->io);
+ }
+ DPRINT("nr=%ld %4.4ux %8.8lux\n", nr, ctlr->rd->status, ctlr->rd->addr);
+ if(nr > n)
+ nr = n; /* shouldn't happen */
+ if(s & TxERR){
+ sprint(up->genbuf, "read: tx status: %.4ux", s);
+ return i2cerror(up->genbuf);
+ }
+ if(s & BDReady || ctlr->rd->status & BDEmpty)
+ return i2cerror("timed out");
+ if(p != Done)
+ return i2cerror("phase error");
+ memmove(buf, ctlr->rxbuf, nr);
+ if(Chatty){
+ for(s=0; s<nr; s++)
+ print(" %2.2ux", ctlr->rxbuf[s]&0xFF);
+ print("\n");
+ }
+ return nr;
+}