1*b8e759b8SDaniel M. Lambea // SPDX-License-Identifier: GPL-2.0+ 2*b8e759b8SDaniel M. Lambea /* 3*b8e759b8SDaniel M. Lambea * HID driver for Cougar 500k Gaming Keyboard 4*b8e759b8SDaniel M. Lambea * 5*b8e759b8SDaniel M. Lambea * Copyright (c) 2018 Daniel M. Lambea <dmlambea@gmail.com> 6*b8e759b8SDaniel M. Lambea */ 7*b8e759b8SDaniel M. Lambea 8*b8e759b8SDaniel M. Lambea #include <linux/hid.h> 9*b8e759b8SDaniel M. Lambea #include <linux/module.h> 10*b8e759b8SDaniel M. Lambea 11*b8e759b8SDaniel M. Lambea #include "hid-ids.h" 12*b8e759b8SDaniel M. Lambea 13*b8e759b8SDaniel M. Lambea MODULE_AUTHOR("Daniel M. Lambea <dmlambea@gmail.com>"); 14*b8e759b8SDaniel M. Lambea MODULE_DESCRIPTION("Cougar 500k Gaming Keyboard"); 15*b8e759b8SDaniel M. Lambea MODULE_LICENSE("GPL"); 16*b8e759b8SDaniel M. Lambea MODULE_INFO(key_mappings, "G1-G6 are mapped to F13-F18"); 17*b8e759b8SDaniel M. Lambea 18*b8e759b8SDaniel M. Lambea static int cougar_g6_is_space = 1; 19*b8e759b8SDaniel M. Lambea module_param_named(g6_is_space, cougar_g6_is_space, int, 0600); 20*b8e759b8SDaniel M. Lambea MODULE_PARM_DESC(g6_is_space, 21*b8e759b8SDaniel M. Lambea "If set, G6 programmable key sends SPACE instead of F18 (0=off, 1=on) (default=1)"); 22*b8e759b8SDaniel M. Lambea 23*b8e759b8SDaniel M. Lambea 24*b8e759b8SDaniel M. Lambea #define COUGAR_VENDOR_USAGE 0xff00ff00 25*b8e759b8SDaniel M. Lambea 26*b8e759b8SDaniel M. Lambea #define COUGAR_FIELD_CODE 1 27*b8e759b8SDaniel M. Lambea #define COUGAR_FIELD_ACTION 2 28*b8e759b8SDaniel M. Lambea 29*b8e759b8SDaniel M. Lambea #define COUGAR_KEY_G1 0x83 30*b8e759b8SDaniel M. Lambea #define COUGAR_KEY_G2 0x84 31*b8e759b8SDaniel M. Lambea #define COUGAR_KEY_G3 0x85 32*b8e759b8SDaniel M. Lambea #define COUGAR_KEY_G4 0x86 33*b8e759b8SDaniel M. Lambea #define COUGAR_KEY_G5 0x87 34*b8e759b8SDaniel M. Lambea #define COUGAR_KEY_G6 0x78 35*b8e759b8SDaniel M. Lambea #define COUGAR_KEY_FN 0x0d 36*b8e759b8SDaniel M. Lambea #define COUGAR_KEY_MR 0x6f 37*b8e759b8SDaniel M. Lambea #define COUGAR_KEY_M1 0x80 38*b8e759b8SDaniel M. Lambea #define COUGAR_KEY_M2 0x81 39*b8e759b8SDaniel M. Lambea #define COUGAR_KEY_M3 0x82 40*b8e759b8SDaniel M. Lambea #define COUGAR_KEY_LEDS 0x67 41*b8e759b8SDaniel M. Lambea #define COUGAR_KEY_LOCK 0x6e 42*b8e759b8SDaniel M. Lambea 43*b8e759b8SDaniel M. Lambea 44*b8e759b8SDaniel M. Lambea /* Default key mappings. The special key COUGAR_KEY_G6 is defined first 45*b8e759b8SDaniel M. Lambea * because it is more frequent to use the spacebar rather than any other 46*b8e759b8SDaniel M. Lambea * special keys. Depending on the value of the parameter 'g6_is_space', 47*b8e759b8SDaniel M. Lambea * the mapping will be updated in the probe function. 48*b8e759b8SDaniel M. Lambea */ 49*b8e759b8SDaniel M. Lambea static unsigned char cougar_mapping[][2] = { 50*b8e759b8SDaniel M. Lambea { COUGAR_KEY_G6, KEY_SPACE }, 51*b8e759b8SDaniel M. Lambea { COUGAR_KEY_G1, KEY_F13 }, 52*b8e759b8SDaniel M. Lambea { COUGAR_KEY_G2, KEY_F14 }, 53*b8e759b8SDaniel M. Lambea { COUGAR_KEY_G3, KEY_F15 }, 54*b8e759b8SDaniel M. Lambea { COUGAR_KEY_G4, KEY_F16 }, 55*b8e759b8SDaniel M. Lambea { COUGAR_KEY_G5, KEY_F17 }, 56*b8e759b8SDaniel M. Lambea { COUGAR_KEY_LOCK, KEY_SCREENLOCK }, 57*b8e759b8SDaniel M. Lambea /* The following keys are handled by the hardware itself, so no special 58*b8e759b8SDaniel M. Lambea * treatment is required: 59*b8e759b8SDaniel M. Lambea { COUGAR_KEY_FN, KEY_RESERVED }, 60*b8e759b8SDaniel M. Lambea { COUGAR_KEY_MR, KEY_RESERVED }, 61*b8e759b8SDaniel M. Lambea { COUGAR_KEY_M1, KEY_RESERVED }, 62*b8e759b8SDaniel M. Lambea { COUGAR_KEY_M2, KEY_RESERVED }, 63*b8e759b8SDaniel M. Lambea { COUGAR_KEY_M3, KEY_RESERVED }, 64*b8e759b8SDaniel M. Lambea { COUGAR_KEY_LEDS, KEY_RESERVED }, 65*b8e759b8SDaniel M. Lambea */ 66*b8e759b8SDaniel M. Lambea { 0, 0 }, 67*b8e759b8SDaniel M. Lambea }; 68*b8e759b8SDaniel M. Lambea 69*b8e759b8SDaniel M. Lambea struct cougar_shared { 70*b8e759b8SDaniel M. Lambea struct list_head list; 71*b8e759b8SDaniel M. Lambea struct kref kref; 72*b8e759b8SDaniel M. Lambea bool enabled; 73*b8e759b8SDaniel M. Lambea struct hid_device *dev; 74*b8e759b8SDaniel M. Lambea struct input_dev *input; 75*b8e759b8SDaniel M. Lambea }; 76*b8e759b8SDaniel M. Lambea 77*b8e759b8SDaniel M. Lambea struct cougar { 78*b8e759b8SDaniel M. Lambea bool special_intf; 79*b8e759b8SDaniel M. Lambea struct cougar_shared *shared; 80*b8e759b8SDaniel M. Lambea }; 81*b8e759b8SDaniel M. Lambea 82*b8e759b8SDaniel M. Lambea static LIST_HEAD(cougar_udev_list); 83*b8e759b8SDaniel M. Lambea static DEFINE_MUTEX(cougar_udev_list_lock); 84*b8e759b8SDaniel M. Lambea 85*b8e759b8SDaniel M. Lambea static void cougar_fix_g6_mapping(struct hid_device *hdev) 86*b8e759b8SDaniel M. Lambea { 87*b8e759b8SDaniel M. Lambea int i; 88*b8e759b8SDaniel M. Lambea 89*b8e759b8SDaniel M. Lambea for (i = 0; cougar_mapping[i][0]; i++) { 90*b8e759b8SDaniel M. Lambea if (cougar_mapping[i][0] == COUGAR_KEY_G6) { 91*b8e759b8SDaniel M. Lambea cougar_mapping[i][1] = 92*b8e759b8SDaniel M. Lambea cougar_g6_is_space ? KEY_SPACE : KEY_F18; 93*b8e759b8SDaniel M. Lambea hid_info(hdev, "G6 mapped to %s\n", 94*b8e759b8SDaniel M. Lambea cougar_g6_is_space ? "space" : "F18"); 95*b8e759b8SDaniel M. Lambea return; 96*b8e759b8SDaniel M. Lambea } 97*b8e759b8SDaniel M. Lambea } 98*b8e759b8SDaniel M. Lambea hid_warn(hdev, "no mapping defined for G6/spacebar"); 99*b8e759b8SDaniel M. Lambea } 100*b8e759b8SDaniel M. Lambea 101*b8e759b8SDaniel M. Lambea /* 102*b8e759b8SDaniel M. Lambea * Constant-friendly rdesc fixup for mouse interface 103*b8e759b8SDaniel M. Lambea */ 104*b8e759b8SDaniel M. Lambea static __u8 *cougar_report_fixup(struct hid_device *hdev, __u8 *rdesc, 105*b8e759b8SDaniel M. Lambea unsigned int *rsize) 106*b8e759b8SDaniel M. Lambea { 107*b8e759b8SDaniel M. Lambea if (rdesc[2] == 0x09 && rdesc[3] == 0x02 && 108*b8e759b8SDaniel M. Lambea (rdesc[115] | rdesc[116] << 8) >= HID_MAX_USAGES) { 109*b8e759b8SDaniel M. Lambea hid_info(hdev, 110*b8e759b8SDaniel M. Lambea "usage count exceeds max: fixing up report descriptor\n"); 111*b8e759b8SDaniel M. Lambea rdesc[115] = ((HID_MAX_USAGES-1) & 0xff); 112*b8e759b8SDaniel M. Lambea rdesc[116] = ((HID_MAX_USAGES-1) >> 8); 113*b8e759b8SDaniel M. Lambea } 114*b8e759b8SDaniel M. Lambea return rdesc; 115*b8e759b8SDaniel M. Lambea } 116*b8e759b8SDaniel M. Lambea 117*b8e759b8SDaniel M. Lambea static struct cougar_shared *cougar_get_shared_data(struct hid_device *hdev) 118*b8e759b8SDaniel M. Lambea { 119*b8e759b8SDaniel M. Lambea struct cougar_shared *shared; 120*b8e759b8SDaniel M. Lambea 121*b8e759b8SDaniel M. Lambea /* Try to find an already-probed interface from the same device */ 122*b8e759b8SDaniel M. Lambea list_for_each_entry(shared, &cougar_udev_list, list) { 123*b8e759b8SDaniel M. Lambea if (hid_compare_device_paths(hdev, shared->dev, '/')) { 124*b8e759b8SDaniel M. Lambea kref_get(&shared->kref); 125*b8e759b8SDaniel M. Lambea return shared; 126*b8e759b8SDaniel M. Lambea } 127*b8e759b8SDaniel M. Lambea } 128*b8e759b8SDaniel M. Lambea return NULL; 129*b8e759b8SDaniel M. Lambea } 130*b8e759b8SDaniel M. Lambea 131*b8e759b8SDaniel M. Lambea static void cougar_release_shared_data(struct kref *kref) 132*b8e759b8SDaniel M. Lambea { 133*b8e759b8SDaniel M. Lambea struct cougar_shared *shared = container_of(kref, 134*b8e759b8SDaniel M. Lambea struct cougar_shared, kref); 135*b8e759b8SDaniel M. Lambea 136*b8e759b8SDaniel M. Lambea mutex_lock(&cougar_udev_list_lock); 137*b8e759b8SDaniel M. Lambea list_del(&shared->list); 138*b8e759b8SDaniel M. Lambea mutex_unlock(&cougar_udev_list_lock); 139*b8e759b8SDaniel M. Lambea 140*b8e759b8SDaniel M. Lambea kfree(shared); 141*b8e759b8SDaniel M. Lambea } 142*b8e759b8SDaniel M. Lambea 143*b8e759b8SDaniel M. Lambea static void cougar_remove_shared_data(void *resource) 144*b8e759b8SDaniel M. Lambea { 145*b8e759b8SDaniel M. Lambea struct cougar *cougar = resource; 146*b8e759b8SDaniel M. Lambea 147*b8e759b8SDaniel M. Lambea if (cougar->shared) { 148*b8e759b8SDaniel M. Lambea kref_put(&cougar->shared->kref, cougar_release_shared_data); 149*b8e759b8SDaniel M. Lambea cougar->shared = NULL; 150*b8e759b8SDaniel M. Lambea } 151*b8e759b8SDaniel M. Lambea } 152*b8e759b8SDaniel M. Lambea 153*b8e759b8SDaniel M. Lambea /* 154*b8e759b8SDaniel M. Lambea * Bind the device group's shared data to this cougar struct. 155*b8e759b8SDaniel M. Lambea * If no shared data exists for this group, create and initialize it. 156*b8e759b8SDaniel M. Lambea */ 157*b8e759b8SDaniel M. Lambea static int cougar_bind_shared_data(struct hid_device *hdev, struct cougar *cougar) 158*b8e759b8SDaniel M. Lambea { 159*b8e759b8SDaniel M. Lambea struct cougar_shared *shared; 160*b8e759b8SDaniel M. Lambea int error = 0; 161*b8e759b8SDaniel M. Lambea 162*b8e759b8SDaniel M. Lambea mutex_lock(&cougar_udev_list_lock); 163*b8e759b8SDaniel M. Lambea 164*b8e759b8SDaniel M. Lambea shared = cougar_get_shared_data(hdev); 165*b8e759b8SDaniel M. Lambea if (!shared) { 166*b8e759b8SDaniel M. Lambea shared = kzalloc(sizeof(*shared), GFP_KERNEL); 167*b8e759b8SDaniel M. Lambea if (!shared) { 168*b8e759b8SDaniel M. Lambea error = -ENOMEM; 169*b8e759b8SDaniel M. Lambea goto out; 170*b8e759b8SDaniel M. Lambea } 171*b8e759b8SDaniel M. Lambea 172*b8e759b8SDaniel M. Lambea kref_init(&shared->kref); 173*b8e759b8SDaniel M. Lambea shared->dev = hdev; 174*b8e759b8SDaniel M. Lambea list_add_tail(&shared->list, &cougar_udev_list); 175*b8e759b8SDaniel M. Lambea } 176*b8e759b8SDaniel M. Lambea 177*b8e759b8SDaniel M. Lambea cougar->shared = shared; 178*b8e759b8SDaniel M. Lambea 179*b8e759b8SDaniel M. Lambea error = devm_add_action(&hdev->dev, cougar_remove_shared_data, cougar); 180*b8e759b8SDaniel M. Lambea if (error) { 181*b8e759b8SDaniel M. Lambea mutex_unlock(&cougar_udev_list_lock); 182*b8e759b8SDaniel M. Lambea cougar_remove_shared_data(cougar); 183*b8e759b8SDaniel M. Lambea return error; 184*b8e759b8SDaniel M. Lambea } 185*b8e759b8SDaniel M. Lambea 186*b8e759b8SDaniel M. Lambea out: 187*b8e759b8SDaniel M. Lambea mutex_unlock(&cougar_udev_list_lock); 188*b8e759b8SDaniel M. Lambea return error; 189*b8e759b8SDaniel M. Lambea } 190*b8e759b8SDaniel M. Lambea 191*b8e759b8SDaniel M. Lambea static int cougar_probe(struct hid_device *hdev, 192*b8e759b8SDaniel M. Lambea const struct hid_device_id *id) 193*b8e759b8SDaniel M. Lambea { 194*b8e759b8SDaniel M. Lambea struct cougar *cougar; 195*b8e759b8SDaniel M. Lambea struct hid_input *next, *hidinput = NULL; 196*b8e759b8SDaniel M. Lambea unsigned int connect_mask; 197*b8e759b8SDaniel M. Lambea int error; 198*b8e759b8SDaniel M. Lambea 199*b8e759b8SDaniel M. Lambea cougar = devm_kzalloc(&hdev->dev, sizeof(*cougar), GFP_KERNEL); 200*b8e759b8SDaniel M. Lambea if (!cougar) 201*b8e759b8SDaniel M. Lambea return -ENOMEM; 202*b8e759b8SDaniel M. Lambea hid_set_drvdata(hdev, cougar); 203*b8e759b8SDaniel M. Lambea 204*b8e759b8SDaniel M. Lambea error = hid_parse(hdev); 205*b8e759b8SDaniel M. Lambea if (error) { 206*b8e759b8SDaniel M. Lambea hid_err(hdev, "parse failed\n"); 207*b8e759b8SDaniel M. Lambea goto fail; 208*b8e759b8SDaniel M. Lambea } 209*b8e759b8SDaniel M. Lambea 210*b8e759b8SDaniel M. Lambea if (hdev->collection->usage == COUGAR_VENDOR_USAGE) { 211*b8e759b8SDaniel M. Lambea cougar->special_intf = true; 212*b8e759b8SDaniel M. Lambea connect_mask = HID_CONNECT_HIDRAW; 213*b8e759b8SDaniel M. Lambea } else 214*b8e759b8SDaniel M. Lambea connect_mask = HID_CONNECT_DEFAULT; 215*b8e759b8SDaniel M. Lambea 216*b8e759b8SDaniel M. Lambea error = hid_hw_start(hdev, connect_mask); 217*b8e759b8SDaniel M. Lambea if (error) { 218*b8e759b8SDaniel M. Lambea hid_err(hdev, "hw start failed\n"); 219*b8e759b8SDaniel M. Lambea goto fail; 220*b8e759b8SDaniel M. Lambea } 221*b8e759b8SDaniel M. Lambea 222*b8e759b8SDaniel M. Lambea error = cougar_bind_shared_data(hdev, cougar); 223*b8e759b8SDaniel M. Lambea if (error) 224*b8e759b8SDaniel M. Lambea goto fail_stop_and_cleanup; 225*b8e759b8SDaniel M. Lambea 226*b8e759b8SDaniel M. Lambea /* The custom vendor interface will use the hid_input registered 227*b8e759b8SDaniel M. Lambea * for the keyboard interface, in order to send translated key codes 228*b8e759b8SDaniel M. Lambea * to it. 229*b8e759b8SDaniel M. Lambea */ 230*b8e759b8SDaniel M. Lambea if (hdev->collection->usage == HID_GD_KEYBOARD) { 231*b8e759b8SDaniel M. Lambea cougar_fix_g6_mapping(hdev); 232*b8e759b8SDaniel M. Lambea list_for_each_entry_safe(hidinput, next, &hdev->inputs, list) { 233*b8e759b8SDaniel M. Lambea if (hidinput->registered && hidinput->input != NULL) { 234*b8e759b8SDaniel M. Lambea cougar->shared->input = hidinput->input; 235*b8e759b8SDaniel M. Lambea cougar->shared->enabled = true; 236*b8e759b8SDaniel M. Lambea break; 237*b8e759b8SDaniel M. Lambea } 238*b8e759b8SDaniel M. Lambea } 239*b8e759b8SDaniel M. Lambea } else if (hdev->collection->usage == COUGAR_VENDOR_USAGE) { 240*b8e759b8SDaniel M. Lambea error = hid_hw_open(hdev); 241*b8e759b8SDaniel M. Lambea if (error) 242*b8e759b8SDaniel M. Lambea goto fail_stop_and_cleanup; 243*b8e759b8SDaniel M. Lambea } 244*b8e759b8SDaniel M. Lambea return 0; 245*b8e759b8SDaniel M. Lambea 246*b8e759b8SDaniel M. Lambea fail_stop_and_cleanup: 247*b8e759b8SDaniel M. Lambea hid_hw_stop(hdev); 248*b8e759b8SDaniel M. Lambea fail: 249*b8e759b8SDaniel M. Lambea hid_set_drvdata(hdev, NULL); 250*b8e759b8SDaniel M. Lambea return error; 251*b8e759b8SDaniel M. Lambea } 252*b8e759b8SDaniel M. Lambea 253*b8e759b8SDaniel M. Lambea /* 254*b8e759b8SDaniel M. Lambea * Convert events from vendor intf to input key events 255*b8e759b8SDaniel M. Lambea */ 256*b8e759b8SDaniel M. Lambea static int cougar_raw_event(struct hid_device *hdev, struct hid_report *report, 257*b8e759b8SDaniel M. Lambea u8 *data, int size) 258*b8e759b8SDaniel M. Lambea { 259*b8e759b8SDaniel M. Lambea struct cougar *cougar; 260*b8e759b8SDaniel M. Lambea unsigned char code, action; 261*b8e759b8SDaniel M. Lambea int i; 262*b8e759b8SDaniel M. Lambea 263*b8e759b8SDaniel M. Lambea cougar = hid_get_drvdata(hdev); 264*b8e759b8SDaniel M. Lambea if (!cougar->special_intf || !cougar->shared || 265*b8e759b8SDaniel M. Lambea !cougar->shared->input || !cougar->shared->enabled) 266*b8e759b8SDaniel M. Lambea return 0; 267*b8e759b8SDaniel M. Lambea 268*b8e759b8SDaniel M. Lambea code = data[COUGAR_FIELD_CODE]; 269*b8e759b8SDaniel M. Lambea action = data[COUGAR_FIELD_ACTION]; 270*b8e759b8SDaniel M. Lambea for (i = 0; cougar_mapping[i][0]; i++) { 271*b8e759b8SDaniel M. Lambea if (code == cougar_mapping[i][0]) { 272*b8e759b8SDaniel M. Lambea input_event(cougar->shared->input, EV_KEY, 273*b8e759b8SDaniel M. Lambea cougar_mapping[i][1], action); 274*b8e759b8SDaniel M. Lambea input_sync(cougar->shared->input); 275*b8e759b8SDaniel M. Lambea return 0; 276*b8e759b8SDaniel M. Lambea } 277*b8e759b8SDaniel M. Lambea } 278*b8e759b8SDaniel M. Lambea hid_warn(hdev, "unmapped special key code %x: ignoring\n", code); 279*b8e759b8SDaniel M. Lambea return 0; 280*b8e759b8SDaniel M. Lambea } 281*b8e759b8SDaniel M. Lambea 282*b8e759b8SDaniel M. Lambea static void cougar_remove(struct hid_device *hdev) 283*b8e759b8SDaniel M. Lambea { 284*b8e759b8SDaniel M. Lambea struct cougar *cougar = hid_get_drvdata(hdev); 285*b8e759b8SDaniel M. Lambea 286*b8e759b8SDaniel M. Lambea if (cougar) { 287*b8e759b8SDaniel M. Lambea /* Stop the vendor intf to process more events */ 288*b8e759b8SDaniel M. Lambea if (cougar->shared) 289*b8e759b8SDaniel M. Lambea cougar->shared->enabled = false; 290*b8e759b8SDaniel M. Lambea if (cougar->special_intf) 291*b8e759b8SDaniel M. Lambea hid_hw_close(hdev); 292*b8e759b8SDaniel M. Lambea } 293*b8e759b8SDaniel M. Lambea hid_hw_stop(hdev); 294*b8e759b8SDaniel M. Lambea } 295*b8e759b8SDaniel M. Lambea 296*b8e759b8SDaniel M. Lambea static struct hid_device_id cougar_id_table[] = { 297*b8e759b8SDaniel M. Lambea { HID_USB_DEVICE(USB_VENDOR_ID_SOLID_YEAR, 298*b8e759b8SDaniel M. Lambea USB_DEVICE_ID_COUGAR_500K_GAMING_KEYBOARD) }, 299*b8e759b8SDaniel M. Lambea {} 300*b8e759b8SDaniel M. Lambea }; 301*b8e759b8SDaniel M. Lambea MODULE_DEVICE_TABLE(hid, cougar_id_table); 302*b8e759b8SDaniel M. Lambea 303*b8e759b8SDaniel M. Lambea static struct hid_driver cougar_driver = { 304*b8e759b8SDaniel M. Lambea .name = "cougar", 305*b8e759b8SDaniel M. Lambea .id_table = cougar_id_table, 306*b8e759b8SDaniel M. Lambea .report_fixup = cougar_report_fixup, 307*b8e759b8SDaniel M. Lambea .probe = cougar_probe, 308*b8e759b8SDaniel M. Lambea .remove = cougar_remove, 309*b8e759b8SDaniel M. Lambea .raw_event = cougar_raw_event, 310*b8e759b8SDaniel M. Lambea }; 311*b8e759b8SDaniel M. Lambea 312*b8e759b8SDaniel M. Lambea module_hid_driver(cougar_driver); 313