#include "stb225_gpio.h"
#include "asm/arch-pnx8330/regaccess.h"
#include "asm/types.h"
#include "common.h"

#define CHECK_BUSY 0

#define LCD_E 24
#define LCD_RW 50
#define LCD_RS 49
#define LCD_CONTRAST 17
#define LCD_DB 34
#define LCD_BUSY 46
#define LCD_BACKLIGHT 16

#define CONTRAST_FREQ 8
#define CONTRAST_LEVEL 6

#define PNX833X_PIO_SEL         PNX833X_REG(0xF014)
#define PNX833X_PIO_DIR         PNX833X_REG(0xF008)
#define PNX833X_PIO_IN          PNX833X_REG(0xF000)
#define PNX833X_PIO_OUT         PNX833X_REG(0xF004)

#define PNX833X_PIO_SEL2        PNX833X_REG(0xF068)
#define PNX833X_PIO_DIR2        PNX833X_REG(0xF064)
#define PNX833X_PIO_IN2         PNX833X_REG(0xF05C)
#define PNX833X_PIO_OUT2        PNX833X_REG(0xF060)

#define PNX833X_FG6_PRESET      PNX833X_REG(0xF084)
#define PNX833X_FG_FREQ_SEL2    PNX833X_REG(0xF0A0)
#define PNX833X_FG_DATA_SEL     PNX833X_REG(0xF0A4)

static int lcd_enable_busy_check;

static void ndelay(u32 ns)
{
	udelay((ns+999)/1000);
}

static inline void lcd_gpio_set(u32 pin)
{
	gpio_set_high(pin);
}

static inline void lcd_gpio_clear(u32 pin)
{
	gpio_set_low(pin);
}

static inline void lcd_gpio2_out(u32 bits, u32 value)
{
	u32 reg;
	
	reg = PNX833X_PIO_OUT2;
	reg &= ~bits;
	reg |= value;
	PNX833X_PIO_OUT2 = reg;
}

static inline void lcd_gpio2_dir(u32 bits, u32 value)
{
	u32 reg;
	
	reg = PNX833X_PIO_DIR2;
	reg &= ~bits;
	reg |= value;
	PNX833X_PIO_DIR2 = reg;
}

static inline void lcd_gpio_dir(u32 bits, u32 value)
{
	u32 reg;
	
	reg = PNX833X_PIO_DIR;
	reg &= ~bits;
	reg |= value;
	PNX833X_PIO_DIR = reg;
}

static inline int lcd_gpio_read(u32 pin)
{
	return gpio_get_value(pin);
}

static inline void lcd_write_data(u8 data)
{
	lcd_gpio2_out(0xff << (LCD_DB - 32), data << (LCD_DB - 32));
}

#define CMD(x) ((1<<8)|(x))
#define IS_CMD(x) (!!((x) & (1<<8)))

#define CMD_FUNCTION_SET CMD(0x38)
#define CMD_CLEAR CMD(0x01)
#define CMD_HOME CMD(0x02)

// *) Write command=0x0C (Display control Bit 3=1, Bit 2(D)=1 for display on, bit 1(C)=0 for cursor off,  Bit 0(B)=0 for blinking off
#define CMD_DISPLAY_ON CMD(0x0c)

#define CMD_ENTRY_MODE_NORMAL CMD(0x06)


static void lcd_send(u32 value)
{
	if (lcd_enable_busy_check)
	{
		int i;
		int busy = 1;
		lcd_gpio_set(LCD_RW);
		for(i = 0; busy && (i != 500); ++i)
		{
			lcd_gpio_set(LCD_E);
			ndelay(450);
			busy = lcd_gpio_read(LCD_BUSY);
			lcd_gpio_clear(LCD_E);
			ndelay(550);
		}
		
		if (busy)
			debug("LCD is still busy!\n");
		lcd_gpio_clear(LCD_RW);	
	}
	
	if (IS_CMD(value)) {
		debug("Send COMMAND %02x\n", value & 0xff);
		lcd_gpio_clear(LCD_RS);
	}
	else {
		debug("Send DATA %02x\n", value & 0xff);
		lcd_gpio_set(LCD_RS);
	}

	lcd_write_data(value & 0xff);
	ndelay(100);
	lcd_gpio_set(LCD_E);
	ndelay(450);
	lcd_gpio_clear(LCD_E);
	ndelay(450);
	lcd_gpio_clear(LCD_RS);

	if (!lcd_enable_busy_check)
	{
		switch (value)
		{
			case CMD_CLEAR:
			case CMD_HOME:
				udelay(1640);
				break;
			default:
				udelay(40);
				break;
		}
	}
}

static void lcd_write_line(u8 address, const char *str)
{
	const char *p;

	lcd_send(CMD(address));
	for(p = str; *p; ++p)
		lcd_send(*p);
}

static void lcd_check_workaround(void)
{
	// On REVA boards GPIO49 and GPIO50 are tied together. Let's
	// try and work out if that is the case.
	gpio_set_as_output(LCD_RS, 0);
	gpio_set_as_input(LCD_RW);

	// On REVB boards LCD_RW is pulled up so let's force LCD_RS
	// low and see if LCD_RW follows. If it stays high then it
	// must be independent.
	if (!lcd_gpio_read(LCD_RW)) {
		// Mojito LCD has shorted RW pin
		lcd_enable_busy_check = 0;
		return;
	}

	// We can use LCD_RW then.
	gpio_set_as_output(LCD_RW, 0);
	lcd_enable_busy_check = 1;
}

void init_mojito_lcd(void)
{
	// Configure our GPIOs how we need them.
	u64 outputs = (1ULL<<LCD_E)|(1ULL<<LCD_RS)|(1ULL<LCD_BACKLIGHT)|(0xffULL<<LCD_DB);
	lcd_gpio2_dir(outputs >> 32, outputs >> 32);
	lcd_gpio_dir(outputs & 0xffffffff, outputs & 0xffffffff);

	lcd_check_workaround();
	
	// The contrast is driven by PWM
	gpio_set_as_function(LCD_CONTRAST);
	{
		u32 reg;
		u32 pattern = (1<<CONTRAST_LEVEL) - 1;
#if LCD_CONTRAST == 17
		reg = PNX833X_FG_FREQ_SEL2;
		reg &= 0x1f;
		reg |= CONTRAST_FREQ & 0x1f;
		PNX833X_FG_FREQ_SEL2 = reg;
#else
#error Unsupported contrast pin
#endif
		// Set up the data source to feed in from the shift register.
		reg = PNX833X_FG_DATA_SEL;
		reg &= (3 << (LCD_CONTRAST - 11)*2);
		PNX833X_FG_DATA_SEL = reg;
		
		PNX833X_FG6_PRESET = pattern;
	}

	
	// *) Write command=0x38 (Function set Bit 5=1, Bit 4(DL)=1 for 8 bit, bit 3(N)=1 for 2 line,  Bit 2(F)=0 for 5X8 dots
	lcd_send(CMD_FUNCTION_SET);
	udelay(4100-40);
	lcd_send(CMD_FUNCTION_SET);
	udelay(100-40);
	lcd_send(CMD_FUNCTION_SET); // We can rely on the busy check from now onwards
	udelay(100-40);
	lcd_send(CMD_DISPLAY_ON);

	lcd_send(CMD_CLEAR);
	lcd_send(CMD_ENTRY_MODE_NORMAL);
	
	lcd_write_line(0x82, "Initializing");
	lcd_write_line(0xc5, "Device");
}
