From 567b842d2fd4c7e5dffa589f1678f4b2dfaf89b5 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Thu, 22 May 2025 16:19:36 +0200 Subject: [PATCH 3/9] asus Signed-off-by: Peter Jung --- .../ABI/testing/sysfs-platform-asus-wmi | 17 + .../display/dc/dml2/dml2_translation_helper.c | 2 +- drivers/hid/Kconfig | 2 + drivers/hid/Makefile | 2 + drivers/hid/asus-ally-hid/Kconfig | 8 + drivers/hid/asus-ally-hid/Makefile | 6 + .../hid/asus-ally-hid/asus-ally-hid-config.c | 2347 +++++++++++++++++ .../hid/asus-ally-hid/asus-ally-hid-core.c | 600 +++++ .../hid/asus-ally-hid/asus-ally-hid-input.c | 345 +++ drivers/hid/asus-ally-hid/asus-ally-rgb.c | 356 +++ drivers/hid/asus-ally-hid/asus-ally.h | 314 +++ drivers/hid/hid-asus.c | 116 +- drivers/hid/hid-ids.h | 1 + drivers/platform/x86/Kconfig | 23 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/asus-armoury.c | 1202 +++++++++ drivers/platform/x86/asus-armoury.h | 1278 +++++++++ drivers/platform/x86/asus-wmi.c | 359 ++- include/linux/platform_data/x86/asus-wmi.h | 43 + 19 files changed, 6915 insertions(+), 107 deletions(-) create mode 100644 drivers/hid/asus-ally-hid/Kconfig create mode 100644 drivers/hid/asus-ally-hid/Makefile create mode 100644 drivers/hid/asus-ally-hid/asus-ally-hid-config.c create mode 100644 drivers/hid/asus-ally-hid/asus-ally-hid-core.c create mode 100644 drivers/hid/asus-ally-hid/asus-ally-hid-input.c create mode 100644 drivers/hid/asus-ally-hid/asus-ally-rgb.c create mode 100644 drivers/hid/asus-ally-hid/asus-ally.h create mode 100644 drivers/platform/x86/asus-armoury.c create mode 100644 drivers/platform/x86/asus-armoury.h diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi index 28144371a0f1..765d50b0d9df 100644 --- a/Documentation/ABI/testing/sysfs-platform-asus-wmi +++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi @@ -63,6 +63,7 @@ Date: Aug 2022 KernelVersion: 6.1 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON Switch the GPU hardware MUX mode. Laptops with this feature can can be toggled to boot with only the dGPU (discrete mode) or in standard Optimus/Hybrid mode. On switch a reboot is required: @@ -75,6 +76,7 @@ Date: Aug 2022 KernelVersion: 5.17 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON Disable discrete GPU: * 0 - Enable dGPU, * 1 - Disable dGPU @@ -84,6 +86,7 @@ Date: Aug 2022 KernelVersion: 5.17 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON Enable the external GPU paired with ROG X-Flow laptops. Toggling this setting will also trigger ACPI to disable the dGPU: @@ -95,6 +98,7 @@ Date: Aug 2022 KernelVersion: 5.17 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON Enable an LCD response-time boost to reduce or remove ghosting: * 0 - Disable, * 1 - Enable @@ -104,6 +108,7 @@ Date: Jun 2023 KernelVersion: 6.5 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON Get the current charging mode being used: * 1 - Barrel connected charger, * 2 - USB-C charging @@ -114,6 +119,7 @@ Date: Jun 2023 KernelVersion: 6.5 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON Show if the egpu (XG Mobile) is correctly connected: * 0 - False, * 1 - True @@ -123,6 +129,7 @@ Date: Jun 2023 KernelVersion: 6.5 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON Change the mini-LED mode: * 0 - Single-zone, * 1 - Multi-zone @@ -133,6 +140,7 @@ Date: Apr 2024 KernelVersion: 6.10 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON List the available mini-led modes. What: /sys/devices/platform//ppt_pl1_spl @@ -140,6 +148,7 @@ Date: Jun 2023 KernelVersion: 6.5 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON Set the Package Power Target total of CPU: PL1 on Intel, SPL on AMD. Shown on Intel+Nvidia or AMD+Nvidia based systems: @@ -150,6 +159,7 @@ Date: Jun 2023 KernelVersion: 6.5 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON Set the Slow Package Power Tracking Limit of CPU: PL2 on Intel, SPPT, on AMD. Shown on Intel+Nvidia or AMD+Nvidia based systems: @@ -160,6 +170,7 @@ Date: Jun 2023 KernelVersion: 6.5 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON Set the Fast Package Power Tracking Limit of CPU. AMD+Nvidia only: * min=5, max=250 @@ -168,6 +179,7 @@ Date: Jun 2023 KernelVersion: 6.5 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON Set the APU SPPT limit. Shown on full AMD systems only: * min=5, max=130 @@ -176,6 +188,7 @@ Date: Jun 2023 KernelVersion: 6.5 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON Set the platform SPPT limit. Shown on full AMD systems only: * min=5, max=130 @@ -184,6 +197,7 @@ Date: Jun 2023 KernelVersion: 6.5 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON Set the dynamic boost limit of the Nvidia dGPU: * min=5, max=25 @@ -192,6 +206,7 @@ Date: Jun 2023 KernelVersion: 6.5 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON Set the target temperature limit of the Nvidia dGPU: * min=75, max=87 @@ -200,6 +215,7 @@ Date: Apr 2024 KernelVersion: 6.10 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON Set if the BIOS POST sound is played on boot. * 0 - False, * 1 - True @@ -209,6 +225,7 @@ Date: Apr 2024 KernelVersion: 6.10 Contact: "Luke Jones" Description: + DEPRECATED, WILL BE REMOVED SOON Set if the MCU can go in to low-power mode on system sleep * 0 - False, * 1 - True diff --git a/drivers/gpu/drm/amd/display/dc/dml2/dml2_translation_helper.c b/drivers/gpu/drm/amd/display/dc/dml2/dml2_translation_helper.c index aeb9fae83cac..ad16cfc5eaed 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2/dml2_translation_helper.c +++ b/drivers/gpu/drm/amd/display/dc/dml2/dml2_translation_helper.c @@ -892,7 +892,7 @@ static void populate_dummy_dml_surface_cfg(struct dml_surface_cfg_st *out, unsig out->SurfaceWidthC[location] = in->timing.h_addressable; out->SurfaceHeightC[location] = in->timing.v_addressable; out->PitchY[location] = ((out->SurfaceWidthY[location] + 127) / 128) * 128; - out->PitchC[location] = 0; + out->PitchC[location] = 1; out->DCCEnable[location] = false; out->DCCMetaPitchY[location] = 0; out->DCCMetaPitchC[location] = 0; diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 4cfea399ebab..673e57fab594 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1397,6 +1397,8 @@ source "drivers/hid/intel-ish-hid/Kconfig" source "drivers/hid/amd-sfh-hid/Kconfig" +source "drivers/hid/asus-ally-hid/Kconfig" + source "drivers/hid/surface-hid/Kconfig" source "drivers/hid/intel-thc-hid/Kconfig" diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index c7ecfbb3e228..8f8b0e71153d 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -170,6 +170,8 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ +obj-$(CONFIG_ASUS_ALLY_HID) += asus-ally-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ diff --git a/drivers/hid/asus-ally-hid/Kconfig b/drivers/hid/asus-ally-hid/Kconfig new file mode 100644 index 000000000000..f83dda32be62 --- /dev/null +++ b/drivers/hid/asus-ally-hid/Kconfig @@ -0,0 +1,8 @@ +config ASUS_ALLY_HID + tristate "Asus Ally handheld support" + depends on USB_HID + depends on LEDS_CLASS + depends on LEDS_CLASS_MULTICOLOR + select POWER_SUPPLY + help + Support for configuring the Asus ROG Ally gamepad using attributes. diff --git a/drivers/hid/asus-ally-hid/Makefile b/drivers/hid/asus-ally-hid/Makefile new file mode 100644 index 000000000000..5c3c304b7b53 --- /dev/null +++ b/drivers/hid/asus-ally-hid/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Makefile - ASUS ROG Ally handheld device driver +# +asus-ally-hid-y := asus-ally-hid-core.o asus-ally-rgb.o asus-ally-hid-input.o asus-ally-hid-config.o +obj-$(CONFIG_ASUS_ALLY_HID) := asus-ally-hid.o diff --git a/drivers/hid/asus-ally-hid/asus-ally-hid-config.c b/drivers/hid/asus-ally-hid/asus-ally-hid-config.c new file mode 100644 index 000000000000..1624880121c4 --- /dev/null +++ b/drivers/hid/asus-ally-hid/asus-ally-hid-config.c @@ -0,0 +1,2347 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Asus ROG laptops and Ally + * + * Copyright (c) 2023 Luke Jones + */ + +#include +#include +#include +#include +#include +#include + +#include "asus-ally.h" +#include "../hid-ids.h" + +enum btn_map_type { + BTN_TYPE_NONE = 0, + BTN_TYPE_PAD = 0x01, + BTN_TYPE_KB = 0x02, + BTN_TYPE_MOUSE = 0x03, + BTN_TYPE_MEDIA = 0x05, +}; + +struct btn_code_map { + unsigned char type; + unsigned char value; + const char *name; +}; + +static const struct btn_code_map ally_btn_codes[] = { + { BTN_TYPE_NONE, 0x00, "NONE" }, + /* Gamepad button codes */ + { BTN_TYPE_PAD, 0x01, "PAD_A" }, + { BTN_TYPE_PAD, 0x02, "PAD_B" }, + { BTN_TYPE_PAD, 0x03, "PAD_X" }, + { BTN_TYPE_PAD, 0x04, "PAD_Y" }, + { BTN_TYPE_PAD, 0x05, "PAD_LB" }, + { BTN_TYPE_PAD, 0x06, "PAD_RB" }, + { BTN_TYPE_PAD, 0x07, "PAD_LS" }, + { BTN_TYPE_PAD, 0x08, "PAD_RS" }, + { BTN_TYPE_PAD, 0x09, "PAD_DPAD_UP" }, + { BTN_TYPE_PAD, 0x0A, "PAD_DPAD_DOWN" }, + { BTN_TYPE_PAD, 0x0B, "PAD_DPAD_LEFT" }, + { BTN_TYPE_PAD, 0x0C, "PAD_DPAD_RIGHT" }, + { BTN_TYPE_PAD, 0x0D, "PAD_LT" }, + { BTN_TYPE_PAD, 0x0E, "PAD_RT" }, + { BTN_TYPE_PAD, 0x11, "PAD_VIEW" }, + { BTN_TYPE_PAD, 0x12, "PAD_MENU" }, + { BTN_TYPE_PAD, 0x13, "PAD_XBOX" }, + + /* Keyboard button codes */ + { BTN_TYPE_KB, 0x8E, "KB_M2" }, + { BTN_TYPE_KB, 0x8F, "KB_M1" }, + { BTN_TYPE_KB, 0x76, "KB_ESC" }, + { BTN_TYPE_KB, 0x50, "KB_F1" }, + { BTN_TYPE_KB, 0x60, "KB_F2" }, + { BTN_TYPE_KB, 0x40, "KB_F3" }, + { BTN_TYPE_KB, 0x0C, "KB_F4" }, + { BTN_TYPE_KB, 0x03, "KB_F5" }, + { BTN_TYPE_KB, 0x0B, "KB_F6" }, + { BTN_TYPE_KB, 0x80, "KB_F7" }, + { BTN_TYPE_KB, 0x0A, "KB_F8" }, + { BTN_TYPE_KB, 0x01, "KB_F9" }, + { BTN_TYPE_KB, 0x09, "KB_F10" }, + { BTN_TYPE_KB, 0x78, "KB_F11" }, + { BTN_TYPE_KB, 0x07, "KB_F12" }, + { BTN_TYPE_KB, 0x18, "KB_F14" }, + { BTN_TYPE_KB, 0x10, "KB_F15" }, + { BTN_TYPE_KB, 0x0E, "KB_BACKTICK" }, + { BTN_TYPE_KB, 0x16, "KB_1" }, + { BTN_TYPE_KB, 0x1E, "KB_2" }, + { BTN_TYPE_KB, 0x26, "KB_3" }, + { BTN_TYPE_KB, 0x25, "KB_4" }, + { BTN_TYPE_KB, 0x2E, "KB_5" }, + { BTN_TYPE_KB, 0x36, "KB_6" }, + { BTN_TYPE_KB, 0x3D, "KB_7" }, + { BTN_TYPE_KB, 0x3E, "KB_8" }, + { BTN_TYPE_KB, 0x46, "KB_9" }, + { BTN_TYPE_KB, 0x45, "KB_0" }, + { BTN_TYPE_KB, 0x4E, "KB_HYPHEN" }, + { BTN_TYPE_KB, 0x55, "KB_EQUALS" }, + { BTN_TYPE_KB, 0x66, "KB_BACKSPACE" }, + { BTN_TYPE_KB, 0x0D, "KB_TAB" }, + { BTN_TYPE_KB, 0x15, "KB_Q" }, + { BTN_TYPE_KB, 0x1D, "KB_W" }, + { BTN_TYPE_KB, 0x24, "KB_E" }, + { BTN_TYPE_KB, 0x2D, "KB_R" }, + { BTN_TYPE_KB, 0x2C, "KB_T" }, + { BTN_TYPE_KB, 0x35, "KB_Y" }, + { BTN_TYPE_KB, 0x3C, "KB_U" }, + { BTN_TYPE_KB, 0x44, "KB_O" }, + { BTN_TYPE_KB, 0x4D, "KB_P" }, + { BTN_TYPE_KB, 0x54, "KB_LBRACKET" }, + { BTN_TYPE_KB, 0x5B, "KB_RBRACKET" }, + { BTN_TYPE_KB, 0x5D, "KB_BACKSLASH" }, + { BTN_TYPE_KB, 0x58, "KB_CAPS" }, + { BTN_TYPE_KB, 0x1C, "KB_A" }, + { BTN_TYPE_KB, 0x1B, "KB_S" }, + { BTN_TYPE_KB, 0x23, "KB_D" }, + { BTN_TYPE_KB, 0x2B, "KB_F" }, + { BTN_TYPE_KB, 0x34, "KB_G" }, + { BTN_TYPE_KB, 0x33, "KB_H" }, + { BTN_TYPE_KB, 0x3B, "KB_J" }, + { BTN_TYPE_KB, 0x42, "KB_K" }, + { BTN_TYPE_KB, 0x4B, "KB_L" }, + { BTN_TYPE_KB, 0x4C, "KB_SEMI" }, + { BTN_TYPE_KB, 0x52, "KB_QUOTE" }, + { BTN_TYPE_KB, 0x5A, "KB_RET" }, + { BTN_TYPE_KB, 0x88, "KB_LSHIFT" }, + { BTN_TYPE_KB, 0x1A, "KB_Z" }, + { BTN_TYPE_KB, 0x22, "KB_X" }, + { BTN_TYPE_KB, 0x21, "KB_C" }, + { BTN_TYPE_KB, 0x2A, "KB_V" }, + { BTN_TYPE_KB, 0x32, "KB_B" }, + { BTN_TYPE_KB, 0x31, "KB_N" }, + { BTN_TYPE_KB, 0x3A, "KB_M" }, + { BTN_TYPE_KB, 0x41, "KB_COMMA" }, + { BTN_TYPE_KB, 0x49, "KB_PERIOD" }, + { BTN_TYPE_KB, 0x89, "KB_RSHIFT" }, + { BTN_TYPE_KB, 0x8C, "KB_LCTL" }, + { BTN_TYPE_KB, 0x82, "KB_META" }, + { BTN_TYPE_KB, 0x8A, "KB_LALT" }, + { BTN_TYPE_KB, 0x29, "KB_SPACE" }, + { BTN_TYPE_KB, 0x8B, "KB_RALT" }, + { BTN_TYPE_KB, 0x84, "KB_MENU" }, + { BTN_TYPE_KB, 0x8D, "KB_RCTL" }, + { BTN_TYPE_KB, 0xC3, "KB_PRNTSCN" }, + { BTN_TYPE_KB, 0x7E, "KB_SCRLCK" }, + { BTN_TYPE_KB, 0x91, "KB_PAUSE" }, + { BTN_TYPE_KB, 0xC2, "KB_INS" }, + { BTN_TYPE_KB, 0x94, "KB_HOME" }, + { BTN_TYPE_KB, 0x96, "KB_PGUP" }, + { BTN_TYPE_KB, 0xC0, "KB_DEL" }, + { BTN_TYPE_KB, 0x95, "KB_END" }, + { BTN_TYPE_KB, 0x97, "KB_PGDWN" }, + { BTN_TYPE_KB, 0x98, "KB_UP_ARROW" }, + { BTN_TYPE_KB, 0x99, "KB_DOWN_ARROW" }, + { BTN_TYPE_KB, 0x91, "KB_LEFT_ARROW" }, + { BTN_TYPE_KB, 0x9B, "KB_RIGHT_ARROW" }, + + /* Numpad button codes */ + { BTN_TYPE_KB, 0x77, "NUMPAD_LOCK" }, + { BTN_TYPE_KB, 0x90, "NUMPAD_FWDSLASH" }, + { BTN_TYPE_KB, 0x7C, "NUMPAD_ASTERISK" }, + { BTN_TYPE_KB, 0x7B, "NUMPAD_HYPHEN" }, + { BTN_TYPE_KB, 0x70, "NUMPAD_0" }, + { BTN_TYPE_KB, 0x69, "NUMPAD_1" }, + { BTN_TYPE_KB, 0x72, "NUMPAD_2" }, + { BTN_TYPE_KB, 0x7A, "NUMPAD_3" }, + { BTN_TYPE_KB, 0x6B, "NUMPAD_4" }, + { BTN_TYPE_KB, 0x73, "NUMPAD_5" }, + { BTN_TYPE_KB, 0x74, "NUMPAD_6" }, + { BTN_TYPE_KB, 0x6C, "NUMPAD_7" }, + { BTN_TYPE_KB, 0x75, "NUMPAD_8" }, + { BTN_TYPE_KB, 0x7D, "NUMPAD_9" }, + { BTN_TYPE_KB, 0x79, "NUMPAD_PLUS" }, + { BTN_TYPE_KB, 0x81, "NUMPAD_ENTER" }, + { BTN_TYPE_KB, 0x71, "NUMPAD_PERIOD" }, + + /* Mouse button codes */ + { BTN_TYPE_MOUSE, 0x01, "MOUSE_LCLICK" }, + { BTN_TYPE_MOUSE, 0x02, "MOUSE_RCLICK" }, + { BTN_TYPE_MOUSE, 0x03, "MOUSE_MCLICK" }, + { BTN_TYPE_MOUSE, 0x04, "MOUSE_WHEEL_UP" }, + { BTN_TYPE_MOUSE, 0x05, "MOUSE_WHEEL_DOWN" }, + + /* Media button codes */ + { BTN_TYPE_MEDIA, 0x16, "MEDIA_SCREENSHOT" }, + { BTN_TYPE_MEDIA, 0x19, "MEDIA_SHOW_KEYBOARD" }, + { BTN_TYPE_MEDIA, 0x1C, "MEDIA_SHOW_DESKTOP" }, + { BTN_TYPE_MEDIA, 0x1E, "MEDIA_START_RECORDING" }, + { BTN_TYPE_MEDIA, 0x01, "MEDIA_MIC_OFF" }, + { BTN_TYPE_MEDIA, 0x02, "MEDIA_VOL_DOWN" }, + { BTN_TYPE_MEDIA, 0x03, "MEDIA_VOL_UP" }, +}; + +static const size_t keymap_len = ARRAY_SIZE(ally_btn_codes); + +/* Button pair indexes for mapping commands */ +enum btn_pair_index { + BTN_PAIR_DPAD_UPDOWN = 0x01, + BTN_PAIR_DPAD_LEFTRIGHT = 0x02, + BTN_PAIR_STICK_LR = 0x03, + BTN_PAIR_BUMPER_LR = 0x04, + BTN_PAIR_AB = 0x05, + BTN_PAIR_XY = 0x06, + BTN_PAIR_VIEW_MENU = 0x07, + BTN_PAIR_M1M2 = 0x08, + BTN_PAIR_TRIGGER_LR = 0x09, +}; + +struct button_map { + struct btn_code_map *remap; + struct btn_code_map *macro; +}; + +struct button_pair_map { + enum btn_pair_index pair_index; + struct button_map first; + struct button_map second; +}; + +/* Store button mapping per gamepad mode */ +struct ally_button_mapping { + struct button_pair_map button_pairs[9]; /* 9 button pairs */ +}; + +/* Find a button code map by its name */ +static const struct btn_code_map *find_button_by_name(const char *name) +{ + int i; + + for (i = 0; i < keymap_len; i++) { + if (strcmp(ally_btn_codes[i].name, name) == 0) + return &ally_btn_codes[i]; + } + + return NULL; +} + +/* Set button mapping for a button pair */ +static int ally_set_button_mapping(struct hid_device *hdev, struct ally_handheld *ally, + struct button_pair_map *mapping) +{ + unsigned char packet[64] = { 0 }; + + if (!mapping) + return -EINVAL; + + packet[0] = HID_ALLY_SET_REPORT_ID; + packet[1] = HID_ALLY_FEATURE_CODE_PAGE; + packet[2] = CMD_SET_MAPPING; + packet[3] = mapping->pair_index; + packet[4] = 0x2C; /* Length */ + + /* First button mapping */ + packet[5] = mapping->first.remap->type; + /* Fill in bytes 6-14 with button code */ + if (mapping->first.remap->type) { + unsigned char btn_bytes[10] = {0}; + btn_bytes[0] = mapping->first.remap->type; + + switch (mapping->first.remap->type) { + case BTN_TYPE_NONE: + break; + case BTN_TYPE_PAD: + case BTN_TYPE_KB: + case BTN_TYPE_MEDIA: + btn_bytes[2] = mapping->first.remap->value; + break; + case BTN_TYPE_MOUSE: + btn_bytes[4] = mapping->first.remap->value; + break; + } + memcpy(&packet[5], btn_bytes, 10); + } + + /* Macro mapping for first button if any */ + packet[15] = mapping->first.macro->type; + if (mapping->first.macro->type) { + unsigned char macro_bytes[11] = {0}; + macro_bytes[0] = mapping->first.macro->type; + + switch (mapping->first.macro->type) { + case BTN_TYPE_NONE: + break; + case BTN_TYPE_PAD: + case BTN_TYPE_KB: + case BTN_TYPE_MEDIA: + macro_bytes[2] = mapping->first.macro->value; + break; + case BTN_TYPE_MOUSE: + macro_bytes[4] = mapping->first.macro->value; + break; + } + memcpy(&packet[15], macro_bytes, 11); + } + + /* Second button mapping */ + packet[27] = mapping->second.remap->type; + /* Fill in bytes 28-36 with button code */ + if (mapping->second.remap->type) { + unsigned char btn_bytes[10] = {0}; + btn_bytes[0] = mapping->second.remap->type; + + switch (mapping->second.remap->type) { + case BTN_TYPE_NONE: + break; + case BTN_TYPE_PAD: + case BTN_TYPE_KB: + case BTN_TYPE_MEDIA: + btn_bytes[2] = mapping->second.remap->value; + break; + case BTN_TYPE_MOUSE: + btn_bytes[4] = mapping->second.remap->value; + break; + } + memcpy(&packet[27], btn_bytes, 10); + } + + /* Macro mapping for second button if any */ + packet[37] = mapping->second.macro->type; + if (mapping->second.macro->type) { + unsigned char macro_bytes[11] = {0}; + macro_bytes[0] = mapping->second.macro->type; + + switch (mapping->second.macro->type) { + case BTN_TYPE_NONE: + break; + case BTN_TYPE_PAD: + case BTN_TYPE_KB: + case BTN_TYPE_MEDIA: + macro_bytes[2] = mapping->second.macro->value; + break; + case BTN_TYPE_MOUSE: + macro_bytes[4] = mapping->second.macro->value; + break; + } + memcpy(&packet[37], macro_bytes, 11); + } + + return ally_gamepad_send_packet(ally, hdev, packet, sizeof(packet)); +} + +/** + * ally_check_capability - Check if a specific capability is supported + * @hdev: HID device + * @flag_code: Capability flag code to check + * + * Returns true if capability is supported, false otherwise + */ +static bool ally_check_capability(struct hid_device *hdev, u8 flag_code) +{ + struct ally_handheld *ally = hid_get_drvdata(hdev); + bool result = false; + u8 *hidbuf; + int ret; + + hidbuf = kzalloc(HID_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return false; + + hidbuf[0] = HID_ALLY_SET_REPORT_ID; + hidbuf[1] = HID_ALLY_FEATURE_CODE_PAGE; + hidbuf[2] = flag_code; + hidbuf[3] = 0x01; + + ret = ally_gamepad_send_receive_packet(ally, hdev, hidbuf, HID_ALLY_REPORT_SIZE); + if (ret < 0) + goto cleanup; + + if (hidbuf[1] == HID_ALLY_FEATURE_CODE_PAGE && hidbuf[2] == flag_code) + result = (hidbuf[4] == 0x01); + +cleanup: + kfree(hidbuf); + return result; +} + +static int ally_detect_capabilities(struct hid_device *hdev, + struct ally_config *cfg) +{ + if (!hdev || !cfg) + return -EINVAL; + + mutex_lock(&cfg->config_mutex); + cfg->is_ally_x = + (hdev->product == USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X); + + cfg->xbox_controller_support = + ally_check_capability(hdev, CMD_CHECK_XBOX_SUPPORT); + cfg->user_cal_support = + ally_check_capability(hdev, CMD_CHECK_USER_CAL_SUPPORT); + cfg->turbo_support = + ally_check_capability(hdev, CMD_CHECK_TURBO_SUPPORT); + cfg->resp_curve_support = + ally_check_capability(hdev, CMD_CHECK_RESP_CURVE_SUPPORT); + cfg->dir_to_btn_support = + ally_check_capability(hdev, CMD_CHECK_DIR_TO_BTN_SUPPORT); + cfg->gyro_support = + ally_check_capability(hdev, CMD_CHECK_GYRO_TO_JOYSTICK); + cfg->anti_deadzone_support = + ally_check_capability(hdev, CMD_CHECK_ANTI_DEADZONE); + mutex_unlock(&cfg->config_mutex); + + hid_dbg( + hdev, + "Ally capabilities: %s, Xbox: %d, UserCal: %d, Turbo: %d, RespCurve: %d, DirToBtn: %d, Gyro: %d, AntiDZ: %d", + cfg->is_ally_x ? "Ally X" : "Ally", + cfg->xbox_controller_support, cfg->user_cal_support, + cfg->turbo_support, cfg->resp_curve_support, + cfg->dir_to_btn_support, cfg->gyro_support, + cfg->anti_deadzone_support); + + return 0; +} + +static int ally_set_xbox_controller(struct hid_device *hdev, + struct ally_config *cfg, bool enabled) +{ + struct ally_handheld *ally = hid_get_drvdata(hdev); + u8 buffer[64] = { 0 }; + int ret; + + if (!cfg || !cfg->xbox_controller_support) + return -ENODEV; + + buffer[0] = HID_ALLY_SET_REPORT_ID; + buffer[1] = HID_ALLY_FEATURE_CODE_PAGE; + buffer[2] = CMD_SET_XBOX_CONTROLLER; + buffer[3] = 0x01; + buffer[4] = enabled ? 0x01 : 0x00; + + ret = ally_gamepad_send_one_byte_packet( + ally, hdev, CMD_SET_XBOX_CONTROLLER, + enabled ? 0x01 : 0x00); + if (ret < 0) return ret; + + cfg->xbox_controller_enabled = enabled; + return 0; +} + +static ssize_t xbox_controller_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct ally_config *cfg; + + if (!ally || !ally->config) + return -ENODEV; + + cfg = ally->config; + + if (!cfg->xbox_controller_support) + return sprintf(buf, "Unsupported\n"); + + return sprintf(buf, "%d\n", cfg->xbox_controller_enabled); +} + +static ssize_t xbox_controller_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct ally_config *cfg; + bool enabled; + int ret; + + cfg = ally->config; + if (!cfg->xbox_controller_support) + return -ENODEV; + + ret = kstrtobool(buf, &enabled); + if (ret) + return ret; + + ret = ally_set_xbox_controller(hdev, cfg, enabled); + + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR_RW(xbox_controller); + +/** + * ally_set_vibration_intensity - Set vibration intensity values + * @hdev: HID device + * @cfg: Ally config + * @left: Left motor intensity (0-100) + * @right: Right motor intensity (0-100) + * + * Returns 0 on success, negative error code on failure + */ +static int ally_set_vibration_intensity(struct hid_device *hdev, + struct ally_config *cfg, u8 left, + u8 right) +{ + struct ally_handheld *ally = hid_get_drvdata(hdev); + u8 buffer[64] = { 0 }; + int ret; + + if (!cfg) + return -ENODEV; + + buffer[0] = HID_ALLY_SET_REPORT_ID; + buffer[1] = HID_ALLY_FEATURE_CODE_PAGE; + buffer[2] = CMD_SET_VIBRATION_INTENSITY; + buffer[3] = 0x02; /* Length */ + buffer[4] = left; + buffer[5] = right; + + ret = ally_gamepad_send_two_byte_packet( + ally, hdev, CMD_SET_VIBRATION_INTENSITY, left, right); + if (ret < 0) + return ret; + + mutex_lock(&cfg->config_mutex); + cfg->vibration_intensity_left = left; + cfg->vibration_intensity_right = right; + mutex_unlock(&cfg->config_mutex); + + return 0; +} + +static ssize_t vibration_intensity_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct ally_config *cfg; + + if (!ally || !ally->config) + return -ENODEV; + + cfg = ally->config; + + return sprintf(buf, "%u,%u\n", cfg->vibration_intensity_left, + cfg->vibration_intensity_right); +} + +static ssize_t vibration_intensity_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct ally_config *cfg; + u8 left, right; + int ret; + + if (!ally || !ally->config) + return -ENODEV; + + cfg = ally->config; + + ret = sscanf(buf, "%hhu %hhu", &left, &right); + if (ret != 2 || left > 100 || right > 100) + return -EINVAL; + + ret = ally_set_vibration_intensity(hdev, cfg, left, right); + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR_RW(vibration_intensity); + +/** + * ally_set_dzot_ranges - Generic function to set joystick or trigger ranges + * @hdev: HID device + * @cfg: Ally config struct + * @command: Command to use (CMD_SET_JOYSTICK_DEADZONE or CMD_SET_TRIGGER_RANGE) + * @param1: First parameter + * @param2: Second parameter + * @param3: Third parameter + * @param4: Fourth parameter + * + * Returns 0 on success, negative error code on failure + */ +static int ally_set_dzot_ranges(struct hid_device *hdev, + struct ally_config *cfg, + u8 command, u8 param1, u8 param2, + u8 param3, u8 param4) +{ + struct ally_handheld *ally = hid_get_drvdata(hdev); + u8 packet[HID_ALLY_REPORT_SIZE] = { 0 }; + int ret; + + packet[0] = HID_ALLY_SET_REPORT_ID; + packet[1] = HID_ALLY_FEATURE_CODE_PAGE; + packet[2] = command; + packet[3] = 0x04; /* Length */ + packet[4] = param1; + packet[5] = param2; + packet[6] = param3; + packet[7] = param4; + + ret = ally_gamepad_send_packet(ally, hdev, packet, + HID_ALLY_REPORT_SIZE); + return ret; +} + +static int ally_validate_joystick_dzot(u8 left_dz, u8 left_ot, u8 right_dz, + u8 right_ot) +{ + if (left_dz > 50 || right_dz > 50) + return -EINVAL; + + if (left_ot < 70 || left_ot > 100 || right_ot < 70 || right_ot > 100) + return -EINVAL; + + return 0; +} + +static int ally_set_joystick_dzot(struct hid_device *hdev, + struct ally_config *cfg, u8 left_dz, + u8 left_ot, u8 right_dz, u8 right_ot) +{ + int ret; + + ret = ally_validate_joystick_dzot(left_dz, left_ot, right_dz, right_ot); + if (ret < 0) + return ret; + + ret = ally_set_dzot_ranges(hdev, cfg, + CMD_SET_JOYSTICK_DEADZONE, + left_dz, left_ot, right_dz, + right_ot); + if (ret < 0) + return ret; + + mutex_lock(&cfg->config_mutex); + cfg->left_deadzone = left_dz; + cfg->left_outer_threshold = left_ot; + cfg->right_deadzone = right_dz; + cfg->right_outer_threshold = right_ot; + mutex_unlock(&cfg->config_mutex); + + return 0; +} + +static ssize_t joystick_deadzone_show(struct device *dev, + struct device_attribute *attr, char *buf, + u8 deadzone, u8 outer_threshold) +{ + return sprintf(buf, "%u %u\n", deadzone, outer_threshold); +} + +static ssize_t joystick_deadzone_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + bool is_left, struct ally_config *cfg) +{ + struct hid_device *hdev = to_hid_device(dev); + u8 dz, ot; + int ret; + + ret = sscanf(buf, "%hhu %hhu", &dz, &ot); + if (ret != 2) + return -EINVAL; + + if (is_left) { + ret = ally_set_joystick_dzot(hdev, cfg, dz, ot, + cfg->right_deadzone, + cfg->right_outer_threshold); + } else { + ret = ally_set_joystick_dzot(hdev, cfg, cfg->left_deadzone, + cfg->left_outer_threshold, dz, ot); + } + + if (ret < 0) + return ret; + + return count; +} + +static ssize_t joystick_left_deadzone_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct ally_config *cfg = ally->config; + + return joystick_deadzone_show(dev, attr, buf, cfg->left_deadzone, + cfg->left_outer_threshold); +} + +static ssize_t joystick_left_deadzone_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + + return joystick_deadzone_store(dev, attr, buf, count, true, + ally->config); +} + +static ssize_t joystick_right_deadzone_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct ally_config *cfg = ally->config; + + return joystick_deadzone_show(dev, attr, buf, cfg->right_deadzone, + cfg->right_outer_threshold); +} + +static ssize_t joystick_right_deadzone_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + + return joystick_deadzone_store(dev, attr, buf, count, false, + ally->config); +} + +ALLY_DEVICE_CONST_ATTR_RO(js_deadzone_index, deadzone_index, "inner outer\n"); +ALLY_DEVICE_CONST_ATTR_RO(js_deadzone_inner_min, deadzone_inner_min, "0\n"); +ALLY_DEVICE_CONST_ATTR_RO(js_deadzone_inner_max, deadzone_inner_max, "50\n"); +ALLY_DEVICE_CONST_ATTR_RO(js_deadzone_outer_min, deadzone_outer_min, "70\n"); +ALLY_DEVICE_CONST_ATTR_RO(js_deadzone_outer_max, deadzone_outer_max, "100\n"); + +ALLY_DEVICE_ATTR_RW(joystick_left_deadzone, deadzone); +ALLY_DEVICE_ATTR_RW(joystick_right_deadzone, deadzone); + +/** + * ally_set_anti_deadzone - Set anti-deadzone values for joysticks + * @ally: ally handheld structure + * @left_adz: Left joystick anti-deadzone value (0-100) + * @right_adz: Right joystick anti-deadzone value (0-100) + * + * Return: 0 on success, negative on failure + */ +static int ally_set_anti_deadzone(struct ally_handheld *ally, u8 left_adz, + u8 right_adz) +{ + struct hid_device *hdev = ally->cfg_hdev; + int ret; + + if (!ally->config->anti_deadzone_support) { + hid_dbg(hdev, "Anti-deadzone not supported on this device\n"); + return -EOPNOTSUPP; + } + + if (left_adz > 100 || right_adz > 100) + return -EINVAL; + + ret = ally_gamepad_send_two_byte_packet( + ally, hdev, CMD_SET_ANTI_DEADZONE, left_adz, right_adz); + if (ret < 0) { + hid_err(hdev, "Failed to set anti-deadzone values: %d\n", ret); + return ret; + } + + ally->config->left_anti_deadzone = left_adz; + ally->config->right_anti_deadzone = right_adz; + hid_dbg(hdev, "Set joystick anti-deadzone: left=%d, right=%d\n", + left_adz, right_adz); + + return 0; +} + +static ssize_t anti_deadzone_show(struct device *dev, + struct device_attribute *attr, char *buf, + u8 anti_deadzone) +{ + return sprintf(buf, "%u\n", anti_deadzone); +} + +static ssize_t anti_deadzone_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, bool is_left, + struct ally_handheld *ally) +{ + u8 adz; + int ret; + + if (!ally || !ally->config) + return -ENODEV; + + if (!ally->config->anti_deadzone_support) + return -EOPNOTSUPP; + + ret = kstrtou8(buf, 10, &adz); + if (ret) + return ret; + + if (adz > 100) + return -EINVAL; + + if (is_left) + ret = ally_set_anti_deadzone(ally, adz, ally->config->right_anti_deadzone); + else + ret = ally_set_anti_deadzone(ally, ally->config->left_anti_deadzone, adz); + + if (ret < 0) + return ret; + + return count; +} + +static ssize_t js_left_anti_deadzone_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + + if (!ally || !ally->config) + return -ENODEV; + + return anti_deadzone_show(dev, attr, buf, + ally->config->left_anti_deadzone); +} + +static ssize_t js_left_anti_deadzone_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + + return anti_deadzone_store(dev, attr, buf, count, true, ally); +} + +static ssize_t js_right_anti_deadzone_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + + if (!ally || !ally->config) + return -ENODEV; + + return anti_deadzone_show(dev, attr, buf, + ally->config->right_anti_deadzone); +} + +static ssize_t js_right_anti_deadzone_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + + return anti_deadzone_store(dev, attr, buf, count, false, ally); +} + +ALLY_DEVICE_ATTR_RW(js_left_anti_deadzone, anti_deadzone); +ALLY_DEVICE_ATTR_RW(js_right_anti_deadzone, anti_deadzone); +ALLY_DEVICE_CONST_ATTR_RO(js_anti_deadzone_min, js_anti_deadzone_min, "0\n"); +ALLY_DEVICE_CONST_ATTR_RO(js_anti_deadzone_max, js_anti_deadzone_max, "100\n"); + +/** + * ally_set_joystick_resp_curve - Set joystick response curve parameters + * @ally: ally handheld structure + * @hdev: HID device + * @side: Which joystick side (0=left, 1=right) + * @curve: Response curve parameter structure + * + * Return: 0 on success, negative on failure + */ +static int ally_set_joystick_resp_curve(struct ally_handheld *ally, + struct hid_device *hdev, u8 side, + struct joystick_resp_curve *curve) +{ + u8 packet[HID_ALLY_REPORT_SIZE] = { 0 }; + int ret; + struct ally_config *cfg = ally->config; + + if (!cfg || !cfg->resp_curve_support) { + hid_dbg(hdev, "Response curve not supported on this device\n"); + return -EOPNOTSUPP; + } + + if (side > 1) { + return -EINVAL; + } + + packet[0] = HID_ALLY_SET_REPORT_ID; + packet[1] = HID_ALLY_FEATURE_CODE_PAGE; + packet[2] = CMD_SET_RESP_CURVE; + packet[3] = 0x09; /* Length */ + packet[4] = side; + + packet[5] = curve->entry_1.move; + packet[6] = curve->entry_1.resp; + packet[7] = curve->entry_2.move; + packet[8] = curve->entry_2.resp; + packet[9] = curve->entry_3.move; + packet[10] = curve->entry_3.resp; + packet[11] = curve->entry_4.move; + packet[12] = curve->entry_4.resp; + + ret = ally_gamepad_send_packet(ally, hdev, packet, + HID_ALLY_REPORT_SIZE); + if (ret < 0) { + hid_err(hdev, "Failed to set joystick response curve: %d\n", + ret); + return ret; + } + + mutex_lock(&cfg->config_mutex); + if (side == 0) { + memcpy(&cfg->left_curve, curve, sizeof(*curve)); + } else { + memcpy(&cfg->right_curve, curve, sizeof(*curve)); + } + mutex_unlock(&cfg->config_mutex); + + hid_dbg(hdev, "Set joystick response curve for side %d\n", side); + return 0; +} + +static int response_curve_apply(struct hid_device *hdev, struct ally_handheld *ally, bool is_left) +{ + struct ally_config *cfg = ally->config; + struct joystick_resp_curve *curve = is_left ? &cfg->left_curve : &cfg->right_curve; + + if (!(curve->entry_1.move < curve->entry_2.move && + curve->entry_2.move < curve->entry_3.move && + curve->entry_3.move < curve->entry_4.move)) { + return -EINVAL; + } + + return ally_set_joystick_resp_curve(ally, hdev, is_left ? 0 : 1, curve); +} + +static ssize_t response_curve_apply_left_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + int ret; + bool apply; + + if (!ally->config->resp_curve_support) + return -EOPNOTSUPP; + + ret = kstrtobool(buf, &apply); + if (ret) + return ret; + + if (!apply) + return count; /* Only apply on "1" or "true" value */ + + ret = response_curve_apply(hdev, ally, true); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t response_curve_apply_right_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + int ret; + bool apply; + + if (!ally->config->resp_curve_support) + return -EOPNOTSUPP; + + ret = kstrtobool(buf, &apply); + if (ret) + return ret; + + if (!apply) + return count; /* Only apply on "1" or "true" value */ + + ret = response_curve_apply(hdev, ally, false); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t response_curve_pct_show(struct device *dev, + struct device_attribute *attr, char *buf, + struct joystick_resp_curve *curve, int idx) +{ + switch (idx) { + case 1: return sprintf(buf, "%u\n", curve->entry_1.resp); + case 2: return sprintf(buf, "%u\n", curve->entry_2.resp); + case 3: return sprintf(buf, "%u\n", curve->entry_3.resp); + case 4: return sprintf(buf, "%u\n", curve->entry_4.resp); + default: return -EINVAL; + } +} + +static ssize_t response_curve_move_show(struct device *dev, + struct device_attribute *attr, char *buf, + struct joystick_resp_curve *curve, int idx) +{ + switch (idx) { + case 1: return sprintf(buf, "%u\n", curve->entry_1.move); + case 2: return sprintf(buf, "%u\n", curve->entry_2.move); + case 3: return sprintf(buf, "%u\n", curve->entry_3.move); + case 4: return sprintf(buf, "%u\n", curve->entry_4.move); + default: return -EINVAL; + } +} + +static ssize_t response_curve_pct_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + bool is_left, struct ally_handheld *ally, + int idx) +{ + struct ally_config *cfg = ally->config; + struct joystick_resp_curve *curve = is_left ? &cfg->left_curve : &cfg->right_curve; + u8 value; + int ret; + + if (!cfg->resp_curve_support) + return -EOPNOTSUPP; + + ret = kstrtou8(buf, 10, &value); + if (ret) + return ret; + + if (value > 100) + return -EINVAL; + + mutex_lock(&cfg->config_mutex); + switch (idx) { + case 1: curve->entry_1.resp = value; break; + case 2: curve->entry_2.resp = value; break; + case 3: curve->entry_3.resp = value; break; + case 4: curve->entry_4.resp = value; break; + default: ret = -EINVAL; + } + mutex_unlock(&cfg->config_mutex); + + if (ret < 0) + return ret; + + return count; +} + +static ssize_t response_curve_move_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + bool is_left, struct ally_handheld *ally, + int idx) +{ + struct ally_config *cfg = ally->config; + struct joystick_resp_curve *curve = is_left ? &cfg->left_curve : &cfg->right_curve; + u8 value; + int ret; + + if (!cfg->resp_curve_support) + return -EOPNOTSUPP; + + ret = kstrtou8(buf, 10, &value); + if (ret) + return ret; + + if (value > 100) + return -EINVAL; + + mutex_lock(&cfg->config_mutex); + switch (idx) { + case 1: curve->entry_1.move = value; break; + case 2: curve->entry_2.move = value; break; + case 3: curve->entry_3.move = value; break; + case 4: curve->entry_4.move = value; break; + default: ret = -EINVAL; + } + mutex_unlock(&cfg->config_mutex); + + if (ret < 0) + return ret; + + return count; +} + +#define DEFINE_JS_CURVE_PCT_FOPS(region, side) \ + static ssize_t response_curve_pct_##region##_##side##_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + struct hid_device *hdev = to_hid_device(dev); \ + struct ally_handheld *ally = hid_get_drvdata(hdev); \ + return response_curve_pct_show( \ + dev, attr, buf, &ally->config->side##_curve, region); \ + } \ + \ + static ssize_t response_curve_pct_##region##_##side##_store( \ + struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct hid_device *hdev = to_hid_device(dev); \ + struct ally_handheld *ally = hid_get_drvdata(hdev); \ + return response_curve_pct_store(dev, attr, buf, count, \ + side##_is_left, ally, region); \ + } + +#define DEFINE_JS_CURVE_MOVE_FOPS(region, side) \ + static ssize_t response_curve_move_##region##_##side##_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + struct hid_device *hdev = to_hid_device(dev); \ + struct ally_handheld *ally = hid_get_drvdata(hdev); \ + return response_curve_move_show( \ + dev, attr, buf, &ally->config->side##_curve, region); \ + } \ + \ + static ssize_t response_curve_move_##region##_##side##_store( \ + struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct hid_device *hdev = to_hid_device(dev); \ + struct ally_handheld *ally = hid_get_drvdata(hdev); \ + return response_curve_move_store(dev, attr, buf, count, \ + side##_is_left, ally, region); \ + } + +#define DEFINE_JS_CURVE_ATTRS(region, side) \ + DEFINE_JS_CURVE_PCT_FOPS(region, side) \ + DEFINE_JS_CURVE_MOVE_FOPS(region, side) \ + ALLY_DEVICE_ATTR_RW(response_curve_pct_##region##_##side, \ + response_curve_pct_##region); \ + ALLY_DEVICE_ATTR_RW(response_curve_move_##region##_##side, \ + response_curve_move_##region) + +/* Helper defines for "is_left" parameter */ +#define left_is_left true +#define right_is_left false + +DEFINE_JS_CURVE_ATTRS(1, left); +DEFINE_JS_CURVE_ATTRS(2, left); +DEFINE_JS_CURVE_ATTRS(3, left); +DEFINE_JS_CURVE_ATTRS(4, left); + +DEFINE_JS_CURVE_ATTRS(1, right); +DEFINE_JS_CURVE_ATTRS(2, right); +DEFINE_JS_CURVE_ATTRS(3, right); +DEFINE_JS_CURVE_ATTRS(4, right); + +ALLY_DEVICE_ATTR_WO(response_curve_apply_left, response_curve_apply); +ALLY_DEVICE_ATTR_WO(response_curve_apply_right, response_curve_apply); + +static ssize_t deadzone_left_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct ally_config *cfg = ally->config; + + return sprintf(buf, "%u %u\n", cfg->left_deadzone, cfg->left_outer_threshold); +} + +static ssize_t deadzone_right_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct ally_config *cfg = ally->config; + + return sprintf(buf, "%u %u\n", cfg->right_deadzone, cfg->right_outer_threshold); +} + +DEVICE_ATTR_RO(deadzone_left); +DEVICE_ATTR_RO(deadzone_right); +ALLY_DEVICE_CONST_ATTR_RO(deadzone_index, deadzone_index, "inner outer\n"); + +static struct attribute *axis_xy_left_attrs[] = { + &dev_attr_joystick_left_deadzone.attr, + &dev_attr_js_deadzone_index.attr, + &dev_attr_js_deadzone_inner_min.attr, + &dev_attr_js_deadzone_inner_max.attr, + &dev_attr_js_deadzone_outer_min.attr, + &dev_attr_js_deadzone_outer_max.attr, + &dev_attr_js_left_anti_deadzone.attr, + &dev_attr_js_anti_deadzone_min.attr, + &dev_attr_js_anti_deadzone_max.attr, + &dev_attr_response_curve_pct_1_left.attr, + &dev_attr_response_curve_pct_2_left.attr, + &dev_attr_response_curve_pct_3_left.attr, + &dev_attr_response_curve_pct_4_left.attr, + &dev_attr_response_curve_move_1_left.attr, + &dev_attr_response_curve_move_2_left.attr, + &dev_attr_response_curve_move_3_left.attr, + &dev_attr_response_curve_move_4_left.attr, + &dev_attr_response_curve_apply_left.attr, + NULL +}; + +static struct attribute *axis_xy_right_attrs[] = { + &dev_attr_joystick_right_deadzone.attr, + &dev_attr_js_deadzone_index.attr, + &dev_attr_js_deadzone_inner_min.attr, + &dev_attr_js_deadzone_inner_max.attr, + &dev_attr_js_deadzone_outer_min.attr, + &dev_attr_js_deadzone_outer_max.attr, + &dev_attr_js_right_anti_deadzone.attr, + &dev_attr_js_anti_deadzone_min.attr, + &dev_attr_js_anti_deadzone_max.attr, + &dev_attr_response_curve_pct_1_right.attr, + &dev_attr_response_curve_pct_2_right.attr, + &dev_attr_response_curve_pct_3_right.attr, + &dev_attr_response_curve_pct_4_right.attr, + &dev_attr_response_curve_move_1_right.attr, + &dev_attr_response_curve_move_2_right.attr, + &dev_attr_response_curve_move_3_right.attr, + &dev_attr_response_curve_move_4_right.attr, + &dev_attr_response_curve_apply_right.attr, + NULL +}; + +/** + * ally_set_trigger_range - Set trigger range values + * @hdev: HID device + * @cfg: Ally config + * @left_min: Left trigger minimum (0-255) + * @left_max: Left trigger maximum (0-255) + * @right_min: Right trigger minimum (0-255) + * @right_max: Right trigger maximum (0-255) + * + * Returns 0 on success, negative error code on failure + */ +static int ally_set_trigger_range(struct hid_device *hdev, + struct ally_config *cfg, u8 left_min, + u8 left_max, u8 right_min, u8 right_max) +{ + int ret; + + if (left_min >= left_max || right_min >= right_max) + return -EINVAL; + + ret = ally_set_dzot_ranges(hdev, cfg, + CMD_SET_TRIGGER_RANGE, + left_min, left_max, right_min, + right_max); + if (ret < 0) + return ret; + + mutex_lock(&cfg->config_mutex); + cfg->left_trigger_min = left_min; + cfg->left_trigger_max = left_max; + cfg->right_trigger_min = right_min; + cfg->right_trigger_max = right_max; + mutex_unlock(&cfg->config_mutex); + + return 0; +} + +static ssize_t trigger_range_show(struct device *dev, + struct device_attribute *attr, char *buf, + u8 min_val, u8 max_val) +{ + return sprintf(buf, "%u %u\n", min_val, max_val); +} + +static ssize_t trigger_range_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, bool is_left, + struct ally_config *cfg) +{ + struct hid_device *hdev = to_hid_device(dev); + u8 min_val, max_val; + int ret; + + ret = sscanf(buf, "%hhu %hhu", &min_val, &max_val); + if (ret != 2) + return -EINVAL; + + if (is_left) { + ret = ally_set_trigger_range(hdev, cfg, min_val, max_val, + cfg->right_trigger_min, + cfg->right_trigger_max); + } else { + ret = ally_set_trigger_range(hdev, cfg, cfg->left_trigger_min, + cfg->left_trigger_max, min_val, + max_val); + } + + if (ret < 0) + return ret; + + return count; +} + +static ssize_t trigger_left_deadzone_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct ally_config *cfg = ally->config; + + return trigger_range_show(dev, attr, buf, cfg->left_trigger_min, + cfg->left_trigger_max); +} + +static ssize_t trigger_left_deadzone_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + + return trigger_range_store(dev, attr, buf, count, true, ally->config); +} + +static ssize_t trigger_right_deadzone_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct ally_config *cfg = ally->config; + + return trigger_range_show(dev, attr, buf, cfg->right_trigger_min, + cfg->right_trigger_max); +} + +static ssize_t trigger_right_deadzone_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + + return trigger_range_store(dev, attr, buf, count, false, ally->config); +} + +ALLY_DEVICE_CONST_ATTR_RO(tr_deadzone_inner_min, deadzone_inner_min, "0\n"); +ALLY_DEVICE_CONST_ATTR_RO(tr_deadzone_inner_max, deadzone_inner_max, "255\n"); + +ALLY_DEVICE_ATTR_RW(trigger_left_deadzone, deadzone); +ALLY_DEVICE_ATTR_RW(trigger_right_deadzone, deadzone); + +static struct attribute *axis_z_left_attrs[] = { + &dev_attr_trigger_left_deadzone.attr, + &dev_attr_tr_deadzone_inner_min.attr, + &dev_attr_tr_deadzone_inner_max.attr, + NULL +}; + +static struct attribute *axis_z_right_attrs[] = { + &dev_attr_trigger_right_deadzone.attr, + &dev_attr_tr_deadzone_inner_min.attr, + &dev_attr_tr_deadzone_inner_max.attr, + NULL +}; + +/* Map from string name to enum value */ +static int get_gamepad_mode_from_name(const char *name) +{ + int i; + + for (i = ALLY_GAMEPAD_MODE_GAMEPAD; i <= ALLY_GAMEPAD_MODE_KEYBOARD; + i++) { + if (gamepad_mode_names[i] && + strcmp(name, gamepad_mode_names[i]) == 0) + return i; + } + + return -1; +} + +/** + * ally_set_gamepad_mode - Set the gamepad operating mode + * @ally: ally handheld structure + * @hdev: HID device + * @mode: Gamepad mode to set + * + * Returns: 0 on success, negative on failure + */ +static int ally_set_gamepad_mode(struct ally_handheld *ally, + struct hid_device *hdev, u8 mode) +{ + struct ally_config *cfg = ally->config; + int ret; + + if (!cfg) + return -EINVAL; + + if (mode < ALLY_GAMEPAD_MODE_GAMEPAD || + mode > ALLY_GAMEPAD_MODE_KEYBOARD) { + hid_err(hdev, "Invalid gamepad mode: %u\n", mode); + return -EINVAL; + } + + ret = ally_gamepad_send_one_byte_packet(ally, hdev, + CMD_SET_GAMEPAD_MODE, mode); + if (ret < 0) { + hid_err(hdev, "Failed to set gamepad mode: %d\n", ret); + return ret; + } + + mutex_lock(&cfg->config_mutex); + cfg->gamepad_mode = mode; + mutex_unlock(&cfg->config_mutex); + + hid_info(hdev, "Set gamepad mode to %s\n", gamepad_mode_names[mode]); + return 0; +} + +static ssize_t gamepad_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct ally_config *cfg; + + if (!ally || !ally->config) + return -ENODEV; + + cfg = ally->config; + + if (cfg->gamepad_mode >= ALLY_GAMEPAD_MODE_GAMEPAD && + cfg->gamepad_mode <= ALLY_GAMEPAD_MODE_KEYBOARD) { + return sprintf(buf, "%s\n", + gamepad_mode_names[cfg->gamepad_mode]); + } else { + return sprintf(buf, "unknown (%u)\n", cfg->gamepad_mode); + } +} + +static ssize_t gamepad_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + char mode_name[16]; + int mode; + int ret; + + if (!ally || !ally->config) + return -ENODEV; + + if (sscanf(buf, "%15s", mode_name) != 1) + return -EINVAL; + + mode = get_gamepad_mode_from_name(mode_name); + if (mode < 0) { + hid_err(hdev, "Unknown gamepad mode: %s\n", mode_name); + return -EINVAL; + } + + ret = ally_set_gamepad_mode(ally, hdev, mode); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t gamepad_modes_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int i; + int len = 0; + + for (i = ALLY_GAMEPAD_MODE_GAMEPAD; i <= ALLY_GAMEPAD_MODE_KEYBOARD; + i++) { + len += sprintf(buf + len, "%s ", gamepad_mode_names[i]); + } + + /* Replace the last space with a newline */ + if (len > 0) + buf[len - 1] = '\n'; + + return len; +} + +DEVICE_ATTR_RW(gamepad_mode); +DEVICE_ATTR_RO(gamepad_modes_available); + +static int ally_set_default_gamepad_mode(struct hid_device *hdev, + struct ally_config *cfg) +{ + struct ally_handheld *ally = hid_get_drvdata(hdev); + + cfg->gamepad_mode = ALLY_GAMEPAD_MODE_GAMEPAD; + + return ally_set_gamepad_mode(ally, hdev, cfg->gamepad_mode); +} + +static struct attribute *ally_config_attrs[] = { + &dev_attr_xbox_controller.attr, + &dev_attr_vibration_intensity.attr, + &dev_attr_gamepad_mode.attr, + &dev_attr_gamepad_modes_available.attr, + NULL +}; + +static const struct attribute_group ally_attr_groups[] = { + { + .attrs = ally_config_attrs, + }, + { + .name = "axis_xy_left", + .attrs = axis_xy_left_attrs, + }, + { + .name = "axis_xy_right", + .attrs = axis_xy_right_attrs, + }, + { + .name = "axis_z_left", + .attrs = axis_z_left_attrs, + }, + { + .name = "axis_z_right", + .attrs = axis_z_right_attrs, + }, +}; + +/** + * ally_get_turbo_params - Get turbo parameters for a specific button + * @cfg: Ally config structure + * @button_id: Button identifier from ally_button_id enum + * + * Returns: Pointer to the button's turbo parameters, or NULL if invalid + */ +static struct button_turbo_params *ally_get_turbo_params(struct ally_config *cfg, + enum ally_button_id button_id) +{ + struct turbo_config *turbo; + + if (!cfg || button_id >= ALLY_BTN_MAX) + return NULL; + + turbo = &cfg->turbo; + + switch (button_id) { + case ALLY_BTN_A: + return &turbo->btn_a; + case ALLY_BTN_B: + return &turbo->btn_b; + case ALLY_BTN_X: + return &turbo->btn_x; + case ALLY_BTN_Y: + return &turbo->btn_y; + case ALLY_BTN_LB: + return &turbo->btn_lb; + case ALLY_BTN_RB: + return &turbo->btn_rb; + case ALLY_BTN_DU: + return &turbo->btn_du; + case ALLY_BTN_DD: + return &turbo->btn_dd; + case ALLY_BTN_DL: + return &turbo->btn_dl; + case ALLY_BTN_DR: + return &turbo->btn_dr; + case ALLY_BTN_J0B: + return &turbo->btn_j0b; + case ALLY_BTN_J1B: + return &turbo->btn_j1b; + case ALLY_BTN_MENU: + return &turbo->btn_menu; + case ALLY_BTN_VIEW: + return &turbo->btn_view; + case ALLY_BTN_M1: + return &turbo->btn_m1; + case ALLY_BTN_M2: + return &turbo->btn_m2; + default: + return NULL; + } +} + +/** + * ally_set_turbo_params - Set turbo parameters for all buttons + * @hdev: HID device + * @cfg: Ally config structure + * + * Returns: 0 on success, negative on failure + */ +static int ally_set_turbo_params(struct hid_device *hdev, struct ally_config *cfg) +{ + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct turbo_config *turbo = &cfg->turbo; + u8 packet[HID_ALLY_REPORT_SIZE] = { 0 }; + int ret; + + if (!cfg->turbo_support) { + hid_dbg(hdev, "Turbo functionality not supported on this device\n"); + return -EOPNOTSUPP; + } + + packet[0] = HID_ALLY_SET_REPORT_ID; + packet[1] = HID_ALLY_FEATURE_CODE_PAGE; + packet[2] = CMD_SET_TURBO_PARAMS; + packet[3] = 0x20; /* Length - 32 bytes for 16 buttons with 2 values each */ + + packet[4] = turbo->btn_du.turbo; + packet[5] = turbo->btn_du.toggle; + packet[6] = turbo->btn_dd.turbo; + packet[7] = turbo->btn_dd.toggle; + packet[8] = turbo->btn_dl.turbo; + packet[9] = turbo->btn_dl.toggle; + packet[10] = turbo->btn_dr.turbo; + packet[11] = turbo->btn_dr.toggle; + packet[12] = turbo->btn_j0b.turbo; + packet[13] = turbo->btn_j0b.toggle; + packet[14] = turbo->btn_j1b.turbo; + packet[15] = turbo->btn_j1b.toggle; + packet[16] = turbo->btn_lb.turbo; + packet[17] = turbo->btn_lb.toggle; + packet[18] = turbo->btn_rb.turbo; + packet[19] = turbo->btn_rb.toggle; + packet[20] = turbo->btn_a.turbo; + packet[21] = turbo->btn_a.toggle; + packet[22] = turbo->btn_b.turbo; + packet[23] = turbo->btn_b.toggle; + packet[24] = turbo->btn_x.turbo; + packet[25] = turbo->btn_x.toggle; + packet[26] = turbo->btn_y.turbo; + packet[27] = turbo->btn_y.toggle; + packet[28] = turbo->btn_view.turbo; + packet[29] = turbo->btn_view.toggle; + packet[30] = turbo->btn_menu.turbo; + packet[31] = turbo->btn_menu.toggle; + packet[32] = turbo->btn_m2.turbo; + packet[33] = turbo->btn_m2.toggle; + packet[34] = turbo->btn_m1.turbo; + packet[35] = turbo->btn_m1.toggle; + + ret = ally_gamepad_send_packet(ally, hdev, packet, HID_ALLY_REPORT_SIZE); + if (ret < 0) { + hid_err(hdev, "Failed to set turbo parameters: %d\n", ret); + return ret; + } + + return 0; +} + +struct button_turbo_attr { + struct device_attribute dev_attr; + int button_id; +}; + +#define to_button_turbo_attr(x) container_of(x, struct button_turbo_attr, dev_attr) + +static ssize_t button_turbo_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct button_turbo_attr *btn_attr = to_button_turbo_attr(attr); + struct button_turbo_params *params; + + if (!ally->config->turbo_support) + return sprintf(buf, "Unsupported\n"); + + params = ally_get_turbo_params(ally->config, btn_attr->button_id); + if (!params) + return -EINVAL; + + /* Format: turbo_interval_ms[,toggle_interval_ms] */ + if (params->toggle) + return sprintf(buf, "%d,%d\n", params->turbo * 50, params->toggle * 50); + else + return sprintf(buf, "%d\n", params->turbo * 50); +} + +static ssize_t button_turbo_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct button_turbo_attr *btn_attr = to_button_turbo_attr(attr); + struct button_turbo_params *params; + unsigned int turbo_ms, toggle_ms = 0; + int ret; + + if (!ally->config->turbo_support) + return -EOPNOTSUPP; + + params = ally_get_turbo_params(ally->config, btn_attr->button_id); + if (!params) + return -EINVAL; + + /* Parse input: turbo_interval_ms[,toggle_interval_ms] */ + ret = sscanf(buf, "%u,%u", &turbo_ms, &toggle_ms); + if (ret < 1) + return -EINVAL; + + if (turbo_ms != 0 && (turbo_ms < 50 || turbo_ms > 1000)) + return -EINVAL; + + if (ret > 1 && toggle_ms > 0 && (toggle_ms < 50 || toggle_ms > 1000)) + return -EINVAL; + + mutex_lock(&ally->config->config_mutex); + + params->turbo = turbo_ms / 50; + params->toggle = toggle_ms / 50; + + ret = ally_set_turbo_params(hdev, ally->config); + + mutex_unlock(&ally->config->config_mutex); + + if (ret < 0) + return ret; + + return count; +} + +/* Helper to create button turbo attribute */ +static struct button_turbo_attr *button_turbo_attr_create(int button_id) +{ + struct button_turbo_attr *attr; + + attr = kzalloc(sizeof(*attr), GFP_KERNEL); + if (!attr) + return NULL; + + attr->button_id = button_id; + sysfs_attr_init(&attr->dev_attr.attr); + attr->dev_attr.attr.name = "turbo"; + attr->dev_attr.attr.mode = 0644; + attr->dev_attr.show = button_turbo_show; + attr->dev_attr.store = button_turbo_store; + + return attr; +} + +/* Button remap attribute structure */ +struct button_remap_attr { + struct device_attribute dev_attr; + enum ally_button_id button_id; + bool is_macro; +}; + +#define to_button_remap_attr(x) container_of(x, struct button_remap_attr, dev_attr) + +/* Get appropriate button pair index and position for a given button */ +static int get_button_pair_info(enum ally_button_id button_id, + enum btn_pair_index *pair_idx, + bool *is_first) +{ + switch (button_id) { + case ALLY_BTN_DU: + *pair_idx = BTN_PAIR_DPAD_UPDOWN; + *is_first = true; + break; + case ALLY_BTN_DD: + *pair_idx = BTN_PAIR_DPAD_UPDOWN; + *is_first = false; + break; + case ALLY_BTN_DL: + *pair_idx = BTN_PAIR_DPAD_LEFTRIGHT; + *is_first = true; + break; + case ALLY_BTN_DR: + *pair_idx = BTN_PAIR_DPAD_LEFTRIGHT; + *is_first = false; + break; + case ALLY_BTN_J0B: + *pair_idx = BTN_PAIR_STICK_LR; + *is_first = true; + break; + case ALLY_BTN_J1B: + *pair_idx = BTN_PAIR_STICK_LR; + *is_first = false; + break; + case ALLY_BTN_LB: + *pair_idx = BTN_PAIR_BUMPER_LR; + *is_first = true; + break; + case ALLY_BTN_RB: + *pair_idx = BTN_PAIR_BUMPER_LR; + *is_first = false; + break; + case ALLY_BTN_A: + *pair_idx = BTN_PAIR_AB; + *is_first = true; + break; + case ALLY_BTN_B: + *pair_idx = BTN_PAIR_AB; + *is_first = false; + break; + case ALLY_BTN_X: + *pair_idx = BTN_PAIR_XY; + *is_first = true; + break; + case ALLY_BTN_Y: + *pair_idx = BTN_PAIR_XY; + *is_first = false; + break; + case ALLY_BTN_VIEW: + *pair_idx = BTN_PAIR_VIEW_MENU; + *is_first = true; + break; + case ALLY_BTN_MENU: + *pair_idx = BTN_PAIR_VIEW_MENU; + *is_first = false; + break; + case ALLY_BTN_M1: + *pair_idx = BTN_PAIR_M1M2; + *is_first = true; + break; + case ALLY_BTN_M2: + *pair_idx = BTN_PAIR_M1M2; + *is_first = false; + break; + default: + return -EINVAL; + } + + return 0; +} + +static ssize_t button_remap_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct button_remap_attr *btn_attr = to_button_remap_attr(attr); + struct ally_config *cfg = ally->config; + enum ally_button_id button_id = btn_attr->button_id; + enum btn_pair_index pair_idx; + bool is_first; + struct button_pair_map *pair; + struct button_map *btn_map; + int ret; + + if (!cfg) + return -ENODEV; + + ret = get_button_pair_info(button_id, &pair_idx, &is_first); + if (ret < 0) + return ret; + + mutex_lock(&cfg->config_mutex); + pair = &((struct ally_button_mapping + *)(cfg->button_mappings))[cfg->gamepad_mode] + .button_pairs[pair_idx - 1]; + btn_map = is_first ? &pair->first : &pair->second; + + if (btn_attr->is_macro) { + if (btn_map->macro->type == BTN_TYPE_NONE) + ret = sprintf(buf, "NONE\n"); + else + ret = sprintf(buf, "%s\n", btn_map->macro->name); + } else { + if (btn_map->remap->type == BTN_TYPE_NONE) + ret = sprintf(buf, "NONE\n"); + else + ret = sprintf(buf, "%s\n", btn_map->remap->name); + } + mutex_unlock(&cfg->config_mutex); + + return ret; +} + +static ssize_t button_remap_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct button_remap_attr *btn_attr = to_button_remap_attr(attr); + struct ally_config *cfg = ally->config; + enum ally_button_id button_id = btn_attr->button_id; + enum btn_pair_index pair_idx; + bool is_first; + struct button_pair_map *pair; + struct button_map *btn_map; + char btn_name[32]; + const struct btn_code_map *code; + int ret; + + if (!cfg) + return -ENODEV; + + if (sscanf(buf, "%31s", btn_name) != 1) + return -EINVAL; + + /* Handle "NONE" specially */ + if (strcmp(btn_name, "NONE") == 0) { + code = &ally_btn_codes[0]; /* NONE entry */ + } else { + code = find_button_by_name(btn_name); + if (!code) + return -EINVAL; + } + + ret = get_button_pair_info(button_id, &pair_idx, &is_first); + if (ret < 0) + return ret; + + mutex_lock(&cfg->config_mutex); + /* Access the mapping for current gamepad mode */ + pair = &((struct ally_button_mapping + *)(cfg->button_mappings))[cfg->gamepad_mode] + .button_pairs[pair_idx - 1]; + btn_map = is_first ? &pair->first : &pair->second; + + if (btn_attr->is_macro) { + btn_map->macro = (struct btn_code_map *)code; + } else { + btn_map->remap = (struct btn_code_map *)code; + } + + /* Update pair index */ + pair->pair_index = pair_idx; + + /* Send mapping to device */ + ret = ally_set_button_mapping(hdev, ally, pair); + mutex_unlock(&cfg->config_mutex); + + if (ret < 0) + return ret; + + return count; +} + +/* Helper to create button remap attribute */ +static struct button_remap_attr *button_remap_attr_create(enum ally_button_id button_id, bool is_macro) +{ + struct button_remap_attr *attr; + + attr = kzalloc(sizeof(*attr), GFP_KERNEL); + if (!attr) + return NULL; + + attr->button_id = button_id; + attr->is_macro = is_macro; + sysfs_attr_init(&attr->dev_attr.attr); + attr->dev_attr.attr.name = is_macro ? "macro" : "remap"; + attr->dev_attr.attr.mode = 0644; + attr->dev_attr.show = button_remap_show; + attr->dev_attr.store = button_remap_store; + + return attr; +} + +/* Structure to hold button sysfs information */ +struct button_sysfs_entry { + struct attribute_group group; + struct attribute *attrs[4]; /* turbo + remap + macro + NULL terminator */ + struct button_turbo_attr *turbo_attr; + struct button_remap_attr *remap_attr; + struct button_remap_attr *macro_attr; +}; + +static void ally_set_default_gamepad_mapping(struct ally_button_mapping *mappings) +{ + struct ally_button_mapping *map = &mappings[ALLY_GAMEPAD_MODE_GAMEPAD]; + int i; + + /* Set all pair indexes and initialize to NONE */ + for (i = 0; i < 9; i++) { + map->button_pairs[i].pair_index = i + 1; + map->button_pairs[i].first.remap = + (struct btn_code_map *)&ally_btn_codes[0]; + map->button_pairs[i].first.macro = + (struct btn_code_map *)&ally_btn_codes[0]; + map->button_pairs[i].second.remap = + (struct btn_code_map *)&ally_btn_codes[0]; + map->button_pairs[i].second.macro = + (struct btn_code_map *)&ally_btn_codes[0]; + } + + /* Set direct mappings using array indices */ + map->button_pairs[BTN_PAIR_AB - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[1]; /* PAD_A */ + map->button_pairs[BTN_PAIR_AB - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[2]; /* PAD_B */ + + map->button_pairs[BTN_PAIR_XY - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[3]; /* PAD_X */ + map->button_pairs[BTN_PAIR_XY - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[4]; /* PAD_Y */ + + map->button_pairs[BTN_PAIR_BUMPER_LR - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[5]; /* PAD_LB */ + map->button_pairs[BTN_PAIR_BUMPER_LR - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[6]; /* PAD_RB */ + + map->button_pairs[BTN_PAIR_STICK_LR - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[7]; /* PAD_LS */ + map->button_pairs[BTN_PAIR_STICK_LR - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[8]; /* PAD_RS */ + + map->button_pairs[BTN_PAIR_DPAD_UPDOWN - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[9]; /* PAD_DPAD_UP */ + map->button_pairs[BTN_PAIR_DPAD_UPDOWN - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[10]; /* PAD_DPAD_DOWN */ + + map->button_pairs[BTN_PAIR_DPAD_LEFTRIGHT - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[11]; /* PAD_DPAD_LEFT */ + map->button_pairs[BTN_PAIR_DPAD_LEFTRIGHT - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[12]; /* PAD_DPAD_RIGHT */ + + map->button_pairs[BTN_PAIR_TRIGGER_LR - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[13]; /* PAD_LT */ + map->button_pairs[BTN_PAIR_TRIGGER_LR - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[14]; /* PAD_RT */ + + map->button_pairs[BTN_PAIR_VIEW_MENU - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[15]; /* PAD_VIEW */ + map->button_pairs[BTN_PAIR_VIEW_MENU - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[16]; /* PAD_MENU */ + + map->button_pairs[BTN_PAIR_M1M2 - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[19]; /* KB_M1 */ + map->button_pairs[BTN_PAIR_M1M2 - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[18]; /* KB_M2 */ +} + +static void ally_set_default_keyboard_mapping(struct ally_button_mapping *mappings) +{ + struct ally_button_mapping *map = &mappings[ALLY_GAMEPAD_MODE_KEYBOARD]; + int i; + + /* Set all pair indexes and initialize to NONE */ + for (i = 0; i < 9; i++) { + map->button_pairs[i].pair_index = i + 1; + map->button_pairs[i].first.remap = + (struct btn_code_map *)&ally_btn_codes[0]; + map->button_pairs[i].first.macro = + (struct btn_code_map *)&ally_btn_codes[0]; + map->button_pairs[i].second.remap = + (struct btn_code_map *)&ally_btn_codes[0]; + map->button_pairs[i].second.macro = + (struct btn_code_map *)&ally_btn_codes[0]; + } + + /* Set direct mappings using array indices */ + map->button_pairs[BTN_PAIR_AB - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[1]; /* PAD_A */ + map->button_pairs[BTN_PAIR_AB - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[2]; /* PAD_B */ + + map->button_pairs[BTN_PAIR_XY - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[3]; /* PAD_X */ + map->button_pairs[BTN_PAIR_XY - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[4]; /* PAD_Y */ + + map->button_pairs[BTN_PAIR_BUMPER_LR - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[5]; /* PAD_LB */ + map->button_pairs[BTN_PAIR_BUMPER_LR - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[6]; /* PAD_RB */ + + map->button_pairs[BTN_PAIR_STICK_LR - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[7]; /* PAD_LS */ + map->button_pairs[BTN_PAIR_STICK_LR - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[8]; /* PAD_RS */ + + map->button_pairs[BTN_PAIR_DPAD_UPDOWN - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[9]; /* PAD_DPAD_UP */ + map->button_pairs[BTN_PAIR_DPAD_UPDOWN - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[10]; /* PAD_DPAD_DOWN */ + + map->button_pairs[BTN_PAIR_DPAD_LEFTRIGHT - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[11]; /* PAD_DPAD_LEFT */ + map->button_pairs[BTN_PAIR_DPAD_LEFTRIGHT - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[12]; /* PAD_DPAD_RIGHT */ + + map->button_pairs[BTN_PAIR_TRIGGER_LR - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[13]; /* PAD_LT */ + map->button_pairs[BTN_PAIR_TRIGGER_LR - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[14]; /* PAD_RT */ + + map->button_pairs[BTN_PAIR_VIEW_MENU - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[15]; /* PAD_VIEW */ + map->button_pairs[BTN_PAIR_VIEW_MENU - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[16]; /* PAD_MENU */ + + map->button_pairs[BTN_PAIR_M1M2 - 1].first.remap = + (struct btn_code_map *)&ally_btn_codes[19]; /* KB_M1 */ + map->button_pairs[BTN_PAIR_M1M2 - 1].second.remap = + (struct btn_code_map *)&ally_btn_codes[18]; /* KB_M2 */ +} + +/** + * ally_create_button_attributes - Create button attributes + * @hdev: HID device + * @cfg: Ally config structure + * + * Returns: 0 on success, negative on failure + */ +static int ally_create_button_attributes(struct hid_device *hdev, + struct ally_config *cfg) +{ + struct button_sysfs_entry *entries; + int i, j, ret; + struct ally_button_mapping *mappings; + + entries = devm_kcalloc(&hdev->dev, ALLY_BTN_MAX, sizeof(*entries), + GFP_KERNEL); + if (!entries) + return -ENOMEM; + + /* Allocate mappings for each gamepad mode (1-based indexing) */ + mappings = devm_kcalloc(&hdev->dev, ALLY_GAMEPAD_MODE_KEYBOARD + 1, + sizeof(*mappings), GFP_KERNEL); + if (!mappings) { + ret = -ENOMEM; + goto err_free_entries; + } + + cfg->button_entries = entries; + cfg->button_mappings = mappings; + ally_set_default_gamepad_mapping(mappings); + ally_set_default_keyboard_mapping(mappings); + + for (i = 0; i < ALLY_BTN_MAX; i++) { + if (cfg->turbo_support) { + entries[i].turbo_attr = button_turbo_attr_create(i); + if (!entries[i].turbo_attr) { + ret = -ENOMEM; + goto err_cleanup; + } + } + + entries[i].remap_attr = button_remap_attr_create(i, false); + if (!entries[i].remap_attr) { + ret = -ENOMEM; + goto err_cleanup; + } + + entries[i].macro_attr = button_remap_attr_create(i, true); + if (!entries[i].macro_attr) { + ret = -ENOMEM; + goto err_cleanup; + } + + /* Set up attributes array based on what's supported */ + if (cfg->turbo_support) { + entries[i].attrs[0] = + &entries[i].turbo_attr->dev_attr.attr; + entries[i].attrs[1] = + &entries[i].remap_attr->dev_attr.attr; + entries[i].attrs[2] = + &entries[i].macro_attr->dev_attr.attr; + entries[i].attrs[3] = NULL; + } else { + entries[i].attrs[0] = + &entries[i].remap_attr->dev_attr.attr; + entries[i].attrs[1] = + &entries[i].macro_attr->dev_attr.attr; + entries[i].attrs[2] = NULL; + } + + entries[i].group.name = ally_button_names[i]; + entries[i].group.attrs = entries[i].attrs; + + ret = sysfs_create_group(&hdev->dev.kobj, &entries[i].group); + if (ret < 0) { + hid_err(hdev, + "Failed to create sysfs group for %s: %d\n", + ally_button_names[i], ret); + goto err_cleanup; + } + } + + return 0; + +err_cleanup: + while (--i >= 0) { + sysfs_remove_group(&hdev->dev.kobj, &entries[i].group); + if (entries[i].turbo_attr) + kfree(entries[i].turbo_attr); + if (entries[i].remap_attr) + kfree(entries[i].remap_attr); + if (entries[i].macro_attr) + kfree(entries[i].macro_attr); + } + +err_free_entries: + if (mappings) + devm_kfree(&hdev->dev, mappings); + devm_kfree(&hdev->dev, entries); + return ret; +} + +/** + * ally_remove_button_attributes - Remove button attributes + * @hdev: HID device + * @cfg: Ally config structure + */ +static void ally_remove_button_attributes(struct hid_device *hdev, + struct ally_config *cfg) +{ + struct button_sysfs_entry *entries; + int i; + + if (!cfg || !cfg->button_entries) + return; + + entries = cfg->button_entries; + + /* Remove all attribute groups */ + for (i = 0; i < ALLY_BTN_MAX; i++) { + sysfs_remove_group(&hdev->dev.kobj, &entries[i].group); + if (entries[i].turbo_attr) + kfree(entries[i].turbo_attr); + if (entries[i].remap_attr) + kfree(entries[i].remap_attr); + if (entries[i].macro_attr) + kfree(entries[i].macro_attr); + } + + if (cfg->button_mappings) + devm_kfree(&hdev->dev, cfg->button_mappings); + devm_kfree(&hdev->dev, entries); +} + +/** + * ally_config_create - Initialize configuration and create sysfs entries + * @hdev: HID device + * @ally: Ally device data + * + * Returns 0 on success, negative error code on failure + */ +int ally_config_create(struct hid_device *hdev, struct ally_handheld *ally) +{ + struct ally_config *cfg; + int ret, i; + + if (!hdev || !ally) + return -EINVAL; + + if (get_endpoint_address(hdev) != HID_ALLY_INTF_CFG_IN) + return 0; + + cfg = devm_kzalloc(&hdev->dev, sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + cfg->hdev = hdev; + + ally->config = cfg; + + ret = ally_detect_capabilities(hdev, cfg); + if (ret < 0) { + hid_err(hdev, "Failed to detect Ally capabilities: %d\n", ret); + goto err_free; + } + + /* Create all attribute groups */ + for (i = 0; i < ARRAY_SIZE(ally_attr_groups); i++) { + ret = sysfs_create_group(&hdev->dev.kobj, &ally_attr_groups[i]); + if (ret < 0) { + hid_err(hdev, "Failed to create sysfs group '%s': %d\n", + ally_attr_groups[i].name, ret); + /* Remove any groups already created */ + while (--i >= 0) + sysfs_remove_group(&hdev->dev.kobj, + &ally_attr_groups[i]); + goto err_free; + } + } + + if (cfg->turbo_support) { + ret = ally_create_button_attributes(hdev, cfg); + if (ret < 0) { + hid_err(hdev, "Failed to create button attributes: %d\n", ret); + for (i = 0; i < ARRAY_SIZE(ally_attr_groups); i++) + sysfs_remove_group(&hdev->dev.kobj, &ally_attr_groups[i]); + goto err_free; + } + } + + ret = ally_set_default_gamepad_mode(hdev, cfg); + if (ret < 0) + hid_warn(hdev, "Failed to set default gamepad mode: %d\n", ret); + + cfg->gamepad_mode = 0x01; + cfg->left_deadzone = 10; + cfg->left_outer_threshold = 90; + cfg->right_deadzone = 10; + cfg->right_outer_threshold = 90; + + cfg->vibration_intensity_left = 100; + cfg->vibration_intensity_right = 100; + cfg->vibration_active = false; + + /* Initialize default response curve values (linear) */ + cfg->left_curve.entry_1.move = 0; + cfg->left_curve.entry_1.resp = 0; + cfg->left_curve.entry_2.move = 33; + cfg->left_curve.entry_2.resp = 33; + cfg->left_curve.entry_3.move = 66; + cfg->left_curve.entry_3.resp = 66; + cfg->left_curve.entry_4.move = 100; + cfg->left_curve.entry_4.resp = 100; + + cfg->right_curve.entry_1.move = 0; + cfg->right_curve.entry_1.resp = 0; + cfg->right_curve.entry_2.move = 33; + cfg->right_curve.entry_2.resp = 33; + cfg->right_curve.entry_3.move = 66; + cfg->right_curve.entry_3.resp = 66; + cfg->right_curve.entry_4.move = 100; + cfg->right_curve.entry_4.resp = 100; + + // ONLY FOR ALLY 1 + if (cfg->xbox_controller_support) { + ret = ally_set_xbox_controller(hdev, cfg, true); + if (ret < 0) + hid_warn( + hdev, + "Failed to set default Xbox controller mode: %d\n", + ret); + } + + cfg->initialized = true; + hid_info(hdev, "Ally configuration system initialized successfully\n"); + + return 0; + +err_free: + ally->config = NULL; + devm_kfree(&hdev->dev, cfg); + return ret; +} + +/** + * ally_config_remove - Clean up configuration resources + * @hdev: HID device + * @ally: Ally device data + */ +void ally_config_remove(struct hid_device *hdev, struct ally_handheld *ally) +{ + struct ally_config *cfg; + int i; + + if (!ally) + return; + + cfg = ally->config; + if (!cfg || !cfg->initialized) + return; + + if (get_endpoint_address(hdev) != HID_ALLY_INTF_CFG_IN) + return; + + if (cfg->turbo_support && cfg->button_entries) + ally_remove_button_attributes(hdev, cfg); + + /* Remove all attribute groups in reverse order */ + for (i = ARRAY_SIZE(ally_attr_groups) - 1; i >= 0; i--) + sysfs_remove_group(&hdev->dev.kobj, &ally_attr_groups[i]); + + ally->config = NULL; + + hid_info(hdev, "Ally configuration system removed\n"); +} diff --git a/drivers/hid/asus-ally-hid/asus-ally-hid-core.c b/drivers/hid/asus-ally-hid/asus-ally-hid-core.c new file mode 100644 index 000000000000..1e8f98b69332 --- /dev/null +++ b/drivers/hid/asus-ally-hid/asus-ally-hid-core.c @@ -0,0 +1,600 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Asus ROG laptops and Ally + * + * Copyright (c) 2023 Luke Jones + */ + +#include "linux/mutex.h" +#include "linux/stddef.h" +#include "linux/types.h" +#include + +#include "../hid-ids.h" +#include "asus-ally.h" + +#define READY_MAX_TRIES 3 + +static const u8 EC_INIT_STRING[] = { 0x5A, 'A', 'S', 'U', 'S', ' ', 'T', 'e','c', 'h', '.', 'I', 'n', 'c', '.', '\0' }; +static const u8 FORCE_FEEDBACK_OFF[] = { 0x0D, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB }; + +static const struct hid_device_id rog_ally_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X) }, + {} +}; + +const char * ally_keyboard_name = "ROG Ally Keyboard"; +const char * ally_mouse_name = "ROG Ally Mouse"; + +/* Changes to ally_drvdata must lock */ +static DEFINE_MUTEX(ally_data_mutex); +static struct ally_handheld ally_drvdata = { + .cfg_hdev = NULL, + .led_rgb_dev = NULL, + .ally_x_input = NULL, +}; + +static inline int asus_dev_set_report(struct hid_device *hdev, const u8 *buf, size_t len) +{ + unsigned char *dmabuf; + int ret; + + dmabuf = kmemdup(buf, len, GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, buf[0], dmabuf, len, HID_FEATURE_REPORT, + HID_REQ_SET_REPORT); + kfree(dmabuf); + + return ret; +} + +static inline int asus_dev_get_report(struct hid_device *hdev, u8 *out, size_t len) +{ + return hid_hw_raw_request(hdev, HID_ALLY_GET_REPORT_ID, out, len, + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); +} + +/** + * ally_gamepad_send_packet - Send a raw packet to the gamepad device. + * + * @ally: ally handheld structure + * @hdev: hid device + * @buf: Buffer containing the packet data + * @len: Length of data to send + * + * Return: count of data transferred, negative if error + */ +int ally_gamepad_send_packet(struct ally_handheld *ally, + struct hid_device *hdev, const u8 *buf, size_t len) +{ + int ret; + + mutex_lock(&ally->intf_mutex); + ret = asus_dev_set_report(hdev, buf, len); + mutex_unlock(&ally->intf_mutex); + + return ret; +} + +/** + * ally_gamepad_send_receive_packet - Send packet and receive response. + * + * @ally: ally handheld structure + * @hdev: hid device + * @buf: Buffer containing the packet data to send and receive response in + * @len: Length of buffer + * + * Return: count of data transferred, negative if error + */ +int ally_gamepad_send_receive_packet(struct ally_handheld *ally, + struct hid_device *hdev, u8 *buf, + size_t len) +{ + int ret; + + mutex_lock(&ally->intf_mutex); + ret = asus_dev_set_report(hdev, buf, len); + if (ret >= 0) { + memset(buf, 0, len); + ret = asus_dev_get_report(hdev, buf, len); + } + mutex_unlock(&ally->intf_mutex); + + return ret; +} + +/** + * ally_gamepad_send_one_byte_packet - Send a one-byte payload packet. + * + * @ally: ally handheld structure + * @hdev: hid device + * @command: Command code + * @param: Parameter byte + * + * Return: count of data transferred, negative if error + */ +int ally_gamepad_send_one_byte_packet(struct ally_handheld *ally, + struct hid_device *hdev, + enum ally_command_codes command, u8 param) +{ + u8 *packet; + int ret; + + packet = kzalloc(HID_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!packet) + return -ENOMEM; + + packet[0] = HID_ALLY_SET_REPORT_ID; + packet[1] = HID_ALLY_FEATURE_CODE_PAGE; + packet[2] = command; + packet[3] = 0x01; /* Length */ + packet[4] = param; + + ret = ally_gamepad_send_packet(ally, hdev, packet, + HID_ALLY_REPORT_SIZE); + kfree(packet); + return ret; +} + +/** + * ally_gamepad_send_two_byte_packet - Send a two-byte payload packet. + * + * @ally: ally handheld structure + * @hdev: hid device + * @command: Command code + * @param1: First parameter byte + * @param2: Second parameter byte + * + * Return: count of data transferred, negative if error + */ +int ally_gamepad_send_two_byte_packet(struct ally_handheld *ally, + struct hid_device *hdev, + enum ally_command_codes command, + u8 param1, u8 param2) +{ + u8 *packet; + int ret; + + packet = kzalloc(HID_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!packet) + return -ENOMEM; + + packet[0] = HID_ALLY_SET_REPORT_ID; + packet[1] = HID_ALLY_FEATURE_CODE_PAGE; + packet[2] = command; + packet[3] = 0x02; /* Length */ + packet[4] = param1; + packet[5] = param2; + + ret = ally_gamepad_send_packet(ally, hdev, packet, + HID_ALLY_REPORT_SIZE); + kfree(packet); + return ret; +} + +/* + * This should be called before any remapping attempts, and on driver init/resume. + */ +int ally_gamepad_check_ready(struct hid_device *hdev) +{ + int ret, count; + u8 *hidbuf; + struct ally_handheld *ally = hid_get_drvdata(hdev); + + hidbuf = kzalloc(HID_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + ret = 0; + for (count = 0; count < READY_MAX_TRIES; count++) { + hidbuf[0] = HID_ALLY_SET_REPORT_ID; + hidbuf[1] = HID_ALLY_FEATURE_CODE_PAGE; + hidbuf[2] = CMD_CHECK_READY; + hidbuf[3] = 01; + + ret = ally_gamepad_send_receive_packet(ally, hdev, hidbuf, + HID_ALLY_REPORT_SIZE); + if (ret < 0) { + hid_err(hdev, "ROG Ally check failed: %d\n", ret); + continue; + } + + ret = hidbuf[2] == CMD_CHECK_READY; + if (ret) + break; + usleep_range(1000, 2000); + } + + if (count == READY_MAX_TRIES) + hid_warn(hdev, "ROG Ally never responded with a ready\n"); + + kfree(hidbuf); + return ret; +} + +u8 get_endpoint_address(struct hid_device *hdev) +{ + struct usb_host_endpoint *ep; + struct usb_interface *intf; + + intf = to_usb_interface(hdev->dev.parent); + if (!intf || !intf->cur_altsetting) + return -ENODEV; + + ep = intf->cur_altsetting->endpoint; + if (!ep) + return -ENODEV; + + return ep->desc.bEndpointAddress; +} + +/**************************************************************************************************/ +/* ROG Ally driver init */ +/**************************************************************************************************/ + +static int cad_sequence_state = 0; +static unsigned long cad_last_event_time = 0; + +/* Ally left buton emits a sequence of events: ctrl+alt+del. Capture this and emit only a single code */ +static bool handle_ctrl_alt_del(struct hid_device *hdev, u8 *data, int size) +{ + if (size < 16 || data[0] != 0x01) + return false; + + if (cad_sequence_state > 0 && time_after(jiffies, cad_last_event_time + msecs_to_jiffies(100))) + cad_sequence_state = 0; + + cad_last_event_time = jiffies; + + switch (cad_sequence_state) { + case 0: + if (data[1] == 0x01 && data[2] == 0x00 && data[3] == 0x00) { + cad_sequence_state = 1; + data[1] = 0x00; + return true; + } + break; + case 1: + if (data[1] == 0x05 && data[2] == 0x00 && data[3] == 0x00) { + cad_sequence_state = 2; + data[1] = 0x00; + return true; + } + break; + case 2: + if (data[1] == 0x05 && data[2] == 0x00 && data[3] == 0x4c) { + cad_sequence_state = 3; + data[1] = 0x00; + data[3] = 0x6F; // F20; + return true; + } + break; + case 3: + if (data[1] == 0x04 && data[2] == 0x00 && data[3] == 0x4c) { + cad_sequence_state = 4; + data[1] = 0x00; + data[1] = data[3] = 0x00; + return true; + } + break; + case 4: + if (data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x4c) { + cad_sequence_state = 5; + data[3] = 0x00; + return true; + } + break; + } + cad_sequence_state = 0; + return false; +} + +static bool handle_ally_event(struct hid_device *hdev, u8 *data, int size) +{ + struct input_dev *keyboard_input; + int keycode = 0; + + if (data[0] == 0x5A) { + switch (data[1]) { + case 0x38: + keycode = KEY_F19; + break; + case 0xA6: + keycode = KEY_F16; + break; + case 0xA7: + keycode = KEY_F17; + break; + case 0xA8: + keycode = KEY_F18; + break; + default: + return false; + } + + keyboard_input = ally_drvdata.keyboard_input; + if (keyboard_input) { + input_report_key(keyboard_input, keycode, 1); + input_sync(keyboard_input); + input_report_key(keyboard_input, keycode, 0); + input_sync(keyboard_input); + return true; + } + + memset(data, 0, size); + } + return false; +} + +static int ally_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, + int size) +{ + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct ally_x_input *ally_x; + int ep; + + if (!ally) + return -ENODEV; + + ep = get_endpoint_address(hdev); + if (ep != HID_ALLY_INTF_CFG_IN && ep != HID_ALLY_X_INTF_IN && ep != HID_ALLY_KEYBOARD_INTF_IN) + return 0; + + ally_x = ally->ally_x_input; + if (ally_x) { + if ((hdev->bus == BUS_USB && report->id == HID_ALLY_X_INPUT_REPORT && + size == HID_ALLY_X_INPUT_REPORT_SIZE) || + (data[0] == 0x5A)) { + if (ally_x_raw_event(ally_x, report, data, size)) + return 0; + } + } + + switch (ep) { + case HID_ALLY_INTF_CFG_IN: + if (handle_ally_event(hdev, data, size)) + return 0; + break; + case HID_ALLY_KEYBOARD_INTF_IN: + if (handle_ctrl_alt_del(hdev, data, size)) + return 0; + break; + } + + return 0; +} + +static int ally_hid_init(struct hid_device *hdev) +{ + int ret; + struct ally_handheld *ally = hid_get_drvdata(hdev); + + ret = ally_gamepad_send_packet(ally, hdev, EC_INIT_STRING, sizeof(EC_INIT_STRING)); + if (ret < 0) { + hid_err(hdev, "Ally failed to send init command: %d\n", ret); + goto cleanup; + } + + /* All gamepad configuration commands must go after the ally_gamepad_check_ready() */ + ret = ally_gamepad_check_ready(hdev); + if (ret < 0) + goto cleanup; + + ret = ally_gamepad_send_packet(ally, hdev, FORCE_FEEDBACK_OFF, sizeof(FORCE_FEEDBACK_OFF)); + if (ret < 0) + hid_err(hdev, "Ally failed to init force-feedback off: %d\n", ret); + +cleanup: + return ret; +} + +static int ally_input_configured(struct hid_device *hdev, struct hid_input *hi) +{ + int ep = get_endpoint_address(hdev); + + hid_info(hdev, "Input configured: endpoint 0x%02x, name: %s\n", ep, hi->input->name); + + if (ep == HID_ALLY_KEYBOARD_INTF_IN) + hi->input->name = ally_keyboard_name; + + if (ep == HID_ALLY_MOUSE_INTF_IN) + hi->input->name = ally_mouse_name; + + return 0; +} + +static int ally_hid_probe(struct hid_device *hdev, const struct hid_device_id *_id) +{ + int ret, ep; + + ep = get_endpoint_address(hdev); + if (ep < 0) + return ep; + + /*** CRITICAL START ***/ + mutex_lock(&ally_data_mutex); + if (ep == HID_ALLY_INTF_CFG_IN) + ally_drvdata.cfg_hdev = hdev; + if (ep == HID_ALLY_KEYBOARD_INTF_IN) + ally_drvdata.keyboard_hdev = hdev; + mutex_unlock(&ally_data_mutex); + /*** CRITICAL END ***/ + + hid_set_drvdata(hdev, &ally_drvdata); + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "Parse failed\n"); + return ret; + } + + if (ep == HID_ALLY_INTF_CFG_IN || ep == HID_ALLY_X_INTF_IN) { + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + } else if (ep == HID_ALLY_KEYBOARD_INTF_IN) { + ret = hid_hw_start(hdev, HID_CONNECT_HIDINPUT | HID_CONNECT_HIDRAW); + if (!list_empty(&hdev->inputs)) { + struct hid_input *hidinput = list_first_entry(&hdev->inputs, struct hid_input, list); + ally_drvdata.keyboard_input = hidinput->input; + } + hid_info(hdev, "Connected keyboard interface with input events\n"); + } else { + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + hid_info(hdev, "Passing through HID events for endpoint: 0x%02x\n", ep); + return 0; + } + + ret = hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "Failed to open HID device\n"); + goto err_stop; + } + + /* Initialize MCU even before alloc */ + ret = ally_hid_init(hdev); + if (ret < 0) + goto err_close; + + if (ep == HID_ALLY_INTF_CFG_IN) { + ret = ally_config_create(hdev, &ally_drvdata); + if (ret < 0) + hid_err(hdev, "Failed to create Ally configuration interface.\n"); + else + hid_info(hdev, "Created Ally configuration interface.\n"); + + ret = ally_rgb_create(hdev, &ally_drvdata); + if (ret < 0) + hid_err(hdev, "Failed to create Ally gamepad LEDs.\n"); + else + hid_info(hdev, "Created Ally RGB LED controls.\n"); + } + + if (ep == HID_ALLY_X_INTF_IN) { + ret = ally_x_create(hdev, &ally_drvdata); + if (ret < 0) { + hid_err(hdev, "Failed to create Ally X gamepad device.\n"); + ally_drvdata.ally_x_input = NULL; + goto err_close; + } else { + hid_info(hdev, "Created Ally X gamepad device.\n"); + } + // Not required since we send this inputs ep through the gamepad input dev + // if (drvdata.gamepad_cfg && drvdata.gamepad_cfg->input) { + // input_unregister_device(drvdata.gamepad_cfg->input); + // hid_info(hdev, "Ally X removed unrequired input dev.\n"); + // } + } + + return 0; + +err_close: + hid_hw_close(hdev); +err_stop: + hid_hw_stop(hdev); + return ret; +} + +static void ally_hid_remove(struct hid_device *hdev) +{ + struct ally_handheld *ally = hid_get_drvdata(hdev); + + if (!ally) + goto out; + + if (ally->led_rgb_dev) + ally_rgb_remove(hdev, ally); + + if (ally->config) + ally_config_remove(hdev, ally); + + if (ally->ally_x_input) + ally_x_remove(hdev, ally); + +out: + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static int ally_hid_reset_resume(struct hid_device *hdev) +{ + struct ally_handheld *ally = hid_get_drvdata(hdev); + int ret; + + if (!ally) + return -EINVAL; + + int ep = get_endpoint_address(hdev); + if (ep != HID_ALLY_INTF_CFG_IN) + return 0; + + ret = ally_hid_init(hdev); + if (ret < 0) + return ret; + + ally_rgb_resume(ally); + + return 0; +} + +static int ally_pm_thaw(struct device *dev) +{ + struct hid_device *hdev = to_hid_device(dev); + + if (!hdev) + return -EINVAL; + + return ally_hid_reset_resume(hdev); +} + +static int ally_pm_prepare(struct device *dev) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + + if (ally->led_rgb_dev) { + ally_rgb_store_settings(ally); + } + + return 0; +} + +static const struct dev_pm_ops ally_pm_ops = { + .thaw = ally_pm_thaw, + .prepare = ally_pm_prepare, +}; + +MODULE_DEVICE_TABLE(hid, rog_ally_devices); + +static struct hid_driver rog_ally_cfg = { .name = "asus_rog_ally", + .id_table = rog_ally_devices, + .probe = ally_hid_probe, + .remove = ally_hid_remove, + .raw_event = ally_raw_event, + .input_configured = ally_input_configured, + /* ALLy 1 requires this to reset device state correctly */ + .reset_resume = ally_hid_reset_resume, + .driver = { + .pm = &ally_pm_ops, + } +}; + +static int __init rog_ally_init(void) +{ + mutex_init(&ally_drvdata.intf_mutex); + return hid_register_driver(&rog_ally_cfg); +} + +static void __exit rog_ally_exit(void) +{ + mutex_destroy(&ally_drvdata.intf_mutex); + hid_unregister_driver(&rog_ally_cfg); +} + +module_init(rog_ally_init); +module_exit(rog_ally_exit); + +MODULE_AUTHOR("Luke D. Jones"); +MODULE_DESCRIPTION("HID Driver for ASUS ROG Ally handeheld."); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/asus-ally-hid/asus-ally-hid-input.c b/drivers/hid/asus-ally-hid/asus-ally-hid-input.c new file mode 100644 index 000000000000..4fc848d67c23 --- /dev/null +++ b/drivers/hid/asus-ally-hid/asus-ally-hid-input.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Asus ROG laptops and Ally + * + * Copyright (c) 2023 Luke Jones + */ + +#include "linux/delay.h" +#include "linux/input-event-codes.h" +#include +#include + +#include "asus-ally.h" + +struct ally_x_input_report { + uint16_t x, y; + uint16_t rx, ry; + uint16_t z, rz; + uint8_t buttons[4]; +} __packed; + +/* The hatswitch outputs integers, we use them to index this X|Y pair */ +static const int hat_values[][2] = { + { 0, 0 }, { 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 }, + { 0, 1 }, { -1, 1 }, { -1, 0 }, { -1, -1 }, +}; + +static void ally_x_work(struct work_struct *work) +{ + struct ally_x_input *ally_x = container_of(work, struct ally_x_input, output_worker); + struct ff_report *ff_report = NULL; + bool update_qam_chord = false; + bool update_ff = false; + unsigned long flags; + + spin_lock_irqsave(&ally_x->lock, flags); + update_qam_chord = ally_x->update_qam_chord; + + update_ff = ally_x->update_ff; + if (ally_x->update_ff) { + ff_report = kmemdup(ally_x->ff_packet, sizeof(*ally_x->ff_packet), GFP_KERNEL); + ally_x->update_ff = false; + } + spin_unlock_irqrestore(&ally_x->lock, flags); + + if (update_ff && ff_report) { + ff_report->ff.magnitude_left = ff_report->ff.magnitude_strong; + ff_report->ff.magnitude_right = ff_report->ff.magnitude_weak; + ally_gamepad_send_packet(ally_x->ally, ally_x->hdev, + (u8 *)ff_report, sizeof(*ff_report)); + } + kfree(ff_report); + + if (update_qam_chord) { + /* + * The sleeps here are required to allow steam to register the button combo. + */ + input_report_key(ally_x->input, BTN_MODE, 1); + input_sync(ally_x->input); + msleep(150); + input_report_key(ally_x->input, BTN_A, 1); + input_sync(ally_x->input); + input_report_key(ally_x->input, BTN_A, 0); + input_sync(ally_x->input); + input_report_key(ally_x->input, BTN_MODE, 0); + input_sync(ally_x->input); + + spin_lock_irqsave(&ally_x->lock, flags); + ally_x->update_qam_chord = false; + spin_unlock_irqrestore(&ally_x->lock, flags); + } +} + +static int ally_x_play_effect(struct input_dev *idev, void *data, struct ff_effect *effect) +{ + struct hid_device *hdev = input_get_drvdata(idev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct ally_x_input *ally_x = ally->ally_x_input; + unsigned long flags; + + if (effect->type != FF_RUMBLE) + return 0; + + spin_lock_irqsave(&ally_x->lock, flags); + ally_x->ff_packet->ff.magnitude_strong = effect->u.rumble.strong_magnitude / 512; + ally_x->ff_packet->ff.magnitude_weak = effect->u.rumble.weak_magnitude / 512; + ally_x->update_ff = true; + spin_unlock_irqrestore(&ally_x->lock, flags); + + if (ally_x->output_worker_initialized) + schedule_work(&ally_x->output_worker); + + return 0; +} + +/* Return true if event was handled, otherwise false */ +bool ally_x_raw_event(struct ally_x_input *ally_x, struct hid_report *report, u8 *data, + int size) +{ + struct ally_x_input_report *in_report; + unsigned long flags; + u8 byte; + + if (data[0] == 0x0B) { + in_report = (struct ally_x_input_report *)&data[1]; + + input_report_abs(ally_x->input, ABS_X, in_report->x - 32768); + input_report_abs(ally_x->input, ABS_Y, in_report->y - 32768); + input_report_abs(ally_x->input, ABS_RX, in_report->rx - 32768); + input_report_abs(ally_x->input, ABS_RY, in_report->ry - 32768); + input_report_abs(ally_x->input, ABS_Z, in_report->z); + input_report_abs(ally_x->input, ABS_RZ, in_report->rz); + + byte = in_report->buttons[0]; + input_report_key(ally_x->input, BTN_A, byte & BIT(0)); + input_report_key(ally_x->input, BTN_B, byte & BIT(1)); + input_report_key(ally_x->input, BTN_X, byte & BIT(2)); + input_report_key(ally_x->input, BTN_Y, byte & BIT(3)); + input_report_key(ally_x->input, BTN_TL, byte & BIT(4)); + input_report_key(ally_x->input, BTN_TR, byte & BIT(5)); + input_report_key(ally_x->input, BTN_SELECT, byte & BIT(6)); + input_report_key(ally_x->input, BTN_START, byte & BIT(7)); + + byte = in_report->buttons[1]; + input_report_key(ally_x->input, BTN_THUMBL, byte & BIT(0)); + input_report_key(ally_x->input, BTN_THUMBR, byte & BIT(1)); + input_report_key(ally_x->input, BTN_MODE, byte & BIT(2)); + + byte = in_report->buttons[2]; + input_report_abs(ally_x->input, ABS_HAT0X, hat_values[byte][0]); + input_report_abs(ally_x->input, ABS_HAT0Y, hat_values[byte][1]); + input_sync(ally_x->input); + + return true; + } + /* + * The MCU used on Ally provides many devices: gamepad, keyboord, mouse, other. + * The AC and QAM buttons route through another interface making it difficult to + * use the events unless we grab those and use them here. Only works for Ally X. + */ + else if (data[0] == 0x5A) { + if (ally_x->qam_mode) { + spin_lock_irqsave(&ally_x->lock, flags); + /* Right Armoury Crate button */ + if (data[1] == 0x38 && !ally_x->update_qam_chord) { + ally_x->update_qam_chord = true; + if (ally_x->output_worker_initialized) + schedule_work(&ally_x->output_worker); + } + spin_unlock_irqrestore(&ally_x->lock, flags); + /* Left/XBox button */ + input_report_key(ally_x->input, BTN_MODE, data[1] == 0xA6); + } else { + /* Right Armoury Crate button */ + input_report_key(ally_x->input, KEY_PROG1, data[1] == 0x38); + /* Left/XBox button */ + input_report_key(ally_x->input, KEY_F16, data[1] == 0xA6); + } + /* QAM long press */ + input_report_key(ally_x->input, KEY_F17, data[1] == 0xA7); + /* QAM long press released */ + input_report_key(ally_x->input, KEY_F18, data[1] == 0xA8); + input_sync(ally_x->input); + + return data[1] == 0xA6 || data[1] == 0xA7 || data[1] == 0xA8 || data[1] == 0x38; + } + + return false; +} + +static struct input_dev *ally_x_alloc_input_dev(struct hid_device *hdev, + const char *name_suffix) +{ + struct input_dev *input_dev; + + input_dev = devm_input_allocate_device(&hdev->dev); + if (!input_dev) + return ERR_PTR(-ENOMEM); + + input_dev->id.bustype = hdev->bus; + input_dev->id.vendor = hdev->vendor; + input_dev->id.product = hdev->product; + input_dev->id.version = hdev->version; + input_dev->uniq = hdev->uniq; + input_dev->name = "ASUS ROG Ally X Gamepad"; + + input_set_drvdata(input_dev, hdev); + + return input_dev; +} + +static ssize_t ally_x_qam_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct ally_x_input *ally_x = ally->ally_x_input; + + if (!ally_x) + return -ENODEV; + + return sysfs_emit(buf, "%d\n", ally_x->qam_mode); +} + +static ssize_t ally_x_qam_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_handheld *ally = hid_get_drvdata(hdev); + struct ally_x_input *ally_x = ally->ally_x_input; + bool val; + int ret; + + if (!ally_x) + return -ENODEV; + + ret = kstrtobool(buf, &val); + if (ret < 0) + return ret; + + ally_x->qam_mode = val; + + return count; +} +ALLY_DEVICE_ATTR_RW(ally_x_qam_mode, qam_mode); + +static int ally_x_setup_input(struct hid_device *hdev, struct ally_x_input *ally_x) +{ + struct input_dev *input; + int ret; + + input = ally_x_alloc_input_dev(hdev, NULL); + if (IS_ERR(input)) + return PTR_ERR(input); + + input_set_abs_params(input, ABS_X, -32768, 32767, 0, 0); + input_set_abs_params(input, ABS_Y, -32768, 32767, 0, 0); + input_set_abs_params(input, ABS_RX, -32768, 32767, 0, 0); + input_set_abs_params(input, ABS_RY, -32768, 32767, 0, 0); + input_set_abs_params(input, ABS_Z, 0, 1023, 0, 0); + input_set_abs_params(input, ABS_RZ, 0, 1023, 0, 0); + input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0); + input_set_capability(input, EV_KEY, BTN_A); + input_set_capability(input, EV_KEY, BTN_B); + input_set_capability(input, EV_KEY, BTN_X); + input_set_capability(input, EV_KEY, BTN_Y); + input_set_capability(input, EV_KEY, BTN_TL); + input_set_capability(input, EV_KEY, BTN_TR); + input_set_capability(input, EV_KEY, BTN_SELECT); + input_set_capability(input, EV_KEY, BTN_START); + input_set_capability(input, EV_KEY, BTN_MODE); + input_set_capability(input, EV_KEY, BTN_THUMBL); + input_set_capability(input, EV_KEY, BTN_THUMBR); + + input_set_capability(input, EV_KEY, KEY_PROG1); + input_set_capability(input, EV_KEY, KEY_F16); + input_set_capability(input, EV_KEY, KEY_F17); + input_set_capability(input, EV_KEY, KEY_F18); + input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY); + input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY1); + + input_set_capability(input, EV_FF, FF_RUMBLE); + input_ff_create_memless(input, NULL, ally_x_play_effect); + + ret = input_register_device(input); + if (ret) { + input_unregister_device(input); + return ret; + } + + ally_x->input = input; + + return 0; +} + +int ally_x_create(struct hid_device *hdev, struct ally_handheld *ally) +{ + uint8_t max_output_report_size; + struct ally_x_input *ally_x; + struct ff_report *ff_report; + int ret; + + ally_x = devm_kzalloc(&hdev->dev, sizeof(*ally_x), GFP_KERNEL); + if (!ally_x) + return -ENOMEM; + + ally_x->hdev = hdev; + ally_x->ally = ally; + ally->ally_x_input = ally_x; + + max_output_report_size = sizeof(struct ally_x_input_report); + + ret = ally_x_setup_input(hdev, ally_x); + if (ret) + goto free_ally_x; + + INIT_WORK(&ally_x->output_worker, ally_x_work); + spin_lock_init(&ally_x->lock); + ally_x->output_worker_initialized = true; + ally_x->qam_mode = + true; + + ff_report = devm_kzalloc(&hdev->dev, sizeof(*ff_report), GFP_KERNEL); + if (!ff_report) { + ret = -ENOMEM; + goto free_ally_x; + } + + /* None of these bytes will change for the FF command for now */ + ff_report->report_id = 0x0D; + ff_report->ff.enable = 0x0F; /* Enable all by default */ + ff_report->ff.pulse_sustain_10ms = 0xFF; /* Duration */ + ff_report->ff.pulse_release_10ms = 0x00; /* Start Delay */ + ff_report->ff.loop_count = 0xEB; /* Loop Count */ + ally_x->ff_packet = ff_report; + + if (sysfs_create_file(&hdev->dev.kobj, &dev_attr_ally_x_qam_mode.attr)) { + ret = -ENODEV; + goto unregister_input; + } + + hid_info(hdev, "Registered Ally X controller using %s\n", + dev_name(&ally_x->input->dev)); + + return 0; + +unregister_input: + input_unregister_device(ally_x->input); +free_ally_x: + devm_kfree(&hdev->dev, ally_x); + return ret; +} + +void ally_x_remove(struct hid_device *hdev, struct ally_handheld *ally) +{ + if (ally->ally_x_input) { + sysfs_remove_file(&hdev->dev.kobj, &dev_attr_ally_x_qam_mode.attr); + + if (ally->ally_x_input->output_worker_initialized) + cancel_work_sync(&ally->ally_x_input->output_worker); + + ally->ally_x_input = NULL; + } +} diff --git a/drivers/hid/asus-ally-hid/asus-ally-rgb.c b/drivers/hid/asus-ally-hid/asus-ally-rgb.c new file mode 100644 index 000000000000..22aec39a7634 --- /dev/null +++ b/drivers/hid/asus-ally-hid/asus-ally-rgb.c @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Asus ROG laptops and Ally + * + * Copyright (c) 2025 Luke Jones + */ + +#include "asus-ally.h" +#include "linux/delay.h" + +static const u8 EC_MODE_LED_APPLY[] = { 0x5A, 0xB4, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; +static const u8 EC_MODE_LED_SET[] = { 0x5A, 0xB5, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + +static struct ally_rgb_resume_data resume_data; + +static void ally_rgb_schedule_work(struct ally_rgb_dev *led) +{ + unsigned long flags; + + if (!led) + return; + + spin_lock_irqsave(&led->lock, flags); + if (!led->removed) + schedule_work(&led->work); + spin_unlock_irqrestore(&led->lock, flags); +} + +/* + * The RGB still has the basic 0-3 level brightness. Since the multicolour + * brightness is being used in place, set this to max + */ +static int ally_rgb_set_bright_base_max(struct hid_device *hdev, struct ally_handheld *ally) +{ + u8 buf[] = { HID_ALLY_SET_RGB_REPORT_ID, 0xba, 0xc5, 0xc4, 0x02 }; + + return ally_gamepad_send_packet(ally, hdev, buf, sizeof(buf)); +} + +static void ally_rgb_do_work(struct work_struct *work) +{ + struct ally_rgb_dev *led = container_of(work, struct ally_rgb_dev, work); + unsigned long flags; + int ret; + + bool update_needed = false; + u8 red[4], green[4], blue[4]; + const int data_size = 12; /* 4 RGB zones × 3 colors */ + + u8 buf[16] = { [0] = HID_ALLY_SET_REPORT_ID, + [1] = HID_ALLY_FEATURE_CODE_PAGE, + [2] = CMD_LED_CONTROL, + [3] = data_size }; + + if (!led || !led->hdev) + return; + + spin_lock_irqsave(&led->lock, flags); + if (led->removed) { + spin_unlock_irqrestore(&led->lock, flags); + return; + } + + if (led->update_rgb) { + memcpy(red, led->red, sizeof(red)); + memcpy(green, led->green, sizeof(green)); + memcpy(blue, led->blue, sizeof(blue)); + led->update_rgb = false; + update_needed = true; + } + spin_unlock_irqrestore(&led->lock, flags); + + if (!update_needed) + return; + + for (int i = 0; i < 4; i++) { + buf[5 + i * 3] = green[i]; + buf[6 + i * 3] = blue[i]; + buf[4 + i * 3] = red[i]; + } + + ret = ally_gamepad_send_packet(led->ally, led->hdev, buf, sizeof(buf)); + if (ret < 0) + hid_err(led->hdev, "Ally failed to set gamepad backlight: %d\n", + ret); +} + +static void ally_rgb_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev; + struct ally_rgb_dev *led; + int intensity, bright; + unsigned long flags; + + mc_cdev = lcdev_to_mccdev(cdev); + if (!mc_cdev) + return; + + led = container_of(mc_cdev, struct ally_rgb_dev, led_rgb_dev); + if (!led) + return; + + led_mc_calc_color_components(mc_cdev, brightness); + + spin_lock_irqsave(&led->lock, flags); + + led->update_rgb = true; + bright = mc_cdev->led_cdev.brightness; + + for (int i = 0; i < 4; i++) { + intensity = mc_cdev->subled_info[i].intensity; + led->red[i] = (((intensity >> 16) & 0xFF) * bright) / 255; + led->green[i] = (((intensity >> 8) & 0xFF) * bright) / 255; + led->blue[i] = ((intensity & 0xFF) * bright) / 255; + } + + resume_data.initialized = true; + + spin_unlock_irqrestore(&led->lock, flags); + + ally_rgb_schedule_work(led); +} + +static int ally_rgb_set_static_from_multi(struct hid_device *hdev, + struct ally_handheld *ally, u8 r, + u8 g, u8 b) +{ + u8 buf[17] = { HID_ALLY_SET_RGB_REPORT_ID, 0xb3 }; + int ret; + + /* + * Set single zone single colour based on the first LED of EC software mode. + * buf[2] = zone, buf[3] = mode + */ + buf[4] = r; + buf[5] = g; + buf[6] = b; + + ret = ally_gamepad_send_packet(ally, hdev, buf, sizeof(buf)); + if (ret < 0) + return ret; + + ret = ally_gamepad_send_packet(ally, hdev, EC_MODE_LED_APPLY, + sizeof(EC_MODE_LED_APPLY)); + if (ret < 0) + return ret; + + return ally_gamepad_send_packet(ally, hdev, EC_MODE_LED_SET, + sizeof(EC_MODE_LED_SET)); +} + +/* + * Store the RGB values for restoring on resume, and set the static mode to the first LED colour +*/ +void ally_rgb_store_settings(struct ally_handheld *ally) +{ + struct ally_rgb_dev *led_rgb; + int arr_size; + u8 r = 0, g = 0, b = 0; + + led_rgb = ally->led_rgb_dev; + if (!led_rgb || !led_rgb->hdev) + return; + + arr_size = sizeof(resume_data.red); + + /* Take a snapshot of current settings with locking */ + spin_lock_irq(&led_rgb->lock); + resume_data.brightness = led_rgb->led_rgb_dev.led_cdev.brightness; + memcpy(resume_data.red, led_rgb->red, arr_size); + memcpy(resume_data.green, led_rgb->green, arr_size); + memcpy(resume_data.blue, led_rgb->blue, arr_size); + r = resume_data.red[0]; + g = resume_data.green[0]; + b = resume_data.blue[0]; + spin_unlock_irq(&led_rgb->lock); + + ally_rgb_set_static_from_multi(led_rgb->hdev, ally, r, g, b); +} + +static void ally_rgb_restore_settings(struct ally_handheld *ally, + struct led_classdev *led_cdev, + struct mc_subled *mc_led_info) +{ + struct ally_rgb_dev *led_rgb_dev; + unsigned long flags; + int arr_size; + + led_rgb_dev = ally->led_rgb_dev; + if (!led_rgb_dev) + return; + + arr_size = sizeof(resume_data.red); + + spin_lock_irqsave(&led_rgb_dev->lock, flags); + + memcpy(led_rgb_dev->red, resume_data.red, arr_size); + memcpy(led_rgb_dev->green, resume_data.green, arr_size); + memcpy(led_rgb_dev->blue, resume_data.blue, arr_size); + + for (int i = 0; i < 4; i++) { + mc_led_info[i].intensity = (resume_data.red[i] << 16) | + (resume_data.green[i] << 8) | + resume_data.blue[i]; + } + led_cdev->brightness = resume_data.brightness; + + spin_unlock_irqrestore(&led_rgb_dev->lock, flags); +} + +/* Set LEDs. Call after any setup. */ +void ally_rgb_resume(struct ally_handheld *ally) +{ + struct ally_rgb_dev *led_rgb; + struct led_classdev *led_cdev; + struct mc_subled *mc_led_info; + + led_rgb = ally->led_rgb_dev; + if (!led_rgb) + return; + + led_cdev = &led_rgb->led_rgb_dev.led_cdev; + mc_led_info = led_rgb->led_rgb_dev.subled_info; + if (!led_cdev || !mc_led_info) + return; + + if (resume_data.initialized) { + ally_rgb_restore_settings(ally, led_cdev, mc_led_info); + + spin_lock_irq(&led_rgb->lock); + led_rgb->update_rgb = true; + spin_unlock_irq(&led_rgb->lock); + + ally_rgb_schedule_work(led_rgb); + ally_rgb_set_bright_base_max(led_rgb->hdev, ally); + } +} + +static int ally_rgb_register(struct hid_device *hdev, + struct ally_rgb_dev *led_rgb) +{ + struct mc_subled *mc_led_info; + struct led_classdev *led_cdev; + int ret; + + if (!hdev || !led_rgb) + return -EINVAL; + + mc_led_info = devm_kmalloc_array(&hdev->dev, 4, sizeof(*mc_led_info), + GFP_KERNEL | __GFP_ZERO); + if (!mc_led_info) + return -ENOMEM; + + for (int i = 0; i < 4; i++) { + mc_led_info[i].color_index = LED_COLOR_ID_RGB; + } + + led_rgb->led_rgb_dev.subled_info = mc_led_info; + led_rgb->led_rgb_dev.num_colors = 4; + + led_cdev = &led_rgb->led_rgb_dev.led_cdev; + led_cdev->brightness = 128; + led_cdev->name = "ally:rgb:joystick_rings"; + led_cdev->max_brightness = 255; + led_cdev->brightness_set = ally_rgb_set; + + ret = devm_led_classdev_multicolor_register(&hdev->dev, + &led_rgb->led_rgb_dev); + if (ret < 0) + hid_err(hdev, "Failed to register RGB LED device: %d\n", ret); + + return ret; +} + +int ally_rgb_create(struct hid_device *hdev, struct ally_handheld *ally) +{ + struct ally_rgb_dev *led_rgb; + int ret; + + led_rgb = devm_kzalloc(&hdev->dev, sizeof(struct ally_rgb_dev), + GFP_KERNEL); + if (!led_rgb) + return -ENOMEM; + + led_rgb->ally = ally; + led_rgb->hdev = hdev; + led_rgb->removed = false; + INIT_WORK(&led_rgb->work, ally_rgb_do_work); + spin_lock_init(&led_rgb->lock); + + /* Set the pointer in ally structure BEFORE doing any operations that might use it */ + ally->led_rgb_dev = led_rgb; + + ret = ally_rgb_register(hdev, led_rgb); + if (ret < 0) { + hid_err(hdev, "Failed to register RGB LED device: %d\n", ret); + cancel_work_sync(&led_rgb->work); + ally->led_rgb_dev = NULL; /* Reset pointer on failure */ + devm_kfree(&hdev->dev, led_rgb); + return ret; + } + + led_rgb->output_worker_initialized = true; + + ret = ally_rgb_set_bright_base_max(hdev, ally); + if (ret < 0) + hid_warn(hdev, "Failed to set maximum base brightness: %d\n", + ret); + + if (resume_data.initialized) { + msleep(1500); + spin_lock_irq(&led_rgb->lock); + led_rgb->update_rgb = true; + spin_unlock_irq(&led_rgb->lock); + ally_rgb_schedule_work(led_rgb); + } + + return 0; +} + +void ally_rgb_remove(struct hid_device *hdev, struct ally_handheld *ally) +{ + struct ally_rgb_dev *led_rgb; + unsigned long flags; + int ep; + + ep = get_endpoint_address(hdev); + if (ep != HID_ALLY_INTF_CFG_IN) + return; + + led_rgb = ally->led_rgb_dev; + if (!led_rgb) + return; + + /* Mark as removed to prevent new work from being scheduled */ + spin_lock_irqsave(&led_rgb->lock, flags); + if (led_rgb->removed) { + spin_unlock_irqrestore(&led_rgb->lock, flags); + return; + } + led_rgb->removed = true; + led_rgb->output_worker_initialized = false; + spin_unlock_irqrestore(&led_rgb->lock, flags); + + cancel_work_sync(&led_rgb->work); + + devm_led_classdev_multicolor_unregister(&hdev->dev, + &led_rgb->led_rgb_dev); + + ally->led_rgb_dev = NULL; + + hid_info(hdev, "Removed Ally RGB interface"); +} diff --git a/drivers/hid/asus-ally-hid/asus-ally.h b/drivers/hid/asus-ally-hid/asus-ally.h new file mode 100644 index 000000000000..fd9c788a4a42 --- /dev/null +++ b/drivers/hid/asus-ally-hid/asus-ally.h @@ -0,0 +1,314 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * + * HID driver for Asus ROG laptops and Ally + * + * Copyright (c) 2023 Luke Jones + */ + + #ifndef __ASUS_ALLY_H + #define __ASUS_ALLY_H + +#include +#include +#include + +#define HID_ALLY_KEYBOARD_INTF_IN 0x81 +#define HID_ALLY_MOUSE_INTF_IN 0x82 +#define HID_ALLY_INTF_CFG_IN 0x83 +#define HID_ALLY_X_INTF_IN 0x87 + +#define HID_ALLY_REPORT_SIZE 64 +#define HID_ALLY_GET_REPORT_ID 0x0D +#define HID_ALLY_SET_REPORT_ID 0x5A +#define HID_ALLY_SET_RGB_REPORT_ID 0x5D +#define HID_ALLY_FEATURE_CODE_PAGE 0xD1 + +#define HID_ALLY_X_INPUT_REPORT 0x0B +#define HID_ALLY_X_INPUT_REPORT_SIZE 16 + +enum ally_command_codes { + CMD_SET_GAMEPAD_MODE = 0x01, + CMD_SET_MAPPING = 0x02, + CMD_SET_JOYSTICK_MAPPING = 0x03, + CMD_SET_JOYSTICK_DEADZONE = 0x04, + CMD_SET_TRIGGER_RANGE = 0x05, + CMD_SET_VIBRATION_INTENSITY = 0x06, + CMD_LED_CONTROL = 0x08, + CMD_CHECK_READY = 0x0A, + CMD_SET_XBOX_CONTROLLER = 0x0B, + CMD_CHECK_XBOX_SUPPORT = 0x0C, + CMD_USER_CAL_DATA = 0x0D, + CMD_CHECK_USER_CAL_SUPPORT = 0x0E, + CMD_SET_TURBO_PARAMS = 0x0F, + CMD_CHECK_TURBO_SUPPORT = 0x10, + CMD_CHECK_RESP_CURVE_SUPPORT = 0x12, + CMD_SET_RESP_CURVE = 0x13, + CMD_CHECK_DIR_TO_BTN_SUPPORT = 0x14, + CMD_SET_GYRO_PARAMS = 0x15, + CMD_CHECK_GYRO_TO_JOYSTICK = 0x16, + CMD_CHECK_ANTI_DEADZONE = 0x17, + CMD_SET_ANTI_DEADZONE = 0x18, +}; + +enum ally_gamepad_mode { + ALLY_GAMEPAD_MODE_GAMEPAD = 0x01, + ALLY_GAMEPAD_MODE_KEYBOARD = 0x02, +}; + +static const char *const gamepad_mode_names[] = { + [ALLY_GAMEPAD_MODE_GAMEPAD] = "gamepad", + [ALLY_GAMEPAD_MODE_KEYBOARD] = "keyboard" +}; + +/* Button identifiers for the attribute system */ +enum ally_button_id { + ALLY_BTN_A, + ALLY_BTN_B, + ALLY_BTN_X, + ALLY_BTN_Y, + ALLY_BTN_LB, + ALLY_BTN_RB, + ALLY_BTN_DU, + ALLY_BTN_DD, + ALLY_BTN_DL, + ALLY_BTN_DR, + ALLY_BTN_J0B, + ALLY_BTN_J1B, + ALLY_BTN_MENU, + ALLY_BTN_VIEW, + ALLY_BTN_M1, + ALLY_BTN_M2, + ALLY_BTN_MAX +}; + +/* Names for the button directories in sysfs */ +static const char *const ally_button_names[ALLY_BTN_MAX] = { + [ALLY_BTN_A] = "btn_a", + [ALLY_BTN_B] = "btn_b", + [ALLY_BTN_X] = "btn_x", + [ALLY_BTN_Y] = "btn_y", + [ALLY_BTN_LB] = "btn_lb", + [ALLY_BTN_RB] = "btn_rb", + [ALLY_BTN_DU] = "dpad_up", + [ALLY_BTN_DD] = "dpad_down", + [ALLY_BTN_DL] = "dpad_left", + [ALLY_BTN_DR] = "dpad_right", + [ALLY_BTN_J0B] = "btn_l3", + [ALLY_BTN_J1B] = "btn_r3", + [ALLY_BTN_MENU] = "btn_menu", + [ALLY_BTN_VIEW] = "btn_view", + [ALLY_BTN_M1] = "btn_m1", + [ALLY_BTN_M2] = "btn_m2", +}; + +struct ally_rgb_resume_data { + uint8_t brightness; + uint8_t red[4]; + uint8_t green[4]; + uint8_t blue[4]; + bool initialized; +}; + +struct ally_rgb_dev { + struct ally_handheld *ally; + struct hid_device *hdev; + struct led_classdev_mc led_rgb_dev; + struct work_struct work; + bool output_worker_initialized; + spinlock_t lock; + + bool removed; + bool update_rgb; + uint8_t red[4]; + uint8_t green[4]; + uint8_t blue[4]; +}; + +/* rumble packet structure */ +struct ff_data { + u8 enable; + u8 magnitude_left; + u8 magnitude_right; + u8 magnitude_strong; + u8 magnitude_weak; + u8 pulse_sustain_10ms; + u8 pulse_release_10ms; + u8 loop_count; +} __packed; + +struct ff_report { + u8 report_id; + struct ff_data ff; +} __packed; + +struct ally_x_input { + struct ally_handheld *ally; + struct input_dev *input; + struct hid_device *hdev; + spinlock_t lock; + + struct work_struct output_worker; + bool output_worker_initialized; + + /* Set if the left QAM emits Guide/Mode and right QAM emits Home + A chord */ + bool qam_mode; + /* Prevent multiple queued event due to the enforced delay in worker */ + bool update_qam_chord; + + struct ff_report *ff_packet; + bool update_ff; +}; + +struct resp_curve_param { + u8 move; + u8 resp; +} __packed; + +struct joystick_resp_curve { + struct resp_curve_param entry_1; + struct resp_curve_param entry_2; + struct resp_curve_param entry_3; + struct resp_curve_param entry_4; +} __packed; + +/* + * Button turbo parameters structure + * Each button can have: + * - turbo: Turbo press interval in multiple of 50ms (0 = disabled, 1-20 = 50ms-1000ms) + * - toggle: Toggle interval (0 = disabled) + */ +struct button_turbo_params { + u8 turbo; + u8 toggle; +} __packed; + +/* Collection of all button turbo settings */ +struct turbo_config { + struct button_turbo_params btn_du; /* D-pad Up */ + struct button_turbo_params btn_dd; /* D-pad Down */ + struct button_turbo_params btn_dl; /* D-pad Left */ + struct button_turbo_params btn_dr; /* D-pad Right */ + struct button_turbo_params btn_j0b; /* Left joystick button */ + struct button_turbo_params btn_j1b; /* Right joystick button */ + struct button_turbo_params btn_lb; /* Left bumper */ + struct button_turbo_params btn_rb; /* Right bumper */ + struct button_turbo_params btn_a; /* A button */ + struct button_turbo_params btn_b; /* B button */ + struct button_turbo_params btn_x; /* X button */ + struct button_turbo_params btn_y; /* Y button */ + struct button_turbo_params btn_view; /* View button */ + struct button_turbo_params btn_menu; /* Menu button */ + struct button_turbo_params btn_m2; /* M2 button */ + struct button_turbo_params btn_m1; /* M1 button */ +}; + +struct ally_config { + struct hid_device *hdev; + /* Must be locked if the data is being changed */ + struct mutex config_mutex; + bool initialized; + + /* Device capabilities flags */ + bool is_ally_x; + bool xbox_controller_support; + bool user_cal_support; + bool turbo_support; + bool resp_curve_support; + bool dir_to_btn_support; + bool gyro_support; + bool anti_deadzone_support; + + /* Current settings */ + bool xbox_controller_enabled; + u8 gamepad_mode; + u8 left_deadzone; + u8 left_outer_threshold; + u8 right_deadzone; + u8 right_outer_threshold; + u8 left_anti_deadzone; + u8 right_anti_deadzone; + u8 left_trigger_min; + u8 left_trigger_max; + u8 right_trigger_min; + u8 right_trigger_max; + + /* Vibration settings */ + u8 vibration_intensity_left; + u8 vibration_intensity_right; + bool vibration_active; + + struct turbo_config turbo; + struct button_sysfs_entry *button_entries; + void *button_mappings; /* ally_button_mapping array indexed by gamepad_mode */ + + struct joystick_resp_curve left_curve; + struct joystick_resp_curve right_curve; +}; + +struct ally_handheld { + /* All read/write to IN interfaces must lock */ + struct mutex intf_mutex; + struct hid_device *cfg_hdev; + + struct ally_rgb_dev *led_rgb_dev; + + struct ally_x_input *ally_x_input; + + struct hid_device *keyboard_hdev; + struct input_dev *keyboard_input; + + struct ally_config *config; +}; + +int ally_gamepad_send_packet(struct ally_handheld *ally, + struct hid_device *hdev, const u8 *buf, + size_t len); +int ally_gamepad_send_receive_packet(struct ally_handheld *ally, + struct hid_device *hdev, u8 *buf, + size_t len); +int ally_gamepad_send_one_byte_packet(struct ally_handheld *ally, + struct hid_device *hdev, + enum ally_command_codes command, + u8 param); +int ally_gamepad_send_two_byte_packet(struct ally_handheld *ally, + struct hid_device *hdev, + enum ally_command_codes command, + u8 param1, u8 param2); +int ally_gamepad_check_ready(struct hid_device *hdev); +u8 get_endpoint_address(struct hid_device *hdev); + +int ally_rgb_create(struct hid_device *hdev, struct ally_handheld *ally); +void ally_rgb_remove(struct hid_device *hdev, struct ally_handheld *ally); +void ally_rgb_store_settings(struct ally_handheld *ally); +void ally_rgb_resume(struct ally_handheld *ally); + +int ally_x_create(struct hid_device *hdev, struct ally_handheld *ally); +void ally_x_remove(struct hid_device *hdev, struct ally_handheld *ally); +bool ally_x_raw_event(struct ally_x_input *ally_x, struct hid_report *report, u8 *data, + int size); + +int ally_config_create(struct hid_device *hdev, struct ally_handheld *ally); +void ally_config_remove(struct hid_device *hdev, struct ally_handheld *ally); + +#define ALLY_DEVICE_ATTR_RW(_name, _sysfs_name) \ + struct device_attribute dev_attr_##_name = \ + __ATTR(_sysfs_name, 0644, _name##_show, _name##_store) + +#define ALLY_DEVICE_ATTR_RO(_name, _sysfs_name) \ + struct device_attribute dev_attr_##_name = \ + __ATTR(_sysfs_name, 0444, _name##_show, NULL) + +#define ALLY_DEVICE_ATTR_WO(_name, _sysfs_name) \ + struct device_attribute dev_attr_##_name = \ + __ATTR(_sysfs_name, 0200, NULL, _name##_store) + +#define ALLY_DEVICE_CONST_ATTR_RO(fname, sysfs_name, value) \ + static ssize_t fname##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return sprintf(buf, value); \ + } \ + struct device_attribute dev_attr_##fname = \ + __ATTR(sysfs_name, 0444, fname##_show, NULL) + +#endif /* __ASUS_ALLY_H */ diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index 46e3e42f9eb5..52882d6589e1 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -52,6 +52,10 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define FEATURE_KBD_LED_REPORT_ID1 0x5d #define FEATURE_KBD_LED_REPORT_ID2 0x5e +#define ROG_ALLY_REPORT_SIZE 64 +#define ROG_ALLY_X_MIN_MCU 313 +#define ROG_ALLY_MIN_MCU 319 + #define SUPPORT_KBD_BACKLIGHT BIT(0) #define MAX_TOUCH_MAJOR 8 @@ -84,6 +88,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define QUIRK_MEDION_E1239T BIT(10) #define QUIRK_ROG_NKEY_KEYBOARD BIT(11) #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12) +#define QUIRK_ROG_ALLY_XPAD BIT(13) #define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \ QUIRK_NO_INIT_REPORTS | \ @@ -534,9 +539,102 @@ static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev) return !!(value & ASUS_WMI_DSTS_PRESENCE_BIT); } +/* + * We don't care about any other part of the string except the version section. + * Example strings: FGA80100.RC72LA.312_T01, FGA80100.RC71LS.318_T01 + * The bytes "5a 05 03 31 00 1a 13" and possibly more come before the version + * string, and there may be additional bytes after the version string such as + * "75 00 74 00 65 00" or a postfix such as "_T01" + */ +static int mcu_parse_version_string(const u8 *response, size_t response_size) +{ + const u8 *end = response + response_size; + const u8 *p = response; + int dots, err, version; + char buf[4]; + + dots = 0; + while (p < end && dots < 2) { + if (*p++ == '.') + dots++; + } + + if (dots != 2 || p >= end || (p + 3) >= end) + return -EINVAL; + + memcpy(buf, p, 3); + buf[3] = '\0'; + + err = kstrtoint(buf, 10, &version); + if (err || version < 0) + return -EINVAL; + + return version; +} + +static int mcu_request_version(struct hid_device *hdev) +{ + u8 *response __free(kfree) = kzalloc(ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + const u8 request[] = { 0x5a, 0x05, 0x03, 0x31, 0x00, 0x20 }; + int ret; + + if (!response) + return -ENOMEM; + + ret = asus_kbd_set_report(hdev, request, sizeof(request)); + if (ret < 0) + return ret; + + ret = hid_hw_raw_request(hdev, FEATURE_REPORT_ID, response, + ROG_ALLY_REPORT_SIZE, HID_FEATURE_REPORT, + HID_REQ_GET_REPORT); + if (ret < 0) + return ret; + + ret = mcu_parse_version_string(response, ROG_ALLY_REPORT_SIZE); + if (ret < 0) { + pr_err("Failed to parse MCU version: %d\n", ret); + print_hex_dump(KERN_ERR, "MCU: ", DUMP_PREFIX_NONE, + 16, 1, response, ROG_ALLY_REPORT_SIZE, false); + } + + return ret; +} + +static void validate_mcu_fw_version(struct hid_device *hdev, int idProduct) +{ + int min_version, version; + + version = mcu_request_version(hdev); + if (version < 0) + return; + + switch (idProduct) { + case USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY: + min_version = ROG_ALLY_MIN_MCU; + break; + case USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X: + min_version = ROG_ALLY_X_MIN_MCU; + break; + default: + min_version = 0; + } + + if (version < min_version) { + hid_warn(hdev, + "The MCU firmware version must be %d or greater to avoid issues with suspend.\n", + min_version); + } else { + set_ally_mcu_hack(ASUS_WMI_ALLY_MCU_HACK_DISABLED); + set_ally_mcu_powersave(true); + } +} + static int asus_kbd_register_leds(struct hid_device *hdev) { struct asus_drvdata *drvdata = hid_get_drvdata(hdev); + struct usb_interface *intf; + struct usb_device *udev; unsigned char kbd_func; int ret; @@ -560,6 +658,14 @@ static int asus_kbd_register_leds(struct hid_device *hdev) if (ret < 0) return ret; } + + if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) { + intf = to_usb_interface(hdev->dev.parent); + udev = interface_to_usbdev(intf); + validate_mcu_fw_version(hdev, + le16_to_cpu(udev->descriptor.idProduct)); + } + } else { /* Initialize keyboard */ ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID); @@ -1278,12 +1384,17 @@ static const struct hid_device_id asus_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR), QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, + + /* asus-ally-hid driver takes over */ + #if !IS_REACHABLE(CONFIG_ASUS_ALLY_HID) { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY), - QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, + QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_ALLY_XPAD}, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X), - QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, + QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_ALLY_XPAD }, + #endif /* !IS_REACHABLE(CONFIG_ASUS_ALLY_HID) */ + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD), QUIRK_ROG_CLAYMORE_II_KEYBOARD }, @@ -1327,4 +1438,5 @@ static struct hid_driver asus_driver = { }; module_hid_driver(asus_driver); +MODULE_IMPORT_NS("ASUS_WMI"); MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 288a2b864cc4..50cd02b049fc 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -217,6 +217,7 @@ #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2 0x19b6 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3 0x1a30 #define USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR 0x18c6 +#define USB_DEVICE_ID_ASUSTEK_ROG_RAIKIRI_PAD 0x1abb #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY 0x1abe #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X 0x1b4c #define USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD 0x196b diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 0258dd879d64..7edab99d3ae9 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -267,6 +267,18 @@ config ASUS_WIRELESS If you choose to compile this driver as a module the module will be called asus-wireless. +config ASUS_ARMOURY + tristate "ASUS Armoury driver" + depends on ASUS_WMI + select FW_ATTR_CLASS + help + Say Y here if you have a WMI aware Asus machine and would like to use the + firmware_attributes API to control various settings typically exposed in + the ASUS Armoury Crate application available on Windows. + + To compile this driver as a module, choose M here: the module will + be called asus-armoury. + config ASUS_WMI tristate "ASUS WMI Driver" depends on ACPI_WMI @@ -289,6 +301,17 @@ config ASUS_WMI To compile this driver as a module, choose M here: the module will be called asus-wmi. +config ASUS_WMI_DEPRECATED_ATTRS + bool "BIOS option support in WMI platform (DEPRECATED)" + depends on ASUS_WMI + default y + help + Say Y to expose the configurable BIOS options through the asus-wmi + driver. + + This can be used with or without the asus-armoury driver which + has the same attributes, but more, and better features. + config ASUS_NB_WMI tristate "Asus Notebook WMI Driver" depends on ASUS_WMI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index e1b142947067..fe3e7e7dede8 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_APPLE_GMUX) += apple-gmux.o # ASUS obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o obj-$(CONFIG_ASUS_WIRELESS) += asus-wireless.o +obj-$(CONFIG_ASUS_ARMOURY) += asus-armoury.o obj-$(CONFIG_ASUS_WMI) += asus-wmi.o obj-$(CONFIG_ASUS_NB_WMI) += asus-nb-wmi.o obj-$(CONFIG_ASUS_TF103C_DOCK) += asus-tf103c-dock.o diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c new file mode 100644 index 000000000000..84abc92bd365 --- /dev/null +++ b/drivers/platform/x86/asus-armoury.c @@ -0,0 +1,1202 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Asus Armoury (WMI) attributes driver. This driver uses the fw_attributes + * class to expose the various WMI functions that many gaming and some + * non-gaming ASUS laptops have available. + * These typically don't fit anywhere else in the sysfs such as under LED class, + * hwmon or other, and are set in Windows using the ASUS Armoury Crate tool. + * + * Copyright(C) 2024 Luke Jones + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asus-armoury.h" +#include "firmware_attributes_class.h" + +#define ASUS_NB_WMI_EVENT_GUID "0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C" + +#define ASUS_MINI_LED_MODE_MASK 0x03 +/* Standard modes for devices with only on/off */ +#define ASUS_MINI_LED_OFF 0x00 +#define ASUS_MINI_LED_ON 0x01 +/* Like "on" but the effect is more vibrant or brighter */ +#define ASUS_MINI_LED_STRONG_MODE 0x02 +/* New modes for devices with 3 mini-led mode types */ +#define ASUS_MINI_LED_2024_WEAK 0x00 +#define ASUS_MINI_LED_2024_STRONG 0x01 +#define ASUS_MINI_LED_2024_OFF 0x02 + +/* Power tunable attribute name defines */ +#define ATTR_PPT_PL1_SPL "ppt_pl1_spl" +#define ATTR_PPT_PL2_SPPT "ppt_pl2_sppt" +#define ATTR_PPT_PL3_FPPT "ppt_pl3_fppt" +#define ATTR_PPT_APU_SPPT "ppt_apu_sppt" +#define ATTR_PPT_PLATFORM_SPPT "ppt_platform_sppt" +#define ATTR_NV_DYNAMIC_BOOST "nv_dynamic_boost" +#define ATTR_NV_TEMP_TARGET "nv_temp_target" +#define ATTR_NV_BASE_TGP "nv_base_tgp" +#define ATTR_NV_TGP "nv_tgp" + +#define ASUS_POWER_CORE_MASK GENMASK(15, 8) +#define ASUS_PERF_CORE_MASK GENMASK(7, 0) + +enum cpu_core_type { + CPU_CORE_PERF = 0, + CPU_CORE_POWER, +}; + +enum cpu_core_value { + CPU_CORE_DEFAULT = 0, + CPU_CORE_MIN, + CPU_CORE_MAX, + CPU_CORE_CURRENT, +}; + +#define CPU_PERF_CORE_COUNT_MIN 4 +#define CPU_POWR_CORE_COUNT_MIN 0 + +/* Tunables provided by ASUS for gaming laptops */ +struct cpu_cores { + u32 cur_perf_cores; + u32 min_perf_cores; + u32 max_perf_cores; + u32 cur_power_cores; + u32 min_power_cores; + u32 max_power_cores; +}; + +struct rog_tunables { + const struct power_limits *power_limits; + u32 ppt_pl1_spl; // cpu + u32 ppt_pl2_sppt; // cpu + u32 ppt_pl3_fppt; // cpu + u32 ppt_apu_sppt; // plat + u32 ppt_platform_sppt; // plat + + u32 nv_dynamic_boost; + u32 nv_temp_target; + u32 nv_tgp; +}; + +static struct asus_armoury_priv { + struct device *fw_attr_dev; + struct kset *fw_attr_kset; + + struct cpu_cores *cpu_cores; + /* Index 0 for DC, 1 for AC */ + struct rog_tunables *rog_tunables[2]; + u32 mini_led_dev_id; + u32 gpu_mux_dev_id; + /* + * Mutex to prevent big/little core count changes writing to same + * endpoint at the same time. Must lock during attr store. + */ + struct mutex cpu_core_mutex; +} asus_armoury = { + .cpu_core_mutex = __MUTEX_INITIALIZER(asus_armoury.cpu_core_mutex) +}; + +struct fw_attrs_group { + bool pending_reboot; +}; + +static struct fw_attrs_group fw_attrs = { + .pending_reboot = false, +}; + +struct asus_attr_group { + const struct attribute_group *attr_group; + u32 wmi_devid; +}; + +static bool asus_wmi_is_present(u32 dev_id) +{ + u32 retval; + int status; + + status = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, dev_id, 0, &retval); + pr_debug("%s called (0x%08x), retval: 0x%08x\n", __func__, dev_id, retval); + + return status == 0 && (retval & ASUS_WMI_DSTS_PRESENCE_BIT); +} + +static void asus_set_reboot_and_signal_event(void) +{ + fw_attrs.pending_reboot = true; + kobject_uevent(&asus_armoury.fw_attr_dev->kobj, KOBJ_CHANGE); +} + +static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%d\n", fw_attrs.pending_reboot); +} + +static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot); + +static bool asus_bios_requires_reboot(struct kobj_attribute *attr) +{ + return !strcmp(attr->attr.name, "gpu_mux_mode") || + !strcmp(attr->attr.name, "cores_performance") || + !strcmp(attr->attr.name, "cores_efficiency") || + !strcmp(attr->attr.name, "panel_hd_mode"); +} + +static int armoury_wmi_set_devstate(struct kobj_attribute *attr, u32 value, u32 wmi_dev) +{ + u32 result; + int err; + + err = asus_wmi_set_devstate(wmi_dev, value, &result); + if (err) { + pr_err("Failed to set %s: %d\n", attr->attr.name, err); + return err; + } + /* + * !1 is usually considered a fail by ASUS, but some WMI methods do use > 1 + * to return a status code or similar. + */ + if (result < 1) { + pr_err("Failed to set %s: (result): 0x%x\n", attr->attr.name, result); + return -EIO; + } + + return 0; +} + +/** + * attr_int_store() - Send an int to wmi method, checks if within min/max exclusive. + * @kobj: Pointer to the driver object. + * @attr: Pointer to the attribute calling this function. + * @buf: The buffer to read from, this is parsed to `int` type. + * @count: Required by sysfs attribute macros, pass in from the callee attr. + * @min: Minimum accepted value. Below this returns -EINVAL. + * @max: Maximum accepted value. Above this returns -EINVAL. + * @store_value: Pointer to where the parsed value should be stored. + * @wmi_dev: The WMI function ID to use. + * + * This function is intended to be generic so it can be called from any "_store" + * attribute which works only with integers. The integer to be sent to the WMI method + * is range checked and an error returned if out of range. + * + * If the value is valid and WMI is success, then the sysfs attribute is notified + * and if asus_bios_requires_reboot() is true then reboot attribute is also notified. + * + * Returns: Either count, or an error. + */ +static ssize_t attr_uint_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, + size_t count, u32 min, u32 max, u32 *store_value, u32 wmi_dev) +{ + u32 value; + int err; + + err = kstrtouint(buf, 10, &value); + if (err) + return err; + + if (value < min || value > max) + return -EINVAL; + + err = armoury_wmi_set_devstate(attr, value, wmi_dev); + if (err) + return err; + + if (store_value != NULL) + *store_value = value; + sysfs_notify(kobj, NULL, attr->attr.name); + + if (asus_bios_requires_reboot(attr)) + asus_set_reboot_and_signal_event(); + + return count; +} + +static ssize_t enum_type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "enumeration\n"); +} + +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "integer\n"); +} + +/* Mini-LED mode **************************************************************/ +static ssize_t mini_led_mode_current_value_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + u32 value; + int err; + + err = asus_wmi_get_devstate_dsts(asus_armoury.mini_led_dev_id, &value); + if (err) + return err; + + value &= ASUS_MINI_LED_MODE_MASK; + + /* + * Remap the mode values to match previous generation mini-LED. The last gen + * WMI 0 == off, while on this version WMI 2 == off (flipped). + */ + if (asus_armoury.mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2) { + switch (value) { + case ASUS_MINI_LED_2024_WEAK: + value = ASUS_MINI_LED_ON; + break; + case ASUS_MINI_LED_2024_STRONG: + value = ASUS_MINI_LED_STRONG_MODE; + break; + case ASUS_MINI_LED_2024_OFF: + value = ASUS_MINI_LED_OFF; + break; + } + } + + return sysfs_emit(buf, "%u\n", value); +} + +static ssize_t mini_led_mode_current_value_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + u32 mode; + int err; + + err = kstrtou32(buf, 10, &mode); + if (err) + return err; + + if (asus_armoury.mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE && + mode > ASUS_MINI_LED_ON) + return -EINVAL; + if (asus_armoury.mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2 && + mode > ASUS_MINI_LED_STRONG_MODE) + return -EINVAL; + + /* + * Remap the mode values so expected behaviour is the same as the last + * generation of mini-LED with 0 == off, 1 == on. + */ + if (asus_armoury.mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2) { + switch (mode) { + case ASUS_MINI_LED_OFF: + mode = ASUS_MINI_LED_2024_OFF; + break; + case ASUS_MINI_LED_ON: + mode = ASUS_MINI_LED_2024_WEAK; + break; + case ASUS_MINI_LED_STRONG_MODE: + mode = ASUS_MINI_LED_2024_STRONG; + break; + } + } + + err = armoury_wmi_set_devstate(attr, mode, asus_armoury.mini_led_dev_id); + if (err) + return err; + + sysfs_notify(kobj, NULL, attr->attr.name); + + return count; +} + +static ssize_t mini_led_mode_possible_values_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + switch (asus_armoury.mini_led_dev_id) { + case ASUS_WMI_DEVID_MINI_LED_MODE: + return sysfs_emit(buf, "0;1\n"); + case ASUS_WMI_DEVID_MINI_LED_MODE2: + return sysfs_emit(buf, "0;1;2\n"); + } + + return sysfs_emit(buf, "0\n"); +} + +ATTR_GROUP_ENUM_CUSTOM(mini_led_mode, "mini_led_mode", "Set the mini-LED backlight mode"); + +static ssize_t gpu_mux_mode_current_value_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, + size_t count) +{ + int result, err; + u32 optimus; + + err = kstrtou32(buf, 10, &optimus); + if (err) + return err; + + if (optimus > 1) + return -EINVAL; + + if (asus_wmi_is_present(ASUS_WMI_DEVID_DGPU)) { + err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_DGPU, &result); + if (err) + return err; + if (result && !optimus) { + pr_warn("Can not switch MUX to dGPU mode when dGPU is disabled: %02X %02X\n", + result, optimus); + return -ENODEV; + } + } + + if (asus_wmi_is_present(ASUS_WMI_DEVID_EGPU)) { + err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_EGPU, &result); + if (err) + return err; + if (result && !optimus) { + pr_warn("Can not switch MUX to dGPU mode when eGPU is enabled\n"); + return -ENODEV; + } + } + + err = armoury_wmi_set_devstate(attr, optimus, asus_armoury.gpu_mux_dev_id); + if (err) + return err; + + sysfs_notify(kobj, NULL, attr->attr.name); + asus_set_reboot_and_signal_event(); + + return count; +} +WMI_SHOW_INT(gpu_mux_mode_current_value, "%d\n", asus_armoury.gpu_mux_dev_id); +ATTR_GROUP_BOOL_CUSTOM(gpu_mux_mode, "gpu_mux_mode", "Set the GPU display MUX mode"); + +/* + * A user may be required to store the value twice, typical store first, then + * rescan PCI bus to activate power, then store a second time to save correctly. + */ +static ssize_t dgpu_disable_current_value_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, + size_t count) +{ + int result, err; + u32 disable; + + err = kstrtou32(buf, 10, &disable); + if (err) + return err; + + if (disable > 1) + return -EINVAL; + + if (asus_armoury.gpu_mux_dev_id) { + err = asus_wmi_get_devstate_dsts(asus_armoury.gpu_mux_dev_id, &result); + if (err) + return err; + if (!result && disable) { + pr_warn("Can not disable dGPU when the MUX is in dGPU mode\n"); + return -ENODEV; + } + } + + err = armoury_wmi_set_devstate(attr, disable, ASUS_WMI_DEVID_DGPU); + if (err) + return err; + + sysfs_notify(kobj, NULL, attr->attr.name); + + return count; +} +WMI_SHOW_INT(dgpu_disable_current_value, "%d\n", ASUS_WMI_DEVID_DGPU); +ATTR_GROUP_BOOL_CUSTOM(dgpu_disable, "dgpu_disable", "Disable the dGPU"); + +/* The ACPI call to enable the eGPU also disables the internal dGPU */ +static ssize_t egpu_enable_current_value_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int result, err; + u32 enable; + + err = kstrtou32(buf, 10, &enable); + if (err) + return err; + + if (enable > 1) + return -EINVAL; + + err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_EGPU_CONNECTED, &result); + if (err) { + pr_warn("Failed to get eGPU connection status: %d\n", err); + return err; + } + + if (asus_armoury.gpu_mux_dev_id) { + err = asus_wmi_get_devstate_dsts(asus_armoury.gpu_mux_dev_id, &result); + if (err) { + pr_warn("Failed to get GPU MUX status: %d\n", result); + return result; + } + if (!result && enable) { + pr_warn("Can not enable eGPU when the MUX is in dGPU mode\n"); + return -ENODEV; + } + } + + err = armoury_wmi_set_devstate(attr, enable, ASUS_WMI_DEVID_EGPU); + if (err) + return err; + + sysfs_notify(kobj, NULL, attr->attr.name); + + return count; +} +WMI_SHOW_INT(egpu_enable_current_value, "%d\n", ASUS_WMI_DEVID_EGPU); +ATTR_GROUP_BOOL_CUSTOM(egpu_enable, "egpu_enable", "Enable the eGPU (also disables dGPU)"); + +/* Device memory available to APU */ + +static ssize_t apu_mem_current_value_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + int err; + u32 mem; + + err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_APU_MEM, &mem); + if (err) + return err; + + switch (mem) { + case 0x100: + mem = 0; + break; + case 0x102: + mem = 1; + break; + case 0x103: + mem = 2; + break; + case 0x104: + mem = 3; + break; + case 0x105: + mem = 4; + break; + case 0x106: + /* This is out of order and looks wrong but is correct */ + mem = 8; + break; + case 0x107: + mem = 5; + break; + case 0x108: + mem = 6; + break; + case 0x109: + mem = 7; + break; + default: + mem = 4; + break; + } + + return sysfs_emit(buf, "%u\n", mem); +} + +static ssize_t apu_mem_current_value_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int result, err; + u32 requested, mem; + + result = kstrtou32(buf, 10, &requested); + if (result) + return result; + + switch (requested) { + case 0: + mem = 0x000; + break; + case 1: + mem = 0x102; + break; + case 2: + mem = 0x103; + break; + case 3: + mem = 0x104; + break; + case 4: + mem = 0x105; + break; + case 5: + mem = 0x107; + break; + case 6: + mem = 0x108; + break; + case 7: + mem = 0x109; + break; + case 8: + /* This is out of order and looks wrong but is correct */ + mem = 0x106; + break; + default: + return -EIO; + } + + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_APU_MEM, mem, &result); + if (err) { + pr_warn("Failed to set apu_mem: %d\n", err); + return err; + } + + pr_info("APU memory changed to %uGB, reboot required\n", requested); + sysfs_notify(kobj, NULL, attr->attr.name); + + asus_set_reboot_and_signal_event(); + + return count; +} + +static ssize_t apu_mem_possible_values_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "0;1;2;3;4;5;6;7;8\n"); +} +ATTR_GROUP_ENUM_CUSTOM(apu_mem, "apu_mem", "Set available system RAM (in GB) for the APU to use"); + +static int init_max_cpu_cores(void) +{ + u32 cores; + int err; + + err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_CORES_MAX, &cores); + if (err) + return err; + + cores &= ~ASUS_WMI_DSTS_PRESENCE_BIT; + asus_armoury.cpu_cores->max_power_cores = FIELD_GET(ASUS_POWER_CORE_MASK, cores); + asus_armoury.cpu_cores->max_perf_cores = FIELD_GET(ASUS_PERF_CORE_MASK, cores); + + err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_CORES, &cores); + if (err) { + pr_err("Could not get CPU core count: error %d", err); + return err; + } + + asus_armoury.cpu_cores->cur_perf_cores = FIELD_GET(ASUS_PERF_CORE_MASK, cores); + asus_armoury.cpu_cores->cur_power_cores = FIELD_GET(ASUS_POWER_CORE_MASK, cores); + + asus_armoury.cpu_cores->min_perf_cores = CPU_PERF_CORE_COUNT_MIN; + asus_armoury.cpu_cores->min_power_cores = CPU_POWR_CORE_COUNT_MIN; + + return 0; +} + +static ssize_t cores_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf, + enum cpu_core_type core_type, enum cpu_core_value core_value) +{ + u32 cores; + + switch (core_value) { + case CPU_CORE_DEFAULT: + case CPU_CORE_MAX: + if (core_type == CPU_CORE_PERF) + return sysfs_emit(buf, "%d\n", + asus_armoury.cpu_cores->max_perf_cores); + else + return sysfs_emit(buf, "%d\n", + asus_armoury.cpu_cores->max_power_cores); + case CPU_CORE_MIN: + if (core_type == CPU_CORE_PERF) + return sysfs_emit(buf, "%d\n", + asus_armoury.cpu_cores->min_perf_cores); + else + return sysfs_emit(buf, "%d\n", + asus_armoury.cpu_cores->min_power_cores); + default: + break; + } + + if (core_type == CPU_CORE_PERF) + cores = asus_armoury.cpu_cores->cur_perf_cores; + else + cores = asus_armoury.cpu_cores->cur_power_cores; + + return sysfs_emit(buf, "%d\n", cores); +} + +static ssize_t cores_current_value_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, enum cpu_core_type core_type) +{ + u32 new_cores, perf_cores, power_cores, out_val, min, max; + int result, err; + + result = kstrtou32(buf, 10, &new_cores); + if (result) + return result; + + mutex_lock(&asus_armoury.cpu_core_mutex); + + if (core_type == CPU_CORE_PERF) { + perf_cores = new_cores; + power_cores = out_val = asus_armoury.cpu_cores->cur_power_cores; + min = asus_armoury.cpu_cores->min_perf_cores; + max = asus_armoury.cpu_cores->max_perf_cores; + } else { + perf_cores = asus_armoury.cpu_cores->cur_perf_cores; + power_cores = out_val = new_cores; + min = asus_armoury.cpu_cores->min_power_cores; + max = asus_armoury.cpu_cores->max_power_cores; + } + + if (new_cores < min || new_cores > max) { + mutex_unlock(&asus_armoury.cpu_core_mutex); + return -EINVAL; + } + + out_val = 0; + out_val |= FIELD_PREP(ASUS_PERF_CORE_MASK, perf_cores); + out_val |= FIELD_PREP(ASUS_POWER_CORE_MASK, power_cores); + + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_CORES, out_val, &result); + + if (err) { + pr_warn("Failed to set CPU core count: %d\n", err); + mutex_unlock(&asus_armoury.cpu_core_mutex); + return err; + } + + if (result > 1) { + pr_warn("Failed to set CPU core count (result): 0x%x\n", result); + mutex_unlock(&asus_armoury.cpu_core_mutex); + return -EIO; + } + + pr_info("CPU core count changed, reboot required\n"); + mutex_unlock(&asus_armoury.cpu_core_mutex); + + sysfs_notify(kobj, NULL, attr->attr.name); + asus_set_reboot_and_signal_event(); + + return 0; +} + +static ssize_t cores_performance_min_value_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return cores_value_show(kobj, attr, buf, CPU_CORE_PERF, CPU_CORE_MIN); +} + +static ssize_t cores_performance_max_value_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return cores_value_show(kobj, attr, buf, CPU_CORE_PERF, CPU_CORE_MAX); +} + +static ssize_t cores_performance_default_value_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return cores_value_show(kobj, attr, buf, CPU_CORE_PERF, CPU_CORE_DEFAULT); +} + +static ssize_t cores_performance_current_value_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return cores_value_show(kobj, attr, buf, CPU_CORE_PERF, CPU_CORE_CURRENT); +} + +static ssize_t cores_performance_current_value_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int err; + + err = cores_current_value_store(kobj, attr, buf, CPU_CORE_PERF); + if (err) + return err; + + return count; +} +ATTR_GROUP_CORES_RW(cores_performance, "cores_performance", + "Set the max available performance cores"); + +static ssize_t cores_efficiency_min_value_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return cores_value_show(kobj, attr, buf, CPU_CORE_POWER, CPU_CORE_MIN); +} + +static ssize_t cores_efficiency_max_value_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return cores_value_show(kobj, attr, buf, CPU_CORE_POWER, CPU_CORE_MAX); +} + +static ssize_t cores_efficiency_default_value_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return cores_value_show(kobj, attr, buf, CPU_CORE_POWER, CPU_CORE_DEFAULT); +} + +static ssize_t cores_efficiency_current_value_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return cores_value_show(kobj, attr, buf, CPU_CORE_POWER, CPU_CORE_CURRENT); +} + +static ssize_t cores_efficiency_current_value_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, + size_t count) +{ + int err; + + err = cores_current_value_store(kobj, attr, buf, CPU_CORE_POWER); + if (err) + return err; + + return count; +} +ATTR_GROUP_CORES_RW(cores_efficiency, "cores_efficiency", + "Set the max available efficiency cores"); + +/* Define helper to access the current power mode tunable values */ +static inline struct rog_tunables *get_current_tunables(void) +{ + return asus_armoury + .rog_tunables[power_supply_is_system_supplied() ? 1 : 0]; +} + +/* Simple attribute creation */ +ATTR_GROUP_ROG_TUNABLE(ppt_pl1_spl, ATTR_PPT_PL1_SPL, ASUS_WMI_DEVID_PPT_PL1_SPL, + "Set the CPU slow package limit"); +ATTR_GROUP_ROG_TUNABLE(ppt_pl2_sppt, ATTR_PPT_PL2_SPPT, ASUS_WMI_DEVID_PPT_PL2_SPPT, + "Set the CPU fast package limit"); +ATTR_GROUP_ROG_TUNABLE(ppt_pl3_fppt, ATTR_PPT_PL3_FPPT, ASUS_WMI_DEVID_PPT_FPPT, + "Set the CPU fastest package limit"); +ATTR_GROUP_ROG_TUNABLE(ppt_apu_sppt, ATTR_PPT_APU_SPPT, ASUS_WMI_DEVID_PPT_APU_SPPT, + "Set the APU package limit"); +ATTR_GROUP_ROG_TUNABLE(ppt_platform_sppt, ATTR_PPT_PLATFORM_SPPT, ASUS_WMI_DEVID_PPT_PLAT_SPPT, + "Set the platform package limit"); +ATTR_GROUP_ROG_TUNABLE(nv_dynamic_boost, ATTR_NV_DYNAMIC_BOOST, ASUS_WMI_DEVID_NV_DYN_BOOST, + "Set the Nvidia dynamic boost limit"); +ATTR_GROUP_ROG_TUNABLE(nv_temp_target, ATTR_NV_TEMP_TARGET, ASUS_WMI_DEVID_NV_THERM_TARGET, + "Set the Nvidia max thermal limit"); +ATTR_GROUP_ROG_TUNABLE(nv_tgp, "nv_tgp", ASUS_WMI_DEVID_DGPU_SET_TGP, + "Set the additional TGP on top of the base TGP"); +ATTR_GROUP_INT_VALUE_ONLY_RO(nv_base_tgp, ATTR_NV_BASE_TGP, ASUS_WMI_DEVID_DGPU_BASE_TGP, + "Read the base TGP value"); + + +ATTR_GROUP_ENUM_INT_RO(charge_mode, "charge_mode", ASUS_WMI_DEVID_CHARGE_MODE, "0;1;2", + "Show the current mode of charging"); + +ATTR_GROUP_BOOL_RW(boot_sound, "boot_sound", ASUS_WMI_DEVID_BOOT_SOUND, + "Set the boot POST sound"); +ATTR_GROUP_BOOL_RW(mcu_powersave, "mcu_powersave", ASUS_WMI_DEVID_MCU_POWERSAVE, + "Set MCU powersaving mode"); +ATTR_GROUP_BOOL_RW(panel_od, "panel_overdrive", ASUS_WMI_DEVID_PANEL_OD, + "Set the panel refresh overdrive"); +ATTR_GROUP_BOOL_RW(panel_hd_mode, "panel_hd_mode", ASUS_WMI_DEVID_PANEL_HD, + "Set the panel HD mode to UHD<0> or FHD<1>"); +ATTR_GROUP_BOOL_RW(screen_auto_brightness, "screen_auto_brightness", + ASUS_WMI_DEVID_SCREEN_AUTO_BRIGHTNESS, + "Set the panel brightness to Off<0> or On<1>"); +ATTR_GROUP_BOOL_RO(egpu_connected, "egpu_connected", ASUS_WMI_DEVID_EGPU_CONNECTED, + "Show the eGPU connection status"); + +/* If an attribute does not require any special case handling add it here */ +static const struct asus_attr_group armoury_attr_groups[] = { + { &egpu_connected_attr_group, ASUS_WMI_DEVID_EGPU_CONNECTED }, + { &egpu_enable_attr_group, ASUS_WMI_DEVID_EGPU }, + { &dgpu_disable_attr_group, ASUS_WMI_DEVID_DGPU }, + { &apu_mem_attr_group, ASUS_WMI_DEVID_APU_MEM }, + { &cores_efficiency_attr_group, ASUS_WMI_DEVID_CORES_MAX }, + { &cores_performance_attr_group, ASUS_WMI_DEVID_CORES_MAX }, + + { &ppt_pl1_spl_attr_group, ASUS_WMI_DEVID_PPT_PL1_SPL }, + { &ppt_pl2_sppt_attr_group, ASUS_WMI_DEVID_PPT_PL2_SPPT }, + { &ppt_pl3_fppt_attr_group, ASUS_WMI_DEVID_PPT_FPPT }, + { &ppt_apu_sppt_attr_group, ASUS_WMI_DEVID_PPT_APU_SPPT }, + { &ppt_platform_sppt_attr_group, ASUS_WMI_DEVID_PPT_PLAT_SPPT }, + { &nv_dynamic_boost_attr_group, ASUS_WMI_DEVID_NV_DYN_BOOST }, + { &nv_temp_target_attr_group, ASUS_WMI_DEVID_NV_THERM_TARGET }, + { &nv_base_tgp_attr_group, ASUS_WMI_DEVID_DGPU_BASE_TGP }, + { &nv_tgp_attr_group, ASUS_WMI_DEVID_DGPU_SET_TGP }, + + { &charge_mode_attr_group, ASUS_WMI_DEVID_CHARGE_MODE }, + { &boot_sound_attr_group, ASUS_WMI_DEVID_BOOT_SOUND }, + { &mcu_powersave_attr_group, ASUS_WMI_DEVID_MCU_POWERSAVE }, + { &panel_od_attr_group, ASUS_WMI_DEVID_PANEL_OD }, + { &panel_hd_mode_attr_group, ASUS_WMI_DEVID_PANEL_HD }, +}; + +/** + * is_power_tunable_attr - Determines if an attribute is a power-related tunable + * @name: The name of the attribute to check + * + * This function checks if the given attribute name is related to power tuning. + * + * Return: true if the attribute is a power-related tunable, false otherwise + */ +static bool is_power_tunable_attr(const char *name) +{ + static const char * const power_tunable_attrs[] = { + ATTR_PPT_PL1_SPL, ATTR_PPT_PL2_SPPT, + ATTR_PPT_PL3_FPPT, ATTR_PPT_APU_SPPT, + ATTR_PPT_PLATFORM_SPPT, ATTR_NV_DYNAMIC_BOOST, + ATTR_NV_TEMP_TARGET, ATTR_NV_BASE_TGP, + ATTR_NV_TGP + }; + + for (int i = 0; i < ARRAY_SIZE(power_tunable_attrs); i++) { + if (!strcmp(name, power_tunable_attrs[i])) + return true; + } + + return false; +} + +/** + * has_valid_limit - Checks if a power-related attribute has a valid limit value + * @name: The name of the attribute to check + * @limits: Pointer to the power_limits structure containing limit values + * + * This function checks if a power-related attribute has a valid limit value. + * It returns false if limits is NULL or if the corresponding limit value is zero. + * + * Return: true if the attribute has a valid limit value, false otherwise + */ +static bool has_valid_limit(const char *name, const struct power_limits *limits) +{ + u32 limit_value = 0; + + if (!limits) + return false; + + if (!strcmp(name, ATTR_PPT_PL1_SPL)) + limit_value = limits->ppt_pl1_spl_max; + else if (!strcmp(name, ATTR_PPT_PL2_SPPT)) + limit_value = limits->ppt_pl2_sppt_max; + else if (!strcmp(name, ATTR_PPT_PL3_FPPT)) + limit_value = limits->ppt_pl3_fppt_max; + else if (!strcmp(name, ATTR_PPT_APU_SPPT)) + limit_value = limits->ppt_apu_sppt_max; + else if (!strcmp(name, ATTR_PPT_PLATFORM_SPPT)) + limit_value = limits->ppt_platform_sppt_max; + else if (!strcmp(name, ATTR_NV_DYNAMIC_BOOST)) + limit_value = limits->nv_dynamic_boost_max; + else if (!strcmp(name, ATTR_NV_TEMP_TARGET)) + limit_value = limits->nv_temp_target_max; + else if (!strcmp(name, ATTR_NV_BASE_TGP) || + !strcmp(name, ATTR_NV_TGP)) + limit_value = limits->nv_tgp_max; + + return limit_value > 0; +} + +static int asus_fw_attr_add(void) +{ + const struct power_limits *limits; + bool should_create; + const char *name; + int err, i; + + asus_armoury.fw_attr_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), + NULL, "%s", DRIVER_NAME); + if (IS_ERR(asus_armoury.fw_attr_dev)) { + err = PTR_ERR(asus_armoury.fw_attr_dev); + goto fail_class_get; + } + + asus_armoury.fw_attr_kset = kset_create_and_add("attributes", NULL, + &asus_armoury.fw_attr_dev->kobj); + if (!asus_armoury.fw_attr_kset) { + err = -ENOMEM; + goto err_destroy_classdev; + } + + err = sysfs_create_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr); + if (err) { + pr_err("Failed to create sysfs level attributes\n"); + goto err_destroy_kset; + } + + asus_armoury.mini_led_dev_id = 0; + if (asus_wmi_is_present(ASUS_WMI_DEVID_MINI_LED_MODE)) + asus_armoury.mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE; + else if (asus_wmi_is_present(ASUS_WMI_DEVID_MINI_LED_MODE2)) + asus_armoury.mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE2; + + if (asus_armoury.mini_led_dev_id) { + err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, + &mini_led_mode_attr_group); + if (err) { + pr_err("Failed to create sysfs-group for mini_led\n"); + goto err_remove_file; + } + } + + asus_armoury.gpu_mux_dev_id = 0; + if (asus_wmi_is_present(ASUS_WMI_DEVID_GPU_MUX)) + asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX; + else if (asus_wmi_is_present(ASUS_WMI_DEVID_GPU_MUX_VIVO)) + asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX_VIVO; + + if (asus_armoury.gpu_mux_dev_id) { + err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, + &gpu_mux_mode_attr_group); + if (err) { + pr_err("Failed to create sysfs-group for gpu_mux\n"); + goto err_remove_mini_led_group; + } + } + + for (i = 0; i < ARRAY_SIZE(armoury_attr_groups); i++) { + if (!asus_wmi_is_present(armoury_attr_groups[i].wmi_devid)) + continue; + + /* Always create by default, unless PPT is not present */ + should_create = true; + name = armoury_attr_groups[i].attr_group->name; + + /* Check if this is a power-related tunable requiring limits */ + if (asus_armoury.rog_tunables[1] && asus_armoury.rog_tunables[1]->power_limits && + is_power_tunable_attr(name)) { + limits = asus_armoury.rog_tunables[1]->power_limits; + /* Check only AC, if DC is not present then AC won't be either */ + should_create = has_valid_limit(name, limits); + if (!should_create) { + pr_debug( + "Missing max value on %s for tunable: %s\n", + dmi_get_system_info(DMI_BOARD_NAME), + name); + } + } + + if (should_create) { + err = sysfs_create_group( + &asus_armoury.fw_attr_kset->kobj, + armoury_attr_groups[i].attr_group); + if (err) { + pr_err("Failed to create sysfs-group for %s\n", + armoury_attr_groups[i].attr_group->name); + goto err_remove_groups; + } + } + } + + return 0; + +err_remove_groups: + while (--i >= 0) { + if (asus_wmi_is_present(armoury_attr_groups[i].wmi_devid)) + sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, + armoury_attr_groups[i].attr_group); + } + if (asus_armoury.gpu_mux_dev_id) + sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group); +err_remove_mini_led_group: + if (asus_armoury.mini_led_dev_id) + sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &mini_led_mode_attr_group); +err_remove_file: + sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr); +err_destroy_kset: + kset_unregister(asus_armoury.fw_attr_kset); +err_destroy_classdev: +fail_class_get: + device_destroy(&firmware_attributes_class, MKDEV(0, 0)); + return err; +} + +/* Init / exit ****************************************************************/ + +/* Set up the min/max and defaults for ROG tunables */ +static void init_rog_tunables(void) +{ + const struct power_limits *ac_limits, *dc_limits; + const struct power_data *power_data; + const struct dmi_system_id *dmi_id; + bool ac_initialized = false, dc_initialized = false; + + /* Match the system against the power_limits table */ + dmi_id = dmi_first_match(power_limits); + if (!dmi_id) { + pr_warn("No matching power limits found for this system\n"); + return; + } + + /* Get the power data for this system */ + power_data = dmi_id->driver_data; + if (!power_data) { + pr_info("No power data available for this system\n"); + return; + } + + /* Initialize AC power tunables */ + ac_limits = power_data->ac_data; + if (ac_limits) { + asus_armoury.rog_tunables[1] = + kzalloc(sizeof(struct rog_tunables), GFP_KERNEL); + if (!asus_armoury.rog_tunables[1]) + goto err_nomem; + + asus_armoury.rog_tunables[1]->power_limits = ac_limits; + + /* Set initial AC values */ + asus_armoury.rog_tunables[1]->ppt_pl1_spl = + ac_limits->ppt_pl1_spl_def ? + ac_limits->ppt_pl1_spl_def : + ac_limits->ppt_pl1_spl_max; + + asus_armoury.rog_tunables[1]->ppt_pl2_sppt = + ac_limits->ppt_pl2_sppt_def ? + ac_limits->ppt_pl2_sppt_def : + ac_limits->ppt_pl2_sppt_max; + + asus_armoury.rog_tunables[1]->ppt_pl3_fppt = + ac_limits->ppt_pl3_fppt_def ? + ac_limits->ppt_pl3_fppt_def : + ac_limits->ppt_pl3_fppt_max; + + asus_armoury.rog_tunables[1]->ppt_apu_sppt = + ac_limits->ppt_apu_sppt_def ? + ac_limits->ppt_apu_sppt_def : + ac_limits->ppt_apu_sppt_max; + + asus_armoury.rog_tunables[1]->ppt_platform_sppt = + ac_limits->ppt_platform_sppt_def ? + ac_limits->ppt_platform_sppt_def : + ac_limits->ppt_platform_sppt_max; + + asus_armoury.rog_tunables[1]->nv_dynamic_boost = + ac_limits->nv_dynamic_boost_max; + asus_armoury.rog_tunables[1]->nv_temp_target = + ac_limits->nv_temp_target_max; + asus_armoury.rog_tunables[1]->nv_tgp = ac_limits->nv_tgp_max; + + ac_initialized = true; + pr_debug("AC power limits initialized for %s\n", dmi_id->matches[0].substr); + } + + /* Initialize DC power tunables */ + dc_limits = power_data->dc_data; + if (dc_limits) { + asus_armoury.rog_tunables[0] = + kzalloc(sizeof(struct rog_tunables), GFP_KERNEL); + if (!asus_armoury.rog_tunables[0]) { + if (ac_initialized) + kfree(asus_armoury.rog_tunables[1]); + goto err_nomem; + } + + asus_armoury.rog_tunables[0]->power_limits = dc_limits; + + /* Set initial DC values */ + asus_armoury.rog_tunables[0]->ppt_pl1_spl = + dc_limits->ppt_pl1_spl_def ? + dc_limits->ppt_pl1_spl_def : + dc_limits->ppt_pl1_spl_max; + + asus_armoury.rog_tunables[0]->ppt_pl2_sppt = + dc_limits->ppt_pl2_sppt_def ? + dc_limits->ppt_pl2_sppt_def : + dc_limits->ppt_pl2_sppt_max; + + asus_armoury.rog_tunables[0]->ppt_pl3_fppt = + dc_limits->ppt_pl3_fppt_def ? + dc_limits->ppt_pl3_fppt_def : + dc_limits->ppt_pl3_fppt_max; + + asus_armoury.rog_tunables[0]->ppt_apu_sppt = + dc_limits->ppt_apu_sppt_def ? + dc_limits->ppt_apu_sppt_def : + dc_limits->ppt_apu_sppt_max; + + asus_armoury.rog_tunables[0]->ppt_platform_sppt = + dc_limits->ppt_platform_sppt_def ? + dc_limits->ppt_platform_sppt_def : + dc_limits->ppt_platform_sppt_max; + + asus_armoury.rog_tunables[0]->nv_dynamic_boost = + dc_limits->nv_dynamic_boost_max; + asus_armoury.rog_tunables[0]->nv_temp_target = + dc_limits->nv_temp_target_max; + asus_armoury.rog_tunables[0]->nv_tgp = dc_limits->nv_tgp_max; + + dc_initialized = true; + pr_debug("DC power limits initialized for %s\n", dmi_id->matches[0].substr); + } + + if (!ac_initialized) + pr_debug("No AC PPT limits defined\n"); + + if (!dc_initialized) + pr_debug("No DC PPT limits defined\n"); + + return; + +err_nomem: + pr_err("Failed to allocate memory for tunables\n"); +} + +static int __init asus_fw_init(void) +{ + char *wmi_uid; + int err; + + wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID); + if (!wmi_uid) + return -ENODEV; + + /* + * if equal to "ASUSWMI" then it's DCTS that can't be used for this + * driver, DSTS is required. + */ + if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI)) + return -ENODEV; + + if (asus_wmi_is_present(ASUS_WMI_DEVID_CORES_MAX)) { + asus_armoury.cpu_cores = kzalloc(sizeof(struct cpu_cores), GFP_KERNEL); + if (!asus_armoury.cpu_cores) + return -ENOMEM; + + err = init_max_cpu_cores(); + if (err) { + kfree(asus_armoury.cpu_cores); + pr_err("Could not initialise CPU core control %d\n", err); + return err; + } + } + + init_rog_tunables(); + + /* Must always be last step to ensure data is available */ + return asus_fw_attr_add(); +} + +static void __exit asus_fw_exit(void) +{ + sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr); + kset_unregister(asus_armoury.fw_attr_kset); + device_destroy(&firmware_attributes_class, MKDEV(0, 0)); + + kfree(asus_armoury.rog_tunables[0]); + kfree(asus_armoury.rog_tunables[1]); +} + +module_init(asus_fw_init); +module_exit(asus_fw_exit); + +MODULE_IMPORT_NS("ASUS_WMI"); +MODULE_AUTHOR("Luke Jones "); +MODULE_DESCRIPTION("ASUS BIOS Configuration Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("wmi:" ASUS_NB_WMI_EVENT_GUID); diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h new file mode 100644 index 000000000000..438768ea14cc --- /dev/null +++ b/drivers/platform/x86/asus-armoury.h @@ -0,0 +1,1278 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Definitions for kernel modules using asus-armoury driver + * + * Copyright (c) 2024 Luke Jones + */ + +#ifndef _ASUS_ARMOURY_H_ +#define _ASUS_ARMOURY_H_ + +#include +#include +#include + +#define DRIVER_NAME "asus-armoury" + +#define __ASUS_ATTR_RO(_func, _name) \ + { \ + .attr = { .name = __stringify(_name), .mode = 0444 }, \ + .show = _func##_##_name##_show, \ + } + +#define __ASUS_ATTR_RO_AS(_name, _show) \ + { \ + .attr = { .name = __stringify(_name), .mode = 0444 }, \ + .show = _show, \ + } + +#define __ASUS_ATTR_RW(_func, _name) \ + __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store) + +#define __WMI_STORE_INT(_attr, _min, _max, _wmi) \ + static ssize_t _attr##_store(struct kobject *kobj, \ + struct kobj_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return attr_uint_store(kobj, attr, buf, count, _min, \ + _max, NULL, _wmi); \ + } + +#define WMI_SHOW_INT(_attr, _fmt, _wmi) \ + static ssize_t _attr##_show(struct kobject *kobj, \ + struct kobj_attribute *attr, char *buf) \ + { \ + u32 result; \ + int err; \ + \ + err = asus_wmi_get_devstate_dsts(_wmi, &result); \ + if (err) \ + return err; \ + return sysfs_emit(buf, _fmt, \ + result & ~ASUS_WMI_DSTS_PRESENCE_BIT); \ + } + +/* Create functions and attributes for use in other macros or on their own */ + +/* Shows a formatted static variable */ +#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val) \ + static ssize_t _attrname##_##_prop##_show( \ + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ + { \ + return sysfs_emit(buf, _fmt, _val); \ + } \ + static struct kobj_attribute attr_##_attrname##_##_prop = \ + __ASUS_ATTR_RO(_attrname, _prop) + +#define __ATTR_RO_INT_GROUP_ENUM(_attrname, _wmi, _fsname, _possible, _dispname)\ + WMI_SHOW_INT(_attrname##_current_value, "%d\n", _wmi); \ + static struct kobj_attribute attr_##_attrname##_current_value = \ + __ASUS_ATTR_RO(_attrname, current_value); \ + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ + __ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", _possible); \ + static struct kobj_attribute attr_##_attrname##_type = \ + __ASUS_ATTR_RO_AS(type, enum_type_show); \ + static struct attribute *_attrname##_attrs[] = { \ + &attr_##_attrname##_current_value.attr, \ + &attr_##_attrname##_display_name.attr, \ + &attr_##_attrname##_possible_values.attr, \ + &attr_##_attrname##_type.attr, \ + NULL \ + }; \ + static const struct attribute_group _attrname##_attr_group = { \ + .name = _fsname, .attrs = _attrname##_attrs \ + } + +#define __ATTR_RW_INT_GROUP_ENUM(_attrname, _minv, _maxv, _wmi, _fsname,\ + _possible, _dispname) \ + __WMI_STORE_INT(_attrname##_current_value, _minv, _maxv, _wmi); \ + WMI_SHOW_INT(_attrname##_current_value, "%d\n", _wmi); \ + static struct kobj_attribute attr_##_attrname##_current_value = \ + __ASUS_ATTR_RW(_attrname, current_value); \ + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ + __ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", _possible); \ + static struct kobj_attribute attr_##_attrname##_type = \ + __ASUS_ATTR_RO_AS(type, enum_type_show); \ + static struct attribute *_attrname##_attrs[] = { \ + &attr_##_attrname##_current_value.attr, \ + &attr_##_attrname##_display_name.attr, \ + &attr_##_attrname##_possible_values.attr, \ + &attr_##_attrname##_type.attr, \ + NULL \ + }; \ + static const struct attribute_group _attrname##_attr_group = { \ + .name = _fsname, .attrs = _attrname##_attrs \ + } + +/* Boolean style enumeration, base macro. Requires adding show/store */ +#define __ATTR_GROUP_ENUM(_attrname, _fsname, _possible, _dispname) \ + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ + __ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", _possible); \ + static struct kobj_attribute attr_##_attrname##_type = \ + __ASUS_ATTR_RO_AS(type, enum_type_show); \ + static struct attribute *_attrname##_attrs[] = { \ + &attr_##_attrname##_current_value.attr, \ + &attr_##_attrname##_display_name.attr, \ + &attr_##_attrname##_possible_values.attr, \ + &attr_##_attrname##_type.attr, \ + NULL \ + }; \ + static const struct attribute_group _attrname##_attr_group = { \ + .name = _fsname, .attrs = _attrname##_attrs \ + } + +#define ATTR_GROUP_BOOL_RO(_attrname, _fsname, _wmi, _dispname) \ + __ATTR_RO_INT_GROUP_ENUM(_attrname, _wmi, _fsname, "0;1", _dispname) + + +#define ATTR_GROUP_BOOL_RW(_attrname, _fsname, _wmi, _dispname) \ + __ATTR_RW_INT_GROUP_ENUM(_attrname, 0, 1, _wmi, _fsname, "0;1", _dispname) + +#define ATTR_GROUP_ENUM_INT_RO(_attrname, _fsname, _wmi, _possible, _dispname) \ + __ATTR_RO_INT_GROUP_ENUM(_attrname, _wmi, _fsname, _possible, _dispname) + +/* + * Requires _current_value_show(), _current_value_show() + */ +#define ATTR_GROUP_BOOL_CUSTOM(_attrname, _fsname, _dispname) \ + static struct kobj_attribute attr_##_attrname##_current_value = \ + __ASUS_ATTR_RW(_attrname, current_value); \ + __ATTR_GROUP_ENUM(_attrname, _fsname, "0;1", _dispname) + +/* + * Requires _current_value_show(), _current_value_show() + * and _possible_values_show() + */ +#define ATTR_GROUP_ENUM_CUSTOM(_attrname, _fsname, _dispname) \ + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ + static struct kobj_attribute attr_##_attrname##_current_value = \ + __ASUS_ATTR_RW(_attrname, current_value); \ + static struct kobj_attribute attr_##_attrname##_possible_values = \ + __ASUS_ATTR_RO(_attrname, possible_values); \ + static struct kobj_attribute attr_##_attrname##_type = \ + __ASUS_ATTR_RO_AS(type, enum_type_show); \ + static struct attribute *_attrname##_attrs[] = { \ + &attr_##_attrname##_current_value.attr, \ + &attr_##_attrname##_display_name.attr, \ + &attr_##_attrname##_possible_values.attr, \ + &attr_##_attrname##_type.attr, \ + NULL \ + }; \ + static const struct attribute_group _attrname##_attr_group = { \ + .name = _fsname, .attrs = _attrname##_attrs \ + } + +/* CPU core attributes need a little different in setup */ +#define ATTR_GROUP_CORES_RW(_attrname, _fsname, _dispname) \ + __ATTR_SHOW_FMT(scalar_increment, _attrname, "%d\n", 1); \ + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ + static struct kobj_attribute attr_##_attrname##_current_value = \ + __ASUS_ATTR_RW(_attrname, current_value); \ + static struct kobj_attribute attr_##_attrname##_default_value = \ + __ASUS_ATTR_RO(_attrname, default_value); \ + static struct kobj_attribute attr_##_attrname##_min_value = \ + __ASUS_ATTR_RO(_attrname, min_value); \ + static struct kobj_attribute attr_##_attrname##_max_value = \ + __ASUS_ATTR_RO(_attrname, max_value); \ + static struct kobj_attribute attr_##_attrname##_type = \ + __ASUS_ATTR_RO_AS(type, int_type_show); \ + static struct attribute *_attrname##_attrs[] = { \ + &attr_##_attrname##_current_value.attr, \ + &attr_##_attrname##_default_value.attr, \ + &attr_##_attrname##_min_value.attr, \ + &attr_##_attrname##_max_value.attr, \ + &attr_##_attrname##_scalar_increment.attr, \ + &attr_##_attrname##_display_name.attr, \ + &attr_##_attrname##_type.attr, \ + NULL \ + }; \ + static const struct attribute_group _attrname##_attr_group = { \ + .name = _fsname, .attrs = _attrname##_attrs \ + } + +#define ATTR_GROUP_INT_VALUE_ONLY_RO(_attrname, _fsname, _wmi, _dispname) \ + WMI_SHOW_INT(_attrname##_current_value, "%d\n", _wmi); \ + static struct kobj_attribute attr_##_attrname##_current_value = \ + __ASUS_ATTR_RO(_attrname, current_value); \ + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ + static struct kobj_attribute attr_##_attrname##_type = \ + __ASUS_ATTR_RO_AS(type, int_type_show); \ + static struct attribute *_attrname##_attrs[] = { \ + &attr_##_attrname##_current_value.attr, \ + &attr_##_attrname##_display_name.attr, \ + &attr_##_attrname##_type.attr, NULL \ + }; \ + static const struct attribute_group _attrname##_attr_group = { \ + .name = _fsname, .attrs = _attrname##_attrs \ + } + +/* + * ROG PPT attributes need a little different in setup as they + * require rog_tunables members. + */ + +#define __ROG_TUNABLE_SHOW(_prop, _attrname, _val) \ + static ssize_t _attrname##_##_prop##_show( \ + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ + { \ + struct rog_tunables *tunables = get_current_tunables(); \ + \ + if (!tunables || !tunables->power_limits) \ + return -ENODEV; \ + \ + return sysfs_emit(buf, "%d\n", tunables->power_limits->_val); \ + } \ + static struct kobj_attribute attr_##_attrname##_##_prop = \ + __ASUS_ATTR_RO(_attrname, _prop) + +#define __ROG_TUNABLE_SHOW_DEFAULT(_attrname) \ + static ssize_t _attrname##_default_value_show( \ + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ + { \ + struct rog_tunables *tunables = get_current_tunables(); \ + \ + if (!tunables || !tunables->power_limits) \ + return -ENODEV; \ + \ + return sysfs_emit( \ + buf, "%d\n", \ + tunables->power_limits->_attrname##_def ? \ + tunables->power_limits->_attrname##_def : \ + tunables->power_limits->_attrname##_max); \ + } \ + static struct kobj_attribute attr_##_attrname##_default_value = \ + __ASUS_ATTR_RO(_attrname, default_value) + +#define __ROG_TUNABLE_RW(_attr, _wmi) \ + static ssize_t _attr##_current_value_store( \ + struct kobject *kobj, struct kobj_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct rog_tunables *tunables = get_current_tunables(); \ + \ + if (!tunables || !tunables->power_limits) \ + return -ENODEV; \ + \ + return attr_uint_store(kobj, attr, buf, count, \ + tunables->power_limits->_attr##_min, \ + tunables->power_limits->_attr##_max, \ + &tunables->_attr, _wmi); \ + } \ + static ssize_t _attr##_current_value_show( \ + struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ + { \ + struct rog_tunables *tunables = get_current_tunables(); \ + \ + if (!tunables) \ + return -ENODEV; \ + \ + return sysfs_emit(buf, "%u\n", tunables->_attr); \ + } \ + static struct kobj_attribute attr_##_attr##_current_value = \ + __ASUS_ATTR_RW(_attr, current_value) + +#define ATTR_GROUP_ROG_TUNABLE(_attrname, _fsname, _wmi, _dispname) \ + __ROG_TUNABLE_RW(_attrname, _wmi); \ + __ROG_TUNABLE_SHOW_DEFAULT(_attrname); \ + __ROG_TUNABLE_SHOW(min_value, _attrname, _attrname##_min); \ + __ROG_TUNABLE_SHOW(max_value, _attrname, _attrname##_max); \ + __ATTR_SHOW_FMT(scalar_increment, _attrname, "%d\n", 1); \ + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ + static struct kobj_attribute attr_##_attrname##_type = \ + __ASUS_ATTR_RO_AS(type, int_type_show); \ + static struct attribute *_attrname##_attrs[] = { \ + &attr_##_attrname##_current_value.attr, \ + &attr_##_attrname##_default_value.attr, \ + &attr_##_attrname##_min_value.attr, \ + &attr_##_attrname##_max_value.attr, \ + &attr_##_attrname##_scalar_increment.attr, \ + &attr_##_attrname##_display_name.attr, \ + &attr_##_attrname##_type.attr, \ + NULL \ + }; \ + static const struct attribute_group _attrname##_attr_group = { \ + .name = _fsname, .attrs = _attrname##_attrs \ + } + +/* Default is always the maximum value unless *_def is specified */ +struct power_limits { + u8 ppt_pl1_spl_min; + u8 ppt_pl1_spl_def; + u8 ppt_pl1_spl_max; + u8 ppt_pl2_sppt_min; + u8 ppt_pl2_sppt_def; + u8 ppt_pl2_sppt_max; + u8 ppt_pl3_fppt_min; + u8 ppt_pl3_fppt_def; + u8 ppt_pl3_fppt_max; + u8 ppt_apu_sppt_min; + u8 ppt_apu_sppt_def; + u8 ppt_apu_sppt_max; + u8 ppt_platform_sppt_min; + u8 ppt_platform_sppt_def; + u8 ppt_platform_sppt_max; + /* Nvidia GPU specific, default is always max */ + u8 nv_dynamic_boost_def; // unused. exists for macro + u8 nv_dynamic_boost_min; + u8 nv_dynamic_boost_max; + u8 nv_temp_target_def; // unused. exists for macro + u8 nv_temp_target_min; + u8 nv_temp_target_max; + u8 nv_tgp_def; // unused. exists for macro + u8 nv_tgp_min; + u8 nv_tgp_max; +}; + +struct power_data { + const struct power_limits *ac_data; + const struct power_limits *dc_data; + bool requires_fan_curve; +}; + +/* + * For each avilable attribute there must be a min and a max. + * _def is not required and will be assumed to be default == max if missing. + */ +static const struct dmi_system_id power_limits[] = { + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA401W"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_max = 80, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 80, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_tgp_min = 55, + .nv_tgp_max = 75, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 30, + .ppt_pl2_sppt_min = 31, + .ppt_pl2_sppt_max = 44, + .ppt_pl3_fppt_min = 45, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA507N"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_max = 80, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 80, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_def = 45, + .ppt_pl1_spl_max = 65, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_def = 54, + .ppt_pl2_sppt_max = 65, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA507R"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 80, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 80 + }, + .dc_data = NULL + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA507X"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_max = 80, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 80, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 20, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_tgp_min = 55, + .nv_tgp_max = 85, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_def = 45, + .ppt_pl1_spl_max = 65, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_def = 54, + .ppt_pl2_sppt_max = 65, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA507Z"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_max = 65, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 105, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 15, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_tgp_min = 55, + .nv_tgp_max = 85, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 45, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_max = 60, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA607P"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 30, + .ppt_pl1_spl_def = 100, + .ppt_pl1_spl_max = 135, + .ppt_pl2_sppt_min = 30, + .ppt_pl2_sppt_def = 115, + .ppt_pl2_sppt_max = 135, + .ppt_pl3_fppt_min = 30, + .ppt_pl3_fppt_max = 135, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_tgp_min = 55, + .nv_tgp_max = 115, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_def = 45, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_def = 60, + .ppt_pl2_sppt_max = 80, + .ppt_pl3_fppt_min = 25, + .ppt_pl3_fppt_max = 80, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA617NS"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_apu_sppt_min = 15, + .ppt_apu_sppt_max = 80, + .ppt_platform_sppt_min = 30, + .ppt_platform_sppt_max = 120 + }, + .dc_data = &(struct power_limits) { + .ppt_apu_sppt_min = 25, + .ppt_apu_sppt_max = 35, + .ppt_platform_sppt_min = 45, + .ppt_platform_sppt_max = 100 + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA617NT"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_apu_sppt_min = 15, + .ppt_apu_sppt_max = 80, + .ppt_platform_sppt_min = 30, + .ppt_platform_sppt_max = 115 + }, + .dc_data = &(struct power_limits) { + .ppt_apu_sppt_min = 15, + .ppt_apu_sppt_max = 45, + .ppt_platform_sppt_min = 30, + .ppt_platform_sppt_max = 50 + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA617XS"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_apu_sppt_min = 15, + .ppt_apu_sppt_max = 80, + .ppt_platform_sppt_min = 30, + .ppt_platform_sppt_max = 120, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_apu_sppt_min = 25, + .ppt_apu_sppt_max = 35, + .ppt_platform_sppt_min = 45, + .ppt_platform_sppt_max = 100, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FX507Z"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_max = 90, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 135, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 15, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 45, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_max = 60, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GA401Q"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 15, + .ppt_pl2_sppt_max = 80, + }, + .dc_data = NULL + }, + }, + { + .matches = { + // This model is full AMD. No Nvidia dGPU. + DMI_MATCH(DMI_BOARD_NAME, "GA402R"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_apu_sppt_min = 15, + .ppt_apu_sppt_max = 80, + .ppt_platform_sppt_min = 30, + .ppt_platform_sppt_max = 115, + }, + .dc_data = &(struct power_limits) { + .ppt_apu_sppt_min = 25, + .ppt_apu_sppt_def = 30, + .ppt_apu_sppt_max = 45, + .ppt_platform_sppt_min = 40, + .ppt_platform_sppt_max = 60, + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GA402X"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_def = 35, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_def = 65, + .ppt_pl2_sppt_max = 80, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 80, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 35, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 35, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GA403U"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 80, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 80, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_tgp_min = 55, + .nv_tgp_max = 65, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 35, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 35, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GA503R"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_def = 35, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_def = 65, + .ppt_pl2_sppt_max = 80, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 80, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 20, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_def = 25, + .ppt_pl1_spl_max = 65, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_def = 54, + .ppt_pl2_sppt_max = 60, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 65 + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GA605W"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_max = 80, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 80, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 20, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_tgp_min = 55, + .nv_tgp_max = 85, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 35, + .ppt_pl2_sppt_min = 31, + .ppt_pl2_sppt_max = 44, + .ppt_pl3_fppt_min = 45, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GU603Z"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 60, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 135, + /* Only allowed in AC mode */ + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 20, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 40, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 40, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GU604V"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 65, + .ppt_pl1_spl_max = 120, + .ppt_pl2_sppt_min = 65, + .ppt_pl2_sppt_max = 150, + /* Only allowed in AC mode */ + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 40, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_def = 40, + .ppt_pl2_sppt_max = 60, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GU605M"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_max = 90, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 135, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 20, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 35, + .ppt_pl2_sppt_min = 38, + .ppt_pl2_sppt_max = 53, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GV301Q"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 45, + .ppt_pl2_sppt_min = 65, + .ppt_pl2_sppt_max = 80, + }, + .dc_data = NULL + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GV301R"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 45, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 54, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 35, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 35, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GV601R"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_def = 35, + .ppt_pl1_spl_max = 90, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_def = 54, + .ppt_pl2_sppt_max = 100, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_def = 80, + .ppt_pl3_fppt_max = 125, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_def = 28, + .ppt_pl1_spl_max = 65, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_def = 54, + .ppt_pl2_sppt_def = 40, + .ppt_pl2_sppt_max = 60, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_def = 80, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GV601V"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_def = 100, + .ppt_pl1_spl_max = 110, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 135, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 20, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 40, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_def = 40, + .ppt_pl2_sppt_max = 60, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GX650P"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_def = 110, + .ppt_pl1_spl_max = 130, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_def = 125, + .ppt_pl2_sppt_max = 130, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_def = 125, + .ppt_pl3_fppt_max = 135, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_def = 25, + .ppt_pl1_spl_max = 65, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_def = 35, + .ppt_pl2_sppt_max = 65, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_def = 42, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G513I"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + /* Yes this laptop is very limited */ + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 15, + .ppt_pl2_sppt_max = 80, + }, + .dc_data = NULL, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G513QM"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + /* Yes this laptop is very limited */ + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 100, + .ppt_pl2_sppt_min = 15, + .ppt_pl2_sppt_max = 190, + }, + .dc_data = NULL, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G513R"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 35, + .ppt_pl1_spl_max = 90, + .ppt_pl2_sppt_min = 54, + .ppt_pl2_sppt_max = 100, + .ppt_pl3_fppt_min = 54, + .ppt_pl3_fppt_max = 125, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_max = 50, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 50, + .ppt_pl3_fppt_min = 28, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G614J"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_max = 140, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 175, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 55, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 70, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G634J"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_max = 140, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 175, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 55, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 70, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G733C"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_max = 170, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 175, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_max = 35, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 35, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G733P"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 30, + .ppt_pl1_spl_def = 100, + .ppt_pl1_spl_max = 130, + .ppt_pl2_sppt_min = 65, + .ppt_pl2_sppt_def = 125, + .ppt_pl2_sppt_max = 130, + .ppt_pl3_fppt_min = 65, + .ppt_pl3_fppt_def = 125, + .ppt_pl3_fppt_max = 130, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 65, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 65, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 75, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G814J"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_max = 140, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 140, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 55, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 70, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G834J"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_max = 140, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 175, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 55, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 70, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "H7606W"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_max = 80, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 80, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 20, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_tgp_min = 55, + .nv_tgp_max = 85, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 35, + .ppt_pl2_sppt_min = 31, + .ppt_pl2_sppt_max = 44, + .ppt_pl3_fppt_min = 45, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "RC71"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 7, + .ppt_pl1_spl_max = 30, + .ppt_pl2_sppt_min = 15, + .ppt_pl2_sppt_max = 43, + .ppt_pl3_fppt_min = 15, + .ppt_pl3_fppt_max = 53 + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 7, + .ppt_pl1_spl_def = 15, + .ppt_pl1_spl_max = 25, + .ppt_pl2_sppt_min = 15, + .ppt_pl2_sppt_def = 20, + .ppt_pl2_sppt_max = 30, + .ppt_pl3_fppt_min = 15, + .ppt_pl3_fppt_def = 25, + .ppt_pl3_fppt_max = 35 + } + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "RC72"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 7, + .ppt_pl1_spl_max = 30, + .ppt_pl2_sppt_min = 15, + .ppt_pl2_sppt_max = 43, + .ppt_pl3_fppt_min = 15, + .ppt_pl3_fppt_max = 53 + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 7, + .ppt_pl1_spl_def = 17, + .ppt_pl1_spl_max = 25, + .ppt_pl2_sppt_min = 15, + .ppt_pl2_sppt_def = 24, + .ppt_pl2_sppt_max = 30, + .ppt_pl3_fppt_min = 15, + .ppt_pl3_fppt_def = 30, + .ppt_pl3_fppt_max = 35 + } + }, + }, + {} +}; + +#endif /* _ASUS_ARMOURY_H_ */ diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index f66d152e265d..cec509171971 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -55,8 +55,6 @@ module_param(fnlock_default, bool, 0444); #define to_asus_wmi_driver(pdrv) \ (container_of((pdrv), struct asus_wmi_driver, platform_driver)) -#define ASUS_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66" - #define NOTIFY_BRNUP_MIN 0x11 #define NOTIFY_BRNUP_MAX 0x1f #define NOTIFY_BRNDOWN_MIN 0x20 @@ -105,8 +103,6 @@ module_param(fnlock_default, bool, 0444); #define USB_INTEL_XUSB2PR 0xD0 #define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI 0x9c31 -#define ASUS_ACPI_UID_ASUSWMI "ASUSWMI" - #define WMI_EVENT_MASK 0xFFFF #define FAN_CURVE_POINTS 8 @@ -127,7 +123,6 @@ module_param(fnlock_default, bool, 0444); #define NVIDIA_TEMP_MIN 75 #define NVIDIA_TEMP_MAX 87 -#define ASUS_SCREENPAD_BRIGHT_MIN 20 #define ASUS_SCREENPAD_BRIGHT_MAX 255 #define ASUS_SCREENPAD_BRIGHT_DEFAULT 60 @@ -142,16 +137,20 @@ module_param(fnlock_default, bool, 0444); #define ASUS_MINI_LED_2024_STRONG 0x01 #define ASUS_MINI_LED_2024_OFF 0x02 -/* Controls the power state of the USB0 hub on ROG Ally which input is on */ #define ASUS_USB0_PWR_EC0_CSEE "\\_SB.PCI0.SBRG.EC0.CSEE" -/* 300ms so far seems to produce a reliable result on AC and battery */ -#define ASUS_USB0_PWR_EC0_CSEE_WAIT 1500 +/* + * The period required to wait after screen off/on/s2idle.check in MS. + * Time here greatly impacts the wake behaviour. Used in suspend/wake. + */ +#define ASUS_USB0_PWR_EC0_CSEE_WAIT 600 +#define ASUS_USB0_PWR_EC0_CSEE_OFF 0xB7 +#define ASUS_USB0_PWR_EC0_CSEE_ON 0xB8 static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL }; static int throttle_thermal_policy_write(struct asus_wmi *); -static const struct dmi_system_id asus_ally_mcu_quirk[] = { +static const struct dmi_system_id asus_rog_ally_device[] = { { .matches = { DMI_MATCH(DMI_BOARD_NAME, "RC71L"), @@ -274,9 +273,6 @@ struct asus_wmi { u32 tablet_switch_dev_id; bool tablet_switch_inverted; - /* The ROG Ally device requires the MCU USB device be disconnected before suspend */ - bool ally_mcu_usb_switch; - enum fan_type fan_type; enum fan_type gpu_fan_type; enum fan_type mid_fan_type; @@ -335,6 +331,16 @@ struct asus_wmi { struct asus_wmi_driver *driver; }; +/* Global to allow setting externally without requiring driver data */ +static enum asus_ally_mcu_hack use_ally_mcu_hack = ASUS_WMI_ALLY_MCU_HACK_INIT; + +#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) +static void asus_wmi_show_deprecated(void) +{ + pr_notice_once("Accessing attributes through /sys/bus/platform/asus_wmi is deprecated and will be removed in a future release. Please switch over to /sys/class/firmware_attributes.\n"); +} +#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ + /* WMI ************************************************************************/ static int asus_wmi_evaluate_method3(u32 method_id, @@ -385,7 +391,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval) { return asus_wmi_evaluate_method3(method_id, arg0, arg1, 0, retval); } -EXPORT_SYMBOL_GPL(asus_wmi_evaluate_method); +EXPORT_SYMBOL_NS_GPL(asus_wmi_evaluate_method, "ASUS_WMI"); static int asus_wmi_evaluate_method5(u32 method_id, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4, u32 *retval) @@ -549,12 +555,51 @@ static int asus_wmi_get_devstate(struct asus_wmi *asus, u32 dev_id, u32 *retval) return 0; } -static int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, - u32 *retval) + +/** + * asus_wmi_get_devstate_dsts() - Get the WMI function state. + * @dev_id: The WMI method ID to call. + * @retval: A pointer to where to store the value returned from WMI. + * + * On success the return value is 0, and the retval is a valid value returned + * by the successful WMI function call otherwise an error is returned if the + * call failed, or if the WMI method ID is unsupported. + */ +int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval) +{ + int err; + + err = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, dev_id, 0, retval); + if (err) + return err; + + if (*retval == ASUS_WMI_UNSUPPORTED_METHOD) + return -ENODEV; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(asus_wmi_get_devstate_dsts, "ASUS_WMI"); + +/** + * asus_wmi_set_devstate() - Set the WMI function state. + * @dev_id: The WMI function to call. + * @ctrl_param: The argument to be used for this WMI function. + * @retval: A pointer to where to store the value returned from WMI. + * + * The returned WMI function state if not checked here for error as + * asus_wmi_set_devstate() is not called unless first paired with a call to + * asus_wmi_get_devstate_dsts() to check that the WMI function is supported. + * + * On success the return value is 0, and the retval is a valid value returned + * by the successful WMI function call. An error value is returned only if the + * WMI function failed. + */ +int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval) { return asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS, dev_id, ctrl_param, retval); } +EXPORT_SYMBOL_NS_GPL(asus_wmi_set_devstate, "ASUS_WMI"); /* Helper for special devices with magic return codes */ static int asus_wmi_get_devstate_bits(struct asus_wmi *asus, @@ -687,6 +732,7 @@ static void asus_wmi_tablet_mode_get_state(struct asus_wmi *asus) } /* Charging mode, 1=Barrel, 2=USB ******************************************/ +#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) static ssize_t charge_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -697,12 +743,16 @@ static ssize_t charge_mode_show(struct device *dev, if (result < 0) return result; + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "%d\n", value & 0xff); } static DEVICE_ATTR_RO(charge_mode); +#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ /* dGPU ********************************************************************/ +#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) static ssize_t dgpu_disable_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -713,6 +763,8 @@ static ssize_t dgpu_disable_show(struct device *dev, if (result < 0) return result; + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "%d\n", result); } @@ -766,8 +818,10 @@ static ssize_t dgpu_disable_store(struct device *dev, return count; } static DEVICE_ATTR_RW(dgpu_disable); +#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ /* eGPU ********************************************************************/ +#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) static ssize_t egpu_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -778,6 +832,8 @@ static ssize_t egpu_enable_show(struct device *dev, if (result < 0) return result; + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "%d\n", result); } @@ -834,8 +890,10 @@ static ssize_t egpu_enable_store(struct device *dev, return count; } static DEVICE_ATTR_RW(egpu_enable); +#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ /* Is eGPU connected? *********************************************************/ +#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) static ssize_t egpu_connected_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -846,12 +904,16 @@ static ssize_t egpu_connected_show(struct device *dev, if (result < 0) return result; + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "%d\n", result); } static DEVICE_ATTR_RO(egpu_connected); +#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ /* gpu mux switch *************************************************************/ +#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) static ssize_t gpu_mux_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -862,6 +924,8 @@ static ssize_t gpu_mux_mode_show(struct device *dev, if (result < 0) return result; + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "%d\n", result); } @@ -920,6 +984,7 @@ static ssize_t gpu_mux_mode_store(struct device *dev, return count; } static DEVICE_ATTR_RW(gpu_mux_mode); +#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ /* TUF Laptop Keyboard RGB Modes **********************************************/ static ssize_t kbd_rgb_mode_store(struct device *dev, @@ -1043,6 +1108,7 @@ static const struct attribute_group *kbd_rgb_mode_groups[] = { }; /* Tunable: PPT: Intel=PL1, AMD=SPPT *****************************************/ +#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) static ssize_t ppt_pl2_sppt_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -1081,6 +1147,8 @@ static ssize_t ppt_pl2_sppt_show(struct device *dev, { struct asus_wmi *asus = dev_get_drvdata(dev); + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "%u\n", asus->ppt_pl2_sppt); } static DEVICE_ATTR_RW(ppt_pl2_sppt); @@ -1123,6 +1191,8 @@ static ssize_t ppt_pl1_spl_show(struct device *dev, { struct asus_wmi *asus = dev_get_drvdata(dev); + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "%u\n", asus->ppt_pl1_spl); } static DEVICE_ATTR_RW(ppt_pl1_spl); @@ -1166,6 +1236,8 @@ static ssize_t ppt_fppt_show(struct device *dev, { struct asus_wmi *asus = dev_get_drvdata(dev); + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "%u\n", asus->ppt_fppt); } static DEVICE_ATTR_RW(ppt_fppt); @@ -1209,6 +1281,8 @@ static ssize_t ppt_apu_sppt_show(struct device *dev, { struct asus_wmi *asus = dev_get_drvdata(dev); + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "%u\n", asus->ppt_apu_sppt); } static DEVICE_ATTR_RW(ppt_apu_sppt); @@ -1252,6 +1326,8 @@ static ssize_t ppt_platform_sppt_show(struct device *dev, { struct asus_wmi *asus = dev_get_drvdata(dev); + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "%u\n", asus->ppt_platform_sppt); } static DEVICE_ATTR_RW(ppt_platform_sppt); @@ -1295,6 +1371,8 @@ static ssize_t nv_dynamic_boost_show(struct device *dev, { struct asus_wmi *asus = dev_get_drvdata(dev); + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "%u\n", asus->nv_dynamic_boost); } static DEVICE_ATTR_RW(nv_dynamic_boost); @@ -1338,11 +1416,53 @@ static ssize_t nv_temp_target_show(struct device *dev, { struct asus_wmi *asus = dev_get_drvdata(dev); + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "%u\n", asus->nv_temp_target); } static DEVICE_ATTR_RW(nv_temp_target); +#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ /* Ally MCU Powersave ********************************************************/ + +/* + * The HID driver needs to check MCU version and set this to false if the MCU FW + * version is >= the minimum requirements. New FW do not need the hacks. + */ +void set_ally_mcu_hack(enum asus_ally_mcu_hack status) +{ + use_ally_mcu_hack = status; + pr_debug("%s Ally MCU suspend quirk\n", + status == ASUS_WMI_ALLY_MCU_HACK_ENABLED ? "Enabled" : "Disabled"); +} +EXPORT_SYMBOL_NS_GPL(set_ally_mcu_hack, "ASUS_WMI"); + +/* + * mcu_powersave should be enabled always, as it is fixed in MCU FW versions: + * - v313 for Ally X + * - v319 for Ally 1 + * The HID driver checks MCU versions and so should set this if requirements match + */ +void set_ally_mcu_powersave(bool enabled) +{ + int result, err; + + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MCU_POWERSAVE, enabled, &result); + if (err) { + pr_warn("Failed to set MCU powersave: %d\n", err); + return; + } + if (result > 1) { + pr_warn("Failed to set MCU powersave (result): 0x%x\n", result); + return; + } + + pr_debug("%s MCU Powersave\n", + enabled ? "Enabled" : "Disabled"); +} +EXPORT_SYMBOL_NS_GPL(set_ally_mcu_powersave, "ASUS_WMI"); + +#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) static ssize_t mcu_powersave_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1353,6 +1473,8 @@ static ssize_t mcu_powersave_show(struct device *dev, if (result < 0) return result; + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "%d\n", result); } @@ -1388,6 +1510,7 @@ static ssize_t mcu_powersave_store(struct device *dev, return count; } static DEVICE_ATTR_RW(mcu_powersave); +#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ /* Battery ********************************************************************/ @@ -2261,6 +2384,7 @@ static int asus_wmi_rfkill_init(struct asus_wmi *asus) } /* Panel Overdrive ************************************************************/ +#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) static ssize_t panel_od_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -2271,6 +2395,8 @@ static ssize_t panel_od_show(struct device *dev, if (result < 0) return result; + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "%d\n", result); } @@ -2307,9 +2433,10 @@ static ssize_t panel_od_store(struct device *dev, return count; } static DEVICE_ATTR_RW(panel_od); +#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ /* Bootup sound ***************************************************************/ - +#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) static ssize_t boot_sound_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -2320,6 +2447,8 @@ static ssize_t boot_sound_show(struct device *dev, if (result < 0) return result; + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "%d\n", result); } @@ -2355,8 +2484,10 @@ static ssize_t boot_sound_store(struct device *dev, return count; } static DEVICE_ATTR_RW(boot_sound); +#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ /* Mini-LED mode **************************************************************/ +#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) static ssize_t mini_led_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -2387,6 +2518,8 @@ static ssize_t mini_led_mode_show(struct device *dev, } } + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "%d\n", value); } @@ -2457,10 +2590,13 @@ static ssize_t available_mini_led_mode_show(struct device *dev, return sysfs_emit(buf, "0 1 2\n"); } + asus_wmi_show_deprecated(); + return sysfs_emit(buf, "0\n"); } static DEVICE_ATTR_RO(available_mini_led_mode); +#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ /* Quirks *********************************************************************/ @@ -3748,6 +3884,7 @@ static int throttle_thermal_policy_set_default(struct asus_wmi *asus) return throttle_thermal_policy_write(asus); } +#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) static ssize_t throttle_thermal_policy_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -3791,6 +3928,7 @@ static ssize_t throttle_thermal_policy_store(struct device *dev, * Throttle thermal policy: 0 - default, 1 - overboost, 2 - silent */ static DEVICE_ATTR_RW(throttle_thermal_policy); +#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ /* Platform profile ***********************************************************/ static int asus_wmi_platform_profile_get(struct device *dev, @@ -3810,7 +3948,7 @@ static int asus_wmi_platform_profile_get(struct device *dev, *profile = PLATFORM_PROFILE_PERFORMANCE; break; case ASUS_THROTTLE_THERMAL_POLICY_SILENT: - *profile = PLATFORM_PROFILE_QUIET; + *profile = PLATFORM_PROFILE_LOW_POWER; break; default: return -EINVAL; @@ -3834,7 +3972,7 @@ static int asus_wmi_platform_profile_set(struct device *dev, case PLATFORM_PROFILE_BALANCED: tp = ASUS_THROTTLE_THERMAL_POLICY_DEFAULT; break; - case PLATFORM_PROFILE_QUIET: + case PLATFORM_PROFILE_LOW_POWER: tp = ASUS_THROTTLE_THERMAL_POLICY_SILENT; break; default: @@ -3847,7 +3985,7 @@ static int asus_wmi_platform_profile_set(struct device *dev, static int asus_wmi_platform_profile_probe(void *drvdata, unsigned long *choices) { - set_bit(PLATFORM_PROFILE_QUIET, choices); + set_bit(PLATFORM_PROFILE_LOW_POWER, choices); set_bit(PLATFORM_PROFILE_BALANCED, choices); set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); @@ -4100,43 +4238,37 @@ static int read_screenpad_brightness(struct backlight_device *bd) return err; /* The device brightness can only be read if powered, so return stored */ if (err == BACKLIGHT_POWER_OFF) - return asus->driver->screenpad_brightness - ASUS_SCREENPAD_BRIGHT_MIN; + return bd->props.brightness; err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_LIGHT, &retval); if (err < 0) return err; - return (retval & ASUS_WMI_DSTS_BRIGHTNESS_MASK) - ASUS_SCREENPAD_BRIGHT_MIN; + return (retval & ASUS_WMI_DSTS_BRIGHTNESS_MASK); } static int update_screenpad_bl_status(struct backlight_device *bd) { - struct asus_wmi *asus = bl_get_data(bd); - int power, err = 0; + int err = 0; u32 ctrl_param; - power = read_screenpad_backlight_power(asus); - if (power < 0) - return power; - - if (bd->props.power != power) { - if (power != BACKLIGHT_POWER_ON) { - /* Only brightness > 0 can power it back on */ - ctrl_param = asus->driver->screenpad_brightness - ASUS_SCREENPAD_BRIGHT_MIN; - err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT, - ctrl_param, NULL); - } else { - err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 0, NULL); - } - } else if (power == BACKLIGHT_POWER_ON) { - /* Only set brightness if powered on or we get invalid/unsync state */ - ctrl_param = bd->props.brightness + ASUS_SCREENPAD_BRIGHT_MIN; + ctrl_param = bd->props.brightness; + if (ctrl_param >= 0 && bd->props.power) { + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 1, + NULL); + if (err < 0) + return err; + ctrl_param = bd->props.brightness; err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT, ctrl_param, NULL); + if (err < 0) + return err; } - /* Ensure brightness is stored to turn back on with */ - if (err == 0) - asus->driver->screenpad_brightness = bd->props.brightness + ASUS_SCREENPAD_BRIGHT_MIN; + if (!bd->props.power) { + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 0, NULL); + if (err < 0) + return err; + } return err; } @@ -4154,22 +4286,19 @@ static int asus_screenpad_init(struct asus_wmi *asus) int err, power; int brightness = 0; - power = read_screenpad_backlight_power(asus); + power = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_SCREENPAD_POWER); if (power < 0) return power; - if (power != BACKLIGHT_POWER_OFF) { + if (power) { err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_LIGHT, &brightness); if (err < 0) return err; } - /* default to an acceptable min brightness on boot if too low */ - if (brightness < ASUS_SCREENPAD_BRIGHT_MIN) - brightness = ASUS_SCREENPAD_BRIGHT_DEFAULT; memset(&props, 0, sizeof(struct backlight_properties)); props.type = BACKLIGHT_RAW; /* ensure this bd is last to be picked */ - props.max_brightness = ASUS_SCREENPAD_BRIGHT_MAX - ASUS_SCREENPAD_BRIGHT_MIN; + props.max_brightness = ASUS_SCREENPAD_BRIGHT_MAX; bd = backlight_device_register("asus_screenpad", &asus->platform_device->dev, asus, &asus_screenpad_bl_ops, &props); @@ -4180,7 +4309,7 @@ static int asus_screenpad_init(struct asus_wmi *asus) asus->screenpad_backlight_device = bd; asus->driver->screenpad_brightness = brightness; - bd->props.brightness = brightness - ASUS_SCREENPAD_BRIGHT_MIN; + bd->props.brightness = brightness; bd->props.power = power; backlight_update_status(bd); @@ -4392,27 +4521,29 @@ static struct attribute *platform_attributes[] = { &dev_attr_camera.attr, &dev_attr_cardr.attr, &dev_attr_touchpad.attr, - &dev_attr_charge_mode.attr, - &dev_attr_egpu_enable.attr, - &dev_attr_egpu_connected.attr, - &dev_attr_dgpu_disable.attr, - &dev_attr_gpu_mux_mode.attr, &dev_attr_lid_resume.attr, &dev_attr_als_enable.attr, &dev_attr_fan_boost_mode.attr, - &dev_attr_throttle_thermal_policy.attr, - &dev_attr_ppt_pl2_sppt.attr, - &dev_attr_ppt_pl1_spl.attr, - &dev_attr_ppt_fppt.attr, - &dev_attr_ppt_apu_sppt.attr, - &dev_attr_ppt_platform_sppt.attr, - &dev_attr_nv_dynamic_boost.attr, - &dev_attr_nv_temp_target.attr, - &dev_attr_mcu_powersave.attr, - &dev_attr_boot_sound.attr, - &dev_attr_panel_od.attr, - &dev_attr_mini_led_mode.attr, - &dev_attr_available_mini_led_mode.attr, +#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) + &dev_attr_charge_mode.attr, + &dev_attr_egpu_enable.attr, + &dev_attr_egpu_connected.attr, + &dev_attr_dgpu_disable.attr, + &dev_attr_gpu_mux_mode.attr, + &dev_attr_ppt_pl2_sppt.attr, + &dev_attr_ppt_pl1_spl.attr, + &dev_attr_ppt_fppt.attr, + &dev_attr_ppt_apu_sppt.attr, + &dev_attr_ppt_platform_sppt.attr, + &dev_attr_nv_dynamic_boost.attr, + &dev_attr_nv_temp_target.attr, + &dev_attr_mcu_powersave.attr, + &dev_attr_boot_sound.attr, + &dev_attr_panel_od.attr, + &dev_attr_mini_led_mode.attr, + &dev_attr_available_mini_led_mode.attr, + &dev_attr_throttle_thermal_policy.attr, +#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ NULL }; @@ -4434,7 +4565,11 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, devid = ASUS_WMI_DEVID_LID_RESUME; else if (attr == &dev_attr_als_enable.attr) devid = ASUS_WMI_DEVID_ALS_ENABLE; - else if (attr == &dev_attr_charge_mode.attr) + else if (attr == &dev_attr_fan_boost_mode.attr) + ok = asus->fan_boost_mode_available; + +#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) + if (attr == &dev_attr_charge_mode.attr) devid = ASUS_WMI_DEVID_CHARGE_MODE; else if (attr == &dev_attr_egpu_enable.attr) ok = asus->egpu_enable_available; @@ -4472,6 +4607,7 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, ok = asus->mini_led_dev_id != 0; else if (attr == &dev_attr_available_mini_led_mode.attr) ok = asus->mini_led_dev_id != 0; +#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ if (devid != -1) { ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0); @@ -4711,7 +4847,23 @@ static int asus_wmi_add(struct platform_device *pdev) if (err) goto fail_platform; + if (use_ally_mcu_hack == ASUS_WMI_ALLY_MCU_HACK_INIT) { + if (acpi_has_method(NULL, ASUS_USB0_PWR_EC0_CSEE) + && dmi_check_system(asus_rog_ally_device)) + use_ally_mcu_hack = ASUS_WMI_ALLY_MCU_HACK_ENABLED; + if (dmi_match(DMI_BOARD_NAME, "RC71")) { + /* + * These steps ensure the device is in a valid good state, this is + * especially important for the Ally 1 after a reboot. + */ + acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, + ASUS_USB0_PWR_EC0_CSEE_ON); + msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT); + } + } + /* ensure defaults for tunables */ +#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) asus->ppt_pl2_sppt = 5; asus->ppt_pl1_spl = 5; asus->ppt_apu_sppt = 5; @@ -4723,8 +4875,6 @@ static int asus_wmi_add(struct platform_device *pdev) asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU); asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU); asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE); - asus->ally_mcu_usb_switch = acpi_has_method(NULL, ASUS_USB0_PWR_EC0_CSEE) - && dmi_check_system(asus_ally_mcu_quirk); if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE)) asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE; @@ -4735,17 +4885,18 @@ static int asus_wmi_add(struct platform_device *pdev) asus->gpu_mux_dev = ASUS_WMI_DEVID_GPU_MUX; else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX_VIVO)) asus->gpu_mux_dev = ASUS_WMI_DEVID_GPU_MUX_VIVO; - - if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE)) - asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE; - else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE2)) - asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE2; +#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY)) asus->throttle_thermal_policy_dev = ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY; else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO)) asus->throttle_thermal_policy_dev = ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO; + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE)) + asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE; + else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE2)) + asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE2; + err = fan_boost_mode_check_present(asus); if (err) goto fail_fan_boost_mode; @@ -4911,34 +5062,6 @@ static int asus_hotk_resume(struct device *device) return 0; } -static int asus_hotk_resume_early(struct device *device) -{ - struct asus_wmi *asus = dev_get_drvdata(device); - - if (asus->ally_mcu_usb_switch) { - /* sleep required to prevent USB0 being yanked then reappearing rapidly */ - if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB8))) - dev_err(device, "ROG Ally MCU failed to connect USB dev\n"); - else - msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT); - } - return 0; -} - -static int asus_hotk_prepare(struct device *device) -{ - struct asus_wmi *asus = dev_get_drvdata(device); - - if (asus->ally_mcu_usb_switch) { - /* sleep required to ensure USB0 is disabled before sleep continues */ - if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB7))) - dev_err(device, "ROG Ally MCU failed to disconnect USB dev\n"); - else - msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT); - } - return 0; -} - static int asus_hotk_restore(struct device *device) { struct asus_wmi *asus = dev_get_drvdata(device); @@ -4979,11 +5102,34 @@ static int asus_hotk_restore(struct device *device) return 0; } +static void asus_ally_s2idle_restore(void) +{ + if (use_ally_mcu_hack == ASUS_WMI_ALLY_MCU_HACK_ENABLED) { + acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, + ASUS_USB0_PWR_EC0_CSEE_ON); + msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT); + } +} + +static int asus_hotk_prepare(struct device *device) +{ + if (use_ally_mcu_hack == ASUS_WMI_ALLY_MCU_HACK_ENABLED) { + acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, + ASUS_USB0_PWR_EC0_CSEE_OFF); + msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT); + } + return 0; +} + +/* Use only for Ally devices due to the wake_on_ac */ +static struct acpi_s2idle_dev_ops asus_ally_s2idle_dev_ops = { + .restore = asus_ally_s2idle_restore, +}; + static const struct dev_pm_ops asus_pm_ops = { .thaw = asus_hotk_thaw, .restore = asus_hotk_restore, .resume = asus_hotk_resume, - .resume_early = asus_hotk_resume_early, .prepare = asus_hotk_prepare, }; @@ -5011,6 +5157,10 @@ static int asus_wmi_probe(struct platform_device *pdev) return ret; } + ret = acpi_register_lps0_dev(&asus_ally_s2idle_dev_ops); + if (ret) + pr_warn("failed to register LPS0 sleep handler in asus-wmi\n"); + return asus_wmi_add(pdev); } @@ -5043,6 +5193,7 @@ EXPORT_SYMBOL_GPL(asus_wmi_register_driver); void asus_wmi_unregister_driver(struct asus_wmi_driver *driver) { + acpi_unregister_lps0_dev(&asus_ally_s2idle_dev_ops); platform_device_unregister(driver->platform_device); platform_driver_unregister(&driver->platform_driver); used = false; diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index 783e2a336861..78261ea49995 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -6,6 +6,9 @@ #include #include +#define ASUS_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66" +#define ASUS_ACPI_UID_ASUSWMI "ASUSWMI" + /* WMI Methods */ #define ASUS_WMI_METHODID_SPEC 0x43455053 /* BIOS SPECification */ #define ASUS_WMI_METHODID_SFBD 0x44424653 /* Set First Boot Device */ @@ -73,12 +76,14 @@ #define ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO 0x00110019 /* Misc */ +#define ASUS_WMI_DEVID_PANEL_HD 0x0005001C #define ASUS_WMI_DEVID_PANEL_OD 0x00050019 #define ASUS_WMI_DEVID_CAMERA 0x00060013 #define ASUS_WMI_DEVID_LID_FLIP 0x00060062 #define ASUS_WMI_DEVID_LID_FLIP_ROG 0x00060077 #define ASUS_WMI_DEVID_MINI_LED_MODE 0x0005001E #define ASUS_WMI_DEVID_MINI_LED_MODE2 0x0005002E +#define ASUS_WMI_DEVID_SCREEN_AUTO_BRIGHTNESS 0x0005002A /* Storage */ #define ASUS_WMI_DEVID_CARDREADER 0x00080013 @@ -133,6 +138,16 @@ /* dgpu on/off */ #define ASUS_WMI_DEVID_DGPU 0x00090020 +/* Intel E-core and P-core configuration in a format 0x0[E]0[P] */ +#define ASUS_WMI_DEVID_CORES 0x001200D2 + /* Maximum Intel E-core and P-core availability */ +#define ASUS_WMI_DEVID_CORES_MAX 0x001200D3 + +#define ASUS_WMI_DEVID_APU_MEM 0x000600C1 + +#define ASUS_WMI_DEVID_DGPU_BASE_TGP 0x00120099 +#define ASUS_WMI_DEVID_DGPU_SET_TGP 0x00120098 + /* gpu mux switch, 0 = dGPU, 1 = Optimus */ #define ASUS_WMI_DEVID_GPU_MUX 0x00090016 #define ASUS_WMI_DEVID_GPU_MUX_VIVO 0x00090026 @@ -157,9 +172,37 @@ #define ASUS_WMI_DSTS_MAX_BRIGTH_MASK 0x0000FF00 #define ASUS_WMI_DSTS_LIGHTBAR_MASK 0x0000000F +enum asus_ally_mcu_hack { + ASUS_WMI_ALLY_MCU_HACK_INIT, + ASUS_WMI_ALLY_MCU_HACK_ENABLED, + ASUS_WMI_ALLY_MCU_HACK_DISABLED, +}; + #if IS_REACHABLE(CONFIG_ASUS_WMI) +void set_ally_mcu_hack(enum asus_ally_mcu_hack status); +void set_ally_mcu_powersave(bool enabled); +int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval); +int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval); int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval); #else +static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status) +{ +} +static inline void set_ally_mcu_powersave(bool enabled) +{ +} +static inline int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval) +{ + return -ENODEV; +} +static inline int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval) +{ + return -ENODEV; +} +static inline int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval) +{ + return -ENODEV; +} static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval) { -- 2.49.0.634.g8613c2bb6c