ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
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;
}