The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [RFC PATCH 0/4] espi: introduce eSPI bus framework
@ 2026-08-04 11:52 Krishnamoorthi M
  2026-08-04 11:52 ` [RFC PATCH 1/4] espi: add core " Krishnamoorthi M
                   ` (4 more replies)
  0 siblings, 5 replies; 13+ messages in thread
From: Krishnamoorthi M @ 2026-08-04 11:52 UTC (permalink / raw)
  To: linux-kernel
  Cc: gregkh, broonie, linux-spi, akshata.mukundshetty, bleung, groeck,
	chrome-platform, corbet, linux-doc, skhan, andrew, linux-aspeed,
	openbmc, Krishnamoorthi M

This RFC proposes a new eSPI (Enhanced Serial Peripheral Interface) bus
subsystem for Linux.

Background
==========

I previously posted to the list asking whether extending the existing SPI
subsystem or introducing a new bus type was the preferred direction for eSPI
support [1]. This series represents the new-bus-type approach, implemented
and validated on AMD hardware.

eSPI is an Intel-defined protocol replacing the legacy LPC bus. Unlike SPI,
eSPI is capability-negotiated: the controller and target exchange capability
registers at link bring-up to agree on I/O mode, clock frequency and CRC.
Traffic is carried over four logically independent channels on a single
shared physical link, and the target signals upstream data availability
asynchronously via an ALERT# pin rather than chip-select assertion.
These characteristics do not fit the synchronous, single-channel, transfer-
oriented SPI model, so a dedicated bus type is proposed.

Channel Model
=============

The four eSPI channels each serve a distinct purpose:

  - Peripheral channel: carries host I/O and memory cycles to/from the
    target, replacing the LPC I/O and memory cycles used by devices such
    as EC and BMC firmware.

  - Virtual Wire channel: transfers logical signal state (power sequencing
    signals, SMI#, SCI#, IRQs) as indexed wire groups, replacing the
    physical LPC sideband signals.

  - OOB channel: tunnels SMBus/I2C messages between the host and an
    out-of-band processor on the target, enabling management traffic
    independent of the host OS.

  - Flash Access channel: provides access to a SPI flash device attached
    to the target, allowing the host to share a single flash with the
    target firmware.

Design Overview
===============

The framework follows the established Linux bus/device/driver model:

  - struct espi_controller: the host controller, registered with
    espi_controller_register(). Capabilities are negotiated at runtime
    and stored in struct espi_capabilities. Each controller is assigned
    a bus number from an XArray allocator. Read-only sysfs attributes
    (supported_channels, channel_enabled, io_mode, max_freq_mhz) expose
    the negotiated link state to userspace.

  - struct espi_device: a target on the bus, identified by its Chip
    Select# index (cs field). The eSPI spec allows one controller to
    drive multiple targets via separate CS# pins; ctrl->max_targets
    advertises the hardware limit and cs is range-checked at device
    creation. Devices are matched to drivers by modalias.

  - struct espi_controller_ops: an all-optional hardware callback table.
    The core returns -EOPNOTSUPP for unimplemented ops, allowing
    incremental controller driver development across patch series.

  - A per-controller blocking notifier chain delivers hardware events
    (Virtual Wire changes, OOB messages, channel state transitions,
    In-Band Reset) to slave drivers from process context. A blocking
    notifier is used rather than a raw notifier because slave driver
    callbacks may sleep, for example to issue follow-up configuration
    commands over the bus.

  - A single per-controller mutex serialises all channel operations.
    This is intentional: eSPI has one shared physical link and only one
    downstream transaction can be in flight at a time regardless of the
    logical channel, consistent with how struct spi_controller is modelled.
    Because the mutex is a sleeping lock, all ops must be called from
    process context; the alert handler is therefore registered with
    IRQF_ONESHOT and dispatches from a threaded IRQ.

Alert Mechanism
===============

When the target has upstream data pending it asserts ALERT# (dedicated
pin or in-band on I/O[1]). The controller's hard-IRQ handler acknowledges
the interrupt and defers processing to a threaded IRQ, which calls
espi_handle_alert(). This dispatches to ops->handle_alert WITHOUT holding
ctrl->lock, so that the driver callback can call espi_notify_event() to
deliver the appropriate ESPI_EVENT_* to registered slave driver notifiers
without deadlocking: notifier callbacks may in turn call channel APIs
that also acquire ctrl->lock. The driver is responsible for acquiring
ctrl->lock around any register accesses that require serialisation with
the channel API. The alert path implementation is deferred to follow-on
patches; the hook points are in place in this series.

Scope of this RFC
=================

This series covers the framework foundation and the AMD FCH controller
driver (ACPI HID: AMDI0070). It intentionally limits scope to the
channel-independent layer: capability discovery, GET/SET_CONFIGURATION
and In-Band Reset. Channel-specific operations (Peripheral I/O and
memory, Virtual Wire, OOB, Flash Access) and the alert/interrupt path
are declared in the API but their implementations are deferred to
follow-on patches, to be posted once the framework design is reviewed.

Testing
=======

The series has been validated on AMD hardware (AMD FCH, AMDI0070) using
an internal test slave driver that binds as an eSPI slave device and
exposes a sysfs command interface. The following scenarios were
exercised:

  - GET_CONFIGURATION on the General Capabilities register (0x08):
    verified correct decoding of I/O mode, operating frequency, CRC,
    Alert mode, Max WAIT STATE, and supported channels.

  - SET_CONFIGURATION on the General Capabilities register: verified
    successful negotiation of I/O mode (single/dual/quad) and operating
    frequency (16/33/66 MHz), and confirmed the host-side register is
    updated to match the negotiated parameters.

  - In-Band Reset: verified the reset completes successfully and the
    host controller registers are restored to match the target's
    post-reset state (16 MHz / single I/O).

Known Limitations / Future Work
================================

  - No Device Tree bindings in this series. The AMD controller uses
    ACPI enumeration. DT support will follow.

  - Slave device enumeration is manual (espi_new_device). ACPI/DT-based
    enumeration will be added in a subsequent patch.

  - Channel ops (Peripheral, VWire, OOB, Flash) and alert/interrupt
    handling are deferred to follow-on patches.

Feedback Requested
==================

  1. We chose a dedicated bus_type for the reasons described above
     (capability negotiation, four independent channels, asynchronous
     ALERT#). Does the community agree this is the right direction, or
     is there a strong preference to extend the SPI subsystem instead?
  2. Is the blocking notifier chain the right mechanism for event
     delivery to slave drivers?
  3. Any concerns with the ops table design or the -EOPNOTSUPP fallback?
  4. Naming and structure of the public API in include/linux/espi/espi.h.

References
==========

[1] https://lore.kernel.org/lkml/9548669c-7d3c-4053-b28b-c82490c0c2b8@amd.com/T/#u
[2] Intel Enhanced Serial Peripheral Interface (eSPI) Interface Base
    Specification

Krishnamoorthi M (4):
  espi: add core bus framework
  espi: add slave device model and event notification
  Documentation: espi: add subsystem overview and MAINTAINERS entry
  espi: amd: add AMD eSPI controller driver

 Documentation/driver-api/espi.rst  | 213 +++++++++++++
 Documentation/driver-api/index.rst |   1 +
 MAINTAINERS                        |   8 +
 drivers/Kconfig                    |   2 +
 drivers/Makefile                   |   1 +
 drivers/espi/Kconfig               |  41 +++
 drivers/espi/Makefile              |   3 +
 drivers/espi/espi-amd.c            | 453 ++++++++++++++++++++++++++
 drivers/espi/espi-amd.h            | 126 ++++++++
 drivers/espi/espi-core.c           | 493 +++++++++++++++++++++++++++++
 drivers/espi/espi-slave.c          | 177 +++++++++++
 include/linux/espi/espi.h          | 345 ++++++++++++++++++++
 12 files changed, 1863 insertions(+)
 create mode 100644 Documentation/driver-api/espi.rst
 create mode 100644 drivers/espi/Kconfig
 create mode 100644 drivers/espi/Makefile
 create mode 100644 drivers/espi/espi-amd.c
 create mode 100644 drivers/espi/espi-amd.h
 create mode 100644 drivers/espi/espi-core.c
 create mode 100644 drivers/espi/espi-slave.c
 create mode 100644 include/linux/espi/espi.h

-- 
2.34.1


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [RFC PATCH 1/4] espi: add core bus framework
  2026-08-04 11:52 [RFC PATCH 0/4] espi: introduce eSPI bus framework Krishnamoorthi M
@ 2026-08-04 11:52 ` Krishnamoorthi M
  2026-08-06 13:31   ` Uwe Kleine-König
  2026-08-04 11:52 ` [RFC PATCH 2/4] espi: add slave device model and event notification Krishnamoorthi M
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 13+ messages in thread
From: Krishnamoorthi M @ 2026-08-04 11:52 UTC (permalink / raw)
  To: linux-kernel
  Cc: gregkh, broonie, linux-spi, akshata.mukundshetty, bleung, groeck,
	chrome-platform, corbet, linux-doc, skhan, andrew, linux-aspeed,
	openbmc, Krishnamoorthi M

Add the core of a new Enhanced Serial Peripheral Interface (eSPI) bus
subsystem under drivers/espi/.

eSPI is a capability-negotiated, message-oriented link: the controller
and slave negotiate I/O mode, clock and channel support at runtime and
exchange packetised transactions over four logically independent channels
over a shared physical link (peripheral, virtual wire, out-of-band and
flash). This does not fit the synchronous, transfer-oriented SPI model,
so eSPI is modelled as its own bus type rather than an extension of SPI.

The core provides:
  - espi_bus_type, binding devices to drivers by ID table or ACPI match
    and emitting a MODALIAS uevent;
  - controller lifecycle helpers, with each controller registered as a
    device and assigned a bus number from an allocating XArray;
  - read-only sysfs attributes for the controller capabilities;
  - a channel API that serialises access with ctrl->lock, dispatches to
    the controller ops, and returns -EOPNOTSUPP for unimplemented ops.

Co-developed-by: Akshata MukundShetty <akshata.mukundshetty@amd.com>
Signed-off-by: Akshata MukundShetty <akshata.mukundshetty@amd.com>
Signed-off-by: Krishnamoorthi M <krishnamoorthi.m@amd.com>
---
 drivers/Kconfig           |   2 +
 drivers/Makefile          |   1 +
 drivers/espi/Kconfig      |  19 ++
 drivers/espi/Makefile     |   2 +
 drivers/espi/espi-core.c  | 493 ++++++++++++++++++++++++++++++++++++++
 include/linux/espi/espi.h | 345 ++++++++++++++++++++++++++
 6 files changed, 862 insertions(+)
 create mode 100644 drivers/espi/Kconfig
 create mode 100644 drivers/espi/Makefile
 create mode 100644 drivers/espi/espi-core.c
 create mode 100644 include/linux/espi/espi.h

diff --git a/drivers/Kconfig b/drivers/Kconfig
index f2bed2ddeb66..d7661432ce7c 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -73,6 +73,8 @@ source "drivers/i3c/Kconfig"
 
 source "drivers/spi/Kconfig"
 
+source "drivers/espi/Kconfig"
+
 source "drivers/spmi/Kconfig"
 
 source "drivers/hsi/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 0841ea851847..ff3d42dd14ba 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -86,6 +86,7 @@ obj-$(CONFIG_ATA)		+= ata/
 obj-$(CONFIG_TARGET_CORE)	+= target/
 obj-$(CONFIG_MTD)		+= mtd/
 obj-$(CONFIG_SPI)		+= spi/
+obj-$(CONFIG_ESPI)		+= espi/
 obj-$(CONFIG_SPMI)		+= spmi/
 obj-$(CONFIG_HSI)		+= hsi/
 obj-$(CONFIG_SLIMBUS)		+= slimbus/
diff --git a/drivers/espi/Kconfig b/drivers/espi/Kconfig
new file mode 100644
index 000000000000..4411d8336e82
--- /dev/null
+++ b/drivers/espi/Kconfig
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# eSPI (Enhanced Serial Peripheral Interface) bus configuration
+#
+
+menuconfig ESPI
+	bool "eSPI (Enhanced Serial Peripheral Interface) bus support"
+	help
+	  Enhanced Serial Peripheral Interface (eSPI) bus framework.
+
+	  eSPI is a serial bus specification by Intel that replaces the
+	  legacy Low Pin Count (LPC) bus. It connects a host controller to
+	  one or more slave devices (for example an EC or BMC) over a
+	  low-pin-count link clocked up to 66 MHz with single, dual or quad
+	  I/O, and carries traffic on four logically independent channels
+	  over a shared physical link: Peripheral (I/O and memory), Virtual
+	  Wire, OOB (Out-of-Band) messaging, and Flash Access.
+
+	  If unsure, say N.
diff --git a/drivers/espi/Makefile b/drivers/espi/Makefile
new file mode 100644
index 000000000000..72712fcb0ada
--- /dev/null
+++ b/drivers/espi/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+obj-$(CONFIG_ESPI)		+= espi-core.o
diff --git a/drivers/espi/espi-core.c b/drivers/espi/espi-core.c
new file mode 100644
index 000000000000..35481f1c1858
--- /dev/null
+++ b/drivers/espi/espi-core.c
@@ -0,0 +1,493 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * eSPI (Enhanced Serial Peripheral Interface) core framework
+ *
+ * Copyright (c) 2026, Advanced Micro Devices, Inc.
+ */
+
+#define pr_fmt(fmt)	"espi: " fmt
+
+#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/xarray.h>
+#include <linux/espi/espi.h>
+
+static DEFINE_XARRAY_ALLOC(espi_controllers);
+
+static int espi_bus_match(struct device *dev, const struct device_driver *drv)
+{
+	const struct espi_device *edev = to_espi_device(dev);
+	const struct espi_driver *edrv = to_espi_driver(drv);
+
+	if (edrv->id_table) {
+		const struct espi_device_id *id = edrv->id_table;
+
+		while (id->name[0]) {
+			if (!strcmp(edev->modalias, id->name))
+				return 1;
+			id++;
+		}
+	}
+	if (acpi_driver_match_device(dev, drv))
+		return 1;
+
+	return 0;
+}
+
+static int espi_bus_uevent(const struct device *dev, struct kobj_uevent_env *env)
+{
+	const struct espi_device *edev = to_espi_device(dev);
+
+	return add_uevent_var(env, "MODALIAS=espi:%s", edev->modalias);
+}
+
+static int espi_bus_probe(struct device *dev)
+{
+	const struct espi_driver *edrv = to_espi_driver(dev->driver);
+	struct espi_device *edev = to_espi_device(dev);
+
+	if (edrv->probe)
+		return edrv->probe(edev);
+	return 0;
+}
+
+static void espi_bus_remove(struct device *dev)
+{
+	const struct espi_driver *edrv = to_espi_driver(dev->driver);
+	struct espi_device *edev = to_espi_device(dev);
+
+	if (edrv->remove)
+		edrv->remove(edev);
+}
+
+const struct bus_type espi_bus_type = {
+	.name	= "espi",
+	.match	= espi_bus_match,
+	.uevent	= espi_bus_uevent,
+	.probe	= espi_bus_probe,
+	.remove	= espi_bus_remove,
+};
+EXPORT_SYMBOL_GPL(espi_bus_type);
+
+static ssize_t supported_channels_show(struct device *dev,
+				       struct device_attribute *attr, char *buf)
+{
+	struct espi_controller *ctrl = to_espi_controller(dev);
+
+	return sysfs_emit(buf, "0x%02x\n", ctrl->caps.supported_channels);
+}
+static DEVICE_ATTR_RO(supported_channels);
+
+static ssize_t max_freq_mhz_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct espi_controller *ctrl = to_espi_controller(dev);
+
+	return sysfs_emit(buf, "%u\n", ctrl->caps.max_freq_mhz);
+}
+static DEVICE_ATTR_RO(max_freq_mhz);
+
+static ssize_t io_mode_show(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	struct espi_controller *ctrl = to_espi_controller(dev);
+	static const char * const modes[] = { "single", "dual", "quad" };
+	u8 m = ctrl->caps.io_mode;
+
+	if (WARN_ON_ONCE(m > ESPI_IO_MODE_QUAD))
+		return sysfs_emit(buf, "unknown\n");
+	return sysfs_emit(buf, "%s\n", modes[m]);
+}
+static DEVICE_ATTR_RO(io_mode);
+
+static ssize_t channel_enabled_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	struct espi_controller *ctrl = to_espi_controller(dev);
+
+	return sysfs_emit(buf, "0x%02x\n", READ_ONCE(ctrl->channel_enabled));
+}
+static DEVICE_ATTR_RO(channel_enabled);
+
+static struct attribute *espi_controller_attrs[] = {
+	&dev_attr_supported_channels.attr,
+	&dev_attr_max_freq_mhz.attr,
+	&dev_attr_io_mode.attr,
+	&dev_attr_channel_enabled.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(espi_controller);
+
+static void espi_controller_release(struct device *dev)
+{
+	struct espi_controller *ctrl = to_espi_controller(dev);
+
+	mutex_destroy(&ctrl->lock);
+	mutex_destroy(&ctrl->device_list_lock);
+	kfree(ctrl);
+}
+
+static const struct device_type espi_controller_type = {
+	.groups	 = espi_controller_groups,
+	.release = espi_controller_release,
+};
+
+struct espi_controller *espi_controller_alloc(struct device *parent,
+					      unsigned int size)
+{
+	struct espi_controller *ctrl;
+
+	if (!parent)
+		return ERR_PTR(-EINVAL);
+
+	ctrl = kzalloc(sizeof(*ctrl) + size, GFP_KERNEL);
+	if (!ctrl)
+		return ERR_PTR(-ENOMEM);
+
+	device_initialize(&ctrl->dev);
+	ctrl->dev.parent = parent;
+	ctrl->dev.type = &espi_controller_type;
+
+	mutex_init(&ctrl->lock);
+	INIT_LIST_HEAD(&ctrl->device_list);
+	mutex_init(&ctrl->device_list_lock);
+	BLOCKING_INIT_NOTIFIER_HEAD(&ctrl->notifier_list);
+
+	if (size)
+		espi_controller_set_devdata(ctrl, (void *)ctrl + sizeof(*ctrl));
+
+	return ctrl;
+}
+EXPORT_SYMBOL_GPL(espi_controller_alloc);
+
+int espi_controller_register(struct espi_controller *ctrl)
+{
+	int ret;
+	u32 id;
+
+	if (!ctrl || !ctrl->ops)
+		return -EINVAL;
+
+	ret = xa_alloc(&espi_controllers, &id, ctrl, xa_limit_31b,
+		       GFP_KERNEL);
+	if (ret)
+		return ret;
+
+	ctrl->bus_num = id;
+	ret = dev_set_name(&ctrl->dev, "espi%d", ctrl->bus_num);
+	if (ret)
+		goto err_erase;
+
+	if (ctrl->ops->setup) {
+		ret = ctrl->ops->setup(ctrl);
+		if (ret) {
+			dev_err(&ctrl->dev, "controller setup failed: %d\n", ret);
+			goto err_erase;
+		}
+	}
+
+	ret = device_add(&ctrl->dev);
+	if (ret) {
+		dev_err(&ctrl->dev, "device_add failed: %d\n", ret);
+		if (ctrl->ops->cleanup)
+			ctrl->ops->cleanup(ctrl);
+		goto err_erase;
+	}
+
+	dev_info(&ctrl->dev, "registered: channels=0x%02x freq=%uMHz\n",
+		 ctrl->caps.supported_channels, ctrl->caps.max_freq_mhz);
+	return 0;
+
+err_erase:
+	xa_erase(&espi_controllers, ctrl->bus_num);
+	ctrl->bus_num = -1;
+	return ret;
+}
+EXPORT_SYMBOL_GPL(espi_controller_register);
+
+void espi_controller_unregister(struct espi_controller *ctrl)
+{
+	if (!ctrl)
+		return;
+	/*
+	 * Remove from the lookup table before dropping the device reference,
+	 * so a concurrent espi_controller_get_by_bus_num() can never take a
+	 * reference on a controller that is going away.
+	 */
+	xa_erase(&espi_controllers, ctrl->bus_num);
+	if (ctrl->ops && ctrl->ops->cleanup)
+		ctrl->ops->cleanup(ctrl);
+	device_unregister(&ctrl->dev);
+}
+EXPORT_SYMBOL_GPL(espi_controller_unregister);
+
+void espi_controller_put(struct espi_controller *ctrl)
+{
+	if (ctrl)
+		put_device(&ctrl->dev);
+}
+EXPORT_SYMBOL_GPL(espi_controller_put);
+
+struct espi_controller *espi_controller_get_by_bus_num(int bus_num)
+{
+	struct espi_controller *ctrl;
+
+	guard(spinlock)(&espi_controllers.xa_lock);
+	ctrl = xa_load(&espi_controllers, bus_num);
+	if (ctrl)
+		get_device(&ctrl->dev);
+	return ctrl;
+}
+EXPORT_SYMBOL_GPL(espi_controller_get_by_bus_num);
+
+int espi_get_capabilities(struct espi_controller *ctrl,
+			  struct espi_capabilities *caps)
+{
+	if (!ctrl || !caps)
+		return -EINVAL;
+	guard(mutex)(&ctrl->lock);
+	*caps = ctrl->caps;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(espi_get_capabilities);
+
+bool espi_channel_is_enabled(struct espi_controller *ctrl, u8 channel)
+{
+	if (!ctrl || channel >= ESPI_CHANNEL_COUNT)
+		return false;
+	guard(mutex)(&ctrl->lock);
+	return !!(ctrl->channel_enabled & BIT(channel));
+}
+EXPORT_SYMBOL_GPL(espi_channel_is_enabled);
+
+int espi_get_configuration(struct espi_controller *ctrl,
+			   u32 slave_reg_addr, u32 *config)
+{
+	if (!ctrl || !ctrl->ops || !ctrl->ops->get_configuration)
+		return -EOPNOTSUPP;
+	if (!config)
+		return -EINVAL;
+	guard(mutex)(&ctrl->lock);
+	return ctrl->ops->get_configuration(ctrl, slave_reg_addr, config);
+}
+EXPORT_SYMBOL_GPL(espi_get_configuration);
+
+int espi_set_configuration(struct espi_controller *ctrl,
+			   u32 slave_reg_addr, u32 config)
+{
+	if (!ctrl || !ctrl->ops || !ctrl->ops->set_configuration)
+		return -EOPNOTSUPP;
+	guard(mutex)(&ctrl->lock);
+	return ctrl->ops->set_configuration(ctrl, slave_reg_addr, config);
+}
+EXPORT_SYMBOL_GPL(espi_set_configuration);
+
+int espi_inband_reset(struct espi_controller *ctrl)
+{
+	if (!ctrl || !ctrl->ops || !ctrl->ops->inband_reset)
+		return -EOPNOTSUPP;
+	guard(mutex)(&ctrl->lock);
+	return ctrl->ops->inband_reset(ctrl);
+}
+EXPORT_SYMBOL_GPL(espi_inband_reset);
+
+int espi_get_status(struct espi_controller *ctrl,
+		    struct espi_slave_status *status)
+{
+	if (!ctrl || !ctrl->ops || !ctrl->ops->get_status)
+		return -EOPNOTSUPP;
+	if (!status)
+		return -EINVAL;
+	guard(mutex)(&ctrl->lock);
+	return ctrl->ops->get_status(ctrl, status);
+}
+EXPORT_SYMBOL_GPL(espi_get_status);
+
+int espi_enable_channel(struct espi_controller *ctrl, u8 channel)
+{
+	int ret;
+
+	if (!ctrl || !ctrl->ops || !ctrl->ops->enable_channel)
+		return -EOPNOTSUPP;
+	if (channel >= ESPI_CHANNEL_COUNT)
+		return -EINVAL;
+	guard(mutex)(&ctrl->lock);
+	ret = ctrl->ops->enable_channel(ctrl, channel);
+	if (!ret)
+		ctrl->channel_enabled |= BIT(channel);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(espi_enable_channel);
+
+int espi_disable_channel(struct espi_controller *ctrl, u8 channel)
+{
+	int ret;
+
+	if (!ctrl || !ctrl->ops || !ctrl->ops->disable_channel)
+		return -EOPNOTSUPP;
+	if (channel >= ESPI_CHANNEL_COUNT)
+		return -EINVAL;
+	guard(mutex)(&ctrl->lock);
+	ret = ctrl->ops->disable_channel(ctrl, channel);
+	if (!ret)
+		ctrl->channel_enabled &= ~BIT(channel);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(espi_disable_channel);
+
+int espi_periph_io_read(struct espi_controller *ctrl,
+			u16 port, u8 width, u32 *value)
+{
+	if (!ctrl || !ctrl->ops || !ctrl->ops->periph_io_read)
+		return -EOPNOTSUPP;
+	guard(mutex)(&ctrl->lock);
+	return ctrl->ops->periph_io_read(ctrl, port, width, value);
+}
+EXPORT_SYMBOL_GPL(espi_periph_io_read);
+
+int espi_periph_io_write(struct espi_controller *ctrl,
+			 u16 port, u8 width, u32 value)
+{
+	if (!ctrl || !ctrl->ops || !ctrl->ops->periph_io_write)
+		return -EOPNOTSUPP;
+	guard(mutex)(&ctrl->lock);
+	return ctrl->ops->periph_io_write(ctrl, port, width, value);
+}
+EXPORT_SYMBOL_GPL(espi_periph_io_write);
+
+int espi_periph_mem_read(struct espi_controller *ctrl,
+			 u32 addr, void *buf, size_t len)
+{
+	if (!ctrl || !ctrl->ops || !ctrl->ops->periph_mem_read)
+		return -EOPNOTSUPP;
+	guard(mutex)(&ctrl->lock);
+	return ctrl->ops->periph_mem_read(ctrl, addr, buf, len);
+}
+EXPORT_SYMBOL_GPL(espi_periph_mem_read);
+
+int espi_periph_mem_write(struct espi_controller *ctrl,
+			  u32 addr, const void *buf, size_t len)
+{
+	if (!ctrl || !ctrl->ops || !ctrl->ops->periph_mem_write)
+		return -EOPNOTSUPP;
+	guard(mutex)(&ctrl->lock);
+	return ctrl->ops->periph_mem_write(ctrl, addr, buf, len);
+}
+EXPORT_SYMBOL_GPL(espi_periph_mem_write);
+
+int espi_vwire_get(struct espi_controller *ctrl, u8 index, u8 *value, u8 *valid)
+{
+	if (!ctrl || !ctrl->ops || !ctrl->ops->vwire_get)
+		return -EOPNOTSUPP;
+	guard(mutex)(&ctrl->lock);
+	return ctrl->ops->vwire_get(ctrl, index, value, valid);
+}
+EXPORT_SYMBOL_GPL(espi_vwire_get);
+
+int espi_vwire_put(struct espi_controller *ctrl, u8 index, u8 value, u8 valid)
+{
+	if (!ctrl || !ctrl->ops || !ctrl->ops->vwire_put)
+		return -EOPNOTSUPP;
+	guard(mutex)(&ctrl->lock);
+	return ctrl->ops->vwire_put(ctrl, index, value, valid);
+}
+EXPORT_SYMBOL_GPL(espi_vwire_put);
+
+int espi_oob_send(struct espi_controller *ctrl, const void *buf, size_t len, u8 tag)
+{
+	if (!ctrl || !ctrl->ops || !ctrl->ops->oob_send)
+		return -EOPNOTSUPP;
+	guard(mutex)(&ctrl->lock);
+	return ctrl->ops->oob_send(ctrl, buf, len, tag);
+}
+EXPORT_SYMBOL_GPL(espi_oob_send);
+
+int espi_oob_recv(struct espi_controller *ctrl, void *buf, size_t *len, u8 *tag)
+{
+	if (!ctrl || !ctrl->ops || !ctrl->ops->oob_recv)
+		return -EOPNOTSUPP;
+	guard(mutex)(&ctrl->lock);
+	return ctrl->ops->oob_recv(ctrl, buf, len, tag);
+}
+EXPORT_SYMBOL_GPL(espi_oob_recv);
+
+int espi_flash_read(struct espi_controller *ctrl, u32 offset, void *buf, size_t len)
+{
+	if (!ctrl || !ctrl->ops || !ctrl->ops->flash_read)
+		return -EOPNOTSUPP;
+	guard(mutex)(&ctrl->lock);
+	return ctrl->ops->flash_read(ctrl, offset, buf, len);
+}
+EXPORT_SYMBOL_GPL(espi_flash_read);
+
+int espi_flash_write(struct espi_controller *ctrl, u32 offset, const void *buf, size_t len)
+{
+	if (!ctrl || !ctrl->ops || !ctrl->ops->flash_write)
+		return -EOPNOTSUPP;
+	guard(mutex)(&ctrl->lock);
+	return ctrl->ops->flash_write(ctrl, offset, buf, len);
+}
+EXPORT_SYMBOL_GPL(espi_flash_write);
+
+int espi_flash_erase(struct espi_controller *ctrl, u32 offset, size_t len)
+{
+	if (!ctrl || !ctrl->ops || !ctrl->ops->flash_erase)
+		return -EOPNOTSUPP;
+	guard(mutex)(&ctrl->lock);
+	return ctrl->ops->flash_erase(ctrl, offset, len);
+}
+EXPORT_SYMBOL_GPL(espi_flash_erase);
+
+/*
+ * espi_handle_alert - dispatch a hardware alert to the controller
+ *
+ * Must be called from process context (threaded IRQ or workqueue).
+ *
+ * ctrl->lock is NOT held across ops->handle_alert so that the driver
+ * callback can call espi_notify_event() without deadlocking: notifier
+ * callbacks may in turn call channel APIs that also acquire ctrl->lock.
+ * The driver is responsible for taking ctrl->lock around any register
+ * accesses that need serialisation with the channel API.
+ */
+int espi_handle_alert(struct espi_controller *ctrl)
+{
+	if (!ctrl || !ctrl->ops || !ctrl->ops->handle_alert)
+		return -EOPNOTSUPP;
+	return ctrl->ops->handle_alert(ctrl);
+}
+EXPORT_SYMBOL_GPL(espi_handle_alert);
+
+int __espi_register_driver(struct module *owner, struct espi_driver *drv)
+{
+	drv->driver.owner = owner;
+	drv->driver.bus = &espi_bus_type;
+	return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(__espi_register_driver);
+
+void espi_unregister_driver(struct espi_driver *drv)
+{
+	driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(espi_unregister_driver);
+
+static int __init espi_init(void)
+{
+	int ret = bus_register(&espi_bus_type);
+
+	if (ret)
+		pr_err("failed to register eSPI bus: %d\n", ret);
+	return ret;
+}
+postcore_initcall(espi_init);
+
+MODULE_AUTHOR("Krishnamoorthi M <krishnamoorthi.m@amd.com>");
+MODULE_DESCRIPTION("eSPI core framework");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/espi/espi.h b/include/linux/espi/espi.h
new file mode 100644
index 000000000000..a191ddc10cdd
--- /dev/null
+++ b/include/linux/espi/espi.h
@@ -0,0 +1,345 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * eSPI (Enhanced Serial Peripheral Interface) framework
+ *
+ * Copyright (c) 2026, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ */
+#ifndef _LINUX_ESPI_ESPI_H
+#define _LINUX_ESPI_ESPI_H
+
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/mod_devicetable.h>
+#include <linux/notifier.h>
+
+struct espi_controller;
+struct espi_device;
+struct espi_driver;
+struct espi_board_info;
+
+#define ESPI_NAME_SIZE			32
+
+#define ESPI_CHANNEL_PERIPH		0
+#define ESPI_CHANNEL_VWIRE		1
+#define ESPI_CHANNEL_OOB		2
+#define ESPI_CHANNEL_FLASH		3
+#define ESPI_CHANNEL_COUNT		4
+
+#define ESPI_CHANNEL_PERIPH_SUPP	BIT(ESPI_CHANNEL_PERIPH)
+#define ESPI_CHANNEL_VWIRE_SUPP		BIT(ESPI_CHANNEL_VWIRE)
+#define ESPI_CHANNEL_OOB_SUPP		BIT(ESPI_CHANNEL_OOB)
+#define ESPI_CHANNEL_FLASH_SUPP		BIT(ESPI_CHANNEL_FLASH)
+#define ESPI_CHANNEL_ALL		(ESPI_CHANNEL_PERIPH_SUPP | \
+					 ESPI_CHANNEL_VWIRE_SUPP | \
+					 ESPI_CHANNEL_OOB_SUPP | \
+					 ESPI_CHANNEL_FLASH_SUPP)
+
+#define ESPI_IO_MODE_SINGLE		0
+#define ESPI_IO_MODE_DUAL		1
+#define ESPI_IO_MODE_QUAD		2
+
+#define ESPI_FREQ_16MHZ			16
+#define ESPI_FREQ_33MHZ			33
+#define ESPI_FREQ_66MHZ			66
+
+enum espi_cmd_type {
+	ESPI_CMD_SET_CONFIGURATION	= 0,
+	ESPI_CMD_GET_CONFIGURATION	= 1,
+	ESPI_CMD_IN_BAND_RESET		= 2,
+	ESPI_CMD_GET_STATUS		= 3,
+	ESPI_CMD_PERIPHERAL		= 4,
+	ESPI_CMD_VWIRE			= 5,
+	ESPI_CMD_OOB			= 6,
+	ESPI_CMD_FLASH			= 7,
+};
+
+#define ESPI_SLAVE_REG_DEVICE_ID	0x0004
+#define ESPI_SLAVE_REG_GENERAL_CFG	0x0008
+
+/* General Configuration register (0x08) field definitions. */
+#define ESPI_GENCFG_OP_FREQ		GENMASK(22, 20)
+#define ESPI_GENCFG_IO_MODE		GENMASK(27, 26)
+#define ESPI_GENCFG_ALERT_MODE		BIT(28)
+#define ESPI_GENCFG_CRC_EN		BIT(31)
+/* ESPI_GENCFG_OP_FREQ values */
+#define ESPI_GENCFG_FREQ_20MHZ		0x0
+#define ESPI_GENCFG_FREQ_33MHZ		0x2
+#define ESPI_GENCFG_FREQ_66MHZ		0x4
+
+/* Channel-specific capability/configuration register addresses */
+#define ESPI_SLAVE_REG_PERIPH_CFG	0x0010
+#define ESPI_SLAVE_REG_VWIRE_CFG	0x0020
+#define ESPI_SLAVE_REG_OOB_CFG		0x0030
+#define ESPI_SLAVE_REG_FLASH_CFG	0x0040
+
+struct espi_slave_status {
+	bool pc_free;
+	bool np_free;
+	bool vwire_free;
+	bool oob_free;
+	bool flash_free;
+	u16 raw;
+};
+
+struct espi_device_id {
+	char name[ESPI_NAME_SIZE];
+	kernel_ulong_t driver_data;
+};
+
+enum espi_event_type {
+	ESPI_EVENT_CHANNEL_READY	= 0,
+	ESPI_EVENT_CHANNEL_RESET	= 1,
+	ESPI_EVENT_VWIRE_CHANGED	= 2,
+	ESPI_EVENT_OOB_RECEIVED		= 3,
+	ESPI_EVENT_PERIPH_POSTED	= 4,
+	ESPI_EVENT_ALERT		= 5,
+	ESPI_EVENT_RESET		= 6,
+};
+
+struct espi_event {
+	enum espi_event_type type;
+	int channel;
+	u32 details;
+	struct espi_controller *ctrl;
+};
+
+struct espi_board_info {
+	char type[ESPI_NAME_SIZE];
+	u8 cs;
+	void *platform_data;
+	struct fwnode_handle *fwnode;
+};
+
+struct espi_capabilities {
+	u32 supported_channels;
+	u32 max_freq_mhz;
+	u8 io_mode;
+	bool alert_mode;
+	bool crc_supported;
+	u16 periph_max_payload;
+	u8 vwire_max_count;
+	u16 oob_max_payload;
+	u16 flash_max_payload;
+};
+
+/**
+ * struct espi_controller_ops - hardware operation callbacks
+ *
+ * All callbacks are optional and return 0 or a negative errno. The core
+ * returns -EOPNOTSUPP for operations a controller does not provide.
+ */
+struct espi_controller_ops {
+	int (*setup)(struct espi_controller *ctrl);
+	void (*cleanup)(struct espi_controller *ctrl);
+
+	int (*get_configuration)(struct espi_controller *ctrl,
+				 u32 slave_reg_addr, u32 *config);
+	int (*set_configuration)(struct espi_controller *ctrl,
+				 u32 slave_reg_addr, u32 config);
+	int (*inband_reset)(struct espi_controller *ctrl);
+	int (*get_status)(struct espi_controller *ctrl,
+			  struct espi_slave_status *status);
+
+	int (*enable_channel)(struct espi_controller *ctrl, u8 channel);
+	int (*disable_channel)(struct espi_controller *ctrl, u8 channel);
+
+	int (*periph_io_read)(struct espi_controller *ctrl,
+			      u16 port, u8 width, u32 *value);
+	int (*periph_io_write)(struct espi_controller *ctrl,
+			       u16 port, u8 width, u32 value);
+	int (*periph_mem_read)(struct espi_controller *ctrl,
+			       u32 addr, void *buf, size_t len);
+	int (*periph_mem_write)(struct espi_controller *ctrl,
+				u32 addr, const void *buf, size_t len);
+
+	int (*vwire_get)(struct espi_controller *ctrl,
+			 u8 index, u8 *value, u8 *valid);
+	int (*vwire_put)(struct espi_controller *ctrl,
+			 u8 index, u8 value, u8 valid);
+
+	int (*oob_send)(struct espi_controller *ctrl,
+			const void *buf, size_t len, u8 tag);
+	int (*oob_recv)(struct espi_controller *ctrl,
+			void *buf, size_t *len, u8 *tag);
+
+	int (*flash_read)(struct espi_controller *ctrl,
+			  u32 offset, void *buf, size_t len);
+	int (*flash_write)(struct espi_controller *ctrl,
+			   u32 offset, const void *buf, size_t len);
+	int (*flash_erase)(struct espi_controller *ctrl,
+			   u32 offset, size_t len);
+
+	int (*handle_alert)(struct espi_controller *ctrl);
+};
+
+struct espi_controller {
+	struct device dev;
+	int bus_num;
+	u8 max_targets;		/* number of Chip Select# pins supported */
+
+	const struct espi_controller_ops *ops;
+	struct espi_capabilities caps;
+
+	struct mutex lock;	/* serialises controller ops and @channel_enabled */
+	u32 channel_enabled;
+
+	struct list_head device_list;
+	struct mutex device_list_lock;	/* protects @device_list */
+
+	struct blocking_notifier_head notifier_list;
+};
+
+#define to_espi_controller(d)	container_of(d, struct espi_controller, dev)
+
+struct espi_device {
+	struct device dev;
+	struct espi_controller *ctrl;
+	u8 cs;
+	char modalias[ESPI_NAME_SIZE];
+	void *platform_data;
+	struct list_head list;
+};
+
+#define to_espi_device(d)	container_of(d, struct espi_device, dev)
+
+struct espi_driver {
+	struct device_driver driver;
+	int (*probe)(struct espi_device *edev);
+	void (*remove)(struct espi_device *edev);
+	const struct espi_device_id *id_table;
+};
+
+#define to_espi_driver(d)	container_of_const(d, struct espi_driver, driver)
+
+extern const struct bus_type espi_bus_type;
+
+struct espi_controller *espi_controller_alloc(struct device *parent,
+					      unsigned int size);
+int espi_controller_register(struct espi_controller *ctrl);
+void espi_controller_unregister(struct espi_controller *ctrl);
+void espi_controller_put(struct espi_controller *ctrl);
+struct espi_controller *espi_controller_get_by_bus_num(int bus_num);
+
+static inline void *espi_controller_get_devdata(struct espi_controller *ctrl)
+{
+	return dev_get_drvdata(&ctrl->dev);
+}
+
+static inline void espi_controller_set_devdata(struct espi_controller *ctrl,
+					       void *data)
+{
+	dev_set_drvdata(&ctrl->dev, data);
+}
+
+int espi_get_capabilities(struct espi_controller *ctrl,
+			  struct espi_capabilities *caps);
+bool espi_channel_is_enabled(struct espi_controller *ctrl, u8 channel);
+
+int espi_get_configuration(struct espi_controller *ctrl,
+			   u32 slave_reg_addr, u32 *config);
+int espi_set_configuration(struct espi_controller *ctrl,
+			   u32 slave_reg_addr, u32 config);
+int espi_inband_reset(struct espi_controller *ctrl);
+int espi_get_status(struct espi_controller *ctrl,
+		    struct espi_slave_status *status);
+
+int espi_enable_channel(struct espi_controller *ctrl, u8 channel);
+int espi_disable_channel(struct espi_controller *ctrl, u8 channel);
+
+/**
+ * espi_periph_io_read - issue a Peripheral Channel I/O read cycle
+ * @ctrl:  controller
+ * @port:  16-bit I/O port address
+ * @width: access width in bytes (1, 2, or 4)
+ * @value: receives the read value
+ */
+int espi_periph_io_read(struct espi_controller *ctrl,
+			u16 port, u8 width, u32 *value);
+/**
+ * espi_periph_io_write - issue a Peripheral Channel I/O write cycle
+ * @ctrl:  controller
+ * @port:  16-bit I/O port address
+ * @width: access width in bytes (1, 2, or 4)
+ * @value: value to write
+ */
+int espi_periph_io_write(struct espi_controller *ctrl,
+			 u16 port, u8 width, u32 value);
+int espi_periph_mem_read(struct espi_controller *ctrl,
+			 u32 addr, void *buf, size_t len);
+int espi_periph_mem_write(struct espi_controller *ctrl,
+			  u32 addr, const void *buf, size_t len);
+
+int espi_vwire_get(struct espi_controller *ctrl,
+		   u8 index, u8 *value, u8 *valid);
+/**
+ * espi_vwire_put - send a PUT_VIRTUAL_WIRE command
+ * @ctrl:  controller
+ * @index: VWire group index
+ * @value: wire value byte
+ * @valid: valid mask byte
+ */
+int espi_vwire_put(struct espi_controller *ctrl,
+		   u8 index, u8 value, u8 valid);
+
+int espi_oob_send(struct espi_controller *ctrl,
+		  const void *buf, size_t len, u8 tag);
+int espi_oob_recv(struct espi_controller *ctrl,
+		  void *buf, size_t *len, u8 *tag);
+
+int espi_flash_read(struct espi_controller *ctrl,
+		    u32 offset, void *buf, size_t len);
+int espi_flash_write(struct espi_controller *ctrl,
+		     u32 offset, const void *buf, size_t len);
+int espi_flash_erase(struct espi_controller *ctrl,
+		     u32 offset, size_t len);
+
+int espi_handle_alert(struct espi_controller *ctrl);
+
+struct espi_device *espi_new_device(struct espi_controller *ctrl,
+				    const struct espi_board_info *info);
+void espi_remove_device(struct espi_device *edev);
+
+int espi_register_notifier(struct espi_controller *ctrl,
+			   struct notifier_block *nb);
+int espi_unregister_notifier(struct espi_controller *ctrl,
+			     struct notifier_block *nb);
+int espi_notify_event(struct espi_controller *ctrl,
+		      struct espi_event *event);
+
+static inline int espi_dev_get_configuration(struct espi_device *edev,
+					     u32 addr, u32 *config)
+{
+	return espi_get_configuration(edev->ctrl, addr, config);
+}
+
+static inline int espi_dev_set_configuration(struct espi_device *edev,
+					     u32 addr, u32 config)
+{
+	return espi_set_configuration(edev->ctrl, addr, config);
+}
+
+static inline int espi_dev_inband_reset(struct espi_device *edev)
+{
+	return espi_inband_reset(edev->ctrl);
+}
+
+static inline int espi_dev_get_status(struct espi_device *edev,
+				      struct espi_slave_status *status)
+{
+	return espi_get_status(edev->ctrl, status);
+}
+
+int __espi_register_driver(struct module *owner, struct espi_driver *drv);
+void espi_unregister_driver(struct espi_driver *drv);
+
+#define espi_register_driver(drv) \
+	__espi_register_driver(THIS_MODULE, drv)
+
+#define module_espi_driver(__espi_driver) \
+	module_driver(__espi_driver, espi_register_driver, espi_unregister_driver)
+
+#endif /* _LINUX_ESPI_ESPI_H */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [RFC PATCH 2/4] espi: add slave device model and event notification
  2026-08-04 11:52 [RFC PATCH 0/4] espi: introduce eSPI bus framework Krishnamoorthi M
  2026-08-04 11:52 ` [RFC PATCH 1/4] espi: add core " Krishnamoorthi M
@ 2026-08-04 11:52 ` Krishnamoorthi M
  2026-08-04 11:52 ` [RFC PATCH 3/4] Documentation: espi: add subsystem overview and MAINTAINERS entry Krishnamoorthi M
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 13+ messages in thread
From: Krishnamoorthi M @ 2026-08-04 11:52 UTC (permalink / raw)
  To: linux-kernel
  Cc: gregkh, broonie, linux-spi, akshata.mukundshetty, bleung, groeck,
	chrome-platform, corbet, linux-doc, skhan, andrew, linux-aspeed,
	openbmc, Krishnamoorthi M

Add espi_new_device()/espi_remove_device() and a process-context
blocking-notifier event chain. Devices are named espi<bus>.<cs>
(unique across controllers) and matched via a modalias string, also
emitted in the MODALIAS uevent.

cs is range-checked against ctrl->max_targets when the controller
advertises a non-zero limit. Devices are added to ctrl->device_list
before device_register() so that any controller-side iteration sees
the device as soon as the uevent fires.

A per-controller blocking notifier chain delivers hardware events to
slave drivers from process context. Slave drivers subscribe with a
notifier_block; the val argument is the espi_event_type and the data
pointer is the full struct espi_event. A blocking notifier is used
rather than a raw notifier because slave driver callbacks may sleep,
for example to issue follow-up configuration commands over the bus.

Signed-off-by: Krishnamoorthi M <krishnamoorthi.m@amd.com>
---
 drivers/espi/Kconfig      |   3 +
 drivers/espi/Makefile     |   2 +-
 drivers/espi/espi-slave.c | 177 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 181 insertions(+), 1 deletion(-)
 create mode 100644 drivers/espi/espi-slave.c

diff --git a/drivers/espi/Kconfig b/drivers/espi/Kconfig
index 4411d8336e82..7e609004967e 100644
--- a/drivers/espi/Kconfig
+++ b/drivers/espi/Kconfig
@@ -16,4 +16,7 @@ menuconfig ESPI
 	  over a shared physical link: Peripheral (I/O and memory), Virtual
 	  Wire, OOB (Out-of-Band) messaging, and Flash Access.
 
+	  Includes slave device registration and a blocking-notifier event
+	  chain for delivering hardware events to slave drivers.
+
 	  If unsure, say N.
diff --git a/drivers/espi/Makefile b/drivers/espi/Makefile
index 72712fcb0ada..48c2a591132f 100644
--- a/drivers/espi/Makefile
+++ b/drivers/espi/Makefile
@@ -1,2 +1,2 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
-obj-$(CONFIG_ESPI)		+= espi-core.o
+obj-$(CONFIG_ESPI)		+= espi-core.o espi-slave.o
diff --git a/drivers/espi/espi-slave.c b/drivers/espi/espi-slave.c
new file mode 100644
index 000000000000..5d7f8c3c0d1f
--- /dev/null
+++ b/drivers/espi/espi-slave.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * eSPI slave-side device management and event notification
+ *
+ * Copyright (c) 2026, Advanced Micro Devices, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/notifier.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/espi/espi.h>
+
+static void espi_device_release(struct device *dev)
+{
+	struct espi_device *edev = to_espi_device(dev);
+
+	fwnode_handle_put(dev_fwnode(dev));
+	kfree(edev);
+}
+
+static const struct device_type espi_device_type = {
+	.release = espi_device_release,
+};
+
+/**
+ * espi_new_device - instantiate a new eSPI slave device
+ * @ctrl: controller to which the device is attached
+ * @info: board-level description of the device
+ *
+ * Creates and registers a new &struct espi_device on @ctrl.  The device
+ * is named ``espi<bus>.<cs>`` and its modalias is set from
+ * @info->type.
+ *
+ * Return: pointer to the new device, or an ERR_PTR() on failure.
+ */
+struct espi_device *espi_new_device(struct espi_controller *ctrl,
+				    const struct espi_board_info *info)
+{
+	struct espi_device *edev;
+	int ret;
+
+	if (!ctrl || !info)
+		return ERR_PTR(-EINVAL);
+	if (ctrl->max_targets && info->cs >= ctrl->max_targets)
+		return ERR_PTR(-EINVAL);
+
+	edev = kzalloc_obj(*edev, GFP_KERNEL);
+	if (!edev)
+		return ERR_PTR(-ENOMEM);
+
+	edev->ctrl = ctrl;
+	edev->cs = info->cs;
+	edev->platform_data = info->platform_data;
+	strscpy(edev->modalias, info->type, sizeof(edev->modalias));
+	INIT_LIST_HEAD(&edev->list);
+
+	edev->dev.parent = &ctrl->dev;
+	edev->dev.bus = &espi_bus_type;
+	edev->dev.type = &espi_device_type;
+	device_set_node(&edev->dev, fwnode_handle_get(info->fwnode));
+
+	dev_set_name(&edev->dev, "espi%d.%u", ctrl->bus_num, info->cs);
+
+	/*
+	 * Add to the list before device_register() so the device is visible
+	 * to any controller-side iteration as soon as the uevent fires.
+	 */
+	mutex_lock(&ctrl->device_list_lock);
+	list_add_tail(&edev->list, &ctrl->device_list);
+	mutex_unlock(&ctrl->device_list_lock);
+
+	ret = device_register(&edev->dev);
+	if (ret) {
+		dev_err(&ctrl->dev, "failed to register device '%s': %d\n",
+			dev_name(&edev->dev), ret);
+		mutex_lock(&ctrl->device_list_lock);
+		list_del_init(&edev->list);
+		mutex_unlock(&ctrl->device_list_lock);
+		put_device(&edev->dev);
+		return ERR_PTR(ret);
+	}
+
+	return edev;
+}
+EXPORT_SYMBOL_GPL(espi_new_device);
+
+/**
+ * espi_remove_device - unregister and free an eSPI slave device
+ * @edev: device to remove
+ *
+ * Removes @edev from the controller's device list and unregisters it
+ * from the bus.  Must be called at most once per device.
+ */
+void espi_remove_device(struct espi_device *edev)
+{
+	struct espi_controller *ctrl;
+
+	if (!edev)
+		return;
+	ctrl = edev->ctrl;
+
+	mutex_lock(&ctrl->device_list_lock);
+	if (WARN_ON(list_empty(&edev->list))) {
+		mutex_unlock(&ctrl->device_list_lock);
+		return;
+	}
+	list_del_init(&edev->list);
+	mutex_unlock(&ctrl->device_list_lock);
+
+	device_unregister(&edev->dev);
+}
+EXPORT_SYMBOL_GPL(espi_remove_device);
+
+/**
+ * espi_register_notifier - subscribe to eSPI hardware events
+ * @ctrl: controller whose event chain to subscribe to
+ * @nb:   notifier block to register
+ *
+ * Notifier callbacks are invoked from process context (threaded IRQ or
+ * workqueue).  The @val argument passed to the callback is the
+ * &enum espi_event_type value; @data points to the &struct espi_event.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int espi_register_notifier(struct espi_controller *ctrl,
+			   struct notifier_block *nb)
+{
+	if (!ctrl || !nb)
+		return -EINVAL;
+	return blocking_notifier_chain_register(&ctrl->notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(espi_register_notifier);
+
+/**
+ * espi_unregister_notifier - unsubscribe from eSPI hardware events
+ * @ctrl: controller whose event chain to unsubscribe from
+ * @nb:   notifier block to unregister
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int espi_unregister_notifier(struct espi_controller *ctrl,
+			     struct notifier_block *nb)
+{
+	if (!ctrl || !nb)
+		return -EINVAL;
+	return blocking_notifier_chain_unregister(&ctrl->notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(espi_unregister_notifier);
+
+/**
+ * espi_notify_event - deliver a hardware event to all registered listeners
+ * @ctrl:  controller on which the event occurred
+ * @event: event descriptor; @event->ctrl is set by this function
+ *
+ * Must be called from process context (threaded IRQ or workqueue), never
+ * from hard-IRQ context and never with @ctrl->lock held.
+ *
+ * Return: a NOTIFY_* value, not an errno.  Callers that need to map this
+ * to an errno should use notifier_to_errno().
+ */
+int espi_notify_event(struct espi_controller *ctrl, struct espi_event *event)
+{
+	if (!ctrl || !event)
+		return notifier_from_errno(-EINVAL);
+	event->ctrl = ctrl;
+	return blocking_notifier_call_chain(&ctrl->notifier_list,
+					    (unsigned long)event->type, event);
+}
+EXPORT_SYMBOL_GPL(espi_notify_event);
+
+MODULE_AUTHOR("Krishnamoorthi M <krishnamoorthi.m@amd.com>");
+MODULE_DESCRIPTION("eSPI slave-side device management");
+MODULE_LICENSE("GPL");
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [RFC PATCH 3/4] Documentation: espi: add subsystem overview and MAINTAINERS entry
  2026-08-04 11:52 [RFC PATCH 0/4] espi: introduce eSPI bus framework Krishnamoorthi M
  2026-08-04 11:52 ` [RFC PATCH 1/4] espi: add core " Krishnamoorthi M
  2026-08-04 11:52 ` [RFC PATCH 2/4] espi: add slave device model and event notification Krishnamoorthi M
@ 2026-08-04 11:52 ` Krishnamoorthi M
  2026-08-04 16:39   ` Randy Dunlap
  2026-08-04 11:52 ` [RFC PATCH 4/4] espi: amd: add AMD eSPI controller driver Krishnamoorthi M
  2026-08-04 12:18 ` [RFC PATCH 0/4] espi: introduce eSPI bus framework Greg KH
  4 siblings, 1 reply; 13+ messages in thread
From: Krishnamoorthi M @ 2026-08-04 11:52 UTC (permalink / raw)
  To: linux-kernel
  Cc: gregkh, broonie, linux-spi, akshata.mukundshetty, bleung, groeck,
	chrome-platform, corbet, linux-doc, skhan, andrew, linux-aspeed,
	openbmc, Krishnamoorthi M

Add a driver-api overview of the eSPI subsystem and a MAINTAINERS entry
covering the subsystem files.

The document describes the architecture, how to write a controller driver
and a slave driver, the per-channel APIs (Peripheral, Virtual Wire, OOB,
Flash), the alert mechanism flow, and the event notification model. An
API Reference section renders kernel-doc from the exported symbols.

Signed-off-by: Krishnamoorthi M <krishnamoorthi.m@amd.com>
---
 Documentation/driver-api/espi.rst  | 213 +++++++++++++++++++++++++++++
 Documentation/driver-api/index.rst |   1 +
 MAINTAINERS                        |   8 ++
 3 files changed, 222 insertions(+)
 create mode 100644 Documentation/driver-api/espi.rst

diff --git a/Documentation/driver-api/espi.rst b/Documentation/driver-api/espi.rst
new file mode 100644
index 000000000000..60a3187edb05
--- /dev/null
+++ b/Documentation/driver-api/espi.rst
@@ -0,0 +1,213 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+===========================================
+eSPI (Enhanced Serial Peripheral Interface)
+===========================================
+
+Introduction
+============
+
+eSPI is a bus defined by Intel that replaces the legacy LPC bus. Unlike
+SPI it is a structured, capability-negotiated, message-oriented protocol
+with four logically independent channels (Peripheral, Virtual Wire, OOB,
+Flash) over a shared physical link, and asynchronous target-to-controller
+events, so it is modelled as its own bus type rather than an extension of
+the SPI subsystem.
+
+Architecture
+============
+
+* ``struct espi_controller`` - host controller, created with
+  espi_controller_alloc() and registered with espi_controller_register().
+  It is not itself a device on espi_bus_type.
+* ``struct espi_device`` - a target on the bus, matched to a
+  ``struct espi_driver`` via its modalias.
+* ``struct espi_controller_ops`` - the optional hardware-op table; the
+  channel API returns -EOPNOTSUPP for ops a controller does not provide.
+
+Writing a controller driver
+===========================
+
+A controller driver allocates and registers a controller from its
+``probe()`` function::
+
+    ctrl = espi_controller_alloc(&pdev->dev, sizeof(*priv));
+    if (IS_ERR(ctrl))
+        return PTR_ERR(ctrl);
+
+    priv = espi_controller_get_devdata(ctrl);
+    ctrl->ops = &my_espi_ops;
+    ctrl->max_targets = 1;
+
+    /* populate ctrl->caps from hardware capability registers */
+    ctrl->caps.supported_channels = ESPI_CHANNEL_ALL;
+    ctrl->caps.max_freq_mhz       = 33;
+    ctrl->caps.io_mode            = ESPI_IO_MODE_SINGLE;
+
+    ret = espi_controller_register(ctrl);
+    if (ret)
+        goto err_put;
+
+After registration the controller calls espi_new_device() for each
+target enumerated from firmware (ACPI or device tree)::
+
+    struct espi_board_info info = {
+        .type = "my-ec",
+        .cs   = 0,
+    };
+    edev = espi_new_device(ctrl, &info);
+
+On removal::
+
+    espi_remove_device(edev);
+    espi_controller_unregister(ctrl);
+    espi_controller_put(ctrl);
+
+Writing a slave driver
+======================
+
+A slave driver declares a device ID table and a ``struct espi_driver``::
+
+    static const struct espi_device_id my_ec_ids[] = {
+        { "my-ec", 0 },
+        { }
+    };
+    MODULE_DEVICE_TABLE(espi, my_ec_ids);
+
+    static int my_ec_probe(struct espi_device *edev)
+    {
+        /* register for hardware events */
+        nb->notifier_call = my_ec_event;
+        espi_register_notifier(edev->ctrl, nb);
+        return 0;
+    }
+
+    static void my_ec_remove(struct espi_device *edev)
+    {
+        espi_unregister_notifier(edev->ctrl, nb);
+    }
+
+    static struct espi_driver my_ec_driver = {
+        .driver   = { .name = "my-ec" },
+        .id_table = my_ec_ids,
+        .probe    = my_ec_probe,
+        .remove   = my_ec_remove,
+    };
+    module_espi_driver(my_ec_driver);
+
+Channel-independent commands
+============================
+
+espi_get_configuration(), espi_set_configuration(), espi_inband_reset()
+and espi_get_status(). GET_STATUS is optional: controllers whose hardware
+does not implement the wire command leave .get_status unset.
+
+Capability negotiation and channel management
+=============================================
+
+At boot the controller driver reads the target's capability registers via
+espi_get_configuration(), negotiates link parameters (I/O mode, clock
+frequency, CRC) via espi_set_configuration(), then enables each channel
+with espi_enable_channel(). espi_channel_is_enabled() may be called at
+any time to query the current state. Channels may be disabled individually
+with espi_disable_channel(), for example before an in-band reset.
+
+Channel APIs
+============
+
+Peripheral channel
+------------------
+
+Carries I/O and memory cycles between the host and target endpoints.
+
+* espi_periph_io_read() / espi_periph_io_write() — 16-bit I/O port
+  access; ``width`` is the access size in bytes (1, 2, or 4).
+* espi_periph_mem_read() / espi_periph_mem_write() — 32-bit memory
+  mapped access.
+
+Virtual Wire channel
+--------------------
+
+Carries logical signal state (power sequencing, SMI#, SCI#, IRQs) as
+indexed wire groups. Each group carries up to four wire values with
+individual valid bits.
+
+* espi_vwire_get() — read a wire group from the target.
+* espi_vwire_put() — send a PUT_VIRTUAL_WIRE command to the target.
+  Named after the eSPI PUT_VW wire command, not a reference-count
+  release.
+
+Wire changes from the target generate an ``ESPI_EVENT_VWIRE_CHANGED``
+event delivered through the notifier chain.
+
+OOB channel
+-----------
+
+Tunnels SMBus/I2C messages between the host and target out-of-band
+processor (BMC, EC). Messages are exchanged as opaque byte buffers with
+a tag field for matching requests to responses.
+
+* espi_oob_send() / espi_oob_recv()
+
+Incoming OOB messages generate an ``ESPI_EVENT_OOB_RECEIVED`` event.
+
+Flash Access channel
+--------------------
+
+Provides access to a SPI flash device attached to the target. The target
+acts as a proxy for flash read, write, and erase operations.
+
+* espi_flash_read() / espi_flash_write() / espi_flash_erase()
+
+Alert mechanism
+===============
+
+When the target has upstream data pending it asserts ``ALERT#``. The
+controller's hard-IRQ handler acknowledges the interrupt and defers
+processing to a threaded IRQ or workqueue. From that process context the
+controller driver calls espi_handle_alert(), which acquires the
+controller lock and dispatches to ``ops->handle_alert``. The hardware
+callback reads the target's status register (GET_STATUS), identifies the
+pending channel, and calls espi_notify_event() to deliver the appropriate
+``ESPI_EVENT_*`` to all registered slave driver notifiers::
+
+    ALERT# asserted by target
+          |
+          v
+    hard-IRQ handler (controller driver)
+          |
+          v
+    threaded IRQ / workqueue
+          |
+          v
+    espi_handle_alert(ctrl)          [espi-core.c]
+          |
+          v
+    ops->handle_alert(ctrl)          [controller driver]
+          | reads GET_STATUS, decodes channel
+          v
+    espi_notify_event(ctrl, &event)  [espi-slave.c]
+          |
+          v
+    slave driver notifier callback
+
+espi_handle_alert() must always be called from process context; it must
+never be called from a hard-IRQ handler.
+
+Events and concurrency
+======================
+
+Hardware events (Virtual Wire changes, OOB messages, Peripheral channel
+completions, channel state changes) are delivered through a per-controller
+blocking notifier chain (espi_register_notifier()/espi_notify_event()).
+Callbacks run in process context; controllers deliver events from a
+threaded IRQ or workqueue, never from hardirq and never while holding the
+controller lock.
+
+API Reference
+=============
+
+.. kernel-doc:: include/linux/espi/espi.h
+
+.. kernel-doc:: drivers/espi/espi-slave.c
+   :export:
diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst
index 6601a258690f..175ed0794a48 100644
--- a/Documentation/driver-api/index.rst
+++ b/Documentation/driver-api/index.rst
@@ -139,6 +139,7 @@ Subsystem-specific APIs
    sm501
    soundwire/index
    spi
+   espi
    surface_aggregator/index
    switchtec
    sync_file
diff --git a/MAINTAINERS b/MAINTAINERS
index e95fc6f2ddc6..c416326d14fd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9705,6 +9705,14 @@ F:	Documentation/devicetree/bindings/clock/eswin,eic7700-clock.yaml
 F:	drivers/clk/eswin/
 F:	include/dt-bindings/clock/eswin,eic7700-clock.h
 
+ESPI SUBSYSTEM
+M:	Krishnamoorthi M <krishnamoorthi.m@amd.com>
+L:	linux-kernel@vger.kernel.org
+S:	Supported
+F:	Documentation/driver-api/espi.rst
+F:	drivers/espi/
+F:	include/linux/espi/
+
 ET131X NETWORK DRIVER
 M:	Mark Einon <mark.einon@gmail.com>
 S:	Odd Fixes
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [RFC PATCH 4/4] espi: amd: add AMD eSPI controller driver
  2026-08-04 11:52 [RFC PATCH 0/4] espi: introduce eSPI bus framework Krishnamoorthi M
                   ` (2 preceding siblings ...)
  2026-08-04 11:52 ` [RFC PATCH 3/4] Documentation: espi: add subsystem overview and MAINTAINERS entry Krishnamoorthi M
@ 2026-08-04 11:52 ` Krishnamoorthi M
  2026-08-04 12:18 ` [RFC PATCH 0/4] espi: introduce eSPI bus framework Greg KH
  4 siblings, 0 replies; 13+ messages in thread
From: Krishnamoorthi M @ 2026-08-04 11:52 UTC (permalink / raw)
  To: linux-kernel
  Cc: gregkh, broonie, linux-spi, akshata.mukundshetty, bleung, groeck,
	chrome-platform, corbet, linux-doc, skhan, andrew, linux-aspeed,
	openbmc, Krishnamoorthi M

Add a controller driver for the AMD eSPI controller (ACPI HID AMDI0070).

This initial implementation supports the channel-independent link
management commands: GET_CONFIGURATION, SET_CONFIGURATION and In-Band
Reset. Command completion and errors are reported via the controller
interrupt-status register, which is polled.

When the link parameters are renegotiated via SET_CONFIGURATION, the
host-side register is updated to match so both ends agree on clock and
I/O mode. In-Band Reset is handled safely by lowering the bus clock
before issuing the reset and restoring the host configuration afterwards.

Channel ops (Peripheral, Virtual Wire, OOB, Flash) and alert handling
are deferred to follow-on patches.

Co-developed-by: Akshata MukundShetty <akshata.mukundshetty@amd.com>
Signed-off-by: Akshata MukundShetty <akshata.mukundshetty@amd.com>
Signed-off-by: Krishnamoorthi M <krishnamoorthi.m@amd.com>
---
 drivers/espi/Kconfig    |  19 ++
 drivers/espi/Makefile   |   1 +
 drivers/espi/espi-amd.c | 453 ++++++++++++++++++++++++++++++++++++++++
 drivers/espi/espi-amd.h | 126 +++++++++++
 4 files changed, 599 insertions(+)
 create mode 100644 drivers/espi/espi-amd.c
 create mode 100644 drivers/espi/espi-amd.h

diff --git a/drivers/espi/Kconfig b/drivers/espi/Kconfig
index 7e609004967e..a6f104003799 100644
--- a/drivers/espi/Kconfig
+++ b/drivers/espi/Kconfig
@@ -20,3 +20,22 @@ menuconfig ESPI
 	  chain for delivering hardware events to slave drivers.
 
 	  If unsure, say N.
+
+if ESPI
+comment "eSPI Controller Drivers"
+
+config ESPI_AMD
+	tristate "AMD eSPI Controller"
+	depends on X86 || COMPILE_TEST
+	help
+	  Enable support for the AMD eSPI controller found on AMD SoCs and
+	  APUs (ACPI HID: AMDI0070). This controller drives the Enhanced
+	  Serial Peripheral Interface link to an eSPI target such as a BMC
+	  and implements the channel-independent configuration commands
+	  (GET_CONFIGURATION, SET_CONFIGURATION and In-Band Reset) used to
+	  bring up and negotiate the link's I/O mode, clock and CRC.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called espi-amd. If unsure, say N.
+
+endif # ESPI
diff --git a/drivers/espi/Makefile b/drivers/espi/Makefile
index 48c2a591132f..2f0a5cae2cb9 100644
--- a/drivers/espi/Makefile
+++ b/drivers/espi/Makefile
@@ -1,2 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 obj-$(CONFIG_ESPI)		+= espi-core.o espi-slave.o
+obj-$(CONFIG_ESPI_AMD)		+= espi-amd.o
diff --git a/drivers/espi/espi-amd.c b/drivers/espi/espi-amd.c
new file mode 100644
index 000000000000..36e3c5bc937f
--- /dev/null
+++ b/drivers/espi/espi-amd.c
@@ -0,0 +1,453 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD eSPI Controller Driver
+ * Copyright (c) 2026, Advanced Micro Devices, Inc.
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/espi/espi.h>
+
+#include "espi-amd.h"
+
+static void amd_espi_clear_status(struct amd_espi_data *priv)
+{
+	u32 status = readl(priv->base + AMD_ESPI_SLAVE0_INT_STS_REG);
+
+	if (status)
+		writel(status, priv->base + AMD_ESPI_SLAVE0_INT_STS_REG);
+}
+
+static int amd_espi_check_status(struct amd_espi_data *priv, u32 status)
+{
+	if (!(status & AMD_ESPI_DNCMD_INT)) {
+		dev_err(priv->dev, "downstream command did not complete\n");
+		return -EIO;
+	}
+	if (!(status & AMD_ESPI_ERR_INT_MASK))
+		return 0;
+
+	if (status & AMD_ESPI_WAIT_TIMEOUT_INT) {
+		dev_err(priv->dev, "wait-state timer timeout\n");
+		return -ETIMEDOUT;
+	}
+	if (status & AMD_ESPI_NO_RESP_INT) {
+		dev_err(priv->dev, "no response from target\n");
+		return -ENODEV;
+	}
+	if (status & AMD_ESPI_CRC_ERR_INT) {
+		dev_err(priv->dev, "CRC error\n");
+		return -EBADMSG;
+	}
+	dev_err(priv->dev, "command error, status=0x%08x\n", status);
+	return -EIO;
+}
+
+static int amd_espi_send_cmd(struct amd_espi_data *priv,
+			     u32 hdr0, u32 hdr1, u32 hdr2)
+{
+	u32 status;
+	int ret;
+
+	ret = readl_poll_timeout(priv->base + AMD_ESPI_DN_TXHDR_REG0, status,
+				 !(status & AMD_ESPI_TXHDR0_CMD_STATUS),
+				 AMD_ESPI_MSG_DELAY_MIN_US,
+				 AMD_ESPI_RESP_MAX_TIMEOUT_US);
+	if (ret) {
+		dev_err(priv->dev, "controller not ready to accept command\n");
+		return -EBUSY;
+	}
+
+	amd_espi_clear_status(priv);
+
+	writel(hdr1, priv->base + AMD_ESPI_DN_TXHDR_REG1);
+	writel(hdr2, priv->base + AMD_ESPI_DN_TXHDR_REG2);
+	/*
+	 * Data port must be written before triggering to frame the packet.
+	 * Channel-independent commands carry no data payload, so zero suffices.
+	 */
+	writel(0, priv->base + AMD_ESPI_DN_TXDATA_REG0);
+
+	dev_dbg(priv->dev,
+		"TX before trigger: hdr0=0x%08x hdr1=0x%08x hdr2=0x%08x data=0x%08x\n",
+		hdr0, hdr1, hdr2, 0);
+
+	writel(hdr0, priv->base + AMD_ESPI_DN_TXHDR_REG0);
+
+	ret = readl_poll_timeout(priv->base + AMD_ESPI_DN_TXHDR_REG0, status,
+				 !(status & AMD_ESPI_TXHDR0_CMD_STATUS),
+				 AMD_ESPI_MSG_DELAY_MIN_US,
+				 AMD_ESPI_RESP_MAX_TIMEOUT_US);
+	if (ret) {
+		dev_err(priv->dev, "timed out sending command\n");
+		return -ETIMEDOUT;
+	}
+
+	ret = readl_poll_timeout(priv->base + AMD_ESPI_SLAVE0_INT_STS_REG,
+				 status,
+				 status & (AMD_ESPI_DNCMD_INT | AMD_ESPI_ERR_INT_MASK),
+				 AMD_ESPI_MSG_DELAY_MIN_US,
+				 AMD_ESPI_RESP_MAX_TIMEOUT_US);
+	if (ret) {
+		dev_err(priv->dev, "timed out waiting for completion\n");
+		return -ETIMEDOUT;
+	}
+
+	ret = amd_espi_check_status(priv, status);
+	writel(AMD_ESPI_DNCMD_INT, priv->base + AMD_ESPI_SLAVE0_INT_STS_REG);
+	return ret;
+}
+
+static u32 amd_espi_cfg_hdr0(enum espi_cmd_type type, u32 addr)
+{
+	return FIELD_PREP(AMD_ESPI_TXHDR0_CMD_TYPE, type) |
+	       AMD_ESPI_TXHDR0_CMD_STATUS |
+	       FIELD_PREP(AMD_ESPI_TXHDR0_HDATA0, (addr >> 8) & 0xff) |
+	       FIELD_PREP(AMD_ESPI_TXHDR0_HDATA1, addr & 0xff);
+}
+
+static int amd_espi_get_configuration(struct espi_controller *ctrl,
+				      u32 slave_reg_addr, u32 *config)
+{
+	struct amd_espi_data *priv = espi_controller_get_devdata(ctrl);
+	u32 hdr0 = amd_espi_cfg_hdr0(ESPI_CMD_GET_CONFIGURATION, slave_reg_addr);
+	int ret;
+
+	ret = amd_espi_send_cmd(priv, hdr0, 0, 0);
+	if (ret)
+		return ret;
+
+	*config = readl(priv->base + AMD_ESPI_DN_TXHDR_REG1);
+	dev_dbg(priv->dev, "GET_CONFIGURATION addr=0x%04x config=0x%08x\n",
+		slave_reg_addr, *config);
+	return 0;
+}
+
+/* Translate: target and host use different bit positions and clock encoding. */
+static void amd_espi_sync_config(struct amd_espi_data *priv, u32 slave_cfg)
+{
+	u32 io_mode = FIELD_GET(ESPI_GENCFG_IO_MODE, slave_cfg);
+	u32 host_freq;
+	u32 reg;
+
+	switch (FIELD_GET(ESPI_GENCFG_OP_FREQ, slave_cfg)) {
+	case ESPI_GENCFG_FREQ_66MHZ:
+		host_freq = AMD_ESPI_SLAVE0_CLK_FREQ_66MHZ;
+		break;
+	case ESPI_GENCFG_FREQ_33MHZ:
+		host_freq = AMD_ESPI_SLAVE0_CLK_FREQ_33MHZ;
+		break;
+	default:
+		host_freq = AMD_ESPI_SLAVE0_CLK_FREQ_16MHZ;
+		break;
+	}
+
+	reg = readl(priv->base + AMD_ESPI_SLAVE0_CONFIG_REG);
+	reg &= ~(AMD_ESPI_SLAVE0_CFG_CLK_FREQ | AMD_ESPI_SLAVE0_CFG_IO_MODE |
+		 AMD_ESPI_SLAVE0_CFG_ALERT_MODE | AMD_ESPI_SLAVE0_CFG_CRC_EN);
+	reg |= FIELD_PREP(AMD_ESPI_SLAVE0_CFG_CLK_FREQ, host_freq);
+	reg |= FIELD_PREP(AMD_ESPI_SLAVE0_CFG_IO_MODE, io_mode);
+	if (slave_cfg & ESPI_GENCFG_ALERT_MODE)
+		reg |= AMD_ESPI_SLAVE0_CFG_ALERT_MODE;
+	if (slave_cfg & ESPI_GENCFG_CRC_EN)
+		reg |= AMD_ESPI_SLAVE0_CFG_CRC_EN;
+	writel(reg, priv->base + AMD_ESPI_SLAVE0_CONFIG_REG);
+
+	dev_dbg(priv->dev,
+		"host SLAVE0_CONFIG updated to 0x%08x (io=%u host_freq=%u)\n",
+		readl(priv->base + AMD_ESPI_SLAVE0_CONFIG_REG), io_mode, host_freq);
+}
+
+static int amd_espi_set_configuration(struct espi_controller *ctrl,
+				      u32 slave_reg_addr, u32 config)
+{
+	struct amd_espi_data *priv = espi_controller_get_devdata(ctrl);
+	u32 hdr0 = amd_espi_cfg_hdr0(ESPI_CMD_SET_CONFIGURATION, slave_reg_addr);
+	int ret;
+
+	dev_dbg(priv->dev, "SET_CONFIGURATION addr=0x%04x config=0x%08x\n",
+		slave_reg_addr, config);
+	ret = amd_espi_send_cmd(priv, hdr0, config, 0);
+	if (ret)
+		return ret;
+
+	/* Sync host link parameters after a General Configuration change. */
+	if (slave_reg_addr == ESPI_SLAVE_REG_GENERAL_CFG)
+		amd_espi_sync_config(priv, config);
+
+	return 0;
+}
+
+/* Lower clock to 16.7 MHz for In-Band Reset; preserve I/O mode. */
+static void amd_espi_lower_clk(struct amd_espi_data *priv)
+{
+	u32 reg = readl(priv->base + AMD_ESPI_SLAVE0_CONFIG_REG);
+
+	reg &= ~AMD_ESPI_SLAVE0_CFG_CLK_FREQ;
+	reg |= FIELD_PREP(AMD_ESPI_SLAVE0_CFG_CLK_FREQ,
+			  AMD_ESPI_SLAVE0_CLK_FREQ_16MHZ);
+	writel(reg, priv->base + AMD_ESPI_SLAVE0_CONFIG_REG);
+}
+
+static const char *amd_espi_io_mode_str(u32 io_mode)
+{
+	switch (io_mode) {
+	case AMD_ESPI_SLAVE0_IO_MODE_SINGLE:
+		return "single";
+	case AMD_ESPI_SLAVE0_IO_MODE_DUAL:
+		return "dual";
+	case AMD_ESPI_SLAVE0_IO_MODE_QUAD:
+		return "quad";
+	default:
+		return "reserved";
+	}
+}
+
+static const char *amd_espi_clk_freq_str(u32 clk_freq)
+{
+	switch (clk_freq) {
+	case AMD_ESPI_SLAVE0_CLK_FREQ_16MHZ:
+		return "16 MHz";
+	case AMD_ESPI_SLAVE0_CLK_FREQ_33MHZ:
+		return "33 MHz";
+	case AMD_ESPI_SLAVE0_CLK_FREQ_66MHZ:
+		return "66 MHz";
+	default:
+		return "reserved";
+	}
+}
+
+static void amd_espi_log_link(struct amd_espi_data *priv, const char *when)
+{
+	u32 reg = readl(priv->base + AMD_ESPI_SLAVE0_CONFIG_REG);
+
+	dev_dbg(priv->dev,
+		"%s: I/O mode=%s frequency=%s (SLAVE0_CONFIG=0x%08x)\n", when,
+		amd_espi_io_mode_str(FIELD_GET(AMD_ESPI_SLAVE0_CFG_IO_MODE, reg)),
+		amd_espi_clk_freq_str(FIELD_GET(AMD_ESPI_SLAVE0_CFG_CLK_FREQ, reg)),
+		reg);
+}
+
+static int amd_espi_inband_reset(struct espi_controller *ctrl)
+{
+	struct amd_espi_data *priv = espi_controller_get_devdata(ctrl);
+	u32 hdr0 = FIELD_PREP(AMD_ESPI_TXHDR0_CMD_TYPE, ESPI_CMD_IN_BAND_RESET) |
+		   AMD_ESPI_TXHDR0_CMD_STATUS;
+	u32 cfg_before;
+	u32 reg;
+	int ret;
+
+	cfg_before = readl(priv->base + AMD_ESPI_SLAVE0_CONFIG_REG);
+	dev_dbg(priv->dev, "IN_BAND_RESET: SLAVE0_CONFIG before = 0x%08x\n",
+		cfg_before);
+
+	amd_espi_lower_clk(priv);
+
+	ret = amd_espi_send_cmd(priv, hdr0, 0, 0);
+	if (ret)
+		return ret;
+
+	/*
+	 * Restore CRC and Alert-mode bits after reset: the controller clears
+	 * SLAVE0_CONFIG but the target retains these across an In-Band Reset.
+	 */
+	reg = readl(priv->base + AMD_ESPI_SLAVE0_CONFIG_REG);
+	if (ctrl->caps.alert_mode)
+		reg |= AMD_ESPI_SLAVE0_CFG_ALERT_MODE;
+	if (ctrl->caps.crc_supported)
+		reg |= AMD_ESPI_SLAVE0_CFG_CRC_EN;
+	writel(reg, priv->base + AMD_ESPI_SLAVE0_CONFIG_REG);
+
+	dev_dbg(priv->dev,
+		"IN_BAND_RESET: SLAVE0_CONFIG after = 0x%08x (was 0x%08x)\n",
+		readl(priv->base + AMD_ESPI_SLAVE0_CONFIG_REG), cfg_before);
+
+	amd_espi_log_link(priv, "after in-band reset");
+	return 0;
+}
+
+static int amd_espi_setup(struct espi_controller *ctrl)
+{
+	struct amd_espi_data *priv = espi_controller_get_devdata(ctrl);
+	u32 reg;
+
+	amd_espi_log_link(priv, "firmware default");
+
+	writel(AMD_ESPI_INTR_MASK, priv->base + AMD_ESPI_SLAVE0_INT_STS_REG);
+
+	/* Controller-wide bring-up: must run before the first downstream command. */
+	reg = readl(priv->base + AMD_ESPI_SLAVE0_RX_VW_REG);
+	writel(reg | AMD_ESPI_SLAVE0_RX_VW_INIT_MASK,
+	       priv->base + AMD_ESPI_SLAVE0_RX_VW_REG);
+
+	reg = readl(priv->base + AMD_ESPI_GLOBAL_CNTRL_REG0);
+	reg |= AMD_ESPI_GLOBAL0_WDG_EN | AMD_ESPI_GLOBAL0_WAIT_CHK_EN;
+	reg &= ~AMD_ESPI_GLOBAL0_WAIT_STATE;
+	reg |= FIELD_PREP(AMD_ESPI_GLOBAL0_WAIT_STATE, AMD_ESPI_WAIT_STATE_CNT);
+	writel(reg, priv->base + AMD_ESPI_GLOBAL_CNTRL_REG0);
+
+	reg = readl(priv->base + AMD_ESPI_SLAVE0_INT_EN_REG);
+	writel(reg | AMD_ESPI_ALL_ERR_INT | AMD_ESPI_REG_CMD_INT,
+	       priv->base + AMD_ESPI_SLAVE0_INT_EN_REG);
+
+	reg = readl(priv->base + AMD_ESPI_GLOBAL_CNTRL_REG1);
+	reg &= ~(AMD_ESPI_ERR_INT_MAP | AMD_ESPI_RGCMD_INT_MAP);
+	reg |= FIELD_PREP(AMD_ESPI_ERR_INT_MAP, AMD_ESPI_INT_MAP_SMI);
+	reg |= FIELD_PREP(AMD_ESPI_RGCMD_INT_MAP, AMD_ESPI_INT_MAP_SMI);
+	reg |= AMD_ESPI_BUS_MASTER_EN | AMD_ESPI_VW_REQ_EN;
+	writel(reg, priv->base + AMD_ESPI_GLOBAL_CNTRL_REG1);
+
+	/*
+	 * Unmask the VW IRQ index window. Do not touch SLAVE0_CONFIG clock or
+	 * I/O mode: preserve the operating point firmware already negotiated.
+	 */
+	reg = readl(priv->base + AMD_ESPI_SLAVE0_VW_MISC_CNTRL_REG);
+	reg &= ~GENMASK(31, 8);
+	writel(reg | GENMASK(3, 0),
+	       priv->base + AMD_ESPI_SLAVE0_VW_MISC_CNTRL_REG);
+
+	writel(AMD_ESPI_INTR_MASK, priv->base + AMD_ESPI_SLAVE0_INT_STS_REG);
+
+	amd_espi_log_link(priv, "after probe setup");
+	return 0;
+}
+
+/* .get_status omitted: AMD HW has no GET_STATUS wire command. */
+static const struct espi_controller_ops amd_espi_ops = {
+	.setup			= amd_espi_setup,
+	.get_configuration	= amd_espi_get_configuration,
+	.set_configuration	= amd_espi_set_configuration,
+	.inband_reset		= amd_espi_inband_reset,
+};
+
+static u32 amd_espi_read_caps(struct amd_espi_data *priv,
+			      struct espi_capabilities *caps)
+{
+	u32 cap = readl(priv->base + AMD_ESPI_MASTER_CAP_REG);
+
+	dev_dbg(priv->dev, "MASTER_CAP=0x%08x\n", cap);
+
+	caps->supported_channels = 0;
+	if (cap & AMD_ESPI_CAP_PR_SUPPORT)
+		caps->supported_channels |= ESPI_CHANNEL_PERIPH_SUPP;
+	if (cap & AMD_ESPI_CAP_VW_SUPPORT)
+		caps->supported_channels |= ESPI_CHANNEL_VWIRE_SUPP;
+	if (cap & AMD_ESPI_CAP_OOB_SUPPORT)
+		caps->supported_channels |= ESPI_CHANNEL_OOB_SUPP;
+	if (cap & AMD_ESPI_CAP_FLASH_SUPPORT)
+		caps->supported_channels |= ESPI_CHANNEL_FLASH_SUPP;
+
+	switch (FIELD_GET(AMD_ESPI_CAP_CLK_FREQ, cap)) {
+	case 0x3:
+		caps->max_freq_mhz = ESPI_FREQ_66MHZ;
+		break;
+	case 0x1:
+		caps->max_freq_mhz = ESPI_FREQ_33MHZ;
+		break;
+	default:
+		caps->max_freq_mhz = ESPI_FREQ_16MHZ;
+		break;
+	}
+	switch (FIELD_GET(AMD_ESPI_CAP_IO_MODE, cap)) {
+	case 0x2:
+		caps->io_mode = ESPI_IO_MODE_QUAD;
+		break;
+	case 0x1:
+		caps->io_mode = ESPI_IO_MODE_DUAL;
+		break;
+	default:
+		caps->io_mode = ESPI_IO_MODE_SINGLE;
+		break;
+	}
+
+	caps->alert_mode = !!(cap & AMD_ESPI_CAP_ALERT_MODE);
+	caps->crc_supported = !!(cap & AMD_ESPI_CAP_CRC_CHECK);
+	caps->periph_max_payload = FIELD_GET(AMD_ESPI_CAP_PR_MAX_SIZE, cap);
+	caps->vwire_max_count = FIELD_GET(AMD_ESPI_CAP_VW_MAX_SIZE, cap);
+	caps->oob_max_payload = FIELD_GET(AMD_ESPI_CAP_OOB_MAX_SIZE, cap);
+	caps->flash_max_payload = FIELD_GET(AMD_ESPI_CAP_FLASH_MAX_SIZE, cap);
+
+	return cap;
+}
+
+static u8 amd_espi_num_targets(u32 cap)
+{
+	/* CAP_SLAVE_NUM is 0-based: 0 means 1 target, 1 means 2, etc. */
+	return (u8)(FIELD_GET(AMD_ESPI_CAP_SLAVE_NUM, cap) + 1);
+}
+
+static int amd_espi_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct espi_controller *ctrl;
+	struct amd_espi_data *priv;
+	int ret;
+	u32 cap;
+
+	ctrl = espi_controller_alloc(dev, sizeof(*priv));
+	if (IS_ERR(ctrl))
+		return dev_err_probe(dev, PTR_ERR(ctrl),
+				     "failed to allocate eSPI controller\n");
+
+	priv = espi_controller_get_devdata(ctrl);
+	priv->dev = dev;
+	priv->version = (enum amd_espi_versions)(uintptr_t)
+			device_get_match_data(dev);
+
+	priv->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(priv->base)) {
+		ret = dev_err_probe(dev, PTR_ERR(priv->base),
+				    "failed to map registers\n");
+		goto err_put;
+	}
+
+	cap = amd_espi_read_caps(priv, &ctrl->caps);
+	ctrl->ops = &amd_espi_ops;
+	ctrl->max_targets = amd_espi_num_targets(cap);
+
+	ret = espi_controller_register(ctrl);
+	if (ret) {
+		ret = dev_err_probe(dev, ret,
+				    "failed to register eSPI controller\n");
+		goto err_put;
+	}
+
+	platform_set_drvdata(pdev, ctrl);
+	return 0;
+
+err_put:
+	espi_controller_put(ctrl);
+	return ret;
+}
+
+static void amd_espi_remove(struct platform_device *pdev)
+{
+	espi_controller_unregister(platform_get_drvdata(pdev));
+}
+
+static const struct acpi_device_id amd_espi_acpi_match[] = {
+	{ "AMDI0070", AMD_ESPI_V1 },
+	{ }
+};
+MODULE_DEVICE_TABLE(acpi, amd_espi_acpi_match);
+
+static struct platform_driver amd_espi_driver = {
+	.driver = {
+		.name			= "amd-espi",
+		.acpi_match_table	= amd_espi_acpi_match,
+	},
+	.probe	= amd_espi_probe,
+	.remove	= amd_espi_remove,
+};
+module_platform_driver(amd_espi_driver);
+
+MODULE_AUTHOR("Krishnamoorthi M <krishnamoorthi.m@amd.com>");
+MODULE_AUTHOR("Akshata MukundShetty <akshata.mukundshetty@amd.com>");
+MODULE_DESCRIPTION("AMD eSPI Controller Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/espi/espi-amd.h b/drivers/espi/espi-amd.h
new file mode 100644
index 000000000000..3c514e005aa2
--- /dev/null
+++ b/drivers/espi/espi-amd.h
@@ -0,0 +1,126 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * AMD eSPI Controller Driver - internal definitions
+ * Copyright (c) 2026, Advanced Micro Devices, Inc.
+ */
+#ifndef _ESPI_AMD_H_
+#define _ESPI_AMD_H_
+
+#include <linux/bits.h>
+#include <linux/types.h>
+
+#define AMD_ESPI_DN_TXHDR_REG0			0x00
+#define AMD_ESPI_DN_TXHDR_REG1			0x04
+#define AMD_ESPI_DN_TXHDR_REG2			0x08
+#define AMD_ESPI_DN_TXDATA_REG0			0x0C
+#define AMD_ESPI_MASTER_CAP_REG			0x2C
+#define AMD_ESPI_GLOBAL_CNTRL_REG0		0x30
+#define AMD_ESPI_GLOBAL_CNTRL_REG1		0x34
+#define AMD_ESPI_SLAVE0_CONFIG_REG		0x68
+#define AMD_ESPI_SLAVE0_INT_EN_REG		0x6C
+#define AMD_ESPI_SLAVE0_INT_STS_REG		0x70
+#define AMD_ESPI_SLAVE0_RX_VW_REG		0x9C
+#define AMD_ESPI_SLAVE0_VW_MISC_CNTRL_REG	0xA8
+
+/*
+ * SLAVE0_CONFIG (0x68) host-side link fields. The negotiated bus clock and
+ * I/O mode use AMD's own encoding (clock 000=16.6/001=33/010=66 MHz).
+ */
+#define AMD_ESPI_SLAVE0_CFG_CLK_FREQ		GENMASK(27, 25)
+#define AMD_ESPI_SLAVE0_CFG_IO_MODE		GENMASK(29, 28)
+#define AMD_ESPI_SLAVE0_CFG_ALERT_MODE		BIT(30)
+#define AMD_ESPI_SLAVE0_CFG_CRC_EN		BIT(31)
+#define AMD_ESPI_SLAVE0_CLK_FREQ_16MHZ		0x0
+#define AMD_ESPI_SLAVE0_CLK_FREQ_33MHZ		0x1
+#define AMD_ESPI_SLAVE0_CLK_FREQ_66MHZ		0x2
+#define AMD_ESPI_SLAVE0_IO_MODE_SINGLE		0x0
+#define AMD_ESPI_SLAVE0_IO_MODE_DUAL		0x1
+#define AMD_ESPI_SLAVE0_IO_MODE_QUAD		0x2
+
+/* GLOBAL_CNTRL_REG0 (0x30) */
+#define AMD_ESPI_GLOBAL0_WDG_EN			BIT(0)
+#define AMD_ESPI_GLOBAL0_WAIT_CHK_EN		BIT(1)
+#define AMD_ESPI_GLOBAL0_WAIT_STATE		GENMASK(29, 23)
+#define AMD_ESPI_WAIT_STATE_CNT			0x3f
+
+/* GLOBAL_CNTRL_REG1 (0x34) */
+#define AMD_ESPI_ERR_INT_MAP			GENMASK(12, 8)
+#define AMD_ESPI_RGCMD_INT_MAP			GENMASK(17, 13)
+#define AMD_ESPI_INT_MAP_SMI			0x1f
+#define AMD_ESPI_BUS_MASTER_EN			BIT(1)
+#define AMD_ESPI_VW_REQ_EN			BIT(21)
+
+/* SLAVE0_INT_EN (0x6C): error [19:0] and register-command [31:24] enables */
+#define AMD_ESPI_ALL_ERR_INT			GENMASK(19, 0)
+#define AMD_ESPI_REG_CMD_INT			GENMASK(31, 24)
+
+/* SLAVE0_RX_VW_REG (0x9C): clears stale VW status [31:8], preserves IRQ [7:0]. */
+#define AMD_ESPI_SLAVE0_RX_VW_INIT_MASK		0xffff6f00
+
+/*
+ * DN_TXHDR_REG0 fields. The 16-bit target register address occupies
+ * [23:8]; the eSPI wire order places the address MSB first, so the high
+ * byte goes in HDATA0 [15:8] and the low byte in HDATA1 [23:16].
+ */
+#define AMD_ESPI_TXHDR0_CMD_TYPE		GENMASK(2, 0)
+#define AMD_ESPI_TXHDR0_CMD_STATUS		BIT(3)
+#define AMD_ESPI_TXHDR0_HDATA0			GENMASK(15, 8)
+#define AMD_ESPI_TXHDR0_HDATA1			GENMASK(23, 16)
+
+/* SLAVE0_INT_STS (0x70) status and interrupt bits */
+#define AMD_ESPI_BUS_ERR_INT			BIT(0)
+#define AMD_ESPI_WAIT_TIMEOUT_INT		BIT(1)
+#define AMD_ESPI_CRC_ERR_INT			BIT(2)
+#define AMD_ESPI_NO_RESP_INT			BIT(4)
+#define AMD_ESPI_FATAL_ERR_INT			BIT(5)
+#define AMD_ESPI_NON_FATAL_ERR_INT		BIT(6)
+#define AMD_ESPI_INVALID_RESP_CODE_INT		BIT(7)
+#define AMD_ESPI_INVALID_CYCLE_TYPE_INT		BIT(8)
+#define AMD_ESPI_UNSUCCESS_CPL_RECV_INT		BIT(9)
+#define AMD_ESPI_ILLEGAL_RESP_TAG_INT		BIT(10)
+#define AMD_ESPI_ILLEGAL_RESP_LEN_INT		BIT(11)
+#define AMD_ESPI_RX_OOB_OVERFLOW_INT		BIT(12)
+#define AMD_ESPI_RX_PC_MSG_OVERFLOW_INT		BIT(13)
+#define AMD_ESPI_RX_FLASH_MSG_OVERFLOW_INT	BIT(14)
+#define AMD_ESPI_PROTOCOL_ERR_INT		BIT(15)
+#define AMD_ESPI_DNCMD_INT			BIT(28)
+#define AMD_ESPI_INTR_MASK			GENMASK(31, 0)
+
+#define AMD_ESPI_ERR_INT_MASK	\
+	(AMD_ESPI_BUS_ERR_INT | AMD_ESPI_WAIT_TIMEOUT_INT | \
+	 AMD_ESPI_CRC_ERR_INT | AMD_ESPI_NO_RESP_INT | \
+	 AMD_ESPI_FATAL_ERR_INT | AMD_ESPI_NON_FATAL_ERR_INT | \
+	 AMD_ESPI_INVALID_RESP_CODE_INT | AMD_ESPI_INVALID_CYCLE_TYPE_INT | \
+	 AMD_ESPI_UNSUCCESS_CPL_RECV_INT | AMD_ESPI_ILLEGAL_RESP_TAG_INT | \
+	 AMD_ESPI_ILLEGAL_RESP_LEN_INT | AMD_ESPI_RX_OOB_OVERFLOW_INT | \
+	 AMD_ESPI_RX_PC_MSG_OVERFLOW_INT | AMD_ESPI_RX_FLASH_MSG_OVERFLOW_INT | \
+	 AMD_ESPI_PROTOCOL_ERR_INT)
+
+#define AMD_ESPI_MSG_DELAY_MIN_US		50
+#define AMD_ESPI_RESP_MAX_TIMEOUT_US		200000	/* 200 ms */
+
+#define AMD_ESPI_CAP_FLASH_SUPPORT		BIT(0)
+#define AMD_ESPI_CAP_OOB_SUPPORT		BIT(1)
+#define AMD_ESPI_CAP_VW_SUPPORT			BIT(2)
+#define AMD_ESPI_CAP_PR_SUPPORT			BIT(3)
+#define AMD_ESPI_CAP_FLASH_MAX_SIZE		GENMASK(9, 7)
+#define AMD_ESPI_CAP_OOB_MAX_SIZE		GENMASK(12, 10)
+#define AMD_ESPI_CAP_VW_MAX_SIZE		GENMASK(18, 13)
+#define AMD_ESPI_CAP_PR_MAX_SIZE		GENMASK(21, 19)
+#define AMD_ESPI_CAP_SLAVE_NUM			GENMASK(24, 22)
+#define AMD_ESPI_CAP_CLK_FREQ			GENMASK(27, 25)
+#define AMD_ESPI_CAP_IO_MODE			GENMASK(29, 28)
+#define AMD_ESPI_CAP_ALERT_MODE			BIT(30)
+#define AMD_ESPI_CAP_CRC_CHECK			BIT(31)
+
+enum amd_espi_versions {
+	AMD_ESPI_V1 = 1,
+};
+
+struct amd_espi_data {
+	void __iomem *base;
+	struct device *dev;
+	enum amd_espi_versions version;
+};
+
+#endif /* _ESPI_AMD_H_ */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [RFC PATCH 0/4] espi: introduce eSPI bus framework
  2026-08-04 11:52 [RFC PATCH 0/4] espi: introduce eSPI bus framework Krishnamoorthi M
                   ` (3 preceding siblings ...)
  2026-08-04 11:52 ` [RFC PATCH 4/4] espi: amd: add AMD eSPI controller driver Krishnamoorthi M
@ 2026-08-04 12:18 ` Greg KH
  2026-08-04 13:26   ` Greg KH
                     ` (2 more replies)
  4 siblings, 3 replies; 13+ messages in thread
From: Greg KH @ 2026-08-04 12:18 UTC (permalink / raw)
  To: Krishnamoorthi M
  Cc: linux-kernel, broonie, linux-spi, akshata.mukundshetty, bleung,
	groeck, chrome-platform, corbet, linux-doc, skhan, andrew,
	linux-aspeed, openbmc

On Tue, Aug 04, 2026 at 05:22:55PM +0530, Krishnamoorthi M wrote:
> Feedback Requested
> ==================
> 
>   1. We chose a dedicated bus_type for the reasons described above
>      (capability negotiation, four independent channels, asynchronous
>      ALERT#). Does the community agree this is the right direction, or
>      is there a strong preference to extend the SPI subsystem instead?

That's up to the SPI maintainers and developers...

>   2. Is the blocking notifier chain the right mechanism for event
>      delivery to slave drivers?

Notifier chains are almost never the correct solution, especially for
real data you wish to send to devices/drivers.  Just use a real
callback function you have to register for, and a workqueue, or
something like that.  Ideally just use the process context of the thread
that created the data in the first place, why can't something simple
work like that?

>   3. Any concerns with the ops table design or the -EOPNOTSUPP fallback?
>   4. Naming and structure of the public API in include/linux/espi/espi.h.

What specifically are you asking for for this?  Do you have userspace
code you want to integrate, if so, does it work with this?  And where
does it live?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC PATCH 0/4] espi: introduce eSPI bus framework
  2026-08-04 12:18 ` [RFC PATCH 0/4] espi: introduce eSPI bus framework Greg KH
@ 2026-08-04 13:26   ` Greg KH
  2026-08-05  0:42   ` Andrew Jeffery
  2026-08-05 10:14   ` M, Krishnamoorthi
  2 siblings, 0 replies; 13+ messages in thread
From: Greg KH @ 2026-08-04 13:26 UTC (permalink / raw)
  To: Krishnamoorthi M
  Cc: linux-kernel, broonie, linux-spi, akshata.mukundshetty, bleung,
	groeck, chrome-platform, corbet, linux-doc, skhan, andrew,
	linux-aspeed, openbmc

On Tue, Aug 04, 2026 at 02:18:42PM +0200, Greg KH wrote:
> On Tue, Aug 04, 2026 at 05:22:55PM +0530, Krishnamoorthi M wrote:
> >   4. Naming and structure of the public API in include/linux/espi/espi.h.
> 
> What specifically are you asking for for this?  Do you have userspace
> code you want to integrate, if so, does it work with this?  And where
> does it live?

Oops, sorry, I thought this was uapi stuff, nevermind...

greg "i need more coffee" k-h

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC PATCH 3/4] Documentation: espi: add subsystem overview and MAINTAINERS entry
  2026-08-04 11:52 ` [RFC PATCH 3/4] Documentation: espi: add subsystem overview and MAINTAINERS entry Krishnamoorthi M
@ 2026-08-04 16:39   ` Randy Dunlap
  2026-08-04 19:14     ` M, Krishnamoorthi
  0 siblings, 1 reply; 13+ messages in thread
From: Randy Dunlap @ 2026-08-04 16:39 UTC (permalink / raw)
  To: Krishnamoorthi M, linux-kernel
  Cc: gregkh, broonie, linux-spi, akshata.mukundshetty, bleung, groeck,
	chrome-platform, corbet, linux-doc, skhan, andrew, linux-aspeed,
	openbmc



On 8/4/26 4:52 AM, Krishnamoorthi M wrote:
> Add a driver-api overview of the eSPI subsystem and a MAINTAINERS entry
> covering the subsystem files.
> 
> The document describes the architecture, how to write a controller driver
> and a slave driver, the per-channel APIs (Peripheral, Virtual Wire, OOB,
> Flash), the alert mechanism flow, and the event notification model. An
> API Reference section renders kernel-doc from the exported symbols.
> 
> Signed-off-by: Krishnamoorthi M <krishnamoorthi.m@amd.com>
> ---
>  Documentation/driver-api/espi.rst  | 213 +++++++++++++++++++++++++++++
>  Documentation/driver-api/index.rst |   1 +
>  MAINTAINERS                        |   8 ++
>  3 files changed, 222 insertions(+)
>  create mode 100644 Documentation/driver-api/espi.rst
> 
> diff --git a/Documentation/driver-api/espi.rst b/Documentation/driver-api/espi.rst
> new file mode 100644
> index 000000000000..60a3187edb05
> --- /dev/null
> +++ b/Documentation/driver-api/espi.rst
> @@ -0,0 +1,213 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +===========================================
> +eSPI (Enhanced Serial Peripheral Interface)
> +===========================================
> +
> +Introduction
> +============
> +
> +eSPI is a bus defined by Intel that replaces the legacy LPC bus. Unlike
> +SPI it is a structured, capability-negotiated, message-oriented protocol
> +with four logically independent channels (Peripheral, Virtual Wire, OOB,
> +Flash) over a shared physical link, and asynchronous target-to-controller
> +events, so it is modelled as its own bus type rather than an extension of
> +the SPI subsystem.
> +
> +Architecture
> +============
> +
> +* ``struct espi_controller`` - host controller, created with
> +  espi_controller_alloc() and registered with espi_controller_register().
> +  It is not itself a device on espi_bus_type.
> +* ``struct espi_device`` - a target on the bus, matched to a
> +  ``struct espi_driver`` via its modalias.
> +* ``struct espi_controller_ops`` - the optional hardware-op table; the
> +  channel API returns -EOPNOTSUPP for ops a controller does not provide.
> +
> +Writing a controller driver
> +===========================
> +
> +A controller driver allocates and registers a controller from its
> +``probe()`` function::
> +
> +    ctrl = espi_controller_alloc(&pdev->dev, sizeof(*priv));
> +    if (IS_ERR(ctrl))
> +        return PTR_ERR(ctrl);
> +
> +    priv = espi_controller_get_devdata(ctrl);
> +    ctrl->ops = &my_espi_ops;
> +    ctrl->max_targets = 1;
> +
> +    /* populate ctrl->caps from hardware capability registers */
> +    ctrl->caps.supported_channels = ESPI_CHANNEL_ALL;
> +    ctrl->caps.max_freq_mhz       = 33;
> +    ctrl->caps.io_mode            = ESPI_IO_MODE_SINGLE;
> +
> +    ret = espi_controller_register(ctrl);
> +    if (ret)
> +        goto err_put;
> +
> +After registration the controller calls espi_new_device() for each
> +target enumerated from firmware (ACPI or device tree)::
> +
> +    struct espi_board_info info = {
> +        .type = "my-ec",
> +        .cs   = 0,
> +    };
> +    edev = espi_new_device(ctrl, &info);
> +
> +On removal::
> +
> +    espi_remove_device(edev);
> +    espi_controller_unregister(ctrl);
> +    espi_controller_put(ctrl);
> +
> +Writing a slave driver
> +======================
> +
> +A slave driver declares a device ID table and a ``struct espi_driver``::
> +
> +    static const struct espi_device_id my_ec_ids[] = {
> +        { "my-ec", 0 },
> +        { }
> +    };
> +    MODULE_DEVICE_TABLE(espi, my_ec_ids);
> +
> +    static int my_ec_probe(struct espi_device *edev)
> +    {
> +        /* register for hardware events */
> +        nb->notifier_call = my_ec_event;
> +        espi_register_notifier(edev->ctrl, nb);
> +        return 0;
> +    }
> +
> +    static void my_ec_remove(struct espi_device *edev)
> +    {
> +        espi_unregister_notifier(edev->ctrl, nb);
> +    }
> +
> +    static struct espi_driver my_ec_driver = {
> +        .driver   = { .name = "my-ec" },
> +        .id_table = my_ec_ids,
> +        .probe    = my_ec_probe,
> +        .remove   = my_ec_remove,
> +    };
> +    module_espi_driver(my_ec_driver);
> +
> +Channel-independent commands
> +============================
> +
> +espi_get_configuration(), espi_set_configuration(), espi_inband_reset()
> +and espi_get_status(). GET_STATUS is optional: controllers whose hardware
> +does not implement the wire command leave .get_status unset.
> +
> +Capability negotiation and channel management
> +=============================================
> +
> +At boot the controller driver reads the target's capability registers via
> +espi_get_configuration(), negotiates link parameters (I/O mode, clock
> +frequency, CRC) via espi_set_configuration(), then enables each channel
> +with espi_enable_channel(). espi_channel_is_enabled() may be called at
> +any time to query the current state. Channels may be disabled individually
> +with espi_disable_channel(), for example before an in-band reset.
> +
> +Channel APIs
> +============
> +
> +Peripheral channel
> +------------------
> +
> +Carries I/O and memory cycles between the host and target endpoints.
> +
> +* espi_periph_io_read() / espi_periph_io_write() — 16-bit I/O port
> +  access; ``width`` is the access size in bytes (1, 2, or 4).
> +* espi_periph_mem_read() / espi_periph_mem_write() — 32-bit memory
> +  mapped access.
> +
> +Virtual Wire channel
> +--------------------
> +
> +Carries logical signal state (power sequencing, SMI#, SCI#, IRQs) as
> +indexed wire groups. Each group carries up to four wire values with
> +individual valid bits.
> +
> +* espi_vwire_get() — read a wire group from the target.
> +* espi_vwire_put() — send a PUT_VIRTUAL_WIRE command to the target.
> +  Named after the eSPI PUT_VW wire command, not a reference-count
> +  release.
> +
> +Wire changes from the target generate an ``ESPI_EVENT_VWIRE_CHANGED``
> +event delivered through the notifier chain.
> +
> +OOB channel
> +-----------
> +
> +Tunnels SMBus/I2C messages between the host and target out-of-band
> +processor (BMC, EC). Messages are exchanged as opaque byte buffers with
> +a tag field for matching requests to responses.
> +
> +* espi_oob_send() / espi_oob_recv()
> +
> +Incoming OOB messages generate an ``ESPI_EVENT_OOB_RECEIVED`` event.
> +
> +Flash Access channel
> +--------------------
> +
> +Provides access to a SPI flash device attached to the target. The target
> +acts as a proxy for flash read, write, and erase operations.
> +
> +* espi_flash_read() / espi_flash_write() / espi_flash_erase()
> +
> +Alert mechanism
> +===============
> +
> +When the target has upstream data pending it asserts ``ALERT#``. The
> +controller's hard-IRQ handler acknowledges the interrupt and defers
> +processing to a threaded IRQ or workqueue. From that process context the
> +controller driver calls espi_handle_alert(), which acquires the
> +controller lock and dispatches to ``ops->handle_alert``. The hardware
> +callback reads the target's status register (GET_STATUS), identifies the
> +pending channel, and calls espi_notify_event() to deliver the appropriate
> +``ESPI_EVENT_*`` to all registered slave driver notifiers::
> +
> +    ALERT# asserted by target
> +          |
> +          v
> +    hard-IRQ handler (controller driver)
> +          |
> +          v
> +    threaded IRQ / workqueue
> +          |
> +          v
> +    espi_handle_alert(ctrl)          [espi-core.c]
> +          |
> +          v
> +    ops->handle_alert(ctrl)          [controller driver]
> +          | reads GET_STATUS, decodes channel
> +          v
> +    espi_notify_event(ctrl, &event)  [espi-slave.c]
> +          |
> +          v
> +    slave driver notifier callback
> +
> +espi_handle_alert() must always be called from process context; it must
> +never be called from a hard-IRQ handler.
> +
> +Events and concurrency
> +======================
> +
> +Hardware events (Virtual Wire changes, OOB messages, Peripheral channel
> +completions, channel state changes) are delivered through a per-controller
> +blocking notifier chain (espi_register_notifier()/espi_notify_event()).
> +Callbacks run in process context; controllers deliver events from a
> +threaded IRQ or workqueue, never from hardirq and never while holding the
> +controller lock.
> +
> +API Reference
> +=============
> +
> +.. kernel-doc:: include/linux/espi/espi.h
> +

Describe each of these:

WARNING: ../include/linux/espi/espi.h:176 struct member 'setup' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'cleanup' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'get_configuration' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'set_configuration' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'inband_reset' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'get_status' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'enable_channel' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'disable_channel' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'periph_io_read' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'periph_io_write' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'periph_mem_read' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'periph_mem_write' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'vwire_get' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'vwire_put' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'oob_send' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'oob_recv' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'flash_read' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'flash_write' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'flash_erase' not described in 'espi_controller_ops'
WARNING: ../include/linux/espi/espi.h:176 struct member 'handle_alert' not described in 'espi_controller_ops'


or don't use "/**" for that struct's comment header.

> +.. kernel-doc:: drivers/espi/espi-slave.c
> +   :export:


-- 
~Randy


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC PATCH 3/4] Documentation: espi: add subsystem overview and MAINTAINERS entry
  2026-08-04 16:39   ` Randy Dunlap
@ 2026-08-04 19:14     ` M, Krishnamoorthi
  0 siblings, 0 replies; 13+ messages in thread
From: M, Krishnamoorthi @ 2026-08-04 19:14 UTC (permalink / raw)
  To: Randy Dunlap, linux-kernel
  Cc: gregkh, broonie, linux-spi, akshata.mukundshetty, bleung, groeck,
	chrome-platform, corbet, linux-doc, skhan, andrew, linux-aspeed,
	openbmc



On 8/4/2026 10:09 PM, Randy Dunlap wrote:
> 
> 
> On 8/4/26 4:52 AM, Krishnamoorthi M wrote:
>> Add a driver-api overview of the eSPI subsystem and a MAINTAINERS entry
>> covering the subsystem files.
>>
>> The document describes the architecture, how to write a controller driver
>> and a slave driver, the per-channel APIs (Peripheral, Virtual Wire, OOB,
>> Flash), the alert mechanism flow, and the event notification model. An
>> API Reference section renders kernel-doc from the exported symbols.
>>
>> Signed-off-by: Krishnamoorthi M <krishnamoorthi.m@amd.com>
>> ---
>>   Documentation/driver-api/espi.rst  | 213 +++++++++++++++++++++++++++++
>>   Documentation/driver-api/index.rst |   1 +
>>   MAINTAINERS                        |   8 ++
>>   3 files changed, 222 insertions(+)
>>   create mode 100644 Documentation/driver-api/espi.rst
>>
>> diff --git a/Documentation/driver-api/espi.rst b/Documentation/driver-api/espi.rst
>> new file mode 100644
>> index 000000000000..60a3187edb05
>> --- /dev/null
>> +++ b/Documentation/driver-api/espi.rst
>> @@ -0,0 +1,213 @@
>> +.. SPDX-License-Identifier: GPL-2.0-or-later
>> +
>> +===========================================
>> +eSPI (Enhanced Serial Peripheral Interface)
>> +===========================================
>> +
>> +Introduction
>> +============
>> +
>> +eSPI is a bus defined by Intel that replaces the legacy LPC bus. Unlike
>> +SPI it is a structured, capability-negotiated, message-oriented protocol
>> +with four logically independent channels (Peripheral, Virtual Wire, OOB,
>> +Flash) over a shared physical link, and asynchronous target-to-controller
>> +events, so it is modelled as its own bus type rather than an extension of
>> +the SPI subsystem.
>> +
>> +Architecture
>> +============
>> +
>> +* ``struct espi_controller`` - host controller, created with
>> +  espi_controller_alloc() and registered with espi_controller_register().
>> +  It is not itself a device on espi_bus_type.
>> +* ``struct espi_device`` - a target on the bus, matched to a
>> +  ``struct espi_driver`` via its modalias.
>> +* ``struct espi_controller_ops`` - the optional hardware-op table; the
>> +  channel API returns -EOPNOTSUPP for ops a controller does not provide.
>> +
>> +Writing a controller driver
>> +===========================
>> +
>> +A controller driver allocates and registers a controller from its
>> +``probe()`` function::
>> +
>> +    ctrl = espi_controller_alloc(&pdev->dev, sizeof(*priv));
>> +    if (IS_ERR(ctrl))
>> +        return PTR_ERR(ctrl);
>> +
>> +    priv = espi_controller_get_devdata(ctrl);
>> +    ctrl->ops = &my_espi_ops;
>> +    ctrl->max_targets = 1;
>> +
>> +    /* populate ctrl->caps from hardware capability registers */
>> +    ctrl->caps.supported_channels = ESPI_CHANNEL_ALL;
>> +    ctrl->caps.max_freq_mhz       = 33;
>> +    ctrl->caps.io_mode            = ESPI_IO_MODE_SINGLE;
>> +
>> +    ret = espi_controller_register(ctrl);
>> +    if (ret)
>> +        goto err_put;
>> +
>> +After registration the controller calls espi_new_device() for each
>> +target enumerated from firmware (ACPI or device tree)::
>> +
>> +    struct espi_board_info info = {
>> +        .type = "my-ec",
>> +        .cs   = 0,
>> +    };
>> +    edev = espi_new_device(ctrl, &info);
>> +
>> +On removal::
>> +
>> +    espi_remove_device(edev);
>> +    espi_controller_unregister(ctrl);
>> +    espi_controller_put(ctrl);
>> +
>> +Writing a slave driver
>> +======================
>> +
>> +A slave driver declares a device ID table and a ``struct espi_driver``::
>> +
>> +    static const struct espi_device_id my_ec_ids[] = {
>> +        { "my-ec", 0 },
>> +        { }
>> +    };
>> +    MODULE_DEVICE_TABLE(espi, my_ec_ids);
>> +
>> +    static int my_ec_probe(struct espi_device *edev)
>> +    {
>> +        /* register for hardware events */
>> +        nb->notifier_call = my_ec_event;
>> +        espi_register_notifier(edev->ctrl, nb);
>> +        return 0;
>> +    }
>> +
>> +    static void my_ec_remove(struct espi_device *edev)
>> +    {
>> +        espi_unregister_notifier(edev->ctrl, nb);
>> +    }
>> +
>> +    static struct espi_driver my_ec_driver = {
>> +        .driver   = { .name = "my-ec" },
>> +        .id_table = my_ec_ids,
>> +        .probe    = my_ec_probe,
>> +        .remove   = my_ec_remove,
>> +    };
>> +    module_espi_driver(my_ec_driver);
>> +
>> +Channel-independent commands
>> +============================
>> +
>> +espi_get_configuration(), espi_set_configuration(), espi_inband_reset()
>> +and espi_get_status(). GET_STATUS is optional: controllers whose hardware
>> +does not implement the wire command leave .get_status unset.
>> +
>> +Capability negotiation and channel management
>> +=============================================
>> +
>> +At boot the controller driver reads the target's capability registers via
>> +espi_get_configuration(), negotiates link parameters (I/O mode, clock
>> +frequency, CRC) via espi_set_configuration(), then enables each channel
>> +with espi_enable_channel(). espi_channel_is_enabled() may be called at
>> +any time to query the current state. Channels may be disabled individually
>> +with espi_disable_channel(), for example before an in-band reset.
>> +
>> +Channel APIs
>> +============
>> +
>> +Peripheral channel
>> +------------------
>> +
>> +Carries I/O and memory cycles between the host and target endpoints.
>> +
>> +* espi_periph_io_read() / espi_periph_io_write() — 16-bit I/O port
>> +  access; ``width`` is the access size in bytes (1, 2, or 4).
>> +* espi_periph_mem_read() / espi_periph_mem_write() — 32-bit memory
>> +  mapped access.
>> +
>> +Virtual Wire channel
>> +--------------------
>> +
>> +Carries logical signal state (power sequencing, SMI#, SCI#, IRQs) as
>> +indexed wire groups. Each group carries up to four wire values with
>> +individual valid bits.
>> +
>> +* espi_vwire_get() — read a wire group from the target.
>> +* espi_vwire_put() — send a PUT_VIRTUAL_WIRE command to the target.
>> +  Named after the eSPI PUT_VW wire command, not a reference-count
>> +  release.
>> +
>> +Wire changes from the target generate an ``ESPI_EVENT_VWIRE_CHANGED``
>> +event delivered through the notifier chain.
>> +
>> +OOB channel
>> +-----------
>> +
>> +Tunnels SMBus/I2C messages between the host and target out-of-band
>> +processor (BMC, EC). Messages are exchanged as opaque byte buffers with
>> +a tag field for matching requests to responses.
>> +
>> +* espi_oob_send() / espi_oob_recv()
>> +
>> +Incoming OOB messages generate an ``ESPI_EVENT_OOB_RECEIVED`` event.
>> +
>> +Flash Access channel
>> +--------------------
>> +
>> +Provides access to a SPI flash device attached to the target. The target
>> +acts as a proxy for flash read, write, and erase operations.
>> +
>> +* espi_flash_read() / espi_flash_write() / espi_flash_erase()
>> +
>> +Alert mechanism
>> +===============
>> +
>> +When the target has upstream data pending it asserts ``ALERT#``. The
>> +controller's hard-IRQ handler acknowledges the interrupt and defers
>> +processing to a threaded IRQ or workqueue. From that process context the
>> +controller driver calls espi_handle_alert(), which acquires the
>> +controller lock and dispatches to ``ops->handle_alert``. The hardware
>> +callback reads the target's status register (GET_STATUS), identifies the
>> +pending channel, and calls espi_notify_event() to deliver the appropriate
>> +``ESPI_EVENT_*`` to all registered slave driver notifiers::
>> +
>> +    ALERT# asserted by target
>> +          |
>> +          v
>> +    hard-IRQ handler (controller driver)
>> +          |
>> +          v
>> +    threaded IRQ / workqueue
>> +          |
>> +          v
>> +    espi_handle_alert(ctrl)          [espi-core.c]
>> +          |
>> +          v
>> +    ops->handle_alert(ctrl)          [controller driver]
>> +          | reads GET_STATUS, decodes channel
>> +          v
>> +    espi_notify_event(ctrl, &event)  [espi-slave.c]
>> +          |
>> +          v
>> +    slave driver notifier callback
>> +
>> +espi_handle_alert() must always be called from process context; it must
>> +never be called from a hard-IRQ handler.
>> +
>> +Events and concurrency
>> +======================
>> +
>> +Hardware events (Virtual Wire changes, OOB messages, Peripheral channel
>> +completions, channel state changes) are delivered through a per-controller
>> +blocking notifier chain (espi_register_notifier()/espi_notify_event()).
>> +Callbacks run in process context; controllers deliver events from a
>> +threaded IRQ or workqueue, never from hardirq and never while holding the
>> +controller lock.
>> +
>> +API Reference
>> +=============
>> +
>> +.. kernel-doc:: include/linux/espi/espi.h
>> +
> 
> Describe each of these:
> 
> WARNING: ../include/linux/espi/espi.h:176 struct member 'setup' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'cleanup' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'get_configuration' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'set_configuration' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'inband_reset' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'get_status' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'enable_channel' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'disable_channel' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'periph_io_read' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'periph_io_write' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'periph_mem_read' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'periph_mem_write' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'vwire_get' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'vwire_put' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'oob_send' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'oob_recv' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'flash_read' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'flash_write' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'flash_erase' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'handle_alert' not described in 'espi_controller_ops'
> 
> 
> or don't use "/**" for that struct's comment header.

Noted. Will add descriptions for all struct espi_controller_ops members 
in v2.

Thanks,
Krishna
> 
>> +.. kernel-doc:: drivers/espi/espi-slave.c
>> +   :export:
> 
> 


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC PATCH 0/4] espi: introduce eSPI bus framework
  2026-08-04 12:18 ` [RFC PATCH 0/4] espi: introduce eSPI bus framework Greg KH
  2026-08-04 13:26   ` Greg KH
@ 2026-08-05  0:42   ` Andrew Jeffery
  2026-08-05 18:35     ` M, Krishnamoorthi
  2026-08-05 10:14   ` M, Krishnamoorthi
  2 siblings, 1 reply; 13+ messages in thread
From: Andrew Jeffery @ 2026-08-05  0:42 UTC (permalink / raw)
  To: Greg KH, Krishnamoorthi M, aspeedyh
  Cc: linux-kernel, broonie, linux-spi, akshata.mukundshetty, bleung,
	groeck, chrome-platform, corbet, linux-doc, skhan, linux-aspeed,
	openbmc

Hi Greg, Krishnamoorthi,

YH Chung has been working on eSPI support for ASPEED's BMC SoCs, so
I've included them in the To line.

On Tue, 2026-08-04 at 14:18 +0200, Greg KH wrote:
> On Tue, Aug 04, 2026 at 05:22:55PM +0530, Krishnamoorthi M wrote:
> > Feedback Requested
> > ==================
> > 
> >   1. We chose a dedicated bus_type for the reasons described above
> >      (capability negotiation, four independent channels, asynchronous
> >      ALERT#). Does the community agree this is the right direction, or
> >      is there a strong preference to extend the SPI subsystem instead?
> 
> That's up to the SPI maintainers and developers...

There's concurrent discussion from YH regarding device-side eSPI
support in the thread ending here:

https://lore.kernel.org/all/KL1PR0601MB4276FA2C6347192CC45826E490ED2@KL1PR0601MB4276.apcprd06.prod.outlook.com/

So far it's arrived at a matching proposal for drivers/espi.

> 
> >   3. Any concerns with the ops table design or the -EOPNOTSUPP fallback?
> >   4. Naming and structure of the public API in include/linux/espi/espi.h.
> 
> What specifically are you asking for for this?  Do you have userspace
> code you want to integrate, if so, does it work with this?  And where
> does it live?

I've seen your follow-up realisation Greg, however, regarding
userspace, the thread above suggests that we should be able to back
existing subsystems (GPIO for VW, MCTP for OOB, MTD for some flash
functionality) onto eSPI to minimise eSPI-specific interfaces:

https://lore.kernel.org/all/KL1PR0601MB4276B5BE3B96C18E3A66AD709049A@KL1PR0601MB4276.apcprd06.prod.outlook.com/

That doesn't cover the peripheral channel, as that's dealt with in
hardware on the device side, but for the purpose of the controller the
devices on the peripheral channel should all be driven by the kernel
anyway.

Andrew

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC PATCH 0/4] espi: introduce eSPI bus framework
  2026-08-04 12:18 ` [RFC PATCH 0/4] espi: introduce eSPI bus framework Greg KH
  2026-08-04 13:26   ` Greg KH
  2026-08-05  0:42   ` Andrew Jeffery
@ 2026-08-05 10:14   ` M, Krishnamoorthi
  2 siblings, 0 replies; 13+ messages in thread
From: M, Krishnamoorthi @ 2026-08-05 10:14 UTC (permalink / raw)
  To: Greg KH
  Cc: linux-kernel, broonie, linux-spi, akshata.mukundshetty, bleung,
	groeck, chrome-platform, corbet, linux-doc, skhan, andrew,
	linux-aspeed, openbmc

Hi Greg,

On 8/4/2026 5:48 PM, Greg KH wrote:
> On Tue, Aug 04, 2026 at 05:22:55PM +0530, Krishnamoorthi M wrote:
>> Feedback Requested
>> ==================
>>
>>    1. We chose a dedicated bus_type for the reasons described above
>>       (capability negotiation, four independent channels, asynchronous
>>       ALERT#). Does the community agree this is the right direction, or
>>       is there a strong preference to extend the SPI subsystem instead?
> 
> That's up to the SPI maintainers and developers...
> 
>>    2. Is the blocking notifier chain the right mechanism for event
>>       delivery to slave drivers?
> 
> Notifier chains are almost never the correct solution, especially for
> real data you wish to send to devices/drivers.  Just use a real
> callback function you have to register for, and a workqueue, or
> something like that.  Ideally just use the process context of the thread
> that created the data in the first place, why can't something simple
> work like that?
> 

Thank you for the feedback. The notifier chain was chosen to support 
multiple slave drivers subscribing to events from a single controller. 
However, your concern is valid — it is not the right abstraction here. A 
cleaner approach is a typed per-device event callback on struct espi_driver:

void (*event)(struct espi_device *edev, struct espi_event *event);

When the controller decodes an ALERT#, it identifies the originating 
chip select# and calls the callback only on the driver bound to that 
device — no chain walking, no per-driver filtering, no untyped casts. 
The callback is invoked directly from the threaded IRQ context that 
decoded the event, keeping the delivery path simple as you suggested.

We will rework the event delivery along these lines in v2. Does this 
direction sound acceptable?

Thanks,
Krishna

>>    3. Any concerns with the ops table design or the -EOPNOTSUPP fallback?
>>    4. Naming and structure of the public API in include/linux/espi/espi.h.
> 
> What specifically are you asking for for this?  Do you have userspace
> code you want to integrate, if so, does it work with this?  And where
> does it live?
> 
> thanks,
> 
> greg k-h


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC PATCH 0/4] espi: introduce eSPI bus framework
  2026-08-05  0:42   ` Andrew Jeffery
@ 2026-08-05 18:35     ` M, Krishnamoorthi
  0 siblings, 0 replies; 13+ messages in thread
From: M, Krishnamoorthi @ 2026-08-05 18:35 UTC (permalink / raw)
  To: Andrew Jeffery, Greg KH, aspeedyh
  Cc: linux-kernel, broonie, linux-spi, akshata.mukundshetty, bleung,
	groeck, chrome-platform, corbet, linux-doc, skhan, linux-aspeed,
	openbmc

Hi Andrew,

On 8/5/2026 6:12 AM, Andrew Jeffery wrote:
> Hi Greg, Krishnamoorthi,
> 
> YH Chung has been working on eSPI support for ASPEED's BMC SoCs, so
> I've included them in the To line.
> 
> On Tue, 2026-08-04 at 14:18 +0200, Greg KH wrote:
>> On Tue, Aug 04, 2026 at 05:22:55PM +0530, Krishnamoorthi M wrote:
>>> Feedback Requested
>>> ==================
>>>
>>>    1. We chose a dedicated bus_type for the reasons described above
>>>       (capability negotiation, four independent channels, asynchronous
>>>       ALERT#). Does the community agree this is the right direction, or
>>>       is there a strong preference to extend the SPI subsystem instead?
>>
>> That's up to the SPI maintainers and developers...
> 
> There's concurrent discussion from YH regarding device-side eSPI
> support in the thread ending here:
> 
> https://lore.kernel.org/all/KL1PR0601MB4276FA2C6347192CC45826E490ED2@KL1PR0601MB4276.apcprd06.prod.outlook.com/
> 
> So far it's arrived at a matching proposal for drivers/espi.

Thank you for the introduction and for pointing to YH Chung's work. It 
is encouraging to see concurrent work arriving at a similar framework 
structure for drivers/espi — this gives us more confidence that the 
proposed design is on the right track.

> 
>>
>>>    3. Any concerns with the ops table design or the -EOPNOTSUPP fallback?
>>>    4. Naming and structure of the public API in include/linux/espi/espi.h.
>>
>> What specifically are you asking for for this?  Do you have userspace
>> code you want to integrate, if so, does it work with this?  And where
>> does it live?
> 
> I've seen your follow-up realisation Greg, however, regarding
> userspace, the thread above suggests that we should be able to back
> existing subsystems (GPIO for VW, MCTP for OOB, MTD for some flash
> functionality) onto eSPI to minimise eSPI-specific interfaces:
> 
> https://lore.kernel.org/all/KL1PR0601MB4276B5BE3B96C18E3A66AD709049A@KL1PR0601MB4276.apcprd06.prod.outlook.com/
> 
> That doesn't cover the peripheral channel, as that's dealt with in
> hardware on the device side, but for the purpose of the controller the
> devices on the peripheral channel should all be driven by the kernel
> anyway.
> 

I will go through YH Chung's complete thread on device-side eSPI support 
to understand the full picture. Reusing existing well-established kernel 
subsystems (GPIO for VWire, MCTP for OOB, MTD for Flash) to minimize 
eSPI-specific userspace interfaces is a sound direction.

YH Chung, would you be open to collaborating on the slave-side 
interfaces of the new eSPI framework? Happy to discuss further on the 
list or off-list to align on the design before the next revision.

Thanks,
Krishna

> Andrew


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC PATCH 1/4] espi: add core bus framework
  2026-08-04 11:52 ` [RFC PATCH 1/4] espi: add core " Krishnamoorthi M
@ 2026-08-06 13:31   ` Uwe Kleine-König
  0 siblings, 0 replies; 13+ messages in thread
From: Uwe Kleine-König @ 2026-08-06 13:31 UTC (permalink / raw)
  To: Krishnamoorthi M
  Cc: linux-kernel, gregkh, broonie, linux-spi, akshata.mukundshetty,
	bleung, groeck, chrome-platform, corbet, linux-doc, skhan, andrew,
	linux-aspeed, openbmc

[-- Attachment #1: Type: text/plain, Size: 15381 bytes --]

Hello,

On Tue, Aug 04, 2026 at 05:22:56PM +0530, Krishnamoorthi M wrote:
> diff --git a/drivers/espi/Kconfig b/drivers/espi/Kconfig
> new file mode 100644
> index 000000000000..4411d8336e82
> --- /dev/null
> +++ b/drivers/espi/Kconfig
> @@ -0,0 +1,19 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# eSPI (Enhanced Serial Peripheral Interface) bus configuration
> +#
> +
> +menuconfig ESPI
> +	bool "eSPI (Enhanced Serial Peripheral Interface) bus support"

Can this be tristate instead?

> +	help
> +	  Enhanced Serial Peripheral Interface (eSPI) bus framework.
> +
> [...]
> +const struct bus_type espi_bus_type = {
> +	.name	= "espi",
> +	.match	= espi_bus_match,
> +	.uevent	= espi_bus_uevent,
> +	.probe	= espi_bus_probe,
> +	.remove	= espi_bus_remove,
> +};
> +EXPORT_SYMBOL_GPL(espi_bus_type);

Do you really need this exported?

> +static ssize_t supported_channels_show(struct device *dev,
> +				       struct device_attribute *attr, char *buf)
> +{
> +	struct espi_controller *ctrl = to_espi_controller(dev);
> +
> +	return sysfs_emit(buf, "0x%02x\n", ctrl->caps.supported_channels);
> +}
> +static DEVICE_ATTR_RO(supported_channels);
> +
> +static ssize_t max_freq_mhz_show(struct device *dev,
> +				 struct device_attribute *attr, char *buf)
> +{
> +	struct espi_controller *ctrl = to_espi_controller(dev);
> +
> +	return sysfs_emit(buf, "%u\n", ctrl->caps.max_freq_mhz);

Is the unit here mHz or MHz? Maybe pick a better name that answers this
question.

> +}
> +static DEVICE_ATTR_RO(max_freq_mhz);
> +
> +static ssize_t io_mode_show(struct device *dev,
> +			    struct device_attribute *attr, char *buf)
> +{
> +	struct espi_controller *ctrl = to_espi_controller(dev);
> +	static const char * const modes[] = { "single", "dual", "quad" };
> +	u8 m = ctrl->caps.io_mode;
> +
> +	if (WARN_ON_ONCE(m > ESPI_IO_MODE_QUAD))
> +		return sysfs_emit(buf, "unknown\n");
> +	return sysfs_emit(buf, "%s\n", modes[m]);
> +}
> +static DEVICE_ATTR_RO(io_mode);
> +
> +static ssize_t channel_enabled_show(struct device *dev,
> +				    struct device_attribute *attr, char *buf)
> +{
> +	struct espi_controller *ctrl = to_espi_controller(dev);
> +
> +	return sysfs_emit(buf, "0x%02x\n", READ_ONCE(ctrl->channel_enabled));
> +}
> +static DEVICE_ATTR_RO(channel_enabled);
> +
> +static struct attribute *espi_controller_attrs[] = {
> +	&dev_attr_supported_channels.attr,
> +	&dev_attr_max_freq_mhz.attr,
> +	&dev_attr_io_mode.attr,
> +	&dev_attr_channel_enabled.attr,
> +	NULL,

No , after the list terminator please.

> +};
> +ATTRIBUTE_GROUPS(espi_controller);
> +
> +static void espi_controller_release(struct device *dev)
> +{
> +	struct espi_controller *ctrl = to_espi_controller(dev);
> +
> +	mutex_destroy(&ctrl->lock);
> +	mutex_destroy(&ctrl->device_list_lock);
> +	kfree(ctrl);
> +}
> +
> +static const struct device_type espi_controller_type = {
> +	.groups	 = espi_controller_groups,
> +	.release = espi_controller_release,
> +};
> +
> +struct espi_controller *espi_controller_alloc(struct device *parent,
> +					      unsigned int size)
> +{
> +	struct espi_controller *ctrl;
> +
> +	if (!parent)
> +		return ERR_PTR(-EINVAL);
> +
> +	ctrl = kzalloc(sizeof(*ctrl) + size, GFP_KERNEL);

You might want to align sizeof(*ctrl) to something like
ARCH_DMA_MINALIGN, to ensure that devdata below is aligned
appropriately. Also use size_add() instead of direct arithmetic with
sizes.

> +	if (!ctrl)
> +		return ERR_PTR(-ENOMEM);
> +
> +	device_initialize(&ctrl->dev);
> +	ctrl->dev.parent = parent;
> +	ctrl->dev.type = &espi_controller_type;
> +
> +	mutex_init(&ctrl->lock);
> +	INIT_LIST_HEAD(&ctrl->device_list);
> +	mutex_init(&ctrl->device_list_lock);
> +	BLOCKING_INIT_NOTIFIER_HEAD(&ctrl->notifier_list);
> +
> +	if (size)
> +		espi_controller_set_devdata(ctrl, (void *)ctrl + sizeof(*ctrl));
> +
> +	return ctrl;
> +}
> +EXPORT_SYMBOL_GPL(espi_controller_alloc);
> +
> +int espi_controller_register(struct espi_controller *ctrl)
> +{
> +	int ret;
> +	u32 id;
> +
> +	if (!ctrl || !ctrl->ops)
> +		return -EINVAL;
> +
> +	ret = xa_alloc(&espi_controllers, &id, ctrl, xa_limit_31b,
> +		       GFP_KERNEL);
> +	if (ret)
> +		return ret;
> +
> +	ctrl->bus_num = id;
> +	ret = dev_set_name(&ctrl->dev, "espi%d", ctrl->bus_num);
> +	if (ret)
> +		goto err_erase;
> +
> +	if (ctrl->ops->setup) {
> +		ret = ctrl->ops->setup(ctrl);
> +		if (ret) {
> +			dev_err(&ctrl->dev, "controller setup failed: %d\n", ret);
> +			goto err_erase;
> +		}
> +	}

is ops->setup supposed to be only called in espi_controller_register()?
If yes, why does it exist? The driver specific stuff in it can just be
done before espi_controller_register() is called, can it not?

> +	ret = device_add(&ctrl->dev);
> +	if (ret) {
> +		dev_err(&ctrl->dev, "device_add failed: %d\n", ret);

Better use %pe for error codes.

> +		if (ctrl->ops->cleanup)
> +			ctrl->ops->cleanup(ctrl);
> +		goto err_erase;
> +	}
> +
> +	dev_info(&ctrl->dev, "registered: channels=0x%02x freq=%uMHz\n",
> +		 ctrl->caps.supported_channels, ctrl->caps.max_freq_mhz);

Please degrade that to dev_dbg. We're already have too many messages
during boot that are not really usefull once driver/subsystem debugging
is done.

Also I'd add a space between "%u" and "MHz".

> +	return 0;
> +
> +err_erase:
> +	xa_erase(&espi_controllers, ctrl->bus_num);
> +	ctrl->bus_num = -1;
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(espi_controller_register);
> +
> +void espi_controller_unregister(struct espi_controller *ctrl)
> +{
> +	if (!ctrl)
> +		return;
> +	/*
> +	 * Remove from the lookup table before dropping the device reference,
> +	 * so a concurrent espi_controller_get_by_bus_num() can never take a
> +	 * reference on a controller that is going away.
> +	 */
> +	xa_erase(&espi_controllers, ctrl->bus_num);
> +	if (ctrl->ops && ctrl->ops->cleanup)
> +		ctrl->ops->cleanup(ctrl);
> +	device_unregister(&ctrl->dev);
> +}
> +EXPORT_SYMBOL_GPL(espi_controller_unregister);
> +
> +void espi_controller_put(struct espi_controller *ctrl)
> +{
> +	if (ctrl)
> +		put_device(&ctrl->dev);
> +}
> +EXPORT_SYMBOL_GPL(espi_controller_put);
> +
> +struct espi_controller *espi_controller_get_by_bus_num(int bus_num)
> +{
> +	struct espi_controller *ctrl;
> +
> +	guard(spinlock)(&espi_controllers.xa_lock);
> +	ctrl = xa_load(&espi_controllers, bus_num);
> +	if (ctrl)
> +		get_device(&ctrl->dev);
> +	return ctrl;
> +}
> +EXPORT_SYMBOL_GPL(espi_controller_get_by_bus_num);
> +
> +int espi_get_capabilities(struct espi_controller *ctrl,
> +			  struct espi_capabilities *caps)
> +{
> +	if (!ctrl || !caps)
> +		return -EINVAL;
> +	guard(mutex)(&ctrl->lock);
> +	*caps = ctrl->caps;
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(espi_get_capabilities);
> +
> +bool espi_channel_is_enabled(struct espi_controller *ctrl, u8 channel)
> +{
> +	if (!ctrl || channel >= ESPI_CHANNEL_COUNT)
> +		return false;
> +	guard(mutex)(&ctrl->lock);
> +	return !!(ctrl->channel_enabled & BIT(channel));
> +}
> +EXPORT_SYMBOL_GPL(espi_channel_is_enabled);
> +
> +int espi_get_configuration(struct espi_controller *ctrl,
> +			   u32 slave_reg_addr, u32 *config)
> +{
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->get_configuration)
> +		return -EOPNOTSUPP;
> +	if (!config)
> +		return -EINVAL;
> +	guard(mutex)(&ctrl->lock);
> +	return ctrl->ops->get_configuration(ctrl, slave_reg_addr, config);
> +}
> +EXPORT_SYMBOL_GPL(espi_get_configuration);
> +
> +int espi_set_configuration(struct espi_controller *ctrl,
> +			   u32 slave_reg_addr, u32 config)
> +{
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->set_configuration)
> +		return -EOPNOTSUPP;
> +	guard(mutex)(&ctrl->lock);
> +	return ctrl->ops->set_configuration(ctrl, slave_reg_addr, config);
> +}
> +EXPORT_SYMBOL_GPL(espi_set_configuration);
> +
> +int espi_inband_reset(struct espi_controller *ctrl)
> +{
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->inband_reset)
> +		return -EOPNOTSUPP;
> +	guard(mutex)(&ctrl->lock);
> +	return ctrl->ops->inband_reset(ctrl);
> +}
> +EXPORT_SYMBOL_GPL(espi_inband_reset);
> +
> +int espi_get_status(struct espi_controller *ctrl,
> +		    struct espi_slave_status *status)
> +{
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->get_status)
> +		return -EOPNOTSUPP;
> +	if (!status)
> +		return -EINVAL;
> +	guard(mutex)(&ctrl->lock);
> +	return ctrl->ops->get_status(ctrl, status);
> +}
> +EXPORT_SYMBOL_GPL(espi_get_status);
> +
> +int espi_enable_channel(struct espi_controller *ctrl, u8 channel)
> +{
> +	int ret;
> +
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->enable_channel)
> +		return -EOPNOTSUPP;
> +	if (channel >= ESPI_CHANNEL_COUNT)
> +		return -EINVAL;
> +	guard(mutex)(&ctrl->lock);
> +	ret = ctrl->ops->enable_channel(ctrl, channel);
> +	if (!ret)
> +		ctrl->channel_enabled |= BIT(channel);
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(espi_enable_channel);
> +
> +int espi_disable_channel(struct espi_controller *ctrl, u8 channel)
> +{
> +	int ret;
> +
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->disable_channel)
> +		return -EOPNOTSUPP;
> +	if (channel >= ESPI_CHANNEL_COUNT)
> +		return -EINVAL;
> +	guard(mutex)(&ctrl->lock);
> +	ret = ctrl->ops->disable_channel(ctrl, channel);
> +	if (!ret)
> +		ctrl->channel_enabled &= ~BIT(channel);
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(espi_disable_channel);
> +
> +int espi_periph_io_read(struct espi_controller *ctrl,
> +			u16 port, u8 width, u32 *value)
> +{
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->periph_io_read)
> +		return -EOPNOTSUPP;
> +	guard(mutex)(&ctrl->lock);
> +	return ctrl->ops->periph_io_read(ctrl, port, width, value);
> +}
> +EXPORT_SYMBOL_GPL(espi_periph_io_read);
> +
> +int espi_periph_io_write(struct espi_controller *ctrl,
> +			 u16 port, u8 width, u32 value)
> +{
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->periph_io_write)
> +		return -EOPNOTSUPP;
> +	guard(mutex)(&ctrl->lock);
> +	return ctrl->ops->periph_io_write(ctrl, port, width, value);
> +}
> +EXPORT_SYMBOL_GPL(espi_periph_io_write);
> +
> +int espi_periph_mem_read(struct espi_controller *ctrl,
> +			 u32 addr, void *buf, size_t len)
> +{
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->periph_mem_read)
> +		return -EOPNOTSUPP;
> +	guard(mutex)(&ctrl->lock);
> +	return ctrl->ops->periph_mem_read(ctrl, addr, buf, len);
> +}
> +EXPORT_SYMBOL_GPL(espi_periph_mem_read);
> +
> +int espi_periph_mem_write(struct espi_controller *ctrl,
> +			  u32 addr, const void *buf, size_t len)
> +{
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->periph_mem_write)
> +		return -EOPNOTSUPP;
> +	guard(mutex)(&ctrl->lock);
> +	return ctrl->ops->periph_mem_write(ctrl, addr, buf, len);
> +}
> +EXPORT_SYMBOL_GPL(espi_periph_mem_write);
> +
> +int espi_vwire_get(struct espi_controller *ctrl, u8 index, u8 *value, u8 *valid)
> +{
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->vwire_get)
> +		return -EOPNOTSUPP;
> +	guard(mutex)(&ctrl->lock);
> +	return ctrl->ops->vwire_get(ctrl, index, value, valid);
> +}
> +EXPORT_SYMBOL_GPL(espi_vwire_get);
> +
> +int espi_vwire_put(struct espi_controller *ctrl, u8 index, u8 value, u8 valid)
> +{
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->vwire_put)
> +		return -EOPNOTSUPP;
> +	guard(mutex)(&ctrl->lock);
> +	return ctrl->ops->vwire_put(ctrl, index, value, valid);
> +}
> +EXPORT_SYMBOL_GPL(espi_vwire_put);
> +
> +int espi_oob_send(struct espi_controller *ctrl, const void *buf, size_t len, u8 tag)
> +{
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->oob_send)
> +		return -EOPNOTSUPP;
> +	guard(mutex)(&ctrl->lock);
> +	return ctrl->ops->oob_send(ctrl, buf, len, tag);
> +}
> +EXPORT_SYMBOL_GPL(espi_oob_send);
> +
> +int espi_oob_recv(struct espi_controller *ctrl, void *buf, size_t *len, u8 *tag)
> +{
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->oob_recv)
> +		return -EOPNOTSUPP;
> +	guard(mutex)(&ctrl->lock);
> +	return ctrl->ops->oob_recv(ctrl, buf, len, tag);
> +}
> +EXPORT_SYMBOL_GPL(espi_oob_recv);
> +
> +int espi_flash_read(struct espi_controller *ctrl, u32 offset, void *buf, size_t len)
> +{
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->flash_read)
> +		return -EOPNOTSUPP;
> +	guard(mutex)(&ctrl->lock);
> +	return ctrl->ops->flash_read(ctrl, offset, buf, len);
> +}
> +EXPORT_SYMBOL_GPL(espi_flash_read);
> +
> +int espi_flash_write(struct espi_controller *ctrl, u32 offset, const void *buf, size_t len)
> +{
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->flash_write)
> +		return -EOPNOTSUPP;
> +	guard(mutex)(&ctrl->lock);
> +	return ctrl->ops->flash_write(ctrl, offset, buf, len);
> +}
> +EXPORT_SYMBOL_GPL(espi_flash_write);
> +
> +int espi_flash_erase(struct espi_controller *ctrl, u32 offset, size_t len)
> +{
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->flash_erase)
> +		return -EOPNOTSUPP;
> +	guard(mutex)(&ctrl->lock);
> +	return ctrl->ops->flash_erase(ctrl, offset, len);
> +}
> +EXPORT_SYMBOL_GPL(espi_flash_erase);
> +
> +/*
> + * espi_handle_alert - dispatch a hardware alert to the controller
> + *
> + * Must be called from process context (threaded IRQ or workqueue).
> + *
> + * ctrl->lock is NOT held across ops->handle_alert so that the driver
> + * callback can call espi_notify_event() without deadlocking: notifier
> + * callbacks may in turn call channel APIs that also acquire ctrl->lock.
> + * The driver is responsible for taking ctrl->lock around any register
> + * accesses that need serialisation with the channel API.
> + */
> +int espi_handle_alert(struct espi_controller *ctrl)
> +{
> +	if (!ctrl || !ctrl->ops || !ctrl->ops->handle_alert)
> +		return -EOPNOTSUPP;
> +	return ctrl->ops->handle_alert(ctrl);
> +}
> +EXPORT_SYMBOL_GPL(espi_handle_alert);
> +
> +int __espi_register_driver(struct module *owner, struct espi_driver *drv)
> +{
> +	drv->driver.owner = owner;
> +	drv->driver.bus = &espi_bus_type;
> +	return driver_register(&drv->driver);
> +}
> +EXPORT_SYMBOL_GPL(__espi_register_driver);
> +
> +void espi_unregister_driver(struct espi_driver *drv)
> +{
> +	driver_unregister(&drv->driver);
> +}
> +EXPORT_SYMBOL_GPL(espi_unregister_driver);
> +
> +static int __init espi_init(void)
> +{
> +	int ret = bus_register(&espi_bus_type);
> +
> +	if (ret)
> +		pr_err("failed to register eSPI bus: %d\n", ret);
> +	return ret;
> +}
> +postcore_initcall(espi_init);
> +
> +MODULE_AUTHOR("Krishnamoorthi M <krishnamoorthi.m@amd.com>");
> +MODULE_DESCRIPTION("eSPI core framework");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/espi/espi.h b/include/linux/espi/espi.h
> new file mode 100644
> index 000000000000..a191ddc10cdd
> --- /dev/null
> +++ b/include/linux/espi/espi.h
> @@ -0,0 +1,345 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * eSPI (Enhanced Serial Peripheral Interface) framework
> + *
> + * Copyright (c) 2026, Advanced Micro Devices, Inc.
> + * All Rights Reserved.
> + */
> +#ifndef _LINUX_ESPI_ESPI_H
> +#define _LINUX_ESPI_ESPI_H
> +
> +#include <linux/bits.h>
> +#include <linux/device.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +#include <linux/types.h>
> +#include <linux/mod_devicetable.h>

Please don't include <linux/mod_devicetable.h>. I think you're not even
using a symbol defined by it, so you can just drop it.

> [...]
> +struct espi_device_id {
> +	char name[ESPI_NAME_SIZE];
> +	kernel_ulong_t driver_data;

There is an effort to replace .driver_data by an anonymous union for the
already existing *_device_id. See
https://lore.kernel.org/all/cover.1780048925.git.u.kleine-koenig@baylibre.com/
for details. It would be awesome if you'd do that from the start.

> +};

Best regards
Uwe

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2026-08-06 13:31 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-04 11:52 [RFC PATCH 0/4] espi: introduce eSPI bus framework Krishnamoorthi M
2026-08-04 11:52 ` [RFC PATCH 1/4] espi: add core " Krishnamoorthi M
2026-08-06 13:31   ` Uwe Kleine-König
2026-08-04 11:52 ` [RFC PATCH 2/4] espi: add slave device model and event notification Krishnamoorthi M
2026-08-04 11:52 ` [RFC PATCH 3/4] Documentation: espi: add subsystem overview and MAINTAINERS entry Krishnamoorthi M
2026-08-04 16:39   ` Randy Dunlap
2026-08-04 19:14     ` M, Krishnamoorthi
2026-08-04 11:52 ` [RFC PATCH 4/4] espi: amd: add AMD eSPI controller driver Krishnamoorthi M
2026-08-04 12:18 ` [RFC PATCH 0/4] espi: introduce eSPI bus framework Greg KH
2026-08-04 13:26   ` Greg KH
2026-08-05  0:42   ` Andrew Jeffery
2026-08-05 18:35     ` M, Krishnamoorthi
2026-08-05 10:14   ` M, Krishnamoorthi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox