184910c45SDerek J. Clark // SPDX-License-Identifier: GPL-2.0-or-later 284910c45SDerek J. Clark /* 384910c45SDerek J. Clark * HID driver for OneXPlayer gamepad configuration devices. 484910c45SDerek J. Clark * 584910c45SDerek J. Clark * Copyright (c) 2026 Valve Corporation 684910c45SDerek J. Clark */ 784910c45SDerek J. Clark 884910c45SDerek J. Clark #include <linux/array_size.h> 984910c45SDerek J. Clark #include <linux/cleanup.h> 1084910c45SDerek J. Clark #include <linux/delay.h> 1184910c45SDerek J. Clark #include <linux/dev_printk.h> 1284910c45SDerek J. Clark #include <linux/device.h> 13252c4bf1SDerek J. Clark #include <linux/dmi.h> 1484910c45SDerek J. Clark #include <linux/hid.h> 1584910c45SDerek J. Clark #include <linux/jiffies.h> 1684910c45SDerek J. Clark #include <linux/kstrtox.h> 1784910c45SDerek J. Clark #include <linux/led-class-multicolor.h> 1884910c45SDerek J. Clark #include <linux/mutex.h> 1984910c45SDerek J. Clark #include <linux/sysfs.h> 2084910c45SDerek J. Clark #include <linux/types.h> 2184910c45SDerek J. Clark #include <linux/workqueue.h> 2284910c45SDerek J. Clark 2384910c45SDerek J. Clark #include "hid-ids.h" 2484910c45SDerek J. Clark 2584910c45SDerek J. Clark #define OXP_PACKET_SIZE 64 2684910c45SDerek J. Clark 2784910c45SDerek J. Clark #define GEN1_MESSAGE_ID 0xff 28252c4bf1SDerek J. Clark #define GEN2_MESSAGE_ID 0x3f 2984910c45SDerek J. Clark 3084910c45SDerek J. Clark #define GEN1_USAGE_PAGE 0xff01 31252c4bf1SDerek J. Clark #define GEN2_USAGE_PAGE 0xff00 3284910c45SDerek J. Clark 3384910c45SDerek J. Clark enum oxp_function_index { 3484910c45SDerek J. Clark OXP_FID_GEN1_RGB_SET = 0x07, 3584910c45SDerek J. Clark OXP_FID_GEN1_RGB_REPLY = 0x0f, 36*2f424f28SDerek J. Clark OXP_FID_GEN2_TOGGLE_MODE = 0xb2, 37252c4bf1SDerek J. Clark OXP_FID_GEN2_STATUS_EVENT = 0xb8, 3884910c45SDerek J. Clark }; 3984910c45SDerek J. Clark 4084910c45SDerek J. Clark static struct oxp_hid_cfg { 4184910c45SDerek J. Clark struct delayed_work oxp_rgb_queue; 42*2f424f28SDerek J. Clark struct delayed_work oxp_mcu_init; 4384910c45SDerek J. Clark struct led_classdev_mc *led_mc; 4484910c45SDerek J. Clark struct hid_device *hdev; 4584910c45SDerek J. Clark struct mutex cfg_mutex; /*ensure single synchronous output report*/ 4684910c45SDerek J. Clark u8 rgb_brightness; 47*2f424f28SDerek J. Clark u8 gamepad_mode; 4884910c45SDerek J. Clark u8 rgb_effect; 4984910c45SDerek J. Clark u8 rgb_speed; 5084910c45SDerek J. Clark u8 rgb_en; 5184910c45SDerek J. Clark } drvdata; 5284910c45SDerek J. Clark 53*2f424f28SDerek J. Clark enum oxp_gamepad_mode_index { 54*2f424f28SDerek J. Clark OXP_GP_MODE_XINPUT = 0x00, 55*2f424f28SDerek J. Clark OXP_GP_MODE_DEBUG = 0x03, 56*2f424f28SDerek J. Clark }; 57*2f424f28SDerek J. Clark 58*2f424f28SDerek J. Clark static const char *const oxp_gamepad_mode_text[] = { 59*2f424f28SDerek J. Clark [OXP_GP_MODE_XINPUT] = "xinput", 60*2f424f28SDerek J. Clark [OXP_GP_MODE_DEBUG] = "debug", 61*2f424f28SDerek J. Clark }; 62*2f424f28SDerek J. Clark 6384910c45SDerek J. Clark enum oxp_feature_en_index { 6484910c45SDerek J. Clark OXP_FEAT_DISABLED, 6584910c45SDerek J. Clark OXP_FEAT_ENABLED, 6684910c45SDerek J. Clark }; 6784910c45SDerek J. Clark 6884910c45SDerek J. Clark static const char *const oxp_feature_en_text[] = { 6984910c45SDerek J. Clark [OXP_FEAT_DISABLED] = "false", 7084910c45SDerek J. Clark [OXP_FEAT_ENABLED] = "true", 7184910c45SDerek J. Clark }; 7284910c45SDerek J. Clark 7384910c45SDerek J. Clark enum oxp_rgb_effect_index { 7484910c45SDerek J. Clark OXP_UNKNOWN, 7584910c45SDerek J. Clark OXP_EFFECT_AURORA, 7684910c45SDerek J. Clark OXP_EFFECT_BIRTHDAY, 7784910c45SDerek J. Clark OXP_EFFECT_FLOWING, 7884910c45SDerek J. Clark OXP_EFFECT_CHROMA_1, 7984910c45SDerek J. Clark OXP_EFFECT_NEON, 8084910c45SDerek J. Clark OXP_EFFECT_CHROMA_2, 8184910c45SDerek J. Clark OXP_EFFECT_DREAMY, 8284910c45SDerek J. Clark OXP_EFFECT_WARM, 8384910c45SDerek J. Clark OXP_EFFECT_CYBERPUNK, 8484910c45SDerek J. Clark OXP_EFFECT_SEA, 8584910c45SDerek J. Clark OXP_EFFECT_SUNSET, 8684910c45SDerek J. Clark OXP_EFFECT_COLORFUL, 8784910c45SDerek J. Clark OXP_EFFECT_MONSTER, 8884910c45SDerek J. Clark OXP_EFFECT_GREEN, 8984910c45SDerek J. Clark OXP_EFFECT_BLUE, 9084910c45SDerek J. Clark OXP_EFFECT_YELLOW, 9184910c45SDerek J. Clark OXP_EFFECT_TEAL, 9284910c45SDerek J. Clark OXP_EFFECT_PURPLE, 9384910c45SDerek J. Clark OXP_EFFECT_FOGGY, 9484910c45SDerek J. Clark OXP_EFFECT_MONO_LIST, /* placeholder for effect_index_show */ 9584910c45SDerek J. Clark }; 9684910c45SDerek J. Clark 9784910c45SDerek J. Clark /* These belong to rgb_effect_index, but we want to hide them from 9884910c45SDerek J. Clark * rgb_effect_text 9984910c45SDerek J. Clark */ 10084910c45SDerek J. Clark 10184910c45SDerek J. Clark #define OXP_GET_PROPERTY 0xfc 10284910c45SDerek J. Clark #define OXP_SET_PROPERTY 0xfd 10384910c45SDerek J. Clark #define OXP_EFFECT_MONO_TRUE 0xfe /* actual index for monocolor */ 10484910c45SDerek J. Clark 10584910c45SDerek J. Clark static const char *const oxp_rgb_effect_text[] = { 10684910c45SDerek J. Clark [OXP_UNKNOWN] = "unknown", 10784910c45SDerek J. Clark [OXP_EFFECT_AURORA] = "aurora", 10884910c45SDerek J. Clark [OXP_EFFECT_BIRTHDAY] = "birthday_cake", 10984910c45SDerek J. Clark [OXP_EFFECT_FLOWING] = "flowing_light", 11084910c45SDerek J. Clark [OXP_EFFECT_CHROMA_1] = "chroma_popping", 11184910c45SDerek J. Clark [OXP_EFFECT_NEON] = "neon", 11284910c45SDerek J. Clark [OXP_EFFECT_CHROMA_2] = "chroma_breathing", 11384910c45SDerek J. Clark [OXP_EFFECT_DREAMY] = "dreamy", 11484910c45SDerek J. Clark [OXP_EFFECT_WARM] = "warm_sun", 11584910c45SDerek J. Clark [OXP_EFFECT_CYBERPUNK] = "cyberpunk", 11684910c45SDerek J. Clark [OXP_EFFECT_SEA] = "sea_foam", 11784910c45SDerek J. Clark [OXP_EFFECT_SUNSET] = "sunset_afterglow", 11884910c45SDerek J. Clark [OXP_EFFECT_COLORFUL] = "colorful", 11984910c45SDerek J. Clark [OXP_EFFECT_MONSTER] = "monster_woke", 12084910c45SDerek J. Clark [OXP_EFFECT_GREEN] = "green_breathing", 12184910c45SDerek J. Clark [OXP_EFFECT_BLUE] = "blue_breathing", 12284910c45SDerek J. Clark [OXP_EFFECT_YELLOW] = "yellow_breathing", 12384910c45SDerek J. Clark [OXP_EFFECT_TEAL] = "teal_breathing", 12484910c45SDerek J. Clark [OXP_EFFECT_PURPLE] = "purple_breathing", 12584910c45SDerek J. Clark [OXP_EFFECT_FOGGY] = "foggy_haze", 12684910c45SDerek J. Clark [OXP_EFFECT_MONO_LIST] = "monocolor", 12784910c45SDerek J. Clark }; 12884910c45SDerek J. Clark 12984910c45SDerek J. Clark struct oxp_gen_1_rgb_report { 13084910c45SDerek J. Clark u8 report_id; 13184910c45SDerek J. Clark u8 message_id; 13284910c45SDerek J. Clark u8 padding_2[2]; 13384910c45SDerek J. Clark u8 effect; 13484910c45SDerek J. Clark u8 enabled; 13584910c45SDerek J. Clark u8 speed; 13684910c45SDerek J. Clark u8 brightness; 13784910c45SDerek J. Clark u8 red; 13884910c45SDerek J. Clark u8 green; 13984910c45SDerek J. Clark u8 blue; 14084910c45SDerek J. Clark } __packed; 14184910c45SDerek J. Clark 142252c4bf1SDerek J. Clark struct oxp_gen_2_rgb_report { 143252c4bf1SDerek J. Clark u8 report_id; 144252c4bf1SDerek J. Clark u8 header_id; 145252c4bf1SDerek J. Clark u8 padding_2; 146252c4bf1SDerek J. Clark u8 message_id; 147252c4bf1SDerek J. Clark u8 padding_4[2]; 148252c4bf1SDerek J. Clark u8 enabled; 149252c4bf1SDerek J. Clark u8 speed; 150252c4bf1SDerek J. Clark u8 brightness; 151252c4bf1SDerek J. Clark u8 red; 152252c4bf1SDerek J. Clark u8 green; 153252c4bf1SDerek J. Clark u8 blue; 154252c4bf1SDerek J. Clark u8 padding_12[3]; 155252c4bf1SDerek J. Clark u8 effect; 156252c4bf1SDerek J. Clark } __packed; 157252c4bf1SDerek J. Clark 15884910c45SDerek J. Clark static u16 get_usage_page(struct hid_device *hdev) 15984910c45SDerek J. Clark { 16084910c45SDerek J. Clark return hdev->collection[0].usage >> 16; 16184910c45SDerek J. Clark } 16284910c45SDerek J. Clark 16384910c45SDerek J. Clark static int oxp_hid_raw_event_gen_1(struct hid_device *hdev, 16484910c45SDerek J. Clark struct hid_report *report, u8 *data, 16584910c45SDerek J. Clark int size) 16684910c45SDerek J. Clark { 16784910c45SDerek J. Clark struct led_classdev_mc *led_mc = drvdata.led_mc; 16884910c45SDerek J. Clark struct oxp_gen_1_rgb_report *rgb_rep; 16984910c45SDerek J. Clark 17084910c45SDerek J. Clark if (data[1] != OXP_FID_GEN1_RGB_REPLY) 17184910c45SDerek J. Clark return 0; 17284910c45SDerek J. Clark 17384910c45SDerek J. Clark rgb_rep = (struct oxp_gen_1_rgb_report *)data; 17484910c45SDerek J. Clark /* Ensure we save monocolor as the list value */ 17584910c45SDerek J. Clark drvdata.rgb_effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ? 17684910c45SDerek J. Clark OXP_EFFECT_MONO_LIST : 17784910c45SDerek J. Clark rgb_rep->effect; 17884910c45SDerek J. Clark drvdata.rgb_speed = rgb_rep->speed; 17984910c45SDerek J. Clark drvdata.rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED : 18084910c45SDerek J. Clark OXP_FEAT_ENABLED; 18184910c45SDerek J. Clark drvdata.rgb_brightness = rgb_rep->brightness; 18284910c45SDerek J. Clark led_mc->led_cdev.brightness = rgb_rep->brightness / 4 * 18384910c45SDerek J. Clark led_mc->led_cdev.max_brightness; 18484910c45SDerek J. Clark /* If monocolor had less than 100% brightness on the previous boot, 18584910c45SDerek J. Clark * there will be no reliable way to determine the real intensity. 18684910c45SDerek J. Clark * Since intensity scaling is used with a hardware brightness set at max, 18784910c45SDerek J. Clark * our brightness will always look like 100%. Use the last set value to 18884910c45SDerek J. Clark * prevent successive boots from lowering the brightness further. 18984910c45SDerek J. Clark * Brightness will be "wrong" but the effect will remain the same visually. 19084910c45SDerek J. Clark */ 19184910c45SDerek J. Clark led_mc->subled_info[0].intensity = rgb_rep->red; 19284910c45SDerek J. Clark led_mc->subled_info[1].intensity = rgb_rep->green; 19384910c45SDerek J. Clark led_mc->subled_info[2].intensity = rgb_rep->blue; 19484910c45SDerek J. Clark 19584910c45SDerek J. Clark return 0; 19684910c45SDerek J. Clark } 19784910c45SDerek J. Clark 198*2f424f28SDerek J. Clark static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, u8 data_size); 199*2f424f28SDerek J. Clark 200*2f424f28SDerek J. Clark static void oxp_mcu_init_fn(struct work_struct *work) 201*2f424f28SDerek J. Clark { 202*2f424f28SDerek J. Clark u8 gp_mode_data[3] = { OXP_GP_MODE_DEBUG, 0x01, 0x02 }; 203*2f424f28SDerek J. Clark int ret; 204*2f424f28SDerek J. Clark 205*2f424f28SDerek J. Clark /* Cycle the gamepad mode */ 206*2f424f28SDerek J. Clark ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3); 207*2f424f28SDerek J. Clark if (ret) 208*2f424f28SDerek J. Clark dev_err(&drvdata.hdev->dev, 209*2f424f28SDerek J. Clark "Error: Failed to set gamepad mode: %i\n", ret); 210*2f424f28SDerek J. Clark 211*2f424f28SDerek J. Clark /* Remainder only applies for xinput mode */ 212*2f424f28SDerek J. Clark if (drvdata.gamepad_mode == OXP_GP_MODE_DEBUG) 213*2f424f28SDerek J. Clark return; 214*2f424f28SDerek J. Clark 215*2f424f28SDerek J. Clark gp_mode_data[0] = OXP_GP_MODE_XINPUT; 216*2f424f28SDerek J. Clark ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3); 217*2f424f28SDerek J. Clark if (ret) 218*2f424f28SDerek J. Clark dev_err(&drvdata.hdev->dev, 219*2f424f28SDerek J. Clark "Error: Failed to set gamepad mode: %i\n", ret); 220*2f424f28SDerek J. Clark } 221*2f424f28SDerek J. Clark 222252c4bf1SDerek J. Clark static int oxp_hid_raw_event_gen_2(struct hid_device *hdev, 223252c4bf1SDerek J. Clark struct hid_report *report, u8 *data, 224252c4bf1SDerek J. Clark int size) 225252c4bf1SDerek J. Clark { 226252c4bf1SDerek J. Clark struct led_classdev_mc *led_mc = drvdata.led_mc; 227252c4bf1SDerek J. Clark struct oxp_gen_2_rgb_report *rgb_rep; 228252c4bf1SDerek J. Clark 229252c4bf1SDerek J. Clark if (data[0] != OXP_FID_GEN2_STATUS_EVENT) 230252c4bf1SDerek J. Clark return 0; 231252c4bf1SDerek J. Clark 232*2f424f28SDerek J. Clark /* Sent ~6s after resume event, indicating the MCU has fully reset. 233*2f424f28SDerek J. Clark * Re-apply our settings after this has been received. 234*2f424f28SDerek J. Clark */ 235*2f424f28SDerek J. Clark if (data[3] == OXP_EFFECT_MONO_TRUE) { 236*2f424f28SDerek J. Clark mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50)); 237*2f424f28SDerek J. Clark return 0; 238*2f424f28SDerek J. Clark } 239*2f424f28SDerek J. Clark 240252c4bf1SDerek J. Clark if (data[3] != OXP_GET_PROPERTY) 241252c4bf1SDerek J. Clark return 0; 242252c4bf1SDerek J. Clark 243252c4bf1SDerek J. Clark rgb_rep = (struct oxp_gen_2_rgb_report *)data; 244252c4bf1SDerek J. Clark /* Ensure we save monocolor as the list value */ 245252c4bf1SDerek J. Clark drvdata.rgb_effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ? 246252c4bf1SDerek J. Clark OXP_EFFECT_MONO_LIST : 247252c4bf1SDerek J. Clark rgb_rep->effect; 248252c4bf1SDerek J. Clark drvdata.rgb_speed = rgb_rep->speed; 249252c4bf1SDerek J. Clark drvdata.rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED : 250252c4bf1SDerek J. Clark OXP_FEAT_ENABLED; 251252c4bf1SDerek J. Clark drvdata.rgb_brightness = rgb_rep->brightness; 252252c4bf1SDerek J. Clark led_mc->led_cdev.brightness = rgb_rep->brightness / 4 * 253252c4bf1SDerek J. Clark led_mc->led_cdev.max_brightness; 254252c4bf1SDerek J. Clark /* If monocolor had less than 100% brightness on the previous boot, 255252c4bf1SDerek J. Clark * there will be no reliable way to determine the real intensity. 256252c4bf1SDerek J. Clark * Since intensity scaling is used with a hardware brightness set at max, 257252c4bf1SDerek J. Clark * our brightness will always look like 100%. Use the last set value to 258252c4bf1SDerek J. Clark * prevent successive boots from lowering the brightness further. 259252c4bf1SDerek J. Clark * Brightness will be "wrong" but the effect will remain the same visually. 260252c4bf1SDerek J. Clark */ 261252c4bf1SDerek J. Clark led_mc->subled_info[0].intensity = rgb_rep->red; 262252c4bf1SDerek J. Clark led_mc->subled_info[1].intensity = rgb_rep->green; 263252c4bf1SDerek J. Clark led_mc->subled_info[2].intensity = rgb_rep->blue; 264252c4bf1SDerek J. Clark 265252c4bf1SDerek J. Clark return 0; 266252c4bf1SDerek J. Clark } 267252c4bf1SDerek J. Clark 26884910c45SDerek J. Clark static int oxp_hid_raw_event(struct hid_device *hdev, struct hid_report *report, 26984910c45SDerek J. Clark u8 *data, int size) 27084910c45SDerek J. Clark { 27184910c45SDerek J. Clark u16 up = get_usage_page(hdev); 27284910c45SDerek J. Clark 27384910c45SDerek J. Clark dev_dbg(&hdev->dev, "raw event data: [%*ph]\n", OXP_PACKET_SIZE, data); 27484910c45SDerek J. Clark 27584910c45SDerek J. Clark switch (up) { 27684910c45SDerek J. Clark case GEN1_USAGE_PAGE: 27784910c45SDerek J. Clark return oxp_hid_raw_event_gen_1(hdev, report, data, size); 278252c4bf1SDerek J. Clark case GEN2_USAGE_PAGE: 279252c4bf1SDerek J. Clark return oxp_hid_raw_event_gen_2(hdev, report, data, size); 28084910c45SDerek J. Clark default: 28184910c45SDerek J. Clark break; 28284910c45SDerek J. Clark } 28384910c45SDerek J. Clark 28484910c45SDerek J. Clark return 0; 28584910c45SDerek J. Clark } 28684910c45SDerek J. Clark 28784910c45SDerek J. Clark static int mcu_property_out(u8 *header, size_t header_size, u8 *data, 28884910c45SDerek J. Clark size_t data_size, u8 *footer, size_t footer_size) 28984910c45SDerek J. Clark { 29084910c45SDerek J. Clark unsigned char *dmabuf __free(kfree) = kzalloc(OXP_PACKET_SIZE, GFP_KERNEL); 29184910c45SDerek J. Clark int ret; 29284910c45SDerek J. Clark 29384910c45SDerek J. Clark if (!dmabuf) 29484910c45SDerek J. Clark return -ENOMEM; 29584910c45SDerek J. Clark 29684910c45SDerek J. Clark if (header_size + data_size + footer_size > OXP_PACKET_SIZE) 29784910c45SDerek J. Clark return -EINVAL; 29884910c45SDerek J. Clark 29984910c45SDerek J. Clark guard(mutex)(&drvdata.cfg_mutex); 30084910c45SDerek J. Clark memcpy(dmabuf, header, header_size); 30184910c45SDerek J. Clark memcpy(dmabuf + header_size, data, data_size); 30284910c45SDerek J. Clark if (footer_size) 30384910c45SDerek J. Clark memcpy(dmabuf + OXP_PACKET_SIZE - footer_size, footer, footer_size); 30484910c45SDerek J. Clark 30584910c45SDerek J. Clark dev_dbg(&drvdata.hdev->dev, "raw data: [%*ph]\n", OXP_PACKET_SIZE, dmabuf); 30684910c45SDerek J. Clark 30784910c45SDerek J. Clark ret = hid_hw_output_report(drvdata.hdev, dmabuf, OXP_PACKET_SIZE); 30884910c45SDerek J. Clark if (ret < 0) 30984910c45SDerek J. Clark return ret; 31084910c45SDerek J. Clark 31184910c45SDerek J. Clark /* MCU takes 200ms to be ready for another command. */ 31284910c45SDerek J. Clark msleep(200); 31384910c45SDerek J. Clark return ret == OXP_PACKET_SIZE ? 0 : -EIO; 31484910c45SDerek J. Clark } 31584910c45SDerek J. Clark 31684910c45SDerek J. Clark static int oxp_gen_1_property_out(enum oxp_function_index fid, u8 *data, 31784910c45SDerek J. Clark u8 data_size) 31884910c45SDerek J. Clark { 31984910c45SDerek J. Clark u8 header[] = { fid, GEN1_MESSAGE_ID }; 32084910c45SDerek J. Clark size_t header_size = ARRAY_SIZE(header); 32184910c45SDerek J. Clark 32284910c45SDerek J. Clark return mcu_property_out(header, header_size, data, data_size, NULL, 0); 32384910c45SDerek J. Clark } 32484910c45SDerek J. Clark 325252c4bf1SDerek J. Clark static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, 326252c4bf1SDerek J. Clark u8 data_size) 327252c4bf1SDerek J. Clark { 328252c4bf1SDerek J. Clark u8 header[] = { fid, GEN2_MESSAGE_ID, 0x01 }; 329252c4bf1SDerek J. Clark u8 footer[] = { GEN2_MESSAGE_ID, fid }; 330252c4bf1SDerek J. Clark size_t header_size = ARRAY_SIZE(header); 331252c4bf1SDerek J. Clark size_t footer_size = ARRAY_SIZE(footer); 332252c4bf1SDerek J. Clark 333252c4bf1SDerek J. Clark return mcu_property_out(header, header_size, data, data_size, footer, 334252c4bf1SDerek J. Clark footer_size); 335252c4bf1SDerek J. Clark } 336252c4bf1SDerek J. Clark 337*2f424f28SDerek J. Clark static ssize_t gamepad_mode_store(struct device *dev, 338*2f424f28SDerek J. Clark struct device_attribute *attr, const char *buf, 339*2f424f28SDerek J. Clark size_t count) 340*2f424f28SDerek J. Clark { 341*2f424f28SDerek J. Clark u16 up = get_usage_page(drvdata.hdev); 342*2f424f28SDerek J. Clark u8 data[3] = { 0x00, 0x01, 0x02 }; 343*2f424f28SDerek J. Clark int ret = -EINVAL; 344*2f424f28SDerek J. Clark int i; 345*2f424f28SDerek J. Clark 346*2f424f28SDerek J. Clark if (up != GEN2_USAGE_PAGE) 347*2f424f28SDerek J. Clark return ret; 348*2f424f28SDerek J. Clark 349*2f424f28SDerek J. Clark for (i = 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) { 350*2f424f28SDerek J. Clark if (oxp_gamepad_mode_text[i] && sysfs_streq(buf, oxp_gamepad_mode_text[i])) { 351*2f424f28SDerek J. Clark ret = i; 352*2f424f28SDerek J. Clark break; 353*2f424f28SDerek J. Clark } 354*2f424f28SDerek J. Clark } 355*2f424f28SDerek J. Clark if (ret < 0) 356*2f424f28SDerek J. Clark return ret; 357*2f424f28SDerek J. Clark 358*2f424f28SDerek J. Clark data[0] = ret; 359*2f424f28SDerek J. Clark 360*2f424f28SDerek J. Clark ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, data, 3); 361*2f424f28SDerek J. Clark if (ret) 362*2f424f28SDerek J. Clark return ret; 363*2f424f28SDerek J. Clark 364*2f424f28SDerek J. Clark drvdata.gamepad_mode = data[0]; 365*2f424f28SDerek J. Clark 366*2f424f28SDerek J. Clark return count; 367*2f424f28SDerek J. Clark } 368*2f424f28SDerek J. Clark 369*2f424f28SDerek J. Clark static ssize_t gamepad_mode_show(struct device *dev, 370*2f424f28SDerek J. Clark struct device_attribute *attr, char *buf) 371*2f424f28SDerek J. Clark { 372*2f424f28SDerek J. Clark return sysfs_emit(buf, "%s\n", oxp_gamepad_mode_text[drvdata.gamepad_mode]); 373*2f424f28SDerek J. Clark } 374*2f424f28SDerek J. Clark static DEVICE_ATTR_RW(gamepad_mode); 375*2f424f28SDerek J. Clark 376*2f424f28SDerek J. Clark static ssize_t gamepad_mode_index_show(struct device *dev, 377*2f424f28SDerek J. Clark struct device_attribute *attr, 378*2f424f28SDerek J. Clark char *buf) 379*2f424f28SDerek J. Clark { 380*2f424f28SDerek J. Clark ssize_t count = 0; 381*2f424f28SDerek J. Clark unsigned int i; 382*2f424f28SDerek J. Clark 383*2f424f28SDerek J. Clark for (i = 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) { 384*2f424f28SDerek J. Clark if (!oxp_gamepad_mode_text[i] || 385*2f424f28SDerek J. Clark oxp_gamepad_mode_text[i][0] == '\0') 386*2f424f28SDerek J. Clark continue; 387*2f424f28SDerek J. Clark 388*2f424f28SDerek J. Clark count += sysfs_emit_at(buf, count, "%s ", oxp_gamepad_mode_text[i]); 389*2f424f28SDerek J. Clark } 390*2f424f28SDerek J. Clark 391*2f424f28SDerek J. Clark if (count) 392*2f424f28SDerek J. Clark buf[count - 1] = '\n'; 393*2f424f28SDerek J. Clark 394*2f424f28SDerek J. Clark return count; 395*2f424f28SDerek J. Clark } 396*2f424f28SDerek J. Clark static DEVICE_ATTR_RO(gamepad_mode_index); 397*2f424f28SDerek J. Clark 398*2f424f28SDerek J. Clark static struct attribute *oxp_cfg_attrs[] = { 399*2f424f28SDerek J. Clark &dev_attr_gamepad_mode.attr, 400*2f424f28SDerek J. Clark &dev_attr_gamepad_mode_index.attr, 401*2f424f28SDerek J. Clark NULL, 402*2f424f28SDerek J. Clark }; 403*2f424f28SDerek J. Clark 404*2f424f28SDerek J. Clark static const struct attribute_group oxp_cfg_attrs_group = { 405*2f424f28SDerek J. Clark .attrs = oxp_cfg_attrs, 406*2f424f28SDerek J. Clark }; 407*2f424f28SDerek J. Clark 40884910c45SDerek J. Clark static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness) 40984910c45SDerek J. Clark { 41084910c45SDerek J. Clark u16 up = get_usage_page(drvdata.hdev); 41184910c45SDerek J. Clark u8 *data; 41284910c45SDerek J. Clark 41384910c45SDerek J. Clark /* Always default to max brightness and use intensity scaling when in 41484910c45SDerek J. Clark * monocolor mode. 41584910c45SDerek J. Clark */ 41684910c45SDerek J. Clark switch (up) { 41784910c45SDerek J. Clark case GEN1_USAGE_PAGE: 41884910c45SDerek J. Clark data = (u8[4]) { OXP_SET_PROPERTY, enabled, speed, brightness }; 41984910c45SDerek J. Clark if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST) 42084910c45SDerek J. Clark data[3] = 0x04; 42184910c45SDerek J. Clark return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 4); 422252c4bf1SDerek J. Clark case GEN2_USAGE_PAGE: 423252c4bf1SDerek J. Clark data = (u8[6]) { OXP_SET_PROPERTY, 0x00, 0x02, enabled, speed, brightness }; 424252c4bf1SDerek J. Clark if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST) 425252c4bf1SDerek J. Clark data[5] = 0x04; 426252c4bf1SDerek J. Clark return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 6); 42784910c45SDerek J. Clark default: 42884910c45SDerek J. Clark return -ENODEV; 42984910c45SDerek J. Clark } 43084910c45SDerek J. Clark } 43184910c45SDerek J. Clark 43284910c45SDerek J. Clark static ssize_t oxp_rgb_status_show(void) 43384910c45SDerek J. Clark { 43484910c45SDerek J. Clark u16 up = get_usage_page(drvdata.hdev); 43584910c45SDerek J. Clark u8 *data; 43684910c45SDerek J. Clark 43784910c45SDerek J. Clark switch (up) { 43884910c45SDerek J. Clark case GEN1_USAGE_PAGE: 43984910c45SDerek J. Clark data = (u8[1]) { OXP_GET_PROPERTY }; 44084910c45SDerek J. Clark return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1); 441252c4bf1SDerek J. Clark case GEN2_USAGE_PAGE: 442252c4bf1SDerek J. Clark data = (u8[3]) { OXP_GET_PROPERTY, 0x00, 0x02 }; 443252c4bf1SDerek J. Clark return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 3); 44484910c45SDerek J. Clark default: 44584910c45SDerek J. Clark return -ENODEV; 44684910c45SDerek J. Clark } 44784910c45SDerek J. Clark } 44884910c45SDerek J. Clark 44984910c45SDerek J. Clark static int oxp_rgb_color_set(void) 45084910c45SDerek J. Clark { 45184910c45SDerek J. Clark u8 max_br = drvdata.led_mc->led_cdev.max_brightness; 45284910c45SDerek J. Clark u8 br = drvdata.led_mc->led_cdev.brightness; 45384910c45SDerek J. Clark u16 up = get_usage_page(drvdata.hdev); 45484910c45SDerek J. Clark u8 green, red, blue; 45584910c45SDerek J. Clark size_t size; 45684910c45SDerek J. Clark u8 *data; 45784910c45SDerek J. Clark int i; 45884910c45SDerek J. Clark 45984910c45SDerek J. Clark red = br * drvdata.led_mc->subled_info[0].intensity / max_br; 46084910c45SDerek J. Clark green = br * drvdata.led_mc->subled_info[1].intensity / max_br; 46184910c45SDerek J. Clark blue = br * drvdata.led_mc->subled_info[2].intensity / max_br; 46284910c45SDerek J. Clark 46384910c45SDerek J. Clark switch (up) { 46484910c45SDerek J. Clark case GEN1_USAGE_PAGE: 46584910c45SDerek J. Clark size = 55; 46684910c45SDerek J. Clark data = (u8[55]) { OXP_EFFECT_MONO_TRUE }; 46784910c45SDerek J. Clark 46884910c45SDerek J. Clark for (i = 0; i < (size - 1) / 3; i++) { 46984910c45SDerek J. Clark data[3 * i + 1] = red; 47084910c45SDerek J. Clark data[3 * i + 2] = green; 47184910c45SDerek J. Clark data[3 * i + 3] = blue; 47284910c45SDerek J. Clark } 47384910c45SDerek J. Clark return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, size); 474252c4bf1SDerek J. Clark case GEN2_USAGE_PAGE: 475252c4bf1SDerek J. Clark size = 57; 476252c4bf1SDerek J. Clark data = (u8[57]) { OXP_EFFECT_MONO_TRUE, 0x00, 0x02 }; 477252c4bf1SDerek J. Clark 478252c4bf1SDerek J. Clark for (i = 1; i < size / 3; i++) { 479252c4bf1SDerek J. Clark data[3 * i] = red; 480252c4bf1SDerek J. Clark data[3 * i + 1] = green; 481252c4bf1SDerek J. Clark data[3 * i + 2] = blue; 482252c4bf1SDerek J. Clark } 483252c4bf1SDerek J. Clark return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, size); 48484910c45SDerek J. Clark default: 48584910c45SDerek J. Clark return -ENODEV; 48684910c45SDerek J. Clark } 48784910c45SDerek J. Clark } 48884910c45SDerek J. Clark 48984910c45SDerek J. Clark static int oxp_rgb_effect_set(u8 effect) 49084910c45SDerek J. Clark { 49184910c45SDerek J. Clark u16 up = get_usage_page(drvdata.hdev); 49284910c45SDerek J. Clark u8 *data; 49384910c45SDerek J. Clark int ret; 49484910c45SDerek J. Clark 49584910c45SDerek J. Clark switch (effect) { 49684910c45SDerek J. Clark case OXP_EFFECT_AURORA: 49784910c45SDerek J. Clark case OXP_EFFECT_BIRTHDAY: 49884910c45SDerek J. Clark case OXP_EFFECT_FLOWING: 49984910c45SDerek J. Clark case OXP_EFFECT_CHROMA_1: 50084910c45SDerek J. Clark case OXP_EFFECT_NEON: 50184910c45SDerek J. Clark case OXP_EFFECT_CHROMA_2: 50284910c45SDerek J. Clark case OXP_EFFECT_DREAMY: 50384910c45SDerek J. Clark case OXP_EFFECT_WARM: 50484910c45SDerek J. Clark case OXP_EFFECT_CYBERPUNK: 50584910c45SDerek J. Clark case OXP_EFFECT_SEA: 50684910c45SDerek J. Clark case OXP_EFFECT_SUNSET: 50784910c45SDerek J. Clark case OXP_EFFECT_COLORFUL: 50884910c45SDerek J. Clark case OXP_EFFECT_MONSTER: 50984910c45SDerek J. Clark case OXP_EFFECT_GREEN: 51084910c45SDerek J. Clark case OXP_EFFECT_BLUE: 51184910c45SDerek J. Clark case OXP_EFFECT_YELLOW: 51284910c45SDerek J. Clark case OXP_EFFECT_TEAL: 51384910c45SDerek J. Clark case OXP_EFFECT_PURPLE: 51484910c45SDerek J. Clark case OXP_EFFECT_FOGGY: 51584910c45SDerek J. Clark switch (up) { 51684910c45SDerek J. Clark case GEN1_USAGE_PAGE: 51784910c45SDerek J. Clark data = (u8[1]) { effect }; 51884910c45SDerek J. Clark ret = oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1); 51984910c45SDerek J. Clark break; 520252c4bf1SDerek J. Clark case GEN2_USAGE_PAGE: 521252c4bf1SDerek J. Clark data = (u8[3]) { effect, 0x00, 0x02 }; 522252c4bf1SDerek J. Clark ret = oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 3); 523252c4bf1SDerek J. Clark break; 52484910c45SDerek J. Clark default: 52584910c45SDerek J. Clark ret = -ENODEV; 52684910c45SDerek J. Clark } 52784910c45SDerek J. Clark break; 52884910c45SDerek J. Clark case OXP_EFFECT_MONO_LIST: 52984910c45SDerek J. Clark ret = oxp_rgb_color_set(); 53084910c45SDerek J. Clark break; 53184910c45SDerek J. Clark default: 53284910c45SDerek J. Clark return -EINVAL; 53384910c45SDerek J. Clark } 53484910c45SDerek J. Clark 53584910c45SDerek J. Clark if (ret) 53684910c45SDerek J. Clark return ret; 53784910c45SDerek J. Clark 53884910c45SDerek J. Clark drvdata.rgb_effect = effect; 53984910c45SDerek J. Clark 54084910c45SDerek J. Clark return 0; 54184910c45SDerek J. Clark } 54284910c45SDerek J. Clark 54384910c45SDerek J. Clark static ssize_t enabled_store(struct device *dev, struct device_attribute *attr, 54484910c45SDerek J. Clark const char *buf, size_t count) 54584910c45SDerek J. Clark { 54684910c45SDerek J. Clark int ret; 54784910c45SDerek J. Clark u8 val; 54884910c45SDerek J. Clark 54984910c45SDerek J. Clark ret = sysfs_match_string(oxp_feature_en_text, buf); 55084910c45SDerek J. Clark if (ret < 0) 55184910c45SDerek J. Clark return ret; 55284910c45SDerek J. Clark val = ret; 55384910c45SDerek J. Clark 55484910c45SDerek J. Clark ret = oxp_rgb_status_store(val, drvdata.rgb_speed, 55584910c45SDerek J. Clark drvdata.rgb_brightness); 55684910c45SDerek J. Clark if (ret) 55784910c45SDerek J. Clark return ret; 55884910c45SDerek J. Clark 55984910c45SDerek J. Clark drvdata.rgb_en = val; 56084910c45SDerek J. Clark return count; 56184910c45SDerek J. Clark } 56284910c45SDerek J. Clark 56384910c45SDerek J. Clark static ssize_t enabled_show(struct device *dev, struct device_attribute *attr, 56484910c45SDerek J. Clark char *buf) 56584910c45SDerek J. Clark { 56684910c45SDerek J. Clark int ret; 56784910c45SDerek J. Clark 56884910c45SDerek J. Clark ret = oxp_rgb_status_show(); 56984910c45SDerek J. Clark if (ret) 57084910c45SDerek J. Clark return ret; 57184910c45SDerek J. Clark 57284910c45SDerek J. Clark if (drvdata.rgb_en >= ARRAY_SIZE(oxp_feature_en_text)) 57384910c45SDerek J. Clark return -EINVAL; 57484910c45SDerek J. Clark 57584910c45SDerek J. Clark return sysfs_emit(buf, "%s\n", oxp_feature_en_text[drvdata.rgb_en]); 57684910c45SDerek J. Clark } 57784910c45SDerek J. Clark static DEVICE_ATTR_RW(enabled); 57884910c45SDerek J. Clark 57984910c45SDerek J. Clark static ssize_t enabled_index_show(struct device *dev, 58084910c45SDerek J. Clark struct device_attribute *attr, char *buf) 58184910c45SDerek J. Clark { 58284910c45SDerek J. Clark size_t count = 0; 58384910c45SDerek J. Clark unsigned int i; 58484910c45SDerek J. Clark 58584910c45SDerek J. Clark for (i = 0; i < ARRAY_SIZE(oxp_feature_en_text); i++) 58684910c45SDerek J. Clark count += sysfs_emit_at(buf, count, "%s ", oxp_feature_en_text[i]); 58784910c45SDerek J. Clark 58884910c45SDerek J. Clark if (count) 58984910c45SDerek J. Clark buf[count - 1] = '\n'; 59084910c45SDerek J. Clark 59184910c45SDerek J. Clark return count; 59284910c45SDerek J. Clark } 59384910c45SDerek J. Clark static DEVICE_ATTR_RO(enabled_index); 59484910c45SDerek J. Clark 59584910c45SDerek J. Clark static ssize_t effect_store(struct device *dev, struct device_attribute *attr, 59684910c45SDerek J. Clark const char *buf, size_t count) 59784910c45SDerek J. Clark { 59884910c45SDerek J. Clark int ret; 59984910c45SDerek J. Clark u8 val; 60084910c45SDerek J. Clark 60184910c45SDerek J. Clark ret = sysfs_match_string(oxp_rgb_effect_text, buf); 60284910c45SDerek J. Clark if (ret < 0) 60384910c45SDerek J. Clark return ret; 60484910c45SDerek J. Clark 60584910c45SDerek J. Clark val = ret; 60684910c45SDerek J. Clark 60784910c45SDerek J. Clark ret = oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed, 60884910c45SDerek J. Clark drvdata.rgb_brightness); 60984910c45SDerek J. Clark if (ret) 61084910c45SDerek J. Clark return ret; 61184910c45SDerek J. Clark 61284910c45SDerek J. Clark ret = oxp_rgb_effect_set(val); 61384910c45SDerek J. Clark if (ret) 61484910c45SDerek J. Clark return ret; 61584910c45SDerek J. Clark 61684910c45SDerek J. Clark return count; 61784910c45SDerek J. Clark } 61884910c45SDerek J. Clark 61984910c45SDerek J. Clark static ssize_t effect_show(struct device *dev, struct device_attribute *attr, 62084910c45SDerek J. Clark char *buf) 62184910c45SDerek J. Clark { 62284910c45SDerek J. Clark int ret; 62384910c45SDerek J. Clark 62484910c45SDerek J. Clark ret = oxp_rgb_status_show(); 62584910c45SDerek J. Clark if (ret) 62684910c45SDerek J. Clark return ret; 62784910c45SDerek J. Clark 62884910c45SDerek J. Clark if (drvdata.rgb_effect >= ARRAY_SIZE(oxp_rgb_effect_text)) 62984910c45SDerek J. Clark return -EINVAL; 63084910c45SDerek J. Clark 63184910c45SDerek J. Clark return sysfs_emit(buf, "%s\n", oxp_rgb_effect_text[drvdata.rgb_effect]); 63284910c45SDerek J. Clark } 63384910c45SDerek J. Clark 63484910c45SDerek J. Clark static DEVICE_ATTR_RW(effect); 63584910c45SDerek J. Clark 63684910c45SDerek J. Clark static ssize_t effect_index_show(struct device *dev, 63784910c45SDerek J. Clark struct device_attribute *attr, char *buf) 63884910c45SDerek J. Clark { 63984910c45SDerek J. Clark size_t count = 0; 64084910c45SDerek J. Clark unsigned int i; 64184910c45SDerek J. Clark 64284910c45SDerek J. Clark for (i = 1; i < ARRAY_SIZE(oxp_rgb_effect_text); i++) 64384910c45SDerek J. Clark count += sysfs_emit_at(buf, count, "%s ", oxp_rgb_effect_text[i]); 64484910c45SDerek J. Clark 64584910c45SDerek J. Clark if (count) 64684910c45SDerek J. Clark buf[count - 1] = '\n'; 64784910c45SDerek J. Clark 64884910c45SDerek J. Clark return count; 64984910c45SDerek J. Clark } 65084910c45SDerek J. Clark static DEVICE_ATTR_RO(effect_index); 65184910c45SDerek J. Clark 65284910c45SDerek J. Clark static ssize_t speed_store(struct device *dev, struct device_attribute *attr, 65384910c45SDerek J. Clark const char *buf, size_t count) 65484910c45SDerek J. Clark { 65584910c45SDerek J. Clark int ret; 65684910c45SDerek J. Clark u8 val; 65784910c45SDerek J. Clark 65884910c45SDerek J. Clark ret = kstrtou8(buf, 10, &val); 65984910c45SDerek J. Clark if (ret) 66084910c45SDerek J. Clark return ret; 66184910c45SDerek J. Clark 66284910c45SDerek J. Clark if (val > 9) 66384910c45SDerek J. Clark return -EINVAL; 66484910c45SDerek J. Clark 66584910c45SDerek J. Clark ret = oxp_rgb_status_store(drvdata.rgb_en, val, drvdata.rgb_brightness); 66684910c45SDerek J. Clark if (ret) 66784910c45SDerek J. Clark return ret; 66884910c45SDerek J. Clark 66984910c45SDerek J. Clark drvdata.rgb_speed = val; 67084910c45SDerek J. Clark return count; 67184910c45SDerek J. Clark } 67284910c45SDerek J. Clark 67384910c45SDerek J. Clark static ssize_t speed_show(struct device *dev, struct device_attribute *attr, 67484910c45SDerek J. Clark char *buf) 67584910c45SDerek J. Clark { 67684910c45SDerek J. Clark int ret; 67784910c45SDerek J. Clark 67884910c45SDerek J. Clark ret = oxp_rgb_status_show(); 67984910c45SDerek J. Clark if (ret) 68084910c45SDerek J. Clark return ret; 68184910c45SDerek J. Clark 68284910c45SDerek J. Clark if (drvdata.rgb_speed > 9) 68384910c45SDerek J. Clark return -EINVAL; 68484910c45SDerek J. Clark 68584910c45SDerek J. Clark return sysfs_emit(buf, "%hhu\n", drvdata.rgb_speed); 68684910c45SDerek J. Clark } 68784910c45SDerek J. Clark static DEVICE_ATTR_RW(speed); 68884910c45SDerek J. Clark 68984910c45SDerek J. Clark static ssize_t speed_range_show(struct device *dev, 69084910c45SDerek J. Clark struct device_attribute *attr, char *buf) 69184910c45SDerek J. Clark { 69284910c45SDerek J. Clark return sysfs_emit(buf, "0-9\n"); 69384910c45SDerek J. Clark } 69484910c45SDerek J. Clark static DEVICE_ATTR_RO(speed_range); 69584910c45SDerek J. Clark 69684910c45SDerek J. Clark static void oxp_rgb_queue_fn(struct work_struct *work) 69784910c45SDerek J. Clark { 69884910c45SDerek J. Clark unsigned int max_brightness = drvdata.led_mc->led_cdev.max_brightness; 69984910c45SDerek J. Clark unsigned int brightness = drvdata.led_mc->led_cdev.brightness; 70084910c45SDerek J. Clark u8 val = 4 * brightness / max_brightness; 70184910c45SDerek J. Clark int ret; 70284910c45SDerek J. Clark 70384910c45SDerek J. Clark if (drvdata.rgb_brightness != val) { 70484910c45SDerek J. Clark ret = oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed, val); 70584910c45SDerek J. Clark if (ret) 70684910c45SDerek J. Clark dev_err(drvdata.led_mc->led_cdev.dev, 70784910c45SDerek J. Clark "Error: Failed to write RGB Status: %i\n", ret); 70884910c45SDerek J. Clark 70984910c45SDerek J. Clark drvdata.rgb_brightness = val; 71084910c45SDerek J. Clark } 71184910c45SDerek J. Clark 71284910c45SDerek J. Clark if (drvdata.rgb_effect != OXP_EFFECT_MONO_LIST) 71384910c45SDerek J. Clark return; 71484910c45SDerek J. Clark 71584910c45SDerek J. Clark ret = oxp_rgb_effect_set(drvdata.rgb_effect); 71684910c45SDerek J. Clark if (ret) 71784910c45SDerek J. Clark dev_err(drvdata.led_mc->led_cdev.dev, "Error: Failed to write RGB color: %i\n", 71884910c45SDerek J. Clark ret); 71984910c45SDerek J. Clark } 72084910c45SDerek J. Clark 72184910c45SDerek J. Clark static void oxp_rgb_brightness_set(struct led_classdev *led_cdev, 72284910c45SDerek J. Clark enum led_brightness brightness) 72384910c45SDerek J. Clark { 72484910c45SDerek J. Clark led_cdev->brightness = brightness; 72584910c45SDerek J. Clark mod_delayed_work(system_wq, &drvdata.oxp_rgb_queue, msecs_to_jiffies(50)); 72684910c45SDerek J. Clark } 72784910c45SDerek J. Clark 72884910c45SDerek J. Clark static struct attribute *oxp_rgb_attrs[] = { 72984910c45SDerek J. Clark &dev_attr_effect.attr, 73084910c45SDerek J. Clark &dev_attr_effect_index.attr, 73184910c45SDerek J. Clark &dev_attr_enabled.attr, 73284910c45SDerek J. Clark &dev_attr_enabled_index.attr, 73384910c45SDerek J. Clark &dev_attr_speed.attr, 73484910c45SDerek J. Clark &dev_attr_speed_range.attr, 73584910c45SDerek J. Clark NULL, 73684910c45SDerek J. Clark }; 73784910c45SDerek J. Clark 73884910c45SDerek J. Clark static const struct attribute_group oxp_rgb_attr_group = { 73984910c45SDerek J. Clark .attrs = oxp_rgb_attrs, 74084910c45SDerek J. Clark }; 74184910c45SDerek J. Clark 74284910c45SDerek J. Clark static struct mc_subled oxp_rgb_subled_info[] = { 74384910c45SDerek J. Clark { 74484910c45SDerek J. Clark .color_index = LED_COLOR_ID_RED, 74584910c45SDerek J. Clark .intensity = 0x24, 74684910c45SDerek J. Clark .channel = 0x1, 74784910c45SDerek J. Clark }, 74884910c45SDerek J. Clark { 74984910c45SDerek J. Clark .color_index = LED_COLOR_ID_GREEN, 75084910c45SDerek J. Clark .intensity = 0x22, 75184910c45SDerek J. Clark .channel = 0x2, 75284910c45SDerek J. Clark }, 75384910c45SDerek J. Clark { 75484910c45SDerek J. Clark .color_index = LED_COLOR_ID_BLUE, 75584910c45SDerek J. Clark .intensity = 0x99, 75684910c45SDerek J. Clark .channel = 0x3, 75784910c45SDerek J. Clark }, 75884910c45SDerek J. Clark }; 75984910c45SDerek J. Clark 76084910c45SDerek J. Clark static struct led_classdev_mc oxp_cdev_rgb = { 76184910c45SDerek J. Clark .led_cdev = { 76284910c45SDerek J. Clark .name = "oxp:rgb:joystick_rings", 76384910c45SDerek J. Clark .color = LED_COLOR_ID_RGB, 76484910c45SDerek J. Clark .brightness = 0x64, 76584910c45SDerek J. Clark .max_brightness = 0x64, 76684910c45SDerek J. Clark .brightness_set = oxp_rgb_brightness_set, 76784910c45SDerek J. Clark }, 76884910c45SDerek J. Clark .num_colors = ARRAY_SIZE(oxp_rgb_subled_info), 76984910c45SDerek J. Clark .subled_info = oxp_rgb_subled_info, 77084910c45SDerek J. Clark }; 77184910c45SDerek J. Clark 772252c4bf1SDerek J. Clark struct quirk_entry { 773252c4bf1SDerek J. Clark bool hybrid_mcu; 774252c4bf1SDerek J. Clark }; 775252c4bf1SDerek J. Clark 776252c4bf1SDerek J. Clark static struct quirk_entry quirk_hybrid_mcu = { 777252c4bf1SDerek J. Clark .hybrid_mcu = true, 778252c4bf1SDerek J. Clark }; 779252c4bf1SDerek J. Clark 780252c4bf1SDerek J. Clark static const struct dmi_system_id oxp_hybrid_mcu_list[] = { 781252c4bf1SDerek J. Clark { 782252c4bf1SDerek J. Clark .ident = "OneXPlayer Apex", 783252c4bf1SDerek J. Clark .matches = { 784252c4bf1SDerek J. Clark DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), 785252c4bf1SDerek J. Clark DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER APEX"), 786252c4bf1SDerek J. Clark }, 787252c4bf1SDerek J. Clark .driver_data = &quirk_hybrid_mcu, 788252c4bf1SDerek J. Clark }, 789252c4bf1SDerek J. Clark { 790252c4bf1SDerek J. Clark .ident = "OneXPlayer G1 AMD", 791252c4bf1SDerek J. Clark .matches = { 792252c4bf1SDerek J. Clark DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), 793252c4bf1SDerek J. Clark DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 A"), 794252c4bf1SDerek J. Clark }, 795252c4bf1SDerek J. Clark .driver_data = &quirk_hybrid_mcu, 796252c4bf1SDerek J. Clark }, 797252c4bf1SDerek J. Clark { 798252c4bf1SDerek J. Clark .ident = "OneXPlayer G1 Intel", 799252c4bf1SDerek J. Clark .matches = { 800252c4bf1SDerek J. Clark DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), 801252c4bf1SDerek J. Clark DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 i"), 802252c4bf1SDerek J. Clark }, 803252c4bf1SDerek J. Clark .driver_data = &quirk_hybrid_mcu, 804252c4bf1SDerek J. Clark }, 805252c4bf1SDerek J. Clark {}, 806252c4bf1SDerek J. Clark }; 807252c4bf1SDerek J. Clark 808252c4bf1SDerek J. Clark static bool oxp_hybrid_mcu_device(void) 809252c4bf1SDerek J. Clark { 810252c4bf1SDerek J. Clark const struct dmi_system_id *dmi_id; 811252c4bf1SDerek J. Clark struct quirk_entry *quirks; 812252c4bf1SDerek J. Clark 813252c4bf1SDerek J. Clark dmi_id = dmi_first_match(oxp_hybrid_mcu_list); 814252c4bf1SDerek J. Clark if (!dmi_id) 815252c4bf1SDerek J. Clark return false; 816252c4bf1SDerek J. Clark 817252c4bf1SDerek J. Clark quirks = dmi_id->driver_data; 818252c4bf1SDerek J. Clark 819252c4bf1SDerek J. Clark return quirks->hybrid_mcu; 820252c4bf1SDerek J. Clark } 821252c4bf1SDerek J. Clark 82284910c45SDerek J. Clark static int oxp_cfg_probe(struct hid_device *hdev, u16 up) 82384910c45SDerek J. Clark { 82484910c45SDerek J. Clark int ret; 82584910c45SDerek J. Clark 82684910c45SDerek J. Clark hid_set_drvdata(hdev, &drvdata); 82784910c45SDerek J. Clark mutex_init(&drvdata.cfg_mutex); 82884910c45SDerek J. Clark drvdata.hdev = hdev; 829252c4bf1SDerek J. Clark 830252c4bf1SDerek J. Clark if (up == GEN2_USAGE_PAGE && oxp_hybrid_mcu_device()) 831252c4bf1SDerek J. Clark goto skip_rgb; 832252c4bf1SDerek J. Clark 83384910c45SDerek J. Clark drvdata.led_mc = &oxp_cdev_rgb; 83484910c45SDerek J. Clark 83584910c45SDerek J. Clark INIT_DELAYED_WORK(&drvdata.oxp_rgb_queue, oxp_rgb_queue_fn); 83684910c45SDerek J. Clark ret = devm_led_classdev_multicolor_register(&hdev->dev, &oxp_cdev_rgb); 83784910c45SDerek J. Clark if (ret) 83884910c45SDerek J. Clark return dev_err_probe(&hdev->dev, ret, 83984910c45SDerek J. Clark "Failed to create RGB device\n"); 84084910c45SDerek J. Clark 84184910c45SDerek J. Clark ret = devm_device_add_group(drvdata.led_mc->led_cdev.dev, 84284910c45SDerek J. Clark &oxp_rgb_attr_group); 84384910c45SDerek J. Clark if (ret) 84484910c45SDerek J. Clark return dev_err_probe(drvdata.led_mc->led_cdev.dev, ret, 84584910c45SDerek J. Clark "Failed to create RGB configuration attributes\n"); 84684910c45SDerek J. Clark 84784910c45SDerek J. Clark ret = oxp_rgb_status_show(); 84884910c45SDerek J. Clark if (ret) 84984910c45SDerek J. Clark dev_warn(drvdata.led_mc->led_cdev.dev, 85084910c45SDerek J. Clark "Failed to query RGB initial state: %i\n", ret); 85184910c45SDerek J. Clark 852*2f424f28SDerek J. Clark /* Below features are only implemented in gen 2 */ 853*2f424f28SDerek J. Clark if (up != GEN2_USAGE_PAGE) 854*2f424f28SDerek J. Clark return 0; 855*2f424f28SDerek J. Clark 856252c4bf1SDerek J. Clark skip_rgb: 857*2f424f28SDerek J. Clark drvdata.gamepad_mode = OXP_GP_MODE_XINPUT; 858*2f424f28SDerek J. Clark 859*2f424f28SDerek J. Clark INIT_DELAYED_WORK(&drvdata.oxp_mcu_init, oxp_mcu_init_fn); 860*2f424f28SDerek J. Clark mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50)); 861*2f424f28SDerek J. Clark 862*2f424f28SDerek J. Clark ret = devm_device_add_group(&hdev->dev, &oxp_cfg_attrs_group); 863*2f424f28SDerek J. Clark if (ret) 864*2f424f28SDerek J. Clark return dev_err_probe(&hdev->dev, ret, 865*2f424f28SDerek J. Clark "Failed to attach configuration attributes\n"); 866*2f424f28SDerek J. Clark 86784910c45SDerek J. Clark return 0; 86884910c45SDerek J. Clark } 86984910c45SDerek J. Clark 87084910c45SDerek J. Clark static int oxp_hid_probe(struct hid_device *hdev, 87184910c45SDerek J. Clark const struct hid_device_id *id) 87284910c45SDerek J. Clark { 87384910c45SDerek J. Clark int ret; 87484910c45SDerek J. Clark u16 up; 87584910c45SDerek J. Clark 87684910c45SDerek J. Clark ret = hid_parse(hdev); 87784910c45SDerek J. Clark if (ret) 87884910c45SDerek J. Clark return dev_err_probe(&hdev->dev, ret, "Failed to parse HID device\n"); 87984910c45SDerek J. Clark 88084910c45SDerek J. Clark ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); 88184910c45SDerek J. Clark if (ret) 88284910c45SDerek J. Clark return dev_err_probe(&hdev->dev, ret, "Failed to start HID device\n"); 88384910c45SDerek J. Clark 88484910c45SDerek J. Clark ret = hid_hw_open(hdev); 88584910c45SDerek J. Clark if (ret) { 88684910c45SDerek J. Clark hid_hw_stop(hdev); 88784910c45SDerek J. Clark return dev_err_probe(&hdev->dev, ret, "Failed to open HID device\n"); 88884910c45SDerek J. Clark } 88984910c45SDerek J. Clark 89084910c45SDerek J. Clark up = get_usage_page(hdev); 89184910c45SDerek J. Clark dev_dbg(&hdev->dev, "Got usage page %04x\n", up); 89284910c45SDerek J. Clark 89384910c45SDerek J. Clark switch (up) { 89484910c45SDerek J. Clark case GEN1_USAGE_PAGE: 895252c4bf1SDerek J. Clark case GEN2_USAGE_PAGE: 89684910c45SDerek J. Clark ret = oxp_cfg_probe(hdev, up); 89784910c45SDerek J. Clark if (ret) { 89884910c45SDerek J. Clark hid_hw_close(hdev); 89984910c45SDerek J. Clark hid_hw_stop(hdev); 90084910c45SDerek J. Clark } 90184910c45SDerek J. Clark 90284910c45SDerek J. Clark return ret; 90384910c45SDerek J. Clark default: 90484910c45SDerek J. Clark return 0; 90584910c45SDerek J. Clark } 90684910c45SDerek J. Clark } 90784910c45SDerek J. Clark 90884910c45SDerek J. Clark static void oxp_hid_remove(struct hid_device *hdev) 90984910c45SDerek J. Clark { 91084910c45SDerek J. Clark cancel_delayed_work(&drvdata.oxp_rgb_queue); 911*2f424f28SDerek J. Clark cancel_delayed_work(&drvdata.oxp_mcu_init); 91284910c45SDerek J. Clark hid_hw_close(hdev); 91384910c45SDerek J. Clark hid_hw_stop(hdev); 91484910c45SDerek J. Clark } 91584910c45SDerek J. Clark 91684910c45SDerek J. Clark static const struct hid_device_id oxp_devices[] = { 91784910c45SDerek J. Clark { HID_USB_DEVICE(USB_VENDOR_ID_CRSC, USB_DEVICE_ID_ONEXPLAYER_GEN1) }, 918252c4bf1SDerek J. Clark { HID_USB_DEVICE(USB_VENDOR_ID_WCH, USB_DEVICE_ID_ONEXPLAYER_GEN2) }, 91984910c45SDerek J. Clark {} 92084910c45SDerek J. Clark }; 92184910c45SDerek J. Clark 92284910c45SDerek J. Clark MODULE_DEVICE_TABLE(hid, oxp_devices); 92384910c45SDerek J. Clark static struct hid_driver hid_oxp = { 92484910c45SDerek J. Clark .name = "hid-oxp", 92584910c45SDerek J. Clark .id_table = oxp_devices, 92684910c45SDerek J. Clark .probe = oxp_hid_probe, 92784910c45SDerek J. Clark .remove = oxp_hid_remove, 92884910c45SDerek J. Clark .raw_event = oxp_hid_raw_event, 92984910c45SDerek J. Clark }; 93084910c45SDerek J. Clark module_hid_driver(hid_oxp); 93184910c45SDerek J. Clark 93284910c45SDerek J. Clark MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); 93384910c45SDerek J. Clark MODULE_DESCRIPTION("Driver for OneXPlayer HID Interfaces"); 93484910c45SDerek J. Clark MODULE_LICENSE("GPL"); 935