#include <msp430.h>
#include <stdint.h>

#include "mw_main.h"

#include "mw_lcd.h"
#include "mw_uart.h"

// SMCLK = 16MHz -> divide by 16 to get 1 MHz SPI clock,
// 1MHz maximum according to LCD spec
//#define SPI_PRESCALE_L		0x10
// currently we only run @1MHz
#define SPI_PRESCALE_L		0x10
#define SPI_PRESCALE_H		0x00

#define LCD_STATIC_CMD		0x00
#define LCD_WRITE_CMD		0x01
#define LCD_CLEAR_CMD		0x04     

static const unsigned char LCD_CLEAR_COMMAND[] = {LCD_CLEAR_CMD, 0x00};
#define LCD_CLEAR_CMD_SIZE	0x02
static const unsigned char LCD_STATIC_COMMAND[] = {LCD_STATIC_CMD, 0x00};
#define LCD_STATIC_CMD_SIZE	0x02


/* the LCD frame buffer, 96 lines */
tLcdLine lcd_buf[96];


#define LCD_DMA
/* errata - DMA variables cannot be function scope */
#ifdef LCD_DMA
static unsigned char LcdDmaBusy = 0;
#endif


void memfill(void *target, unsigned char val, unsigned int count)
{
	while (count--) {
		*(unsigned char *)(target+count) = val;
	}
}

void mw_lcd_init(void)
{
	int i;

	/* basic I/O setup */
	ENABLE_LCD_POWER();
	CONFIG_LCD_PINS();

	// DISABLE_LCD_ENABLE();
	ENABLE_LCD_ENABLE();

	/* Put state machine in reset while it is configured */
	LCD_SPI_UCBxCTL1 |= UCSWRST;

	/* 
	 * 3-pin, 8-bit SPI master, Clock polarity low
	 * Clock phase set, least sig bit first
	 * SMCLK is the clock source
	 * set the clock prescaler
	 */
	LCD_SPI_UCBxCTL0 |= UCMST+ UCCKPH + UCSYNC;      

	LCD_SPI_UCBxCTL1 |= UCSSEL_2;                    
	LCD_SPI_UCBxBR0 = SPI_PRESCALE_L;               
	LCD_SPI_UCBxBR1 = SPI_PRESCALE_H;               

	/* remove reset */
	LCD_SPI_UCBxCTL1 &= ~UCSWRST;

	/* pre-fill the frame-buffer */
	for (i=0; i<96; i++) {
		lcd_buf[i].Row = i+1;
		memfill(lcd_buf[i].Data, 0xff, 12);
		// lcd_buf[i].Data[0] = i;
		lcd_buf[i].Dummy = 0x00;
	};
}

static void mw_lcd_write_line(const void *pData, unsigned char Size)
{
#ifndef xLCD_DMA
	unsigned char Index;
#endif

	LCD_CS_ASSERT();
  
#ifdef xLCD_DMA
	LcdDmaBusy = 1;

	/* USCIB0 TXIFG is the DMA trigger
	 * DMACTL1 controls dma2 and [dma3]
	 */
	DMACTL1 = DMA2TSEL_19;    
    
	DMA2SA = (unsigned int) pData;
	DMA2DA = (unsigned int) &LCD_SPI_UCBxTXBUF;
            
	DMA2SZ = (unsigned int)Size;
  
	/* 
	 * single transfer, increment source address, source byte and dest byte,
	 * level sensitive, enable interrupt, clear interrupt flag
	 */
	DMA2CTL = DMADT_0 + DMASRCINCR_3 + DMASBDB + DMALEVEL + DMAIE;  
  
	/* start the transfer */
	DMA2CTL |= DMAEN;
  
	while (LcdDmaBusy)
		nop();
#else
//	debug_uart_tx("+wl1");
	for ( Index = 0; Index < Size; Index++ ) {
		LCD_SPI_UCBxTXBUF = ((unsigned char *)pData)[Index];
//		debug_uart_tx(".");
		while (!(LCD_SPI_UCBxIFG & UCTXIFG)) {
//			debug_uart_tx("+");
			nop();
		}
	}
//	debug_uart_tx("\n+wl2\n");
#endif
    
	/* wait for shift to complete ( ~3 us ) */
	while (LCD_SPI_UCBxSTAT & 0x01) {
		nop();
//		debug_uart_tx(".");
	};

	/* now the chip select can be deasserted */
	LCD_CS_DEASSERT();
//	debug_uart_tx("\n-wl\n");
}


void mw_lcd_static_mode(void)
{
	mw_lcd_write_line(LCD_STATIC_COMMAND, LCD_STATIC_CMD_SIZE);   
}

void mw_lcd_clear(void)
{
	unsigned char i;

	mw_lcd_write_line(LCD_CLEAR_COMMAND, LCD_CLEAR_CMD_SIZE);

	/* pre-fill the frame-buffer */
	for (i=0; i<96; i++) {
		memfill(lcd_buf[i].Data, 0xff, 12);
	};
}

void mw_lcd_clear_fb(void)
{
	unsigned char i;

	/* pre-fill the frame-buffer */
	for (i=0; i<96; i++) {
#if LCD_BLACK == 0
		memfill(lcd_buf[i].Data, 0xff, 12);
#else
		memfill(lcd_buf[i].Data, 0x00, 12);
#endif
	};
}

#pragma vector=DMA_VECTOR
__interrupt void DMA_ISR (void)
{
	switch (DMAIV) {
	case 0:
		DMA0CTL &= ~DMAEN;	// disable
		DMA0CTL &= ~DMAIFG;	// clear IRQ flag
		debug_uart_tx("DMA0 IRQ\n");
		break;
	case 2:
		debug_uart_tx("DMA1 IRQ\n");
		break;
	case 4:
		debug_uart_tx("DMA2b IRQ\n");
		break;
	case 6:
		DMA2CTL &= ~DMAEN;	// disable
		DMA2CTL &= ~DMAIFG;	// clear IRQ flag
#ifdef LCD_DMA
		LcdDmaBusy = 0;
#endif
		//LED7_TOGGLE();
		// debug_uart_tx("DMA2 IRQ\n");
		break;
	}
}

/* writes the complete internal framebuffer to the LCD */
void mw_lcd_update_screen(void)
{
//#ifndef LCD_DMA
	unsigned int i,j;
//#endif

//	debug_uart_tx("uscr1\n");

	// invert the buffer
	if (0) {
		for (i=0; i<96; i++) {
			for ( j = 0; j < 12; j++ ) {
				lcd_buf[i].Data[j] = ~lcd_buf[i].Data[j];
			}
		}
	}

	LCD_CS_ASSERT();

	/* send WRITE command */
	LCD_SPI_UCBxTXBUF = LCD_WRITE_CMD;
	while (!(LCD_SPI_UCBxIFG & UCTXIFG))
		nop();

//	debug_uart_tx("uscr2\n");

#ifdef LCD_DMA
	LcdDmaBusy = 1;
  
	/* USCIB0 TXIFG is the DMA trigger
	 * DMACTL1 controls dma2 and [dma3]
	 */
	DMACTL1 = DMA2TSEL_19;    

	DMA2SA = (unsigned int) lcd_buf;
	DMA2DA = (unsigned int) &LCD_SPI_UCBxTXBUF;
            
	DMA2SZ = 96 * sizeof(tLcdLine);
  
	/* 
	 * single transfer, increment source address, source byte and dest byte,
	 * level sensitive, enable interrupt, clear interrupt flag
	 */
	DMA2CTL = DMADT_0 + DMASRCINCR_3 + DMASBDB + DMALEVEL + DMAIE;  
  
	/* start the transfer */
	DMA2CTL |= DMAEN;

//	debug_uart_tx("uscr3\n");

	// LED7_OFF();
	while (LcdDmaBusy)
		nop();

//	debug_uart_tx("uscr4\n");
#else
	for (i=0; i<96; i++) {
		for ( j = 0; j < sizeof(tLcdLine); j++ ) {
			LCD_SPI_UCBxTXBUF = ((unsigned char *)(&lcd_buf[i]))[j];
			while (!(LCD_SPI_UCBxIFG & UCTXIFG))
				nop();
		}
	}
#endif

	/* send final trailer byte */
	LCD_SPI_UCBxTXBUF = 0x00;
	while (!(LCD_SPI_UCBxIFG & UCTXIFG))
		nop();

//	debug_uart_tx("uscr5\n");
	/* wait for shift to complete ( ~3 us ) */
	while (LCD_SPI_UCBxSTAT & 0x01)
		nop();

//	debug_uart_tx("uscr6\n");
	LCD_CS_DEASSERT();

	mw_lcd_static_mode();
}

void mw_lcd_draw_pixel(const uint8_t x, const uint8_t y, const uint8_t color)
{
	switch (color) {
		case 1:
			lcd_buf[y].Data[x/8] |= 1 << (x % 8);
			break;
		case 0:
			lcd_buf[y].Data[x/8] &= ~(1 << (x % 8));
			break;
		case 2:
			lcd_buf[y].Data[x/8] ^= 1 << (x % 8);
			break;
	}
}