blob: c4cc5eddf50dc10f0f658833d693c3db85a81952 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
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
Linus Torvalds1da177e2005-04-16 15:20:36 -070051#include <linux/kernel.h>
52#include <linux/module.h>
53#include <linux/delay.h>
54#include <linux/pci.h>
55#include <linux/ioport.h>
56#include <linux/init.h>
57#include <linux/i2c.h>
58#include <asm/io.h>
59
60/* SIS630 SMBus registers */
61#define SMB_STS 0x80 /* status */
62#define SMB_EN 0x81 /* status enable */
63#define SMB_CNT 0x82
64#define SMBHOST_CNT 0x83
65#define SMB_ADDR 0x84
66#define SMB_CMD 0x85
67#define SMB_PCOUNT 0x86 /* processed count */
68#define SMB_COUNT 0x87
69#define SMB_BYTE 0x88 /* ~0x8F data byte field */
70#define SMBDEV_ADDR 0x90
71#define SMB_DB0 0x91
72#define SMB_DB1 0x92
73#define SMB_SAA 0x93
74
75/* register count for request_region */
76#define SIS630_SMB_IOREGION 20
77
78/* PCI address constants */
79/* acpi base address register */
80#define SIS630_ACPI_BASE_REG 0x74
81/* bios control register */
82#define SIS630_BIOS_CTL_REG 0x40
83
84/* Other settings */
85#define MAX_TIMEOUT 500
86
87/* SIS630 constants */
88#define SIS630_QUICK 0x00
89#define SIS630_BYTE 0x01
90#define SIS630_BYTE_DATA 0x02
91#define SIS630_WORD_DATA 0x03
92#define SIS630_PCALL 0x04
93#define SIS630_BLOCK_DATA 0x05
94
Jean Delvared6072f82005-09-25 16:37:04 +020095static struct pci_driver sis630_driver;
96
Linus Torvalds1da177e2005-04-16 15:20:36 -070097/* insmod parameters */
98static int high_clock;
99static int force;
100module_param(high_clock, bool, 0);
101MODULE_PARM_DESC(high_clock, "Set Host Master Clock to 56KHz (default 14KHz).");
102module_param(force, bool, 0);
103MODULE_PARM_DESC(force, "Forcibly enable the SIS630. DANGEROUS!");
104
105/* acpi base address */
Jean Delvare60507092005-09-25 16:23:07 +0200106static unsigned short acpi_base;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700107
108/* supported chips */
109static int supported[] = {
110 PCI_DEVICE_ID_SI_630,
111 PCI_DEVICE_ID_SI_730,
112 0 /* terminates the list */
113};
114
115static inline u8 sis630_read(u8 reg)
116{
117 return inb(acpi_base + reg);
118}
119
120static inline void sis630_write(u8 reg, u8 data)
121{
122 outb(data, acpi_base + reg);
123}
124
125static int sis630_transaction_start(struct i2c_adapter *adap, int size, u8 *oldclock)
126{
127 int temp;
128
129 /* Make sure the SMBus host is ready to start transmitting. */
130 if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) {
131 dev_dbg(&adap->dev, "SMBus busy (%02x).Resetting...\n",temp);
132 /* kill smbus transaction */
133 sis630_write(SMBHOST_CNT, 0x20);
134
135 if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) {
136 dev_dbg(&adap->dev, "Failed! (%02x)\n", temp);
David Brownell97140342008-07-14 22:38:25 +0200137 return -EBUSY;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700138 } else {
Jean Delvarec5d21b72008-04-29 23:11:37 +0200139 dev_dbg(&adap->dev, "Successful!\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700140 }
141 }
142
143 /* save old clock, so we can prevent machine for hung */
144 *oldclock = sis630_read(SMB_CNT);
145
146 dev_dbg(&adap->dev, "saved clock 0x%02x\n", *oldclock);
147
148 /* disable timeout interrupt , set Host Master Clock to 56KHz if requested */
149 if (high_clock)
150 sis630_write(SMB_CNT, 0x20);
151 else
152 sis630_write(SMB_CNT, (*oldclock & ~0x40));
153
154 /* clear all sticky bits */
155 temp = sis630_read(SMB_STS);
156 sis630_write(SMB_STS, temp & 0x1e);
157
158 /* start the transaction by setting bit 4 and size */
159 sis630_write(SMBHOST_CNT,0x10 | (size & 0x07));
160
161 return 0;
162}
163
164static int sis630_transaction_wait(struct i2c_adapter *adap, int size)
165{
166 int temp, result = 0, timeout = 0;
167
168 /* We will always wait for a fraction of a second! */
169 do {
170 msleep(1);
171 temp = sis630_read(SMB_STS);
172 /* check if block transmitted */
173 if (size == SIS630_BLOCK_DATA && (temp & 0x10))
174 break;
175 } while (!(temp & 0x0e) && (timeout++ < MAX_TIMEOUT));
176
177 /* If the SMBus is still busy, we give up */
178 if (timeout >= MAX_TIMEOUT) {
179 dev_dbg(&adap->dev, "SMBus Timeout!\n");
David Brownell97140342008-07-14 22:38:25 +0200180 result = -ETIMEDOUT;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700181 }
182
183 if (temp & 0x02) {
184 dev_dbg(&adap->dev, "Error: Failed bus transaction\n");
David Brownell97140342008-07-14 22:38:25 +0200185 result = -ENXIO;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700186 }
187
188 if (temp & 0x04) {
189 dev_err(&adap->dev, "Bus collision!\n");
David Brownell97140342008-07-14 22:38:25 +0200190 result = -EIO;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700191 /*
192 TBD: Datasheet say:
193 the software should clear this bit and restart SMBUS operation.
194 Should we do it or user start request again?
195 */
196 }
197
198 return result;
199}
200
201static void sis630_transaction_end(struct i2c_adapter *adap, u8 oldclock)
202{
203 int temp = 0;
204
205 /* clear all status "sticky" bits */
206 sis630_write(SMB_STS, temp);
207
208 dev_dbg(&adap->dev, "SMB_CNT before clock restore 0x%02x\n", sis630_read(SMB_CNT));
209
210 /*
211 * restore old Host Master Clock if high_clock is set
212 * and oldclock was not 56KHz
213 */
214 if (high_clock && !(oldclock & 0x20))
215 sis630_write(SMB_CNT,(sis630_read(SMB_CNT) & ~0x20));
216
217 dev_dbg(&adap->dev, "SMB_CNT after clock restore 0x%02x\n", sis630_read(SMB_CNT));
218}
219
220static int sis630_transaction(struct i2c_adapter *adap, int size)
221{
222 int result = 0;
223 u8 oldclock = 0;
224
225 result = sis630_transaction_start(adap, size, &oldclock);
226 if (!result) {
227 result = sis630_transaction_wait(adap, size);
228 sis630_transaction_end(adap, oldclock);
229 }
230
231 return result;
232}
233
234static int sis630_block_data(struct i2c_adapter *adap, union i2c_smbus_data *data, int read_write)
235{
236 int i, len = 0, rc = 0;
237 u8 oldclock = 0;
238
239 if (read_write == I2C_SMBUS_WRITE) {
240 len = data->block[0];
241 if (len < 0)
242 len = 0;
243 else if (len > 32)
244 len = 32;
245 sis630_write(SMB_COUNT, len);
246 for (i=1; i <= len; i++) {
247 dev_dbg(&adap->dev, "set data 0x%02x\n", data->block[i]);
248 /* set data */
249 sis630_write(SMB_BYTE+(i-1)%8, data->block[i]);
250 if (i==8 || (len<8 && i==len)) {
251 dev_dbg(&adap->dev, "start trans len=%d i=%d\n",len ,i);
252 /* first transaction */
David Brownell97140342008-07-14 22:38:25 +0200253 rc = sis630_transaction_start(adap,
254 SIS630_BLOCK_DATA, &oldclock);
255 if (rc)
256 return rc;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700257 }
258 else if ((i-1)%8 == 7 || i==len) {
259 dev_dbg(&adap->dev, "trans_wait len=%d i=%d\n",len,i);
260 if (i>8) {
261 dev_dbg(&adap->dev, "clear smbary_sts len=%d i=%d\n",len,i);
262 /*
263 If this is not first transaction,
264 we must clear sticky bit.
265 clear SMBARY_STS
266 */
267 sis630_write(SMB_STS,0x10);
268 }
David Brownell97140342008-07-14 22:38:25 +0200269 rc = sis630_transaction_wait(adap,
270 SIS630_BLOCK_DATA);
271 if (rc) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700272 dev_dbg(&adap->dev, "trans_wait failed\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700273 break;
274 }
275 }
276 }
277 }
278 else {
279 /* read request */
280 data->block[0] = len = 0;
David Brownell97140342008-07-14 22:38:25 +0200281 rc = sis630_transaction_start(adap,
282 SIS630_BLOCK_DATA, &oldclock);
283 if (rc)
284 return rc;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700285 do {
David Brownell97140342008-07-14 22:38:25 +0200286 rc = sis630_transaction_wait(adap, SIS630_BLOCK_DATA);
287 if (rc) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700288 dev_dbg(&adap->dev, "trans_wait failed\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700289 break;
290 }
291 /* if this first transaction then read byte count */
292 if (len == 0)
293 data->block[0] = sis630_read(SMB_COUNT);
294
295 /* just to be sure */
296 if (data->block[0] > 32)
297 data->block[0] = 32;
298
299 dev_dbg(&adap->dev, "block data read len=0x%x\n", data->block[0]);
300
301 for (i=0; i < 8 && len < data->block[0]; i++,len++) {
302 dev_dbg(&adap->dev, "read i=%d len=%d\n", i, len);
303 data->block[len+1] = sis630_read(SMB_BYTE+i);
304 }
305
306 dev_dbg(&adap->dev, "clear smbary_sts len=%d i=%d\n",len,i);
307
308 /* clear SMBARY_STS */
309 sis630_write(SMB_STS,0x10);
310 } while(len < data->block[0]);
311 }
312
313 sis630_transaction_end(adap, oldclock);
314
315 return rc;
316}
317
David Brownell97140342008-07-14 22:38:25 +0200318/* Return negative errno on error. */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700319static s32 sis630_access(struct i2c_adapter *adap, u16 addr,
320 unsigned short flags, char read_write,
321 u8 command, int size, union i2c_smbus_data *data)
322{
David Brownell97140342008-07-14 22:38:25 +0200323 int status;
324
Linus Torvalds1da177e2005-04-16 15:20:36 -0700325 switch (size) {
326 case I2C_SMBUS_QUICK:
327 sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
328 size = SIS630_QUICK;
329 break;
330 case I2C_SMBUS_BYTE:
331 sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
332 if (read_write == I2C_SMBUS_WRITE)
333 sis630_write(SMB_CMD, command);
334 size = SIS630_BYTE;
335 break;
336 case I2C_SMBUS_BYTE_DATA:
337 sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
338 sis630_write(SMB_CMD, command);
339 if (read_write == I2C_SMBUS_WRITE)
340 sis630_write(SMB_BYTE, data->byte);
341 size = SIS630_BYTE_DATA;
342 break;
343 case I2C_SMBUS_PROC_CALL:
344 case I2C_SMBUS_WORD_DATA:
345 sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01));
346 sis630_write(SMB_CMD, command);
347 if (read_write == I2C_SMBUS_WRITE) {
348 sis630_write(SMB_BYTE, data->word & 0xff);
349 sis630_write(SMB_BYTE + 1,(data->word & 0xff00) >> 8);
350 }
351 size = (size == I2C_SMBUS_PROC_CALL ? SIS630_PCALL : SIS630_WORD_DATA);
352 break;
353 case I2C_SMBUS_BLOCK_DATA:
354 sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01));
355 sis630_write(SMB_CMD, command);
356 size = SIS630_BLOCK_DATA;
357 return sis630_block_data(adap, data, read_write);
358 default:
David Brownell97140342008-07-14 22:38:25 +0200359 printk("Unsupported SMBus operation\n");
360 return -EOPNOTSUPP;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700361 }
362
David Brownell97140342008-07-14 22:38:25 +0200363 status = sis630_transaction(adap, size);
364 if (status)
365 return status;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700366
367 if ((size != SIS630_PCALL) &&
368 ((read_write == I2C_SMBUS_WRITE) || (size == SIS630_QUICK))) {
369 return 0;
370 }
371
372 switch(size) {
373 case SIS630_BYTE:
374 case SIS630_BYTE_DATA:
375 data->byte = sis630_read(SMB_BYTE);
376 break;
377 case SIS630_PCALL:
378 case SIS630_WORD_DATA:
379 data->word = sis630_read(SMB_BYTE) + (sis630_read(SMB_BYTE + 1) << 8);
380 break;
381 default:
David Brownell97140342008-07-14 22:38:25 +0200382 return -EOPNOTSUPP;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700383 }
384
385 return 0;
386}
387
388static u32 sis630_func(struct i2c_adapter *adapter)
389{
390 return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
391 I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_PROC_CALL |
392 I2C_FUNC_SMBUS_BLOCK_DATA;
393}
394
395static int sis630_setup(struct pci_dev *sis630_dev)
396{
397 unsigned char b;
398 struct pci_dev *dummy = NULL;
399 int retval = -ENODEV, i;
400
401 /* check for supported SiS devices */
402 for (i=0; supported[i] > 0 ; i++) {
403 if ((dummy = pci_get_device(PCI_VENDOR_ID_SI, supported[i], dummy)))
404 break; /* found */
405 }
406
407 if (dummy) {
408 pci_dev_put(dummy);
409 }
410 else if (force) {
411 dev_err(&sis630_dev->dev, "WARNING: Can't detect SIS630 compatible device, but "
412 "loading because of force option enabled\n");
413 }
414 else {
415 return -ENODEV;
416 }
417
418 /*
419 Enable ACPI first , so we can accsess reg 74-75
420 in acpi io space and read acpi base addr
421 */
422 if (pci_read_config_byte(sis630_dev, SIS630_BIOS_CTL_REG,&b)) {
423 dev_err(&sis630_dev->dev, "Error: Can't read bios ctl reg\n");
424 goto exit;
425 }
426 /* if ACPI already enabled , do nothing */
427 if (!(b & 0x80) &&
428 pci_write_config_byte(sis630_dev, SIS630_BIOS_CTL_REG, b | 0x80)) {
429 dev_err(&sis630_dev->dev, "Error: Can't enable ACPI\n");
430 goto exit;
431 }
432
433 /* Determine the ACPI base address */
434 if (pci_read_config_word(sis630_dev,SIS630_ACPI_BASE_REG,&acpi_base)) {
435 dev_err(&sis630_dev->dev, "Error: Can't determine ACPI base address\n");
436 goto exit;
437 }
438
439 dev_dbg(&sis630_dev->dev, "ACPI base at 0x%04x\n", acpi_base);
440
441 /* Everything is happy, let's grab the memory and set things up. */
Jean Delvared6072f82005-09-25 16:37:04 +0200442 if (!request_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION,
443 sis630_driver.name)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700444 dev_err(&sis630_dev->dev, "SMBus registers 0x%04x-0x%04x already "
445 "in use!\n", acpi_base + SMB_STS, acpi_base + SMB_SAA);
446 goto exit;
447 }
448
449 retval = 0;
450
451exit:
452 if (retval)
453 acpi_base = 0;
454 return retval;
455}
456
457
Jean Delvare8f9082c2006-09-03 22:39:46 +0200458static const struct i2c_algorithm smbus_algorithm = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700459 .smbus_xfer = sis630_access,
460 .functionality = sis630_func,
461};
462
463static struct i2c_adapter sis630_adapter = {
464 .owner = THIS_MODULE,
Stephen Hemminger9ace5552007-02-13 22:09:01 +0100465 .id = I2C_HW_SMBUS_SIS630,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700466 .class = I2C_CLASS_HWMON,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700467 .algo = &smbus_algorithm,
468};
469
470static struct pci_device_id sis630_ids[] __devinitdata = {
471 { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_503) },
472 { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_LPC) },
473 { 0, }
474};
475
476MODULE_DEVICE_TABLE (pci, sis630_ids);
477
478static int __devinit sis630_probe(struct pci_dev *dev, const struct pci_device_id *id)
479{
480 if (sis630_setup(dev)) {
481 dev_err(&dev->dev, "SIS630 comp. bus not detected, module not inserted.\n");
482 return -ENODEV;
483 }
484
Robert P. J. Day405ae7d2007-02-17 19:13:42 +0100485 /* set up the sysfs linkage to our parent device */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700486 sis630_adapter.dev.parent = &dev->dev;
487
488 sprintf(sis630_adapter.name, "SMBus SIS630 adapter at %04x",
489 acpi_base + SMB_STS);
490
491 return i2c_add_adapter(&sis630_adapter);
492}
493
494static void __devexit sis630_remove(struct pci_dev *dev)
495{
496 if (acpi_base) {
497 i2c_del_adapter(&sis630_adapter);
498 release_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION);
499 acpi_base = 0;
500 }
501}
502
503
504static struct pci_driver sis630_driver = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700505 .name = "sis630_smbus",
506 .id_table = sis630_ids,
507 .probe = sis630_probe,
508 .remove = __devexit_p(sis630_remove),
509};
510
511static int __init i2c_sis630_init(void)
512{
513 return pci_register_driver(&sis630_driver);
514}
515
516
517static void __exit i2c_sis630_exit(void)
518{
519 pci_unregister_driver(&sis630_driver);
520}
521
522
523MODULE_LICENSE("GPL");
524MODULE_AUTHOR("Alexander Malysh <amalysh@web.de>");
525MODULE_DESCRIPTION("SIS630 SMBus driver");
526
527module_init(i2c_sis630_init);
528module_exit(i2c_sis630_exit);