diff --git a/MAINTAINERS b/MAINTAINERS index 4a8b0fd665ce..1ef1ec374eab 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1138,6 +1138,13 @@ F: drivers/char/hw_random/geode-rng.c F: drivers/crypto/geode* F: drivers/video/fbdev/geode/ +AMD HALO BOX RGB LED DRIVER +M: Mario Limonciello (AMD) +R: Yo-Jung Leo Lin (AMD) +L: platform-driver-x86@vger.kernel.org +S: Supported +F: drivers/platform/x86/amd/amd_halo_led.c + AMD HSMP DRIVER M: Naveen Krishna Chatradhi R: Carlos Bilbao diff --git a/drivers/platform/x86/amd/Kconfig b/drivers/platform/x86/amd/Kconfig index b813f9265368..a1a74ef6c859 100644 --- a/drivers/platform/x86/amd/Kconfig +++ b/drivers/platform/x86/amd/Kconfig @@ -34,6 +34,17 @@ config AMD_WBRF This mechanism will only be activated on platforms that advertise a need for it. +config AMD_HALO_LED + tristate "AMD Halo Box RGB LED Driver" + depends on ACPI_WMI && LEDS_CLASS_MULTICOLOR + help + This driver provides RGB LED control for AMD Halo Box devices + through the LED multicolor subsystem. The Halo Box light bar can + be controlled via sysfs to display any RGB color combination. + + To compile this driver as a module, choose M here: the module + will be called amd_halo_led. + config AMD_ISP_PLATFORM tristate "AMD ISP4 platform driver" depends on I2C && X86_64 && ACPI diff --git a/drivers/platform/x86/amd/Makefile b/drivers/platform/x86/amd/Makefile index f6ff0c837f34..2f467dbbfc8a 100644 --- a/drivers/platform/x86/amd/Makefile +++ b/drivers/platform/x86/amd/Makefile @@ -10,5 +10,6 @@ obj-$(CONFIG_AMD_PMC) += pmc/ obj-$(CONFIG_AMD_HSMP) += hsmp/ obj-$(CONFIG_AMD_PMF) += pmf/ obj-$(CONFIG_AMD_WBRF) += wbrf.o +obj-$(CONFIG_AMD_HALO_LED) += amd_halo_led.o obj-$(CONFIG_AMD_ISP_PLATFORM) += amd_isp4.o obj-$(CONFIG_AMD_HFI) += hfi/ diff --git a/drivers/platform/x86/amd/amd_halo_led.c b/drivers/platform/x86/amd/amd_halo_led.c new file mode 100644 index 000000000000..b21d956c7d96 --- /dev/null +++ b/drivers/platform/x86/amd/amd_halo_led.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD Halo Box RGB LED Driver + * + * Copyright (C) 2026 Advanced Micro Devices, Inc. + * + * This driver provides RGB LED control for AMD Halo Box devices through + * the LED multicolor subsystem. The Halo Box light bar can be controlled + * via sysfs to display any RGB color combination. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define AMD_HALO_GUID "081E747B-E028-4232-AF24-EAAEAB2B1E86" + +/* WMI method IDs from MOF */ +enum { + AMD_HALO_WMI_TURN_OFF = 0x04, + AMD_HALO_WMI_RGB = 0x07, +}; + +/* Arg0 of the AMD_HALO_WMI_RGB Method */ +enum { + AMD_HALO_RGB_CMD_GET = 0x0, + AMD_HALO_RGB_CMD_SET = 0x1, +}; + +/* Status codes from spec */ +#define AMD_HALO_STATUS_SUCCESS 0x0000 +#define AMD_HALO_STATUS_INVALID_PARAM 0xFFFD + +/* Brightness uses 0-100 range */ +#define AMD_HALO_MAX_HW_BRIGHTNESS 100 + +/** + * struct amd_halo_led_data - Driver private data + * @wdev: WMI device pointer + * @led_mc: LED multicolor class device + * @subled_info: RGB channel information + * @lock: Mutex to protect WMI calls + */ +struct amd_halo_led_data { + struct wmi_device *wdev; + struct led_classdev_mc led_mc; + struct mc_subled subled_info[3]; + struct mutex lock; +}; + +struct amd_halo_wmi_args { + __le32 arg0; + __le32 arg1; +}; + +struct amd_halo_wmi_args_rgb { + __le32 cmd; + __le32 red; + __le32 green; + __le32 blue; +}; + +struct amd_halo_wmi_output_rgb { + __le16 status; + u8 red; + u8 green; + u8 blue; +} __packed; + +static inline int wmi_status_to_err(u16 status) +{ + switch (status) { + case AMD_HALO_STATUS_SUCCESS: + return 0; + case AMD_HALO_STATUS_INVALID_PARAM: + return -EINVAL; + default: + return -EIO; + } +} + +static int __amd_halo_wmi_call(struct wmi_device *wdev, + u32 method_id, void *data, size_t length) +{ + struct wmi_buffer input = { + .length = length, + .data = data, + }; + struct wmi_buffer output = { }; + int ret; + + /* Return buffer per spec: Bytes[0:1] = Status (little-endian) */ + ret = wmidev_invoke_method(wdev, 0, method_id, + &input, &output, sizeof(__le16)); + if (ret) + return ret; + + __le16 *result_status __free(kfree) = output.data; + + return wmi_status_to_err(le16_to_cpu(*result_status)); +} + +/** + * amd_halo_wmi_turn_off - Turn off all LED channels + * @wdev: WMI device pointer + * + * Return: 0 on success, negative error code on failure + */ +static int amd_halo_wmi_turn_off(struct wmi_device *wdev) +{ + struct amd_halo_wmi_args args = { }; + + return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_TURN_OFF, &args, sizeof(args)); +} + +/** + * amd_halo_wmi_set_rgb - Set all RGB channels atomically + * @wdev: WMI device pointer + * @r: brightness for red channel (0 - 100) + * @g: brightness for green channel (0 - 100) + * @b: brightness for blue channel (0 - 100) + * + * Return: 0 on success, negative error code on failure + */ +static int amd_halo_wmi_set_rgb(struct wmi_device *wdev, u32 r, u32 g, u32 b) +{ + struct amd_halo_wmi_args_rgb args = { + .cmd = cpu_to_le32(AMD_HALO_RGB_CMD_SET), + .red = cpu_to_le32(r), + .green = cpu_to_le32(g), + .blue = cpu_to_le32(b), + }; + + if (r > AMD_HALO_MAX_HW_BRIGHTNESS || + g > AMD_HALO_MAX_HW_BRIGHTNESS || + b > AMD_HALO_MAX_HW_BRIGHTNESS) { + return -EINVAL; + } + + return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_RGB, &args, sizeof(args)); +} + +/** + * amd_halo_wmi_get_rgb - Get RGB values + * @wdev: WMI device pointer + * @r: output buffer for red value + * @g: output buffer for green value + * @b: output buffer for blue value + * + * Return: 0 on success, negative error code on failure + */ +static int amd_halo_wmi_get_rgb(struct wmi_device *wdev, u8 *r, u8 *g, u8 *b) +{ + struct amd_halo_wmi_args_rgb args = { + .cmd = cpu_to_le32(AMD_HALO_RGB_CMD_GET), + }; + struct wmi_buffer input = { + .length = sizeof(args), + .data = &args, + }; + struct wmi_buffer output = { }; + int ret; + + ret = wmidev_invoke_method(wdev, 0, AMD_HALO_WMI_RGB, + &input, &output, sizeof(struct amd_halo_wmi_output_rgb)); + if (ret) + return ret; + + struct amd_halo_wmi_output_rgb *data __free(kfree) = output.data; + + ret = wmi_status_to_err(le16_to_cpu(data->status)); + if (ret) + return ret; + + if (data->red > AMD_HALO_MAX_HW_BRIGHTNESS || + data->green > AMD_HALO_MAX_HW_BRIGHTNESS || + data->blue > AMD_HALO_MAX_HW_BRIGHTNESS) { + return -EPROTO; + } + + *r = data->red; + *g = data->green; + *b = data->blue; + + return 0; +} + +/** + * amd_halo_brightness_set - Set LED brightness + * @cdev: LED class device + * @brightness: Brightness value + * + * Return: 0 on success, negative error code on failure + */ +static int amd_halo_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct amd_halo_led_data *data = container_of(mc_cdev, + struct amd_halo_led_data, + led_mc); + u32 red_hw, green_hw, blue_hw; + int ret; + + guard(mutex)(&data->lock); + + led_mc_calc_color_components(mc_cdev, brightness); + + if (brightness == 0) + return amd_halo_wmi_turn_off(data->wdev); + + red_hw = mc_cdev->subled_info[0].brightness; + green_hw = mc_cdev->subled_info[1].brightness; + blue_hw = mc_cdev->subled_info[2].brightness; + + ret = amd_halo_wmi_set_rgb(data->wdev, red_hw, green_hw, blue_hw); + if (ret) + goto out; + + return 0; + +out: + /* + * Consider the light bar non-functional if AMD_HALO_WMI_RGB failed. + * Attempt to turn the LED off completely as clean-up. + */ + if (amd_halo_wmi_turn_off(data->wdev)) + dev_warn_ratelimited(&data->wdev->dev, "Failed to turn LED off on cleanup\n"); + + return ret; +} + +static int amd_halo_probe(struct wmi_device *wdev, const void *context) +{ + struct led_init_data led_init_data = { + .devicename = "amd_halo", + .default_label = "multicolor:" LED_FUNCTION_STATUS, + .devname_mandatory = true, + }; + struct amd_halo_led_data *data; + u8 r, g, b; + int ret; + + data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret = devm_mutex_init(&wdev->dev, &data->lock); + if (ret) + return ret; + + data->wdev = wdev; + dev_set_drvdata(&wdev->dev, data); + + data->subled_info[0].color_index = LED_COLOR_ID_RED; + data->subled_info[1].color_index = LED_COLOR_ID_GREEN; + data->subled_info[2].color_index = LED_COLOR_ID_BLUE; + + data->led_mc.led_cdev.brightness = AMD_HALO_MAX_HW_BRIGHTNESS; + data->led_mc.led_cdev.max_brightness = AMD_HALO_MAX_HW_BRIGHTNESS; + data->led_mc.led_cdev.brightness_set_blocking = amd_halo_brightness_set; + data->led_mc.led_cdev.flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN; + data->led_mc.num_colors = ARRAY_SIZE(data->subled_info); + data->led_mc.subled_info = data->subled_info; + + ret = amd_halo_wmi_get_rgb(wdev, &r, &g, &b); + if (ret) + return ret; + + data->subled_info[0].intensity = r; + data->subled_info[1].intensity = g; + data->subled_info[2].intensity = b; + + ret = devm_led_classdev_multicolor_register_ext(&wdev->dev, &data->led_mc, + &led_init_data); + if (ret) + return dev_err_probe(&wdev->dev, ret, + "Failed to register multicolor LED\n"); + return 0; +} + +static const struct wmi_device_id amd_halo_id_table[] = { + { .guid_string = AMD_HALO_GUID }, + { } +}; +MODULE_DEVICE_TABLE(wmi, amd_halo_id_table); + +static struct wmi_driver amd_halo_driver = { + .driver = { + .name = "amd_halo_led", + }, + .id_table = amd_halo_id_table, + .probe = amd_halo_probe, + .no_singleton = true, +}; + +module_wmi_driver(amd_halo_driver); + +MODULE_AUTHOR("Mario Limonciello (AMD) "); +MODULE_AUTHOR("Yo-Jung Leo Lin (AMD) "); +MODULE_DESCRIPTION("AMD Halo Box RGB LED Control Driver"); +MODULE_LICENSE("GPL");