Prev: [PATCH 0/3] Provide a zero-copy method on KVM virtio-net.
Next: [PATCH 1/1] MFD: twl4030-core: Fix a typo in the twl_has_pwrbutton definition
From: Jiri Kosina on 10 Feb 2010 08:10 On Tue, 9 Feb 2010, Michael Poole wrote: > >From da3e2b0330fccd584ad79495853e638e9652155b Mon Sep 17 00:00:00 2001 > From: Michael Poole <mdpoole(a)troilus.org> > Date: Sat, 6 Feb 2010 12:24:36 -0500 > Subject: Add a device driver for the Apple Magic Mouse. > > Signed-off-by: Michael Poole <mdpoole(a)troilus.org> > --- > drivers/hid/Kconfig | 10 + > drivers/hid/Makefile | 1 + > drivers/hid/hid-core.c | 1 + > drivers/hid/hid-ids.h | 1 + > drivers/hid/hid-magicmouse.c | 469 ++++++++++++++++++++++++++++++++++++++++++ > 5 files changed, 482 insertions(+), 0 deletions(-) > create mode 100644 drivers/hid/hid-magicmouse.c > > diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig > index 24d90ea..ba14ec8 100644 > --- a/drivers/hid/Kconfig > +++ b/drivers/hid/Kconfig > @@ -183,6 +183,16 @@ config LOGIRUMBLEPAD2_FF > Say Y here if you want to enable force feedback support for Logitech > Rumblepad 2 devices. > > +config HID_MAGICMOUSE > + tristate "Apple" if EMBEDDED Could you please make this something else than "Apple"? This string is already used by HID_APPLE. Thanks, -- Jiri Kosina SUSE Labs, Novell Inc. -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo(a)vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
From: Jiri Kosina on 10 Feb 2010 09:00 On Wed, 10 Feb 2010, Jiri Kosina wrote: > > +config HID_MAGICMOUSE > > + tristate "Apple" if EMBEDDED > > Could you please make this something else than "Apple"? This string is > already used by HID_APPLE. I have fixed this up on my side, no need for you to respin. -- Jiri Kosina SUSE Labs, Novell Inc. -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo(a)vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
From: Dmitry Torokhov on 10 Feb 2010 13:30 Hi Michael, On Tue, Feb 09, 2010 at 08:13:08AM -0500, Michael Poole wrote: > + > +static bool emulate_3button = 1; If it is a bool then values should be true/false. > +module_param(emulate_3button, bool, 0644); > +MODULE_PARM_DESC(emulate_3button, "Emulate a middle button"); > + > +static int middle_button_start = -350; > +static int middle_button_stop = +350; > + > +static bool emulate_scroll_wheel = 1; > +module_param(emulate_scroll_wheel, bool, 0644); > +MODULE_PARM_DESC(emulate_scroll_wheel, "Emulate a scroll wheel"); > + > +static bool report_touches = 1; > +module_param(report_touches, bool, 0644); > +MODULE_PARM_DESC(report_touches, "Emit touch records (otherwise, only use them for emulation)"); > + > +static bool report_undeciphered = 0; No need to initialize statics to 0/false. > +module_param(report_undeciphered, bool, 0644); > +MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event"); > + > +#define TOUCH_REPORT_ID 0x29 > +/* These definitions are not precise, but they're close enough. (Bits > + * 0x03 seem to indicate the aspect ratio of the touch, bits 0x70 seem > + * to be some kind of bit mask -- 0x20 may be a near-field reading, > + * and 0x40 is actual contact, and 0x10 may be a start/stop or change > + * indication.) > + */ > +#define TOUCH_STATE_MASK 0xf0 > +#define TOUCH_STATE_NONE 0x00 > +#define TOUCH_STATE_START 0x30 > +#define TOUCH_STATE_DRAG 0x40 > + > +/** > + * struct magicmouse_sc - Tracks Magic Mouse-specific data. > + * @input: Input device through which we report events. > + * @quirks: Currently unused. > + * @last_timestamp: Timestamp from most recent (18-bit) touch report > + * (units of milliseconds over short windows, but seems to > + * increase faster when there are no touches). > + * @delta_time: 18-bit difference between the two most recent touch > + * reports from the mouse. > + * @ntouches: Number of touches in most recent touch report. > + * @scroll_accel: Number of consecutive scroll motions. > + * @scroll_jiffies: Time of last scroll motion. > + * @touches: Most recent data for a touch, indexed by tracking ID. > + * @tracking_ids: Mapping of current touch input data to @touches. > + */ > +struct magicmouse_sc { > + struct input_dev *input; > + unsigned long quirks; > + > + int last_timestamp; > + int delta_time; > + int ntouches; > + int scroll_accel; > + unsigned long scroll_jiffies; > + > + struct { > + short x; > + short y; > + short scroll_y; > + u8 size; > + } touches[16]; > + int tracking_ids[16]; > +}; > + > +static int magicmouse_firm_touch(struct magicmouse_sc *msc) > +{ > + int touch = -1; > + int ii; > + > + /* If there is only one "firm" touch, set touch to its > + * tracking ID. > + */ > + for (ii = 0; ii < msc->ntouches; ii++) { > + int idx = msc->tracking_ids[ii]; > + if (msc->touches[idx].size < 8) { > + /* Ignore this touch. */ > + } else if (touch >= 0) { > + touch = -1; > + break; > + } else { > + touch = idx; > + } > + } > + > + return touch; > +} > + > +static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state) > +{ > + int last_state = test_bit(BTN_LEFT, msc->input->key) << 0 | > + test_bit(BTN_RIGHT, msc->input->key) << 1 | > + test_bit(BTN_MIDDLE, msc->input->key) << 2; Indented with spaces, we prefer tabs. > + > + if (emulate_3button) { > + int id; > + > + /* If some button was pressed before, keep it held > + * down. Otherwise, if there's exactly one firm > + * touch, use that to override the mouse's guess. > + */ > + if (state == 0) { > + /* The button was released. */ > + } else if (last_state != 0) { > + state = last_state; > + } else if ((id = magicmouse_firm_touch(msc)) >= 0) { > + int x = msc->touches[id].x; > + if (x < middle_button_start) > + state = 1; > + else if (x > middle_button_stop) > + state = 2; > + else > + state = 4; > + } /* else: we keep the mouse's guess */ > + > + input_report_key(msc->input, BTN_MIDDLE, state & 4); > + } > + > + input_report_key(msc->input, BTN_LEFT, state & 1); > + input_report_key(msc->input, BTN_RIGHT, state & 2); > + > + if (state != last_state) > + msc->scroll_accel = 0; > +} > + > +static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tdata) > +{ > + struct input_dev *input = msc->input; > + __s32 x_y = tdata[0] << 8 | tdata[1] << 16 | tdata[2] << 24; > + int misc = tdata[5] | tdata[6] << 8; > + int id = (misc >> 6) & 15; > + int x = x_y << 12 >> 20; > + int y = -(x_y >> 20); > + > + /* Store tracking ID and other fields. */ > + msc->tracking_ids[raw_id] = id; > + msc->touches[id].x = x; > + msc->touches[id].y = y; > + msc->touches[id].size = misc & 63; > + > + /* If requested, emulate a scroll wheel by detecting small > + * vertical touch motions along the middle of the mouse. > + */ > + if (emulate_scroll_wheel && > + middle_button_start < x && x < middle_button_stop) { > + static const int accel_profile[] = { > + 256, 228, 192, 160, 128, 96, 64, 32, > + }; > + unsigned long now = jiffies; > + int step = msc->touches[id].scroll_y - y; > + > + /* Reset acceleration after half a second. */ > + if (time_after(now, msc->scroll_jiffies + HZ / 2)) > + msc->scroll_accel = 0; > + > + /* Calculate and apply the scroll motion. */ > + switch (tdata[7] & TOUCH_STATE_MASK) { > + case TOUCH_STATE_START: > + msc->touches[id].scroll_y = y; > + msc->scroll_accel = min_t(int, msc->scroll_accel + 1, > + ARRAY_SIZE(accel_profile) - 1); > + break; > + case TOUCH_STATE_DRAG: > + step = step / accel_profile[msc->scroll_accel]; > + if (step != 0) { > + msc->touches[id].scroll_y = y; > + msc->scroll_jiffies = now; > + input_report_rel(input, REL_WHEEL, step); > + } > + break; > + } > + } > + > + /* Generate the input events for this touch. */ > + if (report_touches) { > + int orientation = (misc >> 10) - 32; > + > + input_report_abs(input, ABS_MT_TRACKING_ID, id); > + input_report_abs(input, ABS_MT_TOUCH_MAJOR, tdata[3]); > + input_report_abs(input, ABS_MT_TOUCH_MINOR, tdata[4]); > + input_report_abs(input, ABS_MT_ORIENTATION, orientation); > + input_report_abs(input, ABS_MT_POSITION_X, x); > + input_report_abs(input, ABS_MT_POSITION_Y, y); > + > + if (report_undeciphered) { > + input_event(input, EV_MSC, MSC_RAW, tdata[7]); > + } No need for braces for single statement. > + > + input_mt_sync(input); > + } > +} > + > +static int magicmouse_raw_event(struct hid_device *hdev, > + struct hid_report *report, u8 *data, int size) > +{ > + struct magicmouse_sc *msc = hid_get_drvdata(hdev); > + struct input_dev *input = msc->input; > + int x, y, ts, ii, clicks; > + > + switch (data[0]) { > + case 0x10: > + if (size != 6) > + return 0; > + x = (__s16)(data[2] | data[3] << 8); > + y = (__s16)(data[4] | data[5] << 8); > + clicks = data[1]; > + break; > + case TOUCH_REPORT_ID: > + /* Expect six bytes of prefix, and N*8 bytes of touch data. */ > + if (size < 6 || ((size - 6) % 8) != 0) > + return 0; > + ts = data[3] >> 6 | data[4] << 2 | data[5] << 10; > + msc->delta_time = (ts - msc->last_timestamp) & 0x3ffff; > + msc->last_timestamp = ts; > + msc->ntouches = (size - 6) / 8; > + for (ii = 0; ii < msc->ntouches; ii++) > + magicmouse_emit_touch(msc, ii, data + ii * 8 + 6); > + /* When emulating three-button mode, it is important > + * to have the current touch information before > + * generating a click event. > + */ > + x = (signed char)data[1]; > + y = (signed char)data[2]; > + clicks = data[3]; > + break; > + case 0x20: /* Theoretically battery status (0-100), but I have > + * never seen it -- maybe it is only upon request. > + */ > + case 0x60: /* Unknown, maybe laser on/off. */ > + case 0x61: /* Laser reflection status change. > + * data[1]: 0 = spotted, 1 = lost > + */ > + default: > + return 0; > + } > + > + magicmouse_emit_buttons(msc, clicks & 3); > + input_report_rel(input, REL_X, x); > + input_report_rel(input, REL_Y, y); > + input_sync(input); > + return 1; > +} > + > +static int magicmouse_input_open(struct input_dev *dev) > +{ > + struct hid_device *hid = input_get_drvdata(dev); > + > + return hid->ll_driver->open(hid); > +} > + > +static void magicmouse_input_close(struct input_dev *dev) > +{ > + struct hid_device *hid = input_get_drvdata(dev); > + > + hid->ll_driver->close(hid); > +} > + > +static void magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev) > +{ > + input_set_drvdata(input, hdev); > + input->event = hdev->ll_driver->hidinput_input_event; > + input->open = magicmouse_input_open; > + input->close = magicmouse_input_close; > + > + input->name = hdev->name; > + input->phys = hdev->phys; > + input->uniq = hdev->uniq; > + input->id.bustype = hdev->bus; > + input->id.vendor = hdev->vendor; > + input->id.product = hdev->product; > + input->id.version = hdev->version; > + input->dev.parent = hdev->dev.parent; > + > + set_bit(EV_KEY, input->evbit); > + set_bit(BTN_LEFT, input->keybit); > + set_bit(BTN_RIGHT, input->keybit); > + if (emulate_3button) > + set_bit(BTN_MIDDLE, input->keybit); > + set_bit(BTN_TOOL_FINGER, input->keybit); > + > + set_bit(EV_REL, input->evbit); > + set_bit(REL_X, input->relbit); > + set_bit(REL_Y, input->relbit); > + if (emulate_scroll_wheel) > + set_bit(REL_WHEEL, input->relbit); I'd use __set_bit() instead, no need to lock the bus. > + > + if (report_touches) { > + set_bit(EV_ABS, input->evbit); > + > + set_bit(ABS_MT_TRACKING_ID, input->absbit); > + input->absmin[ABS_MT_TRACKING_ID] = 0; > + input->absmax[ABS_MT_TRACKING_ID] = 15; > + input->absfuzz[ABS_MT_TRACKING_ID] = 0; input_set_abs_params() is a bit more compact. > + > + set_bit(ABS_MT_TOUCH_MAJOR, input->absbit); > + input->absmin[ABS_MT_TOUCH_MAJOR] = 0; > + input->absmax[ABS_MT_TOUCH_MAJOR] = 255; > + input->absfuzz[ABS_MT_TOUCH_MAJOR] = 4; > + > + set_bit(ABS_MT_TOUCH_MINOR, input->absbit); > + input->absmin[ABS_MT_TOUCH_MINOR] = 0; > + input->absmax[ABS_MT_TOUCH_MINOR] = 255; > + input->absfuzz[ABS_MT_TOUCH_MINOR] = 4; > + > + set_bit(ABS_MT_ORIENTATION, input->absbit); > + input->absmin[ABS_MT_ORIENTATION] = -32; > + input->absmax[ABS_MT_ORIENTATION] = 31; > + input->absfuzz[ABS_MT_ORIENTATION] = 1; > + > + set_bit(ABS_MT_POSITION_X, input->absbit); > + input->absmin[ABS_MT_POSITION_X] = -1100; > + input->absmax[ABS_MT_POSITION_X] = 1358; > + input->absfuzz[ABS_MT_POSITION_X] = 4; > + > + /* Note: Touch Y position from the device is inverted relative > + * to how pointer motion is reported (and relative to how USB > + * HID recommends the coordinates work). This driver keeps > + * the origin at the same position, and just uses the additive > + * inverse of the reported Y. > + */ > + set_bit(ABS_MT_POSITION_Y, input->absbit); > + input->absmin[ABS_MT_POSITION_Y] = -1589; > + input->absmax[ABS_MT_POSITION_Y] = 2047; > + input->absfuzz[ABS_MT_POSITION_Y] = 4; > + } > + > + if (report_undeciphered) { > + set_bit(EV_MSC, input->evbit); > + set_bit(MSC_RAW, input->mscbit); > + } > +} > + > +static int magicmouse_probe(struct hid_device *hdev, > + const struct hid_device_id *id) > +{ > + __u8 feature_1[] = { 0xd7, 0x01 }; > + __u8 feature_2[] = { 0xf8, 0x01, 0x32 }; > + struct input_dev *input; > + struct magicmouse_sc *msc; > + struct hid_report *report; > + int ret; > + > + msc = kzalloc(sizeof(*msc), GFP_KERNEL); > + if (msc == NULL) { > + dev_err(&hdev->dev, "can't alloc magicmouse descriptor\n"); > + return -ENOMEM; > + } > + > + msc->quirks = id->driver_data; > + hid_set_drvdata(hdev, msc); > + > + ret = hid_parse(hdev); > + if (ret) { > + dev_err(&hdev->dev, "magicmouse hid parse failed\n"); > + goto err_free; > + } > + > + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); > + if (ret) { > + dev_err(&hdev->dev, "magicmouse hw start failed\n"); > + goto err_free; > + } > + > + report = hid_register_report(hdev, HID_INPUT_REPORT, TOUCH_REPORT_ID); > + if (!report) { > + dev_err(&hdev->dev, "unable to register touch report\n"); > + ret = -ENOMEM; > + goto err_free; > + } > + report->size = 6; > + > + ret = hdev->hid_output_raw_report(hdev, feature_1, sizeof(feature_1), > + HID_FEATURE_REPORT); This will cause transmission of on-stack data through USB which it does not like. > + if (ret != sizeof(feature_1)) { > + dev_err(&hdev->dev, "unable to request touch data (1:%d)\n", > + ret); > + goto err_free; > + } > + ret = hdev->hid_output_raw_report(hdev, feature_2, > + sizeof(feature_2), HID_FEATURE_REPORT); Same here. > + if (ret != sizeof(feature_2)) { > + dev_err(&hdev->dev, "unable to request touch data (2:%d)\n", > + ret); > + goto err_free; > + } > + > + input = input_allocate_device(); > + if (!input) { > + dev_err(&hdev->dev, "can't alloc input device\n"); > + ret = -ENOMEM; > + goto err_free; > + } > + magicmouse_setup_input(input, hdev); > + > + ret = input_register_device(input); > + if (ret) { > + dev_err(&hdev->dev, "input device registration failed\n"); > + goto err_both; > + } > + msc->input = input; > + > + return 0; > + err_both: > + input_free_device(input); > + err_free: > + kfree(msc); > + return ret; hid_hw_stop() is missing in error path. Also see question about freeing report below. > +} > + > +static void magicmouse_remove(struct hid_device *hdev) > +{ Do we need to unregister report (I am not that familiar with HID, who is responsible for cleaning report lists?)? > + hid_hw_stop(hdev); > + kfree(hid_get_drvdata(hdev)); > +} > + > +static const struct hid_device_id magic_mice[] = { > + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICMOUSE), > + .driver_data = 0 }, > + { } > +}; > +MODULE_DEVICE_TABLE(hid, magic_mice); > + > +static struct hid_driver magicmouse_driver = { > + .name = "magicmouse", > + .id_table = magic_mice, > + .probe = magicmouse_probe, > + .remove = magicmouse_remove, > + .raw_event = magicmouse_raw_event, > +}; > + > +static int __init magicmouse_init(void) > +{ > + int ret; > + > + ret = hid_register_driver(&magicmouse_driver); > + if (ret) > + printk(KERN_ERR "can't register magicmouse driver\n"); > + > + return ret; > +} > + > +static void __exit magicmouse_exit(void) > +{ > + hid_unregister_driver(&magicmouse_driver); > +} > + > +module_init(magicmouse_init); > +module_exit(magicmouse_exit); > +MODULE_LICENSE("GPL"); > -- > 1.6.5.6 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-input" in > the body of a message to majordomo(a)vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html -- Dmitry -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo(a)vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
From: Michael Poole on 10 Feb 2010 15:40 I'll post a patch for these based on Jiri's for-next tree this evening. Dmitry Torokhov writes: > Hi Michael, > > On Tue, Feb 09, 2010 at 08:13:08AM -0500, Michael Poole wrote: >> + >> +static bool emulate_3button = 1; > > If it is a bool then values should be true/false. Thanks. >> +module_param(emulate_3button, bool, 0644); >> +MODULE_PARM_DESC(emulate_3button, "Emulate a middle button"); >> + >> +static int middle_button_start = -350; >> +static int middle_button_stop = +350; >> + >> +static bool emulate_scroll_wheel = 1; >> +module_param(emulate_scroll_wheel, bool, 0644); >> +MODULE_PARM_DESC(emulate_scroll_wheel, "Emulate a scroll wheel"); >> + >> +static bool report_touches = 1; >> +module_param(report_touches, bool, 0644); >> +MODULE_PARM_DESC(report_touches, "Emit touch records (otherwise, only use them for emulation)"); >> + >> +static bool report_undeciphered = 0; > > No need to initialize statics to 0/false. Okay, I'll remote that. [snip] >> + int last_state = test_bit(BTN_LEFT, msc->input->key) << 0 | >> + test_bit(BTN_RIGHT, msc->input->key) << 1 | >> + test_bit(BTN_MIDDLE, msc->input->key) << 2; > > Indented with spaces, we prefer tabs. Whoops -- one of my machines must be misconfigured. I'll fix that. [snip] >> + if (report_undeciphered) { >> + input_event(input, EV_MSC, MSC_RAW, tdata[7]); >> + } > > No need for braces for single statement. Okay. [snip] >> + if (emulate_scroll_wheel) >> + set_bit(REL_WHEEL, input->relbit); > > I'd use __set_bit() instead, no need to lock the bus. Okay. I wasn't aware of that semantic difference. >> + >> + if (report_touches) { >> + set_bit(EV_ABS, input->evbit); >> + >> + set_bit(ABS_MT_TRACKING_ID, input->absbit); >> + input->absmin[ABS_MT_TRACKING_ID] = 0; >> + input->absmax[ABS_MT_TRACKING_ID] = 15; >> + input->absfuzz[ABS_MT_TRACKING_ID] = 0; > > input_set_abs_params() is a bit more compact. Thanks -- more idiomatic is always handy. [snip] >> + ret = hdev->hid_output_raw_report(hdev, feature_1, sizeof(feature_1), >> + HID_FEATURE_REPORT); > > This will cause transmission of on-stack data through USB which it does > not like. Current Magic Mouse devices are Bluetooth-only. Is this an issue for that stack as well? (hid-sony.c and hid-wacom.c also use hid_output_raw_report() with on-stack buffers for Bluetooth devices -- if they need it too, I can write a patch for those. I have a PS3 controller that I can test with, but don't have a Wacom tablet.) Would making those buffers static put them in an appropriate section, or does the memory need to be allocated from a heap? >> + if (ret != sizeof(feature_1)) { >> + dev_err(&hdev->dev, "unable to request touch data (1:%d)\n", >> + ret); >> + goto err_free; >> + } >> + ret = hdev->hid_output_raw_report(hdev, feature_2, >> + sizeof(feature_2), HID_FEATURE_REPORT); > > Same here. > >> + if (ret != sizeof(feature_2)) { >> + dev_err(&hdev->dev, "unable to request touch data (2:%d)\n", >> + ret); >> + goto err_free; >> + } >> + >> + input = input_allocate_device(); >> + if (!input) { >> + dev_err(&hdev->dev, "can't alloc input device\n"); >> + ret = -ENOMEM; >> + goto err_free; >> + } >> + magicmouse_setup_input(input, hdev); >> + >> + ret = input_register_device(input); >> + if (ret) { >> + dev_err(&hdev->dev, "input device registration failed\n"); >> + goto err_both; >> + } >> + msc->input = input; >> + >> + return 0; >> + err_both: >> + input_free_device(input); >> + err_free: >> + kfree(msc); >> + return ret; > > hid_hw_stop() is missing in error path. Also see question about freeing > report below. Thanks, I'll add the hid_hw_stop(). >> +} >> + >> +static void magicmouse_remove(struct hid_device *hdev) >> +{ > > Do we need to unregister report (I am not that familiar with HID, who is > responsible for cleaning report lists?)? hid-core.c's hid_device_release() frees all the reports that registered with hid_register_report(). Michael Poole -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo(a)vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
From: Ed Tomlinson on 10 Feb 2010 22:10
Hi, When I apply this to head, I get: drivers/hid/hid-magicmouse.c: In function 'magicmouse_probe': drivers/hid/hid-magicmouse.c:393: error: too many arguments to function 'hdev->hid_output_raw_report' drivers/hid/hid-magicmouse.c:400: error: too many arguments to function 'hdev->hid_output_raw_report' I only need the second patch - the first seems to be in tree. Any ideas as to what I am missing? TIA Ed On Tuesday 09 February 2010 08:13:08 Michael Poole wrote: > From da3e2b0330fccd584ad79495853e638e9652155b Mon Sep 17 00:00:00 2001 > From: Michael Poole <mdpoole(a)troilus.org> > Date: Sat, 6 Feb 2010 12:24:36 -0500 > Subject: Add a device driver for the Apple Magic Mouse. > > Signed-off-by: Michael Poole <mdpoole(a)troilus.org> > --- > drivers/hid/Kconfig | 10 + > drivers/hid/Makefile | 1 + > drivers/hid/hid-core.c | 1 + > drivers/hid/hid-ids.h | 1 + > drivers/hid/hid-magicmouse.c | 469 ++++++++++++++++++++++++++++++++++++++++++ > 5 files changed, 482 insertions(+), 0 deletions(-) > create mode 100644 drivers/hid/hid-magicmouse.c > > diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig > index 24d90ea..ba14ec8 100644 > --- a/drivers/hid/Kconfig > +++ b/drivers/hid/Kconfig > @@ -183,6 +183,16 @@ config LOGIRUMBLEPAD2_FF > Say Y here if you want to enable force feedback support for Logitech > Rumblepad 2 devices. > > +config HID_MAGICMOUSE > + tristate "Apple" if EMBEDDED > + depends on BT_HIDP > + default !EMBEDDED > + ---help--- > + Support for the Apple Magic Mouse. > + > + Say Y here if you want support for the multi-touch features of the > + Apple Wireless "Magic" Mouse. > + > config HID_MICROSOFT > tristate "Microsoft" if EMBEDDED > depends on USB_HID > diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile > index 0de2dff..45d81e9 100644 > --- a/drivers/hid/Makefile > +++ b/drivers/hid/Makefile > @@ -31,6 +31,7 @@ obj-$(CONFIG_HID_GYRATION) += hid-gyration.o > obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o > obj-$(CONFIG_HID_KYE) += hid-kye.o > obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o > +obj-$(CONFIG_HID_MAGICMOUSE) += hid-magicmouse.o > obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o > obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o > obj-$(CONFIG_HID_NTRIG) += hid-ntrig.o > diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c > index e39055b..f23ca76 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[] = { > { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ATV_IRCONTROL) }, > { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL4) }, > { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MIGHTYMOUSE) }, > + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICMOUSE) }, > { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI) }, > { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ISO) }, > { 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 010368e..11e5218 100644 > --- a/drivers/hid/hid-ids.h > +++ b/drivers/hid/hid-ids.h > @@ -56,6 +56,7 @@ > > #define USB_VENDOR_ID_APPLE 0x05ac > #define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304 > +#define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d > #define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI 0x020e > #define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO 0x020f > #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 0000000..f94b3e4 > --- /dev/null > +++ b/drivers/hid/hid-magicmouse.c > @@ -0,0 +1,469 @@ > +/* > + * Apple "Magic" Wireless Mouse driver > + * > + * Copyright (c) 2010 Michael Poole <mdpoole(a)troilus.org> > + */ > + > +/* > + * This program is free software; you can redistribute it and/or modify it > + * under the terms of the GNU General Public License as published by the Free > + * Software Foundation; either version 2 of the License, or (at your option) > + * any later version. > + */ > + > +#include <linux/device.h> > +#include <linux/hid.h> > +#include <linux/module.h> > +#include <linux/usb.h> > + > +#include "hid-ids.h" > + > +static bool emulate_3button = 1; > +module_param(emulate_3button, bool, 0644); > +MODULE_PARM_DESC(emulate_3button, "Emulate a middle button"); > + > +static int middle_button_start = -350; > +static int middle_button_stop = +350; > + > +static bool emulate_scroll_wheel = 1; > +module_param(emulate_scroll_wheel, bool, 0644); > +MODULE_PARM_DESC(emulate_scroll_wheel, "Emulate a scroll wheel"); > + > +static bool report_touches = 1; > +module_param(report_touches, bool, 0644); > +MODULE_PARM_DESC(report_touches, "Emit touch records (otherwise, only use them for emulation)"); > + > +static bool report_undeciphered = 0; > +module_param(report_undeciphered, bool, 0644); > +MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event"); > + > +#define TOUCH_REPORT_ID 0x29 > +/* These definitions are not precise, but they're close enough. (Bits > + * 0x03 seem to indicate the aspect ratio of the touch, bits 0x70 seem > + * to be some kind of bit mask -- 0x20 may be a near-field reading, > + * and 0x40 is actual contact, and 0x10 may be a start/stop or change > + * indication.) > + */ > +#define TOUCH_STATE_MASK 0xf0 > +#define TOUCH_STATE_NONE 0x00 > +#define TOUCH_STATE_START 0x30 > +#define TOUCH_STATE_DRAG 0x40 > + > +/** > + * struct magicmouse_sc - Tracks Magic Mouse-specific data. > + * @input: Input device through which we report events. > + * @quirks: Currently unused. > + * @last_timestamp: Timestamp from most recent (18-bit) touch report > + * (units of milliseconds over short windows, but seems to > + * increase faster when there are no touches). > + * @delta_time: 18-bit difference between the two most recent touch > + * reports from the mouse. > + * @ntouches: Number of touches in most recent touch report. > + * @scroll_accel: Number of consecutive scroll motions. > + * @scroll_jiffies: Time of last scroll motion. > + * @touches: Most recent data for a touch, indexed by tracking ID. > + * @tracking_ids: Mapping of current touch input data to @touches. > + */ > +struct magicmouse_sc { > + struct input_dev *input; > + unsigned long quirks; > + > + int last_timestamp; > + int delta_time; > + int ntouches; > + int scroll_accel; > + unsigned long scroll_jiffies; > + > + struct { > + short x; > + short y; > + short scroll_y; > + u8 size; > + } touches[16]; > + int tracking_ids[16]; > +}; > + > +static int magicmouse_firm_touch(struct magicmouse_sc *msc) > +{ > + int touch = -1; > + int ii; > + > + /* If there is only one "firm" touch, set touch to its > + * tracking ID. > + */ > + for (ii = 0; ii < msc->ntouches; ii++) { > + int idx = msc->tracking_ids[ii]; > + if (msc->touches[idx].size < 8) { > + /* Ignore this touch. */ > + } else if (touch >= 0) { > + touch = -1; > + break; > + } else { > + touch = idx; > + } > + } > + > + return touch; > +} > + > +static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state) > +{ > + int last_state = test_bit(BTN_LEFT, msc->input->key) << 0 | > + test_bit(BTN_RIGHT, msc->input->key) << 1 | > + test_bit(BTN_MIDDLE, msc->input->key) << 2; > + > + if (emulate_3button) { > + int id; > + > + /* If some button was pressed before, keep it held > + * down. Otherwise, if there's exactly one firm > + * touch, use that to override the mouse's guess. > + */ > + if (state == 0) { > + /* The button was released. */ > + } else if (last_state != 0) { > + state = last_state; > + } else if ((id = magicmouse_firm_touch(msc)) >= 0) { > + int x = msc->touches[id].x; > + if (x < middle_button_start) > + state = 1; > + else if (x > middle_button_stop) > + state = 2; > + else > + state = 4; > + } /* else: we keep the mouse's guess */ > + > + input_report_key(msc->input, BTN_MIDDLE, state & 4); > + } > + > + input_report_key(msc->input, BTN_LEFT, state & 1); > + input_report_key(msc->input, BTN_RIGHT, state & 2); > + > + if (state != last_state) > + msc->scroll_accel = 0; > +} > + > +static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tdata) > +{ > + struct input_dev *input = msc->input; > + __s32 x_y = tdata[0] << 8 | tdata[1] << 16 | tdata[2] << 24; > + int misc = tdata[5] | tdata[6] << 8; > + int id = (misc >> 6) & 15; > + int x = x_y << 12 >> 20; > + int y = -(x_y >> 20); > + > + /* Store tracking ID and other fields. */ > + msc->tracking_ids[raw_id] = id; > + msc->touches[id].x = x; > + msc->touches[id].y = y; > + msc->touches[id].size = misc & 63; > + > + /* If requested, emulate a scroll wheel by detecting small > + * vertical touch motions along the middle of the mouse. > + */ > + if (emulate_scroll_wheel && > + middle_button_start < x && x < middle_button_stop) { > + static const int accel_profile[] = { > + 256, 228, 192, 160, 128, 96, 64, 32, > + }; > + unsigned long now = jiffies; > + int step = msc->touches[id].scroll_y - y; > + > + /* Reset acceleration after half a second. */ > + if (time_after(now, msc->scroll_jiffies + HZ / 2)) > + msc->scroll_accel = 0; > + > + /* Calculate and apply the scroll motion. */ > + switch (tdata[7] & TOUCH_STATE_MASK) { > + case TOUCH_STATE_START: > + msc->touches[id].scroll_y = y; > + msc->scroll_accel = min_t(int, msc->scroll_accel + 1, > + ARRAY_SIZE(accel_profile) - 1); > + break; > + case TOUCH_STATE_DRAG: > + step = step / accel_profile[msc->scroll_accel]; > + if (step != 0) { > + msc->touches[id].scroll_y = y; > + msc->scroll_jiffies = now; > + input_report_rel(input, REL_WHEEL, step); > + } > + break; > + } > + } > + > + /* Generate the input events for this touch. */ > + if (report_touches) { > + int orientation = (misc >> 10) - 32; > + > + input_report_abs(input, ABS_MT_TRACKING_ID, id); > + input_report_abs(input, ABS_MT_TOUCH_MAJOR, tdata[3]); > + input_report_abs(input, ABS_MT_TOUCH_MINOR, tdata[4]); > + input_report_abs(input, ABS_MT_ORIENTATION, orientation); > + input_report_abs(input, ABS_MT_POSITION_X, x); > + input_report_abs(input, ABS_MT_POSITION_Y, y); > + > + if (report_undeciphered) { > + input_event(input, EV_MSC, MSC_RAW, tdata[7]); > + } > + > + input_mt_sync(input); > + } > +} > + > +static int magicmouse_raw_event(struct hid_device *hdev, > + struct hid_report *report, u8 *data, int size) > +{ > + struct magicmouse_sc *msc = hid_get_drvdata(hdev); > + struct input_dev *input = msc->input; > + int x, y, ts, ii, clicks; > + > + switch (data[0]) { > + case 0x10: > + if (size != 6) > + return 0; > + x = (__s16)(data[2] | data[3] << 8); > + y = (__s16)(data[4] | data[5] << 8); > + clicks = data[1]; > + break; > + case TOUCH_REPORT_ID: > + /* Expect six bytes of prefix, and N*8 bytes of touch data. */ > + if (size < 6 || ((size - 6) % 8) != 0) > + return 0; > + ts = data[3] >> 6 | data[4] << 2 | data[5] << 10; > + msc->delta_time = (ts - msc->last_timestamp) & 0x3ffff; > + msc->last_timestamp = ts; > + msc->ntouches = (size - 6) / 8; > + for (ii = 0; ii < msc->ntouches; ii++) > + magicmouse_emit_touch(msc, ii, data + ii * 8 + 6); > + /* When emulating three-button mode, it is important > + * to have the current touch information before > + * generating a click event. > + */ > + x = (signed char)data[1]; > + y = (signed char)data[2]; > + clicks = data[3]; > + break; > + case 0x20: /* Theoretically battery status (0-100), but I have > + * never seen it -- maybe it is only upon request. > + */ > + case 0x60: /* Unknown, maybe laser on/off. */ > + case 0x61: /* Laser reflection status change. > + * data[1]: 0 = spotted, 1 = lost > + */ > + default: > + return 0; > + } > + > + magicmouse_emit_buttons(msc, clicks & 3); > + input_report_rel(input, REL_X, x); > + input_report_rel(input, REL_Y, y); > + input_sync(input); > + return 1; > +} > + > +static int magicmouse_input_open(struct input_dev *dev) > +{ > + struct hid_device *hid = input_get_drvdata(dev); > + > + return hid->ll_driver->open(hid); > +} > + > +static void magicmouse_input_close(struct input_dev *dev) > +{ > + struct hid_device *hid = input_get_drvdata(dev); > + > + hid->ll_driver->close(hid); > +} > + > +static void magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev) > +{ > + input_set_drvdata(input, hdev); > + input->event = hdev->ll_driver->hidinput_input_event; > + input->open = magicmouse_input_open; > + input->close = magicmouse_input_close; > + > + input->name = hdev->name; > + input->phys = hdev->phys; > + input->uniq = hdev->uniq; > + input->id.bustype = hdev->bus; > + input->id.vendor = hdev->vendor; > + input->id.product = hdev->product; > + input->id.version = hdev->version; > + input->dev.parent = hdev->dev.parent; > + > + set_bit(EV_KEY, input->evbit); > + set_bit(BTN_LEFT, input->keybit); > + set_bit(BTN_RIGHT, input->keybit); > + if (emulate_3button) > + set_bit(BTN_MIDDLE, input->keybit); > + set_bit(BTN_TOOL_FINGER, input->keybit); > + > + set_bit(EV_REL, input->evbit); > + set_bit(REL_X, input->relbit); > + set_bit(REL_Y, input->relbit); > + if (emulate_scroll_wheel) > + set_bit(REL_WHEEL, input->relbit); > + > + if (report_touches) { > + set_bit(EV_ABS, input->evbit); > + > + set_bit(ABS_MT_TRACKING_ID, input->absbit); > + input->absmin[ABS_MT_TRACKING_ID] = 0; > + input->absmax[ABS_MT_TRACKING_ID] = 15; > + input->absfuzz[ABS_MT_TRACKING_ID] = 0; > + > + set_bit(ABS_MT_TOUCH_MAJOR, input->absbit); > + input->absmin[ABS_MT_TOUCH_MAJOR] = 0; > + input->absmax[ABS_MT_TOUCH_MAJOR] = 255; > + input->absfuzz[ABS_MT_TOUCH_MAJOR] = 4; > + > + set_bit(ABS_MT_TOUCH_MINOR, input->absbit); > + input->absmin[ABS_MT_TOUCH_MINOR] = 0; > + input->absmax[ABS_MT_TOUCH_MINOR] = 255; > + input->absfuzz[ABS_MT_TOUCH_MINOR] = 4; > + > + set_bit(ABS_MT_ORIENTATION, input->absbit); > + input->absmin[ABS_MT_ORIENTATION] = -32; > + input->absmax[ABS_MT_ORIENTATION] = 31; > + input->absfuzz[ABS_MT_ORIENTATION] = 1; > + > + set_bit(ABS_MT_POSITION_X, input->absbit); > + input->absmin[ABS_MT_POSITION_X] = -1100; > + input->absmax[ABS_MT_POSITION_X] = 1358; > + input->absfuzz[ABS_MT_POSITION_X] = 4; > + > + /* Note: Touch Y position from the device is inverted relative > + * to how pointer motion is reported (and relative to how USB > + * HID recommends the coordinates work). This driver keeps > + * the origin at the same position, and just uses the additive > + * inverse of the reported Y. > + */ > + set_bit(ABS_MT_POSITION_Y, input->absbit); > + input->absmin[ABS_MT_POSITION_Y] = -1589; > + input->absmax[ABS_MT_POSITION_Y] = 2047; > + input->absfuzz[ABS_MT_POSITION_Y] = 4; > + } > + > + if (report_undeciphered) { > + set_bit(EV_MSC, input->evbit); > + set_bit(MSC_RAW, input->mscbit); > + } > +} > + > +static int magicmouse_probe(struct hid_device *hdev, > + const struct hid_device_id *id) > +{ > + __u8 feature_1[] = { 0xd7, 0x01 }; > + __u8 feature_2[] = { 0xf8, 0x01, 0x32 }; > + struct input_dev *input; > + struct magicmouse_sc *msc; > + struct hid_report *report; > + int ret; > + > + msc = kzalloc(sizeof(*msc), GFP_KERNEL); > + if (msc == NULL) { > + dev_err(&hdev->dev, "can't alloc magicmouse descriptor\n"); > + return -ENOMEM; > + } > + > + msc->quirks = id->driver_data; > + hid_set_drvdata(hdev, msc); > + > + ret = hid_parse(hdev); > + if (ret) { > + dev_err(&hdev->dev, "magicmouse hid parse failed\n"); > + goto err_free; > + } > + > + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); > + if (ret) { > + dev_err(&hdev->dev, "magicmouse hw start failed\n"); > + goto err_free; > + } > + > + report = hid_register_report(hdev, HID_INPUT_REPORT, TOUCH_REPORT_ID); > + if (!report) { > + dev_err(&hdev->dev, "unable to register touch report\n"); > + ret = -ENOMEM; > + goto err_free; > + } > + report->size = 6; > + > + ret = hdev->hid_output_raw_report(hdev, feature_1, sizeof(feature_1), > + HID_FEATURE_REPORT); > + if (ret != sizeof(feature_1)) { > + dev_err(&hdev->dev, "unable to request touch data (1:%d)\n", > + ret); > + goto err_free; > + } > + ret = hdev->hid_output_raw_report(hdev, feature_2, > + sizeof(feature_2), HID_FEATURE_REPORT); > + if (ret != sizeof(feature_2)) { > + dev_err(&hdev->dev, "unable to request touch data (2:%d)\n", > + ret); > + goto err_free; > + } > + > + input = input_allocate_device(); > + if (!input) { > + dev_err(&hdev->dev, "can't alloc input device\n"); > + ret = -ENOMEM; > + goto err_free; > + } > + magicmouse_setup_input(input, hdev); > + > + ret = input_register_device(input); > + if (ret) { > + dev_err(&hdev->dev, "input device registration failed\n"); > + goto err_both; > + } > + msc->input = input; > + > + return 0; > + err_both: > + input_free_device(input); > + err_free: > + kfree(msc); > + return ret; > +} > + > +static void magicmouse_remove(struct hid_device *hdev) > +{ > + hid_hw_stop(hdev); > + kfree(hid_get_drvdata(hdev)); > +} > + > +static const struct hid_device_id magic_mice[] = { > + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICMOUSE), > + .driver_data = 0 }, > + { } > +}; > +MODULE_DEVICE_TABLE(hid, magic_mice); > + > +static struct hid_driver magicmouse_driver = { > + .name = "magicmouse", > + .id_table = magic_mice, > + .probe = magicmouse_probe, > + .remove = magicmouse_remove, > + .raw_event = magicmouse_raw_event, > +}; > + > +static int __init magicmouse_init(void) > +{ > + int ret; > + > + ret = hid_register_driver(&magicmouse_driver); > + if (ret) > + printk(KERN_ERR "can't register magicmouse driver\n"); > + > + return ret; > +} > + > +static void __exit magicmouse_exit(void) > +{ > + hid_unregister_driver(&magicmouse_driver); > +} > + > +module_init(magicmouse_init); > +module_exit(magicmouse_exit); > +MODULE_LICENSE("GPL"); > -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo(a)vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/ |