summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYuchi Chen <yuchi.chen@intel.com>2024-09-03 23:09:23 +0800
committerLean Sheng Tan <sheng.tan@9elements.com>2024-11-09 10:21:17 +0000
commit60771bfdb102be639b7f074299c5778a8d9b24b9 (patch)
treeea30f1bbc516b3f24a4b430eddcef428a7925cbd /src
parent7a7b4f05fc6375d63e2128c791006c5f9e1c45ac (diff)
soc/intel/common/block/imc: Add Integrated Memory Controller driver
Intel common IMC contains an embedded SMBus controller for SPD data access. This patch implements IMC based SPD access supports through MMIO. Register definitons are from Intel Atom Processor C5100, C5300, P5300 and P5700 Product Families EDS, doc No. 575160 rev 2.0. Change-Id: I3f47ddeda94d3882852d64c0052f8fb42b6b7ad2 Tested-by: Yuchi Chen <yuchi.chen@intel.com> Signed-off-by: Yuchi Chen <yuchi.chen@intel.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/83320 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Shuo Liu <shuo.liu@intel.com>
Diffstat (limited to 'src')
-rw-r--r--src/soc/intel/common/block/imc/Kconfig8
-rw-r--r--src/soc/intel/common/block/imc/Makefile.mk6
-rw-r--r--src/soc/intel/common/block/imc/imc.c198
-rw-r--r--src/soc/intel/common/block/imc/imclib.h25
-rw-r--r--src/soc/intel/common/block/imc/spd_access.c38
-rw-r--r--src/soc/intel/common/block/include/intelblocks/imc.h19
-rw-r--r--src/soc/intel/common/block/smbus/Makefile.mk7
-rw-r--r--src/soc/intel/common/block/smbus/smbuslib.c13
8 files changed, 292 insertions, 22 deletions
diff --git a/src/soc/intel/common/block/imc/Kconfig b/src/soc/intel/common/block/imc/Kconfig
new file mode 100644
index 0000000000..f731954730
--- /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
+ Select this to access SPD data through 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 0000000000..0c074a33fc
--- /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_access.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 0000000000..ccc3d5c6fb
--- /dev/null
+++ b/src/soc/intel/common/block/imc/imc.c
@@ -0,0 +1,198 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <commonlib/bsd/cb_err.h>
+#include <console/console.h>
+#include <delay.h>
+#include <device/mmio.h>
+#include <device/pci_ops.h>
+#include <intelblocks/imc.h>
+#include <soc/pci_devs.h>
+#include <stdbool.h>
+#include <timer.h>
+
+#include "imclib.h"
+
+#define IMC_SMBUS_TIMEOUT_MS 100
+
+#define SMB_CMD_CFG 0x80
+#define SMB_CKOVRD BIT(29)
+#define SMB_DIS_WRT BIT(28)
+#define SMB_SOFT_RST BIT(24)
+#define SMB_TSOD_POLL_EN BIT(20)
+#define SMB_CMD_TRIGGER BIT(19)
+#define SMB_WORD_ACCESS BIT(17)
+#define SMB_WRT_READ (0 << 15)
+#define SMB_WRT_WRITE BIT(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 BIT(1)
+#define SMB_BUSY BIT(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_spd_smbus_init(pci_devfn_t dev)
+{
+ uint32_t status, cmd;
+ if (pci_read_config16(dev, 0) == 0xffff) {
+ printk(BIOS_ERR,
+ "IMC SMBUS controller PCI: %02x:%02x:%02x.%01x isn't present!\n",
+ PCI_DEV2SEG(dev), PCI_DEV2BUS(dev), PCI_SLOT(PCI_DEV2DEVFN(dev)),
+ PCI_FUNC(PCI_DEV2DEVFN(dev)));
+ 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 bool 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);
+
+ return 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 = CB_ERR;
+ uint32_t cmdbits, stat, databits, data_mask;
+ uint16_t wdata = 0, rdata = 0;
+
+ /* 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, "Claim controller failed!\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 didn't finished for slave 0x%02x\n", slave_addr);
+ ret = CB_ERR;
+ goto cleanup;
+ }
+
+ if (stat & SMB_SBE) {
+ printk(BIOS_ERR, "IMC SMBUS SBE for slave 0x%02x\n", slave_addr);
+ ret = CB_ERR;
+ 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 = CB_SUCCESS;
+
+cleanup:
+ if (!release_controller(dev)) {
+ printk(BIOS_ERR, "Release controller failed!\n");
+ return CB_ERR;
+ }
+
+ return ret;
+}
diff --git a/src/soc/intel/common/block/imc/imclib.h b/src/soc/intel/common/block/imc/imclib.h
new file mode 100644
index 0000000000..2b0989e71f
--- /dev/null
+++ b/src/soc/intel/common/block/imc/imclib.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef SOC_INTEL_COMMON_BLOCK_IMC_IMCLIB_H
+#define SOC_INTEL_COMMON_BLOCK_IMC_IMCLIB_H
+
+#include <device/pci_type.h>
+
+enum smbus_command { IMC_READ, IMC_WRITE };
+
+enum access_width { IMC_DATA_BYTE, IMC_DATA_WORD };
+
+enum memory_controller_id { IMC_CONTROLLER_ID0, IMC_CONTROLLER_ID1 };
+
+enum device_type_id {
+ IMC_DEVICE_TSOD = 0x3,
+ IMC_DEVICE_WP_EEPROM = 0x6,
+ IMC_DEVICE_EEPROM = 0xa
+};
+
+/* 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,
+ enum memory_controller_id mcid, enum smbus_command cmd, void *data);
+
+#endif
diff --git a/src/soc/intel/common/block/imc/spd_access.c b/src/soc/intel/common/block/imc/spd_access.c
new file mode 100644
index 0000000000..31cc484f95
--- /dev/null
+++ b/src/soc/intel/common/block/imc/spd_access.c
@@ -0,0 +1,38 @@
+/* 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>
+
+#include "imclib.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) == CB_SUCCESS) {
+ return value;
+ }
+
+ return -1;
+}
+
+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) == CB_SUCCESS) {
+ return value;
+ }
+
+ return -1;
+}
+
+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 1607794425..50ae6df757 100644
--- a/src/soc/intel/common/block/include/intelblocks/imc.h
+++ b/src/soc/intel/common/block/include/intelblocks/imc.h
@@ -1,25 +1,10 @@
/* SPDX-License-Identifier: GPL-2.0-only */
-#include <device/pci.h>
-#include <stdint.h>
+#include <device/pci_type.h>
#ifndef SOC_INTEL_COMMON_BLOCK_IMC_H
#define SOC_INTEL_COMMON_BLOCK_IMC_H
-enum smbus_command { IMC_READ, IMC_WRITE };
+void imc_spd_smbus_init(pci_devfn_t dev);
-enum access_width { IMC_DATA_BYTE, IMC_DATA_WORD };
-
-enum memory_controller_id { IMC_CONTROLLER_ID0 = 0, IMC_CONTROLLER_ID1 };
-
-enum device_type_id {
- IMC_DEVICE_TSOD = 0x3,
- IMC_DEVICE_WP_EEPROM = 0x6,
- IMC_DEVICE_EEPROM = 0xa
-};
-
-/* 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,
- enum memory_controller_id mcid, enum smbus_command cmd, void *data);
#endif
diff --git a/src/soc/intel/common/block/smbus/Makefile.mk b/src/soc/intel/common/block/smbus/Makefile.mk
index dd568144e5..2ef3a5b752 100644
--- a/src/soc/intel/common/block/smbus/Makefile.mk
+++ b/src/soc/intel/common/block/smbus/Makefile.mk
@@ -1,10 +1,8 @@
## SPDX-License-Identifier: GPL-2.0-only
-bootblock-$(CONFIG_SOC_INTEL_COMMON_BLOCK_SMBUS) += spd_access.c
bootblock-$(CONFIG_SOC_INTEL_COMMON_BLOCK_SMBUS) += smbuslib.c
bootblock-$(CONFIG_SOC_INTEL_COMMON_BLOCK_SMBUS) += smbus_early.c
bootblock-$(CONFIG_SOC_INTEL_COMMON_BLOCK_TCO) += tco.c
-romstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_SMBUS) += spd_access.c
romstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_SMBUS) += smbuslib.c
romstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_SMBUS) += smbus_early.c
romstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_TCO) += tco.c
@@ -15,3 +13,8 @@ ramstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_TCO) += tco.c
postcar-$(CONFIG_SOC_INTEL_COMMON_BLOCK_TCO) += tco.c
smm-$(CONFIG_SOC_INTEL_COMMON_BLOCK_TCO) += tco.c
verstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_TCO) += tco.c
+
+ifneq ($(CONFIG_SOC_INTEL_COMMON_BLOCK_IMC),y)
+bootblock-$(CONFIG_SOC_INTEL_COMMON_BLOCK_SMBUS) += spd_access.c
+romstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_SMBUS) += spd_access.c
+endif
diff --git a/src/soc/intel/common/block/smbus/smbuslib.c b/src/soc/intel/common/block/smbus/smbuslib.c
index deb09b390d..cf60e64c1d 100644
--- a/src/soc/intel/common/block/smbus/smbuslib.c
+++ b/src/soc/intel/common/block/smbus/smbuslib.c
@@ -52,7 +52,9 @@ static int get_spd(u8 *spd, u8 addr)
return -1;
}
- if (i2c_eeprom_read(addr, 0, SPD_PAGE_LEN, spd) < 0) {
+ /* IMC doesn't support i2c eeprom read. */
+ if (CONFIG(SOC_INTEL_COMMON_BLOCK_IMC) ||
+ i2c_eeprom_read(addr, 0, SPD_PAGE_LEN, spd) < 0) {
printk(BIOS_INFO, "do_i2c_eeprom_read failed, using fallback\n");
spd_read(spd, addr);
}
@@ -62,7 +64,9 @@ static int get_spd(u8 *spd, u8 addr)
/* Switch to page 1 */
spd_write_byte(SPD_PAGE_1, 0, 0);
- if (i2c_eeprom_read(addr, 0, SPD_PAGE_LEN, spd + SPD_PAGE_LEN) < 0) {
+ /* IMC doesn't support i2c eeprom read. */
+ if (CONFIG(SOC_INTEL_COMMON_BLOCK_IMC) ||
+ i2c_eeprom_read(addr, 0, SPD_PAGE_LEN, spd + SPD_PAGE_LEN) < 0) {
printk(BIOS_INFO, "do_i2c_eeprom_read failed, using fallback\n");
spd_read(spd + SPD_PAGE_LEN, addr);
}
@@ -78,7 +82,10 @@ void get_spd_smbus(struct spd_block *blk)
{
u8 i;
for (i = 0 ; i < CONFIG_DIMM_MAX; i++) {
- if (blk->addr_map[i] == 0) {
+ /**
+ * Slave address 0 is also available for IMC based SPD SMBus.
+ */
+ if (!CONFIG(SOC_INTEL_COMMON_BLOCK_IMC) && blk->addr_map[i] == 0) {
blk->spd_array[i] = NULL;
continue;
}