diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index f36ffc5..8fb91ef 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -31,6 +31,15 @@ #include "hid-lg.h" #include "hid-ids.h" +#define FFEX_REV_MAJ 0x21 +#define FFEX_REV_MIN 0x00 +#define DFP_REV_MAJ 0x11 +#define DFP_REV_MIN 0x06 +#define G25_REV_MAJ 0x12 +#define G25_REV_MIN 0x22 +#define G27_REV_MAJ 0x12 +#define G27_REV_MIN 0x38 + #define to_hid_device(pdev) container_of(pdev, struct hid_device, dev) struct lg4ff_native_cmd { @@ -38,8 +47,9 @@ struct lg4ff_native_cmd { const __u8 cmd[]; }; -static void (*hid_lg4ff_set_range)(struct hid_device *hid, u16 range); + static void hid_lg4ff_set_autocenter_default(struct input_dev *hid, u16 magnitude); +static void hid_lg4ff_set_autocenter_ffex(struct input_dev *hid, u16 magnitude); static void hid_lg4ff_switch_native(struct hid_device *hid, const struct lg4ff_native_cmd *cmd); static void hid_lg4ff_set_range_dfp(struct hid_device *hid, u16 range); static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range); @@ -53,8 +63,11 @@ static bool list_inited; struct lg4ff_device_entry { int *device_id; /* Use hid_device structure's address as the ID */ __u16 range; + __u16 min_range; + __u16 max_range; __u8 leds; struct list_head list; + void (*set_range)(struct hid_device *hid, u16 range); }; static struct lg4ff_device_entry device_list; @@ -68,24 +81,26 @@ static const signed short lg4ff_wheel_effects[] = { struct lg4ff_wheel { const __u32 product_id; const signed short *ff_effects; - void (*set_autocenter)(struct input_dev *dev, u16 magnitude); void (*set_range)(struct hid_device *hid, u16 range); + const __u16 min_range; + const __u16 max_range; }; -struct lg4ff_usb_revision { - const u16 rev_maj; - const u16 rev_min; - const struct lg4ff_native_cmd *command; +static const struct lg4ff_wheel lg4ff_devices[] = { + {USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 0, 40, 200}, + {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 0, 0, 0}, + {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, lg4ff_wheel_effects, hid_lg4ff_set_range_dfp, 40, 900}, + {USB_DEVICE_ID_LOGITECH_G25_WHEEL, lg4ff_wheel_effects, hid_lg4ff_set_range_g25, 40, 900}, + {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, lg4ff_wheel_effects, hid_lg4ff_set_range_g25, 40, 900}, + {USB_DEVICE_ID_LOGITECH_G27_WHEEL, lg4ff_wheel_effects, hid_lg4ff_set_range_g25, 40, 900}, + {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, lg4ff_wheel_effects, 0, 0, 0}, + {USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 0, 0, 0} }; -static const struct lg4ff_wheel lg4ff_devices[] = { - {USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, hid_lg4ff_set_autocenter_default, 0}, - {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, hid_lg4ff_set_autocenter_default, 0}, - {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, lg4ff_wheel_effects, hid_lg4ff_set_autocenter_default, hid_lg4ff_set_range_dfp}, - {USB_DEVICE_ID_LOGITECH_G25_WHEEL, lg4ff_wheel_effects, hid_lg4ff_set_autocenter_default, hid_lg4ff_set_range_g25}, - {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, lg4ff_wheel_effects, hid_lg4ff_set_autocenter_default, hid_lg4ff_set_range_g25}, - {USB_DEVICE_ID_LOGITECH_G27_WHEEL, lg4ff_wheel_effects, hid_lg4ff_set_autocenter_default, hid_lg4ff_set_range_g25}, - {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, lg4ff_wheel_effects, hid_lg4ff_set_autocenter_default, 0} +struct lg4ff_usb_revision { + const __u16 rev_maj; + const __u16 rev_min; + const struct lg4ff_native_cmd *command; }; static const struct lg4ff_native_cmd native_dfp = { @@ -111,9 +126,9 @@ static const struct lg4ff_native_cmd native_g27 = { }; static const struct lg4ff_usb_revision lg4ff_revs[] = { - {0x11, 0x06, &native_dfp}, /* Driving Force Pro */ - {0x12, 0x22, &native_g25}, /* G25 */ - {0x12, 0x38, &native_g27}, /* G27 */ + {DFP_REV_MAJ, DFP_REV_MIN, &native_dfp}, /* Driving Force Pro */ + {G25_REV_MAJ, G25_REV_MIN, &native_g25}, /* G25 */ + {G27_REV_MAJ, G27_REV_MIN, &native_g27}, /* G27 */ }; static int hid_lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect) @@ -151,6 +166,8 @@ static void hid_lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitud 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); + dbg_hid("Setting centering force to %u\n", magnitude); + report->field[0]->value[0] = 0xfe; report->field[0]->value[1] = 0x0d; report->field[0]->value[2] = magnitude >> 13; @@ -158,6 +175,26 @@ static void hid_lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitud report->field[0]->value[4] = magnitude >> 8; report->field[0]->value[5] = 0x00; report->field[0]->value[6] = 0x00; + + usbhid_submit_report(hid, report, USB_DIR_OUT); +} + +/* Sends autocentering command compatible with Formula Force EX */ +static void hid_lg4ff_set_autocenter_ffex(struct input_dev *dev, u16 magnitude) +{ + struct hid_device *hid = input_get_drvdata(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); + + report->field[0]->value[0] = 0xfe; + report->field[0]->value[1] = 0x03; + report->field[0]->value[2] = 0x00; + report->field[0]->value[3] = 0x00; + report->field[0]->value[4] = magnitude >> 8; + report->field[0]->value[5] = 0x00; + report->field[0]->value[6] = 0x00; + + usbhid_submit_report(hid, report, USB_DIR_OUT); } /* Sends command to set range compatible with G25/G27/Driving Force GT */ @@ -165,21 +202,8 @@ static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range) { 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); - struct lg4ff_device_entry *entry = 0; - struct list_head *h; dbg_hid("G25/G27/DFGT: setting range to %u\n", range); - list_for_each(h, &device_list.list) { - entry = list_entry(h, struct lg4ff_device_entry, list); - if (entry->device_id == (int *)hid) - break; - } - - dbg_hid("List ID: %p, HID ptr: %p\n", entry->device_id, (int *)hid); - if (h == &device_list.list) { - dbg_hid("Device entry not found!\n"); - return; - } report->field[0]->value[0] = 0xf8; report->field[0]->value[1] = 0x81; @@ -190,7 +214,6 @@ static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range) report->field[0]->value[6] = 0x00; usbhid_submit_report(hid, report, USB_DIR_OUT); - entry->range = range; } /* Sends commands to set range compatible with Driving Force Pro wheel */ @@ -198,21 +221,8 @@ static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range) { 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); - struct lg4ff_device_entry *entry = 0; - struct list_head *h; int start_left, start_right, full_range; dbg_hid("Driving Force Pro: setting range to %u\n", range); - list_for_each(h, &device_list.list) { - entry = list_entry(h, struct lg4ff_device_entry, list); - if (entry->device_id == (int *)hid) - break; - } - - dbg_hid("List ID: %p, HID ptr: %p\n", entry->device_id, (int *)hid); - if (h == &device_list.list) { - dbg_hid("Device entry not found!\n"); - return; - } /* Prepare "coarse" limit command */ report->field[0]->value[0] = 0xf8; @@ -243,7 +253,6 @@ static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range) if (range == 200 || range == 900) { /* Do not apply any fine limit */ usbhid_submit_report(hid, report, USB_DIR_OUT); - entry->range = range; return; } @@ -258,7 +267,6 @@ static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range) report->field[0]->value[6] = 0xff; usbhid_submit_report(hid, report, USB_DIR_OUT); - entry->range = range; } static void hid_lg4ff_switch_native(struct hid_device *hid, const struct lg4ff_native_cmd *cmd) @@ -286,9 +294,14 @@ static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *att list_for_each(h, &device_list.list) { entry = list_entry(h, struct lg4ff_device_entry, list); + dbg_hid("List ID: %p, HID ptr: %p\n", entry->device_id, (int *)hid); if (entry->device_id == (int *)hid) break; } + if (h == &device_list.list) { + dbg_hid("Device not found!"); + return 0; + } count = scnprintf(buf, PAGE_SIZE, "Current range: %u\n", entry->range); return count; @@ -298,11 +311,31 @@ static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *att * according to the type of the wheel */ static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + struct lg4ff_device_entry *entry = 0; + struct list_head *h; + struct hid_device *hid = to_hid_device(dev); __u16 range = simple_strtoul(buf, NULL, 10); - /* Check that the range is within margins and that the wheel - * supports setting a custom range */ - if (range > 39 && range < 901 && hid_lg4ff_set_range != 0) - hid_lg4ff_set_range(to_hid_device(dev), range); + + list_for_each(h, &device_list.list) { + entry = list_entry(h, struct lg4ff_device_entry, list); + dbg_hid("List ID: %p, HID ptr: %p\n", entry->device_id, (int *)hid); + if (entry->device_id == (int *)hid) + break; + } + if (h == &device_list.list) { + dbg_hid("Device not found!"); + return count; + } + + if (range == 0) + range = entry->max_range; + + /* Check if the wheel supports range setting + * and that the range is within limits for the wheel */ + if (entry->set_range != 0 && range >= entry->min_range && range <= entry->max_range) { + entry->set_range(hid, range); + entry->range = range; + } return count; } @@ -315,7 +348,9 @@ int lg4ff_init(struct hid_device *hid) struct hid_report *report; struct hid_field *field; struct lg4ff_device_entry *entry; + struct usb_device_descriptor *udesc = 0; int error, i, j; + __u16 bcdDevice, rev_maj, rev_min; /* Find the report to use */ if (list_empty(report_list)) { @@ -339,24 +374,28 @@ int lg4ff_init(struct hid_device *hid) /* 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 %04X\n", 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, "Device not supported yet by lg4ff\n"); + hid_err(hid, "Device not supported by lg4ff driver. If you think it should be, consider reporting a bug to" + "LKML, Simon Wood or Michal MalĂ˝ \n"); return -1; } + + udesc = &(hid_to_usb_dev(hid)->descriptor); + if (!udesc) { + hid_err(hid, "NULL USB device descriptor\n"); + return -1; + } + bcdDevice = le16_to_cpu(udesc->bcdDevice); + rev_maj = bcdDevice >> 8; + rev_min = bcdDevice & 0xff; if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_WHEEL) { dbg_hid("Default wheel detected, can it do native?\n"); - - struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor); - __u16 bcdDevice = le16_to_cpu(udesc->bcdDevice); - __u16 rev_maj = bcdDevice >> 8; - __u16 rev_min = bcdDevice & 0xff; - dbg_hid("USB revision: %2x.%02x\n", rev_maj, rev_min); for (j = 0; j < ARRAY_SIZE(lg4ff_revs); j++) { @@ -380,12 +419,14 @@ int lg4ff_init(struct hid_device *hid) /* Check if autocentering is available and * disable it by default */ if (test_bit(FF_AUTOCENTER, dev->ffbit)) { - dev->ff->set_autocenter = lg4ff_devices[i].set_autocenter; + if(rev_maj == FFEX_REV_MAJ && rev_min == FFEX_REV_MIN) /* Formula Force EX expects different autocentering command */ + dev->ff->set_autocenter = hid_lg4ff_set_autocenter_ffex; + else + dev->ff->set_autocenter = hid_lg4ff_set_autocenter_default; + dev->ff->set_autocenter(dev, 0); } - hid_lg4ff_set_range = lg4ff_devices[i].set_range; - /* Initialize device_list if this is the first device to handle by lg4ff */ if (!list_inited) { INIT_LIST_HEAD(&device_list.list); @@ -399,16 +440,17 @@ int lg4ff_init(struct hid_device *hid) return -ENOMEM; } entry->device_id = (int *)hid; + entry->min_range = lg4ff_devices[i].min_range; + entry->max_range = lg4ff_devices[i].max_range; + entry->set_range = lg4ff_devices[i].set_range; list_add(&entry->list, &device_list.list); /* Create sysfs interface */ - if (hid_lg4ff_set_range != 0) { - error = device_create_file(&hid->dev, &dev_attr_set_range); - if (error) - return error; - dbg_hid("sysfs interface created\n"); - hid_lg4ff_set_range(hid, 900); - } + error = device_create_file(&hid->dev, &dev_attr_set_range); + if (error) + return error; + dbg_hid("sysfs interface created\n"); + entry->set_range(hid, entry->max_range); hid_info(hid, "Force feedback for Logitech Speed Force Wireless by Simon Wood \n"); return 0; @@ -431,8 +473,8 @@ int lg4ff_deinit(struct hid_device *hid) if (!found) dbg_hid("Device entry not found!\n"); - - + + device_remove_file(&hid->dev, &dev_attr_set_range); dbg_hid("Device successfully unregistered\n"); return 0;