xref: /openbmc/linux/drivers/i2c/i2c-mux.c (revision 92ed1a76)
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 */
29 struct 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 
41 static 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 
59 static 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 */
81 static 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 
89 struct 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 }
146 EXPORT_SYMBOL_GPL(i2c_add_mux_adapter);
147 
148 int 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 }
160 EXPORT_SYMBOL_GPL(i2c_del_mux_adapter);
161 
162 MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
163 MODULE_DESCRIPTION("I2C driver for multiplexed I2C busses");
164 MODULE_LICENSE("GPL v2");
165