From 9fd452f1992f669af280b2171d044ba80cd437c8 Mon Sep 17 00:00:00 2001 From: jostar-yang Date: Thu, 21 Mar 2024 18:19:14 +0800 Subject: [PATCH] Add i2c imc bus driver for support Broadwell memory Signed-off-by: jostar-yang --- patch/driver-support-i2c-bus-imc.patch | 576 +++++++++++++++++++++++++ patch/kconfig-inclusions | 1 + patch/series | 1 + 3 files changed, 578 insertions(+) create mode 100644 patch/driver-support-i2c-bus-imc.patch diff --git a/patch/driver-support-i2c-bus-imc.patch b/patch/driver-support-i2c-bus-imc.patch new file mode 100644 index 000000000..000684e54 --- /dev/null +++ b/patch/driver-support-i2c-bus-imc.patch @@ -0,0 +1,576 @@ +From cf024384f15da7386bbe2c76ee0f9e83e04d17e8 Mon Sep 17 00:00:00 2001 +From: jostar-yang +Date: Thu, 21 Mar 2024 17:44:59 +0800 +Subject: [PATCH] Add i2c imc bus driver for support Broadwell memory + controllers and DIMM thermal sensors + +Signed-off-by: jostar-yang +--- + drivers/i2c/busses/Kconfig | 15 + + drivers/i2c/busses/Makefile | 1 + + drivers/i2c/busses/i2c-imc.c | 515 +++++++++++++++++++++++++++++++++++ + 3 files changed, 531 insertions(+) + create mode 100644 drivers/i2c/busses/i2c-imc.c + +diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig +index e50f9603d..77bd275a5 100644 +--- a/drivers/i2c/busses/Kconfig ++++ b/drivers/i2c/busses/Kconfig +@@ -162,6 +162,21 @@ config I2C_I801 + This driver can also be built as a module. If so, the module + will be called i2c-i801. + ++config I2C_IMC ++ tristate "Intel iMC SMBus Controller" ++ depends on PCI && X86 && SENSORS_JC42 ++ help ++ If you say yes to this option, support will be included for the Intel ++ iMC SMBus host controller. ++ ++ Currently, there is support for Broadwell, only. ++ ++ Behind the iMC SMBus are DIMMs with eeproms and temperature sensors. ++ This driver is also instantiating the temperature sensors. ++ ++ This driver can also be built as a module. If so, the module will be ++ called i2c-imc. ++ + config I2C_ISCH + tristate "Intel SCH SMBus 1.0" + depends on PCI +diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile +index e73cdb1d2..ba3c9433c 100644 +--- a/drivers/i2c/busses/Makefile ++++ b/drivers/i2c/busses/Makefile +@@ -18,6 +18,7 @@ obj-$(CONFIG_I2C_AMD756_S4882) += i2c-amd756-s4882.o + obj-$(CONFIG_I2C_AMD8111) += i2c-amd8111.o + obj-$(CONFIG_I2C_CHT_WC) += i2c-cht-wc.o + obj-$(CONFIG_I2C_I801) += i2c-i801.o ++obj-$(CONFIG_I2C_IMC) += i2c-imc.o + obj-$(CONFIG_I2C_ISCH) += i2c-isch.o + obj-$(CONFIG_I2C_ISMT) += i2c-ismt.o + obj-$(CONFIG_I2C_NFORCE2) += i2c-nforce2.o +diff --git a/drivers/i2c/busses/i2c-imc.c b/drivers/i2c/busses/i2c-imc.c +new file mode 100644 +index 000000000..def639bbe +--- /dev/null ++++ b/drivers/i2c/busses/i2c-imc.c +@@ -0,0 +1,515 @@ ++// SPDX-License-Identifier: GPL-2.0 ++ ++/* ++ * Intel Memory Controller iMC SMBus Driver to DIMMs. ++ * ++ * Copyright (c) 2013-2016 Andrew Lutomirski ++ * Copyright (c) 2020 Stefan Schaeckeler , Cisco Systems ++ */ ++ ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#include ++#include ++#include ++#include ++#include ++ ++/* iMC Main, PCI dev 0x13, fn 0, 8086.6fa8 */ ++#define PCI_DEVICE_ID_INTEL_BROADWELL_IMC_TA 0x6fa8 ++ ++/* Register offsets for channel pairs 0+1 and 2+3 */ ++#define SMBSTAT(i) (0x180 + 0x10*(i)) ++#define SMBCMD(i) (0x184 + 0x10*(i)) ++#define SMBCNTL(i) (0x188 + 0x10*(i)) ++ ++/* SMBSTAT fields */ ++#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 SMBSTAT_RDATA_MASK 0xffff /* Result of a read */ ++ ++/* SMBCMD fields */ ++#define SMBCMD_TRIGGER (1U << 31) /* CMD Trigger */ ++#define SMBCMD_WORD_ACCESS (1U << 29) /* Word (vs byte) access */ ++#define SMBCMD_TYPE_READ (0U << 27) /* Read */ ++#define SMBCMD_TYPE_WRITE (1U << 27) /* Write */ ++#define SMBCMD_SA_SHIFT 24 ++#define SMBCMD_BA_SHIFT 16 ++ ++/* SMBCNTL fields */ ++#define SMBCNTL_DTI_MASK 0xf0000000 /* Slave Address low bits */ ++#define SMBCNTL_DTI_SHIFT 28 /* Slave Address low bits */ ++#define SMBCNTL_DIS_WRT (1U << 26) /* Disable Write */ ++#define SMBCNTL_TSOD_PRES_MASK 0xff /* DIMM Present mask */ ++ ++/* For sanity check: bits that might randomly change if we race with firmware */ ++#define SMBCMD_OUR_BITS (~(u32)SMBCMD_TRIGGER) ++#define SMBCNTL_OUR_BITS (SMBCNTL_DTI_MASK) ++ ++ ++/* System Address Decoder, PCI dev 0xf fn 5, 8086.6ffd */ ++#define PCI_DEVICE_ID_INTEL_BROADWELL_IMC_SAD 0x6ffd ++ ++/* Register offsets */ ++#define SADCNTL 0xf4 ++ ++/* SADCNTL fields */ ++#define SADCNTL_LOCAL_NODEID_MASK 0xf /* Local NodeID of socket */ ++ ++ ++/* Power Control Unit, PCI dev 0x1e fn 1, 8086.6f99 */ ++#define PCI_DEVICE_ID_INTEL_BROADWELL_IMC_PCU 0x6f99 ++ ++/* Register offsets */ ++#define TSODCNTL 0xe0 ++ ++/* TSODCNTL fields */ ++ ++ ++/* DIMMs hold jc42 thermal sensors starting at i2c address 0x18 */ ++#define DIMM_SENSOR_DRV "jc42" ++#define DIMM_SENSOR_BASE_ADR 0x18 ++ ++ ++#define sanitycheck 1 ++ ++struct imc_channelpair { ++ struct i2c_adapter adapter; ++ bool can_write, cltt; ++}; ++ ++struct imc_pcu { ++ struct pci_dev *pci_dev; ++ u32 tsod_polling_interval; ++ struct mutex mutex; /* see imc_channelpair_claim() */ ++}; ++ ++struct imc_priv { ++ struct pci_dev *pci_dev; ++ struct imc_channelpair channelpair[2]; ++ struct imc_pcu pcu; ++ bool suspended; ++}; ++ ++static int imc_channelpair_claim(struct imc_priv *priv, int i) ++{ ++ if (priv->suspended) ++ return -EIO; ++ ++ /* ++ * i2c controllers need exclusive access to a psu register and wait ++ * then for 10ms before starting their transaction. ++ * ++ * Possible optimization: Once an i2c controller modified the psu ++ * register and waits, the other controller does not need to wait for ++ * the whole 10ms, but then only this other controller has to clean up ++ * the psu register. ++ */ ++ mutex_lock(&priv->pcu.mutex); ++ ++ if (priv->channelpair[i].cltt) { ++ pci_write_config_dword(priv->pcu.pci_dev, TSODCNTL, 0); ++ usleep_range(10000, 10500); ++ } ++ return 0; ++} ++ ++static void imc_channelpair_release(struct imc_priv *priv, int i) ++{ ++ if (priv->channelpair[i].cltt) { ++ /* set tosd_control.tsod_polling_interval to previous value */ ++ pci_write_config_dword(priv->pcu.pci_dev, TSODCNTL, ++ priv->pcu.tsod_polling_interval); ++ } ++ mutex_unlock(&priv->pcu.mutex); ++} ++ ++static bool imc_wait_for_transaction(struct imc_priv *priv, int i, u32 *stat) ++{ ++ int j; ++ static int busywaits = 1; ++ ++ /* ++ * Distribution of transaction time from 10000 collected samples: ++ * ++ * 70us: 1, 80us: 12, 90us: 34, 100us: 132, 110us: 424, 120us: 1138, ++ * 130us: 5224, 140us: 3035. ++ * ++ */ ++ usleep_range(131, 140); ++ ++ /* Don't give up, yet */ ++ for (j = 0; j < 20; j++) { ++ pci_read_config_dword(priv->pci_dev, SMBSTAT(i), stat); ++ if (!(*stat & SMBSTAT_SMB_BUSY)) { ++ if (j > busywaits) { ++ busywaits = j; ++ dev_warn(&priv->pci_dev->dev, ++ "Discovered surprisingly long transaction time (%d)\n", ++ busywaits); ++ } ++ return true; ++ } ++ udelay(9); ++ } ++ return false; ++} ++ ++/* ++ * The iMC supports five access types. The terminology is rather inconsistent. ++ * These are the types: ++ * ++ * "Write to pointer register SMBus": I2C_SMBUS_WRITE, I2C_SMBUS_BYTE ++ * ++ * Read byte/word: I2C_SMBUS_READ, I2C_SMBUS_{BYTE|WORD}_DATA ++ * ++ * Write byte/word: I2C_SMBUS_WRITE, I2C_SMBUS_{BYTE|WORD}_DATA ++ */ ++ ++static u32 imc_func(struct i2c_adapter *adapter) ++{ ++ int i; ++ struct imc_channelpair *cp; ++ struct imc_priv *priv = i2c_get_adapdata(adapter); ++ ++ i = (adapter == &priv->channelpair[0].adapter ? 0 : 1); ++ cp = &priv->channelpair[i]; ++ ++ if (cp->can_write) ++ return I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA; ++ else ++ return I2C_FUNC_SMBUS_READ_BYTE_DATA | ++ I2C_FUNC_SMBUS_WORD_DATA; ++} ++ ++static s32 imc_smbus_xfer(struct i2c_adapter *adap, u16 addr, ++ unsigned short flags, char read_write, u8 command, ++ int size, union i2c_smbus_data *data) ++{ ++ int ret, i; ++ u32 cmd = 0, cntl, stat; ++#ifdef sanitycheck ++ u32 final_cmd, final_cntl; ++#endif ++ struct imc_channelpair *cp; ++ struct imc_priv *priv = i2c_get_adapdata(adap); ++ ++ i = (adap == &priv->channelpair[0].adapter ? 0 : 1); ++ cp = &priv->channelpair[i]; ++ ++ /* Encode CMD part of addresses and access size */ ++ cmd |= ((u32)addr & 0x7) << SMBCMD_SA_SHIFT; ++ cmd |= ((u32)command) << SMBCMD_BA_SHIFT; ++ if (size == I2C_SMBUS_WORD_DATA) ++ cmd |= SMBCMD_WORD_ACCESS; ++ ++ /* Encode read/write and data to write */ ++ if (read_write == I2C_SMBUS_READ) { ++ cmd |= SMBCMD_TYPE_READ; ++ } else { ++ cmd |= SMBCMD_TYPE_WRITE; ++ cmd |= (size == I2C_SMBUS_WORD_DATA ++ ? swab16(data->word) ++ : data->byte); ++ } ++ ++ ret = imc_channelpair_claim(priv, i); ++ if (ret) ++ return ret; ++ ++ pci_read_config_dword(priv->pci_dev, SMBCNTL(i), &cntl); ++ cntl &= ~SMBCNTL_DTI_MASK; ++ cntl |= ((u32)addr >> 3) << SMBCNTL_DTI_SHIFT; ++ pci_write_config_dword(priv->pci_dev, SMBCNTL(i), cntl); ++ ++ cmd |= SMBCMD_TRIGGER; ++ pci_write_config_dword(priv->pci_dev, SMBCMD(i), cmd); ++ ++ if (!imc_wait_for_transaction(priv, i, &stat)) { ++ dev_warn(&priv->pci_dev->dev, "smbus transaction did not complete.\n"); ++ ret = -ETIMEDOUT; ++ goto xfer_out_release; ++ } ++ ++#ifdef sanitycheck /* This is a young driver. Keep the checks for now */ ++ pci_read_config_dword(priv->pci_dev, SMBCMD(i), &final_cmd); ++ pci_read_config_dword(priv->pci_dev, SMBCNTL(i), &final_cntl); ++ if (((cmd ^ final_cmd) & SMBCMD_OUR_BITS) || ++ ((cntl ^ final_cntl) & SMBCNTL_OUR_BITS)) { ++ dev_err(&priv->pci_dev->dev, ++ "Access to channel pair %d-%d raced with hardware: cmd 0x%08X->0x%08X, cntl 0x%08X->0x%08X\n", ++ 2*i, 2*i+1, cmd, final_cmd, cntl, final_cntl); ++ ret = -EIO; ++ goto xfer_out_release; ++ } ++#endif ++ ++ if (stat & SMBSTAT_SBE) { ++ /* ++ * While SBE is set hardware TSOD polling is disabled. This is ++ * very bad as this bit is RO-V and will only be cleared after ++ * a further software initiated transaction finishes ++ * successfully. ++ */ ++ dev_err(&priv->pci_dev->dev, ++ "smbus error: sbe is set 0x%x\n", stat); ++ ret = -ENXIO; ++ goto xfer_out_release; ++ } ++ ++ if (read_write == I2C_SMBUS_READ) { ++ if (!(stat & SMBSTAT_RDO)) { ++ dev_warn(&priv->pci_dev->dev, ++ "Unexpected read status 0x%08X\n", stat); ++ ret = -EIO; ++ goto xfer_out_release; ++ } ++ /* ++ * The iMC SMBus controller thinks of SMBus words as being ++ * big-endian (MSB first). Linux treats them as little-endian, ++ * so we need to swap them. ++ */ ++ if (size == I2C_SMBUS_WORD_DATA) ++ data->word = swab16(stat & SMBSTAT_RDATA_MASK); ++ else ++ data->byte = stat & 0xFF; ++ } else { ++ if (!(stat & SMBSTAT_WOD)) { ++ dev_warn(&priv->pci_dev->dev, ++ "Unexpected write status 0x%08X\n", stat); ++ ret = -EIO; ++ } ++ } ++ ++xfer_out_release: ++ imc_channelpair_release(priv, i); ++ ++ return ret; ++} ++ ++static const struct i2c_algorithm imc_smbus_algorithm = { ++ .smbus_xfer = imc_smbus_xfer, ++ .functionality = imc_func, ++}; ++ ++static void imc_instantiate_sensors(struct i2c_adapter *adapter, u8 presence) ++{ ++ struct i2c_board_info info = {}; ++ ++ strcpy(info.type, DIMM_SENSOR_DRV); ++ info.addr = DIMM_SENSOR_BASE_ADR; ++ ++ /* ++ * Presence is a bit vector. Bits from right to left map into i2c slave ++ * addresses starting 0x18. ++ */ ++ while (presence) { ++ if (presence & 0x1) ++ i2c_new_client_device(adapter, &info); ++ info.addr++; ++ presence >>= 1; ++ } ++} ++ ++static int imc_init_channelpair(struct imc_priv *priv, int i, int socket) ++{ ++ int err; ++ u32 val; ++ struct imc_channelpair *cp = &priv->channelpair[i]; ++ ++ i2c_set_adapdata(&cp->adapter, priv); ++ cp->adapter.owner = THIS_MODULE; ++ cp->adapter.algo = &imc_smbus_algorithm; ++ cp->adapter.dev.parent = &priv->pci_dev->dev; ++ ++ pci_read_config_dword(priv->pci_dev, SMBCNTL(i), &val); ++ cp->can_write = !(val & SMBCNTL_DIS_WRT); ++ ++ /* ++ * A TSOD polling interval of > 0 tells us if CLTT mode is enabled on ++ * some channel pair. ++ * ++ * Is there a better way to check for CLTT mode? In particular, is ++ * there a way to distingush the mode on a channel pair basis? ++ */ ++ cp->cltt = (priv->pcu.tsod_polling_interval > 0); ++ ++ snprintf(cp->adapter.name, sizeof(cp->adapter.name), ++ "iMC socket %d for channel pair %d-%d", socket, 2*i, 2*i+1); ++ err = i2c_add_adapter(&cp->adapter); ++ if (err) ++ return err; ++ ++ /* For reasons unknown, TSOD_PRES_MASK is only set in CLTT mode. */ ++ if (cp->cltt) { ++ dev_info(&priv->pci_dev->dev, ++ "CLTT is enabled on channel pair %d-%d. Thermal sensors will be automatically enabled\n", ++ 2*i, 2*i+1); ++ } else { ++ dev_info(&priv->pci_dev->dev, ++ "CLTT is disabled on channel pair %d-%d. Thermal sensors need to be manually enabled\n", ++ 2*i, 2*i+1); ++ } ++ ++ imc_instantiate_sensors(&cp->adapter, val & SMBCNTL_TSOD_PRES_MASK); ++ ++ return 0; ++} ++ ++static void imc_free_channelpair(struct imc_priv *priv, int i) ++{ ++ struct imc_channelpair *cp = &priv->channelpair[i]; ++ ++ i2c_del_adapter(&cp->adapter); ++} ++ ++static struct pci_dev *imc_get_related_device(struct pci_bus *bus, ++ unsigned int devfn, u16 devid) ++{ ++ struct pci_dev *dev = pci_get_slot(bus, devfn); ++ ++ if (!dev) ++ return NULL; ++ ++ if (dev->vendor != PCI_VENDOR_ID_INTEL || dev->device != devid) { ++ pci_dev_put(dev); ++ return NULL; ++ } ++ return dev; ++} ++ ++static int imc_probe(struct pci_dev *dev, const struct pci_device_id *id) ++{ ++ int i, j, err; ++ struct imc_priv *priv; ++ struct pci_dev *sad; /* System Address Decoder */ ++ u32 sadcntl; ++ ++ /* Sanity check. This device is always at 0x13.0 */ ++ if (dev->devfn != PCI_DEVFN(0x13, 0)) ++ return -ENODEV; ++ ++ priv = devm_kzalloc(&dev->dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ priv->pci_dev = dev; ++ pci_set_drvdata(dev, priv); ++ ++ /* ++ * From sad, we learn the local node id of the socket. ++ * ++ * The socket will not change at runtime and so we throw away sad. ++ */ ++ sad = imc_get_related_device(dev->bus, PCI_DEVFN(0x0f, 5), ++ PCI_DEVICE_ID_INTEL_BROADWELL_IMC_SAD); ++ if (!sad) { ++ err = -ENODEV; ++ goto probe_out_free; ++ } ++ pci_read_config_dword(sad, SADCNTL, &sadcntl); ++ pci_dev_put(sad); ++ ++ /* ++ * From pcu, we access the CLTT polling interval. ++ * ++ * The polling interval is set by BIOS. We assume it will not change at ++ * runtime and cache the initial value. ++ */ ++ priv->pcu.pci_dev = imc_get_related_device(dev->bus, PCI_DEVFN(0x1e, 1), ++ PCI_DEVICE_ID_INTEL_BROADWELL_IMC_PCU); ++ if (!priv->pcu.pci_dev) { ++ err = -ENODEV; ++ goto probe_out_free; ++ } ++ pci_read_config_dword(priv->pcu.pci_dev, TSODCNTL, ++ &priv->pcu.tsod_polling_interval); ++ ++ mutex_init(&priv->pcu.mutex); ++ ++ for (i = 0; i < 2; i++) { ++ err = imc_init_channelpair(priv, i, ++ sadcntl & SADCNTL_LOCAL_NODEID_MASK); ++ if (err) ++ goto probe_out_free_channelpair; ++ } ++ ++ return 0; ++ ++probe_out_free_channelpair: ++ for (j = 0; j < i; j++) ++ imc_free_channelpair(priv, j); ++ ++ mutex_destroy(&priv->pcu.mutex); ++ ++probe_out_free: ++ kfree(priv); ++ return err; ++} ++ ++static void imc_remove(struct pci_dev *dev) ++{ ++ int i; ++ struct imc_priv *priv = pci_get_drvdata(dev); ++ ++ for (i = 0; i < 2; i++) ++ imc_free_channelpair(priv, i); ++ ++ /* set tosd_control.tsod_polling_interval to initial value */ ++ pci_write_config_dword(priv->pcu.pci_dev, TSODCNTL, ++ priv->pcu.tsod_polling_interval); ++ ++ mutex_destroy(&priv->pcu.mutex); ++} ++ ++static int imc_suspend(struct pci_dev *dev, pm_message_t mesg) ++{ ++ struct imc_priv *priv = pci_get_drvdata(dev); ++ ++ /* BIOS is in charge. We should finish any pending transaction */ ++ priv->suspended = true; ++ ++ return 0; ++} ++ ++static int imc_resume(struct pci_dev *dev) ++{ ++ struct imc_priv *priv = pci_get_drvdata(dev); ++ ++ priv->suspended = false; ++ ++ return 0; ++} ++ ++static const struct pci_device_id imc_ids[] = { ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, ++ PCI_DEVICE_ID_INTEL_BROADWELL_IMC_TA) }, ++ { 0, } ++}; ++MODULE_DEVICE_TABLE(pci, imc_ids); ++ ++static struct pci_driver imc_pci_driver = { ++ .name = "imc_smbus", ++ .id_table = imc_ids, ++ .probe = imc_probe, ++ .remove = imc_remove, ++ .suspend = imc_suspend, ++ .resume = imc_resume, ++}; ++ ++static int __init i2c_imc_init(void) ++{ ++ return pci_register_driver(&imc_pci_driver); ++} ++module_init(i2c_imc_init); ++ ++static void __exit i2c_imc_exit(void) ++{ ++ pci_unregister_driver(&imc_pci_driver); ++} ++module_exit(i2c_imc_exit); ++ ++MODULE_AUTHOR("Stefan Schaeckeler "); ++MODULE_DESCRIPTION("iMC SMBus driver"); ++MODULE_LICENSE("GPL v2"); +-- +2.20.1 + diff --git a/patch/kconfig-inclusions b/patch/kconfig-inclusions index 5bb2aea0b..d690f02f7 100644 --- a/patch/kconfig-inclusions +++ b/patch/kconfig-inclusions @@ -45,6 +45,7 @@ CONFIG_I2C_CHARDEV=m CONFIG_I2C_MUX=m CONFIG_I2C_SMBUS=m CONFIG_I2C_I801=m +CONFIG_I2C_IMC=m CONFIG_I2C_DESIGNWARE_PCI=m CONFIG_I2C_MLXCPLD=m CONFIG_PINCTRL=y diff --git a/patch/series b/patch/series index 5c6a6e9f7..c5132d42c 100755 --- a/patch/series +++ b/patch/series @@ -33,6 +33,7 @@ driver-support-optoe-twoaddr-a2h-access.patch driver-support-optoe-oneaddr-pageable.patch driver-support-optoe-update-to-linux-6.1.patch driver-support-optoe-dynamic-write-timeout.patch +driver-support-i2c-bus-imc.patch driver-net-tg3-add-param-short-preamble-and-reset.patch 0004-dt-bindings-hwmon-Add-missing-documentation-for-lm75.patch 0005-dt-bindings-hwmon-Add-tmp75b-to-lm75.txt.patch