/**
 * \file i2c_subsystem.c
 * \brief i2cペリフェラルをマスターモードで使うためのサブシステム
 * \details
 * CMSISを使い、i2cペリフェラルをマスターモードで使用する
 */
#include "i2c_subsystem.h"

extern ID SEM_I2C0_BLOCK_id;
extern ID SEM_I2C0_SIGNAL_id;
extern ID SEM_I2C1_BLOCK_id;
extern ID SEM_I2C1_SIGNAL_id;
extern ID SEM_I2C2_BLOCK_id;
extern ID SEM_I2C2_SIGNAL_id;

/**
 * \brief チップ上のI2Cペリフェラルの数
 */
#define I2CNUM 3

/**
 * \brief I2C管理用構造体
 */
struct I2C_CONTROL_TYPE {
	ID signal;				/**< データの送受信が全部終わったときに割り込みハンドラからタスクに知らせるためのセマフォ */
	ID blocking;			/**< I2Cペリフェラルへの排他アクセスのためのセマフォ */
	LPC_I2C_TypeDef *i2c;	/**< I2Cペリフェラルの先頭アドレス。 CMSIS の関数が使用する */
	void 	(*callback)(void);	/* 割り込みハンドラが */
};

/**
 * \brief i2cペリフェラルを管理するための構造体群。
 */
static struct I2C_CONTROL_TYPE i2c_control[I2CNUM];


int i2c_master_write( int peripheral, int slave, unsigned char write_data[], int write_count )
{
		// read のための引数を0にしておくと、writeのみがおこなわれる
	return i2c_master_write_read( peripheral, slave, write_data, write_count, 0, 0);
}

int i2c_master_read( int peripheral, int slave, unsigned char read_data[], int read_count )
{
		// write のための引数を0にしておくと、readのみがおこなわれる
	return i2c_master_write_read( peripheral, slave, 0, 0, read_data, read_count);
}


int i2c_master_write_read( int peripheral, int slave, unsigned char write_data[], int write_count, unsigned char read_data[], int read_count)
{
	I2C_M_SETUP_Type setup;

		// CMSISが内部で使うコンフィギュレーション変数の設定。こ
	setup.sl_addr7bit = 	slave;
	setup.tx_data = 		write_data;
	setup.tx_length = 	write_count;
	setup.rx_data = 		read_data;
	setup.rx_length = 	read_count;
	setup.retransmissions_max = 3;
	setup.callback = i2c_control[peripheral].callback;

		// peripheral 引数で指定されたi2cペリフェラルを排他的に使うためのPV処理。
		// これでスレッドセーフにできる
	wai_sem(i2c_control[peripheral].blocking);

	I2C_MasterTransferData(
								i2c_control[peripheral].i2c, 	// どのI2Cペリフェラルを使うか指定する,
								&setup,						// コンフィギュレーション変数を渡す,
								I2C_TRANSFER_INTERRUPT		//	割り込みベース
								);

		// 割り込みハンドラが送受処理を完了するまで待つ
	wai_sem(i2c_control[peripheral].signal);
		// 排他区間の終了
	sig_sem(i2c_control[peripheral].blocking);

		// setup.status には処理の結果の他、I2Cステータスレジスタの最後の値が入っている
		// 正常終了なら0を返す
	if ( setup.status & I2C_SETUP_STATUS_DONE )
		return 0;
	else
		return setup.status;
}

/**
 * \brief i2c0 コールバック関数
 * \details
 * 割り込みハンドラが、タスクに処理の完了を通知するためのコールバック関数。
 */
static void i2c0_master_report_complete(void)
{
		/* 通知はセマフォを使う。タスクは i2c_master_write_read()の中で待っている。 */
	isig_sem(i2c_control[0].signal);
}
/**
 * \brief i210 コールバック関数
 * \details
 * 割り込みハンドラが、タスクに処理の完了を通知するためのコールバック関数。
 */
static void i2c1_master_report_complete(void)
{
		/* 通知はセマフォを使う。タスクは i2c_master_write_read()の中で待っている。 */
	isig_sem(i2c_control[1].signal);
}
/**
 * \brief i2c2 コールバック関数
 * \details
 * 割り込みハンドラが、タスクに処理の完了を通知するためのコールバック関数。
 */
static void i2c2_master_report_complete(void)
{
		/* 通知はセマフォを使う。タスクは i2c_master_write_read()の中で待っている。 */
	isig_sem(i2c_control[2].signal);
}

/**
 * \brief i2c 割り込みサービスルーチン
 * \details
 * ATT_ISRを使って静的コンフィギュレータで宣言する。
 */
void i2c_master_isr(intptr_t exinf)
{
		/* 処理はCMSISの割り込みハンドラにすべて任せる。ただし、2011年4月時点で、*/
		/* CMSIS 2.0 のI2C_MasterHandler()には、コールバックを行わないバグがある */
		/* このバグは修正が必要 */
	switch ((int)exinf){
	case 0 :
		I2C_MasterHandler (LPC_I2C0);
		break;
	case 1 :
		I2C_MasterHandler (LPC_I2C1);
		break;
	case 2 :
		I2C_MasterHandler (LPC_I2C2);
		break;
	}
}


/**
 * \brief i2c0 イニシャライザ
 * \details
 * I2C0用のコントロールブロックのI2C0相当部分を初期化する。
 */
void i2c0_master_initialize(intptr_t exinf)
{
	i2c_control[0].blocking = SEM_I2C0_BLOCK_id;
	i2c_control[0].signal = SEM_I2C0_SIGNAL_id;
	i2c_control[0].i2c = LPC_I2C0;
	i2c_control[0].callback = i2c0_master_report_complete;

		// CMSIS内部のクロック変数の設定のための呼び出し
		// この関数はシステム初期化時に一度呼べばいいのだが、CMSIS依存関数を
		// target_initialize() で呼ぶのはためらわれる。そのため、複数回呼ぶ
		// 無駄を承知でここで呼んでいる。
	SystemCoreClockUpdate();

		// I2C のクロックデバイダの設定
	I2C_Init(LPC_I2C0, 100000);

		/* Enable  I2C operation */
	I2C_Cmd(LPC_I2C0, ENABLE);
}

/**
 * \brief i2c1 イニシャライザ
 * \details
 * I2C0用のコントロールブロックのI2C1相当部分を初期化する。
 */
void i2c1_master_initialize(intptr_t exinf)
{
	i2c_control[1].blocking = SEM_I2C1_BLOCK_id;
	i2c_control[1].signal = SEM_I2C1_SIGNAL_id;
	i2c_control[1].i2c = LPC_I2C1;
	i2c_control[1].callback = i2c1_master_report_complete;

		// CMSIS内部のクロック変数の設定のための呼び出し
		// この関数はシステム初期化時に一度呼べばいいのだが、CMSIS依存関数を
		// target_initialize() で呼ぶのはためらわれる。そのため、複数回呼ぶ
		// 無駄を承知でここで呼んでいる。
	SystemCoreClockUpdate();

		// I2C のクロックデバイダの設定
	I2C_Init(LPC_I2C1, 100000);

		/* Enable  I2C operation */
	I2C_Cmd(LPC_I2C1, ENABLE);
}

/**
 * \brief i2c2 イニシャライザ
 * \details
 * I2C用のコントロールブロックのI2C2相当部分を初期化する。
 */
void i2c2_master_initialize(intptr_t exinf)
{
	i2c_control[2].blocking = SEM_I2C2_BLOCK_id;
	i2c_control[2].signal = SEM_I2C2_SIGNAL_id;
	i2c_control[2].i2c = LPC_I2C2;
	i2c_control[2].callback = i2c2_master_report_complete;

		// CMSIS内部のクロック変数の設定のための呼び出し
		// この関数はシステム初期化時に一度呼べばいいのだが、CMSIS依存関数を
		// target_initialize() で呼ぶのはためらわれる。そのため、複数回呼ぶ
		// 無駄を承知でここで呼んでいる。
	SystemCoreClockUpdate();

		// I2C のクロックデバイダの設定
	I2C_Init(LPC_I2C2, 100000);

		/* Enable  I2C operation */
	I2C_Cmd(LPC_I2C2, ENABLE);
}


