/* USRP E310 Firmware Atmel AVR TWI driver
 * Copyright (C) 2014 Ettus Research
 * This file is part of the USRP E310 Firmware
 * The USRP E310 Firmware 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.
 * The USRP E310 Firmware 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.
 * You should have received a copy of the GNU General Public License
 * along with the USRP E310 Firmware. If not, see <http://www.gnu.org/licenses/>.
 */

#include "i2c_twi.h"
#include "mcu_settings.h"
#include "utils.h"

#include <stdbool.h>
#include <avr/io.h>
#include <util/twi.h>
#include <util/delay.h>

static const uint8_t I2C_TIMEOUT = 10;

static inline uint8_t I2C_READ_ADDR(const uint8_t x)
{
	return (x << 1) | 0x1;
}

static inline uint8_t I2C_WRITE_ADDR(const uint8_t x)
{
	return (x << 1) & 0xfe;
}

void i2c_twi_init_calc(uint32_t rate)
{
	uint8_t twbr;
	twbr = ((F_CPU/rate)-16)/2;

	TWBR = twbr;

	PRR &= ~BIT(PRTWI);

	/* www.mikrocontroller.net/articles/AVR_TWI says this might help ... */
	TWCR &= ~(BIT(TWSTO) | BIT(TWEN));
	TWCR |=	BIT(TWEN);
}

void i2c_twi_init(i2c_speed_t speed)
{
	switch (speed) {
	case I2C_SPEED_400K:
		TWBR = 16;
		break;
	case I2C_SPEED_100K:
		TWBR = 32;
		break;
	default:
		TWBR = 32;
		break;
	}

	/* reset potential prescalers */
	TWSR = 0;

	/* www.mikrocontroller.net/articles/AVR_TWI says this might help ... */
	TWCR &= ~(BIT(TWSTO) | BIT(TWEN));
	TWCR |=	BIT(TWEN);
}

static void i2c_twi_wait_for_complete(void)
{
	uint8_t timeout = 100;

	do {
		_delay_us(10);
		timeout--;
	} while(timeout && !(TWCR & (1<<TWINT)));
}

static void i2c_twi_start(void)
{
	TWCR = BIT(TWINT) | BIT(TWEN) | BIT(TWSTA);
	i2c_twi_wait_for_complete();
}

static void i2c_twi_stop(void)
{
	TWCR = BIT(TWINT) | BIT(TWEN) | BIT(TWSTO);
}


static uint8_t i2c_twi_recv_byte(bool ack)
{
	TWCR = BIT(TWINT) | BIT(TWEN) | (ack ? BIT(TWEA) : 0);
	i2c_twi_wait_for_complete();
	return TWDR;
}

static void i2c_twi_send_byte(uint8_t data)
{
	TWDR = data;
	TWCR = BIT(TWINT) | BIT(TWEN);
	i2c_twi_wait_for_complete();
}

int8_t i2c_twi_read(uint8_t addr, uint8_t reg, uint8_t *value)
{
	/* start the write transaction to select the register */
	i2c_twi_start();
	i2c_twi_send_byte(I2C_WRITE_ADDR(addr));
	i2c_twi_send_byte(reg);

	/* (re)start for the actual read transaction to read back */
	i2c_twi_start();
	i2c_twi_send_byte(I2C_READ_ADDR(addr));
	*value = i2c_twi_recv_byte(false);
	i2c_twi_stop();

	return 0;
}

int8_t i2c_twi_write(uint8_t addr, uint8_t reg, uint8_t value)
{
	i2c_twi_start();
	i2c_twi_send_byte(I2C_WRITE_ADDR(addr));
	i2c_twi_send_byte(reg);
	i2c_twi_send_byte(value);
	i2c_twi_stop();

	return 0;
}

int8_t i2c_twi_read16(uint8_t addr, uint8_t reg, uint16_t *value)
{
	uint8_t msb, lsb;

	/* start the write transaction to select the register */
	i2c_twi_start();
	i2c_twi_send_byte(I2C_WRITE_ADDR(addr));
	i2c_twi_send_byte(reg);

	/* (re)start for the actual read transaction to read back MSB
	 * then LSB, fortunately the datashit describes the opposite w.r.t ACKs*/
	i2c_twi_start();
	i2c_twi_send_byte(I2C_READ_ADDR(addr));
	msb = i2c_twi_recv_byte(true);
	lsb = i2c_twi_recv_byte(false);
	i2c_twi_stop();

	*value = (msb << 8) | lsb;

	return 0;
}

int8_t i2c_twi_write16(uint8_t addr, uint8_t reg, uint16_t value)
{
	uint8_t msb, lsb;

	msb = value >> 8;
	lsb = value & 0xff;

	i2c_twi_start();
	i2c_twi_send_byte(I2C_WRITE_ADDR(addr));
	i2c_twi_send_byte(reg);
	i2c_twi_send_byte(msb);
	i2c_twi_send_byte(lsb);
        i2c_twi_stop();

	return 0;
}

/*
static const uint8_t I2C_ARA_ADDR = 0x0c;
uint8_t i2c_twi_smbus_ara(void)
{
	volatile uint8_t addr;;

	i2c_twi_start();
	i2c_twi_send_byte(I2C_READ_ADDR(I2C_ARA_ADDR));
	addr = i2c_twi_recv_byte(false);
	i2c_twi_stop();
	return addr;
}
*/