#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "io.h"
#include "devlml.h"
static void * pciPhysBaseAddr;
static ulong pciBaseAddr;
static Pcidev * pcidev;
#define DBGREGS 0x01
#define DBGREAD 0x02
#define DBGWRIT 0x04
#define DBG819 0x08
#define DBGINTR 0x10
#define DBGINTS 0x20
int debug = 0;
// Lml 22 driver
struct {
ulong pci;
ulong statcom;
ulong codedata;
} lmlmap;
enum{
Qdir,
Q060,
Q819,
Q856,
Qreg,
Qmap,
Qbuf,
Qjvideo,
Qjframe,
};
static Dirtab lmldir[]={
// name, qid, size, mode
"lml060", {Q060}, 0x400, 0644,
"lml819", {Q819}, 0, 0644,
"lml856", {Q856}, 0, 0644,
"lmlreg", {Qreg}, 0x400, 0644,
"lmlmap", {Qmap}, sizeof lmlmap, 0444,
"lmlbuf", {Qbuf}, 0, 0644,
"jvideo", {Qjvideo}, 0, 0666,
"jframe", {Qjframe}, 0, 0666,
};
CodeData * codeData;
typedef enum {
New,
Header,
Body,
Error,
} State;
int frameNo;
Rendez sleeper;
int singleFrame;
int nopens;
uchar q856[3];
State state = New;
static FrameHeader frameHeader = {
MRK_SOI, MRK_APP3, (sizeof(FrameHeader)-4) << 8,
{ 'L', 'M', 'L', '\0'},
-1, 0, 0, 0, 0
};
ulong
writel(ulong v, ulong a) {
if (debug&DBGREGS)
pprint("%.8lux (%.8lux) <-- %.8lux\n",
a, (ulong)a-pciBaseAddr, v);
return *(ulong *)a = v;
}
ushort
writew(ushort v, ulong a) {
if (debug&DBGREGS)
pprint("%.8lux (%.8lux) <-- %.4ux\n",
a, (ulong)a-pciBaseAddr, v);
return *(ushort *)a = v;
}
uchar
writeb(uchar v, ulong a) {
if (debug&DBGREGS)
pprint("%.8lux (%.8lux) <-- %.2ux\n",
a, (ulong)a-pciBaseAddr, v);
return *(uchar *)a = v;
}
ulong
readl(ulong a) {
ulong v;
v = *(ulong*)a;
if (debug&DBGREGS)
pprint("%.8lux (%.8lux) --> %.8lux\n",
a, (ulong)a-pciBaseAddr, v);
return v;
}
ushort
readw(ulong a) {
ushort v;
v = *(ushort*)a;
if (debug&DBGREGS)
pprint("%.8lux (%.8lux) --> %.4ux\n",
a, (ulong)a-pciBaseAddr, v);
return v;
}
uchar
readb(ulong a) {
uchar v;
v = *(uchar*)a;
if (debug&DBGREGS)
pprint("%.8lux (%.8lux) --> %.2ux\n",
a, (ulong)a-pciBaseAddr, v);
return v;
}
static void
i2c_pause(void) {
microdelay(I2C_DELAY);
}
static void
i2c_waitscl(void) {
int i;
for (i = 0; i < I2C_TIMEOUT; i++)
if (readl(pciBaseAddr + I2C_BUS) & I2C_SCL)
return;
error(Eio);
}
static void
i2c_start(void) {
writel(I2C_SCL|I2C_SDA, pciBaseAddr + I2C_BUS);
i2c_waitscl();
i2c_pause();
writel(I2C_SCL, pciBaseAddr + I2C_BUS);
i2c_pause();
writel(0, pciBaseAddr + I2C_BUS);
i2c_pause();
}
static void
i2c_stop(void) {
// the clock should already be low, make sure data is
writel(0, pciBaseAddr + I2C_BUS);
i2c_pause();
// set clock high and wait for device to catch up
writel(I2C_SCL, pciBaseAddr + I2C_BUS);
i2c_waitscl();
i2c_pause();
// set the data high to indicate the stop bit
writel(I2C_SCL|I2C_SDA, pciBaseAddr + I2C_BUS);
i2c_pause();
}
static void i2c_wrbit(int bit) {
if (bit){
writel(I2C_SDA, pciBaseAddr + I2C_BUS); // set data
i2c_pause();
writel(I2C_SDA|I2C_SCL, pciBaseAddr + I2C_BUS);
i2c_waitscl();
i2c_pause();
writel(I2C_SDA, pciBaseAddr + I2C_BUS);
i2c_pause();
} else {
writel(0, pciBaseAddr + I2C_BUS); // clr data
i2c_pause();
writel(I2C_SCL, pciBaseAddr + I2C_BUS);
i2c_waitscl();
i2c_pause();
writel(0, pciBaseAddr + I2C_BUS);
i2c_pause();
}
}
static int
i2c_rdbit(void) {
int bit;
// the clk line should be low
// ensure we are not asserting the data line
writel(I2C_SDA, pciBaseAddr + I2C_BUS);
i2c_pause();
// set the clock high and wait for device to catch up
writel(I2C_SDA|I2C_SCL, pciBaseAddr + I2C_BUS);
i2c_waitscl();
i2c_pause();
// the data line should be a valid bit
bit = readl(pciBaseAddr+I2C_BUS);
if (bit & I2C_SDA){
bit = 1;
} else {
bit = 0;
}
// set the clock low to indicate end of cycle
writel(I2C_SDA, pciBaseAddr + I2C_BUS);
i2c_pause();
return bit;
}
static int
i2c_rdbyte(uchar *v) {
int i, ack;
uchar res;
res = 0;
for (i = 0; i < 8; i++) {
res = res << 1;
res += i2c_rdbit();
}
ack = i2c_rdbit();
*v = res;
return ack;
}
static int
i2c_wrbyte(uchar v) {
int i, ack;
for (i = 0; i < 8; i++) {
i2c_wrbit(v & 0x80);
v = v << 1;
}
ack = i2c_rdbit();
return ack;
}
static void
i2c_write_bytes(uchar addr, uchar sub, uchar *bytes, long num) {
int ack;
long i;
i2c_start();
ack = i2c_wrbyte(addr);
if (ack == 1) error(Eio);
ack = i2c_wrbyte(sub);
if (ack == 1) error(Eio);
for (i = 0; i < num; i++) {
ack = i2c_wrbyte(bytes[i]);
if (ack == 1) error(Eio);
}
i2c_stop();
}
static int
i2c_bt856rd8(void) {
uchar ret;
i2c_start ();
if (i2c_wrbyte(BT856Addr + 1) == 1
|| i2c_rdbyte(&ret) == 0) {
i2c_stop ();
return -1;
}
i2c_stop ();
return ret;
}
static int
i2c_rd8(int addr, int sub)
{
uchar msb;
i2c_start();
if (i2c_wrbyte(addr) == 1
|| i2c_wrbyte(sub) == 1) {
if (debug&DBGREGS) pprint("i2c_rd8, failure 1\n");
i2c_stop();
return -1;
}
i2c_start();
if (i2c_wrbyte(addr+1) == 1
|| i2c_rdbyte(&msb) == 0){
if (debug&DBGREGS) pprint("i2c_rd8, failure 2\n");
i2c_stop();
return -1;
}
i2c_stop();
return msb;
}
static int
i2c_wr8(uchar addr, uchar sub, uchar msb) {
i2c_start();
if (i2c_wrbyte(addr) == 1
|| i2c_wrbyte(sub) == 1
|| i2c_wrbyte(msb) == 1)
return 0;
i2c_stop();
return 1;
}
/*
* The following mapping applies for the guests in the LML33
*
* Guest Device
* 0 zr36060
* uses subaddress GADR[0..1]
* 1 zr36060 START#
* 2 -
* 3 zr36060 RESET#
* 4 -
* 5 -
* 6 -
* 7 -
*/
// post_idle waits for the guest bus to become free
static int
post_idle(void) {
ulong a;
int timeout;
for (timeout = 0; timeout < GUEST_TIMEOUT; timeout++){
a = readl(pciBaseAddr + POST_OFFICE);
if ((a & POST_PEND) == 0)
return a;
}
pprint("post_idle timeout\n");
return -1;
}
// post_write writes a byte to a guest using postoffice mechanism
int
post_write(unsigned int guest, unsigned int reg, unsigned int v) {
int w;
// wait for postoffice not busy
post_idle();
// Trim the values, just in case
guest &= 0x07;
reg &= 0x07;
v &= 0xFF;
// write postoffice operation
w = POST_DIR | (guest<<20) | (reg<<16) | v;
writel(w, pciBaseAddr + POST_OFFICE);
// wait for postoffice not busy
return post_idle() == -1;
}
// post_read reads a byte from a guest using postoffice mechanism
int
post_read(int guest, int reg) {
int w;
// wait for postoffice not busy
post_idle();
// Trim the values, just in case
guest &= 0x07;
reg &= 0x07;
// write postoffice operation
w = (guest<<20) + (reg<<16);
writel(w, pciBaseAddr + POST_OFFICE);
// wait for postoffice not busy, get result
w = post_idle();
// decide if read went ok
if (w == -1) return -1;
return w & 0xFF;
}
static int
zr060_write(int reg, int v) {
if (post_write(GID060, 1, (reg>>8) & 0x03)
|| post_write(GID060, 2, reg & 0xff)
|| post_write(GID060, 3, v))
return -1;
}
static int
zr060_read(int reg) {
if (post_write(GID060, 1, (reg>>8) & 0x03)
|| post_write(GID060, 2, reg & 0xff))
return -1;
return post_read(GID060, 3);
}
static int
prepareBuffer(int i) {
if (i >= 0 && i < NBUF && (codeData->statCom[i] & STAT_BIT)) {
codeData->statCom[i] = PADDR(&(codeData->fragdesc[i]));
return codeData->fragdesc[i].leng;
} else
return -1;
}
static int
getProcessedBuffer(void){
static lastBuffer = NBUF-1;
int l = lastBuffer;
while (1) {
lastBuffer = (lastBuffer+1) % NBUF;
if (codeData->statCom[lastBuffer] & STAT_BIT)
return lastBuffer;
if (lastBuffer == l)
break;
}
return -1;
}
static int
getBuffer(int i, int* frameNo) {
if(codeData->statCom[i] & STAT_BIT) {
*frameNo = codeData->statCom[i] >> 24;
return (codeData->statCom[i] & 0x00FFFFFF) >> 1;
} else
return -1;
}
static long
vread(Chan *, void *va, long nbytes, vlong) {
static int bufpos;
static char *bufptr;
static int curbuf;
static int fragsize;
static int frameno;
static int frameprev;
char *p;
long count = nbytes;
int i;
vlong thetime;
p = (char *)va;
while (count > 0) {
switch (state) {
case New:
while ((curbuf = getProcessedBuffer()) == -1)
sleep(&sleeper, return0, 0);
fragsize = getBuffer(curbuf, &frameno);
if (debug & DBGREAD)
pprint("devlml: got read buf %d, fr %d, size %d\n",
curbuf, frameNo, fragsize);
if (frameno != (frameprev + 1) % 256)
pprint("Frame out of sequence: %d %d\n",
frameprev, frameno);
frameprev = frameno;
if (fragsize <= 0 || fragsize > sizeof(Fragment)) {
pprint("Wrong sized fragment, %d (ignored)\n",
fragsize);
prepareBuffer(curbuf);
break;
}
// Fill in APP marker fields here
thetime = todget();
frameHeader.sec = (ulong)(thetime / 1000000000LL);
frameHeader.usec = (ulong)(thetime % 1000000000LL) / 1000;
frameHeader.frameSize = fragsize - 2 + sizeof(FrameHeader);
frameHeader.frameSeqNo++;
frameHeader.frameNo = frameno;
bufpos = 0;
state = Header;
bufptr = (char *)(&frameHeader);
// Fall through
case Header:
i = sizeof(FrameHeader) - bufpos;
if (count <= i) {
memmove(p, bufptr, count);
bufptr += count;
bufpos += count;
return nbytes;
}
memmove(p, bufptr, i);
count -= i;
p += i;
bufpos = 2;
bufptr = codeData->frag[curbuf].fb + 2;
state = Body;
// Fall through
case Body:
i = fragsize - bufpos;
if (count < i) {
memmove(p, bufptr, count);
bufptr += count;
bufpos += count;
return nbytes;
}
memmove(p, bufptr, i);
count -= i;
p += i;
// Allow reuse of current buffer
prepareBuffer(curbuf);
state = New;
if (singleFrame) {
state = Error;
return nbytes - count;
}
break;
case Error:
return 0;
}
}
}
static long
vwrite(Chan *, void *va, long nbytes, vlong) {
static int bufpos;
static char *bufptr;
static int curbuf;
static int fragsize;
char *p;
long count = nbytes;
int i;
p = (char *)va;
while (count > 0) {
switch (state) {
case New:
while((curbuf = getProcessedBuffer()) == -1) {
if (debug&DBGWRIT)
pprint("devlml::sleep\n");
sleep(&sleeper, return0, 0);
}
if (debug&DBGWRIT)
pprint("current buffer %d\n", curbuf);
bufptr = codeData->frag[curbuf].fb;
bufpos = 0;
state = Header;
// Fall through
case Header:
if (count < sizeof(FrameHeader) - bufpos) {
memmove(bufptr, p, count);
bufptr += count;
bufpos += count;
return nbytes;
}
// Fill remainder of header
i = sizeof(FrameHeader) - bufpos;
memmove(bufptr, p, i);
bufptr += i;
bufpos += i;
p += i;
count -= i;
// verify header
if (codeData->frag[curbuf].fh.mrkSOI != MRK_SOI
|| codeData->frag[curbuf].fh.mrkAPP3 != MRK_APP3
|| strcmp(codeData->frag[curbuf].fh.nm, APP_NAME)) {
// Header is bad
pprint("devlml: header error: 0x%.4ux, 0x%.4ux, `%.4s'\n",
codeData->frag[curbuf].fh.mrkSOI,
codeData->frag[curbuf].fh.mrkAPP3,
codeData->frag[curbuf].fh.nm);
state = Error;
return nbytes - count;
}
fragsize = codeData->frag[curbuf].fh.frameSize;
if (fragsize <= sizeof(FrameHeader)
|| fragsize > sizeof(Fragment)) {
pprint("devlml: frame size error: 0x%.8ux\n",
fragsize);
state = Error;
return nbytes - count;
}
state = Body;
// Fall through
case Body:
i = fragsize - bufpos;
if (count < i) {
memmove(bufptr, p, count);
bufptr += count;
bufpos += count;
return nbytes;
}
memmove(bufptr, p, i);
bufptr += i;
bufpos += i;
p += i;
count -= i;
// We have written the frame, time to display it
i = prepareBuffer(curbuf);
if (debug&DBGWRIT)
pprint("Sending buffer %d: %d\n", curbuf, i);
if (singleFrame) {
state = Error;
return nbytes - count;
}
state = New;
break;
case Error:
return 0;
}
}
}
static void lmlintr(Ureg *, void *);
static void
lmlreset(void)
{
// LMLSEG
Physseg segbuf;
Physseg segreg;
//
ulong regpa;
int i;
pcidev = pcimatch(nil, PCI_VENDOR_ZORAN, PCI_DEVICE_ZORAN_36067);
if (pcidev == nil) {
return;
}
codeData = (CodeData*)xspanalloc(sizeof(CodeData), BY2PG, 0);
if (codeData == nil) {
print("devlml: xspanalloc(%ux, %ux, 0)\n", sizeof(CodeData), BY2PG);
return;
}
print("Installing Motion JPEG driver %s\n", MJPG_VERSION);
print("Buffer at 0x%.8lux, size 0x%.8ux\n", codeData, sizeof(CodeData));
// Get access to DMA memory buffer
memset(codeData, 0xAA, sizeof(CodeData));
for (i = 0; i < NBUF; i++) {
codeData->statCom[i] = PADDR(&(codeData->fragdesc[i]));
codeData->fragdesc[i].addr = PADDR(&(codeData->frag[i]));
// Length is in double words, in position 1..20
codeData->fragdesc[i].leng = ((sizeof codeData->frag[i]) >> 1) | FRAGM_FINAL_B;
}
print("initializing LML33 board...");
pciPhysBaseAddr = (void *)(pcidev->mem[0].bar & ~0x0F);
print("zr36067 found at 0x%.8lux", pciPhysBaseAddr);
regpa = upamalloc(pcidev->mem[0].bar & ~0x0F, pcidev->mem[0].size, 0);
if (regpa == 0) {
print("lml: failed to map registers\n");
return;
}
pciBaseAddr = (ulong)KADDR(regpa);
print(", mapped at 0x%.8lux\n", pciBaseAddr);
// make sure the device will respond to mem accesses
// (pcicmd_master | pcicmd_memory) -- probably superfluous
// pcicfgw32(pcidev, PciPCR, 0x04 | 0x02);
// set bus latency -- probably superfluous
// pcicfgw8(pcidev, PciLTR, 64);
// Interrupt handler
intrenable(pcidev->intl, lmlintr, nil, pcidev->tbdf);
lmlmap.pci = pciBaseAddr;
lmlmap.statcom = PADDR(codeData->statCom);
lmlmap.codedata = (ulong)codeData;
// LMLSEG
memset(&segbuf, 0, sizeof(segbuf));
segbuf.attr = SG_PHYSICAL;
segbuf.name = smalloc(NAMELEN);
snprint(segbuf.name, NAMELEN, "lmlmjpg");
segbuf.pa = PADDR(codeData);
segbuf.size = sizeof(CodeData);
addphysseg(&segbuf);
memset(&segreg, 0, sizeof(segreg));
segreg.attr = SG_PHYSICAL;
segreg.name = smalloc(NAMELEN);
snprint(segreg.name, NAMELEN, "lmlregs");
segreg.pa = (ulong)regpa;
segreg.size = pcidev->mem[0].size;
addphysseg(&segreg);
//
return;
}
static Chan*
lmlattach(char *spec)
{
return devattach('V', spec);
}
static int
lmlwalk(Chan *c, char *name)
{
return devwalk(c, name, lmldir, nelem(lmldir), devgen);
}
static void
lmlstat(Chan *c, char *dp)
{
devstat(c, dp, lmldir, nelem(lmldir), devgen);
}
static Chan*
lmlopen(Chan *c, int omode) {
int i;
c->aux = 0;
switch(c->qid.path){
case Q060:
case Q819:
case Q856:
case Qreg:
case Qmap:
case Qbuf:
break;
case Qjframe:
case Qjvideo:
if (nopens)
error(Einuse);
nopens = 1;
singleFrame = (c->qid.path == Qjframe) ? 1 : 0;;
state = New;
for (i = 0; i < NBUF; i++)
codeData->statCom[i] = PADDR(&(codeData->fragdesc[i]));
// allow one open total for these two
break;
}
return devopen(c, omode, lmldir, nelem(lmldir), devgen);
}
static void
lmlclose(Chan *c) {
switch(c->qid.path){
case Q060:
case Q819:
case Q856:
case Qreg:
case Qmap:
case Qbuf:
authclose(c);
break;
case Qjvideo:
case Qjframe:
nopens = 0;
authclose(c);
}
}
static long
lmlread(Chan *c, void *va, long n, vlong voff) {
int i, d;
uchar *buf = va;
long off = voff;
switch(c->qid.path & ~CHDIR){
case Qdir:
return devdirread(c, (char *)buf, n, lmldir, nelem(lmldir), devgen);
case Q060:
if (off < 0 || off + n > 0x400)
return 0;
for (i = 0; i < n; i++) {
if ((d = zr060_read(off + i)) < 0) break;
*buf++ = d;
}
return i;
case Q819:
if (off < 0 || off + n > 0x20)
return 0;
for (i = 0; i < n; i++) {
if ((d = i2c_rd8(BT819Addr, off++)) < 0) break;
*buf++ = d;
}
return i;
case Q856:
if (n != 1)
return 0;
switch ((int)off) {
case 0:
if ((d = i2c_bt856rd8()) < 0)
return 0;
*buf = d;
break;
case 0xDA:
*buf = q856[0];
break;
case 0xDC:
*buf = q856[1];
break;
case 0xDE:
*buf = q856[2];
break;
default:
return 0;
}
return 1;
case Qmap:
if (off < 0)
return 0;
for (i = 0; i < n; i++) {
if (off + i >= sizeof lmlmap)
break;
buf[i] = ((uchar *)&lmlmap)[off + i];
}
return i;
case Qreg:
if (off < 0 || off + n >= 0x400)
return 0;
switch(n) {
case 1:
*buf = readb(pciBaseAddr + off);
break;
case 2:
if (off & (n-1)) return 0;
*(short *)buf = readw(pciBaseAddr + off);
break;
case 4:
if (off & (n-1)) return 0;
*(long *)buf = readl(pciBaseAddr + off);
break;
default:
return 0;
}
return n;
case Qbuf:
if (off < 0 || off + n >= sizeof *codeData)
return 0;
switch(n) {
case 1:
*buf = readb((ulong)codeData + off);
break;
case 2:
if (off & (n-1)) return 0;
*(short *)buf = readw((ulong)codeData + off);
break;
case 4:
if (off & (n-1)) return 0;
*(long *)buf = readl((ulong)codeData + off);
break;
default:
return 0;
}
return n;
case Qjvideo:
case Qjframe:
return vread(c, buf, n, off);
}
}
static long
lmlwrite(Chan *c, void *va, long n, vlong voff) {
int i;
uchar *buf = va;
long off = voff;
switch(c->qid.path & ~CHDIR){
case Qdir:
error(Eperm);
case Q060:
if (off < 0 || off + n > 0x400)
return 0;
for (i = 0; i < n; i++) {
if (zr060_write(off + i, *buf++) < 0) break;
}
return i;
case Q819:
if (off < 0 || off + n >= 0x20)
return 0;
for (i = 0; i < n; i++)
if (i2c_wr8(BT819Addr, off + i, *buf++) == 0)
break;
return i;
case Q856:
if (n != 1 || off < 0xda || off + n > 0xe0)
return 0;
if (i2c_wr8(BT856Addr, off, *buf) == 0)
return 0;
switch ((int)off) {
case 0xDA:
q856[0] = *buf;
break;
case 0xDC:
q856[1] = *buf;
break;
case 0xDE:
q856[2] = *buf;
break;
}
return 1;
case Qreg:
if (off < 0 || off + n >= 0x400)
return 0;
switch (n) {
case 1:
writeb(*buf, pciBaseAddr + off);
break;
case 2:
if (off & 0x1)
return 0;
writew(*(short *)buf, pciBaseAddr + off);
break;
case 4:
if (off & 0x3)
return 0;
writel(*(long *)buf, pciBaseAddr + off);
break;
default:
return 0;
}
return n;
case Qbuf:
if (off < 0 || off + n >= sizeof *codeData)
return 0;
switch (n) {
case 1:
writeb(*buf, (ulong)codeData + off);
break;
case 2:
if (off & 0x1)
return 0;
writew(*(short *)buf, (ulong)codeData + off);
break;
case 4:
if (off & 0x3)
return 0;
writel(*(long *)buf, (ulong)codeData + off);
break;
default:
return 0;
}
return n;
case Qjvideo:
case Qjframe:
return vwrite(c, buf, n, off);
}
}
Dev lmldevtab = {
'V',
"video",
lmlreset,
devinit,
lmlattach,
devclone,
lmlwalk,
lmlstat,
lmlopen,
devcreate,
lmlclose,
lmlread,
devbread,
lmlwrite,
devbwrite,
devremove,
devwstat,
};
static void
lmlintr(Ureg *, void *) {
static count;
ulong flags = readl(pciBaseAddr+INTR_STAT);
if(debug&(DBGINTR))
print("MjpgDrv_intrHandler stat=0x%.8lux\n", flags);
// Reset all interrupts from 067
writel(0xff000000, pciBaseAddr + INTR_STAT);
if(flags & INTR_JPEGREP) {
if ((debug&DBGINTR) || ((debug&DBGINTS) && (count++ % 128) == 0))
print("MjpgDrv_intrHandler wakeup\n");
wakeup(&sleeper);
}
return;
}