/*
 * 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.io/license.html
 */

#include "gfx.h"

#if GFX_USE_GDISP

#define GDISP_DRIVER_VMT			GDISPVMT_ED060SC4
#include "gdisp_lld_config.h"
#include "../../../src/gdisp/gdisp_driver.h"

#include "board_ED060SC4.h"

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

#ifndef GDISP_SCREEN_HEIGHT
	#define GDISP_SCREEN_HEIGHT		600
#endif
#ifndef GDISP_SCREEN_WIDTH
	#define GDISP_SCREEN_WIDTH		800
#endif

/* Number of pixels per byte */
#ifndef EINK_PPB
	#define EINK_PPB 4
#endif

/* Delay for generating clock pulses.
 * Unit is approximate clock cycles of the CPU (0 to 15).
 * This should be atleast 50 ns.
 */
#ifndef EINK_CLOCKDELAY
	#define EINK_CLOCKDELAY 0
#endif

/* Width of one framebuffer block.
 * Must be divisible by EINK_PPB and evenly divide GDISP_SCREEN_WIDTH. */
#ifndef EINK_BLOCKWIDTH
	#define EINK_BLOCKWIDTH 20
#endif

/* Height of one framebuffer block.
 * Must evenly divide GDISP_SCREEN_WIDTH. */
#ifndef EINK_BLOCKHEIGHT
	#define EINK_BLOCKHEIGHT 20
#endif

/* Number of block buffers to use for framebuffer emulation. */
#ifndef EINK_NUMBUFFERS
	#define EINK_NUMBUFFERS 40
#endif

/* Do a "blinking" clear, i.e. clear to opposite polarity first.
 * This reduces the image persistence. */
#ifndef EINK_BLINKCLEAR
	#define EINK_BLINKCLEAR GFXON
#endif

/* Number of passes to use when clearing the display */
#ifndef EINK_CLEARCOUNT
	#define EINK_CLEARCOUNT 10
#endif

/* Number of passes to use when writing to the display */
#ifndef EINK_WRITECOUNT
	#define EINK_WRITECOUNT 4
#endif

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

#define PRIV(g)		((drvPriv *)g->priv)

/* Delay between signal changes, to give time for IO pins to change state. */
static GFXINLINE void clockdelay(void)
{
	#if EINK_CLOCKDELAY & 1
	asm("nop");
	#endif
	#if EINK_CLOCKDELAY & 2
	asm("nop");
	asm("nop");
	#endif
	#if EINK_CLOCKDELAY & 4
	asm("nop");
	asm("nop");
	asm("nop");
	asm("nop");
	#endif
	#if EINK_CLOCKDELAY & 8
	asm("nop");
	asm("nop");
	asm("nop");
	asm("nop");
	asm("nop");
	asm("nop");
	asm("nop");
	asm("nop");
	#endif
}

/* Fast vertical clock pulse for gate driver, used during initializations */
static void vclock_quick(GDisplay *g)
{
	setpin_ckv(g, gTrue);
	eink_delay(1);
	setpin_ckv(g, gFalse);
	eink_delay(4);
}

/* Horizontal clock pulse for clocking data into source driver */
static void hclock(GDisplay *g)
{
	clockdelay();
	setpin_cl(g, gTrue);
	clockdelay();
	setpin_cl(g, gFalse);
}

/* Start a new vertical gate driver scan from top.
 * Note: Does not clear any previous bits in the shift register,
 *       so you should always scan through the whole display before
 *       starting a new scan.
 */
static void vscan_start(GDisplay *g)
{
	setpin_gmode(g, gTrue);
	vclock_quick(g);
	setpin_spv(g, gFalse);
	vclock_quick(g);
	setpin_spv(g, gTrue);
	vclock_quick(g);
}

/* Waveform for strobing a row of data onto the display.
 * Attempts to minimize the leaking of color to other rows by having
 * a long idle period after a medium-length strobe period.
 */
static void vscan_write(GDisplay *g)
{
	setpin_ckv(g, gTrue);
	setpin_oe(g, gTrue);
	eink_delay(5);
	setpin_oe(g, gFalse);
	setpin_ckv(g, gFalse);
	eink_delay(200);
}

/* Waveform used when clearing the display. Strobes a row of data to the
 * screen, but does not mind some of it leaking to other rows.
 */
static void vscan_bulkwrite(GDisplay *g)
{
	setpin_ckv(g, gTrue);
	eink_delay(20);
	setpin_ckv(g, gFalse);
	eink_delay(200);
}

/* Waveform for skipping a vertical row without writing anything.
 * Attempts to minimize the amount of change in any row.
 */
static void vscan_skip(GDisplay *g)
{
	setpin_ckv(g, gTrue);
	eink_delay(1);
	setpin_ckv(g, gFalse);
	eink_delay(100);
}

/* Stop the vertical scan. The significance of this escapes me, but it seems
 * necessary or the next vertical scan may be corrupted.
 */
static void vscan_stop(GDisplay *g)
{
	setpin_gmode(g, gFalse);
	vclock_quick(g);
	vclock_quick(g);
	vclock_quick(g);
	vclock_quick(g);
	vclock_quick(g);
}

/* Start updating the source driver data (from left to right). */
static void hscan_start(GDisplay *g)
{
	/* Disable latching and output enable while we are modifying the row. */
	setpin_le(g, gFalse);
	setpin_oe(g, gFalse);
	
	/* The start pulse should remain low for the duration of the row. */
	setpin_sph(g, gFalse);
}

/* Write data to the horizontal row. */
static void hscan_write(GDisplay *g, const gU8 *data, int count)
{
	while (count--)
	{
		/* Set the next byte on the data pins */
		setpins_data(g, *data++);
		
		/* Give a clock pulse to the shift register */
		hclock(g);
	}
}

/* Finish and transfer the row to the source drivers.
 * Does not set the output enable, so the drivers are not yet active. */
static void hscan_stop(GDisplay *g)
{
	/* End the scan */
	setpin_sph(g, gTrue);
	hclock(g);
	
	/* Latch the new data */
	setpin_le(g, gTrue);
	clockdelay();
	setpin_le(g, gFalse);
}

/* Turn on the power to the E-Ink panel, observing proper power sequencing. */
static void power_on(GDisplay *g)
{
	unsigned i;
	
	/* First the digital power supply and signal levels. */
	setpower_vdd(g, gTrue);
	setpin_le(g, gFalse);
	setpin_oe(g, gFalse);
	setpin_cl(g, gFalse);
	setpin_sph(g, gTrue);
	setpins_data(g, 0);
	setpin_ckv(g, gFalse);
	setpin_gmode(g, gFalse);
	setpin_spv(g, gTrue);
	
	/* Min. 100 microsecond delay after digital supply */
	gfxSleepMicroseconds(100);
	
	/* Then negative voltages and min. 1000 microsecond delay. */
	setpower_vneg(g, gTrue);
	gfxSleepMicroseconds(1000);
	
	/* Finally the positive voltages. */
	setpower_vpos(g, gTrue);
	
	/* Clear the vscan shift register */
	vscan_start(g);
	for (i = 0; i < GDISP_SCREEN_HEIGHT; i++)
		vclock_quick(g);
	vscan_stop(g);
}

/* Turn off the power, observing proper power sequencing. */
static void power_off(GDisplay *g)
{
	/* First the high voltages */
	setpower_vpos(g, gFalse);
	setpower_vneg(g, gFalse);
	
	/* Wait for any capacitors to drain */
	gfxSleepMilliseconds(100);
	
	/* Then put all signals and digital supply to ground. */
	setpin_le(g, gFalse);
	setpin_oe(g, gFalse);
	setpin_cl(g, gFalse);
	setpin_sph(g, gFalse);
	setpins_data(g, 0);
	setpin_ckv(g, gFalse);
	setpin_gmode(g, gFalse);
	setpin_spv(g, gFalse);
	setpower_vdd(g, gFalse);
}

/* ====================================
 *      Framebuffer emulation layer
 * ==================================== */
 
#if EINK_PPB == 4
	#define PIXELMASK 3
	#define PIXEL_WHITE 2
	#define PIXEL_BLACK 1
	#define BYTE_WHITE 0xAA
	#define BYTE_BLACK 0x55
#else
	#error Unsupported EINK_PPB value.
#endif

#if GDISP_SCREEN_HEIGHT % EINK_BLOCKHEIGHT != 0
	#error GDISP_SCREEN_HEIGHT must be evenly divisible by EINK_BLOCKHEIGHT
#endif

#if GDISP_SCREEN_WIDTH % EINK_BLOCKWIDTH != 0
	#error GDISP_SCREEN_WIDTH must be evenly divisible by EINK_BLOCKWIDTH
#endif

#if EINK_BLOCKWIDTH % EINK_PPB != 0
	#error EINK_BLOCKWIDTH must be evenly divisible by EINK_PPB
#endif

#if EINK_NUMBUFFERS > 254
	#error EINK_NUMBUFFERS must be at most 254.
#endif

#define BLOCKS_Y		(GDISP_SCREEN_HEIGHT / EINK_BLOCKHEIGHT)
#define BLOCKS_X		(GDISP_SCREEN_WIDTH / EINK_BLOCKWIDTH)
#define WIDTH_BYTES		(EINK_BLOCKWIDTH / EINK_PPB)

/* Buffers that store the data for a small area of the display. */
typedef struct {
	gU8 data[EINK_BLOCKHEIGHT][WIDTH_BYTES];
} block_t;

typedef struct drvPriv {
	gU8 g_next_block; /* Index of the next free block buffer. */
	block_t g_blocks[EINK_NUMBUFFERS];

	/* Map that stores the buffers associated to each area of the display.
	 * Value of 0 means that the block is not allocated.
	 * Other values are the index in g_blocks + 1.
	 */
	gU8 g_blockmap[BLOCKS_Y][BLOCKS_X];
} drvPriv;

/* Check if the row contains any allocated blocks. */
static gBool blocks_on_row(GDisplay *g, unsigned by)
{
	unsigned bx;
	for (bx = 0; bx < BLOCKS_X; bx++)
	{
		if (PRIV(g)->g_blockmap[by][bx] != 0)
		{
			return gTrue;
		}
	}
	return gFalse;
}

/* Write out a block row. */
static void write_block_row(GDisplay *g, unsigned by)
{
	unsigned bx, dy, dx;
	for (dy = 0; dy < EINK_BLOCKHEIGHT; dy++)
	{
		hscan_start(g);
		for (bx = 0; bx < BLOCKS_X; bx++)
		{
			if (PRIV(g)->g_blockmap[by][bx] == 0)
			{
				for (dx = 0; dx < WIDTH_BYTES; dx++)
				{
					const gU8 dummy = 0;
					hscan_write(g, &dummy, 1);
				}
			}
			else
			{
				block_t *block = &PRIV(g)->g_blocks[PRIV(g)->g_blockmap[by][bx] - 1];
				hscan_write(g, &block->data[dy][0], WIDTH_BYTES);
			}
		}
		hscan_stop(g);
		
		vscan_write(g);
	}
}

/* Clear the block map, i.e. deallocate all blocks */
static void clear_block_map(GDisplay *g)
{
	unsigned bx, by;
	for (by = 0; by < BLOCKS_Y; by++)
	{
		for (bx = 0; bx < BLOCKS_X; bx++)
		{
			PRIV(g)->g_blockmap[by][bx] = 0;
		}
	}
	
	PRIV(g)->g_next_block = 0;
}

/* Initialize a newly allocated block. */
static void zero_block(block_t *block)
{
	unsigned dx, dy;
	for (dy = 0; dy < EINK_BLOCKHEIGHT; dy++)
	{
		for (dx = 0; dx < WIDTH_BYTES; dx++)
		{
			block->data[dy][dx] = 0;
		}
	}
}

/* Allocate a buffer
 * Automatically flushes if all buffers are full. */
static block_t *alloc_buffer(GDisplay *g, unsigned bx, unsigned by)
{
	block_t *result;
	drvPriv *priv;

	priv = PRIV(g);
	if (priv->g_blockmap[by][bx] == 0)
	{
		if (priv->g_next_block >= EINK_NUMBUFFERS)
			gdisp_lld_flush(g);
		
		result = &priv->g_blocks[priv->g_next_block];
		priv->g_blockmap[by][bx] = priv->g_next_block + 1;
		priv->g_next_block++;
		zero_block(result);
		return result;
	}
	else
	{
		result = &priv->g_blocks[priv->g_blockmap[by][bx] - 1];
		return result;
	}
}

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

LLDSPEC gBool gdisp_lld_init(GDisplay *g) {
	g->priv = gfxAlloc(sizeof(drvPriv));

	init_board(g);
	
	/* Make sure that all the pins are in "off" state.
	 * Having any pin high could cause voltage leaking to the
	 * display, which in turn causes the image to leak slowly away.
	 */
	power_off(g);
	
	clear_block_map(g);
	
	/* Initialise the GDISP structure */
	g->g.Width = GDISP_SCREEN_WIDTH;
	g->g.Height = GDISP_SCREEN_HEIGHT;
	g->g.Orientation = gOrientation0;
	g->g.Powermode = gPowerOn;
	g->g.Backlight = 100;
	g->g.Contrast = 100;
	return gTrue;
}

#if GDISP_HARDWARE_FLUSH
	LLDSPEC void gdisp_lld_flush(GDisplay *g) {
		unsigned by, dy, i;

		for (i = 0; i < EINK_WRITECOUNT; i++) {
			vscan_start(g);

			for (by = 0; by < BLOCKS_Y; by++) {
				if (!blocks_on_row(g, by)) {
					/* Skip the whole row of blocks. */
					for (dy = 0; dy < EINK_BLOCKHEIGHT; dy++)
						vscan_skip(g);
				} else {
					/* Write out the blocks. */
					write_block_row(g, by);
				}
			}

			vscan_stop(g);
		}

		clear_block_map(g);
	}
#endif

#if GDISP_HARDWARE_DRAWPIXEL
	void gdisp_lld_draw_pixel(GDisplay *g) {
		block_t *block;
		gU8 byte;
		unsigned bx, by, dx, dy;
		gU8 bitpos;

		switch(g->g.Orientation) {
		default:
		case gOrientation0:
			bx = g->p.x / EINK_BLOCKWIDTH;
			dx = g->p.x % EINK_BLOCKWIDTH;
			by = g->p.y / EINK_BLOCKHEIGHT;
			dy = g->p.y % EINK_BLOCKHEIGHT;
			break;
		case gOrientation90:
			bx = g->p.y / EINK_BLOCKWIDTH;
			dx = g->p.y % EINK_BLOCKWIDTH;
			by = (GDISP_SCREEN_HEIGHT-1 - g->p.x) / EINK_BLOCKHEIGHT;
			dy = (GDISP_SCREEN_HEIGHT-1 - g->p.x) % EINK_BLOCKHEIGHT;
			break;
		case gOrientation180:
			bx = (GDISP_SCREEN_WIDTH-1 - g->p.x) / EINK_BLOCKWIDTH;
			dx = (GDISP_SCREEN_WIDTH-1 - g->p.x) % EINK_BLOCKWIDTH;
			by = (GDISP_SCREEN_HEIGHT-1 - g->p.y) / EINK_BLOCKHEIGHT;
			dy = (GDISP_SCREEN_HEIGHT-1 - g->p.y) % EINK_BLOCKHEIGHT;
			break;
		case gOrientation270:
			bx = (GDISP_SCREEN_WIDTH-1 - g->p.y) / EINK_BLOCKWIDTH;
			dx = (GDISP_SCREEN_WIDTH-1 - g->p.y) % EINK_BLOCKWIDTH;
			by = g->p.x / EINK_BLOCKHEIGHT;
			dy = g->p.x % EINK_BLOCKHEIGHT;
			break;
		}

		block = alloc_buffer(g, bx, by);

		bitpos = (6 - 2 * (dx % EINK_PPB));
		byte = block->data[dy][dx / EINK_PPB];
		byte &= ~(PIXELMASK << bitpos);
		if (gdispColor2Native(g->p.color) != GFX_BLACK)
			byte |= PIXEL_WHITE << bitpos;
		else
			byte |= PIXEL_BLACK << bitpos;
		block->data[dy][dx / EINK_PPB] = byte;
	}
#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 == (gPowermode)g->p.ptr)
				return;
			switch((gPowermode)g->p.ptr) {
			case gPowerOff:
			case gPowerSleep:
			case gPowerDeepSleep:
				gdisp_lld_flush(g);
				power_off(g);
				break;
			case gPowerOn:
				power_on(g);
				break;
			default:
				return;
			}
			g->g.Powermode = (gPowermode)g->p.ptr;
			return;

		case GDISP_CONTROL_ORIENTATION:
			if (g->g.Orientation == (gOrientation)g->p.ptr)
				return;
			switch((gOrientation)g->p.ptr) {
			case gOrientation0:
			case gOrientation180:
				g->g.Height = GDISP_SCREEN_HEIGHT;
				g->g.Width = GDISP_SCREEN_WIDTH;
				break;
			case gOrientation90:
			case gOrientation270:
				g->g.Height = GDISP_SCREEN_WIDTH;
				g->g.Width = GDISP_SCREEN_HEIGHT;
				break;
			default:
				return;
			}
			g->g.Orientation = (gOrientation)g->p.ptr;
			return;
			
		default:
			return;
		}
	}
#endif

/* ===============================
 *       Accelerated routines
 * =============================== */

#if GDISP_HARDWARE_CLEARS
	static void subclear(GDisplay *g, gColor color) {
		unsigned x, y;
		gU8 byte;

		hscan_start(g);
		byte = color ? BYTE_WHITE : BYTE_BLACK;
		for (x = 0; x < GDISP_SCREEN_WIDTH; x++)
		{
			hscan_write(g, &byte, 1);
		}
		hscan_stop(g);

		setpin_oe(g, gTrue);
		vscan_start(g);
		for (y = 0; y < GDISP_SCREEN_HEIGHT; y++)
			vscan_bulkwrite(g);
		vscan_stop(g);
		setpin_oe(g, gFalse);
	}
	
	void gdisp_lld_clear(GDisplay *g) {
		unsigned i;

		clear_block_map(g);

		if (EINK_BLINKCLEAR) {
			subclear(g, !g->p.color);
			gfxSleepMilliseconds(50);
		}

		for (i = 0; i < EINK_CLEARCOUNT; i++) {
			subclear(g, g->p.color);
			gfxSleepMilliseconds(10);
		}
	}
#endif

#endif	// GFX_USE_GDISP