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

enum {
	Enable		= 0x00,

	Getbaud		= 0x1D,
	Setbaud		= 0x1E,
	Setlcr		= 0x03,
	Getlcr		= 0x04,
		Bitsmask	= 0x0F00,
		Bitsshift	= 8,
		Parmask		= 0x00F0,
		Parshift	= 4,
		Stopmask	= 0x000F,
		Stop1		= 0x0000,
		Stop1_5		= 0x0001,
		Stop2		= 0x0002,
};

static Cinfo slinfo[] = {
	{ 0x10c4, 0xea60, },		/* CP210x */
	{ 0x10c4, 0xea61, },		/* CP210x */
	{ 0,	0, },
};

static Serialops slops;

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

	if(matchid(slinfo, ud->vid, ud->did) == nil)
		return -1;
	ser->Serialops = slops;
	return 0;
}

static int
slwrite(Serialport *p, int req, void *buf, int len)
{
	Serial *ser;

	ser = p->s;
	return usbcmd(ser->dev, Rh2d | Rvendor | Riface, req, 0, p->interfc,
		buf, len);
}

static int
slput(Serialport *p, uint op, uint val)
{
	Serial *ser;

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

static int
slread(Serialport *p, int req, void *buf, int len)
{
	Serial *ser;

	ser = p->s;
	return usbcmd(ser->dev, Rd2h | Rvendor | Riface, req, 0, p->interfc,
		buf, len);
}

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

	ser = p->s;
	dsprint(2, "slinit\n");

	slput(p, Enable, 1);

	slops.getparam(p);

	/* p gets freed by closedev, the process has a reference */
	incref(ser->dev);
	return 0;
}

static int
slgetparam(Serialport *p)
{
	u16int lcr;

	slread(p, Getbaud, &p->baud, sizeof(p->baud));
	slread(p, Getlcr, &lcr, sizeof(lcr));
	p->bits = (lcr&Bitsmask)>>Bitsshift;
	p->parity = (lcr&Parmask)>>Parshift;
	p->stop = (lcr&Stopmask) == Stop1? 1 : 2;
	return 0;
}

static int
slsetparam(Serialport *p)
{
	u16int lcr;

	lcr = p->stop == 1? Stop1 : Stop2;
	lcr |= (p->bits<<Bitsshift) | (p->parity<<Parshift);
	slput(p, Setlcr, lcr);
	slwrite(p, Setbaud, &p->baud, sizeof(p->baud));
	return 0;
}

static int
wait4data(Serialport *p, uchar *data, int count)
{
	int n;

	qunlock(p->s);
	while ((n = read(p->epin->dfd, data, count)) == 0)
		;
	qlock(p->s);
	return n;
}

static Serialops slops = {
	.init		= slinit,
	.getparam	= slgetparam,
	.setparam	= slsetparam,
	.wait4data	= wait4data,
	.findeps	= findendpoints,
};
