/*	
 *	clipcp21.c
 *
 *	Silicon Labs CP210x VCP (Virtual COM Port) Emulator
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2007 Naoyuki Sawa
 *
 *	* Sun Dec 09 16:32:20 JST 2007 Naoyuki Sawa
 *	- 1st リリース。
 *	- 送受信の動作はclipacmモジュールと同じです。
 *	  clipacm.cを参照してください。
 *	* Tue Dec 11 14:26:45 JST 2007 Naoyuki Sawa
 *	- clipacm.cが送信ダブルバッファを利用するよう変更したのに追従しました。
 */
#include "clip.h"

/****************************************************************************
 *	ディスクリプタ定義
 ****************************************************************************/

/* * Windows XPにHDL-GT/LCD実機を接続し、Microsoft USB device viewer(UVCView.exe)を使って情報を取得しました。
 *   一部、ERRORやCAUTIONが表示されていて、実機のディスクリプタが誤った設定値を含んでいることがわかりますが、
 *   HDL-GT/LCD実機が特に問題無く動作していることから、許容できる程度の誤りなのだと思います。
 * 
 * 	          ---===>Device Information<===---
 * 	English product name: "CP2102 USB to UART Bridge Controller"
 * 
 * 	ConnectionStatus:                  
 * 	Current Config Value:              0x01  -> Device Bus Speed: Full
 * 	Device Address:                    0x02
 * 	Open Pipes:                           2
 * 
 * 	          ===>Endpoint Descriptor<===
 * 	bLength:                           0x07
 * 	bDescriptorType:                   0x05
 * 	bEndpointAddress:                  0x81  -> Direction: IN - EndpointID: 1
 * 	bmAttributes:                      0x02  -> Bulk Transfer Type
 * 	wMaxPacketSize:                  0x0040 = 0x40 bytes
 * 	bInterval:                         0x00
 * 
 * 	          ===>Endpoint Descriptor<===
 * 	bLength:                           0x07
 * 	bDescriptorType:                   0x05
 * 	bEndpointAddress:                  0x01  -> Direction: OUT - EndpointID: 1
 * 	bmAttributes:                      0x02  -> Bulk Transfer Type
 * 	wMaxPacketSize:                  0x0040 = 0x40 bytes
 * 	bInterval:                         0x00
 * 
 * 	          ===>Device Descriptor<===
 * 	bLength:                           0x12
 * 	bDescriptorType:                   0x01
 * 	bcdUSB:                          0x0110
 * 	bDeviceClass:                      0x00  -> This is an Interface Class Defined Device
 * 	bDeviceSubClass:                   0x00
 * 	bDeviceProtocol:                   0x00
 * 	bMaxPacketSize0:                   0x40 = (64) Bytes
 * 	idVendor:                        0x10C4 = Silicon Laboratories, Inc.
 * 	idProduct:                       0xEA60
 * 	bcdDevice:                       0x0100
 * 	iManufacturer:                     0x01
 * 	     English (United States)  "Silicon Labs"
 * 	iProduct:                          0x02
 * 	     English (United States)  "CP2102 USB to UART Bridge Controller"
 * 	iSerialNumber:                     0x03
 * 	     English (United States)  "10212"
 * 	bNumConfigurations:                0x01
 * 
 * 	          ===>Configuration Descriptor<===
 * 	bLength:                           0x09
 * 	bDescriptorType:                   0x02
 * 	wTotalLength:                    0x0020  -> Validated
 * 	bNumInterfaces:                    0x01
 * 	bConfigurationValue:               0x01
 * 	iConfiguration:                    0x00
 * 	bmAttributes:                      0x80  -> Bus Powered
 * 	MaxPower:                          0x32 = 100 mA
 * 
 * 	          ===>Interface Descriptor<===
 * 	bLength:                           0x09
 * 	bDescriptorType:                   0x04
 * 	bInterfaceNumber:                  0x00
 * 	bAlternateSetting:                 0x00
 * 	bNumEndpoints:                     0x02
 * 	bInterfaceClass:                   0xFF  -> Vendor Specific Device
 * 	bInterfaceSubClass:                0x00
 * 	*!*CAUTION:    This appears to be an invalid bInterfaceSubClass
 * 	bInterfaceProtocol:                0x00
 * 	iInterface:                        0x02
 * 	     English (United States)  "CP2102 USB to UART Bridge Controller"
 * 	*!*ERROR:  0xFF is the prerelease USB Video Class ID
 * 
 * 	          ===>Endpoint Descriptor<===
 * 	bLength:                           0x07
 * 	bDescriptorType:                   0x05
 * 	bEndpointAddress:                  0x81  -> Direction: IN - EndpointID: 1
 * 	bmAttributes:                      0x02  -> Bulk Transfer Type
 * 	wMaxPacketSize:                  0x0040 = 0x40 bytes
 * 	bInterval:                         0x00
 * 
 * 	          ===>Endpoint Descriptor<===
 * 	bLength:                           0x07
 * 	bDescriptorType:                   0x05
 * 	bEndpointAddress:                  0x01  -> Direction: OUT - EndpointID: 1
 * 	bmAttributes:                      0x02  -> Bulk Transfer Type
 * 	wMaxPacketSize:                  0x0040 = 0x40 bytes
 * 	bInterval:                         0x00
 * 
 * * iSerialNumberは、HDL-GT/LCD実機個別の値だと思うので、本モジュールでは設定しないことにしました。
 *   そのため、HDL-GT/LCD実機と本モジュールとでは、レジストリ登録位置が異なります。
 * - HDL-GT/LCD実機は、シリアル番号によって識別され、レジストリに登録されます。
 * 	<例> HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\Vid_10c4&Pid_ea60&Mi_00\10212_00
 * - 本モジュールは、接続経路によって識別され、レジストリに登録されます。
 * 	<例> HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\Vid_10c4&Pid_ea60&Mi_00\7&d099c9&1&2_00
 *   レジストリ登録位置の違いはドライバやアプリケーションに影響を及ぼさないので、大丈夫だと思います。
 *
 * * Windowsの「新しいハードウェアの検出ウィザード」は、CP210xのPIDを見て、WindowsUpdateを検索し、自動的にデバイスドライバをインストールします。
 *   CP210xのPIDには、工場出荷設定値として二種類の値が定められていて、二種類のドライバと以下のように対応します。
 *	VID=0x10C4,PID=0xEA60 -> VCP(Virtual COM Port)ドライバ
 *	VID=0x10C4,PID=0xEA61 -> USBXpressドライバ
 *   CP210xのICハードウェア自体は同じもので、PIDによってドライバを切り替えるという珍しい仕様です。
 *   もし、もう一方のドライバが使いたい場合は、特殊なユーティリティプログラムを使って、CP210xのPIDを書き換える必要があります。
 * - HDL-GT/LCDは、仮想COMポート経由で制御する製品ですから、HDL-GT/LCDに搭載されているCP2102には、
 *   VCP(Virtual COM Port)ドライバを示す、(VID=0x10C4,PID=0xEA60)が書き込まれています。
 */

/*--------------------------------------------------------------------------*/

static const DEVICE_DESCRIPTOR device_desc = {
	bLength:		sizeof(DEVICE_DESCRIPTOR),
	bDescriptorType:	DEVICE_DESCRIPTOR_TYPE,
	bcdUSB:			0x0110,
	bMaxPacketSize0:	EP0_PACKET_SIZE,
	idVendor:		0x10C4,					/* Silicon Laboratories, Inc. */
	idProduct:		0xEA60,					/* CP210x VCP */
	bcdDevice:		0x0100,
	iManufacture:		1,					/* "Silicon Labs" */
	iProduct:		2,					/* "CP2102 USB to UART Bridge Controller" */
//	iSerialNumber:		3,					/* "10212" → HDL-GT/LCD実機個別の値だと思うので、設定しないことにしました */
	bNumConfigurations:	1,
};

/*--------------------------------------------------------------------------*/

static const CONFIGURATION_DESCRIPTOR config_desc = {
	bLength:		sizeof(CONFIGURATION_DESCRIPTOR),
	bDescriptorType:	CONFIGURATION_DESCRIPTOR_TYPE,
	wTotalLength:		sizeof(CONFIGURATION_DESCRIPTOR) +	/* config_desc */
				sizeof(INTERFACE_DESCRIPTOR    ) +	/* interface_desc */
				sizeof(ENDPOINT_DESCRIPTOR     ) +	/* endpoint_desc_ep2in */
				sizeof(ENDPOINT_DESCRIPTOR     ),	/* endpoint_desc_ep2out */
	bNumInterfaces:		1,
	bConfigurationValue:	1,
	bmAttributes:		0x80,					/* Bus Powered */
	MaxPower:		100 / 2,				/* 100 mA */
};
static const INTERFACE_DESCRIPTOR interface_desc = {
	bLength:		sizeof(INTERFACE_DESCRIPTOR),
	bDescriptorType:	INTERFACE_DESCRIPTOR_TYPE,
	bNumEndpoints:		2,
	bInterfaceClass:	0xFF,					/* Vendor Specific Device */
	iInterface:		2,					/* "CP2102 USB to UART Bridge Controller" */
};
static const ENDPOINT_DESCRIPTOR endpoint_desc_ep2in = {
	bLength:		sizeof(ENDPOINT_DESCRIPTOR),
	bDescriptorType:	ENDPOINT_DESCRIPTOR_TYPE,
	bEndpointAddress:	0x82,					/* EP2IN */
	bAttributes:		2,					/* Bulk */
	wMaxPacketSize:		EP2_NONISO_PACKET_SIZE,
};
static const ENDPOINT_DESCRIPTOR endpoint_desc_ep2out = {
	bLength:		sizeof(ENDPOINT_DESCRIPTOR),
	bDescriptorType:	ENDPOINT_DESCRIPTOR_TYPE,
	bEndpointAddress:	0x02,					/* EP2OUT */
	bAttributes:		2,					/* Bulk */
	wMaxPacketSize:		EP2_NONISO_PACKET_SIZE,
};

/*--------------------------------------------------------------------------*/

static const char* const string_desc[] = {
	"\x04\3"	// 0x02 = English (United States)
	"\x9\x4",	// 0x04
	"\x1A\3"	// 0x02 = "Silicon Labs"
	"S" "\0"	// 0x04
	"i" "\0"	// 0x06
	"l" "\0"	// 0x08
	"i" "\0"	// 0x0A
	"c" "\0"	// 0x0C
	"o" "\0"	// 0x0E
	"n" "\0"	// 0x10
	" " "\0"	// 0x12
	"L" "\0"	// 0x14
	"a" "\0"	// 0x16
	"b" "\0"	// 0x18
	"s" "\0",	// 0x1A
	"\x4A\3"	// 0x02 = "CP2102 USB to UART Bridge Controller"
	"C" "\0"	// 0x04
	"P" "\0"	// 0x06
	"2" "\0"	// 0x08
	"1" "\0"	// 0x0A
	"0" "\0"	// 0x0C
	"2" "\0"	// 0x0E
	" " "\0"	// 0x10
	"U" "\0"	// 0x12
	"S" "\0"	// 0x14
	"B" "\0"	// 0x16
	" " "\0"	// 0x18
	"t" "\0"	// 0x1A
	"o" "\0"	// 0x1C
	" " "\0"	// 0x1E
	"U" "\0"	// 0x20
	"A" "\0"	// 0x22
	"R" "\0"	// 0x24
	"T" "\0"	// 0x26
	" " "\0"	// 0x28
	"B" "\0"	// 0x2A
	"r" "\0"	// 0x2C
	"i" "\0"	// 0x2E
	"d" "\0"	// 0x30
	"g" "\0"	// 0x32
	"e" "\0"	// 0x34
	" " "\0"	// 0x36
	"C" "\0"	// 0x38
	"o" "\0"	// 0x3A
	"n" "\0"	// 0x3C
	"t" "\0"	// 0x3E
	"r" "\0"	// 0x40
	"o" "\0"	// 0x42
	"l" "\0"	// 0x44
	"l" "\0"	// 0x46
	"e" "\0"	// 0x48
	"r" "\0",	// 0x4A
};

/****************************************************************************
 *	
 ****************************************************************************/

typedef struct _CP210X {
	unsigned char* rxbuf/*[rxcap]*/;
	int rxcap;
	int rxpos;
	int rxlen;
	int rxlog;
	int rxerr;
	void (*rxdone)();
	//
	unsigned char* txbuf/*[txcap]*/;
	int txcap;
	int txpos;
	int txlen;
	int txlog;
	void (*txdone)();
} CP210X;
static CP210X cp210x;

static void get_descriptor(const DEVICE_REQUEST* req, const unsigned char* dat, int len);
static void set_configuration(const DEVICE_REQUEST* req, const unsigned char* dat, int len);
static void set_interface(const DEVICE_REQUEST* req, const unsigned char* dat, int len);
static void vendor_request(const DEVICE_REQUEST* req, const unsigned char* dat, int len);
static void ep2_rxdone();
static void ep2_rxsub();
static void ep2_txdone();
static void ep2_txsub();

static const USBINF usb_inf = {
	/* USBC mode */
	mode:			D12_ENDP_NONISO,
	/* USBC raw event */
	ep2_rxdone:		ep2_rxdone,
	ep2_txdone:		ep2_txdone,
	/* Device request */
	get_descriptor:		get_descriptor,
	set_configuration:	set_configuration,
	set_interface:		set_interface,
	vendor_request:		vendor_request,
};

/****************************************************************************
 *	
 ****************************************************************************/

static void
get_descriptor(const DEVICE_REQUEST* req, const unsigned char* dat, int len)
{
	static unsigned char tmpbuf[sizeof(CONFIGURATION_DESCRIPTOR) +	/* config_desc */
				    sizeof(INTERFACE_DESCRIPTOR    ) +	/* interface_desc */
				    sizeof(ENDPOINT_DESCRIPTOR     ) +	/* endpoint_desc_ep2in */
				    sizeof(ENDPOINT_DESCRIPTOR     )];	/* endpoint_desc_ep2out */
	//
	int type  = HIBYTE(req->wValue);
	int index = LOBYTE(req->wValue);

	switch(type) {
	case DEVICE_DESCRIPTOR_TYPE:
		usb_ep0_tx(&device_desc, sizeof(DEVICE_DESCRIPTOR));
		return;
	case CONFIGURATION_DESCRIPTOR_TYPE:
		switch(index) {
		case 0:
			len = 0;
			memcpy(&tmpbuf[len], &config_desc         , sizeof(CONFIGURATION_DESCRIPTOR)); len += sizeof(CONFIGURATION_DESCRIPTOR);
			memcpy(&tmpbuf[len], &interface_desc      , sizeof(INTERFACE_DESCRIPTOR    )); len += sizeof(INTERFACE_DESCRIPTOR    );
			memcpy(&tmpbuf[len], &endpoint_desc_ep2in , sizeof(ENDPOINT_DESCRIPTOR     )); len += sizeof(ENDPOINT_DESCRIPTOR     );
			memcpy(&tmpbuf[len], &endpoint_desc_ep2out, sizeof(ENDPOINT_DESCRIPTOR     )); len += sizeof(ENDPOINT_DESCRIPTOR     );
			usb_ep0_tx(tmpbuf, len);
			return;
		}
		break;
	case STRING_DESCRIPTOR_TYPE:
		if(index < ARRAY_SIZE(string_desc)) {
			usb_ep0_tx(string_desc[index], string_desc[index][0]);
			return;
		}
		break;
	}
	usb_ep0_stall(); /* NAK */
}

static void
set_configuration(const DEVICE_REQUEST* req, const unsigned char* dat, int len)
{
	D12_SetEndpointEnable(req->wValue == 1);
	usb_ep0_tx(NULL, 0); /* ACK */
}

static void
set_interface(const DEVICE_REQUEST* req, const unsigned char* dat, int len)
{
	usb_ep0_tx(NULL, 0); /* ACK */
}

static unsigned short cp210x_uart;	/* 0 = disable, 1 = enable */
static unsigned short cp210x_baudrate = 3686400/9600;	/* = 3686400/baud */
static struct cp210x_bits {
	unsigned short stop	:  4;	/* 0 = 1, 1 = 1.5, 2 = 2 */
	unsigned short parity	:  4;	/* 0 = none, 1 = odd, 2 = even, 3 = mark, 4 = space */
	unsigned short data	:  4;	/* 5, 6, 7, 8, 9 */
	unsigned short		:  4;
} cp210x_bits = {0,0,8};
static unsigned short cp210x_break;	/* 0 = off, 1 = on (※TeraTermProで実験すると、左のとおりです。Linuxドライバのコメントは逆みたいですが、誤り?) */
static struct cp210x_control {
	unsigned short dtr	:  1;
	unsigned short rts	:  1;
	unsigned short		:  2;
	unsigned short cts	:  1;
	unsigned short dsr	:  1;
	unsigned short ri	:  1;
	unsigned short cd	:  1;
	unsigned short wr_dtr	:  1;
	unsigned short wr_rts	:  1;
	unsigned short		:  6;
} cp210x_control;
static struct cp210x_modemctl {
	unsigned short		:  3;
	unsigned short rtscts	:  1;
	unsigned short		: 12;
	unsigned short		: 16;
	unsigned int		: 32;
	unsigned int		: 32;
	unsigned int		: 32;
} cp210x_modemctl;
static unsigned char cp210x_unknown[6];

/* * Sun Dec 09 14:02:28 JST 2007 Naoyuki Sawa
 * - Windows 2000に接続してVCPドライバを使ってCOMポートを開いたときに送られて来るVendor requestのうち、
 *   現時点で不明なものの一覧は、以下のとおりです。
 * 
 * 	bmRequestType	bRequest	wValue	wIndex	wLength
 * 	-------------	--------	------	------	-------
 * 	0x41		0x11		0x0000	0x0000	0
 * 	0x41		0x12		0x000F	0x0000	0
 * 	0xC1		0x0F		0x0000	0x0000	576
 * 
 *   これらのVendor requestに対して、EP0 stallで拒否してしまっても、ハイパーターミナルは動作しました。
 * - (bmRequestType=0x41,bRequest=0x19)も不明なVendor requestのひとつで、拒否すると動作しませんでした。
 *   ハイパーターミナルでCOMポートを開いた瞬間に、ハングアップしてしまいます。(Windowsは生きています。)
 *   仕方が無いので、ダミー動作で対応しておくことにしました。
 */
static void
vendor_request(const DEVICE_REQUEST* req, const unsigned char* dat, int len)
{
	switch(req->bmRequestType) {
	case 0x41: /* Interface,Out */
		switch(req->bRequest) {
		case 0x00:
			cp210x_uart = req->wValue;
			usb_ep0_tx(NULL, 0); /* ACK */
			return;
		case 0x01:
			cp210x_baudrate = req->wValue;
			usb_ep0_tx(NULL, 0); /* ACK */
			return;
		case 0x03:
			memcpy(&cp210x_bits, &req->wValue, 2);
			usb_ep0_tx(NULL, 0); /* ACK */
			return;
		case 0x05:
			cp210x_break = req->wValue;
			usb_ep0_tx(NULL, 0); /* ACK */
			return;
		case 0x07:
			{
				struct cp210x_control tmp;
				memcpy(&tmp, &req->wValue, 2);
				if(!tmp.wr_dtr) tmp.dtr = cp210x_control.dtr;
				if(!tmp.wr_rts) tmp.rts = cp210x_control.rts;
				tmp.cts = tmp.rts;
				tmp.dsr = tmp.dtr;
				tmp.cd  = tmp.dtr;	/* HDL-GT/LCDはDTR-DSR結線のみでCDは常にoffですが、汎用性のためにCDにも結線しておくことにしました。 */
				cp210x_control = tmp;
			}
			usb_ep0_tx(NULL, 0); /* ACK */
			return;
		case 0x13:
			memcpy(&cp210x_modemctl, dat, 16);
			usb_ep0_tx(NULL, 0); /* ACK */
			return;
		case 0x19:
			memcpy(cp210x_unknown, dat, 6);
			usb_ep0_tx(NULL, 0); /* ACK */
			return;
		}
		break;
	case 0xc1: /* Interface,In */
		switch(req->bRequest) {
		case 0x00:
			usb_ep0_tx(&cp210x_uart, 2);
			return;
		case 0x01:
			usb_ep0_tx(&cp210x_baudrate, 2);
			return;
		case 0x03:
			usb_ep0_tx(&cp210x_bits, 2);
			return;
		case 0x05:
			usb_ep0_tx(&cp210x_break, 2);
			return;
		case 0x07:
		case 0x08: /* ←※要調査。以下のコメント参照 */
			/* LinuxドライバではbRequest=0x07となっていますが、bRequest=0x08の間違いではないでしょうか?
			 * RTS,DTRの設定が(bmRequestType=0x41,bRequest=0x07)で、CTS,DSRの取得が(bmRequestType=0xC1,bRequest=0x08)ではないかと思います。
			 * Windows環境では(bmRequestType=0xC1,bRequest=0x07)は来ないようですが、とりあえず、0x07,0x08の両方で対応しておくことにします。
			 * Linuxドライバの場合、(bmRequestType=0xC1,bRequest=0x07)で来るかも知れないからです。
			 */
			usb_ep0_tx(&cp210x_control, 2); /* 実際にはwr_*の部分は要求されずwLength=1ですが、usb_ep0_tx()が自動的に切り詰めてくれます。 */
			return;
		case 0x13:
			usb_ep0_tx(&cp210x_modemctl, 16);
			return;
		case 0x19:
			usb_ep0_tx(cp210x_unknown, 6);
			return;
		}
		break;
	}
	usb_ep0_stall(); /* NAK */
}

/****************************************************************************
 *	以下の処理はclipacm.cと同じです。clipacm.cを参照してください。
 ****************************************************************************/

static void
ep2_rxdone()
{
	ep2_rxsub();
	if(cp210x.rxdone) {
		cp210x.rxdone();
	}
}

static void
ep2_rxsub()
{
	unsigned char _dat[EP2_NONISO_PACKET_SIZE];
	unsigned char* dat;
	int len;
	int pos;

	for(;;) {
		len = D12_ReadBuffer(EP2OUT, _dat, sizeof _dat);
		if((len == -1) ||
		   (len > (cp210x.rxcap - cp210x.rxlen))) {
			break;
		}
		D12_ClearBuffer(EP2OUT);
		if(len) {
			pos = (cp210x.rxpos + cp210x.rxlen);
			cp210x.rxlen += len;
			dat = _dat;
			do {
				if(pos >= cp210x.rxcap) {
					pos -= cp210x.rxcap;
				}
				cp210x.rxbuf[pos] = *dat++;
				pos++;
			} while(--len);
		}
	}
}

static void
ep2_txdone()
{
	if(cp210x.txlen) {
		ep2_txsub();
	} else {
		if(cp210x.txdone) {
			cp210x.txdone();
		}
	}
}

static void
ep2_txsub()
{
	unsigned char _dat[EP2_NONISO_PACKET_SIZE - 1];
	unsigned char* dat;
	int len;
	int pos;

	for(;;) {
		len = cp210x.txlen < sizeof _dat ? cp210x.txlen : sizeof _dat;
		if(!len) {
			break;
		}
		pos = cp210x.txpos;
		dat = _dat;
		do {
			*dat++ = cp210x.txbuf[pos];
			if(++pos == cp210x.txcap) {
				pos = 0;
			}
		} while(--len);
		len = D12_WriteBuffer(EP2IN, _dat, dat - _dat);
		if(len == -1) {
			break;
		}
		D12_ValidateBuffer(EP2IN);
		cp210x.txlen -= len;
		cp210x.txpos  = pos;
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
cp210x_start(int rxcap, int txcap, void (*rxdone)(), void (*txdone)())
{
	cp210x_stop();
	memset(&cp210x, 0, sizeof cp210x);
	if(rxcap < EP2_NONISO_PACKET_SIZE) {
		rxcap = EP2_NONISO_PACKET_SIZE;
	}
	cp210x.rxcap = rxcap;
	cp210x.rxbuf = malloc(rxcap);
	if(!cp210x.rxbuf) {
		DIE();
	}
	cp210x.rxdone = rxdone;
	if(txcap < EP2_NONISO_PACKET_SIZE) {
		txcap = EP2_NONISO_PACKET_SIZE;
	}
	cp210x.txcap = txcap;
	cp210x.txbuf = malloc(txcap);
	if(!cp210x.txbuf) {
		DIE();
	}
	cp210x.txdone = txdone;
	usb_start(&usb_inf);
}

void
cp210x_stop()
{
	usb_stop();
	free(cp210x.rxbuf);
	free(cp210x.txbuf);
	memset(&cp210x, 0, sizeof cp210x);
}

void
cp210x_reset()
{
ENTER_CS;
	cp210x.rxpos = 0;
	cp210x.rxlen = 0;
	cp210x.rxlog = 0;
	cp210x.txpos = 0;
	cp210x.txlen = 0;
	cp210x.txlog = 0;
LEAVE_CS;
}

int
cp210x_recv(void* _dat, int maxlen)
{
	unsigned char* dat = _dat;
	int len;

ENTER_CS;
	do {
		len = cp210x.rxlen < maxlen ? cp210x.rxlen : maxlen;
		if(len) {
			maxlen       -= len;
			cp210x.rxlen -= len;
			cp210x.rxlog += len;
			do {
				*dat++ = cp210x.rxbuf[cp210x.rxpos];
				if(++cp210x.rxpos == cp210x.rxcap) {
					cp210x.rxpos = 0;
				}
			} while(--len);
		}
		ep2_rxsub();
	} while(cp210x.rxlen && maxlen);
LEAVE_CS;
	return dat - (unsigned char*)_dat;
}

int
cp210x_send(const void* _dat, int maxlen)
{
	const unsigned char* dat = _dat;
	int len;
	int pos;

	if(maxlen < 0) {
		maxlen = strlen((char*)dat);
	}
ENTER_CS;
	do {
		len = (cp210x.txcap - cp210x.txlen) < maxlen ? (cp210x.txcap - cp210x.txlen) : maxlen;
		if(len) {
			pos = (cp210x.txpos + cp210x.txlen);
			maxlen       -= len;
			cp210x.txlen += len;
			cp210x.txlog += len;
			do {
				if(pos >= cp210x.txcap) {
					pos -= cp210x.txcap;
				}
				cp210x.txbuf[pos] = *dat++;
				pos++;
			} while(--len);
		}
		ep2_txsub();
	} while((cp210x.txcap - cp210x.txlen) && maxlen);
LEAVE_CS;
	return dat - (unsigned char*)_dat;
}

void
cp210x_stat(CP210XSTAT* stat)
{
ENTER_CS;
	stat->rxlen = cp210x.rxlen;
	stat->rxlog = cp210x.rxlog;
	stat->txlen = cp210x.txlen;
	stat->txlog = cp210x.txlog;
LEAVE_CS;
}
