Last active
August 29, 2015 14:16
-
-
Save benbancroft/2a5d9bca700c29615f4d to your computer and use it in GitHub Desktop.
XPad PowerA Spectra Patch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- xpad.c.original 2015-03-02 21:40:18.083854977 +0000 | |
+++ xpad.c 2015-03-03 11:45:57.357186216 +0000 | |
@@ -77,6 +77,7 @@ | |
#include <linux/slab.h> | |
#include <linux/stat.h> | |
#include <linux/module.h> | |
+#include <linux/circ_buf.h> | |
#include <linux/usb/input.h> | |
#define DRIVER_AUTHOR "Marko Friedemann <mfr@bmx-chemnitz.de>" | |
@@ -98,6 +99,15 @@ | |
#define XTYPE_XBOXONE 3 | |
#define XTYPE_UNKNOWN 4 | |
+/* queue for interrupt out transfers */ | |
+#define XMIT_SIZE 256 | |
+#define XMIT_INC(var, n) \ | |
+ do { \ | |
+ var += n; \ | |
+ var &= XMIT_SIZE - 1; \ | |
+ } while (0) | |
+#define XPAD_XMIT_RUNNING 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"); | |
@@ -204,6 +214,7 @@ | |
{ 0x1bad, 0xf903, "Tron Xbox 360 controller", 0, XTYPE_XBOX360 }, | |
{ 0x24c6, 0x5000, "Razer Atrox Arcade Stick", 0, XTYPE_XBOX360 }, | |
{ 0x24c6, 0x5300, "PowerA MINI PROEX Controller", 0, XTYPE_XBOX360 }, | |
+ { 0x24c6, 0x542a, "PowerA Spectra Controller", 0, XTYPE_XBOXONE }, | |
{ 0x24c6, 0x5303, "Xbox Airflo wired controller", 0, XTYPE_XBOX360 }, | |
{ 0x24c6, 0x5500, "Hori XBOX 360 EX 2 with Turbo", 0, XTYPE_XBOX360 }, | |
{ 0x24c6, 0x5501, "Hori Real Arcade Pro VX-SA", 0, XTYPE_XBOX360 }, | |
@@ -306,7 +317,8 @@ | |
XPAD_XBOX360_VENDOR(0x1bad), /* Harminix Rock Band Guitar and Drums */ | |
XPAD_XBOX360_VENDOR(0x0f0d), /* Hori Controllers */ | |
XPAD_XBOX360_VENDOR(0x1689), /* Razer Onza */ | |
- XPAD_XBOX360_VENDOR(0x24c6), /* PowerA Controllers */ | |
+ XPAD_XBOX360_VENDOR(0x24c6), /* PowerA X-Box 360 Controllers */ | |
+ XPAD_XBOXONE_VENDOR(0x24c6), /* PowerA X-Box One Controllers */ | |
XPAD_XBOX360_VENDOR(0x1532), /* Razer Sabertooth */ | |
XPAD_XBOX360_VENDOR(0x15e4), /* Numark X-Box 360 controllers */ | |
XPAD_XBOX360_VENDOR(0x162e), /* Joytech X-Box 360 controllers */ | |
@@ -332,7 +344,11 @@ | |
struct urb *irq_out; /* urb for interrupt out report */ | |
unsigned char *odata; /* output data */ | |
dma_addr_t odata_dma; | |
- struct mutex odata_mutex; | |
+ | |
+ struct circ_buf xmit; /* queue for interrupt out transfers */ | |
+ unsigned char xmit_data[XMIT_SIZE]; | |
+ unsigned long xmit_flags[1]; | |
+ spinlock_t xmit_lock; | |
#if defined(CONFIG_JOYSTICK_XPAD_LEDS) | |
struct xpad_led *led; | |
@@ -603,6 +619,7 @@ | |
static void xpadone_process_packet(struct usb_xpad *xpad, | |
u16 cmd, unsigned char *data) | |
{ | |
+ | |
struct input_dev *dev = xpad->dev; | |
switch (data[0]) { | |
@@ -620,6 +637,7 @@ | |
static void xpad_irq_in(struct urb *urb) | |
{ | |
+ | |
struct usb_xpad *xpad = urb->context; | |
struct device *dev = &xpad->intf->dev; | |
int retval, status; | |
@@ -686,6 +704,107 @@ | |
} | |
} | |
+void xpad_irq_xmit(struct usb_xpad *xpad) | |
+{ | |
+ int length, c, err; | |
+ unsigned long flags; | |
+ | |
+ spin_lock_irqsave(&xpad->xmit_lock, flags); | |
+ | |
+ if (xpad->xmit.head == xpad->xmit.tail) { | |
+ clear_bit(XPAD_XMIT_RUNNING, xpad->xmit_flags); | |
+ goto out_unlock; | |
+ } | |
+ | |
+ /* copy packet length */ | |
+ length = xpad->xmit.buf[xpad->xmit.tail]; | |
+ XMIT_INC(xpad->xmit.tail, 1); | |
+ | |
+ xpad->irq_out->transfer_buffer_length = length; | |
+ | |
+ /* copy packet data */ | |
+ c = CIRC_CNT_TO_END(xpad->xmit.head, xpad->xmit.tail, XMIT_SIZE); | |
+ if (length < c) | |
+ c = length; | |
+ | |
+ memcpy(xpad->odata, | |
+ &xpad->xmit.buf[xpad->xmit.tail], | |
+ c); | |
+ if (length != c) { | |
+ memcpy(xpad->odata + c, | |
+ &xpad->xmit.buf[0], | |
+ length - c); | |
+ } | |
+ XMIT_INC(xpad->xmit.tail, length); | |
+ | |
+ err = usb_submit_urb(xpad->irq_out, GFP_ATOMIC); | |
+ if (err < 0) { | |
+ clear_bit(XPAD_XMIT_RUNNING, xpad->xmit_flags); | |
+ | |
+ /* -ENODEV: device disconnected */ | |
+ if (err == -ENODEV) | |
+ goto out_unlock; | |
+ | |
+ dev_warn(&xpad->intf->dev, "usb_submit_urb failed %d\n", err); | |
+ } | |
+ /* | |
+ * The XPAD_XMIT_RUNNING bit is not cleared here. That's intended. | |
+ * As long as the urb completion handler is not called, the transmiting | |
+ * is considered to be running | |
+ */ | |
+out_unlock: | |
+ spin_unlock_irqrestore(&xpad->xmit_lock, flags); | |
+} | |
+ | |
+int xpad_send_packet(struct usb_xpad *xpad, u8 length, u8 *data) | |
+{ | |
+ int head, tail, empty, c; | |
+ unsigned long flags; | |
+ | |
+ spin_lock_irqsave(&xpad->xmit_lock, flags); | |
+ | |
+ /* update head and tail of xmit buffer */ | |
+ head = xpad->xmit.head; | |
+ tail = xpad->xmit.tail; | |
+ | |
+ if (CIRC_SPACE(head, tail, XMIT_SIZE) < length + 1) { | |
+ dev_warn(&xpad->dev->dev, | |
+ "not enough space in xmit buffer to send new packet\n"); | |
+ spin_unlock_irqrestore(&xpad->xmit_lock, flags); | |
+ return -1; | |
+ } | |
+ | |
+ empty = head == tail; | |
+ XMIT_INC(xpad->xmit.head, length + 1); | |
+ | |
+ /* store packet length in xmit buffer */ | |
+ xpad->xmit.buf[head] = length; | |
+ XMIT_INC(head, 1); | |
+ | |
+ /* store packet data in xmit buffer */ | |
+ c = CIRC_SPACE_TO_END(head, tail, XMIT_SIZE); | |
+ if (length < c) | |
+ c = length; | |
+ | |
+ memcpy(&xpad->xmit.buf[head], | |
+ data, | |
+ c); | |
+ if (length != c) { | |
+ memcpy(&xpad->xmit.buf[0], | |
+ data + c, | |
+ length - c); | |
+ } | |
+ XMIT_INC(head, length); | |
+ | |
+ spin_unlock_irqrestore(&xpad->xmit_lock, flags); | |
+ | |
+ /* if necessary, start the transmission */ | |
+ if (empty && !test_and_set_bit(XPAD_XMIT_RUNNING, xpad->xmit_flags)) | |
+ xpad_irq_xmit(xpad); | |
+ | |
+ return 0; | |
+} | |
+ | |
static void xpad_irq_out(struct urb *urb) | |
{ | |
struct usb_xpad *xpad = urb->context; | |
@@ -697,6 +816,7 @@ | |
switch (status) { | |
case 0: | |
/* success */ | |
+ xpad_irq_xmit(xpad); | |
return; | |
case -ECONNRESET: | |
@@ -705,7 +825,7 @@ | |
/* this urb is terminated, clean up */ | |
dev_dbg(dev, "%s - urb shutting down with status: %d\n", | |
__func__, status); | |
- return; | |
+ break; | |
default: | |
dev_dbg(dev, "%s - nonzero urb status received: %d\n", | |
@@ -718,6 +838,7 @@ | |
if (retval) | |
dev_err(dev, "%s - usb_submit_urb failed with result %d\n", | |
__func__, retval); | |
+ clear_bit(XPAD_XMIT_RUNNING, xpad->xmit_flags); | |
} | |
static int xpad_init_output(struct usb_interface *intf, struct usb_xpad *xpad) | |
@@ -736,7 +857,8 @@ | |
goto fail1; | |
} | |
- mutex_init(&xpad->odata_mutex); | |
+ spin_lock_init(&xpad->xmit_lock); | |
+ xpad->xmit.buf = xpad->xmit_data; | |
xpad->irq_out = usb_alloc_urb(0, GFP_KERNEL); | |
if (!xpad->irq_out) { | |
@@ -745,7 +867,7 @@ | |
} | |
/* Xbox One controller has in/out endpoints swapped. */ | |
- ep_irq_out_idx = xpad->xtype == XTYPE_XBOXONE ? 0 : 1; | |
+ ep_irq_out_idx = xpad->xtype == XTYPE_XBOXONE ? 1 : 1; | |
ep_irq_out = &intf->cur_altsetting->endpoint[ep_irq_out_idx].desc; | |
usb_fill_int_urb(xpad->irq_out, xpad->udev, | |
@@ -780,6 +902,7 @@ | |
static int xpad_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect) | |
{ | |
struct usb_xpad *xpad = input_get_drvdata(dev); | |
+ u8 pdata[12]; | |
if (effect->type == FF_RUMBLE) { | |
__u16 strong = effect->u.rumble.strong_magnitude; | |
@@ -788,45 +911,39 @@ | |
switch (xpad->xtype) { | |
case XTYPE_XBOX: | |
- xpad->odata[0] = 0x00; | |
- xpad->odata[1] = 0x06; | |
- xpad->odata[2] = 0x00; | |
- xpad->odata[3] = strong / 256; /* left actuator */ | |
- xpad->odata[4] = 0x00; | |
- xpad->odata[5] = weak / 256; /* right actuator */ | |
- xpad->irq_out->transfer_buffer_length = 6; | |
- | |
- return usb_submit_urb(xpad->irq_out, GFP_ATOMIC); | |
+ pdata[0] = 0x00; | |
+ pdata[1] = 0x06; | |
+ pdata[2] = 0x00; | |
+ pdata[3] = strong / 256; /* left actuator */ | |
+ pdata[4] = 0x00; | |
+ pdata[5] = weak / 256; /* right actuator */ | |
+ return xpad_send_packet(xpad, 6, pdata); | |
case XTYPE_XBOX360: | |
- xpad->odata[0] = 0x00; | |
- xpad->odata[1] = 0x08; | |
- xpad->odata[2] = 0x00; | |
- xpad->odata[3] = strong / 256; /* left actuator? */ | |
- xpad->odata[4] = weak / 256; /* right actuator? */ | |
- xpad->odata[5] = 0x00; | |
- xpad->odata[6] = 0x00; | |
- xpad->odata[7] = 0x00; | |
- xpad->irq_out->transfer_buffer_length = 8; | |
- | |
- return usb_submit_urb(xpad->irq_out, GFP_ATOMIC); | |
+ pdata[0] = 0x00; | |
+ pdata[1] = 0x08; | |
+ pdata[2] = 0x00; | |
+ pdata[3] = strong / 256; /* left actuator? */ | |
+ pdata[4] = weak / 256; /* right actuator? */ | |
+ pdata[5] = 0x00; | |
+ pdata[6] = 0x00; | |
+ pdata[7] = 0x00; | |
+ return xpad_send_packet(xpad, 8, pdata); | |
case XTYPE_XBOX360W: | |
- xpad->odata[0] = 0x00; | |
- xpad->odata[1] = 0x01; | |
- xpad->odata[2] = 0x0F; | |
- xpad->odata[3] = 0xC0; | |
- xpad->odata[4] = 0x00; | |
- xpad->odata[5] = strong / 256; | |
- xpad->odata[6] = weak / 256; | |
- xpad->odata[7] = 0x00; | |
- xpad->odata[8] = 0x00; | |
- xpad->odata[9] = 0x00; | |
- xpad->odata[10] = 0x00; | |
- xpad->odata[11] = 0x00; | |
- xpad->irq_out->transfer_buffer_length = 12; | |
- | |
- return usb_submit_urb(xpad->irq_out, GFP_ATOMIC); | |
+ pdata[0] = 0x00; | |
+ pdata[1] = 0x01; | |
+ pdata[2] = 0x0F; | |
+ pdata[3] = 0xC0; | |
+ pdata[4] = 0x00; | |
+ pdata[5] = strong / 256; | |
+ pdata[6] = weak / 256; | |
+ pdata[7] = 0x00; | |
+ pdata[8] = 0x00; | |
+ pdata[9] = 0x00; | |
+ pdata[10] = 0x00; | |
+ pdata[11] = 0x00; | |
+ return xpad_send_packet(xpad, 12, pdata); | |
default: | |
dev_dbg(&xpad->dev->dev, | |
@@ -864,14 +981,14 @@ | |
static void xpad_send_led_command(struct usb_xpad *xpad, int command) | |
{ | |
+ | |
+ u8 pdata[3]; | |
+ | |
if (command >= 0 && command < 14) { | |
- mutex_lock(&xpad->odata_mutex); | |
- xpad->odata[0] = 0x01; | |
- xpad->odata[1] = 0x03; | |
- xpad->odata[2] = command; | |
- xpad->irq_out->transfer_buffer_length = 3; | |
- usb_submit_urb(xpad->irq_out, GFP_KERNEL); | |
- mutex_unlock(&xpad->odata_mutex); | |
+ pdata[0] = 0x01; | |
+ pdata[1] = 0x03; | |
+ pdata[2] = command; | |
+ xpad_send_packet(xpad, 3, pdata); | |
} | |
} | |
@@ -952,10 +1069,37 @@ | |
if (xpad->xtype == XTYPE_XBOXONE) { | |
/* Xbox one controller needs to be initialized. */ | |
- xpad->odata[0] = 0x05; | |
- xpad->odata[1] = 0x20; | |
- xpad->irq_out->transfer_buffer_length = 2; | |
- return usb_submit_urb(xpad->irq_out, GFP_KERNEL); | |
+ | |
+ u8 pdata[13]; | |
+ | |
+ pdata[0] = 0x05; | |
+ pdata[1] = 0x20; | |
+ xpad_send_packet(xpad, 2, pdata); | |
+ | |
+ pdata[0] = 0x0A; | |
+ pdata[1] = 0x20; | |
+ pdata[2] = 0x02; | |
+ pdata[3] = 0x03; | |
+ pdata[4] = 0x00; | |
+ pdata[5] = 0x01; | |
+ pdata[6] = 0x14; | |
+ xpad_send_packet(xpad, 7, pdata); | |
+ | |
+ pdata[0] = 0x09; | |
+ pdata[1] = 0x00; | |
+ pdata[2] = 0x03; | |
+ pdata[3] = 0x09; | |
+ pdata[4] = 0x00; | |
+ pdata[5] = 0x0f; | |
+ pdata[6] = 0x00; | |
+ pdata[7] = 0x00; | |
+ pdata[8] = 0x1d; | |
+ pdata[9] = 0x1d; | |
+ pdata[10] = 0xff; | |
+ pdata[11] = 0x00; | |
+ pdata[12] = 0x00; | |
+ xpad_send_packet(xpad, 13, pdata); | |
+ | |
} | |
return 0; | |
@@ -1130,7 +1274,7 @@ | |
goto fail5; | |
/* Xbox One controller has in/out endpoints swapped. */ | |
- ep_irq_in_idx = xpad->xtype == XTYPE_XBOXONE ? 1 : 0; | |
+ ep_irq_in_idx = xpad->xtype == XTYPE_XBOXONE ? 0 : 0; | |
ep_irq_in = &intf->cur_altsetting->endpoint[ep_irq_in_idx].desc; | |
usb_fill_int_urb(xpad->irq_in, udev, |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment