2 Copyright (c) 2002,2003 Alexander Malysh <amalysh@web.de>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 Fixed the typo in sis630_access (Thanks to Mark M. Hoffman)
23 Changed sis630_transaction.(Thanks to Mark M. Hoffman)
25 Added SIS730 as supported.
27 Added high_clock module option.If this option is set
28 used Host Master Clock 56KHz (default 14KHz).For now we save old Host
29 Master Clock and after transaction completed restore (otherwise
30 it's confuse BIOS and hung Machine).
32 Fixed typo in sis630_access
33 Fixed logical error by restoring of Host Master Clock
35 Added block data read/write support.
45 Note: we assume there can only be one device, with one SMBus interface.
48 #include <linux/kernel.h>
49 #include <linux/module.h>
50 #include <linux/delay.h>
51 #include <linux/pci.h>
52 #include <linux/ioport.h>
53 #include <linux/init.h>
54 #include <linux/i2c.h>
55 #include <linux/acpi.h>
58 /* SIS630 SMBus registers */
59 #define SMB_STS 0x80 /* status */
60 #define SMB_EN 0x81 /* status enable */
62 #define SMBHOST_CNT 0x83
65 #define SMB_PCOUNT 0x86 /* processed count */
66 #define SMB_COUNT 0x87
67 #define SMB_BYTE 0x88 /* ~0x8F data byte field */
68 #define SMBDEV_ADDR 0x90
73 /* register count for request_region */
74 #define SIS630_SMB_IOREGION 20
76 /* PCI address constants */
77 /* acpi base address register */
78 #define SIS630_ACPI_BASE_REG 0x74
79 /* bios control register */
80 #define SIS630_BIOS_CTL_REG 0x40
83 #define MAX_TIMEOUT 500
85 /* SIS630 constants */
86 #define SIS630_QUICK 0x00
87 #define SIS630_BYTE 0x01
88 #define SIS630_BYTE_DATA 0x02
89 #define SIS630_WORD_DATA 0x03
90 #define SIS630_PCALL 0x04
91 #define SIS630_BLOCK_DATA 0x05
93 static struct pci_driver sis630_driver;
95 /* insmod parameters */
96 static int high_clock;
98 module_param(high_clock, bool, 0);
99 MODULE_PARM_DESC(high_clock, "Set Host Master Clock to 56KHz (default 14KHz).");
100 module_param(force, bool, 0);
101 MODULE_PARM_DESC(force, "Forcibly enable the SIS630. DANGEROUS!");
103 /* acpi base address */
104 static unsigned short acpi_base;
106 /* supported chips */
107 static int supported[] = {
108 PCI_DEVICE_ID_SI_630,
109 PCI_DEVICE_ID_SI_730,
110 0 /* terminates the list */
113 static inline u8 sis630_read(u8 reg)
115 return inb(acpi_base + reg);
118 static inline void sis630_write(u8 reg, u8 data)
120 outb(data, acpi_base + reg);
123 static int sis630_transaction_start(struct i2c_adapter *adap, int size, u8 *oldclock)
127 /* Make sure the SMBus host is ready to start transmitting. */
128 if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) {
129 dev_dbg(&adap->dev, "SMBus busy (%02x).Resetting...\n",temp);
130 /* kill smbus transaction */
131 sis630_write(SMBHOST_CNT, 0x20);
133 if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) {
134 dev_dbg(&adap->dev, "Failed! (%02x)\n", temp);
137 dev_dbg(&adap->dev, "Successful!\n");
141 /* save old clock, so we can prevent machine for hung */
142 *oldclock = sis630_read(SMB_CNT);
144 dev_dbg(&adap->dev, "saved clock 0x%02x\n", *oldclock);
146 /* disable timeout interrupt , set Host Master Clock to 56KHz if requested */
148 sis630_write(SMB_CNT, 0x20);
150 sis630_write(SMB_CNT, (*oldclock & ~0x40));
152 /* clear all sticky bits */
153 temp = sis630_read(SMB_STS);
154 sis630_write(SMB_STS, temp & 0x1e);
156 /* start the transaction by setting bit 4 and size */
157 sis630_write(SMBHOST_CNT,0x10 | (size & 0x07));
162 static int sis630_transaction_wait(struct i2c_adapter *adap, int size)
164 int temp, result = 0, timeout = 0;
166 /* We will always wait for a fraction of a second! */
169 temp = sis630_read(SMB_STS);
170 /* check if block transmitted */
171 if (size == SIS630_BLOCK_DATA && (temp & 0x10))
173 } while (!(temp & 0x0e) && (timeout++ < MAX_TIMEOUT));
175 /* If the SMBus is still busy, we give up */
176 if (timeout > MAX_TIMEOUT) {
177 dev_dbg(&adap->dev, "SMBus Timeout!\n");
182 dev_dbg(&adap->dev, "Error: Failed bus transaction\n");
187 dev_err(&adap->dev, "Bus collision!\n");
191 the software should clear this bit and restart SMBUS operation.
192 Should we do it or user start request again?
199 static void sis630_transaction_end(struct i2c_adapter *adap, u8 oldclock)
203 /* clear all status "sticky" bits */
204 sis630_write(SMB_STS, temp);
206 dev_dbg(&adap->dev, "SMB_CNT before clock restore 0x%02x\n", sis630_read(SMB_CNT));
209 * restore old Host Master Clock if high_clock is set
210 * and oldclock was not 56KHz
212 if (high_clock && !(oldclock & 0x20))
213 sis630_write(SMB_CNT,(sis630_read(SMB_CNT) & ~0x20));
215 dev_dbg(&adap->dev, "SMB_CNT after clock restore 0x%02x\n", sis630_read(SMB_CNT));
218 static int sis630_transaction(struct i2c_adapter *adap, int size)
223 result = sis630_transaction_start(adap, size, &oldclock);
225 result = sis630_transaction_wait(adap, size);
226 sis630_transaction_end(adap, oldclock);
232 static int sis630_block_data(struct i2c_adapter *adap, union i2c_smbus_data *data, int read_write)
234 int i, len = 0, rc = 0;
237 if (read_write == I2C_SMBUS_WRITE) {
238 len = data->block[0];
243 sis630_write(SMB_COUNT, len);
244 for (i=1; i <= len; i++) {
245 dev_dbg(&adap->dev, "set data 0x%02x\n", data->block[i]);
247 sis630_write(SMB_BYTE+(i-1)%8, data->block[i]);
248 if (i==8 || (len<8 && i==len)) {
249 dev_dbg(&adap->dev, "start trans len=%d i=%d\n",len ,i);
250 /* first transaction */
251 rc = sis630_transaction_start(adap,
252 SIS630_BLOCK_DATA, &oldclock);
256 else if ((i-1)%8 == 7 || i==len) {
257 dev_dbg(&adap->dev, "trans_wait len=%d i=%d\n",len,i);
259 dev_dbg(&adap->dev, "clear smbary_sts len=%d i=%d\n",len,i);
261 If this is not first transaction,
262 we must clear sticky bit.
265 sis630_write(SMB_STS,0x10);
267 rc = sis630_transaction_wait(adap,
270 dev_dbg(&adap->dev, "trans_wait failed\n");
278 data->block[0] = len = 0;
279 rc = sis630_transaction_start(adap,
280 SIS630_BLOCK_DATA, &oldclock);
284 rc = sis630_transaction_wait(adap, SIS630_BLOCK_DATA);
286 dev_dbg(&adap->dev, "trans_wait failed\n");
289 /* if this first transaction then read byte count */
291 data->block[0] = sis630_read(SMB_COUNT);
293 /* just to be sure */
294 if (data->block[0] > 32)
297 dev_dbg(&adap->dev, "block data read len=0x%x\n", data->block[0]);
299 for (i=0; i < 8 && len < data->block[0]; i++,len++) {
300 dev_dbg(&adap->dev, "read i=%d len=%d\n", i, len);
301 data->block[len+1] = sis630_read(SMB_BYTE+i);
304 dev_dbg(&adap->dev, "clear smbary_sts len=%d i=%d\n",len,i);
306 /* clear SMBARY_STS */
307 sis630_write(SMB_STS,0x10);
308 } while(len < data->block[0]);
311 sis630_transaction_end(adap, oldclock);
316 /* Return negative errno on error. */
317 static s32 sis630_access(struct i2c_adapter *adap, u16 addr,
318 unsigned short flags, char read_write,
319 u8 command, int size, union i2c_smbus_data *data)
324 case I2C_SMBUS_QUICK:
325 sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
329 sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
330 if (read_write == I2C_SMBUS_WRITE)
331 sis630_write(SMB_CMD, command);
334 case I2C_SMBUS_BYTE_DATA:
335 sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
336 sis630_write(SMB_CMD, command);
337 if (read_write == I2C_SMBUS_WRITE)
338 sis630_write(SMB_BYTE, data->byte);
339 size = SIS630_BYTE_DATA;
341 case I2C_SMBUS_PROC_CALL:
342 case I2C_SMBUS_WORD_DATA:
343 sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01));
344 sis630_write(SMB_CMD, command);
345 if (read_write == I2C_SMBUS_WRITE) {
346 sis630_write(SMB_BYTE, data->word & 0xff);
347 sis630_write(SMB_BYTE + 1,(data->word & 0xff00) >> 8);
349 size = (size == I2C_SMBUS_PROC_CALL ? SIS630_PCALL : SIS630_WORD_DATA);
351 case I2C_SMBUS_BLOCK_DATA:
352 sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01));
353 sis630_write(SMB_CMD, command);
354 size = SIS630_BLOCK_DATA;
355 return sis630_block_data(adap, data, read_write);
357 dev_warn(&adap->dev, "Unsupported transaction %d\n",
362 status = sis630_transaction(adap, size);
366 if ((size != SIS630_PCALL) &&
367 ((read_write == I2C_SMBUS_WRITE) || (size == SIS630_QUICK))) {
373 case SIS630_BYTE_DATA:
374 data->byte = sis630_read(SMB_BYTE);
377 case SIS630_WORD_DATA:
378 data->word = sis630_read(SMB_BYTE) + (sis630_read(SMB_BYTE + 1) << 8);
385 static u32 sis630_func(struct i2c_adapter *adapter)
387 return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
388 I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_PROC_CALL |
389 I2C_FUNC_SMBUS_BLOCK_DATA;
392 static int __devinit sis630_setup(struct pci_dev *sis630_dev)
395 struct pci_dev *dummy = NULL;
396 int retval = -ENODEV, i;
398 /* check for supported SiS devices */
399 for (i=0; supported[i] > 0 ; i++) {
400 if ((dummy = pci_get_device(PCI_VENDOR_ID_SI, supported[i], dummy)))
408 dev_err(&sis630_dev->dev, "WARNING: Can't detect SIS630 compatible device, but "
409 "loading because of force option enabled\n");
416 Enable ACPI first , so we can accsess reg 74-75
417 in acpi io space and read acpi base addr
419 if (pci_read_config_byte(sis630_dev, SIS630_BIOS_CTL_REG,&b)) {
420 dev_err(&sis630_dev->dev, "Error: Can't read bios ctl reg\n");
423 /* if ACPI already enabled , do nothing */
425 pci_write_config_byte(sis630_dev, SIS630_BIOS_CTL_REG, b | 0x80)) {
426 dev_err(&sis630_dev->dev, "Error: Can't enable ACPI\n");
430 /* Determine the ACPI base address */
431 if (pci_read_config_word(sis630_dev,SIS630_ACPI_BASE_REG,&acpi_base)) {
432 dev_err(&sis630_dev->dev, "Error: Can't determine ACPI base address\n");
436 dev_dbg(&sis630_dev->dev, "ACPI base at 0x%04x\n", acpi_base);
438 retval = acpi_check_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION,
443 /* Everything is happy, let's grab the memory and set things up. */
444 if (!request_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION,
445 sis630_driver.name)) {
446 dev_err(&sis630_dev->dev, "SMBus registers 0x%04x-0x%04x already "
447 "in use!\n", acpi_base + SMB_STS, acpi_base + SMB_SAA);
460 static const struct i2c_algorithm smbus_algorithm = {
461 .smbus_xfer = sis630_access,
462 .functionality = sis630_func,
465 static struct i2c_adapter sis630_adapter = {
466 .owner = THIS_MODULE,
467 .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
468 .algo = &smbus_algorithm,
471 static const struct pci_device_id sis630_ids[] __devinitconst = {
472 { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_503) },
473 { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_LPC) },
477 MODULE_DEVICE_TABLE (pci, sis630_ids);
479 static int __devinit sis630_probe(struct pci_dev *dev, const struct pci_device_id *id)
481 if (sis630_setup(dev)) {
482 dev_err(&dev->dev, "SIS630 comp. bus not detected, module not inserted.\n");
486 /* set up the sysfs linkage to our parent device */
487 sis630_adapter.dev.parent = &dev->dev;
489 snprintf(sis630_adapter.name, sizeof(sis630_adapter.name),
490 "SMBus SIS630 adapter at %04x", acpi_base + SMB_STS);
492 return i2c_add_adapter(&sis630_adapter);
495 static void __devexit sis630_remove(struct pci_dev *dev)
498 i2c_del_adapter(&sis630_adapter);
499 release_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION);
505 static struct pci_driver sis630_driver = {
506 .name = "sis630_smbus",
507 .id_table = sis630_ids,
508 .probe = sis630_probe,
509 .remove = __devexit_p(sis630_remove),
512 static int __init i2c_sis630_init(void)
514 return pci_register_driver(&sis630_driver);
518 static void __exit i2c_sis630_exit(void)
520 pci_unregister_driver(&sis630_driver);
524 MODULE_LICENSE("GPL");
525 MODULE_AUTHOR("Alexander Malysh <amalysh@web.de>");
526 MODULE_DESCRIPTION("SIS630 SMBus driver");
528 module_init(i2c_sis630_init);
529 module_exit(i2c_sis630_exit);