From 37cf6e6fc34e2fca4e7c565697e7cd5c317bc316 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 14 Nov 2012 16:59:13 +0100 Subject: HID: export hidinput_calc_abs_res Exporting the function allows us to calculate the resolution in third party drivers like hid-multitouch. This patch also complete the function with additional valid axes. Signed-off-by: Benjamin Tissoires Acked-by: Jiri Kosina Signed-off-by: Jiri Kosina --- include/linux/hid.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include') diff --git a/include/linux/hid.h b/include/linux/hid.h index c076041a069e..c6bef8f54a82 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -706,6 +706,7 @@ int hid_input_report(struct hid_device *, int type, u8 *, int, int); int hidinput_find_field(struct hid_device *hid, unsigned int type, unsigned int code, struct hid_field **field); struct hid_field *hidinput_get_led_field(struct hid_device *hid); unsigned int hidinput_count_leds(struct hid_device *hid); +__s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code); void hid_output_report(struct hid_report *report, __u8 *data); struct hid_device *hid_allocate_device(void); struct hid_report *hid_register_report(struct hid_device *device, unsigned type, unsigned id); -- cgit v1.2.3 From 774638386826621c984ab6994439f474709cac5e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 14 Nov 2012 16:59:15 +0100 Subject: HID: fix unit exponent parsing HID spec details special values for the HID field unit exponent. Basically, the range [0x8..0xf] correspond to [-8..-1], so this is a standard two's complement on a half-byte. Signed-off-by: Benjamin Tissoires Acked-by: Jiri Kosina Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 16 +++++++++++++++- drivers/hid/hid-input.c | 13 +++++++++---- include/linux/hid.h | 1 + 3 files changed, 25 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index f4109fd657ff..f5004e2ca0c5 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -315,6 +315,7 @@ static s32 item_sdata(struct hid_item *item) static int hid_parser_global(struct hid_parser *parser, struct hid_item *item) { + __u32 raw_value; switch (item->tag) { case HID_GLOBAL_ITEM_TAG_PUSH: @@ -365,7 +366,14 @@ static int hid_parser_global(struct hid_parser *parser, struct hid_item *item) return 0; case HID_GLOBAL_ITEM_TAG_UNIT_EXPONENT: - parser->global.unit_exponent = item_sdata(item); + /* Units exponent negative numbers are given through a + * two's complement. + * See "6.2.2.7 Global Items" for more information. */ + raw_value = item_udata(item); + if (!(raw_value & 0xfffffff0)) + parser->global.unit_exponent = hid_snto32(raw_value, 4); + else + parser->global.unit_exponent = raw_value; return 0; case HID_GLOBAL_ITEM_TAG_UNIT: @@ -865,6 +873,12 @@ static s32 snto32(__u32 value, unsigned n) return value & (1 << (n - 1)) ? value | (-1 << n) : value; } +s32 hid_snto32(__u32 value, unsigned n) +{ + return snto32(value, n); +} +EXPORT_SYMBOL_GPL(hid_snto32); + /* * Convert a signed 32-bit integer to a signed n-bit integer. */ diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 1b0adc3f7803..007a9433bfa7 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -192,7 +192,6 @@ static int hidinput_setkeycode(struct input_dev *dev, return -EINVAL; } - /** * hidinput_calc_abs_res - calculate an absolute axis resolution * @field: the HID report field to calculate resolution for @@ -235,17 +234,23 @@ __s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code) case ABS_MT_TOOL_Y: case ABS_MT_TOUCH_MAJOR: case ABS_MT_TOUCH_MINOR: - if (field->unit == 0x11) { /* If centimeters */ + if (field->unit & 0xffffff00) /* Not a length */ + return 0; + unit_exponent += hid_snto32(field->unit >> 4, 4) - 1; + switch (field->unit & 0xf) { + case 0x1: /* If centimeters */ /* Convert to millimeters */ unit_exponent += 1; - } else if (field->unit == 0x13) { /* If inches */ + break; + case 0x3: /* If inches */ /* Convert to millimeters */ prev = physical_extents; physical_extents *= 254; if (physical_extents < prev) return 0; unit_exponent -= 1; - } else { + break; + default: return 0; } break; diff --git a/include/linux/hid.h b/include/linux/hid.h index c6bef8f54a82..4161bf2c0b5b 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -717,6 +717,7 @@ int hid_connect(struct hid_device *hid, unsigned int connect_mask); void hid_disconnect(struct hid_device *hid); const struct hid_device_id *hid_match_id(struct hid_device *hdev, const struct hid_device_id *id); +s32 hid_snto32(__u32 value, unsigned n); /** * hid_map_usage - map usage input bits -- cgit v1.2.3 From f262d1fa2c651a5e2f92b6aee8779597631cd5d4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 14 Nov 2012 16:59:16 +0100 Subject: HID: add usage_index in struct hid_usage. Currently, there is no way to know the index of the current field in the .input_mapping and .event callbacks when this field is inside an array of HID fields. This patch adds this index to the struct hid_usage so that this information is available to input_mapping and event callbacks. Signed-off-by: Benjamin Tissoires Acked-by: Jiri Kosina Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 4 ++++ include/linux/hid.h | 1 + 2 files changed, 5 insertions(+) (limited to 'include') diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index f5004e2ca0c5..b5974cd45968 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -92,6 +92,7 @@ EXPORT_SYMBOL_GPL(hid_register_report); static struct hid_field *hid_register_field(struct hid_report *report, unsigned usages, unsigned values) { struct hid_field *field; + int i; if (report->maxfield == HID_MAX_FIELDS) { hid_err(report->device, "too many fields in report\n"); @@ -110,6 +111,9 @@ static struct hid_field *hid_register_field(struct hid_report *report, unsigned field->value = (s32 *)(field->usage + usages); field->report = report; + for (i = 0; i < usages; i++) + field->usage[i].usage_index = i; + return field; } diff --git a/include/linux/hid.h b/include/linux/hid.h index 4161bf2c0b5b..d2c42dd222c1 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -342,6 +342,7 @@ struct hid_collection { struct hid_usage { unsigned hid; /* hid usage code */ unsigned collection_index; /* index into collection array */ + unsigned usage_index; /* index into usage array */ /* hidinput data */ __u16 code; /* input driver code */ __u8 type; /* input driver type */ -- cgit v1.2.3 From af8036dd749fbf4e732161ff0f3874759b73be40 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 14 Nov 2012 16:59:21 +0100 Subject: Input: introduce EV_MSC Timestamp Some devices provides the actual timestamp (hid_dg_scan_time in win8 ones) computed by the hardware itself. This value is global to the frame and is not specific to the multitouch protocol. Signed-off-by: Benjamin Tissoires Reviewed-by: Henrik Rydberg Acked-by: Dmitry Torokhov Signed-off-by: Jiri Kosina --- Documentation/input/event-codes.txt | 11 +++++++++++ include/uapi/linux/input.h | 1 + 2 files changed, 12 insertions(+) (limited to 'include') diff --git a/Documentation/input/event-codes.txt b/Documentation/input/event-codes.txt index 53305bd08182..f1ea2c69648d 100644 --- a/Documentation/input/event-codes.txt +++ b/Documentation/input/event-codes.txt @@ -196,6 +196,17 @@ EV_MSC: EV_MSC events are used for input and output events that do not fall under other categories. +A few EV_MSC codes have special meaning: + +* MSC_TIMESTAMP: + - Used to report the number of microseconds since the last reset. This event + should be coded as an uint32 value, which is allowed to wrap around with + no special consequence. It is assumed that the time difference between two + consecutive events is reliable on a reasonable time scale (hours). + A reset to zero can happen, in which case the time since the last event is + unknown. If the device does not provide this information, the driver must + not provide it to user space. + EV_LED: ---------- EV_LED events are used for input and output to set and query the state of diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h index 558828590a69..935119c698ac 100644 --- a/include/uapi/linux/input.h +++ b/include/uapi/linux/input.h @@ -851,6 +851,7 @@ struct input_keymap_entry { #define MSC_GESTURE 0x02 #define MSC_RAW 0x03 #define MSC_SCAN 0x04 +#define MSC_TIMESTAMP 0x05 #define MSC_MAX 0x07 #define MSC_CNT (MSC_MAX+1) -- cgit v1.2.3 From 29807d1e24b7cd696442d2f600057230f084b3c7 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 14 Nov 2012 16:59:22 +0100 Subject: Input: mt: add input_mt_is_used This patch extracts the test (slot->frame == mt->frame) so that it can be used in third party drivers. Signed-off-by: Benjamin Tissoires Reviewed-by: Henrik Rydberg Signed-off-by: Jiri Kosina --- drivers/input/input-mt.c | 2 +- include/linux/input/mt.h | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/input/input-mt.c b/drivers/input/input-mt.c index c0ec7d42c3be..475b9d424eed 100644 --- a/drivers/input/input-mt.c +++ b/drivers/input/input-mt.c @@ -247,7 +247,7 @@ void input_mt_sync_frame(struct input_dev *dev) if (mt->flags & INPUT_MT_DROP_UNUSED) { for (s = mt->slots; s != mt->slots + mt->num_slots; s++) { - if (s->frame == mt->frame) + if (input_mt_is_used(mt, s)) continue; input_mt_slot(dev, s - mt->slots); input_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); diff --git a/include/linux/input/mt.h b/include/linux/input/mt.h index cc5cca774bab..2e86bd0bfba1 100644 --- a/include/linux/input/mt.h +++ b/include/linux/input/mt.h @@ -69,6 +69,12 @@ static inline bool input_mt_is_active(const struct input_mt_slot *slot) return input_mt_get_value(slot, ABS_MT_TRACKING_ID) >= 0; } +static inline bool input_mt_is_used(const struct input_mt *mt, + const struct input_mt_slot *slot) +{ + return slot->frame == mt->frame; +} + int input_mt_init_slots(struct input_dev *dev, unsigned int num_slots, unsigned int flags); void input_mt_destroy_slots(struct input_dev *dev); -- cgit v1.2.3 From 4a200c3b9a40242652b5734630bdd0bcf3aca75f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 12 Nov 2012 15:42:59 +0100 Subject: HID: i2c-hid: introduce HID over i2c specification implementation Microsoft published the protocol specification of HID over i2c: http://msdn.microsoft.com/en-us/library/windows/hardware/hh852380.aspx This patch introduces an implementation of this protocol. This implementation does not includes the ACPI part of the specification. This will come when ACPI 5.0 devices enumeration will be available. Once the ACPI part is done, OEM will not have to declare HID over I2C devices in their platform specific driver. Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 2 + drivers/hid/Makefile | 1 + drivers/hid/i2c-hid/Kconfig | 21 + drivers/hid/i2c-hid/Makefile | 5 + drivers/hid/i2c-hid/i2c-hid.c | 974 ++++++++++++++++++++++++++++++++++++++++++ include/linux/i2c/i2c-hid.h | 35 ++ 6 files changed, 1038 insertions(+) create mode 100644 drivers/hid/i2c-hid/Kconfig create mode 100644 drivers/hid/i2c-hid/Makefile create mode 100644 drivers/hid/i2c-hid/i2c-hid.c create mode 100644 include/linux/i2c/i2c-hid.h (limited to 'include') diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 1630150ad2b1..95cfe9181bf2 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -728,4 +728,6 @@ endif # HID source "drivers/hid/usbhid/Kconfig" +source "drivers/hid/i2c-hid/Kconfig" + endmenu diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index cef68ca859d3..a1a28befac72 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -118,3 +118,4 @@ obj-$(CONFIG_USB_HID) += usbhid/ obj-$(CONFIG_USB_MOUSE) += usbhid/ obj-$(CONFIG_USB_KBD) += usbhid/ +obj-$(CONFIG_I2C_HID) += i2c-hid/ diff --git a/drivers/hid/i2c-hid/Kconfig b/drivers/hid/i2c-hid/Kconfig new file mode 100644 index 000000000000..5b7d4d87a143 --- /dev/null +++ b/drivers/hid/i2c-hid/Kconfig @@ -0,0 +1,21 @@ +menu "I2C HID support" + depends on I2C + +config I2C_HID + tristate "HID over I2C transport layer" + default n + depends on I2C && INPUT + select HID + ---help--- + Say Y here if you want to use the HID over i2c protocol + implementation. + + If unsure, say N. + + This support is also available as a module. If so, the module + will be called i2c-hid. + +comment "Input core support is needed for HID over I2C input layer" + depends on I2C_HID && INPUT=n + +endmenu diff --git a/drivers/hid/i2c-hid/Makefile b/drivers/hid/i2c-hid/Makefile new file mode 100644 index 000000000000..832d8f9aaba2 --- /dev/null +++ b/drivers/hid/i2c-hid/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the I2C input drivers +# + +obj-$(CONFIG_I2C_HID) += i2c-hid.o diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c new file mode 100644 index 000000000000..11140bdae660 --- /dev/null +++ b/drivers/hid/i2c-hid/i2c-hid.c @@ -0,0 +1,974 @@ +/* + * HID over I2C protocol implementation + * + * Copyright (c) 2012 Benjamin Tissoires + * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France + * Copyright (c) 2012 Red Hat, Inc + * + * This code is partly based on "USB HID support for Linux": + * + * Copyright (c) 1999 Andreas Gal + * Copyright (c) 2000-2005 Vojtech Pavlik + * Copyright (c) 2005 Michael Haboustak for Concept2, Inc + * Copyright (c) 2007-2008 Oliver Neukum + * Copyright (c) 2006-2010 Jiri Kosina + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* flags */ +#define I2C_HID_STARTED (1 << 0) +#define I2C_HID_RESET_PENDING (1 << 1) +#define I2C_HID_READ_PENDING (1 << 2) + +#define I2C_HID_PWR_ON 0x00 +#define I2C_HID_PWR_SLEEP 0x01 + +/* debug option */ +static bool debug = false; +module_param(debug, bool, 0444); +MODULE_PARM_DESC(debug, "print a lot of debug information"); + +#define i2c_hid_dbg(ihid, fmt, arg...) \ + if (debug) \ + dev_printk(KERN_DEBUG, &(ihid)->client->dev, fmt, ##arg) + +struct i2c_hid_desc { + __le16 wHIDDescLength; + __le16 bcdVersion; + __le16 wReportDescLength; + __le16 wReportDescRegister; + __le16 wInputRegister; + __le16 wMaxInputLength; + __le16 wOutputRegister; + __le16 wMaxOutputLength; + __le16 wCommandRegister; + __le16 wDataRegister; + __le16 wVendorID; + __le16 wProductID; + __le16 wVersionID; +} __packed; + +struct i2c_hid_cmd { + unsigned int registerIndex; + __u8 opcode; + unsigned int length; + bool wait; +}; + +union command { + u8 data[0]; + struct cmd { + __le16 reg; + __u8 reportTypeID; + __u8 opcode; + } __packed c; +}; + +#define I2C_HID_CMD(opcode_) \ + .opcode = opcode_, .length = 4, \ + .registerIndex = offsetof(struct i2c_hid_desc, wCommandRegister) + +/* fetch HID descriptor */ +static const struct i2c_hid_cmd hid_descr_cmd = { .length = 2 }; +/* fetch report descriptors */ +static const struct i2c_hid_cmd hid_report_descr_cmd = { + .registerIndex = offsetof(struct i2c_hid_desc, + wReportDescRegister), + .opcode = 0x00, + .length = 2 }; +/* commands */ +static const struct i2c_hid_cmd hid_reset_cmd = { I2C_HID_CMD(0x01), + .wait = true }; +static const struct i2c_hid_cmd hid_get_report_cmd = { I2C_HID_CMD(0x02) }; +static const struct i2c_hid_cmd hid_set_report_cmd = { I2C_HID_CMD(0x03) }; +static const struct i2c_hid_cmd hid_get_idle_cmd = { I2C_HID_CMD(0x04) }; +static const struct i2c_hid_cmd hid_set_idle_cmd = { I2C_HID_CMD(0x05) }; +static const struct i2c_hid_cmd hid_get_protocol_cmd = { I2C_HID_CMD(0x06) }; +static const struct i2c_hid_cmd hid_set_protocol_cmd = { I2C_HID_CMD(0x07) }; +static const struct i2c_hid_cmd hid_set_power_cmd = { I2C_HID_CMD(0x08) }; +/* read/write data register */ +static const struct i2c_hid_cmd hid_data_cmd = { + .registerIndex = offsetof(struct i2c_hid_desc, wDataRegister), + .opcode = 0x00, + .length = 2 }; +/* write output reports */ +static const struct i2c_hid_cmd hid_out_cmd = { + .registerIndex = offsetof(struct i2c_hid_desc, + wOutputRegister), + .opcode = 0x00, + .length = 2 }; + +/* The main device structure */ +struct i2c_hid { + struct i2c_client *client; /* i2c client */ + struct hid_device *hid; /* pointer to corresponding HID dev */ + union { + __u8 hdesc_buffer[sizeof(struct i2c_hid_desc)]; + struct i2c_hid_desc hdesc; /* the HID Descriptor */ + }; + __le16 wHIDDescRegister; /* location of the i2c + * register of the HID + * descriptor. */ + unsigned int bufsize; /* i2c buffer size */ + char *inbuf; /* Input buffer */ + char *cmdbuf; /* Command buffer */ + char *argsbuf; /* Command arguments buffer */ + + unsigned long flags; /* device flags */ + + int irq; /* the interrupt line irq */ + + wait_queue_head_t wait; /* For waiting the interrupt */ +}; + +static int __i2c_hid_command(struct i2c_client *client, + const struct i2c_hid_cmd *command, u8 reportID, + u8 reportType, u8 *args, int args_len, + unsigned char *buf_recv, int data_len) +{ + struct i2c_hid *ihid = i2c_get_clientdata(client); + union command *cmd = (union command *)ihid->cmdbuf; + int ret; + struct i2c_msg msg[2]; + int msg_num = 1; + + int length = command->length; + bool wait = command->wait; + unsigned int registerIndex = command->registerIndex; + + /* special case for hid_descr_cmd */ + if (command == &hid_descr_cmd) { + cmd->c.reg = ihid->wHIDDescRegister; + } else { + cmd->data[0] = ihid->hdesc_buffer[registerIndex]; + cmd->data[1] = ihid->hdesc_buffer[registerIndex + 1]; + } + + if (length > 2) { + cmd->c.opcode = command->opcode; + cmd->c.reportTypeID = reportID | reportType << 4; + } + + memcpy(cmd->data + length, args, args_len); + length += args_len; + + i2c_hid_dbg(ihid, "%s: cmd=%*ph\n", __func__, length, cmd->data); + + msg[0].addr = client->addr; + msg[0].flags = client->flags & I2C_M_TEN; + msg[0].len = length; + msg[0].buf = cmd->data; + if (data_len > 0) { + msg[1].addr = client->addr; + msg[1].flags = client->flags & I2C_M_TEN; + msg[1].flags |= I2C_M_RD; + msg[1].len = data_len; + msg[1].buf = buf_recv; + msg_num = 2; + set_bit(I2C_HID_READ_PENDING, &ihid->flags); + } + + if (wait) + set_bit(I2C_HID_RESET_PENDING, &ihid->flags); + + ret = i2c_transfer(client->adapter, msg, msg_num); + + if (data_len > 0) + clear_bit(I2C_HID_READ_PENDING, &ihid->flags); + + if (ret != msg_num) + return ret < 0 ? ret : -EIO; + + ret = 0; + + if (wait) { + i2c_hid_dbg(ihid, "%s: waiting...\n", __func__); + if (!wait_event_timeout(ihid->wait, + !test_bit(I2C_HID_RESET_PENDING, &ihid->flags), + msecs_to_jiffies(5000))) + ret = -ENODATA; + i2c_hid_dbg(ihid, "%s: finished.\n", __func__); + } + + return ret; +} + +static int i2c_hid_command(struct i2c_client *client, + const struct i2c_hid_cmd *command, + unsigned char *buf_recv, int data_len) +{ + return __i2c_hid_command(client, command, 0, 0, NULL, 0, + buf_recv, data_len); +} + +static int i2c_hid_get_report(struct i2c_client *client, u8 reportType, + u8 reportID, unsigned char *buf_recv, int data_len) +{ + struct i2c_hid *ihid = i2c_get_clientdata(client); + u8 args[3]; + int ret; + int args_len = 0; + u16 readRegister = le16_to_cpu(ihid->hdesc.wDataRegister); + + i2c_hid_dbg(ihid, "%s\n", __func__); + + if (reportID >= 0x0F) { + args[args_len++] = reportID; + reportID = 0x0F; + } + + args[args_len++] = readRegister & 0xFF; + args[args_len++] = readRegister >> 8; + + ret = __i2c_hid_command(client, &hid_get_report_cmd, reportID, + reportType, args, args_len, buf_recv, data_len); + if (ret) { + dev_err(&client->dev, + "failed to retrieve report from device.\n"); + return -EINVAL; + } + + return 0; +} + +static int i2c_hid_set_report(struct i2c_client *client, u8 reportType, + u8 reportID, unsigned char *buf, size_t data_len) +{ + struct i2c_hid *ihid = i2c_get_clientdata(client); + u8 *args = ihid->argsbuf; + int ret; + u16 dataRegister = le16_to_cpu(ihid->hdesc.wDataRegister); + + /* hidraw already checked that data_len < HID_MAX_BUFFER_SIZE */ + u16 size = 2 /* size */ + + (reportID ? 1 : 0) /* reportID */ + + data_len /* buf */; + int args_len = (reportID >= 0x0F ? 1 : 0) /* optional third byte */ + + 2 /* dataRegister */ + + size /* args */; + int index = 0; + + i2c_hid_dbg(ihid, "%s\n", __func__); + + if (reportID >= 0x0F) { + args[index++] = reportID; + reportID = 0x0F; + } + + args[index++] = dataRegister & 0xFF; + args[index++] = dataRegister >> 8; + + args[index++] = size & 0xFF; + args[index++] = size >> 8; + + if (reportID) + args[index++] = reportID; + + memcpy(&args[index], buf, data_len); + + ret = __i2c_hid_command(client, &hid_set_report_cmd, reportID, + reportType, args, args_len, NULL, 0); + if (ret) { + dev_err(&client->dev, "failed to set a report to device.\n"); + return -EINVAL; + } + + return data_len; +} + +static int i2c_hid_set_power(struct i2c_client *client, int power_state) +{ + struct i2c_hid *ihid = i2c_get_clientdata(client); + int ret; + + i2c_hid_dbg(ihid, "%s\n", __func__); + + ret = __i2c_hid_command(client, &hid_set_power_cmd, power_state, + 0, NULL, 0, NULL, 0); + if (ret) + dev_err(&client->dev, "failed to change power setting.\n"); + + return ret; +} + +static int i2c_hid_hwreset(struct i2c_client *client) +{ + struct i2c_hid *ihid = i2c_get_clientdata(client); + int ret; + + i2c_hid_dbg(ihid, "%s\n", __func__); + + ret = i2c_hid_set_power(client, I2C_HID_PWR_ON); + if (ret) + return ret; + + i2c_hid_dbg(ihid, "resetting...\n"); + + ret = i2c_hid_command(client, &hid_reset_cmd, NULL, 0); + if (ret) { + dev_err(&client->dev, "failed to reset device.\n"); + i2c_hid_set_power(client, I2C_HID_PWR_SLEEP); + return ret; + } + + return 0; +} + +static int i2c_hid_get_input(struct i2c_hid *ihid) +{ + int ret, ret_size; + int size = le16_to_cpu(ihid->hdesc.wMaxInputLength); + + ret = i2c_master_recv(ihid->client, ihid->inbuf, size); + if (ret != size) { + if (ret < 0) + return ret; + + dev_err(&ihid->client->dev, "%s: got %d data instead of %d\n", + __func__, ret, size); + return ret; + } + + ret_size = ihid->inbuf[0] | ihid->inbuf[1] << 8; + + if (!ret_size) { + /* host or device initiated RESET completed */ + if (test_and_clear_bit(I2C_HID_RESET_PENDING, &ihid->flags)) + wake_up(&ihid->wait); + return 0; + } + + if (ret_size > size) { + dev_err(&ihid->client->dev, "%s: incomplete report (%d/%d)\n", + __func__, size, ret_size); + return -EIO; + } + + i2c_hid_dbg(ihid, "input: %*ph\n", ret_size, ihid->inbuf); + + if (test_bit(I2C_HID_STARTED, &ihid->flags)) + hid_input_report(ihid->hid, HID_INPUT_REPORT, ihid->inbuf + 2, + ret_size - 2, 1); + + return 0; +} + +static irqreturn_t i2c_hid_irq(int irq, void *dev_id) +{ + struct i2c_hid *ihid = dev_id; + + if (test_bit(I2C_HID_READ_PENDING, &ihid->flags)) + return IRQ_HANDLED; + + i2c_hid_get_input(ihid); + + return IRQ_HANDLED; +} + +static int i2c_hid_get_report_length(struct hid_report *report) +{ + return ((report->size - 1) >> 3) + 1 + + report->device->report_enum[report->type].numbered + 2; +} + +static void i2c_hid_init_report(struct hid_report *report, u8 *buffer, + size_t bufsize) +{ + struct hid_device *hid = report->device; + struct i2c_client *client = hid->driver_data; + struct i2c_hid *ihid = i2c_get_clientdata(client); + unsigned int size, ret_size; + + size = i2c_hid_get_report_length(report); + i2c_hid_get_report(client, + report->type == HID_FEATURE_REPORT ? 0x03 : 0x01, + report->id, buffer, size); + + i2c_hid_dbg(ihid, "report (len=%d): %*ph\n", size, size, ihid->inbuf); + + ret_size = buffer[0] | (buffer[1] << 8); + + if (ret_size != size) { + dev_err(&client->dev, "error in %s size:%d / ret_size:%d\n", + __func__, size, ret_size); + return; + } + + /* hid->driver_lock is held as we are in probe function, + * we just need to setup the input fields, so using + * hid_report_raw_event is safe. */ + hid_report_raw_event(hid, report->type, buffer + 2, size - 2, 1); +} + +/* + * Initialize all reports + */ +static void i2c_hid_init_reports(struct hid_device *hid) +{ + struct hid_report *report; + struct i2c_client *client = hid->driver_data; + struct i2c_hid *ihid = i2c_get_clientdata(client); + u8 *inbuf = kzalloc(ihid->bufsize, GFP_KERNEL); + + if (!inbuf) + return; + + list_for_each_entry(report, + &hid->report_enum[HID_INPUT_REPORT].report_list, list) + i2c_hid_init_report(report, inbuf, ihid->bufsize); + + list_for_each_entry(report, + &hid->report_enum[HID_FEATURE_REPORT].report_list, list) + i2c_hid_init_report(report, inbuf, ihid->bufsize); + + kfree(inbuf); +} + +/* + * Traverse the supplied list of reports and find the longest + */ +static void i2c_hid_find_max_report(struct hid_device *hid, unsigned int type, + unsigned int *max) +{ + struct hid_report *report; + unsigned int size; + + /* We should not rely on wMaxInputLength, as some devices may set it to + * a wrong length. */ + list_for_each_entry(report, &hid->report_enum[type].report_list, list) { + size = i2c_hid_get_report_length(report); + if (*max < size) + *max = size; + } +} + +static int i2c_hid_alloc_buffers(struct i2c_hid *ihid) +{ + /* the worst case is computed from the set_report command with a + * reportID > 15 and the maximum report length */ + int args_len = sizeof(__u8) + /* optional ReportID byte */ + sizeof(__u16) + /* data register */ + sizeof(__u16) + /* size of the report */ + ihid->bufsize; /* report */ + + ihid->inbuf = kzalloc(ihid->bufsize, GFP_KERNEL); + + if (!ihid->inbuf) + return -ENOMEM; + + ihid->argsbuf = kzalloc(args_len, GFP_KERNEL); + + if (!ihid->argsbuf) { + kfree(ihid->inbuf); + return -ENOMEM; + } + + ihid->cmdbuf = kzalloc(sizeof(union command) + args_len, GFP_KERNEL); + + if (!ihid->cmdbuf) { + kfree(ihid->inbuf); + kfree(ihid->argsbuf); + ihid->inbuf = NULL; + ihid->argsbuf = NULL; + return -ENOMEM; + } + + return 0; +} + +static void i2c_hid_free_buffers(struct i2c_hid *ihid) +{ + kfree(ihid->inbuf); + kfree(ihid->argsbuf); + kfree(ihid->cmdbuf); + ihid->inbuf = NULL; + ihid->cmdbuf = NULL; + ihid->argsbuf = NULL; +} + +static int i2c_hid_get_raw_report(struct hid_device *hid, + unsigned char report_number, __u8 *buf, size_t count, + unsigned char report_type) +{ + struct i2c_client *client = hid->driver_data; + struct i2c_hid *ihid = i2c_get_clientdata(client); + int ret; + + if (report_type == HID_OUTPUT_REPORT) + return -EINVAL; + + if (count > ihid->bufsize) + count = ihid->bufsize; + + ret = i2c_hid_get_report(client, + report_type == HID_FEATURE_REPORT ? 0x03 : 0x01, + report_number, ihid->inbuf, count); + + if (ret < 0) + return ret; + + count = ihid->inbuf[0] | (ihid->inbuf[1] << 8); + + memcpy(buf, ihid->inbuf + 2, count); + + return count; +} + +static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf, + size_t count, unsigned char report_type) +{ + struct i2c_client *client = hid->driver_data; + int report_id = buf[0]; + + if (report_type == HID_INPUT_REPORT) + return -EINVAL; + + return i2c_hid_set_report(client, + report_type == HID_FEATURE_REPORT ? 0x03 : 0x02, + report_id, buf, count); +} + +static int i2c_hid_parse(struct hid_device *hid) +{ + struct i2c_client *client = hid->driver_data; + struct i2c_hid *ihid = i2c_get_clientdata(client); + struct i2c_hid_desc *hdesc = &ihid->hdesc; + unsigned int rsize; + char *rdesc; + int ret; + int tries = 3; + + i2c_hid_dbg(ihid, "entering %s\n", __func__); + + rsize = le16_to_cpu(hdesc->wReportDescLength); + if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) { + dbg_hid("weird size of report descriptor (%u)\n", rsize); + return -EINVAL; + } + + do { + ret = i2c_hid_hwreset(client); + if (ret) + msleep(1000); + } while (tries-- > 0 && ret); + + if (ret) + return ret; + + rdesc = kzalloc(rsize, GFP_KERNEL); + + if (!rdesc) { + dbg_hid("couldn't allocate rdesc memory\n"); + return -ENOMEM; + } + + i2c_hid_dbg(ihid, "asking HID report descriptor\n"); + + ret = i2c_hid_command(client, &hid_report_descr_cmd, rdesc, rsize); + if (ret) { + hid_err(hid, "reading report descriptor failed\n"); + kfree(rdesc); + return -EIO; + } + + i2c_hid_dbg(ihid, "Report Descriptor: %*ph\n", rsize, rdesc); + + ret = hid_parse_report(hid, rdesc, rsize); + kfree(rdesc); + if (ret) { + dbg_hid("parsing report descriptor failed\n"); + return ret; + } + + return 0; +} + +static int i2c_hid_start(struct hid_device *hid) +{ + struct i2c_client *client = hid->driver_data; + struct i2c_hid *ihid = i2c_get_clientdata(client); + int ret; + int old_bufsize = ihid->bufsize; + + ihid->bufsize = HID_MIN_BUFFER_SIZE; + i2c_hid_find_max_report(hid, HID_INPUT_REPORT, &ihid->bufsize); + i2c_hid_find_max_report(hid, HID_OUTPUT_REPORT, &ihid->bufsize); + i2c_hid_find_max_report(hid, HID_FEATURE_REPORT, &ihid->bufsize); + + if (ihid->bufsize > old_bufsize || !ihid->inbuf || !ihid->cmdbuf) { + i2c_hid_free_buffers(ihid); + + ret = i2c_hid_alloc_buffers(ihid); + + if (ret) { + ihid->bufsize = old_bufsize; + return ret; + } + } + + if (!(hid->quirks & HID_QUIRK_NO_INIT_REPORTS)) + i2c_hid_init_reports(hid); + + return 0; +} + +static void i2c_hid_stop(struct hid_device *hid) +{ + struct i2c_client *client = hid->driver_data; + struct i2c_hid *ihid = i2c_get_clientdata(client); + + hid->claimed = 0; + + i2c_hid_free_buffers(ihid); +} + +static int i2c_hid_open(struct hid_device *hid) +{ + struct i2c_client *client = hid->driver_data; + struct i2c_hid *ihid = i2c_get_clientdata(client); + int ret; + + if (!hid->open++) { + ret = i2c_hid_set_power(client, I2C_HID_PWR_ON); + if (ret) { + hid->open--; + return -EIO; + } + set_bit(I2C_HID_STARTED, &ihid->flags); + } + return 0; +} + +static void i2c_hid_close(struct hid_device *hid) +{ + struct i2c_client *client = hid->driver_data; + struct i2c_hid *ihid = i2c_get_clientdata(client); + + /* protecting hid->open to make sure we don't restart + * data acquistion due to a resumption we no longer + * care about + */ + if (!--hid->open) { + clear_bit(I2C_HID_STARTED, &ihid->flags); + + /* Save some power */ + i2c_hid_set_power(client, I2C_HID_PWR_SLEEP); + } +} + +static int i2c_hid_power(struct hid_device *hid, int lvl) +{ + struct i2c_client *client = hid->driver_data; + struct i2c_hid *ihid = i2c_get_clientdata(client); + int ret = 0; + + i2c_hid_dbg(ihid, "%s lvl:%d\n", __func__, lvl); + + switch (lvl) { + case PM_HINT_FULLON: + ret = i2c_hid_set_power(client, I2C_HID_PWR_ON); + break; + case PM_HINT_NORMAL: + ret = i2c_hid_set_power(client, I2C_HID_PWR_SLEEP); + break; + } + return ret; +} + +static int i2c_hid_hidinput_input_event(struct input_dev *dev, + unsigned int type, unsigned int code, int value) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct hid_field *field; + int offset; + + if (type == EV_FF) + return input_ff_event(dev, type, code, value); + + if (type != EV_LED) + return -1; + + offset = hidinput_find_field(hid, type, code, &field); + + if (offset == -1) { + hid_warn(dev, "event field not found\n"); + return -1; + } + + hid_set_field(field, offset, value); + + return 0; +} + +static struct hid_ll_driver i2c_hid_ll_driver = { + .parse = i2c_hid_parse, + .start = i2c_hid_start, + .stop = i2c_hid_stop, + .open = i2c_hid_open, + .close = i2c_hid_close, + .power = i2c_hid_power, + .hidinput_input_event = i2c_hid_hidinput_input_event, +}; + +static int __devinit i2c_hid_init_irq(struct i2c_client *client) +{ + struct i2c_hid *ihid = i2c_get_clientdata(client); + int ret; + + dev_dbg(&client->dev, "Requesting IRQ: %d\n", client->irq); + + ret = request_threaded_irq(client->irq, NULL, i2c_hid_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + client->name, ihid); + if (ret < 0) { + dev_dbg(&client->dev, + "Could not register for %s interrupt, irq = %d," + " ret = %d\n", + client->name, client->irq, ret); + + return ret; + } + + ihid->irq = client->irq; + + return 0; +} + +static int __devinit i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid) +{ + struct i2c_client *client = ihid->client; + struct i2c_hid_desc *hdesc = &ihid->hdesc; + unsigned int dsize; + int ret; + + /* Fetch the length of HID description, retrieve the 4 first bytes: + * bytes 0-1 -> length + * bytes 2-3 -> bcdVersion (has to be 1.00) */ + ret = i2c_hid_command(client, &hid_descr_cmd, ihid->hdesc_buffer, 4); + + i2c_hid_dbg(ihid, "%s, ihid->hdesc_buffer: %*ph\n", + __func__, 4, ihid->hdesc_buffer); + + if (ret) { + dev_err(&client->dev, "HID_DESCR_LENGTH_CMD Fail (ret=%d)\n", + ret); + return -ENODEV; + } + + dsize = le16_to_cpu(hdesc->wHIDDescLength); + if (!dsize || dsize > HID_MAX_DESCRIPTOR_SIZE) { + dev_err(&client->dev, "weird size of HID descriptor (%u)\n", + dsize); + return -ENODEV; + } + + /* check bcdVersion == 1.0 */ + if (le16_to_cpu(hdesc->bcdVersion) != 0x0100) { + dev_err(&client->dev, + "unexpected HID descriptor bcdVersion (0x%04x)\n", + le16_to_cpu(hdesc->bcdVersion)); + return -ENODEV; + } + + i2c_hid_dbg(ihid, "Fetching the HID descriptor\n"); + + ret = i2c_hid_command(client, &hid_descr_cmd, ihid->hdesc_buffer, + dsize); + if (ret) { + dev_err(&client->dev, "hid_descr_cmd Fail\n"); + return -ENODEV; + } + + i2c_hid_dbg(ihid, "HID Descriptor: %*ph\n", dsize, ihid->hdesc_buffer); + + return 0; +} + +static int __devinit i2c_hid_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + int ret; + struct i2c_hid *ihid; + struct hid_device *hid; + __u16 hidRegister; + struct i2c_hid_platform_data *platform_data = client->dev.platform_data; + + dbg_hid("HID probe called for i2c 0x%02x\n", client->addr); + + if (!platform_data) { + dev_err(&client->dev, "HID register address not provided\n"); + return -EINVAL; + } + + if (!client->irq) { + dev_err(&client->dev, + "HID over i2c has not been provided an Int IRQ\n"); + return -EINVAL; + } + + ihid = kzalloc(sizeof(struct i2c_hid), GFP_KERNEL); + if (!ihid) + return -ENOMEM; + + i2c_set_clientdata(client, ihid); + + ihid->client = client; + + hidRegister = platform_data->hid_descriptor_address; + ihid->wHIDDescRegister = cpu_to_le16(hidRegister); + + init_waitqueue_head(&ihid->wait); + + /* we need to allocate the command buffer without knowing the maximum + * size of the reports. Let's use HID_MIN_BUFFER_SIZE, then we do the + * real computation later. */ + ihid->bufsize = HID_MIN_BUFFER_SIZE; + i2c_hid_alloc_buffers(ihid); + + ret = i2c_hid_fetch_hid_descriptor(ihid); + if (ret < 0) + goto err; + + ret = i2c_hid_init_irq(client); + if (ret < 0) + goto err; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) { + ret = PTR_ERR(hid); + goto err; + } + + ihid->hid = hid; + + hid->driver_data = client; + hid->ll_driver = &i2c_hid_ll_driver; + hid->hid_get_raw_report = i2c_hid_get_raw_report; + hid->hid_output_raw_report = i2c_hid_output_raw_report; + hid->dev.parent = &client->dev; + hid->bus = BUS_I2C; + hid->version = le16_to_cpu(ihid->hdesc.bcdVersion); + hid->vendor = le16_to_cpu(ihid->hdesc.wVendorID); + hid->product = le16_to_cpu(ihid->hdesc.wProductID); + + snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", + client->name, hid->vendor, hid->product); + + ret = hid_add_device(hid); + if (ret) { + if (ret != -ENODEV) + hid_err(client, "can't add hid device: %d\n", ret); + goto err_mem_free; + } + + return 0; + +err_mem_free: + hid_destroy_device(hid); + +err: + if (ihid->irq) + free_irq(ihid->irq, ihid); + + kfree(ihid); + return ret; +} + +static int __devexit i2c_hid_remove(struct i2c_client *client) +{ + struct i2c_hid *ihid = i2c_get_clientdata(client); + struct hid_device *hid; + + if (WARN_ON(!ihid)) + return -1; + + hid = ihid->hid; + hid_destroy_device(hid); + + free_irq(client->irq, ihid); + + kfree(ihid); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int i2c_hid_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct i2c_hid *ihid = i2c_get_clientdata(client); + + if (device_may_wakeup(&client->dev)) + enable_irq_wake(ihid->irq); + + /* Save some power */ + i2c_hid_set_power(client, I2C_HID_PWR_SLEEP); + + return 0; +} + +static int i2c_hid_resume(struct device *dev) +{ + int ret; + struct i2c_client *client = to_i2c_client(dev); + + ret = i2c_hid_hwreset(client); + if (ret) + return ret; + + if (device_may_wakeup(&client->dev)) + disable_irq_wake(client->irq); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(i2c_hid_pm, i2c_hid_suspend, i2c_hid_resume); + +static const struct i2c_device_id i2c_hid_id_table[] = { + { "i2c_hid", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, i2c_hid_id_table); + + +static struct i2c_driver i2c_hid_driver = { + .driver = { + .name = "i2c_hid", + .owner = THIS_MODULE, + .pm = &i2c_hid_pm, + }, + + .probe = i2c_hid_probe, + .remove = __devexit_p(i2c_hid_remove), + + .id_table = i2c_hid_id_table, +}; + +module_i2c_driver(i2c_hid_driver); + +MODULE_DESCRIPTION("HID over I2C core driver"); +MODULE_AUTHOR("Benjamin Tissoires "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/i2c/i2c-hid.h b/include/linux/i2c/i2c-hid.h new file mode 100644 index 000000000000..60e411d764d4 --- /dev/null +++ b/include/linux/i2c/i2c-hid.h @@ -0,0 +1,35 @@ +/* + * HID over I2C protocol implementation + * + * Copyright (c) 2012 Benjamin Tissoires + * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#ifndef __LINUX_I2C_HID_H +#define __LINUX_I2C_HID_H + +#include + +/** + * struct i2chid_platform_data - used by hid over i2c implementation. + * @hid_descriptor_address: i2c register where the HID descriptor is stored. + * + * Note that it is the responsibility of the platform driver (or the acpi 5.0 + * driver) to setup the irq related to the gpio in the struct i2c_board_info. + * The platform driver should also setup the gpio according to the device: + * + * A typical example is the following: + * irq = gpio_to_irq(intr_gpio); + * hkdk4412_i2c_devs5[0].irq = irq; // store the irq in i2c_board_info + * gpio_request(intr_gpio, "elan-irq"); + * s3c_gpio_setpull(intr_gpio, S3C_GPIO_PULL_UP); + */ +struct i2c_hid_platform_data { + u16 hid_descriptor_address; +}; + +#endif /* __LINUX_I2C_HID_H */ -- cgit v1.2.3 From 4529eefad087f97b33c0f31984d924b1f15d7bae Mon Sep 17 00:00:00 2001 From: "Lamarque V. Souza" Date: Thu, 6 Dec 2012 12:39:55 -0200 Subject: HID: hidp: fallback to input session properly if hid is blacklisted This patch against kernel 3.7.0-rc8 fixes a kernel oops when turning on the bluetooth mouse with id 0458:0058 [1]. The mouse in question supports both input and hid sessions, however it is blacklisted in drivers/hid/hid-core.c so the input session is one that should be used. Long ago (around kernel 3.0.0) some changes in the bluetooth subsystem made the kernel do not fallback to input session when hid session is not supported or blacklisted. This patch restore that behaviour by making the kernel try the input session if hid_add_device returns ENODEV. The patch exports hid_ignore() from hid-core.c so that it can be used in the bluetooth subsystem. [1] https://bugzilla.kernel.org/show_bug.cgi?id=39882 Signed-off-by: Lamarque V. Souza Acked-by: Gustavo Padovan Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 11 ++++++++--- include/linux/hid.h | 1 + net/bluetooth/hidp/core.c | 9 ++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index a7550bb30836..2629d5240d63 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2145,8 +2145,13 @@ static const struct hid_device_id hid_mouse_ignore_list[] = { { } }; -static bool hid_ignore(struct hid_device *hdev) +bool hid_ignore(struct hid_device *hdev) { + if (hdev->quirks & HID_QUIRK_NO_IGNORE) + return false; + if (hdev->quirks & HID_QUIRK_IGNORE) + return true; + switch (hdev->vendor) { case USB_VENDOR_ID_CODEMERCS: /* ignore all Code Mercenaries IOWarrior devices */ @@ -2201,6 +2206,7 @@ static bool hid_ignore(struct hid_device *hdev) return !!hid_match_id(hdev, hid_ignore_list); } +EXPORT_SYMBOL_GPL(hid_ignore); int hid_add_device(struct hid_device *hdev) { @@ -2212,8 +2218,7 @@ int hid_add_device(struct hid_device *hdev) /* we need to kill them here, otherwise they will stay allocated to * wait for coming driver */ - if (!(hdev->quirks & HID_QUIRK_NO_IGNORE) - && (hid_ignore(hdev) || (hdev->quirks & HID_QUIRK_IGNORE))) + if (hid_ignore(hdev)) return -ENODEV; /* diff --git a/include/linux/hid.h b/include/linux/hid.h index 7e1f37db7582..abce7eb4f258 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -721,6 +721,7 @@ struct hid_ll_driver { extern int hid_debug; +extern bool hid_ignore(struct hid_device *); extern int hid_add_device(struct hid_device *); extern void hid_destroy_device(struct hid_device *); diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c index ccd985da6518..1f4a32bb2971 100644 --- a/net/bluetooth/hidp/core.c +++ b/net/bluetooth/hidp/core.c @@ -941,6 +941,13 @@ static int hidp_setup_hid(struct hidp_session *session, hid->hid_get_raw_report = hidp_get_raw_report; hid->hid_output_raw_report = hidp_output_raw_report; + /* True if device is blacklisted in drivers/hid/hid-core.c */ + if (hid_ignore(hid)) { + hid_destroy_device(session->hid); + session->hid = NULL; + return -ENODEV; + } + return 0; fault: @@ -1013,7 +1020,7 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, if (req->rd_size > 0) { err = hidp_setup_hid(session, req); - if (err) + if (err && err != -ENODEV) goto purge; } -- cgit v1.2.3 From 83499b52c61f50292f0aae36499de8a8fc3e37c3 Mon Sep 17 00:00:00 2001 From: Alexander Holler Date: Sun, 9 Dec 2012 12:44:30 +0100 Subject: HID: sensors: autodetect USB HID sensor hubs It should not be necessary to add IDs for HID sensor hubs to lists in hid-core.c and hid-sensor-hub.c. So instead of a whitelist, autodetect such USB HID sensor hubs, based on a collection of type physical inside a useage page of type sensor. If some sensor hubs stil must be usable as raw devices, a blacklist might be created. Signed-off-by: Alexander Holler Acked-by: "Pandruvada, Srinivas" Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 11 ++++++++++- drivers/hid/hid-sensor-hub.c | 32 +------------------------------- include/linux/hid-sensor-ids.h | 1 - include/linux/hid.h | 2 ++ 4 files changed, 13 insertions(+), 33 deletions(-) (limited to 'include') diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index f4109fd657ff..7df5399a9386 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -713,7 +713,12 @@ static int hid_scan_report(struct hid_device *hid) hid_scan_usage(hid, u); break; } - } + } else if (page == HID_UP_SENSOR && + item.type == HID_ITEM_TYPE_MAIN && + item.tag == HID_MAIN_ITEM_TAG_BEGIN_COLLECTION && + (item_udata(&item) & 0xff) == HID_COLLECTION_PHYSICAL && + hid->bus == BUS_USB) + hid->group = HID_GROUP_SENSOR_HUB; } return 0; @@ -1465,6 +1470,10 @@ EXPORT_SYMBOL_GPL(hid_disconnect); * there is a proper autodetection and autoloading in place (based on presence * of HID_DG_CONTACTID), so those devices don't need to be added to this list, * as we are doing the right thing in hid_scan_usage(). + * + * Autodetection for (USB) HID sensor hubs exists too. If a collection of type + * physical is found inside a usage page of type sensor, hid-sensor-hub will be + * used as a driver. See hid_scan_report(). */ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_WCP32PU) }, diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index d9d73e9163eb..ca88ddc2d78f 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -82,23 +82,6 @@ struct hid_sensor_hub_callbacks_list { void *priv; }; -static int sensor_hub_check_for_sensor_page(struct hid_device *hdev) -{ - int i; - int ret = -EINVAL; - - for (i = 0; i < hdev->maxcollection; i++) { - struct hid_collection *col = &hdev->collection[i]; - if (col->type == HID_COLLECTION_PHYSICAL && - (col->usage & HID_USAGE_PAGE) == HID_UP_SENSOR) { - ret = 0; - break; - } - } - - return ret; -} - static struct hid_report *sensor_hub_report(int id, struct hid_device *hdev, int dir) { @@ -524,10 +507,6 @@ static int sensor_hub_probe(struct hid_device *hdev, hid_err(hdev, "parse failed\n"); goto err_free; } - if (sensor_hub_check_for_sensor_page(hdev) < 0) { - hid_err(hdev, "sensor page not found\n"); - goto err_free; - } INIT_LIST_HEAD(&hdev->inputs); ret = hid_hw_start(hdev, 0); @@ -630,16 +609,7 @@ static void sensor_hub_remove(struct hid_device *hdev) } static const struct hid_device_id sensor_hub_devices[] = { - { HID_USB_DEVICE(USB_VENDOR_ID_INTEL_8086, - USB_DEVICE_ID_SENSOR_HUB_1020) }, - { HID_USB_DEVICE(USB_VENDOR_ID_INTEL_8087, - USB_DEVICE_ID_SENSOR_HUB_1020) }, - { HID_USB_DEVICE(USB_VENDOR_ID_INTEL_8086, - USB_DEVICE_ID_SENSOR_HUB_09FA) }, - { HID_USB_DEVICE(USB_VENDOR_ID_INTEL_8087, - USB_DEVICE_ID_SENSOR_HUB_09FA) }, - { HID_USB_DEVICE(USB_VENDOR_ID_STANTUM_STM, - USB_DEVICE_ID_SENSOR_HUB_7014) }, + { HID_DEVICE(BUS_USB, HID_GROUP_SENSOR_HUB, HID_ANY_ID, HID_ANY_ID) }, { } }; MODULE_DEVICE_TABLE(hid, sensor_hub_devices); diff --git a/include/linux/hid-sensor-ids.h b/include/linux/hid-sensor-ids.h index ca8d7e94eb3c..55f277372fed 100644 --- a/include/linux/hid-sensor-ids.h +++ b/include/linux/hid-sensor-ids.h @@ -19,7 +19,6 @@ #ifndef _HID_SENSORS_IDS_H #define _HID_SENSORS_IDS_H -#define HID_UP_SENSOR 0x00200000 #define HID_MAX_PHY_DEVICES 0xFF /* Accel 3D (200073) */ diff --git a/include/linux/hid.h b/include/linux/hid.h index c076041a069e..c5f6ec2b15c2 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -167,6 +167,7 @@ struct hid_item { #define HID_UP_MSVENDOR 0xff000000 #define HID_UP_CUSTOM 0x00ff0000 #define HID_UP_LOGIVENDOR 0xffbc0000 +#define HID_UP_SENSOR 0x00200000 #define HID_USAGE 0x0000ffff @@ -292,6 +293,7 @@ struct hid_item { */ #define HID_GROUP_GENERIC 0x0001 #define HID_GROUP_MULTITOUCH 0x0002 +#define HID_GROUP_SENSOR_HUB 0x0003 /* * This is the global environment of the parser. This information is -- cgit v1.2.3