
/**
 * @file i2s_subsystem.c
 * @brief CODECの初期化と制御
 */
#include <s_services.h>
#include "i2s_subsystem.h"
#include "kernel_id.h"
#ifdef _COMMON_BF592
#include <cdefBF592-A.h>
#else
#error "DSP not supported"
#endif

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

#ifdef _COMMON_BF592

#ifndef UZUME_SPORT
#error "UZUME_SPORT Not declared."
#elif (UZUME_SPORT == 0)
    #define pSPORT_STAT             pSPORT0_STAT
    #define pSPORT_RX               pSPORT0_RX
    #define pSPORT_RCR1             pSPORT0_RCR1
    #define pSPORT_RCR2             pSPORT0_RCR2
    #define pSPORT_TCR1             pSPORT0_TCR1
    #define pSPORT_TCR2             pSPORT0_TCR2
    #define pSPORT_MTCS0            pSPORT0_MTCS0
    #define pSPORT_MRCS0            pSPORT0_MRCS0
    #define pSPORT_MCMC1            pSPORT0_MCMC1
    #define pSPORT_MCMC2            pSPORT0_MCMC2
    #define pDMATX_NEXT_DESC_PTR    pDMA2_NEXT_DESC_PTR
    #define pDMARX_NEXT_DESC_PTR    pDMA1_NEXT_DESC_PTR
    #define pDMATX_CONFIG           pDMA2_CONFIG
    #define pDMARX_CONFIG           pDMA1_CONFIG
    #define pDMARX_IRQ_STATUS       pDMA1_IRQ_STATUS
#elif (UZUME_SPORT == 1)
    #define pSPORT_STAT             pSPORT1_STAT
    #define pSPORT_RX               pSPORT1_RX
    #define pSPORT_RCR1             pSPORT1_RCR1
    #define pSPORT_RCR2             pSPORT1_RCR2
    #define pSPORT_TCR1             pSPORT1_TCR1
    #define pSPORT_TCR2             pSPORT1_TCR2
    #define pSPORT_MTCS0            pSPORT1_MTCS0
    #define pSPORT_MRCS0            pSPORT1_MRCS0
    #define pSPORT_MCMC1            pSPORT1_MCMC1
    #define pSPORT_MCMC2            pSPORT1_MCMC2
    #define pDMATX_NEXT_DESC_PTR    pDMA4_NEXT_DESC_PTR
    #define pDMARX_NEXT_DESC_PTR    pDMA3_NEXT_DESC_PTR
    #define pDMATX_CONFIG           pDMA4_CONFIG
    #define pDMARX_CONFIG           pDMA3_CONFIG
    #define pDMARX_IRQ_STATUS       pDMA3_IRQ_STATUS
#else
#error "UZUME_SPORT is unknown #"
#endif // UZUME_SPORT

#else
#error "DSP Not supported."
#endif  // _COMMON_BF592

/**
 * @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をディセーブルにする
    *pSPORT_TCR1 = 0;
    *pSPORT_RCR1 = 0;

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


	// I2S設定
	// Sport0 受信設定 
	// 外部クロック, 外部同期信号,
	// 32-bit データ 
	*pSPORT_RCR1 = RFSR | RCKFE;
	*pSPORT_RCR2 = 31 | RSFSE;		// データ長 32
	
	// Sport0 送信設定
	// 外部クロック, 外部同期信号
	// 32ビットデータ 
	*pSPORT_TCR1 = TFSR | TCKFE;
	*pSPORT_TCR2 = 31 | TSFSE;		// データ長 32
	
	// MTCS/MRCSはI2Sには関係ない
	*pSPORT_MTCS0 = 0x00000000;
	*pSPORT_MRCS0 = 0x00000000;
	
	// マルチチャンネル設定
	*pSPORT_MCMC1 = 0x0000; // オフセット = 0, ウインドウサイズ = 8
	*pSPORT_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を使える。


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

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

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

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

    // DMA 停止
    *pDMATX_CONFIG = 0;
    *pDMARX_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)
{

    PRI old_priority;         // タスクの優先順位を保存しておくための変数

	init_codec();       // CODEC初期化

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

	setup_sport0();     // SPORT0 設定

	
        // タスクの優先順位を一旦引き上げる。これは、SPORT開始処理のレスポンスを保証するためである
    syscall(get_pri( TSK_SELF, &old_priority ));
    syscall(chg_pri( TSK_SELF, TMAX_TPRI ));

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

        // DMA転送開始
    setup_sport0_dma(); // SPORT DMA 設定
    start_audio();      // SPORT0 開始
	
    // 1ブロック受信するまで待機
    syscall(wai_sem( SPORT_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( SPORT_SEM));
			
				// プロセッサが使ってよいバッファを割り出す	
		bufTx = (( struct DMA_DESCRIPTOR * ) *pDMATX_NEXT_DESC_PTR )->avairableBuf;
		bufRx = (( struct DMA_DESCRIPTOR * ) *pDMARX_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)
{

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

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


