#include "u.h"#include "../port/lib.h"#include "mem.h"#include "dat.h"#include "fns.h"#include "../port/error.h" #include "io.h"#include "archipe.h" enum { FPGASIZE = 8*1024*1024, FPGATMR = 2-1, /* BCLK timer number (mapped to origin 0) */ TIMERSH = FPGATMR*4, /* timer field shift */ COM3= IBIT(1)|IBIT(2), /* sccr: clock output disabled */ ConfDone = 1<<1, nStatus = 1<<0,}; /* * provisional FPGA interface for simple development work; * for more complex things, use this to load the device then have a * purpose-built device driver or module */ enum{ Qdir, Qmemb, Qmemw, Qprog, Qctl, Qclk, Qstatus,}; static struct { QLock; int clkspeed;} fpga; static void resetfpga(void);static void startfpga(int);static int endfpga(void);static int fpgastatus(void);static void powerfpga(int);static void vclkenable(int);static void vclkset(char*, char*, char*, char*);static void memmovew(ushort*, ushort*, long); static Dirtab fpgadir[]={ ".", {Qdir, 0, QTDIR}, 0, 0555, "fpgamemb", {Qmemb, 0}, FPGASIZE, 0666, "fpgamemw", {Qmemw, 0}, FPGASIZE, 0666, "fpgaprog", {Qprog, 0}, 0, 0222, "fpgastatus", {Qstatus, 0}, 0, 0444, "fpgactl", {Qctl, 0}, 0, 0666, "fpgaclk", {Qclk, 0}, 0, 0666,}; static char Eodd[] = "odd count or offset"; static voidfpgareset(void){ powerfpga(0);} static Chan*fpgaattach(char *spec){ return devattach('G', spec);} static Walkqid*fpgawalk(Chan *c, Chan *nc, char **name, int nname){ return devwalk(c, nc, name, nname, fpgadir, nelem(fpgadir), devgen);} static intfpgastat(Chan *c, uchar *dp, int n){ return devstat(c, dp, n, fpgadir, nelem(fpgadir), devgen);} static Chan*fpgaopen(Chan *c, int omode){ return devopen(c, omode, fpgadir, nelem(fpgadir), devgen);} static voidfpgaclose(Chan*){} static long fpgaread(Chan *c, void *buf, long n, vlong offset){ int v; char stat[32], *p; if(c->qid.type & QTDIR) return devdirread(c, buf, n, fpgadir, nelem(fpgadir), devgen); switch((ulong)c->qid.path){ case Qmemb: if(offset >= FPGASIZE) return 0; if(offset+n >= FPGASIZE) n = FPGASIZE-offset; memmove(buf, KADDR(FPGAMEM+offset), n); return n; case Qmemw: if((n | offset) & 1) error(Eodd); if(offset >= FPGASIZE) return 0; if(offset+n >= FPGASIZE) n = FPGASIZE-offset; memmovew((ushort*)buf, (ushort*)KADDR(FPGAMEM+offset), n); return n; case Qstatus: v = fpgastatus(); p = seprint(stat, stat+sizeof(stat), "%sconfig", v&ConfDone?"":"!"); seprint(p, stat+sizeof(stat), " %sstatus\n", v&nStatus?"":"!"); return readstr(offset, buf, n, stat); case Qclk: return readnum(offset, buf, n, fpga.clkspeed, NUMSIZE); case Qctl: case Qprog: return 0; } error(Egreg); return 0; /* not reached */} static long fpgawrite(Chan *c, void *buf, long n, vlong offset){ int i, j, v; ulong w; Cmdbuf *cb; ulong *cfg; uchar *cp; switch((ulong)c->qid.path){ case Qmemb: if(offset >= FPGASIZE) return 0; if(offset+n >= FPGASIZE) n = FPGASIZE-offset; memmove(KADDR(FPGAMEM+offset), buf, n); return n; case Qmemw: if((n | offset) & 1) error(Eodd); if(offset >= FPGASIZE) return 0; if(offset+n >= FPGASIZE) n = FPGASIZE-offset; memmovew((ushort*)KADDR(FPGAMEM+offset), (ushort*)buf, n); return n; case Qctl: cb = parsecmd(buf, n); if(waserror()){ free(cb); nexterror(); } if(cb->nf < 1) error(Ebadarg); if(strcmp(cb->f[0], "reset") == 0) resetfpga(); else if(strcmp(cb->f[0], "bclk") == 0){ v = 48; if(cb->nf > 1) v = strtoul(cb->f[1], nil, 0); if(v <= 0 || 48%v != 0) error(Ebadarg); startfpga(48/v-1); }else if(strcmp(cb->f[0], "vclk") == 0){ if(cb->nf == 5){ /* vclk n m v r */ vclkenable(1); vclkset(cb->f[1], cb->f[2], cb->f[3], cb->f[4]); }else vclkenable(cb->nf < 2 || strcmp(cb->f[1], "on") == 0); }else if(strcmp(cb->f[0], "power") == 0) powerfpga(cb->nf < 2 || strcmp(cb->f[1], "off") != 0); else error(Ebadarg); poperror(); free(cb); return n; case Qprog: qlock(&fpga); if(waserror()){ qunlock(&fpga); nexterror(); } powerfpga(1); resetfpga(); cfg = KADDR(FPGACR); cp = buf; for(i=0; i<n; i++){ w = cp[i]; for(j=0; j<8; j++){ *cfg = w&1; w >>= 1; } } for(j=0; j<50; j++) /* Altera note says at least 10 clock cycles, but microblaster uses 50 */ *cfg = 0; v = fpgastatus(); if(v != (nStatus|ConfDone)){ snprint(up->genbuf, sizeof(up->genbuf), "error loading fpga: status %d", v); error(up->genbuf); } poperror(); qunlock(&fpga); return n; } error(Egreg); return 0; /* not reached */} /* * PDN seems to control power to the FPGA subsystem * but it is not documented nor is its scope clear (PLL as well?). * It will not run without it. */static voidpowerfpga(int on){ IMM *io; io = ioplock(); if(io->sccr & COM3){ io->sccrk = KEEP_ALIVE_KEY; io->sccr &= ~ COM3; /* FPGA designs can use the clock */ io->sccrk = ~KEEP_ALIVE_KEY; } io->pcpar &= ~PDN; io->pcdir |= PDN; if(on) io->pcdat &= ~PDN; else io->pcdat |= PDN; iopunlock();} static voidresetfpga(void){ IMM *io; io = ioplock(); io->pcpar &= ~nCONFIG; io->pcdir |= nCONFIG; io->pcdat &= ~nCONFIG; microdelay(200); io->pcdat |= nCONFIG; iopunlock();} static intfpgastatus(void){ /* isolate status bits IP_B0 and IP_B1 */ return (m->iomem->pipr>>14) & (ConfDone|nStatus);} static voidstartfpga(int scale){ IMM *io; io = ioplock(); io->tgcr &= ~(0xF<<TIMERSH); io->tmr2 = ((scale&0xFF)<<8) | 0x2A; io->tcn2 = 0; io->trr2 = 0; io->ter2 = 0xFFFF; io->tgcr |= 0x1<<TIMERSH; io->padir |= BCLK; io->papar |= BCLK; iopunlock();} static voidvclkenable(int i){ IMM *io; io = ioplock(); io->padir &= ~VCLK; io->papar &= ~VCLK; io->pbdir |= EnableVCLK; io->pbpar &= ~EnableVCLK; if(i) io->pbdat |= EnableVCLK; else io->pbdat &= ~EnableVCLK; iopunlock();} static voidvclkin(ulong *clk, int v){ int i; for(i=0; i<7; i++) *clk = (v>>i) & 1;} static voidvclkset(char *ns, char *ms, char *vs, char *rs){ int n, m, v, r; ulong *clk; clk = KADDR(CLOCKCR); n = strtol(ns, nil, 0); m = strtol(ms, nil, 0); v = strtol(vs, nil, 0); r = strtol(rs, nil, 0); if(n < 3 || n > 127 || m < 3 || m > 127 || v != 1 && v != 8 || r != 1 && r != 2 && r != 4 && r != 8) error(Ebadarg); vclkenable(0); vclkin(clk, n); vclkin(clk, m); *clk = (v==0) & 1; *clk = 1; *clk = 1; *clk = r == 2 || r == 8; *clk = r == 4 || r == 8; *clk = 1; /* clock out */ *clk = 0; /* disable clk/x */ *clk = 1; *clk = 0; *clk = 1; *clk = 0; *clk = 0; *clk = 0; vclkenable(1);} /* * copy data aligned on 16-bit word boundaries. */static voidmemmovew(ushort *to, ushort *from, long count){ int n; if(count <= 0) return; count >>= 1; n = (count+7) >> 3; switch(count&7) { /* Duff's device */ case 0: do { *to++ = *from++; case 7: *to++ = *from++; case 6: *to++ = *from++; case 5: *to++ = *from++; case 4: *to++ = *from++; case 3: *to++ = *from++; case 2: *to++ = *from++; case 1: *to++ = *from++; } while(--n > 0); }} Dev fpgadevtab = { 'G', "fpga", fpgareset, devinit, devshutdown, fpgaattach, fpgawalk, fpgastat, fpgaopen, devcreate, fpgaclose, fpgaread, devbread, fpgawrite, devbwrite, devremove, devwstat,};
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "io.h"
#include "archipe.h"
enum {
FPGASIZE = 8*1024*1024,
FPGATMR = 2-1, /* BCLK timer number (mapped to origin 0) */
TIMERSH = FPGATMR*4, /* timer field shift */
COM3= IBIT(1)|IBIT(2), /* sccr: clock output disabled */
ConfDone = 1<<1,
nStatus = 1<<0,
};
/*
* provisional FPGA interface for simple development work;
* for more complex things, use this to load the device then have a
* purpose-built device driver or module
*/
enum{
Qdir,
Qmemb,
Qmemw,
Qprog,
Qctl,
Qclk,
Qstatus,
};
static struct {
QLock;
int clkspeed;
} fpga;
static void resetfpga(void);
static void startfpga(int);
static int endfpga(void);
static int fpgastatus(void);
static void powerfpga(int);
static void vclkenable(int);
static void vclkset(char*, char*, char*, char*);
static void memmovew(ushort*, ushort*, long);
static Dirtab fpgadir[]={
".", {Qdir, 0, QTDIR}, 0, 0555,
"fpgamemb", {Qmemb, 0}, FPGASIZE, 0666,
"fpgamemw", {Qmemw, 0}, FPGASIZE, 0666,
"fpgaprog", {Qprog, 0}, 0, 0222,
"fpgastatus", {Qstatus, 0}, 0, 0444,
"fpgactl", {Qctl, 0}, 0, 0666,
"fpgaclk", {Qclk, 0}, 0, 0666,
};
static char Eodd[] = "odd count or offset";
static void
fpgareset(void)
{
powerfpga(0);
}
static Chan*
fpgaattach(char *spec)
{
return devattach('G', spec);
}
static Walkqid*
fpgawalk(Chan *c, Chan *nc, char **name, int nname)
{
return devwalk(c, nc, name, nname, fpgadir, nelem(fpgadir), devgen);
}
static int
fpgastat(Chan *c, uchar *dp, int n)
{
return devstat(c, dp, n, fpgadir, nelem(fpgadir), devgen);
}
static Chan*
fpgaopen(Chan *c, int omode)
{
return devopen(c, omode, fpgadir, nelem(fpgadir), devgen);
}
static void
fpgaclose(Chan*)
{
}
static long
fpgaread(Chan *c, void *buf, long n, vlong offset)
{
int v;
char stat[32], *p;
if(c->qid.type & QTDIR)
return devdirread(c, buf, n, fpgadir, nelem(fpgadir), devgen);
switch((ulong)c->qid.path){
case Qmemb:
if(offset >= FPGASIZE)
return 0;
if(offset+n >= FPGASIZE)
n = FPGASIZE-offset;
memmove(buf, KADDR(FPGAMEM+offset), n);
return n;
case Qmemw:
if((n | offset) & 1)
error(Eodd);
if(offset >= FPGASIZE)
return 0;
if(offset+n >= FPGASIZE)
n = FPGASIZE-offset;
memmovew((ushort*)buf, (ushort*)KADDR(FPGAMEM+offset), n);
return n;
case Qstatus:
v = fpgastatus();
p = seprint(stat, stat+sizeof(stat), "%sconfig", v&ConfDone?"":"!");
seprint(p, stat+sizeof(stat), " %sstatus\n", v&nStatus?"":"!");
return readstr(offset, buf, n, stat);
case Qclk:
return readnum(offset, buf, n, fpga.clkspeed, NUMSIZE);
case Qctl:
case Qprog:
return 0;
}
error(Egreg);
return 0; /* not reached */
}
static long
fpgawrite(Chan *c, void *buf, long n, vlong offset)
{
int i, j, v;
ulong w;
Cmdbuf *cb;
ulong *cfg;
uchar *cp;
switch((ulong)c->qid.path){
case Qmemb:
if(offset >= FPGASIZE)
return 0;
if(offset+n >= FPGASIZE)
n = FPGASIZE-offset;
memmove(KADDR(FPGAMEM+offset), buf, n);
return n;
case Qmemw:
if((n | offset) & 1)
error(Eodd);
if(offset >= FPGASIZE)
return 0;
if(offset+n >= FPGASIZE)
n = FPGASIZE-offset;
memmovew((ushort*)KADDR(FPGAMEM+offset), (ushort*)buf, n);
return n;
case Qctl:
cb = parsecmd(buf, n);
if(waserror()){
free(cb);
nexterror();
}
if(cb->nf < 1)
error(Ebadarg);
if(strcmp(cb->f[0], "reset") == 0)
resetfpga();
else if(strcmp(cb->f[0], "bclk") == 0){
v = 48;
if(cb->nf > 1)
v = strtoul(cb->f[1], nil, 0);
if(v <= 0 || 48%v != 0)
error(Ebadarg);
startfpga(48/v-1);
}else if(strcmp(cb->f[0], "vclk") == 0){
if(cb->nf == 5){ /* vclk n m v r */
vclkenable(1);
vclkset(cb->f[1], cb->f[2], cb->f[3], cb->f[4]);
}else
vclkenable(cb->nf < 2 || strcmp(cb->f[1], "on") == 0);
}else if(strcmp(cb->f[0], "power") == 0)
powerfpga(cb->nf < 2 || strcmp(cb->f[1], "off") != 0);
else
error(Ebadarg);
poperror();
free(cb);
return n;
case Qprog:
qlock(&fpga);
if(waserror()){
qunlock(&fpga);
nexterror();
}
powerfpga(1);
resetfpga();
cfg = KADDR(FPGACR);
cp = buf;
for(i=0; i<n; i++){
w = cp[i];
for(j=0; j<8; j++){
*cfg = w&1;
w >>= 1;
}
}
for(j=0; j<50; j++) /* Altera note says at least 10 clock cycles, but microblaster uses 50 */
*cfg = 0;
v = fpgastatus();
if(v != (nStatus|ConfDone)){
snprint(up->genbuf, sizeof(up->genbuf), "error loading fpga: status %d", v);
error(up->genbuf);
}
poperror();
qunlock(&fpga);
return n;
}
error(Egreg);
return 0; /* not reached */
}
/*
* PDN seems to control power to the FPGA subsystem
* but it is not documented nor is its scope clear (PLL as well?).
* It will not run without it.
*/
static void
powerfpga(int on)
{
IMM *io;
io = ioplock();
if(io->sccr & COM3){
io->sccrk = KEEP_ALIVE_KEY;
io->sccr &= ~ COM3; /* FPGA designs can use the clock */
io->sccrk = ~KEEP_ALIVE_KEY;
}
io->pcpar &= ~PDN;
io->pcdir |= PDN;
if(on)
io->pcdat &= ~PDN;
else
io->pcdat |= PDN;
iopunlock();
}
static void
resetfpga(void)
{
IMM *io;
io = ioplock();
io->pcpar &= ~nCONFIG;
io->pcdir |= nCONFIG;
io->pcdat &= ~nCONFIG;
microdelay(200);
io->pcdat |= nCONFIG;
iopunlock();
}
static int
fpgastatus(void)
{
/* isolate status bits IP_B0 and IP_B1 */
return (m->iomem->pipr>>14) & (ConfDone|nStatus);
}
static void
startfpga(int scale)
{
IMM *io;
io = ioplock();
io->tgcr &= ~(0xF<<TIMERSH);
io->tmr2 = ((scale&0xFF)<<8) | 0x2A;
io->tcn2 = 0;
io->trr2 = 0;
io->ter2 = 0xFFFF;
io->tgcr |= 0x1<<TIMERSH;
io->padir |= BCLK;
io->papar |= BCLK;
iopunlock();
}
static void
vclkenable(int i)
{
IMM *io;
io = ioplock();
io->padir &= ~VCLK;
io->papar &= ~VCLK;
io->pbdir |= EnableVCLK;
io->pbpar &= ~EnableVCLK;
if(i)
io->pbdat |= EnableVCLK;
else
io->pbdat &= ~EnableVCLK;
iopunlock();
}
static void
vclkin(ulong *clk, int v)
{
int i;
for(i=0; i<7; i++)
*clk = (v>>i) & 1;
}
static void
vclkset(char *ns, char *ms, char *vs, char *rs)
{
int n, m, v, r;
ulong *clk;
clk = KADDR(CLOCKCR);
n = strtol(ns, nil, 0);
m = strtol(ms, nil, 0);
v = strtol(vs, nil, 0);
r = strtol(rs, nil, 0);
if(n < 3 || n > 127 || m < 3 || m > 127 || v != 1 && v != 8 ||
r != 1 && r != 2 && r != 4 && r != 8)
error(Ebadarg);
vclkenable(0);
vclkin(clk, n);
vclkin(clk, m);
*clk = (v==0) & 1;
*clk = 1; *clk = 1;
*clk = r == 2 || r == 8;
*clk = r == 4 || r == 8;
*clk = 1; /* clock out */
*clk = 0; /* disable clk/x */
*clk = 1; *clk = 0; *clk = 1;
*clk = 0; *clk = 0; *clk = 0;
vclkenable(1);
}
/*
* copy data aligned on 16-bit word boundaries.
*/
static void
memmovew(ushort *to, ushort *from, long count)
{
int n;
if(count <= 0)
return;
count >>= 1;
n = (count+7) >> 3;
switch(count&7) { /* Duff's device */
case 0: do { *to++ = *from++;
case 7: *to++ = *from++;
case 6: *to++ = *from++;
case 5: *to++ = *from++;
case 4: *to++ = *from++;
case 3: *to++ = *from++;
case 2: *to++ = *from++;
case 1: *to++ = *from++;
} while(--n > 0);
}
}
Dev fpgadevtab = {
'G',
"fpga",
fpgareset,
devinit,
devshutdown,
fpgaattach,
fpgawalk,
fpgastat,
fpgaopen,
devcreate,
fpgaclose,
fpgaread,
devbread,
fpgawrite,
devbwrite,
devremove,
devwstat,
};