#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>= 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<tmr2 = ((scale&0xFF)<<8) | 0x2A; io->tcn2 = 0; io->trr2 = 0; io->ter2 = 0xFFFF; io->tgcr |= 0x1<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, };