diff options
| author | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 09:55:01 -0400 |
|---|---|---|
| committer | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 11:08:21 -0400 |
| commit | 4617721c502b2ddaa4e324e86da4997edf738fa5 (patch) | |
| tree | 350aa83b47da0b574a814795392b20b0bed83cbc /drivers/hid/hid-oxp.c | |
| download | linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.tar.gz linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.zip | |
ftrace: Synchronize the initialization of ftrace_opsgrafted
There's some internal state that ftrace_ops needs to have set, but since
it can be declared outside of the ftrace.c code, it calls
ftrace_ops_init() on the ops in every global function. The issue is that
if two tasks call it on the same ops at the same time it is possible to
have the initialization of one corrupt the initialization of the other
call.
Create a ops_mutex to use to synchronize every initialization of the
ftrace_ops. The mutex is taken within checking the ftrace_ops flag that
states it was initializied but the flag is checked again after the mutex
has been taken. Checking first outside the mutex allows it to shortcut
having to take the mutex. But then the check needs to be done again after
the mute is taken in case of races.
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home
Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock")
Reported-by: sashiko-bot@kernel.org
Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'drivers/hid/hid-oxp.c')
| -rw-r--r-- | drivers/hid/hid-oxp.c | 1580 |
1 files changed, 1580 insertions, 0 deletions
diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c new file mode 100644 index 000000000..d2ded6b08 --- /dev/null +++ b/drivers/hid/hid-oxp.c @@ -0,0 +1,1580 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for OneXPlayer gamepad configuration devices. + * + * Copyright (c) 2026 Valve Corporation + */ + +#include <linux/array_size.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/device.h> +#include <linux/dmi.h> +#include <linux/hid.h> +#include <linux/jiffies.h> +#include <linux/kstrtox.h> +#include <linux/led-class-multicolor.h> +#include <linux/mutex.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <linux/workqueue.h> + +#include "hid-ids.h" + +#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_TOGGLE_MODE = 0xb2, + OXP_FID_GEN2_RUMBLE_SET = 0xb3, + OXP_FID_GEN2_KEY_STATE = 0xb4, + OXP_FID_GEN2_STATUS_EVENT = 0xb8, +}; + +#define OXP_MAPPING_GAMEPAD 0x01 +#define OXP_MAPPING_KEYBOARD 0x02 + +struct oxp_button_data { + u8 mode; + u8 index; + u8 key_id; + u8 padding[2]; +} __packed; + +struct oxp_button_entry { + struct oxp_button_data data; + const char *name; +}; + +static const struct oxp_button_entry oxp_button_table[] = { + /* Gamepad Buttons */ + { { OXP_MAPPING_GAMEPAD, 0x01 }, "BTN_A" }, + { { OXP_MAPPING_GAMEPAD, 0x02 }, "BTN_B" }, + { { OXP_MAPPING_GAMEPAD, 0x03 }, "BTN_X" }, + { { OXP_MAPPING_GAMEPAD, 0x04 }, "BTN_Y" }, + { { OXP_MAPPING_GAMEPAD, 0x05 }, "BTN_LB" }, + { { OXP_MAPPING_GAMEPAD, 0x06 }, "BTN_RB" }, + { { OXP_MAPPING_GAMEPAD, 0x07 }, "BTN_LT" }, + { { OXP_MAPPING_GAMEPAD, 0x08 }, "BTN_RT" }, + { { OXP_MAPPING_GAMEPAD, 0x09 }, "BTN_START" }, + { { OXP_MAPPING_GAMEPAD, 0x0a }, "BTN_SELECT" }, + { { OXP_MAPPING_GAMEPAD, 0x0b }, "BTN_L3" }, + { { OXP_MAPPING_GAMEPAD, 0x0c }, "BTN_R3" }, + { { OXP_MAPPING_GAMEPAD, 0x0d }, "DPAD_UP" }, + { { OXP_MAPPING_GAMEPAD, 0x0e }, "DPAD_DOWN" }, + { { OXP_MAPPING_GAMEPAD, 0x0f }, "DPAD_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x10 }, "DPAD_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x11 }, "JOY_L_UP" }, + { { OXP_MAPPING_GAMEPAD, 0x12 }, "JOY_L_UP_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x13 }, "JOY_L_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x14 }, "JOY_L_DOWN_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x15 }, "JOY_L_DOWN" }, + { { OXP_MAPPING_GAMEPAD, 0x16 }, "JOY_L_DOWN_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x17 }, "JOY_L_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x18 }, "JOY_L_UP_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x19 }, "JOY_R_UP" }, + { { OXP_MAPPING_GAMEPAD, 0x1a }, "JOY_R_UP_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x1b }, "JOY_R_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x1c }, "JOY_R_DOWN_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x1d }, "JOY_R_DOWN" }, + { { OXP_MAPPING_GAMEPAD, 0x1e }, "JOY_R_DOWN_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x1f }, "JOY_R_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x20 }, "JOY_R_UP_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x22 }, "BTN_GUIDE" }, + /* Keyboard Keys */ + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5a }, "KEY_F1" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5b }, "KEY_F2" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5c }, "KEY_F3" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5d }, "KEY_F4" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5e }, "KEY_F5" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5f }, "KEY_F6" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x60 }, "KEY_F7" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x61 }, "KEY_F8" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x62 }, "KEY_F9" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x63 }, "KEY_F10" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x64 }, "KEY_F11" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x65 }, "KEY_F12" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x66 }, "KEY_F13" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x67 }, "KEY_F14" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x68 }, "KEY_F15" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x69 }, "KEY_F16" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6a }, "KEY_F17" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6b }, "KEY_F18" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6c }, "KEY_F19" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6d }, "KEY_F20" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6e }, "KEY_F21" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6f }, "KEY_F22" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x70 }, "KEY_F23" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x71 }, "KEY_F24" }, +}; + +enum oxp_joybutton_index { + BUTTON_A = 0x01, + BUTTON_B, + BUTTON_X, + BUTTON_Y, + BUTTON_LB, + BUTTON_RB, + BUTTON_LT, + BUTTON_RT, + BUTTON_START, + BUTTON_SELECT, + BUTTON_L3, + BUTTON_R3, + BUTTON_DUP, + BUTTON_DDOWN, + BUTTON_DLEFT, + BUTTON_DRIGHT, + BUTTON_M1 = 0x22, + BUTTON_M2, + /* These are unused currently, reserved for future devices */ + BUTTON_M3, + BUTTON_M4, + BUTTON_M5, + BUTTON_M6, +}; + +struct oxp_button_idx { + enum oxp_joybutton_index button_idx; + u8 mapping_idx; +} __packed; + +struct oxp_bmap_page_1 { + struct oxp_button_idx btn_a; + struct oxp_button_idx btn_b; + struct oxp_button_idx btn_x; + struct oxp_button_idx btn_y; + struct oxp_button_idx btn_lb; + struct oxp_button_idx btn_rb; + struct oxp_button_idx btn_lt; + struct oxp_button_idx btn_rt; + struct oxp_button_idx btn_start; +} __packed; + +struct oxp_bmap_page_2 { + struct oxp_button_idx btn_select; + struct oxp_button_idx btn_l3; + struct oxp_button_idx btn_r3; + struct oxp_button_idx btn_dup; + struct oxp_button_idx btn_ddown; + struct oxp_button_idx btn_dleft; + struct oxp_button_idx btn_dright; + struct oxp_button_idx btn_m1; + struct oxp_button_idx btn_m2; +} __packed; + +static struct oxp_hid_cfg { + struct delayed_work oxp_rgb_queue; + struct delayed_work oxp_btn_queue; + struct oxp_bmap_page_1 *bmap_1; + struct oxp_bmap_page_2 *bmap_2; + struct delayed_work oxp_mcu_init; + struct led_classdev_mc *led_mc; + struct hid_device *hdev; + struct mutex cfg_mutex; /*ensure single synchronous output report*/ + u8 rgb_brightness; + u8 gamepad_mode; + u8 rumble_intensity; + u8 rgb_effect; + u8 rgb_speed; + u8 rgb_en; +} drvdata; + +#define OXP_FILL_PAGE_SLOT(page, btn) \ + { .button_idx = (page)->btn.button_idx, \ + .mapping_idx = (page)->btn.mapping_idx } + +enum oxp_gamepad_mode_index { + OXP_GP_MODE_XINPUT = 0x00, + OXP_GP_MODE_DEBUG = 0x03, +}; + +static const char *const oxp_gamepad_mode_text[] = { + [OXP_GP_MODE_XINPUT] = "xinput", + [OXP_GP_MODE_DEBUG] = "debug", +}; + +enum oxp_feature_en_index { + OXP_FEAT_DISABLED, + OXP_FEAT_ENABLED, +}; + +static const char *const oxp_feature_en_text[] = { + [OXP_FEAT_DISABLED] = "false", + [OXP_FEAT_ENABLED] = "true", +}; + +enum oxp_rgb_effect_index { + OXP_UNKNOWN, + OXP_EFFECT_AURORA, + OXP_EFFECT_BIRTHDAY, + OXP_EFFECT_FLOWING, + OXP_EFFECT_CHROMA_1, + OXP_EFFECT_NEON, + OXP_EFFECT_CHROMA_2, + OXP_EFFECT_DREAMY, + OXP_EFFECT_WARM, + OXP_EFFECT_CYBERPUNK, + OXP_EFFECT_SEA, + OXP_EFFECT_SUNSET, + OXP_EFFECT_COLORFUL, + OXP_EFFECT_MONSTER, + OXP_EFFECT_GREEN, + OXP_EFFECT_BLUE, + OXP_EFFECT_YELLOW, + OXP_EFFECT_TEAL, + OXP_EFFECT_PURPLE, + OXP_EFFECT_FOGGY, + OXP_EFFECT_MONO_LIST, /* placeholder for effect_index_show */ +}; + +/* These belong to rgb_effect_index, but we want to hide them from + * rgb_effect_text + */ + +#define OXP_GET_PROPERTY 0xfc +#define OXP_SET_PROPERTY 0xfd +#define OXP_EFFECT_MONO_TRUE 0xfe /* actual index for monocolor */ + +static const char *const oxp_rgb_effect_text[] = { + [OXP_UNKNOWN] = "unknown", + [OXP_EFFECT_AURORA] = "aurora", + [OXP_EFFECT_BIRTHDAY] = "birthday_cake", + [OXP_EFFECT_FLOWING] = "flowing_light", + [OXP_EFFECT_CHROMA_1] = "chroma_popping", + [OXP_EFFECT_NEON] = "neon", + [OXP_EFFECT_CHROMA_2] = "chroma_breathing", + [OXP_EFFECT_DREAMY] = "dreamy", + [OXP_EFFECT_WARM] = "warm_sun", + [OXP_EFFECT_CYBERPUNK] = "cyberpunk", + [OXP_EFFECT_SEA] = "sea_foam", + [OXP_EFFECT_SUNSET] = "sunset_afterglow", + [OXP_EFFECT_COLORFUL] = "colorful", + [OXP_EFFECT_MONSTER] = "monster_woke", + [OXP_EFFECT_GREEN] = "green_breathing", + [OXP_EFFECT_BLUE] = "blue_breathing", + [OXP_EFFECT_YELLOW] = "yellow_breathing", + [OXP_EFFECT_TEAL] = "teal_breathing", + [OXP_EFFECT_PURPLE] = "purple_breathing", + [OXP_EFFECT_FOGGY] = "foggy_haze", + [OXP_EFFECT_MONO_LIST] = "monocolor", +}; + +enum oxp_rumble_side_index { + OXP_RUMBLE_LEFT = 0x00, + OXP_RUMBLE_RIGHT, +}; + +struct oxp_gen_1_rgb_report { + u8 report_id; + u8 message_id; + u8 padding_2[2]; + u8 effect; + u8 enabled; + u8 speed; + u8 brightness; + u8 red; + u8 green; + 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; + +struct oxp_attr { + u8 index; +}; + +static u16 get_usage_page(struct hid_device *hdev) +{ + return hdev->collection[0].usage >> 16; +} + +static int oxp_hid_raw_event_gen_1(struct hid_device *hdev, + struct hid_report *report, u8 *data, + int size) +{ + struct led_classdev_mc *led_mc = drvdata.led_mc; + struct oxp_gen_1_rgb_report *rgb_rep; + + if (data[1] != OXP_FID_GEN1_RGB_REPLY) + return 0; + + rgb_rep = (struct oxp_gen_1_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_gen_2_property_out(enum oxp_function_index fid, u8 *data, u8 data_size); +static int oxp_set_buttons(void); +static int oxp_rumble_intensity_set(u8 intensity); + +static void oxp_mcu_init_fn(struct work_struct *work) +{ + u8 gp_mode_data[3] = { OXP_GP_MODE_DEBUG, 0x01, 0x02 }; + int ret; + + /* Re-apply the button mapping */ + ret = oxp_set_buttons(); + if (ret) + dev_err(&drvdata.hdev->dev, + "Error: Failed to set button mapping: %i\n", ret); + + /* Cycle the gamepad mode */ + ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3); + if (ret) + dev_err(&drvdata.hdev->dev, + "Error: Failed to set gamepad mode: %i\n", ret); + + /* Remainder only applies for xinput mode */ + if (drvdata.gamepad_mode == OXP_GP_MODE_DEBUG) + return; + + gp_mode_data[0] = OXP_GP_MODE_XINPUT; + ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3); + if (ret) + dev_err(&drvdata.hdev->dev, + "Error: Failed to set gamepad mode: %i\n", ret); + + /* Set vibration level */ + ret = oxp_rumble_intensity_set(drvdata.rumble_intensity); + if (ret) + dev_err(&drvdata.hdev->dev, + "Error: Failed to set rumble intensity: %i\n", ret); +} + +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; + + /* Sent ~6s after resume event, indicating the MCU has fully reset. + * Re-apply our settings after this has been received. + */ + if (data[3] == OXP_EFFECT_MONO_TRUE) { + mod_delayed_work(system_dfl_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50)); + 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) +{ + u16 up = get_usage_page(hdev); + + dev_dbg(&hdev->dev, "raw event data: [%*ph]\n", OXP_PACKET_SIZE, data); + + 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; + } + + return 0; +} + +static int mcu_property_out(u8 *header, size_t header_size, u8 *data, + size_t data_size, u8 *footer, size_t footer_size) +{ + unsigned char *dmabuf __free(kfree) = kzalloc(OXP_PACKET_SIZE, GFP_KERNEL); + int ret; + + if (!dmabuf) + return -ENOMEM; + + if (header_size + data_size + footer_size > OXP_PACKET_SIZE) + return -EINVAL; + + guard(mutex)(&drvdata.cfg_mutex); + memcpy(dmabuf, header, header_size); + memcpy(dmabuf + header_size, data, data_size); + if (footer_size) + memcpy(dmabuf + OXP_PACKET_SIZE - footer_size, footer, footer_size); + + dev_dbg(&drvdata.hdev->dev, "raw data: [%*ph]\n", OXP_PACKET_SIZE, dmabuf); + + ret = hid_hw_output_report(drvdata.hdev, dmabuf, OXP_PACKET_SIZE); + if (ret < 0) + return ret; + + /* MCU takes 200ms to be ready for another command. */ + msleep(200); + return ret == OXP_PACKET_SIZE ? 0 : -EIO; +} + +static int oxp_gen_1_property_out(enum oxp_function_index fid, u8 *data, + u8 data_size) +{ + u8 header[] = { fid, GEN1_MESSAGE_ID }; + size_t header_size = ARRAY_SIZE(header); + + 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 ssize_t gamepad_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + u16 up = get_usage_page(drvdata.hdev); + u8 data[3] = { 0x00, 0x01, 0x02 }; + int ret = -EINVAL; + int i; + + if (up != GEN2_USAGE_PAGE) + return ret; + + for (i = 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) { + if (oxp_gamepad_mode_text[i] && sysfs_streq(buf, oxp_gamepad_mode_text[i])) { + ret = i; + break; + } + } + if (ret < 0) + return ret; + + data[0] = ret; + + ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, data, 3); + if (ret) + return ret; + + drvdata.gamepad_mode = data[0]; + + if (drvdata.gamepad_mode == OXP_GP_MODE_DEBUG) + return count; + + /* Re-apply rumble settings as switching gamepad mode will override */ + ret = oxp_rumble_intensity_set(drvdata.rumble_intensity); + if (ret) + return ret; + + return count; +} + +static ssize_t gamepad_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%s\n", oxp_gamepad_mode_text[drvdata.gamepad_mode]); +} +static DEVICE_ATTR_RW(gamepad_mode); + +static ssize_t gamepad_mode_index_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t count = 0; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) { + if (!oxp_gamepad_mode_text[i] || + oxp_gamepad_mode_text[i][0] == '\0') + continue; + + count += sysfs_emit_at(buf, count, "%s ", oxp_gamepad_mode_text[i]); + } + + if (count) + buf[count - 1] = '\n'; + + return count; +} +static DEVICE_ATTR_RO(gamepad_mode_index); + +static void oxp_set_defaults_bmap_1(struct oxp_bmap_page_1 *bmap) +{ + bmap->btn_a.button_idx = BUTTON_A; + bmap->btn_a.mapping_idx = 0; + bmap->btn_b.button_idx = BUTTON_B; + bmap->btn_b.mapping_idx = 1; + bmap->btn_x.button_idx = BUTTON_X; + bmap->btn_x.mapping_idx = 2; + bmap->btn_y.button_idx = BUTTON_Y; + bmap->btn_y.mapping_idx = 3; + bmap->btn_lb.button_idx = BUTTON_LB; + bmap->btn_lb.mapping_idx = 4; + bmap->btn_rb.button_idx = BUTTON_RB; + bmap->btn_rb.mapping_idx = 5; + bmap->btn_lt.button_idx = BUTTON_LT; + bmap->btn_lt.mapping_idx = 6; + bmap->btn_rt.button_idx = BUTTON_RT; + bmap->btn_rt.mapping_idx = 7; + bmap->btn_start.button_idx = BUTTON_START; + bmap->btn_start.mapping_idx = 8; +} + +static void oxp_set_defaults_bmap_2(struct oxp_bmap_page_2 *bmap) +{ + bmap->btn_select.button_idx = BUTTON_SELECT; + bmap->btn_select.mapping_idx = 9; + bmap->btn_l3.button_idx = BUTTON_L3; + bmap->btn_l3.mapping_idx = 10; + bmap->btn_r3.button_idx = BUTTON_R3; + bmap->btn_r3.mapping_idx = 11; + bmap->btn_dup.button_idx = BUTTON_DUP; + bmap->btn_dup.mapping_idx = 12; + bmap->btn_ddown.button_idx = BUTTON_DDOWN; + bmap->btn_ddown.mapping_idx = 13; + bmap->btn_dleft.button_idx = BUTTON_DLEFT; + bmap->btn_dleft.mapping_idx = 14; + bmap->btn_dright.button_idx = BUTTON_DRIGHT; + bmap->btn_dright.mapping_idx = 15; + bmap->btn_m1.button_idx = BUTTON_M1; + bmap->btn_m1.mapping_idx = 48; /* KEY_F15 */ + bmap->btn_m2.button_idx = BUTTON_M2; + bmap->btn_m2.mapping_idx = 49; /* KEY_F16 */ +} + +static void oxp_page_fill_data(char *buf, const struct oxp_button_idx *buttons, + size_t len) +{ + size_t offset_increment = sizeof(u8) + sizeof(struct oxp_button_idx); + size_t offset = 5; + unsigned int i; + + for (i = 0; i < len; i++, offset += offset_increment) { + buf[offset] = (u8)buttons[i].button_idx; + memcpy(buf + offset + 1, + &oxp_button_table[buttons[i].mapping_idx].data, + sizeof(struct oxp_button_data)); + } +} + +static int oxp_set_buttons(void) +{ + u8 page_1[59] = { 0x02, 0x38, 0x20, 0x01, 0x01 }; + u8 page_2[59] = { 0x02, 0x38, 0x20, 0x02, 0x01 }; + u16 up = get_usage_page(drvdata.hdev); + int ret; + + if (up != GEN2_USAGE_PAGE) + return -EINVAL; + + const struct oxp_button_idx p1[] = { + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_a), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_b), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_x), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_y), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_lb), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_rb), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_lt), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_rt), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_start), + }; + + const struct oxp_button_idx p2[] = { + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_select), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_l3), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_r3), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_dup), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_ddown), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_dleft), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_dright), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_m1), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_m2), + }; + + oxp_page_fill_data(page_1, p1, ARRAY_SIZE(p1)); + oxp_page_fill_data(page_2, p2, ARRAY_SIZE(p2)); + + ret = oxp_gen_2_property_out(OXP_FID_GEN2_KEY_STATE, page_1, ARRAY_SIZE(page_1)); + if (ret) + return ret; + + return oxp_gen_2_property_out(OXP_FID_GEN2_KEY_STATE, page_2, ARRAY_SIZE(page_2)); +} + +static void oxp_reset_buttons(void) +{ + oxp_set_defaults_bmap_1(drvdata.bmap_1); + oxp_set_defaults_bmap_2(drvdata.bmap_2); +} + +static ssize_t reset_buttons_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int val, ret; + + ret = kstrtoint(buf, 10, &val); + if (ret) + return ret; + + if (val != 1) + return -EINVAL; + + oxp_reset_buttons(); + ret = oxp_set_buttons(); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_WO(reset_buttons); + +static void oxp_btn_queue_fn(struct work_struct *work) +{ + int ret; + + ret = oxp_set_buttons(); + if (ret) + dev_err(&drvdata.hdev->dev, + "Error: Failed to write button mapping: %i\n", ret); +} + +static int oxp_button_idx_from_str(const char *buf) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(oxp_button_table); i++) + if (sysfs_streq(buf, oxp_button_table[i].name)) + return i; + + return -EINVAL; +} + +static ssize_t map_button_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count, u8 index) +{ + int idx; + + idx = oxp_button_idx_from_str(buf); + if (idx < 0) + return idx; + + switch (index) { + case BUTTON_A: + drvdata.bmap_1->btn_a.mapping_idx = idx; + break; + case BUTTON_B: + drvdata.bmap_1->btn_b.mapping_idx = idx; + break; + case BUTTON_X: + drvdata.bmap_1->btn_x.mapping_idx = idx; + break; + case BUTTON_Y: + drvdata.bmap_1->btn_y.mapping_idx = idx; + break; + case BUTTON_LB: + drvdata.bmap_1->btn_lb.mapping_idx = idx; + break; + case BUTTON_RB: + drvdata.bmap_1->btn_rb.mapping_idx = idx; + break; + case BUTTON_LT: + drvdata.bmap_1->btn_lt.mapping_idx = idx; + break; + case BUTTON_RT: + drvdata.bmap_1->btn_rt.mapping_idx = idx; + break; + case BUTTON_START: + drvdata.bmap_1->btn_start.mapping_idx = idx; + break; + case BUTTON_SELECT: + drvdata.bmap_2->btn_select.mapping_idx = idx; + break; + case BUTTON_L3: + drvdata.bmap_2->btn_l3.mapping_idx = idx; + break; + case BUTTON_R3: + drvdata.bmap_2->btn_r3.mapping_idx = idx; + break; + case BUTTON_DUP: + drvdata.bmap_2->btn_dup.mapping_idx = idx; + break; + case BUTTON_DDOWN: + drvdata.bmap_2->btn_ddown.mapping_idx = idx; + break; + case BUTTON_DLEFT: + drvdata.bmap_2->btn_dleft.mapping_idx = idx; + break; + case BUTTON_DRIGHT: + drvdata.bmap_2->btn_dright.mapping_idx = idx; + break; + case BUTTON_M1: + drvdata.bmap_2->btn_m1.mapping_idx = idx; + break; + case BUTTON_M2: + drvdata.bmap_2->btn_m2.mapping_idx = idx; + break; + default: + return -EINVAL; + } + mod_delayed_work(system_dfl_wq, &drvdata.oxp_btn_queue, msecs_to_jiffies(50)); + return count; +} + +static ssize_t map_button_show(struct device *dev, + struct device_attribute *attr, char *buf, + u8 index) +{ + u8 i; + + switch (index) { + case BUTTON_A: + i = drvdata.bmap_1->btn_a.mapping_idx; + break; + case BUTTON_B: + i = drvdata.bmap_1->btn_b.mapping_idx; + break; + case BUTTON_X: + i = drvdata.bmap_1->btn_x.mapping_idx; + break; + case BUTTON_Y: + i = drvdata.bmap_1->btn_y.mapping_idx; + break; + case BUTTON_LB: + i = drvdata.bmap_1->btn_lb.mapping_idx; + break; + case BUTTON_RB: + i = drvdata.bmap_1->btn_rb.mapping_idx; + break; + case BUTTON_LT: + i = drvdata.bmap_1->btn_lt.mapping_idx; + break; + case BUTTON_RT: + i = drvdata.bmap_1->btn_rt.mapping_idx; + break; + case BUTTON_START: + i = drvdata.bmap_1->btn_start.mapping_idx; + break; + case BUTTON_SELECT: + i = drvdata.bmap_2->btn_select.mapping_idx; + break; + case BUTTON_L3: + i = drvdata.bmap_2->btn_l3.mapping_idx; + break; + case BUTTON_R3: + i = drvdata.bmap_2->btn_r3.mapping_idx; + break; + case BUTTON_DUP: + i = drvdata.bmap_2->btn_dup.mapping_idx; + break; + case BUTTON_DDOWN: + i = drvdata.bmap_2->btn_ddown.mapping_idx; + break; + case BUTTON_DLEFT: + i = drvdata.bmap_2->btn_dleft.mapping_idx; + break; + case BUTTON_DRIGHT: + i = drvdata.bmap_2->btn_dright.mapping_idx; + break; + case BUTTON_M1: + i = drvdata.bmap_2->btn_m1.mapping_idx; + break; + case BUTTON_M2: + i = drvdata.bmap_2->btn_m2.mapping_idx; + break; + default: + return -EINVAL; + } + + if (i >= ARRAY_SIZE(oxp_button_table)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", oxp_button_table[i].name); +} + +static ssize_t button_mapping_options_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(oxp_button_table); i++) + count += sysfs_emit_at(buf, count, "%s ", oxp_button_table[i].name); + + if (count) + buf[count - 1] = '\n'; + + return count; +} +static DEVICE_ATTR_RO(button_mapping_options); + +static int oxp_rumble_intensity_set(u8 intensity) +{ + u8 header[15] = { 0x02, 0x38, 0x02, 0xe3, 0x39, 0xe3, 0x39, 0xe3, + 0x39, 0x01, intensity, 0x05, 0xe3, 0x39, 0xe3 }; + u8 footer[9] = { 0x39, 0xe3, 0x39, 0xe3, 0xe3, 0x02, 0x04, 0x39, 0x39 }; + size_t footer_size = ARRAY_SIZE(footer); + size_t header_size = ARRAY_SIZE(header); + u8 data[59] = { 0x0 }; + size_t data_size = ARRAY_SIZE(data); + + memcpy(data, header, header_size); + memcpy(data + data_size - footer_size, footer, footer_size); + + return oxp_gen_2_property_out(OXP_FID_GEN2_RUMBLE_SET, data, data_size); +} + +static ssize_t rumble_intensity_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret) + return ret; + + if (val < 0 || val > 5) + return -EINVAL; + + ret = oxp_rumble_intensity_set(val); + if (ret) + return ret; + + drvdata.rumble_intensity = val; + + return count; +} + +static ssize_t rumble_intensity_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%i\n", drvdata.rumble_intensity); +} +static DEVICE_ATTR_RW(rumble_intensity); + +static ssize_t rumble_intensity_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0-5\n"); +} +static DEVICE_ATTR_RO(rumble_intensity_range); + +#define OXP_DEVICE_ATTR_RW(_name, _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); \ + } \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return _group##_show(dev, attr, buf, _name.index); \ + } \ + static DEVICE_ATTR_RW(_name) + +static struct oxp_attr button_a = { BUTTON_A }; +OXP_DEVICE_ATTR_RW(button_a, map_button); + +static struct oxp_attr button_b = { BUTTON_B }; +OXP_DEVICE_ATTR_RW(button_b, map_button); + +static struct oxp_attr button_x = { BUTTON_X }; +OXP_DEVICE_ATTR_RW(button_x, map_button); + +static struct oxp_attr button_y = { BUTTON_Y }; +OXP_DEVICE_ATTR_RW(button_y, map_button); + +static struct oxp_attr button_lb = { BUTTON_LB }; +OXP_DEVICE_ATTR_RW(button_lb, map_button); + +static struct oxp_attr button_rb = { BUTTON_RB }; +OXP_DEVICE_ATTR_RW(button_rb, map_button); + +static struct oxp_attr button_lt = { BUTTON_LT }; +OXP_DEVICE_ATTR_RW(button_lt, map_button); + +static struct oxp_attr button_rt = { BUTTON_RT }; +OXP_DEVICE_ATTR_RW(button_rt, map_button); + +static struct oxp_attr button_start = { BUTTON_START }; +OXP_DEVICE_ATTR_RW(button_start, map_button); + +static struct oxp_attr button_select = { BUTTON_SELECT }; +OXP_DEVICE_ATTR_RW(button_select, map_button); + +static struct oxp_attr button_l3 = { BUTTON_L3 }; +OXP_DEVICE_ATTR_RW(button_l3, map_button); + +static struct oxp_attr button_r3 = { BUTTON_R3 }; +OXP_DEVICE_ATTR_RW(button_r3, map_button); + +static struct oxp_attr button_d_up = { BUTTON_DUP }; +OXP_DEVICE_ATTR_RW(button_d_up, map_button); + +static struct oxp_attr button_d_down = { BUTTON_DDOWN }; +OXP_DEVICE_ATTR_RW(button_d_down, map_button); + +static struct oxp_attr button_d_left = { BUTTON_DLEFT }; +OXP_DEVICE_ATTR_RW(button_d_left, map_button); + +static struct oxp_attr button_d_right = { BUTTON_DRIGHT }; +OXP_DEVICE_ATTR_RW(button_d_right, map_button); + +static struct oxp_attr button_m1 = { BUTTON_M1 }; +OXP_DEVICE_ATTR_RW(button_m1, map_button); + +static struct oxp_attr button_m2 = { BUTTON_M2 }; +OXP_DEVICE_ATTR_RW(button_m2, map_button); + +static struct attribute *oxp_cfg_attrs[] = { + &dev_attr_button_a.attr, + &dev_attr_button_b.attr, + &dev_attr_button_d_down.attr, + &dev_attr_button_d_left.attr, + &dev_attr_button_d_right.attr, + &dev_attr_button_d_up.attr, + &dev_attr_button_l3.attr, + &dev_attr_button_lb.attr, + &dev_attr_button_lt.attr, + &dev_attr_button_m1.attr, + &dev_attr_button_m2.attr, + &dev_attr_button_mapping_options.attr, + &dev_attr_button_r3.attr, + &dev_attr_button_rb.attr, + &dev_attr_button_rt.attr, + &dev_attr_button_select.attr, + &dev_attr_button_start.attr, + &dev_attr_button_x.attr, + &dev_attr_button_y.attr, + &dev_attr_gamepad_mode.attr, + &dev_attr_gamepad_mode_index.attr, + &dev_attr_reset_buttons.attr, + &dev_attr_rumble_intensity.attr, + &dev_attr_rumble_intensity_range.attr, + NULL, +}; + +static const struct attribute_group oxp_cfg_attrs_group = { + .attrs = oxp_cfg_attrs, +}; + +static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness) +{ + u16 up = get_usage_page(drvdata.hdev); + u8 *data; + + /* Always default to max brightness and use intensity scaling when in + * monocolor mode. + */ + switch (up) { + case GEN1_USAGE_PAGE: + data = (u8[4]) { OXP_SET_PROPERTY, enabled, speed, 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; + } +} + +static ssize_t oxp_rgb_status_show(void) +{ + u16 up = get_usage_page(drvdata.hdev); + u8 *data; + + switch (up) { + 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; + } +} + +static int oxp_rgb_color_set(void) +{ + u8 max_br = drvdata.led_mc->led_cdev.max_brightness; + u8 br = drvdata.led_mc->led_cdev.brightness; + u16 up = get_usage_page(drvdata.hdev); + u8 green, red, blue; + size_t size; + u8 *data; + int i; + + red = br * drvdata.led_mc->subled_info[0].intensity / max_br; + green = br * drvdata.led_mc->subled_info[1].intensity / max_br; + blue = br * drvdata.led_mc->subled_info[2].intensity / max_br; + + switch (up) { + case GEN1_USAGE_PAGE: + size = 55; + data = (u8[55]) { OXP_EFFECT_MONO_TRUE }; + + for (i = 0; i < (size - 1) / 3; i++) { + data[3 * i + 1] = red; + data[3 * i + 2] = green; + 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; + } +} + +static int oxp_rgb_effect_set(u8 effect) +{ + u16 up = get_usage_page(drvdata.hdev); + u8 *data; + int ret; + + switch (effect) { + case OXP_EFFECT_AURORA: + case OXP_EFFECT_BIRTHDAY: + case OXP_EFFECT_FLOWING: + case OXP_EFFECT_CHROMA_1: + case OXP_EFFECT_NEON: + case OXP_EFFECT_CHROMA_2: + case OXP_EFFECT_DREAMY: + case OXP_EFFECT_WARM: + case OXP_EFFECT_CYBERPUNK: + case OXP_EFFECT_SEA: + case OXP_EFFECT_SUNSET: + case OXP_EFFECT_COLORFUL: + case OXP_EFFECT_MONSTER: + case OXP_EFFECT_GREEN: + case OXP_EFFECT_BLUE: + case OXP_EFFECT_YELLOW: + case OXP_EFFECT_TEAL: + case OXP_EFFECT_PURPLE: + case OXP_EFFECT_FOGGY: + switch (up) { + case GEN1_USAGE_PAGE: + 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; + } + break; + case OXP_EFFECT_MONO_LIST: + ret = oxp_rgb_color_set(); + break; + default: + return -EINVAL; + } + + if (ret) + return ret; + + drvdata.rgb_effect = effect; + + return 0; +} + +static ssize_t enabled_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + u8 val; + + ret = sysfs_match_string(oxp_feature_en_text, buf); + if (ret < 0) + return ret; + val = ret; + + ret = oxp_rgb_status_store(val, drvdata.rgb_speed, + drvdata.rgb_brightness); + if (ret) + return ret; + + drvdata.rgb_en = val; + return count; +} + +static ssize_t enabled_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + + ret = oxp_rgb_status_show(); + if (ret) + return ret; + + if (drvdata.rgb_en >= ARRAY_SIZE(oxp_feature_en_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", oxp_feature_en_text[drvdata.rgb_en]); +} +static DEVICE_ATTR_RW(enabled); + +static ssize_t enabled_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + size_t count = 0; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(oxp_feature_en_text); i++) + count += sysfs_emit_at(buf, count, "%s ", oxp_feature_en_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} +static DEVICE_ATTR_RO(enabled_index); + +static ssize_t effect_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + u8 val; + + ret = sysfs_match_string(oxp_rgb_effect_text, buf); + if (ret < 0) + return ret; + + val = ret; + + ret = oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed, + drvdata.rgb_brightness); + if (ret) + return ret; + + ret = oxp_rgb_effect_set(val); + if (ret) + return ret; + + return count; +} + +static ssize_t effect_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + + ret = oxp_rgb_status_show(); + if (ret) + return ret; + + if (drvdata.rgb_effect >= ARRAY_SIZE(oxp_rgb_effect_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", oxp_rgb_effect_text[drvdata.rgb_effect]); +} + +static DEVICE_ATTR_RW(effect); + +static ssize_t effect_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + size_t count = 0; + unsigned int i; + + for (i = 1; i < ARRAY_SIZE(oxp_rgb_effect_text); i++) + count += sysfs_emit_at(buf, count, "%s ", oxp_rgb_effect_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} +static DEVICE_ATTR_RO(effect_index); + +static ssize_t speed_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret) + return ret; + + if (val > 9) + return -EINVAL; + + ret = oxp_rgb_status_store(drvdata.rgb_en, val, drvdata.rgb_brightness); + if (ret) + return ret; + + drvdata.rgb_speed = val; + return count; +} + +static ssize_t speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + + ret = oxp_rgb_status_show(); + if (ret) + return ret; + + if (drvdata.rgb_speed > 9) + return -EINVAL; + + return sysfs_emit(buf, "%hhu\n", drvdata.rgb_speed); +} +static DEVICE_ATTR_RW(speed); + +static ssize_t speed_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0-9\n"); +} +static DEVICE_ATTR_RO(speed_range); + +static void oxp_rgb_queue_fn(struct work_struct *work) +{ + unsigned int max_brightness = drvdata.led_mc->led_cdev.max_brightness; + unsigned int brightness = drvdata.led_mc->led_cdev.brightness; + u8 val = 4 * brightness / max_brightness; + int ret; + + if (drvdata.rgb_brightness != val) { + ret = oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed, val); + if (ret) + dev_err(drvdata.led_mc->led_cdev.dev, + "Error: Failed to write RGB Status: %i\n", ret); + + drvdata.rgb_brightness = val; + } + + if (drvdata.rgb_effect != OXP_EFFECT_MONO_LIST) + return; + + ret = oxp_rgb_effect_set(drvdata.rgb_effect); + if (ret) + dev_err(drvdata.led_mc->led_cdev.dev, "Error: Failed to write RGB color: %i\n", + ret); +} + +static void oxp_rgb_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + led_cdev->brightness = brightness; + mod_delayed_work(system_dfl_wq, &drvdata.oxp_rgb_queue, msecs_to_jiffies(50)); +} + +static struct attribute *oxp_rgb_attrs[] = { + &dev_attr_effect.attr, + &dev_attr_effect_index.attr, + &dev_attr_enabled.attr, + &dev_attr_enabled_index.attr, + &dev_attr_speed.attr, + &dev_attr_speed_range.attr, + NULL, +}; + +static const struct attribute_group oxp_rgb_attr_group = { + .attrs = oxp_rgb_attrs, +}; + +static struct mc_subled oxp_rgb_subled_info[] = { + { + .color_index = LED_COLOR_ID_RED, + .intensity = 0x24, + .channel = 0x1, + }, + { + .color_index = LED_COLOR_ID_GREEN, + .intensity = 0x22, + .channel = 0x2, + }, + { + .color_index = LED_COLOR_ID_BLUE, + .intensity = 0x99, + .channel = 0x3, + }, +}; + +static struct led_classdev_mc oxp_cdev_rgb = { + .led_cdev = { + .name = "oxp:rgb:joystick_rings", + .color = LED_COLOR_ID_RGB, + .brightness = 0x64, + .max_brightness = 0x64, + .brightness_set = oxp_rgb_brightness_set, + }, + .num_colors = ARRAY_SIZE(oxp_rgb_subled_info), + .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) +{ + struct oxp_bmap_page_1 *bmap_1; + struct oxp_bmap_page_2 *bmap_2; + int ret; + + 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); + ret = devm_led_classdev_multicolor_register(&hdev->dev, &oxp_cdev_rgb); + if (ret) + return dev_err_probe(&hdev->dev, ret, + "Failed to create RGB device\n"); + + ret = devm_device_add_group(drvdata.led_mc->led_cdev.dev, + &oxp_rgb_attr_group); + if (ret) + return dev_err_probe(drvdata.led_mc->led_cdev.dev, ret, + "Failed to create RGB configuration attributes\n"); + + ret = oxp_rgb_status_show(); + if (ret) + dev_warn(drvdata.led_mc->led_cdev.dev, + "Failed to query RGB initial state: %i\n", ret); + + /* Below features are only implemented in gen 2 */ + if (up != GEN2_USAGE_PAGE) + return 0; + +skip_rgb: + bmap_1 = devm_kzalloc(&hdev->dev, sizeof(struct oxp_bmap_page_1), GFP_KERNEL); + if (!bmap_1) + return dev_err_probe(&hdev->dev, -ENOMEM, + "Unable to allocate button map page 1\n"); + + bmap_2 = devm_kzalloc(&hdev->dev, sizeof(struct oxp_bmap_page_2), GFP_KERNEL); + if (!bmap_2) + return dev_err_probe(&hdev->dev, -ENOMEM, + "Unable to allocate button map page 2\n"); + + drvdata.bmap_1 = bmap_1; + drvdata.bmap_2 = bmap_2; + oxp_reset_buttons(); + INIT_DELAYED_WORK(&drvdata.oxp_btn_queue, oxp_btn_queue_fn); + + drvdata.gamepad_mode = OXP_GP_MODE_XINPUT; + drvdata.rumble_intensity = 5; + + INIT_DELAYED_WORK(&drvdata.oxp_mcu_init, oxp_mcu_init_fn); + mod_delayed_work(system_dfl_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50)); + + ret = devm_device_add_group(&hdev->dev, &oxp_cfg_attrs_group); + if (ret) + return dev_err_probe(&hdev->dev, ret, + "Failed to attach configuration attributes\n"); + + return 0; +} + +static int oxp_hid_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + u16 up; + + ret = hid_parse(hdev); + if (ret) + return dev_err_probe(&hdev->dev, ret, "Failed to parse HID device\n"); + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) + return dev_err_probe(&hdev->dev, ret, "Failed to start HID device\n"); + + ret = hid_hw_open(hdev); + if (ret) { + hid_hw_stop(hdev); + return dev_err_probe(&hdev->dev, ret, "Failed to open HID device\n"); + } + + up = get_usage_page(hdev); + dev_dbg(&hdev->dev, "Got usage page %04x\n", up); + + switch (up) { + case GEN1_USAGE_PAGE: + case GEN2_USAGE_PAGE: + ret = oxp_cfg_probe(hdev, up); + if (ret) { + hid_hw_close(hdev); + hid_hw_stop(hdev); + } + + return ret; + default: + return 0; + } +} + +static void oxp_hid_remove(struct hid_device *hdev) +{ + cancel_delayed_work(&drvdata.oxp_rgb_queue); + cancel_delayed_work(&drvdata.oxp_btn_queue); + cancel_delayed_work(&drvdata.oxp_mcu_init); + hid_hw_close(hdev); + hid_hw_stop(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) }, + {} +}; + +MODULE_DEVICE_TABLE(hid, oxp_devices); +static struct hid_driver hid_oxp = { + .name = "hid-oxp", + .id_table = oxp_devices, + .probe = oxp_hid_probe, + .remove = oxp_hid_remove, + .raw_event = oxp_hid_raw_event, +}; +module_hid_driver(hid_oxp); + +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); +MODULE_DESCRIPTION("Driver for OneXPlayer HID Interfaces"); +MODULE_LICENSE("GPL"); |
