ref: 87d72e7e8614d96b4f61adae5fb05b0534231c4c
dir: /os/cerf405/iic.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 4xx IIC bus (master mode) * ``referred to as IIC to distinguish it from the Phillips IC bus [itself]'' * * TO DO: * power management ref count * power up/power down on timer? */ typedef struct Ctlr Ctlr; typedef struct IICregs IICregs; struct IICregs { uchar mdbuf; /* master data buffer */ uchar rsvd0; uchar sdbuf; /* slave data buffer */ uchar rsvd1; uchar lmadr; /* low master address */ uchar hmadr; /* high master address */ uchar cntl; /* control */ uchar mdcntl; /* mode control */ uchar sts; /* status */ uchar extsts; /* extended status */ uchar lsadr; /* low slave address */ uchar hsadr; /* high slave address */ uchar clkdiv; /* clock divide */ uchar intrmsk; /* interrupt mask */ uchar xfrcnt; /* transfer count */ uchar xtcntlss; /* extended control and slave status */ uchar directcntl; /* direct control */ }; enum { /* cntl */ Hmt= 1<<7, /* halt master transfer */ Amd10= 1<<6, /* =0, 7-bit; =1, 10-bit addressing */ /* 5,4: two bit transfer count (n-1)&3 bytes */ Rpst= 1<<3, /* =0, normal start; =1, repeated Start, transfer should be followed by another start */ Cht= 1<<2, /* chain transfer; not the last */ Write= 0<<1, /* transfer is a write */ Read= 1<<1, /* transfer is a read */ Pt= 1<<0, /* =0, most recent transfer complete; =1, start transfer if bus free */ /* mdcntl */ Fsdb= 1<<7, /* flush slave data buffer */ Fmdb= 1<<6, /* flush master data buffer */ Fsm= 1<<4, /* =0, 100 kHz standard mode; =1, 400 Khz fast mode */ Esm= 1<<3, /* enable slave mode */ Eint= 1<<2, /* enable interrupt */ Eubs= 1<<1, /* exit unknown bus state */ Hscl= 1<<0, /* hold IIC serial clock low */ /* sts */ Sss= 1<<7, /* slave status set (slave operation in progress) */ Slpr= 1<<6, /* sleep mode */ Mdbs= 1<<5, /* master data buffer has data */ Mdbf= 1<<4, /* master data buffer is full */ Scmp= 1<<3, /* stop complete */ Err= 1<<2, /* error set in extsts */ Irqa= 1<<1, /* IRQ active */ /* Pt as above */ /* extsts */ Irqp= 1<<7, /* IRQ pending */ Bcs= 7<<4, Bcs_ssel= 1<<4, /* slave-selected state */ Bcs_sio= 2<<4, /* slave transfer state */ Bcs_mio= 3<<4, /* master transfer state */ Bcs_free= 4<<4, /* bus is free */ Bcs_busy= 5<<4, /* bus is busy */ Bcs_gok= 6<<4, /* unknown state */ Irqd= 1<<3, /* IRQ on deck */ La= 1<<2, /* lost arbitration */ Ict= 1<<1, /* incomplete transfer */ Xfra= 1<<0, /* transfer aborted */ /* intrmsk */ Eirc= 1<<7, /* slave read complete */ Eirs= 1<<6, /* slave read needs service */ Eiwc= 1<<5, /* slave write complete */ Eiws= 1<<4, /* slave write needs service */ Eihe= 1<<3, /* halt executed */ Eiic= 1<<2, /* incomplete transfer */ Eita= 1<<1, /* transfer aborted */ Eimtc= 1<<0, /* master transfer complete */ /* xtcntlss */ Src= 1<<7, /* slave read complete; =1, NAK or Stop, or repeated Start ended op */ Srs= 1<<6, /* slave read needs service */ Swc= 1<<5, /* slave write complete */ Sws= 1<<4, /* slave write needs service */ Sdbd= 1<<3, /* slave buffer has data */ Sdbf= 1<<2, /* slave buffer is full */ Epi= 1<<1, /* enable pulsed IRQ on transfer aborted */ Srst= 1<<0, /* soft reset */ /* directcntl */ Sdac= 1<<3, /* SDA output */ Scc= 1<<2, /* SCL output */ Msda= 1<<1, /* SDA input */ Msc= 1<<0, /* SCL input */ /* others */ Rbit = 1<<0, /* bit in address byte denoting read */ FIFOsize= 4, /* most to be written at once */ 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 = 100000, I2Ctimeout = 125, /* msec (can change) */ 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 */ IICregs* regs; /* hardware registers */ /* controller state (see below) */ int status; int phase; Rendez r; /* transfer parameters */ int cntl; /* everything but transfer length */ int rdcount; /* requested read transfer size */ Block* b; }; enum { /* Ctlr.state */ Idle, Done, Failed, Busy, Halting, }; static Ctlr iicctlr[1]; static void interrupt(Ureg*, void*); static int readyxfer(Ctlr*); static void rxstart(Ctlr*); static void txstart(Ctlr*); static void stopxfer(Ctlr*); static void txoffset(Ctlr*, ulong, int); static int idlectlr(Ctlr*); static void iicdump(char *t, IICregs *iic) { iprint("iic %s: lma=%.2ux hma=%.2ux im=%.2ux mdcntl=%.2ux sts=%.2ux ests=%.2ux cntl=%.2ux\n", t, iic->lmadr, iic->hmadr, iic->intrmsk, iic->mdcntl, iic->sts, iic->extsts, iic->cntl); } static void initialise(IICregs *iic, int intrmsk) { int d; d = (m->opbhz-1000000)/10000000; if(d <= 0) d = 1; /* just in case OPB freq < 20 Mhz */ /* initialisation (see 22.4, p. 22-23) */ iic->lmadr = 0; iic->hmadr = 0; iic->sts = Scmp|Irqa; iic->extsts = Irqp | Irqd | La | Ict | Xfra; iic->clkdiv = d; iic->intrmsk = 0; /* see below */ iic->xfrcnt = 0; iic->xtcntlss = Src | Srs | Swc | Sws; iic->mdcntl = Fsdb | Fmdb | Eubs; /* reset; standard mode */ iic->cntl = 0; eieio(); iic->mdcntl = 0; eieio(); if(intrmsk){ iic->intrmsk = intrmsk; iic->mdcntl = Eint; } } /* * called by the reset routine of any driver using the IIC */ void i2csetup(int polling) { IICregs *iic; Ctlr *ctlr; ctlr = iicctlr; ctlr->polling = polling; iic = (IICregs*)KADDR(PHYSIIC); ctlr->regs = iic; if(!polling){ if(ctlr->init == 0){ initialise(iic, Eihe | Eiic | Eita | Eimtc); ctlr->init = 1; intrenable(VectorIIC, interrupt, iicctlr, BUSUNKNOWN, "iic"); } }else initialise(iic, 0); } static void interrupt(Ureg*, void *arg) { int sts, nb, ext, avail; Ctlr *ctlr; Block *b; IICregs *iic; ctlr = arg; iic = ctlr->regs; if(0) iicdump("intr", iic); sts = iic->sts; if(sts & Pt) iprint("iic: unexpected status: %.2ux", iic->sts); ext = iic->extsts; if(sts & Mdbs) nb = iic->xfrcnt & 7; else nb = 0; eieio(); iic->sts = sts; if(sts & Err && (ext & (La|Xfra)) != 0) iprint("iic: s=%.2ux es=%.2ux (IO)\n", sts, ext); ctlr->status = ext; switch(ctlr->phase){ default: iprint("iic: unexpected interrupt: p-%d s=%.2ux es=%.2ux\n", ctlr->phase, sts, ext); break; case Halting: ctlr->phase = Idle; break; case Busy: b = ctlr->b; if(b == nil) panic("iic: no buffer"); if(ctlr->cntl & Read){ /* copy data in from FIFO */ avail = b->lim - b->wp; if(nb > avail) nb = avail; while(--nb >= 0) *b->wp++ = iic->mdbuf; /* ``the IIC interface handles the [FIFO] latency'' (22-4) */ if(sts & Err || ctlr->rdcount <= 0){ ctlr->phase = Done; wakeup(&ctlr->r); break; } rxstart(ctlr); }else{ /* account for data transmitted */ if((b->rp += nb) > b->wp) b->rp = b->wp; if(sts & Err || BLEN(b) <= 0){ ctlr->phase = Done; wakeup(&ctlr->r); break; } txstart(ctlr); } } } static int done(void *a) { return ((Ctlr*)a)->phase < Busy; } static int i2cerror(char *s) { DPRINT("iic error: %s\n", s); if(up) error(s); /* no current process, don't call error */ return -1; } static char* startxfer(I2Cdev *d, int op, void (*xfer)(Ctlr*), Block *b, int n, ulong offset) { IICregs *iic; Ctlr *ctlr; int i, cntl, p, s; ctlr = iicctlr; 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("iic: ctlr busy"); cntl = op | Pt; if(d->tenbit) cntl |= Amd10; ctlr->cntl = cntl; ctlr->b = b; ctlr->rdcount = n; ctlr->phase = Busy; iic = ctlr->regs; if(d->tenbit){ iic->hmadr = 0xF0 | (d->addr>>7); /* 2 higher bits of address, LSB don't care */ iic->lmadr = d->addr; }else{ iic->hmadr = 0; iic->lmadr = d->addr<<1; /* 7-bit address */ } if(d->salen) txoffset(ctlr, offset, d->salen); else (*xfer)(ctlr); iunlock(ctlr); /* wait for it */ if(ctlr->polling){ for(i=0; !done(ctlr); i++){ delay(2); interrupt(nil, ctlr); } }else tsleep(&ctlr->r, done, 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 & (La|Xfra)){ /* CHECK; time out */ if(s & La) return "iic lost arbitration"; if(s & Xfra) return "iic transfer aborted"; if(p != Done) return "iic timed out"; sprint(up->genbuf, "iic error: phase=%d estatus=%.2ux", 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, Write, txstart, 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, Read, rxstart, 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) { IICregs *iic; iic = ctlr->regs; iic->sts = Scmp | Err; if((iic->sts & Pt) != 0){ ctlr->phase = Failed; wakeup(&ctlr->r); return 0; } iic->mdcntl |= Fmdb; return 1; } /* * start a master transfer to receive the next chunk of data */ static void rxstart(Ctlr *ctlr) { Block *b; int cntl; long nb; b = ctlr->b; if(b == nil || (nb = ctlr->rdcount) <= 0){ ctlr->phase = Done; wakeup(&ctlr->r); return; } if(!readyxfer(ctlr)) return; cntl = ctlr->cntl; if(nb > FIFOsize){ nb = FIFOsize; cntl |= Cht; /* more to come */ } ctlr->rdcount -= nb; ctlr->regs->cntl = cntl | ((nb-1)<<4); } /* * start a master transfer to send the next chunk of data */ static void txstart(Ctlr *ctlr) { Block *b; int cntl, i; long nb; IICregs *iic; b = ctlr->b; if(b == nil || (nb = BLEN(b)) <= 0){ ctlr->phase = Done; wakeup(&ctlr->r); return; } if(!readyxfer(ctlr)) return; cntl = ctlr->cntl; if(nb > FIFOsize){ nb = FIFOsize; cntl |= Cht; /* more to come */ } iic = ctlr->regs; for(i=0; i<nb; i++) iic->mdbuf = *b->rp++; /* load the FIFO */ iic->cntl = cntl | ((nb-1)<<4); } /* * start a master transfer to send a sub-addressing offset; * if subsequently receiving, use Rpst to cause the next transfer to include a Start; * if subsequently sending, use Cht to chain the transfer without a Start. */ static void txoffset(Ctlr *ctlr, ulong offset, int len) { int i, cntl; IICregs *iic; if(!readyxfer(ctlr)) return; iic = ctlr->regs; for(i=len*8; (i -= 8) >= 0;) iic->mdbuf = offset>>i; /* load offset bytes into FIFO */ cntl = ctlr->cntl & Amd10; if(ctlr->cntl & Read) cntl |= Rpst; else cntl |= Cht; iic->cntl = cntl | ((len-1)<<4) | Write | Pt; } /* * stop a transfer if one is in progress */ static void stopxfer(Ctlr *ctlr) { IICregs *iic; int ext; iic = ctlr->regs; ext = iic->extsts; eieio(); iic->sts = Scmp | Irqa; eieio(); if((iic->sts & Pt) == 0){ ctlr->phase = Idle; return; } if((ext & Bcs) == Bcs_mio && ctlr->phase != Halting){ ctlr->phase = Halting; /* interrupt will clear the state */ iic->cntl = Hmt; } } static int idlectlr(Ctlr *ctlr) { IICregs *iic; iic = ctlr->regs; if((iic->extsts & Bcs) == Bcs_free){ if((iic->sts & Pt) == 0){ ctlr->phase = Idle; return 1; } iprint("iic: bus free, ctlr busy: s=%.2ux es=%.2ux\n", iic->sts, iic->extsts); } /* hit it with the hammer, soft reset */ iprint("iic: soft reset\n"); iic->xtcntlss = Srst; iunlock(ctlr); delay(1); ilock(ctlr); initialise(iic, Eihe | Eiic | Eita | Eimtc); ctlr->phase = Idle; return (iic->extsts & Bcs) == Bcs_free && (iic->sts & Pt) == 0; }