diff -upr linux-2.6.27-rc3/drivers/i2c/busses/i2c-i801.c linux-2.6.27-rc3.new/drivers/i2c/busses/i2c-i801.c --- linux-2.6.27-rc3/drivers/i2c/busses/i2c-i801.c 2008-08-13 20:45:50.000000000 +0200 +++ linux-2.6.27-rc3.new/drivers/i2c/busses/i2c-i801.c 2008-08-13 21:04:50.000000000 +0200 @@ -61,6 +61,7 @@ #include #include #include +#include #include #include #include @@ -93,9 +94,9 @@ /* kill bit for SMBHSTCNT */ #define SMBHSTCNT_KILL 2 -/* Other settings */ +/* Timeout settings */ #define MAX_TIMEOUT 100 -#define ENABLE_INT9 0 /* set to 0x01 to enable - untested */ +#define INTERRUPT_TIMEOUT (HZ/2) /* I801 command constants */ #define I801_QUICK 0x00 @@ -120,21 +121,71 @@ #define SMBHSTSTS_INTR 0x02 #define SMBHSTSTS_HOST_BUSY 0x01 +/* Interrupt enabled */ +#define I801_INTREN 0x01 + +/* Mask for events we normally handle */ +#define I801_HST_STS_MASK_NORM ( \ + SMBHSTSTS_FAILED | \ + SMBHSTSTS_BUS_ERR | \ + SMBHSTSTS_DEV_ERR | \ + SMBHSTSTS_INTR) + +/* Mask for all events */ +#define I801_HST_STS_MASK_ALL ( \ + SMBHSTSTS_BYTE_DONE | \ + SMBHSTSTS_SMBALERT_STS | \ + SMBHSTSTS_FAILED | \ + SMBHSTSTS_BUS_ERR | \ + SMBHSTSTS_DEV_ERR | \ + SMBHSTSTS_INTR) + #define STATUS_FLAGS (SMBHSTSTS_BYTE_DONE | SMBHSTSTS_FAILED | \ SMBHSTSTS_BUS_ERR | SMBHSTSTS_DEV_ERR | \ SMBHSTSTS_INTR) +/* If use_irq is set to anything different than 0, interrupts will be used + if availabe. EXPERIMENTAL! */ +static int use_irq; +module_param(use_irq, bool, S_IRUGO); +MODULE_PARM_DESC(force, "Use interrupts if available. EXPERIMENTAL!"); + static unsigned long i801_smba; static unsigned char i801_original_hstcfg; +static struct i2c_adapter i801_adapter; static struct pci_driver i801_driver; static struct pci_dev *I801_dev; +struct i2c_i801_algo_data { + spinlock_t lock; + wait_queue_head_t waitq; + int status; /* copy of h/w register */ + bool use_irq; +}; + +static struct i2c_i801_algo_data i801_algo_data; + #define FEATURE_SMBUS_PEC (1 << 0) #define FEATURE_BLOCK_BUFFER (1 << 1) #define FEATURE_BLOCK_PROC (1 << 2) #define FEATURE_I2C_BLOCK_READ (1 << 3) static unsigned int i801_features; +/* interrupt handling: fetch & consume host status out of algo_data */ +static inline int i801_get_status(struct i2c_i801_algo_data *algo_data) +{ + unsigned long flags; + int status; + + spin_lock_irqsave(&algo_data->lock, flags); + status = algo_data->status; + algo_data->status = 0; + spin_unlock_irqrestore(&algo_data->lock, flags); + + return status; +} + + /* Make sure the SMBus host is ready to start transmitting. Return 0 if it is, -EBUSY if it is not. */ static int i801_check_pre(void) @@ -221,21 +272,33 @@ static int i801_transaction(int xact) int result; int timeout = 0; + struct i2c_i801_algo_data *algo_data = i801_adapter.algo_data; + result = i801_check_pre(); if (result < 0) return result; /* the current contents of SMBHSTCNT can be overwritten, since PEC, * INTREN, SMBSCMD are passed in xact */ - outb_p(xact | I801_START, SMBHSTCNT); + if (algo_data->use_irq) { + outb_p(xact | I801_START | I801_INTREN, SMBHSTCNT); - /* We will always wait for a fraction of a second! */ - do { - msleep(1); - status = inb_p(SMBHSTSTS); - } while ((status & SMBHSTSTS_HOST_BUSY) && (timeout++ < MAX_TIMEOUT)); + timeout = wait_event_timeout(algo_data->waitq, + ((status = i801_get_status(algo_data)) + & I801_HST_STS_MASK_NORM), INTERRUPT_TIMEOUT); + result = i801_check_post(status, timeout == 0); + } else { + outb_p(xact | I801_START, SMBHSTCNT); + + /* We will always wait for a fraction of a second! */ + do { + msleep(1); + status = inb_p(SMBHSTSTS); + } while ((status & SMBHSTSTS_HOST_BUSY) && + (timeout++ < MAX_TIMEOUT)); + result = i801_check_post(status, timeout >= MAX_TIMEOUT); + } - result = i801_check_post(status, timeout >= MAX_TIMEOUT); if (result < 0) return result; @@ -277,8 +340,7 @@ static int i801_block_transaction_by_blo outb_p(data->block[i+1], SMBBLKDAT); } - status = i801_transaction(I801_BLOCK_DATA | ENABLE_INT9 | - I801_PEC_EN * hwpec); + status = i801_transaction(I801_BLOCK_DATA | I801_PEC_EN * hwpec); if (status) return status; @@ -328,7 +390,7 @@ static int i801_block_transaction_byte_b else smbcmd = I801_BLOCK_DATA; } - outb_p(smbcmd | ENABLE_INT9, SMBHSTCNT); + outb_p(smbcmd, SMBHSTCNT); if (i == 1) outb_p(inb(SMBHSTCNT) | I801_START, SMBHSTCNT); @@ -509,7 +571,7 @@ static s32 i801_access(struct i2c_adapte if(block) ret = i801_block_transaction(data, read_write, size, hwpec); else - ret = i801_transaction(xact | ENABLE_INT9); + ret = i801_transaction(xact); /* Some BIOSes don't like it when PEC is enabled at reboot or resume time, so we forcibly disable it after every transaction. Turn off @@ -558,6 +620,7 @@ static struct i2c_adapter i801_adapter = .id = I2C_HW_SMBUS_I801, .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, .algo = &smbus_algorithm, + .algo_data = &i801_algo_data, }; static struct pci_device_id i801_ids[] = { @@ -581,8 +644,38 @@ static struct pci_device_id i801_ids[] = MODULE_DEVICE_TABLE (pci, i801_ids); +static irqreturn_t i801_isr(int irq, void *dev_id) +{ + u8 status = inb(SMBHSTSTS); + + /* bail if it's not ours */ + if (!(status & I801_HST_STS_MASK_ALL)) { + dev_dbg(&I801_dev->dev, "BAILING interrupt\n"); + return IRQ_NONE; + } + + dev_dbg(&I801_dev->dev, "INTERRUPT: IRQ status: 0x%02x\n", status); + + /* ACK */ + outb((status & I801_HST_STS_MASK_ALL), SMBHSTSTS); + + if (status & I801_HST_STS_MASK_NORM) { + struct i2c_adapter *adap = dev_id; + struct i2c_i801_algo_data *algo_data = adap->algo_data; + unsigned long flags; + + spin_lock_irqsave(&algo_data->lock, flags); + algo_data->status = status; + spin_unlock_irqrestore(&algo_data->lock, flags); + wake_up(&algo_data->waitq); + } + + return IRQ_HANDLED; +} + static int __devinit i801_probe(struct pci_dev *dev, const struct pci_device_id *id) { + struct i2c_i801_algo_data *algo_data = i801_adapter.algo_data; unsigned char temp; int err; @@ -644,10 +737,32 @@ static int __devinit i801_probe(struct p } pci_write_config_byte(I801_dev, SMBHSTCFG, temp); - if (temp & SMBHSTCFG_SMB_SMI_EN) + if (temp & SMBHSTCFG_SMB_SMI_EN) { dev_dbg(&dev->dev, "SMBus using interrupt SMI#\n"); - else + algo_data->use_irq = false; + } else { dev_dbg(&dev->dev, "SMBus using PCI Interrupt\n"); + if (use_irq) { + if ((request_irq(I801_dev->irq, i801_isr, IRQF_SHARED, + i801_driver.name, &i801_adapter))) { + dev_err(&dev->dev, "request irq %d failed!\n", + I801_dev->irq); + algo_data->use_irq = false; + } else { + dev_dbg(&dev->dev, "SMBus base address: " + "0x%04lx, IRQ: %d\n", + i801_smba, I801_dev->irq); + + algo_data->use_irq = true; + algo_data->status = 0; + init_waitqueue_head(&algo_data->waitq); + spin_lock_init(&algo_data->lock); + } + } else { + algo_data->use_irq = false; + dev_dbg(&dev->dev, "Interrupts disabled.\n"); + } + } /* Clear special mode bits */ if (i801_features & (FEATURE_SMBUS_PEC | FEATURE_BLOCK_BUFFER)) @@ -674,7 +789,13 @@ exit: static void __devexit i801_remove(struct pci_dev *dev) { + struct i2c_i801_algo_data *algo_data = i801_adapter.algo_data; + i2c_del_adapter(&i801_adapter); + + if (algo_data->use_irq) + free_irq(dev->irq, &i801_adapter); + pci_write_config_byte(I801_dev, SMBHSTCFG, i801_original_hstcfg); pci_release_region(dev, SMBBAR); /*