All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>
To: "Коненко Андрей Викторович" <admin@aquinas.su>
Cc: LKML <linux-kernel@vger.kernel.org>,
	platform-driver-x86@vger.kernel.org,
	 Hans de Goede <hansg@kernel.org>
Subject: Re: [PATCH v10] platform/x86: hp-wmi: Add multicolor LED support for HP keyboard backlight
Date: Wed, 1 Jul 2026 13:54:20 +0300 (EEST)	[thread overview]
Message-ID: <ea021a4e-07de-e736-d095-d657aeda1945@linux.intel.com> (raw)
In-Reply-To: <B3i0fyryR3yLLMsNVCrg3Q@aquinas.su>

[-- Attachment #1: Type: text/plain, Size: 17882 bytes --]

On Sun, 14 Jun 2026, Коненко Андрей Викторович wrote:

> From: Edip Hazuri <edip@medip.dev>
> 
> Add support for the HP keyboard RGB backlight found on HP OMEN and
> HP Victus laptops. These keyboards expose per-zone RGB control through
> WMI commands.
> 
> Register multicolor LED class devices for each keyboard zone (up to 4
> zones, depending on the keyboard type). Each zone exposes individual
> red, green, and blue channels via the multicolor LED subsystem.
> 
> Also hardware-initiated brightness changes (e.g. via the keyboard
> backlight hotkey, mostly fn+f4) are reported.
> 
> The color data is stored in a 128-byte color table managed by the
> firmware, with RGB values starting at offset 25, packed sequentially
> per zone.
> 
> Signed-off-by: Konenko Andrey Viktorovich <admin@aquinas.su>
> 
> ---
> Changes since v9:
>  - Fixed corrupted long lines
>  - Fixed changelog
> 
> Changes since v8:
>  - No changes (It was mistake to resend email without any fix)
> 
> Changes since v7:
>  - The LED class device names changed to be consistent with the documentation.
> 
> Changes since v6:
>  - The LED class device names have been made more consistent with the 
> documentation, in accordance with patch https://lore.kernel.org/linux-leds/
> 20260504145434.12746-1-johannes.goede@oss.qualcomm.com/
> underscores have been replaced with dashes where indicated.
> 
> Changes since v5:
>  - LED class device names changed from a number to using a descriptive name 
> for each zone.
> 
> v4: https://lore.kernel.org/all/20260303084022.7223-3-edip@medip.dev/
> 
> Changes since v4:
>  - Fix circular dependencies
> 
> Changes since v3:
>  - Merge the changes into a single commit
> 
> Changes since v1:
>  - Fix mentioned style errors
>  - Add Kconfig dependencies
> 
> 
> ---
> 
> diff --git a/drivers/platform/x86/hp/Kconfig b/drivers/platform/x86/hp/Kconfig
> index dd51491b9bcd..2a1841cbec76 100644
> --- a/drivers/platform/x86/hp/Kconfig
> +++ b/drivers/platform/x86/hp/Kconfig
> @@ -45,6 +45,8 @@ config HP_WMI
>         select INPUT_SPARSEKMAP
>         select ACPI_PLATFORM_PROFILE
>         select HWMON
> +       select LEDS_CLASS
> +       select LEDS_CLASS_MULTICOLOR
>         help
>           Say Y here if you want to support WMI-based hotkeys on HP laptops and
>           to read data from WMI such as docking or ambient light sensor state.
> diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c
> index d1cc6e7d176c..19da18f68337 100644
> --- a/drivers/platform/x86/hp/hp-wmi.c
> +++ b/drivers/platform/x86/hp/hp-wmi.c
> @@ -14,6 +14,7 @@
>  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>  
>  #include <linux/acpi.h>
> +#include <linux/array_size.h>
>  #include <linux/cleanup.h>
>  #include <linux/compiler_attributes.h>
>  #include <linux/dmi.h>
> @@ -23,6 +24,8 @@
>  #include <linux/input.h>
>  #include <linux/input/sparse-keymap.h>
>  #include <linux/kernel.h>
> +#include <linux/led-class-multicolor.h>
> +#include <linux/leds.h>
>  #include <linux/limits.h>
>  #include <linux/minmax.h>
>  #include <linux/module.h>
> @@ -57,6 +60,7 @@ enum hp_ec_offsets {
>  
>  #define HP_FAN_SPEED_AUTOMATIC  0x00
>  #define HP_POWER_LIMIT_DEFAULT  0x00
> +#define HP_COLOR_TABLE_PADDING  25
>  #define HP_POWER_LIMIT_NO_CHANGE 0xFF
>  
>  #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required
> @@ -97,6 +101,14 @@ enum hp_thermal_profile {
>         HP_THERMAL_PROFILE_QUIET                        = 0x03,
>  };
>  
> +enum hp_keyboard_type {
> +       HP_KEYBOARD_TYPE_NOBACKLIGHT                = 0x0,
> +       HP_KEYBOARD_TYPE_FOURZONE_WITH_NUMPAD       = 0x1,
> +       HP_KEYBOARD_TYPE_FOURZONE_WITHOUT_NUMPAD    = 0x2,
> +       HP_KEYBOARD_TYPE_RGB_PER_KEY                = 0x3,
> +       HP_KEYBOARD_TYPE_SINGLEZONE_WITH_NUMPAD     = 0x4,
> +       HP_KEYBOARD_TYPE_SINGLEZONE_WITHOUT_NUMPAD  = 0x5,
> +};
>  
>  struct thermal_profile_params {
>         u8 performance;
> @@ -338,16 +350,27 @@ enum hp_wmi_gm_commandtype {
>         HPWMI_GET_GPU_THERMAL_MODES_QUERY       = 0x21,
>         HPWMI_SET_GPU_THERMAL_MODES_QUERY       = 0x22,
>         HPWMI_SET_POWER_LIMITS_QUERY            = 0x29,
> +       HPWMI_GET_KEYBOARD_TYPE_QUERY           = 0x2b,
>         HPWMI_VICTUS_S_FAN_SPEED_GET_QUERY      = 0x2D,
>         HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY      = 0x2E,
>         HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY      = 0x2F,
>  };
>  
> +enum hp_wmi_backlight_commandtype {
> +       HPWMI_BACKLIGHT_COLOR_GET_QUERY         = 0x02,
> +       HPWMI_BACKLIGHT_COLOR_SET_QUERY         = 0x03,
> +       HPWMI_BACKLIGHT_BRIGHTNESS_GET_QUERY    = 0x04,
> +       HPWMI_BACKLIGHT_BRIGHTNESS_SET_QUERY    = 0x05,
> +       HPWMI_BACKLIGHT_SET_OFF_QUERY           = 0x64,
> +       HPWMI_BACKLIGHT_SET_ON_QUERY            = 0xE4,
> +};
> +
>  enum hp_wmi_command {
>         HPWMI_READ      = 0x01,
>         HPWMI_WRITE     = 0x02,
>         HPWMI_ODM       = 0x03,
>         HPWMI_GM        = 0x20008,
> +       HPWMI_BACKLIGHT = 0x20009,
>  };
>  
>  enum hp_wmi_hardware_mask {
> @@ -355,6 +378,24 @@ enum hp_wmi_hardware_mask {
>         HPWMI_TABLET_MASK       = 0x04,
>  };
>  
> +struct hp_kbd_led_priv {
> +       int zone;               /* Zone index (0-3) */
> +       enum led_brightness last_brightness;  /* Brightness before turning off */
> +};
> +
> +struct hp_mc_leds {
> +       struct led_classdev_mc devices[4];
> +       struct hp_kbd_led_priv priv[4];
> +};
> +
> +/* zone naming for 4-zone keyboards (HP Omen) */
> +static const char *const hp_zone_names_4[] = {
> +       [0] = "zoned_backlight-right",
> +       [1] = "zoned_backlight-center",
> +       [2] = "zoned_backlight-left",
> +       [3] = "zoned_backlight-wasd",
> +};
> +
>  struct bios_return {
>         u32 sigpass;
>         u32 return_code;
> @@ -435,6 +476,7 @@ static DEFINE_MUTEX(active_platform_profile_lock);
>  static struct input_dev *hp_wmi_input_dev;
>  static struct input_dev *camera_shutter_input_dev;
>  static struct platform_device *hp_wmi_platform_dev;
> +static struct hp_mc_leds hp_multicolor_leds;
>  static struct device *platform_profile_device;
>  static struct notifier_block platform_power_source_nb;
>  static enum platform_profile_option active_platform_profile;
> @@ -1122,6 +1164,8 @@ static struct attribute *hp_wmi_attrs[] = {
>  };
>  ATTRIBUTE_GROUPS(hp_wmi);
>  
> +static void hp_kbd_brightness_set_by_hwd(u32 event_data);
> +
>  static void hp_wmi_notify(union acpi_object *obj, void *context)
>  {
>         u32 event_id, event_data;
> @@ -1222,6 +1266,7 @@ static void hp_wmi_notify(union acpi_object *obj, void *context)
>         case HPWMI_PROXIMITY_SENSOR:
>                 break;
>         case HPWMI_BACKLIT_KB_BRIGHTNESS:
> +               hp_kbd_brightness_set_by_hwd(event_data);
>                 break;
>         case HPWMI_PEAKSHIFT_PERIOD:
>                 break;
> @@ -1484,6 +1529,241 @@ static int __init hp_wmi_rfkill2_setup(struct platform_device *device)
>         return err;
>  }
>  
> +static struct hp_kbd_led_priv *hp_led_get_priv(struct led_classdev *led_cdev)
> +{
> +       struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
> +       int zone = mc_cdev - hp_multicolor_leds.devices;
> +
> +       return &hp_multicolor_leds.priv[zone];
> +}
> +
> +static int hp_kbd_backlight_set_rgb_color(int zone, int red, int green, int blue)
> +{
> +       u8 color_table[128];
> +       int ret;
> +
> +       /*
> +        * Get the current color table and then change only the relevant parts.
> +        */
> +       ret = hp_wmi_perform_query(HPWMI_BACKLIGHT_COLOR_GET_QUERY,
> +                                  HPWMI_BACKLIGHT, color_table,
> +                                  zero_if_sup(color_table),
> +                                  sizeof(color_table));
> +       if (ret)
> +               return ret;
> +
> +       /*
> +        * RGB color data starts at offset 25 +3 per zone (r g b)
> +        * e.g. if zone 1 starts in 25 zone 2 starts in 28
> +        */
> +       color_table[HP_COLOR_TABLE_PADDING + zone * 3]     = red;
> +       color_table[HP_COLOR_TABLE_PADDING + zone * 3 + 1] = green;
> +       color_table[HP_COLOR_TABLE_PADDING + zone * 3 + 2] = blue;
> +
> +       ret = hp_wmi_perform_query(HPWMI_BACKLIGHT_COLOR_SET_QUERY, HPWMI_BACKLIGHT,
> +                                  color_table, sizeof(color_table), sizeof(color_table));
> +       if (ret < 0)
> +               return ret;
> +       if (ret)
> +               return -EINVAL;
> +
> +       return 0;
> +}
> +
> +static bool hp_kbd_backlight_is_on(void)
> +{
> +       u8 data;
> +       int ret;
> +
> +       ret = hp_wmi_perform_query(HPWMI_BACKLIGHT_BRIGHTNESS_GET_QUERY, HPWMI_BACKLIGHT, &data,
> +                               sizeof(data), sizeof(data));
> +       if (ret)
> +               return false;
> +
> +       return data == HPWMI_BACKLIGHT_SET_ON_QUERY;
> +}
> +
> +static int hp_kbd_set_brightness(struct led_classdev *led_cdev,
> +                                       enum led_brightness brightness)
> +{
> +       struct hp_kbd_led_priv *priv = hp_led_get_priv(led_cdev);
> +       struct led_classdev_mc *mc_cdev;
> +       struct led_classdev_mc *device;
> +       int red, green, blue;
> +       int ret;
> +
> +       if (!hp_kbd_backlight_is_on()) {
> +               u8 data = HPWMI_BACKLIGHT_SET_ON_QUERY;
> +
> +               ret = hp_wmi_perform_query(HPWMI_BACKLIGHT_BRIGHTNESS_SET_QUERY,
> +                                          HPWMI_BACKLIGHT, &data,
> +                                          sizeof(data), sizeof(data));
> +               if (ret)
> +                       return ret;
> +
> +               /*
> +                * Turning the backlight on via WMI turns all zones,
> +                * so we need to restore the other zones' off state.
> +                */
> +               for (int i = 0; i < ARRAY_SIZE(hp_multicolor_leds.devices); i++) {
> +                       if (i == priv->zone)
> +                               continue;
> +
> +                       device = &hp_multicolor_leds.devices[i];
> +                       if (!device->led_cdev.name)
> +                               continue;
> +                       hp_kbd_backlight_set_rgb_color(i,
> +                               device->subled_info[0].brightness,
> +                               device->subled_info[1].brightness,
> +                               device->subled_info[2].brightness);
> +               }
> +       }
> +
> +       led_cdev->brightness = brightness;
> +
> +       mc_cdev = lcdev_to_mccdev(led_cdev);
> +       led_mc_calc_color_components(mc_cdev, brightness);
> +
> +       red = mc_cdev->subled_info[0].brightness;
> +       green = mc_cdev->subled_info[1].brightness;
> +       blue = mc_cdev->subled_info[2].brightness;
> +
> +       return hp_kbd_backlight_set_rgb_color(priv->zone, red, green, blue);
> +}
> +
> +static void hp_kbd_brightness_set_by_hwd(u32 event_data)
> +{
> +       struct device *dev = &hp_wmi_platform_dev->dev;
> +       struct led_classdev *led_cdev;
> +       struct hp_kbd_led_priv *priv;
> +       u8 brightness;
> +
> +       for (int zone = 0; zone < ARRAY_SIZE(hp_multicolor_leds.devices); zone++) {
> +               if (!hp_multicolor_leds.devices[zone].led_cdev.name)
> +                       continue;
> +
> +               led_cdev = &hp_multicolor_leds.devices[zone].led_cdev;
> +               if (!led_cdev->dev)
> +                       continue;
> +
> +               priv = hp_led_get_priv(led_cdev);
> +
> +               if (event_data == 0x2) {
> +                       brightness = priv->last_brightness ? : LED_FULL;
> +               } else if (event_data == 0x0) {

Instead of literals, can these two be named with defines?

It would also seem like switch/case is more appropriate construct here 
that if/else forest.

> +                       priv->last_brightness = led_cdev->brightness;
> +                       brightness = LED_OFF;
> +               } else {
> +                       dev_warn(dev, "Unknown keyboard backlight event - 0x%x\n",
> +                                event_data);
> +                       return;
> +               }
> +
> +               led_cdev->brightness = brightness;
> +               led_classdev_notify_brightness_hw_changed(led_cdev, brightness);
> +       }
> +}
> +
> +static int __init hp_mc_leds_register(int num_zones)
> +{
> +       u8 color_table[128];
> +       int ret;
> +
> +       ret = hp_wmi_perform_query(HPWMI_BACKLIGHT_COLOR_GET_QUERY, HPWMI_BACKLIGHT,
> +                                  color_table, zero_if_sup(color_table),
> +                                  sizeof(color_table));
> +       if (ret)
> +               return ret;
> +
> +       for (int zone = 0; zone < num_zones; zone++) {
> +               struct led_classdev_mc *multicolor_led_dev;
> +               struct led_classdev *led_cdev;
> +               struct mc_subled *mc_subled_info;
> +               struct hp_kbd_led_priv *priv;
> +               struct device *dev;
> +
> +               dev = &hp_wmi_platform_dev->dev;
> +               multicolor_led_dev = &hp_multicolor_leds.devices[zone];
> +               led_cdev = &multicolor_led_dev->led_cdev;
> +               priv = &hp_multicolor_leds.priv[zone];
> +
> +               if (num_zones == 1)
> +                       led_cdev->name = devm_kasprintf(dev, GFP_KERNEL,
> +                                                       "hp::kbd_backlight");
> +               else if (num_zones == 4 && zone < ARRAY_SIZE(hp_zone_names_4))
> +                       led_cdev->name = devm_kasprintf(dev, GFP_KERNEL,
> +                                                       "hp::kbd_%s",
> +                                                       hp_zone_names_4[zone]);
> +               else if (num_zones > 1)
> +                       led_cdev->name = devm_kasprintf(dev, GFP_KERNEL,
> +                                                       "hp::kbd_zoned_backlight-%d",
> +                                                       zone);
> +
> +               if (!led_cdev->name)
> +                       return -ENOMEM;
> +               led_cdev->brightness = hp_kbd_backlight_is_on() ?
> +                                      LED_FULL : LED_OFF;
> +               led_cdev->max_brightness = LED_FULL;
> +               led_cdev->brightness_set_blocking = hp_kbd_set_brightness;
> +               led_cdev->flags = LED_CORE_SUSPENDRESUME |
> +                                 LED_RETAIN_AT_SHUTDOWN |
> +                                 LED_BRIGHT_HW_CHANGED;
> +
> +               mc_subled_info = devm_kzalloc(dev, sizeof(*mc_subled_info) * 3,
> +                                             GFP_KERNEL);
> +               if (!mc_subled_info)
> +                       return -ENOMEM;
> +
> +               mc_subled_info[0].color_index = LED_COLOR_ID_RED;
> +               mc_subled_info[1].color_index = LED_COLOR_ID_GREEN;
> +               mc_subled_info[2].color_index = LED_COLOR_ID_BLUE;
> +
> +               for (int i = 0; i < 3; i++) {
> +                       int off = HP_COLOR_TABLE_PADDING + zone * 3 + i;
> +
> +                       mc_subled_info[i].channel = zone * 3 + i;
> +                       mc_subled_info[i].intensity = color_table[off];
> +                       mc_subled_info[i].brightness = LED_FULL;
> +               }
> +
> +               multicolor_led_dev->subled_info = mc_subled_info;
> +               multicolor_led_dev->num_colors = 3;

You seem to have many literal 3 in this driver, and it would be easier to 
find relevant ones for each case if you replace them with define(s).

This seems fine otherwise, AFAICT.

> +               ret = devm_led_classdev_multicolor_register(dev, multicolor_led_dev);
> +               if (ret) {
> +                       dev_err(dev, "Failed to register multicolor RGB backlight\n");
> +                       return ret;
> +               }
> +
> +               /* Initialize private data */
> +               priv->zone = zone;
> +               priv->last_brightness = LED_FULL;
> +       }
> +       return 0;
> +}
> +
> +static int __init hp_kbd_rgb_setup(void)
> +{
> +       u8 keyboard_type;
> +       int ret;
> +
> +       ret = hp_wmi_perform_query(HPWMI_GET_KEYBOARD_TYPE_QUERY, HPWMI_GM, &keyboard_type,
> +                                  sizeof(keyboard_type), sizeof(keyboard_type));
> +       if (ret)
> +               return ret;
> +
> +       switch (keyboard_type) {
> +       case HP_KEYBOARD_TYPE_FOURZONE_WITH_NUMPAD:
> +       case HP_KEYBOARD_TYPE_FOURZONE_WITHOUT_NUMPAD:
> +               return hp_mc_leds_register(4);
> +       case HP_KEYBOARD_TYPE_SINGLEZONE_WITH_NUMPAD:
> +       case HP_KEYBOARD_TYPE_SINGLEZONE_WITHOUT_NUMPAD:
> +               return hp_mc_leds_register(1);
> +       default:
> +               return 0;
> +       }
> +}
> +
>  static int platform_profile_omen_get_ec(enum platform_profile_option *profile)
>  {
>         int tp;
> @@ -2285,7 +2565,9 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
>                 return err;
>  
>         thermal_profile_setup(device);
> -
> +       err = hp_kbd_rgb_setup();
> +       if (err)
> +               dev_err(&device->dev, "Failed to initialize keyboard RGB\n");
>         return 0;
>  }
>  
> 

-- 
 i.

      reply	other threads:[~2026-07-01 10:54 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-29  6:56 [PATCH v6] platform/x86: hp-wmi: Add multicolor LED support for HP keyboard backlight Коненко Андрей Викторович
2026-05-15  8:00 ` Hans de Goede
2026-05-15 11:49   ` [PATCH v7] " Коненко Андрей Викторович
2026-05-18  8:26     ` Hans de Goede
2026-05-18 13:36       ` [PATCH v8] " Коненко Андрей Викторович
2026-06-08 11:29         ` Hans de Goede
2026-06-10 12:18         ` Ilpo Järvinen
2026-06-14 13:36           ` [PATCH v9] " Коненко Андрей Викторович
2026-06-14 14:03           ` [PATCH v10] " Коненко Андрей Викторович
2026-07-01 10:54             ` Ilpo Järvinen [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=ea021a4e-07de-e736-d095-d657aeda1945@linux.intel.com \
    --to=ilpo.jarvinen@linux.intel.com \
    --cc=admin@aquinas.su \
    --cc=hansg@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=platform-driver-x86@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.