From: Andrew de los Reyes Date: Sun, 6 Jan 2013 01:49:05 +0000 (-0800) Subject: CHROMEOS: HID: Helper functions for Logitech HID++ X-Git-Url: http://git.cascardo.eti.br/?a=commitdiff_plain;h=782883b335f7e44563e1f5d47bcbaa02ecf611e8;p=cascardo%2Flinux.git CHROMEOS: HID: Helper functions for Logitech HID++ 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 Change-Id: I1b87558cc31cb77ef277820f0b00f3ea6e1b367e Reviewed-on: https://gerrit.chromium.org/gerrit/46009 Reviewed-by: Yufeng Shen --- diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index a6f6b50b67af..50618158dbcf 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -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 diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 22f1d16cd79c..ce7afbc9cb25 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -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 diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 14007dc3c032..5b19aea6150d 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -30,6 +30,7 @@ #include #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 index 000000000000..691ad8e16ed8 --- /dev/null +++ b/drivers/hid/hid-logitech-hidpp.c @@ -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 + * + */ + + +#include +#include +#include +#include +#include +#include "hid-logitech-hidpp.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Benjamin Tissoires "); +MODULE_AUTHOR("Nestor Lopez Casado "); + +#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 index 000000000000..3f12daa9517b --- /dev/null +++ b/drivers/hid/hid-logitech-hidpp.h @@ -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 + * + */ +#include + +#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 diff --git a/include/linux/input.h b/include/linux/input.h index a81671453575..2ac17b0644a8 100644 --- a/include/linux/input.h +++ b/include/linux/input.h @@ -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