[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20260201094358.1440593-7-alexander.usyskin@intel.com>
Date: Sun, 1 Feb 2026 11:43:57 +0200
From: Alexander Usyskin <alexander.usyskin@...el.com>
To: Greg Kroah-Hartman <gregkh@...uxfoundation.org>
Cc: Menachem Adin <menachem.adin@...el.com>,
Alexander Usyskin <alexander.usyskin@...el.com>,
linux-kernel@...r.kernel.org,
Mika Westerberg <mika.westerberg@...ux.intel.com>
Subject: [char-misc-next 6/7] mei: csc: support controller with separate PCI device
Intel PCI driver for chassis controller embedded in Intel graphics
devices.
An MEI device here called CSC can be embedded in discrete
Intel graphics devices having separate PCI device, to support a range
of chassis tasks such as graphics card firmware update and security tasks.
Reviewed-by: Mika Westerberg <mika.westerberg@...ux.intel.com>
Signed-off-by: Alexander Usyskin <alexander.usyskin@...el.com>
---
drivers/misc/mei/Kconfig | 11 ++
drivers/misc/mei/Makefile | 3 +
drivers/misc/mei/hw-me-regs.h | 3 +
drivers/misc/mei/hw-me.c | 27 ++++
drivers/misc/mei/hw-me.h | 2 +
drivers/misc/mei/init.c | 4 +-
drivers/misc/mei/mei_dev.h | 3 +
drivers/misc/mei/pci-csc.c | 259 ++++++++++++++++++++++++++++++++++
8 files changed, 311 insertions(+), 1 deletion(-)
create mode 100644 drivers/misc/mei/pci-csc.c
diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig
index 5902dd1ee44b..8d192c3a1d59 100644
--- a/drivers/misc/mei/Kconfig
+++ b/drivers/misc/mei/Kconfig
@@ -58,6 +58,17 @@ config INTEL_MEI_GSC
tasks such as graphics card firmware update and security
tasks.
+config INTEL_MEI_CSC
+ tristate "Intel MEI CSC embedded device"
+ depends on INTEL_MEI_ME
+ help
+ Intel PCI driver for the chassis controller embedded in Intel graphics devices.
+
+ An MEI device here called CSC can be embedded in discrete
+ Intel graphics devices, to support a range of chassis
+ tasks such as graphics card firmware update and security
+ tasks.
+
config INTEL_MEI_VSC_HW
tristate "Intel visual sensing controller device transport driver"
depends on ACPI && SPI
diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile
index a203ed766b33..9a6aa335921e 100644
--- a/drivers/misc/mei/Makefile
+++ b/drivers/misc/mei/Makefile
@@ -21,6 +21,9 @@ mei-me-objs += hw-me.o
obj-$(CONFIG_INTEL_MEI_GSC) += mei-gsc.o
mei-gsc-objs := gsc-me.o
+obj-$(CONFIG_INTEL_MEI_CSC) += mei-csc.o
+mei-csc-objs := pci-csc.o
+
obj-$(CONFIG_INTEL_MEI_TXE) += mei-txe.o
mei-txe-objs := pci-txe.o
mei-txe-objs += hw-txe.o
diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h
index 840e1fd2714c..9b1675f98404 100644
--- a/drivers/misc/mei/hw-me-regs.h
+++ b/drivers/misc/mei/hw-me-regs.h
@@ -124,6 +124,8 @@
#define PCI_DEVICE_ID_INTEL_MEI_NVL_S 0x6E68 /* Nova Lake Point S */
+#define PCI_DEVICE_ID_INTEL_MEI_CRI 0x6766 /* Crescent Island */
+
/*
* MEI HW Section
*/
@@ -134,6 +136,7 @@
# define PCI_CFG_HFS_1_OPMODE_MSK 0xf0000 /* OP MODE Mask: SPS <= 4.0 */
# define PCI_CFG_HFS_1_OPMODE_SPS 0xf0000 /* SPS SKU : SPS <= 4.0 */
#define PCI_CFG_HFS_2 0x48
+# define PCI_CFG_HFS_2_D3_BLOCK BIT(7)
# define PCI_CFG_HFS_2_PM_CMOFF_TO_CMX_ERROR 0x1000000 /* CMoff->CMx wake after an error */
# define PCI_CFG_HFS_2_PM_CM_RESET_ERROR 0x5000000 /* CME reset due to exception */
# define PCI_CFG_HFS_2_PM_EVENT_MASK 0xf000000
diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c
index 72a7cfb2989f..3412a7b5b0e8 100644
--- a/drivers/misc/mei/hw-me.c
+++ b/drivers/misc/mei/hw-me.c
@@ -224,6 +224,15 @@ static int mei_me_fw_status(struct mei_device *dev,
return 0;
}
+static bool mei_csc_pg_blocked(struct mei_device *dev)
+{
+ struct mei_me_hw *hw = to_me_hw(dev);
+ u32 reg = 0;
+
+ hw->read_fws(dev, PCI_CFG_HFS_2, "PCI_CFG_HFS_2", ®);
+ return (reg & PCI_CFG_HFS_2_D3_BLOCK) == PCI_CFG_HFS_2_D3_BLOCK;
+}
+
/**
* mei_me_hw_config - configure hw dependent settings
*
@@ -1206,6 +1215,7 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable)
return ret;
} else {
hw->pg_state = MEI_PG_OFF;
+ dev->pg_blocked = mei_csc_pg_blocked(dev);
}
}
@@ -1294,6 +1304,7 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
{
struct mei_device *dev = (struct mei_device *) dev_id;
struct list_head cmpl_list;
+ bool pg_blocked;
s32 slots;
u32 hcsr;
int rets = 0;
@@ -1351,6 +1362,14 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
}
goto end;
}
+
+ pg_blocked = mei_csc_pg_blocked(dev);
+ if (pg_blocked && !dev->pg_blocked) /* PG block requested */
+ pm_request_resume(&dev->dev);
+ else if (!pg_blocked && dev->pg_blocked) /* PG block lifted */
+ pm_request_autosuspend(&dev->dev);
+ dev->pg_blocked = pg_blocked;
+
/* check slots available for reading */
slots = mei_count_full_read_slots(dev);
while (slots > 0) {
@@ -1726,6 +1745,13 @@ static const struct mei_cfg mei_me_gscfi_cfg = {
MEI_CFG_FW_VER_SUPP,
};
+/* Chassis System Controller Firmware Interface */
+static const struct mei_cfg mei_me_csc_cfg = {
+ MEI_CFG_TYPE_GSCFI,
+ MEI_CFG_PCH8_HFS,
+ MEI_CFG_FW_VER_SUPP,
+};
+
/*
* mei_cfg_list - A list of platform platform specific configurations.
* Note: has to be synchronized with enum mei_cfg_idx.
@@ -1748,6 +1774,7 @@ static const struct mei_cfg *const mei_cfg_list[] = {
[MEI_ME_PCH15_SPS_CFG] = &mei_me_pch15_sps_cfg,
[MEI_ME_GSC_CFG] = &mei_me_gsc_cfg,
[MEI_ME_GSCFI_CFG] = &mei_me_gscfi_cfg,
+ [MEI_ME_CSC_CFG] = &mei_me_csc_cfg,
};
const struct mei_cfg *mei_me_get_cfg(kernel_ulong_t idx)
diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h
index 9df5899d2602..8da8662a9d61 100644
--- a/drivers/misc/mei/hw-me.h
+++ b/drivers/misc/mei/hw-me.h
@@ -104,6 +104,7 @@ static inline bool mei_me_hw_use_polling(const struct mei_me_hw *hw)
* SPS firmware exclusion.
* @MEI_ME_GSC_CFG: Graphics System Controller
* @MEI_ME_GSCFI_CFG: Graphics System Controller Firmware Interface
+ * @MEI_ME_CSC_CFG: Chassis System Controller Firmware Interface
* @MEI_ME_NUM_CFG: Upper Sentinel.
*/
enum mei_cfg_idx {
@@ -124,6 +125,7 @@ enum mei_cfg_idx {
MEI_ME_PCH15_SPS_CFG,
MEI_ME_GSC_CFG,
MEI_ME_GSCFI_CFG,
+ MEI_ME_CSC_CFG,
MEI_ME_NUM_CFG,
};
diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c
index f54991b40fc7..766f119f7ed0 100644
--- a/drivers/misc/mei/init.c
+++ b/drivers/misc/mei/init.c
@@ -344,13 +344,15 @@ EXPORT_SYMBOL_GPL(mei_stop);
bool mei_write_is_idle(struct mei_device *dev)
{
bool idle = (dev->dev_state == MEI_DEV_ENABLED &&
+ !dev->pg_blocked &&
list_empty(&dev->ctrl_wr_list) &&
list_empty(&dev->write_list) &&
list_empty(&dev->write_waiting_list));
- dev_dbg(&dev->dev, "write pg: is idle[%d] state=%s ctrl=%d write=%d wwait=%d\n",
+ dev_dbg(&dev->dev, "write pg: is idle[%d] state=%s blocked=%d ctrl=%d write=%d wwait=%d\n",
idle,
mei_dev_state_str(dev->dev_state),
+ dev->pg_blocked,
list_empty(&dev->ctrl_wr_list),
list_empty(&dev->write_list),
list_empty(&dev->write_waiting_list));
diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h
index 0bf8d552c3ea..1796c6793a94 100644
--- a/drivers/misc/mei/mei_dev.h
+++ b/drivers/misc/mei/mei_dev.h
@@ -490,6 +490,7 @@ struct mei_dev_timeouts {
* @timer_work : MEI timer delayed work (timeouts)
*
* @recvd_hw_ready : hw ready message received flag
+ * @pg_blocked : low power mode is not allowed
*
* @wait_hw_ready : wait queue for receive HW ready message form FW
* @wait_pg : wait queue for receive PG message from FW
@@ -575,6 +576,8 @@ struct mei_device {
struct delayed_work timer_work;
bool recvd_hw_ready;
+ bool pg_blocked;
+
/*
* waiting queue for receive message from FW
*/
diff --git a/drivers/misc/mei/pci-csc.c b/drivers/misc/mei/pci-csc.c
new file mode 100644
index 000000000000..15e170b1e0b6
--- /dev/null
+++ b/drivers/misc/mei/pci-csc.c
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025-2026, Intel Corporation. All rights reserved.
+ * Intel Management Engine Interface (Intel MEI) Linux driver
+ * for CSC platforms.
+ */
+
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+
+#include "client.h"
+#include "hw-me-regs.h"
+#include "hw-me.h"
+#include "mei_dev.h"
+#include "mei-trace.h"
+
+#define MEI_CSC_HECI2_OFFSET 0x1000
+
+static int mei_csc_read_fws(const struct mei_device *mdev, int where, const char *name, u32 *val)
+{
+ struct mei_me_hw *hw = to_me_hw(mdev);
+
+ *val = ioread32(hw->mem_addr + where + 0xC00);
+ trace_mei_reg_read(&mdev->dev, name, where, *val);
+ return 0;
+}
+
+static int mei_csc_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ struct device *dev = &pdev->dev;
+ const struct mei_cfg *cfg;
+ char __iomem *registers;
+ struct mei_device *mdev;
+ struct mei_me_hw *hw;
+ int err;
+
+ cfg = mei_me_get_cfg(ent->driver_data);
+ if (!cfg)
+ return -ENODEV;
+
+ err = pcim_enable_device(pdev);
+ if (err)
+ return dev_err_probe(dev, err, "Failed to enable PCI device.\n");
+
+ pci_set_master(pdev);
+
+ registers = pcim_iomap_region(pdev, 0, KBUILD_MODNAME);
+ if (IS_ERR(registers))
+ return dev_err_probe(dev, PTR_ERR(registers), "Failed to get PCI region.\n");
+
+ err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+ if (err)
+ return dev_err_probe(dev, err, "No usable DMA configuration.\n");
+
+ /* allocates and initializes the mei dev structure */
+ mdev = mei_me_dev_init(dev, cfg, false);
+ if (!mdev)
+ return -ENOMEM;
+
+ hw = to_me_hw(mdev);
+
+ /*
+ * Both HECI1 and HECI2 are on this device, but only HECI2 is supported.
+ */
+ hw->mem_addr = registers + MEI_CSC_HECI2_OFFSET;
+ hw->read_fws = mei_csc_read_fws;
+
+ /*
+ * mei_register() assumes ownership of mdev.
+ * No need to release it explicitly in error path.
+ */
+ err = mei_register(mdev, dev);
+ if (err)
+ return err;
+
+ pci_set_drvdata(pdev, mdev);
+
+ err = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_INTX | PCI_IRQ_MSI);
+ if (err < 0) {
+ dev_err_probe(dev, err, "Failed to allocate IRQ.\n");
+ goto err_mei_unreg;
+ }
+
+ hw->irq = pci_irq_vector(pdev, 0);
+
+ /* request and enable interrupt */
+ err = request_threaded_irq(hw->irq,
+ mei_me_irq_quick_handler, mei_me_irq_thread_handler,
+ IRQF_SHARED | IRQF_ONESHOT, KBUILD_MODNAME, mdev);
+ if (err)
+ goto err_free_irq_vectors;
+
+ /*
+ * Continue to char device setup in spite of firmware handshake failure.
+ * In order to provide access to the firmware status registers to the user
+ * space via sysfs. The firmware status registers required to understand
+ * firmware error state and possible recovery flow.
+ */
+ if (mei_start(mdev))
+ dev_warn(dev, "Failed to initialize HECI hardware.\n");
+
+ pm_runtime_set_autosuspend_delay(dev, MEI_ME_RPM_TIMEOUT);
+ pm_runtime_use_autosuspend(dev);
+
+ /*
+ * MEI requires to resume from runtime suspend mode
+ * in order to perform link reset flow upon system suspend.
+ */
+ dev_pm_set_driver_flags(dev, DPM_FLAG_NO_DIRECT_COMPLETE);
+
+ pm_runtime_allow(dev);
+ pm_runtime_put_noidle(dev);
+
+ return 0;
+
+err_free_irq_vectors:
+ pci_free_irq_vectors(pdev);
+err_mei_unreg:
+ mei_deregister(mdev);
+ return err;
+}
+
+static void mei_csc_shutdown(struct pci_dev *pdev)
+{
+ struct mei_device *mdev = pci_get_drvdata(pdev);
+ struct mei_me_hw *hw = to_me_hw(mdev);
+
+ pm_runtime_get_noresume(&pdev->dev);
+
+ mei_stop(mdev);
+
+ mei_disable_interrupts(mdev);
+ free_irq(hw->irq, mdev);
+ pci_free_irq_vectors(pdev);
+}
+
+static void mei_csc_remove(struct pci_dev *pdev)
+{
+ struct mei_device *mdev = pci_get_drvdata(pdev);
+
+ mei_csc_shutdown(pdev);
+
+ mei_deregister(mdev);
+}
+
+static int mei_csc_pci_prepare(struct device *dev)
+{
+ pm_runtime_resume(dev);
+ return 0;
+}
+
+static int mei_csc_pci_suspend(struct device *dev)
+{
+ struct mei_device *mdev = dev_get_drvdata(dev);
+
+ mei_stop(mdev);
+
+ mei_disable_interrupts(mdev);
+
+ return 0;
+}
+
+static int mei_csc_pci_resume(struct device *dev)
+{
+ struct mei_device *mdev = dev_get_drvdata(dev);
+ int err;
+
+ err = mei_restart(mdev);
+ if (err)
+ return err;
+
+ /* Start timer if stopped in suspend */
+ schedule_delayed_work(&mdev->timer_work, HZ);
+
+ return 0;
+}
+
+static void mei_csc_pci_complete(struct device *dev)
+{
+ pm_runtime_suspend(dev);
+}
+
+static int mei_csc_pm_runtime_idle(struct device *dev)
+{
+ struct mei_device *mdev = dev_get_drvdata(dev);
+
+ return mei_write_is_idle(mdev) ? 0 : -EBUSY;
+}
+
+static int mei_csc_pm_runtime_suspend(struct device *dev)
+{
+ struct mei_device *mdev = dev_get_drvdata(dev);
+ struct mei_me_hw *hw = to_me_hw(mdev);
+
+ guard(mutex)(&mdev->device_lock);
+
+ if (!mei_write_is_idle(mdev))
+ return -EAGAIN;
+
+ hw->pg_state = MEI_PG_ON;
+ return 0;
+}
+
+static int mei_csc_pm_runtime_resume(struct device *dev)
+{
+ struct mei_device *mdev = dev_get_drvdata(dev);
+ struct mei_me_hw *hw = to_me_hw(mdev);
+ irqreturn_t irq_ret;
+
+ scoped_guard(mutex, &mdev->device_lock)
+ hw->pg_state = MEI_PG_OFF;
+
+ /* Process all queues that wait for resume */
+ irq_ret = mei_me_irq_thread_handler(1, mdev);
+ if (irq_ret != IRQ_HANDLED)
+ dev_err(dev, "thread handler fail %d\n", irq_ret);
+
+ return 0;
+}
+
+static const struct dev_pm_ops mei_csc_pm_ops = {
+ .prepare = pm_sleep_ptr(mei_csc_pci_prepare),
+ .complete = pm_sleep_ptr(mei_csc_pci_complete),
+ SYSTEM_SLEEP_PM_OPS(mei_csc_pci_suspend, mei_csc_pci_resume)
+ RUNTIME_PM_OPS(mei_csc_pm_runtime_suspend,
+ mei_csc_pm_runtime_resume, mei_csc_pm_runtime_idle)
+};
+
+static const struct pci_device_id mei_csc_pci_tbl[] = {
+ { PCI_DEVICE_DATA(INTEL, MEI_CRI, MEI_ME_CSC_CFG) },
+ {}
+};
+MODULE_DEVICE_TABLE(pci, mei_csc_pci_tbl);
+
+static struct pci_driver mei_csc_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = mei_csc_pci_tbl,
+ .probe = mei_csc_probe,
+ .remove = mei_csc_remove,
+ .shutdown = mei_csc_shutdown,
+ .driver = {
+ .pm = &mei_csc_pm_ops,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ }
+};
+module_pci_driver(mei_csc_driver);
+
+MODULE_DESCRIPTION("Intel(R) Management Engine Interface for discrete graphics (CSC)");
+MODULE_LICENSE("GPL");
--
2.43.0
Powered by blists - more mailing lists