#include <u.h>
#include <libc.h>
#include <thread.h>
#include <fcall.h>
#include <9p.h>
#include "usb.h"
#include "serial.h"

enum {
	/* flavours of the device */
	TypeH,
	TypeHX,
	TypeUnk,

	RevH		= 0x0202,
	RevX		= 0x0300,
	RevHX		= 0x0400,
	Rev1		= 0x0001,

	/* usbcmd parameters */
	SetLineReq	= 0x20,

	SetCtlReq	= 0x22,

	BreakReq	= 0x23,
	BreakOn		= 0xffff,
	BreakOff	= 0x0000,

	GetLineReq	= 0x21,

	VendorWriteReq	= 0x01,		/* BUG: is this a standard request? */
	VendorReadReq	= 0x01,

	ParamReqSz	= 7,
	VendorReqSz	= 10,

	/* status read from interrupt endpoint */
	DcdStatus	= 0x01,
	DsrStatus	= 0x02,
	BreakerrStatus	= 0x04,
	RingStatus	= 0x08,
	FrerrStatus	= 0x10,
	ParerrStatus	= 0x20,
	OvererrStatus	= 0x40,
	CtsStatus	= 0x80,

	DcrGet		= 0x80,
	DcrSet		= 0x00,

	Dcr0Idx		= 0x00,

	Dcr0Init	= 0x0001,
	Dcr0HwFcH	= 0x0040,
	Dcr0HwFcX	= 0x0060,

	Dcr1Idx		= 0x01,

	Dcr1Init	= 0x0000,
	Dcr1InitH	= 0x0080,
	Dcr1InitX	= 0x0000,

	Dcr2Idx		= 0x02,

	Dcr2InitH	= 0x0024,
	Dcr2InitX	= 0x0044,

	PipeDSRst	= 0x08,
	PipeUSRst	= 0x09,

};

enum {
	PL2303Vid	= 0x067b,
	PL2303Did	= 0x2303,
	PL2303DidRSAQ2	= 0x04bb,
	PL2303DidDCU11	= 0x1234,
	PL2303DidPHAROS	= 0xaaa0,
	PL2303DidRSAQ3	= 0xaaa2,
	PL2303DidALDIGA	= 0x0611,
	PL2303DidMMX	= 0x0612,
	PL2303DidGPRS	= 0x0609,

	ATENVid		= 0x0557,
	ATENVid2	= 0x0547,
	ATENDid		= 0x2008,

	IODATAVid	= 0x04bb,
	IODATADid	= 0x0a03,
	IODATADidRSAQ5	= 0x0a0e,

	ELCOMVid	= 0x056e,
	ELCOMDid	= 0x5003,
	ELCOMDidUCSGT	= 0x5004,

	ITEGNOVid	= 0x0eba,
	ITEGNODid	= 0x1080,
	ITEGNODid2080	= 0x2080,

	MA620Vid	= 0x0df7,
	MA620Did	= 0x0620,

	RATOCVid	= 0x0584,
	RATOCDid	= 0xb000,

	TRIPPVid	= 0x2478,
	TRIPPDid	= 0x2008,

	RADIOSHACKVid	= 0x1453,
	RADIOSHACKDid	= 0x4026,

	DCU10Vid	= 0x0731,
	DCU10Did	= 0x0528,

	SITECOMVid	= 0x6189,
	SITECOMDid	= 0x2068,

	 /* Alcatel OT535/735 USB cable */
	ALCATELVid	= 0x11f7,
	ALCATELDid	= 0x02df,

	/* Samsung I330 phone cradle */
	SAMSUNGVid	= 0x04e8,
	SAMSUNGDid	= 0x8001,

	SIEMENSVid	= 0x11f5,
	SIEMENSDidSX1	= 0x0001,
	SIEMENSDidX65	= 0x0003,
	SIEMENSDidX75	= 0x0004,
	SIEMENSDidEF81	= 0x0005,

	SYNTECHVid	= 0x0745,
	SYNTECHDid	= 0x0001,

	/* Nokia CA-42 Cable */
	NOKIACA42Vid	= 0x078b,
	NOKIACA42Did	= 0x1234,

	/* CA-42 CLONE Cable www.ca-42.com chipset: Prolific Technology Inc */
	CA42CA42Vid	= 0x10b5,
	CA42CA42Did	= 0xac70,

	SAGEMVid	= 0x079b,
	SAGEMDid	= 0x0027,

	/* Leadtek GPS 9531 (ID 0413:2101) */
	LEADTEKVid	= 0x0413,
	LEADTEK9531Did	= 0x2101,

	 /* USB GSM cable from Speed Dragon Multimedia, Ltd */
	SPEEDDRAGONVid	= 0x0e55,
	SPEEDDRAGONDid	= 0x110b,

	/* DATAPILOT Universal-2 Phone Cable */
	BELKINVid	= 0x050d,
	BELKINDid	= 0x0257,

	/* Belkin "F5U257" Serial Adapter */
	DATAPILOTU2Vid	= 0x0731,
	DATAPILOTU2Did	= 0x2003,

	ALCORVid	= 0x058F,
	ALCORDid	= 0x9720,

	/* Willcom WS002IN Data Driver (by NetIndex Inc.) */,
	WS002INVid	= 0x11f6,
	WS002INDid	= 0x2001,

	/* Corega CG-USBRS232R Serial Adapter */,
	COREGAVid	= 0x07aa,
	COREGADid	= 0x002a,

	/* Y.C. Cable U.S.A., Inc - USB to RS-232 */,
	YCCABLEVid	= 0x05ad,
	YCCABLEDid	= 0x0fba,

	/* "Superial" USB - Serial */,
	SUPERIALVid	= 0x5372,
	SUPERIALDid	= 0x2303,

	/* Hewlett-Packard LD220-HP POS Pole Display */,
	HPVid		= 0x03f0,
	HPLD220Did	= 0x3524,
};

Cinfo plinfo[] = {
	{ PL2303Vid,	PL2303Did },
	{ PL2303Vid,	PL2303DidRSAQ2 },
	{ PL2303Vid,	PL2303DidDCU11 },
	{ PL2303Vid,	PL2303DidRSAQ3 },
	{ PL2303Vid,	PL2303DidPHAROS },
	{ PL2303Vid,	PL2303DidALDIGA },
	{ PL2303Vid,	PL2303DidMMX },
	{ PL2303Vid,	PL2303DidGPRS },
	{ IODATAVid,	IODATADid },
	{ IODATAVid,	IODATADidRSAQ5 },
	{ ATENVid,	ATENDid },
	{ ATENVid2,	ATENDid },
	{ ELCOMVid,	ELCOMDid },
	{ ELCOMVid,	ELCOMDidUCSGT },
	{ ITEGNOVid,	ITEGNODid },
	{ ITEGNOVid,	ITEGNODid2080 },
	{ MA620Vid,	MA620Did },
	{ RATOCVid,	RATOCDid },
	{ TRIPPVid,	TRIPPDid },
	{ RADIOSHACKVid,RADIOSHACKDid },
	{ DCU10Vid,	DCU10Did },
	{ SITECOMVid,	SITECOMDid },
	{ ALCATELVid,	ALCATELDid },
	{ SAMSUNGVid,	SAMSUNGDid },
	{ SIEMENSVid,	SIEMENSDidSX1 },
	{ SIEMENSVid,	SIEMENSDidX65 },
	{ SIEMENSVid,	SIEMENSDidX75 },
	{ SIEMENSVid,	SIEMENSDidEF81 },
	{ SYNTECHVid,	SYNTECHDid },
	{ NOKIACA42Vid,	NOKIACA42Did },
	{ CA42CA42Vid,	CA42CA42Did },
	{ SAGEMVid,	SAGEMDid },
	{ LEADTEKVid,	LEADTEK9531Did },
	{ SPEEDDRAGONVid,SPEEDDRAGONDid },
	{ DATAPILOTU2Vid,DATAPILOTU2Did },
	{ BELKINVid,	BELKINDid },
	{ ALCORVid,	ALCORDid },
	{ WS002INVid,	WS002INDid },
	{ COREGAVid,	COREGADid },
	{ YCCABLEVid,	YCCABLEDid },
	{ SUPERIALVid,	SUPERIALDid },
	{ HPVid,	HPLD220Did },
	{ 0,		0 },
};

static Serialops plops;

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

	if(matchid(plinfo, ud->vid, ud->did) == nil)
		return -1;
	ser->hasepintr = 1;
	ser->Serialops = plops;
	return 0;
}

static void	statusreader(void *u);

static void
dumpbuf(uchar *buf, int bufsz)
{
	int i;

	for(i=0; i<bufsz; i++)
		print("buf[%d]=%#ux ", i, buf[i]);
	print("\n");
}

static int
vendorread(Serialport *p, int val, int index, uchar *buf)
{
	int res;
	Serial *ser;

	ser = p->s;

	dsprint(2, "serial: vendorread val: 0x%x idx:%d buf:%p\n",
		val, index, buf);
	res = usbcmd(ser->dev,  Rd2h | Rvendor | Rdev, VendorReadReq,
		val, index, buf, 1);
	if(res != 1) fprint(2, "serial: vendorread failed with res=%d\n", res);
	return res;
}

static int
vendorwrite(Serialport *p, int val, int index)
{
	int res;
	Serial *ser;

	ser = p->s;

	dsprint(2, "serial: vendorwrite val: 0x%x idx:%d\n", val, index);
	res = usbcmd(ser->dev, Rh2d | Rvendor | Rdev, VendorWriteReq,
		val, index, nil, 0);
	if(res != 0) fprint(2, "serial: vendorwrite failed with res=%d\n", res);
	return res;
}

/* BUG: I could probably read Dcr0 and set only the bits */
static int
plmodemctl(Serialport *p, int set)
{
	Serial *ser;

	ser = p->s;

	if(set == 0){
		p->mctl = 0;
		vendorwrite(p, Dcr0Idx|DcrSet, Dcr0Init);
		return 0;
	}

	p->mctl = 1;
	if(ser->type == TypeHX)
		vendorwrite(p, Dcr0Idx|DcrSet, Dcr0Init|Dcr0HwFcX);
	else
		vendorwrite(p, Dcr0Idx|DcrSet, Dcr0Init|Dcr0HwFcH);
	return 0;
}

static int
plgetparam(Serialport *p)
{
	uchar buf[ParamReqSz];
	int res;
	Serial *ser;

	ser = p->s;


	res = usbcmd(ser->dev, Rd2h | Rclass | Riface, GetLineReq,
		0, 0, buf, sizeof buf);
	if(res != ParamReqSz)
		memset(buf, 0, sizeof(buf));
	p->baud = GET4(buf);

	/*
	 * with the Pl9 interface it is not possible to set `1.5' as stop bits
	 * for the prologic:
	 *	0 is 1 stop bit
	 *	1 is 1.5 stop bits
	 *	2 is 2 stop bits
	 */
	if(buf[4] == 1)
		fprint(2, "warning, stop bit set to 1.5 unsupported");
	else if(buf[4] == 0)
		p->stop = 1;
	else if(buf[4] == 2)
		p->stop = 2;
	p->parity = buf[5];
	p->bits = buf[6];

	dsprint(2, "serial: getparam: ");
	if(serialdebug)
		dumpbuf(buf, sizeof buf);

	if(res == ParamReqSz)
		return 0;
	fprint(2, "serial: plgetparam failed with res=%d\n", res);
	if(res >= 0) werrstr("plgetparam failed with res=%d", res);
	return -1;
}

static int
plsetparam(Serialport *p)
{
	uchar buf[ParamReqSz];
	int res;
	Serial *ser;

	ser = p->s;

	PUT4(buf, p->baud);

	if(p->stop == 1)
		buf[4] = 0;
	else if(p->stop == 2)
		buf[4] = 2; 			/* see comment in getparam */
	buf[5] = p->parity;
	buf[6] = p->bits;

	dsprint(2, "serial: setparam: ");
	if(serialdebug)
		dumpbuf(buf, sizeof buf);
	res = usbcmd(ser->dev, Rh2d | Rclass | Riface, SetLineReq,
		0, 0, buf, sizeof buf);
	if(res != ParamReqSz){
		fprint(2, "serial: plsetparam failed with res=%d\n", res);
		if(res >= 0) werrstr("plsetparam failed with res=%d", res);
		return -1;
	}
	plmodemctl(p, p->mctl);
	if(plgetparam(p) < 0)		/* make sure our state corresponds */
		return -1;

	return 0;
}

static int
revid(ulong devno)
{
	switch(devno){
	case RevH:
		return TypeH;
	case RevX:
	case RevHX:
	case Rev1:
		return TypeHX;
	default:
		return TypeUnk;
	}
}

/* linux driver says the release id is not always right */
static int
heuristicid(ulong csp, ulong maxpkt)
{
	if(Class(csp) == 0x02)
		return TypeH;
	else if(maxpkt == 0x40)
		return TypeHX;
	else if(Class(csp) == 0x00 || Class(csp) == 0xFF)
		return TypeH;
	else{
		fprint(2, "serial: chip unknown, setting to HX version\n");
		return TypeHX;
	}
}

static int
plinit(Serialport *p)
{
	char *st;
	uchar *buf;
	ulong csp, maxpkt, dno;
	Serial *ser;

	ser = p->s;
	buf = emallocz(VendorReqSz, 1);
	dsprint(2, "plinit\n");

	csp = ser->dev->usb->csp;
	maxpkt = ser->dev->maxpkt;
	dno = ser->dev->usb->dno;

	if((ser->type = revid(dno)) == TypeUnk)
		ser->type = heuristicid(csp, maxpkt);

	dsprint(2, "serial: type %d\n", ser->type);

	vendorread(p, 0x8484, 0, buf);
	vendorwrite(p, 0x0404, 0);
	vendorread(p, 0x8484, 0, buf);
	vendorread(p, 0x8383, 0, buf);
	vendorread(p, 0x8484, 0, buf);
	vendorwrite(p, 0x0404, 1);
	vendorread(p, 0x8484, 0, buf);
	vendorread(p, 0x8383, 0, buf);

	vendorwrite(p, Dcr0Idx|DcrSet, Dcr0Init);
	vendorwrite(p, Dcr1Idx|DcrSet, Dcr1Init);

	if(ser->type == TypeHX)
		vendorwrite(p, Dcr2Idx|DcrSet, Dcr2InitX);
	else
		vendorwrite(p, Dcr2Idx|DcrSet, Dcr2InitH);

	plgetparam(p);
	qunlock(ser);
	free(buf);
	qlock(ser);
	if(serialdebug){
		st = emallocz(255, 1);
		serdumpst(p, st, 255);
		dsprint(2, "%s", st);
		free(st);
	}
	/* p gets freed by closedev, the process has a reference */
	incref(ser->dev);
	proccreate(statusreader, p, 8*1024);
	return 0;
}

static int
plsetbreak(Serialport *p, int val)
{
	Serial *ser;

	ser = p->s;
	return usbcmd(ser->dev, Rh2d | Rclass | Riface,
		(val != 0? BreakOn: BreakOff), val, 0, nil, 0);
}

static int
plclearpipes(Serialport *p)
{
	Serial *ser;

	ser = p->s;

	if(ser->type == TypeHX){
		vendorwrite(p, PipeDSRst, 0);
		vendorwrite(p, PipeUSRst, 0);
	}else{
		if(unstall(ser->dev, p->epout, Eout) < 0)
			dprint(2, "disk: unstall epout: %r\n");
		if(unstall(ser->dev, p->epin, Ein) < 0)
			dprint(2, "disk: unstall epin: %r\n");
		if(unstall(ser->dev, p->epintr, Ein) < 0)
			dprint(2, "disk: unstall epintr: %r\n");
	}
	return 0;
}

static int
setctlline(Serialport *p, uchar val)
{
	Serial *ser;

	ser = p->s;
	return usbcmd(ser->dev, Rh2d | Rclass | Riface, SetCtlReq,
		val, 0, nil, 0);
}

static void
composectl(Serialport *p)
{
	if(p->rts)
		p->ctlstate |= CtlRTS;
	else
		p->ctlstate &= ~CtlRTS;
	if(p->dtr)
		p->ctlstate |= CtlDTR;
	else
		p->ctlstate &= ~CtlDTR;
}

static int
plsendlines(Serialport *p)
{
	int res;

	dsprint(2, "serial: sendlines: %#2.2x\n", p->ctlstate);
	composectl(p);
	res = setctlline(p, p->ctlstate);
	dsprint(2, "serial: sendlines res: %d\n", res);
	return 0;
}

static int
plreadstatus(Serialport *p)
{
	int nr, dfd;
	char err[ERRMAX];
	uchar buf[VendorReqSz];
	Serial *ser;

	ser = p->s;

	qlock(ser);
	dfd = p->epintr->dfd;
	qunlock(ser);
	nr = read(dfd, buf, sizeof buf);
	qlock(ser);
	rerrstr(err, sizeof err);
	if(nr < 0 && strstr(err, "timed out") == nil){
		if(serialrecover(ser, nil, nil, err) < 0){
			qunlock(ser);
			return -1;
		}
	}
	if(nr < 0)
		dsprint(2, "serial: reading status: %r\n");
	else if(nr >= sizeof buf - 1){
		p->dcd = buf[8] & DcdStatus;
		p->dsr = buf[8] & DsrStatus;
		p->cts = buf[8] & BreakerrStatus;
		p->ring = buf[8] & RingStatus;
		p->cts = buf[8] & CtsStatus;
		if(buf[8] & FrerrStatus)
			p->nframeerr++;
		if(buf[8] & ParerrStatus)
			p->nparityerr++;
		if(buf[8] & OvererrStatus)
			p->novererr++;
	} else
		dsprint(2, "serial: bad status read %d\n", nr);
	qunlock(ser);
	return 0;
}

static void
statusreader(void *u)
{
	Serialport *p;
	Serial *ser;

	p = u;
	ser = p->s;
	threadsetname("statusreaderproc");
	while(plreadstatus(p) >= 0)
		;
	fprint(2, "serial: statusreader exiting\n");
	closedev(ser->dev);
}

/*
 * Maximum number of bytes transferred per frame
 * The output buffer size cannot be increased due to the size encoding
 */

static int
plseteps(Serialport *p)
{
	devctl(p->epin,  "maxpkt 256");
	devctl(p->epout, "maxpkt 256");
	return 0;
}

static Serialops plops = {
	.init		= plinit,
	.getparam	= plgetparam,
	.setparam	= plsetparam,
	.clearpipes	= plclearpipes,
	.sendlines	= plsendlines,
	.modemctl	= plmodemctl,
	.setbreak	= plsetbreak,
	.seteps		= plseteps,
	.findeps	= findendpoints,
};
