/*	
 *	clipprin.c
 *
 *	USB Printer Device
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2015 Naoyuki Sawa
 *
 *	* Sat May 23 13:57:58 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 */
#include "clip.h"
/****************************************************************************
 *	グローバル変数
 ****************************************************************************/
ST_Printer stPrinter;
/****************************************************************************
 *	ローカル関数宣言
 ****************************************************************************/
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 class_request(const DEVICE_REQUEST* req, const unsigned char* dat, int len);
static void ep2_rxdone();
static void ep2_rxsub();
/****************************************************************************
 *	USBフック情報
 ****************************************************************************/
static const USBINF usb_inf={
	//USBC mode
	.mode			= D12_ENDP_NONISO,
	//USBC raw event
	.ep2_rxdone		= ep2_rxdone,
	//Device request
	.get_descriptor		= get_descriptor,
	.set_configuration	= set_configuration,
	.set_interface		= set_interface,
	//Class request
	.class_request		= class_request,
};
/****************************************************************************
 *	ディスクリプタ
 ****************************************************************************/
//■参照資料
//│「Universal Serial Bus Device Class Definition for Printing Devices Version 1.1 January 2000」(usbprint11a021811.pdf)
//│5. Standard Descriptors
/*--------------------------------------------------------------------------*/
//5.1 Device Descriptor
static const DEVICE_DESCRIPTOR DeviceDescriptor={
	.bLength		= sizeof(DEVICE_DESCRIPTOR),
	.bDescriptorType	= DEVICE_DESCRIPTOR_TYPE,
	.bcdUSB			= 0x0110,
//	.bDeviceClass		= 0,
//	.bDeviceSubClass	= 0,
//	.bDeviceProtocol	= 0,
	.bMaxPacketSize0	= EP0_PACKET_SIZE,
	.idVendor		= PCE_VENDOR_ID,
	.idProduct		= PRINTER_PRODUCT_ID,
	.bcdDevice		= 0x0001,
	.iManufacture		= 1,
	.iProduct		= 2,
//	.iSerialNumber		= 0,
	.bNumConfigurations	= 1,
};
/*--------------------------------------------------------------------------*/
//5.2 Configuration Descriptor
static const CONFIGURATION_DESCRIPTOR ConfigurationDescriptor={
	.bLength		= sizeof(CONFIGURATION_DESCRIPTOR),
	.bDescriptorType	= CONFIGURATION_DESCRIPTOR_TYPE,
	.wTotalLength		= sizeof(CONFIGURATION_DESCRIPTOR) +
				  sizeof(INTERFACE_DESCRIPTOR) +
				  sizeof(ENDPOINT_DESCRIPTOR),
	.bNumInterfaces		= 1,
	.bConfigurationValue	= 1,
//	.iConfiguration		= 0,
	.bmAttributes		= 0x80,
	.MaxPower		= 100/2,
};
/*--------------------------------------------------------------------------*/
//5.3 Interface Descriptors
static const INTERFACE_DESCRIPTOR InterfaceDescriptor={
	.bLength		= sizeof(INTERFACE_DESCRIPTOR),
	.bDescriptorType	= INTERFACE_DESCRIPTOR_TYPE,
//	.bInterfaceNumber	= 0,
//	.bAlternateSetting	= 0,
	.bNumEndpoints		= 1,
	.bInterfaceClass	= 7,						//Base class for printers.
	.bInterfaceSubClass	= 1,						//The subclass codes for Printer devices are: 01 Printers
	.bInterfaceProtocol	= 1,						//Printer Interface Type: 00 Reserved, undefined. 01 Unidirectional interface. 02 Bi-directional interface. 03 1284.4 compatible bi-directional interface. 04-FEh Reserved for future use. FFh Vendor-specific printers do not use classspecific protocols.
//	.iInterface		= 0,
};
/*--------------------------------------------------------------------------*/
//5.4 Endpoint Descriptors
static const ENDPOINT_DESCRIPTOR EndpointDescriptor={
	.bLength		= sizeof(ENDPOINT_DESCRIPTOR),
	.bDescriptorType	= ENDPOINT_DESCRIPTOR_TYPE,
	.bEndpointAddress	= 0x02,						//EP2OUT
	.bmAttributes		= 2,						//Bulk
	.wMaxPacketSize		= EP2_NONISO_PACKET_SIZE,
//	.bInterval		= 0,
};
/*--------------------------------------------------------------------------*/
static const uint16_t* const TBL_StringDescriptor[]={
  (const uint16_t []){
	STRING_DESCRIPTOR_TYPE<<8|4,						//bLength,bDescriptorType
	0x0409,									//wLANGID[0]
},(const uint16_t []){
	STRING_DESCRIPTOR_TYPE<<8|22,						//bLength,bDescriptorType
	'P','i','e','c','e',' ','L','a','b','.',				//bString
},(const uint16_t []){
	STRING_DESCRIPTOR_TYPE<<8|34,						//bLength,bDescriptorType
	'P','/','E','C','E',' ','U','S','B',' ','d','e','v','i','c','e'
}};
/****************************************************************************
 *	
 ****************************************************************************/
void Printer_Start(int rxcap/*0可*/, void (*rxdone/*NULL可*/)()) {
	//確実に終了しておく。
	Printer_Stop();
	//構造体をクリアする。
	memset(&stPrinter, 0, sizeof stPrinter);
	//受信バッファを確保する。
	if(rxcap < EP2_NONISO_PACKET_SIZE) { rxcap = EP2_NONISO_PACKET_SIZE; }
	stPrinter.rxcap = rxcap;
	stPrinter.rxbuf = malloc(rxcap);
	if(!stPrinter.rxbuf) { DIE(); }
	//受信コールバックを格納する。
	stPrinter.rxdone = rxdone;
	//USBのフックを開始する。
	usb_start(&usb_inf);
}
/*--------------------------------------------------------------------------*/
void Printer_Stop() {
	//USBのフックを終了する。
	usb_stop();
	//受信バッファを開放する。
	free(stPrinter.rxbuf/*NULL可*/);
	//構造体をクリアする。
	memset(&stPrinter, 0, sizeof stPrinter);
}
/****************************************************************************
 *	
 ****************************************************************************/
static void get_descriptor(const DEVICE_REQUEST* req, const unsigned char* dat, int len) {
	static void* buf;
	       void* ptr;
	int type  = HIBYTE(req->wValue);
	int index = LOBYTE(req->wValue);
	switch(type) {
	case DEVICE_DESCRIPTOR_TYPE:
		{
			usb_ep0_tx(&DeviceDescriptor, sizeof DeviceDescriptor);
			return;
		}
		break;
	case CONFIGURATION_DESCRIPTOR_TYPE:
		if(index == 0) {
			buf = realloc(buf, sizeof ConfigurationDescriptor + sizeof InterfaceDescriptor + sizeof EndpointDescriptor);
			ptr =         buf;
			ptr = mempcpy(ptr, &ConfigurationDescriptor, sizeof ConfigurationDescriptor);
			ptr = mempcpy(ptr, &InterfaceDescriptor    , sizeof InterfaceDescriptor    );
			ptr = mempcpy(ptr, &EndpointDescriptor     , sizeof EndpointDescriptor     );
			usb_ep0_tx(   buf, sizeof ConfigurationDescriptor + sizeof InterfaceDescriptor + sizeof EndpointDescriptor);
			return;
		}
		break;
	case STRING_DESCRIPTOR_TYPE:
		if(index < ARRAY_SIZE(TBL_StringDescriptor)) {
			usb_ep0_tx(TBL_StringDescriptor[index], (uint8_t)TBL_StringDescriptor[index][0]);
			return;
		}
		break;
	}
	usb_ep0_stall();	//未対応
}
/*--------------------------------------------------------------------------*/
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 void class_request(const DEVICE_REQUEST* req, const unsigned char* dat, int len) {
	switch(req->bRequest) {
	case PRINTER_GET_DEVICE_ID:
		{
			//■参考資料
			//│①「Undocumented Printing Wiki」(http://www.undocprint.org/)の『IEEE Standard 1284』(http://www.undocprint.org/formats/communication_protocols/ieee_1284)
			//│②「なひたふ新聞」(http://www.nahitech.com/nahitafu/)さんの『IEEE1284をFPGAで実現しよう』(http://www.nahitech.com/nahitafu/fpgavhdl/ieee1284_2/ieee1284_2.html)
			//│③「Microchip」(http://www.microchip.com/)の『USB Printer Class(Client, not Host)』(http://www.microchip.com/forums/m491016.aspx)
			static const char DeviceId[]=
				"\x00\x50"			// 2	このフィールド自身のサイズも含むのが正しい。参照資料③の例は間違っていますがたまたま最後のフィールドがDESで欠けてもデバイスの認識に影響が無かったようです。
				"MFG:Generic;"			//12	【必須】Windowsに'Generic / Text Only'プリンタとして認識させるために必要な設定。(参照資料③)
				"MDL:Generic_/_Text_Only;"	//24	【必須】Windowsに'Generic / Text Only'プリンタとして認識させるために必要な設定。(参照資料③)
				"CLS:PRINTER;"			//12
				"CMD:none;"			// 9	(関係無し?)
				"DES:P/ECE USB device;";	//21
			usb_ep0_tx(&DeviceId, sizeof DeviceId);
		}
		return;
	case PRINTER_GET_PORT_STATUS:
		{
			//7..6	Reserved Reserved for future use; device shall return these bits reset to zero.
			//5	Paper Empty 1 = Paper Empty, 0 = Paper Not Empty
			//4	Select 1 = Selected, 0 = Not Selected
			//3	Not Error 1 = No Error, 0 = Error
			//2..0	Reserved Reserved for future use; device shall return these bits reset to zero.
			static const uint8_t PortStatus = (0/*Paper Not Empty*/<<5)|(1/*Selected*/<<4)|(1/*No Error*/<<3);
			usb_ep0_tx(&PortStatus, sizeof PortStatus);
		}
		return;
	case PRINTER_SOFT_RESET:
		{
			usb_ep0_tx(NULL, 0);	//ACK
		}
		return;
	}
	//The Device may support class-specific requests.
	//The device shall return STALL if an unrecognized or unsupported device-specific request is received.
	usb_ep0_stall();	//未対応
}
/****************************************************************************
 *	
 ****************************************************************************/
//受信処理のアルゴリズムは、既存のclipacm.cモジュールと同じです。
/*--------------------------------------------------------------------------*/
static void ep2_rxdone() {
	ep2_rxsub();
	if(stPrinter.rxdone) { stPrinter.rxdone(); }
}
/*--------------------------------------------------------------------------*/
static void ep2_rxsub() {
	uint8_t buf[EP2_NONISO_PACKET_SIZE], *ptr;
	int len, pos;
	//ダブルバッファに到着したパケットを全て処理する。
	for(;;) {
		len = D12_ReadBuffer(EP2OUT, buf, sizeof buf);
		if((len == -1) || //到着したパケットがもう無い。
		   (len > (stPrinter.rxcap - stPrinter.rxlen))) {
			//stPrinter.rxbufに空きが無い場合は、到着したパケットをクリアせずに、USBCの受信バッファに残しておきます。
			//受信バッファに前のパケットが残っていれば、PCは次のパケットを送信できず、ブロックした状態となります。
			//後で、stPrinter_recv()が呼び出され、stPrinter.rxbufに空きができたら、残っていたパケットを読み出します。
			//それによって、受信バッファが空となって、PCのブロック状態も解除されて、次のパケットが送られて来ます。
			break;
		}
		D12_ClearBuffer(EP2OUT);
		if(len) {
			pos = (stPrinter.rxpos + stPrinter.rxlen);
			stPrinter.rxlen += len;
			ptr = buf;
			do {
				if(pos >= stPrinter.rxcap) { pos -= stPrinter.rxcap; }
				stPrinter.rxbuf[pos++] = *ptr++;
			} while(--len);
		}
	}
}
/*--------------------------------------------------------------------------*/
int Printer_Recv(void* buf/*[buflen]*/, int buflen) {
	uint8_t* ptr = buf;
ENTER_CS;
	do {
		int len = (stPrinter.rxlen <= buflen) ? stPrinter.rxlen : buflen;
		if(len) {
			buflen          -= len;
			stPrinter.rxlen -= len;
			do {
				*ptr++ = stPrinter.rxbuf[stPrinter.rxpos++];
				if(stPrinter.rxpos == stPrinter.rxcap) { stPrinter.rxpos = 0; }
			} while(--len);
		}
		//stPrinter.rxbufに空きができたら、その分USBCの受信バッファから読み出す。
		//USBCの受信バッファをできるだけ早く空にするために、必ず一度は呼び出す。
		ep2_rxsub();
	} while(stPrinter.rxlen && buflen);
LEAVE_CS;
	return ptr - (uint8_t*)buf;
}
/****************************************************************************
 *	アプリケーションからの使用例(ポーリングの場合)
 ****************************************************************************/
#if 0
void app_main(){
 char buf[256/*変更可*/];
 FILE* fp=fopen("test.txt","w");
 Printer_Start(0,NULL);
 for(;;){
  int len;
  schedule();
  if(joy&TRG_A){break;}
  if((len=Printer_Recv(buf,sizeof buf))){fwrite(buf,1,len,fp);}
 }
 Printer_Stop();
}
#endif
/****************************************************************************
 *	アプリケーションからの使用例(イベントドリブンの場合)
 ****************************************************************************/
#if 0
static FILE* fp;
static void onRecv(){
 char buf[256/*変更可*/];
 int len=Printer_Recv(buf,sizeof buf);
 fwrite(buf,1,len,fp);
}
void app_main(){
 fp=fopen("test.txt","w");
 Printer_Start(0,onRecv);
 for(;;){
  schedule();
  if(joy&TRG_A){break;}
 }
 Printer_Stop();
}
#endif
/****************************************************************************
 *	テスト方法(Windows Xpの場合)
 ****************************************************************************/
//■プリンタとFAX
//⇒Generic / Text Only
//⇒プロパティ
//⇒テストページの印刷
//日本語の部分は'.'になってしまいますが、'Generic / Text Only'プリンタの印刷結果としてはこれで正常みたいです。
//秀丸エディタでは印刷できませんでした。秀丸エディタはテキストをグラフィックとして印刷する方式専用みたいです。
//メモ帳(notepad.exe)ならば印刷できます。
