i2c: Multiplexed I2C bus core support
Subsystems (3)
PATCH v2 1/2 i2c: Multiplexed I2C bus core support
Add multiplexed bus core support. I2C multiplexer and switches like pca954x instantiate new adapters per output.
---
Based on kernel 2.6.33 +
[PATCH] i2c-core: Use per-adapter userspace device lists
by Jean Delware <[email protected]>
<http://article.gmane.org/gmane.linux.drivers.i2c/5994/>
From 1d17617d8845f2268d4123ec0cf5310743303d45 Mon Sep 17 00:00:00 2001
From: Michael Lawnick <demx1175@demmc3qc.(none)>
Date: Tue, 4 May 2010 13:34:52 +0200
Subject: [PATCH] mux_core_support
--- 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
drivers/i2c/Kconfig +8 -0
@@ -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
drivers/i2c/Makefile +1 -0
@@ -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)
drivers/i2c/i2c-core.c +4 -1
@@ -20,7 +20,10 @@/* 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 <mdsxyz123-/[email protected]> and -Jean Delvare <[email protected]> */ +Jean Delvare <[email protected]> +Mux support by Rodolfo Giometti <giometti-AVVDYK/kqiJWk0Htik3J/[email protected]> and +Michael Lawnick <[email protected]> */ +#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);
drivers/i2c/i2c-dev.c +0 -0
@@ -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,
drivers/i2c/i2c-mux.c new file +7 -0
@@ -0,0 +1,184 @@ +/* +* 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 an additional 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 Ken Harrenstien, Copyright (c) 2004 Google, Inc.
+ * i2c-virtual.c from Brian Kuschak <bkuschak-/[email protected]>
+ *
+ * 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 <[email protected]> */
+
+#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 <[email protected]>"); +MODULE_DESCRIPTION("I2C driver for multiplexed I2C busses"); +MODULE_LICENSE("GPL v2");
include/linux/i2c-mux.h new file +46 -0
46 lines not on this page · raw message
include/linux/i2c.h +2 -0
@@ -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 */
A message that is not in the archive (sent off-list or not yet received): [email protected]
Rodolfo Giometti
Acked-byQuoted 4 lines · Michael Lawnick
Add multiplexed bus core support. I2C multiplexer and switches like pca954x instantiate new adapters per output.
Signed-off-by Michael Lawnick <[email protected]>
Signature · 5 lines
GNU/Linux Solutions e-mail: giometti-AVVDYK/kqiJWk0Htik3J/[email protected] Linux Device Driver [email protected] Embedded Systems phone: +39 349 2432127 UNIX programming skype: rodolfo.giometti Freelance ICT Italia - Consulente ICT Italia - www.consulenti-ict.it
Seems like Thunderbird isn't usable for sending patches :-( Trying again with attachment.
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 2 lines · Michael Lawnick
Seems like Thunderbird isn't usable for sending patches :-( Trying again with attachment.
Have you tried External Editor with VIM:
http://globs.org/articles.php?pg=2&lng=en
For me it is working like a charm.
Regards,
Yegor