[i2c] [PATCH] change to i2c-i801 driver to use internal 32-byte buffer on ICH4+
Oleg Ryjkov
olegr at google.com
Thu Jun 7 20:46:09 CEST 2007
Hi Jean, hi all.
This patch adds an ability to utilize the internal SRAM buffer on ICH4
and newer host controllers to speed up execution of block operations.
I've split the code so that it is more clear which block transaction is
performed.
Firs of all the host controller's type is identified. isich4 is set when
we think that the controller has the internal buffer.
Before every block transaction, we check whether the E32B bit in
SMBAUXCTL register is set(to enable the controller's buffer), otherwise
we do an attempt to enable it.
I've also made some cosmetic changes to i801_transaction, introducing
some defines for status bits and an attempt to kill the current
trancaction if it has timed out.
Oleg
Signed-off-by: Oleg Ryjkov <olegr at google.com>
--- linux-2.6-untouched/drivers/i2c/busses/i2c-i801.c 2007-05-17 16:10:33.000000000 -0700
+++ linux-2.6/drivers/i2c/busses/i2c-i801.c 2007-06-07 11:38:24.000000000 -0700
@@ -68,6 +68,11 @@
/* PCI Address Constants */
#define SMBBAR 4
#define SMBHSTCFG 0x040
+/* 32 byte block bit for SMBHSTAUXCTL */
+#define SMBAUXCTL_E32B 2
+
+/* kill bit for SMBHSTCNT */
+#define SMBHSTCNT_KILL 2
/* Host configuration bits for SMBHSTCFG */
#define SMBHSTCFG_HST_EN 1
@@ -91,8 +96,17 @@
#define I801_START 0x40
#define I801_PEC_EN 0x80 /* ICH4 only */
+/* I801 Hosts Status register bits */
+#define I801_HST_STS_BYTE_DONE (0x01<<7)
+#define I801_HST_STS_INUSE_STS (0x01<<6)
+#define I801_HST_STS_SMBALERT_STS (0x01<<5)
+#define I801_HST_STS_FAILED (0x01<<4)
+#define I801_HST_STS_BUS_ERR (0x01<<3)
+#define I801_HST_STS_DEV_ERR (0x01<<2)
+#define I801_HST_STS_INTR (0x01<<1)
+#define I801_HST_STS_HOST_BUSY (0x01<<0)
-static int i801_transaction(void);
+static int i801_transaction(int xact);
static int i801_block_transaction(union i2c_smbus_data *data, char read_write,
int command, int hwpec);
@@ -102,7 +116,7 @@
static struct pci_dev *I801_dev;
static int isich4;
-static int i801_transaction(void)
+static int i801_transaction(int xact)
{
int temp;
int result = 0;
@@ -127,13 +141,15 @@
}
}
- outb_p(inb(SMBHSTCNT) | I801_START, SMBHSTCNT);
+ /* the current contents of SMBHSTCNT can be overwritten, since PEC,
+ * INTREN, SMBSCMD are passed in xact */
+ outb_p( xact | 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));
+ } while ((temp & I801_HST_STS_HOST_BUSY) && (timeout++ < MAX_TIMEOUT));
/* If the SMBus is still busy, we give up */
if (timeout >= MAX_TIMEOUT) {
@@ -141,19 +157,27 @@
result = -1;
}
- if (temp & 0x10) {
+ /* try to stop the current command */
+ if (temp & I801_HST_STS_HOST_BUSY) {
+ dev_dbg(&I801_dev->dev,"Terminating the current operation\n");
+ outb_p(inb_p(SMBHSTCNT) | SMBHSTCNT_KILL, SMBHSTCNT);
+ msleep(1);
+ outb_p(inb_p(SMBHSTCNT) & (~SMBHSTCNT_KILL), SMBHSTCNT);
+ }
+
+ if (temp & I801_HST_STS_FAILED) {
result = -1;
dev_dbg(&I801_dev->dev, "Error: Failed bus transaction\n");
}
- if (temp & 0x08) {
+ if (temp & I801_HST_STS_BUS_ERR) {
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 */
}
- if (temp & 0x04) {
+ if (temp & I801_HST_STS_DEV_ERR) {
result = -1;
dev_dbg(&I801_dev->dev, "Error: no response!\n");
}
@@ -172,45 +196,65 @@
return result;
}
-/* All-inclusive block transaction function */
-static int i801_block_transaction(union i2c_smbus_data *data, char read_write,
- int command, int hwpec)
+/* wait for INTR bit as advised by Intel */
+static void i801_wait_hwpec(void)
{
- int i, len;
- int smbcmd;
+ int timeout = 0;
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;
- }
+ do {
+ msleep(1);
+ temp = inb_p(SMBHSTSTS);
+ } while ((!(temp & I801_HST_STS_INTR))
+ && (timeout++ < MAX_TIMEOUT));
+
+ if (timeout >= MAX_TIMEOUT) {
+ dev_dbg(&I801_dev->dev, "PEC Timeout!\n");
}
+ outb_p(temp, SMBHSTSTS);
+}
- if (read_write == I2C_SMBUS_WRITE) {
- len = data->block[0];
+
+static int i801_block_transaction_by_block(union i2c_smbus_data *data, char read_write,
+ int len, int hwpec) {
+ int i;
+
+ inb_p(SMBHSTCNT); /* reset the data buffer index */
+
+ /* Use 32-byte buffer to process this transaction */
+ if (read_write == I2C_SMBUS_WRITE)
+ for (i = 0; i < len; i += 1 )
+ outb_p(data->block[i+1], SMBBLKDAT);
+
+ if (i801_transaction(I801_BLOCK_DATA | ENABLE_INT9 | hwpec))
+ return -1;
+
+ if (read_write == I2C_SMBUS_READ) {
+ len = inb_p(SMBHSTDAT0);
if (len < 1)
len = 1;
if (len > 32)
len = 32;
- outb_p(len, SMBHSTDAT0);
- outb_p(data->block[1], SMBBLKDAT);
- } else {
- len = 32; /* max for reads */
- }
- if(isich4 && command != I2C_SMBUS_I2C_BLOCK_DATA) {
- /* set 32 byte buffer */
+ data->block[0] = len;
+ for (i = 0; i < len; i += 1)
+ data->block[i + 1] = inb_p(SMBBLKDAT);
}
+ return 0;
+}
+
+static int i801_block_transaction_byte_by_byte(union i2c_smbus_data *data, char read_write,
+ int len, int hwpec) {
+
+ int temp;
+ int i;
+ int result = 0;
+ int smbcmd;
+ int timeout;
+ unsigned char errmask;
+
+ if (read_write == I2C_SMBUS_WRITE)
+ outb_p(len, SMBHSTDAT0);
for (i = 1; i <= len; i++) {
if (i == len && read_write == I2C_SMBUS_READ)
@@ -227,13 +271,14 @@
/* 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;
+ /* Erronenous conditions before transaction:
+ * Byte_Done, Failed, Bus_Err, Dev_Err, Intr,
+ * Host_Busy */
+ errmask=0x9f;
} else {
- /* Erronenous conditions during transaction:
+ /* Erronenous conditions during transaction:
* Failed, Bus_Err, Dev_Err, Intr */
- errmask=0x1e;
+ errmask=0x1e;
}
if (temp & errmask) {
dev_dbg(&I801_dev->dev, "SMBus busy (%02x). "
@@ -242,14 +287,11 @@
if (((temp = inb_p(SMBHSTSTS)) & errmask) != 0x00) {
dev_err(&I801_dev->dev,
"Reset failed! (%02x)\n", temp);
- result = -1;
- goto END;
+ return -1;
}
- if (i != 1) {
+ if (i != 1)
/* if die in middle of block transaction, fail */
- result = -1;
- goto END;
- }
+ return -1;
}
if (i == 1)
@@ -261,8 +303,8 @@
msleep(1);
temp = inb_p(SMBHSTSTS);
}
- while ((!(temp & 0x80))
- && (timeout++ < MAX_TIMEOUT));
+ while ((!(temp & 0x80))
+ && (timeout++ < MAX_TIMEOUT));
/* If the SMBus is still busy, we give up */
if (timeout >= MAX_TIMEOUT) {
@@ -310,25 +352,70 @@
inb_p(SMBHSTDAT0), inb_p(SMBBLKDAT));
if (result < 0)
- goto END;
+ return result;
}
- if (hwpec) {
- /* wait for INTR bit as advised by Intel */
- timeout = 0;
- do {
- msleep(1);
- temp = inb_p(SMBHSTSTS);
- } while ((!(temp & 0x02))
- && (timeout++ < MAX_TIMEOUT));
+ return result;
+}
- if (timeout >= MAX_TIMEOUT) {
- dev_dbg(&I801_dev->dev, "PEC Timeout!\n");
+static int i801_try_set_block_buffer_mode(void)
+{
+ int temp;
+ temp = inb_p(SMBAUXCTL);
+ if (temp & SMBAUXCTL_E32B)
+ return 0;
+ dev_dbg(&I801_dev->dev, "Enabling 32-bit data buffer\n");
+ /* enable 32-bit data buffer */
+ outb_p(inb_p(SMBAUXCTL) | SMBAUXCTL_E32B, SMBAUXCTL);
+ if ((inb_p(SMBAUXCTL) & SMBAUXCTL_E32B) == 0) {
+ dev_err(&I801_dev->dev, "Failed to set E32B bit\n");
+ return -1;
+ }
+ return 0;
+}
+
+/* All-inclusive block transaction function */
+static int i801_block_transaction(union i2c_smbus_data *data, char read_write,
+ int command, int hwpec)
+{
+ int len;
+ int result = 0;
+ unsigned char hostc;
+
+ 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;
}
- outb_p(temp, SMBHSTSTS);
}
- result = 0;
-END:
+
+ 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);
+ } else {
+ len = 32; /* max for reads */
+ }
+
+ if (isich4 && command != I2C_SMBUS_I2C_BLOCK_DATA
+ && i801_try_set_block_buffer_mode() == 0 )
+ result = i801_block_transaction_by_block(data, read_write, len, hwpec);
+ else
+ result = i801_block_transaction_byte_by_byte(data, read_write, len, hwpec);
+
+ if (result == 0 && hwpec)
+ i801_wait_hwpec();
+
if (command == I2C_SMBUS_I2C_BLOCK_DATA) {
/* restore saved configuration register value */
pci_write_config_byte(I801_dev, SMBHSTCFG, hostc);
@@ -393,19 +480,20 @@
return -1;
}
- outb_p(hwpec, SMBAUXCTL); /* enable/disable hardware PEC */
+ if (hwpec)
+ outb_p(inb_p(SMBAUXCTL) | hwpec, SMBAUXCTL); /* enable/disable
+ hardware PEC */
if(block)
ret = i801_block_transaction(data, read_write, size, hwpec);
- else {
- outb_p(xact | ENABLE_INT9, SMBHSTCNT);
- ret = i801_transaction();
- }
+ else
+ ret = i801_transaction(xact | ENABLE_INT9);
/* Some BIOSes don't like it when PEC is enabled at reboot or resume
- time, so we forcibly disable it after every transaction. */
+ * time, so we forcibly disable it after every transaction. Make sure
+ * we preserve the E32B bit. */
if (hwpec)
- outb_p(0, SMBAUXCTL);
+ outb_p(inb_p(SMBAUXCTL) & 0xfe, SMBAUXCTL);
if(block)
return ret;
@@ -480,10 +568,13 @@
case PCI_DEVICE_ID_INTEL_ESB2_17:
case PCI_DEVICE_ID_INTEL_ICH8_5:
case PCI_DEVICE_ID_INTEL_ICH9_6:
+ /* ICH4 and the newer host controllers also support 32-byte
+ * data buffer */
isich4 = 1;
break;
default:
isich4 = 0;
+ dev_dbg(&dev->dev, "32-bit data buffer is not present\n");
}
err = pci_enable_device(dev);
More information about the i2c
mailing list