forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
431 lines
14 KiB
431 lines
14 KiB
// SPDX-License-Identifier: GPL-2.0+ |
|
|
|
/* |
|
* LED & force feedback support for BigBen Interactive |
|
* |
|
* 0x146b:0x0902 "Bigben Interactive Bigben Game Pad" |
|
* "Kid-friendly Wired Controller" PS3OFMINIPAD SONY |
|
* sold for use with the PS3 |
|
* |
|
* Copyright (c) 2018 Hanno Zulla <[email protected]> |
|
*/ |
|
|
|
#include <linux/input.h> |
|
#include <linux/slab.h> |
|
#include <linux/module.h> |
|
#include <linux/leds.h> |
|
#include <linux/hid.h> |
|
|
|
#include "hid-ids.h" |
|
|
|
|
|
/* |
|
* The original descriptor for 0x146b:0x0902 |
|
* |
|
* 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) |
|
* 0x09, 0x05, // Usage (Game Pad) |
|
* 0xA1, 0x01, // Collection (Application) |
|
* 0x15, 0x00, // Logical Minimum (0) |
|
* 0x25, 0x01, // Logical Maximum (1) |
|
* 0x35, 0x00, // Physical Minimum (0) |
|
* 0x45, 0x01, // Physical Maximum (1) |
|
* 0x75, 0x01, // Report Size (1) |
|
* 0x95, 0x0D, // Report Count (13) |
|
* 0x05, 0x09, // Usage Page (Button) |
|
* 0x19, 0x01, // Usage Minimum (0x01) |
|
* 0x29, 0x0D, // Usage Maximum (0x0D) |
|
* 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) |
|
* 0x95, 0x03, // Report Count (3) |
|
* 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) |
|
* 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) |
|
* 0x25, 0x07, // Logical Maximum (7) |
|
* 0x46, 0x3B, 0x01, // Physical Maximum (315) |
|
* 0x75, 0x04, // Report Size (4) |
|
* 0x95, 0x01, // Report Count (1) |
|
* 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter) |
|
* 0x09, 0x39, // Usage (Hat switch) |
|
* 0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) |
|
* 0x65, 0x00, // Unit (None) |
|
* 0x95, 0x01, // Report Count (1) |
|
* 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) |
|
* 0x26, 0xFF, 0x00, // Logical Maximum (255) |
|
* 0x46, 0xFF, 0x00, // Physical Maximum (255) |
|
* 0x09, 0x30, // Usage (X) |
|
* 0x09, 0x31, // Usage (Y) |
|
* 0x09, 0x32, // Usage (Z) |
|
* 0x09, 0x35, // Usage (Rz) |
|
* 0x75, 0x08, // Report Size (8) |
|
* 0x95, 0x04, // Report Count (4) |
|
* 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) |
|
* 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) |
|
* 0x09, 0x20, // Usage (0x20) |
|
* 0x09, 0x21, // Usage (0x21) |
|
* 0x09, 0x22, // Usage (0x22) |
|
* 0x09, 0x23, // Usage (0x23) |
|
* 0x09, 0x24, // Usage (0x24) |
|
* 0x09, 0x25, // Usage (0x25) |
|
* 0x09, 0x26, // Usage (0x26) |
|
* 0x09, 0x27, // Usage (0x27) |
|
* 0x09, 0x28, // Usage (0x28) |
|
* 0x09, 0x29, // Usage (0x29) |
|
* 0x09, 0x2A, // Usage (0x2A) |
|
* 0x09, 0x2B, // Usage (0x2B) |
|
* 0x95, 0x0C, // Report Count (12) |
|
* 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) |
|
* 0x0A, 0x21, 0x26, // Usage (0x2621) |
|
* 0x95, 0x08, // Report Count (8) |
|
* 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) |
|
* 0x0A, 0x21, 0x26, // Usage (0x2621) |
|
* 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) |
|
* 0x26, 0xFF, 0x03, // Logical Maximum (1023) |
|
* 0x46, 0xFF, 0x03, // Physical Maximum (1023) |
|
* 0x09, 0x2C, // Usage (0x2C) |
|
* 0x09, 0x2D, // Usage (0x2D) |
|
* 0x09, 0x2E, // Usage (0x2E) |
|
* 0x09, 0x2F, // Usage (0x2F) |
|
* 0x75, 0x10, // Report Size (16) |
|
* 0x95, 0x04, // Report Count (4) |
|
* 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) |
|
* 0xC0, // End Collection |
|
*/ |
|
|
|
#define PID0902_RDESC_ORIG_SIZE 137 |
|
|
|
/* |
|
* The fixed descriptor for 0x146b:0x0902 |
|
* |
|
* - map buttons according to gamepad.rst |
|
* - assign right stick from Z/Rz to Rx/Ry |
|
* - map previously unused analog trigger data to Z/RZ |
|
* - simplify feature and output descriptor |
|
*/ |
|
static __u8 pid0902_rdesc_fixed[] = { |
|
0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ |
|
0x09, 0x05, /* Usage (Game Pad) */ |
|
0xA1, 0x01, /* Collection (Application) */ |
|
0x15, 0x00, /* Logical Minimum (0) */ |
|
0x25, 0x01, /* Logical Maximum (1) */ |
|
0x35, 0x00, /* Physical Minimum (0) */ |
|
0x45, 0x01, /* Physical Maximum (1) */ |
|
0x75, 0x01, /* Report Size (1) */ |
|
0x95, 0x0D, /* Report Count (13) */ |
|
0x05, 0x09, /* Usage Page (Button) */ |
|
0x09, 0x05, /* Usage (BTN_WEST) */ |
|
0x09, 0x01, /* Usage (BTN_SOUTH) */ |
|
0x09, 0x02, /* Usage (BTN_EAST) */ |
|
0x09, 0x04, /* Usage (BTN_NORTH) */ |
|
0x09, 0x07, /* Usage (BTN_TL) */ |
|
0x09, 0x08, /* Usage (BTN_TR) */ |
|
0x09, 0x09, /* Usage (BTN_TL2) */ |
|
0x09, 0x0A, /* Usage (BTN_TR2) */ |
|
0x09, 0x0B, /* Usage (BTN_SELECT) */ |
|
0x09, 0x0C, /* Usage (BTN_START) */ |
|
0x09, 0x0E, /* Usage (BTN_THUMBL) */ |
|
0x09, 0x0F, /* Usage (BTN_THUMBR) */ |
|
0x09, 0x0D, /* Usage (BTN_MODE) */ |
|
0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ |
|
0x75, 0x01, /* Report Size (1) */ |
|
0x95, 0x03, /* Report Count (3) */ |
|
0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ |
|
0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ |
|
0x25, 0x07, /* Logical Maximum (7) */ |
|
0x46, 0x3B, 0x01, /* Physical Maximum (315) */ |
|
0x75, 0x04, /* Report Size (4) */ |
|
0x95, 0x01, /* Report Count (1) */ |
|
0x65, 0x14, /* Unit (System: English Rotation, Length: Centimeter) */ |
|
0x09, 0x39, /* Usage (Hat switch) */ |
|
0x81, 0x42, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) */ |
|
0x65, 0x00, /* Unit (None) */ |
|
0x95, 0x01, /* Report Count (1) */ |
|
0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ |
|
0x26, 0xFF, 0x00, /* Logical Maximum (255) */ |
|
0x46, 0xFF, 0x00, /* Physical Maximum (255) */ |
|
0x09, 0x30, /* Usage (X) */ |
|
0x09, 0x31, /* Usage (Y) */ |
|
0x09, 0x33, /* Usage (Rx) */ |
|
0x09, 0x34, /* Usage (Ry) */ |
|
0x75, 0x08, /* Report Size (8) */ |
|
0x95, 0x04, /* Report Count (4) */ |
|
0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ |
|
0x95, 0x0A, /* Report Count (10) */ |
|
0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ |
|
0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ |
|
0x26, 0xFF, 0x00, /* Logical Maximum (255) */ |
|
0x46, 0xFF, 0x00, /* Physical Maximum (255) */ |
|
0x09, 0x32, /* Usage (Z) */ |
|
0x09, 0x35, /* Usage (Rz) */ |
|
0x95, 0x02, /* Report Count (2) */ |
|
0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ |
|
0x95, 0x08, /* Report Count (8) */ |
|
0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ |
|
0x06, 0x00, 0xFF, /* Usage Page (Vendor Defined 0xFF00) */ |
|
0xB1, 0x02, /* Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) */ |
|
0x0A, 0x21, 0x26, /* Usage (0x2621) */ |
|
0x95, 0x08, /* Report Count (8) */ |
|
0x91, 0x02, /* Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) */ |
|
0x0A, 0x21, 0x26, /* Usage (0x2621) */ |
|
0x95, 0x08, /* Report Count (8) */ |
|
0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ |
|
0xC0, /* End Collection */ |
|
}; |
|
|
|
#define NUM_LEDS 4 |
|
|
|
struct bigben_device { |
|
struct hid_device *hid; |
|
struct hid_report *report; |
|
bool removed; |
|
u8 led_state; /* LED1 = 1 .. LED4 = 8 */ |
|
u8 right_motor_on; /* right motor off/on 0/1 */ |
|
u8 left_motor_force; /* left motor force 0-255 */ |
|
struct led_classdev *leds[NUM_LEDS]; |
|
bool work_led; |
|
bool work_ff; |
|
struct work_struct worker; |
|
}; |
|
|
|
|
|
static void bigben_worker(struct work_struct *work) |
|
{ |
|
struct bigben_device *bigben = container_of(work, |
|
struct bigben_device, worker); |
|
struct hid_field *report_field = bigben->report->field[0]; |
|
|
|
if (bigben->removed) |
|
return; |
|
|
|
if (bigben->work_led) { |
|
bigben->work_led = false; |
|
report_field->value[0] = 0x01; /* 1 = led message */ |
|
report_field->value[1] = 0x08; /* reserved value, always 8 */ |
|
report_field->value[2] = bigben->led_state; |
|
report_field->value[3] = 0x00; /* padding */ |
|
report_field->value[4] = 0x00; /* padding */ |
|
report_field->value[5] = 0x00; /* padding */ |
|
report_field->value[6] = 0x00; /* padding */ |
|
report_field->value[7] = 0x00; /* padding */ |
|
hid_hw_request(bigben->hid, bigben->report, HID_REQ_SET_REPORT); |
|
} |
|
|
|
if (bigben->work_ff) { |
|
bigben->work_ff = false; |
|
report_field->value[0] = 0x02; /* 2 = rumble effect message */ |
|
report_field->value[1] = 0x08; /* reserved value, always 8 */ |
|
report_field->value[2] = bigben->right_motor_on; |
|
report_field->value[3] = bigben->left_motor_force; |
|
report_field->value[4] = 0xff; /* duration 0-254 (255 = nonstop) */ |
|
report_field->value[5] = 0x00; /* padding */ |
|
report_field->value[6] = 0x00; /* padding */ |
|
report_field->value[7] = 0x00; /* padding */ |
|
hid_hw_request(bigben->hid, bigben->report, HID_REQ_SET_REPORT); |
|
} |
|
} |
|
|
|
static int hid_bigben_play_effect(struct input_dev *dev, void *data, |
|
struct ff_effect *effect) |
|
{ |
|
struct hid_device *hid = input_get_drvdata(dev); |
|
struct bigben_device *bigben = hid_get_drvdata(hid); |
|
u8 right_motor_on; |
|
u8 left_motor_force; |
|
|
|
if (!bigben) { |
|
hid_err(hid, "no device data\n"); |
|
return 0; |
|
} |
|
|
|
if (effect->type != FF_RUMBLE) |
|
return 0; |
|
|
|
right_motor_on = effect->u.rumble.weak_magnitude ? 1 : 0; |
|
left_motor_force = effect->u.rumble.strong_magnitude / 256; |
|
|
|
if (right_motor_on != bigben->right_motor_on || |
|
left_motor_force != bigben->left_motor_force) { |
|
bigben->right_motor_on = right_motor_on; |
|
bigben->left_motor_force = left_motor_force; |
|
bigben->work_ff = true; |
|
schedule_work(&bigben->worker); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void bigben_set_led(struct led_classdev *led, |
|
enum led_brightness value) |
|
{ |
|
struct device *dev = led->dev->parent; |
|
struct hid_device *hid = to_hid_device(dev); |
|
struct bigben_device *bigben = hid_get_drvdata(hid); |
|
int n; |
|
bool work; |
|
|
|
if (!bigben) { |
|
hid_err(hid, "no device data\n"); |
|
return; |
|
} |
|
|
|
for (n = 0; n < NUM_LEDS; n++) { |
|
if (led == bigben->leds[n]) { |
|
if (value == LED_OFF) { |
|
work = (bigben->led_state & BIT(n)); |
|
bigben->led_state &= ~BIT(n); |
|
} else { |
|
work = !(bigben->led_state & BIT(n)); |
|
bigben->led_state |= BIT(n); |
|
} |
|
|
|
if (work) { |
|
bigben->work_led = true; |
|
schedule_work(&bigben->worker); |
|
} |
|
return; |
|
} |
|
} |
|
} |
|
|
|
static enum led_brightness bigben_get_led(struct led_classdev *led) |
|
{ |
|
struct device *dev = led->dev->parent; |
|
struct hid_device *hid = to_hid_device(dev); |
|
struct bigben_device *bigben = hid_get_drvdata(hid); |
|
int n; |
|
|
|
if (!bigben) { |
|
hid_err(hid, "no device data\n"); |
|
return LED_OFF; |
|
} |
|
|
|
for (n = 0; n < NUM_LEDS; n++) { |
|
if (led == bigben->leds[n]) |
|
return (bigben->led_state & BIT(n)) ? LED_ON : LED_OFF; |
|
} |
|
|
|
return LED_OFF; |
|
} |
|
|
|
static void bigben_remove(struct hid_device *hid) |
|
{ |
|
struct bigben_device *bigben = hid_get_drvdata(hid); |
|
|
|
bigben->removed = true; |
|
cancel_work_sync(&bigben->worker); |
|
hid_hw_stop(hid); |
|
} |
|
|
|
static int bigben_probe(struct hid_device *hid, |
|
const struct hid_device_id *id) |
|
{ |
|
struct bigben_device *bigben; |
|
struct hid_input *hidinput; |
|
struct list_head *report_list; |
|
struct led_classdev *led; |
|
char *name; |
|
size_t name_sz; |
|
int n, error; |
|
|
|
bigben = devm_kzalloc(&hid->dev, sizeof(*bigben), GFP_KERNEL); |
|
if (!bigben) |
|
return -ENOMEM; |
|
hid_set_drvdata(hid, bigben); |
|
bigben->hid = hid; |
|
bigben->removed = false; |
|
|
|
error = hid_parse(hid); |
|
if (error) { |
|
hid_err(hid, "parse failed\n"); |
|
return error; |
|
} |
|
|
|
error = hid_hw_start(hid, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); |
|
if (error) { |
|
hid_err(hid, "hw start failed\n"); |
|
return error; |
|
} |
|
|
|
report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; |
|
bigben->report = list_entry(report_list->next, |
|
struct hid_report, list); |
|
|
|
hidinput = list_first_entry(&hid->inputs, struct hid_input, list); |
|
set_bit(FF_RUMBLE, hidinput->input->ffbit); |
|
|
|
INIT_WORK(&bigben->worker, bigben_worker); |
|
|
|
error = input_ff_create_memless(hidinput->input, NULL, |
|
hid_bigben_play_effect); |
|
if (error) |
|
goto error_hw_stop; |
|
|
|
name_sz = strlen(dev_name(&hid->dev)) + strlen(":red:bigben#") + 1; |
|
|
|
for (n = 0; n < NUM_LEDS; n++) { |
|
led = devm_kzalloc( |
|
&hid->dev, |
|
sizeof(struct led_classdev) + name_sz, |
|
GFP_KERNEL |
|
); |
|
if (!led) { |
|
error = -ENOMEM; |
|
goto error_hw_stop; |
|
} |
|
name = (void *)(&led[1]); |
|
snprintf(name, name_sz, |
|
"%s:red:bigben%d", |
|
dev_name(&hid->dev), n + 1 |
|
); |
|
led->name = name; |
|
led->brightness = (n == 0) ? LED_ON : LED_OFF; |
|
led->max_brightness = 1; |
|
led->brightness_get = bigben_get_led; |
|
led->brightness_set = bigben_set_led; |
|
bigben->leds[n] = led; |
|
error = devm_led_classdev_register(&hid->dev, led); |
|
if (error) |
|
goto error_hw_stop; |
|
} |
|
|
|
/* initial state: LED1 is on, no rumble effect */ |
|
bigben->led_state = BIT(0); |
|
bigben->right_motor_on = 0; |
|
bigben->left_motor_force = 0; |
|
bigben->work_led = true; |
|
bigben->work_ff = true; |
|
schedule_work(&bigben->worker); |
|
|
|
hid_info(hid, "LED and force feedback support for BigBen gamepad\n"); |
|
|
|
return 0; |
|
|
|
error_hw_stop: |
|
hid_hw_stop(hid); |
|
return error; |
|
} |
|
|
|
static __u8 *bigben_report_fixup(struct hid_device *hid, __u8 *rdesc, |
|
unsigned int *rsize) |
|
{ |
|
if (*rsize == PID0902_RDESC_ORIG_SIZE) { |
|
rdesc = pid0902_rdesc_fixed; |
|
*rsize = sizeof(pid0902_rdesc_fixed); |
|
} else |
|
hid_warn(hid, "unexpected rdesc, please submit for review\n"); |
|
return rdesc; |
|
} |
|
|
|
static const struct hid_device_id bigben_devices[] = { |
|
{ HID_USB_DEVICE(USB_VENDOR_ID_BIGBEN, USB_DEVICE_ID_BIGBEN_PS3OFMINIPAD) }, |
|
{ } |
|
}; |
|
MODULE_DEVICE_TABLE(hid, bigben_devices); |
|
|
|
static struct hid_driver bigben_driver = { |
|
.name = "bigben", |
|
.id_table = bigben_devices, |
|
.probe = bigben_probe, |
|
.report_fixup = bigben_report_fixup, |
|
.remove = bigben_remove, |
|
}; |
|
module_hid_driver(bigben_driver); |
|
|
|
MODULE_LICENSE("GPL");
|
|
|