Skip to content

Commit

Permalink
hid-xpadneo: Allow the driver to operate in Xpad360 emulation mode
Browse files Browse the repository at this point in the history
This mode disables most advanced features and exposes the device as an
original USB-connected Xbox 360 controller. Use module parameter
`xpad_emulation=1`.

This may be useful for applications like Retroarch or other programs
that need the Guide button exposed as a joystick button. Essentially,
it enables such applications to use the button as a macro modifier
button, and the button will no longer be sent as a consumer control
key (but still announced as being available).

In this mode, joydev inserts the button before the left and right
thumb buttons which may mess up some mappings, especially with joydev.
Since we are pretending to be a USB Xbox360 controller, most programs
will take care of it.

For hidraw programs, we also scale the trigger range down from 0..1023
to 0..255 which makes the trigger slightly less precise.

This parameter cannot be changed at runtime, you must reload the module
with the parameter changed to apply the emulation correctly.

Fixes: atar-axis#237
Affects: atar-axis#273
Fixes: atar-axis#280
Signed-off-by: Kai Krakow <[email protected]>
  • Loading branch information
kakra committed Mar 28, 2021
1 parent dba420a commit 36afa54
Showing 1 changed file with 88 additions and 36 deletions.
124 changes: 88 additions & 36 deletions hid-xpadneo/src/hid-xpadneo.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ 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_xpad_emulation = 0;
module_param_named(xpad_emulation, param_xpad_emulation, bool, 0444);
MODULE_PARM_DESC(xpad_emulation,
"(bool) Enable emulation of original Xpad360 USB controller, disables most advanced features. "
"0: disable, 1: enable.");

static struct {
char *args[17];
unsigned int nargs;
Expand Down Expand Up @@ -356,8 +362,13 @@ static int xpadneo_ff_play(struct input_dev *dev, void *data, struct ff_effect *
break;
case PARAM_TRIGGER_RUMBLE_PRESSURE:
fraction_MAIN = percent_MAIN;
fraction_TL = (xdata->last_abs_z * percent_TRIGGERS + 511) / 1023;
fraction_TR = (xdata->last_abs_rz * percent_TRIGGERS + 511) / 1023;
if (param_xpad_emulation) {
fraction_TL = (xdata->last_abs_z * percent_TRIGGERS + 127) / 255;
fraction_TR = (xdata->last_abs_rz * percent_TRIGGERS + 127) / 255;
} else {
fraction_TL = (xdata->last_abs_z * percent_TRIGGERS + 511) / 1023;
fraction_TR = (xdata->last_abs_rz * percent_TRIGGERS + 511) / 1023;
}
break;
default:
fraction_MAIN = percent_MAIN;
Expand Down Expand Up @@ -674,17 +685,27 @@ 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)
{
struct xpadneo_devdata *xdata = hid_get_drvdata(hdev);
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;
}

/* force emulation of original USB Xpad360 */
if (param_xpad_emulation) {
switch (usage->hid) {
case 0x10085:
case 0xC0223:
case 0x9000B:
hid_map_usage_clear(hi, usage, bit, max, EV_KEY, BTN_MODE);
return MAP_STATIC;
}
}

for (i = 0; i < ARRAY_SIZE(xpadneo_usage_maps); i++) {
const struct usage_map *entry = &xpadneo_usage_maps[i];

Expand Down Expand Up @@ -806,6 +827,7 @@ static void xpadneo_switch_triggers(struct xpadneo_devdata *xdata, const u8 mode
}
}

#define AXIS(o) ((((u16)data[o+1])<<8)+data[o])
#define SWAP_BITS(v,b1,b2) \
(((v)>>(b1)&1)==((v)>>(b2)&1)?(v):(v^(1ULL<<(b1))^(1ULL<<(b2))))
static int xpadneo_raw_event(struct hid_device *hdev, struct hid_report *report,
Expand Down Expand Up @@ -852,6 +874,19 @@ static int xpadneo_raw_event(struct hid_device *hdev, struct hid_report *report,
data[16] = 0;
}

/* trigger emulation of Xpad360 */
if (param_xpad_emulation && reportsize >= 13) {
u16 axis;
/* left trigger */
axis = AXIS(9) >> 2;
data[9] = (u8)(axis & 0xFF);
data[10] = (u8)(axis >> 8);
/* right trigger */
axis = AXIS(11) >> 2;
data[11] = (u8)(axis & 0xFF);
data[12] = (u8)(axis >> 8);
}

/* 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);
Expand All @@ -877,7 +912,7 @@ static int xpadneo_raw_event(struct hid_device *hdev, struct hid_report *report,
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;
int deadzone = 3072, abs_min = 0, abs_max = 65535, abs_trigger = 1023, fuzz_trigger = 4;

switch (hi->application) {
case HID_GD_GAMEPAD:
Expand Down Expand Up @@ -926,35 +961,43 @@ static int xpadneo_input_configured(struct hid_device *hdev, struct hid_input *h
* still detect our driver as the correct model. Currently this
* maps all controllers to the same model.
*/
switch (xdata->gamepad->id.product) {
case 0x02E0:
xdata->gamepad->id.version = 0x00000903;
break;
case 0x02FD:
xdata->gamepad->id.product = 0x02E0;
break;
case 0x0B05:
case 0x0B13:
xdata->gamepad->id.product = 0x02E0;
xdata->gamepad->id.version = 0x00000903;
break;
}
if (param_xpad_emulation) {
xdata->gamepad->id.bustype = BUS_USB;
xdata->gamepad->id.product = 0x028E;
xdata->gamepad->id.version = 0x00000110;
hid_info(hdev, "emulating as Xpad360 USB device (0003:045E:028E:0110)");
hid_warn(hdev, "most advanced features are disabled");
} else {
switch (xdata->gamepad->id.product) {
case 0x02E0:
xdata->gamepad->id.version = 0x00000903;
break;
case 0x02FD:
xdata->gamepad->id.product = 0x02E0;
break;
case 0x0B05:
case 0x0B13:
xdata->gamepad->id.product = 0x02E0;
xdata->gamepad->id.version = 0x00000903;
break;
}

if (hdev->product != xdata->gamepad->id.product)
hid_info(hdev,
"pretending XB1S Windows wireless mode "
"(changed PID from 0x%04X to 0x%04X)\n", hdev->product,
(u16)xdata->gamepad->id.product);

if (hdev->version != xdata->gamepad->id.version)
hid_info(hdev,
"working around wrong SDL2 mappings "
"(changed version from 0x%08X to 0x%08X)\n", hdev->version,
xdata->gamepad->id.version);

if (param_disable_deadzones) {
hid_warn(hdev, "disabling dead zones\n");
deadzone = 0;
if (hdev->product != xdata->gamepad->id.product)
hid_info(hdev,
"pretending XB1S Windows wireless mode "
"(changed PID from 0x%04X to 0x%04X)\n", hdev->product,
(u16)xdata->gamepad->id.product);

if (hdev->version != xdata->gamepad->id.version)
hid_info(hdev,
"working around wrong SDL2 mappings "
"(changed version from 0x%08X to 0x%08X)\n", hdev->version,
xdata->gamepad->id.version);

if (param_disable_deadzones) {
hid_warn(hdev, "disabling dead zones\n");
deadzone = 0;
}
}

if (param_gamepad_compliance) {
Expand All @@ -968,11 +1011,17 @@ static int xpadneo_input_configured(struct hid_device *hdev, struct hid_input *h
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);
/* Xpad360 emulation */
if (param_xpad_emulation) {
abs_trigger = 255;
fuzz_trigger = 1;
}

input_set_abs_params(xdata->gamepad, ABS_Z, 0, abs_trigger, fuzz_trigger, 0);
input_set_abs_params(xdata->gamepad, ABS_RZ, 0, abs_trigger, fuzz_trigger, 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);
input_set_abs_params(xdata->gamepad, ABS_MISC, -abs_trigger, abs_trigger, fuzz_trigger, 0);

/* do not report the consumer control buttons as part of the gamepad */
__clear_bit(BTN_XBOX, xdata->gamepad->keybit);
Expand Down Expand Up @@ -1008,6 +1057,9 @@ static int xpadneo_event(struct hid_device *hdev, struct hid_field *field,
xdata->last_abs_rz = value;
goto combine_z_axes;
}
} else if (param_xpad_emulation && (usage->type == EV_KEY) && (usage->code == BTN_MODE)) {
/* emulating Xpad360 */
return 0;
} else if ((usage->type == EV_KEY) && (usage->code == BTN_XBOX)) {
/*
* Handle the Xbox logo button: We want to cache the button
Expand Down

0 comments on commit 36afa54

Please sign in to comment.