/***************************************************************************
*             __________               __   ___.
*   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
*   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
*   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
*   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
*                     \/            \/     \/    \/            \/
*
* Additional LCD routines not present in the rockbox core
* Scrolling functions
*
* Copyright (C) 2005 Jens Arnold
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/

#include "plugin.h"

#include "xlcd.h"

#if (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_INTERLEAVED)
static const unsigned short patterns[4] = {0xFFFF, 0xFF00, 0x00FF, 0x0000};
#endif

#if LCD_STRIDEFORMAT == VERTICAL_STRIDE
void xlcd_scroll_left(int count)
{
    /*size_t dst_stride;*/
    /*struct viewport *vp_main = NULL;*/
    fb_data *lcd_fb = get_framebuffer(NULL, NULL);


    int length, oldmode;

    if ((unsigned)count >= LCD_WIDTH)
    {
        rb->lcd_clear_display();
        return;
    }

    length = (LCD_WIDTH-count)*LCD_FBHEIGHT;

    rb->memmove(lcd_fb, lcd_fb + LCD_HEIGHT*count, length * sizeof(fb_data));

    oldmode = rb->lcd_get_drawmode();
    rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
    rb->lcd_fillrect(LCD_WIDTH-count, 0, count, LCD_HEIGHT);
    rb->lcd_set_drawmode(oldmode);
}

/* Scroll right */
void xlcd_scroll_right(int count)
{
    /*size_t dst_stride;*/
    /*struct viewport *vp_main = NULL;*/
    fb_data *lcd_fb = get_framebuffer(NULL, NULL);


    int length, oldmode;

    if ((unsigned)count >= LCD_WIDTH)
    {
        rb->lcd_clear_display();
        return;
    }

    length = (LCD_WIDTH-count)*LCD_FBHEIGHT;

    rb->memmove(lcd_fb + LCD_HEIGHT*count,
                lcd_fb, length * sizeof(fb_data));

    oldmode = rb->lcd_get_drawmode();
    rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
    rb->lcd_fillrect(0, 0, count, LCD_HEIGHT);
    rb->lcd_set_drawmode(oldmode);
}

/* Scroll up */
void xlcd_scroll_up(int count)
{
    /*size_t dst_stride;*/
    /*struct viewport *vp_main = NULL;*/
    fb_data *lcd_fb = get_framebuffer(NULL, NULL);


    int width, length, oldmode;

    fb_data *data;

    if ((unsigned)count >= LCD_HEIGHT)
    {
        rb->lcd_clear_display();
        return;
    }

    length = LCD_HEIGHT - count;

    width = LCD_WIDTH-1;
    data = lcd_fb;

    do {
        rb->memmove(data,data + count,length * sizeof(fb_data));
        data += LCD_HEIGHT;
    } while(width--);

    oldmode = rb->lcd_get_drawmode();
    rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
    rb->lcd_fillrect(0, length, LCD_WIDTH, count);
    rb->lcd_set_drawmode(oldmode);
}

/* Scroll down */
void xlcd_scroll_down(int count)
{
    /*size_t dst_stride;*/
    /*struct viewport *vp_main = NULL;*/
    fb_data *lcd_fb = get_framebuffer(NULL, NULL);


    int width, length, oldmode;

    fb_data *data;

    if ((unsigned)count >= LCD_HEIGHT)
    {
        rb->lcd_clear_display();
        return;
    }

    length = LCD_HEIGHT - count;

    width = LCD_WIDTH-1;
    data = lcd_fb;

    do {
        rb->memmove(data + count, data, length * sizeof(fb_data));
        data += LCD_HEIGHT;
    } while(width--);

    oldmode = rb->lcd_get_drawmode();
    rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
    rb->lcd_fillrect(0, 0, LCD_WIDTH, count);
    rb->lcd_set_drawmode(oldmode);
}
#else

#if (LCD_PIXELFORMAT == HORIZONTAL_PACKING) && (LCD_DEPTH < 8)

/* Scroll left */
void xlcd_scroll_left(int count)
{
    /*size_t dst_stride;*/
    /*struct viewport *vp_main = NULL;*/
    fb_data *lcd_fb = get_framebuffer(NULL, NULL);


    int bitcount=0, oldmode;
    int blockcount=0, blocklen;

    if ((unsigned) count >= LCD_WIDTH)
    {
        rb->lcd_clear_display();
        return;
    }

#if LCD_DEPTH == 2
    blockcount = count >> 2;
    blocklen = LCD_FBWIDTH - blockcount;
    bitcount = 2 * (count & 3);
#endif

    if (blockcount)
    {
        unsigned char *data = lcd_fb;
        unsigned char *data_end = data + LCD_FBWIDTH*LCD_HEIGHT;

        do
        {
            rb->memmove(data, data + blockcount, blocklen);
            data += LCD_FBWIDTH;
        }
        while (data < data_end);
    }
    if (bitcount)
    {
        int bx, y;
        unsigned char *addr = lcd_fb + blocklen;
#if LCD_DEPTH == 2
        unsigned fill = (0x55 * (~rb->lcd_get_background() & 3)) << bitcount;
#endif

        for (y = 0; y < LCD_HEIGHT; y++)
        {
            unsigned char *row_addr = addr;
            unsigned data = fill;

            for (bx = 0; bx < blocklen; bx++)
            {
                --row_addr;
                data = (data >> 8) | (*row_addr << bitcount);
                *row_addr = data;
            }
            addr += LCD_FBWIDTH;
        }
    }
    oldmode = rb->lcd_get_drawmode();
    rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
    rb->lcd_fillrect(LCD_WIDTH - count, 0, count, LCD_HEIGHT);
    rb->lcd_set_drawmode(oldmode);
}

/* Scroll right */
void xlcd_scroll_right(int count)
{
    /*size_t dst_stride;*/
    /*struct viewport *vp_main = NULL;*/
    fb_data *lcd_fb = get_framebuffer(NULL, NULL);


    int bitcount=0, oldmode;
    int blockcount=0, blocklen;

    if ((unsigned) count >= LCD_WIDTH)
    {
        rb->lcd_clear_display();
        return;
    }

#if LCD_DEPTH == 2
    blockcount = count >> 2;
    blocklen = LCD_FBWIDTH - blockcount;
    bitcount = 2 * (count & 3);
#endif

    if (blockcount)
    {
        unsigned char *data = lcd_fb;
        unsigned char *data_end = data + LCD_FBWIDTH*LCD_HEIGHT;

        do
        {
            rb->memmove(data + blockcount, data, blocklen);
            data += LCD_FBWIDTH;
        }
        while (data < data_end);
    }
    if (bitcount)
    {
        int bx, y;
        unsigned char *addr = lcd_fb + blockcount;
#if LCD_DEPTH == 2
        unsigned fill = 0x55 * (~rb->lcd_get_background() & 3);
#endif

        for (y = 0; y < LCD_HEIGHT; y++)
        {
            unsigned char *row_addr = addr;
            unsigned data = fill;

            for (bx = 0; bx < blocklen; bx++)
            {
                data = (data << 8) | *row_addr;
                *row_addr = data >> bitcount;
                row_addr++;
            }
            addr += LCD_FBWIDTH;
        }
    }
    oldmode = rb->lcd_get_drawmode();
    rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
    rb->lcd_fillrect(0, 0, count, LCD_HEIGHT);
    rb->lcd_set_drawmode(oldmode);
}

#else /* LCD_PIXELFORMAT vertical packed or >= 8bit / pixel */

/* Scroll left */
void xlcd_scroll_left(int count)
{
    /*size_t dst_stride;*/
    /*struct viewport *vp_main = NULL;*/
    fb_data *lcd_fb = get_framebuffer(NULL, NULL);


    fb_data *data, *data_end;
    int length, oldmode;

    if ((unsigned)count >= LCD_WIDTH)
    {
        rb->lcd_clear_display();
        return;
    }

    data = lcd_fb;
    data_end = data + LCD_WIDTH*LCD_FBHEIGHT;
    length = LCD_WIDTH - count;

    do
    {
        rb->memmove(data, data + count, length * sizeof(fb_data));
        data += LCD_WIDTH;
    }
    while (data < data_end);

    oldmode = rb->lcd_get_drawmode();
    rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
    rb->lcd_fillrect(length, 0, count, LCD_HEIGHT);
    rb->lcd_set_drawmode(oldmode);
}

/* Scroll right */
void xlcd_scroll_right(int count)
{
    /*size_t dst_stride;*/
    /*struct viewport *vp_main = NULL;*/
    fb_data *lcd_fb = get_framebuffer(NULL, NULL);


    fb_data *data, *data_end;
    int length, oldmode;

    if ((unsigned)count >= LCD_WIDTH)
    {
        rb->lcd_clear_display();
        return;
    }

    data = lcd_fb;
    data_end = data + LCD_WIDTH*LCD_FBHEIGHT;
    length = LCD_WIDTH - count;

    do
    {
        rb->memmove(data + count, data, length * sizeof(fb_data));
        data += LCD_WIDTH;
    }
    while (data < data_end);

    oldmode = rb->lcd_get_drawmode();
    rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
    rb->lcd_fillrect(0, 0, count, LCD_HEIGHT);
    rb->lcd_set_drawmode(oldmode);
}

#endif /* LCD_PIXELFORMAT, LCD_DEPTH */

#if (LCD_PIXELFORMAT == HORIZONTAL_PACKING) || (LCD_DEPTH >= 8)

/* Scroll up */
void xlcd_scroll_up(int count)
{
    /*size_t dst_stride;*/
    /*struct viewport *vp_main = NULL;*/
    fb_data *lcd_fb = get_framebuffer(NULL, NULL);

    int length, oldmode;

    if ((unsigned)count >= LCD_HEIGHT)
    {
        rb->lcd_clear_display();
        return;
    }

    length = LCD_HEIGHT - count;

    rb->memmove(lcd_fb,
                      lcd_fb + count * LCD_FBWIDTH,
                      length * LCD_FBWIDTH * sizeof(fb_data));

    oldmode = rb->lcd_get_drawmode();
    rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
    rb->lcd_fillrect(0, length, LCD_WIDTH, count);
    rb->lcd_set_drawmode(oldmode);
}

/* Scroll down */
void xlcd_scroll_down(int count)
{
    /*size_t dst_stride;*/
    /*struct viewport *vp_main = NULL;*/
    fb_data *lcd_fb = get_framebuffer(NULL, NULL);

    int length, oldmode;

    if ((unsigned)count >= LCD_HEIGHT)
    {
        rb->lcd_clear_display();
        return;
    }

    length = LCD_HEIGHT - count;

    rb->memmove(lcd_fb + count * LCD_FBWIDTH,
                      lcd_fb,
                      length * LCD_FBWIDTH * sizeof(fb_data));

    oldmode = rb->lcd_get_drawmode();
    rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
    rb->lcd_fillrect(0, 0, LCD_WIDTH, count);
    rb->lcd_set_drawmode(oldmode);
}

#else /* LCD_PIXELFORMAT == VERTICAL_PACKING,
         LCD_PIXELFORMAT == VERTICAL_INTERLEAVED */

/* Scroll up */
void xlcd_scroll_up(int count)
{
    /*size_t dst_stride;*/
    /*struct viewport *vp_main = NULL;*/
    fb_data *lcd_fb = get_framebuffer(NULL, NULL);

    int bitcount=0, oldmode;
    int blockcount=0, blocklen;

    if ((unsigned) count >= LCD_HEIGHT)
    {
        rb->lcd_clear_display();
        return;
    }

#if (LCD_DEPTH == 1) \
 || (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_INTERLEAVED)
    blockcount = count >> 3;
    bitcount = count & 7;
#elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_PACKING)
    blockcount = count >> 2;
    bitcount = 2 * (count & 3);
#endif
    blocklen = LCD_FBHEIGHT - blockcount;

    if (blockcount)
    {
        rb->memmove(lcd_fb,
                          lcd_fb + blockcount * LCD_FBWIDTH,
                          blocklen * LCD_FBWIDTH * sizeof(fb_data));
    }
    if (bitcount)
    {
#if LCD_PIXELFORMAT == VERTICAL_PACKING

#if defined(CPU_COLDFIRE) && (LCD_DEPTH == 2)
        asm (
            "move.l  %[wide],%%d3\n"  /* columns = width */

        ".su_cloop:              \n"  /* repeat for every column */
            "move.l  %[addr],%%a1\n"  /* get start address */
            "move.l  %[rows],%%d2\n"  /* rows = row_count */
            "move.l  %[bkg],%%d1 \n"  /* fill with background */

        ".su_iloop:              \n"  /* repeat for all rows */
            "sub.l   %[wide],%%a1\n"  /* address -= width */

            "lsl.l   #8,%%d1     \n"  /* old data to 2nd byte */
            "move.b  (%%a1),%%d1 \n"  /* combine with new data byte */
            "move.l  %%d1,%%d0   \n"  /* keep data for next round */
            "lsr.l   %[cnt],%%d0 \n"  /* shift right */
            "move.b  %%d0,(%%a1) \n"  /* store data */

            "subq.l  #1,%%d2     \n"  /* rows-- */
            "bne.b   .su_iloop   \n"

            "addq.l  #1,%[addr]  \n"  /* start_address++ */
            "subq.l  #1,%%d3     \n"  /* columns-- */
            "bne.b   .su_cloop   \n"
            : /* outputs */
            : /* inputs */
            [wide]"r"(LCD_FBWIDTH),
            [rows]"r"(blocklen),
            [addr]"a"(lcd_fb + blocklen * LCD_FBWIDTH),
            [cnt] "d"(bitcount),
            [bkg] "d"(0x55 * (~rb->lcd_get_background() & 3))
            : /* clobbers */
            "a1", "d0", "d1", "d2", "d3"
        );
#else /* C version */
        int x, by;
        unsigned char *addr = lcd_fb + blocklen * LCD_FBWIDTH;
#if LCD_DEPTH == 2
        unsigned fill = 0x55 * (~rb->lcd_get_background() & 3);
#else
        const unsigned fill = 0;
#endif

        for (x = 0; x < LCD_WIDTH; x++)
        {
            unsigned char *col_addr = addr++;
            unsigned data = fill;

            for (by = 0; by < blocklen; by++)
            {
                col_addr -= LCD_FBWIDTH;
                data = (data << 8) | *col_addr;
                *col_addr = data >> bitcount;
            }
        }
#endif /* CPU, LCD_DEPTH */

#elif LCD_PIXELFORMAT == VERTICAL_INTERLEAVED

#if LCD_DEPTH == 2
        int x, by;
        fb_data *addr = lcd_fb + blocklen * LCD_FBWIDTH;
        unsigned fill, mask;

        fill = patterns[rb->lcd_get_background() & 3] << 8;
        mask = (0xFFu >> bitcount) << bitcount;
        mask |= mask << 8;

        for (x = 0; x < LCD_WIDTH; x++)
        {
            fb_data *col_addr = addr++;
            unsigned olddata = fill;
            unsigned data;

            for (by = 0; by < blocklen; by++)
            {
                col_addr -= LCD_FBWIDTH;
                data = *col_addr;
                *col_addr = (olddata ^ ((data ^ olddata) & mask)) >> bitcount;
                olddata = data << 8;
            }
        }
#endif /* LCD_DEPTH == 2 */

#endif /* LCD_PIXELFORMAT */
    }
    oldmode = rb->lcd_get_drawmode();
    rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
    rb->lcd_fillrect(0, LCD_HEIGHT - count, LCD_WIDTH, count);
    rb->lcd_set_drawmode(oldmode);
}

/* Scroll up */
void xlcd_scroll_down(int count)
{
    /*size_t dst_stride;*/
    /*struct viewport *vp_main = NULL;*/
    fb_data *lcd_fb = get_framebuffer(NULL, NULL);

    int bitcount=0, oldmode;
    int blockcount=0, blocklen;

    if ((unsigned) count >= LCD_HEIGHT)
    {
        rb->lcd_clear_display();
        return;
    }

#if (LCD_DEPTH == 1) \
 || (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_INTERLEAVED)
    blockcount = count >> 3;
    bitcount = count & 7;
#elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_PACKING)
    blockcount = count >> 2;
    bitcount = 2 * (count & 3);
#endif
    blocklen = LCD_FBHEIGHT - blockcount;

    if (blockcount)
    {
        rb->memmove(lcd_fb + blockcount * LCD_FBWIDTH,
                          lcd_fb,
                          blocklen * LCD_FBWIDTH * sizeof(fb_data));
    }
    if (bitcount)
    {
#if LCD_PIXELFORMAT == VERTICAL_PACKING

#if defined(CPU_COLDFIRE) && (LCD_DEPTH == 2)
        asm (
            "move.l  %[wide],%%d3\n"  /* columns = width */

        ".sd_cloop:              \n"  /* repeat for every column */
            "move.l  %[addr],%%a1\n"  /* get start address */
            "move.l  %[rows],%%d2\n"  /* rows = row_count */
            "move.l  %[bkg],%%d1 \n"  /* fill with background */

        ".sd_iloop:              \n"  /* repeat for all rows */
            "lsr.l   #8,%%d1     \n"  /* shift right to get residue */
            "clr.l   %%d0        \n"
            "move.b  (%%a1),%%d0 \n"  /* get data byte */
            "lsl.l   %[cnt],%%d0 \n"
            "or.l    %%d0,%%d1   \n"  /* combine with last residue */
            "move.b  %%d1,(%%a1) \n"  /* store data */

            "add.l   %[wide],%%a1\n"  /* address += width */
            "subq.l  #1,%%d2     \n"  /* rows-- */
            "bne.b   .sd_iloop   \n"

            "lea.l   (1,%[addr]),%[addr] \n"  /* start_address++ */
            "subq.l  #1,%%d3     \n"  /* columns-- */
            "bne.b   .sd_cloop   \n"
            : /* outputs */
            : /* inputs */
            [wide]"r"(LCD_WIDTH),
            [rows]"r"(blocklen),
            [addr]"a"(lcd_fb + blockcount * LCD_FBWIDTH),
            [cnt] "d"(bitcount),
            [bkg] "d"((0x55 * (~rb->lcd_get_background() & 3)) << bitcount)
            : /* clobbers */
            "a1", "d0", "d1", "d2", "d3"
        );
#else /* C version */
        int x, by;
        unsigned char *addr = lcd_fb + blockcount * LCD_FBWIDTH;
#if LCD_DEPTH == 2
        unsigned fill = (0x55 * (~rb->lcd_get_background() & 3)) << bitcount;
#else
        const unsigned fill = 0;
#endif

        for (x = 0; x < LCD_WIDTH; x++)
        {
            unsigned char *col_addr = addr++;
            unsigned data = fill;

            for (by = 0; by < blocklen; by++)
            {
                data = (data >> 8) | (*col_addr << bitcount);
                *col_addr = data;
                col_addr += LCD_FBWIDTH;
            }
        }
#endif /* CPU, LCD_DEPTH */

#elif LCD_PIXELFORMAT == VERTICAL_INTERLEAVED

#if LCD_DEPTH == 2
        int x, by;
        fb_data *addr = lcd_fb + blockcount * LCD_FBWIDTH;
        unsigned fill, mask;

        fill = patterns[rb->lcd_get_background() & 3] >> (8 - bitcount);
        mask = (0xFFu >> bitcount) << bitcount;
        mask |= mask << 8;

        for (x = 0; x < LCD_WIDTH; x++)
        {
            fb_data *col_addr = addr++;
            unsigned olddata = fill;
            unsigned data;

            for (by = 0; by < blocklen; by++)
            {
                data = *col_addr << bitcount;
                *col_addr = olddata ^ ((data ^ olddata) & mask);
                olddata = data >> 8;
                col_addr += LCD_FBWIDTH;
            }
        }
#endif /* LCD_DEPTH == 2 */

#endif /* LCD_PIXELFORMAT */
    }
    oldmode = rb->lcd_get_drawmode();
    rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
    rb->lcd_fillrect(0, 0, LCD_WIDTH, count);
    rb->lcd_set_drawmode(oldmode);
}

#endif /* LCD_PIXELFORMAT, LCD_DEPTH */
#endif /* LCD_STRIDEFORMAT == VERTICAL_STRIDE */
