// 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");