#include "lpcxclk.h"
#include "LPC17xx.h"

/*
 * UM10360 (Rev.2 - 19 August 2010)
 */

/*
 * LPC1769版に限って言うと、ピン配置が比較的綺麗なので、ビット毎の操作が必要というわけではない。
 * ここであえてそうしているのは、他のLPCXpressoへの移植を容易にするためである。
 */

#define ANSEL_PORT_0	LPC_GPIO0	/**< セグメントAのポート。 */
#define ANSEL_PORT_1	LPC_GPIO0	/**< セグメントBのポート。 */
#define ANSEL_PORT_2	LPC_GPIO0	/**< セグメントCのポート。 */
#define ANSEL_PORT_3	LPC_GPIO0	/**< セグメントDのポート。 */
#define ANSEL_PORT_4	LPC_GPIO0	/**< セグメントEのポート。 */
#define ANSEL_PORT_5	LPC_GPIO0	/**< セグメントFのポート。 */
#define ANSEL_PORT_6	LPC_GPIO0	/**< セグメントGのポート。 */
#define ANSEL_PORT_7	LPC_GPIO0	/**< セグメントDPのポート。 */

#define ANSEL_PIN_0	(1 << 23)	/**< セグメントAのビット。 */
#define ANSEL_PIN_1	(1 << 24)	/**< セグメントBのビット。 */
#define ANSEL_PIN_2	(1 << 25)	/**< セグメントCのビット。 */
#define ANSEL_PIN_3	(1 << 26)	/**< セグメントDのビット。 */
#define ANSEL_PIN_4	(1 <<  6)	/**< セグメントEのビット。 */
#define ANSEL_PIN_5	(1 <<  7)	/**< セグメントFのビット。 */
#define ANSEL_PIN_6	(1 <<  8)	/**< セグメントGのビット。 */
#define ANSEL_PIN_7	(1 <<  9)	/**< セグメントDPのビット。 */

#define CASEL_PORT_A	LPC_GPIO2	/**< セグメントAのポート。 */
#define CASEL_PORT_B	LPC_GPIO2	/**< セグメントBのポート。 */
#define CASEL_PORT_C	LPC_GPIO2	/**< セグメントCのポート。 */
#define CASEL_PORT_D	LPC_GPIO2	/**< セグメントDのポート。 */
#define CASEL_PORT_E	LPC_GPIO2	/**< セグメントEのポート。 */
#define CASEL_PORT_F	LPC_GPIO2	/**< セグメントFのポート。 */
#define CASEL_PORT_G	LPC_GPIO2	/**< セグメントGのポート。 */
#define CASEL_PORT_P	LPC_GPIO2	/**< セグメントDPのポート。 */

#define CASEL_PIN_A	(1 <<  4)	/**< セグメントAのビット。 */
#define CASEL_PIN_B	(1 <<  5)	/**< セグメントBのビット。 */
#define CASEL_PIN_C	(1 <<  6)	/**< セグメントCのビット。 */
#define CASEL_PIN_D	(1 <<  7)	/**< セグメントDのビット。 */
#define CASEL_PIN_E	(1 <<  8)	/**< セグメントEのビット。 */
#define CASEL_PIN_F	(1 << 10)	/**< セグメントFのビット。 */
#define CASEL_PIN_G	(1 << 11)	/**< セグメントGのビット。 */
#define CASEL_PIN_P	(1 << 12)	/**< セグメントDPのビット。 */

static segsel_t lpcxclk_get_segsel(unsigned char c);

/**
 * @brief アノード選択をクリアする。
 */
static void ansel_clear(void) {
    ANSEL_PORT_0->FIOCLR = ANSEL_PIN_0;
    ANSEL_PORT_1->FIOCLR = ANSEL_PIN_1;
    ANSEL_PORT_2->FIOCLR = ANSEL_PIN_2;
    ANSEL_PORT_3->FIOCLR = ANSEL_PIN_3;
    ANSEL_PORT_4->FIOCLR = ANSEL_PIN_4;
    ANSEL_PORT_5->FIOCLR = ANSEL_PIN_5;
    ANSEL_PORT_6->FIOCLR = ANSEL_PIN_6;
    ANSEL_PORT_7->FIOCLR = ANSEL_PIN_7;
}

static void ansel_set(const int ansel) {
    switch (ansel) {
        case 0:
            ANSEL_PORT_0->FIOSET = ANSEL_PIN_0;
            break;
        case 1:
            ANSEL_PORT_1->FIOSET = ANSEL_PIN_1;
            break;
        case 2:
            ANSEL_PORT_2->FIOSET = ANSEL_PIN_2;
            break;
        case 3:
            ANSEL_PORT_3->FIOSET = ANSEL_PIN_3;
            break;
        case 4:
            ANSEL_PORT_4->FIOSET = ANSEL_PIN_4;
            break;
        case 5:
            ANSEL_PORT_5->FIOSET = ANSEL_PIN_5;
            break;
        case 6:
            ANSEL_PORT_6->FIOSET = ANSEL_PIN_6;
            break;
        case 7:
            ANSEL_PORT_7->FIOSET = ANSEL_PIN_7;
            break;
    }
}

/**
 * @brief カソード選択をクリアする。
 */
static void casel_clear(void) {
    CASEL_PORT_A->FIOCLR = CASEL_PIN_A;
    CASEL_PORT_B->FIOCLR = CASEL_PIN_B;
    CASEL_PORT_C->FIOCLR = CASEL_PIN_C;
    CASEL_PORT_D->FIOCLR = CASEL_PIN_D;
    CASEL_PORT_E->FIOCLR = CASEL_PIN_E;
    CASEL_PORT_F->FIOCLR = CASEL_PIN_F;
    CASEL_PORT_G->FIOCLR = CASEL_PIN_G;
    CASEL_PORT_P->FIOCLR = CASEL_PIN_P;

}

static void casel_set(const int casel) {
    if (casel & SEGSEL_A) {
        CASEL_PORT_A->FIOSET = CASEL_PIN_A;
    } else {
        CASEL_PORT_A->FIOCLR = CASEL_PIN_A;
    }
    if (casel & SEGSEL_B) {
        CASEL_PORT_B->FIOSET = CASEL_PIN_B;
    } else {
        CASEL_PORT_B->FIOCLR = CASEL_PIN_B;
    }
    if (casel & SEGSEL_C) {
        CASEL_PORT_C->FIOSET = CASEL_PIN_C;
    } else {
        CASEL_PORT_C->FIOCLR = CASEL_PIN_C;
    }
    if (casel & SEGSEL_D) {
        CASEL_PORT_D->FIOSET = CASEL_PIN_D;
    } else {
        CASEL_PORT_D->FIOCLR = CASEL_PIN_D;
    }
    if (casel & SEGSEL_E) {
        CASEL_PORT_E->FIOSET = CASEL_PIN_E;
    } else {
        CASEL_PORT_E->FIOCLR = CASEL_PIN_E;
    }
    if (casel & SEGSEL_F) {
        CASEL_PORT_F->FIOSET = CASEL_PIN_F;
    } else {
        CASEL_PORT_F->FIOCLR = CASEL_PIN_F;
    }
    if (casel & SEGSEL_G) {
        CASEL_PORT_G->FIOSET = CASEL_PIN_G;
    } else {
        CASEL_PORT_G->FIOCLR = CASEL_PIN_G;
    }
    if (casel & SEGSEL_P) {
        CASEL_PORT_P->FIOSET = CASEL_PIN_P;
    } else {
        CASEL_PORT_P->FIOCLR = CASEL_PIN_P;
    }
}

void lpcxclk_open(lpcxclk_t *p) {
    int i;

    /*
     *
     */
    ansel_clear();
    casel_clear();

    /*
     * ピンの入出力設定を実行する。
     */

    /* ANSEL0:P0[23] */
    LPC_PINCON->PINSEL1 &= (~(3 << 14));
    /* ANSEL1:P0[24] */
    LPC_PINCON->PINSEL1 &= (~(3 << 16));
    /* ANSEL2:P0[25] */
    LPC_PINCON->PINSEL1 &= (~(3 << 18));
    /* ANSEL3:P0[26] */
    LPC_PINCON->PINSEL1 &= (~(3 << 20));
    /* ANSEL4:P0[6] */
    LPC_PINCON->PINSEL0 &= (~(3 << 12));
    /* ANSEL5:P0[7] */
    LPC_PINCON->PINSEL0 &= (~(3 << 14));
    /* ANSEL6:P0[8] */
    LPC_PINCON->PINSEL0 &= (~(3 << 16));
    /* ANSEL7:P0[9] */
    LPC_PINCON->PINSEL0 &= (~(3 << 18));

    /* CASEL0:P2[4] */
    LPC_PINCON->PINSEL4 &= (~(3 << 8));
    /* CASEL1:P2[5] */
    LPC_PINCON->PINSEL4 &= (~(3 << 10));
    /* CASEL2:P2[6] */
    LPC_PINCON->PINSEL4 &= (~(3 << 12));
    /* CASEL3:P2[7] */
    LPC_PINCON->PINSEL4 &= (~(3 << 14));
    /* CASEL4:P2[8] */
    LPC_PINCON->PINSEL4 &= (~(3 << 16));
    /* CASEL5:P2[10] */
    LPC_PINCON->PINSEL4 &= (~(3 << 20));
    /* CASEL6:P2[11] */
    LPC_PINCON->PINSEL4 &= (~(3 << 22));
    /* CASEL7:P2[12] */
    LPC_PINCON->PINSEL4 &= (~(3 << 24));

    /*
     *
     */

    ANSEL_PORT_0->FIODIR |= (ANSEL_PIN_0);
    ANSEL_PORT_1->FIODIR |= (ANSEL_PIN_1);
    ANSEL_PORT_2->FIODIR |= (ANSEL_PIN_2);
    ANSEL_PORT_3->FIODIR |= (ANSEL_PIN_3);
    ANSEL_PORT_4->FIODIR |= (ANSEL_PIN_4);
    ANSEL_PORT_5->FIODIR |= (ANSEL_PIN_5);
    ANSEL_PORT_6->FIODIR |= (ANSEL_PIN_6);
    ANSEL_PORT_7->FIODIR |= (ANSEL_PIN_7);

    CASEL_PORT_A->FIODIR |= (CASEL_PIN_A);
    CASEL_PORT_B->FIODIR |= (CASEL_PIN_B);
    CASEL_PORT_C->FIODIR |= (CASEL_PIN_C);
    CASEL_PORT_D->FIODIR |= (CASEL_PIN_D);
    CASEL_PORT_E->FIODIR |= (CASEL_PIN_E);
    CASEL_PORT_F->FIODIR |= (CASEL_PIN_F);
    CASEL_PORT_G->FIODIR |= (CASEL_PIN_G);
    CASEL_PORT_P->FIODIR |= (CASEL_PIN_P);

    /*
     *
     */
    for (i = 0; i < 8; i++) {
        p->segsel[i] = 0;
        p->brightness[i] = 0xFF;
    }
}

void lpcxclk_7seg_write(
        lpcxclk_t *p,
        const unsigned char segsel0,
        const unsigned char segsel1,
        const unsigned char segsel2,
        const unsigned char segsel3,
        const unsigned char segsel4,
        const unsigned char segsel5,
        const segsel_t dotsel)
{
    /*
     * ここでは構造体のメンバに値を代入するだけ。
     */
    p->segsel[0] = lpcxclk_get_segsel(segsel0);
    p->segsel[1] = lpcxclk_get_segsel(segsel1);
    p->segsel[2] = lpcxclk_get_segsel(segsel2);
    p->segsel[3] = lpcxclk_get_segsel(segsel3);
    p->segsel[4] = lpcxclk_get_segsel(segsel4);
    p->segsel[5] = lpcxclk_get_segsel(segsel5);
    p->segsel[6] = dotsel;
    p->segsel[7] = 0;
}

void lpcxclk_7seg_brightness(lpcxclk_t *p, const int index,
        const unsigned char brightness) {
    /*
     * ここでは構造体のメンバに値を代入するだけ。
     */
    p->brightness[index] = brightness;
}

void lpcxclk_7seg_clock(lpcxclk_t *p) {
    /*
     * 現在のカラム選択番号に従って所定のパターンを出力する。
     *
     * カラム：LEDのアノードの選択に用いる。
     * セグメント：LEDのカソードの選択に用いる。
     */
    unsigned char colsel = p->colsel & 0x07;
    unsigned char segsel = p->segsel[colsel];

    if (colsel == 0) {
        /*
         * 輝度は８段階で設定可能とする。
         */
        p->counter = (p->counter + 1) & 0x07;
    }

    /*
     * アノードの選択状態をクリアする。
     * この時点で全ての表示が消去される。
     */
    ansel_clear();

    /*
     * セグメントのパターンに従ってピン状態を設定する。
     */
    casel_set(segsel);

    /*
     * 表示対象カラムに相当するアノードピンを活性化する。
     */
    if (p->counter <= p->brightness[colsel]) {
        ansel_set(colsel);
    }

    /*
     * 次の選択に備える。
     */
    p->colsel = (p->colsel + 1) & 0x07;
}

void lpcxclk_close(lpcxclk_t *p) {
}

static segsel_t lpcxclk_get_segsel(unsigned char c)
{
    static const segsel_t table[] = {
        /*0*/(SEGSEL_A | SEGSEL_B | SEGSEL_C | SEGSEL_D | SEGSEL_E | SEGSEL_F),
        /*1*/(SEGSEL_B | SEGSEL_C),
        /*2*/(SEGSEL_A | SEGSEL_B | SEGSEL_G | SEGSEL_E | SEGSEL_D),
        /*3*/(SEGSEL_A | SEGSEL_B | SEGSEL_G | SEGSEL_C | SEGSEL_D),
        /*4*/(SEGSEL_F | SEGSEL_G | SEGSEL_B | SEGSEL_C),
        /*5*/(SEGSEL_A | SEGSEL_F | SEGSEL_G | SEGSEL_C | SEGSEL_D),
        /*6*/(SEGSEL_A | SEGSEL_F | SEGSEL_E | SEGSEL_D | SEGSEL_C | SEGSEL_G),
        /*7*/(SEGSEL_A | SEGSEL_B | SEGSEL_C),
        /*8*/(SEGSEL_A | SEGSEL_B | SEGSEL_C | SEGSEL_D | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*9*/(SEGSEL_A | SEGSEL_B | SEGSEL_C | SEGSEL_D | SEGSEL_F | SEGSEL_G),
        /*:*/(SEGSEL_G),
        /*;*/(SEGSEL_G),
        /*<*/(SEGSEL_G),
        /*=*/(SEGSEL_A | SEGSEL_D),
        /*>*/(SEGSEL_G),
        /*?*/(SEGSEL_G),
        /*@*/(SEGSEL_G),
        /*A*/(SEGSEL_A | SEGSEL_B | SEGSEL_C | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*b*/(SEGSEL_C | SEGSEL_D | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*C*/(SEGSEL_A | SEGSEL_D | SEGSEL_E | SEGSEL_F),
        /*d*/(SEGSEL_B | SEGSEL_C | SEGSEL_D | SEGSEL_E | SEGSEL_G),
        /*E*/(SEGSEL_A | SEGSEL_D | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*F*/(SEGSEL_A | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*G*/(SEGSEL_A | SEGSEL_C | SEGSEL_D | SEGSEL_E | SEGSEL_F),
        /*H*/(SEGSEL_B | SEGSEL_C | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*I*/(SEGSEL_E | SEGSEL_F),
        /*J*/(SEGSEL_B | SEGSEL_C | SEGSEL_D | SEGSEL_E),
        /*k*/(SEGSEL_D | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*L*/(SEGSEL_D | SEGSEL_E | SEGSEL_F),
        /*m*/(SEGSEL_A | SEGSEL_C | SEGSEL_E),
        /*n*/(SEGSEL_C | SEGSEL_E | SEGSEL_G),
        /*O*/(SEGSEL_A | SEGSEL_B | SEGSEL_C | SEGSEL_D | SEGSEL_E | SEGSEL_F),
        /*P*/(SEGSEL_A | SEGSEL_B | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*q*/(SEGSEL_A | SEGSEL_B | SEGSEL_C | SEGSEL_F | SEGSEL_G),
        /*r*/(SEGSEL_E | SEGSEL_G),
        /*S*/(SEGSEL_A | SEGSEL_C | SEGSEL_D | SEGSEL_F | SEGSEL_G),
        /*t*/(SEGSEL_D | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*U*/(SEGSEL_B | SEGSEL_C | SEGSEL_D | SEGSEL_E | SEGSEL_F),
        /*V*/(SEGSEL_B | SEGSEL_D | SEGSEL_F),
        /*W*/(SEGSEL_B | SEGSEL_D | SEGSEL_F | SEGSEL_G),
        /*X*/(SEGSEL_A | SEGSEL_D | SEGSEL_G),
        /*y*/(SEGSEL_B | SEGSEL_C | SEGSEL_D | SEGSEL_F | SEGSEL_G),
        /*z*/(SEGSEL_B | SEGSEL_E | SEGSEL_G),
        /*[*/(SEGSEL_A | SEGSEL_D | SEGSEL_E | SEGSEL_F),
        /*\\*/(SEGSEL_G),
        /*]*/(SEGSEL_A | SEGSEL_B | SEGSEL_C | SEGSEL_D),
        /*^*/(SEGSEL_G),
        /*_*/(SEGSEL_D),
        /*'*/(SEGSEL_G),
        /*A*/(SEGSEL_A | SEGSEL_B | SEGSEL_C | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*b*/(SEGSEL_C | SEGSEL_D | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*C*/(SEGSEL_A | SEGSEL_D | SEGSEL_E | SEGSEL_F),
        /*d*/(SEGSEL_B | SEGSEL_C | SEGSEL_D | SEGSEL_E | SEGSEL_G),
        /*E*/(SEGSEL_A | SEGSEL_D | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*F*/(SEGSEL_A | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*G*/(SEGSEL_A | SEGSEL_C | SEGSEL_D | SEGSEL_E | SEGSEL_F),
        /*H*/(SEGSEL_B | SEGSEL_C | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*I*/(SEGSEL_E | SEGSEL_F),
        /*J*/(SEGSEL_B | SEGSEL_C | SEGSEL_D | SEGSEL_E),
        /*k*/(SEGSEL_D | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*L*/(SEGSEL_D | SEGSEL_E | SEGSEL_F),
        /*m*/(SEGSEL_A | SEGSEL_C | SEGSEL_E),
        /*n*/(SEGSEL_C | SEGSEL_E | SEGSEL_G),
        /*O*/(SEGSEL_A | SEGSEL_B | SEGSEL_C | SEGSEL_D | SEGSEL_E | SEGSEL_F),
        /*P*/(SEGSEL_A | SEGSEL_B | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*q*/(SEGSEL_A | SEGSEL_B | SEGSEL_C | SEGSEL_F | SEGSEL_G),
        /*r*/(SEGSEL_E | SEGSEL_G),
        /*S*/(SEGSEL_A | SEGSEL_C | SEGSEL_D | SEGSEL_F | SEGSEL_G),
        /*t*/(SEGSEL_D | SEGSEL_E | SEGSEL_F | SEGSEL_G),
        /*U*/(SEGSEL_B | SEGSEL_C | SEGSEL_D | SEGSEL_E | SEGSEL_F),
        /*V*/(SEGSEL_B | SEGSEL_D | SEGSEL_F),
        /*W*/(SEGSEL_B | SEGSEL_D | SEGSEL_F | SEGSEL_G),
        /*X*/(SEGSEL_A | SEGSEL_D | SEGSEL_G),
        /*y*/(SEGSEL_B | SEGSEL_C | SEGSEL_D | SEGSEL_F | SEGSEL_G),
        /*z*/(SEGSEL_B | SEGSEL_E | SEGSEL_G),
        /*{*/(SEGSEL_A | SEGSEL_D | SEGSEL_E | SEGSEL_F),
        /*|*/(SEGSEL_E | SEGSEL_F),
        /*}*/(SEGSEL_A | SEGSEL_B | SEGSEL_C | SEGSEL_D),
        /*~*/(SEGSEL_A),
        /* */0
    };
    int codeofs = 0x30;
    if (c < codeofs) {
        return 0;
    }
    return table[c - codeofs];
}

