/*
* bcm2835 i2c controller
*
* Only i2c1 is supported.
* i2c2 is reserved for HDMI.
* i2c0 SDA0/SCL0 pins are not routed to P1 connector (except for early Rev 0 boards)
*/
#include "u.h"
#include "../port/lib.h"
#include "../port/error.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#define I2CREGS (VIRTIO+0x804000)
#define SDA0Pin 2
#define SCL0Pin 3
#define Alt0 0x4
typedef struct I2c I2c;
typedef struct Bsc Bsc;
/*
* Registers for Broadcom Serial Controller (i2c compatible)
*/
struct Bsc {
u32int ctrl;
u32int stat;
u32int dlen;
u32int addr;
u32int fifo;
u32int clkdiv; /* default 1500 => 100 KHz assuming 150Mhz input clock */
u32int delay; /* default (48<<16)|48 falling:rising edge */
u32int clktimeout; /* default 64 */
};
/*
* Per-controller info
*/
struct I2c {
QLock lock;
Lock reglock;
Rendez r;
Bsc *regs;
};
static I2c i2c;
enum {
/* ctrl */
I2cen = 1<<15,
Intr = 1<<10,
Intt = 1<<9,
Intd = 1<<8,
Start = 1<<7,
Clear = 1<<4,
Read = 1<<0,
Write = 0<<0,
/* stat */
Clkt = 1<<9,
Err = 1<<8,
Rxf = 1<<7,
Txe = 1<<6,
Rxd = 1<<5,
Txd = 1<<4,
Rxr = 1<<3,
Txw = 1<<2,
Done = 1<<1,
Ta = 1<<0,
};
static void
i2cinterrupt(Ureg*, void*)
{
Bsc *r;
int st;
r = i2c.regs;
st = 0;
if((r->ctrl & Intr) && (r->stat & Rxd))
st |= Intr;
if((r->ctrl & Intt) && (r->stat & Txd))
st |= Intt;
if(r->stat & Done)
st |= Intd;
if(st){
r->ctrl &= ~st;
wakeup(&i2c.r);
}
}
static int
i2cready(void *st)
{
return (i2c.regs->stat & (uintptr)st);
}
static void
i2cinit(void)
{
i2c.regs = (Bsc*)I2CREGS;
i2c.regs->clkdiv = 2500;
gpiosel(SDA0Pin, Alt0);
gpiosel(SCL0Pin, Alt0);
gpiopullup(SDA0Pin);
gpiopullup(SCL0Pin);
intrenable(IRQi2c, i2cinterrupt, 0, 0, "i2c");
}
static void
i2cio(int rw, uint addr, void *buf, int len)
{
Bsc *r;
uchar *p;
int st;
qlock(&i2c.lock);
if(i2c.regs == 0)
i2cinit();
r = i2c.regs;
p = buf;
r->ctrl = I2cen | Clear;
r->addr = addr;
r->dlen = len;
r->stat = Clkt|Err|Done;
r->ctrl = I2cen | Start | Intd | rw;
st = rw == Read? Rxd : Txd;
while(len > 0){
while((r->stat & (st|Done)) == 0){
r->ctrl |= rw == Read? Intr : Intt;
sleep(&i2c.r, i2cready, (void*)(st|Done));
}
if(r->stat & (Err|Clkt)){
qunlock(&i2c.lock);
error(Eio);
}
if(rw == Read){
do{
*p++ = r->fifo;
len--;
}while ((r->stat & Rxd) && len > 0);
}else{
do{
r->fifo = *p++;
len--;
}while((r->stat & Txd) && len > 0);
}
}
while((r->stat & Done) == 0)
sleep(&i2c.r, i2cready, (void*)Done);
if(r->stat & (Err|Clkt)){
qunlock(&i2c.lock);
error(Eio);
}
r->ctrl = 0;
qunlock(&i2c.lock);
}
void
i2cread(uint addr, void *buf, int len)
{
i2cio(Read, addr, buf, len);
}
void
i2cwrite(uint addr, void *buf, int len)
{
i2cio(Write, addr, buf, len);
}
|