/*
 *  /drivers/i2c/busses/i2c-wmt.c
 *
 *  Copyright (C) 2008 WonderMedia Technologies, Inc.
 *  Copyright (C) 2011 Tony Prisk
 *
 * 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.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-id.h>

#include <mach/i2c.h>

#define DRIVER_NAME	"wmt-i2c"
#define MAX_BUS_READY_CNT	50
#define MAX_RX_TIMEOUT		500
#define MAX_TX_TIMEOUT		500

#define wmt_i2c_write_reg(priv, reg, val)	writew(val, priv->base + reg)
#define wmt_i2c_read_reg(priv, reg)		readw(priv->base + reg)

DECLARE_WAIT_QUEUE_HEAD(i2c_wait);
static DEFINE_SPINLOCK(wmt_i2c_irqlock);

struct wmt_i2c_priv {
	void __iomem *base;
	int irq_num;
	enum i2c_mode_e i2c_mode;

	int volatile isr_nack;
	int volatile isr_byte_end;
	int volatile isr_timeout;
	int volatile isr_int_pending;
};

extern int wmt_i2c_add_bus(struct i2c_adapter *);
extern int wmt_i2c_del_bus(struct i2c_adapter *);

void wmt_i2c_set_mode(void *priv_data, enum i2c_mode_e mode)
{
	struct wmt_i2c_priv *priv = priv_data;

	priv->i2c_mode = mode;

	if (mode == I2C_STANDARD_MODE)
		wmt_i2c_write_reg(priv, I2C_TR, I2C_TR_STD_VALUE);
	if (mode == I2C_FAST_MODE)
		wmt_i2c_write_reg(priv, I2C_TR, I2C_TR_FAST_VALUE);
}

static int wmt_i2c_wait_bus_not_busy(void *priv_data)
{
	struct wmt_i2c_priv *priv = priv_data;
	int ret;
	int cnt;

	DECLARE_WAITQUEUE(wait, current);
	add_wait_queue(&i2c_wait, &wait);

	ret = 0;
	cnt = 0;
	while (1) {
		if ((wmt_i2c_read_reg(priv, I2C_CSR) & I2C_CSR_STATUS_MASK) == I2C_CSR_READY)
			break;

		set_current_state(TASK_INTERRUPTIBLE);

		if (signal_pending(current))
			break;

		schedule_timeout(1);
		cnt++;

		if (cnt > MAX_BUS_READY_CNT) {
			ret = -EBUSY;
			printk(KERN_ERR "[i2c] wait but not ready timeout\n");
			break;
		}
	};

	set_current_state(TASK_RUNNING);

	remove_wait_queue(&i2c_wait, &wait);

	return ret;
}


int wmt_i2c_read_msg(void *priv_data, 
			unsigned int slave_addr,
			char *buf,
			unsigned int length,
			int restart,
			int last)
{
	struct wmt_i2c_priv *priv = priv_data;

	unsigned short tcr_value;
	unsigned int xfer_length;
	int is_timeout;
	int ret = 0;
	int wait_event_result = 0;
	u32 reg_tmp;

	if (length <= 0)
		return -1;

	xfer_length = 0;

	if (restart == 0) {
		ret = wmt_i2c_wait_bus_not_busy(priv);
		if (ret < 0)
			return ret;
	}
	
	priv->isr_nack = 0;
	priv->isr_byte_end = 0;
	priv->isr_timeout = 0;
	priv->isr_int_pending = 0;

	reg_tmp = wmt_i2c_read_reg(priv, I2C_CR);
	wmt_i2c_write_reg(priv, I2C_CR, reg_tmp & ~(I2C_CR_TX_NEXT_NO_ACK));

	if (restart == 0) {
		reg_tmp = wmt_i2c_read_reg(priv, I2C_CR);
		wmt_i2c_write_reg(priv, I2C_CR, reg_tmp | I2C_CR_CPU_RDY);
	}

	tcr_value = 0;
	if (priv->i2c_mode == I2C_STANDARD_MODE) {
		tcr_value = (unsigned short)(I2C_TCR_STANDARD_MODE | I2C_TCR_MASTER_READ |\
				(slave_addr & I2C_TCR_SLAVE_ADDR_MASK));
	} else if (priv->i2c_mode == I2C_FAST_MODE) {
		tcr_value = (unsigned short)(I2C_TCR_FAST_MODE | I2C_TCR_MASTER_READ |\
				(slave_addr & I2C_TCR_SLAVE_ADDR_MASK));
	}
	wmt_i2c_write_reg(priv, I2C_TCR, tcr_value);

	if (restart == 1) {
		reg_tmp = wmt_i2c_read_reg(priv, I2C_CR);
		wmt_i2c_write_reg(priv, I2C_CR, reg_tmp | I2C_CR_CPU_RDY);
	}

	if (length == 1) {
		reg_tmp = wmt_i2c_read_reg(priv, I2C_CR);
		wmt_i2c_write_reg(priv, I2C_CR, reg_tmp | I2C_CR_TX_NEXT_NO_ACK);
	}

	ret = 0;
	for (;;) {
		is_timeout = 0;

		wait_event_result = wait_event_interruptible_timeout(i2c_wait, \
			priv->isr_int_pending, (MAX_RX_TIMEOUT * HZ / 1000));
	
		if (likely(wait_event_result > 0)) {
			printk(KERN_INFO "[i2c] wait interrupted (rx)\n");
			ret = 0;
		} else if (likely(priv->isr_int_pending == 0)) {
			printk(KERN_INFO "[i2c] wait timeout (rx)\n");
			is_timeout = 1;
			ret = -ETIMEDOUT;
		}

		if (priv->isr_nack == 1) {
			printk(KERN_INFO "[i2c] write NACK error (rx)\n");
			ret = -EIO;
			break;
		}
		if (priv->isr_timeout == 1) {
			printk(KERN_INFO "[i2c] write SCL timeout error (rx)\n");
			ret = -ETIMEDOUT;
			break;
		}
		if (is_timeout == 1) {
			printk(KERN_INFO "[i2c] write software timeout error (rx)\n");
			ret = -ETIMEDOUT;
			break;
		}

		if (priv->isr_byte_end == 1) {
			buf[xfer_length] = wmt_i2c_read_reg(priv, I2C_CDR) >> 8;
			++xfer_length;
			printk(KERN_INFO "[i2c] recieved BYTE_END\n");
		}

		priv->isr_int_pending = 0;
		priv->isr_nack = 0;
		priv->isr_byte_end = 0;
		priv->isr_timeout = 0;

		if (length > xfer_length) {
			if ((length - 1) == xfer_length) {
				reg_tmp = wmt_i2c_read_reg(priv, I2C_CR);
				wmt_i2c_write_reg(priv, I2C_CR, reg_tmp | (I2C_CR_TX_NEXT_NO_ACK |\
									I2C_CR_CPU_RDY));
			} else {
				reg_tmp = wmt_i2c_read_reg(priv, I2C_CR);
				wmt_i2c_write_reg(priv, I2C_CR, reg_tmp | I2C_CR_CPU_RDY);
			}
		} else if (length == xfer_length) {
			if (last == 1)
				break;
			else {
				printk(KERN_ERR "[i2c] RX restart case\n");
				break;
			}
		} else {
			printk(KERN_ERR "[i2c] read known error\n");
			ret = -EIO;
			break;
		}
	}
		
	return 0;
}

int wmt_i2c_write_msg(void *priv_data, 
			unsigned int slave_addr,
			char *buf,
			unsigned int length,
			int restart,
			int last)
{
	struct wmt_i2c_priv *priv = priv_data;
	unsigned short tcr_value;
	unsigned int xfer_length;
	int is_timeout;
	int ret = 0;
	int wait_event_result;
	u32 reg_tmp;

	printk(KERN_INFO "[i2c] length=%d slave_addr=%d\n", length, slave_addr);

	/* this is some kind of hack that WMT incorporated to do with probing */
	if (slave_addr == WMT_I2C_API_I2C_ADDR)
		return 0;

	if (length < 0)
		return -1;

	xfer_length = 0;

	if (restart == 0) {
		ret = wmt_i2c_wait_bus_not_busy(priv);
		if (ret < 0)
			return ret;
	}

	priv->isr_nack = 0;
	priv->isr_byte_end = 0;
	priv->isr_timeout = 0;
	priv->isr_int_pending = 0;

	if (length == 0)
		wmt_i2c_write_reg(priv, I2C_CDR, 0);
	else
		wmt_i2c_write_reg(priv, I2C_CDR, (unsigned short)(buf[xfer_length] & \
							I2C_CDR_DATA_WRITE_MASK));

	if (restart == 0) {
		reg_tmp = wmt_i2c_read_reg(priv, I2C_CR);
		wmt_i2c_write_reg(priv, I2C_CR, reg_tmp & ~(I2C_CR_TX_END));
		reg_tmp = wmt_i2c_read_reg(priv, I2C_CR);
		wmt_i2c_write_reg(priv, I2C_CR, reg_tmp | I2C_CR_CPU_RDY);
	}

	tcr_value = 0;
	if (priv->i2c_mode == I2C_STANDARD_MODE) {
		tcr_value = (unsigned short)(I2C_TCR_STANDARD_MODE | I2C_TCR_MASTER_WRITE |\
				(slave_addr & I2C_TCR_SLAVE_ADDR_MASK));
	} else if (priv->i2c_mode == I2C_FAST_MODE) {
		tcr_value = (unsigned short)(I2C_TCR_FAST_MODE | I2C_TCR_MASTER_WRITE |\
				(slave_addr & I2C_TCR_SLAVE_ADDR_MASK));
	}
	wmt_i2c_write_reg(priv, I2C_TCR, tcr_value);

	if (restart == 1) {
		reg_tmp = wmt_i2c_read_reg(priv, I2C_CR);
		wmt_i2c_write_reg(priv, I2C_CR, reg_tmp | I2C_CR_CPU_RDY);
	}

	for (;;) {
		is_timeout = 0;

		wait_event_result = wait_event_interruptible_timeout(i2c_wait, \
					priv->isr_int_pending, (MAX_TX_TIMEOUT * HZ / 1000));

		if (likely(wait_event_result > 0)) {
			printk(KERN_INFO "[i2c] wait interrupted (tx)\n");
			ret = 0;
		} else if (likely(priv->isr_int_pending == 0)) {
			printk(KERN_INFO "[i2c] wait timeout (tx)\n");
			is_timeout = 1;
			ret = -ETIMEDOUT;
		}

		if (priv->isr_nack == 1) {
			printk(KERN_INFO "[i2c] write NACK error (tx)\n");
			ret = -EIO;
			break;
		}
		if (priv->isr_timeout == 1) {
			printk(KERN_INFO "[i2c] write SCL timeout error (tx)\n");
			ret = -ETIMEDOUT;
			break;
		}
		if (is_timeout == 1) {
			printk(KERN_INFO "[i2c] write software timeout error (tx)\n");
			ret = -ETIMEDOUT;
			break;
		}

		if (priv->isr_byte_end == 1) {
			buf[xfer_length] = wmt_i2c_read_reg(priv, I2C_CDR) >> 8;
			++xfer_length;
			printk(KERN_INFO "[i2c] recieved BYTE_END\n");
		}

		priv->isr_int_pending = 0;
		priv->isr_nack = 0;
		priv->isr_byte_end = 0;
		priv->isr_timeout = 0;

		if (length == 0) {
			wmt_i2c_write_reg(priv, I2C_CR, I2C_CR_TX_END | I2C_CR_CPU_RDY | I2C_CR_ENABLE);
			break;
		}

		if (length > xfer_length) {
			wmt_i2c_write_reg(priv, I2C_CDR, (unsigned short)(buf[xfer_length] & I2C_CDR_DATA_WRITE_MASK));
			wmt_i2c_write_reg(priv, I2C_CR, I2C_CR_CPU_RDY | I2C_CR_ENABLE);
		} else if (length == xfer_length) {
			if (last == 1) {
				wmt_i2c_write_reg(priv, I2C_CR, I2C_CR_TX_END | I2C_CR_CPU_RDY | I2C_CR_ENABLE);
				break;
			} else {
				wmt_i2c_write_reg(priv, I2C_CR, I2C_CR_ENABLE);
				printk(KERN_ERR "[i2c] TX restart case\n");
				break;
			}
		} else {
			printk(KERN_ERR "[i2c] write unknown error\n");
			ret = -EIO;
			break;
		}
	}

	return 0;
}

static void wmt_i2c_reset(void *priv_data)
{
	struct wmt_i2c_priv *priv = priv_data;
	unsigned short tmp;

	wmt_i2c_write_reg(priv, I2C_CR, 0);
	wmt_i2c_write_reg(priv, I2C_DIV, APB_96M_I2C_DIV);
	wmt_i2c_write_reg(priv, I2C_ISR, I2C_ISR_ALL_WRITE_CLEAR);
	wmt_i2c_write_reg(priv, I2C_IMR, I2C_IMR_ALL_ENABLE);

	wmt_i2c_write_reg(priv, I2C_CR, I2C_CR_ENABLE);
	tmp = wmt_i2c_read_reg(priv, I2C_CR);
	wmt_i2c_write_reg(priv, I2C_ISR, I2C_ISR_ALL_WRITE_CLEAR);

	wmt_i2c_write_reg(priv, I2C_TR, I2C_TR_STD_VALUE);
}

static irqreturn_t wmt_i2c_handler(int irq, void *dev_id)
{
	struct wmt_i2c_priv *priv = dev_id;

	int wakeup;
	unsigned short isr_status;
	unsigned short tmp;
	unsigned long flags;

	isr_status = wmt_i2c_read_reg(priv, I2C_ISR);
	wakeup = 0;

	if (isr_status & I2C_ISR_NACK_ADDR) {
		wmt_i2c_write_reg(priv, I2C_ISR, I2C_ISR_NACK_ADDR_WRITE_CLEAR);
		tmp = wmt_i2c_read_reg(priv, I2C_CSR);
		priv->isr_nack = 1;
		wakeup = 1;
	}
	if (isr_status & I2C_ISR_BYTE_END) {
		wmt_i2c_write_reg(priv, I2C_ISR, I2C_ISR_BYTE_END_WRITE_CLEAR);
		priv->isr_byte_end = 1;
		wakeup = 1;
	}
	if (isr_status & I2C_ISR_SCL_TIMEOUT) {
		wmt_i2c_write_reg(priv, I2C_ISR, I2C_ISR_SCL_TIMEOUT_WRITE_CLEAR);
		priv->isr_timeout = 1;
		wakeup = 1;
	}

	if (wakeup) {
		spin_lock_irqsave(&wmt_i2c_irqlock, flags);
		priv->isr_int_pending = 1;
		spin_unlock_irqrestore(&wmt_i2c_irqlock, flags);
		wake_up_interruptible(&i2c_wait);
	} else
		printk(KERN_ERR "[i2c] Unknown I2C ISR handle 0x%4.4X\n", isr_status);

	return IRQ_HANDLED;
}

static struct i2c_algo_wmt_data wmt_i2c_data = {
	write_msg:		wmt_i2c_write_msg,
	read_msg:		wmt_i2c_read_msg,
	wait_bus_not_busy:	wmt_i2c_wait_bus_not_busy,
	reset:			wmt_i2c_reset,
	set_mode:		wmt_i2c_set_mode,
	udelay:			I2C_ALGO_UDELAY,
	timeout:		I2C_ALGO_TIMEOUT,
};

static struct i2c_adapter wmt_i2c_ops = {
	.owner			= THIS_MODULE,
	.id			= I2C_ALGO_WMT,
	.algo_data		= &wmt_i2c_data,
	.name			= "wmt_i2c_adapter",
	.retries		= I2C_ADAPTER_RETRIES,
};

/* Hacky code */

void wmt_i2c_gpio_init(void)
{
	void __iomem *gpio_base;
	u32 tmp_reg;

	gpio_base = ioremap(0xD8110500, 4);
	if  (gpio_base) {
		tmp_reg = readl(gpio_base);
		writel(tmp_reg & ~(0x03), gpio_base);
		iounmap(gpio_base);
	} else
		printk(KERN_ERR "[i2c] Failed to reprogram GPIO pins\n");
}

/* End hacky code */

void wmt_i2c_init_hardware(struct wmt_i2c_priv *priv)
{
	unsigned short tmp;

	wmt_i2c_gpio_init();
/* TODO: PMC  - I2C0 Clock Enable
	*(u32 *)(0xD8130250) |= 0x00000020;
*/
	wmt_i2c_write_reg(priv, I2C_CR, 0);
	wmt_i2c_write_reg(priv, I2C_DIV, APB_166M_I2C_DIV);
	wmt_i2c_write_reg(priv, I2C_ISR, I2C_ISR_ALL_WRITE_CLEAR);
	wmt_i2c_write_reg(priv, I2C_IMR, I2C_IMR_ALL_ENABLE);

	wmt_i2c_write_reg(priv, I2C_CR, I2C_CR_ENABLE);
	tmp = wmt_i2c_read_reg(priv, I2C_CR);
	wmt_i2c_write_reg(priv, I2C_ISR, I2C_ISR_ALL_WRITE_CLEAR);

	wmt_i2c_write_reg(priv, I2C_TR, I2C_TR_STD_VALUE);
}

static int __init wmt_i2c_probe(struct platform_device *pdev)
{
	struct wmt_i2c_priv *priv;
	struct resource *res;
	int ret;

	printk(KERN_INFO "[i2c] Probing I2C driver\n");

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		printk(KERN_ERR "[i2c] Getting platform resource failed: Memory-mapped IO\n");
		return -ENXIO;
	}

	if (!request_mem_region(res->start, res->end - res->start + 1, DRIVER_NAME)) {
		printk(KERN_ERR "[i2c] Getting memory region failed\n");
		return -EBUSY;
	}

	priv = kzalloc(sizeof(struct wmt_i2c_priv), GFP_KERNEL);
	if (!priv) {
		printk(KERN_ERR "[i2c] Unable to allocate private data memory\n");
		ret = -ENOMEM;
		goto fail1;
	}

	priv->base = ioremap(res->start, res->end - res->start + 1);
	if (!priv->base) {
		printk(KERN_ERR "[i2c] Unable to map IO space\n");
		ret = -ENOMEM;
		goto fail2;
	}

	priv->irq_num = platform_get_irq(pdev, 0);
	if (priv->irq_num == -1) {
		printk(KERN_ERR "[i2c] Getting platform IRQ failed\n");
		ret = -ENXIO;
		goto fail3;
	}

	ret = request_irq(priv->irq_num, wmt_i2c_handler, 0, "wmt_i2c", priv);
	if (ret) {
		printk(KERN_ERR "[i2c] Failed to register interrupt handler\n");
		goto fail3;
	}

	platform_set_drvdata(pdev, priv);

	wmt_i2c_data.priv_data = &priv;
	
	wmt_i2c_init_hardware(priv);
	wmt_i2c_add_bus(&wmt_i2c_ops);

	printk(KERN_INFO "[i2c] I2C bus driver registered\n");
	return 0;

fail3:
	iounmap(priv->base);
fail2:
	kfree(priv);
fail1:
	release_mem_region(res->start, res->end - res->start + 1);
	printk(KERN_INFO "[i2c] Failed: error = %d\n", ret);
	return ret;
}

static int __exit wmt_i2c_remove(struct platform_device *pdev)
{
	struct wmt_i2c_priv *priv = platform_get_drvdata(pdev);
	struct resource *res;

	wmt_i2c_del_bus(&wmt_i2c_ops);

	free_irq(priv->irq_num, priv);

	kfree(priv);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	release_mem_region(res->start, res->end - res->start + 1);

	platform_set_drvdata(pdev, NULL);

	printk(KERN_INFO "[i2c] I2C bus driver removed\n");

	return 0;
}

static struct platform_driver wmt_i2c_driver = {
	.remove = __exit_p(wmt_i2c_remove),
	.suspend = NULL,
	.resume = NULL,
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
	},
};

static int __init wmt_i2c_init(void)
{
	return platform_driver_probe(&wmt_i2c_driver, &wmt_i2c_probe);
}

static void __exit wmt_i2c_exit(void)
{
	platform_driver_unregister(&wmt_i2c_driver);
}

module_init(wmt_i2c_init);
module_exit(wmt_i2c_exit);

MODULE_AUTHOR("Tony Prisk");
MODULE_DESCRIPTION("WMT I2C driver");
MODULE_LICENSE("GPL");
