1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * USB Synaptics device driver |
4 | * |
5 | * Copyright (c) 2002 Rob Miller (rob@inpharmatica . co . uk) |
6 | * Copyright (c) 2003 Ron Lee (ron@debian.org) |
7 | * cPad driver for kernel 2.4 |
8 | * |
9 | * Copyright (c) 2004 Jan Steinhoff (cpad@jan-steinhoff . de) |
10 | * Copyright (c) 2004 Ron Lee (ron@debian.org) |
11 | * rewritten for kernel 2.6 |
12 | * |
13 | * cPad display character device part is not included. It can be found at |
14 | * http://jan-steinhoff.de/linux/synaptics-usb.html |
15 | * |
16 | * Bases on: usb_skeleton.c v2.2 by Greg Kroah-Hartman |
17 | * drivers/hid/usbhid/usbmouse.c by Vojtech Pavlik |
18 | * drivers/input/mouse/synaptics.c by Peter Osterlund |
19 | * |
20 | * Trademarks are the property of their respective owners. |
21 | */ |
22 | |
23 | /* |
24 | * There are three different types of Synaptics USB devices: Touchpads, |
25 | * touchsticks (or trackpoints), and touchscreens. Touchpads are well supported |
26 | * by this driver, touchstick support has not been tested much yet, and |
27 | * touchscreens have not been tested at all. |
28 | * |
29 | * Up to three alternate settings are possible: |
30 | * setting 0: one int endpoint for relative movement (used by usbhid.ko) |
31 | * setting 1: one int endpoint for absolute finger position |
32 | * setting 2 (cPad only): one int endpoint for absolute finger position and |
33 | * two bulk endpoints for the display (in/out) |
34 | * This driver uses setting 1. |
35 | */ |
36 | |
37 | #include <linux/kernel.h> |
38 | #include <linux/slab.h> |
39 | #include <linux/module.h> |
40 | #include <linux/moduleparam.h> |
41 | #include <linux/usb.h> |
42 | #include <linux/input.h> |
43 | #include <linux/usb/input.h> |
44 | |
45 | #define USB_VENDOR_ID_SYNAPTICS 0x06cb |
46 | #define USB_DEVICE_ID_SYNAPTICS_TP 0x0001 /* Synaptics USB TouchPad */ |
47 | #define USB_DEVICE_ID_SYNAPTICS_INT_TP 0x0002 /* Integrated USB TouchPad */ |
48 | #define USB_DEVICE_ID_SYNAPTICS_CPAD 0x0003 /* Synaptics cPad */ |
49 | #define USB_DEVICE_ID_SYNAPTICS_TS 0x0006 /* Synaptics TouchScreen */ |
50 | #define USB_DEVICE_ID_SYNAPTICS_STICK 0x0007 /* Synaptics USB Styk */ |
51 | #define USB_DEVICE_ID_SYNAPTICS_WP 0x0008 /* Synaptics USB WheelPad */ |
52 | #define USB_DEVICE_ID_SYNAPTICS_COMP_TP 0x0009 /* Composite USB TouchPad */ |
53 | #define USB_DEVICE_ID_SYNAPTICS_WTP 0x0010 /* Wireless TouchPad */ |
54 | #define USB_DEVICE_ID_SYNAPTICS_DPAD 0x0013 /* DisplayPad */ |
55 | |
56 | #define SYNUSB_TOUCHPAD (1 << 0) |
57 | #define SYNUSB_STICK (1 << 1) |
58 | #define SYNUSB_TOUCHSCREEN (1 << 2) |
59 | #define SYNUSB_AUXDISPLAY (1 << 3) /* For cPad */ |
60 | #define SYNUSB_COMBO (1 << 4) /* Composite device (TP + stick) */ |
61 | #define SYNUSB_IO_ALWAYS (1 << 5) |
62 | |
63 | #define USB_DEVICE_SYNAPTICS(prod, kind) \ |
64 | USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, \ |
65 | USB_DEVICE_ID_SYNAPTICS_##prod), \ |
66 | .driver_info = (kind), |
67 | |
68 | #define SYNUSB_RECV_SIZE 8 |
69 | |
70 | #define XMIN_NOMINAL 1472 |
71 | #define XMAX_NOMINAL 5472 |
72 | #define YMIN_NOMINAL 1408 |
73 | #define YMAX_NOMINAL 4448 |
74 | |
75 | struct synusb { |
76 | struct usb_device *udev; |
77 | struct usb_interface *intf; |
78 | struct urb *urb; |
79 | unsigned char *data; |
80 | |
81 | /* serialize access to open/suspend */ |
82 | struct mutex pm_mutex; |
83 | bool is_open; |
84 | |
85 | /* input device related data structures */ |
86 | struct input_dev *input; |
87 | char name[128]; |
88 | char phys[64]; |
89 | |
90 | /* characteristics of the device */ |
91 | unsigned long flags; |
92 | }; |
93 | |
94 | static void synusb_report_buttons(struct synusb *synusb) |
95 | { |
96 | struct input_dev *input_dev = synusb->input; |
97 | |
98 | input_report_key(dev: input_dev, BTN_LEFT, value: synusb->data[1] & 0x04); |
99 | input_report_key(dev: input_dev, BTN_RIGHT, value: synusb->data[1] & 0x01); |
100 | input_report_key(dev: input_dev, BTN_MIDDLE, value: synusb->data[1] & 0x02); |
101 | } |
102 | |
103 | static void synusb_report_stick(struct synusb *synusb) |
104 | { |
105 | struct input_dev *input_dev = synusb->input; |
106 | int x, y; |
107 | unsigned int pressure; |
108 | |
109 | pressure = synusb->data[6]; |
110 | x = (s16)(be16_to_cpup(p: (__be16 *)&synusb->data[2]) << 3) >> 7; |
111 | y = (s16)(be16_to_cpup(p: (__be16 *)&synusb->data[4]) << 3) >> 7; |
112 | |
113 | if (pressure > 0) { |
114 | input_report_rel(dev: input_dev, REL_X, value: x); |
115 | input_report_rel(dev: input_dev, REL_Y, value: -y); |
116 | } |
117 | |
118 | input_report_abs(dev: input_dev, ABS_PRESSURE, value: pressure); |
119 | |
120 | synusb_report_buttons(synusb); |
121 | |
122 | input_sync(dev: input_dev); |
123 | } |
124 | |
125 | static void synusb_report_touchpad(struct synusb *synusb) |
126 | { |
127 | struct input_dev *input_dev = synusb->input; |
128 | unsigned int num_fingers, tool_width; |
129 | unsigned int x, y; |
130 | unsigned int pressure, w; |
131 | |
132 | pressure = synusb->data[6]; |
133 | x = be16_to_cpup(p: (__be16 *)&synusb->data[2]); |
134 | y = be16_to_cpup(p: (__be16 *)&synusb->data[4]); |
135 | w = synusb->data[0] & 0x0f; |
136 | |
137 | if (pressure > 0) { |
138 | num_fingers = 1; |
139 | tool_width = 5; |
140 | switch (w) { |
141 | case 0 ... 1: |
142 | num_fingers = 2 + w; |
143 | break; |
144 | |
145 | case 2: /* pen, pretend its a finger */ |
146 | break; |
147 | |
148 | case 4 ... 15: |
149 | tool_width = w; |
150 | break; |
151 | } |
152 | } else { |
153 | num_fingers = 0; |
154 | tool_width = 0; |
155 | } |
156 | |
157 | /* |
158 | * Post events |
159 | * BTN_TOUCH has to be first as mousedev relies on it when doing |
160 | * absolute -> relative conversion |
161 | */ |
162 | |
163 | if (pressure > 30) |
164 | input_report_key(dev: input_dev, BTN_TOUCH, value: 1); |
165 | if (pressure < 25) |
166 | input_report_key(dev: input_dev, BTN_TOUCH, value: 0); |
167 | |
168 | if (num_fingers > 0) { |
169 | input_report_abs(dev: input_dev, ABS_X, value: x); |
170 | input_report_abs(dev: input_dev, ABS_Y, |
171 | YMAX_NOMINAL + YMIN_NOMINAL - y); |
172 | } |
173 | |
174 | input_report_abs(dev: input_dev, ABS_PRESSURE, value: pressure); |
175 | input_report_abs(dev: input_dev, ABS_TOOL_WIDTH, value: tool_width); |
176 | |
177 | input_report_key(dev: input_dev, BTN_TOOL_FINGER, value: num_fingers == 1); |
178 | input_report_key(dev: input_dev, BTN_TOOL_DOUBLETAP, value: num_fingers == 2); |
179 | input_report_key(dev: input_dev, BTN_TOOL_TRIPLETAP, value: num_fingers == 3); |
180 | |
181 | synusb_report_buttons(synusb); |
182 | if (synusb->flags & SYNUSB_AUXDISPLAY) |
183 | input_report_key(dev: input_dev, BTN_MIDDLE, value: synusb->data[1] & 0x08); |
184 | |
185 | input_sync(dev: input_dev); |
186 | } |
187 | |
188 | static void synusb_irq(struct urb *urb) |
189 | { |
190 | struct synusb *synusb = urb->context; |
191 | int error; |
192 | |
193 | /* Check our status in case we need to bail out early. */ |
194 | switch (urb->status) { |
195 | case 0: |
196 | usb_mark_last_busy(udev: synusb->udev); |
197 | break; |
198 | |
199 | /* Device went away so don't keep trying to read from it. */ |
200 | case -ECONNRESET: |
201 | case -ENOENT: |
202 | case -ESHUTDOWN: |
203 | return; |
204 | |
205 | default: |
206 | goto resubmit; |
207 | break; |
208 | } |
209 | |
210 | if (synusb->flags & SYNUSB_STICK) |
211 | synusb_report_stick(synusb); |
212 | else |
213 | synusb_report_touchpad(synusb); |
214 | |
215 | resubmit: |
216 | error = usb_submit_urb(urb, GFP_ATOMIC); |
217 | if (error && error != -EPERM) |
218 | dev_err(&synusb->intf->dev, |
219 | "%s - usb_submit_urb failed with result: %d" , |
220 | __func__, error); |
221 | } |
222 | |
223 | static struct usb_endpoint_descriptor * |
224 | synusb_get_in_endpoint(struct usb_host_interface *iface) |
225 | { |
226 | |
227 | struct usb_endpoint_descriptor *endpoint; |
228 | int i; |
229 | |
230 | for (i = 0; i < iface->desc.bNumEndpoints; ++i) { |
231 | endpoint = &iface->endpoint[i].desc; |
232 | |
233 | if (usb_endpoint_is_int_in(epd: endpoint)) { |
234 | /* we found our interrupt in endpoint */ |
235 | return endpoint; |
236 | } |
237 | } |
238 | |
239 | return NULL; |
240 | } |
241 | |
242 | static int synusb_open(struct input_dev *dev) |
243 | { |
244 | struct synusb *synusb = input_get_drvdata(dev); |
245 | int retval; |
246 | |
247 | retval = usb_autopm_get_interface(intf: synusb->intf); |
248 | if (retval) { |
249 | dev_err(&synusb->intf->dev, |
250 | "%s - usb_autopm_get_interface failed, error: %d\n" , |
251 | __func__, retval); |
252 | return retval; |
253 | } |
254 | |
255 | mutex_lock(&synusb->pm_mutex); |
256 | retval = usb_submit_urb(urb: synusb->urb, GFP_KERNEL); |
257 | if (retval) { |
258 | dev_err(&synusb->intf->dev, |
259 | "%s - usb_submit_urb failed, error: %d\n" , |
260 | __func__, retval); |
261 | retval = -EIO; |
262 | goto out; |
263 | } |
264 | |
265 | synusb->intf->needs_remote_wakeup = 1; |
266 | synusb->is_open = true; |
267 | |
268 | out: |
269 | mutex_unlock(lock: &synusb->pm_mutex); |
270 | usb_autopm_put_interface(intf: synusb->intf); |
271 | return retval; |
272 | } |
273 | |
274 | static void synusb_close(struct input_dev *dev) |
275 | { |
276 | struct synusb *synusb = input_get_drvdata(dev); |
277 | int autopm_error; |
278 | |
279 | autopm_error = usb_autopm_get_interface(intf: synusb->intf); |
280 | |
281 | mutex_lock(&synusb->pm_mutex); |
282 | usb_kill_urb(urb: synusb->urb); |
283 | synusb->intf->needs_remote_wakeup = 0; |
284 | synusb->is_open = false; |
285 | mutex_unlock(lock: &synusb->pm_mutex); |
286 | |
287 | if (!autopm_error) |
288 | usb_autopm_put_interface(intf: synusb->intf); |
289 | } |
290 | |
291 | static int synusb_probe(struct usb_interface *intf, |
292 | const struct usb_device_id *id) |
293 | { |
294 | struct usb_device *udev = interface_to_usbdev(intf); |
295 | struct usb_endpoint_descriptor *ep; |
296 | struct synusb *synusb; |
297 | struct input_dev *input_dev; |
298 | unsigned int intf_num = intf->cur_altsetting->desc.bInterfaceNumber; |
299 | unsigned int altsetting = min(intf->num_altsetting, 1U); |
300 | int error; |
301 | |
302 | error = usb_set_interface(dev: udev, ifnum: intf_num, alternate: altsetting); |
303 | if (error) { |
304 | dev_err(&udev->dev, |
305 | "Can not set alternate setting to %i, error: %i" , |
306 | altsetting, error); |
307 | return error; |
308 | } |
309 | |
310 | ep = synusb_get_in_endpoint(iface: intf->cur_altsetting); |
311 | if (!ep) |
312 | return -ENODEV; |
313 | |
314 | synusb = kzalloc(size: sizeof(*synusb), GFP_KERNEL); |
315 | input_dev = input_allocate_device(); |
316 | if (!synusb || !input_dev) { |
317 | error = -ENOMEM; |
318 | goto err_free_mem; |
319 | } |
320 | |
321 | synusb->udev = udev; |
322 | synusb->intf = intf; |
323 | synusb->input = input_dev; |
324 | mutex_init(&synusb->pm_mutex); |
325 | |
326 | synusb->flags = id->driver_info; |
327 | if (synusb->flags & SYNUSB_COMBO) { |
328 | /* |
329 | * This is a combo device, we need to set proper |
330 | * capability, depending on the interface. |
331 | */ |
332 | synusb->flags |= intf_num == 1 ? |
333 | SYNUSB_STICK : SYNUSB_TOUCHPAD; |
334 | } |
335 | |
336 | synusb->urb = usb_alloc_urb(iso_packets: 0, GFP_KERNEL); |
337 | if (!synusb->urb) { |
338 | error = -ENOMEM; |
339 | goto err_free_mem; |
340 | } |
341 | |
342 | synusb->data = usb_alloc_coherent(dev: udev, SYNUSB_RECV_SIZE, GFP_KERNEL, |
343 | dma: &synusb->urb->transfer_dma); |
344 | if (!synusb->data) { |
345 | error = -ENOMEM; |
346 | goto err_free_urb; |
347 | } |
348 | |
349 | usb_fill_int_urb(urb: synusb->urb, dev: udev, |
350 | usb_rcvintpipe(udev, ep->bEndpointAddress), |
351 | transfer_buffer: synusb->data, SYNUSB_RECV_SIZE, |
352 | complete_fn: synusb_irq, context: synusb, |
353 | interval: ep->bInterval); |
354 | synusb->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; |
355 | |
356 | if (udev->manufacturer) |
357 | strscpy(p: synusb->name, q: udev->manufacturer, |
358 | size: sizeof(synusb->name)); |
359 | |
360 | if (udev->product) { |
361 | if (udev->manufacturer) |
362 | strlcat(p: synusb->name, q: " " , avail: sizeof(synusb->name)); |
363 | strlcat(p: synusb->name, q: udev->product, avail: sizeof(synusb->name)); |
364 | } |
365 | |
366 | if (!strlen(synusb->name)) |
367 | snprintf(buf: synusb->name, size: sizeof(synusb->name), |
368 | fmt: "USB Synaptics Device %04x:%04x" , |
369 | le16_to_cpu(udev->descriptor.idVendor), |
370 | le16_to_cpu(udev->descriptor.idProduct)); |
371 | |
372 | if (synusb->flags & SYNUSB_STICK) |
373 | strlcat(p: synusb->name, q: " (Stick)" , avail: sizeof(synusb->name)); |
374 | |
375 | usb_make_path(dev: udev, buf: synusb->phys, size: sizeof(synusb->phys)); |
376 | strlcat(p: synusb->phys, q: "/input0" , avail: sizeof(synusb->phys)); |
377 | |
378 | input_dev->name = synusb->name; |
379 | input_dev->phys = synusb->phys; |
380 | usb_to_input_id(dev: udev, id: &input_dev->id); |
381 | input_dev->dev.parent = &synusb->intf->dev; |
382 | |
383 | if (!(synusb->flags & SYNUSB_IO_ALWAYS)) { |
384 | input_dev->open = synusb_open; |
385 | input_dev->close = synusb_close; |
386 | } |
387 | |
388 | input_set_drvdata(dev: input_dev, data: synusb); |
389 | |
390 | __set_bit(EV_ABS, input_dev->evbit); |
391 | __set_bit(EV_KEY, input_dev->evbit); |
392 | |
393 | if (synusb->flags & SYNUSB_STICK) { |
394 | __set_bit(EV_REL, input_dev->evbit); |
395 | __set_bit(REL_X, input_dev->relbit); |
396 | __set_bit(REL_Y, input_dev->relbit); |
397 | __set_bit(INPUT_PROP_POINTING_STICK, input_dev->propbit); |
398 | input_set_abs_params(dev: input_dev, ABS_PRESSURE, min: 0, max: 127, fuzz: 0, flat: 0); |
399 | } else { |
400 | input_set_abs_params(dev: input_dev, ABS_X, |
401 | XMIN_NOMINAL, XMAX_NOMINAL, fuzz: 0, flat: 0); |
402 | input_set_abs_params(dev: input_dev, ABS_Y, |
403 | YMIN_NOMINAL, YMAX_NOMINAL, fuzz: 0, flat: 0); |
404 | input_set_abs_params(dev: input_dev, ABS_PRESSURE, min: 0, max: 255, fuzz: 0, flat: 0); |
405 | input_set_abs_params(dev: input_dev, ABS_TOOL_WIDTH, min: 0, max: 15, fuzz: 0, flat: 0); |
406 | __set_bit(BTN_TOUCH, input_dev->keybit); |
407 | __set_bit(BTN_TOOL_FINGER, input_dev->keybit); |
408 | __set_bit(BTN_TOOL_DOUBLETAP, input_dev->keybit); |
409 | __set_bit(BTN_TOOL_TRIPLETAP, input_dev->keybit); |
410 | } |
411 | |
412 | if (synusb->flags & SYNUSB_TOUCHSCREEN) |
413 | __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); |
414 | else |
415 | __set_bit(INPUT_PROP_POINTER, input_dev->propbit); |
416 | |
417 | __set_bit(BTN_LEFT, input_dev->keybit); |
418 | __set_bit(BTN_RIGHT, input_dev->keybit); |
419 | __set_bit(BTN_MIDDLE, input_dev->keybit); |
420 | |
421 | usb_set_intfdata(intf, data: synusb); |
422 | |
423 | if (synusb->flags & SYNUSB_IO_ALWAYS) { |
424 | error = synusb_open(dev: input_dev); |
425 | if (error) |
426 | goto err_free_dma; |
427 | } |
428 | |
429 | error = input_register_device(input_dev); |
430 | if (error) { |
431 | dev_err(&udev->dev, |
432 | "Failed to register input device, error %d\n" , |
433 | error); |
434 | goto err_stop_io; |
435 | } |
436 | |
437 | return 0; |
438 | |
439 | err_stop_io: |
440 | if (synusb->flags & SYNUSB_IO_ALWAYS) |
441 | synusb_close(dev: synusb->input); |
442 | err_free_dma: |
443 | usb_free_coherent(dev: udev, SYNUSB_RECV_SIZE, addr: synusb->data, |
444 | dma: synusb->urb->transfer_dma); |
445 | err_free_urb: |
446 | usb_free_urb(urb: synusb->urb); |
447 | err_free_mem: |
448 | input_free_device(dev: input_dev); |
449 | kfree(objp: synusb); |
450 | usb_set_intfdata(intf, NULL); |
451 | |
452 | return error; |
453 | } |
454 | |
455 | static void synusb_disconnect(struct usb_interface *intf) |
456 | { |
457 | struct synusb *synusb = usb_get_intfdata(intf); |
458 | struct usb_device *udev = interface_to_usbdev(intf); |
459 | |
460 | if (synusb->flags & SYNUSB_IO_ALWAYS) |
461 | synusb_close(dev: synusb->input); |
462 | |
463 | input_unregister_device(synusb->input); |
464 | |
465 | usb_free_coherent(dev: udev, SYNUSB_RECV_SIZE, addr: synusb->data, |
466 | dma: synusb->urb->transfer_dma); |
467 | usb_free_urb(urb: synusb->urb); |
468 | kfree(objp: synusb); |
469 | |
470 | usb_set_intfdata(intf, NULL); |
471 | } |
472 | |
473 | static int synusb_suspend(struct usb_interface *intf, pm_message_t message) |
474 | { |
475 | struct synusb *synusb = usb_get_intfdata(intf); |
476 | |
477 | mutex_lock(&synusb->pm_mutex); |
478 | usb_kill_urb(urb: synusb->urb); |
479 | mutex_unlock(lock: &synusb->pm_mutex); |
480 | |
481 | return 0; |
482 | } |
483 | |
484 | static int synusb_resume(struct usb_interface *intf) |
485 | { |
486 | struct synusb *synusb = usb_get_intfdata(intf); |
487 | int retval = 0; |
488 | |
489 | mutex_lock(&synusb->pm_mutex); |
490 | |
491 | if ((synusb->is_open || (synusb->flags & SYNUSB_IO_ALWAYS)) && |
492 | usb_submit_urb(urb: synusb->urb, GFP_NOIO) < 0) { |
493 | retval = -EIO; |
494 | } |
495 | |
496 | mutex_unlock(lock: &synusb->pm_mutex); |
497 | |
498 | return retval; |
499 | } |
500 | |
501 | static int synusb_pre_reset(struct usb_interface *intf) |
502 | { |
503 | struct synusb *synusb = usb_get_intfdata(intf); |
504 | |
505 | mutex_lock(&synusb->pm_mutex); |
506 | usb_kill_urb(urb: synusb->urb); |
507 | |
508 | return 0; |
509 | } |
510 | |
511 | static int synusb_post_reset(struct usb_interface *intf) |
512 | { |
513 | struct synusb *synusb = usb_get_intfdata(intf); |
514 | int retval = 0; |
515 | |
516 | if ((synusb->is_open || (synusb->flags & SYNUSB_IO_ALWAYS)) && |
517 | usb_submit_urb(urb: synusb->urb, GFP_NOIO) < 0) { |
518 | retval = -EIO; |
519 | } |
520 | |
521 | mutex_unlock(lock: &synusb->pm_mutex); |
522 | |
523 | return retval; |
524 | } |
525 | |
526 | static int synusb_reset_resume(struct usb_interface *intf) |
527 | { |
528 | return synusb_resume(intf); |
529 | } |
530 | |
531 | static const struct usb_device_id synusb_idtable[] = { |
532 | { USB_DEVICE_SYNAPTICS(TP, SYNUSB_TOUCHPAD) }, |
533 | { USB_DEVICE_SYNAPTICS(INT_TP, SYNUSB_TOUCHPAD) }, |
534 | { USB_DEVICE_SYNAPTICS(CPAD, |
535 | SYNUSB_TOUCHPAD | SYNUSB_AUXDISPLAY | SYNUSB_IO_ALWAYS) }, |
536 | { USB_DEVICE_SYNAPTICS(TS, SYNUSB_TOUCHSCREEN) }, |
537 | { USB_DEVICE_SYNAPTICS(STICK, SYNUSB_STICK) }, |
538 | { USB_DEVICE_SYNAPTICS(WP, SYNUSB_TOUCHPAD) }, |
539 | { USB_DEVICE_SYNAPTICS(COMP_TP, SYNUSB_COMBO) }, |
540 | { USB_DEVICE_SYNAPTICS(WTP, SYNUSB_TOUCHPAD) }, |
541 | { USB_DEVICE_SYNAPTICS(DPAD, SYNUSB_TOUCHPAD) }, |
542 | { } |
543 | }; |
544 | MODULE_DEVICE_TABLE(usb, synusb_idtable); |
545 | |
546 | static struct usb_driver synusb_driver = { |
547 | .name = "synaptics_usb" , |
548 | .probe = synusb_probe, |
549 | .disconnect = synusb_disconnect, |
550 | .id_table = synusb_idtable, |
551 | .suspend = synusb_suspend, |
552 | .resume = synusb_resume, |
553 | .pre_reset = synusb_pre_reset, |
554 | .post_reset = synusb_post_reset, |
555 | .reset_resume = synusb_reset_resume, |
556 | .supports_autosuspend = 1, |
557 | }; |
558 | |
559 | module_usb_driver(synusb_driver); |
560 | |
561 | MODULE_AUTHOR("Rob Miller <rob@inpharmatica.co.uk>, " |
562 | "Ron Lee <ron@debian.org>, " |
563 | "Jan Steinhoff <cpad@jan-steinhoff.de>" ); |
564 | MODULE_DESCRIPTION("Synaptics USB device driver" ); |
565 | MODULE_LICENSE("GPL" ); |
566 | |