1126 lines · 34.1 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* *   Apple "Magic" Wireless Mouse driver * *   Copyright (c) 2010 Michael Poole <mdpoole@troilus.org> *   Copyright (c) 2010 Chase Douglas <chase.douglas@canonical.com> */ /* */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/device.h>#include <linux/hid.h>#include <linux/input/mt.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/workqueue.h> #include "hid-ids.h" static bool emulate_3button = true;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 = true;module_param(emulate_scroll_wheel, bool, 0644);MODULE_PARM_DESC(emulate_scroll_wheel, "Emulate a scroll wheel"); static unsigned int scroll_speed = 32;static int param_set_scroll_speed(const char *val,				  const struct kernel_param *kp) {	unsigned long speed;	if (!val || kstrtoul(val, 0, &speed) || speed > 63)		return -EINVAL;	scroll_speed = speed;	return 0;}module_param_call(scroll_speed, param_set_scroll_speed, param_get_uint, &scroll_speed, 0644);MODULE_PARM_DESC(scroll_speed, "Scroll speed, value from 0 (slow) to 63 (fast)"); static bool scroll_acceleration = false;module_param(scroll_acceleration, bool, 0644);MODULE_PARM_DESC(scroll_acceleration, "Accelerate sequential scroll events"); static bool report_undeciphered;module_param(report_undeciphered, bool, 0644);MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event"); #define TRACKPAD2_2021_BT_VERSION 0x110#define TRACKPAD_2024_BT_VERSION 0x314 #define TRACKPAD_REPORT_ID 0x28#define TRACKPAD2_USB_REPORT_ID 0x02#define TRACKPAD2_BT_REPORT_ID 0x31#define MOUSE_REPORT_ID    0x29#define MOUSE2_REPORT_ID   0x12#define DOUBLE_REPORT_ID   0xf7#define USB_BATTERY_TIMEOUT_SEC 60 /* 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 /* Number of high-resolution events for each low-resolution detent. */#define SCROLL_HR_STEPS 10#define SCROLL_HR_MULT (120 / SCROLL_HR_STEPS)#define SCROLL_HR_THRESHOLD 90 /* units */#define SCROLL_ACCEL_DEFAULT 7 /* Touch surface information. Dimension is in hundredths of a mm, min and max * are in units. */#define MOUSE_DIMENSION_X (float)9056#define MOUSE_MIN_X -1100#define MOUSE_MAX_X 1258#define MOUSE_RES_X ((MOUSE_MAX_X - MOUSE_MIN_X) / (MOUSE_DIMENSION_X / 100))#define MOUSE_DIMENSION_Y (float)5152#define MOUSE_MIN_Y -1589#define MOUSE_MAX_Y 2047#define MOUSE_RES_Y ((MOUSE_MAX_Y - MOUSE_MIN_Y) / (MOUSE_DIMENSION_Y / 100)) #define TRACKPAD_DIMENSION_X (float)13000#define TRACKPAD_MIN_X -2909#define TRACKPAD_MAX_X 3167#define TRACKPAD_RES_X \	((TRACKPAD_MAX_X - TRACKPAD_MIN_X) / (TRACKPAD_DIMENSION_X / 100))#define TRACKPAD_DIMENSION_Y (float)11000#define TRACKPAD_MIN_Y -2456#define TRACKPAD_MAX_Y 2565#define TRACKPAD_RES_Y \	((TRACKPAD_MAX_Y - TRACKPAD_MIN_Y) / (TRACKPAD_DIMENSION_Y / 100)) #define TRACKPAD2_DIMENSION_X (float)16000#define TRACKPAD2_MIN_X -3678#define TRACKPAD2_MAX_X 3934#define TRACKPAD2_RES_X \	((TRACKPAD2_MAX_X - TRACKPAD2_MIN_X) / (TRACKPAD2_DIMENSION_X / 100))#define TRACKPAD2_DIMENSION_Y (float)11490#define TRACKPAD2_MIN_Y -2478#define TRACKPAD2_MAX_Y 2587#define TRACKPAD2_RES_Y \	((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100)) /** * struct magicmouse_sc - Tracks Magic Mouse-specific data. * @input: Input device through which we report events. * @quirks: Currently unused. * @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. * @hdev: Pointer to the underlying HID device. * @work: Workqueue to handle initialization retry for quirky devices. * @battery_timer: Timer for obtaining battery level information. */struct magicmouse_sc {	struct input_dev *input;	unsigned long quirks; 	int ntouches;	int scroll_accel;	unsigned long scroll_jiffies; 	struct {		short x;		short y;		short scroll_x;		short scroll_y;		short scroll_x_hr;		short scroll_y_hr;		u8 size;		bool scroll_x_active;		bool scroll_y_active;	} touches[16];	int tracking_ids[16]; 	struct hid_device *hdev;	struct delayed_work work;	struct timer_list battery_timer;}; 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 = SCROLL_ACCEL_DEFAULT;} static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tdata){	struct input_dev *input = msc->input;	int id, x, y, size, orientation, touch_major, touch_minor, state, down;	int pressure = 0; 	if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||	    input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||	    input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC) {		id = (tdata[6] << 2 | tdata[5] >> 6) & 0xf;		x = (tdata[1] << 28 | tdata[0] << 20) >> 20;		y = -((tdata[2] << 24 | tdata[1] << 16) >> 20);		size = tdata[5] & 0x3f;		orientation = (tdata[6] >> 2) - 32;		touch_major = tdata[3];		touch_minor = tdata[4];		state = tdata[7] & TOUCH_STATE_MASK;		down = state != TOUCH_STATE_NONE;	} else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||		   input->id.product ==			   USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {		id = tdata[8] & 0xf;		x = (tdata[1] << 27 | tdata[0] << 19) >> 19;		y = -((tdata[3] << 30 | tdata[2] << 22 | tdata[1] << 14) >> 19);		size = tdata[6];		orientation = (tdata[8] >> 5) - 4;		touch_major = tdata[4];		touch_minor = tdata[5];		pressure = tdata[7];		state = tdata[3] & 0xC0;		down = state == 0x80;	} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */		id = (tdata[7] << 2 | tdata[6] >> 6) & 0xf;		x = (tdata[1] << 27 | tdata[0] << 19) >> 19;		y = -((tdata[3] << 30 | tdata[2] << 22 | tdata[1] << 14) >> 19);		size = tdata[6] & 0x3f;		orientation = (tdata[7] >> 2) - 32;		touch_major = tdata[4];		touch_minor = tdata[5];		state = tdata[8] & TOUCH_STATE_MASK;		down = state != TOUCH_STATE_NONE;	} 	/* 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 = size; 	/* If requested, emulate a scroll wheel by detecting small	 * vertical touch motions.	 */	if (emulate_scroll_wheel &&	    input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 &&	    input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {		unsigned long now = jiffies;		int step_x = msc->touches[id].scroll_x - x;		int step_y = msc->touches[id].scroll_y - y;		int step_hr =			max_t(int,			      ((64 - (int)scroll_speed) * msc->scroll_accel) /					SCROLL_HR_STEPS,			      1);		int step_x_hr = msc->touches[id].scroll_x_hr - x;		int step_y_hr = msc->touches[id].scroll_y_hr - y; 		/* Calculate and apply the scroll motion. */		switch (state) {		case TOUCH_STATE_START:			msc->touches[id].scroll_x = x;			msc->touches[id].scroll_y = y;			msc->touches[id].scroll_x_hr = x;			msc->touches[id].scroll_y_hr = y;			msc->touches[id].scroll_x_active = false;			msc->touches[id].scroll_y_active = false; 			/* Reset acceleration after half a second. */			if (scroll_acceleration && time_before(now,						msc->scroll_jiffies + HZ / 2))				msc->scroll_accel = max_t(int,						msc->scroll_accel - 1, 1);			else				msc->scroll_accel = SCROLL_ACCEL_DEFAULT; 			break;		case TOUCH_STATE_DRAG:			step_x /= (64 - (int)scroll_speed) * msc->scroll_accel;			if (step_x != 0) {				msc->touches[id].scroll_x -= step_x *					(64 - scroll_speed) * msc->scroll_accel;				msc->scroll_jiffies = now;				input_report_rel(input, REL_HWHEEL, -step_x);			} 			step_y /= (64 - (int)scroll_speed) * msc->scroll_accel;			if (step_y != 0) {				msc->touches[id].scroll_y -= step_y *					(64 - scroll_speed) * msc->scroll_accel;				msc->scroll_jiffies = now;				input_report_rel(input, REL_WHEEL, step_y);			} 			if (!msc->touches[id].scroll_x_active &&			    abs(step_x_hr) > SCROLL_HR_THRESHOLD) {				msc->touches[id].scroll_x_active = true;				msc->touches[id].scroll_x_hr = x;				step_x_hr = 0;			} 			step_x_hr /= step_hr;			if (step_x_hr != 0 &&			    msc->touches[id].scroll_x_active) {				msc->touches[id].scroll_x_hr -= step_x_hr *					step_hr;				input_report_rel(input,						 REL_HWHEEL_HI_RES,						 -step_x_hr * SCROLL_HR_MULT);			} 			if (!msc->touches[id].scroll_y_active &&			    abs(step_y_hr) > SCROLL_HR_THRESHOLD) {				msc->touches[id].scroll_y_active = true;				msc->touches[id].scroll_y_hr = y;				step_y_hr = 0;			} 			step_y_hr /= step_hr;			if (step_y_hr != 0 &&			    msc->touches[id].scroll_y_active) {				msc->touches[id].scroll_y_hr -= step_y_hr *					step_hr;				input_report_rel(input,						 REL_WHEEL_HI_RES,						 step_y_hr * SCROLL_HR_MULT);			}			break;		}	} 	if (down)		msc->ntouches++; 	input_mt_slot(input, id);	input_mt_report_slot_state(input, MT_TOOL_FINGER, down); 	/* Generate the input events for this touch. */	if (down) {		input_report_abs(input, ABS_MT_TOUCH_MAJOR, touch_major << 2);		input_report_abs(input, ABS_MT_TOUCH_MINOR, touch_minor << 2);		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 (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||		    input->id.product ==			    USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC)			input_report_abs(input, ABS_MT_PRESSURE, pressure); 		if (report_undeciphered) {			if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||			    input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||			    input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC)				input_event(input, EV_MSC, MSC_RAW, tdata[7]);			else if (input->id.product !=					 USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 &&				 input->id.product !=					 USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC)				input_event(input, EV_MSC, MSC_RAW, tdata[8]);		}	}} static int __magicmouse_raw_event(struct hid_device *hdev,		struct hid_report *report, u8 *data, int size, bool nested){	struct magicmouse_sc *msc = hid_get_drvdata(hdev);	struct input_dev *input = msc->input;	int x = 0, y = 0, ii, clicks = 0, npoints; 	/* Protect against zero sized recursive calls from DOUBLE_REPORT_ID */	if (size < 1)		return 0; 	switch (data[0]) {	case TRACKPAD_REPORT_ID:	case TRACKPAD2_BT_REPORT_ID:		/* Expect four bytes of prefix, and N*9 bytes of touch data. */		if (size < 4 || ((size - 4) % 9) != 0)			return 0;		npoints = (size - 4) / 9;		if (npoints > 15) {			hid_warn(hdev, "invalid size value (%d) for TRACKPAD_REPORT_ID\n",					size);			return 0;		}		msc->ntouches = 0;		for (ii = 0; ii < npoints; ii++)			magicmouse_emit_touch(msc, ii, data + ii * 9 + 4); 		clicks = data[1]; 		/* The following bits provide a device specific timestamp. They		 * are unused here.		 *		 * ts = data[1] >> 6 | data[2] << 2 | data[3] << 10;		 */		break;	case TRACKPAD2_USB_REPORT_ID:		/* Expect twelve bytes of prefix and N*9 bytes of touch data. */		if (size < 12 || ((size - 12) % 9) != 0)			return 0;		npoints = (size - 12) / 9;		if (npoints > 15) {			hid_warn(hdev, "invalid size value (%d) for TRACKPAD2_USB_REPORT_ID\n",					size);			return 0;		}		msc->ntouches = 0;		for (ii = 0; ii < npoints; ii++)			magicmouse_emit_touch(msc, ii, data + ii * 9 + 12); 		clicks = data[1];		break;	case MOUSE_REPORT_ID:		/* Expect six bytes of prefix, and N*8 bytes of touch data. */		if (size < 6 || ((size - 6) % 8) != 0)			return 0;		npoints = (size - 6) / 8;		if (npoints > 15) {			hid_warn(hdev, "invalid size value (%d) for MOUSE_REPORT_ID\n",					size);			return 0;		}		msc->ntouches = 0;		for (ii = 0; ii < npoints; 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 = (int)(((data[3] & 0x0c) << 28) | (data[1] << 22)) >> 22;		y = (int)(((data[3] & 0x30) << 26) | (data[2] << 22)) >> 22;		clicks = data[3]; 		/* The following bits provide a device specific timestamp. They		 * are unused here.		 *		 * ts = data[3] >> 6 | data[4] << 2 | data[5] << 10;		 */		break;	case MOUSE2_REPORT_ID:		/* Size is either 8 or (14 + 8 * N) */		if (size != 8 && (size < 14 || (size - 14) % 8 != 0))			return 0;		npoints = (size - 14) / 8;		if (npoints > 15) {			hid_warn(hdev, "invalid size value (%d) for MOUSE2_REPORT_ID\n",					size);			return 0;		}		msc->ntouches = 0;		for (ii = 0; ii < npoints; ii++)			magicmouse_emit_touch(msc, ii, data + ii * 8 + 14); 		/* When emulating three-button mode, it is important		 * to have the current touch information before		 * generating a click event.		 */		x = (int)((data[3] << 24) | (data[2] << 16)) >> 16;		y = (int)((data[5] << 24) | (data[4] << 16)) >> 16;		clicks = data[1]; 		/* The following bits provide a device specific timestamp. They		 * are unused here.		 *		 * ts = data[11] >> 6 | data[12] << 2 | data[13] << 10;		 */		break;	case DOUBLE_REPORT_ID:		/* Sometimes the trackpad sends two touch reports in one		 * packet.		 */ 		/*		 * A double report only ever wraps two normal reports, so it is		 * never nested. Refuse to recurse a second time; otherwise a

Lines 1–500 of 1126.