/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2012 Henrik Nordstrom <henrik@henriknordstrom.net>
 * Copyright (C) 2013 Alexandru Gagniuc <mr.nuke.me@gmail.com>
 *
 * 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; 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.
 *
 * Setup helpers for Two Wire Interface (TWI) (I2C) Allwinner CPUs
 *
 * Only functionality for I2C master is provided.
 * Largely based on the uboot-sunxi code.
 */

#include "memmap.h"
#include "twi.h"

#include <arch/io.h>
#include <delay.h>
#include <device/i2c_simple.h>

#define TWI_BASE(n)			(A1X_TWI0_BASE + 0x400 * (n))

#define TWI_TIMEOUT			(50 * 1000)

static u8 is_busy(struct a1x_twi *twi)
{
	return (read32(&twi->stat) != TWI_STAT_IDLE);
}

static enum cb_err wait_until_idle(struct a1x_twi *twi)
{
	u32 i = TWI_TIMEOUT;
	while (i-- && is_busy((twi)))
		udelay(1);
	return i ? CB_SUCCESS : CB_ERR;
}

/* FIXME: This function is basic, and unintelligent */
static void configure_clock(struct a1x_twi *twi, u32 speed_hz)
{
	/* FIXME: We assume clock is 24MHz, which may not be the case */
	u32 apb_clk = 24000000, m, n;

	/* Pre-divide the clock by 8 */
	n = 3;
	m = (apb_clk >> n) / speed_hz;
	write32(&twi->clk, TWI_CLK_M(m) | TWI_CLK_N(n));
}

void a1x_twi_init(u8 bus, u32 speed_hz)
{
	u32 i = TWI_TIMEOUT;
	struct a1x_twi *twi = (void *)TWI_BASE(bus);

	configure_clock(twi, speed_hz);

	/* Enable the I2C bus */
	write32(&twi->ctl, TWI_CTL_BUS_EN);
	/* Issue soft reset */
	write32(&twi->reset, 1);

	while (i-- && read32(&twi->reset))
		udelay(1);
}

static void clear_interrupt_flag(struct a1x_twi *twi)
{
	write32(&twi->ctl, read32(&twi->ctl) & ~TWI_CTL_INT_FLAG);
}

static void i2c_send_data(struct a1x_twi *twi, u8 data)
{
	write32(&twi->data, data);
	clear_interrupt_flag(twi);
}

static enum twi_status wait_for_status(struct a1x_twi *twi)
{
	u32 i = TWI_TIMEOUT;
	/* Wait until interrupt is asserted again */
	while (i-- && !(read32(&twi->ctl) & TWI_CTL_INT_FLAG))
		udelay(1);
	/* A timeout here most likely indicates a bus error */
	return i ? read32(&twi->stat) : TWI_STAT_BUS_ERROR;
}

static void i2c_send_start(struct a1x_twi *twi)
{
	u32 reg32, i;

	/* Send START condition */
	reg32 = read32(&twi->ctl);
	reg32 &= ~TWI_CTL_INT_FLAG;
	reg32 |= TWI_CTL_M_START;
	write32(&twi->ctl, reg32);

	/* M_START is automatically cleared after condition is transmitted */
	i = TWI_TIMEOUT;
	while (i-- && (read32(&twi->ctl) & TWI_CTL_M_START))
		udelay(1);
}

static void i2c_send_stop(struct a1x_twi *twi)
{
	u32 reg32;

	/* Send STOP condition */
	reg32 = read32(&twi->ctl);
	reg32 &= ~TWI_CTL_INT_FLAG;
	reg32 |= TWI_CTL_M_STOP;
	write32(&twi->ctl, reg32);
}

static int i2c_read(struct a1x_twi *twi, uint8_t chip,
			uint8_t *buf, size_t len)
{
	unsigned count = len;
	enum twi_status expected_status;

	/* Send restart for read */
	i2c_send_start(twi);
	if (wait_for_status(twi) != TWI_STAT_TX_RSTART)
		return CB_ERR;

	/* Send chip address */
	i2c_send_data(twi, chip << 1 | 1);
	if (wait_for_status(twi) != TWI_STAT_TX_AR_ACK)
		return CB_ERR;

	/* Start ACK-ing received data */
	setbits_le32(&twi->ctl, TWI_CTL_A_ACK);
	expected_status = TWI_STAT_RXD_ACK;

	/* Read data */
	while (count > 0) {
		if (count == 1) {
			/* Do not ACK the last byte */
			clrbits_le32(&twi->ctl, TWI_CTL_A_ACK);
			expected_status = TWI_STAT_RXD_NAK;
		}

		clear_interrupt_flag(twi);

		if (wait_for_status(twi) != expected_status)
			return CB_ERR;

		*buf++ = read32(&twi->data);
		count--;
	}

	return len;
}

static int i2c_write(struct a1x_twi *twi, uint8_t chip,
		     const uint8_t *buf, size_t len)
{
	size_t count = len;

	i2c_send_start(twi);
	if (wait_for_status(twi) != TWI_STAT_TX_START)
		return CB_ERR;

	/* Send chip address */
	i2c_send_data(twi, chip << 1);
	if (wait_for_status(twi) != TWI_STAT_TX_AW_ACK)
		return CB_ERR;

	/* Send data */
	while (count > 0) {
		i2c_send_data(twi, *buf++);
		if (wait_for_status(twi) != TWI_STAT_TXD_ACK)
			return CB_ERR;
		count--;
	}

	return len;
}

int platform_i2c_transfer(unsigned bus, struct i2c_msg *segments, int count)
{
	int i, ret = CB_SUCCESS;
	struct i2c_msg *seg = segments;
	struct a1x_twi *twi = (void *)TWI_BASE(bus);


	if (wait_until_idle(twi) != CB_SUCCESS)
		return CB_ERR;

	for (i = 0; i < count; i++) {
		seg = segments + i;

		if (seg->flags & I2C_M_RD) {
			ret = i2c_read(twi, seg->slave, seg->buf, seg->len);
			if (ret < 0)
				break;
		} else {
			ret = i2c_write(twi, seg->slave, seg->buf, seg->len);
			if (ret < 0)
				break;
		}
	}

	/* Don't worry about the status. STOP is on a best-effort basis */
	i2c_send_stop(twi);

	return ret;
}