in hid-lg4ff.c [1254:1446]
int lg4ff_init(struct hid_device *hid)
{
struct hid_input *hidinput;
struct input_dev *dev;
struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor);
const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice);
const struct lg4ff_multimode_wheel *mmode_wheel = NULL;
struct lg4ff_device_entry *entry;
struct lg_drv_data *drv_data;
int error, i, j;
int mmode_ret, mmode_idx = -1;
u16 real_product_id;
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
return -ENODEV;
}
hidinput = list_entry(hid->inputs.next, struct hid_input, list);
dev = hidinput->input;
/* Check that the report looks ok */
if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7))
return -1;
drv_data = hid_get_drvdata(hid);
if (!drv_data) {
hid_err(hid, "Cannot add device, private driver data not allocated\n");
return -1;
}
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry)
return -ENOMEM;
spin_lock_init(&entry->report_lock);
entry->report = report;
drv_data->device_props = entry;
/* Check if a multimode wheel has been connected and
* handle it appropriately */
mmode_ret = lg4ff_handle_multimode_wheel(hid, &real_product_id, bcdDevice);
/* Wheel has been told to switch to native mode. There is no point in going on
* with the initialization as the wheel will do a USB reset when it switches mode
*/
if (mmode_ret == LG4FF_MMODE_SWITCHED)
return 0;
else if (mmode_ret < 0) {
hid_err(hid, "Unable to switch device mode during initialization, errno %d\n", mmode_ret);
error = mmode_ret;
goto err_init;
}
/* Check what wheel has been connected */
for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) {
if (hid->product == lg4ff_devices[i].product_id) {
dbg_hid("Found compatible device, product ID %04X\n", lg4ff_devices[i].product_id);
break;
}
}
if (i == ARRAY_SIZE(lg4ff_devices)) {
hid_err(hid, "This device is flagged to be handled by the lg4ff module but this module does not know how to handle it. "
"Please report this as a bug to LKML, Simon Wood <simon@mungewell.org> or "
"Michal Maly <madcatxster@devoid-pointer.net>\n");
error = -1;
goto err_init;
}
if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
for (mmode_idx = 0; mmode_idx < ARRAY_SIZE(lg4ff_multimode_wheels); mmode_idx++) {
if (real_product_id == lg4ff_multimode_wheels[mmode_idx].product_id)
break;
}
if (mmode_idx == ARRAY_SIZE(lg4ff_multimode_wheels)) {
hid_err(hid, "Device product ID %X is not listed as a multimode wheel", real_product_id);
error = -1;
goto err_init;
}
}
/* Set supported force feedback capabilities */
for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++)
set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit);
error = input_ff_create_memless(dev, NULL, lg4ff_play);
if (error)
goto err_init;
/* Initialize device properties */
if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
BUG_ON(mmode_idx == -1);
mmode_wheel = &lg4ff_multimode_wheels[mmode_idx];
}
lg4ff_init_wheel_data(&entry->wdata, &lg4ff_devices[i], mmode_wheel, real_product_id);
/* Check if autocentering is available and
* set the centering force to zero by default */
if (test_bit(FF_AUTOCENTER, dev->ffbit)) {
/* Formula Force EX expects different autocentering command */
if ((bcdDevice >> 8) == LG4FF_FFEX_REV_MAJ &&
(bcdDevice & 0xff) == LG4FF_FFEX_REV_MIN)
dev->ff->set_autocenter = lg4ff_set_autocenter_ffex;
else
dev->ff->set_autocenter = lg4ff_set_autocenter_default;
dev->ff->set_autocenter(dev, 0);
}
/* Create sysfs interface */
error = device_create_file(&hid->dev, &dev_attr_combine_pedals);
if (error)
hid_warn(hid, "Unable to create sysfs interface for \"combine\", errno %d\n", error);
error = device_create_file(&hid->dev, &dev_attr_range);
if (error)
hid_warn(hid, "Unable to create sysfs interface for \"range\", errno %d\n", error);
if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
error = device_create_file(&hid->dev, &dev_attr_real_id);
if (error)
hid_warn(hid, "Unable to create sysfs interface for \"real_id\", errno %d\n", error);
error = device_create_file(&hid->dev, &dev_attr_alternate_modes);
if (error)
hid_warn(hid, "Unable to create sysfs interface for \"alternate_modes\", errno %d\n", error);
}
dbg_hid("sysfs interface created\n");
/* Set the maximum range to start with */
entry->wdata.range = entry->wdata.max_range;
if (entry->wdata.set_range)
entry->wdata.set_range(hid, entry->wdata.range);
#ifdef CONFIG_LEDS_CLASS
/* register led subsystem - G27/G29 only */
entry->wdata.led_state = 0;
for (j = 0; j < 5; j++)
entry->wdata.led[j] = NULL;
if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL ||
lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G29_WHEEL) {
struct led_classdev *led;
size_t name_sz;
char *name;
lg4ff_set_leds(hid, 0);
name_sz = strlen(dev_name(&hid->dev)) + 8;
for (j = 0; j < 5; j++) {
led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
if (!led) {
hid_err(hid, "can't allocate memory for LED %d\n", j);
goto err_leds;
}
name = (void *)(&led[1]);
snprintf(name, name_sz, "%s::RPM%d", dev_name(&hid->dev), j+1);
led->name = name;
led->brightness = 0;
led->max_brightness = 1;
led->brightness_get = lg4ff_led_get_brightness;
led->brightness_set = lg4ff_led_set_brightness;
entry->wdata.led[j] = led;
error = led_classdev_register(&hid->dev, led);
if (error) {
hid_err(hid, "failed to register LED %d. Aborting.\n", j);
err_leds:
/* Deregister LEDs (if any) */
for (j = 0; j < 5; j++) {
led = entry->wdata.led[j];
entry->wdata.led[j] = NULL;
if (!led)
continue;
led_classdev_unregister(led);
kfree(led);
}
goto out; /* Let the driver continue without LEDs */
}
}
}
out:
#endif
hid_info(hid, "Force feedback support for Logitech Gaming Wheels\n");
return 0;
err_init:
drv_data->device_props = NULL;
kfree(entry);
return error;
}