aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hid
diff options
context:
space:
mode:
authorMichael Poole <mdpoole@troilus.org>2010-02-06 12:24:36 -0500
committerJiri Kosina <jkosina@suse.cz>2010-02-10 08:57:33 -0500
commit128537cea464d919febeaea2000e256749f317eb (patch)
treee9d661d8ca243f90b32adda5e6b4c6115bc7767a /drivers/hid
parent90a006abf8015c8cab893555244d8fc673b24839 (diff)
HID: add a device driver for the Apple Magic Mouse.
The Magic Mouse requires that a driver send an unlock Report(Feature) command, similar to the Wacom wireless tablet and Sixaxis controller quirks. This turns on an Input Report that isn't published in the input Report descriptor that contains touch data (and usually overrides the normal motion and click Report). Because the mouse has only one switch and no scroll wheel, the driver (under control of parameters) emulates a middle button and scroll wheel. User space could also ignore and/or re-synthesize those events based on the reported events. Some user-space tools to talk to the mouse directly (that is, when it is not associated with the host's HIDP stack) are at http://github.com/entrope/linux-magicmouse Signed-off-by: Michael Poole <mdpoole@troilus.org> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/Kconfig10
-rw-r--r--drivers/hid/Makefile1
-rw-r--r--drivers/hid/hid-core.c1
-rw-r--r--drivers/hid/hid-ids.h1
-rw-r--r--drivers/hid/hid-magicmouse.c469
5 files changed, 482 insertions, 0 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 24d90ea246ce..ba14ec898258 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -183,6 +183,16 @@ config LOGIRUMBLEPAD2_FF
183 Say Y here if you want to enable force feedback support for Logitech 183 Say Y here if you want to enable force feedback support for Logitech
184 Rumblepad 2 devices. 184 Rumblepad 2 devices.
185 185
186config HID_MAGICMOUSE
187 tristate "Apple" if EMBEDDED
188 depends on BT_HIDP
189 default !EMBEDDED
190 ---help---
191 Support for the Apple Magic Mouse.
192
193 Say Y here if you want support for the multi-touch features of the
194 Apple Wireless "Magic" Mouse.
195
186config HID_MICROSOFT 196config HID_MICROSOFT
187 tristate "Microsoft" if EMBEDDED 197 tristate "Microsoft" if EMBEDDED
188 depends on USB_HID 198 depends on USB_HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 0de2dff5542c..45d81e9cd2de 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_HID_GYRATION) += hid-gyration.o
31obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o 31obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o
32obj-$(CONFIG_HID_KYE) += hid-kye.o 32obj-$(CONFIG_HID_KYE) += hid-kye.o
33obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o 33obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o
34obj-$(CONFIG_HID_MAGICMOUSE) += hid-magicmouse.o
34obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o 35obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o
35obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o 36obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o
36obj-$(CONFIG_HID_NTRIG) += hid-ntrig.o 37obj-$(CONFIG_HID_NTRIG) += hid-ntrig.o
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 66a91eb3e4c4..f038422f432c 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1254,6 +1254,7 @@ static const struct hid_device_id hid_blacklist[] = {
1254 { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ATV_IRCONTROL) }, 1254 { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ATV_IRCONTROL) },
1255 { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL4) }, 1255 { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL4) },
1256 { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MIGHTYMOUSE) }, 1256 { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MIGHTYMOUSE) },
1257 { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICMOUSE) },
1257 { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI) }, 1258 { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI) },
1258 { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ISO) }, 1259 { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ISO) },
1259 { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ANSI) }, 1260 { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ANSI) },
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 010368e649ed..11e521864abb 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -56,6 +56,7 @@
56 56
57#define USB_VENDOR_ID_APPLE 0x05ac 57#define USB_VENDOR_ID_APPLE 0x05ac
58#define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304 58#define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304
59#define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d
59#define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI 0x020e 60#define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI 0x020e
60#define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO 0x020f 61#define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO 0x020f
61#define USB_DEVICE_ID_APPLE_GEYSER_ANSI 0x0214 62#define USB_DEVICE_ID_APPLE_GEYSER_ANSI 0x0214
diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
new file mode 100644
index 000000000000..f94b3e43c5b6
--- /dev/null
+++ b/drivers/hid/hid-magicmouse.c
@@ -0,0 +1,469 @@
1/*
2 * Apple "Magic" Wireless Mouse driver
3 *
4 * Copyright (c) 2010 Michael Poole <mdpoole@troilus.org>
5 */
6
7/*
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the Free
10 * Software Foundation; either version 2 of the License, or (at your option)
11 * any later version.
12 */
13
14#include <linux/device.h>
15#include <linux/hid.h>
16#include <linux/module.h>
17#include <linux/usb.h>
18
19#include "hid-ids.h"
20
21static bool emulate_3button = 1;
22module_param(emulate_3button, bool, 0644);
23MODULE_PARM_DESC(emulate_3button, "Emulate a middle button");
24
25static int middle_button_start = -350;
26static int middle_button_stop = +350;
27
28static bool emulate_scroll_wheel = 1;
29module_param(emulate_scroll_wheel, bool, 0644);
30MODULE_PARM_DESC(emulate_scroll_wheel, "Emulate a scroll wheel");
31
32static bool report_touches = 1;
33module_param(report_touches, bool, 0644);
34MODULE_PARM_DESC(report_touches, "Emit touch records (otherwise, only use them for emulation)");
35
36static bool report_undeciphered = 0;
37module_param(report_undeciphered, bool, 0644);
38MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event");
39
40#define TOUCH_REPORT_ID 0x29
41/* These definitions are not precise, but they're close enough. (Bits
42 * 0x03 seem to indicate the aspect ratio of the touch, bits 0x70 seem
43 * to be some kind of bit mask -- 0x20 may be a near-field reading,
44 * and 0x40 is actual contact, and 0x10 may be a start/stop or change
45 * indication.)
46 */
47#define TOUCH_STATE_MASK 0xf0
48#define TOUCH_STATE_NONE 0x00
49#define TOUCH_STATE_START 0x30
50#define TOUCH_STATE_DRAG 0x40
51
52/**
53 * struct magicmouse_sc - Tracks Magic Mouse-specific data.
54 * @input: Input device through which we report events.
55 * @quirks: Currently unused.
56 * @last_timestamp: Timestamp from most recent (18-bit) touch report
57 * (units of milliseconds over short windows, but seems to
58 * increase faster when there are no touches).
59 * @delta_time: 18-bit difference between the two most recent touch
60 * reports from the mouse.
61 * @ntouches: Number of touches in most recent touch report.
62 * @scroll_accel: Number of consecutive scroll motions.
63 * @scroll_jiffies: Time of last scroll motion.
64 * @touches: Most recent data for a touch, indexed by tracking ID.
65 * @tracking_ids: Mapping of current touch input data to @touches.
66 */
67struct magicmouse_sc {
68 struct input_dev *input;
69 unsigned long quirks;
70
71 int last_timestamp;
72 int delta_time;
73 int ntouches;
74 int scroll_accel;
75 unsigned long scroll_jiffies;
76
77 struct {
78 short x;
79 short y;
80 short scroll_y;
81 u8 size;
82 } touches[16];
83 int tracking_ids[16];
84};
85
86static int magicmouse_firm_touch(struct magicmouse_sc *msc)
87{
88 int touch = -1;
89 int ii;
90
91 /* If there is only one "firm" touch, set touch to its
92 * tracking ID.
93 */
94 for (ii = 0; ii < msc->ntouches; ii++) {
95 int idx = msc->tracking_ids[ii];
96 if (msc->touches[idx].size < 8) {
97 /* Ignore this touch. */
98 } else if (touch >= 0) {
99 touch = -1;
100 break;
101 } else {
102 touch = idx;
103 }
104 }
105
106 return touch;
107}
108
109static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state)
110{
111 int last_state = test_bit(BTN_LEFT, msc->input->key) << 0 |
112 test_bit(BTN_RIGHT, msc->input->key) << 1 |
113 test_bit(BTN_MIDDLE, msc->input->key) << 2;
114
115 if (emulate_3button) {
116 int id;
117
118 /* If some button was pressed before, keep it held
119 * down. Otherwise, if there's exactly one firm
120 * touch, use that to override the mouse's guess.
121 */
122 if (state == 0) {
123 /* The button was released. */
124 } else if (last_state != 0) {
125 state = last_state;
126 } else if ((id = magicmouse_firm_touch(msc)) >= 0) {
127 int x = msc->touches[id].x;
128 if (x < middle_button_start)
129 state = 1;
130 else if (x > middle_button_stop)
131 state = 2;
132 else
133 state = 4;
134 } /* else: we keep the mouse's guess */
135
136 input_report_key(msc->input, BTN_MIDDLE, state & 4);
137 }
138
139 input_report_key(msc->input, BTN_LEFT, state & 1);
140 input_report_key(msc->input, BTN_RIGHT, state & 2);
141
142 if (state != last_state)
143 msc->scroll_accel = 0;
144}
145
146static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tdata)
147{
148 struct input_dev *input = msc->input;
149 __s32 x_y = tdata[0] << 8 | tdata[1] << 16 | tdata[2] << 24;
150 int misc = tdata[5] | tdata[6] << 8;
151 int id = (misc >> 6) & 15;
152 int x = x_y << 12 >> 20;
153 int y = -(x_y >> 20);
154
155 /* Store tracking ID and other fields. */
156 msc->tracking_ids[raw_id] = id;
157 msc->touches[id].x = x;
158 msc->touches[id].y = y;
159 msc->touches[id].size = misc & 63;
160
161 /* If requested, emulate a scroll wheel by detecting small
162 * vertical touch motions along the middle of the mouse.
163 */
164 if (emulate_scroll_wheel &&
165 middle_button_start < x && x < middle_button_stop) {
166 static const int accel_profile[] = {
167 256, 228, 192, 160, 128, 96, 64, 32,
168 };
169 unsigned long now = jiffies;
170 int step = msc->touches[id].scroll_y - y;
171
172 /* Reset acceleration after half a second. */
173 if (time_after(now, msc->scroll_jiffies + HZ / 2))
174 msc->scroll_accel = 0;
175
176 /* Calculate and apply the scroll motion. */
177 switch (tdata[7] & TOUCH_STATE_MASK) {
178 case TOUCH_STATE_START:
179 msc->touches[id].scroll_y = y;
180 msc->scroll_accel = min_t(int, msc->scroll_accel + 1,
181 ARRAY_SIZE(accel_profile) - 1);
182 break;
183 case TOUCH_STATE_DRAG:
184 step = step / accel_profile[msc->scroll_accel];
185 if (step != 0) {
186 msc->touches[id].scroll_y = y;
187 msc->scroll_jiffies = now;
188 input_report_rel(input, REL_WHEEL, step);
189 }
190 break;
191 }
192 }
193
194 /* Generate the input events for this touch. */
195 if (report_touches) {
196 int orientation = (misc >> 10) - 32;
197
198 input_report_abs(input, ABS_MT_TRACKING_ID, id);
199 input_report_abs(input, ABS_MT_TOUCH_MAJOR, tdata[3]);
200 input_report_abs(input, ABS_MT_TOUCH_MINOR, tdata[4]);
201 input_report_abs(input, ABS_MT_ORIENTATION, orientation);
202 input_report_abs(input, ABS_MT_POSITION_X, x);
203 input_report_abs(input, ABS_MT_POSITION_Y, y);
204
205 if (report_undeciphered) {
206 input_event(input, EV_MSC, MSC_RAW, tdata[7]);
207 }
208
209 input_mt_sync(input);
210 }
211}
212
213static int magicmouse_raw_event(struct hid_device *hdev,
214 struct hid_report *report, u8 *data, int size)
215{
216 struct magicmouse_sc *msc = hid_get_drvdata(hdev);
217 struct input_dev *input = msc->input;
218 int x, y, ts, ii, clicks;
219
220 switch (data[0]) {
221 case 0x10:
222 if (size != 6)
223 return 0;
224 x = (__s16)(data[2] | data[3] << 8);
225 y = (__s16)(data[4] | data[5] << 8);
226 clicks = data[1];
227 break;
228 case TOUCH_REPORT_ID:
229 /* Expect six bytes of prefix, and N*8 bytes of touch data. */
230 if (size < 6 || ((size - 6) % 8) != 0)
231 return 0;
232 ts = data[3] >> 6 | data[4] << 2 | data[5] << 10;
233 msc->delta_time = (ts - msc->last_timestamp) & 0x3ffff;
234 msc->last_timestamp = ts;
235 msc->ntouches = (size - 6) / 8;
236 for (ii = 0; ii < msc->ntouches; ii++)
237 magicmouse_emit_touch(msc, ii, data + ii * 8 + 6);
238 /* When emulating three-button mode, it is important
239 * to have the current touch information before
240 * generating a click event.
241 */
242 x = (signed char)data[1];
243 y = (signed char)data[2];
244 clicks = data[3];
245 break;
246 case 0x20: /* Theoretically battery status (0-100), but I have
247 * never seen it -- maybe it is only upon request.
248 */
249 case 0x60: /* Unknown, maybe laser on/off. */
250 case 0x61: /* Laser reflection status change.
251 * data[1]: 0 = spotted, 1 = lost
252 */
253 default:
254 return 0;
255 }
256
257 magicmouse_emit_buttons(msc, clicks & 3);
258 input_report_rel(input, REL_X, x);
259 input_report_rel(input, REL_Y, y);
260 input_sync(input);
261 return 1;
262}
263
264static int magicmouse_input_open(struct input_dev *dev)
265{
266 struct hid_device *hid = input_get_drvdata(dev);
267
268 return hid->ll_driver->open(hid);
269}
270
271static void magicmouse_input_close(struct input_dev *dev)
272{
273 struct hid_device *hid = input_get_drvdata(dev);
274
275 hid->ll_driver->close(hid);
276}
277
278static void magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev)
279{
280 input_set_drvdata(input, hdev);
281 input->event = hdev->ll_driver->hidinput_input_event;
282 input->open = magicmouse_input_open;
283 input->close = magicmouse_input_close;
284
285 input->name = hdev->name;
286 input->phys = hdev->phys;
287 input->uniq = hdev->uniq;
288 input->id.bustype = hdev->bus;
289 input->id.vendor = hdev->vendor;
290 input->id.product = hdev->product;
291 input->id.version = hdev->version;
292 input->dev.parent = hdev->dev.parent;
293
294 set_bit(EV_KEY, input->evbit);
295 set_bit(BTN_LEFT, input->keybit);
296 set_bit(BTN_RIGHT, input->keybit);
297 if (emulate_3button)
298 set_bit(BTN_MIDDLE, input->keybit);
299 set_bit(BTN_TOOL_FINGER, input->keybit);
300
301 set_bit(EV_REL, input->evbit);
302 set_bit(REL_X, input->relbit);
303 set_bit(REL_Y, input->relbit);
304 if (emulate_scroll_wheel)
305 set_bit(REL_WHEEL, input->relbit);
306
307 if (report_touches) {
308 set_bit(EV_ABS, input->evbit);
309
310 set_bit(ABS_MT_TRACKING_ID, input->absbit);
311 input->absmin[ABS_MT_TRACKING_ID] = 0;
312 input->absmax[ABS_MT_TRACKING_ID] = 15;
313 input->absfuzz[ABS_MT_TRACKING_ID] = 0;
314
315 set_bit(ABS_MT_TOUCH_MAJOR, input->absbit);
316 input->absmin[ABS_MT_TOUCH_MAJOR] = 0;
317 input->absmax[ABS_MT_TOUCH_MAJOR] = 255;
318 input->absfuzz[ABS_MT_TOUCH_MAJOR] = 4;
319
320 set_bit(ABS_MT_TOUCH_MINOR, input->absbit);
321 input->absmin[ABS_MT_TOUCH_MINOR] = 0;
322 input->absmax[ABS_MT_TOUCH_MINOR] = 255;
323 input->absfuzz[ABS_MT_TOUCH_MINOR] = 4;
324
325 set_bit(ABS_MT_ORIENTATION, input->absbit);
326 input->absmin[ABS_MT_ORIENTATION] = -32;
327 input->absmax[ABS_MT_ORIENTATION] = 31;
328 input->absfuzz[ABS_MT_ORIENTATION] = 1;
329
330 set_bit(ABS_MT_POSITION_X, input->absbit);
331 input->absmin[ABS_MT_POSITION_X] = -1100;
332 input->absmax[ABS_MT_POSITION_X] = 1358;
333 input->absfuzz[ABS_MT_POSITION_X] = 4;
334
335 /* Note: Touch Y position from the device is inverted relative
336 * to how pointer motion is reported (and relative to how USB
337 * HID recommends the coordinates work). This driver keeps
338 * the origin at the same position, and just uses the additive
339 * inverse of the reported Y.
340 */
341 set_bit(ABS_MT_POSITION_Y, input->absbit);
342 input->absmin[ABS_MT_POSITION_Y] = -1589;
343 input->absmax[ABS_MT_POSITION_Y] = 2047;
344 input->absfuzz[ABS_MT_POSITION_Y] = 4;
345 }
346
347 if (report_undeciphered) {
348 set_bit(EV_MSC, input->evbit);
349 set_bit(MSC_RAW, input->mscbit);
350 }
351}
352
353static int magicmouse_probe(struct hid_device *hdev,
354 const struct hid_device_id *id)
355{
356 __u8 feature_1[] = { 0xd7, 0x01 };
357 __u8 feature_2[] = { 0xf8, 0x01, 0x32 };
358 struct input_dev *input;
359 struct magicmouse_sc *msc;
360 struct hid_report *report;
361 int ret;
362
363 msc = kzalloc(sizeof(*msc), GFP_KERNEL);
364 if (msc == NULL) {
365 dev_err(&hdev->dev, "can't alloc magicmouse descriptor\n");
366 return -ENOMEM;
367 }
368
369 msc->quirks = id->driver_data;
370 hid_set_drvdata(hdev, msc);
371
372 ret = hid_parse(hdev);
373 if (ret) {
374 dev_err(&hdev->dev, "magicmouse hid parse failed\n");
375 goto err_free;
376 }
377
378 ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
379 if (ret) {
380 dev_err(&hdev->dev, "magicmouse hw start failed\n");
381 goto err_free;
382 }
383
384 report = hid_register_report(hdev, HID_INPUT_REPORT, TOUCH_REPORT_ID);
385 if (!report) {
386 dev_err(&hdev->dev, "unable to register touch report\n");
387 ret = -ENOMEM;
388 goto err_free;
389 }
390 report->size = 6;
391
392 ret = hdev->hid_output_raw_report(hdev, feature_1, sizeof(feature_1),
393 HID_FEATURE_REPORT);
394 if (ret != sizeof(feature_1)) {
395 dev_err(&hdev->dev, "unable to request touch data (1:%d)\n",
396 ret);
397 goto err_free;
398 }
399 ret = hdev->hid_output_raw_report(hdev, feature_2,
400 sizeof(feature_2), HID_FEATURE_REPORT);
401 if (ret != sizeof(feature_2)) {
402 dev_err(&hdev->dev, "unable to request touch data (2:%d)\n",
403 ret);
404 goto err_free;
405 }
406
407 input = input_allocate_device();
408 if (!input) {
409 dev_err(&hdev->dev, "can't alloc input device\n");
410 ret = -ENOMEM;
411 goto err_free;
412 }
413 magicmouse_setup_input(input, hdev);
414
415 ret = input_register_device(input);
416 if (ret) {
417 dev_err(&hdev->dev, "input device registration failed\n");
418 goto err_both;
419 }
420 msc->input = input;
421
422 return 0;
423 err_both:
424 input_free_device(input);
425 err_free:
426 kfree(msc);
427 return ret;
428}
429
430static void magicmouse_remove(struct hid_device *hdev)
431{
432 hid_hw_stop(hdev);
433 kfree(hid_get_drvdata(hdev));
434}
435
436static const struct hid_device_id magic_mice[] = {
437 { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICMOUSE),
438 .driver_data = 0 },
439 { }
440};
441MODULE_DEVICE_TABLE(hid, magic_mice);
442
443static struct hid_driver magicmouse_driver = {
444 .name = "magicmouse",
445 .id_table = magic_mice,
446 .probe = magicmouse_probe,
447 .remove = magicmouse_remove,
448 .raw_event = magicmouse_raw_event,
449};
450
451static int __init magicmouse_init(void)
452{
453 int ret;
454
455 ret = hid_register_driver(&magicmouse_driver);
456 if (ret)
457 printk(KERN_ERR "can't register magicmouse driver\n");
458
459 return ret;
460}
461
462static void __exit magicmouse_exit(void)
463{
464 hid_unregister_driver(&magicmouse_driver);
465}
466
467module_init(magicmouse_init);
468module_exit(magicmouse_exit);
469MODULE_LICENSE("GPL");