/* I2C EEPROM memory read and write utilities
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
#include "board.h"
#include <stdbool.h>

/* Low-Level I2C Routines */

static const Pin pin_sda = {PIO_PA30, PIOA, ID_PIOA, PIO_OUTPUT_1, PIO_OPENDRAIN };
static const Pin pin_sda_in = {PIO_PA30, PIOA, ID_PIOA, PIO_INPUT, PIO_DEFAULT };
static const Pin pin_scl = {PIO_PA31, PIOA, ID_PIOA, PIO_OUTPUT_1, PIO_OPENDRAIN };

static void i2c_delay()
{
	volatile int v;
	int i;

	/* 100 cycles results in SCL peak length of 44us, so it's about
	 * 440ns per cycle here */
	for (i = 0; i < 14; i++) {
		v = 0;
	}
}

void i2c_pin_init(void)
{
	PIO_Configure(&pin_scl, PIO_LISTSIZE(pin_scl));
	PIO_Configure(&pin_sda, PIO_LISTSIZE(pin_sda));
}

static void set_scl(void)
{
	PIO_Set(&pin_scl);
	i2c_delay();
}

static void set_sda(void)
{
	PIO_Set(&pin_sda);
	i2c_delay();
}

static void clear_scl(void)
{
	PIO_Clear(&pin_scl);
	i2c_delay();
}

static void clear_sda(void)
{
	PIO_Clear(&pin_sda);
	i2c_delay();
}

static bool read_sda(void)
{
	bool ret;

	PIO_Configure(&pin_sda_in, PIO_LISTSIZE(pin_sda_in));
	if (PIO_Get(&pin_sda_in))
		ret = true;
	else
		ret = false;
	PIO_Configure(&pin_sda, PIO_LISTSIZE(pin_sda));

	return ret;
}

/* Core I2C Routines */

static bool i2c_started = false;

static void i2c_start_cond(void)
{
	if (i2c_started) {
		set_sda();
		set_scl();
	}

	clear_sda();
	i2c_delay();
	clear_scl();
	i2c_started = true;
}

void i2c_stop_cond(void)
{
	clear_sda();
	set_scl();
	set_sda();
	i2c_delay();
	i2c_started = false;
}

static void i2c_write_bit(bool bit)
{
	if (bit)
		set_sda();
	else
		clear_sda();
	i2c_delay(); // ?
	set_scl();
	clear_scl();
}

static bool i2c_read_bit(void)
{
	bool bit;

	set_sda();
	set_scl();
	bit = read_sda();
	clear_scl();

	return bit;
}

bool i2c_write_byte(bool send_start, bool send_stop, uint8_t byte)
{
	uint8_t bit;
	bool nack;

	if (send_start)
		i2c_start_cond();

	for (bit = 0; bit < 8; bit++) {
		i2c_write_bit((byte & 0x80) != 0);
		byte <<= 1;
	}

	nack = i2c_read_bit();

	if (send_stop)
		i2c_stop_cond();

	return nack;
}

uint8_t i2c_read_byte(bool nack, bool send_stop)
{
	uint8_t byte = 0;
	uint8_t bit;

	for (bit = 0; bit < 8; bit++) {
		byte = (byte << 1) | i2c_read_bit();
	}

	i2c_write_bit(nack);

	if (send_stop)
		i2c_stop_cond();

	return byte;
}


/* EEPROM related code */

int eeprom_write_byte(uint8_t slave, uint8_t addr, uint8_t byte)
{
	bool nack;

	WDT_Restart(WDT);

	/* Write slave address */
	nack = i2c_write_byte(true, false, slave << 1);
	if (nack)
		goto out_stop;
	nack = i2c_write_byte(false, false, addr);
	if (nack)
		goto out_stop;
	nack = i2c_write_byte(false, true, byte);
	if (nack)
		goto out_stop;
	/* Wait tWR time to ensure EEPROM is writing correctly (tWR = 5 ms for AT24C02) */
	mdelay(5);

out_stop:
	i2c_stop_cond();
	if (nack)
		return -1;
	else
		return 0;
}

int eeprom_read_byte(uint8_t slave, uint8_t addr)
{
	bool nack;

	WDT_Restart(WDT);

	/* dummy write cycle */
	nack = i2c_write_byte(true, false, slave << 1);
	if (nack)
		goto out_stop;
	nack = i2c_write_byte(false, false, addr);
	if (nack)
		goto out_stop;
	/* Re-start with read */
	nack = i2c_write_byte(true, false, (slave << 1) | 1);
	if (nack)
		goto out_stop;

	return i2c_read_byte(true, true);

out_stop:
	i2c_stop_cond();
	if (nack)
		return -1;
	else
		return 0;
}
