diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index db6e1e0bca6e..f60837e4ed52 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -901,6 +901,7 @@ config HID_OXP depends on USB_HID depends on LEDS_CLASS depends on LEDS_CLASS_MULTICOLOR + depends on DMI help Say Y here if you would like to enable support for OneXPlayer handheld devices that come with RGB LED rings around the joysticks and macro buttons. diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 589e4db940c7..93939e489d83 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1130,6 +1130,9 @@ #define USB_VENDOR_ID_CRSC 0x1a2c #define USB_DEVICE_ID_ONEXPLAYER_GEN1 0xb001 +#define USB_VENDOR_ID_WCH 0x1a86 +#define USB_DEVICE_ID_ONEXPLAYER_GEN2 0xfe00 + #define USB_VENDOR_ID_ONTRAK 0x0a07 #define USB_DEVICE_ID_ONTRAK_ADU100 0x0064 diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c index f72bc74a7e6e..835de2118e3c 100644 --- a/drivers/hid/hid-oxp.c +++ b/drivers/hid/hid-oxp.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -24,12 +25,15 @@ #define OXP_PACKET_SIZE 64 #define GEN1_MESSAGE_ID 0xff +#define GEN2_MESSAGE_ID 0x3f #define GEN1_USAGE_PAGE 0xff01 +#define GEN2_USAGE_PAGE 0xff00 enum oxp_function_index { OXP_FID_GEN1_RGB_SET = 0x07, OXP_FID_GEN1_RGB_REPLY = 0x0f, + OXP_FID_GEN2_STATUS_EVENT = 0xb8, }; static struct oxp_hid_cfg { @@ -122,6 +126,22 @@ struct oxp_gen_1_rgb_report { u8 blue; } __packed; +struct oxp_gen_2_rgb_report { + u8 report_id; + u8 header_id; + u8 padding_2; + u8 message_id; + u8 padding_4[2]; + u8 enabled; + u8 speed; + u8 brightness; + u8 red; + u8 green; + u8 blue; + u8 padding_12[3]; + u8 effect; +} __packed; + static u16 get_usage_page(struct hid_device *hdev) { return hdev->collection[0].usage >> 16; @@ -162,6 +182,44 @@ static int oxp_hid_raw_event_gen_1(struct hid_device *hdev, return 0; } +static int oxp_hid_raw_event_gen_2(struct hid_device *hdev, + struct hid_report *report, u8 *data, + int size) +{ + struct led_classdev_mc *led_mc = drvdata.led_mc; + struct oxp_gen_2_rgb_report *rgb_rep; + + if (data[0] != OXP_FID_GEN2_STATUS_EVENT) + return 0; + + if (data[3] != OXP_GET_PROPERTY) + return 0; + + rgb_rep = (struct oxp_gen_2_rgb_report *)data; + /* Ensure we save monocolor as the list value */ + drvdata.rgb_effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ? + OXP_EFFECT_MONO_LIST : + rgb_rep->effect; + drvdata.rgb_speed = rgb_rep->speed; + drvdata.rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED : + OXP_FEAT_ENABLED; + drvdata.rgb_brightness = rgb_rep->brightness; + led_mc->led_cdev.brightness = rgb_rep->brightness / 4 * + led_mc->led_cdev.max_brightness; + /* If monocolor had less than 100% brightness on the previous boot, + * there will be no reliable way to determine the real intensity. + * Since intensity scaling is used with a hardware brightness set at max, + * our brightness will always look like 100%. Use the last set value to + * prevent successive boots from lowering the brightness further. + * Brightness will be "wrong" but the effect will remain the same visually. + */ + led_mc->subled_info[0].intensity = rgb_rep->red; + led_mc->subled_info[1].intensity = rgb_rep->green; + led_mc->subled_info[2].intensity = rgb_rep->blue; + + return 0; +} + static int oxp_hid_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { @@ -172,6 +230,8 @@ static int oxp_hid_raw_event(struct hid_device *hdev, struct hid_report *report, switch (up) { case GEN1_USAGE_PAGE: return oxp_hid_raw_event_gen_1(hdev, report, data, size); + case GEN2_USAGE_PAGE: + return oxp_hid_raw_event_gen_2(hdev, report, data, size); default: break; } @@ -217,6 +277,18 @@ static int oxp_gen_1_property_out(enum oxp_function_index fid, u8 *data, return mcu_property_out(header, header_size, data, data_size, NULL, 0); } +static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, + u8 data_size) +{ + u8 header[] = { fid, GEN2_MESSAGE_ID, 0x01 }; + u8 footer[] = { GEN2_MESSAGE_ID, fid }; + size_t header_size = ARRAY_SIZE(header); + size_t footer_size = ARRAY_SIZE(footer); + + return mcu_property_out(header, header_size, data, data_size, footer, + footer_size); +} + static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness) { u16 up = get_usage_page(drvdata.hdev); @@ -231,6 +303,11 @@ static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness) if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST) data[3] = 0x04; return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 4); + case GEN2_USAGE_PAGE: + data = (u8[6]) { OXP_SET_PROPERTY, 0x00, 0x02, enabled, speed, brightness }; + if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST) + data[5] = 0x04; + return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 6); default: return -ENODEV; } @@ -245,6 +322,9 @@ static ssize_t oxp_rgb_status_show(void) case GEN1_USAGE_PAGE: data = (u8[1]) { OXP_GET_PROPERTY }; return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1); + case GEN2_USAGE_PAGE: + data = (u8[3]) { OXP_GET_PROPERTY, 0x00, 0x02 }; + return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 3); default: return -ENODEV; } @@ -275,6 +355,16 @@ static int oxp_rgb_color_set(void) data[3 * i + 3] = blue; } return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, size); + case GEN2_USAGE_PAGE: + size = 57; + data = (u8[57]) { OXP_EFFECT_MONO_TRUE, 0x00, 0x02 }; + + for (i = 1; i < size / 3; i++) { + data[3 * i] = red; + data[3 * i + 1] = green; + data[3 * i + 2] = blue; + } + return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, size); default: return -ENODEV; } @@ -311,6 +401,10 @@ static int oxp_rgb_effect_set(u8 effect) data = (u8[1]) { effect }; ret = oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1); break; + case GEN2_USAGE_PAGE: + data = (u8[3]) { effect, 0x00, 0x02 }; + ret = oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 3); + break; default: ret = -ENODEV; } @@ -559,6 +653,56 @@ static struct led_classdev_mc oxp_cdev_rgb = { .subled_info = oxp_rgb_subled_info, }; +struct quirk_entry { + bool hybrid_mcu; +}; + +static struct quirk_entry quirk_hybrid_mcu = { + .hybrid_mcu = true, +}; + +static const struct dmi_system_id oxp_hybrid_mcu_list[] = { + { + .ident = "OneXPlayer Apex", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER APEX"), + }, + .driver_data = &quirk_hybrid_mcu, + }, + { + .ident = "OneXPlayer G1 AMD", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 A"), + }, + .driver_data = &quirk_hybrid_mcu, + }, + { + .ident = "OneXPlayer G1 Intel", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 i"), + }, + .driver_data = &quirk_hybrid_mcu, + }, + {}, +}; + +static bool oxp_hybrid_mcu_device(void) +{ + const struct dmi_system_id *dmi_id; + struct quirk_entry *quirks; + + dmi_id = dmi_first_match(oxp_hybrid_mcu_list); + if (!dmi_id) + return false; + + quirks = dmi_id->driver_data; + + return quirks->hybrid_mcu; +} + static int oxp_cfg_probe(struct hid_device *hdev, u16 up) { int ret; @@ -566,6 +710,10 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up) hid_set_drvdata(hdev, &drvdata); mutex_init(&drvdata.cfg_mutex); drvdata.hdev = hdev; + + if (up == GEN2_USAGE_PAGE && oxp_hybrid_mcu_device()) + goto skip_rgb; + drvdata.led_mc = &oxp_cdev_rgb; INIT_DELAYED_WORK(&drvdata.oxp_rgb_queue, oxp_rgb_queue_fn); @@ -585,6 +733,7 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up) dev_warn(drvdata.led_mc->led_cdev.dev, "Failed to query RGB initial state: %i\n", ret); +skip_rgb: return 0; } @@ -613,6 +762,7 @@ static int oxp_hid_probe(struct hid_device *hdev, switch (up) { case GEN1_USAGE_PAGE: + case GEN2_USAGE_PAGE: ret = oxp_cfg_probe(hdev, up); if (ret) { hid_hw_close(hdev); @@ -634,6 +784,7 @@ static void oxp_hid_remove(struct hid_device *hdev) static const struct hid_device_id oxp_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_CRSC, USB_DEVICE_ID_ONEXPLAYER_GEN1) }, + { HID_USB_DEVICE(USB_VENDOR_ID_WCH, USB_DEVICE_ID_ONEXPLAYER_GEN2) }, {} };