[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