/**
 * @brief       GPIO Manager
 * @author      AXELL CORPORATION
 * @description GPIO Manager Layer
 * @note        none
 * @history     2017_02_22  初版
 * @history     2017_10_26  Ver2.0
*/
/* DOM-IGNORE-BEGIN */
/*
 * This program was created by AXELL CORPORATION.
 * Copyright (C) 2017-2019 AXELL CORPORATION, all rights reserved.
 */
/* DOM-IGNORE-END */

#include "AG903_errno.h"
#include "AG903_intno.h"
#include "gpio/gpiomgr.h"
#include "gpio/gpioprm.h"
#include "int/intmgr.h"

typedef void (*AG903_GPIOMgrIntHdr)(void);	/* コールバック関数型 */

typedef struct _AG903_GPIOMgrIntParamt{
	AG903_GPIOMgrClbk 	clbk;	/* コールバック */
	uint32_t	target;			/* コールバック対象 */
} AG903_GPIOMgrIntParam;

typedef struct _AG903_GPIOMgrChStat{
	AG903_GPIOMgrIntParam	intr[AG903_GPIO_CALLBACK_NUM];
	int32_t		hdrid;			/* ハンドラID */
} AG903_GPIOMgrChStat;


static AG903_GPIOMgrChStat	GpioChStat[AG903_GPIO_CH_NUM];	/* ステータス */

static void GPIOMgr_Init(uint8_t ch);
static int32_t GPIOMgr_DeleteCallback(uint8_t ch, uint32_t num);
static void GPIOMgr_Inthdr0(void);
static void GPIOMgr_Inthdr1(void);
static void GPIOMgr_Inthdr2(void);
static void GPIOMgr_Inthdr3(void);
static void GPIOMgr_IntProcess(uint8_t ch);

static const AG903_GPIOMgrIntHdr GpioIntHdr[AG903_GPIO_CH_NUM] =
{ GPIOMgr_Inthdr0, GPIOMgr_Inthdr1, GPIOMgr_Inthdr2, GPIOMgr_Inthdr3 };


/**
 * @brief           GPIO Manager初期化
 * @param           none
 * @return          初期化結果
 * @retval          AG903_ENONE 正常終了
 * @description     内部変数を初期化します。システム起動時にCALLして下さい。
 * @note            使用を中止するときは必要に応じアプリでAG903_INTMgrDisableIntによりGPIOの割り込みを無効にして下さい。
*/
int32_t AG903_GPIOMgrInit(void)
{
	int32_t	 retval = AG903_ENONE;
	uint32_t loop;
	
	for(loop=0; loop<AG903_GPIO_CH_NUM; loop++) {
		GPIOMgr_Init(loop);
	}
	
	return retval;
}

/**
 * @brief           Portデータ書込み（8ビット単位）
 * @param           blknum [in] Portブロック番号（0〜15）
 * @param           data [in] 8ビットデータ
 * @return          Portデータ書込み（8ビット単位）結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Protブロック(8bit)単位でデータを書き込みます。<p>
 *                  Protブロック番号 = GPIO番号/8
*/
int32_t AG903_GPIOMgrSetByteData(uint8_t blknum, uint8_t data)
{
	int32_t retval = AG903_ENONE;
	uint8_t ch;
	uint8_t offset;
	
	if(AG903_GPIO_PORTBLK_TORTAL <= blknum) {
		return -AG903_EINVAL;
	}
	ch		= (blknum / 4);	/* 8ビット単位 */
	offset 	= (blknum % 4);	/* 8ビット単位 */
	AG903_GPIOPrmSetByteData(ch, offset, data);

	return retval;
}

/**
 * @brief           Portデータ書込み
 * @param           portch [in] Portチャネル番号（0〜3）
 * @param           data [in] Portデータ（32ビット）
 * @param           target [in] ターゲット指定（0:無変更, 1: dataを反映）
 * @return          Portデータ書込み結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Portチャネル（32bit）単位でデータを書き込みます。
 *                  target で1を指定したbitのみ data の内容が反映されます。
*/
int32_t AG903_GPIOMgrSetPortData(uint8_t portch, uint32_t data, uint32_t target)
{
	int32_t	 retval = AG903_ENONE;
	uint32_t set;
	uint32_t clr;

	if(AG903_GPIO_CH_NUM <= portch) {
		return -AG903_EINVAL;
	}
	
	set = (data&target);
	clr = (~data&target);
	
#if AG903_GPIO_USE_LDREX
	AG903_GPIOPrmModifyBit(portch, set, clr, true);
#else
	AG903_GPIOPrmModifyBit(portch, set, clr, false);
#endif

	return retval;
}

/**
 * @brief           Portデータ取得（8ビット単位）
 * @param           blknum [in] Port ブロック番号（0〜15）
 * @param           data [out] Portデータ（8ビット）
 * @return          Portデータ取得（8ビット単位）結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Protブロック(8bit)単位でデータを取得します。<p>
 *                  Protブロック番号 = GPIO番号/8
*/
int32_t AG903_GPIOMgrGetByteData(uint8_t blknum, uint8_t* data)
{
	int32_t retval = AG903_ENONE;
	uint8_t ch;
	uint8_t offset;
	
	if( (AG903_GPIO_PORTBLK_TORTAL <= blknum) ||
		(NULL == data) ){
		return -AG903_EINVAL;
	}
	ch		= (blknum / 4);	/* 8ビット単位 */
	offset 	= (blknum % 4);	/* 8ビット単位 */
	AG903_GPIOPrmGetByteData(ch, offset, data);

	return retval;
}

/**
 * @brief           Portデータ取得
 * @param           portch [in] Portチャネル番号（0〜3）
 * @param           data [out] Portデータ（32ビット）
 * @return          Portデータ取得結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Portチャネル（32bit）単位でデータを取得します。
*/
int32_t AG903_GPIOMgrGetPortData(uint8_t portch, uint32_t* data)
{
	int32_t	 retval = AG903_ENONE;
	
	if( (AG903_GPIO_CH_NUM <= portch) ||
		(NULL == data) ){
		return -AG903_EINVAL;
	}
	AG903_GPIOPrmGetPortData(portch, data);
	
	return retval;
}

/**
 * @brief           入出力設定
 * @param           portch [in] Portチャネル番号（0〜3）
 * @param           direction [in] 入出力指定（0:入力, 1:出力）
 * @return          入出力設定結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Portチャネル（32bit）単位で入出力を設定します。
*/
int32_t AG903_GPIOMgrSetDirection(uint8_t portch, uint32_t direction)
{
	int32_t	 retval = AG903_ENONE;

	if(AG903_GPIO_CH_NUM <= portch) {
		return -AG903_EINVAL;
	}
	AG903_GPIOPrmSetDirection(portch, direction);

	return retval;
}

/**
 * @brief           入出力取得
 * @param           portch [in] Portチャネル番号（0〜3）
 * @param           direction [out] 入出力（0:入力, 1:出力）
 * @return          入出力取得結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Portチャネル（32bit）単位で入出力の設定状態を取得します。
*/
int32_t AG903_GPIOMgrGetDirection(uint8_t portch, uint32_t* direction)
{
	int32_t	 retval = AG903_ENONE;

	if( (AG903_GPIO_CH_NUM <= portch) ||
		(NULL == direction) ){
		return -AG903_EINVAL;
	}
	AG903_GPIOPrmGetDirection(portch, direction);

	return retval;
}

/**
 * @brief           端子タイプ設定
 * @param           portch [in] Portチャネル番号（0〜3）
 * @param           type [in] 端子タイプ（0:通常, 1:オープンドレイン）
 * @return          端子タイプ設定結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Portチャネル（32bit）単位で端子タイプ（通常 or オープンドレイン）を設定します。
*/
int32_t AG903_GPIOMgrSetType(uint8_t portch, uint32_t type)
{
	int32_t	 retval = AG903_ENONE;

	if(AG903_GPIO_CH_NUM <= portch) {
		return -AG903_EINVAL;
	}
	AG903_GPIOPrmSetType(portch, type);

	return retval;
}

/**
 * @brief           端子タイプ取得
 * @param           portch [in] Portチャネル番号（0〜3）
 * @param           type [out] 端子タイプ（0:通常, 1:オープンドレイン）
 * @return          端子タイプ取得結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Portチャネル（32bit）単位で端子タイプ（通常 or オープンドレイン）の設定状態を取得します。
*/
int32_t AG903_GPIOMgrGetType(uint8_t portch, uint32_t* type)
{
	int32_t	 retval = AG903_ENONE;

	if( (AG903_GPIO_CH_NUM <= portch) ||
		(NULL == type) ){
		return -AG903_EINVAL;
	}
	AG903_GPIOPrmGetType(portch, type);

	return retval;
}

/**
 * @brief           立ち上がりエッジ検出設定
 * @param           portch [in] Portチャネル番号（0〜3）
 * @param           enable [in] 検出設定（0:無効, 1:有効）
 * @return          立ち上がりエッジ検出設定結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Portチャネル（32bit）単位で立ち上がりエッジ検出の有効/無効を設定します。
*/
int32_t AG903_GPIOMgrSetPositiveDetect(uint8_t portch, uint32_t enable)
{
	int32_t	 retval = AG903_ENONE;

	if(AG903_GPIO_CH_NUM <= portch) {
		return -AG903_EINVAL;
	}
	AG903_GPIOPrmSetPosEdgeDetect(portch, enable);

	return retval;
}

/**
 * @brief           立ち上がりエッジ検出設定取得
 * @param           portch [in] Portチャネル番号（0〜3）
 * @param           enable [out] 検出設定（0:無効, 1:有効）
 * @return          立ち上がりエッジ検出設定取得結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Portチャネル（32bit）単位で立ち上がりエッジ検出の有効/無効設定状態を取得します。
*/
int32_t AG903_GPIOMgrGetPositiveDetect(uint8_t portch, uint32_t* enable)
{
	int32_t	 retval = AG903_ENONE;

	if( (AG903_GPIO_CH_NUM <= portch) ||
		(NULL == enable) ){
		return -AG903_EINVAL;
	}
	AG903_GPIOPrmGetPosEdgeDetect(portch, enable);

	return retval;
}

/**
 * @brief           立ち下がりエッジ検出設定
 * @param           portch [in] Portチャネル番号（0〜3）
 * @param           enable [in] 検出設定（0:無効, 1:有効）
 * @return          立ち下がりエッジ検出設定結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Portチャネル（32bit）単位で立ち下がりエッジ検出の有効/無効を設定します。
*/
int32_t AG903_GPIOMgrSetNegativeDetect(uint8_t portch, uint32_t enable)
{
	int32_t	 retval = AG903_ENONE;

	if(AG903_GPIO_CH_NUM <= portch) {
		return -AG903_EINVAL;
	}
	AG903_GPIOPrmSetNegEdgeDetect(portch, enable);

	return retval;
}

/**
 * @brief           立ち下がりエッジ検出設定取得
 * @param           portch [in] Portチャネル番号（0〜3）
 * @param           enable [out] 検出設定（0:無効, 1:有効）
 * @return          立ち下がりエッジ検出設定取得結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Portチャネル（32bit）単位で立ち下がりエッジ検出の有効/無効設定状態を取得します。
*/
int32_t AG903_GPIOMgrGetNegativeDetect(uint8_t portch, uint32_t* enable)
{
	int32_t	 retval = AG903_ENONE;

	if( (AG903_GPIO_CH_NUM <= portch) ||
		(NULL == enable) ){
		return -AG903_EINVAL;
	}
	AG903_GPIOPrmGetNegEdgeDetect(portch, enable);

	return retval;
}

/**
 * @brief           エッジ検出マスク設定
 * @param           portch [in] Portチャネル番号（0〜3）
 * @param           mask [in] マスク設定（0:無効, 1:有効）
 * @return          エッジ検出マスク設定結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Portチャネル（32bit）単位でエッジ検出マスクの有効/無効を設定します。
*/
int32_t AG903_GPIOMgrSetMask(uint8_t portch, uint32_t mask)
{
	int32_t	 retval = AG903_ENONE;

	if(AG903_GPIO_CH_NUM <= portch) {
		return -AG903_EINVAL;
	}
	AG903_GPIOPrmSetMask(portch, mask);

	return retval;
}

/**
 * @brief           エッジ検出マスク設定取得
 * @param           portch [in] Portチャネル番号（0〜3）
 * @param           mask [out] マスク設定（0:無効, 1:有効）
 * @return          エッジ検出マスク設定取得結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Portチャネル（32bit）単位でエッジ検出マスクの有効/無効設定状態を取得します。
*/
int32_t AG903_GPIOMgrGetMask(uint8_t portch, uint32_t* mask)
{
	int32_t	 retval = AG903_ENONE;

	if( (AG903_GPIO_CH_NUM <= portch) ||
		(NULL == mask) ){
		return -AG903_EINVAL;
	}
	AG903_GPIOPrmGetMask(portch, mask);

	return retval;
}

/**
 * @brief           エッジ検出ステータス取得
 * @param           portch [in] Portチャネル
 * @param           status [out] エッジ検出ステータス
 * @return          エッジ検出ステータス取得結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Portチャネル（32bit）単位でエッジ検出ステータスを取得します。（0:未検出, 1:検出）
*/
int32_t AG903_GPIOMgrGetEdgeDetect(uint8_t portch, uint32_t* status)
{
	int32_t	 retval = AG903_ENONE;

	if( (AG903_GPIO_CH_NUM <= portch) ||
		(NULL == status) ){
		return -AG903_EINVAL;
	}
	AG903_GPIOPrmGetEdgeDetect(portch, status);

	return retval;
}

/**
 * @brief           エッジ検出ステータスクリア
 * @param           portch [in] Portチャネル
 * @param           clrbit [in] 制御対象（ビット指定）
 * @return          エッジ検出ステータスクリア結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Portチャネル（32bit）単位でエッジ検出ステータスをクリアします。<p>
 *                  clrbit で1を設定したbitのみクリアされます。
*/
int32_t AG903_GPIOMgrClearEdgeDetect(uint8_t portch, uint32_t clrbit)
{
	int32_t	 retval = AG903_ENONE;

	if( AG903_GPIO_CH_NUM <= portch) {
		return -AG903_EINVAL;
	}
	AG903_GPIOPrmClearEdgeDetect(portch, clrbit);

	return retval;
}

/**
 * @brief           エッジ検出コールバック設定
 * @param           portch [in] Portチャネル番号（0〜3）
 * @param           target [in] 対象ターゲット（ビット指定）
 * @param           clbk [in] コールバック関数ポインタ
 * @return          エッジ検出コールバック設定結果
 * @retval          AG903_ENONE     正常終了
 * @retval          -AG903_EINVAL   引数異常
 * @retval          -AG903_ENOBUFS  登録制限数オーバー
 * @retval          -AG903_EFAULT   処理失敗（割込みハンドラ登録失敗)
 * @description     Portチャネル（32bit）単位でエッジ検出コールバック関数を設定します。<p>
 *                  target で1を設定したbitに対して設定されます。
*/
int32_t AG903_GPIOMgrSetCallback(uint8_t portch, uint32_t target, AG903_GPIOMgrClbk clbk)
{
	int32_t	 retval = AG903_ENONE;
	AG903_INTMgrHdrPrm inthdr;
	int32_t	 hdrid;
	uint32_t loop;

	if( (AG903_GPIO_CH_NUM <= portch) ||
		(NULL == clbk) ){
		return -AG903_EINVAL;
	}
	
	if(0 == GpioChStat[portch].hdrid) {
		inthdr.atr   = AG903_INT_HLNG;
		inthdr.intno = AG903_IRQ40_GPIO0+portch;
		inthdr.func  = (void*)GpioIntHdr[portch];
		hdrid = AG903_INTMgrSetHandler(&inthdr);
		if(0 >= hdrid) {
			return -AG903_EFAULT;
		}
		GpioChStat[portch].hdrid = hdrid;	/* 割込みハンドラ登録 */
		AG903_INTMgrEnableInt(AG903_IRQ40_GPIO0+portch);
	}
	
	do{
		if(AG903_ENONE != retval) {
			break;
		}
		
		for(loop=0; loop<AG903_GPIO_CALLBACK_NUM; loop++) {
			if(clbk == GpioChStat[portch].intr[loop].clbk) {
				GpioChStat[portch].intr[loop].target |= target;	/* 追加登録 */
				break;
			}
		}
		if(AG903_GPIO_CALLBACK_NUM > loop) {
			break;
		}
		
		for(loop=0; loop<AG903_GPIO_CALLBACK_NUM; loop++) {
			if(NULL == GpioChStat[portch].intr[loop].clbk) {
				GpioChStat[portch].intr[loop].clbk = clbk;
				GpioChStat[portch].intr[loop].target = target;	/* 新規登録 */
				break;
			}
		}
		if(AG903_GPIO_CALLBACK_NUM <= loop) {
			retval = -AG903_ENOBUFS;	/* これ以上登録出来ない */
		}
	}while(0);

	return retval;
}

/**
 * @brief           エッジ検出コールバック設定クリア.
 * @param           portch [in] Portチャネル番号（0〜3）
 * @param           target [in] 対象ターゲット（ビット指定）
 * @param           clbk [in] コールバック関数ポインタ
 * @return          エッジ検出コールバック設定クリア結果
 * @retval          AG903_ENONE   正常終了
 * @retval          -AG903_EINVAL 引数異常
 * @description     Portチャネル（32bit）単位でエッジ検出コールバック関数を設定します。<p>
 *                  target で1を設定したbitに対して設定されます。
*/
int32_t AG903_GPIOMgrClearCallback(uint8_t portch, uint32_t target, AG903_GPIOMgrClbk clbk)
{
	int32_t	 retval = AG903_ENONE;
	uint32_t loop;

	if( (AG903_GPIO_CH_NUM <= portch) ||
		(NULL == clbk) ){
		return -AG903_EINVAL;
	}

	for(loop=0; loop<AG903_GPIO_CALLBACK_NUM; loop++) {
		if(clbk == GpioChStat[portch].intr[loop].clbk) {
			GpioChStat[portch].intr[loop].target &= ~target;
			if(0 == target) {
				GPIOMgr_DeleteCallback(portch, loop);
			}
			break;
		}
	}
	if(AG903_GPIO_CALLBACK_NUM <= loop) {
		retval = -AG903_EINVAL;	/* clbk未登録 */
	}
	
	return retval;
}

/*
 	状態初期化
	ch：Portチャネル番号（0〜3）
*/
static void GPIOMgr_Init(uint8_t ch)
{
	int32_t loop;
	for(loop=0; loop<AG903_GPIO_CALLBACK_NUM; loop++) {
		GpioChStat[ch].intr[loop].clbk   = NULL;
		GpioChStat[ch].intr[loop].target = 0;
	}
	GpioChStat[ch].hdrid = 0;

	return;
}

/*
	エッジ検出コールバック登録削除
	ch：Portチャネル番号（0〜3）
	num：コールバック登録番号
*/
static int32_t GPIOMgr_DeleteCallback(uint8_t ch, uint32_t num)
{
	int32_t	 retval = AG903_ENONE;
	uint32_t loop;

	if( (AG903_GPIO_CH_NUM <= ch) ||
		(AG903_GPIO_CALLBACK_NUM <= num) ){
		return -AG903_EINVAL;
	}
	
	if((AG903_GPIO_CALLBACK_NUM-1) <= num) {
		GpioChStat[ch].intr[num].clbk = NULL;
	}
	else {
		for(loop=num; loop<(AG903_GPIO_CALLBACK_NUM-1); loop++) {
			GpioChStat[ch].intr[loop] = GpioChStat[ch].intr[loop+1];	/* 構造体コピー */
			if(NULL == GpioChStat[ch].intr[loop].clbk) {
				break;
			}
		}
	}
	
	return retval;
}


/*
	割込みハンドラ(CH0)
*/
static void GPIOMgr_Inthdr0(void)
{
	GPIOMgr_IntProcess(0);	/* CH0 */
	return;
}

/*
	割込みハンドラ(CH1)
*/
static void GPIOMgr_Inthdr1(void)
{
	GPIOMgr_IntProcess(1);	/* CH1 */
	return;
}

/*
	割込みハンドラ(CH2).
*/
static void GPIOMgr_Inthdr2(void)
{
	GPIOMgr_IntProcess(2);	/* CH2 */
	return;
}

/*
	割込みハンドラ(CH3).
*/
static void GPIOMgr_Inthdr3(void)
{
	GPIOMgr_IntProcess(3);	/* CH3 */
	return;
}

/*
	割込み処理.
*/
static void GPIOMgr_IntProcess(uint8_t ch)
{
	uint32_t status = 0;
	uint32_t val;
	uint32_t loop;
	
	AG903_GPIOPrmGetEdgeDetect(ch, &status);
	AG903_GPIOPrmClearEdgeDetect(ch, status);	/* 検出クリア */
	
	for(loop=0; loop<AG903_GPIO_CALLBACK_NUM; loop++) {
		val = (status & GpioChStat[ch].intr[loop].target);
		if(0 != val) {
			GpioChStat[ch].intr[loop].clbk(ch, val); /* コールバック */
		}
		if(NULL == GpioChStat[ch].intr[loop].clbk) {
			break;
		}
	}
	
	return;
}
