linux/drivers/hid/hid-lg4ff.c
<<
>>
Prefs
   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
  48static void hid_lg4ff_set_range_dfp(struct hid_device *hid, u16 range);
  49static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range);
  50static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf);
  51static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
  52
  53static DEVICE_ATTR(range, S_IRWXU | S_IRWXG | S_IRWXO, lg4ff_range_show, lg4ff_range_store);
  54
  55struct 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
  67static const signed short lg4ff_wheel_effects[] = {
  68        FF_CONSTANT,
  69        FF_AUTOCENTER,
  70        -1
  71};
  72
  73struct 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
  81static 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
  92struct lg4ff_native_cmd {
  93        const __u8 cmd_num;     /* Number of commands to send */
  94        const __u8 cmd[];
  95};
  96
  97struct lg4ff_usb_revision {
  98        const __u16 rev_maj;
  99        const __u16 rev_min;
 100        const struct lg4ff_native_cmd *command;
 101};
 102
 103static const struct lg4ff_native_cmd native_dfp = {
 104        1,
 105        {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
 106};
 107
 108static 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
 114static const struct lg4ff_native_cmd native_g25 = {
 115        1,
 116        {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
 117};
 118
 119static 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
 125static 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
 132static 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 */
 161static 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 */
 179static 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 */
 199static 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 */
 217static 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
 269static 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 */
 285static 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 */
 310static 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
 343static 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
 358static 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
 394static 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
 424int 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);
 577err:
 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        }
 591out:
 592#endif
 593        hid_info(hid, "Force feedback support for Logitech Gaming Wheels\n");
 594        return 0;
 595}
 596
 597int 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
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.