Frans Hendriks has uploaded this change for review. ( https://review.coreboot.org/c/coreboot/+/30800
Change subject: southbridge/intel/common/smbus.{c,h}: Add support for I2C commands ......................................................................
southbridge/intel/common/smbus.{c,h}: Add support for I2C commands
Intel Braswell supports communication with I2C device using SMBus controller. This support is missing in actual smbus routines.
Added i2c_block_read() and i2c_block_write() which configures the SMBus controller into I2C mode before sending the commands. Added do_i2c_block_write() for sending block writes in I2C mode.
BUG=N/A TEST=Facebook FBG-1701 booting Embedded Linux
Change-Id: I40f8c0f5257a62398189f36892b8159052481693 Signed-off-by: Frans Hendriks fhendriks@eltan.com --- M src/southbridge/intel/common/smbus.c M src/southbridge/intel/common/smbus.h 2 files changed, 124 insertions(+), 3 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/00/30800/1
diff --git a/src/southbridge/intel/common/smbus.c b/src/southbridge/intel/common/smbus.c index de4ff91..3b50e6b 100644 --- a/src/southbridge/intel/common/smbus.c +++ b/src/southbridge/intel/common/smbus.c @@ -4,6 +4,7 @@ * Copyright (C) 2005 Yinghai Lu yinghailu@gmail.com * Copyright (C) 2009 coresystems GmbH * Copyright (C) 2013 Vladimir Serbinenko + * Copyright (C) 2018 Eltan B.V. * * 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 @@ -16,6 +17,7 @@ */
#include <arch/io.h> +#include <device/pci.h> #include <console/console.h> #include <device/smbus_def.h> #include <stdlib.h> @@ -96,9 +98,8 @@ unsigned char val; smbus_delay(); val = inb(smbus_base + SMBHSTSTAT); - if ((val & SMBHSTSTS_HOST_BUSY)) { + if ((val & SMBHSTSTS_HOST_BUSY)) break; - } } while (--loops); return loops ? 0 : -1; } @@ -208,6 +209,7 @@ int slave_bytes; int bytes_read = 0; unsigned int loops = SMBUS_TIMEOUT; + if (smbus_wait_until_ready(smbus_base) < 0) return SMBUS_WAIT_UNTIL_READY_TIMEOUT;
@@ -220,7 +222,7 @@ /* Set the device I'm talking to */ outb(((device & 0x7f) << 1) | 1, smbus_base + SMBXMITADD); /* 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)); @@ -350,6 +352,19 @@ }
/* Only since ICH5 */ +int i2c_block_read(u8 smbus_devnr, u8 smbus_func, unsigned int device, u8 cmd, + u32 bytes, u8 *buf) +{ + struct device *smbus_dev; + u32 reg; + + /* SMBus I/O BAR */ + smbus_dev = dev_find_slot(0, PCI_DEVFN(smbus_devnr, smbus_func)); + reg = pci_read_config32(smbus_dev, PCI_BASE_ADDRESS_4) & 0xFFFFFFFE; + + return do_i2c_block_read((unsigned int)reg, device, cmd, bytes, buf); +} + int do_i2c_block_read(unsigned int smbus_base, u8 device, unsigned int offset, const unsigned int bytes, u8 *buf) { @@ -418,3 +433,102 @@
return bytes_read; } + +int i2c_block_write(u8 smbus_devnr, u8 smbus_func, unsigned int device, u8 cmd, + u32 bytes, u8 *buf) +{ + struct device *smbus_dev; + u32 reg; + u32 smb_ctlr_reg; + int status; + + /* SMBus I/O BAR */ + smbus_dev = dev_find_slot(0, PCI_DEVFN(smbus_devnr, smbus_func)); + reg = pci_read_config32(smbus_dev, PCI_BASE_ADDRESS_4) & 0xFFFFFFFE; + + smb_ctlr_reg = pci_read_config32(smbus_dev, 0x40); + + /* Enable I2C_EN bit */ + pci_write_config32(smbus_dev, 0x40, smb_ctlr_reg | 4); + + status = do_i2c_block_write((unsigned int)reg, device, cmd, bytes, + buf); + + /* Restore I2C_EN bit */ + pci_write_config32(smbus_dev, 0x40, smb_ctlr_reg); + + return status; +} + +int do_i2c_block_write(unsigned int smbus_base, u8 device, u8 cmd, + const unsigned int bytes, u8 *buf) +{ + u8 status; + int bytes_write; + 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 slave address */ + outb(((device & 0x7f) << 1), smbus_base + SMBXMITADD); + + outb(cmd, smbus_base + SMBHSTCMD); + outb(cmd, smbus_base + SMBHSTDAT1); + + /* Number of bytes to write */ + outb(bytes, smbus_base + SMBHSTDAT0); + + /* Set up for a smbus 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); + + /* Send first byte from buffer */ + outb(*buf++, smbus_base + SMBBLKDAT); + + for (bytes_write = 0; bytes_write < bytes; bytes_write++) { + /* 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; + + loops = SMBUS_TIMEOUT; + 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; + } while (!(status & SMBHSTSTS_BYTE_DONE) && loops); + + if (status & SMBHSTSTS_BYTE_DONE) { + if (bytes_write+1 < bytes) + outb(*buf++, smbus_base + SMBBLKDAT); + if ((bytes_write + 1 >= bytes) && (bytes > 1)) { + /* indicate that next byte is the last one */ + outb(inb(smbus_base + SMBHSTCTL) | + SMBHSTCNT_LAST_BYTE, + smbus_base + SMBHSTCTL); + } + + outb(status & (SMBHSTSTS_BYTE_DONE | SMBHSTSTS_INTR), + smbus_base + SMBHSTSTAT); + } else { + return SMBUS_ERROR; + } + } + return (bytes_write+1); +} + diff --git a/src/southbridge/intel/common/smbus.h b/src/southbridge/intel/common/smbus.h index be1aa76..f272165 100644 --- a/src/southbridge/intel/common/smbus.h +++ b/src/southbridge/intel/common/smbus.h @@ -4,6 +4,7 @@ * Copyright (C) 2005 Yinghai Lu yinghailu@gmail.com * Copyright (C) 2009 coresystems GmbH * Copyright (C) 2013 Vladimir Serbinenko + * Copyright (C) 2018 Eltan B.V. * * 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 @@ -43,4 +44,10 @@ /* Only since ICH5 */ int do_i2c_block_read(unsigned int smbus_base, u8 device, unsigned int offset, unsigned int bytes, u8 *buf); +int do_i2c_block_write(unsigned int smbus_base, u8 device, u8 cmd, + const unsigned int bytes, u8 *buf); +int i2c_block_read(u8 smbus_devnr, u8 smbus_func, unsigned int device, u8 cmd, + u32 bytes, u8 *buf); +int i2c_block_write(u8 smbus_devnr, u8 smbus_func, unsigned int device, u8 cmd, + u32 bytes, u8 *buf); #endif