/*  gfxdriver.cpp
    Graphics driver for SED1520 (or comaptible) LCDs

    30.06.2001: tk, after some tests and many different blit functions,
                i finally changed back to the old memory layout. There's
                no big performance difference, and it doesn't even make
                the blit functions easier (on the contrary!!)
    26.12.2001: tk, changed the internal screen array: Before
                the change, the array represented 4 lines that
                spanned 122x8 pixel each, and made an efficient
                get/putimage quite challenging to implement.
                In the new layout, all bytes belonging to the a
                single column (or x-coordinate) are now directly
                adjacent to each other.
    22.12.2001: tk, implemented display mirror code, as suggested
                by robert dubber, r.dubber@chello.nl (thanks!)
    09.06.2001: tk, initial implementation.

    Copyright (c)2001 by Thomas Kindler, thomas.kindler@gmx.de

    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. Read the
    full License at http://www.gnu.org/copyleft for more details.
*/

#include "clib/hwapi.h"
#include "clib/rtos.h"
#include "bitmap.h"
#include "gfxdriver.h"
#include "misc.h"
#include <stdio.h>
#include <conio.h>
#include <string.h>

#ifdef  LCD_UPSIDEDOWN
/**
 * lookup table for display-mirroring
 */
static near const char mirror[256] = {
  0x00,0x80,0x40,0xC0,0x20,0xA0,0x60,0xE0,0x10,0x90,0x50,0xD0,0x30,0xB0,0x70,0xF0,
  0x08,0x88,0x48,0xC8,0x28,0xA8,0x68,0xE8,0x18,0x98,0x58,0xD8,0x38,0xB8,0x78,0xF8,
  0x04,0x84,0x44,0xC4,0x24,0xA4,0x64,0xE4,0x14,0x94,0x54,0xD4,0x34,0xB4,0x74,0xF4,
  0x0C,0x8C,0x4C,0xCC,0x2C,0xAC,0x6C,0xEC,0x1C,0x9C,0x5C,0xDC,0x3C,0xBC,0x7C,0xFC,
  0x02,0x82,0x42,0xC2,0x22,0xA2,0x62,0xE2,0x12,0x92,0x52,0xD2,0x32,0xB2,0x72,0xF2,
  0x0A,0x8A,0x4A,0xCA,0x2A,0xAA,0x6A,0xEA,0x1A,0x9A,0x5A,0xDA,0x3A,0xBA,0x7A,0xFA,
  0x06,0x86,0x46,0xC6,0x26,0xA6,0x66,0xE6,0x16,0x96,0x56,0xD6,0x36,0xB6,0x76,0xF6,
  0x0E,0x8E,0x4E,0xCE,0x2E,0xAE,0x6E,0xEE,0x1E,0x9E,0x5E,0xDE,0x3E,0xBE,0x7E,0xFE,
  0x01,0x81,0x41,0xC1,0x21,0xA1,0x61,0xE1,0x11,0x91,0x51,0xD1,0x31,0xB1,0x71,0xF1,
  0x09,0x89,0x49,0xC9,0x29,0xA9,0x69,0xE9,0x19,0x99,0x59,0xD9,0x39,0xB9,0x79,0xF9,
  0x05,0x85,0x45,0xC5,0x25,0xA5,0x65,0xE5,0x15,0x95,0x55,0xD5,0x35,0xB5,0x75,0xF5,
  0x0D,0x8D,0x4D,0xCD,0x2D,0xAD,0x6D,0xED,0x1D,0x9D,0x5D,0xDD,0x3D,0xBD,0x7D,0xFD,
  0x03,0x83,0x43,0xC3,0x23,0xA3,0x63,0xE3,0x13,0x93,0x53,0xD3,0x33,0xB3,0x73,0xF3,
  0x0B,0x8B,0x4B,0xCB,0x2B,0xAB,0x6B,0xEB,0x1B,0x9B,0x5B,0xDB,0x3B,0xBB,0x7B,0xFB,
  0x07,0x87,0x47,0xC7,0x27,0xA7,0x67,0xE7,0x17,0x97,0x57,0xD7,0x37,0xB7,0x77,0xF7,
  0x0F,0x8F,0x4F,0xCF,0x2F,0xAF,0x6F,0xEF,0x1F,0x9F,0x5F,0xDF,0x3F,0xBF,0x7F,0xFF
};
#endif


/**
 * Update the LCD Display.
 *
 * \param bm  Bitmap to copy to the display.
 * \note  Takes 370us on a 20MHz SC12, 122x32.
 *       (1250us if LCD_UPSIDEDOWN is defined.)
 */
void LCD_Update(Bitmap *bm) {
  for (int p=0; p<LCD_PAGES; p++) {
    outp(LCD_IOBASE0, 0x00);
    outp(LCD_IOBASE0, 0xB8 + p);
    outp(LCD_IOBASE1, 0x00);
    outp(LCD_IOBASE1, 0xB8 + p);

#ifndef LCD_UPSIDEDOWN
    char *src = &bm->bmBits[p*bm->width];
    asm {
      pushf
      push  cx
      push  dx
      push  si
      push  ds
      cld

      lds   si, src
      mov   cx, LCD_WIDTH/2
      mov   dx, LCD_IOBASE0+2
      rep   outsb

      mov   cx, LCD_WIDTH/2
      mov   dx, LCD_IOBASE1+2
      rep   outsb


      pop   ds
      pop   si
      pop   dx
      pop   cx
      popf
    }
#else
    char *src = &bm->bmBits[(LCD_WIDTH*LCD_PAGES-1) - p*bm->width];
    asm {
      pushf
      push  bx
      push  cx
      push  dx
      push  ds
      push  si
      push  es
      std

      mov   bx, offset mirror
      les   si, src
      mov   cx, LCD_WIDTH/2
      mov   dx, LCD_IOBASE0+2
l1:   db    0x26        // ES prefix
      lodsb
      xlat
      out   dx, al
      loop  l1

      mov   cx, LCD_WIDTH/2
      mov   dx, LCD_IOBASE1+2
l2:   db    0x26        // ES prefix
      lodsb
      xlat
      out   dx, al
      loop  l2

      pop   es
      pop   si
      pop   ds
      pop   dx
      pop   cx
      pop   bx
      popf
    }
#endif
  }
}


/**
 * Clear the LCD Display.
 *
 * \param pattern  pattern to clear with
 *                 (0x00 is all clear, 0xff is all set)
 * \note Takes 578us on a 20MHz SC12.
 */
void LCD_Clear(unsigned char pattern)
{
  for (int p=0; p<LCD_PAGES; p++) {
    outp(LCD_IOBASE0, 0x00);
    outp(LCD_IOBASE0, 0xB8 + p);
    outp(LCD_IOBASE1, 0x00);
    outp(LCD_IOBASE1, 0xB8 + p);
    asm {
      push  cx
      push  dx
      mov   al, pattern

      mov   dx, LCD_IOBASE0+2
      mov   cx, LCD_WIDTH/2
l1:   out   dx, al
      loop  l1

      mov   dx, LCD_IOBASE1+2
      mov   cx, LCD_WIDTH/2
l2:   out   dx, al
      loop  l2
      
      pop   dx
      pop   cx
    }
  }
}


/**
 * Set display brightness. This function MUST be called
 * during initialization, because it sets up the vertical
 * refresh timer for the LCD. (the brightness PWM signal
 * is derived from that clock)
 *
 * \param level  brigthness level 0..255
 *
 */
void LCD_SetBrightness(int level)
{
  long     period;
  unsigned pwmOn, pwmOff;

  if (level <   0) level =   0;
  if (level > 255) level = 255;
  period = hal_get_frequencies(GET_FRQ_TIMER)/LCD_CLOCK;
  pwmOn  = (unsigned)(period*level/255);
  pwmOff = (unsigned)period - pwmOn;
  if (pwmOn  < LCD_MINPWMON )  pwmOn  = LCD_MINPWMON;
  if (pwmOff < LCD_MINPWMOFF)  pwmOff = LCD_MINPWMOFF;

  // start timer in continuos mode
  //
  hal_init_timer(0, 1, pwmOn);
  hal_set_duty_cycle_waveform(0, true, pwmOff);
  hal_start_timer(0);
}


/**
 * LCD_Init() must be called, before using any of the other
 * LCD functions. The display gets initialized, and cleared.
 */
void LCD_Init()
{
  printf("LCD_Init(): initializing SED1520..\n");
  pfe_enable_bus(0xff, true);
  pfe_enable_pcs(LCD_IOBASE0 >> 8);
  pfe_enable_pcs(LCD_IOBASE1 >> 8);

  // enable refresh timer
  //
  pfe_enable_timer(3);      // timer out 0
  LCD_SetBrightness(255);

  outp(LCD_IOBASE0, 0xe2);  // reset
  outp(LCD_IOBASE0, 0xa0);  // select forward adc
  outp(LCD_IOBASE0, 0xa4);  // static drive off
  outp(LCD_IOBASE0, 0xaf);  // display on
  outp(LCD_IOBASE1, 0xe2);
  outp(LCD_IOBASE1, 0xa0);
  outp(LCD_IOBASE1, 0xa4);
  outp(LCD_IOBASE1, 0xaf);
  LCD_Clear();
}


/**
 * Shut down the LCD controller.
 */
void LCD_Done()
{
  // enter power-save mode
  //
  outp(LCD_IOBASE0, 0xa5);  // static drive on
  outp(LCD_IOBASE0, 0xae);  // display off
  outp(LCD_IOBASE1, 0xa5);
  outp(LCD_IOBASE1, 0xae);

  // disable refresh timer
  //
  hal_stop_timer(0);

  // switch off backlight
  //
  pfe_enable_pio(13, PIO_IPD);
}
