Rev 420 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
420 | giacomo | 1 | /* |
2 | sis96x.c - Part of lm_sensors, Linux kernel modules for hardware |
||
3 | monitoring |
||
4 | |||
5 | Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com> |
||
6 | |||
7 | This program is free software; you can redistribute it and/or modify |
||
8 | it under the terms of the GNU General Public License as published by |
||
9 | the Free Software Foundation; either version 2 of the License, or |
||
10 | (at your option) any later version. |
||
11 | |||
12 | This program is distributed in the hope that it will be useful, |
||
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
15 | GNU General Public License for more details. |
||
16 | |||
17 | You should have received a copy of the GNU General Public License |
||
18 | along with this program; if not, write to the Free Software |
||
19 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
||
20 | */ |
||
21 | |||
22 | /* |
||
23 | This module must be considered BETA unless and until |
||
24 | the chipset manufacturer releases a datasheet. |
||
25 | The register definitions are based on the SiS630. |
||
26 | |||
27 | This module relies on quirk_sis_96x_smbus (drivers/pci/quirks.c) |
||
28 | for just about every machine for which users have reported. |
||
29 | If this module isn't detecting your 96x south bridge, have a |
||
30 | look there. |
||
31 | |||
32 | We assume there can only be one SiS96x with one SMBus interface. |
||
33 | */ |
||
34 | |||
35 | /* #define DEBUG */ |
||
36 | |||
37 | #include <linux/module.h> |
||
38 | #include <linux/pci.h> |
||
39 | #include <linux/kernel.h> |
||
40 | #include <linux/stddef.h> |
||
41 | #include <linux/sched.h> |
||
42 | #include <linux/ioport.h> |
||
43 | #include <linux/i2c.h> |
||
44 | #include <linux/init.h> |
||
45 | #include <asm/io.h> |
||
46 | |||
47 | /* |
||
48 | HISTORY: |
||
49 | 2003-05-11 1.0.0 Updated from lm_sensors project for kernel 2.5 |
||
50 | (was i2c-sis645.c from lm_sensors 2.7.0) |
||
51 | */ |
||
52 | #define SIS96x_VERSION "1.0.0" |
||
53 | |||
54 | /* SiS96x SMBus PCI device ID */ |
||
55 | #define PCI_DEVICE_ID_SI_SMBUS 0x16 |
||
56 | |||
57 | /* base address register in PCI config space */ |
||
58 | #define SIS96x_BAR 0x04 |
||
59 | |||
60 | /* SiS96x SMBus registers */ |
||
61 | #define SMB_STS 0x00 |
||
62 | #define SMB_EN 0x01 |
||
63 | #define SMB_CNT 0x02 |
||
64 | #define SMB_HOST_CNT 0x03 |
||
65 | #define SMB_ADDR 0x04 |
||
66 | #define SMB_CMD 0x05 |
||
67 | #define SMB_PCOUNT 0x06 |
||
68 | #define SMB_COUNT 0x07 |
||
69 | #define SMB_BYTE 0x08 |
||
70 | #define SMB_DEV_ADDR 0x10 |
||
71 | #define SMB_DB0 0x11 |
||
72 | #define SMB_DB1 0x12 |
||
73 | #define SMB_SAA 0x13 |
||
74 | |||
75 | /* register count for request_region */ |
||
76 | #define SMB_IOSIZE 0x20 |
||
77 | |||
78 | /* Other settings */ |
||
79 | #define MAX_TIMEOUT 500 |
||
80 | |||
81 | /* SiS96x SMBus constants */ |
||
82 | #define SIS96x_QUICK 0x00 |
||
83 | #define SIS96x_BYTE 0x01 |
||
84 | #define SIS96x_BYTE_DATA 0x02 |
||
85 | #define SIS96x_WORD_DATA 0x03 |
||
86 | #define SIS96x_PROC_CALL 0x04 |
||
87 | #define SIS96x_BLOCK_DATA 0x05 |
||
88 | |||
89 | static struct i2c_adapter sis96x_adapter; |
||
90 | static u16 sis96x_smbus_base = 0; |
||
91 | |||
92 | static inline u8 sis96x_read(u8 reg) |
||
93 | { |
||
94 | return inb(sis96x_smbus_base + reg) ; |
||
95 | } |
||
96 | |||
97 | static inline void sis96x_write(u8 reg, u8 data) |
||
98 | { |
||
99 | outb(data, sis96x_smbus_base + reg) ; |
||
100 | } |
||
101 | |||
102 | /* Execute a SMBus transaction. |
||
103 | int size is from SIS96x_QUICK to SIS96x_BLOCK_DATA |
||
104 | */ |
||
105 | static int sis96x_transaction(int size) |
||
106 | { |
||
107 | int temp; |
||
108 | int result = 0; |
||
109 | int timeout = 0; |
||
110 | |||
111 | dev_dbg(&sis96x_adapter.dev, "SMBus transaction %d\n", size); |
||
112 | |||
113 | /* Make sure the SMBus host is ready to start transmitting */ |
||
114 | if (((temp = sis96x_read(SMB_CNT)) & 0x03) != 0x00) { |
||
115 | |||
116 | dev_dbg(&sis96x_adapter.dev, "SMBus busy (0x%02x). " |
||
117 | "Resetting...\n", temp); |
||
118 | |||
119 | /* kill the transaction */ |
||
120 | sis96x_write(SMB_HOST_CNT, 0x20); |
||
121 | |||
122 | /* check it again */ |
||
123 | if (((temp = sis96x_read(SMB_CNT)) & 0x03) != 0x00) { |
||
124 | dev_dbg(&sis96x_adapter.dev, "Failed (0x%02x)\n", temp); |
||
125 | return -1; |
||
126 | } else { |
||
127 | dev_dbg(&sis96x_adapter.dev, "Successful\n"); |
||
128 | } |
||
129 | } |
||
130 | |||
131 | /* Turn off timeout interrupts, set fast host clock */ |
||
132 | sis96x_write(SMB_CNT, 0x20); |
||
133 | |||
134 | /* clear all (sticky) status flags */ |
||
135 | temp = sis96x_read(SMB_STS); |
||
136 | sis96x_write(SMB_STS, temp & 0x1e); |
||
137 | |||
138 | /* start the transaction by setting bit 4 and size bits */ |
||
139 | sis96x_write(SMB_HOST_CNT, 0x10 | (size & 0x07)); |
||
140 | |||
141 | /* We will always wait for a fraction of a second! */ |
||
142 | do { |
||
143 | i2c_delay(1); |
||
144 | temp = sis96x_read(SMB_STS); |
||
145 | } while (!(temp & 0x0e) && (timeout++ < MAX_TIMEOUT)); |
||
146 | |||
147 | /* If the SMBus is still busy, we give up */ |
||
148 | if (timeout >= MAX_TIMEOUT) { |
||
149 | dev_dbg(&sis96x_adapter.dev, "SMBus Timeout! (0x%02x)\n", temp); |
||
150 | result = -1; |
||
151 | } |
||
152 | |||
153 | /* device error - probably missing ACK */ |
||
154 | if (temp & 0x02) { |
||
155 | dev_dbg(&sis96x_adapter.dev, "Failed bus transaction!\n"); |
||
156 | result = -1; |
||
157 | } |
||
158 | |||
159 | /* bus collision */ |
||
160 | if (temp & 0x04) { |
||
161 | dev_dbg(&sis96x_adapter.dev, "Bus collision!\n"); |
||
162 | result = -1; |
||
163 | } |
||
164 | |||
165 | /* Finish up by resetting the bus */ |
||
166 | sis96x_write(SMB_STS, temp); |
||
167 | if ((temp = sis96x_read(SMB_STS))) { |
||
168 | dev_dbg(&sis96x_adapter.dev, "Failed reset at " |
||
169 | "end of transaction! (0x%02x)\n", temp); |
||
170 | } |
||
171 | |||
172 | return result; |
||
173 | } |
||
174 | |||
175 | /* Return -1 on error. */ |
||
176 | static s32 sis96x_access(struct i2c_adapter * adap, u16 addr, |
||
177 | unsigned short flags, char read_write, |
||
178 | u8 command, int size, union i2c_smbus_data * data) |
||
179 | { |
||
180 | |||
181 | switch (size) { |
||
182 | case I2C_SMBUS_QUICK: |
||
183 | sis96x_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); |
||
184 | size = SIS96x_QUICK; |
||
185 | break; |
||
186 | |||
187 | case I2C_SMBUS_BYTE: |
||
188 | sis96x_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); |
||
189 | if (read_write == I2C_SMBUS_WRITE) |
||
190 | sis96x_write(SMB_CMD, command); |
||
191 | size = SIS96x_BYTE; |
||
192 | break; |
||
193 | |||
194 | case I2C_SMBUS_BYTE_DATA: |
||
195 | sis96x_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); |
||
196 | sis96x_write(SMB_CMD, command); |
||
197 | if (read_write == I2C_SMBUS_WRITE) |
||
198 | sis96x_write(SMB_BYTE, data->byte); |
||
199 | size = SIS96x_BYTE_DATA; |
||
200 | break; |
||
201 | |||
202 | case I2C_SMBUS_PROC_CALL: |
||
203 | case I2C_SMBUS_WORD_DATA: |
||
204 | sis96x_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); |
||
205 | sis96x_write(SMB_CMD, command); |
||
206 | if (read_write == I2C_SMBUS_WRITE) { |
||
207 | sis96x_write(SMB_BYTE, data->word & 0xff); |
||
208 | sis96x_write(SMB_BYTE + 1, (data->word & 0xff00) >> 8); |
||
209 | } |
||
210 | size = (size == I2C_SMBUS_PROC_CALL ? |
||
211 | SIS96x_PROC_CALL : SIS96x_WORD_DATA); |
||
212 | break; |
||
213 | |||
214 | case I2C_SMBUS_BLOCK_DATA: |
||
215 | /* TO DO: */ |
||
216 | dev_info(&adap->dev, "SMBus block not implemented!\n"); |
||
217 | return -1; |
||
218 | break; |
||
219 | |||
220 | default: |
||
221 | dev_info(&adap->dev, "Unsupported I2C size\n"); |
||
222 | return -1; |
||
223 | break; |
||
224 | } |
||
225 | |||
226 | if (sis96x_transaction(size)) |
||
227 | return -1; |
||
228 | |||
229 | if ((size != SIS96x_PROC_CALL) && |
||
230 | ((read_write == I2C_SMBUS_WRITE) || (size == SIS96x_QUICK))) |
||
231 | return 0; |
||
232 | |||
233 | switch (size) { |
||
234 | case SIS96x_BYTE: |
||
235 | case SIS96x_BYTE_DATA: |
||
236 | data->byte = sis96x_read(SMB_BYTE); |
||
237 | break; |
||
238 | |||
239 | case SIS96x_WORD_DATA: |
||
240 | case SIS96x_PROC_CALL: |
||
241 | data->word = sis96x_read(SMB_BYTE) + |
||
242 | (sis96x_read(SMB_BYTE + 1) << 8); |
||
243 | break; |
||
244 | } |
||
245 | return 0; |
||
246 | } |
||
247 | |||
248 | static u32 sis96x_func(struct i2c_adapter *adapter) |
||
249 | { |
||
250 | return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | |
||
251 | I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | |
||
252 | I2C_FUNC_SMBUS_PROC_CALL; |
||
253 | } |
||
254 | |||
255 | static struct i2c_algorithm smbus_algorithm = { |
||
256 | .name = "Non-I2C SMBus adapter", |
||
257 | .id = I2C_ALGO_SMBUS, |
||
258 | .smbus_xfer = sis96x_access, |
||
259 | .functionality = sis96x_func, |
||
260 | }; |
||
261 | |||
262 | static struct i2c_adapter sis96x_adapter = { |
||
263 | .owner = THIS_MODULE, |
||
264 | .class = I2C_ADAP_CLASS_SMBUS, |
||
265 | .algo = &smbus_algorithm, |
||
266 | .name = "unset", |
||
267 | }; |
||
268 | |||
269 | static struct pci_device_id sis96x_ids[] = { |
||
270 | |||
271 | { |
||
272 | .vendor = PCI_VENDOR_ID_SI, |
||
273 | .device = PCI_DEVICE_ID_SI_SMBUS, |
||
274 | .subvendor = PCI_ANY_ID, |
||
275 | .subdevice = PCI_ANY_ID, |
||
276 | }, |
||
277 | |||
278 | { 0, } |
||
279 | }; |
||
280 | |||
281 | static int __devinit sis96x_probe(struct pci_dev *dev, |
||
282 | const struct pci_device_id *id) |
||
283 | { |
||
284 | u16 ww = 0; |
||
285 | int retval; |
||
286 | |||
287 | if (sis96x_smbus_base) { |
||
288 | dev_err(&dev->dev, "Only one device supported.\n"); |
||
289 | return -EBUSY; |
||
290 | } |
||
291 | |||
292 | pci_read_config_word(dev, PCI_CLASS_DEVICE, &ww); |
||
293 | if (PCI_CLASS_SERIAL_SMBUS != ww) { |
||
294 | dev_err(&dev->dev, "Unsupported device class 0x%04x!\n", ww); |
||
295 | return -ENODEV; |
||
296 | } |
||
297 | |||
298 | sis96x_smbus_base = pci_resource_start(dev, SIS96x_BAR); |
||
299 | if (!sis96x_smbus_base) { |
||
300 | dev_err(&dev->dev, "SiS96x SMBus base address " |
||
301 | "not initialized!\n"); |
||
302 | return -EINVAL; |
||
303 | } |
||
304 | dev_info(&dev->dev, "SiS96x SMBus base address: 0x%04x\n", |
||
305 | sis96x_smbus_base); |
||
306 | |||
307 | /* Everything is happy, let's grab the memory and set things up. */ |
||
308 | if (!request_region(sis96x_smbus_base, SMB_IOSIZE, "sis96x-smbus")) { |
||
309 | dev_err(&dev->dev, "SMBus registers 0x%04x-0x%04x " |
||
310 | "already in use!\n", sis96x_smbus_base, |
||
311 | sis96x_smbus_base + SMB_IOSIZE - 1); |
||
312 | |||
313 | sis96x_smbus_base = 0; |
||
314 | return -EINVAL; |
||
315 | } |
||
316 | |||
317 | /* set up the driverfs linkage to our parent device */ |
||
318 | sis96x_adapter.dev.parent = &dev->dev; |
||
319 | |||
320 | snprintf(sis96x_adapter.name, I2C_NAME_SIZE, |
||
321 | "SiS96x SMBus adapter at 0x%04x", sis96x_smbus_base); |
||
322 | |||
323 | if ((retval = i2c_add_adapter(&sis96x_adapter))) { |
||
324 | dev_err(&dev->dev, "Couldn't register adapter!\n"); |
||
325 | release_region(sis96x_smbus_base, SMB_IOSIZE); |
||
326 | sis96x_smbus_base = 0; |
||
327 | } |
||
328 | |||
329 | return retval; |
||
330 | } |
||
331 | |||
332 | static void __devexit sis96x_remove(struct pci_dev *dev) |
||
333 | { |
||
334 | if (sis96x_smbus_base) { |
||
335 | i2c_del_adapter(&sis96x_adapter); |
||
336 | release_region(sis96x_smbus_base, SMB_IOSIZE); |
||
337 | sis96x_smbus_base = 0; |
||
338 | } |
||
339 | } |
||
340 | |||
341 | static struct pci_driver sis96x_driver = { |
||
342 | .name = "sis96x smbus", |
||
343 | .id_table = sis96x_ids, |
||
344 | .probe = sis96x_probe, |
||
345 | .remove = __devexit_p(sis96x_remove), |
||
346 | }; |
||
347 | |||
348 | static int __init i2c_sis96x_init(void) |
||
349 | { |
||
350 | printk(KERN_INFO "i2c-sis96x version %s\n", SIS96x_VERSION); |
||
351 | return pci_module_init(&sis96x_driver); |
||
352 | } |
||
353 | |||
354 | static void __exit i2c_sis96x_exit(void) |
||
355 | { |
||
356 | pci_unregister_driver(&sis96x_driver); |
||
357 | } |
||
358 | |||
359 | MODULE_AUTHOR("Mark M. Hoffman <mhoffman@lightlink.com>"); |
||
360 | MODULE_DESCRIPTION("SiS96x SMBus driver"); |
||
361 | MODULE_LICENSE("GPL"); |
||
362 | |||
363 | /* Register initialization functions using helper macros */ |
||
364 | module_init(i2c_sis96x_init); |
||
365 | module_exit(i2c_sis96x_exit); |
||
366 |