Re: [PATCH v5] platform/x86: lenovo-super-hotkey-wmi.c: Support formic and audio mute LEDs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu Feb 13, 2025 at 4:32 AM -05, Jackie Dong wrote:
> Hi Kurt,
>     Thanks for your careful review and new comments which are helpful 
> for me to understand maintainers ideas, pls review my comments as below.
>     I'll submit a new revison of the patch after get your feedback.
> On 2/13/25 01:43, Kurt Borja wrote:
>> Hi Jackie,
>> 
>> I left some style recommendations below and a couple questions. I
>> apologize for not including this in the last review.
>> 
>> On Wed Feb 12, 2025 at 2:01 AM -05, Jackie Dong wrote:
>>> Implement Lenovo utility data WMI calls needed to make LEDs
>>> work on Ideapads that support this GUID.
>>> This enables the mic and audio LEDs to be updated correctly.
>>>
>>> Tested on below samples.
>>> ThinkBook 13X Gen4 IMH
>>> ThinkBook 14 G6 ABP
>>> ThinkBook 16p Gen4-21J8
>>> ThinkBook 16 G8 IRL
>>> ThinkBook 16 G7+ ASP
>>>
>>> Signed-off-by: Jackie Dong <xy-jackie@xxxxxxx>
>>> Suggested-by: Mark Pearson <mpearson-lenovo@xxxxxxxxx>
>>> ---
>>> Changes in v5:
>>>   - Take out union acpi_object *obj __free(kfree) = output.pointer from
>>>     if-else block
>>>   - Remove lsk_wmi_context_lud_keys related source code
>>>
>>> Changes in v4:
>>>   - Add related head files include cleanup.h, dev_printk.h, device.h,
>>>     module.h
>>>   - Replaced kfree() by __free()
>>>   - Remove double free for obj
>>>   - Remove wpriv->cdev[led_type].dev = dev
>>>   - Remove *wpriv = *(const struct lenovo_super_hotkey_wmi_private *)context
>>>   - Remove wpriv->event == LSH_WMI_EVENT_LUD_KEYS
>>>   - Remove lenovo_super_hotkey_wmi_remove() for unnecessary
>>>
>>> Changes in v3:
>>>   - Changed the name of the Kconfig entry to LENOVO_SUPER_HOTKEY_WMI
>>>   - Renamed everything in this driver which contains the name "ideapad"
>>>     to instead contain the name of this driver.
>>>   - Moved struct wmi_device *led_wdev in lenovo_super_hotkey_wmi_private,
>>>     and use container_of() to the led_wdev pointer.
>>>   - Replaced sizeof(struct wmi_led_args) by sizeof(led_arg)
>>>   - Added condtions checking for obj && obj->type == ACPI_TYPE_INTEGER
>>>     and free the ACPI object after get the required value.
>>>   - Removed led_classdev_unregister() after led_reg_failed label, but
>>>     add lenovo_super_hotkey_wmi_remove(struct wmi_device *wdev) to free
>>>     resource.
>>>   - Removed IDEAPAD_WMI_EVENT_FN_KEYS/IDEAPAD_WMI_EVENT_LUD_KEYS related
>>>     source codes and only keep LUD_WMI_METHOD_GUID.
>>>
>>> Changes in v2:
>>>   - Update code layout and formatting as recommended in review
>>>   - Improved error handling in ideapad_wmi_led_init
>>>   - Separated a WMI driver named lenovo-super-hotkey-wmi.c from
>>>     ideapad-lap.c, it's only for Lenovo Super Hotkey WMI devices.
>>>
>>>   drivers/platform/x86/Kconfig                  |   9 +
>>>   drivers/platform/x86/Makefile                 |   1 +
>>>   .../platform/x86/lenovo-super-hotkey-wmi.c    | 222 ++++++++++++++++++
>>>   3 files changed, 232 insertions(+)
>>>   create mode 100644 drivers/platform/x86/lenovo-super-hotkey-wmi.c
>>>
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> index 0258dd879d64..c1792e8f04e1 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -475,6 +475,15 @@ config IDEAPAD_LAPTOP
>>>   	  This is a driver for Lenovo IdeaPad netbooks contains drivers for
>>>   	  rfkill switch, hotkey, fan control and backlight control.
>>>   
>>> +config LENOVO_SUPER_HOTKEY_WMI
>>> +	tristate "Lenovo Super Hotkey Utility WMI extras driver"
>>> +	depends on ACPI_WMI
>>> +	depends on IDEAPAD_LAPTOP
>> 
>> Does this still depend on IDEAPAD_LAPTOP? Also add:
>> 	select NEW_LEDS
>> 	select LEDS_CLASS
>> 
> In my view, keep depend on IDEAPAD, because that mic-mute LED function 
> doesn't work without ideapad_laptop.ko. Due to KEY_MICMUTE event is 
> handled in ideapad_laptop. And this file only turn on/off MIC mute LED 
> after the KEY_MICMUTE pressed. For Audio mute LED, audio mute key is 
> common handled by keyboard driver, but MIC mute key is special and 
> handled in ideapad_laptop.ko for Lenovo non-ThinkPad products. Audio 
> mute and MIC mute function can work when user press related hotkey in 
> past, only it's mute LED doesn't turn on/off.
> For ThinkPad products, MIC mute key is handled in thinkpad_acpi.

Oh - Thanks, this is important information.

>
> { KE_KEY,       0x3e | IDEAPAD_WMI_KEY, { KEY_MICMUTE } }, in 
> ideapad_keymap[].
>
> This LENOVO_SUPER_HOTKEY_WMI depends on IDEAPAD_LAPTOP which included 
> led moudles(NEW_LEDS/LEDS_CLASS) as below. Maybe it shoud be enough.

I'd add them anyway, to be explicit about what this module needs.

>
> config IDEAPAD_LAPTOP
>          tristate "Lenovo IdeaPad Laptop Extras"
>          depends on ACPI
>          depends on RFKILL && INPUT
>          depends on SERIO_I8042
>          depends on BACKLIGHT_CLASS_DEVICE
>          depends on ACPI_VIDEO || ACPI_VIDEO = n
>          depends on ACPI_WMI || ACPI_WMI = n
>          select ACPI_PLATFORM_PROFILE
>          select INPUT_SPARSEKMAP
>          select NEW_LEDS
>          select LEDS_CLASS
>          help
>            This is a driver for Lenovo IdeaPad netbooks contains drivers for
>            rfkill switch, hotkey, fan control and backlight control.
>
>>> +	help
>>> +	  This driver provides WMI support for Lenovo customized hotkeys function
>>> +	  of Lenovo NoteBooks which are for Consumer and SMB customers, such as
>>> +	  Ideapad/YOGA/XiaoXin/Gaming/ThinkBook and so on.
>> 
>> This driver deals with mute key leds. Is this description right?
> In fact, this driver should handle all lenovo super hotkey functions 
> which are implemeteled by wmi. Audio mute LED and Mic mute LED are just 
> the two functions of them. The driver will be extented for all lenovo 
> super hotkey in future. I have to say that different products are with 
> different hotkey, not all products support all hotkeys function, such as 
> many ThinkBook products without MIC mute LED. Currently,

Interesting.

As this driver doesn't handle the hotkeys directly, may I suggest some
slight rewording? Something like:

"This driver provides WMI support for Lenovo hotkey utilities, such as
LED control for audio/mic mute events for IdeaPad, etc..."

Remember this can be edited as new features are implemented.

>
> Some links are FYR.
> How to use Lenovo Hotkeys on keyboard?
> https://tt-hardware.com/en/pc/how-to-use-lenovo-hotkeys-on-keyboard/
> Lenovo Hotkeys
> https://apps.microsoft.com/detail/9pcmmnb260tx?hl=en-us&gl=US
>
> For these Lenovo Hotkeys, ThinkPad series are impelemented by acpi, but 
> some hotkeys of non-ThinkPad products are impelemented by wmi, special 
> for latest non-ThinkPad products. This is why I write this driver.
>> 
>>> +
>>>   config LENOVO_YMC
>>>   	tristate "Lenovo Yoga Tablet Mode Control"
>>>   	depends on ACPI_WMI
>>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
>>> index e1b142947067..598335da9f55 100644
>>> --- a/drivers/platform/x86/Makefile
>>> +++ b/drivers/platform/x86/Makefile
>>> @@ -61,6 +61,7 @@ obj-$(CONFIG_UV_SYSFS)       += uv_sysfs.o
>>>   # IBM Thinkpad and Lenovo
>>>   obj-$(CONFIG_IBM_RTL)		+= ibm_rtl.o
>>>   obj-$(CONFIG_IDEAPAD_LAPTOP)	+= ideapad-laptop.o
>>> +obj-$(CONFIG_LENOVO_SUPER_HOTKEY_WMI)	+= lenovo-super-hotkey-wmi.o
>>>   obj-$(CONFIG_LENOVO_YMC)	+= lenovo-ymc.o
>>>   obj-$(CONFIG_SENSORS_HDAPS)	+= hdaps.o
>>>   obj-$(CONFIG_THINKPAD_ACPI)	+= thinkpad_acpi.o
>>> diff --git a/drivers/platform/x86/lenovo-super-hotkey-wmi.c b/drivers/platform/x86/lenovo-super-hotkey-wmi.c
>> 
>> This name doesn't tell me a lot about the features of this driver. Is
>> this the internal name of the WMI device?
> Reference previous comment.
> If you have a better proposal, let me know.

I propose lenovo-wmi-hotkey-utilities.c.

Let me know what you think about the description and name proposals.

>> 
>>> new file mode 100644
>>> index 000000000000..86c3dc2009b8
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-super-hotkey-wmi.c
>>> @@ -0,0 +1,222 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + *  Lenovo Super Hotkey Utility WMI extras driver for Ideapad laptop
>>> + *
>>> + *  Copyright (C) 2025	Lenovo
>>> + */
>>> +
>>> +#include <linux/cleanup.h>
>>> +#include <linux/dev_printk.h>
>>> +#include <linux/device.h>
>>> +#include <linux/leds.h>
>>> +#include <linux/module.h>
>>> +#include <linux/wmi.h>
>>> +
>>> +/* Lenovo Super Hotkey WMI GUIDs */
>>> +#define LUD_WMI_METHOD_GUID	"CE6C0974-0407-4F50-88BA-4FC3B6559AD8"
>>> +
>>> +/* Lenovo Utility Data WMI method_id */
>>> +#define WMI_LUD_GET_SUPPORT 1
>>> +#define WMI_LUD_SET_FEATURE 2
>>> +
>>> +#define WMI_LUD_GET_MICMUTE_LED_VER   20
>>> +#define WMI_LUD_GET_AUDIOMUTE_LED_VER 26
>>> +
>>> +#define WMI_LUD_SUPPORT_MICMUTE_LED_VER   25
>>> +#define WMI_LUD_SUPPORT_AUDIOMUTE_LED_VER 27
>>> +
>>> +/* Input parameters to mute/unmute audio LED and Mic LED */
>>> +struct wmi_led_args {
>>> +	u8 id;
>>> +	u8 subid;
>>> +	u16 value;
>>> +};
>>> +
>>> +/* Values of input parameters to SetFeature of audio LED and Mic LED */
>>> +enum hotkey_set_feature {
>>> +	MIC_MUTE_LED_ON = 1,
>>> +	MIC_MUTE_LED_OFF,
>>> +	AUDIO_MUTE_LED_ON = 4,
>> 
>> Please align this values.
> For this comment, I understand it should be as below. If my 
> understanding is insufficient,pls give me a sample to update it.
>
> +enum hotkey_set_feature {
> +       MIC_MUTE_LED_ON   = 1,
> +       MIC_MUTE_LED_OFF,
> +       AUDIO_MUTE_LED_ON = 4,
> +       AUDIO_MUTE_LED_OFF,

I'd go for:

	MIC_MUTE_LED_ON		= 1,
	MIC_MUTE_LED_ON		= 2,
	AUDIO_MUTE_LED_ON	= 4,
	AUDIO_MUTE_LED_OFF	= 5,

To be more explicit.

>
>
>> 
>>> +	AUDIO_MUTE_LED_OFF,
>>> +};
>>> +
>>> +#define LSH_ACPI_LED_MAX 2
>>> +
>>> +struct lenovo_super_hotkey_wmi_private {
>>> +	struct led_classdev cdev[LSH_ACPI_LED_MAX];
>>> +	struct wmi_device *led_wdev;
>>> +};
>>> +
>>> +enum mute_led_type {
>>> +	MIC_MUTE,
>>> +	AUDIO_MUTE,
>>> +};
>>> +
>>> +static int lsh_wmi_mute_led_set(enum mute_led_type led_type, struct led_classdev *led_cdev,
>>> +				enum led_brightness brightness)
>>> +
>>> +{
>>> +	struct lenovo_super_hotkey_wmi_private *wpriv = container_of(led_cdev,
>>> +			struct lenovo_super_hotkey_wmi_private, cdev[led_type]);
>>> +	struct wmi_led_args led_arg = {0, 0, 0};
>>> +	struct acpi_buffer input;
>>> +	acpi_status status;
>>> +
>>> +	switch (led_type) {
>>> +	case MIC_MUTE:
>>> +		led_arg.id = brightness == LED_ON ? MIC_MUTE_LED_ON : MIC_MUTE_LED_OFF;
>>> +		break;
>>> +	case AUDIO_MUTE:
>>> +		led_arg.id = brightness == LED_ON ? AUDIO_MUTE_LED_ON : AUDIO_MUTE_LED_OFF;
>>> +		break;
>>> +	default:
>>> +		return -EINVAL;
>>> +	}
>>> +
>>> +	input.length = sizeof(led_arg);
>>> +	input.pointer = &led_arg;
>>> +	status = wmidev_evaluate_method(wpriv->led_wdev, 0, WMI_LUD_SET_FEATURE, &input, NULL);
>>> +	if (ACPI_FAILURE(status))
>>> +		return -EIO;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int lsh_wmi_audiomute_led_set(struct led_classdev *led_cdev,
>>> +				     enum led_brightness brightness)
>>> +
>>> +{
>>> +	return lsh_wmi_mute_led_set(AUDIO_MUTE, led_cdev, brightness);
>>> +}
>>> +
>>> +static int lsh_wmi_micmute_led_set(struct led_classdev *led_cdev,
>>> +				   enum led_brightness brightness)
>>> +{
>>> +	return lsh_wmi_mute_led_set(MIC_MUTE, led_cdev, brightness);
>>> +}
>>> +
>>> +static int lenovo_super_hotkey_wmi_led_init(enum mute_led_type led_type, struct device *dev)
>>> +{
>>> +	struct lenovo_super_hotkey_wmi_private *wpriv = dev_get_drvdata(dev);
>>> +	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
>>> +	struct acpi_buffer input;
>>> +	int led_version, err = 0;
>>> +	unsigned int wmiarg;
>>> +	acpi_status status;
>>> +
>>> +	switch (led_type) {
>>> +	case MIC_MUTE:
>>> +		wmiarg = WMI_LUD_GET_MICMUTE_LED_VER;
>>> +		break;
>>> +	case AUDIO_MUTE:
>>> +		wmiarg = WMI_LUD_GET_AUDIOMUTE_LED_VER;
>>> +		break;
>>> +	default:
>>> +		return -EINVAL;
>>> +	}
>>> +
>>> +	input.length = sizeof(wmiarg);
>>> +	input.pointer = &wmiarg;
>>> +	status = wmidev_evaluate_method(wpriv->led_wdev, 0, WMI_LUD_GET_SUPPORT, &input, &output);
>>> +	if (ACPI_FAILURE(status))
>>> +		return -EIO;
>>> +
>>> +	union acpi_object *obj __free(kfree) = output.pointer;
>>> +	if (obj && obj->type == ACPI_TYPE_INTEGER) {
>>> +		led_version = obj->integer.value;
>>> +	} else {
>>> +		err = -EIO;
>>> +		return err;
>> 
>> Return -EIO directly here and drop the braces on both branches.
>> 
> OK, will update it next revision.
>>> +	}
>>> +
>>> +	wpriv->cdev[led_type].max_brightness = LED_ON;
>>> +	wpriv->cdev[led_type].flags = LED_CORE_SUSPENDRESUME;
>>> +
>>> +	switch (led_type) {
>>> +	case MIC_MUTE:
>>> +		if (led_version != WMI_LUD_SUPPORT_MICMUTE_LED_VER) {
>>> +			err = -EIO;
>>> +			goto led_error;
>> 
>> Return -EIO and drop brances.
>> 
> OK, will update it next revision.
>>> +		}
>>> +		wpriv->cdev[led_type].name = "platform::micmute";
>>> +		wpriv->cdev[led_type].brightness_set_blocking = &lsh_wmi_micmute_led_set;
>>> +		wpriv->cdev[led_type].default_trigger = "audio-micmute";
>>> +
>>> +		err = devm_led_classdev_register(dev, &wpriv->cdev[led_type]);
>>> +		if (err < 0)
>>> +			goto led_reg_failed;
>>> +
>>> +		break;
>>> +	case AUDIO_MUTE:
>>> +		if (led_version != WMI_LUD_SUPPORT_AUDIOMUTE_LED_VER) {
>>> +			err = -EIO;
>>> +			goto led_error;
>> 
>> Return -EIO and drop brances.
>> 
> OK, will update it next revision.
>>> +		}
>>> +		wpriv->cdev[led_type].name = "platform::mute";
>>> +		wpriv->cdev[led_type].brightness_set_blocking = &lsh_wmi_audiomute_led_set;
>>> +		wpriv->cdev[led_type].default_trigger = "audio-mute";
>>> +
>>> +		err = devm_led_classdev_register(dev, &wpriv->cdev[led_type]);
>>> +		if (err < 0)
>>> +			goto led_reg_failed;
>>> +
>>> +		break;
>>> +	default:
>>> +		err = -EINVAL;
>>> +		dev_err(dev, "Unknown LED type %d\n", led_type);
>>> +		goto led_error;
>> 
>> Return -EINVAL directly.
>> 
> OK, will update it next revision.
>>> +	}
>>> +
>>> +	return 0;
>>> +
>>> +led_reg_failed:
>>> +	dev_err(dev, "Could not register mute LED %d : %d\n", led_type, err);
>>> +
>>> +led_error:
>>> +	return err;
>>> +}
>>> +
>>> +static void lenovo_super_hotkey_wmi_leds_setup(struct device *dev)
>>> +{
>>> +	lenovo_super_hotkey_wmi_led_init(MIC_MUTE, dev);
>>> +	lenovo_super_hotkey_wmi_led_init(AUDIO_MUTE, dev);
>> 
>> I wonder why you decided not to propagate errors of this calls. Maybe
>> you are expecting one to fail but not the other?
>> 
>> If this both fail this module would remain loaded for no reason, so
>> maybe propagate some error in that case.
>> 
> Oh, it's my fault. I'll update related source code as below.
>
> static int lenovo_super_hotkey_wmi_leds_setup(struct device *dev)
> {
>          int err;
>          err = lenovo_super_hotkey_wmi_led_init(MIC_MUTE, dev);
>          if (err)
>                  return err;
>
>          err = lenovo_super_hotkey_wmi_led_init(AUDIO_MUTE, dev);
>          if (err)
>                  return err;
>
>          return 0;
> }
>
>>> +}
>>> +
>>> +static int lenovo_super_hotkey_wmi_probe(struct wmi_device *wdev, const void *context)
>>> +{
>>> +	struct lenovo_super_hotkey_wmi_private *wpriv;
>>> +
>>> +	wpriv = devm_kzalloc(&wdev->dev, sizeof(*wpriv), GFP_KERNEL);
>>> +	if (!wpriv)
>>> +		return -ENOMEM;
>>> +
>>> +	dev_set_drvdata(&wdev->dev, wpriv);
>>> +	wpriv->led_wdev = wdev;
>>> +	lenovo_super_hotkey_wmi_leds_setup(&wdev->dev);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static const struct wmi_device_id lenovo_super_hotkey_wmi_id_table[] = {
>>> +	{ LUD_WMI_METHOD_GUID, NULL }, /* Utility data */
>> 
>> Maybe drop this comment.
>> 
>  From Lenovo Keyboard WMI Specification V3.6, there're 3 WMI GUIDs at 
> least now. I hope to keep the comment which should be helpful for new 
> developer of this driver.

Then it's fine IMO.

-- 
 ~ Kurt

>
> [WMI,
> Dynamic,
> Provider("WmiProv"),
> Locale("MS\\0x409"),
> Description("Lenovo Utility Key Press Event"),
> guid("{8fc0de0c-b4e4-43fd-b0f3-8871711c1294}")
> ]
> class LENOVO_UTILITY_EVENT: WMIEvent
> ...
> [WMI,
> Dynamic,
> Provider("WmiProv"),
> Locale("MS\\0x409"),
> Description("LENOVO_UTILITY_DATA class"),
> guid("{ce6c0974-0407-4f50-88ba-4fc3b6559ad8}")
> ]
> class LENOVO_UTILITY_DATA
> ...
> LENOVO_INTERNAL_PANEL_REFRESH_RATE_DATA can
> get panel refresh rate)
> Namespace:L"root\\wmi"
> Class LENOVO_INTERNAL_PANEL_REFRESH_RATE_DATA
> GUID guid("{6260ecad-0d7d-4201-a8bd-2552e812501f}"
>
>> Additionally, please CC me and previous reviewers the next revision.
>> 
> OK, will do next revision.
>> --
>>   ~ Kurt
>>
>>> +	{ }
>>> +};
>>> +
>>> +MODULE_DEVICE_TABLE(wmi, lenovo_super_hotkey_wmi_id_table);
>>> +
>>> +static struct wmi_driver lenovo_super_hotkey_wmi_driver = {
>>> +	 .driver = {
>>> +		 .name = "lenovo_super_hotkey_wmi",
>>> +		 .probe_type = PROBE_PREFER_ASYNCHRONOUS
>>> +	 },
>>> +	 .id_table = lenovo_super_hotkey_wmi_id_table,
>>> +	 .probe = lenovo_super_hotkey_wmi_probe,
>>> +	 .no_singleton = true,
>>> +};
>>> +
>>> +module_wmi_driver(lenovo_super_hotkey_wmi_driver);
>>> +
>>> +MODULE_INFO(depends, "wmi,ideapad-laptop"); MODULE_AUTHOR("Jackie
>>> +Dong <dongeg1@xxxxxxxxxx>"); MODULE_DESCRIPTION("Lenovo Super Hotkey
>>> +Utility WMI extras driver"); MODULE_LICENSE("GPL");
>>






[Index of Archives]     [Linux Kernel Development]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux