ref: 536478cdbe30f61b423c8fbe2aeebbd2c26b517f
dir: /sys/src/9/bcm/devgpio.c/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "io.h"
// path:
// 3 bits - generic file type (Qinctl, Qindata)
// 3 bits - parent type
// 3 bits - chosen scheme type (Qgeneric, Qbcm, Qboard, Qwpi)
// 6 bits - input number
#define PIN_TABLE_SIZE 32
#define PIN_OFFSET SCHEME_OFFSET + SCHEME_BITS
#define PIN_BITS 6
#define PIN_MASK ((1 << PIN_BITS) - 1)
#define PIN_NUMBER(q) (((q).path >> PIN_OFFSET) & PIN_MASK)
#define SCHEME_OFFSET PARENT_OFFSET + PARENT_BITS
#define SCHEME_BITS 3
#define SCHEME_MASK ((1 << SCHEME_BITS) - 1)
#define SCHEME_TYPE(q) (((q).path >> SCHEME_OFFSET) & SCHEME_MASK)
#define PARENT_OFFSET FILE_OFFSET + FILE_BITS
#define PARENT_BITS 3
#define PARENT_MASK ((1 << PARENT_BITS) - 1)
#define PARENT_TYPE(q) (((q).path >> PARENT_OFFSET) & PARENT_MASK)
#define FILE_OFFSET 0
#define FILE_BITS 3
#define FILE_MASK ((1 << FILE_BITS) - 1)
#define FILE_TYPE(q) (((q).path >> FILE_OFFSET) & FILE_MASK)
// pin is valid only when file is Qdata otherwise 0 is used
#define PATH(pin, scheme, parent, file) \
((pin & PIN_MASK) << PIN_OFFSET) \
| ((scheme & SCHEME_MASK) << SCHEME_OFFSET) \
| ((parent & PARENT_MASK) << PARENT_OFFSET) \
| ((file & FILE_MASK) << FILE_OFFSET)
#define SET_BIT(f, offset, value) \
(*f = ((*f & ~(1 << (offset % 32))) | (value << (offset % 32))))
static int dflag = 0;
#define D(...) if(dflag) print(__VA_ARGS__)
enum {
// parent types
Qtopdir = 0,
Qgpiodir,
// file types
Qdir,
Qdata,
Qctl,
Qevent,
};
enum {
// naming schemes
Qbcm,
Qboard,
Qwpi,
Qgeneric
};
// commands
enum {
CMzero,
CMone,
CMscheme,
CMfunc,
CMpull,
CMevent,
};
// dev entries
Dirtab topdir = { "#G", {PATH(0, Qgeneric, Qtopdir, Qdir), 0, QTDIR}, 0, 0555 };
Dirtab gpiodir = { "gpio", {PATH(0, Qgeneric, Qgpiodir, Qdir), 0, QTDIR}, 0, 0555 };
Dirtab typedir[] = {
"OK", { PATH(16, Qgeneric, Qgpiodir, Qdata), 0, QTFILE }, 0, 0666,
"ctl", { PATH(0, Qgeneric, Qgpiodir, Qctl), 0, QTFILE }, 0, 0666,
"event", { PATH(0, Qgeneric, Qgpiodir, Qevent), 0, QTFILE }, 0, 0444,
};
// commands definition
static
Cmdtab gpiocmd[] = {
CMzero, "0", 1,
CMone, "1", 1,
CMscheme, "scheme", 2,
CMfunc, "function", 3,
CMpull, "pull", 3,
CMevent, "event", 4,
};
static int pinscheme;
static int boardrev;
static Rendez rend;
static u32int eventvalue;
static long eventinuse;
static Lock eventlock;
//
// BCM
//
enum {
Fin = 0,
Fout,
Ffunc5,
Ffunc4,
Ffunc0,
Ffunc1,
Ffunc2,
Ffunc3,
};
static char *funcname[] = {
"in", "out", "5", "4", "0", "1", "2", "3",
};
enum {
Poff = 0,
Pdown,
Pup,
};
static char *pudname[] = {
"off", "down", "up",
};
static char *evstatename[] = {
"disable", "enable",
};
enum {
Erising,
Efalling,
};
static char *evtypename[] = {
"edge-rising", "edge-falling",
};
static char *bcmtableR1[PIN_TABLE_SIZE] = {
"1", "2", 0, 0, // 0-3
"4", "5", "6", "7", // 4-7
"8", "9", "10", "11", // 8-11
0, 0, "14", "15", // 12-15
0, "17", "18", 0, // 16-19
0, "21", "22", "23", // 20-23
"24", "25", 0, 0, // 24-27
0, 0, 0, 0, // 28-31
};
static char *bcmtableR2[PIN_TABLE_SIZE] = {
0, 0, "2", "3", // 0-3
"4", 0, 0, "7", // 4-7
"8", "9", "10", "11", // 8-11
0, 0, "14", "15", // 12-15
0, "17", "18", 0, // 16-19
0, 0, "22", "23", // 20-23
"24", "25", 0, "27", // 24-27
"28", "29", "30", "31", // 28-31
};
static char *boardtableR1[PIN_TABLE_SIZE] = {
"SDA", "SCL", 0, 0, // 0-3
"GPIO7", 0, 0, "CE1", // 4-7
"CE0", "MISO", "MOSI", "SCLK", // 8-11
0, 0, "TxD", "RxD", // 12-15
0, "GPIO0", "GPIO1", 0, // 16-19
0, "GPIO2", "GPIO3", "GPIO4", // 20-23
"GPIO5", "GPIO6", 0, 0, // 24-27
0, 0, 0, 0, // 28-31
};
static char *boardtableR2[PIN_TABLE_SIZE] = {
0, 0, "SDA", "SCL", // 0-3
"GPIO7", 0, 0, "CE1", // 4-7
"CE0", "MISO", "MOSI", "SCLK", // 8-11
0, 0, "TxD", "RxD", // 12-15
0, "GPIO0", "GPIO1", 0, // 16-19
0, 0, "GPIO3", "GPIO4", // 20-23
"GPIO5", "GPIO6", 0, "GPIO2", // 24-27
"GPIO8", "GPIO9", "GPIO10", "GPIO11", // 28-31
};
static char *wpitableR1[PIN_TABLE_SIZE] = {
"8", "9", 0, 0, // 0-3
"7", 0, 0, "11", // 4-7
"10", "13", "12", "14", // 8-11
0, 0, "15", "16", // 12-15
0, "0", "1", 0, // 16-19
0, "2", "3", "4", // 20-23
"5", "6", 0, 0, // 24-27
0, 0, 0, 0, // 28-31
};
static char *wpitableR2[PIN_TABLE_SIZE] = {
0, 0, "8", "9", // 0-3
"7", 0, 0, "11", // 4-7
"10", "13", "12", "14", // 8-11
0, 0, "15", "16", // 12-15
0, "0", "1", 0, // 16-19
0, 0, "3", "4", // 20-23
"5", "6", 0, "2", // 24-27
"17", "18", "19", "20", // 28-31
};
static char *schemename[] = {
"bcm", "board", "wpi",
};
static char**
getpintable(void)
{
switch(pinscheme)
{
case Qbcm:
return (boardrev>3)?bcmtableR2:bcmtableR1;
case Qboard:
return (boardrev>3)?boardtableR2:boardtableR1;
case Qwpi:
return (boardrev>3)?wpitableR2:wpitableR1;
default:
return nil;
}
}
static void
mkdeventry(Chan *c, Qid qid, Dirtab *tab, Dir *db)
{
mkqid(&qid, tab->qid.path, tab->qid.vers, tab->qid.type);
devdir(c, qid, tab->name, tab->length, eve, tab->perm, db);
}
static int
gpiogen(Chan *c, char *, Dirtab *, int , int s, Dir *db)
{
Qid qid;
int parent, scheme, l;
char **pintable = getpintable();
qid.vers = 0;
parent = PARENT_TYPE(c->qid);
scheme = SCHEME_TYPE(c->qid);
if(s == DEVDOTDOT)
{
switch(parent)
{
case Qtopdir:
case Qgpiodir:
mkdeventry(c, qid, &topdir, db);
break;
default:
return -1;
}
return 1;
}
if(parent == Qtopdir)
{
switch(s)
{
case 0:
mkdeventry(c, qid, &gpiodir, db);
break;
default:
return -1;
}
return 1;
}
if(scheme != Qgeneric && scheme != pinscheme)
{
error(nil);
}
if(parent == Qgpiodir)
{
l = nelem(typedir);
if(s < l)
{
mkdeventry(c, qid, &typedir[s], db);
} else if (s < l + PIN_TABLE_SIZE)
{
s -= l;
if(pintable[s] == 0)
{
return 0;
}
mkqid(&qid, PATH(s, pinscheme, Qgpiodir, Qdata), 0, QTFILE);
snprint(up->genbuf, sizeof up->genbuf, "%s", pintable[s]);
devdir(c, qid, up->genbuf, 0, eve, 0666, db);
}
else
{
return -1;
}
return 1;
}
return 1;
}
static void
interrupt(Ureg*, void *)
{
uint pin;
coherence();
eventvalue = 0;
for(pin = 0; pin < PIN_TABLE_SIZE; pin++)
{
if(gpiogetevent(pin))
SET_BIT(&eventvalue, pin, 1);
}
coherence();
wakeup(&rend);
}
static void
gpioinit(void)
{
gpiomeminit();
boardrev = getboardrev() & 0xff;
pinscheme = Qboard;
intrenable(49, interrupt, nil, BUSUNKNOWN, "gpio1");
}
static void
gpioshutdown(void)
{ }
static Chan*
gpioattach(char *spec)
{
return devattach('G', spec);
}
static Walkqid*
gpiowalk(Chan *c, Chan *nc, char** name, int nname)
{
return devwalk(c, nc, name, nname, 0, 0, gpiogen);
}
static int
gpiostat(Chan *c, uchar *db, int n)
{
return devstat(c, db, n, 0, 0, gpiogen);
}
static Chan*
gpioopen(Chan *c, int omode)
{
int type;
c = devopen(c, omode, 0, 0, gpiogen);
type = FILE_TYPE(c->qid);
switch(type)
{
case Qdata:
c->iounit = 1;
break;
case Qctl:
break;
case Qevent:
lock(&eventlock);
if(eventinuse != 0){
c->flag &= ~COPEN;
unlock(&eventlock);
error(Einuse);
}
eventinuse = 1;
unlock(&eventlock);
eventvalue = 0;
c->iounit = 4;
}
return c;
}
static void
gpioclose(Chan *c)
{
int type;
type = FILE_TYPE(c->qid);
switch(type)
{
case Qevent:
if(c->flag & COPEN)
{
if(c->flag & COPEN){
eventinuse = 0;
}
}
break;
}
}
static int
isset(void *)
{
return eventvalue;
}
static long
gpioread(Chan *c, void *va, long n, vlong off)
{
int type, scheme;
uint pin;
char *a;
a = va;
if(c->qid.type & QTDIR)
{
return devdirread(c, va, n, 0, 0, gpiogen);
}
type = FILE_TYPE(c->qid);
scheme = SCHEME_TYPE(c->qid);
if(scheme != Qgeneric && scheme != pinscheme)
{
error(nil);
}
switch(type)
{
case Qdata:
pin = PIN_NUMBER(c->qid);
a[0] = (gpioin(pin))?'1':'0';
n = 1;
break;
case Qctl:
n = 0;
break;
case Qevent:
if(off >= 4)
{
off %= 4;
eventvalue = 0;
}
sleep(&rend, isset, 0);
if(off + n > 4)
{
n = 4 - off;
}
memmove(a, &eventvalue + off, n);
}
return n;
}
static int
getpin(char *pinname)
{
int i;
char **pintable = getpintable();
for(i = 0; i < PIN_TABLE_SIZE; i++)
{
if(!pintable[i])
{
continue;
}
if(strncmp(pintable[i], pinname, strlen(pintable[i])) == 0)
{
return i;
}
}
return -1;
}
static long
gpiowrite(Chan *c, void *va, long n, vlong)
{
int type, i, scheme;
uint pin;
char *arg;
Cmdbuf *cb;
Cmdtab *ct;
if(c->qid.type & QTDIR)
{
error(Eisdir);
}
type = FILE_TYPE(c->qid);
scheme = SCHEME_TYPE(c->qid);
if(scheme != Qgeneric && scheme != pinscheme)
{
error(nil);
}
cb = parsecmd(va, n);
if(waserror())
{
free(cb);
nexterror();
}
ct = lookupcmd(cb, gpiocmd, nelem(gpiocmd));
if(ct == nil)
{
error(Ebadctl);
}
switch(type)
{
case Qdata:
pin = PIN_NUMBER(c->qid);
switch(ct->index)
{
case CMzero:
gpioout(pin, 0);
break;
case CMone:
gpioout(pin, 1);
break;
default:
error(Ebadctl);
}
break;
case Qctl:
switch(ct->index)
{
case CMscheme:
arg = cb->f[1];
for(i = 0; i < nelem(schemename); i++)
{
if(strncmp(schemename[i], arg, strlen(schemename[i])) == 0)
{
pinscheme = i;
break;
}
}
break;
case CMfunc:
pin = getpin(cb->f[2]);
if(pin == -1) {
error(Ebadctl);
}
arg = cb->f[1];
for(i = 0; i < nelem(funcname); i++)
{
if(strncmp(funcname[i], arg, strlen(funcname[i])) == 0)
{
gpiosel(pin, i);
break;
}
}
break;
case CMpull:
pin = getpin(cb->f[2]);
if(pin == -1) {
error(Ebadctl);
}
arg = cb->f[1];
for(i = 0; i < nelem(pudname); i++)
{
if(strncmp(pudname[i], arg, strlen(pudname[i])) == 0)
{
gpiopull(pin, i);
break;
}
}
break;
case CMevent:
pin = getpin(cb->f[3]);
if(pin == -1) {
error(Ebadctl);
}
arg = cb->f[1];
for(i = 0; i < nelem(evtypename); i++)
{
if(strncmp(evtypename[i], arg, strlen(evtypename[i])) == 0)
{
gpioselevent(pin, i, (cb->f[2][0] == 'e'));
break;
}
}
break;
default:
error(Ebadctl);
}
break;
}
free(cb);
poperror();
return n;
}
Dev gpiodevtab = {
'G',
"gpio",
devreset,
gpioinit,
gpioshutdown,
gpioattach,
gpiowalk,
gpiostat,
gpioopen,
devcreate,
gpioclose,
gpioread,
devbread,
gpiowrite,
devbwrite,
devremove,
devwstat,
};