# HG changeset patch
# User Matt Mackall <mpm@selenic.com>
# Date 1249942085 18000
# Node ID 4ac22168c4f00cf68d851fc60b996d14d898071a
# Parent  ad457f5cae5afe8df00d9c57717ab5ace505e7c7
imported patch comcerto-i2c

diff -r ad457f5cae5a -r 4ac22168c4f0 arch/arm/mach-comcerto/include/mach/i2c.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/arch/arm/mach-comcerto/include/mach/i2c.h	Mon Aug 10 17:08:05 2009 -0500
@@ -0,0 +1,58 @@
+/*
+ *  linux/include/asm-arm/arch-comcerto/i2c.h
+ *
+ *  Copyright (C) 2008 Mindspeed Technologies, 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; 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
+ *
+ */
+#ifndef __ASM_ARCH_COMCERTO_I2C_H
+#define __ASM_ARCH_COMCERTO_I2C_H
+
+#define COMCERTO_I2C_ADDR		(0x00*4)
+#define COMCERTO_I2C_DATA		(0x01*4)
+#define COMCERTO_I2C_CNTR		(0x02*4)
+#define COMCERTO_I2C_STAT		(0x03*4)
+#define COMCERTO_I2C_CCRFS		(0x03*4)
+#define COMCERTO_I2C_XADDR		(0x04*4)
+#define COMCERTO_I2C_CCRH		(0x05*4)
+#define COMCERTO_I2C_RESET		(0x07*4)
+
+/* CNTR - Control register bits */
+#define CNTR_IEN			(1<<7)
+#define CNTR_ENAB			(1<<6)
+#define CNTR_STA			(1<<5)
+#define CNTR_STP			(1<<4)
+#define CNTR_IFLG			(1<<3)
+#define CNTR_AAK			(1<<2)
+
+/* STAT - Status codes */
+#define STAT_BUS_ERROR			0x00	/* Bus error in master mode only */
+#define STAT_START			0x08	/* Start condition transmitted */
+#define STAT_START_REPEATED		0x10	/* Repeated Start condition transmited */
+#define STAT_ADDR_WR_ACK		0x18	/* Address + Write bit transmitted, ACK received */
+#define STAT_ADDR_WR_NACK		0x20	/* Address + Write bit transmitted, NACK received */
+#define STAT_DATA_WR_ACK		0x28	/* Data byte transmitted in master mode , ACK received */
+#define STAT_DATA_WR_NACK		0x30	/* Data byte transmitted in master mode , NACK received */
+#define STAT_ARBIT_LOST			0x38	/* Arbitration lost in address or data byte */
+#define STAT_ADDR_RD_ACK		0x40	/* Address + Read bit transmitted, ACK received  */
+#define STAT_ADDR_RD_NACK		0x48	/* Address + Read bit transmitted, NACK received  */
+#define STAT_DATA_RD_ACK		0x50	/* Data byte received in master mode, ACK transmitted  */
+#define STAT_DATA_RD_NACK		0x58	/* Data byte received in master mode, NACK transmitted*/
+#define STAT_ARBIT_LOST_ADDR		0x68	/* Arbitration lost in address  */
+#define STAT_GENERAL_CALL		0x70	/* General Call, ACK transmitted */
+#define STAT_NO_RELEVANT_INFO		0xF8	/* No relevant status information, IFLF=0 */
+
+#endif /* __ASM_ARCH_COMCERTO_I2C_H */
diff -r ad457f5cae5a -r 4ac22168c4f0 drivers/i2c/busses/Kconfig
--- a/drivers/i2c/busses/Kconfig	Mon Aug 10 17:08:05 2009 -0500
+++ b/drivers/i2c/busses/Kconfig	Mon Aug 10 17:08:05 2009 -0500
@@ -76,6 +76,18 @@
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-amd8111.
 
+config I2C_COMCERTO
+	tristate "Comcerto I2C interface"
+	depends on I2C 
+	help
+	    If you say yes to this option, support will be included for the
+	    Comcerto I2C interface.
+
+	    This driver can also be built as a module.  If so, the module
+	    will be called i2c-comcerto.
+
+
+
 config I2C_I801
 	tristate "Intel 82801 (ICH)"
 	depends on PCI
@@ -628,6 +640,13 @@
 
 	  If you don't know, say Y.
 
+config I2C_COMCERTO
+	tristate "Comcerto i2c interface"
+	help
+		If you say yes to this option, support will be included for
+		the comcerto i2c interface. The driver can also be built as
+		a module.
+
 config I2C_ELEKTOR
 	tristate "Elektor ISA card"
 	depends on ISA && BROKEN_ON_SMP
diff -r ad457f5cae5a -r 4ac22168c4f0 drivers/i2c/busses/Makefile
--- a/drivers/i2c/busses/Makefile	Mon Aug 10 17:08:05 2009 -0500
+++ b/drivers/i2c/busses/Makefile	Mon Aug 10 17:08:05 2009 -0500
@@ -28,6 +28,7 @@
 obj-$(CONFIG_I2C_AT91)		+= i2c-at91.o
 obj-$(CONFIG_I2C_AU1550)	+= i2c-au1550.o
 obj-$(CONFIG_I2C_BLACKFIN_TWI)	+= i2c-bfin-twi.o
+obj-$(CONFIG_I2C_COMCERTO)	+= i2c-comcerto.o
 obj-$(CONFIG_I2C_CPM)		+= i2c-cpm.o
 obj-$(CONFIG_I2C_DAVINCI)	+= i2c-davinci.o
 obj-$(CONFIG_I2C_GPIO)		+= i2c-gpio.o
diff -r ad457f5cae5a -r 4ac22168c4f0 drivers/i2c/busses/i2c-comcerto.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/drivers/i2c/busses/i2c-comcerto.c	Mon Aug 10 17:08:05 2009 -0500
@@ -0,0 +1,685 @@
+/*
+ *  drivers/i2c/busses/i2c-comcerto.c
+ *
+ *  Copyright (C) 2008 Mindspeed Technologies, 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; 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/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <asm/io.h>
+#include <asm/sizes.h>
+#include <mach/i2c.h>
+#include <mach/irqs.h>
+
+
+MODULE_AUTHOR("Mindspeed Technologies, Inc.");
+MODULE_DESCRIPTION("Comcerto I2C bus driver");
+MODULE_LICENSE("GPL");
+
+
+#define SPEED_HIGH_KHZ		3400
+#define SPEED_FULL_KHZ		400
+#define SPEED_NORMAL_KHZ	100
+
+static int force_poll = 0;
+module_param(force_poll, bool, S_IRUGO);
+MODULE_PARM_DESC(force_poll, "Force polling mode: 0=interrupt mode, polling mode otherwise");
+
+static int speed = 0;
+module_param(speed, int, S_IRUGO);
+MODULE_PARM_DESC(speed, "I2C speed: 0=standard, 1=fast, 2=high speed");
+
+
+struct comcerto_i2c
+{
+	struct i2c_adapter	*adapter;
+	struct device		*dev;
+	unsigned long		membase;
+	struct resource		*io;
+	int			irq;
+	u32			speed_khz;
+
+	wait_queue_head_t	wait;
+	struct i2c_msg		*msg;
+	int			msg_state;
+	int			msg_status;	/* < 0: error, == 0: success, > 0: message in progress */
+	int			msg_len;
+	int			msg_retries;
+};
+
+
+#define REG_ADDR(i2c, offset)		((i2c)->membase + (offset))
+#define RD_REG(i2c, offset)		__raw_readb(REG_ADDR(i2c, offset))
+#define WR_REG(i2c, offset, byte)	__raw_writeb(byte, REG_ADDR(i2c, offset))
+#define RD_DATA(i2c)			RD_REG(i2c, COMCERTO_I2C_DATA)
+#define WR_DATA(i2c, byte)		WR_REG(i2c, COMCERTO_I2C_DATA, byte)
+#define RD_CNTR(i2c)			RD_REG(i2c, COMCERTO_I2C_CNTR)
+#define WR_CNTR(i2c, byte)		WR_REG(i2c, COMCERTO_I2C_CNTR, byte)
+#define RD_STAT(i2c)			RD_REG(i2c, COMCERTO_I2C_STAT)
+#define WR_CCRFS(i2c, byte)		WR_REG(i2c, COMCERTO_I2C_CCRFS, byte)
+#define WR_CCRH(i2c, byte)		WR_REG(i2c, COMCERTO_I2C_CCRH, byte)
+#define WR_RESET(i2c, byte)		WR_REG(i2c, COMCERTO_I2C_RESET, byte)
+
+
+enum
+{
+	TR_IDLE = 0,
+	TR_START_ACK,
+	TR_ADDR_ACK,
+	TR_DATA_ACK,
+	RX_DATA_NACK,
+};
+
+
+static u8 comcerto_i2c_calculate_dividers(struct comcerto_i2c *i2c)
+{
+	int m, n, hz, speed_hz;
+	int saved_n, saved_m, saved_hz;
+	u8 dividers;
+
+	speed_hz = i2c->speed_khz*1000;
+	saved_hz = saved_n = saved_m = 0;
+
+	for (m = 0; m < 16; m++) {
+		for (n = 0; n < 8; n++) {
+			hz = (COMCERTO_AHBCLK*1000*1000) / ((1 << n) * (m + 1) * 10);
+			if (!saved_hz || abs(speed_hz - hz) < abs(speed_hz - saved_hz)) {
+				saved_n = n;
+				saved_m = m;
+				saved_hz = hz;
+			}
+		}
+	}
+
+	dividers = (saved_m << 3) | saved_n;
+
+	dev_dbg(i2c->dev, "%s: speed=%dkHz, M=%d, N=%d, dividers=0x%02x\n", __FUNCTION__,
+		saved_hz/1000, saved_m, saved_n, dividers);
+
+	return dividers;
+}
+
+/*
+ * Returns the timeout (in jiffies) for the given message.
+ */
+static int comcerto_i2c_calculate_timeout(struct comcerto_i2c *i2c, struct i2c_msg *msg)
+{
+	int timeout;
+
+	/* if no timeout was specified, calculate it */
+	if (i2c->adapter->timeout <= 0) {
+		if (i2c->irq >= 0) {
+			/* for the interrupt mode calculate timeout for 'full' message */
+			timeout = ((int)msg->len)*10;	/* convert approx. to bits */
+			timeout /= i2c->speed_khz;	/* convert to bits per ms (note of kHz scale) */
+			timeout += timeout >> 1;	/* add 50% */
+			timeout = timeout*HZ/1000;	/* convert to jiffies */
+			if (timeout < HZ/5)		/* at least 200ms */
+				timeout = HZ/5;
+		}
+		else
+			timeout = HZ;			/* 1 second for the polling mode */ 
+	}
+	else
+		timeout = i2c->adapter->timeout;
+
+	return timeout;
+}
+
+/*
+ * Initialize I2C core. Zero CNTR and DATA, try RESET. Short
+ * busy wait and check core status. After that set dividers for
+ * choosen speed.
+ */
+static void comcerto_i2c_reset(struct comcerto_i2c *i2c)
+{
+	u8 status, dividers;
+
+	dev_dbg(i2c->dev, "%s\n", __FUNCTION__);
+
+	WR_CNTR(i2c, 0);
+	WR_DATA(i2c, 0);
+	WR_RESET(i2c, 1);
+
+	udelay(10);
+
+	status = RD_STAT(i2c);
+	if (status != STAT_NO_RELEVANT_INFO)
+		dev_warn(i2c->dev, "%s: unexpected status after reset: 0x%02x\n", __FUNCTION__, status);
+
+	/* dividers should be placed in CCRH for high-sped mode and in CCRFS for standard/full modes */
+	dividers = comcerto_i2c_calculate_dividers(i2c);
+	if (i2c->speed_khz == SPEED_HIGH_KHZ)
+		WR_CCRH(i2c, dividers);
+	else
+		WR_CCRFS(i2c, dividers);
+}
+
+static inline void comcerto_i2c_message_complete(struct comcerto_i2c *i2c, int status)
+{
+	i2c->msg_status = status;
+	WR_CNTR(i2c, CNTR_STP);
+}
+
+static inline int comcerto_i2c_message_in_progress(struct comcerto_i2c *i2c)
+{
+	return i2c->msg_status > 0;
+}
+
+/*
+ * Wait event. This function sleeps in polling mode, in interrupt
+ * mode it enables IRQ from I2C core and exits immediately.
+ */
+static int comcerto_i2c_wait(struct comcerto_i2c *i2c, u8 cntr)
+{
+	cntr &= ~(CNTR_IFLG | CNTR_IEN);	/* clear both IFLG and IEN */
+
+	if (i2c->irq < 0) {
+		ulong jiffies_mark = jiffies + comcerto_i2c_calculate_timeout(i2c, i2c->msg);
+
+		WR_CNTR(i2c, cntr);
+		while ((RD_CNTR(i2c) & CNTR_IFLG) == 0) {
+			if (need_resched())
+				schedule();
+
+			if (time_after(jiffies, jiffies_mark)) {
+				dev_dbg(i2c->dev, "%s: polling transfer timeout\n", __FUNCTION__);
+				comcerto_i2c_message_complete(i2c, -ETIME);
+				comcerto_i2c_reset(i2c);
+				break;
+			}
+		}
+	}
+	else {
+		/* enable interrupt */
+		WR_CNTR(i2c, cntr | CNTR_IEN);
+	}
+
+	return 0;
+}
+
+static void comcerto_i2c_state_idle(struct comcerto_i2c *i2c, u8 *cntr)
+{
+	if (unlikely(i2c->msg->flags & I2C_M_NOSTART)) {
+		i2c->msg_state = TR_ADDR_ACK;
+	}
+	else {
+		*cntr = CNTR_STP|CNTR_STA;	/* SPT|STA to auto recover from bus error state transparently at the start of the transfer */
+		i2c->msg_state = TR_START_ACK;
+	}
+}
+
+static void comcerto_i2c_state_start_ack(struct comcerto_i2c *i2c, u8 *cntr)
+{
+	u8 status, addr;
+
+	*cntr = 0;	/* zero IFLG, IEN (for the interrupt mode it will be enabled in wait function) */
+
+	status = RD_STAT(i2c);
+
+	if (status == STAT_START || status == STAT_START_REPEATED) {
+		i2c->msg_state = TR_ADDR_ACK;
+
+		addr = i2c->msg->addr << 1;
+		if (i2c->msg->flags & I2C_M_RD)
+			addr |= 1;
+		if (i2c->msg->flags & I2C_M_REV_DIR_ADDR)
+			addr ^= 1;		/* invert RW bit if it's requested */
+
+		WR_DATA(i2c, addr);		/* write address and read/write bit */
+	} else {
+		dev_dbg(i2c->dev, "%s: unexpected state (%#x) on start phase, %s\n",
+			__FUNCTION__, status, i2c->msg_retries > 1 ? "retrying":"aborting");
+
+		if (--i2c->msg_retries < 0)
+			comcerto_i2c_message_complete(i2c, -1);
+		else
+			comcerto_i2c_state_idle(i2c, cntr);
+	}
+}
+
+static void comcerto_i2c_rx(struct comcerto_i2c *i2c)
+{
+	u8 status, cntr = 0;
+
+restart:
+	switch (i2c->msg_state) {
+	case TR_IDLE:
+		comcerto_i2c_state_idle(i2c, &cntr);
+		if (unlikely(i2c->msg->flags & I2C_M_NOSTART))
+			goto restart;	/* needed to avoid event loss in interrupt mode */
+		break;
+
+	case TR_START_ACK:
+		comcerto_i2c_state_start_ack(i2c, &cntr);
+		break;
+
+	case TR_ADDR_ACK:
+		if (unlikely(i2c->msg->flags & I2C_M_NOSTART)) {
+			/* we can enter this state if skip start/addr flag is set, so fake good ack */
+			status = STAT_ADDR_RD_ACK;
+		}
+		else {
+			status = RD_STAT(i2c);
+			/* check whether we should ignore NACK */
+			if (status == STAT_DATA_RD_NACK && (i2c->msg->flags & I2C_M_IGNORE_NAK))
+				status = STAT_DATA_RD_ACK;
+		}
+
+		if (likely(status == STAT_ADDR_RD_ACK)) {
+			/* start reception phase - wait until data is ready and loop in RX_DATA_ACK state
+			 * until we read all the data, sending ACK after each byte (but the last)
+			 */
+			i2c->msg_len = 0;
+			if (i2c->msg->len > 1) {
+				i2c->msg_state = TR_DATA_ACK;
+				cntr = CNTR_AAK;
+			}
+			else if (i2c->msg->len == 1) {
+				i2c->msg_state = RX_DATA_NACK;
+			}
+			else {	/* nothing to receive, send STOP and signal success */
+				comcerto_i2c_message_complete(i2c, 0);
+			}
+		}
+		else {
+			dev_dbg(i2c->dev, "%s: unexpected state (%#x) on address phase, %s\n",
+				__FUNCTION__, status, i2c->msg_retries > 1 ? "retrying":"aborting");
+
+			if (--i2c->msg_retries < 0)
+				comcerto_i2c_message_complete(i2c, -1);
+			else
+				comcerto_i2c_state_idle(i2c, &cntr);
+		}
+		break;
+
+	case TR_DATA_ACK:
+		status = RD_STAT(i2c);
+
+		if (likely(status == STAT_DATA_RD_ACK)) {
+			i2c->msg->buf[i2c->msg_len++] = RD_DATA(i2c);
+
+			if (likely(i2c->msg->len - i2c->msg_len > 0)) {
+				cntr = CNTR_AAK;
+			}
+			else {
+				i2c->msg_state = RX_DATA_NACK;
+				/* NACK should be transmitted on the last byte */
+			}
+		}
+		else {
+			dev_dbg(i2c->dev, "%s: unexpected state (%#x) on read phase\n", __FUNCTION__, status);
+			comcerto_i2c_message_complete(i2c, -1);
+		}
+		break;
+
+	case RX_DATA_NACK:
+		status = RD_STAT(i2c);
+		if (likely(status == STAT_DATA_RD_NACK)) {
+			i2c->msg->buf[i2c->msg_len++] = RD_DATA(i2c);
+			comcerto_i2c_message_complete(i2c, 0);
+		}
+		else {
+			dev_dbg(i2c->dev, "%s: unexpected state (%#x) on finishing read phase\n", __FUNCTION__, status);
+			comcerto_i2c_message_complete(i2c, -1);
+		}
+	}
+
+	/* no wait if we completed message */
+	if (comcerto_i2c_message_in_progress(i2c))
+		comcerto_i2c_wait(i2c, cntr);
+}
+
+static void comcerto_i2c_tx(struct comcerto_i2c *i2c)
+{
+	u8 status, cntr = 0;
+
+restart:
+	switch (i2c->msg_state) {
+	case TR_IDLE:
+		comcerto_i2c_state_idle(i2c, &cntr);
+		if (unlikely(i2c->msg->flags & I2C_M_NOSTART))
+			goto restart;	/* needed to avoid event loss in interrupt mode */
+		break;
+
+	case TR_START_ACK:
+		comcerto_i2c_state_start_ack(i2c, &cntr);
+		break;
+
+	case TR_ADDR_ACK:
+		if (unlikely(i2c->msg->flags & I2C_M_NOSTART)) {
+			/* we can enter this state if skip start/addr flag is set, so fake good ack */
+			status = STAT_ADDR_WR_ACK;
+		}
+		else {
+			status = RD_STAT(i2c);
+			if (status == STAT_DATA_WR_NACK && (i2c->msg->flags & I2C_M_IGNORE_NAK))
+				status = STAT_DATA_WR_ACK;
+		}
+
+		if (likely(status == STAT_ADDR_WR_ACK)) {
+			/* start reception phase - wait until data is ready and loop in TX_DATA_ACK state
+			 * until we read all the data, sending ACK after each byte (but the last)
+			 */
+			i2c->msg_state = TR_DATA_ACK;
+			i2c->msg_len = 0;
+			if (likely(i2c->msg->len != 0)) {
+				WR_DATA(i2c, i2c->msg->buf[i2c->msg_len++]);
+			}
+			else {
+				/* nothing to transmit, send STOP and signal success */
+				comcerto_i2c_message_complete(i2c, 0);
+			}
+		}
+		else {
+			dev_dbg(i2c->dev, "%s: unexpected state (%#x) on address phase, %s\n",
+				__FUNCTION__, status, i2c->msg_retries > 1 ? "retrying":"aborting");
+
+			if (--i2c->msg_retries < 0)
+				comcerto_i2c_message_complete(i2c, -1);
+			else
+				comcerto_i2c_state_idle(i2c, &cntr);
+		}
+		break;
+
+	case TR_DATA_ACK:
+		status = RD_STAT(i2c);
+		if (status == STAT_DATA_WR_NACK && (i2c->msg->flags & I2C_M_IGNORE_NAK))
+			status = STAT_DATA_WR_ACK;
+
+		if (likely(status == STAT_DATA_WR_ACK)) {
+			if (i2c->msg->len > i2c->msg_len)
+				WR_DATA(i2c, i2c->msg->buf[i2c->msg_len++]);
+			else
+				comcerto_i2c_message_complete(i2c, 0);
+		}
+		else {
+			dev_dbg(i2c->dev, "%s: unexpected state (%#x) on read data phase\n", __FUNCTION__, status);
+			comcerto_i2c_message_complete(i2c, -1);
+		}
+		break;
+	}
+
+	if (comcerto_i2c_message_in_progress(i2c))
+		comcerto_i2c_wait(i2c, cntr);
+}
+
+static irqreturn_t comcerto_i2c_interrupt(int irq, void *dev_id)
+{
+	struct comcerto_i2c *i2c = dev_id;
+
+	if (!(RD_CNTR(i2c) & CNTR_IFLG))
+		goto none;
+
+	/* IRQ enable/disable logic is hidden in state handlers, all we need is to wake
+	 * process when message completed.
+	 */
+	if (i2c->msg->flags & I2C_M_RD)
+		comcerto_i2c_rx(i2c);
+	else
+		comcerto_i2c_tx(i2c);
+
+	if (!comcerto_i2c_message_in_progress(i2c)) {
+		WR_CNTR(i2c, RD_CNTR(i2c) & ~CNTR_IEN);	/* disable interrupt unconditionally */
+		wake_up(&i2c->wait);
+	}
+
+	return IRQ_HANDLED;
+none:
+	return IRQ_NONE;
+}
+
+static void comcerto_i2c_message_process(struct comcerto_i2c *i2c, struct i2c_msg *msg)
+{
+	i2c->msg = msg;
+	i2c->msg_state = TR_IDLE;
+	i2c->msg_status = 1;
+	i2c->msg_retries = i2c->adapter->retries;
+
+polling_mode:
+	if (msg->flags & I2C_M_RD)
+		comcerto_i2c_rx(i2c);
+	else
+		comcerto_i2c_tx(i2c);
+	if (i2c->irq < 0) {
+		/*if (i2c->msg != NULL)*/
+			goto polling_mode;
+	}
+	else {
+		int timeout, res;
+		ulong flags;
+
+		timeout = comcerto_i2c_calculate_timeout(i2c, msg);
+
+		res = wait_event_timeout(i2c->wait, i2c->msg_status <= 0, timeout);
+
+		local_irq_save(flags);
+
+		/* check if we timed out and set respective error codes */
+		if (res == 0) {
+			if (comcerto_i2c_message_in_progress(i2c)) {
+				dev_dbg(i2c->dev, "%s: interrupt transfer timeout\n", __FUNCTION__);
+				comcerto_i2c_message_complete(i2c, -ETIME);
+				comcerto_i2c_reset(i2c);
+			}
+		}
+
+		local_irq_restore(flags);
+	}
+}
+
+/*
+ * Generic master transfer entrypoint.
+ * Returns the number of processed messages or error value
+ */
+static int comcerto_i2c_master_xfer(struct i2c_adapter *adapter, struct i2c_msg msgs[], int num)
+{
+	struct comcerto_i2c *i2c = i2c_get_adapdata(adapter);
+	int i;
+
+	dev_dbg(i2c->dev, "%s: %d messages to process\n", __FUNCTION__, num);
+
+	for (i = 0; i < num; i++) {
+		dev_dbg(i2c->dev, "%s: message #%d: addr=%#x, flags=%#x, len=%u\n", __FUNCTION__,
+			i, msgs[i].addr, msgs[i].flags, msgs[i].len);
+
+		comcerto_i2c_message_process(i2c, &msgs[i]);
+
+		if (i2c->msg_status < 0) {
+			dev_dbg(i2c->dev, "%s: transfer failed on message #%d (addr=%#x, flags=%#x, len=%u)\n",
+				__FUNCTION__, i, msgs[i].addr, msgs[i].flags, msgs[i].len);
+			break;
+		}
+	}
+
+	if (i2c->msg_status == -1)
+		i2c->msg_status = -EIO;
+
+	if (i2c->msg_status == 0)
+		i2c->msg_status = num;
+
+	return i2c->msg_status;
+}
+
+static u32 comcerto_i2c_functionality(struct i2c_adapter *adap)
+{
+	return (I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL);
+}
+
+static struct i2c_algorithm comcerto_i2c_algo = {
+	.master_xfer	= comcerto_i2c_master_xfer,
+	.functionality	= comcerto_i2c_functionality,
+};
+
+static struct i2c_adapter comcerto_i2c_adapter = {
+	.owner		= THIS_MODULE,
+	.algo		=  &comcerto_i2c_algo,
+	.timeout	= 0,	/* <= zero means that we calculate timeout in run-time, can be changed with ioctl call */
+	.retries	= 0,	/* no retries by default - let the user decide what's the best, can be changed with ioctl call */
+};
+
+static int comcerto_i2c_probe(struct platform_device *pdev)
+{
+	struct comcerto_i2c *i2c;
+	struct resource *irq;
+	int res = -1;
+
+	dev_dbg(&pdev->dev, "%s\n", __FUNCTION__);
+
+	i2c = kzalloc(sizeof(*i2c), GFP_KERNEL);
+	if (i2c == NULL) {
+		dev_err(&pdev->dev, "%s: failed allocate memory\n", __FUNCTION__);
+		res = -ENOMEM;
+		goto err0;
+	}
+
+	i2c->adapter = &comcerto_i2c_adapter;
+	i2c->adapter->dev.parent = &pdev->dev;
+	i2c->dev = &pdev->dev;
+
+	init_waitqueue_head(&i2c->wait);
+
+	platform_set_drvdata(pdev, i2c);
+	i2c_set_adapdata(&comcerto_i2c_adapter, i2c);
+
+	i2c->io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (i2c->io == NULL) {
+		dev_err(&pdev->dev, "%s: no IO region specified\n", __FUNCTION__);
+		res = -ENOENT;
+		goto err1;
+	}
+
+	if (!request_mem_region(i2c->io->start, i2c->io->end - i2c->io->start + 1, "I2C")) {
+		dev_err(i2c->dev, "%s: failed to request memory region\n", __FUNCTION__);
+		goto err1;
+	}
+
+	/* io-remaped in arch/arm/mach-comcerto/comcerto-xxx.c */
+	i2c->membase = APB_VADDR(i2c->io->start);
+
+	irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if (irq == NULL && !force_poll) {
+		dev_warn(i2c->dev, "%s: no IRQ specified in resources, polling mode forced\n", __FUNCTION__);
+		force_poll = 1;
+		i2c->irq = -1;
+	}
+
+	if (speed == 0) {
+		i2c->speed_khz = SPEED_NORMAL_KHZ;
+	}
+	else if (speed == 1) {
+		i2c->speed_khz = SPEED_FULL_KHZ;
+	}
+	else if (speed == 2) {
+		i2c->speed_khz = SPEED_HIGH_KHZ;
+	}
+	else {
+		dev_err(i2c->dev, "%s: invalid 'speed' module option provided (%d, must be 0,1,2 for normal/full/high modes)\n",
+			__FUNCTION__, speed);
+		goto err2;
+	}
+
+	comcerto_i2c_reset(i2c);
+
+	if (!force_poll) {
+		i2c->irq = irq->start;
+
+		res = request_irq(i2c->irq, comcerto_i2c_interrupt, IRQF_SHARED, "I2C", i2c);
+		if (res < 0) {
+			dev_warn(i2c->dev, "%s: failed to request IRQ%d, polling mode forced\n", __FUNCTION__, i2c->irq);
+			force_poll = 1;
+			i2c->irq = -1;
+		}
+	}
+	else
+		i2c->irq = -1;
+
+	if (i2c_add_adapter(&comcerto_i2c_adapter) != 0) {
+		dev_err(i2c->dev, "%s: failed to add I2C adapter\n", __FUNCTION__);
+		goto err3;
+	}
+
+	dev_dbg(&pdev->dev, "%s: I2C adapter registered\n", __FUNCTION__);
+
+	return 0;
+
+err3:
+	if (i2c->irq >= 0)
+		free_irq(i2c->irq, i2c);
+
+err2:
+	release_mem_region(i2c->io->start, i2c->io->end - i2c->io->end + 1);
+
+err1:
+	kfree(i2c);
+
+err0:
+	return res;
+}
+
+static int comcerto_i2c_remove(struct platform_device *pdev)
+{
+	struct comcerto_i2c *i2c = platform_get_drvdata(pdev);
+
+	dev_dbg(i2c->dev, "%s\n", __FUNCTION__);
+
+	platform_set_drvdata(pdev, NULL);
+
+	i2c_del_adapter(i2c->adapter);
+
+	if (i2c->irq >= 0)
+		free_irq(i2c->irq, i2c);
+
+	release_mem_region(i2c->io->start, i2c->io->end - i2c->io->start + 1);
+
+	kfree(i2c);
+
+	return 0;
+}
+
+static struct platform_driver comcerto_i2c_driver = {
+	.driver = {
+		.name	= "comcerto_i2c",
+		.owner	= THIS_MODULE,
+	},
+	.probe	= comcerto_i2c_probe,
+	.remove	= comcerto_i2c_remove,
+};
+
+static int __init comcerto_i2c_init(void)
+{
+	printk(KERN_DEBUG "%s: module loaded\n", __FUNCTION__);
+
+	if (platform_driver_register(&comcerto_i2c_driver)) {
+		printk(KERN_ERR "%s: failed to register platform device\n", __FUNCTION__);
+		return -1;
+	}
+
+	return 0;
+}
+
+static void __exit comcerto_i2c_exit(void)
+{
+	printk(KERN_DEBUG "%s: module unloaded\n", __FUNCTION__);
+
+	platform_driver_unregister(&comcerto_i2c_driver);
+}
+
+module_init(comcerto_i2c_init);
+module_exit(comcerto_i2c_exit);
