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