CHROMEOS: HID: Helper functions for Logitech HID++
authorAndrew de los Reyes <adlr@chromium.org>
Sun, 6 Jan 2013 01:49:05 +0000 (17:49 -0800)
committerChromeBot <chrome-bot@google.com>
Fri, 22 Mar 2013 02:09:15 +0000 (19:09 -0700)
This is part of the next iteration of Nestor Lopez Casado's
introduction of HID++ support to the kernel from his patch "[PATCH
1/1] HID: Add full MT support for Logitech Wireless Touchpad"

HID++ is a Logitech-specific protocol for communicating with HID
devices. It can be used, for example, to probe properties of devices
(like touch surface dimensions, battery status, and more), and to set
state on a device (like a request to put the device into raw mode).

Documentation is provided at
https://drive.google.com/a/logitech.com/?tab=mo#folders/0BxbRzx7vEV7eWmgwazJ3NUFfQ28

BUG=chromium-os:39354
TEST=manually tested on Link

Signed-off-by: Andrew de los Reyes <adlr@chromium.org>
Change-Id: I1b87558cc31cb77ef277820f0b00f3ea6e1b367e
Reviewed-on: https://gerrit.chromium.org/gerrit/46009
Reviewed-by: Yufeng Shen <miletus@chromium.org>
drivers/hid/Kconfig
drivers/hid/Makefile
drivers/hid/hid-logitech-dj.c
drivers/hid/hid-logitech-hidpp.c [new file with mode: 0644]
drivers/hid/hid-logitech-hidpp.h [new file with mode: 0644]
include/linux/input.h

index a6f6b50..5061815 100644 (file)
@@ -260,6 +260,7 @@ config HID_LOGITECH
 config HID_LOGITECH_DJ
        tristate "Logitech Unifying receivers full support"
        depends on HID_LOGITECH
+       select HID_LOGITECH_HIDPP
        default m
        ---help---
        Say Y if you want support for Logitech Unifying receivers and devices.
@@ -268,6 +269,12 @@ config HID_LOGITECH_DJ
        generic USB_HID driver and all incomming events will be multiplexed
        into a single mouse and a single keyboard device.
 
+config HID_LOGITECH_HIDPP
+       tristate "Support for Logitech Vendor Specific HID commands"
+       ---help---
+       This module allows sending vendor specific commands to Logitech
+       devices
+
 config LOGITECH_FF
        bool "Logitech force feedback support"
        depends on HID_LOGITECH
index 22f1d16..ce7afbc 100644 (file)
@@ -53,6 +53,7 @@ obj-$(CONFIG_HID_KYE)         += hid-kye.o
 obj-$(CONFIG_HID_LCPOWER)       += hid-lcpower.o
 obj-$(CONFIG_HID_LOGITECH)     += hid-logitech.o
 obj-$(CONFIG_HID_LOGITECH_DJ)  += hid-logitech-dj.o
+obj-$(CONFIG_HID_LOGITECH_HIDPP)       += hid-logitech-hidpp.o
 obj-$(CONFIG_HID_MAGICMOUSE)    += hid-magicmouse.o
 obj-$(CONFIG_HID_MICROSOFT)    += hid-microsoft.o
 obj-$(CONFIG_HID_MONTEREY)     += hid-monterey.o
index 14007dc..5b19aea 100644 (file)
@@ -30,6 +30,7 @@
 #include <asm/unaligned.h>
 #include "usbhid/usbhid.h"
 #include "hid-ids.h"
+#include "hid-logitech-hidpp.h"
 
 #define DJ_MAX_PAIRED_DEVICES                  6
 #define DJ_MAX_NUMBER_NOTIFICATIONS            8
@@ -117,16 +118,6 @@ struct dj_device {
        unsigned hid_device_started:1;
 };
 
-/**
- * is_dj_device - know if the given dj_device is not the receiver.
- * @dj_dev: the dj device to test
- *
- * This macro tests if a struct dj_device pointer is a device created
- * by the bus enumarator.
- */
-#define is_dj_device(dj_dev) \
-       (&(dj_dev)->dj_receiver_dev->hdev->dev == (dj_dev)->hdev->dev.parent)
-
 /* Keyboard descriptor (1) */
 static const char kbd_descriptor[] = {
        0x05, 0x01,             /* USAGE_PAGE (generic Desktop)     */
@@ -286,6 +277,22 @@ static const u8 hid_reportid_size_map[NUMBER_OF_HID_REPORTS] = {
 
 static struct hid_ll_driver logi_dj_ll_driver;
 
+static void logi_dj_print_raw_event(const char *header, u8 *data, int size)
+{
+       int i;
+       unsigned char log[96];
+       unsigned char tmpstr[60];
+
+       snprintf(log, sizeof(tmpstr), "%s (size=%d)", header, size);
+
+       for (i = 0; i < size; i++) {
+               snprintf(tmpstr, sizeof(tmpstr), " %02x", data[i]);
+               strlcat(log, tmpstr, sizeof(log));
+       }
+
+       dbg_hid("hid-logitech-dj:%s\n", log);
+}
+
 static int logi_dj_output_hidraw_report(struct hid_device *hid, u8 * buf,
                                        size_t count,
                                        unsigned char report_type);
@@ -350,13 +357,14 @@ static void logi_dj_recv_add_djhid_device(struct dj_receiver_dev *djrcv_dev,
        dj_hiddev->hid_output_raw_report = logi_dj_output_hidraw_report;
 
        dj_hiddev->dev.parent = &djrcv_hdev->dev;
-       dj_hiddev->bus = BUS_USB;
+       dj_hiddev->bus = BUS_DJ;
        dj_hiddev->vendor = le16_to_cpu(usbdev->descriptor.idVendor);
-       dj_hiddev->product = le16_to_cpu(usbdev->descriptor.idProduct);
+       dj_hiddev->product =
+               dj_report->report_params[DEVICE_PAIRED_PARAM_EQUAD_ID_MSB] << 8
+               | dj_report->report_params[DEVICE_PAIRED_PARAM_EQUAD_ID_LSB];
        snprintf(dj_hiddev->name, sizeof(dj_hiddev->name),
-               "Logitech Unifying Device. Wireless PID:%02x%02x",
-               dj_report->report_params[DEVICE_PAIRED_PARAM_EQUAD_ID_MSB],
-               dj_report->report_params[DEVICE_PAIRED_PARAM_EQUAD_ID_LSB]);
+               "Logitech Unifying Device. Wireless PID:%04x",
+               dj_hiddev->product);
 
        usb_make_path(usbdev, dj_hiddev->phys, sizeof(dj_hiddev->phys));
        snprintf(tmpstr, sizeof(tmpstr), ":%d", dj_report->device_index);
@@ -391,6 +399,7 @@ static void delayedwork_callback(struct work_struct *work)
                container_of(work, struct dj_receiver_dev, work);
        struct hid_device *djrcv_hdev = djrcv_dev->hdev;
 
+       struct hidpp_device *hidpp_dev;
        struct dj_device *djdev;
        struct dj_report dj_report;
        unsigned long flags;
@@ -449,6 +458,12 @@ static void delayedwork_callback(struct work_struct *work)
                                djdev->hid_device_started = 1;
                        }
                }
+               hidpp_dev = hid_get_drvdata(djdev->hdev);
+               if (!hidpp_dev) {
+                       dbg_hid("%s: hidpp_dev is NULL\n", __func__);
+                       return;
+               }
+               hidpp_connect_change(hidpp_dev, connected);
        default:
                dbg_hid("%s: unexpected report type\n", __func__);
        }
@@ -504,6 +519,7 @@ static void logi_dj_recv_forward_report(struct dj_receiver_dev *djrcv_dev,
 {
        /* We are called from atomic context (tasklet && djrcv->lock held) */
        struct dj_device *dj_device;
+       int error;
 
        dj_device = djrcv_dev->paired_dj_devices[dj_report->device_index];
 
@@ -519,13 +535,40 @@ static void logi_dj_recv_forward_report(struct dj_receiver_dev *djrcv_dev,
                return;
        }
 
-       if (hid_input_report(dj_device->hdev,
-                       HID_INPUT_REPORT, &dj_report->report_type,
-                       hid_reportid_size_map[dj_report->report_type], 1)) {
-               dbg_hid("hid_input_report error\n");
-       }
+       error = hid_input_report(dj_device->hdev,
+                               HID_INPUT_REPORT, &dj_report->report_type,
+                               hid_reportid_size_map[dj_report->report_type], 1);
+
+       if (error)
+               dbg_hid("%s:hid_input_report returned error:%d", __func__, error);
 }
 
+static void logi_dj_recv_forward_raw_report(struct dj_receiver_dev *djrcv_dev,
+                       struct dj_report *dj_report,
+                       struct hid_report *report, u8 *data, int size)
+{
+       /* We are called from atomic context (tasklet && djrcv->lock held) */
+
+       struct dj_device *dj_dev = NULL;
+       int error;
+
+       if ((dj_report->device_index < DJ_DEVICE_INDEX_MIN) ||
+           (dj_report->device_index > DJ_DEVICE_INDEX_MAX))
+               return;
+
+       dj_dev = djrcv_dev->paired_dj_devices[dj_report->device_index];
+
+       if (!dj_dev) {
+               dbg_hid("%s:warning, dropping report to device index:%d\n",
+                       __func__, dj_report->device_index);
+               return;
+       }
+
+       error = hid_input_report(dj_dev->hdev, HID_INPUT_REPORT, data, size, 1);
+
+       if (error)
+               dbg_hid("%s:hid_input_report returned error:%d", __func__, error);
+}
 
 static int logi_dj_recv_send_report(struct dj_receiver_dev *djrcv_dev,
                                    struct dj_report *dj_report)
@@ -598,9 +641,32 @@ static int logi_dj_output_hidraw_report(struct hid_device *hid, u8 * buf,
                                        size_t count,
                                        unsigned char report_type)
 {
+       struct dj_device *djdev = hid->driver_data;
+       struct dj_receiver_dev *djrcv_dev = djdev->dj_receiver_dev;
+       struct hid_report* report;
+       struct hid_report_enum *output_report_enum;
+       int i;
+
        /* Called by hid raw to send data */
        dbg_hid("%s\n", __func__);
 
+       switch (buf[0]) {
+       case REPORT_ID_HIDPP_SHORT:
+       case REPORT_ID_HIDPP_LONG:
+               break;
+       default:
+               return -1;
+       }
+
+       output_report_enum = &djrcv_dev->hdev->report_enum[HID_OUTPUT_REPORT];
+       report = output_report_enum->report_id_hash[buf[0]];
+       hid_set_field(report->field[0], 0, djdev->device_index);
+
+       for (i = 2; i < HIDPP_REPORT_LONG_LENGTH - 1; i++)
+               hid_set_field(report->field[0], i-1, buf[i]);
+
+       usbhid_submit_report(djrcv_dev->hdev, report, USB_DIR_OUT);
+
        return 0;
 }
 
@@ -616,6 +682,7 @@ static int logi_dj_ll_parse(struct hid_device *hid)
        unsigned int rsize = 0;
        char *rdesc;
        int retval;
+       struct hid_report *report;
 
        dbg_hid("%s\n", __func__);
 
@@ -661,6 +728,13 @@ static int logi_dj_ll_parse(struct hid_device *hid)
                        __func__, djdev->reports_supported);
        }
 
+       report = hid_register_report(hid, HID_INPUT_REPORT,
+               REPORT_ID_HIDPP_SHORT);
+       report->size = HIDPP_REPORT_SHORT_LENGTH;
+       report = hid_register_report(hid, HID_INPUT_REPORT,
+               REPORT_ID_HIDPP_LONG);
+       report->size = HIDPP_REPORT_LONG_LENGTH;
+
        retval = hid_parse_report(hid, rdesc, rsize);
        kfree(rdesc);
 
@@ -742,9 +816,9 @@ static int logi_dj_raw_event(struct hid_device *hdev,
        unsigned long flags;
        bool report_processed = false;
 
-       dbg_hid("%s, size:%d\n", __func__, size);
+       logi_dj_print_raw_event("logi_dj_raw_event", data, size);
 
-       /* Here we receive all data coming from iface 2, there are 4 cases:
+       /* Here we receive all data coming from iface 2, there are 5 cases:
         *
         * 1) Data should continue its normal processing i.e. data does not
         * come from the DJ collection, in which case we do nothing and
@@ -766,6 +840,12 @@ static int logi_dj_raw_event(struct hid_device *hdev,
         * a paired DJ device in which case we forward it to the correct hid
         * device (via hid_input_report() ) and return 1 so hid-core does not do
         * anything else with it.
+        *
+        * 5) Data is from HIDPP collection, in this case, we forward the data
+        * to the corresponding child hid device and return 0 to hid-core so
+        * the data also goes to the hidraw device of the receiver. This allows
+        * a user space application to implement the full hidpp20 routing via
+        * the receiver.
         */
 
        spin_lock_irqsave(&djrcv_dev->lock, flags);
@@ -786,6 +866,13 @@ static int logi_dj_raw_event(struct hid_device *hdev,
                        logi_dj_recv_forward_report(djrcv_dev, dj_report);
                }
                report_processed = true;
+       } else {
+               if (dj_report->report_id == REPORT_ID_HIDPP_SHORT ||
+                   dj_report->report_id == REPORT_ID_HIDPP_LONG) {
+                       logi_dj_recv_forward_raw_report(djrcv_dev, dj_report,
+                                                       report, data, size);
+                       report_processed = false;
+               }
        }
        spin_unlock_irqrestore(&djrcv_dev->lock, flags);
 
@@ -799,9 +886,6 @@ static int logi_dj_probe(struct hid_device *hdev,
        struct dj_receiver_dev *djrcv_dev;
        int retval;
 
-       if (is_dj_device((struct dj_device *)hdev->driver_data))
-               return -ENODEV;
-
        dbg_hid("%s called for ifnum %d\n", __func__,
                intf->cur_altsetting->desc.bInterfaceNumber);
 
@@ -948,14 +1032,18 @@ static void logi_dj_remove(struct hid_device *hdev)
        hid_set_drvdata(hdev, NULL);
 }
 
+static const u16 dj_have_special_driver[] = {
+};
+
 static int logi_djdevice_probe(struct hid_device *hdev,
                         const struct hid_device_id *id)
 {
-       int ret;
-       struct dj_device *dj_dev = hdev->driver_data;
+       int ret, i;
 
-       if (!is_dj_device(dj_dev))
-               return -ENODEV;
+       for (i = 0; i < ARRAY_SIZE(dj_have_special_driver) ; i++) {
+               if (dj_have_special_driver[i] == hdev->product)
+                       return -ENODEV;
+       }
 
        ret = hid_parse(hdev);
        if (!ret)
@@ -985,12 +1073,8 @@ static struct hid_driver logi_djreceiver_driver = {
 #endif
 };
 
-
 static const struct hid_device_id logi_dj_devices[] = {
-       {HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
-               USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER)},
-       {HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
-               USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2)},
+       {HID_DEVICE(BUS_DJ, USB_VENDOR_ID_LOGITECH, HID_ANY_ID)},
        {}
 };
 
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
new file mode 100644 (file)
index 0000000..691ad8e
--- /dev/null
@@ -0,0 +1,356 @@
+/*
+ *  HIDPP protocol for Logitech Unifying receivers
+ *
+ *  Copyright (c) 2011 Logitech (c)
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so by e-mail send
+ * your message to Benjamin Tissoires <benjamin.tissoires at gmail com>
+ *
+ */
+
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include "hid-logitech-hidpp.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
+MODULE_AUTHOR("Nestor Lopez Casado <nlopezcasad@logitech.com>");
+
+#define MAX_INIT_RETRY 5
+
+enum delayed_work_type {
+       HIDPP_INIT = 0
+};
+
+static void hidpp_print_raw_event(const char *header, u8 *data, int size)
+{
+       int i;
+       unsigned char log[96];
+       unsigned char tmpstr[60];
+
+       snprintf(log, sizeof(tmpstr), "%s (size=%d)", header, size);
+
+       for (i = 0; i < size; i++) {
+               snprintf(tmpstr, sizeof(tmpstr), " %02x", data[i]);
+               strlcat(log, tmpstr, sizeof(log));
+       }
+
+       dbg_hid("%s\n", log);
+}
+
+static int __hidpp_send_report(struct hid_device *hdev,
+                               struct hidpp_report *hidpp_rept)
+{
+       int sent_bytes;
+
+       if (!hdev->hid_output_raw_report) {
+               dev_err(&hdev->dev, "%s:"
+                       "hid_output_raw_report is null\n", __func__);
+               return -ENODEV;
+       }
+
+       hidpp_print_raw_event("sending ", (u8 *)hidpp_rept,
+                               HIDPP_REPORT_LONG_LENGTH);
+       sent_bytes = hdev->hid_output_raw_report(hdev, (u8 *) hidpp_rept,
+                                                sizeof(struct hidpp_report),
+                                                HID_OUTPUT_REPORT);
+
+       return (sent_bytes < 0) ? sent_bytes : 0;
+}
+
+static int hidpp_send_message_sync(struct hidpp_device *hidpp_dev,
+       struct hidpp_report *message,
+       struct hidpp_report *response)
+{
+       int ret;
+
+       mutex_lock(&hidpp_dev->send_mutex);
+
+       hidpp_dev->send_receive_buf = response;
+       hidpp_dev->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_dev->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_dev->wait, hidpp_dev->answer_available, 10*HZ)) {
+               dbg_hid("%s:timeout waiting for response\n", __func__);
+               memset(response, 0, sizeof(struct hidpp_report));
+               ret = -1;
+       }
+
+       if (response->report_id == REPORT_ID_HIDPP_SHORT &&
+           response->fap.feature_index == HIDPP_ERROR) {
+               ret = response->fap.params[0];
+          dbg_hid("__hidpp_send_report got hidpp error %d\n", ret);
+               goto exit;
+       }
+
+exit:
+       mutex_unlock(&hidpp_dev->send_mutex);
+       return ret;
+
+}
+
+int hidpp_send_fap_command_sync(struct hidpp_device *hidpp_dev,
+       u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count,
+       struct hidpp_report *response)
+{
+       struct hidpp_report message;
+
+       if (param_count > sizeof(message.rap.params))
+               return -EINVAL;
+
+       memset(&message, 0, sizeof(message));
+       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);
+
+       return hidpp_send_message_sync(hidpp_dev, &message, response);
+}
+EXPORT_SYMBOL_GPL(hidpp_send_fap_command_sync);
+
+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;
+
+       if ((report_id != REPORT_ID_HIDPP_SHORT) &&
+           (report_id != REPORT_ID_HIDPP_LONG))
+               return -EINVAL;
+
+       if (param_count > sizeof(message.rap.params))
+               return -EINVAL;
+
+       memset(&message, 0, sizeof(message));
+       message.report_id = report_id;
+       message.rap.sub_id = sub_id;
+       message.rap.reg_address = reg_address;
+       memcpy(&message.rap.params, params, param_count);
+
+       return hidpp_send_message_sync(hidpp_dev, &message, response);
+}
+EXPORT_SYMBOL_GPL(hidpp_send_rap_command_sync);
+
+
+static void schedule_delayed_hidpp_init(struct hidpp_device *hidpp_dev)
+{
+       enum delayed_work_type work_type = HIDPP_INIT;
+
+       kfifo_in(&hidpp_dev->delayed_work_fifo, &work_type,
+                               sizeof(enum delayed_work_type));
+
+       if (schedule_work(&hidpp_dev->work) == 0) {
+               dbg_hid("%s: did not schedule the work item,"
+                       " was already queued\n",
+                       __func__);
+       }
+}
+
+void hidpp_delayed_init(struct hidpp_device *hidpp_device)
+{
+       struct hid_device *hdev = hidpp_device->hid_dev;
+       int ret = 0;
+
+       dbg_hid("%s: hdev:%p\n", __func__, hdev);
+
+       if (hidpp_device->initialized)
+               return;
+
+       if (down_trylock(&hidpp_device->hid_dev->driver_lock)) {
+               if (hidpp_device->init_retry < MAX_INIT_RETRY) {
+                       dbg_hid("%s: we need to reschedule the work item."
+                               "Semaphore still held on device\n", __func__);
+                       schedule_delayed_hidpp_init(hidpp_device);
+                       hidpp_device->init_retry++;
+               } else {
+                       dbg_hid("%s: giving up initialization now.", __func__);
+                       hidpp_device->init_retry = 0;
+               }
+               return;
+       }
+       up(&hidpp_device->hid_dev->driver_lock);
+
+       if (hidpp_device->device_init)
+               ret = hidpp_device->device_init(hidpp_device);
+
+       if (!ret)
+               hidpp_device->initialized = true;
+}
+EXPORT_SYMBOL_GPL(hidpp_delayed_init);
+
+static void delayed_work_cb(struct work_struct *work)
+{
+       struct hidpp_device *hidpp_device =
+               container_of(work, struct hidpp_device, work);
+       unsigned long flags;
+       int count;
+       enum delayed_work_type work_type;
+
+       dbg_hid("%s\n", __func__);
+
+       spin_lock_irqsave(&hidpp_device->lock, flags);
+
+       count = kfifo_out(&hidpp_device->delayed_work_fifo, &work_type,
+                               sizeof(enum delayed_work_type));
+
+       if (count != sizeof(enum delayed_work_type)) {
+               dev_err(&hidpp_device->hid_dev->dev, "%s: workitem triggered without "
+                       "notifications available\n", __func__);
+               spin_unlock_irqrestore(&hidpp_device->lock, flags);
+               return;
+       }
+
+       if (!kfifo_is_empty(&hidpp_device->delayed_work_fifo)) {
+               if (schedule_work(&hidpp_device->work) == 0) {
+                       dbg_hid("%s: did not schedule the work item, was "
+                               "already queued\n", __func__);
+               }
+       }
+
+       spin_unlock_irqrestore(&hidpp_device->lock, flags);
+
+       switch (work_type) {
+       case HIDPP_INIT:
+               hidpp_delayed_init(hidpp_device);
+               break;
+       default:
+               dbg_hid("%s: unexpected report type\n", __func__);
+       }
+}
+
+int hidpp_init(struct hidpp_device *hidpp_dev, struct hid_device *hid_dev)
+{
+       if (hidpp_dev->initialized)
+               return 0;
+
+       hidpp_dev->init_retry = 0;
+       hidpp_dev->hid_dev = hid_dev;
+       hidpp_dev->initialized = 1;
+
+       INIT_WORK(&hidpp_dev->work, delayed_work_cb);
+       mutex_init(&hidpp_dev->send_mutex);
+       init_waitqueue_head(&hidpp_dev->wait);
+
+       spin_lock_init(&hidpp_dev->lock);
+       if (kfifo_alloc(&hidpp_dev->delayed_work_fifo,
+                       4 * sizeof(struct hidpp_report),
+                       GFP_KERNEL)) {
+               dev_err(&hidpp_dev->hid_dev->dev,
+                       "%s:failed allocating delayed_work_fifo\n", __func__);
+               mutex_destroy(&hidpp_dev->send_mutex);
+               return -ENOMEM;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(hidpp_init);
+
+void hidpp_connect_change(struct hidpp_device *hidpp_dev, bool connected)
+{
+       if ((!hidpp_dev->initialized) && (connected))
+               hidpp_delayed_init(hidpp_dev);
+
+       if (hidpp_dev->connect_change)
+               hidpp_dev->connect_change(hidpp_dev, connected);
+}
+EXPORT_SYMBOL_GPL(hidpp_connect_change);
+
+void hidpp_remove(struct hidpp_device *hidpp_dev)
+{
+       dbg_hid("%s\n", __func__);
+       cancel_work_sync(&hidpp_dev->work);
+       mutex_destroy(&hidpp_dev->send_mutex);
+       kfifo_free(&hidpp_dev->delayed_work_fifo);
+       hidpp_dev->initialized = false;
+       hidpp_dev->hid_dev = NULL;
+}
+EXPORT_SYMBOL_GPL(hidpp_remove);
+
+int hidpp_raw_event(struct hid_device *hdev, struct hid_report *hid_report,
+                       u8 *data, int size)
+{
+       struct hidpp_device *hidpp_dev = hid_get_drvdata(hdev);
+       struct hidpp_report *report = (struct hidpp_report *)data;
+       struct hidpp_report *question = hidpp_dev->send_receive_buf;
+       struct hidpp_report *answer = hidpp_dev->send_receive_buf;
+
+       dbg_hid("%s\n", __func__);
+
+       hidpp_print_raw_event("hidpp_raw_event", data, size);
+
+       if ((report->report_id != REPORT_ID_HIDPP_LONG) &&
+           (report->report_id != REPORT_ID_HIDPP_SHORT)) {
+               dbg_hid("hid-logitech-hidpp.c:%s: ignore report_id:%d\n",
+                       __func__, report->report_id);
+               return 0;
+       }
+
+       /* If the mutex is locked then we have a pending answer from a
+        * previoulsly sent command
+        */
+       if (unlikely(mutex_is_locked(&hidpp_dev->send_mutex))) {
+               /* Check for a correct hidpp20 answer */
+               bool correct_answer =
+                       report->fap.feature_index == question->fap.feature_index &&
+               report->fap.funcindex_clientid == question->fap.funcindex_clientid;
+               dbg_hid("%s mutex is locked, waiting for reply\n", __func__);
+
+               /* Check for a "correct" hidpp10 error message, this means the
+                * device is hidpp10 and does not support the command sent */
+               correct_answer = correct_answer ||
+                       (report->fap.feature_index == HIDPP_ERROR &&
+               report->fap.funcindex_clientid == question->fap.feature_index &&
+               report->fap.params[0] == question->fap.funcindex_clientid);
+
+               if (correct_answer) {
+                       hidpp_print_raw_event("answer", data, size);
+                       *answer = *report;
+                       hidpp_dev->answer_available = true;
+                       wake_up(&hidpp_dev->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 (hidpp_dev->raw_event != NULL)
+               return hidpp_dev->raw_event(hidpp_dev, report);
+
+       hidpp_print_raw_event("event not treated", data, size);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(hidpp_raw_event);
diff --git a/drivers/hid/hid-logitech-hidpp.h b/drivers/hid/hid-logitech-hidpp.h
new file mode 100644 (file)
index 0000000..3f12daa
--- /dev/null
@@ -0,0 +1,144 @@
+#ifndef __HID_LOGITECH_HIDPP_H
+#define __HID_LOGITECH_HIDPP_H
+
+/*
+ *  HIDPP protocol
+ *
+ *  Copyright (c) 2011 Logitech (c)
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so by e-mail send
+ * your message to Benjamin Tissoires <benjamin.tissoires at gmail com>
+ *
+ */
+#include <linux/kfifo.h>
+
+#define REPORT_ID_HIDPP_SHORT                  0x10
+#define REPORT_ID_HIDPP_LONG                   0x11
+#define REPORT_ID_HIDPP_REL                    0x20
+
+#define HIDPP_REPORT_SHORT_LENGTH              7
+#define HIDPP_REPORT_LONG_LENGTH               20
+
+/* 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_LONG_LENGTH - 4U];
+};
+
+struct rap {
+       u8 sub_id;
+       u8 reg_address;
+       u8 params[HIDPP_REPORT_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;
+       void *driver_data;
+
+       int (*raw_event)(struct hidpp_device *hidpp_dev, struct hidpp_report *report);
+       int (*device_init)(struct hidpp_device *hidpp_dev);
+       void (*connect_change)(struct hidpp_device *hidpp_dev, bool connected);
+
+       /* private */
+       struct work_struct work;
+       struct mutex send_mutex;
+       struct kfifo delayed_work_fifo;
+       spinlock_t lock;
+       void *send_receive_buf;
+       wait_queue_head_t wait;
+       bool answer_available;
+       bool initialized;
+       int init_retry;
+};
+
+extern int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
+                       u8 *data, int size);
+
+extern void hidpp_connect_change(struct hidpp_device *hidpp_dev, bool connected);
+extern int hidpp_init(struct hidpp_device *hidpp_dev, struct hid_device *hid_dev);
+extern void hidpp_delayed_init(struct hidpp_device *hidpp_device);
+extern void hidpp_remove(struct hidpp_device *hidpp_dev);
+
+extern int hidpp_send_command_sync(struct hidpp_device *hidpp_dev,
+                       u16 feature, u8 cmd, u8 *params, int param_count,
+                       struct hidpp_report *response);
+
+
+extern int hidpp_send_fap_command_sync(struct hidpp_device *hidpp_dev,
+               u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count,
+               struct hidpp_report *response);
+
+extern 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);
+
+#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
+
+#define HIDPP_TYPE_KEYBOARD                    0x00
+#define HIDPP_TYPE_REMOTE_CONTROL              0x01
+#define HIDPP_TYPE_NUMPAD                      0x02
+#define HIDPP_TYPE_MOUSE                       0x03
+#define HIDPP_TYPE_TOUCHPAD                    0x04
+#define HIDPP_TYPE_TRACKBALL                   0x05
+#define HIDPP_TYPE_PRESENTER                   0x06
+#define HIDPP_TYPE_RECEIVER                    0x07
+
+#endif
index a816714..2ac17b0 100644 (file)
@@ -924,6 +924,7 @@ struct input_keymap_entry {
 #define BUS_GSC                        0x1A
 #define BUS_ATARI              0x1B
 #define BUS_SPI                        0x1C
+#define BUS_DJ                 0x1D
 
 /*
  * MT_TOOL types