/*
 * This file is part of the coreboot project.
 *
 * (C) Copyright 2002
 * David Mueller, ELSOFT AG, d.mueller@elsoft.ch
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <assert.h>
#include <console/console.h>
#include <delay.h>
#include <arch/io.h>
#include <device/i2c.h>
#include "clk.h"
#include "i2c.h"
#include "periph.h"

struct __attribute__ ((packed)) i2c_regs
{
	uint8_t con;
	uint8_t _1[3];
	uint8_t stat;
	uint8_t _2[3];
	uint8_t add;
	uint8_t _3[3];
	uint8_t ds;
	uint8_t _4[3];
	uint8_t lc;
	uint8_t _5[3];
};

struct s3c24x0_i2c_bus {
	int bus_num;
	struct i2c_regs *regs;
	enum periph_id periph_id;
};

enum {
	I2cConIntPending = 0x1 << 4,
	I2cConIntEn = 0x1 << 5,
	I2cConAckGen = 0x1 << 7
};

enum {
	I2cStatAck = 0x1 << 0,
	I2cStatAddrZero = 0x1 << 1,
	I2cStatAddrSlave = 0x1 << 2,
	I2cStatArb = 0x1 << 3,
	I2cStatEnable = 0x1 << 4,
	I2cStatStartStop = 0x1 << 5,
	I2cStatBusy = 0x1 << 5,

	I2cStatModeMask = 0x3 << 6,
	I2cStatSlaveRecv = 0x0 << 6,
	I2cStatSlaveXmit = 0x1 << 6,
	I2cStatMasterRecv = 0x2 << 6,
	I2cStatMasterXmit = 0x3 << 6
};

static struct s3c24x0_i2c_bus i2c_busses[] = {
	{
		.bus_num = 0,
		.regs = (void *)0x12c60000,
		.periph_id = PERIPH_ID_I2C0,
	},
	{
		.bus_num = 1,
		.regs = (void *)0x12c70000,
		.periph_id = PERIPH_ID_I2C1,
	},
	{
		.bus_num = 2,
		.regs = (void *)0x12c80000,
		.periph_id = PERIPH_ID_I2C2,
	},
	{
		.bus_num = 3,
		.regs = (void *)0x12c90000,
		.periph_id = PERIPH_ID_I2C3,
	},
	{
		.bus_num = 4,
		.regs = (void *)0x12ca0000,
		.periph_id = PERIPH_ID_I2C4,
	},
	{
		.bus_num = 5,
		.regs = (void *)0x12cb0000,
		.periph_id = PERIPH_ID_I2C5,
	},
	{
		.bus_num = 6,
		.regs = (void *)0x12cc0000,
		.periph_id = PERIPH_ID_I2C6,
	},
	{
		.bus_num = 7,
		.regs = (void *)0x12cd0000,
		.periph_id = PERIPH_ID_I2C7,
	},
};




static int i2c_int_pending(struct i2c_regs *regs)
{
	return readb(&regs->con) & I2cConIntPending;
}

static void i2c_clear_int(struct i2c_regs *regs)
{
	writeb(readb(&regs->con) & ~I2cConIntPending, &regs->con);
}

static void i2c_ack_enable(struct i2c_regs *regs)
{
	writeb(readb(&regs->con) | I2cConAckGen, &regs->con);
}

static void i2c_ack_disable(struct i2c_regs *regs)
{
	writeb(readb(&regs->con) & ~I2cConAckGen, &regs->con);
}

static int i2c_got_ack(struct i2c_regs *regs)
{
	return !(readb(&regs->stat) & I2cStatAck);
}

static int i2c_wait_for_idle(struct i2c_regs *regs)
{
	int timeout = 1000 * 100; // 1s.
	while (timeout--) {
		if (!(readb(&regs->stat) & I2cStatBusy))
			return 0;
		udelay(10);
	}
	printk(BIOS_ERR, "I2C timeout waiting for idle.\n");
	return 1;
}

static int i2c_wait_for_int(struct i2c_regs *regs)
{
	int timeout = 1000 * 100; // 1s.
	while (timeout--) {
		if (i2c_int_pending(regs))
			return 0;
		udelay(10);
	}
	printk(BIOS_ERR, "I2C timeout waiting for I2C interrupt.\n");
	return 1;
}




static int i2c_send_stop(struct i2c_regs *regs)
{
	uint8_t mode = readb(&regs->stat) & (I2cStatModeMask);
	writeb(mode | I2cStatEnable, &regs->stat);
	i2c_clear_int(regs);
	return i2c_wait_for_idle(regs);
}

static int i2c_send_start(struct i2c_regs *regs, int read, int chip)
{
	writeb(chip << 1, &regs->ds);
	uint8_t mode = read ? I2cStatMasterRecv : I2cStatMasterXmit;
	writeb(mode | I2cStatStartStop | I2cStatEnable, &regs->stat);
	i2c_clear_int(regs);

	if (i2c_wait_for_int(regs))
		return 1;

	if (!i2c_got_ack(regs)) {
		// Nobody home, but they may just be asleep.
		return 1;
	}

	return 0;
}

static int i2c_xmit_buf(struct i2c_regs *regs, uint8_t *data, int len)
{
	ASSERT(len);

	i2c_ack_enable(regs);

	int i;
	for (i = 0; i < len; i++) {
		writeb(data[i], &regs->ds);

		i2c_clear_int(regs);
		if (i2c_wait_for_int(regs))
			return 1;

		if (!i2c_got_ack(regs)) {
			printk(BIOS_INFO, "I2c nacked.\n");
			return 1;
		}
	}

	return 0;
}

static int i2c_recv_buf(struct i2c_regs *regs, uint8_t *data, int len)
{
	ASSERT(len);

	i2c_ack_enable(regs);

	int i;
	for (i = 0; i < len; i++) {
		if (i == len - 1)
			i2c_ack_disable(regs);

		i2c_clear_int(regs);
		if (i2c_wait_for_int(regs))
			return 1;

		data[i] = readb(&regs->ds);
	}

	return 0;
}

int platform_i2c_transfer(unsigned bus, struct i2c_seg *segments, int seg_count)
{
	struct s3c24x0_i2c_bus *i2c = &i2c_busses[bus];
	struct i2c_regs *regs = i2c->regs;
	int res = 0;

	if (!regs || i2c_wait_for_idle(regs))
		return 1;

	writeb(I2cStatMasterXmit | I2cStatEnable, &regs->stat);

	int i;
	for (i = 0; i < seg_count; i++) {
		struct i2c_seg *seg = &segments[i];

		res = i2c_send_start(regs, seg->read, seg->chip);
		if (res)
			break;
		if (seg->read)
			res = i2c_recv_buf(regs, seg->buf, seg->len);
		else
			res = i2c_xmit_buf(regs, seg->buf, seg->len);
		if (res)
			break;
	}

	return i2c_send_stop(regs) || res;
}

void i2c_init(unsigned bus, int speed, int slaveadd)
{
	struct s3c24x0_i2c_bus *i2c = &i2c_busses[bus];

	unsigned long freq, pres = 16, div;
	unsigned long val;

	freq = clock_get_periph_rate(i2c->periph_id);
	// Calculate prescaler and divisor values.
	if ((freq / pres / (16 + 1)) > speed)
		/* set prescaler to 512 */
		pres = 512;

	div = 0;

	while ((freq / pres / (div + 1)) > speed)
		div++;

	// Set prescaler, divisor according to freq, also set ACKGEN, IRQ.
	val = (div & 0x0f) | 0xa0 | ((pres == 512) ? 0x40 : 0);
	writel(val, &i2c->regs->con);

	// Init to SLAVE RECEIVE mode and clear I2CADDn.
	writel(0, &i2c->regs->stat);
	writel(slaveadd, &i2c->regs->add);
	// program Master Transmit (and implicit STOP).
	writel(I2cStatMasterXmit | I2cStatEnable, &i2c->regs->stat);
}
