[RFC PATCH 3/4] Documentation: espi: add subsystem overview and MAINTAINERS entry

Krishnamoorthi M <[email protected]> Tue, 4 Aug 2026 17:22:58 +0530
Newsgroups org.ozlabs.lists.openbmc,dev.linux.lists.chrome-platform,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel,org.kernel.vger.linux-spi,org.ozlabs.lists.linux-aspeed
Message-ID <[email protected]>
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 <[email protected]>
---
 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 <[email protected]>
+L:	[email protected]
+S:	Supported
+F:	Documentation/driver-api/espi.rst
+F:	drivers/espi/
+F:	include/linux/espi/
+
 ET131X NETWORK DRIVER
 M:	Mark Einon <[email protected]>
 S:	Odd Fixes
-- 
2.34.1