diff options
Diffstat (limited to 'drivers/hid/hid-sony.c')
-rw-r--r-- | drivers/hid/hid-sony.c | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c new file mode 100644 index 00000000..5cd25bd9 --- /dev/null +++ b/drivers/hid/hid-sony.c @@ -0,0 +1,246 @@ +/* + * HID driver for some sony "special" devices + * + * Copyright (c) 1999 Andreas Gal + * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz> + * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc + * Copyright (c) 2007 Paul Walmsley + * Copyright (c) 2008 Jiri Slaby + * Copyright (c) 2006-2008 Jiri Kosina + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include <linux/device.h> +#include <linux/hid.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/usb.h> + +#include "hid-ids.h" + +#define VAIO_RDESC_CONSTANT (1 << 0) +#define SIXAXIS_CONTROLLER_USB (1 << 1) +#define SIXAXIS_CONTROLLER_BT (1 << 2) + +static const u8 sixaxis_rdesc_fixup[] = { + 0x95, 0x13, 0x09, 0x01, 0x81, 0x02, 0x95, 0x0C, + 0x81, 0x01, 0x75, 0x10, 0x95, 0x04, 0x26, 0xFF, + 0x03, 0x46, 0xFF, 0x03, 0x09, 0x01, 0x81, 0x02 +}; + +struct sony_sc { + unsigned long quirks; +}; + +/* Sony Vaio VGX has wrongly mouse pointer declared as constant */ +static __u8 *sony_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + struct sony_sc *sc = hid_get_drvdata(hdev); + + if ((sc->quirks & VAIO_RDESC_CONSTANT) && + *rsize >= 56 && rdesc[54] == 0x81 && rdesc[55] == 0x07) { + hid_info(hdev, "Fixing up Sony Vaio VGX report descriptor\n"); + rdesc[55] = 0x06; + } + + /* The HID descriptor exposed over BT has a trailing zero byte */ + if ((((sc->quirks & SIXAXIS_CONTROLLER_USB) && *rsize == 148) || + ((sc->quirks & SIXAXIS_CONTROLLER_BT) && *rsize == 149)) && + rdesc[83] == 0x75) { + hid_info(hdev, "Fixing up Sony Sixaxis report descriptor\n"); + memcpy((void *)&rdesc[83], (void *)&sixaxis_rdesc_fixup, + sizeof(sixaxis_rdesc_fixup)); + } + return rdesc; +} + +static int sony_raw_event(struct hid_device *hdev, struct hid_report *report, + __u8 *rd, int size) +{ + struct sony_sc *sc = hid_get_drvdata(hdev); + + /* Sixaxis HID report has acclerometers/gyro with MSByte first, this + * has to be BYTE_SWAPPED before passing up to joystick interface + */ + if ((sc->quirks & (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT)) && + rd[0] == 0x01 && size == 49) { + swap(rd[41], rd[42]); + swap(rd[43], rd[44]); + swap(rd[45], rd[46]); + swap(rd[47], rd[48]); + } + + return 0; +} + +/* + * The Sony Sixaxis does not handle HID Output Reports on the Interrupt EP + * like it should according to usbhid/hid-core.c::usbhid_output_raw_report() + * so we need to override that forcing HID Output Reports on the Control EP. + * + * There is also another issue about HID Output Reports via USB, the Sixaxis + * does not want the report_id as part of the data packet, so we have to + * discard buf[0] when sending the actual control message, even for numbered + * reports, humpf! + */ +static int sixaxis_usb_output_raw_report(struct hid_device *hid, __u8 *buf, + size_t count, unsigned char report_type) +{ + struct usb_interface *intf = to_usb_interface(hid->dev.parent); + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_host_interface *interface = intf->cur_altsetting; + int report_id = buf[0]; + int ret; + + if (report_type == HID_OUTPUT_REPORT) { + /* Don't send the Report ID */ + buf++; + count--; + } + + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + HID_REQ_SET_REPORT, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + ((report_type + 1) << 8) | report_id, + interface->desc.bInterfaceNumber, buf, count, + USB_CTRL_SET_TIMEOUT); + + /* Count also the Report ID, in case of an Output report. */ + if (ret > 0 && report_type == HID_OUTPUT_REPORT) + ret++; + + return ret; +} + +/* + * Sending HID_REQ_GET_REPORT changes the operation mode of the ps3 controller + * to "operational". Without this, the ps3 controller will not report any + * events. + */ +static int sixaxis_set_operational_usb(struct hid_device *hdev) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *dev = interface_to_usbdev(intf); + __u16 ifnum = intf->cur_altsetting->desc.bInterfaceNumber; + int ret; + char *buf = kmalloc(18, GFP_KERNEL); + + if (!buf) + return -ENOMEM; + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + HID_REQ_GET_REPORT, + USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + (3 << 8) | 0xf2, ifnum, buf, 17, + USB_CTRL_GET_TIMEOUT); + if (ret < 0) + hid_err(hdev, "can't set operational mode\n"); + + kfree(buf); + + return ret; +} + +static int sixaxis_set_operational_bt(struct hid_device *hdev) +{ + unsigned char buf[] = { 0xf4, 0x42, 0x03, 0x00, 0x00 }; + return hdev->hid_output_raw_report(hdev, buf, sizeof(buf), HID_FEATURE_REPORT); +} + +static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret; + unsigned long quirks = id->driver_data; + struct sony_sc *sc; + + sc = kzalloc(sizeof(*sc), GFP_KERNEL); + if (sc == NULL) { + hid_err(hdev, "can't alloc sony descriptor\n"); + return -ENOMEM; + } + + sc->quirks = quirks; + hid_set_drvdata(hdev, sc); + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "parse failed\n"); + goto err_free; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT | + HID_CONNECT_HIDDEV_FORCE); + if (ret) { + hid_err(hdev, "hw start failed\n"); + goto err_free; + } + + if (sc->quirks & SIXAXIS_CONTROLLER_USB) { + hdev->hid_output_raw_report = sixaxis_usb_output_raw_report; + ret = sixaxis_set_operational_usb(hdev); + } + else if (sc->quirks & SIXAXIS_CONTROLLER_BT) + ret = sixaxis_set_operational_bt(hdev); + else + ret = 0; + + if (ret < 0) + goto err_stop; + + return 0; +err_stop: + hid_hw_stop(hdev); +err_free: + kfree(sc); + return ret; +} + +static void sony_remove(struct hid_device *hdev) +{ + hid_hw_stop(hdev); + kfree(hid_get_drvdata(hdev)); +} + +static const struct hid_device_id sony_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER), + .driver_data = SIXAXIS_CONTROLLER_USB }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER), + .driver_data = SIXAXIS_CONTROLLER_USB }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER), + .driver_data = SIXAXIS_CONTROLLER_BT }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE), + .driver_data = VAIO_RDESC_CONSTANT }, + { } +}; +MODULE_DEVICE_TABLE(hid, sony_devices); + +static struct hid_driver sony_driver = { + .name = "sony", + .id_table = sony_devices, + .probe = sony_probe, + .remove = sony_remove, + .report_fixup = sony_report_fixup, + .raw_event = sony_raw_event +}; + +static int __init sony_init(void) +{ + return hid_register_driver(&sony_driver); +} + +static void __exit sony_exit(void) +{ + hid_unregister_driver(&sony_driver); +} + +module_init(sony_init); +module_exit(sony_exit); +MODULE_LICENSE("GPL"); |