All of lore.kernel.org
 help / color / mirror / Atom feed
From: eajames@linux.vnet.ibm.com
To: openbmc@lists.ozlabs.org
Cc: joel@jms.id.au, alistair@popple.id.au, benh@kernel.crashing.org,
	"Edward A. James" <eajames@us.ibm.com>
Subject: [PATCH linux v1 7/8] drivers: fsi: i2c: add driver file operations and bus locking
Date: Thu,  2 Feb 2017 17:26:00 -0600	[thread overview]
Message-ID: <1486077964-11346-5-git-send-email-eajames@linux.vnet.ibm.com> (raw)
In-Reply-To: <1486077964-11346-1-git-send-email-eajames@linux.vnet.ibm.com>

From: "Edward A. James" <eajames@us.ibm.com>

Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
 drivers/fsi/i2c/Makefile    |    2 +-
 drivers/fsi/i2c/iic-lock.c  |  439 +++++++++++
 drivers/fsi/i2c/iic-mstr.c  | 1834 +++++++++++++++++++++++++++++++++++++++++++
 include/uapi/linux/Kbuild   |    1 +
 include/uapi/linux/i2cfsi.h |  136 ++++
 5 files changed, 2411 insertions(+), 1 deletion(-)
 create mode 100644 drivers/fsi/i2c/iic-lock.c
 create mode 100644 include/uapi/linux/i2cfsi.h

diff --git a/drivers/fsi/i2c/Makefile b/drivers/fsi/i2c/Makefile
index b1f28a1..4d04026 100644
--- a/drivers/fsi/i2c/Makefile
+++ b/drivers/fsi/i2c/Makefile
@@ -1 +1 @@
-obj-$(CONFIG_FSI_I2C) += iic-fsi.o iic-mstr.o
+obj-$(CONFIG_FSI_I2C) += iic-fsi.o iic-mstr.o iic-lock.o
diff --git a/drivers/fsi/i2c/iic-lock.c b/drivers/fsi/i2c/iic-lock.c
new file mode 100644
index 0000000..ea5a42f8
--- /dev/null
+++ b/drivers/fsi/i2c/iic-lock.c
@@ -0,0 +1,439 @@
+/*
+ *   Copyright (c) International Business Machines Corp., 2006, 2010
+ *
+ *   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/sched.h>
+#include <linux/slab.h>
+
+#include "iic-int.h"
+
+#define IIC_NO_MATCH 0
+#define IIC_PARTIAL_MATCH 1
+#define IIC_EXACT_MATCH 2
+#define IIC_ADDR_MAX_BITS 10
+
+
+void iic_lck_mgr_init(iic_lck_mgr_t* mgr)
+{
+	IENTER();
+	INIT_LIST_HEAD(&mgr->locked);
+	INIT_LIST_HEAD(&mgr->reqs);
+	IEXIT(0);
+}
+EXPORT_SYMBOL(iic_lck_mgr_init);
+
+int iic_get_match_type(short a_addr, short a_mask, short b_addr, short b_mask)
+{
+	int rc = IIC_PARTIAL_MATCH;
+	short mask = a_mask | b_mask;
+
+	IENTER();
+
+	/* no match if the fixed bits aren't the same */
+	if((a_addr & ~mask) != (b_addr & ~mask))
+	{
+		rc = IIC_NO_MATCH;
+	}
+
+	/* exact match if the fixed bits and mask bits are the same */
+	else if(a_mask == b_mask)
+	{
+		rc = IIC_EXACT_MATCH;
+	}
+
+	/* everything else is partial */
+
+	IEXIT(rc);
+	return rc;
+}
+
+
+/* to must be in jiffies! */ 
+int iic_wait_lock(iic_lck_mgr_t *lm, short addr, short mask, 
+		  iic_client_t *client, unsigned long to)
+{
+	iic_lck_t *handle = 0;
+	int rc = 0;
+	unsigned long flags;
+
+	IENTER();
+
+	spin_lock_irqsave(&client->bus->eng->lock, flags);
+
+	/* try the lock and enqueue our request if locked by someone else */
+	rc = iic_req_lock(lm, addr, mask, client, &handle);
+
+	spin_unlock_irqrestore(&client->bus->eng->lock, flags);
+
+	if(rc == IIC_REQ_QUEUED)
+	{
+		/* wait for timeout, signal, or lock to be granted */
+		rc = wait_event_interruptible_timeout(
+				client->wait,
+				handle->count > 0,
+				to);
+		if(rc > 0)
+		{
+			rc = 0;
+		}
+		else
+		{
+			if(!rc)
+			{
+				rc = -ETIME;
+				IFLDe(4, "lock req timed out: client[%p] bus[%08lx] lock[%04x:%04x]\n", 
+				      client, client->bus->bus_id,
+				      handle->addr, handle->mask);
+			}
+			else if(rc == -ERESTARTSYS)
+			{
+				rc = -EINTR;
+				IFLDe(4, "lock req interrupted: client[%p] bus[%08lx] lock[%04x:%04x]\n", 
+				      client, client->bus->bus_id,
+				      handle->addr, handle->mask);
+			}
+
+			/* interrupted or timed out.  delete request and
+			 * return to caller.
+			 */
+			spin_lock_irqsave(&client->bus->eng->lock, flags);
+			iic_unlock(lm, handle);
+			spin_unlock_irqrestore(&client->bus->eng->lock, flags);
+		}
+	}
+	IEXIT(rc);
+	return rc;
+}
+
+int iic_sideways_lock_bus(iic_client_t * client, unsigned short addr,
+			  unsigned short mask, unsigned long timeout)
+{
+	int rc = 0;
+	iic_eng_t * eng = client->bus->eng;
+
+	rc = iic_wait_lock(&eng->lck_mgr, addr, mask >> 1, client,
+			   msecs_to_jiffies(timeout));
+
+	return rc;
+}
+EXPORT_SYMBOL(iic_sideways_lock_bus);
+
+int iic_sideways_unlock_bus(iic_client_t * client, unsigned short addr,
+			    unsigned short mask)
+{
+	int rc = 0;
+	iic_lck_t * klck;
+	iic_eng_t * eng = client->bus->eng;
+
+	spin_lock_irq(&eng->lock);
+	klck = iic_find_handle(&eng->lck_mgr, client, addr, mask >> 1);
+
+	if(klck)
+	{
+		rc = iic_unlock(&eng->lck_mgr, klck);
+		if(!rc)
+			iic_start_next_xfr(eng);
+	}
+	spin_unlock_irq(&eng->lock);
+
+	return rc;
+}
+EXPORT_SYMBOL(iic_sideways_unlock_bus);
+
+/* Engine must be locked before using this function!
+ *
+ * This function only requests a lock.  It doesn't block waiting for the
+ * lock to be granted.  Instead, a handle is returned and the caller can
+ * query this handle to determine if the lock was granted.  If the lock
+ * count is not zero, the lock has been granted.
+ *
+ * returns IIC_REQ_QUEUED on success, negative value on failure.
+ *
+ * Locks from the same client can overlap each other, so a client could
+ * lock all addresses on a bus and still request a subset of locks within
+ * that range.
+ * 
+ * Here's the algorithm in a nutshell:
+ *
+ * Iterate through lock list
+ *     -If exact match and same client, stop iterating and increment the
+ * 	lock count of the existing lock.
+ *     -If partial or exact match but different client, stop iterating and
+ *     	queue a new request on the request queue without granting the lock.
+ *     -If partial and same client, keep iterating.
+ *     -If we reach the end and none of the above apply,
+ *       create a new lock and increment the count.
+ */
+int iic_req_lock(iic_lck_mgr_t *lm, short addr, unsigned short mask,
+		 iic_client_t *client, iic_lck_t **handle)
+{
+	iic_lck_t *iterator, *found;
+	int mtype = IIC_NO_MATCH;
+	int rc = IIC_REQ_QUEUED;
+	iic_lck_t *new_req = 0;
+
+	IENTER();
+	found = 0;
+	addr =  (client->bus->port << IIC_ADDR_MAX_BITS) | (addr >> 1);
+
+	IFLDs(4, "REQLOCK  client[%p] bus[%08lx] lock[%04x:%04x]\n",
+			client, client->bus->bus_id, addr, mask);
+
+	/* Look for a partial or exact address match in the lock list */
+	list_for_each_entry(iterator, &lm->locked, list)
+	{
+		mtype = iic_get_match_type(iterator->addr, iterator->mask,
+				           addr, mask);
+		if(mtype != IIC_NO_MATCH)
+		{
+			found = iterator;
+			if((found->client == client) && 
+					(mtype == IIC_EXACT_MATCH))
+			{
+				found->count++;
+				IDBGd(4, "bus[%08lx] lock[%04x:%04x] count=%ld\n",
+					client->bus->bus_id, addr, mask, 
+					found->count);
+				*handle = found;
+				goto exit;
+			}
+			break;
+		}
+	}
+
+	/* Exact matches with same client id have already exited.  Deal
+	 * with the rest as follows:
+	 * 	exact/partial matches w/diff client -> new lock requested
+	 * 	no matches or partial w/same client -> new lock granted
+	 */
+	new_req = (iic_lck_t*)kmalloc(sizeof(iic_lck_t), GFP_KERNEL);
+	if(!new_req)
+	{
+		rc = -ENOMEM;
+		goto exit;
+	}
+	new_req->client = client;
+	new_req->addr = addr;
+	new_req->mask = mask;
+	new_req->cur_xfr = 0;
+	*handle = new_req;
+
+	/* queue the request if someone else owns the lock */
+	if((mtype != IIC_NO_MATCH) && (found->client != client)){
+		IFLDi(5, "BLOCKED  client[%p] bus[%08lx] lock[%04x:%04x] blocked by client[%p]\n", 
+		      client, client->bus->bus_id, addr, mask, found->client);
+		new_req->count = 0;
+		list_add_tail(&new_req->list, &lm->reqs);
+	}
+	/* if nobody owns the lock, then grant it to the requestor */
+	else
+	{
+		IDBGd(4, "bus[%08lx] lock[%04x:%04x] count=%d\n", 
+			client->bus->bus_id, addr, mask, 1);
+		new_req->count = 1;
+		list_add_tail(&new_req->list, &lm->locked);
+	}
+
+exit:
+	IEXIT(rc);
+	return rc;
+}
+
+/* Engine must be locked before calling this function!
+ *
+ * If the lock pointed to by lck has been granted, the lock count is decremented
+ * and if it reaches zero, the lock is freed up, and the next in line (if any)
+ * is granted the lock.  If lck points to a queued up lock request, the
+ * request is removed from the request queue and freed up.
+ *
+ * Here's the algorithm for finding the "next in line":
+ *
+ * Iterate through the request list
+ * 	-if exact or partial match of unlocked range found, search the 
+ * 	 lock list for partial matches to the requested lock that have
+ * 	 different clients.
+ * 		-If not found, grant the lock request.
+ * 		-If found, don't grant the lock, continue iterating the
+ * 		 request list for other potential candidates.
+ */
+int iic_unlock(iic_lck_mgr_t *lm, iic_lck_t *lck)
+{
+	iic_lck_t *req, *temp, *locked, *candidate;
+	int mtype = IIC_NO_MATCH;
+
+	IENTER();
+
+	IFLDs(5, "UNLOCK   client[%p] bus[%08lx] lock[%04x:%04x] count=%ld\n", 
+		lck->client, lck->client->bus->bus_id, lck->addr, lck->mask, 
+		lck->count);
+	switch(lck->count)
+	{
+	/* lock count will decrement to 0, lock is to be unlocked and 
+	 * next in line gets the lock
+	 */
+	case 1: 
+		/*remove the lock from the locked list*/
+		list_del(&lck->list);
+
+		/* Look for a partial or exact address match in the 
+		 * request queue as a candidate for being granted
+		 * the lock.
+		 */
+		candidate = 0;
+		list_for_each_entry_safe(req, temp, &lm->reqs, list)
+		{
+			mtype = iic_get_match_type(req->addr, req->mask,
+						   lck->addr, lck->mask);
+			if(mtype == IIC_NO_MATCH)
+			{
+				continue;
+			}
+
+			/* found someone waiting for this lock!
+			 * Check if the requestors lock range
+			 * is free
+			 */
+			candidate = req;
+			list_for_each_entry(locked, &lm->locked, list)
+			{
+				mtype = iic_get_match_type(
+						req->addr, 
+						req->mask, 
+						locked->addr, 
+						locked->mask);
+				if(mtype != IIC_NO_MATCH &&
+				   req->client != locked->client)
+				{
+					candidate = 0;
+					break;
+				}
+			}
+			if(candidate)
+			{
+				/* if the candidate survived the test,
+				 * grant the lock and discontinue search.
+				 * Also, wakeup the client waiting for
+				 * this lock to be granted.
+				 */
+				candidate->count++;
+				list_del(&candidate->list);
+				list_add_tail(&candidate->list, &lm->locked);
+				wake_up_interruptible(&candidate->client->wait);
+				IFLDi(4, "UNBLOCK  client[%p] bus[%08lx] lock[%04x:%04x]\n",
+				     candidate->client, 
+				     candidate->client->bus->bus_id,
+				     candidate->addr, candidate->mask);
+			}
+		}
+		kfree(lck);
+		break;
+
+	/* lock is unlocked (above case) or was waiting on the request q */
+	case 0: 
+		/* Remove the old lock from the locked or request
+		 * q and free it.
+		 */
+		list_del(&lck->list);
+		kfree(lck);
+		break;
+
+	default: /* lock count is >= 2 */
+		lck->count--;
+	}
+	IEXIT(0);
+	return 0;
+}
+
+/* Find all locks and lock requests associated with the client_id and remove
+ * them all regardless of lock count.  All associated transfers should have
+ * already been aborted before calling this function.
+ */
+int iic_unlock_all(iic_lck_mgr_t *lm, iic_client_t *client)
+{
+	iic_lck_t* iterator, *temp;
+
+	IENTER();
+
+	if(!lm || !client)
+	{
+		return 0;
+	}
+	/* first, search the request q */
+	list_for_each_entry_safe(iterator, temp, &lm->reqs, list)
+	{
+		if(iterator->client == client)
+		{
+			iic_unlock(lm, iterator);
+		}
+	}
+
+	/* then, search the lock q */
+	list_for_each_entry_safe(iterator, temp, &lm->locked, list)
+	{
+		if(iterator->client == client)
+		{
+			/* This removes all explicit locks and leaves a single
+			 * implicit lock in case there is an active transfer
+			 * associated with the lock.
+			 */
+			iterator->count = 1;
+			/* if cur_xfr is set, it means that the xfr has
+			 * been started and not completed.  It is bad to
+			 * forcefully unlock a lock associated with a
+			 * xfr that hasn't completed because the implicit
+			 * unlock will fail.
+			 */
+			if(!iterator->cur_xfr) 
+				iic_unlock(lm, iterator); //unlock now
+		}
+	}
+	IEXIT(0);
+	return 0;
+}
+
+/* Find the lock handle for a given client and address range */
+iic_lck_t* iic_find_handle(iic_lck_mgr_t *lm, iic_client_t *client, 
+		                short addr, short mask)
+{
+	iic_lck_t *iterator, *found;
+	IENTER();
+	addr =  (client->bus->port << IIC_ADDR_MAX_BITS) | (addr >> 1);
+        found = 0;
+	list_for_each_entry(iterator, &lm->locked, list)
+	{
+		if((iterator->client == client) &&
+				(iic_get_match_type(addr, mask, iterator->addr,
+					iterator->mask) == IIC_EXACT_MATCH))
+		{
+			found = iterator;
+			goto exit;
+		}
+	}
+	list_for_each_entry(iterator, &lm->reqs, list)
+	{
+		if((iterator->client == client) &&
+				(iic_get_match_type(addr, mask, iterator->addr,
+					iterator->mask) == IIC_EXACT_MATCH))
+		{
+			found = iterator;
+			break;
+		}
+	}
+exit:
+	IEXIT((int)found);
+	return found;
+}
diff --git a/drivers/fsi/i2c/iic-mstr.c b/drivers/fsi/i2c/iic-mstr.c
index de05e5d..d7f029c 100644
--- a/drivers/fsi/i2c/iic-mstr.c
+++ b/drivers/fsi/i2c/iic-mstr.c
@@ -32,7 +32,9 @@
 #include <asm/page.h>
 #include <linux/pagemap.h>
 #include <linux/aio.h>
+#include <linux/i2cfsi.h>
 #include "iic-int.h"
+#include "iic-fsi.h"
 #include <linux/fsi.h>
 #include <linux/time.h>
 #include <asm/io.h>
@@ -149,15 +151,1847 @@ static iic_bus_t * iic_get_bus(unsigned long port, unsigned long type)
 	return (found == 1)? iterator->bus: NULL;
 }
 
+
+int iic_common_open(iic_client_t ** o_client, iic_bus_t * bus, int engine_num)
+{
+	int ret = 0;
+	iic_client_t * client;
+	IENTER();
+
+
+	BUG_ON(in_atomic());
+
+	/*
+	 * Create a client with the default attributes and associate it
+	 * with the file descriptor.
+	 */
+	client = (iic_client_t*)kzalloc(sizeof(*client), GFP_KERNEL);
+	if(!client)
+	{
+		ret = -ENOMEM;
+		goto exit;
+	}
+	memcpy(&client->opts, &iic_dflt_opts, sizeof(iic_dflt_opts));
+
+	if(!bus)
+	{
+		ret = -ENOMEM;
+		kfree(client);
+		goto exit;
+	}
+	else
+	{
+		client->flags |= IIC_CLIENT_SOURCE_USER;
+	}
+
+	client->bus = bus;
+	client->tgid = current->tgid;
+	sema_init(&client->sem, 1);
+	init_waitqueue_head(&client->wait);
+	kobject_get(&bus->eng->kobj);
+	*o_client = client;
+
+exit:
+	IEXIT(0);
+	return ret;
+}
+
+int iic_sideways_open(iic_client_t ** o_client,
+		      iic_bus_t * bus,
+		      int engine_num)
+{
+	return iic_common_open(o_client, bus, engine_num);
+}
+EXPORT_SYMBOL(iic_sideways_open);
+
+int iic_open(struct inode* inode, struct file* filp)
+{
+	int ret = 0;
+	iic_client_t* client;
+	iic_bus_t* bus = container_of(inode->i_cdev,
+				      iic_bus_t,
+				      cdev);
+	IENTER();
+	if(!bus)
+	{
+		ret = -ENODEV;
+		goto exit;
+	}
+
+	ret = iic_common_open(&client, bus, 0);
+	filp->private_data = client;
+	IFLDs(2, "OPEN     client[%p] bus[%08lx]\n", client, bus->bus_id);
+
+exit:
+	IEXIT(ret);
+	return ret;
+}
+
 /* Abort all pending xfrs for a client, or if client is 0, abort all
  * pending xfrs for the engine.  
  */
 int iic_abort_all(iic_eng_t* eng, iic_client_t* client, int status)
 {
+	unsigned long flags;
+	iic_xfr_t *iterator, *temp;
+
+	IENTER();
+	/* abort currently running xfr */
+	spin_lock_irqsave(&eng->lock, flags);
+	if(eng->cur_xfr && (!client || ( eng->cur_xfr->client == client)))
+	{
+		iic_xfr_t* cur_xfr = eng->cur_xfr;
+		cur_xfr->status = status;
+		iic_abort_xfr(cur_xfr);
+
+		iic_xfr_complete(cur_xfr);
+	}
+
+	/* abort queued xfrs */
+	list_for_each_entry_safe(iterator, temp, &eng->xfrq, q_entry)
+	{
+		if(!client || (iterator->client == client))
+		{
+			iterator->status = status;
+			iic_abort_xfr(iterator);
+			iic_xfr_complete(iterator);
+		}
+	}
+	spin_unlock_irqrestore(&eng->lock, flags);
+	IEXIT(0);
 	return 0;
 }
 EXPORT_SYMBOL(iic_abort_all);
 
+int iic_common_release(iic_client_t * client)
+{
+        int rc = 0;
+        iic_bus_t * bus = client->bus;
+
+        IENTER();
+
+	BUG_ON(in_atomic());
+
+        /* abort all pending transfers for this client */
+        iic_abort_all(bus->eng, client, -EPIPE);
+
+        /* unlock any address locks associated with this client */
+        spin_lock_irq(&bus->eng->lock);
+        iic_unlock_all(&bus->eng->lck_mgr, client);
+        iic_start_next_xfr(bus->eng);
+        spin_unlock_irq(&bus->eng->lock);
+
+        client->bus = 0;
+        kfree(client);
+        kobject_put(&bus->eng->kobj);
+
+        IEXIT(rc);
+        return rc;
+}
+
+int iic_sideways_release(iic_client_t * client)
+{
+	return iic_common_release(client);
+}
+EXPORT_SYMBOL(iic_sideways_release);
+
+int iic_release(struct inode* inode, struct file* filp)
+{
+	iic_client_t* client = (iic_client_t*)filp->private_data;
+	iic_bus_t* bus = container_of(inode->i_cdev,
+				      iic_bus_t,
+				      cdev);
+	IENTER();
+
+	IFLDs(2, "CLOSE    client[%p] bus[%08lx]\n", client, bus->bus_id);
+
+	iic_common_release(client);
+
+	/* Delete the client object associated with the file descriptor */
+	filp->private_data = 0;
+
+	IEXIT(0);
+	return 0;
+}
+EXPORT_SYMBOL(iic_release);
+
+
+void iic_cleanup_xfr(iic_xfr_t* xfr, dd_ffdc_t ** o_ffdc)
+{
+	int i;
+	IENTER();
+
+	del_timer(&xfr->delay);
+	del_timer(&xfr->timeout);
+	kfree(xfr);
+	IEXIT(0);
+}
+
+#ifndef MSEC_PER_SEC
+#define MSEC_PER_SEC 1000
+#endif
+int iic_create_xfr(iic_client_t* client, struct kiocb* iocb, 
+		   void* buf, size_t len, unsigned long flags,
+		   iic_xfr_t** new_xfr, dd_ffdc_t ** o_ffdc)
+{
+	int rc = 0;
+	iic_xfr_t *xfr;
+	iic_xfr_opts_t *t_opts;
+	iic_eng_t *eng = client->bus->eng;
+	int i;
+	unsigned short j = 0, count = 0, size = 0;
+
+	IENTER();
+
+	xfr = (iic_xfr_t*) kmalloc(sizeof(iic_xfr_t), GFP_KERNEL);
+	if(!xfr)
+	{
+		*new_xfr = 0;
+		rc = -ENOMEM;
+		IFLDe(0, "kmalloc xfr failed\n");
+		goto exit;
+	}
+
+	memset(xfr, 0, sizeof(iic_xfr_t));
+
+	/* Copy all client attributes neccesary for doing the transfer
+	 * into the xfr struct.
+	 */
+	memcpy(&xfr->opts, &client->opts, sizeof(iic_opts_t));
+	xfr->client = client;
+	xfr->iocb = iocb;
+	xfr->flags = flags;
+	xfr->buf = (char*)buf;
+	xfr->size = len;
+	xfr->pid = current->pid;
+
+	/* modify the xfr opts for ease of use in the device driver */
+	t_opts = &xfr->opts.xfr_opts;
+	t_opts->inc_addr = (t_opts->inc_addr >> 1) << (t_opts->dev_width * 8);
+
+	/* device driver code will only look at the rdelay and rsplit fields.
+	 * wdelay and wsplit values will be copied to rdelay and rsplit if
+	 * this is a write transfer.
+	 */
+	if(test_bit(IIC_XFR_RD, &xfr->flags))
+	{
+		if(t_opts->rsplit)
+		{
+			if (t_opts->rsplit > 0x8000)
+			{
+				t_opts->rsplit = 0x7FFF;
+			}
+			else {
+				t_opts->rsplit = t_opts->rsplit - 1;
+			}
+		}
+		else {
+			t_opts->rsplit = 0x7FFF;
+		}
+	}
+	else
+	{
+		unsigned long data_sz = xfr->size;
+		unsigned long start;
+		char* data = (char*)&xfr->offset_ffdc;
+
+		if(t_opts->wsplit)
+		{
+			t_opts->rsplit = t_opts->wsplit - 1;
+			t_opts->rdelay = t_opts->wdelay;
+		}
+
+		/* store off the first 4 bytes of write transfers now
+		 * for ffdc.
+		 */
+		if(data_sz > sizeof(long))
+			data_sz = sizeof(long);
+		start = sizeof(long) - data_sz;
+	}
+
+	/* prevent split numbers that just have one bit set (0x800,
+	 * 0x20, etc) to avoid problems with split calculation
+	 * in engine
+	 */
+	count = 0;
+	size = sizeof(t_opts->rsplit) * 8;
+	for (j = 0; j < size && count <= 1; j++) {
+		if (t_opts->rsplit & (1 << j))
+			count++;
+	}
+
+	if (count == 1 && t_opts->rsplit > 2)
+		t_opts->rsplit = t_opts->rsplit - 1;
+
+	if(test_bit(IIC_ENG_BLOCK, &eng->flags))
+	{
+		IFLDe(1, "eng[%08x] blocked\n", eng->id);
+		rc = -ENODEV;
+		if(test_bit(IIC_ENG_REMOVED, &eng->flags))
+			rc = -ENOLINK;
+		xfr->status = rc;
+		goto error;
+	}
+
+	rc = 0;
+
+	*new_xfr = xfr;
+	goto exit;
+		
+error:
+	kfree(xfr);
+	*new_xfr = 0;
+
+exit:
+	IEXIT(rc);
+	return rc;
+}
+
+/* called within a timer context to continue a delayed transfer */
+void iic_continue_xfr(unsigned long data)
+{
+	iic_xfr_t *xfr = (iic_xfr_t*)data;
+	IENTER();
+	IFLDd(1, "CONTINUE xfr[%p]\n", xfr);
+	clear_bit(IIC_XFR_DELAYED, &xfr->flags);
+
+	iic_start_next_xfr(xfr->client->bus->eng);
+	IEXIT(0);
+}
+
+/* Called by the engine when a transfer should only be continued after
+ * a period of time has expired.
+ * This is needed for implementing write delays.
+ * Note: delay is in milliseconds!
+ */
+void iic_delay_xfr(iic_xfr_t* xfr, unsigned long delay)
+{
+	iic_eng_t *eng = xfr->client->bus->eng;
+	IENTER();
+	IFLDd(2, "DELAY    xfr[%p] time[%ld]\n", xfr, delay);
+	eng->cur_xfr = 0;
+
+	/* Get the next xfr started (if any) */
+	iic_start_next_xfr(eng);
+
+	/* Make sure the delayed bit is set */
+	set_bit(IIC_XFR_DELAYED, &xfr->flags);
+
+	/* Place this xfr back at the beginning of the queue */
+	list_add(&xfr->q_entry, &eng->xfrq);
+	
+	/* Start a timer that will allow the transfer to start back up
+	 * when it pops.
+	 */
+	xfr->delay.data = (unsigned long)xfr;
+	xfr->delay.function = iic_continue_xfr;
+	mod_timer(&xfr->delay, jiffies + msecs_to_jiffies( delay ) );
+	IEXIT(0);
+
+}
+EXPORT_SYMBOL(iic_delay_xfr);
+
+void iic_finish_complete(unsigned long data)
+{
+	iic_xfr_t *xfr = (iic_xfr_t*)data;
+	IENTER();
+	clear_bit(IIC_XFR_DELAYED, &xfr->flags);
+	iic_xfr_complete(xfr);
+	IEXIT(0);
+}
+
+#define NUM_RETRIES 15
+#define RETRY_DELAY 5
+#define BACKOFF_DELAY 500
+
+/* Retry timeout fails for relatively long timeout periods */
+static unsigned long allow_retry(iic_xfr_t* xfr)
+{
+	/* No retry allowed - use original timeout period */
+	return (xfr->opts.xfr_opts.timeout);
+}
+
+unsigned long error_match(int status, unsigned long policy, iic_xfr_t* xfr)
+{
+	unsigned long error_bit = 0;
+	unsigned long rc = 0;
+	IENTER();
+	switch(status)
+	{
+		case -ENXIO:
+			/* Allow one retry for addr NACK */
+			rc = 1;
+			error_bit = IIC_VAL_ADDR_NOACK;
+			break;
+		case -ENODATA:
+			error_bit = IIC_VAL_DATA_NOACK;
+			break;
+		case -ETIME:
+			/* Allow one retry for long timeout periods */
+			if (allow_retry(xfr) != xfr->opts.xfr_opts.timeout)
+				rc = 1;
+			error_bit = IIC_VAL_TIMEOUT;
+			break;
+		case -EALREADY:
+			error_bit = IIC_VAL_LOST_ARB;
+
+			/* More retries hardcoded for bus multimaster failure
+			   This behavior can be overridden by user config
+			   set delay to 5 ms */
+			if (!(error_bit & policy)) {
+				/* the return code should actually be the
+				   number of retries. See comparison in
+				   rec_retry */
+				rc = NUM_RETRIES;
+				xfr->opts.recovery.redo_delay = RETRY_DELAY;
+				goto exit;
+			}
+			break;
+		case -EIO:
+			/* Allow retries for bus errors */
+			rc = 3;
+			error_bit = IIC_VAL_BUS_ERR;
+			break;
+		default:
+			break;
+	}
+	if(error_bit & policy)
+		rc = policy & ~IIC_VAL_ALL_ERRS;
+exit:
+	IEXIT((int)rc);
+	return rc;
+}
+
+/* 1 means 1 retry, 0 means no retries. */
+unsigned short rec_retry(iic_xfr_t* xfr)
+{
+	unsigned short rc;
+	IENTER();
+	rc = error_match(xfr->status, xfr->opts.recovery.redo_pol, xfr);
+	if(rc <= xfr->retry_count)
+		rc = 0;
+	IEXIT(rc);
+	return 0;
+}
+
+/* Returns the delay needed prior to retry.  If a read or write delay
+ * was specified and is larger than the retry delay or the error
+ * isn't a policy match, then the read/write delay will be returned.
+ */
+unsigned long rec_delay(iic_xfr_t* xfr)
+{
+	unsigned long rc;
+	IENTER();
+	rc = xfr->opts.recovery.redo_delay ;
+	if(rc < xfr->opts.xfr_opts.rdelay)
+		rc = xfr->opts.xfr_opts.rdelay;
+	IEXIT((int)rc);
+	return rc;
+}
+
+/* Keep IIC_XFR_RD, IIC_XFR_ASYNC, and IIC_XFR_FAST when retrying xfr. */
+#define IIC_XFR_RESET_MASK 0x00000007
+
+/* Called by the engine specific code to notify us that the transfer ended.
+ * If the transfer requires a delay before starting a new transfer,
+ * (i.e., the IIC_XFR_DELAYED bit is set), then unlocking the address,
+ * notifying caller of completion, and cleanup of transfer will be delayed
+ * using a kernel timer.
+ */ 
+void iic_xfr_complete(iic_xfr_t* xfr)
+{
+	unsigned short delay;
+	int rc = 0;
+	iic_eng_t *eng = 0;
+	iic_xfr_opts_t *opts;
+
+	IENTER();
+
+	if(!xfr)
+	{
+		IFLDe(0, "iic_xfr_complete called on null xfr!\n");
+		goto exit;
+	}
+
+	opts = &xfr->opts.xfr_opts;
+
+	if(test_bit(IIC_XFR_ENDED, &xfr->flags) ||
+	   test_bit(IIC_XFR_RETRY_IN_PROGRESS, &xfr->flags))
+	{
+		IFLDd(2, "iic_xfr_complete xfr[%p] flags[%08lx] no-op\n", 
+				xfr, xfr->flags);
+		goto exit;
+	}
+	
+	eng = xfr->client->bus->eng;
+
+	if(xfr->status == -ETIME && eng->ops->finish_rescue_timeout) {
+		xfr->status =
+			((rc = eng->ops->finish_rescue_timeout(eng, xfr)) >=
+			(long int)xfr->size)
+			? 0
+			: -ETIME;
+		if(xfr->status == 0 && xfr->bytes_xfrd < xfr->size)
+			xfr->client->flags |= IIC_CLIENT_EOD;
+		IFLDd(1, "xfr->status[%d]\n", xfr->status);
+	}
+
+	/* Check if we need to retry this transfer if it failed.
+	 * Only the first failure's FFDC and status will be kept.
+	 * If the transfer succeeds on a retry, the FFDC will be freed
+	 * and no error will be reported.
+	 */
+	if(rec_retry(xfr))
+	{
+			
+		IFLDi(7, "RETRY    client[%p], bus[%d.%d:%d.%d.%d.%d]\n", 
+		      xfr->client, IIC_GET_PLINK(eng->id), IIC_GET_PCFAM(eng->id),
+		      IIC_GET_LINK(eng->id), IIC_GET_CFAM(eng->id), 
+		      IIC_GET_ENG(eng->id), xfr->client->bus->port);
+		IFLDi(3, "  xfr[%p] count[%d] status[%d]\n", 
+		      xfr, xfr->retry_count + 1, xfr->status);
+
+		/* increment retry count */
+		xfr->retry_count++;
+
+		/* reset timeout timer */
+		if(xfr->opts.xfr_opts.timeout)
+		{
+			mod_timer(&xfr->timeout, jiffies + 
+				   msecs_to_jiffies( allow_retry(xfr) ) );
+		}
+
+		/* if xfr timed out before starting, just leave it
+		 * on the queue and give it another chance to run.
+		 */
+		if(!test_bit(IIC_XFR_STARTED, &xfr->flags))
+		{
+			goto exit;
+		}
+
+		/* reset xfr to start at the beginning
+		 * Note: can't do DMA on a retry because
+		 * dma_setup can't be called from a
+		 * interrupt handler.
+		 */ 
+		delay = rec_delay(xfr);
+
+		/* Multi-master - Backoff an extended period after every four retries */
+		if ((xfr->status == -EALREADY) && ((xfr->retry_count % 4) == 0))
+		{
+			delay += BACKOFF_DELAY;
+
+			/* adjust timeout timer to include backoff */
+			if(xfr->opts.xfr_opts.timeout)
+			{
+				mod_timer(&xfr->timeout, jiffies + 
+					   msecs_to_jiffies( xfr->opts.xfr_opts.timeout +
+								 BACKOFF_DELAY ) );
+			}
+		}
+
+		xfr->status = 0;
+		xfr->flags &= IIC_XFR_RESET_MASK;
+
+		/* notify others that a retry is in progress, so don't
+		 * call iic_xfr_complete until retry is attempted.
+		 * This flag is cleared when an error occurs or the xfr
+		 * completes successfully or is cancelled.  (Problem
+		 * noticed in timeout function when dma xfrs were retried.)
+		 */
+		set_bit(IIC_XFR_RETRY_IN_PROGRESS, &xfr->flags);
+		
+		/* Always call iic_delay_xfr so that failed transfers
+		 * are given time to be cleaned up before we try
+		 * a new transfer.  If the delay is 0, the transfer
+		 * is placed back on the queue and started as soon as
+		 * cleanup of the previous attempt completes
+		 */
+		iic_delay_xfr(xfr, delay);
+		goto exit;
+	}
+
+
+#ifdef DELAYED_COMPLETION
+	if(!test_bit(IIC_XFR_DELAYED, &xfr->flags))
+	{
+#endif
+		/* if this xfr currently owned the address lock, release it */
+		if(xfr->addr_lck->cur_xfr == xfr)
+		{
+			xfr->addr_lck->cur_xfr = 0;
+		}
+
+		/* unlock this xfr's address lock or dequeue lock request */
+		IDBGd(1, "xfr[%p] releasing lock\n", xfr);
+		iic_unlock(&xfr->client->bus->eng->lck_mgr, xfr->addr_lck);
+#ifdef DELAYED_COMPLETION
+	}
+#endif
+
+	/* If a transfer isn't already running, check if one is ready and
+	 * start it.
+	 */
+	if(eng->cur_xfr == xfr)
+	{
+		eng->cur_xfr = 0;
+	}
+	iic_start_next_xfr(eng);
+
+	/* Once iic_xfr_complete is called, the timeout and delay timers are
+	 * no longer needed.
+	 */
+	del_timer(&xfr->timeout);
+
+#ifdef DELAYED_COMPLETION
+	/* For transfers that require a delay, take care of unlocking
+	 * the address, completion notification, and cleanup later.
+	 */
+	if(test_bit(IIC_XFR_DELAYED, &xfr->flags))
+	{
+		xfr->delay.data = (unsigned long)xfr;
+		xfr->delay.function = iic_finish_complete;
+		mod_timer(&xfr->delay, jiffies + msecs_to_jiffies( xfr->opts.xfr_opts.rdelay ) );
+		IFLDd(2, "DELAYCOMP xfr[%p] time[%d]\n", xfr,
+						xfr->opts.xfr_opts.rdelay);
+		goto exit;
+	}
+#endif
+	del_timer(&xfr->delay);
+
+	set_bit(IIC_XFR_ENDED, &xfr->flags);
+
+	IFLDi(7, "COMPLETE client[%p] bus[%d.%d:%d.%d.%d.%d]\n", 
+	      xfr->client, IIC_GET_PLINK(eng->id), IIC_GET_PCFAM(eng->id),
+	      IIC_GET_LINK(eng->id), IIC_GET_CFAM(eng->id), IIC_GET_ENG(eng->id),
+	      xfr->client->bus->port);
+	IFLDi(2, "  xfr[%p] status[%d]\n", xfr, xfr->status);
+
+	/**
+	 * defer queueing of ffdc to the calling thread
+	 * or to iic_cleanup_xfr for async transfers.
+	 */
+
+	/* for async transfers, just call aio_complete and then cleanup
+	 * the xfr object.
+	 */
+	if(test_bit(IIC_XFR_ASYNC, &xfr->flags))
+	{
+		xfr->status = (xfr->status)? xfr->status: xfr->bytes_xfrd;
+		IFLDd(1, "aio_complete xfr[%p]\n", xfr);
+//		aio_complete(xfr->iocb, xfr->status, 0);
+		iic_cleanup_xfr(xfr, NULL);
+	}
+	
+	/* for sync transfers, just wake up the calling thread.  The
+	 * calling thread will handle any necessary cleanup.
+	 */
+	else
+	{
+		IFLDd(1, "wake xfr[%p] client\n", xfr);
+		wake_up_interruptible(&xfr->client->wait);
+	}
+exit:
+	IEXIT(0);
+	return;
+}
+EXPORT_SYMBOL(iic_xfr_complete);
+
+/* This function is either called within an interrupt context or when
+ * interrupts are disabled.  This function is called as recovery from
+ * various types of failures.  FFDC should already be filled in before
+ * calling this function.  Failures caused by the abort are ignored.
+ */
+void iic_abort_xfr(iic_xfr_t* xfr)
+{
+	int rc = 0;
+	iic_eng_t *eng = xfr->client->bus->eng;
+	IENTER();
+	IFLDi(1, "ABORTREQ xfr[%p]\n", xfr);
+
+	if(test_bit(IIC_XFR_ABORT, &xfr->flags))
+	{
+		IDBGd(0, "abort already started!\n");
+		goto exit;
+	}
+
+	/* If this was a retry, the retry completed.  clear flag so that
+	 * iic_xfr_complete can do its work.
+	 */	
+	clear_bit(IIC_XFR_RETRY_IN_PROGRESS, &xfr->flags);
+	
+	set_bit(IIC_XFR_ABORT, &xfr->flags);
+	/* If the xfr is still waiting to run, remove it from the queue */
+	if(eng->cur_xfr != xfr)
+	{
+		list_del(&xfr->q_entry);
+	}
+	/* Otherwise, the xfr is running.  Lock the engine, Signal the hw 
+	 * to halt the transfer.
+	 */
+	else
+	{
+		/* lock the engine so we don't try to start a new transfer
+		 * until the current transfer is aborted
+		 */
+		set_bit(IIC_ENG_ABORT, &eng->flags);
+
+		/* once the IIC_ENG_ABORT flag is set, the interrupt
+		 * handler will no longer access the xfr data structure
+		 * and it's safe to set the IIC_XFR_ENG_COMPLETE flag.
+		 */
+		set_bit(IIC_XFR_ENG_COMPLETED, &xfr->flags);
+
+		/* don't access hw if failed due to a parent bus access error */
+		if(!test_bit(IIC_NO_ACCESS, &eng->flags))
+		{
+			/* start the abort procedure */
+			rc = eng->ops->start_abort(eng, 0/*ignore ffdc*/);
+		}
+
+		/* Finish off the abort inside a work queue context.  When
+		 * the abort is completed, the engine will get unlocked and
+		 * iic_start_next_xfr will get called.
+		 */
+		schedule_work(&eng->work);
+
+	}
+exit:
+	IEXIT(0);
+	return;
+}
+EXPORT_SYMBOL(iic_abort_xfr);
+
+/* Work queue function that finishes an abort operation */
+void iic_finish_abort(struct work_struct * work)
+{
+	unsigned long flags;
+	iic_eng_t* eng = container_of(work, iic_eng_t, work);
+	IENTER();
+	/* don't access hw if we lost engine access */
+	if(!test_bit(IIC_NO_ACCESS, &eng->flags))
+	{
+		eng->ops->finish_abort(eng, 0);
+	}
+	spin_lock_irqsave(&eng->lock, flags);
+	clear_bit(IIC_ENG_ABORT, &eng->flags);
+	iic_start_next_xfr(eng);
+	spin_unlock_irqrestore(&eng->lock, flags);
+	IFLDd(0, "ABORTREQ (completed)\n");
+	IEXIT(0);
+}
+
+	
+/* Timer function that handles the case where a transfer or abort takes
+ * too long to complete.
+ */
+void iic_timeout(unsigned long data)
+{
+	iic_xfr_t *xfr = (iic_xfr_t*)data;
+	iic_eng_t *eng  = xfr->client->bus->eng;
+	unsigned long flags;
+
+	spin_lock_irqsave(&eng->lock, flags);
+	IENTER();
+	IFLDi(1, "TIMEOUT  xfr[%p]\n", xfr);
+	if(test_bit(IIC_XFR_ENDED, &xfr->flags))
+		goto exit;
+
+	if(eng->ops->start_rescue_timeout) {
+		int rc;
+		xfr->status =
+			((rc = eng->ops->start_rescue_timeout(eng, xfr)) >=
+			(long int)xfr->size)
+			? xfr->status
+			: -ETIME;
+		if(xfr->status == 0 && xfr->bytes_xfrd < xfr->size)
+			xfr->client->flags |= IIC_CLIENT_EOD;
+	} else
+		xfr->status = -ETIME;
+
+	IFLDd(1, "xfr->status[%d]\n", xfr->status);
+
+	/* makes sure xfr_complete gets called in dma callback function */
+	set_bit(IIC_XFR_ENG_COMPLETED, &xfr->flags);
+
+	/* for DMA, this will cause dma_notify to get called which
+	 * calls our callback function, which calls
+	 * abort_xfr / xfr_complete.
+	 */
+	iic_abort_xfr(xfr);
+
+	/* Don't force users to wait for the abort to complete */
+	iic_xfr_complete(xfr);
+exit:
+	spin_unlock_irqrestore(&eng->lock, flags);
+	IEXIT(0);
+}
+
+int iic_xfr_ready(iic_xfr_t* xfr)
+{
+	int rc = 0;  //xfr not ready
+	iic_lck_t *lck = xfr->addr_lck;
+	IENTER();
+
+	/* If this xfr owns the lock and isn't write delayed, and isn't
+	 * blacklisted, the transfer is ready to run.
+	 */
+	if(!test_bit(IIC_XFR_DELAYED, &xfr->flags) &&
+			(lck->count > 0) &&
+			((lck->cur_xfr == 0) || (lck->cur_xfr == xfr)))
+	{
+		IDBGf(1, "xfr[%p] good to go\n", xfr);
+		lck->cur_xfr = xfr;
+		rc = 1;
+	}
+
+	IEXIT(rc);
+	return rc;
+}
+
+int iic_start_next_xfr(iic_eng_t* eng)
+{
+	int rc = 0;
+	iic_xfr_t *iterator, *xfr;
+	IENTER();
+	xfr = 0;
+
+	/* if a xfr is already running, or there is an abort or reset then
+	 * do nothing.
+	 */
+	if(eng->cur_xfr || 
+	   test_bit(IIC_ENG_ABORT, &eng->flags) ||
+	   test_bit(IIC_ENG_RESET, &eng->flags) ||
+	   test_bit(IIC_ENG_BLOCK, &eng->flags))
+	{
+		/* Notify thread waiting to do reset that the engine might
+		 * be idle now.
+		 */
+		if(test_bit(IIC_ENG_RESET, &eng->flags))
+		{
+			wake_up_interruptible(&eng->waitq);
+		}
+		goto exit;
+	}
+
+	IDBGl(0, "Looking for next xfr\n");
+	/* scan the queue from the beginning for a transfer that's ready */
+	/* if the process that submitted the xfr is black-listed, it will
+	 * be skipped
+	 */
+	list_for_each_entry(iterator, &eng->xfrq, q_entry)
+	{
+		if(iic_xfr_ready(iterator))
+		{
+			xfr = iterator;
+			break;
+		}
+	}
+
+	/* If a xfr is ready to go,  start it */
+	if(xfr)
+	{
+		/* set the delay bit here if necessary so that if the transfer
+		 * is aborted other transfers to the same address will be
+		 * delayed appropriately in iic_xfr_complete.
+		 */
+		if(xfr->opts.xfr_opts.rdelay)
+		{
+			set_bit(IIC_XFR_DELAYED, &xfr->flags);
+		}
+		eng->cur_xfr = xfr;
+		list_del(&xfr->q_entry);
+		if(!test_bit(IIC_XFR_STARTED, &xfr->flags))
+			IFLDs(3, "START    client[%p] bus[%08lx] xfr[%p]\n",
+				xfr->client, xfr->client->bus->bus_id, xfr);
+		clear_bit(IIC_NO_ACCESS, &eng->flags);
+		set_bit(IIC_XFR_STARTED, &xfr->flags);
+		rc = eng->ops->start(xfr); 
+		if(rc)
+		{
+			/* If this was a retry, the retry completed.  
+			 * clear flag so that
+			 * iic_xfr_complete can do its work.
+			 */	
+			clear_bit(IIC_XFR_RETRY_IN_PROGRESS, &xfr->flags);
+
+			IFLDe(2, "xfr[%p] start failed: %d\n", xfr, rc);
+			iic_abort_xfr(xfr); 
+			set_bit(IIC_XFR_ENG_COMPLETED, &xfr->flags);
+
+			iic_xfr_complete(xfr);
+		}
+	}
+
+exit:
+	IEXIT(rc);
+	return rc;
+}
+
+/* Adds a xfr to the end of the queue */
+int iic_enq_xfr(iic_xfr_t *xfr)
+{
+	int rc;
+	unsigned long flags;
+	iic_eng_t *eng = xfr->client->bus->eng;
+	iic_xfr_opts_t *opts = &xfr->opts.xfr_opts;
+	IENTER();
+	spin_lock_irqsave(&eng->lock, flags);
+
+	/* Submit a lock request for this xfr (non-blocking) */
+	rc = iic_req_lock(&eng->lck_mgr,
+			  opts->dev_addr,
+			  (opts->inc_addr >> (opts->dev_width * 8)),
+			  xfr->client,
+			  &xfr->addr_lck);
+	if(rc < 0)
+	{
+		goto exit;
+	}
+
+	/* enqueue this xfr */
+	IFLDi(7, "SUBMIT   client[%p] bus[%d.%d:%d.%d.%d.%d]\n",
+	      xfr->client, IIC_GET_PLINK(eng->id), IIC_GET_PCFAM(eng->id), 
+	      IIC_GET_LINK(eng->id), IIC_GET_CFAM(eng->id), IIC_GET_ENG(eng->id), 
+	      xfr->client->bus->port); 
+	IFLDi(5, "  xfr[%p] addr[%04x:%04x] sz[%08lx] timeout[%ld]\n", 
+	      xfr, opts->dev_addr + ((test_bit(IIC_XFR_RD, &xfr->flags))? 1:0), 
+	      opts->rsplit, xfr->size, opts->timeout);
+	list_add_tail(&xfr->q_entry, &eng->xfrq);
+	set_bit(IIC_XFR_QUEUED, &xfr->flags);
+
+	/* start a kernel timer that will abort the transfer 
+	 * if it takes too long.
+	 */
+	init_timer(&xfr->timeout);
+	xfr->timeout.data = (unsigned long)xfr;
+	xfr->timeout.function = iic_timeout;
+	if(opts->timeout)
+	{
+		xfr->timeout.expires = jiffies +
+				msecs_to_jiffies( allow_retry(xfr) );
+		add_timer(&xfr->timeout);
+	}
+	init_timer(&xfr->delay);
+
+	/* If no transfers are currently active, scan the queue for the
+	 * next transfer and start it
+	 */
+	iic_start_next_xfr(eng); 
+
+	rc = -EIOCBQUEUED;
+exit:
+	spin_unlock_irqrestore(&eng->lock, flags);
+	IEXIT(rc);
+
+	return rc;
+}
+
+int iic_wait_xfr(iic_xfr_t *xfr)
+{
+	int rc = 0;
+	unsigned long flags;
+
+	IENTER();
+	IFLDd(2, "WAIT     xfr[%p] time[%ld]\n", 
+			xfr, xfr->opts.xfr_opts.timeout);
+	rc = wait_event_interruptible(xfr->client->wait, 
+			test_bit(IIC_XFR_ENDED, &xfr->flags));
+	if(rc < 0)
+	{
+		/* EINTR is always retried at the adal level.  ADAL users
+		 * will never see the EINTR errno and won't know to collect
+		 * FFDC for it, so don't generate FFDC for EINTR but do
+		 * trace it.
+		 */
+		spin_lock_irqsave(&xfr->client->bus->eng->lock, flags);
+		if(!xfr->status)
+			xfr->status = -EINTR;
+		IFLDe(2, "aborting xfr[%p] due to signal. pid[%d]\n",
+			xfr, xfr->pid);
+		iic_abort_xfr(xfr);
+
+		/* Don't force users to wait for the abort to complete */
+		iic_xfr_complete(xfr);
+		spin_unlock_irqrestore(&xfr->client->bus->eng->lock, flags);
+	}
+	IEXIT(rc);
+	return rc;
+}
+
+/*
+ * Shared read method between user space applications and sideways kernel
+ * calls.
+ */
+ssize_t iic_common_read(iic_client_t * client, void * buf, size_t count,
+                        loff_t *offset, dd_ffdc_t ** o_ffdc)
+{
+	ssize_t rc = count;
+	iic_xfr_t *xfr;
+
+	IENTER();
+
+	BUG_ON(in_atomic());
+
+	if(!count)
+	{
+		rc = -EINVAL;
+		goto no_up;
+	}
+
+	if(down_interruptible(&client->sem))
+	{
+		rc = -EINTR;
+		goto no_up;
+	}
+
+	rc = iic_create_xfr(client, 0, buf, count, (1 << IIC_XFR_RD), &xfr,
+			o_ffdc);
+	if(rc)
+	{
+		goto exit;
+	}
+
+	/* enqueue or start the xfr */
+	rc = iic_enq_xfr(xfr);
+	if(rc != -EIOCBQUEUED)
+	{
+		goto error;
+	}
+
+	/* wait for xfr to complete */
+	iic_wait_xfr(xfr);
+
+	/* set rc appropriately */
+	if(xfr->status)
+	{
+		rc = xfr->status;
+	}
+	else
+	{
+		rc = xfr->bytes_xfrd;
+		client->opts.xfr_opts.offset += rc;
+	}
+
+	/* Data is already in the user buffer at this point.
+	 * Cleanup the transfer and return status to the user.
+	 */
+
+error:
+	iic_cleanup_xfr(xfr, o_ffdc);
+exit:
+	up(&client->sem);
+no_up:
+	IEXIT(rc);
+	return rc;
+}
+
+ssize_t iic_sideways_read(iic_client_t * client, void * buf, size_t count,
+                         loff_t *offset, dd_ffdc_t ** o_ffdc)
+{
+	client->opts.xfr_opts.offset = *offset;
+	return iic_common_read(client, buf, count, offset, o_ffdc);
+}
+EXPORT_SYMBOL(iic_sideways_read);
+
+ssize_t iic_read(struct file *filp, char __user *buf, size_t count,
+		 loff_t *offset)
+{
+	ssize_t rc = count;
+	char *kbuf;
+	iic_client_t *client = (iic_client_t*)filp->private_data;
+
+	IENTER();
+
+	if (client->flags & IIC_CLIENT_EOD) {
+		client->flags &= ~(IIC_CLIENT_EOD);
+		return 0;
+	}
+
+	if(filp->f_flags & O_NONBLOCK)
+	{
+		rc = -EAGAIN;
+		goto exit;
+	}
+
+	if(!access_ok(VERIFY_READ, buf, count))
+	{
+		rc = -EFAULT;
+		goto exit;
+	}
+
+	kbuf = kzalloc(count, GFP_KERNEL);
+	if (!kbuf) {
+		rc = -ENOMEM;
+		goto exit;
+	}
+
+	rc = iic_common_read(client, kbuf, count, offset, NULL);
+
+	copy_to_user(buf, kbuf, count);
+
+	kfree(kbuf);
+
+exit:
+	IEXIT(rc);
+	return rc;
+}
+
+/*
+ * Shared write method between user space and kernel 'sideways' calls.
+ */
+ssize_t iic_common_write(iic_client_t * client, void * buf, size_t count,
+                         loff_t * offset, dd_ffdc_t ** o_ffdc)
+{
+	ssize_t rc = count;
+	iic_xfr_t *xfr;
+
+	IENTER();
+
+	BUG_ON(in_atomic());
+
+	if(!count)
+	{
+		rc = -EINVAL;
+		goto no_up;
+	}
+
+	if(down_interruptible(&client->sem))
+	{
+		rc = -EINTR;
+		goto no_up;
+	}
+
+	rc = iic_create_xfr(client, 0, buf, count, 0, &xfr, o_ffdc);
+	if(rc)
+	{
+		goto exit;
+	}
+
+	/* enqueue or start the xfr */
+	rc = iic_enq_xfr(xfr);
+	if(rc != -EIOCBQUEUED)
+	{
+		goto error;
+	}
+
+	/* wait for xfr to complete */
+	iic_wait_xfr(xfr);
+
+	/* set rc appropriately */
+	if(xfr->status)
+	{
+		rc = xfr->status;
+	}
+	else
+	{
+		rc = xfr->bytes_xfrd;
+		client->opts.xfr_opts.offset += rc;
+	}
+
+error:
+	iic_cleanup_xfr(xfr, o_ffdc);
+exit:
+	up(&client->sem);
+no_up:
+	IEXIT(rc);
+	return rc;
+}
+
+ssize_t iic_sideways_write(iic_client_t * client, void * buf, size_t count,
+                          loff_t * offset, dd_ffdc_t ** o_ffdc)
+{
+	client->opts.xfr_opts.offset = *offset;
+	return iic_common_write(client, buf, count, offset, o_ffdc);
+}
+EXPORT_SYMBOL(iic_sideways_write);
+
+ssize_t iic_write(struct file *filp, const char __user *buf, size_t count,
+	       	  loff_t *offset)
+{
+	ssize_t rc = count;
+	char *kbuf;
+	iic_client_t *client = (iic_client_t*)filp->private_data;
+
+	IENTER();
+
+	if (client->flags & IIC_CLIENT_EOD) {
+		client->flags &= ~(IIC_CLIENT_EOD);
+		return 0;
+	}
+
+	/* don't support posted writes at this time */
+	if(filp->f_flags & O_NONBLOCK)
+	{
+		rc = -EAGAIN;
+		goto exit;
+	}
+
+	if(!access_ok(VERIFY_WRITE, buf, count))
+	{
+		rc = -EFAULT;
+		goto exit;
+	}
+
+	kbuf = kzalloc(count, GFP_KERNEL);
+	if (!kbuf) {
+		rc = -ENOMEM;
+		goto exit;
+	}
+
+	copy_from_user(kbuf, buf, count);
+
+	rc = iic_common_write(client, kbuf, count, offset, NULL);
+
+	kfree(kbuf);
+
+exit:
+	IEXIT(rc);
+	return rc;
+}
+
+/* timout is in milliseconds! */
+int iic_reset(iic_bus_t* bus, int timeout, iic_ffdc_t** ffdc)
+{
+	int rc;
+	IENTER();
+	//IFLDi(1, "bus[%08lx]: reset requested\n", bus->bus_id);
+	/* block new transfers from starting on the engine */
+	set_bit(IIC_ENG_RESET, &bus->eng->flags);
+
+	/* wait for any pending operations on the engine to complete */
+	/* Note - timeout must be in jiffies for wait_for_idle! */
+	rc = bus->eng->ops->wait_for_idle(bus->eng, msecs_to_jiffies( timeout ), ffdc);
+	if(!rc)
+	{
+		/* do the reset */
+		rc = bus->eng->ops->reset_bus(bus, ffdc);
+		if(!rc)
+		{
+			set_current_state(TASK_UNINTERRUPTIBLE);
+
+			/* schedule_timeout requires its parameter in jiffies. */
+			rc = schedule_timeout(IIC_RESET_DELAY);
+		}
+	}
+	if(rc < 0)
+	{
+		IFLDe(2, "bus[%08lx] reset failed: %d\n", bus->bus_id, rc);
+	}
+	else
+	{
+		IFLDi(2, "bus[%08lx]: reset complete. stucked[%d]",
+		      bus->bus_id, (rc == 1)? 1: 0);
+	}
+
+	/* restart processing of new transfers */
+	spin_lock_irq(&bus->eng->lock);
+	clear_bit(IIC_ENG_RESET, &bus->eng->flags);
+	iic_start_next_xfr(bus->eng);
+	spin_unlock_irq(&bus->eng->lock);
+
+	IEXIT(rc);
+	return rc;
+
+}
+EXPORT_SYMBOL(iic_reset);
+
+/* We need to make sure no transfers are in progress before reading the state
+ * of a bus in case we need to switch to a different bus.
+ * Note: timeout is in milliseconds!
+ */
+int iic_get_bus_state(iic_bus_t* bus, unsigned long* state, int timeout, 
+		iic_ffdc_t** ffdc)
+{
+	int rc;
+	
+	IENTER();
+	/* block new transfers from starting on the engine */
+	set_bit(IIC_ENG_RESET, &bus->eng->flags);
+	
+	/* wait for any pending operations on the engine to complete
+	 * Note:  timeout must be in jiffies
+	 */
+	rc = bus->eng->ops->wait_for_idle(bus->eng, msecs_to_jiffies( timeout ), ffdc);
+	if(!rc)
+	{
+	        /* check bus state */
+	        rc = bus->eng->ops->get_bus_state(bus, state, ffdc);
+		IDBGs(3, "get_bus_state[%08lx]: state=%08lx, rc=%d\n",
+				bus->bus_id, *state, rc);
+	}
+	
+	/* restart processing of new transfers */
+	spin_lock_irq(&bus->eng->lock);
+	clear_bit(IIC_ENG_RESET, &bus->eng->flags);
+	iic_start_next_xfr(bus->eng);
+	spin_unlock_irq(&bus->eng->lock);
+	        
+	IEXIT(rc);
+	return rc;
+}
+
+#define IIC_W(a) _IOC(_IOC_WRITE, 0, a, 0)
+#define IIC_R(a) _IOC(_IOC_READ, 0, a, 0)
+
+/*
+During an I2C transfer there is often the need to first send a command 
+and then read back an answer right away. This has to be done without the 
+risk of another (multimaster) device interrupting this atomic operation. 
+The I2C protocol defines a so-called repeated start condition. After 
+having sent the address byte (address and read/write bit) the master may 
+send any number of bytes followed by a stop condition. Instead of sending 
+the stop condition it is also allowed to send another start condition 
+again followed by an address (and of course including a read/write bit) 
+and more data. This is defined recursively allowing any number of start 
+conditions to be sent. The purpose of this is to allow combined 
+write/read operations to one or more devices without releasing the bus 
+and thus with the guarantee that the operation is not interrupted.
+
+Before reading data from the slave, you must tell it which of its
+internal address (offset) you want to read.
+So a read of the slave actually starts off by writing to it.
+This is the same as when you want to write to it: You send the
+start sequence, the I2C address of the slave with the R/W bit
+and the internal register number (i.e offset) you want to write to.
+Now you send another start sequence (sometimes called a restart)
+and the I2C address again - this time
+with the read bit set. You then read as many data bytes as you
+wish and terminate the transaction with a stop sequence.
+*/
+
+int iic_repeated_xfer(iic_client_t *client, struct i2c_msg msgs[], int num)
+{
+	struct i2c_msg *pmsg;
+	iic_xfr_t *xfr;
+	iic_opts_t* opts;
+	iic_xfr_opts_t* xfr_opts;
+	u8 __user **data_ptrs;
+	u8 *current_msg_buf_ptr;
+	int rc = 0;
+	int i;
+	unsigned long options;
+
+	opts = &client->opts;
+	xfr_opts = &opts->xfr_opts;
+	//unsigned long new_port = xfr->client->bus->port;
+
+	data_ptrs = kmalloc(num * sizeof(u8 __user *), GFP_KERNEL);
+        if (data_ptrs == NULL) {
+                rc = -ENOMEM;
+		goto exit;
+        }
+
+	// Get the offset from the configuration which is the default.
+	// The default will be overridden by the message.
+	for (i = 0; i < num; i++) {
+		pmsg = &msgs[i];
+		if (!pmsg->len) /* If length is zero */
+                     continue;  /* on to the next request. */
+		data_ptrs[i] = (u8 __user *)msgs[i].buf;
+		current_msg_buf_ptr = kmalloc(pmsg->len, GFP_KERNEL); 
+		if (current_msg_buf_ptr == NULL) 
+		{
+			rc = -ENOMEM;
+			goto error;
+		}
+		// Bring over the user space buffer in order to
+		// retrieve the offset.
+		// We need to set up the offset before calling
+		// iic_create_xfr. 
+		// We still pass the user buffer to the iic_create_xfr because
+		// the function will do its own conversion.
+	  	if(copy_from_user(current_msg_buf_ptr,
+                         data_ptrs[i],
+                         msgs[i].len)) 
+		{
+                        rc = -EFAULT;
+			kfree(current_msg_buf_ptr);
+			goto error;
+                }
+
+		options = 0;
+		if (i != num -1)
+		{
+			set_bit(IIC_REPEATED_START, &options);
+			xfr_opts->flags |= options;
+		}
+		else
+		{
+			clear_bit(IIC_REPEATED_START, &options);
+			xfr_opts->flags &= options;
+		}
+
+		// Need to set the slave address here
+		xfr_opts->dev_addr =  pmsg->addr;
+		// The offset is passed down by the adal_iic_config using 
+		// the ADAL_IIC_CFG_OFFSET parameter.
+		// Refer to the ioctl case IIC_W(IIC_IOC_OFFSET):
+		// xfr_opts->offset = val;
+		// The user should configure the dev_width for their specific
+		// slave device before calling the ioctl.
+		// If the dev_width = 0, then we set the option to default 2. 
+		if (xfr_opts->dev_width == 0)
+		{
+			xfr_opts->dev_width = 2;
+		}
+		
+
+		// This is the read command
+		if (pmsg->flags & I2C_M_RD) 
+		{
+			//set_bit(IIC_XFR_RD, &options);
+			rc = iic_create_xfr(client, 0, (void*)pmsg->buf,
+					   pmsg->len, (1 << IIC_XFR_RD), &xfr,
+					   NULL);
+		}
+		else
+		{
+			if (num > 1)
+			{
+				// Multiple messages recieved
+				// The first msg contains the offset for 
+				// the repeated start.
+				// Otherwise it's just a regular write.
+				if ( (i == 0) && (msgs[1].flags & I2C_M_RD) )
+				{
+					   xfr_opts->offset = *current_msg_buf_ptr;
+					// Set the offset and don't do the write
+					continue;
+				}
+			} 
+
+			// This is a regular write
+
+			rc = iic_create_xfr(client, 0, (void*)pmsg->buf,
+					    pmsg->len, 0, &xfr, NULL);
+		}
+		if(rc)
+		{
+			goto exit;
+		}
+		/* enqueue or start the xfr */
+		rc = iic_enq_xfr(xfr);
+		if(rc != -EIOCBQUEUED)
+		{
+			goto error;
+		}
+
+		/* wait for xfr to complete */
+		iic_wait_xfr(xfr);
+		/* set rc appropriately */
+		rc = xfr->status;
+		kfree(current_msg_buf_ptr);
+	}
+	/* Data is already in the user buffer at this point.
+	 * Cleanup the transfer and return status to the user.
+	 */
+	
+error:
+	iic_cleanup_xfr(xfr, NULL);
+exit:
+	kfree(data_ptrs);
+	return rc;
+}
+long iic_ioctl(struct file *file, unsigned int cmd,
+              unsigned long arg)
+{
+	iic_ffdc_t* ffdc = 0;
+	int ret = 0;
+	unsigned long val = 0;
+	iic_client_t* client;
+	iic_eng_t* eng;
+	iic_opts_t* opts;
+	iic_xfr_opts_t* xfr_opts;
+	iic_rec_pol_t* recovery;
+	int ioc_nr = _IOC_NR(cmd);
+	struct i2c_msg *iic_msg_ptr;
+	struct i2c_rdwr_ioctl_data iic_msg_arg;
+
+	IENTER();
+
+	client = (iic_client_t*)file->private_data;
+	eng = client->bus->eng;
+	
+	/* Allow address unlock to occur even if blacklisted or blocked */
+	if(ioc_nr == IIC_IOC_ULCK_ADDR)
+		goto skip_check;
+	if(test_bit(IIC_ENG_BLOCK, &eng->flags))
+	{
+		IFLDe(1, "IOCTL    eng[%08x] blocked\n", eng->id);
+		ret = -ENODEV;
+		if(test_bit(IIC_ENG_REMOVED, &eng->flags))
+			ret = -ENOLINK;
+		goto exit;
+	}
+
+skip_check:
+	opts = &client->opts;
+	xfr_opts = &opts->xfr_opts;
+	recovery = &opts->recovery;
+
+	if(down_interruptible(&client->sem))
+	{
+		IEXIT(-EINTR);
+		return -EINTR;
+	}
+
+	if((_IOC_TYPE(cmd) != IIC_IOC_MAGIC) ||
+	   (_IOC_NR(cmd) > IIC_IOC_MAXNR))
+	{
+		ret = -ENOTTY;
+		goto exit;
+	}
+
+	/* strip the magic and size info from the command that we don't care
+	 * about.
+	 */
+	cmd = _IOC(_IOC_DIR(cmd), 0, _IOC_NR(cmd), 0);
+
+	/* Check if no data needs to be transfered for this ioctl */
+	if(_IOC_DIR(cmd) == _IOC_NONE)
+	{
+		switch(_IOC_NR(cmd))
+		{
+			case IIC_IOC_RESET_FULL:
+				/* reset xfr opts to default values */
+				memcpy(opts, &iic_dflt_opts, sizeof(*opts));
+
+			case IIC_IOC_RESET_LIGHT:
+				/* only allow 1 user requested reset per engine
+				 * at a time.
+				 */
+				IFLDi(2, "RESET    client[%p] bus[%08lx]\n",
+						client, client->bus->bus_id);
+				if(down_interruptible(&eng->sem))
+				{
+					ret = -EINTR;
+					break;
+				}
+				ret = iic_reset(client->bus, xfr_opts->timeout, &ffdc);
+				up(&eng->sem);
+
+				break;
+			case IIC_IOC_REPEATED_IO:
+				// The buffer pointer is stored in arg.
+				// Try to get the pointer out from the arg and
+				// send the request out one by one using the
+				// existing I/O method. 
+				// Do not write "STOP" until the last I/O request
+				// is done.
+	            		ret = copy_from_user(&iic_msg_arg,
+               		   		(struct i2c_rdwr_ioctl_data __user *)arg, sizeof(iic_msg_arg));
+	            		if (ret)
+				{
+					ret = -EFAULT;
+					break;
+				}
+				/* Put an arbitrary limit on the number of messages that can
+                 		* be sent at once */
+                		if (iic_msg_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)
+				{
+					ret = -EFAULT;
+					break;
+				}
+				iic_msg_ptr = (struct i2c_msg *)
+                        	kmalloc(iic_msg_arg.nmsgs * sizeof(struct i2c_msg),
+                        		GFP_KERNEL);
+				if (iic_msg_ptr == NULL)
+				{
+					ret = -ENOMEM;
+					break;
+				}
+
+		        	if (copy_from_user(iic_msg_ptr, iic_msg_arg.msgs,
+                                   iic_msg_arg.nmsgs * sizeof(struct i2c_msg))) 				{
+                            		kfree(iic_msg_ptr);
+                            		ret =  -EFAULT;
+					break;
+                		}
+
+				// We don't want to convert the data pointer here
+				// because the set_iic_xfr will do the conversion 
+
+				ret = iic_repeated_xfer(client, iic_msg_ptr, 
+					iic_msg_arg.nmsgs);
+				if (ret < 0)
+				{
+					ret = -EFAULT;
+                			kfree(iic_msg_ptr);
+					break;
+				}
+				else
+					ret = 0;
+                		kfree(iic_msg_ptr);
+
+			break;
+			default:
+				ret = -EINVAL;
+		}
+		goto exit;
+	}
+
+	/* handle 4 byte args here */
+	if(ioc_nr <= IIC_IOC_4_BYTES)
+	{
+		if((_IOC_DIR(cmd) == _IOC_WRITE) &&
+	   	   (ret = get_user(val, (unsigned long*)arg)))
+		{
+			goto exit;
+		}
+		switch(cmd)
+		{
+			case IIC_W(IIC_IOC_SPEED):
+				if((val < 1) || (val > 55))
+				{
+					ret = -EINVAL;
+					break;
+				}
+				ret = eng->ops->set_speed(client->bus, 
+							  val);
+				break;
+			case IIC_R(IIC_IOC_SPEED):
+				val = eng->ops->get_speed(client->bus);
+				break;
+			case IIC_W(IIC_IOC_DEV_ADDR):
+				xfr_opts->dev_addr = val;
+				xfr_opts->offset = 0;
+				break;
+			case IIC_R(IIC_IOC_DEV_ADDR):
+				val = xfr_opts->dev_addr;
+				break;
+			case IIC_W(IIC_IOC_DEV_WIDTH):
+				xfr_opts->dev_width = val;
+				xfr_opts->offset = 0;
+				break;
+			case IIC_R(IIC_IOC_DEV_WIDTH):
+				val = xfr_opts->dev_width;
+				break;
+			case IIC_W(IIC_IOC_OFFSET):
+				xfr_opts->offset = val;
+				break;
+			case IIC_R(IIC_IOC_OFFSET):
+				val = xfr_opts->offset;
+				break;
+			case IIC_W(IIC_IOC_INC_ADDR):
+				xfr_opts->inc_addr = val;
+				break;
+			case IIC_R(IIC_IOC_INC_ADDR):
+				val = xfr_opts->inc_addr;
+				break;
+			case IIC_W(IIC_IOC_TIMEOUT):
+				xfr_opts->timeout = val;
+				break;
+			case IIC_R(IIC_IOC_TIMEOUT):
+				val = xfr_opts->timeout;
+				break;
+			case IIC_W(IIC_IOC_RDELAY):
+				xfr_opts->rdelay = val;
+				break;
+			case IIC_R(IIC_IOC_RDELAY):
+				val = xfr_opts->rdelay;
+				break;
+			case IIC_W(IIC_IOC_WDELAY):
+				xfr_opts->wdelay = val;
+				break;
+			case IIC_R(IIC_IOC_WDELAY):
+				val = xfr_opts->wdelay;
+				break;
+			case IIC_W(IIC_IOC_RSPLIT):
+				xfr_opts->rsplit = val;
+				break;
+			case IIC_R(IIC_IOC_RSPLIT):
+				val = xfr_opts->rsplit;
+				break;
+			case IIC_W(IIC_IOC_WSPLIT):
+				xfr_opts->wsplit = val;
+				break;
+			case IIC_R(IIC_IOC_WSPLIT):
+				val = xfr_opts->wsplit;
+				break;
+			case IIC_W(IIC_IOC_REDO_POL):
+				recovery->redo_pol = val;
+				break;
+			case IIC_R(IIC_IOC_REDO_POL):
+				val = recovery->redo_pol;
+				break;
+			case IIC_W(IIC_IOC_REDO_DELAY):
+				recovery->redo_delay = val;
+				break;
+			case IIC_R(IIC_IOC_REDO_DELAY):
+				val = recovery->redo_delay;
+				break;
+			case IIC_R(IIC_IOC_BUS_STATE):
+				if(down_interruptible(&eng->sem))
+				{
+					ret = -EINTR;
+					break;
+				}
+				ret = iic_get_bus_state(client->bus, &val,
+							xfr_opts->timeout, 
+							&ffdc);
+				up(&eng->sem);
+				break;
+			case IIC_W(IIC_IOC_FLAGS):
+				if(val & ~(IIC_FORCE_DMA | IIC_NO_DMA | 
+							IIC_SPECIAL_RD))
+				{
+					ret = -EINVAL;
+					break;
+				}
+				xfr_opts->flags = val;
+				break;
+			case IIC_R(IIC_IOC_FLAGS):
+				val = xfr_opts->flags;
+				break;
+			default:
+				ret = -EINVAL;
+		}
+		if((_IOC_DIR(cmd) == _IOC_READ) && !ret)
+		{
+			ret = put_user(val, (unsigned long*)arg);
+		}
+		goto exit;
+	}
+
+	/* handle objects larger than 4 bytes here */
+	switch(cmd)
+	{
+		iic_lock_t ulck;
+		iic_lck_t *klck;
+
+		case IIC_W(IIC_IOC_LCK_ADDR):
+		case IIC_W(IIC_IOC_LCK_ENG):
+			if((ret = copy_from_user(&ulck, (void*)arg, 
+							sizeof(ulck))))
+			{
+				ret = -EFAULT;
+				break;
+			}
+
+			ret = iic_wait_lock(&eng->lck_mgr, ulck.addr,
+					    (cmd == IIC_W(IIC_IOC_LCK_ENG))
+					    ? ulck.mask
+					    : ulck.mask >> 1,
+					    client,
+					    msecs_to_jiffies( ulck.timeout));
+			break;
+		case IIC_W(IIC_IOC_ULCK_ADDR):
+		case IIC_W(IIC_IOC_ULCK_ENG):
+			if((ret = copy_from_user(&ulck, (void*)arg, 
+							sizeof(ulck))))
+			{
+				ret = -EFAULT;
+				break;
+			}
+			spin_lock_irq(&eng->lock);
+			klck = iic_find_handle(&eng->lck_mgr, client,
+					       ulck.addr,
+					       (cmd == IIC_W(IIC_IOC_ULCK_ENG))
+					       ? ulck.mask
+					       : ulck.mask >> 1);
+			if(klck)
+			{
+				ret = iic_unlock(&eng->lck_mgr, klck);
+				if(!ret)
+					iic_start_next_xfr(eng);
+			}
+			spin_unlock_irq(&eng->lock);
+			break;
+		case IIC_W(IIC_IOC_ALL):
+			if((ret = copy_from_user(opts, (void*)arg, 
+							sizeof(*opts))))
+			{
+				ret = -EFAULT;
+			}
+			break;
+		case IIC_R(IIC_IOC_ALL):
+			if((ret = copy_to_user((void*)arg, opts, 
+							sizeof(*opts))))
+			{
+				ret = -EFAULT;
+			}
+			break;
+		case IIC_W(IIC_IOC_DISPLAY_REGS):
+			eng->ops->display_regs(eng, 0);
+			break;
+		default:
+			ret = -EINVAL;
+			goto exit;
+	}
+
+exit:
+	up(&client->sem);
+	IFLDd(5, "IOCTL    client[%p] bus[%08lx] cmd[%08x] ptr[%08lx] val[%08lx]\n",
+			client, client->bus->bus_id, cmd, arg, val);
+	IEXIT(ret);
+	return ret;
+}
+
+loff_t iic_llseek(struct file *filp, loff_t off, int whence)
+{
+	iic_client_t* client;
+	iic_xfr_opts_t* xfr_opts;
+	loff_t new_pos;
+
+	IENTER();
+	client = (iic_client_t*)filp->private_data;
+	xfr_opts = &client->opts.xfr_opts;
+
+	if(down_interruptible(&client->sem))
+	{
+		new_pos = -EINTR;
+		goto exit;
+	}
+	switch(whence)
+	{
+		case 0: /* SEEK_SET */
+			new_pos = off;
+			break;
+		case 1: /* SEEK_CUR */
+			new_pos = xfr_opts->offset + off;
+			break;
+		case 2: /* SEEK_END, not supported */
+		default:
+			new_pos = -EINVAL;
+	}
+
+	if(new_pos >= 0)
+	{
+		xfr_opts->offset = new_pos;
+	}
+	up(&client->sem);
+exit:
+	IFLDd(2, "client[%p] seek: new_pos=%08lx\n", client, 
+		(unsigned long)new_pos);
+	IEXIT((int)new_pos);
+	return new_pos;
+}
+
+static int iic_mmap(struct file* filp, struct vm_area_struct* vma)
+{
+	int rc = 0;
+	iic_client_t *client = (iic_client_t*)filp->private_data;
+	iic_eng_t* eng = client->bus->eng;
+	// iopa doesn't exist in MCP6 kernel 
+	unsigned long phys_base_addr = virt_to_phys((unsigned long *)eng->base);
+	IENTER();
+	printk("mmap\n");
+
+	vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP | VM_IO;
+	printk(">>remap_page_range(%08lX, 0, %08lX, %08lX, )\n",
+			vma->vm_start, phys_base_addr, vma->vm_end - vma->vm_start);
+	rc = remap_pfn_range(vma, vma->vm_start, phys_base_addr,
+			vma->vm_end - vma->vm_start,
+			vma->vm_page_prot);
+	printk("<<remap_page_range = %d\n", rc);
+	if(rc){
+		return -EINVAL;
+	}
+	IEXIT(0);
+	return 0;
+}
+
 int iic_register_eng_ops(iic_eng_ops_t* new_ops, unsigned long type)
 {
 	iic_eng_type_t* new_type = (iic_eng_type_t*)
diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
index 9493842..aa4eb10 100644
--- a/include/uapi/linux/Kbuild
+++ b/include/uapi/linux/Kbuild
@@ -155,6 +155,7 @@ header-y += hyperv.h
 header-y += hysdn_if.h
 header-y += i2c-dev.h
 header-y += i2c.h
+header-y += i2cfsi.h
 header-y += i2o-dev.h
 header-y += i8k.h
 header-y += icmp.h
diff --git a/include/uapi/linux/i2cfsi.h b/include/uapi/linux/i2cfsi.h
new file mode 100644
index 0000000..78cab53
--- /dev/null
+++ b/include/uapi/linux/i2cfsi.h
@@ -0,0 +1,136 @@
+/*
+ *   Copyright (c) International Business Machines Corp., 2006, 2009
+ *
+ *   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 _UAPI_I2CFSI_H
+#define _UAPI_I2CFSI_H
+
+#define  I2C_RDRW_IOCTL_MAX_MSGS        42
+
+typedef struct iic_rec_pol
+{
+	unsigned long redo_pol;
+#define IIC_VAL_ADDR_NOACK	0x00010000
+#define IIC_VAL_DATA_NOACK	0x00020000
+#define IIC_VAL_TIMEOUT		0x00040000
+#define IIC_VAL_LOST_ARB	0x00080000
+#define IIC_VAL_BUS_ERR		0x00100000
+#define IIC_VAL_ALL_ERRS	0xffff0000
+	unsigned long rsvd;
+	unsigned long redo_delay;
+} iic_rec_pol_t;
+
+#define IIC_VAL_100KHZ  100
+#define IIC_VAL_400KHZ	400
+typedef struct iic_xfr_opts
+{
+	unsigned short rsvd;
+	unsigned short dev_addr;	// address of end device
+	unsigned short dev_width;	// number of bytes for offset (1-4)
+	unsigned long inc_addr;		// mask of address bits to increment
+					// for devices that span multiple
+					// addresses.
+	unsigned long timeout;		// operation timeout (msec)
+	unsigned short wdelay;		// delay between write xfrs (msec)
+	unsigned short rdelay;		// delay between read xfrs (msec)
+	unsigned short wsplit;		// splits writes into smaller chunks
+	unsigned short rsplit;		// splits reads into smaller chunks
+	unsigned long offset;		// offset from beginning of device
+	unsigned long flags;		// flags defined below
+} iic_xfr_opts_t;
+
+enum 
+{
+	IIC_FORCE_DMA = 0x01,		// use dma regardless of xfr size
+	IIC_NO_DMA = 0x02,		// disallow dma
+	IIC_SPECIAL_RD = 0x04,		// workaround for PLL/CRC chips
+	IIC_REPEATED_START = 0x08,      // repeated start
+};
+
+typedef struct iic_opts
+{
+	iic_xfr_opts_t xfr_opts;
+	iic_rec_pol_t recovery;
+} iic_opts_t;
+
+typedef struct iic_lock
+{
+	unsigned short mask;
+	unsigned short addr;
+	unsigned long timeout;
+} iic_lock_t;
+
+typedef struct iicslv_opts
+{
+	unsigned long addr;
+	unsigned long timeout;
+} iicslv_opts_t;
+
+#define IICSLV_ZBUF_MAX_SZ	256
+
+/* external master access mode of local slave shared buffer */
+enum 
+{
+	IICSLV_BUF_MODE_EXT_R = 1,      
+	IICSLV_BUF_MODE_EXT_RW = 2,  
+};
+
+/* Master IOCTL Ordinal Numbers */
+#define IIC_IOC_MAGIC 		0x07
+enum
+{
+	/* 0 bytes */
+	IIC_IOC_RESET_LIGHT,
+	IIC_IOC_RESET_FULL,
+
+	IIC_IOC_0_BYTES = IIC_IOC_RESET_FULL,
+
+	/* 4 bytes */
+	IIC_IOC_SPEED,
+	IIC_IOC_DEV_ADDR,
+	IIC_IOC_DEV_WIDTH,
+	IIC_IOC_OFFSET,
+	IIC_IOC_INC_ADDR,
+	IIC_IOC_TIMEOUT,
+	IIC_IOC_RDELAY,
+	IIC_IOC_WDELAY,
+	IIC_IOC_RSPLIT,
+	IIC_IOC_WSPLIT,
+	IIC_IOC_REDO_POL,
+	IIC_IOC_SPD_POL,
+	IIC_IOC_REDO_DELAY,
+	IIC_IOC_BUS_STATE,
+#define IIC_VAL_BOTH_LO 0x00
+#define IIC_VAL_SDA_LO  0x01
+#define IIC_VAL_SCL_LO  0x02
+#define IIC_VAL_BOTH_HI 0x03
+	IIC_IOC_FLAGS,
+
+	IIC_IOC_4_BYTES = IIC_IOC_FLAGS,
+
+	/* Objects */
+	IIC_IOC_LCK_ADDR,
+	IIC_IOC_ULCK_ADDR,
+	IIC_IOC_LCK_ENG,
+	IIC_IOC_ULCK_ENG,
+	IIC_IOC_ALL,
+	IIC_IOC_DISPLAY_REGS,
+	IIC_IOC_REPEATED_IO,
+	IIC_IOC_MAXNR = IIC_IOC_REPEATED_IO,
+};
+
+#endif
-- 
1.8.3.1

  parent reply	other threads:[~2017-02-02 23:26 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-02-02 23:25 [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client eajames
2017-02-02 23:25 ` [PATCH linux v1 4/8] drivers: fsi: Add i2c client driver eajames
2017-02-03  0:56   ` Alistair Popple
2017-02-03  0:58     ` Joel Stanley
2017-02-02 23:25 ` [PATCH linux v1 5/8] drivers: fsi: i2c: Add engine access wrappers eajames
2017-02-02 23:25 ` [PATCH linux v1 6/8] drivers: fsi: i2c: probe fsi device for i2c client eajames
2017-02-02 23:26 ` eajames [this message]
2017-02-02 23:26 ` [PATCH linux v1 8/8] drivers: fsi: i2c: boe engine eajames
2017-02-02 23:26 ` [PATCH v2 1/3] drivers/fsi: Add slave interrupt polling eajames
2017-02-03  1:11   ` Alistair Popple
2017-02-03 16:18     ` eajames
2017-02-03 20:33       ` Benjamin Herrenschmidt
2017-02-02 23:26 ` [PATCH v2 2/3] drivers/fsi: Add Client IRQ Enable / Disable eajames
2017-02-02 23:26 ` [PATCH v2 3/3] drivers/fsi: Add sysfs file to adjust i-poll period eajames
2017-02-03  0:55 ` [PATCH linux v1 0/8] drivers: fsi: interrupt polling, i2c client Joel Stanley
2017-02-03 16:36   ` eajames

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1486077964-11346-5-git-send-email-eajames@linux.vnet.ibm.com \
    --to=eajames@linux.vnet.ibm.com \
    --cc=alistair@popple.id.au \
    --cc=benh@kernel.crashing.org \
    --cc=eajames@us.ibm.com \
    --cc=joel@jms.id.au \
    --cc=openbmc@lists.ozlabs.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.