/*  sed1520.cpp
    Graphics LCD driver for SED1520 122x32 graphic LCDs

    22.12.2001: tk, implemented display mirror code, as
                    suggested by robert dubber.
    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 <stdio.h>
#include <conio.h>
#include <string.h>
#include "sc12/hwapi.h"
#include "sc12/rtos.h"
#include "gfxdriver.h"
#include "misc.h"


/**
 * LCD Refresh clock (in Hz)
 */
#define LCD_CLOCK       2000

/**
 * The brightness PWM signal is derived from the LCD_CLOCK.
 * Some displays have constraints on the duty cycle of the
 * clock signal, so the minimum and maximum duty cycles can be
 * set by LCD_MINPWMON and LCD_MINPWMOFF (in timer ticks)
 *
 * NOTE:
 *   If you have problems with your LCD, you should try
 *   higher PWM values first! I had a real debugging
 *   nightmare when i got random pixels on my display,
 *   only to find out that the timing was too tight..
 *
 *   50 and 10 should do for any SED1520 driven display,
 *   while still allowing brightness control between 4 and
 *   99.2% (i even tried heating it with a hairdryer,
 *   because the display controller seems to get slower
 *   when it's warm :)
 */
#define LCD_MINPWMON      50
#define LCD_MINPWMOFF     10


/**
 * If you want to mount the display upside down, you can
 * mirror the graphics using this define.
 * (Thanks to Robert Dubber)
 */
#define LCD_UPSIDEDOWN

#ifdef  LCD_UPSIDEDOWN
static const unsigned 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

/**
 * memory buffer for the LCD screen.
 */
static unsigned char lcd_screen[4][128];


/**
 * Get a pointer to the screen buffer.
 */
char *LCD_GetScreenBuffer()
{
  return (char*)(&lcd_screen[0][0]);
}


/**
 * Get Width of LCD matrix (in pixels).
 */
int  LCD_GetWidth()
{
  return 122;
}


/**
 * Get Height of LCD matrix (in pixels).
 */
int  LCD_GetHeight()
{
  return 32;
}


/**
 * Clear the lcd_screen array.
 */
void LCD_Clear()
{
  memset(lcd_screen, 0x00, sizeof(lcd_screen));
}


/**
 * Send the screen buffer to the actual display memory
 * \note
 *   I tried REP_OUTSB, but some displays are too
 *   slow for this transfer.
 *
 */
void LCD_Update()
{
#ifndef LCD_UPSIDEDOWN
  for (int y=0; y<4; y++) {
    outp(0x600, 0x00);
    outp(0x600, 0xB8 + y);
    outp(0x500, 0x00);
    outp(0x500, 0xB8 + y);
    for (int x=0; x<61; x++) {
      outp(0x602, lcd_screen[y][x   ]);
      outp(0x502, lcd_screen[y][x+61]);
    }
  }
#else
  for (int y=0; y<4; y++) {
    outp(0x600, 0x00);
    outp(0x600, 0xB8 + (3-y));
    outp(0x500, 0x00);
    outp(0x500, 0xB8 + (3-y));
    for (int x=60; x>=0; x--) {
      outp(0x502, mirror[lcd_screen[y][x   ]]);
      outp(0x602, mirror[lcd_screen[y][x+61]]);
    }
  }
#endif

}


/**
 * 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_frequency(FREQ_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;

  hal_init_timer(0, TIMER_CONT | TIMER_INT, pwmOn);
  hal_set_timer_duty(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("initializing SED1520 LCD..\n");
  pfe_enable_bus(0xff, true);
  pfe_enable_pcs(5);
  pfe_enable_pcs(6);

  // enable refresh timer
  //
  pfe_enable_timer(TIMER_OUT0);
  LCD_SetBrightness(255);

  outp(0x500, 0xe2);  // reset
  outp(0x600, 0xe2);
  outp(0x500, 0xa0);  // select forward adc
  outp(0x600, 0xa0);
  outp(0x500, 0xa4);  // static drive off
  outp(0x600, 0xa4);
  outp(0x500, 0xaf);  // display on
  outp(0x600, 0xaf);

  LCD_Clear();
  LCD_Update();
}


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

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

  // free pio pins
  //
  pfe_enable_pio(13, PIO_IPD);
  pfe_enable_pio( 2, PIO_IPU);
  pfe_enable_pio( 3, PIO_IPU);
}