/********************************************************************************
    KS0066-compatible LCD display driver
    Copyright (C) 2006  Alexey "Gall" Galakhov

    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 program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*********************************************************************************/
#include <stdint.h>
#include <avr/io.h>
#include <stdio.h>
#include "bit.h"

#include "lcd.h"
#include "lcdcmd.h"

static const uint8_t LCD_LINE [LCD_NUM_LINES] = 
{
  0x00,
  #if (LCD_NUM_LINES >= 2)  
    0x40,
  #endif
  #if (LCD_NUM_LINES >= 4)
    #if (LCD_NUM_CHARS == 16)
      0x10, 0x50,
    #else
      0x14, 0x54,
    #endif
  #endif
};

FILE lcd_fdev = FDEV_SETUP_STREAM (lcd_putchar, NULL, _FDEV_SETUP_WRITE);

#define lcd_set_e_high()	sbi (LCD_CTRL_PORT, LCD_PIN_E)
#define lcd_set_e_low()		cbi (LCD_CTRL_PORT, LCD_PIN_E)
#define lcd_set_rs_cmd()	cbi (LCD_CTRL_PORT, LCD_PIN_RS)
#define lcd_set_rs_data()	sbi (LCD_CTRL_PORT, LCD_PIN_RS)
#define lcd_set_rw_read()	sbi (LCD_CTRL_PORT, LCD_PIN_RW)
#define lcd_set_rw_write()	cbi (LCD_CTRL_PORT, LCD_PIN_RW)
#define lcd_set_input()		\
{				\
  LCD_DATA_DDR = 0x00;		\
  LCD_DATA_PORT = 0xff;		\
}
#define lcd_set_output()	\
{				\
  LCD_DATA_DDR = 0xff;		\
}

static void
lcd_delay (void)
{
  asm volatile ("nop; nop; nop" ::);
}

void
lcd_init (void)
{
  (void) lcd_fdev;
  /* Initialize hardware interface */
  LCD_CTRL_PORT &= ~(BV (LCD_PIN_RS) | BV (LCD_PIN_RW) | BV (LCD_PIN_E));
#ifdef LCD_PIN_LIGHT
  sbi (LCD_CTRL_PORT, LCD_PIN_LIGHT);
#endif
  LCD_CTRL_DDR |= (BV (LCD_PIN_RS) | BV (LCD_PIN_RW) 
                 #ifdef LCD_PIN_LIGHT
		   | BV (LCD_PIN_LIGHT)
	         #endif
		   | BV (LCD_PIN_E));
  /* Do LCD start-up sequence */
  lcd_control (LCD_CMD_FUNC | LCD_FUNC_8BIT | LCD_FUNC_2LINE);
  lcd_control (LCD_CMD_CLS);
  lcd_control (LCD_CMD_ENTRY | LCD_ENTRY_INCR);
  /* Turn on display */
  lcd_control (LCD_CMD_ONOFF | LCD_ONOFF_ON);
  lcd_control (LCD_CMD_HOME);
  lcd_control (LCD_CMD_DDRAM | 0x00);
}

void
lcd_wait (void)
{
  lcd_set_rs_cmd ();
  lcd_set_input ();
  lcd_set_rw_read ();
  lcd_set_e_high ();
  while (LCD_DATA_PIN & LCD_BUSY)
    {
      lcd_set_e_low ();
      lcd_delay ();
      lcd_set_e_high ();
      lcd_delay ();
    }
  lcd_set_e_low ();
}

static void
lcd_lowlevel_write (const uint8_t data)
{
  lcd_set_rw_write ();
  lcd_set_output ();
  LCD_DATA_PORT = data;
  lcd_set_e_high ();
  lcd_delay ();
  lcd_set_e_low ();
  lcd_set_input ();
}

void
lcd_control (const uint8_t cmd)
{
  lcd_wait ();
  lcd_set_rs_cmd ();
  lcd_lowlevel_write (cmd);
}

void
lcd_putch (const char chr)
{
  lcd_wait ();
  lcd_set_rs_data ();
  lcd_lowlevel_write ((uint8_t) chr);
}

int
lcd_putchar (const char chr, FILE* stream)
{
  (void) stream;
  if ((uint8_t) chr >= 0x20)
    lcd_putch (chr);
  return 0;
}

void
lcd_gotoxy (const uint8_t x, const uint8_t y)
{
  if ((y >= LCD_NUM_LINES) || (x >= LCD_NUM_CHARS))
    return;
  lcd_control (LCD_CMD_DDRAM | (LCD_LINE [y] + x));
}
