/*! \file ks0108.c \brief Graphic LCD driver for HD61202/KS0108 displays. */
//*****************************************************************************
//
// File Name	: 'ks0108.c'
// Title		: Graphic LCD driver for HD61202/KS0108 displays
// Author		: Pascal Stang - Copyright (C) 2001-2003
// Date			: 10/19/2002
// Revised		: 5/5/2003
// Version		: 0.5
// Target MCU	: Atmel AVR
// Editor Tabs	: 4
//
// NOTE: This code is currently below version 1.0, and therefore is considered
// to be lacking in some functionality or documentation, or may not be fully
// tested.  Nonetheless, you can expect most functions to work.
//
// This code is distributed under the GNU Public License
//		which can be found at http://www.gnu.org/licenses/gpl.txt
//
//*****************************************************************************

#ifndef WIN32
// AVR specific includes
	#include <avr/io.h>
	#include <avr/interrupt.h>
#endif

#include "global.h"
#include "ks0108.h"

// global variables
GrLcdStateType GrLcdState;

/*************************************************************/
/********************** LOCAL FUNCTIONS **********************/
/*************************************************************/

void glcdInitHW(void)
{
	// initialize I/O ports
	// if I/O interface is in use
#ifdef GLCD_PORT_INTERFACE

	//TODO: make setup of chip select lines contingent on how
	// many controllers are actually in the display
	
	// initialize LCD control lines levels
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_RS);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_RW);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_E);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_CS0);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_CS1);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_CS2);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_CS3);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_RESET);
	// initialize LCD control port to output
	sbi(GLCD_CTRL_DDR, GLCD_CTRL_RS);
	sbi(GLCD_CTRL_DDR, GLCD_CTRL_RW);
	sbi(GLCD_CTRL_DDR, GLCD_CTRL_E);
	sbi(GLCD_CTRL_DDR, GLCD_CTRL_CS0);
	sbi(GLCD_CTRL_DDR, GLCD_CTRL_CS1);
	sbi(GLCD_CTRL_DDR, GLCD_CTRL_CS2);
	sbi(GLCD_CTRL_DDR, GLCD_CTRL_CS3);
	sbi(GLCD_CTRL_DDR, GLCD_CTRL_RESET);
	// initialize LCD data
	outb(GLCD_DATA_PORT, 0x00);
	// initialize LCD data port to output
	outb(GLCD_DATA_DDR, 0xFF);
#endif
}

void glcdControllerSelect(u08 controller)
{
#ifdef GLCD_PORT_INTERFACE
	//TODO: make control of chip select lines contingent on how
	// many controllers are actually in the display

	// unselect all controllers
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_CS0);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_CS1);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_CS2);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_CS3);

	// select requested controller
	switch(controller)
	{
	case 0:	sbi(GLCD_CTRL_PORT, GLCD_CTRL_CS0); break;
	case 1:	sbi(GLCD_CTRL_PORT, GLCD_CTRL_CS1); break;
	case 2:	sbi(GLCD_CTRL_PORT, GLCD_CTRL_CS2); break;
	case 3:	sbi(GLCD_CTRL_PORT, GLCD_CTRL_CS3); break;
	default: break;
	}
#endif
}

void glcdBusyWait(u08 controller)
{
#ifdef GLCD_PORT_INTERFACE
	cli();
	// wait until LCD busy bit goes to zero
	// select the controller chip
	glcdControllerSelect(controller);
	// do a read from control register
	outb(GLCD_DATA_PORT, 0xFF);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_RS);
	outb(GLCD_DATA_DDR, 0x00);
	sbi(GLCD_CTRL_PORT, GLCD_CTRL_RW);
	sbi(GLCD_CTRL_PORT, GLCD_CTRL_E);
	asm volatile ("nop"); asm volatile ("nop");
	while(inb(GLCD_DATA_PIN) & GLCD_STATUS_BUSY)
	{
		cbi(GLCD_CTRL_PORT, GLCD_CTRL_E);
		asm volatile ("nop"); asm volatile ("nop");
		asm volatile ("nop"); asm volatile ("nop");
		sbi(GLCD_CTRL_PORT, GLCD_CTRL_E);
		asm volatile ("nop"); asm volatile ("nop");
		asm volatile ("nop"); asm volatile ("nop");
	}
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_E);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_RW);
	outb(GLCD_DATA_DDR, 0xFF);
	sei();
#else
	// sbi(MCUCR, SRW);			// enable RAM waitstate
	// wait until LCD busy bit goes to zero
	while(*(volatile unsigned char *)
		(GLCD_CONTROLLER0_CTRL_ADDR + GLCD_CONTROLLER_ADDR_OFFSET*controller) & GLCD_STATUS_BUSY);
	// cbi(MCUCR, SRW);			// disable RAM waitstate
#endif
}

void glcdControlWrite(u08 controller, u08 data)
{
#ifdef GLCD_PORT_INTERFACE
	cli();
	glcdBusyWait(controller);	// wait until LCD not busy
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_RS);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_RW);
	sbi(GLCD_CTRL_PORT, GLCD_CTRL_E);
	outb(GLCD_DATA_DDR, 0xFF);
	outb(GLCD_DATA_PORT, data);
	asm volatile ("nop"); asm volatile ("nop");
	asm volatile ("nop"); asm volatile ("nop");
	asm volatile ("nop"); asm volatile ("nop");
	asm volatile ("nop"); asm volatile ("nop");
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_E);
	sei();
#else
	//sbi(MCUCR, SRW);				// enable RAM waitstate
	glcdBusyWait(controller);		// wait until LCD not busy
	*(volatile unsigned char *) (GLCD_CONTROLLER0_CTRL_ADDR + GLCD_CONTROLLER_ADDR_OFFSET*controller) = data;
	//cbi(MCUCR, SRW);				// disable RAM waitstate
#endif
}

u08 glcdControlRead(u08 controller)
{
	register u08 data;
#ifdef GLCD_PORT_INTERFACE
	cli();
	glcdBusyWait(controller);		// wait until LCD not busy
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_RS);
	outb(GLCD_DATA_DDR, 0x00);
	sbi(GLCD_CTRL_PORT, GLCD_CTRL_RW);
	sbi(GLCD_CTRL_PORT, GLCD_CTRL_E);
	asm volatile ("nop"); asm volatile ("nop");
	asm volatile ("nop"); asm volatile ("nop");
	asm volatile ("nop"); asm volatile ("nop");
	asm volatile ("nop"); asm volatile ("nop");
	data = inb(GLCD_DATA_PIN);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_E);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_RW);
	outb(GLCD_DATA_DDR, 0xFF);
	sei();
#else
	//sbi(MCUCR, SRW);				// enable RAM waitstate
	glcdBusyWait(controller);		// wait until LCD not busy
	data = *(volatile unsigned char *) (GLCD_CONTROLLER0_CTRL_ADDR + GLCD_CONTROLLER_ADDR_OFFSET*controller);
	//cbi(MCUCR, SRW);				// disable RAM waitstate
#endif
	return data;
}

void glcdDataWrite(u08 data)
{
	register u08 controller = (GrLcdState.lcdXAddr/GLCD_CONTROLLER_XPIXELS);
#ifdef GLCD_PORT_INTERFACE
	cli();
	glcdBusyWait(controller);		// wait until LCD not busy
	sbi(GLCD_CTRL_PORT, GLCD_CTRL_RS);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_RW);
	sbi(GLCD_CTRL_PORT, GLCD_CTRL_E);
	outb(GLCD_DATA_DDR, 0xFF);
	outb(GLCD_DATA_PORT, data);
	asm volatile ("nop"); asm volatile ("nop");
	asm volatile ("nop"); asm volatile ("nop");
	asm volatile ("nop"); asm volatile ("nop");
	asm volatile ("nop"); asm volatile ("nop");
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_E);
	sei();
#else
	//sbi(MCUCR, SRW);				// enable RAM waitstate
	glcdBusyWait(controller);		// wait until LCD not busy
	*(volatile unsigned char *) (GLCD_CONTROLLER0_CTRL_ADDR + GLCD_CONTROLLER_ADDR_OFFSET*controller) = data;
	//cbi(MCUCR, SRW);				// disable RAM waitstate
#endif
	// increment our local address counter
	GrLcdState.ctrlr[controller].xAddr++;
	GrLcdState.lcdXAddr++;
	if(GrLcdState.lcdXAddr >= GLCD_XPIXELS)
	{
		GrLcdState.lcdYAddr++;
		glcdSetYAddress(GrLcdState.lcdYAddr);
		glcdSetXAddress(0);
	}
}

u08 glcdDataRead(void)
{
	register u08 data;
	register u08 controller = (GrLcdState.lcdXAddr/GLCD_CONTROLLER_XPIXELS);
#ifdef GLCD_PORT_INTERFACE
	cli();
	glcdBusyWait(controller);		// wait until LCD not busy
	sbi(GLCD_CTRL_PORT, GLCD_CTRL_RS);
	outb(GLCD_DATA_DDR, 0x00);
	sbi(GLCD_CTRL_PORT, GLCD_CTRL_RW);
	sbi(GLCD_CTRL_PORT, GLCD_CTRL_E);
	asm volatile ("nop"); asm volatile ("nop");
	asm volatile ("nop"); asm volatile ("nop");
	asm volatile ("nop"); asm volatile ("nop");
	asm volatile ("nop"); asm volatile ("nop");
	data = inb(GLCD_DATA_PIN);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_E);
	cbi(GLCD_CTRL_PORT, GLCD_CTRL_RW);
	sei();
#else
	//sbi(MCUCR, SRW);				// enable RAM waitstate
	glcdBusyWait(controller);		// wait until LCD not busy
	data = *(volatile unsigned char *) (GLCD_CONTROLLER0_CTRL_ADDR + GLCD_CONTROLLER_ADDR_OFFSET*controller);
	//cbi(MCUCR, SRW);				// disable RAM waitstate
#endif
	// increment our local address counter
	GrLcdState.ctrlr[controller].xAddr++;
	GrLcdState.lcdXAddr++;
	if(GrLcdState.lcdXAddr >= GLCD_XPIXELS)
	{
		GrLcdState.lcdYAddr++;
		glcdSetYAddress(GrLcdState.lcdYAddr);
		glcdSetXAddress(0);
	}
	return data;
}

void glcdReset(u08 resetState)
{
	// reset lcd if argument is true
	// run lcd if argument is false
#ifdef GLCD_PORT_INTERFACE
	if(resetState)
		cbi(GLCD_CTRL_PORT, GLCD_CTRL_RESET);
	else
		sbi(GLCD_CTRL_PORT, GLCD_CTRL_RESET);
#endif
}

void glcdSetXAddress(u08 xAddr)
{
	u08 i;
	// record address change locally
	GrLcdState.lcdXAddr = xAddr;
	
	// clear y (col) address on all controllers
	for(i=0; i<GLCD_NUM_CONTROLLERS; i++)
	{
		glcdControlWrite(i, GLCD_SET_Y_ADDR | 0x00);
		GrLcdState.ctrlr[i].xAddr = 0;
	}

	// set y (col) address on destination controller
	glcdControlWrite((GrLcdState.lcdXAddr/GLCD_CONTROLLER_XPIXELS),
		GLCD_SET_Y_ADDR | (GrLcdState.lcdXAddr & 0x3F));
}

void glcdSetYAddress(u08 yAddr)
{
	u08 i;
	// record address change locally
	GrLcdState.lcdYAddr = yAddr;
	// set page address for all controllers
	for(i=0; i<GLCD_NUM_CONTROLLERS; i++)
	{
		glcdControlWrite(i, GLCD_SET_PAGE | yAddr);
	}
}

/*************************************************************/
/********************* PUBLIC FUNCTIONS **********************/
/*************************************************************/

void glcdInit()
{
	u08 i;
	// initialize hardware
	glcdInitHW();
	// bring lcd out of reset
	glcdReset(FALSE);
	// Turn on LCD
	for(i=0; i<GLCD_NUM_CONTROLLERS; i++)
	{
		glcdControlWrite(i, GLCD_ON_CTRL | GLCD_ON_DISPLAY);
	}
	// clear lcd
	glcdClearScreen();
	// initialize positions
	glcdHome();
}

void glcdHome(void)
{
	u08 i;
	// initialize addresses/positions
	glcdStartLine(0);
	glcdSetAddress(0,0);
	// initialize local data structures
	for(i=0; i<GLCD_NUM_CONTROLLERS; i++)
	{
		GrLcdState.ctrlr[i].xAddr = 0;
		GrLcdState.ctrlr[i].yAddr = 0;
	}
}

void glcdClearScreen(void)
{
	u08 pageAddr;
	u08 xAddr;

	// clear LCD
	// loop through all pages
	for(pageAddr=0; pageAddr<(GLCD_YPIXELS>>3); pageAddr++)
	{
		// set page address
		glcdSetAddress(0, pageAddr);
		// clear all lines of this page of display memory
		for(xAddr=0; xAddr<GLCD_XPIXELS; xAddr++)
		{
			glcdDataWrite(0x00);
		}
	}
}

void glcdStartLine(u08 start)
{
	glcdControlWrite(0, GLCD_START_LINE | start);
	glcdControlWrite(1, GLCD_START_LINE | start);
}

void glcdSetAddress(u08 x, u08 yLine)
{
	// set addresses
	glcdSetYAddress(yLine);
	glcdSetXAddress(x);
}

void glcdGotoChar(u08 line, u08 col)
{
	glcdSetAddress(col*6, line);
}

void glcdDelay(u16 p)			// 1-8us      ...2-13us     ...5-31us
{								// 10-60us    ...50-290us
	unsigned int i;				// 100-580us  ...500-2,9ms
	unsigned char j; 			// 1000-5,8ms ...5000-29ms
								// 10000-56ms ...30000-170ms
								// 50000-295ms...60000-345ms
//	for (i = 0; i < p; i++) for (j = 0; j < 10; j++) asm volatile ("nop");
	for (i = 0; i < p; i++) for (j = 0; j < 10; j++);
}


// Higher level functionality has been moved to the API-layer glcd.c/glcd.h
