// SPDX-License-Identifier: GPL-2.0-or-later /* * HID driver for Lenovo Legion Go series gamepads. * * Copyright (c) 2026 Derek J. Clark * Copyright (c) 2026 Valve Corporation */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hid-ids.h" #define GO_GP_INTF_IN 0x83 #define GO_OUTPUT_REPORT_ID 0x05 #define GO_GP_RESET_SUCCESS 0x01 #define GO_PACKET_SIZE 64 static struct hid_go_cfg { struct delayed_work go_cfg_setup; struct completion send_cmd_complete; struct led_classdev *led_cdev; struct hid_device *hdev; struct mutex cfg_mutex; /*ensure single synchronous output report*/ u8 fps_mode; u8 gp_left_auto_sleep_time; u8 gp_left_gyro_cal_status; u8 gp_left_joy_cal_status; u8 gp_left_notify_en; u8 gp_left_rumble_mode; u8 gp_left_trigg_cal_status; u32 gp_left_version_firmware; u8 gp_left_version_gen; u32 gp_left_version_hardware; u32 gp_left_version_product; u32 gp_left_version_protocol; u8 gp_mode; u8 gp_right_auto_sleep_time; u8 gp_right_gyro_cal_status; u8 gp_right_joy_cal_status; u8 gp_right_notify_en; u8 gp_right_rumble_mode; u8 gp_right_trigg_cal_status; u32 gp_right_version_firmware; u8 gp_right_version_gen; u32 gp_right_version_hardware; u32 gp_right_version_product; u32 gp_right_version_protocol; u8 gp_rumble_intensity; u8 imu_left_bypass_en; u8 imu_left_sensor_en; u8 imu_right_bypass_en; u8 imu_right_sensor_en; u32 mcu_version_firmware; u8 mcu_version_gen; u32 mcu_version_hardware; u32 mcu_version_product; u32 mcu_version_protocol; u32 mouse_dpi; u8 os_mode; u8 rgb_effect; u8 rgb_en; u8 rgb_mode; u8 rgb_profile; u8 rgb_speed; u8 tp_en; u8 tp_vibration_en; u8 tp_vibration_intensity; u32 tx_dongle_version_firmware; u8 tx_dongle_version_gen; u32 tx_dongle_version_hardware; u32 tx_dongle_version_product; u32 tx_dongle_version_protocol; } drvdata; struct go_cfg_attr { u8 index; }; struct command_report { u8 report_id; u8 id; u8 cmd; u8 sub_cmd; u8 device_type; u8 data[59]; } __packed; enum command_id { MCU_CONFIG_DATA = 0x00, OS_MODE_DATA = 0x06, GAMEPAD_DATA = 0x3c, }; enum mcu_command_index { GET_VERSION_DATA = 0x02, GET_FEATURE_STATUS, SET_FEATURE_STATUS, GET_MOTOR_CFG, SET_MOTOR_CFG, GET_DPI_CFG, SET_DPI_CFG, SET_TRIGGER_CFG = 0x0a, SET_JOYSTICK_CFG = 0x0c, SET_GYRO_CFG = 0x0e, GET_RGB_CFG, SET_RGB_CFG, GET_DEVICE_STATUS = 0xa0, }; enum dev_type { UNSPECIFIED, USB_MCU, TX_DONGLE, LEFT_CONTROLLER, RIGHT_CONTROLLER, }; enum enabled_status_index { FEATURE_UNKNOWN, FEATURE_ENABLED, FEATURE_DISABLED, }; static const char *const enabled_status_text[] = { [FEATURE_UNKNOWN] = "unknown", [FEATURE_ENABLED] = "true", [FEATURE_DISABLED] = "false", }; enum version_data_index { PRODUCT_VERSION = 0x02, PROTOCOL_VERSION, FIRMWARE_VERSION, HARDWARE_VERSION, HARDWARE_GENERATION, }; enum feature_status_index { FEATURE_RESET_GAMEPAD = 0x02, FEATURE_IMU_BYPASS, FEATURE_IMU_ENABLE = 0x05, FEATURE_TOUCHPAD_ENABLE = 0x07, FEATURE_LIGHT_ENABLE, FEATURE_AUTO_SLEEP_TIME, FEATURE_FPS_SWITCH_STATUS = 0x0b, FEATURE_GAMEPAD_MODE = 0x0e, }; #define FEATURE_OS_MODE 0x69 enum fps_switch_status_index { FPS_STATUS_UNKNOWN, GAMEPAD, FPS, }; static const char *const fps_switch_text[] = { [FPS_STATUS_UNKNOWN] = "unknown", [GAMEPAD] = "gamepad", [FPS] = "fps", }; enum gamepad_mode_index { GAMEPAD_MODE_UNKNOWN, XINPUT, DINPUT, }; static const char *const gamepad_mode_text[] = { [GAMEPAD_MODE_UNKNOWN] = "unknown", [XINPUT] = "xinput", [DINPUT] = "dinput", }; enum motor_cfg_index { MOTOR_CFG_ALL = 0x01, MOTOR_INTENSITY, VIBRATION_NOTIFY_ENABLE, RUMBLE_MODE, TP_VIBRATION_ENABLE, TP_VIBRATION_INTENSITY, }; enum intensity_index { INTENSITY_UNKNOWN, INTENSITY_OFF, INTENSITY_LOW, INTENSITY_MEDIUM, INTENSITY_HIGH, }; static const char *const intensity_text[] = { [INTENSITY_UNKNOWN] = "unknown", [INTENSITY_OFF] = "off", [INTENSITY_LOW] = "low", [INTENSITY_MEDIUM] = "medium", [INTENSITY_HIGH] = "high", }; enum rumble_mode_index { RUMBLE_MODE_UNKNOWN, RUMBLE_MODE_FPS, RUMBLE_MODE_RACE, RUMBLE_MODE_AVERAGE, RUMBLE_MODE_SPG, RUMBLE_MODE_RPG, }; static const char *const rumble_mode_text[] = { [RUMBLE_MODE_UNKNOWN] = "unknown", [RUMBLE_MODE_FPS] = "fps", [RUMBLE_MODE_RACE] = "racing", [RUMBLE_MODE_AVERAGE] = "standard", [RUMBLE_MODE_SPG] = "spg", [RUMBLE_MODE_RPG] = "rpg", }; #define FPS_MODE_DPI 0x02 #define TRIGGER_CALIBRATE 0x04 #define JOYSTICK_CALIBRATE 0x04 #define GYRO_CALIBRATE 0x06 enum cal_device_type { CALDEV_GYROSCOPE = 0x01, CALDEV_JOYSTICK, CALDEV_TRIGGER, CALDEV_JOY_TRIGGER, }; enum cal_enable { CAL_UNKNOWN, CAL_START, CAL_STOP, }; static const char *const cal_enabled_text[] = { [CAL_UNKNOWN] = "unknown", [CAL_START] = "start", [CAL_STOP] = "stop", }; enum cal_status_index { CAL_STAT_UNKNOWN, CAL_STAT_SUCCESS, CAL_STAT_FAILURE, }; static const char *const cal_status_text[] = { [CAL_STAT_UNKNOWN] = "unknown", [CAL_STAT_SUCCESS] = "success", [CAL_STAT_FAILURE] = "failure", }; enum rgb_config_index { LIGHT_CFG_ALL = 0x01, LIGHT_MODE_SEL, LIGHT_PROFILE_SEL, USR_LIGHT_PROFILE_1, USR_LIGHT_PROFILE_2, USR_LIGHT_PROFILE_3, }; enum rgb_mode_index { RGB_MODE_UNKNOWN, RGB_MODE_DYNAMIC, RGB_MODE_CUSTOM, }; static const char *const rgb_mode_text[] = { [RGB_MODE_UNKNOWN] = "unknown", [RGB_MODE_DYNAMIC] = "dynamic", [RGB_MODE_CUSTOM] = "custom", }; enum rgb_effect_index { RGB_EFFECT_MONO, RGB_EFFECT_BREATHE, RGB_EFFECT_CHROMA, RGB_EFFECT_RAINBOW, }; static const char *const rgb_effect_text[] = { [RGB_EFFECT_MONO] = "monocolor", [RGB_EFFECT_BREATHE] = "breathe", [RGB_EFFECT_CHROMA] = "chroma", [RGB_EFFECT_RAINBOW] = "rainbow", }; enum device_status_index { GET_CAL_STATUS = 0x02, GET_UPGRADE_STATUS, GET_MACRO_REC_STATUS, GET_HOTKEY_TRIGG_STATUS, }; enum os_mode_cfg_index { SET_OS_MODE = 0x09, GET_OS_MODE, }; enum os_mode_type_index { OS_UNKNOWN, WINDOWS, LINUX, }; static const char *const os_mode_text[] = { [OS_UNKNOWN] = "unknown", [WINDOWS] = "windows", [LINUX] = "linux", }; static int hid_go_version_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { case PRODUCT_VERSION: switch (cmd_rep->device_type) { case USB_MCU: drvdata.mcu_version_product = get_unaligned_be32(cmd_rep->data); return 0; case TX_DONGLE: drvdata.tx_dongle_version_product = get_unaligned_be32(cmd_rep->data); return 0; case LEFT_CONTROLLER: drvdata.gp_left_version_product = get_unaligned_be32(cmd_rep->data); return 0; case RIGHT_CONTROLLER: drvdata.gp_right_version_product = get_unaligned_be32(cmd_rep->data); return 0; default: return -EINVAL; } case PROTOCOL_VERSION: switch (cmd_rep->device_type) { case USB_MCU: drvdata.mcu_version_protocol = get_unaligned_be32(cmd_rep->data); return 0; case TX_DONGLE: drvdata.tx_dongle_version_protocol = get_unaligned_be32(cmd_rep->data); return 0; case LEFT_CONTROLLER: drvdata.gp_left_version_protocol = get_unaligned_be32(cmd_rep->data); return 0; case RIGHT_CONTROLLER: drvdata.gp_right_version_protocol = get_unaligned_be32(cmd_rep->data); return 0; default: return -EINVAL; } case FIRMWARE_VERSION: switch (cmd_rep->device_type) { case USB_MCU: drvdata.mcu_version_firmware = get_unaligned_be32(cmd_rep->data); return 0; case TX_DONGLE: drvdata.tx_dongle_version_firmware = get_unaligned_be32(cmd_rep->data); return 0; case LEFT_CONTROLLER: drvdata.gp_left_version_firmware = get_unaligned_be32(cmd_rep->data); return 0; case RIGHT_CONTROLLER: drvdata.gp_right_version_firmware = get_unaligned_be32(cmd_rep->data); return 0; default: return -EINVAL; } case HARDWARE_VERSION: switch (cmd_rep->device_type) { case USB_MCU: drvdata.mcu_version_hardware = get_unaligned_be32(cmd_rep->data); return 0; case TX_DONGLE: drvdata.tx_dongle_version_hardware = get_unaligned_be32(cmd_rep->data); return 0; case LEFT_CONTROLLER: drvdata.gp_left_version_hardware = get_unaligned_be32(cmd_rep->data); return 0; case RIGHT_CONTROLLER: drvdata.gp_right_version_hardware = get_unaligned_be32(cmd_rep->data); return 0; default: return -EINVAL; } case HARDWARE_GENERATION: switch (cmd_rep->device_type) { case USB_MCU: drvdata.mcu_version_gen = cmd_rep->data[0]; return 0; case TX_DONGLE: drvdata.tx_dongle_version_gen = cmd_rep->data[0]; return 0; case LEFT_CONTROLLER: drvdata.gp_left_version_gen = cmd_rep->data[0]; return 0; case RIGHT_CONTROLLER: drvdata.gp_right_version_gen = cmd_rep->data[0]; return 0; default: return -EINVAL; } default: return -EINVAL; } } static int hid_go_feature_status_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { case FEATURE_RESET_GAMEPAD: return 0; case FEATURE_IMU_ENABLE: switch (cmd_rep->device_type) { case LEFT_CONTROLLER: drvdata.imu_left_sensor_en = cmd_rep->data[0]; return 0; case RIGHT_CONTROLLER: drvdata.imu_right_sensor_en = cmd_rep->data[0]; return 0; default: return -EINVAL; } case FEATURE_IMU_BYPASS: switch (cmd_rep->device_type) { case LEFT_CONTROLLER: drvdata.imu_left_bypass_en = cmd_rep->data[0]; return 0; case RIGHT_CONTROLLER: drvdata.imu_right_bypass_en = cmd_rep->data[0]; return 0; default: return -EINVAL; } break; case FEATURE_LIGHT_ENABLE: drvdata.rgb_en = cmd_rep->data[0]; return 0; case FEATURE_AUTO_SLEEP_TIME: switch (cmd_rep->device_type) { case LEFT_CONTROLLER: drvdata.gp_left_auto_sleep_time = cmd_rep->data[0]; return 0; case RIGHT_CONTROLLER: drvdata.gp_right_auto_sleep_time = cmd_rep->data[0]; return 0; default: return -EINVAL; } break; case FEATURE_TOUCHPAD_ENABLE: drvdata.tp_en = cmd_rep->data[0]; return 0; case FEATURE_GAMEPAD_MODE: drvdata.gp_mode = cmd_rep->data[0]; return 0; case FEATURE_FPS_SWITCH_STATUS: drvdata.fps_mode = cmd_rep->data[0]; return 0; default: return -EINVAL; } } static int hid_go_motor_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { case MOTOR_CFG_ALL: return -EINVAL; case MOTOR_INTENSITY: drvdata.gp_rumble_intensity = cmd_rep->data[0]; return 0; case VIBRATION_NOTIFY_ENABLE: switch (cmd_rep->device_type) { case LEFT_CONTROLLER: drvdata.gp_left_notify_en = cmd_rep->data[0]; return 0; case RIGHT_CONTROLLER: drvdata.gp_right_notify_en = cmd_rep->data[0]; return 0; default: return -EINVAL; } break; case RUMBLE_MODE: switch (cmd_rep->device_type) { case LEFT_CONTROLLER: drvdata.gp_left_rumble_mode = cmd_rep->data[0]; return 0; case RIGHT_CONTROLLER: drvdata.gp_right_rumble_mode = cmd_rep->data[0]; return 0; default: return -EINVAL; } case TP_VIBRATION_ENABLE: drvdata.tp_vibration_en = cmd_rep->data[0]; return 0; case TP_VIBRATION_INTENSITY: drvdata.tp_vibration_intensity = cmd_rep->data[0]; return 0; } return -EINVAL; } static int hid_go_fps_dpi_event(struct command_report *cmd_rep) { if (cmd_rep->sub_cmd != FPS_MODE_DPI) return -EINVAL; drvdata.mouse_dpi = get_unaligned_le32(cmd_rep->data); return 0; } static int hid_go_light_event(struct command_report *cmd_rep) { struct led_classdev_mc *mc_cdev; switch (cmd_rep->sub_cmd) { case LIGHT_MODE_SEL: drvdata.rgb_mode = cmd_rep->data[0]; return 0; case LIGHT_PROFILE_SEL: drvdata.rgb_profile = cmd_rep->data[0]; return 0; case USR_LIGHT_PROFILE_1: case USR_LIGHT_PROFILE_2: case USR_LIGHT_PROFILE_3: mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); drvdata.rgb_effect = cmd_rep->data[0]; mc_cdev->subled_info[0].intensity = cmd_rep->data[1]; mc_cdev->subled_info[1].intensity = cmd_rep->data[2]; mc_cdev->subled_info[2].intensity = cmd_rep->data[3]; drvdata.led_cdev->brightness = cmd_rep->data[4]; drvdata.rgb_speed = 100 - cmd_rep->data[5]; return 0; default: return -EINVAL; } } static int hid_go_device_status_event(struct command_report *cmd_rep) { switch (cmd_rep->device_type) { case LEFT_CONTROLLER: switch (cmd_rep->data[0]) { case CALDEV_GYROSCOPE: drvdata.gp_left_gyro_cal_status = cmd_rep->data[1]; return 0; case CALDEV_JOYSTICK: drvdata.gp_left_joy_cal_status = cmd_rep->data[1]; return 0; case CALDEV_TRIGGER: drvdata.gp_left_trigg_cal_status = cmd_rep->data[1]; return 0; default: return -EINVAL; } break; case RIGHT_CONTROLLER: switch (cmd_rep->data[0]) { case CALDEV_GYROSCOPE: drvdata.gp_right_gyro_cal_status = cmd_rep->data[1]; return 0; case CALDEV_JOYSTICK: drvdata.gp_right_joy_cal_status = cmd_rep->data[1]; return 0; case CALDEV_TRIGGER: drvdata.gp_right_trigg_cal_status = cmd_rep->data[1]; return 0; default: return -EINVAL; } break; default: return -EINVAL; } } static int hid_go_os_mode_cfg_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { case SET_OS_MODE: if (cmd_rep->data[0] != 1) return -EIO; return 0; case GET_OS_MODE: drvdata.os_mode = cmd_rep->data[0]; return 0; default: return -EINVAL; } } static int hid_go_set_event_return(struct command_report *cmd_rep) { if (cmd_rep->data[0] != 0) return -EIO; return 0; } static int get_endpoint_address(struct hid_device *hdev) { struct usb_interface *intf = to_usb_interface(hdev->dev.parent); struct usb_host_endpoint *ep; if (!intf) return -ENODEV; ep = intf->cur_altsetting->endpoint; if (!ep) return -ENODEV; return ep->desc.bEndpointAddress; } static int hid_go_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { struct command_report *cmd_rep; int ep, ret; if (size != GO_PACKET_SIZE) goto passthrough; ep = get_endpoint_address(hdev); if (ep != GO_GP_INTF_IN) goto passthrough; cmd_rep = (struct command_report *)data; switch (cmd_rep->id) { case MCU_CONFIG_DATA: switch (cmd_rep->cmd) { case GET_VERSION_DATA: ret = hid_go_version_event(cmd_rep); break; case GET_FEATURE_STATUS: ret = hid_go_feature_status_event(cmd_rep); break; case GET_MOTOR_CFG: ret = hid_go_motor_event(cmd_rep); break; case GET_DPI_CFG: ret = hid_go_fps_dpi_event(cmd_rep); break; case GET_RGB_CFG: ret = hid_go_light_event(cmd_rep); break; case GET_DEVICE_STATUS: ret = hid_go_device_status_event(cmd_rep); break; case SET_FEATURE_STATUS: case SET_MOTOR_CFG: case SET_DPI_CFG: case SET_RGB_CFG: case SET_TRIGGER_CFG: case SET_JOYSTICK_CFG: case SET_GYRO_CFG: ret = hid_go_set_event_return(cmd_rep); break; default: ret = -EINVAL; break; } break; case OS_MODE_DATA: ret = hid_go_os_mode_cfg_event(cmd_rep); break; default: goto passthrough; } dev_dbg(&hdev->dev, "Rx data as raw input report: [%*ph]\n", GO_PACKET_SIZE, data); complete(&drvdata.send_cmd_complete); return ret; passthrough: /* Forward other HID reports so they generate events */ hid_input_report(hdev, HID_INPUT_REPORT, data, size, 1); return 0; } static int mcu_property_out(struct hid_device *hdev, u8 id, u8 command, u8 index, enum dev_type device, u8 *data, size_t len) { unsigned char *dmabuf __free(kfree) = NULL; u8 header[] = { GO_OUTPUT_REPORT_ID, id, command, index, device }; size_t header_size = ARRAY_SIZE(header); int timeout = 50; int ret; if (header_size + len > GO_PACKET_SIZE) return -EINVAL; guard(mutex)(&drvdata.cfg_mutex); /* We can't use a devm_alloc reusable buffer without side effects during suspend */ dmabuf = kzalloc(GO_PACKET_SIZE, GFP_KERNEL); if (!dmabuf) return -ENOMEM; memcpy(dmabuf, header, header_size); memcpy(dmabuf + header_size, data, len); dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n", GO_PACKET_SIZE, dmabuf); ret = hid_hw_output_report(hdev, dmabuf, GO_PACKET_SIZE); if (ret < 0) return ret; ret = ret == GO_PACKET_SIZE ? 0 : -EINVAL; if (ret) return ret; ret = wait_for_completion_interruptible_timeout(&drvdata.send_cmd_complete, msecs_to_jiffies(timeout)); if (ret == 0) /* timeout occurred */ ret = -EBUSY; reinit_completion(&drvdata.send_cmd_complete); return 0; } static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf, enum version_data_index index, enum dev_type device_type) { ssize_t count = 0; int ret; ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, index, device_type, NULL, 0); if (ret) return ret; switch (index) { case PRODUCT_VERSION: switch (device_type) { case USB_MCU: count = sysfs_emit(buf, "%x\n", drvdata.mcu_version_product); break; case TX_DONGLE: count = sysfs_emit(buf, "%x\n", drvdata.tx_dongle_version_product); break; case LEFT_CONTROLLER: count = sysfs_emit(buf, "%x\n", drvdata.gp_left_version_product); break; case RIGHT_CONTROLLER: count = sysfs_emit(buf, "%x\n", drvdata.gp_right_version_product); break; default: return -EINVAL; } break; case PROTOCOL_VERSION: switch (device_type) { case USB_MCU: count = sysfs_emit(buf, "%x\n", drvdata.mcu_version_protocol); break; case TX_DONGLE: count = sysfs_emit(buf, "%x\n", drvdata.tx_dongle_version_protocol); break; case LEFT_CONTROLLER: count = sysfs_emit(buf, "%x\n", drvdata.gp_left_version_protocol); break; case RIGHT_CONTROLLER: count = sysfs_emit(buf, "%x\n", drvdata.gp_right_version_protocol); break; default: return -EINVAL; } break; case FIRMWARE_VERSION: switch (device_type) { case USB_MCU: count = sysfs_emit(buf, "%x\n", drvdata.mcu_version_firmware); break; case TX_DONGLE: count = sysfs_emit(buf, "%x\n", drvdata.tx_dongle_version_firmware); break; case LEFT_CONTROLLER: count = sysfs_emit(buf, "%x\n", drvdata.gp_left_version_firmware); break; case RIGHT_CONTROLLER: count = sysfs_emit(buf, "%x\n", drvdata.gp_right_version_firmware); break; default: return -EINVAL; } break; case HARDWARE_VERSION: switch (device_type) { case USB_MCU: count = sysfs_emit(buf, "%x\n", drvdata.mcu_version_hardware); break; case TX_DONGLE: count = sysfs_emit(buf, "%x\n", drvdata.tx_dongle_version_hardware); break; case LEFT_CONTROLLER: count = sysfs_emit(buf, "%x\n", drvdata.gp_left_version_hardware); break; case RIGHT_CONTROLLER: count = sysfs_emit(buf, "%x\n", drvdata.gp_right_version_hardware); break; default: return -EINVAL; } break; case HARDWARE_GENERATION: switch (device_type) { case USB_MCU: count = sysfs_emit(buf, "%x\n", drvdata.mcu_version_gen); break; case TX_DONGLE: count = sysfs_emit(buf, "%x\n", drvdata.tx_dongle_version_gen); break; case LEFT_CONTROLLER: count = sysfs_emit(buf, "%x\n", drvdata.gp_left_version_gen); break; case RIGHT_CONTROLLER: count = sysfs_emit(buf, "%x\n", drvdata.gp_right_version_gen); break; default: return -EINVAL; } break; } return count; } static ssize_t feature_status_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, enum feature_status_index index, enum dev_type device_type) { size_t size = 1; u8 val = 0; int ret; switch (index) { case FEATURE_IMU_ENABLE: case FEATURE_IMU_BYPASS: case FEATURE_LIGHT_ENABLE: case FEATURE_TOUCHPAD_ENABLE: ret = sysfs_match_string(enabled_status_text, buf); val = ret; break; case FEATURE_AUTO_SLEEP_TIME: ret = kstrtou8(buf, 10, &val); break; case FEATURE_RESET_GAMEPAD: ret = kstrtou8(buf, 10, &val); if (val != GO_GP_RESET_SUCCESS) return -EINVAL; break; case FEATURE_FPS_SWITCH_STATUS: ret = sysfs_match_string(fps_switch_text, buf); val = ret; break; case FEATURE_GAMEPAD_MODE: ret = sysfs_match_string(gamepad_mode_text, buf); val = ret; break; default: return -EINVAL; } if (ret < 0) return ret; if (!val) size = 0; ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, SET_FEATURE_STATUS, index, device_type, &val, size); if (ret < 0) return ret; return count; } static ssize_t feature_status_show(struct device *dev, struct device_attribute *attr, char *buf, enum feature_status_index index, enum dev_type device_type) { ssize_t count = 0; int ret; u8 i; ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_FEATURE_STATUS, index, device_type, NULL, 0); if (ret) return ret; switch (index) { case FEATURE_IMU_ENABLE: switch (device_type) { case LEFT_CONTROLLER: i = drvdata.imu_left_sensor_en; break; case RIGHT_CONTROLLER: i = drvdata.imu_right_sensor_en; break; default: return -EINVAL; } if (i >= ARRAY_SIZE(enabled_status_text)) return -EINVAL; count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); break; case FEATURE_IMU_BYPASS: switch (device_type) { case LEFT_CONTROLLER: i = drvdata.imu_left_bypass_en; break; case RIGHT_CONTROLLER: i = drvdata.imu_right_bypass_en; break; default: return -EINVAL; } if (i >= ARRAY_SIZE(enabled_status_text)) return -EINVAL; count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); break; case FEATURE_LIGHT_ENABLE: i = drvdata.rgb_en; if (i >= ARRAY_SIZE(enabled_status_text)) return -EINVAL; count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); break; case FEATURE_TOUCHPAD_ENABLE: i = drvdata.tp_en; if (i >= ARRAY_SIZE(enabled_status_text)) return -EINVAL; count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); break; case FEATURE_AUTO_SLEEP_TIME: switch (device_type) { case LEFT_CONTROLLER: i = drvdata.gp_left_auto_sleep_time; break; case RIGHT_CONTROLLER: i = drvdata.gp_right_auto_sleep_time; break; default: return -EINVAL; } count = sysfs_emit(buf, "%u\n", i); break; case FEATURE_FPS_SWITCH_STATUS: i = drvdata.fps_mode; if (i >= ARRAY_SIZE(fps_switch_text)) return -EINVAL; count = sysfs_emit(buf, "%s\n", fps_switch_text[i]); break; case FEATURE_GAMEPAD_MODE: i = drvdata.gp_mode; if (i >= ARRAY_SIZE(gamepad_mode_text)) return -EINVAL; count = sysfs_emit(buf, "%s\n", gamepad_mode_text[i]); break; default: return -EINVAL; } return count; } static ssize_t feature_status_options(struct device *dev, struct device_attribute *attr, char *buf, enum feature_status_index index) { ssize_t count = 0; unsigned int i; switch (index) { case FEATURE_IMU_ENABLE: case FEATURE_IMU_BYPASS: case FEATURE_LIGHT_ENABLE: case FEATURE_TOUCHPAD_ENABLE: for (i = 1; i < ARRAY_SIZE(enabled_status_text); i++) { count += sysfs_emit_at(buf, count, "%s ", enabled_status_text[i]); } break; case FEATURE_AUTO_SLEEP_TIME: return sysfs_emit(buf, "0-255\n"); case FEATURE_FPS_SWITCH_STATUS: for (i = 1; i < ARRAY_SIZE(fps_switch_text); i++) { count += sysfs_emit_at(buf, count, "%s ", fps_switch_text[i]); } break; case FEATURE_GAMEPAD_MODE: for (i = 1; i < ARRAY_SIZE(gamepad_mode_text); i++) { count += sysfs_emit_at(buf, count, "%s ", gamepad_mode_text[i]); } break; default: return -EINVAL; } if (count) buf[count - 1] = '\n'; return count; } static ssize_t motor_config_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, enum motor_cfg_index index, enum dev_type device_type) { size_t size = 1; u8 val = 0; int ret; switch (index) { case MOTOR_CFG_ALL: return -EINVAL; case MOTOR_INTENSITY: ret = sysfs_match_string(intensity_text, buf); val = ret; break; case VIBRATION_NOTIFY_ENABLE: ret = sysfs_match_string(enabled_status_text, buf); val = ret; break; case RUMBLE_MODE: ret = sysfs_match_string(rumble_mode_text, buf); val = ret; break; case TP_VIBRATION_ENABLE: ret = sysfs_match_string(enabled_status_text, buf); val = ret; break; case TP_VIBRATION_INTENSITY: ret = sysfs_match_string(intensity_text, buf); val = ret; break; } if (ret < 0) return ret; if (!val) size = 0; ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, SET_MOTOR_CFG, index, device_type, &val, size); if (ret < 0) return ret; return count; } static ssize_t motor_config_show(struct device *dev, struct device_attribute *attr, char *buf, enum motor_cfg_index index, enum dev_type device_type) { ssize_t count = 0; int ret; u8 i; ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_MOTOR_CFG, index, device_type, NULL, 0); if (ret) return ret; switch (index) { case MOTOR_CFG_ALL: return -EINVAL; case MOTOR_INTENSITY: i = drvdata.gp_rumble_intensity; if (i >= ARRAY_SIZE(intensity_text)) return -EINVAL; count = sysfs_emit(buf, "%s\n", intensity_text[i]); break; case VIBRATION_NOTIFY_ENABLE: switch (device_type) { case LEFT_CONTROLLER: i = drvdata.gp_left_notify_en; break; case RIGHT_CONTROLLER: i = drvdata.gp_right_notify_en; break; default: return -EINVAL; } if (i >= ARRAY_SIZE(enabled_status_text)) return -EINVAL; count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); break; case RUMBLE_MODE: switch (device_type) { case LEFT_CONTROLLER: i = drvdata.gp_left_rumble_mode; break; case RIGHT_CONTROLLER: i = drvdata.gp_right_rumble_mode; break; default: return -EINVAL; } if (i >= ARRAY_SIZE(rumble_mode_text)) return -EINVAL; count = sysfs_emit(buf, "%s\n", rumble_mode_text[i]); break; case TP_VIBRATION_ENABLE: i = drvdata.tp_vibration_en; if (i >= ARRAY_SIZE(enabled_status_text)) return -EINVAL; count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); break; case TP_VIBRATION_INTENSITY: i = drvdata.tp_vibration_intensity; if (i >= ARRAY_SIZE(intensity_text)) return -EINVAL; count = sysfs_emit(buf, "%s\n", intensity_text[i]); break; } return count; } static ssize_t motor_config_options(struct device *dev, struct device_attribute *attr, char *buf, enum motor_cfg_index index) { ssize_t count = 0; unsigned int i; switch (index) { case MOTOR_CFG_ALL: break; case RUMBLE_MODE: for (i = 1; i < ARRAY_SIZE(rumble_mode_text); i++) { count += sysfs_emit_at(buf, count, "%s ", rumble_mode_text[i]); } break; case MOTOR_INTENSITY: case TP_VIBRATION_INTENSITY: for (i = 1; i < ARRAY_SIZE(intensity_text); i++) { count += sysfs_emit_at(buf, count, "%s ", intensity_text[i]); } break; case VIBRATION_NOTIFY_ENABLE: case TP_VIBRATION_ENABLE: for (i = 1; i < ARRAY_SIZE(enabled_status_text); i++) { count += sysfs_emit_at(buf, count, "%s ", enabled_status_text[i]); } break; } if (count) buf[count - 1] = '\n'; return count; } static ssize_t fps_mode_dpi_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { size_t size = 4; u32 value; u8 val[4]; int ret; ret = kstrtou32(buf, 10, &value); if (ret) return ret; if (value != 500 && value != 800 && value != 1200 && value != 1800) return -EINVAL; put_unaligned_le32(value, val); ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, SET_DPI_CFG, FPS_MODE_DPI, UNSPECIFIED, val, size); if (ret < 0) return ret; return count; } static ssize_t fps_mode_dpi_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_DPI_CFG, FPS_MODE_DPI, UNSPECIFIED, NULL, 0); if (ret < 0) return ret; return sysfs_emit(buf, "%u\n", drvdata.mouse_dpi); } static ssize_t fps_mode_dpi_index_show(struct device *dev, struct device_attribute *attr, char *buf) { return sysfs_emit(buf, "500 800 1200 1800\n"); } static ssize_t device_status_show(struct device *dev, struct device_attribute *attr, char *buf, enum device_status_index index, enum dev_type device_type, enum cal_device_type cal_type) { u8 i; switch (index) { case GET_CAL_STATUS: switch (device_type) { case LEFT_CONTROLLER: switch (cal_type) { case CALDEV_GYROSCOPE: i = drvdata.gp_left_gyro_cal_status; break; case CALDEV_JOYSTICK: i = drvdata.gp_left_joy_cal_status; break; case CALDEV_TRIGGER: i = drvdata.gp_left_trigg_cal_status; break; default: return -EINVAL; } break; case RIGHT_CONTROLLER: switch (cal_type) { case CALDEV_GYROSCOPE: i = drvdata.gp_right_gyro_cal_status; break; case CALDEV_JOYSTICK: i = drvdata.gp_right_joy_cal_status; break; case CALDEV_TRIGGER: i = drvdata.gp_right_trigg_cal_status; break; default: return -EINVAL; } break; default: return -EINVAL; } break; default: return -EINVAL; } if (i >= ARRAY_SIZE(cal_status_text)) return -EINVAL; return sysfs_emit(buf, "%s\n", cal_status_text[i]); } static ssize_t calibrate_config_store(struct device *dev, struct device_attribute *attr, const char *buf, u8 cmd, u8 sub_cmd, size_t count, enum dev_type device_type) { size_t size = 1; u8 val = 0; int ret; ret = sysfs_match_string(cal_enabled_text, buf); if (ret < 0) return ret; val = ret; if (!val) size = 0; ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, cmd, sub_cmd, device_type, &val, size); if (ret < 0) return ret; return count; } static ssize_t calibrate_config_options(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t count = 0; unsigned int i; for (i = 1; i < ARRAY_SIZE(cal_enabled_text); i++) count += sysfs_emit_at(buf, count, "%s ", cal_enabled_text[i]); buf[count - 1] = '\n'; return count; } static ssize_t os_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { size_t size = 1; int ret; u8 val; ret = sysfs_match_string(os_mode_text, buf); if (ret <= 0) return ret; val = ret; ret = mcu_property_out(drvdata.hdev, OS_MODE_DATA, FEATURE_OS_MODE, SET_OS_MODE, USB_MCU, &val, size); if (ret < 0) return ret; drvdata.os_mode = val; return count; } static ssize_t os_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t count = 0; int ret; u8 i; ret = mcu_property_out(drvdata.hdev, OS_MODE_DATA, FEATURE_OS_MODE, GET_OS_MODE, USB_MCU, NULL, 0); if (ret) return ret; i = drvdata.os_mode; if (i >= ARRAY_SIZE(os_mode_text)) return -EINVAL; count = sysfs_emit(buf, "%s\n", os_mode_text[i]); return count; } static ssize_t os_mode_index_show(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t count = 0; unsigned int i; for (i = 1; i < ARRAY_SIZE(os_mode_text); i++) count += sysfs_emit_at(buf, count, "%s ", os_mode_text[i]); if (count) buf[count - 1] = '\n'; return count; } static int rgb_cfg_call(struct hid_device *hdev, enum mcu_command_index cmd, enum rgb_config_index index, u8 *val, size_t size) { if (cmd != SET_RGB_CFG && cmd != GET_RGB_CFG) return -EINVAL; if (index < LIGHT_CFG_ALL || index > USR_LIGHT_PROFILE_3) return -EINVAL; return mcu_property_out(hdev, MCU_CONFIG_DATA, cmd, index, UNSPECIFIED, val, size); } static int rgb_attr_show(void) { enum rgb_config_index index; index = drvdata.rgb_profile + 3; return rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, index, NULL, 0); } static ssize_t rgb_effect_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); enum rgb_config_index index; u8 effect; int ret; ret = sysfs_match_string(rgb_effect_text, buf); if (ret < 0) return ret; effect = ret; index = drvdata.rgb_profile + 3; u8 rgb_profile[6] = { effect, mc_cdev->subled_info[0].intensity, mc_cdev->subled_info[1].intensity, mc_cdev->subled_info[2].intensity, drvdata.led_cdev->brightness, drvdata.rgb_speed }; ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); if (ret) return ret; drvdata.rgb_effect = effect; return count; } static ssize_t rgb_effect_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; ret = rgb_attr_show(); if (ret) return ret; if (drvdata.rgb_effect >= ARRAY_SIZE(rgb_effect_text)) return -EINVAL; return sysfs_emit(buf, "%s\n", rgb_effect_text[drvdata.rgb_effect]); } static ssize_t rgb_effect_index_show(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t count = 0; unsigned int i; for (i = 0; i < ARRAY_SIZE(rgb_effect_text); i++) count += sysfs_emit_at(buf, count, "%s ", rgb_effect_text[i]); if (count) buf[count - 1] = '\n'; return count; } static ssize_t rgb_speed_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); enum rgb_config_index index; int val = 0; int ret; ret = kstrtoint(buf, 10, &val); if (ret) return ret; if (val < 0 || val > 100) return -EINVAL; /* This is a delay setting, invert logic for consistency with other drivers */ val = 100 - val; index = drvdata.rgb_profile + 3; u8 rgb_profile[6] = { drvdata.rgb_effect, mc_cdev->subled_info[0].intensity, mc_cdev->subled_info[1].intensity, mc_cdev->subled_info[2].intensity, drvdata.led_cdev->brightness, val }; ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); if (ret) return ret; drvdata.rgb_speed = val; return count; } static ssize_t rgb_speed_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret, val; ret = rgb_attr_show(); if (ret) return ret; if (drvdata.rgb_speed > 100) return -EINVAL; val = drvdata.rgb_speed; return sysfs_emit(buf, "%hhu\n", val); } static ssize_t rgb_speed_range_show(struct device *dev, struct device_attribute *attr, char *buf) { return sysfs_emit(buf, "0-100\n"); } static ssize_t rgb_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; u8 val; ret = sysfs_match_string(rgb_mode_text, buf); if (ret <= 0) return ret; val = ret; ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, LIGHT_MODE_SEL, &val, 1); if (ret) return ret; drvdata.rgb_mode = val; return count; } static ssize_t rgb_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; ret = rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_MODE_SEL, NULL, 0); if (ret) return ret; if (drvdata.rgb_mode >= ARRAY_SIZE(rgb_mode_text)) return -EINVAL; return sysfs_emit(buf, "%s\n", rgb_mode_text[drvdata.rgb_mode]); } static ssize_t rgb_mode_index_show(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t count = 0; unsigned int i; for (i = 1; i < ARRAY_SIZE(rgb_mode_text); i++) count += sysfs_emit_at(buf, count, "%s ", rgb_mode_text[i]); if (count) buf[count - 1] = '\n'; return count; } static ssize_t rgb_profile_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { size_t size = 1; int ret; u8 val; ret = kstrtou8(buf, 10, &val); if (ret < 0) return ret; if (val < 1 || val > 3) return -EINVAL; ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, LIGHT_PROFILE_SEL, &val, size); if (ret) return ret; drvdata.rgb_profile = val; return count; } static ssize_t rgb_profile_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; ret = rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_PROFILE_SEL, NULL, 0); if (ret) return ret; if (drvdata.rgb_profile < 1 || drvdata.rgb_profile > 3) return -EINVAL; return sysfs_emit(buf, "%hhu\n", drvdata.rgb_profile); } static ssize_t rgb_profile_range_show(struct device *dev, struct device_attribute *attr, char *buf) { return sysfs_emit(buf, "1-3\n"); } static void hid_go_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); enum rgb_config_index index; int ret; if (brightness > led_cdev->max_brightness) { dev_err(led_cdev->dev, "Invalid argument\n"); return; } index = drvdata.rgb_profile + 3; u8 rgb_profile[6] = { drvdata.rgb_effect, mc_cdev->subled_info[0].intensity, mc_cdev->subled_info[1].intensity, mc_cdev->subled_info[2].intensity, brightness, drvdata.rgb_speed }; ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); switch (ret) { case 0: led_cdev->brightness = brightness; break; case -ENODEV: /* during switch to IAP -ENODEV is expected */ case -ENOSYS: /* during rmmod -ENOSYS is expected */ dev_dbg(led_cdev->dev, "Failed to write RGB profile: %i\n", ret); break; default: dev_err(led_cdev->dev, "Failed to write RGB profile: %i\n", ret); } } #define LEGO_DEVICE_ATTR_RW(_name, _attrname, _dtype, _rtype, _group) \ static ssize_t _name##_store(struct device *dev, \ struct device_attribute *attr, \ const char *buf, size_t count) \ { \ return _group##_store(dev, attr, buf, count, _name.index, \ _dtype); \ } \ static ssize_t _name##_show(struct device *dev, \ struct device_attribute *attr, char *buf) \ { \ return _group##_show(dev, attr, buf, _name.index, _dtype); \ } \ static ssize_t _name##_##_rtype##_show( \ struct device *dev, struct device_attribute *attr, char *buf) \ { \ return _group##_options(dev, attr, buf, _name.index); \ } \ static DEVICE_ATTR_RW_NAMED(_name, _attrname) #define LEGO_DEVICE_ATTR_WO(_name, _attrname, _dtype, _group) \ static ssize_t _name##_store(struct device *dev, \ struct device_attribute *attr, \ const char *buf, size_t count) \ { \ return _group##_store(dev, attr, buf, count, _name.index, \ _dtype); \ } \ static DEVICE_ATTR_WO_NAMED(_name, _attrname) #define LEGO_DEVICE_ATTR_RO(_name, _attrname, _dtype, _group) \ static ssize_t _name##_show(struct device *dev, \ struct device_attribute *attr, char *buf) \ { \ return _group##_show(dev, attr, buf, _name.index, _dtype); \ } \ static DEVICE_ATTR_RO_NAMED(_name, _attrname) #define LEGO_CAL_DEVICE_ATTR(_name, _attrname, _scmd, _dtype, _rtype) \ static ssize_t _name##_store(struct device *dev, \ struct device_attribute *attr, \ const char *buf, size_t count) \ { \ return calibrate_config_store(dev, attr, buf, _name.index, \ _scmd, count, _dtype); \ } \ static ssize_t _name##_##_rtype##_show( \ struct device *dev, struct device_attribute *attr, char *buf) \ { \ return calibrate_config_options(dev, attr, buf); \ } \ static DEVICE_ATTR_WO_NAMED(_name, _attrname) #define LEGO_DEVICE_STATUS_ATTR(_name, _attrname, _scmd, _dtype) \ static ssize_t _name##_show(struct device *dev, \ struct device_attribute *attr, char *buf) \ { \ return device_status_show(dev, attr, buf, _name.index, _scmd, \ _dtype); \ } \ static DEVICE_ATTR_RO_NAMED(_name, _attrname) /* Gamepad - MCU */ static struct go_cfg_attr version_product_mcu = { PRODUCT_VERSION }; LEGO_DEVICE_ATTR_RO(version_product_mcu, "product_version", USB_MCU, version); static struct go_cfg_attr version_protocol_mcu = { PROTOCOL_VERSION }; LEGO_DEVICE_ATTR_RO(version_protocol_mcu, "protocol_version", USB_MCU, version); static struct go_cfg_attr version_firmware_mcu = { FIRMWARE_VERSION }; LEGO_DEVICE_ATTR_RO(version_firmware_mcu, "firmware_version", USB_MCU, version); static struct go_cfg_attr version_hardware_mcu = { HARDWARE_VERSION }; LEGO_DEVICE_ATTR_RO(version_hardware_mcu, "hardware_version", USB_MCU, version); static struct go_cfg_attr version_gen_mcu = { HARDWARE_GENERATION }; LEGO_DEVICE_ATTR_RO(version_gen_mcu, "hardware_generation", USB_MCU, version); static struct go_cfg_attr fps_switch_status = { FEATURE_FPS_SWITCH_STATUS }; LEGO_DEVICE_ATTR_RO(fps_switch_status, "fps_switch_status", UNSPECIFIED, feature_status); static struct go_cfg_attr gamepad_mode = { FEATURE_GAMEPAD_MODE }; LEGO_DEVICE_ATTR_RW(gamepad_mode, "mode", UNSPECIFIED, index, feature_status); static DEVICE_ATTR_RO_NAMED(gamepad_mode_index, "mode_index"); static struct go_cfg_attr reset_mcu = { FEATURE_RESET_GAMEPAD }; LEGO_DEVICE_ATTR_WO(reset_mcu, "reset_mcu", USB_MCU, feature_status); static struct go_cfg_attr gamepad_rumble_intensity = { MOTOR_INTENSITY }; LEGO_DEVICE_ATTR_RW(gamepad_rumble_intensity, "rumble_intensity", UNSPECIFIED, index, motor_config); static DEVICE_ATTR_RO_NAMED(gamepad_rumble_intensity_index, "rumble_intensity_index"); static DEVICE_ATTR_RW(fps_mode_dpi); static DEVICE_ATTR_RO(fps_mode_dpi_index); static DEVICE_ATTR_RW(os_mode); static DEVICE_ATTR_RO(os_mode_index); static struct attribute *mcu_attrs[] = { &dev_attr_fps_mode_dpi.attr, &dev_attr_fps_mode_dpi_index.attr, &dev_attr_fps_switch_status.attr, &dev_attr_gamepad_mode.attr, &dev_attr_gamepad_mode_index.attr, &dev_attr_gamepad_rumble_intensity.attr, &dev_attr_gamepad_rumble_intensity_index.attr, &dev_attr_os_mode.attr, &dev_attr_os_mode_index.attr, &dev_attr_reset_mcu.attr, &dev_attr_version_firmware_mcu.attr, &dev_attr_version_gen_mcu.attr, &dev_attr_version_hardware_mcu.attr, &dev_attr_version_product_mcu.attr, &dev_attr_version_protocol_mcu.attr, NULL, }; static const struct attribute_group mcu_attr_group = { .attrs = mcu_attrs, }; /* Gamepad - TX Dongle */ static struct go_cfg_attr version_product_tx_dongle = { PRODUCT_VERSION }; LEGO_DEVICE_ATTR_RO(version_product_tx_dongle, "product_version", TX_DONGLE, version); static struct go_cfg_attr version_protocol_tx_dongle = { PROTOCOL_VERSION }; LEGO_DEVICE_ATTR_RO(version_protocol_tx_dongle, "protocol_version", TX_DONGLE, version); static struct go_cfg_attr version_firmware_tx_dongle = { FIRMWARE_VERSION }; LEGO_DEVICE_ATTR_RO(version_firmware_tx_dongle, "firmware_version", TX_DONGLE, version); static struct go_cfg_attr version_hardware_tx_dongle = { HARDWARE_VERSION }; LEGO_DEVICE_ATTR_RO(version_hardware_tx_dongle, "hardware_version", TX_DONGLE, version); static struct go_cfg_attr version_gen_tx_dongle = { HARDWARE_GENERATION }; LEGO_DEVICE_ATTR_RO(version_gen_tx_dongle, "hardware_generation", TX_DONGLE, version); static struct go_cfg_attr reset_tx_dongle = { FEATURE_RESET_GAMEPAD }; LEGO_DEVICE_ATTR_RO(reset_tx_dongle, "reset", TX_DONGLE, feature_status); static struct attribute *tx_dongle_attrs[] = { &dev_attr_reset_tx_dongle.attr, &dev_attr_version_hardware_tx_dongle.attr, &dev_attr_version_firmware_tx_dongle.attr, &dev_attr_version_gen_tx_dongle.attr, &dev_attr_version_product_tx_dongle.attr, &dev_attr_version_protocol_tx_dongle.attr, NULL, }; static const struct attribute_group tx_dongle_attr_group = { .name = "tx_dongle", .attrs = tx_dongle_attrs, }; /* Gamepad - Left */ static struct go_cfg_attr version_product_left = { PRODUCT_VERSION }; LEGO_DEVICE_ATTR_RO(version_product_left, "product_version", LEFT_CONTROLLER, version); static struct go_cfg_attr version_protocol_left = { PROTOCOL_VERSION }; LEGO_DEVICE_ATTR_RO(version_protocol_left, "protocol_version", LEFT_CONTROLLER, version); static struct go_cfg_attr version_firmware_left = { FIRMWARE_VERSION }; LEGO_DEVICE_ATTR_RO(version_firmware_left, "firmware_version", LEFT_CONTROLLER, version); static struct go_cfg_attr version_hardware_left = { HARDWARE_VERSION }; LEGO_DEVICE_ATTR_RO(version_hardware_left, "hardware_version", LEFT_CONTROLLER, version); static struct go_cfg_attr version_gen_left = { HARDWARE_GENERATION }; LEGO_DEVICE_ATTR_RO(version_gen_left, "hardware_generation", LEFT_CONTROLLER, version); static struct go_cfg_attr auto_sleep_time_left = { FEATURE_AUTO_SLEEP_TIME }; LEGO_DEVICE_ATTR_RW(auto_sleep_time_left, "auto_sleep_time", LEFT_CONTROLLER, range, feature_status); static DEVICE_ATTR_RO_NAMED(auto_sleep_time_left_range, "auto_sleep_time_range"); static struct go_cfg_attr imu_bypass_left = { FEATURE_IMU_BYPASS }; LEGO_DEVICE_ATTR_RW(imu_bypass_left, "imu_bypass_enabled", LEFT_CONTROLLER, index, feature_status); static DEVICE_ATTR_RO_NAMED(imu_bypass_left_index, "imu_bypass_enabled_index"); static struct go_cfg_attr imu_enabled_left = { FEATURE_IMU_ENABLE }; LEGO_DEVICE_ATTR_RW(imu_enabled_left, "imu_enabled", LEFT_CONTROLLER, index, feature_status); static DEVICE_ATTR_RO_NAMED(imu_enabled_left_index, "imu_enabled_index"); static struct go_cfg_attr reset_left = { FEATURE_RESET_GAMEPAD }; LEGO_DEVICE_ATTR_WO(reset_left, "reset", LEFT_CONTROLLER, feature_status); static struct go_cfg_attr rumble_mode_left = { RUMBLE_MODE }; LEGO_DEVICE_ATTR_RW(rumble_mode_left, "rumble_mode", LEFT_CONTROLLER, index, motor_config); static DEVICE_ATTR_RO_NAMED(rumble_mode_left_index, "rumble_mode_index"); static struct go_cfg_attr rumble_notification_left = { VIBRATION_NOTIFY_ENABLE }; LEGO_DEVICE_ATTR_RW(rumble_notification_left, "rumble_notification", LEFT_CONTROLLER, index, motor_config); static DEVICE_ATTR_RO_NAMED(rumble_notification_left_index, "rumble_notification_index"); static struct go_cfg_attr cal_trigg_left = { TRIGGER_CALIBRATE }; LEGO_CAL_DEVICE_ATTR(cal_trigg_left, "calibrate_trigger", SET_TRIGGER_CFG, LEFT_CONTROLLER, index); static DEVICE_ATTR_RO_NAMED(cal_trigg_left_index, "calibrate_trigger_index"); static struct go_cfg_attr cal_joy_left = { JOYSTICK_CALIBRATE }; LEGO_CAL_DEVICE_ATTR(cal_joy_left, "calibrate_joystick", SET_JOYSTICK_CFG, LEFT_CONTROLLER, index); static DEVICE_ATTR_RO_NAMED(cal_joy_left_index, "calibrate_joystick_index"); static struct go_cfg_attr cal_gyro_left = { GYRO_CALIBRATE }; LEGO_CAL_DEVICE_ATTR(cal_gyro_left, "calibrate_gyro", SET_GYRO_CFG, LEFT_CONTROLLER, index); static DEVICE_ATTR_RO_NAMED(cal_gyro_left_index, "calibrate_gyro_index"); static struct go_cfg_attr cal_trigg_left_status = { GET_CAL_STATUS }; LEGO_DEVICE_STATUS_ATTR(cal_trigg_left_status, "calibrate_trigger_status", LEFT_CONTROLLER, CALDEV_TRIGGER); static struct go_cfg_attr cal_joy_left_status = { GET_CAL_STATUS }; LEGO_DEVICE_STATUS_ATTR(cal_joy_left_status, "calibrate_joystick_status", LEFT_CONTROLLER, CALDEV_JOYSTICK); static struct go_cfg_attr cal_gyro_left_status = { GET_CAL_STATUS }; LEGO_DEVICE_STATUS_ATTR(cal_gyro_left_status, "calibrate_gyro_status", LEFT_CONTROLLER, CALDEV_GYROSCOPE); static struct attribute *left_gamepad_attrs[] = { &dev_attr_auto_sleep_time_left.attr, &dev_attr_auto_sleep_time_left_range.attr, &dev_attr_cal_gyro_left.attr, &dev_attr_cal_gyro_left_index.attr, &dev_attr_cal_gyro_left_status.attr, &dev_attr_cal_joy_left.attr, &dev_attr_cal_joy_left_index.attr, &dev_attr_cal_joy_left_status.attr, &dev_attr_cal_trigg_left.attr, &dev_attr_cal_trigg_left_index.attr, &dev_attr_cal_trigg_left_status.attr, &dev_attr_imu_bypass_left.attr, &dev_attr_imu_bypass_left_index.attr, &dev_attr_imu_enabled_left.attr, &dev_attr_imu_enabled_left_index.attr, &dev_attr_reset_left.attr, &dev_attr_rumble_mode_left.attr, &dev_attr_rumble_mode_left_index.attr, &dev_attr_rumble_notification_left.attr, &dev_attr_rumble_notification_left_index.attr, &dev_attr_version_hardware_left.attr, &dev_attr_version_firmware_left.attr, &dev_attr_version_gen_left.attr, &dev_attr_version_product_left.attr, &dev_attr_version_protocol_left.attr, NULL, }; static const struct attribute_group left_gamepad_attr_group = { .name = "left_handle", .attrs = left_gamepad_attrs, }; /* Gamepad - Right */ static struct go_cfg_attr version_product_right = { PRODUCT_VERSION }; LEGO_DEVICE_ATTR_RO(version_product_right, "product_version", RIGHT_CONTROLLER, version); static struct go_cfg_attr version_protocol_right = { PROTOCOL_VERSION }; LEGO_DEVICE_ATTR_RO(version_protocol_right, "protocol_version", RIGHT_CONTROLLER, version); static struct go_cfg_attr version_firmware_right = { FIRMWARE_VERSION }; LEGO_DEVICE_ATTR_RO(version_firmware_right, "firmware_version", RIGHT_CONTROLLER, version); static struct go_cfg_attr version_hardware_right = { HARDWARE_VERSION }; LEGO_DEVICE_ATTR_RO(version_hardware_right, "hardware_version", RIGHT_CONTROLLER, version); static struct go_cfg_attr version_gen_right = { HARDWARE_GENERATION }; LEGO_DEVICE_ATTR_RO(version_gen_right, "hardware_generation", RIGHT_CONTROLLER, version); static struct go_cfg_attr auto_sleep_time_right = { FEATURE_AUTO_SLEEP_TIME }; LEGO_DEVICE_ATTR_RW(auto_sleep_time_right, "auto_sleep_time", RIGHT_CONTROLLER, range, feature_status); static DEVICE_ATTR_RO_NAMED(auto_sleep_time_right_range, "auto_sleep_time_range"); static struct go_cfg_attr imu_bypass_right = { FEATURE_IMU_BYPASS }; LEGO_DEVICE_ATTR_RW(imu_bypass_right, "imu_bypass_enabled", RIGHT_CONTROLLER, index, feature_status); static DEVICE_ATTR_RO_NAMED(imu_bypass_right_index, "imu_bypass_enabled_index"); static struct go_cfg_attr imu_enabled_right = { FEATURE_IMU_BYPASS }; LEGO_DEVICE_ATTR_RW(imu_enabled_right, "imu_enabled", RIGHT_CONTROLLER, index, feature_status); static DEVICE_ATTR_RO_NAMED(imu_enabled_right_index, "imu_enabled_index"); static struct go_cfg_attr reset_right = { FEATURE_RESET_GAMEPAD }; LEGO_DEVICE_ATTR_WO(reset_right, "reset", LEFT_CONTROLLER, feature_status); static struct go_cfg_attr rumble_mode_right = { RUMBLE_MODE }; LEGO_DEVICE_ATTR_RW(rumble_mode_right, "rumble_mode", RIGHT_CONTROLLER, index, motor_config); static DEVICE_ATTR_RO_NAMED(rumble_mode_right_index, "rumble_mode_index"); static struct go_cfg_attr rumble_notification_right = { VIBRATION_NOTIFY_ENABLE }; LEGO_DEVICE_ATTR_RW(rumble_notification_right, "rumble_notification", RIGHT_CONTROLLER, index, motor_config); static DEVICE_ATTR_RO_NAMED(rumble_notification_right_index, "rumble_notification_index"); static struct go_cfg_attr cal_trigg_right = { TRIGGER_CALIBRATE }; LEGO_CAL_DEVICE_ATTR(cal_trigg_right, "calibrate_trigger", SET_TRIGGER_CFG, RIGHT_CONTROLLER, index); static DEVICE_ATTR_RO_NAMED(cal_trigg_right_index, "calibrate_trigger_index"); static struct go_cfg_attr cal_joy_right = { JOYSTICK_CALIBRATE }; LEGO_CAL_DEVICE_ATTR(cal_joy_right, "calibrate_joystick", SET_JOYSTICK_CFG, RIGHT_CONTROLLER, index); static DEVICE_ATTR_RO_NAMED(cal_joy_right_index, "calibrate_joystick_index"); static struct go_cfg_attr cal_gyro_right = { GYRO_CALIBRATE }; LEGO_CAL_DEVICE_ATTR(cal_gyro_right, "calibrate_gyro", SET_GYRO_CFG, RIGHT_CONTROLLER, index); static DEVICE_ATTR_RO_NAMED(cal_gyro_right_index, "calibrate_gyro_index"); static struct go_cfg_attr cal_trigg_right_status = { GET_CAL_STATUS }; LEGO_DEVICE_STATUS_ATTR(cal_trigg_right_status, "calibrate_trigger_status", RIGHT_CONTROLLER, CALDEV_TRIGGER); static struct go_cfg_attr cal_joy_right_status = { GET_CAL_STATUS }; LEGO_DEVICE_STATUS_ATTR(cal_joy_right_status, "calibrate_joystick_status", RIGHT_CONTROLLER, CALDEV_JOYSTICK); static struct go_cfg_attr cal_gyro_right_status = { GET_CAL_STATUS }; LEGO_DEVICE_STATUS_ATTR(cal_gyro_right_status, "calibrate_gyro_status", RIGHT_CONTROLLER, CALDEV_GYROSCOPE); static struct attribute *right_gamepad_attrs[] = { &dev_attr_auto_sleep_time_right.attr, &dev_attr_auto_sleep_time_right_range.attr, &dev_attr_cal_gyro_right.attr, &dev_attr_cal_gyro_right_index.attr, &dev_attr_cal_gyro_right_status.attr, &dev_attr_cal_joy_right.attr, &dev_attr_cal_joy_right_index.attr, &dev_attr_cal_joy_right_status.attr, &dev_attr_cal_trigg_right.attr, &dev_attr_cal_trigg_right_index.attr, &dev_attr_cal_trigg_right_status.attr, &dev_attr_imu_bypass_right.attr, &dev_attr_imu_bypass_right_index.attr, &dev_attr_imu_enabled_right.attr, &dev_attr_imu_enabled_right_index.attr, &dev_attr_reset_right.attr, &dev_attr_rumble_mode_right.attr, &dev_attr_rumble_mode_right_index.attr, &dev_attr_rumble_notification_right.attr, &dev_attr_rumble_notification_right_index.attr, &dev_attr_version_hardware_right.attr, &dev_attr_version_firmware_right.attr, &dev_attr_version_gen_right.attr, &dev_attr_version_product_right.attr, &dev_attr_version_protocol_right.attr, NULL, }; static const struct attribute_group right_gamepad_attr_group = { .name = "right_handle", .attrs = right_gamepad_attrs, }; /* Touchpad */ static struct go_cfg_attr touchpad_enabled = { FEATURE_TOUCHPAD_ENABLE }; LEGO_DEVICE_ATTR_RW(touchpad_enabled, "enabled", UNSPECIFIED, index, feature_status); static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); static struct go_cfg_attr touchpad_vibration_enabled = { TP_VIBRATION_ENABLE }; LEGO_DEVICE_ATTR_RW(touchpad_vibration_enabled, "vibration_enabled", UNSPECIFIED, index, motor_config); static DEVICE_ATTR_RO_NAMED(touchpad_vibration_enabled_index, "vibration_enabled_index"); static struct go_cfg_attr touchpad_vibration_intensity = { TP_VIBRATION_INTENSITY }; LEGO_DEVICE_ATTR_RW(touchpad_vibration_intensity, "vibration_intensity", UNSPECIFIED, index, motor_config); static DEVICE_ATTR_RO_NAMED(touchpad_vibration_intensity_index, "vibration_intensity_index"); static struct attribute *touchpad_attrs[] = { &dev_attr_touchpad_enabled.attr, &dev_attr_touchpad_enabled_index.attr, &dev_attr_touchpad_vibration_enabled.attr, &dev_attr_touchpad_vibration_enabled_index.attr, &dev_attr_touchpad_vibration_intensity.attr, &dev_attr_touchpad_vibration_intensity_index.attr, NULL, }; static const struct attribute_group touchpad_attr_group = { .name = "touchpad", .attrs = touchpad_attrs, }; static const struct attribute_group *top_level_attr_groups[] = { &mcu_attr_group, &tx_dongle_attr_group, &left_gamepad_attr_group, &right_gamepad_attr_group, &touchpad_attr_group, NULL, }; /* RGB */ static struct go_cfg_attr rgb_enabled = { FEATURE_LIGHT_ENABLE }; LEGO_DEVICE_ATTR_RW(rgb_enabled, "enabled", UNSPECIFIED, index, feature_status); static DEVICE_ATTR_RO_NAMED(rgb_effect_index, "effect_index"); static DEVICE_ATTR_RO_NAMED(rgb_enabled_index, "enabled_index"); static DEVICE_ATTR_RO_NAMED(rgb_mode_index, "mode_index"); static DEVICE_ATTR_RO_NAMED(rgb_profile_range, "profile_range"); static DEVICE_ATTR_RO_NAMED(rgb_speed_range, "speed_range"); static DEVICE_ATTR_RW_NAMED(rgb_effect, "effect"); static DEVICE_ATTR_RW_NAMED(rgb_mode, "mode"); static DEVICE_ATTR_RW_NAMED(rgb_profile, "profile"); static DEVICE_ATTR_RW_NAMED(rgb_speed, "speed"); static struct attribute *go_rgb_attrs[] = { &dev_attr_rgb_effect.attr, &dev_attr_rgb_effect_index.attr, &dev_attr_rgb_enabled.attr, &dev_attr_rgb_enabled_index.attr, &dev_attr_rgb_mode.attr, &dev_attr_rgb_mode_index.attr, &dev_attr_rgb_profile.attr, &dev_attr_rgb_profile_range.attr, &dev_attr_rgb_speed.attr, &dev_attr_rgb_speed_range.attr, NULL, }; static struct attribute_group rgb_attr_group = { .attrs = go_rgb_attrs, }; static struct mc_subled go_rgb_subled_info[] = { { .color_index = LED_COLOR_ID_RED, .brightness = 0x50, .intensity = 0x24, .channel = 0x1, }, { .color_index = LED_COLOR_ID_GREEN, .brightness = 0x50, .intensity = 0x22, .channel = 0x2, }, { .color_index = LED_COLOR_ID_BLUE, .brightness = 0x50, .intensity = 0x99, .channel = 0x3, }, }; static struct led_classdev_mc go_cdev_rgb = { .led_cdev = { .name = "go:rgb:joystick_rings", .color = LED_COLOR_ID_RGB, .brightness = 0x50, .max_brightness = 0x64, .brightness_set = hid_go_brightness_set, }, .num_colors = ARRAY_SIZE(go_rgb_subled_info), .subled_info = go_rgb_subled_info, }; static void cfg_setup(struct work_struct *work) { int ret; /* MCU Version Attrs */ ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, PRODUCT_VERSION, USB_MCU, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve USB_MCU Product Version: %i\n", ret); return; } ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, PROTOCOL_VERSION, USB_MCU, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve USB_MCU Protocol Version: %i\n", ret); return; } ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, FIRMWARE_VERSION, USB_MCU, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve USB_MCU Firmware Version: %i\n", ret); return; } ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, HARDWARE_VERSION, USB_MCU, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve USB_MCU Hardware Version: %i\n", ret); return; } ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, HARDWARE_GENERATION, USB_MCU, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve USB_MCU Hardware Generation: %i\n", ret); return; } /* TX Dongle Version Attrs */ ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, PRODUCT_VERSION, TX_DONGLE, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve TX_DONGLE Product Version: %i\n", ret); return; } ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, PROTOCOL_VERSION, TX_DONGLE, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve TX_DONGLE Protocol Version: %i\n", ret); return; } ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, FIRMWARE_VERSION, TX_DONGLE, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve TX_DONGLE Firmware Version: %i\n", ret); return; } ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, HARDWARE_VERSION, TX_DONGLE, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve TX_DONGLE Hardware Version: %i\n", ret); return; } ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, HARDWARE_GENERATION, TX_DONGLE, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve TX_DONGLE Hardware Generation: %i\n", ret); return; } /* Left Handle Version Attrs */ ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, PRODUCT_VERSION, LEFT_CONTROLLER, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve LEFT_CONTROLLER Product Version: %i\n", ret); return; } ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, PROTOCOL_VERSION, LEFT_CONTROLLER, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve LEFT_CONTROLLER Protocol Version: %i\n", ret); return; } ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, FIRMWARE_VERSION, LEFT_CONTROLLER, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve LEFT_CONTROLLER Firmware Version: %i\n", ret); return; } ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, HARDWARE_VERSION, LEFT_CONTROLLER, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve LEFT_CONTROLLER Hardware Version: %i\n", ret); return; } ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, HARDWARE_GENERATION, LEFT_CONTROLLER, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve LEFT_CONTROLLER Hardware Generation: %i\n", ret); return; } /* Right Handle Version Attrs */ ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, PRODUCT_VERSION, RIGHT_CONTROLLER, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve RIGHT_CONTROLLER Product Version: %i\n", ret); return; } ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, PROTOCOL_VERSION, RIGHT_CONTROLLER, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve RIGHT_CONTROLLER Protocol Version: %i\n", ret); return; } ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, FIRMWARE_VERSION, RIGHT_CONTROLLER, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve RIGHT_CONTROLLER Firmware Version: %i\n", ret); return; } ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, HARDWARE_VERSION, RIGHT_CONTROLLER, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve RIGHT_CONTROLLER Hardware Version: %i\n", ret); return; } ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, HARDWARE_GENERATION, RIGHT_CONTROLLER, NULL, 0); if (ret < 0) { dev_err(&drvdata.hdev->dev, "Failed to retrieve RIGHT_CONTROLLER Hardware Generation: %i\n", ret); return; } } static int hid_go_cfg_probe(struct hid_device *hdev, const struct hid_device_id *_id) { unsigned char *buf; int ret; buf = devm_kzalloc(&hdev->dev, GO_PACKET_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; hid_set_drvdata(hdev, &drvdata); drvdata.hdev = hdev; mutex_init(&drvdata.cfg_mutex); ret = sysfs_create_groups(&hdev->dev.kobj, top_level_attr_groups); if (ret) { dev_err_probe(&hdev->dev, ret, "Failed to create gamepad configuration attributes\n"); return ret; } ret = devm_led_classdev_multicolor_register(&hdev->dev, &go_cdev_rgb); if (ret) { dev_err_probe(&hdev->dev, ret, "Failed to create RGB device\n"); return ret; } ret = devm_device_add_group(go_cdev_rgb.led_cdev.dev, &rgb_attr_group); if (ret) { dev_err_probe(&hdev->dev, ret, "Failed to create RGB configuration attributes\n"); return ret; } drvdata.led_cdev = &go_cdev_rgb.led_cdev; init_completion(&drvdata.send_cmd_complete); /* Executing calls prior to returning from probe will lock the MCU. Schedule * initial data call after probe has completed and MCU can accept calls. */ INIT_DELAYED_WORK(&drvdata.go_cfg_setup, &cfg_setup); ret = schedule_delayed_work(&drvdata.go_cfg_setup, msecs_to_jiffies(2)); if (!ret) { dev_err(&hdev->dev, "Failed to schedule startup delayed work\n"); return -ENODEV; } return 0; } static void hid_go_cfg_remove(struct hid_device *hdev) { guard(mutex)(&drvdata.cfg_mutex); sysfs_remove_groups(&hdev->dev.kobj, top_level_attr_groups); hid_hw_close(hdev); hid_hw_stop(hdev); hid_set_drvdata(hdev, NULL); } static int hid_go_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret, ep; hdev->quirks |= HID_QUIRK_INPUT_PER_APP | HID_QUIRK_MULTI_INPUT; ret = hid_parse(hdev); if (ret) { hid_err(hdev, "Parse failed\n"); return ret; } ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); 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"); hid_hw_stop(hdev); return ret; } ep = get_endpoint_address(hdev); if (ep != GO_GP_INTF_IN) { dev_dbg(&hdev->dev, "Started interface %x as generic HID device\n", ep); return 0; } ret = hid_go_cfg_probe(hdev, id); if (ret) dev_err_probe(&hdev->dev, ret, "Failed to start configuration interface\n"); dev_dbg(&hdev->dev, "Started Legion Go HID Device: %x\n", ep); return ret; } static void hid_go_remove(struct hid_device *hdev) { int ep = get_endpoint_address(hdev); if (ep <= 0) return; switch (ep) { case GO_GP_INTF_IN: hid_go_cfg_remove(hdev); break; default: hid_hw_close(hdev); hid_hw_stop(hdev); break; } } static const struct hid_device_id hid_go_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_LEGION_GO2_XINPUT) }, { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_LEGION_GO2_DINPUT) }, { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_LEGION_GO2_DUAL_DINPUT) }, { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_LEGION_GO2_FPS) }, {} }; MODULE_DEVICE_TABLE(hid, hid_go_devices); static struct hid_driver hid_lenovo_go = { .name = "hid-lenovo-go", .id_table = hid_go_devices, .probe = hid_go_probe, .remove = hid_go_remove, .raw_event = hid_go_raw_event, }; module_hid_driver(hid_lenovo_go); MODULE_AUTHOR("Derek J. Clark"); MODULE_DESCRIPTION("HID Driver for Lenovo Legion Go Series Gamepads."); MODULE_LICENSE("GPL");