summaryrefslogtreecommitdiff
path: root/os/pxa/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/pxa/i2c.c
parent46439007cf417cbd9ac8049bb4122c890097a0fa (diff)
20060303
Diffstat (limited to 'os/pxa/i2c.c')
-rw-r--r--os/pxa/i2c.c561
1 files changed, 561 insertions, 0 deletions
diff --git a/os/pxa/i2c.c b/os/pxa/i2c.c
new file mode 100644
index 00000000..b45542ae
--- /dev/null
+++ b/os/pxa/i2c.c
@@ -0,0 +1,561 @@
+/*
+ * basic read/write interface to PXA25x I⁲C bus (master mode)
+ * 7 bit addressing only.
+ * TO DO:
+ * - enable unit clock
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "io.h"
+
+typedef struct Ctlr Ctlr;
+typedef struct I2Cregs I2Cregs;
+struct I2Cregs {
+ ulong ibmr; /* bus monitor */
+ ulong pad0;
+ ulong idbr; /* data buffer */
+ ulong pad1;
+ ulong icr; /* control */
+ ulong pad2;
+ ulong isr; /* status */
+ ulong pad3;
+ ulong isar; /* slave address */
+};
+
+enum {
+ /* ibmr */
+ Scls= 1<<1, /* SCL pin status */
+ Sdas= 1<<0, /* SDA pin status */
+
+ /* icr */
+ Fm= 1<<15, /* =0, 100 kb/sec; =1, 400 kb/sec */
+ Ur= 1<<14, /* reset the i2c unit only */
+ Sadie= 1<<13, /* slave address detected interrupt enable */
+ Aldie= 1<<12, /* arbitration loss detected interrupt enable (master mode) */
+ Ssdie= 1<<11, /* stop detected interrupt enable (slave mode) */
+ Beie= 1<<10, /* bus error interrupt enable */
+ Irfie= 1<<9, /* idbr receive full, interrupt enable */
+ Iteie= 1<<8, /* idbr transmit empty interrupt enable */
+ Gcd= 1<<7, /* disable response to general call message (slave); must be set if master uses g.c. */
+ Scle= 1<<6, /* SCL enable: enable clock output for master mode */
+ Iue= 1<<5, /* enable i2c (default: slave) */
+ Ma= 1<<4, /* master abort (send STOP without data) */
+ Tb= 1<<3, /* transfer byte on i2c bus */
+ Ack= 0<<2,
+ Nak= 1<<2,
+ Stop= 1<<1, /* send a stop */
+ Start= 1<<0, /* send a stop */
+
+ /* isr */
+ Bed= 1<<10, /* bus error detected */
+ Sad= 1<<9, /* slave address detected */
+ Gcad= 1<<8, /* general call address detected */
+ Irf= 1<<7, /* idbr receive full */
+ Ite= 1<<6, /* idbr transmit empty */
+ Ald= 1<<5, /* arbitration loss detected (multi-master) */
+ Ssd= 1<<4, /* slave stop detected */
+ Ibb= 1<<3, /* i2c bus is busy */
+ Ub= 1<<2, /* unit is busy (between start and stop) */
+ Nakrcv= 1<<1, /* nak received or sent a NAK */
+ Rwm= 1<<0, /* =0, master transmit (or slave receive); =1, master receive (or slave transmit) */
+ Err= Bed | Ssd,
+
+ /* isar address (0x7F bits) */
+
+ /* others */
+ Rbit = 1<<0, /* bit in address byte denoting read */
+ Wbit= 0<<0,
+
+ MaxIO = 8192, /* largest transfer done at once (can change) */
+ MaxSA= 2, /* largest subaddress; could be FIFOsize */
+ Bufsize = MaxIO, /* subaddress bytes don't go in buffer */
+ Freq = 0, /* set to Fm for high-speed */
+// I2Ctimeout = 125, /* msec (can change) */
+ I2Ctimeout = 10000, /* msec when Chatty */
+
+ Chatty = 0,
+};
+
+#define DPRINT if(Chatty)print
+
+/*
+ * I2C software structures
+ */
+
+struct Ctlr {
+ Lock;
+ QLock io;
+ int init;
+ int polling; /* eg, when running before system set up */
+ I2Cregs* regs; /* hardware registers */
+
+ /* controller state (see below) */
+ int status;
+ int phase;
+ Rendez r;
+
+ /* transfer parameters */
+ int addr;
+ int salen; /* bytes remaining of subaddress */
+ int offset; /* sub-addressed offset */
+ int cntl; /* everything but transfer length */
+ int rdcount; /* requested read transfer size */
+ Block* b;
+};
+
+enum {
+ /* Ctlr.state */
+ Idle,
+ Done,
+ Failed,
+ Busy,
+ Address,
+ Subaddress,
+ Read,
+ Write,
+ Halting,
+};
+
+static Ctlr i2cctlr[1];
+
+static void interrupt(Ureg*, void*);
+static int readyxfer(Ctlr*, int);
+static void rxstart(Ctlr*);
+static void txstart(Ctlr*);
+static void stopxfer(Ctlr*);
+static void txoffset(Ctlr*, ulong, int);
+static int idlectlr(Ctlr*);
+
+static void
+i2cdump(char *t, I2Cregs *i2c)
+{
+ iprint("i2c %s: ibmr=%.4lux icr=%.4lux isr=%.4lux\n", t, i2c->ibmr, i2c->icr, i2c->isr);
+}
+
+static void
+initialise(I2Cregs *i2c, int eintr)
+{
+ int ctl;
+
+ /* initialisation (see p. 9-11 on) */
+ i2c->isar = 0;
+ ctl = Freq | Gcd | Scle | Iue;
+ if(eintr)
+ ctl |= Beie | Irfie; /* Iteie set by txstart */
+ i2c->icr = ctl;
+ if(Chatty)
+ iprint("ctl=%4.4ux icr=%4.4lux\n", ctl, i2c->icr);
+}
+
+/*
+ * called by the reset routine of any driver using the IIC
+ */
+void
+i2csetup(int polling)
+{
+ I2Cregs *i2c;
+ Ctlr *ctlr;
+
+ ctlr = i2cctlr;
+ ctlr->polling = polling;
+ i2c = KADDR(PHYSI2C);
+ ctlr->regs = i2c;
+ if(!polling){
+ if(ctlr->init == 0){
+ initialise(i2c, 1);
+ ctlr->init = 1;
+ intrenable(IRQ, IRQi2c, interrupt, i2cctlr, "i2c");
+ if(Chatty)
+ i2cdump("init", i2c);
+ }
+ }else
+ initialise(i2c, 0);
+}
+
+static void
+done(Ctlr *ctlr)
+{
+ ctlr->phase = Done;
+ wakeup(&ctlr->r);
+}
+
+static void
+failed(Ctlr *ctlr)
+{
+ ctlr->phase = Failed;
+ wakeup(&ctlr->r);
+}
+
+static void
+interrupt(Ureg*, void *arg)
+{
+ int sts, idl;
+ Ctlr *ctlr;
+ Block *b;
+ I2Cregs *i2c;
+ char xx[12];
+
+ ctlr = arg;
+ i2c = ctlr->regs;
+ idl = (i2c->ibmr & 3) == 3;
+ if(Chatty && ctlr->phase != Read && ctlr->phase != Write){
+ snprint(xx, sizeof(xx), "intr %d", ctlr->phase);
+ i2cdump(xx, i2c);
+ }
+ sts = i2c->isr;
+ if(sts & (Bed | Sad | Gcad | Ald))
+ iprint("i2c: unexpected status: %.4ux", sts);
+ i2c->isr = sts;
+ ctlr->status = sts;
+ i2c->icr &= ~(Start | Stop | Nak | Ma | Iteie);
+ if(sts & Err){
+ failed(ctlr);
+ return;
+ }
+ switch(ctlr->phase){
+ default:
+ iprint("i2c: unexpected interrupt: p-%d s=%.4ux\n", ctlr->phase, sts);
+ break;
+
+ case Halting:
+ ctlr->phase = Idle;
+ break;
+
+ case Subaddress:
+ if(ctlr->salen){
+ /* push out next byte of subaddress */
+ ctlr->salen -= 8;
+ i2c->idbr = ctlr->offset >> ctlr->salen;
+ i2c->icr |= Aldie | Tb | Iteie;
+ break;
+ }
+ /* subaddress finished */
+ if(ctlr->cntl & Rbit){
+ /* must readdress if reading to change mode */
+ i2c->idbr = (ctlr->addr << 1) | Rbit;
+ i2c->icr |= Start | Tb | Iteie;
+ ctlr->phase = Address; /* readdress */
+ break;
+ }
+ /* FALL THROUGH if writing */
+ case Address:
+ /* if not sub-addressed, rxstart/txstart */
+ if(ctlr->cntl & Rbit)
+ rxstart(ctlr);
+ else
+ txstart(ctlr);
+ break;
+
+ case Read:
+ b = ctlr->b;
+ if(b == nil)
+ panic("i2c: no buffer");
+ /* master receive: next byte */
+ if(sts & Irf){
+ ctlr->rdcount--;
+ if(b->wp < b->lim)
+ *b->wp++ = i2c->idbr;
+ }
+ if(ctlr->rdcount <= 0 || sts & Nakrcv || idl){
+ if(Chatty)
+ iprint("done: %.4ux\n", sts);
+ done(ctlr);
+ break;
+ }
+ rxstart(ctlr);
+ break;
+
+ case Write:
+ b = ctlr->b;
+ if(b == nil)
+ panic("i2c: no buffer");
+ /* account for data transmitted */
+ if(BLEN(b) <= 0 || sts & Nakrcv){
+ done(ctlr);
+ break;
+ }
+ txstart(ctlr);
+ break;
+ }
+}
+
+static int
+isdone(void *a)
+{
+ return ((Ctlr*)a)->phase < Busy;
+}
+
+static int
+i2cerror(char *s)
+{
+ DPRINT("i2c error: %s\n", s);
+ if(up)
+ error(s);
+ /* no current process, don't call error */
+ return -1;
+}
+
+static char*
+startxfer(I2Cdev *d, int op, Block *b, int n, ulong offset)
+{
+ I2Cregs *i2c;
+ Ctlr *ctlr;
+ int i, p, s;
+
+ ctlr = i2cctlr;
+ if(up){
+ qlock(&ctlr->io);
+ if(waserror()){
+ qunlock(&ctlr->io);
+ nexterror();
+ }
+ }
+ ilock(ctlr);
+ if(!idlectlr(ctlr)){
+ iunlock(ctlr);
+ if(up)
+ error("bus confused");
+ return "bus confused";
+ }
+ if(ctlr->phase >= Busy)
+ panic("i2c: ctlr busy");
+ ctlr->cntl = op;
+ ctlr->b = b;
+ ctlr->rdcount = n;
+ ctlr->addr = d->addr;
+ i2c = ctlr->regs;
+ ctlr->salen = d->salen*8;
+ ctlr->offset = offset;
+ if(ctlr->salen){
+ ctlr->phase = Subaddress;
+ op = Wbit;
+ }else
+ ctlr->phase = Address;
+ i2c->idbr = (d->addr<<1) | op; /* 7-bit address + R/nW */
+ i2c->icr |= Start | Tb | Iteie;
+ if(Chatty)
+ i2cdump("start", i2c);
+ iunlock(ctlr);
+
+ /* wait for it */
+ if(ctlr->polling){
+ for(i=0; !isdone(ctlr); i++){
+ delay(2);
+ interrupt(nil, ctlr);
+ }
+ }else
+ tsleep(&ctlr->r, isdone, ctlr, I2Ctimeout);
+
+ ilock(ctlr);
+ p = ctlr->phase;
+ s = ctlr->status;
+ ctlr->b = nil;
+ if(ctlr->phase != Done && ctlr->phase != Idle)
+ stopxfer(ctlr);
+ iunlock(ctlr);
+
+ if(up){
+ poperror();
+ qunlock(&ctlr->io);
+ }
+ if(p != Done || s & (Bed|Ald)){ /* CHECK; time out */
+ if(s & Ald)
+ return "i2c lost arbitration";
+ if(s & Bed)
+ return "i2c bus error";
+ if(s & Ssd)
+ return "i2c transfer aborted"; /* ?? */
+ if(0 && p != Done)
+ return "i2c timed out";
+ sprint(up->genbuf, "i2c error: phase=%d status=%.4ux", p, s);
+ return up->genbuf;
+ }
+ return nil;
+}
+
+long
+i2csend(I2Cdev *d, void *buf, long n, ulong offset)
+{
+ Block *b;
+ char *e;
+
+ if(n <= 0)
+ return 0;
+ if(n > MaxIO)
+ n = MaxIO;
+
+ if(up){
+ b = allocb(n);
+ if(b == nil)
+ error(Enomem);
+ if(waserror()){
+ freeb(b);
+ nexterror();
+ }
+ }else{
+ b = iallocb(n);
+ if(b == nil)
+ return -1;
+ }
+ memmove(b->wp, buf, n);
+ b->wp += n;
+ e = startxfer(d, 0, b, 0, offset);
+ if(up)
+ poperror();
+ n -= BLEN(b); /* residue */
+ freeb(b);
+ if(e)
+ return i2cerror(e);
+ return n;
+}
+
+long
+i2crecv(I2Cdev *d, void *buf, long n, ulong offset)
+{
+ Block *b;
+ long nr;
+ char *e;
+
+ if(n <= 0)
+ return 0;
+ if(n > MaxIO)
+ n = MaxIO;
+
+ if(up){
+ b = allocb(n);
+ if(b == nil)
+ error(Enomem);
+ if(waserror()){
+ freeb(b);
+ nexterror();
+ }
+ }else{
+ b = iallocb(n);
+ if(b == nil)
+ return -1;
+ }
+ e = startxfer(d, Rbit, b, n, offset);
+ nr = BLEN(b);
+ if(nr > 0)
+ memmove(buf, b->rp, nr);
+ if(up)
+ poperror();
+ freeb(b);
+ if(e)
+ return i2cerror(e);
+ return nr;
+}
+
+/*
+ * the controller must be locked for the following functions
+ */
+
+static int
+readyxfer(Ctlr *ctlr, int phase)
+{
+ I2Cregs *i2c;
+
+ i2c = ctlr->regs;
+ if((i2c->isr & Bed) != 0){
+ failed(ctlr);
+ return 0;
+ }
+ ctlr->phase = phase;
+ return 1;
+}
+
+/*
+ * start a master transfer to receive the next byte of data
+ */
+static void
+rxstart(Ctlr *ctlr)
+{
+ Block *b;
+ int cntl;
+
+ b = ctlr->b;
+ if(b == nil || ctlr->rdcount<= 0){
+ done(ctlr);
+ return;
+ }
+ if(!readyxfer(ctlr, Read))
+ return;
+ cntl = Aldie | Tb;
+ if(ctlr->rdcount == 1)
+ cntl |= Stop | Nak | Iteie; /* last byte of transfer */
+ ctlr->regs->icr |= cntl;
+}
+
+/*
+ * start a master transfer to send the next chunk of data
+ */
+static void
+txstart(Ctlr *ctlr)
+{
+ Block *b;
+ int cntl;
+ long nb;
+ I2Cregs *i2c;
+
+ b = ctlr->b;
+ if(b == nil || (nb = BLEN(b)) <= 0){
+ done(ctlr);
+ return;
+ }
+ if(!readyxfer(ctlr, Write))
+ return;
+ i2c = ctlr->regs;
+ i2c->idbr = *b->rp++;
+ cntl = Aldie | Tb | Iteie;
+ if(nb == 1)
+ cntl |= Stop;
+ i2c->icr |= cntl;
+}
+
+/*
+ * stop a transfer if one is in progress
+ */
+static void
+stopxfer(Ctlr *ctlr)
+{
+ I2Cregs *i2c;
+
+ i2c = ctlr->regs;
+ if((i2c->isr & Ub) == 0){
+ ctlr->phase = Idle;
+ return;
+ }
+ if((i2c->isr & Ibb) == 0 && ctlr->phase != Halting){
+ ctlr->phase = Halting; /* interrupt will clear the state */
+ i2c->icr |= Ma;
+ }
+ /* if that doesn't clear it by the next operation, idlectlr will do so below */
+}
+
+static int
+idlectlr(Ctlr *ctlr)
+{
+ I2Cregs *i2c;
+
+ i2c = ctlr->regs;
+ if((i2c->isr & Ibb) == 0){
+ if((i2c->isr & Ub) == 0){
+ ctlr->phase = Idle;
+ return 1;
+ }
+ iprint("i2c: bus free, ctlr busy: isr=%.4lux icr=%.4lux\n", i2c->isr, i2c->icr);
+ }
+ /* hit it with the hammer, soft reset */
+ iprint("i2c: soft reset\n");
+ i2c->icr = Ur;
+ iunlock(ctlr);
+ delay(1);
+ ilock(ctlr);
+ initialise(i2c, !ctlr->polling);
+ ctlr->phase = Idle;
+ return (i2c->isr & (Ibb | Ub)) == 0;
+}