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>
This commit is contained in:
Andrey Petrov 2019-07-29 14:41:35 -07:00 committed by Martin Roth
parent c0193c9237
commit f377fafd94
5 changed files with 228 additions and 0 deletions

View File

@ -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.

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -26,6 +26,9 @@ config CPU_SPECIFIC_OPTIONS
select TSC_CONSTANT_RATE
select HAVE_FSP_BIN
select CPU_INTEL_FIRMWARE_INTERFACE_TABLE
select SOC_INTEL_COMMON
select SOC_INTEL_COMMON_BLOCK
select SOC_INTEL_COMMON_BLOCK_IMC
config VBOOT
select VBOOT_STARTS_IN_ROMSTAGE