#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 voidenable(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 voiddisable(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 */voidi2csetup(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 voidinterrupt(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 intdone(void *a){ return ((Ctlr*)a)->phase < Busy;} static voidi2cwait(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 inti2cerror(char *s){ if(up) error(s); /* no current process, don't call error */ DPRINT("i2c error: %s\n", s); return -1;} longi2csend(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;} longi2crecv(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;}
#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;
}