/*
 * 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_UC1610
#include "gdisp_lld_config.h"
#include "../../../src/gdisp/gdisp_driver.h"

#include "board_UC1610.h"

/*===========================================================================*/
/* Driver local definitions.                                                 */
/*===========================================================================*/

#include "UC1610.h"

#ifndef GDISP_SCREEN_HEIGHT
	#define GDISP_SCREEN_HEIGHT		UC1610_SCREEN_HEIGHT
#endif

#ifndef GDISP_SCREEN_WIDTH
	#define GDISP_SCREEN_WIDTH		UC1610_SCREEN_WIDTH
#endif

#ifndef GDISP_INITIAL_CONTRAST
	#define GDISP_INITIAL_CONTRAST          40
#endif

#ifndef GDISP_INITIAL_BACKLIGHT
	#define GDISP_INITIAL_BACKLIGHT		100
#endif

// Some common routines and macros
#define PRIV(g)					((UC1610_Window *)g->priv)
#define RAM(g)					((uint8_t *)(PRIV(g) + 1))
#define xyaddr(x, y)			((x) + GDISP_SCREEN_WIDTH * ((y) >> 2))
#define xybit(y, c)				((c) << (((y) & 3) << 1))
#define GDISP_FLG_NEEDFLUSH		(GDISP_FLG_DRIVER << 0)

typedef struct UC1610_Window {
	coord_t x1;
	coord_t y1;
	coord_t x2;
	coord_t y2;
} UC1610_Window;

/*===========================================================================*/
/* Driver local varriables.                                                  */
/*===========================================================================*/

static uint8_t cmdBuffer[11];

/*===========================================================================*/
/* Driver local functions.                                                   */
/*===========================================================================*/

static void GFXINLINE power_on_sequence(GDisplay* g) {
	cmdBuffer[0]  = UC1610_SYSTEM_RESET;                 // software reset
	write_cmd(g, cmdBuffer, 1);
	gfxSleepMilliseconds(2);
	cmdBuffer[0]  = UC1610_SET_COM_END;                  // set com end value
	cmdBuffer[1]  = GDISP_SCREEN_HEIGHT - 1;
	cmdBuffer[2]  = UC1610_SET_PANEL_LOADING;
	cmdBuffer[3]  = UC1610_SET_LCD_BIAS_RATIO;
	cmdBuffer[4]  = UC1610_SET_VBIAS_POT;                // set contrast
	cmdBuffer[5]  = (uint8_t) (GDISP_INITIAL_CONTRAST * 254 / 100);
	cmdBuffer[6]  = UC1610_SET_MAPPING_CONTROL;          // bottom view
	cmdBuffer[7]  = UC1610_SET_SCROLL_LINES_LSB | 0;
	cmdBuffer[8]  = UC1610_SET_SCROLL_LINES_MSB | 0;     // set scroll line on line 0
	cmdBuffer[9]  = UC1610_SET_AC | UC1610_AC_WA_MASK;   // set auto increment wrap arround
	cmdBuffer[10] = UC1610_SET_DISPLAY_ENABLE | 1;       // turn display on
	write_cmd(g, cmdBuffer, 11);
}

static void GFXINLINE flush_screen(GDisplay* g) {
	PRIV(g)->x1 = 0;
	PRIV(g)->y1 = 0;
	PRIV(g)->x2 = GDISP_SCREEN_WIDTH-1;
	PRIV(g)->y2 = GDISP_SCREEN_HEIGHT-1;
	g->flags |= GDISP_FLG_NEEDFLUSH;
	gdisp_lld_flush(g);
}

/*===========================================================================*/
/* Driver exported functions.                                                */
/*===========================================================================*/

LLDSPEC bool_t gdisp_lld_init(GDisplay *g) {
	// The private area is the display surface + flush window structure.
	g->priv = gfxAlloc(sizeof(UC1610_Window) + GDISP_SCREEN_WIDTH * GDISP_SCREEN_HEIGHT / UC1610_PAGE_HEIGHT);

	// Initialise the board interface
	init_board(g);

	// Hardware reset
        // not needed : reset pin set to ON by init_board(g), software reset by power_on_sequence(g)
	gfxSleepMilliseconds(12);

        // Acquire the bus
	acquire_bus(g);

	// Init commands sequence
	power_on_sequence(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 = GDISP_INITIAL_BACKLIGHT;
	g->g.Contrast = GDISP_INITIAL_CONTRAST;
	return TRUE;
}

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

		// handle orientation
		switch (g->g.Orientation) {
			default :
				x = g->p.x;
				y = g->p.y;
				break;
			case GDISP_ROTATE_90 :
				y = g->p.x;
				x = g->p.y;
				break;
			case GDISP_ROTATE_270 :
				y = g->p.x;
				x = g->p.y;
				break;
		}


		// update pixel color
		c = RAM(g) + xyaddr(x, y);
		*c &= ~xybit(y, LLDCOLOR_MASK());
		*c |= xybit(y, gdispColor2Native(g->p.color));

		// update flush window boundaries
		if (x < PRIV(g)->x1) { PRIV(g)->x1 = x; }
		if (x > PRIV(g)->x2) { PRIV(g)->x2 = x; }
		if (y < PRIV(g)->y1) { PRIV(g)->y1 = y; }
		if (y > PRIV(g)->y2) { PRIV(g)->y2 = y; }
		g->flags |= GDISP_FLG_NEEDFLUSH;
	}
#endif

#if GDISP_HARDWARE_FLUSH
	LLDSPEC void gdisp_lld_flush(GDisplay* g)
	{
		coord_t x1, y1, x2, y2, cx;
		uint8_t *c;

		// Don't flush unless we really need to
		if (!(g->flags & GDISP_FLG_NEEDFLUSH))
			return;

		x1 = PRIV(g)->x1;
		y1 = PRIV(g)->y1;
		x2 = PRIV(g)->x2;
		y2 = PRIV(g)->y2;
		cx = x2 - x1 + 1;
		
		// Clear the 'need-flushing' flag and reset the window
		g->flags &= ~GDISP_FLG_NEEDFLUSH;
		PRIV(g)->x1 = GDISP_SCREEN_WIDTH;
		PRIV(g)->y1 = GDISP_SCREEN_HEIGHT;
		PRIV(g)->x2 = -1;
		PRIV(g)->y2 = -1;

		// set window to fill
		cmdBuffer[0] = UC1610_SET_WINDOW_PROGRAM_ENABLE | 0;	// before changing boundaries
		cmdBuffer[1] = UC1610_SET_WP_STARTING_CA;
		cmdBuffer[2] = x1;
		cmdBuffer[3] = UC1610_SET_WP_ENDING_CA;
		cmdBuffer[4] = x2;
		cmdBuffer[5] = UC1610_SET_WP_STARTING_PA;
		cmdBuffer[6] = y1 >> 2;
		cmdBuffer[7] = UC1610_SET_WP_ENDING_PA;
		cmdBuffer[8] = y2 >> 2;
		cmdBuffer[9] = UC1610_SET_WINDOW_PROGRAM_ENABLE | 1;	// entering window programming
		
		acquire_bus(g);
		write_cmd (g, cmdBuffer, 10);
		
		// write each page segment from RAM(g) to display RAM
		for (c = RAM(g) + xyaddr(x1, y1) ; y1 <= y2 ; c += GDISP_SCREEN_WIDTH, y1 += UC1610_PAGE_HEIGHT) {
			write_data(g, c, cx);
		}
		release_bus(g);
	}
#endif

#if GDISP_NEED_CONTROL && GDISP_HARDWARE_CONTROL
	LLDSPEC void gdisp_lld_control(GDisplay *g) {
		switch(g->p.x) {
			case GDISP_CONTROL_POWER:
				if (g->g.Powermode == (powermode_t)g->p.ptr) { return; }
				switch((powermode_t)g->p.ptr) {
					case powerOff:
						cmdBuffer[0] = UC1610_SYSTEM_RESET;
						acquire_bus(g);
						write_cmd(g, cmdBuffer, 1);
						release_bus(g);
						gfxSleepMilliseconds(2);
						break;
					case powerSleep:
						cmdBuffer[0] = UC1610_SET_DISPLAY_ENABLE | 0;
						acquire_bus(g);
						write_cmd(g, cmdBuffer, 1);
						release_bus(g);
						gfxSleepMilliseconds(2);
						break;
					case powerOn:
						if (g->g.Powermode == powerSleep) {
							cmdBuffer[0] = UC1610_SET_DISPLAY_ENABLE | 1;
							acquire_bus(g);
							write_cmd(g, cmdBuffer, 1);
							release_bus(g);
							flush_screen(g);
						} else {
							gfxSleepMilliseconds(12);
							acquire_bus(g);
							power_on_sequence(g);
							release_bus(g);
						}
						break;
					default:
						return;
				}
				g->g.Powermode = (powermode_t)g->p.ptr;
				return;

			case GDISP_CONTROL_ORIENTATION:
				if (g->g.Orientation == (orientation_t)g->p.ptr) { return; }
				switch((orientation_t)g->p.ptr) {
					case GDISP_ROTATE_0:
						g->g.Width  = GDISP_SCREEN_WIDTH;
						g->g.Height = GDISP_SCREEN_HEIGHT;
						cmdBuffer[0] = UC1610_SET_MAPPING_CONTROL;
						acquire_bus(g);
						write_cmd(g, cmdBuffer, 1);
						release_bus(g);
						flush_screen(g);
						break;
					case GDISP_ROTATE_180:	// we can rotate 180° without modify screen buffer RAM(g)
						g->g.Width  = GDISP_SCREEN_WIDTH;
						g->g.Height = GDISP_SCREEN_HEIGHT;
						cmdBuffer[0] = UC1610_SET_MAPPING_CONTROL | UC1610_SET_MAPPING_CONTROL_MY_MASK | UC1610_SET_MAPPING_CONTROL_MX_MASK;
						acquire_bus(g);
						write_cmd(g, cmdBuffer, 1);
						release_bus(g);
						flush_screen(g);
						break;
					case GDISP_ROTATE_90:	// needs clearing screen and updating RAM(g)
						g->g.Width  = GDISP_SCREEN_HEIGHT;
						g->g.Height = GDISP_SCREEN_WIDTH;
						cmdBuffer[0] = UC1610_SET_MAPPING_CONTROL | UC1610_SET_MAPPING_CONTROL_MX_MASK;
						acquire_bus(g);
						write_cmd(g, cmdBuffer, 1);
						release_bus(g);
						break;
					case GDISP_ROTATE_270:	// needs clearing screen and updating RAM(g)
						g->g.Width  = GDISP_SCREEN_HEIGHT;
						g->g.Height = GDISP_SCREEN_WIDTH;
						cmdBuffer[0] = UC1610_SET_MAPPING_CONTROL | UC1610_SET_MAPPING_CONTROL_MY_MASK;
						acquire_bus(g);
						write_cmd(g, cmdBuffer, 1);
						release_bus(g);
						break;
					default:
						return;
				}
				g->g.Orientation = (orientation_t)g->p.ptr;
				return;

			case GDISP_CONTROL_BACKLIGHT:
				// TODO: backlight support at board level
				/*if ((unsigned)g->p.ptr > 100) { g->p.ptr = (void *)100; }
				set_backlight(g, (unsigned)g->p.ptr);
				g->g.Backlight = (unsigned)g->p.ptr;*/
				return;

			case GDISP_CONTROL_CONTRAST:
				if ((unsigned)g->p.ptr > 100) { g->p.ptr = (void *)100; }
				acquire_bus(g);
				cmdBuffer[0] = UC1610_SET_VBIAS_POT;
				cmdBuffer[1] = (uint8_t)((unsigned)g->p.ptr * 254 / 100);
				write_cmd(g, cmdBuffer, 2);
				release_bus(g);
				g->g.Contrast = (unsigned)g->p.ptr;
				flush_screen(g);
				return;

			case GDISP_CONTROL_INVERSE:
				cmdBuffer[0] = g->p.ptr ? (UC1610_SET_INVERSE_DISPLAY | 1) : (UC1610_SET_INVERSE_DISPLAY | 0);
				acquire_bus(g);
				write_cmd(g, cmdBuffer, 1);
				release_bus(g);
				//flush_screen(g);
				return;
		}
	}
#endif

#endif // GFX_USE_GDISP
