[PATCH v2 1/2] i2c: Multiplexed I2C bus core support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add multiplexed bus core support. I2C multiplexer and switches
like pca954x get instantiated as new adapters per output.

Signed-off-by: Michael Lawnick <ml.lawnick@xxxxxx>

---
Based on kernel 2.6.33 +
[PATCH] i2c-core: Use per-adapter userspace device lists
by Jean Delware <20100424201914.08b3f008@xxxxxxxxxxxxxxxx>
<http://article.gmane.org/gmane.linux.drivers.i2c/5994/>

 drivers/i2c/Kconfig     |    8 ++
 drivers/i2c/Makefile    |    1 +
 drivers/i2c/i2c-core.c  |   93 ++++++++++++++++++++++--
 drivers/i2c/i2c-dev.c   |   46 ++++++++++++-
 drivers/i2c/i2c-mux.c   |  184 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/i2c-mux.h |   46 ++++++++++++
 include/linux/i2c.h     |   11 +++
 7 files changed, 381 insertions(+), 8 deletions(-)
 create mode 100755 drivers/i2c/i2c-mux.c
 create mode 100755 include/linux/i2c-mux.h

diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
index 8d8a00e..2cd6d78 100755
--- a/drivers/i2c/Kconfig
+++ b/drivers/i2c/Kconfig
@@ -36,6 +36,14 @@ config I2C_COMPAT
 	  other user-space package which expects i2c adapters to be class
 	  devices. If you don't know, say Y.
 
+config I2C_MUX
+	tristate "I2C bus multiplexing support"
+	depends on I2C
+	help
+	  Say Y here if you want the I2C core to support the ability to
+	  handle multiplexed I2C bus topologies, by presenting each
+	  multiplexed segment as a I2C adapter.
+
 config I2C_CHARDEV
 	tristate "I2C device interface"
 	help
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
index ba26e6c..a488e8b 100755
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -5,6 +5,7 @@
 obj-$(CONFIG_I2C_BOARDINFO)	+= i2c-boardinfo.o
 obj-$(CONFIG_I2C)		+= i2c-core.o
 obj-$(CONFIG_I2C_CHARDEV)	+= i2c-dev.o
+obj-$(CONFIG_I2C_MUX)		+= i2c-mux.o
 obj-y				+= busses/ chips/ algos/
 
 ifeq ($(CONFIG_I2C_DEBUG_CORE),y)
diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c
index fc93e28..682966a 100755
--- a/drivers/i2c/i2c-core.c
+++ b/drivers/i2c/i2c-core.c
@@ -20,7 +20,10 @@
 /* With some changes from Kyösti Mälkki <kmalkki@xxxxxxxxx>.
    All SMBus-related things are written by Frodo Looijaard <frodol@xxxxxx>
    SMBus 2.0 support by Mark Studebaker <mdsxyz123@xxxxxxxxx> and
-   Jean Delvare <khali@xxxxxxxxxxxx> */
+   Jean Delvare <khali@xxxxxxxxxxxx>
+   Mux support by Rodolfo Giometti <giometti@xxxxxxxxxxxx> and
+   Michael Lawnick <michael.lawnick.ext@xxxxxxx> */
+
 
 #include <linux/module.h>
 #include <linux/kernel.h>
@@ -288,6 +291,12 @@ struct i2c_client *i2c_verify_client(struct device *dev)
 }
 EXPORT_SYMBOL(i2c_verify_client);
 
+static int i2c_parent_is_i2c_adapter(const struct i2c_adapter *adapter)
+{
+	return adapter->dev.parent != NULL
+		&& adapter->dev.parent->bus == &i2c_bus_type;
+}
+
 
 /**
  * i2c_new_device - instantiate an i2c device
@@ -624,6 +633,7 @@ static int i2c_register_adapter(struct i2c_adapter *adap)
 
 	rt_mutex_init(&adap->bus_lock);
 	INIT_LIST_HEAD(&adap->userspace_clients);
+	INIT_LIST_HEAD(&adap->mux_list_head);
 
 	/* Set default timeout to 1 second if not already set */
 	if (adap->timeout == 0)
@@ -949,9 +959,78 @@ static int __i2c_check_addr(struct device *dev, void *addrp)
 	return 0;
 }
 
+/* walk up mux tree */
+static int i2c_check_mux_parents(struct i2c_adapter *adapter, int addr)
+{
+	int	result;
+
+	result = device_for_each_child(&adapter->dev, &addr, __i2c_check_addr);
+
+	if (!result && i2c_parent_is_i2c_adapter(adapter))
+		result = i2c_check_mux_parents(
+				    to_i2c_adapter(adapter->dev.parent), addr);
+
+	return result;
+}
+
+/* recurse down mux tree */
+static int i2c_check_mux_childs(struct i2c_adapter *adapter, int addr)
+{
+	int			result = 0;
+	struct i2c_adapter	*child, *next;
+
+	list_for_each_entry_safe(child, next, &adapter->mux_list_head,
+							 mux_list) {
+		result = device_for_each_child(&child->dev, &addr,
+							     __i2c_check_addr);
+		if (result)
+			break;
+		result = i2c_check_mux_childs(child, addr);
+	}
+	return result;
+}
+
 static int i2c_check_addr(struct i2c_adapter *adapter, int addr)
 {
-	return device_for_each_child(&adapter->dev, &addr, __i2c_check_addr);
+	int	result;
+
+	result = i2c_check_mux_parents(adapter, addr);
+
+	if (!result)
+		result = i2c_check_mux_childs(adapter, addr);
+
+	return result;
+}
+
+static void i2c_mux_tree_lock(struct i2c_adapter *adap)
+{
+	if (i2c_parent_is_i2c_adapter(adap))
+		i2c_mux_tree_lock(to_i2c_adapter(adap->dev.parent));
+	else
+		i2c_lock_adapter(adap);
+}
+
+static int i2c_mux_tree_trylock(struct i2c_adapter *adap)
+{
+	int			ret;
+	struct i2c_adapter	*parent;
+
+	if (i2c_parent_is_i2c_adapter(adap)) {
+		parent = to_i2c_adapter(adap->dev.parent);
+		ret = i2c_mux_tree_trylock(parent);
+	} else {
+		ret = i2c_trylock_adapter(adap);
+	}
+
+	return ret;
+}
+
+static void i2c_mux_tree_unlock(struct i2c_adapter *adap)
+{
+	if (i2c_parent_is_i2c_adapter(adap))
+		i2c_mux_tree_unlock(to_i2c_adapter(adap->dev.parent));
+	else
+		i2c_unlock_adapter(adap);
 }
 
 /**
@@ -1104,12 +1183,12 @@ int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
 #endif
 
 		if (in_atomic() || irqs_disabled()) {
-			ret = rt_mutex_trylock(&adap->bus_lock);
+			ret = i2c_mux_tree_trylock(adap);
 			if (!ret)
 				/* I2C activity is ongoing. */
 				return -EAGAIN;
 		} else {
-			rt_mutex_lock(&adap->bus_lock);
+			i2c_mux_tree_lock(adap);
 		}
 
 		/* Retry automatically on arbitration loss */
@@ -1121,7 +1200,7 @@ int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
 			if (time_after(jiffies, orig_jiffies + adap->timeout))
 				break;
 		}
-		rt_mutex_unlock(&adap->bus_lock);
+		i2c_mux_tree_unlock(adap);
 
 		return ret;
 	} else {
@@ -1857,7 +1936,7 @@ s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags,
 	flags &= I2C_M_TEN | I2C_CLIENT_PEC;
 
 	if (adapter->algo->smbus_xfer) {
-		rt_mutex_lock(&adapter->bus_lock);
+		i2c_mux_tree_lock(adapter);
 
 		/* Retry automatically on arbitration loss */
 		orig_jiffies = jiffies;
@@ -1871,7 +1950,7 @@ s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags,
 				       orig_jiffies + adapter->timeout))
 				break;
 		}
-		rt_mutex_unlock(&adapter->bus_lock);
+		i2c_mux_tree_unlock(adapter);
 	} else
 		res = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,
 					      command, protocol, data);
diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c
index f4110aa..da5327f 100755
--- a/drivers/i2c/i2c-dev.c
+++ b/drivers/i2c/i2c-dev.c
@@ -193,12 +193,56 @@ static int i2cdev_check(struct device *dev, void *addrp)
 	return dev->driver ? -EBUSY : 0;
 }
 
+static int i2cdev_parent_is_i2c_adapter(const struct i2c_adapter *adapter)
+{
+	return adapter->dev.parent != NULL
+		&& adapter->dev.parent->bus == &i2c_bus_type;
+}
+
+/* walk up mux tree */
+static int i2cdev_check_mux_parents(struct i2c_adapter *adapter, int addr)
+{
+	int result;
+
+	result = device_for_each_child(&adapter->dev, &addr, i2cdev_check);
+
+	if (!result && i2cdev_parent_is_i2c_adapter(adapter))
+		result = i2cdev_check_mux_parents(
+				    to_i2c_adapter(adapter->dev.parent), addr);
+
+	return result;
+}
+
+/* recurse down mux tree */
+static int i2cdev_check_mux_childs(struct i2c_adapter *adapter, int addr)
+{
+	int result = 0;
+	struct i2c_adapter *child, *next;
+
+	list_for_each_entry_safe(child, next, &adapter->mux_list_head,
+				 mux_list) {
+		result = device_for_each_child(&child->dev, &addr,
+					       i2cdev_check);
+		if (result)
+			break;
+		result = i2cdev_check_mux_childs(child, addr);
+	}
+	return result;
+}
+
 /* This address checking function differs from the one in i2c-core
    in that it considers an address with a registered device, but no
    driver bound to it, as NOT busy. */
 static int i2cdev_check_addr(struct i2c_adapter *adapter, unsigned int addr)
 {
-	return device_for_each_child(&adapter->dev, &addr, i2cdev_check);
+	int result;
+
+	result = i2cdev_check_mux_parents(adapter, addr);
+
+	if (!result)
+		result = i2cdev_check_mux_childs(adapter, addr);
+
+	return result;
 }
 
 static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client,
diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c
new file mode 100755
index 0000000..f88d29a
--- /dev/null
+++ b/drivers/i2c/i2c-mux.c
@@ -0,0 +1,184 @@
+/*
+ * Multiplexed I2C bus driver.
+ *
+ * Copyright (c) 2008-2009 Rodolfo Giometti <giometti@xxxxxxxx>
+ * Copyright (c) 2008-2009 Eurotech S.p.A. <info@xxxxxxxxxxx>
+ *
+ * Simplifies access to complex multiplexed I2C bus topologies, by presenting
+ * each multiplexed bus segment as an additional I2C adapter.
+ * Supports multi-level mux'ing (mux behind a mux).
+ *
+ * Based on:
+ *	i2c-virt.c from Kumar Gala <galak@xxxxxxxxxxxxxxxxxxx>
+ *	i2c-virtual.c from Ken Harrenstien, Copyright (c) 2004 Google, Inc.
+ *	i2c-virtual.c from Brian Kuschak <bkuschak@xxxxxxxxx>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+/* Adapted to kernel 2.6.33 by Michael Lawnick <michael.lawnick.ext@xxxxxxx> */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/i2c-mux.h>
+
+/* multiplexer per channel data */
+struct i2c_mux_priv {
+	struct i2c_adapter adap;
+	struct i2c_algorithm algo;
+
+	struct i2c_adapter *parent;
+	void *mux_dev;	/* the mux chip/device */
+	u32  chan_id;	/* the channel id */
+
+	int (*select)(struct i2c_adapter *adap, void *mux_dev, u32 chan_id);
+	int (*deselect)(struct i2c_adapter *adap, void *mux_dev, u32 chan_id);
+};
+
+#define VIRT_TIMEOUT		(HZ/2)
+#define VIRT_RETRIES		3
+
+static int i2c_mux_master_xfer(struct i2c_adapter *adap,
+			       struct i2c_msg msgs[], int num)
+{
+	struct i2c_mux_priv *priv = adap->algo_data;
+	struct i2c_adapter *parent = priv->parent;
+	int ret;
+
+	/* Switch to the right mux port and perform the transfer.*/
+
+	ret = priv->select(parent, priv->mux_dev, priv->chan_id);
+	if (ret >= 0)
+		ret = parent->algo->master_xfer(parent, msgs, num);
+	if (priv->deselect)
+		priv->deselect(parent, priv->mux_dev, priv->chan_id);
+
+	return ret;
+}
+
+static int i2c_mux_smbus_xfer(struct i2c_adapter *adap,
+			      u16 addr, unsigned short flags,
+			      char read_write, u8 command,
+			      int size, union i2c_smbus_data *data)
+{
+	struct i2c_mux_priv *priv = adap->algo_data;
+	struct i2c_adapter *parent = priv->parent;
+	int ret;
+
+	/* Select the right mux port and perform the transfer.*/
+
+	ret = priv->select(parent, priv->mux_dev, priv->chan_id);
+	if (ret >= 0)
+		ret = parent->algo->smbus_xfer(parent, addr, flags,
+					   read_write, command, size, data);
+	if (priv->deselect)
+		priv->deselect(parent, priv->mux_dev, priv->chan_id);
+
+	return ret;
+}
+
+/* Return the parent's functionality for the virtual adapter */
+static u32 i2c_mux_functionality(struct i2c_adapter *adap)
+{
+	struct i2c_mux_priv *priv = adap->algo_data;
+	struct i2c_adapter *parent = priv->parent;
+
+	return parent->algo->functionality(parent);
+}
+
+struct i2c_adapter *i2c_add_mux_adapter(struct i2c_adapter *parent,
+				void *mux_dev, u32 force_nr, u32 chan_id,
+				int (*select) (struct i2c_adapter *,
+					       void *, u32),
+				int (*deselect) (struct i2c_adapter *,
+						 void *, u32))
+{
+	struct i2c_mux_priv *priv;
+	int ret;
+
+	priv = kzalloc(sizeof(struct i2c_mux_priv), GFP_KERNEL);
+	if (!priv)
+		return NULL;
+
+	/* Set up private adapter data */
+	priv->parent = parent;
+	priv->mux_dev = mux_dev;
+	priv->chan_id = chan_id;
+	priv->select = select;
+	priv->deselect = deselect;
+
+	/* Need to do algo dynamically because we don't know ahead
+	 * of time what sort of physical adapter we'll be dealing with.
+	 */
+	if (parent->algo->master_xfer)
+		priv->algo.master_xfer = i2c_mux_master_xfer;
+	if (parent->algo->smbus_xfer)
+		priv->algo.smbus_xfer = i2c_mux_smbus_xfer;
+	priv->algo.functionality = i2c_mux_functionality;
+
+	/* Now fill out new adapter structure */
+	snprintf(priv->adap.name, sizeof(priv->adap.name),
+		 "i2c-%d-mux (chan_id %d)", i2c_adapter_id(parent), chan_id);
+	priv->adap.owner = THIS_MODULE;
+	priv->adap.id = parent->id;
+	priv->adap.algo = &priv->algo;
+	priv->adap.algo_data = priv;
+	priv->adap.dev.parent = &parent->dev;
+
+	if (force_nr) {
+		priv->adap.nr = force_nr;
+		ret = i2c_add_numbered_adapter(&priv->adap);
+	} else {
+		ret = i2c_add_adapter(&priv->adap);
+	}
+	if (ret < 0) {
+		dev_err(&parent->dev, "failed to add mux-adapter (error=%d)\n",
+			ret);
+		kfree(priv);
+		return NULL;
+	}
+
+	list_add_tail(&priv->adap.mux_list, &parent->mux_list_head);
+
+	dev_info(&parent->dev, "i2c-mux-%d: Multiplexed I2C bus on "
+		"parent bus i2c-%d, chan_id %d\n",
+		i2c_adapter_id(&priv->adap), i2c_adapter_id(parent), chan_id);
+
+	return &priv->adap;
+}
+EXPORT_SYMBOL_GPL(i2c_add_mux_adapter);
+
+int i2c_del_mux_adapter(struct i2c_adapter *adap)
+{
+	struct i2c_mux_priv *priv = adap->algo_data;
+	int ret;
+	struct i2c_adapter *child, *next, *parent;
+
+	if (adap->dev.parent != NULL
+		&& adap->dev.parent->bus == &i2c_bus_type) {
+		parent = to_i2c_adapter(adap->dev.parent);
+		list_for_each_entry_safe(child, next, &parent->mux_list_head,
+					 mux_list) {
+			if (child == adap) {
+				list_del(&child->mux_list);
+				break;
+			}
+		}
+	}
+
+	ret = i2c_del_adapter(adap);
+	if (ret < 0)
+		return ret;
+	kfree(priv);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(i2c_del_mux_adapter);
+
+MODULE_AUTHOR("Rodolfo Giometti <giometti@xxxxxxxx>");
+MODULE_DESCRIPTION("I2C driver for multiplexed I2C busses");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/i2c-mux.h b/include/linux/i2c-mux.h
new file mode 100755
index 0000000..0d5d6c8
--- /dev/null
+++ b/include/linux/i2c-mux.h
@@ -0,0 +1,46 @@
+/*
+ *
+ * i2c-mux.h - functions for the i2c-bus mux support
+ *
+ * Copyright (c) 2008-2009 Rodolfo Giometti <giometti@xxxxxxxx>
+ * Copyright (c) 2008-2009 Eurotech S.p.A. <info@xxxxxxxxxxx>
+ * Michael Lawnick <michael.lawnick.ext@xxxxxxx>
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _LINUX_I2C_MUX_H
+#define _LINUX_I2C_MUX_H
+
+#ifdef __KERNEL__
+
+/*
+ * Called to create a i2c bus on a multiplexed bus segment.
+ * The mux_dev and chan_id parameters are passed to the select
+ * and deselect callback functions to perform hardware-specific
+ * mux control.
+ */
+struct i2c_adapter *i2c_add_mux_adapter(struct i2c_adapter *parent,
+				void *mux_dev, u32 force_nr, u32 chan_id,
+				int (*select) (struct i2c_adapter *adap,
+					       void *mux_dev, u32 chan_id),
+				int (*deselect) (struct i2c_adapter *,
+						 void *mux_dev, u32 chan_id));
+
+int i2c_del_mux_adapter(struct i2c_adapter *adap);
+
+#endif /* __KERNEL__ */
+
+#endif /* _LINUX_I2C_H */
diff --git a/include/linux/i2c.h b/include/linux/i2c.h
index 1fd7b12..898a8c1 100755
--- a/include/linux/i2c.h
+++ b/include/linux/i2c.h
@@ -349,6 +349,8 @@ struct i2c_adapter {
 	struct completion dev_released;
 
 	struct list_head userspace_clients;
+	struct list_head mux_list_head;
+	struct list_head mux_list;
 };
 #define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev)
 
@@ -380,6 +382,15 @@ static inline void i2c_unlock_adapter(struct i2c_adapter *adapter)
 	rt_mutex_unlock(&adapter->bus_lock);
 }
 
+/**
+ * i2c_trylock_adapter - Try to prevent access to an I2C bus segment
+ * @adapter: Target I2C bus segment
+ */
+static inline int i2c_trylock_adapter(struct i2c_adapter *adapter)
+{
+	return rt_mutex_trylock(&adapter->bus_lock);
+}
+
 /*flags for the client struct: */
 #define I2C_CLIENT_PEC	0x04		/* Use Packet Error Checking */
 #define I2C_CLIENT_TEN	0x10		/* we have a ten bit chip address */
-- 
1.6.2.1


[Index of Archives]     [Linux GPIO]     [Linux SPI]     [Linux Hardward Monitoring]     [LM Sensors]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux