Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0826374b authored by Michael Lawnick's avatar Michael Lawnick Committed by Jean Delvare
Browse files

i2c: Multiplexed I2C bus core support



Add multiplexed bus core support. I2C multiplexer and switches
like pca954x get instantiated as new adapters per port.

Signed-off-by: default avatarMichael Lawnick <ml.lawnick@gmx.de>
Acked-by: default avatarRodolfo Giometti <giometti@linux.it>
Signed-off-by: default avatarJean Delvare <khali@linux-fr.org>
parent dafc50d1
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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)
+57 −7
Original line number Diff line number Diff line
@@ -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>
@@ -423,10 +425,48 @@ static int __i2c_check_addr_busy(struct device *dev, void *addrp)
	return 0;
}

static int i2c_check_addr_busy(struct i2c_adapter *adapter, int addr)
/* walk up mux tree */
static int i2c_check_mux_parents(struct i2c_adapter *adapter, int addr)
{
	return device_for_each_child(&adapter->dev, &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)
{
	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;
}

/**
@@ -435,6 +475,9 @@ static int i2c_check_addr_busy(struct i2c_adapter *adapter, int addr)
 */
void i2c_lock_adapter(struct i2c_adapter *adapter)
{
	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);
@@ -445,6 +488,9 @@ EXPORT_SYMBOL_GPL(i2c_lock_adapter);
 */
static int i2c_trylock_adapter(struct i2c_adapter *adapter)
{
	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);
}

@@ -454,6 +500,9 @@ static int i2c_trylock_adapter(struct i2c_adapter *adapter)
 */
void i2c_unlock_adapter(struct i2c_adapter *adapter)
{
	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);
@@ -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;
+39 −1
Original line number Diff line number Diff line
@@ -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,

drivers/i2c/i2c-mux.c

0 → 100644
+165 −0
Original line number Diff line number Diff line
/*
 * 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