summaryrefslogtreecommitdiff
path: root/sys/src/cmd/nusb/serial/ftdi.c
diff options
context:
space:
mode:
authoraiju <aiju@phicode.de>2011-07-30 19:14:18 +0200
committeraiju <aiju@phicode.de>2011-07-30 19:14:18 +0200
commit9f4184892cb4d95c39064906d2c7630f30352215 (patch)
tree0f4ab2102f86d3f93bdd684c5cd0fcf116673ef2 /sys/src/cmd/nusb/serial/ftdi.c
parent2ba1b4c476c986ae788e3b0869fc799e5516a2c2 (diff)
added nusb/serial
Diffstat (limited to 'sys/src/cmd/nusb/serial/ftdi.c')
-rw-r--r--sys/src/cmd/nusb/serial/ftdi.c960
1 files changed, 960 insertions, 0 deletions
diff --git a/sys/src/cmd/nusb/serial/ftdi.c b/sys/src/cmd/nusb/serial/ftdi.c
new file mode 100644
index 000000000..ee88e5433
--- /dev/null
+++ b/sys/src/cmd/nusb/serial/ftdi.c
@@ -0,0 +1,960 @@
+/* Future Technology Devices International serial ports */
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+#include "usb.h"
+#include "serial.h"
+#include "ftdi.h"
+
+/*
+ * BUG: This keeps growing, there has to be a better way, but without
+ * devices to try it... We can probably simply look for FTDI in the
+ * string, or use regular expressions somehow.
+ */
+Cinfo ftinfo[] = {
+ { FTVid, FTACTZWAVEDid },
+ { FTSheevaVid, FTSheevaDid },
+ { FTVid, FTOpenRDUltDid},
+ { FTVid, FTIRTRANSDid },
+ { FTVid, FTIPLUSDid },
+ { FTVid, FTSIODid },
+ { FTVid, FT8U232AMDid },
+ { FTVid, FT8U232AMALTDid },
+ { FTVid, FT8U2232CDid },
+ { FTVid, FTRELAISDid },
+ { INTERBIOMVid, INTERBIOMIOBRDDid },
+ { INTERBIOMVid, INTERBIOMMINIIOBRDDid },
+ { FTVid, FTXF632Did },
+ { FTVid, FTXF634Did },
+ { FTVid, FTXF547Did },
+ { FTVid, FTXF633Did },
+ { FTVid, FTXF631Did },
+ { FTVid, FTXF635Did },
+ { FTVid, FTXF640Did },
+ { FTVid, FTXF642Did },
+ { FTVid, FTDSS20Did },
+ { FTNFRICVid, FTNFRICDid },
+ { FTVid, FTVNHCPCUSBDDid },
+ { FTVid, FTMTXORB0Did },
+ { FTVid, FTMTXORB1Did },
+ { FTVid, FTMTXORB2Did },
+ { FTVid, FTMTXORB3Did },
+ { FTVid, FTMTXORB4Did },
+ { FTVid, FTMTXORB5Did },
+ { FTVid, FTMTXORB6Did },
+ { FTVid, FTPERLEULTRAPORTDid },
+ { FTVid, FTPIEGROUPDid },
+ { SEALEVELVid, SEALEVEL2101Did },
+ { SEALEVELVid, SEALEVEL2102Did },
+ { SEALEVELVid, SEALEVEL2103Did },
+ { SEALEVELVid, SEALEVEL2104Did },
+ { SEALEVELVid, SEALEVEL22011Did },
+ { SEALEVELVid, SEALEVEL22012Did },
+ { SEALEVELVid, SEALEVEL22021Did },
+ { SEALEVELVid, SEALEVEL22022Did },
+ { SEALEVELVid, SEALEVEL22031Did },
+ { SEALEVELVid, SEALEVEL22032Did },
+ { SEALEVELVid, SEALEVEL24011Did },
+ { SEALEVELVid, SEALEVEL24012Did },
+ { SEALEVELVid, SEALEVEL24013Did },
+ { SEALEVELVid, SEALEVEL24014Did },
+ { SEALEVELVid, SEALEVEL24021Did },
+ { SEALEVELVid, SEALEVEL24022Did },
+ { SEALEVELVid, SEALEVEL24023Did },
+ { SEALEVELVid, SEALEVEL24024Did },
+ { SEALEVELVid, SEALEVEL24031Did },
+ { SEALEVELVid, SEALEVEL24032Did },
+ { SEALEVELVid, SEALEVEL24033Did },
+ { SEALEVELVid, SEALEVEL24034Did },
+ { SEALEVELVid, SEALEVEL28011Did },
+ { SEALEVELVid, SEALEVEL28012Did },
+ { SEALEVELVid, SEALEVEL28013Did },
+ { SEALEVELVid, SEALEVEL28014Did },
+ { SEALEVELVid, SEALEVEL28015Did },
+ { SEALEVELVid, SEALEVEL28016Did },
+ { SEALEVELVid, SEALEVEL28017Did },
+ { SEALEVELVid, SEALEVEL28018Did },
+ { SEALEVELVid, SEALEVEL28021Did },
+ { SEALEVELVid, SEALEVEL28022Did },
+ { SEALEVELVid, SEALEVEL28023Did },
+ { SEALEVELVid, SEALEVEL28024Did },
+ { SEALEVELVid, SEALEVEL28025Did },
+ { SEALEVELVid, SEALEVEL28026Did },
+ { SEALEVELVid, SEALEVEL28027Did },
+ { SEALEVELVid, SEALEVEL28028Did },
+ { SEALEVELVid, SEALEVEL28031Did },
+ { SEALEVELVid, SEALEVEL28032Did },
+ { SEALEVELVid, SEALEVEL28033Did },
+ { SEALEVELVid, SEALEVEL28034Did },
+ { SEALEVELVid, SEALEVEL28035Did },
+ { SEALEVELVid, SEALEVEL28036Did },
+ { SEALEVELVid, SEALEVEL28037Did },
+ { SEALEVELVid, SEALEVEL28038Did },
+ { IDTECHVid, IDTECHIDT1221UDid },
+ { OCTVid, OCTUS101Did },
+ { FTVid, FTHETIRA1Did }, /* special quirk div = 240 baud = B38400 rtscts = 1 */
+ { FTVid, FTUSBUIRTDid }, /* special quirk div = 77, baud = B38400 */
+ { FTVid, PROTEGOSPECIAL1 },
+ { FTVid, PROTEGOR2X0 },
+ { FTVid, PROTEGOSPECIAL3 },
+ { FTVid, PROTEGOSPECIAL4 },
+ { FTVid, FTGUDEADSE808Did },
+ { FTVid, FTGUDEADSE809Did },
+ { FTVid, FTGUDEADSE80ADid },
+ { FTVid, FTGUDEADSE80BDid },
+ { FTVid, FTGUDEADSE80CDid },
+ { FTVid, FTGUDEADSE80DDid },
+ { FTVid, FTGUDEADSE80EDid },
+ { FTVid, FTGUDEADSE80FDid },
+ { FTVid, FTGUDEADSE888Did },
+ { FTVid, FTGUDEADSE889Did },
+ { FTVid, FTGUDEADSE88ADid },
+ { FTVid, FTGUDEADSE88BDid },
+ { FTVid, FTGUDEADSE88CDid },
+ { FTVid, FTGUDEADSE88DDid },
+ { FTVid, FTGUDEADSE88EDid },
+ { FTVid, FTGUDEADSE88FDid },
+ { FTVid, FTELVUO100Did },
+ { FTVid, FTELVUM100Did },
+ { FTVid, FTELVUR100Did },
+ { FTVid, FTELVALC8500Did },
+ { FTVid, FTPYRAMIDDid },
+ { FTVid, FTELVFHZ1000PCDid },
+ { FTVid, FTELVCLI7000Did },
+ { FTVid, FTELVPPS7330Did },
+ { FTVid, FTELVTFM100Did },
+ { FTVid, FTELVUDF77Did },
+ { FTVid, FTELVUIO88Did },
+ { FTVid, FTELVUAD8Did },
+ { FTVid, FTELVUDA7Did },
+ { FTVid, FTELVUSI2Did },
+ { FTVid, FTELVT1100Did },
+ { FTVid, FTELVPCD200Did },
+ { FTVid, FTELVULA200Did },
+ { FTVid, FTELVCSI8Did },
+ { FTVid, FTELVEM1000DLDid },
+ { FTVid, FTELVPCK100Did },
+ { FTVid, FTELVRFP500Did },
+ { FTVid, FTELVFS20SIGDid },
+ { FTVid, FTELVWS300PCDid },
+ { FTVid, FTELVFHZ1300PCDid },
+ { FTVid, FTELVWS500Did },
+ { FTVid, LINXSDMUSBQSSDid },
+ { FTVid, LINXMASTERDEVEL2Did },
+ { FTVid, LINXFUTURE0Did },
+ { FTVid, LINXFUTURE1Did },
+ { FTVid, LINXFUTURE2Did },
+ { FTVid, FTCCSICDU200Did },
+ { FTVid, FTCCSICDU401Did },
+ { FTVid, INSIDEACCESSO },
+ { INTREDidVid, INTREDidVALUECANDid },
+ { INTREDidVid, INTREDidNEOVIDid },
+ { FALCOMVid, FALCOMTWISTDid },
+ { FALCOMVid, FALCOMSAMBADid },
+ { FTVid, FTSUUNTOSPORTSDid },
+ { FTVid, FTRMCANVIEWDid },
+ { BANDBVid, BANDBUSOTL4Did },
+ { BANDBVid, BANDBUSTL4Did },
+ { BANDBVid, BANDBUSO9ML2Did },
+ { FTVid, EVERECOPROCDSDid },
+ { FTVid, FT4NGALAXYDE0Did },
+ { FTVid, FT4NGALAXYDE1Did },
+ { FTVid, FT4NGALAXYDE2Did },
+ { FTVid, XSENSCONVERTER0Did },
+ { FTVid, XSENSCONVERTER1Did },
+ { FTVid, XSENSCONVERTER2Did },
+ { FTVid, XSENSCONVERTER3Did },
+ { FTVid, XSENSCONVERTER4Did },
+ { FTVid, XSENSCONVERTER5Did },
+ { FTVid, XSENSCONVERTER6Did },
+ { FTVid, XSENSCONVERTER7Did },
+ { MOBILITYVid, MOBILITYUSBSERIALDid },
+ { FTVid, FTACTIVEROBOTSDid },
+ { FTVid, FTMHAMKWDid },
+ { FTVid, FTMHAMYSDid },
+ { FTVid, FTMHAMY6Did },
+ { FTVid, FTMHAMY8Did },
+ { FTVid, FTMHAMICDid },
+ { FTVid, FTMHAMDB9Did },
+ { FTVid, FTMHAMRS232Did },
+ { FTVid, FTMHAMY9Did },
+ { FTVid, FTTERATRONIKVCPDid },
+ { FTVid, FTTERATRONIKD2XXDid },
+ { EVOLUTIONVid, EVOLUTIONER1Did },
+ { FTVid, FTARTEMISDid },
+ { FTVid, FTATIKATK16Did },
+ { FTVid, FTATIKATK16CDid },
+ { FTVid, FTATIKATK16HRDid },
+ { FTVid, FTATIKATK16HRCDid },
+ { KOBILVid, KOBILCONVB1Did },
+ { KOBILVid, KOBILCONVKAANDid },
+ { POSIFLEXVid, POSIFLEXPP7000Did },
+ { FTVid, FTTTUSBDid },
+ { FTVid, FTECLOCOM1WIREDid },
+ { FTVid, FTWESTREXMODEL777Did },
+ { FTVid, FTWESTREXMODEL8900FDid },
+ { FTVid, FTPCDJDAC2Did },
+ { FTVid, FTRRCIRKITSLOCOBUFFERDid },
+ { FTVid, FTASKRDR400Did },
+ { ICOMID1Vid, ICOMID1Did },
+ { PAPOUCHVid, PAPOUCHTMUDid },
+ { FTVid, FTACGHFDUALDid },
+ { FT8U232AMDid, FT4232HDid },
+ { 0, 0 },
+};
+
+enum {
+ Packsz = 64, /* default size */
+ Maxpacksz = 512,
+ Bufsiz = 4 * 1024,
+};
+
+static int
+ftdiread(Serialport *p, int index, int req, uchar *buf, int len)
+{
+ int res;
+ Serial *ser;
+
+ ser = p->s;
+
+ if(req != FTGETE2READ)
+ index |= p->interfc + 1;
+ dsprint(2, "serial: ftdiread %#p [%d] req: %#x val: %#x idx:%d buf:%p len:%d\n",
+ p, p->interfc, req, 0, index, buf, len);
+ res = usbcmd(ser->dev, Rd2h | Rftdireq | Rdev, req, 0, index, buf, len);
+ dsprint(2, "serial: ftdiread res:%d\n", res);
+ return res;
+}
+
+static int
+ftdiwrite(Serialport *p, int val, int index, int req)
+{
+ int res;
+ Serial *ser;
+
+ ser = p->s;
+
+ if(req != FTGETE2READ || req != FTSETE2ERASE || req != FTSETBAUDRATE)
+ index |= p->interfc + 1;
+ dsprint(2, "serial: ftdiwrite %#p [%d] req: %#x val: %#x idx:%d\n",
+ p, p->interfc, req, val, index);
+ res = usbcmd(ser->dev, Rh2d | Rftdireq | Rdev, req, val, index, nil, 0);
+ dsprint(2, "serial: ftdiwrite res:%d\n", res);
+ return res;
+}
+
+static int
+ftmodemctl(Serialport *p, int set)
+{
+ if(set == 0){
+ p->mctl = 0;
+ ftdiwrite(p, 0, 0, FTSETMODEMCTRL);
+ return 0;
+ }
+ p->mctl = 1;
+ ftdiwrite(p, 0, FTRTSCTSHS, FTSETFLOWCTRL);
+ return 0;
+}
+
+static ushort
+ft232ambaudbase2div(int baud, int base)
+{
+ int divisor3;
+ ushort divisor;
+
+ divisor3 = (base / 2) / baud;
+ if((divisor3 & 7) == 7)
+ divisor3++; /* round x.7/8 up to x+1 */
+ divisor = divisor3 >> 3;
+ divisor3 &= 7;
+
+ if(divisor3 == 1)
+ divisor |= 0xc000; /* 0.125 */
+ else if(divisor3 >= 4)
+ divisor |= 0x4000; /* 0.5 */
+ else if(divisor3 != 0)
+ divisor |= 0x8000; /* 0.25 */
+ if( divisor == 1)
+ divisor = 0; /* special case for maximum baud rate */
+ return divisor;
+}
+
+enum{
+ ClockNew = 48000000,
+ ClockOld = 12000000 / 16,
+ HetiraDiv = 240,
+ UirtDiv = 77,
+};
+
+static ushort
+ft232ambaud2div(int baud)
+{
+ return ft232ambaudbase2div(baud, ClockNew);
+}
+
+static ulong divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7};
+
+static ulong
+ft232bmbaudbase2div(int baud, int base)
+{
+ int divisor3;
+ u32int divisor;
+
+ divisor3 = (base / 2) / baud;
+ divisor = divisor3 >> 3 | divfrac[divisor3 & 7] << 14;
+
+ /* Deal with special cases for highest baud rates. */
+ if( divisor == 1)
+ divisor = 0; /* 1.0 */
+ else if( divisor == 0x4001)
+ divisor = 1; /* 1.5 */
+ return divisor;
+}
+
+static ulong
+ft232bmbaud2div (int baud)
+{
+ return ft232bmbaudbase2div (baud, ClockNew);
+}
+
+static int
+customdiv(Serial *ser)
+{
+ if(ser->dev->usb->vid == FTVid && ser->dev->usb->did == FTHETIRA1Did)
+ return HetiraDiv;
+ else if(ser->dev->usb->vid == FTVid && ser->dev->usb->did == FTUSBUIRTDid)
+ return UirtDiv;
+
+ fprint(2, "serial: weird custom divisor\n");
+ return 0; /* shouldn't happen, break as much as I can */
+}
+
+static ulong
+ftbaudcalcdiv(Serial *ser, int baud)
+{
+ int cusdiv;
+ ulong divval;
+
+ if(baud == 38400 && (cusdiv = customdiv(ser)) != 0)
+ baud = ser->baudbase / cusdiv;
+
+ if(baud == 0)
+ baud = 9600;
+
+ switch(ser->type) {
+ case SIO:
+ switch(baud) {
+ case 300:
+ divval = FTb300;
+ break;
+ case 600:
+ divval = FTb600;
+ break;
+ case 1200:
+ divval = FTb1200;
+ break;
+ case 2400:
+ divval = FTb2400;
+ break;
+ case 4800:
+ divval = FTb4800;
+ break;
+ case 9600:
+ divval = FTb9600;
+ break;
+ case 19200:
+ divval = FTb19200;
+ break;
+ case 38400:
+ divval = FTb38400;
+ break;
+ case 57600:
+ divval = FTb57600;
+ break;
+ case 115200:
+ divval = FTb115200;
+ break;
+ default:
+ divval = FTb9600;
+ break;
+ }
+ break;
+ case FT8U232AM:
+ if(baud <= 3000000)
+ divval = ft232ambaud2div(baud);
+ else
+ divval = ft232ambaud2div(9600);
+ break;
+ case FT232BM:
+ case FT2232C:
+ case FTKINDR:
+ case FT2232H:
+ case FT4232H:
+ if(baud <= 3000000)
+ divval = ft232bmbaud2div(baud);
+ else
+ divval = ft232bmbaud2div(9600);
+ break;
+ default:
+ divval = ft232bmbaud2div(9600);
+ break;
+ }
+ return divval;
+}
+
+static int
+ftsetparam(Serialport *p)
+{
+ int res;
+ ushort val;
+ ulong bauddiv;
+
+ val = 0;
+ if(p->stop == 1)
+ val |= FTSETDATASTOPBITS1;
+ else if(p->stop == 2)
+ val |= FTSETDATASTOPBITS2;
+ else if(p->stop == 15)
+ val |= FTSETDATASTOPBITS15;
+ switch(p->parity){
+ case 0:
+ val |= FTSETDATAParNONE;
+ break;
+ case 1:
+ val |= FTSETDATAParODD;
+ break;
+ case 2:
+ val |= FTSETDATAParEVEN;
+ break;
+ case 3:
+ val |= FTSETDATAParMARK;
+ break;
+ case 4:
+ val |= FTSETDATAParSPACE;
+ break;
+ };
+
+ dsprint(2, "serial: setparam\n");
+
+ res = ftdiwrite(p, val, 0, FTSETDATA);
+ if(res < 0)
+ return res;
+
+ res = ftmodemctl(p, p->mctl);
+ if(res < 0)
+ return res;
+
+ bauddiv = ftbaudcalcdiv(p->s, p->baud);
+ res = ftdiwrite(p, bauddiv, (bauddiv>>16) & 1, FTSETBAUDRATE);
+
+ dsprint(2, "serial: setparam res: %d\n", res);
+ return res;
+}
+
+static int
+hasjtag(Usbdev *udev)
+{
+ /* no string, for now, by default we detect no jtag */
+ if(udev->product != nil && cistrstr(udev->product, "jtag") != nil)
+ return 1;
+ return 0;
+}
+
+/* ser locked */
+static void
+ftgettype(Serial *ser)
+{
+ int i, outhdrsz, dno, pksz;
+ ulong baudbase;
+ Conf *cnf;
+
+ pksz = Packsz;
+ /* Assume it is not the original SIO device for now. */
+ baudbase = ClockNew / 2;
+ outhdrsz = 0;
+ dno = ser->dev->usb->dno;
+ cnf = ser->dev->usb->conf[0];
+ ser->nifcs = 0;
+ for(i = 0; i < Niface; i++)
+ if(cnf->iface[i] != nil)
+ ser->nifcs++;
+ if(ser->nifcs > 1) {
+ /*
+ * Multiple interfaces. default assume FT2232C,
+ */
+ if(dno == 0x500)
+ ser->type = FT2232C;
+ else if(dno == 0x600)
+ ser->type = FTKINDR;
+ else if(dno == 0x700){
+ ser->type = FT2232H;
+ pksz = Maxpacksz;
+ } else if(dno == 0x800){
+ ser->type = FT4232H;
+ pksz = Maxpacksz;
+ } else
+ ser->type = FT2232C;
+
+ if(hasjtag(ser->dev->usb))
+ ser->jtag = 0;
+
+ /*
+ * BM-type devices have a bug where dno gets set
+ * to 0x200 when serial is 0.
+ */
+ if(dno < 0x500)
+ fprint(2, "serial: warning: dno %d too low for "
+ "multi-interface device\n", dno);
+ } else if(dno < 0x200) {
+ /* Old device. Assume it is the original SIO. */
+ ser->type = SIO;
+ baudbase = ClockOld/16;
+ outhdrsz = 1;
+ } else if(dno < 0x400)
+ /*
+ * Assume its an FT8U232AM (or FT8U245AM)
+ * (It might be a BM because of the iSerialNumber bug,
+ * but it will still work as an AM device.)
+ */
+ ser->type = FT8U232AM;
+ else /* Assume it is an FT232BM (or FT245BM) */
+ ser->type = FT232BM;
+
+ ser->maxrtrans = ser->maxwtrans = pksz;
+ ser->baudbase = baudbase;
+ ser->outhdrsz = outhdrsz;
+ ser->inhdrsz = 2;
+
+ dsprint (2, "serial: detected type: %#x\n", ser->type);
+}
+
+int
+ftmatch(Serial *ser, char *info)
+{
+ Cinfo *ip;
+ char buf[50];
+
+ for(ip = ftinfo; ip->vid != 0; ip++){
+ snprint(buf, sizeof buf, "vid %#06x did %#06x", ip->vid, ip->did);
+ dsprint(2, "serial: %s %s\n", buf, info);
+ if(strstr(info, buf) != nil){
+ if(ser != nil){
+ qlock(ser);
+ ftgettype(ser);
+ qunlock(ser);
+ }
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static int
+ftuseinhdr(Serialport *p, uchar *b)
+{
+ if(b[0] & FTICTS)
+ p->cts = 1;
+ else
+ p->cts = 0;
+ if(b[0] & FTIDSR)
+ p->dsr = 1;
+ else
+ p->dsr = 0;
+ if(b[0] & FTIRI)
+ p->ring = 1;
+ else
+ p->ring = 0;
+ if(b[0] & FTIRLSD)
+ p->rlsd = 1;
+ else
+ p->rlsd = 0;
+
+ if(b[1] & FTIOE)
+ p->novererr++;
+ if(b[1] & FTIPE)
+ p->nparityerr++;
+ if(b[1] & FTIFE)
+ p->nframeerr++;
+ if(b[1] & FTIBI)
+ p->nbreakerr++;
+ return 0;
+}
+
+static int
+ftsetouthdr(Serialport *p, uchar *b, int len)
+{
+ if(p->s->outhdrsz != 0)
+ b[0] = FTOPORT | (FTOLENMSK & len);
+ return p->s->outhdrsz;
+}
+
+static int
+wait4data(Serialport *p, uchar *data, int count)
+{
+ int d;
+ Serial *ser;
+
+ ser = p->s;
+
+ qunlock(ser);
+ d = sendul(p->w4data, 1);
+ qlock(ser);
+ if(d <= 0)
+ return -1;
+ if(p->ndata >= count)
+ p->ndata -= count;
+ else{
+ count = p->ndata;
+ p->ndata = 0;
+ }
+ assert(count >= 0);
+ assert(p->ndata >= 0);
+ memmove(data, p->data, count);
+ if(p->ndata != 0)
+ memmove(p->data, p->data+count, p->ndata);
+
+ recvul(p->gotdata);
+ return count;
+}
+
+static int
+wait4write(Serialport *p, uchar *data, int count)
+{
+ int off, fd;
+ uchar *b;
+ Serial *ser;
+
+ ser = p->s;
+
+ b = emallocz(count+ser->outhdrsz, 1);
+ off = ftsetouthdr(p, b, count);
+ memmove(b+off, data, count);
+
+ fd = p->epout->dfd;
+ qunlock(ser);
+ count = write(fd, b, count+off);
+ qlock(ser);
+ free(b);
+ return count;
+}
+
+typedef struct Packser Packser;
+struct Packser{
+ int nb;
+ uchar b[Bufsiz];
+};
+
+typedef struct Areader Areader;
+struct Areader{
+ Serialport *p;
+ Channel *c;
+};
+
+static void
+shutdownchan(Channel *c)
+{
+ Packser *bp;
+
+ while((bp=nbrecvp(c)) != nil)
+ free(bp);
+ chanfree(c);
+}
+
+int
+cpdata(Serial *ser, Serialport *port, uchar *out, uchar *in, int sz)
+{
+ int i, ncp, ntotcp, pksz;
+
+ pksz = ser->maxrtrans;
+ ntotcp = 0;
+
+ for(i = 0; i < sz; i+= pksz){
+ ftuseinhdr(port, in + i);
+ if(sz - i > pksz)
+ ncp = pksz - ser->inhdrsz;
+ else
+ ncp = sz - i - ser->inhdrsz;
+ memmove(out, in + i + ser->inhdrsz, ncp);
+ out += ncp;
+ ntotcp += ncp;
+ }
+ return ntotcp;
+}
+
+static void
+epreader(void *u)
+{
+ int dfd, rcount, cl, ntries, recov;
+ char err[40];
+ Areader *a;
+ Channel *c;
+ Packser *pk;
+ Serial *ser;
+ Serialport *p;
+
+ threadsetname("epreader proc");
+ a = u;
+ p = a->p;
+ ser = p->s;
+ c = a->c;
+ free(a);
+
+ qlock(ser); /* this makes the reader wait end of initialization too */
+ dfd = p->epin->dfd;
+ qunlock(ser);
+
+ ntries = 0;
+ pk = nil;
+ do {
+ if (pk == nil)
+ pk = emallocz(sizeof(Packser), 1);
+Eagain:
+ rcount = read(dfd, pk->b, sizeof pk->b);
+ if(serialdebug > 5)
+ dsprint(2, "%d %#ux%#ux ", rcount, p->data[0],
+ p->data[1]);
+
+ if(rcount < 0){
+ if(ntries++ > 100)
+ break;
+ qlock(ser);
+ recov = serialrecover(ser, p, nil, "epreader: bulkin error");
+ qunlock(ser);
+ if(recov >= 0)
+ goto Eagain;
+ }
+ if(rcount == 0)
+ continue;
+ if(rcount >= ser->inhdrsz){
+ rcount = cpdata(ser, p, pk->b, pk->b, rcount);
+ if(rcount != 0){
+ pk->nb = rcount;
+ cl = sendp(c, pk);
+ if(cl < 0){
+ /*
+ * if it was a time-out, I don't want
+ * to give back an error.
+ */
+ rcount = 0;
+ break;
+ }
+ }else
+ free(pk);
+ qlock(ser);
+ ser->recover = 0;
+ qunlock(ser);
+ ntries = 0;
+ pk = nil;
+ }
+ } while(rcount >= 0 || (rcount < 0 && strstr(err, "timed out") != nil));
+
+ if(rcount < 0)
+ fprint(2, "%s: error reading %s: %r\n", argv0, p->name);
+ free(pk);
+ nbsendp(c, nil);
+ if(p->w4data != nil)
+ chanclose(p->w4data);
+ if(p->gotdata != nil)
+ chanclose(p->gotdata);
+ devctl(ser->dev, "detach");
+ closedev(ser->dev);
+}
+
+static void
+statusreader(void *u)
+{
+ Areader *a;
+ Channel *c;
+ Packser *pk;
+ Serialport *p;
+ Serial *ser;
+ int cl;
+
+ p = u;
+ ser = p->s;
+ threadsetname("statusreader thread");
+ /* big buffering, fewer bytes lost */
+ c = chancreate(sizeof(Packser *), 128);
+ a = emallocz(sizeof(Areader), 1);
+ a->p = p;
+ a->c = c;
+ incref(ser->dev);
+ proccreate(epreader, a, 16*1024);
+
+ while((pk = recvp(c)) != nil){
+ memmove(p->data, pk->b, pk->nb);
+ p->ndata = pk->nb;
+ free(pk);
+ dsprint(2, "serial %p: status reader %d \n", p, p->ndata);
+ /* consume it all */
+ while(p->ndata != 0){
+ dsprint(2, "serial %p: status reader to consume: %d\n",
+ p, p->ndata);
+ cl = recvul(p->w4data);
+ if(cl < 0)
+ break;
+ cl = sendul(p->gotdata, 1);
+ if(cl < 0)
+ break;
+ }
+ }
+
+ shutdownchan(c);
+ devctl(ser->dev, "detach");
+ closedev(ser->dev);
+}
+
+static int
+ftreset(Serial *ser, Serialport *p)
+{
+ int i;
+
+ if(p != nil){
+ ftdiwrite(p, FTRESETCTLVAL, 0, FTRESET);
+ return 0;
+ }
+ p = ser->p;
+ for(i = 0; i < Maxifc; i++)
+ if(p[i].s != nil)
+ ftdiwrite(&p[i], FTRESETCTLVAL, 0, FTRESET);
+ return 0;
+}
+
+static int
+ftinit(Serialport *p)
+{
+ Serial *ser;
+ uint timerval;
+ int res;
+
+ ser = p->s;
+ if(p->isjtag){
+ res = ftdiwrite(p, FTSETFLOWCTRL, 0, FTDISABLEFLOWCTRL);
+ if(res < 0)
+ return -1;
+ res = ftdiread(p, FTSETLATENCYTIMER, 0, (uchar *)&timerval,
+ FTLATENCYTIMERSZ);
+ if(res < 0)
+ return -1;
+ dsprint(2, "serial: jtag latency timer is %d\n", timerval);
+ timerval = 2;
+ ftdiwrite(p, FTLATENCYDEFAULT, 0, FTSETLATENCYTIMER);
+ res = ftdiread(p, FTSETLATENCYTIMER, 0, (uchar *)&timerval,
+ FTLATENCYTIMERSZ);
+ if(res < 0)
+ return -1;
+
+ dsprint(2, "serial: jtag latency timer set to %d\n", timerval);
+ /* may be unnecessary */
+ devctl(p->epin, "timeout 5000");
+ devctl(p->epout, "timeout 5000");
+ /* 0xb is the mask for lines. plug dependant? */
+ ftdiwrite(p, BMMPSSE|0x0b, 0, FTSETBITMODE);
+ }
+ incref(ser->dev);
+ threadcreate(statusreader, p, 8*1024);
+ return 0;
+}
+
+static int
+ftsetbreak(Serialport *p, int val)
+{
+ return ftdiwrite(p, (val != 0? FTSETBREAK: 0), 0, FTSETDATA);
+}
+
+static int
+ftclearpipes(Serialport *p)
+{
+ /* maybe can be done in one... */
+ ftdiwrite(p, FTRESETCTLVALPURGETX, 0, FTRESET);
+ ftdiwrite(p, FTRESETCTLVALPURGERX, 0, FTRESET);
+ return 0;
+}
+
+static int
+setctlline(Serialport *p, uchar val)
+{
+ return ftdiwrite(p, val | (val << 8), 0, FTSETMODEMCTRL);
+}
+
+static void
+updatectlst(Serialport *p, int val)
+{
+ if(p->rts)
+ p->ctlstate |= val;
+ else
+ p->ctlstate &= ~val;
+}
+
+static int
+setctl(Serialport *p)
+{
+ int res;
+ Serial *ser;
+
+ ser = p->s;
+
+ if(ser->dev->usb->vid == FTVid && ser->dev->usb->did == FTHETIRA1Did){
+ fprint(2, "serial: cannot set lines for this device\n");
+ updatectlst(p, CtlRTS|CtlDTR);
+ p->rts = p->dtr = 1;
+ return -1;
+ }
+
+ /* NB: you can not set DTR and RTS with one control message */
+ updatectlst(p, CtlRTS);
+ res = setctlline(p, (CtlRTS<<8)|p->ctlstate);
+ if(res < 0)
+ return res;
+
+ updatectlst(p, CtlDTR);
+ res = setctlline(p, (CtlDTR<<8)|p->ctlstate);
+ if(res < 0)
+ return res;
+
+ return 0;
+}
+
+static int
+ftsendlines(Serialport *p)
+{
+ int res;
+
+ dsprint(2, "serial: sendlines: %#2.2x\n", p->ctlstate);
+ res = setctl(p);
+ dsprint(2, "serial: sendlines res: %d\n", res);
+ return 0;
+}
+
+static int
+ftseteps(Serialport *p)
+{
+ char *s;
+ Serial *ser;
+
+ ser = p->s;
+
+ s = smprint("maxpkt %d", ser->maxrtrans);
+ devctl(p->epin, s);
+ free(s);
+
+ s = smprint("maxpkt %d", ser->maxwtrans);
+ devctl(p->epout, s);
+ free(s);
+ return 0;
+}
+
+Serialops ftops = {
+ .init = ftinit,
+ .seteps = ftseteps,
+ .setparam = ftsetparam,
+ .clearpipes = ftclearpipes,
+ .reset = ftreset,
+ .sendlines = ftsendlines,
+ .modemctl = ftmodemctl,
+ .setbreak = ftsetbreak,
+ .wait4data = wait4data,
+ .wait4write = wait4write,
+};