Commit 2b24a960 authored by Michal Malý's avatar Michal Malý Committed by Jiri Kosina

HID: hid-lg4ff: Adjust X axis input value accordingly to selected range.

Range limiting command for the Driving Force Pro wheel is only a FF_SPRING
effect so that the wheel creates resistance when the user tries to turn it past
the limit. It is however possible to overpower the FFB motors quite easily which
leads to the X axis value exceeding the expected limit. This confuses
games which dynamically adjust calibration using the highest/lowest min and max
values reported by the wheel. Joydev device driver also doesn't take in account
any changes in an axis range after the joystick device is created.

This patch recalculates received ABS_X axis value so it is always in
<0; 16383> range where 0 is the left limit and 16383 the right limit.
Logitech driver for Windows does the same thing.  As for any concerns about
possible loss of precision, I compared a large set of raw/adjusted values
generated by "mult_frac" to values returned by the Windows driver and I got
a 100% match.

Other Logitech wheels will probably need a similar fix, but I currently lack
the information needed to write one.
Signed-off-by: default avatarMichal Malý <madcatxster@gmail.com>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent 74479ba8
...@@ -342,6 +342,9 @@ static int lg_event(struct hid_device *hdev, struct hid_field *field, ...@@ -342,6 +342,9 @@ static int lg_event(struct hid_device *hdev, struct hid_field *field,
-value); -value);
return 1; return 1;
} }
if (drv_data->quirks & LG_FF4) {
return lg4ff_adjust_input_event(hdev, field, usage, value, drv_data);
}
return 0; return 0;
} }
......
...@@ -25,9 +25,13 @@ static inline int lg3ff_init(struct hid_device *hdev) { return -1; } ...@@ -25,9 +25,13 @@ static inline int lg3ff_init(struct hid_device *hdev) { return -1; }
#endif #endif
#ifdef CONFIG_LOGIWHEELS_FF #ifdef CONFIG_LOGIWHEELS_FF
int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
struct hid_usage *usage, __s32 value, struct lg_drv_data *drv_data);
int lg4ff_init(struct hid_device *hdev); int lg4ff_init(struct hid_device *hdev);
int lg4ff_deinit(struct hid_device *hdev); int lg4ff_deinit(struct hid_device *hdev);
#else #else
static inline int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
struct hid_usage *usage, __s32 value, struct lg_drv_data *drv_data) { return 0; }
static inline int lg4ff_init(struct hid_device *hdev) { return -1; } static inline int lg4ff_init(struct hid_device *hdev) { return -1; }
static inline int lg4ff_deinit(struct hid_device *hdev) { return -1; } static inline int lg4ff_deinit(struct hid_device *hdev) { return -1; }
#endif #endif
......
...@@ -53,6 +53,7 @@ static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *at ...@@ -53,6 +53,7 @@ static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *at
static DEVICE_ATTR(range, S_IRWXU | S_IRWXG | S_IRWXO, lg4ff_range_show, lg4ff_range_store); static DEVICE_ATTR(range, S_IRWXU | S_IRWXG | S_IRWXO, lg4ff_range_show, lg4ff_range_store);
struct lg4ff_device_entry { struct lg4ff_device_entry {
__u32 product_id;
__u16 range; __u16 range;
__u16 min_range; __u16 min_range;
__u16 max_range; __u16 max_range;
...@@ -129,6 +130,56 @@ static const struct lg4ff_usb_revision lg4ff_revs[] = { ...@@ -129,6 +130,56 @@ static const struct lg4ff_usb_revision lg4ff_revs[] = {
{G27_REV_MAJ, G27_REV_MIN, &native_g27}, /* G27 */ {G27_REV_MAJ, G27_REV_MIN, &native_g27}, /* G27 */
}; };
/* Recalculates X axis value accordingly to currently selected range */
static __s32 lg4ff_adjust_dfp_x_axis(__s32 value, __u16 range)
{
__u16 max_range;
__s32 new_value;
if (range == 900)
return value;
else if (range == 200)
return value;
else if (range < 200)
max_range = 200;
else
max_range = 900;
new_value = 8192 + mult_frac(value - 8192, max_range, range);
if (new_value < 0)
return 0;
else if (new_value > 16383)
return 16383;
else
return new_value;
}
int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
struct hid_usage *usage, __s32 value, struct lg_drv_data *drv_data)
{
struct lg4ff_device_entry *entry = drv_data->device_props;
__s32 new_value = 0;
if (!entry) {
hid_err(hid, "Device properties not found");
return 0;
}
switch (entry->product_id) {
case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
switch (usage->code) {
case ABS_X:
new_value = lg4ff_adjust_dfp_x_axis(value, entry->range);
input_event(field->hidinput->input, usage->type, usage->code, new_value);
return 1;
default:
return 0;
}
default:
return 0;
}
}
static int hid_lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect) static int hid_lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
{ {
struct hid_device *hid = input_get_drvdata(dev); struct hid_device *hid = input_get_drvdata(dev);
...@@ -531,6 +582,7 @@ int lg4ff_init(struct hid_device *hid) ...@@ -531,6 +582,7 @@ int lg4ff_init(struct hid_device *hid)
} }
drv_data->device_props = entry; drv_data->device_props = entry;
entry->product_id = lg4ff_devices[i].product_id;
entry->min_range = lg4ff_devices[i].min_range; entry->min_range = lg4ff_devices[i].min_range;
entry->max_range = lg4ff_devices[i].max_range; entry->max_range = lg4ff_devices[i].max_range;
entry->set_range = lg4ff_devices[i].set_range; entry->set_range = lg4ff_devices[i].set_range;
...@@ -601,6 +653,8 @@ int lg4ff_init(struct hid_device *hid) ...@@ -601,6 +653,8 @@ int lg4ff_init(struct hid_device *hid)
return 0; return 0;
} }
int lg4ff_deinit(struct hid_device *hid) int lg4ff_deinit(struct hid_device *hid)
{ {
struct lg4ff_device_entry *entry; struct lg4ff_device_entry *entry;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment