diff --git a/Makefile b/Makefile index d9243ad053f9af..5aba9f2b7e5419 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 6 PATCHLEVEL = 9 SUBLEVEL = 12 -EXTRAVERSION = +EXTRAVERSION = -chos9 NAME = Hurr durr I'ma ninja sloth # *DOCUMENTATION* diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index dd0b40b9bbe8be..9f4281c8ff5883 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "../sleep.h" @@ -53,6 +54,7 @@ static const struct acpi_device_id lps0_device_ids[] = { #define ACPI_LPS0_SCREEN_OFF_AMD 4 #define ACPI_LPS0_SCREEN_ON_AMD 5 +static struct acpi_device *lps0_device; static acpi_handle lps0_device_handle; static guid_t lps0_dsm_guid; static int lps0_dsm_func_mask; @@ -60,6 +62,7 @@ static int lps0_dsm_func_mask; static guid_t lps0_dsm_guid_microsoft; static int lps0_dsm_func_mask_microsoft; static int lps0_dsm_state; +static int lps0_ac_state; /* Device constraint entry structure */ struct lpi_device_info { @@ -507,6 +510,8 @@ static int lps0_device_attach(struct acpi_device *adev, return 0; //function evaluation failed lps0_device_handle = adev->handle; + lps0_device = adev; + device_set_wakeup_capable(&adev->dev, true); if (acpi_s2idle_vendor_amd()) lpi_device_get_constraints_amd(); @@ -539,17 +544,21 @@ static struct acpi_scan_handler lps0_handler = { .attach = lps0_device_attach, }; -int acpi_s2idle_prepare_late(void) +static int acpi_s2idle_screen_off(void) { - struct acpi_s2idle_dev_ops *handler; - if (!lps0_device_handle || sleep_no_lps0) return 0; - if (pm_debug_messages_on) - lpi_check_constraints(); + switch (lps0_dsm_state) { + case ACPI_LPS0_SCREEN_OFF_AMD: + case ACPI_LPS0_SCREEN_OFF: + if (pm_debug_messages_on) + acpi_handle_info(lps0_device_handle, + "already in %s\n", + acpi_sleep_dsm_state_to_str(lps0_dsm_state)); + return 0; + } - /* Screen off */ if (lps0_dsm_func_mask > 0) acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ? ACPI_LPS0_SCREEN_OFF_AMD : @@ -560,6 +569,50 @@ int acpi_s2idle_prepare_late(void) acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF, lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + return 0; +} + +static int acpi_s2idle_screen_on(void) +{ + if (!lps0_device_handle || sleep_no_lps0) + return 0; + + switch (lps0_dsm_state) { + case ACPI_LPS0_SCREEN_ON_AMD: + case ACPI_LPS0_SCREEN_ON: + if (pm_debug_messages_on) + acpi_handle_info(lps0_device_handle, + "already in %s\n", + acpi_sleep_dsm_state_to_str(lps0_dsm_state)); + return 0; + } + + if (lps0_dsm_func_mask_microsoft > 0) + acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON, + lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + + if (lps0_dsm_func_mask > 0) + acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ? + ACPI_LPS0_SCREEN_ON_AMD : + ACPI_LPS0_SCREEN_ON, + lps0_dsm_func_mask, lps0_dsm_guid); + + return 0; +} + +int acpi_s2idle_prepare_late(void) +{ + struct acpi_s2idle_dev_ops *handler; + + if (!lps0_device_handle || sleep_no_lps0) + return 0; + + if (pm_debug_messages_on) + lpi_check_constraints(); + + /* capture AC adapter state */ + lps0_ac_state = power_supply_is_system_supplied(); + /* LPS0 entry */ if (lps0_dsm_func_mask > 0 && acpi_s2idle_vendor_amd()) acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY_AMD, @@ -596,6 +649,15 @@ void acpi_s2idle_check(void) if (handler->check) handler->check(); } + + /* if configured, wake system from AC adapter changes */ + if (device_may_wakeup(&lps0_device->dev) && + power_supply_is_system_supplied() != lps0_ac_state) { + if (pm_debug_messages_on) + acpi_handle_info(lps0_device_handle, + "AC adapter state changed\n"); + acpi_pm_wakeup_event(&lps0_device->dev); + } } void acpi_s2idle_restore_early(void) @@ -623,20 +685,18 @@ void acpi_s2idle_restore_early(void) acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT, lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); } +} - /* Screen on */ - if (lps0_dsm_func_mask_microsoft > 0) - acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON, - lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); - if (lps0_dsm_func_mask > 0) - acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ? - ACPI_LPS0_SCREEN_ON_AMD : - ACPI_LPS0_SCREEN_ON, - lps0_dsm_func_mask, lps0_dsm_guid); +static int acpi_x86_s2idle_begin(void) +{ + lps0_dsm_state = -1; + return acpi_s2idle_begin(); } static const struct platform_s2idle_ops acpi_s2idle_ops_lps0 = { - .begin = acpi_s2idle_begin, + .begin = acpi_x86_s2idle_begin, + .screen_off = acpi_s2idle_screen_off, + .screen_on = acpi_s2idle_screen_on, .prepare = acpi_s2idle_prepare, .prepare_late = acpi_s2idle_prepare_late, .check = acpi_s2idle_check, @@ -661,6 +721,8 @@ int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg) sleep_flags = lock_system_sleep(); list_add(&arg->list_node, &lps0_s2idle_devops_head); + if (arg->wake_on_ac) + device_set_wakeup_enable(&lps0_device->dev, true); unlock_system_sleep(sleep_flags); return 0; diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 39ef0a6addeba8..8afabbfe4c1b6f 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -3526,6 +3527,13 @@ struct drm_atomic_state *drm_atomic_helper_suspend(struct drm_device *dev) goto unlock; } + err = platform_suspend_screen_off(); + if (err < 0) { + drm_atomic_state_put(state); + state = ERR_PTR(err); + goto unlock; + } + unlock: DRM_MODESET_LOCK_ALL_END(dev, ctx, err); if (err) @@ -3607,7 +3615,12 @@ int drm_atomic_helper_resume(struct drm_device *dev, DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, err); err = drm_atomic_helper_commit_duplicated_state(state, &ctx); + if (err < 0) + goto unlock; + err = platform_suspend_screen_on(); + +unlock: DRM_MODESET_LOCK_ALL_END(dev, ctx, err); drm_atomic_state_put(state); diff --git a/drivers/gpu/drm/drm_panel_orientation_quirks.c b/drivers/gpu/drm/drm_panel_orientation_quirks.c index c8671fb53d19ea..2d1b520634d3ef 100644 --- a/drivers/gpu/drm/drm_panel_orientation_quirks.c +++ b/drivers/gpu/drm/drm_panel_orientation_quirks.c @@ -190,6 +190,12 @@ static const struct dmi_system_id orientation_data[] = { DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "AYANEO 2"), }, .driver_data = (void *)&lcd1200x1920_rightside_up, + }, { /* AYA NEO AYANEO 2S */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "AYANEO 2S"), + }, + .driver_data = (void *)&lcd1200x1920_rightside_up, }, { /* AYA NEO 2021 */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYADEVICE"), diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 4c682c65070408..a921b16ed8bc46 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -164,6 +164,15 @@ config HID_ASUS - GL553V series - GL753V series +config HID_ASUS_ALLY + tristate "Asus Ally gamepad configuration 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. + config HID_AUREAL tristate "Aureal" help diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 082a728eac6004..f909663151143f 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_HID_APPLE) += hid-apple.o obj-$(CONFIG_HID_APPLEIR) += hid-appleir.o obj-$(CONFIG_HID_CREATIVE_SB0540) += hid-creative-sb0540.o obj-$(CONFIG_HID_ASUS) += hid-asus.o +obj-$(CONFIG_HID_ASUS_ALLY) += hid-asus-ally.o obj-$(CONFIG_HID_AUREAL) += hid-aureal.o obj-$(CONFIG_HID_BELKIN) += hid-belkin.o obj-$(CONFIG_HID_BETOP_FF) += hid-betopff.o diff --git a/drivers/hid/hid-asus-ally.c b/drivers/hid/hid-asus-ally.c new file mode 100644 index 00000000000000..528441a2ca6a66 --- /dev/null +++ b/drivers/hid/hid-asus-ally.c @@ -0,0 +1,2313 @@ +// 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/device.h" +#include "linux/err.h" +#include "linux/input-event-codes.h" +#include "linux/kstrtox.h" +#include "linux/slab.h" +#include "linux/stddef.h" +#include "linux/sysfs.h" +#include +#include +#include +#include +#include + +#include "hid-ids.h" +#include "hid-asus-ally.h" + +#define READY_MAX_TRIES 3 +#define FEATURE_REPORT_ID 0x0d +#define FEATURE_ROG_ALLY_REPORT_ID 0x5a +#define FEATURE_ROG_ALLY_CODE_PAGE 0xD1 +#define FEATURE_ROG_ALLY_REPORT_SIZE 64 +#define ALLY_X_INPUT_REPORT_USB 0x0B +#define ALLY_X_INPUT_REPORT_USB_SIZE 16 + +#define ALLY_CFG_INTF_IN_ADDRESS 0x83 +#define ALLY_CFG_INTF_OUT_ADDRESS 0x04 +#define ALLY_X_INTERFACE_ADDRESS 0x87 + +#define FEATURE_KBD_LED_REPORT_ID1 0x5d +#define FEATURE_KBD_LED_REPORT_ID2 0x5e + +enum ROG_ALLY_TYPE { + ROG_ALLY_TYPE, + ROG_ALLY_TYPE_X, +}; + +static const struct hid_device_id rog_ally_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY), + .driver_data = ROG_ALLY_TYPE }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X), + .driver_data = ROG_ALLY_TYPE_X }, + {} +}; + +struct KeyCode { + const char *label; + u8 code; +}; + +static const struct KeyCode gamepad_codes[] = { + { "PAD_A", 0x01 }, { "PAD_B", 0x02 }, { "PAD_X", 0x03 }, + { "PAD_Y", 0x04 }, { "PAD_LB", 0x05 }, { "PAD_RB", 0x06 }, + { "PAD_LS", 0x07 }, { "PAD_RS", 0x08 }, { "PAD_DPAD_UP", 0x09 }, + { "PAD_DPAD_DOWN", 0x0a }, { "PAD_DPAD_LEFT", 0x0b }, { "PAD_DPAD_RIGHT", 0x0c }, + { "PAD_VIEW", 0x11 }, { "PAD_MENU", 0x12 }, { "PAD_XBOX", 0x13 } +}; + +static const struct KeyCode keyboard_codes[] = { { "KB_M1", 0x8f }, + { "KB_M2", 0x8e }, + { "KB_ESC", 0x76 }, + { "KB_F1", 0x50 }, + { "KB_F2", 0x60 }, + { "KB_F3", 0x40 }, + { "KB_F4", 0x0c }, + { "KB_F5", 0x03 }, + { "KB_F6", 0x0b }, + { "KB_F7", 0x80 }, + { "KB_F8", 0x0a }, + { "KB_F9", 0x01 }, + { "KB_F10", 0x09 }, + { "KB_F11", 0x78 }, + { "KB_F12", 0x07 }, + { "KB_F14", 0x10 }, + { "KB_F15", 0x18 }, + { "KB_BACKTICK", 0x0e }, + { "KB_1", 0x16 }, + { "KB_2", 0x1e }, + { "KB_3", 0x26 }, + { "KB_4", 0x25 }, + { "KB_5", 0x2e }, + { "KB_6", 0x36 }, + { "KB_7", 0x3d }, + { "KB_8", 0x3e }, + { "KB_9", 0x46 }, + { "KB_0", 0x45 }, + { "KB_HYPHEN", 0x4e }, + { "KB_EQUALS", 0x55 }, + { "KB_BACKSPACE", 0x66 }, + { "KB_TAB", 0x0d }, + { "KB_Q", 0x15 }, + { "KB_W", 0x1d }, + { "KB_E", 0x24 }, + { "KB_R", 0x2d }, + { "KB_T", 0x2d }, + { "KB_Y", 0x35 }, + { "KB_U", 0x3c }, + { "KB_I", 0x43 }, + { "KB_O", 0x44 }, + { "KB_P", 0x4d }, + { "KB_LBRACKET", 0x54 }, + { "KB_RBRACKET", 0x5b }, + { "KB_BACKSLASH", 0x5d }, + { "KB_CAPS", 0x58 }, + { "KB_A", 0x1c }, + { "KB_S", 0x1b }, + { "KB_D", 0x23 }, + { "KB_F", 0x2b }, + { "KB_G", 0x34 }, + { "KB_H", 0x33 }, + { "KB_J", 0x3b }, + { "KB_K", 0x42 }, + { "KB_L", 0x4b }, + { "KB_SEMI", 0x4c }, + { "KB_QUOTE", 0x52 }, + { "KB_RET", 0x5a }, + { "KB_LSHIFT", 0x88 }, + { "KB_Z", 0x1a }, + { "KB_X", 0x22 }, + { "KB_C", 0x21 }, + { "KB_V", 0x2a }, + { "KB_B", 0x32 }, + { "KB_N", 0x31 }, + { "KB_M", 0x3a }, + { "KB_COMMA", 0x41 }, + { "KB_PERIOD", 0x49 }, + { "KB_FWDSLASH", 0x4a }, + { "KB_RSHIFT", 0x89 }, + { "KB_LCTL", 0x8c }, + { "KB_META", 0x82 }, + { "KB_LALT", 0xba }, + { "KB_SPACE", 0x29 }, + { "KB_RALT", 0x8b }, + { "KB_MENU", 0x84 }, + { "KB_RCTL", 0x8d }, + { "KB_PRNTSCN", 0xc3 }, + { "KB_SCRLCK", 0x7e }, + { "KB_PAUSE", 0x91 }, + { "KB_INS", 0xc2 }, + { "KB_HOME", 0x94 }, + { "KB_PGUP", 0x96 }, + { "KB_DEL", 0xc0 }, + { "KB_END", 0x95 }, + { "KB_PGDWN", 0x97 }, + { "KB_UP_ARROW", 0x99 }, + { "KB_DOWN_ARROW", 0x98 }, + { "KB_LEFT_ARROW", 0x91 }, + { "KB_RIGHT_ARROW", 0x9b }, + { "NUMPAD_LOCK", 0x77 }, + { "NUMPAD_FWDSLASH", 0x90 }, + { "NUMPAD_ASTERISK", 0x7c }, + { "NUMPAD_HYPHEN", 0x7b }, + { "NUMPAD_0", 0x70 }, + { "NUMPAD_1", 0x69 }, + { "NUMPAD_2", 0x72 }, + { "NUMPAD_3", 0x7a }, + { "NUMPAD_4", 0x6b }, + { "NUMPAD_5", 0x73 }, + { "NUMPAD_6", 0x74 }, + { "NUMPAD_7", 0x6c }, + { "NUMPAD_8", 0x75 }, + { "NUMPAD_9", 0x7d }, + { "NUMPAD_PLUS", 0x79 }, + { "NUMPAD_ENTER", 0x81 }, + { "NUMPAD_PERIOD", 0x71 } }; + +static const struct KeyCode mouse_codes[] = { { "MOUSE_LCLICK", 0x01 }, + { "MOUSE_RCLICK", 0x02 }, + { "MOUSE_MCLICK", 0x03 }, + { "MOUSE_WHEEL_UP", 0x04 }, + { "MOUSE_WHEEL_DOWN", 0x05 } }; + +static const struct KeyCode media_codes[] = { + { "MEDIA_SCREENSHOT", 0x16 }, { "MEDIA_SHOW_KEYBOARD", 0x19 }, + { "MEDIA_SHOW_DESKTOP", 0x1c }, { "MEDIA_START_RECORDING", 0x1e }, + { "MEDIA_MIC_OFF", 0x01 }, { "MEDIA_VOL_DOWN", 0x02 }, + { "MEDIA_VOL_UP", 0x03 } +}; + +/* 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 }, +}; + +/* 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_report { + uint16_t x, y; + uint16_t rx, ry; + uint16_t z, rz; + uint8_t buttons[4]; +} __packed; + +struct ally_x_device { + struct input_dev *input; + struct hid_device *hdev; + spinlock_t lock; + + struct ff_report *ff_packet; + struct work_struct output_worker; + bool output_worker_initialized; + /* Prevent multiple queued event due to the enforced delay in worker */ + bool update_qam_btn; + /* Set if the QAM and AC buttons emit Xbox and Xbox+A */ + bool qam_btns_steam_mode; + bool update_ff; +}; + +struct ally_rgb_leds { + struct hid_device *hdev; + /* Need two dev here to enable the 3 step brightness */ + struct led_classdev led_bright_dev; + struct led_classdev_mc led_rgb_dev; + struct work_struct work; + spinlock_t lock; + + bool removed; + + /* Update the main brightness 0-2 using a single raw write */ + bool update_bright; + unsigned int brightness; + + /* Update the RGB only to keep write efficient */ + bool update_rgb; + uint8_t gamepad_red[4]; + uint8_t gamepad_green[4]; + uint8_t gamepad_blue[4]; + + /* Once the RGB is toggled this is set until next boot */ + bool rgb_software_mode; +}; + +/* ROG Ally has many settings related to the gamepad, all using the same n-key endpoint */ +struct ally_gamepad_cfg { + struct hid_device *hdev; + struct input_dev *input; + + enum xpad_mode mode; + /* + * index: [joysticks/triggers][left(2 bytes), right(2 bytes)] + * joysticks: 2 bytes: inner, outer + * triggers: 2 bytes: lower, upper + * min/max: 0-64 + */ + u8 deadzones[xpad_mode_mouse][2][4]; + /* + * index: left, right + * max: 64 + */ + u8 vibration_intensity[xpad_mode_mouse][2]; + /* + * index: [joysticks][2 byte stepping per point] + * - 4 points of 2 bytes each + * - byte 0 of pair = stick move % + * - byte 1 of pair = stick response % + * - min/max: 1-63 + */ + bool supports_response_curves; + u8 response_curve[xpad_mode_mouse][2][8]; + /* + * left = byte 0, right = byte 1 + */ + bool supports_anti_deadzones; + u8 anti_deadzones[xpad_mode_mouse][2]; + /* + * index: [mode][phys pair][b1, b1 secondary, b2, b2 secondary, blocks of 11] + */ + u8 key_mapping[xpad_mode_mouse][btn_pair_lt_rt][MAPPING_BLOCK_LEN]; + /* + * index: [mode][button index] + */ + u8 turbo_btns[xpad_mode_mouse][TURBO_BLOCK_LEN]; + /* + * index: [joystick side][Y-stable, Y-min, Y-max, X-stable, X-min, X-max] + */ + u32 js_calibrations[2][6]; + /* + * index: [trigger side][stable, max] + */ + u32 tr_calibrations[2][2]; +}; + +static struct ally_drvdata { + struct hid_device *hdev; + struct ally_x_device *ally_x; + struct ally_gamepad_cfg *gamepad_cfg; + struct ally_rgb_leds *led_rgb; +} drvdata; + +static int asus_dev_get_report(struct hid_device *hdev, u8 *out_buf, size_t out_buf_size) +{ + return hid_hw_raw_request(hdev, FEATURE_REPORT_ID, out_buf, out_buf_size, + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); +} + +static int asus_dev_set_report(struct hid_device *hdev, const u8 *buf, size_t buf_size) +{ + unsigned char *dmabuf; + int ret; + + dmabuf = kmemdup(buf, buf_size, GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, buf[0], dmabuf, buf_size, HID_FEATURE_REPORT, + HID_REQ_SET_REPORT); + kfree(dmabuf); + + return ret; +} + +/**************************************************************************************************/ +/* ROG Ally gamepad i/o and force-feedback */ +/**************************************************************************************************/ +static int ally_x_raw_event(struct ally_x_device *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); + input_report_abs(ally_x->input, ABS_Y, in_report->y); + input_report_abs(ally_x->input, ABS_RX, in_report->rx); + input_report_abs(ally_x->input, ABS_RY, in_report->ry); + 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]); + } + /* + * 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_btns_steam_mode) { + spin_lock_irqsave(&ally_x->lock, flags); + if (data[1] == 0x38 && !ally_x->update_qam_btn) { + ally_x->update_qam_btn = true; + if (ally_x->output_worker_initialized) + schedule_work(&ally_x->output_worker); + } + spin_unlock_irqrestore(&ally_x->lock, flags); + /* Left/XBox button. Long press does ctrl+alt+del which we can't catch */ + input_report_key(ally_x->input, BTN_MODE, data[1] == 0xA6); + } else { + input_report_key(ally_x->input, KEY_F16, data[1] == 0xA6); + input_report_key(ally_x->input, KEY_PROG1, data[1] == 0x38); + } + /* 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 0; +} + +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 int ally_x_play_effect(struct input_dev *idev, void *data, struct ff_effect *effect) +{ + struct ally_x_device *ally_x = drvdata.ally_x; + 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; +} + +static void ally_x_work(struct work_struct *work) +{ + struct ally_x_device *ally_x = container_of(work, struct ally_x_device, output_worker); + struct ff_report *ff_report = NULL; + bool update_qam = false; + bool update_ff = false; + unsigned long flags; + + spin_lock_irqsave(&ally_x->lock, flags); + 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; + } + update_qam = ally_x->update_qam_btn; + 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; + asus_dev_set_report(ally_x->hdev, (u8 *)ff_report, sizeof(*ff_report)); + } + kfree(ff_report); + + if (update_qam) { + /* + * The sleeps here are required to allow steam to register the button combo. + */ + usleep_range(1000, 2000); + input_report_key(ally_x->input, BTN_MODE, 1); + input_sync(ally_x->input); + + msleep(80); + input_report_key(ally_x->input, BTN_A, 1); + input_sync(ally_x->input); + + msleep(80); + input_report_key(ally_x->input, BTN_A, 0); + input_sync(ally_x->input); + + msleep(80); + 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_btn = false; + spin_unlock_irqrestore(&ally_x->lock, flags); + } +} + +static struct input_dev *ally_x_setup_input(struct hid_device *hdev) +{ + int ret, abs_min = 0, js_abs_max = 65535, tr_abs_max = 1023; + struct input_dev *input; + + input = ally_x_alloc_input_dev(hdev, NULL); + if (IS_ERR(input)) + return ERR_CAST(input); + + input_set_abs_params(input, ABS_X, abs_min, js_abs_max, 0, 0); + input_set_abs_params(input, ABS_Y, abs_min, js_abs_max, 0, 0); + input_set_abs_params(input, ABS_RX, abs_min, js_abs_max, 0, 0); + input_set_abs_params(input, ABS_RY, abs_min, js_abs_max, 0, 0); + input_set_abs_params(input, ABS_Z, abs_min, tr_abs_max, 0, 0); + input_set_abs_params(input, ABS_RZ, abs_min, tr_abs_max, 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_FF, FF_RUMBLE); + input_ff_create_memless(input, NULL, ally_x_play_effect); + + ret = input_register_device(input); + if (ret) + return ERR_PTR(ret); + + return input; +} + +static ssize_t ally_x_qam_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ally_x_device *ally_x = drvdata.ally_x; + + return sysfs_emit(buf, "%d\n", ally_x->qam_btns_steam_mode); +} + +static ssize_t ally_x_qam_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ally_x_device *ally_x = drvdata.ally_x; + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret < 0) + return ret; + + ally_x->qam_btns_steam_mode = val; + + return count; +} +ALLY_DEVICE_ATTR_RW(ally_x_qam_mode, qam_mode); + +static struct ally_x_device *ally_x_create(struct hid_device *hdev) +{ + uint8_t max_output_report_size; + struct ally_x_device *ally_x; + struct ff_report *report; + int ret; + + ally_x = devm_kzalloc(&hdev->dev, sizeof(*ally_x), GFP_KERNEL); + if (!ally_x) + return ERR_PTR(-ENOMEM); + + ally_x->hdev = hdev; + INIT_WORK(&ally_x->output_worker, ally_x_work); + spin_lock_init(&ally_x->lock); + ally_x->output_worker_initialized = true; + ally_x->qam_btns_steam_mode = + true; /* Always default to steam mode, it can be changed by userspace attr */ + + max_output_report_size = sizeof(struct ally_x_input_report); + report = devm_kzalloc(&hdev->dev, sizeof(*report), GFP_KERNEL); + if (!report) { + ret = -ENOMEM; + goto free_ally_x; + } + + /* None of these bytes will change for the FF command for now */ + report->report_id = 0x0D; + report->ff.enable = 0x0F; /* Enable all by default */ + report->ff.pulse_sustain_10ms = 0xFF; /* Duration */ + report->ff.pulse_release_10ms = 0x00; /* Start Delay */ + report->ff.loop_count = 0xEB; /* Loop Count */ + ally_x->ff_packet = report; + + ally_x->input = ally_x_setup_input(hdev); + if (IS_ERR(ally_x->input)) { + ret = PTR_ERR(ally_x->input); + goto free_ff_packet; + } + + if (sysfs_create_file(&hdev->dev.kobj, &dev_attr_ally_x_qam_mode.attr)) { + ret = -ENODEV; + goto unregister_input; + } + + ally_x->update_ff = true; + if (ally_x->output_worker_initialized) + schedule_work(&ally_x->output_worker); + + hid_info(hdev, "Registered Ally X controller using %s\n", + dev_name(&ally_x->input->dev)); + return ally_x; + +unregister_input: + input_unregister_device(ally_x->input); +free_ff_packet: + kfree(ally_x->ff_packet); +free_ally_x: + kfree(ally_x); + return ERR_PTR(ret); +} + +static void ally_x_remove(struct hid_device *hdev) +{ + struct ally_x_device *ally_x = drvdata.ally_x; + unsigned long flags; + + spin_lock_irqsave(&ally_x->lock, flags); + ally_x->output_worker_initialized = false; + spin_unlock_irqrestore(&ally_x->lock, flags); + cancel_work_sync(&ally_x->output_worker); + sysfs_remove_file(&hdev->dev.kobj, &dev_attr_ally_x_qam_mode.attr); +} + +/**************************************************************************************************/ +/* ROG Ally configuration */ +/**************************************************************************************************/ +static int __gamepad_write_all_to_mcu(struct hid_device *hdev, + struct ally_gamepad_cfg *ally_cfg); + +static int process_key_code(const struct KeyCode *codes, int code_count, const char *buf_copy, + u8 *out, int out_idx) +{ + for (int i = 0; i < code_count; i++) { + if (strcmp(buf_copy, codes[i].label) == 0) { + out[out_idx] = codes[i].code; + return 0; // Found + } + } + return -EINVAL; // Not found +} + +static int __string_to_key_code(const char *buf, u8 *out, int out_len) +{ + char buf_copy[32]; + u8 *save_buf; + + if (out_len != BTN_CODE_LEN) + return -EINVAL; + + save_buf = kzalloc(out_len, GFP_KERNEL); + if (!save_buf) + return -ENOMEM; + memcpy(save_buf, out, out_len); + memset(out, 0, out_len); /* always clear before adjusting */ + + strscpy(buf_copy, buf); + buf_copy[strcspn(buf_copy, "\n")] = 0; + + /* Gamepad group */ + out[0] = 0x01; + if (process_key_code(gamepad_codes, ARRAY_SIZE(gamepad_codes), buf_copy, out, 1) == 0) + goto success; + + /* Keyboard group */ + out[0] = 0x02; + if (process_key_code(keyboard_codes, ARRAY_SIZE(keyboard_codes), buf_copy, out, 2) == 0) + goto success; + + /* Mouse group */ + out[0] = 0x03; + if (process_key_code(mouse_codes, ARRAY_SIZE(mouse_codes), buf_copy, out, 4) == 0) + goto success; + + /* Media group */ + out[0] = 0x05; + if (process_key_code(media_codes, ARRAY_SIZE(media_codes), buf_copy, out, 3) == 0) + goto success; + + /* Restore bytes if invalid input */ + memcpy(out, save_buf, out_len); + kfree(save_buf); + return -EINVAL; + +success: + kfree(save_buf); + return 0; +} + +static const char *key_code_to_string(const struct KeyCode *codes, int code_count, u8 code) +{ + for (int i = 0; i < code_count; i++) { + if (codes[i].code == code) + return codes[i].label; + } + return ""; +} + +static u8 *__get_btn_block(struct ally_gamepad_cfg *ally_cfg, enum btn_pair pair, + enum btn_pair_side side, bool secondary) +{ + int offs; + + offs = side ? MAPPING_BLOCK_LEN / 2 : 0; + offs = secondary ? offs + BTN_CODE_LEN : offs; + return ally_cfg->key_mapping[ally_cfg->mode - 1][pair - 1] + offs; +} + +static const char *__btn_map_to_string(struct ally_gamepad_cfg *ally_cfg, enum btn_pair pair, + enum btn_pair_side side, bool secondary) +{ + u8 *out_arg = __get_btn_block(ally_cfg, pair, side, secondary); + + switch (out_arg[0]) { + case 0x01: // Gamepad buttons + return key_code_to_string(gamepad_codes, ARRAY_SIZE(gamepad_codes), out_arg[1]); + case 0x02: // Keyboard keys + return key_code_to_string(keyboard_codes, ARRAY_SIZE(keyboard_codes), + out_arg[2]); + case 0x03: // Mouse buttons + return key_code_to_string(mouse_codes, ARRAY_SIZE(mouse_codes), out_arg[4]); + case 0x05: // Media controls + return key_code_to_string(media_codes, ARRAY_SIZE(media_codes), out_arg[3]); + default: + return ""; + } +} + +/* ASUS ROG Ally device specific attributes */ + +/* This should be called before any attempts to set device functions */ +static int __gamepad_check_ready(struct hid_device *hdev) +{ + int ret, count; + u8 *hidbuf; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + ret = 0; + for (count = 0; count < READY_MAX_TRIES; count++) { + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + hidbuf[2] = xpad_cmd_check_ready; + hidbuf[3] = 01; + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + hid_dbg(hdev, "ROG Ally check failed set report: %d\n", ret); + + hidbuf[0] = hidbuf[1] = hidbuf[2] = hidbuf[3] = 0; + ret = asus_dev_get_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + hid_dbg(hdev, "ROG Ally check failed get report: %d\n", ret); + + ret = hidbuf[2] == xpad_cmd_check_ready; + if (ret) + break; + usleep_range( + 1000, + 2000); /* don't spam the entire loop in less than USB response time */ + } + + if (count == READY_MAX_TRIES) + hid_warn(hdev, "ROG Ally never responded with a ready\n"); + + kfree(hidbuf); + return ret; +} + +/* BUTTON REMAPPING *******************************************************************************/ +static void __btn_pair_to_pkt(struct ally_gamepad_cfg *ally_cfg, enum btn_pair pair, u8 *out, + int out_len) +{ + out[0] = FEATURE_ROG_ALLY_REPORT_ID; + out[1] = FEATURE_ROG_ALLY_CODE_PAGE; + out[2] = xpad_cmd_set_mapping; + out[3] = pair; + out[4] = xpad_cmd_len_mapping; + memcpy(&out[5], &ally_cfg->key_mapping[ally_cfg->mode - 1][pair - 1], + MAPPING_BLOCK_LEN); +} + +/* Store the button setting in driver data. Does not apply to device until __gamepad_set_mapping */ +static int __gamepad_mapping_store(struct ally_gamepad_cfg *ally_cfg, const char *buf, + enum btn_pair pair, int side, bool secondary) +{ + u8 *out_arg; + + out_arg = __get_btn_block(ally_cfg, pair, side, secondary); + + return __string_to_key_code(buf, out_arg, BTN_CODE_LEN); +} + +/* Apply the mapping pair to the device */ +static int __gamepad_set_mapping(struct hid_device *hdev, struct ally_gamepad_cfg *ally_cfg, + enum btn_pair pair) +{ + u8 *hidbuf; + int ret; + + ret = __gamepad_check_ready(hdev); + if (ret < 0) + return ret; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + __btn_pair_to_pkt(ally_cfg, pair, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + + kfree(hidbuf); + + return ret; +} + +static ssize_t btn_mapping_apply_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + struct hid_device *hdev = to_hid_device(dev); + int ret; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + ret = __gamepad_write_all_to_mcu(hdev, ally_cfg); + if (ret < 0) + return ret; + + return count; +} +ALLY_DEVICE_ATTR_WO(btn_mapping_apply, apply_all); + +/* BUTTON TURBO ***********************************************************************************/ +static int __btn_turbo_index(enum btn_pair pair, int side) +{ + return (pair - 1) * (2 * TURBO_BLOCK_STEP) + (side * TURBO_BLOCK_STEP); +}; + +static int __gamepad_turbo_show(struct device *dev, enum btn_pair pair, int side) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + return ally_cfg->turbo_btns[ally_cfg->mode - 1][__btn_turbo_index(pair, side)]; +}; + +static int __gamepad_turbo_store(struct device *dev, const char *buf, enum btn_pair pair, + int side) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + int ret, val; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + if (val < 0 || val > 16) + return -EINVAL; + + ally_cfg->turbo_btns[ally_cfg->mode - 1][__btn_turbo_index(pair, side)] = val; + + return 0; +}; + +/* button map attributes, regular and macro*/ +ALLY_BTN_MAPPING(m2, btn_pair_m1_m2, btn_pair_side_left); +ALLY_BTN_MAPPING(m1, btn_pair_m1_m2, btn_pair_side_right); +ALLY_BTN_MAPPING(a, btn_pair_a_b, btn_pair_side_left); +ALLY_BTN_MAPPING(b, btn_pair_a_b, btn_pair_side_right); +ALLY_BTN_MAPPING(x, btn_pair_x_y, btn_pair_side_left); +ALLY_BTN_MAPPING(y, btn_pair_x_y, btn_pair_side_right); +ALLY_BTN_MAPPING(lb, btn_pair_lb_rb, btn_pair_side_left); +ALLY_BTN_MAPPING(rb, btn_pair_lb_rb, btn_pair_side_right); +ALLY_BTN_MAPPING(ls, btn_pair_ls_rs, btn_pair_side_left); +ALLY_BTN_MAPPING(rs, btn_pair_ls_rs, btn_pair_side_right); +ALLY_BTN_MAPPING(lt, btn_pair_lt_rt, btn_pair_side_left); +ALLY_BTN_MAPPING(rt, btn_pair_lt_rt, btn_pair_side_right); +ALLY_BTN_MAPPING(dpad_u, btn_pair_dpad_u_d, btn_pair_side_left); +ALLY_BTN_MAPPING(dpad_d, btn_pair_dpad_u_d, btn_pair_side_right); +ALLY_BTN_MAPPING(dpad_l, btn_pair_dpad_l_r, btn_pair_side_left); +ALLY_BTN_MAPPING(dpad_r, btn_pair_dpad_l_r, btn_pair_side_right); +ALLY_BTN_MAPPING(view, btn_pair_view_menu, btn_pair_side_left); +ALLY_BTN_MAPPING(menu, btn_pair_view_menu, btn_pair_side_right); + +static void __gamepad_mapping_xpad_default(struct ally_gamepad_cfg *ally_cfg) +{ + memcpy(&ally_cfg->key_mapping[0][0], &XPAD_DEF1, MAPPING_BLOCK_LEN); + memcpy(&ally_cfg->key_mapping[0][1], &XPAD_DEF2, MAPPING_BLOCK_LEN); + memcpy(&ally_cfg->key_mapping[0][2], &XPAD_DEF3, MAPPING_BLOCK_LEN); + memcpy(&ally_cfg->key_mapping[0][3], &XPAD_DEF4, MAPPING_BLOCK_LEN); + memcpy(&ally_cfg->key_mapping[0][4], &XPAD_DEF5, MAPPING_BLOCK_LEN); + memcpy(&ally_cfg->key_mapping[0][5], &XPAD_DEF6, MAPPING_BLOCK_LEN); + memcpy(&ally_cfg->key_mapping[0][6], &XPAD_DEF7, MAPPING_BLOCK_LEN); + memcpy(&ally_cfg->key_mapping[0][7], &XPAD_DEF8, MAPPING_BLOCK_LEN); + memcpy(&ally_cfg->key_mapping[0][8], &XPAD_DEF9, MAPPING_BLOCK_LEN); +} + +static void __gamepad_mapping_wasd_default(struct ally_gamepad_cfg *ally_cfg) +{ + memcpy(&ally_cfg->key_mapping[1][0], &WASD_DEF1, MAPPING_BLOCK_LEN); + memcpy(&ally_cfg->key_mapping[1][1], &WASD_DEF2, MAPPING_BLOCK_LEN); + memcpy(&ally_cfg->key_mapping[1][2], &WASD_DEF3, MAPPING_BLOCK_LEN); + memcpy(&ally_cfg->key_mapping[1][3], &WASD_DEF4, MAPPING_BLOCK_LEN); + memcpy(&ally_cfg->key_mapping[1][4], &WASD_DEF5, MAPPING_BLOCK_LEN); + memcpy(&ally_cfg->key_mapping[1][5], &WASD_DEF6, MAPPING_BLOCK_LEN); + memcpy(&ally_cfg->key_mapping[1][6], &WASD_DEF7, MAPPING_BLOCK_LEN); + memcpy(&ally_cfg->key_mapping[1][7], &WASD_DEF8, MAPPING_BLOCK_LEN); + memcpy(&ally_cfg->key_mapping[1][8], &WASD_DEF9, MAPPING_BLOCK_LEN); +} + +static ssize_t btn_mapping_reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + switch (ally_cfg->mode) { + case xpad_mode_game: + __gamepad_mapping_xpad_default(ally_cfg); + break; + case xpad_mode_wasd: + __gamepad_mapping_wasd_default(ally_cfg); + break; + default: + __gamepad_mapping_xpad_default(ally_cfg); + break; + } + + return count; +} + +ALLY_DEVICE_ATTR_WO(btn_mapping_reset, reset_btn_mapping); + +/* GAMEPAD MODE ***********************************************************************************/ +static ssize_t __gamepad_set_mode(struct hid_device *hdev, struct ally_gamepad_cfg *ally_cfg, + int val) +{ + u8 *hidbuf; + int ret; + + ret = __gamepad_check_ready(hdev); + if (ret < 0) + return ret; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + hidbuf[2] = xpad_cmd_set_mode; + hidbuf[3] = xpad_cmd_len_mode; + hidbuf[4] = val; + + ret = __gamepad_check_ready(hdev); + if (ret < 0) + goto report_fail; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + goto report_fail; + + ret = __gamepad_write_all_to_mcu(hdev, ally_cfg); + if (ret < 0) + goto report_fail; + +report_fail: + kfree(hidbuf); + return ret; +} + +static ssize_t gamepad_mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + return sysfs_emit(buf, "%d\n", ally_cfg->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_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + int ret, val; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + + if (val < xpad_mode_game || val > xpad_mode_mouse) + return -EINVAL; + + ally_cfg->mode = val; + + ret = __gamepad_set_mode(hdev, ally_cfg, val); + if (ret < 0) + return ret; + + return count; +} + +DEVICE_ATTR_RW(gamepad_mode); + +/* VIBRATION INTENSITY ****************************************************************************/ +static ssize_t gamepad_vibration_intensity_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "left right\n"); +} + +ALLY_DEVICE_ATTR_RO(gamepad_vibration_intensity_index, vibration_intensity_index); + +static ssize_t __gamepad_write_vibe_intensity_to_mcu(struct hid_device *hdev, + struct ally_gamepad_cfg *ally_cfg) +{ + u8 *hidbuf; + int ret; + + ret = __gamepad_check_ready(hdev); + if (ret < 0) + return ret; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + hidbuf[2] = xpad_cmd_set_vibe_intensity; + hidbuf[3] = xpad_cmd_len_vibe_intensity; + hidbuf[4] = ally_cfg->vibration_intensity[ally_cfg->mode - 1][btn_pair_side_left]; + hidbuf[5] = ally_cfg->vibration_intensity[ally_cfg->mode - 1][btn_pair_side_right]; + + ret = __gamepad_check_ready(hdev); + if (ret < 0) + goto report_fail; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + goto report_fail; + +report_fail: + kfree(hidbuf); + return ret; +} + +static ssize_t gamepad_vibration_intensity_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + return sysfs_emit( + buf, "%d %d\n", + ally_cfg->vibration_intensity[ally_cfg->mode - 1][btn_pair_side_left], + ally_cfg->vibration_intensity[ally_cfg->mode - 1][btn_pair_side_right]); +} + +static ssize_t gamepad_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_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + u32 left, right; + int ret; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + if (sscanf(buf, "%d %d", &left, &right) != 2) + return -EINVAL; + + if (left > 64 || right > 64) + return -EINVAL; + + ally_cfg->vibration_intensity[ally_cfg->mode - 1][btn_pair_side_left] = left; + ally_cfg->vibration_intensity[ally_cfg->mode - 1][btn_pair_side_right] = right; + + ret = __gamepad_write_vibe_intensity_to_mcu(hdev, ally_cfg); + if (ret < 0) + return ret; + + return count; +} + +ALLY_DEVICE_ATTR_RW(gamepad_vibration_intensity, vibration_intensity); + +/* ROOT LEVEL ATTRS *******************************************************************************/ +static struct attribute *gamepad_device_attrs[] = { + &dev_attr_gamepad_mode.attr, + &dev_attr_btn_mapping_reset.attr, + &dev_attr_btn_mapping_apply.attr, + &dev_attr_gamepad_vibration_intensity.attr, + &dev_attr_gamepad_vibration_intensity_index.attr, + NULL +}; + +static const struct attribute_group ally_controller_attr_group = { + .attrs = gamepad_device_attrs, +}; + +/* ANALOGUE DEADZONES *****************************************************************************/ +static ssize_t __gamepad_set_deadzones(struct hid_device *hdev, + struct ally_gamepad_cfg *ally_cfg) +{ + u8 *hidbuf; + int ret; + + ret = __gamepad_check_ready(hdev); + if (ret < 0) + return ret; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + hidbuf[2] = xpad_cmd_set_js_dz; + hidbuf[3] = xpad_cmd_len_deadzone; + hidbuf[4] = ally_cfg->deadzones[ally_cfg->mode - 1][0][0]; + hidbuf[5] = ally_cfg->deadzones[ally_cfg->mode - 1][0][1]; + hidbuf[6] = ally_cfg->deadzones[ally_cfg->mode - 1][0][2]; + hidbuf[7] = ally_cfg->deadzones[ally_cfg->mode - 1][0][3]; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + goto end; + + hidbuf[2] = xpad_cmd_set_tr_dz; + hidbuf[4] = ally_cfg->deadzones[ally_cfg->mode - 1][1][0]; + hidbuf[5] = ally_cfg->deadzones[ally_cfg->mode - 1][1][1]; + hidbuf[6] = ally_cfg->deadzones[ally_cfg->mode - 1][1][2]; + hidbuf[7] = ally_cfg->deadzones[ally_cfg->mode - 1][1][3]; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + goto end; + +end: + kfree(hidbuf); + return ret; +} + +static ssize_t __gamepad_store_deadzones(struct ally_gamepad_cfg *ally_cfg, enum xpad_axis axis, + const char *buf) +{ + int cmd, side, is_tr; + u32 inner, outer; + + if (sscanf(buf, "%d %d", &inner, &outer) != 2) + return -EINVAL; + + if (inner > 64 || outer > 64 || inner > outer) + return -EINVAL; + + is_tr = axis > xpad_axis_xy_right; + side = axis == xpad_axis_xy_right || axis == xpad_axis_z_right ? 2 : 0; + cmd = is_tr ? xpad_cmd_set_js_dz : xpad_cmd_set_tr_dz; + + ally_cfg->deadzones[ally_cfg->mode - 1][is_tr][side] = inner; + ally_cfg->deadzones[ally_cfg->mode - 1][is_tr][side + 1] = outer; + + return 0; +} + +static ssize_t axis_xyz_deadzone_index_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "inner outer\n"); +} + +ALLY_DEVICE_ATTR_RO(axis_xyz_deadzone_index, deadzone_index); + +ALLY_AXIS_DEADZONE(xpad_axis_xy_left, deadzone); +ALLY_AXIS_DEADZONE(xpad_axis_xy_right, deadzone); +ALLY_AXIS_DEADZONE(xpad_axis_z_left, deadzone); +ALLY_AXIS_DEADZONE(xpad_axis_z_right, deadzone); + +/* ANTI-DEADZONES *********************************************************************************/ +static ssize_t __gamepad_write_js_ADZ_to_mcu(struct hid_device *hdev, + struct ally_gamepad_cfg *ally_cfg) +{ + u8 *hidbuf; + int ret; + + ret = __gamepad_check_ready(hdev); + if (ret < 0) + return ret; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + hidbuf[2] = xpad_cmd_set_adz; + hidbuf[3] = xpad_cmd_len_adz; + hidbuf[4] = ally_cfg->anti_deadzones[ally_cfg->mode - 1][btn_pair_side_left]; + hidbuf[5] = ally_cfg->anti_deadzones[ally_cfg->mode - 1][btn_pair_side_right]; + + ret = __gamepad_check_ready(hdev); + if (ret < 0) + goto report_fail; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + goto report_fail; + +report_fail: + kfree(hidbuf); + return ret; +} + +static ssize_t __gamepad_js_ADZ_store(struct device *dev, const char *buf, + enum btn_pair_side side) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + int ret, val; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + + if (val < 0 || val > 32) + return -EINVAL; + + ally_cfg->anti_deadzones[ally_cfg->mode - 1][side] = val; + + return ret; +} + +static ssize_t xpad_axis_xy_left_ADZ_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + return sysfs_emit(buf, "%d\n", + ally_cfg->anti_deadzones[ally_cfg->mode - 1][btn_pair_side_left]); +} + +static ssize_t xpad_axis_xy_left_ADZ_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret = __gamepad_js_ADZ_store(dev, buf, btn_pair_side_left); + + if (ret) + return ret; + + return count; +} + +ALLY_DEVICE_ATTR_RW(xpad_axis_xy_left_ADZ, anti_deadzone); + +static ssize_t xpad_axis_xy_right_ADZ_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + return sysfs_emit(buf, "%d\n", + ally_cfg->anti_deadzones[ally_cfg->mode - 1][btn_pair_side_right]); +} + +static ssize_t xpad_axis_xy_right_ADZ_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret = __gamepad_js_ADZ_store(dev, buf, btn_pair_side_right); + + if (ret) + return ret; + + return count; +} + +ALLY_DEVICE_ATTR_RW(xpad_axis_xy_right_ADZ, anti_deadzone); + +/* JS RESPONSE CURVES *****************************************************************************/ +static ssize_t rc_point_index_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "move response\n"); +} + +ALLY_DEVICE_ATTR_RO(rc_point_index, rc_point_index); + +static ssize_t __gamepad_write_response_curves_to_mcu(struct hid_device *hdev, + struct ally_gamepad_cfg *ally_cfg) +{ + u8 *hidbuf; + int ret; + + ret = __gamepad_check_ready(hdev); + if (ret < 0) + return ret; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + hidbuf[2] = xpad_cmd_set_response_curve; + hidbuf[3] = xpad_cmd_len_response_curve; + hidbuf[4] = 0x01; + memcpy(&hidbuf[5], &ally_cfg->response_curve[ally_cfg->mode - 1][btn_pair_side_left], + 8); + + ret = __gamepad_check_ready(hdev); + if (ret < 0) + goto report_fail; + + hidbuf[4] = 0x02; + memcpy(&hidbuf[5], &ally_cfg->response_curve[ally_cfg->mode - 1][btn_pair_side_right], + 8); + + ret = __gamepad_check_ready(hdev); + if (ret < 0) + goto report_fail; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + goto report_fail; + +report_fail: + kfree(hidbuf); + return ret; +} + +static ssize_t __gamepad_store_response_curve(struct device *dev, const char *buf, + enum btn_pair_side side, int point) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + u32 move, response; + int idx; + + idx = (point - 1) * 2; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + if (sscanf(buf, "%d %d", &move, &response) != 2) + return -EINVAL; + + if (move > 64 || response > 64) + return -EINVAL; + + ally_cfg->response_curve[ally_cfg->mode - 1][side][idx] = move; + ally_cfg->response_curve[ally_cfg->mode - 1][side][idx + 1] = response; + + return 0; +} + +ALLY_JS_RC_POINT(left, 1, rc_point_); +ALLY_JS_RC_POINT(left, 2, rc_point_); +ALLY_JS_RC_POINT(left, 3, rc_point_); +ALLY_JS_RC_POINT(left, 4, rc_point_); + +ALLY_JS_RC_POINT(right, 1, rc_point_); +ALLY_JS_RC_POINT(right, 2, rc_point_); +ALLY_JS_RC_POINT(right, 3, rc_point_); +ALLY_JS_RC_POINT(right, 4, rc_point_); + +/* CALIBRATIONS ***********************************************************************************/ +static int __gamepad_get_calibration(struct hid_device *hdev) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + u8 *hidbuf; + int ret, i; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + for (i = 0; i < 2; i++) { + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = 0xD0; + hidbuf[2] = 0x03; + hidbuf[3] = i + 1; // 0x01 JS, 0x02 TR + hidbuf[4] = 0x20; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) { + hid_warn(hdev, "ROG Ally check failed set report: %d\n", ret); + goto cleanup; + } + + memset(hidbuf, 0, FEATURE_ROG_ALLY_REPORT_SIZE); + ret = asus_dev_get_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0 || hidbuf[5] != 1) { + hid_warn(hdev, "ROG Ally check failed get report: %d\n", ret); + goto cleanup; + } + + if (i == 0) { + /* Joystick calibration */ + /* [left][index] is Y: stable, min, max. X: stable, min, max */ + ally_cfg->js_calibrations[0][3] = (hidbuf[6] << 8) | hidbuf[7]; + ally_cfg->js_calibrations[0][4] = (hidbuf[8] << 8) | hidbuf[9]; + ally_cfg->js_calibrations[0][5] = (hidbuf[10] << 8) | hidbuf[11]; + ally_cfg->js_calibrations[0][0] = (hidbuf[12] << 8) | hidbuf[13]; + ally_cfg->js_calibrations[0][1] = (hidbuf[14] << 8) | hidbuf[15]; + ally_cfg->js_calibrations[0][2] = (hidbuf[16] << 8) | hidbuf[17]; + /* [right][index] is Y: stable, min, max. X: stable, min, max */ + ally_cfg->js_calibrations[1][0] = (hidbuf[24] << 8) | hidbuf[25]; + ally_cfg->js_calibrations[1][1] = (hidbuf[26] << 8) | hidbuf[27]; + ally_cfg->js_calibrations[1][2] = (hidbuf[28] << 8) | hidbuf[29]; + ally_cfg->js_calibrations[1][3] = (hidbuf[18] << 8) | hidbuf[19]; + ally_cfg->js_calibrations[1][4] = (hidbuf[20] << 8) | hidbuf[21]; + ally_cfg->js_calibrations[1][5] = (hidbuf[22] << 8) | hidbuf[23]; + } else { + /* Trigger calibration */ + /* [left/right][stable/max] */ + ally_cfg->tr_calibrations[0][0] = (hidbuf[6] << 8) | hidbuf[7]; + ally_cfg->tr_calibrations[0][1] = (hidbuf[8] << 8) | hidbuf[9]; + ally_cfg->tr_calibrations[1][0] = (hidbuf[10] << 8) | hidbuf[11]; + ally_cfg->tr_calibrations[1][1] = (hidbuf[12] << 8) | hidbuf[13]; + } + } + +cleanup: + kfree(hidbuf); + return ret; +} + +static ssize_t __gamepad_write_cal_to_mcu(struct device *dev, enum xpad_axis axis) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + u8 *c, side, pkt_len, data_len; + int ret, cal, checksum = 0; + u8 *hidbuf; + int *head; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + ret = __gamepad_check_ready(hdev); + if (ret < 0) + return ret; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + side = axis == xpad_axis_xy_right || axis == xpad_axis_z_right ? 1 : 0; + pkt_len = axis > xpad_axis_xy_right ? 0x06 : 0x0E; + data_len = axis > xpad_axis_xy_right ? 2 : 6; + head = axis > xpad_axis_xy_right ? ally_cfg->tr_calibrations[side] : + ally_cfg->js_calibrations[side]; + + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + hidbuf[2] = xpad_cmd_set_calibration; + hidbuf[3] = pkt_len; + hidbuf[4] = 0x01; /* second command (write calibration) */ + hidbuf[5] = axis; + c = &hidbuf[6]; /* pointer to data start */ + + for (size_t i = 0; i < data_len; i++) { + cal = head[i]; + *c = (u8)((cal & 0xff00) >> 8); + checksum += *c; + c += 1; + *c = (u8)(cal & 0xff); + checksum += *c; + c += 1; + } + + hidbuf[6 + data_len * 2] = checksum; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + goto report_fail; + + memset(hidbuf, 0, FEATURE_ROG_ALLY_REPORT_SIZE); + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + hidbuf[2] = xpad_cmd_set_calibration; + hidbuf[3] = xpad_cmd_len_calibration3; + hidbuf[4] = 0x03; /* second command (apply the calibration that was written) */ + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + goto report_fail; + +report_fail: + kfree(hidbuf); + return ret; +} + +static ssize_t __gamepad_cal_store(struct device *dev, const char *buf, enum xpad_axis axis) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + u32 x_stable, x_min, x_max, y_stable, y_min, y_max, side; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + if (axis == xpad_axis_xy_left || axis == xpad_axis_xy_right) { + if (sscanf(buf, "%d %d %d %d %d %d", &x_stable, &x_min, &x_max, &y_stable, + &y_min, &y_max) != 6) + return -EINVAL; + + side = axis == xpad_axis_xy_right || axis == xpad_axis_z_right ? 1 : 0; + /* stored in reverse order for easy copy to packet */ + ally_cfg->js_calibrations[side][0] = y_stable; + ally_cfg->js_calibrations[side][1] = y_min; + ally_cfg->js_calibrations[side][2] = y_max; + ally_cfg->js_calibrations[side][3] = x_stable; + ally_cfg->js_calibrations[side][4] = x_min; + ally_cfg->js_calibrations[side][5] = x_max; + + return __gamepad_write_cal_to_mcu(dev, axis); + } + if (sscanf(buf, "%d %d", &x_stable, &x_max) != 2) + return -EINVAL; + + side = axis == xpad_axis_xy_right || axis == xpad_axis_z_right ? 1 : 0; + /* stored in reverse order for easy copy to packet */ + ally_cfg->tr_calibrations[side][0] = x_stable; + ally_cfg->tr_calibrations[side][1] = x_max; + + return __gamepad_write_cal_to_mcu(dev, axis); +} + +static ssize_t __gamepad_cal_show(struct device *dev, char *buf, enum xpad_axis axis) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + int side = (axis == xpad_axis_xy_right || axis == xpad_axis_z_right) ? 1 : 0; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + if (axis == xpad_axis_xy_left || axis == xpad_axis_xy_right) { + return sysfs_emit( + buf, "%d %d %d %d %d %d\n", ally_cfg->js_calibrations[side][3], + ally_cfg->js_calibrations[side][4], ally_cfg->js_calibrations[side][5], + ally_cfg->js_calibrations[side][0], ally_cfg->js_calibrations[side][1], + ally_cfg->js_calibrations[side][2]); + } + + return sysfs_emit(buf, "%d %d\n", ally_cfg->tr_calibrations[side][0], + ally_cfg->tr_calibrations[side][1]); +} + +ALLY_CAL_ATTR(xpad_axis_xy_left_cal, xpad_axis_xy_left, calibration); +ALLY_CAL_ATTR(xpad_axis_xy_right_cal, xpad_axis_xy_right, calibration); +ALLY_CAL_ATTR(xpad_axis_z_left_cal, xpad_axis_z_left, calibration); +ALLY_CAL_ATTR(xpad_axis_z_right_cal, xpad_axis_z_right, calibration); + +static ssize_t xpad_axis_xy_cal_index_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "x_stable x_min x_max y_stable y_min y_max\n"); +} + +ALLY_DEVICE_ATTR_RO(xpad_axis_xy_cal_index, calibration_index); + +static ssize_t xpad_axis_z_cal_index_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "z_stable z_max\n"); +} + +ALLY_DEVICE_ATTR_RO(xpad_axis_z_cal_index, calibration_index); + +static ssize_t __gamepad_cal_reset(struct device *dev, const char *buf, enum xpad_axis axis) +{ + struct hid_device *hdev = to_hid_device(dev); + u8 *hidbuf; + int ret; + + ret = __gamepad_check_ready(hdev); + if (ret < 0) + return ret; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + /* Write the reset value, then apply it */ + for (u8 cmd = 0x02; cmd <= 0x03; cmd++) { + memset(hidbuf, 0, FEATURE_ROG_ALLY_REPORT_SIZE); + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + hidbuf[2] = xpad_cmd_set_calibration; + hidbuf[3] = (cmd == 0x02) ? xpad_cmd_len_calibration2 : + xpad_cmd_len_calibration3; + hidbuf[4] = cmd; + hidbuf[5] = axis; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + break; + } + + __gamepad_get_calibration(hdev); + + kfree(hidbuf); + return ret; +} + +ALLY_CAL_RESET_ATTR(xpad_axis_xy_left_cal_reset, xpad_axis_xy_left, calibration_reset); +ALLY_CAL_RESET_ATTR(xpad_axis_xy_right_cal_reset, xpad_axis_xy_right, calibration_reset); +ALLY_CAL_RESET_ATTR(xpad_axis_z_left_cal_reset, xpad_axis_z_left, calibration_reset); +ALLY_CAL_RESET_ATTR(xpad_axis_z_right_cal_reset, xpad_axis_z_right, calibration_reset); + +static struct attribute *gamepad_axis_xy_left_attrs[] = { + &dev_attr_xpad_axis_xy_left_deadzone.attr, + &dev_attr_axis_xyz_deadzone_index.attr, + &dev_attr_xpad_axis_xy_left_ADZ.attr, + &dev_attr_xpad_axis_xy_left_cal_reset.attr, + &dev_attr_xpad_axis_xy_left_cal.attr, + &dev_attr_xpad_axis_xy_cal_index.attr, + &dev_attr_rc_point_left_1.attr, + &dev_attr_rc_point_left_2.attr, + &dev_attr_rc_point_left_3.attr, + &dev_attr_rc_point_left_4.attr, + &dev_attr_rc_point_index.attr, + NULL +}; +static const struct attribute_group ally_controller_axis_xy_left_attr_group = { + .name = "axis_xy_left", + .attrs = gamepad_axis_xy_left_attrs, +}; + +static struct attribute *gamepad_axis_xy_right_attrs[] = { + &dev_attr_xpad_axis_xy_right_deadzone.attr, + &dev_attr_axis_xyz_deadzone_index.attr, + &dev_attr_xpad_axis_xy_right_ADZ.attr, + &dev_attr_xpad_axis_xy_right_cal_reset.attr, + &dev_attr_xpad_axis_xy_right_cal.attr, + &dev_attr_xpad_axis_xy_cal_index.attr, + &dev_attr_rc_point_right_1.attr, + &dev_attr_rc_point_right_2.attr, + &dev_attr_rc_point_right_3.attr, + &dev_attr_rc_point_right_4.attr, + &dev_attr_rc_point_index.attr, + NULL +}; +static const struct attribute_group ally_controller_axis_xy_right_attr_group = { + .name = "axis_xy_right", + .attrs = gamepad_axis_xy_right_attrs, +}; + +static struct attribute *gamepad_axis_z_left_attrs[] = { + &dev_attr_xpad_axis_z_left_deadzone.attr, &dev_attr_axis_xyz_deadzone_index.attr, + &dev_attr_xpad_axis_z_left_cal.attr, &dev_attr_xpad_axis_z_cal_index.attr, + &dev_attr_xpad_axis_z_left_cal_reset.attr, NULL +}; +static const struct attribute_group ally_controller_axis_z_left_attr_group = { + .name = "axis_z_left", + .attrs = gamepad_axis_z_left_attrs, +}; + +static struct attribute *gamepad_axis_z_right_attrs[] = { + &dev_attr_xpad_axis_z_right_deadzone.attr, &dev_attr_axis_xyz_deadzone_index.attr, + &dev_attr_xpad_axis_z_right_cal.attr, &dev_attr_xpad_axis_z_cal_index.attr, + &dev_attr_xpad_axis_z_right_cal_reset.attr, NULL +}; +static const struct attribute_group ally_controller_axis_z_right_attr_group = { + .name = "axis_z_right", + .attrs = gamepad_axis_z_right_attrs, +}; + +static const struct attribute_group *gamepad_device_attr_groups[] = { + &ally_controller_attr_group, + &ally_controller_axis_xy_left_attr_group, + &ally_controller_axis_xy_right_attr_group, + &ally_controller_axis_z_left_attr_group, + &ally_controller_axis_z_right_attr_group, + &btn_mapping_m1_attr_group, + &btn_mapping_m2_attr_group, + &btn_mapping_a_attr_group, + &btn_mapping_b_attr_group, + &btn_mapping_x_attr_group, + &btn_mapping_y_attr_group, + &btn_mapping_lb_attr_group, + &btn_mapping_rb_attr_group, + &btn_mapping_ls_attr_group, + &btn_mapping_rs_attr_group, + &btn_mapping_dpad_u_attr_group, + &btn_mapping_dpad_d_attr_group, + &btn_mapping_dpad_l_attr_group, + &btn_mapping_dpad_r_attr_group, + &btn_mapping_view_attr_group, + &btn_mapping_menu_attr_group, + NULL +}; + +static int __gamepad_write_all_to_mcu(struct hid_device *hdev, + struct ally_gamepad_cfg *ally_cfg) +{ + u8 *hidbuf; + int ret; + + ret = __gamepad_set_mapping(hdev, ally_cfg, btn_pair_dpad_u_d); + if (ret < 0) + return ret; + ret = __gamepad_set_mapping(hdev, ally_cfg, btn_pair_dpad_l_r); + if (ret < 0) + return ret; + ret = __gamepad_set_mapping(hdev, ally_cfg, btn_pair_ls_rs); + if (ret < 0) + return ret; + ret = __gamepad_set_mapping(hdev, ally_cfg, btn_pair_lb_rb); + if (ret < 0) + return ret; + ret = __gamepad_set_mapping(hdev, ally_cfg, btn_pair_a_b); + if (ret < 0) + return ret; + ret = __gamepad_set_mapping(hdev, ally_cfg, btn_pair_x_y); + if (ret < 0) + return ret; + ret = __gamepad_set_mapping(hdev, ally_cfg, btn_pair_view_menu); + if (ret < 0) + return ret; + ret = __gamepad_set_mapping(hdev, ally_cfg, btn_pair_m1_m2); + if (ret < 0) + return ret; + __gamepad_set_mapping(hdev, ally_cfg, btn_pair_lt_rt); + if (ret < 0) + return ret; + __gamepad_set_deadzones(hdev, ally_cfg); + if (ret < 0) + return ret; + __gamepad_write_js_ADZ_to_mcu(hdev, ally_cfg); + if (ret < 0) + return ret; + __gamepad_write_vibe_intensity_to_mcu(hdev, ally_cfg); + if (ret < 0) + return ret; + __gamepad_write_response_curves_to_mcu(hdev, ally_cfg); + if (ret < 0) + return ret; + ret = __gamepad_check_ready(hdev); + if (ret < 0) + return ret; + + /* set turbo */ + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + hidbuf[2] = xpad_cmd_set_turbo; + hidbuf[3] = xpad_cmd_len_turbo; + memcpy(&hidbuf[4], ally_cfg->turbo_btns[ally_cfg->mode - 1], TURBO_BLOCK_LEN); + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + + kfree(hidbuf); + return ret; +} + +static struct ally_gamepad_cfg *ally_gamepad_cfg_create(struct hid_device *hdev) +{ + struct ally_gamepad_cfg *ally_cfg; + struct input_dev *input_dev; + int i, err; + + ally_cfg = devm_kzalloc(&hdev->dev, sizeof(*ally_cfg), GFP_KERNEL); + if (!ally_cfg) + return ERR_PTR(-ENOMEM); + ally_cfg->hdev = hdev; + + input_dev = devm_input_allocate_device(&hdev->dev); + if (!input_dev) { + err = -ENOMEM; + goto free_ally_cfg; + } + ally_cfg->input = input_dev; + + 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 Config"; + input_set_capability(input_dev, EV_KEY, KEY_PROG1); + input_set_capability(input_dev, EV_KEY, KEY_F16); + input_set_capability(input_dev, EV_KEY, KEY_F17); + input_set_capability(input_dev, EV_KEY, KEY_F18); + + input_set_drvdata(input_dev, hdev); + + err = input_register_device(input_dev); + if (err) + goto free_input_dev; + + ally_cfg->mode = xpad_mode_game; + for (i = 0; i < xpad_mode_mouse; i++) { + ally_cfg->deadzones[i][0][1] = 64; + ally_cfg->deadzones[i][0][3] = 64; + ally_cfg->deadzones[i][1][1] = 64; + ally_cfg->deadzones[i][1][3] = 64; + ally_cfg->response_curve[i][0][0] = 0x14; + ally_cfg->response_curve[i][0][1] = 0x14; + ally_cfg->response_curve[i][0][2] = 0x28; + ally_cfg->response_curve[i][0][3] = 0x28; + ally_cfg->response_curve[i][0][4] = 0x3c; + ally_cfg->response_curve[i][0][5] = 0x3c; + ally_cfg->response_curve[i][0][6] = 0x63; + ally_cfg->response_curve[i][0][7] = 0x63; + ally_cfg->response_curve[i][1][0] = 0x14; + ally_cfg->response_curve[i][1][1] = 0x14; + ally_cfg->response_curve[i][1][2] = 0x28; + ally_cfg->response_curve[i][1][3] = 0x28; + ally_cfg->response_curve[i][1][4] = 0x3c; + ally_cfg->response_curve[i][1][5] = 0x3c; + ally_cfg->response_curve[i][1][6] = 0x63; + ally_cfg->response_curve[i][1][7] = 0x63; + ally_cfg->vibration_intensity[i][0] = 64; + ally_cfg->vibration_intensity[i][1] = 64; + } + drvdata.gamepad_cfg = ally_cfg; + + /* ignore all errors for this as they are related to USB HID I/O */ + __gamepad_mapping_xpad_default(ally_cfg); + __gamepad_mapping_wasd_default(ally_cfg); + /* these calls will never error so ignore the return */ + __gamepad_mapping_store(ally_cfg, KB_M2, btn_pair_m1_m2, btn_pair_side_left, false); + __gamepad_mapping_store(ally_cfg, KB_M1, btn_pair_m1_m2, btn_pair_side_right, false); + __gamepad_set_mode(hdev, ally_cfg, xpad_mode_game); + __gamepad_set_mapping(hdev, ally_cfg, btn_pair_m1_m2); + /* ensure we have data for users to start from */ + __gamepad_get_calibration(hdev); + + if (sysfs_create_groups(&hdev->dev.kobj, gamepad_device_attr_groups)) { + err = -ENODEV; + goto unregister_input_dev; + } + + return ally_cfg; + +unregister_input_dev: + input_unregister_device(input_dev); + ally_cfg->input = NULL; // Prevent double free when kfree(ally_cfg) happens + +free_input_dev: + devm_kfree(&hdev->dev, input_dev); + +free_ally_cfg: + devm_kfree(&hdev->dev, ally_cfg); + return ERR_PTR(err); +} + +static void ally_cfg_remove(struct hid_device *hdev) +{ + __gamepad_set_mode(hdev, drvdata.gamepad_cfg, xpad_mode_mouse); + sysfs_remove_groups(&hdev->dev.kobj, gamepad_device_attr_groups); +} + +/**************************************************************************************************/ +/* ROG Ally LED control */ +/**************************************************************************************************/ +static void ally_schedule_work(struct ally_rgb_leds *led) +{ + unsigned long flags; + + spin_lock_irqsave(&led->lock, flags); + if (!led->removed) + schedule_work(&led->work); + spin_unlock_irqrestore(&led->lock, flags); +} + +static void ally_led_do_brightness(struct work_struct *work) +{ + struct ally_rgb_leds *led = container_of(work, struct ally_rgb_leds, work); + u8 buf[] = { FEATURE_ROG_ALLY_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 }; + unsigned long flags; + bool do_rgb = false; + + spin_lock_irqsave(&led->lock, flags); + if (!led->update_bright) { + spin_unlock_irqrestore(&led->lock, flags); + return; + } + led->update_bright = false; + do_rgb = led->rgb_software_mode; + buf[4] = led->brightness; + spin_unlock_irqrestore(&led->lock, flags); + + if (asus_dev_set_report(led->hdev, buf, sizeof(buf)) < 0) + hid_err(led->hdev, "Ally failed to set backlight\n"); + + if (do_rgb) { + led->update_rgb = true; + ally_schedule_work(led); + } +} + +static void ally_led_do_rgb(struct work_struct *work) +{ + struct ally_rgb_leds *led = container_of(work, struct ally_rgb_leds, work); + unsigned long flags; + int ret; + + u8 buf[16] = { [0] = FEATURE_ROG_ALLY_REPORT_ID, + [1] = FEATURE_ROG_ALLY_CODE_PAGE, + [2] = xpad_cmd_set_leds, + [3] = xpad_cmd_len_leds }; + + spin_lock_irqsave(&led->lock, flags); + if (!led->update_rgb) { + spin_unlock_irqrestore(&led->lock, flags); + return; + } + for (int i = 0; i < 4; i++) { + buf[4 + i * 3] = led->gamepad_red[i]; + buf[5 + i * 3] = led->gamepad_green[i]; + buf[6 + i * 3] = led->gamepad_blue[i]; + } + led->update_rgb = false; + spin_unlock_irqrestore(&led->lock, flags); + + ret = asus_dev_set_report(led->hdev, buf, sizeof(buf)); + if (ret < 0) + hid_err(led->hdev, "Ally failed to set gamepad backlight: %d\n", ret); +} + +static void ally_led_work(struct work_struct *work) +{ + ally_led_do_brightness(work); + ally_led_do_rgb(work); +} + +static void ally_backlight_set(struct led_classdev *led_cdev, enum led_brightness brightness) +{ + struct ally_rgb_leds *led = + container_of(led_cdev, struct ally_rgb_leds, led_bright_dev); + unsigned long flags; + + spin_lock_irqsave(&led->lock, flags); + led->update_bright = true; + led->brightness = brightness; + spin_unlock_irqrestore(&led->lock, flags); + + ally_schedule_work(led); +} + +static enum led_brightness ally_backlight_get(struct led_classdev *led_cdev) +{ + struct ally_rgb_leds *led = + container_of(led_cdev, struct ally_rgb_leds, led_bright_dev); + enum led_brightness brightness; + unsigned long flags; + + spin_lock_irqsave(&led->lock, flags); + brightness = led->brightness; + spin_unlock_irqrestore(&led->lock, flags); + + return brightness; +} + +static void ally_set_rgb_brightness(struct led_classdev *cdev, enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct ally_rgb_leds *led = container_of(mc_cdev, struct ally_rgb_leds, led_rgb_dev); + unsigned long flags; + + led_mc_calc_color_components(mc_cdev, brightness); + spin_lock_irqsave(&led->lock, flags); + led->update_rgb = true; + led->rgb_software_mode = true; + for (int i = 0; i < 4; i++) { + led->gamepad_red[i] = mc_cdev->subled_info[i * 3].brightness; + led->gamepad_green[i] = mc_cdev->subled_info[i * 3 + 1].brightness; + led->gamepad_blue[i] = mc_cdev->subled_info[i * 3 + 2].brightness; + } + spin_unlock_irqrestore(&led->lock, flags); + + ally_schedule_work(led); +} + +static int ally_gamepad_register_brightness(struct hid_device *hdev, + struct ally_rgb_leds *led_rgb) +{ + struct led_classdev *led_cdev; + + led_cdev = &led_rgb->led_bright_dev; + led_cdev->name = "asus::kbd_backlight"; /* Let a desktop control it also */ + led_cdev->max_brightness = 3; + led_cdev->brightness_set = ally_backlight_set; + led_cdev->brightness_get = ally_backlight_get; + + return devm_led_classdev_register(&hdev->dev, &led_rgb->led_bright_dev); +} + +static int ally_gamepad_register_rgb_leds(struct hid_device *hdev, + struct ally_rgb_leds *led_rgb) +{ + struct mc_subled *mc_led_info; + struct led_classdev *led_cdev; + + mc_led_info = devm_kmalloc_array(&hdev->dev, 12, sizeof(*mc_led_info), + GFP_KERNEL | __GFP_ZERO); + if (!mc_led_info) + return -ENOMEM; + + mc_led_info[0].color_index = LED_COLOR_ID_RED; + mc_led_info[1].color_index = LED_COLOR_ID_GREEN; + mc_led_info[2].color_index = LED_COLOR_ID_BLUE; + + mc_led_info[3].color_index = LED_COLOR_ID_RED; + mc_led_info[4].color_index = LED_COLOR_ID_GREEN; + mc_led_info[5].color_index = LED_COLOR_ID_BLUE; + + mc_led_info[6].color_index = LED_COLOR_ID_RED; + mc_led_info[7].color_index = LED_COLOR_ID_GREEN; + mc_led_info[8].color_index = LED_COLOR_ID_BLUE; + + mc_led_info[9].color_index = LED_COLOR_ID_RED; + mc_led_info[10].color_index = LED_COLOR_ID_GREEN; + mc_led_info[11].color_index = LED_COLOR_ID_BLUE; + + led_rgb->led_rgb_dev.subled_info = mc_led_info; + led_rgb->led_rgb_dev.num_colors = 3 * 4; + + led_cdev = &led_rgb->led_rgb_dev.led_cdev; + led_cdev->name = "ally:rgb:joystick_rings"; + led_cdev->brightness = 128; + led_cdev->max_brightness = 255; + led_cdev->brightness_set = ally_set_rgb_brightness; + + return devm_led_classdev_multicolor_register(&hdev->dev, &led_rgb->led_rgb_dev); +} + +static struct ally_rgb_leds *ally_gamepad_rgb_create(struct hid_device *hdev) +{ + struct ally_rgb_leds *led_rgb; + int ret; + + led_rgb = devm_kzalloc(&hdev->dev, sizeof(struct ally_rgb_leds), GFP_KERNEL); + if (!led_rgb) + return ERR_PTR(-ENOMEM); + + ret = ally_gamepad_register_rgb_leds(hdev, led_rgb); + if (ret < 0) { + cancel_work_sync(&led_rgb->work); + devm_kfree(&hdev->dev, led_rgb); + return ERR_PTR(ret); + } + + ret = ally_gamepad_register_brightness(hdev, led_rgb); + if (ret < 0) { + cancel_work_sync(&led_rgb->work); + devm_kfree(&hdev->dev, led_rgb); + return ERR_PTR(ret); + } + + led_rgb->hdev = hdev; + led_rgb->brightness = 3; + led_rgb->removed = false; + + for (int i = 0; i < 4; i++) { + led_rgb->gamepad_red[i] = 255; + led_rgb->gamepad_green[i] = 255; + led_rgb->gamepad_blue[i] = 255; + } + + INIT_WORK(&led_rgb->work, ally_led_work); + spin_lock_init(&led_rgb->lock); + + led_rgb->update_bright = true; + ally_schedule_work(led_rgb); + + return led_rgb; +} + +static void ally_rgb_remove(struct hid_device *hdev) +{ + struct ally_rgb_leds *led_rgb = drvdata.led_rgb; + unsigned long flags; + + spin_lock_irqsave(&led_rgb->lock, flags); + led_rgb->removed = true; + spin_unlock_irqrestore(&led_rgb->lock, flags); + cancel_work_sync(&led_rgb->work); +} + +/**************************************************************************************************/ +/* ROG Ally driver init */ +/**************************************************************************************************/ +static int ally_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, + int size) +{ + struct ally_gamepad_cfg *cfg = drvdata.gamepad_cfg; + struct ally_x_device *ally_x = drvdata.ally_x; + + if (ally_x) { + if ((hdev->bus == BUS_USB && report->id == ALLY_X_INPUT_REPORT_USB && + size == ALLY_X_INPUT_REPORT_USB_SIZE) || + (cfg && data[0] == 0x5A)) { + ally_x_raw_event(ally_x, report, data, size); + } else { + return -1; + } + } + if (cfg && !ally_x) { + input_report_key(cfg->input, KEY_PROG1, data[1] == 0x38); + input_report_key(cfg->input, KEY_F16, data[1] == 0xA6); + input_report_key(cfg->input, KEY_F17, data[1] == 0xA7); + input_report_key(cfg->input, KEY_F18, data[1] == 0xA8); + input_sync(cfg->input); + } + + return 0; +} + +static int ally_gamepad_init(struct hid_device *hdev, u8 report_id) +{ + const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54, 0x65, + 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 }; + int ret; + + ret = asus_dev_set_report(hdev, buf, sizeof(buf)); + if (ret < 0) + hid_err(hdev, "Ally failed to send init command: %d\n", ret); + + return ret; +} + +static int ally_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_host_endpoint *ep = intf->cur_altsetting->endpoint; + int ret; + + if (ep->desc.bEndpointAddress != ALLY_CFG_INTF_IN_ADDRESS && + ep->desc.bEndpointAddress != ALLY_X_INTERFACE_ADDRESS) + return -ENODEV; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "Parse failed\n"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) { + hid_err(hdev, "Failed to start HID device\n"); + return ret; + } + + 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_gamepad_init(hdev, FEATURE_REPORT_ID); + if (ret < 0) + return ret; + + ret = ally_gamepad_init(hdev, FEATURE_KBD_LED_REPORT_ID1); + if (ret < 0) + return ret; + + ret = ally_gamepad_init(hdev, FEATURE_KBD_LED_REPORT_ID2); + if (ret < 0) + return ret; + + drvdata.hdev = hdev; + hid_set_drvdata(hdev, &drvdata); + + /* This should almost always exist */ + if (ep->desc.bEndpointAddress == ALLY_CFG_INTF_IN_ADDRESS) { + drvdata.led_rgb = ally_gamepad_rgb_create(hdev); + if (IS_ERR(drvdata.led_rgb)) + hid_err(hdev, "Failed to create Ally gamepad LEDs.\n"); + else + hid_info(hdev, "Created Ally RGB LED controls.\n"); + + ally_gamepad_cfg_create(hdev); // assigns self + if (IS_ERR(drvdata.gamepad_cfg)) + hid_err(hdev, "Failed to create Ally gamepad attributes.\n"); + else + hid_info(hdev, "Created Ally gamepad attributes.\n"); + + if (IS_ERR(drvdata.led_rgb) && IS_ERR(drvdata.gamepad_cfg)) + goto err_close; + } + + /* May or may not exist */ + if (ep->desc.bEndpointAddress == ALLY_X_INTERFACE_ADDRESS) { + drvdata.ally_x = ally_x_create(hdev); + if (IS_ERR(drvdata.ally_x)) { + hid_err(hdev, "Failed to create Ally X gamepad.\n"); + drvdata.ally_x = NULL; + goto err_close; + } + hid_info(hdev, "Created Ally X controller.\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_remove(struct hid_device *hdev) +{ + if (drvdata.ally_x) + ally_x_remove(hdev); + if (drvdata.led_rgb) + ally_rgb_remove(hdev); + if (drvdata.gamepad_cfg) + ally_cfg_remove(hdev); + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static int ally_resume(struct hid_device *hdev) +{ + int ret; + + ret = ally_gamepad_init(hdev, FEATURE_REPORT_ID); + if (ret < 0) + return ret; + + ret = ally_gamepad_init(hdev, FEATURE_KBD_LED_REPORT_ID1); + if (ret < 0) + return ret; + + ret = ally_gamepad_init(hdev, FEATURE_KBD_LED_REPORT_ID2); + if (ret < 0) + return ret; + + if (drvdata.ally_x && drvdata.ally_x->output_worker_initialized) + schedule_work(&drvdata.ally_x->output_worker); + + return ret; +} + +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_probe, + .remove = ally_remove, + .raw_event = ally_raw_event, + .resume = ally_resume, +}; + +static int __init rog_ally_cfg_init(void) +{ + return hid_register_driver(&rog_ally_cfg); +} + +static void __exit rog_ally_cfg_exit(void) +{ + hid_unregister_driver(&rog_ally_cfg); +} + +module_init(rog_ally_cfg_init); +module_exit(rog_ally_cfg_exit); + +MODULE_AUTHOR("Luke D. Jones"); +MODULE_DESCRIPTION("HID Driver for ASUS ROG Ally gamepad configuration."); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-asus-ally.h b/drivers/hid/hid-asus-ally.h new file mode 100644 index 00000000000000..252d9f126e32fb --- /dev/null +++ b/drivers/hid/hid-asus-ally.h @@ -0,0 +1,544 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * + * HID driver for Asus ROG laptops and Ally + * + * Copyright (c) 2023 Luke Jones + */ + +#include +#include + +#define ALLY_X_INTERFACE_ADDRESS 0x87 + +#define BTN_CODE_LEN 11 +#define MAPPING_BLOCK_LEN 44 + +#define TURBO_BLOCK_LEN 32 +#define TURBO_BLOCK_STEP 2 + +#define PAD_A "pad_a" +#define PAD_B "pad_b" +#define PAD_X "pad_x" +#define PAD_Y "pad_y" +#define PAD_LB "pad_lb" +#define PAD_RB "pad_rb" +#define PAD_LS "pad_ls" +#define PAD_RS "pad_rs" +#define PAD_DPAD_UP "pad_dpad_up" +#define PAD_DPAD_DOWN "pad_dpad_down" +#define PAD_DPAD_LEFT "pad_dpad_left" +#define PAD_DPAD_RIGHT "pad_dpad_right" +#define PAD_VIEW "pad_view" +#define PAD_MENU "pad_menu" +#define PAD_XBOX "pad_xbox" + +#define KB_M1 "kb_m1" +#define KB_M2 "kb_m2" +#define KB_ESC "kb_esc" +#define KB_F1 "kb_f1" +#define KB_F2 "kb_f2" +#define KB_F3 "kb_f3" +#define KB_F4 "kb_f4" +#define KB_F5 "kb_f5" +#define KB_F6 "kb_f6" +#define KB_F7 "kb_f7" +#define KB_F8 "kb_f8" +#define KB_F9 "kb_f9" +#define KB_F10 "kb_f10" +#define KB_F11 "kb_f11" +#define KB_F12 "kb_f12" +#define KB_F14 "kb_f14" +#define KB_F15 "kb_f15" + +#define KB_BACKTICK "kb_backtick" +#define KB_1 "kb_1" +#define KB_2 "kb_2" +#define KB_3 "kb_3" +#define KB_4 "kb_4" +#define KB_5 "kb_5" +#define KB_6 "kb_6" +#define KB_7 "kb_7" +#define KB_8 "kb_8" +#define KB_9 "kb_9" +#define KB_0 "kb_0" +#define KB_HYPHEN "kb_hyphen" +#define KB_EQUALS "kb_equals" +#define KB_BACKSPACE "kb_backspace" + +#define KB_TAB "kb_tab" +#define KB_Q "kb_q" +#define KB_W "kb_w" +#define KB_E "kb_e" +#define KB_R "kb_r" +#define KB_T "kb_t" +#define KB_Y "kb_y" +#define KB_U "kb_u" +#define KB_I "kb_i" +#define KB_O "kb_o" +#define KB_P "kb_p" +#define KB_LBRACKET "kb_lbracket" +#define KB_RBRACKET "kb_rbracket" +#define KB_BACKSLASH "kb_bkslash" + +#define KB_CAPS "kb_caps" +#define KB_A "kb_a" +#define KB_S "kb_s" +#define KB_D "kb_d" +#define KB_F "kb_f" +#define KB_G "kb_g" +#define KB_H "kb_h" +#define KB_J "kb_j" +#define KB_K "kb_k" +#define KB_L "kb_l" +#define KB_SEMI "kb_semicolon" +#define KB_QUOTE "kb_quote" +#define KB_RET "kb_enter" + +#define KB_LSHIFT "kb_lshift" +#define KB_Z "kb_z" +#define KB_X "kb_x" +#define KB_C "kb_c" +#define KB_V "kb_v" +#define KB_B "kb_b" +#define KB_N "kb_n" +#define KB_M "kb_m" +#define KB_COMMA "kb_comma" +#define KB_PERIOD "kb_period" +#define KB_FWDSLASH "kb_fwdslash" +#define KB_RSHIFT "kb_rshift" + +#define KB_LCTL "kb_lctl" +#define KB_META "kb_meta" +#define KB_LALT "kb_lalt" +#define KB_SPACE "kb_space" +#define KB_RALT "kb_ralt" +#define KB_MENU "kb_menu" +#define KB_RCTL "kb_rctl" + +#define KB_PRNTSCN "kb_prntscn" +#define KB_SCRLCK "kb_scrlck" +#define KB_PAUSE "kb_pause" +#define KB_INS "kb_ins" +#define KB_HOME "kb_home" +#define KB_PGUP "kb_pgup" +#define KB_DEL "kb_del" +#define KB_END "kb_end" +#define KB_PGDWN "kb_pgdwn" + +#define KB_UP_ARROW "kb_up_arrow" +#define KB_DOWN_ARROW "kb_down_arrow" +#define KB_LEFT_ARROW "kb_left_arrow" +#define KB_RIGHT_ARROW "kb_right_arrow" + +#define NUMPAD_LOCK "numpad_lock" +#define NUMPAD_FWDSLASH "numpad_fwdslash" +#define NUMPAD_ASTERISK "numpad_asterisk" +#define NUMPAD_HYPHEN "numpad_hyphen" +#define NUMPAD_0 "numpad_0" +#define NUMPAD_1 "numpad_1" +#define NUMPAD_2 "numpad_2" +#define NUMPAD_3 "numpad_3" +#define NUMPAD_4 "numpad_4" +#define NUMPAD_5 "numpad_5" +#define NUMPAD_6 "numpad_6" +#define NUMPAD_7 "numpad_7" +#define NUMPAD_8 "numpad_8" +#define NUMPAD_9 "numpad_9" +#define NUMPAD_PLUS "numpad_plus" +#define NUMPAD_ENTER "numpad_enter" +#define NUMPAD_PERIOD "numpad_." + +#define MOUSE_LCLICK "rat_lclick" +#define MOUSE_RCLICK "rat_rclick" +#define MOUSE_MCLICK "rat_mclick" +#define MOUSE_WHEEL_UP "rat_wheel_up" +#define MOUSE_WHEEL_DOWN "rat_wheel_down" + +#define MEDIA_SCREENSHOT "media_screenshot" +#define MEDIA_SHOW_KEYBOARD "media_show_keyboard" +#define MEDIA_SHOW_DESKTOP "media_show_desktop" +#define MEDIA_START_RECORDING "media_start_recording" +#define MEDIA_MIC_OFF "media_mic_off" +#define MEDIA_VOL_DOWN "media_vol_down" +#define MEDIA_VOL_UP "media_vol_up" + +/* required so we can have nested attributes with same name but different functions */ +#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) + +/* response curve macros */ +#define ALLY_RESP_CURVE_SHOW(_name, _point_n) \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ + int idx = (_point_n - 1) * 2; \ + if (!drvdata.gamepad_cfg) \ + return -ENODEV; \ + return sysfs_emit( \ + buf, "%d %d\n", \ + ally_cfg->response_curve[ally_cfg->mode] \ + [btn_pair_side_left][idx], \ + ally_cfg->response_curve[ally_cfg->mode] \ + [btn_pair_side_right] \ + [idx + 1]); \ + } + +#define ALLY_RESP_CURVE_STORE(_name, _side, _point_n) \ + static ssize_t _name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + int ret = __gamepad_store_response_curve( \ + dev, buf, btn_pair_side_##_side, _point_n); \ + if (ret < 0) \ + return ret; \ + return count; \ + } + +/* _point_n must start at 1 */ +#define ALLY_JS_RC_POINT(_side, _point_n, _sysfs_label) \ + ALLY_RESP_CURVE_SHOW(rc_point_##_side##_##_point_n, _point_n); \ + ALLY_RESP_CURVE_STORE(rc_point_##_side##_##_point_n, _side, _point_n); \ + ALLY_DEVICE_ATTR_RW(rc_point_##_side##_##_point_n, \ + _sysfs_label##_point_n) + +/* deadzone macros */ +#define ALLY_AXIS_DEADZONE_SHOW(_axis) \ + static ssize_t _axis##_deadzone_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ + int side, is_tr; \ + if (!drvdata.gamepad_cfg) \ + return -ENODEV; \ + is_tr = _axis > xpad_axis_xy_right; \ + side = _axis == xpad_axis_xy_right || \ + _axis == xpad_axis_z_right ? \ + 2 : \ + 0; \ + return sysfs_emit( \ + buf, "%d %d\n", \ + ally_cfg->deadzones[ally_cfg->mode][is_tr][side], \ + ally_cfg->deadzones[ally_cfg->mode][is_tr][side + 1]); \ + } + +#define ALLY_AXIS_DEADZONE_STORE(_axis) \ + static ssize_t _axis##_deadzone_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ + if (!drvdata.gamepad_cfg) \ + return -ENODEV; \ + int ret = __gamepad_store_deadzones(ally_cfg, _axis, buf); \ + if (ret < 0) \ + return ret; \ + return count; \ + } + +#define ALLY_AXIS_DEADZONE(_axis, _sysfs_label) \ + ALLY_AXIS_DEADZONE_SHOW(_axis); \ + ALLY_AXIS_DEADZONE_STORE(_axis); \ + ALLY_DEVICE_ATTR_RW(_axis##_deadzone, _sysfs_label) + +/* button specific macros */ +#define ALLY_BTN_SHOW(_fname, _pair, _side, _secondary) \ + static ssize_t _fname##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ + if (!drvdata.gamepad_cfg) \ + return -ENODEV; \ + return sysfs_emit(buf, "%s\n", \ + __btn_map_to_string(ally_cfg, _pair, _side, \ + _secondary)); \ + } + +#define ALLY_BTN_STORE(_fname, _pair, _side, _secondary) \ + static ssize_t _fname##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ + if (!drvdata.gamepad_cfg) \ + return -ENODEV; \ + int ret = __gamepad_mapping_store(ally_cfg, buf, _pair, _side, \ + _secondary); \ + if (ret < 0) \ + return ret; \ + return count; \ + } + +#define ALLY_BTN_TURBO_SHOW(_fname, _pair, _side) \ + static ssize_t _fname##_turbo_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return sysfs_emit(buf, "%d\n", \ + __gamepad_turbo_show(dev, _pair, _side)); \ + } + +#define ALLY_BTN_TURBO_STORE(_fname, _pair, _side) \ + static ssize_t _fname##_turbo_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + int ret = __gamepad_turbo_store(dev, buf, _pair, _side); \ + if (ret < 0) \ + return ret; \ + return count; \ + } + +#define ALLY_BTN_ATTRS_GROUP(_name, _fname) \ + static struct attribute *_fname##_attrs[] = { \ + &dev_attr_##_fname.attr, &dev_attr_##_fname##_macro.attr, \ + &dev_attr_##_fname##_turbo.attr, NULL \ + }; \ + static const struct attribute_group _fname##_attr_group = { \ + .name = __stringify(_name), \ + .attrs = _fname##_attrs, \ + } + +#define ALLY_BTN_MAPPING(_fname, _pair, _side) \ + ALLY_BTN_SHOW(btn_mapping_##_fname, _pair, _side, false); \ + ALLY_BTN_STORE(btn_mapping_##_fname, _pair, _side, false); \ + ALLY_BTN_SHOW(btn_mapping_##_fname##_macro, _pair, _side, true); \ + ALLY_BTN_STORE(btn_mapping_##_fname##_macro, _pair, _side, true); \ + ALLY_BTN_TURBO_SHOW(btn_mapping_##_fname, _pair, _side); \ + ALLY_BTN_TURBO_STORE(btn_mapping_##_fname, _pair, _side); \ + ALLY_DEVICE_ATTR_RW(btn_mapping_##_fname, remap); \ + ALLY_DEVICE_ATTR_RW(btn_mapping_##_fname##_macro, macro_remap); \ + ALLY_DEVICE_ATTR_RW(btn_mapping_##_fname##_turbo, turbo); \ + ALLY_BTN_ATTRS_GROUP(btn_##_fname, btn_mapping_##_fname) + +/* calibration macros */ +#define ALLY_CAL_STORE(_fname, _axis) \ + static ssize_t _fname##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + int ret = __gamepad_cal_store(dev, buf, _axis); \ + if (ret < 0) \ + return ret; \ + return count; \ + } + +#define ALLY_CAL_SHOW(_fname, _axis) \ + static ssize_t _fname##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return __gamepad_cal_show(dev, buf, _axis); \ + } + +#define ALLY_CAL_ATTR(_fname, _axis, _sysfs_label) \ + ALLY_CAL_STORE(_fname, _axis); \ + ALLY_CAL_SHOW(_fname, _axis); \ + ALLY_DEVICE_ATTR_RW(_fname, _sysfs_label) + +#define ALLY_CAL_RESET_STORE(_fname, _axis) \ + static ssize_t _fname##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + int ret = __gamepad_cal_reset(dev, buf, _axis); \ + if (ret < 0) \ + return ret; \ + return count; \ + } + +#define ALLY_CAL_RESET_ATTR(_fname, _axis, _sysfs_label) \ + ALLY_CAL_RESET_STORE(_fname, _axis); \ + ALLY_DEVICE_ATTR_WO(_fname, _sysfs_label) + +/* + * The following blocks of packets exist to make setting a default boot config + * easier. They were directly captured from setting the gamepad up. + */ + +/* Default blocks for the xpad mode */ +static const u8 XPAD_DEF1[MAPPING_BLOCK_LEN] = { + 0x01, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x03, 0x8c, 0x88, 0x76, 0x00, 0x00 +}; +static const u8 XPAD_DEF2[MAPPING_BLOCK_LEN] = { + 0x01, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x23, 0x00, 0x00, 0x00, + 0x01, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x0d, 0x00, 0x00, 0x00 +}; +static const u8 XPAD_DEF3[MAPPING_BLOCK_LEN] = { + 0x01, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +static const u8 XPAD_DEF4[MAPPING_BLOCK_LEN] = { + 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +static const u8 XPAD_DEF5[MAPPING_BLOCK_LEN] = { + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x31, 0x00, 0x00, 0x00 +}; +static const u8 XPAD_DEF6[MAPPING_BLOCK_LEN] = { + 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x4d, 0x00, 0x00, 0x00, + 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +static const u8 XPAD_DEF7[MAPPING_BLOCK_LEN] = { + 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +static const u8 XPAD_DEF8[MAPPING_BLOCK_LEN] = { + 0x02, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +static const u8 XPAD_DEF9[MAPPING_BLOCK_LEN] = { + 0x01, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* default blocks for the wasd mode */ +static const u8 WASD_DEF1[MAPPING_BLOCK_LEN] = { + 0x02, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x03, 0x8c, 0x88, 0x76, 0x00, 0x00 +}; +static const u8 WASD_DEF2[MAPPING_BLOCK_LEN] = { + 0x02, 0x00, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x23, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x0d, 0x00, 0x00, 0x00 +}; +static const u8 WASD_DEF3[MAPPING_BLOCK_LEN] = { + 0x02, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +static const u8 WASD_DEF4[MAPPING_BLOCK_LEN] = { + 0x02, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +static const u8 WASD_DEF5[MAPPING_BLOCK_LEN] = { + 0x02, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x31, 0x00, 0x00, 0x00 +}; +static const u8 WASD_DEF6[MAPPING_BLOCK_LEN] = { + 0x02, 0x00, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x4d, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +static const u8 WASD_DEF7[MAPPING_BLOCK_LEN] = { + 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +static const u8 WASD_DEF8[MAPPING_BLOCK_LEN] = { + 0x02, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +static const u8 WASD_DEF9[MAPPING_BLOCK_LEN] = { + 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x88, 0x0d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* + * the xpad_mode is used inside the mode setting packet and is used + * for indexing (xpad_mode - 1) + */ +enum xpad_mode { + xpad_mode_game = 0x01, + xpad_mode_wasd = 0x02, + xpad_mode_mouse = 0x03, +}; + +/* the xpad_cmd determines which feature is set or queried */ +enum xpad_cmd { + xpad_cmd_set_mode = 0x01, + xpad_cmd_set_mapping = 0x02, + xpad_cmd_set_js_dz = 0x04, /* deadzones */ + xpad_cmd_set_tr_dz = 0x05, /* deadzones */ + xpad_cmd_set_vibe_intensity = 0x06, + xpad_cmd_set_leds = 0x08, + xpad_cmd_check_ready = 0x0A, + xpad_cmd_set_calibration = 0x0D, + xpad_cmd_set_turbo = 0x0F, + xpad_cmd_set_response_curve = 0x13, + xpad_cmd_set_adz = 0x18, +}; + +/* the xpad_cmd determines which feature is set or queried */ +enum xpad_cmd_len { + xpad_cmd_len_mode = 0x01, + xpad_cmd_len_mapping = 0x2c, + xpad_cmd_len_deadzone = 0x04, + xpad_cmd_len_vibe_intensity = 0x02, + xpad_cmd_len_leds = 0x0C, + xpad_cmd_len_calibration2 = 0x01, + xpad_cmd_len_calibration3 = 0x01, + xpad_cmd_len_turbo = 0x20, + xpad_cmd_len_response_curve = 0x09, + xpad_cmd_len_adz = 0x02, +}; + +/* + * the xpad_mode is used in various set and query HID packets and is + * used for indexing (xpad_axis - 1) + */ +enum xpad_axis { + xpad_axis_xy_left = 0x01, + xpad_axis_xy_right = 0x02, + xpad_axis_z_left = 0x03, + xpad_axis_z_right = 0x04, +}; + +enum btn_pair { + btn_pair_dpad_u_d = 0x01, + btn_pair_dpad_l_r = 0x02, + btn_pair_ls_rs = 0x03, + btn_pair_lb_rb = 0x04, + btn_pair_a_b = 0x05, + btn_pair_x_y = 0x06, + btn_pair_view_menu = 0x07, + btn_pair_m1_m2 = 0x08, + btn_pair_lt_rt = 0x09, +}; + +enum btn_pair_side { + btn_pair_side_left = 0x00, + btn_pair_side_right = 0x01, +}; diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index 7cd63613609e01..dbcce421de0211 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 ALLY_CFG_INTF_IN_ADDRESS 0x83 +#define ALLY_CFG_INTF_OUT_ADDRESS 0x04 +#define ALLY_X_INTERFACE_ADDRESS 0x87 + #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 | \ @@ -1004,6 +1009,17 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) drvdata->quirks = id->driver_data; + /* Ignore these endpoints as they will be used by other drivers */ + if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) { + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_host_endpoint *ep = intf->cur_altsetting->endpoint; + + if (ep->desc.bEndpointAddress == ALLY_X_INTERFACE_ADDRESS || + ep->desc.bEndpointAddress == ALLY_CFG_INTF_IN_ADDRESS || + ep->desc.bEndpointAddress == ALLY_CFG_INTF_OUT_ADDRESS) + return -ENODEV; + } + /* * T90CHI's keyboard dock returns same ID values as T100CHI's dock. * Thus, identify T90CHI dock with product name string. @@ -1252,10 +1268,10 @@ static const struct hid_device_id asus_devices[] = { QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, { 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 }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_AZOTH_KEYBOARD), QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index eff7f5df08e27f..f6134b20e20d9f 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -697,7 +697,7 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RS100, quirk_ati_ /* * In the AMD NL platform, this device ([1022:7912]) has a class code of * PCI_CLASS_SERIAL_USB_XHCI (0x0c0330), which means the xhci driver will - * claim it. The same applies on the VanGogh platform device ([1022:163a]). + * claim it. * * But the dwc3 driver is a more specific driver for this device, and we'd * prefer to use it instead of xhci. To prevent xhci from claiming the @@ -705,7 +705,7 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_RS100, quirk_ati_ * defines as "USB device (not host controller)". The dwc3 driver can then * claim it based on its Vendor and Device ID. */ -static void quirk_amd_dwc_class(struct pci_dev *pdev) +static void quirk_amd_nl_class(struct pci_dev *pdev) { u32 class = pdev->class; @@ -718,9 +718,7 @@ static void quirk_amd_dwc_class(struct pci_dev *pdev) } } DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_NL_USB, - quirk_amd_dwc_class); -DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VANGOGH_USB, - quirk_amd_dwc_class); + quirk_amd_nl_class); /* * Synopsys USB 3.x host HAPS platform has a class code of diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 7e9251fc33416e..86cfe292409a04 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -253,6 +253,19 @@ 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 (firmware) Driver" + depends on ACPI_WMI + depends on ASUS_WMI + select FW_ATTR_CLASS + help + Say Y here if you have a WMI aware Asus laptop 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 @@ -264,6 +277,7 @@ config ASUS_WMI depends on HOTPLUG_PCI depends on ACPI_VIDEO || ACPI_VIDEO = n depends on SERIO_I8042 || SERIO_I8042 = n + select ASUS_ARMOURY select INPUT_SPARSEKMAP select LEDS_CLASS select NEW_LEDS diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 1de432e8861eac..cd85f335a1d290 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/amd/pmf/pmf-quirks.c b/drivers/platform/x86/amd/pmf/pmf-quirks.c index 0b2eb0ae85febd..460444cda1b295 100644 --- a/drivers/platform/x86/amd/pmf/pmf-quirks.c +++ b/drivers/platform/x86/amd/pmf/pmf-quirks.c @@ -29,6 +29,14 @@ static const struct dmi_system_id fwbug_list[] = { }, .driver_data = &quirk_no_sps_bug, }, + { + .ident = "ROG Ally X", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "RC72LA"), + }, + .driver_data = &quirk_no_sps_bug, + }, {} }; @@ -48,4 +56,3 @@ void amd_pmf_quirks_init(struct amd_pmf_dev *dev) dmi_id->ident); } } - diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c new file mode 100644 index 00000000000000..9aec498096a981 --- /dev/null +++ b/drivers/platform/x86/asus-armoury.c @@ -0,0 +1,1042 @@ +// 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) 2010 Intel Corporation. + * Copyright(C) 2024-2024 Luke Jones + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#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 +/* New mode on some devices, define here to clarify remapping later */ +#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 + +#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, +}; + +/* Default limits for tunables available on ASUS ROG laptops */ +#define PPT_CPU_LIMIT_MIN 5 +#define PPT_CPU_LIMIT_MAX 150 +#define PPT_CPU_LIMIT_DEFAULT 80 +#define PPT_PLATFORM_MIN 5 +#define PPT_PLATFORM_MAX 100 +#define PPT_PLATFORM_DEFAULT 80 +#define NVIDIA_BOOST_MIN 5 +#define NVIDIA_BOOST_MAX 25 +#define NVIDIA_TEMP_MIN 75 +#define NVIDIA_TEMP_MAX 87 +#define NVIDIA_POWER_MIN 0 +#define NVIDIA_POWER_MAX 70 +#define NVIDIA_POWER_DEFAULT 70 + +/* Tunables provided by ASUS for gaming laptops */ +struct rog_tunables { + u32 cpu_default; + u32 cpu_min; + u32 cpu_max; + + u32 platform_default; + u32 platform_min; + u32 platform_max; + + u32 ppt_pl1_spl; // cpu + u32 ppt_pl2_sppt; // cpu + u32 ppt_apu_sppt; // plat + u32 ppt_platform_sppt; // plat + u32 ppt_fppt; // cpu + + u32 nv_boost_default; + u32 nv_boost_min; + u32 nv_boost_max; + u32 nv_dynamic_boost; + + u32 nv_temp_default; + u32 nv_temp_min; + u32 nv_temp_max; + u32 nv_temp_target; + + u32 dgpu_tgp_default; + u32 dgpu_tgp_min; + u32 dgpu_tgp_max; + u32 dgpu_tgp; + + u32 min_perf_cores; + u32 max_perf_cores; + u32 max_power_cores; +}; + +static const struct class *fw_attr_class; + +struct asus_armoury_priv { + struct device *fw_attr_dev; + struct kset *fw_attr_kset; + + struct rog_tunables *rog_tunables; + u32 mini_led_dev_id; + u32 gpu_mux_dev_id; + + struct mutex mutex; +}; + +static struct asus_armoury_priv asus_armoury = { + .mutex = __MUTEX_INITIALIZER(asus_armoury.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"); +} + +/** + * attr_int_store() - Generic store function for use with most WMI functions. + * @kobj: Pointer to the driver object. + * @kobj_attribute: Pointer the the attribute calling this function. + * @buf: The buffer to read from, this is parsed to `int` type. + * @count: + * @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. + * + * The WMI functions available on most ASUS laptops return a 1 as "success", and + * a 0 as failed. However some functions can return n > 1 for additional errors. + * attr_int_store() currently treats all values which are not 1 as errors, ignoring + * the possible differences in WMI error returns. + * + * Returns: Either count, or an error. + */ +static ssize_t attr_int_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 result, value; + int err; + + err = kstrtouint(buf, 10, &value); + if (err) + return err; + + if (value < min || value > max) + return -EINVAL; + + 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; + } + + if (result != 1) { + pr_err("Failed to set %s (result): 0x%x\n", attr->attr.name, result); + return -EIO; + } + + 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; +} + +/* 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) +{ + int result, err; + u32 mode; + + 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 = asus_wmi_set_devstate(asus_armoury.mini_led_dev_id, mode, &result); + if (err) { + pr_warn("Failed to set mini-LED: %d\n", err); + return err; + } + + if (result != 1) { + pr_warn("Failed to set mini-LED mode (result): 0x%x\n", result); + return -EIO; + } + + 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) { + err = -ENODEV; + pr_warn("Can not switch MUX to dGPU mode when dGPU is disabled: %02X %02X %d\n", result, optimus, err); + return err; + } + } + + 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) { + err = -ENODEV; + pr_warn("Can not switch MUX to dGPU mode when eGPU is enabled: %d\n", err); + return err; + } + } + + err = asus_wmi_set_devstate(asus_armoury.gpu_mux_dev_id, optimus, &result); + if (err) { + pr_err("Failed to set GPU MUX mode: %d\n", err); + return err; + } + /* !1 is considered a fail by ASUS */ + if (result != 1) { + pr_warn("Failed to set GPU MUX mode (result): 0x%x\n", result); + return -EIO; + } + + 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. + * The reason for this is that an extra code path in the ACPI is enabled when + * the device and bus are powered. + */ +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) { + err = -ENODEV; + pr_warn("Can not disable dGPU when the MUX is in dGPU mode: %d\n", err); + return err; + } + } + + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_DGPU, disable, &result); + if (err) { + pr_warn("Failed to set dGPU disable: %d\n", err); + return err; + } + + if (result != 1) { + pr_warn("Failed to set dGPU disable (result): 0x%x\n", result); + return -EIO; + } + + 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) { + err = -ENODEV; + pr_warn("Can not enable eGPU when the MUX is in dGPU mode: %d\n", err); + return err; + } + } + + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_EGPU, enable, &result); + if (err) { + pr_warn("Failed to set eGPU state: %d\n", err); + return err; + } + + if (result != 1) { + pr_warn("Failed to set eGPU state (retval): 0x%x\n", result); + return -EIO; + } + + 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 256: + mem = 0; + break; + case 258: + mem = 1; + break; + case 259: + mem = 2; + break; + case 260: + mem = 3; + break; + case 261: + mem = 4; + break; + case 262: + /* This is out of order and looks wrong but is correct */ + mem = 8; + break; + case 263: + mem = 5; + break; + case 264: + mem = 6; + break; + case 265: + 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 = 0; + break; + case 1: + mem = 258; + break; + case 2: + mem = 259; + break; + case 3: + mem = 260; + break; + case 4: + mem = 261; + break; + case 5: + mem = 263; + break; + case 6: + mem = 264; + break; + case 7: + mem = 265; + break; + case 8: + /* This is out of order and looks wrong but is correct */ + mem = 262; + 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 the available system memory for the APU to use"); + +static int init_max_cpu_cores(void) +{ + u32 cores; + int err; + + asus_armoury.rog_tunables->min_perf_cores = 4; + asus_armoury.rog_tunables->max_perf_cores = 4; + asus_armoury.rog_tunables->max_power_cores = 8; + + err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_CORES_MAX, &cores); + if (err) + return err; + + cores &= ~ASUS_WMI_DSTS_PRESENCE_BIT; + asus_armoury.rog_tunables->max_power_cores = FIELD_GET(ASUS_POWER_CORE_MASK, cores); + asus_armoury.rog_tunables->max_perf_cores = FIELD_GET(ASUS_PERF_CORE_MASK, cores); + + 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; + int err; + + 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.rog_tunables->max_perf_cores); + else + return sysfs_emit(buf, "%d\n", asus_armoury.rog_tunables->max_power_cores); + case CPU_CORE_MIN: + if (core_type == CPU_CORE_PERF) + return sysfs_emit(buf, "%d\n", asus_armoury.rog_tunables->min_perf_cores); + else + return sysfs_emit(buf, "%d\n", 0); + default: + break; + } + + err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_CORES, &cores); + if (err) + return err; + + if (core_type == CPU_CORE_PERF) + cores = FIELD_GET(ASUS_PERF_CORE_MASK, cores); + else + cores = FIELD_GET(ASUS_POWER_CORE_MASK, 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) +{ + int result, err; + u32 cores, currentv, min, max; + + result = kstrtou32(buf, 10, &cores); + if (result) + return result; + + if (core_type == CPU_CORE_PERF) { + min = asus_armoury.rog_tunables->min_perf_cores; + max = asus_armoury.rog_tunables->max_perf_cores; + } else { + min = 0; + max = asus_armoury.rog_tunables->max_power_cores; + } + if (cores < min || cores > max) + return -EINVAL; + + err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_CORES, ¤tv); + if (err) + return err; + + if (core_type == CPU_CORE_PERF) + cores |= (currentv & 0xff00); + else + cores |= currentv & 0xff; + + if (cores == currentv) + return 0; + + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_CORES, cores, &result); + if (err) { + pr_warn("Failed to set CPU core count: %d\n", err); + return err; + } + + if (result > 1) { + pr_warn("Failed to set CPU core count (result): 0x%x\n", result); + return -EIO; + } + + pr_info("CPU core count changed, reboot required\n"); + 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"); + +/* Simple attribute creation */ +ATTR_GROUP_ROG_TUNABLE(ppt_pl1_spl, "ppt_pl1_spl", ASUS_WMI_DEVID_PPT_PL1_SPL, + cpu_default, cpu_min, cpu_max, 1, "Set the CPU slow package limit"); +ATTR_GROUP_ROG_TUNABLE(ppt_pl2_sppt, "ppt_pl2_sppt", ASUS_WMI_DEVID_PPT_PL2_SPPT, + cpu_default, cpu_min, cpu_max, 1, "Set the CPU fast package limit"); +ATTR_GROUP_ROG_TUNABLE(ppt_apu_sppt, "ppt_apu_sppt", ASUS_WMI_DEVID_PPT_APU_SPPT, + platform_default, platform_min, platform_max, 1, "Set the CPU slow package limit"); +ATTR_GROUP_ROG_TUNABLE(ppt_platform_sppt, "ppt_platform_sppt", ASUS_WMI_DEVID_PPT_PLAT_SPPT, + platform_default, platform_min, platform_max, 1, "Set the CPU slow package limit"); +ATTR_GROUP_ROG_TUNABLE(ppt_fppt, "ppt_fppt", ASUS_WMI_DEVID_PPT_FPPT, + cpu_default, cpu_min, cpu_max, 1, "Set the CPU slow package limit"); + +ATTR_GROUP_ROG_TUNABLE(nv_dynamic_boost, "nv_dynamic_boost", ASUS_WMI_DEVID_NV_DYN_BOOST, + nv_boost_default, nv_boost_min, nv_boost_max, 1, "Set the Nvidia dynamic boost limit"); +ATTR_GROUP_ROG_TUNABLE(nv_temp_target, "nv_temp_target", ASUS_WMI_DEVID_NV_THERM_TARGET, + nv_temp_default, nv_boost_min, nv_temp_max, 1, "Set the Nvidia max thermal limit"); +ATTR_GROUP_INT_VALUE_ONLY_RO(dgpu_base_tgp, "dgpu_base_tgp", ASUS_WMI_DEVID_DGPU_BASE_TGP, + "Read the base TGP value"); +ATTR_GROUP_ROG_TUNABLE(dgpu_tgp, "dgpu_tgp", ASUS_WMI_DEVID_DGPU_SET_TGP, + dgpu_tgp_default, dgpu_tgp_min, dgpu_tgp_max, 1, + "Set the additional TGP on top of the base TGP"); + +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_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 }, + + { &ppt_pl1_spl_attr_group, ASUS_WMI_DEVID_PPT_PL1_SPL }, + { &ppt_pl2_sppt_attr_group, ASUS_WMI_DEVID_PPT_PL2_SPPT }, + { &ppt_apu_sppt_attr_group, ASUS_WMI_DEVID_PPT_APU_SPPT }, + { &ppt_platform_sppt_attr_group, ASUS_WMI_DEVID_PPT_PLAT_SPPT }, + { &ppt_fppt_attr_group, ASUS_WMI_DEVID_PPT_FPPT }, + { &nv_dynamic_boost_attr_group, ASUS_WMI_DEVID_NV_DYN_BOOST }, + { &nv_temp_target_attr_group, ASUS_WMI_DEVID_NV_THERM_TARGET }, + { &dgpu_base_tgp_attr_group, ASUS_WMI_DEVID_DGPU_BASE_TGP }, + { &dgpu_tgp_attr_group, ASUS_WMI_DEVID_DGPU_SET_TGP }, + { &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 }, + + { &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 }, +}; + +static int asus_fw_attr_add(void) +{ + int err; + + err = fw_attributes_class_get(&fw_attr_class); + if (err) + goto fail_class_created; + + asus_armoury.fw_attr_dev = device_create(fw_attr_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_created; + } + + asus_armoury.fw_attr_kset = kset_create_and_add("attributes", NULL, + &asus_armoury.fw_attr_dev->kobj); + if (!asus_armoury.fw_attr_dev) { + err = -ENOMEM; + pr_debug("Failed to create and add attributes\n"); + goto err_destroy_classdev; + } + + err = sysfs_create_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr); + if (err) { + pr_warn("Failed to create sysfs level attributes\n"); + goto fail_class_created; + } + + err = 0; + 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; + err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, + &mini_led_mode_attr_group); + } else if (asus_wmi_is_present(ASUS_WMI_DEVID_MINI_LED_MODE2)) { + asus_armoury.mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE2; + err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, + &mini_led_mode_attr_group); + } + if (err) + pr_warn("Failed to create sysfs-group for mini_led\n"); + + err = 0; + 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; + err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group); + } else if (asus_wmi_is_present(ASUS_WMI_DEVID_GPU_MUX_VIVO)) { + asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX_VIVO; + err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group); + } + if (err) + pr_warn("Failed to create sysfs-group for gpu_mux\n"); + + for (int i = 0; i < ARRAY_SIZE(armoury_attr_groups); i++) { + if (!asus_wmi_is_present(armoury_attr_groups[i].wmi_devid)) + continue; + + err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, + armoury_attr_groups[i].attr_group); + if (err) + pr_warn("Failed to create sysfs-group for %s\n", + armoury_attr_groups[i].attr_group->name); + else + pr_debug("Created sysfs-group for %s\n", + armoury_attr_groups[i].attr_group->name); + } + + return 0; + +err_destroy_classdev: + device_destroy(fw_attr_class, MKDEV(0, 0)); + +fail_class_created: + fw_attributes_class_put(); + return err; +} + +/* Init / exit ****************************************************************/ + +/* Set up the min/max and defaults for ROG tunables */ +static void init_rog_tunables(struct rog_tunables *rog) +{ + const char *product; + u32 max_boost = NVIDIA_BOOST_MAX; + u32 cpu_default = PPT_CPU_LIMIT_DEFAULT; + u32 cpu_max = PPT_CPU_LIMIT_MAX; + u32 platform_default = PPT_PLATFORM_DEFAULT; + u32 platform_max = PPT_PLATFORM_MAX; + + /* + * ASUS product_name contains everything required, e.g, + * "ROG Flow X16 GV601VV_GV601VV_00185149B" + */ + product = dmi_get_system_info(DMI_PRODUCT_NAME); + + if (strstr(product, "GA402R")) { + cpu_default = 125; + } else if (strstr(product, "13QY")) { + cpu_max = 250; + } else if (strstr(product, "X13")) { + cpu_max = 75; + cpu_default = 50; + } else if (strstr(product, "RC71")) { + cpu_max = 50; + cpu_default = 30; + } else if (strstr(product, "G814") + || strstr(product, "G614") + || strstr(product, "G834") + || strstr(product, "G634")) { + cpu_max = 175; + } else if (strstr(product, "GA402X") + || strstr(product, "GA403") + || strstr(product, "FA507N") + || strstr(product, "FA507X") + || strstr(product, "FA707N") + || strstr(product, "FA707X")) { + cpu_max = 90; + } + + if (strstr(product, "GZ301ZE")) + max_boost = 5; + else if (strstr(product, "FX507ZC4")) + max_boost = 15; + else if (strstr(product, "GU605")) + max_boost = 20; + + /* ensure defaults for tunables */ + rog->cpu_default = cpu_default; + rog->cpu_min = PPT_CPU_LIMIT_MIN; + rog->cpu_max = cpu_max; + + rog->platform_default = platform_default; + rog->platform_max = PPT_PLATFORM_MIN; + rog->platform_max = platform_max; + + rog->ppt_pl1_spl = cpu_default; + rog->ppt_pl2_sppt = cpu_default; + rog->ppt_apu_sppt = cpu_default; + + rog->ppt_platform_sppt = platform_default; + rog->ppt_fppt = platform_default; + + rog->nv_boost_default = NVIDIA_BOOST_MAX; + rog->nv_boost_max = NVIDIA_BOOST_MIN; + rog->nv_boost_max = max_boost; + rog->nv_dynamic_boost = NVIDIA_BOOST_MIN; + + rog->nv_temp_default = NVIDIA_TEMP_MAX; + rog->nv_temp_max = NVIDIA_TEMP_MIN; + rog->nv_temp_max = NVIDIA_TEMP_MAX; + rog->nv_temp_target = NVIDIA_TEMP_MIN; + + rog->dgpu_tgp_default = NVIDIA_POWER_DEFAULT; + rog->dgpu_tgp_min = NVIDIA_POWER_MIN; + rog->dgpu_tgp_max = NVIDIA_POWER_MAX; + rog->dgpu_tgp = NVIDIA_POWER_MAX; + +} + +static int __init asus_fw_init(void) +{ + int err; + + fw_attrs.pending_reboot = false; + + asus_armoury.rog_tunables = kzalloc(sizeof(struct rog_tunables), GFP_KERNEL); + if (!asus_armoury.rog_tunables) { + return -ENOMEM; + } + init_rog_tunables(asus_armoury.rog_tunables); + init_max_cpu_cores(); + + err = asus_fw_attr_add(); + if (err) + return err; + + return 0; +} + +static void __exit asus_fw_exit(void) +{ + mutex_lock(&asus_armoury.mutex); + + sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr); + kset_unregister(asus_armoury.fw_attr_kset); + device_destroy(fw_attr_class, MKDEV(0, 0)); + fw_attributes_class_put(); + + mutex_unlock(&asus_armoury.mutex); +} + +module_init(asus_fw_init); +module_exit(asus_fw_exit); + +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 00000000000000..78c9278b2e7502 --- /dev/null +++ b/drivers/platform/x86/asus-armoury.h @@ -0,0 +1,258 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Definitions for kernel modules using asus-armoury driver + * + * Copyright (c) 2024 Luke Jones + */ + +#ifndef _ASUS_BIOSCFG_H_ +#define _ASUS_BIOSCFG_H_ + +#include + +#define DRIVER_NAME "asus-armoury" + +static ssize_t attr_int_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count, + u32 min, u32 max, u32 *store_value, u32 wmi_dev); + + +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "integer\n"); +} + +static ssize_t enum_type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "enumeration\n"); +} + +#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_int_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 */ + +#define __ATTR_CURRENT_INT_RO(_attr, _wmi) \ +WMI_SHOW_INT(_attr##_current_value, "%d\n", _wmi); \ +static struct kobj_attribute attr_##_attr##_current_value = \ + __ASUS_ATTR_RO(_attr, current_value) + +#define __ATTR_CURRENT_INT_RW(_attr, _minv, _maxv, _wmi) \ +__WMI_STORE_INT(_attr##_current_value, _minv, _maxv, _wmi); \ +WMI_SHOW_INT(_attr##_current_value, "%d\n", _wmi); \ +static struct kobj_attribute attr_##_attr##_current_value = \ + __ASUS_ATTR_RW(_attr, current_value) + +/* 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) + +/* Requires current_value_show */ +#define __ATTR_GROUP_INT_VALUE_ONLY(_attrname, _fsname, _dispname) \ +__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 \ +} + +/* 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_INT_VALUE_ONLY_RO(_attrname, _fsname, _wmi, _dispname) \ + __ATTR_CURRENT_INT_RO(_attrname, _wmi); \ + __ATTR_GROUP_INT_VALUE_ONLY(_attrname, _fsname, _dispname) + +#define ATTR_GROUP_BOOL_RO(_attrname, _fsname, _wmi, _dispname) \ + __ATTR_CURRENT_INT_RO(_attrname, _wmi); \ + __ATTR_GROUP_ENUM(_attrname, _fsname, "0;1", _dispname) + +#define ATTR_GROUP_BOOL_RW(_attrname, _fsname, _wmi, _dispname) \ + __ATTR_CURRENT_INT_RW(_attrname, 0, 1, _wmi); \ + __ATTR_GROUP_ENUM(_attrname, _fsname, "0;1", _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) + +#define ATTR_GROUP_ENUM_INT_RO(_attrname, _fsname, _wmi, \ + _possible, _dispname) \ + __ATTR_CURRENT_INT_RO(_attrname, _wmi); \ + __ATTR_GROUP_ENUM(_attrname, _fsname, _possible, _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 \ +} + +/* + * ROG PPT attributes need a little different in setup as they + * require rog_tunables members. + */ + +#define __ROG_TUNABLE_RW(_attr, _min, _max, _wmi) \ +static ssize_t _attr##_current_value_store(struct kobject *kobj, \ + struct kobj_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + return attr_int_store(kobj, attr, buf, count, \ + asus_armoury.rog_tunables->_min, \ + asus_armoury.rog_tunables->_max, \ + &asus_armoury.rog_tunables->_attr, _wmi); \ +} \ +static ssize_t _attr##_current_value_show(struct kobject *kobj, \ + struct kobj_attribute *attr, char *buf) \ +{ \ + return sysfs_emit(buf, "%u\n", asus_armoury.rog_tunables->_attr);\ +} \ +static struct kobj_attribute attr_##_attr##_current_value = \ + __ASUS_ATTR_RW(_attr, current_value) + +#define __ROG_TUNABLE_SHOW(_prop, _attrname, _val) \ +static ssize_t _attrname##_##_prop##_show(struct kobject *kobj, \ + struct kobj_attribute *attr, char *buf) \ +{ \ + return sysfs_emit(buf, "%d\n", asus_armoury.rog_tunables->_val);\ +} \ +static struct kobj_attribute attr_##_attrname##_##_prop = \ + __ASUS_ATTR_RO(_attrname, _prop) + +#define ATTR_GROUP_ROG_TUNABLE(_attrname, _fsname, _wmi, _default, \ + _min, _max, _incstep, _dispname) \ +__ROG_TUNABLE_SHOW(default_value, _attrname, _default); \ +__ROG_TUNABLE_RW(_attrname, _min, _max, _wmi); \ +__ROG_TUNABLE_SHOW(min_value, _attrname, _min); \ +__ROG_TUNABLE_SHOW(max_value, _attrname, _max); \ +__ATTR_SHOW_FMT(scalar_increment, _attrname, "%d\n", _incstep); \ +__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 \ +} + +#endif /* _ASUS_BIOSCFG_H_ */ diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index eef544d24e48e7..d7033d92fe13f0 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -11,8 +11,11 @@ * Copyright (C) 2005 Dmitry Torokhov */ +#include "linux/usb/ch9.h" #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include "linux/printk.h" + #include #include #include @@ -128,8 +131,6 @@ module_param(fnlock_default, bool, 0444); /* 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 static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL }; @@ -243,9 +244,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; @@ -306,6 +304,9 @@ struct asus_wmi { struct asus_wmi_driver *driver; }; +static bool ally_mcu_usb_switch; +static int ally_suspended_power_state; + /* WMI ************************************************************************/ static int asus_wmi_evaluate_method3(u32 method_id, @@ -520,12 +521,58 @@ 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 function to call. + * @retval: A pointer to where to store the value returned from WMI. + * + * The returned WMI function state can also be used to determine if the WMI + * function is supported by checking if the asus_wmi_get_devstate_dsts() + * returns an error. + * + * 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, or if it returns "unsupported" which is typically a 0 + * (no return, and no 'supported' bit set), or a 0xFFFFFFFE (~1) which if not + * caught here can result in unexpected behaviour later. + */ +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); + *retval &= ~ASUS_WMI_DSTS_PRESENCE_BIT; + + if (err) + return err; + /* Be explicit about retval */ + if (*retval == ASUS_WMI_UNSUPPORTED_METHOD) + return -ENODEV; + + return 0; +} +EXPORT_SYMBOL_GPL(asus_wmi_get_devstate_dsts); + +/** + * 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_GPL(asus_wmi_set_devstate); /* Helper for special devices with magic return codes */ static int asus_wmi_get_devstate_bits(struct asus_wmi *asus, @@ -4705,27 +4752,20 @@ 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) + 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_mode_available = true; + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE)) asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE; - } else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE2)) { - asus->mini_led_mode_available = true; + else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE2)) asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE2; - } - if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX)) { - asus->gpu_mux_mode_available = true; + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX)) 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_mode_available = true; + 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_mode_available = true; 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_mode_available = true; @@ -4908,34 +4948,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); @@ -4976,11 +4988,47 @@ static int asus_hotk_restore(struct device *device) return 0; } +static void asus_ally_s2idle_restore(void) +{ + int power_state; + + if (ally_mcu_usb_switch) { + int powersave = 0; + /* Call here so it is as early as possible */ + platform_suspend_screen_on(); + + power_state = power_supply_is_system_supplied(); + asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_MCU_POWERSAVE, &powersave); + /* These are the only states we need to do this for */ + if (powersave && (!power_state || ally_suspended_power_state != power_state)) + if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB8))) + pr_err("ROG Ally MCU failed to connect USB dev\n"); + } +} + +static int asus_hotk_prepare(struct device *device) +{ + if (ally_mcu_usb_switch) { + int powersave = 0; + platform_suspend_screen_off(); + + /* Time here greatly impacts the wake behaviour */ + asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_MCU_POWERSAVE, &powersave); + if (powersave) + msleep(3000); + } + return 0; +} + +static struct acpi_s2idle_dev_ops asus_ally_s2idle_dev_ops = { + .restore = asus_ally_s2idle_restore, + .wake_on_ac = true, +}; + 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, }; @@ -4990,7 +5038,7 @@ static int asus_wmi_probe(struct platform_device *pdev) { struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver); struct asus_wmi_driver *wdrv = to_asus_wmi_driver(pdrv); - int ret; + int ret, err; if (!wmi_has_guid(ASUS_WMI_MGMT_GUID)) { pr_warn("ASUS Management GUID not found\n"); @@ -5008,6 +5056,10 @@ static int asus_wmi_probe(struct platform_device *pdev) return ret; } + err = acpi_register_lps0_dev(&asus_ally_s2idle_dev_ops); + if (err) + pr_warn("failed to register LPS0 sleep handler in asus-wmi\n"); + return asus_wmi_add(pdev); } @@ -5040,6 +5092,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/acpi.h b/include/linux/acpi.h index 168201e4c78276..ae98e66782c4a3 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -1109,6 +1109,7 @@ struct acpi_s2idle_dev_ops { void (*prepare)(void); void (*check)(void); void (*restore)(void); + bool wake_on_ac; }; int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg); void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg); diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 4beb29907c2bd4..51080970843258 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -583,7 +583,6 @@ #define PCI_DEVICE_ID_AMD_1AH_M70H_DF_F3 0x12bb #define PCI_DEVICE_ID_AMD_MI200_DF_F3 0x14d3 #define PCI_DEVICE_ID_AMD_MI300_DF_F3 0x152b -#define PCI_DEVICE_ID_AMD_VANGOGH_USB 0x163a #define PCI_DEVICE_ID_AMD_CNB17H_F3 0x1703 #define PCI_DEVICE_ID_AMD_LANCE 0x2000 #define PCI_DEVICE_ID_AMD_LANCE_HOME 0x2001 diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index fba9751cda5bf6..269dbad427c17a 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -67,6 +67,7 @@ #define ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY 0x00120075 /* 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 @@ -127,6 +128,14 @@ /* 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_DGPU_BASE_TGP 0x00120099 +#define ASUS_WMI_DEVID_DGPU_SET_TGP 0x00120098 +#define ASUS_WMI_DEVID_APU_MEM 0x000600C1 + /* gpu mux switch, 0 = dGPU, 1 = Optimus */ #define ASUS_WMI_DEVID_GPU_MUX 0x00090016 #define ASUS_WMI_DEVID_GPU_MUX_VIVO 0x00090026 @@ -152,8 +161,18 @@ #define ASUS_WMI_DSTS_LIGHTBAR_MASK 0x0000000F #if IS_REACHABLE(CONFIG_ASUS_WMI) +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 int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval) +{ + return -ENODEV; +} +static 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) { diff --git a/include/linux/suspend.h b/include/linux/suspend.h index da6ebca3ff774c..4535ae82363c1f 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h @@ -133,6 +133,8 @@ struct platform_suspend_ops { struct platform_s2idle_ops { int (*begin)(void); + int (*screen_off)(void); + int (*screen_on)(void); int (*prepare)(void); int (*prepare_late)(void); void (*check)(void); @@ -160,6 +162,9 @@ extern unsigned int pm_suspend_global_flags; #define PM_SUSPEND_FLAG_FW_RESUME BIT(1) #define PM_SUSPEND_FLAG_NO_PLATFORM BIT(2) +int platform_suspend_screen_off(void); +int platform_suspend_screen_on(void); + static inline void pm_suspend_clear_flags(void) { pm_suspend_global_flags = 0; @@ -296,6 +301,9 @@ static inline bool idle_should_enter_s2idle(void) { return false; } static inline void __init pm_states_init(void) {} static inline void s2idle_set_ops(const struct platform_s2idle_ops *ops) {} static inline void s2idle_wake(void) {} +static inline int platform_suspend_screen_off(void) { return -ENODEV }; +static inline int platform_suspend_screen_on(void) { return -ENODEV }; + #endif /* !CONFIG_SUSPEND */ /* struct pbe is used for creating lists of pages that should be restored diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c index 09f8397bae15fb..19734b297527c2 100644 --- a/kernel/power/suspend.c +++ b/kernel/power/suspend.c @@ -254,6 +254,18 @@ static bool sleep_state_supported(suspend_state_t state) (valid_state(state) && !cxl_mem_active()); } +int platform_suspend_screen_off(void) +{ + return s2idle_ops && s2idle_ops->screen_off ? s2idle_ops->screen_off() : 0; +} +EXPORT_SYMBOL_GPL(platform_suspend_screen_off); + +int platform_suspend_screen_on(void) +{ + return s2idle_ops && s2idle_ops->screen_on ? s2idle_ops->screen_on() : 0; +} +EXPORT_SYMBOL_GPL(platform_suspend_screen_on); + static int platform_suspend_prepare(suspend_state_t state) { return state != PM_SUSPEND_TO_IDLE && suspend_ops->prepare ? diff --git a/sound/pci/hda/hda_controller.h b/sound/pci/hda/hda_controller.h index 8556031bcd68e4..f31cb31d463621 100644 --- a/sound/pci/hda/hda_controller.h +++ b/sound/pci/hda/hda_controller.h @@ -28,7 +28,7 @@ #else #define AZX_DCAPS_I915_COMPONENT 0 /* NOP */ #endif -/* 14 unused */ +#define AZX_DCAPS_AMD_ALLOC_FIX (1 << 14) /* AMD allocation workaround */ #define AZX_DCAPS_CTX_WORKAROUND (1 << 15) /* X-Fi workaround */ #define AZX_DCAPS_POSFIX_LPIB (1 << 16) /* Use LPIB as default */ #define AZX_DCAPS_AMD_WORKAROUND (1 << 17) /* AMD-specific workaround */ diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 1b550c42db0927..58fb1e0fc5947a 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -40,6 +40,7 @@ #ifdef CONFIG_X86 /* for snoop control */ +#include #include #include #endif @@ -301,7 +302,7 @@ enum { /* quirks for ATI HDMI with snoop off */ #define AZX_DCAPS_PRESET_ATI_HDMI_NS \ - (AZX_DCAPS_PRESET_ATI_HDMI | AZX_DCAPS_SNOOP_OFF) + (AZX_DCAPS_PRESET_ATI_HDMI | AZX_DCAPS_AMD_ALLOC_FIX) /* quirks for AMD SB */ #define AZX_DCAPS_PRESET_AMD_SB \ @@ -1709,6 +1710,13 @@ static void azx_check_snoop_available(struct azx *chip) if (chip->driver_caps & AZX_DCAPS_SNOOP_OFF) snoop = false; +#ifdef CONFIG_X86 + /* check the presence of DMA ops (i.e. IOMMU), disable snoop conditionally */ + if ((chip->driver_caps & AZX_DCAPS_AMD_ALLOC_FIX) && + !get_dma_ops(chip->card->dev)) + snoop = false; +#endif + chip->snoop = snoop; if (!snoop) { dev_info(chip->card->dev, "Force to non-snoop mode\n");