diff options
author | Jean Delvare <khali@linux-fr.org> | 2008-07-14 16:38:21 -0400 |
---|---|---|
committer | Jean Delvare <khali@mahadeva.delvare> | 2008-07-14 16:38:21 -0400 |
commit | 279e902445557897707d325182916a6e28ba80de (patch) | |
tree | 48f518f932b7a527ccd749f7e310d4fa92149c7e /drivers | |
parent | 2e7437879897a4185bd84478a0451e5367dee7ed (diff) |
i2c-nforce2: Add support for multiplexing on the Tyan S4985
Just like the Tyan S4882, the S4985 uses a multiplexer to give access
to all 16 memory module SPD EEPROMs. This specific i2c-nforce2-s4985
driver adds support for this. It is heavily based on the older
i2c-amd756-s4882 driver.
As more mainboards will use multiplexer chips, we will have to find a
way to support them without having to write a new specfic driver for
each. The recent changes to the i2c subsystem should help us, and the
new gpio subsystem might help, too.
Signed-off-by: Jean Delvare <khali@linux-fr.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/i2c/busses/Kconfig | 13 | ||||
-rw-r--r-- | drivers/i2c/busses/Makefile | 1 | ||||
-rw-r--r-- | drivers/i2c/busses/i2c-nforce2-s4985.c | 257 | ||||
-rw-r--r-- | drivers/i2c/busses/i2c-nforce2.c | 16 |
4 files changed, 287 insertions, 0 deletions
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 48438cc5d0c..00d76e13588 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig | |||
@@ -315,6 +315,19 @@ config I2C_NFORCE2 | |||
315 | This driver can also be built as a module. If so, the module | 315 | This driver can also be built as a module. If so, the module |
316 | will be called i2c-nforce2. | 316 | will be called i2c-nforce2. |
317 | 317 | ||
318 | config I2C_NFORCE2_S4985 | ||
319 | tristate "SMBus multiplexing on the Tyan S4985" | ||
320 | depends on I2C_NFORCE2 && EXPERIMENTAL | ||
321 | help | ||
322 | Enabling this option will add specific SMBus support for the Tyan | ||
323 | S4985 motherboard. On this 4-CPU board, the SMBus is multiplexed | ||
324 | over 4 different channels, where the various memory module EEPROMs | ||
325 | live. Saying yes here will give you access to these in addition | ||
326 | to the trunk. | ||
327 | |||
328 | This driver can also be built as a module. If so, the module | ||
329 | will be called i2c-nforce2-s4985. | ||
330 | |||
318 | config I2C_OCORES | 331 | config I2C_OCORES |
319 | tristate "OpenCores I2C Controller" | 332 | tristate "OpenCores I2C Controller" |
320 | depends on EXPERIMENTAL | 333 | depends on EXPERIMENTAL |
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index e8c882a5ea6..8b0a8c25790 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile | |||
@@ -24,6 +24,7 @@ obj-$(CONFIG_I2C_POWERMAC) += i2c-powermac.o | |||
24 | obj-$(CONFIG_I2C_MPC) += i2c-mpc.o | 24 | obj-$(CONFIG_I2C_MPC) += i2c-mpc.o |
25 | obj-$(CONFIG_I2C_MV64XXX) += i2c-mv64xxx.o | 25 | obj-$(CONFIG_I2C_MV64XXX) += i2c-mv64xxx.o |
26 | obj-$(CONFIG_I2C_NFORCE2) += i2c-nforce2.o | 26 | obj-$(CONFIG_I2C_NFORCE2) += i2c-nforce2.o |
27 | obj-$(CONFIG_I2C_NFORCE2_S4985) += i2c-nforce2-s4985.o | ||
27 | obj-$(CONFIG_I2C_OCORES) += i2c-ocores.o | 28 | obj-$(CONFIG_I2C_OCORES) += i2c-ocores.o |
28 | obj-$(CONFIG_I2C_OMAP) += i2c-omap.o | 29 | obj-$(CONFIG_I2C_OMAP) += i2c-omap.o |
29 | obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o | 30 | obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o |
diff --git a/drivers/i2c/busses/i2c-nforce2-s4985.c b/drivers/i2c/busses/i2c-nforce2-s4985.c new file mode 100644 index 00000000000..6a8995dfd0b --- /dev/null +++ b/drivers/i2c/busses/i2c-nforce2-s4985.c | |||
@@ -0,0 +1,257 @@ | |||
1 | /* | ||
2 | * i2c-nforce2-s4985.c - i2c-nforce2 extras for the Tyan S4985 motherboard | ||
3 | * | ||
4 | * Copyright (C) 2008 Jean Delvare <khali@linux-fr.org> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
19 | */ | ||
20 | |||
21 | /* | ||
22 | * We select the channels by sending commands to the Philips | ||
23 | * PCA9556 chip at I2C address 0x18. The main adapter is used for | ||
24 | * the non-multiplexed part of the bus, and 4 virtual adapters | ||
25 | * are defined for the multiplexed addresses: 0x50-0x53 (memory | ||
26 | * module EEPROM) located on channels 1-4. We define one virtual | ||
27 | * adapter per CPU, which corresponds to one multiplexed channel: | ||
28 | * CPU0: virtual adapter 1, channel 1 | ||
29 | * CPU1: virtual adapter 2, channel 2 | ||
30 | * CPU2: virtual adapter 3, channel 3 | ||
31 | * CPU3: virtual adapter 4, channel 4 | ||
32 | */ | ||
33 | |||
34 | #include <linux/module.h> | ||
35 | #include <linux/kernel.h> | ||
36 | #include <linux/slab.h> | ||
37 | #include <linux/init.h> | ||
38 | #include <linux/i2c.h> | ||
39 | #include <linux/mutex.h> | ||
40 | |||
41 | extern struct i2c_adapter *nforce2_smbus; | ||
42 | |||
43 | static struct i2c_adapter *s4985_adapter; | ||
44 | static struct i2c_algorithm *s4985_algo; | ||
45 | |||
46 | /* Wrapper access functions for multiplexed SMBus */ | ||
47 | static DEFINE_MUTEX(nforce2_lock); | ||
48 | |||
49 | static s32 nforce2_access_virt0(struct i2c_adapter *adap, u16 addr, | ||
50 | unsigned short flags, char read_write, | ||
51 | u8 command, int size, | ||
52 | union i2c_smbus_data *data) | ||
53 | { | ||
54 | int error; | ||
55 | |||
56 | /* We exclude the multiplexed addresses */ | ||
57 | if ((addr & 0xfc) == 0x50 || (addr & 0xfc) == 0x30 | ||
58 | || addr == 0x18) | ||
59 | return -ENXIO; | ||
60 | |||
61 | mutex_lock(&nforce2_lock); | ||
62 | error = nforce2_smbus->algo->smbus_xfer(adap, addr, flags, read_write, | ||
63 | command, size, data); | ||
64 | mutex_unlock(&nforce2_lock); | ||
65 | |||
66 | return error; | ||
67 | } | ||
68 | |||
69 | /* We remember the last used channels combination so as to only switch | ||
70 | channels when it is really needed. This greatly reduces the SMBus | ||
71 | overhead, but also assumes that nobody will be writing to the PCA9556 | ||
72 | in our back. */ | ||
73 | static u8 last_channels; | ||
74 | |||
75 | static inline s32 nforce2_access_channel(struct i2c_adapter *adap, u16 addr, | ||
76 | unsigned short flags, char read_write, | ||
77 | u8 command, int size, | ||
78 | union i2c_smbus_data *data, | ||
79 | u8 channels) | ||
80 | { | ||
81 | int error; | ||
82 | |||
83 | /* We exclude the non-multiplexed addresses */ | ||
84 | if ((addr & 0xfc) != 0x50 && (addr & 0xfc) != 0x30) | ||
85 | return -ENXIO; | ||
86 | |||
87 | mutex_lock(&nforce2_lock); | ||
88 | if (last_channels != channels) { | ||
89 | union i2c_smbus_data mplxdata; | ||
90 | mplxdata.byte = channels; | ||
91 | |||
92 | error = nforce2_smbus->algo->smbus_xfer(adap, 0x18, 0, | ||
93 | I2C_SMBUS_WRITE, 0x01, | ||
94 | I2C_SMBUS_BYTE_DATA, | ||
95 | &mplxdata); | ||
96 | if (error) | ||
97 | goto UNLOCK; | ||
98 | last_channels = channels; | ||
99 | } | ||
100 | error = nforce2_smbus->algo->smbus_xfer(adap, addr, flags, read_write, | ||
101 | command, size, data); | ||
102 | |||
103 | UNLOCK: | ||
104 | mutex_unlock(&nforce2_lock); | ||
105 | return error; | ||
106 | } | ||
107 | |||
108 | static s32 nforce2_access_virt1(struct i2c_adapter *adap, u16 addr, | ||
109 | unsigned short flags, char read_write, | ||
110 | u8 command, int size, | ||
111 | union i2c_smbus_data *data) | ||
112 | { | ||
113 | /* CPU0: channel 1 enabled */ | ||
114 | return nforce2_access_channel(adap, addr, flags, read_write, command, | ||
115 | size, data, 0x02); | ||
116 | } | ||
117 | |||
118 | static s32 nforce2_access_virt2(struct i2c_adapter *adap, u16 addr, | ||
119 | unsigned short flags, char read_write, | ||
120 | u8 command, int size, | ||
121 | union i2c_smbus_data *data) | ||
122 | { | ||
123 | /* CPU1: channel 2 enabled */ | ||
124 | return nforce2_access_channel(adap, addr, flags, read_write, command, | ||
125 | size, data, 0x04); | ||
126 | } | ||
127 | |||
128 | static s32 nforce2_access_virt3(struct i2c_adapter *adap, u16 addr, | ||
129 | unsigned short flags, char read_write, | ||
130 | u8 command, int size, | ||
131 | union i2c_smbus_data *data) | ||
132 | { | ||
133 | /* CPU2: channel 3 enabled */ | ||
134 | return nforce2_access_channel(adap, addr, flags, read_write, command, | ||
135 | size, data, 0x08); | ||
136 | } | ||
137 | |||
138 | static s32 nforce2_access_virt4(struct i2c_adapter *adap, u16 addr, | ||
139 | unsigned short flags, char read_write, | ||
140 | u8 command, int size, | ||
141 | union i2c_smbus_data *data) | ||
142 | { | ||
143 | /* CPU3: channel 4 enabled */ | ||
144 | return nforce2_access_channel(adap, addr, flags, read_write, command, | ||
145 | size, data, 0x10); | ||
146 | } | ||
147 | |||
148 | static int __init nforce2_s4985_init(void) | ||
149 | { | ||
150 | int i, error; | ||
151 | union i2c_smbus_data ioconfig; | ||
152 | |||
153 | /* Unregister physical bus */ | ||
154 | if (!nforce2_smbus) | ||
155 | return -ENODEV; | ||
156 | error = i2c_del_adapter(nforce2_smbus); | ||
157 | if (error) { | ||
158 | dev_err(&nforce2_smbus->dev, "Physical bus removal failed\n"); | ||
159 | goto ERROR0; | ||
160 | } | ||
161 | |||
162 | printk(KERN_INFO "Enabling SMBus multiplexing for Tyan S4985\n"); | ||
163 | /* Define the 5 virtual adapters and algorithms structures */ | ||
164 | s4985_adapter = kzalloc(5 * sizeof(struct i2c_adapter), GFP_KERNEL); | ||
165 | if (!s4985_adapter) { | ||
166 | error = -ENOMEM; | ||
167 | goto ERROR1; | ||
168 | } | ||
169 | s4985_algo = kzalloc(5 * sizeof(struct i2c_algorithm), GFP_KERNEL); | ||
170 | if (!s4985_algo) { | ||
171 | error = -ENOMEM; | ||
172 | goto ERROR2; | ||
173 | } | ||
174 | |||
175 | /* Fill in the new structures */ | ||
176 | s4985_algo[0] = *(nforce2_smbus->algo); | ||
177 | s4985_algo[0].smbus_xfer = nforce2_access_virt0; | ||
178 | s4985_adapter[0] = *nforce2_smbus; | ||
179 | s4985_adapter[0].algo = s4985_algo; | ||
180 | s4985_adapter[0].dev.parent = nforce2_smbus->dev.parent; | ||
181 | for (i = 1; i < 5; i++) { | ||
182 | s4985_algo[i] = *(nforce2_smbus->algo); | ||
183 | s4985_adapter[i] = *nforce2_smbus; | ||
184 | snprintf(s4985_adapter[i].name, sizeof(s4985_adapter[i].name), | ||
185 | "SMBus nForce2 adapter (CPU%d)", i - 1); | ||
186 | s4985_adapter[i].algo = s4985_algo + i; | ||
187 | s4985_adapter[i].dev.parent = nforce2_smbus->dev.parent; | ||
188 | } | ||
189 | s4985_algo[1].smbus_xfer = nforce2_access_virt1; | ||
190 | s4985_algo[2].smbus_xfer = nforce2_access_virt2; | ||
191 | s4985_algo[3].smbus_xfer = nforce2_access_virt3; | ||
192 | s4985_algo[4].smbus_xfer = nforce2_access_virt4; | ||
193 | |||
194 | /* Configure the PCA9556 multiplexer */ | ||
195 | ioconfig.byte = 0x00; /* All I/O to output mode */ | ||
196 | error = nforce2_smbus->algo->smbus_xfer(nforce2_smbus, 0x18, 0, | ||
197 | I2C_SMBUS_WRITE, 0x03, | ||
198 | I2C_SMBUS_BYTE_DATA, &ioconfig); | ||
199 | if (error) { | ||
200 | dev_err(&nforce2_smbus->dev, "PCA9556 configuration failed\n"); | ||
201 | error = -EIO; | ||
202 | goto ERROR3; | ||
203 | } | ||
204 | |||
205 | /* Register virtual adapters */ | ||
206 | for (i = 0; i < 5; i++) { | ||
207 | error = i2c_add_adapter(s4985_adapter + i); | ||
208 | if (error) { | ||
209 | dev_err(&nforce2_smbus->dev, | ||
210 | "Virtual adapter %d registration " | ||
211 | "failed, module not inserted\n", i); | ||
212 | for (i--; i >= 0; i--) | ||
213 | i2c_del_adapter(s4985_adapter + i); | ||
214 | goto ERROR3; | ||
215 | } | ||
216 | } | ||
217 | |||
218 | return 0; | ||
219 | |||
220 | ERROR3: | ||
221 | kfree(s4985_algo); | ||
222 | s4985_algo = NULL; | ||
223 | ERROR2: | ||
224 | kfree(s4985_adapter); | ||
225 | s4985_adapter = NULL; | ||
226 | ERROR1: | ||
227 | /* Restore physical bus */ | ||
228 | i2c_add_adapter(nforce2_smbus); | ||
229 | ERROR0: | ||
230 | return error; | ||
231 | } | ||
232 | |||
233 | static void __exit nforce2_s4985_exit(void) | ||
234 | { | ||
235 | if (s4985_adapter) { | ||
236 | int i; | ||
237 | |||
238 | for (i = 0; i < 5; i++) | ||
239 | i2c_del_adapter(s4985_adapter+i); | ||
240 | kfree(s4985_adapter); | ||
241 | s4985_adapter = NULL; | ||
242 | } | ||
243 | kfree(s4985_algo); | ||
244 | s4985_algo = NULL; | ||
245 | |||
246 | /* Restore physical bus */ | ||
247 | if (i2c_add_adapter(nforce2_smbus)) | ||
248 | dev_err(&nforce2_smbus->dev, "Physical bus restoration " | ||
249 | "failed\n"); | ||
250 | } | ||
251 | |||
252 | MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>"); | ||
253 | MODULE_DESCRIPTION("S4985 SMBus multiplexing"); | ||
254 | MODULE_LICENSE("GPL"); | ||
255 | |||
256 | module_init(nforce2_s4985_init); | ||
257 | module_exit(nforce2_s4985_exit); | ||
diff --git a/drivers/i2c/busses/i2c-nforce2.c b/drivers/i2c/busses/i2c-nforce2.c index 43c9f8df950..f95efff9b3d 100644 --- a/drivers/i2c/busses/i2c-nforce2.c +++ b/drivers/i2c/busses/i2c-nforce2.c | |||
@@ -124,6 +124,20 @@ static struct dmi_system_id __devinitdata nforce2_dmi_blacklist2[] = { | |||
124 | 124 | ||
125 | static struct pci_driver nforce2_driver; | 125 | static struct pci_driver nforce2_driver; |
126 | 126 | ||
127 | /* For multiplexing support, we need a global reference to the 1st | ||
128 | SMBus channel */ | ||
129 | #if defined CONFIG_I2C_NFORCE2_S4985 || defined CONFIG_I2C_NFORCE2_S4985_MODULE | ||
130 | struct i2c_adapter *nforce2_smbus; | ||
131 | EXPORT_SYMBOL_GPL(nforce2_smbus); | ||
132 | |||
133 | static void nforce2_set_reference(struct i2c_adapter *adap) | ||
134 | { | ||
135 | nforce2_smbus = adap; | ||
136 | } | ||
137 | #else | ||
138 | static inline void nforce2_set_reference(struct i2c_adapter *adap) { } | ||
139 | #endif | ||
140 | |||
127 | static void nforce2_abort(struct i2c_adapter *adap) | 141 | static void nforce2_abort(struct i2c_adapter *adap) |
128 | { | 142 | { |
129 | struct nforce2_smbus *smbus = adap->algo_data; | 143 | struct nforce2_smbus *smbus = adap->algo_data; |
@@ -398,6 +412,7 @@ static int __devinit nforce2_probe(struct pci_dev *dev, const struct pci_device_ | |||
398 | return -ENODEV; | 412 | return -ENODEV; |
399 | } | 413 | } |
400 | 414 | ||
415 | nforce2_set_reference(&smbuses[0].adapter); | ||
401 | return 0; | 416 | return 0; |
402 | } | 417 | } |
403 | 418 | ||
@@ -406,6 +421,7 @@ static void __devexit nforce2_remove(struct pci_dev *dev) | |||
406 | { | 421 | { |
407 | struct nforce2_smbus *smbuses = (void*) pci_get_drvdata(dev); | 422 | struct nforce2_smbus *smbuses = (void*) pci_get_drvdata(dev); |
408 | 423 | ||
424 | nforce2_set_reference(NULL); | ||
409 | if (smbuses[0].base) { | 425 | if (smbuses[0].base) { |
410 | i2c_del_adapter(&smbuses[0].adapter); | 426 | i2c_del_adapter(&smbuses[0].adapter); |
411 | release_region(smbuses[0].base, smbuses[0].size); | 427 | release_region(smbuses[0].base, smbuses[0].size); |