/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id: lcd-h100-remote.c,v 1.1 2005/04/14 11:40:40 linus Exp $
 *
 * Copyright (C) 2005 by Richard S. La Charité III
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#if CONFIG_CPU == MCF5249

#include "config.h"
#include "cpu.h"
#include "lcd-remote.h"
#include "kernel.h"
#include "thread.h"
#include <string.h>
#include <stdlib.h>
#include "file.h"
#include "debug.h"
#include "system.h"
#include "font.h"


unsigned char lcd_remote_framebuffer[LCD_REMOTE_HEIGHT/8][LCD_REMOTE_WIDTH];

#define CS_LO       GPIO1_OUT &= ~0x00000004
#define CS_HI       GPIO1_OUT |= 0x00000004
#define CLK_LO      GPIO_OUT &= ~0x10000000
#define CLK_HI      GPIO_OUT |= 0x10000000
#define DATA_LO     GPIO1_OUT &= ~0x00040000
#define DATA_HI     GPIO1_OUT |= 0x00040000
#define RS_LO       GPIO_OUT &= ~0x00010000
#define RS_HI       GPIO_OUT |= 0x00010000

/* delay loop */
#define DELAY   do { int _x; for(_x=0;_x<3;_x++);} while (0)

void lcd_remote_backlight_on(void)
{
    GPIO_OUT &= ~0x00000800;
}

void lcd_remote_backlight_off(void)
{
    GPIO_OUT |= 0x00000800;
}

void lcd_remote_write_command(int cmd)
{
    int i;
    
    CS_LO;
    RS_LO;
    
    for (i = 0; i < 8; i++)
    {
        if (cmd & 0x80)
            DATA_HI;
        else
            DATA_LO;
        
        CLK_HI;
        cmd <<= 1;
        DELAY;
        
        CLK_LO;
    }
    
    CS_HI;
}

void lcd_remote_write_data(const unsigned char* p_bytes, int count)
{
    int i, j;
    int data;
    
    CS_LO;
    RS_HI;
    
    for (i = 0; i < count; i++)
    {
        data = p_bytes[i];
        
        for (j = 0; j < 8; j++)
        {
            if (data & 0x80)
                DATA_HI;
            else
                DATA_LO;
            
            CLK_HI;
            data <<= 1;
            DELAY;
            
            CLK_LO;
        }
    }
    
    CS_HI;
}

void lcd_remote_write_command_ex(int cmd, int data)
{
    int i;
    
    CS_LO;
    RS_LO;
    
    for (i = 0; i < 8; i++)
    {
        if (cmd & 0x80)
            DATA_HI;
        else
            DATA_LO;
        
        CLK_HI;
        cmd <<= 1;
        DELAY;
        
        CLK_LO;
    }
    
    for (i = 0; i < 8; i++)
    {
        if (data & 0x80)
            DATA_HI;
        else
            DATA_LO;
        
        CLK_HI;
        data <<= 1;
        DELAY;
        
        CLK_LO;
    }
    
    CS_HI;
}

#define LCD_REMOTE_CNTL_ADC_NORMAL          0xa0
#define LCD_REMOTE_CNTL_ADC_REVERSE         0xa1
#define LCD_REMOTE_CNTL_SHL_NORMAL          0xc0
#define LCD_REMOTE_CNTL_SHL_REVERSE         0xc8
#define LCD_REMOTE_CNTL_DISPLAY_ON_OFF      0xae
#define LCD_REMOTE_CNTL_ENTIRE_ON_OFF       0xa4
#define LCD_REMOTE_CNTL_REVERSE_ON_OFF      0xa6
#define LCD_REMOTE_CTNL_NOP                 0xe3
#define LCD_REMOTE_CNTL_POWER_CONTROL       0x2b
#define LCD_REMOTE_CNTL_SELECT_REGULATOR    0x20
#define LCD_REMOTE_CNTL_SELECT_BIAS         0xa2
#define LCD_REMOTE_CNTL_SELECT_VOLTAGE      0x81
#define LCD_REMOTE_CNTL_INIT_LINE           0x40
#define LCD_REMOTE_CNTL_SET_PAGE_ADDRESS    0xB0

void lcd_remote_powersave(bool on)
{
    lcd_remote_write_command(LCD_REMOTE_CNTL_DISPLAY_ON_OFF | (on ? 0 : 1));
    lcd_remote_write_command(LCD_REMOTE_CNTL_ENTIRE_ON_OFF | (on ? 1 : 0));
}

void lcd_remote_set_contrast(int val)
{
    lcd_remote_write_command_ex(LCD_REMOTE_CNTL_SELECT_VOLTAGE, val);
}

void lcd_remote_set_invert_display(bool yesno)
{
    lcd_remote_write_command(LCD_REMOTE_CNTL_REVERSE_ON_OFF | yesno);
}

void lcd_remote_bitmap(const unsigned char *src, int x, int y, int nx, int ny, bool clear) __attribute__ ((section (".icode")));
void lcd_remote_bitmap(const unsigned char *src, int x, int y, int nx, int ny, bool clear)
{
    const unsigned char *src_col;
    unsigned char *dst, *dst_col;
    unsigned int data, mask1, mask2, mask3, mask4;
    int stride, shift;
    
    if (((unsigned) x >= LCD_REMOTE_WIDTH) || ((unsigned) y >= LCD_REMOTE_HEIGHT))
    {
        return;
    }
    
    stride = nx;    /* otherwise right-clipping will destroy the image */

    if (((unsigned) (x + nx)) >= LCD_REMOTE_WIDTH)
    {
        nx = LCD_REMOTE_WIDTH - x;
    }
    
    if (((unsigned) (y + ny)) >= LCD_REMOTE_HEIGHT)
    {
        ny = LCD_REMOTE_HEIGHT - y;
    }
        
    dst = &lcd_remote_framebuffer[y >> 3][x];
    shift = y & 7;
    
    if (!shift && clear)  /* shortcut for byte aligned match with clear */
    {
        while (ny >= 8)   /* all full rows */
        {
            memcpy(dst, src, nx);
            src += stride;
            dst += LCD_REMOTE_WIDTH;
            ny -= 8;
        }
        if (ny == 0)     /* nothing left to do? */
        {
            return;
        }
        /* last partial row to do by default routine */
    }

    ny += shift;

    /* Calculate bit masks */
    mask4 = ~(0xfe << ((ny-1) & 7));  /* data mask for last partial row */
    
    if (clear)
    {
        mask1 = ~(0xff << shift); /* clearing of first partial row */
        mask2 = 0;                /* clearing of intermediate (full) rows */
        mask3 = ~mask4;           /* clearing of last partial row */
        if (ny <= 8)
        {
            mask3 |= mask1;
        }
    }
    else
    {
        mask1 = mask2 = mask3 = 0xff;
    }

    /* Loop for each column */
    for (x = 0; x < nx; x++)
    {
        src_col = src++;
        dst_col = dst++;
        data = 0;
        y = 0;

        if (ny > 8)
        {
            /* First partial row */
            data = *src_col << shift;
            *dst_col = (*dst_col & mask1) | data;
            src_col += stride;
            dst_col += LCD_REMOTE_WIDTH;
            data >>= 8;

            /* Intermediate rows */
            for (y = 8; y < ny-8; y += 8)
            {
                data |= *src_col << shift;
                *dst_col = (*dst_col & mask2) | data;
                src_col += stride;
                dst_col += LCD_REMOTE_WIDTH;
                data >>= 8;
            }
        }

        /* Last partial row */
        if (y + shift < ny)
        {
            data |= *src_col << shift;
        }
        
        *dst_col = (*dst_col & mask3) | (data & mask4);
    }
}

void lcd_remote_drawrect(int x, int y, int nx, int ny)
{
    int i;

    if (x > LCD_REMOTE_WIDTH)
    {
        return;
    }
    
    if (y > LCD_REMOTE_HEIGHT)
    {
        return;
    }

    if (x + nx > LCD_REMOTE_WIDTH)
    {
        nx = LCD_REMOTE_WIDTH - x;
    }
    
    if (y + ny > LCD_REMOTE_HEIGHT)
    {
        ny = LCD_REMOTE_HEIGHT - y;
    }

    /* vertical lines */
    for (i = 0; i < ny; i++)
    {
        REMOTE_DRAW_PIXEL(x, (y + i));
        REMOTE_DRAW_PIXEL((x + nx - 1), (y + i));
    }

    /* horizontal lines */
    for (i = 0; i < nx; i++)
    {
        REMOTE_DRAW_PIXEL((x + i),y);
        REMOTE_DRAW_PIXEL((x + i),(y + ny - 1));
    }
}

void lcd_remote_clear(void)
{
    memset(lcd_remote_framebuffer, 0, sizeof lcd_remote_framebuffer);
}

void lcd_remote_update(void)
{
    int y;

    /* Copy display bitmap to hardware */
    for (y = 0; y < LCD_REMOTE_HEIGHT / 8; y++)
    {
        lcd_remote_write_command(LCD_REMOTE_CNTL_SET_PAGE_ADDRESS | y);
        lcd_remote_write_command_ex(0x10, 0x00);
        lcd_remote_write_data(lcd_remote_framebuffer[y], LCD_REMOTE_WIDTH);
    }
}

void lcd_remote_init(void)
{
    char buf[] = "\xFF";
    
    GPIO_FUNCTION   |= 0x10010800;  /* GPIO11: Backlight
                                       GPIO16: RS
                                       GPIO28: CLK */
    
    GPIO1_FUNCTION  |= 0x00040004;  /* GPIO34: CS
                                       GPIO50: Data */
    GPIO_ENABLE     |= 0x10010800;
    GPIO1_ENABLE    |= 0x00040004;
    
    CLK_LO;
    CS_HI;
    
    lcd_remote_write_command(LCD_REMOTE_CNTL_ADC_REVERSE);
    lcd_remote_write_command(LCD_REMOTE_CNTL_SHL_REVERSE);
    lcd_remote_write_command(LCD_REMOTE_CNTL_SELECT_BIAS | 0x0);
    
    lcd_remote_write_command(LCD_REMOTE_CNTL_POWER_CONTROL | 0x5);
    sleep(1);
    lcd_remote_write_command(LCD_REMOTE_CNTL_POWER_CONTROL | 0x6);
    sleep(1);
    lcd_remote_write_command(LCD_REMOTE_CNTL_POWER_CONTROL | 0x7);
    
    lcd_remote_write_command(LCD_REMOTE_CNTL_SELECT_REGULATOR | 0x4); // 0x4 Select regulator @ 5.0 (default);
    lcd_remote_set_contrast(32);
    
    sleep(1);
    
    lcd_remote_write_command(LCD_REMOTE_CNTL_INIT_LINE | 0x0); // init line
    lcd_remote_write_command(LCD_REMOTE_CNTL_SET_PAGE_ADDRESS | 0x0); // page address
    lcd_remote_write_command_ex(0x10, 0x00); // Column MSB + LSB
    
    lcd_remote_write_command(LCD_REMOTE_CNTL_DISPLAY_ON_OFF | 1);
    
    lcd_remote_clear();
    lcd_remote_drawrect(0, 0, 10, 20);
    lcd_remote_update();
}

#endif
