]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blobdiff - drivers/hid/hid-logitech-hidpp.c
HID: logitech-hidpp: limit visibility of init/deinit functions
[mirror_ubuntu-artful-kernel.git] / drivers / hid / hid-logitech-hidpp.c
index 5fd97860aec4d8ec1e92b02031e715e8644367e8..2e2515a4c070eac305a834c9229d0e1fa27f722f 100644 (file)
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <linux/device.h>
+#include <linux/input.h>
+#include <linux/usb.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 <linux/workqueue.h>
+#include <linux/atomic.h>
+#include <linux/fixp-arith.h>
 #include <asm/unaligned.h>
+#include "usbhid/usbhid.h"
 #include "hid-ids.h"
 
 MODULE_LICENSE("GPL");
@@ -40,18 +46,22 @@ MODULE_PARM_DESC(disable_tap_to_click,
 
 #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)
@@ -81,13 +91,13 @@ MODULE_PARM_DESC(disable_tap_to_click,
 struct fap {
        u8 feature_index;
        u8 funcindex_clientid;
-       u8 params[HIDPP_REPORT_LONG_LENGTH - 4U];
+       u8 params[HIDPP_REPORT_VERY_LONG_LENGTH - 4U];
 };
 
 struct rap {
        u8 sub_id;
        u8 reg_address;
-       u8 params[HIDPP_REPORT_LONG_LENGTH - 4U];
+       u8 params[HIDPP_REPORT_VERY_LONG_LENGTH - 4U];
 };
 
 struct hidpp_report {
@@ -144,8 +154,11 @@ 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;
@@ -153,6 +166,9 @@ static int __hidpp_send_report(struct hid_device *hdev,
        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;
        }
@@ -163,9 +179,13 @@ static int __hidpp_send_report(struct hid_device *hdev,
         */
        hidpp_report->device_index = 0xff;
 
-       ret = hid_hw_raw_request(hdev, hidpp_report->report_id,
-               (u8 *)hidpp_report, fields_count, HID_OUTPUT_REPORT,
-               HID_REQ_SET_REPORT);
+       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;
 }
@@ -217,8 +237,9 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp,
                goto exit;
        }
 
-       if (response->report_id == REPORT_ID_HIDPP_LONG &&
-           response->fap.feature_index == HIDPP20_ERROR) {
+       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;
@@ -243,7 +264,11 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
        message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
        if (!message)
                return -ENOMEM;
-       message->report_id = REPORT_ID_HIDPP_LONG;
+
+       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);
@@ -258,13 +283,23 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
        struct hidpp_report *response)
 {
        struct hidpp_report *message;
-       int ret;
+       int ret, max_count;
 
-       if ((report_id != REPORT_ID_HIDPP_SHORT) &&
-           (report_id != REPORT_ID_HIDPP_LONG))
+       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 > sizeof(message->rap.params))
+       if (param_count > max_count)
                return -EINVAL;
 
        message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
@@ -508,10 +543,19 @@ static int hidpp_devicenametype_get_device_name(struct hidpp_device *hidpp,
        if (ret)
                return ret;
 
-       if (response.report_id == REPORT_ID_HIDPP_LONG)
+       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;
-       else
+               break;
+       case REPORT_ID_HIDPP_SHORT:
                count = HIDPP_REPORT_SHORT_LENGTH - 4;
+               break;
+       default:
+               return -EPROTO;
+       }
 
        if (len_buf < count)
                count = len_buf;
@@ -735,6 +779,589 @@ static void hidpp_touchpad_raw_xy_event(struct hidpp_device *hidpp_dev,
        }
 }
 
+/* -------------------------------------------------------------------------- */
+/* 0x8123: Force feedback support                                             */
+/* -------------------------------------------------------------------------- */
+
+#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_FRICTION       0x08
+#define HIDPP_FF_EFFECT_INERTIA                0x09
+#define HIDPP_FF_EFFECT_RAMP           0x0A
+
+#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 HIDPP_FF_RESERVED_SLOTS        1
+
+struct hidpp_ff_private_data {
+       struct hidpp_device *hidpp;
+       u8 feature_index;
+       u8 version;
+       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 const signed short hiddpp_ff_effects_v2[] = {
+       FF_RAMP,
+       FF_FRICTION,
+       FF_INERTIA,
+       -1
+};
+
+static const u8 HIDPP_FF_CONDITION_CMDS[] = {
+       HIDPP_FF_EFFECT_SPRING,
+       HIDPP_FF_EFFECT_FRICTION,
+       HIDPP_FF_EFFECT_DAMPER,
+       HIDPP_FF_EFFECT_INERTIA
+};
+
+static const char *HIDPP_FF_CONDITION_NAMES[] = {
+       "spring",
+       "friction",
+       "damper",
+       "inertia"
+};
+
+
+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(*wd), 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 >> 7;
+               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 >> 7;
+               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_RAMP:
+               params[1] = HIDPP_FF_EFFECT_RAMP;
+               force = (effect->u.ramp.start_level * fixp_sin16((effect->direction * 360) >> 16)) >> 15;
+               params[6] = force >> 8;
+               params[7] = force & 255;
+               force = (effect->u.ramp.end_level * fixp_sin16((effect->direction * 360) >> 16)) >> 15;
+               params[8] = force >> 8;
+               params[9] = force & 255;
+               params[10] = effect->u.ramp.envelope.attack_level >> 7;
+               params[11] = effect->u.ramp.envelope.attack_length >> 8;
+               params[12] = effect->u.ramp.envelope.attack_length & 255;
+               params[13] = effect->u.ramp.envelope.fade_level >> 7;
+               params[14] = effect->u.ramp.envelope.fade_length >> 8;
+               params[15] = effect->u.ramp.envelope.fade_length & 255;
+               size = 16;
+               dbg_hid("Uploading ramp force level=%d -> %d in dir %d = %d\n",
+                               effect->u.ramp.start_level,
+                               effect->u.ramp.end_level,
+                               effect->direction, force);
+               dbg_hid("          envelope attack=(%d, %d ms) fade=(%d, %d ms)\n",
+                               effect->u.ramp.envelope.attack_level,
+                               effect->u.ramp.envelope.attack_length,
+                               effect->u.ramp.envelope.fade_level,
+                               effect->u.ramp.envelope.fade_length);
+               break;
+       case FF_FRICTION:
+       case FF_INERTIA:
+       case FF_SPRING:
+       case FF_DAMPER:
+               params[1] = HIDPP_FF_CONDITION_CMDS[effect->type - FF_SPRING];
+               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",
+                               HIDPP_FF_CONDITION_NAMES[effect->type - FF_SPRING],
+                               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, ARRAY_SIZE(params));
+}
+
+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);
+
+       /* 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;
+       /* set coeff to 25% of saturation */
+       params[8] = params[14] = magnitude >> 11;
+       params[9] = params[15] = (magnitude >> 3) & 255;
+       params[6] = params[16] = magnitude >> 9;
+       params[7] = params[17] = (magnitude >> 1) & 255;
+       /* 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));
+}
+
+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, ARRAY_SIZE(params));
+}
+
+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, ARRAY_SIZE(params));
+
+       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);
+}
+
+static 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;
+       const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor);
+       const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice);
+       struct ff_device *ff;
+       struct hidpp_report response;
+       struct hidpp_ff_private_data *data;
+       int error, j, num_slots;
+       u8 version;
+
+       if (!dev) {
+               hid_err(hid, "Struct input_dev not set!\n");
+               return -EINVAL;
+       }
+
+       /* Get firmware release */
+       version = bcdDevice & 255;
+
+       /* Set supported force feedback capabilities */
+       for (j = 0; hiddpp_ff_effects[j] >= 0; j++)
+               set_bit(hiddpp_ff_effects[j], dev->ffbit);
+       if (version > 1)
+               for (j = 0; hiddpp_ff_effects_v2[j] >= 0; j++)
+                       set_bit(hiddpp_ff_effects_v2[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] - HIDPP_FF_RESERVED_SLOTS;
+
+       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 = kcalloc(num_slots, sizeof(int), GFP_KERNEL);
+       if (!data->effect_ids) {
+               kfree(data);
+               return -ENOMEM;
+       }
+       data->hidpp = hidpp;
+       data->feature_index = feature_index;
+       data->version = version;
+       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);
+
+       /* initialize with zero autocenter to get wheel in usable state */
+       hidpp_ff_set_autocenter(dev, 0);
+
+       hid_info(hid, "Force feeback support loaded (firmware release %d).\n", version);
+
+       return 0;
+}
+
+static 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;
+}
+
+
 /* ************************************************************************** */
 /*                                                                            */
 /* Device Support                                                             */
@@ -1257,6 +1884,32 @@ static int k400_connect(struct hid_device *hdev, bool connected)
        return k400_disable_tap_to_click(hidpp);
 }
 
+/* ------------------------------------------------------------------------- */
+/* Logitech G920 Driving Force Racing Wheel for Xbox One                     */
+/* ------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_G920_FORCE_FEEDBACK                 0x8123
+
+static int g920_get_config(struct hidpp_device *hidpp)
+{
+       u8 feature_type;
+       u8 feature_index;
+       int ret;
+
+       /* 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;
+
+       ret = hidpp_ff_init(hidpp, feature_index);
+       if (ret)
+               hid_warn(hidpp->hid_dev, "Unable to initialize force feedback support, errno %d\n",
+                               ret);
+
+       return 0;
+}
+
 /* -------------------------------------------------------------------------- */
 /* Generic HID++ devices                                                      */
 /* -------------------------------------------------------------------------- */
@@ -1276,6 +1929,25 @@ static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
        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)
 {
@@ -1347,6 +2019,14 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
 
        /* 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)",
@@ -1393,10 +2073,12 @@ static void hidpp_overwrite_name(struct hid_device *hdev, bool use_unifying)
        else
                name = hidpp_get_device_name(hidpp);
 
-       if (!name)
+       if (!name) {
                hid_err(hdev, "unable to retrieve the name of the device");
-       else
+       } else {
+               dbg_hid("HID++: Got name: %s\n", name);
                snprintf(hdev->name, sizeof(hdev->name), "%s", name);
+       }
 
        kfree(name);
 }
@@ -1559,6 +2241,25 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
                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);
 
@@ -1567,8 +2268,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
                if (!connected) {
                        ret = -ENODEV;
                        hid_err(hdev, "Device not connected");
-                       hid_device_io_stop(hdev);
-                       goto hid_parse_fail;
+                       goto hid_hw_open_failed;
                }
 
                hid_info(hdev, "HID++ %u.%u device connected.\n",
@@ -1581,19 +2281,22 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
        if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) {
                ret = wtp_get_config(hidpp);
                if (ret)
-                       goto hid_parse_fail;
+                       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_NO_HIDINPUT)
-               connect_mask &= ~HID_CONNECT_HIDINPUT;
-
-       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_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) {
@@ -1605,6 +2308,12 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 
        return ret;
 
+hid_hw_open_failed:
+       hid_device_io_stop(hdev);
+       if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
+               hid_hw_close(hdev);
+               hid_hw_stop(hdev);
+       }
 hid_hw_start_fail:
 hid_parse_fail:
        cancel_work_sync(&hidpp->work);
@@ -1618,9 +2327,13 @@ static void hidpp_remove(struct hid_device *hdev)
 {
        struct hidpp_device *hidpp = hid_get_drvdata(hdev);
 
+       if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
+               hidpp_ff_deinit(hdev);
+               hid_hw_close(hdev);
+       }
+       hid_hw_stop(hdev);
        cancel_work_sync(&hidpp->work);
        mutex_destroy(&hidpp->send_mutex);
-       hid_hw_stop(hdev);
 }
 
 static const struct hid_device_id hidpp_devices[] = {
@@ -1648,6 +2361,9 @@ static const struct hid_device_id hidpp_devices[] = {
 
        { 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},
        {}
 };
 
@@ -1661,6 +2377,7 @@ static struct hid_driver hidpp_driver = {
        .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);