/*	
 *	clip915g.c
 *
 *	ASUS P5GDC-V+WinXpでP/ECEを使った時に起きたUSBの問題を回避
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2015 Naoyuki Sawa
 *
 *	* Sun Dec 28 22:47:06 JST 2014 Naoyuki Sawa
 *	- 1st リリース。
 *	  「割り込み禁止での対策方法」を作成しました。
 *	* Mon Dec 29 18:38:36 JST 2014 Naoyuki Sawa
 *	- 「PDIUSBD12のクロックを止めない対策方法」を追加しました。
 *	- 検証プログラムのコメントに「追記:PC側で対策を行う方法」を追加しました。
 *	* Tue Dec 30 04:17:46 JST 2014 Naoyuki Sawa
 *	- 「IDMAを使って割り込みを解除する方法」を作成しました。
 *	* Tue Dec 30 15:44:40 JST 2014 Naoyuki Sawa
 *	- 「IDMAを使って割り込みを解除する方法」を複数の環境で動作確認し、問題なく動作する事を確認しました。
 *	  「割り込み禁止での対策方法」「PC側で対策を行う方法」「IDMAを使って割り込みを解除する方法」を作ってきましたが、
 *	  「IDMAを使って割り込みを解除する方法」を使うのが良さそうです。
 *	* Fri Jan 09 21:26:07 JST 2015 Naoyuki Sawa
 *	- 内蔵I/Oメモリのミラー空間を使って、「割り込み禁止での対策方法」のp0_isr()の命令数を削減しました。
 *	* Fri Jan 09 21:29:11 JST 2015 Naoyuki Sawa
 *	- 「IDMAを使って割り込みを解除する方法」だけでは足りず、まだ普通の操作でハングアップする場合が有ることが判明し、さらに対策を追加しました。
 *	* Sun Jan 11 01:12:43 JST 2015 Naoyuki Sawa
 *	- 「内蔵I/Oメモリ領域にアクセスするコードのサイズ節約」をモジュール全体に適用しました。
 */
#include "clip.h"
#undef plc_IO		//「内蔵I/Oメモリ領域にアクセスするコードのサイズ節約」開始	モジュールに適用

#if 0
//==============================================================================================================================================================
//■割り込み禁止での対策方法(終了時にハングアップする問題が残っている)
//==============================================================================================================================================================
/****************************************************************************
 *	ローカル関数宣言
 ****************************************************************************/
/*static*/ void p0_isr();
/****************************************************************************
 *	グローバル関数
 ****************************************************************************/
void Intel915G_WindowsXp_UsbSuspend_ProblemWorkaround_init() {
ENTER_CS;
	//割り込みサービスルーチンをインストールする。
	pceVectorSetTrap(TRAP_P0, p0_isr);
	/*** Port input 0 interrupt 設定 ここから ***/
	//Interrupt priority register
	bINT_PP01L_PP0L = 7;			//Port input 0 interrupt priority	//USB割り込みよりも高ければ何でもok
	//Key input 0-1, port input 0-3 interrupt enable register
	bINT_EK01_EP03_EP0 = 1;			//Port input 0
	//Port input interrupt select register
	bPINTSEL_SPT03_SPT0 = 1/*K50*/;		//FPT0 interrupt input port selection
	//Port input interrupt input polarity select register
	bPINTPOL_SPPT_SPP0 = 1/*high*/;		//FPT0 input polarity selection
	//Port input interrupt edge/level select register
	bPINTEL_SEPT_SEPT0 = 0/*level*/;	//FPT0 edge/level selection
	/*** Port input 0 interrupt 設定 ここまで ***/
	//割り込み要因フラグを確実にリセットしておく。
	//レベルトリガなので、K50=highならば直ちに再度、割り込み要因フラグがセットされ、p0_isr()が呼び出される。
	bRST_RESET_RSTONLY = 1;		//割り込み要因フラグのリセット方式を、リセットオンリー方式に切り替える。
	pINT_FK01_FP03 = (1<<0);	//割り込み要因フラグをリセットする。
	bRST_RESET_RSTONLY = 0;		//割り込み要因フラグのリセット方式を、リード/ライト方式に戻す。
LEAVE_CS;
}
/*--------------------------------------------------------------------------*/
void Intel915G_WindowsXp_UsbSuspend_ProblemWorkaround_free() {
ENTER_CS;
	/*** Port input 0 interrupt 設定 ここから ***/
	//Key input 0-1, port input 0-3 interrupt enable register
	bINT_EK01_EP03_EP0 = 0;			//Port input 0
	/*** Port input 0 interrupt 設定 ここまで ***/
	//USB割り込みを禁止したままアプリケーションが終了しようとしたら、USB割り込みを許可する。
	if(!bINT_EK01_EP03_EP1) {
		//Key input 0-1, port input 0-3 interrupt enable register
		bINT_EK01_EP03_EP1 = 1;			//Port input 1
		//そのままではUSB割り込みが掛かりっ放しになるので、USBを再接続して正常復帰する。
		pceUSBReconnect();
		//⇒試したところ、これでは正常復帰できませんでした。気休めとして残しておきます。
		//  実際には、USB割り込みが掛かりっ放しの状態でアプリケーションを終了しようとすると、ハングアップします。
		//  アプリケーションを終了する前に、PC側からP/ECEへの通信が発生し、サスペンドが解除すれば正常終了します。
	}
LEAVE_CS;
}
/****************************************************************************
 *	ローカル関数
 ****************************************************************************/
#ifndef PIECE
/*static*/ void p0_isr();
asm("
		.code
		.align		1
p0_isr:
		pushn		%r15
		ld.w		%r0, %alr
		ld.w		%r1, %ahr
		xcall		_p0_isr
		ld.w		%ahr, %r1
		ld.w		%alr, %r0
		popn		%r15
		reti
");
static void __attribute__((unused))/*asmブロックから参照*/ _p0_isr() {
	//Device is in Suspend state.
	if(bK5_K5D_K50D) {
		//Key input 0-1, port input 0-3 interrupt enable register
		bINT_EK01_EP03_EP1 = 0;			//Port input 1
		//Port input interrupt input polarity select register
		bPINTPOL_SPPT_SPP0 = 0/*low*/;		//FPT0 input polarity selection
	//Device is not in Suspend state.
	} else {
		//Key input 0-1, port input 0-3 interrupt enable register
		bINT_EK01_EP03_EP1 = 1;			//Port input 1
		//Port input interrupt input polarity select register
		bPINTPOL_SPPT_SPP0 = 1/*high*/;		//FPT0 input polarity selection
	}
	//↑↓この処理順序は必須。極性を変更する前に割り込み要因フラグをリセットすると、再度セットされてしまう。
	//割り込み要因フラグをリセットする。
	bRST_RESET_RSTONLY = 1;		//割り込み要因フラグのリセット方式を、リセットオンリー方式に切り替える。
	pINT_FK01_FP03 = (1<<0);	//割り込み要因フラグをリセットする。
	bRST_RESET_RSTONLY = 0;		//割り込み要因フラグのリセット方式を、リード/ライト方式に戻す。
}
#else //PIECE
/*static*/ void p0_isr();
asm("
		.code
		.align		1
p0_isr:
		pushn		%r1
		;//-------------------------------------;//
  ;//{{2015/01/09変更:内蔵I/Oメモリのミラー空間を使って、「割り込み禁止での対策方法」のp0_isr()の命令数を削減しました。
  ;//│* Fri Jan 09 21:26:07 JST 2015 Naoyuki Sawa
  ;//│- S1C33209/221/222テクニカルマニュアル『図4.1 メモリマップ』(s1c33209_221_222j.pdf B-II-4-4)参照
  ;//│  内蔵I/Oメモリの正式なアドレスは0x40000〜0x4FFFFだが、0x30000〜0x3FFFF,及び,0x50000〜0x5FFFFにもミラーが見える。
  ;//│  内蔵I/Oメモリのミラー空間を使って命令数を削減する事が出来る。具体的には下記の通り:
  ;//│- 0x40000〜0x4FFFFのベースアドレスをレジスタに保持してオフセットでアクセスする場合、即値0x40000をレジスタにロードするには3命令かかる。
  ;//│  しかし0x40000〜0x4FFFFの代りに0x30000〜0x3FFFFのミラー空間を使ってアクセスすれば、即値0x30000をレジスタにロードするには2命令で済む。
  ;//│- ベースアドレスに限らず、内蔵I/Oメモリの特定のアドレスをレジスタにロードする場合も、上記の工夫を適用可能である。
  ;//		xld.w		%r0, 0x40000		;//%r0  := plc_IO
  ;//↓2015/01/09変更:内蔵I/Oメモリのミラー空間を使って、「割り込み禁止での対策方法」のp0_isr()の命令数を削減しました。
		xld.w		%r0, 0x30000		;//%r0  := plc_IO
  ;//}}2015/01/09変更:内蔵I/Oメモリのミラー空間を使って、「割り込み禁止での対策方法」のp0_isr()の命令数を削減しました。
		xbtst		[%r0+0x2c1], 0		;//if(bK5_K5D_K50D) {
		jreq		p0_isr_L10		;//  
		xbclr		[%r0+0x270], 1		;//  bINT_EK01_EP03_EP1 = 0
		xbclr		[%r0+0x2c8], 0		;//  bPINTPOL_SPPT_SPP0 = 0
		xjp		p0_isr_L20		;//  
p0_isr_L10:						;//} else {
		xbset		[%r0+0x270], 1		;//  bINT_EK01_EP03_EP1 = 1
		xbset		[%r0+0x2c8], 0		;//  bPINTPOL_SPPT_SPP0 = 1
p0_isr_L20:						;//}
		xbset		[%r0+0x29f], 0		;//bRST_RESET_RSTONLY = 1
		ld.w		%r1, 1			;//
		xld.b		[%r0+0x280], %r1	;//pINT_FK01_FP03 = (1<<0)
		xbclr		[%r0+0x29f], 0		;//bRST_RESET_RSTONLY = 0
		;//-------------------------------------;//
		popn		%r1
		reti
");
#endif//PIECE
//==============================================================================================================================================================
#elif 0
//==============================================================================================================================================================
//■PDIUSBD12のクロックを止めない対策方法(これだけでokかも?)
//==============================================================================================================================================================
void Intel915G_WindowsXp_UsbSuspend_ProblemWorkaround_init() {
	//この問題の原因は、PDIUSBD12がサスペンドへ遷移した直後に割り込みステータスを読んで割り込み解除しないと、割り込みが解除できなくなってしまう事のようだ。
	//サスペンドしてもPDIUSBD12のクロックを止めなければ、サスペンドへ遷移した後、かなり時間が経ってからでも割り込みが解除できるので、問題が解決するようだ。
	D12_SetMode(D12_ENDP_NONISO | D12_SOFTCONNECT | D12_CLOCKRUNNING, D12_SETTOONE);
}
void Intel915G_WindowsXp_UsbSuspend_ProblemWorkaround_free() {
	pceUSBReconnect();//P/ECE標準のD12_CLOCKRUNNING=Offに戻すため。戻さなくて良ければこの処理も不要。
}
//==============================================================================================================================================================
#elif 1
//==============================================================================================================================================================
//■IDMAを使って割り込みを解除する方法(タイミング依存であるが実用上okみたい)
//==============================================================================================================================================================
void Intel915G_WindowsXp_UsbSuspend_ProblemWorkaround_init() {
	//D12_COMMANDにREAD_INTERRUPT_REGISTERを書き込むだけでは、割り込みが解除されない。
	//D12_COMMANDにREAD_INTERRUPT_REGISTERを書き込んだ後、D12_DATAから少なくとも1バイト読み出すと、割り込みが解除される。
	//本来は(READ_INTERRUPT_REGISTER⇒2バイト読み出し)だが、割り込みを解除するためだけならば1バイト読み出しで良いようだ。
	static const int TBL_IDMA[2][3]={{
		//IDMA Ch.0
		0x00000000,			//TC=0,LINKCHN=x,LINKEN=0	//実際にはconstではなく、IDMAによって書換えられる。(1^24)回サスペンドするまで終わらないので、実用上充分です。	//この構造体をフラッシュROM上に置けば、本当にconstにできると思うかも知れませんが、それは不可です。IDMAは常に(CPUが割り込み禁止でも)起動する可能性が有り、フラッシュROMの書き換え中にIDMAが起動する可能性も有り、フラッシュROMの書き換え中にフラッシュROMへのアクセスが生じ、フラッシュROMの書き換えが失敗するからです。この構造体をSRAM上に置いておけば、フラッシュROMの書き換え中にIDMAが起動しても問題は生じません。
		0x00400000,			//SRADR=D12_DATA,SRINC=DIN_FIXED,DATSIZ=byte,DINTEN=0
		0x01000000,			//DSADR=Areas 12-11,DSINC=DIN_FIXED,DMOD=DMOD_SINGLE
	},{	//IDMA Ch.1
		0x80000000,			//TC=0,LINKCHN=0,LINKEN=0	//実際にはconstではなく、IDMAによって書換えられる。(1^24)回サスペンドするまで終わらないので、実用上充分です。	//この構造体をフラッシュROM上に置けば、本当にconstにできると思うかも知れませんが、それは不可です。IDMAは常に(CPUが割り込み禁止でも)起動する可能性が有り、フラッシュROMの書き換え中にIDMAが起動する可能性も有り、フラッシュROMの書き換え中にフラッシュROMへのアクセスが生じ、フラッシュROMの書き換えが失敗するからです。この構造体をSRAM上に置いておけば、フラッシュROMの書き換え中にIDMAが起動しても問題は生じません。
		(int)(const uint8_t[]){0xF4},	//SRADR=READ_INTERRUPT_REGISTER,SRINC=DIN_FIXED,DATSIZ=byte,DINTEN=0
		0x00400004,			//DSADR=D12_COMMAND,DSINC=DIN_FIXED,DMOD=DMOD_SINGLE
	}};
//ENTER_CS;	//割り込みは関係無いので不要
	//Port input interrupt select register
	bPINTSEL_SPT03_SPT0 = 1/*K50*/;		//FPT0 interrupt input port selection
	//Port input interrupt input polarity select register
	bPINTPOL_SPPT_SPP0 = 1/*high*/;		//FPT0 input polarity selection
	//Port input interrupt edge/level select register
	bPINTEL_SEPT_SEPT0 = 1/*edge*/;		//FPT0 edge/level selection
	//IDMA control register
	IDMAEN_DISABLE;				//IDMA enable
	pIDMA_DBASE	= (int)TBL_IDMA;	//IDMA base address (0x48200)
	IDMAEN_ENABLE;				//IDMA enable
	//Port input 0-3, high-speed DMA, 16-bit timer 0 IDMA request register
	bIDMAREQ_RP03_RHS_R16T0_RP0	= 1;	//Port input 0 IDMA request
	//Port input 0-3, high-speed DMA, 16-bit timer 0 IDMA enable register
	bIDMAEN_DEP03_DEHS_DE16T0_DEP0	= 1;	//Port input 0 IDMA enable
//LEAVE_CS;	//割り込みは関係無いので不要
    //{{2015/01/09追加:「IDMAを使って割り込みを解除する方法」だけでは足りず、まだ普通の操作でハングアップする場合が有ることが判明し、さらに対策を追加しました。
	// * Fri Jan 09 18:29:39 JST 2015 Naoyuki Sawa
	// - 「IDMAを使って割り込みを解除する方法」だけでは足りず、まだ普通の操作でハングアップする場合が有ることが判明し、さらに対策を追加しました。
	//   □新たに判明したハングアップの要因
	//   ・割り込み禁止期間中にサスペンドとUSB通信が両方発生すると、IDMAだけでは割り込み要因を解除しきれず、割り込み許可後にハングアップする。
	//   │//ﾊﾝｸﾞｱｯﾌﾟするﾌﾟﾛｸﾞﾗﾑ
	//   │#if 0
	//   │void app_main() {
	//   │ENTER_CS;
	//   │  pdwait(8000000);//約10秒 //この間にUSB通信が発生するとﾊﾝｸﾞｱｯﾌﾟする｡IDMAがREAD_INTERRUPT_REGISTERを行ってSUSPENDCHANGEの割込要因をｸﾘｱしても､USB通信の割込要因はｸﾘｱされない｡USB通信の割込要因が残ったままPDIUSBD12がｻｽﾍﾟﾝﾄﾞしてしまい､ﾊﾝｸﾞｱｯﾌﾟする｡実際にも､run.batで実行するとこのﾀｲﾐﾝｸﾞでUSB受信が発生するので､(ｾﾚｸﾃｨﾌﾞｻｽﾍﾟﾝﾄﾞするPC環境では)ﾊﾝｸﾞｱｯﾌﾟする｡
	//   │LEAVE_CS;
	//   │  for(;;) {
	//   │    schedule();
	//   │    surface_clear(&surface, 0);
	//   │    render_string(&render,0,0,"ok",1,3);
	//   │} }
	//   □新たに判明したハングアップへの対策
	//   ・完全な対策は難しい。ただし、普通の操作で発生するのはrun.batで実行した場合だけであるので、run.batのタイミングに特化して対策を追加した。
	//   │//ﾊﾝｸﾞｱｯﾌﾟしないように対策したﾌﾟﾛｸﾞﾗﾑ
	//   │void app_main() {
	//   │  pdwait(100000);//0.1秒強 //run.batで実行した場合のUSB受信を､ここで受け切ることを期待する｡ﾀｲﾐﾝｸﾞ依存ではあるが､まず大丈夫だろう｡←←←★
	//   │ENTER_CS;
	//   │  pdwait(8000000);//約10秒 //上記'0.1秒強'でrun.batのUSB受信を受け切っておけば､ここでｻｽﾍﾟﾝﾄﾞしてもIDMAが要因をｸﾘｱするので大丈夫｡ただしここで別のUSB通信が発生するとやはりﾊﾝｸﾞｱｯﾌﾟするのだが､PC側で意図的にそのような操作を行わなければ大丈夫だろう｡
	//   │LEAVE_CS;
	//   │  for(;;) {
	//   │    schedule();
	//   │    surface_clear(&surface, 0);
	//   │    render_string(&render,0,0,"ok",1,3);
	//   │} }
	//   アプリケーションの実行開始が'0.1秒強'遅くなってしまうが、動作上も体感上も問題無いだろう。
	// - 極端に遅いPCでは'0.1秒強'でも足りない可能性が有る。
	//   'ASUS P5GDC-V Deluxe, Windows Xp Home SP3, Pentium 4 3GHz, 1.5GB RAM'では、0.02秒以上ならば大丈夫だったので、5倍以上の余裕があるので安全だと思う。
	//   もし、これでもハングアップする問題が生じた場合は、pdwait()の時間を長くするように調整せよ。
	pdwait(100000);//0.1秒強
    //}}2015/01/09追加:「IDMAを使って割り込みを解除する方法」だけでは足りず、まだ普通の操作でハングアップする場合が有ることが判明し、さらに対策を追加しました。
}
/*--------------------------------------------------------------------------*/
void Intel915G_WindowsXp_UsbSuspend_ProblemWorkaround_free() {
//ENTER_CS;	//割り込みは関係無いので不要
	//IDMA control register
	IDMAEN_DISABLE;				//IDMA enable
	//Port input 0-3, high-speed DMA, 16-bit timer 0 IDMA request register	//┐
	bIDMAREQ_RP03_RHS_R16T0_RP0	= 0;	//Port input 0 IDMA request	//├動作上はIDMAEN_DISABLEだけでも良いが、一応こちらもクリアしておくことにした。
	//Port input 0-3, high-speed DMA, 16-bit timer 0 IDMA enable register	//│
	bIDMAEN_DEP03_DEHS_DE16T0_DEP0	= 0;	//Port input 0 IDMA enable	//┘
//LEAVE_CS;	//割り込みは関係無いので不要
}
//==============================================================================================================================================================
#endif
