/* Copyright 2020 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * LCD driver for I2C LCD 2004.
 */

#include "i2c.h"
#include "lcd.h"
#include "timer.h"

struct lcd_state_info {
	uint8_t addr;
	uint8_t displayfunction;
	uint8_t displaycontrol;
	uint8_t backlightval;
};

static struct lcd_state_info state = {
	.addr = LCD_SLAVE_ADDR,
	.backlightval = LCD_BACKLIGHT,
	.displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5X8DOTS,
};

/************ low level data pushing commands **********/
/* write either command or data */
static void expander_write(uint8_t data)
{
	i2c_write8(I2C_PORT_TCPC, LCD_SLAVE_ADDR, 0x00, data |
		state.backlightval);
}

static void pulse_enable(uint8_t data)
{
	expander_write(data | LCD_EN);/* En high */
	usleep(1);	/* enable pulse must be >450ns */

	expander_write(data & ~LCD_EN);/* En low */
	usleep(50);	/* commands need > 37us to settle */
}

static void write_4bits(uint8_t value)
{
	expander_write(value);
	pulse_enable(value);
}

static void send(uint8_t value, uint8_t mode)
{
	uint8_t highnib = value & 0xf0;
	uint8_t lownib = (value << 4) & 0xf0;

	write_4bits(highnib | mode);
	write_4bits(lownib | mode);
}

/*********** mid level commands, for sending data/cmds */
static void command(uint8_t value)
{
	send(value, 0);
}

/********** high level commands, for the user! */
void lcd_clear(void)
{
	command(LCD_CLEAR_DISPLAY);/* clear display, set cursor to zero */
	usleep(2000);	/* this command takes a long time! */
}

void lcd_set_cursor(uint8_t col, uint8_t row)
{
	int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };

	command(LCD_SET_DDRAMADDR | (col + row_offsets[row]));
}

void lcd_print_char(char data)
{
	send(data, LCD_RS);
}

void lcd_print_string(const char *str)
{
	while (*str)
		lcd_print_char(*str++);
}

/* Turn the display on/off (quickly) */
void lcd_disable_display(void)
{
	state.displaycontrol &= ~LCD_DISPLAY_ON;
	command(LCD_DISPLAY_CONTROL | state.displaycontrol);
}
void lcd_enable_display(void)
{
	state.displaycontrol |= LCD_DISPLAY_ON;
	command(LCD_DISPLAY_CONTROL | state.displaycontrol);
}

/* Turn the (optional) backlight off/on */
void lcd_disable_backlight(void)
{
	state.backlightval = LCD_NO_BACKLIGHT;
	expander_write(0);
}

void lcd_enable_backlight(void)
{
	state.backlightval = LCD_BACKLIGHT;
	expander_write(0);
}

void lcd_init(uint8_t cols, uint8_t rows, uint8_t dotsize)
{
	if (rows > 1)
		state.displayfunction |= LCD_2LINE;

	/* for some 1 line displays you can select a 10 pixel high font */
	if ((dotsize != 0) && (rows == 1))
		state.displayfunction |= LCD_5X10DOTS;

	/* SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
	 * according to datasheet, we need at least 40ms after power rises
	 * above 2.7V before sending commands. Arduino can turn on way
	 * before 4.5V so we'll wait 50
	 */
	usleep(50);

	/* Now we pull both RS and R/W low to begin commands */
	/* reset expanderand turn backlight off (Bit 8 =1) */
	expander_write(state.backlightval);
	usleep(1000);

	/* put the LCD into 4 bit mode
	 * this is according to the hitachi HD44780 datasheet
	 * figure 24, pg 46
	 * we start in 8bit mode, try to set 4 bit mode
	 */
	write_4bits(0x03 << 4);
	usleep(4500); /* wait min 4.1ms */
	/*second try */
	write_4bits(0x03 << 4);
	usleep(4500); /* wait min 4.1ms */
	/* third go! */
	write_4bits(0x03 << 4);
	usleep(150);
	/* finally, set to 4-bit interface */
	write_4bits(0x02 << 4);

	/* set # lines, font size, etc. */
	command(LCD_FUNCTION_SET | state.displayfunction);

	/* turn the display on with no cursor or blinking default */
	state.displaycontrol = LCD_DISPLAY_ON | LCD_CURSOR_OFF | LCD_BLINK_OFF;
	lcd_enable_display();

	/* clear it off */
	lcd_clear();

	/* Initialize to default text direction (for roman languages)
	 * and set the entry mode
	 */
	command(LCD_ENTRYMODE_SET | LCD_ENTRY_LEFT | LCD_ENTRY_SHIFT_DECREMENT);

	lcd_set_cursor(0, 0);
}
