diff options
author | Arthur Heymans <arthur@aheymans.xyz> | 2017-04-12 17:01:31 +0200 |
---|---|---|
committer | Martin Roth <martinroth@google.com> | 2017-08-06 23:26:15 +0000 |
commit | 16fe79048f5254661ff2342aa481cbb44657b7ff (patch) | |
tree | 5ad72bc5c5a97ca9a7a47f5ab24bbe622f12e9e9 /src/southbridge/intel/common | |
parent | 12d010306b3892b01350e96d83275206215d9f31 (diff) |
sb/intel/*: Use common SMBus functions
All Intel southbridges implement the same SMBus functions.
This patch replaces all these similar and mostly identical
implementations with a common file.
This also makes i2c block read available to all those southbridges.
If the northbridge has to read a lot of SPD bytes sequentially, using
this function can reduce the time being spent to read SPD five-fold.
Change-Id: I93bb186e04e8c32dff04fc1abe4b5ecbc4c9c962
Signed-off-by: Arthur Heymans <arthur@aheymans.xyz>
Reviewed-on: https://review.coreboot.org/19258
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Kyösti Mälkki <kyosti.malkki@gmail.com>
Diffstat (limited to 'src/southbridge/intel/common')
-rw-r--r-- | src/southbridge/intel/common/Kconfig | 2 | ||||
-rw-r--r-- | src/southbridge/intel/common/Makefile.inc | 4 | ||||
-rw-r--r-- | src/southbridge/intel/common/smbus.c | 365 | ||||
-rw-r--r-- | src/southbridge/intel/common/smbus.h | 46 |
4 files changed, 417 insertions, 0 deletions
diff --git a/src/southbridge/intel/common/Kconfig b/src/southbridge/intel/common/Kconfig index 7bc686defa..23fb8cea5b 100644 --- a/src/southbridge/intel/common/Kconfig +++ b/src/southbridge/intel/common/Kconfig @@ -2,3 +2,5 @@ config SOUTHBRIDGE_INTEL_COMMON def_bool n config SOUTHBRIDGE_INTEL_COMMON_GPIO def_bool n +config SOUTHBRIDGE_INTEL_COMMON_SMBUS + def_bool n diff --git a/src/southbridge/intel/common/Makefile.inc b/src/southbridge/intel/common/Makefile.inc index 56ba56fc01..5810394bcb 100644 --- a/src/southbridge/intel/common/Makefile.inc +++ b/src/southbridge/intel/common/Makefile.inc @@ -24,4 +24,8 @@ ramstage-$(CONFIG_USBDEBUG) += usb_debug.c romstage-$(CONFIG_SOUTHBRIDGE_INTEL_COMMON_GPIO) += gpio.c ramstage-$(CONFIG_SOUTHBRIDGE_INTEL_COMMON_GPIO) += gpio.c smm-$(CONFIG_SOUTHBRIDGE_INTEL_COMMON_GPIO) += gpio.c + +romstage-$(CONFIG_SOUTHBRIDGE_INTEL_COMMON_SMBUS) += smbus.c +ramstage-$(CONFIG_SOUTHBRIDGE_INTEL_COMMON_SMBUS) += smbus.c + endif diff --git a/src/southbridge/intel/common/smbus.c b/src/southbridge/intel/common/smbus.c new file mode 100644 index 0000000000..cac9ba7943 --- /dev/null +++ b/src/southbridge/intel/common/smbus.c @@ -0,0 +1,365 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2005 Yinghai Lu <yinghailu@gmail.com> + * Copyright (C) 2009 coresystems GmbH + * Copyright (C) 2013 Vladimir Serbinenko + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <arch/io.h> +#include <device/smbus_def.h> +#include "smbus.h" + + +/* I801 command constants */ +#define I801_QUICK (0 << 2) +#define I801_BYTE (1 << 2) +#define I801_BYTE_DATA (2 << 2) +#define I801_WORD_DATA (3 << 2) +#define I801_BLOCK_DATA (5 << 2) +#define I801_I2C_BLOCK_DATA (6 << 2) /* ICH5 and later */ + +/* I801 Host Control register bits */ +#define SMBHSTCNT_INTREN (1 << 0) +#define SMBHSTCNT_KILL (1 << 1) +#define SMBHSTCNT_LAST_BYTE (1 << 5) +#define SMBHSTCNT_START (1 << 6) +#define SMBHSTCNT_PEC_EN (1 << 7) /* ICH3 and later */ + +/* I801 Hosts Status register bits */ +#define SMBHSTSTS_BYTE_DONE (1 << 7) +#define SMBHSTSTS_INUSE_STS (1 << 6) +#define SMBHSTSTS_SMBALERT_STS (1 << 5) +#define SMBHSTSTS_FAILED (1 << 4) +#define SMBHSTSTS_BUS_ERR (1 << 3) +#define SMBHSTSTS_DEV_ERR (1 << 2) +#define SMBHSTSTS_INTR (1 << 1) +#define SMBHSTSTS_HOST_BUSY (1 << 0) + +#define SMBUS_TIMEOUT (10 * 1000 * 100) + +static void smbus_delay(void) +{ + inb(0x80); +} + +static int smbus_wait_until_ready(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); + 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; +} + +static int smbus_wait_until_active(u16 smbus_base) +{ + unsigned long loops; + loops = SMBUS_TIMEOUT; + do { + unsigned char val; + smbus_delay(); + val = inb(smbus_base + SMBHSTSTAT); + if ((val & SMBHSTSTS_HOST_BUSY)) { + break; + } + } while (--loops); + return loops ? 0 : -1; +} + +int do_smbus_read_byte(unsigned int smbus_base, u8 device, + unsigned int address) +{ + unsigned char status; + unsigned char byte; + + if (smbus_wait_until_ready(smbus_base) < 0) + return SMBUS_WAIT_UNTIL_READY_TIMEOUT; + /* Set up transaction */ + /* Disable interrupts */ + outb(inb(smbus_base + SMBHSTCTL) & ~SMBHSTCNT_INTREN, + smbus_base + SMBHSTCTL); + /* Set the device I'm talking too */ + outb(((device & 0x7f) << 1) | 1, smbus_base + SMBXMITADD); + /* Set the command/address... */ + outb(address & 0xff, 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... */ + outb(0, smbus_base + SMBHSTDAT0); + + /* Start the command */ + outb((inb(smbus_base + SMBHSTCTL) | SMBHSTCNT_START), + smbus_base + SMBHSTCTL); + + /* poll for it to start */ + if (smbus_wait_until_active(smbus_base) < 0) + return SMBUS_WAIT_UNTIL_ACTIVE_TIMEOUT; + + /* Poll for transaction completion */ + if (smbus_wait_until_done(smbus_base) < 0) + return SMBUS_WAIT_UNTIL_DONE_TIMEOUT; + + status = inb(smbus_base + SMBHSTSTAT); + + /* Ignore the "In Use" status... */ + status &= ~(SMBHSTSTS_SMBALERT_STS | SMBHSTSTS_INUSE_STS); + + /* Read results of transaction */ + byte = inb(smbus_base + SMBHSTDAT0); + if (status != SMBHSTSTS_INTR) + return SMBUS_ERROR; + return byte; +} + +int do_smbus_write_byte(unsigned int smbus_base, u8 device, + unsigned int address, unsigned int data) +{ + unsigned char status; + + if (smbus_wait_until_ready(smbus_base) < 0) + return SMBUS_WAIT_UNTIL_READY_TIMEOUT; + + /* Set up transaction */ + /* Disable interrupts */ + outb(inb(smbus_base + SMBHSTCTL) & ~SMBHSTCNT_INTREN, + smbus_base + SMBHSTCTL); + /* Set the device I'm talking too */ + outb(((device & 0x7f) << 1) & ~0x01, smbus_base + SMBXMITADD); + /* Set the command/address... */ + outb(address & 0xff, 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... */ + outb(data, smbus_base + SMBHSTDAT0); + + /* Start the command */ + outb((inb(smbus_base + SMBHSTCTL) | SMBHSTCNT_START), + smbus_base + SMBHSTCTL); + + /* poll for it to start */ + if (smbus_wait_until_active(smbus_base) < 0) + return SMBUS_WAIT_UNTIL_ACTIVE_TIMEOUT; + + /* Poll for transaction completion */ + if (smbus_wait_until_done(smbus_base) < 0) + return SMBUS_WAIT_UNTIL_DONE_TIMEOUT; + + status = inb(smbus_base + SMBHSTSTAT); + + /* Ignore the "In Use" status... */ + status &= ~(SMBHSTSTS_SMBALERT_STS | SMBHSTSTS_INUSE_STS); + + /* Read results of transaction */ + if (status != SMBHSTSTS_INTR) + return SMBUS_ERROR; + + return 0; +} + +int do_smbus_block_read(unsigned int smbus_base, u8 device, u8 cmd, + unsigned int bytes, u8 *buf) +{ + u8 status; + int bytes_read = 0; + unsigned int loops = SMBUS_TIMEOUT; + if (smbus_wait_until_ready(smbus_base) < 0) + return SMBUS_WAIT_UNTIL_READY_TIMEOUT; + + /* Set up transaction */ + /* Disable interrupts */ + outb(inb(smbus_base + SMBHSTCTL) & ~SMBHSTCNT_INTREN, + smbus_base + SMBHSTCTL); + /* Set the device I'm talking too */ + outb(((device & 0x7f) << 1) | 1, smbus_base + SMBXMITADD); + /* Set the command/address... */ + outb(cmd & 0xff, smbus_base + SMBHSTCMD); + /* Set up for a block data read */ + outb((inb(smbus_base + SMBHSTCTL) & 0xe3) | I801_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 */ + outb((inb(smbus_base + SMBHSTCTL) | SMBHSTCNT_START), + smbus_base + SMBHSTCTL); + + /* poll for it to start */ + if (smbus_wait_until_active(smbus_base) < 0) + return SMBUS_WAIT_UNTIL_ACTIVE_TIMEOUT; + + /* Poll for transaction completion */ + do { + loops--; + status = inb(smbus_base + SMBHSTSTAT); + if (status & (SMBHSTSTS_FAILED | /* FAILED */ + SMBHSTSTS_BUS_ERR | /* BUS ERR */ + SMBHSTSTS_DEV_ERR)) /* DEV ERR */ + return SMBUS_ERROR; + + if (status & SMBHSTSTS_BYTE_DONE) { /* Byte done */ + *buf = inb(smbus_base + SMBBLKDAT); + buf++; + bytes_read++; + if (--bytes == 1) { + /* indicate that next byte is the last one */ + outb(inb(smbus_base + SMBHSTCTL) + | SMBHSTCNT_LAST_BYTE, + smbus_base + SMBHSTCTL); + } + outb(status, smbus_base + SMBHSTSTAT); + } + } while ((status & SMBHSTSTS_HOST_BUSY) && loops); + + return bytes_read; +} + +int do_smbus_block_write(unsigned int smbus_base, u8 device, u8 cmd, + unsigned int bytes, const u8 *buf) +{ + u8 status; + unsigned int loops = SMBUS_TIMEOUT; + + if (smbus_wait_until_ready(smbus_base) < 0) + return SMBUS_WAIT_UNTIL_READY_TIMEOUT; + + /* Set up transaction */ + /* Disable interrupts */ + outb(inb(smbus_base + SMBHSTCTL) & ~SMBHSTCNT_INTREN, + smbus_base + SMBHSTCTL); + /* Set the device I'm talking too */ + outb(((device & 0x7f) << 1) & ~0x01, smbus_base + SMBXMITADD); + /* Set the command/address... */ + outb(cmd & 0xff, smbus_base + SMBHSTCMD); + /* Set up for a block data write */ + outb((inb(smbus_base + SMBHSTCTL) & 0xe3) | 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 */ + outb(bytes, smbus_base + SMBHSTDAT0); + + outb(*buf++, smbus_base + SMBBLKDAT); + bytes--; + + /* Start the command */ + outb((inb(smbus_base + SMBHSTCTL) | SMBHSTCNT_START), + smbus_base + SMBHSTCTL); + + /* poll for it to start */ + if (smbus_wait_until_active(smbus_base) < 0) + return SMBUS_WAIT_UNTIL_ACTIVE_TIMEOUT; + + /* Poll for transaction completion */ + do { + loops--; + status = inb(smbus_base + SMBHSTSTAT); + if (status & (SMBHSTSTS_FAILED | /* FAILED */ + SMBHSTSTS_BUS_ERR | /* BUS ERR */ + SMBHSTSTS_DEV_ERR)) /* DEV ERR */ + return SMBUS_ERROR; + + if (status & SMBHSTSTS_BYTE_DONE) { + outb(*buf++, smbus_base + SMBBLKDAT); + outb(status, smbus_base + SMBHSTSTAT); + } + } while ((status & SMBHSTSTS_HOST_BUSY) && loops); + + return 0; +} + +/* Only since ICH5 */ +int do_i2c_block_read(unsigned int smbus_base, u8 device, + unsigned int offset, u32 bytes, u8 *buf) +{ + u8 status; + int bytes_read = 0; + unsigned int loops = SMBUS_TIMEOUT; + if (smbus_wait_until_ready(smbus_base) < 0) + return SMBUS_WAIT_UNTIL_READY_TIMEOUT; + + /* Set upp transaction */ + /* Disable interrupts */ + outb(inb(smbus_base + SMBHSTCTL) & SMBHSTCNT_INTREN, + smbus_base + SMBHSTCTL); + /* Set the device I'm talking to */ + outb((device & 0x7f) << 1, smbus_base + SMBXMITADD); + + /* device offset */ + 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 */ + outb((inb(smbus_base + SMBHSTCTL) | SMBHSTCNT_START), + smbus_base + SMBHSTCTL); + + /* poll for it to start */ + if (smbus_wait_until_active(smbus_base) < 0) + return SMBUS_WAIT_UNTIL_ACTIVE_TIMEOUT; + + /* Poll for transaction completion */ + do { + loops--; + status = inb(smbus_base + SMBHSTSTAT); + if (status & (SMBHSTSTS_FAILED | /* FAILED */ + SMBHSTSTS_BUS_ERR | /* BUS ERR */ + SMBHSTSTS_DEV_ERR)) /* DEV ERR */ + return SMBUS_ERROR; + + if (status & SMBHSTSTS_BYTE_DONE) { + *buf = inb(smbus_base + SMBBLKDAT); + buf++; + bytes_read++; + if (--bytes == 1) { + /* indicate that next byte is the last one */ + outb(inb(smbus_base + SMBHSTCTL) + | SMBHSTCNT_LAST_BYTE, + smbus_base + SMBHSTCTL); + } + outb(status, smbus_base + SMBHSTSTAT); + } + } while ((status & SMBHSTSTS_HOST_BUSY) && loops); + + return bytes_read; +} diff --git a/src/southbridge/intel/common/smbus.h b/src/southbridge/intel/common/smbus.h new file mode 100644 index 0000000000..f2e903c82b --- /dev/null +++ b/src/southbridge/intel/common/smbus.h @@ -0,0 +1,46 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2005 Yinghai Lu <yinghailu@gmail.com> + * Copyright (C) 2009 coresystems GmbH + * Copyright (C) 2013 Vladimir Serbinenko + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef INTEL_COMMON_SMBUS_H +#define INTEL_COMMON_SMBUS_H + +/* SMBus register offsets. */ +#define SMBHSTSTAT 0x0 +#define SMBHSTCTL 0x2 +#define SMBHSTCMD 0x3 +#define SMBXMITADD 0x4 +#define SMBHSTDAT0 0x5 +#define SMBHSTDAT1 0x6 +#define SMBBLKDAT 0x7 +#define SMBTRNSADD 0x9 +#define SMBSLVDATA 0xa +#define SMLINK_PIN_CTL 0xe +#define SMBUS_PIN_CTL 0xf +#define SMBSLVCMD 0x11 + +int do_smbus_read_byte(unsigned int smbus_base, u8 device, + unsigned int address); +int do_smbus_write_byte(unsigned int smbus_base, u8 device, + unsigned int address, unsigned int data); +int do_smbus_block_read(unsigned int smbus_base, u8 device, + u8 cmd, unsigned int bytes, u8 *buf); +int do_smbus_block_write(unsigned int smbus_base, u8 device, + u8 cmd, unsigned int bytes, const u8 *buf); +/* Only since ICH5 */ +int do_i2c_block_read(unsigned int smbus_base, u8 device, + unsigned int offset, u32 bytes, u8 *buf); +#endif |