diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 790aa908e2a788..e54ce0e46cc149 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1246,6 +1246,14 @@ config HID_XINMO standard. Currently only supports the Xin-Mo Dual Arcade. Say Y here if you have a Xin-Mo Dual Arcade controller. +config HID_XPADNEO + tristate "XBOX ONE S and X controller over Bluetooth, including FF" + depends on HID + select INPUT_FF_MEMLESS + help + Support for Xbox One S and X controllers over BT, including + force-feedback. + config HID_ZEROPLUS tristate "Zeroplus based game controller support" help diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 8a06d0f840bcbe..a28b58a9275615 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -140,6 +140,11 @@ obj-$(CONFIG_HID_UDRAW_PS3) += hid-udraw-ps3.o obj-$(CONFIG_HID_LED) += hid-led.o obj-$(CONFIG_HID_XIAOMI) += hid-xiaomi.o obj-$(CONFIG_HID_XINMO) += hid-xinmo.o +hid-xpadneo-objs := xpadneo/consumer.o \ + xpadneo/core.o \ + xpadneo/keyboard.o \ + xpadneo/legacy.o +obj-$(CONFIG_HID_XPADNEO) += hid-xpadneo.o obj-$(CONFIG_HID_ZEROPLUS) += hid-zpff.o obj-$(CONFIG_HID_ZYDACRON) += hid-zydacron.o obj-$(CONFIG_HID_VIEWSONIC) += hid-viewsonic.o diff --git a/drivers/hid/hid-microsoft.c b/drivers/hid/hid-microsoft.c index 9345e2bfd56ede..9058bc00068e57 100644 --- a/drivers/hid/hid-microsoft.c +++ b/drivers/hid/hid-microsoft.c @@ -447,6 +447,7 @@ static const struct hid_device_id ms_devices[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x091B), .driver_data = MS_SURFACE_DIAL }, +#if !IS_ENABLED(CONFIG_HID_XPADNEO) { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_XBOX_CONTROLLER_MODEL_1708), .driver_data = MS_QUIRK_FF }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_XBOX_CONTROLLER_MODEL_1708_BLE), @@ -459,6 +460,7 @@ static const struct hid_device_id ms_devices[] = { .driver_data = MS_QUIRK_FF }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_8BITDO_SN30_PRO_PLUS), .driver_data = MS_QUIRK_FF }, +#endif { } }; MODULE_DEVICE_TABLE(hid, ms_devices); diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 3983b4f282f8f8..c71814f8139605 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -713,6 +713,14 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_XIN_MO, USB_DEVICE_ID_XIN_MO_DUAL_ARCADE) }, { HID_USB_DEVICE(USB_VENDOR_ID_XIN_MO, USB_DEVICE_ID_THT_2P_ARCADE) }, #endif +#if IS_ENABLED(CONFIG_HID_XPADNEO) + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x02FD) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x02E0) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x0B05) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x0B13) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x0B20) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x0B22) }, +#endif #if IS_ENABLED(CONFIG_HID_ZEROPLUS) { HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0005) }, { HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0030) }, diff --git a/drivers/hid/xpadneo/consumer.c b/drivers/hid/xpadneo/consumer.c new file mode 100644 index 00000000000000..20e2e2ab3ee7ab --- /dev/null +++ b/drivers/hid/xpadneo/consumer.c @@ -0,0 +1,32 @@ +/* + * xpadneo consumer control driver + * + * Copyright (c) 2021 Kai Krakow + */ + +#include "xpadneo.h" + +extern int xpadneo_init_consumer(struct xpadneo_devdata *xdata) +{ + struct hid_device *hdev = xdata->hdev; + int ret, synth = 0; + + if (!xdata->consumer) { + synth = 1; + ret = xpadneo_init_synthetic(xdata, "Consumer Control", &xdata->consumer); + if (ret || !xdata->consumer) + return ret; + } + + if (synth) { + ret = input_register_device(xdata->consumer); + if (ret) { + hid_err(hdev, "failed to register consumer control\n"); + return ret; + } + + hid_info(hdev, "consumer control added\n"); + } + + return 0; +} diff --git a/drivers/hid/xpadneo/core.c b/drivers/hid/xpadneo/core.c new file mode 100644 index 00000000000000..b1d1fa577c0722 --- /dev/null +++ b/drivers/hid/xpadneo/core.c @@ -0,0 +1,37 @@ +/* + * xpadneo driver core + * + * Copyright (c) 2021 Kai Krakow + */ + +#include "xpadneo.h" + +extern int xpadneo_init_synthetic(struct xpadneo_devdata *xdata, char *suffix, + struct input_dev **devp) +{ + struct hid_device *hdev = xdata->hdev; + struct input_dev *input_dev = devm_input_allocate_device(&hdev->dev); + size_t suffix_len, name_len; + + if (!input_dev) + return -ENOMEM; + + name_len = strlen(hdev->name); + suffix_len = strlen(suffix); + if ((name_len < suffix_len) || strcmp(xdata->hdev->name + name_len - suffix_len, suffix)) { + input_dev->name = kasprintf(GFP_KERNEL, "%s %s", hdev->name, suffix); + if (!input_dev->name) + return -ENOMEM; + } + + dev_set_drvdata(&input_dev->dev, xdata); + input_dev->phys = hdev->phys; + input_dev->uniq = hdev->uniq; + input_dev->id.bustype = hdev->bus; + input_dev->id.vendor = hdev->vendor; + input_dev->id.product = hdev->product; + input_dev->id.version = hdev->version; + + *devp = input_dev; + return 0; +} diff --git a/drivers/hid/xpadneo/keyboard.c b/drivers/hid/xpadneo/keyboard.c new file mode 100644 index 00000000000000..4ccfdac62dc845 --- /dev/null +++ b/drivers/hid/xpadneo/keyboard.c @@ -0,0 +1,35 @@ +/* + * xpadneo keyboard driver + * + * Copyright (c) 2021 Kai Krakow + */ + +#include "xpadneo.h" + +extern int xpadneo_init_keyboard(struct xpadneo_devdata *xdata) +{ + struct hid_device *hdev = xdata->hdev; + int ret, synth = 0; + + if (!xdata->keyboard) { + synth = 1; + ret = xpadneo_init_synthetic(xdata, "Keyboard", &xdata->keyboard); + if (ret || !xdata->keyboard) + return ret; + } + + /* enable key events for keyboard */ + input_set_capability(xdata->keyboard, EV_KEY, BTN_SHARE); + + if (synth) { + ret = input_register_device(xdata->keyboard); + if (ret) { + hid_err(hdev, "failed to register keyboard\n"); + return ret; + } + + hid_info(hdev, "keyboard added\n"); + } + + return 0; +} diff --git a/drivers/hid/xpadneo/legacy.c b/drivers/hid/xpadneo/legacy.c new file mode 100644 index 00000000000000..59e96a562c69cf --- /dev/null +++ b/drivers/hid/xpadneo/legacy.c @@ -0,0 +1,1298 @@ +/* + * Force feedback support for XBOX ONE S and X gamepads via Bluetooth + * + * This driver was developed for a student project at fortiss GmbH in Munich. + * Copyright (c) 2017 Florian Dollinger + * + * Additional features and code redesign + * Copyright (c) 2020 Kai Krakow + */ + +#include +#include + +#include "xpadneo.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Florian Dollinger "); +MODULE_AUTHOR("Kai Krakow "); +MODULE_DESCRIPTION("Linux kernel driver for Xbox ONE S+ gamepads (BT), incl. FF"); +MODULE_VERSION(XPADNEO_VERSION); + +static u8 param_trigger_rumble_mode = 0; +module_param_named(trigger_rumble_mode, param_trigger_rumble_mode, byte, 0644); +MODULE_PARM_DESC(trigger_rumble_mode, "(u8) Trigger rumble mode. 0: pressure, 2: disable."); + +static u8 param_rumble_attenuation[2]; +module_param_array_named(rumble_attenuation, param_rumble_attenuation, byte, NULL, 0644); +MODULE_PARM_DESC(rumble_attenuation, + "(u8) Attenuate the rumble strength: all[,triggers] " + "0 (none, full rumble) to 100 (max, no rumble)."); + +static bool param_ff_connect_notify = 1; +module_param_named(ff_connect_notify, param_ff_connect_notify, bool, 0644); +MODULE_PARM_DESC(ff_connect_notify, + "(bool) Connection notification using force feedback. 1: enable, 0: disable."); + +static bool param_gamepad_compliance = 1; +module_param_named(gamepad_compliance, param_gamepad_compliance, bool, 0444); +MODULE_PARM_DESC(gamepad_compliance, + "(bool) Adhere to Linux Gamepad Specification by using signed axis values. " + "1: enable, 0: disable."); + +static bool param_disable_deadzones = 0; +module_param_named(disable_deadzones, param_disable_deadzones, bool, 0444); +MODULE_PARM_DESC(disable_deadzones, + "(bool) Disable dead zone handling for raw processing by Wine/Proton, confuses joydev. " + "0: disable, 1: enable."); + +static struct { + char *args[17]; + unsigned int nargs; +} param_quirks; +module_param_array_named(quirks, param_quirks.args, charp, ¶m_quirks.nargs, 0644); +MODULE_PARM_DESC(quirks, + "(string) Override or change device quirks, specify as: \"MAC1{:,+,-}quirks1[,...16]\"" + ", MAC format = 11:22:33:44:55:66" + ", no pulse parameters = " __stringify(XPADNEO_QUIRK_NO_PULSE) + ", no trigger rumble = " __stringify(XPADNEO_QUIRK_NO_TRIGGER_RUMBLE) + ", no motor masking = " __stringify(XPADNEO_QUIRK_NO_MOTOR_MASK) + ", use Linux button mappings = " __stringify(XPADNEO_QUIRK_LINUX_BUTTONS) + ", use Nintendo mappings = " __stringify(XPADNEO_QUIRK_NINTENDO) + ", use Share button mappings = " __stringify(XPADNEO_QUIRK_SHARE_BUTTON) + ", reversed motor masking = " __stringify(XPADNEO_QUIRK_REVERSE_MASK)); + +static DEFINE_IDA(xpadneo_device_id_allocator); + +static enum power_supply_property xpadneo_battery_props[] = { + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_STATUS, +}; + +#define DEVICE_NAME_QUIRK(n, f) \ + { .name_match = (n), .name_len = sizeof(n) - 1, .flags = (f) } +#define DEVICE_OUI_QUIRK(o, f) \ + { .oui_match = (o), .flags = (f) } + +struct quirk { + char *name_match; + char *oui_match; + u16 name_len; + u32 flags; +}; + +static const struct quirk xpadneo_quirks[] = { + DEVICE_OUI_QUIRK("98:B6:EA", + XPADNEO_QUIRK_NO_PULSE | XPADNEO_QUIRK_NO_TRIGGER_RUMBLE | + XPADNEO_QUIRK_REVERSE_MASK), + DEVICE_OUI_QUIRK("E4:17:D8", + XPADNEO_QUIRK_NO_PULSE | XPADNEO_QUIRK_NO_TRIGGER_RUMBLE | + XPADNEO_QUIRK_NO_MOTOR_MASK), +}; + +struct usage_map { + u32 usage; + enum { + MAP_IGNORE = -1, /* Completely ignore this field */ + MAP_AUTO, /* Do not really map it, let hid-core decide */ + MAP_STATIC /* Map to the values given */ + } behaviour; + struct { + u8 event_type; /* input event (EV_KEY, EV_ABS, ...) */ + u16 input_code; /* input code (BTN_A, ABS_X, ...) */ + } ev; +}; + +#define USAGE_MAP(u, b, e, i) \ + { .usage = (u), .behaviour = (b), .ev = { .event_type = (e), .input_code = (i) } } +#define USAGE_IGN(u) USAGE_MAP(u, MAP_IGNORE, 0, 0) + +static const struct usage_map xpadneo_usage_maps[] = { + /* fixup buttons to Linux codes */ + USAGE_MAP(0x90001, MAP_STATIC, EV_KEY, BTN_A), /* A */ + USAGE_MAP(0x90002, MAP_STATIC, EV_KEY, BTN_B), /* B */ + USAGE_MAP(0x90003, MAP_STATIC, EV_KEY, BTN_X), /* X */ + USAGE_MAP(0x90004, MAP_STATIC, EV_KEY, BTN_Y), /* Y */ + USAGE_MAP(0x90005, MAP_STATIC, EV_KEY, BTN_TL), /* LB */ + USAGE_MAP(0x90006, MAP_STATIC, EV_KEY, BTN_TR), /* RB */ + USAGE_MAP(0x90007, MAP_STATIC, EV_KEY, BTN_SELECT), /* Back */ + USAGE_MAP(0x90008, MAP_STATIC, EV_KEY, BTN_START), /* Menu */ + USAGE_MAP(0x90009, MAP_STATIC, EV_KEY, BTN_THUMBL), /* LS */ + USAGE_MAP(0x9000A, MAP_STATIC, EV_KEY, BTN_THUMBR), /* RS */ + + /* fixup the Xbox logo button */ + USAGE_MAP(0x9000B, MAP_STATIC, EV_KEY, BTN_XBOX), /* Xbox */ + + /* fixup the Share button */ + USAGE_MAP(0x9000C, MAP_STATIC, EV_KEY, BTN_SHARE), /* Share */ + + /* fixup code "Sys Main Menu" from Windows report descriptor */ + USAGE_MAP(0x10085, MAP_STATIC, EV_KEY, BTN_XBOX), + + /* fixup code "AC Home" from Linux report descriptor */ + USAGE_MAP(0xC0223, MAP_STATIC, EV_KEY, BTN_XBOX), + + /* fixup code "AC Back" from Linux report descriptor */ + USAGE_MAP(0xC0224, MAP_STATIC, EV_KEY, BTN_SELECT), + + /* map special buttons without HID bitmaps, corrected in event handler */ + USAGE_MAP(0xC0081, MAP_STATIC, EV_KEY, BTN_PADDLES(0)), /* Four paddles */ + + /* hardware features handled at the raw report level */ + USAGE_IGN(0xC0085), /* Profile switcher */ + USAGE_IGN(0xC0099), /* Trigger scale switches */ + + /* XBE2: Disable "dial", which is a redundant representation of the D-Pad */ + USAGE_IGN(0x10037), + + /* XBE2: Disable duplicate report fields of broken v1 packet format */ + USAGE_IGN(0x10040), /* Vx, copy of X axis */ + USAGE_IGN(0x10041), /* Vy, copy of Y axis */ + USAGE_IGN(0x10042), /* Vz, copy of Z axis */ + USAGE_IGN(0x10043), /* Vbrx, copy of Rx */ + USAGE_IGN(0x10044), /* Vbry, copy of Ry */ + USAGE_IGN(0x10045), /* Vbrz, copy of Rz */ + USAGE_IGN(0x90010), /* copy of A */ + USAGE_IGN(0x90011), /* copy of B */ + USAGE_IGN(0x90013), /* copy of X */ + USAGE_IGN(0x90014), /* copy of Y */ + USAGE_IGN(0x90016), /* copy of LB */ + USAGE_IGN(0x90017), /* copy of RB */ + USAGE_IGN(0x9001B), /* copy of Start */ + USAGE_IGN(0x9001D), /* copy of LS */ + USAGE_IGN(0x9001E), /* copy of RS */ + USAGE_IGN(0xC0082), /* copy of Select button */ + + /* XBE2: Disable unused buttons */ + USAGE_IGN(0x90012), /* 6 "TRIGGER_HAPPY" buttons */ + USAGE_IGN(0x90015), + USAGE_IGN(0x90018), + USAGE_IGN(0x90019), + USAGE_IGN(0x9001A), + USAGE_IGN(0x9001C), + USAGE_IGN(0xC00B9), /* KEY_SHUFFLE button */ +}; + +static struct workqueue_struct *xpadneo_rumble_wq; + +static void xpadneo_ff_worker(struct work_struct *work) +{ + struct xpadneo_devdata *xdata = + container_of(to_delayed_work(work), struct xpadneo_devdata, ff_worker); + struct hid_device *hdev = xdata->hdev; + struct ff_report *r = xdata->output_report_dmabuf; + int ret; + unsigned long flags; + + memset(r, 0, sizeof(*r)); + r->report_id = XPADNEO_XB1S_FF_REPORT; + r->ff.enable = FF_RUMBLE_ALL; + + /* + * if pulse is not supported, we do not have to care about explicitly + * stopping the effect, the kernel will do this for us as part of its + * ff-memless emulation + */ + if (likely((xdata->quirks & XPADNEO_QUIRK_NO_PULSE) == 0)) { + /* + * ff-memless has a time resolution of 50ms but we pulse the + * motors for 60 minutes as the Windows driver does. To work + * around a potential firmware crash, we filter out repeated + * motor programming further below. + */ + r->ff.pulse_sustain_10ms = 0xFF; + r->ff.loop_count = 0xEB; + } + + spin_lock_irqsave(&xdata->ff_lock, flags); + + /* let our scheduler know we've been called */ + xdata->ff_scheduled = false; + + if (unlikely(xdata->quirks & XPADNEO_QUIRK_NO_TRIGGER_RUMBLE)) { + /* do not send these bits if not supported */ + r->ff.enable &= ~FF_RUMBLE_TRIGGERS; + } else { + /* trigger motors */ + r->ff.magnitude_left = xdata->ff.magnitude_left; + r->ff.magnitude_right = xdata->ff.magnitude_right; + } + + /* main motors */ + r->ff.magnitude_strong = xdata->ff.magnitude_strong; + r->ff.magnitude_weak = xdata->ff.magnitude_weak; + + /* do not reprogram motors that have not changed */ + if (unlikely(xdata->ff_shadow.magnitude_strong == r->ff.magnitude_strong)) + r->ff.enable &= ~FF_RUMBLE_STRONG; + if (unlikely(xdata->ff_shadow.magnitude_weak == r->ff.magnitude_weak)) + r->ff.enable &= ~FF_RUMBLE_WEAK; + if (likely(xdata->ff_shadow.magnitude_left == r->ff.magnitude_left)) + r->ff.enable &= ~FF_RUMBLE_LEFT; + if (likely(xdata->ff_shadow.magnitude_right == r->ff.magnitude_right)) + r->ff.enable &= ~FF_RUMBLE_RIGHT; + + /* do not send a report if nothing changed */ + if (unlikely(r->ff.enable == FF_RUMBLE_NONE)) { + spin_unlock_irqrestore(&xdata->ff_lock, flags); + return; + } + + /* shadow our current rumble values for the next cycle */ + memcpy(&xdata->ff_shadow, &xdata->ff, sizeof(xdata->ff)); + + /* clear the magnitudes to properly accumulate the maximum values */ + xdata->ff.magnitude_left = 0; + xdata->ff.magnitude_right = 0; + xdata->ff.magnitude_weak = 0; + xdata->ff.magnitude_strong = 0; + + /* + * throttle next command submission, the firmware doesn't like us to + * send rumble data any faster + */ + xdata->ff_throttle_until = XPADNEO_RUMBLE_THROTTLE_JIFFIES; + + spin_unlock_irqrestore(&xdata->ff_lock, flags); + + /* do not send these bits if not supported */ + if (unlikely(xdata->quirks & XPADNEO_QUIRK_NO_MOTOR_MASK)) + r->ff.enable = 0; + + /* reverse the bits for trigger and main motors */ + if (unlikely(xdata->quirks & XPADNEO_QUIRK_REVERSE_MASK)) + r->ff.enable = SWAP_BITS(SWAP_BITS(r->ff.enable, 1, 2), 0, 3); + + ret = hid_hw_output_report(hdev, (__u8 *) r, sizeof(*r)); + if (ret < 0) + hid_warn(hdev, "failed to send FF report: %d\n", ret); +} + +#define update_magnitude(m, v) m = (v) > 0 ? max(m, v) : 0 +static int xpadneo_ff_play(struct input_dev *dev, void *data, struct ff_effect *effect) +{ + unsigned long flags, ff_run_at, ff_throttle_until; + long delay_work; + int fraction_TL, fraction_TR, fraction_MAIN, percent_TRIGGERS, percent_MAIN; + s32 weak, strong, max_main; + + struct hid_device *hdev = input_get_drvdata(dev); + struct xpadneo_devdata *xdata = hid_get_drvdata(hdev); + + if (effect->type != FF_RUMBLE) + return 0; + + /* copy data from effect structure at the very beginning */ + weak = effect->u.rumble.weak_magnitude; + strong = effect->u.rumble.strong_magnitude; + + /* calculate the rumble attenuation */ + percent_MAIN = 100 - param_rumble_attenuation[0]; + percent_MAIN = clamp(percent_MAIN, 0, 100); + + percent_TRIGGERS = 100 - param_rumble_attenuation[1]; + percent_TRIGGERS = clamp(percent_TRIGGERS, 0, 100); + percent_TRIGGERS = percent_TRIGGERS * percent_MAIN / 100; + + switch (param_trigger_rumble_mode) { + case PARAM_TRIGGER_RUMBLE_DISABLE: + fraction_MAIN = percent_MAIN; + fraction_TL = 0; + fraction_TR = 0; + break; + case PARAM_TRIGGER_RUMBLE_PRESSURE: + default: + fraction_MAIN = percent_MAIN; + fraction_TL = (xdata->last_abs_z * percent_TRIGGERS + 511) / 1023; + fraction_TR = (xdata->last_abs_rz * percent_TRIGGERS + 511) / 1023; + break; + } + + /* + * we want to keep the rumbling at the triggers at the maximum + * of the weak and strong main rumble + */ + max_main = max(weak, strong); + + spin_lock_irqsave(&xdata->ff_lock, flags); + + /* calculate the physical magnitudes, scale from 16 bit to 0..100 */ + update_magnitude(xdata->ff.magnitude_strong, + (u8)((strong * fraction_MAIN + S16_MAX) / U16_MAX)); + update_magnitude(xdata->ff.magnitude_weak, + (u8)((weak * fraction_MAIN + S16_MAX) / U16_MAX)); + + /* calculate the physical magnitudes, scale from 16 bit to 0..100 */ + update_magnitude(xdata->ff.magnitude_left, + (u8)((max_main * fraction_TL + S16_MAX) / U16_MAX)); + update_magnitude(xdata->ff.magnitude_right, + (u8)((max_main * fraction_TR + S16_MAX) / U16_MAX)); + + /* synchronize: is our worker still scheduled? */ + if (xdata->ff_scheduled) { + /* the worker is still guarding rumble programming */ + hid_notice_once(hdev, "throttling rumble reprogramming\n"); + goto unlock_and_return; + } + + /* we want to run now but may be throttled */ + ff_run_at = jiffies; + ff_throttle_until = xdata->ff_throttle_until; + if (time_before(ff_run_at, ff_throttle_until)) { + /* last rumble was recently executed */ + delay_work = (long)ff_throttle_until - (long)ff_run_at; + delay_work = clamp(delay_work, 0L, (long)HZ); + } else { + /* the firmware is ready */ + delay_work = 0; + } + + /* schedule writing a rumble report to the controller */ + if (queue_delayed_work(xpadneo_rumble_wq, &xdata->ff_worker, delay_work)) + xdata->ff_scheduled = true; + else + hid_err(hdev, "lost rumble packet\n"); + +unlock_and_return: + spin_unlock_irqrestore(&xdata->ff_lock, flags); + return 0; +} + +static void xpadneo_welcome_rumble(struct hid_device *hdev) +{ + struct xpadneo_devdata *xdata = hid_get_drvdata(hdev); + struct ff_report ff_pck; + + memset(&ff_pck.ff, 0, sizeof(ff_pck.ff)); + + ff_pck.report_id = XPADNEO_XB1S_FF_REPORT; + if ((xdata->quirks & XPADNEO_QUIRK_NO_MOTOR_MASK) == 0) { + ff_pck.ff.magnitude_weak = 40; + ff_pck.ff.magnitude_strong = 20; + ff_pck.ff.magnitude_right = 10; + ff_pck.ff.magnitude_left = 10; + } + + if ((xdata->quirks & XPADNEO_QUIRK_NO_PULSE) == 0) { + ff_pck.ff.pulse_sustain_10ms = 5; + ff_pck.ff.pulse_release_10ms = 5; + ff_pck.ff.loop_count = 3; + } + + /* + * TODO Think about a way of dry'ing the following blocks in a way + * that doesn't compromise the testing nature of this + */ + + if (xdata->quirks & XPADNEO_QUIRK_NO_MOTOR_MASK) + ff_pck.ff.magnitude_weak = 40; + else if (xdata->quirks & XPADNEO_QUIRK_REVERSE_MASK) + ff_pck.ff.enable = FF_RUMBLE_LEFT; + else + ff_pck.ff.enable = FF_RUMBLE_WEAK; + hid_hw_output_report(hdev, (u8 *)&ff_pck, sizeof(ff_pck)); + mdelay(300); + if (xdata->quirks & XPADNEO_QUIRK_NO_PULSE) { + u8 save = ff_pck.ff.magnitude_weak; + ff_pck.ff.magnitude_weak = 0; + hid_hw_output_report(hdev, (u8 *)&ff_pck, sizeof(ff_pck)); + ff_pck.ff.magnitude_weak = save; + } + ff_pck.ff.enable = 0; + mdelay(30); + + if (xdata->quirks & XPADNEO_QUIRK_NO_MOTOR_MASK) + ff_pck.ff.magnitude_strong = 20; + else if (xdata->quirks & XPADNEO_QUIRK_REVERSE_MASK) + ff_pck.ff.enable = FF_RUMBLE_RIGHT; + else + ff_pck.ff.enable = FF_RUMBLE_STRONG; + hid_hw_output_report(hdev, (u8 *)&ff_pck, sizeof(ff_pck)); + mdelay(300); + if (xdata->quirks & XPADNEO_QUIRK_NO_PULSE) { + u8 save = ff_pck.ff.magnitude_strong; + ff_pck.ff.magnitude_strong = 0; + hid_hw_output_report(hdev, (u8 *)&ff_pck, sizeof(ff_pck)); + ff_pck.ff.magnitude_strong = save; + } + ff_pck.ff.enable = 0; + mdelay(30); + + if ((xdata->quirks & XPADNEO_QUIRK_NO_TRIGGER_RUMBLE) == 0) { + if (xdata->quirks & XPADNEO_QUIRK_NO_MOTOR_MASK) { + ff_pck.ff.magnitude_left = 10; + ff_pck.ff.magnitude_right = 10; + } else if (xdata->quirks & XPADNEO_QUIRK_REVERSE_MASK) { + ff_pck.ff.enable = FF_RUMBLE_MAIN; + } else { + ff_pck.ff.enable = FF_RUMBLE_TRIGGERS; + } + hid_hw_output_report(hdev, (u8 *)&ff_pck, sizeof(ff_pck)); + mdelay(300); + if (xdata->quirks & XPADNEO_QUIRK_NO_PULSE) { + u8 lsave = ff_pck.ff.magnitude_left; + u8 rsave = ff_pck.ff.magnitude_right; + ff_pck.ff.magnitude_left = 0; + ff_pck.ff.magnitude_right = 0; + hid_hw_output_report(hdev, (u8 *)&ff_pck, sizeof(ff_pck)); + ff_pck.ff.magnitude_left = lsave; + ff_pck.ff.magnitude_right = rsave; + } + ff_pck.ff.enable = 0; + mdelay(30); + } +} + +static int xpadneo_init_ff(struct hid_device *hdev) +{ + struct xpadneo_devdata *xdata = hid_get_drvdata(hdev); + struct input_dev *gamepad = xdata->gamepad; + + INIT_DELAYED_WORK(&xdata->ff_worker, xpadneo_ff_worker); + xdata->output_report_dmabuf = devm_kzalloc(&hdev->dev, + sizeof(struct ff_report), GFP_KERNEL); + if (xdata->output_report_dmabuf == NULL) + return -ENOMEM; + + if (param_trigger_rumble_mode == PARAM_TRIGGER_RUMBLE_DISABLE) + xdata->quirks |= XPADNEO_QUIRK_NO_TRIGGER_RUMBLE; + + if (param_ff_connect_notify) { + xpadneo_benchmark_start(xpadneo_welcome_rumble); + xpadneo_welcome_rumble(hdev); + xpadneo_benchmark_stop(xpadneo_welcome_rumble); + } + + /* initialize our rumble command throttle */ + xdata->ff_throttle_until = XPADNEO_RUMBLE_THROTTLE_JIFFIES; + + input_set_capability(gamepad, EV_FF, FF_RUMBLE); + return input_ff_create_memless(gamepad, NULL, xpadneo_ff_play); +} + +#define XPADNEO_PSY_ONLINE(data) ((data&0x80)>0) +#define XPADNEO_PSY_MODE(data) ((data&0x0C)>>2) + +#define XPADNEO_POWER_USB(data) (XPADNEO_PSY_MODE(data)==0) +#define XPADNEO_POWER_BATTERY(data) (XPADNEO_PSY_MODE(data)==1) +#define XPADNEO_POWER_PNC(data) (XPADNEO_PSY_MODE(data)==2) + +#define XPADNEO_BATTERY_ONLINE(data) ((data&0x0C)>0) +#define XPADNEO_BATTERY_CHARGING(data) ((data&0x10)>0) +#define XPADNEO_BATTERY_CAPACITY_LEVEL(data) (data&0x03) + +static int xpadneo_get_psy_property(struct power_supply *psy, + enum power_supply_property property, + union power_supply_propval *val) +{ + struct xpadneo_devdata *xdata = power_supply_get_drvdata(psy); + int ret = 0; + + static int capacity_level_map[] = { + [0] = POWER_SUPPLY_CAPACITY_LEVEL_LOW, + [1] = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL, + [2] = POWER_SUPPLY_CAPACITY_LEVEL_HIGH, + [3] = POWER_SUPPLY_CAPACITY_LEVEL_FULL, + }; + + u8 flags = xdata->battery.flags; + u8 level = min(3, XPADNEO_BATTERY_CAPACITY_LEVEL(flags)); + bool online = XPADNEO_PSY_ONLINE(flags); + bool charging = XPADNEO_BATTERY_CHARGING(flags); + + switch (property) { + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + if (online && XPADNEO_BATTERY_ONLINE(flags)) + val->intval = capacity_level_map[level]; + else + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + break; + + case POWER_SUPPLY_PROP_MODEL_NAME: + if (online && XPADNEO_POWER_PNC(flags)) + val->strval = xdata->battery.name_pnc; + else + val->strval = xdata->battery.name; + break; + + case POWER_SUPPLY_PROP_ONLINE: + val->intval = online && XPADNEO_BATTERY_ONLINE(flags); + break; + + case POWER_SUPPLY_PROP_PRESENT: + val->intval = online && (XPADNEO_BATTERY_ONLINE(flags) || charging); + break; + + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_DEVICE; + break; + + case POWER_SUPPLY_PROP_STATUS: + if (online && charging) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (online && !charging && XPADNEO_POWER_USB(flags)) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (online && XPADNEO_BATTERY_ONLINE(flags)) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int xpadneo_setup_psy(struct hid_device *hdev) +{ + struct xpadneo_devdata *xdata = hid_get_drvdata(hdev); + struct power_supply_config ps_config = { + .drv_data = xdata + }; + int ret; + + /* already registered */ + if (xdata->battery.psy) + return 0; + + xdata->battery.desc.name = kasprintf(GFP_KERNEL, "xpadneo_battery_%i", xdata->id); + if (!xdata->battery.desc.name) + return -ENOMEM; + + xdata->battery.desc.properties = xpadneo_battery_props; + xdata->battery.desc.num_properties = ARRAY_SIZE(xpadneo_battery_props); + xdata->battery.desc.use_for_apm = 0; + xdata->battery.desc.get_property = xpadneo_get_psy_property; + xdata->battery.desc.type = POWER_SUPPLY_TYPE_BATTERY; + + /* register battery via device manager */ + xdata->battery.psy = + devm_power_supply_register(&hdev->dev, &xdata->battery.desc, &ps_config); + if (IS_ERR(xdata->battery.psy)) { + ret = PTR_ERR(xdata->battery.psy); + hid_err(hdev, "battery registration failed\n"); + goto err_free_name; + } else { + hid_info(hdev, "battery registered\n"); + } + + power_supply_powers(xdata->battery.psy, &xdata->hdev->dev); + + return 0; + +err_free_name: + kfree(xdata->battery.desc.name); + xdata->battery.desc.name = NULL; + + return ret; +} + +static void xpadneo_update_psy(struct xpadneo_devdata *xdata, u8 value) +{ + int old_value = xdata->battery.flags; + + if (!xdata->battery.initialized && XPADNEO_PSY_ONLINE(value)) { + xdata->battery.initialized = true; + xpadneo_setup_psy(xdata->hdev); + } + + if (!xdata->battery.psy) + return; + + xdata->battery.flags = value; + if (old_value != value) { + if (!XPADNEO_PSY_ONLINE(value)) + hid_info(xdata->hdev, "shutting down\n"); + power_supply_changed(xdata->battery.psy); + } +} + +#define xpadneo_map_usage_clear(ev) hid_map_usage_clear(hi, usage, bit, max, (ev).event_type, (ev).input_code) +static int xpadneo_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, int *max) +{ + int i = 0; + + if (usage->hid == HID_DC_BATTERYSTRENGTH) { + struct xpadneo_devdata *xdata = hid_get_drvdata(hdev); + + xdata->battery.report_id = field->report->id; + hid_info(hdev, "battery detected\n"); + + return MAP_IGNORE; + } + + for (i = 0; i < ARRAY_SIZE(xpadneo_usage_maps); i++) { + const struct usage_map *entry = &xpadneo_usage_maps[i]; + + if (entry->usage == usage->hid) { + if (entry->behaviour == MAP_STATIC) + xpadneo_map_usage_clear(entry->ev); + return entry->behaviour; + } + } + + /* let HID handle this */ + return MAP_AUTO; +} + +static u8 *xpadneo_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *rsize) +{ + struct xpadneo_devdata *xdata = hid_get_drvdata(hdev); + + xdata->original_rsize = *rsize; + hid_info(hdev, "report descriptor size: %d bytes\n", *rsize); + + /* fixup trailing NUL byte */ + if (rdesc[*rsize - 2] == 0xC0 && rdesc[*rsize - 1] == 0x00) { + hid_notice(hdev, "fixing up report descriptor size\n"); + *rsize -= 1; + } + + /* fixup reported axes for Xbox One S */ + if (*rsize >= 81) { + if (rdesc[34] == 0x09 && rdesc[35] == 0x32) { + hid_notice(hdev, "fixing up Rx axis\n"); + rdesc[35] = 0x33; /* Z --> Rx */ + } + if (rdesc[36] == 0x09 && rdesc[37] == 0x35) { + hid_notice(hdev, "fixing up Ry axis\n"); + rdesc[37] = 0x34; /* Rz --> Ry */ + } + if (rdesc[52] == 0x05 && rdesc[53] == 0x02 && + rdesc[54] == 0x09 && rdesc[55] == 0xC5) { + hid_notice(hdev, "fixing up Z axis\n"); + rdesc[53] = 0x01; /* Simulation -> Gendesk */ + rdesc[55] = 0x32; /* Brake -> Z */ + } + if (rdesc[77] == 0x05 && rdesc[78] == 0x02 && + rdesc[79] == 0x09 && rdesc[80] == 0xC4) { + hid_notice(hdev, "fixing up Rz axis\n"); + rdesc[78] = 0x01; /* Simulation -> Gendesk */ + rdesc[80] = 0x35; /* Accelerator -> Rz */ + } + } + + /* fixup reported button count for Xbox controllers in Linux mode */ + if (*rsize >= 164) { + /* + * 12 buttons instead of 10: properly remap the + * Xbox button (button 11) + * Share button (button 12) + */ + if (rdesc[140] == 0x05 && rdesc[141] == 0x09 && + rdesc[144] == 0x29 && rdesc[145] == 0x0F && + rdesc[152] == 0x95 && rdesc[153] == 0x0F && + rdesc[162] == 0x95 && rdesc[163] == 0x01) { + hid_notice(hdev, "fixing up button mapping\n"); + xdata->quirks |= XPADNEO_QUIRK_LINUX_BUTTONS; + rdesc[145] = 0x0C; /* 15 buttons -> 12 buttons */ + rdesc[153] = 0x0C; /* 15 bits -> 12 bits buttons */ + rdesc[163] = 0x04; /* 1 bit -> 4 bits constants */ + } + } + + return rdesc; +} + +static void xpadneo_toggle_mouse(struct xpadneo_devdata *xdata) +{ + if (xdata->mouse_mode) { + xdata->mouse_mode = false; + hid_info(xdata->hdev, "mouse mode disabled\n"); + } else { + xdata->mouse_mode = true; + hid_info(xdata->hdev, "mouse mode enabled\n"); + } + + /* Indicate that a request was made */ + xdata->profile_switched = true; +} + +static void xpadneo_switch_profile(struct xpadneo_devdata *xdata, const u8 profile, + const bool emulated) +{ + if (xdata->profile != profile) { + hid_info(xdata->hdev, "switching profile to %d\n", profile); + xdata->profile = profile; + } + + /* Indicate to profile emulation that a request was made */ + xdata->profile_switched = emulated; +} + +static void xpadneo_switch_triggers(struct xpadneo_devdata *xdata, const u8 mode) +{ + char *name[XBOX_TRIGGER_SCALE_NUM] = { + [XBOX_TRIGGER_SCALE_FULL] = "full range", + [XBOX_TRIGGER_SCALE_HALF] = "half range", + [XBOX_TRIGGER_SCALE_DIGITAL] = "digital", + }; + + enum xpadneo_trigger_scale left = (mode >> 0) & 0x03; + enum xpadneo_trigger_scale right = (mode >> 2) & 0x03; + + if ((xdata->trigger_scale.left != left) && (left < XBOX_TRIGGER_SCALE_NUM)) { + hid_info(xdata->hdev, "switching left trigger to %s mode\n", name[left]); + xdata->trigger_scale.left = left; + } + + if ((xdata->trigger_scale.right != right) && (right < XBOX_TRIGGER_SCALE_NUM)) { + hid_info(xdata->hdev, "switching right trigger to %s mode\n", name[right]); + xdata->trigger_scale.right = right; + } +} + +static int xpadneo_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int reportsize) +{ + struct xpadneo_devdata *xdata = hid_get_drvdata(hdev); + + /* the controller spams reports multiple times */ + if (likely(report->id == 0x01)) { + int size = min(reportsize, (int)XPADNEO_REPORT_0x01_LENGTH); + if (likely(memcmp(&xdata->input_report_0x01, data, size) == 0)) + return -1; + memcpy(&xdata->input_report_0x01, data, size); + } + + /* reset the count at the beginning of the frame */ + xdata->count_abs_z_rz = 0; + + /* we are taking care of the battery report ourselves */ + if (xdata->battery.report_id && report->id == xdata->battery.report_id && reportsize == 2) { + xpadneo_update_psy(xdata, data[1]); + return -1; + } + + /* correct button mapping of Xbox controllers in Linux mode */ + if ((xdata->quirks & XPADNEO_QUIRK_LINUX_BUTTONS) && report->id == 1 && reportsize >= 17) { + u16 bits = 0; + + bits |= (data[14] & (BIT(0) | BIT(1))) >> 0; /* A, B */ + bits |= (data[14] & (BIT(3) | BIT(4))) >> 1; /* X, Y */ + bits |= (data[14] & (BIT(6) | BIT(7))) >> 2; /* LB, RB */ + if (xdata->quirks & XPADNEO_QUIRK_SHARE_BUTTON) + bits |= (data[15] & BIT(2)) << 4; /* Back */ + else + bits |= (data[16] & BIT(0)) << 6; /* Back */ + bits |= (data[15] & BIT(3)) << 4; /* Menu */ + bits |= (data[15] & BIT(5)) << 3; /* LS */ + bits |= (data[15] & BIT(6)) << 3; /* RS */ + bits |= (data[15] & BIT(4)) << 6; /* Xbox */ + if (xdata->quirks & XPADNEO_QUIRK_SHARE_BUTTON) + bits |= (data[16] & BIT(0)) << 11; /* Share */ + data[14] = (u8)((bits >> 0) & 0xFF); + data[15] = (u8)((bits >> 8) & 0xFF); + data[16] = 0; + } + + /* swap button A with B and X with Y for Nintendo style controllers */ + if ((xdata->quirks & XPADNEO_QUIRK_NINTENDO) && report->id == 1 && reportsize >= 15) { + data[14] = SWAP_BITS(data[14], 0, 1); + data[14] = SWAP_BITS(data[14], 2, 3); + } + + /* XBE2: track the current controller settings */ + if (report->id == 1 && reportsize >= 20) { + if (reportsize == 55) { + hid_notice_once(hdev, + "detected broken XBE2 v1 packet format, please update the firmware"); + xpadneo_switch_profile(xdata, data[35] & 0x03, false); + xpadneo_switch_triggers(xdata, data[36] & 0x0F); + } else if (reportsize >= 21) { + /* firmware 4.x style packet */ + xpadneo_switch_profile(xdata, data[19] & 0x03, false); + xpadneo_switch_triggers(xdata, data[20] & 0x0F); + } else { + /* firmware 5.x style packet */ + xpadneo_switch_profile(xdata, data[17] & 0x03, false); + xpadneo_switch_triggers(xdata, data[18] & 0x0F); + } + } + + return 0; +} + +static int xpadneo_input_configured(struct hid_device *hdev, struct hid_input *hi) +{ + struct xpadneo_devdata *xdata = hid_get_drvdata(hdev); + int deadzone = 3072, abs_min = 0, abs_max = 65535; + + switch (hi->application) { + case HID_GD_GAMEPAD: + hid_info(hdev, "gamepad detected\n"); + xdata->gamepad = hi->input; + break; + case HID_GD_KEYBOARD: + hid_info(hdev, "keyboard detected\n"); + xdata->keyboard = hi->input; + return 0; + case HID_CP_CONSUMER_CONTROL: + hid_info(hdev, "consumer control detected\n"); + xdata->consumer = hi->input; + return 0; + case 0xFF000005: + hid_info(hdev, "mapping profiles detected\n"); + xdata->quirks |= XPADNEO_QUIRK_USE_HW_PROFILES; + return 0; + default: + hid_warn(hdev, "unhandled input application 0x%x\n", hi->application); + } + + if (param_disable_deadzones) { + hid_warn(hdev, "disabling dead zones\n"); + deadzone = 0; + } + + if (param_gamepad_compliance) { + hid_info(hdev, "enabling compliance with Linux Gamepad Specification\n"); + abs_min = -32768; + abs_max = 32767; + } + + input_set_abs_params(xdata->gamepad, ABS_X, abs_min, abs_max, 32, deadzone); + input_set_abs_params(xdata->gamepad, ABS_Y, abs_min, abs_max, 32, deadzone); + input_set_abs_params(xdata->gamepad, ABS_RX, abs_min, abs_max, 32, deadzone); + input_set_abs_params(xdata->gamepad, ABS_RY, abs_min, abs_max, 32, deadzone); + + input_set_abs_params(xdata->gamepad, ABS_Z, 0, 1023, 4, 0); + input_set_abs_params(xdata->gamepad, ABS_RZ, 0, 1023, 4, 0); + + /* combine triggers to form a rudder, use ABS_MISC to order after dpad */ + input_set_abs_params(xdata->gamepad, ABS_MISC, -1023, 1023, 3, 63); + + /* do not report the keyboard buttons as part of the gamepad */ + __clear_bit(BTN_SHARE, xdata->gamepad->keybit); + + /* add paddles as part of the gamepad */ + __set_bit(BTN_PADDLES(0), xdata->gamepad->keybit); + __set_bit(BTN_PADDLES(1), xdata->gamepad->keybit); + __set_bit(BTN_PADDLES(2), xdata->gamepad->keybit); + __set_bit(BTN_PADDLES(3), xdata->gamepad->keybit); + + return 0; +} + +static int xpadneo_event(struct hid_device *hdev, struct hid_field *field, + struct hid_usage *usage, __s32 value) +{ + struct xpadneo_devdata *xdata = hid_get_drvdata(hdev); + struct input_dev *gamepad = xdata->gamepad; + struct input_dev *keyboard = xdata->keyboard; + + if ((usage->type == EV_KEY) && (usage->code == BTN_PADDLES(0))) { + if (gamepad && xdata->profile == 0) { + /* report the paddles individually */ + input_report_key(gamepad, BTN_PADDLES(0), value & 1 ? 1 : 0); + input_report_key(gamepad, BTN_PADDLES(1), value & 2 ? 1 : 0); + input_report_key(gamepad, BTN_PADDLES(2), value & 4 ? 1 : 0); + input_report_key(gamepad, BTN_PADDLES(3), value & 8 ? 1 : 0); + } + goto stop_processing; + } else if (usage->type == EV_ABS) { + switch (usage->code) { + case ABS_X: + case ABS_Y: + case ABS_RX: + case ABS_RY: + /* Linux Gamepad Specification */ + if (param_gamepad_compliance) { + input_report_abs(gamepad, usage->code, value - 32768); + /* no need to sync here */ + goto stop_processing; + } + break; + case ABS_Z: + xdata->last_abs_z = value; + goto combine_z_axes; + case ABS_RZ: + xdata->last_abs_rz = value; + goto combine_z_axes; + } + } else if ((usage->type == EV_KEY) && (usage->code == BTN_XBOX)) { + /* + * Handle the Xbox logo button: We want to cache the button + * down event to allow for profile switching. The button will + * act as a shift key and only send the input events when + * released without pressing an additional button. + */ + if (!xdata->xbox_button_down && (value == 1)) { + /* cache this event */ + xdata->xbox_button_down = true; + } else if (xdata->xbox_button_down && (value == 0)) { + xdata->xbox_button_down = false; + if (xdata->profile_switched) { + xdata->profile_switched = false; + } else { + /* replay cached event */ + input_report_key(gamepad, BTN_XBOX, 1); + input_sync(gamepad); + /* synthesize the release to remove the scan code */ + input_report_key(gamepad, BTN_XBOX, 0); + input_sync(gamepad); + } + } + goto stop_processing; + } else if ((usage->type == EV_KEY) && (usage->code == BTN_SHARE)) { + /* move the Share button to the keyboard device */ + if (!keyboard) + goto keyboard_missing; + input_report_key(keyboard, BTN_SHARE, value); + input_sync(keyboard); + goto stop_processing; + } else if (xdata->xbox_button_down && (usage->type == EV_KEY)) { + if (!(xdata->quirks & XPADNEO_QUIRK_USE_HW_PROFILES)) { + switch (usage->code) { + case BTN_A: + if (value == 1) + xpadneo_switch_profile(xdata, 0, true); + goto stop_processing; + case BTN_B: + if (value == 1) + xpadneo_switch_profile(xdata, 1, true); + goto stop_processing; + case BTN_X: + if (value == 1) + xpadneo_switch_profile(xdata, 2, true); + goto stop_processing; + case BTN_Y: + if (value == 1) + xpadneo_switch_profile(xdata, 3, true); + goto stop_processing; + case BTN_SELECT: + if (value == 1) + xpadneo_toggle_mouse(xdata); + goto stop_processing; + } + } + } + + /* Let hid-core handle the event */ + return 0; + +combine_z_axes: + if (++xdata->count_abs_z_rz == 2) { + xdata->count_abs_z_rz = 0; + input_report_abs(gamepad, ABS_MISC, xdata->last_abs_rz - xdata->last_abs_z); + } + return 0; + +keyboard_missing: + if ((xdata->missing_reported && XPADNEO_MISSING_KEYBOARD) == 0) { + xdata->missing_reported |= XPADNEO_MISSING_KEYBOARD; + hid_err(hdev, "keyboard not detected\n"); + } + +stop_processing: + return 1; +} + +static int xpadneo_init_hw(struct hid_device *hdev) +{ + int i, ret; + struct xpadneo_devdata *xdata = hid_get_drvdata(hdev); + + if (!xdata->gamepad) { + if ((xdata->missing_reported && XPADNEO_MISSING_GAMEPAD) == 0) { + xdata->missing_reported |= XPADNEO_MISSING_GAMEPAD; + hid_err(hdev, "gamepad not detected\n"); + } + return -EINVAL; + } + + xdata->battery.name = + kasprintf(GFP_KERNEL, "%s [%s]", xdata->gamepad->name, xdata->gamepad->uniq); + if (!xdata->battery.name) { + ret = -ENOMEM; + goto err_free_name; + } + + xdata->battery.name_pnc = + kasprintf(GFP_KERNEL, "%s [%s] Play'n Charge Kit", xdata->gamepad->name, + xdata->gamepad->uniq); + if (!xdata->battery.name_pnc) { + ret = -ENOMEM; + goto err_free_name; + } + + for (i = 0; i < ARRAY_SIZE(xpadneo_quirks); i++) { + const struct quirk *q = &xpadneo_quirks[i]; + + if (q->name_match + && (strncmp(q->name_match, xdata->gamepad->name, q->name_len) == 0)) + xdata->quirks |= q->flags; + + if (q->oui_match && (strncasecmp(q->oui_match, xdata->gamepad->uniq, 8) == 0)) + xdata->quirks |= q->flags; + } + + kernel_param_lock(THIS_MODULE); + for (i = 0; i < param_quirks.nargs; i++) { + int offset = strnlen(xdata->gamepad->uniq, 18); + if ((strncasecmp(xdata->gamepad->uniq, param_quirks.args[i], offset) == 0) + && ((param_quirks.args[i][offset] == ':') + || (param_quirks.args[i][offset] == '+') + || (param_quirks.args[i][offset] == '-'))) { + char *quirks_arg = ¶m_quirks.args[i][offset + 1]; + u32 quirks = 0; + ret = kstrtou32(quirks_arg, 0, &quirks); + if (ret) { + hid_err(hdev, "quirks override invalid: %s\n", quirks_arg); + goto err_free_name; + } else if (param_quirks.args[i][offset] == ':') { + hid_info(hdev, "quirks override: %s\n", xdata->gamepad->uniq); + xdata->quirks = quirks; + } else if (param_quirks.args[i][offset] == '-') { + hid_info(hdev, "quirks removed: %s flag 0x%08X\n", + xdata->gamepad->uniq, quirks); + xdata->quirks &= ~quirks; + } else { + hid_info(hdev, "quirks added: %s flags 0x%08X\n", + xdata->gamepad->uniq, quirks); + xdata->quirks |= quirks; + } + break; + } + } + kernel_param_unlock(THIS_MODULE); + + if (xdata->quirks > 0) + hid_info(hdev, "controller quirks: 0x%08x\n", xdata->quirks); + + return 0; + +err_free_name: + kfree(xdata->battery.name); + xdata->battery.name = NULL; + + kfree(xdata->battery.name_pnc); + xdata->battery.name_pnc = NULL; + + return ret; +} + +static int xpadneo_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret; + struct xpadneo_devdata *xdata; + + xdata = devm_kzalloc(&hdev->dev, sizeof(*xdata), GFP_KERNEL); + if (xdata == NULL) + return -ENOMEM; + + xdata->id = ida_simple_get(&xpadneo_device_id_allocator, 0, 0, GFP_KERNEL); + xdata->quirks = id->driver_data; + + xdata->hdev = hdev; + hdev->quirks |= HID_QUIRK_INPUT_PER_APP; + hid_set_drvdata(hdev, xdata); + + if (hdev->version == 0x00000903) + hid_warn(hdev, "buggy firmware detected, please upgrade to the latest version\n"); + else if (hdev->version < 0x00000500) + hid_warn(hdev, + "classic Bluetooth firmware version %x.%02x, please upgrade for better stability\n", + hdev->version >> 8, (u8)hdev->version); + else if (hdev->version < 0x00000512) + hid_warn(hdev, + "BLE firmware version %x.%02x, please upgrade for better stability\n", + hdev->version >> 8, (u8)hdev->version); + else + hid_info(hdev, "BLE firmware version %x.%02x\n", + hdev->version >> 8, (u8)hdev->version); + + /* + * Pretend that we are in Windows pairing mode as we are actually + * exposing the Windows mapping. This prevents SDL and other layers + * (probably browser game controller APIs) from treating our driver + * unnecessarily with button and axis mapping fixups, and it seems + * this is actually a firmware mode meant for Android usage only: + * + * Xbox One S: + * 0x2E0 wireless Windows mode (non-Android mode) + * 0x2EA USB Windows and Linux mode + * 0x2FD wireless Linux mode (Android mode) + * + * Xbox Elite 2: + * 0xB00 USB Windows and Linux mode + * 0xB05 wireless Linux mode (Android mode) + * + * Xbox Series X|S: + * 0xB12 Dongle, USB Windows and USB Linux mode + * 0xB13 wireless Linux mode (Android mode) + * + * Xbox Controller BLE mode: + * 0xB20 wireless BLE mode + * + * TODO: We should find a better way of doing this so SDL2 could + * still detect our driver as the correct model. Currently this + * maps all controllers to the same model. + */ + xdata->original_product = hdev->product; + xdata->original_version = hdev->version; + hdev->product = 0x028E; + hdev->version = 0x00001130; + + if (hdev->product != xdata->original_product) + hid_info(hdev, + "pretending XB1S Windows wireless mode " + "(changed PID from 0x%04X to 0x%04X)\n", xdata->original_product, + hdev->product); + + if (hdev->version != xdata->original_version) + hid_info(hdev, + "working around wrong SDL2 mappings " + "(changed version from 0x%08X to 0x%08X)\n", xdata->original_version, + hdev->version); + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "parse failed\n"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "hw start failed\n"); + return ret; + } + + ret = xpadneo_init_consumer(xdata); + if (ret) + return ret; + + ret = xpadneo_init_keyboard(xdata); + if (ret) + return ret; + + ret = xpadneo_init_hw(hdev); + if (ret) { + hid_err(hdev, "hw init failed: %d\n", ret); + hid_hw_stop(hdev); + return ret; + } + + ret = xpadneo_init_ff(hdev); + if (ret) + hid_err(hdev, "could not initialize ff, continuing anyway\n"); + + hid_info(hdev, "%s connected\n", xdata->battery.name); + + return 0; +} + +static void xpadneo_release_device_id(struct xpadneo_devdata *xdata) +{ + if (xdata->id >= 0) { + ida_simple_remove(&xpadneo_device_id_allocator, xdata->id); + xdata->id = -1; + } +} + +static void xpadneo_remove(struct hid_device *hdev) +{ + struct xpadneo_devdata *xdata = hid_get_drvdata(hdev); + + hid_hw_close(hdev); + + if (hdev->version != xdata->original_version) { + hid_info(hdev, + "reverting to original version " + "(changed version from 0x%08X to 0x%08X)\n", + hdev->version, xdata->original_version); + hdev->version = xdata->original_version; + } + + if (hdev->product != xdata->original_product) { + hid_info(hdev, + "reverting to original product " + "(changed PID from 0x%04X to 0x%04X)\n", + hdev->product, xdata->original_product); + hdev->product = xdata->original_product; + } + + cancel_delayed_work_sync(&xdata->ff_worker); + + kfree(xdata->battery.name); + xdata->battery.name = NULL; + + kfree(xdata->battery.name_pnc); + xdata->battery.name_pnc = NULL; + + xpadneo_release_device_id(xdata); + hid_hw_stop(hdev); +} + +static const struct hid_device_id xpadneo_devices[] = { + /* XBOX ONE S / X */ + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x02FD) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x02E0) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x0B20), + .driver_data = XPADNEO_QUIRK_SHARE_BUTTON }, + + /* XBOX ONE Elite Series 2 */ + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x0B05) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x0B22), + .driver_data = XPADNEO_QUIRK_SHARE_BUTTON }, + + /* XBOX Series X|S / Xbox Wireless Controller (BLE) */ + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x0B13), + .driver_data = XPADNEO_QUIRK_SHARE_BUTTON }, + + /* SENTINEL VALUE, indicates the end */ + { } +}; + +MODULE_DEVICE_TABLE(hid, xpadneo_devices); + +static struct hid_driver xpadneo_driver = { + .name = "xpadneo", + .id_table = xpadneo_devices, + .input_mapping = xpadneo_input_mapping, + .input_configured = xpadneo_input_configured, + .probe = xpadneo_probe, + .remove = xpadneo_remove, + .report_fixup = xpadneo_report_fixup, + .raw_event = xpadneo_raw_event, + .event = xpadneo_event, +}; + +static int __init xpadneo_init(void) +{ + pr_info("loaded hid-xpadneo %s\n", XPADNEO_VERSION); + dbg_hid("xpadneo:%s\n", __func__); + + if (param_trigger_rumble_mode == 1) + pr_warn("hid-xpadneo trigger_rumble_mode=1 is unknown, defaulting to 0\n"); + + xpadneo_rumble_wq = alloc_ordered_workqueue("xpadneo/rumbled", WQ_HIGHPRI); + if (xpadneo_rumble_wq) { + int ret = hid_register_driver(&xpadneo_driver); + if (ret != 0) { + destroy_workqueue(xpadneo_rumble_wq); + xpadneo_rumble_wq = NULL; + } + return ret; + } + return -EINVAL; +} + +static void __exit xpadneo_exit(void) +{ + dbg_hid("xpadneo:%s\n", __func__); + hid_unregister_driver(&xpadneo_driver); + ida_destroy(&xpadneo_device_id_allocator); + destroy_workqueue(xpadneo_rumble_wq); +} + +module_init(xpadneo_init); +module_exit(xpadneo_exit); diff --git a/drivers/hid/xpadneo/xpadneo.h b/drivers/hid/xpadneo/xpadneo.h new file mode 100644 index 00000000000000..38fa0fd31dabf5 --- /dev/null +++ b/drivers/hid/xpadneo/xpadneo.h @@ -0,0 +1,186 @@ +/* + * Force feedback support for XBOX ONE S and X gamepads via Bluetooth + * + * This driver was developed for a student project at fortiss GmbH in Munich. + * Copyright (c) 2017 Florian Dollinger + * + * Additional features and code redesign + * Copyright (c) 2020 Kai Krakow + */ + +#ifndef XPADNEO_H_FILE +#define XPADNEO_H_FILE + +#include + +#include "../hid-ids.h" + +#define XPADNEO_VERSION "v0.9.9999-git" + +/* helper for printing a notice only once */ +#ifndef hid_notice_once +#define hid_notice_once(hid, fmt, ...) \ +do { \ + static bool __print_once __read_mostly; \ + if (!__print_once) { \ + __print_once = true; \ + hid_notice(hid, fmt, ##__VA_ARGS__); \ + } \ +} while (0) +#endif + +/* benchmark helper */ +#define xpadneo_benchmark_start(name) \ +do { \ + unsigned long __##name_jiffies = jiffies; \ + pr_info("xpadneo " #name " start\n") +#define xpadneo_benchmark_stop(name) \ + pr_info("xpadneo " #name " took %ums\n", jiffies_to_msecs(jiffies - __##name_jiffies)); \ +} while (0) + +/* button aliases */ +#define BTN_PADDLES(b) (BTN_TRIGGER_HAPPY37+(b)) +#define BTN_SHARE KEY_F12 +#define BTN_XBOX BTN_MODE + +/* module parameter "trigger_rumble_mode" */ +#define PARAM_TRIGGER_RUMBLE_PRESSURE 0 +#define PARAM_TRIGGER_RUMBLE_RESERVED 1 +#define PARAM_TRIGGER_RUMBLE_DISABLE 2 + +/* module parameter "quirks" */ +#define XPADNEO_QUIRK_NO_PULSE 1 +#define XPADNEO_QUIRK_NO_TRIGGER_RUMBLE 2 +#define XPADNEO_QUIRK_NO_MOTOR_MASK 4 +#define XPADNEO_QUIRK_USE_HW_PROFILES 8 +#define XPADNEO_QUIRK_LINUX_BUTTONS 16 +#define XPADNEO_QUIRK_NINTENDO 32 +#define XPADNEO_QUIRK_SHARE_BUTTON 64 +#define XPADNEO_QUIRK_REVERSE_MASK 128 + +/* timing of rumble commands to work around firmware crashes */ +#define XPADNEO_RUMBLE_THROTTLE_DELAY msecs_to_jiffies(50) +#define XPADNEO_RUMBLE_THROTTLE_JIFFIES (jiffies + XPADNEO_RUMBLE_THROTTLE_DELAY) + +/* helpers */ +#define SWAP_BITS(v,b1,b2) \ + (((v)>>(b1)&1)==((v)>>(b2)&1)?(v):(v^(1ULL<<(b1))^(1ULL<<(b2)))) + +/* rumble motors enable bits */ +enum xpadneo_rumble_motors { + FF_RUMBLE_NONE = 0x00, + FF_RUMBLE_WEAK = 0x01, + FF_RUMBLE_STRONG = 0x02, + FF_RUMBLE_MAIN = FF_RUMBLE_WEAK | FF_RUMBLE_STRONG, + FF_RUMBLE_RIGHT = 0x04, + FF_RUMBLE_LEFT = 0x08, + FF_RUMBLE_TRIGGERS = FF_RUMBLE_LEFT | FF_RUMBLE_RIGHT, + FF_RUMBLE_ALL = 0x0F +} __packed; + +/* rumble packet structure */ +struct ff_data { + enum xpadneo_rumble_motors enable; + u8 magnitude_left; + u8 magnitude_right; + u8 magnitude_strong; + u8 magnitude_weak; + u8 pulse_sustain_10ms; + u8 pulse_release_10ms; + u8 loop_count; +} __packed; +#ifdef static_assert +static_assert(sizeof(struct ff_data) == 8); +#endif + +/* report number for rumble commands */ +#define XPADNEO_XB1S_FF_REPORT 0x03 + +/* maximum length of report 0x01 for duplicate packet filtering */ +#define XPADNEO_REPORT_0x01_LENGTH (55+1) + +/* HID packet for rumble commands */ +struct ff_report { + u8 report_id; + struct ff_data ff; +} __packed; +#ifdef static_assert +static_assert(sizeof(struct ff_report) == 9); +#endif + +/* trigger range limits implemented in XBE2 controllers */ +enum xpadneo_trigger_scale { + XBOX_TRIGGER_SCALE_FULL, + XBOX_TRIGGER_SCALE_HALF, + XBOX_TRIGGER_SCALE_DIGITAL, + XBOX_TRIGGER_SCALE_NUM +} __packed; + +#define XPADNEO_MISSING_CONSUMER 1 +#define XPADNEO_MISSING_GAMEPAD 2 +#define XPADNEO_MISSING_KEYBOARD 4 + +/* private driver instance data */ +struct xpadneo_devdata { + /* unique physical device id (randomly assigned) */ + int id; + + /* logical device interfaces */ + struct hid_device *hdev; + struct input_dev *consumer, *gamepad, *keyboard; + short int missing_reported; + + /* revert fixups on removal */ + u16 original_product; + u32 original_version; + + /* quirk flags */ + unsigned int original_rsize; + u32 quirks; + + /* profile switching */ + bool xbox_button_down, profile_switched; + u8 profile; + + /* mouse mode */ + bool mouse_mode; + + /* trigger scale */ + struct { + u8 left, right; + } trigger_scale; + + /* battery information */ + struct { + bool initialized; + struct power_supply *psy; + struct power_supply_desc desc; + char *name; + char *name_pnc; + u8 report_id; + u8 flags; + } battery; + + /* duplicate report buffers */ + u8 input_report_0x01[XPADNEO_REPORT_0x01_LENGTH]; + + /* axis states */ + u8 count_abs_z_rz; + s32 last_abs_z; + s32 last_abs_rz; + + /* buffer for ff_worker */ + spinlock_t ff_lock; + struct delayed_work ff_worker; + unsigned long ff_throttle_until; + bool ff_scheduled; + struct ff_data ff; + struct ff_data ff_shadow; + void *output_report_dmabuf; +}; + +extern int xpadneo_init_consumer(struct xpadneo_devdata *); +extern int xpadneo_init_keyboard(struct xpadneo_devdata *); +extern int xpadneo_init_synthetic(struct xpadneo_devdata *, char *, struct input_dev **); + +#endif