123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 |
- /*
- * basic read/write interface to PXA25x IC 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;
- }
|