diff mbox series

[V6,RESEND] HID: ASUS: Add support for ASUS N-Key keyboard

Message ID 20200923215633.209724-1-luke@ljones.dev (mailing list archive)
State Superseded
Headers show
Series [V6,RESEND] HID: ASUS: Add support for ASUS N-Key keyboard | expand

Commit Message

Luke D. Jones Sept. 23, 2020, 9:56 p.m. UTC
The ASUS N-Key keyboard uses the productId of 0x1866 and is used in
almost all modern ASUS gaming laptops with slight changes to the
firmware. This patch enables: Fn+key hotkeys, keyboard backlight
brightness control, and notify asus-wmi to toggle "fan-mode".

The keyboard has many of the same key outputs as the existing G752
keyboard including a few extras, and varies a little between laptop
models. The key-sets have been split and sub-grouped so that there
will not be conflict between key event codes used.

An existing key event used across some keyboards for "Mic Toggle"
has been changed to emit "F20" as this is what all the main
desktop environments are using.

Additionally this keyboard requires the LED interface to be
intitialised before such things as keyboard backlight control work.

Misc changes in scope: update some hardcoded comparisons to use an
available define.

Signed-off-by: Luke D Jones <luke@ljones.dev>
---
 drivers/hid/hid-asus.c                     | 188 ++++++++++++++++++---
 drivers/hid/hid-ids.h                      |   1 +
 include/linux/platform_data/x86/asus-wmi.h |   2 +
 3 files changed, 166 insertions(+), 25 deletions(-)

--
2.26.2

Comments

Luke D. Jones Oct. 8, 2020, 9:33 a.m. UTC | #1
Jikos, is it possible we could try to get this moving? I have something
like 300+ users in a Discord channel using laptops this this particular
device, and more keep arriving. It's a good problem to have but the
support isn't something I have much time for now. Users rely on a
daemon I wrote to manage ASUS AniME and RGB keyboards, which in turn
relies on this patch, which, in turn, relies on users being able to
either compile a kernel, install a custom one, or use dkms.

The main change from previous version beyond cleanup is to use existing
key event codes for the Aura-mode buttons so that there is no hold-up
to okay the removed *new* codes. In future I will go through the
process of adding custom codes.

Many thanks.
Luke.

On Thu, Sep 24, 2020 at 09:56, Luke D Jones <luke@ljones.dev> wrote:
> The ASUS N-Key keyboard uses the productId of 0x1866 and is used in
> almost all modern ASUS gaming laptops with slight changes to the
> firmware. This patch enables: Fn+key hotkeys, keyboard backlight
> brightness control, and notify asus-wmi to toggle "fan-mode".
> 
> The keyboard has many of the same key outputs as the existing G752
> keyboard including a few extras, and varies a little between laptop
> models. The key-sets have been split and sub-grouped so that there
> will not be conflict between key event codes used.
> 
> An existing key event used across some keyboards for "Mic Toggle"
> has been changed to emit "F20" as this is what all the main
> desktop environments are using.
> 
> Additionally this keyboard requires the LED interface to be
> intitialised before such things as keyboard backlight control work.
> 
> Misc changes in scope: update some hardcoded comparisons to use an
> available define.
> 
> Signed-off-by: Luke D Jones <luke@ljones.dev>
> ---
>  drivers/hid/hid-asus.c                     | 188 
> ++++++++++++++++++---
>  drivers/hid/hid-ids.h                      |   1 +
>  include/linux/platform_data/x86/asus-wmi.h |   2 +
>  3 files changed, 166 insertions(+), 25 deletions(-)
> 
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index c183caf89d49..541c92a7f108 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -26,6 +26,8 @@
>  #include <linux/dmi.h>
>  #include <linux/hid.h>
>  #include <linux/module.h>
> +
> +#include <linux/acpi.h>
>  #include <linux/platform_data/x86/asus-wmi.h>
>  #include <linux/input/mt.h>
>  #include <linux/usb.h> /* For to_usb_interface for T100 touchpad 
> intf check */
> @@ -48,6 +50,8 @@ MODULE_DESCRIPTION("Asus HID Keyboard and 
> TouchPad");
>  #define INPUT_REPORT_ID 0x5d
>  #define FEATURE_KBD_REPORT_ID 0x5a
>  #define FEATURE_KBD_REPORT_SIZE 16
> +#define FEATURE_KBD_LED_REPORT_ID1 0x5d
> +#define FEATURE_KBD_LED_REPORT_ID2 0x5e
> 
>  #define SUPPORT_KBD_BACKLIGHT BIT(0)
> 
> @@ -80,6 +84,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and 
> TouchPad");
>  #define QUIRK_T101HA_DOCK		BIT(9)
>  #define QUIRK_T90CHI			BIT(10)
>  #define QUIRK_MEDION_E1239T		BIT(11)
> +#define QUIRK_ROG_NKEY_KEYBOARD		BIT(12)
> 
>  #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
>  						 QUIRK_NO_INIT_REPORTS | \
> @@ -305,10 +310,33 @@ static int asus_e1239t_event(struct 
> asus_drvdata *drvdat, u8 *data, int size)
>  	return 0;
>  }
> 
> +/*
> + * This enables triggering events in asus-wmi
> + */
> +static int asus_wmi_send_event(struct asus_drvdata *drvdat, u8 code)
> +{
> +	int err;
> +	u32 retval;
> +
> +	err = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS,
> +		ASUS_WMI_METHODID_NOTIF, code, &retval);
> +	if (err) {
> +		pr_warn("Failed to notify asus-wmi: %d\n", err);
> +		return err;
> +	}
> +
> +	if (retval != 0) {
> +		pr_warn("Failed to notify asus-wmi (retval): 0x%x\n", retval);
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
>  static int asus_event(struct hid_device *hdev, struct hid_field 
> *field,
>  		      struct hid_usage *usage, __s32 value)
>  {
> -	if ((usage->hid & HID_USAGE_PAGE) == 0xff310000 &&
> +	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR &&
>  	    (usage->hid & HID_USAGE) != 0x00 &&
>  	    (usage->hid & HID_USAGE) != 0xff && !usage->type) {
>  		hid_warn(hdev, "Unmapped Asus vendor usagepage code 0x%02x\n",
> @@ -321,6 +349,7 @@ static int asus_event(struct hid_device *hdev, 
> struct hid_field *field,
>  static int asus_raw_event(struct hid_device *hdev,
>  		struct hid_report *report, u8 *data, int size)
>  {
> +	int ret;
>  	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
> 
>  	if (drvdata->battery && data[0] == BATTERY_REPORT_ID)
> @@ -332,6 +361,35 @@ static int asus_raw_event(struct hid_device 
> *hdev,
>  	if (drvdata->quirks & QUIRK_MEDION_E1239T)
>  		return asus_e1239t_event(drvdata, data, size);
> 
> +	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
> +		/*
> +		 * Skip these report ID, the device emits a continuous stream 
> associated
> +		 * with the AURA mode it is in which looks like an 'echo'
> +		*/
> +		if (report->id == FEATURE_KBD_LED_REPORT_ID1 ||
> +				report->id == FEATURE_KBD_LED_REPORT_ID2) {
> +			return -1;
> +		/* Additional report filtering */
> +		} else if (report->id == FEATURE_KBD_REPORT_ID) {
> +			/* Fn+F5 "fan" symbol, trigger WMI event to toggle next mode */
> +			if (data[1] == 0xae) {
> +				ret = asus_wmi_send_event(drvdata, 0xae);
> +				if (ret < 0) {
> +					hid_warn(hdev, "Asus failed to trigger fan control event");
> +				}
> +				return -1;
> +			/*
> +			 * G14 and G15 send these codes on some keypresses with no
> +			 * discernable reason for doing so. We'll filter them out to avoid
> +			 * unmapped warning messages later
> +			*/
> +			} else if (data[1] == 0xea || data[1] == 0xec || data[1] == 0x02 
> ||
> +					data[1] == 0x8a || data[1] == 0x9e) {
> +				return -1;
> +			}
> +		}
> +	}
> +
>  	return 0;
>  }
> 
> @@ -344,7 +402,11 @@ static int asus_kbd_set_report(struct hid_device 
> *hdev, u8 *buf, size_t buf_size
>  	if (!dmabuf)
>  		return -ENOMEM;
> 
> -	ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, dmabuf,
> +	/*
> +	 * The report ID should be set from the incoming buffer due to LED 
> and key
> +	 * interfaces having different pages
> +	*/
> +	ret = hid_hw_raw_request(hdev, buf[0], dmabuf,
>  				 buf_size, HID_FEATURE_REPORT,
>  				 HID_REQ_SET_REPORT);
>  	kfree(dmabuf);
> @@ -397,6 +459,44 @@ static int asus_kbd_get_functions(struct 
> hid_device *hdev,
>  	return ret;
>  }
> 
> +static int asus_kbd_led_init(struct hid_device *hdev)
> +{
> +	u8 buf_init_start[] = { FEATURE_KBD_LED_REPORT_ID1, 0xB9 };
> +	u8 buf_init2[] = { FEATURE_KBD_LED_REPORT_ID1, 0x41, 0x53, 0x55, 
> 0x53, 0x20,
> +				0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
> +	u8 buf_init3[] = { FEATURE_KBD_LED_REPORT_ID1,
> +						0x05, 0x20, 0x31, 0x00, 0x08 };
> +	int ret;
> +
> +	hid_warn(hdev, "Asus initialise N-KEY Device");
> +	/* The first message is an init start */
> +	ret = asus_kbd_set_report(hdev, buf_init_start, 
> sizeof(buf_init_start));
> +	if (ret < 0)
> +		hid_err(hdev, "Asus failed to send init start command: %d\n", ret);
> +	/* Followed by a string */
> +	ret = asus_kbd_set_report(hdev, buf_init2, sizeof(buf_init2));
> +	if (ret < 0)
> +		hid_err(hdev, "Asus failed to send init command 1.0: %d\n", ret);
> +	/* Followed by a string */
> +	ret = asus_kbd_set_report(hdev, buf_init3, sizeof(buf_init3));
> +	if (ret < 0)
> +		hid_err(hdev, "Asus failed to send init command 1.1: %d\n", ret);
> +
> +	/* begin second report ID with same data */
> +	buf_init2[0] = FEATURE_KBD_LED_REPORT_ID2;
> +	buf_init3[0] = FEATURE_KBD_LED_REPORT_ID2;
> +
> +	ret = asus_kbd_set_report(hdev, buf_init2, sizeof(buf_init2));
> +	if (ret < 0)
> +		hid_err(hdev, "Asus failed to send init command 2.0: %d\n", ret);
> +
> +	ret = asus_kbd_set_report(hdev, buf_init3, sizeof(buf_init3));
> +	if (ret < 0)
> +		hid_err(hdev, "Asus failed to send init command 2.1: %d\n", ret);
> +
> +	return ret;
> +}
> +
>  static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
>  				   enum led_brightness brightness)
>  {
> @@ -460,19 +560,28 @@ static int asus_kbd_register_leds(struct 
> hid_device *hdev)
>  	unsigned char kbd_func;
>  	int ret;
> 
> -	/* Initialize keyboard */
> -	ret = asus_kbd_init(hdev);
> -	if (ret < 0)
> -		return ret;
> +	if (drvdata->quirks & QUIRK_G752_KEYBOARD) {
> +		/* Initialize keyboard */
> +		ret = asus_kbd_init(hdev);
> +		if (ret < 0)
> +			return ret;
> 
> -	/* Get keyboard functions */
> -	ret = asus_kbd_get_functions(hdev, &kbd_func);
> -	if (ret < 0)
> -		return ret;
> +		/* Get keyboard functions */
> +		ret = asus_kbd_get_functions(hdev, &kbd_func);
> +		if (ret < 0)
> +			return ret;
> 
> -	/* Check for backlight support */
> -	if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
> -		return -ENODEV;
> +		/* Check for backlight support */
> +		if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
> +			return -ENODEV;
> +	}
> +
> +	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
> +		/* Initialize keyboard LED interface and Vendor keys on 0x1866 */
> +		ret = asus_kbd_led_init(hdev);
> +		if (ret < 0)
> +			return ret;
> +	}
> 
>  	drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
>  					      sizeof(struct asus_kbd_leds),
> @@ -751,14 +860,14 @@ static int asus_input_mapping(struct hid_device 
> *hdev,
>  	     usage->hid == (HID_UP_GENDEVCTRLS | 0x0026)))
>  		return -1;
> 
> -	/* ASUS-specific keyboard hotkeys */
> -	if ((usage->hid & HID_USAGE_PAGE) == 0xff310000) {
> +	/* ASUS-specific keyboard hotkeys and led backlight */
> +	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR) {
>  		switch (usage->hid & HID_USAGE) {
>  		case 0x10: asus_map_key_clear(KEY_BRIGHTNESSDOWN);	break;
>  		case 0x20: asus_map_key_clear(KEY_BRIGHTNESSUP);		break;
>  		case 0x35: asus_map_key_clear(KEY_DISPLAY_OFF);		break;
>  		case 0x6c: asus_map_key_clear(KEY_SLEEP);		break;
> -		case 0x7c: asus_map_key_clear(KEY_MICMUTE);		break;
> +		case 0x7c: asus_map_key_clear(KEY_F20);		break;
>  		case 0x82: asus_map_key_clear(KEY_CAMERA);		break;
>  		case 0x88: asus_map_key_clear(KEY_RFKILL);			break;
>  		case 0xb5: asus_map_key_clear(KEY_CALC);			break;
> @@ -771,16 +880,42 @@ static int asus_input_mapping(struct hid_device 
> *hdev,
>  		/* ROG key */
>  		case 0x38: asus_map_key_clear(KEY_PROG1);		break;
> 
> -		/* Fn+C ASUS Splendid */
> -		case 0xba: asus_map_key_clear(KEY_PROG2);		break;
> -
> -		/* Fn+Space Power4Gear Hybrid */
> -		case 0x5c: asus_map_key_clear(KEY_PROG3);		break;
> -
> -		/* Fn+F5 "fan" symbol on FX503VD */
> -		case 0x99: asus_map_key_clear(KEY_PROG4);		break;
> -
> +		/* The keyboard ID's in this section don't share key events */
>  		default:
> +			if (drvdata->quirks & QUIRK_G752_KEYBOARD) {
> +				switch (usage->hid & HID_USAGE) {
> +				/* Fn+C ASUS Splendid */
> +				case 0xba: asus_map_key_clear(KEY_PROG2);		break;
> +
> +				/* Fn+Space Power4Gear Hybrid */
> +				case 0x5c: asus_map_key_clear(KEY_PROG3);		break;
> +
> +				/* Fn+F5 "fan" symbol on FX503VD */
> +				case 0x99: asus_map_key_clear(KEY_PROG4);		break;
> +
> +				default:
> +					return -1;
> +				}
> +				break;
> +			}
> +			/* device 0x1866, N-KEY Device specific */
> +			else if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
> +				switch (usage->hid & HID_USAGE) {
> +				/* Fn+Ret "Calc" symbol on device 0x1866, N-KEY Device */
> +				case 0x92: asus_map_key_clear(KEY_CALC);		break;
> +
> +				/* Fn+Left Aura mode previous */
> +				case 0xb2: asus_map_key_clear(KEY_PROG2);	break;
> +
> +				/* Fn+Right Aura mode next */
> +				case 0xb3: asus_map_key_clear(KEY_PROG3);	break;
> +
> +				default:
> +					return -1;
> +				}
> +				break;
> +			}
> +
>  			/* ASUS lazily declares 256 usages, ignore the rest,
>  			 * as some make the keyboard appear as a pointer device. */
>  			return -1;
> @@ -1126,6 +1261,9 @@ static const struct hid_device_id 
> asus_devices[] = {
>  	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>  		USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD),
>  	  QUIRK_USE_KBD_BACKLIGHT },
> +	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> +	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
> +	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>  	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>  		USB_DEVICE_ID_ASUSTEK_T100TA_KEYBOARD),
>  	  QUIRK_T100_KEYBOARD | QUIRK_NO_CONSUMER_USAGES },
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index 74fc1df6e3c2..ce5ca078b232 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -190,6 +190,7 @@
>  #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1 0x1854
>  #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2 0x1837
>  #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822
> +#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD	0x1866
>  #define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD	0x1869
> 
>  #define USB_VENDOR_ID_ATEN		0x0557
> diff --git a/include/linux/platform_data/x86/asus-wmi.h 
> b/include/linux/platform_data/x86/asus-wmi.h
> index 897b8332a39f..05253cfe786c 100644
> --- a/include/linux/platform_data/x86/asus-wmi.h
> +++ b/include/linux/platform_data/x86/asus-wmi.h
> @@ -27,6 +27,8 @@
>  #define ASUS_WMI_METHODID_INIT		0x54494E49 /* INITialize */
>  #define ASUS_WMI_METHODID_HKEY		0x59454B48 /* Hot KEY ?? */
> 
> +#define ASUS_WMI_METHODID_NOTIF		0x00100021 /* Notify method ?? */
> +
>  #define ASUS_WMI_UNSUPPORTED_METHOD	0xFFFFFFFE
> 
>  /* Wireless */
> --
> 2.26.2
>
Jiri Kosina Oct. 9, 2020, 9:38 a.m. UTC | #2
On Thu, 24 Sep 2020, Luke D Jones wrote:

> The ASUS N-Key keyboard uses the productId of 0x1866 and is used in
> almost all modern ASUS gaming laptops with slight changes to the
> firmware. This patch enables: Fn+key hotkeys, keyboard backlight
> brightness control, and notify asus-wmi to toggle "fan-mode".
> 
> The keyboard has many of the same key outputs as the existing G752
> keyboard including a few extras, and varies a little between laptop
> models. The key-sets have been split and sub-grouped so that there
> will not be conflict between key event codes used.
> 
> An existing key event used across some keyboards for "Mic Toggle"
> has been changed to emit "F20" as this is what all the main
> desktop environments are using.
> 
> Additionally this keyboard requires the LED interface to be
> intitialised before such things as keyboard backlight control work.
> 
> Misc changes in scope: update some hardcoded comparisons to use an
> available define.
> 
> Signed-off-by: Luke D Jones <luke@ljones.dev>

Thanks for the patch. Looks good to me in general, one small nit before 
this can be merged as a whole ...

> ---
>  drivers/hid/hid-asus.c                     | 188 ++++++++++++++++++---
>  drivers/hid/hid-ids.h                      |   1 +
>  include/linux/platform_data/x86/asus-wmi.h |   2 +

... I'd like to get Ack from Andy (CCing) on the addition below to 
asus-wmi.h.

[ ... snip ... ]
> diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
> index 897b8332a39f..05253cfe786c 100644
> --- a/include/linux/platform_data/x86/asus-wmi.h
> +++ b/include/linux/platform_data/x86/asus-wmi.h
> @@ -27,6 +27,8 @@
>  #define ASUS_WMI_METHODID_INIT		0x54494E49 /* INITialize */
>  #define ASUS_WMI_METHODID_HKEY		0x59454B48 /* Hot KEY ?? */
> 
> +#define ASUS_WMI_METHODID_NOTIF		0x00100021 /* Notify method ?? */
> +
>  #define ASUS_WMI_UNSUPPORTED_METHOD	0xFFFFFFFE
> 
>  /* Wireless */

Thanks,
Andy Shevchenko Oct. 9, 2020, 1:14 p.m. UTC | #3
On Fri, Oct 09, 2020 at 11:38:55AM +0200, Jiri Kosina wrote:
> On Thu, 24 Sep 2020, Luke D Jones wrote:
> 
> > The ASUS N-Key keyboard uses the productId of 0x1866 and is used in
> > almost all modern ASUS gaming laptops with slight changes to the
> > firmware. This patch enables: Fn+key hotkeys, keyboard backlight
> > brightness control, and notify asus-wmi to toggle "fan-mode".
> > 
> > The keyboard has many of the same key outputs as the existing G752
> > keyboard including a few extras, and varies a little between laptop
> > models. The key-sets have been split and sub-grouped so that there
> > will not be conflict between key event codes used.
> > 
> > An existing key event used across some keyboards for "Mic Toggle"
> > has been changed to emit "F20" as this is what all the main
> > desktop environments are using.
> > 
> > Additionally this keyboard requires the LED interface to be
> > intitialised before such things as keyboard backlight control work.
> > 
> > Misc changes in scope: update some hardcoded comparisons to use an
> > available define.
> > 
> > Signed-off-by: Luke D Jones <luke@ljones.dev>
> 
> Thanks for the patch. Looks good to me in general, one small nit before 
> this can be merged as a whole ...
> 
> > ---
> >  drivers/hid/hid-asus.c                     | 188 ++++++++++++++++++---
> >  drivers/hid/hid-ids.h                      |   1 +
> >  include/linux/platform_data/x86/asus-wmi.h |   2 +
> 
> ... I'd like to get Ack from Andy (CCing) on the addition below to 
> asus-wmi.h.

There is a new sheriff in town (Hans and Mark).
My personal opinion it is good to go.

Acked-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>

> [ ... snip ... ]
> > diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
> > index 897b8332a39f..05253cfe786c 100644
> > --- a/include/linux/platform_data/x86/asus-wmi.h
> > +++ b/include/linux/platform_data/x86/asus-wmi.h
> > @@ -27,6 +27,8 @@
> >  #define ASUS_WMI_METHODID_INIT		0x54494E49 /* INITialize */
> >  #define ASUS_WMI_METHODID_HKEY		0x59454B48 /* Hot KEY ?? */
> > 
> > +#define ASUS_WMI_METHODID_NOTIF		0x00100021 /* Notify method ?? */
> > +
> >  #define ASUS_WMI_UNSUPPORTED_METHOD	0xFFFFFFFE
> > 
> >  /* Wireless */
Hans de Goede Oct. 9, 2020, 1:37 p.m. UTC | #4
Hi,

On 10/9/20 3:14 PM, Andy Shevchenko wrote:
> On Fri, Oct 09, 2020 at 11:38:55AM +0200, Jiri Kosina wrote:
>> On Thu, 24 Sep 2020, Luke D Jones wrote:
>>
>>> The ASUS N-Key keyboard uses the productId of 0x1866 and is used in
>>> almost all modern ASUS gaming laptops with slight changes to the
>>> firmware. This patch enables: Fn+key hotkeys, keyboard backlight
>>> brightness control, and notify asus-wmi to toggle "fan-mode".
>>>
>>> The keyboard has many of the same key outputs as the existing G752
>>> keyboard including a few extras, and varies a little between laptop
>>> models. The key-sets have been split and sub-grouped so that there
>>> will not be conflict between key event codes used.
>>>
>>> An existing key event used across some keyboards for "Mic Toggle"
>>> has been changed to emit "F20" as this is what all the main
>>> desktop environments are using.
>>>
>>> Additionally this keyboard requires the LED interface to be
>>> intitialised before such things as keyboard backlight control work.
>>>
>>> Misc changes in scope: update some hardcoded comparisons to use an
>>> available define.
>>>
>>> Signed-off-by: Luke D Jones <luke@ljones.dev>
>>
>> Thanks for the patch. Looks good to me in general, one small nit before
>> this can be merged as a whole ...
>>
>>> ---
>>>   drivers/hid/hid-asus.c                     | 188 ++++++++++++++++++---
>>>   drivers/hid/hid-ids.h                      |   1 +
>>>   include/linux/platform_data/x86/asus-wmi.h |   2 +
>>
>> ... I'd like to get Ack from Andy (CCing) on the addition below to
>> asus-wmi.h.
> 
> There is a new sheriff in town (Hans and Mark).
> My personal opinion it is good to go.
> 
> Acked-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>

I'm afraid that a quick review by me has found multiple issues with
this patch. I'm going to take a quick break now, I'll email a
detailed review after that.

Regards,

Hans


> 
>> [ ... snip ... ]
>>> diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
>>> index 897b8332a39f..05253cfe786c 100644
>>> --- a/include/linux/platform_data/x86/asus-wmi.h
>>> +++ b/include/linux/platform_data/x86/asus-wmi.h
>>> @@ -27,6 +27,8 @@
>>>   #define ASUS_WMI_METHODID_INIT		0x54494E49 /* INITialize */
>>>   #define ASUS_WMI_METHODID_HKEY		0x59454B48 /* Hot KEY ?? */
>>>
>>> +#define ASUS_WMI_METHODID_NOTIF		0x00100021 /* Notify method ?? */
>>> +
>>>   #define ASUS_WMI_UNSUPPORTED_METHOD	0xFFFFFFFE
>>>
>>>   /* Wireless */
>
Andy Shevchenko Oct. 9, 2020, 2:03 p.m. UTC | #5
On Fri, Oct 09, 2020 at 03:37:20PM +0200, Hans de Goede wrote:
> On 10/9/20 3:14 PM, Andy Shevchenko wrote:
> > On Fri, Oct 09, 2020 at 11:38:55AM +0200, Jiri Kosina wrote:
> > > On Thu, 24 Sep 2020, Luke D Jones wrote:
> > > 
> > > > The ASUS N-Key keyboard uses the productId of 0x1866 and is used in
> > > > almost all modern ASUS gaming laptops with slight changes to the
> > > > firmware. This patch enables: Fn+key hotkeys, keyboard backlight
> > > > brightness control, and notify asus-wmi to toggle "fan-mode".
> > > > 
> > > > The keyboard has many of the same key outputs as the existing G752
> > > > keyboard including a few extras, and varies a little between laptop
> > > > models. The key-sets have been split and sub-grouped so that there
> > > > will not be conflict between key event codes used.
> > > > 
> > > > An existing key event used across some keyboards for "Mic Toggle"
> > > > has been changed to emit "F20" as this is what all the main
> > > > desktop environments are using.
> > > > 
> > > > Additionally this keyboard requires the LED interface to be
> > > > intitialised before such things as keyboard backlight control work.
> > > > 
> > > > Misc changes in scope: update some hardcoded comparisons to use an
> > > > available define.
> > > > 
> > > > Signed-off-by: Luke D Jones <luke@ljones.dev>
> > > 
> > > Thanks for the patch. Looks good to me in general, one small nit before
> > > this can be merged as a whole ...
> > > 
> > > > ---
> > > >   drivers/hid/hid-asus.c                     | 188 ++++++++++++++++++---
> > > >   drivers/hid/hid-ids.h                      |   1 +
> > > >   include/linux/platform_data/x86/asus-wmi.h |   2 +
> > > 
> > > ... I'd like to get Ack from Andy (CCing) on the addition below to
> > > asus-wmi.h.
> > 
> > There is a new sheriff in town (Hans and Mark).
> > My personal opinion it is good to go.
> > 
> > Acked-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> 
> I'm afraid that a quick review by me has found multiple issues with
> this patch. I'm going to take a quick break now, I'll email a
> detailed review after that.

Thanks Hans.
I forgot to add that I reviewed only asus-wmi.h part. Sorry for the confusion.


> > > > diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
> > > > index 897b8332a39f..05253cfe786c 100644
> > > > --- a/include/linux/platform_data/x86/asus-wmi.h
> > > > +++ b/include/linux/platform_data/x86/asus-wmi.h
> > > > @@ -27,6 +27,8 @@
> > > >   #define ASUS_WMI_METHODID_INIT		0x54494E49 /* INITialize */
> > > >   #define ASUS_WMI_METHODID_HKEY		0x59454B48 /* Hot KEY ?? */
> > > > 
> > > > +#define ASUS_WMI_METHODID_NOTIF		0x00100021 /* Notify method ?? */
> > > > +
> > > >   #define ASUS_WMI_UNSUPPORTED_METHOD	0xFFFFFFFE
> > > > 
> > > >   /* Wireless */
> > 
>
Hans de Goede Oct. 9, 2020, 2:22 p.m. UTC | #6
Hi,

On 9/23/20 11:56 PM, Luke D Jones wrote:
> The ASUS N-Key keyboard uses the productId of 0x1866 and is used in
> almost all modern ASUS gaming laptops with slight changes to the
> firmware. This patch enables: Fn+key hotkeys, keyboard backlight
> brightness control, and notify asus-wmi to toggle "fan-mode".
> 
> The keyboard has many of the same key outputs as the existing G752
> keyboard including a few extras, and varies a little between laptop
> models. The key-sets have been split and sub-grouped so that there
> will not be conflict between key event codes used.
> 
> An existing key event used across some keyboards for "Mic Toggle"
> has been changed to emit "F20" as this is what all the main
> desktop environments are using.
> 
> Additionally this keyboard requires the LED interface to be
> intitialised before such things as keyboard backlight control work.
> 
> Misc changes in scope: update some hardcoded comparisons to use an
> available define.
> 
> Signed-off-by: Luke D Jones <luke@ljones.dev>
> ---
>   drivers/hid/hid-asus.c                     | 188 ++++++++++++++++++---
>   drivers/hid/hid-ids.h                      |   1 +
>   include/linux/platform_data/x86/asus-wmi.h |   2 +
>   3 files changed, 166 insertions(+), 25 deletions(-)
> 
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index c183caf89d49..541c92a7f108 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -26,6 +26,8 @@
>   #include <linux/dmi.h>
>   #include <linux/hid.h>
>   #include <linux/module.h>
> +
> +#include <linux/acpi.h>
>   #include <linux/platform_data/x86/asus-wmi.h>
>   #include <linux/input/mt.h>
>   #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
> @@ -48,6 +50,8 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>   #define INPUT_REPORT_ID 0x5d
>   #define FEATURE_KBD_REPORT_ID 0x5a
>   #define FEATURE_KBD_REPORT_SIZE 16
> +#define FEATURE_KBD_LED_REPORT_ID1 0x5d
> +#define FEATURE_KBD_LED_REPORT_ID2 0x5e
> 
>   #define SUPPORT_KBD_BACKLIGHT BIT(0)
> 
> @@ -80,6 +84,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>   #define QUIRK_T101HA_DOCK		BIT(9)
>   #define QUIRK_T90CHI			BIT(10)
>   #define QUIRK_MEDION_E1239T		BIT(11)
> +#define QUIRK_ROG_NKEY_KEYBOARD		BIT(12)
> 
>   #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
>   						 QUIRK_NO_INIT_REPORTS | \
> @@ -305,10 +310,33 @@ static int asus_e1239t_event(struct asus_drvdata *drvdat, u8 *data, int size)
>   	return 0;
>   }
> 
> +/*
> + * This enables triggering events in asus-wmi
> + */
> +static int asus_wmi_send_event(struct asus_drvdata *drvdat, u8 code)
> +{
> +	int err;
> +	u32 retval;
> +
> +	err = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS,
> +		ASUS_WMI_METHODID_NOTIF, code, &retval);
> +	if (err) {
> +		pr_warn("Failed to notify asus-wmi: %d\n", err);
> +		return err;
> +	}
> +
> +	if (retval != 0) {
> +		pr_warn("Failed to notify asus-wmi (retval): 0x%x\n", retval);
> +		return -EIO;
> +	}

2 remarks here:

1. Why do you not propogate the error returned by asus_wmi_evaluate_method()?
2. When CONFIG_ASUS_WMI is not set asus_wmi_evaluate_method() is a stub which
    always return -ENODEV. I guess that that counts as a broken kernel-config
    if a N-Key kbd is use, so printing an error then is probably fine...


> +
> +	return 0;
> +}
> +
>   static int asus_event(struct hid_device *hdev, struct hid_field *field,
>   		      struct hid_usage *usage, __s32 value)
>   {
> -	if ((usage->hid & HID_USAGE_PAGE) == 0xff310000 &&
> +	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR &&
>   	    (usage->hid & HID_USAGE) != 0x00 &&
>   	    (usage->hid & HID_USAGE) != 0xff && !usage->type) {
>   		hid_warn(hdev, "Unmapped Asus vendor usagepage code 0x%02x\n",
> @@ -321,6 +349,7 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field,
>   static int asus_raw_event(struct hid_device *hdev,
>   		struct hid_report *report, u8 *data, int size)
>   {
> +	int ret;
>   	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
> 
>   	if (drvdata->battery && data[0] == BATTERY_REPORT_ID)
> @@ -332,6 +361,35 @@ static int asus_raw_event(struct hid_device *hdev,
>   	if (drvdata->quirks & QUIRK_MEDION_E1239T)
>   		return asus_e1239t_event(drvdata, data, size);
> 
> +	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
> +		/*
> +		 * Skip these report ID, the device emits a continuous stream associated
> +		 * with the AURA mode it is in which looks like an 'echo'
> +		*/
> +		if (report->id == FEATURE_KBD_LED_REPORT_ID1 ||
> +				report->id == FEATURE_KBD_LED_REPORT_ID2) {
> +			return -1;
> +		/* Additional report filtering */
> +		} else if (report->id == FEATURE_KBD_REPORT_ID) {
> +			/* Fn+F5 "fan" symbol, trigger WMI event to toggle next mode */
> +			if (data[1] == 0xae) {
> +				ret = asus_wmi_send_event(drvdata, 0xae);
> +				if (ret < 0) {
> +					hid_warn(hdev, "Asus failed to trigger fan control event");
> +				}
> +				return -1;
> +			/*
> +			 * G14 and G15 send these codes on some keypresses with no
> +			 * discernable reason for doing so. We'll filter them out to avoid
> +			 * unmapped warning messages later
> +			*/
> +			} else if (data[1] == 0xea || data[1] == 0xec || data[1] == 0x02 ||
> +					data[1] == 0x8a || data[1] == 0x9e) {
> +				return -1;
> +			}
> +		}
> +	}
> +
>   	return 0;
>   }
> 
> @@ -344,7 +402,11 @@ static int asus_kbd_set_report(struct hid_device *hdev, u8 *buf, size_t buf_size
>   	if (!dmabuf)
>   		return -ENOMEM;
> 
> -	ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, dmabuf,
> +	/*
> +	 * The report ID should be set from the incoming buffer due to LED and key
> +	 * interfaces having different pages
> +	*/
> +	ret = hid_hw_raw_request(hdev, buf[0], dmabuf,
>   				 buf_size, HID_FEATURE_REPORT,
>   				 HID_REQ_SET_REPORT);
>   	kfree(dmabuf);
> @@ -397,6 +459,44 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
>   	return ret;
>   }
> 
> +static int asus_kbd_led_init(struct hid_device *hdev)

Maybe put rog_nkey in the function name here? This sounds very generic,
while it actually is rog_nkey specific.

> +{
> +	u8 buf_init_start[] = { FEATURE_KBD_LED_REPORT_ID1, 0xB9 };
> +	u8 buf_init2[] = { FEATURE_KBD_LED_REPORT_ID1, 0x41, 0x53, 0x55, 0x53, 0x20,
> +				0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
> +	u8 buf_init3[] = { FEATURE_KBD_LED_REPORT_ID1,
> +						0x05, 0x20, 0x31, 0x00, 0x08 };
> +	int ret;
> +
> +	hid_warn(hdev, "Asus initialise N-KEY Device");
> +	/* The first message is an init start */
> +	ret = asus_kbd_set_report(hdev, buf_init_start, sizeof(buf_init_start));
> +	if (ret < 0)
> +		hid_err(hdev, "Asus failed to send init start command: %d\n", ret);
> +	/* Followed by a string */
> +	ret = asus_kbd_set_report(hdev, buf_init2, sizeof(buf_init2));
> +	if (ret < 0)
> +		hid_err(hdev, "Asus failed to send init command 1.0: %d\n", ret);
> +	/* Followed by a string */
> +	ret = asus_kbd_set_report(hdev, buf_init3, sizeof(buf_init3));
> +	if (ret < 0)
> +		hid_err(hdev, "Asus failed to send init command 1.1: %d\n", ret);
> +
> +	/* begin second report ID with same data */
> +	buf_init2[0] = FEATURE_KBD_LED_REPORT_ID2;
> +	buf_init3[0] = FEATURE_KBD_LED_REPORT_ID2;
> +
> +	ret = asus_kbd_set_report(hdev, buf_init2, sizeof(buf_init2));
> +	if (ret < 0)
> +		hid_err(hdev, "Asus failed to send init command 2.0: %d\n", ret);
> +
> +	ret = asus_kbd_set_report(hdev, buf_init3, sizeof(buf_init3));
> +	if (ret < 0)
> +		hid_err(hdev, "Asus failed to send init command 2.1: %d\n", ret);
> +
> +	return ret;
> +}
> +
>   static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
>   				   enum led_brightness brightness)
>   {
> @@ -460,19 +560,28 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
>   	unsigned char kbd_func;
>   	int ret;
> 
> -	/* Initialize keyboard */
> -	ret = asus_kbd_init(hdev);
> -	if (ret < 0)
> -		return ret;
> +	if (drvdata->quirks & QUIRK_G752_KEYBOARD) {
> +		/* Initialize keyboard */
> +		ret = asus_kbd_init(hdev);
> +		if (ret < 0)
> +			return ret;

Why are you all of a sudden making the existing code-path
depend on the G752_KEYBOARD quirk?

In the current hid-asus.c code asus_kbd_register_leds()
gets called if drvdata->enable_backlight is set, which
gets set on the interface with the HID_UP_ASUSVENDOR
usage-page when the USE_KBD_BACKLIGHT quirk is set.

That quirk currently is set on the
ASUSTEK_ROG_KEYBOARD1, ASUSTEK_ROG_KEYBOARD2 and
ASUSTEK_FX503VD_KEYBOARD

And you even set it on the new id-table entry
you add (which makes sense because otherwise
this entire function would not get called for the
N-keys kbd at all).

Notice that the G752_KEYBOARD is missing from the list
of usb-ids for which we set USE_KBD_BACKLIGHT so by making
the old code-path check for that you are essentially
breaking kbd-backlight support on all the models where
it currently already works!

Instead please do:

	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
		/* your new stuff */
	} else {
		/* old code path */
	}

So that you do not change behavior for existing kbds.

> -	/* Get keyboard functions */
> -	ret = asus_kbd_get_functions(hdev, &kbd_func);
> -	if (ret < 0)
> -		return ret;
> +		/* Get keyboard functions */
> +		ret = asus_kbd_get_functions(hdev, &kbd_func);
> +		if (ret < 0)
> +			return ret;
> 
> -	/* Check for backlight support */
> -	if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
> -		return -ENODEV;
> +		/* Check for backlight support */
> +		if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
> +			return -ENODEV;
> +	}
> +
> +	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
> +		/* Initialize keyboard LED interface and Vendor keys on 0x1866 */
> +		ret = asus_kbd_led_init(hdev);
> +		if (ret < 0)
> +			return ret;
> +	}
> 
>   	drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
>   					      sizeof(struct asus_kbd_leds),
> @@ -751,14 +860,14 @@ static int asus_input_mapping(struct hid_device *hdev,
>   	     usage->hid == (HID_UP_GENDEVCTRLS | 0x0026)))
>   		return -1;
> 
> -	/* ASUS-specific keyboard hotkeys */
> -	if ((usage->hid & HID_USAGE_PAGE) == 0xff310000) {
> +	/* ASUS-specific keyboard hotkeys and led backlight */
> +	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR) {
>   		switch (usage->hid & HID_USAGE) {
>   		case 0x10: asus_map_key_clear(KEY_BRIGHTNESSDOWN);	break;
>   		case 0x20: asus_map_key_clear(KEY_BRIGHTNESSUP);		break;
>   		case 0x35: asus_map_key_clear(KEY_DISPLAY_OFF);		break;
>   		case 0x6c: asus_map_key_clear(KEY_SLEEP);		break;
> -		case 0x7c: asus_map_key_clear(KEY_MICMUTE);		break;
> +		case 0x7c: asus_map_key_clear(KEY_F20);		break;
>   		case 0x82: asus_map_key_clear(KEY_CAMERA);		break;
>   		case 0x88: asus_map_key_clear(KEY_RFKILL);			break;
>   		case 0xb5: asus_map_key_clear(KEY_CALC);			break;
> @@ -771,16 +880,42 @@ static int asus_input_mapping(struct hid_device *hdev,
>   		/* ROG key */
>   		case 0x38: asus_map_key_clear(KEY_PROG1);		break;
> 
> -		/* Fn+C ASUS Splendid */
> -		case 0xba: asus_map_key_clear(KEY_PROG2);		break;
> -
> -		/* Fn+Space Power4Gear Hybrid */
> -		case 0x5c: asus_map_key_clear(KEY_PROG3);		break;
> -
> -		/* Fn+F5 "fan" symbol on FX503VD */
> -		case 0x99: asus_map_key_clear(KEY_PROG4);		break;
> -
> +		/* The keyboard ID's in this section don't share key events */
>   		default:
> +			if (drvdata->quirks & QUIRK_G752_KEYBOARD) {
> +				switch (usage->hid & HID_USAGE) {
> +				/* Fn+C ASUS Splendid */
> +				case 0xba: asus_map_key_clear(KEY_PROG2);		break;
> +
> +				/* Fn+Space Power4Gear Hybrid */
> +				case 0x5c: asus_map_key_clear(KEY_PROG3);		break;
> +
> +				/* Fn+F5 "fan" symbol on FX503VD */
> +				case 0x99: asus_map_key_clear(KEY_PROG4);		break;
> +
> +				default:
> +					return -1;
> +				}
> +				break;
> +			}

Same thing again, who says that these 3 usage-ids in the asus-page are only
found on the QUIRK_G752_KEYBOARD ?

Also I see no conflict between these 3 usage ids and the 3 new ones which you add,
so why not just add those 3 new ones to the normal switch-case and be done with
it, without any quirk checking at all ?

That has worked well for all previous Asus kbd models ...

> +			/* device 0x1866, N-KEY Device specific */
> +			else if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
> +				switch (usage->hid & HID_USAGE) {
> +				/* Fn+Ret "Calc" symbol on device 0x1866, N-KEY Device */
> +				case 0x92: asus_map_key_clear(KEY_CALC);		break;
> +
> +				/* Fn+Left Aura mode previous */
> +				case 0xb2: asus_map_key_clear(KEY_PROG2);	break;
> +
> +				/* Fn+Right Aura mode next */
> +				case 0xb3: asus_map_key_clear(KEY_PROG3);	break;
> +
> +				default:
> +					return -1;
> +				}
> +				break;

If you drop the default: return -1 and the break after the switch-case here, then
you will just fall through to the return -1 below, which has a nice comment why
this is done.

Note as said please just add the 3 new usage-ids directly to the main switch case instead
of checking for a quirk and having a nested switch-case just for the N-key kbd, then
this all goes away and my remark here can be ignored.

> +			}
> +
>   			/* ASUS lazily declares 256 usages, ignore the rest,
>   			 * as some make the keyboard appear as a pointer device. */
>   			return -1;
> @@ -1126,6 +1261,9 @@ static const struct hid_device_id asus_devices[] = {
>   	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>   		USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD),
>   	  QUIRK_USE_KBD_BACKLIGHT },
> +	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> +	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
> +	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>   	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>   		USB_DEVICE_ID_ASUSTEK_T100TA_KEYBOARD),
>   	  QUIRK_T100_KEYBOARD | QUIRK_NO_CONSUMER_USAGES },
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index 74fc1df6e3c2..ce5ca078b232 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -190,6 +190,7 @@
>   #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1 0x1854
>   #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2 0x1837
>   #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822
> +#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD	0x1866
>   #define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD	0x1869
> 
>   #define USB_VENDOR_ID_ATEN		0x0557
> diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
> index 897b8332a39f..05253cfe786c 100644
> --- a/include/linux/platform_data/x86/asus-wmi.h
> +++ b/include/linux/platform_data/x86/asus-wmi.h
> @@ -27,6 +27,8 @@
>   #define ASUS_WMI_METHODID_INIT		0x54494E49 /* INITialize */
>   #define ASUS_WMI_METHODID_HKEY		0x59454B48 /* Hot KEY ?? */
> 
> +#define ASUS_WMI_METHODID_NOTIF		0x00100021 /* Notify method ?? */
> +
>   #define ASUS_WMI_UNSUPPORTED_METHOD	0xFFFFFFFE
> 
>   /* Wireless */

This new define is passed as second argument to the
asus_wmi_evaluate_method call, that means that it is not
a METHODID but a DEVID and we already have a bunch of
0x0010002x  DEVID defines:

/* Input */
#define ASUS_WMI_DEVID_TOUCHPAD         0x00100011
#define ASUS_WMI_DEVID_TOUCHPAD_LED     0x00100012
#define ASUS_WMI_DEVID_FNLOCK           0x00100023

So I suggest that you add a new:

#define ASUS_WMI_DEVID_FAN_MODE         0x00100021

define for this in this list instead.

Regards,

Hans
Luke D. Jones Oct. 11, 2020, 7:05 a.m. UTC | #7
Hello Hans and others, thank you for your valuable feedback

On Fri, Oct 9, 2020 at 16:22, Hans de Goede <hdegoede@redhat.com> wrote:
> Hi,
> 
> On 9/23/20 11:56 PM, Luke D Jones wrote:
>> The ASUS N-Key keyboard uses the productId of 0x1866 and is used in
>> almost all modern ASUS gaming laptops with slight changes to the
>> firmware. This patch enables: Fn+key hotkeys, keyboard backlight
>> brightness control, and notify asus-wmi to toggle "fan-mode".
>> 
>> The keyboard has many of the same key outputs as the existing G752
>> keyboard including a few extras, and varies a little between laptop
>> models. The key-sets have been split and sub-grouped so that there
>> will not be conflict between key event codes used.
>> 
>> An existing key event used across some keyboards for "Mic Toggle"
>> has been changed to emit "F20" as this is what all the main
>> desktop environments are using.
>> 
>> Additionally this keyboard requires the LED interface to be
>> intitialised before such things as keyboard backlight control work.
>> 
>> Misc changes in scope: update some hardcoded comparisons to use an
>> available define.
>> 
>> Signed-off-by: Luke D Jones <luke@ljones.dev>
>> ---
>>   drivers/hid/hid-asus.c                     | 188 
>> ++++++++++++++++++---
>>   drivers/hid/hid-ids.h                      |   1 +
>>   include/linux/platform_data/x86/asus-wmi.h |   2 +
>>   3 files changed, 166 insertions(+), 25 deletions(-)
>> 
>> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
>> index c183caf89d49..541c92a7f108 100644
>> --- a/drivers/hid/hid-asus.c
>> +++ b/drivers/hid/hid-asus.c
>> @@ -26,6 +26,8 @@
>>   #include <linux/dmi.h>
>>   #include <linux/hid.h>
>>   #include <linux/module.h>
>> +
>> +#include <linux/acpi.h>
>>   #include <linux/platform_data/x86/asus-wmi.h>
>>   #include <linux/input/mt.h>
>>   #include <linux/usb.h> /* For to_usb_interface for T100 touchpad 
>> intf check */
>> @@ -48,6 +50,8 @@ MODULE_DESCRIPTION("Asus HID Keyboard and 
>> TouchPad");
>>   #define INPUT_REPORT_ID 0x5d
>>   #define FEATURE_KBD_REPORT_ID 0x5a
>>   #define FEATURE_KBD_REPORT_SIZE 16
>> +#define FEATURE_KBD_LED_REPORT_ID1 0x5d
>> +#define FEATURE_KBD_LED_REPORT_ID2 0x5e
>> 
>>   #define SUPPORT_KBD_BACKLIGHT BIT(0)
>> 
>> @@ -80,6 +84,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and 
>> TouchPad");
>>   #define QUIRK_T101HA_DOCK		BIT(9)
>>   #define QUIRK_T90CHI			BIT(10)
>>   #define QUIRK_MEDION_E1239T		BIT(11)
>> +#define QUIRK_ROG_NKEY_KEYBOARD		BIT(12)
>> 
>>   #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
>>   						 QUIRK_NO_INIT_REPORTS | \
>> @@ -305,10 +310,33 @@ static int asus_e1239t_event(struct 
>> asus_drvdata *drvdat, u8 *data, int size)
>>   	return 0;
>>   }
>> 
>> +/*
>> + * This enables triggering events in asus-wmi
>> + */
>> +static int asus_wmi_send_event(struct asus_drvdata *drvdat, u8 code)
>> +{
>> +	int err;
>> +	u32 retval;
>> +
>> +	err = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS,
>> +		ASUS_WMI_METHODID_NOTIF, code, &retval);
>> +	if (err) {
>> +		pr_warn("Failed to notify asus-wmi: %d\n", err);
>> +		return err;
>> +	}
>> +
>> +	if (retval != 0) {
>> +		pr_warn("Failed to notify asus-wmi (retval): 0x%x\n", retval);
>> +		return -EIO;
>> +	}
> 
> 2 remarks here:
> 
> 1. Why do you not propogate the error returned by 
> asus_wmi_evaluate_method()?
> 2. When CONFIG_ASUS_WMI is not set asus_wmi_evaluate_method() is a 
> stub which
>    always return -ENODEV. I guess that that counts as a broken 
> kernel-config
>    if a N-Key kbd is use, so printing an error then is probably 
> fine...

I'm sorry, I don't really understand what you mean here. C isn't a 
language I
spend a lot of time in, and the kernel is fairly new to me. Can you 
suggest what
I should do?

> 
> 
>> +
>> +	return 0;
>> +}
>> +
>>   static int asus_event(struct hid_device *hdev, struct hid_field 
>> *field,
>>   		      struct hid_usage *usage, __s32 value)
>>   {
>> -	if ((usage->hid & HID_USAGE_PAGE) == 0xff310000 &&
>> +	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR &&
>>   	    (usage->hid & HID_USAGE) != 0x00 &&
>>   	    (usage->hid & HID_USAGE) != 0xff && !usage->type) {
>>   		hid_warn(hdev, "Unmapped Asus vendor usagepage code 0x%02x\n",
>> @@ -321,6 +349,7 @@ static int asus_event(struct hid_device *hdev, 
>> struct hid_field *field,
>>   static int asus_raw_event(struct hid_device *hdev,
>>   		struct hid_report *report, u8 *data, int size)
>>   {
>> +	int ret;
>>   	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
>> 
>>   	if (drvdata->battery && data[0] == BATTERY_REPORT_ID)
>> @@ -332,6 +361,35 @@ static int asus_raw_event(struct hid_device 
>> *hdev,
>>   	if (drvdata->quirks & QUIRK_MEDION_E1239T)
>>   		return asus_e1239t_event(drvdata, data, size);
>> 
>> +	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
>> +		/*
>> +		 * Skip these report ID, the device emits a continuous stream 
>> associated
>> +		 * with the AURA mode it is in which looks like an 'echo'
>> +		*/
>> +		if (report->id == FEATURE_KBD_LED_REPORT_ID1 ||
>> +				report->id == FEATURE_KBD_LED_REPORT_ID2) {
>> +			return -1;
>> +		/* Additional report filtering */
>> +		} else if (report->id == FEATURE_KBD_REPORT_ID) {
>> +			/* Fn+F5 "fan" symbol, trigger WMI event to toggle next mode */
>> +			if (data[1] == 0xae) {
>> +				ret = asus_wmi_send_event(drvdata, 0xae);
>> +				if (ret < 0) {
>> +					hid_warn(hdev, "Asus failed to trigger fan control event");
>> +				}
>> +				return -1;
>> +			/*
>> +			 * G14 and G15 send these codes on some keypresses with no
>> +			 * discernable reason for doing so. We'll filter them out to 
>> avoid
>> +			 * unmapped warning messages later
>> +			*/
>> +			} else if (data[1] == 0xea || data[1] == 0xec || data[1] == 0x02 
>> ||
>> +					data[1] == 0x8a || data[1] == 0x9e) {
>> +				return -1;
>> +			}
>> +		}
>> +	}
>> +
>>   	return 0;
>>   }
>> 
>> @@ -344,7 +402,11 @@ static int asus_kbd_set_report(struct 
>> hid_device *hdev, u8 *buf, size_t buf_size
>>   	if (!dmabuf)
>>   		return -ENOMEM;
>> 
>> -	ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, dmabuf,
>> +	/*
>> +	 * The report ID should be set from the incoming buffer due to LED 
>> and key
>> +	 * interfaces having different pages
>> +	*/
>> +	ret = hid_hw_raw_request(hdev, buf[0], dmabuf,
>>   				 buf_size, HID_FEATURE_REPORT,
>>   				 HID_REQ_SET_REPORT);
>>   	kfree(dmabuf);
>> @@ -397,6 +459,44 @@ static int asus_kbd_get_functions(struct 
>> hid_device *hdev,
>>   	return ret;
>>   }
>> 
>> +static int asus_kbd_led_init(struct hid_device *hdev)
> 
> Maybe put rog_nkey in the function name here? This sounds very 
> generic,
> while it actually is rog_nkey specific.

Done.

> 
>> +{
>> +	u8 buf_init_start[] = { FEATURE_KBD_LED_REPORT_ID1, 0xB9 };
>> +	u8 buf_init2[] = { FEATURE_KBD_LED_REPORT_ID1, 0x41, 0x53, 0x55, 
>> 0x53, 0x20,
>> +				0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
>> +	u8 buf_init3[] = { FEATURE_KBD_LED_REPORT_ID1,
>> +						0x05, 0x20, 0x31, 0x00, 0x08 };
>> +	int ret;
>> +
>> +	hid_warn(hdev, "Asus initialise N-KEY Device");
>> +	/* The first message is an init start */
>> +	ret = asus_kbd_set_report(hdev, buf_init_start, 
>> sizeof(buf_init_start));
>> +	if (ret < 0)
>> +		hid_err(hdev, "Asus failed to send init start command: %d\n", 
>> ret);
>> +	/* Followed by a string */
>> +	ret = asus_kbd_set_report(hdev, buf_init2, sizeof(buf_init2));
>> +	if (ret < 0)
>> +		hid_err(hdev, "Asus failed to send init command 1.0: %d\n", ret);
>> +	/* Followed by a string */
>> +	ret = asus_kbd_set_report(hdev, buf_init3, sizeof(buf_init3));
>> +	if (ret < 0)
>> +		hid_err(hdev, "Asus failed to send init command 1.1: %d\n", ret);
>> +
>> +	/* begin second report ID with same data */
>> +	buf_init2[0] = FEATURE_KBD_LED_REPORT_ID2;
>> +	buf_init3[0] = FEATURE_KBD_LED_REPORT_ID2;
>> +
>> +	ret = asus_kbd_set_report(hdev, buf_init2, sizeof(buf_init2));
>> +	if (ret < 0)
>> +		hid_err(hdev, "Asus failed to send init command 2.0: %d\n", ret);
>> +
>> +	ret = asus_kbd_set_report(hdev, buf_init3, sizeof(buf_init3));
>> +	if (ret < 0)
>> +		hid_err(hdev, "Asus failed to send init command 2.1: %d\n", ret);
>> +
>> +	return ret;
>> +}
>> +
>>   static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
>>   				   enum led_brightness brightness)
>>   {
>> @@ -460,19 +560,28 @@ static int asus_kbd_register_leds(struct 
>> hid_device *hdev)
>>   	unsigned char kbd_func;
>>   	int ret;
>> 
>> -	/* Initialize keyboard */
>> -	ret = asus_kbd_init(hdev);
>> -	if (ret < 0)
>> -		return ret;
>> +	if (drvdata->quirks & QUIRK_G752_KEYBOARD) {
>> +		/* Initialize keyboard */
>> +		ret = asus_kbd_init(hdev);
>> +		if (ret < 0)
>> +			return ret;
> 
> Why are you all of a sudden making the existing code-path
> depend on the G752_KEYBOARD quirk?
> 
> In the current hid-asus.c code asus_kbd_register_leds()
> gets called if drvdata->enable_backlight is set, which
> gets set on the interface with the HID_UP_ASUSVENDOR
> usage-page when the USE_KBD_BACKLIGHT quirk is set.
> 
> That quirk currently is set on the
> ASUSTEK_ROG_KEYBOARD1, ASUSTEK_ROG_KEYBOARD2 and
> ASUSTEK_FX503VD_KEYBOARD
> 
> And you even set it on the new id-table entry
> you add (which makes sense because otherwise
> this entire function would not get called for the
> N-keys kbd at all).
> 
> Notice that the G752_KEYBOARD is missing from the list
> of usb-ids for which we set USE_KBD_BACKLIGHT so by making
> the old code-path check for that you are essentially
> breaking kbd-backlight support on all the models where
> it currently already works!
> 
> Instead please do:
> 
> 	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
> 		/* your new stuff */
> 	} else {
> 		/* old code path */
> 	}
> 
> So that you do not change behavior for existing kbds.

Done. Very sorry, I should have caught this. It looks like a remnant
of my initial try and I never cleaned up. In either case, definitely
changed now.

> 
>> -	/* Get keyboard functions */
>> -	ret = asus_kbd_get_functions(hdev, &kbd_func);
>> -	if (ret < 0)
>> -		return ret;
>> +		/* Get keyboard functions */
>> +		ret = asus_kbd_get_functions(hdev, &kbd_func);
>> +		if (ret < 0)
>> +			return ret;
>> 
>> -	/* Check for backlight support */
>> -	if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
>> -		return -ENODEV;
>> +		/* Check for backlight support */
>> +		if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
>> +			return -ENODEV;
>> +	}
>> +
>> +	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
>> +		/* Initialize keyboard LED interface and Vendor keys on 0x1866 */
>> +		ret = asus_kbd_led_init(hdev);
>> +		if (ret < 0)
>> +			return ret;
>> +	}
>> 
>>   	drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
>>   					      sizeof(struct asus_kbd_leds),
>> @@ -751,14 +860,14 @@ static int asus_input_mapping(struct 
>> hid_device *hdev,
>>   	     usage->hid == (HID_UP_GENDEVCTRLS | 0x0026)))
>>   		return -1;
>> 
>> -	/* ASUS-specific keyboard hotkeys */
>> -	if ((usage->hid & HID_USAGE_PAGE) == 0xff310000) {
>> +	/* ASUS-specific keyboard hotkeys and led backlight */
>> +	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR) {
>>   		switch (usage->hid & HID_USAGE) {
>>   		case 0x10: asus_map_key_clear(KEY_BRIGHTNESSDOWN);	break;
>>   		case 0x20: asus_map_key_clear(KEY_BRIGHTNESSUP);		break;
>>   		case 0x35: asus_map_key_clear(KEY_DISPLAY_OFF);		break;
>>   		case 0x6c: asus_map_key_clear(KEY_SLEEP);		break;
>> -		case 0x7c: asus_map_key_clear(KEY_MICMUTE);		break;
>> +		case 0x7c: asus_map_key_clear(KEY_F20);		break;
>>   		case 0x82: asus_map_key_clear(KEY_CAMERA);		break;
>>   		case 0x88: asus_map_key_clear(KEY_RFKILL);			break;
>>   		case 0xb5: asus_map_key_clear(KEY_CALC);			break;
>> @@ -771,16 +880,42 @@ static int asus_input_mapping(struct 
>> hid_device *hdev,
>>   		/* ROG key */
>>   		case 0x38: asus_map_key_clear(KEY_PROG1);		break;
>> 
>> -		/* Fn+C ASUS Splendid */
>> -		case 0xba: asus_map_key_clear(KEY_PROG2);		break;
>> -
>> -		/* Fn+Space Power4Gear Hybrid */
>> -		case 0x5c: asus_map_key_clear(KEY_PROG3);		break;
>> -
>> -		/* Fn+F5 "fan" symbol on FX503VD */
>> -		case 0x99: asus_map_key_clear(KEY_PROG4);		break;
>> -
>> +		/* The keyboard ID's in this section don't share key events */
>>   		default:
>> +			if (drvdata->quirks & QUIRK_G752_KEYBOARD) {
>> +				switch (usage->hid & HID_USAGE) {
>> +				/* Fn+C ASUS Splendid */
>> +				case 0xba: asus_map_key_clear(KEY_PROG2);		break;
>> +
>> +				/* Fn+Space Power4Gear Hybrid */
>> +				case 0x5c: asus_map_key_clear(KEY_PROG3);		break;
>> +
>> +				/* Fn+F5 "fan" symbol on FX503VD */
>> +				case 0x99: asus_map_key_clear(KEY_PROG4);		break;
>> +
>> +				default:
>> +					return -1;
>> +				}
>> +				break;
>> +			}
> 
> Same thing again, who says that these 3 usage-ids in the asus-page 
> are only
> found on the QUIRK_G752_KEYBOARD ?
> 
> Also I see no conflict between these 3 usage ids and the 3 new ones 
> which you add,
> so why not just add those 3 new ones to the normal switch-case and be 
> done with
> it, without any quirk checking at all ?
> 
> That has worked well for all previous Asus kbd models ...

Also done. We've gathered a fair amount of data on ASUS laptops in the 
community
centered around these models now. What you say is true, and so far 
looks like it
also applies even to different product ID (like 0x18**)

> 
>> +			/* device 0x1866, N-KEY Device specific */
>> +			else if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
>> +				switch (usage->hid & HID_USAGE) {
>> +				/* Fn+Ret "Calc" symbol on device 0x1866, N-KEY Device */
>> +				case 0x92: asus_map_key_clear(KEY_CALC);		break;
>> +
>> +				/* Fn+Left Aura mode previous */
>> +				case 0xb2: asus_map_key_clear(KEY_PROG2);	break;
>> +
>> +				/* Fn+Right Aura mode next */
>> +				case 0xb3: asus_map_key_clear(KEY_PROG3);	break;
>> +
>> +				default:
>> +					return -1;
>> +				}
>> +				break;
> 
> If you drop the default: return -1 and the break after the 
> switch-case here, then
> you will just fall through to the return -1 below, which has a nice 
> comment why
> this is done.
> 
> Note as said please just add the 3 new usage-ids directly to the main 
> switch case instead
> of checking for a quirk and having a nested switch-case just for the 
> N-key kbd, then
> this all goes away and my remark here can be ignored.

Definitely done. I think I was trying to avoid conflict or something. 
But yes, nice
and clean now :)

> 
>> +			}
>> +
>>   			/* ASUS lazily declares 256 usages, ignore the rest,
>>   			 * as some make the keyboard appear as a pointer device. */
>>   			return -1;
>> @@ -1126,6 +1261,9 @@ static const struct hid_device_id 
>> asus_devices[] = {
>>   	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>>   		USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD),
>>   	  QUIRK_USE_KBD_BACKLIGHT },
>> +	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>> +	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
>> +	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>>   	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>>   		USB_DEVICE_ID_ASUSTEK_T100TA_KEYBOARD),
>>   	  QUIRK_T100_KEYBOARD | QUIRK_NO_CONSUMER_USAGES },
>> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
>> index 74fc1df6e3c2..ce5ca078b232 100644
>> --- a/drivers/hid/hid-ids.h
>> +++ b/drivers/hid/hid-ids.h
>> @@ -190,6 +190,7 @@
>>   #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1 0x1854
>>   #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2 0x1837
>>   #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822
>> +#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD	0x1866
>>   #define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD	0x1869
>> 
>>   #define USB_VENDOR_ID_ATEN		0x0557
>> diff --git a/include/linux/platform_data/x86/asus-wmi.h 
>> b/include/linux/platform_data/x86/asus-wmi.h
>> index 897b8332a39f..05253cfe786c 100644
>> --- a/include/linux/platform_data/x86/asus-wmi.h
>> +++ b/include/linux/platform_data/x86/asus-wmi.h
>> @@ -27,6 +27,8 @@
>>   #define ASUS_WMI_METHODID_INIT		0x54494E49 /* INITialize */
>>   #define ASUS_WMI_METHODID_HKEY		0x59454B48 /* Hot KEY ?? */
>> 
>> +#define ASUS_WMI_METHODID_NOTIF		0x00100021 /* Notify method ?? */
>> +
>>   #define ASUS_WMI_UNSUPPORTED_METHOD	0xFFFFFFFE
>> 
>>   /* Wireless */
> 
> This new define is passed as second argument to the
> asus_wmi_evaluate_method call, that means that it is not
> a METHODID but a DEVID and we already have a bunch of
> 0x0010002x  DEVID defines:
> 
> /* Input */
> #define ASUS_WMI_DEVID_TOUCHPAD         0x00100011
> #define ASUS_WMI_DEVID_TOUCHPAD_LED     0x00100012
> #define ASUS_WMI_DEVID_FNLOCK           0x00100023
> 
> So I suggest that you add a new:
> 
> #define ASUS_WMI_DEVID_FAN_MODE         0x00100021

This is a tricky one. That ACPI method can actually be used to trigger 
many
different key events, so in the code I've written it is used just for 
triggering
an Fn+F5 event (fan) which is the 0xae code. The USB keyboard emtis the 
same code
but if we use this method then the associated fan control in asus-wmi 
is triggered.

I'm not really up with the terminology on the other interfaces, the TUF 
series use
a keyboard with the exact same controller but connected over PS/2? 
Which enables the
key events to trigger the correct things in asus-wmi. So that block in 
the DSL
is common across a lot of ASUS laptops, and we can actually call the 
method to
/emulate/ the PS/2 keyboard sort of.

The function in drivers/platform/x86/asus-wmi.c triggered by this is:
asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
...
	if (asus->throttle_thermal_policy_available && code == NOTIFY_KBD_TTP) 
{
		throttle_thermal_policy_switch_next(asus);
		return;
	}
...
where NOTIFY_KBD_TTP = 0xae, the same code the USB keyboard emits.

So um, yeah, 0x00100021 can trigger all the keycodes in that block. I 
didn't think
labelling it as ASUS_WMI_DEVID_FAN_MODE was appropriate. I'm not sure 
what else to
name it.

All the DSL we've dumped are here:
https://gitlab.com/asus-linux/reverse-engineering/-/tree/master/dsl

I'm not sure where to go from here. Do I resubmit the revised patch in 
a new thread
or should I attach here? (I'll attach for now)

Kind regards,
Luke.
Hans de Goede Oct. 12, 2020, 9:51 a.m. UTC | #8
Hi,

On 10/11/20 9:05 AM, Luke Jones wrote:
> Hello Hans and others, thank you for your valuable feedback
> 
> On Fri, Oct 9, 2020 at 16:22, Hans de Goede <hdegoede@redhat.com> wrote:
>> Hi,
>>
>> On 9/23/20 11:56 PM, Luke D Jones wrote:
>>> The ASUS N-Key keyboard uses the productId of 0x1866 and is used in
>>> almost all modern ASUS gaming laptops with slight changes to the
>>> firmware. This patch enables: Fn+key hotkeys, keyboard backlight
>>> brightness control, and notify asus-wmi to toggle "fan-mode".
>>>
>>> The keyboard has many of the same key outputs as the existing G752
>>> keyboard including a few extras, and varies a little between laptop
>>> models. The key-sets have been split and sub-grouped so that there
>>> will not be conflict between key event codes used.
>>>
>>> An existing key event used across some keyboards for "Mic Toggle"
>>> has been changed to emit "F20" as this is what all the main
>>> desktop environments are using.
>>>
>>> Additionally this keyboard requires the LED interface to be
>>> intitialised before such things as keyboard backlight control work.
>>>
>>> Misc changes in scope: update some hardcoded comparisons to use an
>>> available define.
>>>
>>> Signed-off-by: Luke D Jones <luke@ljones.dev>
>>> ---
>>>   drivers/hid/hid-asus.c                     | 188 ++++++++++++++++++---
>>>   drivers/hid/hid-ids.h                      |   1 +
>>>   include/linux/platform_data/x86/asus-wmi.h |   2 +
>>>   3 files changed, 166 insertions(+), 25 deletions(-)
>>>
>>> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
>>> index c183caf89d49..541c92a7f108 100644
>>> --- a/drivers/hid/hid-asus.c
>>> +++ b/drivers/hid/hid-asus.c
>>> @@ -26,6 +26,8 @@
>>>   #include <linux/dmi.h>
>>>   #include <linux/hid.h>
>>>   #include <linux/module.h>
>>> +
>>> +#include <linux/acpi.h>
>>>   #include <linux/platform_data/x86/asus-wmi.h>
>>>   #include <linux/input/mt.h>
>>>   #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
>>> @@ -48,6 +50,8 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>>>   #define INPUT_REPORT_ID 0x5d
>>>   #define FEATURE_KBD_REPORT_ID 0x5a
>>>   #define FEATURE_KBD_REPORT_SIZE 16
>>> +#define FEATURE_KBD_LED_REPORT_ID1 0x5d
>>> +#define FEATURE_KBD_LED_REPORT_ID2 0x5e
>>>
>>>   #define SUPPORT_KBD_BACKLIGHT BIT(0)
>>>
>>> @@ -80,6 +84,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>>>   #define QUIRK_T101HA_DOCK        BIT(9)
>>>   #define QUIRK_T90CHI            BIT(10)
>>>   #define QUIRK_MEDION_E1239T        BIT(11)
>>> +#define QUIRK_ROG_NKEY_KEYBOARD        BIT(12)
>>>
>>>   #define I2C_KEYBOARD_QUIRKS            (QUIRK_FIX_NOTEBOOK_REPORT | \
>>>                            QUIRK_NO_INIT_REPORTS | \
>>> @@ -305,10 +310,33 @@ static int asus_e1239t_event(struct asus_drvdata *drvdat, u8 *data, int size)
>>>       return 0;
>>>   }
>>>
>>> +/*
>>> + * This enables triggering events in asus-wmi
>>> + */
>>> +static int asus_wmi_send_event(struct asus_drvdata *drvdat, u8 code)
>>> +{
>>> +    int err;
>>> +    u32 retval;
>>> +
>>> +    err = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS,
>>> +        ASUS_WMI_METHODID_NOTIF, code, &retval);
>>> +    if (err) {
>>> +        pr_warn("Failed to notify asus-wmi: %d\n", err);
>>> +        return err;
>>> +    }
>>> +
>>> +    if (retval != 0) {
>>> +        pr_warn("Failed to notify asus-wmi (retval): 0x%x\n", retval);
>>> +        return -EIO;
>>> +    }
>>
>> 2 remarks here:
>>
>> 1. Why do you not propogate the error returned by asus_wmi_evaluate_method()?
>> 2. When CONFIG_ASUS_WMI is not set asus_wmi_evaluate_method() is a stub which
>>    always return -ENODEV. I guess that that counts as a broken kernel-config
>>    if a N-Key kbd is use, so printing an error then is probably fine...
> 
> I'm sorry, I don't really understand what you mean here. C isn't a language I
> spend a lot of time in, and the kernel is fairly new to me. Can you suggest what
> I should do?

1. My first remark was wrong, I now see that there are 2 return values, the "err"
variable, which you do propogate by "return err" as is. And a WMI specific
retval, for which you return -EIO when it is not 0, which is fine too. The first
time I did not notice that there are actually 2 different values involved, sorry.

2. My second remark was to point out that the if (err) will always be true, and
thus the pr_warn("Failed to notify asus-wmi: %d\n", err); will always happen when
the kernel-config does not define CONFIG_ASUS_WMI. This is fine it was just a remark.

Summary: you do not need to make any changes here :)

>>> +
>>> +    return 0;
>>> +}
>>> +
>>>   static int asus_event(struct hid_device *hdev, struct hid_field *field,
>>>                 struct hid_usage *usage, __s32 value)
>>>   {
>>> -    if ((usage->hid & HID_USAGE_PAGE) == 0xff310000 &&
>>> +    if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR &&
>>>           (usage->hid & HID_USAGE) != 0x00 &&
>>>           (usage->hid & HID_USAGE) != 0xff && !usage->type) {
>>>           hid_warn(hdev, "Unmapped Asus vendor usagepage code 0x%02x\n",
>>> @@ -321,6 +349,7 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field,
>>>   static int asus_raw_event(struct hid_device *hdev,
>>>           struct hid_report *report, u8 *data, int size)
>>>   {
>>> +    int ret;
>>>       struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
>>>
>>>       if (drvdata->battery && data[0] == BATTERY_REPORT_ID)
>>> @@ -332,6 +361,35 @@ static int asus_raw_event(struct hid_device *hdev,
>>>       if (drvdata->quirks & QUIRK_MEDION_E1239T)
>>>           return asus_e1239t_event(drvdata, data, size);
>>>
>>> +    if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
>>> +        /*
>>> +         * Skip these report ID, the device emits a continuous stream associated
>>> +         * with the AURA mode it is in which looks like an 'echo'
>>> +        */
>>> +        if (report->id == FEATURE_KBD_LED_REPORT_ID1 ||
>>> +                report->id == FEATURE_KBD_LED_REPORT_ID2) {
>>> +            return -1;
>>> +        /* Additional report filtering */
>>> +        } else if (report->id == FEATURE_KBD_REPORT_ID) {
>>> +            /* Fn+F5 "fan" symbol, trigger WMI event to toggle next mode */
>>> +            if (data[1] == 0xae) {
>>> +                ret = asus_wmi_send_event(drvdata, 0xae);
>>> +                if (ret < 0) {
>>> +                    hid_warn(hdev, "Asus failed to trigger fan control event");
>>> +                }
>>> +                return -1;
>>> +            /*
>>> +             * G14 and G15 send these codes on some keypresses with no
>>> +             * discernable reason for doing so. We'll filter them out to avoid
>>> +             * unmapped warning messages later
>>> +            */
>>> +            } else if (data[1] == 0xea || data[1] == 0xec || data[1] == 0x02 ||
>>> +                    data[1] == 0x8a || data[1] == 0x9e) {
>>> +                return -1;
>>> +            }
>>> +        }
>>> +    }
>>> +
>>>       return 0;
>>>   }
>>>
>>> @@ -344,7 +402,11 @@ static int asus_kbd_set_report(struct hid_device *hdev, u8 *buf, size_t buf_size
>>>       if (!dmabuf)
>>>           return -ENOMEM;
>>>
>>> -    ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, dmabuf,
>>> +    /*
>>> +     * The report ID should be set from the incoming buffer due to LED and key
>>> +     * interfaces having different pages
>>> +    */
>>> +    ret = hid_hw_raw_request(hdev, buf[0], dmabuf,
>>>                    buf_size, HID_FEATURE_REPORT,
>>>                    HID_REQ_SET_REPORT);
>>>       kfree(dmabuf);
>>> @@ -397,6 +459,44 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
>>>       return ret;
>>>   }
>>>
>>> +static int asus_kbd_led_init(struct hid_device *hdev)
>>
>> Maybe put rog_nkey in the function name here? This sounds very generic,
>> while it actually is rog_nkey specific.
> 
> Done.
> 
>>
>>> +{
>>> +    u8 buf_init_start[] = { FEATURE_KBD_LED_REPORT_ID1, 0xB9 };
>>> +    u8 buf_init2[] = { FEATURE_KBD_LED_REPORT_ID1, 0x41, 0x53, 0x55, 0x53, 0x20,
>>> +                0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
>>> +    u8 buf_init3[] = { FEATURE_KBD_LED_REPORT_ID1,
>>> +                        0x05, 0x20, 0x31, 0x00, 0x08 };
>>> +    int ret;
>>> +
>>> +    hid_warn(hdev, "Asus initialise N-KEY Device");
>>> +    /* The first message is an init start */
>>> +    ret = asus_kbd_set_report(hdev, buf_init_start, sizeof(buf_init_start));
>>> +    if (ret < 0)
>>> +        hid_err(hdev, "Asus failed to send init start command: %d\n", ret);
>>> +    /* Followed by a string */
>>> +    ret = asus_kbd_set_report(hdev, buf_init2, sizeof(buf_init2));
>>> +    if (ret < 0)
>>> +        hid_err(hdev, "Asus failed to send init command 1.0: %d\n", ret);
>>> +    /* Followed by a string */
>>> +    ret = asus_kbd_set_report(hdev, buf_init3, sizeof(buf_init3));
>>> +    if (ret < 0)
>>> +        hid_err(hdev, "Asus failed to send init command 1.1: %d\n", ret);
>>> +
>>> +    /* begin second report ID with same data */
>>> +    buf_init2[0] = FEATURE_KBD_LED_REPORT_ID2;
>>> +    buf_init3[0] = FEATURE_KBD_LED_REPORT_ID2;
>>> +
>>> +    ret = asus_kbd_set_report(hdev, buf_init2, sizeof(buf_init2));
>>> +    if (ret < 0)
>>> +        hid_err(hdev, "Asus failed to send init command 2.0: %d\n", ret);
>>> +
>>> +    ret = asus_kbd_set_report(hdev, buf_init3, sizeof(buf_init3));
>>> +    if (ret < 0)
>>> +        hid_err(hdev, "Asus failed to send init command 2.1: %d\n", ret);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>>   static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
>>>                      enum led_brightness brightness)
>>>   {
>>> @@ -460,19 +560,28 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
>>>       unsigned char kbd_func;
>>>       int ret;
>>>
>>> -    /* Initialize keyboard */
>>> -    ret = asus_kbd_init(hdev);
>>> -    if (ret < 0)
>>> -        return ret;
>>> +    if (drvdata->quirks & QUIRK_G752_KEYBOARD) {
>>> +        /* Initialize keyboard */
>>> +        ret = asus_kbd_init(hdev);
>>> +        if (ret < 0)
>>> +            return ret;
>>
>> Why are you all of a sudden making the existing code-path
>> depend on the G752_KEYBOARD quirk?
>>
>> In the current hid-asus.c code asus_kbd_register_leds()
>> gets called if drvdata->enable_backlight is set, which
>> gets set on the interface with the HID_UP_ASUSVENDOR
>> usage-page when the USE_KBD_BACKLIGHT quirk is set.
>>
>> That quirk currently is set on the
>> ASUSTEK_ROG_KEYBOARD1, ASUSTEK_ROG_KEYBOARD2 and
>> ASUSTEK_FX503VD_KEYBOARD
>>
>> And you even set it on the new id-table entry
>> you add (which makes sense because otherwise
>> this entire function would not get called for the
>> N-keys kbd at all).
>>
>> Notice that the G752_KEYBOARD is missing from the list
>> of usb-ids for which we set USE_KBD_BACKLIGHT so by making
>> the old code-path check for that you are essentially
>> breaking kbd-backlight support on all the models where
>> it currently already works!
>>
>> Instead please do:
>>
>>     if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
>>         /* your new stuff */
>>     } else {
>>         /* old code path */
>>     }
>>
>> So that you do not change behavior for existing kbds.
> 
> Done. Very sorry, I should have caught this. It looks like a remnant
> of my initial try and I never cleaned up. In either case, definitely
> changed now.

Great, this looks good now in the new version which you attached.

One remark I would drop the:

        /*
         * Special case to init the LEDS for N-Key device but otherwise continue
         * on the previous codepath so other devices also init correctly
         */

Comment, that there is a special case for N-key device is obvious from the
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) check, not need to add a comment
for that.

While looking at the new version, this also stood out (which I may just have
missed in the original version) :

+       hid_warn(hdev, "Asus initialise N-KEY Device");

Please use hid_info here.

And this bit:

                 case 0x20: asus_map_key_clear(KEY_BRIGHTNESSUP);                break;
                 case 0x35: asus_map_key_clear(KEY_DISPLAY_OFF);         break;
                 case 0x6c: asus_map_key_clear(KEY_SLEEP);               break;
-               case 0x7c: asus_map_key_clear(KEY_MICMUTE);             break;
+               case 0x7c: asus_map_key_clear(KEY_F20);         break;
                 case 0x82: asus_map_key_clear(KEY_CAMERA);              break;
                 case 0x88: asus_map_key_clear(KEY_RFKILL);                      break;
                 case 0xb5: asus_map_key_clear(KEY_CALC);                        break;

Should really go into a separate commit/patch. I'm not against the chance,
as the use F20 as micmute case works under X11 where as KEY_MICMUTE only
works under Wayland because its key-code is > 248, but this really is
unrelated to the N-Key keyboard, so it should be in its own commit.


>>> -    /* Get keyboard functions */
>>> -    ret = asus_kbd_get_functions(hdev, &kbd_func);
>>> -    if (ret < 0)
>>> -        return ret;
>>> +        /* Get keyboard functions */
>>> +        ret = asus_kbd_get_functions(hdev, &kbd_func);
>>> +        if (ret < 0)
>>> +            return ret;
>>>
>>> -    /* Check for backlight support */
>>> -    if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
>>> -        return -ENODEV;
>>> +        /* Check for backlight support */
>>> +        if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
>>> +            return -ENODEV;
>>> +    }
>>> +
>>> +    if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
>>> +        /* Initialize keyboard LED interface and Vendor keys on 0x1866 */
>>> +        ret = asus_kbd_led_init(hdev);
>>> +        if (ret < 0)
>>> +            return ret;
>>> +    }
>>>
>>>       drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
>>>                             sizeof(struct asus_kbd_leds),
>>> @@ -751,14 +860,14 @@ static int asus_input_mapping(struct hid_device *hdev,
>>>            usage->hid == (HID_UP_GENDEVCTRLS | 0x0026)))
>>>           return -1;
>>>
>>> -    /* ASUS-specific keyboard hotkeys */
>>> -    if ((usage->hid & HID_USAGE_PAGE) == 0xff310000) {
>>> +    /* ASUS-specific keyboard hotkeys and led backlight */
>>> +    if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR) {
>>>           switch (usage->hid & HID_USAGE) {
>>>           case 0x10: asus_map_key_clear(KEY_BRIGHTNESSDOWN);    break;
>>>           case 0x20: asus_map_key_clear(KEY_BRIGHTNESSUP);        break;
>>>           case 0x35: asus_map_key_clear(KEY_DISPLAY_OFF);        break;
>>>           case 0x6c: asus_map_key_clear(KEY_SLEEP);        break;
>>> -        case 0x7c: asus_map_key_clear(KEY_MICMUTE);        break;
>>> +        case 0x7c: asus_map_key_clear(KEY_F20);        break;
>>>           case 0x82: asus_map_key_clear(KEY_CAMERA);        break;
>>>           case 0x88: asus_map_key_clear(KEY_RFKILL);            break;
>>>           case 0xb5: asus_map_key_clear(KEY_CALC);            break;
>>> @@ -771,16 +880,42 @@ static int asus_input_mapping(struct hid_device *hdev,
>>>           /* ROG key */
>>>           case 0x38: asus_map_key_clear(KEY_PROG1);        break;
>>>
>>> -        /* Fn+C ASUS Splendid */
>>> -        case 0xba: asus_map_key_clear(KEY_PROG2);        break;
>>> -
>>> -        /* Fn+Space Power4Gear Hybrid */
>>> -        case 0x5c: asus_map_key_clear(KEY_PROG3);        break;
>>> -
>>> -        /* Fn+F5 "fan" symbol on FX503VD */
>>> -        case 0x99: asus_map_key_clear(KEY_PROG4);        break;
>>> -
>>> +        /* The keyboard ID's in this section don't share key events */
>>>           default:
>>> +            if (drvdata->quirks & QUIRK_G752_KEYBOARD) {
>>> +                switch (usage->hid & HID_USAGE) {
>>> +                /* Fn+C ASUS Splendid */
>>> +                case 0xba: asus_map_key_clear(KEY_PROG2);        break;
>>> +
>>> +                /* Fn+Space Power4Gear Hybrid */
>>> +                case 0x5c: asus_map_key_clear(KEY_PROG3);        break;
>>> +
>>> +                /* Fn+F5 "fan" symbol on FX503VD */
>>> +                case 0x99: asus_map_key_clear(KEY_PROG4);        break;
>>> +
>>> +                default:
>>> +                    return -1;
>>> +                }
>>> +                break;
>>> +            }
>>
>> Same thing again, who says that these 3 usage-ids in the asus-page are only
>> found on the QUIRK_G752_KEYBOARD ?
>>
>> Also I see no conflict between these 3 usage ids and the 3 new ones which you add,
>> so why not just add those 3 new ones to the normal switch-case and be done with
>> it, without any quirk checking at all ?
>>
>> That has worked well for all previous Asus kbd models ...
> 
> Also done. We've gathered a fair amount of data on ASUS laptops in the community
> centered around these models now. What you say is true, and so far looks like it
> also applies even to different product ID (like 0x18**)

Great, looks good now.

> 
>>
>>> +            /* device 0x1866, N-KEY Device specific */
>>> +            else if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
>>> +                switch (usage->hid & HID_USAGE) {
>>> +                /* Fn+Ret "Calc" symbol on device 0x1866, N-KEY Device */
>>> +                case 0x92: asus_map_key_clear(KEY_CALC);        break;
>>> +
>>> +                /* Fn+Left Aura mode previous */
>>> +                case 0xb2: asus_map_key_clear(KEY_PROG2);    break;
>>> +
>>> +                /* Fn+Right Aura mode next */
>>> +                case 0xb3: asus_map_key_clear(KEY_PROG3);    break;
>>> +
>>> +                default:
>>> +                    return -1;
>>> +                }
>>> +                break;
>>
>> If you drop the default: return -1 and the break after the switch-case here, then
>> you will just fall through to the return -1 below, which has a nice comment why
>> this is done.
>>
>> Note as said please just add the 3 new usage-ids directly to the main switch case instead
>> of checking for a quirk and having a nested switch-case just for the N-key kbd, then
>> this all goes away and my remark here can be ignored.
> 
> Definitely done. I think I was trying to avoid conflict or something. But yes, nice
> and clean now :)
> 
>>
>>> +            }
>>> +
>>>               /* ASUS lazily declares 256 usages, ignore the rest,
>>>                * as some make the keyboard appear as a pointer device. */
>>>               return -1;
>>> @@ -1126,6 +1261,9 @@ static const struct hid_device_id asus_devices[] = {
>>>       { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>>>           USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD),
>>>         QUIRK_USE_KBD_BACKLIGHT },
>>> +    { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>>> +        USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
>>> +      QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>>>       { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>>>           USB_DEVICE_ID_ASUSTEK_T100TA_KEYBOARD),
>>>         QUIRK_T100_KEYBOARD | QUIRK_NO_CONSUMER_USAGES },
>>> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
>>> index 74fc1df6e3c2..ce5ca078b232 100644
>>> --- a/drivers/hid/hid-ids.h
>>> +++ b/drivers/hid/hid-ids.h
>>> @@ -190,6 +190,7 @@
>>>   #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1 0x1854
>>>   #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2 0x1837
>>>   #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822
>>> +#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD    0x1866
>>>   #define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD    0x1869
>>>
>>>   #define USB_VENDOR_ID_ATEN        0x0557
>>> diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
>>> index 897b8332a39f..05253cfe786c 100644
>>> --- a/include/linux/platform_data/x86/asus-wmi.h
>>> +++ b/include/linux/platform_data/x86/asus-wmi.h
>>> @@ -27,6 +27,8 @@
>>>   #define ASUS_WMI_METHODID_INIT        0x54494E49 /* INITialize */
>>>   #define ASUS_WMI_METHODID_HKEY        0x59454B48 /* Hot KEY ?? */
>>>
>>> +#define ASUS_WMI_METHODID_NOTIF        0x00100021 /* Notify method ?? */
>>> +
>>>   #define ASUS_WMI_UNSUPPORTED_METHOD    0xFFFFFFFE
>>>
>>>   /* Wireless */
>>
>> This new define is passed as second argument to the
>> asus_wmi_evaluate_method call, that means that it is not
>> a METHODID but a DEVID and we already have a bunch of
>> 0x0010002x  DEVID defines:
>>
>> /* Input */
>> #define ASUS_WMI_DEVID_TOUCHPAD         0x00100011
>> #define ASUS_WMI_DEVID_TOUCHPAD_LED     0x00100012
>> #define ASUS_WMI_DEVID_FNLOCK           0x00100023
>>
>> So I suggest that you add a new:
>>
>> #define ASUS_WMI_DEVID_FAN_MODE         0x00100021
> 
> This is a tricky one. That ACPI method can actually be used to trigger many
> different key events, so in the code I've written it is used just for triggering
> an Fn+F5 event (fan) which is the 0xae code. The USB keyboard emtis the same code
> but if we use this method then the associated fan control in asus-wmi is triggered.
> 
> I'm not really up with the terminology on the other interfaces, the TUF series use
> a keyboard with the exact same controller but connected over PS/2? Which enables the
> key events to trigger the correct things in asus-wmi. So that block in the DSL
> is common across a lot of ASUS laptops, and we can actually call the method to
> /emulate/ the PS/2 keyboard sort of.
> 
> The function in drivers/platform/x86/asus-wmi.c triggered by this is:
> asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
> ...
>      if (asus->throttle_thermal_policy_available && code == NOTIFY_KBD_TTP) {
>          throttle_thermal_policy_switch_next(asus);
>          return;
>      }
> ...
> where NOTIFY_KBD_TTP = 0xae, the same code the USB keyboard emits.
> 
> So um, yeah, 0x00100021 can trigger all the keycodes in that block. I didn't think
> labelling it as ASUS_WMI_DEVID_FAN_MODE was appropriate. I'm not sure what else to
> name it.

My main objection was the use of METHODID instead of DEVID, so please name it:
ASUS_WMI_DEVID_NOTIF and add it to the list of existing DEVIDs and then it is fine.

> All the DSL we've dumped are here:
> https://gitlab.com/asus-linux/reverse-engineering/-/tree/master/dsl
> 
> I'm not sure where to go from here. Do I resubmit the revised patch in a new thread
> or should I attach here? (I'll attach for now)

Please submit a new version in a new email thread with [PATCH v2] as prefix to the
subject, you can do this by passing --subject-prefix="PATCH v2" to git format-patch.

Regards,

Hans
Andy Shevchenko Oct. 12, 2020, 10:14 a.m. UTC | #9
On Mon, Oct 12, 2020 at 12:51 PM Hans de Goede <hdegoede@redhat.com> wrote:
> On 10/11/20 9:05 AM, Luke Jones wrote:
> > On Fri, Oct 9, 2020 at 16:22, Hans de Goede <hdegoede@redhat.com> wrote:

...

> > All the DSL we've dumped are here:
> > https://gitlab.com/asus-linux/reverse-engineering/-/tree/master/dsl
> >
> > I'm not sure where to go from here. Do I resubmit the revised patch in a new thread
> > or should I attach here? (I'll attach for now)
>
> Please submit a new version in a new email thread with [PATCH v2] as prefix to the
> subject, you can do this by passing --subject-prefix="PATCH v2" to git format-patch.

-v<N> will do it for you in a shorter way.
diff mbox series

Patch

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index c183caf89d49..541c92a7f108 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -26,6 +26,8 @@ 
 #include <linux/dmi.h>
 #include <linux/hid.h>
 #include <linux/module.h>
+
+#include <linux/acpi.h>
 #include <linux/platform_data/x86/asus-wmi.h>
 #include <linux/input/mt.h>
 #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
@@ -48,6 +50,8 @@  MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
 #define INPUT_REPORT_ID 0x5d
 #define FEATURE_KBD_REPORT_ID 0x5a
 #define FEATURE_KBD_REPORT_SIZE 16
+#define FEATURE_KBD_LED_REPORT_ID1 0x5d
+#define FEATURE_KBD_LED_REPORT_ID2 0x5e

 #define SUPPORT_KBD_BACKLIGHT BIT(0)

@@ -80,6 +84,7 @@  MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
 #define QUIRK_T101HA_DOCK		BIT(9)
 #define QUIRK_T90CHI			BIT(10)
 #define QUIRK_MEDION_E1239T		BIT(11)
+#define QUIRK_ROG_NKEY_KEYBOARD		BIT(12)

 #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
 						 QUIRK_NO_INIT_REPORTS | \
@@ -305,10 +310,33 @@  static int asus_e1239t_event(struct asus_drvdata *drvdat, u8 *data, int size)
 	return 0;
 }

+/*
+ * This enables triggering events in asus-wmi
+ */
+static int asus_wmi_send_event(struct asus_drvdata *drvdat, u8 code)
+{
+	int err;
+	u32 retval;
+
+	err = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS,
+		ASUS_WMI_METHODID_NOTIF, code, &retval);
+	if (err) {
+		pr_warn("Failed to notify asus-wmi: %d\n", err);
+		return err;
+	}
+
+	if (retval != 0) {
+		pr_warn("Failed to notify asus-wmi (retval): 0x%x\n", retval);
+		return -EIO;
+	}
+
+	return 0;
+}
+
 static int asus_event(struct hid_device *hdev, struct hid_field *field,
 		      struct hid_usage *usage, __s32 value)
 {
-	if ((usage->hid & HID_USAGE_PAGE) == 0xff310000 &&
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR &&
 	    (usage->hid & HID_USAGE) != 0x00 &&
 	    (usage->hid & HID_USAGE) != 0xff && !usage->type) {
 		hid_warn(hdev, "Unmapped Asus vendor usagepage code 0x%02x\n",
@@ -321,6 +349,7 @@  static int asus_event(struct hid_device *hdev, struct hid_field *field,
 static int asus_raw_event(struct hid_device *hdev,
 		struct hid_report *report, u8 *data, int size)
 {
+	int ret;
 	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);

 	if (drvdata->battery && data[0] == BATTERY_REPORT_ID)
@@ -332,6 +361,35 @@  static int asus_raw_event(struct hid_device *hdev,
 	if (drvdata->quirks & QUIRK_MEDION_E1239T)
 		return asus_e1239t_event(drvdata, data, size);

+	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
+		/*
+		 * Skip these report ID, the device emits a continuous stream associated
+		 * with the AURA mode it is in which looks like an 'echo'
+		*/
+		if (report->id == FEATURE_KBD_LED_REPORT_ID1 ||
+				report->id == FEATURE_KBD_LED_REPORT_ID2) {
+			return -1;
+		/* Additional report filtering */
+		} else if (report->id == FEATURE_KBD_REPORT_ID) {
+			/* Fn+F5 "fan" symbol, trigger WMI event to toggle next mode */
+			if (data[1] == 0xae) {
+				ret = asus_wmi_send_event(drvdata, 0xae);
+				if (ret < 0) {
+					hid_warn(hdev, "Asus failed to trigger fan control event");
+				}
+				return -1;
+			/*
+			 * G14 and G15 send these codes on some keypresses with no
+			 * discernable reason for doing so. We'll filter them out to avoid
+			 * unmapped warning messages later
+			*/
+			} else if (data[1] == 0xea || data[1] == 0xec || data[1] == 0x02 ||
+					data[1] == 0x8a || data[1] == 0x9e) {
+				return -1;
+			}
+		}
+	}
+
 	return 0;
 }

@@ -344,7 +402,11 @@  static int asus_kbd_set_report(struct hid_device *hdev, u8 *buf, size_t buf_size
 	if (!dmabuf)
 		return -ENOMEM;

-	ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, dmabuf,
+	/*
+	 * The report ID should be set from the incoming buffer due to LED and key
+	 * interfaces having different pages
+	*/
+	ret = hid_hw_raw_request(hdev, buf[0], dmabuf,
 				 buf_size, HID_FEATURE_REPORT,
 				 HID_REQ_SET_REPORT);
 	kfree(dmabuf);
@@ -397,6 +459,44 @@  static int asus_kbd_get_functions(struct hid_device *hdev,
 	return ret;
 }

+static int asus_kbd_led_init(struct hid_device *hdev)
+{
+	u8 buf_init_start[] = { FEATURE_KBD_LED_REPORT_ID1, 0xB9 };
+	u8 buf_init2[] = { FEATURE_KBD_LED_REPORT_ID1, 0x41, 0x53, 0x55, 0x53, 0x20,
+				0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
+	u8 buf_init3[] = { FEATURE_KBD_LED_REPORT_ID1,
+						0x05, 0x20, 0x31, 0x00, 0x08 };
+	int ret;
+
+	hid_warn(hdev, "Asus initialise N-KEY Device");
+	/* The first message is an init start */
+	ret = asus_kbd_set_report(hdev, buf_init_start, sizeof(buf_init_start));
+	if (ret < 0)
+		hid_err(hdev, "Asus failed to send init start command: %d\n", ret);
+	/* Followed by a string */
+	ret = asus_kbd_set_report(hdev, buf_init2, sizeof(buf_init2));
+	if (ret < 0)
+		hid_err(hdev, "Asus failed to send init command 1.0: %d\n", ret);
+	/* Followed by a string */
+	ret = asus_kbd_set_report(hdev, buf_init3, sizeof(buf_init3));
+	if (ret < 0)
+		hid_err(hdev, "Asus failed to send init command 1.1: %d\n", ret);
+
+	/* begin second report ID with same data */
+	buf_init2[0] = FEATURE_KBD_LED_REPORT_ID2;
+	buf_init3[0] = FEATURE_KBD_LED_REPORT_ID2;
+
+	ret = asus_kbd_set_report(hdev, buf_init2, sizeof(buf_init2));
+	if (ret < 0)
+		hid_err(hdev, "Asus failed to send init command 2.0: %d\n", ret);
+
+	ret = asus_kbd_set_report(hdev, buf_init3, sizeof(buf_init3));
+	if (ret < 0)
+		hid_err(hdev, "Asus failed to send init command 2.1: %d\n", ret);
+
+	return ret;
+}
+
 static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
 				   enum led_brightness brightness)
 {
@@ -460,19 +560,28 @@  static int asus_kbd_register_leds(struct hid_device *hdev)
 	unsigned char kbd_func;
 	int ret;

-	/* Initialize keyboard */
-	ret = asus_kbd_init(hdev);
-	if (ret < 0)
-		return ret;
+	if (drvdata->quirks & QUIRK_G752_KEYBOARD) {
+		/* Initialize keyboard */
+		ret = asus_kbd_init(hdev);
+		if (ret < 0)
+			return ret;

-	/* Get keyboard functions */
-	ret = asus_kbd_get_functions(hdev, &kbd_func);
-	if (ret < 0)
-		return ret;
+		/* Get keyboard functions */
+		ret = asus_kbd_get_functions(hdev, &kbd_func);
+		if (ret < 0)
+			return ret;

-	/* Check for backlight support */
-	if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
-		return -ENODEV;
+		/* Check for backlight support */
+		if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
+			return -ENODEV;
+	}
+
+	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
+		/* Initialize keyboard LED interface and Vendor keys on 0x1866 */
+		ret = asus_kbd_led_init(hdev);
+		if (ret < 0)
+			return ret;
+	}

 	drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
 					      sizeof(struct asus_kbd_leds),
@@ -751,14 +860,14 @@  static int asus_input_mapping(struct hid_device *hdev,
 	     usage->hid == (HID_UP_GENDEVCTRLS | 0x0026)))
 		return -1;

-	/* ASUS-specific keyboard hotkeys */
-	if ((usage->hid & HID_USAGE_PAGE) == 0xff310000) {
+	/* ASUS-specific keyboard hotkeys and led backlight */
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR) {
 		switch (usage->hid & HID_USAGE) {
 		case 0x10: asus_map_key_clear(KEY_BRIGHTNESSDOWN);	break;
 		case 0x20: asus_map_key_clear(KEY_BRIGHTNESSUP);		break;
 		case 0x35: asus_map_key_clear(KEY_DISPLAY_OFF);		break;
 		case 0x6c: asus_map_key_clear(KEY_SLEEP);		break;
-		case 0x7c: asus_map_key_clear(KEY_MICMUTE);		break;
+		case 0x7c: asus_map_key_clear(KEY_F20);		break;
 		case 0x82: asus_map_key_clear(KEY_CAMERA);		break;
 		case 0x88: asus_map_key_clear(KEY_RFKILL);			break;
 		case 0xb5: asus_map_key_clear(KEY_CALC);			break;
@@ -771,16 +880,42 @@  static int asus_input_mapping(struct hid_device *hdev,
 		/* ROG key */
 		case 0x38: asus_map_key_clear(KEY_PROG1);		break;

-		/* Fn+C ASUS Splendid */
-		case 0xba: asus_map_key_clear(KEY_PROG2);		break;
-
-		/* Fn+Space Power4Gear Hybrid */
-		case 0x5c: asus_map_key_clear(KEY_PROG3);		break;
-
-		/* Fn+F5 "fan" symbol on FX503VD */
-		case 0x99: asus_map_key_clear(KEY_PROG4);		break;
-
+		/* The keyboard ID's in this section don't share key events */
 		default:
+			if (drvdata->quirks & QUIRK_G752_KEYBOARD) {
+				switch (usage->hid & HID_USAGE) {
+				/* Fn+C ASUS Splendid */
+				case 0xba: asus_map_key_clear(KEY_PROG2);		break;
+
+				/* Fn+Space Power4Gear Hybrid */
+				case 0x5c: asus_map_key_clear(KEY_PROG3);		break;
+
+				/* Fn+F5 "fan" symbol on FX503VD */
+				case 0x99: asus_map_key_clear(KEY_PROG4);		break;
+
+				default:
+					return -1;
+				}
+				break;
+			}
+			/* device 0x1866, N-KEY Device specific */
+			else if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
+				switch (usage->hid & HID_USAGE) {
+				/* Fn+Ret "Calc" symbol on device 0x1866, N-KEY Device */
+				case 0x92: asus_map_key_clear(KEY_CALC);		break;
+
+				/* Fn+Left Aura mode previous */
+				case 0xb2: asus_map_key_clear(KEY_PROG2);	break;
+
+				/* Fn+Right Aura mode next */
+				case 0xb3: asus_map_key_clear(KEY_PROG3);	break;
+
+				default:
+					return -1;
+				}
+				break;
+			}
+
 			/* ASUS lazily declares 256 usages, ignore the rest,
 			 * as some make the keyboard appear as a pointer device. */
 			return -1;
@@ -1126,6 +1261,9 @@  static const struct hid_device_id asus_devices[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 		USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD),
 	  QUIRK_USE_KBD_BACKLIGHT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
+	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
+	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 		USB_DEVICE_ID_ASUSTEK_T100TA_KEYBOARD),
 	  QUIRK_T100_KEYBOARD | QUIRK_NO_CONSUMER_USAGES },
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 74fc1df6e3c2..ce5ca078b232 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -190,6 +190,7 @@ 
 #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1 0x1854
 #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2 0x1837
 #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822
+#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD	0x1866
 #define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD	0x1869

 #define USB_VENDOR_ID_ATEN		0x0557
diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
index 897b8332a39f..05253cfe786c 100644
--- a/include/linux/platform_data/x86/asus-wmi.h
+++ b/include/linux/platform_data/x86/asus-wmi.h
@@ -27,6 +27,8 @@ 
 #define ASUS_WMI_METHODID_INIT		0x54494E49 /* INITialize */
 #define ASUS_WMI_METHODID_HKEY		0x59454B48 /* Hot KEY ?? */

+#define ASUS_WMI_METHODID_NOTIF		0x00100021 /* Notify method ?? */
+
 #define ASUS_WMI_UNSUPPORTED_METHOD	0xFFFFFFFE

 /* Wireless */