Skip to content

Commit

Permalink
i2c: Multiplexed I2C bus core support
Browse files Browse the repository at this point in the history
Add multiplexed bus core support. I2C multiplexer and switches
like pca954x get instantiated as new adapters per port.

Signed-off-by: Michael Lawnick <ml.lawnick@gmx.de>
Acked-by: Rodolfo Giometti <giometti@linux.it>
Signed-off-by: Jean Delvare <khali@linux-fr.org>
  • Loading branch information
Michael Lawnick authored and Jean Delvare committed Aug 11, 2010
1 parent dafc50d commit 0826374
Show file tree
Hide file tree
Showing 7 changed files with 327 additions and 8 deletions.
11 changes: 11 additions & 0 deletions drivers/i2c/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ config I2C_CHARDEV
This support is also available as a module. If so, the module
will be called i2c-dev.

config I2C_MUX
tristate "I2C bus multiplexing support"
depends on EXPERIMENTAL
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.

This support is also available as a module. If so, the module
will be called i2c-mux.

config I2C_HELPER_AUTO
bool "Autoselect pertinent helper modules"
default y
Expand Down
1 change: 1 addition & 0 deletions drivers/i2c/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ obj-$(CONFIG_I2C_BOARDINFO) += i2c-boardinfo.o
obj-$(CONFIG_I2C) += i2c-core.o
obj-$(CONFIG_I2C_SMBUS) += i2c-smbus.o
obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o
obj-$(CONFIG_I2C_MUX) += i2c-mux.o
obj-y += algos/ busses/

ifeq ($(CONFIG_I2C_DEBUG_CORE),y)
Expand Down
64 changes: 57 additions & 7 deletions drivers/i2c/i2c-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
/* With some changes from Kyösti Mälkki <kmalkki@cc.hut.fi>.
All SMBus-related things are written by Frodo Looijaard <frodol@dds.nl>
SMBus 2.0 support by Mark Studebaker <mdsxyz123@yahoo.com> and
Jean Delvare <khali@linux-fr.org> */
Jean Delvare <khali@linux-fr.org>
Mux support by Rodolfo Giometti <giometti@enneenne.com> and
Michael Lawnick <michael.lawnick.ext@nsn.com> */

#include <linux/module.h>
#include <linux/kernel.h>
Expand Down Expand Up @@ -423,10 +425,48 @@ static int __i2c_check_addr_busy(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_busy);

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_children(struct device *dev, void *addrp)
{
int result;

if (dev->type == &i2c_adapter_type)
result = device_for_each_child(dev, addrp,
i2c_check_mux_children);
else
result = __i2c_check_addr_busy(dev, addrp);

return result;
}

static int i2c_check_addr_busy(struct i2c_adapter *adapter, int addr)
{
return device_for_each_child(&adapter->dev, &addr,
__i2c_check_addr_busy);
int result = 0;

if (i2c_parent_is_i2c_adapter(adapter))
result = i2c_check_mux_parents(
to_i2c_adapter(adapter->dev.parent), addr);

if (!result)
result = device_for_each_child(&adapter->dev, &addr,
i2c_check_mux_children);

return result;
}

/**
Expand All @@ -435,7 +475,10 @@ static int i2c_check_addr_busy(struct i2c_adapter *adapter, int addr)
*/
void i2c_lock_adapter(struct i2c_adapter *adapter)
{
rt_mutex_lock(&adapter->bus_lock);
if (i2c_parent_is_i2c_adapter(adapter))
i2c_lock_adapter(to_i2c_adapter(adapter->dev.parent));
else
rt_mutex_lock(&adapter->bus_lock);
}
EXPORT_SYMBOL_GPL(i2c_lock_adapter);

Expand All @@ -445,7 +488,10 @@ EXPORT_SYMBOL_GPL(i2c_lock_adapter);
*/
static int i2c_trylock_adapter(struct i2c_adapter *adapter)
{
return rt_mutex_trylock(&adapter->bus_lock);
if (i2c_parent_is_i2c_adapter(adapter))
return i2c_trylock_adapter(to_i2c_adapter(adapter->dev.parent));
else
return rt_mutex_trylock(&adapter->bus_lock);
}

/**
Expand All @@ -454,7 +500,10 @@ static int i2c_trylock_adapter(struct i2c_adapter *adapter)
*/
void i2c_unlock_adapter(struct i2c_adapter *adapter)
{
rt_mutex_unlock(&adapter->bus_lock);
if (i2c_parent_is_i2c_adapter(adapter))
i2c_unlock_adapter(to_i2c_adapter(adapter->dev.parent));
else
rt_mutex_unlock(&adapter->bus_lock);
}
EXPORT_SYMBOL_GPL(i2c_unlock_adapter);

Expand Down Expand Up @@ -743,10 +792,11 @@ static const struct attribute_group *i2c_adapter_attr_groups[] = {
NULL
};

static struct device_type i2c_adapter_type = {
struct device_type i2c_adapter_type = {
.groups = i2c_adapter_attr_groups,
.release = i2c_adapter_dev_release,
};
EXPORT_SYMBOL_GPL(i2c_adapter_type);

#ifdef CONFIG_I2C_COMPAT
static struct class_compat *i2c_adapter_compat_class;
Expand Down
40 changes: 39 additions & 1 deletion drivers/i2c/i2c-dev.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,50 @@ static int i2cdev_check(struct device *dev, void *addrp)
return dev->driver ? -EBUSY : 0;
}

/* 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 && i2c_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_children(struct device *dev, void *addrp)
{
int result;

if (dev->type == &i2c_adapter_type)
result = device_for_each_child(dev, addrp,
i2cdev_check_mux_children);
else
result = i2cdev_check(dev, addrp);

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;

if (i2c_parent_is_i2c_adapter(adapter))
result = i2cdev_check_mux_parents(
to_i2c_adapter(adapter->dev.parent), addr);

if (!result)
result = device_for_each_child(&adapter->dev, &addr,
i2cdev_check_mux_children);

return result;
}

static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client,
Expand Down
165 changes: 165 additions & 0 deletions drivers/i2c/i2c-mux.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* Multiplexed I2C bus driver.
*
* Copyright (c) 2008-2009 Rodolfo Giometti <giometti@linux.it>
* Copyright (c) 2008-2009 Eurotech S.p.A. <info@eurotech.it>
* Copyright (c) 2009-2010 NSN GmbH & Co KG <michael.lawnick.ext@nsn.com>
*
* 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@kernel.crashing.org>
* i2c-virtual.c from Ken Harrenstien, Copyright (c) 2004 Google, Inc.
* i2c-virtual.c from Brian Kuschak <bkuschak@yahoo.com>
*
* 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/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 *, void *mux_dev, u32 chan_id);
int (*deselect)(struct i2c_adapter *, void *mux_dev, u32 chan_id);
};

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 */
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;
}

dev_info(&parent->dev, "Added multiplexed i2c bus %d\n",
i2c_adapter_id(&priv->adap));

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 <giometti@linux.it>");
MODULE_DESCRIPTION("I2C driver for multiplexed I2C busses");
MODULE_LICENSE("GPL v2");
Loading

0 comments on commit 0826374

Please sign in to comment.