/*
 * TechToys TY320T interface code
 *
 * The LCD module is used with 16bit mode
 *
 * /WR    P4[29]
 * /RD    P4[28]
 * /CS    P4[30]
 * RS     P4[31]
 * EN     P2[1] (PWM1[1])
 * D15..D0   P4[15]..P4[0]
 *
 * CLE    P3[0]
 * ALE    P3[1]
 * R/B    P3[2]
 * NANDCE P3[3]
 */
#include <t_services.h>
#include "lpc23xx.h"
#include "ili9320.h"
#include "lcd.h"
#include "kernel_id.h"

typedef struct {
    UH regnum;
    UH regval;
} gammadef;

const gammadef gamma1[10] = {
    {0x0030, 0x0000},
    {0x0031, 0x0505}, 
    {0x0032, 0x0004},
    {0x0035, 0x0006},
    {0x0036, 0x0707},
    {0x0037, 0x0105},
    {0x0038, 0x0002},
    {0x0039, 0x0707},
    {0x003c, 0x0704},
    {0x003d, 0x0807},
};

const gammadef gamma2[10] = {
    {0x0030, 0x0504},
    {0x0031, 0x0703}, 
    {0x0032, 0x0702},
    {0x0035, 0x0101},
    {0x0036, 0x0a1f},
    {0x0037, 0x0504},
    {0x0038, 0x0003},
    {0x0039, 0x0706},
    {0x003c, 0x0707},
    {0x003d, 0x091f},
};

const gammadef gamma3[10] = {
    {0x0030, 0x0207},
    {0x0031, 0x0505}, 
    {0x0032, 0x0102},
    {0x0035, 0x0006},
    {0x0036, 0x0606},
    {0x0037, 0x0707},
    {0x0038, 0x0506},
    {0x0039, 0x0407},
    {0x003c, 0x0106},
    {0x003d, 0x0601},
};

const gammadef gamma4[10] = {
    {0x0030, 0x0000},
    {0x0031, 0x0207}, 
    {0x0032, 0x0707},
    {0x0035, 0x0203},
    {0x0036, 0x0609},
    {0x0037, 0x0707},
    {0x0038, 0x0400},
    {0x0039, 0x0707},
    {0x003c, 0x0500},
    {0x003d, 0x1807},
};

const gammadef gamma5[10] = {
    {0x0030, 0x0307},
    {0x0031, 0x0507}, 
    {0x0032, 0x0500},
    {0x0035, 0x0304},
    {0x0036, 0x0007},
    {0x0037, 0x0707},
    {0x0038, 0x0007},
    {0x0039, 0x0007},
    {0x003c, 0x0600},
    {0x003d, 0x0700},
};

const gammadef gamma6[10] = {
    {0x0030, 0x0007},
    {0x0031, 0x0403}, 
    {0x0032, 0x0400},
    {0x0035, 0x0003},
    {0x0036, 0x0007},
    {0x0037, 0x0606},
    {0x0038, 0x0106},
    {0x0039, 0x0007},
    {0x003c, 0x0700},
    {0x003d, 0x0700},
};

gammadef gamma7[10] = {
    {0x0030, 0x0007},
    {0x0031, 0x0203}, 
    {0x0032, 0x0001},
    {0x0035, 0x0007},
    {0x0036, 0x0407},
    {0x0037, 0x0607},
    {0x0038, 0x0106},
    {0x0039, 0x0007},
    {0x003c, 0x0007},
    {0x003d, 0x001e},
};

const gammadef gamma8[10] = {
    {0x0030, 0x0000},
    {0x0031, 0x0007}, 
    {0x0032, 0x0103},
    {0x0035, 0x0407},
    {0x0036, 0x090f},
    {0x0037, 0x0404},
    {0x0038, 0x0400},
    {0x0039, 0x0404},
    {0x003c, 0x0000},
    {0x003d, 0x0400},
};

const gammadef gamma9[10] = {
    {0x0030, 0x0001},
    {0x0031, 0x0606}, 
    {0x0032, 0x0304},
    {0x0035, 0x0103},
    {0x0036, 0x011d},
    {0x0037, 0x0404},
    {0x0038, 0x0404},
    {0x0039, 0x0404},
    {0x003c, 0x0700},
    {0x003d, 0x0a1f},
};

const gammadef *gammatab[] = {
    gamma1, gamma2, gamma3, gamma4, gamma5,
    gamma6, gamma7, gamma8, gamma9,
};

inline void lcd_write_command(UB cmd)
{
    /* /WR = 0 */
    LCD_DATA = cmd | PIO_nRD;
asm("nop");
    /* /WR = 1 */
    LCD_DATA = cmd | PIO_nWR | PIO_nRD;
}

inline void lcd_write_data(UH val)
{
    /* /WR = 0 */
    LCD_DATA = val | PIO_nRD | PIO_RS;
asm("nop");
    /* /WR = 1 */
    LCD_DATA = val | PIO_nWR | PIO_nRD | PIO_RS;
}

void nandWriteCmd(int cmd)
{
    cmd <<= 8;
    FIO3SET = PIO_CLE;
    LCD_DATA = cmd | PIO_nRD | PIO_RS | PIO_nCS;	/* /WR = 0 */
asm("nop");
    LCD_DATA = cmd | PIO_nRD | PIO_RS | PIO_nCS | PIO_nWR;	/* /WR = 1 */
asm("nop");
    FIO3CLR = PIO_CLE;
}

void nandWriteAddr(int addr)
{
    FIO3SET = PIO_ALE;
    addr <<= 8;
    LCD_DATA = addr | PIO_nRD | PIO_RS | PIO_nCS; /* /RW = 0 */
asm("nop");
    LCD_DATA = addr | PIO_nRD | PIO_RS | PIO_nCS | PIO_nWR; /* /RW = 1 */
    FIO3CLR = PIO_ALE;
}

void nandWriteData(int datab)
{
    datab <<= 8;
    LCD_DATA = datab | PIO_nRD | PIO_RS | PIO_nCS; /* /RW = 0 */
asm("nop");
    LCD_DATA = datab | PIO_nRD | PIO_RS | PIO_nCS | PIO_nWR; /* /RW = 1 */
}


UH nandRead()
{
    UH data;

    FIO4DIR  = CTRL_MASK;
    FIO4CLR = PIO_nRD;
asm("nop");
asm("nop");

    data = FIO4PIN & 0xffff;

    FIO4SET = PIO_nRD;
    FIO4DIR  = CTRL_MASK | 0xffff;
    return data >> 8;
}

void lcd_set_register(UB index, UH val)
{
    lcd_write_command(index);
    lcd_write_data(val);
}

void lcd_init(int gamma)
{
    int i;
    const gammadef *gp;

    FIO4MASK = ~(CTRL_MASK | 0xffff);
    FIO4DIR  = CTRL_MASK | 0xffff;
    FIO3MASK = ~(PIO_CLE|PIO_ALE|PIO_RDYBUSY|PIO_NANDCE);
    FIO3DIR  = (PIO_CLE|PIO_ALE|PIO_NANDCE);

    FIO3SET = PIO_NANDCE;
    FIO3CLR = PIO_CLE | PIO_ALE;

    LCD_DATA = PIO_nWR | PIO_nRD | PIO_RS | PIO_nCS;
    Clear_CS();

    lcd_set_register(0xe5, 0x8000); /* Set the internal vcore voltage */
    lcd_set_register(START_OSCILATION, BIT_OSC); /* Start internal OSC */

    dly_tsk(10);

    lcd_set_register(0xa4, 0x0001);	/* Calb */
    lcd_set_register(DISPLAY_CTRL1, 0x0000);	/* display control (1) */

    dly_tsk(10);

    /* Display setting */
    lcd_set_register(DRIVER_OUTPUT_CTRL, BIT_SS);
    lcd_set_register(LCD_DRIVING_CTRL, 0x0700);
    lcd_set_register(ENTRY_MODE, 0x1030);	/* RGB, I/D[1:0] = 11 */
    lcd_set_register(RESIZE_CTRL, 0x0000);
    lcd_set_register(DISPLAY_CTRL2, 0x0202);
    lcd_set_register(DISPLAY_CTRL3, 0x0000);

    lcd_set_register(DISPLAY_CTRL1, 0x0101);
#ifdef notdef
    lcd_set_register(0x17, 0x0001);
#endif

    /* Power On sequence */
    lcd_set_register(POWER_CONTROL1, 0x0000);
    lcd_set_register(POWER_CONTROL2, 0x0007);
    lcd_set_register(POWER_CONTROL3, 0x0000);
    lcd_set_register(POWER_CONTROL4, 0x0000);

    dly_tsk(20);

    lcd_set_register(POWER_CONTROL1, 0x16b0);
    lcd_set_register(POWER_CONTROL2, 0x0037);
    dly_tsk(50);
    lcd_set_register(POWER_CONTROL3, 0x013e);
    dly_tsk(50);
    lcd_set_register(POWER_CONTROL4, 0x1a00);
    lcd_set_register(POWER_CONTROL7, 0x000f);
    dly_tsk(50);
    lcd_set_register(H_GRAM_ADDR_SET, 0x0000);
    lcd_set_register(V_GRAM_ADDR_SET, 0x0000);

    lcd_set_register(H_ADDR_START_POSITION, 0x0000);
    lcd_set_register(H_ADDR_END_POSITION, 0x00ef);
    lcd_set_register(V_ADDR_START_POSITION, 0x0000);
    lcd_set_register(V_ADDR_END_POSITION, 0x013f);

    lcd_set_register(DRIVER_OUTPUT_CTRL2, 0x2700);
    lcd_set_register(BASE_IMAGE_DISPLAY_CTRL, 0x0001);
    lcd_set_register(V_SCROLL_CTRL, 0x0000);

    lcd_set_register(PANEL_IF_CTRL1, 0x0010);
    lcd_set_register(PANEL_IF_CTRL2, 0x0000);
    lcd_set_register(PANEL_IF_CTRL3, 0x0000);

    /* GAMMA Control */
#ifdef OLDCODE
    lcd_set_register(0x30, 0x0007);
    lcd_set_register(0x31, 0x0403);
    lcd_set_register(0x32, 0x0404);
    lcd_set_register(0x35, 0x0002);
    lcd_set_register(0x36, 0x0707);
    lcd_set_register(0x37, 0x0606);
    lcd_set_register(0x38, 0x0106);
    lcd_set_register(0x39, 0x0007);
    lcd_set_register(0x3c, 0x0700);
    lcd_set_register(0x3d, 0x0707);
#else
    gp = gammatab[gamma];
    for (i = 0; i < 10; i++)
        lcd_set_register(gp->regnum, gp->regval);
#endif

    lcd_set_register(0x07, 0x0173);

    Set_CS();
    dly_tsk(30);
}

void lcd_set_address(UH haddr, UH vaddr)
{
    lcd_set_register(H_GRAM_ADDR_SET, haddr);
    lcd_set_register(V_GRAM_ADDR_SET, vaddr);
}

void lcd_clear_screen(UH color)
{
    int i;

#ifdef XX
    wai_sem(SEM_LCD);
#endif
    Clear_CS();
    lcd_set_address(0, 0);

    lcd_write_command(WRITE_DATA_TO_GRAM);
    for (i = 0; i < LCD_WIDTH * LCD_HEIGHT; i++)
        lcd_write_data(color);
    Set_CS();
#ifdef XX
    sig_sem(SEM_LCD);
#endif
}

void lcd_fill_root(int xpos, int ypos, int width, int height, const UH *pimage)
{
    const UH *pbase, *pline;
    int x, y;

    Clear_CS();
    lcd_set_address(xpos, ypos);
    lcd_set_register(H_ADDR_START_POSITION, xpos);
    lcd_set_register(V_ADDR_START_POSITION, ypos);
    lcd_set_register(H_ADDR_END_POSITION, xpos + width - 1);
    lcd_set_register(V_ADDR_END_POSITION, ypos + height - 1);

    lcd_write_command(WRITE_DATA_TO_GRAM);
    pbase = pimage + ypos * LCD_WIDTH + xpos;
    for (y = 0; y < height; y++) {
        pline = pbase;
        for (x = 0; x < width; x++)
            lcd_write_data(*pline++);
        pbase += LCD_WIDTH;
    }
    Set_CS();
}

/*
 * XXX: If width is less than four, we can't use windowing function.
 */
void lcd_fill_rect(int xpos, int ypos, int width, int height, int color)
{
    int i;

    Clear_CS();
    lcd_set_address(xpos, ypos);
    lcd_set_register(H_ADDR_START_POSITION, xpos);
    lcd_set_register(V_ADDR_START_POSITION, ypos);
    lcd_set_register(H_ADDR_END_POSITION, xpos + width - 1);
    lcd_set_register(V_ADDR_END_POSITION, ypos + height - 1);

    lcd_write_command(WRITE_DATA_TO_GRAM);
    for (i = width * height; i > 0; i--) {
        lcd_write_data(color);
    }
    Set_CS();
}

void
ul_lcd_plot(int x1, int y1, int line_type, int color)
{
    if (line_type == LINE_THICK) {
        lcd_set_address(x1, y1);
        lcd_write_command(WRITE_DATA_TO_GRAM);
        lcd_write_data(color);
        lcd_write_data(color);
        lcd_set_address(x1, y1+1);
        lcd_write_command(WRITE_DATA_TO_GRAM);
        lcd_write_data(color);
        lcd_write_data(color);
    } else {
        lcd_set_address(x1, y1);
        lcd_write_command(WRITE_DATA_TO_GRAM);
        lcd_write_data(color);
    }
}

void lcd_load_icon(int xpos, int ypos, const struct icon *icon)
{
    int pix;
    UH alpha, beta;
    UB red, green, blue;
    UH nred, ngreen, nblue;
    const UH *psimage;

    wai_sem(SEM_LCD);
    Clear_CS();
#ifdef DEBUG
syslog(LOG_NOTICE, "load_icon: (%d, %d), %d", xpos, ypos, icon->bwidth);
#endif
    lcd_set_address(xpos, ypos);
    lcd_set_register(H_ADDR_START_POSITION, xpos);
    lcd_set_register(V_ADDR_START_POSITION, ypos);
    lcd_set_register(H_ADDR_END_POSITION, xpos + icon->width - 1);
    lcd_set_register(V_ADDR_END_POSITION, ypos + icon->height -1);
    pix = icon->width * icon->height;
#ifdef DEBUG
syslog(LOG_NOTICE, "%d X %d --> %d", icon->width, icon->height, pix);
#endif
    psimage = icon->bitmap;
    if (icon->bwidth == 24) {
        lcd_set_register(ENTRY_MODE, 0x9030);	/* TRI, RGB, I/D[1:0] = 11 */
        lcd_write_command(WRITE_DATA_TO_GRAM);
        while (pix-- > 0) {
            lcd_write_data(*psimage++);
            lcd_write_data(*psimage++);
        }
        lcd_set_register(ENTRY_MODE, 0x1030);	/* RGB, I/D[1:0] = 11 */
    } else {
        lcd_set_register(ENTRY_MODE, 0x9030);   /* TRI, RGB, I/D[1:0] = 11 */
        lcd_write_command(WRITE_DATA_TO_GRAM);
        while (pix-- > 0) {
            alpha = (*psimage >> 8);
            blue = *psimage & 0xff;
            psimage++;
            green = *psimage >> 8;
            red = *psimage & 0xff;
            psimage++;
            switch (alpha) {
            case 0:
                nred = red >> 2;
                ngreen = green >> 2;
                nblue = blue >> 2;
                break;
            case 255:
                nred = ngreen = nblue = 0x3f;
                break;
            default:
                beta = 256 - alpha;
                nred = (red * beta + C_WHITE * alpha);
                ngreen = (green * beta + C_WHITE * alpha);
                nblue = (blue * beta + C_WHITE * alpha);
                nred >>= 10;
                ngreen >>= 10;
                nblue >>= 10;
                break;
            }
            lcd_write_data((nred << 10) | (ngreen << 4) | (nblue >> 2));
            lcd_write_data((nblue & 0x03) << 14);
        }
        lcd_set_register(ENTRY_MODE, 0x1030);   /* RGB, I/D[1:0] = 11 */
    }
    Set_CS();
    sig_sem(SEM_LCD);
}

void lcd_reset_window(void)
{
#ifdef XX
    wai_sem(SEM_LCD);
#endif
    Clear_CS();
    lcd_set_register(H_ADDR_START_POSITION, 0);
    lcd_set_register(V_ADDR_START_POSITION, 0);
    lcd_set_register(H_ADDR_END_POSITION, LCD_WIDTH-1);
    lcd_set_register(V_ADDR_END_POSITION, LCD_HEIGHT-1);
    Set_CS();
#ifdef XX
    sig_sem(SEM_LCD);
#endif
}

void lcd_hline(int xpos, int color)
{
    Clear_CS();
    lcd_set_register(H_ADDR_START_POSITION, 0);
    lcd_set_register(V_ADDR_START_POSITION, LCD_WIDTH-1);
    lcd_set_register(H_ADDR_END_POSITION, 0);
    lcd_set_register(V_ADDR_END_POSITION, LCD_HEIGHT-1);
    Set_CS();
}

void lcd_line(int x1, int y1, int x2, int y2, int type, int color)
{
    int x = x1;
    int y = y1;
    int d = 0;
    int hx = x2 - x1;
    int hy = y2 - y1;
    int xinc, yinc;

    Clear_CS();

    xinc = yinc = 1;
    if (hx < 0) {
        xinc = -1;
        hx = -hx;
    }
    if (hy < 0) {
        yinc = -1;
        hy = -hy;
    }
    if (hy <= hx) {
        int c = hx * 2;
        int m = hy * 2;
        while (x != x2) {
            ul_lcd_plot(x, y, type, color);
            x += xinc;
            d += m;
            if (d > hx) {
                y += yinc;
                d -= c;
            }
        }
    } else {
        int c = hy * 2;
        int m = hx * 2;
        while (y != y2) {
            ul_lcd_plot(x, y, type, color);
            y += yinc;
            d += m;
            if (d > hy) {
                x += xinc;
                d -= c;
            }
        }
    }
    ul_lcd_plot(x, y, type, color);

    Set_CS();
}

void lcd_string(char *string, const UB *font, int x, int y, int fgcolor, int bgcolor)
{
    UINT i, j;
    int mask, z, xme, yme, offset, color;
    const UB *data;
    UH *bp;

    data = font;

    xme = *data++;
    yme = *data++;
    offset = *data;

    Clear_CS();
    while (*string >= ' ') {
        data = (font + offset) + (offset * (*string - ' '));

        lcd_set_address(x, y);
        lcd_set_register(H_ADDR_START_POSITION, x);
        lcd_set_register(V_ADDR_START_POSITION, y);
        lcd_set_register(H_ADDR_END_POSITION, x+xme-1);
        lcd_set_register(V_ADDR_END_POSITION, y+yme-1);

        lcd_write_command(WRITE_DATA_TO_GRAM);
        for (i = 0; i < yme; i++) {
            mask = 0x80;
            for (j = x; j < (x+xme); j++) {
                color = (*data & mask)? fgcolor : bgcolor;
                lcd_write_data(color);
                mask >>= 1;
            }
            data++;
        }
        x += xme;
        string++;
    }
    Set_CS();
}

void lcd_command(int argc, char **argv)
{
    int val, i;
    SYSTIM time1, time2;

    if (argc > 1) {
        val = atoi(argv[1]);
        if (val < 9) {
            lcd_init(val);
            return;
        }
        lcd_reset_window();
        get_tim(&time1);
        for (i = 0; i < 10; i++) lcd_clear_screen(val);
        get_tim(&time2);
        cons_printf("%dmsec\n", time2 - time1);
    } else {
        lcd_init(0);
    }
}

void pwm_command(int argc, char **argv)
{
    int i, pwm;

    if (argc > 1) {
        pwm = atoi(argv[1]);
        set_pwm_duty(pwm);
        return;
    }
    for (i = 0; i < 2; i++) {
        for (pwm = 0; pwm < 1023; pwm++) {
            set_pwm_duty(pwm);
            dly_tsk(1);
        }
        while (pwm-- > 0) {
            set_pwm_duty(pwm);
            dly_tsk(1);
        }
    }
    set_pwm_duty(512);
}

#ifdef DEBUG_COMMAND
void scroll_command(int argc, char **argv)
{
    int i, j;

    Clear_CS();
    lcd_set_register(BASE_IMAGE_DISPLAY_CTRL, 0x0003);
    for (i = 0; i < 2; i++) {
        for (j = 0; j < LCD_HEIGHT; j++) {
            lcd_set_register(V_SCROLL_CTRL, j);
            dly_tsk(2);
        }
    }
    for (i = 0; i < 2; i++) {
        for (j = LCD_HEIGHT; j >= 0; j--) {
            lcd_set_register(V_SCROLL_CTRL, j);
            dly_tsk(2);
        }
    }
    lcd_set_register(V_SCROLL_CTRL, 0x0000);
    lcd_set_register(BASE_IMAGE_DISPLAY_CTRL, 0x0001);
    Set_CS();
}
#else
void scroll_command(int argc, char **argv)
{
}
#endif

#ifdef DEBUG_COMMAND
void gamma_command(int argc, char **argv)
{
    int val = 1;
    int i;
    gammadef *gp;

    if (argc > 1)
        val = atoi(argv[1]);
    switch (val) {
    case 1: gp = gamma1;
        break;
    case 2: gp = gamma2;
        break;
    case 3: gp = gamma3;
        break;
    case 4: gp = gamma4;
        break;
    case 5: gp = gamma5;
        break;
    case 6: gp = gamma6;
        break;
    case 7: gp = gamma7;
        break;
    case 8: gp = gamma8;
        break;
    case 9: gp = gamma9;
        break;
    default: gp = gamma1;
        break;
    }
cons_printf("Use gamma%d (%x)\n", val, gp);
    Clear_CS();
    lcd_set_register(DISPLAY_CTRL1, 0x0101);
    for (i = 0; i < 10; i++)
        lcd_set_register(gp->regnum, gp->regval);
    lcd_set_register(0x07, 0x0173);
    Set_CS();
}
#else
void gamma_command(int argc, char **argv)
{
}
#endif

void draw_lcd_line(UB *bp, int xoffset, int ypos, int width)
{
    int i;
    UB rval, gval, bval;

    ypos--;
    wai_sem(SEM_LCD);
    Clear_CS();
    lcd_set_address(xoffset, ypos);
    lcd_set_register(H_ADDR_START_POSITION, xoffset);
    lcd_set_register(V_ADDR_START_POSITION, ypos);
    lcd_set_register(H_ADDR_END_POSITION, xoffset + width-1);
    lcd_set_register(V_ADDR_END_POSITION, ypos);
    lcd_write_command(WRITE_DATA_TO_GRAM);
    for (i = width; i > 0; i--) {
        rval = *bp++;
        gval = *bp++;
        bval = *bp++;
        lcd_write_data(((rval >> 3) << 11) |
                ((gval >> 2) << 5) | (bval >> 3));
    }
    Set_CS();
    sig_sem(SEM_LCD);
}

void reset_entry_mode()
{
    Clear_CS();
    lcd_set_register(ENTRY_MODE, 0x1030);	/* RGB, I/D[1:0] = 11 */
    Set_CS();
}
