From: Simone Chifari <simone.chifari@gmail.com>
To: linux-i2c@vger.kernel.org
Cc: wsa@kernel.org, linux-kernel@vger.kernel.org,
Simone Chifari <simone.chifari@gmail.com>
Subject: [PATCH] i2c: imc-x299: add driver for Intel X299/Skylake-X iMC SMBus engine
Date: Wed, 17 Jun 2026 21:45:35 +0200 [thread overview]
Message-ID: <20260617194536.298765-1-simone.chifari@gmail.com> (raw)
The integrated memory controller (iMC) on Intel Skylake-X / Cascade
Lake-X (X299, PCU function 8086:2085) exposes an SMBus engine that
reaches the DDR4 DIMM SPD EEPROMs and thermal sensors. Present it as two
standard I2C adapters (one per hardware SMBus channel) so that i2c-tools
and lm-sensors can use it.
The engine is driven through the PCI configuration space of the PCU
function. On this platform System Management Mode traps CF8/CFC
port-based config writes to this function, so pci_write_config_dword()
never completes. The MMCONFIG (ECAM) window is not trapped, so the
driver ioremap()s the ECAM page of the function and drives the registers
by MMIO, locating the MMCONFIG base via a manual walk of the ACPI MCFG
table (the pci_mmcfg_* helpers are not exported to modules).
Relation to prior iMC SMBus work: two earlier attempts to upstream an
iMC SMBus driver exist. Lutomirski (2013-2016, Sandy Bridge-EP
8086:3ca8) used CF8/CFC pci_{read,write}_config_dword, which is correct
on that platform because SMM does not trap those ports there; it was not
merged, largely over missing BMC/CLTT bus arbitration and a required
allow_unsafe_access flag. schaecsn (2020, Broadwell-E 8086:6fa8) added
TSOD arbitration but saw no reviewer response. This device is a
different ID (0x2085), with a different register layout (DATA/STATUS/CTRL
at 0x9C-0xB8 vs 0x180-0x188) and a fundamentally different access method
(ECAM MMIO), so a separate file is cleaner than a per-generation hw_data
table with mutually incompatible access paths. The X299 HEDT platform
has no BMC and no CLTT firmware polling, so the arbitration concern does
not apply and no allow_unsafe_access flag is needed.
Only SMBus BYTE and BYTE_DATA transfers are implemented; the devices on
this bus need nothing more. An optional settle_us module parameter
(default 0) inserts a post-write delay for devices that require one.
Tested on ASUS TUF X299 Mark 1 (i9-10900X, 4x Kingston FURY DDR4): both
channels enumerate, SPD EEPROMs are readable, udev autoloads the module,
and 20 rmmod/modprobe cycles run clean with no oops or leaks.
Signed-off-by: Simone Chifari <simone.chifari@gmail.com>
---
MAINTAINERS | 6 +
drivers/i2c/busses/Kconfig | 19 ++
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-imc-x299.c | 475 ++++++++++++++++++++++++++++++
4 files changed, 501 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-imc-x299.c
diff --git a/MAINTAINERS b/MAINTAINERS
index c8d4b913f..bb4b06b5b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13374,6 +13374,12 @@ L: netdev@vger.kernel.org
S: Orphan
F: drivers/net/wwan/iosm/
+INTEL X299 iMC SMBUS DRIVER
+M: Simone Chifari <simone.chifari@gmail.com>
+L: linux-i2c@vger.kernel.org
+S: Maintained
+F: drivers/i2c/busses/i2c-imc-x299.c
+
INTEL(R) FLEXIBLE RETURN AND EVENT DELIVERY
M: Xin Li <xin@zytor.com>
M: "H. Peter Anvin" <hpa@zytor.com>
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 8c935f867..12732b8ff 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -180,6 +180,25 @@ config I2C_I801_MUX
Optional support for multiplexed SMBUS on certain systems with
more than 8 memory slots.
+config I2C_IMC_X299
+ tristate "Intel X299 / Skylake-X iMC SMBus adapter"
+ depends on PCI && ACPI && X86
+ help
+ Say Y here if you want kernel support for the integrated memory
+ controller (iMC) SMBus engine found in Intel Skylake-X / Cascade
+ Lake-X processors (socket LGA 2066, platform X299, PCU function
+ 8086:2085).
+
+ The engine is exposed through the PCI configuration space of the
+ PCU function and is driven via ECAM MMIO (not CF8/CFC port I/O,
+ which is trapped by System Management Mode on this platform).
+ Two I2C adapters are registered, one per hardware SMBus channel,
+ allowing access to DDR4 DIMM SPD EEPROMs (0x50-0x57) and thermal
+ sensors from userspace via standard i2c-tools and lm-sensors.
+
+ If unsure, say N. This driver is only useful on Intel X299 desktop
+ / HEDT systems with the Skylake-X or Cascade Lake-X CPU.
+
config I2C_ISCH
tristate "Intel SCH SMBus 1.0"
depends on PCI && HAS_IOPORT
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 547123ab3..8c7cb83fd 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_I2C_AMD756) += i2c-amd756.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_X299) += i2c-imc-x299.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-x299.c b/drivers/i2c/busses/i2c-imc-x299.c
new file mode 100644
index 000000000..f175b9da2
--- /dev/null
+++ b/drivers/i2c/busses/i2c-imc-x299.c
@@ -0,0 +1,475 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel X299 / Skylake-X iMC SMBus I2C adapter.
+ *
+ * The integrated memory controller (iMC) on Intel Skylake-X / Cascade Lake-X
+ * (X299) exposes an SMBus engine used to reach the SPD EEPROMs and thermal
+ * sensors on DDR4 DIMMs. The engine is driven through the PCI configuration
+ * space of the Sky Lake-E PCU function (0000:16:1e.5, 8086:2085). This driver
+ * presents that engine as two standard Linux I2C adapters - one per hardware
+ * SMBus channel - so that i2c-tools and lm-sensors can use it without bespoke
+ * sysfs hacks.
+ *
+ * Why ECAM MMIO instead of the usual CF8/CFC config accessors:
+ * On this platform System Management Mode (SMM) traps and mangles port-based
+ * (CF8/CFC) writes to the first 256 config bytes of this function, so the SMBus
+ * command never completes. The boot log reports "PCI: Using configuration
+ * type 1 (probe)" confirming the default path is port-based; the MMCONFIG
+ * (ECAM) window is not trapped. Windows' NTIOLib reaches the registers via
+ * ECAM, which is how we confirmed the register layout on hardware. We map the
+ * ECAM page of the target function and drive the registers by MMIO, exactly as
+ * the firmware does.
+ *
+ * ECAM phys(off) = mmcfg_base + (bus<<20) + (dev<<15) + (fn<<12) + off
+ * mmcfg_base is read from the ACPI MCFG table at probe time (not hardcoded).
+ *
+ * Per-channel register triple within the config page:
+ * ch0 ch1
+ * CTRL (data) 0xB4 0xB8 write: data byte in bits[23:16]; read: low byte
+ * DATA (cmd) 0x9C 0xA0 FRAME | (cmd << 8) | reg ; bit19 = GO
+ * STATUS 0xA8 0xAC busy while bit0 set; done when clear,
+ * bit1 (0x02) set on completion = device NACKed
+ * SMBus command byte (DATA bits[15:8]) = (rw << 7) | addr7: the 7-bit slave
+ * address with bit7 = direction (1 write / 0 read). The register/offset goes
+ * in DATA[7:0]. So 0x50 reads SPD EEPROM 0x50. This was decoded and
+ * confirmed on hardware.
+ *
+ * The two channels are independent SMBus buses (DIMMs 1,2 on ch0; DIMMs 3,4 on
+ * ch1), hence two i2c_adapter instances exposed as separate /dev/i2c-* nodes.
+ * Each channel carries the DIMM SPD EEPROMs (0x50-0x57) and thermal sensors -
+ * all reachable by 7-bit address.
+ */
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+
+/* expected PCI id of the Sky Lake-E PCU SMBus function */
+#define PCU_DEVICE 0x2085
+#define PCU_ID ((PCU_DEVICE << 16) | PCI_VENDOR_ID_INTEL)
+
+#define CFG_SIZE 0x1000UL
+
+/* per-channel register offsets within the config page */
+#define CH0_CTRL 0xB4
+#define CH0_DATA 0x9C
+#define CH0_STAT 0xA8
+#define CH1_CTRL 0xB8
+#define CH1_DATA 0xA0
+#define CH1_STAT 0xAC
+
+/*
+ * Command word written to the DATA register:
+ * bits[31:16] = FRAME (engine config + GO bit19), constant
+ * bits[15:8] = SMBus command byte = (rw << 7) | addr7
+ * rw bit (0x80): 1 = write, 0 = read
+ * addr7 : 7-bit SMBus slave address
+ * bits[7:0] = register/offset within the addressed device
+ * For a write the data byte is latched into CTRL[23:16] beforehand. This
+ * encoding was confirmed on hardware: command 0x50 reads SPD EEPROM 0x50
+ * (DDR4 signature).
+ */
+#define FRAME 0x20080000U /* engine config + GO bit19, constant */
+#define RW_WRITE 0x80 /* OR into the command byte for a write */
+#define GO_BIT BIT(19)
+#define STAT_BUSY BIT(0) /* low bit set while transaction in flight */
+#define STAT_NACK BIT(1) /* set on completion if the device NACKed */
+
+static unsigned int settle_us;
+module_param(settle_us, uint, 0644);
+MODULE_PARM_DESC(settle_us, "post-write settle delay in us (default 0)");
+
+struct imc_chan {
+ u32 ctrl, data, stat;
+};
+
+static const struct imc_chan imc_chans[2] = {
+ { CH0_CTRL, CH0_DATA, CH0_STAT },
+ { CH1_CTRL, CH1_DATA, CH1_STAT },
+};
+
+/* one driver state object, shared by both per-channel adapters */
+struct imc_smbus {
+ struct device *dev; /* &pdev->dev, for dev_*() logging */
+ void __iomem *cfg; /* ioremapped ECAM page of the function */
+ struct mutex lock; /* serialises all SMBus transactions */
+ struct i2c_adapter adap[2]; /* one per hardware channel */
+};
+
+/*
+ * ECAM base discovery from ACPI MCFG. acpi_table_parse() is not exported to
+ * modules, so map the MCFG table with acpi_get_table() (exported) and walk the
+ * allocation entries by hand. Uses pdev's PCI segment and bus number so no
+ * module parameter override is needed.
+ */
+#ifdef CONFIG_ACPI
+static u64 imc_detect_mmcfg_base(struct pci_dev *pdev)
+{
+ unsigned int seg = pci_domain_nr(pdev->bus);
+ unsigned int bus = pdev->bus->number;
+ struct acpi_table_header *hdr;
+ struct acpi_table_mcfg *mcfg;
+ struct acpi_mcfg_allocation *e;
+ unsigned long n, i;
+ u64 base = 0;
+
+ if (ACPI_FAILURE(acpi_get_table(ACPI_SIG_MCFG, 0, &hdr)))
+ return 0;
+
+ mcfg = (struct acpi_table_mcfg *)hdr;
+ if (hdr->length < sizeof(*mcfg)) {
+ acpi_put_table(hdr);
+ return 0;
+ }
+ e = (struct acpi_mcfg_allocation *)(mcfg + 1);
+ n = (hdr->length - sizeof(*mcfg)) / sizeof(*e);
+ for (i = 0; i < n; i++) {
+ if (e[i].pci_segment == seg &&
+ bus >= e[i].start_bus_number &&
+ bus <= e[i].end_bus_number) {
+ base = e[i].address;
+ break;
+ }
+ }
+ acpi_put_table(hdr);
+ return base;
+}
+#else
+static u64 imc_detect_mmcfg_base(struct pci_dev *pdev)
+{
+ return 0;
+}
+#endif
+
+/*
+ * Wait until GO clears (transaction issued), then until the busy bit drops.
+ * Returns 0 on completion, -ETIMEDOUT if the engine never went idle. On
+ * success *status (if non-NULL) gets the final status word. Process context
+ * only - it sleeps between polls.
+ */
+static int imc_wait(struct imc_smbus *s, const struct imc_chan *c, u32 *status)
+{
+ u32 val;
+ int ret;
+
+ ret = readl_poll_timeout(s->cfg + c->data, val, !(val & GO_BIT), 10, 200000);
+ if (ret)
+ return ret;
+
+ ret = readl_poll_timeout(s->cfg + c->stat, val, !(val & STAT_BUSY), 10, 50000);
+ if (ret)
+ return ret;
+
+ if (status)
+ *status = val;
+
+ return 0;
+}
+
+/*
+ * Poll the busy bit clear only (no GO check). The firmware polls STATUS after
+ * the CTRL (data-latch) write too, before issuing the DATA/GO word.
+ */
+static int imc_wait_status(struct imc_smbus *s, const struct imc_chan *c)
+{
+ u32 val;
+ int ret;
+
+ ret = readl_poll_timeout(s->cfg + c->stat, val, !(val & STAT_BUSY), 10, 50000);
+ if (ret) {
+ dev_dbg(s->dev, "pre-command poll timed out, engine still busy\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+/* translate a completed transaction's status into an errno (0 = ACK) */
+static int imc_check_status(struct imc_smbus *s, u32 status, u8 addr, u8 reg)
+{
+ if (status & STAT_NACK) {
+ /* no device at this address, or it refused the access */
+ dev_dbg(s->dev, "addr 0x%02x reg 0x%02x NACK (stat 0x%08x)\n",
+ addr, reg, status);
+ return -ENXIO;
+ }
+ return 0;
+}
+
+/* SMBus write-byte to addr: latch the data byte, then issue the command. */
+static int imc_write_byte(struct imc_smbus *s, const struct imc_chan *c,
+ u8 addr, u8 reg, u8 val)
+{
+ u32 status = 0;
+ int ret;
+
+ writel((u32)val << 16, s->cfg + c->ctrl);
+ ret = imc_wait_status(s, c);
+ if (ret)
+ return ret;
+ writel(FRAME | ((u32)(addr | RW_WRITE) << 8) | reg, s->cfg + c->data);
+ ret = imc_wait(s, c, &status);
+ if (ret) {
+ dev_warn_ratelimited(s->dev,
+ "write addr 0x%02x reg 0x%02x timed out\n",
+ addr, reg);
+ return ret;
+ }
+ ret = imc_check_status(s, status, addr, reg);
+ if (ret)
+ return ret;
+
+ /*
+ * Optional post-write settle delay. Some target devices on this bus
+ * may require a quiet period after a write transaction before the
+ * next command is issued.
+ */
+ if (settle_us)
+ usleep_range(settle_us, settle_us + 1000);
+
+ return 0;
+}
+
+/* SMBus read-byte from addr: issue the command, return the low data byte. */
+static int imc_read_byte(struct imc_smbus *s, const struct imc_chan *c,
+ u8 addr, u8 reg, u8 *val)
+{
+ u32 status = 0;
+ int ret;
+
+ ret = imc_wait_status(s, c);
+ if (ret)
+ return ret;
+ writel(FRAME | ((u32)addr << 8) | reg, s->cfg + c->data);
+ ret = imc_wait(s, c, &status);
+ if (ret) {
+ dev_warn_ratelimited(s->dev,
+ "read addr 0x%02x reg 0x%02x timed out\n",
+ addr, reg);
+ return ret;
+ }
+ ret = imc_check_status(s, status, addr, reg);
+ if (ret)
+ return ret;
+ *val = readl(s->cfg + c->ctrl) & 0xFF;
+ return 0;
+}
+
+/*
+ * Standard SMBus transfer callback. The per-channel imc_chan is stashed in the
+ * adapter's algo_data. The command word carries the 7-bit address, so
+ * SPD EEPROMs (0x50-0x57) are reachable on each channel. Only BYTE and
+ * BYTE_DATA are supported (see imc_func()); larger transfers would need the
+ * engine's block primitives, which are not used by the devices on this bus.
+ */
+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)
+{
+ struct imc_smbus *s = i2c_get_adapdata(adap);
+ const struct imc_chan *c = adap->algo_data;
+ u8 reg, val = 0;
+ int ret;
+
+ if (addr > 0x7f)
+ return -EINVAL;
+
+ /*
+ * SMBus QUICK has no data phase, and this engine has no zero-length
+ * transaction, so emulate the presence probe with a receive-byte (read
+ * offset 0): it is side-effect-free, and every device on this bus (SPD
+ * 0x50-0x57, the DDR4 page-select 0x36/0x37) ACKs a read.
+ * QUICK is what i2cdetect (without -r) and other userspace probing tools
+ * use to test presence, so reporting it lets stock tooling detect the DIMMs.
+ */
+ if (size == I2C_SMBUS_QUICK) {
+ mutex_lock(&s->lock);
+ ret = imc_read_byte(s, c, addr, 0, &val);
+ mutex_unlock(&s->lock);
+ return ret;
+ }
+
+ /*
+ * Every transaction on this engine carries a register/offset byte. For
+ * BYTE_DATA the SMBus "command" is that register; for plain BYTE there
+ * is no register phase, so offset 0 is used (read = receive-byte;
+ * write = the command byte is the value written to offset 0).
+ */
+ switch (size) {
+ case I2C_SMBUS_BYTE:
+ reg = (read_write == I2C_SMBUS_WRITE) ? 0 : command;
+ break;
+ case I2C_SMBUS_BYTE_DATA:
+ reg = command;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ mutex_lock(&s->lock);
+ if (read_write == I2C_SMBUS_WRITE) {
+ val = (size == I2C_SMBUS_BYTE) ? command : data->byte;
+ dev_dbg(s->dev, "ch%d W addr=%02x reg=%02x val=%02x\n",
+ (int)(c - imc_chans), addr, reg, val);
+ ret = imc_write_byte(s, c, addr, reg, val);
+ } else {
+ ret = imc_read_byte(s, c, addr, reg, &val);
+ if (!ret)
+ data->byte = val;
+ dev_dbg(s->dev, "ch%d R addr=%02x reg=%02x -> %02x (ret %d)\n",
+ (int)(c - imc_chans), addr, reg, val, ret);
+ }
+ mutex_unlock(&s->lock);
+
+ return ret;
+}
+
+static u32 imc_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_BYTE;
+}
+
+static const struct i2c_algorithm imc_algo = {
+ .smbus_xfer = imc_smbus_xfer,
+ .functionality = imc_func,
+};
+
+static void imc_mutex_destroy(void *data)
+{
+ struct mutex *lock = data;
+
+ mutex_destroy(lock);
+}
+
+static int imc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ resource_size_t phys;
+ struct imc_smbus *s;
+ u8 imc_bus_hw;
+ u64 base;
+ u32 cfg0, cc;
+ int ret, i;
+
+ s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL);
+ if (!s)
+ return -ENOMEM;
+
+ ret = pcim_enable_device(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "cannot enable PCI device: %d\n", ret);
+ return ret;
+ }
+
+ s->dev = &pdev->dev;
+ mutex_init(&s->lock);
+ ret = devm_add_action_or_reset(&pdev->dev, imc_mutex_destroy, &s->lock);
+ if (ret)
+ return ret;
+
+ base = imc_detect_mmcfg_base(pdev);
+ if (!base) {
+ dev_err(&pdev->dev, "cannot resolve MMCONFIG base\n");
+ return -ENODEV;
+ }
+
+ phys = base +
+ ((resource_size_t)pdev->bus->number << 20) +
+ ((resource_size_t)PCI_SLOT(pdev->devfn) << 15) +
+ ((resource_size_t)PCI_FUNC(pdev->devfn) << 12);
+
+ /*
+ * Deliberately no request_mem_region(): the MMCONFIG window is already
+ * claimed as a firmware/PCI resource, so a reservation would fail with
+ * -EBUSY. The pci_driver binding keeps the function alive; the registers
+ * we drive are side-band controls the kernel does not otherwise touch.
+ */
+ s->cfg = devm_ioremap(&pdev->dev, phys, CFG_SIZE);
+ if (!s->cfg) {
+ dev_err(&pdev->dev, "ioremap(%pa) failed\n", &phys);
+ return -ENOMEM;
+ }
+
+ cfg0 = readl(s->cfg + 0);
+ if (cfg0 != PCU_ID) {
+ dev_err(&pdev->dev, "wrong device at ECAM %pa (cfg[0]=0x%08x)\n",
+ &phys, cfg0);
+ return -ENODEV;
+ }
+
+ /*
+ * Cross-check the iMC bus number against the configuration register
+ * value: cfg[0xCC] bits[15:8] = the iMC SMBus bus number as seen by
+ * the PCU. On all known X299 boards this matches the probed bus number.
+ * A mismatch means the ECAM walk landed on the wrong slot — warn but
+ * continue; the binding is already locked to 8086:2085.
+ */
+ cc = readl(s->cfg + 0xCC);
+ imc_bus_hw = (cc >> 8) & 0xFF;
+
+ if (imc_bus_hw && imc_bus_hw != (u8)pdev->bus->number)
+ dev_warn(&pdev->dev,
+ "cfg[0xCC] reports iMC bus 0x%02x but probed bus=0x%02x\n",
+ imc_bus_hw, (u8)pdev->bus->number);
+ else
+ dev_dbg(&pdev->dev,
+ "cfg[0xCC]=0x%08x iMC bus 0x%02x confirmed\n",
+ cc, imc_bus_hw);
+
+ dev_info(&pdev->dev, "ECAM %pa (mmcfg_base=0x%llx)\n", &phys, base);
+
+ for (i = 0; i < 2; i++) {
+ struct i2c_adapter *a = &s->adap[i];
+
+ a->owner = THIS_MODULE;
+ a->algo = &imc_algo;
+ a->algo_data = (void *)&imc_chans[i];
+ a->dev.parent = &pdev->dev;
+ i2c_set_adapdata(a, s);
+ snprintf(a->name, sizeof(a->name),
+ "iMC SMBus X299 channel %d", i);
+
+ ret = devm_i2c_add_adapter(&pdev->dev, a);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "i2c_add_adapter ch%d failed: %d\n", i, ret);
+ return ret;
+ }
+ }
+
+ dev_info(&pdev->dev, "registered 2 SMBus channels (use i2cdetect -l)\n");
+ return 0;
+}
+
+static const struct pci_device_id imc_pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCU_DEVICE) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, imc_pci_ids);
+
+/*
+ * All resources (ioremap, i2c adapters, mutex) are devm-managed and are
+ * released automatically after probe returns or during device unbinding, so
+ * no .remove callback is needed. The register state is deliberately left
+ * as-is on unbind: the engine carries no driver-private latched state that
+ * needs teardown, and the SMBus controls we drive are side-band registers the
+ * rest of the kernel does not touch.
+ */
+static struct pci_driver imc_driver = {
+ .name = "i2c-imc-x299",
+ .id_table = imc_pci_ids,
+ .probe = imc_pci_probe,
+};
+module_pci_driver(imc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Simone Chifari");
+MODULE_DESCRIPTION("Intel X299/Skylake-X iMC SMBus I2C adapter (ECAM MMIO)");
--
2.43.0
reply other threads:[~2026-06-17 19:46 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260617194536.298765-1-simone.chifari@gmail.com \
--to=simone.chifari@gmail.com \
--cc=linux-i2c@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=wsa@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox