
/**
 * @file i2s_subsystem.c
 * @brief CODECの初期化と制御
 */
#include "i2s_subsystem.h"
#include "kernel_id.h"
#include <cdefBF592-A.h>


// CODECのスロット番号
#define ADC_L0  0
#define ADC_R0  1
#define DAC_L0  0
#define DAC_R0  1

/**
 * @brief DMAデスクリプタ型
 * 
 * ADSP-BF533のDMA用デスクリプタ型。 avairablebuf メンバーはDMAが使わないフィールドで、
 * これは task_audiocodec() が利用できるバッファを特定するためのIDとして使う。
 */
struct DMA_DESCRIPTOR{
	struct DMA_DESCRIPTOR * next; 	///< 次のデスクリプタへのポインタ
	void * start;					///< DMAバッファの開始アドレス
	unsigned short config;			///< DMAコンフィグレーションレジスタへのロード値
	unsigned short x_count;			///< DMAのX方向への転送数[個]
	short x_modify;					///< DMAのX方向へのインクリメント値[BYTE]
	/**
	 * @brief バッファ特定用フィールド
	 * 
	 * 初期化時に定数を設定しておく。@ref task_audiocodec() はDMAが使用中のデスクリプタの
	 * このフィールドを読み、有効なバッファにアクセスするためのインデックスとして使う。
	 */
	short avairableBuf;	
};



/**
 * @brief 送信CODECバッファ
 * 
 * DMAがSPORT経由でCODECにデータを送り出すためのバッファ。配列要素が3つあるのはトリプルバッファであるため。
 */
static struct CODEC_BUFFER TxBuffer[3];
/**
 * @brief 受信CODECバッファ
 * 
 * 
 * DMAがSPORT経由でCODECにデータを受信するためのバッファ。配列要素が3つあるのはトリプルバッファであるため。
 */
static struct CODEC_BUFFER RxBuffer[3];

/**
 * @brief 送信DMAデスクリプタ変数
 * 
 * 
 * トリプルバッファなのでデスクリプタを3個使用する。
 */
static struct DMA_DESCRIPTOR txDesc[3];
/**
 * @brief 受信DMAデスクリプタ変数
 * 
 * 
 * トリプルバッファなのでデスクリプタを3個使用する。
 */
static struct DMA_DESCRIPTOR rxDesc[3];

/**
 * @brief 左入力データ引渡し変数
 * 
 * 
 * @ref codec_callback() 関数に入力オーディオデータを渡すための引数。
 * 左右チャンネルそれぞれに配列を与える。配列内部にはサンプル順にデータが並ぶ。
 * ステレオは2系統あるので左チャンネルも２系統分のデータがある。
 */
static AUDIOSAMPLE leftIn[UZUME_BLOCKSIZE];
/**
 * @brief 右入力データ引渡し変数
 * 
 * 
 * @ref codec_callback() 関数に入力オーディオデータを渡すための引数。
 * 左右チャンネルそれぞれに配列を与える。配列内部にはサンプル順にデータが並ぶ。
 * ステレオは2系統あるので右チャンネルも２系統分のデータがある。
 */
static AUDIOSAMPLE rightIn[UZUME_BLOCKSIZE];
/**
 * @brief 左出力データ引渡し変数
 * 
 * 
 * @ref codec_callback() 関数から出力オーディオデータをうけとるための引数。
 * 左右チャンネルそれぞれに配列を与える。配列内部にはサンプル順にデータが並ぶ。
 */
static AUDIOSAMPLE leftOut[UZUME_BLOCKSIZE];
/**
 * @brief 右出力データ引渡し変数
 * 
 * 
 * @ref codec_callback() 関数から出力オーディオデータをうけとるための引数。
 * 左右チャンネルそれぞれに配列を与える。配列内部にはサンプル順にデータが並ぶ。
 */
static AUDIOSAMPLE rightOut[UZUME_BLOCKSIZE];

/**
 * @brief CODEC用SPORTを設定する
 * 
 * 
 * SPORT0をCODEC用に設定する。RCRx, TCRxの設定はBF592のマニュアルを参照されたい。
 */
static void setup_sport0(void)
{
        // SPORTをディセーブルにする
    *pSPORT0_TCR1 = 0;
    *pSPORT0_RCR1 = 0;

        // DMAをディセーブルにする
    *pDMA2_CONFIG = 0;
    *pDMA1_CONFIG = 0;


	// I2S設定
	// Sport0 受信設定 
	// 外部クロック, 外部同期信号,
	// 32-bit データ 
	*pSPORT0_RCR1 = RFSR | RCKFE;
	*pSPORT0_RCR2 = 31 | RSFSE;		// データ長 32
	
	// Sport0 送信設定
	// 外部クロック, 外部同期信号
	// 32ビットデータ 
	*pSPORT0_TCR1 = TFSR | TCKFE;
	*pSPORT0_TCR2 = 31 | TSFSE;		// データ長 32
	
	// MTCS/MRCSはI2Sには関係ない
	*pSPORT0_MTCS0 = 0x00000000;
	*pSPORT0_MRCS0 = 0x00000000;
	
	// マルチチャンネル設定
	*pSPORT0_MCMC1 = 0x0000; // オフセット = 0, ウインドウサイズ = 8
	*pSPORT0_MCMC2 = 0x0000; // マルチチャンネル・ディセーブル;
}

/**
 * @brief CODEC用にSPORT0 DMAを設定する
 * 
 * 
 * CODEC用のオーディオDMAを設定する。DMAはデスクリプタのリンクからなる。トリプルバッファを
 * 使うため、デスクリプタは送受それぞれに3つ用意する。
 * 
 * デスクリプタ変数 のavairableBuf フィールドはDMAハードウェア用ではなく、管理ソフトウェア
 *　用である。DMAエンジンが次にフェッチするDMAデスクリプタのアドレスは、DMA_NEXT_PTRレジスタ
 * からしることができる。そこで、DMA_NEXT_PTRレジスタが示すデスクリプタのavairableBuf
 * メンバーを読めば、CPUが「現在」データを読み書きしてもよいバッファのインデックスがわかるよう
 * になっている。このインデックスはトリプルバッファのうちどれを使うかを指す。つまり0から2の値をとる。
 * 
 */
static void setup_sport0_dma(void)
{
	rxDesc[0].next = &rxDesc[1];
	rxDesc[0].start = RxBuffer[0].data;
	rxDesc[0].config = 0x7700 | WNR | WDSIZE_32 | DI_EN | DMAEN;	// Large List, 7 short word.
	rxDesc[0].x_count = SLOT_PER_SAMPLE * UZUME_BLOCKSIZE;
	rxDesc[0].x_modify = 4;
	rxDesc[0].avairableBuf = 1;	// DMA_NEXT_PTRがデスクリプタ[0]なら、バッファ1を使える。
	
	rxDesc[1].next = &rxDesc[2];
	rxDesc[1].start = RxBuffer[1].data;
	rxDesc[1].config = 0x7700 | WNR | WDSIZE_32 | DI_EN | DMAEN;	// Large List, 7 short word.
	rxDesc[1].x_count = SLOT_PER_SAMPLE * UZUME_BLOCKSIZE;
	rxDesc[1].x_modify = 4;
	rxDesc[1].avairableBuf = 2;	// DMA_NEXT_PTRがデスクリプタ[1]なら、バッファ2を使える。
	
	rxDesc[2].next = &rxDesc[0];
	rxDesc[2].start = RxBuffer[2].data;
	rxDesc[2].config = 0x7700 | WNR | WDSIZE_32 | DI_EN | DMAEN;	// Large List, 7 short word.
	rxDesc[2].x_count = SLOT_PER_SAMPLE * UZUME_BLOCKSIZE;
	rxDesc[2].x_modify = 4;
	rxDesc[2].avairableBuf = 0;	// DMA_NEXT_PTRがデスクリプタ[2]なら、バッファ0を使える。
	
	// DMA2設定 (SPORT0 TX)
	txDesc[0].next = &txDesc[1];
	txDesc[0].start = TxBuffer[0].data;
	txDesc[0].config = 0x7700 | WDSIZE_32 | DMAEN;	// Large List, 7 short word.
	txDesc[0].x_count = SLOT_PER_SAMPLE * UZUME_BLOCKSIZE;
	txDesc[0].x_modify = 4;
	txDesc[0].avairableBuf = 1;	// DMA_NEXT_PTRがデスクリプタ[0]なら、バッファ1を使える。
	
	txDesc[1].next = &txDesc[2];
	txDesc[1].start = TxBuffer[1].data;
	txDesc[1].config = 0x7700 | WDSIZE_32 | DMAEN;	// Large List, 7 short word.
	txDesc[1].x_count =  SLOT_PER_SAMPLE * UZUME_BLOCKSIZE;
	txDesc[1].x_modify = 4;
	txDesc[1].avairableBuf = 2;	// DMA_NEXT_PTRがデスクリプタ[1]なら、バッファ2を使える。

	txDesc[2].next = &txDesc[0];
	txDesc[2].start = TxBuffer[2].data;
	txDesc[2].config = 0x7700 | WDSIZE_32 | DMAEN;	// Large List, 7 short word.
	txDesc[2].x_count =  SLOT_PER_SAMPLE * UZUME_BLOCKSIZE;
	txDesc[2].x_modify = 4;
	txDesc[2].avairableBuf = 0;	// DMA_NEXT_PTRがデスクリプタ[2]なら、バッファ1を使える。


	*pDMA2_NEXT_DESC_PTR = &txDesc[0];		// 送信設定
	*pDMA1_NEXT_DESC_PTR = &rxDesc[0];		// 受信DMA設定
	*pDMA2_CONFIG = 0x7700 | WDSIZE_32;	// 32bit転送、デスクリプタ・リンク、デスクリプタ・サイズ=7word
	*pDMA1_CONFIG = 0x7700 | WNR | WDSIZE_32;
}

/**
 * @brief SPORTをイネーブルにしてオーディオ処理をはじめる
 * 
 * 
 * DMA送受信を開始してオーディオ送受信を開始する。この関数はCPUアンロック状態で呼ばなければならない。
 */
static void start_audio(void)
{
	// DMA 開始 
	*pDMA2_CONFIG |= DMAEN;
	*pDMA1_CONFIG |= DMAEN;
	asm("ssync;");

	// SPORT0 開始 なるべくアトミックに近い状態になるよう、CPUロック状態で開始する。
	_syscall(loc_cpu());
	*pSPORT0_TCR1 |= TSPEN;
	*pSPORT0_RCR1 |= RSPEN;
	_syscall(unl_cpu());
}

/**
 * @brief SPORTをディセーブルにしてオーディオ処理をはじめる
 *
 *
 * DMA送受信を開始してオーディオ送受信を開始する。この関数はCPUアンロック状態で呼ばなければならない。
 */
static void stop_audio(void)
{
    // SPORT0 停止
    *pSPORT0_TCR1 =0 ;
    *pSPORT0_RCR1 =0 ;

    // DMA 停止
    *pDMA2_CONFIG = 0;
    *pDMA1_CONFIG = 0;

}



/**
 * @brief オーディオ処理の開始とコールバック呼び出し
 * @param extinf ITRONのタスク引数。このタスクはタスク引数を利用しない。
 * @ingroup TOPPERS
 * 
 * オーディオ・ペリフェラルの初期化と処理の実行を行う。
 * 
 * 最初にSPIとSPORT0の割り込み禁止を解除する。SPORT0はオーディオ受信DMA割り込み
 * であるため問題はないが、SPIに関しては他のペリフェラルと共用するならば何らかの検討が必要である。
 * 
 * 
 * SPORT DMAは送受信いずれもDMAチェーンを用いている。そのための設計をする際、
 * デスクリプタ変数に有効なバッファのインデックスを書き込んでおくことで後の処理を簡略化
 * できるようにしている。
 * 
 * 割り込み待ちのループ内部ではsport0_semを使ってSPORT RX割り込みハンドらである
 * @ref inh_codec_sport() からの割り込み通知を待つ。通知を受けたらCODECの構造に依存した
 * バッファを解いてフラットな入力配列をL/R両方に用意する。これは @ref codec_callback()に
 * 渡すためのものである。
 * 
 * @ref codec_callback()から戻ったら、今度は先ほどと逆にフラットな出力配列からCODECの
 * 構造に応じたバッファを組み立てる。
 * 
 */
void task_audiocodec(VP_INT extinf)
{
    unsigned int dummy;
    PRI old_priority;         // タスクの優先順位を保存しておくための変数

	init_codec();       // CODEC初期化

	init_audio();       // コールバックの初期化

	setup_sport0();     // SPORT0 設定

	    // これより1サンプルだけデータを空読みする。このカラ読みによって
	    // SPORTの外部同期信号に対する同期点直後で以下の処理をすすめることが可能になる。
	    // このようなことをするのは、RXとTXのSPORT開始の間に同期点が来ると、TXとRXの
	    // スタート・タイミングがずれることがおきうるからである。
	while ( SPORT0_STAT & RXNE )    // RX FIFOがカラになるまで空読み
	    dummy = SPORT0_RX;
	
        // タスクの優先順位を一旦引き上げる。これは、SPORT開始処理のレスポンスを保証するためである
    syscall(get_pri( TSK_SELF, &old_priority ));
    syscall(chg_pri( TSK_SELF, TMAX_TPRI ));

    syscall(ena_int( INTNO_SPORT0_RX ));     // SPORT0 DMA割り込みマスクをはずす

        // DMA転送開始
    setup_sport0_dma(); // SPORT DMA 設定
    start_audio();      // SPORT0 開始
	
    // 1ブロック受信するまで待機
    syscall(wai_sem( SPORT0_SEM));

    stop_audio();

        // この時点で、我々は「SPORTの同期点直後」にいると言える
        // 再度データ転送を開始する
	setup_sport0_dma();	// SPORT DMA 設定
	start_audio();		// SPORT0 開始

	    // ここでタスク優先順位を戻す
	syscall(chg_pri( TSK_SELF, old_priority ));

		// 割り込み待ちループ
	while( 1 ){
		int sample;		// どのサンプルを処理するかを示すループ・インデックス
		int bufTx, bufRx;	// 利用する送受信バッファのインデックス

		
				// 受信DMA終了割り込みと同期
		syscall(wai_sem( SPORT0_SEM));
			
				// プロセッサが使ってよいバッファを割り出す	
		bufTx = (( struct DMA_DESCRIPTOR * ) *pDMA2_NEXT_DESC_PTR )->avairableBuf;
		bufRx = (( struct DMA_DESCRIPTOR * ) *pDMA1_NEXT_DESC_PTR )->avairableBuf;
		
				// CODECの受信データを引数バッファにコピー
		for ( sample = 0; sample < UZUME_BLOCKSIZE; sample++ ){
			leftIn[sample]  = RxBuffer[bufRx].data[sample][ADC_L0] >> UZUME_INT_BITS;
			rightIn[sample] = RxBuffer[bufRx].data[sample][ADC_R0] >> UZUME_INT_BITS;
		}
		
				// 信号処理
		process_audio( 
				leftIn,
				rightIn,
				leftOut,
				rightOut
				);
		
				// 引数データをCODEC用送信バッファにコピー
		for ( sample = 0; sample < UZUME_BLOCKSIZE; sample++ ){
			TxBuffer[bufRx].data[sample][DAC_L0] = leftOut[sample]  << UZUME_INT_BITS;
			TxBuffer[bufRx].data[sample][DAC_R0] = rightOut[sample] << UZUME_INT_BITS;
		}				

	}

}


/**
 * @brief SPORT RX割り込み
 * @ingroup TOPPERS
 * 
 * SPORT0 RX DMA割り込みを検出してタスクに通知する。このハンドラでは
 * 通知後割り込みをクリアするだけで割り込みの原因となった事象(DMA完了)の
 * 処理は行わない。
 * 
 * このハンドラの登録はコンフィグレーターが行う。したがってプログラム中で
 * 明示的に登録する必要はない。
 */
void inh_codec_sport(void)
{

    *pDMA1_IRQ_STATUS = DMA_DONE;       // SPORT0 RX DMA割り込みをクリア

	syscall(isig_sem( SPORT0_SEM ));	// タスクに通知
    asm("ssync;");                      // クリアが終了するのを待つ
}


