Add Google vivaldi HID driver. This driver allows us to read and report
the top row layout of keyboards which provide a vendor-defined HID
usage.

Signed-off-by: Sean O'Brien <seobr...@chromium.org>
---

 drivers/hid/Kconfig              |   9 ++
 drivers/hid/Makefile             |   1 +
 drivers/hid/hid-core.c           |   7 ++
 drivers/hid/hid-google-vivaldi.c | 144 +++++++++++++++++++++++++++++++
 include/linux/hid.h              |   2 +
 5 files changed, 163 insertions(+)
 create mode 100644 drivers/hid/hid-google-vivaldi.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 05315b434276..b608a5b1d753 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -397,6 +397,15 @@ config HID_GOOGLE_HAMMER
        help
        Say Y here if you have a Google Hammer device.
 
+config HID_GOOGLE_VIVALDI
+       tristate "Google Vivaldi Keyboard"
+       depends on HID
+       help
+         Say Y here if you want to enable support for Google Vivaldi keyboards.
+
+         Vivaldi keyboards use a vendor-specific HID usage to report how the
+         keys in the top row are physically ordered.
+
 config HID_GT683R
        tristate "MSI GT68xR LED support"
        depends on LEDS_CLASS && USB_HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index d8ea4b8c95af..35ca714c7ee2 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -50,6 +50,7 @@ obj-$(CONFIG_HID_GEMBIRD)     += hid-gembird.o
 obj-$(CONFIG_HID_GFRM)         += hid-gfrm.o
 obj-$(CONFIG_HID_GLORIOUS)  += hid-glorious.o
 obj-$(CONFIG_HID_GOOGLE_HAMMER)        += hid-google-hammer.o
+obj-$(CONFIG_HID_GOOGLE_VIVALDI)       += hid-google-vivaldi.o
 obj-$(CONFIG_HID_GT683R)       += hid-gt683r.o
 obj-$(CONFIG_HID_GYRATION)     += hid-gyration.o
 obj-$(CONFIG_HID_HOLTEK)       += hid-holtek-kbd.o
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 359616e3efbb..4df05b35b4d0 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -814,6 +814,13 @@ static void hid_scan_collection(struct hid_parser *parser, 
unsigned type)
 
        if ((parser->global.usage_page << 16) >= HID_UP_MSVENDOR)
                parser->scan_flags |= HID_SCAN_FLAG_VENDOR_SPECIFIC;
+
+       if ((parser->global.usage_page << 16) == HID_UP_GOOGLEVENDOR)
+               for (i = 0; i < parser->local.usage_index; i++)
+                       if (parser->local.usage[i] ==
+                                       (HID_UP_GOOGLEVENDOR | 0x0001))
+                               parser->device->group =
+                                       HID_GROUP_GOOGLE_VIVALDI;
 }
 
 static int hid_scan_main(struct hid_parser *parser, struct hid_item *item)
diff --git a/drivers/hid/hid-google-vivaldi.c b/drivers/hid/hid-google-vivaldi.c
new file mode 100644
index 000000000000..9165ef5e1542
--- /dev/null
+++ b/drivers/hid/hid-google-vivaldi.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HID support for Google Vivaldi Keyboard
+ *
+ * Copyright 2020 Google LLC.
+ * Author: Sean O'Brien <seobr...@chromium.org>
+ */
+
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#define MIN_FN_ROW_KEY 1
+#define MAX_FN_ROW_KEY 24
+#define HID_VD_FN_ROW_PHYSMAP 0x00000001
+#define HID_USAGE_FN_ROW_PHYSMAP (HID_UP_GOOGLEVENDOR | HID_VD_FN_ROW_PHYSMAP)
+
+static struct hid_driver hid_vivaldi;
+
+struct vivaldi_data {
+       u32 function_row_physmap[MAX_FN_ROW_KEY - MIN_FN_ROW_KEY + 1];
+       int max_function_row_key;
+};
+
+static ssize_t function_row_physmap_show(struct device *dev,
+                                        struct device_attribute *attr,
+                                        char *buf)
+{
+       struct hid_device *hdev = to_hid_device(dev);
+       struct vivaldi_data *drvdata = hid_get_drvdata(hdev);
+       ssize_t size = 0;
+       int i;
+
+       if (!drvdata->max_function_row_key)
+               return 0;
+
+       for (i = 0; i < drvdata->max_function_row_key; i++)
+               size += sprintf(buf + size, "%02X ",
+                               drvdata->function_row_physmap[i]);
+       size += sprintf(buf + size, "\n");
+       return size;
+}
+
+DEVICE_ATTR_RO(function_row_physmap);
+static struct attribute *sysfs_attrs[] = {
+       &dev_attr_function_row_physmap.attr,
+       NULL
+};
+
+static const struct attribute_group input_attribute_group = {
+       .attrs = sysfs_attrs
+};
+
+static int vivaldi_probe(struct hid_device *hdev,
+                        const struct hid_device_id *id)
+{
+       struct vivaldi_data *drvdata;
+       int ret;
+
+       drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
+       hid_set_drvdata(hdev, drvdata);
+
+       ret = hid_parse(hdev);
+       if (ret)
+               return ret;
+
+       return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+}
+
+static void vivaldi_feature_mapping(struct hid_device *hdev,
+                                   struct hid_field *field,
+                                   struct hid_usage *usage)
+{
+       struct vivaldi_data *drvdata = hid_get_drvdata(hdev);
+       int fn_key;
+       int ret;
+       u32 report_len;
+       u8 *buf;
+
+       if (field->logical != HID_USAGE_FN_ROW_PHYSMAP ||
+           (usage->hid & HID_USAGE_PAGE) != HID_UP_ORDINAL)
+               return;
+
+       fn_key = (usage->hid & HID_USAGE);
+       if (fn_key < MIN_FN_ROW_KEY || fn_key > MAX_FN_ROW_KEY)
+               return;
+       if (fn_key > drvdata->max_function_row_key)
+               drvdata->max_function_row_key = fn_key;
+
+       buf = hid_alloc_report_buf(field->report, GFP_KERNEL);
+       if (!buf)
+               return;
+
+       report_len = hid_report_len(field->report);
+       ret = hid_hw_raw_request(hdev, field->report->id, buf,
+                                report_len, HID_FEATURE_REPORT,
+                                HID_REQ_GET_REPORT);
+       if (ret < 0) {
+               dev_warn(&hdev->dev, "failed to fetch feature %d\n",
+                        field->report->id);
+               goto out;
+       }
+
+       ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, buf,
+                                  report_len, 0);
+       if (ret) {
+               dev_warn(&hdev->dev, "failed to report feature %d\n",
+                        field->report->id);
+               goto out;
+       }
+
+       drvdata->function_row_physmap[fn_key - MIN_FN_ROW_KEY] =
+           field->value[usage->usage_index];
+
+out:
+       kfree(buf);
+}
+
+static int vivaldi_input_configured(struct hid_device *hdev,
+                                   struct hid_input *hidinput)
+{
+       return sysfs_create_group(&hdev->dev.kobj, &input_attribute_group);
+}
+
+static const struct hid_device_id vivaldi_table[] = {
+       { HID_DEVICE(HID_BUS_ANY, HID_GROUP_GOOGLE_VIVALDI, HID_ANY_ID,
+                    HID_ANY_ID) },
+       { }
+};
+
+MODULE_DEVICE_TABLE(hid, vivaldi_table);
+
+static struct hid_driver hid_vivaldi = {
+       .name = "hid-vivaldi",
+       .id_table = vivaldi_table,
+       .probe = vivaldi_probe,
+       .feature_mapping = vivaldi_feature_mapping,
+       .input_configured = vivaldi_input_configured,
+};
+
+module_hid_driver(hid_vivaldi);
+
+MODULE_AUTHOR("Sean O'Brien");
+MODULE_DESCRIPTION("HID Google vivaldi driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 875f71132b14..cfcc76dcc3b6 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -163,6 +163,7 @@ struct hid_item {
 #define HID_UP_LNVENDOR                0xffa00000
 #define HID_UP_SENSOR          0x00200000
 #define HID_UP_ASUSVENDOR      0xff310000
+#define HID_UP_GOOGLEVENDOR    0xffd10000
 
 #define HID_USAGE              0x0000ffff
 
@@ -371,6 +372,7 @@ struct hid_item {
 #define HID_GROUP_LOGITECH_DJ_DEVICE           0x0102
 #define HID_GROUP_STEAM                                0x0103
 #define HID_GROUP_LOGITECH_27MHZ_DEVICE                0x0104
+#define HID_GROUP_GOOGLE_VIVALDI               0x0105
 
 /*
  * HID protocol status
-- 
2.28.0.297.g1956fa8f8d-goog

Reply via email to