1
linux/drivers/hid/hid-sony.c
Fernando Luis Vázquez Cao a464918419 HID: add support for Sony RF receiver with USB product id 0x0374
Some Vaio desktop computers, among them the VGC-LN51JGB multimedia PC, have
a RF receiver, multi-interface USB device 054c:0374, that is used to connect
a wireless keyboard and a wireless mouse.

The keyboard works flawlessly, but the mouse (VGP-WMS3 in my case) does not
seem to be generating any pointer events. The problem is that the mouse pointer
is wrongly declared as a constant non-data variable in the report descriptor
(see lsusb and usbhid-dump output below), with the consequence that it is
ignored by the HID code.

Add this device to the have-special-driver list and fix up the report
descriptor in the Sony-specific driver which happens to already have a fixup
for a similar firmware bug.

# lsusb -vd 054C:0374
Bus 003 Device 002: ID 054c:0374 Sony Corp.
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0         8
  idVendor           0x054c Sony Corp.
  idProduct          0x0374
  iSerial                 0
[...]
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      1 Boot Interface Subclass
      bInterfaceProtocol      2 Mouse
      iInterface              2 RF Receiver
[...]
          Report Descriptor: (length is 100)
[...]
            Item(Global): Usage Page, data= [ 0x01 ] 1
                            Generic Desktop Controls
            Item(Local ): Usage, data= [ 0x30 ] 48
                            Direction-X
            Item(Local ): Usage, data= [ 0x31 ] 49
                            Direction-Y
            Item(Global): Report Count, data= [ 0x02 ] 2
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Global): Logical Minimum, data= [ 0x81 ] 129
            Item(Global): Logical Maximum, data= [ 0x7f ] 127
            Item(Main  ): Input, data= [ 0x07 ] 7
                            Constant Variable Relative No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield

# usbhid-dump
003:002:001:DESCRIPTOR         1357910009.758544
 05 01 09 02 A1 01 05 01 09 02 A1 02 85 01 09 01
 A1 00 05 09 19 01 29 05 95 05 75 01 15 00 25 01
 81 02 75 03 95 01 81 01 05 01 09 30 09 31 95 02
 75 08 15 81 25 7F 81 07 A1 02 85 01 09 38 35 00
 45 00 15 81 25 7F 95 01 75 08 81 06 C0 A1 02 85
 01 05 0C 15 81 25 7F 95 01 75 08 0A 38 02 81 06
 C0 C0 C0 C0

Cc: linux-input@vger.kernel.org
Cc: linux-usb@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Fernando Luis Vazquez Cao <fernando@oss.ntt.co.jp>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2013-01-15 17:01:11 +01:00

276 lines
8.0 KiB
C

/*
* 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) 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
};
static const u8 sixaxis_rdesc_fixup2[] = {
0x05, 0x01, 0x09, 0x04, 0xa1, 0x01, 0xa1, 0x02,
0x85, 0x01, 0x75, 0x08, 0x95, 0x01, 0x15, 0x00,
0x26, 0xff, 0x00, 0x81, 0x03, 0x75, 0x01, 0x95,
0x13, 0x15, 0x00, 0x25, 0x01, 0x35, 0x00, 0x45,
0x01, 0x05, 0x09, 0x19, 0x01, 0x29, 0x13, 0x81,
0x02, 0x75, 0x01, 0x95, 0x0d, 0x06, 0x00, 0xff,
0x81, 0x03, 0x15, 0x00, 0x26, 0xff, 0x00, 0x05,
0x01, 0x09, 0x01, 0xa1, 0x00, 0x75, 0x08, 0x95,
0x04, 0x35, 0x00, 0x46, 0xff, 0x00, 0x09, 0x30,
0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x81, 0x02,
0xc0, 0x05, 0x01, 0x95, 0x13, 0x09, 0x01, 0x81,
0x02, 0x95, 0x0c, 0x81, 0x01, 0x75, 0x10, 0x95,
0x04, 0x26, 0xff, 0x03, 0x46, 0xff, 0x03, 0x09,
0x01, 0x81, 0x02, 0xc0, 0xa1, 0x02, 0x85, 0x02,
0x75, 0x08, 0x95, 0x30, 0x09, 0x01, 0xb1, 0x02,
0xc0, 0xa1, 0x02, 0x85, 0xee, 0x75, 0x08, 0x95,
0x30, 0x09, 0x01, 0xb1, 0x02, 0xc0, 0xa1, 0x02,
0x85, 0xef, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01,
0xb1, 0x02, 0xc0, 0xc0,
};
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 RF Receiver 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));
} else if (sc->quirks & SIXAXIS_CONTROLLER_USB &&
*rsize > sizeof(sixaxis_rdesc_fixup2)) {
hid_info(hdev, "Sony Sixaxis clone detected. Using original report descriptor (size: %d clone; %d new)\n",
*rsize, (int)sizeof(sixaxis_rdesc_fixup2));
*rsize = sizeof(sixaxis_rdesc_fixup2);
memcpy(rdesc, &sixaxis_rdesc_fixup2, *rsize);
}
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 },
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_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");