All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] HID: Force feedback support for the Logitech G920 Wheel
@ 2015-11-21 12:37 Edwin Velds
  2015-11-21 12:37 ` [PATCH 1/2] HID: Create header for Logitech HID++ driver Edwin Velds
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Edwin Velds @ 2015-11-21 12:37 UTC (permalink / raw)
  To: linux-input
  Cc: linux-kernel, Simon Wood, Jiri Kosina, Michal Malý,
	elias vanderstuyft, Benjamin Tissoires, Dmitry Torokhov,
	Edwin Velds

This patch implements force feedback support for the Logitech G920 Driving Force
Racing Wheel.

This patch is based on the basic G920 support patch by Simon Wood:
http://www.spinics.net/lists/linux-input/msg42174.html

The first patch splits and renames the HID++ driver (hid-logitech-hidpp.c) into
a basic header and implementation in order to allow building additional features
from multiple sources. Since this only satisfies the needs for the FF driver,
any comments or suggestions are welcome.

The second patch implements all supported force feedback effects. The force
feedback implementation was not made optional because the G920 (like all 
Logitech wheels) can't function without it due to very stiff to default springs.

Edwin Velds (1):
  HID: Create header for Logitech HID++ driver
  HID: Force feedback support for the Logitech G920

 drivers/hid/Makefile                  |    2 +
 drivers/hid/hid-logitech-hidpp-base.c | 1725 ++++++++++++++++++++++++++++++
 drivers/hid/hid-logitech-hidpp-base.h |   81 ++
 drivers/hid/hid-logitech-hidpp-ff.c   |  540 ++++++++++
 drivers/hid/hid-logitech-hidpp-ff.h   |    9 +
 drivers/hid/hid-logitech-hidpp.c      | 1900 ---------------------------------
 6 files changed, 2357 insertions(+), 1900 deletions(-)
 create mode 100644 drivers/hid/hid-logitech-hidpp-base.c
 create mode 100644 drivers/hid/hid-logitech-hidpp-base.h
 create mode 100644 drivers/hid/hid-logitech-hidpp-ff.c
 create mode 100644 drivers/hid/hid-logitech-hidpp-ff.h
 delete mode 100644 drivers/hid/hid-logitech-hidpp.c

-- 
2.5.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/2] HID: Create header for Logitech HID++ driver
  2015-11-21 12:37 [PATCH 0/2] HID: Force feedback support for the Logitech G920 Wheel Edwin Velds
@ 2015-11-21 12:37 ` Edwin Velds
  2015-11-21 12:37 ` [PATCH 2/2] HID: Force feedback support for the Logitech G920 Edwin Velds
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 6+ messages in thread
From: Edwin Velds @ 2015-11-21 12:37 UTC (permalink / raw)
  To: linux-input
  Cc: linux-kernel, Simon Wood, Jiri Kosina, Michal Malý,
	elias vanderstuyft, Benjamin Tissoires, Dmitry Torokhov,
	Edwin Velds

Renamed the hid-logitech-hidpp.c file and created a header. This prepares
for building the driver from multiple sources.

Signed-off-by: Edwin Velds <e.velds@gmail.com>
---
 drivers/hid/Makefile                  |    2 +
 drivers/hid/hid-logitech-hidpp-base.c | 1831 +++++++++++++++++++++++++++++++
 drivers/hid/hid-logitech-hidpp-base.h |   81 ++
 drivers/hid/hid-logitech-hidpp.c      | 1900 ---------------------------------
 4 files changed, 1914 insertions(+), 1900 deletions(-)
 create mode 100644 drivers/hid/hid-logitech-hidpp-base.c
 create mode 100644 drivers/hid/hid-logitech-hidpp-base.h
 delete mode 100644 drivers/hid/hid-logitech-hidpp.c

diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 00011fe..a01c448 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -17,6 +17,8 @@ hid-logitech-$(CONFIG_LOGIRUMBLEPAD2_FF)	+= hid-lg2ff.o
 hid-logitech-$(CONFIG_LOGIG940_FF)	+= hid-lg3ff.o
 hid-logitech-$(CONFIG_LOGIWHEELS_FF)	+= hid-lg4ff.o
 
+hid-logitech-hidpp-y	:= hid-logitech-hidpp-base.o
+
 hid-wiimote-y		:= hid-wiimote-core.o hid-wiimote-modules.o
 hid-wiimote-$(CONFIG_DEBUG_FS)	+= hid-wiimote-debug.o
 
diff --git a/drivers/hid/hid-logitech-hidpp-base.c b/drivers/hid/hid-logitech-hidpp-base.c
new file mode 100644
index 0000000..40ab079
--- /dev/null
+++ b/drivers/hid/hid-logitech-hidpp-base.c
@@ -0,0 +1,1831 @@
+/*
+ *  HIDPP protocol for Logitech Unifying receivers
+ *
+ *  Copyright (c) 2011 Logitech (c)
+ *  Copyright (c) 2012-2013 Google (c)
+ *  Copyright (c) 2013-2014 Red Hat Inc.
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/input/mt.h>
+#include <asm/unaligned.h>
+#include "hid-ids.h"
+#include "hid-logitech-hidpp-base.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
+MODULE_AUTHOR("Nestor Lopez Casado <nlopezcasad@logitech.com>");
+
+static bool disable_raw_mode;
+module_param(disable_raw_mode, bool, 0644);
+MODULE_PARM_DESC(disable_raw_mode,
+	"Disable Raw mode reporting for touchpads and keep firmware gestures.");
+
+static bool disable_tap_to_click;
+module_param(disable_tap_to_click, bool, 0644);
+MODULE_PARM_DESC(disable_tap_to_click,
+	"Disable Tap-To-Click mode reporting for touchpads (only on the K400 currently).");
+
+#define REPORT_ID_HIDPP_SHORT			0x10
+#define REPORT_ID_HIDPP_LONG			0x11
+#define REPORT_ID_HIDPP_VERY_LONG		0x12
+
+#define HIDPP_QUIRK_CLASS_WTP			BIT(0)
+#define HIDPP_QUIRK_CLASS_M560			BIT(1)
+#define HIDPP_QUIRK_CLASS_K400			BIT(2)
+#define HIDPP_QUIRK_CLASS_G920			BIT(3)
+
+/* bits 2..20 are reserved for classes */
+#define HIDPP_QUIRK_CONNECT_EVENTS		BIT(21)
+#define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS	BIT(22)
+#define HIDPP_QUIRK_NO_HIDINPUT			BIT(23)
+#define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS	BIT(24)
+
+#define HIDPP_QUIRK_DELAYED_INIT		(HIDPP_QUIRK_NO_HIDINPUT | \
+						 HIDPP_QUIRK_CONNECT_EVENTS)
+
+
+/* HID++ 1.0 error codes */
+#define HIDPP_ERROR				0x8f
+#define HIDPP_ERROR_SUCCESS			0x00
+#define HIDPP_ERROR_INVALID_SUBID		0x01
+#define HIDPP_ERROR_INVALID_ADRESS		0x02
+#define HIDPP_ERROR_INVALID_VALUE		0x03
+#define HIDPP_ERROR_CONNECT_FAIL		0x04
+#define HIDPP_ERROR_TOO_MANY_DEVICES		0x05
+#define HIDPP_ERROR_ALREADY_EXISTS		0x06
+#define HIDPP_ERROR_BUSY			0x07
+#define HIDPP_ERROR_UNKNOWN_DEVICE		0x08
+#define HIDPP_ERROR_RESOURCE_ERROR		0x09
+#define HIDPP_ERROR_REQUEST_UNAVAILABLE		0x0a
+#define HIDPP_ERROR_INVALID_PARAM_VALUE		0x0b
+#define HIDPP_ERROR_WRONG_PIN_CODE		0x0c
+/* HID++ 2.0 error codes */
+#define HIDPP20_ERROR				0xff
+
+static void hidpp_connect_event(struct hidpp_device *hidpp_dev);
+
+static int __hidpp_send_report(struct hid_device *hdev,
+				struct hidpp_report *hidpp_report)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	int fields_count, ret;
+
+	hidpp = hid_get_drvdata(hdev);
+
+	switch (hidpp_report->report_id) {
+	case REPORT_ID_HIDPP_SHORT:
+		fields_count = HIDPP_REPORT_SHORT_LENGTH;
+		break;
+	case REPORT_ID_HIDPP_LONG:
+		fields_count = HIDPP_REPORT_LONG_LENGTH;
+		break;
+	case REPORT_ID_HIDPP_VERY_LONG:
+		fields_count = HIDPP_REPORT_VERY_LONG_LENGTH;
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	/*
+	 * set the device_index as the receiver, it will be overwritten by
+	 * hid_hw_request if needed
+	 */
+	hidpp_report->device_index = 0xff;
+
+	if (hidpp->quirks & HIDPP_QUIRK_FORCE_OUTPUT_REPORTS) {
+		ret = hid_hw_output_report(hdev, (u8 *)hidpp_report, fields_count);
+	} else {
+		ret = hid_hw_raw_request(hdev, hidpp_report->report_id,
+			(u8 *)hidpp_report, fields_count, HID_OUTPUT_REPORT,
+			HID_REQ_SET_REPORT);
+	}
+
+	return ret == fields_count ? 0 : -1;
+}
+
+/**
+ * hidpp_send_message_sync() returns 0 in case of success, and something else
+ * in case of a failure.
+ * - If ' something else' is positive, that means that an error has been raised
+ *   by the protocol itself.
+ * - If ' something else' is negative, that means that we had a classic error
+ *   (-ENOMEM, -EPIPE, etc...)
+ */
+static int hidpp_send_message_sync(struct hidpp_device *hidpp,
+	struct hidpp_report *message,
+	struct hidpp_report *response)
+{
+	int ret;
+
+	mutex_lock(&hidpp->send_mutex);
+
+	hidpp->send_receive_buf = response;
+	hidpp->answer_available = false;
+
+	/*
+	 * So that we can later validate the answer when it arrives
+	 * in hidpp_raw_event
+	 */
+	*response = *message;
+
+	ret = __hidpp_send_report(hidpp->hid_dev, message);
+
+	if (ret) {
+		dbg_hid("__hidpp_send_report returned err: %d\n", ret);
+		memset(response, 0, sizeof(struct hidpp_report));
+		goto exit;
+	}
+
+	if (!wait_event_timeout(hidpp->wait, hidpp->answer_available,
+				5*HZ)) {
+		dbg_hid("%s:timeout waiting for response\n", __func__);
+		memset(response, 0, sizeof(struct hidpp_report));
+		ret = -ETIMEDOUT;
+	}
+
+	if (response->report_id == REPORT_ID_HIDPP_SHORT &&
+	    response->rap.sub_id == HIDPP_ERROR) {
+		ret = response->rap.params[1];
+		dbg_hid("%s:got hidpp error %02X\n", __func__, ret);
+		goto exit;
+	}
+
+	if ((response->report_id == REPORT_ID_HIDPP_LONG ||
+			response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
+			response->fap.feature_index == HIDPP20_ERROR) {
+		ret = response->fap.params[1];
+		dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret);
+		goto exit;
+	}
+
+exit:
+	mutex_unlock(&hidpp->send_mutex);
+	return ret;
+
+}
+
+int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
+	u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count,
+	struct hidpp_report *response)
+{
+	struct hidpp_report *message;
+	int ret;
+
+	if (param_count > sizeof(message->fap.params))
+		return -EINVAL;
+
+	message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
+	if (!message)
+		return -ENOMEM;
+
+	if (param_count > (HIDPP_REPORT_LONG_LENGTH - 4))
+		message->report_id = REPORT_ID_HIDPP_VERY_LONG;
+	else
+		message->report_id = REPORT_ID_HIDPP_LONG;
+	message->fap.feature_index = feat_index;
+	message->fap.funcindex_clientid = funcindex_clientid;
+	memcpy(&message->fap.params, params, param_count);
+
+	ret = hidpp_send_message_sync(hidpp, message, response);
+	kfree(message);
+	return ret;
+}
+
+static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
+	u8 report_id, u8 sub_id, u8 reg_address, u8 *params, int param_count,
+	struct hidpp_report *response)
+{
+	struct hidpp_report *message;
+	int ret, max_count;
+
+	switch (report_id) {
+	case REPORT_ID_HIDPP_SHORT:
+		max_count = HIDPP_REPORT_SHORT_LENGTH - 4;
+		break;
+	case REPORT_ID_HIDPP_LONG:
+		max_count = HIDPP_REPORT_LONG_LENGTH - 4;
+		break;
+	case REPORT_ID_HIDPP_VERY_LONG:
+		max_count = HIDPP_REPORT_VERY_LONG_LENGTH - 4;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (param_count > max_count)
+		return -EINVAL;
+
+	message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
+	if (!message)
+		return -ENOMEM;
+	message->report_id = report_id;
+	message->rap.sub_id = sub_id;
+	message->rap.reg_address = reg_address;
+	memcpy(&message->rap.params, params, param_count);
+
+	ret = hidpp_send_message_sync(hidpp_dev, message, response);
+	kfree(message);
+	return ret;
+}
+
+static void delayed_work_cb(struct work_struct *work)
+{
+	struct hidpp_device *hidpp = container_of(work, struct hidpp_device,
+							work);
+	hidpp_connect_event(hidpp);
+}
+
+static inline bool hidpp_match_answer(struct hidpp_report *question,
+		struct hidpp_report *answer)
+{
+	return (answer->fap.feature_index == question->fap.feature_index) &&
+	   (answer->fap.funcindex_clientid == question->fap.funcindex_clientid);
+}
+
+static inline bool hidpp_match_error(struct hidpp_report *question,
+		struct hidpp_report *answer)
+{
+	return ((answer->rap.sub_id == HIDPP_ERROR) ||
+	    (answer->fap.feature_index == HIDPP20_ERROR)) &&
+	    (answer->fap.funcindex_clientid == question->fap.feature_index) &&
+	    (answer->fap.params[0] == question->fap.funcindex_clientid);
+}
+
+static inline bool hidpp_report_is_connect_event(struct hidpp_report *report)
+{
+	return (report->report_id == REPORT_ID_HIDPP_SHORT) &&
+		(report->rap.sub_id == 0x41);
+}
+
+/**
+ * hidpp_prefix_name() prefixes the current given name with "Logitech ".
+ */
+static void hidpp_prefix_name(char **name, int name_length)
+{
+#define PREFIX_LENGTH 9 /* "Logitech " */
+
+	int new_length;
+	char *new_name;
+
+	if (name_length > PREFIX_LENGTH &&
+	    strncmp(*name, "Logitech ", PREFIX_LENGTH) == 0)
+		/* The prefix has is already in the name */
+		return;
+
+	new_length = PREFIX_LENGTH + name_length;
+	new_name = kzalloc(new_length, GFP_KERNEL);
+	if (!new_name)
+		return;
+
+	snprintf(new_name, new_length, "Logitech %s", *name);
+
+	kfree(*name);
+
+	*name = new_name;
+}
+
+/* -------------------------------------------------------------------------- */
+/* HIDP++ 1.0 commands                                                        */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_SET_REGISTER				0x80
+#define HIDPP_GET_REGISTER				0x81
+#define HIDPP_SET_LONG_REGISTER				0x82
+#define HIDPP_GET_LONG_REGISTER				0x83
+
+#define HIDPP_REG_PAIRING_INFORMATION			0xB5
+#define DEVICE_NAME					0x40
+
+static char *hidpp_get_unifying_name(struct hidpp_device *hidpp_dev)
+{
+	struct hidpp_report response;
+	int ret;
+	/* hid-logitech-dj is in charge of setting the right device index */
+	u8 params[1] = { DEVICE_NAME };
+	char *name;
+	int len;
+
+	ret = hidpp_send_rap_command_sync(hidpp_dev,
+					REPORT_ID_HIDPP_SHORT,
+					HIDPP_GET_LONG_REGISTER,
+					HIDPP_REG_PAIRING_INFORMATION,
+					params, 1, &response);
+	if (ret)
+		return NULL;
+
+	len = response.rap.params[1];
+
+	if (2 + len > sizeof(response.rap.params))
+		return NULL;
+
+	name = kzalloc(len + 1, GFP_KERNEL);
+	if (!name)
+		return NULL;
+
+	memcpy(name, &response.rap.params[2], len);
+
+	/* include the terminating '\0' */
+	hidpp_prefix_name(&name, len + 1);
+
+	return name;
+}
+
+/* -------------------------------------------------------------------------- */
+/* 0x0000: Root                                                               */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_ROOT					0x0000
+#define HIDPP_PAGE_ROOT_IDX				0x00
+
+#define CMD_ROOT_GET_FEATURE				0x01
+#define CMD_ROOT_GET_PROTOCOL_VERSION			0x11
+
+static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature,
+	u8 *feature_index, u8 *feature_type)
+{
+	struct hidpp_report response;
+	int ret;
+	u8 params[2] = { feature >> 8, feature & 0x00FF };
+
+	ret = hidpp_send_fap_command_sync(hidpp,
+			HIDPP_PAGE_ROOT_IDX,
+			CMD_ROOT_GET_FEATURE,
+			params, 2, &response);
+	if (ret)
+		return ret;
+
+	*feature_index = response.fap.params[0];
+	*feature_type = response.fap.params[1];
+
+	return ret;
+}
+
+static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp)
+{
+	struct hidpp_report response;
+	int ret;
+
+	ret = hidpp_send_fap_command_sync(hidpp,
+			HIDPP_PAGE_ROOT_IDX,
+			CMD_ROOT_GET_PROTOCOL_VERSION,
+			NULL, 0, &response);
+
+	if (ret == HIDPP_ERROR_INVALID_SUBID) {
+		hidpp->protocol_major = 1;
+		hidpp->protocol_minor = 0;
+		return 0;
+	}
+
+	/* the device might not be connected */
+	if (ret == HIDPP_ERROR_RESOURCE_ERROR)
+		return -EIO;
+
+	if (ret > 0) {
+		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+			__func__, ret);
+		return -EPROTO;
+	}
+	if (ret)
+		return ret;
+
+	hidpp->protocol_major = response.fap.params[0];
+	hidpp->protocol_minor = response.fap.params[1];
+
+	return ret;
+}
+
+static bool hidpp_is_connected(struct hidpp_device *hidpp)
+{
+	int ret;
+
+	ret = hidpp_root_get_protocol_version(hidpp);
+	if (!ret)
+		hid_dbg(hidpp->hid_dev, "HID++ %u.%u device connected.\n",
+			hidpp->protocol_major, hidpp->protocol_minor);
+	return ret == 0;
+}
+
+/* -------------------------------------------------------------------------- */
+/* 0x0005: GetDeviceNameType                                                  */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_GET_DEVICE_NAME_TYPE			0x0005
+
+#define CMD_GET_DEVICE_NAME_TYPE_GET_COUNT		0x01
+#define CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME	0x11
+#define CMD_GET_DEVICE_NAME_TYPE_GET_TYPE		0x21
+
+static int hidpp_devicenametype_get_count(struct hidpp_device *hidpp,
+	u8 feature_index, u8 *nameLength)
+{
+	struct hidpp_report response;
+	int ret;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+		CMD_GET_DEVICE_NAME_TYPE_GET_COUNT, NULL, 0, &response);
+
+	if (ret > 0) {
+		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+			__func__, ret);
+		return -EPROTO;
+	}
+	if (ret)
+		return ret;
+
+	*nameLength = response.fap.params[0];
+
+	return ret;
+}
+
+static int hidpp_devicenametype_get_device_name(struct hidpp_device *hidpp,
+	u8 feature_index, u8 char_index, char *device_name, int len_buf)
+{
+	struct hidpp_report response;
+	int ret, i;
+	int count;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+		CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME, &char_index, 1,
+		&response);
+
+	if (ret > 0) {
+		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+			__func__, ret);
+		return -EPROTO;
+	}
+	if (ret)
+		return ret;
+
+	switch (response.report_id) {
+	case REPORT_ID_HIDPP_VERY_LONG:
+		count = HIDPP_REPORT_VERY_LONG_LENGTH - 4;
+		break;
+	case REPORT_ID_HIDPP_LONG:
+		count = HIDPP_REPORT_LONG_LENGTH - 4;
+		break;
+	case REPORT_ID_HIDPP_SHORT:
+		count = HIDPP_REPORT_SHORT_LENGTH - 4;
+		break;
+	default:
+		return -EPROTO;
+	}
+
+	if (len_buf < count)
+		count = len_buf;
+
+	for (i = 0; i < count; i++)
+		device_name[i] = response.fap.params[i];
+
+	return count;
+}
+
+static char *hidpp_get_device_name(struct hidpp_device *hidpp)
+{
+	u8 feature_type;
+	u8 feature_index;
+	u8 __name_length;
+	char *name;
+	unsigned index = 0;
+	int ret;
+
+	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_GET_DEVICE_NAME_TYPE,
+		&feature_index, &feature_type);
+	if (ret)
+		return NULL;
+
+	ret = hidpp_devicenametype_get_count(hidpp, feature_index,
+		&__name_length);
+	if (ret)
+		return NULL;
+
+	name = kzalloc(__name_length + 1, GFP_KERNEL);
+	if (!name)
+		return NULL;
+
+	while (index < __name_length) {
+		ret = hidpp_devicenametype_get_device_name(hidpp,
+			feature_index, index, name + index,
+			__name_length - index);
+		if (ret <= 0) {
+			kfree(name);
+			return NULL;
+		}
+		index += ret;
+	}
+
+	/* include the terminating '\0' */
+	hidpp_prefix_name(&name, __name_length + 1);
+
+	return name;
+}
+
+/* -------------------------------------------------------------------------- */
+/* 0x6010: Touchpad FW items                                                  */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_TOUCHPAD_FW_ITEMS			0x6010
+
+#define CMD_TOUCHPAD_FW_ITEMS_SET			0x10
+
+struct hidpp_touchpad_fw_items {
+	uint8_t presence;
+	uint8_t desired_state;
+	uint8_t state;
+	uint8_t persistent;
+};
+
+/**
+ * send a set state command to the device by reading the current items->state
+ * field. items is then filled with the current state.
+ */
+static int hidpp_touchpad_fw_items_set(struct hidpp_device *hidpp,
+				       u8 feature_index,
+				       struct hidpp_touchpad_fw_items *items)
+{
+	struct hidpp_report response;
+	int ret;
+	u8 *params = (u8 *)response.fap.params;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+		CMD_TOUCHPAD_FW_ITEMS_SET, &items->state, 1, &response);
+
+	if (ret > 0) {
+		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+			__func__, ret);
+		return -EPROTO;
+	}
+	if (ret)
+		return ret;
+
+	items->presence = params[0];
+	items->desired_state = params[1];
+	items->state = params[2];
+	items->persistent = params[3];
+
+	return 0;
+}
+
+/* -------------------------------------------------------------------------- */
+/* 0x6100: TouchPadRawXY                                                      */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_TOUCHPAD_RAW_XY			0x6100
+
+#define CMD_TOUCHPAD_GET_RAW_INFO			0x01
+#define CMD_TOUCHPAD_SET_RAW_REPORT_STATE		0x21
+
+#define EVENT_TOUCHPAD_RAW_XY				0x00
+
+#define TOUCHPAD_RAW_XY_ORIGIN_LOWER_LEFT		0x01
+#define TOUCHPAD_RAW_XY_ORIGIN_UPPER_LEFT		0x03
+
+struct hidpp_touchpad_raw_info {
+	u16 x_size;
+	u16 y_size;
+	u8 z_range;
+	u8 area_range;
+	u8 timestamp_unit;
+	u8 maxcontacts;
+	u8 origin;
+	u16 res;
+};
+
+struct hidpp_touchpad_raw_xy_finger {
+	u8 contact_type;
+	u8 contact_status;
+	u16 x;
+	u16 y;
+	u8 z;
+	u8 area;
+	u8 finger_id;
+};
+
+struct hidpp_touchpad_raw_xy {
+	u16 timestamp;
+	struct hidpp_touchpad_raw_xy_finger fingers[2];
+	u8 spurious_flag;
+	u8 end_of_frame;
+	u8 finger_count;
+	u8 button;
+};
+
+static int hidpp_touchpad_get_raw_info(struct hidpp_device *hidpp,
+	u8 feature_index, struct hidpp_touchpad_raw_info *raw_info)
+{
+	struct hidpp_report response;
+	int ret;
+	u8 *params = (u8 *)response.fap.params;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+		CMD_TOUCHPAD_GET_RAW_INFO, NULL, 0, &response);
+
+	if (ret > 0) {
+		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+			__func__, ret);
+		return -EPROTO;
+	}
+	if (ret)
+		return ret;
+
+	raw_info->x_size = get_unaligned_be16(&params[0]);
+	raw_info->y_size = get_unaligned_be16(&params[2]);
+	raw_info->z_range = params[4];
+	raw_info->area_range = params[5];
+	raw_info->maxcontacts = params[7];
+	raw_info->origin = params[8];
+	/* res is given in unit per inch */
+	raw_info->res = get_unaligned_be16(&params[13]) * 2 / 51;
+
+	return ret;
+}
+
+static int hidpp_touchpad_set_raw_report_state(struct hidpp_device *hidpp_dev,
+		u8 feature_index, bool send_raw_reports,
+		bool sensor_enhanced_settings)
+{
+	struct hidpp_report response;
+
+	/*
+	 * Params:
+	 *   bit 0 - enable raw
+	 *   bit 1 - 16bit Z, no area
+	 *   bit 2 - enhanced sensitivity
+	 *   bit 3 - width, height (4 bits each) instead of area
+	 *   bit 4 - send raw + gestures (degrades smoothness)
+	 *   remaining bits - reserved
+	 */
+	u8 params = send_raw_reports | (sensor_enhanced_settings << 2);
+
+	return hidpp_send_fap_command_sync(hidpp_dev, feature_index,
+		CMD_TOUCHPAD_SET_RAW_REPORT_STATE, &params, 1, &response);
+}
+
+static void hidpp_touchpad_touch_event(u8 *data,
+	struct hidpp_touchpad_raw_xy_finger *finger)
+{
+	u8 x_m = data[0] << 2;
+	u8 y_m = data[2] << 2;
+
+	finger->x = x_m << 6 | data[1];
+	finger->y = y_m << 6 | data[3];
+
+	finger->contact_type = data[0] >> 6;
+	finger->contact_status = data[2] >> 6;
+
+	finger->z = data[4];
+	finger->area = data[5];
+	finger->finger_id = data[6] >> 4;
+}
+
+static void hidpp_touchpad_raw_xy_event(struct hidpp_device *hidpp_dev,
+		u8 *data, struct hidpp_touchpad_raw_xy *raw_xy)
+{
+	memset(raw_xy, 0, sizeof(struct hidpp_touchpad_raw_xy));
+	raw_xy->end_of_frame = data[8] & 0x01;
+	raw_xy->spurious_flag = (data[8] >> 1) & 0x01;
+	raw_xy->finger_count = data[15] & 0x0f;
+	raw_xy->button = (data[8] >> 2) & 0x01;
+
+	if (raw_xy->finger_count) {
+		hidpp_touchpad_touch_event(&data[2], &raw_xy->fingers[0]);
+		hidpp_touchpad_touch_event(&data[9], &raw_xy->fingers[1]);
+	}
+}
+
+/* ************************************************************************** */
+/*                                                                            */
+/* Device Support                                                             */
+/*                                                                            */
+/* ************************************************************************** */
+
+/* -------------------------------------------------------------------------- */
+/* Touchpad HID++ devices                                                     */
+/* -------------------------------------------------------------------------- */
+
+#define WTP_MANUAL_RESOLUTION				39
+
+struct wtp_data {
+	struct input_dev *input;
+	u16 x_size, y_size;
+	u8 finger_count;
+	u8 mt_feature_index;
+	u8 button_feature_index;
+	u8 maxcontacts;
+	bool flip_y;
+	unsigned int resolution;
+};
+
+static int wtp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	return -1;
+}
+
+static void wtp_populate_input(struct hidpp_device *hidpp,
+		struct input_dev *input_dev, bool origin_is_hid_core)
+{
+	struct wtp_data *wd = hidpp->private_data;
+
+	__set_bit(EV_ABS, input_dev->evbit);
+	__set_bit(EV_KEY, input_dev->evbit);
+	__clear_bit(EV_REL, input_dev->evbit);
+	__clear_bit(EV_LED, input_dev->evbit);
+
+	input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, wd->x_size, 0, 0);
+	input_abs_set_res(input_dev, ABS_MT_POSITION_X, wd->resolution);
+	input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, wd->y_size, 0, 0);
+	input_abs_set_res(input_dev, ABS_MT_POSITION_Y, wd->resolution);
+
+	/* Max pressure is not given by the devices, pick one */
+	input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 50, 0, 0);
+
+	input_set_capability(input_dev, EV_KEY, BTN_LEFT);
+
+	if (hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS)
+		input_set_capability(input_dev, EV_KEY, BTN_RIGHT);
+	else
+		__set_bit(INPUT_PROP_BUTTONPAD, input_dev->propbit);
+
+	input_mt_init_slots(input_dev, wd->maxcontacts, INPUT_MT_POINTER |
+		INPUT_MT_DROP_UNUSED);
+
+	wd->input = input_dev;
+}
+
+static void wtp_touch_event(struct wtp_data *wd,
+	struct hidpp_touchpad_raw_xy_finger *touch_report)
+{
+	int slot;
+
+	if (!touch_report->finger_id || touch_report->contact_type)
+		/* no actual data */
+		return;
+
+	slot = input_mt_get_slot_by_key(wd->input, touch_report->finger_id);
+
+	input_mt_slot(wd->input, slot);
+	input_mt_report_slot_state(wd->input, MT_TOOL_FINGER,
+					touch_report->contact_status);
+	if (touch_report->contact_status) {
+		input_event(wd->input, EV_ABS, ABS_MT_POSITION_X,
+				touch_report->x);
+		input_event(wd->input, EV_ABS, ABS_MT_POSITION_Y,
+				wd->flip_y ? wd->y_size - touch_report->y :
+					     touch_report->y);
+		input_event(wd->input, EV_ABS, ABS_MT_PRESSURE,
+				touch_report->area);
+	}
+}
+
+static void wtp_send_raw_xy_event(struct hidpp_device *hidpp,
+		struct hidpp_touchpad_raw_xy *raw)
+{
+	struct wtp_data *wd = hidpp->private_data;
+	int i;
+
+	for (i = 0; i < 2; i++)
+		wtp_touch_event(wd, &(raw->fingers[i]));
+
+	if (raw->end_of_frame &&
+	    !(hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS))
+		input_event(wd->input, EV_KEY, BTN_LEFT, raw->button);
+
+	if (raw->end_of_frame || raw->finger_count <= 2) {
+		input_mt_sync_frame(wd->input);
+		input_sync(wd->input);
+	}
+}
+
+static int wtp_mouse_raw_xy_event(struct hidpp_device *hidpp, u8 *data)
+{
+	struct wtp_data *wd = hidpp->private_data;
+	u8 c1_area = ((data[7] & 0xf) * (data[7] & 0xf) +
+		      (data[7] >> 4) * (data[7] >> 4)) / 2;
+	u8 c2_area = ((data[13] & 0xf) * (data[13] & 0xf) +
+		      (data[13] >> 4) * (data[13] >> 4)) / 2;
+	struct hidpp_touchpad_raw_xy raw = {
+		.timestamp = data[1],
+		.fingers = {
+			{
+				.contact_type = 0,
+				.contact_status = !!data[7],
+				.x = get_unaligned_le16(&data[3]),
+				.y = get_unaligned_le16(&data[5]),
+				.z = c1_area,
+				.area = c1_area,
+				.finger_id = data[2],
+			}, {
+				.contact_type = 0,
+				.contact_status = !!data[13],
+				.x = get_unaligned_le16(&data[9]),
+				.y = get_unaligned_le16(&data[11]),
+				.z = c2_area,
+				.area = c2_area,
+				.finger_id = data[8],
+			}
+		},
+		.finger_count = wd->maxcontacts,
+		.spurious_flag = 0,
+		.end_of_frame = (data[0] >> 7) == 0,
+		.button = data[0] & 0x01,
+	};
+
+	wtp_send_raw_xy_event(hidpp, &raw);
+
+	return 1;
+}
+
+static int wtp_raw_event(struct hid_device *hdev, u8 *data, int size)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct wtp_data *wd = hidpp->private_data;
+	struct hidpp_report *report = (struct hidpp_report *)data;
+	struct hidpp_touchpad_raw_xy raw;
+
+	if (!wd || !wd->input)
+		return 1;
+
+	switch (data[0]) {
+	case 0x02:
+		if (size < 2) {
+			hid_err(hdev, "Received HID report of bad size (%d)",
+				size);
+			return 1;
+		}
+		if (hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS) {
+			input_event(wd->input, EV_KEY, BTN_LEFT,
+					!!(data[1] & 0x01));
+			input_event(wd->input, EV_KEY, BTN_RIGHT,
+					!!(data[1] & 0x02));
+			input_sync(wd->input);
+			return 0;
+		} else {
+			if (size < 21)
+				return 1;
+			return wtp_mouse_raw_xy_event(hidpp, &data[7]);
+		}
+	case REPORT_ID_HIDPP_LONG:
+		/* size is already checked in hidpp_raw_event. */
+		if ((report->fap.feature_index != wd->mt_feature_index) ||
+		    (report->fap.funcindex_clientid != EVENT_TOUCHPAD_RAW_XY))
+			return 1;
+		hidpp_touchpad_raw_xy_event(hidpp, data + 4, &raw);
+
+		wtp_send_raw_xy_event(hidpp, &raw);
+		return 0;
+	}
+
+	return 0;
+}
+
+static int wtp_get_config(struct hidpp_device *hidpp)
+{
+	struct wtp_data *wd = hidpp->private_data;
+	struct hidpp_touchpad_raw_info raw_info = {0};
+	u8 feature_type;
+	int ret;
+
+	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_TOUCHPAD_RAW_XY,
+		&wd->mt_feature_index, &feature_type);
+	if (ret)
+		/* means that the device is not powered up */
+		return ret;
+
+	ret = hidpp_touchpad_get_raw_info(hidpp, wd->mt_feature_index,
+		&raw_info);
+	if (ret)
+		return ret;
+
+	wd->x_size = raw_info.x_size;
+	wd->y_size = raw_info.y_size;
+	wd->maxcontacts = raw_info.maxcontacts;
+	wd->flip_y = raw_info.origin == TOUCHPAD_RAW_XY_ORIGIN_LOWER_LEFT;
+	wd->resolution = raw_info.res;
+	if (!wd->resolution)
+		wd->resolution = WTP_MANUAL_RESOLUTION;
+
+	return 0;
+}
+
+static int wtp_allocate(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct wtp_data *wd;
+
+	wd = devm_kzalloc(&hdev->dev, sizeof(struct wtp_data),
+			GFP_KERNEL);
+	if (!wd)
+		return -ENOMEM;
+
+	hidpp->private_data = wd;
+
+	return 0;
+};
+
+static int wtp_connect(struct hid_device *hdev, bool connected)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct wtp_data *wd = hidpp->private_data;
+	int ret;
+
+	if (!connected)
+		return 0;
+
+	if (!wd->x_size) {
+		ret = wtp_get_config(hidpp);
+		if (ret) {
+			hid_err(hdev, "Can not get wtp config: %d\n", ret);
+			return ret;
+		}
+	}
+
+	return hidpp_touchpad_set_raw_report_state(hidpp, wd->mt_feature_index,
+			true, true);
+}
+
+/* ------------------------------------------------------------------------- */
+/* Logitech M560 devices                                                     */
+/* ------------------------------------------------------------------------- */
+
+/*
+ * Logitech M560 protocol overview
+ *
+ * The Logitech M560 mouse, is designed for windows 8. When the middle and/or
+ * the sides buttons are pressed, it sends some keyboard keys events
+ * instead of buttons ones.
+ * To complicate things further, the middle button keys sequence
+ * is different from the odd press and the even press.
+ *
+ * forward button -> Super_R
+ * backward button -> Super_L+'d' (press only)
+ * middle button -> 1st time: Alt_L+SuperL+XF86TouchpadOff (press only)
+ *                  2nd time: left-click (press only)
+ * NB: press-only means that when the button is pressed, the
+ * KeyPress/ButtonPress and KeyRelease/ButtonRelease events are generated
+ * together sequentially; instead when the button is released, no event is
+ * generated !
+ *
+ * With the command
+ *	10<xx>0a 3500af03 (where <xx> is the mouse id),
+ * the mouse reacts differently:
+ * - it never sends a keyboard key event
+ * - for the three mouse button it sends:
+ *	middle button               press   11<xx>0a 3500af00...
+ *	side 1 button (forward)     press   11<xx>0a 3500b000...
+ *	side 2 button (backward)    press   11<xx>0a 3500ae00...
+ *	middle/side1/side2 button   release 11<xx>0a 35000000...
+ */
+
+static const u8 m560_config_parameter[] = {0x00, 0xaf, 0x03};
+
+struct m560_private_data {
+	struct input_dev *input;
+};
+
+/* how buttons are mapped in the report */
+#define M560_MOUSE_BTN_LEFT		0x01
+#define M560_MOUSE_BTN_RIGHT		0x02
+#define M560_MOUSE_BTN_WHEEL_LEFT	0x08
+#define M560_MOUSE_BTN_WHEEL_RIGHT	0x10
+
+#define M560_SUB_ID			0x0a
+#define M560_BUTTON_MODE_REGISTER	0x35
+
+static int m560_send_config_command(struct hid_device *hdev, bool connected)
+{
+	struct hidpp_report response;
+	struct hidpp_device *hidpp_dev;
+
+	hidpp_dev = hid_get_drvdata(hdev);
+
+	if (!connected)
+		return -ENODEV;
+
+	return hidpp_send_rap_command_sync(
+		hidpp_dev,
+		REPORT_ID_HIDPP_SHORT,
+		M560_SUB_ID,
+		M560_BUTTON_MODE_REGISTER,
+		(u8 *)m560_config_parameter,
+		sizeof(m560_config_parameter),
+		&response
+	);
+}
+
+static int m560_allocate(struct hid_device *hdev)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct m560_private_data *d;
+
+	d = devm_kzalloc(&hdev->dev, sizeof(struct m560_private_data),
+			GFP_KERNEL);
+	if (!d)
+		return -ENOMEM;
+
+	hidpp->private_data = d;
+
+	return 0;
+};
+
+static int m560_raw_event(struct hid_device *hdev, u8 *data, int size)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct m560_private_data *mydata = hidpp->private_data;
+
+	/* sanity check */
+	if (!mydata || !mydata->input) {
+		hid_err(hdev, "error in parameter\n");
+		return -EINVAL;
+	}
+
+	if (size < 7) {
+		hid_err(hdev, "error in report\n");
+		return 0;
+	}
+
+	if (data[0] == REPORT_ID_HIDPP_LONG &&
+	    data[2] == M560_SUB_ID && data[6] == 0x00) {
+		/*
+		 * m560 mouse report for middle, forward and backward button
+		 *
+		 * data[0] = 0x11
+		 * data[1] = device-id
+		 * data[2] = 0x0a
+		 * data[5] = 0xaf -> middle
+		 *	     0xb0 -> forward
+		 *	     0xae -> backward
+		 *	     0x00 -> release all
+		 * data[6] = 0x00
+		 */
+
+		switch (data[5]) {
+		case 0xaf:
+			input_report_key(mydata->input, BTN_MIDDLE, 1);
+			break;
+		case 0xb0:
+			input_report_key(mydata->input, BTN_FORWARD, 1);
+			break;
+		case 0xae:
+			input_report_key(mydata->input, BTN_BACK, 1);
+			break;
+		case 0x00:
+			input_report_key(mydata->input, BTN_BACK, 0);
+			input_report_key(mydata->input, BTN_FORWARD, 0);
+			input_report_key(mydata->input, BTN_MIDDLE, 0);
+			break;
+		default:
+			hid_err(hdev, "error in report\n");
+			return 0;
+		}
+		input_sync(mydata->input);
+
+	} else if (data[0] == 0x02) {
+		/*
+		 * Logitech M560 mouse report
+		 *
+		 * data[0] = type (0x02)
+		 * data[1..2] = buttons
+		 * data[3..5] = xy
+		 * data[6] = wheel
+		 */
+
+		int v;
+
+		input_report_key(mydata->input, BTN_LEFT,
+			!!(data[1] & M560_MOUSE_BTN_LEFT));
+		input_report_key(mydata->input, BTN_RIGHT,
+			!!(data[1] & M560_MOUSE_BTN_RIGHT));
+
+		if (data[1] & M560_MOUSE_BTN_WHEEL_LEFT)
+			input_report_rel(mydata->input, REL_HWHEEL, -1);
+		else if (data[1] & M560_MOUSE_BTN_WHEEL_RIGHT)
+			input_report_rel(mydata->input, REL_HWHEEL, 1);
+
+		v = hid_snto32(hid_field_extract(hdev, data+3, 0, 12), 12);
+		input_report_rel(mydata->input, REL_X, v);
+
+		v = hid_snto32(hid_field_extract(hdev, data+3, 12, 12), 12);
+		input_report_rel(mydata->input, REL_Y, v);
+
+		v = hid_snto32(data[6], 8);
+		input_report_rel(mydata->input, REL_WHEEL, v);
+
+		input_sync(mydata->input);
+	}
+
+	return 1;
+}
+
+static void m560_populate_input(struct hidpp_device *hidpp,
+		struct input_dev *input_dev, bool origin_is_hid_core)
+{
+	struct m560_private_data *mydata = hidpp->private_data;
+
+	mydata->input = input_dev;
+
+	__set_bit(EV_KEY, mydata->input->evbit);
+	__set_bit(BTN_MIDDLE, mydata->input->keybit);
+	__set_bit(BTN_RIGHT, mydata->input->keybit);
+	__set_bit(BTN_LEFT, mydata->input->keybit);
+	__set_bit(BTN_BACK, mydata->input->keybit);
+	__set_bit(BTN_FORWARD, mydata->input->keybit);
+
+	__set_bit(EV_REL, mydata->input->evbit);
+	__set_bit(REL_X, mydata->input->relbit);
+	__set_bit(REL_Y, mydata->input->relbit);
+	__set_bit(REL_WHEEL, mydata->input->relbit);
+	__set_bit(REL_HWHEEL, mydata->input->relbit);
+}
+
+static int m560_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	return -1;
+}
+
+/* ------------------------------------------------------------------------- */
+/* Logitech K400 devices                                                     */
+/* ------------------------------------------------------------------------- */
+
+/*
+ * The Logitech K400 keyboard has an embedded touchpad which is seen
+ * as a mouse from the OS point of view. There is a hardware shortcut to disable
+ * tap-to-click but the setting is not remembered accross reset, annoying some
+ * users.
+ *
+ * We can toggle this feature from the host by using the feature 0x6010:
+ * Touchpad FW items
+ */
+
+struct k400_private_data {
+	u8 feature_index;
+};
+
+static int k400_disable_tap_to_click(struct hidpp_device *hidpp)
+{
+	struct k400_private_data *k400 = hidpp->private_data;
+	struct hidpp_touchpad_fw_items items = {};
+	int ret;
+	u8 feature_type;
+
+	if (!k400->feature_index) {
+		ret = hidpp_root_get_feature(hidpp,
+			HIDPP_PAGE_TOUCHPAD_FW_ITEMS,
+			&k400->feature_index, &feature_type);
+		if (ret)
+			/* means that the device is not powered up */
+			return ret;
+	}
+
+	ret = hidpp_touchpad_fw_items_set(hidpp, k400->feature_index, &items);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int k400_allocate(struct hid_device *hdev)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct k400_private_data *k400;
+
+	k400 = devm_kzalloc(&hdev->dev, sizeof(struct k400_private_data),
+			    GFP_KERNEL);
+	if (!k400)
+		return -ENOMEM;
+
+	hidpp->private_data = k400;
+
+	return 0;
+};
+
+static int k400_connect(struct hid_device *hdev, bool connected)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+
+	if (!connected)
+		return 0;
+
+	if (!disable_tap_to_click)
+		return 0;
+
+	return k400_disable_tap_to_click(hidpp);
+}
+
+/* ------------------------------------------------------------------------- */
+/* Logitech G920 Driving Force Racing Wheel for Xbox One                     */
+/* ------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_G920_FORCE_FEEDBACK			0x8123
+
+/* Using session ID = 1 */
+#define CMD_G920_FORCE_GET_APERTURE			0x51
+#define CMD_G920_FORCE_SET_APERTURE			0x61
+
+struct g920_private_data {
+	u8 force_feature;
+	u16 range;
+};
+
+#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)
+
+static ssize_t g920_range_show(struct device *dev, struct device_attribute *attr,
+				char *buf)
+{
+	struct hid_device *hid = to_hid_device(dev);
+	struct hidpp_device *hidpp = hid_get_drvdata(hid);
+	struct g920_private_data *pdata;
+
+	pdata = hidpp->private_data;
+	if (!pdata) {
+		hid_err(hid, "Private driver data not found!\n");
+		return -EINVAL;
+	}
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", pdata->range);
+}
+
+static ssize_t g920_range_store(struct device *dev, struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	struct hid_device *hid = to_hid_device(dev);
+	struct hidpp_device *hidpp = hid_get_drvdata(hid);
+	struct g920_private_data *pdata;
+	struct hidpp_report response;
+	u8 params[2];
+	int ret;
+	u16 range = simple_strtoul(buf, NULL, 10);
+
+	pdata = hidpp->private_data;
+	if (!pdata) {
+		hid_err(hid, "Private driver data not found!\n");
+		return -EINVAL;
+	}
+
+	if (range < 180)
+		range = 180;
+	else if (range > 900)
+		range = 900;
+
+	params[0] = range >> 8;
+	params[1] = range & 0x00FF;
+
+	ret = hidpp_send_fap_command_sync(hidpp, pdata->force_feature,
+		CMD_G920_FORCE_SET_APERTURE, params, 2, &response);
+	if (ret)
+		return ret;
+
+	pdata->range = range;
+	return count;
+}
+
+static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, g920_range_show, g920_range_store);
+
+static int g920_allocate(struct hid_device *hdev)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct g920_private_data *pdata;
+
+	pdata = devm_kzalloc(&hdev->dev, sizeof(struct g920_private_data),
+			GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	hidpp->private_data = pdata;
+
+	return 0;
+}
+
+static int g920_get_config(struct hidpp_device *hidpp)
+{
+	struct g920_private_data *pdata = hidpp->private_data;
+	struct hidpp_report response;
+	u8 feature_type;
+	u8 feature_index;
+	int ret;
+
+	pdata = hidpp->private_data;
+	if (!pdata) {
+		hid_err(hidpp->hid_dev, "Private driver data not found!\n");
+		return -EINVAL;
+	}
+
+	/* Find feature and store for later use */
+	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_G920_FORCE_FEEDBACK,
+		&feature_index, &feature_type);
+	if (ret)
+		return ret;
+
+	pdata->force_feature = feature_index;
+
+	/* Read current Range */
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+		CMD_G920_FORCE_GET_APERTURE, NULL, 0, &response);
+	if (ret > 0) {
+		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+			__func__, ret);
+		return -EPROTO;
+	}
+	if (ret)
+		return ret;
+
+	pdata->range = get_unaligned_be16(&response.fap.params[0]);
+
+	/* Create sysfs interface */
+	ret = device_create_file(&(hidpp->hid_dev->dev), &dev_attr_range);
+	if (ret)
+		hid_warn(hidpp->hid_dev, "Unable to create sysfs interface for \"range\", errno %d\n", ret);
+
+	return 0;
+}
+
+/* -------------------------------------------------------------------------- */
+/* Generic HID++ devices                                                      */
+/* -------------------------------------------------------------------------- */
+
+static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
+		return wtp_input_mapping(hdev, hi, field, usage, bit, max);
+	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560 &&
+			field->application != HID_GD_MOUSE)
+		return m560_input_mapping(hdev, hi, field, usage, bit, max);
+
+	return 0;
+}
+
+static int hidpp_input_mapped(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+
+	/* Ensure that Logitech G920 is not given a default fuzz/flat value */
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
+		if (usage->type == EV_ABS && (usage->code == ABS_X ||
+				usage->code == ABS_Y || usage->code == ABS_Z ||
+				usage->code == ABS_RZ)) {
+			field->application = HID_GD_MULTIAXIS;
+		}
+	}
+
+	return 0;
+}
+
+
+static void hidpp_populate_input(struct hidpp_device *hidpp,
+		struct input_dev *input, bool origin_is_hid_core)
+{
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
+		wtp_populate_input(hidpp, input, origin_is_hid_core);
+	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
+		m560_populate_input(hidpp, input, origin_is_hid_core);
+}
+
+static int hidpp_input_configured(struct hid_device *hdev,
+				struct hid_input *hidinput)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct input_dev *input = hidinput->input;
+
+	hidpp_populate_input(hidpp, input, true);
+
+	return 0;
+}
+
+static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
+		int size)
+{
+	struct hidpp_report *question = hidpp->send_receive_buf;
+	struct hidpp_report *answer = hidpp->send_receive_buf;
+	struct hidpp_report *report = (struct hidpp_report *)data;
+
+	/*
+	 * If the mutex is locked then we have a pending answer from a
+	 * previously sent command.
+	 */
+	if (unlikely(mutex_is_locked(&hidpp->send_mutex))) {
+		/*
+		 * Check for a correct hidpp20 answer or the corresponding
+		 * error
+		 */
+		if (hidpp_match_answer(question, report) ||
+				hidpp_match_error(question, report)) {
+			*answer = *report;
+			hidpp->answer_available = true;
+			wake_up(&hidpp->wait);
+			/*
+			 * This was an answer to a command that this driver sent
+			 * We return 1 to hid-core to avoid forwarding the
+			 * command upstream as it has been treated by the driver
+			 */
+
+			return 1;
+		}
+	}
+
+	if (unlikely(hidpp_report_is_connect_event(report))) {
+		atomic_set(&hidpp->connected,
+				!(report->rap.params[0] & (1 << 6)));
+		if ((hidpp->quirks & HIDPP_QUIRK_CONNECT_EVENTS) &&
+		    (schedule_work(&hidpp->work) == 0))
+			dbg_hid("%s: connect event already queued\n", __func__);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
+		u8 *data, int size)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	int ret = 0;
+
+	/* Generic HID++ processing. */
+	switch (data[0]) {
+	case REPORT_ID_HIDPP_VERY_LONG:
+		if (size != HIDPP_REPORT_VERY_LONG_LENGTH) {
+			hid_err(hdev, "received hid++ report of bad size (%d)",
+				size);
+			return 1;
+		}
+		ret = hidpp_raw_hidpp_event(hidpp, data, size);
+		break;
+	case REPORT_ID_HIDPP_LONG:
+		if (size != HIDPP_REPORT_LONG_LENGTH) {
+			hid_err(hdev, "received hid++ report of bad size (%d)",
+				size);
+			return 1;
+		}
+		ret = hidpp_raw_hidpp_event(hidpp, data, size);
+		break;
+	case REPORT_ID_HIDPP_SHORT:
+		if (size != HIDPP_REPORT_SHORT_LENGTH) {
+			hid_err(hdev, "received hid++ report of bad size (%d)",
+				size);
+			return 1;
+		}
+		ret = hidpp_raw_hidpp_event(hidpp, data, size);
+		break;
+	}
+
+	/* If no report is available for further processing, skip calling
+	 * raw_event of subclasses. */
+	if (ret != 0)
+		return ret;
+
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
+		return wtp_raw_event(hdev, data, size);
+	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
+		return m560_raw_event(hdev, data, size);
+
+	return 0;
+}
+
+static void hidpp_overwrite_name(struct hid_device *hdev, bool use_unifying)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	char *name;
+
+	if (use_unifying)
+		/*
+		 * the device is connected through an Unifying receiver, and
+		 * might not be already connected.
+		 * Ask the receiver for its name.
+		 */
+		name = hidpp_get_unifying_name(hidpp);
+	else
+		name = hidpp_get_device_name(hidpp);
+
+	if (!name) {
+		hid_err(hdev, "unable to retrieve the name of the device");
+	} else {
+		dbg_hid("HID++: Got name: %s\n", name);
+		snprintf(hdev->name, sizeof(hdev->name), "%s", name);
+	}
+
+	kfree(name);
+}
+
+static int hidpp_input_open(struct input_dev *dev)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+
+	return hid_hw_open(hid);
+}
+
+static void hidpp_input_close(struct input_dev *dev)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+
+	hid_hw_close(hid);
+}
+
+static struct input_dev *hidpp_allocate_input(struct hid_device *hdev)
+{
+	struct input_dev *input_dev = devm_input_allocate_device(&hdev->dev);
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+
+	if (!input_dev)
+		return NULL;
+
+	input_set_drvdata(input_dev, hdev);
+	input_dev->open = hidpp_input_open;
+	input_dev->close = hidpp_input_close;
+
+	input_dev->name = hidpp->name;
+	input_dev->phys = hdev->phys;
+	input_dev->uniq = hdev->uniq;
+	input_dev->id.bustype = hdev->bus;
+	input_dev->id.vendor  = hdev->vendor;
+	input_dev->id.product = hdev->product;
+	input_dev->id.version = hdev->version;
+	input_dev->dev.parent = &hdev->dev;
+
+	return input_dev;
+}
+
+static void hidpp_connect_event(struct hidpp_device *hidpp)
+{
+	struct hid_device *hdev = hidpp->hid_dev;
+	int ret = 0;
+	bool connected = atomic_read(&hidpp->connected);
+	struct input_dev *input;
+	char *name, *devm_name;
+
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
+		ret = wtp_connect(hdev, connected);
+		if (ret)
+			return;
+	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) {
+		ret = m560_send_config_command(hdev, connected);
+		if (ret)
+			return;
+	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) {
+		ret = k400_connect(hdev, connected);
+		if (ret)
+			return;
+	}
+
+	if (!connected || hidpp->delayed_input)
+		return;
+
+	/* the device is already connected, we can ask for its name and
+	 * protocol */
+	if (!hidpp->protocol_major) {
+		ret = !hidpp_is_connected(hidpp);
+		if (ret) {
+			hid_err(hdev, "Can not get the protocol version.\n");
+			return;
+		}
+		hid_info(hdev, "HID++ %u.%u device connected.\n",
+			 hidpp->protocol_major, hidpp->protocol_minor);
+	}
+
+	if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT))
+		/* if HID created the input nodes for us, we can stop now */
+		return;
+
+	if (!hidpp->name || hidpp->name == hdev->name) {
+		name = hidpp_get_device_name(hidpp);
+		if (!name) {
+			hid_err(hdev,
+				"unable to retrieve the name of the device");
+			return;
+		}
+
+		devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s", name);
+		kfree(name);
+		if (!devm_name)
+			return;
+
+		hidpp->name = devm_name;
+	}
+
+	input = hidpp_allocate_input(hdev);
+	if (!input) {
+		hid_err(hdev, "cannot allocate new input device: %d\n", ret);
+		return;
+	}
+
+	hidpp_populate_input(hidpp, input, false);
+
+	ret = input_register_device(input);
+	if (ret)
+		input_free_device(input);
+
+	hidpp->delayed_input = input;
+}
+
+static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	struct hidpp_device *hidpp;
+	int ret;
+	bool connected;
+	unsigned int connect_mask = HID_CONNECT_DEFAULT;
+
+	hidpp = devm_kzalloc(&hdev->dev, sizeof(struct hidpp_device),
+			GFP_KERNEL);
+	if (!hidpp)
+		return -ENOMEM;
+
+	hidpp->hid_dev = hdev;
+	hidpp->name = hdev->name;
+	hid_set_drvdata(hdev, hidpp);
+
+	hidpp->quirks = id->driver_data;
+
+	if (disable_raw_mode) {
+		hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP;
+		hidpp->quirks &= ~HIDPP_QUIRK_CONNECT_EVENTS;
+		hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT;
+	}
+
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
+		ret = wtp_allocate(hdev, id);
+		if (ret)
+			goto allocate_fail;
+	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) {
+		ret = m560_allocate(hdev);
+		if (ret)
+			goto allocate_fail;
+	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) {
+		ret = k400_allocate(hdev);
+		if (ret)
+			goto allocate_fail;
+	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
+		ret = g920_allocate(hdev);
+		if (ret)
+			goto allocate_fail;
+	}
+
+	INIT_WORK(&hidpp->work, delayed_work_cb);
+	mutex_init(&hidpp->send_mutex);
+	init_waitqueue_head(&hidpp->wait);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "%s:parse failed\n", __func__);
+		goto hid_parse_fail;
+	}
+
+	if (hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT)
+		connect_mask &= ~HID_CONNECT_HIDINPUT;
+
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
+		ret = hid_hw_start(hdev, connect_mask);
+		if (ret) {
+			hid_err(hdev, "hw start failed\n");
+			goto hid_hw_start_fail;
+		}
+		ret = hid_hw_open(hdev);
+		if (ret < 0) {
+			dev_err(&hdev->dev, "%s:hid_hw_open returned error:%d\n",
+				__func__, ret);
+			hid_hw_stop(hdev);
+			goto hid_hw_start_fail;
+		}
+	}
+
+
+	/* Allow incoming packets */
+	hid_device_io_start(hdev);
+
+	connected = hidpp_is_connected(hidpp);
+	if (id->group != HID_GROUP_LOGITECH_DJ_DEVICE) {
+		if (!connected) {
+			ret = -ENODEV;
+			hid_err(hdev, "Device not connected");
+			goto hid_hw_open_failed;
+		}
+
+		hid_info(hdev, "HID++ %u.%u device connected.\n",
+			 hidpp->protocol_major, hidpp->protocol_minor);
+	}
+
+	hidpp_overwrite_name(hdev, id->group == HID_GROUP_LOGITECH_DJ_DEVICE);
+	atomic_set(&hidpp->connected, connected);
+
+	if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) {
+		ret = wtp_get_config(hidpp);
+		if (ret)
+			goto hid_hw_open_failed;
+	} else if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) {
+		ret = g920_get_config(hidpp);
+		if (ret)
+			goto hid_hw_open_failed;
+	}
+
+	/* Block incoming packets */
+	hid_device_io_stop(hdev);
+
+	if (!(hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) {
+		ret = hid_hw_start(hdev, connect_mask);
+		if (ret) {
+			hid_err(hdev, "%s:hid_hw_start returned error\n", __func__);
+			goto hid_hw_start_fail;
+		}
+	}
+
+	if (hidpp->quirks & HIDPP_QUIRK_CONNECT_EVENTS) {
+		/* Allow incoming packets */
+		hid_device_io_start(hdev);
+
+		hidpp_connect_event(hidpp);
+	}
+
+	return ret;
+
+hid_hw_open_failed:
+	hid_device_io_stop(hdev);
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
+		device_remove_file(&hdev->dev, &dev_attr_range);
+		hid_hw_close(hdev);
+		hid_hw_stop(hdev);
+	}
+hid_hw_start_fail:
+hid_parse_fail:
+	cancel_work_sync(&hidpp->work);
+	mutex_destroy(&hidpp->send_mutex);
+allocate_fail:
+	hid_set_drvdata(hdev, NULL);
+	return ret;
+}
+
+static void hidpp_remove(struct hid_device *hdev)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
+		device_remove_file(&hdev->dev, &dev_attr_range);
+		hid_hw_close(hdev);
+	}
+	hid_hw_stop(hdev);
+	cancel_work_sync(&hidpp->work);
+	mutex_destroy(&hidpp->send_mutex);
+}
+
+static const struct hid_device_id hidpp_devices[] = {
+	{ /* wireless touchpad */
+	  HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+		USB_VENDOR_ID_LOGITECH, 0x4011),
+	  .driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT |
+			 HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS },
+	{ /* wireless touchpad T650 */
+	  HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+		USB_VENDOR_ID_LOGITECH, 0x4101),
+	  .driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT },
+	{ /* wireless touchpad T651 */
+	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
+		USB_DEVICE_ID_LOGITECH_T651),
+	  .driver_data = HIDPP_QUIRK_CLASS_WTP },
+	{ /* Mouse logitech M560 */
+	  HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+		USB_VENDOR_ID_LOGITECH, 0x402d),
+	  .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 },
+	{ /* Keyboard logitech K400 */
+	  HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+		USB_VENDOR_ID_LOGITECH, 0x4024),
+	  .driver_data = HIDPP_QUIRK_CONNECT_EVENTS | HIDPP_QUIRK_CLASS_K400 },
+
+	{ HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+		USB_VENDOR_ID_LOGITECH, HID_ANY_ID)},
+
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL),
+		.driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS},
+	{}
+};
+
+MODULE_DEVICE_TABLE(hid, hidpp_devices);
+
+static struct hid_driver hidpp_driver = {
+	.name = "logitech-hidpp-device",
+	.id_table = hidpp_devices,
+	.probe = hidpp_probe,
+	.remove = hidpp_remove,
+	.raw_event = hidpp_raw_event,
+	.input_configured = hidpp_input_configured,
+	.input_mapping = hidpp_input_mapping,
+	.input_mapped = hidpp_input_mapped,
+};
+
+module_hid_driver(hidpp_driver);
diff --git a/drivers/hid/hid-logitech-hidpp-base.h b/drivers/hid/hid-logitech-hidpp-base.h
new file mode 100644
index 0000000..4d73055
--- /dev/null
+++ b/drivers/hid/hid-logitech-hidpp-base.h
@@ -0,0 +1,81 @@
+#ifndef __HID_LOGITECH_HIDPP_H
+#define __HID_LOGITECH_HIDPP_H
+
+#include <linux/hid.h>
+#include <linux/kfifo.h>
+
+
+/*
+ * There are two hidpp protocols in use, the first version hidpp10 is known
+ * as register access protocol or RAP, the second version hidpp20 is known as
+ * feature access protocol or FAP
+ *
+ * Most older devices (including the Unifying usb receiver) use the RAP protocol
+ * where as most newer devices use the FAP protocol. Both protocols are
+ * compatible with the underlying transport, which could be usb, Unifiying, or
+ * bluetooth. The message lengths are defined by the hid vendor specific report
+ * descriptor for the HIDPP_SHORT report type (total message lenth 7 bytes) and
+ * the HIDPP_LONG report type (total message length 20 bytes)
+ *
+ * The RAP protocol uses both report types, whereas the FAP only uses HIDPP_LONG
+ * messages. The Unifying receiver itself responds to RAP messages (device index
+ * is 0xFF for the receiver), and all messages (short or long) with a device
+ * index between 1 and 6 are passed untouched to the corresponding paired
+ * Unifying device.
+ *
+ * The paired device can be RAP or FAP, it will receive the message untouched
+ * from the Unifiying receiver.
+ */
+
+#define HIDPP_REPORT_SHORT_LENGTH		7
+#define HIDPP_REPORT_LONG_LENGTH		20
+#define HIDPP_REPORT_VERY_LONG_LENGTH		64
+
+struct fap {
+	u8 feature_index;
+	u8 funcindex_clientid;
+	u8 params[HIDPP_REPORT_VERY_LONG_LENGTH - 4U];
+};
+
+struct rap {
+	u8 sub_id;
+	u8 reg_address;
+	u8 params[HIDPP_REPORT_VERY_LONG_LENGTH - 4U];
+};
+
+struct hidpp_report {
+	u8 report_id;
+	u8 device_index;
+	union {
+		struct fap fap;
+		struct rap rap;
+		u8 rawbytes[sizeof(struct fap)];
+	};
+} __packed;
+
+struct hidpp_device {
+	struct hid_device *hid_dev;
+	struct mutex send_mutex;
+	void *send_receive_buf;
+	char *name;		/* will never be NULL and should not be freed */
+	wait_queue_head_t wait;
+	bool answer_available;
+	u8 protocol_major;
+	u8 protocol_minor;
+
+	void *private_data;
+
+	struct work_struct work;
+	struct kfifo delayed_work_fifo;
+	atomic_t connected;
+	struct input_dev *delayed_input;
+
+	unsigned long quirks;
+};
+
+
+int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
+	u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count,
+	struct hidpp_report *response);
+
+#endif
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
deleted file mode 100644
index f2a4811..0000000
--- a/drivers/hid/hid-logitech-hidpp.c
+++ /dev/null
@@ -1,1900 +0,0 @@
-/*
- *  HIDPP protocol for Logitech Unifying receivers
- *
- *  Copyright (c) 2011 Logitech (c)
- *  Copyright (c) 2012-2013 Google (c)
- *  Copyright (c) 2013-2014 Red Hat Inc.
- */
-
-/*
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the Free
- * Software Foundation; version 2 of the License.
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/device.h>
-#include <linux/hid.h>
-#include <linux/module.h>
-#include <linux/slab.h>
-#include <linux/sched.h>
-#include <linux/kfifo.h>
-#include <linux/input/mt.h>
-#include <asm/unaligned.h>
-#include "hid-ids.h"
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
-MODULE_AUTHOR("Nestor Lopez Casado <nlopezcasad@logitech.com>");
-
-static bool disable_raw_mode;
-module_param(disable_raw_mode, bool, 0644);
-MODULE_PARM_DESC(disable_raw_mode,
-	"Disable Raw mode reporting for touchpads and keep firmware gestures.");
-
-static bool disable_tap_to_click;
-module_param(disable_tap_to_click, bool, 0644);
-MODULE_PARM_DESC(disable_tap_to_click,
-	"Disable Tap-To-Click mode reporting for touchpads (only on the K400 currently).");
-
-#define REPORT_ID_HIDPP_SHORT			0x10
-#define REPORT_ID_HIDPP_LONG			0x11
-#define REPORT_ID_HIDPP_VERY_LONG		0x12
-
-#define HIDPP_REPORT_SHORT_LENGTH		7
-#define HIDPP_REPORT_LONG_LENGTH		20
-#define HIDPP_REPORT_VERY_LONG_LENGTH		64
-
-#define HIDPP_QUIRK_CLASS_WTP			BIT(0)
-#define HIDPP_QUIRK_CLASS_M560			BIT(1)
-#define HIDPP_QUIRK_CLASS_K400			BIT(2)
-#define HIDPP_QUIRK_CLASS_G920			BIT(3)
-
-/* bits 2..20 are reserved for classes */
-#define HIDPP_QUIRK_CONNECT_EVENTS		BIT(21)
-#define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS	BIT(22)
-#define HIDPP_QUIRK_NO_HIDINPUT			BIT(23)
-#define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS	BIT(24)
-
-#define HIDPP_QUIRK_DELAYED_INIT		(HIDPP_QUIRK_NO_HIDINPUT | \
-						 HIDPP_QUIRK_CONNECT_EVENTS)
-
-/*
- * There are two hidpp protocols in use, the first version hidpp10 is known
- * as register access protocol or RAP, the second version hidpp20 is known as
- * feature access protocol or FAP
- *
- * Most older devices (including the Unifying usb receiver) use the RAP protocol
- * where as most newer devices use the FAP protocol. Both protocols are
- * compatible with the underlying transport, which could be usb, Unifiying, or
- * bluetooth. The message lengths are defined by the hid vendor specific report
- * descriptor for the HIDPP_SHORT report type (total message lenth 7 bytes) and
- * the HIDPP_LONG report type (total message length 20 bytes)
- *
- * The RAP protocol uses both report types, whereas the FAP only uses HIDPP_LONG
- * messages. The Unifying receiver itself responds to RAP messages (device index
- * is 0xFF for the receiver), and all messages (short or long) with a device
- * index between 1 and 6 are passed untouched to the corresponding paired
- * Unifying device.
- *
- * The paired device can be RAP or FAP, it will receive the message untouched
- * from the Unifiying receiver.
- */
-
-struct fap {
-	u8 feature_index;
-	u8 funcindex_clientid;
-	u8 params[HIDPP_REPORT_VERY_LONG_LENGTH - 4U];
-};
-
-struct rap {
-	u8 sub_id;
-	u8 reg_address;
-	u8 params[HIDPP_REPORT_VERY_LONG_LENGTH - 4U];
-};
-
-struct hidpp_report {
-	u8 report_id;
-	u8 device_index;
-	union {
-		struct fap fap;
-		struct rap rap;
-		u8 rawbytes[sizeof(struct fap)];
-	};
-} __packed;
-
-struct hidpp_device {
-	struct hid_device *hid_dev;
-	struct mutex send_mutex;
-	void *send_receive_buf;
-	char *name;		/* will never be NULL and should not be freed */
-	wait_queue_head_t wait;
-	bool answer_available;
-	u8 protocol_major;
-	u8 protocol_minor;
-
-	void *private_data;
-
-	struct work_struct work;
-	struct kfifo delayed_work_fifo;
-	atomic_t connected;
-	struct input_dev *delayed_input;
-
-	unsigned long quirks;
-};
-
-
-/* HID++ 1.0 error codes */
-#define HIDPP_ERROR				0x8f
-#define HIDPP_ERROR_SUCCESS			0x00
-#define HIDPP_ERROR_INVALID_SUBID		0x01
-#define HIDPP_ERROR_INVALID_ADRESS		0x02
-#define HIDPP_ERROR_INVALID_VALUE		0x03
-#define HIDPP_ERROR_CONNECT_FAIL		0x04
-#define HIDPP_ERROR_TOO_MANY_DEVICES		0x05
-#define HIDPP_ERROR_ALREADY_EXISTS		0x06
-#define HIDPP_ERROR_BUSY			0x07
-#define HIDPP_ERROR_UNKNOWN_DEVICE		0x08
-#define HIDPP_ERROR_RESOURCE_ERROR		0x09
-#define HIDPP_ERROR_REQUEST_UNAVAILABLE		0x0a
-#define HIDPP_ERROR_INVALID_PARAM_VALUE		0x0b
-#define HIDPP_ERROR_WRONG_PIN_CODE		0x0c
-/* HID++ 2.0 error codes */
-#define HIDPP20_ERROR				0xff
-
-static void hidpp_connect_event(struct hidpp_device *hidpp_dev);
-
-static int __hidpp_send_report(struct hid_device *hdev,
-				struct hidpp_report *hidpp_report)
-{
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-	int fields_count, ret;
-
-	hidpp = hid_get_drvdata(hdev);
-
-	switch (hidpp_report->report_id) {
-	case REPORT_ID_HIDPP_SHORT:
-		fields_count = HIDPP_REPORT_SHORT_LENGTH;
-		break;
-	case REPORT_ID_HIDPP_LONG:
-		fields_count = HIDPP_REPORT_LONG_LENGTH;
-		break;
-	case REPORT_ID_HIDPP_VERY_LONG:
-		fields_count = HIDPP_REPORT_VERY_LONG_LENGTH;
-		break;
-	default:
-		return -ENODEV;
-	}
-
-	/*
-	 * set the device_index as the receiver, it will be overwritten by
-	 * hid_hw_request if needed
-	 */
-	hidpp_report->device_index = 0xff;
-
-	if (hidpp->quirks & HIDPP_QUIRK_FORCE_OUTPUT_REPORTS) {
-		ret = hid_hw_output_report(hdev, (u8 *)hidpp_report, fields_count);
-	} else {
-		ret = hid_hw_raw_request(hdev, hidpp_report->report_id,
-			(u8 *)hidpp_report, fields_count, HID_OUTPUT_REPORT,
-			HID_REQ_SET_REPORT);
-	}
-
-	return ret == fields_count ? 0 : -1;
-}
-
-/**
- * hidpp_send_message_sync() returns 0 in case of success, and something else
- * in case of a failure.
- * - If ' something else' is positive, that means that an error has been raised
- *   by the protocol itself.
- * - If ' something else' is negative, that means that we had a classic error
- *   (-ENOMEM, -EPIPE, etc...)
- */
-static int hidpp_send_message_sync(struct hidpp_device *hidpp,
-	struct hidpp_report *message,
-	struct hidpp_report *response)
-{
-	int ret;
-
-	mutex_lock(&hidpp->send_mutex);
-
-	hidpp->send_receive_buf = response;
-	hidpp->answer_available = false;
-
-	/*
-	 * So that we can later validate the answer when it arrives
-	 * in hidpp_raw_event
-	 */
-	*response = *message;
-
-	ret = __hidpp_send_report(hidpp->hid_dev, message);
-
-	if (ret) {
-		dbg_hid("__hidpp_send_report returned err: %d\n", ret);
-		memset(response, 0, sizeof(struct hidpp_report));
-		goto exit;
-	}
-
-	if (!wait_event_timeout(hidpp->wait, hidpp->answer_available,
-				5*HZ)) {
-		dbg_hid("%s:timeout waiting for response\n", __func__);
-		memset(response, 0, sizeof(struct hidpp_report));
-		ret = -ETIMEDOUT;
-	}
-
-	if (response->report_id == REPORT_ID_HIDPP_SHORT &&
-	    response->rap.sub_id == HIDPP_ERROR) {
-		ret = response->rap.params[1];
-		dbg_hid("%s:got hidpp error %02X\n", __func__, ret);
-		goto exit;
-	}
-
-	if ((response->report_id == REPORT_ID_HIDPP_LONG ||
-			response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
-			response->fap.feature_index == HIDPP20_ERROR) {
-		ret = response->fap.params[1];
-		dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret);
-		goto exit;
-	}
-
-exit:
-	mutex_unlock(&hidpp->send_mutex);
-	return ret;
-
-}
-
-static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
-	u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count,
-	struct hidpp_report *response)
-{
-	struct hidpp_report *message;
-	int ret;
-
-	if (param_count > sizeof(message->fap.params))
-		return -EINVAL;
-
-	message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
-	if (!message)
-		return -ENOMEM;
-
-	if (param_count > (HIDPP_REPORT_LONG_LENGTH - 4))
-		message->report_id = REPORT_ID_HIDPP_VERY_LONG;
-	else
-		message->report_id = REPORT_ID_HIDPP_LONG;
-	message->fap.feature_index = feat_index;
-	message->fap.funcindex_clientid = funcindex_clientid;
-	memcpy(&message->fap.params, params, param_count);
-
-	ret = hidpp_send_message_sync(hidpp, message, response);
-	kfree(message);
-	return ret;
-}
-
-static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
-	u8 report_id, u8 sub_id, u8 reg_address, u8 *params, int param_count,
-	struct hidpp_report *response)
-{
-	struct hidpp_report *message;
-	int ret, max_count;
-
-	switch (report_id) {
-	case REPORT_ID_HIDPP_SHORT:
-		max_count = HIDPP_REPORT_SHORT_LENGTH - 4;
-		break;
-	case REPORT_ID_HIDPP_LONG:
-		max_count = HIDPP_REPORT_LONG_LENGTH - 4;
-		break;
-	case REPORT_ID_HIDPP_VERY_LONG:
-		max_count = HIDPP_REPORT_VERY_LONG_LENGTH - 4;
-		break;
-	default:
-		return -EINVAL;
-	}
-
-	if (param_count > max_count)
-		return -EINVAL;
-
-	message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
-	if (!message)
-		return -ENOMEM;
-	message->report_id = report_id;
-	message->rap.sub_id = sub_id;
-	message->rap.reg_address = reg_address;
-	memcpy(&message->rap.params, params, param_count);
-
-	ret = hidpp_send_message_sync(hidpp_dev, message, response);
-	kfree(message);
-	return ret;
-}
-
-static void delayed_work_cb(struct work_struct *work)
-{
-	struct hidpp_device *hidpp = container_of(work, struct hidpp_device,
-							work);
-	hidpp_connect_event(hidpp);
-}
-
-static inline bool hidpp_match_answer(struct hidpp_report *question,
-		struct hidpp_report *answer)
-{
-	return (answer->fap.feature_index == question->fap.feature_index) &&
-	   (answer->fap.funcindex_clientid == question->fap.funcindex_clientid);
-}
-
-static inline bool hidpp_match_error(struct hidpp_report *question,
-		struct hidpp_report *answer)
-{
-	return ((answer->rap.sub_id == HIDPP_ERROR) ||
-	    (answer->fap.feature_index == HIDPP20_ERROR)) &&
-	    (answer->fap.funcindex_clientid == question->fap.feature_index) &&
-	    (answer->fap.params[0] == question->fap.funcindex_clientid);
-}
-
-static inline bool hidpp_report_is_connect_event(struct hidpp_report *report)
-{
-	return (report->report_id == REPORT_ID_HIDPP_SHORT) &&
-		(report->rap.sub_id == 0x41);
-}
-
-/**
- * hidpp_prefix_name() prefixes the current given name with "Logitech ".
- */
-static void hidpp_prefix_name(char **name, int name_length)
-{
-#define PREFIX_LENGTH 9 /* "Logitech " */
-
-	int new_length;
-	char *new_name;
-
-	if (name_length > PREFIX_LENGTH &&
-	    strncmp(*name, "Logitech ", PREFIX_LENGTH) == 0)
-		/* The prefix has is already in the name */
-		return;
-
-	new_length = PREFIX_LENGTH + name_length;
-	new_name = kzalloc(new_length, GFP_KERNEL);
-	if (!new_name)
-		return;
-
-	snprintf(new_name, new_length, "Logitech %s", *name);
-
-	kfree(*name);
-
-	*name = new_name;
-}
-
-/* -------------------------------------------------------------------------- */
-/* HIDP++ 1.0 commands                                                        */
-/* -------------------------------------------------------------------------- */
-
-#define HIDPP_SET_REGISTER				0x80
-#define HIDPP_GET_REGISTER				0x81
-#define HIDPP_SET_LONG_REGISTER				0x82
-#define HIDPP_GET_LONG_REGISTER				0x83
-
-#define HIDPP_REG_PAIRING_INFORMATION			0xB5
-#define DEVICE_NAME					0x40
-
-static char *hidpp_get_unifying_name(struct hidpp_device *hidpp_dev)
-{
-	struct hidpp_report response;
-	int ret;
-	/* hid-logitech-dj is in charge of setting the right device index */
-	u8 params[1] = { DEVICE_NAME };
-	char *name;
-	int len;
-
-	ret = hidpp_send_rap_command_sync(hidpp_dev,
-					REPORT_ID_HIDPP_SHORT,
-					HIDPP_GET_LONG_REGISTER,
-					HIDPP_REG_PAIRING_INFORMATION,
-					params, 1, &response);
-	if (ret)
-		return NULL;
-
-	len = response.rap.params[1];
-
-	if (2 + len > sizeof(response.rap.params))
-		return NULL;
-
-	name = kzalloc(len + 1, GFP_KERNEL);
-	if (!name)
-		return NULL;
-
-	memcpy(name, &response.rap.params[2], len);
-
-	/* include the terminating '\0' */
-	hidpp_prefix_name(&name, len + 1);
-
-	return name;
-}
-
-/* -------------------------------------------------------------------------- */
-/* 0x0000: Root                                                               */
-/* -------------------------------------------------------------------------- */
-
-#define HIDPP_PAGE_ROOT					0x0000
-#define HIDPP_PAGE_ROOT_IDX				0x00
-
-#define CMD_ROOT_GET_FEATURE				0x01
-#define CMD_ROOT_GET_PROTOCOL_VERSION			0x11
-
-static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature,
-	u8 *feature_index, u8 *feature_type)
-{
-	struct hidpp_report response;
-	int ret;
-	u8 params[2] = { feature >> 8, feature & 0x00FF };
-
-	ret = hidpp_send_fap_command_sync(hidpp,
-			HIDPP_PAGE_ROOT_IDX,
-			CMD_ROOT_GET_FEATURE,
-			params, 2, &response);
-	if (ret)
-		return ret;
-
-	*feature_index = response.fap.params[0];
-	*feature_type = response.fap.params[1];
-
-	return ret;
-}
-
-static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp)
-{
-	struct hidpp_report response;
-	int ret;
-
-	ret = hidpp_send_fap_command_sync(hidpp,
-			HIDPP_PAGE_ROOT_IDX,
-			CMD_ROOT_GET_PROTOCOL_VERSION,
-			NULL, 0, &response);
-
-	if (ret == HIDPP_ERROR_INVALID_SUBID) {
-		hidpp->protocol_major = 1;
-		hidpp->protocol_minor = 0;
-		return 0;
-	}
-
-	/* the device might not be connected */
-	if (ret == HIDPP_ERROR_RESOURCE_ERROR)
-		return -EIO;
-
-	if (ret > 0) {
-		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
-			__func__, ret);
-		return -EPROTO;
-	}
-	if (ret)
-		return ret;
-
-	hidpp->protocol_major = response.fap.params[0];
-	hidpp->protocol_minor = response.fap.params[1];
-
-	return ret;
-}
-
-static bool hidpp_is_connected(struct hidpp_device *hidpp)
-{
-	int ret;
-
-	ret = hidpp_root_get_protocol_version(hidpp);
-	if (!ret)
-		hid_dbg(hidpp->hid_dev, "HID++ %u.%u device connected.\n",
-			hidpp->protocol_major, hidpp->protocol_minor);
-	return ret == 0;
-}
-
-/* -------------------------------------------------------------------------- */
-/* 0x0005: GetDeviceNameType                                                  */
-/* -------------------------------------------------------------------------- */
-
-#define HIDPP_PAGE_GET_DEVICE_NAME_TYPE			0x0005
-
-#define CMD_GET_DEVICE_NAME_TYPE_GET_COUNT		0x01
-#define CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME	0x11
-#define CMD_GET_DEVICE_NAME_TYPE_GET_TYPE		0x21
-
-static int hidpp_devicenametype_get_count(struct hidpp_device *hidpp,
-	u8 feature_index, u8 *nameLength)
-{
-	struct hidpp_report response;
-	int ret;
-
-	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
-		CMD_GET_DEVICE_NAME_TYPE_GET_COUNT, NULL, 0, &response);
-
-	if (ret > 0) {
-		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
-			__func__, ret);
-		return -EPROTO;
-	}
-	if (ret)
-		return ret;
-
-	*nameLength = response.fap.params[0];
-
-	return ret;
-}
-
-static int hidpp_devicenametype_get_device_name(struct hidpp_device *hidpp,
-	u8 feature_index, u8 char_index, char *device_name, int len_buf)
-{
-	struct hidpp_report response;
-	int ret, i;
-	int count;
-
-	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
-		CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME, &char_index, 1,
-		&response);
-
-	if (ret > 0) {
-		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
-			__func__, ret);
-		return -EPROTO;
-	}
-	if (ret)
-		return ret;
-
-	switch (response.report_id) {
-	case REPORT_ID_HIDPP_VERY_LONG:
-		count = HIDPP_REPORT_VERY_LONG_LENGTH - 4;
-		break;
-	case REPORT_ID_HIDPP_LONG:
-		count = HIDPP_REPORT_LONG_LENGTH - 4;
-		break;
-	case REPORT_ID_HIDPP_SHORT:
-		count = HIDPP_REPORT_SHORT_LENGTH - 4;
-		break;
-	default:
-		return -EPROTO;
-	}
-
-	if (len_buf < count)
-		count = len_buf;
-
-	for (i = 0; i < count; i++)
-		device_name[i] = response.fap.params[i];
-
-	return count;
-}
-
-static char *hidpp_get_device_name(struct hidpp_device *hidpp)
-{
-	u8 feature_type;
-	u8 feature_index;
-	u8 __name_length;
-	char *name;
-	unsigned index = 0;
-	int ret;
-
-	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_GET_DEVICE_NAME_TYPE,
-		&feature_index, &feature_type);
-	if (ret)
-		return NULL;
-
-	ret = hidpp_devicenametype_get_count(hidpp, feature_index,
-		&__name_length);
-	if (ret)
-		return NULL;
-
-	name = kzalloc(__name_length + 1, GFP_KERNEL);
-	if (!name)
-		return NULL;
-
-	while (index < __name_length) {
-		ret = hidpp_devicenametype_get_device_name(hidpp,
-			feature_index, index, name + index,
-			__name_length - index);
-		if (ret <= 0) {
-			kfree(name);
-			return NULL;
-		}
-		index += ret;
-	}
-
-	/* include the terminating '\0' */
-	hidpp_prefix_name(&name, __name_length + 1);
-
-	return name;
-}
-
-/* -------------------------------------------------------------------------- */
-/* 0x6010: Touchpad FW items                                                  */
-/* -------------------------------------------------------------------------- */
-
-#define HIDPP_PAGE_TOUCHPAD_FW_ITEMS			0x6010
-
-#define CMD_TOUCHPAD_FW_ITEMS_SET			0x10
-
-struct hidpp_touchpad_fw_items {
-	uint8_t presence;
-	uint8_t desired_state;
-	uint8_t state;
-	uint8_t persistent;
-};
-
-/**
- * send a set state command to the device by reading the current items->state
- * field. items is then filled with the current state.
- */
-static int hidpp_touchpad_fw_items_set(struct hidpp_device *hidpp,
-				       u8 feature_index,
-				       struct hidpp_touchpad_fw_items *items)
-{
-	struct hidpp_report response;
-	int ret;
-	u8 *params = (u8 *)response.fap.params;
-
-	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
-		CMD_TOUCHPAD_FW_ITEMS_SET, &items->state, 1, &response);
-
-	if (ret > 0) {
-		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
-			__func__, ret);
-		return -EPROTO;
-	}
-	if (ret)
-		return ret;
-
-	items->presence = params[0];
-	items->desired_state = params[1];
-	items->state = params[2];
-	items->persistent = params[3];
-
-	return 0;
-}
-
-/* -------------------------------------------------------------------------- */
-/* 0x6100: TouchPadRawXY                                                      */
-/* -------------------------------------------------------------------------- */
-
-#define HIDPP_PAGE_TOUCHPAD_RAW_XY			0x6100
-
-#define CMD_TOUCHPAD_GET_RAW_INFO			0x01
-#define CMD_TOUCHPAD_SET_RAW_REPORT_STATE		0x21
-
-#define EVENT_TOUCHPAD_RAW_XY				0x00
-
-#define TOUCHPAD_RAW_XY_ORIGIN_LOWER_LEFT		0x01
-#define TOUCHPAD_RAW_XY_ORIGIN_UPPER_LEFT		0x03
-
-struct hidpp_touchpad_raw_info {
-	u16 x_size;
-	u16 y_size;
-	u8 z_range;
-	u8 area_range;
-	u8 timestamp_unit;
-	u8 maxcontacts;
-	u8 origin;
-	u16 res;
-};
-
-struct hidpp_touchpad_raw_xy_finger {
-	u8 contact_type;
-	u8 contact_status;
-	u16 x;
-	u16 y;
-	u8 z;
-	u8 area;
-	u8 finger_id;
-};
-
-struct hidpp_touchpad_raw_xy {
-	u16 timestamp;
-	struct hidpp_touchpad_raw_xy_finger fingers[2];
-	u8 spurious_flag;
-	u8 end_of_frame;
-	u8 finger_count;
-	u8 button;
-};
-
-static int hidpp_touchpad_get_raw_info(struct hidpp_device *hidpp,
-	u8 feature_index, struct hidpp_touchpad_raw_info *raw_info)
-{
-	struct hidpp_report response;
-	int ret;
-	u8 *params = (u8 *)response.fap.params;
-
-	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
-		CMD_TOUCHPAD_GET_RAW_INFO, NULL, 0, &response);
-
-	if (ret > 0) {
-		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
-			__func__, ret);
-		return -EPROTO;
-	}
-	if (ret)
-		return ret;
-
-	raw_info->x_size = get_unaligned_be16(&params[0]);
-	raw_info->y_size = get_unaligned_be16(&params[2]);
-	raw_info->z_range = params[4];
-	raw_info->area_range = params[5];
-	raw_info->maxcontacts = params[7];
-	raw_info->origin = params[8];
-	/* res is given in unit per inch */
-	raw_info->res = get_unaligned_be16(&params[13]) * 2 / 51;
-
-	return ret;
-}
-
-static int hidpp_touchpad_set_raw_report_state(struct hidpp_device *hidpp_dev,
-		u8 feature_index, bool send_raw_reports,
-		bool sensor_enhanced_settings)
-{
-	struct hidpp_report response;
-
-	/*
-	 * Params:
-	 *   bit 0 - enable raw
-	 *   bit 1 - 16bit Z, no area
-	 *   bit 2 - enhanced sensitivity
-	 *   bit 3 - width, height (4 bits each) instead of area
-	 *   bit 4 - send raw + gestures (degrades smoothness)
-	 *   remaining bits - reserved
-	 */
-	u8 params = send_raw_reports | (sensor_enhanced_settings << 2);
-
-	return hidpp_send_fap_command_sync(hidpp_dev, feature_index,
-		CMD_TOUCHPAD_SET_RAW_REPORT_STATE, &params, 1, &response);
-}
-
-static void hidpp_touchpad_touch_event(u8 *data,
-	struct hidpp_touchpad_raw_xy_finger *finger)
-{
-	u8 x_m = data[0] << 2;
-	u8 y_m = data[2] << 2;
-
-	finger->x = x_m << 6 | data[1];
-	finger->y = y_m << 6 | data[3];
-
-	finger->contact_type = data[0] >> 6;
-	finger->contact_status = data[2] >> 6;
-
-	finger->z = data[4];
-	finger->area = data[5];
-	finger->finger_id = data[6] >> 4;
-}
-
-static void hidpp_touchpad_raw_xy_event(struct hidpp_device *hidpp_dev,
-		u8 *data, struct hidpp_touchpad_raw_xy *raw_xy)
-{
-	memset(raw_xy, 0, sizeof(struct hidpp_touchpad_raw_xy));
-	raw_xy->end_of_frame = data[8] & 0x01;
-	raw_xy->spurious_flag = (data[8] >> 1) & 0x01;
-	raw_xy->finger_count = data[15] & 0x0f;
-	raw_xy->button = (data[8] >> 2) & 0x01;
-
-	if (raw_xy->finger_count) {
-		hidpp_touchpad_touch_event(&data[2], &raw_xy->fingers[0]);
-		hidpp_touchpad_touch_event(&data[9], &raw_xy->fingers[1]);
-	}
-}
-
-/* ************************************************************************** */
-/*                                                                            */
-/* Device Support                                                             */
-/*                                                                            */
-/* ************************************************************************** */
-
-/* -------------------------------------------------------------------------- */
-/* Touchpad HID++ devices                                                     */
-/* -------------------------------------------------------------------------- */
-
-#define WTP_MANUAL_RESOLUTION				39
-
-struct wtp_data {
-	struct input_dev *input;
-	u16 x_size, y_size;
-	u8 finger_count;
-	u8 mt_feature_index;
-	u8 button_feature_index;
-	u8 maxcontacts;
-	bool flip_y;
-	unsigned int resolution;
-};
-
-static int wtp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
-		struct hid_field *field, struct hid_usage *usage,
-		unsigned long **bit, int *max)
-{
-	return -1;
-}
-
-static void wtp_populate_input(struct hidpp_device *hidpp,
-		struct input_dev *input_dev, bool origin_is_hid_core)
-{
-	struct wtp_data *wd = hidpp->private_data;
-
-	__set_bit(EV_ABS, input_dev->evbit);
-	__set_bit(EV_KEY, input_dev->evbit);
-	__clear_bit(EV_REL, input_dev->evbit);
-	__clear_bit(EV_LED, input_dev->evbit);
-
-	input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, wd->x_size, 0, 0);
-	input_abs_set_res(input_dev, ABS_MT_POSITION_X, wd->resolution);
-	input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, wd->y_size, 0, 0);
-	input_abs_set_res(input_dev, ABS_MT_POSITION_Y, wd->resolution);
-
-	/* Max pressure is not given by the devices, pick one */
-	input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 50, 0, 0);
-
-	input_set_capability(input_dev, EV_KEY, BTN_LEFT);
-
-	if (hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS)
-		input_set_capability(input_dev, EV_KEY, BTN_RIGHT);
-	else
-		__set_bit(INPUT_PROP_BUTTONPAD, input_dev->propbit);
-
-	input_mt_init_slots(input_dev, wd->maxcontacts, INPUT_MT_POINTER |
-		INPUT_MT_DROP_UNUSED);
-
-	wd->input = input_dev;
-}
-
-static void wtp_touch_event(struct wtp_data *wd,
-	struct hidpp_touchpad_raw_xy_finger *touch_report)
-{
-	int slot;
-
-	if (!touch_report->finger_id || touch_report->contact_type)
-		/* no actual data */
-		return;
-
-	slot = input_mt_get_slot_by_key(wd->input, touch_report->finger_id);
-
-	input_mt_slot(wd->input, slot);
-	input_mt_report_slot_state(wd->input, MT_TOOL_FINGER,
-					touch_report->contact_status);
-	if (touch_report->contact_status) {
-		input_event(wd->input, EV_ABS, ABS_MT_POSITION_X,
-				touch_report->x);
-		input_event(wd->input, EV_ABS, ABS_MT_POSITION_Y,
-				wd->flip_y ? wd->y_size - touch_report->y :
-					     touch_report->y);
-		input_event(wd->input, EV_ABS, ABS_MT_PRESSURE,
-				touch_report->area);
-	}
-}
-
-static void wtp_send_raw_xy_event(struct hidpp_device *hidpp,
-		struct hidpp_touchpad_raw_xy *raw)
-{
-	struct wtp_data *wd = hidpp->private_data;
-	int i;
-
-	for (i = 0; i < 2; i++)
-		wtp_touch_event(wd, &(raw->fingers[i]));
-
-	if (raw->end_of_frame &&
-	    !(hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS))
-		input_event(wd->input, EV_KEY, BTN_LEFT, raw->button);
-
-	if (raw->end_of_frame || raw->finger_count <= 2) {
-		input_mt_sync_frame(wd->input);
-		input_sync(wd->input);
-	}
-}
-
-static int wtp_mouse_raw_xy_event(struct hidpp_device *hidpp, u8 *data)
-{
-	struct wtp_data *wd = hidpp->private_data;
-	u8 c1_area = ((data[7] & 0xf) * (data[7] & 0xf) +
-		      (data[7] >> 4) * (data[7] >> 4)) / 2;
-	u8 c2_area = ((data[13] & 0xf) * (data[13] & 0xf) +
-		      (data[13] >> 4) * (data[13] >> 4)) / 2;
-	struct hidpp_touchpad_raw_xy raw = {
-		.timestamp = data[1],
-		.fingers = {
-			{
-				.contact_type = 0,
-				.contact_status = !!data[7],
-				.x = get_unaligned_le16(&data[3]),
-				.y = get_unaligned_le16(&data[5]),
-				.z = c1_area,
-				.area = c1_area,
-				.finger_id = data[2],
-			}, {
-				.contact_type = 0,
-				.contact_status = !!data[13],
-				.x = get_unaligned_le16(&data[9]),
-				.y = get_unaligned_le16(&data[11]),
-				.z = c2_area,
-				.area = c2_area,
-				.finger_id = data[8],
-			}
-		},
-		.finger_count = wd->maxcontacts,
-		.spurious_flag = 0,
-		.end_of_frame = (data[0] >> 7) == 0,
-		.button = data[0] & 0x01,
-	};
-
-	wtp_send_raw_xy_event(hidpp, &raw);
-
-	return 1;
-}
-
-static int wtp_raw_event(struct hid_device *hdev, u8 *data, int size)
-{
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-	struct wtp_data *wd = hidpp->private_data;
-	struct hidpp_report *report = (struct hidpp_report *)data;
-	struct hidpp_touchpad_raw_xy raw;
-
-	if (!wd || !wd->input)
-		return 1;
-
-	switch (data[0]) {
-	case 0x02:
-		if (size < 2) {
-			hid_err(hdev, "Received HID report of bad size (%d)",
-				size);
-			return 1;
-		}
-		if (hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS) {
-			input_event(wd->input, EV_KEY, BTN_LEFT,
-					!!(data[1] & 0x01));
-			input_event(wd->input, EV_KEY, BTN_RIGHT,
-					!!(data[1] & 0x02));
-			input_sync(wd->input);
-			return 0;
-		} else {
-			if (size < 21)
-				return 1;
-			return wtp_mouse_raw_xy_event(hidpp, &data[7]);
-		}
-	case REPORT_ID_HIDPP_LONG:
-		/* size is already checked in hidpp_raw_event. */
-		if ((report->fap.feature_index != wd->mt_feature_index) ||
-		    (report->fap.funcindex_clientid != EVENT_TOUCHPAD_RAW_XY))
-			return 1;
-		hidpp_touchpad_raw_xy_event(hidpp, data + 4, &raw);
-
-		wtp_send_raw_xy_event(hidpp, &raw);
-		return 0;
-	}
-
-	return 0;
-}
-
-static int wtp_get_config(struct hidpp_device *hidpp)
-{
-	struct wtp_data *wd = hidpp->private_data;
-	struct hidpp_touchpad_raw_info raw_info = {0};
-	u8 feature_type;
-	int ret;
-
-	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_TOUCHPAD_RAW_XY,
-		&wd->mt_feature_index, &feature_type);
-	if (ret)
-		/* means that the device is not powered up */
-		return ret;
-
-	ret = hidpp_touchpad_get_raw_info(hidpp, wd->mt_feature_index,
-		&raw_info);
-	if (ret)
-		return ret;
-
-	wd->x_size = raw_info.x_size;
-	wd->y_size = raw_info.y_size;
-	wd->maxcontacts = raw_info.maxcontacts;
-	wd->flip_y = raw_info.origin == TOUCHPAD_RAW_XY_ORIGIN_LOWER_LEFT;
-	wd->resolution = raw_info.res;
-	if (!wd->resolution)
-		wd->resolution = WTP_MANUAL_RESOLUTION;
-
-	return 0;
-}
-
-static int wtp_allocate(struct hid_device *hdev, const struct hid_device_id *id)
-{
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-	struct wtp_data *wd;
-
-	wd = devm_kzalloc(&hdev->dev, sizeof(struct wtp_data),
-			GFP_KERNEL);
-	if (!wd)
-		return -ENOMEM;
-
-	hidpp->private_data = wd;
-
-	return 0;
-};
-
-static int wtp_connect(struct hid_device *hdev, bool connected)
-{
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-	struct wtp_data *wd = hidpp->private_data;
-	int ret;
-
-	if (!connected)
-		return 0;
-
-	if (!wd->x_size) {
-		ret = wtp_get_config(hidpp);
-		if (ret) {
-			hid_err(hdev, "Can not get wtp config: %d\n", ret);
-			return ret;
-		}
-	}
-
-	return hidpp_touchpad_set_raw_report_state(hidpp, wd->mt_feature_index,
-			true, true);
-}
-
-/* ------------------------------------------------------------------------- */
-/* Logitech M560 devices                                                     */
-/* ------------------------------------------------------------------------- */
-
-/*
- * Logitech M560 protocol overview
- *
- * The Logitech M560 mouse, is designed for windows 8. When the middle and/or
- * the sides buttons are pressed, it sends some keyboard keys events
- * instead of buttons ones.
- * To complicate things further, the middle button keys sequence
- * is different from the odd press and the even press.
- *
- * forward button -> Super_R
- * backward button -> Super_L+'d' (press only)
- * middle button -> 1st time: Alt_L+SuperL+XF86TouchpadOff (press only)
- *                  2nd time: left-click (press only)
- * NB: press-only means that when the button is pressed, the
- * KeyPress/ButtonPress and KeyRelease/ButtonRelease events are generated
- * together sequentially; instead when the button is released, no event is
- * generated !
- *
- * With the command
- *	10<xx>0a 3500af03 (where <xx> is the mouse id),
- * the mouse reacts differently:
- * - it never sends a keyboard key event
- * - for the three mouse button it sends:
- *	middle button               press   11<xx>0a 3500af00...
- *	side 1 button (forward)     press   11<xx>0a 3500b000...
- *	side 2 button (backward)    press   11<xx>0a 3500ae00...
- *	middle/side1/side2 button   release 11<xx>0a 35000000...
- */
-
-static const u8 m560_config_parameter[] = {0x00, 0xaf, 0x03};
-
-struct m560_private_data {
-	struct input_dev *input;
-};
-
-/* how buttons are mapped in the report */
-#define M560_MOUSE_BTN_LEFT		0x01
-#define M560_MOUSE_BTN_RIGHT		0x02
-#define M560_MOUSE_BTN_WHEEL_LEFT	0x08
-#define M560_MOUSE_BTN_WHEEL_RIGHT	0x10
-
-#define M560_SUB_ID			0x0a
-#define M560_BUTTON_MODE_REGISTER	0x35
-
-static int m560_send_config_command(struct hid_device *hdev, bool connected)
-{
-	struct hidpp_report response;
-	struct hidpp_device *hidpp_dev;
-
-	hidpp_dev = hid_get_drvdata(hdev);
-
-	if (!connected)
-		return -ENODEV;
-
-	return hidpp_send_rap_command_sync(
-		hidpp_dev,
-		REPORT_ID_HIDPP_SHORT,
-		M560_SUB_ID,
-		M560_BUTTON_MODE_REGISTER,
-		(u8 *)m560_config_parameter,
-		sizeof(m560_config_parameter),
-		&response
-	);
-}
-
-static int m560_allocate(struct hid_device *hdev)
-{
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-	struct m560_private_data *d;
-
-	d = devm_kzalloc(&hdev->dev, sizeof(struct m560_private_data),
-			GFP_KERNEL);
-	if (!d)
-		return -ENOMEM;
-
-	hidpp->private_data = d;
-
-	return 0;
-};
-
-static int m560_raw_event(struct hid_device *hdev, u8 *data, int size)
-{
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-	struct m560_private_data *mydata = hidpp->private_data;
-
-	/* sanity check */
-	if (!mydata || !mydata->input) {
-		hid_err(hdev, "error in parameter\n");
-		return -EINVAL;
-	}
-
-	if (size < 7) {
-		hid_err(hdev, "error in report\n");
-		return 0;
-	}
-
-	if (data[0] == REPORT_ID_HIDPP_LONG &&
-	    data[2] == M560_SUB_ID && data[6] == 0x00) {
-		/*
-		 * m560 mouse report for middle, forward and backward button
-		 *
-		 * data[0] = 0x11
-		 * data[1] = device-id
-		 * data[2] = 0x0a
-		 * data[5] = 0xaf -> middle
-		 *	     0xb0 -> forward
-		 *	     0xae -> backward
-		 *	     0x00 -> release all
-		 * data[6] = 0x00
-		 */
-
-		switch (data[5]) {
-		case 0xaf:
-			input_report_key(mydata->input, BTN_MIDDLE, 1);
-			break;
-		case 0xb0:
-			input_report_key(mydata->input, BTN_FORWARD, 1);
-			break;
-		case 0xae:
-			input_report_key(mydata->input, BTN_BACK, 1);
-			break;
-		case 0x00:
-			input_report_key(mydata->input, BTN_BACK, 0);
-			input_report_key(mydata->input, BTN_FORWARD, 0);
-			input_report_key(mydata->input, BTN_MIDDLE, 0);
-			break;
-		default:
-			hid_err(hdev, "error in report\n");
-			return 0;
-		}
-		input_sync(mydata->input);
-
-	} else if (data[0] == 0x02) {
-		/*
-		 * Logitech M560 mouse report
-		 *
-		 * data[0] = type (0x02)
-		 * data[1..2] = buttons
-		 * data[3..5] = xy
-		 * data[6] = wheel
-		 */
-
-		int v;
-
-		input_report_key(mydata->input, BTN_LEFT,
-			!!(data[1] & M560_MOUSE_BTN_LEFT));
-		input_report_key(mydata->input, BTN_RIGHT,
-			!!(data[1] & M560_MOUSE_BTN_RIGHT));
-
-		if (data[1] & M560_MOUSE_BTN_WHEEL_LEFT)
-			input_report_rel(mydata->input, REL_HWHEEL, -1);
-		else if (data[1] & M560_MOUSE_BTN_WHEEL_RIGHT)
-			input_report_rel(mydata->input, REL_HWHEEL, 1);
-
-		v = hid_snto32(hid_field_extract(hdev, data+3, 0, 12), 12);
-		input_report_rel(mydata->input, REL_X, v);
-
-		v = hid_snto32(hid_field_extract(hdev, data+3, 12, 12), 12);
-		input_report_rel(mydata->input, REL_Y, v);
-
-		v = hid_snto32(data[6], 8);
-		input_report_rel(mydata->input, REL_WHEEL, v);
-
-		input_sync(mydata->input);
-	}
-
-	return 1;
-}
-
-static void m560_populate_input(struct hidpp_device *hidpp,
-		struct input_dev *input_dev, bool origin_is_hid_core)
-{
-	struct m560_private_data *mydata = hidpp->private_data;
-
-	mydata->input = input_dev;
-
-	__set_bit(EV_KEY, mydata->input->evbit);
-	__set_bit(BTN_MIDDLE, mydata->input->keybit);
-	__set_bit(BTN_RIGHT, mydata->input->keybit);
-	__set_bit(BTN_LEFT, mydata->input->keybit);
-	__set_bit(BTN_BACK, mydata->input->keybit);
-	__set_bit(BTN_FORWARD, mydata->input->keybit);
-
-	__set_bit(EV_REL, mydata->input->evbit);
-	__set_bit(REL_X, mydata->input->relbit);
-	__set_bit(REL_Y, mydata->input->relbit);
-	__set_bit(REL_WHEEL, mydata->input->relbit);
-	__set_bit(REL_HWHEEL, mydata->input->relbit);
-}
-
-static int m560_input_mapping(struct hid_device *hdev, struct hid_input *hi,
-		struct hid_field *field, struct hid_usage *usage,
-		unsigned long **bit, int *max)
-{
-	return -1;
-}
-
-/* ------------------------------------------------------------------------- */
-/* Logitech K400 devices                                                     */
-/* ------------------------------------------------------------------------- */
-
-/*
- * The Logitech K400 keyboard has an embedded touchpad which is seen
- * as a mouse from the OS point of view. There is a hardware shortcut to disable
- * tap-to-click but the setting is not remembered accross reset, annoying some
- * users.
- *
- * We can toggle this feature from the host by using the feature 0x6010:
- * Touchpad FW items
- */
-
-struct k400_private_data {
-	u8 feature_index;
-};
-
-static int k400_disable_tap_to_click(struct hidpp_device *hidpp)
-{
-	struct k400_private_data *k400 = hidpp->private_data;
-	struct hidpp_touchpad_fw_items items = {};
-	int ret;
-	u8 feature_type;
-
-	if (!k400->feature_index) {
-		ret = hidpp_root_get_feature(hidpp,
-			HIDPP_PAGE_TOUCHPAD_FW_ITEMS,
-			&k400->feature_index, &feature_type);
-		if (ret)
-			/* means that the device is not powered up */
-			return ret;
-	}
-
-	ret = hidpp_touchpad_fw_items_set(hidpp, k400->feature_index, &items);
-	if (ret)
-		return ret;
-
-	return 0;
-}
-
-static int k400_allocate(struct hid_device *hdev)
-{
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-	struct k400_private_data *k400;
-
-	k400 = devm_kzalloc(&hdev->dev, sizeof(struct k400_private_data),
-			    GFP_KERNEL);
-	if (!k400)
-		return -ENOMEM;
-
-	hidpp->private_data = k400;
-
-	return 0;
-};
-
-static int k400_connect(struct hid_device *hdev, bool connected)
-{
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-
-	if (!connected)
-		return 0;
-
-	if (!disable_tap_to_click)
-		return 0;
-
-	return k400_disable_tap_to_click(hidpp);
-}
-
-/* ------------------------------------------------------------------------- */
-/* Logitech G920 Driving Force Racing Wheel for Xbox One                     */
-/* ------------------------------------------------------------------------- */
-
-#define HIDPP_PAGE_G920_FORCE_FEEDBACK			0x8123
-
-/* Using session ID = 1 */
-#define CMD_G920_FORCE_GET_APERTURE			0x51
-#define CMD_G920_FORCE_SET_APERTURE			0x61
-
-struct g920_private_data {
-	u8 force_feature;
-	u16 range;
-};
-
-#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)
-
-static ssize_t g920_range_show(struct device *dev, struct device_attribute *attr,
-				char *buf)
-{
-	struct hid_device *hid = to_hid_device(dev);
-	struct hidpp_device *hidpp = hid_get_drvdata(hid);
-	struct g920_private_data *pdata;
-
-	pdata = hidpp->private_data;
-	if (!pdata) {
-		hid_err(hid, "Private driver data not found!\n");
-		return -EINVAL;
-	}
-
-	return scnprintf(buf, PAGE_SIZE, "%u\n", pdata->range);
-}
-
-static ssize_t g920_range_store(struct device *dev, struct device_attribute *attr,
-				 const char *buf, size_t count)
-{
-	struct hid_device *hid = to_hid_device(dev);
-	struct hidpp_device *hidpp = hid_get_drvdata(hid);
-	struct g920_private_data *pdata;
-	struct hidpp_report response;
-	u8 params[2];
-	int ret;
-	u16 range = simple_strtoul(buf, NULL, 10);
-
-	pdata = hidpp->private_data;
-	if (!pdata) {
-		hid_err(hid, "Private driver data not found!\n");
-		return -EINVAL;
-	}
-
-	if (range < 180)
-		range = 180;
-	else if (range > 900)
-		range = 900;
-
-	params[0] = range >> 8;
-	params[1] = range & 0x00FF;
-
-	ret = hidpp_send_fap_command_sync(hidpp, pdata->force_feature,
-		CMD_G920_FORCE_SET_APERTURE, params, 2, &response);
-	if (ret)
-		return ret;
-
-	pdata->range = range;
-	return count;
-}
-
-static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, g920_range_show, g920_range_store);
-
-static int g920_allocate(struct hid_device *hdev)
-{
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-	struct g920_private_data *pdata;
-
-	pdata = devm_kzalloc(&hdev->dev, sizeof(struct g920_private_data),
-			GFP_KERNEL);
-	if (!pdata)
-		return -ENOMEM;
-
-	hidpp->private_data = pdata;
-
-	return 0;
-}
-
-static int g920_get_config(struct hidpp_device *hidpp)
-{
-	struct g920_private_data *pdata = hidpp->private_data;
-	struct hidpp_report response;
-	u8 feature_type;
-	u8 feature_index;
-	int ret;
-
-	pdata = hidpp->private_data;
-	if (!pdata) {
-		hid_err(hidpp->hid_dev, "Private driver data not found!\n");
-		return -EINVAL;
-	}
-
-	/* Find feature and store for later use */
-	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_G920_FORCE_FEEDBACK,
-		&feature_index, &feature_type);
-	if (ret)
-		return ret;
-
-	pdata->force_feature = feature_index;
-
-	/* Read current Range */
-	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
-		CMD_G920_FORCE_GET_APERTURE, NULL, 0, &response);
-	if (ret > 0) {
-		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
-			__func__, ret);
-		return -EPROTO;
-	}
-	if (ret)
-		return ret;
-
-	pdata->range = get_unaligned_be16(&response.fap.params[0]);
-
-	/* Create sysfs interface */
-	ret = device_create_file(&(hidpp->hid_dev->dev), &dev_attr_range);
-	if (ret)
-		hid_warn(hidpp->hid_dev, "Unable to create sysfs interface for \"range\", errno %d\n", ret);
-
-	return 0;
-}
-
-/* -------------------------------------------------------------------------- */
-/* Generic HID++ devices                                                      */
-/* -------------------------------------------------------------------------- */
-
-static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
-		struct hid_field *field, struct hid_usage *usage,
-		unsigned long **bit, int *max)
-{
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-
-	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
-		return wtp_input_mapping(hdev, hi, field, usage, bit, max);
-	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560 &&
-			field->application != HID_GD_MOUSE)
-		return m560_input_mapping(hdev, hi, field, usage, bit, max);
-
-	return 0;
-}
-
-static int hidpp_input_mapped(struct hid_device *hdev, struct hid_input *hi,
-		struct hid_field *field, struct hid_usage *usage,
-		unsigned long **bit, int *max)
-{
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-
-	/* Ensure that Logitech G920 is not given a default fuzz/flat value */
-	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
-		if (usage->type == EV_ABS && (usage->code == ABS_X ||
-				usage->code == ABS_Y || usage->code == ABS_Z ||
-				usage->code == ABS_RZ)) {
-			field->application = HID_GD_MULTIAXIS;
-		}
-	}
-
-	return 0;
-}
-
-
-static void hidpp_populate_input(struct hidpp_device *hidpp,
-		struct input_dev *input, bool origin_is_hid_core)
-{
-	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
-		wtp_populate_input(hidpp, input, origin_is_hid_core);
-	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
-		m560_populate_input(hidpp, input, origin_is_hid_core);
-}
-
-static int hidpp_input_configured(struct hid_device *hdev,
-				struct hid_input *hidinput)
-{
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-	struct input_dev *input = hidinput->input;
-
-	hidpp_populate_input(hidpp, input, true);
-
-	return 0;
-}
-
-static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
-		int size)
-{
-	struct hidpp_report *question = hidpp->send_receive_buf;
-	struct hidpp_report *answer = hidpp->send_receive_buf;
-	struct hidpp_report *report = (struct hidpp_report *)data;
-
-	/*
-	 * If the mutex is locked then we have a pending answer from a
-	 * previously sent command.
-	 */
-	if (unlikely(mutex_is_locked(&hidpp->send_mutex))) {
-		/*
-		 * Check for a correct hidpp20 answer or the corresponding
-		 * error
-		 */
-		if (hidpp_match_answer(question, report) ||
-				hidpp_match_error(question, report)) {
-			*answer = *report;
-			hidpp->answer_available = true;
-			wake_up(&hidpp->wait);
-			/*
-			 * This was an answer to a command that this driver sent
-			 * We return 1 to hid-core to avoid forwarding the
-			 * command upstream as it has been treated by the driver
-			 */
-
-			return 1;
-		}
-	}
-
-	if (unlikely(hidpp_report_is_connect_event(report))) {
-		atomic_set(&hidpp->connected,
-				!(report->rap.params[0] & (1 << 6)));
-		if ((hidpp->quirks & HIDPP_QUIRK_CONNECT_EVENTS) &&
-		    (schedule_work(&hidpp->work) == 0))
-			dbg_hid("%s: connect event already queued\n", __func__);
-		return 1;
-	}
-
-	return 0;
-}
-
-static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
-		u8 *data, int size)
-{
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-	int ret = 0;
-
-	/* Generic HID++ processing. */
-	switch (data[0]) {
-	case REPORT_ID_HIDPP_VERY_LONG:
-		if (size != HIDPP_REPORT_VERY_LONG_LENGTH) {
-			hid_err(hdev, "received hid++ report of bad size (%d)",
-				size);
-			return 1;
-		}
-		ret = hidpp_raw_hidpp_event(hidpp, data, size);
-		break;
-	case REPORT_ID_HIDPP_LONG:
-		if (size != HIDPP_REPORT_LONG_LENGTH) {
-			hid_err(hdev, "received hid++ report of bad size (%d)",
-				size);
-			return 1;
-		}
-		ret = hidpp_raw_hidpp_event(hidpp, data, size);
-		break;
-	case REPORT_ID_HIDPP_SHORT:
-		if (size != HIDPP_REPORT_SHORT_LENGTH) {
-			hid_err(hdev, "received hid++ report of bad size (%d)",
-				size);
-			return 1;
-		}
-		ret = hidpp_raw_hidpp_event(hidpp, data, size);
-		break;
-	}
-
-	/* If no report is available for further processing, skip calling
-	 * raw_event of subclasses. */
-	if (ret != 0)
-		return ret;
-
-	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
-		return wtp_raw_event(hdev, data, size);
-	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
-		return m560_raw_event(hdev, data, size);
-
-	return 0;
-}
-
-static void hidpp_overwrite_name(struct hid_device *hdev, bool use_unifying)
-{
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-	char *name;
-
-	if (use_unifying)
-		/*
-		 * the device is connected through an Unifying receiver, and
-		 * might not be already connected.
-		 * Ask the receiver for its name.
-		 */
-		name = hidpp_get_unifying_name(hidpp);
-	else
-		name = hidpp_get_device_name(hidpp);
-
-	if (!name) {
-		hid_err(hdev, "unable to retrieve the name of the device");
-	} else {
-		dbg_hid("HID++: Got name: %s\n", name);
-		snprintf(hdev->name, sizeof(hdev->name), "%s", name);
-	}
-
-	kfree(name);
-}
-
-static int hidpp_input_open(struct input_dev *dev)
-{
-	struct hid_device *hid = input_get_drvdata(dev);
-
-	return hid_hw_open(hid);
-}
-
-static void hidpp_input_close(struct input_dev *dev)
-{
-	struct hid_device *hid = input_get_drvdata(dev);
-
-	hid_hw_close(hid);
-}
-
-static struct input_dev *hidpp_allocate_input(struct hid_device *hdev)
-{
-	struct input_dev *input_dev = devm_input_allocate_device(&hdev->dev);
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-
-	if (!input_dev)
-		return NULL;
-
-	input_set_drvdata(input_dev, hdev);
-	input_dev->open = hidpp_input_open;
-	input_dev->close = hidpp_input_close;
-
-	input_dev->name = hidpp->name;
-	input_dev->phys = hdev->phys;
-	input_dev->uniq = hdev->uniq;
-	input_dev->id.bustype = hdev->bus;
-	input_dev->id.vendor  = hdev->vendor;
-	input_dev->id.product = hdev->product;
-	input_dev->id.version = hdev->version;
-	input_dev->dev.parent = &hdev->dev;
-
-	return input_dev;
-}
-
-static void hidpp_connect_event(struct hidpp_device *hidpp)
-{
-	struct hid_device *hdev = hidpp->hid_dev;
-	int ret = 0;
-	bool connected = atomic_read(&hidpp->connected);
-	struct input_dev *input;
-	char *name, *devm_name;
-
-	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
-		ret = wtp_connect(hdev, connected);
-		if (ret)
-			return;
-	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) {
-		ret = m560_send_config_command(hdev, connected);
-		if (ret)
-			return;
-	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) {
-		ret = k400_connect(hdev, connected);
-		if (ret)
-			return;
-	}
-
-	if (!connected || hidpp->delayed_input)
-		return;
-
-	/* the device is already connected, we can ask for its name and
-	 * protocol */
-	if (!hidpp->protocol_major) {
-		ret = !hidpp_is_connected(hidpp);
-		if (ret) {
-			hid_err(hdev, "Can not get the protocol version.\n");
-			return;
-		}
-		hid_info(hdev, "HID++ %u.%u device connected.\n",
-			 hidpp->protocol_major, hidpp->protocol_minor);
-	}
-
-	if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT))
-		/* if HID created the input nodes for us, we can stop now */
-		return;
-
-	if (!hidpp->name || hidpp->name == hdev->name) {
-		name = hidpp_get_device_name(hidpp);
-		if (!name) {
-			hid_err(hdev,
-				"unable to retrieve the name of the device");
-			return;
-		}
-
-		devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s", name);
-		kfree(name);
-		if (!devm_name)
-			return;
-
-		hidpp->name = devm_name;
-	}
-
-	input = hidpp_allocate_input(hdev);
-	if (!input) {
-		hid_err(hdev, "cannot allocate new input device: %d\n", ret);
-		return;
-	}
-
-	hidpp_populate_input(hidpp, input, false);
-
-	ret = input_register_device(input);
-	if (ret)
-		input_free_device(input);
-
-	hidpp->delayed_input = input;
-}
-
-static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
-{
-	struct hidpp_device *hidpp;
-	int ret;
-	bool connected;
-	unsigned int connect_mask = HID_CONNECT_DEFAULT;
-
-	hidpp = devm_kzalloc(&hdev->dev, sizeof(struct hidpp_device),
-			GFP_KERNEL);
-	if (!hidpp)
-		return -ENOMEM;
-
-	hidpp->hid_dev = hdev;
-	hidpp->name = hdev->name;
-	hid_set_drvdata(hdev, hidpp);
-
-	hidpp->quirks = id->driver_data;
-
-	if (disable_raw_mode) {
-		hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP;
-		hidpp->quirks &= ~HIDPP_QUIRK_CONNECT_EVENTS;
-		hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT;
-	}
-
-	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
-		ret = wtp_allocate(hdev, id);
-		if (ret)
-			goto allocate_fail;
-	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) {
-		ret = m560_allocate(hdev);
-		if (ret)
-			goto allocate_fail;
-	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) {
-		ret = k400_allocate(hdev);
-		if (ret)
-			goto allocate_fail;
-	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
-		ret = g920_allocate(hdev);
-		if (ret)
-			goto allocate_fail;
-	}
-
-	INIT_WORK(&hidpp->work, delayed_work_cb);
-	mutex_init(&hidpp->send_mutex);
-	init_waitqueue_head(&hidpp->wait);
-
-	ret = hid_parse(hdev);
-	if (ret) {
-		hid_err(hdev, "%s:parse failed\n", __func__);
-		goto hid_parse_fail;
-	}
-
-	if (hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT)
-		connect_mask &= ~HID_CONNECT_HIDINPUT;
-
-	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
-		ret = hid_hw_start(hdev, connect_mask);
-		if (ret) {
-			hid_err(hdev, "hw start failed\n");
-			goto hid_hw_start_fail;
-		}
-		ret = hid_hw_open(hdev);
-		if (ret < 0) {
-			dev_err(&hdev->dev, "%s:hid_hw_open returned error:%d\n",
-				__func__, ret);
-			hid_hw_stop(hdev);
-			goto hid_hw_start_fail;
-		}
-	}
-
-
-	/* Allow incoming packets */
-	hid_device_io_start(hdev);
-
-	connected = hidpp_is_connected(hidpp);
-	if (id->group != HID_GROUP_LOGITECH_DJ_DEVICE) {
-		if (!connected) {
-			ret = -ENODEV;
-			hid_err(hdev, "Device not connected");
-			goto hid_hw_open_failed;
-		}
-
-		hid_info(hdev, "HID++ %u.%u device connected.\n",
-			 hidpp->protocol_major, hidpp->protocol_minor);
-	}
-
-	hidpp_overwrite_name(hdev, id->group == HID_GROUP_LOGITECH_DJ_DEVICE);
-	atomic_set(&hidpp->connected, connected);
-
-	if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) {
-		ret = wtp_get_config(hidpp);
-		if (ret)
-			goto hid_hw_open_failed;
-	} else if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) {
-		ret = g920_get_config(hidpp);
-		if (ret)
-			goto hid_hw_open_failed;
-	}
-
-	/* Block incoming packets */
-	hid_device_io_stop(hdev);
-
-	if (!(hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) {
-		ret = hid_hw_start(hdev, connect_mask);
-		if (ret) {
-			hid_err(hdev, "%s:hid_hw_start returned error\n", __func__);
-			goto hid_hw_start_fail;
-		}
-	}
-
-	if (hidpp->quirks & HIDPP_QUIRK_CONNECT_EVENTS) {
-		/* Allow incoming packets */
-		hid_device_io_start(hdev);
-
-		hidpp_connect_event(hidpp);
-	}
-
-	return ret;
-
-hid_hw_open_failed:
-	hid_device_io_stop(hdev);
-	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
-		device_remove_file(&hdev->dev, &dev_attr_range);
-		hid_hw_close(hdev);
-		hid_hw_stop(hdev);
-	}
-hid_hw_start_fail:
-hid_parse_fail:
-	cancel_work_sync(&hidpp->work);
-	mutex_destroy(&hidpp->send_mutex);
-allocate_fail:
-	hid_set_drvdata(hdev, NULL);
-	return ret;
-}
-
-static void hidpp_remove(struct hid_device *hdev)
-{
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-
-	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
-		device_remove_file(&hdev->dev, &dev_attr_range);
-		hid_hw_close(hdev);
-	}
-	hid_hw_stop(hdev);
-	cancel_work_sync(&hidpp->work);
-	mutex_destroy(&hidpp->send_mutex);
-}
-
-static const struct hid_device_id hidpp_devices[] = {
-	{ /* wireless touchpad */
-	  HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
-		USB_VENDOR_ID_LOGITECH, 0x4011),
-	  .driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT |
-			 HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS },
-	{ /* wireless touchpad T650 */
-	  HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
-		USB_VENDOR_ID_LOGITECH, 0x4101),
-	  .driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT },
-	{ /* wireless touchpad T651 */
-	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
-		USB_DEVICE_ID_LOGITECH_T651),
-	  .driver_data = HIDPP_QUIRK_CLASS_WTP },
-	{ /* Mouse logitech M560 */
-	  HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
-		USB_VENDOR_ID_LOGITECH, 0x402d),
-	  .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 },
-	{ /* Keyboard logitech K400 */
-	  HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
-		USB_VENDOR_ID_LOGITECH, 0x4024),
-	  .driver_data = HIDPP_QUIRK_CONNECT_EVENTS | HIDPP_QUIRK_CLASS_K400 },
-
-	{ HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
-		USB_VENDOR_ID_LOGITECH, HID_ANY_ID)},
-
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL),
-		.driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS},
-	{}
-};
-
-MODULE_DEVICE_TABLE(hid, hidpp_devices);
-
-static struct hid_driver hidpp_driver = {
-	.name = "logitech-hidpp-device",
-	.id_table = hidpp_devices,
-	.probe = hidpp_probe,
-	.remove = hidpp_remove,
-	.raw_event = hidpp_raw_event,
-	.input_configured = hidpp_input_configured,
-	.input_mapping = hidpp_input_mapping,
-	.input_mapped = hidpp_input_mapped,
-};
-
-module_hid_driver(hidpp_driver);
-- 
2.5.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 2/2] HID: Force feedback support for the Logitech G920
  2015-11-21 12:37 [PATCH 0/2] HID: Force feedback support for the Logitech G920 Wheel Edwin Velds
  2015-11-21 12:37 ` [PATCH 1/2] HID: Create header for Logitech HID++ driver Edwin Velds
@ 2015-11-21 12:37 ` Edwin Velds
  2015-11-26 14:42 ` [PATCH 0/2] HID: Force feedback support for the Logitech G920 Wheel Jiri Kosina
  2015-11-28  7:53 ` Elias Vanderstuyft
  3 siblings, 0 replies; 6+ messages in thread
From: Edwin Velds @ 2015-11-21 12:37 UTC (permalink / raw)
  To: linux-input
  Cc: linux-kernel, Simon Wood, Jiri Kosina, Michal Malý,
	elias vanderstuyft, Benjamin Tissoires, Dmitry Torokhov,
	Edwin Velds

This patch implements force feedback support for the Logitech
G920 Driving Force Racing Wheel. It is a generic implementation
of feature 0x8123 of the Logitech HID++ protocol and should be
usable for any future devices that implement this feature.

The driving supports everything that is supported by the G920
firmware:
	FF_CONSTANT
	FF_PERIODIC
	FF_SINE
	FF_SQUARE
	FF_SAW_UP
	FF_SAW_DOWN
	FF_TRIANGLE
	FF_SPRING
	FF_DAMPER
	FF_AUTOCENTER
	FF_GAIN

Both envelopes and replay values are supported as well.

Note that the G920 (like previous Logitech wheels) starts with
a heavy default spring enabled. Unlike previous wheels, there
is no command to disable this spring and it will only release
when other forces are downloaded into the device.

Signed-off-by: Edwin Velds <e.velds@gmail.com>
---
 drivers/hid/Makefile                  |   2 +-
 drivers/hid/hid-logitech-hidpp-base.c | 114 +------
 drivers/hid/hid-logitech-hidpp-ff.c   | 540 ++++++++++++++++++++++++++++++++++
 drivers/hid/hid-logitech-hidpp-ff.h   |   9 +
 4 files changed, 554 insertions(+), 111 deletions(-)
 create mode 100644 drivers/hid/hid-logitech-hidpp-ff.c
 create mode 100644 drivers/hid/hid-logitech-hidpp-ff.h

diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index a01c448..fd49428 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -17,7 +17,7 @@ hid-logitech-$(CONFIG_LOGIRUMBLEPAD2_FF)	+= hid-lg2ff.o
 hid-logitech-$(CONFIG_LOGIG940_FF)	+= hid-lg3ff.o
 hid-logitech-$(CONFIG_LOGIWHEELS_FF)	+= hid-lg4ff.o
 
-hid-logitech-hidpp-y	:= hid-logitech-hidpp-base.o
+hid-logitech-hidpp-y	:= hid-logitech-hidpp-base.o hid-logitech-hidpp-ff.o
 
 hid-wiimote-y		:= hid-wiimote-core.o hid-wiimote-modules.o
 hid-wiimote-$(CONFIG_DEBUG_FS)	+= hid-wiimote-debug.o
diff --git a/drivers/hid/hid-logitech-hidpp-base.c b/drivers/hid/hid-logitech-hidpp-base.c
index 40ab079..9b6d82a 100644
--- a/drivers/hid/hid-logitech-hidpp-base.c
+++ b/drivers/hid/hid-logitech-hidpp-base.c
@@ -22,6 +22,7 @@
 #include <asm/unaligned.h>
 #include "hid-ids.h"
 #include "hid-logitech-hidpp-base.h"
+#include "hid-logitech-hidpp-ff.h"
 
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
@@ -1232,123 +1233,21 @@ static int k400_connect(struct hid_device *hdev, bool connected)
 
 #define HIDPP_PAGE_G920_FORCE_FEEDBACK			0x8123
 
-/* Using session ID = 1 */
-#define CMD_G920_FORCE_GET_APERTURE			0x51
-#define CMD_G920_FORCE_SET_APERTURE			0x61
-
-struct g920_private_data {
-	u8 force_feature;
-	u16 range;
-};
-
-#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)
-
-static ssize_t g920_range_show(struct device *dev, struct device_attribute *attr,
-				char *buf)
-{
-	struct hid_device *hid = to_hid_device(dev);
-	struct hidpp_device *hidpp = hid_get_drvdata(hid);
-	struct g920_private_data *pdata;
-
-	pdata = hidpp->private_data;
-	if (!pdata) {
-		hid_err(hid, "Private driver data not found!\n");
-		return -EINVAL;
-	}
-
-	return scnprintf(buf, PAGE_SIZE, "%u\n", pdata->range);
-}
-
-static ssize_t g920_range_store(struct device *dev, struct device_attribute *attr,
-				 const char *buf, size_t count)
-{
-	struct hid_device *hid = to_hid_device(dev);
-	struct hidpp_device *hidpp = hid_get_drvdata(hid);
-	struct g920_private_data *pdata;
-	struct hidpp_report response;
-	u8 params[2];
-	int ret;
-	u16 range = simple_strtoul(buf, NULL, 10);
-
-	pdata = hidpp->private_data;
-	if (!pdata) {
-		hid_err(hid, "Private driver data not found!\n");
-		return -EINVAL;
-	}
-
-	if (range < 180)
-		range = 180;
-	else if (range > 900)
-		range = 900;
-
-	params[0] = range >> 8;
-	params[1] = range & 0x00FF;
-
-	ret = hidpp_send_fap_command_sync(hidpp, pdata->force_feature,
-		CMD_G920_FORCE_SET_APERTURE, params, 2, &response);
-	if (ret)
-		return ret;
-
-	pdata->range = range;
-	return count;
-}
-
-static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, g920_range_show, g920_range_store);
-
-static int g920_allocate(struct hid_device *hdev)
-{
-	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-	struct g920_private_data *pdata;
-
-	pdata = devm_kzalloc(&hdev->dev, sizeof(struct g920_private_data),
-			GFP_KERNEL);
-	if (!pdata)
-		return -ENOMEM;
-
-	hidpp->private_data = pdata;
-
-	return 0;
-}
-
 static int g920_get_config(struct hidpp_device *hidpp)
 {
-	struct g920_private_data *pdata = hidpp->private_data;
-	struct hidpp_report response;
 	u8 feature_type;
 	u8 feature_index;
 	int ret;
 
-	pdata = hidpp->private_data;
-	if (!pdata) {
-		hid_err(hidpp->hid_dev, "Private driver data not found!\n");
-		return -EINVAL;
-	}
-
 	/* Find feature and store for later use */
 	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_G920_FORCE_FEEDBACK,
 		&feature_index, &feature_type);
 	if (ret)
 		return ret;
 
-	pdata->force_feature = feature_index;
-
-	/* Read current Range */
-	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
-		CMD_G920_FORCE_GET_APERTURE, NULL, 0, &response);
-	if (ret > 0) {
-		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
-			__func__, ret);
-		return -EPROTO;
-	}
+	ret = hidpp_ff_init(hidpp, feature_index);
 	if (ret)
-		return ret;
-
-	pdata->range = get_unaligned_be16(&response.fap.params[0]);
-
-	/* Create sysfs interface */
-	ret = device_create_file(&(hidpp->hid_dev->dev), &dev_attr_range);
-	if (ret)
-		hid_warn(hidpp->hid_dev, "Unable to create sysfs interface for \"range\", errno %d\n", ret);
+		hid_warn(hidpp->hid_dev, "Unable to initialize force feedback support, errno %d\n", ret);
 
 	return 0;
 }
@@ -1672,10 +1571,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 		ret = k400_allocate(hdev);
 		if (ret)
 			goto allocate_fail;
-	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
-		ret = g920_allocate(hdev);
-		if (ret)
-			goto allocate_fail;
 	}
 
 	INIT_WORK(&hidpp->work, delayed_work_cb);
@@ -1758,7 +1653,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 hid_hw_open_failed:
 	hid_device_io_stop(hdev);
 	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
-		device_remove_file(&hdev->dev, &dev_attr_range);
 		hid_hw_close(hdev);
 		hid_hw_stop(hdev);
 	}
@@ -1776,7 +1670,7 @@ static void hidpp_remove(struct hid_device *hdev)
 	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
 
 	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
-		device_remove_file(&hdev->dev, &dev_attr_range);
+		hidpp_ff_deinit(hdev);
 		hid_hw_close(hdev);
 	}
 	hid_hw_stop(hdev);
diff --git a/drivers/hid/hid-logitech-hidpp-ff.c b/drivers/hid/hid-logitech-hidpp-ff.c
new file mode 100644
index 0000000..31ae359
--- /dev/null
+++ b/drivers/hid/hid-logitech-hidpp-ff.c
@@ -0,0 +1,540 @@
+/*
+ *  Force feedback support for Logitech HID++ devices [feature 0x8123]
+ *
+ *  Currently only used for the G920 Driving Force Racing Wheel
+ *
+ *  Copyright (c) 2015 Edwin Velds <e.velds@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ */
+
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <asm/unaligned.h>
+#include <linux/atomic.h>
+#include <linux/fixp-arith.h>
+#include "hid-logitech-hidpp-ff.h"
+
+
+#define HIDPP_FF_GET_INFO		0x01
+#define HIDPP_FF_RESET_ALL		0x11
+#define HIDPP_FF_DOWNLOAD_EFFECT	0x21
+#define HIDPP_FF_SET_EFFECT_STATE	0x31
+#define HIDPP_FF_DESTROY_EFFECT		0x41
+#define HIDPP_FF_GET_APERTURE		0x51
+#define HIDPP_FF_SET_APERTURE		0x61
+#define HIDPP_FF_GET_GLOBAL_GAINS	0x71
+#define HIDPP_FF_SET_GLOBAL_GAINS	0x81
+
+#define HIDPP_FF_EFFECT_STATE_GET	0x00
+#define HIDPP_FF_EFFECT_STATE_STOP	0x01
+#define HIDPP_FF_EFFECT_STATE_PLAY	0x02
+#define HIDPP_FF_EFFECT_STATE_PAUSE	0x03
+
+#define HIDPP_FF_EFFECT_CONSTANT	0x00
+#define HIDPP_FF_EFFECT_PERIODIC_SINE		0x01
+#define HIDPP_FF_EFFECT_PERIODIC_SQUARE		0x02
+#define HIDPP_FF_EFFECT_PERIODIC_TRIANGLE	0x03
+#define HIDPP_FF_EFFECT_PERIODIC_SAWTOOTHUP	0x04
+#define HIDPP_FF_EFFECT_PERIODIC_SAWTOOTHDOWN	0x05
+#define HIDPP_FF_EFFECT_SPRING		0x06
+#define HIDPP_FF_EFFECT_DAMPER		0x07
+
+#define HIDPP_FF_EFFECT_AUTOSTART	0x80
+
+#define HIDPP_FF_EFFECTID_NONE		-1
+#define HIDPP_FF_EFFECTID_AUTOCENTER	-2
+
+#define HIDPP_FF_MAX_PARAMS	20
+
+#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)
+
+
+struct hidpp_ff_private_data {
+	struct hidpp_device *hidpp;
+	u8 feature_index;
+	u16 gain;
+	s16 range;
+	u8 slot_autocenter;
+	u8 num_effects;
+	int *effect_ids;
+	struct workqueue_struct *wq;
+	atomic_t workqueue_size;
+};
+
+struct hidpp_ff_work_data {
+	struct work_struct work;
+	struct hidpp_ff_private_data *data;
+	int effect_id;
+	u8 command;
+	u8 params[HIDPP_FF_MAX_PARAMS];
+	u8 size;
+};
+
+static const signed short hiddpp_ff_effects[] = {
+	FF_CONSTANT,
+	FF_PERIODIC,
+	FF_SINE,
+	FF_SQUARE,
+	FF_SAW_UP,
+	FF_SAW_DOWN,
+	FF_TRIANGLE,
+	FF_SPRING,
+	FF_DAMPER,
+	FF_AUTOCENTER,
+	FF_GAIN,
+	-1
+};
+
+
+static u8 hidpp_ff_find_effect(struct hidpp_ff_private_data *data, int effect_id)
+{
+	int i;
+
+	for (i = 0; i < data->num_effects; i++)
+		if (data->effect_ids[i] == effect_id)
+			return i+1;
+
+	return 0;
+}
+
+static void hidpp_ff_work_handler(struct work_struct *w)
+{
+	struct hidpp_ff_work_data *wd = container_of(w, struct hidpp_ff_work_data, work);
+	struct hidpp_ff_private_data *data = wd->data;
+	struct hidpp_report response;
+	u8 slot;
+	int ret;
+
+	/* add slot number if needed */
+	switch (wd->effect_id) {
+	case HIDPP_FF_EFFECTID_AUTOCENTER:
+		wd->params[0] = data->slot_autocenter;
+		break;
+	case HIDPP_FF_EFFECTID_NONE:
+		/* leave slot as zero */
+		break;
+	default:
+		/* find current slot for effect */
+		wd->params[0] = hidpp_ff_find_effect(data, wd->effect_id);
+		break;
+	}
+
+	/* send command and wait for reply */
+	ret = hidpp_send_fap_command_sync(data->hidpp, data->feature_index,
+		wd->command, wd->params, wd->size, &response);
+
+	if (ret) {
+		hid_err(data->hidpp->hid_dev, "Failed to send command to device!\n");
+		goto out;
+	}
+
+	/* parse return data */
+	switch (wd->command) {
+	case HIDPP_FF_DOWNLOAD_EFFECT:
+		slot = response.fap.params[0];
+		if (slot > 0 && slot <= data->num_effects) {
+			if (wd->effect_id >= 0)
+				/* regular effect uploaded */
+				data->effect_ids[slot-1] = wd->effect_id;
+			else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER)
+				/* autocenter spring uploaded */
+				data->slot_autocenter = slot;
+		}
+		break;
+	case HIDPP_FF_DESTROY_EFFECT:
+		if (wd->effect_id >= 0)
+			/* regular effect destroyed */
+			data->effect_ids[wd->params[0]-1] = -1;
+		else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER)
+			/* autocenter spring destoyed */
+			data->slot_autocenter = 0;
+		break;
+	case HIDPP_FF_SET_GLOBAL_GAINS:
+		data->gain = (wd->params[0] << 8) + wd->params[1];
+		break;
+	case HIDPP_FF_SET_APERTURE:
+		data->range = (wd->params[0] << 8) + wd->params[1];
+		break;
+	default:
+		/* no action needed */
+		break;
+	}
+
+out:
+	atomic_dec(&data->workqueue_size);
+	kfree(wd);
+}
+
+static int hidpp_ff_queue_work(struct hidpp_ff_private_data *data, int effect_id, u8 command, u8 *params, u8 size)
+{
+	struct hidpp_ff_work_data *wd = kzalloc(sizeof(struct hidpp_ff_work_data), GFP_KERNEL);
+	int s;
+
+	if (!wd)
+		return -ENOMEM;
+
+	INIT_WORK(&wd->work, hidpp_ff_work_handler);
+
+	wd->data = data;
+	wd->effect_id = effect_id;
+	wd->command = command;
+	wd->size = size;
+	memcpy(wd->params, params, size);
+
+	atomic_inc(&data->workqueue_size);
+	queue_work(data->wq, &wd->work);
+
+	/* warn about excessive queue size */
+	s = atomic_read(&data->workqueue_size);
+	if (s >= 20 && s % 20 == 0)
+		hid_warn(data->hidpp->hid_dev, "Force feedback command queue contains %d commands, causing substantial delays.", s);
+
+	return 0;
+}
+
+static int hidpp_ff_upload_effect(struct input_dev *dev, struct ff_effect *effect, struct ff_effect *old)
+{
+	struct hidpp_ff_private_data *data = dev->ff->private;
+	u8 params[20];
+	u8 size;
+	int force;
+
+	/* set common parameters */
+	params[2] = effect->replay.length >> 8;
+	params[3] = effect->replay.length & 255;
+	params[4] = effect->replay.delay >> 8;
+	params[5] = effect->replay.delay & 255;
+
+	switch (effect->type) {
+	case FF_CONSTANT:
+		force = (effect->u.constant.level * fixp_sin16((effect->direction * 360) >> 16)) >> 15;
+		params[1] = HIDPP_FF_EFFECT_CONSTANT;
+		params[6] = force >> 8;
+		params[7] = force & 255;
+		params[8] = effect->u.constant.envelope.attack_level >> 8;
+		params[9] = effect->u.constant.envelope.attack_length >> 8;
+		params[10] = effect->u.constant.envelope.attack_length & 255;
+		params[11] = effect->u.constant.envelope.fade_level >> 8;
+		params[12] = effect->u.constant.envelope.fade_length >> 8;
+		params[13] = effect->u.constant.envelope.fade_length & 255;
+		size = 14;
+		dbg_hid("Uploading constant force level=%d in dir %d = %d\n",
+				effect->u.constant.level,
+				effect->direction, force);
+		dbg_hid("          envelope attack=(%d, %d ms) fade=(%d, %d ms)\n",
+				effect->u.constant.envelope.attack_level,
+				effect->u.constant.envelope.attack_length,
+				effect->u.constant.envelope.fade_level,
+				effect->u.constant.envelope.fade_length);
+		break;
+	case FF_PERIODIC:
+	{
+		switch (effect->u.periodic.waveform) {
+		case FF_SINE:
+			params[1] = HIDPP_FF_EFFECT_PERIODIC_SINE;
+			break;
+		case FF_SQUARE:
+			params[1] = HIDPP_FF_EFFECT_PERIODIC_SQUARE;
+			break;
+		case FF_SAW_UP:
+			params[1] = HIDPP_FF_EFFECT_PERIODIC_SAWTOOTHUP;
+			break;
+		case FF_SAW_DOWN:
+			params[1] = HIDPP_FF_EFFECT_PERIODIC_SAWTOOTHDOWN;
+			break;
+		case FF_TRIANGLE:
+			params[1] = HIDPP_FF_EFFECT_PERIODIC_TRIANGLE;
+			break;
+		default:
+			hid_err(data->hidpp->hid_dev, "Unexpected periodic waveform type %i!\n", effect->u.periodic.waveform);
+			return -EINVAL;
+		}
+		force = (effect->u.periodic.magnitude * fixp_sin16((effect->direction * 360) >> 16)) >> 15;
+		params[6] = effect->u.periodic.magnitude >> 8;
+		params[7] = effect->u.periodic.magnitude & 255;
+		params[8] = effect->u.periodic.offset >> 8;
+		params[9] = effect->u.periodic.offset & 255;
+		params[10] = effect->u.periodic.period >> 8;
+		params[11] = effect->u.periodic.period & 255;
+		params[12] = effect->u.periodic.phase >> 8;
+		params[13] = effect->u.periodic.phase & 255;
+		params[14] = effect->u.periodic.envelope.attack_level >> 7;
+		params[15] = effect->u.periodic.envelope.attack_length >> 8;
+		params[16] = effect->u.periodic.envelope.attack_length & 255;
+		params[17] = effect->u.periodic.envelope.fade_level >> 7;
+		params[18] = effect->u.periodic.envelope.fade_length >> 8;
+		params[19] = effect->u.periodic.envelope.fade_length & 255;
+		size = 20;
+		dbg_hid("Uploading periodic force mag=%d/dir=%d, offset=%d, period=%d ms, phase=%d\n",
+				effect->u.periodic.magnitude, effect->direction,
+				effect->u.periodic.offset,
+				effect->u.periodic.period,
+				effect->u.periodic.phase);
+		dbg_hid("          envelope attack=(%d, %d ms) fade=(%d, %d ms)\n",
+				effect->u.periodic.envelope.attack_level,
+				effect->u.periodic.envelope.attack_length,
+				effect->u.periodic.envelope.fade_level,
+				effect->u.periodic.envelope.fade_length);
+		break;
+	}
+	case FF_SPRING:
+	case FF_DAMPER:
+		params[1] = effect->type == FF_SPRING ? HIDPP_FF_EFFECT_SPRING : HIDPP_FF_EFFECT_DAMPER;
+		params[6] = effect->u.condition[0].left_saturation >> 9;
+		params[7] = (effect->u.condition[0].left_saturation >> 1) & 255;
+		params[8] = effect->u.condition[0].left_coeff >> 8;
+		params[9] = effect->u.condition[0].left_coeff & 255;
+		params[10] = effect->u.condition[0].deadband >> 9;
+		params[11] = (effect->u.condition[0].deadband >> 1) & 255;
+		params[12] = effect->u.condition[0].center >> 8;
+		params[13] = effect->u.condition[0].center & 255;
+		params[14] = effect->u.condition[0].right_coeff >> 8;
+		params[15] = effect->u.condition[0].right_coeff & 255;
+		params[16] = effect->u.condition[0].right_saturation >> 9;
+		params[17] = (effect->u.condition[0].right_saturation >> 1) & 255;
+		size = 18;
+		dbg_hid("Uploading %s force left coeff=%d, left sat=%d, right coeff=%d, right sat=%d\n",
+				effect->type == FF_SPRING ? "spring" : "damper",
+				effect->u.condition[0].left_coeff,
+				effect->u.condition[0].left_saturation,
+				effect->u.condition[0].right_coeff,
+				effect->u.condition[0].right_saturation);
+		dbg_hid("          deadband=%d, center=%d\n",
+				effect->u.condition[0].deadband,
+				effect->u.condition[0].center);
+		break;
+	default:
+		hid_err(data->hidpp->hid_dev, "Unexpected force type %i!\n", effect->type);
+		return -EINVAL;
+	}
+
+	return hidpp_ff_queue_work(data, effect->id, HIDPP_FF_DOWNLOAD_EFFECT, params, size);
+}
+
+static int hidpp_ff_playback(struct input_dev *dev, int effect_id, int value)
+{
+	struct hidpp_ff_private_data *data = dev->ff->private;
+	u8 params[2];
+
+	params[1] = value ? HIDPP_FF_EFFECT_STATE_PLAY : HIDPP_FF_EFFECT_STATE_STOP;
+
+	dbg_hid("St%sing playback of effect %d!\n", value?"art":"opp", effect_id);
+
+	return hidpp_ff_queue_work(data, effect_id, HIDPP_FF_SET_EFFECT_STATE, params, 2);
+}
+
+static int hidpp_ff_erase_effect(struct input_dev *dev, int effect_id)
+{
+	struct hidpp_ff_private_data *data = dev->ff->private;
+	u8 slot = 0;
+
+	dbg_hid("Erasing effect %d!\n", effect_id);
+
+	return hidpp_ff_queue_work(data, effect_id, HIDPP_FF_DESTROY_EFFECT, &slot, 1);
+}
+
+static void hidpp_ff_set_autocenter(struct input_dev *dev, u16 magnitude)
+{
+	struct hidpp_ff_private_data *data = dev->ff->private;
+	u8 params[18];
+
+	dbg_hid("Setting autocenter to %d!\n", magnitude);
+
+	if (magnitude) {
+		/* start a standard spring effect */
+		params[1] = HIDPP_FF_EFFECT_SPRING | HIDPP_FF_EFFECT_AUTOSTART;
+		/* zero delay and duration */
+		params[2] = params[3] = params[4] = params[5] = 0;
+		/* saturation and coeff to equal value */
+		params[6] = params[8] = params[14] = params[16] = magnitude >> 9;
+		params[7] = params[9] = params[15] = params[17] = (magnitude & 255) >> 1;
+		/* zero deadband and center */
+		params[10] = params[11] = params[12] = params[13] = 0;
+
+		hidpp_ff_queue_work(data, HIDPP_FF_EFFECTID_AUTOCENTER, HIDPP_FF_DOWNLOAD_EFFECT, params, ARRAY_SIZE(params));
+	} else {
+		if (data->slot_autocenter) {
+			/* remove spring effect */
+			hidpp_ff_queue_work(data, HIDPP_FF_EFFECTID_AUTOCENTER, HIDPP_FF_DESTROY_EFFECT, params, 1);
+		}
+	}
+}
+
+static void hidpp_ff_set_gain(struct input_dev *dev, u16 gain)
+{
+	struct hidpp_ff_private_data *data = dev->ff->private;
+	u8 params[4];
+
+	dbg_hid("Setting gain to %d!\n", gain);
+
+	params[0] = gain >> 8;
+	params[1] = gain & 255;
+	params[2] = 0; /* no boost */
+	params[3] = 0;
+
+	hidpp_ff_queue_work(data, HIDPP_FF_EFFECTID_NONE, HIDPP_FF_SET_GLOBAL_GAINS, params, 4);
+}
+
+static ssize_t hidpp_ff_range_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct hid_device *hid = to_hid_device(dev);
+	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+	struct input_dev *idev = hidinput->input;
+	struct hidpp_ff_private_data *data = idev->ff->private;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", data->range);
+}
+
+static ssize_t hidpp_ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct hid_device *hid = to_hid_device(dev);
+	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+	struct input_dev *idev = hidinput->input;
+	struct hidpp_ff_private_data *data = idev->ff->private;
+	u8 params[2];
+	int range = simple_strtoul(buf, NULL, 10);
+
+	range = clamp(range, 180, 900);
+
+	params[0] = range >> 8;
+	params[1] = range & 0x00FF;
+
+	hidpp_ff_queue_work(data, -1, HIDPP_FF_SET_APERTURE, params, 2);
+
+	return count;
+}
+
+static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, hidpp_ff_range_show, hidpp_ff_range_store);
+
+static void hidpp_ff_destroy(struct ff_device *ff)
+{
+	struct hidpp_ff_private_data *data = ff->private;
+
+	kfree(data->effect_ids);
+}
+
+int hidpp_ff_init(struct hidpp_device *hidpp, u8 feature_index)
+{
+	struct hid_device *hid = hidpp->hid_dev;
+	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+	struct input_dev *dev = hidinput->input;
+	struct ff_device *ff;
+	struct hidpp_report response;
+	struct hidpp_ff_private_data *data;
+	int error, j, num_slots;
+
+	if (!dev) {
+		hid_err(hid, "Struct input_dev not set!\n");
+		return -EINVAL;
+	}
+
+	/* Set supported force feedback capabilities */
+	for (j = 0; hiddpp_ff_effects[j] >= 0; j++)
+		set_bit(hiddpp_ff_effects[j], dev->ffbit);
+
+	/* Read number of slots available in device */
+	error = hidpp_send_fap_command_sync(hidpp, feature_index,
+		HIDPP_FF_GET_INFO, NULL, 0, &response);
+	if (error) {
+		if (error < 0)
+			return error;
+		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+			__func__, error);
+		return -EPROTO;
+	}
+
+	num_slots = response.fap.params[0];
+
+	error = input_ff_create(dev, num_slots);
+
+	if (error) {
+		hid_err(dev, "Failed to create FF device\n");
+		return error;
+	}
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+	data->effect_ids = kzalloc(num_slots*sizeof(int), GFP_KERNEL);
+	if (!data->effect_ids) {
+		kfree(data);
+		return -ENOMEM;
+	}
+	data->hidpp = hidpp;
+	data->feature_index = feature_index;
+	data->slot_autocenter = 0;
+	data->num_effects = num_slots;
+	for (j = 0; j < num_slots; j++)
+		data->effect_ids[j] = -1;
+
+	ff = dev->ff;
+	ff->private = data;
+
+	ff->upload = hidpp_ff_upload_effect;
+	ff->erase = hidpp_ff_erase_effect;
+	ff->playback = hidpp_ff_playback;
+	ff->set_gain = hidpp_ff_set_gain;
+	ff->set_autocenter = hidpp_ff_set_autocenter;
+	ff->destroy = hidpp_ff_destroy;
+
+
+	/* reset all forces */
+	error = hidpp_send_fap_command_sync(hidpp, feature_index,
+		HIDPP_FF_RESET_ALL, NULL, 0, &response);
+
+	/* Read current Range */
+	error = hidpp_send_fap_command_sync(hidpp, feature_index,
+		HIDPP_FF_GET_APERTURE, NULL, 0, &response);
+	if (error)
+		hid_warn(hidpp->hid_dev, "Failed to read range from device.\n");
+	data->range = error ? 900 : get_unaligned_be16(&response.fap.params[0]);
+
+	/* Create sysfs interface */
+	error = device_create_file(&(hidpp->hid_dev->dev), &dev_attr_range);
+	if (error)
+		hid_warn(hidpp->hid_dev, "Unable to create sysfs interface for \"range\", errno %d\n", error);
+
+	/* Read the current gain values */
+	error = hidpp_send_fap_command_sync(hidpp, feature_index,
+		HIDPP_FF_GET_GLOBAL_GAINS, NULL, 0, &response);
+	if (error)
+		hid_warn(hidpp->hid_dev, "Failed to read gain values from device.\n");
+	data->gain = error ? 0xffff : get_unaligned_be16(&response.fap.params[0]);
+	/* ignore boost value at response.fap.params[2] */
+
+	/* init the hardware command queue */
+	data->wq = create_singlethread_workqueue("hidpp-ff-sendqueue");
+	atomic_set(&data->workqueue_size, 0);
+
+	return 0;
+}
+
+int hidpp_ff_deinit(struct hid_device *hid)
+{
+	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+	struct input_dev *dev = hidinput->input;
+	struct hidpp_ff_private_data *data;
+
+	if (!dev) {
+		hid_err(hid, "Struct input_dev not found!\n");
+		return -EINVAL;
+	}
+
+	hid_info(hid, "Unloading HID++ force feedback.\n");
+	data = dev->ff->private;
+	if (!data) {
+		hid_err(hid, "Private data not found!\n");
+		return -EINVAL;
+	}
+
+	destroy_workqueue(data->wq);
+	device_remove_file(&hid->dev, &dev_attr_range);
+
+	return 0;
+}
diff --git a/drivers/hid/hid-logitech-hidpp-ff.h b/drivers/hid/hid-logitech-hidpp-ff.h
new file mode 100644
index 0000000..e153019
--- /dev/null
+++ b/drivers/hid/hid-logitech-hidpp-ff.h
@@ -0,0 +1,9 @@
+#ifndef __HID_LOGITECH_HIDPP_FF_H
+#define __HID_LOGITECH_HIDPP_FF_H
+
+#include "hid-logitech-hidpp-base.h"
+
+int hidpp_ff_init(struct hidpp_device *hidpp, u8 feature_index);
+int hidpp_ff_deinit(struct hid_device *hid);
+
+#endif
-- 
2.5.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH 0/2] HID: Force feedback support for the Logitech G920 Wheel
  2015-11-21 12:37 [PATCH 0/2] HID: Force feedback support for the Logitech G920 Wheel Edwin Velds
  2015-11-21 12:37 ` [PATCH 1/2] HID: Create header for Logitech HID++ driver Edwin Velds
  2015-11-21 12:37 ` [PATCH 2/2] HID: Force feedback support for the Logitech G920 Edwin Velds
@ 2015-11-26 14:42 ` Jiri Kosina
  2015-11-26 15:13   ` Benjamin Tissoires
  2015-11-28  7:53 ` Elias Vanderstuyft
  3 siblings, 1 reply; 6+ messages in thread
From: Jiri Kosina @ 2015-11-26 14:42 UTC (permalink / raw)
  To: Edwin Velds, Simon Wood
  Cc: linux-input, linux-kernel, Michal Malý,
	elias vanderstuyft, Benjamin Tissoires, Dmitry Torokhov

On Sat, 21 Nov 2015, Edwin Velds wrote:

> This patch implements force feedback support for the Logitech G920 Driving Force
> Racing Wheel.
> 
> This patch is based on the basic G920 support patch by Simon Wood:
> http://www.spinics.net/lists/linux-input/msg42174.html

Simon, it'd be nice if I could get your Tested-by: / Reviewed-by: for 
this.

Thanks,

-- 
Jiri Kosina
SUSE Labs


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 0/2] HID: Force feedback support for the Logitech G920 Wheel
  2015-11-26 14:42 ` [PATCH 0/2] HID: Force feedback support for the Logitech G920 Wheel Jiri Kosina
@ 2015-11-26 15:13   ` Benjamin Tissoires
  0 siblings, 0 replies; 6+ messages in thread
From: Benjamin Tissoires @ 2015-11-26 15:13 UTC (permalink / raw)
  To: Jiri Kosina
  Cc: Edwin Velds, Simon Wood, linux-input, linux-kernel,
	Michal Malý,
	elias vanderstuyft, Benjamin Tissoires, Dmitry Torokhov

On Thu, Nov 26, 2015 at 3:42 PM, Jiri Kosina <jikos@kernel.org> wrote:
> On Sat, 21 Nov 2015, Edwin Velds wrote:
>
>> This patch implements force feedback support for the Logitech G920 Driving Force
>> Racing Wheel.
>>
>> This patch is based on the basic G920 support patch by Simon Wood:
>> http://www.spinics.net/lists/linux-input/msg42174.html
>
> Simon, it'd be nice if I could get your Tested-by: / Reviewed-by: for
> this.
>

Talking about it, I am not a big fan of renaming hid-logitech-hidpp.c
into hid-logitech-hidpp-base.c. This will just break history for no
reasons.
We could keep it named like that and just add the ff module later.

The other question is why do we need to have the ff module in a
separate file? We generally try to keep the number of file to a small
number, and I am not sure we need to split this for ff only.
I am open to any position in favor of splitting, I am just asking.

Cheers,
Benjamin

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 0/2] HID: Force feedback support for the Logitech G920 Wheel
  2015-11-21 12:37 [PATCH 0/2] HID: Force feedback support for the Logitech G920 Wheel Edwin Velds
                   ` (2 preceding siblings ...)
  2015-11-26 14:42 ` [PATCH 0/2] HID: Force feedback support for the Logitech G920 Wheel Jiri Kosina
@ 2015-11-28  7:53 ` Elias Vanderstuyft
  3 siblings, 0 replies; 6+ messages in thread
From: Elias Vanderstuyft @ 2015-11-28  7:53 UTC (permalink / raw)
  To: Edwin Velds
  Cc: open list:HID CORE LAYER, linux-kernel, Simon Wood, Jiri Kosina,
	Michal Malý,
	Benjamin Tissoires, Dmitry Torokhov

On Sat, Nov 21, 2015 at 1:37 PM, Edwin Velds <e.velds@gmail.com> wrote:
> This patch implements force feedback support for the Logitech G920 Driving Force
> Racing Wheel.
>
> This patch is based on the basic G920 support patch by Simon Wood:
> http://www.spinics.net/lists/linux-input/msg42174.html
>
> The first patch splits and renames the HID++ driver (hid-logitech-hidpp.c) into
> a basic header and implementation in order to allow building additional features
> from multiple sources. Since this only satisfies the needs for the FF driver,
> any comments or suggestions are welcome.
>
> The second patch implements all supported force feedback effects. The force
> feedback implementation was not made optional because the G920 (like all
> Logitech wheels) can't function without it due to very stiff to default springs.
>
> Edwin Velds (1):
>   HID: Create header for Logitech HID++ driver
>   HID: Force feedback support for the Logitech G920
>
>  drivers/hid/Makefile                  |    2 +
>  drivers/hid/hid-logitech-hidpp-base.c | 1725 ++++++++++++++++++++++++++++++
>  drivers/hid/hid-logitech-hidpp-base.h |   81 ++
>  drivers/hid/hid-logitech-hidpp-ff.c   |  540 ++++++++++
>  drivers/hid/hid-logitech-hidpp-ff.h   |    9 +
>  drivers/hid/hid-logitech-hidpp.c      | 1900 ---------------------------------
>  6 files changed, 2357 insertions(+), 1900 deletions(-)
>  create mode 100644 drivers/hid/hid-logitech-hidpp-base.c
>  create mode 100644 drivers/hid/hid-logitech-hidpp-base.h
>  create mode 100644 drivers/hid/hid-logitech-hidpp-ff.c
>  create mode 100644 drivers/hid/hid-logitech-hidpp-ff.h
>  delete mode 100644 drivers/hid/hid-logitech-hidpp.c
>
> --
> 2.5.0
>

You have my:
Tested-by: Elias Vanderstuyft <elias.vds@gmail.com>

Note however that at least a V2 can be expected:
I did a review off-list just before this patchset made it to this
mailing list, and not all changes are applied yet.

Thanks,
Elias

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2015-11-28  7:53 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-11-21 12:37 [PATCH 0/2] HID: Force feedback support for the Logitech G920 Wheel Edwin Velds
2015-11-21 12:37 ` [PATCH 1/2] HID: Create header for Logitech HID++ driver Edwin Velds
2015-11-21 12:37 ` [PATCH 2/2] HID: Force feedback support for the Logitech G920 Edwin Velds
2015-11-26 14:42 ` [PATCH 0/2] HID: Force feedback support for the Logitech G920 Wheel Jiri Kosina
2015-11-26 15:13   ` Benjamin Tissoires
2015-11-28  7:53 ` Elias Vanderstuyft

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.