2015-02-06 16:44:32

by Michal Malý

[permalink] [raw]
Subject: [PATCH 0/4] HID: Improve handling of multimode Logitech handling wheels

This patch series improves handling of various Logitech gaming wheels and
allows switching between various compatibility modes which might be useful
to improve compatibility with very old games and testing purposes.

Signed-off-by: Michal Malý <[email protected]>

Michal Malý (4):
Identify Logitech gaming wheels in compatibility modes accordingly to
Logitech specifications
Display the real wheel model and supported alternate modes through
sysfs. This applies only to multimode wheels.
Introduce a module parameter to disable automatic switch of Logitech
gaming wheels from compatibility to native mode. This only applies
to multimode wheels.
Allow switching of Logitech gaming wheels between available
compatibility modes through sysfs. This only applies to multimode
wheels.

.../ABI/testing/sysfs-driver-hid-logitech-lg4ff | 45 ++
drivers/hid/hid-lg.c | 6 +
drivers/hid/hid-lg4ff.c | 608 ++++++++++++++++++---
3 files changed, 583 insertions(+), 76 deletions(-)

--
2.2.2


2015-02-06 16:45:18

by Michal Malý

[permalink] [raw]
Subject: [PATCH 1/4] HID: Identify Logitech gaming wheels in compatibility modes accordingly to Logitech specifications

Identify Logitech gaming wheels in compatibility modes accordingly to Logitech
specifications.

Logitech specification contains a general method of identifying various
models of their gaming wheels while they are in "compatibility" mode.
This patch implements the method instead of checking against known
values of bcdDevice. Handling of the mode switch upon initialization is
also adjusted so that the driver does not have to go through the entire
initialization routine because the wheels are set to perform a USB
detach before they reappear in "native" mode.

Signed-off-by: Michal Malý <[email protected]>
---
drivers/hid/hid-lg4ff.c | 266 ++++++++++++++++++++++++++++++++++--------------
1 file changed, 190 insertions(+), 76 deletions(-)

diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
index 7835717..3a9de73 100644
--- a/drivers/hid/hid-lg4ff.c
+++ b/drivers/hid/hid-lg4ff.c
@@ -32,21 +32,15 @@
#include "hid-lg.h"
#include "hid-ids.h"

-#define DFGT_REV_MAJ 0x13
-#define DFGT_REV_MIN 0x22
-#define DFGT2_REV_MIN 0x26
-#define DFP_REV_MAJ 0x11
-#define DFP_REV_MIN 0x06
-#define FFEX_REV_MAJ 0x21
-#define FFEX_REV_MIN 0x00
-#define G25_REV_MAJ 0x12
-#define G25_REV_MIN 0x22
-#define G27_REV_MAJ 0x12
-#define G27_REV_MIN 0x38
-#define G27_2_REV_MIN 0x39
-
#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)

+#define LG4FF_MMODE_DONE 0
+#define LG4FF_MMODE_SWITCHED 1
+#define LG4FF_MMODE_NOT_MULTIMODE 2
+
+#define LG4FF_FFEX_REV_MAJ 0x21
+#define LG4FF_FFEX_REV_MIN 0x00
+
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);
static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf);
@@ -81,6 +75,22 @@ struct lg4ff_wheel {
void (*set_range)(struct hid_device *hid, u16 range);
};

+struct lg4ff_compat_mode_switch {
+ const __u8 cmd_count; /* Number of commands to send */
+ const __u8 cmd[];
+};
+
+struct lg4ff_wheel_ident_info {
+ const u16 mask;
+ const u16 result;
+ const u16 real_product_id;
+};
+
+struct lg4ff_wheel_ident_checklist {
+ const u32 count;
+ const struct lg4ff_wheel_ident_info *models[];
+};
+
static const struct lg4ff_wheel lg4ff_devices[] = {
{USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270, NULL},
{USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270, NULL},
@@ -92,48 +102,63 @@ static const struct lg4ff_wheel lg4ff_devices[] = {
{USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}
};

-struct lg4ff_native_cmd {
- const __u8 cmd_num; /* Number of commands to send */
- const __u8 cmd[];
+/* Multimode wheel identificators */
+static const struct lg4ff_wheel_ident_info lg4ff_dfp_ident_info = {
+ 0xf000,
+ 0x1000,
+ USB_DEVICE_ID_LOGITECH_DFP_WHEEL
+};
+
+static const struct lg4ff_wheel_ident_info lg4ff_g25_ident_info = {
+ 0xff00,
+ 0x1200,
+ USB_DEVICE_ID_LOGITECH_G25_WHEEL
+};
+
+static const struct lg4ff_wheel_ident_info lg4ff_g27_ident_info = {
+ 0xfff0,
+ 0x1230,
+ USB_DEVICE_ID_LOGITECH_G27_WHEEL
};

-struct lg4ff_usb_revision {
- const __u16 rev_maj;
- const __u16 rev_min;
- const struct lg4ff_native_cmd *command;
+static const struct lg4ff_wheel_ident_info lg4ff_dfgt_ident_info = {
+ 0xff00,
+ 0x1300,
+ USB_DEVICE_ID_LOGITECH_DFGT_WHEEL
};

-static const struct lg4ff_native_cmd native_dfp = {
+/* Multimode wheel identification checklists */
+static const struct lg4ff_wheel_ident_checklist lg4ff_main_checklist = {
+ 4,
+ {&lg4ff_dfgt_ident_info,
+ &lg4ff_g27_ident_info,
+ &lg4ff_g25_ident_info,
+ &lg4ff_dfp_ident_info}
+};
+
+/* Compatibility mode switching commands */
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_dfp = {
1,
{0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
};

-static const struct lg4ff_native_cmd native_dfgt = {
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_dfgt = {
2,
{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */
0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* 2nd command */
};

-static const struct lg4ff_native_cmd native_g25 = {
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_g25 = {
1,
{0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
};

-static const struct lg4ff_native_cmd native_g27 = {
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_g27 = {
2,
{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */
0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* 2nd command */
};

-static const struct lg4ff_usb_revision lg4ff_revs[] = {
- {DFGT_REV_MAJ, DFGT_REV_MIN, &native_dfgt}, /* Driving Force GT */
- {DFGT_REV_MAJ, DFGT2_REV_MIN, &native_dfgt}, /* Driving Force GT v2 */
- {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 */
- {G27_REV_MAJ, G27_2_REV_MIN, &native_g27}, /* G27 v2 */
-};
-
/* Recalculates X axis value accordingly to currently selected range */
static __s32 lg4ff_adjust_dfp_x_axis(__s32 value, __u16 range)
{
@@ -400,19 +425,22 @@ static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range)
hid_hw_request(hid, report, HID_REQ_SET_REPORT);
}

-static void hid_lg4ff_switch_native(struct hid_device *hid, const struct lg4ff_native_cmd *cmd)
+static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct lg4ff_compat_mode_switch *s)
{
- 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);
- __u8 i, j;
+ struct usb_device *usbdev = hid_to_usb_dev(hid);
+ struct usbhid_device *usbhid = hid->driver_data;
+ u8 i;

- j = 0;
- while (j < 7*cmd->cmd_num) {
- for (i = 0; i < 7; i++)
- report->field[0]->value[i] = cmd->cmd[j++];
+ for (i = 0; i < s->cmd_count; i++) {
+ int xferd, ret;
+ u8 data[7];

- hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+ memcpy(data, s->cmd + (7*i), 7);
+ ret = usb_interrupt_msg(usbdev, usbhid->urbout->pipe, data, 7, &xferd, USB_CTRL_SET_TIMEOUT);
+ if (ret)
+ return ret;
}
+ return 0;
}

/* Read current range and display it in terminal */
@@ -556,20 +584,129 @@ static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cde
}
#endif

+u16 lg4ff_identify_multimode_wheel(struct hid_device *hid, const u16 reported_product_id, const u16 bcdDevice)
+{
+ const struct lg4ff_wheel_ident_checklist *checklist;
+ int i, from_idx, to_idx;
+
+ switch (reported_product_id) {
+ case USB_DEVICE_ID_LOGITECH_WHEEL:
+ case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+ checklist = &lg4ff_main_checklist;
+ from_idx = 0;
+ to_idx = checklist->count - 1;
+ break;
+ case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+ checklist = &lg4ff_main_checklist;
+ from_idx = 0;
+ to_idx = checklist->count - 2; /* End identity check at G25 */
+ break;
+ case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+ checklist = &lg4ff_main_checklist;
+ from_idx = 1; /* Start identity check at G27 */
+ to_idx = checklist->count - 3; /* End identity check at G27 */
+ break;
+ case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+ checklist = &lg4ff_main_checklist;
+ from_idx = 0;
+ to_idx = checklist->count - 4; /* End identity check at DFGT */
+ break;
+ default:
+ return 0;
+ }
+
+ for (i = from_idx; i <= to_idx; i++) {
+ const u16 mask = checklist->models[i]->mask;
+ const u16 result = checklist->models[i]->result;
+ const u16 real_product_id = checklist->models[i]->real_product_id;
+
+ if ((bcdDevice & mask) == result) {
+ dbg_hid("Found wheel with real PID %X whose reported PID is %X\n", real_product_id, reported_product_id);
+ return real_product_id;
+ }
+ }
+
+ /* No match found. This is an unknown wheel model, do not touch it */
+ dbg_hid("Wheel with bcdDevice %X was not recognized as multimode wheel, leaving in its current mode\n", bcdDevice);
+ return 0;
+}
+
+int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_product_id, const u16 bcdDevice)
+{
+ const u16 reported_product_id = hid->product;
+ int ret;
+
+ *real_product_id = lg4ff_identify_multimode_wheel(hid, reported_product_id, bcdDevice);
+ /* Probed wheel is not a multimode wheel */
+ if (!*real_product_id) {
+ *real_product_id = reported_product_id;
+ dbg_hid("Wheel is not a multimode wheel\n");
+ return LG4FF_MMODE_NOT_MULTIMODE;
+ }
+
+ /* Switch from "Driving Force" mode to native mode automatically.
+ * Otherwise keep the wheel in its current mode */
+ if (reported_product_id == USB_DEVICE_ID_LOGITECH_WHEEL &&
+ reported_product_id != *real_product_id) {
+ const struct lg4ff_compat_mode_switch *s;
+
+ switch (*real_product_id) {
+ case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+ s = &lg4ff_mode_switch_dfp;
+ break;
+ case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+ s = &lg4ff_mode_switch_g25;
+ break;
+ case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+ s = &lg4ff_mode_switch_g27;
+ break;
+ case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+ s = &lg4ff_mode_switch_dfgt;
+ break;
+ default:
+ hid_err(hid, "Invalid product id %X\n", *real_product_id);
+ return LG4FF_MMODE_DONE;
+ }
+
+ ret = lg4ff_switch_compatibility_mode(hid, s);
+ if (ret) {
+ /* Wheel could not have been switched to native mode,
+ * leave it in "Driving Force" mode and continue */
+ hid_err(hid, "Unable to switch wheel mode, errno %d\n", ret);
+ return LG4FF_MMODE_DONE;
+ }
+ return LG4FF_MMODE_SWITCHED;
+ }
+
+ return LG4FF_MMODE_DONE;
+}
+
+
int lg4ff_init(struct hid_device *hid)
{
struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
struct input_dev *dev = hidinput->input;
+ const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor);
+ const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice);
struct lg4ff_device_entry *entry;
struct lg_drv_data *drv_data;
- struct usb_device_descriptor *udesc;
- int error, i, j;
- __u16 bcdDevice, rev_maj, rev_min;
+ int error, i, j, ret;
+ u16 real_product_id;

/* Check that the report looks ok */
if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7))
return -1;

+ /* Check if a multimode wheel has been connected and
+ * handle it appropriately */
+ 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 (ret == LG4FF_MMODE_SWITCHED)
+ return 0;
+
/* Check what wheel has been connected */
for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) {
if (hid->product == lg4ff_devices[i].product_id) {
@@ -584,28 +721,6 @@ int lg4ff_init(struct hid_device *hid)
return -1;
}

- /* Attempt to switch wheel to native mode when applicable */
- 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("Generic wheel detected, can it do native?\n");
- dbg_hid("USB revision: %2x.%02x\n", rev_maj, rev_min);
-
- for (j = 0; j < ARRAY_SIZE(lg4ff_revs); j++) {
- if (lg4ff_revs[j].rev_maj == rev_maj && lg4ff_revs[j].rev_min == rev_min) {
- hid_lg4ff_switch_native(hid, lg4ff_revs[j].command);
- hid_info(hid, "Switched to native mode\n");
- }
- }
- }
-
/* 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);
@@ -638,7 +753,9 @@ int lg4ff_init(struct hid_device *hid)
/* Check if autocentering is available and
* set the centering force to zero by default */
if (test_bit(FF_AUTOCENTER, dev->ffbit)) {
- if (rev_maj == FFEX_REV_MAJ && rev_min == FFEX_REV_MIN) /* Formula Force EX expects different autocentering command */
+ /* Formula Force EX expects different autocentering command */
+ if ((bcdDevice >> 8) == LG4FF_FFEX_REV_MAJ &&
+ (bcdDevice & 0xff) == LG4FF_FFEX_REV_MIN)
dev->ff->set_autocenter = hid_lg4ff_set_autocenter_ffex;
else
dev->ff->set_autocenter = hid_lg4ff_set_autocenter_default;
@@ -712,25 +829,21 @@ out:
return 0;
}

-
-
int lg4ff_deinit(struct hid_device *hid)
{
struct lg4ff_device_entry *entry;
struct lg_drv_data *drv_data;

- device_remove_file(&hid->dev, &dev_attr_range);
-
drv_data = hid_get_drvdata(hid);
if (!drv_data) {
hid_err(hid, "Error while deinitializing device, no private driver data.\n");
return -1;
}
entry = drv_data->device_props;
- if (!entry) {
- hid_err(hid, "Error while deinitializing device, no device properties data.\n");
- return -1;
- }
+ if (!entry)
+ goto out; /* Nothing more to do */
+
+ device_remove_file(&hid->dev, &dev_attr_range);

#ifdef CONFIG_LEDS_CLASS
{
@@ -753,6 +866,7 @@ int lg4ff_deinit(struct hid_device *hid)
/* Deallocate memory */
kfree(entry);

+out:
dbg_hid("Device successfully unregistered\n");
return 0;
}
--
2.2.2

2015-02-06 16:45:20

by Michal Malý

[permalink] [raw]
Subject: [PATCH 2/4] HID: Display the real wheel model and supported alternate modes through sysfs. This applies only to multimode wheels.

Display the real wheel model and supported alternate modes through sysfs. This
applies only to multimode wheels.

Signed-off-by: Michal Malý <[email protected]>
---
drivers/hid/hid-lg4ff.c | 209 ++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 202 insertions(+), 7 deletions(-)

diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
index 3a9de73..b9c9fe6 100644
--- a/drivers/hid/hid-lg4ff.c
+++ b/drivers/hid/hid-lg4ff.c
@@ -34,19 +34,51 @@

#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)

-#define LG4FF_MMODE_DONE 0
+#define LG4FF_MMODE_IS_MULTIMODE 0
#define LG4FF_MMODE_SWITCHED 1
#define LG4FF_MMODE_NOT_MULTIMODE 2

+#define LG4FF_MODE_NATIVE_IDX 0
+#define LG4FF_MODE_DFEX_IDX 1
+#define LG4FF_MODE_DFP_IDX 2
+#define LG4FF_MODE_G25_IDX 3
+#define LG4FF_MODE_DFGT_IDX 4
+#define LG4FF_MODE_G27_IDX 5
+#define LG4FF_MODE_MAX_IDX 6
+
+#define LG4FF_MODE_NATIVE BIT(LG4FF_MODE_NATIVE_IDX)
+#define LG4FF_MODE_DFEX BIT(LG4FF_MODE_DFEX_IDX)
+#define LG4FF_MODE_DFP BIT(LG4FF_MODE_DFP_IDX)
+#define LG4FF_MODE_G25 BIT(LG4FF_MODE_G25_IDX)
+#define LG4FF_MODE_DFGT BIT(LG4FF_MODE_DFGT_IDX)
+#define LG4FF_MODE_G27 BIT(LG4FF_MODE_G27_IDX)
+
+#define LG4FF_DFEX_TAG "DF-EX"
+#define LG4FF_DFEX_NAME "Driving Force / Formula EX"
+#define LG4FF_DFP_TAG "DFP"
+#define LG4FF_DFP_NAME "Driving Force Pro"
+#define LG4FF_G25_TAG "G25"
+#define LG4FF_G25_NAME "G25 Racing Wheel"
+#define LG4FF_G27_TAG "G27"
+#define LG4FF_G27_NAME "G27 Racing Wheel"
+#define LG4FF_DFGT_TAG "DFGT"
+#define LG4FF_DFGT_NAME "Driving Force GT"
+
#define LG4FF_FFEX_REV_MAJ 0x21
#define LG4FF_FFEX_REV_MIN 0x00

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);
+static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attribute *attr, char *buf);
+static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
+static ssize_t lg4ff_real_id_show(struct device *dev, struct device_attribute *attr, char *buf);
+static ssize_t lg4ff_real_id_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);

+static DEVICE_ATTR(alternate_modes, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_alternate_modes_show, lg4ff_alternate_modes_store);
static DEVICE_ATTR(range, S_IRWXU | S_IRWXG | S_IROTH, lg4ff_range_show, lg4ff_range_store);
+static DEVICE_ATTR(real_id, S_IRUGO, lg4ff_real_id_show, lg4ff_real_id_store);

struct lg4ff_device_entry {
__u32 product_id;
@@ -57,6 +89,10 @@ struct lg4ff_device_entry {
__u8 led_state;
struct led_classdev *led[5];
#endif
+ u32 alternate_modes;
+ const char *real_tag;
+ const char *real_name;
+ u16 real_product_id;
struct list_head list;
void (*set_range)(struct hid_device *hid, u16 range);
};
@@ -91,6 +127,19 @@ struct lg4ff_wheel_ident_checklist {
const struct lg4ff_wheel_ident_info *models[];
};

+struct lg4ff_multimode_wheel {
+ const u16 product_id;
+ const u32 alternate_modes;
+ const char *real_tag;
+ const char *real_name;
+};
+
+struct lg4ff_alternate_mode {
+ const u16 product_id;
+ const char *tag;
+ const char *name;
+};
+
static const struct lg4ff_wheel lg4ff_devices[] = {
{USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270, NULL},
{USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270, NULL},
@@ -102,6 +151,30 @@ static const struct lg4ff_wheel lg4ff_devices[] = {
{USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}
};

+static const struct lg4ff_multimode_wheel lg4ff_multimode_wheels[] = {
+ {USB_DEVICE_ID_LOGITECH_DFP_WHEEL,
+ LG4FF_MODE_NATIVE | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+ LG4FF_DFP_TAG, LG4FF_DFP_NAME},
+ {USB_DEVICE_ID_LOGITECH_G25_WHEEL,
+ LG4FF_MODE_NATIVE | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+ LG4FF_G25_TAG, LG4FF_G25_NAME},
+ {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL,
+ LG4FF_MODE_NATIVE | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+ LG4FF_DFGT_TAG, LG4FF_DFGT_NAME},
+ {USB_DEVICE_ID_LOGITECH_G27_WHEEL,
+ LG4FF_MODE_NATIVE | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+ LG4FF_G27_TAG, LG4FF_G27_NAME},
+};
+
+static const struct lg4ff_alternate_mode lg4ff_alternate_modes[] = {
+ [LG4FF_MODE_NATIVE_IDX] = {0, "native", ""},
+ [LG4FF_MODE_DFEX_IDX] = {USB_DEVICE_ID_LOGITECH_WHEEL, LG4FF_DFEX_TAG, LG4FF_DFEX_NAME},
+ [LG4FF_MODE_DFP_IDX] = {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, LG4FF_DFP_TAG, LG4FF_DFP_NAME},
+ [LG4FF_MODE_G25_IDX] = {USB_DEVICE_ID_LOGITECH_G25_WHEEL, LG4FF_G25_TAG, LG4FF_G25_NAME},
+ [LG4FF_MODE_DFGT_IDX] = {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, LG4FF_DFGT_TAG, LG4FF_DFGT_NAME},
+ [LG4FF_MODE_G27_IDX] = {USB_DEVICE_ID_LOGITECH_G27_WHEEL, LG4FF_G27_TAG, LG4FF_G27_NAME}
+};
+
/* Multimode wheel identificators */
static const struct lg4ff_wheel_ident_info lg4ff_dfp_ident_info = {
0xf000,
@@ -443,6 +516,60 @@ static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct
return 0;
}

+static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct hid_device *hid = to_hid_device(dev);
+ struct lg4ff_device_entry *entry;
+ struct lg_drv_data *drv_data;
+ ssize_t count = 0;
+ int i;
+
+ drv_data = hid_get_drvdata(hid);
+ if (!drv_data) {
+ hid_err(hid, "Private driver data not found!\n");
+ return 0;
+ }
+
+ entry = drv_data->device_props;
+ if (!entry) {
+ hid_err(hid, "Device properties not found!\n");
+ return 0;
+ }
+
+ if (!entry->real_name) {
+ hid_err(hid, "NULL pointer to string\n");
+ return 0;
+ }
+
+ for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
+ if (entry->alternate_modes & BIT(i)) {
+ /* Print tag and full name */
+ count += scnprintf(buf + count, PAGE_SIZE - count, "%s: %s",
+ lg4ff_alternate_modes[i].tag,
+ !lg4ff_alternate_modes[i].product_id ? entry->real_name : lg4ff_alternate_modes[i].name);
+ if (count >= PAGE_SIZE - 1)
+ return count;
+
+ /* Mark the currently active mode with an asterisk */
+ if (lg4ff_alternate_modes[i].product_id == entry->product_id ||
+ (lg4ff_alternate_modes[i].product_id == 0 && entry->product_id == entry->real_product_id))
+ count += scnprintf(buf + count, PAGE_SIZE - count, " *\n");
+ else
+ count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
+
+ if (count >= PAGE_SIZE - 1)
+ return count;
+ }
+ }
+
+ return count;
+}
+
+static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+ return -ENOSYS;
+}
+
/* Read current range and display it in terminal */
static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf)
{
@@ -501,6 +628,40 @@ static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *at
return count;
}

+static ssize_t lg4ff_real_id_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct hid_device *hid = to_hid_device(dev);
+ struct lg4ff_device_entry *entry;
+ struct lg_drv_data *drv_data;
+ size_t count;
+
+ drv_data = hid_get_drvdata(hid);
+ if (!drv_data) {
+ hid_err(hid, "Private driver data not found!\n");
+ return 0;
+ }
+
+ entry = drv_data->device_props;
+ if (!entry) {
+ hid_err(hid, "Device properties not found!\n");
+ return 0;
+ }
+
+ if (!entry->real_tag || !entry->real_name) {
+ hid_err(hid, "NULL pointer to string\n");
+ return 0;
+ }
+
+ count = scnprintf(buf, PAGE_SIZE, "%s: %s\n", entry->real_tag, entry->real_name);
+ return count;
+}
+
+static ssize_t lg4ff_real_id_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+ /* Real ID is a read-only value */
+ return -EPERM;
+}
+
#ifdef CONFIG_LEDS_CLASS
static void lg4ff_set_leds(struct hid_device *hid, __u8 leds)
{
@@ -665,7 +826,7 @@ int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_product_id, c
break;
default:
hid_err(hid, "Invalid product id %X\n", *real_product_id);
- return LG4FF_MMODE_DONE;
+ return LG4FF_MMODE_NOT_MULTIMODE;
}

ret = lg4ff_switch_compatibility_mode(hid, s);
@@ -673,12 +834,12 @@ int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_product_id, c
/* Wheel could not have been switched to native mode,
* leave it in "Driving Force" mode and continue */
hid_err(hid, "Unable to switch wheel mode, errno %d\n", ret);
- return LG4FF_MMODE_DONE;
+ return LG4FF_MMODE_IS_MULTIMODE;
}
return LG4FF_MMODE_SWITCHED;
}

- return LG4FF_MMODE_DONE;
+ return LG4FF_MMODE_IS_MULTIMODE;
}


@@ -690,7 +851,8 @@ int lg4ff_init(struct hid_device *hid)
const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice);
struct lg4ff_device_entry *entry;
struct lg_drv_data *drv_data;
- int error, i, j, ret;
+ int error, i, j;
+ int mmode_ret, mmode_idx = -1;
u16 real_product_id;

/* Check that the report looks ok */
@@ -699,12 +861,12 @@ int lg4ff_init(struct hid_device *hid)

/* Check if a multimode wheel has been connected and
* handle it appropriately */
- ret = lg4ff_handle_multimode_wheel(hid, &real_product_id, bcdDevice);
+ 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 (ret == LG4FF_MMODE_SWITCHED)
+ if (mmode_ret == LG4FF_MMODE_SWITCHED)
return 0;

/* Check what wheel has been connected */
@@ -721,6 +883,18 @@ int lg4ff_init(struct hid_device *hid)
return -1;
}

+ 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);
+ return -1;
+ }
+ }
+
/* 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);
@@ -746,9 +920,16 @@ int lg4ff_init(struct hid_device *hid)
drv_data->device_props = entry;

entry->product_id = lg4ff_devices[i].product_id;
+ entry->real_product_id = real_product_id;
entry->min_range = lg4ff_devices[i].min_range;
entry->max_range = lg4ff_devices[i].max_range;
entry->set_range = lg4ff_devices[i].set_range;
+ if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
+ BUG_ON(mmode_idx == -1);
+ entry->alternate_modes = lg4ff_multimode_wheels[mmode_idx].alternate_modes;
+ entry->real_tag = lg4ff_multimode_wheels[mmode_idx].real_tag;
+ entry->real_name = lg4ff_multimode_wheels[mmode_idx].real_name;
+ }

/* Check if autocentering is available and
* set the centering force to zero by default */
@@ -767,6 +948,14 @@ int lg4ff_init(struct hid_device *hid)
error = device_create_file(&hid->dev, &dev_attr_range);
if (error)
return error;
+ if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
+ error = device_create_file(&hid->dev, &dev_attr_real_id);
+ if (error)
+ return error;
+ error = device_create_file(&hid->dev, &dev_attr_alternate_modes);
+ if (error)
+ return error;
+ }
dbg_hid("sysfs interface created\n");

/* Set the maximum range to start with */
@@ -845,6 +1034,12 @@ int lg4ff_deinit(struct hid_device *hid)

device_remove_file(&hid->dev, &dev_attr_range);

+ /* Multimode devices will have at least the "MODE_NATIVE" bit set */
+ if (entry->alternate_modes) {
+ device_remove_file(&hid->dev, &dev_attr_real_id);
+ device_remove_file(&hid->dev, &dev_attr_alternate_modes);
+ }
+
#ifdef CONFIG_LEDS_CLASS
{
int j;
--
2.2.2

2015-02-06 16:44:33

by Michal Malý

[permalink] [raw]
Subject: [PATCH 3/4] HID: Introduce a module parameter to disable automatic switch of Logitech gaming wheels from compatibility to native mode. This only applies to multimode wheels.

Introduce a module parameter to disable automatic switch of Logitech gaming
wheels from compatibility to native mode. This only applies to multimode wheels.

Signed-off-by: Michal Malý <[email protected]>
---
drivers/hid/hid-lg.c | 6 ++++++
drivers/hid/hid-lg4ff.c | 4 +++-
2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c
index f91ff14..8dae03f 100644
--- a/drivers/hid/hid-lg.c
+++ b/drivers/hid/hid-lg.c
@@ -818,4 +818,10 @@ static struct hid_driver lg_driver = {
};
module_hid_driver(lg_driver);

+#ifdef CONFIG_LOGIWHEELS_FF
+extern int lg4ff_no_autoswitch; /* From hid-lg4ff.c */
+module_param_named(lg4ff_no_autoswitch, lg4ff_no_autoswitch, int, S_IRUGO);
+MODULE_PARM_DESC(lg4ff_no_autoswitch, "Do not switch multimode wheels to their native mode automatically");
+#endif
+
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
index b9c9fe6..cbb000a 100644
--- a/drivers/hid/hid-lg4ff.c
+++ b/drivers/hid/hid-lg4ff.c
@@ -67,6 +67,7 @@
#define LG4FF_FFEX_REV_MAJ 0x21
#define LG4FF_FFEX_REV_MIN 0x00

+int lg4ff_no_autoswitch = 0;
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);
static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attribute *attr, char *buf);
@@ -808,7 +809,8 @@ int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_product_id, c
/* Switch from "Driving Force" mode to native mode automatically.
* Otherwise keep the wheel in its current mode */
if (reported_product_id == USB_DEVICE_ID_LOGITECH_WHEEL &&
- reported_product_id != *real_product_id) {
+ reported_product_id != *real_product_id &&
+ !lg4ff_no_autoswitch) {
const struct lg4ff_compat_mode_switch *s;

switch (*real_product_id) {
--
2.2.2

2015-02-06 16:44:34

by Michal Malý

[permalink] [raw]
Subject: [PATCH 4/4] HID: Allow switching of Logitech gaming wheels between available compatibility modes through sysfs. This only applies to multimode wheels.

Allow switching of Logitech gaming wheels between available compatibility modes
through sysfs. This only applies to multimode wheels.

Signed-off-by: Michal Malý <[email protected]>
---
.../ABI/testing/sysfs-driver-hid-logitech-lg4ff | 45 +++++
drivers/hid/hid-lg4ff.c | 203 ++++++++++++++++++---
2 files changed, 219 insertions(+), 29 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff b/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff
index 167d903..0dfeb6c 100644
--- a/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff
+++ b/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff
@@ -5,3 +5,48 @@ Contact: Michal Malý <[email protected]>
Description: Display minimum, maximum and current range of the steering
wheel. Writing a value within min and max boundaries sets the
range of the wheel.
+
+What: /sys/bus/hid/drivers/logitech/<dev>/alternate_modes
+Date: Feb 2015
+KernelVersion: 3.21
+Contact: Michal Malý <[email protected]>
+Description: Displays a set of alternate modes supported by a wheel. Each
+ mode is listed as follows:
+ Tag: Mode Name
+ Currently active mode is marked with an asterisk. List also
+ contains an abstract item "native" which always denotes the
+ native mode of the wheel. Echoing the mode tag switches the
+ wheel into the corresponding mode. Depending on the exact model
+ of the wheel not all listed modes might always be selectable.
+ If a wheel cannot be switched into the desired mode, -EINVAL
+ is returned accompanied with an explanatory message in the
+ kernel log.
+ This entry is not created for devices that have only one mode.
+
+ Currently supported mode switches:
+ Driving Force Pro:
+ DF-EX --> DFP
+
+ G25:
+ DF-EX --> DFP --> G25
+
+ G27:
+ DF-EX <*> DFP <-> G25 <-> G27
+ DF-EX <*--------> G25 <-> G27
+ DF-EX <*----------------> G27
+
+ DFGT:
+ DF-EX <*> DFP <-> DFGT
+ DF-EX <*--------> DFGT
+
+ * hid_logitech module must be loaded with lg4ff_no_autoswitch=1
+ parameter set in order for the switch to DF-EX mode to work.
+
+What: /sys/bus/hid/drivers/logitech/<dev>/real_id
+Date: Feb 2015
+KernelVersion: 3.21
+Contact: Michal Malý <[email protected]>
+Description: Displays the real model of the wheel regardless of any
+ alternate mode the wheel might be switched to.
+ It is a read-only value.
+ This entry is not created for devices that have only one mode.
diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
index cbb000a..f1ae03a 100644
--- a/drivers/hid/hid-lg4ff.c
+++ b/drivers/hid/hid-lg4ff.c
@@ -211,26 +211,47 @@ static const struct lg4ff_wheel_ident_checklist lg4ff_main_checklist = {
};

/* Compatibility mode switching commands */
-static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_dfp = {
- 1,
- {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
+/* EXT_CMD9 - Understood by G27 and DFGT */
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfex = {
+ 2,
+ {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
+ 0xf8, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DF-EX with detach */
};

-static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_dfgt = {
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfp = {
2,
- {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */
- 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* 2nd command */
+ {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
+ 0xf8, 0x09, 0x01, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DFP with detach */
};

-static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_g25 = {
- 1,
- {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g25 = {
+ 2,
+ {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
+ 0xf8, 0x09, 0x02, 0x01, 0x00, 0x00, 0x00} /* Switch mode to G25 with detach */
};

-static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_g27 = {
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfgt = {
2,
- {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */
- 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* 2nd command */
+ {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
+ 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DFGT with detach */
+};
+
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g27 = {
+ 2,
+ {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
+ 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* Switch mode to G27 with detach */
+};
+
+/* EXT_CMD1 - Understood by DFP, G25, G27 and DFGT */
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext01_dfp = {
+ 1,
+ {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
+};
+
+/* EXT_CMD16 - Understood by G25 and G27 */
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext16_g25 = {
+ 1,
+ {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
};

/* Recalculates X axis value accordingly to currently selected range */
@@ -499,6 +520,63 @@ static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range)
hid_hw_request(hid, report, HID_REQ_SET_REPORT);
}

+static const struct lg4ff_compat_mode_switch *lg4ff_get_mode_switch_command(const u16 real_product_id, const u16 target_product_id)
+{
+ switch (real_product_id) {
+ case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+ switch (target_product_id) {
+ case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+ return &lg4ff_mode_switch_ext01_dfp;
+ /* DFP can only be switched to its native mode */
+ default:
+ return NULL;
+ }
+ break;
+ case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+ switch (target_product_id) {
+ case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+ return &lg4ff_mode_switch_ext01_dfp;
+ case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+ return &lg4ff_mode_switch_ext16_g25;
+ /* G25 can only be switched to DFP mode or its native mode */
+ default:
+ return NULL;
+ }
+ break;
+ case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+ switch (target_product_id) {
+ case USB_DEVICE_ID_LOGITECH_WHEEL:
+ return &lg4ff_mode_switch_ext09_dfex;
+ case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+ return &lg4ff_mode_switch_ext09_dfp;
+ case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+ return &lg4ff_mode_switch_ext09_g25;
+ case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+ return &lg4ff_mode_switch_ext09_g27;
+ /* G27 can only be switched to DF-EX, DFP, G25 or its native mode */
+ default:
+ return NULL;
+ }
+ break;
+ case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+ switch (target_product_id) {
+ case USB_DEVICE_ID_LOGITECH_WHEEL:
+ return &lg4ff_mode_switch_ext09_dfex;
+ case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+ return &lg4ff_mode_switch_ext09_dfp;
+ case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+ return &lg4ff_mode_switch_ext09_dfgt;
+ /* DFGT can only be switched to DF-EX, DFP or its native mode */
+ default:
+ return NULL;
+ }
+ break;
+ /* No other wheels have multiple modes */
+ default:
+ return NULL;
+ }
+}
+
static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct lg4ff_compat_mode_switch *s)
{
struct usb_device *usbdev = hid_to_usb_dev(hid);
@@ -568,7 +646,86 @@ static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attr

static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
- return -ENOSYS;
+ struct hid_device *hid = to_hid_device(dev);
+ struct lg4ff_device_entry *entry;
+ struct lg_drv_data *drv_data;
+ const struct lg4ff_compat_mode_switch *s;
+ u16 target_product_id = 0;
+ int i, ret;
+ char *lbuf;
+
+ drv_data = hid_get_drvdata(hid);
+ if (!drv_data) {
+ hid_err(hid, "Private driver data not found!\n");
+ return -EINVAL;
+ }
+
+ entry = drv_data->device_props;
+ if (!entry) {
+ hid_err(hid, "Device properties not found!\n");
+ return -EINVAL;
+ }
+
+ /* Allow \n at the end of the input parameter */
+ lbuf = kasprintf(GFP_KERNEL, "%s", buf);
+ if (!lbuf)
+ return -ENOMEM;
+
+ i = strlen(lbuf);
+ if (lbuf[i-1] == '\n') {
+ if (i == 1) {
+ kfree(lbuf);
+ return -EINVAL;
+ }
+ lbuf[i-1] = '\0';
+ }
+
+ for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
+ const u16 mode_product_id = lg4ff_alternate_modes[i].product_id;
+ const char *tag = lg4ff_alternate_modes[i].tag;
+
+ if (entry->alternate_modes & BIT(i)) {
+ if (!strcmp(tag, lbuf)) {
+ if (!mode_product_id)
+ target_product_id = entry->real_product_id;
+ else
+ target_product_id = mode_product_id;
+ break;
+ }
+ } else {
+ dbg_hid("Alternate mode \"%s\" not supported by the device\n", tag);
+ }
+ }
+ kfree(lbuf); /* Not needed anymore */
+
+ if (i == LG4FF_MODE_MAX_IDX)
+ return -EINVAL;
+
+ if (target_product_id == entry->product_id) /* Nothing to do */
+ return count;
+
+ /* Automatic switching has to be disabled for the switch to DF-EX mode to work correctly */
+ if (target_product_id == USB_DEVICE_ID_LOGITECH_WHEEL && !lg4ff_no_autoswitch) {
+ hid_info(hid, "\"%s\" cannot be switched to \"DF-EX\" mode. Load the \"hid_logitech\" module with \"lg4ff_no_autoswitch=1\" parameter set and try again.\n",
+ entry->real_name);
+ return -EINVAL;
+ }
+
+ /* Take care of hardware limitations */
+ if ((entry->real_product_id == USB_DEVICE_ID_LOGITECH_DFP_WHEEL || entry->real_product_id == USB_DEVICE_ID_LOGITECH_G25_WHEEL) &&
+ entry->product_id > target_product_id) {
+ hid_info(hid, "\"%s\" cannot be switched back into \"%s\" mode.\n", entry->real_name, lg4ff_alternate_modes[i].name);
+ return -EINVAL;
+ }
+
+ s = lg4ff_get_mode_switch_command(entry->real_product_id, target_product_id);
+ if (!s) {
+ hid_err(hid, "Invalid target product ID %X\n", target_product_id);
+ return -EINVAL;
+ }
+
+ ret = lg4ff_switch_compatibility_mode(hid, s);
+ return (ret == 0 ? count : ret);
}

/* Read current range and display it in terminal */
@@ -788,7 +945,8 @@ u16 lg4ff_identify_multimode_wheel(struct hid_device *hid, const u16 reported_pr
}
}

- /* No match found. This is an unknown wheel model, do not touch it */
+ /* No match found. This is either Driving Force or an unknown
+ * wheel model, do not touch it */
dbg_hid("Wheel with bcdDevice %X was not recognized as multimode wheel, leaving in its current mode\n", bcdDevice);
return 0;
}
@@ -811,22 +969,9 @@ int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_product_id, c
if (reported_product_id == USB_DEVICE_ID_LOGITECH_WHEEL &&
reported_product_id != *real_product_id &&
!lg4ff_no_autoswitch) {
- const struct lg4ff_compat_mode_switch *s;
+ const struct lg4ff_compat_mode_switch *s = lg4ff_get_mode_switch_command(*real_product_id, *real_product_id);

- switch (*real_product_id) {
- case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
- s = &lg4ff_mode_switch_dfp;
- break;
- case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
- s = &lg4ff_mode_switch_g25;
- break;
- case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
- s = &lg4ff_mode_switch_g27;
- break;
- case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
- s = &lg4ff_mode_switch_dfgt;
- break;
- default:
+ if (!s) {
hid_err(hid, "Invalid product id %X\n", *real_product_id);
return LG4FF_MMODE_NOT_MULTIMODE;
}
--
2.2.2

2015-02-06 18:24:54

by Simon Wood

[permalink] [raw]
Subject: Re: [PATCH 0/4] HID: Improve handling of multimode Logitech handling wheels

> This patch series improves handling of various Logitech gaming wheels and
> allows switching between various compatibility modes which might be useful
> to improve compatibility with very old games and testing purposes.

Hi all,
We should note that part 1 performs a very important function of identify
the wheels as per Logitech's schema.

At present our (old detection) uses USB Revision to identify 'fancy'
wheels which can be placed into native mode. This means when Logitech
updates the hardware we are playing catch-up (this has happened 3 times
with the DF-GT wheel so far).

This newer code should automatically cope with new wheels in the future.


Thanks to Michal for undertaking this work. I have been testing the code
over the past weeks and believe it to be working, I will confirm mid-next
week when I can gain access to my wheels.

Simon

2015-02-09 09:14:48

by Elias Vanderstuyft

[permalink] [raw]
Subject: Re: [PATCH 0/4] HID: Improve handling of multimode Logitech handling wheels

On Fri, Feb 6, 2015 at 5:34 PM, Michal Malý
<[email protected]> wrote:
> This patch series improves handling of various Logitech gaming wheels and
> allows switching between various compatibility modes which might be useful
> to improve compatibility with very old games and testing purposes.
>
> Signed-off-by: Michal Malý <[email protected]>
>
> Michal Malý (4):
> Identify Logitech gaming wheels in compatibility modes accordingly to
> Logitech specifications
> Display the real wheel model and supported alternate modes through
> sysfs. This applies only to multimode wheels.
> Introduce a module parameter to disable automatic switch of Logitech
> gaming wheels from compatibility to native mode. This only applies
> to multimode wheels.
> Allow switching of Logitech gaming wheels between available
> compatibility modes through sysfs. This only applies to multimode
> wheels.
>
> .../ABI/testing/sysfs-driver-hid-logitech-lg4ff | 45 ++
> drivers/hid/hid-lg.c | 6 +
> drivers/hid/hid-lg4ff.c | 608 ++++++++++++++++++---
> 3 files changed, 583 insertions(+), 76 deletions(-)
>
> --
> 2.2.2
>

Applied cleanly on kernel 3.17.8, and tested with:
- Logitech Formula Vibration Wheel
- Logitech MOMO (Black) Wheel
Everything keeps working like it was before, which is correct because
these wheels are no multimode wheels.

Tested-by: Elias Vanderstuyft <[email protected]>

Thanks,
Elias

2015-02-13 16:27:54

by Simon Wood

[permalink] [raw]
Subject: Re: [PATCH 0/4] HID: Improve handling of multimode Logitech handling wheels

Hi all,
I tested this with a DFP, G27 and WiiWheell was looking good, but then
I found a small bug.

It seems that the requested mode is case sensitive. 'G25' works, but
'g25' is seen as a request for a 'DF-GT' and causes and error.

Sorry I didn't see this earlier. I'd be happy to apply and fix later,
but I suspect Jiri will say fix it first.
Simon

PS. Resent via Gmail as my hosting is having issues.

On Fri, 6 Feb 2015 10:27:21 -0700, [email protected] wrote:
> > This patch series improves handling of various Logitech gaming wheels and
> > allows switching between various compatibility modes which might be useful
> > to improve compatibility with very old games and testing purposes.
>
> Hi all,
> We should note that part 1 performs a very important function of identify
> the wheels as per Logitech's schema.
>
> At present our (old detection) uses USB Revision to identify 'fancy'
> wheels which can be placed into native mode. This means when Logitech
> updates the hardware we are playing catch-up (this has happened 3 times
> with the DF-GT wheel so far).
>
> This newer code should automatically cope with new wheels in the future.
>
>
> Thanks to Michal for undertaking this work. I have been testing the code
> over the past weeks and believe it to be working, I will confirm mid-next
> week when I can gain access to my wheels.
>
> Simon


Attachments:
modeswitch_v1_fail.txt (10.72 kB)

2015-02-17 12:10:48

by Jiri Kosina

[permalink] [raw]
Subject: Re: [PATCH 3/4] HID: Introduce a module parameter to disable automatic switch of Logitech gaming wheels from compatibility to native mode. This only applies to multimode wheels.

On Fri, 6 Feb 2015, Michal Malý wrote:

> Introduce a module parameter to disable automatic switch of Logitech gaming
> wheels from compatibility to native mode. This only applies to multimode wheels.
>
> Signed-off-by: Michal Malý <[email protected]>
> ---
> drivers/hid/hid-lg.c | 6 ++++++
> drivers/hid/hid-lg4ff.c | 4 +++-
> 2 files changed, 9 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c
> index f91ff14..8dae03f 100644
> --- a/drivers/hid/hid-lg.c
> +++ b/drivers/hid/hid-lg.c
> @@ -818,4 +818,10 @@ static struct hid_driver lg_driver = {
> };
> module_hid_driver(lg_driver);
>
> +#ifdef CONFIG_LOGIWHEELS_FF
> +extern int lg4ff_no_autoswitch; /* From hid-lg4ff.c */
> +module_param_named(lg4ff_no_autoswitch, lg4ff_no_autoswitch, int, S_IRUGO);
> +MODULE_PARM_DESC(lg4ff_no_autoswitch, "Do not switch multimode wheels to their native mode automatically");
> +#endif
> +
> MODULE_LICENSE("GPL");
> diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
> index b9c9fe6..cbb000a 100644
> --- a/drivers/hid/hid-lg4ff.c
> +++ b/drivers/hid/hid-lg4ff.c
> @@ -67,6 +67,7 @@
> #define LG4FF_FFEX_REV_MAJ 0x21
> #define LG4FF_FFEX_REV_MIN 0x00
>
> +int lg4ff_no_autoswitch = 0;

This sharing of variable without header file is ugly. Could you please
declare it properly as extern in logitech header?

--
Jiri Kosina
SUSE Labs

2015-02-17 12:12:10

by Jiri Kosina

[permalink] [raw]
Subject: Re: [PATCH 1/4] HID: Identify Logitech gaming wheels in compatibility modes accordingly to Logitech specifications

On Fri, 6 Feb 2015, Michal Malý wrote:

> Identify Logitech gaming wheels in compatibility modes accordingly to Logitech
> specifications.
>
> Logitech specification contains a general method of identifying various
> models of their gaming wheels while they are in "compatibility" mode.
> This patch implements the method instead of checking against known
> values of bcdDevice. Handling of the mode switch upon initialization is
> also adjusted so that the driver does not have to go through the entire
> initialization routine because the wheels are set to perform a USB
> detach before they reappear in "native" mode.
>
> Signed-off-by: Michal Malý <[email protected]>
[ ... snip ...
> +u16 lg4ff_identify_multimode_wheel(struct hid_device *hid, const u16 reported_product_id, const u16 bcdDevice)

This should be static.

> +{
> + const struct lg4ff_wheel_ident_checklist *checklist;
> + int i, from_idx, to_idx;
> +
> + switch (reported_product_id) {
> + case USB_DEVICE_ID_LOGITECH_WHEEL:
> + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
> + checklist = &lg4ff_main_checklist;
> + from_idx = 0;
> + to_idx = checklist->count - 1;
> + break;
> + case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
> + checklist = &lg4ff_main_checklist;
> + from_idx = 0;
> + to_idx = checklist->count - 2; /* End identity check at G25 */
> + break;
> + case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
> + checklist = &lg4ff_main_checklist;
> + from_idx = 1; /* Start identity check at G27 */
> + to_idx = checklist->count - 3; /* End identity check at G27 */
> + break;
> + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
> + checklist = &lg4ff_main_checklist;
> + from_idx = 0;
> + to_idx = checklist->count - 4; /* End identity check at DFGT */
> + break;
> + default:
> + return 0;
> + }
> +
> + for (i = from_idx; i <= to_idx; i++) {
> + const u16 mask = checklist->models[i]->mask;
> + const u16 result = checklist->models[i]->result;
> + const u16 real_product_id = checklist->models[i]->real_product_id;
> +
> + if ((bcdDevice & mask) == result) {
> + dbg_hid("Found wheel with real PID %X whose reported PID is %X\n", real_product_id, reported_product_id);
> + return real_product_id;
> + }
> + }
> +
> + /* No match found. This is an unknown wheel model, do not touch it */
> + dbg_hid("Wheel with bcdDevice %X was not recognized as multimode wheel, leaving in its current mode\n", bcdDevice);
> + return 0;
> +}
> +
> +int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_product_id, const u16 bcdDevice)

And this as well.

Otherwise the patchset looks good, please resend with these major things
fixed and I'll apply it.

Thanks,

--
Jiri Kosina
SUSE Labs