From 0826374bff57411d239f2fcb15da3c35af0a93cd Mon Sep 17 00:00:00 2001 From: Michael Lawnick Date: Wed, 11 Aug 2010 18:21:02 +0200 Subject: [PATCH] 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: Michael Lawnick Acked-by: Rodolfo Giometti Signed-off-by: Jean Delvare --- drivers/i2c/Kconfig | 11 +++ drivers/i2c/Makefile | 1 + drivers/i2c/i2c-core.c | 64 ++++++++++++++-- drivers/i2c/i2c-dev.c | 40 +++++++++- drivers/i2c/i2c-mux.c | 165 ++++++++++++++++++++++++++++++++++++++++ include/linux/i2c-mux.h | 46 +++++++++++ include/linux/i2c.h | 8 ++ 7 files changed, 327 insertions(+), 8 deletions(-) create mode 100644 drivers/i2c/i2c-mux.c create mode 100644 include/linux/i2c-mux.h diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index d06083fdffbb..efb48ad1ba34 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -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 diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index a7d9b4be9bb3..f363258daa3d 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -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) diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c index 97f96b66653c..6649176de940 100644 --- a/drivers/i2c/i2c-core.c +++ b/drivers/i2c/i2c-core.c @@ -20,7 +20,9 @@ /* With some changes from Kyösti Mälkki . All SMBus-related things are written by Frodo Looijaard SMBus 2.0 support by Mark Studebaker and - Jean Delvare */ + Jean Delvare + Mux support by Rodolfo Giometti and + Michael Lawnick */ #include #include @@ -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; } /** @@ -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); @@ -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); } /** @@ -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); @@ -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; diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c index 0b0427f7d348..5f3a52d517c3 100644 --- a/drivers/i2c/i2c-dev.c +++ b/drivers/i2c/i2c-dev.c @@ -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, diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c new file mode 100644 index 000000000000..d32a4843fc3a --- /dev/null +++ b/drivers/i2c/i2c-mux.c @@ -0,0 +1,165 @@ +/* + * Multiplexed I2C bus driver. + * + * Copyright (c) 2008-2009 Rodolfo Giometti + * Copyright (c) 2008-2009 Eurotech S.p.A. + * Copyright (c) 2009-2010 NSN GmbH & Co KG + * + * 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 + * i2c-virtual.c from Ken Harrenstien, Copyright (c) 2004 Google, Inc. + * i2c-virtual.c from Brian Kuschak + * + * 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 +#include +#include +#include +#include + +/* 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 "); +MODULE_DESCRIPTION("I2C driver for multiplexed I2C busses"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/i2c-mux.h b/include/linux/i2c-mux.h new file mode 100644 index 000000000000..34536effd652 --- /dev/null +++ b/include/linux/i2c-mux.h @@ -0,0 +1,46 @@ +/* + * + * i2c-mux.h - functions for the i2c-bus mux support + * + * Copyright (c) 2008-2009 Rodolfo Giometti + * Copyright (c) 2008-2009 Eurotech S.p.A. + * Michael Lawnick + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _LINUX_I2C_MUX_H +#define _LINUX_I2C_MUX_H + +#ifdef __KERNEL__ + +/* + * Called to create a i2c bus on a multiplexed bus segment. + * The mux_dev and chan_id parameters are passed to the select + * and deselect callback functions to perform hardware-specific + * mux control. + */ +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 *mux_dev, u32 chan_id), + int (*deselect) (struct i2c_adapter *, + void *mux_dev, u32 chan_id)); + +int i2c_del_mux_adapter(struct i2c_adapter *adap); + +#endif /* __KERNEL__ */ + +#endif /* _LINUX_I2C_MUX_H */ diff --git a/include/linux/i2c.h b/include/linux/i2c.h index 798bad8741e4..4bae0b72ed3c 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -37,6 +37,7 @@ #include /* for struct device_node */ extern struct bus_type i2c_bus_type; +extern struct device_type i2c_adapter_type; /* --- General options ------------------------------------------------ */ @@ -383,6 +384,13 @@ static inline void i2c_set_adapdata(struct i2c_adapter *dev, void *data) dev_set_drvdata(&dev->dev, data); } +static inline int i2c_parent_is_i2c_adapter(const struct i2c_adapter *adapter) +{ + return adapter->dev.parent != NULL + && adapter->dev.parent->bus == &i2c_bus_type + && adapter->dev.parent->type == &i2c_adapter_type; +} + /* Adapter locking functions, exported for shared pin cases */ void i2c_lock_adapter(struct i2c_adapter *); void i2c_unlock_adapter(struct i2c_adapter *); -- 2.39.5