hid-xpadneo/src/hid-xpadneo.c
/*
* 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 <dollinger.florian@gmx.de>
*
* Additional features and code redesign
* Copyright (c) 2020 Kai Krakow <kai@kaishome.de>
*/
#include <linux/delay.h>
#include <linux/module.h>
#include "xpadneo.h"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Florian Dollinger <dollinger.florian@gmx.de>");
MODULE_AUTHOR("Kai Krakow <kai@kaishome.de>");
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 bool param_enable_rolling_axis = 0;
module_param_named(enable_rolling_axis, param_enable_rolling_axis, bool, 0444);
MODULE_PARM_DESC(enable_rolling_axis,
"(bool) Enable rolling axis by combining both triggers, out of spec for many games. (deprecated) "
"0: disable, 1: enable.");
static bool param_disable_shift_mode = 0;
module_param_named(disable_shift_mode, param_disable_shift_mode, bool, 0644);
MODULE_PARM_DESC(disable_shift_mode,
"(bool) Disable use Xbox logo button as shift. Will prohibit profile switching when enabled. "
"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("A0:5A:5D", XPADNEO_QUIRK_NO_HAPTICS),
DEVICE_OUI_QUIRK("E4:17:D8", XPADNEO_QUIRK_NO_HAPTICS | XPADNEO_QUIRK_NO_TRIGGER_RUMBLE),
};
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;
u8 save, lsave, rsave;
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
*/
save = ff_pck.ff.magnitude_weak;
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) {
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);
save = ff_pck.ff.magnitude_strong;
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) {
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);
lsave = ff_pck.ff.magnitude_left;
rsave = ff_pck.ff.magnitude_right;
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) {
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 (!(xdata->quirks & XPADNEO_QUIRK_USE_HW_PROFILES)) {
hid_info(hdev, "mapping profiles detected\n");
xdata->quirks |= XPADNEO_QUIRK_USE_HW_PROFILES;
}
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;
/* do not report bogus keys as part of the keyboard */
__clear_bit(KEY_UNKNOWN, xdata->keyboard->keybit);
return 0;
case HID_CP_CONSUMER_CONTROL:
hid_info(hdev, "consumer control detected\n");
xdata->consumer = hi->input;
return 0;
case 0xFF000005:
/* FIXME: this is no longer in the current firmware */
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 */
if (param_enable_rolling_axis) {
hid_info(hdev, "enabling rolling axis is deprecated\n");
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);
__clear_bit(KEY_RECORD, xdata->gamepad->keybit);
__clear_bit(KEY_UNKNOWN, xdata->gamepad->keybit);
/* ensure all four paddles exist as part of the gamepad */
if (test_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);
xdata->gamepad_sync = true;
}
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);
xdata->gamepad_sync = true;
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 (!param_disable_shift_mode && (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);
xdata->keyboard_sync = true;
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 */
xdata->gamepad_sync = true;
return 0;
combine_z_axes:
if (++xdata->count_abs_z_rz == 2) {
xdata->count_abs_z_rz = 0;
if (param_enable_rolling_axis) {
input_report_abs(gamepad, ABS_MISC, xdata->last_abs_rz - xdata->last_abs_z);
xdata->gamepad_sync = true;
}
}
return 0;
keyboard_missing:
xpadneo_core_missing(xdata, XPADNEO_MISSING_KEYBOARD);
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) {
xpadneo_core_missing(xdata, XPADNEO_MISSING_GAMEPAD);
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;
hdev->quirks |= HID_QUIRK_NO_INPUT_SYNC;
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, 0x02E0) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x02FD) },
{ 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 = xpadneo_report,
.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);