Rev 420 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
420 | giacomo | 1 | /* |
2 | i2c-sis630.c - Part of lm_sensors, Linux kernel modules for hardware |
||
3 | monitoring |
||
4 | |||
5 | Copyright (c) 2002,2003 Alexander Malysh <amalysh@web.de> |
||
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 | Changes: |
||
24 | 24.08.2002 |
||
25 | Fixed the typo in sis630_access (Thanks to Mark M. Hoffman) |
||
26 | Changed sis630_transaction.(Thanks to Mark M. Hoffman) |
||
27 | 18.09.2002 |
||
28 | Added SIS730 as supported. |
||
29 | 21.09.2002 |
||
30 | Added high_clock module option.If this option is set |
||
31 | used Host Master Clock 56KHz (default 14KHz).For now we save old Host |
||
32 | Master Clock and after transaction completed restore (otherwise |
||
33 | it's confuse BIOS and hung Machine). |
||
34 | 24.09.2002 |
||
35 | Fixed typo in sis630_access |
||
36 | Fixed logical error by restoring of Host Master Clock |
||
37 | 31.07.2003 |
||
38 | Added block data read/write support. |
||
39 | */ |
||
40 | |||
41 | /* |
||
42 | Status: beta |
||
43 | |||
44 | Supports: |
||
45 | SIS 630 |
||
46 | SIS 730 |
||
47 | |||
48 | Note: we assume there can only be one device, with one SMBus interface. |
||
49 | */ |
||
50 | |||
51 | /* #define DEBUG 1 */ |
||
52 | |||
53 | #include <linux/kernel.h> |
||
54 | #include <linux/module.h> |
||
55 | #include <linux/pci.h> |
||
56 | #include <linux/ioport.h> |
||
57 | #include <linux/init.h> |
||
58 | #include <linux/i2c.h> |
||
59 | #include <asm/io.h> |
||
60 | |||
61 | /* SIS630 SMBus registers */ |
||
62 | #define SMB_STS 0x80 /* status */ |
||
63 | #define SMB_EN 0x81 /* status enable */ |
||
64 | #define SMB_CNT 0x82 |
||
65 | #define SMBHOST_CNT 0x83 |
||
66 | #define SMB_ADDR 0x84 |
||
67 | #define SMB_CMD 0x85 |
||
68 | #define SMB_PCOUNT 0x86 /* processed count */ |
||
69 | #define SMB_COUNT 0x87 |
||
70 | #define SMB_BYTE 0x88 /* ~0x8F data byte field */ |
||
71 | #define SMBDEV_ADDR 0x90 |
||
72 | #define SMB_DB0 0x91 |
||
73 | #define SMB_DB1 0x92 |
||
74 | #define SMB_SAA 0x93 |
||
75 | |||
76 | /* register count for request_region */ |
||
77 | #define SIS630_SMB_IOREGION 20 |
||
78 | |||
79 | /* PCI address constants */ |
||
80 | /* acpi base address register */ |
||
81 | #define SIS630_ACPI_BASE_REG 0x74 |
||
82 | /* bios control register */ |
||
83 | #define SIS630_BIOS_CTL_REG 0x40 |
||
84 | |||
85 | /* Other settings */ |
||
86 | #define MAX_TIMEOUT 500 |
||
87 | |||
88 | /* SIS630 constants */ |
||
89 | #define SIS630_QUICK 0x00 |
||
90 | #define SIS630_BYTE 0x01 |
||
91 | #define SIS630_BYTE_DATA 0x02 |
||
92 | #define SIS630_WORD_DATA 0x03 |
||
93 | #define SIS630_PCALL 0x04 |
||
94 | #define SIS630_BLOCK_DATA 0x05 |
||
95 | |||
96 | /* insmod parameters */ |
||
97 | static int high_clock = 0; |
||
98 | static int force = 0; |
||
99 | MODULE_PARM(high_clock, "i"); |
||
100 | MODULE_PARM_DESC(high_clock, "Set Host Master Clock to 56KHz (default 14KHz)."); |
||
101 | MODULE_PARM(force, "i"); |
||
102 | MODULE_PARM_DESC(force, "Forcibly enable the SIS630. DANGEROUS!"); |
||
103 | |||
104 | /* acpi base address */ |
||
105 | static unsigned short acpi_base = 0; |
||
106 | |||
107 | /* supported chips */ |
||
108 | static int supported[] = { |
||
109 | PCI_DEVICE_ID_SI_630, |
||
110 | PCI_DEVICE_ID_SI_730, |
||
111 | |||
112 | }; |
||
113 | |||
114 | static inline u8 sis630_read(u8 reg) |
||
115 | { |
||
116 | return inb(acpi_base + reg); |
||
117 | } |
||
118 | |||
119 | static inline void sis630_write(u8 reg, u8 data) |
||
120 | { |
||
121 | outb(data, acpi_base + reg); |
||
122 | } |
||
123 | |||
124 | static int sis630_transaction_start(struct i2c_adapter *adap, int size, u8 *oldclock) |
||
125 | { |
||
126 | int temp; |
||
127 | |||
128 | /* Make sure the SMBus host is ready to start transmitting. */ |
||
129 | if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) { |
||
130 | dev_dbg(&adap->dev, "SMBus busy (%02x).Resetting...\n",temp); |
||
131 | /* kill smbus transaction */ |
||
132 | sis630_write(SMBHOST_CNT, 0x20); |
||
133 | |||
134 | if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) { |
||
135 | dev_dbg(&adap->dev, "Failed! (%02x)\n", temp); |
||
136 | return -1; |
||
137 | } else { |
||
138 | dev_dbg(&adap->dev, "Successfull!\n"); |
||
139 | } |
||
140 | } |
||
141 | |||
142 | /* save old clock, so we can prevent machine for hung */ |
||
143 | *oldclock = sis630_read(SMB_CNT); |
||
144 | |||
145 | dev_dbg(&adap->dev, "saved clock 0x%02x\n", *oldclock); |
||
146 | |||
147 | /* disable timeout interrupt , set Host Master Clock to 56KHz if requested */ |
||
148 | if (high_clock > 0) |
||
149 | sis630_write(SMB_CNT, 0x20); |
||
150 | else |
||
151 | sis630_write(SMB_CNT, (*oldclock & ~0x40)); |
||
152 | |||
153 | /* clear all sticky bits */ |
||
154 | temp = sis630_read(SMB_STS); |
||
155 | sis630_write(SMB_STS, temp & 0x1e); |
||
156 | |||
157 | /* start the transaction by setting bit 4 and size */ |
||
158 | sis630_write(SMBHOST_CNT,0x10 | (size & 0x07)); |
||
159 | |||
160 | return 0; |
||
161 | } |
||
162 | |||
163 | static int sis630_transaction_wait(struct i2c_adapter *adap, int size) |
||
164 | { |
||
165 | int temp, result = 0, timeout = 0; |
||
166 | |||
167 | /* We will always wait for a fraction of a second! */ |
||
168 | do { |
||
169 | i2c_delay(1); |
||
170 | temp = sis630_read(SMB_STS); |
||
171 | /* check if block transmitted */ |
||
172 | if (size == SIS630_BLOCK_DATA && (temp & 0x10)) |
||
173 | break; |
||
174 | } while (!(temp & 0x0e) && (timeout++ < MAX_TIMEOUT)); |
||
175 | |||
176 | /* If the SMBus is still busy, we give up */ |
||
177 | if (timeout >= MAX_TIMEOUT) { |
||
178 | dev_dbg(&adap->dev, "SMBus Timeout!\n"); |
||
179 | result = -1; |
||
180 | } |
||
181 | |||
182 | if (temp & 0x02) { |
||
183 | dev_dbg(&adap->dev, "Error: Failed bus transaction\n"); |
||
184 | result = -1; |
||
185 | } |
||
186 | |||
187 | if (temp & 0x04) { |
||
188 | dev_err(&adap->dev, "Bus collision!\n"); |
||
189 | result = -1; |
||
190 | /* |
||
191 | TBD: Datasheet say: |
||
192 | the software should clear this bit and restart SMBUS operation. |
||
193 | Should we do it or user start request again? |
||
194 | */ |
||
195 | } |
||
196 | |||
197 | return result; |
||
198 | } |
||
199 | |||
200 | static void sis630_transaction_end(struct i2c_adapter *adap, u8 oldclock) |
||
201 | { |
||
202 | int temp = 0; |
||
203 | |||
204 | /* clear all status "sticky" bits */ |
||
205 | sis630_write(SMB_STS, temp); |
||
206 | |||
207 | dev_dbg(&adap->dev, "SMB_CNT before clock restore 0x%02x\n", sis630_read(SMB_CNT)); |
||
208 | |||
209 | /* |
||
210 | * restore old Host Master Clock if high_clock is set |
||
211 | * and oldclock was not 56KHz |
||
212 | */ |
||
213 | if (high_clock > 0 && !(oldclock & 0x20)) |
||
214 | sis630_write(SMB_CNT,(sis630_read(SMB_CNT) & ~0x20)); |
||
215 | |||
216 | dev_dbg(&adap->dev, "SMB_CNT after clock restore 0x%02x\n", sis630_read(SMB_CNT)); |
||
217 | } |
||
218 | |||
219 | static int sis630_transaction(struct i2c_adapter *adap, int size) |
||
220 | { |
||
221 | int result = 0; |
||
222 | u8 oldclock = 0; |
||
223 | |||
224 | result = sis630_transaction_start(adap, size, &oldclock); |
||
225 | if (!result) { |
||
226 | result = sis630_transaction_wait(adap, size); |
||
227 | sis630_transaction_end(adap, oldclock); |
||
228 | } |
||
229 | |||
230 | return result; |
||
231 | } |
||
232 | |||
233 | static int sis630_block_data(struct i2c_adapter *adap, union i2c_smbus_data *data, int read_write) |
||
234 | { |
||
235 | int i, len = 0, rc = 0; |
||
236 | u8 oldclock = 0; |
||
237 | |||
238 | if (read_write == I2C_SMBUS_WRITE) { |
||
239 | len = data->block[0]; |
||
240 | if (len < 0) |
||
241 | len = 0; |
||
242 | else if (len > 32) |
||
243 | len = 32; |
||
244 | sis630_write(SMB_COUNT, len); |
||
245 | for (i=1; i <= len; i++) { |
||
246 | dev_dbg(&adap->dev, "set data 0x%02x\n", data->block[i]); |
||
247 | /* set data */ |
||
248 | sis630_write(SMB_BYTE+(i-1)%8, data->block[i]); |
||
249 | if (i==8 || (len<8 && i==len)) { |
||
250 | dev_dbg(&adap->dev, "start trans len=%d i=%d\n",len ,i); |
||
251 | /* first transaction */ |
||
252 | if (sis630_transaction_start(adap, SIS630_BLOCK_DATA, &oldclock)) |
||
253 | return -1; |
||
254 | } |
||
255 | else if ((i-1)%8 == 7 || i==len) { |
||
256 | dev_dbg(&adap->dev, "trans_wait len=%d i=%d\n",len,i); |
||
257 | if (i>8) { |
||
258 | dev_dbg(&adap->dev, "clear smbary_sts len=%d i=%d\n",len,i); |
||
259 | /* |
||
260 | If this is not first transaction, |
||
261 | we must clear sticky bit. |
||
262 | clear SMBARY_STS |
||
263 | */ |
||
264 | sis630_write(SMB_STS,0x10); |
||
265 | } |
||
266 | if (sis630_transaction_wait(adap, SIS630_BLOCK_DATA)) { |
||
267 | dev_dbg(&adap->dev, "trans_wait failed\n"); |
||
268 | rc = -1; |
||
269 | break; |
||
270 | } |
||
271 | } |
||
272 | } |
||
273 | } |
||
274 | else { |
||
275 | /* read request */ |
||
276 | data->block[0] = len = 0; |
||
277 | if (sis630_transaction_start(adap, SIS630_BLOCK_DATA, &oldclock)) { |
||
278 | return -1; |
||
279 | } |
||
280 | do { |
||
281 | if (sis630_transaction_wait(adap, SIS630_BLOCK_DATA)) { |
||
282 | dev_dbg(&adap->dev, "trans_wait failed\n"); |
||
283 | rc = -1; |
||
284 | break; |
||
285 | } |
||
286 | /* if this first transaction then read byte count */ |
||
287 | if (len == 0) |
||
288 | data->block[0] = sis630_read(SMB_COUNT); |
||
289 | |||
290 | /* just to be sure */ |
||
291 | if (data->block[0] > 32) |
||
292 | data->block[0] = 32; |
||
293 | |||
294 | dev_dbg(&adap->dev, "block data read len=0x%x\n", data->block[0]); |
||
295 | |||
296 | for (i=0; i < 8 && len < data->block[0]; i++,len++) { |
||
297 | dev_dbg(&adap->dev, "read i=%d len=%d\n", i, len); |
||
298 | data->block[len+1] = sis630_read(SMB_BYTE+i); |
||
299 | } |
||
300 | |||
301 | dev_dbg(&adap->dev, "clear smbary_sts len=%d i=%d\n",len,i); |
||
302 | |||
303 | /* clear SMBARY_STS */ |
||
304 | sis630_write(SMB_STS,0x10); |
||
305 | } while(len < data->block[0]); |
||
306 | } |
||
307 | |||
308 | sis630_transaction_end(adap, oldclock); |
||
309 | |||
310 | return rc; |
||
311 | } |
||
312 | |||
313 | /* Return -1 on error. */ |
||
314 | static s32 sis630_access(struct i2c_adapter *adap, u16 addr, |
||
315 | unsigned short flags, char read_write, |
||
316 | u8 command, int size, union i2c_smbus_data *data) |
||
317 | { |
||
318 | switch (size) { |
||
319 | case I2C_SMBUS_QUICK: |
||
320 | sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); |
||
321 | size = SIS630_QUICK; |
||
322 | break; |
||
323 | case I2C_SMBUS_BYTE: |
||
324 | sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); |
||
325 | if (read_write == I2C_SMBUS_WRITE) |
||
326 | sis630_write(SMB_CMD, command); |
||
327 | size = SIS630_BYTE; |
||
328 | break; |
||
329 | case I2C_SMBUS_BYTE_DATA: |
||
330 | sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); |
||
331 | sis630_write(SMB_CMD, command); |
||
332 | if (read_write == I2C_SMBUS_WRITE) |
||
333 | sis630_write(SMB_BYTE, data->byte); |
||
334 | size = SIS630_BYTE_DATA; |
||
335 | break; |
||
336 | case I2C_SMBUS_PROC_CALL: |
||
337 | case I2C_SMBUS_WORD_DATA: |
||
338 | sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01)); |
||
339 | sis630_write(SMB_CMD, command); |
||
340 | if (read_write == I2C_SMBUS_WRITE) { |
||
341 | sis630_write(SMB_BYTE, data->word & 0xff); |
||
342 | sis630_write(SMB_BYTE + 1,(data->word & 0xff00) >> 8); |
||
343 | } |
||
344 | size = (size == I2C_SMBUS_PROC_CALL ? SIS630_PCALL : SIS630_WORD_DATA); |
||
345 | break; |
||
346 | case I2C_SMBUS_BLOCK_DATA: |
||
347 | sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01)); |
||
348 | sis630_write(SMB_CMD, command); |
||
349 | size = SIS630_BLOCK_DATA; |
||
350 | return sis630_block_data(adap, data, read_write); |
||
351 | default: |
||
352 | printk("Unsupported I2C size\n"); |
||
353 | return -1; |
||
354 | break; |
||
355 | } |
||
356 | |||
357 | if (sis630_transaction(adap, size)) |
||
358 | return -1; |
||
359 | |||
360 | if ((size != SIS630_PCALL) && |
||
361 | ((read_write == I2C_SMBUS_WRITE) || (size == SIS630_QUICK))) { |
||
362 | return 0; |
||
363 | } |
||
364 | |||
365 | switch(size) { |
||
366 | case SIS630_BYTE: |
||
367 | case SIS630_BYTE_DATA: |
||
368 | data->byte = sis630_read(SMB_BYTE); |
||
369 | break; |
||
370 | case SIS630_PCALL: |
||
371 | case SIS630_WORD_DATA: |
||
372 | data->word = sis630_read(SMB_BYTE) + (sis630_read(SMB_BYTE + 1) << 8); |
||
373 | break; |
||
374 | default: |
||
375 | return -1; |
||
376 | break; |
||
377 | } |
||
378 | |||
379 | return 0; |
||
380 | } |
||
381 | |||
382 | static u32 sis630_func(struct i2c_adapter *adapter) |
||
383 | { |
||
384 | return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | |
||
385 | I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_PROC_CALL | |
||
386 | I2C_FUNC_SMBUS_BLOCK_DATA; |
||
387 | } |
||
388 | |||
389 | static int sis630_setup(struct pci_dev *sis630_dev) |
||
390 | { |
||
391 | unsigned char b; |
||
392 | struct pci_dev *dummy = NULL; |
||
393 | int retval = -ENODEV, i; |
||
394 | |||
395 | /* check for supported SiS devices */ |
||
396 | for (i=0; supported[i] > 0 ; i++) { |
||
397 | if ((dummy = pci_get_device(PCI_VENDOR_ID_SI, supported[i], dummy))) |
||
398 | break; /* found */ |
||
399 | } |
||
400 | |||
401 | if (dummy) { |
||
402 | pci_dev_put(dummy); |
||
403 | } |
||
404 | else if (force > 0) { |
||
405 | dev_err(&sis630_dev->dev, "WARNING: Can't detect SIS630 compatible device, but " |
||
406 | "loading because of force option enabled\n"); |
||
407 | } |
||
408 | else { |
||
409 | return -ENODEV; |
||
410 | } |
||
411 | |||
412 | /* |
||
413 | Enable ACPI first , so we can accsess reg 74-75 |
||
414 | in acpi io space and read acpi base addr |
||
415 | */ |
||
416 | if (pci_read_config_byte(sis630_dev, SIS630_BIOS_CTL_REG,&b)) { |
||
417 | dev_err(&sis630_dev->dev, "Error: Can't read bios ctl reg\n"); |
||
418 | goto exit; |
||
419 | } |
||
420 | /* if ACPI already enabled , do nothing */ |
||
421 | if (!(b & 0x80) && |
||
422 | pci_write_config_byte(sis630_dev, SIS630_BIOS_CTL_REG, b | 0x80)) { |
||
423 | dev_err(&sis630_dev->dev, "Error: Can't enable ACPI\n"); |
||
424 | goto exit; |
||
425 | } |
||
426 | |||
427 | /* Determine the ACPI base address */ |
||
428 | if (pci_read_config_word(sis630_dev,SIS630_ACPI_BASE_REG,&acpi_base)) { |
||
429 | dev_err(&sis630_dev->dev, "Error: Can't determine ACPI base address\n"); |
||
430 | goto exit; |
||
431 | } |
||
432 | |||
433 | dev_dbg(&sis630_dev->dev, "ACPI base at 0x%04x\n", acpi_base); |
||
434 | |||
435 | /* Everything is happy, let's grab the memory and set things up. */ |
||
436 | if (!request_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION, "sis630-smbus")) { |
||
437 | dev_err(&sis630_dev->dev, "SMBus registers 0x%04x-0x%04x already " |
||
438 | "in use!\n", acpi_base + SMB_STS, acpi_base + SMB_SAA); |
||
439 | goto exit; |
||
440 | } |
||
441 | |||
442 | retval = 0; |
||
443 | |||
444 | exit: |
||
445 | if (retval) |
||
446 | acpi_base = 0; |
||
447 | return retval; |
||
448 | } |
||
449 | |||
450 | |||
451 | static struct i2c_algorithm smbus_algorithm = { |
||
452 | .name = "Non-I2C SMBus adapter", |
||
453 | .id = I2C_ALGO_SMBUS, |
||
454 | .smbus_xfer = sis630_access, |
||
455 | .functionality = sis630_func, |
||
456 | }; |
||
457 | |||
458 | static struct i2c_adapter sis630_adapter = { |
||
459 | .owner = THIS_MODULE, |
||
460 | .class = I2C_ADAP_CLASS_SMBUS, |
||
461 | .name = "unset", |
||
462 | .algo = &smbus_algorithm, |
||
463 | }; |
||
464 | |||
465 | static struct pci_device_id sis630_ids[] __devinitdata = { |
||
466 | { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_503) }, |
||
467 | { 0, } |
||
468 | }; |
||
469 | |||
470 | static int __devinit sis630_probe(struct pci_dev *dev, const struct pci_device_id *id) |
||
471 | { |
||
472 | if (sis630_setup(dev)) { |
||
473 | dev_err(&dev->dev, "SIS630 comp. bus not detected, module not inserted.\n"); |
||
474 | return -ENODEV; |
||
475 | } |
||
476 | |||
477 | /* set up the driverfs linkage to our parent device */ |
||
478 | sis630_adapter.dev.parent = &dev->dev; |
||
479 | |||
480 | sprintf(sis630_adapter.name, "SMBus SIS630 adapter at %04x", |
||
481 | acpi_base + SMB_STS); |
||
482 | |||
483 | return i2c_add_adapter(&sis630_adapter); |
||
484 | } |
||
485 | |||
486 | static void __devexit sis630_remove(struct pci_dev *dev) |
||
487 | { |
||
488 | if (acpi_base) { |
||
489 | i2c_del_adapter(&sis630_adapter); |
||
490 | release_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION); |
||
491 | acpi_base = 0; |
||
492 | } |
||
493 | } |
||
494 | |||
495 | |||
496 | static struct pci_driver sis630_driver = { |
||
497 | .name = "sis630 smbus", |
||
498 | .id_table = sis630_ids, |
||
499 | .probe = sis630_probe, |
||
500 | .remove = __devexit_p(sis630_remove), |
||
501 | }; |
||
502 | |||
503 | static int __init i2c_sis630_init(void) |
||
504 | { |
||
505 | return pci_module_init(&sis630_driver); |
||
506 | } |
||
507 | |||
508 | |||
509 | static void __exit i2c_sis630_exit(void) |
||
510 | { |
||
511 | pci_unregister_driver(&sis630_driver); |
||
512 | } |
||
513 | |||
514 | |||
515 | MODULE_LICENSE("GPL"); |
||
516 | MODULE_AUTHOR("Alexander Malysh <amalysh@web.de>"); |
||
517 | MODULE_DESCRIPTION("SIS630 SMBus driver"); |
||
518 | |||
519 | module_init(i2c_sis630_init); |
||
520 | module_exit(i2c_sis630_exit); |