2 * Multiplexed I2C bus driver.
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>
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).
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>
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.
22 #include <linux/acpi.h>
23 #include <linux/i2c.h>
24 #include <linux/i2c-mux.h>
25 #include <linux/kernel.h>
26 #include <linux/module.h>
28 #include <linux/slab.h>
30 /* multiplexer per channel data */
32 struct i2c_adapter adap
;
33 struct i2c_algorithm algo
;
35 struct i2c_adapter
*parent
;
36 struct device
*mux_dev
;
40 int (*select
)(struct i2c_adapter
*, void *mux_priv
, u32 chan_id
);
41 int (*deselect
)(struct i2c_adapter
*, void *mux_priv
, u32 chan_id
);
44 static int i2c_mux_master_xfer(struct i2c_adapter
*adap
,
45 struct i2c_msg msgs
[], int num
)
47 struct i2c_mux_priv
*priv
= adap
->algo_data
;
48 struct i2c_adapter
*parent
= priv
->parent
;
51 /* Switch to the right mux port and perform the transfer. */
53 ret
= priv
->select(parent
, priv
->mux_priv
, priv
->chan_id
);
55 ret
= __i2c_transfer(parent
, msgs
, num
);
57 priv
->deselect(parent
, priv
->mux_priv
, priv
->chan_id
);
62 static int i2c_mux_smbus_xfer(struct i2c_adapter
*adap
,
63 u16 addr
, unsigned short flags
,
64 char read_write
, u8 command
,
65 int size
, union i2c_smbus_data
*data
)
67 struct i2c_mux_priv
*priv
= adap
->algo_data
;
68 struct i2c_adapter
*parent
= priv
->parent
;
71 /* Select the right mux port and perform the transfer. */
73 ret
= priv
->select(parent
, priv
->mux_priv
, priv
->chan_id
);
75 ret
= parent
->algo
->smbus_xfer(parent
, addr
, flags
,
76 read_write
, command
, size
, data
);
78 priv
->deselect(parent
, priv
->mux_priv
, priv
->chan_id
);
83 /* Return the parent's functionality */
84 static u32
i2c_mux_functionality(struct i2c_adapter
*adap
)
86 struct i2c_mux_priv
*priv
= adap
->algo_data
;
87 struct i2c_adapter
*parent
= priv
->parent
;
89 return parent
->algo
->functionality(parent
);
92 /* Return all parent classes, merged */
93 static unsigned int i2c_mux_parent_classes(struct i2c_adapter
*parent
)
95 unsigned int class = 0;
98 class |= parent
->class;
99 parent
= i2c_parent_is_i2c_adapter(parent
);
105 struct i2c_adapter
*i2c_add_mux_adapter(struct i2c_adapter
*parent
,
106 struct device
*mux_dev
,
107 void *mux_priv
, u32 force_nr
, u32 chan_id
,
109 int (*select
) (struct i2c_adapter
*,
111 int (*deselect
) (struct i2c_adapter
*,
114 struct i2c_mux_priv
*priv
;
115 char symlink_name
[20];
118 priv
= kzalloc(sizeof(struct i2c_mux_priv
), GFP_KERNEL
);
122 /* Set up private adapter data */
123 priv
->parent
= parent
;
124 priv
->mux_dev
= mux_dev
;
125 priv
->mux_priv
= mux_priv
;
126 priv
->chan_id
= chan_id
;
127 priv
->select
= select
;
128 priv
->deselect
= deselect
;
130 /* Need to do algo dynamically because we don't know ahead
131 * of time what sort of physical adapter we'll be dealing with.
133 if (parent
->algo
->master_xfer
)
134 priv
->algo
.master_xfer
= i2c_mux_master_xfer
;
135 if (parent
->algo
->smbus_xfer
)
136 priv
->algo
.smbus_xfer
= i2c_mux_smbus_xfer
;
137 priv
->algo
.functionality
= i2c_mux_functionality
;
139 /* Now fill out new adapter structure */
140 snprintf(priv
->adap
.name
, sizeof(priv
->adap
.name
),
141 "i2c-%d-mux (chan_id %d)", i2c_adapter_id(parent
), chan_id
);
142 priv
->adap
.owner
= THIS_MODULE
;
143 priv
->adap
.algo
= &priv
->algo
;
144 priv
->adap
.algo_data
= priv
;
145 priv
->adap
.dev
.parent
= &parent
->dev
;
146 priv
->adap
.retries
= parent
->retries
;
147 priv
->adap
.timeout
= parent
->timeout
;
148 priv
->adap
.quirks
= parent
->quirks
;
150 /* Sanity check on class */
151 if (i2c_mux_parent_classes(parent
) & class)
152 dev_err(&parent
->dev
,
153 "Segment %d behind mux can't share classes with ancestors\n",
156 priv
->adap
.class = class;
159 * Try to populate the mux adapter's of_node, expands to
160 * nothing if !CONFIG_OF.
162 if (mux_dev
->of_node
) {
163 struct device_node
*child
;
166 for_each_child_of_node(mux_dev
->of_node
, child
) {
167 ret
= of_property_read_u32(child
, "reg", ®
);
170 if (chan_id
== reg
) {
171 priv
->adap
.dev
.of_node
= child
;
178 * Associate the mux channel with an ACPI node.
180 if (has_acpi_companion(mux_dev
))
181 acpi_preset_companion(&priv
->adap
.dev
, ACPI_COMPANION(mux_dev
),
185 priv
->adap
.nr
= force_nr
;
186 ret
= i2c_add_numbered_adapter(&priv
->adap
);
188 ret
= i2c_add_adapter(&priv
->adap
);
191 dev_err(&parent
->dev
,
192 "failed to add mux-adapter (error=%d)\n",
198 WARN(sysfs_create_link(&priv
->adap
.dev
.kobj
, &mux_dev
->kobj
, "mux_device"),
199 "can't create symlink to mux device\n");
201 snprintf(symlink_name
, sizeof(symlink_name
), "channel-%u", chan_id
);
202 WARN(sysfs_create_link(&mux_dev
->kobj
, &priv
->adap
.dev
.kobj
, symlink_name
),
203 "can't create symlink for channel %u\n", chan_id
);
204 dev_info(&parent
->dev
, "Added multiplexed i2c bus %d\n",
205 i2c_adapter_id(&priv
->adap
));
209 EXPORT_SYMBOL_GPL(i2c_add_mux_adapter
);
211 void i2c_del_mux_adapter(struct i2c_adapter
*adap
)
213 struct i2c_mux_priv
*priv
= adap
->algo_data
;
214 char symlink_name
[20];
216 snprintf(symlink_name
, sizeof(symlink_name
), "channel-%u", priv
->chan_id
);
217 sysfs_remove_link(&priv
->mux_dev
->kobj
, symlink_name
);
219 sysfs_remove_link(&priv
->adap
.dev
.kobj
, "mux_device");
220 i2c_del_adapter(adap
);
223 EXPORT_SYMBOL_GPL(i2c_del_mux_adapter
);
225 MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
226 MODULE_DESCRIPTION("I2C driver for multiplexed I2C busses");
227 MODULE_LICENSE("GPL v2");