diff options
author | Andrey Petrov <anpetrov@fb.com> | 2019-07-29 14:41:35 -0700 |
---|---|---|
committer | Martin Roth <martinroth@google.com> | 2019-08-14 03:34:42 +0000 |
commit | f377fafd941a44252c4c7527ba08f798d222e7ff (patch) | |
tree | c90e095c3234bf0ecb4abc2ccb6a8f1107fb7c37 /src/soc/intel/common | |
parent | c0193c92379e19add78e4e3668fd222e4d041672 (diff) |
common/block/imc: Add Integrated Memory Controller (IMC) driver
IMC is found on certain Xeon processors. On such platforms SPDs are not
connected to SMBus on PCH but to dedicated IMC-owned pins. The purpose
of this driver is to expose access to the i2c/smbus controller associated
with IMC.
Datasheet used: Intel Xeon Processor D-1500 Product Family, Volume 2,
reference 332051-001
This driver is largely based on i2c-imc.c Linux driver.
https://lwn.net/Articles/685475/
TEST=single/double reads and single writes on Xeon-D1500.
Hardware: Open Compute Project Monolake platform.
Signed-off-by: Andrey Petrov <anpetrov@fb.com>
Change-Id: Idbcda1c2273b9a5721fcd9470b4de182192779e7
Reviewed-on: https://review.coreboot.org/c/coreboot/+/34678
Reviewed-by: David Hendricks <david.hendricks@gmail.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Diffstat (limited to 'src/soc/intel/common')
-rw-r--r-- | src/soc/intel/common/block/imc/Kconfig | 7 | ||||
-rw-r--r-- | src/soc/intel/common/block/imc/Makefile.inc | 9 | ||||
-rw-r--r-- | src/soc/intel/common/block/imc/imc.c | 171 | ||||
-rw-r--r-- | src/soc/intel/common/block/include/intelblocks/imc.h | 38 |
4 files changed, 225 insertions, 0 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..490b80ebeb --- /dev/null +++ b/src/soc/intel/common/block/imc/Kconfig @@ -0,0 +1,7 @@ +config SOC_INTEL_COMMON_BLOCK_IMC + bool + depends on MMCONF_SUPPORT + default n + help + Driver for communication with Integrated Memory Controller that is found on + some Xeon server processors. diff --git a/src/soc/intel/common/block/imc/Makefile.inc b/src/soc/intel/common/block/imc/Makefile.inc new file mode 100644 index 0000000000..a6bc985eb2 --- /dev/null +++ b/src/soc/intel/common/block/imc/Makefile.inc @@ -0,0 +1,9 @@ +ifeq ($(CONFIG_SOC_INTEL_COMMON_BLOCK_IMC),y) + +bootblock-y += imc.c +romstage-y += imc.c +verstage-y += imc.c +postcar-y += imc.c +ramstage-y += imc.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..e7f20bef03 --- /dev/null +++ b/src/soc/intel/common/block/imc/imc.c @@ -0,0 +1,171 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2019 Facebook, Inc. + * + * 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. + */ + +/* + * Please note: the driver uses MMIO PCIe register access. IO based access will + * not work. + */ + +#include <console/console.h> +#include <delay.h> +#include <device/pci_ops.h> +#include <intelblocks/imc.h> +#include <soc/pci_devs.h> +#include <timer.h> + +#define IMC_SMBUS_TIMEOUT_MS 100 +#define IMC_SMBCNTL_DTI_TSOD 0x3 +#define IMC_SMBCNTL_DTI_EEPROM 0xa +#define IMC_SMBCNTL_DTI_WP_EEPROM 0x6 + +#define SMBSTAT(i) (0x180 + 0x10 * i) +#define SMBCMD(i) (0x184 + 0x10 * i) +#define SMBCNTL(i) (0x188 + 0x10 * i) + +#define SMBSTAT_RDO (1u << 31) /* Read Data Valid */ +#define SMBSTAT_WOD (1u << 30) /* Write Operation Done */ +#define SMBSTAT_SBE (1u << 29) /* SMBus Error */ +#define SMBSTAT_SMB_BUSY (1u << 28) /* SMBus Busy State */ + +#define SMBCMD_TRIGGER (1u << 31) /* CMD Trigger */ +#define SMBCMD_PNTR_SEL (1u << 30) /* HW polls TSOD with pointer */ +#define SMBCMD_WORD_ACCESS (1u << 29) /* word (vs byte) access */ +#define SMBCMD_TYPE_MASK (3u << 27) /* Mask for access type */ +#define SMBCMD_TYPE_READ (0u << 27) /* Read */ +#define SMBCMD_TYPE_WRITE (1u << 27) /* Write */ +#define SMBCMD_TYPE_PNTR_WRITE (3u << 27) /* Write to pointer */ +#define SMBCMD_SA_MASK (7u << 24) /* Slave Address high bits */ +#define SMBCMD_SA_SHIFT 24 +#define SMBCMD_BA_MASK 0xff0000 /* Bus Txn address */ +#define SMBCMD_BA_SHIFT 16 +#define SMBCMD_WDATA_MASK 0xffff /* data to write */ + +#define SMBCNTL_DTI_MASK 0xf0000000 /* Slave Address low bits */ +#define SMBCNTL_DTI_SHIFT 28 /* Slave Address low bits */ +#define SMBCNTL_CKOVRD (1u << 27) /* # Clock Override */ +#define SMBCNTL_DIS_WRT (1u << 26) /* Disable Write (sadly) */ +#define SMBCNTL_SOFT_RST (1u << 10) /* Soft Reset */ +#define SMBCNTL_TSOD_POLL_EN (1u << 8) /* TSOD Polling Enable */ + +static bool poll_ready(pci_devfn_t dev, enum memory_controller_id mcid, uint32_t *status) +{ + struct stopwatch sw; + + stopwatch_init_msecs_expire(&sw, IMC_SMBUS_TIMEOUT_MS); + + do { + *status = pci_mmio_read_config32(dev, SMBSTAT(mcid)); + if (!(*status & SMBSTAT_SMB_BUSY)) + break; + } while (!stopwatch_expired(&sw)); + + return (!(*status & SMBSTAT_SMB_BUSY)); +} + +static bool claim_controller(pci_devfn_t dev, enum memory_controller_id mcid) +{ + uint32_t cntl, status; + + cntl = pci_mmio_read_config32(dev, SMBCNTL(mcid)); + cntl &= ~SMBCNTL_TSOD_POLL_EN; + cntl &= ~SMBCNTL_DIS_WRT; + pci_mmio_write_config32(dev, SMBCNTL(mcid), cntl); + + return poll_ready(dev, mcid, &status); +} + + +static void release_controller(pci_devfn_t dev, enum memory_controller_id mcid) +{ + uint32_t cntl, status; + + cntl = pci_mmio_read_config32(dev, SMBCNTL(mcid)); + cntl |= SMBCNTL_TSOD_POLL_EN; + pci_mmio_write_config32(dev, SMBCNTL(mcid), cntl); + + poll_ready(dev, mcid, &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 = 0, stat = 0, cntlbits = 0, data_mask = 0; + uint16_t wdata = 0, rdata = 0; + + /* slaves addresses are 7 bits length */ + if (slave_addr > (1 << 7) - 1) { + printk(BIOS_ERR, "invalid SMBus address, aborting xfer\n"); + return -1; + } + + if (!claim_controller(dev, mcid)) { + printk(BIOS_ERR, "ayee! couldn't claim controller, giving up xfer\n"); + return -1; + } + + cmdbits = (slave_addr << SMBCMD_SA_SHIFT); + cmdbits |= (bus_addr << SMBCMD_BA_SHIFT); + + if (cmd == IMC_WRITE) { + wdata = (width == IMC_DATA_BYTE ? read8(data) : cpu_to_be16(read16(data))); + cmdbits |= (SMBCMD_TYPE_WRITE | wdata); + } else { + cmdbits |= SMBCMD_TYPE_READ; + } + + if (width == IMC_DATA_WORD) { + cmdbits |= SMBCMD_WORD_ACCESS; + data_mask = 0xffff; + } else { + data_mask = 0xff; + } + + cntlbits = pci_mmio_read_config32(dev, SMBCNTL(mcid)); + cntlbits &= ~SMBCNTL_DTI_MASK; + cntlbits |= (dti << SMBCNTL_DTI_SHIFT); + + pci_mmio_write_config32(dev, SMBCNTL(mcid), cntlbits); + + /* Pull the trigger */ + cmdbits |= SMBCMD_TRIGGER; + pci_mmio_write_config32(dev, SMBCMD(mcid), cmdbits); + + if (!poll_ready(dev, mcid, &stat)) { + printk(BIOS_ERR, "IMC xfer failed for slave %x", slave_addr); + ret = -1; + goto cleanup; + } + + if (stat & SMBSTAT_SBE) { + ret = -1; + goto cleanup; + } + + if (cmd == IMC_READ) { + rdata = stat & data_mask; + if (width == IMC_DATA_WORD) + write16(data, cpu_to_be16(rdata)); + else + write8(data, rdata); + } + + ret = 0; +cleanup: + release_controller(dev, SMBSTAT(mcid)); + + return ret; +} diff --git a/src/soc/intel/common/block/include/intelblocks/imc.h b/src/soc/intel/common/block/include/intelblocks/imc.h new file mode 100644 index 0000000000..fc3c241564 --- /dev/null +++ b/src/soc/intel/common/block/include/intelblocks/imc.h @@ -0,0 +1,38 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2019 Facebook, Inc. + * + * 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 <device/pci.h> +#include <stdint.h> + +#ifndef SOC_INTEL_COMMON_BLOCK_IMC_H +#define SOC_INTEL_COMMON_BLOCK_IMC_H + +enum smbus_command { IMC_READ, IMC_WRITE }; + +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 |