From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755538Ab0BJSUl (ORCPT ); Wed, 10 Feb 2010 13:20:41 -0500 Received: from qw-out-2122.google.com ([74.125.92.25]:25586 "EHLO qw-out-2122.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755000Ab0BJSUj (ORCPT ); Wed, 10 Feb 2010 13:20:39 -0500 DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=date:from:to:cc:subject:message-id:references:mime-version :content-type:content-disposition:in-reply-to:user-agent; b=dJBtg3aK7OghTNANymvw7jTXQCpzxaMJPk0QZMyxHNxRDKd8FcpFoxhfX/efjpwyD7 Nv2NXdc6RWArZdoV7JdtlpIWuVD+hSJV++e2IreLc+/TSRX2ZvGIVN8vOMbsy4WFXu1b 0V5RELclpy77NtD/r+Hv0pwXIoYXe5IlZNzoI= Date: Wed, 10 Feb 2010 10:20:24 -0800 From: Dmitry Torokhov To: Michael Poole Cc: Jiri Kosina , linux-input@vger.kernel.org, Marcel Holtmann , linux-bluetooth@vger.kernel.org, linux-kernel@vger.kernel.org Subject: Re: [PATCH 2/2] Add a device driver for the Apple Magic Mouse. Message-ID: <20100210182024.GA29610@core.coreip.homeip.net> References: <87y6j2eeqv.fsf_-_@troilus.org> <87pr4eeemz.fsf_-_@troilus.org> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <87pr4eeemz.fsf_-_@troilus.org> User-Agent: Mutt/1.5.20 (2009-08-17) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html -- Dmitry