sb/intel/common: SMBus setup_command()
Implements the common parts of any SMBus transaction with a stub to log and recover (TBD) from timeout errors. Bits in SMBHSTCTL register are no longer preserved between transactions. Change-Id: I7ce14d3e895c30d595a94ce29ce0dc8cf51eb453 Signed-off-by: Kyösti Mälkki <kyosti.malkki@gmail.com> Reviewed-on: https://review.coreboot.org/c/21118 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Arthur Heymans <arthur@aheymans.xyz>
This commit is contained in:
parent
4c2ce72341
commit
957511cd92
|
@ -53,6 +53,10 @@
|
||||||
#define SMBHSTSTS_INTR (1 << 1)
|
#define SMBHSTSTS_INTR (1 << 1)
|
||||||
#define SMBHSTSTS_HOST_BUSY (1 << 0)
|
#define SMBHSTSTS_HOST_BUSY (1 << 0)
|
||||||
|
|
||||||
|
/* For SMBXMITADD register. */
|
||||||
|
#define XMIT_WRITE(dev) (((dev) << 1) | 0)
|
||||||
|
#define XMIT_READ(dev) (((dev) << 1) | 1)
|
||||||
|
|
||||||
#define SMBUS_TIMEOUT (10 * 1000 * 100)
|
#define SMBUS_TIMEOUT (10 * 1000 * 100)
|
||||||
#define SMBUS_BLOCK_MAXLEN 32
|
#define SMBUS_BLOCK_MAXLEN 32
|
||||||
|
|
||||||
|
@ -61,31 +65,40 @@ static void smbus_delay(void)
|
||||||
inb(0x80);
|
inb(0x80);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int smbus_wait_until_ready(u16 smbus_base)
|
static int recover_master(int smbus_base, int ret)
|
||||||
{
|
{
|
||||||
unsigned int loops = SMBUS_TIMEOUT;
|
/* TODO: Depending of the failure, drive KILL transaction
|
||||||
unsigned char byte;
|
* or force soft reset on SMBus master controller.
|
||||||
do {
|
*/
|
||||||
smbus_delay();
|
printk(BIOS_ERR, "SMBus: Fatal master timeout (%d)\n", ret);
|
||||||
if (--loops == 0)
|
return ret;
|
||||||
break;
|
|
||||||
byte = inb(smbus_base + SMBHSTSTAT);
|
|
||||||
} while (byte & SMBHSTSTS_HOST_BUSY);
|
|
||||||
return loops ? 0 : -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int smbus_wait_until_done(u16 smbus_base)
|
static int setup_command(unsigned int smbus_base, u8 ctrl, u8 xmitadd)
|
||||||
{
|
{
|
||||||
unsigned int loops = SMBUS_TIMEOUT;
|
unsigned int loops = SMBUS_TIMEOUT;
|
||||||
unsigned char byte;
|
u8 host_busy;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
smbus_delay();
|
smbus_delay();
|
||||||
if (--loops == 0)
|
host_busy = inb(smbus_base + SMBHSTSTAT) & SMBHSTSTS_HOST_BUSY;
|
||||||
break;
|
} while (--loops && host_busy);
|
||||||
byte = inb(smbus_base + SMBHSTSTAT);
|
|
||||||
} while ((byte & SMBHSTSTS_HOST_BUSY)
|
if (loops == 0)
|
||||||
|| (byte & ~(SMBHSTSTS_INUSE_STS | SMBHSTSTS_HOST_BUSY)) == 0);
|
return recover_master(smbus_base,
|
||||||
return loops ? 0 : -1;
|
SMBUS_WAIT_UNTIL_READY_TIMEOUT);
|
||||||
|
|
||||||
|
/* Clear any lingering errors, so the transaction will run. */
|
||||||
|
outb(inb(smbus_base + SMBHSTSTAT), smbus_base + SMBHSTSTAT);
|
||||||
|
|
||||||
|
/* Set up transaction */
|
||||||
|
/* Disable interrupts */
|
||||||
|
outb(ctrl, (smbus_base + SMBHSTCTL));
|
||||||
|
|
||||||
|
/* Set the device I'm talking to. */
|
||||||
|
outb(xmitadd, smbus_base + SMBXMITADD);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int smbus_wait_until_active(u16 smbus_base)
|
static int smbus_wait_until_active(u16 smbus_base)
|
||||||
|
@ -103,27 +116,34 @@ static int smbus_wait_until_active(u16 smbus_base)
|
||||||
return loops ? 0 : -1;
|
return loops ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int smbus_wait_until_done(u16 smbus_base)
|
||||||
|
{
|
||||||
|
unsigned int loops = SMBUS_TIMEOUT;
|
||||||
|
unsigned char byte;
|
||||||
|
do {
|
||||||
|
smbus_delay();
|
||||||
|
if (--loops == 0)
|
||||||
|
break;
|
||||||
|
byte = inb(smbus_base + SMBHSTSTAT);
|
||||||
|
} while ((byte & SMBHSTSTS_HOST_BUSY)
|
||||||
|
|| (byte & ~(SMBHSTSTS_INUSE_STS | SMBHSTSTS_HOST_BUSY)) == 0);
|
||||||
|
return loops ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
int do_smbus_read_byte(unsigned int smbus_base, u8 device,
|
int do_smbus_read_byte(unsigned int smbus_base, u8 device,
|
||||||
unsigned int address)
|
unsigned int address)
|
||||||
{
|
{
|
||||||
|
int ret;
|
||||||
unsigned char status;
|
unsigned char status;
|
||||||
unsigned char byte;
|
unsigned char byte;
|
||||||
|
|
||||||
if (smbus_wait_until_ready(smbus_base) < 0)
|
/* Set up for a byte data read. */
|
||||||
return SMBUS_WAIT_UNTIL_READY_TIMEOUT;
|
ret = setup_command(smbus_base, I801_BYTE_DATA, XMIT_READ(device));
|
||||||
/* Set up transaction */
|
if (ret < 0)
|
||||||
/* Disable interrupts */
|
return ret;
|
||||||
outb(inb(smbus_base + SMBHSTCTL) & ~SMBHSTCNT_INTREN,
|
|
||||||
smbus_base + SMBHSTCTL);
|
|
||||||
/* Set the device I'm talking to */
|
|
||||||
outb(((device & 0x7f) << 1) | 1, smbus_base + SMBXMITADD);
|
|
||||||
/* Set the command/address... */
|
/* Set the command/address... */
|
||||||
outb(address & 0xff, smbus_base + SMBHSTCMD);
|
outb(address, smbus_base + SMBHSTCMD);
|
||||||
/* Set up for a byte data read */
|
|
||||||
outb((inb(smbus_base + SMBHSTCTL) & 0xe3) | I801_BYTE_DATA,
|
|
||||||
(smbus_base + SMBHSTCTL));
|
|
||||||
/* Clear any lingering errors, so the transaction will run */
|
|
||||||
outb(inb(smbus_base + SMBHSTSTAT), smbus_base + SMBHSTSTAT);
|
|
||||||
|
|
||||||
/* Clear the data byte... */
|
/* Clear the data byte... */
|
||||||
outb(0, smbus_base + SMBHSTDAT0);
|
outb(0, smbus_base + SMBHSTDAT0);
|
||||||
|
@ -156,25 +176,17 @@ int do_smbus_write_byte(unsigned int smbus_base, u8 device,
|
||||||
unsigned int address, unsigned int data)
|
unsigned int address, unsigned int data)
|
||||||
{
|
{
|
||||||
unsigned char status;
|
unsigned char status;
|
||||||
|
int ret;
|
||||||
|
|
||||||
if (smbus_wait_until_ready(smbus_base) < 0)
|
/* Set up for a byte data write. */
|
||||||
return SMBUS_WAIT_UNTIL_READY_TIMEOUT;
|
ret = setup_command(smbus_base, I801_BYTE_DATA, XMIT_WRITE(device));
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
/* Set up transaction */
|
|
||||||
/* Disable interrupts */
|
|
||||||
outb(inb(smbus_base + SMBHSTCTL) & ~SMBHSTCNT_INTREN,
|
|
||||||
smbus_base + SMBHSTCTL);
|
|
||||||
/* Set the device I'm talking to */
|
|
||||||
outb(((device & 0x7f) << 1) & ~0x01, smbus_base + SMBXMITADD);
|
|
||||||
/* Set the command/address... */
|
/* Set the command/address... */
|
||||||
outb(address & 0xff, smbus_base + SMBHSTCMD);
|
outb(address, smbus_base + SMBHSTCMD);
|
||||||
/* Set up for a byte data read */
|
|
||||||
outb((inb(smbus_base + SMBHSTCTL) & 0xe3) | I801_BYTE_DATA,
|
|
||||||
(smbus_base + SMBHSTCTL));
|
|
||||||
/* Clear any lingering errors, so the transaction will run */
|
|
||||||
outb(inb(smbus_base + SMBHSTSTAT), smbus_base + SMBHSTSTAT);
|
|
||||||
|
|
||||||
/* Clear the data byte... */
|
/* Set the data byte... */
|
||||||
outb(data, smbus_base + SMBHSTDAT0);
|
outb(data, smbus_base + SMBHSTDAT0);
|
||||||
|
|
||||||
/* Start the command */
|
/* Start the command */
|
||||||
|
@ -205,27 +217,19 @@ int do_smbus_block_read(unsigned int smbus_base, u8 device, u8 cmd,
|
||||||
unsigned int max_bytes, u8 *buf)
|
unsigned int max_bytes, u8 *buf)
|
||||||
{
|
{
|
||||||
u8 status;
|
u8 status;
|
||||||
int slave_bytes;
|
int ret, slave_bytes;
|
||||||
int bytes_read = 0;
|
int bytes_read = 0;
|
||||||
unsigned int loops = SMBUS_TIMEOUT;
|
unsigned int loops = SMBUS_TIMEOUT;
|
||||||
if (smbus_wait_until_ready(smbus_base) < 0)
|
|
||||||
return SMBUS_WAIT_UNTIL_READY_TIMEOUT;
|
|
||||||
|
|
||||||
max_bytes = MIN(SMBUS_BLOCK_MAXLEN, max_bytes);
|
max_bytes = MIN(SMBUS_BLOCK_MAXLEN, max_bytes);
|
||||||
|
|
||||||
/* Set up transaction */
|
/* Set up for a block data read. */
|
||||||
/* Disable interrupts */
|
ret = setup_command(smbus_base, I801_BLOCK_DATA, XMIT_READ(device));
|
||||||
outb(inb(smbus_base + SMBHSTCTL) & ~SMBHSTCNT_INTREN,
|
if (ret < 0)
|
||||||
smbus_base + SMBHSTCTL);
|
return ret;
|
||||||
/* Set the device I'm talking to */
|
|
||||||
outb(((device & 0x7f) << 1) | 1, smbus_base + SMBXMITADD);
|
|
||||||
/* Set the command/address... */
|
/* Set the command/address... */
|
||||||
outb(cmd & 0xff, smbus_base + SMBHSTCMD);
|
outb(cmd, smbus_base + SMBHSTCMD);
|
||||||
/* Set up for a block data read */
|
|
||||||
outb((inb(smbus_base + SMBHSTCTL) & 0xc3) | I801_BLOCK_DATA,
|
|
||||||
(smbus_base + SMBHSTCTL));
|
|
||||||
/* Clear any lingering errors, so the transaction will run */
|
|
||||||
outb(inb(smbus_base + SMBHSTSTAT), smbus_base + SMBHSTSTAT);
|
|
||||||
|
|
||||||
/* Reset number of bytes to transfer so we notice later it
|
/* Reset number of bytes to transfer so we notice later it
|
||||||
* was really updated with the transaction. */
|
* was really updated with the transaction. */
|
||||||
|
@ -279,30 +283,21 @@ int do_smbus_block_write(unsigned int smbus_base, u8 device, u8 cmd,
|
||||||
const unsigned int bytes, const u8 *buf)
|
const unsigned int bytes, const u8 *buf)
|
||||||
{
|
{
|
||||||
u8 status;
|
u8 status;
|
||||||
int bytes_sent = 0;
|
int ret, bytes_sent = 0;
|
||||||
unsigned int loops = SMBUS_TIMEOUT;
|
unsigned int loops = SMBUS_TIMEOUT;
|
||||||
|
|
||||||
if (bytes > SMBUS_BLOCK_MAXLEN)
|
if (bytes > SMBUS_BLOCK_MAXLEN)
|
||||||
return SMBUS_ERROR;
|
return SMBUS_ERROR;
|
||||||
|
|
||||||
if (smbus_wait_until_ready(smbus_base) < 0)
|
/* Set up for a block data write. */
|
||||||
return SMBUS_WAIT_UNTIL_READY_TIMEOUT;
|
ret = setup_command(smbus_base, I801_BLOCK_DATA, XMIT_WRITE(device));
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
/* Set up transaction */
|
|
||||||
/* Disable interrupts */
|
|
||||||
outb(inb(smbus_base + SMBHSTCTL) & ~SMBHSTCNT_INTREN,
|
|
||||||
smbus_base + SMBHSTCTL);
|
|
||||||
/* Set the device I'm talking to */
|
|
||||||
outb(((device & 0x7f) << 1) & ~0x01, smbus_base + SMBXMITADD);
|
|
||||||
/* Set the command/address... */
|
/* Set the command/address... */
|
||||||
outb(cmd & 0xff, smbus_base + SMBHSTCMD);
|
outb(cmd, smbus_base + SMBHSTCMD);
|
||||||
/* Set up for a block data write */
|
|
||||||
outb((inb(smbus_base + SMBHSTCTL) & 0xc3) | I801_BLOCK_DATA,
|
|
||||||
(smbus_base + SMBHSTCTL));
|
|
||||||
/* Clear any lingering errors, so the transaction will run */
|
|
||||||
outb(inb(smbus_base + SMBHSTSTAT), smbus_base + SMBHSTSTAT);
|
|
||||||
|
|
||||||
/* set number of bytes to transfer */
|
/* Set number of bytes to transfer. */
|
||||||
outb(bytes, smbus_base + SMBHSTDAT0);
|
outb(bytes, smbus_base + SMBHSTDAT0);
|
||||||
|
|
||||||
/* Send first byte from buffer, bytes_sent increments after
|
/* Send first byte from buffer, bytes_sent increments after
|
||||||
|
@ -354,27 +349,23 @@ int do_i2c_block_read(unsigned int smbus_base, u8 device,
|
||||||
unsigned int offset, const unsigned int bytes, u8 *buf)
|
unsigned int offset, const unsigned int bytes, u8 *buf)
|
||||||
{
|
{
|
||||||
u8 status;
|
u8 status;
|
||||||
int bytes_read = 0;
|
int ret, bytes_read = 0;
|
||||||
unsigned int loops = SMBUS_TIMEOUT;
|
unsigned int loops = SMBUS_TIMEOUT;
|
||||||
if (smbus_wait_until_ready(smbus_base) < 0)
|
|
||||||
return SMBUS_WAIT_UNTIL_READY_TIMEOUT;
|
|
||||||
|
|
||||||
/* Set upp transaction */
|
/* Set up for a i2c block data read.
|
||||||
/* Disable interrupts */
|
*
|
||||||
outb(inb(smbus_base + SMBHSTCTL) & SMBHSTCNT_INTREN,
|
* FIXME: Address parameter changes to XMIT_READ(device) with
|
||||||
smbus_base + SMBHSTCTL);
|
* some revision of PCH. Presumably hardware revisions that
|
||||||
/* Set the device I'm talking to */
|
* do not have i2c block write support internally set LSB.
|
||||||
outb((device & 0x7f) << 1, smbus_base + SMBXMITADD);
|
*/
|
||||||
|
ret = setup_command(smbus_base, I801_I2C_BLOCK_DATA,
|
||||||
|
XMIT_WRITE(device));
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
/* device offset */
|
/* device offset */
|
||||||
outb(offset, smbus_base + SMBHSTDAT1);
|
outb(offset, smbus_base + SMBHSTDAT1);
|
||||||
|
|
||||||
/* Set up for a i2c block data read */
|
|
||||||
outb((inb(smbus_base + SMBHSTCTL) & 0xc3) | I801_I2C_BLOCK_DATA,
|
|
||||||
(smbus_base + SMBHSTCTL));
|
|
||||||
|
|
||||||
/* Clear any lingering errors, so the transaction will run */
|
|
||||||
outb(inb(smbus_base + SMBHSTSTAT), smbus_base + SMBHSTSTAT);
|
|
||||||
/* Start the command */
|
/* Start the command */
|
||||||
outb((inb(smbus_base + SMBHSTCTL) | SMBHSTCNT_START),
|
outb((inb(smbus_base + SMBHSTCTL) | SMBHSTCNT_START),
|
||||||
smbus_base + SMBHSTCTL);
|
smbus_base + SMBHSTCTL);
|
||||||
|
|
Loading…
Reference in New Issue