The Acer Predator Helios 500's keyboard has four zones of RGB LEDs. This driver allows them to be controlled from Linux. Signed-off-by: leo60228 <leo@xxxxxxxxx> --- MAINTAINERS | 6 ++ drivers/platform/x86/Kconfig | 13 +++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/acer-led.c | 156 ++++++++++++++++++++++++++++++++ 4 files changed, 176 insertions(+) create mode 100644 drivers/platform/x86/acer-led.c diff --git a/MAINTAINERS b/MAINTAINERS index bc0ceef87..f647ea81c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -327,6 +327,12 @@ S: Maintained W: http://piie.net/?section=acerhdf F: drivers/platform/x86/acerhdf.c +ACER PREDATOR LAPTOP LEDS +M: leo60228 <leo@xxxxxxxxx> +L: platform-driver-x86@xxxxxxxxxxxxxxx +S: Maintained +F: drivers/platform/x86/acer-led.c + ACER WMI LAPTOP EXTRAS M: "Lee, Chun-Yi" <jlee@xxxxxxxx> L: platform-driver-x86@xxxxxxxxxxxxxxx diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 60592fb88..7dc4fd1ef 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -190,6 +190,19 @@ config ACER_WMI If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M here. +config ACER_LED + tristate "Acer Predator Laptop LEDs" + depends on ACPI + depends on ACPI_WMI + depends on LEDS_CLASS + depends on NEW_LEDS + help + This is a driver for the RGB keyboard LEDs in Acer Predator laptops. + It was designed for the Acer Predator Helios 500. + + If you choose to compile this driver as a module the module will be + called acer-led. + config AMD_PMC tristate "AMD SoC PMC driver" depends on ACPI && PCI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index dcc8cdb95..36722207b 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_GIGABYTE_WMI) += gigabyte-wmi.o obj-$(CONFIG_ACERHDF) += acerhdf.o obj-$(CONFIG_ACER_WIRELESS) += acer-wireless.o obj-$(CONFIG_ACER_WMI) += acer-wmi.o +obj-$(CONFIG_ACER_LED) += acer-led.o # AMD obj-$(CONFIG_AMD_PMC) += amd-pmc.o diff --git a/drivers/platform/x86/acer-led.c b/drivers/platform/x86/acer-led.c new file mode 100644 index 000000000..82a7b099a --- /dev/null +++ b/drivers/platform/x86/acer-led.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Acer LED Driver + * + * Copyright (C) 2021 leo60228 + */ + +#include <linux/acpi.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/leds.h> +#include <linux/wmi.h> + +MODULE_AUTHOR("leo60228"); +MODULE_DESCRIPTION("Acer LED Driver"); +MODULE_LICENSE("GPL"); + +#define ACER_LED_METHOD_GUID "7A4DDFE7-5B5D-40B4-8595-4408E0CC7F56" + +struct acer_led { + char name[32]; + struct led_classdev cdev; + struct acer_led_zone *zone; +}; + +struct acer_led_zone { + int id; + struct acer_led red; + struct acer_led green; + struct acer_led blue; +}; + +struct acer_led_priv { + struct acer_led_zone zones[4]; +}; + +struct led_zone_set_param { + u8 zone; + u8 red; + u8 green; + u8 blue; +} __packed; + +static int acer_led_update_zone(struct acer_led_zone *zone) +{ + int status; + + struct led_zone_set_param set_params = { + .zone = 1 << zone->id, + .red = zone->red.cdev.brightness, + .green = zone->green.cdev.brightness, + .blue = zone->blue.cdev.brightness, + }; + struct acpi_buffer set_input = { + sizeof(struct led_zone_set_param), + &set_params + }; + + status = wmi_evaluate_method( + ACER_LED_METHOD_GUID, 0, 0x6, &set_input, NULL); + if (ACPI_FAILURE(status)) + return -EINVAL; + + return 0; +} + +static int acer_led_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct acer_led *led = container_of(cdev, struct acer_led, cdev); + + return acer_led_update_zone(led->zone); +} + +static int acer_led_setup_led(struct wmi_device *wdev, + struct acer_led *led, + struct acer_led_zone *zone, + const char *color) +{ + snprintf(led->name, sizeof(led->name), "%s:kbd_backlight-%d", + color, zone->id + 1); + led->cdev.name = led->name; + led->cdev.max_brightness = 255; + led->cdev.brightness_set_blocking = acer_led_set; + led->zone = zone; + + return devm_led_classdev_register(&wdev->dev, &led->cdev); +} + +static int acer_led_setup(struct wmi_device *wdev) +{ + struct acer_led_priv *priv = dev_get_drvdata(&wdev->dev); + int i, err = 0; + + for (i = 0; i < 4; i++) { + priv->zones[i].id = i; + + err = acer_led_setup_led(wdev, &priv->zones[i].red, + &priv->zones[i], "red"); + if (err) + return err; + + err = acer_led_setup_led(wdev, &priv->zones[i].green, + &priv->zones[i], "green"); + if (err) + return err; + + err = acer_led_setup_led(wdev, &priv->zones[i].blue, + &priv->zones[i], "blue"); + if (err) + return err; + } + + return 0; +} + +static int acer_led_probe(struct wmi_device *wdev, const void *context) +{ + struct acer_led_priv *priv; + + priv = devm_kzalloc( + &wdev->dev, sizeof(struct acer_led_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + dev_set_drvdata(&wdev->dev, priv); + + return acer_led_setup(wdev); +} + +static const struct wmi_device_id acer_led_id_table[] = { + { .guid_string = ACER_LED_METHOD_GUID }, + { }, +}; + +static struct wmi_driver acer_led_driver = { + .driver = { + .name = "acer-led", + }, + .id_table = acer_led_id_table, + .probe = acer_led_probe, +}; + +static int __init acer_led_init(void) +{ + return wmi_driver_register(&acer_led_driver); +} +late_initcall(acer_led_init); + +static void __exit acer_led_exit(void) +{ + wmi_driver_unregister(&acer_led_driver); +} +module_exit(acer_led_exit); + +MODULE_DEVICE_TABLE(wmi, acer_led_id_table); base-commit: 009c9aa5be652675a06d5211e1640e02bbb1c33d -- 2.28.0