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

/* Robert Offner 2017
 * This is a driver for the KS0107 Displays.
 * It should work with displays up to 240 pixel in width and 64 pixel in height
 * ATTENTION some newer displays on ebay look like KS0107 Type but they're not!
 * They use a new controller: ST7920. These are easy to identify: NO CS pin but
 * a PSB Pin which switches from parallel to serial communication mode.
 * If it cost less than 10.- it is probably a ST7920.
 * Features: Well not much. For now write pixel to display and
 * Read from Display / Buffer in RAM. Buffer in Ram is much faster than
 * readback!
 * I am trying to get the auto increment feature somehow implemented. It
 * basically means if you start at x=0 and continue with x=1, x=2,... you don't
 * have to write the address just data.
 *
 * Version: 0.3
 *
 * History:
 * v0.4
 * .) Cleanup by uGFX team. Code needs a lot of work.
 *
 * v0.3
 * .) Removed initialization of g->priv because it is already done by uGFX.
 *
 * v0.2
 * .) Cleanup,
 * .) changed osalThreadSleep to gfxSleepMicroseconds(x)
 *    ATTENTION: for gfxSleepMicroseconds to work on chibios you have to
 *    increase CH_CFG_ST_FREQUENCY to 1000000 and probably CH_CFG_ST_RESOLUTION
 *    to 32 bit (not tested because STM32F103 doesn't have a 32 bit timer
 * .) changed lcdbuf to g->priv
 *
 * v0.1 Initial Release
 */

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

#include "board_KS0108.h"

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

#ifndef GDISP_SCREEN_HEIGHT
	#define GDISP_SCREEN_HEIGHT		64
#endif
#ifndef GDISP_SCREEN_WIDTH
	#define GDISP_SCREEN_WIDTH		128
#endif

#define CHIPS (GDISP_SCREEN_WIDTH/64)

#if !KS0108_NEED_READ
	#define BUFFSZ (GDISP_SCREEN_HEIGHT/8 * GDISP_SCREEN_WIDTH)
	#define RAM(g) ((uint8_t *)g->priv)
#endif
#ifndef GDISP_INITIAL_CONTRAST
	#define GDISP_INITIAL_CONTRAST	50
#endif
#ifndef GDISP_INITIAL_BACKLIGHT
	#define GDISP_INITIAL_BACKLIGHT	100
#endif

// KS0108 Commands
#define KS0108_CHIP1_ON                0x003F
#define KS0108_CHIP2_ON                0x013F
#define KS0108_DISP1START              0x00C0
#define KS0108_DISP2START              0x01C0
#define KS0108_DISP1OFF                0x003E
#define KS0108_DISP2OFF                0x013E
#define KS0108_SET_ADDR                0x0040
#define KS0108_SET_PAGE                0x00B8

/*===========================================================================*/
/* Driver local functions.                                                   */
/*===========================================================================*/
#ifndef write_data_repeat
	#define write_data_repeat(g, data, count) { int i; for (i = 0; i < count; ++i) write_data (g, data); }
#endif

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

LLDSPEC bool_t gdisp_lld_init(GDisplay *g) {
	#if !KS0108_NEED_READ
		// The private area is the display surface.
		g->priv = gfxAlloc(BUFFSZ);
	#else
		g->priv = 0;
	#endif
	// Initialise the board interface
	init_board(g);

	#if KS0108_HAS_RESET  //Make Hardware Reset
		setpin_reset(g, TRUE);
		gfxSleepMilliseconds(120);
		setpin_reset(g, FALSE);
	#endif
	gfxSleepMilliseconds(120);
	write_cmd(g, KS0108_DISP1OFF);
	gfxSleepMilliseconds(1);
	write_cmd(g, KS0108_DISP2OFF);
	gfxSleepMilliseconds(1);
	write_cmd(g, KS0108_CHIP1_ON);                        //0x3F Chip1
	gfxSleepMilliseconds(1);
	write_cmd(g, KS0108_CHIP2_ON);                        //0x3F Chip2
	gfxSleepMilliseconds(1);
	write_cmd(g, KS0108_DISP1START);                      //0xC0 Chip1
	gfxSleepMilliseconds(1);
	write_cmd(g, KS0108_DISP2START);                      //0xC0 Chip2
	gfxSleepMilliseconds(1);

	// Finish Init
	post_init_board(g);

	#if KS0108_NEED_BACKLIGHT
		// Turn on the back-light
		set_backlight(g, GDISP_INITIAL_BACKLIGHT);
	#endif

	// 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;
}

GFXINLINE void KS0108_goto(GDisplay* g, ) {
}

static void set_viewport(GDisplay *g) {
	uint16_t	pg;
	uint16_t	chip;
	
	pg = g->p.y >> 3;
	chip = (g->p.x >> 6) << 8;
	write_cmd(g, KS0108_SET_PAGE | chip | pg);			// (0xB8) - Set page
	write_cmd(g, KS0108_SET_ADDR | chip | g->p.x);		// (0x40) - Set x Address
}

LLDSPEC void gdisp_lld_write_color(GDisplay *g) {
	uint16_t	data;

	data = (g->p.x >> 6) << 8;		// Set the chip
	if (g->p.color != White)
		data |= 0x01;				// set dot
	write_data(g, data);
}

LLDSPEC void gdisp_lld_draw_pixel(GDisplay *g) {
	set_viewport(g);
	gdisp_lld_write_color(g);
}

#if GDISP_HARDWARE_STREAM_WRITE
	LLDSPEC void gdisp_lld_write_start(GDisplay *g) {
		acquire_bus(g);
		set_viewport(g);
	}

	LLDSPEC void gdisp_lld_write_stop(GDisplay *g) {
		release_bus(g);
	}
#endif

#if GDISP_HARDWARE_STREAM_READ
	LLDSPEC void gdisp_lld_read_start(GDisplay *g) {
		acquire_bus(g);
		set_viewport(g);
		setreadmode(g);
		dummy_read(g);
	}

	LLDSPEC color_t gdisp_lld_read_color(GDisplay *g) {
		uint16_t data;

		data = read_data(g);
		return gdispNative2Color(data);
	}

	LLDSPEC void gdisp_lld_read_stop(GDisplay *g) {
		setwritemode(g);
		release_bus(g);
	}
#endif

#if GDISP_HARDWARE_FILLS
	LLDSPEC void gdisp_lld_fill_area(GDisplay *g) {
		uint8_t data, j;
		set_viewport(g);

		if (g->p.color != White) {
			data = 0xFF;              // set dot
		}
		else {
			data = 0;              // clr dot
		}
		for (j=0; j < (g->p.cy)/8; j++) {
			write_data_repeat(g, data, (g->p.cx));
			(g->p.cy) +=8;
			set_viewport(g);
		}

	}
#endif // GDISP_HARDWARE_FILLS

#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:
				acquire_bus(g);
				write_index(g, 0x28);
				gfxSleepMilliseconds(10);
				write_index(g, 0x10);
				release_bus(g);
				break;

			case powerOn:
				acquire_bus(g);
				write_index(g, 0x11);
				gfxSleepMilliseconds(120);
				write_index(g, 0x29);
				release_bus(g);
				if (g->g.Powermode != powerSleep)
					gdisp_lld_init(g);
			break;

			case powerSleep:
				acquire_bus(g);
				write_index(g, 0x28);
				gfxSleepMilliseconds(10);
				write_index(g, 0x10);
				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:
				acquire_bus(g);

				write_index(g, 0x36);
				write_data(g, 0x08);

				release_bus(g);
				g->g.Height = GDISP_SCREEN_HEIGHT;
				g->g.Width = GDISP_SCREEN_WIDTH;
				break;

			case GDISP_ROTATE_90:
				acquire_bus(g);

				write_index(g, 0x36);
				write_data(g, 0x68);

				release_bus(g);
				g->g.Height = GDISP_SCREEN_WIDTH;
				g->g.Width = GDISP_SCREEN_HEIGHT;
				break;

			case GDISP_ROTATE_180:
				acquire_bus(g);

				write_index(g, 0x36);
				write_data(g, 0xC8);

				release_bus(g);
				g->g.Height = GDISP_SCREEN_HEIGHT;
				g->g.Width = GDISP_SCREEN_WIDTH;
				break;

			case GDISP_ROTATE_270:
				acquire_bus(g);

				write_index(g, 0x36);
				write_data(g, 0xA8);

				release_bus(g);
				g->g.Height = GDISP_SCREEN_WIDTH;
				g->g.Width = GDISP_SCREEN_HEIGHT;
				break;

			default:
				return;
			}

			g->g.Orientation = (orientation_t)g->p.ptr;
			return;

		default:
			return;
		}
	}
#endif

#endif /* GFX_USE_GDISP */
