diff --git a/Documentation/devicetree/bindings/misc/amd,apml-alertl.yaml b/Documentation/devicetree/bindings/misc/amd,apml-alertl.yaml new file mode 100644 index 00000000000000..6aed8ac525d858 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/amd,apml-alertl.yaml @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/amd,apml-alertl.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: > + Sideband Remote Management Interface (SB-RMI) and Temperature Sensor Interface + (SB-TSI) compliant AMD APML Alert_L GPIO. + +maintainers: + - Akshay Gupta + +description: | + Processors from AMD provide APML ALERT_L for BMC users to + monitor events. + APML Alert_L is asserted in multiple events, + - Machine Check Exception occurs within the system + - The processor alerts the SBI on system fatal error event + - Set by hardware as a result of a 0x71/0x72/0x73 command completion + - Set by firmware to indicate the completion of a mailbox operation + - High/Low Temperature Alert + + APML Alert_L module define uevents to notify userspace of the + alert event. + +properties: + compatible: + const: apml-alertl + + gpio: + maxItems: 1 + description: | + GPIO specifier for APML Alert_L line. Specify GPIO controller, GPIO pin + and polarity. + + socket-num: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 255 + description: | + Socket identifier for multi-socket AMD systems. This property identifies + which CPU socket this Alert_L GPIO is associated with. + +required: + - compatible + - gpio + - socket-num + +additionalProperties: false + +examples: + - | + /* Alert_L associated with Socket 0 */ + alertl_sock0 { + compatible = "apml-alertl"; + status = "okay"; + gpios = <<pi0_gpio 20 GPIO_ACTIVE_LOW>; + socket-num = /bits/ 8 <1>; + }; +... diff --git a/arch/arm64/boot/dts/aspeed/aspeed-bmc-amd-congo.dts b/arch/arm64/boot/dts/aspeed/aspeed-bmc-amd-congo.dts index c5894a34425d94..ee2371fa5437b6 100644 --- a/arch/arm64/boot/dts/aspeed/aspeed-bmc-amd-congo.dts +++ b/arch/arm64/boot/dts/aspeed/aspeed-bmc-amd-congo.dts @@ -882,6 +882,16 @@ }; }; +/ { + /* Alert_L associated with socket 0 */ + alertl_sock0 { + compatible = "apml-alertl"; + status = "okay"; + socket-num = /bits/ 8 <0>; + gpios = <<pi0_gpio 20 GPIO_ACTIVE_LOW>; + }; +}; + #ifdef I3C_HUB #define JESD300_SPD_I3C_MODE(bus, index, addr) \ diff --git a/arch/arm64/boot/dts/aspeed/aspeed-bmc-amd-kenya.dts b/arch/arm64/boot/dts/aspeed/aspeed-bmc-amd-kenya.dts index ec9f6c44b7b84d..d224ef3c8ac4d3 100755 --- a/arch/arm64/boot/dts/aspeed/aspeed-bmc-amd-kenya.dts +++ b/arch/arm64/boot/dts/aspeed/aspeed-bmc-amd-kenya.dts @@ -572,6 +572,16 @@ }; }; +/ { + /* Alert_L associated with socket 0 */ + alertl_sock0 { + compatible = "apml-alertl"; + status = "okay"; + socket-num = /bits/ 8 <0>; + gpios = <<pi0_gpio 20 GPIO_ACTIVE_LOW>; + }; +}; + #ifdef I3C_HUB #define JESD300_SPD_I3C_MODE(bus, index, addr) \ diff --git a/arch/arm64/boot/dts/aspeed/aspeed-bmc-amd-morocco.dts b/arch/arm64/boot/dts/aspeed/aspeed-bmc-amd-morocco.dts index 1c241dfc5ccc41..bd178cc7d1393a 100644 --- a/arch/arm64/boot/dts/aspeed/aspeed-bmc-amd-morocco.dts +++ b/arch/arm64/boot/dts/aspeed/aspeed-bmc-amd-morocco.dts @@ -970,6 +970,24 @@ }; }; +/ { + /* Alert_L associated with socket 0 */ + alertl_sock0 { + compatible = "apml-alertl"; + status = "okay"; + socket-num = /bits/ 8 <0>; + gpios = <<pi0_gpio 20 GPIO_ACTIVE_LOW>; + }; + + /* Alert_L associated with socket 1 */ + alertl_sock1 { + compatible = "apml-alertl"; + status = "okay"; + socket-num = /bits/ 8 <1>; + gpios = <<pi0_gpio 104 GPIO_ACTIVE_LOW>; + }; +}; + #ifdef I3C_HUB #define JESD300_SPD_I3C_MODE(bus, index, addr) \ diff --git a/arch/arm64/boot/dts/aspeed/aspeed-bmc-amd-nigeria.dts b/arch/arm64/boot/dts/aspeed/aspeed-bmc-amd-nigeria.dts index f061b9b01acedc..a0bd47a9df6471 100755 --- a/arch/arm64/boot/dts/aspeed/aspeed-bmc-amd-nigeria.dts +++ b/arch/arm64/boot/dts/aspeed/aspeed-bmc-amd-nigeria.dts @@ -741,6 +741,24 @@ }; }; +/ { + /* Alert_L associated with socket 0 */ + alertl_sock0 { + compatible = "apml-alertl"; + status = "okay"; + socket-num = /bits/ 8 <0>; + gpios = <<pi0_gpio 20 GPIO_ACTIVE_LOW>; + }; + + /* Alert_L associated with socket 1 */ + alertl_sock1 { + compatible = "apml-alertl"; + status = "okay"; + socket-num = /bits/ 8 <1>; + gpios = <<pi0_gpio 104 GPIO_ACTIVE_LOW>; + }; +}; + #ifdef I3C_HUB #define JESD300_SPD_I3C_MODE(bus, index, addr) \ diff --git a/drivers/misc/amd-apml/Kconfig b/drivers/misc/amd-apml/Kconfig index 6fed94971b0fb1..c0543ca63e1d4a 100644 --- a/drivers/misc/amd-apml/Kconfig +++ b/drivers/misc/amd-apml/Kconfig @@ -3,9 +3,22 @@ # AMD APML BMC interface drivers # +config APML_COMMON + tristate "AMD APML common device registration framework" + default n + help + Enable support for the AMD APML (Advanced Platform Management Link) + common device registration framework. This module provides a centralized + registry where APML devices (SBRMI and SBTSI) can register themselves + for discovery by other APML subsystems. + + This framework enables the Alert_L driver to safely discover and access + registered APML devices for alert processing across multiple sockets. + config APML_SBRMI tristate "Emulated SB-RMI interface driver over i3c bus" depends on I3C && !SENSORS_SBRMI + depends on APML_COMMON select REGMAP_I3C if I3C help If you say yes here you get support for emulated RMI @@ -17,6 +30,7 @@ config APML_SBRMI config APML_SBTSI tristate "Emulated SB-TSI interface driver over i3c bus" depends on I3C && !SENSORS_SBTSI + depends on APML_COMMON select REGMAP_I3C if I3C help If you say yes here you get support for emulated TSI @@ -24,3 +38,19 @@ config APML_SBTSI This driver can also be built as a module. If so, the module will be called apml_sbtsi. + +config APML_ALERTL + tristate "Emulated apml alertl interface driver over i3c bus" + depends on (APML_SBRMI || APML_SBTSI) && APML_COMMON + default n + help + This driver provides support for AMD APML Alert_L interface, + enabling alert notification from AMD SoCs to a BMC (Baseboard + Management Controller) over I2C/I3C bus. + + The driver monitors RAS and thermal events by handling alerts + from SBRMI and SBTSI devices, and reports these events to userspace + via uevents. Device configuration is managed via device tree bindings. + + This driver can also be built as a module. If so, the module will + be called apml_alertl.ko. diff --git a/drivers/misc/amd-apml/Makefile b/drivers/misc/amd-apml/Makefile index 7eef318915f122..a8884a496e490a 100644 --- a/drivers/misc/amd-apml/Makefile +++ b/drivers/misc/amd-apml/Makefile @@ -4,5 +4,7 @@ # AMD APML BMC interface drivers # +obj-$(CONFIG_APML_COMMON) += apml_common.o obj-$(CONFIG_APML_SBRMI) += sbrmi.o sbrmi-common.o obj-$(CONFIG_APML_SBTSI) += apml_sbtsi.o +obj-$(CONFIG_APML_ALERTL) += apml_alertl.o diff --git a/drivers/misc/amd-apml/apml_alertl.c b/drivers/misc/amd-apml/apml_alertl.c new file mode 100644 index 00000000000000..ac06caf6948f72 --- /dev/null +++ b/drivers/misc/amd-apml/apml_alertl.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * apml_alertl.c - Alert_L driver for AMD APML devices + * + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "apml_alertl.h" + +#define DRIVER_NAME "apml_alertl" + +#define RAS_STATUS_REG 0x4C +#define RMI_STATUS_REG 0x2 +#define TSI_STATUS_REG 0x2 +#define RAS_ALERT_STATUS BIT(1) +#define RAS_ALERT_ASYNC BIT(3) +#define TSI_STATUS_SHIFT 24 + +#define ENVP_SRC_INDX 0 +#define ENVP_BUS_NUM_INDX 1 +#define ENVP_PID_INDX 2 +#define ENVP_ADDR_INDX 3 +#define NUM_ENVP 5 + +MODULE_ALIAS("apml_alertl:" DRIVER_NAME); + +/* + * The driver generates uevents for Temperature and RAS alerts (both fatal and non-fatal). + * Event data contains address, bus number, PID (for I3C devices; 0 otherwise), and alert + * source information. See amd-apml.h for alert source details. + */ +static int send_uevent(u8 address, u8 bus_num, u32 alert_src, + u64 pid, struct device *dev) +{ + char *alert_source[NUM_ENVP]; + + alert_source[ENVP_SRC_INDX] = devm_kasprintf(dev, GFP_KERNEL, "SOURCE=0x%08x", alert_src); + alert_source[ENVP_BUS_NUM_INDX] = devm_kasprintf(dev, GFP_KERNEL, "BUS_NUM=%u", bus_num); + alert_source[ENVP_PID_INDX] = devm_kasprintf(dev, GFP_KERNEL, "PID=0x%016llx", pid); + alert_source[ENVP_ADDR_INDX] = devm_kasprintf(dev, GFP_KERNEL, "ADDRESS=0x%02x", address); + alert_source[NUM_ENVP - 1] = NULL; + + dev_dbg(dev, "Sending uevent: Addr:0x%x Src:0x%08x\n bus:%d pid: 0x%llx\n", + address, alert_src, bus_num, pid); + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, alert_source); + + return 0; +} + +static int handle_rmi_device_alert(struct apml_device_node *device_node, struct device *dev) +{ + int status = 0, ret; + u8 addr, bus_num; + u64 pid; + + if (!device_node->rmi_dev || !device_node->rmi_dev->regmap) { + dev_warn(dev, "Invalid RMI device found\n"); + return -EINVAL; + } + + /* Protects individual device state and regmap transactions */ + mutex_lock(&device_node->rmi_dev->lock); + /* Read RAS Status register */ + ret = regmap_read(device_node->rmi_dev->regmap, RAS_STATUS_REG, &status); + mutex_unlock(&device_node->rmi_dev->lock); + if (ret) + return ret; + + if (!status) { + /* No alert status - normal condition */ + return ret; + } + /* Extract device information based on bus type (I3C or I2C) */ + if (device_node->rmi_dev->i3cdev) { + /* I3C device path */ + addr = device_node->rmi_dev->dev_static_addr; + bus_num = device_node->rmi_dev->i3cdev->desc->dev->bus->id; + pid = device_node->rmi_dev->i3cdev->desc->info.pid; + } else if (device_node->rmi_dev->client) { + /* I2C device path */ + addr = device_node->rmi_dev->client->addr; + bus_num = device_node->rmi_dev->client->adapter->nr; + pid = 0; /* I2C devices do not have PID */ + } else { + return -EINVAL; + } + + if (!addr) + return -EINVAL; + + /* Send uevent for RAS alert */ + ret = send_uevent(addr, bus_num, status, pid, dev); + if (ret) { + dev_info(dev, "Failed to send uevent for RAS alert (device: 0x%x, err: %d)\n", + addr, ret); + } + + /* Clear RAS and RMI status registers */ + mutex_lock(&device_node->rmi_dev->lock); + ret = regmap_write(device_node->rmi_dev->regmap, RAS_STATUS_REG, status); + if (ret) + dev_warn(dev, "Failed to clear RAS status register (device: 0x%x): %d\n", + addr, ret); + + ret = regmap_write(device_node->rmi_dev->regmap, RMI_STATUS_REG, RAS_ALERT_ASYNC); + if (ret) + dev_warn(dev, "Failed to clear RMI status register (device: 0x%x): %d\n", + addr, ret); + + mutex_unlock(&device_node->rmi_dev->lock); + + return ret; /* Alert was processed */ +} + +/* Handle TSI device alerts */ +static int handle_tsi_device_alert(struct apml_device_node *device_node, struct device *dev) +{ + int status = 0, ret; + u8 addr, bus_num; + u64 pid; + + if (!device_node->tsi_dev || !device_node->tsi_dev->regmap) { + dev_warn(dev, "Invalid TSI device found\n"); + return -EINVAL; + } + + /* Protects individual device state and regmap transactions */ + mutex_lock(&device_node->tsi_dev->lock); + /* Read TSI Status register */ + ret = regmap_read(device_node->tsi_dev->regmap, TSI_STATUS_REG, &status); + mutex_unlock(&device_node->tsi_dev->lock); + + if (ret) { + dev_warn(dev, "Failed to read TSI status from device %d\n", ret); + return ret; + } + + if (!status) { + /* No alert status - normal condition */ + return ret; + } + + if (device_node->tsi_dev->i3cdev) { + addr = device_node->tsi_dev->dev_static_addr; + bus_num = device_node->tsi_dev->i3cdev->desc->dev->bus->id; + pid = device_node->tsi_dev->i3cdev->desc->info.pid; + } else if (device_node->tsi_dev->client) { + addr = device_node->tsi_dev->client->addr; + bus_num = device_node->tsi_dev->client->adapter->nr; + pid = 0; + } else { + return -EINVAL; + } + + if (!addr || !bus_num) + return -EINVAL; + + /* Send uevent for temperature alert (shifted to avoid RAS bit overlap) */ + ret = send_uevent(addr, bus_num, status << TSI_STATUS_SHIFT, pid, dev); + if (ret) { + dev_info(dev, "Failed to send uevent for temp alert (device: 0x%x, err: %d)\n", + addr, ret); + } + return ret; /* Alert was processed */ +} + +static void handle_apml_alerts(struct device *dev) +{ + struct apml_device_node *device_node; + int ret; + + mutex_lock(&apml_devices_lock); + list_for_each_entry(device_node, &apml_devices, list) { + /* Get a safe reference to the device node */ + if (!kref_get_unless_zero(&device_node->ref)) + continue; + + /* Device-specific alert processing */ + switch (device_node->dev_type) { + case APML_RMI_DEVICE: + ret = handle_rmi_device_alert(device_node, dev); + break; + case APML_TSI_DEVICE: + ret = handle_tsi_device_alert(device_node, dev); + break; + default: + dev_warn(dev, "Unknown device type: %d\n", device_node->dev_type); + ret = -EINVAL; + break; + } + + if (ret) { + dev_dbg(dev, "Alert processing failed for device type %d: %d\n", + device_node->dev_type, ret); + } + /* Always release the reference */ + apml_put_device_node(device_node); + } + mutex_unlock(&apml_devices_lock); +} + +/* Handles Alert_L interrupts by delegating to unified alert handler */ +static irqreturn_t alert_l_irq_thread_handler(int irq, void *dev_id) +{ + struct device *dev = (struct device *)dev_id; + + handle_apml_alerts(dev); + + return IRQ_HANDLED; +} + +static int apml_alertl_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct apml_alertl_data *oob_alert; + struct gpio_desc *alertl_gpiod; + int ret; + u8 socket_num = 0; + char *irq_name; + + oob_alert = devm_kzalloc(dev, sizeof(*oob_alert), GFP_KERNEL); + if (!oob_alert) + return -ENOMEM; + + oob_alert->dev = dev; + + /* Get the alert_l gpio */ + alertl_gpiod = devm_gpiod_get(dev, NULL, GPIOD_IN); + if (IS_ERR(alertl_gpiod)) + return PTR_ERR(alertl_gpiod); + + /* Get IRQ number from GPIO */ + ret = gpiod_to_irq(alertl_gpiod); + if (ret < 0) { + dev_err(dev, + "APML AlertL: No corresponding irq for gpio error: %d\n", + ret); + return ret; + } + + oob_alert->irq_num = ret; + + /* Try to read socket-id property from DTS */ + ret = of_property_read_u8(np, "socket-num", &socket_num); + if (!ret) { + irq_name = devm_kasprintf(dev, GFP_KERNEL, "apml_irq%u", socket_num); + if (!irq_name) + return -ENOMEM; + } else { + irq_name = devm_kstrdup(dev, "apml_irq", GFP_KERNEL); + if (!irq_name) + return -ENOMEM; + } + dev_info(dev, "APML Alert_L for socket %u, IRQ %u\n", socket_num, oob_alert->irq_num); + /* Register threaded IRQ handler */ + ret = devm_request_threaded_irq(dev, oob_alert->irq_num, + NULL, + alert_l_irq_thread_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + irq_name, + dev); + if (ret) { + dev_err(dev, "Cannot register IRQ:%u\n", oob_alert->irq_num); + return ret; + } + + platform_set_drvdata(pdev, oob_alert); + return 0; +} + +static int apml_alertl_remove(struct platform_device *pdev) +{ + struct apml_alertl_data *alertl_data = platform_get_drvdata(pdev); + + if (alertl_data) + /* Ensure any running interrupt handlers complete */ + synchronize_irq(alertl_data->irq_num); + + return 0; +} + +static const struct of_device_id apml_alertl_dt_ids[] = { + {.compatible = "apml-alertl", }, + {}, +}; +MODULE_DEVICE_TABLE(of, apml_alertl_dt_ids); + +static struct platform_driver apml_alertl_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(apml_alertl_dt_ids), + }, + .probe = apml_alertl_probe, + .remove = apml_alertl_remove, +}; + +module_platform_driver(apml_alertl_driver); + +MODULE_AUTHOR("Akshay Gupta "); +MODULE_AUTHOR("Naveenkrishna Chatradhi "); +MODULE_DESCRIPTION("AMD APML ALERT_L Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/amd-apml/apml_alertl.h b/drivers/misc/amd-apml/apml_alertl.h new file mode 100644 index 00000000000000..880ba2008bdf95 --- /dev/null +++ b/drivers/misc/amd-apml/apml_alertl.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _AMD_APML_ALERT_L__ +#define _AMD_APML_ALERT_L__ + +#include "apml_common.h" + +/* struct apml_alertl_data - APML Alert_L driver data structure */ +struct apml_alertl_data { + struct device *dev; + struct gpio_desc *alertl_gpiod; + int irq_num; +} __packed; + +#endif /*_AMD_APML_ALERT_L__*/ diff --git a/drivers/misc/amd-apml/apml_common.c b/drivers/misc/amd-apml/apml_common.c new file mode 100644 index 00000000000000..04b3a9d0769772 --- /dev/null +++ b/drivers/misc/amd-apml/apml_common.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * apml-common.c - Common registration system for APML devices + * + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "apml_common.h" + +/* Global list of registered APML devices */ +LIST_HEAD(apml_devices); +EXPORT_SYMBOL_GPL(apml_devices); + +/* + * Global Lock: Protects the shared apml_devices list during registration, + * unregistration, and iteration through all registered devices + */ +DEFINE_MUTEX(apml_devices_lock); +EXPORT_SYMBOL_GPL(apml_devices_lock); + +/* Release function for device node reference counting */ +static void apml_device_node_release(struct kref *ref) +{ + struct apml_device_node *node = container_of(ref, struct apml_device_node, ref); + + kfree(node); +} + +/* Get a reference to a device node - increases reference count */ +struct apml_device_node *apml_get_device_node(struct apml_device_node *node) +{ + if (node && kref_get_unless_zero(&node->ref)) + return node; + return NULL; +} +EXPORT_SYMBOL_GPL(apml_get_device_node); + +/* Put a reference to a device node - decreases reference count */ +void apml_put_device_node(struct apml_device_node *node) +{ + if (node) + kref_put(&node->ref, apml_device_node_release); +} +EXPORT_SYMBOL_GPL(apml_put_device_node); + +static void apml_add_device_node(struct apml_device_node *node) +{ + mutex_lock(&apml_devices_lock); + list_add_tail(&node->list, &apml_devices); + mutex_unlock(&apml_devices_lock); +} + +/* Device registration function */ +int apml_register_device(void *device, enum apml_device_type dev_type) +{ + struct apml_device_node *node; + struct apml_sbrmi_device *rmi_dev; + struct apml_sbtsi_device *tsi_dev; + + if (!device) { + pr_info("APML: Cannot register NULL device\n"); + return -EINVAL; + } + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) + return -ENOMEM; + + node->dev_type = dev_type; + kref_init(&node->ref); /* Initialize reference count to 1 */ + + switch (node->dev_type) { + case APML_RMI_DEVICE: + rmi_dev = (struct apml_sbrmi_device *)device; + + if (!rmi_dev->i3cdev && !rmi_dev->client) { + pr_err("APML: Invalid RMI device - no I2C or I3C device\n"); + kfree(node); + return -EINVAL; + } + node->rmi_dev = rmi_dev; + pr_info("APML: Registered SBRMI device at address 0x%x\n", + rmi_dev->i3cdev ? rmi_dev->dev_static_addr : rmi_dev->client->addr); + break; + case APML_TSI_DEVICE: + tsi_dev = (struct apml_sbtsi_device *)device; + + if (!tsi_dev->i3cdev && !tsi_dev->client) { + pr_info("APML: Invalid TSI device - no I2C or I3C device\n"); + kfree(node); + return -EINVAL; + } + node->tsi_dev = tsi_dev; + pr_info("APML: Registered SBTSI device at address 0x%x\n", + tsi_dev->i3cdev ? tsi_dev->dev_static_addr : tsi_dev->client->addr); + break; + default: + kfree(node); + return -EINVAL; + } + + apml_add_device_node(node); + return 0; +} + +/* Register an SBRMI device */ +int apml_register_sbrmi_device(struct apml_sbrmi_device *rmi_dev) +{ + return apml_register_device(rmi_dev, APML_RMI_DEVICE); +} +EXPORT_SYMBOL_GPL(apml_register_sbrmi_device); + +/* Register an SBTSI device*/ +int apml_register_sbtsi_device(struct apml_sbtsi_device *tsi_dev) +{ + return apml_register_device(tsi_dev, APML_TSI_DEVICE); +} +EXPORT_SYMBOL_GPL(apml_register_sbtsi_device); + +/* Device unregistration function*/ +void apml_unregister_device(void *device, enum apml_device_type dev_type) +{ + struct apml_device_node *node, *tmp, *found_node = NULL; + bool found = false; + + if (!device) + return; + + /* Find and mark device as invalid and removing */ + mutex_lock(&apml_devices_lock); + list_for_each_entry(node, &apml_devices, list) { + if (node->dev_type != dev_type) + continue; + + switch (dev_type) { + case APML_RMI_DEVICE: + if (node->rmi_dev == (struct apml_sbrmi_device *)device) + found = true; + break; + case APML_TSI_DEVICE: + if (node->tsi_dev == (struct apml_sbtsi_device *)device) + found = true; + break; + } + + if (found) { + /* Get a reference to keep node alive during removal */ + found_node = apml_get_device_node(node); + break; + } + } + mutex_unlock(&apml_devices_lock); + + if (!found_node) + return; + + /* Remove from list and release reference */ + mutex_lock(&apml_devices_lock); + list_for_each_entry_safe(node, tmp, &apml_devices, list) { + if (node == found_node) { + list_del(&node->list); + break; + } + } + mutex_unlock(&apml_devices_lock); + + /* Release our reference - this may free the node if no other references exist */ + apml_put_device_node(found_node); +} + +/* Unregister an SBRMI device */ +void apml_unregister_sbrmi_device(struct apml_sbrmi_device *rmi_dev) +{ + apml_unregister_device(rmi_dev, APML_RMI_DEVICE); +} +EXPORT_SYMBOL_GPL(apml_unregister_sbrmi_device); + +/* Unregister an SBTSI device */ +void apml_unregister_sbtsi_device(struct apml_sbtsi_device *tsi_dev) +{ + apml_unregister_device(tsi_dev, APML_TSI_DEVICE); +} +EXPORT_SYMBOL_GPL(apml_unregister_sbtsi_device); + +MODULE_AUTHOR("Akshay Gupta "); +MODULE_AUTHOR("sathya priya kumar "); +MODULE_AUTHOR("Naveenkrishna Chatradhi "); +MODULE_DESCRIPTION("AMD APML Common Device Registration"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/amd-apml/apml_common.h b/drivers/misc/amd-apml/apml_common.h new file mode 100644 index 00000000000000..a028e75f9be2fb --- /dev/null +++ b/drivers/misc/amd-apml/apml_common.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _AMD_APML_COMMON_H_ +#define _AMD_APML_COMMON_H_ + +#include +#include +#include "sbrmi-common.h" +#include "sbtsi-common.h" + +extern struct list_head apml_devices; +extern struct mutex apml_devices_lock; + +enum apml_device_type { + APML_RMI_DEVICE, + APML_TSI_DEVICE, +}; + +struct apml_device_node { + struct list_head list; + struct kref ref; + enum apml_device_type dev_type; + union { + struct apml_sbrmi_device *rmi_dev; + struct apml_sbtsi_device *tsi_dev; + }; +}; + +/* Function declarations - available when APML_COMMON is built */ +#if IS_ENABLED(CONFIG_APML_COMMON) +int apml_register_sbrmi_device(struct apml_sbrmi_device *rmi_dev); +void apml_unregister_sbrmi_device(struct apml_sbrmi_device *rmi_dev); +int apml_register_sbtsi_device(struct apml_sbtsi_device *tsi_dev); +void apml_unregister_sbtsi_device(struct apml_sbtsi_device *tsi_dev); +struct apml_device_node *apml_get_device_node(struct apml_device_node *node); +void apml_put_device_node(struct apml_device_node *node); +#else +/* Stub functions when APML_COMMON is not available */ +static inline int apml_register_sbrmi_device(struct apml_sbrmi_device *rmi_dev) +{ + return 0; +} + +static inline void apml_unregister_sbrmi_device(struct apml_sbrmi_device *rmi_dev) +{ + return 0; +} + +static inline int apml_register_sbtsi_device(struct apml_sbtsi_device *tsi_dev) +{ + return 0; +} + +static inline void apml_unregister_sbtsi_device(struct apml_sbtsi_device *tsi_dev) +{ + return 0 +} + +static inline struct apml_device_node *apml_get_device_node(struct apml_device_node *node) +{ + return NULL; +} + +static inline void apml_put_device_node(struct apml_device_node *node) +{ + return 0; +} + +#endif /* IS_ENABLED(CONFIG_APML_COMMON) */ +#endif /* _AMD_APML_COMMON_H_ */ diff --git a/drivers/misc/amd-apml/apml_sbtsi.c b/drivers/misc/amd-apml/apml_sbtsi.c index 02e040f352d99b..9cb91ea222f2ac 100644 --- a/drivers/misc/amd-apml/apml_sbtsi.c +++ b/drivers/misc/amd-apml/apml_sbtsi.c @@ -24,7 +24,8 @@ #include #include -#include +#include +#include "apml_common.h" /* * SB-TSI registers only support SMBus byte data access. "_INT" registers are @@ -69,15 +70,6 @@ #define SBTSI_DEC_OFFSET 5 #define SBTSI_DEC_MASK 0x7 -struct apml_sbtsi_device { - struct miscdevice sbtsi_misc_dev; - struct i2c_client *client; - struct i3c_device *i3cdev; - struct regmap *regmap; - struct mutex lock; - u8 dev_static_addr; -} __packed; - /* * From SB-TSI spec: CPU temperature readings and limit registers encode the * temperature in increments of 0.125 from 0 to 255.875. The "high byte" @@ -484,6 +476,7 @@ static int sbtsi_i3c_probe(struct i3c_device *i3cdev) }; struct regmap *regmap; const char *hwmon_dev_name; + int ret; dev_err(dev, "SBTSI: PID: %llx\n", i3cdev->desc->info.pid); if (!(I3C_PID_INSTANCE_ID(i3cdev->desc->info.pid) == 0 || @@ -533,7 +526,16 @@ static int sbtsi_i3c_probe(struct i3c_device *i3cdev) return PTR_ERR_OR_ZERO(hwmon_dev); } - return create_misc_tsi_device(tsi_dev, dev); + ret = create_misc_tsi_device(tsi_dev, dev); + if (ret) + return ret; + + /* Register with ALERT_L common system */ + ret = apml_register_sbtsi_device(tsi_dev); + if (ret) + dev_warn(dev, "Failed to register with ALERT_L common system: %d\n", ret); + + return 0; } #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0) @@ -551,6 +553,7 @@ static int sbtsi_i2c_probe(struct i2c_client *client) .val_bits = 8, }; const char *hwmon_dev_name; + int ret; tsi_dev = devm_kzalloc(dev, sizeof(struct apml_sbtsi_device), GFP_KERNEL); if (!tsi_dev) @@ -577,7 +580,16 @@ static int sbtsi_i2c_probe(struct i2c_client *client) if (!hwmon_dev) return PTR_ERR_OR_ZERO(hwmon_dev); - return create_misc_tsi_device(tsi_dev, dev); + ret = create_misc_tsi_device(tsi_dev, dev); + if (ret) + return ret; + + /* Register with ALERT_L common system */ + ret = apml_register_sbtsi_device(tsi_dev); + if (ret) + dev_warn(dev, "Failed to register with ALERT_L common system: %d\n", ret); + + return 0; } #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0) @@ -588,8 +600,10 @@ static void sbtsi_i3c_remove(struct i3c_device *i3cdev) { struct apml_sbtsi_device *tsi_dev = dev_get_drvdata(&i3cdev->dev); - if (tsi_dev) + if (tsi_dev) { + apml_unregister_sbtsi_device(tsi_dev); misc_deregister(&tsi_dev->sbtsi_misc_dev); + } dev_info(&i3cdev->dev, "Removed sbtsi-i3c driver\n"); #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0) @@ -605,8 +619,10 @@ static void sbtsi_i2c_remove(struct i2c_client *client) { struct apml_sbtsi_device *tsi_dev = dev_get_drvdata(&client->dev); - if (tsi_dev) + if (tsi_dev) { + apml_unregister_sbtsi_device(tsi_dev); misc_deregister(&tsi_dev->sbtsi_misc_dev); + } dev_info(&client->dev, "Removed sbtsi driver\n"); #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) diff --git a/drivers/misc/amd-apml/sbrmi-common.h b/drivers/misc/amd-apml/sbrmi-common.h index c59d3b52fb3946..3abc8357cb44f6 100644 --- a/drivers/misc/amd-apml/sbrmi-common.h +++ b/drivers/misc/amd-apml/sbrmi-common.h @@ -35,6 +35,4 @@ int rmi_cpuid_read(struct apml_sbrmi_device *rmi_dev, struct apml_message *msg); int rmi_mailbox_xfer(struct apml_sbrmi_device *rmi_dev, struct apml_message *msg); -int sbrmi_match_i3c(struct device *dev, const void *data); -int sbrmi_match_i2c(struct device *dev, const void *data); #endif /*_AMD_APML_SBRMI_H_*/ diff --git a/drivers/misc/amd-apml/sbrmi.c b/drivers/misc/amd-apml/sbrmi.c index 83e168a233421e..ecaa0e95b86142 100644 --- a/drivers/misc/amd-apml/sbrmi.c +++ b/drivers/misc/amd-apml/sbrmi.c @@ -22,6 +22,7 @@ #include #include "sbrmi-common.h" +#include "apml_common.h" /* Do not allow setting negative power limit */ #define SBRMI_PWR_MIN 0 @@ -491,6 +492,7 @@ static int sbrmi_i2c_probe(struct i2c_client *client) struct device *hwmon_dev; struct apml_sbrmi_device *rmi_dev; const char *hwmon_dev_name; + int ret; rmi_dev = devm_kzalloc(dev, sizeof(struct apml_sbrmi_device), GFP_KERNEL); if (!rmi_dev) @@ -517,7 +519,15 @@ static int sbrmi_i2c_probe(struct i2c_client *client) return PTR_ERR_OR_ZERO(hwmon_dev); init_completion(&rmi_dev->misc_fops_done); - return create_misc_rmi_device(rmi_dev, dev); + ret = create_misc_rmi_device(rmi_dev, dev); + if (ret) + return ret; + /* Register with ALERT_L common system */ + ret = apml_register_sbrmi_device(rmi_dev); + if (ret) + dev_warn(dev, "Failed to register with ALERT_L common system: %d\n", ret); + + return ret; } static int sbrmi_i3c_reg_read(struct i3c_device *i3cdev, int reg_size, u32 *val) @@ -713,7 +723,15 @@ static int sbrmi_i3c_probe(struct i3c_device *i3cdev) } init_completion(&rmi_dev->misc_fops_done); - return create_misc_rmi_device(rmi_dev, dev); + ret = create_misc_rmi_device(rmi_dev, dev); + if (ret) + return ret; + /* Register with ALERT_L common system */ + ret = apml_register_sbrmi_device(rmi_dev); + if (ret) + dev_warn(dev, "Failed to register with ALERT_L common system: %d\n", ret); + + return ret; } #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) @@ -730,7 +748,8 @@ static void sbrmi_i2c_remove(struct i2c_client *client) #else return; #endif - + /* Unregister from APML common system */ + apml_unregister_sbrmi_device(rmi_dev); /* * Set the no_new_trans so no new transaction can * occur in sbrmi_ioctl @@ -770,7 +789,8 @@ static void sbrmi_i3c_remove(struct i3c_device *i3cdev) #else return; #endif - + /* Unregister from APML common system */ + apml_unregister_sbrmi_device(rmi_dev); /* * Set the no_new_trans so no new transaction can * occur in sbrmi_ioctl @@ -845,26 +865,6 @@ static struct i3c_driver sbrmi_i3c_driver = { module_i3c_i2c_driver(sbrmi_i3c_driver, &sbrmi_driver) -int sbrmi_match_i3c(struct device *dev, const void *data) -{ - const struct device_node *node = (const struct device_node *)data; - - if (dev->of_node == node && dev->driver == &sbrmi_i3c_driver.driver) - return 1; - return 0; -} -EXPORT_SYMBOL_GPL(sbrmi_match_i3c); - -int sbrmi_match_i2c(struct device *dev, const void *data) -{ - const struct device_node *node = (const struct device_node *)data; - - if (dev->of_node == node && dev->driver == &sbrmi_driver.driver) - return 1; - return 0; -} -EXPORT_SYMBOL_GPL(sbrmi_match_i2c); - MODULE_AUTHOR("Akshay Gupta "); MODULE_AUTHOR("Naveenkrishna Chatradhi "); MODULE_DESCRIPTION("Hwmon driver for AMD SB-RMI emulated sensor"); diff --git a/drivers/misc/amd-apml/sbtsi-common.h b/drivers/misc/amd-apml/sbtsi-common.h new file mode 100644 index 00000000000000..7fdd1e098ac491 --- /dev/null +++ b/drivers/misc/amd-apml/sbtsi-common.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * sbtsi-common.h - hwmon driver for a SBI Temperature Sensor Interface (SB-TSI) + * compliant AMD SoC temperature device. + * Also register to misc driver with an IOCTL. + * + * Copyright (c) 2020, Google Inc. + * Copyright (c) 2020, Kun Yi + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _AMD_SBTSI_COMMON_H_ +#define _AMD_SBTSI_COMMON_H_ + +struct apml_sbtsi_device { + struct miscdevice sbtsi_misc_dev; + struct i2c_client *client; + struct i3c_device *i3cdev; + struct regmap *regmap; + struct mutex lock; //lock for tsi devices + u8 dev_static_addr; +} __packed; + +#endif diff --git a/include/uapi/linux/amd-apml.h b/include/uapi/linux/amd-apml.h index d52f3a172faf83..cfcafe555b0383 100644 --- a/include/uapi/linux/amd-apml.h +++ b/include/uapi/linux/amd-apml.h @@ -1,17 +1,41 @@ /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ /* - * Copyright (C) 2021-2022 Advanced Micro Devices, Inc. + * Copyright (C) 2025 Advanced Micro Devices, Inc. */ #ifndef _AMD_APML_H_ #define _AMD_APML_H_ #include +#ifndef BIT +#define BIT(n) (1U << (n)) +#endif + /* - * Currently signal 33 to 64 are unused, - * using user signal number from that range + * APML RAS and Temperature alert source. Uevent + * returns 32 bit value which is sent to userspace. + * [23:0]: defined for RAS Alerts + * SBRMI RAS register 0x4C. RAS alert register bits(0-6) + * + * [31:24] : defined for Temperature Alerts + * SBTSI Temp register 0x02. socket temperature alert bits(3 & 4) + * + * Note: Additional Alert_L bit definition may be added in future + * for RAS alert extension + * */ -#define USR_SIGNAL 44 + +enum apml_alert_src { + APML_FATAL_ALERT = BIT(0), + APML_FCH_ALERT = BIT(1), + APML_RESET_CTRL_ALERT = BIT(2), + APML_MCA_ALERT = BIT(3), + APML_DRAM_CECC_ALERT = BIT(4), + APML_PCIE_ALERT = BIT(5), + APML_CPU_SHUTDOWN_ALERT = BIT(6), + APML_TEMP_LOW_ALERT = BIT(27), + APML_TEMP_HIGH_ALERT = BIT(28), +}; enum apml_protocol { APML_CPUID = 0x1000,