Subject: [PATCH 1/2] i2c: Multiplexed I2C bus core support.
Signed-off-by: Rodolfo Giometti <[email protected]>
Signed-off-by: Michael Lawnick <[email protected]>
---
drivers/i2c/Kconfig | 10 +++
drivers/i2c/Makefile | 3 +-
drivers/i2c/i2c-core.c | 73 +++++++++++++++++--
drivers/i2c/i2c-dev.c | 32 ++++++++-
drivers/i2c/i2c-mux.c | 167
++++++++++++++++++++++++++++++++++++++++++++
drivers/i2c/muxes/Kconfig | 8 ++
drivers/i2c/muxes/Makefile | 6 ++
include/linux/i2c.h | 14 ++++
8 files changed, 303 insertions(+), 10 deletions(-)
create mode 100755 drivers/i2c/i2c-mux.c
create mode 100755 drivers/i2c/muxes/Kconfig
create mode 100755 drivers/i2c/muxes/Makefile
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
index d7ece13..0c2f86a 100755
--- a/drivers/i2c/Kconfig
+++ b/drivers/i2c/Kconfig
@@ -35,6 +35,16 @@ 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.
+
+source drivers/i2c/muxes/Kconfig
+
config I2C_CHARDEV
tristate "I2C device interface"
help
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
index ba26e6c..281e2a7 100755
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -4,8 +4,9 @@
obj-$(CONFIG_I2C_BOARDINFO) += i2c-boardinfo.o
obj-$(CONFIG_I2C) += i2c-core.o
+obj-$(CONFIG_I2C_MUX) += i2c-mux.o
obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o
-obj-y += busses/ chips/ algos/
+obj-y += busses/ chips/ muxes/ algos/
ifeq ($(CONFIG_I2C_DEBUG_CORE),y)
EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c
index 2965043..83119ae 100644
--- a/drivers/i2c/i2c-core.c
+++ b/drivers/i2c/i2c-core.c
@@ -20,7 +20,9 @@
/* With some changes from Kyösti Mälkki <[email protected]>.
All SMBus-related things are written by Frodo Looijaard <[email protected]>
SMBus 2.0 support by Mark Studebaker <[email protected]> and
- Jean Delvare <[email protected]> */
+ Jean Delvare <[email protected]>
+ Mux support by Rodolfo Giometti <[email protected]> and
+ Michael Lawnick <[email protected]>*/
#include <linux/module.h>
#include <linux/kernel.h>
@@ -937,9 +939,66 @@ static int __i2c_check_addr(struct device *dev,
void *addrp)
return 0;
}
+static int i2c_check_clients(struct i2c_adapter *adapter, int addr)
+{
+ int result = 0;
+
+ result = device_for_each_child(&adapter->dev, &addr, __i2c_check_addr);
+
+ if (!result && (adapter->dev.parent->bus == &i2c_bus_type))
+ result = i2c_check_clients(to_i2c_adapter(adapter->dev.parent),
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 = 0;
+
+ result = i2c_check_clients(adapter, addr);
+
+ if (!result && (adapter->dev.parent->bus == &i2c_bus_type))
+ {
+ struct i2c_client dummy;
+ char buff;
+
+ dummy.adapter = to_i2c_adapter(adapter->dev.parent);
+ dummy.addr = addr;
+ dummy.flags = 0;
+
+ result = i2c_master_recv(&dummy, &buff, 1) == 1;
+ }
+
+ return result;
+}
+
+static void i2c_mux_tree_lock(struct i2c_adapter *adap)
+{
+ mutex_lock_nested(&adap->bus_lock, SINGLE_DEPTH_NESTING);
+ if(adap->dev.parent->bus == &i2c_bus_type)
+ i2c_mux_tree_lock(to_i2c_adapter(adap->dev.parent));
+}
+
+static int i2c_mux_tree_trylock(struct i2c_adapter *adap)
+{
+ int ret;
+
+ ret = mutex_trylock(&adap->bus_lock);
+
+ if(ret && (adap->dev.parent->bus == &i2c_bus_type)) {
+ ret = i2c_mux_tree_trylock(to_i2c_adapter(adap->dev.parent));
+ if (!ret)
+ mutex_unlock(&adap->bus_lock);
+ }
+
+ return ret;
+}
+
+static void i2c_mux_tree_unlock(struct i2c_adapter *adap)
+{
+ if(adap->dev.parent->bus == &i2c_bus_type)
+ i2c_mux_tree_unlock(to_i2c_adapter(adap->dev.parent));
+ mutex_unlock(&adap->bus_lock);
}
/**
@@ -1092,12 +1151,12 @@ int i2c_transfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
#endif
if (in_atomic() || irqs_disabled()) {
- ret = mutex_trylock(&adap->bus_lock);
+ ret = i2c_mux_tree_trylock(adap);
if (!ret)
/* I2C activity is ongoing. */
return -EAGAIN;
} else {
- mutex_lock_nested(&adap->bus_lock, adap->level);
+ i2c_mux_tree_lock(adap);
}
/* Retry automatically on arbitration loss */
@@ -1109,7 +1168,7 @@ int i2c_transfer(struct i2c_adapter *adap, struct
i2c_msg *msgs, int num)
if (time_after(jiffies, orig_jiffies + adap->timeout))
break;
}
- mutex_unlock(&adap->bus_lock);
+ i2c_mux_tree_unlock(adap);
return ret;
} else {
@@ -1913,7 +1972,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) {
- mutex_lock(&adapter->bus_lock);
+ i2c_mux_tree_lock(adapter);
/* Retry automatically on arbitration loss */
orig_jiffies = jiffies;
@@ -1927,7 +1986,7 @@ s32 i2c_smbus_xfer(struct i2c_adapter *adapter,
u16 addr, unsigned short flags,
orig_jiffies + adapter->timeout))
break;
}
- 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 7e13d2d..ce2f1a1 100644
--- a/drivers/i2c/i2c-dev.c
+++ b/drivers/i2c/i2c-dev.c
@@ -190,16 +190,44 @@ static int i2cdev_check(struct device *dev, void
*addrp)
if (!client || client->addr != *(unsigned int *)addrp)
return 0;
-
+
return dev->driver ? -EBUSY : 0;
}
+static int i2cdev_check_clients(struct i2c_adapter *adapter, int addr)
+{
+ int result = 0;
+
+ result = device_for_each_child(&adapter->dev, &addr, i2cdev_check);
+
+ if (!result && (adapter->dev.parent->bus == &i2c_bus_type))
+ result =
i2cdev_check_clients(to_i2c_adapter(adapter->dev.parent), 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 = 0;
+
+ result = i2cdev_check_clients(adapter, addr);
+
+ if (!result && (adapter->dev.parent->bus == &i2c_bus_type))
+ {
+ struct i2c_client dummy;
+ char buff;
+
+ dummy.adapter = to_i2c_adapter(adapter->dev.parent);
+ dummy.addr = addr;
+ dummy.flags = 0;
+
+ result = i2c_master_recv(&dummy, &buff, 1) == 1;
+ }
+
+ 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..014d488
--- /dev/null
+++ b/drivers/i2c/i2c-mux.c
@@ -0,0 +1,167 @@
+/*
+ * Multiplexed I2C bus driver.
+ *
+ * Copyright (c) 2008-2009 Rodolfo Giometti <[email protected]>
+ * Copyright (c) 2008-2009 Eurotech S.p.A. <[email protected]>
+ *
+ * Simplifies access to complex multiplexed I2C bus topologies, by
presenting
+ * each multiplexed bus segment as a virtual I2C adapter. Supports
multi-level
+ * mux'ing (mux behind a mux).
+ *
+ * Based on:
+ * i2c-virt.c from Kumar Gala <[email protected]>
+ * i2c-virtual.c from Copyright (c) 2004 Google, Inc. (Ken Harrenstien)
+ * i2c-virtual.c from Brian Kuschak <[email protected]>
+ * which was:
+ * Adapted from i2c-adap-ibm_ocp.c
+ * Original file Copyright 2000-2002 MontaVista Software Inc.
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/i2c-id.h>
+
+struct i2c_mux_priv {
+ struct i2c_adapter adap;
+ struct i2c_algorithm algo;
+
+ struct i2c_adapter *parent_adap;
+ void *client; /* The mux chip/device */
+ u32 id; /* the mux id */
+
+ int (*select)(struct i2c_adapter *, void *, u32);
+ int (*deselect)(struct i2c_adapter *, void *, u32);
+};
+
+#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_adap;
+ int ret;
+
+ /* Select the right mux port and perform the transfer. */
+
+ ret = priv->select(parent, priv->client, priv->id);
+ if (ret >= 0)
+ ret = parent->algo->master_xfer(parent, msgs, num);
+ priv->deselect(parent, priv->client, priv->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_adap;
+ int ret;
+
+ /* Select the right mux port and perform the transfer. */
+
+ ret = priv->select(parent, priv->client, priv->id);
+ if (ret == 0)
+ ret = parent->algo->smbus_xfer(parent, addr, flags,
+ read_write, command, size, data);
+ priv->deselect(parent, priv->client, priv->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_adap;
+
+ return parent->algo->functionality(parent);
+}
+
+struct i2c_adapter *i2c_add_mux_adapter(struct i2c_adapter *parent,
+ void *client, u32 force_nr, u32 mux_val,
+ int (*select_cb) (struct i2c_adapter *,
+ void *, u32),
+ int (*deselect_cb) (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_adap = parent;
+ priv->client = client;
+ priv->id = mux_val;
+ priv->select = select_cb;
+ priv->deselect = deselect_cb;
+
+ /* 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 (mux %02x)",
+ i2c_adapter_id(parent), mux_val);
+ priv->adap.owner = THIS_MODULE;
+ priv->adap.id = i2c_adapter_id(parent);
+ priv->adap.algo = &priv->algo;
+ priv->adap.algo_data = priv;
+ priv->adap.timeout = VIRT_TIMEOUT;
+ priv->adap.retries = VIRT_RETRIES;
+ 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) {
+ kfree(priv);
+ return NULL;
+ }
+
+ dev_info(&parent->dev, "i2c-mux-%d: Virtual I2C bus - "
+ "physical bus i2c-%d, client %02x, port %d\n",
+ i2c_adapter_id(&priv->adap), i2c_adapter_id(parent), ((struct
i2c_client*)priv->client)->addr, mux_val);
+
+ 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;
+
+ 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 <[email protected]>");
+MODULE_DESCRIPTION("Virtual I2C driver for multiplexed I2C busses");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig
new file mode 100755
index 0000000..95b9bde
--- /dev/null
+++ b/drivers/i2c/muxes/Kconfig
@@ -0,0 +1,8 @@
+#
+# Multiplexer I2C chip drivers configuration
+#
+
+menu "Multiplexer I2C Chip support"
+ depends on I2C && I2C_MUX && EXPERIMENTAL
+
+endmenu
diff --git a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile
new file mode 100755
index 0000000..0416a52
--- /dev/null
+++ b/drivers/i2c/muxes/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for multiplexer I2C chip drivers.
+
+ifeq ($(CONFIG_I2C_DEBUG_CHIP),y)
+EXTRA_CFLAGS += -DDEBUG -DI2C_DEBUG_CORE
+endif
diff --git a/include/linux/i2c.h b/include/linux/i2c.h
index 7b40cda..24ff2e5 100755
--- a/include/linux/i2c.h
+++ b/include/linux/i2c.h
@@ -580,6 +580,20 @@ union i2c_smbus_data {
#define I2C_SMBUS_BLOCK_PROC_CALL 7 /* SMBus 2.0 */
#define I2C_SMBUS_I2C_BLOCK_DATA 8
+/*
+ * Called to create a 'virtual' i2c bus which represents a multiplexed bus
+ * segment. The client and mux_val 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 *client, u32 force_nr, u32 mux_val,
+ int (*select_cb) (struct i2c_adapter *,
+ void *, u32),
+ int (*deselect_cb) (struct i2c_adapter *,
+ void *, u32));
+
+int i2c_del_mux_adapter(struct i2c_adapter *adap);
+
#ifdef __KERNEL__
--
1.6.1.2
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html