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

#ifdef _COMMON_BF592
#include <cdefBF592-A.h>
#elif defined(_COMMON_BF506)
#include <cdefBF506F.h>
#elif defined(_COMMON_BF518)
#include <cdefBF518.h>
#elif defined(_COMMON_BF537)
#include <cdefBF537.h>
#else
#error "This processor is not supported"
#endif

#include <ccblkfn.h>


//#define I2C_DEBUG

#ifdef I2C_DEBUG
#define I2C_SYSLOG(level,msg) syslog( level, msg )
#else
#define I2C_SYSLOG(level,msg)
#endif


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

/**
 * @brief I2C ペリフェラルのタイムアウト時間(mSec)
 */
#define I2C_TIMEOUT 100

/**
 * @brief TWIペリフェラルの管理用ステート型。内部ステートマシンの状態値。
 */
enum I2C_STATE {
    I2C_XMT_ENTRY,
    I2C_XMTRCV_ENTRY,
    I2C_XMT_WAIT,
    I2C_XMT_INT,
    I2C_XMT_NEXT_BYTE,
    I2C_RCV_ENTRY,
    I2C_RCV_WAIT,
    I2C_RCV_INT,
    I2C_RCV_NEXT_BYTE,
    I2C_EXIT
};


/**
 * @brief I2C管理用構造体
 */
struct I2C_MASTER_CONTROL_TYPE {
    ID signal;                /**< データの送受信が全部終わったときに割り込みハンドラからタスクに知らせるためのセマフォ */
    ID blocking;            /**< I2Cペリフェラルへの排他アクセスのためのセマフォ */
    unsigned short intr_state;         /**< TWIペリフェラルの割り込みステータスのコピー **/
    volatile unsigned short *   clkdiv;      /**< TWI_CLCKDIVレジスタのアドレス **/
    volatile unsigned short *    control;     /**< TWI_CONTROLレジスタのアドレス **/
    volatile unsigned short *    master_ctl;  /**< TWI_MASTER_CTLレジスタのアドレス **/
    volatile unsigned short *    master_stat; /**< TWI_MASTER_STATレジスタのアドレス **/
    volatile unsigned short *    master_addr; /**< TWI_MASTER_ADDRレジスタのアドレス **/
    volatile unsigned short *    int_stat;    /**< TWI_MASTER_STATレジスタのアドレス **/
    volatile unsigned short *    int_mask;    /**< TWI_INT_MASKレジスタのアドレス **/
    volatile unsigned short *    fifo_ctl;    /**< TWI_FIFO_CTLレジスタのアドレス **/
    volatile unsigned short *    fifo_stat;   /**< TWI_FIFO_STATレジスタのアドレス **/
    volatile unsigned short *    xmt_data8;   /**< TWI_XMT_DATA8レジスタのアドレス **/
    volatile unsigned short *    rcv_data8;   /**< TWI_RCV_DATA8レジスタのアドレス **/
    volatile unsigned short *    slave_ctl;   /**< TWI_MASTER_CTLレジスタのアドレス **/

};

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

static char * i2c_strerror( ER ercd )
{
    ercd &= 0xFFFF0000;
    switch (ercd)
    {
    case I2C_ERR_WRONGPARAM :
        return("I2C_ERR_WRONGPARAM");
    case I2C_ERR_TOOLONGBUFFER :
        return("I2C_ERR_TOOLONGBUFFER");
    case I2C_ERR_TIMEOUT :
        return ("I2C_ERR_TIMEOUT");
    }
    return ("I2C_ERR_UNKNOWN");
}


void
i2c_perror(UINT prio, const char *file, int line, const char *expr, ER ercd)
{
    syslog_5(prio, "%s reported by `%s' in line %d of `%s'. Interrupt Status = %04x",
        i2c_strerror(ercd), expr, line, file, ercd & 0xFFFF);
}


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)
{
    struct I2C_MASTER_CONTROL_TYPE *twi;
    BOOL read, write, rstart, quit;
    unsigned char *wptr, *rptr;
    enum I2C_STATE state;      /**< I2Cペリフェラルハンドラの内部状態 */
    PRI old_priority;         // タスクの優先順位を保存しておくための変数

        // 可読性向上のため、TWI管理構造体のポインタを設定する。
    twi = &i2c_control[peripheral];

        // 送受信ポインタ.とかくこの世は生きにくい
    wptr = write_data;
    rptr = read_data;

        // この代入は必要ないが、警告がうるさいので
    rstart= FALSE;

        // ループ終了条件の成立フラグ
    quit = FALSE;

        // 読み書きの有無の確認
        // バッファがNULLだったり長さが0のパラメタは転送に使わない
    write = ( write_data != NULL ) && ( write_count != 0 );
    read =  ( read_data != NULL )  && ( read_count != 0 );

        // パラメタの組み合わせによって初期状態が変わる
    if ( read && write )
        state = I2C_XMTRCV_ENTRY;
    else if ( write && ! read )
        state = I2C_XMT_ENTRY;
    else if ( ! write && read )
        state = I2C_RCV_ENTRY;
    else
        return I2C_ERR_WRONGPARAM;

    if ( write_count > 254 || read_count > 254 )
        return I2C_ERR_TOOLONGBUFFER;


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

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

        // SPIスレーブデバイスのアドレスを設定(7bit)
    *twi->master_addr = slave;

        // FIFO フラッシュ
    *twi->fifo_ctl = XMTFLUSH | RCVFLUSH;
    ssync();

        // FIFO 設定
    *twi->fifo_ctl = XMTINTLEN;                        // 割り込みスレシホールドは送受とも１バイト、FLUSHはクリアする

        // TWIステートマシン
    do {
        switch (state)
        {
        case I2C_XMT_ENTRY :
            I2C_SYSLOG(LOG_NOTICE, "I2C_XMT_ENTRY" );
            rstart = FALSE;
            *twi->master_ctl = ( write_count<<6 ) | MEN;            // no rstart, transmit, no fast mode;
            state = I2C_XMT_NEXT_BYTE;
            break;
        case I2C_XMTRCV_ENTRY :
            I2C_SYSLOG(LOG_NOTICE, "I2C_XMTRCV_ENTRY" );
            rstart = TRUE;
            *twi->master_ctl = ( write_count<<6 ) | RSTART | MEN;    // rstart, transmit, no fast mode;
            state = I2C_XMT_NEXT_BYTE;
            break;
        case I2C_RCV_ENTRY :
            I2C_SYSLOG(LOG_NOTICE, "I2C_RCV_ENTRY" );
            rstart = FALSE;
            *twi->master_ctl = ( read_count<<6 ) | MDIR | MEN;      // no rstart, receive, no fast mode;
            state = I2C_RCV_WAIT;
            break;
        case I2C_XMT_WAIT :
            I2C_SYSLOG(LOG_NOTICE, "I2C_XMT_WAIT" );
                // 割り込みハンドラが送信割り込みを通知するまで待つ
            if ( E_OK != twai_sem(twi->signal, I2C_TIMEOUT))
            {
                twi->intr_state = I2C_ERR_TIMEOUT | *twi->int_stat;     // エラーなら割り込みステータスを併記
                state = I2C_EXIT;
            }
            else
                state = I2C_XMT_INT;
            break;
        case I2C_XMT_INT :
            I2C_SYSLOG(LOG_NOTICE, "I2C_XMT_INT" );
            if ( twi->intr_state & MERR )                        // エラーならすぐ終了
                state = I2C_EXIT;
            else if ( ( twi->intr_state & MCOMP ) && rstart)    // MCOMP かつ RSTARTなら受信へ
                state = I2C_RCV_ENTRY;
            else if ( twi->intr_state & MCOMP ){                // RSTARTがないMCOMPなら終了
                twi->intr_state &= ~MCOMP;
                state = I2C_EXIT;
            }
            else                                                // それ以外は送信割り込み
                state = I2C_XMT_NEXT_BYTE;
            break;
        case I2C_XMT_NEXT_BYTE :
            I2C_SYSLOG(LOG_NOTICE, "I2C_XMT_NEXT_BYTE" );
            *twi->xmt_data8 = *(wptr++);        // 1バイト送信
            state = I2C_XMT_WAIT;          // 次の送信待
            break;
        case I2C_RCV_WAIT :
            I2C_SYSLOG(LOG_NOTICE, "I2C_RCV_WAIT" );
                // 割り込みハンドラが受信割り込みを通知するまで待つ
            if ( E_OK != twai_sem(twi->signal, I2C_TIMEOUT))
            {
                twi->intr_state = I2C_ERR_TIMEOUT | *twi->int_stat;     // エラーなら割り込みステータスを併記
                state = I2C_EXIT;
            }
            else
                state = I2C_RCV_INT;
            break;
        case I2C_RCV_INT :
            I2C_SYSLOG(LOG_NOTICE, "I2C_RCV_INT" );
            if ( twi->intr_state & MERR)            // エラーならすぐ終了
                state = I2C_EXIT;
            else if ( twi->intr_state & MCOMP ){   // 終了ならエラークリア
                twi->intr_state &= ~MCOMP;
                state = I2C_EXIT;
            }
            else                                   // それ以外は受信割り込み
                state = I2C_RCV_NEXT_BYTE;
            break;
        case I2C_RCV_NEXT_BYTE :
            I2C_SYSLOG(LOG_NOTICE, "I2C_RCV_NEXT_BYTE" );
            *(rptr++) = *twi->rcv_data8;        // 1バイト受信
            state = I2C_RCV_WAIT;        // 次の受信待
            break;
        case I2C_EXIT :
            I2C_SYSLOG(LOG_NOTICE, "I2C_EXIT" );
            * twi->master_ctl = 0;            // マスターをディセーブルして終了
            quit = TRUE;
            break;
        }
    } while ( !quit );

    // タスクの優先順位を元に戻す
    chg_pri( TSK_SELF, old_priority);

    // 排他区間の終了
    sig_sem(twi->blocking);

        // 割り込みステータスを返す。正常終了なら0
    return twi->intr_state;
}

/**
 * @brief i2c 割り込みサービスルーチン本体
 * @param peripheral I2Cペリフェラル番号。0から始まる。
 * @details
 * 割り込みサービスルーチンから呼び出す。この関数が割り込みハンドラの実体である。
 * チップに複数のTWIペリフェラルがある場合には、引数に当該TWIペリフェラル番号を与えて呼び出す。
 *
 * 関数内部では、タスクに割り込みが入ったことを知らせ、ペリフェラルの割り込みステータスを
 * 変数に退避してから戻る。
 */
static void i2c_master_handler( int peripheral )
{
    struct I2C_MASTER_CONTROL_TYPE *twi;

        // 可読性向上のため、TWI管理構造体のポインタを設定する。
    twi = &i2c_control[peripheral];

        // TWIペリフェラルの割り込みステータスを取得し、コピーを保存する。
        // コピーを保存するのは、このあと割り込みクリアで消えるからである。
    twi->intr_state = *twi->int_stat;

        // 割り込みクリア。すべての割り込み要素をクリアしてしまう。ステータスはコピーしているのでタスクで処理する。
    *twi->int_stat = 0xFF;

        // 割り込みクリアが確定するまで待つ。
    ssync();

    /* 通知はセマフォを使う。タスクは i2c_master_write_read()の中で待っている。 */
    isig_sem(twi->signal);
}


/**
 * @details
 * TOPPERS/JSPでは、DEF_INHから割り込みハンドラに引数を渡せない。そのため、この関数内で
 * i2c_master_handler() を引数0決め打ちで呼ぶ。
 */
void i2c0_master_handler(void)
{
    i2c_master_handler( 0 );
}


/**
 * @brief i2c イニシャライザ
 * @param exinf ペリフェラル番号。TWI0ならば、0を渡す。
 * @details
 * I2C用のコントロールブロックのI2C相当部分を初期化する。
 */
void i2c_master_initialize(VP_INT exinf)
{
    struct I2C_MASTER_CONTROL_TYPE *twi;
    unsigned int peripheral = (unsigned int)exinf;

        // 可読性向上のため、TWI管理構造体のポインタを設定する。
    twi = &i2c_control[peripheral];

    twi->blocking = SEM_I2C0_BLOCK;    // ペリフェラルブロック用セマフォ
    twi->signal = SEM_I2C0_SIGNAL;    // 割り込み・タスク通信用セマフォ
        // レジスタアドレスを設定
    twi->clkdiv         =    pTWI_CLKDIV    ;
    twi->control        =    pTWI_CONTROL    ;
    twi->master_ctl     =    pTWI_MASTER_CTL    ;
    twi->master_stat    =    pTWI_MASTER_STAT    ;
    twi->master_addr    =    pTWI_MASTER_ADDR    ;
    twi->int_stat       =    pTWI_INT_STAT    ;
    twi->int_mask       =    pTWI_INT_MASK    ;
    twi->fifo_ctl       =    pTWI_FIFO_CTL    ;
    twi->fifo_stat      =    pTWI_FIFO_STAT    ;
    twi->xmt_data8      =    pTWI_XMT_DATA8    ;
    twi->rcv_data8      =    pTWI_RCV_DATA8    ;
    twi->slave_ctl      =    pTWI_SLAVE_CTL    ;


    *twi->control = 0;        // TWI をディセーブル
    *twi->master_ctl = 0;    // マスター機能をディセーブル
    *twi->slave_ctl = 0;    // スレーブ機能をディセーブル
    *twi->master_addr = 0;
    *twi->fifo_ctl = 0;
    *twi->int_mask = 0;
    *twi->int_stat = 0x1F;  // すべての割り込みステータスをクリア
    *twi->master_stat =0x1F; // すべてのステータスをクリア

    *twi->control = TWI_ENA | (SYSCLOCK/10000000);     // HWRによると、プリスケール値はSYSCLKを10MHzで割ったものでなければならない。
    *twi->clkdiv = 30<<8 | 70;                         // TWI内部クロック10MHzに対して、100kHzのI2Cクロックは100分の１である

        // 割り込みイネーブル設定。送受割り込み、完了割り込み及びエラー割り込み。
    *twi->int_mask =     MCOMP | MERR | XMTSERV | RCVSERV;

        // システム割り込みをイネーブルにする。
    ena_int(INTNO_TWI);
}


