/* vim: set sw=8 ts=8 si : */
/****************************************************************************
Title	:   HD44780U LCD library
Authors:   
Based on Volker Oth's lcd library (http://members.xoom.com/volkeroth)
modified by Peter Fleury's (http://jump.to/fleury). Flexible pin
configuration by Markus Ermert. Adapted for the linuxfocus LCD display
by Guido Socher.

Software:  AVR-GCC with AVR-AS
Target:    any AVR device
Copyright: GPL
       
*****************************************************************************/
#include <io.h>
#include <progmem.h>
#include "avr-util.h"
#include "lcd_hw.h"
#include "lcd.h"


/* 
** constants/macros 
*/
#define DDR(x) ((x)-1)		/* address of data direction register of port x */
#define PIN(x) ((x)-2)		/* address of input register of port x */

#if LCD_IO_MODE
/* #define lcd_e_delay()   delay_us(1) */
#define lcd_e_delay()   asm volatile("rjmp _PC_+0")  /* delay 500ns with 4Mhz */
#define lcd_e_high()    sbi(LCD_E_PORT, LCD_E_PIN)
#define lcd_e_low()     cbi(LCD_E_PORT, LCD_E_PIN)
#define lcd_e_toggle()  toggle_e()

#define lcd_cmd_mode()	cbi(LCD_RS_PORT, LCD_RS_PIN)	/* RS=0  command mode   */
#define lcd_data_mode()	sbi(LCD_RS_PORT, LCD_RS_PIN)	/* RS=1  data mode      */
#define lcd_wr_mode()		cbi(LCD_RW_PORT, LCD_RW_PIN)	/* RW=0  write mode     */
#define lcd_rd_mode()		sbi(LCD_RW_PORT, LCD_RW_PIN)	/* RW=1  read mode      */
#define lcd_data_port_in()	{	/* defines all data pins as input */ \
					cbi(DDR(LCD_DATA_PORT_D7),LCD_DATA_PIN_D7);\
					cbi(DDR(LCD_DATA_PORT_D6),LCD_DATA_PIN_D6);\
					cbi(DDR(LCD_DATA_PORT_D5),LCD_DATA_PIN_D5);\
					cbi(DDR(LCD_DATA_PORT_D4),LCD_DATA_PIN_D4);\
				}
#define lcd_data_port_out()	{	/* defines all data pins as output */ \
					sbi(DDR(LCD_DATA_PORT_D7),LCD_DATA_PIN_D7);\
					sbi(DDR(LCD_DATA_PORT_D6),LCD_DATA_PIN_D6);\
				 	sbi(DDR(LCD_DATA_PORT_D5),LCD_DATA_PIN_D5);\
				 	sbi(DDR(LCD_DATA_PORT_D4),LCD_DATA_PIN_D4);\
				}
#endif

#if LCD_IO_MODE
#if LCD_LINES==1
#define LCD_FUNCTION_DEFAULT    LCD_FUNCTION_4BIT_1LINE
#else
#define LCD_FUNCTION_DEFAULT    LCD_FUNCTION_4BIT_2LINES
#endif
#else
#if LCD_LINES==1
#define LCD_FUNCTION_DEFAULT    LCD_FUNCTION_8BIT_1LINE
#else
#define LCD_FUNCTION_DEFAULT    LCD_FUNCTION_8BIT_2LINES
#endif
#endif


/* 
** function prototypes 
*/
#if LCD_IO_MODE
static void toggle_e(void);
static void lcd_out_high(u08 d);
static void lcd_out_low(u08 d);
#endif

/*
** local functions
*/

#if LCD_IO_MODE
static void lcd_out_low(u08 d)
{	/* output low nibble */
	if (d&0x08)  sbi(LCD_DATA_PORT_D7,LCD_DATA_PIN_D7);
		else cbi(LCD_DATA_PORT_D7,LCD_DATA_PIN_D7);
	if (d&0x04)  sbi(LCD_DATA_PORT_D6,LCD_DATA_PIN_D6);
		else cbi(LCD_DATA_PORT_D6,LCD_DATA_PIN_D6);
	if (d&0x02)  sbi(LCD_DATA_PORT_D5,LCD_DATA_PIN_D5);
		else cbi(LCD_DATA_PORT_D5,LCD_DATA_PIN_D5);
	if (d&0x01)  sbi(LCD_DATA_PORT_D4,LCD_DATA_PIN_D4);
		else cbi(LCD_DATA_PORT_D4,LCD_DATA_PIN_D4); 
}
static void lcd_out_high(u08 d)
{	/* output high nibble */ 
	if (d&0x80)  sbi(LCD_DATA_PORT_D7,LCD_DATA_PIN_D7);
		else cbi(LCD_DATA_PORT_D7,LCD_DATA_PIN_D7);
	if (d&0x40)  sbi(LCD_DATA_PORT_D6,LCD_DATA_PIN_D6);
		else cbi(LCD_DATA_PORT_D6,LCD_DATA_PIN_D6);
	if (d&0x20)  sbi(LCD_DATA_PORT_D5,LCD_DATA_PIN_D5);
		else cbi(LCD_DATA_PORT_D5,LCD_DATA_PIN_D5);
	if (d&0x10)  sbi(LCD_DATA_PORT_D4,LCD_DATA_PIN_D4);
		else cbi(LCD_DATA_PORT_D4,LCD_DATA_PIN_D4); 
}

static void toggle_e(void)
/* toggle Enable Pin */
{
	lcd_e_high();
	lcd_e_delay();
	lcd_e_low();
}
#endif


#if LCD_IO_MODE
static void lcd_write(u08 data, u08 rs)
{
	/* configure data pins as output */
	lcd_data_port_out();

	/* output high nibble first */

	lcd_out_high(data);

	if (rs)
		lcd_data_mode();	/* RS=1: write data            */
	else
		lcd_cmd_mode();	/* RS=0: write instruction     */
	lcd_wr_mode();		/* RW=0  write mode         */
	lcd_e_toggle();

	/* output low nibble */
	lcd_out_low(data);

	if (rs)
		lcd_data_mode();	/* RS=1: write data            */
	else
		lcd_cmd_mode();	/* RS=0: write instruction     */
	lcd_wr_mode();		/* RW=0  write mode         */

	lcd_e_toggle();

	/* all data pins high (inactive) */
	lcd_data_port_in();
}
#else
/* Memory mapped mode */
#define lcd_write(d,rs) if (rs) *(volatile u08*)(LCD_IO_DATA) = d; else *(volatile u08*)(LCD_IO_FUNCTION) = d;
/* rs==0 -> write instruction to LCD_IO_FUNCTION */
/* rs==1 -> write data to LCD_IO_DATA */
#endif


#if LCD_IO_MODE
static u08 lcd_read(u08 rs)
{
	register u08 data;

	/* configure data pins as input */
	lcd_data_port_in();

	if (rs)
		lcd_data_mode();	/* RS=1: read data      */
	else
		lcd_cmd_mode();	/* RS=0: read busy flag */
	lcd_rd_mode();		/* RW=1  read mode      */


	lcd_e_high();
	lcd_e_delay();

	/* read high nibble first */


	data =
	    bit_is_set(PIN(LCD_DATA_PORT_D7), LCD_DATA_PIN_D7) ? 0x80 : 0;
	data |=
	    bit_is_set(PIN(LCD_DATA_PORT_D6), LCD_DATA_PIN_D6) ? 0x40 : 0;
	data |=
	    bit_is_set(PIN(LCD_DATA_PORT_D5), LCD_DATA_PIN_D5) ? 0x20 : 0;
	data |=
	    bit_is_set(PIN(LCD_DATA_PORT_D4), LCD_DATA_PIN_D4) ? 0x10 : 0;


	lcd_e_low();
	lcd_e_delay();		/* Enable low       */

	lcd_e_high();
	lcd_e_delay();

	/* read low nibble        */


	data |=
	    bit_is_set(PIN(LCD_DATA_PORT_D7), LCD_DATA_PIN_D7) ? 0x08 : 0;
	data |=
	    bit_is_set(PIN(LCD_DATA_PORT_D6), LCD_DATA_PIN_D6) ? 0x04 : 0;
	data |=
	    bit_is_set(PIN(LCD_DATA_PORT_D5), LCD_DATA_PIN_D5) ? 0x02 : 0;
	data |=
	    bit_is_set(PIN(LCD_DATA_PORT_D4), LCD_DATA_PIN_D4) ? 0x01 : 0;


	lcd_e_low();

	return (data);
}
#else
#define lcd_read(rs) (rs) ? *(volatile u08*)(LCD_IO_DATA+LCD_IO_READ) : *(volatile u08*)(LCD_IO_FUNCTION+LCD_IO_READ)
/* rs==0 -> read instruction from LCD_IO_FUNCTION */
/* rs==1 -> read data from LCD_IO_DATA */
#endif


static unsigned char lcd_waitbusy(void)
/* loops while lcd is busy, reads address counter */
{
	register unsigned char c;

	while ((c = lcd_read(0)) & (1 << LCD_BUSY)) {
	}

	return (c); // return address counter=position
}




/*
** PUBLIC FUNCTIONS 
*/

void lcd_command(u08 cmd)
/* send commando <cmd> to LCD */
{
	lcd_waitbusy();
	lcd_write(cmd, 0);
}


void lcd_gotoxy(u08 x, u08 y)
/* goto position (x,y) */
{
#if LCD_LINES==1
	lcd_command((1 << LCD_DDRAM) + LCD_START_LINE1 + x);
#endif
#if LCD_LINES==2
	if (y == 0)
		lcd_command((1 << LCD_DDRAM) + LCD_START_LINE1 + x);
	else
		lcd_command((1 << LCD_DDRAM) + LCD_START_LINE2 + x);
#endif
#if LCD_LINES==3
	if (y == 0)
		lcd_command((1 << LCD_DDRAM) + LCD_START_LINE1 + x);
	else if (y == 1)
		lcd_command((1 << LCD_DDRAM) + LCD_START_LINE2 + x);
	else if (y == 2)
		lcd_command((1 << LCD_DDRAM) + LCD_START_LINE3 + x);
#endif
#if LCD_LINES==4
	if (y == 0)
		lcd_command((1 << LCD_DDRAM) + LCD_START_LINE1 + x);
	else if (y == 1)
		lcd_command((1 << LCD_DDRAM) + LCD_START_LINE2 + x);
	else if (y == 2)
		lcd_command((1 << LCD_DDRAM) + LCD_START_LINE3 + x);
	else			/* y==3 */
		lcd_command((1 << LCD_DDRAM) + LCD_START_LINE4 + x);
#endif

}				/* lcd_gotoxy */


void lcd_clrscr(void)
/* clear lcd and set cursor to home position */
{
	lcd_command(1 << LCD_CLR);
}


void lcd_home(void)
/* set cursor to home position */
{
	lcd_command(1 << LCD_HOME);
}


void lcd_putc(char c)
/* print character at current cursor position */
{
	lcd_waitbusy();	// read busy-flag and address counter
	lcd_write((unsigned char)c, 1);
}

void rolltext_lcd_p(prog_char *text, int shiftlen){
        unsigned char i;
        unsigned char j;
        unsigned char textlen=0;
        while (PRG_RDB(text+textlen)){
                textlen++;
        }
        if (shiftlen <= ROLLLEN){
		i=0;
                j=ROLLLEN-shiftlen + 1;
                while(i < j){
                        lcd_putc(' ');
                        i++;
                }
		j=0;
        }else{
                j=shiftlen-ROLLLEN;
		i=0;
        }
        if (j <= textlen) {
                while(i < ROLLLEN){
                        if (PRG_RDB(text+j)=='\0') break;
                        lcd_putc(PRG_RDB(text+j));
                        j++;
                        i++;
                }
        }
}

void lcd_puts(const char *s)
/* print string on lcd  */
{
	while (*s) {
		lcd_putc(*s);
		s++;
	}

}


void lcd_puts_p(const prog_char *progmem_s)
/* print string from program memory on lcd  */
{
	register char c;

	while ((c = PRG_RDB(progmem_s++))) {
		lcd_putc(c);
	}

}


void lcd_init(u08 dispAttr)
/* initialize display and select type of cursor */
/* dispAttr: LCD_DISP_OFF, LCD_DISP_ON, LCD_DISP_ON_CURSOR, LCD_DISP_CURSOR_BLINK */
{
#if LCD_IO_MODE
    /*------ Initialize lcd to 4 bit i/o mode -------*/

	lcd_data_port_out();	/* all data port bits as output */
	sbi(DDR(LCD_RS_PORT), LCD_RS_PIN);	/* RS pin as output */
	sbi(DDR(LCD_RW_PORT), LCD_RW_PIN);	/* RW pin as output */
	sbi(DDR(LCD_E_PORT), LCD_E_PIN);	/* E  pin as output */


	delay_us(16000);	/* wait 16ms or more after power-on       */

	/* initial write to lcd is 8bit */
	lcd_out_high(LCD_FUNCTION_8BIT_1LINE);
	lcd_e_toggle();
	delay_us(4992);		/* delay, busy flag can't be checked here */

	lcd_out_high(LCD_FUNCTION_8BIT_1LINE);
	lcd_e_toggle();
	delay_us(64);		/* delay, busy flag can't be checked here */

	lcd_out_high(LCD_FUNCTION_8BIT_1LINE);
	lcd_e_toggle();
	delay_us(64);		/* delay, busy flag can't be checked here */

	lcd_out_high(LCD_FUNCTION_4BIT_1LINE);	/* set IO mode to 4bit */
	lcd_e_toggle();

	/* from now the lcd only accepts 4 bit I/O, we can use lcd_command() */
#else
    /*----- Initialize lcd to 8 bit memory mapped mode ------*/

	/* enable external SRAM (memory mapped lcd) and one wait state */
	/* add  -Wl,--defsym,__init_mcucr__=0xC0 to the link command   */
	/* or uncomment the next line:                                 */
	outp((1 << SRE) | (1 << SRW), MCUCR);

	/* reset lcd */
	delay_us(16000);	/* wait 16ms after power-on     */
	lcd_write(LCD_FUNCTION_8BIT_1LINE, 0);	/* function set: 8bit interface */
	delay_us(4992);		/* wait 5ms                     */
	lcd_write(LCD_FUNCTION_8BIT_1LINE, 0);	/* function set: 8bit interface */
	delay_us(64);		/* wait 64us                    */
	lcd_write(LCD_FUNCTION_8BIT_1LINE, 0);	/* function set: 8bit interface */
	delay_us(64);		/* wait 64us                    */
#endif
	lcd_command(LCD_FUNCTION_DEFAULT);	/* function set: display lines  */
	lcd_command(LCD_DISP_OFF);	/* display off                  */
	lcd_clrscr();		/* display clear                */
	lcd_command(LCD_MODE_DEFAULT);	/* set entry mode               */
	lcd_command(dispAttr);	/* display/cursor control       */

}				/* lcd_init */