[RFC PATCH 1/4] espi: add core bus framework
Krishnamoorthi M <[email protected]> Tue, 4 Aug 2026 17:22:56 +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 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 <[email protected]>
Signed-off-by: Akshata MukundShetty <[email protected]>
Signed-off-by: Krishnamoorthi M <[email protected]>
---
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 <[email protected]>");
+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