@@ -11,6 +11,8 @@
* 2006 Adam Buchbinder <adam.buchbinder@gmail.com>
* 2007 Jan Kratochvil <honza@jikos.cz>
* 2010 Christoph Fritz <chf.fritz@googlemail.com>
+ * 2025 Pascal Giard <pascal.giard@etsmtl.ca>
+ * 2025 Daniel Nguyen <daniel.nguyen.1@ens.etsmtl.ca>
*
* This driver is based on:
* - information from http://euc.jp/periphs/xbox-controller.ja.html
@@ -70,9 +72,16 @@
#include <linux/module.h>
#include <linux/usb/input.h>
#include <linux/usb/quirks.h>
+#include <linux/timer.h>
#define XPAD_PKT_LEN 64
+/*
+ * The Guitar Hero Live (GHL) Xbox One dongles require a poke
+ * every 8 seconds.
+ */
+#define GHL_GUITAR_POKE_INTERVAL 8 /* in seconds */
+
/*
* xbox d-pads should map to buttons, as is required for DDR pads
* but we map them to axes when possible to simplify things
@@ -104,6 +113,8 @@
#define PKT_XBE2_FW_5_EARLY 3
#define PKT_XBE2_FW_5_11 4
+#define QUIRK_GHL_XBOXONE BIT(0)
+
static bool dpad_to_buttons;
module_param(dpad_to_buttons, bool, S_IRUGO);
MODULE_PARM_DESC(dpad_to_buttons, "Map D-PAD to buttons rather than axes for unknown pads");
@@ -126,6 +137,7 @@ static const struct xpad_device {
char *name;
u8 mapping;
u8 xtype;
+ u8 quirks;
} xpad_device[] = {
/* Please keep this list sorted by vendor and product ID. */
{ 0x0079, 0x18d4, "GPD Win 2 X-Box Controller", 0, XTYPE_XBOX360 },
@@ -289,6 +301,7 @@ static const struct xpad_device {
{ 0x12ab, 0x0301, "PDP AFTERGLOW AX.1", 0, XTYPE_XBOX360 },
{ 0x12ab, 0x0303, "Mortal Kombat Klassic FightStick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
{ 0x12ab, 0x8809, "Xbox DDR dancepad", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX },
+ { 0x1430, 0x079B, "RedOctane GHL Controller", 0, XTYPE_XBOXONE, QUIRK_GHL_XBOXONE},
{ 0x1430, 0x4748, "RedOctane Guitar Hero X-plorer", 0, XTYPE_XBOX360 },
{ 0x1430, 0x8888, "TX6500+ Dance Pad (first generation)", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX },
{ 0x1430, 0xf801, "RedOctane Controller", 0, XTYPE_XBOX360 },
@@ -451,6 +464,12 @@ static const signed short xpad_btn_paddles[] = {
-1 /* terminating entry */
};
+/* used for the dmap mapping of the GHL Xbox One dongle */
+static const struct {int x; int y; } xpad_dpad_ghl[] = {
+ {0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1},
+ {0, 0}
+};
+
/*
* Xbox 360 has a vendor-specific class, so we cannot match it with only
* USB_INTERFACE_INFO (also specifically refused by USB subsystem), so we
@@ -511,6 +530,7 @@ static const struct usb_device_id xpad_table[] = {
XPAD_XBOX360_VENDOR(0x1209), /* Ardwiino Controllers */
XPAD_XBOX360_VENDOR(0x12ab), /* Xbox 360 dance pads */
XPAD_XBOX360_VENDOR(0x1430), /* RedOctane Xbox 360 controllers */
+ XPAD_XBOXONE_VENDOR(0x1430), /* RedOctane Xbox One controllers */
XPAD_XBOX360_VENDOR(0x146b), /* Bigben Interactive controllers */
XPAD_XBOX360_VENDOR(0x1532), /* Razer Sabertooth */
XPAD_XBOXONE_VENDOR(0x1532), /* Razer Wildcat */
@@ -677,6 +697,14 @@ static const u8 xboxone_rumbleend_init[] = {
0x00, GIP_MOTOR_ALL, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
+/*
+ * Magic data for the GHL Xbox One dongles sniffed with a USB
+ * protocol analyzer.
+ */
+static const char ghl_xboxone_magic_data[] = {
+ 0x22, 0x00, 0x00, 0x08, 0x02, 0x08, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
/*
* This specifies the selection of init packets that a gamepad
* will be sent on init *and* the order in which they will be
@@ -686,12 +714,15 @@ static const u8 xboxone_rumbleend_init[] = {
static const struct xboxone_init_packet xboxone_init_packets[] = {
XBOXONE_INIT_PKT(0x0e6f, 0x0165, xboxone_hori_ack_id),
XBOXONE_INIT_PKT(0x0f0d, 0x0067, xboxone_hori_ack_id),
+ XBOXONE_INIT_PKT(0x1430, 0x079b, xboxone_hori_ack_id),
XBOXONE_INIT_PKT(0x0000, 0x0000, xboxone_power_on),
XBOXONE_INIT_PKT(0x045e, 0x02ea, xboxone_s_init),
XBOXONE_INIT_PKT(0x045e, 0x0b00, xboxone_s_init),
XBOXONE_INIT_PKT(0x045e, 0x0b00, extra_input_packet_init),
XBOXONE_INIT_PKT(0x0e6f, 0x0000, xboxone_pdp_led_on),
+ XBOXONE_INIT_PKT(0x1430, 0x079b, xboxone_pdp_led_on),
XBOXONE_INIT_PKT(0x0e6f, 0x0000, xboxone_pdp_auth),
+ XBOXONE_INIT_PKT(0x1430, 0x079b, xboxone_pdp_auth),
XBOXONE_INIT_PKT(0x24c6, 0x541a, xboxone_rumblebegin_init),
XBOXONE_INIT_PKT(0x24c6, 0x542a, xboxone_rumblebegin_init),
XBOXONE_INIT_PKT(0x24c6, 0x543a, xboxone_rumblebegin_init),
@@ -751,6 +782,10 @@ struct usb_xpad {
const char *name; /* name of the device */
struct work_struct work; /* init/remove device from callback */
time64_t mode_btn_down_ts;
+ int quirks;
+ struct urb *ghl_urb; /* URB for GHL Xbox One dongle magic data */
+ /* timer for periodic poke of Xbox One dongle with magic data */
+ struct timer_list ghl_poke_timer;
};
static int xpad_init_input(struct usb_xpad *xpad);
@@ -758,6 +793,63 @@ static void xpad_deinit_input(struct usb_xpad *xpad);
static void xpadone_ack_mode_report(struct usb_xpad *xpad, u8 seq_num);
static void xpad360w_poweroff_controller(struct usb_xpad *xpad);
+/*
+ * ghl_magic_poke_cb
+ *
+ * Callback function that resets the timer for the next magic data poke
+ * as required by the GHL Xbox One dongle.
+ */
+static void ghl_magic_poke_cb(struct urb *urb)
+{
+ struct usb_xpad *xpad = urb->context;
+
+ if (urb->status < 0)
+ pr_warn("URB transfer failed.\n");
+
+ mod_timer(&xpad->ghl_poke_timer, jiffies + GHL_GUITAR_POKE_INTERVAL * HZ);
+}
+
+/*
+ * ghl_magic_poke
+ *
+ * Submits the magic_data URB as required by the GHL Xbox One dongle.
+ */
+static void ghl_magic_poke(struct timer_list *t)
+{
+ int ret;
+ struct usb_xpad *xpad = from_timer(xpad, t, ghl_poke_timer);
+
+ ret = usb_submit_urb(xpad->ghl_urb, GFP_ATOMIC);
+ if (ret < 0)
+ pr_warn("URB transfer failed.\n");
+}
+
+/*
+ * ghl_init_urb
+ *
+ * Prepares the interrupt URB for magic_data as required by the GHL Xbox One dongle.
+ */
+static int ghl_init_urb(struct usb_xpad *xpad, struct usb_device *usbdev,
+ const char ghl_magic_data[], u16 poke_size,
+ struct usb_endpoint_descriptor *ep_irq_out)
+{
+ u8 *databuf;
+ unsigned int pipe;
+
+ pipe = usb_sndintpipe(usbdev, ep_irq_out->bEndpointAddress);
+
+ databuf = devm_kzalloc(&xpad->udev->dev, poke_size, GFP_ATOMIC);
+ if (!databuf)
+ return -ENOMEM;
+
+ memcpy(databuf, ghl_magic_data, poke_size);
+
+ usb_fill_int_urb(xpad->ghl_urb, usbdev, pipe, databuf, poke_size,
+ ghl_magic_poke_cb, xpad, ep_irq_out->bInterval);
+
+ return 0;
+}
+
/*
* xpad_process_packet
*
@@ -1006,6 +1098,7 @@ static void xpadone_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char
{
struct input_dev *dev = xpad->dev;
bool do_sync = false;
+ int dpad_value;
/* the xbox button has its own special report */
if (data[0] == GIP_CMD_VIRTUAL_KEY) {
@@ -1152,6 +1245,50 @@ static void xpadone_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char
}
}
+ do_sync = true;
+ } else if (data[0] == 0X21) { /* The main valid packet type for GHL inputs */
+ /* Mapping chosen to be coherent with GHL dongles of other consoles:
+ * PS2, WiiU & PS4.
+ *
+ * Refer to drivers/hid/hid-sony.c.
+ */
+ /* The 6 fret buttons */
+ input_report_key(dev, BTN_A, data[4] & BIT(0));
+ input_report_key(dev, BTN_B, data[4] & BIT(1));
+ input_report_key(dev, BTN_X, data[4] & BIT(2));
+ input_report_key(dev, BTN_Y, data[4] & BIT(3));
+ input_report_key(dev, BTN_TL, data[4] & BIT(4));
+ input_report_key(dev, BTN_TR, data[4] & BIT(5));
+
+ /* D-pad */
+ dpad_value = data[6] & 0xF;
+ if (dpad_value > 7)
+ dpad_value = 8;
+
+ input_report_abs(dev, ABS_HAT0X, xpad_dpad_ghl[dpad_value].x);
+ input_report_abs(dev, ABS_HAT0Y, xpad_dpad_ghl[dpad_value].y);
+
+ /* Strum bar */
+ input_report_abs(dev, ABS_Y, ((data[8] - 0x80) << 9));
+
+ /* Tilt sensor */
+ input_report_abs(dev, ABS_Z, ((data[9] - 0x80) << 9));
+
+ /* Whammy bar */
+ input_report_abs(dev, ABS_RZ, ((data[10] - 0x80) << 9));
+
+ /* Power button */
+ input_report_key(dev, BTN_THUMBR, data[5] & BIT(4));
+
+ /* GHTV button */
+ input_report_key(dev, BTN_START, data[5] & BIT(2));
+
+ /* Hero Power button */
+ input_report_key(dev, BTN_MODE, data[5] & BIT(0));
+
+ /* Pause button */
+ input_report_key(dev, BTN_THUMBL, data[5] & BIT(1));
+
do_sync = true;
}
@@ -1843,16 +1980,29 @@ static void xpad_set_up_abs(struct input_dev *input_dev, signed short abs)
switch (abs) {
case ABS_X:
case ABS_Y:
+ /* GHL Strum bar */
+ if (xpad->xtype == XTYPE_XBOXONE && xpad->quirks & QUIRK_GHL_XBOXONE)
+ input_set_abs_params(input_dev, abs, -32768, 32767, 0, 0);
+ break;
case ABS_RX:
case ABS_RY: /* the two sticks */
input_set_abs_params(input_dev, abs, -32768, 32767, 16, 128);
break;
case ABS_Z:
+ /* GHL Tilt sensor */
+ if (xpad->xtype == XTYPE_XBOXONE && xpad->quirks & QUIRK_GHL_XBOXONE)
+ input_set_abs_params(input_dev, abs, -32768, 32767, 0, 0);
+ break;
case ABS_RZ: /* the triggers (if mapped to axes) */
- if (xpad->xtype == XTYPE_XBOXONE)
- input_set_abs_params(input_dev, abs, 0, 1023, 0, 0);
- else
+ if (xpad->xtype == XTYPE_XBOXONE) {
+ /* GHL Whammy bar */
+ if (xpad->quirks & QUIRK_GHL_XBOXONE)
+ input_set_abs_params(input_dev, abs, -32768, 32767, 0, 0);
+ else
+ input_set_abs_params(input_dev, abs, 0, 1023, 0, 0);
+ } else {
input_set_abs_params(input_dev, abs, 0, 255, 0, 0);
+ }
break;
case ABS_HAT0X:
case ABS_HAT0Y: /* the d-pad (only if dpad is mapped to axes */
@@ -2028,6 +2178,7 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
xpad->mapping = xpad_device[i].mapping;
xpad->xtype = xpad_device[i].xtype;
xpad->name = xpad_device[i].name;
+ xpad->quirks = xpad_device[i].quirks;
xpad->packet_type = PKT_XB;
INIT_WORK(&xpad->work, xpad_presence_work);
@@ -2150,8 +2301,26 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
if (error)
goto err_deinit_output;
}
+
+ if (xpad->quirks & QUIRK_GHL_XBOXONE) {
+ xpad->ghl_urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!xpad->ghl_urb) {
+ error = -ENOMEM;
+ goto err_deinit_output;
+ }
+
+ error = ghl_init_urb(xpad, udev, ghl_xboxone_magic_data,
+ ARRAY_SIZE(ghl_xboxone_magic_data), ep_irq_out);
+ if (error)
+ goto err_free_ghl_urb;
+
+ timer_setup(&xpad->ghl_poke_timer, ghl_magic_poke, 0);
+ mod_timer(&xpad->ghl_poke_timer, jiffies + GHL_GUITAR_POKE_INTERVAL * HZ);
+ }
return 0;
+err_free_ghl_urb:
+ usb_free_urb(xpad->ghl_urb);
err_deinit_output:
xpad_deinit_output(xpad);
err_free_in_urb:
@@ -2181,6 +2350,12 @@ static void xpad_disconnect(struct usb_interface *intf)
xpad_deinit_output(xpad);
usb_free_urb(xpad->irq_in);
+
+ if (xpad->quirks & QUIRK_GHL_XBOXONE) {
+ usb_free_urb(xpad->ghl_urb);
+ del_timer_sync(&xpad->ghl_poke_timer);
+ }
+
usb_free_coherent(xpad->udev, XPAD_PKT_LEN,
xpad->idata, xpad->idata_dma);