linux/drivers/i2c/i2c-mux.c
<<
>>
Prefs
   1/*
   2 * Multiplexed I2C bus driver.
   3 *
   4 * Copyright (c) 2008-2009 Rodolfo Giometti <giometti@linux.it>
   5 * Copyright (c) 2008-2009 Eurotech S.p.A. <info@eurotech.it>
   6 * Copyright (c) 2009-2010 NSN GmbH & Co KG <michael.lawnick.ext@nsn.com>
   7 *
   8 * Simplifies access to complex multiplexed I2C bus topologies, by presenting
   9 * each multiplexed bus segment as an additional I2C adapter.
  10 * Supports multi-level mux'ing (mux behind a mux).
  11 *
  12 * Based on:
  13 *      i2c-virt.c from Kumar Gala <galak@kernel.crashing.org>
  14 *      i2c-virtual.c from Ken Harrenstien, Copyright (c) 2004 Google, Inc.
  15 *      i2c-virtual.c from Brian Kuschak <bkuschak@yahoo.com>
  16 *
  17 * This file is licensed under the terms of the GNU General Public
  18 * License version 2. This program is licensed "as is" without any
  19 * warranty of any kind, whether express or implied.
  20 */
  21
  22#include <linux/kernel.h>
  23#include <linux/module.h>
  24#include <linux/slab.h>
  25#include <linux/i2c.h>
  26#include <linux/i2c-mux.h>
  27
  28/* multiplexer per channel data */
  29struct i2c_mux_priv {
  30        struct i2c_adapter adap;
  31        struct i2c_algorithm algo;
  32
  33        struct i2c_adapter *parent;
  34        void *mux_dev;  /* the mux chip/device */
  35        u32  chan_id;   /* the channel id */
  36
  37        int (*select)(struct i2c_adapter *, void *mux_dev, u32 chan_id);
  38        int (*deselect)(struct i2c_adapter *, void *mux_dev, u32 chan_id);
  39};
  40
  41static int i2c_mux_master_xfer(struct i2c_adapter *adap,
  42                               struct i2c_msg msgs[], int num)
  43{
  44        struct i2c_mux_priv *priv = adap->algo_data;
  45        struct i2c_adapter *parent = priv->parent;
  46        int ret;
  47
  48        /* Switch to the right mux port and perform the transfer. */
  49
  50        ret = priv->select(parent, priv->mux_dev, priv->chan_id);
  51        if (ret >= 0)
  52                ret = parent->algo->master_xfer(parent, msgs, num);
  53        if (priv->deselect)
  54                priv->deselect(parent, priv->mux_dev, priv->chan_id);
  55
  56        return ret;
  57}
  58
  59static int i2c_mux_smbus_xfer(struct i2c_adapter *adap,
  60                              u16 addr, unsigned short flags,
  61                              char read_write, u8 command,
  62                              int size, union i2c_smbus_data *data)
  63{
  64        struct i2c_mux_priv *priv = adap->algo_data;
  65        struct i2c_adapter *parent = priv->parent;
  66        int ret;
  67
  68        /* Select the right mux port and perform the transfer. */
  69
  70        ret = priv->select(parent, priv->mux_dev, priv->chan_id);
  71        if (ret >= 0)
  72                ret = parent->algo->smbus_xfer(parent, addr, flags,
  73                                        read_write, command, size, data);
  74        if (priv->deselect)
  75                priv->deselect(parent, priv->mux_dev, priv->chan_id);
  76
  77        return ret;
  78}
  79
  80/* Return the parent's functionality */
  81static u32 i2c_mux_functionality(struct i2c_adapter *adap)
  82{
  83        struct i2c_mux_priv *priv = adap->algo_data;
  84        struct i2c_adapter *parent = priv->parent;
  85
  86        return parent->algo->functionality(parent);
  87}
  88
  89struct i2c_adapter *i2c_add_mux_adapter(struct i2c_adapter *parent,
  90                                void *mux_dev, u32 force_nr, u32 chan_id,
  91                                int (*select) (struct i2c_adapter *,
  92                                               void *, u32),
  93                                int (*deselect) (struct i2c_adapter *,
  94                                                 void *, u32))
  95{
  96        struct i2c_mux_priv *priv;
  97        int ret;
  98
  99        priv = kzalloc(sizeof(struct i2c_mux_priv), GFP_KERNEL);
 100        if (!priv)
 101                return NULL;
 102
 103        /* Set up private adapter data */
 104        priv->parent = parent;
 105        priv->mux_dev = mux_dev;
 106        priv->chan_id = chan_id;
 107        priv->select = select;
 108        priv->deselect = deselect;
 109
 110        /* Need to do algo dynamically because we don't know ahead
 111         * of time what sort of physical adapter we'll be dealing with.
 112         */
 113        if (parent->algo->master_xfer)
 114                priv->algo.master_xfer = i2c_mux_master_xfer;
 115        if (parent->algo->smbus_xfer)
 116                priv->algo.smbus_xfer = i2c_mux_smbus_xfer;
 117        priv->algo.functionality = i2c_mux_functionality;
 118
 119        /* Now fill out new adapter structure */
 120        snprintf(priv->adap.name, sizeof(priv->adap.name),
 121                 "i2c-%d-mux (chan_id %d)", i2c_adapter_id(parent), chan_id);
 122        priv->adap.owner = THIS_MODULE;
 123        priv->adap.algo = &priv->algo;
 124        priv->adap.algo_data = priv;
 125        priv->adap.dev.parent = &parent->dev;
 126
 127        if (force_nr) {
 128                priv->adap.nr = force_nr;
 129                ret = i2c_add_numbered_adapter(&priv->adap);
 130        } else {
 131                ret = i2c_add_adapter(&priv->adap);
 132        }
 133        if (ret < 0) {
 134                dev_err(&parent->dev,
 135                        "failed to add mux-adapter (error=%d)\n",
 136                        ret);
 137                kfree(priv);
 138                return NULL;
 139        }
 140
 141        dev_info(&parent->dev, "Added multiplexed i2c bus %d\n",
 142                 i2c_adapter_id(&priv->adap));
 143
 144        return &priv->adap;
 145}
 146EXPORT_SYMBOL_GPL(i2c_add_mux_adapter);
 147
 148int i2c_del_mux_adapter(struct i2c_adapter *adap)
 149{
 150        struct i2c_mux_priv *priv = adap->algo_data;
 151        int ret;
 152
 153        ret = i2c_del_adapter(adap);
 154        if (ret < 0)
 155                return ret;
 156        kfree(priv);
 157
 158        return 0;
 159}
 160EXPORT_SYMBOL_GPL(i2c_del_mux_adapter);
 161
 162MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
 163MODULE_DESCRIPTION("I2C driver for multiplexed I2C busses");
 164MODULE_LICENSE("GPL v2");
 165