/*
 * This file is part of the coreboot project.
 *
 * Copyright 2015 Google Inc.
 *
 * 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.
 *
 * 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 <arch/io.h>
#include <assert.h>
#include <console/console.h>
#include <delay.h>
#include <device/i2c.h>
#include <soc/i2c.h>

struct cygnus_i2c_regs {
	u32 i2c_con;
	u32 i2c_timing_con;
	u32 i2c_addr;
	u32 i2c_fifo_master;
	u32 i2c_fifo_slave;
	u32 i2c_bit_bang;
	u32 reserved0[(0x30 - 0x18) / 4];
	u32 i2c_master_comm;
	u32 i2c_slave_comm;
	u32 i2c_int_en;
	u32 i2c_int_status;
	u32 i2c_master_data_wr;
	u32 i2c_master_data_rd;
	u32 i2c_slave_data_wr;
	u32 i2c_slave_data_rd;
	u32 reserved1[(0xb0 - 0x50) / 4];
	u32 i2c_timing_con2;
};

static struct cygnus_i2c_regs *i2c_bus[] = {
	(struct cygnus_i2c_regs *)0x18008000,
	(struct cygnus_i2c_regs *)0x1800b000,
};

#define I2C_TIMEOUT_US	100000 /* 100ms */
#define I2C_FIFO_MAX_SIZE 64

#define ETIMEDOUT 1
#define EINVAL 2
#define EBUSY 3

/* Configuration (0x0) */
#define I2C_SMB_RESET (1 << 31)
#define I2C_SMB_EN (1 << 30)

/* Timing configuration (0x4) */
#define I2C_MODE_400 (1 << 31)

/* Master FIFO control (0xc) */
#define I2C_MASTER_RX_FIFO_FLUSH (1 << 31)
#define I2C_MASTER_TX_FIFO_FLUSH (1 << 30)

/* Master command (0x30) */
#define I2C_MASTER_START_BUSY (1 << 31)
#define I2C_MASTER_STATUS_SFT 25
#define I2C_MASTER_STATUS_MASK (0x7 << I2C_MASTER_STATUS_SFT)
#define I2C_MASTER_PROT_SFT   9
#define I2C_MASTER_PROT_BLK_WR (0x7 << I2C_MASTER_PROT_SFT)
#define I2C_MASTER_PROT_BLK_RD (0x8 << I2C_MASTER_PROT_SFT)

/* Master data write (0x40) */
#define I2C_MASTER_WR_STATUS (1 << 31)

/* Master data read (0x44) */
#define I2C_MASTER_RD_DATA_MASK 0xff

static unsigned int i2c_bus_busy(struct cygnus_i2c_regs *reg_addr)
{
	return read32(&reg_addr->i2c_master_comm) & I2C_MASTER_START_BUSY;
}

static int i2c_wait_bus_busy(struct cygnus_i2c_regs *reg_addr)
{
	int timeout = I2C_TIMEOUT_US;
	while (timeout--) {
		if (!i2c_bus_busy(reg_addr))
			break;
		udelay(1);
	}

	if (timeout <= 0)
		return ETIMEDOUT;

	return 0;
}

static void i2c_flush_fifo(struct cygnus_i2c_regs *reg_addr)
{
	write32(&reg_addr->i2c_fifo_master,
		I2C_MASTER_RX_FIFO_FLUSH | I2C_MASTER_TX_FIFO_FLUSH);
}

static int i2c_write(struct cygnus_i2c_regs *reg_addr, struct i2c_seg *segment)
{
	uint8_t *data = segment->buf;
	unsigned int val, status;
	int i, ret;

	write32(&reg_addr->i2c_master_data_wr, segment->chip << 1);

	for (i = 0; i < segment->len; i++) {
		val = data[i];

		/* mark the last byte */
		if (i == segment->len - 1)
			val |= I2C_MASTER_WR_STATUS;

		write32(&reg_addr->i2c_master_data_wr, val);
	}
	if (segment->len == 0)
		write32(&reg_addr->i2c_master_data_wr, I2C_MASTER_WR_STATUS);

	/*
	 * Now we can activate the transfer.
	 */
	write32(&reg_addr->i2c_master_comm,
		I2C_MASTER_START_BUSY | I2C_MASTER_PROT_BLK_WR);

	ret = i2c_wait_bus_busy(reg_addr);
	if (ret) {
		printk(BIOS_ERR, "I2C bus timeout\n");
		goto flush_fifo;
	}

	/* check transaction successful */
	status = read32(&reg_addr->i2c_master_comm);
	ret = (status & I2C_MASTER_STATUS_MASK) >> I2C_MASTER_STATUS_SFT;
	if (ret) {
		printk(BIOS_ERR, "I2C write error %u\n", status);
		goto flush_fifo;
	}

	return 0;

flush_fifo:
	i2c_flush_fifo(reg_addr);
	return ret;
}

static int i2c_read(struct cygnus_i2c_regs *reg_addr, struct i2c_seg *segment)
{
	uint8_t *data = segment->buf;
	int i, ret;
	unsigned int status;

	write32(&reg_addr->i2c_master_data_wr, segment->chip << 1 | 1);

	/*
	 * Now we can activate the transfer. Specify the number of bytes to read
	 */
	write32(&reg_addr->i2c_master_comm,
		I2C_MASTER_START_BUSY | I2C_MASTER_PROT_BLK_RD | segment->len);

	ret = i2c_wait_bus_busy(reg_addr);
	if (ret) {
		printk(BIOS_ERR, "I2C bus timeout\n");
		goto flush_fifo;
	}

	/* check transaction successful */
	status = read32(&reg_addr->i2c_master_comm);
	ret = (status & I2C_MASTER_STATUS_MASK) >> I2C_MASTER_STATUS_SFT;
	if (ret) {
		printk(BIOS_ERR, "I2C read error %u\n", status);
		goto flush_fifo;
	}

	for (i = 0; i < segment->len; i++)
		data[i] = read32(&reg_addr->i2c_master_data_rd) &
			I2C_MASTER_RD_DATA_MASK;

	return 0;

flush_fifo:
	i2c_flush_fifo(reg_addr);
	return ret;
}

static int i2c_do_xfer(struct cygnus_i2c_regs *reg_addr,
	struct i2c_seg *segment)
{
	int ret;

	if (segment->len > I2C_FIFO_MAX_SIZE - 1) {
		printk(BIOS_ERR,
			"I2C transfer error: segment size (%d) is larger than limit (%d)\n",
			segment->len, I2C_FIFO_MAX_SIZE);
		return EINVAL;
	}

	if (i2c_bus_busy(reg_addr)) {
		printk(BIOS_WARNING, "I2C transfer error: bus is busy\n");
		return EBUSY;
	}

	if (segment->read)
		ret = i2c_read(reg_addr, segment);
	else
		ret = i2c_write(reg_addr, segment);

	return ret;
}

int platform_i2c_transfer(unsigned bus, struct i2c_seg *segments, int seg_count)
{
	int i;
	int res = 0;
	struct cygnus_i2c_regs *regs = i2c_bus[bus];
	struct i2c_seg *seg = segments;

	for (i = 0; i < seg_count; i++, seg++) {
		res = i2c_do_xfer(regs, seg);
		if (res)
			break;
	}
	return res;
}

void i2c_init(unsigned int bus, unsigned int hz)
{
	struct cygnus_i2c_regs *regs = i2c_bus[bus];

	assert(bus >= 0 && bus <= 1);

	setbits_le32(&regs->i2c_con, I2C_SMB_RESET);
	udelay(100); /* wait 100 usec per spec */
	clrbits_le32(&regs->i2c_con, I2C_SMB_RESET);

	switch (hz) {
	case 100000:
		clrbits_le32(&regs->i2c_timing_con, I2C_MODE_400);
		break;
	case 400000:
		setbits_le32(&regs->i2c_timing_con, I2C_MODE_400);
		break;
	default:
		printk(BIOS_ERR, "I2C bus does not support frequency %d Hz\n",
			hz);
		break;
	}

	i2c_flush_fifo(regs);

	/* disable all interrupts */
	write32(&regs->i2c_int_en, 0);

	/* clear all pending interrupts */
	write32(&regs->i2c_int_status, 0xffffffff);

	write32(&regs->i2c_con, I2C_SMB_EN);
}