diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index a756ee6..79ba659 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -433,6 +433,7 @@
 #define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL	0xc295
 #define USB_DEVICE_ID_LOGITECH_DFP_WHEEL	0xc298
 #define USB_DEVICE_ID_LOGITECH_G25_WHEEL	0xc299
+#define USB_DEVICE_ID_LOGITECH_DFGT_WHEEL	0xc29a
 #define USB_DEVICE_ID_LOGITECH_G27_WHEEL	0xc29b
 #define USB_DEVICE_ID_LOGITECH_WII_WHEEL	0xc29c
 #define USB_DEVICE_ID_LOGITECH_ELITE_KBD	0xc30a
diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c
index a15ce9e..52e8d65 100644
--- a/drivers/hid/hid-lg.c
+++ b/drivers/hid/hid-lg.c
@@ -452,6 +452,8 @@ static const struct hid_device_id lg_devices[] = {
 		.driver_data = LG_FF },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFP_WHEEL),
 		.driver_data = LG_NOGET | LG_FF },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFGT_WHEEL),
+		.driver_data = LG_FF },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WII_WHEEL),
 		.driver_data = LG_FF4 },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FFG ),
-- 
1.7.6

From 7992434338d39959b26cdc1bed39a2a0f2afd59b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michal=20Mal=C3=BD?= <madcatxster@gmail.com>
Date: Wed, 6 Jul 2011 00:36:10 +0200
Subject: [PATCH 3/3] - Moved all Logitech wheel force feedback handling to
 lg4ff driver - Added basic sysfs support to se wheel's
 range (if supported). Currently   available for Driving
 Force pro only

---
 drivers/hid/hid-lg.c    |   26 +++++---
 drivers/hid/hid-lg.h    |    2 +
 drivers/hid/hid-lg4ff.c |  181 +++++++++++++++++++++++++++++++++++++++++------
 drivers/hid/hid-lgff.c  |    7 --
 4 files changed, 179 insertions(+), 37 deletions(-)

diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c
index 52e8d65..be77789 100644
--- a/drivers/hid/hid-lg.c
+++ b/drivers/hid/hid-lg.c
@@ -362,7 +362,7 @@ static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id)
 		goto err_free;
 	}
 
-	if (quirks & (LG_FF | LG_FF2 | LG_FF3))
+	if (quirks & (LG_FF | LG_FF2 | LG_FF3 | LG_FF4))
 		connect_mask &= ~HID_CONNECT_FF;
 
 	ret = hid_hw_start(hdev, connect_mask);
@@ -371,7 +371,8 @@ static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id)
 		goto err_free;
 	}
 
-	if (quirks & LG_FF4) {
+	/* Setup wireless link with Logitech Wii wheel */
+	if(hdev->product == USB_DEVICE_ID_LOGITECH_WII_WHEEL) {
 		unsigned char buf[] = { 0x00, 0xAF,  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
 
 		ret = hdev->hid_output_raw_report(hdev, buf, sizeof(buf), HID_FEATURE_REPORT);
@@ -404,6 +405,12 @@ err_free:
 	return ret;
 }
 
+static void lg_remove(struct hid_device *hdev)
+{
+	if(hdev->quirks & LG_FF4)
+		lg4ff_deinit(hdev);
+}
+
 static const struct hid_device_id lg_devices[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER),
 		.driver_data = LG_RDESC | LG_WIRELESS },
@@ -430,7 +437,7 @@ static const struct hid_device_id lg_devices[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_EXTREME_3D),
 		.driver_data = LG_NOGET },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WHEEL),
-		.driver_data = LG_NOGET | LG_FF },
+		.driver_data = LG_NOGET | LG_FF4 },
 
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD),
 		.driver_data = LG_FF2 },
@@ -443,17 +450,17 @@ static const struct hid_device_id lg_devices[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FORCE3D_PRO),
 		.driver_data = LG_FF },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL),
-		.driver_data = LG_FF },
+		.driver_data = LG_FF4 },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2),
-		.driver_data = LG_FF },
+		.driver_data = LG_FF4 },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G25_WHEEL),
-		.driver_data = LG_FF },
+		.driver_data = LG_FF4 },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G27_WHEEL),
-		.driver_data = LG_FF },
+		.driver_data = LG_FF4 },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFP_WHEEL),
-		.driver_data = LG_NOGET | LG_FF },
+		.driver_data = LG_NOGET | LG_FF4 },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFGT_WHEEL),
-		.driver_data = LG_FF },
+		.driver_data = LG_FF4 },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WII_WHEEL),
 		.driver_data = LG_FF4 },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FFG ),
@@ -479,6 +486,7 @@ static struct hid_driver lg_driver = {
 	.input_mapped = lg_input_mapped,
 	.event = lg_event,
 	.probe = lg_probe,
+	.remove = lg_remove
 };
 
 static int __init lg_init(void)
diff --git a/drivers/hid/hid-lg.h b/drivers/hid/hid-lg.h
index b0100ba..3a32959 100644
--- a/drivers/hid/hid-lg.h
+++ b/drivers/hid/hid-lg.h
@@ -21,8 +21,10 @@ static inline int lg3ff_init(struct hid_device *hdev) { return -1; }
 
 #ifdef CONFIG_LOGIWII_FF
 int lg4ff_init(struct hid_device *hdev);
+int lg4ff_deinit(struct hid_device *hdev);
 #else
 static inline int lg4ff_init(struct hid_device *hdev) { return -1; }
+static inline int lg4ff_deinit(struct hid_device *hdev) { return -1; }
 #endif
 
 #endif
diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
index fa550c8..b3bcf06 100644
--- a/drivers/hid/hid-lg4ff.c
+++ b/drivers/hid/hid-lg4ff.c
@@ -29,19 +29,43 @@
 
 #include "usbhid/usbhid.h"
 #include "hid-lg.h"
+#include "hid-ids.h"
 
-struct lg4ff_device {
-	struct hid_report *report;
-};
+#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)
+
+unsigned short ff4_wheel_range;
+
+static void hid_lg4ff_set_autocenter_default(struct input_dev *hid, u16 magnitude);
+void (*hid_ff4_set_range)(struct hid_device *hid, u16 range);
+static ssize_t ff4_range_show(struct device *dev, struct device_attribute *attr, char *buf);
+static ssize_t ff4_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
+
+static DEVICE_ATTR(range_set, S_IWUSR | S_IRUGO, ff4_range_show, ff4_range_store);
 
-static const signed short ff4_wheel_ac[] = {
+static const signed short ff4_wheel_effects[] = {
 	FF_CONSTANT,
 	FF_AUTOCENTER,
 	-1
 };
 
-static int hid_lg4ff_play(struct input_dev *dev, void *data,
-			 struct ff_effect *effect)
+struct ff4_wheel_device {
+	__u32 product_id;
+	const signed short *ff_effects;
+	void (*set_autocenter)(struct input_dev *dev, u16 magnitude);
+	const int adj_range;	/* 0 - Not available, 1 - G2x/DFGT command, 2 - DFP command */
+};
+
+static const struct ff4_wheel_device ff4_devices[] = {
+	{USB_DEVICE_ID_LOGITECH_WHEEL,       ff4_wheel_effects, hid_lg4ff_set_autocenter_default, 0},
+	{USB_DEVICE_ID_LOGITECH_MOMO_WHEEL,  ff4_wheel_effects, hid_lg4ff_set_autocenter_default, 0},
+	{USB_DEVICE_ID_LOGITECH_DFP_WHEEL,   ff4_wheel_effects, hid_lg4ff_set_autocenter_default, 2},
+	{USB_DEVICE_ID_LOGITECH_G25_WHEEL,   ff4_wheel_effects, hid_lg4ff_set_autocenter_default, 1},
+	{USB_DEVICE_ID_LOGITECH_DFGT_WHEEL,  ff4_wheel_effects, hid_lg4ff_set_autocenter_default, 1},
+	{USB_DEVICE_ID_LOGITECH_G27_WHEEL,   ff4_wheel_effects, hid_lg4ff_set_autocenter_default, 1},
+	{USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, ff4_wheel_effects, hid_lg4ff_set_autocenter_default, 0}
+};
+
+static int hid_lg4ff_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;
@@ -55,11 +79,11 @@ static int hid_lg4ff_play(struct input_dev *dev, void *data,
 		x = effect->u.ramp.start_level + 0x80;	/* 0x80 is no force */
 		CLAMP(x);
 		report->field[0]->value[0] = 0x11;	/* Slot 1 */
-		report->field[0]->value[1] = 0x10;
+		report->field[0]->value[1] = 0x08;
 		report->field[0]->value[2] = x;
-		report->field[0]->value[3] = 0x00;
+		report->field[0]->value[3] = 0x80;
 		report->field[0]->value[4] = 0x00;
-		report->field[0]->value[5] = 0x08;
+		report->field[0]->value[5] = 0x00;
 		report->field[0]->value[6] = 0x00;
 		dbg_hid("Autocenter, x=0x%02X\n", x);
 
@@ -69,7 +93,9 @@ static int hid_lg4ff_play(struct input_dev *dev, void *data,
 	return 0;
 }
 
-static void hid_lg4ff_set_autocenter(struct input_dev *dev, u16 magnitude)
+/* Sends default autocentering command compatible with
+ * all wheels except Formula Force EX */
+static void hid_lg4ff_set_autocenter_default(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;
@@ -78,15 +104,88 @@ static void hid_lg4ff_set_autocenter(struct input_dev *dev, u16 magnitude)
 
 	*value++ = 0xfe;
 	*value++ = 0x0d;
-	*value++ = 0x07;
-	*value++ = 0x07;
-	*value++ = (magnitude >> 8) & 0xff;
+	*value++ = magnitude >> 13;
+	*value++ = magnitude >> 13;
+	*value++ = magnitude >> 8;
 	*value++ = 0x00;
 	*value = 0x00;
 
 	usbhid_submit_report(hid, report, USB_DIR_OUT);
 }
 
+/* Sends commands to set range compatible with Driving Force Pro wheel */
+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);
+	int start_left, start_right, full_range;
+	dbg_hid("Driving Force Pro: setting range to %u\n", range);
+	
+	/* Prepare "coarse" limit command */
+	report->field[0]->value[0] = 0xf8;
+	report->field[0]->value[1] = 0x00; 	/* Set later */
+	report->field[0]->value[2] = 0x00;
+	report->field[0]->value[3] = 0x00;
+	report->field[0]->value[4] = 0x00;
+	report->field[0]->value[5] = 0x00;
+	report->field[0]->value[6] = 0x00;
+    
+	if(range > 200) {
+		report->field[0]->value[1] = 0x03;
+		full_range = 900;
+	} else {
+		report->field[0]->value[1] = 0x02;
+		full_range = 200;
+	}
+	usbhid_submit_report(hid, report, USB_DIR_OUT);
+	
+	/* Prepare "fine" limit command */
+	report->field[0]->value[0] = 0x81;
+	report->field[0]->value[1] = 0x0b;
+	report->field[0]->value[2] = 0x00;
+	report->field[0]->value[3] = 0x00;
+	report->field[0]->value[4] = 0x00;
+	report->field[0]->value[5] = 0x00;
+	report->field[0]->value[6] = 0x00;
+	
+	if(range == 200 || range == 900) {	/* Do not apply any fine limit */
+		usbhid_submit_report(hid, report, USB_DIR_OUT);
+		ff4_wheel_range = range;
+		return;
+	}
+
+	/* Construct range limiting command */
+	start_left = (((full_range - range + 1) * 2047) / full_range);
+	start_right = 0xfff - start_left;
+
+	report->field[0]->value[2] = start_left >> 4;
+	report->field[0]->value[3] = start_right >> 4;
+	report->field[0]->value[4] = 0xff;
+	report->field[0]->value[5] = (start_right & 0xe) << 4 | (start_left & 0xe);
+	report->field[0]->value[6] = 0xff;
+
+	usbhid_submit_report(hid, report, USB_DIR_OUT);
+	ff4_wheel_range = range;
+}
+
+/* Read current range and display it in terminal */
+static ssize_t ff4_range_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	return scnprintf(buf, PAGE_SIZE, "%d\n", ff4_wheel_range);
+}
+
+/* Set range to user specified value, call appropriate function
+ * according to the type of the wheel */
+static ssize_t ff4_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	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_ff4_set_range != 0)
+		hid_ff4_set_range(to_hid_device(dev), range);
+
+	return count;
+}
 
 int lg4ff_init(struct hid_device *hid)
 {
@@ -95,9 +194,7 @@ int lg4ff_init(struct hid_device *hid)
 	struct input_dev *dev = hidinput->input;
 	struct hid_report *report;
 	struct hid_field *field;
-	const signed short *ff_bits = ff4_wheel_ac;
-	int error;
-	int i;
+	int error, i, j;
 
 	/* Find the report to use */
 	if (list_empty(report_list)) {
@@ -117,19 +214,61 @@ int lg4ff_init(struct hid_device *hid)
 		hid_err(hid, "NULL field\n");
 		return -1;
 	}
-
-	for (i = 0; ff_bits[i] >= 0; i++)
-		set_bit(ff_bits[i], dev->ffbit);
+	
+	/* Check what model of wheel has been connected */
+	for(i = 0; i < ARRAY_SIZE(ff4_devices); i++) {
+		if(hid->product == ff4_devices[i].product_id) {
+			dbg_hid("Found compatible device %04X\n", ff4_devices[i].product_id);
+			break;
+		}
+	}
+	
+	if(i == ARRAY_SIZE(ff4_devices)) {
+		hid_info(hid, "Device not supported yet\n");
+		return -1;
+	}
+	
+	/* Set supported force feedback capabilities */
+	for (j = 0; ff4_devices[i].ff_effects[j] >= 0; j++)
+		set_bit(ff4_devices[i].ff_effects[j], dev->ffbit);
 
 	error = input_ff_create_memless(dev, NULL, hid_lg4ff_play);
 
 	if (error)
 		return error;
 
-	if (test_bit(FF_AUTOCENTER, dev->ffbit))
-		dev->ff->set_autocenter = hid_lg4ff_set_autocenter;
+	/* Check if autocentering is available and
+	 * disable it by default */
+	if (test_bit(FF_AUTOCENTER, dev->ffbit)) {
+		dev->ff->set_autocenter = ff4_devices[i].set_autocenter;
+		dev->ff->set_autocenter(dev, (u16)0);
+	}
+
+	/* Check if the wheel supports range adjustment, if it does,
+	 * set appropirate range setting function. */
+	switch(ff4_devices[i].adj_range) {
+		case 2:
+			hid_ff4_set_range = hid_lg4ff_set_range_dfp;
+			break;
+		default:
+			hid_ff4_set_range = 0;
+	}
 
+	/* Create /sys interface */
+	if(ff4_devices[i].adj_range > 0) {
+		error = device_create_file(&hid->dev, &dev_attr_range_set);
+		if(error)
+			return error;
+		dbg_hid("/sys interface created\n");
+		hid_ff4_set_range(hid, (u16)900);
+	}
+	
 	hid_info(hid, "Force feedback for Logitech Speed Force Wireless by Simon Wood <simon@mungewell.org>\n");
 	return 0;
 }
 
+int lg4ff_deinit(struct hid_device *hid)
+{
+	device_remove_file(&hid->dev, &dev_attr_range_set);
+	return 0;
+}
diff --git a/drivers/hid/hid-lgff.c b/drivers/hid/hid-lgff.c
index 088f850..a53175e 100644
--- a/drivers/hid/hid-lgff.c
+++ b/drivers/hid/hid-lgff.c
@@ -71,14 +71,7 @@ static const struct dev_type devices[] = {
 	{ 0x046d, 0xc286, ff_joystick_ac },
 	{ 0x046d, 0xc287, ff_joystick_ac },
 	{ 0x046d, 0xc293, ff_joystick },
-	{ 0x046d, 0xc294, ff_wheel },
-	{ 0x046d, 0xc298, ff_wheel },
-	{ 0x046d, 0xc299, ff_wheel },
-	{ 0x046d, 0xc29b, ff_wheel },
 	{ 0x046d, 0xc295, ff_joystick },
-	{ 0x046d, 0xc298, ff_wheel },
-	{ 0x046d, 0xc299, ff_wheel },
-	{ 0x046d, 0xca03, ff_wheel },
 };
 
 static int hid_lgff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
