[PATCH][UPDATE] i2c: Add support for virtual I2C adapters
From: Kumar Gala
Date: Thu Mar 30 2006 - 18:08:06 EST
Virtual adapters are useful to handle multiplexed I2C bus topologies, by
presenting each multiplexed segment as a I2C adapter. Typically, either
a mux (or switch) exists which is an I2C device on the parent bus. One
selects a given child bus via programming the mux and then all the devices
on that bus become present on the parent bus. The intent is to allow
multiple devices of the same type to exist in a system which would normally
have address conflicts.
Since virtual adapters will get registered in an I2C client's detect
function we have to expose versions of i2c_{add,del}_adapter for
i2c_{add,del}_virt_adapter to call that don't lock.
Additionally, i2c_virt_master_xfer (and i2c_virt_smbus_xfer) acquire
the parent->bus_lock and call the parent's master_xfer directly. This
is because on a i2c_virt_master_xfer we have issue an i2c write on
the parent bus to select the given virtual adapter, then do the i2c
operation on the parent bus, followed by another i2c write on the
parent to deslect the virtual adapter.
Signed-off-by: Kumar Gala <galak@xxxxxxxxxxxxxxxxxxx>
---
commit 862cbc263e3d3e44028d7465a912847cf5366163
tree 2c91bad8eb66cab9727f3071831a916ada41edf8
parent 5d4fe2c1ce83c3e967ccc1ba3d580c1a5603a866
author Kumar Gala <galak@xxxxxxxxxxxxxxxxxxx> Thu, 30 Mar 2006 17:03:42 -0600
committer Kumar Gala <galak@xxxxxxxxxxxxxxxxxxx> Thu, 30 Mar 2006 17:03:42 -0600
drivers/i2c/Kconfig | 9 ++
drivers/i2c/Makefile | 1
drivers/i2c/i2c-core.c | 42 ++++++++----
drivers/i2c/i2c-virt.c | 173 ++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/i2c-id.h | 2 +
include/linux/i2c.h | 20 ++++++
6 files changed, 234 insertions(+), 13 deletions(-)
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
index 24383af..b8a8fc1 100644
--- a/drivers/i2c/Kconfig
+++ b/drivers/i2c/Kconfig
@@ -34,6 +34,15 @@ config I2C_CHARDEV
This support is also available as a module. If so, the module
will be called i2c-dev.
+config I2C_VIRT
+ tristate "I2C virtual adapter support"
+ depends on I2C
+ help
+ Say Y here if you want the I2C core to support the ability to have
+ virtual adapters. Virtual adapters are useful to handle multiplexed
+ I2C bus topologies, by presenting each multiplexed segment as a
+ I2C adapter.
+
source drivers/i2c/algos/Kconfig
source drivers/i2c/busses/Kconfig
source drivers/i2c/chips/Kconfig
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
index 71c5a85..4467db2 100644
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -3,6 +3,7 @@
#
obj-$(CONFIG_I2C) += i2c-core.o
+obj-$(CONFIG_I2C_VIRT) += i2c-virt.o
obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o
obj-y += busses/ chips/ algos/
diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c
index 45e2cdf..64c1c9e 100644
--- a/drivers/i2c/i2c-core.c
+++ b/drivers/i2c/i2c-core.c
@@ -150,22 +150,31 @@ static struct device_attribute dev_attr_
*/
int i2c_add_adapter(struct i2c_adapter *adap)
{
+ int res;
+
+ mutex_lock(&core_lists);
+ res = i2c_add_adapter_nolock(adap);
+ mutex_unlock(&core_lists);
+
+ return res;
+}
+
+int i2c_add_adapter_nolock(struct i2c_adapter *adap)
+{
int id, res = 0;
struct list_head *item;
struct i2c_driver *driver;
- mutex_lock(&core_lists);
-
if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0) {
res = -ENOMEM;
- goto out_unlock;
+ goto out;
}
res = idr_get_new(&i2c_adapter_idr, adap, &id);
if (res < 0) {
if (res == -EAGAIN)
res = -ENOMEM;
- goto out_unlock;
+ goto out;
}
adap->nr = id & MAX_ID_MASK;
@@ -203,21 +212,29 @@ int i2c_add_adapter(struct i2c_adapter *
driver->attach_adapter(adap);
}
-out_unlock:
- mutex_unlock(&core_lists);
+out:
return res;
}
-
int i2c_del_adapter(struct i2c_adapter *adap)
{
+ int res;
+
+ mutex_lock(&core_lists);
+ res = i2c_del_adapter_nolock(adap);
+ mutex_unlock(&core_lists);
+
+ return res;
+}
+
+int i2c_del_adapter_nolock(struct i2c_adapter *adap)
+{
struct list_head *item, *_n;
struct i2c_adapter *adap_from_list;
struct i2c_driver *driver;
struct i2c_client *client;
int res = 0;
- mutex_lock(&core_lists);
/* First make sure that this adapter was ever added */
list_for_each_entry(adap_from_list, &adapters, list) {
@@ -228,7 +245,7 @@ int i2c_del_adapter(struct i2c_adapter *
pr_debug("i2c-core: attempting to delete unregistered "
"adapter [%s]\n", adap->name);
res = -EINVAL;
- goto out_unlock;
+ goto out;
}
list_for_each(item,&drivers) {
@@ -238,7 +255,7 @@ int i2c_del_adapter(struct i2c_adapter *
dev_err(&adap->dev, "detach_adapter failed "
"for driver [%s]\n",
driver->driver.name);
- goto out_unlock;
+ goto out;
}
}
@@ -251,7 +268,7 @@ int i2c_del_adapter(struct i2c_adapter *
dev_err(&adap->dev, "detach_client failed for client "
"[%s] at address 0x%02x\n", client->name,
client->addr);
- goto out_unlock;
+ goto out;
}
}
@@ -272,8 +289,7 @@ int i2c_del_adapter(struct i2c_adapter *
dev_dbg(&adap->dev, "adapter [%s] unregistered\n", adap->name);
- out_unlock:
- mutex_unlock(&core_lists);
+out:
return res;
}
diff --git a/drivers/i2c/i2c-virt.c b/drivers/i2c/i2c-virt.c
new file mode 100644
index 0000000..2bd9ea3
--- /dev/null
+++ b/drivers/i2c/i2c-virt.c
@@ -0,0 +1,173 @@
+/*
+ * i2c-virtual.c - Virtual I2C bus driver.
+ *
+ * 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-virtual.c from Copyright (c) 2004 Google, Inc. (Ken Harrenstien)
+ * i2c-virtual.c from Brian Kuschak <bkuschak@xxxxxxxxx>
+ * 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_virt_priv {
+ struct i2c_adapter *parent_adap;
+ struct i2c_client *client; /* The mux chip/device */
+
+ u32 id; /* the mux id */
+
+ /* fn which enables the mux */
+ int (*select) (struct i2c_adapter *, struct i2c_client *, u32);
+
+ /* fn which disables the mux */
+ int (*deselect) (struct i2c_adapter *, struct i2c_client *, u32);
+};
+
+#define VIRT_TIMEOUT (HZ/2)
+#define VIRT_RETRIES 3
+
+static int
+i2c_virt_master_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
+{
+ struct i2c_virt_priv *priv = adap->algo_data;
+ struct i2c_adapter *parent = priv->parent_adap;
+ int ret;
+
+ /* Grab the lock for the parent adapter. We already hold the lock for
+ the virtual adapter. Then select the right mux port and perform
+ the transfer.
+ */
+
+ mutex_lock(&parent->bus_lock);
+ if ((ret = priv->select(parent, priv->client, priv->id)) >= 0) {
+ ret = parent->algo->master_xfer(parent, msgs, num);
+ }
+ priv->deselect(parent, priv->client, priv->id);
+ mutex_unlock(&parent->bus_lock);
+
+ return ret;
+}
+
+static int
+i2c_virt_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_virt_priv *priv = adap->algo_data;
+ struct i2c_adapter *parent = priv->parent_adap;
+ int ret;
+
+ /* Grab the lock for the parent adapter. We already hold the lock for
+ the virtual adapter. Then select the right mux port and perform
+ the transfer.
+ */
+
+ mutex_lock(&parent->bus_lock);
+ if ((ret = priv->select(parent, priv->client, priv->id)) == 0) {
+ ret = parent->algo->smbus_xfer(parent, addr, flags,
+ read_write, command, size, data);
+ }
+ priv->deselect(parent, priv->client, priv->id);
+ mutex_unlock(&parent->bus_lock);
+
+ return ret;
+}
+
+/* return the parent's functionality for the virtual adapter */
+static u32 i2c_virt_functionality(struct i2c_adapter *adap)
+{
+ struct i2c_virt_priv *priv = adap->algo_data;
+ struct i2c_adapter *parent = priv->parent_adap;
+
+ return parent->algo->functionality(parent);
+}
+
+struct i2c_adapter *
+i2c_add_virt_adapter(struct i2c_adapter *parent, struct i2c_client *client,
+ u32 mux_val,
+ int (*select_cb) (struct i2c_adapter *,
+ struct i2c_client *, u32),
+ int (*deselect_cb) (struct i2c_adapter *,
+ struct i2c_client *, u32))
+{
+ struct i2c_adapter *adap;
+ struct i2c_virt_priv *priv;
+ struct i2c_algorithm *algo;
+
+ if (!(adap = kzalloc(sizeof(struct i2c_adapter)
+ + sizeof(struct i2c_virt_priv)
+ + sizeof(struct i2c_algorithm), GFP_KERNEL)))
+ return NULL;
+
+ priv = (struct i2c_virt_priv *)(adap + 1);
+ algo = (struct i2c_algorithm *)(priv + 1);
+
+ /* 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.
+ */
+ algo->master_xfer = (parent->algo->master_xfer
+ ? i2c_virt_master_xfer : NULL);
+ algo->smbus_xfer = (parent->algo->smbus_xfer
+ ? i2c_virt_smbus_xfer : NULL);
+ algo->functionality = i2c_virt_functionality;
+
+ /* Now fill out new adapter structure */
+ snprintf(adap->name, sizeof(adap->name),
+ "Virtual I2C (i2c-%d, mux %02x:%02x)",
+ i2c_adapter_id(parent), client->addr, mux_val);
+ adap->id = I2C_HW_VIRT | i2c_adapter_id(parent);
+ adap->algo = algo;
+ adap->algo_data = priv;
+ adap->timeout = VIRT_TIMEOUT;
+ adap->retries = VIRT_RETRIES;
+ adap->dev.parent = &parent->dev;
+
+ if (i2c_add_adapter_nolock(adap) < 0) {
+ kfree(adap);
+ return NULL;
+ }
+
+ printk(KERN_NOTICE "i2c-%d: Virtual I2C bus "
+ "(Physical bus i2c-%d, multiplexer 0x%02x port %d)\n",
+ i2c_adapter_id(adap), i2c_adapter_id(parent),
+ client->addr, mux_val);
+
+ return adap;
+}
+
+int i2c_del_virt_adapter(struct i2c_adapter *adap)
+{
+ int ret;
+
+ if ((ret = i2c_del_adapter_nolock(adap)) < 0)
+ return ret;
+ kfree(adap);
+
+ return 0;
+}
+
+EXPORT_SYMBOL_GPL(i2c_add_virt_adapter);
+EXPORT_SYMBOL_GPL(i2c_del_virt_adapter);
+
+MODULE_AUTHOR("Kumar Gala <galak@xxxxxxxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("Virtual I2C driver for multiplexed I2C busses");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h
index c8b81f4..66d5533 100644
--- a/include/linux/i2c-id.h
+++ b/include/linux/i2c-id.h
@@ -265,4 +265,6 @@
#define I2C_HW_SAA7146 0x060000 /* SAA7146 video decoder bus */
#define I2C_HW_SAA7134 0x090000 /* SAA7134 video decoder bus */
+#define I2C_HW_VIRT 0x80000000 /* a virtual adapter */
+
#endif /* LINUX_I2C_ID_H */
diff --git a/include/linux/i2c.h b/include/linux/i2c.h
index 1635ee2..ba41f97 100644
--- a/include/linux/i2c.h
+++ b/include/linux/i2c.h
@@ -294,6 +294,10 @@ struct i2c_client_address_data {
extern int i2c_add_adapter(struct i2c_adapter *);
extern int i2c_del_adapter(struct i2c_adapter *);
+/* Assume the caller has the core_list lock already */
+extern int i2c_add_adapter_nolock(struct i2c_adapter *);
+extern int i2c_del_adapter_nolock(struct i2c_adapter *);
+
extern int i2c_register_driver(struct module *, struct i2c_driver *);
extern int i2c_del_driver(struct i2c_driver *);
@@ -440,6 +444,22 @@ union i2c_smbus_data {
#define I2C_SMBUS_I2C_BLOCK_DATA 6
#define I2C_SMBUS_BLOCK_PROC_CALL 7 /* SMBus 2.0 */
+/*
+ * 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.
+ *
+ * The caller is expected to have the core_lists lock
+ */
+struct i2c_adapter *
+i2c_add_virt_adapter(struct i2c_adapter *parent, struct i2c_client *client,
+ u32 mux_val,
+ int (*select_cb) (struct i2c_adapter *,
+ struct i2c_client *, u32),
+ int (*deselect_cb) (struct i2c_adapter *,
+ struct i2c_client *, u32));
+
+int i2c_del_virt_adapter(struct i2c_adapter *adap);
/* ----- commands for the ioctl like i2c_command call:
* note that additional calls are defined in the algorithm and hw
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/