/*
 * ssd1306.c
 *
 *  Created on: Jan 13, 2016
 *      Author: tkl
 */

#include <string.h>
#include "i2c.h"
#include "font.h"
#include "bitmap.h"
#include "ssd1306.h"

#define MAX_COLUMN			128
#define MAX_ROW				64
#define PAGE_COUNT			8
#define HEAD_PAGE_COUNT		2
#define BODY_PAGE_COUNT		PAGE_COUNT - HEAD_PAGE_COUNT

#define CMD_MODE			0x80
#define DATA_MODE			0x40

#define COLUMN_ADDR 		0x21
#define PAGE_ADDR			0x22
#define DISPLAY_OFF			0xAE

static const char init_sequence[] = {
		0x80, 0xAE, 0x80, 0x20, 0x80, 0x10, 0x80, 0xb0, 0x80, 0xc8, 0x80, 0x00,
		0x80, 0x10, 0x80, 0x40, 0x80, 0x81, 0x80, 0x7f, 0x80, 0xa1, 0x80, 0xa6,
		0x80, 0xa8, 0x80, 0x3f, 0x80, 0xa4, 0x80, 0xd3, 0x80, 0x00, 0x80, 0xd5,
		0x80, 0xf0, 0x80, 0xd9, 0x80, 0x22, 0x80, 0xda, 0x80, 0x12, 0x80, 0xdb,
		0x80, 0x20, 0x80, 0x8d, 0x80, 0x14, 0x80, 0xaf
};

static int set_column(const struct ssd1306 *dev, char column)
{
	char buf[6];
	buf[0] = CMD_MODE;
	buf[1] = COLUMN_ADDR;
	buf[2] = CMD_MODE;
	buf[3] = column;
	buf[4] = CMD_MODE;
	buf[5] = MAX_COLUMN - 1;
	return i2c_write(dev->dev, dev->i2c_addr, buf, 6);
}

static int set_page(const struct ssd1306 *dev, char page)
{
	char buf[6];
	buf[0] = CMD_MODE;
	buf[1] = PAGE_ADDR;
	buf[2] = CMD_MODE;
	buf[3] = page;
	buf[4] = CMD_MODE;
	buf[5] = MAX_ROW / PAGE_COUNT - 1;
	return i2c_write(dev->dev, dev->i2c_addr, buf, 6);
}

static int clear_head(const struct ssd1306 *dev)
{
	int ret, i, j;
	char buf[2];

	ret = set_column(dev, 0);
	ret |= set_page(dev, 0);

	buf[0] = DATA_MODE;
	buf[1] = 0x00;
	for(i = 0; i < HEAD_PAGE_COUNT; i++)
		for(j = 0; j < MAX_COLUMN; j++)
			ret |= i2c_write(dev->dev, dev->i2c_addr, buf, 2);
	return ret;
}

static int clear_body(const struct ssd1306 *dev)
{
	int ret, i, j;
	char buf[2];

	ret = set_column(dev, 0);
	ret |= set_page(dev, 2);

	buf[0] = DATA_MODE;
	buf[1] = 0x00;
	for(i = 0; i < BODY_PAGE_COUNT; i++)
		for(j = 0; j < MAX_COLUMN; j++)
			ret |= i2c_write(dev->dev, dev->i2c_addr, buf, 2);
	return ret;
}

static int clear(const struct ssd1306 *dev)
{
	int ret;
	ret = clear_head(dev);
	ret |= clear_body(dev);

	return ret;
}

int ssd1306_open(const struct ssd1306 *dev)
{
	int ret;
	ret = i2c_open((struct i2c *)dev->dev);
	ret |= i2c_write(dev->dev, dev->i2c_addr, init_sequence,
			sizeof(init_sequence) / sizeof(init_sequence[0]));
	ret |= clear(dev);
	ret |= set_column(dev, 0);
	ret |= set_page(dev, 0);

	return ret;
}

int ssd1306_close(const struct ssd1306 *dev)
{
	int ret;
	char buf[2];
	buf[0] = CMD_MODE;
	buf[1] = DISPLAY_OFF;
	ret = i2c_write(dev->dev, dev->i2c_addr, buf, 2);
	ret |= i2c_close((struct i2c *)dev->dev);
	return ret;
}

int ssd1306_write(const struct ssd1306 *dev, const struct bitmap *bitmap)
{
	int ret, i, len = bitmap->x_size * bitmap->y_size / 8;
	char buf[2] = {DATA_MODE, 0x00};

	if(len != MAX_COLUMN * PAGE_COUNT)
		return -1;
	ret = set_column(dev, 0);
	ret |= set_page(dev, 0);
	for(i = 0; i < len; i++) {
		buf[1] = bitmap->buffer[i];
		ret |= i2c_write(dev->dev, dev->i2c_addr, buf, 2);
	}

	return ret;
}
