From patchwork Tue Jul 17 21:35:37 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Daniel M. Lambea" X-Patchwork-Id: 10530775 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 7CC8060247 for ; Tue, 17 Jul 2018 21:35:55 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 6F164290D0 for ; Tue, 17 Jul 2018 21:35:55 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 63D6C2957C; Tue, 17 Jul 2018 21:35:55 +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=-7.8 required=2.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, MAILING_LIST_MULTI, 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 981F1290D0 for ; Tue, 17 Jul 2018 21:35:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729675AbeGQWK2 (ORCPT ); Tue, 17 Jul 2018 18:10:28 -0400 Received: from mail-wr1-f68.google.com ([209.85.221.68]:34152 "EHLO mail-wr1-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729759AbeGQWK1 (ORCPT ); Tue, 17 Jul 2018 18:10:27 -0400 Received: by mail-wr1-f68.google.com with SMTP id c13-v6so2613506wrt.1 for ; Tue, 17 Jul 2018 14:35:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=bKIEvNkRRg6YXj6KQNeCoE9b1pT9oTnSHR9kFvYFyo8=; b=sAqr9IbbT61IKyvGDGNi62htWMfNuy4atzEQa2ZedJ3rnAzIYeNogZ9IeiuX+1rCHn w52UPFKpvUn3oEQQsjCKxFrA/qQKtGJ+ZoMHhsbDXKrlZAXx1RRpXhxrdr3lrFX0KCvn nyTfiwrezuQ7EQ0wkx/Cgj1np/fZlpwop+KhfTszEH67TzfpdwD9TnaYoqq/LGXoLqrD ORcub3zx7gF8h3UjwbS90T3+OFYgFOibZxhdNsouKb4/sD8NuF4gEv/AGAka3hc+OPZJ imLMLEPbfBHAc2mKveQVeul8g4nKG1MuXJFTzwIZjexk1GuJy+HFf1gIwI9NFIP5Zz0i rvuA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=bKIEvNkRRg6YXj6KQNeCoE9b1pT9oTnSHR9kFvYFyo8=; b=L3DE4kHvCaNOhs1OY2Kf0W/gpk0+6Egxts3HQd9xgsREL7K2vMlysgUk1WsyygHovX oMH+6QiVUDZtluPs2OLFCDh0I3lct+8naH12aUN6NsUH/iDGThwpdZ5rguGxBv68Z8Et WYoZNef0m2SGzbtPdMjPnKiUQqB7TRQ4FhKRsRu40AgpwBLJFHx4sq3Jk4D2Kxycvjis xkfwLoizX4W6TXFKeV5hOapZvKtZze++2jb0mqz5hpOmgEVlHe9fv4Nv8QMYSEGVJ5RB GM4g/f1lbBO4cY4HEdWcdJ57KS9XbSsqjkqn+9U0vFJMJujUGzgWGD412WGYWO9Ij9RN 06kA== X-Gm-Message-State: AOUpUlHQY2VONKDCzN6IfNfY2NfwPm6brthiKP5l6rlu977LACcrwoBb 6rl8IlQSK481yZ/e/cLTtwn+Sg== X-Google-Smtp-Source: AAOMgpdCcJLKgfIR8XMF57mL3BciHWUzn1pyN/cWAKd+C52W/t6z3cNgk40OdqMabBpy1LpnlDlJVg== X-Received: by 2002:adf:9aeb:: with SMTP id a98-v6mr2435845wrc.110.1531863351106; Tue, 17 Jul 2018 14:35:51 -0700 (PDT) Received: from localhost.localdomain (86.red-83-42-60.dynamicip.rima-tde.net. [83.42.60.86]) by smtp.gmail.com with ESMTPSA id y11-v6sm3303609wrr.84.2018.07.17.14.35.50 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 17 Jul 2018 14:35:50 -0700 (PDT) From: "Daniel M. Lambea" To: Jiri Kosina Cc: Benjamin Tissoires , linux-input@vger.kernel.org Subject: [PATCH v2 2/2] HID: cougar: Add support for the Cougar 500k Gaming Keyboard Date: Tue, 17 Jul 2018 22:35:37 +0100 Message-Id: <20180717213537.12851-2-dmlambea@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20180717213537.12851-1-dmlambea@gmail.com> References: <20180717213537.12851-1-dmlambea@gmail.com> 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 Cougar 500k Gaming Keyboard have some special function keys that make the keyboard stop responding once pressed. Implement the custom vendor interface that deals with the extended keypresses to fix. The bug can be reproduced by plugging in the keyboard, then pressing the rightmost part of the spacebar. Signed-off-by: Daniel M. Lambea Reviewed-by: Benjamin Tissoires --- drivers/hid/Kconfig | 10 ++ drivers/hid/Makefile | 1 + drivers/hid/hid-cougar.c | 312 +++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-ids.h | 3 + 4 files changed, 326 insertions(+) create mode 100644 drivers/hid/hid-cougar.c diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index a49a10437c40..61e1953ff921 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -207,6 +207,16 @@ config HID_CORSAIR - Vengeance K90 - Scimitar PRO RGB +config HID_COUGAR + tristate "Cougar devices" + depends on HID + help + Support for Cougar devices that are not fully compliant with the + HID standard. + + Supported devices: + - Cougar 500k Gaming Keyboard + config HID_PRODIKEYS tristate "Prodikeys PC-MIDI Keyboard support" depends on HID && SND diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 511e1cbff768..bd7ac53b75c5 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -35,6 +35,7 @@ obj-$(CONFIG_HID_CHERRY) += hid-cherry.o obj-$(CONFIG_HID_CHICONY) += hid-chicony.o obj-$(CONFIG_HID_CMEDIA) += hid-cmedia.o obj-$(CONFIG_HID_CORSAIR) += hid-corsair.o +obj-$(CONFIG_HID_COUGAR) += hid-cougar.o obj-$(CONFIG_HID_CP2112) += hid-cp2112.o obj-$(CONFIG_HID_CYPRESS) += hid-cypress.o obj-$(CONFIG_HID_DRAGONRISE) += hid-dr.o diff --git a/drivers/hid/hid-cougar.c b/drivers/hid/hid-cougar.c new file mode 100644 index 000000000000..ad2e87de7dc5 --- /dev/null +++ b/drivers/hid/hid-cougar.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * HID driver for Cougar 500k Gaming Keyboard + * + * Copyright (c) 2018 Daniel M. Lambea + */ + +#include +#include + +#include "hid-ids.h" + +MODULE_AUTHOR("Daniel M. Lambea "); +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); +MODULE_PARM_DESC(g6_is_space, + "If set, G6 programmable key sends SPACE instead of F18 (0=off, 1=on) (default=1)"); + + +#define COUGAR_VENDOR_USAGE 0xff00ff00 + +#define COUGAR_FIELD_CODE 1 +#define COUGAR_FIELD_ACTION 2 + +#define COUGAR_KEY_G1 0x83 +#define COUGAR_KEY_G2 0x84 +#define COUGAR_KEY_G3 0x85 +#define COUGAR_KEY_G4 0x86 +#define COUGAR_KEY_G5 0x87 +#define COUGAR_KEY_G6 0x78 +#define COUGAR_KEY_FN 0x0d +#define COUGAR_KEY_MR 0x6f +#define COUGAR_KEY_M1 0x80 +#define COUGAR_KEY_M2 0x81 +#define COUGAR_KEY_M3 0x82 +#define COUGAR_KEY_LEDS 0x67 +#define COUGAR_KEY_LOCK 0x6e + + +/* Default key mappings. The special key COUGAR_KEY_G6 is defined first + * because it is more frequent to use the spacebar rather than any other + * special keys. Depending on the value of the parameter 'g6_is_space', + * the mapping will be updated in the probe function. + */ +static unsigned char cougar_mapping[][2] = { + { COUGAR_KEY_G6, KEY_SPACE }, + { COUGAR_KEY_G1, KEY_F13 }, + { COUGAR_KEY_G2, KEY_F14 }, + { COUGAR_KEY_G3, KEY_F15 }, + { COUGAR_KEY_G4, KEY_F16 }, + { COUGAR_KEY_G5, KEY_F17 }, + { COUGAR_KEY_LOCK, KEY_SCREENLOCK }, +/* The following keys are handled by the hardware itself, so no special + * treatment is required: + { COUGAR_KEY_FN, KEY_RESERVED }, + { COUGAR_KEY_MR, KEY_RESERVED }, + { COUGAR_KEY_M1, KEY_RESERVED }, + { COUGAR_KEY_M2, KEY_RESERVED }, + { COUGAR_KEY_M3, KEY_RESERVED }, + { COUGAR_KEY_LEDS, KEY_RESERVED }, +*/ + { 0, 0 }, +}; + +struct cougar_shared { + struct list_head list; + struct kref kref; + bool enabled; + struct hid_device *dev; + struct input_dev *input; +}; + +struct cougar { + bool special_intf; + struct cougar_shared *shared; +}; + +static LIST_HEAD(cougar_udev_list); +static DEFINE_MUTEX(cougar_udev_list_lock); + +static void cougar_fix_g6_mapping(struct hid_device *hdev) +{ + 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"); + return; + } + } + hid_warn(hdev, "no mapping defined for G6/spacebar"); +} + +/* + * Constant-friendly rdesc fixup for mouse interface + */ +static __u8 *cougar_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + if (rdesc[2] == 0x09 && rdesc[3] == 0x02 && + (rdesc[115] | rdesc[116] << 8) >= HID_MAX_USAGES) { + hid_info(hdev, + "usage count exceeds max: fixing up report descriptor\n"); + rdesc[115] = ((HID_MAX_USAGES-1) & 0xff); + rdesc[116] = ((HID_MAX_USAGES-1) >> 8); + } + return rdesc; +} + +static struct cougar_shared *cougar_get_shared_data(struct hid_device *hdev) +{ + struct cougar_shared *shared; + + /* Try to find an already-probed interface from the same device */ + list_for_each_entry(shared, &cougar_udev_list, list) { + if (hid_compare_device_paths(hdev, shared->dev, '/')) { + kref_get(&shared->kref); + return shared; + } + } + return NULL; +} + +static void cougar_release_shared_data(struct kref *kref) +{ + struct cougar_shared *shared = container_of(kref, + struct cougar_shared, kref); + + mutex_lock(&cougar_udev_list_lock); + list_del(&shared->list); + mutex_unlock(&cougar_udev_list_lock); + + kfree(shared); +} + +static void cougar_remove_shared_data(void *resource) +{ + struct cougar *cougar = resource; + + if (cougar->shared) { + kref_put(&cougar->shared->kref, cougar_release_shared_data); + cougar->shared = NULL; + } +} + +/* + * 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) +{ + struct cougar_shared *shared; + int error = 0; + + mutex_lock(&cougar_udev_list_lock); + + shared = cougar_get_shared_data(hdev); + if (!shared) { + shared = kzalloc(sizeof(*shared), GFP_KERNEL); + if (!shared) { + error = -ENOMEM; + goto out; + } + + kref_init(&shared->kref); + shared->dev = hdev; + list_add_tail(&shared->list, &cougar_udev_list); + } + + cougar->shared = shared; + + error = devm_add_action(&hdev->dev, cougar_remove_shared_data, cougar); + if (error) { + mutex_unlock(&cougar_udev_list_lock); + cougar_remove_shared_data(cougar); + return error; + } + +out: + mutex_unlock(&cougar_udev_list_lock); + return error; +} + +static int cougar_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + struct cougar *cougar; + struct hid_input *next, *hidinput = NULL; + unsigned int connect_mask; + int error; + + cougar = devm_kzalloc(&hdev->dev, sizeof(*cougar), GFP_KERNEL); + if (!cougar) + return -ENOMEM; + hid_set_drvdata(hdev, cougar); + + error = hid_parse(hdev); + if (error) { + hid_err(hdev, "parse failed\n"); + goto fail; + } + + if (hdev->collection->usage == COUGAR_VENDOR_USAGE) { + cougar->special_intf = true; + connect_mask = HID_CONNECT_HIDRAW; + } else + connect_mask = HID_CONNECT_DEFAULT; + + error = hid_hw_start(hdev, connect_mask); + if (error) { + hid_err(hdev, "hw start failed\n"); + goto fail; + } + + error = cougar_bind_shared_data(hdev, cougar); + if (error) + goto fail_stop_and_cleanup; + + /* The custom vendor interface will use the hid_input registered + * for the keyboard interface, in order to send translated key codes + * 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; + cougar->shared->enabled = true; + break; + } + } + } else if (hdev->collection->usage == COUGAR_VENDOR_USAGE) { + error = hid_hw_open(hdev); + if (error) + goto fail_stop_and_cleanup; + } + return 0; + +fail_stop_and_cleanup: + hid_hw_stop(hdev); +fail: + hid_set_drvdata(hdev, NULL); + return error; +} + +/* + * Convert events from vendor intf to input key events + */ +static int cougar_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct cougar *cougar; + unsigned char code, action; + int i; + + cougar = hid_get_drvdata(hdev); + if (!cougar->special_intf || !cougar->shared || + !cougar->shared->input || !cougar->shared->enabled) + return 0; + + 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, + cougar_mapping[i][1], action); + input_sync(cougar->shared->input); + return 0; + } + } + hid_warn(hdev, "unmapped special key code %x: ignoring\n", code); + return 0; +} + +static void cougar_remove(struct hid_device *hdev) +{ + struct cougar *cougar = hid_get_drvdata(hdev); + + if (cougar) { + /* Stop the vendor intf to process more events */ + if (cougar->shared) + cougar->shared->enabled = false; + if (cougar->special_intf) + hid_hw_close(hdev); + } + hid_hw_stop(hdev); +} + +static struct hid_device_id cougar_id_table[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_SOLID_YEAR, + USB_DEVICE_ID_COUGAR_500K_GAMING_KEYBOARD) }, + {} +}; +MODULE_DEVICE_TABLE(hid, cougar_id_table); + +static struct hid_driver cougar_driver = { + .name = "cougar", + .id_table = cougar_id_table, + .report_fixup = cougar_report_fixup, + .probe = cougar_probe, + .remove = cougar_remove, + .raw_event = cougar_raw_event, +}; + +module_hid_driver(cougar_driver); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index c7981ddd8776..a1ee8f643f24 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1001,6 +1001,9 @@ #define USB_VENDOR_ID_SINO_LITE 0x1345 #define USB_DEVICE_ID_SINO_LITE_CONTROLLER 0x3008 +#define USB_VENDOR_ID_SOLID_YEAR 0x060b +#define USB_DEVICE_ID_COUGAR_500K_GAMING_KEYBOARD 0x500a + #define USB_VENDOR_ID_SOUNDGRAPH 0x15c2 #define USB_DEVICE_ID_SOUNDGRAPH_IMON_FIRST 0x0034 #define USB_DEVICE_ID_SOUNDGRAPH_IMON_LAST 0x0046