Hanno Zulla | 256a90e | 2018-08-23 17:03:38 +0200 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | |
| 3 | /* |
| 4 | * LED & force feedback support for BigBen Interactive |
| 5 | * |
| 6 | * 0x146b:0x0902 "Bigben Interactive Bigben Game Pad" |
| 7 | * "Kid-friendly Wired Controller" PS3OFMINIPAD SONY |
| 8 | * sold for use with the PS3 |
| 9 | * |
| 10 | * Copyright (c) 2018 Hanno Zulla <kontakt@hanno.de> |
| 11 | */ |
| 12 | |
| 13 | #include <linux/input.h> |
| 14 | #include <linux/slab.h> |
| 15 | #include <linux/module.h> |
| 16 | #include <linux/leds.h> |
| 17 | #include <linux/hid.h> |
| 18 | |
| 19 | #include "hid-ids.h" |
| 20 | |
| 21 | |
| 22 | /* |
| 23 | * The original descriptor for 0x146b:0x0902 |
| 24 | * |
| 25 | * 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) |
| 26 | * 0x09, 0x05, // Usage (Game Pad) |
| 27 | * 0xA1, 0x01, // Collection (Application) |
| 28 | * 0x15, 0x00, // Logical Minimum (0) |
| 29 | * 0x25, 0x01, // Logical Maximum (1) |
| 30 | * 0x35, 0x00, // Physical Minimum (0) |
| 31 | * 0x45, 0x01, // Physical Maximum (1) |
| 32 | * 0x75, 0x01, // Report Size (1) |
| 33 | * 0x95, 0x0D, // Report Count (13) |
| 34 | * 0x05, 0x09, // Usage Page (Button) |
| 35 | * 0x19, 0x01, // Usage Minimum (0x01) |
| 36 | * 0x29, 0x0D, // Usage Maximum (0x0D) |
| 37 | * 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) |
| 38 | * 0x95, 0x03, // Report Count (3) |
| 39 | * 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) |
| 40 | * 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) |
| 41 | * 0x25, 0x07, // Logical Maximum (7) |
| 42 | * 0x46, 0x3B, 0x01, // Physical Maximum (315) |
| 43 | * 0x75, 0x04, // Report Size (4) |
| 44 | * 0x95, 0x01, // Report Count (1) |
| 45 | * 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter) |
| 46 | * 0x09, 0x39, // Usage (Hat switch) |
| 47 | * 0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) |
| 48 | * 0x65, 0x00, // Unit (None) |
| 49 | * 0x95, 0x01, // Report Count (1) |
| 50 | * 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) |
| 51 | * 0x26, 0xFF, 0x00, // Logical Maximum (255) |
| 52 | * 0x46, 0xFF, 0x00, // Physical Maximum (255) |
| 53 | * 0x09, 0x30, // Usage (X) |
| 54 | * 0x09, 0x31, // Usage (Y) |
| 55 | * 0x09, 0x32, // Usage (Z) |
| 56 | * 0x09, 0x35, // Usage (Rz) |
| 57 | * 0x75, 0x08, // Report Size (8) |
| 58 | * 0x95, 0x04, // Report Count (4) |
| 59 | * 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) |
| 60 | * 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) |
| 61 | * 0x09, 0x20, // Usage (0x20) |
| 62 | * 0x09, 0x21, // Usage (0x21) |
| 63 | * 0x09, 0x22, // Usage (0x22) |
| 64 | * 0x09, 0x23, // Usage (0x23) |
| 65 | * 0x09, 0x24, // Usage (0x24) |
| 66 | * 0x09, 0x25, // Usage (0x25) |
| 67 | * 0x09, 0x26, // Usage (0x26) |
| 68 | * 0x09, 0x27, // Usage (0x27) |
| 69 | * 0x09, 0x28, // Usage (0x28) |
| 70 | * 0x09, 0x29, // Usage (0x29) |
| 71 | * 0x09, 0x2A, // Usage (0x2A) |
| 72 | * 0x09, 0x2B, // Usage (0x2B) |
| 73 | * 0x95, 0x0C, // Report Count (12) |
| 74 | * 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) |
| 75 | * 0x0A, 0x21, 0x26, // Usage (0x2621) |
| 76 | * 0x95, 0x08, // Report Count (8) |
| 77 | * 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) |
| 78 | * 0x0A, 0x21, 0x26, // Usage (0x2621) |
| 79 | * 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) |
| 80 | * 0x26, 0xFF, 0x03, // Logical Maximum (1023) |
| 81 | * 0x46, 0xFF, 0x03, // Physical Maximum (1023) |
| 82 | * 0x09, 0x2C, // Usage (0x2C) |
| 83 | * 0x09, 0x2D, // Usage (0x2D) |
| 84 | * 0x09, 0x2E, // Usage (0x2E) |
| 85 | * 0x09, 0x2F, // Usage (0x2F) |
| 86 | * 0x75, 0x10, // Report Size (16) |
| 87 | * 0x95, 0x04, // Report Count (4) |
| 88 | * 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) |
| 89 | * 0xC0, // End Collection |
| 90 | */ |
| 91 | |
| 92 | #define PID0902_RDESC_ORIG_SIZE 137 |
| 93 | |
| 94 | /* |
| 95 | * The fixed descriptor for 0x146b:0x0902 |
| 96 | * |
| 97 | * - map buttons according to gamepad.rst |
| 98 | * - assign right stick from Z/Rz to Rx/Ry |
| 99 | * - map previously unused analog trigger data to Z/RZ |
| 100 | * - simplify feature and output descriptor |
| 101 | */ |
| 102 | static __u8 pid0902_rdesc_fixed[] = { |
| 103 | 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ |
| 104 | 0x09, 0x05, /* Usage (Game Pad) */ |
| 105 | 0xA1, 0x01, /* Collection (Application) */ |
| 106 | 0x15, 0x00, /* Logical Minimum (0) */ |
| 107 | 0x25, 0x01, /* Logical Maximum (1) */ |
| 108 | 0x35, 0x00, /* Physical Minimum (0) */ |
| 109 | 0x45, 0x01, /* Physical Maximum (1) */ |
| 110 | 0x75, 0x01, /* Report Size (1) */ |
| 111 | 0x95, 0x0D, /* Report Count (13) */ |
| 112 | 0x05, 0x09, /* Usage Page (Button) */ |
| 113 | 0x09, 0x05, /* Usage (BTN_WEST) */ |
| 114 | 0x09, 0x01, /* Usage (BTN_SOUTH) */ |
| 115 | 0x09, 0x02, /* Usage (BTN_EAST) */ |
| 116 | 0x09, 0x04, /* Usage (BTN_NORTH) */ |
| 117 | 0x09, 0x07, /* Usage (BTN_TL) */ |
| 118 | 0x09, 0x08, /* Usage (BTN_TR) */ |
| 119 | 0x09, 0x09, /* Usage (BTN_TL2) */ |
| 120 | 0x09, 0x0A, /* Usage (BTN_TR2) */ |
| 121 | 0x09, 0x0B, /* Usage (BTN_SELECT) */ |
| 122 | 0x09, 0x0C, /* Usage (BTN_START) */ |
| 123 | 0x09, 0x0E, /* Usage (BTN_THUMBL) */ |
| 124 | 0x09, 0x0F, /* Usage (BTN_THUMBR) */ |
| 125 | 0x09, 0x0D, /* Usage (BTN_MODE) */ |
| 126 | 0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ |
| 127 | 0x75, 0x01, /* Report Size (1) */ |
| 128 | 0x95, 0x03, /* Report Count (3) */ |
| 129 | 0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ |
| 130 | 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ |
| 131 | 0x25, 0x07, /* Logical Maximum (7) */ |
| 132 | 0x46, 0x3B, 0x01, /* Physical Maximum (315) */ |
| 133 | 0x75, 0x04, /* Report Size (4) */ |
| 134 | 0x95, 0x01, /* Report Count (1) */ |
| 135 | 0x65, 0x14, /* Unit (System: English Rotation, Length: Centimeter) */ |
| 136 | 0x09, 0x39, /* Usage (Hat switch) */ |
| 137 | 0x81, 0x42, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) */ |
| 138 | 0x65, 0x00, /* Unit (None) */ |
| 139 | 0x95, 0x01, /* Report Count (1) */ |
| 140 | 0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ |
| 141 | 0x26, 0xFF, 0x00, /* Logical Maximum (255) */ |
| 142 | 0x46, 0xFF, 0x00, /* Physical Maximum (255) */ |
| 143 | 0x09, 0x30, /* Usage (X) */ |
| 144 | 0x09, 0x31, /* Usage (Y) */ |
| 145 | 0x09, 0x33, /* Usage (Rx) */ |
| 146 | 0x09, 0x34, /* Usage (Ry) */ |
| 147 | 0x75, 0x08, /* Report Size (8) */ |
| 148 | 0x95, 0x04, /* Report Count (4) */ |
| 149 | 0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ |
| 150 | 0x95, 0x0A, /* Report Count (10) */ |
| 151 | 0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ |
| 152 | 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ |
| 153 | 0x26, 0xFF, 0x00, /* Logical Maximum (255) */ |
| 154 | 0x46, 0xFF, 0x00, /* Physical Maximum (255) */ |
| 155 | 0x09, 0x32, /* Usage (Z) */ |
| 156 | 0x09, 0x35, /* Usage (Rz) */ |
| 157 | 0x95, 0x02, /* Report Count (2) */ |
| 158 | 0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ |
| 159 | 0x95, 0x08, /* Report Count (8) */ |
| 160 | 0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ |
| 161 | 0x06, 0x00, 0xFF, /* Usage Page (Vendor Defined 0xFF00) */ |
| 162 | 0xB1, 0x02, /* Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) */ |
| 163 | 0x0A, 0x21, 0x26, /* Usage (0x2621) */ |
| 164 | 0x95, 0x08, /* Report Count (8) */ |
| 165 | 0x91, 0x02, /* Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) */ |
| 166 | 0x0A, 0x21, 0x26, /* Usage (0x2621) */ |
| 167 | 0x95, 0x08, /* Report Count (8) */ |
| 168 | 0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ |
| 169 | 0xC0, /* End Collection */ |
| 170 | }; |
| 171 | |
| 172 | #define NUM_LEDS 4 |
| 173 | |
| 174 | struct bigben_device { |
| 175 | struct hid_device *hid; |
| 176 | struct hid_report *report; |
Hanno Zulla | 4eb1b01 | 2020-02-18 12:39:31 +0100 | [diff] [blame] | 177 | bool removed; |
Hanno Zulla | 256a90e | 2018-08-23 17:03:38 +0200 | [diff] [blame] | 178 | u8 led_state; /* LED1 = 1 .. LED4 = 8 */ |
| 179 | u8 right_motor_on; /* right motor off/on 0/1 */ |
| 180 | u8 left_motor_force; /* left motor force 0-255 */ |
| 181 | struct led_classdev *leds[NUM_LEDS]; |
| 182 | bool work_led; |
| 183 | bool work_ff; |
| 184 | struct work_struct worker; |
| 185 | }; |
| 186 | |
| 187 | |
| 188 | static void bigben_worker(struct work_struct *work) |
| 189 | { |
| 190 | struct bigben_device *bigben = container_of(work, |
| 191 | struct bigben_device, worker); |
| 192 | struct hid_field *report_field = bigben->report->field[0]; |
| 193 | |
Hanno Zulla | 4eb1b01 | 2020-02-18 12:39:31 +0100 | [diff] [blame] | 194 | if (bigben->removed) |
| 195 | return; |
| 196 | |
Hanno Zulla | 256a90e | 2018-08-23 17:03:38 +0200 | [diff] [blame] | 197 | if (bigben->work_led) { |
| 198 | bigben->work_led = false; |
| 199 | report_field->value[0] = 0x01; /* 1 = led message */ |
| 200 | report_field->value[1] = 0x08; /* reserved value, always 8 */ |
| 201 | report_field->value[2] = bigben->led_state; |
| 202 | report_field->value[3] = 0x00; /* padding */ |
| 203 | report_field->value[4] = 0x00; /* padding */ |
| 204 | report_field->value[5] = 0x00; /* padding */ |
| 205 | report_field->value[6] = 0x00; /* padding */ |
| 206 | report_field->value[7] = 0x00; /* padding */ |
| 207 | hid_hw_request(bigben->hid, bigben->report, HID_REQ_SET_REPORT); |
| 208 | } |
| 209 | |
| 210 | if (bigben->work_ff) { |
| 211 | bigben->work_ff = false; |
| 212 | report_field->value[0] = 0x02; /* 2 = rumble effect message */ |
| 213 | report_field->value[1] = 0x08; /* reserved value, always 8 */ |
| 214 | report_field->value[2] = bigben->right_motor_on; |
| 215 | report_field->value[3] = bigben->left_motor_force; |
| 216 | report_field->value[4] = 0xff; /* duration 0-254 (255 = nonstop) */ |
| 217 | report_field->value[5] = 0x00; /* padding */ |
| 218 | report_field->value[6] = 0x00; /* padding */ |
| 219 | report_field->value[7] = 0x00; /* padding */ |
| 220 | hid_hw_request(bigben->hid, bigben->report, HID_REQ_SET_REPORT); |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | static int hid_bigben_play_effect(struct input_dev *dev, void *data, |
| 225 | struct ff_effect *effect) |
| 226 | { |
Hanno Zulla | 789a2c2 | 2020-02-18 12:37:47 +0100 | [diff] [blame] | 227 | struct hid_device *hid = input_get_drvdata(dev); |
| 228 | struct bigben_device *bigben = hid_get_drvdata(hid); |
Hanno Zulla | 256a90e | 2018-08-23 17:03:38 +0200 | [diff] [blame] | 229 | u8 right_motor_on; |
| 230 | u8 left_motor_force; |
| 231 | |
Hanno Zulla | 789a2c2 | 2020-02-18 12:37:47 +0100 | [diff] [blame] | 232 | if (!bigben) { |
| 233 | hid_err(hid, "no device data\n"); |
| 234 | return 0; |
| 235 | } |
| 236 | |
Hanno Zulla | 256a90e | 2018-08-23 17:03:38 +0200 | [diff] [blame] | 237 | if (effect->type != FF_RUMBLE) |
| 238 | return 0; |
| 239 | |
| 240 | right_motor_on = effect->u.rumble.weak_magnitude ? 1 : 0; |
| 241 | left_motor_force = effect->u.rumble.strong_magnitude / 256; |
| 242 | |
| 243 | if (right_motor_on != bigben->right_motor_on || |
| 244 | left_motor_force != bigben->left_motor_force) { |
| 245 | bigben->right_motor_on = right_motor_on; |
| 246 | bigben->left_motor_force = left_motor_force; |
| 247 | bigben->work_ff = true; |
| 248 | schedule_work(&bigben->worker); |
| 249 | } |
| 250 | |
| 251 | return 0; |
| 252 | } |
| 253 | |
| 254 | static void bigben_set_led(struct led_classdev *led, |
| 255 | enum led_brightness value) |
| 256 | { |
| 257 | struct device *dev = led->dev->parent; |
| 258 | struct hid_device *hid = to_hid_device(dev); |
| 259 | struct bigben_device *bigben = hid_get_drvdata(hid); |
| 260 | int n; |
| 261 | bool work; |
| 262 | |
| 263 | if (!bigben) { |
| 264 | hid_err(hid, "no device data\n"); |
| 265 | return; |
| 266 | } |
| 267 | |
| 268 | for (n = 0; n < NUM_LEDS; n++) { |
| 269 | if (led == bigben->leds[n]) { |
| 270 | if (value == LED_OFF) { |
| 271 | work = (bigben->led_state & BIT(n)); |
| 272 | bigben->led_state &= ~BIT(n); |
| 273 | } else { |
| 274 | work = !(bigben->led_state & BIT(n)); |
| 275 | bigben->led_state |= BIT(n); |
| 276 | } |
| 277 | |
| 278 | if (work) { |
| 279 | bigben->work_led = true; |
| 280 | schedule_work(&bigben->worker); |
| 281 | } |
| 282 | return; |
| 283 | } |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | static enum led_brightness bigben_get_led(struct led_classdev *led) |
| 288 | { |
| 289 | struct device *dev = led->dev->parent; |
| 290 | struct hid_device *hid = to_hid_device(dev); |
| 291 | struct bigben_device *bigben = hid_get_drvdata(hid); |
| 292 | int n; |
| 293 | |
| 294 | if (!bigben) { |
| 295 | hid_err(hid, "no device data\n"); |
| 296 | return LED_OFF; |
| 297 | } |
| 298 | |
| 299 | for (n = 0; n < NUM_LEDS; n++) { |
| 300 | if (led == bigben->leds[n]) |
| 301 | return (bigben->led_state & BIT(n)) ? LED_ON : LED_OFF; |
| 302 | } |
| 303 | |
| 304 | return LED_OFF; |
| 305 | } |
| 306 | |
| 307 | static void bigben_remove(struct hid_device *hid) |
| 308 | { |
| 309 | struct bigben_device *bigben = hid_get_drvdata(hid); |
| 310 | |
Hanno Zulla | 4eb1b01 | 2020-02-18 12:39:31 +0100 | [diff] [blame] | 311 | bigben->removed = true; |
Hanno Zulla | 256a90e | 2018-08-23 17:03:38 +0200 | [diff] [blame] | 312 | cancel_work_sync(&bigben->worker); |
Hanno Zulla | 256a90e | 2018-08-23 17:03:38 +0200 | [diff] [blame] | 313 | hid_hw_stop(hid); |
| 314 | } |
| 315 | |
| 316 | static int bigben_probe(struct hid_device *hid, |
| 317 | const struct hid_device_id *id) |
| 318 | { |
| 319 | struct bigben_device *bigben; |
| 320 | struct hid_input *hidinput; |
| 321 | struct list_head *report_list; |
| 322 | struct led_classdev *led; |
| 323 | char *name; |
| 324 | size_t name_sz; |
| 325 | int n, error; |
| 326 | |
| 327 | bigben = devm_kzalloc(&hid->dev, sizeof(*bigben), GFP_KERNEL); |
| 328 | if (!bigben) |
| 329 | return -ENOMEM; |
| 330 | hid_set_drvdata(hid, bigben); |
| 331 | bigben->hid = hid; |
Hanno Zulla | 4eb1b01 | 2020-02-18 12:39:31 +0100 | [diff] [blame] | 332 | bigben->removed = false; |
Hanno Zulla | 256a90e | 2018-08-23 17:03:38 +0200 | [diff] [blame] | 333 | |
| 334 | error = hid_parse(hid); |
| 335 | if (error) { |
| 336 | hid_err(hid, "parse failed\n"); |
| 337 | return error; |
| 338 | } |
| 339 | |
| 340 | error = hid_hw_start(hid, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); |
| 341 | if (error) { |
| 342 | hid_err(hid, "hw start failed\n"); |
| 343 | return error; |
| 344 | } |
| 345 | |
| 346 | report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; |
| 347 | bigben->report = list_entry(report_list->next, |
| 348 | struct hid_report, list); |
| 349 | |
| 350 | hidinput = list_first_entry(&hid->inputs, struct hid_input, list); |
| 351 | set_bit(FF_RUMBLE, hidinput->input->ffbit); |
| 352 | |
| 353 | INIT_WORK(&bigben->worker, bigben_worker); |
| 354 | |
Hanno Zulla | 789a2c2 | 2020-02-18 12:37:47 +0100 | [diff] [blame] | 355 | error = input_ff_create_memless(hidinput->input, NULL, |
Hanno Zulla | 256a90e | 2018-08-23 17:03:38 +0200 | [diff] [blame] | 356 | hid_bigben_play_effect); |
| 357 | if (error) |
Hanno Zulla | 976a54d | 2020-02-18 12:38:34 +0100 | [diff] [blame] | 358 | goto error_hw_stop; |
Hanno Zulla | 256a90e | 2018-08-23 17:03:38 +0200 | [diff] [blame] | 359 | |
| 360 | name_sz = strlen(dev_name(&hid->dev)) + strlen(":red:bigben#") + 1; |
| 361 | |
| 362 | for (n = 0; n < NUM_LEDS; n++) { |
| 363 | led = devm_kzalloc( |
| 364 | &hid->dev, |
| 365 | sizeof(struct led_classdev) + name_sz, |
| 366 | GFP_KERNEL |
| 367 | ); |
Hanno Zulla | 976a54d | 2020-02-18 12:38:34 +0100 | [diff] [blame] | 368 | if (!led) { |
| 369 | error = -ENOMEM; |
| 370 | goto error_hw_stop; |
| 371 | } |
Hanno Zulla | 256a90e | 2018-08-23 17:03:38 +0200 | [diff] [blame] | 372 | name = (void *)(&led[1]); |
| 373 | snprintf(name, name_sz, |
| 374 | "%s:red:bigben%d", |
| 375 | dev_name(&hid->dev), n + 1 |
| 376 | ); |
| 377 | led->name = name; |
| 378 | led->brightness = (n == 0) ? LED_ON : LED_OFF; |
| 379 | led->max_brightness = 1; |
| 380 | led->brightness_get = bigben_get_led; |
| 381 | led->brightness_set = bigben_set_led; |
| 382 | bigben->leds[n] = led; |
| 383 | error = devm_led_classdev_register(&hid->dev, led); |
| 384 | if (error) |
Hanno Zulla | 976a54d | 2020-02-18 12:38:34 +0100 | [diff] [blame] | 385 | goto error_hw_stop; |
Hanno Zulla | 256a90e | 2018-08-23 17:03:38 +0200 | [diff] [blame] | 386 | } |
| 387 | |
| 388 | /* initial state: LED1 is on, no rumble effect */ |
| 389 | bigben->led_state = BIT(0); |
| 390 | bigben->right_motor_on = 0; |
| 391 | bigben->left_motor_force = 0; |
| 392 | bigben->work_led = true; |
| 393 | bigben->work_ff = true; |
| 394 | schedule_work(&bigben->worker); |
| 395 | |
| 396 | hid_info(hid, "LED and force feedback support for BigBen gamepad\n"); |
| 397 | |
| 398 | return 0; |
Hanno Zulla | 976a54d | 2020-02-18 12:38:34 +0100 | [diff] [blame] | 399 | |
| 400 | error_hw_stop: |
| 401 | hid_hw_stop(hid); |
| 402 | return error; |
Hanno Zulla | 256a90e | 2018-08-23 17:03:38 +0200 | [diff] [blame] | 403 | } |
| 404 | |
| 405 | static __u8 *bigben_report_fixup(struct hid_device *hid, __u8 *rdesc, |
| 406 | unsigned int *rsize) |
| 407 | { |
| 408 | if (*rsize == PID0902_RDESC_ORIG_SIZE) { |
| 409 | rdesc = pid0902_rdesc_fixed; |
| 410 | *rsize = sizeof(pid0902_rdesc_fixed); |
| 411 | } else |
| 412 | hid_warn(hid, "unexpected rdesc, please submit for review\n"); |
| 413 | return rdesc; |
| 414 | } |
| 415 | |
| 416 | static const struct hid_device_id bigben_devices[] = { |
| 417 | { HID_USB_DEVICE(USB_VENDOR_ID_BIGBEN, USB_DEVICE_ID_BIGBEN_PS3OFMINIPAD) }, |
| 418 | { } |
| 419 | }; |
| 420 | MODULE_DEVICE_TABLE(hid, bigben_devices); |
| 421 | |
| 422 | static struct hid_driver bigben_driver = { |
| 423 | .name = "bigben", |
| 424 | .id_table = bigben_devices, |
| 425 | .probe = bigben_probe, |
| 426 | .report_fixup = bigben_report_fixup, |
| 427 | .remove = bigben_remove, |
| 428 | }; |
| 429 | module_hid_driver(bigben_driver); |
| 430 | |
| 431 | MODULE_LICENSE("GPL"); |