162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci/* 462306a36Sopenharmony_ci * LED & force feedback support for BigBen Interactive 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * 0x146b:0x0902 "Bigben Interactive Bigben Game Pad" 762306a36Sopenharmony_ci * "Kid-friendly Wired Controller" PS3OFMINIPAD SONY 862306a36Sopenharmony_ci * sold for use with the PS3 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * Copyright (c) 2018 Hanno Zulla <kontakt@hanno.de> 1162306a36Sopenharmony_ci */ 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/input.h> 1462306a36Sopenharmony_ci#include <linux/slab.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/leds.h> 1762306a36Sopenharmony_ci#include <linux/hid.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#include "hid-ids.h" 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci/* 2362306a36Sopenharmony_ci * The original descriptor for 0x146b:0x0902 2462306a36Sopenharmony_ci * 2562306a36Sopenharmony_ci * 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 2662306a36Sopenharmony_ci * 0x09, 0x05, // Usage (Game Pad) 2762306a36Sopenharmony_ci * 0xA1, 0x01, // Collection (Application) 2862306a36Sopenharmony_ci * 0x15, 0x00, // Logical Minimum (0) 2962306a36Sopenharmony_ci * 0x25, 0x01, // Logical Maximum (1) 3062306a36Sopenharmony_ci * 0x35, 0x00, // Physical Minimum (0) 3162306a36Sopenharmony_ci * 0x45, 0x01, // Physical Maximum (1) 3262306a36Sopenharmony_ci * 0x75, 0x01, // Report Size (1) 3362306a36Sopenharmony_ci * 0x95, 0x0D, // Report Count (13) 3462306a36Sopenharmony_ci * 0x05, 0x09, // Usage Page (Button) 3562306a36Sopenharmony_ci * 0x19, 0x01, // Usage Minimum (0x01) 3662306a36Sopenharmony_ci * 0x29, 0x0D, // Usage Maximum (0x0D) 3762306a36Sopenharmony_ci * 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 3862306a36Sopenharmony_ci * 0x95, 0x03, // Report Count (3) 3962306a36Sopenharmony_ci * 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) 4062306a36Sopenharmony_ci * 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 4162306a36Sopenharmony_ci * 0x25, 0x07, // Logical Maximum (7) 4262306a36Sopenharmony_ci * 0x46, 0x3B, 0x01, // Physical Maximum (315) 4362306a36Sopenharmony_ci * 0x75, 0x04, // Report Size (4) 4462306a36Sopenharmony_ci * 0x95, 0x01, // Report Count (1) 4562306a36Sopenharmony_ci * 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter) 4662306a36Sopenharmony_ci * 0x09, 0x39, // Usage (Hat switch) 4762306a36Sopenharmony_ci * 0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) 4862306a36Sopenharmony_ci * 0x65, 0x00, // Unit (None) 4962306a36Sopenharmony_ci * 0x95, 0x01, // Report Count (1) 5062306a36Sopenharmony_ci * 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) 5162306a36Sopenharmony_ci * 0x26, 0xFF, 0x00, // Logical Maximum (255) 5262306a36Sopenharmony_ci * 0x46, 0xFF, 0x00, // Physical Maximum (255) 5362306a36Sopenharmony_ci * 0x09, 0x30, // Usage (X) 5462306a36Sopenharmony_ci * 0x09, 0x31, // Usage (Y) 5562306a36Sopenharmony_ci * 0x09, 0x32, // Usage (Z) 5662306a36Sopenharmony_ci * 0x09, 0x35, // Usage (Rz) 5762306a36Sopenharmony_ci * 0x75, 0x08, // Report Size (8) 5862306a36Sopenharmony_ci * 0x95, 0x04, // Report Count (4) 5962306a36Sopenharmony_ci * 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 6062306a36Sopenharmony_ci * 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) 6162306a36Sopenharmony_ci * 0x09, 0x20, // Usage (0x20) 6262306a36Sopenharmony_ci * 0x09, 0x21, // Usage (0x21) 6362306a36Sopenharmony_ci * 0x09, 0x22, // Usage (0x22) 6462306a36Sopenharmony_ci * 0x09, 0x23, // Usage (0x23) 6562306a36Sopenharmony_ci * 0x09, 0x24, // Usage (0x24) 6662306a36Sopenharmony_ci * 0x09, 0x25, // Usage (0x25) 6762306a36Sopenharmony_ci * 0x09, 0x26, // Usage (0x26) 6862306a36Sopenharmony_ci * 0x09, 0x27, // Usage (0x27) 6962306a36Sopenharmony_ci * 0x09, 0x28, // Usage (0x28) 7062306a36Sopenharmony_ci * 0x09, 0x29, // Usage (0x29) 7162306a36Sopenharmony_ci * 0x09, 0x2A, // Usage (0x2A) 7262306a36Sopenharmony_ci * 0x09, 0x2B, // Usage (0x2B) 7362306a36Sopenharmony_ci * 0x95, 0x0C, // Report Count (12) 7462306a36Sopenharmony_ci * 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 7562306a36Sopenharmony_ci * 0x0A, 0x21, 0x26, // Usage (0x2621) 7662306a36Sopenharmony_ci * 0x95, 0x08, // Report Count (8) 7762306a36Sopenharmony_ci * 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 7862306a36Sopenharmony_ci * 0x0A, 0x21, 0x26, // Usage (0x2621) 7962306a36Sopenharmony_ci * 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 8062306a36Sopenharmony_ci * 0x26, 0xFF, 0x03, // Logical Maximum (1023) 8162306a36Sopenharmony_ci * 0x46, 0xFF, 0x03, // Physical Maximum (1023) 8262306a36Sopenharmony_ci * 0x09, 0x2C, // Usage (0x2C) 8362306a36Sopenharmony_ci * 0x09, 0x2D, // Usage (0x2D) 8462306a36Sopenharmony_ci * 0x09, 0x2E, // Usage (0x2E) 8562306a36Sopenharmony_ci * 0x09, 0x2F, // Usage (0x2F) 8662306a36Sopenharmony_ci * 0x75, 0x10, // Report Size (16) 8762306a36Sopenharmony_ci * 0x95, 0x04, // Report Count (4) 8862306a36Sopenharmony_ci * 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 8962306a36Sopenharmony_ci * 0xC0, // End Collection 9062306a36Sopenharmony_ci */ 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci#define PID0902_RDESC_ORIG_SIZE 137 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci/* 9562306a36Sopenharmony_ci * The fixed descriptor for 0x146b:0x0902 9662306a36Sopenharmony_ci * 9762306a36Sopenharmony_ci * - map buttons according to gamepad.rst 9862306a36Sopenharmony_ci * - assign right stick from Z/Rz to Rx/Ry 9962306a36Sopenharmony_ci * - map previously unused analog trigger data to Z/RZ 10062306a36Sopenharmony_ci * - simplify feature and output descriptor 10162306a36Sopenharmony_ci */ 10262306a36Sopenharmony_cistatic __u8 pid0902_rdesc_fixed[] = { 10362306a36Sopenharmony_ci 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ 10462306a36Sopenharmony_ci 0x09, 0x05, /* Usage (Game Pad) */ 10562306a36Sopenharmony_ci 0xA1, 0x01, /* Collection (Application) */ 10662306a36Sopenharmony_ci 0x15, 0x00, /* Logical Minimum (0) */ 10762306a36Sopenharmony_ci 0x25, 0x01, /* Logical Maximum (1) */ 10862306a36Sopenharmony_ci 0x35, 0x00, /* Physical Minimum (0) */ 10962306a36Sopenharmony_ci 0x45, 0x01, /* Physical Maximum (1) */ 11062306a36Sopenharmony_ci 0x75, 0x01, /* Report Size (1) */ 11162306a36Sopenharmony_ci 0x95, 0x0D, /* Report Count (13) */ 11262306a36Sopenharmony_ci 0x05, 0x09, /* Usage Page (Button) */ 11362306a36Sopenharmony_ci 0x09, 0x05, /* Usage (BTN_WEST) */ 11462306a36Sopenharmony_ci 0x09, 0x01, /* Usage (BTN_SOUTH) */ 11562306a36Sopenharmony_ci 0x09, 0x02, /* Usage (BTN_EAST) */ 11662306a36Sopenharmony_ci 0x09, 0x04, /* Usage (BTN_NORTH) */ 11762306a36Sopenharmony_ci 0x09, 0x07, /* Usage (BTN_TL) */ 11862306a36Sopenharmony_ci 0x09, 0x08, /* Usage (BTN_TR) */ 11962306a36Sopenharmony_ci 0x09, 0x09, /* Usage (BTN_TL2) */ 12062306a36Sopenharmony_ci 0x09, 0x0A, /* Usage (BTN_TR2) */ 12162306a36Sopenharmony_ci 0x09, 0x0B, /* Usage (BTN_SELECT) */ 12262306a36Sopenharmony_ci 0x09, 0x0C, /* Usage (BTN_START) */ 12362306a36Sopenharmony_ci 0x09, 0x0E, /* Usage (BTN_THUMBL) */ 12462306a36Sopenharmony_ci 0x09, 0x0F, /* Usage (BTN_THUMBR) */ 12562306a36Sopenharmony_ci 0x09, 0x0D, /* Usage (BTN_MODE) */ 12662306a36Sopenharmony_ci 0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ 12762306a36Sopenharmony_ci 0x75, 0x01, /* Report Size (1) */ 12862306a36Sopenharmony_ci 0x95, 0x03, /* Report Count (3) */ 12962306a36Sopenharmony_ci 0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ 13062306a36Sopenharmony_ci 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ 13162306a36Sopenharmony_ci 0x25, 0x07, /* Logical Maximum (7) */ 13262306a36Sopenharmony_ci 0x46, 0x3B, 0x01, /* Physical Maximum (315) */ 13362306a36Sopenharmony_ci 0x75, 0x04, /* Report Size (4) */ 13462306a36Sopenharmony_ci 0x95, 0x01, /* Report Count (1) */ 13562306a36Sopenharmony_ci 0x65, 0x14, /* Unit (System: English Rotation, Length: Centimeter) */ 13662306a36Sopenharmony_ci 0x09, 0x39, /* Usage (Hat switch) */ 13762306a36Sopenharmony_ci 0x81, 0x42, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) */ 13862306a36Sopenharmony_ci 0x65, 0x00, /* Unit (None) */ 13962306a36Sopenharmony_ci 0x95, 0x01, /* Report Count (1) */ 14062306a36Sopenharmony_ci 0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ 14162306a36Sopenharmony_ci 0x26, 0xFF, 0x00, /* Logical Maximum (255) */ 14262306a36Sopenharmony_ci 0x46, 0xFF, 0x00, /* Physical Maximum (255) */ 14362306a36Sopenharmony_ci 0x09, 0x30, /* Usage (X) */ 14462306a36Sopenharmony_ci 0x09, 0x31, /* Usage (Y) */ 14562306a36Sopenharmony_ci 0x09, 0x33, /* Usage (Rx) */ 14662306a36Sopenharmony_ci 0x09, 0x34, /* Usage (Ry) */ 14762306a36Sopenharmony_ci 0x75, 0x08, /* Report Size (8) */ 14862306a36Sopenharmony_ci 0x95, 0x04, /* Report Count (4) */ 14962306a36Sopenharmony_ci 0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ 15062306a36Sopenharmony_ci 0x95, 0x0A, /* Report Count (10) */ 15162306a36Sopenharmony_ci 0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ 15262306a36Sopenharmony_ci 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ 15362306a36Sopenharmony_ci 0x26, 0xFF, 0x00, /* Logical Maximum (255) */ 15462306a36Sopenharmony_ci 0x46, 0xFF, 0x00, /* Physical Maximum (255) */ 15562306a36Sopenharmony_ci 0x09, 0x32, /* Usage (Z) */ 15662306a36Sopenharmony_ci 0x09, 0x35, /* Usage (Rz) */ 15762306a36Sopenharmony_ci 0x95, 0x02, /* Report Count (2) */ 15862306a36Sopenharmony_ci 0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ 15962306a36Sopenharmony_ci 0x95, 0x08, /* Report Count (8) */ 16062306a36Sopenharmony_ci 0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ 16162306a36Sopenharmony_ci 0x06, 0x00, 0xFF, /* Usage Page (Vendor Defined 0xFF00) */ 16262306a36Sopenharmony_ci 0xB1, 0x02, /* Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) */ 16362306a36Sopenharmony_ci 0x0A, 0x21, 0x26, /* Usage (0x2621) */ 16462306a36Sopenharmony_ci 0x95, 0x08, /* Report Count (8) */ 16562306a36Sopenharmony_ci 0x91, 0x02, /* Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) */ 16662306a36Sopenharmony_ci 0x0A, 0x21, 0x26, /* Usage (0x2621) */ 16762306a36Sopenharmony_ci 0x95, 0x08, /* Report Count (8) */ 16862306a36Sopenharmony_ci 0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ 16962306a36Sopenharmony_ci 0xC0, /* End Collection */ 17062306a36Sopenharmony_ci}; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci#define NUM_LEDS 4 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_cistruct bigben_device { 17562306a36Sopenharmony_ci struct hid_device *hid; 17662306a36Sopenharmony_ci struct hid_report *report; 17762306a36Sopenharmony_ci spinlock_t lock; 17862306a36Sopenharmony_ci bool removed; 17962306a36Sopenharmony_ci u8 led_state; /* LED1 = 1 .. LED4 = 8 */ 18062306a36Sopenharmony_ci u8 right_motor_on; /* right motor off/on 0/1 */ 18162306a36Sopenharmony_ci u8 left_motor_force; /* left motor force 0-255 */ 18262306a36Sopenharmony_ci struct led_classdev *leds[NUM_LEDS]; 18362306a36Sopenharmony_ci bool work_led; 18462306a36Sopenharmony_ci bool work_ff; 18562306a36Sopenharmony_ci struct work_struct worker; 18662306a36Sopenharmony_ci}; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_cistatic inline void bigben_schedule_work(struct bigben_device *bigben) 18962306a36Sopenharmony_ci{ 19062306a36Sopenharmony_ci unsigned long flags; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci spin_lock_irqsave(&bigben->lock, flags); 19362306a36Sopenharmony_ci if (!bigben->removed) 19462306a36Sopenharmony_ci schedule_work(&bigben->worker); 19562306a36Sopenharmony_ci spin_unlock_irqrestore(&bigben->lock, flags); 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_cistatic void bigben_worker(struct work_struct *work) 19962306a36Sopenharmony_ci{ 20062306a36Sopenharmony_ci struct bigben_device *bigben = container_of(work, 20162306a36Sopenharmony_ci struct bigben_device, worker); 20262306a36Sopenharmony_ci struct hid_field *report_field = bigben->report->field[0]; 20362306a36Sopenharmony_ci bool do_work_led = false; 20462306a36Sopenharmony_ci bool do_work_ff = false; 20562306a36Sopenharmony_ci u8 *buf; 20662306a36Sopenharmony_ci u32 len; 20762306a36Sopenharmony_ci unsigned long flags; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci buf = hid_alloc_report_buf(bigben->report, GFP_KERNEL); 21062306a36Sopenharmony_ci if (!buf) 21162306a36Sopenharmony_ci return; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci len = hid_report_len(bigben->report); 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci /* LED work */ 21662306a36Sopenharmony_ci spin_lock_irqsave(&bigben->lock, flags); 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci if (bigben->work_led) { 21962306a36Sopenharmony_ci bigben->work_led = false; 22062306a36Sopenharmony_ci do_work_led = true; 22162306a36Sopenharmony_ci report_field->value[0] = 0x01; /* 1 = led message */ 22262306a36Sopenharmony_ci report_field->value[1] = 0x08; /* reserved value, always 8 */ 22362306a36Sopenharmony_ci report_field->value[2] = bigben->led_state; 22462306a36Sopenharmony_ci report_field->value[3] = 0x00; /* padding */ 22562306a36Sopenharmony_ci report_field->value[4] = 0x00; /* padding */ 22662306a36Sopenharmony_ci report_field->value[5] = 0x00; /* padding */ 22762306a36Sopenharmony_ci report_field->value[6] = 0x00; /* padding */ 22862306a36Sopenharmony_ci report_field->value[7] = 0x00; /* padding */ 22962306a36Sopenharmony_ci hid_output_report(bigben->report, buf); 23062306a36Sopenharmony_ci } 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci spin_unlock_irqrestore(&bigben->lock, flags); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci if (do_work_led) { 23562306a36Sopenharmony_ci hid_hw_raw_request(bigben->hid, bigben->report->id, buf, len, 23662306a36Sopenharmony_ci bigben->report->type, HID_REQ_SET_REPORT); 23762306a36Sopenharmony_ci } 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci /* FF work */ 24062306a36Sopenharmony_ci spin_lock_irqsave(&bigben->lock, flags); 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci if (bigben->work_ff) { 24362306a36Sopenharmony_ci bigben->work_ff = false; 24462306a36Sopenharmony_ci do_work_ff = true; 24562306a36Sopenharmony_ci report_field->value[0] = 0x02; /* 2 = rumble effect message */ 24662306a36Sopenharmony_ci report_field->value[1] = 0x08; /* reserved value, always 8 */ 24762306a36Sopenharmony_ci report_field->value[2] = bigben->right_motor_on; 24862306a36Sopenharmony_ci report_field->value[3] = bigben->left_motor_force; 24962306a36Sopenharmony_ci report_field->value[4] = 0xff; /* duration 0-254 (255 = nonstop) */ 25062306a36Sopenharmony_ci report_field->value[5] = 0x00; /* padding */ 25162306a36Sopenharmony_ci report_field->value[6] = 0x00; /* padding */ 25262306a36Sopenharmony_ci report_field->value[7] = 0x00; /* padding */ 25362306a36Sopenharmony_ci hid_output_report(bigben->report, buf); 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci spin_unlock_irqrestore(&bigben->lock, flags); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci if (do_work_ff) { 25962306a36Sopenharmony_ci hid_hw_raw_request(bigben->hid, bigben->report->id, buf, len, 26062306a36Sopenharmony_ci bigben->report->type, HID_REQ_SET_REPORT); 26162306a36Sopenharmony_ci } 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci kfree(buf); 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_cistatic int hid_bigben_play_effect(struct input_dev *dev, void *data, 26762306a36Sopenharmony_ci struct ff_effect *effect) 26862306a36Sopenharmony_ci{ 26962306a36Sopenharmony_ci struct hid_device *hid = input_get_drvdata(dev); 27062306a36Sopenharmony_ci struct bigben_device *bigben = hid_get_drvdata(hid); 27162306a36Sopenharmony_ci u8 right_motor_on; 27262306a36Sopenharmony_ci u8 left_motor_force; 27362306a36Sopenharmony_ci unsigned long flags; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci if (!bigben) { 27662306a36Sopenharmony_ci hid_err(hid, "no device data\n"); 27762306a36Sopenharmony_ci return 0; 27862306a36Sopenharmony_ci } 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci if (effect->type != FF_RUMBLE) 28162306a36Sopenharmony_ci return 0; 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci right_motor_on = effect->u.rumble.weak_magnitude ? 1 : 0; 28462306a36Sopenharmony_ci left_motor_force = effect->u.rumble.strong_magnitude / 256; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci if (right_motor_on != bigben->right_motor_on || 28762306a36Sopenharmony_ci left_motor_force != bigben->left_motor_force) { 28862306a36Sopenharmony_ci spin_lock_irqsave(&bigben->lock, flags); 28962306a36Sopenharmony_ci bigben->right_motor_on = right_motor_on; 29062306a36Sopenharmony_ci bigben->left_motor_force = left_motor_force; 29162306a36Sopenharmony_ci bigben->work_ff = true; 29262306a36Sopenharmony_ci spin_unlock_irqrestore(&bigben->lock, flags); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci bigben_schedule_work(bigben); 29562306a36Sopenharmony_ci } 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci return 0; 29862306a36Sopenharmony_ci} 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_cistatic void bigben_set_led(struct led_classdev *led, 30162306a36Sopenharmony_ci enum led_brightness value) 30262306a36Sopenharmony_ci{ 30362306a36Sopenharmony_ci struct device *dev = led->dev->parent; 30462306a36Sopenharmony_ci struct hid_device *hid = to_hid_device(dev); 30562306a36Sopenharmony_ci struct bigben_device *bigben = hid_get_drvdata(hid); 30662306a36Sopenharmony_ci int n; 30762306a36Sopenharmony_ci bool work; 30862306a36Sopenharmony_ci unsigned long flags; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci if (!bigben) { 31162306a36Sopenharmony_ci hid_err(hid, "no device data\n"); 31262306a36Sopenharmony_ci return; 31362306a36Sopenharmony_ci } 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci for (n = 0; n < NUM_LEDS; n++) { 31662306a36Sopenharmony_ci if (led == bigben->leds[n]) { 31762306a36Sopenharmony_ci spin_lock_irqsave(&bigben->lock, flags); 31862306a36Sopenharmony_ci if (value == LED_OFF) { 31962306a36Sopenharmony_ci work = (bigben->led_state & BIT(n)); 32062306a36Sopenharmony_ci bigben->led_state &= ~BIT(n); 32162306a36Sopenharmony_ci } else { 32262306a36Sopenharmony_ci work = !(bigben->led_state & BIT(n)); 32362306a36Sopenharmony_ci bigben->led_state |= BIT(n); 32462306a36Sopenharmony_ci } 32562306a36Sopenharmony_ci spin_unlock_irqrestore(&bigben->lock, flags); 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci if (work) { 32862306a36Sopenharmony_ci bigben->work_led = true; 32962306a36Sopenharmony_ci bigben_schedule_work(bigben); 33062306a36Sopenharmony_ci } 33162306a36Sopenharmony_ci return; 33262306a36Sopenharmony_ci } 33362306a36Sopenharmony_ci } 33462306a36Sopenharmony_ci} 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_cistatic enum led_brightness bigben_get_led(struct led_classdev *led) 33762306a36Sopenharmony_ci{ 33862306a36Sopenharmony_ci struct device *dev = led->dev->parent; 33962306a36Sopenharmony_ci struct hid_device *hid = to_hid_device(dev); 34062306a36Sopenharmony_ci struct bigben_device *bigben = hid_get_drvdata(hid); 34162306a36Sopenharmony_ci int n; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci if (!bigben) { 34462306a36Sopenharmony_ci hid_err(hid, "no device data\n"); 34562306a36Sopenharmony_ci return LED_OFF; 34662306a36Sopenharmony_ci } 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci for (n = 0; n < NUM_LEDS; n++) { 34962306a36Sopenharmony_ci if (led == bigben->leds[n]) 35062306a36Sopenharmony_ci return (bigben->led_state & BIT(n)) ? LED_ON : LED_OFF; 35162306a36Sopenharmony_ci } 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci return LED_OFF; 35462306a36Sopenharmony_ci} 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_cistatic void bigben_remove(struct hid_device *hid) 35762306a36Sopenharmony_ci{ 35862306a36Sopenharmony_ci struct bigben_device *bigben = hid_get_drvdata(hid); 35962306a36Sopenharmony_ci unsigned long flags; 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci spin_lock_irqsave(&bigben->lock, flags); 36262306a36Sopenharmony_ci bigben->removed = true; 36362306a36Sopenharmony_ci spin_unlock_irqrestore(&bigben->lock, flags); 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci cancel_work_sync(&bigben->worker); 36662306a36Sopenharmony_ci hid_hw_stop(hid); 36762306a36Sopenharmony_ci} 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_cistatic int bigben_probe(struct hid_device *hid, 37062306a36Sopenharmony_ci const struct hid_device_id *id) 37162306a36Sopenharmony_ci{ 37262306a36Sopenharmony_ci struct bigben_device *bigben; 37362306a36Sopenharmony_ci struct hid_input *hidinput; 37462306a36Sopenharmony_ci struct led_classdev *led; 37562306a36Sopenharmony_ci char *name; 37662306a36Sopenharmony_ci size_t name_sz; 37762306a36Sopenharmony_ci int n, error; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci bigben = devm_kzalloc(&hid->dev, sizeof(*bigben), GFP_KERNEL); 38062306a36Sopenharmony_ci if (!bigben) 38162306a36Sopenharmony_ci return -ENOMEM; 38262306a36Sopenharmony_ci hid_set_drvdata(hid, bigben); 38362306a36Sopenharmony_ci bigben->hid = hid; 38462306a36Sopenharmony_ci bigben->removed = false; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci error = hid_parse(hid); 38762306a36Sopenharmony_ci if (error) { 38862306a36Sopenharmony_ci hid_err(hid, "parse failed\n"); 38962306a36Sopenharmony_ci return error; 39062306a36Sopenharmony_ci } 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci error = hid_hw_start(hid, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); 39362306a36Sopenharmony_ci if (error) { 39462306a36Sopenharmony_ci hid_err(hid, "hw start failed\n"); 39562306a36Sopenharmony_ci return error; 39662306a36Sopenharmony_ci } 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci bigben->report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 8); 39962306a36Sopenharmony_ci if (!bigben->report) { 40062306a36Sopenharmony_ci hid_err(hid, "no output report found\n"); 40162306a36Sopenharmony_ci error = -ENODEV; 40262306a36Sopenharmony_ci goto error_hw_stop; 40362306a36Sopenharmony_ci } 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci if (list_empty(&hid->inputs)) { 40662306a36Sopenharmony_ci hid_err(hid, "no inputs found\n"); 40762306a36Sopenharmony_ci error = -ENODEV; 40862306a36Sopenharmony_ci goto error_hw_stop; 40962306a36Sopenharmony_ci } 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci hidinput = list_first_entry(&hid->inputs, struct hid_input, list); 41262306a36Sopenharmony_ci set_bit(FF_RUMBLE, hidinput->input->ffbit); 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci INIT_WORK(&bigben->worker, bigben_worker); 41562306a36Sopenharmony_ci spin_lock_init(&bigben->lock); 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci error = input_ff_create_memless(hidinput->input, NULL, 41862306a36Sopenharmony_ci hid_bigben_play_effect); 41962306a36Sopenharmony_ci if (error) 42062306a36Sopenharmony_ci goto error_hw_stop; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci name_sz = strlen(dev_name(&hid->dev)) + strlen(":red:bigben#") + 1; 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci for (n = 0; n < NUM_LEDS; n++) { 42562306a36Sopenharmony_ci led = devm_kzalloc( 42662306a36Sopenharmony_ci &hid->dev, 42762306a36Sopenharmony_ci sizeof(struct led_classdev) + name_sz, 42862306a36Sopenharmony_ci GFP_KERNEL 42962306a36Sopenharmony_ci ); 43062306a36Sopenharmony_ci if (!led) { 43162306a36Sopenharmony_ci error = -ENOMEM; 43262306a36Sopenharmony_ci goto error_hw_stop; 43362306a36Sopenharmony_ci } 43462306a36Sopenharmony_ci name = (void *)(&led[1]); 43562306a36Sopenharmony_ci snprintf(name, name_sz, 43662306a36Sopenharmony_ci "%s:red:bigben%d", 43762306a36Sopenharmony_ci dev_name(&hid->dev), n + 1 43862306a36Sopenharmony_ci ); 43962306a36Sopenharmony_ci led->name = name; 44062306a36Sopenharmony_ci led->brightness = (n == 0) ? LED_ON : LED_OFF; 44162306a36Sopenharmony_ci led->max_brightness = 1; 44262306a36Sopenharmony_ci led->brightness_get = bigben_get_led; 44362306a36Sopenharmony_ci led->brightness_set = bigben_set_led; 44462306a36Sopenharmony_ci bigben->leds[n] = led; 44562306a36Sopenharmony_ci error = devm_led_classdev_register(&hid->dev, led); 44662306a36Sopenharmony_ci if (error) 44762306a36Sopenharmony_ci goto error_hw_stop; 44862306a36Sopenharmony_ci } 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci /* initial state: LED1 is on, no rumble effect */ 45162306a36Sopenharmony_ci bigben->led_state = BIT(0); 45262306a36Sopenharmony_ci bigben->right_motor_on = 0; 45362306a36Sopenharmony_ci bigben->left_motor_force = 0; 45462306a36Sopenharmony_ci bigben->work_led = true; 45562306a36Sopenharmony_ci bigben->work_ff = true; 45662306a36Sopenharmony_ci bigben_schedule_work(bigben); 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci hid_info(hid, "LED and force feedback support for BigBen gamepad\n"); 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci return 0; 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_cierror_hw_stop: 46362306a36Sopenharmony_ci hid_hw_stop(hid); 46462306a36Sopenharmony_ci return error; 46562306a36Sopenharmony_ci} 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_cistatic __u8 *bigben_report_fixup(struct hid_device *hid, __u8 *rdesc, 46862306a36Sopenharmony_ci unsigned int *rsize) 46962306a36Sopenharmony_ci{ 47062306a36Sopenharmony_ci if (*rsize == PID0902_RDESC_ORIG_SIZE) { 47162306a36Sopenharmony_ci rdesc = pid0902_rdesc_fixed; 47262306a36Sopenharmony_ci *rsize = sizeof(pid0902_rdesc_fixed); 47362306a36Sopenharmony_ci } else 47462306a36Sopenharmony_ci hid_warn(hid, "unexpected rdesc, please submit for review\n"); 47562306a36Sopenharmony_ci return rdesc; 47662306a36Sopenharmony_ci} 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_cistatic const struct hid_device_id bigben_devices[] = { 47962306a36Sopenharmony_ci { HID_USB_DEVICE(USB_VENDOR_ID_BIGBEN, USB_DEVICE_ID_BIGBEN_PS3OFMINIPAD) }, 48062306a36Sopenharmony_ci { } 48162306a36Sopenharmony_ci}; 48262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(hid, bigben_devices); 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_cistatic struct hid_driver bigben_driver = { 48562306a36Sopenharmony_ci .name = "bigben", 48662306a36Sopenharmony_ci .id_table = bigben_devices, 48762306a36Sopenharmony_ci .probe = bigben_probe, 48862306a36Sopenharmony_ci .report_fixup = bigben_report_fixup, 48962306a36Sopenharmony_ci .remove = bigben_remove, 49062306a36Sopenharmony_ci}; 49162306a36Sopenharmony_cimodule_hid_driver(bigben_driver); 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 494