* [PATCH v3 0/1] HP: wmi: added support for 4 zone keyboard rgb @ 2024-07-07 17:54 Carlos Ferreira 2024-07-07 17:54 ` [PATCH v3 1/1] " Carlos Ferreira 0 siblings, 1 reply; 9+ messages in thread From: Carlos Ferreira @ 2024-07-07 17:54 UTC (permalink / raw) To: hdegoede; +Cc: platform-driver-x86, linux-kernel, carlosmiguelferreira.2003 Hi, As suggested, this driver now uses the multicolor led api. Please note that for brightness, it takes the brightness files in each zone and uses them as one as there is no per zone brightness control, so all the files control the brightness of the entire keyboard. Also, when the backlight toggle key is pressed it updates the brightness on all the zones to avoid some edge case problems i ran into where setting the same color more than once would set the brightness to the previous value. Please let me know if this is a good approach. Carlos Ferreira (1): HP: wmi: added support for 4 zone keyboard rgb drivers/platform/x86/hp/hp-wmi.c | 248 +++++++++++++++++++++++++++++-- 1 file changed, 239 insertions(+), 9 deletions(-) -- 2.45.2 ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 1/1] HP: wmi: added support for 4 zone keyboard rgb 2024-07-07 17:54 [PATCH v3 0/1] HP: wmi: added support for 4 zone keyboard rgb Carlos Ferreira @ 2024-07-07 17:54 ` Carlos Ferreira 2024-07-07 22:22 ` kernel test robot ` (3 more replies) 0 siblings, 4 replies; 9+ messages in thread From: Carlos Ferreira @ 2024-07-07 17:54 UTC (permalink / raw) To: hdegoede; +Cc: platform-driver-x86, linux-kernel, carlosmiguelferreira.2003 This driver adds supports for 4 zone keyboard rgb on omen laptops and maps the wmi backlight toggle event to KEY_KBDILLUMTOGGLE. For the backlight, it uses the multicolor led api. Tested on the HP Omen 15-en1001np. Signed-off-by: Carlos Ferreira <carlosmiguelferreira.2003@gmail.com> --- Changes in v3: - Moved to the multicolor led api - Mapped the wmi backlight toggle event to KEY_KBDILLUMTOGGLE - Some other minor changes Changes in v2: - Rearranged code to remove forward declarations - Changed from sprintf() to sysfs_emit() - Fixed some identation and coding style problems - Switched from manual bit manipulation to GENMASK(x, y) + FIELD_PREP(XX, ) - #define'ed magic constants --- drivers/platform/x86/hp/hp-wmi.c | 248 +++++++++++++++++++++++++++++-- 1 file changed, 239 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 5fa553023842..5eae47961f76 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -14,6 +14,8 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/kernel.h> +#include <linux/led-class-multicolor.h> +#include <linux/leds.h> #include <linux/module.h> #include <linux/init.h> #include <linux/slab.h> @@ -24,6 +26,7 @@ #include <linux/platform_profile.h> #include <linux/hwmon.h> #include <linux/acpi.h> +#include <linux/bits.h> #include <linux/rfkill.h> #include <linux/string.h> #include <linux/dmi.h> @@ -44,6 +47,14 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4"); #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required +#define FOURZONE_LIGHTING_SUPPORTED_BIT 0x01 +#define FOURZONE_LIGHTING_ON 228 +#define FOURZONE_LIGHTING_OFF 100 + +#define FOURZONE_COLOR_R GENMASK(23, 16) +#define FOURZONE_COLOR_G GENMASK(15, 8) +#define FOURZONE_COLOR_B GENMASK(7, 0) + /* DMI board names of devices that should use the omen specific path for * thermal profiles. * This was obtained by taking a look in the windows omen command center @@ -143,18 +154,36 @@ enum hp_wmi_commandtype { }; enum hp_wmi_gm_commandtype { - HPWMI_FAN_SPEED_GET_QUERY = 0x11, - HPWMI_SET_PERFORMANCE_MODE = 0x1A, - HPWMI_FAN_SPEED_MAX_GET_QUERY = 0x26, - HPWMI_FAN_SPEED_MAX_SET_QUERY = 0x27, - HPWMI_GET_SYSTEM_DESIGN_DATA = 0x28, + HPWMI_FAN_SPEED_GET_QUERY = 0x11, + HPWMI_SET_PERFORMANCE_MODE = 0x1A, + HPWMI_FAN_SPEED_MAX_GET_QUERY = 0x26, + HPWMI_FAN_SPEED_MAX_SET_QUERY = 0x27, + HPWMI_GET_SYSTEM_DESIGN_DATA = 0x28, + HPWMI_GET_KEYBOARD_TYPE = 0x2B, +}; + +enum hp_wmi_fourzone_commandtype { + HPWMI_SUPPORTS_LIGHTNING = 0x01, + HPWMI_FOURZONE_COLOR_GET = 0x02, + HPWMI_FOURZONE_COLOR_SET = 0x03, + HPWMI_LED_BRIGHTNESS_GET = 0x04, + HPWMI_LED_BRIGHTNESS_SET = 0x05, +}; + +enum hp_wmi_keyboardtype { + HPWMI_KEYBOARD_INVALID = 0x00, + HPWMI_KEYBOARD_NORMAL = 0x01, + HPWMI_KEYBOARD_WITH_NUMPAD = 0x02, + HPWMI_KEYBOARD_WITHOUT_NUMPAD = 0x03, + HPWMI_KEYBOARD_RGB = 0x04, }; enum hp_wmi_command { - HPWMI_READ = 0x01, - HPWMI_WRITE = 0x02, - HPWMI_ODM = 0x03, - HPWMI_GM = 0x20008, + HPWMI_READ = 0x01, + HPWMI_WRITE = 0x02, + HPWMI_ODM = 0x03, + HPWMI_GM = 0x20008, + HPWMI_FOURZONE = 0x20009, }; enum hp_wmi_hardware_mask { @@ -265,6 +294,7 @@ static struct platform_device *hp_wmi_platform_dev; static struct platform_profile_handler platform_profile_handler; static bool platform_profile_support; static bool zero_insize_support; +static bool fourzone_lightning_support; static struct rfkill *wifi_rfkill; static struct rfkill *bluetooth_rfkill; @@ -821,6 +851,40 @@ static struct attribute *hp_wmi_attrs[] = { }; ATTRIBUTE_GROUPS(hp_wmi); +static const char * const fourzone_zone_names[4] = { + "hp:rgb:kbd_zoned_backlight-right", + "hp:rgb:kbd_zoned_backlight-middle", + "hp:rgb:kbd_zoned_backlight-left", + "hp:rgb:kbd_zoned_backlight-wasd" +}; + +struct hp_fourzone_leds { + struct led_classdev_mc leds[4]; + struct mc_subled subleds[4]; + u32 color_cache[4]; +}; +static struct hp_fourzone_leds fourzone_leds; + +static enum led_brightness get_fourzone_brightness(struct led_classdev *led_cdev) +{ + u8 buff[4]; + + hp_wmi_perform_query(HPWMI_LED_BRIGHTNESS_GET, HPWMI_FOURZONE, + &buff, sizeof(buff), sizeof(buff)); + + return buff[0] == FOURZONE_LIGHTING_ON ? LED_ON : LED_OFF; +} + +static void fourzone_update_brightness(void) +{ + unsigned int br; + + /* synchronize the brightness level on all zones */ + br = get_fourzone_brightness(NULL); + for (size_t i = 0; i < 4; i++) + fourzone_leds.leds[i].led_cdev.brightness = br; +} + static void hp_wmi_notify(u32 value, void *context) { struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -932,6 +996,14 @@ static void hp_wmi_notify(u32 value, void *context) case HPWMI_PROXIMITY_SENSOR: break; case HPWMI_BACKLIT_KB_BRIGHTNESS: + if (fourzone_lightning_support) { + input_report_key(hp_wmi_input_dev, KEY_KBDILLUMTOGGLE, true); + input_sync(hp_wmi_input_dev); + input_report_key(hp_wmi_input_dev, KEY_KBDILLUMTOGGLE, false); + input_sync(hp_wmi_input_dev); + + fourzone_update_brightness(); + } break; case HPWMI_PEAKSHIFT_PERIOD: break; @@ -1505,6 +1577,154 @@ static int thermal_profile_setup(void) return 0; } +static int fourzone_set_colors(u32 color, size_t zone) +{ + u8 buff[128]; + int ret; + + ret = hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_GET, HPWMI_FOURZONE, + &buff, sizeof(buff), sizeof(buff)); + if (ret != 0) + return -EINVAL; + + buff[25 + zone * 3] = FIELD_GET(FOURZONE_COLOR_R, color); + buff[25 + zone * 3 + 1] = FIELD_GET(FOURZONE_COLOR_G, color); + buff[25 + zone * 3 + 2] = FIELD_GET(FOURZONE_COLOR_B, color); + + return hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_SET, HPWMI_FOURZONE, + &buff, sizeof(buff), sizeof(buff)); +} + +static int fourzone_get_colors(u32 *colors) +{ + u8 buff[128]; + int ret; + + ret = hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_GET, HPWMI_FOURZONE, + &buff, sizeof(buff), sizeof(buff)); + if (ret != 0) + return -EINVAL; + + for (int i = 0; i < 4; i++) { + colors[i] = FIELD_PREP(FOURZONE_COLOR_R, buff[25 + i * 3]) + | FIELD_PREP(FOURZONE_COLOR_G, buff[25 + i * 3 + 1]) + | FIELD_PREP(FOURZONE_COLOR_B, buff[25 + i * 3 + 2]); + } + + return 0; +} + +static void set_fourzone_brightness(struct led_classdev *led_cdev, enum led_brightness brightness) +{ + size_t zone; + + for (size_t i = 0; i < 4; i++) + if (strcmp(led_cdev->name, fourzone_zone_names[i]) == 0) + zone = i; + + if (fourzone_leds.leds[zone].subled_info->intensity == fourzone_leds.color_cache[zone]) { + u8 buff[4] = { + brightness == LED_ON ? FOURZONE_LIGHTING_ON : FOURZONE_LIGHTING_OFF, + 0, 0, 0 + }; + + hp_wmi_perform_query(HPWMI_LED_BRIGHTNESS_SET, HPWMI_FOURZONE, &buff, + sizeof(buff), 0); + + fourzone_update_brightness(); + } else { + fourzone_set_colors(fourzone_leds.leds[zone].subled_info->intensity, zone); + fourzone_leds.color_cache[zone] = fourzone_leds.leds[zone].subled_info->intensity; + } +} + +static int __init fourzone_leds_init(struct platform_device *device) +{ + enum led_brightness brightness; + u32 colors[4]; + int ret; + + ret = fourzone_get_colors(colors); + if (ret < 0) + return ret; + + memcpy(fourzone_leds.color_cache, colors, sizeof(colors)); + + brightness = get_fourzone_brightness(NULL); + + for (size_t i = 0; i < 4; i++) { + fourzone_leds.subleds[i] = (struct mc_subled) { + .color_index = LED_COLOR_ID_RGB, + .brightness = 1, + .intensity = colors[i] + }; + + fourzone_leds.leds[i] = (struct led_classdev_mc) { + .led_cdev = { + .name = fourzone_zone_names[i], + .brightness = brightness, + .max_brightness = 1, + .brightness_set = set_fourzone_brightness, + .brightness_get = get_fourzone_brightness, + .color = LED_COLOR_ID_RGB, + .flags = LED_BRIGHT_HW_CHANGED + }, + .num_colors = 1, + .subled_info = &fourzone_leds.subleds[i] + }; + + ret = devm_led_classdev_multicolor_register(&device->dev, &fourzone_leds.leds[i]); + if (ret) + return -ENODEV; + } + + return 0; +} + +static enum hp_wmi_keyboardtype fourzone_get_keyboard_type(void) +{ + u8 buff[128]; + int ret; + + ret = hp_wmi_perform_query(HPWMI_GET_KEYBOARD_TYPE, HPWMI_GM, + &buff, sizeof(buff), sizeof(buff)); + if (ret != 0) + return HPWMI_KEYBOARD_INVALID; + + /* the first byte in the response represents the keyboard type */ + return (enum hp_wmi_keyboardtype)(buff[0] + 1); +} + +static bool fourzone_supports_lighting(void) +{ + u8 buff[128]; + int ret; + + ret = hp_wmi_perform_query(HPWMI_SUPPORTS_LIGHTNING, HPWMI_FOURZONE, + &buff, sizeof(buff), sizeof(buff)); + if (ret != 0) + return false; + + return buff[0] & FOURZONE_LIGHTING_SUPPORTED_BIT; +} + +static int fourzone_setup(struct platform_device *device) +{ + if (!fourzone_supports_lighting()) + return -ENODEV; + + if (fourzone_get_keyboard_type() != HPWMI_KEYBOARD_WITHOUT_NUMPAD) + return -ENODEV; + + /* register leds */ + if (fourzone_leds_init(device) < 0) + return -ENODEV; + + input_set_capability(hp_wmi_input_dev, KE_KEY, KEY_KBDILLUMTOGGLE); + + return 0; +} + static int hp_wmi_hwmon_init(void); static int __init hp_wmi_bios_setup(struct platform_device *device) @@ -1534,6 +1754,10 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) thermal_profile_setup(); + /* setup 4 zone rgb */ + if (!fourzone_setup(device)) + fourzone_lightning_support = true; + return 0; } @@ -1561,6 +1785,12 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device) if (platform_profile_support) platform_profile_remove(); + + if (fourzone_lightning_support) + for (size_t i = 0; i < 4; i++) { + devm_led_classdev_multicolor_unregister(&device->dev, + &fourzone_leds.leds[i]); + } } static int hp_wmi_resume_handler(struct device *device) -- 2.45.2 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v3 1/1] HP: wmi: added support for 4 zone keyboard rgb 2024-07-07 17:54 ` [PATCH v3 1/1] " Carlos Ferreira @ 2024-07-07 22:22 ` kernel test robot 2024-07-08 0:36 ` kernel test robot ` (2 subsequent siblings) 3 siblings, 0 replies; 9+ messages in thread From: kernel test robot @ 2024-07-07 22:22 UTC (permalink / raw) To: Carlos Ferreira, hdegoede Cc: llvm, oe-kbuild-all, platform-driver-x86, linux-kernel, carlosmiguelferreira.2003 Hi Carlos, kernel test robot noticed the following build errors: [auto build test ERROR on linus/master] [also build test ERROR on v6.10-rc6 next-20240703] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Carlos-Ferreira/HP-wmi-added-support-for-4-zone-keyboard-rgb/20240708-015808 base: linus/master patch link: https://lore.kernel.org/r/20240707175613.27529-2-carlosmiguelferreira.2003%40gmail.com patch subject: [PATCH v3 1/1] HP: wmi: added support for 4 zone keyboard rgb config: i386-randconfig-001-20240708 (https://download.01.org/0day-ci/archive/20240708/202407080657.XLHI1WRv-lkp@intel.com/config) compiler: clang version 18.1.5 (https://github.com/llvm/llvm-project 617a15a9eac96088ae5e9134248d8236e34b91b1) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240708/202407080657.XLHI1WRv-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202407080657.XLHI1WRv-lkp@intel.com/ All errors (new ones prefixed by >>): >> drivers/platform/x86/hp/hp-wmi.c:1590:28: error: call to undeclared function 'FIELD_GET'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration] 1590 | buff[25 + zone * 3] = FIELD_GET(FOURZONE_COLOR_R, color); | ^ >> drivers/platform/x86/hp/hp-wmi.c:1609:15: error: call to undeclared function 'FIELD_PREP'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration] 1609 | colors[i] = FIELD_PREP(FOURZONE_COLOR_R, buff[25 + i * 3]) | ^ 2 errors generated. vim +/FIELD_GET +1590 drivers/platform/x86/hp/hp-wmi.c 1579 1580 static int fourzone_set_colors(u32 color, size_t zone) 1581 { 1582 u8 buff[128]; 1583 int ret; 1584 1585 ret = hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_GET, HPWMI_FOURZONE, 1586 &buff, sizeof(buff), sizeof(buff)); 1587 if (ret != 0) 1588 return -EINVAL; 1589 > 1590 buff[25 + zone * 3] = FIELD_GET(FOURZONE_COLOR_R, color); 1591 buff[25 + zone * 3 + 1] = FIELD_GET(FOURZONE_COLOR_G, color); 1592 buff[25 + zone * 3 + 2] = FIELD_GET(FOURZONE_COLOR_B, color); 1593 1594 return hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_SET, HPWMI_FOURZONE, 1595 &buff, sizeof(buff), sizeof(buff)); 1596 } 1597 1598 static int fourzone_get_colors(u32 *colors) 1599 { 1600 u8 buff[128]; 1601 int ret; 1602 1603 ret = hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_GET, HPWMI_FOURZONE, 1604 &buff, sizeof(buff), sizeof(buff)); 1605 if (ret != 0) 1606 return -EINVAL; 1607 1608 for (int i = 0; i < 4; i++) { > 1609 colors[i] = FIELD_PREP(FOURZONE_COLOR_R, buff[25 + i * 3]) 1610 | FIELD_PREP(FOURZONE_COLOR_G, buff[25 + i * 3 + 1]) 1611 | FIELD_PREP(FOURZONE_COLOR_B, buff[25 + i * 3 + 2]); 1612 } 1613 1614 return 0; 1615 } 1616 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 1/1] HP: wmi: added support for 4 zone keyboard rgb 2024-07-07 17:54 ` [PATCH v3 1/1] " Carlos Ferreira 2024-07-07 22:22 ` kernel test robot @ 2024-07-08 0:36 ` kernel test robot 2024-07-11 8:41 ` Ilpo Järvinen 2024-07-11 17:37 ` Hans de Goede 3 siblings, 0 replies; 9+ messages in thread From: kernel test robot @ 2024-07-08 0:36 UTC (permalink / raw) To: Carlos Ferreira, hdegoede Cc: oe-kbuild-all, platform-driver-x86, linux-kernel, carlosmiguelferreira.2003 Hi Carlos, kernel test robot noticed the following build errors: [auto build test ERROR on linus/master] [also build test ERROR on v6.10-rc7 next-20240703] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Carlos-Ferreira/HP-wmi-added-support-for-4-zone-keyboard-rgb/20240708-015808 base: linus/master patch link: https://lore.kernel.org/r/20240707175613.27529-2-carlosmiguelferreira.2003%40gmail.com patch subject: [PATCH v3 1/1] HP: wmi: added support for 4 zone keyboard rgb config: i386-randconfig-012-20240708 (https://download.01.org/0day-ci/archive/20240708/202407080802.Qbwt3Edr-lkp@intel.com/config) compiler: gcc-9 (Ubuntu 9.5.0-4ubuntu2) 9.5.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240708/202407080802.Qbwt3Edr-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202407080802.Qbwt3Edr-lkp@intel.com/ All errors (new ones prefixed by >>, old ones prefixed by <<): WARNING: modpost: missing MODULE_DESCRIPTION() in kernel/locking/locktorture.o WARNING: modpost: missing MODULE_DESCRIPTION() in kernel/locking/test-ww_mutex.o WARNING: modpost: missing MODULE_DESCRIPTION() in kernel/rcu/rcutorture.o WARNING: modpost: missing MODULE_DESCRIPTION() in kernel/rcu/rcuscale.o WARNING: modpost: missing MODULE_DESCRIPTION() in kernel/rcu/refscale.o WARNING: modpost: missing MODULE_DESCRIPTION() in kernel/torture.o WARNING: modpost: missing MODULE_DESCRIPTION() in mm/dmapool_test.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nfs/nfsv4.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_cp437.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_cp737.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_cp852.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_cp855.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_cp861.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_cp862.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_cp932.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_euc-jp.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_ascii.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_iso8859-2.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_iso8859-9.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_koi8-u.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_koi8-ru.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/mac-centeuro.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/mac-croatian.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/mac-roman.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/nls/nls_ucs2_utils.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/binfmt_misc.o WARNING: modpost: missing MODULE_DESCRIPTION() in fs/autofs/autofs4.o WARNING: modpost: missing MODULE_DESCRIPTION() in crypto/curve25519-generic.o WARNING: modpost: missing MODULE_DESCRIPTION() in lib/math/prime_numbers.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/acpi/platform_profile.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/dma/qcom/hdma_mgmt.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/regulator/da9121-regulator.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/tty/ttynull.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/char/agp/ati-agp.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/char/agp/amd64-agp.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/char/agp/efficeon-agp.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/char/agp/sis-agp.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/char/ppdev.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/gpu/drm/tiny/cirrus.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/base/regmap/regmap-spmi.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/mfd/pcf50633-gpio.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/mfd/qcom-pm8008.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/uio/uio_aec.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/uio/uio_netx.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/i2c/busses/i2c-pxa.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/media/rc/rc-core.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/mmc/core/mmc_core.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/mmc/core/pwrseq_simple.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/mmc/core/pwrseq_emmc.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/leds/simple/simatic-ipc-leds-gpio-core.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/leds/simple/simatic-ipc-leds-gpio-f7188x.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/firmware/google/coreboot_table.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/firmware/google/framebuffer-coreboot.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/firmware/google/memconsole.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/firmware/google/memconsole-coreboot.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/devfreq/governor_userspace.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/fsi/fsi-master-aspeed.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/fsi/fsi-master-gpio.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/parport/parport.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/mtd/chips/cfi_util.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/mtd/chips/cfi_cmdset_0020.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/spmi/hisi-spmi-controller.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/hwmon/mr75203.o >> ERROR: modpost: "devm_led_classdev_multicolor_unregister" [drivers/platform/x86/hp/hp-wmi.ko] undefined! >> ERROR: modpost: "devm_led_classdev_multicolor_register_ext" [drivers/platform/x86/hp/hp-wmi.ko] undefined! -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 1/1] HP: wmi: added support for 4 zone keyboard rgb 2024-07-07 17:54 ` [PATCH v3 1/1] " Carlos Ferreira 2024-07-07 22:22 ` kernel test robot 2024-07-08 0:36 ` kernel test robot @ 2024-07-11 8:41 ` Ilpo Järvinen 2024-07-11 15:17 ` Hans de Goede 2024-07-11 17:37 ` Hans de Goede 3 siblings, 1 reply; 9+ messages in thread From: Ilpo Järvinen @ 2024-07-11 8:41 UTC (permalink / raw) To: Carlos Ferreira; +Cc: Hans de Goede, platform-driver-x86, LKML On Sun, 7 Jul 2024, Carlos Ferreira wrote: > This driver adds supports for 4 zone keyboard rgb on omen laptops > and maps the wmi backlight toggle event to KEY_KBDILLUMTOGGLE. > For the backlight, it uses the multicolor led api. > > Tested on the HP Omen 15-en1001np. > > Signed-off-by: Carlos Ferreira <carlosmiguelferreira.2003@gmail.com> > --- > Changes in v3: > - Moved to the multicolor led api > - Mapped the wmi backlight toggle event to KEY_KBDILLUMTOGGLE > - Some other minor changes > Changes in v2: > - Rearranged code to remove forward declarations > - Changed from sprintf() to sysfs_emit() > - Fixed some identation and coding style problems > - Switched from manual bit manipulation to GENMASK(x, y) + FIELD_PREP(XX, ) > - #define'ed magic constants > --- > drivers/platform/x86/hp/hp-wmi.c | 248 +++++++++++++++++++++++++++++-- > 1 file changed, 239 insertions(+), 9 deletions(-) > > diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c > index 5fa553023842..5eae47961f76 100644 > --- a/drivers/platform/x86/hp/hp-wmi.c > +++ b/drivers/platform/x86/hp/hp-wmi.c > @@ -14,6 +14,8 @@ > #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt > > #include <linux/kernel.h> > +#include <linux/led-class-multicolor.h> > +#include <linux/leds.h> > #include <linux/module.h> > #include <linux/init.h> > #include <linux/slab.h> > @@ -24,6 +26,7 @@ > #include <linux/platform_profile.h> > #include <linux/hwmon.h> > #include <linux/acpi.h> > +#include <linux/bits.h> You need to add #include <linux/bitfield.h> as LKP found out. The MODULE_DESCRIPTION() ones you can ignore as LKP unfortunately spams some kernel-wide problems to all patches even if they're entirely unrelated to the patch in question. > + for (size_t i = 0; i < 4; i++) > + fourzone_leds.leds[i].led_cdev.brightness = br; > + for (int i = 0; i < 4; i++) { > + colors[i] = FIELD_PREP(FOURZONE_COLOR_R, buff[25 + i * 3]) > + | FIELD_PREP(FOURZONE_COLOR_G, buff[25 + i * 3 + 1]) > + | FIELD_PREP(FOURZONE_COLOR_B, buff[25 + i * 3 + 2]); The loop variables are not particularly consistent... I'd prefer them to be declared in the normal function variable declaration block. > + > + for (size_t i = 0; i < 4; i++) > + if (strcmp(led_cdev->name, fourzone_zone_names[i]) == 0) !strcmp() > + ret = fourzone_get_colors(colors); > + if (ret < 0) > + return ret; > + > + memcpy(fourzone_leds.color_cache, colors, sizeof(colors)); > + > + brightness = get_fourzone_brightness(NULL); > + > + for (size_t i = 0; i < 4; i++) { > + fourzone_leds.subleds[i] = (struct mc_subled) { > + .color_index = LED_COLOR_ID_RGB, > + .brightness = 1, > + .intensity = colors[i] Please add comma to the last line because if this is ever extended, this line doesn't need to be touched. > + }; > + > + fourzone_leds.leds[i] = (struct led_classdev_mc) { > + .led_cdev = { > + .name = fourzone_zone_names[i], > + .brightness = brightness, > + .max_brightness = 1, > + .brightness_set = set_fourzone_brightness, > + .brightness_get = get_fourzone_brightness, > + .color = LED_COLOR_ID_RGB, > + .flags = LED_BRIGHT_HW_CHANGED Ditto. > + }, > + .num_colors = 1, > + .subled_info = &fourzone_leds.subleds[i] Ditto. > + return 0; > +} > + > +static enum hp_wmi_keyboardtype fourzone_get_keyboard_type(void) > +{ > + u8 buff[128]; > + int ret; > + > + ret = hp_wmi_perform_query(HPWMI_GET_KEYBOARD_TYPE, HPWMI_GM, > + &buff, sizeof(buff), sizeof(buff)); Align the continuation of parameters to the opening (. > + if (ret != 0) > + return HPWMI_KEYBOARD_INVALID; > + > + /* the first byte in the response represents the keyboard type */ > + return (enum hp_wmi_keyboardtype)(buff[0] + 1); > +} > + > +static bool fourzone_supports_lighting(void) > +{ > + u8 buff[128]; > + int ret; > + > + ret = hp_wmi_perform_query(HPWMI_SUPPORTS_LIGHTNING, HPWMI_FOURZONE, > + &buff, sizeof(buff), sizeof(buff)); Ditto. I probably missed a few similar cases as mentioned above so please go through your patch and correct them all. I'm left unsure if a mutex would be necessary to protect the brightness updates. Probably. Hans might have more to say about the approach. -- i. ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 1/1] HP: wmi: added support for 4 zone keyboard rgb 2024-07-11 8:41 ` Ilpo Järvinen @ 2024-07-11 15:17 ` Hans de Goede 0 siblings, 0 replies; 9+ messages in thread From: Hans de Goede @ 2024-07-11 15:17 UTC (permalink / raw) To: Ilpo Järvinen, Carlos Ferreira; +Cc: platform-driver-x86, LKML Hi Ilpo, Thank you for reviewing this. On 7/11/24 10:41 AM, Ilpo Järvinen wrote: > On Sun, 7 Jul 2024, Carlos Ferreira wrote: > >> This driver adds supports for 4 zone keyboard rgb on omen laptops >> and maps the wmi backlight toggle event to KEY_KBDILLUMTOGGLE. >> For the backlight, it uses the multicolor led api. >> >> Tested on the HP Omen 15-en1001np. >> >> Signed-off-by: Carlos Ferreira <carlosmiguelferreira.2003@gmail.com> >> --- >> Changes in v3: >> - Moved to the multicolor led api >> - Mapped the wmi backlight toggle event to KEY_KBDILLUMTOGGLE >> - Some other minor changes >> Changes in v2: >> - Rearranged code to remove forward declarations >> - Changed from sprintf() to sysfs_emit() >> - Fixed some identation and coding style problems >> - Switched from manual bit manipulation to GENMASK(x, y) + FIELD_PREP(XX, ) >> - #define'ed magic constants >> --- >> drivers/platform/x86/hp/hp-wmi.c | 248 +++++++++++++++++++++++++++++-- >> 1 file changed, 239 insertions(+), 9 deletions(-) >> >> diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c >> index 5fa553023842..5eae47961f76 100644 >> --- a/drivers/platform/x86/hp/hp-wmi.c >> +++ b/drivers/platform/x86/hp/hp-wmi.c >> @@ -14,6 +14,8 @@ >> #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt >> >> #include <linux/kernel.h> >> +#include <linux/led-class-multicolor.h> >> +#include <linux/leds.h> >> #include <linux/module.h> >> #include <linux/init.h> >> #include <linux/slab.h> >> @@ -24,6 +26,7 @@ >> #include <linux/platform_profile.h> >> #include <linux/hwmon.h> >> #include <linux/acpi.h> >> +#include <linux/bits.h> > > You need to add #include <linux/bitfield.h> as LKP found out. The > MODULE_DESCRIPTION() ones you can ignore as LKP unfortunately spams some > kernel-wide problems to all patches even if they're entirely unrelated > to the patch in question. Actually the LKP / kernel test robot email with the MODULE_DESCRIPTION() warnings does contain useful, new errors. The problem though is that the bot prefixes new (not seen on builds without this patch) messages with '>>' making them show up as quoted (replied-to) text making the actual new errors harder to spot :| The 2 new actual errors in the email with all the MODULE_DESCRIPTION warnings are: ERROR: modpost: "devm_led_classdev_multicolor_unregister" [drivers/platform/x86/hp/hp-wmi.ko] undefined! ERROR: modpost: "devm_led_classdev_multicolor_register_ext" [drivers/platform/x86/hp/hp-wmi.ko] undefined! I agree with all the other review remarks and I'll do my own review soon. Regards, Hans ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 1/1] HP: wmi: added support for 4 zone keyboard rgb 2024-07-07 17:54 ` [PATCH v3 1/1] " Carlos Ferreira ` (2 preceding siblings ...) 2024-07-11 8:41 ` Ilpo Järvinen @ 2024-07-11 17:37 ` Hans de Goede 2024-07-12 16:39 ` Carlos Ferreira 3 siblings, 1 reply; 9+ messages in thread From: Hans de Goede @ 2024-07-11 17:37 UTC (permalink / raw) To: Carlos Ferreira; +Cc: platform-driver-x86, linux-kernel Hi Carlos, On 7/7/24 7:54 PM, Carlos Ferreira wrote: > This driver adds supports for 4 zone keyboard rgb on omen laptops > and maps the wmi backlight toggle event to KEY_KBDILLUMTOGGLE. > For the backlight, it uses the multicolor led api. > > Tested on the HP Omen 15-en1001np. > > Signed-off-by: Carlos Ferreira <carlosmiguelferreira.2003@gmail.com> > --- > Changes in v3: > - Moved to the multicolor led api > - Mapped the wmi backlight toggle event to KEY_KBDILLUMTOGGLE > - Some other minor changes > Changes in v2: > - Rearranged code to remove forward declarations > - Changed from sprintf() to sysfs_emit() > - Fixed some identation and coding style problems > - Switched from manual bit manipulation to GENMASK(x, y) + FIELD_PREP(XX, ) > - #define'ed magic constants > --- > drivers/platform/x86/hp/hp-wmi.c | 248 +++++++++++++++++++++++++++++-- > 1 file changed, 239 insertions(+), 9 deletions(-) > > diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c > index 5fa553023842..5eae47961f76 100644 > --- a/drivers/platform/x86/hp/hp-wmi.c > +++ b/drivers/platform/x86/hp/hp-wmi.c > @@ -14,6 +14,8 @@ > #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt > > #include <linux/kernel.h> > +#include <linux/led-class-multicolor.h> > +#include <linux/leds.h> This means that you now also need to update Kconfig to depend on LEDS_CLASS_MULTICOLOR, so add the following line to the existing Kconfig entry for the HP WMI driver: depends on LEDS_CLASS_MULTICOLOR Adding this should fix the following errors reported by the kernel test robot: ERROR: modpost: "devm_led_classdev_multicolor_unregister" [drivers/platform/x86/hp/hp-wmi.ko] undefined! ERROR: modpost: "devm_led_classdev_multicolor_register_ext" [drivers/platform/x86/hp/hp-wmi.ko] undefined! > #include <linux/module.h> > #include <linux/init.h> > #include <linux/slab.h> > @@ -24,6 +26,7 @@ > #include <linux/platform_profile.h> > #include <linux/hwmon.h> > #include <linux/acpi.h> > +#include <linux/bits.h> > #include <linux/rfkill.h> > #include <linux/string.h> > #include <linux/dmi.h> As Ilpo mentioned you need to add a: #include <linux/bitfield.h> here. > @@ -44,6 +47,14 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4"); > > #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required > > +#define FOURZONE_LIGHTING_SUPPORTED_BIT 0x01 > +#define FOURZONE_LIGHTING_ON 228 > +#define FOURZONE_LIGHTING_OFF 100 > + > +#define FOURZONE_COLOR_R GENMASK(23, 16) > +#define FOURZONE_COLOR_G GENMASK(15, 8) > +#define FOURZONE_COLOR_B GENMASK(7, 0) I see here and below that you are encoding all 3 color components in a single 24bit intensity / brightness value, that is not how you are supposed to use the multi-color LED API. More about this below. > + > /* DMI board names of devices that should use the omen specific path for > * thermal profiles. > * This was obtained by taking a look in the windows omen command center > @@ -143,18 +154,36 @@ enum hp_wmi_commandtype { > }; > > enum hp_wmi_gm_commandtype { > - HPWMI_FAN_SPEED_GET_QUERY = 0x11, > - HPWMI_SET_PERFORMANCE_MODE = 0x1A, > - HPWMI_FAN_SPEED_MAX_GET_QUERY = 0x26, > - HPWMI_FAN_SPEED_MAX_SET_QUERY = 0x27, > - HPWMI_GET_SYSTEM_DESIGN_DATA = 0x28, > + HPWMI_FAN_SPEED_GET_QUERY = 0x11, > + HPWMI_SET_PERFORMANCE_MODE = 0x1A, > + HPWMI_FAN_SPEED_MAX_GET_QUERY = 0x26, > + HPWMI_FAN_SPEED_MAX_SET_QUERY = 0x27, > + HPWMI_GET_SYSTEM_DESIGN_DATA = 0x28, > + HPWMI_GET_KEYBOARD_TYPE = 0x2B, > +}; > + > +enum hp_wmi_fourzone_commandtype { > + HPWMI_SUPPORTS_LIGHTNING = 0x01, > + HPWMI_FOURZONE_COLOR_GET = 0x02, > + HPWMI_FOURZONE_COLOR_SET = 0x03, > + HPWMI_LED_BRIGHTNESS_GET = 0x04, > + HPWMI_LED_BRIGHTNESS_SET = 0x05, > +}; > + > +enum hp_wmi_keyboardtype { > + HPWMI_KEYBOARD_INVALID = 0x00, > + HPWMI_KEYBOARD_NORMAL = 0x01, > + HPWMI_KEYBOARD_WITH_NUMPAD = 0x02, > + HPWMI_KEYBOARD_WITHOUT_NUMPAD = 0x03, > + HPWMI_KEYBOARD_RGB = 0x04, > }; > > enum hp_wmi_command { > - HPWMI_READ = 0x01, > - HPWMI_WRITE = 0x02, > - HPWMI_ODM = 0x03, > - HPWMI_GM = 0x20008, > + HPWMI_READ = 0x01, > + HPWMI_WRITE = 0x02, > + HPWMI_ODM = 0x03, > + HPWMI_GM = 0x20008, > + HPWMI_FOURZONE = 0x20009, > }; > > enum hp_wmi_hardware_mask { > @@ -265,6 +294,7 @@ static struct platform_device *hp_wmi_platform_dev; > static struct platform_profile_handler platform_profile_handler; > static bool platform_profile_support; > static bool zero_insize_support; > +static bool fourzone_lightning_support; > > static struct rfkill *wifi_rfkill; > static struct rfkill *bluetooth_rfkill; > @@ -821,6 +851,40 @@ static struct attribute *hp_wmi_attrs[] = { > }; > ATTRIBUTE_GROUPS(hp_wmi); > > +static const char * const fourzone_zone_names[4] = { > + "hp:rgb:kbd_zoned_backlight-right", > + "hp:rgb:kbd_zoned_backlight-middle", > + "hp:rgb:kbd_zoned_backlight-left", > + "hp:rgb:kbd_zoned_backlight-wasd" > +}; > + > +struct hp_fourzone_leds { > + struct led_classdev_mc leds[4]; > + struct mc_subled subleds[4]; The idea with the multi-color API and subleds is that a RGB LED really are 3 seperate LEDs (R + G + B) in one. This is alsohow they are actually phyiscally made. So for 4 zones you need 12 subleds. I think it would be best to to have a single struct per zone: struct hp_fourzone_led { struct led_classdev_mc mc_led; struct mc_subled subleds[3]; u32 cache; /* Not sure if you still want this */ }; And then declare an array of 4 of these: static struct hp_fourzone_led hp_fourzone_leds[4]; > + u32 color_cache[4]; > +}; > +static struct hp_fourzone_leds fourzone_leds; > + > +static enum led_brightness get_fourzone_brightness(struct led_classdev *led_cdev) > +{ > + u8 buff[4]; > + > + hp_wmi_perform_query(HPWMI_LED_BRIGHTNESS_GET, HPWMI_FOURZONE, > + &buff, sizeof(buff), sizeof(buff)); > + > + return buff[0] == FOURZONE_LIGHTING_ON ? LED_ON : LED_OFF; > +} > + > +static void fourzone_update_brightness(void) > +{ > + unsigned int br; > + > + /* synchronize the brightness level on all zones */ > + br = get_fourzone_brightness(NULL); > + for (size_t i = 0; i < 4; i++) > + fourzone_leds.leds[i].led_cdev.brightness = br; > +} > + > static void hp_wmi_notify(u32 value, void *context) > { > struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; > @@ -932,6 +996,14 @@ static void hp_wmi_notify(u32 value, void *context) > case HPWMI_PROXIMITY_SENSOR: > break; > case HPWMI_BACKLIT_KB_BRIGHTNESS: > + if (fourzone_lightning_support) { > + input_report_key(hp_wmi_input_dev, KEY_KBDILLUMTOGGLE, true); > + input_sync(hp_wmi_input_dev); > + input_report_key(hp_wmi_input_dev, KEY_KBDILLUMTOGGLE, false); > + input_sync(hp_wmi_input_dev); > + > + fourzone_update_brightness(); Does you calling fourzone_update_brightness() here mean that the embedded controller (EC) if the laptop toggles the kbd backlight on/off itself when you press the Fn + key combo for this on the kbd ? In that case you should not be sending a KEY_KBDILLUMTOGGLE key press event. That event is telling userspace that it should toggle the brightness, which you should only do if this is not done by the EC itself. If the EC does indeed toggle the brightness on/off (or even cycles it between various brightness levels) then the right thing to do is to call led_classdev_notify_brightness_hw_changed() on mc_led.led_cdev for each of the 4 zones when receiving this event. > + } > break; > case HPWMI_PEAKSHIFT_PERIOD: > break; > @@ -1505,6 +1577,154 @@ static int thermal_profile_setup(void) > return 0; > } > > +static int fourzone_set_colors(u32 color, size_t zone) > +{ > + u8 buff[128]; > + int ret; > + > + ret = hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_GET, HPWMI_FOURZONE, > + &buff, sizeof(buff), sizeof(buff)); > + if (ret != 0) > + return -EINVAL; You are doing a read + modify + write of the kbd settings here. This is fine, but to avoid racing against another r/m/w cycle done at the same time if userspace writes 2 zones at the same time you need to take a mutex here. > + > + buff[25 + zone * 3] = FIELD_GET(FOURZONE_COLOR_R, color); > + buff[25 + zone * 3 + 1] = FIELD_GET(FOURZONE_COLOR_G, color); > + buff[25 + zone * 3 + 2] = FIELD_GET(FOURZONE_COLOR_B, color); As mentioned above this is wrong. You should have a separate mc_subled struct for each color for each zone (so 3 mc_subled-s per zone, one for each of R, G and B; and the you take subled.brightness field from the 3 subleds for the 3 different values. > + > + return hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_SET, HPWMI_FOURZONE, > + &buff, sizeof(buff), sizeof(buff)); > +} > + > +static int fourzone_get_colors(u32 *colors) > +{ > + u8 buff[128]; > + int ret; > + > + ret = hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_GET, HPWMI_FOURZONE, > + &buff, sizeof(buff), sizeof(buff)); > + if (ret != 0) > + return -EINVAL; > + > + for (int i = 0; i < 4; i++) { > + colors[i] = FIELD_PREP(FOURZONE_COLOR_R, buff[25 + i * 3]) > + | FIELD_PREP(FOURZONE_COLOR_G, buff[25 + i * 3 + 1]) > + | FIELD_PREP(FOURZONE_COLOR_B, buff[25 + i * 3 + 2]); > + } same here. > + > + return 0; > +} > + > +static void set_fourzone_brightness(struct led_classdev *led_cdev, enum led_brightness brightness) > +{ > + size_t zone; > + > + for (size_t i = 0; i < 4; i++) > + if (strcmp(led_cdev->name, fourzone_zone_names[i]) == 0) > + zone = i; > + So the way how the multicolor LED class devices work is that they have 2 brightness controls: /sys/class/leds/hp:rgb:kbd_zoned_backlight-right/brightness /sys/class/leds/hp:rgb:kbd_zoned_backlight-right/multi_intensity Where brightness is a single integer value for overall brightness control and multi_intensity is a per channel brightness control, also see Documentation/leds/leds-class-multicolor.rst Now most hw does not have a main/overall brightness control only per channel controls (like this hp code) so there is a helper which you pass the overall brightness value and which then calculates the per channel brightnesses for you. The LED core code caches the last multi_intensity values for you and there is a helper you can call from the (this) brightness_set() callback: led_mc_calc_color_components(&fourzone_leds[i].mc_led, brightness); which will then update the fourzone_leds[i].subleds[0 - 2].brightness values with the desired per channel brightness values and then you can use those 3 brightness values in fourzone_set_colors() to send to the keyboard. > + if (fourzone_leds.leds[zone].subled_info->intensity == fourzone_leds.color_cache[zone]) { > + u8 buff[4] = { > + brightness == LED_ON ? FOURZONE_LIGHTING_ON : FOURZONE_LIGHTING_OFF, > + 0, 0, 0 > + }; > + > + hp_wmi_perform_query(HPWMI_LED_BRIGHTNESS_SET, HPWMI_FOURZONE, &buff, > + sizeof(buff), 0); > + > + fourzone_update_brightness(); > + } else { > + fourzone_set_colors(fourzone_leds.leds[zone].subled_info->intensity, zone); > + fourzone_leds.color_cache[zone] = fourzone_leds.leds[zone].subled_info->intensity; > + } And this weird cahce thing can be removed then too, just always send the newly calculated 3 brightness values to the kbd. > +} > + > +static int __init fourzone_leds_init(struct platform_device *device) > +{ > + enum led_brightness brightness; > + u32 colors[4]; > + int ret; > + > + ret = fourzone_get_colors(colors); > + if (ret < 0) > + return ret; > + > + memcpy(fourzone_leds.color_cache, colors, sizeof(colors)); > + > + brightness = get_fourzone_brightness(NULL); > + > + for (size_t i = 0; i < 4; i++) { > + fourzone_leds.subleds[i] = (struct mc_subled) { > + .color_index = LED_COLOR_ID_RGB, > + .brightness = 1, > + .intensity = colors[i] > + }; So over here you need to have a nested for loop to initialize all 3 subleds for each zone. > + > + fourzone_leds.leds[i] = (struct led_classdev_mc) { > + .led_cdev = { > + .name = fourzone_zone_names[i], > + .brightness = brightness, > + .max_brightness = 1, This should be 255 now. > + .brightness_set = set_fourzone_brightness, > + ` .brightness_get = get_fourzone_brightness, > + .color = LED_COLOR_ID_RGB, > + .flags = LED_BRIGHT_HW_CHANGED > + }, > + .num_colors = 1, This should be 3. > + .subled_info = &fourzone_leds.subleds[i] And this points to fourzone_leds[i].subleds now. > + }; > + > + ret = devm_led_classdev_multicolor_register(&device->dev, &fourzone_leds.leds[i]); > + if (ret) > + return -ENODEV; > + } > + > + return 0; > +} > + > +static enum hp_wmi_keyboardtype fourzone_get_keyboard_type(void) > +{ > + u8 buff[128]; > + int ret; > + > + ret = hp_wmi_perform_query(HPWMI_GET_KEYBOARD_TYPE, HPWMI_GM, > + &buff, sizeof(buff), sizeof(buff)); > + if (ret != 0) > + return HPWMI_KEYBOARD_INVALID; > + > + /* the first byte in the response represents the keyboard type */ > + return (enum hp_wmi_keyboardtype)(buff[0] + 1); > +} > + > +static bool fourzone_supports_lighting(void) > +{ > + u8 buff[128]; > + int ret; > + > + ret = hp_wmi_perform_query(HPWMI_SUPPORTS_LIGHTNING, HPWMI_FOURZONE, > + &buff, sizeof(buff), sizeof(buff)); > + if (ret != 0) > + return false; > + > + return buff[0] & FOURZONE_LIGHTING_SUPPORTED_BIT; > +} > + > +static int fourzone_setup(struct platform_device *device) > +{ > + if (!fourzone_supports_lighting()) > + return -ENODEV; > + > + if (fourzone_get_keyboard_type() != HPWMI_KEYBOARD_WITHOUT_NUMPAD) > + return -ENODEV; > + > + /* register leds */ > + if (fourzone_leds_init(device) < 0) > + return -ENODEV; > + > + input_set_capability(hp_wmi_input_dev, KE_KEY, KEY_KBDILLUMTOGGLE); > + > + return 0; > +} > + > static int hp_wmi_hwmon_init(void); > > static int __init hp_wmi_bios_setup(struct platform_device *device) > @@ -1534,6 +1754,10 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) > > thermal_profile_setup(); > > + /* setup 4 zone rgb */ > + if (!fourzone_setup(device)) > + fourzone_lightning_support = true; > + > return 0; > } > > @@ -1561,6 +1785,12 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device) > > if (platform_profile_support) > platform_profile_remove(); > + > + if (fourzone_lightning_support) > + for (size_t i = 0; i < 4; i++) { > + devm_led_classdev_multicolor_unregister(&device->dev, > + &fourzone_leds.leds[i]); > + } The whole idea behind devm_register_foo() functions is that they get automatically removed when the driver is unbound from the device. So this code and the fourzone_lightning_support flag are not necessary. Regards, Hans > } > > static int hp_wmi_resume_handler(struct device *device) ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 1/1] HP: wmi: added support for 4 zone keyboard rgb 2024-07-11 17:37 ` Hans de Goede @ 2024-07-12 16:39 ` Carlos Ferreira 2024-07-13 11:29 ` Hans de Goede 0 siblings, 1 reply; 9+ messages in thread From: Carlos Ferreira @ 2024-07-12 16:39 UTC (permalink / raw) To: hdegoede; +Cc: carlosmiguelferreira.2003, linux-kernel, platform-driver-x86 > > drivers/platform/x86/hp/hp-wmi.c | 248 +++++++++++++++++++++++++++++-- > > 1 file changed, 239 insertions(+), 9 deletions(-) > > > > diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c > > index 5fa553023842..5eae47961f76 100644 > > --- a/drivers/platform/x86/hp/hp-wmi.c > > +++ b/drivers/platform/x86/hp/hp-wmi.c > > @@ -14,6 +14,8 @@ > > #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt > > > > #include <linux/kernel.h> > > +#include <linux/led-class-multicolor.h> > > +#include <linux/leds.h> > > This means that you now also need to update Kconfig to depend on > LEDS_CLASS_MULTICOLOR, so add the following line to the existing > Kconfig entry for the HP WMI driver: > > depends on LEDS_CLASS_MULTICOLOR > > Adding this should fix the following errors reported by > the kernel test robot: > > ERROR: modpost: "devm_led_classdev_multicolor_unregister" [drivers/platform/x86/hp/hp-wmi.ko] undefined! > ERROR: modpost: "devm_led_classdev_multicolor_register_ext" [drivers/platform/x86/hp/hp-wmi.ko] undefined! Yes, i completely forgot that. Thank you. > > +static const char * const fourzone_zone_names[4] = { > > + "hp:rgb:kbd_zoned_backlight-right", > > + "hp:rgb:kbd_zoned_backlight-middle", > > + "hp:rgb:kbd_zoned_backlight-left", > > + "hp:rgb:kbd_zoned_backlight-wasd" > > +}; > > + > > +struct hp_fourzone_leds { > > + struct led_classdev_mc leds[4]; > > + struct mc_subled subleds[4]; > > The idea with the multi-color API and subleds is that > a RGB LED really are 3 seperate LEDs (R + G + B) in one. > This is alsohow they are actually phyiscally made. > So for 4 zones you need 12 subleds. > > I think it would be best to to have a single struct per zone: > > struct hp_fourzone_led { > struct led_classdev_mc mc_led; > struct mc_subled subleds[3]; > u32 cache; /* Not sure if you still want this */ > }; > > And then declare an array of 4 of these: > > static struct hp_fourzone_led hp_fourzone_leds[4]; That makes sense, i will do it like that. > > + u32 color_cache[4]; > > +}; > > +static struct hp_fourzone_leds fourzone_leds; > > + > > +static enum led_brightness get_fourzone_brightness(struct led_classdev *led_cdev) > > +{ > > + u8 buff[4]; > > + > > + hp_wmi_perform_query(HPWMI_LED_BRIGHTNESS_GET, HPWMI_FOURZONE, > > + &buff, sizeof(buff), sizeof(buff)); > > + > > + return buff[0] == FOURZONE_LIGHTING_ON ? LED_ON : LED_OFF; > > +} > > + > > +static void fourzone_update_brightness(void) > > +{ > > + unsigned int br; > > + > > + /* synchronize the brightness level on all zones */ > > + br = get_fourzone_brightness(NULL); > > + for (size_t i = 0; i < 4; i++) > > + fourzone_leds.leds[i].led_cdev.brightness = br; > > +} > > + > > static void hp_wmi_notify(u32 value, void *context) > > { > > struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; > > @@ -932,6 +996,14 @@ static void hp_wmi_notify(u32 value, void *context) > > case HPWMI_PROXIMITY_SENSOR: > > break; > > case HPWMI_BACKLIT_KB_BRIGHTNESS: > > + if (fourzone_lightning_support) { > > + input_report_key(hp_wmi_input_dev, KEY_KBDILLUMTOGGLE, true); > > + input_sync(hp_wmi_input_dev); > > + input_report_key(hp_wmi_input_dev, KEY_KBDILLUMTOGGLE, false); > > + input_sync(hp_wmi_input_dev); > > + > > + fourzone_update_brightness(); > > Does you calling fourzone_update_brightness() here mean that the embedded > controller (EC) if the laptop toggles the kbd backlight on/off itself when > you press the Fn + key combo for this on the kbd ? In that case you It switches between the on/off modes. More about this below. > should not be sending a KEY_KBDILLUMTOGGLE key press event. That event > is telling userspace that it should toggle the brightness, which you > should only do if this is not done by the EC itself. Is there a way i could do this that i still get this nice indication on the screen that the key was pressed? For me it would make sense to tell the user that the brightness key was pressed. > If the EC does indeed toggle the brightness on/off (or even cycles it > between various brightness levels) then the right thing to do is to > call led_classdev_notify_brightness_hw_changed() on mc_led.led_cdev for > each of the 4 zones when receiving this event. For this to work properly, it would depend on how we manage the brightness. More about this below. > > + } > > break; > > case HPWMI_PEAKSHIFT_PERIOD: > > break; > > @@ -1505,6 +1577,154 @@ static int thermal_profile_setup(void) > > return 0; > > } > > > > +static int fourzone_set_colors(u32 color, size_t zone) > > +{ > > + u8 buff[128]; > > + int ret; > > + > > + ret = hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_GET, HPWMI_FOURZONE, > > + &buff, sizeof(buff), sizeof(buff)); > > + if (ret != 0) > > + return -EINVAL; > > You are doing a read + modify + write of the kbd settings here. > > This is fine, but to avoid racing against another r/m/w cycle > done at the same time if userspace writes 2 zones at the same > time you need to take a mutex here. I agree with you and Ilpo, this should have a mutex. > > + > > + buff[25 + zone * 3] = FIELD_GET(FOURZONE_COLOR_R, color); > > + buff[25 + zone * 3 + 1] = FIELD_GET(FOURZONE_COLOR_G, color); > > + buff[25 + zone * 3 + 2] = FIELD_GET(FOURZONE_COLOR_B, color); > > As mentioned above this is wrong. You should have a separate mc_subled > struct for each color for each zone (so 3 mc_subled-s per zone, > one for each of R, G and B; and the you take subled.brightness field > from the 3 subleds for the 3 different values. > > > + > > + return hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_SET, HPWMI_FOURZONE, > > + &buff, sizeof(buff), sizeof(buff)); > > +} > > + > > +static int fourzone_get_colors(u32 *colors) > > +{ > > + u8 buff[128]; > > + int ret; > > + > > + ret = hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_GET, HPWMI_FOURZONE, > > + &buff, sizeof(buff), sizeof(buff)); > > + if (ret != 0) > > + return -EINVAL; > > + > > + for (int i = 0; i < 4; i++) { > > + colors[i] = FIELD_PREP(FOURZONE_COLOR_R, buff[25 + i * 3]) > > + | FIELD_PREP(FOURZONE_COLOR_G, buff[25 + i * 3 + 1]) > > + | FIELD_PREP(FOURZONE_COLOR_B, buff[25 + i * 3 + 2]); > > + } > > same here. > > > + > > + return 0; > > +} > > + > > +static void set_fourzone_brightness(struct led_classdev *led_cdev, enum led_brightness brightness) > > +{ > > + size_t zone; > > + > > + for (size_t i = 0; i < 4; i++) > > + if (strcmp(led_cdev->name, fourzone_zone_names[i]) == 0) > > + zone = i; > > + > > So the way how the multicolor LED class devices work is that they have 2 > brightness controls: > > /sys/class/leds/hp:rgb:kbd_zoned_backlight-right/brightness > /sys/class/leds/hp:rgb:kbd_zoned_backlight-right/multi_intensity > > Where brightness is a single integer value for overall brightness > control and multi_intensity is a per channel brightness control, also see > Documentation/leds/leds-class-multicolor.rst > > Now most hw does not have a main/overall brightness control only > per channel controls (like this hp code) so there is a helper which > you pass the overall brightness value and which then calculates the > per channel brightnesses for you. This keyboard uses the Fn + key combo more like a mode switcher where it puts the kbd backligh in on/off mode. What i was doing was taking the overall brightness and using it as a mode controller where it would take only 2 value 1/0 for on/off respectively and propagate the values to the other zones to avoid some edge case problems. If i start using the overall brightness control to control the brightness of each channel (like it should probably be) then i loose the ability to control the modes and we would need to find a way to control them. But i would definitely agree that having mode control and brightness control at the same time would be better. Please note that brightness/color control is completely independent from mode control in this keyboard. I can set any brightness/color to it even if the backligh is off (it will not turn the backligh on) and when turned on, the new color will be there. Perhaps we can create a sysfs entry to control the mode and use the main/overall brightness control the way it was intended? Now that i think about it, what i made was kinda of a mess :/ > The LED core code caches the last multi_intensity values for you > and there is a helper you can call from the (this) brightness_set() > callback: > > led_mc_calc_color_components(&fourzone_leds[i].mc_led, brightness); > > which will then update the fourzone_leds[i].subleds[0 - 2].brightness > values with the desired per channel brightness values and then you can > use those 3 brightness values in fourzone_set_colors() to send to > the keyboard. Again, this would depend on how we make brightness and mode control. > > + if (fourzone_leds.leds[zone].subled_info->intensity == fourzone_leds.color_cache[zone]) { > > + u8 buff[4] = { > > + brightness == LED_ON ? FOURZONE_LIGHTING_ON : FOURZONE_LIGHTING_OFF, > > + 0, 0, 0 > > + }; > > + > > + hp_wmi_perform_query(HPWMI_LED_BRIGHTNESS_SET, HPWMI_FOURZONE, &buff, > > + sizeof(buff), 0); > > + > > + fourzone_update_brightness(); > > + } else { > > + fourzone_set_colors(fourzone_leds.leds[zone].subled_info->intensity, zone); > > + fourzone_leds.color_cache[zone] = fourzone_leds.leds[zone].subled_info->intensity; > > + } > > And this weird cahce thing can be removed then too, just always send the newly calculated > 3 brightness values to the kbd. It is weird indeed but it was used to know if i should change the color or the keyboard mode. So when the color didn't change between calls, i would take the overall brightness and change the mode from it. Obviously, this means loosing brightness control but the way i see it, mode control is more important. > > @@ -1561,6 +1785,12 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device) > > > > if (platform_profile_support) > > platform_profile_remove(); > > + > > + if (fourzone_lightning_support) > > + for (size_t i = 0; i < 4; i++) { > > + devm_led_classdev_multicolor_unregister(&device->dev, > > + &fourzone_leds.leds[i]); > > + } > > The whole idea behind devm_register_foo() functions is that they get automatically > removed when the driver is unbound from the device. So this code and > the fourzone_lightning_support flag are not necessary. That is good to know. I will remove it. ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 1/1] HP: wmi: added support for 4 zone keyboard rgb 2024-07-12 16:39 ` Carlos Ferreira @ 2024-07-13 11:29 ` Hans de Goede 0 siblings, 0 replies; 9+ messages in thread From: Hans de Goede @ 2024-07-13 11:29 UTC (permalink / raw) To: Carlos Ferreira; +Cc: linux-kernel, platform-driver-x86 Hi Carlos, On 7/12/24 6:39 PM, Carlos Ferreira wrote: >>> drivers/platform/x86/hp/hp-wmi.c | 248 +++++++++++++++++++++++++++++-- >>> 1 file changed, 239 insertions(+), 9 deletions(-) >>> >>> diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c >>> index 5fa553023842..5eae47961f76 100644 >>> --- a/drivers/platform/x86/hp/hp-wmi.c >>> +++ b/drivers/platform/x86/hp/hp-wmi.c >>> @@ -14,6 +14,8 @@ >>> #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt >>> >>> #include <linux/kernel.h> >>> +#include <linux/led-class-multicolor.h> >>> +#include <linux/leds.h> >> >> This means that you now also need to update Kconfig to depend on >> LEDS_CLASS_MULTICOLOR, so add the following line to the existing >> Kconfig entry for the HP WMI driver: >> >> depends on LEDS_CLASS_MULTICOLOR >> >> Adding this should fix the following errors reported by >> the kernel test robot: >> >> ERROR: modpost: "devm_led_classdev_multicolor_unregister" [drivers/platform/x86/hp/hp-wmi.ko] undefined! >> ERROR: modpost: "devm_led_classdev_multicolor_register_ext" [drivers/platform/x86/hp/hp-wmi.ko] undefined! > > Yes, i completely forgot that. Thank you. > >>> +static const char * const fourzone_zone_names[4] = { >>> + "hp:rgb:kbd_zoned_backlight-right", >>> + "hp:rgb:kbd_zoned_backlight-middle", >>> + "hp:rgb:kbd_zoned_backlight-left", >>> + "hp:rgb:kbd_zoned_backlight-wasd" >>> +}; >>> + >>> +struct hp_fourzone_leds { >>> + struct led_classdev_mc leds[4]; >>> + struct mc_subled subleds[4]; >> >> The idea with the multi-color API and subleds is that >> a RGB LED really are 3 seperate LEDs (R + G + B) in one. >> This is alsohow they are actually phyiscally made. >> So for 4 zones you need 12 subleds. >> >> I think it would be best to to have a single struct per zone: >> >> struct hp_fourzone_led { >> struct led_classdev_mc mc_led; >> struct mc_subled subleds[3]; >> u32 cache; /* Not sure if you still want this */ >> }; >> >> And then declare an array of 4 of these: >> >> static struct hp_fourzone_led hp_fourzone_leds[4]; > > That makes sense, i will do it like that. > >>> + u32 color_cache[4]; >>> +}; >>> +static struct hp_fourzone_leds fourzone_leds; >>> + >>> +static enum led_brightness get_fourzone_brightness(struct led_classdev *led_cdev) >>> +{ >>> + u8 buff[4]; >>> + >>> + hp_wmi_perform_query(HPWMI_LED_BRIGHTNESS_GET, HPWMI_FOURZONE, >>> + &buff, sizeof(buff), sizeof(buff)); >>> + >>> + return buff[0] == FOURZONE_LIGHTING_ON ? LED_ON : LED_OFF; >>> +} >>> + >>> +static void fourzone_update_brightness(void) >>> +{ >>> + unsigned int br; >>> + >>> + /* synchronize the brightness level on all zones */ >>> + br = get_fourzone_brightness(NULL); >>> + for (size_t i = 0; i < 4; i++) >>> + fourzone_leds.leds[i].led_cdev.brightness = br; >>> +} >>> + >>> static void hp_wmi_notify(u32 value, void *context) >>> { >>> struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; >>> @@ -932,6 +996,14 @@ static void hp_wmi_notify(u32 value, void *context) >>> case HPWMI_PROXIMITY_SENSOR: >>> break; >>> case HPWMI_BACKLIT_KB_BRIGHTNESS: >>> + if (fourzone_lightning_support) { >>> + input_report_key(hp_wmi_input_dev, KEY_KBDILLUMTOGGLE, true); >>> + input_sync(hp_wmi_input_dev); >>> + input_report_key(hp_wmi_input_dev, KEY_KBDILLUMTOGGLE, false); >>> + input_sync(hp_wmi_input_dev); >>> + >>> + fourzone_update_brightness(); >> >> Does you calling fourzone_update_brightness() here mean that the embedded >> controller (EC) if the laptop toggles the kbd backlight on/off itself when >> you press the Fn + key combo for this on the kbd ? In that case you > > It switches between the on/off modes. > More about this below. > >> should not be sending a KEY_KBDILLUMTOGGLE key press event. That event >> is telling userspace that it should toggle the brightness, which you >> should only do if this is not done by the EC itself. > > Is there a way i could do this that i still get this nice indication on the > screen that the key was pressed? Yes, led_classdev_notify_brightness_hw_changed() will also inform userspace about the brightness being changed by the hw itself and at least upower and GNOME support this. One problem is that upower does not know that the new /sys/class/leds/hp:rgb:kbd_zoned_backlight-<zone> devices are for a backlit keyboard. So for testing what you could do is name one of the zones in the driver "hp:rgb:kbd_backlight". Then GNOME should show the nice on screen notification when you toggle the brightness with the key, if you call led_classdev_notify_brightness_hw_changed() on all 4 led_classdev-s when this happens. > For me it would make sense to tell the user that the brightness key was pressed. Right and that is what led_classdev_notify_brightness_hw_changed() does. The difference is led_classdev_notify_brightness_hw_changed() is for notifying of the hw changing the brightness itself, where as KEY_KBDILLUMTOGGLE key press events ask of userspace to toggle the brightness itself. You are not seeing userspace toggling the brightness itself with your testing because upower does not support /sys/class/leds/<foo>:rgb:kbd_zoned_backlight-<zone> devices yet. But once it does support those then sending KEY_KBDILLUMTOGGLE will not only show the on screen notification it will also cause GNOME to toggle the brightness (a second time) basically undoing the hw toggle. >> If the EC does indeed toggle the brightness on/off (or even cycles it >> between various brightness levels) then the right thing to do is to >> call led_classdev_notify_brightness_hw_changed() on mc_led.led_cdev for >> each of the 4 zones when receiving this event. > > For this to work properly, it would depend on how we manage the brightness. > More about this below. > >>> + } >>> break; >>> case HPWMI_PEAKSHIFT_PERIOD: >>> break; >>> @@ -1505,6 +1577,154 @@ static int thermal_profile_setup(void) >>> return 0; >>> } >>> >>> +static int fourzone_set_colors(u32 color, size_t zone) >>> +{ >>> + u8 buff[128]; >>> + int ret; >>> + >>> + ret = hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_GET, HPWMI_FOURZONE, >>> + &buff, sizeof(buff), sizeof(buff)); >>> + if (ret != 0) >>> + return -EINVAL; >> >> You are doing a read + modify + write of the kbd settings here. >> >> This is fine, but to avoid racing against another r/m/w cycle >> done at the same time if userspace writes 2 zones at the same >> time you need to take a mutex here. > > I agree with you and Ilpo, this should have a mutex. > >>> + >>> + buff[25 + zone * 3] = FIELD_GET(FOURZONE_COLOR_R, color); >>> + buff[25 + zone * 3 + 1] = FIELD_GET(FOURZONE_COLOR_G, color); >>> + buff[25 + zone * 3 + 2] = FIELD_GET(FOURZONE_COLOR_B, color); >> >> As mentioned above this is wrong. You should have a separate mc_subled >> struct for each color for each zone (so 3 mc_subled-s per zone, >> one for each of R, G and B; and the you take subled.brightness field >> from the 3 subleds for the 3 different values. >> >>> + >>> + return hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_SET, HPWMI_FOURZONE, >>> + &buff, sizeof(buff), sizeof(buff)); >>> +} >>> + >>> +static int fourzone_get_colors(u32 *colors) >>> +{ >>> + u8 buff[128]; >>> + int ret; >>> + >>> + ret = hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_GET, HPWMI_FOURZONE, >>> + &buff, sizeof(buff), sizeof(buff)); >>> + if (ret != 0) >>> + return -EINVAL; >>> + >>> + for (int i = 0; i < 4; i++) { >>> + colors[i] = FIELD_PREP(FOURZONE_COLOR_R, buff[25 + i * 3]) >>> + | FIELD_PREP(FOURZONE_COLOR_G, buff[25 + i * 3 + 1]) >>> + | FIELD_PREP(FOURZONE_COLOR_B, buff[25 + i * 3 + 2]); >>> + } >> >> same here. >> >>> + >>> + return 0; >>> +} >>> + >>> +static void set_fourzone_brightness(struct led_classdev *led_cdev, enum led_brightness brightness) >>> +{ >>> + size_t zone; >>> + >>> + for (size_t i = 0; i < 4; i++) >>> + if (strcmp(led_cdev->name, fourzone_zone_names[i]) == 0) >>> + zone = i; >>> + >> >> So the way how the multicolor LED class devices work is that they have 2 >> brightness controls: >> >> /sys/class/leds/hp:rgb:kbd_zoned_backlight-right/brightness >> /sys/class/leds/hp:rgb:kbd_zoned_backlight-right/multi_intensity >> >> Where brightness is a single integer value for overall brightness >> control and multi_intensity is a per channel brightness control, also see >> Documentation/leds/leds-class-multicolor.rst >> >> Now most hw does not have a main/overall brightness control only >> per channel controls (like this hp code) so there is a helper which >> you pass the overall brightness value and which then calculates the >> per channel brightnesses for you. > > This keyboard uses the Fn + key combo more like a mode switcher where it puts > the kbd backligh in on/off mode. > > What i was doing was taking the overall brightness and using it as a mode controller > where it would take only 2 value 1/0 for on/off respectively and propagate the values > to the other zones to avoid some edge case problems. > > If i start using the overall brightness control to control the brightness of each > channel (like it should probably be) then i loose the ability to control the modes > and we would need to find a way to control them. > > But i would definitely agree that having mode control and brightness control at the same > time would be better. > > Please note that brightness/color control is completely independent from mode control in > this keyboard. I can set any brightness/color to it even if the backligh is off (it will not > turn the backlight on) and when turned on, the new color will be there. > > Perhaps we can create a sysfs entry to control the mode and use the main/overall brightness > control the way it was intended? So basically we have 1 on/off toggle for the entire keyboard and 12 rgb channels as controls. So my suggestion would be to do as I proposed previously which is to use the standard multi-color LED model / helpers with an overall brightness value per zone and multi_intensity with 3 values for the R / G / B ratio and then set the global on/off toggle to off if all 4 main brightness values are set to off (to zero). So then the brightness_set() callback function should do something like this: #define KBD_ZONE_COUNT 4 struct hp_fourzone_led { struct led_classdev_mc mc_led; struct mc_subled subleds[3]; /* * This stores the last set brightness level to restore it on off->on toggle * by the FN-key combo. */ enum led_brightness brightness; }; static void set_fourzone_brightness(struct led_classdev *led_cdev, enum led_brightness brightness) { u8 buff[4] = { }; int i, zone = 0; bool on = false; for (i = 0; i < KBD_ZONE_COUNT; i++) if (strcmp(led_cdev->name, fourzone_zone_names[i]) == 0) zone = i; /* Always update main and per color brightness values even when the backlight is off */ fourzone_leds[zone].brightness = brightness; fourzone_leds[zone].mc_led.led_cdev.brightness = brightness; led_mc_calc_color_components(&fourzone_leds[zone].mc_led, brightness); /* Update the RGB channels for the current zone */ /* This should use a new version of fourzone_set_colors() which sends the fourzone_leds[zone].subleds[0 - 2].brightness to the kbd */ fourzone_set_colors(zone); for (i = 0; i < KBD_ZONE_COUNT; i++) if (fourzone_leds[i].mc_led.led_cdev.brightness) on = true; buff[0] = on ? FOURZONE_LIGHTING_ON : FOURZONE_LIGHTING_OFF; hp_wmi_perform_query(HPWMI_LED_BRIGHTNESS_SET, HPWMI_FOURZONE, &buff, sizeof(buff), 0); } Admittedly this is the easier part. The harder part is what to do on brightness_get() I would not implement a brightness_get() callback, since that then will be called every time userspace reads the brightness. Since there is an event when the brightness is changed underneath us by the hw we don't need to poll the hw. By leaving out the brightness_get() callback we get to control when we read back the info from the hw. So instead of implementing a brightness_get() callback, there should be a helper to get the state of the global on/off toggle and use this in probe() to set initial brightness values. Also the actual brightness level (without the on/off toggle taking into account) should now become an extra member of struct hp_fourzone_led replacing the old cache this should always represent the latest brightness value set by either probe() or userspace calling set_brightness() even if the toggle is off (I already added this above). And then on events that the Fn-key combo is used, call the helper to get the new state. If the new state is off, set fourzone_leds[0-3].mc_led.led_cdev.brightness to 0 for all 4 zones *before* calling led_classdev_notify_brightness_hw_changed(), so that when userspace reads /sys/class/leds/hp:rgb:kbd_zoned_backlight-<zone>/brightness it will read 0. If the new state is on, restore the old brightness levels saved in fourzone_leds[i].brightness to fourzone_leds[i].mc_led.led_cdev.brightness *before* calling led_classdev_notify_brightness_hw_changed(). This way we correctly reflect the on/off toggle state to userspace, making /sys/class/leds/hp:rgb:kbd_zoned_backlight-<zone>/brightness read 0 when it is off, while otherwise making things behave as standard multi-color LED class devices. > Now that i think about it, what i made was kinda of a mess :/ No worries, that is what the review process is for :) Regards, Hans ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2024-07-13 11:29 UTC | newest] Thread overview: 9+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2024-07-07 17:54 [PATCH v3 0/1] HP: wmi: added support for 4 zone keyboard rgb Carlos Ferreira 2024-07-07 17:54 ` [PATCH v3 1/1] " Carlos Ferreira 2024-07-07 22:22 ` kernel test robot 2024-07-08 0:36 ` kernel test robot 2024-07-11 8:41 ` Ilpo Järvinen 2024-07-11 15:17 ` Hans de Goede 2024-07-11 17:37 ` Hans de Goede 2024-07-12 16:39 ` Carlos Ferreira 2024-07-13 11:29 ` Hans de Goede
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox