summaryrefslogtreecommitdiff
path: root/src/southbridge/intel/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/southbridge/intel/common')
-rw-r--r--src/southbridge/intel/common/Kconfig2
-rw-r--r--src/southbridge/intel/common/Makefile.inc4
-rw-r--r--src/southbridge/intel/common/smbus.c365
-rw-r--r--src/southbridge/intel/common/smbus.h46
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