Skip to content

Commit f31a2de

Browse files
MadCatXJiri Kosina
authored andcommitted
HID: hid-lg4ff: Allow switching of Logitech gaming wheels between compatibility modes
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]> Tested-by: Simon Wood <[email protected]> Signed-off-by: Jiri Kosina <[email protected]>
1 parent a54dc77 commit f31a2de

File tree

2 files changed

+201
-30
lines changed

2 files changed

+201
-30
lines changed

Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,32 @@ Description: Displays a set of alternate modes supported by a wheel. Each
1515
Tag: Mode Name
1616
Currently active mode is marked with an asterisk. List also
1717
contains an abstract item "native" which always denotes the
18-
native mode of the wheel.
18+
native mode of the wheel. Echoing the mode tag switches the
19+
wheel into the corresponding mode. Depending on the exact model
20+
of the wheel not all listed modes might always be selectable.
21+
If a wheel cannot be switched into the desired mode, -EINVAL
22+
is returned accompanied with an explanatory message in the
23+
kernel log.
24+
This entry is not created for devices that have only one mode.
25+
26+
Currently supported mode switches:
27+
Driving Force Pro:
28+
DF-EX --> DFP
29+
30+
G25:
31+
DF-EX --> DFP --> G25
32+
33+
G27:
34+
DF-EX <*> DFP <-> G25 <-> G27
35+
DF-EX <*--------> G25 <-> G27
36+
DF-EX <*----------------> G27
37+
38+
DFGT:
39+
DF-EX <*> DFP <-> DFGT
40+
DF-EX <*--------> DFGT
41+
42+
* hid_logitech module must be loaded with lg4ff_no_autoswitch=1
43+
parameter set in order for the switch to DF-EX mode to work.
1944

2045
What: /sys/bus/hid/drivers/logitech/<dev>/real_id
2146
Date: Feb 2015

drivers/hid/hid-lg4ff.c

Lines changed: 175 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -201,26 +201,47 @@ static const struct lg4ff_wheel_ident_checklist lg4ff_main_checklist = {
201201
};
202202

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

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

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

220-
static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_g27 = {
223+
static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfgt = {
221224
2,
222-
{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */
223-
0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* 2nd command */
225+
{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
226+
0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DFGT with detach */
227+
};
228+
229+
static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g27 = {
230+
2,
231+
{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
232+
0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* Switch mode to G27 with detach */
233+
};
234+
235+
/* EXT_CMD1 - Understood by DFP, G25, G27 and DFGT */
236+
static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext01_dfp = {
237+
1,
238+
{0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
239+
};
240+
241+
/* EXT_CMD16 - Understood by G25 and G27 */
242+
static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext16_g25 = {
243+
1,
244+
{0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
224245
};
225246

226247
/* Recalculates X axis value accordingly to currently selected range */
@@ -489,6 +510,63 @@ static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range)
489510
hid_hw_request(hid, report, HID_REQ_SET_REPORT);
490511
}
491512

513+
static const struct lg4ff_compat_mode_switch *lg4ff_get_mode_switch_command(const u16 real_product_id, const u16 target_product_id)
514+
{
515+
switch (real_product_id) {
516+
case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
517+
switch (target_product_id) {
518+
case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
519+
return &lg4ff_mode_switch_ext01_dfp;
520+
/* DFP can only be switched to its native mode */
521+
default:
522+
return NULL;
523+
}
524+
break;
525+
case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
526+
switch (target_product_id) {
527+
case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
528+
return &lg4ff_mode_switch_ext01_dfp;
529+
case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
530+
return &lg4ff_mode_switch_ext16_g25;
531+
/* G25 can only be switched to DFP mode or its native mode */
532+
default:
533+
return NULL;
534+
}
535+
break;
536+
case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
537+
switch (target_product_id) {
538+
case USB_DEVICE_ID_LOGITECH_WHEEL:
539+
return &lg4ff_mode_switch_ext09_dfex;
540+
case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
541+
return &lg4ff_mode_switch_ext09_dfp;
542+
case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
543+
return &lg4ff_mode_switch_ext09_g25;
544+
case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
545+
return &lg4ff_mode_switch_ext09_g27;
546+
/* G27 can only be switched to DF-EX, DFP, G25 or its native mode */
547+
default:
548+
return NULL;
549+
}
550+
break;
551+
case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
552+
switch (target_product_id) {
553+
case USB_DEVICE_ID_LOGITECH_WHEEL:
554+
return &lg4ff_mode_switch_ext09_dfex;
555+
case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
556+
return &lg4ff_mode_switch_ext09_dfp;
557+
case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
558+
return &lg4ff_mode_switch_ext09_dfgt;
559+
/* DFGT can only be switched to DF-EX, DFP or its native mode */
560+
default:
561+
return NULL;
562+
}
563+
break;
564+
/* No other wheels have multiple modes */
565+
default:
566+
return NULL;
567+
}
568+
}
569+
492570
static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct lg4ff_compat_mode_switch *s)
493571
{
494572
struct usb_device *usbdev = hid_to_usb_dev(hid);
@@ -558,7 +636,87 @@ static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attr
558636

559637
static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
560638
{
561-
return -ENOSYS;
639+
struct hid_device *hid = to_hid_device(dev);
640+
struct lg4ff_device_entry *entry;
641+
struct lg_drv_data *drv_data;
642+
const struct lg4ff_compat_mode_switch *s;
643+
u16 target_product_id = 0;
644+
int i, ret;
645+
char *lbuf;
646+
647+
drv_data = hid_get_drvdata(hid);
648+
if (!drv_data) {
649+
hid_err(hid, "Private driver data not found!\n");
650+
return -EINVAL;
651+
}
652+
653+
entry = drv_data->device_props;
654+
if (!entry) {
655+
hid_err(hid, "Device properties not found!\n");
656+
return -EINVAL;
657+
}
658+
659+
/* Allow \n at the end of the input parameter */
660+
lbuf = kasprintf(GFP_KERNEL, "%s", buf);
661+
if (!lbuf)
662+
return -ENOMEM;
663+
664+
i = strlen(lbuf);
665+
if (lbuf[i-1] == '\n') {
666+
if (i == 1) {
667+
kfree(lbuf);
668+
return -EINVAL;
669+
}
670+
lbuf[i-1] = '\0';
671+
}
672+
673+
for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
674+
const u16 mode_product_id = lg4ff_alternate_modes[i].product_id;
675+
const char *tag = lg4ff_alternate_modes[i].tag;
676+
677+
if (entry->alternate_modes & BIT(i)) {
678+
if (!strcmp(tag, lbuf)) {
679+
if (!mode_product_id)
680+
target_product_id = entry->real_product_id;
681+
else
682+
target_product_id = mode_product_id;
683+
break;
684+
}
685+
}
686+
}
687+
688+
if (i == LG4FF_MODE_MAX_IDX) {
689+
hid_info(hid, "Requested mode \"%s\" is not supported by the device\n", lbuf);
690+
kfree(lbuf);
691+
return -EINVAL;
692+
}
693+
kfree(lbuf); /* Not needed anymore */
694+
695+
if (target_product_id == entry->product_id) /* Nothing to do */
696+
return count;
697+
698+
/* Automatic switching has to be disabled for the switch to DF-EX mode to work correctly */
699+
if (target_product_id == USB_DEVICE_ID_LOGITECH_WHEEL && !lg4ff_no_autoswitch) {
700+
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",
701+
entry->real_name);
702+
return -EINVAL;
703+
}
704+
705+
/* Take care of hardware limitations */
706+
if ((entry->real_product_id == USB_DEVICE_ID_LOGITECH_DFP_WHEEL || entry->real_product_id == USB_DEVICE_ID_LOGITECH_G25_WHEEL) &&
707+
entry->product_id > target_product_id) {
708+
hid_info(hid, "\"%s\" cannot be switched back into \"%s\" mode\n", entry->real_name, lg4ff_alternate_modes[i].name);
709+
return -EINVAL;
710+
}
711+
712+
s = lg4ff_get_mode_switch_command(entry->real_product_id, target_product_id);
713+
if (!s) {
714+
hid_err(hid, "Invalid target product ID %X\n", target_product_id);
715+
return -EINVAL;
716+
}
717+
718+
ret = lg4ff_switch_compatibility_mode(hid, s);
719+
return (ret == 0 ? count : ret);
562720
}
563721
static DEVICE_ATTR(alternate_modes, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_alternate_modes_show, lg4ff_alternate_modes_store);
564722

@@ -783,7 +941,8 @@ static u16 lg4ff_identify_multimode_wheel(struct hid_device *hid, const u16 repo
783941
}
784942
}
785943

786-
/* No match found. This is an unknown wheel model, do not touch it */
944+
/* No match found. This is either Driving Force or an unknown
945+
* wheel model, do not touch it */
787946
dbg_hid("Wheel with bcdDevice %X was not recognized as multimode wheel, leaving in its current mode\n", bcdDevice);
788947
return 0;
789948
}
@@ -806,22 +965,9 @@ static int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_produc
806965
if (reported_product_id == USB_DEVICE_ID_LOGITECH_WHEEL &&
807966
reported_product_id != *real_product_id &&
808967
!lg4ff_no_autoswitch) {
809-
const struct lg4ff_compat_mode_switch *s;
968+
const struct lg4ff_compat_mode_switch *s = lg4ff_get_mode_switch_command(*real_product_id, *real_product_id);
810969

811-
switch (*real_product_id) {
812-
case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
813-
s = &lg4ff_mode_switch_dfp;
814-
break;
815-
case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
816-
s = &lg4ff_mode_switch_g25;
817-
break;
818-
case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
819-
s = &lg4ff_mode_switch_g27;
820-
break;
821-
case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
822-
s = &lg4ff_mode_switch_dfgt;
823-
break;
824-
default:
970+
if (!s) {
825971
hid_err(hid, "Invalid product id %X\n", *real_product_id);
826972
return LG4FF_MMODE_NOT_MULTIMODE;
827973
}

0 commit comments

Comments
 (0)