/*
 * $Id: oled_ssd1306.c 464 2019-08-01 02:15:54Z true $
 * begin 20190505 true
 */

#include "driver/efm8_smb1.h"
#include "driver/oled_ssd1306.h"


SSD1306 xdata oled;
uint8_t xdata oled_fb[((SSD1306_HEIGHT >> 3) * SSD1306_WIDTH) + 1];

uint8_t pdata ssd1306_buf[8];

volatile bit ssd1306_cb_pending;
volatile uint8_t xdata ssd1306_cb_state;


const char code ssd1306_cmd_init[] = {
		0x00,	// command mode, all following bytes are command data
//		0xae,	// display off
		0xd5,	// set display clock div
		0x80,	//   suggested ratio
		0xa8,	// set multiplex
		0x1f,	//   128x32 (0x3f = 128x64)
		0xd3,	// set display offset
		0x00,	//   none
		0x40,	// set start line
		0x8d,	// set charge pump
		0x14,	//   internal VCC
		0x20,	// set memory addressing mode
		0x00,	//   horizontal mode
		0xa1,	// segremap
		0xc8,	// comscandec
		0xda,	// set compins
		0x02,	//
		0x81,	// set contrast
		0xa0,	//   good default value
		0xd9,	// set precharge
		0xf1,	//   internal vcc
		0xdb,	// set vcomdeselect
		0x30,	//
		0x2e,	// disable scroll
		0xa6,	// non-inverted display
		0xa4	// resume display operations
};

const char code ssd1306_cmd_disp_window_def[] = {
		SSD1306_MODE_CMD,
		SSD1306_CMD_COLUMNADDR,		// columns: 0 to (width - 1)
		0,
		SSD1306_WIDTH - 1,
		SSD1306_CMD_PAGEADDR,		// pages (rows*8): 0 to ((height / 8) - 1)
		0,
		(SSD1306_HEIGHT >> 3) - 1
};


void ssd1306_cb(uint8_t p)
{
	ssd1306_cb_pending = 1;
	ssd1306_cb_state = p;
}

bit ssd1306_cb_get()
{
	bit ret;

	ret = ssd1306_cb_pending;
	ssd1306_cb_pending = 0;

	return ret;
}



void ssd1306_idle_wait()
{
#ifdef SSD1306_RESET_ON_COMM_FAIL
	uint16_t timeout = 2000;
#endif

	while (!ssd1306_idle()) {
#ifdef SSD1306_RESET_ON_COMM_FAIL
		timeout--;
		if (!timeout) {
			RSTSRC |=  RSTSRC_SWRSF__SET;
		}
#endif
	}
}

/*
 * sends a single command byte.
 */
void ssd1306_cmd(uint8_t cmd)
{
	uint8_t xdata *b;

	b = ssd1306_buf;

	*b++ = SSD1306_MODE_CMD;
	*b++ = cmd;

	ssd1306_write(ssd1306_buf, 2, 0);

	ssd1306_idle_wait();
}

/*
 * sets up the SSD1306.
 * does not clear the screen, turn on the screen, or send data.
 * you may want to use the callback for that.
 */
void ssd1306_init_step3()
{
	ssd1306_set_display(1);
	ssd1306_set_display(1);	// just to make sure, may fix no display bug
	oled.callback = 0;
	oled.state = SSD1306_STATE_INITIALIZED | SSD1306_STATE_SET_PIXEL;
}
void ssd1306_init_step2()
{
	ssd1306_cls(&oled);
	ssd1306_update();
	oled.callback = ssd1306_init_step3;
}
void ssd1306_init(uint8_t display_off_during_init)
{
	oled.width = SSD1306_WIDTH;
	oled.height = SSD1306_HEIGHT;
	oled.state = 0;
	oled.mode = oled_fb;
	oled.fb = oled_fb + 1;

	if (display_off_during_init) {
		ssd1306_set_display(1);
	}

	ssd1306_write(ssd1306_cmd_init, (sizeof(ssd1306_cmd_init)), ssd1306_cb);

	if (display_off_during_init) {
		oled.callback = ssd1306_init_step2;
	} else {
		oled.callback = ssd1306_init_step3;
	}
}

void ssd1306_update()
{
	// reset our drawing window
	ssd1306_write(ssd1306_cmd_disp_window_def, (sizeof(ssd1306_cmd_disp_window_def)), 0);
	ssd1306_idle_wait();

	// write our framebuffer
	// since our i2c routine is state driven, and we let the state machine handle it,
	// we can't just send some static data (in this case, first byte selecting mode)
	// and then send our framebuffer. our display requires sending the comm mode
	// byte first before receiving framebuffer data. so we waste a byte to do this...
	// alternative would be to rewrite i2c routines to handle this...
	*oled.mode = SSD1306_MODE_DATA;
	ssd1306_write((const char *)oled.mode, ((oled.height >> 3) * oled.width) + 1, ssd1306_cb);
}

void ssd1306_cls(SSD1306 xdata *o)
{
	uint16_t len;
	uint8_t xdata *buf;

	len = ((o->height >> 3) * o->width);
	buf = o->fb;

	while (len--) {
		*buf++ = 0;
	}
}

void ssd1306_set_display(uint8_t display)
{
	uint8_t xdata *b;

	b = ssd1306_buf;

	*b++ = SSD1306_MODE_CMD;
	*b++ = display ? SSD1306_CMD_DISPLAY_ON : SSD1306_CMD_DISPLAY_OFF;

	ssd1306_write(ssd1306_buf, 2, 0);
	ssd1306_idle_wait();
}

void ssd1306_set_invert(uint8_t invert)
{
	uint8_t xdata *b;

	b = ssd1306_buf;

	*b++ = SSD1306_MODE_CMD;
	*b++ = invert ? SSD1306_CMD_INVERT_ON : SSD1306_CMD_INVERT_OFF;

	ssd1306_write(ssd1306_buf, 2, 0);
	ssd1306_idle_wait();
}

void ssd1306_set_contrast(uint8_t contrast)
{
	uint8_t xdata *b;

	b = ssd1306_buf;

	*b++ = SSD1306_MODE_CMD;
	*b++ = SSD1306_CMD_CONTRAST;
	*b++ = contrast;

	ssd1306_write(ssd1306_buf, 3, 0);
	ssd1306_idle_wait();
}

void ssd1306_set_flip(uint8_t flip)
{
	uint8_t xdata *b;

	b = ssd1306_buf;

	*b++ = SSD1306_MODE_CMD;
	*b++ = flip ? SSD1306_CMD_COMSCANINC : SSD1306_CMD_COMSCANDEC;

	ssd1306_write(ssd1306_buf, 2, 0);
	ssd1306_idle_wait();

	if (flip) {
		oled.state |=  SSD1306_STATE_FLIP;
	} else {
		oled.state &= ~SSD1306_STATE_FLIP;
	}
}

void ssd1306_set_mirror(uint8_t mirror)
{
	uint8_t xdata *b;

	b = ssd1306_buf;

	*b++ = SSD1306_MODE_CMD;
	*b++ = mirror ? SSD1306_CMD_SEGREMAP : SSD1306_CMD_SEGREMAP_MIRROR;

	ssd1306_write(ssd1306_buf, 2, 0);
	ssd1306_idle_wait();

	if (mirror) {
		oled.state |=  SSD1306_STATE_MIRROR;
	} else {
		oled.state &= ~SSD1306_STATE_MIRROR;
	}
}

void ssd1306_set_flipmirror(uint8_t flipm)
{
	uint8_t xdata *b;

	b = ssd1306_buf;

	ssd1306_set_flip(flipm);
	ssd1306_set_mirror(flipm);
}
