diff options
author | Michael Lawnick <ml.lawnick@gmx.de> | 2010-08-11 12:21:02 -0400 |
---|---|---|
committer | Jean Delvare <khali@linux-fr.org> | 2010-08-11 12:21:02 -0400 |
commit | 0826374bff57411d239f2fcb15da3c35af0a93cd (patch) | |
tree | 514d4361cfc9cc546306612de3464def7fe8a7cd /drivers/i2c | |
parent | dafc50d141c27959dbd3a1cfe9857a86d23402a7 (diff) |
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 <ml.lawnick@gmx.de>
Acked-by: Rodolfo Giometti <giometti@linux.it>
Signed-off-by: Jean Delvare <khali@linux-fr.org>
Diffstat (limited to 'drivers/i2c')
-rw-r--r-- | drivers/i2c/Kconfig | 11 | ||||
-rw-r--r-- | drivers/i2c/Makefile | 1 | ||||
-rw-r--r-- | drivers/i2c/i2c-core.c | 64 | ||||
-rw-r--r-- | drivers/i2c/i2c-dev.c | 40 | ||||
-rw-r--r-- | drivers/i2c/i2c-mux.c | 165 |
5 files changed, 273 insertions, 8 deletions
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 | |||
47 | This support is also available as a module. If so, the module | 47 | This support is also available as a module. If so, the module |
48 | will be called i2c-dev. | 48 | will be called i2c-dev. |
49 | 49 | ||
50 | config I2C_MUX | ||
51 | tristate "I2C bus multiplexing support" | ||
52 | depends on EXPERIMENTAL | ||
53 | help | ||
54 | Say Y here if you want the I2C core to support the ability to | ||
55 | handle multiplexed I2C bus topologies, by presenting each | ||
56 | multiplexed segment as a I2C adapter. | ||
57 | |||
58 | This support is also available as a module. If so, the module | ||
59 | will be called i2c-mux. | ||
60 | |||
50 | config I2C_HELPER_AUTO | 61 | config I2C_HELPER_AUTO |
51 | bool "Autoselect pertinent helper modules" | 62 | bool "Autoselect pertinent helper modules" |
52 | default y | 63 | 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 | |||
6 | obj-$(CONFIG_I2C) += i2c-core.o | 6 | obj-$(CONFIG_I2C) += i2c-core.o |
7 | obj-$(CONFIG_I2C_SMBUS) += i2c-smbus.o | 7 | obj-$(CONFIG_I2C_SMBUS) += i2c-smbus.o |
8 | obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o | 8 | obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o |
9 | obj-$(CONFIG_I2C_MUX) += i2c-mux.o | ||
9 | obj-y += algos/ busses/ | 10 | obj-y += algos/ busses/ |
10 | 11 | ||
11 | ifeq ($(CONFIG_I2C_DEBUG_CORE),y) | 12 | 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 @@ | |||
20 | /* With some changes from Kyösti Mälkki <kmalkki@cc.hut.fi>. | 20 | /* With some changes from Kyösti Mälkki <kmalkki@cc.hut.fi>. |
21 | All SMBus-related things are written by Frodo Looijaard <frodol@dds.nl> | 21 | All SMBus-related things are written by Frodo Looijaard <frodol@dds.nl> |
22 | SMBus 2.0 support by Mark Studebaker <mdsxyz123@yahoo.com> and | 22 | SMBus 2.0 support by Mark Studebaker <mdsxyz123@yahoo.com> and |
23 | Jean Delvare <khali@linux-fr.org> */ | 23 | Jean Delvare <khali@linux-fr.org> |
24 | Mux support by Rodolfo Giometti <giometti@enneenne.com> and | ||
25 | Michael Lawnick <michael.lawnick.ext@nsn.com> */ | ||
24 | 26 | ||
25 | #include <linux/module.h> | 27 | #include <linux/module.h> |
26 | #include <linux/kernel.h> | 28 | #include <linux/kernel.h> |
@@ -423,10 +425,48 @@ static int __i2c_check_addr_busy(struct device *dev, void *addrp) | |||
423 | return 0; | 425 | return 0; |
424 | } | 426 | } |
425 | 427 | ||
428 | /* walk up mux tree */ | ||
429 | static int i2c_check_mux_parents(struct i2c_adapter *adapter, int addr) | ||
430 | { | ||
431 | int result; | ||
432 | |||
433 | result = device_for_each_child(&adapter->dev, &addr, | ||
434 | __i2c_check_addr_busy); | ||
435 | |||
436 | if (!result && i2c_parent_is_i2c_adapter(adapter)) | ||
437 | result = i2c_check_mux_parents( | ||
438 | to_i2c_adapter(adapter->dev.parent), addr); | ||
439 | |||
440 | return result; | ||
441 | } | ||
442 | |||
443 | /* recurse down mux tree */ | ||
444 | static int i2c_check_mux_children(struct device *dev, void *addrp) | ||
445 | { | ||
446 | int result; | ||
447 | |||
448 | if (dev->type == &i2c_adapter_type) | ||
449 | result = device_for_each_child(dev, addrp, | ||
450 | i2c_check_mux_children); | ||
451 | else | ||
452 | result = __i2c_check_addr_busy(dev, addrp); | ||
453 | |||
454 | return result; | ||
455 | } | ||
456 | |||
426 | static int i2c_check_addr_busy(struct i2c_adapter *adapter, int addr) | 457 | static int i2c_check_addr_busy(struct i2c_adapter *adapter, int addr) |
427 | { | 458 | { |
428 | return device_for_each_child(&adapter->dev, &addr, | 459 | int result = 0; |
429 | __i2c_check_addr_busy); | 460 | |
461 | if (i2c_parent_is_i2c_adapter(adapter)) | ||
462 | result = i2c_check_mux_parents( | ||
463 | to_i2c_adapter(adapter->dev.parent), addr); | ||
464 | |||
465 | if (!result) | ||
466 | result = device_for_each_child(&adapter->dev, &addr, | ||
467 | i2c_check_mux_children); | ||
468 | |||
469 | return result; | ||
430 | } | 470 | } |
431 | 471 | ||
432 | /** | 472 | /** |
@@ -435,7 +475,10 @@ static int i2c_check_addr_busy(struct i2c_adapter *adapter, int addr) | |||
435 | */ | 475 | */ |
436 | void i2c_lock_adapter(struct i2c_adapter *adapter) | 476 | void i2c_lock_adapter(struct i2c_adapter *adapter) |
437 | { | 477 | { |
438 | rt_mutex_lock(&adapter->bus_lock); | 478 | if (i2c_parent_is_i2c_adapter(adapter)) |
479 | i2c_lock_adapter(to_i2c_adapter(adapter->dev.parent)); | ||
480 | else | ||
481 | rt_mutex_lock(&adapter->bus_lock); | ||
439 | } | 482 | } |
440 | EXPORT_SYMBOL_GPL(i2c_lock_adapter); | 483 | EXPORT_SYMBOL_GPL(i2c_lock_adapter); |
441 | 484 | ||
@@ -445,7 +488,10 @@ EXPORT_SYMBOL_GPL(i2c_lock_adapter); | |||
445 | */ | 488 | */ |
446 | static int i2c_trylock_adapter(struct i2c_adapter *adapter) | 489 | static int i2c_trylock_adapter(struct i2c_adapter *adapter) |
447 | { | 490 | { |
448 | return rt_mutex_trylock(&adapter->bus_lock); | 491 | if (i2c_parent_is_i2c_adapter(adapter)) |
492 | return i2c_trylock_adapter(to_i2c_adapter(adapter->dev.parent)); | ||
493 | else | ||
494 | return rt_mutex_trylock(&adapter->bus_lock); | ||
449 | } | 495 | } |
450 | 496 | ||
451 | /** | 497 | /** |
@@ -454,7 +500,10 @@ static int i2c_trylock_adapter(struct i2c_adapter *adapter) | |||
454 | */ | 500 | */ |
455 | void i2c_unlock_adapter(struct i2c_adapter *adapter) | 501 | void i2c_unlock_adapter(struct i2c_adapter *adapter) |
456 | { | 502 | { |
457 | rt_mutex_unlock(&adapter->bus_lock); | 503 | if (i2c_parent_is_i2c_adapter(adapter)) |
504 | i2c_unlock_adapter(to_i2c_adapter(adapter->dev.parent)); | ||
505 | else | ||
506 | rt_mutex_unlock(&adapter->bus_lock); | ||
458 | } | 507 | } |
459 | EXPORT_SYMBOL_GPL(i2c_unlock_adapter); | 508 | EXPORT_SYMBOL_GPL(i2c_unlock_adapter); |
460 | 509 | ||
@@ -743,10 +792,11 @@ static const struct attribute_group *i2c_adapter_attr_groups[] = { | |||
743 | NULL | 792 | NULL |
744 | }; | 793 | }; |
745 | 794 | ||
746 | static struct device_type i2c_adapter_type = { | 795 | struct device_type i2c_adapter_type = { |
747 | .groups = i2c_adapter_attr_groups, | 796 | .groups = i2c_adapter_attr_groups, |
748 | .release = i2c_adapter_dev_release, | 797 | .release = i2c_adapter_dev_release, |
749 | }; | 798 | }; |
799 | EXPORT_SYMBOL_GPL(i2c_adapter_type); | ||
750 | 800 | ||
751 | #ifdef CONFIG_I2C_COMPAT | 801 | #ifdef CONFIG_I2C_COMPAT |
752 | static struct class_compat *i2c_adapter_compat_class; | 802 | 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) | |||
189 | return dev->driver ? -EBUSY : 0; | 189 | return dev->driver ? -EBUSY : 0; |
190 | } | 190 | } |
191 | 191 | ||
192 | /* walk up mux tree */ | ||
193 | static int i2cdev_check_mux_parents(struct i2c_adapter *adapter, int addr) | ||
194 | { | ||
195 | int result; | ||
196 | |||
197 | result = device_for_each_child(&adapter->dev, &addr, i2cdev_check); | ||
198 | |||
199 | if (!result && i2c_parent_is_i2c_adapter(adapter)) | ||
200 | result = i2cdev_check_mux_parents( | ||
201 | to_i2c_adapter(adapter->dev.parent), addr); | ||
202 | |||
203 | return result; | ||
204 | } | ||
205 | |||
206 | /* recurse down mux tree */ | ||
207 | static int i2cdev_check_mux_children(struct device *dev, void *addrp) | ||
208 | { | ||
209 | int result; | ||
210 | |||
211 | if (dev->type == &i2c_adapter_type) | ||
212 | result = device_for_each_child(dev, addrp, | ||
213 | i2cdev_check_mux_children); | ||
214 | else | ||
215 | result = i2cdev_check(dev, addrp); | ||
216 | |||
217 | return result; | ||
218 | } | ||
219 | |||
192 | /* This address checking function differs from the one in i2c-core | 220 | /* This address checking function differs from the one in i2c-core |
193 | in that it considers an address with a registered device, but no | 221 | in that it considers an address with a registered device, but no |
194 | driver bound to it, as NOT busy. */ | 222 | driver bound to it, as NOT busy. */ |
195 | static int i2cdev_check_addr(struct i2c_adapter *adapter, unsigned int addr) | 223 | static int i2cdev_check_addr(struct i2c_adapter *adapter, unsigned int addr) |
196 | { | 224 | { |
197 | return device_for_each_child(&adapter->dev, &addr, i2cdev_check); | 225 | int result = 0; |
226 | |||
227 | if (i2c_parent_is_i2c_adapter(adapter)) | ||
228 | result = i2cdev_check_mux_parents( | ||
229 | to_i2c_adapter(adapter->dev.parent), addr); | ||
230 | |||
231 | if (!result) | ||
232 | result = device_for_each_child(&adapter->dev, &addr, | ||
233 | i2cdev_check_mux_children); | ||
234 | |||
235 | return result; | ||
198 | } | 236 | } |
199 | 237 | ||
200 | static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client, | 238 | 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 @@ | |||
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.id = parent->id; | ||
124 | priv->adap.algo = &priv->algo; | ||
125 | priv->adap.algo_data = priv; | ||
126 | priv->adap.dev.parent = &parent->dev; | ||
127 | |||
128 | if (force_nr) { | ||
129 | priv->adap.nr = force_nr; | ||
130 | ret = i2c_add_numbered_adapter(&priv->adap); | ||
131 | } else { | ||
132 | ret = i2c_add_adapter(&priv->adap); | ||
133 | } | ||
134 | if (ret < 0) { | ||
135 | dev_err(&parent->dev, | ||
136 | "failed to add mux-adapter (error=%d)\n", | ||
137 | ret); | ||
138 | kfree(priv); | ||
139 | return NULL; | ||
140 | } | ||
141 | |||
142 | dev_info(&parent->dev, "Added multiplexed i2c bus %d\n", | ||
143 | i2c_adapter_id(&priv->adap)); | ||
144 | |||
145 | return &priv->adap; | ||
146 | } | ||
147 | EXPORT_SYMBOL_GPL(i2c_add_mux_adapter); | ||
148 | |||
149 | int i2c_del_mux_adapter(struct i2c_adapter *adap) | ||
150 | { | ||
151 | struct i2c_mux_priv *priv = adap->algo_data; | ||
152 | int ret; | ||
153 | |||
154 | ret = i2c_del_adapter(adap); | ||
155 | if (ret < 0) | ||
156 | return ret; | ||
157 | kfree(priv); | ||
158 | |||
159 | return 0; | ||
160 | } | ||
161 | EXPORT_SYMBOL_GPL(i2c_del_mux_adapter); | ||
162 | |||
163 | MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>"); | ||
164 | MODULE_DESCRIPTION("I2C driver for multiplexed I2C busses"); | ||
165 | MODULE_LICENSE("GPL v2"); | ||