[PATCH 3/3] drivers: hid: G940 FF structure & autocenter fixes

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Disable default auto-centering of the Logitech G940, which otherwise
makes the stick difficult to use. Also when autocenter is requested, obey
the magnitude.

The auto-centering effect will only unlock (or become the requested
level) when an application starts using the joystick and the hand sensor
on the stick is covered. That appears to be a hardware limitation.

To make the above and future work on spring/damper support simpler and
more type-safe, add a struct of the HID report. Many thanks to fred41 from
forums.x-plane.org for useful information towards that.

It seems byte 0 does not need to be a "command byte" of 0x51; it can be
anything and is the least-significant byte of the constant force.

Signed-off-by: Chris Boyle <chris@xxxxxxxxxx>
---
 drivers/hid/hid-lg3ff.c | 148 +++++++++++++++++++++-------------------
 1 file changed, 78 insertions(+), 70 deletions(-)

diff --git a/drivers/hid/hid-lg3ff.c b/drivers/hid/hid-lg3ff.c
index 8c2da183d3bc..6fcd449d5b46 100644
--- a/drivers/hid/hid-lg3ff.c
+++ b/drivers/hid/hid-lg3ff.c
@@ -2,6 +2,7 @@
  *  Force feedback support for Logitech Flight System G940
  *
  *  Copyright (c) 2009 Gary Stein <LordCnidarian@xxxxxxxxx>
+ *  Copyright (c) 2019 Chris Boyle
  */
 
 /*
@@ -26,51 +27,68 @@
 
 #include "hid-lg.h"
 
-/*
- * G940 Theory of Operation (from experimentation)
- *
- * There are 63 fields (only 3 of them currently used)
- * 0 - seems to be command field
- * 1 - 30 deal with the x axis
- * 31 -60 deal with the y axis
- *
- * Field 1 is x axis constant force
- * Field 31 is y axis constant force
- *
- * other interesting fields 1,2,3,4 on x axis
- * (same for 31,32,33,34 on y axis)
- *
- * 0 0 127 127 makes the joystick autocenter hard
- *
- * 127 0 127 127 makes the joystick loose on the right,
- * but stops all movemnt left
- *
- * -127 0 -127 -127 makes the joystick loose on the left,
- * but stops all movement right
- *
- * 0 0 -127 -127 makes the joystick rattle very hard
- *
- * I'm sure these are effects that I don't know enough about them
- */
+/* Ensure we remember to swap bytes (there's no sle16) */
+typedef __s16 __bitwise lg3_s16;
 
-struct lg3ff_device {
-	struct hid_report *report;
-};
+static inline lg3_s16 lg3ff_cpu_to_sle16(s16 val)
+{
+	return (__force lg3_s16)cpu_to_le16(val);
+}
+
+struct hid_lg3ff_axis {
+	lg3_s16	constant_force;  /* can cancel autocenter on relevant side */
+	u8	_padding0;  /* extra byte of strength? no apparent effect */
+	/* how far towards center does the effect keep pushing:
+	 * 0   = no autocenter, up to:
+	 * 127 = push immediately on any deflection
+	 * <0  = repel center
+	 */
+	s8	autocenter_strength;
+	/* how hard does autocenter push */
+	s8	autocenter_force;
+	/* damping with force of autocenter_force (see also damper_*) */
+	s8	autocenter_damping;
+	lg3_s16	spring_deadzone_neg;  /* for offset center, set these equal */
+	lg3_s16	spring_deadzone_pos;
+	s8	spring_coeff_neg;  /* <0 repels center */
+	s8	spring_coeff_pos;
+	lg3_s16	spring_saturation;
+	u8	_padding1[8];  /* [4-8]: a different way of autocentering? */
+	s8	damper_coeff_neg;
+	s8	damper_coeff_pos;
+	lg3_s16	damper_saturation;
+	u8	_padding2[4];  /* seems to do the same as damper*? */
+} __packed;
+
+struct hid_lg3ff_report {
+	struct hid_lg3ff_axis x;
+	struct hid_lg3ff_axis y;
+	u8	_padding[3];
+} __packed;
+
+#define FF_REPORT_ID 2
+
+static void hig_lg3ff_send(struct input_dev *idev,
+			   struct hid_lg3ff_report *raw_rep)
+{
+	struct hid_device *hid = input_get_drvdata(idev);
+	struct hid_report *hid_rep = hid->report_enum[HID_OUTPUT_REPORT]
+					 .report_id_hash[FF_REPORT_ID];
+	int i;
+
+	/* We can be called while atomic (via hid_lg3ff_play) and must queue;
+	 * there's nowhere to enqueue a raw report, so populate a hid_report.
+	 */
+	for (i = 0; i < sizeof(*raw_rep); i++)
+		hid_rep->field[0]->value[i] = ((u8 *)raw_rep)[i];
+	hid_hw_request(hid, hid_rep, HID_REQ_SET_REPORT);
+}
 
 static int hid_lg3ff_play(struct input_dev *dev, void *data,
 			 struct ff_effect *effect)
 {
-	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);
-	int x, y;
-
-/*
- * Available values in the field should always be 63, but we only use up to
- * 35. Instead, clear the entire area, however big it is.
- */
-	memset(report->field[0]->value, 0,
-	       sizeof(__s32) * report->field[0]->report_count);
+	struct hid_lg3ff_report report = {0};
+	s16 x, y;
 
 	switch (effect->type) {
 	case FF_CONSTANT:
@@ -78,46 +96,32 @@ static int hid_lg3ff_play(struct input_dev *dev, void *data,
  * Already clamped in ff_memless
  * 0 is center (different then other logitech)
  */
-		x = effect->u.ramp.start_level;
-		y = effect->u.ramp.end_level;
-
-		/* send command byte */
-		report->field[0]->value[0] = 0x51;
+		x = -effect->u.ramp.start_level << 8;
+		y = -effect->u.ramp.end_level << 8;
 
 /*
  * Sign backwards from other Force3d pro
  * which get recast here in two's complement 8 bits
  */
-		report->field[0]->value[1] = (unsigned char)(-x);
-		report->field[0]->value[31] = (unsigned char)(-y);
-
-		hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+		report.x.constant_force = lg3ff_cpu_to_sle16(x);
+		report.y.constant_force = lg3ff_cpu_to_sle16(y);
+		hig_lg3ff_send(dev, &report);
 		break;
 	}
 	return 0;
 }
 static void hid_lg3ff_set_autocenter(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);
+	struct hid_lg3ff_report report = {0};
 
-/*
- * Auto Centering probed from device
- * NOTE: deadman's switch on G940 must be covered
- * for effects to work
- */
-	report->field[0]->value[0] = 0x51;
-	report->field[0]->value[1] = 0x00;
-	report->field[0]->value[2] = 0x00;
-	report->field[0]->value[3] = 0x7F;
-	report->field[0]->value[4] = 0x7F;
-	report->field[0]->value[31] = 0x00;
-	report->field[0]->value[32] = 0x00;
-	report->field[0]->value[33] = 0x7F;
-	report->field[0]->value[34] = 0x7F;
-
-	hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+	/* negative means repel from center, so scale to 0-127 */
+	s8 mag_scaled = magnitude >> 9;
+
+	report.x.autocenter_strength = 127;
+	report.x.autocenter_force = mag_scaled;
+	report.y.autocenter_strength = 127;
+	report.y.autocenter_force = mag_scaled;
+	hig_lg3ff_send(dev, &report);
 }
 
 
@@ -136,7 +140,9 @@ int lg3ff_init(struct hid_device *hid)
 	int i;
 
 	/* Check that the report looks ok */
-	if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 35))
+	BUILD_BUG_ON(sizeof(struct hid_lg3ff_report) != 63);  /* excl. id */
+	if (!hid_validate_values(hid, HID_OUTPUT_REPORT, FF_REPORT_ID, 0,
+				 sizeof(struct hid_lg3ff_report)))
 		return -ENODEV;
 
 	/* Assume single fixed device G940 */
@@ -147,8 +153,10 @@ int lg3ff_init(struct hid_device *hid)
 	if (error)
 		return error;
 
-	if (test_bit(FF_AUTOCENTER, dev->ffbit))
+	if (test_bit(FF_AUTOCENTER, dev->ffbit)) {
 		dev->ff->set_autocenter = hid_lg3ff_set_autocenter;
+		hid_lg3ff_set_autocenter(dev, 0);
+	}
 
 	hid_info(hid, "Force feedback for Logitech Flight System G940 by Gary Stein <LordCnidarian@xxxxxxxxx>\n");
 	return 0;
-- 
2.17.1


-- 
Chris Boyle
https://chris.boyle.name/



[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux