1 /* 2 * Force feedback support for Logitech Gaming Wheels 3 * 4 * Including G27, G25, DFP, DFGT, FFEX, Momo, Momo2 & 5 * Speed Force Wireless (WiiWheel) 6 * 7 * Copyright (c) 2010 Simon Wood <simon@mungewell.org> 8 */ 9 10 /* 11 * This program is free software; you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License as published by 13 * the Free Software Foundation; either version 2 of the License, or 14 * (at your option) any later version. 15 * 16 * This program is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU General Public License for more details. 20 * 21 * You should have received a copy of the GNU General Public License 22 * along with this program; if not, write to the Free Software 23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 24 */ 25 26 27 #include <linux/input.h> 28 #include <linux/usb.h> 29 #include <linux/hid.h> 30 31 #include "usbhid/usbhid.h" 32 #include "hid-lg.h" 33 #include "hid-ids.h" 34 35 #define DFGT_REV_MAJ 0x13 36 #define DFGT_REV_MIN 0x22 37 #define DFP_REV_MAJ 0x11 38 #define DFP_REV_MIN 0x06 39 #define FFEX_REV_MAJ 0x21 40 #define FFEX_REV_MIN 0x00 41 #define G25_REV_MAJ 0x12 42 #define G25_REV_MIN 0x22 43 #define G27_REV_MAJ 0x12 44 #define G27_REV_MIN 0x38 45 46 #define to_hid_device(pdev) container_of(pdev, struct hid_device, dev) 47 48 static void hid_lg4ff_set_range_dfp(struct hid_device *hid, u16 range); 49 static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range); 50 static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf); 51 static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); 52 53 static DEVICE_ATTR(range, S_IRWXU | S_IRWXG | S_IRWXO, lg4ff_range_show, lg4ff_range_store); 54 55 struct lg4ff_device_entry { 56 __u16 range; 57 __u16 min_range; 58 __u16 max_range; 59 #ifdef CONFIG_LEDS_CLASS 60 __u8 led_state; 61 struct led_classdev *led[5]; 62 #endif 63 struct list_head list; 64 void (*set_range)(struct hid_device *hid, u16 range); 65 }; 66 67 static const signed short lg4ff_wheel_effects[] = { 68 FF_CONSTANT, 69 FF_AUTOCENTER, 70 -1 71 }; 72 73 struct lg4ff_wheel { 74 const __u32 product_id; 75 const signed short *ff_effects; 76 const __u16 min_range; 77 const __u16 max_range; 78 void (*set_range)(struct hid_device *hid, u16 range); 79 }; 80 81 static const struct lg4ff_wheel lg4ff_devices[] = { 82 {USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}, 83 {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}, 84 {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_dfp}, 85 {USB_DEVICE_ID_LOGITECH_G25_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25}, 86 {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25}, 87 {USB_DEVICE_ID_LOGITECH_G27_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25}, 88 {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, lg4ff_wheel_effects, 40, 270, NULL}, 89 {USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270, NULL} 90 }; 91 92 struct lg4ff_native_cmd { 93 const __u8 cmd_num; /* Number of commands to send */ 94 const __u8 cmd[]; 95 }; 96 97 struct lg4ff_usb_revision { 98 const __u16 rev_maj; 99 const __u16 rev_min; 100 const struct lg4ff_native_cmd *command; 101 }; 102 103 static const struct lg4ff_native_cmd native_dfp = { 104 1, 105 {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00} 106 }; 107 108 static const struct lg4ff_native_cmd native_dfgt = { 109 2, 110 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */ 111 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* 2nd command */ 112 }; 113 114 static const struct lg4ff_native_cmd native_g25 = { 115 1, 116 {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00} 117 }; 118 119 static const struct lg4ff_native_cmd native_g27 = { 120 2, 121 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */ 122 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* 2nd command */ 123 }; 124 125 static const struct lg4ff_usb_revision lg4ff_revs[] = { 126 {DFGT_REV_MAJ, DFGT_REV_MIN, &native_dfgt}, /* Driving Force GT */ 127 {DFP_REV_MAJ, DFP_REV_MIN, &native_dfp}, /* Driving Force Pro */ 128 {G25_REV_MAJ, G25_REV_MIN, &native_g25}, /* G25 */ 129 {G27_REV_MAJ, G27_REV_MIN, &native_g27}, /* G27 */ 130 }; 131 132 static int hid_lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect) 133 { 134 struct hid_device *hid = input_get_drvdata(dev); 135 struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; 136 struct hid_report *report = list_entry(report_list->next, struct hid_report, list); 137 int x; 138 139 #define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff 140 141 switch (effect->type) { 142 case FF_CONSTANT: 143 x = effect->u.ramp.start_level + 0x80; /* 0x80 is no force */ 144 CLAMP(x); 145 report->field[0]->value[0] = 0x11; /* Slot 1 */ 146 report->field[0]->value[1] = 0x08; 147 report->field[0]->value[2] = x; 148 report->field[0]->value[3] = 0x80; 149 report->field[0]->value[4] = 0x00; 150 report->field[0]->value[5] = 0x00; 151 report->field[0]->value[6] = 0x00; 152 153 usbhid_submit_report(hid, report, USB_DIR_OUT); 154 break; 155 } 156 return 0; 157 } 158 159 /* Sends default autocentering command compatible with 160 * all wheels except Formula Force EX */ 161 static void hid_lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude) 162 { 163 struct hid_device *hid = input_get_drvdata(dev); 164 struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; 165 struct hid_report *report = list_entry(report_list->next, struct hid_report, list); 166 167 report->field[0]->value[0] = 0xfe; 168 report->field[0]->value[1] = 0x0d; 169 report->field[0]->value[2] = magnitude >> 13; 170 report->field[0]->value[3] = magnitude >> 13; 171 report->field[0]->value[4] = magnitude >> 8; 172 report->field[0]->value[5] = 0x00; 173 report->field[0]->value[6] = 0x00; 174 175 usbhid_submit_report(hid, report, USB_DIR_OUT); 176 } 177 178 /* Sends autocentering command compatible with Formula Force EX */ 179 static void hid_lg4ff_set_autocenter_ffex(struct input_dev *dev, u16 magnitude) 180 { 181 struct hid_device *hid = input_get_drvdata(dev); 182 struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; 183 struct hid_report *report = list_entry(report_list->next, struct hid_report, list); 184 magnitude = magnitude * 90 / 65535; 185 186 187 report->field[0]->value[0] = 0xfe; 188 report->field[0]->value[1] = 0x03; 189 report->field[0]->value[2] = magnitude >> 14; 190 report->field[0]->value[3] = magnitude >> 14; 191 report->field[0]->value[4] = magnitude; 192 report->field[0]->value[5] = 0x00; 193 report->field[0]->value[6] = 0x00; 194 195 usbhid_submit_report(hid, report, USB_DIR_OUT); 196 } 197 198 /* Sends command to set range compatible with G25/G27/Driving Force GT */ 199 static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range) 200 { 201 struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; 202 struct hid_report *report = list_entry(report_list->next, struct hid_report, list); 203 dbg_hid("G25/G27/DFGT: setting range to %u\n", range); 204 205 report->field[0]->value[0] = 0xf8; 206 report->field[0]->value[1] = 0x81; 207 report->field[0]->value[2] = range & 0x00ff; 208 report->field[0]->value[3] = (range & 0xff00) >> 8; 209 report->field[0]->value[4] = 0x00; 210 report->field[0]->value[5] = 0x00; 211 report->field[0]->value[6] = 0x00; 212 213 usbhid_submit_report(hid, report, USB_DIR_OUT); 214 } 215 216 /* Sends commands to set range compatible with Driving Force Pro wheel */ 217 static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range) 218 { 219 struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; 220 struct hid_report *report = list_entry(report_list->next, struct hid_report, list); 221 int start_left, start_right, full_range; 222 dbg_hid("Driving Force Pro: setting range to %u\n", range); 223 224 /* Prepare "coarse" limit command */ 225 report->field[0]->value[0] = 0xf8; 226 report->field[0]->value[1] = 0x00; /* Set later */ 227 report->field[0]->value[2] = 0x00; 228 report->field[0]->value[3] = 0x00; 229 report->field[0]->value[4] = 0x00; 230 report->field[0]->value[5] = 0x00; 231 report->field[0]->value[6] = 0x00; 232 233 if (range > 200) { 234 report->field[0]->value[1] = 0x03; 235 full_range = 900; 236 } else { 237 report->field[0]->value[1] = 0x02; 238 full_range = 200; 239 } 240 usbhid_submit_report(hid, report, USB_DIR_OUT); 241 242 /* Prepare "fine" limit command */ 243 report->field[0]->value[0] = 0x81; 244 report->field[0]->value[1] = 0x0b; 245 report->field[0]->value[2] = 0x00; 246 report->field[0]->value[3] = 0x00; 247 report->field[0]->value[4] = 0x00; 248 report->field[0]->value[5] = 0x00; 249 report->field[0]->value[6] = 0x00; 250 251 if (range == 200 || range == 900) { /* Do not apply any fine limit */ 252 usbhid_submit_report(hid, report, USB_DIR_OUT); 253 return; 254 } 255 256 /* Construct fine limit command */ 257 start_left = (((full_range - range + 1) * 2047) / full_range); 258 start_right = 0xfff - start_left; 259 260 report->field[0]->value[2] = start_left >> 4; 261 report->field[0]->value[3] = start_right >> 4; 262 report->field[0]->value[4] = 0xff; 263 report->field[0]->value[5] = (start_right & 0xe) << 4 | (start_left & 0xe); 264 report->field[0]->value[6] = 0xff; 265 266 usbhid_submit_report(hid, report, USB_DIR_OUT); 267 } 268 269 static void hid_lg4ff_switch_native(struct hid_device *hid, const struct lg4ff_native_cmd *cmd) 270 { 271 struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; 272 struct hid_report *report = list_entry(report_list->next, struct hid_report, list); 273 __u8 i, j; 274 275 j = 0; 276 while (j < 7*cmd->cmd_num) { 277 for (i = 0; i < 7; i++) 278 report->field[0]->value[i] = cmd->cmd[j++]; 279 280 usbhid_submit_report(hid, report, USB_DIR_OUT); 281 } 282 } 283 284 /* Read current range and display it in terminal */ 285 static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf) 286 { 287 struct hid_device *hid = to_hid_device(dev); 288 struct lg4ff_device_entry *entry; 289 struct lg_drv_data *drv_data; 290 size_t count; 291 292 drv_data = hid_get_drvdata(hid); 293 if (!drv_data) { 294 hid_err(hid, "Private driver data not found!\n"); 295 return 0; 296 } 297 298 entry = drv_data->device_props; 299 if (!entry) { 300 hid_err(hid, "Device properties not found!\n"); 301 return 0; 302 } 303 304 count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->range); 305 return count; 306 } 307 308 /* Set range to user specified value, call appropriate function 309 * according to the type of the wheel */ 310 static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) 311 { 312 struct hid_device *hid = to_hid_device(dev); 313 struct lg4ff_device_entry *entry; 314 struct lg_drv_data *drv_data; 315 __u16 range = simple_strtoul(buf, NULL, 10); 316 317 drv_data = hid_get_drvdata(hid); 318 if (!drv_data) { 319 hid_err(hid, "Private driver data not found!\n"); 320 return 0; 321 } 322 323 entry = drv_data->device_props; 324 if (!entry) { 325 hid_err(hid, "Device properties not found!\n"); 326 return 0; 327 } 328 329 if (range == 0) 330 range = entry->max_range; 331 332 /* Check if the wheel supports range setting 333 * and that the range is within limits for the wheel */ 334 if (entry->set_range != NULL && range >= entry->min_range && range <= entry->max_range) { 335 entry->set_range(hid, range); 336 entry->range = range; 337 } 338 339 return count; 340 } 341 342 #ifdef CONFIG_LEDS_CLASS 343 static void lg4ff_set_leds(struct hid_device *hid, __u8 leds) 344 { 345 struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; 346 struct hid_report *report = list_entry(report_list->next, struct hid_report, list); 347 348 report->field[0]->value[0] = 0xf8; 349 report->field[0]->value[1] = 0x12; 350 report->field[0]->value[2] = leds; 351 report->field[0]->value[3] = 0x00; 352 report->field[0]->value[4] = 0x00; 353 report->field[0]->value[5] = 0x00; 354 report->field[0]->value[6] = 0x00; 355 usbhid_submit_report(hid, report, USB_DIR_OUT); 356 } 357 358 static void lg4ff_led_set_brightness(struct led_classdev *led_cdev, 359 enum led_brightness value) 360 { 361 struct device *dev = led_cdev->dev->parent; 362 struct hid_device *hid = container_of(dev, struct hid_device, dev); 363 struct lg_drv_data *drv_data = (struct lg_drv_data *)hid_get_drvdata(hid); 364 struct lg4ff_device_entry *entry; 365 int i, state = 0; 366 367 if (!drv_data) { 368 hid_err(hid, "Device data not found."); 369 return; 370 } 371 372 entry = (struct lg4ff_device_entry *)drv_data->device_props; 373 374 if (!entry) { 375 hid_err(hid, "Device properties not found."); 376 return; 377 } 378 379 for (i = 0; i < 5; i++) { 380 if (led_cdev != entry->led[i]) 381 continue; 382 state = (entry->led_state >> i) & 1; 383 if (value == LED_OFF && state) { 384 entry->led_state &= ~(1 << i); 385 lg4ff_set_leds(hid, entry->led_state); 386 } else if (value != LED_OFF && !state) { 387 entry->led_state |= 1 << i; 388 lg4ff_set_leds(hid, entry->led_state); 389 } 390 break; 391 } 392 } 393 394 static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cdev) 395 { 396 struct device *dev = led_cdev->dev->parent; 397 struct hid_device *hid = container_of(dev, struct hid_device, dev); 398 struct lg_drv_data *drv_data = (struct lg_drv_data *)hid_get_drvdata(hid); 399 struct lg4ff_device_entry *entry; 400 int i, value = 0; 401 402 if (!drv_data) { 403 hid_err(hid, "Device data not found."); 404 return LED_OFF; 405 } 406 407 entry = (struct lg4ff_device_entry *)drv_data->device_props; 408 409 if (!entry) { 410 hid_err(hid, "Device properties not found."); 411 return LED_OFF; 412 } 413 414 for (i = 0; i < 5; i++) 415 if (led_cdev == entry->led[i]) { 416 value = (entry->led_state >> i) & 1; 417 break; 418 } 419 420 return value ? LED_FULL : LED_OFF; 421 } 422 #endif 423 424 int lg4ff_init(struct hid_device *hid) 425 { 426 struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); 427 struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; 428 struct input_dev *dev = hidinput->input; 429 struct hid_report *report; 430 struct hid_field *field; 431 struct lg4ff_device_entry *entry; 432 struct lg_drv_data *drv_data; 433 struct usb_device_descriptor *udesc; 434 int error, i, j; 435 __u16 bcdDevice, rev_maj, rev_min; 436 437 /* Find the report to use */ 438 if (list_empty(report_list)) { 439 hid_err(hid, "No output report found\n"); 440 return -1; 441 } 442 443 /* Check that the report looks ok */ 444 report = list_entry(report_list->next, struct hid_report, list); 445 if (!report) { 446 hid_err(hid, "NULL output report\n"); 447 return -1; 448 } 449 450 field = report->field[0]; 451 if (!field) { 452 hid_err(hid, "NULL field\n"); 453 return -1; 454 } 455 456 /* Check what wheel has been connected */ 457 for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) { 458 if (hid->product == lg4ff_devices[i].product_id) { 459 dbg_hid("Found compatible device, product ID %04X\n", lg4ff_devices[i].product_id); 460 break; 461 } 462 } 463 464 if (i == ARRAY_SIZE(lg4ff_devices)) { 465 hid_err(hid, "Device is not supported by lg4ff driver. If you think it should be, consider reporting a bug to" 466 "LKML, Simon Wood <simon@mungewell.org> or Michal Maly <madcatxster@gmail.com>\n"); 467 return -1; 468 } 469 470 /* Attempt to switch wheel to native mode when applicable */ 471 udesc = &(hid_to_usb_dev(hid)->descriptor); 472 if (!udesc) { 473 hid_err(hid, "NULL USB device descriptor\n"); 474 return -1; 475 } 476 bcdDevice = le16_to_cpu(udesc->bcdDevice); 477 rev_maj = bcdDevice >> 8; 478 rev_min = bcdDevice & 0xff; 479 480 if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_WHEEL) { 481 dbg_hid("Generic wheel detected, can it do native?\n"); 482 dbg_hid("USB revision: %2x.%02x\n", rev_maj, rev_min); 483 484 for (j = 0; j < ARRAY_SIZE(lg4ff_revs); j++) { 485 if (lg4ff_revs[j].rev_maj == rev_maj && lg4ff_revs[j].rev_min == rev_min) { 486 hid_lg4ff_switch_native(hid, lg4ff_revs[j].command); 487 hid_info(hid, "Switched to native mode\n"); 488 } 489 } 490 } 491 492 /* Set supported force feedback capabilities */ 493 for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++) 494 set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit); 495 496 error = input_ff_create_memless(dev, NULL, hid_lg4ff_play); 497 498 if (error) 499 return error; 500 501 /* Check if autocentering is available and 502 * set the centering force to zero by default */ 503 if (test_bit(FF_AUTOCENTER, dev->ffbit)) { 504 if(rev_maj == FFEX_REV_MAJ && rev_min == FFEX_REV_MIN) /* Formula Force EX expects different autocentering command */ 505 dev->ff->set_autocenter = hid_lg4ff_set_autocenter_ffex; 506 else 507 dev->ff->set_autocenter = hid_lg4ff_set_autocenter_default; 508 509 dev->ff->set_autocenter(dev, 0); 510 } 511 512 /* Get private driver data */ 513 drv_data = hid_get_drvdata(hid); 514 if (!drv_data) { 515 hid_err(hid, "Cannot add device, private driver data not allocated\n"); 516 return -1; 517 } 518 519 /* Initialize device properties */ 520 entry = kzalloc(sizeof(struct lg4ff_device_entry), GFP_KERNEL); 521 if (!entry) { 522 hid_err(hid, "Cannot add device, insufficient memory to allocate device properties.\n"); 523 return -ENOMEM; 524 } 525 drv_data->device_props = entry; 526 527 entry->min_range = lg4ff_devices[i].min_range; 528 entry->max_range = lg4ff_devices[i].max_range; 529 entry->set_range = lg4ff_devices[i].set_range; 530 531 /* Create sysfs interface */ 532 error = device_create_file(&hid->dev, &dev_attr_range); 533 if (error) 534 return error; 535 dbg_hid("sysfs interface created\n"); 536 537 /* Set the maximum range to start with */ 538 entry->range = entry->max_range; 539 if (entry->set_range != NULL) 540 entry->set_range(hid, entry->range); 541 542 #ifdef CONFIG_LEDS_CLASS 543 /* register led subsystem - G27 only */ 544 entry->led_state = 0; 545 for (j = 0; j < 5; j++) 546 entry->led[j] = NULL; 547 548 if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL) { 549 struct led_classdev *led; 550 size_t name_sz; 551 char *name; 552 553 lg4ff_set_leds(hid, 0); 554 555 name_sz = strlen(dev_name(&hid->dev)) + 8; 556 557 for (j = 0; j < 5; j++) { 558 led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); 559 if (!led) { 560 hid_err(hid, "can't allocate memory for LED %d\n", j); 561 goto err; 562 } 563 564 name = (void *)(&led[1]); 565 snprintf(name, name_sz, "%s::RPM%d", dev_name(&hid->dev), j+1); 566 led->name = name; 567 led->brightness = 0; 568 led->max_brightness = 1; 569 led->brightness_get = lg4ff_led_get_brightness; 570 led->brightness_set = lg4ff_led_set_brightness; 571 572 entry->led[j] = led; 573 error = led_classdev_register(&hid->dev, led); 574 575 if (error) { 576 hid_err(hid, "failed to register LED %d. Aborting.\n", j); 577 err: 578 /* Deregister LEDs (if any) */ 579 for (j = 0; j < 5; j++) { 580 led = entry->led[j]; 581 entry->led[j] = NULL; 582 if (!led) 583 continue; 584 led_classdev_unregister(led); 585 kfree(led); 586 } 587 goto out; /* Let the driver continue without LEDs */ 588 } 589 } 590 } 591 out: 592 #endif 593 hid_info(hid, "Force feedback support for Logitech Gaming Wheels\n"); 594 return 0; 595 } 596 597 int lg4ff_deinit(struct hid_device *hid) 598 { 599 struct lg4ff_device_entry *entry; 600 struct lg_drv_data *drv_data; 601 602 device_remove_file(&hid->dev, &dev_attr_range); 603 604 drv_data = hid_get_drvdata(hid); 605 if (!drv_data) { 606 hid_err(hid, "Error while deinitializing device, no private driver data.\n"); 607 return -1; 608 } 609 entry = drv_data->device_props; 610 if (!entry) { 611 hid_err(hid, "Error while deinitializing device, no device properties data.\n"); 612 return -1; 613 } 614 615 #ifdef CONFIG_LEDS_CLASS 616 { 617 int j; 618 struct led_classdev *led; 619 620 /* Deregister LEDs (if any) */ 621 for (j = 0; j < 5; j++) { 622 623 led = entry->led[j]; 624 entry->led[j] = NULL; 625 if (!led) 626 continue; 627 led_classdev_unregister(led); 628 kfree(led); 629 } 630 } 631 #endif 632 633 /* Deallocate memory */ 634 kfree(entry); 635 636 dbg_hid("Device successfully unregistered\n"); 637 return 0; 638 } 639