/*
 * This file is subject to the terms of the GFX License. If a copy of
 * the license was not distributed with this file, you can obtain one at:
 *
 *              http://ugfx.org/license.html
 */

#include "gfx.h"

#if GFX_USE_GDISP

#define GDISP_DRIVER_VMT			GDISPVMT_UC8173
#include "gdisp_lld_config.h"
#include "../../../src/gdisp/gdisp_driver.h"
#include "UC8173.h"
#include "board_UC8173.h"

#if defined(GDISP_SCREEN_WIDTH)
	#warning "GDISP: This low level driver does not support setting a screen size. It is being ignored."
	#define GDISP_SCREEN_WIDTH		240
#endif
#if defined(GDISP_SCREEN_HEIGHT)
	#warning "GDISP: This low level driver does not support setting a screen size. It is being ignored."
	#define GDISP_SCREEN_HEIGHT		240
#endif

#ifndef GDISP_SCREEN_HEIGHT
	#define GDISP_SCREEN_HEIGHT		240
#endif
#ifndef GDISP_SCREEN_WIDTH
	#define GDISP_SCREEN_WIDTH		240
#endif

#define PRIV(g)						((UC8173_Private*)((g)->priv))
#define FRAMEBUFFER(g)				((uint8_t *)(PRIV(g)+1))
#define GDISP_FLG_NEEDFLUSH			(GDISP_FLG_DRIVER << 0)

#if GDISP_LLD_PIXELFORMAT == GDISP_PIXELFORMAT_MONO
	#define LINE_BYTES					(GDISP_SCREEN_WIDTH/8)
	#define WRITEBUFCMD					DTM4
	#define xyaddr(x, y)				(((x)>>3) + ((y) * LINE_BYTES))
	//#define xybit(x, c)				((c) << ((x) & 7))			// This one has the wrong order of the pixels inside the byte
	#define xybit(x, c)					((c) << (7-((x) & 7)))
#elif  GDISP_LLD_PIXELFORMAT == GDISP_PIXELFORMAT_GRAY4
	#define LINE_BYTES					(GDISP_SCREEN_WIDTH/4)
	#define WRITEBUFCMD					DTM2						// NOT SURE THIS IS RIGHT - MAY NEED TO USE DTM0 and then send a refresh???
	#define xyaddr(x, y)				(((x)>>2) + ((y) * LINE_BYTES))
	//#define xybit(x, c)				((c) << (((x) & 3)<<1))		// This one has the wrong order of the pixels inside the byte
	#define xybit(x, c)					((c) << (6-((x) & 3)<<1))
#else
	#error "UC8173: Unsupported driver color format"
#endif

typedef struct UC8173_Private {
	coord_t flushWindowX;
	coord_t flushWindowY;
	coord_t flushWindowWidth;
	coord_t flushWindowHeight;
} UC8173_Private;

// This function rounds a given integer up to a specified multiple. Note, multiple must be a power of 2!
static GFXINLINE void _roundUp(coord_t* numToRound, uint8_t multiple)
{
	*numToRound = (*numToRound + multiple - 1) & ~(multiple - 1);
}

static GFXINLINE void _wait_for_busy_high(GDisplay* g)
{
	while (!getpin_busy(g));
}

static GFXINLINE void _wait_for_busy_low(GDisplay* g)
{
	while (getpin_busy(g));
}

void _load_lut(GDisplay* g, uint32_t lutRegister, const uint8_t* lut, uint32_t lutCounter)
{
	uint32_t i;

	write_cmd(g, lutRegister);
	for (i = 0; i < lutCounter; i++) {
		write_data(g, *lut);
		lut++;
	}
}

static void _upload_lut(GDisplay* g)
{
	_load_lut(g, LUT_KWVCOM, _lut_KWvcom_DC_A2_240ms, 32);
	_load_lut(g, LUT_KW, _lut_kw_A2_240ms, 512);
	_load_lut(g, LUT_FT, _lut_ft, 128);
}

static void _clear_lut(GDisplay* g)
{
	write_cmd(g, PON);
	_wait_for_busy_high(g);

	_load_lut(g, LUT_KW, _lut_None, 42);
	_load_lut(g, LUT_KWVCOM, _lut_None, 42);

    write_cmd(g, POF); 
    _wait_for_busy_low(g);
}

#if GDISP_NEED_CONTROL && GDISP_HARDWARE_CONTROL
	static void _invertFramebuffer(GDisplay* g)
	{
		uint32_t i;

		for (i = 0; i < LINE_BYTES*GDISP_SCREEN_HEIGHT; i++) {
			FRAMEBUFFER(g)[i] = ~(FRAMEBUFFER(g)[i]);
		}
		
		// We should flush these changes to the display controller framebuffer at some point
		g->flags |= GDISP_FLG_NEEDFLUSH;
	}
#endif

LLDSPEC bool_t gdisp_lld_init(GDisplay* g)
{
	// Allocate the private area plus the framebuffer
	g->priv = gfxAlloc(sizeof(UC8173_Private) + LINE_BYTES*GDISP_SCREEN_HEIGHT);
	if (!g->priv) {
		return FALSE;
	}

	// Initialize the private area
	PRIV(g)->flushWindowX = 0;
	PRIV(g)->flushWindowY = 0;
	PRIV(g)->flushWindowWidth = GDISP_SCREEN_WIDTH;
	PRIV(g)->flushWindowHeight = GDISP_SCREEN_HEIGHT;

	// Initialise the board interface
	if (!init_board(g)) {
		return FALSE;
	}

	// Hardware reset
	setpin_reset(g, FALSE);
	gfxSleepMilliseconds(100);
	setpin_reset(g, TRUE);
	gfxSleepMilliseconds(1000);

	// Acquire the bus
	acquire_bus(g);

	// Booster soft-start
	write_cmd(g, BTST);
	write_data(g, 0x17);   //0x17
	write_data(g, 0x97);   //0x97
	write_data(g, 0x20);   //0x20
  
	// Power settings
	write_cmd(g, PWR);
	write_data(g, 0x03);
	write_data(g, 0x00);
	write_data(g, 0x2B);  //1C 2B
	write_data(g, 0x2B);  //1C 2B
	write_data(g, 0x00);

	// Power-on
	write_cmd(g, PON);
	_wait_for_busy_high(g);

	// Panel setting register
	write_cmd(g, PSR);
	write_data(g, 0x0F);  //0x0B
	write_data(g, 0x86);  //0x86

	// Power-off sequence
	write_cmd(g, PFS);
	write_data(g, 0x00);

	// PLL control
	write_cmd(g, LPRD);
	write_data(g, 0x25);

	// Internal temperature sensor enable
	write_cmd(g, TSE);
	write_data(g, 0x00);		// Use internal temperature sensor

	// VCOM and data interval settings
	write_cmd(g, CDI);
	write_data(g, 0xE1);
	write_data(g, 0x20);
	write_data(g, 0x10);
  
	// Set display panel resolution
	write_cmd(g, TRES);
	write_data(g, 0xEF);
	write_data(g, 0x00);
	write_data(g, 0xEF);

	// Undocumented register, taken from sample code
	write_cmd(g, GDS);
	write_data(g, 0xA9);
	write_data(g, 0xA9);
	write_data(g, 0xEB);
	write_data(g, 0xEB);
	write_data(g, 0x02);

	// Auto measure VCOM
	write_cmd(g, AMV);
	write_data(g, 0x11);		// 5 seconds, enabled
	_wait_for_busy_high(g);

	// Get current VCOM value
	// write_cmd(g, VV);
	// unsigned char vcom_temp = spi_9b_get();
	// vcom_temp = vcom_temp + 4;
	// Auto_VCOM = vcom_temp;

	// VCM_DC setting
	write_cmd(g, VDCS);
	write_data(g, 0x12);		// Write vcom_temp here
  
	// Undocumented register, taken from sample code
	write_cmd(g, VBDS);
	write_data(g, 0x22);
  
	// Undocumented register, taken from sample code
	write_cmd(g, LVSEL);
	write_data(g, 0x02);
  
	// Undocumented register, taken from sample code
	write_cmd(g, GBS);
	write_data(g, 0x02);
	write_data(g, 0x02);
  
	// Undocumented register, taken from sample code
	write_cmd(g, GSS);
	write_data(g, 0x02);
	write_data(g, 0x02);
  
	// Undocumented register, taken from sample code
	write_cmd(g, DF);			// For REGAL (???)
	write_data(g, 0x1F);
	
	// Clear the look-up table
	_clear_lut(g);	
	
	// Finish Init
	post_init_board(g);

 	// Release the bus
	release_bus(g);

	// Initialise the GDISP structure
	g->g.Width = GDISP_SCREEN_WIDTH;
	g->g.Height = GDISP_SCREEN_HEIGHT;
	g->g.Orientation = GDISP_ROTATE_0;
	g->g.Powermode = powerOn;
	g->g.Backlight = 0;
	g->g.Contrast = 0;

	return TRUE;
}

#if GDISP_HARDWARE_FLUSH
	LLDSPEC void gdisp_lld_flush(GDisplay* g)
	{
		coord_t y;

		// Don't flush unless we really need to
		if (!(g->flags & GDISP_FLG_NEEDFLUSH)) {
			return;
		}
		
		// Round the flushing window width and height up to the next multiple of four
		_roundUp(&(PRIV(g)->flushWindowWidth), 4);
		_roundUp(&(PRIV(g)->flushWindowWidth), 4);

		// Acquire the bus to communicate with the display controller
		acquire_bus(g);
		
		// Upload the new temperature LUT
		_upload_lut(g);

		// Setup the window
		write_cmd(g, DTMW);
		write_data(g, (uint8_t)((PRIV(g)->flushWindowX >> 0) & 0xFF));
		write_data(g, (uint8_t)((PRIV(g)->flushWindowY >> 8) & 0x03));
		write_data(g, (uint8_t)((PRIV(g)->flushWindowY >> 0) & 0xFF));
		write_data(g, (uint8_t)((((PRIV(g)->flushWindowWidth)-1) >> 0) & 0xFF));
		write_data(g, (uint8_t)((((PRIV(g)->flushWindowHeight)-1) >> 8) & 0x03));
		write_data(g, (uint8_t)((((PRIV(g)->flushWindowHeight)-1) >> 0) & 0xFF));

		// Dump our framebuffer
		// Note: The display controller doesn't allow changing the vertical scanning direction
		// so we have to manually send the lines "the other way around" here.
		write_cmd(g, WRITEBUFCMD);
		for (y = GDISP_SCREEN_HEIGHT-1; y >= 0; y--) {
			write_data_burst(g, FRAMEBUFFER(g)+y*LINE_BYTES, LINE_BYTES);
		}

		// Power-up the DC/DC converter to update the display panel
		write_cmd(g, PON);
		_wait_for_busy_high(g);

		// Refresh the panel contents
		write_cmd(g, DRF);
		write_data(g, 0x00);	// Enable REGAL function
		write_data(g, 0x00);
		write_data(g, 0x00);
		write_data(g, 0x00);
		write_data(g, 0xEF);
		write_data(g, 0x00);
		write_data(g, 0xEF);
		_wait_for_busy_high(g);

		// Power-down the DC/DC converter to make all the low-power pussys happy
		write_cmd(g, POF);
		_wait_for_busy_low(g);

		// Release the bus again
		release_bus(g);

		// Clear the 'need-flushing' flag
		g->flags &=~ GDISP_FLG_NEEDFLUSH;
	}
#endif

#if GDISP_HARDWARE_DRAWPIXEL
	LLDSPEC void gdisp_lld_draw_pixel(GDisplay* g)
	{
		coord_t			x, y;
		LLDCOLOR_TYPE	*p;

		// Handle the different possible orientations
		switch(g->g.Orientation) {
		default:
		case GDISP_ROTATE_0:
			x = g->p.x;
			y = g->p.y;
			break;
		case GDISP_ROTATE_90:
			x = g->p.y;
			y = GDISP_SCREEN_HEIGHT-1 - g->p.x;
			break;
		case GDISP_ROTATE_180:
			x = GDISP_SCREEN_WIDTH-1 - g->p.x;
			y = GDISP_SCREEN_HEIGHT-1 - g->p.y;
			break;
		case GDISP_ROTATE_270:
			x = GDISP_SCREEN_WIDTH-1 - g->p.y;
			y = g->p.x;
			break;
		}
		
		// Modify the framebuffer content
		p = &FRAMEBUFFER(g)[xyaddr(x,y)];
		*p &=~ xybit(x, LLDCOLOR_MASK());
		*p |= xybit(x, gdispColor2Native(g->p.color));

//#warning ToDo
// There appears to be an issue in the silicone, still talking to the manufacturer about this one. Update will follow!
#if 0
		// Update the flush window region
		if (g->flags & GDISP_FLG_NEEDFLUSH) {
			if (x < PRIV(g)->flushWindowX)
				PRIV(g)->flushWindowX = x;
			if (y < PRIV(g)->flushWindowY)
				PRIV(g)->flushWindowY = y;
			if (x > PRIV(g)->flushWindowX + PRIV(g)->flushWindowWidth)
				PRIV(g)->flushWindowWidth = 
		} else {
			PRIV(g)->flushWindowX = x;
			PRIV(g)->flushWindowY = y;
			PRIV(g)->flushWindowWidth = 1;
			PRIV(g)->flushWindowHeight = 1;
		}
#else
		PRIV(g)->flushWindowX = 0;
		PRIV(g)->flushWindowY = 0;
		PRIV(g)->flushWindowWidth = GDISP_SCREEN_WIDTH;
		PRIV(g)->flushWindowHeight = GDISP_SCREEN_HEIGHT;
#endif

		// We should flush these changes to the display controller framebuffer at some point
		g->flags |= GDISP_FLG_NEEDFLUSH;
	}
#endif
	
#if GDISP_NEED_CONTROL && GDISP_HARDWARE_CONTROL
	LLDSPEC void gdisp_lld_control(GDisplay* g) {
		switch(g->p.x) {
		case GDISP_CONTROL_INVERT:
			_invertFramebuffer(g);
			break;
		
		default:
			break;
		}
	}
#endif

#endif // GFX_USE_GDISP
