Return-Path: Message-ID: <1347591892.6145.14.camel@obelisk.thedillows.org> Subject: [KERNEL PATCH] HID: Add support for Sony BD Remote From: David Dillow To: linux-bluetooth@vger.kernel.org Cc: David Herrmann , Luiz Augusto von Dentz , Bastien Nocera , Antonio Ospite Date: Thu, 13 Sep 2012 23:04:52 -0400 In-Reply-To: <20120913003628.ce5babb2a66d09fe17fa15de@studenti.unina.it> References: <1346378760.7976.2.camel@obelisk.thedillows.org> <20120907124559.GA16092@x220> <1347284864.3532.1.camel@sirocco.hadess.net> <1347394656.1606.10.camel@sirocco.hadess.net> <1347456657.23874.29.camel@sirocco.hadess.net> <20120913003628.ce5babb2a66d09fe17fa15de@studenti.unina.it> Content-Type: text/plain; charset="UTF-8" Mime-Version: 1.0 Sender: linux-bluetooth-owner@vger.kernel.org List-ID: We also gain support for the Logitech Harmony Adapter for PS3. Signed-off-by: David Dillow -- drivers/hid/Kconfig | 7 + drivers/hid/Makefile | 1 + drivers/hid/hid-core.c | 2 + drivers/hid/hid-ps3remote.c | 325 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 335 insertions(+), 0 deletions(-) On Thu, 2012-09-13 at 00:36 +0200, Antonio Ospite wrote: On Wed, 12 Sep 2012 22:26:04 +0200 David Herrmann wrote: > > So if the devices only need some short setup-command during > > initialization and then they send HID reports whenever a button is > > pressed, then everything should be very easy to get working. Even > > auto-reconnect and 10min idle-disconnect are _very_ easy to get > > working in the kernel with the current HID infrastructure. > > I too will take a look during the week-end about writing a kernel > driver, if it's not that much of an effort I might take the project > (no promises yet). I have a PS3 BD remote Bastien donated. Here's a first pass at a kernel driver to support the BD remote and Harmony PS adapter. I've tested it on Fedora 16 with single keypresses only, as I cannot send multiple simultaneous keypresses with the Harmony. This patch is against kernel.org's bluetooth.git as of an hour ago, but should apply to other versions without much fuss. Antonio, if you could test this with the real remote you have, I'd be very appreciative. David, could you point me at the controls for the 10min idle-disconnect? auto-connect and idle-disconnect seem to be working on my Fedora 16 box, though it looks like the Harmony adapter is initiating the disconnect -- this is likely an area for improvement with the Sony remote. diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index fbf4950..7e6ab25 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -534,6 +534,13 @@ config HID_PRIMAX Support for Primax devices that are not fully compliant with the HID standard. +config HID_PS3REMOTE + tristate "Sony PS3 Bluetooth BD Remote" + depends on BT_HIDP + ---help--- + Support for Sony PS3 Bluetooth BD Remote and Logitech Harmony Adapter + for PS3. + config HID_ROCCAT tristate "Roccat device support" depends on USB_HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index f975485..333ed6c 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -70,6 +70,7 @@ obj-$(CONFIG_HID_PANTHERLORD) += hid-pl.o obj-$(CONFIG_HID_PETALYNX) += hid-petalynx.o obj-$(CONFIG_HID_PICOLCD) += hid-picolcd.o obj-$(CONFIG_HID_PRIMAX) += hid-primax.o +obj-$(CONFIG_HID_PS3REMOTE) += hid-ps3remote.o obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o hid-roccat-common.o \ hid-roccat-arvo.o hid-roccat-isku.o hid-roccat-kone.o \ hid-roccat-koneplus.o hid-roccat-kovaplus.o hid-roccat-pyra.o \ diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 60ea284..1838c12 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1591,6 +1591,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACETRAVELLER) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACENAVIGATOR) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0x0306) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD_BOOTLOADER) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_COMFORT_MOUSE_4500) }, @@ -1641,6 +1642,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, 0x0306) }, { HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) }, { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300) }, { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb304) }, diff --git a/drivers/hid/hid-ps3remote.c b/drivers/hid/hid-ps3remote.c new file mode 100644 index 0000000..a87346e --- /dev/null +++ b/drivers/hid/hid-ps3remote.c @@ -0,0 +1,325 @@ +/* + * HID driver for Sony PS3 BD Remote + * + * Copyright (c) 2012 David Dillow + * Based on a blend of the bluez fakehid user-space code by Marcel Holtmann + * and other kernel HID drivers. + */ + +/* + * 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 +#include +#include + +#include "hid-ids.h" + +enum ps3remote_special_keys { + PS3R_BIT_PS = 0, + PS3R_BIT_ENTER = 3, + PS3R_BIT_L2 = 8, + PS3R_BIT_R2 = 9, + PS3R_BIT_L1 = 10, + PS3R_BIT_R1 = 11, + PS3R_BIT_TRIANGLE = 12, + PS3R_BIT_CIRCLE = 13, + PS3R_BIT_CROSS = 14, + PS3R_BIT_SQUARE = 15, + PS3R_BIT_SELECT = 16, + PS3R_BIT_L3 = 17, + PS3R_BIT_R3 = 18, + PS3R_BIT_START = 19, + PS3R_BIT_UP = 20, + PS3R_BIT_RIGHT = 21, + PS3R_BIT_DOWN = 22, + PS3R_BIT_LEFT = 23, +}; + +static unsigned int ps3remote_bits[] = { + [PS3R_BIT_ENTER] = 0x0b, + [PS3R_BIT_PS] = 0x43, + [PS3R_BIT_SQUARE] = 0x5f, + [PS3R_BIT_CROSS] = 0x5e, + [PS3R_BIT_CIRCLE] = 0x5d, + [PS3R_BIT_TRIANGLE] = 0x5c, + [PS3R_BIT_R1] = 0x5b, + [PS3R_BIT_L1] = 0x5a, + [PS3R_BIT_R2] = 0x59, + [PS3R_BIT_L2] = 0x58, + [PS3R_BIT_LEFT] = 0x57, + [PS3R_BIT_DOWN] = 0x56, + [PS3R_BIT_RIGHT] = 0x55, + [PS3R_BIT_UP] = 0x54, + [PS3R_BIT_START] = 0x53, + [PS3R_BIT_R3] = 0x52, + [PS3R_BIT_L3] = 0x51, + [PS3R_BIT_SELECT] = 0x50, +}; + +const static unsigned int ps3remote_keymap[] = { + [0x16] = KEY_EJECTCD, + [0x64] = KEY_AUDIO, + [0x65] = KEY_ANGLE, + [0x63] = KEY_SUBTITLE, + [0x0f] = KEY_CLEAR, + [0x28] = KEY_TIME, + [0x00] = KEY_1, + [0x01] = KEY_2, + [0x02] = KEY_3, + [0x03] = KEY_4, + [0x04] = KEY_5, + [0x05] = KEY_6, + [0x06] = KEY_7, + [0x07] = KEY_8, + [0x08] = KEY_9, + [0x09] = KEY_0, + [0x81] = KEY_RED, + [0x82] = KEY_GREEN, + [0x80] = KEY_BLUE, + [0x83] = KEY_YELLOW, + [0x70] = KEY_INFO, /* display */ + [0x1a] = KEY_MENU, /* top menu */ + [0x40] = KEY_CONTEXT_MENU, /* pop up/menu */ + [0x0e] = KEY_ESC, /* return */ + [0x5c] = KEY_OPTION, /* options/triangle */ + [0x5d] = KEY_BACK, /* back/circle */ + [0x5f] = KEY_SCREEN, /* view/square */ + [0x5e] = BTN_0, /* cross */ + [0x54] = KEY_UP, + [0x56] = KEY_DOWN, + [0x57] = KEY_LEFT, + [0x55] = KEY_RIGHT, + [0x0b] = KEY_ENTER, + [0x5a] = BTN_TL, /* L1 */ + [0x58] = BTN_TL2, /* L2 */ + [0x51] = BTN_THUMBL, /* L3 */ + [0x5b] = BTN_TR, /* R1 */ + [0x59] = BTN_TR2, /* R2 */ + [0x52] = BTN_THUMBR, /* R3 */ + [0x43] = KEY_HOMEPAGE, /* PS button */ + [0x50] = KEY_SELECT, + [0x53] = BTN_START, + [0x33] = KEY_REWIND, /* scan back */ + [0x32] = KEY_PLAY, + [0x34] = KEY_FORWARD, /* scan forward */ + [0x30] = KEY_PREVIOUS, + [0x38] = KEY_STOP, + [0x31] = KEY_NEXT, + [0x60] = KEY_FRAMEBACK, /* slow/step back */ + [0x39] = KEY_PAUSE, + [0x61] = KEY_FRAMEFORWARD, /* slow/step forward */ +}; + +static __u8 ps3remote_rdesc[] = { + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */ + 0x09, 0x05, /* USAGE (Game Pad) */ + 0xa1, 0x01, /* COLLECTION (Application) */ + 0x05, 0x06, /* USAGE PAGE (Generic Device) */ + 0x09, 0x20, /* USAGE (Battery Strength) */ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */ + 0x25, 0x64, /* LOGICAL MAXIMUM (100) */ + 0x75, 0x08, /* REPORT SIZE (8) */ + 0x95, 0x01, /* REPORT COUNT (1) */ + 0x81, 0x02, /* INPUT (Variable, Absolute) */ + 0x05, 0x09, /* USAGE PAGE (Button) */ + 0x19, 0x00, /* USAGE MINUMUM (0) */ + 0x29, 0xfe, /* USAGE MAXIMUM (254) */ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */ + 0x25, 0xfe, /* LOGICAL MAXIMUM (254) */ + 0x75, 0x08, /* REPORT SIZE (8) */ + 0x95, 0x01, /* REPORT COUNT (11) */ + 0x81, 0x00, /* INPUT (Array, Absolute) */ + 0xc0, /* END_COLLECTION */ +}; + +struct ps3remote_data { + u8 report[12]; + u8 last_key; + u32 last_mask; +}; + +static __u8 *ps3remote_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + *rsize = sizeof(ps3remote_rdesc); + return ps3remote_rdesc; +} + +static int ps3remote_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + unsigned int key = usage->hid & HID_USAGE; + + if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON || + key >= ARRAY_SIZE(ps3remote_keymap)) + return -1; + + key = ps3remote_keymap[key]; + if (!key) + return -1; + + hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key); + return 1; +} + +static void ps3remote_keychange(struct ps3remote_data *data, u8 key, u8 pressed) +{ + /* + * Update our report to include/exclude the key that changed, but + * make sure it doesn't get listed twice. It's OK if we don't have + * room to store a keypress, as that means 11 keys are simultaneously + * pressed. + */ + u8 *p = memchr(data->report + 1, key, 11); + if (pressed) { + if (p) + return; + + /* Not present, find a free spot to add it */ + p = memchr(data->report + 1, 0xff, 11); + } else + key = 0xff; + + if (p) + *p = key; +} + +static int ps3remote_event(struct hid_device *hdev, struct hid_report *report, + u8 *raw_data, int size) +{ + struct ps3remote_data *data = hid_get_drvdata(hdev); + u32 mask, changed; + u8 key, pressed; + int i; + + if (size != 12) { + hid_dbg(hdev, "unsupported report size\n"); + return 1; + } + + /* Battery appears to range from 0 to 5, convert into a percentage */ + data->report[0] = raw_data[11] * 20; + + /* + * Sometimes key presses are reported in the bitmask, sometimes + * standalone. It appears the bitmask is used for buttons that + * may pressed at the same time. + */ + mask = be32_to_cpup((__be32 *) raw_data); + mask &= ~0xff000000; + key = raw_data[4]; + pressed = raw_data[10]; + + changed = data->last_mask ^ mask; + if (changed) { + /* Update any changed multiple keypress reports */ + for (i = 0; i < 24; i++) { + if ((changed & (1 << i)) && ps3remote_bits[i]) { + ps3remote_keychange(data, ps3remote_bits[i], + !!(mask & (1 << i))); + } + } + data->last_mask = mask; + } else if (pressed && key != 0xff) { + /* Single key pressed */ + ps3remote_keychange(data, key, 1); + data->last_key = key; + } else if (!pressed && data->last_key != 0xff) { + /* Single key released */ + ps3remote_keychange(data, data->last_key, 0); + data->last_key = 0xff; + } else { + hid_dbg(hdev, "Incoherent report, %06x %06x %02x %02x %02x\n", + mask, data->last_mask, key, data->last_key, pressed); + } + + /* Substitue our updated report */ + memcpy(raw_data, data->report, 12); + return 0; +} + +static int ps3remote_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + struct ps3remote_data *data; + int ret = -ENOMEM; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + hid_err(hdev, "Can't alloc private data\n"); + goto err; + } + + data->report[0] = 0; + memset(data->report + 1, 0xff, 11); + data->last_key = 0xff; + data->last_mask = 0; + + hid_set_drvdata(hdev, data); + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "HID parse failed\n"); + goto err; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "HW start failed\n"); + goto err; + } + + return ret; + +err: + kfree(data); + return ret; +} + +static void ps3remote_remove(struct hid_device *hdev) +{ + struct ps3remote_data *data = hid_get_drvdata(hdev); + + hid_hw_stop(hdev); + kfree(data); +} + +static const struct hid_device_id ps3remote_devices[] = { + /* PS3 BD Remote */ + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, 0x0306) }, + /* Logitech Harmony Adapter for PS3 */ + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0x0306) }, + { } +}; +MODULE_DEVICE_TABLE(hid, ps3remote_devices); + +static struct hid_driver ps3remote_driver = { + .name = "ps3_remote", + .id_table = ps3remote_devices, + .probe = ps3remote_probe, + .remove = ps3remote_remove, + .report_fixup = ps3remote_fixup, + .input_mapping = ps3remote_mapping, + .raw_event = ps3remote_event, +}; + +static int __init ps3remote_init(void) +{ + return hid_register_driver(&ps3remote_driver); +} + +static void __exit ps3remote_exit(void) +{ + hid_unregister_driver(&ps3remote_driver); +} + +module_init(ps3remote_init); +module_exit(ps3remote_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Dillow ");