yuchi.chen@intel.com has uploaded this change for review. ( https://review.coreboot.org/c/coreboot/+/83320?usp=email )
Change subject: src/soc/intel/common/block/imc: add driver for Integrated Memory Controller ......................................................................
src/soc/intel/common/block/imc: add driver for Integrated Memory Controller
This patch wraps the smbus IO calls with weak spd IO functions so that firmware could use IMC to get SPD data.
Change-Id: I3f47ddeda94d3882852d64c0052f8fb42b6b7ad2 Signed-off-by: Yuchi Chen yuchi.chen@intel.com --- M src/include/spd_bin.h A src/soc/intel/common/block/imc/Kconfig A src/soc/intel/common/block/imc/Makefile.mk A src/soc/intel/common/block/imc/imc.c A src/soc/intel/common/block/imc/spd.c M src/soc/intel/common/block/include/intelblocks/imc.h M src/soc/intel/common/block/smbus/smbuslib.c M src/southbridge/intel/common/smbus.c 8 files changed, 293 insertions(+), 18 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/20/83320/1
diff --git a/src/include/spd_bin.h b/src/include/spd_bin.h index c51e449..434d674 100644 --- a/src/include/spd_bin.h +++ b/src/include/spd_bin.h @@ -49,6 +49,10 @@ void dump_spd_info(struct spd_block *blk); void get_spd_smbus(struct spd_block *blk);
+int spd_read_byte(u8 slave_addr, u8 bus_addr); +int spd_read_word(u8 slave_addr, u8 bus_addr); +void spd_write_byte(u8 slave_addr, u8 bus_addr, u8 value); + /* * get_spd_sn returns the SODIMM serial number. It only supports DDR3 and DDR4. * return CB_SUCCESS, sn is the serial number and sn=0xffffffff if the dimm is not present. diff --git a/src/soc/intel/common/block/imc/Kconfig b/src/soc/intel/common/block/imc/Kconfig new file mode 100644 index 0000000..3651613 --- /dev/null +++ b/src/soc/intel/common/block/imc/Kconfig @@ -0,0 +1,8 @@ +## SPDX-License-Identifier: GPL-2.0-only + +config SOC_INTEL_COMMON_BLOCK_IMC + bool + depends on ECAM_MMCONF_SUPPORT + default n + help + Support for Integrated Memory Controller. diff --git a/src/soc/intel/common/block/imc/Makefile.mk b/src/soc/intel/common/block/imc/Makefile.mk new file mode 100644 index 0000000..7f9d9d9 --- /dev/null +++ b/src/soc/intel/common/block/imc/Makefile.mk @@ -0,0 +1,6 @@ +## SPDX-License-Identifier: GPL-2.0-only + +ifeq ($(CONFIG_SOC_INTEL_COMMON_BLOCK_IMC),y) +romstage-y += imc.c +romstage-y += spd.c +endif diff --git a/src/soc/intel/common/block/imc/imc.c b/src/soc/intel/common/block/imc/imc.c new file mode 100644 index 0000000..0c5b77b --- /dev/null +++ b/src/soc/intel/common/block/imc/imc.c @@ -0,0 +1,204 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +/** + * @note The driver uses MMIO PCIe register access. IO based access will not work. + */ + +#include <arch/mmio.h> +#include <commonlib/bsd/cb_err.h> +#include <console/console.h> +#include <delay.h> +#include <device/pci_ops.h> +#include <intelblocks/imc.h> +#include <soc/pci_devs.h> +#include <stdbool.h> +#include <timer.h> + +#define IMC_SMBUS_TIMEOUT_MS 100 + +#define SMB_CMD_CFG 0x80 +#define SMB_CKOVRD (1 << 29) +#define SMB_DIS_WRT (1 << 28) +#define SMB_SOFT_RST (1 << 24) +#define SMB_TSOD_POLL_EN (1 << 20) +#define SMB_CMD_TRIGGER (1 << 19) +#define SMB_WORD_ACCESS (1 << 17) +#define SMB_WRT_READ (0 << 15) +#define SMB_WRT_WRITE (1 << 15) +#define SMB_DTI_MASK (7 << 11) +#define SMB_CMD_SA_SHIFT 8 +#define SMB_CMD_BA_SHIFT 0 +#define SMB_CMD_DTI_SHIFT 11 +#define SMB_STATUS_CFG 0x84 +#define SMB_SBE (1 << 1) +#define SMB_BUSY (1 << 0) +#define SMB_DATA_CFG 0x88 +#define SMB_PERIOD_CFG 0x90 +#define SMB_CLOCK_PERIOD_400K 250 /**< Clock period for 400K. */ +#define SMB_CLOCK_OFFSET_400K 35 /**< Clock offset for 400K. */ + +static void imc_spd_smbus_reset(pci_devfn_t dev) +{ + uint32_t cmd; + + cmd = pci_read_config32(dev, SMB_CMD_CFG); + cmd &= ~SMB_CKOVRD; + cmd |= SMB_SOFT_RST; + pci_write_config32(dev, SMB_CMD_CFG, cmd); + + mdelay(35); /**< See description of `SMB_CKOVRD` field. */ + + cmd = pci_read_config32(dev, SMB_CMD_CFG); + cmd |= SMB_CKOVRD; + cmd &= ~SMB_SOFT_RST; + pci_write_config32(dev, SMB_CMD_CFG, cmd); +} + +void imc_smbus_spd_init(pci_devfn_t dev) +{ + uint32_t status, cmd; + + if (pci_read_config16(dev, 0) == 0xffff) { + printk(BIOS_ERR, "IMC SMBUS controller (B1E:D0B:F0) does not present!\n"); + return; + } + + /** + * Set SMB CLOCK to 400K to detect DIMM SPDs + */ + pci_write_config32(dev, SMB_PERIOD_CFG, + (SMB_CLOCK_OFFSET_400K << 16) | SMB_CLOCK_PERIOD_400K); + + /** + * Reset the bus if the first access is busy. + */ + status = pci_read_config32(dev, SMB_STATUS_CFG); + if (status & SMB_BUSY) + imc_spd_smbus_reset(dev); + + /** + * Disable TSOD polling. + */ + cmd = pci_read_config32(dev, SMB_CMD_CFG); + cmd &= ~SMB_TSOD_POLL_EN; + pci_write_config32(dev, SMB_CMD_CFG, cmd); +} + +static bool poll_ready(pci_devfn_t dev, uint32_t *status) +{ + struct stopwatch sw; + + stopwatch_init_msecs_expire(&sw, IMC_SMBUS_TIMEOUT_MS); + + do { + *status = pci_read_config32(dev, SMB_STATUS_CFG); + if ((*status & SMB_BUSY) == 0) { + return true; + } + } while (!stopwatch_expired(&sw)); + + return false; +} + +static bool claim_controller(pci_devfn_t dev) +{ + uint32_t cmd, status; + + cmd = pci_read_config32(dev, SMB_CMD_CFG); + cmd &= ~SMB_TSOD_POLL_EN; + cmd &= ~SMB_DIS_WRT; + pci_write_config32(dev, SMB_CMD_CFG, cmd); + + return poll_ready(dev, &status); +} + +static void release_controller(pci_devfn_t dev) +{ + uint32_t cmd, status; + + cmd = pci_read_config32(dev, SMB_CMD_CFG); + cmd |= SMB_TSOD_POLL_EN; + pci_write_config32(dev, SMB_CMD_CFG, cmd); + + poll_ready(dev, &status); +} + +int imc_smbus_spd_xfer(pci_devfn_t dev, uint8_t slave_addr, uint8_t bus_addr, + enum device_type_id dti, enum access_width width, + enum memory_controller_id mcid, enum smbus_command cmd, void *data) +{ + int ret = -1; + uint32_t cmdbits, stat, databits, data_mask; + uint16_t wdata = 0, rdata = 0; + + /** + * @note Slaves addresses are 3 bits length, and bus address is 8 bits length. + */ + if (slave_addr > (1 << 7) - 1) { + printk(BIOS_ERR, "Invalid SMBus slave 0x%02x\n", slave_addr); + return CB_ERR; + } + + if (!claim_controller(dev)) { + printk(BIOS_ERR, "Can't claim controller!\n"); + return CB_ERR; + } + + cmdbits = slave_addr << SMB_CMD_SA_SHIFT; + cmdbits |= bus_addr << SMB_CMD_BA_SHIFT; + + if (cmd == IMC_WRITE) { + databits = pci_read_config32(dev, SMB_DATA_CFG); + wdata = (width == IMC_DATA_BYTE ? read8(data) : cpu_to_be16(read16(data))); + databits |= (wdata << 16); + pci_write_config32(dev, SMB_DATA_CFG, databits); + + cmdbits |= SMB_WRT_WRITE; + cmdbits &= ~SMB_DIS_WRT; + } else { + cmdbits |= SMB_WRT_READ; + } + + if (width == IMC_DATA_WORD) { + cmdbits |= SMB_WORD_ACCESS; + data_mask = 0xffff; + } else { + data_mask = 0xff; + } + + cmdbits &= ~SMB_DTI_MASK; + cmdbits |= dti << SMB_CMD_DTI_SHIFT; + cmdbits |= SMB_CKOVRD; + + /* Pull the trigger */ + cmdbits |= SMB_CMD_TRIGGER; + pci_write_config32(dev, SMB_CMD_CFG, cmdbits); + + if (!poll_ready(dev, &stat)) { + printk(BIOS_ERR, "IMC transfer doesn't finished for slave 0x%02x\n", slave_addr); + ret = -1; + goto cleanup; + } + + if (stat & SMB_SBE) { + printk(BIOS_ERR, "IMC SMBUS SBE for slave 0x%02x\n", slave_addr); + ret = -1; + goto cleanup; + } + + if (cmd == IMC_READ) { + databits = pci_read_config32(dev, SMB_DATA_CFG); + rdata = databits & data_mask; + if (width == IMC_DATA_WORD) + write16(data, be16_to_cpu(rdata)); + else + write8(data, rdata); + } + + ret = 0; + +cleanup: + release_controller(dev); + + return ret; +} diff --git a/src/soc/intel/common/block/imc/spd.c b/src/soc/intel/common/block/imc/spd.c new file mode 100644 index 0000000..449e744 --- /dev/null +++ b/src/soc/intel/common/block/imc/spd.c @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <commonlib/bsd/cb_err.h> +#include <intelblocks/imc.h> +#include <soc/pci_devs.h> +#include <spd_bin.h> + +int spd_read_byte(u8 slave_addr, u8 bus_addr) +{ + uint8_t value; + + if (imc_smbus_spd_xfer(IMC_SPD_DEV, slave_addr, bus_addr, IMC_DEVICE_EEPROM, IMC_DATA_BYTE, + IMC_CONTROLLER_ID0, IMC_READ, &value) == 0) { + return value; + } + + return CB_ERR; +} + +int spd_read_word(u8 slave_addr, u8 bus_addr) +{ + uint16_t value = 0; + + if (imc_smbus_spd_xfer(IMC_SPD_DEV, slave_addr, bus_addr, IMC_DEVICE_EEPROM, IMC_DATA_WORD, + IMC_CONTROLLER_ID0, IMC_READ, &value) == 0) { + return value; + } + + return CB_ERR; +} + +void spd_write_byte(u8 slave_addr, u8 bus_addr, u8 value) +{ + imc_smbus_spd_xfer(IMC_SPD_DEV, slave_addr, bus_addr, IMC_DEVICE_WP_EEPROM, + IMC_DATA_BYTE, IMC_CONTROLLER_ID0, IMC_WRITE, &value); +} diff --git a/src/soc/intel/common/block/include/intelblocks/imc.h b/src/soc/intel/common/block/include/intelblocks/imc.h index 1607794..1bf3063 100644 --- a/src/soc/intel/common/block/include/intelblocks/imc.h +++ b/src/soc/intel/common/block/include/intelblocks/imc.h @@ -18,6 +18,8 @@ IMC_DEVICE_EEPROM = 0xa };
+void imc_smbus_spd_init(pci_devfn_t dev); + /* Initiate SMBus/I2C transaction to DIMM EEPROM */ int imc_smbus_spd_xfer(pci_devfn_t dev, uint8_t slave_addr, uint8_t bus_addr, enum device_type_id dti, enum access_width width, diff --git a/src/soc/intel/common/block/smbus/smbuslib.c b/src/soc/intel/common/block/smbus/smbuslib.c index aff6fb0..2794cd6 100644 --- a/src/soc/intel/common/block/smbus/smbuslib.c +++ b/src/soc/intel/common/block/smbus/smbuslib.c @@ -6,6 +6,21 @@ #include <device/smbus_host.h> #include "smbuslib.h"
+__weak int spd_read_byte(u8 slave_addr, u8 bus_addr) +{ + return smbus_read_byte(slave_addr, bus_addr); +} + +__weak int spd_read_word(u8 slave_addr, u8 bus_addr) +{ + return smbus_read_word(slave_addr, bus_addr); +} + +__weak void spd_write_byte(u8 slave_addr, u8 bus_addr, u8 value) +{ + smbus_write_byte(slave_addr, bus_addr, value); +} + static void update_spd_len(struct spd_block *blk) { u8 i, j = 0; @@ -20,7 +35,7 @@ blk->len = SPD_PAGE_LEN; }
-static void smbus_read_spd(u8 *spd, u8 addr) +static void spd_read(u8 *spd, u8 addr) { u16 i; u8 step = 1; @@ -31,9 +46,9 @@ for (i = 0; i < SPD_PAGE_LEN; i += step) { if (CONFIG(SPD_READ_BY_WORD)) ((u16*)spd)[i / sizeof(uint16_t)] = - smbus_read_word(addr, i); + spd_read_word(addr, i); else - spd[i] = smbus_read_byte(addr, i); + spd[i] = spd_read_byte(addr, i); } }
@@ -42,11 +57,11 @@ { if (CONFIG_DIMM_SPD_SIZE > SPD_PAGE_LEN) { /* Restore to page 0 before reading */ - smbus_write_byte(SPD_PAGE_0, 0, 0); + spd_write_byte(SPD_PAGE_0, 0, 0); }
/* If address is not 0, it will return CB_ERR(-1) if no dimm */ - if (smbus_read_byte(addr, 0) < 0) { + if (spd_read_byte(addr, 0) < 0) { printk(BIOS_INFO, "No memory dimm at address %02X\n", addr << 1); return -1; @@ -54,20 +69,20 @@
if (i2c_eeprom_read(addr, 0, SPD_PAGE_LEN, spd) < 0) { printk(BIOS_INFO, "do_i2c_eeprom_read failed, using fallback\n"); - smbus_read_spd(spd, addr); + spd_read(spd, addr); }
/* Check if module is DDR4, DDR4 spd is 512 byte. */ if (spd[SPD_DRAM_TYPE] == SPD_DRAM_DDR4 && CONFIG_DIMM_SPD_SIZE > SPD_PAGE_LEN) { /* Switch to page 1 */ - smbus_write_byte(SPD_PAGE_1, 0, 0); + spd_write_byte(SPD_PAGE_1, 0, 0);
if (i2c_eeprom_read(addr, 0, SPD_PAGE_LEN, spd + SPD_PAGE_LEN) < 0) { printk(BIOS_INFO, "do_i2c_eeprom_read failed, using fallback\n"); - smbus_read_spd(spd + SPD_PAGE_LEN, addr); + spd_read(spd + SPD_PAGE_LEN, addr); } /* Restore to page 0 */ - smbus_write_byte(SPD_PAGE_0, 0, 0); + spd_write_byte(SPD_PAGE_0, 0, 0); } return 0; } @@ -78,7 +93,7 @@ { u8 i; for (i = 0 ; i < CONFIG_DIMM_MAX; i++) { - if (blk->addr_map[i] == 0) { + if (!CONFIG(SOC_INTEL_COMMON_BLOCK_IMC) && blk->addr_map[i] == 0) { blk->spd_array[i] = NULL; continue; } @@ -109,11 +124,11 @@
if (CONFIG_DIMM_SPD_SIZE > SPD_PAGE_LEN) { /* Restore to page 0 before reading */ - smbus_write_byte(SPD_PAGE_0, 0, 0); + spd_write_byte(SPD_PAGE_0, 0, 0); }
/* If dimm is not present, set sn to 0xff. */ - smbus_ret = smbus_read_byte(addr, SPD_DRAM_TYPE); + smbus_ret = spd_read_byte(addr, SPD_DRAM_TYPE); if (smbus_ret < 0) { printk(BIOS_INFO, "No memory dimm at address %02X\n", addr << 1); *sn = 0xffffffff; @@ -125,17 +140,17 @@ /* Check if module is DDR4, DDR4 spd is 512 byte. */ if (dram_type == SPD_DRAM_DDR4 && CONFIG_DIMM_SPD_SIZE > SPD_PAGE_LEN) { /* Switch to page 1 */ - smbus_write_byte(SPD_PAGE_1, 0, 0); + spd_write_byte(SPD_PAGE_1, 0, 0);
for (i = 0; i < SPD_SN_LEN; i++) - *((u8 *)sn + i) = smbus_read_byte(addr, + *((u8 *)sn + i) = spd_read_byte(addr, i + DDR4_SPD_SN_OFF);
/* Restore to page 0 */ - smbus_write_byte(SPD_PAGE_0, 0, 0); + spd_write_byte(SPD_PAGE_0, 0, 0); } else if (dram_type == SPD_DRAM_DDR3) { for (i = 0; i < SPD_SN_LEN; i++) - *((u8 *)sn + i) = smbus_read_byte(addr, + *((u8 *)sn + i) = spd_read_byte(addr, i + DDR3_SPD_SN_OFF); } else { printk(BIOS_ERR, "Unsupported dram_type\n"); diff --git a/src/southbridge/intel/common/smbus.c b/src/southbridge/intel/common/smbus.c index 1594e68..63bec66 100644 --- a/src/southbridge/intel/common/smbus.c +++ b/src/southbridge/intel/common/smbus.c @@ -456,8 +456,8 @@ /* Only since ICH5 */ static int has_i2c_read_command(void) { - if (CONFIG(SOUTHBRIDGE_INTEL_I82371EB) || - CONFIG(SOUTHBRIDGE_INTEL_I82801DX)) + if (CONFIG(SOUTHBRIDGE_INTEL_I82371EB) || CONFIG(SOUTHBRIDGE_INTEL_I82801DX) || + CONFIG(SOC_INTEL_COMMON_BLOCK_IMC)) return 0; return 1; }