/**
 * @brief           DMAC Manager
 * @author          AXELL CORPORATION
 * @description     DMAC Manager Layer.
 * @note            none
 * @history         2017_02_22  初版
 * @history         2017_05_29  [#1348]DMAC動作状態の判別方法を修正
 * @history         2017_10_26  Ver2.0
 * @history         2019_03_08  [SDK2.2] ディスクリプタによるDMA転送の不具合を修正 (#2446)
 * @history         2019_12_27  [SDK3.0] DMACマネージャの割り込みクリア順序を修正 (#2589)
 * @history         2025_03_06  [SDK3.7] DMACに割り込みコールバックバックを解除する関数を追加 (#5756)
 * @history         2025_03_06  [SDK3.7] DMACマネージャで管理外チャンネルの割り込みをクリアする不具合を修正 (#5763)
*/
/* DOM-IGNORE-BEGIN */
/*
 * This program was created by Axell Corporation.
 * Copyright (C) 2017-2025 Axell Corporation, all rights reserved.
 */
/* DOM-IGNORE-END */

#include "AG903_common.h"
#include "AG903_regmap.h"

#include "dmac/dmacprm.h"
#include "dmac/dmacmgr.h"
#include "int/intmgr.h"

/** ディスクリプタアドレス */
typedef struct _AG903_DMACMgrAddrTable
{
	uint32_t current;	/** 現在値 */
	uint32_t next;		/** 次回値 */
} AG903_DMACMgrAddrTable;

/** ハンドル情報 */
typedef struct _AG903_DMACMgrHandleStat
{
	void *cb_term;	/** term割込みコールバック */
	void *cb_abt;	/** abort割込みコールバック */
	void *cb_wdt;	/** wdt割込みコールバック */
	void *cb_error;	/** error割込みコールバック */

	/** ディスクリプタアドレステーブル（循環参照用）
	 * AG903_DMACMgrSetDescListでディスクリプタを転送するときにこのテーブルも構築する
	 */
	AG903_DMACMgrAddrTable term_addr_table[AG903_DMAC_DESC_LINK_MAX];
	uint32_t term_addr_table_num; /** ディスクリプタアドレステーブル数 */

	uint8_t	unit_id;	/** DMACチャンネル */
	uint8_t	term_count; /** ターミナルカウント */
	uint8_t	reserve[2]; /** (削除可) */

} AG903_DMACMgrHandleStat;

static AG903_DMACMgrHandleStat handle_list[AG903_DMAC_UNIT_NUM];
static _Bool handle_used_flag[AG903_DMAC_UNIT_NUM] = {false};

#define LOCAL_DESC_MEM_ADDR   (0xFD100000)		/** メモリディスクリプタの保存先のアドレス */

#define CH_REG_SIZE (0x20)						/** チャンネルごとのレジスタのサイズ */

static int8_t dmac_int_init_flag = false;		/** DMACの割り込みハンドラ登録フラグ */
												/** DMACManagerで一度のみ割り込みハンドラを登録する */

static int32_t dmac_int_dmactc_hdrid  = 0;		/** dmactcのID */

static int8_t ldd_flag[AG903_DMAC_LDD_ENTRY_NUM] = {0};	/** Local DMAC Descriptor */

static int8_t rdd_flag[AG903_DMAC_RDD_ENTRY_NUM] = {0};	/** Remote DMAC Descriptor */
static uint8_t* rdd_data = NULL;

#define TOTAL_ENTRY_NUM (AG903_DMAC_LDD_ENTRY_NUM+AG903_DMAC_RDD_ENTRY_NUM)	/** トータルのディスクリプタの数 */

#define AG903_DMAC_CONFIG_TCMASK	(1<<0)	/** ConfigレジスタのTCIintMaskビット */

static _Bool initdesc = false;	/** LDM初期化フラグ */

static void write_desc(AG903_DMACMgrDesc *from, AG903_DMACMgrDesc *next, void *to, int is_reg);
static int32_t remove_desc_entry(int seq_id);
static uint8_t DMACMgr_CheckEnable(uint32_t unit);
static void DMACMgrTermIntHandler(uint32_t term);
static void DMACMgrErrIntHandler(uint32_t abt, uint32_t err);

/*
	割込みハンドラ.
*/
static void DMACMgrDmactcIntHandler()
{
	uint32_t term, abt, wdt, err;

	AG903_DMACPrmGetTIMECOUNT_STATUS(&term);
	AG903_DMACPrmGetERR_ABORT_STATUS(&err, &wdt, &abt);

	if(0 != term) {
		DMACMgrTermIntHandler(term);
	}
	if((0 != abt) || (0 != err)) {
		DMACMgrErrIntHandler(abt, err);
	}
	if(0 != wdt) {
		/* WDTでは割込みが発生しない為、処理なし */
		/* 必要に応じてアプリケーションがAG903_DMACMgrGetIntStatus()でチェックする */
	}

	return;
}

/*
	Term割込み処理.
*/
static void DMACMgrTermIntHandler(uint32_t term)
{
	AG903_DMACMgrHandleStat *handle;
	AG903_DMACMgrDesc *desc = NULL;
	AG903_DMACMgrDesc *next = NULL;
	uint32_t desc_save_mem;
	int loop;
	uint8_t end = false;

	for(loop=0; loop<AG903_DMAC_UNIT_NUM; loop++) {
		if(0 == term) {
			break;
		}
		if(term & (0x1<<loop)) {
			if(true == handle_used_flag[loop]) {	/* ハンドル動作中 */
				/* コールバックで新たな転送が開始される場合があるため予め割り込みをクリアする */
				AG903_DMACPrmSetTIMECOUNT_INT_CLEAR(0x1<<loop);

				handle = &handle_list[loop];
				if(handle->cb_term != NULL) { 		/* Callback登録あり */
					void (*cb_term)(AG903_DMACMgrHandle*, AG903_DMACMgrDesc**, uint8_t*) =
						(void(*)(AG903_DMACMgrHandle*, AG903_DMACMgrDesc**, uint8_t*))handle->cb_term;
					if(0 >= handle->term_addr_table_num) {
						cb_term((AG903_DMACMgrHandle*)handle, NULL, NULL);	/* Callback (第二,第三引数無効) */
					}
					else {
						cb_term((AG903_DMACMgrHandle*)handle, &desc, &end);	/* Callback (循環参照) */
						if(NULL != desc) {
							if(true == end) {
								next = NULL; /* endが指定された場合は循環を終了させる */
							}
							else {
								next = (AG903_DMACMgrDesc*)handle->term_addr_table[handle->term_count].next;
							}
							desc_save_mem = handle->term_addr_table[handle->term_count].current;
							write_desc(desc, next, (void*)desc_save_mem, 0);	/* Current書換え */
						}
						else {
							/* 循環参照中の場合、ディスクリプタの内容は更新されずに循環参照される */
							if(true == end) {
								desc_save_mem = handle->term_addr_table[handle->term_count].current;
								*(uint32_t*)(((uint32_t)desc_save_mem)+0x8) = 0; /* endが指定された場合は循環を終了させる */
							}
						}
						handle->term_count = ((handle->term_count+1) % handle->term_addr_table_num);
					}
				}
			}
			else {
				/* 使われていないハンドルの割込みステータスは無視 */
			}
			term &= ~(0x1<<loop);
		}

	}

	return;
}

/*
	Err割込み処理.
*/
static void DMACMgrErrIntHandler(uint32_t abt, uint32_t err)
{
	AG903_DMACMgrHandleStat *handle;
	int loop;

	/*
	割り込みの頻度が高くなると、以下のループのコストが大きくなる可能性があるため、
	大きくerrorの単位でチェックを行う
	*/
	for(loop=0; loop<AG903_DMAC_UNIT_NUM; loop++) {
		if(0 == abt) {
			break;
		}
		if(abt & (0x1<<loop)) {
			if(true == handle_used_flag[loop]) {
				handle = &handle_list[loop];
				if(NULL != handle_list[loop].cb_abt) {
					void (*cb_abt)(AG903_DMACMgrHandle*) = (void(*)(AG903_DMACMgrHandle*))handle->cb_abt;
					cb_abt((AG903_DMACMgrHandle*)handle); /* Callback */
				}
				AG903_DMACMgrClearInt((AG903_DMACMgrHandle*)handle, AG903_DMAC_INTSTS_ABORT);	/* 割込みステータスクリア */
			}
			else {
				/* 使われていないハンドルの割込みステータスは無視 */
			}
			abt &= ~(0x1<<loop);
		}
	}

	for(loop=0; loop<AG903_DMAC_UNIT_NUM; loop++) {
		if(0 == err) {
			break;
		}
		if(err & (0x1<<loop)) {
			if(true == handle_used_flag[loop]) {
				handle = &handle_list[loop];
				if(NULL != handle_list[loop].cb_error) {
					void (*cb_error)(AG903_DMACMgrHandle*) = (void(*)(AG903_DMACMgrHandle*))handle->cb_error;
					cb_error((AG903_DMACMgrHandle*)handle); /* Callback */
				}
				AG903_DMACMgrClearInt((AG903_DMACMgrHandle*)handle, AG903_DMAC_INTSTS_ERROR);	/* 割込みステータスクリア */
			}
			else {
				/* 使われていないハンドルの割込みステータスは無視 */
			}
			err &= ~(0x1<<loop);
		}
	}

	return;
}

/**
 * @brief           DMACハンドル取得
 * @param           ch [in] DMACチャンネル番号
 * @param           handle [out] DMACハンドル
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @retval          -AG903_EBUSY  ハンドル使用中
 * @description     DMACハンドルの取得 (ch指定)
*/
int32_t AG903_DMACMgrGetHandle(int ch, AG903_DMACMgrHandle **handle)
{
	if(ch < 0 || AG903_DMAC_UNIT_NUM <= ch || NULL == handle)
	{
		return -AG903_EINVAL;
	}

	if(handle_used_flag[ch] == true)
	{
		return -AG903_EBUSY;
	}

	/* 現在の設定を初期化する */
	uint32_t chpos = 1 << ch;
	AG903_DMACPrmSetTIMECOUNT_INT_CLEAR(chpos);
	AG903_DMACPrmSetERR_ABORT_INT_CLEAR(chpos, chpos, chpos);

	(*handle) = (AG903_DMACMgrHandle*)&handle_list[ch];
	handle_used_flag[ch] = true;

	handle_list[ch].unit_id = ch;

	handle_list[ch].cb_term  = NULL;
	handle_list[ch].cb_error = NULL;
	handle_list[ch].cb_abt  = NULL;
	handle_list[ch].cb_wdt   = NULL;

	return AG903_ENONE;
}

/**
 * @brief           DMACハンドル解放
 * @param           handle [in] DMACハンドル
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @retval          -AG903_EBUSY  実行中
 * @description     DMACハンドラの解放
 * @note            この関数はディスクリプタリストを使った転送が行われているとき実行禁止です。
 * @note            ディスクリプタリストはDMACMgrハンドラ間で排他的にアクセスして下さい。
 * @note            必要に応じアプリでAG903_INTMgrDisableIntによりDMACの割り込みを無効にして下さい。
*/
int32_t AG903_DMACMgrReleaseHandle(AG903_DMACMgrHandle *handle)
{
	uint32_t unit = ((AG903_DMACMgrHandleStat*)handle - handle_list);
	uint8_t	 enable;

	/* 無効なハンドル */
	if(unit >= AG903_DMAC_UNIT_NUM || &handle_list[unit] != (AG903_DMACMgrHandleStat*)handle)
	{
		return -AG903_EINVAL;
	}

	/* 実行時の場合 */
	enable = DMACMgr_CheckEnable(unit);
	if(true == enable) {
		return -AG903_EBUSY;
	}

	remove_desc_entry(unit);
	handle_used_flag[unit] = false;

	return AG903_ENONE;
}

/**
 * @brief           割り込みステータス取得
 * @param           handle [in] DMACハンドル
 * @param           status [out] 割り込みステータス
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @description     割り込みステータス取得
*/
int32_t AG903_DMACMgrGetIntStatus(AG903_DMACMgrHandle *handle, uint32_t *status)
{
	uint32_t unit = ((AG903_DMACMgrHandleStat*)handle - handle_list);
	uint32_t term, abt, wdt, error;

	/* 無効なハンドル */
	if(unit >= AG903_DMAC_UNIT_NUM || &handle_list[unit] != (AG903_DMACMgrHandleStat*)handle || NULL == status)
	{
		return -AG903_EINVAL;
	}

	AG903_DMACPrmGetTIMECOUNT_STATUS(&term);
	AG903_DMACPrmGetERR_ABORT_STATUS(&error, &wdt, &abt);
	term  = (term  >> unit) & 0x01;
	error = (error >> unit) & 0x01;
	wdt   = (wdt   >> unit) & 0x01;
	abt   = (abt   >> unit) & 0x01;

	(*status)  = (uint32_t)term;
	(*status) |= (uint32_t)(abt<<1);
	(*status) |= (uint32_t)(wdt<<2);
	(*status) |= (uint32_t)(error<<3);

	return AG903_ENONE;
}

/**
 * @brief           割り込みステータスクリア
 * @param           handle [in] DMACハンドル
 * @param           clrbit [in] クリア指定 (ビット指定)
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @description     割り込みステータスクリア
*/
int32_t AG903_DMACMgrClearInt(AG903_DMACMgrHandle *handle, uint32_t clrbit)
{
	uint32_t unit = ((AG903_DMACMgrHandleStat*)handle - handle_list);
	uint8_t	 term, abt, wdt, error;

	/* 無効なハンドル */
	if(unit >= AG903_DMAC_UNIT_NUM || &handle_list[unit] != (AG903_DMACMgrHandleStat*)handle)
	{
		return -AG903_EINVAL;
	}

	term  = 0;
	abt   = 0;
	wdt   = 0;
	error = 0;
	if(AG903_DMAC_INTSTS_TERM&clrbit) {
		term =1;
	}
	if(AG903_DMAC_INTSTS_ABORT&clrbit) {
		abt=1;
	}
	if(AG903_DMAC_INTSTS_WDT&clrbit) {
		wdt=1;
	}
	if(AG903_DMAC_INTSTS_ERROR&clrbit) {
		error=1;
	}
	AG903_DMACPrmSetTIMECOUNT_INT_CLEAR(term<<unit);
	AG903_DMACPrmSetERR_ABORT_INT_CLEAR(error<<unit, wdt<<unit, abt<<unit);

	return AG903_ENONE;
}

/**
 * @brief           コールバック登録
 * @param           handle [in] DMACハンドル
 * @param           cb_term [out] 終了割り込みのコールバック関数（指定しない場合はNULL）
 * @param           cb_abt [out] Abt割り込みのコールバック関数（指定しない場合はNULL）
 * @param           cb_error [out] エラー割り込みのコールバック関数（指定しない場合はNULL）
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @retval          -AG903_EFAULT 割り込みハンドラ登録失敗
 * @description     各種コールバックの登録<p>
 *                  以下のINTManager APIを使用します。<p>
 *                  ・AG903_INTMgrSetHandler<p>
 *                  ・AG903_INTMgrEnableInt
 * @note            この関数は内部でINTMgrを使用します。予めINTMgrを初期化して下さい。
 * @note            割り込みはコールバックの中ではクリアしないで下さい。
 *                  コールバックから戻り必要な処理を終えた後ライブラリでクリアされます。
 * @seealso         AG903_INTMgrInit
*/
int32_t AG903_DMACMgrSetIntCallback(AG903_DMACMgrHandle *handle,
 void (*cb_term)(AG903_DMACMgrHandle*, AG903_DMACMgrDesc**, uint8_t*),
 void (*cb_abt)(AG903_DMACMgrHandle*),
 void (*cb_error)(AG903_DMACMgrHandle*))
{
	uint32_t unit = ((AG903_DMACMgrHandleStat*)handle - handle_list);
	AG903_INTMgrHdrPrm inthdr;

	/* 無効なハンドル */
	if(unit >= AG903_DMAC_UNIT_NUM || &handle_list[unit] != (AG903_DMACMgrHandleStat*)handle)
	{
		return -AG903_EINVAL;
	}

	/* ハンドラの登録 */
	if(dmac_int_init_flag == false)
	{
		inthdr.atr   = AG903_INT_HLNG;
		inthdr.intno = AG903_IRQ26_DMAC;
		inthdr.func  = (void*)DMACMgrDmactcIntHandler;
		dmac_int_dmactc_hdrid = AG903_INTMgrSetHandler(&inthdr);
		if(0 >= dmac_int_dmactc_hdrid)
		{
			return -AG903_EFAULT;
		}
		dmac_int_init_flag = true;
	}

	handle_list[unit].cb_term  = (void*)cb_term;
	handle_list[unit].cb_abt  = (void*)cb_abt;
	handle_list[unit].cb_error = (void*)cb_error;

	AG903_INTMgrEnableInt(AG903_IRQ26_DMAC);

	return AG903_ENONE;
}

/**
 * @brief           コールバック解除
 * @param           handle [in] DMACハンドル
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @retval          -AG903_EFAULT 割り込みハンドラ解除失敗
 * @retval          -AG903_EPREM  割り込みハンドラ未登録
 * @description     各種コールバックの解除<p>
 *                  以下のINTManager APIを使用します。<p>
 *                  ・AG903_INTMgrDeleteHandler<p>
 * @note            この関数は内部でINTMgrを使用します。予めINTMgrを初期化して下さい。
 * @note            解除後もDMAC割り込み(IRQ26)は禁止にしていません。
 *                  必要に応じてミドルウェアを含めすべてのDMACチャンネルが未使用となった後
 *                  AG903_INTMgrDisableIntで禁止してください。
 * @seealso         AG903_INTMgrInit
 * @seealso         AG903_INTMgrDisableInt
*/
int32_t AG903_DMACMgrUnsetIntCallback(AG903_DMACMgrHandle *handle)
{
	uint32_t unit = ((AG903_DMACMgrHandleStat*)handle - handle_list);
	int32_t retval;

	/* 無効なハンドル */
	if(unit >= AG903_DMAC_UNIT_NUM || &handle_list[unit] != (AG903_DMACMgrHandleStat*)handle)
	{
		return -AG903_EINVAL;
	}

	/* ハンドラの未登録 */
	if(dmac_int_init_flag == false || dmac_int_dmactc_hdrid <= 0)
	{
		return -AG903_EPERM;
	}
		
	retval = AG903_INTMgrDeleteHandler(dmac_int_dmactc_hdrid);
	if(retval < 0)
	{
		return -AG903_EFAULT;
	}

	dmac_int_init_flag    = false;
	dmac_int_dmactc_hdrid = 0;

	return AG903_ENONE;
}

/**
 * @brief           DMAC転送許可（開始）
 * @param           handle [in] DMACハンドル
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @retval          -AG903_EBUSY  実行中
 * @description     DMAC転送許可（開始）
*/
int32_t AG903_DMACMgrEnable(AG903_DMACMgrHandle *handle)
{
	uint32_t unit = ((AG903_DMACMgrHandleStat*)handle - handle_list);
	uint8_t	 enable;

	/* 無効なハンドル */
	if(unit >= AG903_DMAC_UNIT_NUM || &handle_list[unit] != (AG903_DMACMgrHandleStat*)handle)
	{
		return -AG903_EINVAL;
	}

	/* 実行中の場合 */
	enable = DMACMgr_CheckEnable(unit);
	if(true == enable) {
		return -AG903_EBUSY;
	}

	/*
	EnableにしたタイミングでDMACのLDDを初めから読み込もうとするので、
	このタイミングでカウンタをリセットする
	*/
	handle_list[unit].term_count = 0;

	DMACPrmParamCTRL ctrl;
	AG903_DMACPrmGetCTRL_REG(unit, &ctrl);
	ctrl.ChEn = 1;
	AG903_DMACPrmSetCTRL_REG(unit, &ctrl);

	return AG903_ENONE;
}

/**
 * @brief           DMAC転送Abort
 * @param           handle [in] DMACハンドル
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @retval          -AG903_EPREM  停止中
 * @description     DMAC転送Abort
*/
int32_t AG903_DMACMgrAbort(AG903_DMACMgrHandle *handle)
{
	uint32_t unit = ((AG903_DMACMgrHandleStat*)handle - handle_list);
	uint8_t	 enable;

	/* 無効なハンドル */
	if(unit >= AG903_DMAC_UNIT_NUM || &handle_list[unit] != (AG903_DMACMgrHandleStat*)handle)
	{
		return -AG903_EINVAL;
	}

	/* 停止中の場合 */
	enable = DMACMgr_CheckEnable(unit);
	if(false == enable) {
		return -AG903_EPERM;
	}

	AG903_DMACPrmSetCHANNEL_ENABLE(~(1 << unit));

	return AG903_ENONE;
}

/**
 * @brief           ペリフェラル同期設定
 * @param           port [in] DMACポート番号
 * @param           val [in] 0で無効、1で有効
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @description     ペリフェラル同期設定
*/
int32_t AG903_DMACMgrSetSyncPeripheral(int port, int val)
{
	/* 無効なハンドル */
	if(0 > port || port >= AG903_DMAC_PORT_NUM)
	{
		return -AG903_EINVAL;
	}

	if(!(val == 0 || val == 1))
	{
		return -AG903_EINVAL;
	}

	uint32_t reg;
	AG903_DMACPrmGetSYNC_PERI_IF(&reg);
	if (val == 0)
		reg &= ~(1 << port);
	else /* if == 1 */
		reg |= (1 << port);
	AG903_DMACPrmSetSYNC_PERI_IF(reg);

	return AG903_ENONE;
}

/**
 * @brief           ウォッチドッグ設定
 * @param           val [in] クロック数（最大値は0xFFFF）
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @description     ウォッチドッグ設定
 * @note            この設定はすべてのDMACチャンネルに共通です。
*/
int32_t AG903_DMACMgrSetWatchdog(int val)
{
	if(0 > val || val > 0xFFFF)
	{
		return -AG903_EINVAL;
	}

	AG903_DMACPrmSetWATCHDOG_TIMER(val);

	return AG903_ENONE;
}

/**
 * @brief           WriteOnlyModeの固定値設定
 * @param           val [in] 設定値
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @description     WriteOnlyModeの固定値設定
 * @note            この設定はすべてのDMACチャンネルに共通です。
*/
int32_t AG903_DMACMgrSetConstantValue(uint32_t val)
{
	AG903_DMACPrmSetCONSTANT_VALUE_WRITE_ONLY(val);

	return AG903_ENONE;
}

/**
 * @brief           イベント設定
 * @param           id [in] イベントID
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @description     イベント設定
*/
int32_t AG903_DMACMgrSetEvent(int id)
{
	if(0 > id || AG903_DMAC_EVENT_NUM <= id)
	{
		return -AG903_EINVAL;
	}

	AG903_DMACPrmSetGLOBAL_EVENT(1 << id, 0);

	return AG903_ENONE;
}

/**
 * @brief           イベントクリア
 * @param           id [in] イベントID
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @description     イベントクリア
*/
int32_t AG903_DMACMgrClearEvent(int id)
{
	if(0 > id || AG903_DMAC_EVENT_NUM <= id)
	{
		return -AG903_EINVAL;
	}

	AG903_DMACPrmSetGLOBAL_EVENT(0, 1 << id);

	return AG903_ENONE;
}

/**
 * @brief           イベント状態取得
 * @param           id [in] イベントID
 * @param           val [out] イベント状態（1：set）
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @description     イベント状態取得
*/
int32_t AG903_DMACMgrGetEvent(int id, uint8_t *val)
{
	if(0 > id || AG903_DMAC_EVENT_NUM <= id || NULL == val)
	{
		return -AG903_EINVAL;
	}

	uint32_t reg;
	AG903_DMACPrmGetGLOBAL_EVENT(&reg);
	*val = (reg >> id) & 0x01;

	return AG903_ENONE;
}

/**
 * @brief           DMACの状態を取得する
 * @param           handle [in] DMACハンドル
 * @param           status [out] DMAC状態（true：Enable）
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @description     DMACの状態を取得する
*/
int32_t AG903_DMACMgrGetStatus(AG903_DMACMgrHandle *handle, AG903_DMACMgrStatus *status)
{
	uint32_t unit = ((AG903_DMACMgrHandleStat*)handle - handle_list);

	if(unit >= AG903_DMAC_UNIT_NUM || &handle_list[unit] != (AG903_DMACMgrHandleStat*)handle || NULL == status)
	{
		return -AG903_EINVAL;
	}

	status->enable = DMACMgr_CheckEnable(unit);

	return AG903_ENONE;
}

/*
	Enable状態取得
	unit：DMACチャンネル番号
*/
static uint8_t DMACMgr_CheckEnable(uint32_t unit)
{
	uint8_t retval = false;
	DMACPrmParamCTRL	ctrl;

	AG903_DMACPrmGetCTRL_REG(unit, &ctrl);
	if(0 != ctrl.ChEn) {
		retval = true;
	}

	return retval;
}


/*	ディスクリプタの構築関連  */

/*
	ldd_flag/rdd_flagから空きのエントリを検索する
	seq_id：エントリを使用するシーケンスのID
	return：エントリのindex
			（-1）空きが見つからなかった
*/
static int get_free_desc_index(int seq_id)
{
	int i;

	/*
	0は何も割り当てられていない状態なので、
	それを除外するようにseq_idを割り当てる
	*/
	if(seq_id == 0)	seq_id = TOTAL_ENTRY_NUM;

	/* 最初にLDD（Local）を検索する */
	for(i = 0 ; i < AG903_DMAC_LDD_ENTRY_NUM ; i++)
	{
		if(ldd_flag[i] == 0)
		{
			ldd_flag[i] = seq_id;
			return i;
		}
	}

	/* 次にRDD（Remote）を検索する */
	if(NULL != rdd_data)
	{
		for(i = AG903_DMAC_LDD_ENTRY_NUM ; i < AG903_DMAC_RDD_ENTRY_NUM+AG903_DMAC_LDD_ENTRY_NUM ; i++)
		{
			if(rdd_flag[i-AG903_DMAC_LDD_ENTRY_NUM] == 0)
			{
				rdd_flag[i-AG903_DMAC_LDD_ENTRY_NUM] = seq_id;
				return i;
			}
		}
	}

	/* 見つからなかった */
	return -1;
}

/*
	ldd_flag/rdd_flagからエントリを削除する
	seq_id：エントリを使用しているシーケンスのID
*/
static int32_t remove_desc_entry(int seq_id)
{
	int i;

	if(seq_id == 0)	seq_id = TOTAL_ENTRY_NUM;

	/* 最初にLDD（Local）を検索する */
	for(i = 0 ; i < AG903_DMAC_LDD_ENTRY_NUM ; i++)
	{
		if(ldd_flag[i] == seq_id)
		{
			ldd_flag[i] = 0;
		}
	}

	/* 次にRDD（Remote）を検索する */
	for(i = AG903_DMAC_LDD_ENTRY_NUM ; i < AG903_DMAC_RDD_ENTRY_NUM+AG903_DMAC_LDD_ENTRY_NUM ; i++)
	{
		if(rdd_flag[i-AG903_DMAC_LDD_ENTRY_NUM] == seq_id)
		{
			rdd_flag[i-AG903_DMAC_LDD_ENTRY_NUM] = 0;
		}
	}

	return AG903_ENONE;
}

/*
	DMACのIndexからアドレスに変更する
	index：各ディスクリプタのindex値
	return：all ディスクリプタのポインタ
*/
static AG903_DMACMgrDesc* desc_index_to_addr(int index)
{
	if(index < AG903_DMAC_LDD_ENTRY_NUM)
	{
		return (AG903_DMACMgrDesc*)(LOCAL_DESC_MEM_ADDR + (index * CH_REG_SIZE));
	}else
	{
		return (AG903_DMACMgrDesc*)(rdd_data+((index-AG903_DMAC_LDD_ENTRY_NUM)*CH_REG_SIZE));
	}
}

/*
	ディスクリプタのレジスタ領域のアドレスを取得する
	unit：DMACのチャンネルID
	return：ディスクリプタのレジスタ領域のアドレス
*/
static void* get_desc_reg_addr(int unit)
{
	return (void*)(AG903_DMAC_BASE + 0x100 + (unit*AG903_DMAC_DESC_SIZE));
}

/*
	DMACの空きのディスクリプタを検索する
	seq_id：エントリを使用しているシーケンスのID
	return：ディスクリプタのポインタ
			（NULL） 空きが見つからなかった場合
*/
static AG903_DMACMgrDesc* get_free_desc(int seq_id)
{
	int index = get_free_desc_index(seq_id);
	if(index == -1)
	{
		return NULL;
	}


	return desc_index_to_addr(index);
}

/*
	ディスクリプタを書き込む
	from：書き込み元のアドレス
	next：書き込むディスクリプタの次のアドレス
	to：書き込み先のアドレス
	is_reg：書き込み先がレジスタ(1)、メモリ(0)
 */
static void write_desc(AG903_DMACMgrDesc *from, AG903_DMACMgrDesc *next, void *to, int is_reg)
{
	if(is_reg == 1)
	{
		*(uint32_t*)(((uint32_t)to) + 0x0)  = from->Ctrl.val;
		*(uint32_t*)(((uint32_t)to) + 0x8)  = from->SrcAddr;
		*(uint32_t*)(((uint32_t)to) + 0xc)  = from->DstAddr;
		*(uint32_t*)(((uint32_t)to) + 0x10) = (uint32_t)next;
		*(uint32_t*)(((uint32_t)to) + 0x14) = from->Trns.val;
		*(uint32_t*)(((uint32_t)to) + 0x18) = from->Stride.val;
	}else
	{
		*(uint32_t*)(((uint32_t)to) + 0x0)  = from->SrcAddr;
		*(uint32_t*)(((uint32_t)to) + 0x4)  = from->DstAddr;
		*(uint32_t*)(((uint32_t)to) + 0x8)  = (uint32_t)next;
		*(uint32_t*)(((uint32_t)to) + 0xc)  = from->Ctrl.val;
		*(uint32_t*)(((uint32_t)to) + 0x10) = from->Trns.val;
		*(uint32_t*)(((uint32_t)to) + 0x14) = from->Stride.val;
	}
}


/*
	ディスクリプタのエントリを設定する
	handle：DMACハンドル
	desc_list：ディスクリプタアドレス
	list_num：ディスクリプタリスト数
*/
static int set_desc_entry(AG903_DMACMgrHandleStat *handle, AG903_DMACMgrDesc *desc_list, int list_num)
{
	int i;
	uint32_t loop;
	DMACPrmParamCFG cfg;
	AG903_DMACMgrDesc *desc = desc_list;

	/*
	与えられたdescのリストをldd/rddにコピーする
	コピーをするため、リンクリストが循環参照している場合の
	検索が少し煩雑になっていることに注意をする
	（補足）
	 一度コピーしたものでも、循環参照された場合には再度参照される可能性がある。
	 このとき、Hashmapとしてみて、キーが元の要素のポインタ、値がコピー先の
	 メモリアドレスとする変数がdesc_copy_entryである
	*/

	struct
	{
		/* @brief コピー元のアドレス */
		AG903_DMACMgrDesc *src;

		/* @brief コピー先のアドレス */
		AG903_DMACMgrDesc *dst;
	} desc_copy_entry[TOTAL_ENTRY_NUM];
	int desc_copy_entry_num = 0;

	/* リンクリストが循環参照しているかのフラグ */
	uint8_t ll_circ = false;
	uint32_t unit = (handle - handle_list);

	/* ディスクリプタの保存先のアドレス */
	AG903_DMACMgrDesc *desc_save_mem = get_free_desc(unit);
	if(desc_save_mem == NULL)
	{
		return -AG903_ENOBUFS;
	}

	/* 最初のレジスタに書き込む */
	AG903_DMACMgrDesc first_desc;

	first_desc.SrcAddr       = 0;
	first_desc.DstAddr       = 0;
	first_desc.next			 = 0;
	first_desc.Ctrl.val		 = 0;
	first_desc.Ctrl.st.TCMsk = 1;
	first_desc.Trns.val		 = 0;
	first_desc.Stride.val	 = 0;
	write_desc(&first_desc, desc_save_mem, get_desc_reg_addr(unit), 1);

	/* term_countは毎回リセットする */
	handle->term_count          = 0;
	handle->term_addr_table_num = 0;
	AG903_DMACPrmGetCFG_REG(unit, &cfg);

	for(loop=0; loop<AG903_DMAC_DESC_LINK_MAX; loop++)
	{
		/* nextのアドレスを確定させる */
		AG903_DMACMgrDesc *next_desc_save_mem = NULL;

		if(desc->next != NULL)
		{
			/* 循環参照しているケースを確認する */
			for(i = 0 ; i < desc_copy_entry_num ; i++)
			{
				if(desc_copy_entry[i].src == desc->next)
				{
					next_desc_save_mem = desc_copy_entry[i].dst;
					ll_circ = true;
				}
			}

			/* 循環参照していなければ、新しいエントリを決定する */
			if(ll_circ != true)
			{
				next_desc_save_mem = get_free_desc(unit);

				/* 空きのアドレスがない場合 */
				if(next_desc_save_mem == NULL)
				{
					remove_desc_entry(unit);
					return -AG903_ENOBUFS;
				}
			}
		}

		/* 現在のdescをメモリに書き込む */
		write_desc(desc, next_desc_save_mem, (void*)desc_save_mem, 0);

		if(true == ll_circ)
		{
			handle->term_addr_table[handle->term_addr_table_num].current = (uint32_t)desc_save_mem;
			handle->term_addr_table[handle->term_addr_table_num].next    = (uint32_t)next_desc_save_mem;
			handle->term_addr_table_num++;
		}

		desc_copy_entry[desc_copy_entry_num].src = desc;
		desc_copy_entry[desc_copy_entry_num].dst = desc_save_mem;
		desc_save_mem = next_desc_save_mem;
		desc_copy_entry_num++;

		/*
		終端条件は以下の2パターン
		1. desc->nextがNULLの場合
		2. desc->nextが循環参照している場合
		3. リストの要素をすべて書き終わったとき
		*/
		if(desc->next == NULL || ll_circ == true || list_num == 0)
		{
			break;
		}

		list_num--;
		desc = desc->next;
	}
	if(AG903_DMAC_DESC_LINK_MAX <= loop)
	{
		return -AG903_EINVAL;	/* 最大リンク数超過 */
	}

	return AG903_ENONE;
}


/**
 * @brief           簡易転送用ディスクリプタ設定
 * @param           handle [in] DMACハンドル
 * @param           from [in] 転送元メモリアドレス
 * @param           from_size [in] 転送元メモリサイズ
 * @param           to [in] 転送先メモリアドレス
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @retval          -AG903_EBUSY  実行中
 * @description     簡易転送用ディスクリプタ設定<p>
 *                  DMA転送前に再設定した場合は後の内容で上書きされます。
*/
int32_t AG903_DMACMgrSetSimpleTrnsDesc(AG903_DMACMgrHandle *handle, void *from,
 uint32_t from_size, void *to)
{
	uint32_t unit = ((AG903_DMACMgrHandleStat*)handle - handle_list);
	AG903_DMACMgrDesc 	dmac_desc;
	DMACPrmParamCFG cfg = {0};
	uint8_t	 enable;

	if(unit >= AG903_DMAC_UNIT_NUM || &handle_list[unit] != (AG903_DMACMgrHandleStat*)handle ||
	   NULL == from || NULL == to || 0 == from_size || AG903_DMAC_1DTRANS_MAX < from_size)
	{
		return -AG903_EINVAL;
	}

	/* 実行中の場合 */
	enable = DMACMgr_CheckEnable(unit);
	if(true == enable) {
		return -AG903_EBUSY;
	}

	cfg.ChPri = 1;
	AG903_DMACPrmSetCFG_REG(unit, &cfg);

	dmac_desc.SrcAddr           = (uint32_t)from;
	dmac_desc.DstAddr           = (uint32_t)to;
	dmac_desc.next				= 0;
	dmac_desc.Ctrl.val			= 0;
	dmac_desc.Trns.val			= 0;
	dmac_desc.Trns.d1.TCnt      = from_size;
	dmac_desc.Stride.val		= 0;

	/* レジスタへ書き込み */
	write_desc(&dmac_desc, NULL, get_desc_reg_addr(unit), 1);

	return AG903_ENONE;
}

/**
 * @brief           ディスクリプタ設定
 * @param           handle [in] DMACハンドル
 * @param           config [in] コンフィグ設定
 * @param           desc_list [in] ディスクリプタリスト
 * @param           list_num [in] ディスクリプタリスト数
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @retval          -AG903_EBUSY  実行中
 * @retval          -AG903_ENOBUFS ディスクリプタ保存先不足
 * @description     ディスクリプタ設定
 * @description     ディスクリプタリストの設定はAG903仕様書(AX51903_DSxx.pdf)のレジスタ編を参照して下さい。
 *                  * DMACnCTRL_REG DMACnCTRL_REG DMACnSRC_ADDR DMACnDST_ADDR DMACnLINK_LIST_POINTER DMACnTRNS_SIZE_xD DMACnSTRIDE_SRC_DST_ADDR
 *                  * 設定すべき共用体は引数のコンフィグ設定によります。
 *                  * nextは引数として渡すディスクリプタリストのアドレスで指定して下さい。
 * @note            ディスクリプタリストは転送完了ごとに再設定する必要があります。
 * @note            この関数は割り込みコールバックの中で使用することができます。
 *                  ただしその場合引数のディスクリプタリストはstaticな変数として下さい。
 *                  割り込みコールバックの中でなければ関数から戻った後に破棄することができます。
 * @note            この関数はディスクリプタリストを使った転送が行われているとき実行禁止です。
 * @note            ディスクリプタリストはDMACMgrハンドラ間で排他的にアクセスして下さい。
*/
int32_t AG903_DMACMgrSetDescList(AG903_DMACMgrHandle *handle, AG903_DMACMgrConfig *config,
	                                                          AG903_DMACMgrDesc *desc_list, int list_num)
{
	uint32_t unit = ((AG903_DMACMgrHandleStat*)handle - handle_list);
	uint8_t	 enable;

	if(unit >= AG903_DMAC_UNIT_NUM || &handle_list[unit] != (AG903_DMACMgrHandleStat*)handle ||
	   NULL == config || NULL == desc_list || 0 == list_num)
	{
		return -AG903_EINVAL;
	}

	/* 実行中の場合 */
	enable = DMACMgr_CheckEnable(unit);
	if(true == enable) {
		return -AG903_EBUSY;
	}

	if(false == initdesc) {
		initdesc = true;
		AG903_DMACPrmSetLOCAL_DESC_MEM_BASE(LOCAL_DESC_MEM_ADDR);
	}
	DMACPrmParamCFG cfg = {
		.TCIntMsk    = config->st.TCIntMsk,
		.ErrIntMsk	 = config->st.ErrIntMsk,
		.AbtIntMsk	 = config->st.AbtIntMsk,
		.SrcRS		 = config->st.SrcRS,
		.SrcHEn		 = config->st.SrcHEn,
		.DstRS		 = config->st.DstRS,
		.DstHEn		 = config->st.DstHEn,
		.LLPCnt		 = config->st.LLPCnt,
		.ChGntWin	 = config->st.ChGntWin,
		.ChPri		 = config->st.ChPri,
		.WOMode		 = config->st.WOMode,
		.UnalignMode = config->st.UnalignMode,
	};
	AG903_DMACPrmSetCFG_REG(unit, &cfg);

	return set_desc_entry((AG903_DMACMgrHandleStat*)handle, desc_list, list_num);
}

/**
 * @brief           ディスクリプタ破棄
 * @param           handle [in] DMACハンドル
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @description     ディスクリプタ破棄
 * @note            この関数はディスクリプタリストを使った転送が行われているとき実行禁止です。
 * @note            ディスクリプタリストはDMACMgrハンドラ間で排他的にアクセスして下さい。
*/
int32_t AG903_DMACMgrRemoveDescList(AG903_DMACMgrHandle *handle)
{
	uint32_t unit = ((AG903_DMACMgrHandleStat*)handle - handle_list);

	/* 無効なハンドル */
	if(unit >= AG903_DMAC_UNIT_NUM || &handle_list[unit] != (AG903_DMACMgrHandleStat*)handle)
	{
		return -AG903_EINVAL;
	}

	return remove_desc_entry(unit);
}

/**
 * @brief           リモードディスクリプタ領域アドレス指定
 * @param           addr [in] リモードディスクリプタ領域アドレス
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EINVAL パラメータ異常
 * @retval          -AG903_EBUSY  実行中
 * @retval          -AG903_EPERM  設定済
 * @description     リモードディスクリプタ領域アドレス指定
 * @note            この設定はすべてのDMACチャンネルに共通です。
 * @note            ディスクリプタリストを使った転送を開始する前に設定して下さい。
 * @note            リモートディスクリプタ領域は AG903_DMAC_LDD_ENTRY_NUM × 20バイト必要です。
 * @note            リモートディスクリプタ領域のメモリ属性はキャッシュ不可にして下さい。
 * @note            この関数はディスクリプタリストを使った転送が行われているとき実行禁止です。
 * @seealso         AG903_DMAC_LDD_ENTRY_NUM
*/
int32_t AG903_DMACMgrSetRemoteDescAddr(void *addr)
{
	int32_t  retval = AG903_ENONE;
	uint32_t loop;
	uint8_t	 enable;

	if(NULL == addr)
	{
		return -AG903_EINVAL;
	}
	if(NULL != rdd_data)
	{
		return -AG903_EPERM;
	}

	for(loop=0; loop<AG903_DMAC_UNIT_NUM; loop++)
	{
		enable = DMACMgr_CheckEnable(loop);
		if(true == enable) {
			retval = -AG903_EBUSY;
			break;
		}
	}
	if(AG903_ENONE == retval)
	{
		rdd_data = (uint8_t*)addr;
	}

	return retval;
}

/**
 * @brief           リモードディスクリプタ領域アドレスクリア
 * @param           none
 * @return          エラーコード
 * @retval          AG903_ENONE  正常終了
 * @retval          -AG903_EBUSY  実行中
 * @description     リモードディスクリプタ領域アドレスクリア
 * @note            この設定はすべてのDMACチャンネルに共通です。
 * @note            この関数はディスクリプタリストを使った転送が行われているとき実行禁止です。
*/
int32_t AG903_DMACMgrClearRemoteDescAddr(void)
{
	int32_t  retval = AG903_ENONE;
	uint32_t loop;
	uint8_t	 enable;

	for(loop=0; loop<AG903_DMAC_UNIT_NUM; loop++)
	{
		enable = DMACMgr_CheckEnable(loop);
		if(true == enable) {
			retval = -AG903_EBUSY;
			break;
		}
	}
	if(AG903_ENONE == retval)
	{
		rdd_data = NULL;
	}

	return retval;
}
