From patchwork Sat Mar 25 22:49:40 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mauro Carvalho Chehab X-Patchwork-Id: 9644897 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 2F546601E9 for ; Sat, 25 Mar 2017 22:50:16 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 13C1C27165 for ; Sat, 25 Mar 2017 22:50:16 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 043F827FA8; Sat, 25 Mar 2017 22:50:16 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 4DE5627165 for ; Sat, 25 Mar 2017 22:50:15 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751227AbdCYWuP (ORCPT ); Sat, 25 Mar 2017 18:50:15 -0400 Received: from bombadil.infradead.org ([65.50.211.133]:57022 "EHLO bombadil.infradead.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751210AbdCYWuO (ORCPT ); Sat, 25 Mar 2017 18:50:14 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=bombadil.20170209; h=Sender:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Reply-To:MIME-Version:Content-Type: Content-Transfer-Encoding:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Id: List-Help:List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive; bh=bszSMmhep47H1gprPNNzSuRjt59eqLFLtXzzKdYXdTQ=; b=h2ybEI4lno+YmF3mEKS9jeH8X HLGJnTihQlOJLdkfexR2xXl3zvIP94rK6xxMwiEWsBjN5P3i5EJFgCnLBlvGDCxYOtQWswdaIMEvS X1GKoddwaQnuiE7H7wA6H23h4ACN2ZBBfa9/IEmjgYKG/987Kg6hUI8fnoVJftYaVusKOv6q/PLwo RmuW/PrekIQbwozNWDGZ0ADK359QxZPD4v7/v+fmFq3wqIZywnuNkcXnO0nb82JoAGUy13CBw+5TD B6+fq9pfo5muNBYWr/ATLKZ0s692FycD+pAKk5qe0XZqGUPl4C7C5oVaNVRIvfHlKx3TCzDDxMe8o O6pTJfZDQ==; Received: from 177.205.100.127.dynamic.adsl.gvt.net.br ([177.205.100.127] helo=smtp.w2.samsung.com) by bombadil.infradead.org with esmtpsa (Exim 4.87 #1 (Red Hat Linux)) id 1cruVG-0007Jm-QA; Sat, 25 Mar 2017 22:49:47 +0000 Received: from mchehab by smtp.w2.samsung.com with local (Exim 4.87) (envelope-from ) id 1cruVD-0000la-Ab; Sat, 25 Mar 2017 19:49:43 -0300 From: Mauro Carvalho Chehab To: linux-input@vger.kernel.org Cc: Mauro Carvalho Chehab , Dmitry Torokhov , Peter Hutterer , Benjamin Tissoires , Jiri Kosina Subject: [PATCH RFC 2/4] hid-logitech-hidpp: add support for high res wheel Date: Sat, 25 Mar 2017 19:49:40 -0300 Message-Id: <2529a55b0477cc638f1104a423cb67de1628a96d.1490481944.git.mchehab@s-opensource.com> X-Mailer: git-send-email 2.9.3 In-Reply-To: References: In-Reply-To: References: Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Some Logitech mouses (MX Anyware 2 and MX Master) have support for a high-resolution wheel. This wheel can work in backward-compatible mode, generating wheel events via HID normal events, or it can use new HID++ events that report not only the wheel movement, but also the resolution. Add support for it. Signed-off-by: Mauro Carvalho Chehab --- drivers/hid/hid-logitech-hidpp.c | 197 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 2e2515a4c070..ee3f544bda4f 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -62,6 +62,7 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22) #define HIDPP_QUIRK_NO_HIDINPUT BIT(23) #define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24) +#define HIDPP_QUIRK_HIRES_SCROLL BIT(25) #define HIDPP_QUIRK_DELAYED_INIT (HIDPP_QUIRK_NO_HIDINPUT | \ HIDPP_QUIRK_CONNECT_EVENTS) @@ -1361,6 +1362,67 @@ static int hidpp_ff_deinit(struct hid_device *hid) return 0; } +/* -------------------------------------------------------------------------- */ +/* 0x2121: High Resolution Wheel */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_HIGH_RES_WHEEL 0x2121 + +#define CMD_MOUSE_SET_WHEEL_MODE 0x20 +#define CMD_MOUSE_GET_WHEEL_RATCHET 0x30 + +struct high_res_wheel_data { + u8 feature_index; + struct input_dev *input; + bool ratchet; +}; + +/** + * hidpp_mouse_set_wheel_mode - Sets high resolution wheel mode + * + * @invert: if true, inverts wheel movement + * @high_res: if true, wheel is in high-resolution mode. Otherwise, low res + * @hidpp: if true, report wheel events via HID++ notification. If false, + * use standard HID events + */ +static int hidpp_mouse_set_wheel_mode(struct hidpp_device *hidpp, + bool invert, + bool high_res, + bool hidpp_mode) +{ + struct high_res_wheel_data *hrd = hidpp->private_data; + u8 feature_type; + struct hidpp_report response; + int ret; + u8 params[16] = { 0 }; + + if (!hrd->feature_index) { + ret = hidpp_root_get_feature(hidpp, + HIDPP_HIGH_RES_WHEEL, + &hrd->feature_index, + &feature_type); + if (ret) + /* means that the device is not powered up */ + return ret; + } + + params[0] = invert ? 0x4 : 0 | + high_res ? 0x2 : 0 | + hidpp_mode ? 0x1 : 0; + + ret = hidpp_send_fap_command_sync(hidpp, hrd->feature_index, + CMD_MOUSE_SET_WHEEL_MODE, + params, 16, &response); + if (ret > 0) { + hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", + __func__, ret); + return -EPROTO; + } + if (ret) + return ret; + + return 0; +} /* ************************************************************************** */ /* */ @@ -1816,6 +1878,119 @@ static int m560_input_mapping(struct hid_device *hdev, struct hid_input *hi, } /* ------------------------------------------------------------------------- */ +/* Logitech mouse devices with high resolution wheel */ +/* ------------------------------------------------------------------------- */ + +static int high_res_raw_event(struct hid_device *hdev, u8 *data, int size) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + struct high_res_wheel_data *hrd = hidpp->private_data; + + /* Don't handle special raw events before setting feature_index */ + if (!hrd || !hrd->feature_index) + return 0; + + if (data[0] != REPORT_ID_HIDPP_LONG || + data[2] != hrd->feature_index) + return 1; + + if (size < 8) { + hid_err(hdev, "error in report: size = %d: %*ph\n", size, + size, data); + return 0; + } + + /* + * high res wheel mouse events + * + * Wheel movement events are like: + * + * 11 03 0b 00 01 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 + * + * data[0] = 0x11 + * data[1] = device-id + * data[2] = feature index (0b) + * data[3] = event type: 0x00 - wheel movement + * data[4] = bitmask: + * bits 0-3: number of sampling periods combined + * bit 4: + * 0 = low resolution + * 1 = high resolution + * data[5] - deltaV MSB + * data[6] = deltaV LSB + * Remaining payload is reserved + * + * Ratchet events are like: + * 11 03 0b 10 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * + * data[0] = 0x11 + * data[1] = device-id + * data[2] = feature index + * data[3] = event type: 0x10 - ratchet state + * data[4] = bit 0: + * 1 = ratchet + * 0 = free wheel + * Remaining payload is reserved + */ + + if (data[3] == 0) { + s16 delta = data[6] | data[5] << 8; + bool res = data[4] & 0x10; + + /* + * Report high-resolution events as REL_HWHEEL and + * low-resolution events as REL_WHEEL. + */ + if (res) + input_report_rel(hrd->input, REL_HIRES_WHEEL, delta); + else + input_report_rel(hrd->input, REL_WHEEL, delta); + } + + /* FIXME: also report ratchet events to userspace */ + + return 1; +} + +static void high_res_populate_input(struct hidpp_device *hidpp, + struct input_dev *input_dev, bool origin_is_hid_core) +{ + struct high_res_wheel_data *hrd = hidpp->private_data; + + hrd->input = input_dev; + + __set_bit(REL_WHEEL, hrd->input->relbit); + __set_bit(REL_HIRES_WHEEL, hrd->input->relbit); +} + + +static int high_res_allocate(struct hid_device *hdev) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + struct high_res_wheel_data *hrd; + + hrd = devm_kzalloc(&hdev->dev, sizeof(struct high_res_wheel_data), + GFP_KERNEL); + if (!hrd) + return -ENOMEM; + + hidpp->private_data = hrd; + + return 0; +}; + +static int high_res_connect(struct hid_device *hdev, bool connected) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + + if (!connected) + return 0; + + /* Enable HID++ wheel event output mode */ + return hidpp_mouse_set_wheel_mode(hidpp, false, false, true); +} + +/* ------------------------------------------------------------------------- */ /* Logitech K400 devices */ /* ------------------------------------------------------------------------- */ @@ -1955,6 +2130,9 @@ 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); + else if (hidpp->quirks & HIDPP_QUIRK_HIRES_SCROLL) + high_res_populate_input(hidpp, input, origin_is_hid_core); + } static int hidpp_input_configured(struct hid_device *hdev, @@ -2054,6 +2232,8 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report, return wtp_raw_event(hdev, data, size); else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) return m560_raw_event(hdev, data, size); + else if (hidpp->quirks & HIDPP_QUIRK_HIRES_SCROLL) + return high_res_raw_event(hdev, data, size); return 0; } @@ -2141,6 +2321,10 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) ret = k400_connect(hdev, connected); if (ret) return; + } else if (hidpp->quirks & HIDPP_QUIRK_HIRES_SCROLL) { + ret = high_res_connect(hdev, connected); + if (ret) + return; } if (!connected || hidpp->delayed_input) @@ -2215,6 +2399,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP; hidpp->quirks &= ~HIDPP_QUIRK_CONNECT_EVENTS; hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT; + hidpp->quirks &= ~HIDPP_QUIRK_HIRES_SCROLL; } if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { @@ -2229,6 +2414,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) ret = k400_allocate(hdev); if (ret) goto allocate_fail; + } else if (hidpp->quirks & HIDPP_QUIRK_HIRES_SCROLL) { + ret = high_res_allocate(hdev); + if (ret) + goto allocate_fail; } INIT_WORK(&hidpp->work, delayed_work_cb); @@ -2354,6 +2543,14 @@ static const struct hid_device_id hidpp_devices[] = { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, USB_VENDOR_ID_LOGITECH, 0x402d), .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 }, + { /* Logitech MX Master with high resolution scroll */ + HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, + USB_VENDOR_ID_LOGITECH, 0x4041), + .driver_data = HIDPP_QUIRK_CONNECT_EVENTS | HIDPP_QUIRK_HIRES_SCROLL }, + { /* Logitech MX Anywhere 2r with high resolution scroll */ + HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, + USB_VENDOR_ID_LOGITECH, 0x404a), + .driver_data = HIDPP_QUIRK_CONNECT_EVENTS | HIDPP_QUIRK_HIRES_SCROLL }, { /* Keyboard logitech K400 */ HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, USB_VENDOR_ID_LOGITECH, 0x4024),