/*
 * 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"

#if defined(GDISP_SCREEN_HEIGHT) || defined(GDISP_SCREEN_HEIGHT)
	#if GFX_COMPILER_WARNING_TYPE == GFX_COMPILER_WARNING_DIRECT
		#warning "GDISP: This low level driver does not support setting a screen size. It is being ignored."
	#elif GFX_COMPILER_WARNING_TYPE == GFX_COMPILER_WARNING_MACRO
		COMPILER_WARNING("GDISP: This low level driver does not support setting a screen size. It is being ignored.")
	#endif
	#undef GDISP_SCREEN_WIDTH
	#undef GDISP_SCREEN_HEIGHT
#endif

/*------------------ Load the board file ------------------*/
typedef struct UC8173Lut {
	uint8_t const *lutVCOM;				// 32 bytes
	uint8_t const *lutFW;				// 512 bytes
	uint8_t const *lutFT;				// 128 bytes
	bool_t	regal;
	} UC8173Lut;

#include "board_UC8173.h"

/*------------------ Default UC8173 parameters ------------------*/
#ifndef UC8173_REVERSEAXIS_Y
	#define UC8173_REVERSEAXIS_Y	FALSE
#endif
#ifndef UC8173_REVERSEAXIS_X
	#define UC8173_REVERSEAXIS_X	FALSE
#endif
#ifndef UC8173_DEFAULT_MODE
	#define UC8173_DEFAULT_MODE		0
#endif
#ifndef UC8173_USE_OTP_LUT
	#define UC8173_USE_OTP_LUT		FALSE
#endif
#ifndef UC8173_CAN_READ
	#define UC8173_CAN_READ			FALSE
#endif
#ifdef UC8173_VCOM_VOLTAGE
	#define UC8173_VCOM_VOLTAGEBYTE	(((UC8173_VCOM_VOLTAGE) + 0.1)/-0.05)
#endif
#ifndef UC8171_BORDER
	#define UC8171_BORDER			0			/* 0 = Hi-Z, 1 = Black, 2 = White */
#endif
#ifndef UC8173_INIT_REAL_LUT
	#define UC8173_INIT_REAL_LUT	TRUE
#endif
#define UC8173_HEIGHT				240
#define UC8173_WIDTH				240

/*------------------ Set FB parameters ------------------*/
#define FB_REVERSEAXIS_Y			UC8173_REVERSEAXIS_Y
#define FB_REVERSEAXIS_X			UC8173_REVERSEAXIS_X
#define FB_WIDTH					UC8173_WIDTH
#define FB_HEIGHT					UC8173_HEIGHT
#define FB_PAGES					1
#define FB_PIXELORDER_MSB			TRUE

/*------------------ Include Generic FB Code ------------------*/
// This FB is for 1,2 or 4 bits per pixel packed along the x-axis
#define FB_TYPE_PIXELS				(LLDCOLOR_TYPE_BITS/LLDCOLOR_BITS)
#define FB_AXIS_MASK				(FB_TYPE_PIXELS-1)
#define FB_LINE_TYPES				((FB_WIDTH+FB_TYPE_PIXELS-1)/FB_TYPE_PIXELS)
#define FB_PAGE_TYPES				(FB_LINE_TYPES*FB_HEIGHT)
#define FB_ADDR(fbp, pg, px, py)	((fbp)->fb + ((px)/FB_TYPE_PIXELS + (py)*FB_LINE_TYPES + (pg)*FB_PAGE_TYPES))
#if FB_PIXELORDER_MSB
	#define FB_COLOR(px, py, c)		((c) << ((LLDCOLOR_TYPE_BITS-LLDCOLOR_BITS)-(((px) & FB_AXIS_MASK)<<(LLDCOLOR_BITS-1))))
#else
	#define FB_COLOR(px, py, c)		((c) << (((px) & FB_AXIS_MASK)<<(LLDCOLOR_BITS-1)))
#endif
#define FB_FLUSH_REQUIRED(fbp)		((fbp)->fa0.x < (fbp)->fa1.x)
#define FB_FLUSH_WIDTH(fbp)			((fbp)->fa1.x - (fbp)->fa0.x + 1)
#define FB_FLUSH_HEIGHT(fbp)		((fbp)->fa1.y - (fbp)->fa0.y + 1)
#define FB_FLUSH_ALL(fbp)			{ (fbp)->fa0.x = (fbp)->fa0.y = 0; (fbp)->fa1.x = FB_WIDTH-1; (fbp)->fa1.y = FB_HEIGHT-1; }
#define FB_FLUSH_NONE(fbp)			{ (fbp)->fa0.x = FB_WIDTH; (fbp)->fa0.y = FB_HEIGHT; (fbp)->fa1.x = (fbp)->fa1.y = -1; }
#define FB_FLUSH_POINT(fbp, px, py)	{											\
			if ((px) < (fbp)->fa0.x) (fbp)->fa0.x = (px) & ~FB_AXIS_MASK;		\
			if ((px) > (fbp)->fa1.x) (fbp)->fa1.x = (px) | FB_AXIS_MASK;		\
			if ((py) < (fbp)->fa0.y) (fbp)->fa0.y = (py);						\
			if ((py) > (fbp)->fa1.y) (fbp)->fa1.y = (py);						\
		}
#define FB_SETPIXEL(fbp, pg, px, py, c)	{										\
			LLDCOLOR_TYPE	*p, oc;												\
			p = FB_ADDR((fbp), (pg), (px), (py));								\
			oc = (*p & ~FB_COLOR((px), (py), LLDCOLOR_MASK())) | FB_COLOR((px), (py), (c));	\
			if (oc != *p) { *p = oc; FB_FLUSH_POINT((fbp), (px), (py)); }		\
		}

typedef struct FBpriv {
	point_t			fa0, fa1;
	LLDCOLOR_TYPE	fb[FB_PAGE_TYPES * FB_PAGES];
	} FBpriv;

/*------------------ UC8173 Driver Code ------------------*/

#include "UC8173.h"

// UC8173 Inernal Macros & types
typedef struct UC8173_Private {
	UC8173Lut	*lut;
	FBpriv		fb;
} UC8173_Private;

#if !UC8173_INIT_REAL_LUT
	static uint8_t const UC8173_LUT_None[] = {
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00 
	};	
#endif
	
LLDSPEC bool_t gdisp_lld_init(GDisplay* g)
{
	UC8173_Private	*priv;
	
	// Allocate the private area
	g->priv = gfxAlloc(sizeof(UC8173_Private));
	if (!g->priv)
		return FALSE;
	priv = (UC8173_Private *)g->priv;

	// Initialize the private area
	// As the display is non-volatile we need to ensure a full flush on the first draw
	priv->lut = &UC8173_ModeTable[UC8173_DEFAULT_MODE];
	FB_FLUSH_ALL(&priv->fb);

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

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

	// Acquire the bus
	acquire_bus(g);

	// Booster soft-start		- Values from example code
	write_cmd(g, BTST);
	write_data(g, 0x17);		// data: PhaseA: Bits76=StartupTime [10ms+n*10ms def=0x00], Bits543=Strength, Bits210=MinGDROffTime
	write_data(g, 0x97);		// data: PhaseB: Bits76=StartupTime [10ms+n*10ms def=0x00], Bits543=Strength, Bits210=MinGDROffTime
	write_data(g, 0x20);		// data: PhaseC:                                            Bits543=Strength, Bits210=MinGDROffTime [def=0x07]
  
	// Power settings			- Values from datasheet default values
	write_cmd(g, PWR);
	write_data(g, 0x03);		// data: 0x02=Internal VDH/VDL, 0x01=Internal VGH/VGL
	write_data(g, 0x00);		// data: Always 0x00
	write_data(g, 0x26);		// data: 0x00->0x3F VSH Voltage 2.4V -> 11.0V step 0.2V - default=10V (0x26)
	write_data(g, 0x26);		// data: 0x00->0x3F VSL Voltage 2.4V -> 11.0V step 0.2V - default=10V (0x26)
	write_data(g, 0x03);		// data: 0x00->0x3F Red Voltage 2.4V -> 11.0V step 0.2V - default= 3V (0x03)

	// Power-on
	write_cmd(g, PON);
	while (!getpin_busy(g));

	// Panel setting register	- Values from datasheet (OTP untested)
	write_cmd(g, PSR);			// data: 0x08 + ..., 0x04=Shift Right, 0x02=Booster On, 0x01=Reset Off
	write_data(g, 0x0F);
	#if UC8173_USE_OTP_LUT
		write_cmd(g, 0x86);		// data: Always 0x86, write_data=Register LUT, write_cmd=OTP LUT
	#else
		write_data(g, 0x86);
	#endif

	// Power-off sequence		- Values from example code
	write_cmd(g, PFS);			// data: Shutdown frames - 0x00=1, 0x10=2, 0x20=3, 0x30=4
	write_data(g, 0x00);

	// PLL control				- Values from example code
	write_cmd(g, LPRD);			// data: PLL Clock Freq = 1MHz/(n+1) Min=0x04 (=200kHz)
	write_data(g, 0x25);

	// Internal temperature sensor enable	- Values from example code
	write_cmd(g, TSE);			// data: 0x00=Use internal temperature sensor, 0x80=external, Bits0-4=signed degrees to add eg 0x0F = -1
	write_data(g, 0x00);

	// VCOM settings 			- Values from datasheet
	write_cmd(g, CDI);			// data: 0x80=Border Hi-Z, 0x40=Border Output DC enabled, 0x30=BorderColor, 0x01=Normal/Inverted
	#if UC8171_BORDER == 0
		write_data(g, 0x81); 
	#elif UC8171_BORDER == 1
		write_data(g, 0x41);
	#elif UC8171_BORDER == 2
		write_data(g, 0x71);
	#else
		#error "UC8173: UC8173_BORDER must be 0..2"
	#endif
	
	// More VCOM data			- Values from example code
	write_data(g, 0x20);		// data: 0xX0=Src->VCOM interval (n+1 HSync, Def=0011), 0b0000-00XX=VCOM->Src interval (top 2 bits)
	write_data(g, 0x10);		// data: 0xXX=VCOM->Src interval (bottom 8 bits) Def=0x18
  
	// Set display panel resolution - from datasheet: should always be 240x240 for this chip
	write_cmd(g, TRES);
	write_data(g, (UC8173_WIDTH-1));
	write_data(g, ((UC8173_HEIGHT-1)>>8));
	write_data(g, ((UC8173_HEIGHT-1) & 0xFF));

	// Undocumented register	- Values from example 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		- Values from example code		
	write_cmd(g, AMV);
	write_data(g, 0x11);
	while (!getpin_busy(g));

	// Get current VCOM value and write it
	#if defined(UC8173_VCOM_VOLTAGEBYTE)
		// VCM_DC setting
		write_cmd(g, VDCS);
		write_data(g, UC8173_VCOM_VOLTAGEBYTE);			// was 0x12 in example code = -1.0V
	#elif UC8173_CAN_READ
		{
			uint8_t	vc;
				
			write_cmd(g, VV);
			vc = read_data(g);
			write_cmd(g, VDCS);
			write_data(g, vc);
		}
	#else
		#error "UC8173: Either UC8173_VCOM_VOLTAGE or UC8173_VCOM_VOLTAGEBYTE must be defined or UC8173_CAN_READ must be TRUE"
	#endif
  
	// Undocumented register	- Values from example code
	write_cmd(g, VBDS);
	write_data(g, 0x22);
  
	// Undocumented register	- Values from example code
	write_cmd(g, LVSEL);
	write_data(g, 0x02);
  
	// Undocumented register	- Values from example code
	write_cmd(g, GBS);
	write_data(g, 0x02);
	write_data(g, 0x02);
  
	// Undocumented register	- Values from example code
	write_cmd(g, GSS);
	write_data(g, 0x02);
	write_data(g, 0x02);
  
	// Undocumented register	- Values from example code
	write_cmd(g, DF);			// For REGAL (???)
	write_data(g, 0x1F);
	
	write_cmd(g, PON);
	while (!getpin_busy(g));
	
	#if UC8173_INIT_REAL_LUT
		// Load the real LUT's
		write_cmd(g, LUT_KWVCOM);
		write_data_burst(g, priv->lut->lutVCOM, 32);
		write_cmd(g, LUT_KW);
		write_data_burst(g, priv->lut->lutFW, 512);
		write_cmd(g, LUT_FT);
		write_data_burst(g, priv->lut->lutFT, 128);
	#else
		// Clear the LUT's		- Values from example code
		write_cmd(g, LUT_KW);
		write_data_burst(g, UC8173_LUT_None, sizeof(UC8173_LUT_None));
		write_cmd(g, LUT_KWVCOM);
		write_data_burst(g, UC8173_LUT_None, sizeof(UC8173_LUT_None));
	#endif
	
    write_cmd(g, POF); 
    while (getpin_busy(g));
	
	// Finish Init
	post_init_board(g);

 	// Release the bus
	release_bus(g);

	// Initialise the GDISP structure
	g->g.Width = UC8173_WIDTH;
	g->g.Height = UC8173_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 		cy, cx, dx, dy;
		LLDCOLOR_TYPE	*fb;
		UC8173_Private	*priv;

		priv = (UC8173_Private *)g->priv;

		if (!FB_FLUSH_REQUIRED(&priv->fb))
			return;

		#if 0
			FB_FLUSH_ALL(&priv->fb);
		#endif
		
		// Acquire the bus to communicate with the display controller
		acquire_bus(g);
		
		// Upload the new temperature LUT
		write_cmd(g, LUT_KWVCOM);
		write_data_burst(g, priv->lut->lutVCOM, 32);
		write_cmd(g, LUT_KW);
		write_data_burst(g, priv->lut->lutFW, 512);
		write_cmd(g, LUT_FT);
		write_data_burst(g, priv->lut->lutFT, 128);

		// Calculate the width, height
		cx = FB_FLUSH_WIDTH(&priv->fb);
		cy = FB_FLUSH_HEIGHT(&priv->fb);

		// Setup the window
		// Datasheet says x,y,w,h but in practice it needs to be x,y,w-1,h-1
		write_cmd(g, DTMW);
		write_data(g, (uint8_t)((priv->fb.fa0.x >> 0) & 0xFF));
		write_data(g, (uint8_t)((priv->fb.fa0.y >> 8) & 0x03));
		write_data(g, (uint8_t)((priv->fb.fa0.y >> 0) & 0xFF));
		write_data(g, (uint8_t)(((cx-1) >> 0) & 0xFF));
		write_data(g, (uint8_t)(((cy-1) >> 8) & 0x03));
		write_data(g, (uint8_t)(((cy-1) >> 0) & 0xFF));

		// Transfer the buffer
		#if GDISP_LLD_PIXELFORMAT == GDISP_PIXELFORMAT_MONO
			write_cmd(g, DTM4);
		#elif  GDISP_LLD_PIXELFORMAT == GDISP_PIXELFORMAT_GRAY4
			write_cmd(g, DTM2);
		#else
			#error "UC8173: Unsupported driver color format"
		#endif
		dx = (cx+FB_TYPE_PIXELS-1)/FB_TYPE_PIXELS * (LLDCOLOR_TYPE_BITS/8);
		for (fb = FB_ADDR(&priv->fb, 0, priv->fb.fa0.x, priv->fb.fa0.y), dy = cy; dy; dy--, fb += FB_LINE_TYPES)
			write_data_burst(g, (uint8_t *)fb, dx);

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

		// Refresh the panel contents
		// Datasheet says x,y,w,h but in practice it needs to be x,y,w-1,h-1
		write_cmd(g, DRF);		// data: Partial Scan = 0x10, REGAL = 0x08, VCOM_DoNothing = 0x04 (GC4/A2 = 0x00, GU4 = 0x08)
		write_data(g, (priv->lut->regal ? 0x08 : 0x00));
		write_data(g, (uint8_t)((priv->fb.fa0.x >> 0) & 0xFF));
		write_data(g, (uint8_t)((priv->fb.fa0.y >> 8) & 0x03));
		write_data(g, (uint8_t)((priv->fb.fa0.y >> 0) & 0xFF));
		write_data(g, (uint8_t)(((cx-1) >> 0) & 0xFF));
		write_data(g, (uint8_t)(((cy-1) >> 8) & 0x03));
		write_data(g, (uint8_t)(((cy-1) >> 0) & 0xFF));
		while (!getpin_busy(g));

		// Power-down the DC/DC converter
		write_cmd(g, POF);
		while (getpin_busy(g));

		// Release the bus
		release_bus(g);

		// Mark as flushed
		FB_FLUSH_NONE(&priv->fb);
	}
#endif

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

		priv = (UC8173_Private *)g->priv;

		// Handle the different possible orientations
		#if GDISP_NEED_CONTROL && GDISP_HARDWARE_CONTROL
			switch(g->g.Orientation) {
			default:
			case GDISP_ROTATE_0:
				#if FB_REVERSEAXIS_X
					x = FB_WIDTH-1 - g->p.x;
				#else
					x = g->p.x;
				#endif
				#if FB_REVERSEAXIS_Y
					y = FB_HEIGHT-1 - g->p.y;
				#else
					y = g->p.y;
				#endif
				break;
			case GDISP_ROTATE_90:
				#if FB_REVERSEAXIS_X
					x = FB_WIDTH-1 - g->p.y;
				#else
					x = g->p.y;
				#endif
				#if FB_REVERSEAXIS_Y
					y = g->p.x;
				#else
					y = FB_HEIGHT-1 - g->p.x;
				#endif
				break;
			case GDISP_ROTATE_180:
				#if FB_REVERSEAXIS_X
					x = g->p.x;
				#else
					x = FB_WIDTH-1 - g->p.x;
				#endif
				#if FB_REVERSEAXIS_Y
					y = g->p.y;
				#else
					y = FB_HEIGHT-1 - g->p.y;
				#endif
				break;
			case GDISP_ROTATE_270:
				#if FB_REVERSEAXIS_X
					x = g->p.y;
				#else
					x = FB_WIDTH-1 - g->p.y;
				#endif
				#if FB_REVERSEAXIS_Y
					y = FB_HEIGHT-1 - g->p.x;
				#else
					y = g->p.x;
				#endif
				break;
			}
		#else
			#if FB_REVERSEAXIS_X
				x = FB_WIDTH-1 - g->p.x;
			#else
				x = g->p.x;
			#endif
			#if FB_REVERSEAXIS_Y
				y = FB_HEIGHT-1 - g->p.y;
			#else
				y = g->p.y;
			#endif
		#endif
		
		// Modify the framebuffer content
		FB_SETPIXEL(&priv->fb, 0, x, y, gdispColor2Native(g->p.color));
	}
#endif
	
#if GDISP_NEED_CONTROL && GDISP_HARDWARE_CONTROL
	LLDSPEC void gdisp_lld_control(GDisplay* g) {
		UC8173_Private	*priv;
	
		priv = (UC8173_Private *)g->priv;
		
		switch(g->p.x) {
			
		// Custom gdispControl() to invert the framebuffer
		case GDISP_CONTROL_INVERT:
			{
				unsigned		i;
				LLDCOLOR_TYPE	*fb;

				// Invert the framebuffer
				for (fb = priv->fb.fb, i = FB_PAGE_TYPES; i ; i--, fb++)
					fb[0] = ~fb[0];
				
				// We should flush these changes to the display controller framebuffer at some point
				FB_FLUSH_ALL(&priv->fb);
			}
			break;
		
		// Custom gdispControl() to set which EINK Mode (waveform) to use
		case GDISP_CONTROL_SETMODE:
			if ((unsigned)g->p.ptr < sizeof(UC8173_ModeTable)/sizeof(UC8173_ModeTable[0]))
				priv->lut = &UC8173_ModeTable[(unsigned)g->p.ptr];
			break;

		// Custom gdispControl() to set the EINK border
		case GDISP_CONTROL_SETBORDER:
			if ((unsigned)g->p.ptr > 2)
				break;
			acquire_bus(g);
			write_cmd(g, PON);
			while (!getpin_busy(g));
			write_cmd(g, CDI);
			switch((unsigned)g->p.ptr) {
			case 0:
				write_data(g, 0x81);	// Border Hi-Z
				break;
			case 1:
				write_data(g, 0x41);	// Border Black
				break;
			case 2:
				write_data(g, 0x71);	// Border White
				break;
			}
			write_cmd(g, POF); 
			while (getpin_busy(g));
			release_bus(g);
			break;
			
		default:
			break;
		}
	}
#endif

#endif // GFX_USE_GDISP
