* [PATCH] I2C patch 5 - Add a non-blocking interface to the I2C core (again)
@ 2005-02-25 14:26 Corey Minyard
2005-02-25 14:36 ` Arjan van de Ven
0 siblings, 1 reply; 3+ messages in thread
From: Corey Minyard @ 2005-02-25 14:26 UTC (permalink / raw)
To: lkml
[-- Attachment #1: Type: text/plain, Size: 115 bytes --]
This patch didn't make it to the mailing list because my fat fingers
type lmkl instead of lkml. One more time...
[-- Attachment #2: i2c_nonblock_i801.diff --]
[-- Type: text/plain, Size: 24189 bytes --]
This patch modifies the I801 SMBus driver to use the non-blocking
interface.
Signed-off-by: Corey Minyard <minyard@acm.org>
Index: linux-2.6.11-rc2/drivers/i2c/busses/i2c-i801.c
===================================================================
--- linux-2.6.11-rc2.orig/drivers/i2c/busses/i2c-i801.c
+++ linux-2.6.11-rc2/drivers/i2c/busses/i2c-i801.c
@@ -40,6 +40,14 @@
/* Note: we assume there can only be one I801, with one SMBus interface */
+/* Another note: This interface is extremely sensitive to timing and
+ failure handling. If you don't wait at least one jiffie after
+ starting the transaction before checking things, you will screw it
+ up. If you don't wait a jiffie after the final check, you will
+ screw it up. If you screw it up by these manners or by abandoning
+ an operation in progress, the I2C bus is likely stuck and won't
+ work any more. Gotta love this hardware. */
+
#include <linux/config.h>
#include <linux/module.h>
#include <linux/pci.h>
@@ -79,7 +87,8 @@
#define SMBHSTCFG_I2C_EN 4
/* Other settings */
-#define MAX_TIMEOUT 100
+#define MAX_TIMEOUT_US 100000
+#define RETRY_TIME_US 500 /* Retry minimum is 500us */
#define ENABLE_INT9 0 /* set to 0x01 to enable - untested */
/* I801 command constants */
@@ -105,21 +114,35 @@
"Forcibly enable the I801 at the given address. "
"EXTREMELY DANGEROUS!");
-static int i801_transaction(void);
-static int i801_block_transaction(union i2c_smbus_data *data,
- char read_write, int command);
-
static unsigned short i801_smba;
static struct pci_dev *I801_dev;
static int isich4;
+struct i801_i2c_data
+{
+ int i;
+ int len;
+ unsigned char hostc;
+ int block;
+ int hwpec;
+ int xact;
+ int hststs;
+ int wait_intr;
+ int finished;
+
+ /* Used to handle removal race conditions. */
+ int in_removal;
+ int in_use;
+};
+struct i801_i2c_data i801_data;
+
static int i801_setup(struct pci_dev *dev)
{
int error_return = 0;
unsigned char temp;
/* Note: we keep on searching until we have found 'function 3' */
- if(PCI_FUNC(dev->devfn) != 3)
+ if (PCI_FUNC(dev->devfn) != 3)
return -ENODEV;
I801_dev = dev;
@@ -136,7 +159,7 @@
} else {
pci_read_config_word(I801_dev, SMBBA, &i801_smba);
i801_smba &= 0xfff0;
- if(i801_smba == 0) {
+ if (i801_smba == 0) {
dev_err(&dev->dev, "SMB base address uninitialized"
"- upgrade BIOS or use force_addr=0xaddr\n");
return -ENODEV;
@@ -180,12 +203,93 @@
return error_return;
}
-static int i801_transaction(void)
+static void i801_check_hststs(struct i2c_adapter *adap,
+ struct i2c_op_q_entry *entry,
+ struct i801_i2c_data *d)
+{
+ if (d->hststs & 0x10) {
+ entry->result = -EIO;
+ dev_dbg(&I801_dev->dev,
+ "Error: Failed bus transaction\n");
+ } else if (d->hststs & 0x08) {
+ entry->result = -EIO;
+ dev_err(&I801_dev->dev, "Bus collision!\n");
+ /* Clock stops and slave is stuck in mid-transmission */
+ } else if (d->hststs & 0x04) {
+ entry->result = -EIO;
+ dev_dbg(&I801_dev->dev, "Error: no response!\n");
+ }
+}
+
+static void i801_finish(struct i2c_adapter *adap,
+ struct i2c_op_q_entry *entry,
+ struct i801_i2c_data *d)
+{
+ d->finished = 1;
+
+#ifdef HAVE_PEC
+ if (isich4 && d->hwpec) {
+ if (entry->smbus.size != I2C_SMBUS_QUICK &&
+ entry->smbus.size != I2C_SMBUS_I2C_BLOCK_DATA)
+ outb_p(0, SMBAUXCTL);
+ }
+#endif
+
+ if (d->block || (entry->result < 0) ||
+ ((entry->smbus.read_write == I2C_SMBUS_WRITE)
+ || (d->xact == I801_QUICK)))
+ return;
+
+ switch (d->xact & 0x7f) {
+ case I801_BYTE: /* Result put in SMBHSTDAT0 */
+ case I801_BYTE_DATA:
+ entry->smbus.data->byte = inb_p(SMBHSTDAT0);
+ break;
+ case I801_WORD_DATA:
+ entry->smbus.data->word = inb_p(SMBHSTDAT0)
+ + (inb_p(SMBHSTDAT1) << 8);
+ break;
+ }
+}
+
+static void i801_transaction_final_check(struct i2c_adapter *adap,
+ struct i2c_op_q_entry *entry,
+ struct i801_i2c_data *d)
{
- int temp;
- int result = 0;
- int timeout = 0;
+ i801_check_hststs(adap, entry, d);
+ if ((inb_p(SMBHSTSTS) & 0x1f) != 0x00)
+ outb_p(inb(SMBHSTSTS), SMBHSTSTS);
+
+ if ((d->hststs = (0x1f & inb_p(SMBHSTSTS))) != 0x00) {
+ dev_dbg(&I801_dev->dev, "Failed reset at end of transaction"
+ "(%02x)\n", d->hststs);
+ }
+ dev_dbg(&I801_dev->dev, "Transaction (post): CNT=%02x, CMD=%02x, "
+ "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT),
+ inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0),
+ inb_p(SMBHSTDAT1));
+}
+
+static void i801_transaction_poll(struct i2c_adapter *adap,
+ struct i2c_op_q_entry *entry,
+ struct i801_i2c_data *d)
+{
+ d->hststs = inb_p(SMBHSTSTS);
+ if (!(d->hststs & 0x01)) {
+ i801_transaction_final_check(adap, entry, d);
+ i801_finish(adap, entry, d);
+ } else if (entry->time_left <= 0) {
+ dev_dbg(&I801_dev->dev, "SMBus Timeout!\n");
+ entry->result = -EIO;
+ i801_transaction_final_check(adap, entry, d);
+ }
+}
+
+static void i801_transaction_start(struct i2c_adapter *adap,
+ struct i2c_op_q_entry *entry,
+ struct i801_i2c_data *d)
+{
dev_dbg(&I801_dev->dev, "Transaction (pre): CNT=%02x, CMD=%02x,"
"ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT),
inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0),
@@ -193,331 +297,368 @@
/* Make sure the SMBus host is ready to start transmitting */
/* 0x1f = Failed, Bus_Err, Dev_Err, Intr, Host_Busy */
- if ((temp = (0x1f & inb_p(SMBHSTSTS))) != 0x00) {
+ if ((d->hststs = (0x1f & inb_p(SMBHSTSTS))) != 0x00) {
dev_dbg(&I801_dev->dev, "SMBus busy (%02x). Resetting... \n",
- temp);
- outb_p(temp, SMBHSTSTS);
- if ((temp = (0x1f & inb_p(SMBHSTSTS))) != 0x00) {
- dev_dbg(&I801_dev->dev, "Failed! (%02x)\n", temp);
- return -1;
+ d->hststs);
+ outb_p(d->hststs, SMBHSTSTS);
+ if ((d->hststs = (0x1f & inb_p(SMBHSTSTS))) != 0x00) {
+ dev_dbg(&I801_dev->dev, "Failed! (%02x)\n", d->hststs);
+ entry->result = -EIO;
+ return;
} else {
dev_dbg(&I801_dev->dev, "Successfull!\n");
}
}
outb_p(inb(SMBHSTCNT) | I801_START, SMBHSTCNT);
+}
- /* We will always wait for a fraction of a second! */
- do {
- msleep(1);
- temp = inb_p(SMBHSTSTS);
- } while ((temp & 0x01) && (timeout++ < MAX_TIMEOUT));
-
- /* If the SMBus is still busy, we give up */
- if (timeout >= MAX_TIMEOUT) {
- dev_dbg(&I801_dev->dev, "SMBus Timeout!\n");
- result = -1;
+static void i801_block_finish(struct i2c_adapter *adap,
+ struct i2c_op_q_entry *entry,
+ struct i801_i2c_data *d)
+{
+ if (entry->smbus.size == I2C_SMBUS_I2C_BLOCK_DATA) {
+ /* restore saved configuration register value */
+ pci_write_config_byte(I801_dev, SMBHSTCFG, d->hostc);
}
- if (temp & 0x10) {
- result = -1;
- dev_dbg(&I801_dev->dev, "Error: Failed bus transaction\n");
- }
+ i801_finish(adap, entry, d);
+}
- if (temp & 0x08) {
- result = -1;
- dev_err(&I801_dev->dev, "Bus collision! SMBus may be locked "
- "until next hard reset. (sorry!)\n");
- /* Clock stops and slave is stuck in mid-transmission */
- }
+static void i801_block_poll_wait_intr(struct i2c_adapter *adap,
+ struct i2c_op_q_entry *entry,
+ struct i801_i2c_data *d)
+{
+#ifdef HAVE_PEC
+ if (entry->result >= 0 &&
+ isich4 &&
+ entry->smbus.size == I2C_SMBUS_BLOCK_DATA_PEC)
+ {
+ /* wait for INTR bit as advised by Intel */
+ d->hststs = inb_p(SMBHSTSTS);
+ if (d->hststs & 0x02) {
+ outb_p(d->hststs, SMBHSTSTS);
+ i801_block_finish(adap, entry, d);
+ } else if (entry->time_left <= 0) {
+ /* Timed out */
+ outb_p(d->hststs, SMBHSTSTS);
+ entry->result = -EIO;
+ dev_dbg(&I801_dev->dev, "PEC Timeout!\n");
+ }
+ } else
+#endif
+ i801_block_finish(adap, entry, d);
+}
- if (temp & 0x04) {
- result = -1;
- dev_dbg(&I801_dev->dev, "Error: no response!\n");
+static void i801_block_next_byte(struct i2c_adapter *adap,
+ struct i2c_op_q_entry *entry,
+ struct i801_i2c_data *d)
+{
+ int smbcmd;
+ unsigned char errmask;
+
+ if (d->i > d->len) {
+ d->wait_intr = 1;
+ entry->time_left = MAX_TIMEOUT_US;
+ i801_block_poll_wait_intr(adap, entry, d);
+ return;
}
- if ((inb_p(SMBHSTSTS) & 0x1f) != 0x00)
- outb_p(inb(SMBHSTSTS), SMBHSTSTS);
+ if (d->i == d->len && entry->smbus.read_write == I2C_SMBUS_READ)
+ smbcmd = I801_BLOCK_LAST;
+ else
+ smbcmd = I801_BLOCK_DATA;
+ outb_p(smbcmd | ENABLE_INT9, SMBHSTCNT);
- if ((temp = (0x1f & inb_p(SMBHSTSTS))) != 0x00) {
- dev_dbg(&I801_dev->dev, "Failed reset at end of transaction"
- "(%02x)\n", temp);
+ dev_dbg(&I801_dev->dev, "Block (pre %d): CNT=%02x, CMD=%02x, "
+ "ADD=%02x, DAT0=%02x, BLKDAT=%02x\n", d->i,
+ inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), inb_p(SMBHSTADD),
+ inb_p(SMBHSTDAT0), inb_p(SMBBLKDAT));
+
+ /* Make sure the SMBus host is ready to start transmitting */
+ d->hststs = inb_p(SMBHSTSTS);
+ if (d->i == 1) {
+ /* Erronenous conditions before transaction:
+ * Byte_Done, Failed, Bus_Err, Dev_Err, Intr, Host_Busy */
+ errmask=0x9f;
+ } else {
+ /* Erronenous conditions during transaction:
+ * Failed, Bus_Err, Dev_Err, Intr */
+ errmask=0x1e;
+ }
+ if (d->hststs & errmask) {
+ dev_dbg(&I801_dev->dev, "SMBus busy (%02x). "
+ "Resetting... \n", d->hststs);
+ outb_p(d->hststs, SMBHSTSTS);
+ if (((d->hststs = inb_p(SMBHSTSTS)) & errmask) != 0x00) {
+ dev_err(&I801_dev->dev,
+ "Reset failed! (%02x)\n", d->hststs);
+ entry->result = -EIO;
+ return;
+ }
+ if (d->i != 1) {
+ /* if die in middle of block transaction, fail */
+ entry->result = -EIO;
+ return;
+ }
}
- dev_dbg(&I801_dev->dev, "Transaction (post): CNT=%02x, CMD=%02x, "
- "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT),
- inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0),
- inb_p(SMBHSTDAT1));
- return result;
+
+ if (d->i == 1)
+ outb_p(inb(SMBHSTCNT) | I801_START, SMBHSTCNT);
}
-/* All-inclusive block transaction function */
-static int i801_block_transaction(union i2c_smbus_data *data, char read_write,
- int command)
+/* Called on timer ticks. This checks the result of the
+ transaction. */
+static void i801_block_poll(struct i2c_adapter *adap,
+ struct i2c_op_q_entry *entry,
+ struct i801_i2c_data *d)
{
- int i, len;
- int smbcmd;
- int temp;
- int result = 0;
- int timeout;
- unsigned char hostc, errmask;
-
- if (command == I2C_SMBUS_I2C_BLOCK_DATA) {
- if (read_write == I2C_SMBUS_WRITE) {
- /* set I2C_EN bit in configuration register */
- pci_read_config_byte(I801_dev, SMBHSTCFG, &hostc);
- pci_write_config_byte(I801_dev, SMBHSTCFG,
- hostc | SMBHSTCFG_I2C_EN);
- } else {
- dev_err(&I801_dev->dev,
- "I2C_SMBUS_I2C_BLOCK_READ not DB!\n");
- return -1;
+ d->hststs = inb_p(SMBHSTSTS);
+ if (!(d->hststs & 0x80)) {
+ /* Not ready yet */
+ if (entry->time_left <= 0) {
+ dev_dbg(&I801_dev->dev, "SMBus Timeout!\n");
+ entry->result = -EIO;
}
+ return;
}
- if (read_write == I2C_SMBUS_WRITE) {
- len = data->block[0];
- if (len < 1)
- len = 1;
- if (len > 32)
- len = 32;
- outb_p(len, SMBHSTDAT0);
- outb_p(data->block[1], SMBBLKDAT);
+ i801_check_hststs(adap, entry, d);
+ if (d->i == 1 && entry->smbus.read_write == I2C_SMBUS_READ) {
+ d->len = inb_p(SMBHSTDAT0);
+ if (d->len < 1)
+ d->len = 1;
+ if (d->len > 32)
+ d->len = 32;
+ entry->smbus.data->block[0] = d->len;
+ }
+
+ /* Retrieve/store value in SMBBLKDAT */
+ if (entry->smbus.read_write == I2C_SMBUS_READ)
+ entry->smbus.data->block[d->i] = inb_p(SMBBLKDAT);
+ if (entry->smbus.read_write == I2C_SMBUS_WRITE && d->i+1 <= d->len)
+ outb_p(entry->smbus.data->block[d->i+1], SMBBLKDAT);
+ if ((d->hststs & 0x9e) != 0x00)
+ outb_p(d->hststs, SMBHSTSTS); /* signals SMBBLKDAT ready */
+
+ if ((d->hststs = (0x1e & inb_p(SMBHSTSTS))) != 0x00) {
+ dev_dbg(&I801_dev->dev,
+ "Bad status (%02x) at end of transaction\n",
+ d->hststs);
+ }
+ dev_dbg(&I801_dev->dev, "Block (post %d): CNT=%02x, CMD=%02x, "
+ "ADD=%02x, DAT0=%02x, BLKDAT=%02x\n", d->i,
+ inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), inb_p(SMBHSTADD),
+ inb_p(SMBHSTDAT0), inb_p(SMBBLKDAT));
+
+ if (entry->result)
+ return;
+
+ (d->i)++;
+ i801_block_next_byte(adap, entry, d);
+}
+
+static void i801_block_start(struct i2c_adapter *adap,
+ struct i2c_op_q_entry *entry,
+ struct i801_i2c_data *d)
+{
+ if (entry->smbus.read_write == I2C_SMBUS_WRITE) {
+ d->len = entry->smbus.data->block[0];
+ if (d->len < 1)
+ d->len = 1;
+ if (d->len > 32)
+ d->len = 32;
+ outb_p(d->len, SMBHSTDAT0);
+ outb_p(entry->smbus.data->block[1], SMBBLKDAT);
} else {
- len = 32; /* max for reads */
+ d->len = 32; /* max for reads */
}
- if(isich4 && command != I2C_SMBUS_I2C_BLOCK_DATA) {
+ if(isich4 && entry->smbus.size != I2C_SMBUS_I2C_BLOCK_DATA) {
/* set 32 byte buffer */
}
- for (i = 1; i <= len; i++) {
- if (i == len && read_write == I2C_SMBUS_READ)
- smbcmd = I801_BLOCK_LAST;
- else
- smbcmd = I801_BLOCK_DATA;
- outb_p(smbcmd | ENABLE_INT9, SMBHSTCNT);
-
- dev_dbg(&I801_dev->dev, "Block (pre %d): CNT=%02x, CMD=%02x, "
- "ADD=%02x, DAT0=%02x, BLKDAT=%02x\n", i,
- inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), inb_p(SMBHSTADD),
- inb_p(SMBHSTDAT0), inb_p(SMBBLKDAT));
-
- /* Make sure the SMBus host is ready to start transmitting */
- temp = inb_p(SMBHSTSTS);
- if (i == 1) {
- /* Erronenous conditions before transaction:
- * Byte_Done, Failed, Bus_Err, Dev_Err, Intr, Host_Busy */
- errmask=0x9f;
- } else {
- /* Erronenous conditions during transaction:
- * Failed, Bus_Err, Dev_Err, Intr */
- errmask=0x1e;
- }
- if (temp & errmask) {
- dev_dbg(&I801_dev->dev, "SMBus busy (%02x). "
- "Resetting... \n", temp);
- outb_p(temp, SMBHSTSTS);
- if (((temp = inb_p(SMBHSTSTS)) & errmask) != 0x00) {
- dev_err(&I801_dev->dev,
- "Reset failed! (%02x)\n", temp);
- result = -1;
- goto END;
- }
- if (i != 1) {
- /* if die in middle of block transaction, fail */
- result = -1;
- goto END;
- }
- }
+ d->i = 1;
+ i801_block_next_byte(adap, entry, d);
+}
- if (i == 1)
- outb_p(inb(SMBHSTCNT) | I801_START, SMBHSTCNT);
+/* General poll routine. Called periodically by the i2c code. */
+static void i801_poll(struct i2c_adapter *adap,
+ struct i2c_op_q_entry *entry,
+ unsigned int us_since_last_poll)
+{
+ struct i801_i2c_data *d = entry->data;
- /* We will always wait for a fraction of a second! */
- timeout = 0;
- do {
- temp = inb_p(SMBHSTSTS);
- msleep(1);
- }
- while ((!(temp & 0x80))
- && (timeout++ < MAX_TIMEOUT));
+ dev_dbg(&I801_dev->dev, "Poll call for %p %p at %ld\n", adap, entry,
+ jiffies);
- /* If the SMBus is still busy, we give up */
- if (timeout >= MAX_TIMEOUT) {
- result = -1;
- dev_dbg(&I801_dev->dev, "SMBus Timeout!\n");
- }
+ if (!d)
+ /* The entry hasn't been started yet. */
+ return;
- if (temp & 0x10) {
- result = -1;
- dev_dbg(&I801_dev->dev,
- "Error: Failed bus transaction\n");
- } else if (temp & 0x08) {
- result = -1;
- dev_err(&I801_dev->dev, "Bus collision!\n");
- } else if (temp & 0x04) {
- result = -1;
- dev_dbg(&I801_dev->dev, "Error: no response!\n");
- }
+ if (d->finished) {
+ /* We delay an extra poll to keep the hardware happy.
+ Otherwise the hardware is not ready when we start
+ the next operation. */
+ i2c_op_done(adap, entry);
+ d->in_use = 0;
+ return;
+ }
- if (i == 1 && read_write == I2C_SMBUS_READ) {
- len = inb_p(SMBHSTDAT0);
- if (len < 1)
- len = 1;
- if (len > 32)
- len = 32;
- data->block[0] = len;
- }
+ /* Decrement timeout */
+ entry->time_left -= us_since_last_poll;
+
+ /* Wait a jiffie normally. */
+ entry->call_again_us = RETRY_TIME_US;
- /* Retrieve/store value in SMBBLKDAT */
- if (read_write == I2C_SMBUS_READ)
- data->block[i] = inb_p(SMBBLKDAT);
- if (read_write == I2C_SMBUS_WRITE && i+1 <= len)
- outb_p(data->block[i+1], SMBBLKDAT);
- if ((temp & 0x9e) != 0x00)
- outb_p(temp, SMBHSTSTS); /* signals SMBBLKDAT ready */
-
- if ((temp = (0x1e & inb_p(SMBHSTSTS))) != 0x00) {
- dev_dbg(&I801_dev->dev,
- "Bad status (%02x) at end of transaction\n",
- temp);
+ if (d->block) {
+ if (d->wait_intr) {
+ i801_block_poll_wait_intr(adap, entry, d);
+ } else {
+ i801_block_poll(adap, entry, d);
}
- dev_dbg(&I801_dev->dev, "Block (post %d): CNT=%02x, CMD=%02x, "
- "ADD=%02x, DAT0=%02x, BLKDAT=%02x\n", i,
- inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), inb_p(SMBHSTADD),
- inb_p(SMBHSTDAT0), inb_p(SMBBLKDAT));
+ if (entry->result < 0)
+ /* Error, finish the transaction */
+ i801_block_finish(adap, entry, d);
+ } else {
+ i801_transaction_poll(adap, entry, d);
+ if (entry->result < 0)
+ /* Error, finish the transaction */
+ i801_finish(adap, entry, d);
+ }
+}
- if (result < 0)
- goto END;
+/* Start a general SMBUS transaction on the i801. Figure out what
+ kind of transaction it is, set it up, and start it. */
+static void i801_start(struct i2c_adapter *adap,
+ struct i2c_op_q_entry *entry)
+{
+ struct i801_i2c_data *d = adap->algo_data;
+
+ d->in_use = 1;
+ if (d->in_removal) {
+ d->in_use = 0;
+ entry->result = -ENODEV;
+ return;
}
-#ifdef HAVE_PEC
- if(isich4 && command == I2C_SMBUS_BLOCK_DATA_PEC) {
- /* wait for INTR bit as advised by Intel */
- timeout = 0;
- do {
- temp = inb_p(SMBHSTSTS);
- msleep(1);
- } while ((!(temp & 0x02))
- && (timeout++ < MAX_TIMEOUT));
+ dev_dbg(&I801_dev->dev, "start call for %p %p at %ld\n", adap, entry,
+ jiffies);
- if (timeout >= MAX_TIMEOUT) {
- dev_dbg(&I801_dev->dev, "PEC Timeout!\n");
+ if (entry->smbus.size == I2C_SMBUS_I2C_BLOCK_DATA) {
+ if (entry->smbus.read_write == I2C_SMBUS_WRITE) {
+ /* set I2C_EN bit in configuration register */
+ pci_read_config_byte(I801_dev, SMBHSTCFG, &d->hostc);
+ pci_write_config_byte(I801_dev, SMBHSTCFG,
+ d->hostc | SMBHSTCFG_I2C_EN);
+ } else {
+ dev_err(&I801_dev->dev,
+ "I2C_SMBUS_I2C_BLOCK_READ not DB!\n");
+ d->in_use = 0;
+ entry->result = -EINVAL;
+ return;
}
- outb_p(temp, SMBHSTSTS);
}
-#endif
- result = 0;
-END:
- if (command == I2C_SMBUS_I2C_BLOCK_DATA) {
- /* restore saved configuration register value */
- pci_write_config_byte(I801_dev, SMBHSTCFG, hostc);
- }
- return result;
-}
-/* Return -1 on error. */
-static s32 i801_access(struct i2c_adapter * adap, u16 addr,
- unsigned short flags, char read_write, u8 command,
- int size, union i2c_smbus_data * data)
-{
- int hwpec = 0;
- int block = 0;
- int ret, xact = 0;
+ d->block = 0;
+ d->hwpec = 0;
+ d->xact = 0;
+ d->wait_intr = 0;
+ d->finished = 0;
#ifdef HAVE_PEC
- if(isich4)
- hwpec = (flags & I2C_CLIENT_PEC) != 0;
+ if (isich4)
+ d->hwpec = (entry->smbus.flags & I2C_CLIENT_PEC) != 0;
#endif
- switch (size) {
+ switch (entry->smbus.size) {
case I2C_SMBUS_QUICK:
- outb_p(((addr & 0x7f) << 1) | (read_write & 0x01),
+ outb_p(((entry->smbus.addr & 0x7f) << 1)
+ | (entry->smbus.read_write & 0x01),
SMBHSTADD);
- xact = I801_QUICK;
+ d->xact = I801_QUICK;
break;
case I2C_SMBUS_BYTE:
- outb_p(((addr & 0x7f) << 1) | (read_write & 0x01),
+ outb_p(((entry->smbus.addr & 0x7f) << 1)
+ | (entry->smbus.read_write & 0x01),
SMBHSTADD);
- if (read_write == I2C_SMBUS_WRITE)
- outb_p(command, SMBHSTCMD);
- xact = I801_BYTE;
+ if (entry->smbus.read_write == I2C_SMBUS_WRITE)
+ outb_p(entry->smbus.command, SMBHSTCMD);
+ d->xact = I801_BYTE;
break;
case I2C_SMBUS_BYTE_DATA:
- outb_p(((addr & 0x7f) << 1) | (read_write & 0x01),
+ outb_p(((entry->smbus.addr & 0x7f) << 1)
+ | (entry->smbus.read_write & 0x01),
SMBHSTADD);
- outb_p(command, SMBHSTCMD);
- if (read_write == I2C_SMBUS_WRITE)
- outb_p(data->byte, SMBHSTDAT0);
- xact = I801_BYTE_DATA;
+ outb_p(entry->smbus.command, SMBHSTCMD);
+ if (entry->smbus.read_write == I2C_SMBUS_WRITE)
+ outb_p(entry->smbus.data->byte, SMBHSTDAT0);
+ d->xact = I801_BYTE_DATA;
break;
case I2C_SMBUS_WORD_DATA:
- outb_p(((addr & 0x7f) << 1) | (read_write & 0x01),
+ outb_p(((entry->smbus.addr & 0x7f) << 1)
+ | (entry->smbus.read_write & 0x01),
SMBHSTADD);
- outb_p(command, SMBHSTCMD);
- if (read_write == I2C_SMBUS_WRITE) {
- outb_p(data->word & 0xff, SMBHSTDAT0);
- outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1);
+ outb_p(entry->smbus.command, SMBHSTCMD);
+ if (entry->smbus.read_write == I2C_SMBUS_WRITE) {
+ outb_p(entry->smbus.data->word & 0xff, SMBHSTDAT0);
+ outb_p((entry->smbus.data->word & 0xff00) >> 8,
+ SMBHSTDAT1);
}
- xact = I801_WORD_DATA;
+ d->xact = I801_WORD_DATA;
break;
case I2C_SMBUS_BLOCK_DATA:
case I2C_SMBUS_I2C_BLOCK_DATA:
#ifdef HAVE_PEC
case I2C_SMBUS_BLOCK_DATA_PEC:
- if(hwpec && size == I2C_SMBUS_BLOCK_DATA)
- size = I2C_SMBUS_BLOCK_DATA_PEC;
+ if (d->hwpec && entry->smbus.size == I2C_SMBUS_BLOCK_DATA)
+ entry->smbus.size = I2C_SMBUS_BLOCK_DATA_PEC;
#endif
- outb_p(((addr & 0x7f) << 1) | (read_write & 0x01),
+ outb_p(((entry->smbus.addr & 0x7f) << 1)
+ | (entry->smbus.read_write & 0x01),
SMBHSTADD);
- outb_p(command, SMBHSTCMD);
- block = 1;
+ outb_p(entry->smbus.command, SMBHSTCMD);
+ d->block = 1;
break;
case I2C_SMBUS_PROC_CALL:
default:
- dev_err(&I801_dev->dev, "Unsupported transaction %d\n", size);
- return -1;
+ dev_err(&I801_dev->dev, "Unsupported transaction %d\n",
+ entry->smbus.size);
+ entry->result = -EINVAL;
+ d->in_use = 0;
+ return;
}
#ifdef HAVE_PEC
- if(isich4 && hwpec) {
- if(size != I2C_SMBUS_QUICK &&
- size != I2C_SMBUS_I2C_BLOCK_DATA)
+ if (isich4 && d->hwpec) {
+ if (entry->smbus.size != I2C_SMBUS_QUICK &&
+ entry->smbus.size != I2C_SMBUS_I2C_BLOCK_DATA)
outb_p(1, SMBAUXCTL); /* enable HW PEC */
}
#endif
- if(block)
- ret = i801_block_transaction(data, read_write, size);
- else {
- outb_p(xact | ENABLE_INT9, SMBHSTCNT);
- ret = i801_transaction();
- }
-
-#ifdef HAVE_PEC
- if(isich4 && hwpec) {
- if(size != I2C_SMBUS_QUICK &&
- size != I2C_SMBUS_I2C_BLOCK_DATA)
- outb_p(0, SMBAUXCTL);
- }
-#endif
-
- if(block)
- return ret;
- if(ret)
- return -1;
- if ((read_write == I2C_SMBUS_WRITE) || (xact == I801_QUICK))
- return 0;
-
- switch (xact & 0x7f) {
- case I801_BYTE: /* Result put in SMBHSTDAT0 */
- case I801_BYTE_DATA:
- data->byte = inb_p(SMBHSTDAT0);
- break;
- case I801_WORD_DATA:
- data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8);
- break;
- }
- return 0;
+ if (d->block) {
+ i801_block_start(adap, entry, d);
+ if (entry->result < 0)
+ /* Error, finish the transaction */
+ i801_block_finish(adap, entry, d);
+ } else {
+ outb_p(d->xact | ENABLE_INT9, SMBHSTCNT);
+ i801_transaction_start(adap, entry, d);
+ if (entry->result < 0)
+ /* Error, finish the transaction */
+ i801_finish(adap, entry, d);
+ }
+
+ /* Wait extra long here, we want at least 2 ticks to guarantee
+ we wait >= 1 tick. */
+ entry->call_again_us = (1000000 / HZ) * 2;
+ entry->time_left = MAX_TIMEOUT_US;
+
+ if (d->finished) {
+ i2c_op_done(adap, entry);
+ d->in_use = 0;
+ } else
+ entry->data = d;
}
@@ -537,7 +678,8 @@
static struct i2c_algorithm smbus_algorithm = {
.name = "Non-I2C SMBus adapter",
.id = I2C_ALGO_SMBUS,
- .smbus_xfer = i801_access,
+ .smbus_start = i801_start,
+ .poll = i801_poll,
.functionality = i801_func,
};
@@ -545,6 +687,7 @@
.owner = THIS_MODULE,
.class = I2C_CLASS_HWMON,
.algo = &smbus_algorithm,
+ .algo_data = &i801_data,
.name = "unset",
};
@@ -563,7 +706,8 @@
MODULE_DEVICE_TABLE (pci, i801_ids);
-static int __devinit i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
+static int __devinit i801_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
{
if (i801_setup(dev)) {
@@ -582,6 +726,14 @@
static void __devexit i801_remove(struct pci_dev *dev)
{
+ struct i801_i2c_data *d = i801_adapter.algo_data;
+
+ /* Shut down any new requests and wait for any in-progress
+ operations to complete. */
+ d->in_removal = 1;
+ while (d->in_use)
+ mdelay(1);
+
i2c_del_adapter(&i801_adapter);
release_region(i801_smba, (isich4 ? 16 : 8));
}
^ permalink raw reply [flat|nested] 3+ messages in thread* Re: [PATCH] I2C patch 5 - Add a non-blocking interface to the I2C core (again)
2005-02-25 14:26 [PATCH] I2C patch 5 - Add a non-blocking interface to the I2C core (again) Corey Minyard
@ 2005-02-25 14:36 ` Arjan van de Ven
2005-02-25 15:01 ` [PATCH] I2C patch 5 - Add a non-blocking interface to the I2C core (one more time) Corey Minyard
0 siblings, 1 reply; 3+ messages in thread
From: Arjan van de Ven @ 2005-02-25 14:36 UTC (permalink / raw)
To: Corey Minyard; +Cc: lkml
> +/* Another note: This interface is extremely sensitive to timing and
> + failure handling. If you don't wait at least one jiffie after
> + starting the transaction before checking things, you will screw it
> + up. If you don't wait a jiffie after the final check, you will
> + screw it up. If you screw it up by these manners or by abandoning
> + an operation in progress, the I2C bus is likely stuck and won't
> + work any more. Gotta love this hardware. */
this sounds scary. Your "jiffie" in the comment, for which value of HZ
is that taken? Would you consider changing this to absolute time
instead?
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] I2C patch 5 - Add a non-blocking interface to the I2C core (one more time)
2005-02-25 14:36 ` Arjan van de Ven
@ 2005-02-25 15:01 ` Corey Minyard
0 siblings, 0 replies; 3+ messages in thread
From: Corey Minyard @ 2005-02-25 15:01 UTC (permalink / raw)
To: Arjan van de Ven; +Cc: lkml
[-- Attachment #1: Type: text/plain, Size: 806 bytes --]
Dang, this was the wrong patch. Although you are right, I'll look at
that for the patch you talk about.
here it is atain.
-Corey
Arjan van de Ven wrote:
>>+/* Another note: This interface is extremely sensitive to timing and
>>+ failure handling. If you don't wait at least one jiffie after
>>+ starting the transaction before checking things, you will screw it
>>+ up. If you don't wait a jiffie after the final check, you will
>>+ screw it up. If you screw it up by these manners or by abandoning
>>+ an operation in progress, the I2C bus is likely stuck and won't
>>+ work any more. Gotta love this hardware. */
>>
>>
>
>this sounds scary. Your "jiffie" in the comment, for which value of HZ
>is that taken? Would you consider changing this to absolute time
>instead?
>
>
>
[-- Attachment #2: i2c_add_non_blocking.diff --]
[-- Type: text/plain, Size: 9171 bytes --]
This patch adds a non-blocking interface to the I2C code. This
is needed by some RTC drivers on the I2C bus and is needed by
the IPMI code so it can do things at panic time.
The non-blocking interface requires changes to the driver
below it. The current drivers will work as-is, but will not
be able to use the non-blocking interfaces.
Signed-off-by: Corey Minyard <minyard@acm.org>
Index: linux-2.6.11-rc4/drivers/i2c/i2c-core.c
===================================================================
--- linux-2.6.11-rc4.orig/drivers/i2c/i2c-core.c
+++ linux-2.6.11-rc4/drivers/i2c/i2c-core.c
@@ -678,7 +678,8 @@
return;
if (sequence_match) {
- /* Poll will go here. */
+ /* This is the one we expected, call the poll routine. */
+ adap->algo->poll(adap, entry, t->next_call_time);
if (!entry_completed(entry))
i2c_start_timer(adap, entry);
@@ -695,12 +696,101 @@
* ----------------------------------------------------
*/
+/* Must be called with the q_lock held. */
+static void i2c_start_entry(struct i2c_adapter * adap,
+ struct i2c_op_q_entry * entry)
+{
+ entry->started = 1;
+ switch (entry->xfer_type) {
+ case I2C_OP_I2C:
+ adap->algo->master_start(adap, entry);
+ break;
+ case I2C_OP_SMBUS:
+ adap->algo->smbus_start(adap, entry);
+ break;
+ default:
+ entry->result = -EINVAL;
+ i2c_op_done(adap, entry);
+ }
+
+ if (!entry_completed(entry) && entry->use_timer)
+ i2c_start_timer(adap, entry);
+}
+
+static void i2c_wait_complete(struct i2c_op_q_entry * entry)
+{
+ struct completion *done = entry->handler_data;
+ pr_debug("i2c_wait_complete %p\n", entry);
+ complete(done);
+}
+
+static void i2c_perform_op_wait(struct i2c_adapter * adap,
+ struct i2c_op_q_entry * entry)
+{
+ struct completion done;
+ unsigned long flags;
+ struct i2c_algorithm *algo = adap->algo;
+
+ pr_debug("i2c_perform_op_wait %p %p\n", adap, entry);
+ init_completion(&done);
+ entry->start = NULL;
+ entry->handler = i2c_wait_complete;
+ entry->handler_data = &done;
+ entry->started = 0;
+ atomic_set(&entry->completed, 1);
+ entry->result = 0;
+ entry->use_timer = 0; /* We poll it directly. */
+ entry->data = NULL;
+ atomic_set(&entry->usecount, 1);
+ spin_lock_irqsave(&adap->q_lock, flags);
+ list_add_tail(&entry->link, &adap->q);
+ if (adap->q.next == &entry->link) {
+ /* Added to the list head, start it */
+ spin_unlock_irqrestore(&adap->q_lock, flags);
+ i2c_start_entry(adap, entry);
+ } else {
+ struct completion start;
+ init_completion(&start);
+ entry->start = &start;
+ spin_unlock_irqrestore(&adap->q_lock, flags);
+
+ wait_for_completion_interruptible(&start);
+
+ spin_lock_irqsave(&adap->q_lock, flags);
+ if (!entry->started) {
+ /* Operation was interrupted. There
+ is a race, we can't use the
+ wait_for_completion return code. */
+ entry->result = -ERESTARTSYS;
+ atomic_set(&entry->completed, 0);
+ list_del(&entry->link);
+ }
+ spin_unlock_irqrestore(&adap->q_lock, flags);
+ }
+
+ /* Once the operation is started, we will not
+ interrupt it. */
+ while (!entry_completed(entry)) {
+ unsigned int timeout = entry->call_again_us;
+ timeout += (USEC_PER_JIFFIE - 1);
+ timeout /= USEC_PER_JIFFIE;
+ if (timeout == 0)
+ timeout = 1;
+ wait_for_completion_timeout(&done, timeout);
+ if (entry_completed(entry))
+ break;
+ algo->poll(adap, entry, timeout * USEC_PER_JIFFIE);
+ }
+}
+
static void i2c_transfer_entry(struct i2c_adapter * adap,
struct i2c_op_q_entry * entry)
{
entry->xfer_type = I2C_OP_I2C;
entry->complete = NULL;
- if (adap->algo->master_xfer) {
+ if (adap->algo->master_start) {
+ i2c_perform_op_wait(adap, entry);
+ } else if (adap->algo->master_xfer) {
dev_dbg(&adap->dev, "master_xfer: with %d msgs.\n",
entry->i2c.num);
@@ -1406,7 +1496,9 @@
i2c_smbus_format_entry(adap, entry);
- if (algo->smbus_xfer) {
+ if (algo->smbus_start) {
+ i2c_perform_op_wait(adap, entry);
+ } else if (algo->smbus_xfer) {
down(&adap->bus_lock);
entry->result = adap->algo->smbus_xfer(adap,
entry->smbus.addr,
@@ -1430,6 +1522,63 @@
return result;
}
+int i2c_non_blocking_capable(struct i2c_adapter *adap)
+{
+ return adap->algo->poll != NULL;
+}
+
+void i2c_poll(struct i2c_client *client,
+ unsigned int us_since_last_call)
+{
+ struct i2c_adapter *adap = client->adapter;
+ struct i2c_op_q_entry *entry;
+
+ entry = i2c_entry_get(adap);
+ if (!entry)
+ return;
+ adap->algo->poll(adap, entry, us_since_last_call);
+ i2c_entry_put(adap, entry);
+}
+
+int i2c_non_blocking_op(struct i2c_client *client,
+ struct i2c_op_q_entry *entry)
+{
+ unsigned long flags;
+ struct i2c_adapter *adap = client->adapter;
+
+ if (!i2c_non_blocking_capable(adap))
+ return -ENOSYS;
+
+ entry->smbus.addr = client->addr;
+ entry->smbus.flags = client->flags;
+
+ if (entry->xfer_type == I2C_OP_SMBUS) {
+ i2c_smbus_format_entry(adap, entry);
+ if (!adap->algo->smbus_start) {
+ if (i2c_smbus_emu_format(adap, entry))
+ return -EINVAL;
+ }
+ }
+
+ entry->start = NULL;
+ entry->started = 0;
+ atomic_set(&entry->completed, 1);
+ entry->result = 0;
+ entry->use_timer = 1; /* Let the timer code poll it. */
+ entry->data = NULL;
+ atomic_set(&entry->usecount, 1);
+
+ spin_lock_irqsave(&adap->q_lock, flags);
+ list_add_tail(&entry->link, &adap->q);
+ if (adap->q.next == &entry->link) {
+ /* Added to the list head, start it */
+ spin_unlock_irqrestore(&adap->q_lock, flags);
+ i2c_start_entry(adap, entry);
+ } else
+ spin_unlock_irqrestore(&adap->q_lock, flags);
+ return 0;
+}
+
/* You should always define `functionality'; the 'else' is just for
backward compatibility. */
u32 i2c_get_functionality (struct i2c_adapter *adap)
@@ -1517,7 +1666,7 @@
entry->handler(entry);
if (new_entry) {
- /* start entry will go here. */
+ i2c_start_entry(adap, new_entry);
if (new_entry->start)
complete(new_entry->start);
/* Do tail recursion ourself. */
@@ -1587,6 +1736,10 @@
EXPORT_SYMBOL(i2c_smbus_write_block_data);
EXPORT_SYMBOL(i2c_smbus_read_i2c_block_data);
+EXPORT_SYMBOL(i2c_non_blocking_capable);
+EXPORT_SYMBOL(i2c_poll);
+EXPORT_SYMBOL(i2c_non_blocking_op);
+
EXPORT_SYMBOL(i2c_get_functionality);
EXPORT_SYMBOL(i2c_check_functionality);
Index: linux-2.6.11-rc4/include/linux/i2c.h
===================================================================
--- linux-2.6.11-rc4.orig/include/linux/i2c.h
+++ linux-2.6.11-rc4/include/linux/i2c.h
@@ -101,6 +101,23 @@
extern s32 i2c_smbus_read_i2c_block_data(struct i2c_client * client,
u8 command, u8 *values);
+/* Non-blocking interface. The user should fill out the public
+ portions of the entry structure. All data in the entry structure
+ should be guaranteed to be available until the handler callback is
+ called with the entry. */
+extern int i2c_non_blocking_op(struct i2c_client *client,
+ struct i2c_op_q_entry *entry);
+
+/* Can the adapter do non-blocking operations? */
+extern int i2c_non_blocking_capable(struct i2c_adapter *adap);
+
+/* Poll the i2c interface. This should only be called in a situation
+ where scheduling and interrupts are off. You should put the amount
+ of microseconds between calls in us_since_last_call. */
+extern void i2c_poll(struct i2c_client *client,
+ unsigned int us_since_last_call);
+
+
/*
* A driver is capable of handling one or more physical devices present on
* I2C adapters. This information is used to inform the driver of adapter
@@ -233,6 +250,31 @@
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data * data);
+ /* These are like the previous calls, but they will only start
+ the operation. The poll call will be called periodically
+ to drive the operation of the bus. Each of these calls
+ should set the result on an error, and set the timeout as
+ necessary. Note that even interrupt driven drivers need to
+ poll so they can time out operations. When the operation
+ is complete, these should call i2c_op_done(). Note that
+ all the data structures passed in are guaranteed to be kept
+ around until the operation completes. These may be called
+ from interrupt context. If the start operation fails, this
+ should set the result and call i2c_op_done(). */
+ void (*master_start)(struct i2c_adapter *adap,
+ struct i2c_op_q_entry *entry);
+ void (*smbus_start)(struct i2c_adapter *adap,
+ struct i2c_op_q_entry *entry);
+ /* us_since_last_poll is the amount of time since the last
+ time poll was called. Note that this may be *less* than the
+ time you requested, so always use this number and don't
+ assume it's the one you gave it. This time is approximate
+ and is only guaranteed to be >= the time since the last
+ poll. The value may be zero. */
+ void (*poll)(struct i2c_adapter *adap,
+ struct i2c_op_q_entry *entry,
+ unsigned int us_since_last_poll);
+
/* --- these optional/future use for some adapter types.*/
int (*slave_send)(struct i2c_adapter *,char*,int);
int (*slave_recv)(struct i2c_adapter *,char*,int);
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2005-02-25 15:01 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-02-25 14:26 [PATCH] I2C patch 5 - Add a non-blocking interface to the I2C core (again) Corey Minyard
2005-02-25 14:36 ` Arjan van de Ven
2005-02-25 15:01 ` [PATCH] I2C patch 5 - Add a non-blocking interface to the I2C core (one more time) Corey Minyard
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.