From 71f6fa90a353605bf25c36417c9ae529ac1a9a8d Mon Sep 17 00:00:00 2001 From: "Song, Hongyan" Date: Fri, 3 Aug 2018 14:45:59 +0800 Subject: [PATCH 01/35] HID: increase maximum global item tag report size to 256 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The maximum globale report size has changed from 32->...->96->128 in the past years. With the development usage of HID, the report_size max value 128 cannot satisfy all requirements. There are applications need to expose intrinsic metadata to camera stabilizing applications such as 3DFE application. 3DFE intrinsic is designed to express environmental information about sensor that may dynamically change while the sensor is running (such data include noise spectral density, bias standard deviation) A sensor data field is SENSOR_VALUE_PAIR that consists of a PROPERTYKEY and PROPVARIANT pair. It need to report a unique PROPERTYKEY for each data field. Take “Noise Spectral Density” as an example, it report count will be defined as below: "Size of Property key GUID(16 Byte) + property key index(4 Byte) + size of Noise Spectral Density value(4 Byte)" In this case, the data report max is totally 192(24Byte), which is larger than 128, while max size 128 blocked it as illegal length. So increase the report size to satisfy it and more demands in the future. Signed-off-by: Song Hongyan Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 3da354af7a0aa..ef009db512ee0 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -406,7 +406,7 @@ static int hid_parser_global(struct hid_parser *parser, struct hid_item *item) case HID_GLOBAL_ITEM_TAG_REPORT_SIZE: parser->global.report_size = item_udata(item); - if (parser->global.report_size > 128) { + if (parser->global.report_size > 256) { hid_err(parser->device, "invalid report_size %d\n", parser->global.report_size); return -1; From d7065620b89fcc183caa6fa6ea36de5e01211137 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 4 Sep 2018 15:31:13 +0200 Subject: [PATCH 02/35] HID: input: do not append a suffix if the name already has it Or it creates some weird input names like: "MI Dongle MI Wireless Mouse Mouse" Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 4e94ea3e280a3..83755ba33ef44 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -1516,6 +1516,7 @@ static struct hid_input *hidinput_allocate(struct hid_device *hid, struct hid_input *hidinput = kzalloc(sizeof(*hidinput), GFP_KERNEL); struct input_dev *input_dev = input_allocate_device(); const char *suffix = NULL; + size_t suffix_len, name_len; if (!hidinput || !input_dev) goto fail; @@ -1559,10 +1560,15 @@ static struct hid_input *hidinput_allocate(struct hid_device *hid, } if (suffix) { - hidinput->name = kasprintf(GFP_KERNEL, "%s %s", - hid->name, suffix); - if (!hidinput->name) - goto fail; + name_len = strlen(hid->name); + suffix_len = strlen(suffix); + if ((name_len < suffix_len) || + strcmp(hid->name + name_len - suffix_len, suffix)) { + hidinput->name = kasprintf(GFP_KERNEL, "%s %s", + hid->name, suffix); + if (!hidinput->name) + goto fail; + } } input_set_drvdata(input_dev, hid); From 7ffa13be4945b2f60dfe6c71acbc1fdcfc4629a0 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 4 Sep 2018 15:31:15 +0200 Subject: [PATCH 03/35] HID: multitouch: simplify the application retrieval Now that the application is simply stored in struct hid_input, we can overwrite it in mt_input_mapping() for the faulty egalax and have a simpler suffix processing in mt_input_configured() Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-multitouch.c | 72 ++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index da954f3f4da7f..f7c6de2b6730b 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -1319,6 +1319,13 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, application); + /* + * some egalax touchscreens have "application == DG_TOUCHSCREEN" + * for the stylus. Overwrite the hid_input application + */ + if (field->physical == HID_DG_STYLUS) + hi->application = HID_DG_STYLUS; + /* let hid-core decide for the others */ return 0; } @@ -1507,14 +1514,12 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) struct mt_device *td = hid_get_drvdata(hdev); char *name; const char *suffix = NULL; - unsigned int application = 0; struct mt_report_data *rdata; struct mt_application *mt_application = NULL; struct hid_report *report; int ret; list_for_each_entry(report, &hi->reports, hidinput_list) { - application = report->application; rdata = mt_find_report_data(td, report); if (!rdata) { hid_err(hdev, "failed to allocate data for report\n"); @@ -1529,46 +1534,33 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) if (ret) return ret; } - - /* - * some egalax touchscreens have "application == DG_TOUCHSCREEN" - * for the stylus. Check this first, and then rely on - * the application field. - */ - if (report->field[0]->physical == HID_DG_STYLUS) { - suffix = "Pen"; - /* force BTN_STYLUS to allow tablet matching in udev */ - __set_bit(BTN_STYLUS, hi->input->keybit); - } } - if (!suffix) { - switch (application) { - case HID_GD_KEYBOARD: - case HID_GD_KEYPAD: - case HID_GD_MOUSE: - case HID_DG_TOUCHPAD: - case HID_GD_SYSTEM_CONTROL: - case HID_CP_CONSUMER_CONTROL: - case HID_GD_WIRELESS_RADIO_CTLS: - case HID_GD_SYSTEM_MULTIAXIS: - /* already handled by hid core */ - break; - case HID_DG_TOUCHSCREEN: - /* we do not set suffix = "Touchscreen" */ - hi->input->name = hdev->name; - break; - case HID_DG_STYLUS: - /* force BTN_STYLUS to allow tablet matching in udev */ - __set_bit(BTN_STYLUS, hi->input->keybit); - break; - case HID_VD_ASUS_CUSTOM_MEDIA_KEYS: - suffix = "Custom Media Keys"; - break; - default: - suffix = "UNKNOWN"; - break; - } + switch (hi->application) { + case HID_GD_KEYBOARD: + case HID_GD_KEYPAD: + case HID_GD_MOUSE: + case HID_DG_TOUCHPAD: + case HID_GD_SYSTEM_CONTROL: + case HID_CP_CONSUMER_CONTROL: + case HID_GD_WIRELESS_RADIO_CTLS: + case HID_GD_SYSTEM_MULTIAXIS: + /* already handled by hid core */ + break; + case HID_DG_TOUCHSCREEN: + /* we do not set suffix = "Touchscreen" */ + hi->input->name = hdev->name; + break; + case HID_DG_STYLUS: + /* force BTN_STYLUS to allow tablet matching in udev */ + __set_bit(BTN_STYLUS, hi->input->keybit); + break; + case HID_VD_ASUS_CUSTOM_MEDIA_KEYS: + suffix = "Custom Media Keys"; + break; + default: + suffix = "UNKNOWN"; + break; } if (suffix) { From aaf9978c3c0291ef3beaa97610bc9c3084656a85 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Thu, 30 Aug 2018 14:56:18 -0700 Subject: [PATCH 04/35] Input: Add the `REL_WHEEL_HI_RES` event code This event code represents scroll reports from high-resolution wheels, and will be used by future patches in this series. See the linux-input "Reporting high-resolution scroll events" thread [0] for more details. [0]: https://www.spinics.net/lists/linux-input/msg57380.html Signed-off-by: Harry Cutts Acked-by: Dmitry Torokhov Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- Documentation/input/event-codes.rst | 11 ++++++++++- include/uapi/linux/input-event-codes.h | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Documentation/input/event-codes.rst b/Documentation/input/event-codes.rst index a8c0873beb952..cef220c176a40 100644 --- a/Documentation/input/event-codes.rst +++ b/Documentation/input/event-codes.rst @@ -190,7 +190,16 @@ A few EV_REL codes have special meanings: * REL_WHEEL, REL_HWHEEL: - These codes are used for vertical and horizontal scroll wheels, - respectively. + respectively. The value is the number of "notches" moved on the wheel, the + physical size of which varies by device. For high-resolution wheels (which + report multiple events for each notch of movement, or do not have notches) + this may be an approximation based on the high-resolution scroll events. + +* REL_WHEEL_HI_RES: + + - If a vertical scroll wheel supports high-resolution scrolling, this code + will be emitted in addition to REL_WHEEL. The value is the (approximate) + distance travelled by the user's finger, in microns. EV_ABS ------ diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h index 53fbae27b280c..dad8d3890a3a5 100644 --- a/include/uapi/linux/input-event-codes.h +++ b/include/uapi/linux/input-event-codes.h @@ -708,6 +708,7 @@ #define REL_DIAL 0x07 #define REL_WHEEL 0x08 #define REL_MISC 0x09 +#define REL_WHEEL_HI_RES 0x0a #define REL_MAX 0x0f #define REL_CNT (REL_MAX+1) From 1ff2e1a44e02d4bdbb9be67c7d9acc240a67141f Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Thu, 30 Aug 2018 14:56:19 -0700 Subject: [PATCH 05/35] HID: input: Create a utility class for counting scroll events To avoid code duplication, this class counts high-resolution scroll movements and emits the legacy low-resolution events when appropriate. Drivers should be able to create one instance for each scroll wheel that they need to handle. Signed-off-by: Harry Cutts Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 45 +++++++++++++++++++++++++++++++++++++++++ include/linux/hid.h | 28 +++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 4e94ea3e280a3..6e84e7b9afcb1 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -1826,3 +1826,48 @@ void hidinput_disconnect(struct hid_device *hid) } EXPORT_SYMBOL_GPL(hidinput_disconnect); +/** + * hid_scroll_counter_handle_scroll() - Send high- and low-resolution scroll + * events given a high-resolution wheel + * movement. + * @counter: a hid_scroll_counter struct describing the wheel. + * @hi_res_value: the movement of the wheel, in the mouse's high-resolution + * units. + * + * Given a high-resolution movement, this function converts the movement into + * microns and emits high-resolution scroll events for the input device. It also + * uses the multiplier from &struct hid_scroll_counter to emit low-resolution + * scroll events when appropriate for backwards-compatibility with userspace + * input libraries. + */ +void hid_scroll_counter_handle_scroll(struct hid_scroll_counter *counter, + int hi_res_value) +{ + int low_res_scroll_amount; + /* Some wheels will rest 7/8ths of a notch from the previous notch + * after slow movement, so we want the threshold for low-res events to + * be in the middle of the notches (e.g. after 4/8ths) as opposed to on + * the notches themselves (8/8ths). + */ + int threshold = counter->resolution_multiplier / 2; + + input_report_rel(counter->dev, REL_WHEEL_HI_RES, + hi_res_value * counter->microns_per_hi_res_unit); + + counter->remainder += hi_res_value; + if (abs(counter->remainder) >= threshold) { + /* Add (or subtract) 1 because we want to trigger when the wheel + * is half-way to the next notch (i.e. scroll 1 notch after a + * 1/2 notch movement, 2 notches after a 1 1/2 notch movement, + * etc.). + */ + low_res_scroll_amount = + counter->remainder / counter->resolution_multiplier + + (hi_res_value > 0 ? 1 : -1); + input_report_rel(counter->dev, REL_WHEEL, + low_res_scroll_amount); + counter->remainder -= + low_res_scroll_amount * counter->resolution_multiplier; + } +} +EXPORT_SYMBOL_GPL(hid_scroll_counter_handle_scroll); diff --git a/include/linux/hid.h b/include/linux/hid.h index 834e6461a6905..037e37b0b0e61 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -1138,6 +1138,34 @@ static inline u32 hid_report_len(struct hid_report *report) int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size, int interrupt); + +/** + * struct hid_scroll_counter - Utility class for processing high-resolution + * scroll events. + * @dev: the input device for which events should be reported. + * @microns_per_hi_res_unit: the amount moved by the user's finger for each + * high-resolution unit reported by the mouse, in + * microns. + * @resolution_multiplier: the wheel's resolution in high-resolution mode as a + * multiple of its lower resolution. For example, if + * moving the wheel by one "notch" would result in a + * value of 1 in low-resolution mode but 8 in + * high-resolution, the multiplier is 8. + * @remainder: counts the number of high-resolution units moved since the last + * low-resolution event (REL_WHEEL or REL_HWHEEL) was sent. Should + * only be used by class methods. + */ +struct hid_scroll_counter { + struct input_dev *dev; + int microns_per_hi_res_unit; + int resolution_multiplier; + + int remainder; +}; + +void hid_scroll_counter_handle_scroll(struct hid_scroll_counter *counter, + int hi_res_value); + /* HID quirks API */ unsigned long hid_lookup_quirk(const struct hid_device *hdev); int hid_quirks_init(char **quirks_param, __u16 bus, int count); From 051dc9b0579602bd63e9df74d0879b5293e71581 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Thu, 30 Aug 2018 14:56:20 -0700 Subject: [PATCH 06/35] HID: logitech: Add function to enable HID++ 1.0 "scrolling acceleration" "Scrolling acceleration" is a bit of a misnomer: it doesn't deal with acceleration at all. However, that's the name used in Logitech's spec, so I used it here. Signed-off-by: Harry Cutts Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 47 +++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 19cc980eebce6..7f8218f6ff566 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -400,32 +400,53 @@ static void hidpp_prefix_name(char **name, int name_length) #define HIDPP_SET_LONG_REGISTER 0x82 #define HIDPP_GET_LONG_REGISTER 0x83 -#define HIDPP_REG_GENERAL 0x00 - -static int hidpp10_enable_battery_reporting(struct hidpp_device *hidpp_dev) +/** + * hidpp10_set_register_bit() - Sets a single bit in a HID++ 1.0 register. + * @hidpp_dev: the device to set the register on. + * @register_address: the address of the register to modify. + * @byte: the byte of the register to modify. Should be less than 3. + * Return: 0 if successful, otherwise a negative error code. + */ +static int hidpp10_set_register_bit(struct hidpp_device *hidpp_dev, + u8 register_address, u8 byte, u8 bit) { struct hidpp_report response; int ret; u8 params[3] = { 0 }; ret = hidpp_send_rap_command_sync(hidpp_dev, - REPORT_ID_HIDPP_SHORT, - HIDPP_GET_REGISTER, - HIDPP_REG_GENERAL, - NULL, 0, &response); + REPORT_ID_HIDPP_SHORT, + HIDPP_GET_REGISTER, + register_address, + NULL, 0, &response); if (ret) return ret; memcpy(params, response.rap.params, 3); - /* Set the battery bit */ - params[0] |= BIT(4); + params[byte] |= BIT(bit); return hidpp_send_rap_command_sync(hidpp_dev, - REPORT_ID_HIDPP_SHORT, - HIDPP_SET_REGISTER, - HIDPP_REG_GENERAL, - params, 3, &response); + REPORT_ID_HIDPP_SHORT, + HIDPP_SET_REGISTER, + register_address, + params, 3, &response); +} + + +#define HIDPP_REG_GENERAL 0x00 + +static int hidpp10_enable_battery_reporting(struct hidpp_device *hidpp_dev) +{ + return hidpp10_set_register_bit(hidpp_dev, HIDPP_REG_GENERAL, 0, 4); +} + +#define HIDPP_REG_FEATURES 0x01 + +/* On HID++ 1.0 devices, high-res scroll was called "scrolling acceleration". */ +static int hidpp10_enable_scrolling_acceleration(struct hidpp_device *hidpp_dev) +{ + return hidpp10_set_register_bit(hidpp_dev, HIDPP_REG_FEATURES, 0, 6); } #define HIDPP_REG_BATTERY_STATUS 0x07 From d56ca9855bf924f3bc9807a3e42f38539df3f41f Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Thu, 30 Aug 2018 14:56:21 -0700 Subject: [PATCH 07/35] HID: logitech: Enable high-resolution scrolling on Logitech mice There are three features used by various Logitech mice for high-resolution scrolling: the scrolling acceleration bit in HID++ 1.0, and the x2120 and x2121 features in HID++ 2.0 and above. This patch supports all three, and uses the multiplier reported by the mouse for the HID++ 2.0+ features. The full list of product IDs of mice which support high-resolution scrolling was provided by Logitech, but the patch was tested using the following mice (using the Unifying receiver): * HID++ 1.0: Anywhere MX, Performance MX * x2120: M560 * x2121: MX Anywhere 2, MX Master 2S Signed-off-by: Harry Cutts Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 249 ++++++++++++++++++++++++++++++- 1 file changed, 245 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 7f8218f6ff566..fd6a8c325fa0b 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -64,6 +64,14 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_QUIRK_NO_HIDINPUT BIT(23) #define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24) #define HIDPP_QUIRK_UNIFYING BIT(25) +#define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(26) +#define HIDPP_QUIRK_HI_RES_SCROLL_X2120 BIT(27) +#define HIDPP_QUIRK_HI_RES_SCROLL_X2121 BIT(28) + +/* Convenience constant to check for any high-res support. */ +#define HIDPP_QUIRK_HI_RES_SCROLL (HIDPP_QUIRK_HI_RES_SCROLL_1P0 | \ + HIDPP_QUIRK_HI_RES_SCROLL_X2120 | \ + HIDPP_QUIRK_HI_RES_SCROLL_X2121) #define HIDPP_QUIRK_DELAYED_INIT HIDPP_QUIRK_NO_HIDINPUT @@ -149,6 +157,7 @@ struct hidpp_device { unsigned long capabilities; struct hidpp_battery battery; + struct hid_scroll_counter vertical_wheel_counter; }; /* HID++ 1.0 error codes */ @@ -1157,6 +1166,101 @@ static int hidpp_battery_get_property(struct power_supply *psy, return ret; } +/* -------------------------------------------------------------------------- */ +/* 0x2120: Hi-resolution scrolling */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_HI_RESOLUTION_SCROLLING 0x2120 + +#define CMD_HI_RESOLUTION_SCROLLING_SET_HIGHRES_SCROLLING_MODE 0x10 + +static int hidpp_hrs_set_highres_scrolling_mode(struct hidpp_device *hidpp, + bool enabled, u8 *multiplier) +{ + u8 feature_index; + u8 feature_type; + int ret; + u8 params[1]; + struct hidpp_report response; + + ret = hidpp_root_get_feature(hidpp, + HIDPP_PAGE_HI_RESOLUTION_SCROLLING, + &feature_index, + &feature_type); + if (ret) + return ret; + + params[0] = enabled ? BIT(0) : 0; + ret = hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_HI_RESOLUTION_SCROLLING_SET_HIGHRES_SCROLLING_MODE, + params, sizeof(params), &response); + if (ret) + return ret; + *multiplier = response.fap.params[1]; + return 0; +} + +/* -------------------------------------------------------------------------- */ +/* 0x2121: HiRes Wheel */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_HIRES_WHEEL 0x2121 + +#define CMD_HIRES_WHEEL_GET_WHEEL_CAPABILITY 0x00 +#define CMD_HIRES_WHEEL_SET_WHEEL_MODE 0x20 + +static int hidpp_hrw_get_wheel_capability(struct hidpp_device *hidpp, + u8 *multiplier) +{ + u8 feature_index; + u8 feature_type; + int ret; + struct hidpp_report response; + + ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL, + &feature_index, &feature_type); + if (ret) + goto return_default; + + ret = hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_HIRES_WHEEL_GET_WHEEL_CAPABILITY, + NULL, 0, &response); + if (ret) + goto return_default; + + *multiplier = response.fap.params[0]; + return 0; +return_default: + *multiplier = 8; + hid_warn(hidpp->hid_dev, + "Couldn't get wheel multiplier (error %d), assuming %d.\n", + ret, *multiplier); + return ret; +} + +static int hidpp_hrw_set_wheel_mode(struct hidpp_device *hidpp, bool invert, + bool high_resolution, bool use_hidpp) +{ + u8 feature_index; + u8 feature_type; + int ret; + u8 params[1]; + struct hidpp_report response; + + ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL, + &feature_index, &feature_type); + if (ret) + return ret; + + params[0] = (invert ? BIT(2) : 0) | + (high_resolution ? BIT(1) : 0) | + (use_hidpp ? BIT(0) : 0); + + return hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_HIRES_WHEEL_SET_WHEEL_MODE, + params, sizeof(params), &response); +} + /* -------------------------------------------------------------------------- */ /* 0x4301: Solar Keyboard */ /* -------------------------------------------------------------------------- */ @@ -2420,7 +2524,8 @@ static int m560_raw_event(struct hid_device *hdev, u8 *data, int size) input_report_rel(mydata->input, REL_Y, v); v = hid_snto32(data[6], 8); - input_report_rel(mydata->input, REL_WHEEL, v); + hid_scroll_counter_handle_scroll( + &hidpp->vertical_wheel_counter, v); input_sync(mydata->input); } @@ -2548,6 +2653,73 @@ static int g920_get_config(struct hidpp_device *hidpp) return 0; } +/* -------------------------------------------------------------------------- */ +/* High-resolution scroll wheels */ +/* -------------------------------------------------------------------------- */ + +/** + * struct hi_res_scroll_info - Stores info on a device's high-res scroll wheel. + * @product_id: the HID product ID of the device being described. + * @microns_per_hi_res_unit: the distance moved by the user's finger for each + * high-resolution unit reported by the device, in + * 256ths of a millimetre. + */ +struct hi_res_scroll_info { + __u32 product_id; + int microns_per_hi_res_unit; +}; + +static struct hi_res_scroll_info hi_res_scroll_devices[] = { + { /* Anywhere MX */ + .product_id = 0x1017, .microns_per_hi_res_unit = 445 }, + { /* Performance MX */ + .product_id = 0x101a, .microns_per_hi_res_unit = 406 }, + { /* M560 */ + .product_id = 0x402d, .microns_per_hi_res_unit = 435 }, + { /* MX Master 2S */ + .product_id = 0x4069, .microns_per_hi_res_unit = 406 }, +}; + +static int hi_res_scroll_look_up_microns(__u32 product_id) +{ + int i; + int num_devices = sizeof(hi_res_scroll_devices) + / sizeof(hi_res_scroll_devices[0]); + for (i = 0; i < num_devices; i++) { + if (hi_res_scroll_devices[i].product_id == product_id) + return hi_res_scroll_devices[i].microns_per_hi_res_unit; + } + /* We don't have a value for this device, so use a sensible default. */ + return 406; +} + +static int hi_res_scroll_enable(struct hidpp_device *hidpp) +{ + int ret; + u8 multiplier; + + if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_X2121) { + ret = hidpp_hrw_set_wheel_mode(hidpp, false, true, false); + hidpp_hrw_get_wheel_capability(hidpp, &multiplier); + } else if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_X2120) { + ret = hidpp_hrs_set_highres_scrolling_mode(hidpp, true, + &multiplier); + } else /* if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_1P0) */ { + ret = hidpp10_enable_scrolling_acceleration(hidpp); + multiplier = 8; + } + if (ret) + return ret; + + hidpp->vertical_wheel_counter.resolution_multiplier = multiplier; + hidpp->vertical_wheel_counter.microns_per_hi_res_unit = + hi_res_scroll_look_up_microns(hidpp->hid_dev->product); + hid_info(hidpp->hid_dev, "multiplier = %d, microns = %d\n", + multiplier, + hidpp->vertical_wheel_counter.microns_per_hi_res_unit); + return 0; +} + /* -------------------------------------------------------------------------- */ /* Generic HID++ devices */ /* -------------------------------------------------------------------------- */ @@ -2593,6 +2765,11 @@ static void hidpp_populate_input(struct hidpp_device *hidpp, wtp_populate_input(hidpp, input, origin_is_hid_core); else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) m560_populate_input(hidpp, input, origin_is_hid_core); + + if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL) { + input_set_capability(input, EV_REL, REL_WHEEL_HI_RES); + hidpp->vertical_wheel_counter.dev = input; + } } static int hidpp_input_configured(struct hid_device *hdev, @@ -2711,6 +2888,27 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report, return 0; } +static int hidpp_event(struct hid_device *hdev, struct hid_field *field, + struct hid_usage *usage, __s32 value) +{ + /* This function will only be called for scroll events, due to the + * restriction imposed in hidpp_usages. + */ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + struct hid_scroll_counter *counter = &hidpp->vertical_wheel_counter; + /* A scroll event may occur before the multiplier has been retrieved or + * the input device set, or high-res scroll enabling may fail. In such + * cases we must return early (falling back to default behaviour) to + * avoid a crash in hid_scroll_counter_handle_scroll. + */ + if (!(hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL) || value == 0 + || counter->dev == NULL || counter->resolution_multiplier == 0) + return 0; + + hid_scroll_counter_handle_scroll(counter, value); + return 1; +} + static int hidpp_initialize_battery(struct hidpp_device *hidpp) { static atomic_t battery_no = ATOMIC_INIT(0); @@ -2922,6 +3120,9 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) if (hidpp->battery.ps) power_supply_changed(hidpp->battery.ps); + if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL) + hi_res_scroll_enable(hidpp); + if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) || hidpp->delayed_input) /* if the input nodes are already created, we can stop now */ return; @@ -3107,6 +3308,10 @@ static void hidpp_remove(struct hid_device *hdev) mutex_destroy(&hidpp->send_mutex); } +#define LDJ_DEVICE(product) \ + HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, \ + USB_VENDOR_ID_LOGITECH, (product)) + static const struct hid_device_id hidpp_devices[] = { { /* wireless touchpad */ HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, @@ -3121,10 +3326,39 @@ static const struct hid_device_id hidpp_devices[] = { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_T651), .driver_data = HIDPP_QUIRK_CLASS_WTP }, + { /* Mouse Logitech Anywhere MX */ + LDJ_DEVICE(0x1017), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 }, + { /* Mouse Logitech Cube */ + LDJ_DEVICE(0x4010), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2120 }, + { /* Mouse Logitech M335 */ + LDJ_DEVICE(0x4050), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, + { /* Mouse Logitech M515 */ + LDJ_DEVICE(0x4007), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2120 }, { /* Mouse logitech M560 */ - HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, - USB_VENDOR_ID_LOGITECH, 0x402d), - .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 }, + LDJ_DEVICE(0x402d), + .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 + | HIDPP_QUIRK_HI_RES_SCROLL_X2120 }, + { /* Mouse Logitech M705 (firmware RQM17) */ + LDJ_DEVICE(0x101b), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 }, + { /* Mouse Logitech M705 (firmware RQM67) */ + LDJ_DEVICE(0x406d), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, + { /* Mouse Logitech M720 */ + LDJ_DEVICE(0x405e), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, + { /* Mouse Logitech MX Anywhere 2 */ + LDJ_DEVICE(0x404a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, + { LDJ_DEVICE(0xb013), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, + { LDJ_DEVICE(0xb018), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, + { LDJ_DEVICE(0xb01f), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, + { /* Mouse Logitech MX Anywhere 2S */ + LDJ_DEVICE(0x406a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, + { /* Mouse Logitech MX Master */ + LDJ_DEVICE(0x4041), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, + { LDJ_DEVICE(0x4060), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, + { LDJ_DEVICE(0x4071), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, + { /* Mouse Logitech MX Master 2S */ + LDJ_DEVICE(0x4069), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, + { /* Mouse Logitech Performance MX */ + LDJ_DEVICE(0x101a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 }, { /* Keyboard logitech K400 */ HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, USB_VENDOR_ID_LOGITECH, 0x4024), @@ -3144,12 +3378,19 @@ static const struct hid_device_id hidpp_devices[] = { MODULE_DEVICE_TABLE(hid, hidpp_devices); +static const struct hid_usage_id hidpp_usages[] = { + { HID_GD_WHEEL, EV_REL, REL_WHEEL }, + { HID_ANY_ID - 1, HID_ANY_ID - 1, HID_ANY_ID - 1} +}; + static struct hid_driver hidpp_driver = { .name = "logitech-hidpp-device", .id_table = hidpp_devices, .probe = hidpp_probe, .remove = hidpp_remove, .raw_event = hidpp_raw_event, + .usage_table = hidpp_usages, + .event = hidpp_event, .input_configured = hidpp_input_configured, .input_mapping = hidpp_input_mapping, .input_mapped = hidpp_input_mapped, From 3fe1d6bbcd16f384d2c7dab2caf8e4b2df9ea7e6 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Thu, 30 Aug 2018 14:56:22 -0700 Subject: [PATCH 08/35] HID: logitech: Use LDJ_DEVICE macro for existing Logitech mice Signed-off-by: Harry Cutts Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index fd6a8c325fa0b..5f0c080059c68 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -3314,13 +3314,11 @@ static void hidpp_remove(struct hid_device *hdev) static const struct hid_device_id hidpp_devices[] = { { /* wireless touchpad */ - HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, - USB_VENDOR_ID_LOGITECH, 0x4011), + LDJ_DEVICE(0x4011), .driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS }, { /* wireless touchpad T650 */ - HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, - USB_VENDOR_ID_LOGITECH, 0x4101), + LDJ_DEVICE(0x4101), .driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT }, { /* wireless touchpad T651 */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, @@ -3360,16 +3358,13 @@ static const struct hid_device_id hidpp_devices[] = { { /* Mouse Logitech Performance MX */ LDJ_DEVICE(0x101a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 }, { /* Keyboard logitech K400 */ - HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, - USB_VENDOR_ID_LOGITECH, 0x4024), + LDJ_DEVICE(0x4024), .driver_data = HIDPP_QUIRK_CLASS_K400 }, { /* Solar Keyboard Logitech K750 */ - HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, - USB_VENDOR_ID_LOGITECH, 0x4002), + LDJ_DEVICE(0x4002), .driver_data = HIDPP_QUIRK_CLASS_K750 }, - { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, - USB_VENDOR_ID_LOGITECH, HID_ANY_ID)}, + { LDJ_DEVICE(HID_ANY_ID) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL), .driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS}, From f2d3b625dae10cdf14bcfcacb965b02f5c0acc01 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Wed, 15 Aug 2018 19:44:09 -0700 Subject: [PATCH 09/35] HID: microsoft: Convert private data to be a proper struct In order to be able to have more than just an unsigned long worth of private data, convert the code to allocate and use a dedicated struct. Cc: Dmitry Torokhov Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: linux-input@vger.kernel.org Cc: linux-kernel@vger.kernel.org Cc: Pierre-Loup A. Griffais Signed-off-by: Juha Kuikka Signed-off-by: Andrey Smirnov Signed-off-by: Jiri Kosina --- drivers/hid/hid-microsoft.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-microsoft.c b/drivers/hid/hid-microsoft.c index 72d983626afd0..1cbeeeabd2dd4 100644 --- a/drivers/hid/hid-microsoft.c +++ b/drivers/hid/hid-microsoft.c @@ -30,10 +30,15 @@ #define MS_DUPLICATE_USAGES BIT(5) #define MS_SURFACE_DIAL BIT(6) +struct ms_data { + unsigned long quirks; +}; + static __u8 *ms_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { - unsigned long quirks = (unsigned long)hid_get_drvdata(hdev); + struct ms_data *ms = hid_get_drvdata(hdev); + unsigned long quirks = ms->quirks; /* * Microsoft Wireless Desktop Receiver (Model 1028) has @@ -159,7 +164,8 @@ static int ms_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { - unsigned long quirks = (unsigned long)hid_get_drvdata(hdev); + struct ms_data *ms = hid_get_drvdata(hdev); + unsigned long quirks = ms->quirks; if (quirks & MS_ERGONOMY) { int ret = ms_ergonomy_kb_quirk(hi, usage, bit, max); @@ -185,7 +191,8 @@ static int ms_input_mapped(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { - unsigned long quirks = (unsigned long)hid_get_drvdata(hdev); + struct ms_data *ms = hid_get_drvdata(hdev); + unsigned long quirks = ms->quirks; if (quirks & MS_DUPLICATE_USAGES) clear_bit(usage->code, *bit); @@ -196,7 +203,8 @@ static int ms_input_mapped(struct hid_device *hdev, struct hid_input *hi, static int ms_event(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage, __s32 value) { - unsigned long quirks = (unsigned long)hid_get_drvdata(hdev); + struct ms_data *ms = hid_get_drvdata(hdev); + unsigned long quirks = ms->quirks; struct input_dev *input; if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput || @@ -254,9 +262,16 @@ static int ms_event(struct hid_device *hdev, struct hid_field *field, static int ms_probe(struct hid_device *hdev, const struct hid_device_id *id) { unsigned long quirks = id->driver_data; + struct ms_data *ms; int ret; - hid_set_drvdata(hdev, (void *)quirks); + ms = devm_kzalloc(&hdev->dev, sizeof(*ms), GFP_KERNEL); + if (ms == NULL) + return -ENOMEM; + + ms->quirks = quirks; + + hid_set_drvdata(hdev, ms); if (quirks & MS_NOGET) hdev->quirks |= HID_QUIRK_NOGET; From 73c5b254c36529c84c9d19e07905f7103bb32e79 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Wed, 15 Aug 2018 19:44:10 -0700 Subject: [PATCH 10/35] HID: microsoft: Add rumble support for Xbox One S controller Add HID quirk driver for Xbox One S controller over bluetooth. This driver only adds support for rumble. Standard controller functionality is exposed by default HID driver. [jkosina@suse.cz: straightforward rebase on more recent driver code] Cc: Dmitry Torokhov Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: linux-input@vger.kernel.org Cc: linux-kernel@vger.kernel.org Cc: Pierre-Loup A. Griffais Signed-off-by: Juha Kuikka Signed-off-by: Andrey Smirnov Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-microsoft.c | 116 ++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 79bdf0c7e3516..a523d54c4f758 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -800,6 +800,7 @@ #define USB_DEVICE_ID_MS_TOUCH_COVER_2 0x07a7 #define USB_DEVICE_ID_MS_TYPE_COVER_2 0x07a9 #define USB_DEVICE_ID_MS_POWER_COVER 0x07da +#define USB_DEVICE_ID_MS_XBOX_ONE_S_CONTROLLER 0x02fd #define USB_VENDOR_ID_MOJO 0x8282 #define USB_DEVICE_ID_RETRO_ADAPTER 0x3201 diff --git a/drivers/hid/hid-microsoft.c b/drivers/hid/hid-microsoft.c index 1cbeeeabd2dd4..330cb073cb667 100644 --- a/drivers/hid/hid-microsoft.c +++ b/drivers/hid/hid-microsoft.c @@ -29,11 +29,36 @@ #define MS_NOGET BIT(4) #define MS_DUPLICATE_USAGES BIT(5) #define MS_SURFACE_DIAL BIT(6) +#define MS_QUIRK_FF BIT(7) struct ms_data { unsigned long quirks; + struct hid_device *hdev; + struct work_struct ff_worker; + __u8 strong; + __u8 weak; + void *output_report_dmabuf; }; +#define XB1S_FF_REPORT 3 +#define ENABLE_WEAK BIT(0) +#define ENABLE_STRONG BIT(1) + +enum { + MAGNITUDE_STRONG = 2, + MAGNITUDE_WEAK, + MAGNITUDE_NUM +}; + +struct xb1s_ff_report { + __u8 report_id; + __u8 enable; + __u8 magnitude[MAGNITUDE_NUM]; + __u8 duration_10ms; + __u8 start_delay_10ms; + __u8 loop_count; +} __packed; + static __u8 *ms_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { @@ -259,6 +284,84 @@ static int ms_event(struct hid_device *hdev, struct hid_field *field, return 0; } +static void ms_ff_worker(struct work_struct *work) +{ + struct ms_data *ms = container_of(work, struct ms_data, ff_worker); + struct hid_device *hdev = ms->hdev; + struct xb1s_ff_report *r = ms->output_report_dmabuf; + int ret; + + memset(r, 0, sizeof(*r)); + + r->report_id = XB1S_FF_REPORT; + r->enable = ENABLE_WEAK | ENABLE_STRONG; + /* + * Specifying maximum duration and maximum loop count should + * cover maximum duration of a single effect, which is 65536 + * ms + */ + r->duration_10ms = U8_MAX; + r->loop_count = U8_MAX; + r->magnitude[MAGNITUDE_STRONG] = ms->strong; /* left actuator */ + r->magnitude[MAGNITUDE_WEAK] = ms->weak; /* right actuator */ + + ret = hid_hw_output_report(hdev, (__u8 *)r, sizeof(*r)); + if (ret) + hid_warn(hdev, "failed to send FF report\n"); +} + +static int ms_play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct ms_data *ms = hid_get_drvdata(hid); + + if (effect->type != FF_RUMBLE) + return 0; + + /* + * Magnitude is 0..100 so scale the 16-bit input here + */ + ms->strong = ((u32) effect->u.rumble.strong_magnitude * 100) / U16_MAX; + ms->weak = ((u32) effect->u.rumble.weak_magnitude * 100) / U16_MAX; + + schedule_work(&ms->ff_worker); + return 0; +} + +static int ms_init_ff(struct hid_device *hdev) +{ + struct hid_input *hidinput = list_entry(hdev->inputs.next, + struct hid_input, list); + struct input_dev *input_dev = hidinput->input; + struct ms_data *ms = hid_get_drvdata(hdev); + + if (!(ms->quirks & MS_QUIRK_FF)) + return 0; + + ms->hdev = hdev; + INIT_WORK(&ms->ff_worker, ms_ff_worker); + + ms->output_report_dmabuf = devm_kzalloc(&hdev->dev, + sizeof(struct xb1s_ff_report), + GFP_KERNEL); + if (ms->output_report_dmabuf == NULL) + return -ENOMEM; + + input_set_capability(input_dev, EV_FF, FF_RUMBLE); + return input_ff_create_memless(input_dev, NULL, ms_play_effect); +} + +static void ms_remove_ff(struct hid_device *hdev) +{ + struct ms_data *ms = hid_get_drvdata(hdev); + + if (!(ms->quirks & MS_QUIRK_FF)) + return; + + cancel_work_sync(&ms->ff_worker); +} + static int ms_probe(struct hid_device *hdev, const struct hid_device_id *id) { unsigned long quirks = id->driver_data; @@ -292,11 +395,21 @@ static int ms_probe(struct hid_device *hdev, const struct hid_device_id *id) goto err_free; } + ret = ms_init_ff(hdev); + if (ret) + hid_err(hdev, "could not initialize ff, continuing anyway"); + return 0; err_free: return ret; } +static void ms_remove(struct hid_device *hdev) +{ + hid_hw_stop(hdev); + ms_remove_ff(hdev); +} + static const struct hid_device_id ms_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_SIDEWINDER_GV), .driver_data = MS_HIDINPUT }, @@ -333,6 +446,8 @@ static const struct hid_device_id ms_devices[] = { .driver_data = MS_PRESENTER }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x091B), .driver_data = MS_SURFACE_DIAL }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_XBOX_ONE_S_CONTROLLER), + .driver_data = MS_QUIRK_FF }, { } }; MODULE_DEVICE_TABLE(hid, ms_devices); @@ -345,6 +460,7 @@ static struct hid_driver ms_driver = { .input_mapped = ms_input_mapped, .event = ms_event, .probe = ms_probe, + .remove = ms_remove, }; module_hid_driver(ms_driver); From 6b003a8ddd6f413a7e17470515accdd4cd80e871 Mon Sep 17 00:00:00 2001 From: "Daniel M. Lambea" Date: Fri, 27 Jul 2018 21:19:11 +0100 Subject: [PATCH 11/35] HID: cougar: Make parameter 'g6_is_space' dinamically settable Parameter g6_is_space instructs the driver to map G6 keypresses to KEY_SPACE (true) or to KEY_F18 (false). Make the parameter configurable via module_param_cb to allow users to change its value without reloading the module. Signed-off-by: Daniel M. Lambea Signed-off-by: Jiri Kosina --- drivers/hid/hid-cougar.c | 46 ++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/drivers/hid/hid-cougar.c b/drivers/hid/hid-cougar.c index ad2e87de7dc53..910ef4312157d 100644 --- a/drivers/hid/hid-cougar.c +++ b/drivers/hid/hid-cougar.c @@ -7,6 +7,7 @@ #include #include +#include #include "hid-ids.h" @@ -15,11 +16,9 @@ MODULE_DESCRIPTION("Cougar 500k Gaming Keyboard"); MODULE_LICENSE("GPL"); MODULE_INFO(key_mappings, "G1-G6 are mapped to F13-F18"); -static int cougar_g6_is_space = 1; -module_param_named(g6_is_space, cougar_g6_is_space, int, 0600); +static bool g6_is_space = true; MODULE_PARM_DESC(g6_is_space, - "If set, G6 programmable key sends SPACE instead of F18 (0=off, 1=on) (default=1)"); - + "If true, G6 programmable key sends SPACE instead of F18 (default=true)"); #define COUGAR_VENDOR_USAGE 0xff00ff00 @@ -82,20 +81,23 @@ struct cougar { static LIST_HEAD(cougar_udev_list); static DEFINE_MUTEX(cougar_udev_list_lock); -static void cougar_fix_g6_mapping(struct hid_device *hdev) +/** + * cougar_fix_g6_mapping - configure the mapping for key G6/Spacebar + */ +static void cougar_fix_g6_mapping(void) { int i; for (i = 0; cougar_mapping[i][0]; i++) { if (cougar_mapping[i][0] == COUGAR_KEY_G6) { cougar_mapping[i][1] = - cougar_g6_is_space ? KEY_SPACE : KEY_F18; - hid_info(hdev, "G6 mapped to %s\n", - cougar_g6_is_space ? "space" : "F18"); + g6_is_space ? KEY_SPACE : KEY_F18; + pr_info("cougar: G6 mapped to %s\n", + g6_is_space ? "space" : "F18"); return; } } - hid_warn(hdev, "no mapping defined for G6/spacebar"); + pr_warn("cougar: no mappings defined for G6/spacebar"); } /* @@ -154,7 +156,8 @@ static void cougar_remove_shared_data(void *resource) * Bind the device group's shared data to this cougar struct. * If no shared data exists for this group, create and initialize it. */ -static int cougar_bind_shared_data(struct hid_device *hdev, struct cougar *cougar) +static int cougar_bind_shared_data(struct hid_device *hdev, + struct cougar *cougar) { struct cougar_shared *shared; int error = 0; @@ -228,7 +231,6 @@ static int cougar_probe(struct hid_device *hdev, * to it. */ if (hdev->collection->usage == HID_GD_KEYBOARD) { - cougar_fix_g6_mapping(hdev); list_for_each_entry_safe(hidinput, next, &hdev->inputs, list) { if (hidinput->registered && hidinput->input != NULL) { cougar->shared->input = hidinput->input; @@ -237,6 +239,8 @@ static int cougar_probe(struct hid_device *hdev, } } } else if (hdev->collection->usage == COUGAR_VENDOR_USAGE) { + /* Preinit the mapping table */ + cougar_fix_g6_mapping(); error = hid_hw_open(hdev); if (error) goto fail_stop_and_cleanup; @@ -293,6 +297,26 @@ static void cougar_remove(struct hid_device *hdev) hid_hw_stop(hdev); } +static int cougar_param_set_g6_is_space(const char *val, + const struct kernel_param *kp) +{ + int ret; + + ret = param_set_bool(val, kp); + if (ret) + return ret; + + cougar_fix_g6_mapping(); + + return 0; +} + +static const struct kernel_param_ops cougar_g6_is_space_ops = { + .set = cougar_param_set_g6_is_space, + .get = param_get_bool, +}; +module_param_cb(g6_is_space, &cougar_g6_is_space_ops, &g6_is_space, 0644); + static struct hid_device_id cougar_id_table[] = { { HID_USB_DEVICE(USB_VENDOR_ID_SOLID_YEAR, USB_DEVICE_ID_COUGAR_500K_GAMING_KEYBOARD) }, From 75f1f19bdd60575164b5d1fb04602bf4450e2b62 Mon Sep 17 00:00:00 2001 From: "Daniel M. Lambea" Date: Fri, 27 Jul 2018 21:19:12 +0100 Subject: [PATCH 12/35] HID: cougar: Stop processing vendor events on hid-core Special key events received by the custom vendor's hdev are translated to key events on the kbd iface's input device, so their processing must not continue. Return -EPERM from raw_event handler to effectively stop source events from being processed in hid-core. Signed-off-by: Daniel M. Lambea Signed-off-by: Jiri Kosina --- drivers/hid/hid-cougar.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/hid/hid-cougar.c b/drivers/hid/hid-cougar.c index 910ef4312157d..3f0916b64c60e 100644 --- a/drivers/hid/hid-cougar.c +++ b/drivers/hid/hid-cougar.c @@ -261,26 +261,32 @@ static int cougar_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { struct cougar *cougar; + struct cougar_shared *shared; unsigned char code, action; int i; cougar = hid_get_drvdata(hdev); - if (!cougar->special_intf || !cougar->shared || - !cougar->shared->input || !cougar->shared->enabled) + shared = cougar->shared; + if (!cougar->special_intf || !shared) return 0; + if (!shared->enabled || !shared->input) + return -EPERM; + code = data[COUGAR_FIELD_CODE]; action = data[COUGAR_FIELD_ACTION]; for (i = 0; cougar_mapping[i][0]; i++) { if (code == cougar_mapping[i][0]) { - input_event(cougar->shared->input, EV_KEY, + input_event(shared->input, EV_KEY, cougar_mapping[i][1], action); - input_sync(cougar->shared->input); - return 0; + input_sync(shared->input); + return -EPERM; } } - hid_warn(hdev, "unmapped special key code %x: ignoring\n", code); - return 0; + /* Avoid warnings on the same unmapped key twice */ + if (action != 0) + hid_warn(hdev, "unmapped special key code %0x: ignoring\n", code); + return -EPERM; } static void cougar_remove(struct hid_device *hdev) From 830e82aa48f32518dfa5335c0134f611d4bab4b2 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Wed, 5 Sep 2018 13:24:47 +0200 Subject: [PATCH 13/35] HID: microsoft: the driver now neeed MEMLESS_FF infrastructure Reflect that fact in Kconfig, the same we do for other such drivers. Reported-by: kbuild test robot Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 61e1953ff9219..e5ec47705fa2b 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -596,6 +596,7 @@ config HID_MICROSOFT tristate "Microsoft non-fully HID-compliant devices" depends on HID default !EXPERT + select INPUT_FF_MEMLESS ---help--- Support for Microsoft devices that are not fully compliant with HID standard. From 8473a93d1ba5385f63a128a285702ccc1d3ae2cc Mon Sep 17 00:00:00 2001 From: Tatsunosuke Tobita Date: Wed, 8 Aug 2018 09:31:43 +0900 Subject: [PATCH 14/35] HID: input: Set INPUT_PROP_-property for HID_UP_DIGITIZERS Some system may want to know if a detected digitizer device is either an integrated or an external device. In order to distinguish such condition, setting either INPUT_PROP_DIRECT or INPUT_PROP_POINTER is required, checking the member, "application", in "hid_field" structure. Signed-off-by: Tatsunosuke Tobita Reviewed-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 83755ba33ef44..42c1a4c2a978d 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -758,6 +758,11 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel break; case HID_UP_DIGITIZER: + if ((field->application & 0xff) == 0x01) /* Digitizer */ + __set_bit(INPUT_PROP_POINTER, input->propbit); + else if ((field->application & 0xff) == 0x02) /* Pen */ + __set_bit(INPUT_PROP_DIRECT, input->propbit); + switch (usage->hid & 0xff) { case 0x00: /* Undefined */ goto ignore; From d0b41230e80cd27e78cd3767da463e6cd0246de7 Mon Sep 17 00:00:00 2001 From: Even Xu Date: Tue, 11 Sep 2018 16:44:13 -0700 Subject: [PATCH 15/35] HID: intel-ish-hid: ishtp: add helper function for driver data get/set Add helper function ishtp_set_drvdata() and ishtp_get_drvdata() for different ISH client drivers to set/get private driver data. Signed-off-by: Even Xu Reviewed-by: Andriy Shevchenko Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ishtp/bus.c | 27 +++++++++++++++++++++++++++ drivers/hid/intel-ish-hid/ishtp/bus.h | 3 +++ 2 files changed, 30 insertions(+) diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c index 2623a567ffba5..d5db5e96e4a13 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.c +++ b/drivers/hid/intel-ish-hid/ishtp/bus.c @@ -563,6 +563,33 @@ void ishtp_put_device(struct ishtp_cl_device *cl_device) } EXPORT_SYMBOL(ishtp_put_device); +/** + * ishtp_set_drvdata() - set client driver data + * @cl_device: client device instance + * @data: driver data need to be set + * + * Set client driver data to cl_device->driver_data. + */ +void ishtp_set_drvdata(struct ishtp_cl_device *cl_device, void *data) +{ + cl_device->driver_data = data; +} +EXPORT_SYMBOL(ishtp_set_drvdata); + +/** + * ishtp_get_drvdata() - get client driver data + * @cl_device: client device instance + * + * Get client driver data from cl_device->driver_data. + * + * Return: pointer of driver data + */ +void *ishtp_get_drvdata(struct ishtp_cl_device *cl_device) +{ + return cl_device->driver_data; +} +EXPORT_SYMBOL(ishtp_get_drvdata); + /** * ishtp_bus_new_client() - Create a new client * @dev: ISHTP device instance diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.h b/drivers/hid/intel-ish-hid/ishtp/bus.h index a1ffae7f26ad2..88883311667e8 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.h +++ b/drivers/hid/intel-ish-hid/ishtp/bus.h @@ -101,6 +101,9 @@ void ishtp_reset_compl_handler(struct ishtp_device *dev); void ishtp_put_device(struct ishtp_cl_device *); void ishtp_get_device(struct ishtp_cl_device *); +void ishtp_set_drvdata(struct ishtp_cl_device *cl_device, void *data); +void *ishtp_get_drvdata(struct ishtp_cl_device *cl_device); + int __ishtp_cl_driver_register(struct ishtp_cl_driver *driver, struct module *owner); #define ishtp_cl_driver_register(driver) \ From d174c6664f09e35079aa63a13f8cf37a8b42357a Mon Sep 17 00:00:00 2001 From: Even Xu Date: Tue, 11 Sep 2018 16:44:14 -0700 Subject: [PATCH 16/35] HID: intel-ish-hid: use helper function for private driver data set/get Use helper set/get function to set/get driver data in ishtp-hid-client driver instead of directly accessing cl_device driver_data member. Signed-off-by: Even Xu Reviewed-by: Andriy Shevchenko Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ishtp-hid-client.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c index 2d28cffc14046..11ce54fcd0980 100644 --- a/drivers/hid/intel-ish-hid/ishtp-hid-client.c +++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c @@ -320,7 +320,7 @@ static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf, */ static void ish_cl_event_cb(struct ishtp_cl_device *device) { - struct ishtp_cl *hid_ishtp_cl = device->driver_data; + struct ishtp_cl *hid_ishtp_cl = ishtp_get_drvdata(device); struct ishtp_cl_rb *rb_in_proc; size_t r_length; unsigned long flags; @@ -765,7 +765,7 @@ static void hid_ishtp_cl_reset_handler(struct work_struct *work) if (!hid_ishtp_cl) return; - cl_device->driver_data = hid_ishtp_cl; + ishtp_set_drvdata(cl_device, hid_ishtp_cl); hid_ishtp_cl->client_data = client_data; client_data->hid_ishtp_cl = hid_ishtp_cl; @@ -814,7 +814,7 @@ static int hid_ishtp_cl_probe(struct ishtp_cl_device *cl_device) if (!hid_ishtp_cl) return -ENOMEM; - cl_device->driver_data = hid_ishtp_cl; + ishtp_set_drvdata(cl_device, hid_ishtp_cl); hid_ishtp_cl->client_data = client_data; client_data->hid_ishtp_cl = hid_ishtp_cl; client_data->cl_device = cl_device; @@ -844,7 +844,7 @@ static int hid_ishtp_cl_probe(struct ishtp_cl_device *cl_device) */ static int hid_ishtp_cl_remove(struct ishtp_cl_device *cl_device) { - struct ishtp_cl *hid_ishtp_cl = cl_device->driver_data; + struct ishtp_cl *hid_ishtp_cl = ishtp_get_drvdata(cl_device); struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, @@ -874,7 +874,7 @@ static int hid_ishtp_cl_remove(struct ishtp_cl_device *cl_device) */ static int hid_ishtp_cl_reset(struct ishtp_cl_device *cl_device) { - struct ishtp_cl *hid_ishtp_cl = cl_device->driver_data; + struct ishtp_cl *hid_ishtp_cl = ishtp_get_drvdata(cl_device); struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, @@ -898,7 +898,7 @@ static int hid_ishtp_cl_reset(struct ishtp_cl_device *cl_device) static int hid_ishtp_cl_suspend(struct device *device) { struct ishtp_cl_device *cl_device = to_ishtp_cl_device(device); - struct ishtp_cl *hid_ishtp_cl = cl_device->driver_data; + struct ishtp_cl *hid_ishtp_cl = ishtp_get_drvdata(cl_device); struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, @@ -919,7 +919,7 @@ static int hid_ishtp_cl_suspend(struct device *device) static int hid_ishtp_cl_resume(struct device *device) { struct ishtp_cl_device *cl_device = to_ishtp_cl_device(device); - struct ishtp_cl *hid_ishtp_cl = cl_device->driver_data; + struct ishtp_cl *hid_ishtp_cl = ishtp_get_drvdata(cl_device); struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, From a1c40ce62fd24badcd65cd14533386fc382332be Mon Sep 17 00:00:00 2001 From: Even Xu Date: Tue, 11 Sep 2018 16:44:15 -0700 Subject: [PATCH 17/35] HID: intel-ish-hid: ishtp: add helper functions for client buffer operation Add helper ishtp_cl_tx_empty() and ishtp_cl_rx_get_rb() to hide internal details from callers, who needs this functionality. Signed-off-by: Even Xu Reviewed-by: Andriy Shevchenko Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- .../hid/intel-ish-hid/ishtp/client-buffers.c | 45 +++++++++++++++++++ drivers/hid/intel-ish-hid/ishtp/client.h | 2 + 2 files changed, 47 insertions(+) diff --git a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c index b9b917d2d50db..12d61305e83dd 100644 --- a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c +++ b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c @@ -255,3 +255,48 @@ int ishtp_cl_io_rb_recycle(struct ishtp_cl_rb *rb) return rets; } EXPORT_SYMBOL(ishtp_cl_io_rb_recycle); + +/** + * ishtp_cl_tx_empty() -test whether client device tx buffer is empty + * @cl: Pointer to client device instance + * + * Look client device tx buffer list, and check whether this list is empty + * + * Return: true if client tx buffer list is empty else false + */ +bool ishtp_cl_tx_empty(struct ishtp_cl *cl) +{ + int tx_list_empty; + unsigned long tx_flags; + + spin_lock_irqsave(&cl->tx_list_spinlock, tx_flags); + tx_list_empty = list_empty(&cl->tx_list.list); + spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags); + + return !!tx_list_empty; +} +EXPORT_SYMBOL(ishtp_cl_tx_empty); + +/** + * ishtp_cl_rx_get_rb() -Get a rb from client device rx buffer list + * @cl: Pointer to client device instance + * + * Check client device in-processing buffer list and get a rb from it. + * + * Return: rb pointer if buffer list isn't empty else NULL + */ +struct ishtp_cl_rb *ishtp_cl_rx_get_rb(struct ishtp_cl *cl) +{ + unsigned long rx_flags; + struct ishtp_cl_rb *rb; + + spin_lock_irqsave(&cl->in_process_spinlock, rx_flags); + rb = list_first_entry_or_null(&cl->in_process_list.list, + struct ishtp_cl_rb, list); + if (rb) + list_del_init(&rb->list); + spin_unlock_irqrestore(&cl->in_process_spinlock, rx_flags); + + return rb; +} +EXPORT_SYMBOL(ishtp_cl_rx_get_rb); diff --git a/drivers/hid/intel-ish-hid/ishtp/client.h b/drivers/hid/intel-ish-hid/ishtp/client.h index 79eade547f5db..a82ee4d82cdb9 100644 --- a/drivers/hid/intel-ish-hid/ishtp/client.h +++ b/drivers/hid/intel-ish-hid/ishtp/client.h @@ -178,5 +178,7 @@ int ishtp_cl_flush_queues(struct ishtp_cl *cl); /* exported functions from ISHTP client buffer management scope */ int ishtp_cl_io_rb_recycle(struct ishtp_cl_rb *rb); +bool ishtp_cl_tx_empty(struct ishtp_cl *cl); +struct ishtp_cl_rb *ishtp_cl_rx_get_rb(struct ishtp_cl *cl); #endif /* _ISHTP_CLIENT_H_ */ From 816e7ed1c85dbb5e477139d6f28ffd175d0a609e Mon Sep 17 00:00:00 2001 From: Even Xu Date: Tue, 11 Sep 2018 16:44:16 -0700 Subject: [PATCH 18/35] HID: intel-ish-hid: use helper function to access client buffer ishtp bus driver exposed helper functions for client buffer accessing, so change to use these functions in ishtp-hid-client driver to avoid access client buffer directly. Signed-off-by: Even Xu Reviewed-by: Andriy Shevchenko Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ishtp-hid-client.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c index 11ce54fcd0980..bb8402f108317 100644 --- a/drivers/hid/intel-ish-hid/ishtp-hid-client.c +++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c @@ -323,20 +323,11 @@ static void ish_cl_event_cb(struct ishtp_cl_device *device) struct ishtp_cl *hid_ishtp_cl = ishtp_get_drvdata(device); struct ishtp_cl_rb *rb_in_proc; size_t r_length; - unsigned long flags; if (!hid_ishtp_cl) return; - spin_lock_irqsave(&hid_ishtp_cl->in_process_spinlock, flags); - while (!list_empty(&hid_ishtp_cl->in_process_list.list)) { - rb_in_proc = list_entry( - hid_ishtp_cl->in_process_list.list.next, - struct ishtp_cl_rb, list); - list_del_init(&rb_in_proc->list); - spin_unlock_irqrestore(&hid_ishtp_cl->in_process_spinlock, - flags); - + while ((rb_in_proc = ishtp_cl_rx_get_rb(hid_ishtp_cl)) != NULL) { if (!rb_in_proc->buffer.data) return; @@ -346,9 +337,7 @@ static void ish_cl_event_cb(struct ishtp_cl_device *device) process_recv(hid_ishtp_cl, rb_in_proc->buffer.data, r_length); ishtp_cl_io_rb_recycle(rb_in_proc); - spin_lock_irqsave(&hid_ishtp_cl->in_process_spinlock, flags); } - spin_unlock_irqrestore(&hid_ishtp_cl->in_process_spinlock, flags); } /** From e625020b2c1e51a216094d60a6a72596926dd77d Mon Sep 17 00:00:00 2001 From: Even Xu Date: Tue, 11 Sep 2018 16:44:17 -0700 Subject: [PATCH 19/35] HID: intel-ish-hid: ishtp: add helper function for client search Add helper function ishtp_fw_cl_get_client() for client driver searching client information to hide internal details from callers. Signed-off-by: Even Xu Reviewed-by: Andriy Shevchenko Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ishtp/bus.c | 25 +++++++++++++++++++++++++ drivers/hid/intel-ish-hid/ishtp/bus.h | 2 ++ 2 files changed, 27 insertions(+) diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c index d5db5e96e4a13..728dc6d4561aa 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.c +++ b/drivers/hid/intel-ish-hid/ishtp/bus.c @@ -148,6 +148,31 @@ int ishtp_fw_cl_by_uuid(struct ishtp_device *dev, const uuid_le *uuid) } EXPORT_SYMBOL(ishtp_fw_cl_by_uuid); +/** + * ishtp_fw_cl_get_client() - return client information to client + * @dev: the ishtp device structure + * @uuid: uuid of the client to search + * + * Search firmware client using UUID and reture related client information. + * + * Return: pointer of client information on success, NULL on failure. + */ +struct ishtp_fw_client *ishtp_fw_cl_get_client(struct ishtp_device *dev, + const uuid_le *uuid) +{ + int i; + unsigned long flags; + + spin_lock_irqsave(&dev->fw_clients_lock, flags); + i = ishtp_fw_cl_by_uuid(dev, uuid); + spin_unlock_irqrestore(&dev->fw_clients_lock, flags); + if (i < 0 || dev->fw_clients[i].props.fixed_address) + return NULL; + + return &dev->fw_clients[i]; +} +EXPORT_SYMBOL(ishtp_fw_cl_get_client); + /** * ishtp_fw_cl_by_id() - return index to fw_clients for client_id * @dev: the ishtp device structure diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.h b/drivers/hid/intel-ish-hid/ishtp/bus.h index 88883311667e8..b8a5bcc825369 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.h +++ b/drivers/hid/intel-ish-hid/ishtp/bus.h @@ -113,5 +113,7 @@ void ishtp_cl_driver_unregister(struct ishtp_cl_driver *driver); int ishtp_register_event_cb(struct ishtp_cl_device *device, void (*read_cb)(struct ishtp_cl_device *)); int ishtp_fw_cl_by_uuid(struct ishtp_device *dev, const uuid_le *cuuid); +struct ishtp_fw_client *ishtp_fw_cl_get_client(struct ishtp_device *dev, + const uuid_le *uuid); #endif /* _LINUX_ISHTP_CL_BUS_H */ From f26de33faff19bc937311df66278a2d3a1535fff Mon Sep 17 00:00:00 2001 From: Even Xu Date: Tue, 11 Sep 2018 16:44:18 -0700 Subject: [PATCH 20/35] HID: intel-ish-hid: use helper function to search client id ishtp exposed helper ishtp_fw_cl_get_client() function for client information searching, so switch to use it. Signed-off-by: Even Xu Reviewed-by: Andriy Shevchenko Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ishtp-hid-client.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c index bb8402f108317..e64243bc9c96f 100644 --- a/drivers/hid/intel-ish-hid/ishtp-hid-client.c +++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c @@ -626,8 +626,8 @@ static int ishtp_get_report_descriptor(struct ishtp_cl *hid_ishtp_cl, static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, int reset) { struct ishtp_device *dev; - unsigned long flags; struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; + struct ishtp_fw_client *fw_client; int i; int rv; @@ -649,16 +649,14 @@ static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, int reset) hid_ishtp_cl->rx_ring_size = HID_CL_RX_RING_SIZE; hid_ishtp_cl->tx_ring_size = HID_CL_TX_RING_SIZE; - spin_lock_irqsave(&dev->fw_clients_lock, flags); - i = ishtp_fw_cl_by_uuid(dev, &hid_ishtp_guid); - if (i < 0) { - spin_unlock_irqrestore(&dev->fw_clients_lock, flags); + fw_client = ishtp_fw_cl_get_client(dev, &hid_ishtp_guid); + if (!fw_client) { dev_err(&client_data->cl_device->dev, "ish client uuid not found\n"); - return i; + return -ENOENT; } - hid_ishtp_cl->fw_client_id = dev->fw_clients[i].client_id; - spin_unlock_irqrestore(&dev->fw_clients_lock, flags); + + hid_ishtp_cl->fw_client_id = fw_client->client_id; hid_ishtp_cl->state = ISHTP_CL_CONNECTING; rv = ishtp_cl_connect(hid_ishtp_cl); From 18c0b5467498ccbedd1646cbeecbd199e5f3f860 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 11 Sep 2018 16:44:19 -0700 Subject: [PATCH 21/35] HID: intel_ish-hid: Enhance API to get ring buffer sizes Added two APIs: - ishtp_cl_get_tx_free_buffer_size: This returns total size available for a client to queue TX data. - ishtp_cl_get_tx_free_rings: This returns total number of TX rings available for a client. Signed-off-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- .../hid/intel-ish-hid/ishtp/client-buffers.c | 4 ++++ drivers/hid/intel-ish-hid/ishtp/client.c | 24 +++++++++++++++++++ drivers/hid/intel-ish-hid/ishtp/client.h | 3 +++ 3 files changed, 31 insertions(+) diff --git a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c index 12d61305e83dd..248651c35497e 100644 --- a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c +++ b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c @@ -69,6 +69,8 @@ int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl) int j; unsigned long flags; + cl->tx_ring_free_size = 0; + /* Allocate pool to free Tx bufs */ for (j = 0; j < cl->tx_ring_size; ++j) { struct ishtp_cl_tx_ring *tx_buf; @@ -85,6 +87,7 @@ int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl) spin_lock_irqsave(&cl->tx_free_list_spinlock, flags); list_add_tail(&tx_buf->list, &cl->tx_free_list.list); + ++cl->tx_ring_free_size; spin_unlock_irqrestore(&cl->tx_free_list_spinlock, flags); } return 0; @@ -144,6 +147,7 @@ void ishtp_cl_free_tx_ring(struct ishtp_cl *cl) tx_buf = list_entry(cl->tx_free_list.list.next, struct ishtp_cl_tx_ring, list); list_del(&tx_buf->list); + --cl->tx_ring_free_size; kfree(tx_buf->send_buf.data); kfree(tx_buf); } diff --git a/drivers/hid/intel-ish-hid/ishtp/client.c b/drivers/hid/intel-ish-hid/ishtp/client.c index 007443ef5fca4..faeccdb1475bd 100644 --- a/drivers/hid/intel-ish-hid/ishtp/client.c +++ b/drivers/hid/intel-ish-hid/ishtp/client.c @@ -22,6 +22,25 @@ #include "hbm.h" #include "client.h" +int ishtp_cl_get_tx_free_buffer_size(struct ishtp_cl *cl) +{ + unsigned long tx_free_flags; + int size; + + spin_lock_irqsave(&cl->tx_free_list_spinlock, tx_free_flags); + size = cl->tx_ring_free_size * cl->device->fw_client->props.max_msg_length; + spin_unlock_irqrestore(&cl->tx_free_list_spinlock, tx_free_flags); + + return size; +} +EXPORT_SYMBOL(ishtp_cl_get_tx_free_buffer_size); + +int ishtp_cl_get_tx_free_rings(struct ishtp_cl *cl) +{ + return cl->tx_ring_free_size; +} +EXPORT_SYMBOL(ishtp_cl_get_tx_free_rings); + /** * ishtp_read_list_flush() - Flush read queue * @cl: ishtp client instance @@ -90,6 +109,7 @@ static void ishtp_cl_init(struct ishtp_cl *cl, struct ishtp_device *dev) cl->rx_ring_size = CL_DEF_RX_RING_SIZE; cl->tx_ring_size = CL_DEF_TX_RING_SIZE; + cl->tx_ring_free_size = cl->tx_ring_size; /* dma */ cl->last_tx_path = CL_TX_PATH_IPC; @@ -577,6 +597,8 @@ int ishtp_cl_send(struct ishtp_cl *cl, uint8_t *buf, size_t length) * max ISHTP message size per client */ list_del_init(&cl_msg->list); + --cl->tx_ring_free_size; + spin_unlock_irqrestore(&cl->tx_free_list_spinlock, tx_free_flags); memcpy(cl_msg->send_buf.data, buf, length); cl_msg->send_buf.size = length; @@ -685,6 +707,7 @@ static void ipc_tx_callback(void *prm) ishtp_write_message(dev, &ishtp_hdr, pmsg); spin_lock_irqsave(&cl->tx_free_list_spinlock, tx_free_flags); list_add_tail(&cl_msg->list, &cl->tx_free_list.list); + ++cl->tx_ring_free_size; spin_unlock_irqrestore(&cl->tx_free_list_spinlock, tx_free_flags); } else { @@ -778,6 +801,7 @@ static void ishtp_cl_send_msg_dma(struct ishtp_device *dev, ishtp_write_message(dev, &hdr, (unsigned char *)&dma_xfer); spin_lock_irqsave(&cl->tx_free_list_spinlock, tx_free_flags); list_add_tail(&cl_msg->list, &cl->tx_free_list.list); + ++cl->tx_ring_free_size; spin_unlock_irqrestore(&cl->tx_free_list_spinlock, tx_free_flags); ++cl->send_msg_cnt_dma; } diff --git a/drivers/hid/intel-ish-hid/ishtp/client.h b/drivers/hid/intel-ish-hid/ishtp/client.h index a82ee4d82cdb9..042f4c4853b12 100644 --- a/drivers/hid/intel-ish-hid/ishtp/client.h +++ b/drivers/hid/intel-ish-hid/ishtp/client.h @@ -84,6 +84,7 @@ struct ishtp_cl { /* Client Tx buffers list */ unsigned int tx_ring_size; struct ishtp_cl_tx_ring tx_list, tx_free_list; + int tx_ring_free_size; spinlock_t tx_list_spinlock; spinlock_t tx_free_list_spinlock; size_t tx_offs; /* Offset in buffer at head of 'tx_list' */ @@ -137,6 +138,8 @@ int ishtp_cl_alloc_rx_ring(struct ishtp_cl *cl); int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl); void ishtp_cl_free_rx_ring(struct ishtp_cl *cl); void ishtp_cl_free_tx_ring(struct ishtp_cl *cl); +int ishtp_cl_get_tx_free_buffer_size(struct ishtp_cl *cl); +int ishtp_cl_get_tx_free_rings(struct ishtp_cl *cl); /* DMA I/F functions */ void recv_ishtp_cl_msg_dma(struct ishtp_device *dev, void *msg, From 946a7773dcb64afa8164c63c378aa087d69f7d8f Mon Sep 17 00:00:00 2001 From: Hong Liu Date: Tue, 11 Sep 2018 16:44:20 -0700 Subject: [PATCH 22/35] HID: intel-ish-hid: use resource-managed api Use resource-managed api to simplify error handling in probe and driver remove logic. With this patch, we can save ~170 bytes code size in intel-ish-ipc.ko. before: text data bss dec hex filename 11495 1568 72 13135 334f intel-ish-ipc.ko after: text data bss dec hex filename 11327 1568 72 12967 32a7 intel-ish-ipc.ko Signed-off-by: Hong Liu Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ipc/pci-ish.c | 75 ++++++++----------------- 1 file changed, 24 insertions(+), 51 deletions(-) diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c index a1125a5c7965a..63c15c917ccbf 100644 --- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c +++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c @@ -114,18 +114,19 @@ static const struct pci_device_id ish_invalid_pci_ids[] = { */ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { - struct ishtp_device *dev; + int ret; struct ish_hw *hw; - int ret; + struct ishtp_device *ishtp; + struct device *dev = &pdev->dev; /* Check for invalid platforms for ISH support */ if (pci_dev_present(ish_invalid_pci_ids)) return -ENODEV; /* enable pci dev */ - ret = pci_enable_device(pdev); + ret = pcim_enable_device(pdev); if (ret) { - dev_err(&pdev->dev, "ISH: Failed to enable PCI device\n"); + dev_err(dev, "ISH: Failed to enable PCI device\n"); return ret; } @@ -133,65 +134,44 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent) pci_set_master(pdev); /* pci request regions for ISH driver */ - ret = pci_request_regions(pdev, KBUILD_MODNAME); + ret = pcim_iomap_regions(pdev, 1 << 0, KBUILD_MODNAME); if (ret) { - dev_err(&pdev->dev, "ISH: Failed to get PCI regions\n"); - goto disable_device; + dev_err(dev, "ISH: Failed to get PCI regions\n"); + return ret; } /* allocates and initializes the ISH dev structure */ - dev = ish_dev_init(pdev); - if (!dev) { + ishtp = ish_dev_init(pdev); + if (!ishtp) { ret = -ENOMEM; - goto release_regions; + return ret; } - hw = to_ish_hw(dev); - dev->print_log = ish_event_tracer; + hw = to_ish_hw(ishtp); + ishtp->print_log = ish_event_tracer; /* mapping IO device memory */ - hw->mem_addr = pci_iomap(pdev, 0, 0); - if (!hw->mem_addr) { - dev_err(&pdev->dev, "ISH: mapping I/O range failure\n"); - ret = -ENOMEM; - goto free_device; - } - - dev->pdev = pdev; - + hw->mem_addr = pcim_iomap_table(pdev)[0]; + ishtp->pdev = pdev; pdev->dev_flags |= PCI_DEV_FLAGS_NO_D3; /* request and enable interrupt */ - ret = request_irq(pdev->irq, ish_irq_handler, IRQF_SHARED, - KBUILD_MODNAME, dev); + ret = devm_request_irq(dev, pdev->irq, ish_irq_handler, + IRQF_SHARED, KBUILD_MODNAME, ishtp); if (ret) { - dev_err(&pdev->dev, "ISH: request IRQ failure (%d)\n", - pdev->irq); - goto free_device; + dev_err(dev, "ISH: request IRQ %d failed\n", pdev->irq); + return ret; } - dev_set_drvdata(dev->devc, dev); + dev_set_drvdata(ishtp->devc, ishtp); - init_waitqueue_head(&dev->suspend_wait); - init_waitqueue_head(&dev->resume_wait); + init_waitqueue_head(&ishtp->suspend_wait); + init_waitqueue_head(&ishtp->resume_wait); - ret = ish_init(dev); + ret = ish_init(ishtp); if (ret) - goto free_irq; + return ret; return 0; - -free_irq: - free_irq(pdev->irq, dev); -free_device: - pci_iounmap(pdev, hw->mem_addr); -release_regions: - pci_release_regions(pdev); -disable_device: - pci_clear_master(pdev); - pci_disable_device(pdev); - dev_err(&pdev->dev, "ISH: PCI driver initialization failed.\n"); - - return ret; } /** @@ -203,16 +183,9 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent) static void ish_remove(struct pci_dev *pdev) { struct ishtp_device *ishtp_dev = pci_get_drvdata(pdev); - struct ish_hw *hw = to_ish_hw(ishtp_dev); ishtp_bus_remove_all_clients(ishtp_dev, false); ish_device_disable(ishtp_dev); - - free_irq(pdev->irq, ishtp_dev); - pci_iounmap(pdev, hw->mem_addr); - pci_release_regions(pdev); - pci_clear_master(pdev); - pci_disable_device(pdev); } static struct device __maybe_unused *ish_resume_device; From dc4e05d079591c6f69bb28a07bcc13d4f1c9993b Mon Sep 17 00:00:00 2001 From: Hong Liu Date: Tue, 11 Sep 2018 16:44:21 -0700 Subject: [PATCH 23/35] HID: intel-ish-hid: using list_head for ipc write queue Currently wr_msg_ctl_info is used in ishtp_device just for list head purpose, using list_head directly can save ~150 bytes size for each replacement. Also this patch can save ~170 bytes of code size in intel-ish-ipc.ko. Signed-off-by: Hong Liu Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ipc/ipc.c | 32 +++++++++------------ drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h | 2 +- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c index bfbca7ec54ce4..742191bb24c69 100644 --- a/drivers/hid/intel-ish-hid/ipc/ipc.c +++ b/drivers/hid/intel-ish-hid/ipc/ipc.c @@ -280,14 +280,14 @@ static int write_ipc_from_queue(struct ishtp_device *dev) * if tx send list is empty - return 0; * may happen, as RX_COMPLETE handler doesn't check list emptiness. */ - if (list_empty(&dev->wr_processing_list_head.link)) { + if (list_empty(&dev->wr_processing_list)) { spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); out_ipc_locked = 0; return 0; } - ipc_link = list_entry(dev->wr_processing_list_head.link.next, - struct wr_msg_ctl_info, link); + ipc_link = list_first_entry(&dev->wr_processing_list, + struct wr_msg_ctl_info, link); /* first 4 bytes of the data is the doorbell value (IPC header) */ length = ipc_link->length - sizeof(uint32_t); doorbell_val = *(uint32_t *)ipc_link->inline_data; @@ -338,7 +338,7 @@ static int write_ipc_from_queue(struct ishtp_device *dev) ipc_send_compl = ipc_link->ipc_send_compl; ipc_send_compl_prm = ipc_link->ipc_send_compl_prm; list_del_init(&ipc_link->link); - list_add_tail(&ipc_link->link, &dev->wr_free_list_head.link); + list_add(&ipc_link->link, &dev->wr_free_list); spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); /* @@ -372,18 +372,18 @@ static int write_ipc_to_queue(struct ishtp_device *dev, unsigned char *msg, int length) { struct wr_msg_ctl_info *ipc_link; - unsigned long flags; + unsigned long flags; if (length > IPC_FULL_MSG_SIZE) return -EMSGSIZE; spin_lock_irqsave(&dev->wr_processing_spinlock, flags); - if (list_empty(&dev->wr_free_list_head.link)) { + if (list_empty(&dev->wr_free_list)) { spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); return -ENOMEM; } - ipc_link = list_entry(dev->wr_free_list_head.link.next, - struct wr_msg_ctl_info, link); + ipc_link = list_first_entry(&dev->wr_free_list, + struct wr_msg_ctl_info, link); list_del_init(&ipc_link->link); ipc_link->ipc_send_compl = ipc_send_compl; @@ -391,7 +391,7 @@ static int write_ipc_to_queue(struct ishtp_device *dev, ipc_link->length = length; memcpy(ipc_link->inline_data, msg, length); - list_add_tail(&ipc_link->link, &dev->wr_processing_list_head.link); + list_add_tail(&ipc_link->link, &dev->wr_processing_list); spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); write_ipc_from_queue(dev); @@ -487,17 +487,13 @@ static int ish_fw_reset_handler(struct ishtp_device *dev) { uint32_t reset_id; unsigned long flags; - struct wr_msg_ctl_info *processing, *next; /* Read reset ID */ reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF; /* Clear IPC output queue */ spin_lock_irqsave(&dev->wr_processing_spinlock, flags); - list_for_each_entry_safe(processing, next, - &dev->wr_processing_list_head.link, link) { - list_move_tail(&processing->link, &dev->wr_free_list_head.link); - } + list_splice_init(&dev->wr_processing_list, &dev->wr_free_list); spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); /* ISHTP notification in IPC_RESET */ @@ -921,9 +917,9 @@ struct ishtp_device *ish_dev_init(struct pci_dev *pdev) spin_lock_init(&dev->out_ipc_spinlock); /* Init IPC processing and free lists */ - INIT_LIST_HEAD(&dev->wr_processing_list_head.link); - INIT_LIST_HEAD(&dev->wr_free_list_head.link); - for (i = 0; i < IPC_TX_FIFO_SIZE; ++i) { + INIT_LIST_HEAD(&dev->wr_processing_list); + INIT_LIST_HEAD(&dev->wr_free_list); + for (i = 0; i < IPC_TX_FIFO_SIZE; i++) { struct wr_msg_ctl_info *tx_buf; tx_buf = devm_kzalloc(&pdev->dev, @@ -939,7 +935,7 @@ struct ishtp_device *ish_dev_init(struct pci_dev *pdev) i); break; } - list_add_tail(&tx_buf->link, &dev->wr_free_list_head.link); + list_add_tail(&tx_buf->link, &dev->wr_free_list); } dev->ops = &ish_hw_ops; diff --git a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h index 6a6d927b78b0e..e7c6bfefaf9eb 100644 --- a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h +++ b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h @@ -207,7 +207,7 @@ struct ishtp_device { struct work_struct bh_hbm_work; /* IPC write queue */ - struct wr_msg_ctl_info wr_processing_list_head, wr_free_list_head; + struct list_head wr_processing_list, wr_free_list; /* For both processing list and free list */ spinlock_t wr_processing_spinlock; From 5fe2ccbef9d7aecf5c4402c753444f1a12096cfd Mon Sep 17 00:00:00 2001 From: zhong jiang Date: Thu, 13 Sep 2018 15:41:10 +0800 Subject: [PATCH 24/35] HID: logitech: fix a used uninitialized GCC warning Fix the following compile warning: drivers/hid/hid-logitech-hidpp.c: In function 'hi_res_scroll_enable': drivers/hid/hid-logitech-hidpp.c:2714:54: warning: 'multiplier' may be used uninitialized in this function [-Wmaybe-uninitialized] hidpp->vertical_wheel_counter.resolution_multiplier = multiplier; Signed-off-by: zhong jiang Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 5f0c080059c68..f01280898b24b 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -1231,7 +1231,6 @@ static int hidpp_hrw_get_wheel_capability(struct hidpp_device *hidpp, *multiplier = response.fap.params[0]; return 0; return_default: - *multiplier = 8; hid_warn(hidpp->hid_dev, "Couldn't get wheel multiplier (error %d), assuming %d.\n", ret, *multiplier); @@ -2696,7 +2695,7 @@ static int hi_res_scroll_look_up_microns(__u32 product_id) static int hi_res_scroll_enable(struct hidpp_device *hidpp) { int ret; - u8 multiplier; + u8 multiplier = 8; if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_X2121) { ret = hidpp_hrw_set_wheel_mode(hidpp, false, true, false); @@ -2704,10 +2703,9 @@ static int hi_res_scroll_enable(struct hidpp_device *hidpp) } else if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_X2120) { ret = hidpp_hrs_set_highres_scrolling_mode(hidpp, true, &multiplier); - } else /* if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_1P0) */ { + } else /* if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_1P0) */ ret = hidpp10_enable_scrolling_acceleration(hidpp); - multiplier = 8; - } + if (ret) return ret; From 256a90ed9e46b270bbc4e15ef05216ff049c3721 Mon Sep 17 00:00:00 2001 From: Hanno Zulla Date: Thu, 23 Aug 2018 17:03:38 +0200 Subject: [PATCH 25/35] HID: hid-bigbenff: driver for BigBen Interactive PS3OFMINIPAD gamepad This is a driver to fix input mapping and add LED & force feedback support for the "BigBen Interactive Kid-friendly Wired Controller PS3OFMINIPAD SONY" gamepad with USB id 146b:0902. It was originally sold as a PS3 accessory and makes a very nice gamepad for Retropie. Signed-off-by: Hanno Zulla Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 13 ++ drivers/hid/Makefile | 1 + drivers/hid/hid-bigbenff.c | 414 +++++++++++++++++++++++++++++++++++++ drivers/hid/hid-ids.h | 3 + 4 files changed, 431 insertions(+) create mode 100644 drivers/hid/hid-bigbenff.c diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 61e1953ff9219..5a163f13663cf 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -182,6 +182,19 @@ config HID_BETOP_FF Currently the following devices are known to be supported: - BETOP 2185 PC & BFM MODE +config HID_BIGBEN_FF + tristate "BigBen Interactive Kids' gamepad support" + depends on USB_HID + depends on NEW_LEDS + depends on LEDS_CLASS + select INPUT_FF_MEMLESS + default !EXPERT + help + Support for the "Kid-friendly Wired Controller" PS3OFMINIPAD + gamepad made by BigBen Interactive, originally sold as a PS3 + accessory. This driver fixes input mapping and adds support for + force feedback effects and LEDs on the device. + config HID_CHERRY tristate "Cherry Cymotion keyboard" depends on HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index bd7ac53b75c56..896a51ce7ce01 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_HID_ASUS) += hid-asus.o obj-$(CONFIG_HID_AUREAL) += hid-aureal.o obj-$(CONFIG_HID_BELKIN) += hid-belkin.o obj-$(CONFIG_HID_BETOP_FF) += hid-betopff.o +obj-$(CONFIG_HID_BIGBEN_FF) += hid-bigbenff.o obj-$(CONFIG_HID_CHERRY) += hid-cherry.o obj-$(CONFIG_HID_CHICONY) += hid-chicony.o obj-$(CONFIG_HID_CMEDIA) += hid-cmedia.o diff --git a/drivers/hid/hid-bigbenff.c b/drivers/hid/hid-bigbenff.c new file mode 100644 index 0000000000000..3f6abd190df43 --- /dev/null +++ b/drivers/hid/hid-bigbenff.c @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * LED & force feedback support for BigBen Interactive + * + * 0x146b:0x0902 "Bigben Interactive Bigben Game Pad" + * "Kid-friendly Wired Controller" PS3OFMINIPAD SONY + * sold for use with the PS3 + * + * Copyright (c) 2018 Hanno Zulla + */ + +#include +#include +#include +#include +#include + +#include "hid-ids.h" + + +/* + * The original descriptor for 0x146b:0x0902 + * + * 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + * 0x09, 0x05, // Usage (Game Pad) + * 0xA1, 0x01, // Collection (Application) + * 0x15, 0x00, // Logical Minimum (0) + * 0x25, 0x01, // Logical Maximum (1) + * 0x35, 0x00, // Physical Minimum (0) + * 0x45, 0x01, // Physical Maximum (1) + * 0x75, 0x01, // Report Size (1) + * 0x95, 0x0D, // Report Count (13) + * 0x05, 0x09, // Usage Page (Button) + * 0x19, 0x01, // Usage Minimum (0x01) + * 0x29, 0x0D, // Usage Maximum (0x0D) + * 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + * 0x95, 0x03, // Report Count (3) + * 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + * 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + * 0x25, 0x07, // Logical Maximum (7) + * 0x46, 0x3B, 0x01, // Physical Maximum (315) + * 0x75, 0x04, // Report Size (4) + * 0x95, 0x01, // Report Count (1) + * 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter) + * 0x09, 0x39, // Usage (Hat switch) + * 0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) + * 0x65, 0x00, // Unit (None) + * 0x95, 0x01, // Report Count (1) + * 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + * 0x26, 0xFF, 0x00, // Logical Maximum (255) + * 0x46, 0xFF, 0x00, // Physical Maximum (255) + * 0x09, 0x30, // Usage (X) + * 0x09, 0x31, // Usage (Y) + * 0x09, 0x32, // Usage (Z) + * 0x09, 0x35, // Usage (Rz) + * 0x75, 0x08, // Report Size (8) + * 0x95, 0x04, // Report Count (4) + * 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + * 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) + * 0x09, 0x20, // Usage (0x20) + * 0x09, 0x21, // Usage (0x21) + * 0x09, 0x22, // Usage (0x22) + * 0x09, 0x23, // Usage (0x23) + * 0x09, 0x24, // Usage (0x24) + * 0x09, 0x25, // Usage (0x25) + * 0x09, 0x26, // Usage (0x26) + * 0x09, 0x27, // Usage (0x27) + * 0x09, 0x28, // Usage (0x28) + * 0x09, 0x29, // Usage (0x29) + * 0x09, 0x2A, // Usage (0x2A) + * 0x09, 0x2B, // Usage (0x2B) + * 0x95, 0x0C, // Report Count (12) + * 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + * 0x0A, 0x21, 0x26, // Usage (0x2621) + * 0x95, 0x08, // Report Count (8) + * 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + * 0x0A, 0x21, 0x26, // Usage (0x2621) + * 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + * 0x26, 0xFF, 0x03, // Logical Maximum (1023) + * 0x46, 0xFF, 0x03, // Physical Maximum (1023) + * 0x09, 0x2C, // Usage (0x2C) + * 0x09, 0x2D, // Usage (0x2D) + * 0x09, 0x2E, // Usage (0x2E) + * 0x09, 0x2F, // Usage (0x2F) + * 0x75, 0x10, // Report Size (16) + * 0x95, 0x04, // Report Count (4) + * 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + * 0xC0, // End Collection + */ + +#define PID0902_RDESC_ORIG_SIZE 137 + +/* + * The fixed descriptor for 0x146b:0x0902 + * + * - map buttons according to gamepad.rst + * - assign right stick from Z/Rz to Rx/Ry + * - map previously unused analog trigger data to Z/RZ + * - simplify feature and output descriptor + */ +static __u8 pid0902_rdesc_fixed[] = { + 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ + 0x09, 0x05, /* Usage (Game Pad) */ + 0xA1, 0x01, /* Collection (Application) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0x01, /* Logical Maximum (1) */ + 0x35, 0x00, /* Physical Minimum (0) */ + 0x45, 0x01, /* Physical Maximum (1) */ + 0x75, 0x01, /* Report Size (1) */ + 0x95, 0x0D, /* Report Count (13) */ + 0x05, 0x09, /* Usage Page (Button) */ + 0x09, 0x05, /* Usage (BTN_WEST) */ + 0x09, 0x01, /* Usage (BTN_SOUTH) */ + 0x09, 0x02, /* Usage (BTN_EAST) */ + 0x09, 0x04, /* Usage (BTN_NORTH) */ + 0x09, 0x07, /* Usage (BTN_TL) */ + 0x09, 0x08, /* Usage (BTN_TR) */ + 0x09, 0x09, /* Usage (BTN_TL2) */ + 0x09, 0x0A, /* Usage (BTN_TR2) */ + 0x09, 0x0B, /* Usage (BTN_SELECT) */ + 0x09, 0x0C, /* Usage (BTN_START) */ + 0x09, 0x0E, /* Usage (BTN_THUMBL) */ + 0x09, 0x0F, /* Usage (BTN_THUMBR) */ + 0x09, 0x0D, /* Usage (BTN_MODE) */ + 0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ + 0x75, 0x01, /* Report Size (1) */ + 0x95, 0x03, /* Report Count (3) */ + 0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ + 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ + 0x25, 0x07, /* Logical Maximum (7) */ + 0x46, 0x3B, 0x01, /* Physical Maximum (315) */ + 0x75, 0x04, /* Report Size (4) */ + 0x95, 0x01, /* Report Count (1) */ + 0x65, 0x14, /* Unit (System: English Rotation, Length: Centimeter) */ + 0x09, 0x39, /* Usage (Hat switch) */ + 0x81, 0x42, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) */ + 0x65, 0x00, /* Unit (None) */ + 0x95, 0x01, /* Report Count (1) */ + 0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ + 0x26, 0xFF, 0x00, /* Logical Maximum (255) */ + 0x46, 0xFF, 0x00, /* Physical Maximum (255) */ + 0x09, 0x30, /* Usage (X) */ + 0x09, 0x31, /* Usage (Y) */ + 0x09, 0x33, /* Usage (Rx) */ + 0x09, 0x34, /* Usage (Ry) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x04, /* Report Count (4) */ + 0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ + 0x95, 0x0A, /* Report Count (10) */ + 0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ + 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ + 0x26, 0xFF, 0x00, /* Logical Maximum (255) */ + 0x46, 0xFF, 0x00, /* Physical Maximum (255) */ + 0x09, 0x32, /* Usage (Z) */ + 0x09, 0x35, /* Usage (Rz) */ + 0x95, 0x02, /* Report Count (2) */ + 0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ + 0x95, 0x08, /* Report Count (8) */ + 0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ + 0x06, 0x00, 0xFF, /* Usage Page (Vendor Defined 0xFF00) */ + 0xB1, 0x02, /* Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) */ + 0x0A, 0x21, 0x26, /* Usage (0x2621) */ + 0x95, 0x08, /* Report Count (8) */ + 0x91, 0x02, /* Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) */ + 0x0A, 0x21, 0x26, /* Usage (0x2621) */ + 0x95, 0x08, /* Report Count (8) */ + 0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */ + 0xC0, /* End Collection */ +}; + +#define NUM_LEDS 4 + +struct bigben_device { + struct hid_device *hid; + struct hid_report *report; + u8 led_state; /* LED1 = 1 .. LED4 = 8 */ + u8 right_motor_on; /* right motor off/on 0/1 */ + u8 left_motor_force; /* left motor force 0-255 */ + struct led_classdev *leds[NUM_LEDS]; + bool work_led; + bool work_ff; + struct work_struct worker; +}; + + +static void bigben_worker(struct work_struct *work) +{ + struct bigben_device *bigben = container_of(work, + struct bigben_device, worker); + struct hid_field *report_field = bigben->report->field[0]; + + if (bigben->work_led) { + bigben->work_led = false; + report_field->value[0] = 0x01; /* 1 = led message */ + report_field->value[1] = 0x08; /* reserved value, always 8 */ + report_field->value[2] = bigben->led_state; + report_field->value[3] = 0x00; /* padding */ + report_field->value[4] = 0x00; /* padding */ + report_field->value[5] = 0x00; /* padding */ + report_field->value[6] = 0x00; /* padding */ + report_field->value[7] = 0x00; /* padding */ + hid_hw_request(bigben->hid, bigben->report, HID_REQ_SET_REPORT); + } + + if (bigben->work_ff) { + bigben->work_ff = false; + report_field->value[0] = 0x02; /* 2 = rumble effect message */ + report_field->value[1] = 0x08; /* reserved value, always 8 */ + report_field->value[2] = bigben->right_motor_on; + report_field->value[3] = bigben->left_motor_force; + report_field->value[4] = 0xff; /* duration 0-254 (255 = nonstop) */ + report_field->value[5] = 0x00; /* padding */ + report_field->value[6] = 0x00; /* padding */ + report_field->value[7] = 0x00; /* padding */ + hid_hw_request(bigben->hid, bigben->report, HID_REQ_SET_REPORT); + } +} + +static int hid_bigben_play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct bigben_device *bigben = data; + u8 right_motor_on; + u8 left_motor_force; + + if (effect->type != FF_RUMBLE) + return 0; + + right_motor_on = effect->u.rumble.weak_magnitude ? 1 : 0; + left_motor_force = effect->u.rumble.strong_magnitude / 256; + + if (right_motor_on != bigben->right_motor_on || + left_motor_force != bigben->left_motor_force) { + bigben->right_motor_on = right_motor_on; + bigben->left_motor_force = left_motor_force; + bigben->work_ff = true; + schedule_work(&bigben->worker); + } + + return 0; +} + +static void bigben_set_led(struct led_classdev *led, + enum led_brightness value) +{ + struct device *dev = led->dev->parent; + struct hid_device *hid = to_hid_device(dev); + struct bigben_device *bigben = hid_get_drvdata(hid); + int n; + bool work; + + if (!bigben) { + hid_err(hid, "no device data\n"); + return; + } + + for (n = 0; n < NUM_LEDS; n++) { + if (led == bigben->leds[n]) { + if (value == LED_OFF) { + work = (bigben->led_state & BIT(n)); + bigben->led_state &= ~BIT(n); + } else { + work = !(bigben->led_state & BIT(n)); + bigben->led_state |= BIT(n); + } + + if (work) { + bigben->work_led = true; + schedule_work(&bigben->worker); + } + return; + } + } +} + +static enum led_brightness bigben_get_led(struct led_classdev *led) +{ + struct device *dev = led->dev->parent; + struct hid_device *hid = to_hid_device(dev); + struct bigben_device *bigben = hid_get_drvdata(hid); + int n; + + if (!bigben) { + hid_err(hid, "no device data\n"); + return LED_OFF; + } + + for (n = 0; n < NUM_LEDS; n++) { + if (led == bigben->leds[n]) + return (bigben->led_state & BIT(n)) ? LED_ON : LED_OFF; + } + + return LED_OFF; +} + +static void bigben_remove(struct hid_device *hid) +{ + struct bigben_device *bigben = hid_get_drvdata(hid); + + cancel_work_sync(&bigben->worker); + hid_hw_close(hid); + hid_hw_stop(hid); +} + +static int bigben_probe(struct hid_device *hid, + const struct hid_device_id *id) +{ + struct bigben_device *bigben; + struct hid_input *hidinput; + struct list_head *report_list; + struct led_classdev *led; + char *name; + size_t name_sz; + int n, error; + + bigben = devm_kzalloc(&hid->dev, sizeof(*bigben), GFP_KERNEL); + if (!bigben) + return -ENOMEM; + hid_set_drvdata(hid, bigben); + bigben->hid = hid; + + error = hid_parse(hid); + if (error) { + hid_err(hid, "parse failed\n"); + return error; + } + + error = hid_hw_start(hid, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); + if (error) { + hid_err(hid, "hw start failed\n"); + return error; + } + + report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; + bigben->report = list_entry(report_list->next, + struct hid_report, list); + + hidinput = list_first_entry(&hid->inputs, struct hid_input, list); + set_bit(FF_RUMBLE, hidinput->input->ffbit); + + INIT_WORK(&bigben->worker, bigben_worker); + + error = input_ff_create_memless(hidinput->input, bigben, + hid_bigben_play_effect); + if (error) + return error; + + name_sz = strlen(dev_name(&hid->dev)) + strlen(":red:bigben#") + 1; + + for (n = 0; n < NUM_LEDS; n++) { + led = devm_kzalloc( + &hid->dev, + sizeof(struct led_classdev) + name_sz, + GFP_KERNEL + ); + if (!led) + return -ENOMEM; + name = (void *)(&led[1]); + snprintf(name, name_sz, + "%s:red:bigben%d", + dev_name(&hid->dev), n + 1 + ); + led->name = name; + led->brightness = (n == 0) ? LED_ON : LED_OFF; + led->max_brightness = 1; + led->brightness_get = bigben_get_led; + led->brightness_set = bigben_set_led; + bigben->leds[n] = led; + error = devm_led_classdev_register(&hid->dev, led); + if (error) + return error; + } + + /* initial state: LED1 is on, no rumble effect */ + bigben->led_state = BIT(0); + bigben->right_motor_on = 0; + bigben->left_motor_force = 0; + bigben->work_led = true; + bigben->work_ff = true; + schedule_work(&bigben->worker); + + hid_info(hid, "LED and force feedback support for BigBen gamepad\n"); + + return 0; +} + +static __u8 *bigben_report_fixup(struct hid_device *hid, __u8 *rdesc, + unsigned int *rsize) +{ + if (*rsize == PID0902_RDESC_ORIG_SIZE) { + rdesc = pid0902_rdesc_fixed; + *rsize = sizeof(pid0902_rdesc_fixed); + } else + hid_warn(hid, "unexpected rdesc, please submit for review\n"); + return rdesc; +} + +static const struct hid_device_id bigben_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_BIGBEN, USB_DEVICE_ID_BIGBEN_PS3OFMINIPAD) }, + { } +}; +MODULE_DEVICE_TABLE(hid, bigben_devices); + +static struct hid_driver bigben_driver = { + .name = "bigben", + .id_table = bigben_devices, + .probe = bigben_probe, + .report_fixup = bigben_report_fixup, + .remove = bigben_remove, +}; +module_hid_driver(bigben_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 5146ee029db4b..1ff9b3fdcf462 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -229,6 +229,9 @@ #define USB_VENDOR_ID_BETOP_2185V2PC 0x8380 #define USB_VENDOR_ID_BETOP_2185V2BFM 0x20bc +#define USB_VENDOR_ID_BIGBEN 0x146b +#define USB_DEVICE_ID_BIGBEN_PS3OFMINIPAD 0x0902 + #define USB_VENDOR_ID_BTC 0x046e #define USB_DEVICE_ID_BTC_EMPREX_REMOTE 0x5578 #define USB_DEVICE_ID_BTC_EMPREX_REMOTE_2 0x5577 From 9ee3e06610fdb8a601cde59c92089fb6c1deb4aa Mon Sep 17 00:00:00 2001 From: Julian Sax Date: Wed, 19 Sep 2018 11:46:23 +0200 Subject: [PATCH 26/35] HID: i2c-hid: override HID descriptors for certain devices A particular touchpad (SIPODEV SP1064) refuses to supply the HID descriptors. This patch provides the framework for overriding these descriptors based on DMI data. It also includes the descriptors for said touchpad, which were extracted by listening to the traffic of the windows filter driver, as well as the DMI data for the laptops known to use this device. Relevant Bug: https://bugzilla.redhat.com/show_bug.cgi?id=1526312 Cc: Hans de Goede Reported-and-tested-by: ahormann@gmx.net Reported-and-tested-by: Bruno Jesus Reported-and-tested-by: Dietrich Reported-and-tested-by: kloxdami@yahoo.com Signed-off-by: Julian Sax Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/Makefile | 3 + .../hid/i2c-hid/{i2c-hid.c => i2c-hid-core.c} | 60 ++- drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c | 376 ++++++++++++++++++ drivers/hid/i2c-hid/i2c-hid.h | 20 + 4 files changed, 439 insertions(+), 20 deletions(-) rename drivers/hid/i2c-hid/{i2c-hid.c => i2c-hid-core.c} (96%) create mode 100644 drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c create mode 100644 drivers/hid/i2c-hid/i2c-hid.h diff --git a/drivers/hid/i2c-hid/Makefile b/drivers/hid/i2c-hid/Makefile index 832d8f9aaba27..099e1ce2f2347 100644 --- a/drivers/hid/i2c-hid/Makefile +++ b/drivers/hid/i2c-hid/Makefile @@ -3,3 +3,6 @@ # obj-$(CONFIG_I2C_HID) += i2c-hid.o + +i2c-hid-objs = i2c-hid-core.o +i2c-hid-$(CONFIG_DMI) += i2c-hid-dmi-quirks.o diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid-core.c similarity index 96% rename from drivers/hid/i2c-hid/i2c-hid.c rename to drivers/hid/i2c-hid/i2c-hid-core.c index f3076659361ab..823c63ad08b1f 100644 --- a/drivers/hid/i2c-hid/i2c-hid.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -43,6 +43,7 @@ #include #include "../hid-ids.h" +#include "i2c-hid.h" /* quirks to control the device */ #define I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV BIT(0) @@ -669,6 +670,7 @@ static int i2c_hid_parse(struct hid_device *hid) char *rdesc; int ret; int tries = 3; + char *use_override; i2c_hid_dbg(ihid, "entering %s\n", __func__); @@ -687,26 +689,37 @@ static int i2c_hid_parse(struct hid_device *hid) if (ret) return ret; - rdesc = kzalloc(rsize, GFP_KERNEL); + use_override = i2c_hid_get_dmi_hid_report_desc_override(client->name, + &rsize); - 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; + if (use_override) { + rdesc = use_override; + i2c_hid_dbg(ihid, "Using a HID report descriptor override\n"); + } else { + 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 (!use_override) + kfree(rdesc); + if (ret) { dbg_hid("parsing report descriptor failed\n"); return ret; @@ -833,12 +846,19 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid) int ret; /* i2c hid fetch using a fixed descriptor size (30 bytes) */ - i2c_hid_dbg(ihid, "Fetching the HID descriptor\n"); - ret = i2c_hid_command(client, &hid_descr_cmd, ihid->hdesc_buffer, - sizeof(struct i2c_hid_desc)); - if (ret) { - dev_err(&client->dev, "hid_descr_cmd failed\n"); - return -ENODEV; + if (i2c_hid_get_dmi_i2c_hid_desc_override(client->name)) { + i2c_hid_dbg(ihid, "Using a HID descriptor override\n"); + ihid->hdesc = + *i2c_hid_get_dmi_i2c_hid_desc_override(client->name); + } else { + i2c_hid_dbg(ihid, "Fetching the HID descriptor\n"); + ret = i2c_hid_command(client, &hid_descr_cmd, + ihid->hdesc_buffer, + sizeof(struct i2c_hid_desc)); + if (ret) { + dev_err(&client->dev, "hid_descr_cmd failed\n"); + return -ENODEV; + } } /* Validate the length of HID descriptor, the 4 first bytes: diff --git a/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c b/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c new file mode 100644 index 0000000000000..1d645c9ab417b --- /dev/null +++ b/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Quirks for I2C-HID devices that do not supply proper descriptors + * + * Copyright (c) 2018 Julian Sax + * + */ + +#include +#include +#include + +#include "i2c-hid.h" + + +struct i2c_hid_desc_override { + union { + struct i2c_hid_desc *i2c_hid_desc; + uint8_t *i2c_hid_desc_buffer; + }; + uint8_t *hid_report_desc; + unsigned int hid_report_desc_size; + uint8_t *i2c_name; +}; + + +/* + * descriptors for the SIPODEV SP1064 touchpad + * + * This device does not supply any descriptors and on windows a filter + * driver operates between the i2c-hid layer and the device and injects + * these descriptors when the device is prompted. The descriptors were + * extracted by listening to the i2c-hid traffic that occurs between the + * windows filter driver and the windows i2c-hid driver. + */ + +static const struct i2c_hid_desc_override sipodev_desc = { + .i2c_hid_desc_buffer = (uint8_t []) + {0x1e, 0x00, /* Length of descriptor */ + 0x00, 0x01, /* Version of descriptor */ + 0xdb, 0x01, /* Length of report descriptor */ + 0x21, 0x00, /* Location of report descriptor */ + 0x24, 0x00, /* Location of input report */ + 0x1b, 0x00, /* Max input report length */ + 0x25, 0x00, /* Location of output report */ + 0x11, 0x00, /* Max output report length */ + 0x22, 0x00, /* Location of command register */ + 0x23, 0x00, /* Location of data register */ + 0x11, 0x09, /* Vendor ID */ + 0x88, 0x52, /* Product ID */ + 0x06, 0x00, /* Version ID */ + 0x00, 0x00, 0x00, 0x00 /* Reserved */ + }, + + .hid_report_desc = (uint8_t []) + {0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x02, /* Usage (Mouse), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x01, /* Report ID (1), */ + 0x09, 0x01, /* Usage (Pointer), */ + 0xA1, 0x00, /* Collection (Physical), */ + 0x05, 0x09, /* Usage Page (Button), */ + 0x19, 0x01, /* Usage Minimum (01h), */ + 0x29, 0x02, /* Usage Maximum (02h), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x02, /* Report Count (2), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x06, /* Report Count (6), */ + 0x81, 0x01, /* Input (Constant), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x30, /* Usage (X), */ + 0x09, 0x31, /* Usage (Y), */ + 0x15, 0x81, /* Logical Minimum (-127), */ + 0x25, 0x7F, /* Logical Maximum (127), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x02, /* Report Count (2), */ + 0x81, 0x06, /* Input (Variable, Relative), */ + 0xC0, /* End Collection, */ + 0xC0, /* End Collection, */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x05, /* Usage (Touchpad), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x04, /* Report ID (4), */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x22, /* Usage (Finger), */ + 0xA1, 0x02, /* Collection (Logical), */ + 0x15, 0x00, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x09, 0x47, /* Usage (Touch Valid), */ + 0x09, 0x42, /* Usage (Tip Switch), */ + 0x95, 0x02, /* Report Count (2), */ + 0x75, 0x01, /* Report Size (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x01, /* Report Count (1), */ + 0x75, 0x03, /* Report Size (3), */ + 0x25, 0x05, /* Logical Maximum (5), */ + 0x09, 0x51, /* Usage (Contact Identifier), */ + 0x81, 0x02, /* Input (Variable), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x26, 0x44, 0x0A, /* Logical Maximum (2628), */ + 0x75, 0x10, /* Report Size (16), */ + 0x55, 0x0E, /* Unit Exponent (14), */ + 0x65, 0x11, /* Unit (Centimeter), */ + 0x09, 0x30, /* Usage (X), */ + 0x46, 0x1A, 0x04, /* Physical Maximum (1050), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x46, 0xBC, 0x02, /* Physical Maximum (700), */ + 0x26, 0x34, 0x05, /* Logical Maximum (1332), */ + 0x09, 0x31, /* Usage (Y), */ + 0x81, 0x02, /* Input (Variable), */ + 0xC0, /* End Collection, */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x22, /* Usage (Finger), */ + 0xA1, 0x02, /* Collection (Logical), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x09, 0x47, /* Usage (Touch Valid), */ + 0x09, 0x42, /* Usage (Tip Switch), */ + 0x95, 0x02, /* Report Count (2), */ + 0x75, 0x01, /* Report Size (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x01, /* Report Count (1), */ + 0x75, 0x03, /* Report Size (3), */ + 0x25, 0x05, /* Logical Maximum (5), */ + 0x09, 0x51, /* Usage (Contact Identifier), */ + 0x81, 0x02, /* Input (Variable), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x26, 0x44, 0x0A, /* Logical Maximum (2628), */ + 0x75, 0x10, /* Report Size (16), */ + 0x09, 0x30, /* Usage (X), */ + 0x46, 0x1A, 0x04, /* Physical Maximum (1050), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x46, 0xBC, 0x02, /* Physical Maximum (700), */ + 0x26, 0x34, 0x05, /* Logical Maximum (1332), */ + 0x09, 0x31, /* Usage (Y), */ + 0x81, 0x02, /* Input (Variable), */ + 0xC0, /* End Collection, */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x22, /* Usage (Finger), */ + 0xA1, 0x02, /* Collection (Logical), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x09, 0x47, /* Usage (Touch Valid), */ + 0x09, 0x42, /* Usage (Tip Switch), */ + 0x95, 0x02, /* Report Count (2), */ + 0x75, 0x01, /* Report Size (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x01, /* Report Count (1), */ + 0x75, 0x03, /* Report Size (3), */ + 0x25, 0x05, /* Logical Maximum (5), */ + 0x09, 0x51, /* Usage (Contact Identifier), */ + 0x81, 0x02, /* Input (Variable), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x26, 0x44, 0x0A, /* Logical Maximum (2628), */ + 0x75, 0x10, /* Report Size (16), */ + 0x09, 0x30, /* Usage (X), */ + 0x46, 0x1A, 0x04, /* Physical Maximum (1050), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x46, 0xBC, 0x02, /* Physical Maximum (700), */ + 0x26, 0x34, 0x05, /* Logical Maximum (1332), */ + 0x09, 0x31, /* Usage (Y), */ + 0x81, 0x02, /* Input (Variable), */ + 0xC0, /* End Collection, */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x22, /* Usage (Finger), */ + 0xA1, 0x02, /* Collection (Logical), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x09, 0x47, /* Usage (Touch Valid), */ + 0x09, 0x42, /* Usage (Tip Switch), */ + 0x95, 0x02, /* Report Count (2), */ + 0x75, 0x01, /* Report Size (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x01, /* Report Count (1), */ + 0x75, 0x03, /* Report Size (3), */ + 0x25, 0x05, /* Logical Maximum (5), */ + 0x09, 0x51, /* Usage (Contact Identifier), */ + 0x81, 0x02, /* Input (Variable), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x26, 0x44, 0x0A, /* Logical Maximum (2628), */ + 0x75, 0x10, /* Report Size (16), */ + 0x09, 0x30, /* Usage (X), */ + 0x46, 0x1A, 0x04, /* Physical Maximum (1050), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x46, 0xBC, 0x02, /* Physical Maximum (700), */ + 0x26, 0x34, 0x05, /* Logical Maximum (1332), */ + 0x09, 0x31, /* Usage (Y), */ + 0x81, 0x02, /* Input (Variable), */ + 0xC0, /* End Collection, */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x55, 0x0C, /* Unit Exponent (12), */ + 0x66, 0x01, 0x10, /* Unit (Seconds), */ + 0x47, 0xFF, 0xFF, 0x00, 0x00,/* Physical Maximum (65535), */ + 0x27, 0xFF, 0xFF, 0x00, 0x00,/* Logical Maximum (65535), */ + 0x75, 0x10, /* Report Size (16), */ + 0x95, 0x01, /* Report Count (1), */ + 0x09, 0x56, /* Usage (Scan Time), */ + 0x81, 0x02, /* Input (Variable), */ + 0x09, 0x54, /* Usage (Contact Count), */ + 0x25, 0x7F, /* Logical Maximum (127), */ + 0x75, 0x08, /* Report Size (8), */ + 0x81, 0x02, /* Input (Variable), */ + 0x05, 0x09, /* Usage Page (Button), */ + 0x09, 0x01, /* Usage (01h), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x07, /* Report Count (7), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x85, 0x02, /* Report ID (2), */ + 0x09, 0x55, /* Usage (Contact Count Maximum), */ + 0x09, 0x59, /* Usage (59h), */ + 0x75, 0x04, /* Report Size (4), */ + 0x95, 0x02, /* Report Count (2), */ + 0x25, 0x0F, /* Logical Maximum (15), */ + 0xB1, 0x02, /* Feature (Variable), */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x85, 0x07, /* Report ID (7), */ + 0x09, 0x60, /* Usage (60h), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x01, /* Report Count (1), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0xB1, 0x02, /* Feature (Variable), */ + 0x95, 0x07, /* Report Count (7), */ + 0xB1, 0x03, /* Feature (Constant, Variable), */ + 0x85, 0x06, /* Report ID (6), */ + 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ + 0x09, 0xC5, /* Usage (C5h), */ + 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ + 0x75, 0x08, /* Report Size (8), */ + 0x96, 0x00, 0x01, /* Report Count (256), */ + 0xB1, 0x02, /* Feature (Variable), */ + 0xC0, /* End Collection, */ + 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ + 0x09, 0x01, /* Usage (01h), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x0D, /* Report ID (13), */ + 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ + 0x19, 0x01, /* Usage Minimum (01h), */ + 0x29, 0x02, /* Usage Maximum (02h), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x02, /* Report Count (2), */ + 0xB1, 0x02, /* Feature (Variable), */ + 0xC0, /* End Collection, */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x0E, /* Usage (Configuration), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x03, /* Report ID (3), */ + 0x09, 0x22, /* Usage (Finger), */ + 0xA1, 0x02, /* Collection (Logical), */ + 0x09, 0x52, /* Usage (Device Mode), */ + 0x25, 0x0A, /* Logical Maximum (10), */ + 0x95, 0x01, /* Report Count (1), */ + 0xB1, 0x02, /* Feature (Variable), */ + 0xC0, /* End Collection, */ + 0x09, 0x22, /* Usage (Finger), */ + 0xA1, 0x00, /* Collection (Physical), */ + 0x85, 0x05, /* Report ID (5), */ + 0x09, 0x57, /* Usage (57h), */ + 0x09, 0x58, /* Usage (58h), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x02, /* Report Count (2), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0xB1, 0x02, /* Feature (Variable), */ + 0x95, 0x06, /* Report Count (6), */ + 0xB1, 0x03, /* Feature (Constant, Variable),*/ + 0xC0, /* End Collection, */ + 0xC0 /* End Collection */ + }, + .hid_report_desc_size = 475, + .i2c_name = "SYNA3602:00" +}; + + +static const struct dmi_system_id i2c_hid_dmi_desc_override_table[] = { + { + .ident = "Teclast F6 Pro", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TECLAST"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "F6 Pro"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "Teclast F7", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TECLAST"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "F7"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "Trekstor Primebook C13", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TREKSTOR"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Primebook C13"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "Trekstor Primebook C11", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TREKSTOR"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Primebook C11"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "Direkt-Tek DTLAPY116-2", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Direkt-Tek"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "DTLAPY116-2"), + }, + .driver_data = (void *)&sipodev_desc + }, + { + .ident = "Mediacom Flexbook Edge 11", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "MEDIACOM"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "FlexBook edge11 - M-FBE11"), + }, + .driver_data = (void *)&sipodev_desc + } +}; + + +struct i2c_hid_desc *i2c_hid_get_dmi_i2c_hid_desc_override(uint8_t *i2c_name) +{ + struct i2c_hid_desc_override *override; + const struct dmi_system_id *system_id; + + system_id = dmi_first_match(i2c_hid_dmi_desc_override_table); + if (!system_id) + return NULL; + + override = system_id->driver_data; + if (strcmp(override->i2c_name, i2c_name)) + return NULL; + + return override->i2c_hid_desc; +} + +char *i2c_hid_get_dmi_hid_report_desc_override(uint8_t *i2c_name, + unsigned int *size) +{ + struct i2c_hid_desc_override *override; + const struct dmi_system_id *system_id; + + system_id = dmi_first_match(i2c_hid_dmi_desc_override_table); + if (!system_id) + return NULL; + + override = system_id->driver_data; + if (strcmp(override->i2c_name, i2c_name)) + return NULL; + + *size = override->hid_report_desc_size; + return override->hid_report_desc; +} diff --git a/drivers/hid/i2c-hid/i2c-hid.h b/drivers/hid/i2c-hid/i2c-hid.h new file mode 100644 index 0000000000000..a8c19aef5824c --- /dev/null +++ b/drivers/hid/i2c-hid/i2c-hid.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef I2C_HID_H +#define I2C_HID_H + + +#ifdef CONFIG_DMI +struct i2c_hid_desc *i2c_hid_get_dmi_i2c_hid_desc_override(uint8_t *i2c_name); +char *i2c_hid_get_dmi_hid_report_desc_override(uint8_t *i2c_name, + unsigned int *size); +#else +static inline struct i2c_hid_desc + *i2c_hid_get_dmi_i2c_hid_desc_override(uint8_t *i2c_name) +{ return NULL; } +static inline char *i2c_hid_get_dmi_hid_report_desc_override(uint8_t *i2c_name, + unsigned int *size) +{ return NULL; } +#endif + +#endif From 9d7b18668956c411a422d04c712994c5fdb23a4b Mon Sep 17 00:00:00 2001 From: Sean O'Brien Date: Tue, 2 Oct 2018 15:53:37 -0700 Subject: [PATCH 27/35] HID: magicmouse: add support for Apple Magic Trackpad 2 USB device Vendor 05ac (Apple) Device 0265 (Magic Trackpad 2) Bluetooth device Vendor 004c (Apple) Device 0265 (Magic Trackpad 2) Add support for Apple Magic Trackpad 2 over USB and bluetooth, putting the device in multi-touch mode. Signed-off-by: Claudio Mettler Signed-off-by: Marek Wyborski Signed-off-by: Sean O'Brien Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-magicmouse.c | 142 ++++++++++++++++++++++++++++++++--- 2 files changed, 132 insertions(+), 11 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 5146ee029db4b..bb0cd212c7cca 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -92,6 +92,7 @@ #define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304 #define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d #define USB_DEVICE_ID_APPLE_MAGICTRACKPAD 0x030e +#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 0x0265 #define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI 0x020e #define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO 0x020f #define USB_DEVICE_ID_APPLE_GEYSER_ANSI 0x0214 diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index b454c43861578..1d5ea678d2687 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -54,6 +54,8 @@ module_param(report_undeciphered, bool, 0644); MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event"); #define TRACKPAD_REPORT_ID 0x28 +#define TRACKPAD2_USB_REPORT_ID 0x02 +#define TRACKPAD2_BT_REPORT_ID 0x31 #define MOUSE_REPORT_ID 0x29 #define DOUBLE_REPORT_ID 0xf7 /* These definitions are not precise, but they're close enough. (Bits @@ -91,6 +93,17 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie #define TRACKPAD_RES_Y \ ((TRACKPAD_MAX_Y - TRACKPAD_MIN_Y) / (TRACKPAD_DIMENSION_Y / 100)) +#define TRACKPAD2_DIMENSION_X (float)16000 +#define TRACKPAD2_MIN_X -3678 +#define TRACKPAD2_MAX_X 3934 +#define TRACKPAD2_RES_X \ + ((TRACKPAD2_MAX_X - TRACKPAD2_MIN_X) / (TRACKPAD2_DIMENSION_X / 100)) +#define TRACKPAD2_DIMENSION_Y (float)11490 +#define TRACKPAD2_MIN_Y -2478 +#define TRACKPAD2_MAX_Y 2587 +#define TRACKPAD2_RES_Y \ + ((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100)) + /** * struct magicmouse_sc - Tracks Magic Mouse-specific data. * @input: Input device through which we report events. @@ -183,6 +196,7 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda { struct input_dev *input = msc->input; int id, x, y, size, orientation, touch_major, touch_minor, state, down; + int pressure = 0; if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) { id = (tdata[6] << 2 | tdata[5] >> 6) & 0xf; @@ -194,6 +208,17 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda touch_minor = tdata[4]; state = tdata[7] & TOUCH_STATE_MASK; down = state != TOUCH_STATE_NONE; + } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) { + id = tdata[8] & 0xf; + x = (tdata[1] << 27 | tdata[0] << 19) >> 19; + y = -((tdata[3] << 30 | tdata[2] << 22 | tdata[1] << 14) >> 19); + size = tdata[6]; + orientation = (tdata[8] >> 5) - 4; + touch_major = tdata[4]; + touch_minor = tdata[5]; + pressure = tdata[7]; + state = tdata[3] & 0xC0; + down = state == 0x80; } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ id = (tdata[7] << 2 | tdata[6] >> 6) & 0xf; x = (tdata[1] << 27 | tdata[0] << 19) >> 19; @@ -215,7 +240,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda /* If requested, emulate a scroll wheel by detecting small * vertical touch motions. */ - if (emulate_scroll_wheel) { + if (emulate_scroll_wheel && (input->id.product != + USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)) { unsigned long now = jiffies; int step_x = msc->touches[id].scroll_x - x; int step_y = msc->touches[id].scroll_y - y; @@ -269,10 +295,14 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda input_report_abs(input, ABS_MT_POSITION_X, x); input_report_abs(input, ABS_MT_POSITION_Y, y); + if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) + input_report_abs(input, ABS_MT_PRESSURE, pressure); + if (report_undeciphered) { if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) input_event(input, EV_MSC, MSC_RAW, tdata[7]); - else /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ + else if (input->id.product != + USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) input_event(input, EV_MSC, MSC_RAW, tdata[8]); } } @@ -287,6 +317,7 @@ static int magicmouse_raw_event(struct hid_device *hdev, switch (data[0]) { case TRACKPAD_REPORT_ID: + case TRACKPAD2_BT_REPORT_ID: /* Expect four bytes of prefix, and N*9 bytes of touch data. */ if (size < 4 || ((size - 4) % 9) != 0) return 0; @@ -308,6 +339,22 @@ static int magicmouse_raw_event(struct hid_device *hdev, * ts = data[1] >> 6 | data[2] << 2 | data[3] << 10; */ break; + case TRACKPAD2_USB_REPORT_ID: + /* Expect twelve bytes of prefix and N*9 bytes of touch data. */ + if (size < 12 || ((size - 12) % 9) != 0) + return 0; + npoints = (size - 12) / 9; + if (npoints > 15) { + hid_warn(hdev, "invalid size value (%d) for TRACKPAD2_USB_REPORT_ID\n", + size); + return 0; + } + msc->ntouches = 0; + for (ii = 0; ii < npoints; ii++) + magicmouse_emit_touch(msc, ii, data + ii * 9 + 12); + + clicks = data[1]; + break; case MOUSE_REPORT_ID: /* Expect six bytes of prefix, and N*8 bytes of touch data. */ if (size < 6 || ((size - 6) % 8) != 0) @@ -352,6 +399,9 @@ static int magicmouse_raw_event(struct hid_device *hdev, magicmouse_emit_buttons(msc, clicks & 3); input_report_rel(input, REL_X, x); input_report_rel(input, REL_Y, y); + } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) { + input_mt_sync_frame(input); + input_report_key(input, BTN_MOUSE, clicks & 1); } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ input_report_key(input, BTN_MOUSE, clicks & 1); input_mt_report_pointer_emulation(input, true); @@ -364,6 +414,7 @@ static int magicmouse_raw_event(struct hid_device *hdev, static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev) { int error; + int mt_flags = 0; __set_bit(EV_KEY, input->evbit); @@ -380,6 +431,22 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd __set_bit(REL_WHEEL, input->relbit); __set_bit(REL_HWHEEL, input->relbit); } + } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) { + /* setting the device name to ensure the same driver settings + * get loaded, whether connected through bluetooth or USB + */ + input->name = "Apple Inc. Magic Trackpad 2"; + + __clear_bit(EV_MSC, input->evbit); + __clear_bit(BTN_0, input->keybit); + __clear_bit(BTN_RIGHT, input->keybit); + __clear_bit(BTN_MIDDLE, input->keybit); + __set_bit(BTN_MOUSE, input->keybit); + __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); + __set_bit(BTN_TOOL_FINGER, input->keybit); + + mt_flags = INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED | + INPUT_MT_TRACK; } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ /* input->keybit is initialized with incorrect button info * for Magic Trackpad. There really is only one physical @@ -402,14 +469,13 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd __set_bit(EV_ABS, input->evbit); - error = input_mt_init_slots(input, 16, 0); + error = input_mt_init_slots(input, 16, mt_flags); if (error) return error; input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2, 4, 0); input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255 << 2, 4, 0); - input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0); /* Note: Touch Y position from the device is inverted relative * to how pointer motion is reported (and relative to how USB @@ -418,6 +484,7 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd * inverse of the reported Y. */ if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) { + input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0); input_set_abs_params(input, ABS_MT_POSITION_X, MOUSE_MIN_X, MOUSE_MAX_X, 4, 0); input_set_abs_params(input, ABS_MT_POSITION_Y, @@ -427,7 +494,25 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd MOUSE_RES_X); input_abs_set_res(input, ABS_MT_POSITION_Y, MOUSE_RES_Y); + } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) { + input_set_abs_params(input, ABS_MT_PRESSURE, 0, 253, 0, 0); + input_set_abs_params(input, ABS_PRESSURE, 0, 253, 0, 0); + input_set_abs_params(input, ABS_MT_ORIENTATION, -3, 4, 0, 0); + input_set_abs_params(input, ABS_X, TRACKPAD2_MIN_X, + TRACKPAD2_MAX_X, 0, 0); + input_set_abs_params(input, ABS_Y, TRACKPAD2_MIN_Y, + TRACKPAD2_MAX_Y, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_X, + TRACKPAD2_MIN_X, TRACKPAD2_MAX_X, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, + TRACKPAD2_MIN_Y, TRACKPAD2_MAX_Y, 0, 0); + + input_abs_set_res(input, ABS_X, TRACKPAD2_RES_X); + input_abs_set_res(input, ABS_Y, TRACKPAD2_RES_Y); + input_abs_set_res(input, ABS_MT_POSITION_X, TRACKPAD2_RES_X); + input_abs_set_res(input, ABS_MT_POSITION_Y, TRACKPAD2_RES_Y); } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ + input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0); input_set_abs_params(input, ABS_X, TRACKPAD_MIN_X, TRACKPAD_MAX_X, 4, 0); input_set_abs_params(input, ABS_Y, TRACKPAD_MIN_Y, @@ -447,7 +532,8 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd input_set_events_per_packet(input, 60); - if (report_undeciphered) { + if (report_undeciphered && + input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) { __set_bit(EV_MSC, input->evbit); __set_bit(MSC_RAW, input->mscbit); } @@ -465,7 +551,8 @@ static int magicmouse_input_mapping(struct hid_device *hdev, msc->input = hi->input; /* Magic Trackpad does not give relative data after switching to MT */ - if (hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD && + if ((hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD || + hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) && field->flags & HID_MAIN_ITEM_RELATIVE) return -1; @@ -494,11 +581,20 @@ static int magicmouse_input_configured(struct hid_device *hdev, static int magicmouse_probe(struct hid_device *hdev, const struct hid_device_id *id) { - const u8 feature[] = { 0xd7, 0x01 }; + const u8 *feature; + const u8 feature_mt[] = { 0xD7, 0x01 }; + const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 }; + const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 }; u8 *buf; struct magicmouse_sc *msc; struct hid_report *report; int ret; + int feature_size; + + if (id->vendor == USB_VENDOR_ID_APPLE && + id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 && + hdev->type != HID_TYPE_USBMOUSE) + return 0; msc = devm_kzalloc(&hdev->dev, sizeof(*msc), GFP_KERNEL); if (msc == NULL) { @@ -532,7 +628,14 @@ static int magicmouse_probe(struct hid_device *hdev, if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE) report = hid_register_report(hdev, HID_INPUT_REPORT, MOUSE_REPORT_ID, 0); - else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ + else if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) { + if (id->vendor == BT_VENDOR_ID_APPLE) + report = hid_register_report(hdev, HID_INPUT_REPORT, + TRACKPAD2_BT_REPORT_ID, 0); + else /* USB_VENDOR_ID_APPLE */ + report = hid_register_report(hdev, HID_INPUT_REPORT, + TRACKPAD2_USB_REPORT_ID, 0); + } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ report = hid_register_report(hdev, HID_INPUT_REPORT, TRACKPAD_REPORT_ID, 0); report = hid_register_report(hdev, HID_INPUT_REPORT, @@ -546,7 +649,20 @@ static int magicmouse_probe(struct hid_device *hdev, } report->size = 6; - buf = kmemdup(feature, sizeof(feature), GFP_KERNEL); + if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) { + if (id->vendor == BT_VENDOR_ID_APPLE) { + feature_size = sizeof(feature_mt_trackpad2_bt); + feature = feature_mt_trackpad2_bt; + } else { /* USB_VENDOR_ID_APPLE */ + feature_size = sizeof(feature_mt_trackpad2_usb); + feature = feature_mt_trackpad2_usb; + } + } else { + feature_size = sizeof(feature_mt); + feature = feature_mt; + } + + buf = kmemdup(feature, feature_size, GFP_KERNEL); if (!buf) { ret = -ENOMEM; goto err_stop_hw; @@ -560,10 +676,10 @@ static int magicmouse_probe(struct hid_device *hdev, * but there seems to be no other way of switching the mode. * Thus the super-ugly hacky success check below. */ - ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(feature), + ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); kfree(buf); - if (ret != -EIO && ret != sizeof(feature)) { + if (ret != -EIO && ret != feature_size) { hid_err(hdev, "unable to request touch data (%d)\n", ret); goto err_stop_hw; } @@ -579,6 +695,10 @@ static const struct hid_device_id magic_mice[] = { USB_DEVICE_ID_APPLE_MAGICMOUSE), .driver_data = 0 }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICTRACKPAD), .driver_data = 0 }, + { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, + USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, + USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 }, { } }; MODULE_DEVICE_TABLE(hid, magic_mice); From d9ca1c990a7ffee7e68ab8d64efacd6c73103203 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 4 Oct 2018 14:34:30 +0200 Subject: [PATCH 28/35] Input: reserve 2 events code because of HID Prior to commit 190d7f02ce8e ("HID: input: do not increment usages when a duplicate is found") from the v4.18 kernel, HID used to shift the event codes if a duplicate usage was found. This ended up in a situation where a device would export a ton of ABS_MISC+n event codes, or a ton of REL_MISC+n event codes. This is now fixed, however userspace needs to detect those situation. Fortunately, ABS_MT_SLOT-1 (ABS_MISC+6) was never assigned a code, and so libinput can detect fake multitouch devices from genuine ones by checking if ABS_MT_SLOT-1 is set. Now that we have REL_WHEEL_HI_RES, libinput won't be able to differentiate true high res mice from some other device in a pre-v4.18 kernel. Set in stone that the ABS_MISC+6 and REL_MISC+1 are reserved and should not be used so userspace can properly work around those old kernels. Signed-off-by: Benjamin Tissoires Acked-by: Dmitry Torokhov Signed-off-by: Jiri Kosina --- include/uapi/linux/input-event-codes.h | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h index dad8d3890a3a5..6d180cc60a5df 100644 --- a/include/uapi/linux/input-event-codes.h +++ b/include/uapi/linux/input-event-codes.h @@ -708,7 +708,15 @@ #define REL_DIAL 0x07 #define REL_WHEEL 0x08 #define REL_MISC 0x09 -#define REL_WHEEL_HI_RES 0x0a +/* + * 0x0a is reserved and should not be used in input drivers. + * It was used by HID as REL_MISC+1 and userspace needs to detect if + * the next REL_* event is correct or is just REL_MISC + n. + * We define here REL_RESERVED so userspace can rely on it and detect + * the situation described above. + */ +#define REL_RESERVED 0x0a +#define REL_WHEEL_HI_RES 0x0b #define REL_MAX 0x0f #define REL_CNT (REL_MAX+1) @@ -745,6 +753,15 @@ #define ABS_MISC 0x28 +/* + * 0x2e is reserved and should not be used in input drivers. + * It was used by HID as ABS_MISC+6 and userspace needs to detect if + * the next ABS_* event is correct or is just ABS_MISC + n. + * We define here ABS_RESERVED so userspace can rely on it and detect + * the situation described above. + */ +#define ABS_RESERVED 0x2e + #define ABS_MT_SLOT 0x2f /* MT slot being modified */ #define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */ #define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */ From 78d8f8f1a1800cf6068ffcac7a14cda19ae693d8 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 5 Oct 2018 11:41:13 -0700 Subject: [PATCH 29/35] mfd: cros: add "base attached" MKBP switch definition This adds a "base attached" switch definition to the MKBP protocol that is used by Whiskers driver to properly determine device state (clamshell vs tablet mode). Signed-off-by: Dmitry Torokhov Acked-by: Lee Jones Signed-off-by: Jiri Kosina --- include/linux/mfd/cros_ec_commands.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/mfd/cros_ec_commands.h b/include/linux/mfd/cros_ec_commands.h index 6e1ab9bead288..7690f07939652 100644 --- a/include/linux/mfd/cros_ec_commands.h +++ b/include/linux/mfd/cros_ec_commands.h @@ -2132,6 +2132,7 @@ struct ec_response_get_next_event_v1 { /* Switches */ #define EC_MKBP_LID_OPEN 0 #define EC_MKBP_TABLET_MODE 1 +#define EC_MKBP_BASE_ATTACHED 2 /*****************************************************************************/ /* Temperature sensor commands */ From eb1aac4c8744f75460c34d71b0c73bebf3e8ee5c Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 5 Oct 2018 11:41:14 -0700 Subject: [PATCH 30/35] HID: google: add support tablet mode switch for Whiskers Whiskers is a foldable base, and thus requires combining "base presence" signal coming from EC with base state signal (folded/unfolded) coming from USB/HID interface to produce proper SW_TABLET_MODE event. Signed-off-by: Nicolas Boichat Signed-off-by: Dmitry Torokhov Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-google-hammer.c | 413 ++++++++++++++++++++++++++++++-- 1 file changed, 394 insertions(+), 19 deletions(-) diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index 6bf4da7ad63a5..de3e7d055ca4c 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -13,16 +13,268 @@ * any later version. */ +#include #include #include +#include +#include #include +#include +#include +#include #include "hid-ids.h" -#define MAX_BRIGHTNESS 100 +/* + * C(hrome)B(ase)A(ttached)S(witch) - switch exported by Chrome EC and reporting + * state of the "Whiskers" base - attached or detached. Whiskers USB device also + * reports position of the keyboard - folded or not. Combining base state and + * position allows us to generate proper "Tablet mode" events. + */ +struct cbas_ec { + struct device *dev; /* The platform device (EC) */ + struct input_dev *input; + bool base_present; + struct notifier_block notifier; +}; -/* HID usage for keyboard backlight (Alphanumeric display brightness) */ -#define HID_AD_BRIGHTNESS 0x00140046 +static struct cbas_ec cbas_ec; +static DEFINE_SPINLOCK(cbas_ec_lock); +static DEFINE_MUTEX(cbas_ec_reglock); + +static bool cbas_parse_base_state(const void *data) +{ + u32 switches = get_unaligned_le32(data); + + return !!(switches & BIT(EC_MKBP_BASE_ATTACHED)); +} + +static int cbas_ec_query_base(struct cros_ec_device *ec_dev, bool get_state, + bool *state) +{ + struct ec_params_mkbp_info *params; + struct cros_ec_command *msg; + int ret; + + msg = kzalloc(sizeof(*msg) + max(sizeof(u32), sizeof(*params)), + GFP_KERNEL); + if (!msg) + return -ENOMEM; + + msg->command = EC_CMD_MKBP_INFO; + msg->version = 1; + msg->outsize = sizeof(*params); + msg->insize = sizeof(u32); + params = (struct ec_params_mkbp_info *)msg->data; + params->info_type = get_state ? + EC_MKBP_INFO_CURRENT : EC_MKBP_INFO_SUPPORTED; + params->event_type = EC_MKBP_EVENT_SWITCH; + + ret = cros_ec_cmd_xfer_status(ec_dev, msg); + if (ret >= 0) { + if (ret != sizeof(u32)) { + dev_warn(ec_dev->dev, "wrong result size: %d != %zu\n", + ret, sizeof(u32)); + ret = -EPROTO; + } else { + *state = cbas_parse_base_state(msg->data); + ret = 0; + } + } + + kfree(msg); + + return ret; +} + +static int cbas_ec_notify(struct notifier_block *nb, + unsigned long queued_during_suspend, + void *_notify) +{ + struct cros_ec_device *ec = _notify; + unsigned long flags; + bool base_present; + + if (ec->event_data.event_type == EC_MKBP_EVENT_SWITCH) { + base_present = cbas_parse_base_state( + &ec->event_data.data.switches); + dev_dbg(cbas_ec.dev, + "%s: base: %d\n", __func__, base_present); + + if (device_may_wakeup(cbas_ec.dev) || + !queued_during_suspend) { + + pm_wakeup_event(cbas_ec.dev, 0); + + spin_lock_irqsave(&cbas_ec_lock, flags); + + /* + * While input layer dedupes the events, we do not want + * to disrupt the state reported by the base by + * overriding it with state reported by the LID. Only + * report changes, as we assume that on attach the base + * is not folded. + */ + if (base_present != cbas_ec.base_present) { + input_report_switch(cbas_ec.input, + SW_TABLET_MODE, + !base_present); + input_sync(cbas_ec.input); + cbas_ec.base_present = base_present; + } + + spin_unlock_irqrestore(&cbas_ec_lock, flags); + } + } + + return NOTIFY_OK; +} + +static __maybe_unused int cbas_ec_resume(struct device *dev) +{ + struct cros_ec_device *ec = dev_get_drvdata(dev->parent); + bool base_present; + int error; + + error = cbas_ec_query_base(ec, true, &base_present); + if (error) { + dev_warn(dev, "failed to fetch base state on resume: %d\n", + error); + } else { + spin_lock_irq(&cbas_ec_lock); + + cbas_ec.base_present = base_present; + + /* + * Only report if base is disconnected. If base is connected, + * it will resend its state on resume, and we'll update it + * in hammer_event(). + */ + if (!cbas_ec.base_present) { + input_report_switch(cbas_ec.input, SW_TABLET_MODE, 1); + input_sync(cbas_ec.input); + } + + spin_unlock_irq(&cbas_ec_lock); + } + + return 0; +} + +static const SIMPLE_DEV_PM_OPS(cbas_ec_pm_ops, NULL, cbas_ec_resume); + +static void cbas_ec_set_input(struct input_dev *input) +{ + /* Take the lock so hammer_event() does not race with us here */ + spin_lock_irq(&cbas_ec_lock); + cbas_ec.input = input; + spin_unlock_irq(&cbas_ec_lock); +} + +static int __cbas_ec_probe(struct platform_device *pdev) +{ + struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); + struct input_dev *input; + bool base_supported; + int error; + + error = cbas_ec_query_base(ec, false, &base_supported); + if (error) + return error; + + if (!base_supported) + return -ENXIO; + + input = devm_input_allocate_device(&pdev->dev); + if (!input) + return -ENOMEM; + + input->name = "Whiskers Tablet Mode Switch"; + input->id.bustype = BUS_HOST; + + input_set_capability(input, EV_SW, SW_TABLET_MODE); + + error = input_register_device(input); + if (error) { + dev_err(&pdev->dev, "cannot register input device: %d\n", + error); + return error; + } + + /* Seed the state */ + error = cbas_ec_query_base(ec, true, &cbas_ec.base_present); + if (error) { + dev_err(&pdev->dev, "cannot query base state: %d\n", error); + return error; + } + + input_report_switch(input, SW_TABLET_MODE, !cbas_ec.base_present); + + cbas_ec_set_input(input); + + cbas_ec.dev = &pdev->dev; + cbas_ec.notifier.notifier_call = cbas_ec_notify; + error = blocking_notifier_chain_register(&ec->event_notifier, + &cbas_ec.notifier); + if (error) { + dev_err(&pdev->dev, "cannot register notifier: %d\n", error); + cbas_ec_set_input(NULL); + return error; + } + + device_init_wakeup(&pdev->dev, true); + return 0; +} + +static int cbas_ec_probe(struct platform_device *pdev) +{ + int retval; + + mutex_lock(&cbas_ec_reglock); + + if (cbas_ec.input) { + retval = -EBUSY; + goto out; + } + + retval = __cbas_ec_probe(pdev); + +out: + mutex_unlock(&cbas_ec_reglock); + return retval; +} + +static int cbas_ec_remove(struct platform_device *pdev) +{ + struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); + + mutex_lock(&cbas_ec_reglock); + + blocking_notifier_chain_unregister(&ec->event_notifier, + &cbas_ec.notifier); + cbas_ec_set_input(NULL); + + mutex_unlock(&cbas_ec_reglock); + return 0; +} + +static const struct acpi_device_id cbas_ec_acpi_ids[] = { + { "GOOG000B", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, cbas_ec_acpi_ids); + +static struct platform_driver cbas_ec_driver = { + .probe = cbas_ec_probe, + .remove = cbas_ec_remove, + .driver = { + .name = "cbas_ec", + .acpi_match_table = ACPI_PTR(cbas_ec_acpi_ids), + .pm = &cbas_ec_pm_ops, + }, +}; + +#define MAX_BRIGHTNESS 100 struct hammer_kbd_leds { struct led_classdev cdev; @@ -90,33 +342,130 @@ static int hammer_register_leds(struct hid_device *hdev) return devm_led_classdev_register(&hdev->dev, &kbd_backlight->cdev); } -static int hammer_input_configured(struct hid_device *hdev, - struct hid_input *hi) +#define HID_UP_GOOGLEVENDOR 0xffd10000 +#define HID_VD_KBD_FOLDED 0x00000019 +#define WHISKERS_KBD_FOLDED (HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED) + +/* HID usage for keyboard backlight (Alphanumeric display brightness) */ +#define HID_AD_BRIGHTNESS 0x00140046 + +static int hammer_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, + struct hid_usage *usage, + unsigned long **bit, int *max) { - struct list_head *report_list = - &hdev->report_enum[HID_OUTPUT_REPORT].report_list; + if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS && + usage->hid == WHISKERS_KBD_FOLDED) { + /* + * We do not want to have this usage mapped as it will get + * mixed in with "base attached" signal and delivered over + * separate input device for tablet switch mode. + */ + return -1; + } + + return 0; +} + +static int hammer_event(struct hid_device *hid, struct hid_field *field, + struct hid_usage *usage, __s32 value) +{ + unsigned long flags; + + if (hid->product == USB_DEVICE_ID_GOOGLE_WHISKERS && + usage->hid == WHISKERS_KBD_FOLDED) { + spin_lock_irqsave(&cbas_ec_lock, flags); + + hid_dbg(hid, "%s: base: %d, folded: %d\n", __func__, + cbas_ec.base_present, value); + + /* + * We should not get event if base is detached, but in case + * we happen to service HID and EC notifications out of order + * let's still check the "base present" flag. + */ + if (cbas_ec.input && cbas_ec.base_present) { + input_report_switch(cbas_ec.input, + SW_TABLET_MODE, value); + input_sync(cbas_ec.input); + } + + spin_unlock_irqrestore(&cbas_ec_lock, flags); + return 1; /* We handled this event */ + } + + return 0; +} + +static bool hammer_is_keyboard_interface(struct hid_device *hdev) +{ + struct hid_report_enum *re = &hdev->report_enum[HID_INPUT_REPORT]; struct hid_report *report; - if (list_empty(report_list)) - return 0; + list_for_each_entry(report, &re->report_list, list) + if (report->application == HID_GD_KEYBOARD) + return true; - report = list_first_entry(report_list, struct hid_report, list); + return false; +} + +static bool hammer_has_backlight_control(struct hid_device *hdev) +{ + struct hid_report_enum *re = &hdev->report_enum[HID_OUTPUT_REPORT]; + struct hid_report *report; + int i, j; - if (report->maxfield == 1 && - report->field[0]->application == HID_GD_KEYBOARD && - report->field[0]->maxusage == 1 && - report->field[0]->usage[0].hid == HID_AD_BRIGHTNESS) { - int err = hammer_register_leds(hdev); + list_for_each_entry(report, &re->report_list, list) { + if (report->application != HID_GD_KEYBOARD) + continue; - if (err) + for (i = 0; i < report->maxfield; i++) { + struct hid_field *field = report->field[i]; + + for (j = 0; j < field->maxusage; j++) + if (field->usage[j].hid == HID_AD_BRIGHTNESS) + return true; + } + } + + return false; +} + +static int hammer_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int error; + + /* + * We always want to poll for, and handle tablet mode events from + * Whiskers, even when nobody has opened the input device. This also + * prevents the hid core from dropping early tablet mode events from + * the device. + */ + if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS && + hammer_is_keyboard_interface(hdev)) + hdev->quirks |= HID_QUIRK_ALWAYS_POLL; + + error = hid_parse(hdev); + if (error) + return error; + + error = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (error) + return error; + + if (hammer_has_backlight_control(hdev)) { + error = hammer_register_leds(hdev); + if (error) hid_warn(hdev, "Failed to register keyboard backlight: %d\n", - err); + error); } return 0; } + static const struct hid_device_id hammer_devices[] = { { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_HAMMER) }, @@ -133,8 +482,34 @@ MODULE_DEVICE_TABLE(hid, hammer_devices); static struct hid_driver hammer_driver = { .name = "hammer", .id_table = hammer_devices, - .input_configured = hammer_input_configured, + .probe = hammer_probe, + .input_mapping = hammer_input_mapping, + .event = hammer_event, }; -module_hid_driver(hammer_driver); + +static int __init hammer_init(void) +{ + int error; + + error = platform_driver_register(&cbas_ec_driver); + if (error) + return error; + + error = hid_register_driver(&hammer_driver); + if (error) { + platform_driver_unregister(&cbas_ec_driver); + return error; + } + + return 0; +} +module_init(hammer_init); + +static void __exit hammer_exit(void) +{ + hid_unregister_driver(&hammer_driver); + platform_driver_unregister(&cbas_ec_driver); +} +module_exit(hammer_exit); MODULE_LICENSE("GPL"); From 8f35260e0b6cc09d77bcbbc21554342aa79ceb2f Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Tue, 9 Oct 2018 10:43:39 +0200 Subject: [PATCH 31/35] HID: google: drop superfluous const before SIMPLE_DEV_PM_OPS() SIMPLE_DEV_PM_OPS() already implies const for the type; drop the extra modifier. Fixes: eb1aac4c8744f75460c34d71b0c73bebf3e8ee5c ("HID: google: add support tablet mode switch for Whiskers") Signed-off-by: Jiri Kosina --- drivers/hid/hid-google-hammer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index de3e7d055ca4c..ee5e0bdcf078f 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -161,7 +161,7 @@ static __maybe_unused int cbas_ec_resume(struct device *dev) return 0; } -static const SIMPLE_DEV_PM_OPS(cbas_ec_pm_ops, NULL, cbas_ec_resume); +static SIMPLE_DEV_PM_OPS(cbas_ec_pm_ops, NULL, cbas_ec_resume); static void cbas_ec_set_input(struct input_dev *input) { From 14f39921c906357391d634864d627c7bf8a18c30 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 27 Sep 2018 15:00:52 +0100 Subject: [PATCH 32/35] HID: elan: fix spelling mistake "registred" -> "registered" Trivial fix to spelling mistake in hid_err error message Signed-off-by: Colin Ian King Signed-off-by: Jiri Kosina --- drivers/hid/hid-elan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-elan.c b/drivers/hid/hid-elan.c index 07e26c3567eb9..0bfd6d1b44c14 100644 --- a/drivers/hid/hid-elan.c +++ b/drivers/hid/hid-elan.c @@ -497,7 +497,7 @@ static int elan_probe(struct hid_device *hdev, const struct hid_device_id *id) return 0; if (!drvdata->input) { - hid_err(hdev, "Input device is not registred\n"); + hid_err(hdev, "Input device is not registered\n"); ret = -ENAVAIL; goto err; } From 23e542e5ba5e4298a1475f94750317ec1662ba21 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Tue, 9 Oct 2018 10:52:30 -0700 Subject: [PATCH 33/35] HID: google: add dependency on Cros EC for Hammer Whiskers tablet mode support needs access to Chrome Embedded Controller, so we need to add dependency on MFD_CROS_EC. Reported-by: kbuild test robot Fixes: eb1aac4c8744 ("HID: google: add support tablet mode switch for Whiskers") Signed-off-by: Dmitry Torokhov Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 61e1953ff9219..68d2f773b5227 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -351,7 +351,7 @@ config HOLTEK_FF config HID_GOOGLE_HAMMER tristate "Google Hammer Keyboard" - depends on USB_HID && LEDS_CLASS + depends on USB_HID && LEDS_CLASS && MFD_CROS_EC ---help--- Say Y here if you have a Google Hammer device. From 11db8173dbab7a94cf5ba5225fcedbfc0f3b7e54 Mon Sep 17 00:00:00 2001 From: Jason Gerecke Date: Wed, 10 Oct 2018 13:40:26 -0700 Subject: [PATCH 34/35] HID: wacom: Work around HID descriptor bug in DTK-2451 and DTH-2452 The DTK-2451 and DTH-2452 have a buggy HID descriptor which incorrectly contains a Cintiq-like report, complete with pen tilt, rotation, twist, serial number, etc. The hardware doesn't actually support this data but our driver duitifully sets up the device as though it does. To ensure userspace has a correct view of devices without updated firmware, we clean up this incorrect data in wacom_setup_device_quirks. We're also careful to clear the WACOM_QUIRK_TOOLSERIAL flag since its presence causes the driver to wait for serial number information (via wacom_wac_pen_serial_enforce) that never comes, resulting in the pen being non-responsive. Signed-off-by: Jason Gerecke Fixes: 8341720642 ("HID: wacom: Queue events with missing type/serial data for later processing") Cc: stable@vger.kernel.org # v4.16+ Signed-off-by: Jiri Kosina --- drivers/hid/wacom_wac.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index e0a06be5ef5c0..5dd3a8245f0fd 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -3335,6 +3335,7 @@ static void wacom_setup_intuos(struct wacom_wac *wacom_wac) void wacom_setup_device_quirks(struct wacom *wacom) { + struct wacom_wac *wacom_wac = &wacom->wacom_wac; struct wacom_features *features = &wacom->wacom_wac.features; /* The pen and pad share the same interface on most devices */ @@ -3464,6 +3465,24 @@ void wacom_setup_device_quirks(struct wacom *wacom) if (features->type == REMOTE) features->device_type |= WACOM_DEVICETYPE_WL_MONITOR; + + /* HID descriptor for DTK-2451 / DTH-2452 claims to report lots + * of things it shouldn't. Lets fix up the damage... + */ + if (wacom->hdev->product == 0x382 || wacom->hdev->product == 0x37d) { + features->quirks &= ~WACOM_QUIRK_TOOLSERIAL; + __clear_bit(BTN_TOOL_BRUSH, wacom_wac->pen_input->keybit); + __clear_bit(BTN_TOOL_PENCIL, wacom_wac->pen_input->keybit); + __clear_bit(BTN_TOOL_AIRBRUSH, wacom_wac->pen_input->keybit); + __clear_bit(ABS_Z, wacom_wac->pen_input->absbit); + __clear_bit(ABS_DISTANCE, wacom_wac->pen_input->absbit); + __clear_bit(ABS_TILT_X, wacom_wac->pen_input->absbit); + __clear_bit(ABS_TILT_Y, wacom_wac->pen_input->absbit); + __clear_bit(ABS_WHEEL, wacom_wac->pen_input->absbit); + __clear_bit(ABS_MISC, wacom_wac->pen_input->absbit); + __clear_bit(MSC_SERIAL, wacom_wac->pen_input->mscbit); + __clear_bit(EV_MSC, wacom_wac->pen_input->evbit); + } } int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, From ce9d58d3eafcb2d452a69bdcc1c5f8b8ff640de5 Mon Sep 17 00:00:00 2001 From: Kai-Heng Feng Date: Thu, 11 Oct 2018 15:56:33 +0800 Subject: [PATCH 35/35] HID: usbhid: Add quirk for Redragon/Dragonrise Seymur 2 Redragon Seymur 2 stops working since commit 190d7f02ce8e ("HID: input: do not increment usages when a duplicate is found"). Use quirk HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE suggested by the commit can solve the issue. Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=200995 BugLink: https://bugs.launchpad.net/bugs/1793846 Signed-off-by: Kai-Heng Feng Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-quirks.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index bc49909aba8e6..0e0aa7f0e6fc0 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -342,6 +342,7 @@ #define USB_DEVICE_ID_DMI_ENC 0x5fab #define USB_VENDOR_ID_DRAGONRISE 0x0079 +#define USB_DEVICE_ID_REDRAGON_SEYMUR2 0x0006 #define USB_DEVICE_ID_DRAGONRISE_WIIU 0x1800 #define USB_DEVICE_ID_DRAGONRISE_PS3 0x1801 #define USB_DEVICE_ID_DRAGONRISE_DOLPHINBAR 0x1803 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 249d49b6b16c7..52c3b01917e72 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -70,6 +70,7 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_DMI, USB_DEVICE_ID_DMI_ENC), HID_QUIRK_NOGET }, { HID_USB_DEVICE(USB_VENDOR_ID_DRACAL_RAPHNET, USB_DEVICE_ID_RAPHNET_2NES2SNES), HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_DRACAL_RAPHNET, USB_DEVICE_ID_RAPHNET_4NES4SNES), HID_QUIRK_MULTI_INPUT }, + { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_REDRAGON_SEYMUR2), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_DOLPHINBAR), HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_GAMECUBE1), HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_PS3), HID_QUIRK_MULTI_INPUT },