shithub: riscv

ref: 63f9a4fda36f916e543c8f3ea117b31c194a4db7
dir: /sys/src/cmd/nusb/serial/ftdi.c/

View raw version
/* 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"

enum {
	/* used by devices which don't provide their own Vid */
	FTVid		= 0x0403,

	FTSheevaVid	= 0x9E88,
	FTSheevaDid	= 0x9E8F,
	FTOpenRDUltDid	= 0x9E90,

	FTSIODid	= 0x8372,	/* Product Id SIO appl'n of 8U100AX */
	FT8U232AMDid	= 0x6001,	/* Similar device to SIO above */
	FT8U232AMALTDid	= 0x6006,	/* FT's alternate Did for above*/
	FT8U2232CDid	= 0x6010,	/* Dual channel device */
	FTRELAISDid	= 0xFA10,	/* Relais device */

	/* NF reader */
	FTNFRICVid	= 0x0DCD,
	FTNFRICDid	= 0x0001,

	FTACTZWAVEDid	= 0xF2D0,		/* www.irtrans.de device */

	/*
	 * ACT Solutions HomePro ZWave interface
	 * http://www.act-solutions.com/HomePro.htm)
	 */
	FTIRTRANSDid	= 0xFC60,

	/*
	 * www.thoughttechnology.com/ TT-USB
	 */
	FTTTUSBDid	= 0xFF20,

	/* iPlus device */
	FTIPLUSDid	= 0xD070,

	/* www.crystalfontz.com devices */
	FTXF632Did = 0xFC08,	/* 632: 16x2 Character Display */
	FTXF634Did = 0xFC09,	/* 634: 20x4 Character Display */
	FTXF547Did = 0xFC0A,	/* 547: Two line Display */
	FTXF633Did = 0xFC0B,	/* 633: 16x2 Character Display with Keys */
	FTXF631Did = 0xFC0C,	/* 631: 20x2 Character Display */
	FTXF635Did = 0xFC0D,	/* 635: 20x4 Character Display */
	FTXF640Did = 0xFC0E,	/* 640: Two line Display */
	FTXF642Did = 0xFC0F,	/* 642: Two line Display */

	/*
	 * Video Networks Limited / Homechoice in the UK
	 * use an ftdi-based device for their 1Mb broadband
	 */
	FTVNHCPCUSBDDid	= 0xfe38,

	/*
	 * PCDJ use ftdi based dj-controllers
	 * DAC-2 device http://www.pcdjhardware.com/DAC2.asp
	 */
	FTPCDJDAC2Did = 0xFA88,

	/*
	 * Matrix Orbital LCD displays,
	 * which are the FT232BM (similar to the 8U232AM)
	 */
	FTMTXORB0Did = 0xFA00,
	FTMTXORB1Did = 0xFA01,
	FTMTXORB2Did = 0xFA02,
	FTMTXORB3Did = 0xFA03,
	FTMTXORB4Did = 0xFA04,
	FTMTXORB5Did = 0xFA05,
	FTMTXORB6Did = 0xFA06,

	/* Interbiometrics USB I/O Board */
	INTERBIOMVid		= 0x1209,
	INTERBIOMIOBRDDid	= 0x1002,
	INTERBIOMMINIIOBRDDid	= 0x1006,

	/*
	 * The following are the values for the Perle Systems
	 * UltraPort USB serial converters
	 */
	FTPERLEULTRAPORTDid = 0xF0C0,

	/*
	 * Sealevel SeaLINK+ adapters.
	 */

	SEALEVELVid = 0x0c52,

	SEALEVEL2101Did = 0x2101,	/* SeaLINK+232 (2101/2105) */
	SEALEVEL2102Did = 0x2102,	/* SeaLINK+485 (2102) */
	SEALEVEL2103Did = 0x2103,	/* SeaLINK+232I (2103) */
	SEALEVEL2104Did = 0x2104,	/* SeaLINK+485I (2104) */
	SEALEVEL22011Did = 0x2211,	/* SeaPORT+2/232 (2201) Port 1 */
	SEALEVEL22012Did = 0x2221,	/* SeaPORT+2/232 (2201) Port 2 */
	SEALEVEL22021Did = 0x2212,	/* SeaPORT+2/485 (2202) Port 1 */
	SEALEVEL22022Did = 0x2222,	/* SeaPORT+2/485 (2202) Port 2 */
	SEALEVEL22031Did = 0x2213,	/* SeaPORT+2 (2203) Port 1 */
	SEALEVEL22032Did = 0x2223,	/* SeaPORT+2 (2203) Port 2 */
	SEALEVEL24011Did = 0x2411,	/* SeaPORT+4/232 (2401) Port 1 */
	SEALEVEL24012Did = 0x2421,	/* SeaPORT+4/232 (2401) Port 2 */
	SEALEVEL24013Did = 0x2431,	/* SeaPORT+4/232 (2401) Port 3 */
	SEALEVEL24014Did = 0x2441,	/* SeaPORT+4/232 (2401) Port 4 */
	SEALEVEL24021Did = 0x2412,	/* SeaPORT+4/485 (2402) Port 1 */
	SEALEVEL24022Did = 0x2422,	/* SeaPORT+4/485 (2402) Port 2 */
	SEALEVEL24023Did = 0x2432,	/* SeaPORT+4/485 (2402) Port 3 */
	SEALEVEL24024Did = 0x2442,	/* SeaPORT+4/485 (2402) Port 4 */
	SEALEVEL24031Did = 0x2413,	/* SeaPORT+4 (2403) Port 1 */
	SEALEVEL24032Did = 0x2423,	/* SeaPORT+4 (2403) Port 2 */
	SEALEVEL24033Did = 0x2433,	/* SeaPORT+4 (2403) Port 3 */
	SEALEVEL24034Did = 0x2443,	/* SeaPORT+4 (2403) Port 4 */
	SEALEVEL28011Did = 0x2811,	/* SeaLINK+8/232 (2801) Port 1 */
	SEALEVEL28012Did = 0x2821,	/* SeaLINK+8/232 (2801) Port 2 */
	SEALEVEL28013Did = 0x2831,	/* SeaLINK+8/232 (2801) Port 3 */
	SEALEVEL28014Did = 0x2841,	/* SeaLINK+8/232 (2801) Port 4 */
	SEALEVEL28015Did = 0x2851,	/* SeaLINK+8/232 (2801) Port 5 */
	SEALEVEL28016Did = 0x2861,	/* SeaLINK+8/232 (2801) Port 6 */
	SEALEVEL28017Did = 0x2871,	/* SeaLINK+8/232 (2801) Port 7 */
	SEALEVEL28018Did = 0x2881,	/* SeaLINK+8/232 (2801) Port 8 */
	SEALEVEL28021Did = 0x2812,	/* SeaLINK+8/485 (2802) Port 1 */
	SEALEVEL28022Did = 0x2822,	/* SeaLINK+8/485 (2802) Port 2 */
	SEALEVEL28023Did = 0x2832,	/* SeaLINK+8/485 (2802) Port 3 */
	SEALEVEL28024Did = 0x2842,	/* SeaLINK+8/485 (2802) Port 4 */
	SEALEVEL28025Did = 0x2852,	/* SeaLINK+8/485 (2802) Port 5 */
	SEALEVEL28026Did = 0x2862,	/* SeaLINK+8/485 (2802) Port 6 */
	SEALEVEL28027Did = 0x2872,	/* SeaLINK+8/485 (2802) Port 7 */
	SEALEVEL28028Did = 0x2882,	/* SeaLINK+8/485 (2802) Port 8 */
	SEALEVEL28031Did = 0x2813,	/* SeaLINK+8 (2803) Port 1 */
	SEALEVEL28032Did = 0x2823,	/* SeaLINK+8 (2803) Port 2 */
	SEALEVEL28033Did = 0x2833,	/* SeaLINK+8 (2803) Port 3 */
	SEALEVEL28034Did = 0x2843,	/* SeaLINK+8 (2803) Port 4 */
	SEALEVEL28035Did = 0x2853,	/* SeaLINK+8 (2803) Port 5 */
	SEALEVEL28036Did = 0x2863,	/* SeaLINK+8 (2803) Port 6 */
	SEALEVEL28037Did = 0x2873,	/* SeaLINK+8 (2803) Port 7 */
	SEALEVEL28038Did = 0x2883,	/* SeaLINK+8 (2803) Port 8 */

	/* KOBIL Vendor ID chipcard terminals */
	KOBILVid	= 0x0d46,
	KOBILCONVB1Did	= 0x2020,	/* KOBIL Konverter for B1 */
	KOBILCONVKAANDid = 0x2021,	/* KOBILKonverter for KAAN */

	/* Icom ID-1 digital transceiver */
	ICOMID1Vid	= 0x0C26,
	ICOMID1Did	= 0x0004,

	FTASKRDR400Did	= 0xC991,	/* ASK RDR 400 series card reader */
	FTDSS20Did	= 0xFC82,	/* DSS-20 Sync Station for Sony Ericsson P800 */

	/*
	 * Home Electronics (www.home-electro.com) USB gadgets
	 */
	FTHETIRA1Did	= 0xFA78,	/* Tira-1 IR transceiver */

	/*
	 * An infrared receiver and transmitter using the 8U232AM chip
	 * http://www.usbuirt.com
	 */
	FTUSBUIRTDid	= 0xF850,

	FTELVUR100Did	= 0xFB58,	/* USB-RS232-Umsetzer (UR 100) */
	FTELVUM100Did	= 0xFB5A,	/* USB-Modul UM 100 */
	FTELVUO100Did	= 0xFB5B,	/* USB-Modul UO 100 */
	FTELVALC8500Did	= 0xF06E,	/* ALC 8500 Expert */
	FTELVCLI7000Did	= 0xFB59,	/* Computer-Light-Interface */
	FTELVPPS7330Did	= 0xFB5C,	/* Processor-Power-Supply (PPS 7330) */
	FTELVTFM100Did	= 0xFB5D,	/* Temperartur-Feuchte Messgeraet (TFM 100) */
	FTELVUDF77Did	= 0xFB5E,	/* USB DCF Funkurh (UDF 77) */
	FTELVUIO88Did	= 0xFB5F,	/* USB-I/O Interface (UIO 88) */
	FTELVUAD8Did	= 0xF068,	/* USB-AD-Wandler (UAD 8) */
	FTELVUDA7Did	= 0xF069,	/* USB-DA-Wandler (UDA 7) */
	FTELVUSI2Did	= 0xF06A,	/* USB-Schrittmotoren-Interface (USI 2) */
	FTELVT1100Did	= 0xF06B,	/* Thermometer (T 1100) */
	FTELVPCD200Did	= 0xF06C,	/* PC-Datenlogger (PCD 200) */
	FTELVULA200Did	= 0xF06D,	/* USB-LCD-Ansteuerung (ULA 200) */
	FTELVFHZ1000PCDid= 0xF06F,	/* FHZ 1000 PC */
	FTELVCSI8Did	= 0xE0F0,	/* Computer-Schalt-Interface (CSI 8) */
	FTELVEM1000DLDid= 0xE0F1,	/* PC-Datenlogger fuer Energiemonitor (EM 1000 DL) */
	FTELVPCK100Did	= 0xE0F2,	/* PC-Kabeltester (PCK 100) */
	FTELVRFP500Did	= 0xE0F3,	/* HF-Leistungsmesser (RFP 500) */
	FTELVFS20SIGDid	= 0xE0F4,	/* Signalgeber (FS 20 SIG) */
	FTELVWS300PCDid	= 0xE0F6,	/* PC-Wetterstation (WS 300 PC) */
	FTELVFHZ1300PCDid= 0xE0E8,	/* FHZ 1300 PC */
	FTELVWS500Did	= 0xE0E9,	/* PC-Wetterstation (WS 500) */

	/*
	 * Definitions for ID TECH (http://www.idt-net.com) devices
	 */
	IDTECHVid	= 0x0ACD,	/* ID TECH Vendor ID */
	IDTECHIDT1221UDid= 0x0300,	/* IDT1221U USB to RS-232 */

	/*
	 * Definitions for Omnidirectional Control Technology, Inc. devices
	 */
	OCTVid		= 0x0B39,	/* OCT vendor ID */

	/*
	 * Note: OCT US101 is also rebadged as Dick Smith Electronics
	 * (NZ) XH6381, Dick Smith Electronics (Aus) XH6451, and SIIG
	 * Inc. model US2308 hardware version 1.
	 */
	OCTUS101Did	= 0x0421,	/* OCT US101 USB to RS-232 */

	/*
	 *	infrared receiver for access control with IR tags
	 */
	FTPIEGROUPDid	= 0xF208,

	/*
	 * Definitions for Artemis astronomical USB based cameras
	 * http://www.artemisccd.co.uk/
	 */

	FTARTEMISDid	= 0xDF28,	/* All Artemis Cameras */

	FTATIKATK16Did	= 0xDF30,	/* ATIK ATK-16 Grayscale Camera */
	FTATIKATK16CDid = 0xDF32,	/* ATIK ATK-16C Colour Camera */
	FTATIKATK16HRDid= 0xDF31,	/* ATIK ATK-16HR Grayscale */
	FTATIKATK16HRCDid= 0xDF33,	/* ATIK ATK-16HRC Colour Camera */

	/*
	 * Protego products
	 */
	PROTEGOSPECIAL1	= 0xFC70,	/* special/unknown device */
	PROTEGOR2X0	= 0xFC71,	/* R200-USB TRNG unit (R210, R220, and R230) */
	PROTEGOSPECIAL3	= 0xFC72,	/* special/unknown device */
	PROTEGOSPECIAL4	= 0xFC73,	/* special/unknown device */

	/*
	 * Gude Analog- und Digitalsysteme GmbH
	 */
	FTGUDEADSE808Did = 0xE808,
	FTGUDEADSE809Did = 0xE809,
	FTGUDEADSE80ADid = 0xE80A,
	FTGUDEADSE80BDid = 0xE80B,
	FTGUDEADSE80CDid = 0xE80C,
	FTGUDEADSE80DDid = 0xE80D,
	FTGUDEADSE80EDid = 0xE80E,
	FTGUDEADSE80FDid = 0xE80F,
	FTGUDEADSE888Did = 0xE888,	/* Expert ISDN Control USB */
	FTGUDEADSE889Did = 0xE889,	/* USB RS-232 OptoBridge */
	FTGUDEADSE88ADid = 0xE88A,
	FTGUDEADSE88BDid = 0xE88B,
	FTGUDEADSE88CDid = 0xE88C,
	FTGUDEADSE88DDid = 0xE88D,
	FTGUDEADSE88EDid = 0xE88E,
	FTGUDEADSE88FDid = 0xE88F,

	/*
	 * Linx Technologies
	 */
	LINXSDMUSBQSSDid= 0xF448,	/* Linx SDM-USB-QS-S */
	LINXMASTERDEVEL2Did= 0xF449,	/* Linx Master Development.0 */
	LINXFUTURE0Did	= 0xF44A,	/* Linx future device */
	LINXFUTURE1Did	= 0xF44B,	/* Linx future device */
	LINXFUTURE2Did	= 0xF44C,	/* Linx future device */

	/*
	 * CCS Inc. ICDU/ICDU40 - the FT232BM used in a in-circuit-debugger
	 * unit for PIC16's/PIC18's
	 */
	FTCCSICDU200Did	= 0xF9D0,
	FTCCSICDU401Did	= 0xF9D1,

	/* Inside Accesso contactless reader (http://www.insidefr.com) */
	INSIDEACCESSO	= 0xFAD0,

	/*
	 * Intrepid Control Systems (http://www.intrepidcs.com/)
	 * ValueCAN and NeoVI
	 */
	INTREDidVid	= 0x093C,
	INTREDidVALUECANDid= 0x0601,
	INTREDidNEOVIDid= 0x0701,

	/*
	 * Falcom Wireless Communications GmbH
	 */
	FALCOMVid	= 0x0F94,
	FALCOMTWISTDid	= 0x0001,	/* Falcom Twist USB GPRS modem */
	FALCOMSAMBADid	= 0x0005,	/* Falcom Samba USB GPRS modem */

	/*
	 * SUUNTO
	 */
	FTSUUNTOSPORTSDid= 0xF680,	/* Suunto Sports instrument */

	/*
	 * B&B Electronics
	 */
	BANDBVid	= 0x0856,	/* B&B Electronics Vendor ID */
	BANDBUSOTL4Did	= 0xAC01,	/* USOTL4 Isolated RS-485 */
	BANDBUSTL4Did	= 0xAC02,	/* USTL4 RS-485 Converter */
	BANDBUSO9ML2Did	= 0xAC03,	/* USO9ML2 Isolated RS-232 */

	/*
	 * RM Michaelides CANview USB (http://www.rmcan.com)
	 * CAN fieldbus interface adapter
	 */
	FTRMCANVIEWDid	= 0xfd60,

	/*
	 * EVER Eco Pro UPS (http://www.ever.com.pl/)
	 */
	EVERECOPROCDSDid = 0xe520,	/* RS-232 converter */

	/*
	 * 4N-GALAXY.DE PIDs for CAN-USB, USB-RS232, USB-RS422, USB-RS485,
	 * USB-TTY activ, USB-TTY passiv. Some PIDs are used by several devices
	 */
	FT4NGALAXYDE0Did = 0x8372,
	FT4NGALAXYDE1Did = 0xF3C0,
	FT4NGALAXYDE2Did = 0xF3C1,

	/*
	 * Mobility Electronics products.
	 */
	MOBILITYVid	= 0x1342,
	MOBILITYUSBSERIALDid= 0x0202,	/* EasiDock USB 200 serial */

	/*
	 * microHAM product IDs (http://www.microham.com)
	 */
	FTMHAMKWDid	= 0xEEE8,	/* USB-KW interface */
	FTMHAMYSDid	= 0xEEE9,	/* USB-YS interface */
	FTMHAMY6Did	= 0xEEEA,	/* USB-Y6 interface */
	FTMHAMY8Did	= 0xEEEB,	/* USB-Y8 interface */
	FTMHAMICDid	= 0xEEEC,	/* USB-IC interface */
	FTMHAMDB9Did	= 0xEEED,	/* USB-DB9 interface */
	FTMHAMRS232Did	= 0xEEEE,	/* USB-RS232 interface */
	FTMHAMY9Did	= 0xEEEF,	/* USB-Y9 interface */

	/*
	 * Active Robots product ids.
	 */
	FTACTIVEROBOTSDid	= 0xE548,	/* USB comms board */
	XSENSCONVERTER0Did	= 0xD388,
	XSENSCONVERTER1Did	= 0xD389,
	XSENSCONVERTER2Did	= 0xD38A,
	XSENSCONVERTER3Did	= 0xD38B,
	XSENSCONVERTER4Did	= 0xD38C,
	XSENSCONVERTER5Did	= 0xD38D,
	XSENSCONVERTER6Did	= 0xD38E,
	XSENSCONVERTER7Did	= 0xD38F,

	/*
	 * Xsens Technologies BV products (http://www.xsens.com).
	 */
	FTTERATRONIKVCPDid	= 0xEC88,	/* Teratronik device */
	FTTERATRONIKD2XXDid	= 0xEC89,	/* Teratronik device */

	/*
	 * Evolution Robotics products (http://www.evolution.com/).
	 */
	EVOLUTIONVid	= 0xDEEE,
	EVOLUTIONER1Did	= 0x0300,		/* ER1 Control Module */

	/* Pyramid Computer GmbH */
	FTPYRAMIDDid	= 0xE6C8,		/* Pyramid Appliance Display */

	/*
	 * Posiflex inc retail equipment (http://www.posiflex.com.tw)
	 */
	POSIFLEXVid	= 0x0d3a,
	POSIFLEXPP7000Did= 0x0300,		/* PP-7000II thermal printer */

	/*
	 * Westrex International devices
	 */
	FTWESTREXMODEL777Did	= 0xDC00,	/* Model 777 */
	FTWESTREXMODEL8900FDid	= 0xDC01,	/* Model 8900F */

	/*
	 * RR-CirKits LocoBuffer USB (http://www.rr-cirkits.com)
	 */
	FTRRCIRKITSLOCOBUFFERDid= 0xc7d0,	/* LocoBuffer USB */
	FTECLOCOM1WIREDid	= 0xEA90,	/* COM to 1-Wire USB */

	/*
	 * Papouch products (http://www.papouch.com/)
	 */
	PAPOUCHVid	= 0x5050,
	PAPOUCHTMUDid	= 0x0400,		/* TMU USB Thermometer */

	/*
	 * ACG Identification Technologies GmbH products http://www.acg.de/
	 */
	FTACGHFDUALDid	= 0xDD20,		/* HF Dual ISO Reader (RFID) */
	/*
	 * new high speed devices
	 */
	FT4232HDid	= 0x6011,		/* FTDI FT4232H based device */

	/*
	 * Amontec JTAGkey (http://www.amontec.com/)
	 */
	AMONKEYDid	= 0xCFF8,
};

/* Commands */
enum {
	FTRESET		= 0,		/* Reset the port */
	FTSETMODEMCTRL,			/* Set the modem control register */
	FTSETFLOWCTRL,			/* Set flow control register */
	FTSETBAUDRATE,			/* Set baud rate */
	FTSETDATA,			/* Set the parameters, parity */
	FTGETMODEMSTATUS,		/* Retrieve current value of modem ctl */
	FTSETEVENTCHAR,			/* Set the event character */
	FTSETERRORCHAR,			/* Set the error character */
	FTUNKNOWN,
	FTSETLATENCYTIMER,		/* Set the latency timer */
	FTGETLATENCYTIMER,		/* Get the latency timer */
	FTSETBITMODE,			/* Set bit mode */
	FTGETPINS,			/* Read pins state */
	FTGETE2READ	= 0x90,		/* Read address from 128-byte I2C EEPROM */
	FTSETE2WRITE,			/* Write to address on 128-byte I2C EEPROM */
	FTSETE2ERASE,			/* Erase address on 128-byte I2C EEPROM */
};

/* Port Identifier Table, index for interfaces */
enum {
	PITDEFAULT = 0,		/* SIOA */
	PITA,			/* SIOA jtag if there is one */
};

enum {
	Rftdireq = 1<<6,		/* bit for type of request */
};

/*
 * Commands Data size
 * Sets have wLength = 0
 * Gets have wValue = 0
 */
enum {
	FTMODEMSTATUSSZ	= 1,
	FTLATENCYTIMERSZ= 1,
	FTPINSSZ	= 1,
	FTE2READSZ	= 2,
};

/*
 * bRequest: FTGETE2READ
 * wIndex: Address of word to read
 * Data: Will return a word (2 bytes) of data from E2Address
 * Results put in the I2C 128 byte EEPROM string eeprom+(2*index)
 */

/*
 * bRequest: FTSETE2WRITE
 * wIndex: Address of word to read
 * wValue: Value of the word
 * Data: Will return a word (2 bytes) of data from E2Address
 */

/*
 * bRequest: FTSETE2ERASE
 * Erases the EEPROM
 * wIndex: 0
 */

/*
 * bRequest: FTRESET
 * wValue: Ctl Val
 * wIndex: Port
 */
enum {
	FTRESETCTLVAL		= 0,
	FTRESETCTLVALPURGERX	= 1,
	FTRESETCTLVALPURGETX	= 2,
};

/*
 * BmRequestType: SET
 * bRequest: FTSETBAUDRATE
 * wValue: BaudDivisor value - see below
 * Bits 15 to 0 of the 17-bit divisor are placed in the request value.
 * Bit 16 is placed in bit 0 of the request index.
 */

/* chip type */
enum {
	SIO		= 1,
	FT8U232AM	= 2,
	FT232BM		= 3,
	FT2232C		= 4,
	FTKINDR		= 5,
	FT2232H		= 6,
	FT4232H		= 7,
};

enum {
	 FTb300		= 0,
	 FTb600		= 1,
	 FTb1200	= 2,
	 FTb2400	= 3,
	 FTb4800	= 4,
	 FTb9600	= 5,
	 FTb19200	= 6,
	 FTb38400	= 7,
	 FTb57600	= 8,
	 FTb115200	= 9,
};

/*
 * bRequest: FTSETDATA
 * wValue: Data characteristics
 *	bits 0-7 number of data bits
 * wIndex: Port
 */
enum {
	FTSETDATAParNONE	= 0 << 8,
	FTSETDATAParODD		= 1 << 8,
	FTSETDATAParEVEN	= 2 << 8,
	FTSETDATAParMARK	= 3 << 8,
	FTSETDATAParSPACE	= 4 << 8,
	FTSETDATASTOPBITS1	= 0 << 11,
	FTSETDATASTOPBITS15	= 1 << 11,
	FTSETDATASTOPBITS2	= 2 << 11,
	FTSETBREAK		= 1 << 14,
};

/*
 * bRequest: FTSETMODEMCTRL
 * wValue: ControlValue (see below)
 * wIndex: Port
 */

/*
 * bRequest: FTSETFLOWCTRL
 * wValue: Xoff/Xon
 * wIndex: Protocol/Port - hIndex is protocol; lIndex is port
 */
enum {
	FTDISABLEFLOWCTRL= 0,
	FTRTSCTSHS	= 1 << 8,
	FTDTRDSRHS	= 2 << 8,
	FTXONXOFFHS	= 4 << 8,
};

/*
 * bRequest: FTGETLATENCYTIMER
 * wIndex: Port
 * wLength: 0
 * Data: latency (on return)
 */

/*
 * bRequest: FTSETBITMODE
 * wIndex: Port
 * either it is big bang mode, in which case
 * wValue: 1 byte L is the big bang mode BIG*
 *	or BM is
 * wValue: 1 byte bitbang mode H, 1 byte bitmask for lines L
 */
enum {
	BMSERIAL	= 0,		/* reset, turn off bit-bang mode */

	BIGBMNORMAL	= 1,		/* normal bit-bang mode */
	BIGBMSPI	= 2,		/* spi bit-bang mode */

	BMABM		= 1<<8,		/* async mode */
	BMMPSSE		= 2<<8,
	BMSYNCBB	= 4<<8,		/* sync bit-bang -- 2232x and R-type */
	BMMCU		= 8<<8,		/* MCU Host Bus -- 2232x */
	BMOPTO		= 0x10<<8,	/* opto-isolated<<8, 2232x */
	BMCBUS		= 0x20<<8,	/* CBUS pins of R-type chips */
	BMSYNCFF	= 0x40<<8,	/* Single Channel Sync FIFO, 2232H only */
};

/*
 * bRequest: FTSETLATENCYTIMER
 * wValue: Latency (milliseconds 1-255)
 * wIndex: Port
 */
enum {
	FTLATENCYDEFAULT = 2,
};

/*
 * BmRequestType: SET
 * bRequest: FTSETEVENTCHAR
 * wValue: EventChar
 * wIndex: Port
 * 0-7 lower bits event char
 * 8 enable
 */
enum {
	FTEVCHARENAB = 1<<8,
};

/*
 * BmRequestType: SET
 * bRequest: FTSETERRORCHAR
 * wValue: Error Char
 * wIndex: Port
 * 0-7 lower bits event char
 * 8 enable
 */
enum {
	FTERRCHARENAB = 1<<8,
};
/*
 * BmRequestType: GET
 * bRequest: FTGETMODEMSTATUS
 * wIndex: Port
 * wLength: 1
 * Data: Status
 */
enum {
	FTCTSMASK	= 0x10,
	FTDSRMASK	= 0x20,
	FTRIMASK	= 0x40,
	FTRLSDMASK	= 0x80,
};

enum {
	/* byte 0 of in data hdr */
	FTICTS	= 1 << 4,
	FTIDSR	= 1 << 5,
	FTIRI	= 1 << 6,
	FTIRLSD	= 1 << 7,	/* receive line signal detect */

	/* byte 1 of in data hdr */
	FTIDR	= 1<<0,		/* data ready */
	FTIOE	= 1<<1,		/* overrun error */
	FTIPE	= 1<<2,		/* parity error */
	FTIFE	= 1<<3,		/* framing error */
	FTIBI	= 1<<4,		/* break interrupt */
	FTITHRE	= 1<<5,		/* xmitter holding register */
	FTITEMT	= 1<<6,		/* xmitter empty */
	FTIFIFO	= 1<<7,		/* error in rcv fifo */

	/* byte 0 of out data hdr len does not include byte 0 */
	FTOLENMSK= 0x3F,
	FTOPORT	= 0x80,		/* must be set */
};

/*
 * 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 },
	{ FTVid, AMONKEYDid },
	{ 0,	0 },
};

static Serialops ftops;

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;
	/* blank aijuboard has jtag for initial bringup */
	if(udev->csp == 0xffffff)
		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;
	ser->Serialops = ftops;

	dsprint (2, "serial: detected type: %#x\n", ser->type);
}

int
ftprobe(Serial *ser)
{
	Usbdev *ud = ser->dev->usb;

	if(matchid(ftinfo, ud->vid, ud->did) == nil)
		return -1;
	ftgettype(ser);
	return 0;
}

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;
	}
	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;
	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;
	for(;;) {
		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;
		}
	}

	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);
		/* consume it all */
		while(p->ndata != 0){
			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);
		/* 0xb is the mask for lines. plug dependant? */
		ftdiwrite(p, BMMPSSE|0x0b, 0, FTSETBITMODE);
	}
	incref(ser->dev);
	proccreate(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;
}

static Serialops ftops = {
	.init		= ftinit,
	.seteps		= ftseteps,
	.setparam	= ftsetparam,
	.clearpipes	= ftclearpipes,
	.reset		= ftreset,
	.sendlines	= ftsendlines,
	.modemctl	= ftmodemctl,
	.setbreak	= ftsetbreak,
	.wait4data	= wait4data,
	.wait4write	= wait4write,
};