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.
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
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
#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
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) */
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);
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);
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;
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__);
}
{
/* 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];
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)
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;
}
unsigned int rsize = 0;
char *rdesc;
int retval;
+ struct hid_report *report;
dbg_hid("%s\n", __func__);
__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);
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
* 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);
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);
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);
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)
#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)},
{}
};
--- /dev/null
+/*
+ * 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);
--- /dev/null
+#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
#define BUS_GSC 0x1A
#define BUS_ATARI 0x1B
#define BUS_SPI 0x1C
+#define BUS_DJ 0x1D
/*
* MT_TOOL types