/**
 * @file
 * @brief Text and graphics output to the LCD display.
 *
 * @date 09.10.10
 * @author Nikolay Korotky
 */

#include <embox/unit.h>
#include <hal/reg.h>
#include <string.h>
#include <drivers/at91sam7s256.h>
#include <drivers/nxt/lcd.h>

EMBOX_UNIT_INIT(unit_lcd_init);

//TODO move to bsp
#define SPI_BITRATE     2000000
#define CLOCK_FREQUENCY 48054850

#define CS_PIN  (1<<10)
#define CD_PIN  (1<<12)

__u8 mode = 0xff;

__u8 display_buffer[NXT_LCD_DEPTH + 1][NXT_LCD_WIDTH];

static void spi_set_mode(__u8 m) {
	__u32 status;

	/* nothing to do if we are already in the correct mode */
	if (m == mode) {
		return;
	}

	/* Wait until all bytes have been sent */
	do {
		status = REG_LOAD(AT91C_SPI_SR);
	} while (!(status & 0x200));
	/* Set command or data mode */
	if (m) {
		REG_STORE(AT91C_PIOA_SODR, CD_PIN);
	} else {
		REG_STORE(AT91C_PIOA_CODR, CD_PIN);
	}
	/* remember the current mode */
	mode = m;
}

static void nxt_spi_write(__u32 CD, const __u8 *data, __u32 nBytes) {
	__u32 status;
	__u32 cd_mask = (CD ? 0x100 : 0);

	spi_set_mode(CD);
	while (nBytes) {
		REG_STORE(AT91C_SPI_TDR, (*data | cd_mask));
		data++;
		nBytes--;
		/* Wait until byte sent */
		do {
			status = REG_LOAD(AT91C_SPI_SR);
		} while (!(status & 0x200));
	}
}

static void nxt_lcd_command(__u8 cmd) {
	__u8 tmp = cmd;
	nxt_spi_write(0, &tmp, 1);
}

static void nxt_lcd_set_col(__u32 coladdr) {
	nxt_lcd_command(0x00 | (coladdr & 0xF));
	nxt_lcd_command(0x10 | ((coladdr >> 4) & 0xF));
}

// XXX defined but not used
#if 0
static void nxt_lcd_set_temp_comp(__u32 tc) {
	nxt_lcd_command(0x24 | (tc & 3));
}

static void nxt_lcd_set_panel_loading(__u32 hi) {
	nxt_lcd_command(0x28 | ((hi) ? 1 : 0));
}

static void nxt_lcd_set_pump_control(__u32 pc) {
	nxt_lcd_command(0x2c | (pc & 3));
}

static void nxt_lcd_set_scroll_line(__u32 sl) {
	nxt_lcd_command(0x40 | (sl & 0x3f));
}
#endif

static void nxt_lcd_set_page_address(__u32 pa) {
	nxt_lcd_command(0xB0 | (pa & 0xf));
}

// XXX defined but not used
#if 0
static void nxt_lcd_set_frame_rate(__u32 fr) {
	nxt_lcd_command(0xA0 | (fr & 1));
}

static void nxt_lcd_inverse_display(__u32 on) {
	nxt_lcd_command(0xA6 | ((on) ? 1 : 0));
}

static void nxt_lcd_set_cursor_update(__u32 on) {
	nxt_lcd_command(0xEE | ((on) ? 1 : 0));
}
#endif

static void nxt_lcd_reset(void) {
	nxt_lcd_command(0xE2);
}

static void nxt_lcd_set_multiplex_rate(__u32 mr) {
	nxt_lcd_command(0x20 | (mr & 3));
}

static void nxt_lcd_set_bias_ratio(__u32 ratio) {
	nxt_lcd_command(0xE8 | (ratio & 3));
}

static void nxt_lcd_set_pot(__u32 pot) {
	nxt_lcd_command(0x81);
	nxt_lcd_command(pot & 0xff);
}

static void nxt_lcd_set_ram_address_control(__u32 ac) {
	nxt_lcd_command(0x88 | (ac & 7));
}

static void nxt_lcd_set_map_control(__u32 map_control) {
	nxt_lcd_command(0xC0 | ((map_control & 3) << 1));
}

static void nxt_lcd_enable(__u32 on) {
	nxt_lcd_command(0xAE | ((on) ? 1 : 0));
}

/* just it's great for debuggin */
void nxt_lcd_set_all_pixels_on(__u32 on) {
	nxt_lcd_command(0xA4 | ((on) ? 1 : 0));
}

static void nxt_lcd_power_up(void) {
	//sleep(20);
	int i = 0;
	while (i<10000) {i++;}
	nxt_lcd_reset();
	//sleep(20);
	i = 0;
	while (i<10000) {i++;}
	nxt_lcd_set_multiplex_rate(3);
	nxt_lcd_set_bias_ratio(3);
	nxt_lcd_set_pot(0x60);

	nxt_lcd_set_ram_address_control(1);
	nxt_lcd_set_map_control(0x02);

	spi_set_mode(1);
	REG_STORE(AT91C_SPI_TNPR, (__u32)display_buffer);
	REG_STORE(AT91C_SPI_TNCR, 132);
	nxt_lcd_enable(1);
	//nxt_lcd_set_all_pixels_on(1);
}

void nxt_lcd_force_update(void) {
	int i;
	__u8 *disp = (__u8*) display_buffer;
	REG_STORE(AT91C_SPI_IER, AT91C_SPI_ENDTX);

	for (i = 0; i < NXT_LCD_DEPTH; i++) {
		nxt_lcd_set_col(0);
		nxt_lcd_set_page_address(i);
		nxt_spi_write(1, disp, NXT_LCD_WIDTH);
		disp += NXT_LCD_WIDTH;
	}
}

int lcd_init(void) {
	REG_STORE(AT91C_PMC_PCER, (1L << AT91C_ID_SPI)); /* Enable MCK clock */
	REG_STORE(AT91C_PIOA_PER, AT91C_PIO_PA12); /*EnableA0onPA12*/
	REG_STORE(AT91C_PIOA_OER, AT91C_PIO_PA12);
	REG_STORE(AT91C_PIOA_CODR, AT91C_PIO_PA12);
	REG_STORE(AT91C_PIOA_PDR, AT91C_PA14_SPCK); /*EnableSPCKonPA14*/

	REG_STORE(AT91C_PIOA_ASR, AT91C_PA14_SPCK);
	REG_STORE(AT91C_PIOA_ODR, AT91C_PA14_SPCK);
	REG_STORE(AT91C_PIOA_OWER, AT91C_PA14_SPCK);
	REG_STORE(AT91C_PIOA_MDDR, AT91C_PA14_SPCK);
	REG_STORE(AT91C_PIOA_PPUDR, AT91C_PA14_SPCK);
	REG_STORE(AT91C_PIOA_IFDR, AT91C_PA14_SPCK);
	REG_STORE(AT91C_PIOA_CODR, AT91C_PA14_SPCK);
	REG_STORE(AT91C_PIOA_IDR, AT91C_PA14_SPCK);
	REG_STORE(AT91C_PIOA_PDR, AT91C_PA13_MOSI); /*EnablemosionPA13*/

	REG_STORE(AT91C_PIOA_ASR, AT91C_PA13_MOSI);
	REG_STORE(AT91C_PIOA_ODR, AT91C_PA13_MOSI);
	REG_STORE(AT91C_PIOA_OWER, AT91C_PA13_MOSI);
	REG_STORE(AT91C_PIOA_MDDR, AT91C_PA13_MOSI);
	REG_STORE(AT91C_PIOA_PPUDR, AT91C_PA13_MOSI);
	REG_STORE(AT91C_PIOA_IFDR, AT91C_PA13_MOSI);
	REG_STORE(AT91C_PIOA_CODR, AT91C_PA13_MOSI);
	REG_STORE(AT91C_PIOA_IDR, AT91C_PA13_MOSI);
	REG_STORE(AT91C_PIOA_PDR, AT91C_PA10_NPCS2); /*Enablenpcs0onPA10*/

	REG_STORE(AT91C_PIOA_BSR, AT91C_PA10_NPCS2);
	REG_STORE(AT91C_PIOA_ODR, AT91C_PA10_NPCS2);
	REG_STORE(AT91C_PIOA_OWER, AT91C_PA10_NPCS2);
	REG_STORE(AT91C_PIOA_MDDR, AT91C_PA10_NPCS2);
	REG_STORE(AT91C_PIOA_PPUDR, AT91C_PA10_NPCS2);
	REG_STORE(AT91C_PIOA_IFDR, AT91C_PA10_NPCS2);
	REG_STORE(AT91C_PIOA_CODR, AT91C_PA10_NPCS2);
	REG_STORE(AT91C_PIOA_IDR, AT91C_PA10_NPCS2);

	REG_STORE(AT91C_SPI_CR, AT91C_SPI_SWRST);/*Softreset*/
	REG_STORE(AT91C_SPI_CR, AT91C_SPI_SPIEN);/*Enablespi*/
	REG_STORE(AT91C_SPI_MR, AT91C_SPI_MSTR|AT91C_SPI_MODFDIS | (0xB<<16));
	AT91C_SPI_CSR[2] = ((CLOCK_FREQUENCY/SPI_BITRATE)<<8) | AT91C_SPI_CPOL;

	nxt_lcd_power_up();
	memset(display_buffer, 0, NXT_LCD_WIDTH*NXT_LCD_DEPTH);
	return 0;
}

static int unit_lcd_init(void) {
	return lcd_init();
}
