HID: hid-logitech-hidpp: G920 remove deadzones
[sfrench/cifs-2.6.git] / drivers / hid / hid-logitech-hidpp.c
index 98b8f096d7ee5f309b8cef965dbb052bf374e388..f2a481125522e33f912db0455c0b4de21396890c 100644 (file)
@@ -1295,6 +1295,133 @@ 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
+
+/* 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                                                      */
 /* -------------------------------------------------------------------------- */
@@ -1314,6 +1441,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)
 {
@@ -1595,6 +1741,10 @@ 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);
@@ -1648,6 +1798,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
                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 */
@@ -1673,6 +1827,7 @@ 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);
        }
@@ -1689,8 +1844,10 @@ static void hidpp_remove(struct hid_device *hdev)
 {
        struct hidpp_device *hidpp = hid_get_drvdata(hdev);
 
-       if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)
+       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);
@@ -1737,6 +1894,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);