From nobody Fri Oct 2 06:58:32 2026 Received: from PH8PR06CU001.outbound.protection.outlook.com (mail-westus3azon11012052.outbound.protection.outlook.com [40.107.209.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 26B2A382F15; Tue, 4 Aug 2026 11:54:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=40.107.209.52 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785844468; cv=fail; b=TlD0w1NSZuNidfVAf3o8QvzxS0mib92I7k1M+iNoig8qagZiUpPSScHLNqTQtq8PtDePSBqpuOsQb2duGEmQsqB/nkhOAbbzcZ/7rF4N6h3noMPBfoxKH004oEy7DqoIwBu2wgyN+Nppxnl+hFWgRz9CQ3wNODxIAPbS73cpr/s= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785844468; c=relaxed/simple; bh=ncP2ZehdcOqVszJoioamoLWElFiVTUEF4SBL12pUyrE=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=p75+GsQFAHPaMle2p5BqkpcMz5HzPGQ092v2rWY6dR9MVQQuIOAWGbathdId6GC1sFgfSZDZlFbs9Cwa+IZ5XRRkbmhJAe/hFydhCXFJ33qz3rAuORH3vLiPa7r4SUR6Ed3UZUs0RglXYwhiuqfuPTiDxz272rd0obZJEECnKPo= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=amd.com; spf=fail smtp.mailfrom=amd.com; dkim=pass (1024-bit key) header.d=amd.com header.i=@amd.com header.b=svVvmWAt; arc=fail smtp.client-ip=40.107.209.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=amd.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=amd.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=amd.com header.i=@amd.com header.b="svVvmWAt" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=cJ5muNxWL7xOJa4MyRfim72TIWjDZH0Biyfc2eFcD7LDmS6Gj8EEOHlF0aU8+PwAknHBEeGBINTknr1xBfvAbTg8pcYCZ6XDtw8Q8gMW39w12Pc4HFT57E4faM3aPte8C3G1OGVzY6Z/r5Ouy+1JpLqdGR6ws3spuEa2wzTtM1nMOgab9qr/UehteBdsaXJFI7bsEoA+5475SDLEQEHfLQzuO7Pna31Bt7Jql1nq4hMnNODoPP5F4mR20R99JdrdNVZS/MW8CdqWdmH+IRRFS6dtyELaofWkKnSYSWM041khMrlgKJZYNOiNUS3+wTIY62tMobFHy1UKZ6dHXUvlaw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=SmoOwZromLXayU0K0V8rtpB/mCueeotKaoJj8v+d72U=; b=WE+pE0QwOfcjYrGWY/9YAMGlDZC6BkxhPO2kvoAtIP9NQuSusj9BxIDWykNWV1HCvQG2FyfMojx6SUDrX8vlZnn8HATCHd8jTuIEz81sLzRFmgCJGcIl+VTZA9Zw//W8DPVEwUfFJ3IgXzXDy7HOmdIzzSGh1RdOIJQ5qrDHmMT5TmjyRPs9+zt4Hkz2pPlSUchPSic+9CKz1ZHipbLF8/0isjOshWybkohL1AlYPf9ZdDFjf35SpKU9v93I4iEl/NXNBMhr4/BJ06TDc0TZGnlwvvqj6Vx28Ql42OCSQ6Mg1FAjD6HJK6wLSRKwlOT0Pt9Ryxtu7cqosDix98W/cA== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass (sender ip is 165.204.84.17) smtp.rcpttodomain=vger.kernel.org smtp.mailfrom=amd.com; dmarc=pass (p=quarantine sp=quarantine pct=100) action=none header.from=amd.com; dkim=none (message not signed); arc=none (0) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=amd.com; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=SmoOwZromLXayU0K0V8rtpB/mCueeotKaoJj8v+d72U=; b=svVvmWAtyevz417Im51/3HRyVHQLON7k7KQGYpPfEhoQ3gPMo00Xx4xqIZkCKhpAJVyPLloManAP+TKpHhJzDWMrbM8YZ9qjqWcD1tZbF//5V6+PutqsPECZDekWj9w40QOIgAhxeIhmNeVWwqyrBRG5oElqpoaaUOnaRDn+Cuk= Received: from CH0PR03CA0270.namprd03.prod.outlook.com (2603:10b6:610:e5::35) by SJ0PR12MB6734.namprd12.prod.outlook.com (2603:10b6:a03:478::16) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.270.18; Tue, 4 Aug 2026 11:54:19 +0000 Received: from CH3PEPF00000010.namprd04.prod.outlook.com (2603:10b6:610:e5:cafe::a6) by CH0PR03CA0270.outlook.office365.com (2603:10b6:610:e5::35) with Microsoft SMTP Server (version=TLS1_3, cipher=TLS_AES_256_GCM_SHA384) id 15.21.292.15 via Frontend Transport; Tue, 4 Aug 2026 11:54:19 +0000 X-MS-Exchange-Authentication-Results: spf=pass (sender IP is 165.204.84.17) smtp.mailfrom=amd.com; dkim=none (message not signed) header.d=none;dmarc=pass action=none header.from=amd.com; Received-SPF: Pass (protection.outlook.com: domain of amd.com designates 165.204.84.17 as permitted sender) receiver=protection.outlook.com; client-ip=165.204.84.17; helo=satlexmb07.amd.com; pr=C Received: from satlexmb07.amd.com (165.204.84.17) by CH3PEPF00000010.mail.protection.outlook.com (10.167.244.41) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.292.8 via Frontend Transport; Tue, 4 Aug 2026 11:54:19 +0000 Received: from airavat.amd.com (10.180.168.240) by satlexmb07.amd.com (10.181.42.216) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.41; Tue, 4 Aug 2026 06:54:15 -0500 From: Krishnamoorthi M To: CC: , , , , , , , , , , , , , Krishnamoorthi M Subject: [RFC PATCH 1/4] espi: add core bus framework Date: Tue, 4 Aug 2026 17:22:56 +0530 Message-ID: <20260804115259.4065638-2-krishnamoorthi.m@amd.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260804115259.4065638-1-krishnamoorthi.m@amd.com> References: <20260804115259.4065638-1-krishnamoorthi.m@amd.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ClientProxiedBy: satlexmb07.amd.com (10.181.42.216) To satlexmb07.amd.com (10.181.42.216) X-EOPAttributedMessage: 0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: CH3PEPF00000010:EE_|SJ0PR12MB6734:EE_ X-MS-Office365-Filtering-Correlation-Id: 9f6c3e2d-a77e-4ab7-2474-08def21f1e2c X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|7416014|376014|1800799024|82310400026|36860700016|23010399003|11063799006|56012099006|10067099003|6133799003|18002099003|22082099003; X-Microsoft-Antispam-Message-Info: BHFmnyjcJ5N9lgMxhQ+MV9/b1hipC0Sb7hhe/NZK+kDBDNJDzrKAWJC6g6LIXh8gOiCGBMaOzt+kP5eSs2r7vyDRYz/xO5cqVLy5iOxOcbEYfFpU9XHDijKONgYtmucuZmvft6ioVgE5lyeWnledIs1DD/uto+48s8E7YPPlVpG/+KI4Zu9a+rOYGJDNKQ8jXPH/AwyEnC2SIb9wyyMlNC+Y2q33E+EOY3OAEPl++/3aDWz77qzpn4iRdR94/RIaRrNetAqrUKU3gLepa/wafkaur1g6VjNzCgO8sSdX3L19K55bIoLL9wIaLbezHuGlG0Jz6mYL3pPGiVK84HxN+dq1F7DPRlmrKZ6jt6f3uESI3mm1r6qa+Hh0HviiwsF0Iv7NQQJQJEcAsyGR0zA5QS1z5/9tQYO31ldZ6WQCDrpuGi4cHS+/QjgKn0w48sT5IrJqVbKP1/639Gy8nc6qAssfYUQsMl0CQI3t8oK0kIdY6DOSfDxSD57pzVc9bg7/USRnYCfoE8k1Fyes+rWy6OUSahVHQdjFlbMllTC6J71Prg2cdcAaIry/CNRtmJZJgcgTJlYbrcuJruIz8yX0HnZDC8jRyRd4TkJZAfhpOaS4psRdRwPJBdBsjy7Yvvg2nT6t4+JwJeElsy5kuHQ/cJWf/7xFmbSateprSpH4S1aXPnnZia5CMYONQogDjYdTey99qlKWLpyOvVl8qD9EHQ== X-Forefront-Antispam-Report: CIP:165.204.84.17;CTRY:US;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:satlexmb07.amd.com;PTR:InfoDomainNonexistent;CAT:NONE;SFS:(13230040)(7416014)(376014)(1800799024)(82310400026)(36860700016)(23010399003)(11063799006)(56012099006)(10067099003)(6133799003)(18002099003)(22082099003);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: Im3700ocZI8rBzYCDRzY3eH22PqcuCF6Q7X+lA83yd60eP/M2+V9GvDk1V8QOiXNRgNAZu0z6UonlSFZd2o5/vSN230lBTNlG3PdDjgfGLJ6rDj03qzbW1S9Owq6jJWi5EOHNAU3SWtUJfHzNLNuJCjBjmWwKQsPn4jMKx6yAsMjJqB+nJLVIGkr0nKT+y0GZdoPXmg1MkzfjGY9Fb2zfcmKp9Qy8a+9AI3hqXQMTyQhjxIdOZPmxDnMYtN4w6f2hDtppRbBJflsgn0Aj/45JHZVPsqCK0koT4f9O0Ka2Ejo0kOShJtZYXdfxMY5TuWnnoRw3/VWvcfPe3pMrKa/5hnwdqUmEg3T4N/VktYod1DyD1OvKbU6e/y76LpcgJWBuWklTDQkBFKfPqsS2cJ6zDm3HeCevbTSYLlc9EuE33FE+bi7qabgP2SXeQN26YJm X-OriginatorOrg: amd.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 04 Aug 2026 11:54:19.6769 (UTC) X-MS-Exchange-CrossTenant-Network-Message-Id: 9f6c3e2d-a77e-4ab7-2474-08def21f1e2c X-MS-Exchange-CrossTenant-Id: 3dd8961f-e488-4e60-8e11-a82d994e183d X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=3dd8961f-e488-4e60-8e11-a82d994e183d;Ip=[165.204.84.17];Helo=[satlexmb07.amd.com] X-MS-Exchange-CrossTenant-AuthSource: CH3PEPF00000010.namprd04.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Anonymous X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: SJ0PR12MB6734 Content-Type: text/plain; charset="utf-8" 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 Signed-off-by: Akshata MukundShetty Signed-off-by: Krishnamoorthi M --- 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" =20 source "drivers/spi/Kconfig" =20 +source "drivers/espi/Kconfig" + source "drivers/spmi/Kconfig" =20 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) +=3D ata/ obj-$(CONFIG_TARGET_CORE) +=3D target/ obj-$(CONFIG_MTD) +=3D mtd/ obj-$(CONFIG_SPI) +=3D spi/ +obj-$(CONFIG_ESPI) +=3D espi/ obj-$(CONFIG_SPMI) +=3D spmi/ obj-$(CONFIG_HSI) +=3D hsi/ obj-$(CONFIG_SLIMBUS) +=3D 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) +=3D 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_XARRAY_ALLOC(espi_controllers); + +static int espi_bus_match(struct device *dev, const struct device_driver *= drv) +{ + const struct espi_device *edev =3D to_espi_device(dev); + const struct espi_driver *edrv =3D to_espi_driver(drv); + + if (edrv->id_table) { + const struct espi_device_id *id =3D 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_en= v *env) +{ + const struct espi_device *edev =3D to_espi_device(dev); + + return add_uevent_var(env, "MODALIAS=3Despi:%s", edev->modalias); +} + +static int espi_bus_probe(struct device *dev) +{ + const struct espi_driver *edrv =3D to_espi_driver(dev->driver); + struct espi_device *edev =3D 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 =3D to_espi_driver(dev->driver); + struct espi_device *edev =3D to_espi_device(dev); + + if (edrv->remove) + edrv->remove(edev); +} + +const struct bus_type espi_bus_type =3D { + .name =3D "espi", + .match =3D espi_bus_match, + .uevent =3D espi_bus_uevent, + .probe =3D espi_bus_probe, + .remove =3D 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 =3D 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 =3D 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 =3D to_espi_controller(dev); + static const char * const modes[] =3D { "single", "dual", "quad" }; + u8 m =3D 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 =3D 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[] =3D { + &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 =3D to_espi_controller(dev); + + mutex_destroy(&ctrl->lock); + mutex_destroy(&ctrl->device_list_lock); + kfree(ctrl); +} + +static const struct device_type espi_controller_type =3D { + .groups =3D espi_controller_groups, + .release =3D 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 =3D kzalloc(sizeof(*ctrl) + size, GFP_KERNEL); + if (!ctrl) + return ERR_PTR(-ENOMEM); + + device_initialize(&ctrl->dev); + ctrl->dev.parent =3D parent; + ctrl->dev.type =3D &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 =3D xa_alloc(&espi_controllers, &id, ctrl, xa_limit_31b, + GFP_KERNEL); + if (ret) + return ret; + + ctrl->bus_num =3D id; + ret =3D dev_set_name(&ctrl->dev, "espi%d", ctrl->bus_num); + if (ret) + goto err_erase; + + if (ctrl->ops->setup) { + ret =3D ctrl->ops->setup(ctrl); + if (ret) { + dev_err(&ctrl->dev, "controller setup failed: %d\n", ret); + goto err_erase; + } + } + + ret =3D 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=3D0x%02x freq=3D%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 =3D -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 =3D 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 =3D ctrl->caps; + return 0; +} +EXPORT_SYMBOL_GPL(espi_get_capabilities); + +bool espi_channel_is_enabled(struct espi_controller *ctrl, u8 channel) +{ + if (!ctrl || channel >=3D 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 >=3D ESPI_CHANNEL_COUNT) + return -EINVAL; + guard(mutex)(&ctrl->lock); + ret =3D ctrl->ops->enable_channel(ctrl, channel); + if (!ret) + ctrl->channel_enabled |=3D 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 >=3D ESPI_CHANNEL_COUNT) + return -EINVAL; + guard(mutex)(&ctrl->lock); + ret =3D ctrl->ops->disable_channel(ctrl, channel); + if (!ret) + ctrl->channel_enabled &=3D ~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 va= lid) +{ + 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 le= n, 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, s= ize_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 =3D owner; + drv->driver.bus =3D &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 =3D 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 "); +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 +#include +#include +#include +#include +#include +#include + +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 =3D 0, + ESPI_CMD_GET_CONFIGURATION =3D 1, + ESPI_CMD_IN_BAND_RESET =3D 2, + ESPI_CMD_GET_STATUS =3D 3, + ESPI_CMD_PERIPHERAL =3D 4, + ESPI_CMD_VWIRE =3D 5, + ESPI_CMD_OOB =3D 6, + ESPI_CMD_FLASH =3D 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 =3D 0, + ESPI_EVENT_CHANNEL_RESET =3D 1, + ESPI_EVENT_VWIRE_CHANGED =3D 2, + ESPI_EVENT_OOB_RECEIVED =3D 3, + ESPI_EVENT_PERIPH_POSTED =3D 4, + ESPI_EVENT_ALERT =3D 5, + ESPI_EVENT_RESET =3D 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 *ct= rl) +{ + return dev_get_drvdata(&ctrl->dev); +} + +static inline void espi_controller_set_devdata(struct espi_controller *ctr= l, + 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 */ --=20 2.34.1 From nobody Fri Oct 2 06:58:32 2026 Received: from CH1PR05CU001.outbound.protection.outlook.com (mail-northcentralusazon11010060.outbound.protection.outlook.com [52.101.193.60]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0265F383310; Tue, 4 Aug 2026 11:54:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.193.60 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785844478; cv=fail; b=lh+ESxcmEkNIjm+7bZqBC6sRdOlgcf01bvJfrdaZF32UWITHJPpADHEyzLlKbMXZsrR3zWjTFjXizAWCLM4bcdjpR9xe/Svf4tQ0OiZhlTv3hE2Q95uSnnEwzG/2AcoIPd9zIpvC4UBTgDNA82H8XxIcrGpTsWRUMWWCVO5rUAE= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785844478; c=relaxed/simple; bh=wmTyO84ht0Q+39TliuYjkNouDdzqbmI9kjsflt3EX8c=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=MFLG+mpsm8hy2bR8yqRMqK9HX5K5WgZot82qh+hfQOQse1BN11xtDopDQgoWURFboDSuLejFI668J7rgiIXkfQp2INzuJiSheNKhGRKsJBa2enHTCLU4DpVWcCV4S6hn6nA8CFJDZkLWTwATvQDZwwrE/U6JTP8fs/zgg9C5ob0= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=amd.com; spf=fail smtp.mailfrom=amd.com; dkim=pass (1024-bit key) header.d=amd.com header.i=@amd.com header.b=QnVEW7R/; arc=fail smtp.client-ip=52.101.193.60 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=amd.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=amd.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=amd.com header.i=@amd.com header.b="QnVEW7R/" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=prEgw3+Yiq8Z4RtxQOugk1yHD3iXMSvsv7WUvEkggyNMWorRh6kHfO7jD0fPzvTxp2Zs6M9LN9BPkOAeMjuYRogrDl05F1eP+N+JZr1dcpfxNnsGHnaFitVBE+9dKpCJDvfkg1+DRDwlvjkThWChoJcfl+JfvqG7/V23hY1Cimll5yV2kUztCo8xFLS1RLqTHqXWtAOwhKEOF2Hdd8QFJkhhme2fj6wbxzAe8HtEOX9pc6Ms4n0wxwGU/S2+Fn4Vt6yochg9MHZ9lQfxnXUml5yWkUOsSrmEWEm6BGjsoxGFfoDZpdsTlb+/LH5v5Qtzfqdbj44b/nSip1/3sa+WMg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=pSgBMog4Z3WKer5LhvpdcpMFJ1cTXhIW3nz5stH/uZY=; b=W8v0DlYFS35MzIMnxldZlH7YVJDxQnIxnzImdchA21yjgAQAX6xjiqivgKHRZq/YS/S8cwTaIbvj3zbnIm+WNYe+OL98nQOTdBaPlh/FvTrBcu96Xg/Oz3ir/eRazluOCErKKztpeWJNfLlUij7ReDenIbBntvM1aDb9MptzCfoMmEiaUrzPdMvSIuAGGBTd1QK4MX/hXFNQjPC292U9wf0P74ffM5o/zzYtriyUUU+aGT/yZhnayoZbcvVv18J7kAt9e7fqN6Ut9VGdExGRFodM1eTZWyHEpBJOqLEibiGBXvuU3zSLLzWU2YKL9HNyxUPwyQuH+WCNlar7BE6k7w== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass (sender ip is 165.204.84.17) smtp.rcpttodomain=vger.kernel.org smtp.mailfrom=amd.com; dmarc=pass (p=quarantine sp=quarantine pct=100) action=none header.from=amd.com; dkim=none (message not signed); arc=none (0) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=amd.com; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=pSgBMog4Z3WKer5LhvpdcpMFJ1cTXhIW3nz5stH/uZY=; b=QnVEW7R/bq1v30bJzWmRo0fsdGnI89kr92MWMDChL2WakaHGpnYVpx5dILJQMvpCnIYyacNIW7zaYuyQQPC0DofpOjE6DVA1Vcm+XgBPtCo1XIeYjxuPZr/FinzaTa8XjF5cARSe9IAf0VBdKqPxnsWWe8QkVlMHpjqyehyC/sw= Received: from CH0PR03CA0044.namprd03.prod.outlook.com (2603:10b6:610:b3::19) by CY8PR12MB7684.namprd12.prod.outlook.com (2603:10b6:930:87::16) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.292.15; Tue, 4 Aug 2026 11:54:31 +0000 Received: from CH3PEPF0000000D.namprd04.prod.outlook.com (2603:10b6:610:b3:cafe::a4) by CH0PR03CA0044.outlook.office365.com (2603:10b6:610:b3::19) with Microsoft SMTP Server (version=TLS1_3, cipher=TLS_AES_256_GCM_SHA384) id 15.21.292.15 via Frontend Transport; Tue, 4 Aug 2026 11:54:31 +0000 X-MS-Exchange-Authentication-Results: spf=pass (sender IP is 165.204.84.17) smtp.mailfrom=amd.com; dkim=none (message not signed) header.d=none;dmarc=pass action=none header.from=amd.com; Received-SPF: Pass (protection.outlook.com: domain of amd.com designates 165.204.84.17 as permitted sender) receiver=protection.outlook.com; client-ip=165.204.84.17; helo=satlexmb07.amd.com; pr=C Received: from satlexmb07.amd.com (165.204.84.17) by CH3PEPF0000000D.mail.protection.outlook.com (10.167.244.43) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.292.8 via Frontend Transport; Tue, 4 Aug 2026 11:54:31 +0000 Received: from airavat.amd.com (10.180.168.240) by satlexmb07.amd.com (10.181.42.216) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.41; Tue, 4 Aug 2026 06:54:27 -0500 From: Krishnamoorthi M To: CC: , , , , , , , , , , , , , Krishnamoorthi M Subject: [RFC PATCH 2/4] espi: add slave device model and event notification Date: Tue, 4 Aug 2026 17:22:57 +0530 Message-ID: <20260804115259.4065638-3-krishnamoorthi.m@amd.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260804115259.4065638-1-krishnamoorthi.m@amd.com> References: <20260804115259.4065638-1-krishnamoorthi.m@amd.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ClientProxiedBy: satlexmb07.amd.com (10.181.42.216) To satlexmb07.amd.com (10.181.42.216) X-EOPAttributedMessage: 0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: CH3PEPF0000000D:EE_|CY8PR12MB7684:EE_ X-MS-Office365-Filtering-Correlation-Id: f2b9cc1c-03c5-4fb9-8a09-08def21f2534 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|4022899009|36860700016|23010399003|376014|7416014|30052699003|1800799024|82310400026|10067099003|11063799006|5023799004|56012099006|18002099003|22082099003|3023799007|6133799003; X-Microsoft-Antispam-Message-Info: ubIzRy1TUIOXNwBsBP2ORrDhQ/mL0zs9a8GnNI6fqQNm2oodIzsrdfd9aDLmg4IPrMCjZcofdwlwL0TMspDE6BphZdGhYIdpXIZDAIvBfEChVSsXW9m/KQ6tAZidW6YKkILiNBFmfl8BN+YZ5Dkpw/oasGuO29KFY8wIDRtCe9/Ew2mNNxQ5HYwqKI2bK2KiRwTepYp1KU9j63xwikhHf0oSbHd2chxfW2PoiiJlw58ccoh/gnWDWiVDVOowCTMWqcK+AoEsoFZChr7tWY8RrNO7nR0BMlnIcAfG+0UQPPcw+uSDK53eokVhYpU6QrlIB/Zi8tLHkTei8U0N9xbYDWqIx3aNzVUO03tWLwonZSF67/7iqoMkh0C4b6H7NIWPuRUr54uSW9aP3MT0hLVjbP+5IyIrVBWK66ox5MoNUiP9kBiq/BU14SbeRd2nK0pg/vINZ1ayzAeu6W9ddgQHN4WvvXwE6ab0DI7vBfvlD4wS4nOh4H1NTmWLJslrrePZ1UXVAFQslt7oTYqaeqdHQRWVbRL32FaT6Ae9ZivfmVu4aOo2D+vrrB1WhZO3CvSXvB1rD5gKKoHhoj2aAENwwjohwikEJaOSttAIzadTpmcnECId1ENeFS7PhU9xpvs2UDpMSvimrloL35q+eBPDPXQrL8LGzPLjhCOIhB0pZpEt+OOaQplOFkWgDG6ZswKH9OZpsIe5Tv/hIJRNswQJAw== X-Forefront-Antispam-Report: CIP:165.204.84.17;CTRY:US;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:satlexmb07.amd.com;PTR:InfoDomainNonexistent;CAT:NONE;SFS:(13230040)(4022899009)(36860700016)(23010399003)(376014)(7416014)(30052699003)(1800799024)(82310400026)(10067099003)(11063799006)(5023799004)(56012099006)(18002099003)(22082099003)(3023799007)(6133799003);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: Tm0lEz5Rh4lC+RMWji+WeJAeeQ0RcSFFiTnmcb5oRTl7u909u+F3CxjggmJO/EKO3GCUW9RRAaOAP5k1BAiB80q4ditKGYxeAdHOSm9srEvzeFCW21Xk5a+EF7lik9ibnH7pyItjSxL1silh3iyCVtLwDv6Y6uTw8yBzKSE6n3LL0Vqjj+09P3nU424CiQ+Ypc04NgOOvtgbOdv+MS0ARVezHONBVhTueL4arcXmlEZCrvSNe/4NnPZN4kQdEHiMcqxSm3qTvae0j9AD3hKFhshZtOgijdkY/r0D+Svx2AHcFZ2FG7o/P7uhd7y4UzXNkpeHxKNlgOTIlM4hs9doeOOqNgYrHCburCUU/45j0zYfnnGCGBQlMJykQh3xIqD1zpL1TBRJNtivs7lLOqDQF5eQSIxATEXXCkzr7v90iNMJn8EH2lHN8wwkltsQGrAB X-OriginatorOrg: amd.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 04 Aug 2026 11:54:31.4756 (UTC) X-MS-Exchange-CrossTenant-Network-Message-Id: f2b9cc1c-03c5-4fb9-8a09-08def21f2534 X-MS-Exchange-CrossTenant-Id: 3dd8961f-e488-4e60-8e11-a82d994e183d X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=3dd8961f-e488-4e60-8e11-a82d994e183d;Ip=[165.204.84.17];Helo=[satlexmb07.amd.com] X-MS-Exchange-CrossTenant-AuthSource: CH3PEPF0000000D.namprd04.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Anonymous X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: CY8PR12MB7684 Content-Type: text/plain; charset="utf-8" Add espi_new_device()/espi_remove_device() and a process-context blocking-notifier event chain. Devices are named espi. (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 --- 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. =20 + 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) +=3D espi-core.o +obj-$(CONFIG_ESPI) +=3D 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 +#include +#include +#include +#include +#include +#include +#include + +static void espi_device_release(struct device *dev) +{ + struct espi_device *edev =3D to_espi_device(dev); + + fwnode_handle_put(dev_fwnode(dev)); + kfree(edev); +} + +static const struct device_type espi_device_type =3D { + .release =3D 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.`` 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 >=3D ctrl->max_targets) + return ERR_PTR(-EINVAL); + + edev =3D kzalloc_obj(*edev, GFP_KERNEL); + if (!edev) + return ERR_PTR(-ENOMEM); + + edev->ctrl =3D ctrl; + edev->cs =3D info->cs; + edev->platform_data =3D info->platform_data; + strscpy(edev->modalias, info->type, sizeof(edev->modalias)); + INIT_LIST_HEAD(&edev->list); + + edev->dev.parent =3D &ctrl->dev; + edev->dev.bus =3D &espi_bus_type; + edev->dev.type =3D &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 =3D 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 =3D 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 *eve= nt) +{ + if (!ctrl || !event) + return notifier_from_errno(-EINVAL); + event->ctrl =3D ctrl; + return blocking_notifier_call_chain(&ctrl->notifier_list, + (unsigned long)event->type, event); +} +EXPORT_SYMBOL_GPL(espi_notify_event); + +MODULE_AUTHOR("Krishnamoorthi M "); +MODULE_DESCRIPTION("eSPI slave-side device management"); +MODULE_LICENSE("GPL"); --=20 2.34.1 From nobody Fri Oct 2 06:58:32 2026 Received: from CO1PR03CU002.outbound.protection.outlook.com (mail-westus2azon11010066.outbound.protection.outlook.com [52.101.46.66]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 08DE43845A7; Tue, 4 Aug 2026 11:54:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.46.66 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785844490; cv=fail; b=j5Y2fqffwkWZiAC/ualXj6wTv/Z5IXB6q+x0EtXbrKwHqmE/8s1wv3oQ9EFcUeMlEKWQHtDltiKc2REtS27wdzwDn27HQnB88M5M110h4cRB3Nci0QW5LOBbu3B5TAeY8I/rFZhdH+taWdms949UElcXq1AHI8xJguWUwjcrqVc= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785844490; c=relaxed/simple; bh=WD6oKBuaRl/55FmHDDfsKA3uufB6/Ud2n0SiBPktfX4=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=GAZEmTN4KbJj0Fsul9LKyKY5ksMQNrBzhiZHiB0xH3hGqx/8dn/wHHw8XIc1YHIlR74jLXy9DGSFG3COETWVxSbQnFVoHR1RGeUbD5V2Fmg2N/K4Y5VFZqnVBKjfCdr0CWEklDSfkIqhH3f6JQ0e4Vhsa+Ggm8xMLbf+K48N0OI= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=amd.com; spf=fail smtp.mailfrom=amd.com; dkim=pass (1024-bit key) header.d=amd.com header.i=@amd.com header.b=yhEkd3rZ; arc=fail smtp.client-ip=52.101.46.66 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=amd.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=amd.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=amd.com header.i=@amd.com header.b="yhEkd3rZ" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=Z63Av2sRrsrTBqauhzQqY40b9+GteRNa2mDl0CXhM0E0vFKEyEuGyBRa6wh5kXJIP4tj0Dq2qwtwgkLkexkliBTadz2vcIoEcJTxiDNfk3tAuub6IQbpZuqeh5ezQlSuLRuPr7unKgwv7BGd8LzXUheTHXyvtG0yJ2JQLnftHgskPN1LATO/72cb6vwZ6pe1o/zz6/sWvGh8Xz+uMqUyy8zjG9UydbkGAVYUhtZ2X1thFApgGH8M4D2G2fFfaSVKAdeqBuKbGjHDFpkymS9VY2Ys2/blk79RpWLWC3dtJXYLv8s408NW8XN/TN0VJ677cMstUGY02nIo0lHwjKwbBg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=xQcTl/fV+iuJ9ImdmCIgjqy3ijrM5jXF3JtsMPK3hDQ=; b=k3rIE5mX8f85nf2fXvWVoJqZcMptQPqtITSxMphEIeeY7i1czMiDAV1wIOUyTpsn4+4vp0X+8k1tH1Ny7lQawcA9HjPxuuvz4wm7FZsxNFWhSvjpr8cF9SSqm4qQCBoK9eiy5Sb/xYa8Ht3mNqeoVpPyCJYMmZ0j2wKEBDpIUsvMj7J6lm1LbSp68wExx7xwRWNiEQAxSheSbQg+3TX1KOqf0+lBpNKNXkw+AG8JDSlZSSfAUUsz2K2goC0jxBr6OjQBRBhZffVRr0sNvQYx7uqKwvx28EdwvawCeKXywkH7ZErbmFt0Xx9qNS9vB20M0RmWu8gsmiPTM6ad9mv9Bg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass (sender ip is 165.204.84.17) smtp.rcpttodomain=vger.kernel.org smtp.mailfrom=amd.com; dmarc=pass (p=quarantine sp=quarantine pct=100) action=none header.from=amd.com; dkim=none (message not signed); arc=none (0) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=amd.com; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=xQcTl/fV+iuJ9ImdmCIgjqy3ijrM5jXF3JtsMPK3hDQ=; b=yhEkd3rZjY0KsgBC2SQVeFhdbt9uX7vJua880e7D9bKxiBeQ0Cmib2yHzDX9p5DJCIyt08Mpf1ZjWeC3SjPSbx/AWd18skJv+mMng5nNfds6n00oNFqWHcVLdnimKUjj1D85apknxdr/lLGsCN/R5DC1FIrK8IwqfRqopdjYzJw= Received: from CH0P223CA0011.NAMP223.PROD.OUTLOOK.COM (2603:10b6:610:116::26) by SA1PR12MB6893.namprd12.prod.outlook.com (2603:10b6:806:24c::12) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.270.18; Tue, 4 Aug 2026 11:54:42 +0000 Received: from CH3PEPF0000000B.namprd04.prod.outlook.com (2603:10b6:610:116:cafe::44) by CH0P223CA0011.outlook.office365.com (2603:10b6:610:116::26) with Microsoft SMTP Server (version=TLS1_3, cipher=TLS_AES_256_GCM_SHA384) id 15.21.292.16 via Frontend Transport; Tue, 4 Aug 2026 11:54:42 +0000 X-MS-Exchange-Authentication-Results: spf=pass (sender IP is 165.204.84.17) smtp.mailfrom=amd.com; dkim=none (message not signed) header.d=none;dmarc=pass action=none header.from=amd.com; Received-SPF: Pass (protection.outlook.com: domain of amd.com designates 165.204.84.17 as permitted sender) receiver=protection.outlook.com; client-ip=165.204.84.17; helo=satlexmb07.amd.com; pr=C Received: from satlexmb07.amd.com (165.204.84.17) by CH3PEPF0000000B.mail.protection.outlook.com (10.167.244.38) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.292.8 via Frontend Transport; Tue, 4 Aug 2026 11:54:41 +0000 Received: from airavat.amd.com (10.180.168.240) by satlexmb07.amd.com (10.181.42.216) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.41; Tue, 4 Aug 2026 06:54:37 -0500 From: Krishnamoorthi M To: CC: , , , , , , , , , , , , , Krishnamoorthi M Subject: [RFC PATCH 3/4] Documentation: espi: add subsystem overview and MAINTAINERS entry Date: Tue, 4 Aug 2026 17:22:58 +0530 Message-ID: <20260804115259.4065638-4-krishnamoorthi.m@amd.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260804115259.4065638-1-krishnamoorthi.m@amd.com> References: <20260804115259.4065638-1-krishnamoorthi.m@amd.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-ClientProxiedBy: satlexmb07.amd.com (10.181.42.216) To satlexmb07.amd.com (10.181.42.216) X-EOPAttributedMessage: 0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: CH3PEPF0000000B:EE_|SA1PR12MB6893:EE_ X-MS-Office365-Filtering-Correlation-Id: b04f22b4-8a36-4a0f-4f83-08def21f2b75 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|36860700016|23010399003|376014|7416014|1800799024|82310400026|6133799003|10067099003|56012099006|11063799006|22082099003|18002099003|3023799007; X-Microsoft-Antispam-Message-Info: Ytmb6yIQthAOhFEeU9sOVSgRpid+bM6BKRCCyCbxnPTqZwwCZ6dloNMPypgiTo5QwGk4JKQG6aznCF0DFM6rS7VC23Ai8/TwyrsuKZCZjgnWb/Ngie/LV5xyfJzk2+ADCPnmha3DALfxNucKWSUF28n217ixeN+grgiSvADwH5E9Az5w826G342SErLEJQ320ndB9jeqz2uqzAaQC9T13pL61wbxyIpgZMfT9aSWfJ4/9CC7eZLvzw0abfp8eo5r5nTbPgzNWX15/keQPVA5si8pFISTG10yEWFI688kH2GZwvexALLVfKOVfqL0rswxPhgA5w/1uZQZlP7rRgTylUesL8xUOJcpuWzgAIijxm+kWjANORlOjJP3eZ2+nERmwCcqCSXYZ5aU6kwLFua4JDzWW52LENY1TO6I8vbU+o5U8Bquu8oLv3j4j5QPWRHipbtb3oRJ8+tNbnLphXDNzU9CY9hDnEi1HIb5SjqT8S2g5sDI+eezixDWe1aJifhcqco0CU457ECW/DHDLjvmES7P/cO1W7LPvhGwueN4zl23TFxcOdlAl50J45teot+hwmpSMuvq1+VdZltgYiQrfN3S1rgsJKjlvOUXRgqXw2Pk0N2iP9VFVphFW3xjysPPAgRfz7tVkwN2dJH/Bu/BIsbW9to6hnMhNlwu8PQB3w+pRPeWoyXuTWvoxus3ByBeC2wZjrfe/2jsRz999980Jw== X-Forefront-Antispam-Report: CIP:165.204.84.17;CTRY:US;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:satlexmb07.amd.com;PTR:InfoDomainNonexistent;CAT:NONE;SFS:(13230040)(36860700016)(23010399003)(376014)(7416014)(1800799024)(82310400026)(6133799003)(10067099003)(56012099006)(11063799006)(22082099003)(18002099003)(3023799007);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: WRJ38oM7I/jwqPchD0CA79/Bz1UaMeoeswyzW6p6RwCoYeQHb6s0okj3PDEOumMef8A1/764LTAkO/Dm4cEFgPoaiOlugaqO5+CIawzXA4aPEh9CeQ2Z660uWwCNRSbQo6M5T1CZ9QKt7aU1hbGJyTJzTmrVNbIiuQSoelw+5UpIOA4icikWSyA+xRvLMVvgnhcWnP8+d9w6poDymCoW4S84/mGmVsfXtUc7plfFRwnQ0vmb4yJgH33JqSVMThVGZUdwoq7CXhddNR8kcQtsOCAIdvnrubJl93YJpwCxLM2PncOWb2XcR+H8caJhvftoN31cBQEeliH6z6I0SEKEKfC1GPWiUiZKAR6lC8x1h5RzUBADw8qiB8ogrwum9lu4CcVWoJiSHr9Miz3ap/ba45ZtwZHnqEp0gNRAHLyKnt3OdW44ikg/HvVfNsxmu6PC X-OriginatorOrg: amd.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 04 Aug 2026 11:54:41.9640 (UTC) X-MS-Exchange-CrossTenant-Network-Message-Id: b04f22b4-8a36-4a0f-4f83-08def21f2b75 X-MS-Exchange-CrossTenant-Id: 3dd8961f-e488-4e60-8e11-a82d994e183d X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=3dd8961f-e488-4e60-8e11-a82d994e183d;Ip=[165.204.84.17];Helo=[satlexmb07.amd.com] X-MS-Exchange-CrossTenant-AuthSource: CH3PEPF0000000B.namprd04.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Anonymous X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: SA1PR12MB6893 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 --- 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/e= spi.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 + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +eSPI (Enhanced Serial Peripheral Interface) +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Introduction +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +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 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +* ``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 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + +A controller driver allocates and registers a controller from its +``probe()`` function:: + + ctrl =3D espi_controller_alloc(&pdev->dev, sizeof(*priv)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + priv =3D espi_controller_get_devdata(ctrl); + ctrl->ops =3D &my_espi_ops; + ctrl->max_targets =3D 1; + + /* populate ctrl->caps from hardware capability registers */ + ctrl->caps.supported_channels =3D ESPI_CHANNEL_ALL; + ctrl->caps.max_freq_mhz =3D 33; + ctrl->caps.io_mode =3D ESPI_IO_MODE_SINGLE; + + ret =3D 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 =3D { + .type =3D "my-ec", + .cs =3D 0, + }; + edev =3D espi_new_device(ctrl, &info); + +On removal:: + + espi_remove_device(edev); + espi_controller_unregister(ctrl); + espi_controller_put(ctrl); + +Writing a slave driver +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +A slave driver declares a device ID table and a ``struct espi_driver``:: + + static const struct espi_device_id my_ec_ids[] =3D { + { "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 =3D 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 =3D { + .driver =3D { .name =3D "my-ec" }, + .id_table =3D my_ec_ids, + .probe =3D my_ec_probe, + .remove =3D my_ec_remove, + }; + module_espi_driver(my_ec_driver); + +Channel-independent commands +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D + +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 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +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 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Peripheral channel +------------------ + +Carries I/O and memory cycles between the host and target endpoints. + +* espi_periph_io_read() / espi_periph_io_write() =E2=80=94 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() =E2=80=94 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() =E2=80=94 read a wire group from the target. +* espi_vwire_put() =E2=80=94 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 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +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 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +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 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +.. 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,eic= 7700-clock.yaml F: drivers/clk/eswin/ F: include/dt-bindings/clock/eswin,eic7700-clock.h =20 +ESPI SUBSYSTEM +M: Krishnamoorthi M +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 S: Odd Fixes --=20 2.34.1 From nobody Fri Oct 2 06:58:32 2026 Received: from BN8PR05CU002.outbound.protection.outlook.com (mail-eastus2azon11011024.outbound.protection.outlook.com [52.101.57.24]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 18B9019C54E; Tue, 4 Aug 2026 11:55:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.57.24 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785844504; cv=fail; b=smQuukIPKNsOrC43JET41jJ1GPppw8uPQsYHv/E0MJdvnZS6eN4ebz3ZROKm0bDjvTBKOcMlo/IxEtVGvgZH1Cxt1xnusXI9pdI/kVOc3uIxnubqJ4wTB/cM+Or8UN9lrzpyFl9cdtM5TQ9HkHgRQaOoBSL417GoaeH+tRJbOeo= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785844504; c=relaxed/simple; bh=V38xEiBRYJ9lob+U4Q/VvB9sWP7S7iWpg4JL/mCeWE8=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=tE9ZkqlgxDM2vZ6L3XvmlOYYnSuQo7aSL/feVg/nPMvGTNxTmuiYR745/lZt3z2LvV5Vk30jeHsI2LzGwg1zDJyw6K+LQFRXxwIVegskVUz9it8SOF7PhpHSrMaFa3bqBrQ8LPlVqz8SRRJGbEDKRL3nvPnjH7AIqPPzKG9/1tc= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=amd.com; spf=fail smtp.mailfrom=amd.com; dkim=pass (1024-bit key) header.d=amd.com header.i=@amd.com header.b=FtucDkNg; arc=fail smtp.client-ip=52.101.57.24 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=amd.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=amd.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=amd.com header.i=@amd.com header.b="FtucDkNg" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=H6E+ecjpGEJ3tKYyHGZTWHs5wnsmrfD0ifXYz4FUcT43XZ57qteQNs7DpGdMZTTVlUCLRwMGjBq3+9UF7pL12GzMliFdhEFoRApkcN9QHSqmxeNyY8xYHAgrJUZkeeWybeLDo6q3LtuVHhY7DH/VYoArmSeHS1l3kCOYcDNRaMg+s35WCr4Zxgj2vtmGgXNoIThpBoqzZG4BhVllwNFXdSldA52f/PhLnN3QaU+RLWTmT3vltb2iOW89gpizkxfBVUKDgaPtCGg34yYZjMIZV1k8bQ3oBuAnq+4bgk7TLI/Pqyy5zGZjjbnYa1kkDFNKw41bL2YV6pTzGoNGzxbCSw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=cRdNmUhV3F1lfZU93i9g6Rv6PAZM3X4Uowb5y27aiec=; b=JBN/Ky+2e9yCFmQAeNnH6slcKT7Ap03nKgoEmz8tQbujQkSV2vomea0hWhWGvLFiau3ca9yDVMeFulxft/slQY5eSON3Uyc13eTk2LieUAl+saxbYCyz1rY1rGND4UCvC7tUti6nom84PRxg6mMwGZM4xaFmkJp81HleSjV8C7TJuhSrKeOAEfkYjvUrZzHDIBHHmSL6h/EKncTuJnJM1ZuJmntRW7ECxMLaa7dpllCmF91w0uwZIhwVU5LnU+m0V4UkUTGAa/p1jCZTSH7w1c0mQDgKDQLiB+ynxBtuHwQ3FMx01WFWZ2MSSZMMRkOlLFO36IrwAYKFJ+GssA0XzA== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass (sender ip is 165.204.84.17) smtp.rcpttodomain=vger.kernel.org smtp.mailfrom=amd.com; dmarc=pass (p=quarantine sp=quarantine pct=100) action=none header.from=amd.com; dkim=none (message not signed); arc=none (0) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=amd.com; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=cRdNmUhV3F1lfZU93i9g6Rv6PAZM3X4Uowb5y27aiec=; b=FtucDkNguPqPJ/mKkSbhCye/VayV4BBDkYI4SjzixSFSxZOOcOABcuM8Bb4ElW5uqK8+pFh95U1m2dAFqIBR7F+7lAKSq0lNU7b4Te1lPLpjy5cgcmk9xDJDeA11uH0/R02LAnKHnzficWbieKh9llincJlUKxT2yaVJdhDteZY= Received: from CH2PR04CA0009.namprd04.prod.outlook.com (2603:10b6:610:52::19) by IA0PPF1D04084C7.namprd12.prod.outlook.com (2603:10b6:20f:fc04::bca) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.270.18; Tue, 4 Aug 2026 11:54:55 +0000 Received: from CH3PEPF0000000E.namprd04.prod.outlook.com (2603:10b6:610:52:cafe::2c) by CH2PR04CA0009.outlook.office365.com (2603:10b6:610:52::19) with Microsoft SMTP Server (version=TLS1_3, cipher=TLS_AES_256_GCM_SHA384) id 15.21.270.18 via Frontend Transport; Tue, 4 Aug 2026 11:54:55 +0000 X-MS-Exchange-Authentication-Results: spf=pass (sender IP is 165.204.84.17) smtp.mailfrom=amd.com; dkim=none (message not signed) header.d=none;dmarc=pass action=none header.from=amd.com; Received-SPF: Pass (protection.outlook.com: domain of amd.com designates 165.204.84.17 as permitted sender) receiver=protection.outlook.com; client-ip=165.204.84.17; helo=satlexmb07.amd.com; pr=C Received: from satlexmb07.amd.com (165.204.84.17) by CH3PEPF0000000E.mail.protection.outlook.com (10.167.244.42) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.292.8 via Frontend Transport; Tue, 4 Aug 2026 11:54:55 +0000 Received: from airavat.amd.com (10.180.168.240) by satlexmb07.amd.com (10.181.42.216) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.41; Tue, 4 Aug 2026 06:54:51 -0500 From: Krishnamoorthi M To: CC: , , , , , , , , , , , , , Krishnamoorthi M Subject: [RFC PATCH 4/4] espi: amd: add AMD eSPI controller driver Date: Tue, 4 Aug 2026 17:22:59 +0530 Message-ID: <20260804115259.4065638-5-krishnamoorthi.m@amd.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260804115259.4065638-1-krishnamoorthi.m@amd.com> References: <20260804115259.4065638-1-krishnamoorthi.m@amd.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ClientProxiedBy: satlexmb07.amd.com (10.181.42.216) To satlexmb07.amd.com (10.181.42.216) X-EOPAttributedMessage: 0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: CH3PEPF0000000E:EE_|IA0PPF1D04084C7:EE_ X-MS-Office365-Filtering-Correlation-Id: f4645671-a316-40c5-2f65-08def21f3373 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|82310400026|1800799024|7416014|36860700016|376014|23010399003|22082099003|18002099003|56012099006|11063799006|3023799007|6133799003|10067099003; X-Microsoft-Antispam-Message-Info: odWNKktP6g9s3vzsavGtJyKZR45frg3CWIZzn85rnyR2T6bFd66lE1IyXnmntcIVVHLyDLLJCHvjck9rVjs9j2vIbPpixIlQpow2obNo3wtDZXuaETOqdjWduEBC/ylIT7/c4LuddK8gnxwb8+N6dw76vZZKisKS5pOI7/EbPCVwRpE1kJim9QtrKacy/wNnPuFxuquUnQ7HgY3tYA0A94un8gJrDLUvNAgdj8NhKOzu2sgjeeBKCoujLAhuTyMU1lovibsUBULttVclF8WDpj0LFFSiRReFcxD6pW8pDlt+UdilZUvQbu+4yQyDA9QA/8COicp3WhcqWVKpJea1xczVEv+PvOL3+ADA0+37L4XS/OrWl7goMRsNN6o5Y9L9MkX1AS64ckd/iHBTI6u57zDgbZ+3xVmXwMVvsgnJmzDJTnekDCf6axyr2jSLiHZp/QLMqEIX55rt4b1s55ZLx85SBE65EZe7yN+DP7M7W6OVS6fbwaP8rKtGlfwREsJsUk723pEBllVDqDsUcPfYU8rBXAsAkQvTZy+wYXIw/BUEZoaGgMlXVqWUzFfS5JeuOEA96Njsuc1YZZ2sL2Frpfs4DWWPpI23GgCbf97S327T4VU4YX+TCa8+q6watyAiLcbE/ZvlL2G/VHaLR6eyZtIeB1IRZGD56XQfmCPN1o72AR85ZRrsT3GVrAkdJj89V4+46s4L2algNG7nOFhYIg== X-Forefront-Antispam-Report: CIP:165.204.84.17;CTRY:US;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:satlexmb07.amd.com;PTR:InfoDomainNonexistent;CAT:NONE;SFS:(13230040)(82310400026)(1800799024)(7416014)(36860700016)(376014)(23010399003)(22082099003)(18002099003)(56012099006)(11063799006)(3023799007)(6133799003)(10067099003);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: 3EcbE/2Hff//GWsrRsdG5ZICD6dJhl/Y/dLz/yZHRbOexm3qgopKGqwkuKS97tYgol1vaqJiTBd+Ew9C1UAdcGTxTixe0rfsAGOFgL4bjO2d8dM07xNQC/rIlJ7yh52GLUBmUvMbpP6ib0juKeZJmmyz84QAqIjXQm54ZKI/tveogEgmglg04uY2IDuXcHe+bRrndbSRAL4ZmjpTfmU8BFeiUiLy9YpbtjGJKboLkyy89tyP099A8/zW9EihepUTs4OjRfhd0+Itwo152zB/5xOQX5AjABYOG6pjwFJjjenK3EgT5cGZoHvvhOVYpsYr+zjH9xCNP3BBFuBJorIphJ1Y6YJ80m/7zcNtH3qQ0HbGCJWxJwDxBRIci6MilTk9wymj2lVc5/OHoULme0gvGmYd25BZWHH/QwT42Vwia+9DSfEfftLHWvGID3TP2eiT X-OriginatorOrg: amd.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 04 Aug 2026 11:54:55.3653 (UTC) X-MS-Exchange-CrossTenant-Network-Message-Id: f4645671-a316-40c5-2f65-08def21f3373 X-MS-Exchange-CrossTenant-Id: 3dd8961f-e488-4e60-8e11-a82d994e183d X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=3dd8961f-e488-4e60-8e11-a82d994e183d;Ip=[165.204.84.17];Helo=[satlexmb07.amd.com] X-MS-Exchange-CrossTenant-AuthSource: CH3PEPF0000000E.namprd04.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Anonymous X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: IA0PPF1D04084C7 Content-Type: text/plain; charset="utf-8" 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 Signed-off-by: Akshata MukundShetty Signed-off-by: Krishnamoorthi M --- 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. =20 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) +=3D espi-core.o espi-slave.o +obj-$(CONFIG_ESPI_AMD) +=3D 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "espi-amd.h" + +static void amd_espi_clear_status(struct amd_espi_data *priv) +{ + u32 status =3D 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=3D0x%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 =3D 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=3D0x%08x hdr1=3D0x%08x hdr2=3D0x%08x data=3D0x%= 08x\n", + hdr0, hdr1, hdr2, 0); + + writel(hdr0, priv->base + AMD_ESPI_DN_TXHDR_REG0); + + ret =3D 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 =3D 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 =3D 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 =3D espi_controller_get_devdata(ctrl); + u32 hdr0 =3D amd_espi_cfg_hdr0(ESPI_CMD_GET_CONFIGURATION, slave_reg_addr= ); + int ret; + + ret =3D amd_espi_send_cmd(priv, hdr0, 0, 0); + if (ret) + return ret; + + *config =3D readl(priv->base + AMD_ESPI_DN_TXHDR_REG1); + dev_dbg(priv->dev, "GET_CONFIGURATION addr=3D0x%04x config=3D0x%08x\n", + slave_reg_addr, *config); + return 0; +} + +/* Translate: target and host use different bit positions and clock encodi= ng. */ +static void amd_espi_sync_config(struct amd_espi_data *priv, u32 slave_cfg) +{ + u32 io_mode =3D 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 =3D AMD_ESPI_SLAVE0_CLK_FREQ_66MHZ; + break; + case ESPI_GENCFG_FREQ_33MHZ: + host_freq =3D AMD_ESPI_SLAVE0_CLK_FREQ_33MHZ; + break; + default: + host_freq =3D AMD_ESPI_SLAVE0_CLK_FREQ_16MHZ; + break; + } + + reg =3D readl(priv->base + AMD_ESPI_SLAVE0_CONFIG_REG); + reg &=3D ~(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 |=3D FIELD_PREP(AMD_ESPI_SLAVE0_CFG_CLK_FREQ, host_freq); + reg |=3D FIELD_PREP(AMD_ESPI_SLAVE0_CFG_IO_MODE, io_mode); + if (slave_cfg & ESPI_GENCFG_ALERT_MODE) + reg |=3D AMD_ESPI_SLAVE0_CFG_ALERT_MODE; + if (slave_cfg & ESPI_GENCFG_CRC_EN) + reg |=3D 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=3D%u host_freq=3D%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 =3D espi_controller_get_devdata(ctrl); + u32 hdr0 =3D amd_espi_cfg_hdr0(ESPI_CMD_SET_CONFIGURATION, slave_reg_addr= ); + int ret; + + dev_dbg(priv->dev, "SET_CONFIGURATION addr=3D0x%04x config=3D0x%08x\n", + slave_reg_addr, config); + ret =3D 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 =3D=3D 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 =3D readl(priv->base + AMD_ESPI_SLAVE0_CONFIG_REG); + + reg &=3D ~AMD_ESPI_SLAVE0_CFG_CLK_FREQ; + reg |=3D 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 =3D readl(priv->base + AMD_ESPI_SLAVE0_CONFIG_REG); + + dev_dbg(priv->dev, + "%s: I/O mode=3D%s frequency=3D%s (SLAVE0_CONFIG=3D0x%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 =3D espi_controller_get_devdata(ctrl); + u32 hdr0 =3D 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 =3D readl(priv->base + AMD_ESPI_SLAVE0_CONFIG_REG); + dev_dbg(priv->dev, "IN_BAND_RESET: SLAVE0_CONFIG before =3D 0x%08x\n", + cfg_before); + + amd_espi_lower_clk(priv); + + ret =3D 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 =3D readl(priv->base + AMD_ESPI_SLAVE0_CONFIG_REG); + if (ctrl->caps.alert_mode) + reg |=3D AMD_ESPI_SLAVE0_CFG_ALERT_MODE; + if (ctrl->caps.crc_supported) + reg |=3D 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 =3D 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 =3D 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 =3D 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 =3D readl(priv->base + AMD_ESPI_GLOBAL_CNTRL_REG0); + reg |=3D AMD_ESPI_GLOBAL0_WDG_EN | AMD_ESPI_GLOBAL0_WAIT_CHK_EN; + reg &=3D ~AMD_ESPI_GLOBAL0_WAIT_STATE; + reg |=3D FIELD_PREP(AMD_ESPI_GLOBAL0_WAIT_STATE, AMD_ESPI_WAIT_STATE_CNT); + writel(reg, priv->base + AMD_ESPI_GLOBAL_CNTRL_REG0); + + reg =3D 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 =3D readl(priv->base + AMD_ESPI_GLOBAL_CNTRL_REG1); + reg &=3D ~(AMD_ESPI_ERR_INT_MAP | AMD_ESPI_RGCMD_INT_MAP); + reg |=3D FIELD_PREP(AMD_ESPI_ERR_INT_MAP, AMD_ESPI_INT_MAP_SMI); + reg |=3D FIELD_PREP(AMD_ESPI_RGCMD_INT_MAP, AMD_ESPI_INT_MAP_SMI); + reg |=3D 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 =3D readl(priv->base + AMD_ESPI_SLAVE0_VW_MISC_CNTRL_REG); + reg &=3D ~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 =3D { + .setup =3D amd_espi_setup, + .get_configuration =3D amd_espi_get_configuration, + .set_configuration =3D amd_espi_set_configuration, + .inband_reset =3D amd_espi_inband_reset, +}; + +static u32 amd_espi_read_caps(struct amd_espi_data *priv, + struct espi_capabilities *caps) +{ + u32 cap =3D readl(priv->base + AMD_ESPI_MASTER_CAP_REG); + + dev_dbg(priv->dev, "MASTER_CAP=3D0x%08x\n", cap); + + caps->supported_channels =3D 0; + if (cap & AMD_ESPI_CAP_PR_SUPPORT) + caps->supported_channels |=3D ESPI_CHANNEL_PERIPH_SUPP; + if (cap & AMD_ESPI_CAP_VW_SUPPORT) + caps->supported_channels |=3D ESPI_CHANNEL_VWIRE_SUPP; + if (cap & AMD_ESPI_CAP_OOB_SUPPORT) + caps->supported_channels |=3D ESPI_CHANNEL_OOB_SUPP; + if (cap & AMD_ESPI_CAP_FLASH_SUPPORT) + caps->supported_channels |=3D ESPI_CHANNEL_FLASH_SUPP; + + switch (FIELD_GET(AMD_ESPI_CAP_CLK_FREQ, cap)) { + case 0x3: + caps->max_freq_mhz =3D ESPI_FREQ_66MHZ; + break; + case 0x1: + caps->max_freq_mhz =3D ESPI_FREQ_33MHZ; + break; + default: + caps->max_freq_mhz =3D ESPI_FREQ_16MHZ; + break; + } + switch (FIELD_GET(AMD_ESPI_CAP_IO_MODE, cap)) { + case 0x2: + caps->io_mode =3D ESPI_IO_MODE_QUAD; + break; + case 0x1: + caps->io_mode =3D ESPI_IO_MODE_DUAL; + break; + default: + caps->io_mode =3D ESPI_IO_MODE_SINGLE; + break; + } + + caps->alert_mode =3D !!(cap & AMD_ESPI_CAP_ALERT_MODE); + caps->crc_supported =3D !!(cap & AMD_ESPI_CAP_CRC_CHECK); + caps->periph_max_payload =3D FIELD_GET(AMD_ESPI_CAP_PR_MAX_SIZE, cap); + caps->vwire_max_count =3D FIELD_GET(AMD_ESPI_CAP_VW_MAX_SIZE, cap); + caps->oob_max_payload =3D FIELD_GET(AMD_ESPI_CAP_OOB_MAX_SIZE, cap); + caps->flash_max_payload =3D 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 =3D &pdev->dev; + struct espi_controller *ctrl; + struct amd_espi_data *priv; + int ret; + u32 cap; + + ctrl =3D 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 =3D espi_controller_get_devdata(ctrl); + priv->dev =3D dev; + priv->version =3D (enum amd_espi_versions)(uintptr_t) + device_get_match_data(dev); + + priv->base =3D devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) { + ret =3D dev_err_probe(dev, PTR_ERR(priv->base), + "failed to map registers\n"); + goto err_put; + } + + cap =3D amd_espi_read_caps(priv, &ctrl->caps); + ctrl->ops =3D &amd_espi_ops; + ctrl->max_targets =3D amd_espi_num_targets(cap); + + ret =3D espi_controller_register(ctrl); + if (ret) { + ret =3D 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[] =3D { + { "AMDI0070", AMD_ESPI_V1 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, amd_espi_acpi_match); + +static struct platform_driver amd_espi_driver =3D { + .driver =3D { + .name =3D "amd-espi", + .acpi_match_table =3D amd_espi_acpi_match, + }, + .probe =3D amd_espi_probe, + .remove =3D amd_espi_remove, +}; +module_platform_driver(amd_espi_driver); + +MODULE_AUTHOR("Krishnamoorthi M "); +MODULE_AUTHOR("Akshata MukundShetty "); +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 +#include + +#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=3D16.6/001=3D33/010=3D66 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 =3D 1, +}; + +struct amd_espi_data { + void __iomem *base; + struct device *dev; + enum amd_espi_versions version; +}; + +#endif /* _ESPI_AMD_H_ */ --=20 2.34.1