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>
106b003a8dSDaniel 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
196b003a8dSDaniel M. Lambea static bool g6_is_space = true;
20b8e759b8SDaniel M. Lambea MODULE_PARM_DESC(g6_is_space,
216b003a8dSDaniel 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
846b003a8dSDaniel M. Lambea /**
856b003a8dSDaniel M. Lambea * cougar_fix_g6_mapping - configure the mapping for key G6/Spacebar
866b003a8dSDaniel M. Lambea */
cougar_fix_g6_mapping(void)876b003a8dSDaniel 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] =
946b003a8dSDaniel M. Lambea g6_is_space ? KEY_SPACE : KEY_F18;
956b003a8dSDaniel M. Lambea pr_info("cougar: G6 mapped to %s\n",
966b003a8dSDaniel M. Lambea g6_is_space ? "space" : "F18");
97b8e759b8SDaniel M. Lambea return;
98b8e759b8SDaniel M. Lambea }
99b8e759b8SDaniel M. Lambea }
1006b003a8dSDaniel 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 */
cougar_report_fixup(struct hid_device * hdev,__u8 * rdesc,unsigned int * rsize)106b8e759b8SDaniel M. Lambea static const __u8 *cougar_report_fixup(struct hid_device *hdev, __u8 *rdesc,
107b8e759b8SDaniel M. Lambea unsigned int *rsize)
108b8e759b8SDaniel M. Lambea {
109*a6e9c391SCamila Alvarez if (*rsize >= 117 && 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
cougar_get_shared_data(struct hid_device * hdev)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
cougar_release_shared_data(struct kref * kref)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
cougar_remove_shared_data(void * resource)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 */
cougar_bind_shared_data(struct hid_device * hdev,struct cougar * cougar)1596b003a8dSDaniel M. Lambea static int cougar_bind_shared_data(struct hid_device *hdev,
1606b003a8dSDaniel 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
1828e3cd922SCai Huoqing error = devm_add_action_or_reset(&hdev->dev, cougar_remove_shared_data, cougar);
183b8e759b8SDaniel M. Lambea if (error) {
184b8e759b8SDaniel M. Lambea mutex_unlock(&cougar_udev_list_lock);
185b8e759b8SDaniel M. Lambea return error;
186b8e759b8SDaniel M. Lambea }
187b8e759b8SDaniel M. Lambea
188b8e759b8SDaniel M. Lambea out:
189b8e759b8SDaniel M. Lambea mutex_unlock(&cougar_udev_list_lock);
190b8e759b8SDaniel M. Lambea return error;
191b8e759b8SDaniel M. Lambea }
192b8e759b8SDaniel M. Lambea
cougar_probe(struct hid_device * hdev,const struct hid_device_id * id)193b8e759b8SDaniel M. Lambea static int cougar_probe(struct hid_device *hdev,
194b8e759b8SDaniel M. Lambea const struct hid_device_id *id)
195b8e759b8SDaniel M. Lambea {
196b8e759b8SDaniel M. Lambea struct cougar *cougar;
197b8e759b8SDaniel M. Lambea struct hid_input *next, *hidinput = NULL;
198b8e759b8SDaniel M. Lambea unsigned int connect_mask;
199b8e759b8SDaniel M. Lambea int error;
200b8e759b8SDaniel M. Lambea
201b8e759b8SDaniel M. Lambea cougar = devm_kzalloc(&hdev->dev, sizeof(*cougar), GFP_KERNEL);
202b8e759b8SDaniel M. Lambea if (!cougar)
203b8e759b8SDaniel M. Lambea return -ENOMEM;
204b8e759b8SDaniel M. Lambea hid_set_drvdata(hdev, cougar);
205b8e759b8SDaniel M. Lambea
206b8e759b8SDaniel M. Lambea error = hid_parse(hdev);
207b8e759b8SDaniel M. Lambea if (error) {
208b8e759b8SDaniel M. Lambea hid_err(hdev, "parse failed\n");
20987fcb6a6SBenjamin Tissoires return error;
210b8e759b8SDaniel M. Lambea }
211b8e759b8SDaniel M. Lambea
212b8e759b8SDaniel M. Lambea if (hdev->collection->usage == COUGAR_VENDOR_USAGE) {
213b8e759b8SDaniel M. Lambea cougar->special_intf = true;
214b8e759b8SDaniel M. Lambea connect_mask = HID_CONNECT_HIDRAW;
215b8e759b8SDaniel M. Lambea } else
216b8e759b8SDaniel M. Lambea connect_mask = HID_CONNECT_DEFAULT;
217b8e759b8SDaniel M. Lambea
218b8e759b8SDaniel M. Lambea error = hid_hw_start(hdev, connect_mask);
219b8e759b8SDaniel M. Lambea if (error) {
220b8e759b8SDaniel M. Lambea hid_err(hdev, "hw start failed\n");
22187fcb6a6SBenjamin Tissoires return error;
222b8e759b8SDaniel M. Lambea }
223b8e759b8SDaniel M. Lambea
224b8e759b8SDaniel M. Lambea error = cougar_bind_shared_data(hdev, cougar);
225b8e759b8SDaniel M. Lambea if (error)
226b8e759b8SDaniel M. Lambea goto fail_stop_and_cleanup;
227b8e759b8SDaniel M. Lambea
228b8e759b8SDaniel M. Lambea /* The custom vendor interface will use the hid_input registered
229b8e759b8SDaniel M. Lambea * for the keyboard interface, in order to send translated key codes
230b8e759b8SDaniel M. Lambea * to it.
231b8e759b8SDaniel M. Lambea */
232b8e759b8SDaniel M. Lambea if (hdev->collection->usage == HID_GD_KEYBOARD) {
233b8e759b8SDaniel M. Lambea list_for_each_entry_safe(hidinput, next, &hdev->inputs, list) {
234b8e759b8SDaniel M. Lambea if (hidinput->registered && hidinput->input != NULL) {
235b8e759b8SDaniel M. Lambea cougar->shared->input = hidinput->input;
236b8e759b8SDaniel M. Lambea cougar->shared->enabled = true;
237b8e759b8SDaniel M. Lambea break;
238b8e759b8SDaniel M. Lambea }
239b8e759b8SDaniel M. Lambea }
240b8e759b8SDaniel M. Lambea } else if (hdev->collection->usage == COUGAR_VENDOR_USAGE) {
2416b003a8dSDaniel M. Lambea /* Preinit the mapping table */
2426b003a8dSDaniel M. Lambea cougar_fix_g6_mapping();
243b8e759b8SDaniel M. Lambea error = hid_hw_open(hdev);
244b8e759b8SDaniel M. Lambea if (error)
245b8e759b8SDaniel M. Lambea goto fail_stop_and_cleanup;
246b8e759b8SDaniel M. Lambea }
247b8e759b8SDaniel M. Lambea return 0;
248b8e759b8SDaniel M. Lambea
249b8e759b8SDaniel M. Lambea fail_stop_and_cleanup:
250b8e759b8SDaniel M. Lambea hid_hw_stop(hdev);
251b8e759b8SDaniel M. Lambea return error;
252b8e759b8SDaniel M. Lambea }
253b8e759b8SDaniel M. Lambea
254b8e759b8SDaniel M. Lambea /*
255b8e759b8SDaniel M. Lambea * Convert events from vendor intf to input key events
256b8e759b8SDaniel M. Lambea */
cougar_raw_event(struct hid_device * hdev,struct hid_report * report,u8 * data,int size)257b8e759b8SDaniel M. Lambea static int cougar_raw_event(struct hid_device *hdev, struct hid_report *report,
258b8e759b8SDaniel M. Lambea u8 *data, int size)
259b8e759b8SDaniel M. Lambea {
260b8e759b8SDaniel M. Lambea struct cougar *cougar;
26175f1f19bSDaniel M. Lambea struct cougar_shared *shared;
262b8e759b8SDaniel M. Lambea unsigned char code, action;
263b8e759b8SDaniel M. Lambea int i;
264b8e759b8SDaniel M. Lambea
265b8e759b8SDaniel M. Lambea cougar = hid_get_drvdata(hdev);
26675f1f19bSDaniel M. Lambea shared = cougar->shared;
26775f1f19bSDaniel M. Lambea if (!cougar->special_intf || !shared)
268b8e759b8SDaniel M. Lambea return 0;
269b8e759b8SDaniel M. Lambea
27075f1f19bSDaniel M. Lambea if (!shared->enabled || !shared->input)
27175f1f19bSDaniel M. Lambea return -EPERM;
27275f1f19bSDaniel M. Lambea
273b8e759b8SDaniel M. Lambea code = data[COUGAR_FIELD_CODE];
274b8e759b8SDaniel M. Lambea action = data[COUGAR_FIELD_ACTION];
275b8e759b8SDaniel M. Lambea for (i = 0; cougar_mapping[i][0]; i++) {
276b8e759b8SDaniel M. Lambea if (code == cougar_mapping[i][0]) {
27775f1f19bSDaniel M. Lambea input_event(shared->input, EV_KEY,
278b8e759b8SDaniel M. Lambea cougar_mapping[i][1], action);
27975f1f19bSDaniel M. Lambea input_sync(shared->input);
28075f1f19bSDaniel M. Lambea return -EPERM;
281b8e759b8SDaniel M. Lambea }
282b8e759b8SDaniel M. Lambea }
28375f1f19bSDaniel M. Lambea /* Avoid warnings on the same unmapped key twice */
28475f1f19bSDaniel M. Lambea if (action != 0)
28575f1f19bSDaniel M. Lambea hid_warn(hdev, "unmapped special key code %0x: ignoring\n", code);
28675f1f19bSDaniel M. Lambea return -EPERM;
287b8e759b8SDaniel M. Lambea }
288b8e759b8SDaniel M. Lambea
cougar_remove(struct hid_device * hdev)289b8e759b8SDaniel M. Lambea static void cougar_remove(struct hid_device *hdev)
290b8e759b8SDaniel M. Lambea {
291b8e759b8SDaniel M. Lambea struct cougar *cougar = hid_get_drvdata(hdev);
292b8e759b8SDaniel M. Lambea
293b8e759b8SDaniel M. Lambea if (cougar) {
294b8e759b8SDaniel M. Lambea /* Stop the vendor intf to process more events */
295b8e759b8SDaniel M. Lambea if (cougar->shared)
296b8e759b8SDaniel M. Lambea cougar->shared->enabled = false;
297b8e759b8SDaniel M. Lambea if (cougar->special_intf)
298b8e759b8SDaniel M. Lambea hid_hw_close(hdev);
299b8e759b8SDaniel M. Lambea }
300b8e759b8SDaniel M. Lambea hid_hw_stop(hdev);
301b8e759b8SDaniel M. Lambea }
302b8e759b8SDaniel M. Lambea
cougar_param_set_g6_is_space(const char * val,const struct kernel_param * kp)3036b003a8dSDaniel M. Lambea static int cougar_param_set_g6_is_space(const char *val,
3046b003a8dSDaniel M. Lambea const struct kernel_param *kp)
3056b003a8dSDaniel M. Lambea {
3066b003a8dSDaniel M. Lambea int ret;
3076b003a8dSDaniel M. Lambea
3086b003a8dSDaniel M. Lambea ret = param_set_bool(val, kp);
3096b003a8dSDaniel M. Lambea if (ret)
3106b003a8dSDaniel M. Lambea return ret;
3116b003a8dSDaniel M. Lambea
3126b003a8dSDaniel M. Lambea cougar_fix_g6_mapping();
3136b003a8dSDaniel M. Lambea
3146b003a8dSDaniel M. Lambea return 0;
3156b003a8dSDaniel M. Lambea }
3166b003a8dSDaniel M. Lambea
3176b003a8dSDaniel M. Lambea static const struct kernel_param_ops cougar_g6_is_space_ops = {
3186b003a8dSDaniel M. Lambea .set = cougar_param_set_g6_is_space,
3196b003a8dSDaniel M. Lambea .get = param_get_bool,
3206b003a8dSDaniel M. Lambea };
3216b003a8dSDaniel M. Lambea module_param_cb(g6_is_space, &cougar_g6_is_space_ops, &g6_is_space, 0644);
3226b003a8dSDaniel M. Lambea
323857ce8c1SRikard Falkeborn static const struct hid_device_id cougar_id_table[] = {
324b8e759b8SDaniel M. Lambea { HID_USB_DEVICE(USB_VENDOR_ID_SOLID_YEAR,
325b8e759b8SDaniel M. Lambea USB_DEVICE_ID_COUGAR_500K_GAMING_KEYBOARD) },
326aeed35faSDaniel M. Lambea { HID_USB_DEVICE(USB_VENDOR_ID_SOLID_YEAR,
327aeed35faSDaniel M. Lambea USB_DEVICE_ID_COUGAR_700K_GAMING_KEYBOARD) },
328b8e759b8SDaniel M. Lambea {}
329b8e759b8SDaniel M. Lambea };
330b8e759b8SDaniel M. Lambea MODULE_DEVICE_TABLE(hid, cougar_id_table);
331b8e759b8SDaniel M. Lambea
332b8e759b8SDaniel M. Lambea static struct hid_driver cougar_driver = {
333b8e759b8SDaniel M. Lambea .name = "cougar",
334b8e759b8SDaniel M. Lambea .id_table = cougar_id_table,
335b8e759b8SDaniel M. Lambea .report_fixup = cougar_report_fixup,
336b8e759b8SDaniel M. Lambea .probe = cougar_probe,
337b8e759b8SDaniel M. Lambea .remove = cougar_remove,
338b8e759b8SDaniel M. Lambea .raw_event = cougar_raw_event,
339b8e759b8SDaniel M. Lambea };
340b8e759b8SDaniel M. Lambea
341b8e759b8SDaniel M. Lambea module_hid_driver(cougar_driver);
342