lkml.org 
[lkml]   [2006]   [Apr]   [7]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
From
SubjectRe: [PATCH][UPDATE] i2c: Add support for virtual I2C adapters
Date
Any comments or acceptance of this patch?

- k

On Mar 30, 2006, at 5:05 PM, Kumar Gala wrote:

> 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@kernel.crashing.org>
>
> ---
> commit 862cbc263e3d3e44028d7465a912847cf5366163
> tree 2c91bad8eb66cab9727f3071831a916ada41edf8
> parent 5d4fe2c1ce83c3e967ccc1ba3d580c1a5603a866
> author Kumar Gala <galak@kernel.crashing.org> Thu, 30 Mar 2006
> 17:03:42 -0600
> committer Kumar Gala <galak@kernel.crashing.org> 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@yahoo.com>
> + * 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@kernel.crashing.org>");
> +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@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

\
 
 \ /
  Last update: 2006-04-07 20:57    [W:0.077 / U:0.380 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site