devi2c: add generic i2c bus driver
This commit is contained in:
parent
3e176bd975
commit
276f2039a9
2 changed files with 653 additions and 0 deletions
597
sys/src/9/port/devi2c.c
Normal file
597
sys/src/9/port/devi2c.c
Normal file
|
@ -0,0 +1,597 @@
|
|||
/* I²C bus driver */
|
||||
#include "u.h"
|
||||
#include "../port/lib.h"
|
||||
#include "mem.h"
|
||||
#include "dat.h"
|
||||
#include "fns.h"
|
||||
#include "../port/error.h"
|
||||
#include "../port/i2c.h"
|
||||
|
||||
enum {
|
||||
Qdir = 0, /* #J */
|
||||
Qbus, /* #J/bus */
|
||||
Qctl, /* #J/bus/i2c.n.ctl */
|
||||
Qdata, /* #J/bus/i2c.n.data */
|
||||
};
|
||||
|
||||
#define TYPE(q) ((ulong)(q).path & 0x0F)
|
||||
#define BUS(q) (((ulong)(q).path>>4) & 0xFF)
|
||||
#define DEV(q) (((ulong)(q).path>>12) & 0xFFF)
|
||||
#define QID(d, b, t) (((d)<<12)|((b)<<4)|(t))
|
||||
|
||||
static I2Cbus *buses[16];
|
||||
|
||||
static I2Cdev *devs[1024];
|
||||
static Lock devslock;
|
||||
|
||||
static void probebus(I2Cbus *bus);
|
||||
|
||||
void
|
||||
addi2cbus(I2Cbus *bus)
|
||||
{
|
||||
int i;
|
||||
|
||||
if(bus == nil)
|
||||
return;
|
||||
|
||||
for(i = 0; i < nelem(buses); i++){
|
||||
if(buses[i] == nil
|
||||
|| buses[i] == bus
|
||||
|| strcmp(bus->name, buses[i]->name) == 0){
|
||||
buses[i] = bus;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
I2Cbus*
|
||||
i2cbus(char *name)
|
||||
{
|
||||
I2Cbus *bus;
|
||||
int i;
|
||||
|
||||
for(i = 0; i < nelem(buses); i++){
|
||||
bus = buses[i];
|
||||
if(bus == nil)
|
||||
break;
|
||||
if(strcmp(bus->name, name) == 0){
|
||||
probebus(bus);
|
||||
return bus;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
void
|
||||
addi2cdev(I2Cdev *dev)
|
||||
{
|
||||
int i;
|
||||
|
||||
if(dev == nil || dev->bus == nil)
|
||||
return;
|
||||
|
||||
lock(&devslock);
|
||||
for(i = 0; i < nelem(devs); i++){
|
||||
if(devs[i] == nil
|
||||
|| devs[i] == dev
|
||||
|| devs[i]->addr == dev->addr && devs[i]->bus == dev->bus){
|
||||
devs[i] = dev;
|
||||
unlock(&devslock);
|
||||
return;
|
||||
}
|
||||
}
|
||||
unlock(&devslock);
|
||||
}
|
||||
|
||||
I2Cdev*
|
||||
i2cdev(I2Cbus *bus, int addr)
|
||||
{
|
||||
I2Cdev *dev;
|
||||
int i;
|
||||
|
||||
if(bus == nil || addr < 0 || addr >= 1<<10)
|
||||
return nil;
|
||||
|
||||
lock(&devslock);
|
||||
for(i = 0; i < nelem(devs) && (dev = devs[i]) != nil; i++){
|
||||
if(dev->addr == addr && dev->bus == bus){
|
||||
unlock(&devslock);
|
||||
return dev;
|
||||
}
|
||||
}
|
||||
unlock(&devslock);
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
enterbus(I2Cbus *bus)
|
||||
{
|
||||
if(up != nil && islo()){
|
||||
eqlock(bus);
|
||||
return 1;
|
||||
} else {
|
||||
while(!canqlock(bus))
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
leavebus(I2Cbus *bus)
|
||||
{
|
||||
qunlock(bus);
|
||||
}
|
||||
|
||||
int
|
||||
i2cbusio(I2Cbus *bus, uchar *pkt, int olen, int ilen)
|
||||
{
|
||||
int user, n;
|
||||
|
||||
user = enterbus(bus);
|
||||
if(!bus->probed){
|
||||
leavebus(bus);
|
||||
return -1;
|
||||
}
|
||||
if(user && waserror()){
|
||||
(*bus->io)(bus, nil, 0, 0);
|
||||
leavebus(bus);
|
||||
nexterror();
|
||||
}
|
||||
// iprint("%s: <- %.*H\n", bus->name, olen, pkt);
|
||||
n = (*bus->io)(bus, pkt, olen, ilen);
|
||||
// if(n > olen) iprint("%s: -> %.*H\n", bus->name, n - olen, pkt+olen);
|
||||
|
||||
leavebus(bus);
|
||||
if(user) poperror();
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static int
|
||||
putaddr(I2Cdev *dev, int isread, uchar *pkt, vlong addr)
|
||||
{
|
||||
int n, o = 0;
|
||||
|
||||
if(dev->a10){
|
||||
pkt[o++] = 0xF0 | (dev->addr>>(8-1))&6 | (isread != 0);
|
||||
pkt[o++] = dev->addr;
|
||||
} else
|
||||
pkt[o++] = dev->addr<<1 | (isread != 0);
|
||||
|
||||
if(addr >= 0){
|
||||
for(n=0; n<dev->subaddr; n++)
|
||||
pkt[o++] = addr >> (n*8);
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
int
|
||||
i2csend(I2Cdev *dev, void *data, int len, vlong addr)
|
||||
{
|
||||
uchar pkt[138];
|
||||
int o;
|
||||
|
||||
o = putaddr(dev, 0, pkt, addr);
|
||||
if(o+len > sizeof(pkt))
|
||||
len = sizeof(pkt)-o;
|
||||
|
||||
if(len > 0)
|
||||
memmove(pkt+o, data, len);
|
||||
|
||||
return i2cbusio(dev->bus, pkt, o + len, 0) - o;
|
||||
}
|
||||
|
||||
int
|
||||
i2crecv(I2Cdev *dev, void *data, int len, vlong addr)
|
||||
{
|
||||
uchar pkt[138];
|
||||
int o;
|
||||
|
||||
o = putaddr(dev, 1, pkt, addr);
|
||||
if(o+len > sizeof(pkt))
|
||||
len = sizeof(pkt)-o;
|
||||
|
||||
len = i2cbusio(dev->bus, pkt, o, len) - o;
|
||||
if(len > 0)
|
||||
memmove(data, pkt+o, len);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int
|
||||
i2cquick(I2Cdev *dev, int rw)
|
||||
{
|
||||
uchar pkt[2];
|
||||
int o = putaddr(dev, rw, pkt, -1);
|
||||
if(i2cbusio(dev->bus, pkt, o, 0) != o)
|
||||
return -1;
|
||||
return rw != 0;
|
||||
}
|
||||
int
|
||||
i2crecvbyte(I2Cdev *dev)
|
||||
{
|
||||
uchar pkt[2+1];
|
||||
int o = putaddr(dev, 1, pkt, -1);
|
||||
if(i2cbusio(dev->bus, pkt, o, 1) - o != 1)
|
||||
return -1;
|
||||
return pkt[o];
|
||||
}
|
||||
int
|
||||
i2csendbyte(I2Cdev *dev, uchar b)
|
||||
{
|
||||
uchar pkt[2+1];
|
||||
int o = putaddr(dev, 0, pkt, -1);
|
||||
pkt[o] = b;
|
||||
if(i2cbusio(dev->bus, pkt, o+1, 0) - o != 1)
|
||||
return -1;
|
||||
return b;
|
||||
}
|
||||
int
|
||||
i2creadbyte(I2Cdev *dev, ulong addr)
|
||||
{
|
||||
uchar pkt[2+4+1];
|
||||
int o = putaddr(dev, 1, pkt, addr);
|
||||
if(i2cbusio(dev->bus, pkt, o, 1) - o != 1)
|
||||
return -1;
|
||||
return pkt[o];
|
||||
}
|
||||
int
|
||||
i2cwritebyte(I2Cdev *dev, ulong addr, uchar b)
|
||||
{
|
||||
uchar pkt[2+4+1];
|
||||
int o = putaddr(dev, 0, pkt, addr);
|
||||
pkt[o] = b;
|
||||
if(i2cbusio(dev->bus, pkt, o+1, 0) - o != 1)
|
||||
return -1;
|
||||
return b;
|
||||
}
|
||||
int
|
||||
i2creadword(I2Cdev *dev, ulong addr)
|
||||
{
|
||||
uchar pkt[2+4+2];
|
||||
int o = putaddr(dev, 1, pkt, addr);
|
||||
if(i2cbusio(dev->bus, pkt, o, 2) - o != 2)
|
||||
return -1;
|
||||
return pkt[o] | (ushort)pkt[o+1]<<8;
|
||||
}
|
||||
int
|
||||
i2cwriteword(I2Cdev *dev, ulong addr, ushort w)
|
||||
{
|
||||
uchar pkt[2+4+2];
|
||||
int o = putaddr(dev, 0, pkt, addr);
|
||||
pkt[o+0] = w;
|
||||
pkt[o+1] = w>>8;
|
||||
if(i2cbusio(dev->bus, pkt, o+2, 0) - o != 2)
|
||||
return -1;
|
||||
return w;
|
||||
}
|
||||
vlong
|
||||
i2cread32(I2Cdev *dev, ulong addr)
|
||||
{
|
||||
uchar pkt[2+4+4];
|
||||
int o = putaddr(dev, 1, pkt, addr);
|
||||
if(i2cbusio(dev->bus, pkt, o, 4) - o != 4)
|
||||
return -1;
|
||||
return pkt[o] | (ulong)pkt[o+1]<<8 | (ulong)pkt[o+2]<<16 | (ulong)pkt[o+3]<<24;
|
||||
}
|
||||
vlong
|
||||
i2cwrite32(I2Cdev *dev, ulong addr, ulong u)
|
||||
{
|
||||
uchar pkt[2+4+4];
|
||||
int o = putaddr(dev, 0, pkt, addr);
|
||||
pkt[o+0] = u;
|
||||
pkt[o+1] = u>>8;
|
||||
pkt[o+2] = u>>16;
|
||||
pkt[o+3] = u>>24;
|
||||
if(i2cbusio(dev->bus, pkt, o+4, 0) - o != 4)
|
||||
return -1;
|
||||
return u;
|
||||
}
|
||||
|
||||
static void
|
||||
probeddev(I2Cdev *dummy)
|
||||
{
|
||||
I2Cdev *dev = smalloc(sizeof(I2Cdev));
|
||||
memmove(dev, dummy, sizeof(I2Cdev));
|
||||
addi2cdev(dev);
|
||||
}
|
||||
|
||||
static void
|
||||
probebus(I2Cbus *bus)
|
||||
{
|
||||
I2Cdev dummy;
|
||||
uchar pkt[2];
|
||||
int user, n;
|
||||
|
||||
if(bus->probed)
|
||||
return;
|
||||
|
||||
user = enterbus(bus);
|
||||
if(bus->probed){
|
||||
leavebus(bus);
|
||||
return;
|
||||
}
|
||||
if(user && waserror()
|
||||
|| (*bus->init)(bus)){
|
||||
leavebus(bus);
|
||||
if(user) nexterror();
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&dummy, 0, sizeof(dummy));
|
||||
dummy.bus = bus;
|
||||
|
||||
dummy.a10 = 0;
|
||||
for(dummy.addr = 8; dummy.addr < 0x78; dummy.addr++) {
|
||||
if(i2cdev(bus, dummy.addr) != nil)
|
||||
continue;
|
||||
if(user && waserror()){
|
||||
(*bus->io)(bus, nil, 0, 0);
|
||||
continue;
|
||||
}
|
||||
n = putaddr(&dummy, 0, pkt, -1);
|
||||
if((*bus->io)(bus, pkt, n, 0) == n)
|
||||
probeddev(&dummy);
|
||||
if(user) poperror();
|
||||
}
|
||||
|
||||
dummy.a10 = 1;
|
||||
for(dummy.addr = 0; dummy.addr < (1<<10); dummy.addr++) {
|
||||
if(i2cdev(bus, dummy.addr) != nil)
|
||||
continue;
|
||||
if(user && waserror()){
|
||||
(*bus->io)(bus, nil, 0, 0);
|
||||
continue;
|
||||
}
|
||||
n = putaddr(&dummy, 0, pkt, -1);
|
||||
if((*bus->io)(bus, pkt, n, 0) == n)
|
||||
probeddev(&dummy);
|
||||
if(user) poperror();
|
||||
}
|
||||
|
||||
bus->probed = 1;
|
||||
leavebus(bus);
|
||||
if(user) poperror();
|
||||
}
|
||||
|
||||
static int
|
||||
i2cgen(Chan *c, char *, Dirtab*, int, int s, Dir *dp)
|
||||
{
|
||||
I2Cbus *bus;
|
||||
I2Cdev *dev;
|
||||
|
||||
Qid q;
|
||||
|
||||
switch(TYPE(c->qid)){
|
||||
case Qdir:
|
||||
if(s == DEVDOTDOT){
|
||||
Gendir:
|
||||
mkqid(&q, QID(0, 0, Qdir), 0, QTDIR);
|
||||
snprint(up->genbuf, sizeof up->genbuf, "#J");
|
||||
devdir(c, q, up->genbuf, 0, eve, 0500, dp);
|
||||
return 1;
|
||||
}
|
||||
if(s >= nelem(buses))
|
||||
return -1;
|
||||
bus = buses[s];
|
||||
if(bus == nil)
|
||||
return -1;
|
||||
mkqid(&q, QID(0, s, Qbus), 0, QTDIR);
|
||||
devdir(c, q, bus->name, 0, eve, 0500, dp);
|
||||
return 1;
|
||||
case Qbus:
|
||||
if(s == DEVDOTDOT)
|
||||
goto Gendir;
|
||||
if((s/2) >= nelem(devs))
|
||||
return -1;
|
||||
|
||||
bus = buses[BUS(c->qid)];
|
||||
probebus(bus);
|
||||
|
||||
lock(&devslock);
|
||||
dev = devs[s/2];
|
||||
unlock(&devslock);
|
||||
|
||||
if(dev == nil)
|
||||
return -1;
|
||||
if(dev->bus != bus)
|
||||
return 0;
|
||||
if(s & 1){
|
||||
mkqid(&q, QID(dev->addr, BUS(c->qid), Qdata), 0, 0);
|
||||
goto Gendata;
|
||||
}
|
||||
mkqid(&q, QID(dev->addr, BUS(c->qid), Qctl), 0, 0);
|
||||
goto Genctl;
|
||||
case Qctl:
|
||||
q = c->qid;
|
||||
Genctl:
|
||||
snprint(up->genbuf, sizeof up->genbuf, "i2c.%lux.ctl", DEV(q));
|
||||
devdir(c, q, up->genbuf, 0, eve, 0600, dp);
|
||||
return 1;
|
||||
case Qdata:
|
||||
q = c->qid;
|
||||
bus = buses[BUS(q)];
|
||||
dev = i2cdev(bus, DEV(q));
|
||||
if(dev == nil)
|
||||
return -1;
|
||||
Gendata:
|
||||
snprint(up->genbuf, sizeof up->genbuf, "i2c.%lux.data", DEV(q));
|
||||
devdir(c, q, up->genbuf, dev->size, eve, 0600, dp);
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static Chan*
|
||||
i2cattach(char *spec)
|
||||
{
|
||||
return devattach('J', spec);
|
||||
}
|
||||
|
||||
static Chan*
|
||||
i2copen(Chan *c, int mode)
|
||||
{
|
||||
c = devopen(c, mode, nil, 0, i2cgen);
|
||||
switch(TYPE(c->qid)){
|
||||
case Qctl:
|
||||
case Qdata:
|
||||
c->aux = i2cdev(buses[BUS(c->qid)], DEV(c->qid));
|
||||
if(c->aux == nil)
|
||||
error(Enonexist);
|
||||
break;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
enum {
|
||||
CMsize,
|
||||
CMsubaddress,
|
||||
};
|
||||
|
||||
static Cmdtab i2cctlmsg[] =
|
||||
{
|
||||
CMsize, "size", 2,
|
||||
CMsubaddress, "subaddress", 2,
|
||||
};
|
||||
|
||||
static long
|
||||
i2cwrctl(I2Cdev *dev, void *data, long len)
|
||||
{
|
||||
Cmdbuf *cb;
|
||||
Cmdtab *ct;
|
||||
ulong u;
|
||||
|
||||
cb = parsecmd(data, len);
|
||||
if(waserror()){
|
||||
free(cb);
|
||||
nexterror();
|
||||
}
|
||||
ct = lookupcmd(cb, i2cctlmsg, nelem(i2cctlmsg));
|
||||
switch(ct->index){
|
||||
case CMsize:
|
||||
dev->size = strtoul(cb->f[1], nil, 0);
|
||||
break;
|
||||
case CMsubaddress:
|
||||
u = strtoul(cb->f[1], nil, 0);
|
||||
if(u > 4)
|
||||
cmderror(cb, Ebadarg);
|
||||
dev->subaddr = u;
|
||||
break;
|
||||
default:
|
||||
cmderror(cb, Ebadarg);
|
||||
}
|
||||
free(cb);
|
||||
poperror();
|
||||
return len;
|
||||
}
|
||||
|
||||
static long
|
||||
i2crdctl(I2Cdev *dev, void *data, long len, vlong offset)
|
||||
{
|
||||
char cfg[64];
|
||||
|
||||
snprint(cfg, sizeof(cfg), "size %lud\nsubaddress %d\n", dev->size, dev->subaddr);
|
||||
return readstr((ulong)offset, data, len, cfg);
|
||||
}
|
||||
|
||||
static long
|
||||
i2cwrite(Chan *c, void *data, long len, vlong offset)
|
||||
{
|
||||
I2Cdev *dev;
|
||||
|
||||
switch(TYPE(c->qid)){
|
||||
default:
|
||||
error(Egreg);
|
||||
return -1;
|
||||
case Qctl:
|
||||
dev = c->aux;
|
||||
return i2cwrctl(dev, data, len);
|
||||
case Qdata:
|
||||
break;
|
||||
}
|
||||
dev = c->aux;
|
||||
if(dev->size){
|
||||
if(offset+len > dev->size){
|
||||
if(offset >= dev->size)
|
||||
return 0;
|
||||
len = dev->size - offset;
|
||||
}
|
||||
}
|
||||
len = i2csend(dev, data, len, offset);
|
||||
if(len < 0)
|
||||
error(Eio);
|
||||
return len;
|
||||
}
|
||||
|
||||
static long
|
||||
i2cread(Chan *c, void *data, long len, vlong offset)
|
||||
{
|
||||
I2Cdev *dev;
|
||||
|
||||
if(c->qid.type == QTDIR)
|
||||
return devdirread(c, data, len, nil, 0, i2cgen);
|
||||
|
||||
switch(TYPE(c->qid)){
|
||||
default:
|
||||
error(Egreg);
|
||||
case Qctl:
|
||||
dev = c->aux;
|
||||
return i2crdctl(dev, data, len, offset);
|
||||
case Qdata:
|
||||
break;
|
||||
}
|
||||
dev = c->aux;
|
||||
if(dev->size){
|
||||
if(offset+len > dev->size){
|
||||
if(offset >= dev->size)
|
||||
return 0;
|
||||
len = dev->size - offset;
|
||||
}
|
||||
}
|
||||
len = i2crecv(dev, data, len, offset);
|
||||
if(len < 0)
|
||||
error(Eio);
|
||||
return len;
|
||||
}
|
||||
|
||||
void
|
||||
i2cclose(Chan*)
|
||||
{
|
||||
}
|
||||
|
||||
static Walkqid*
|
||||
i2cwalk(Chan *c, Chan *nc, char **name, int nname)
|
||||
{
|
||||
return devwalk(c, nc, name, nname, nil, 0, i2cgen);
|
||||
}
|
||||
|
||||
static int
|
||||
i2cstat(Chan *c, uchar *dp, int n)
|
||||
{
|
||||
return devstat(c, dp, n, nil, 0, i2cgen);
|
||||
}
|
||||
|
||||
Dev i2cdevtab = {
|
||||
'J',
|
||||
"i2c",
|
||||
|
||||
devreset,
|
||||
devinit,
|
||||
devshutdown,
|
||||
i2cattach,
|
||||
i2cwalk,
|
||||
i2cstat,
|
||||
i2copen,
|
||||
devcreate,
|
||||
i2cclose,
|
||||
i2cread,
|
||||
devbread,
|
||||
i2cwrite,
|
||||
devbwrite,
|
||||
devremove,
|
||||
devwstat,
|
||||
devpower,
|
||||
};
|
56
sys/src/9/port/i2c.h
Normal file
56
sys/src/9/port/i2c.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
typedef struct I2Cbus I2Cbus;
|
||||
struct I2Cbus
|
||||
{
|
||||
char *name;
|
||||
int speed;
|
||||
|
||||
void *ctlr;
|
||||
int (*init)(I2Cbus *bus);
|
||||
int (*io)(I2Cbus *bus, uchar *pkt, int olen, int ilen);
|
||||
|
||||
int probed;
|
||||
QLock;
|
||||
};
|
||||
|
||||
typedef struct I2Cdev I2Cdev;
|
||||
struct I2Cdev
|
||||
{
|
||||
I2Cbus *bus;
|
||||
|
||||
int a10;
|
||||
int addr;
|
||||
int subaddr;
|
||||
ulong size;
|
||||
};
|
||||
|
||||
/*
|
||||
* Register busses (controllers) and devices (addresses)
|
||||
*/
|
||||
extern void addi2cbus(I2Cbus *bus);
|
||||
extern void addi2cdev(I2Cdev *dev);
|
||||
|
||||
/*
|
||||
* Look-up busses and devices by name and address
|
||||
*/
|
||||
extern I2Cbus* i2cbus(char *name);
|
||||
extern I2Cdev* i2cdev(I2Cbus *bus, int addr);
|
||||
|
||||
/*
|
||||
* generic I/O
|
||||
*/
|
||||
extern int i2cbusio(I2Cbus *bus, uchar *pkt, int olen, int ilen);
|
||||
extern int i2crecv(I2Cdev *dev, void *data, int len, vlong addr);
|
||||
extern int i2csend(I2Cdev *dev, void *data, int len, vlong addr);
|
||||
|
||||
/*
|
||||
* common I/O for SMbus
|
||||
*/
|
||||
extern int i2cquick(I2Cdev *dev, int rw);
|
||||
extern int i2crecvbyte(I2Cdev *dev);
|
||||
extern int i2csendbyte(I2Cdev *dev, uchar b);
|
||||
extern int i2creadbyte(I2Cdev *dev, ulong addr);
|
||||
extern int i2cwritebyte(I2Cdev *dev, ulong addr, uchar b);
|
||||
extern int i2creadword(I2Cdev *dev, ulong addr);
|
||||
extern int i2cwriteword(I2Cdev *dev, ulong addr, ushort w);
|
||||
extern vlong i2cread32(I2Cdev *dev, ulong addr);
|
||||
extern vlong i2cwrite32(I2Cdev *dev, ulong addr, ulong u);
|
Loading…
Reference in a new issue