summaryrefslogtreecommitdiff
path: root/drivers/hid/hid-magicmouse.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid/hid-magicmouse.c')
-rw-r--r--drivers/hid/hid-magicmouse.c334
1 files changed, 325 insertions, 9 deletions
diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index c9c968d4b36a..b22ab0f320c0 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -59,8 +59,12 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
#define MOUSE_REPORT_ID 0x29
#define MOUSE2_REPORT_ID 0x12
#define DOUBLE_REPORT_ID 0xf7
+#define SPI_REPORT_ID 0x02
+#define MTP_REPORT_ID 0x75
#define USB_BATTERY_TIMEOUT_MS 60000
+#define MAX_CONTACTS 16
+
/* 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,
@@ -111,6 +115,25 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
#define TRACKPAD2_RES_Y \
((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100))
+#define J314_TP_DIMENSION_X (float)13000
+#define J314_TP_MIN_X -5900
+#define J314_TP_MAX_X 6500
+#define J314_TP_RES_X \
+ ((J314_TP_MAX_X - J314_TP_MIN_X) / (J314_TP_DIMENSION_X / 100))
+#define J314_TP_DIMENSION_Y (float)8100
+#define J314_TP_MIN_Y -200
+#define J314_TP_MAX_Y 7400
+#define J314_TP_RES_Y \
+ ((J314_TP_MAX_Y - J314_TP_MIN_Y) / (J314_TP_DIMENSION_Y / 100))
+
+#define J314_TP_MAX_FINGER_ORIENTATION 16384
+
+struct magicmouse_input_ops {
+ int (*raw_event)(struct hid_device *hdev,
+ struct hid_report *report, u8 *data, int size);
+ int (*setup_input)(struct input_dev *input, struct hid_device *hdev);
+};
+
/**
* struct magicmouse_sc - Tracks Magic Mouse-specific data.
* @input: Input device through which we report events.
@@ -129,9 +152,8 @@ struct magicmouse_sc {
int scroll_accel;
unsigned long scroll_jiffies;
+ struct input_mt_pos pos[MAX_CONTACTS];
struct {
- short x;
- short y;
short scroll_x;
short scroll_y;
short scroll_x_hr;
@@ -139,12 +161,13 @@ struct magicmouse_sc {
u8 size;
bool scroll_x_active;
bool scroll_y_active;
- } touches[16];
- int tracking_ids[16];
+ } touches[MAX_CONTACTS];
+ int tracking_ids[MAX_CONTACTS];
struct hid_device *hdev;
struct delayed_work work;
struct timer_list battery_timer;
+ struct magicmouse_input_ops input_ops;
};
static int magicmouse_firm_touch(struct magicmouse_sc *msc)
@@ -188,7 +211,7 @@ static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state)
} else if (last_state != 0) {
state = last_state;
} else if ((id = magicmouse_firm_touch(msc)) >= 0) {
- int x = msc->touches[id].x;
+ int x = msc->pos[id].x;
if (x < middle_button_start)
state = 1;
else if (x > middle_button_stop)
@@ -249,8 +272,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
/* Store tracking ID and other fields. */
msc->tracking_ids[raw_id] = id;
- msc->touches[id].x = x;
- msc->touches[id].y = y;
+ msc->pos[id].x = x;
+ msc->pos[id].y = y;
msc->touches[id].size = size;
/* If requested, emulate a scroll wheel by detecting small
@@ -374,6 +397,14 @@ 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);
+
+ return msc->input_ops.raw_event(hdev, report, data, size);
+}
+
+static int magicmouse_raw_event_usb(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 = 0, y = 0, ii, clicks = 0, npoints;
@@ -502,6 +533,175 @@ static int magicmouse_raw_event(struct hid_device *hdev,
return 1;
}
+/**
+ * struct tp_finger - single trackpad finger structure, le16-aligned
+ *
+ * @unknown1: unknown
+ * @unknown2: unknown
+ * @abs_x: absolute x coordinate
+ * @abs_y: absolute y coordinate
+ * @rel_x: relative x coordinate
+ * @rel_y: relative y coordinate
+ * @tool_major: tool area, major axis
+ * @tool_minor: tool area, minor axis
+ * @orientation: 16384 when point, else 15 bit angle
+ * @touch_major: touch area, major axis
+ * @touch_minor: touch area, minor axis
+ * @unused: zeros
+ * @pressure: pressure on forcetouch touchpad
+ * @multi: one finger: varies, more fingers: constant
+ * @crc16: on last finger: crc over the whole message struct
+ * (i.e. message header + this struct) minus the last
+ * @crc16 field; unknown on all other fingers.
+ */
+struct tp_finger {
+ __le16 unknown1;
+ __le16 unknown2;
+ __le16 abs_x;
+ __le16 abs_y;
+ __le16 rel_x;
+ __le16 rel_y;
+ __le16 tool_major;
+ __le16 tool_minor;
+ __le16 orientation;
+ __le16 touch_major;
+ __le16 touch_minor;
+ __le16 unused[2];
+ __le16 pressure;
+ __le16 multi;
+} __attribute__((packed, aligned(2)));
+
+/**
+ * vendor trackpad report
+ *
+ * @num_fingers: the number of fingers being reported in @fingers
+ * @buttons: same as HID buttons
+ */
+struct tp_header {
+ // HID vendor part, up to 1751 bytes
+ u8 unknown[22];
+ u8 num_fingers;
+ u8 buttons;
+ u8 unknown3[14];
+};
+
+/**
+ * standard HID mouse report
+ *
+ * @report_id: reportid
+ * @buttons: HID Usage Buttons 3 1-bit reports
+ */
+struct tp_mouse_report {
+ // HID mouse report
+ u8 report_id;
+ u8 buttons;
+ u8 rel_x;
+ u8 rel_y;
+ u8 padding[4];
+};
+
+static inline int le16_to_int(__le16 x)
+{
+ return (signed short)le16_to_cpu(x);
+}
+
+static void report_finger_data(struct input_dev *input, int slot,
+ const struct input_mt_pos *pos,
+ const struct tp_finger *f)
+{
+ input_mt_slot(input, slot);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+
+ input_report_abs(input, ABS_MT_TOUCH_MAJOR,
+ le16_to_int(f->touch_major) << 1);
+ input_report_abs(input, ABS_MT_TOUCH_MINOR,
+ le16_to_int(f->touch_minor) << 1);
+ input_report_abs(input, ABS_MT_WIDTH_MAJOR,
+ le16_to_int(f->tool_major) << 1);
+ input_report_abs(input, ABS_MT_WIDTH_MINOR,
+ le16_to_int(f->tool_minor) << 1);
+ input_report_abs(input, ABS_MT_ORIENTATION,
+ J314_TP_MAX_FINGER_ORIENTATION - le16_to_int(f->orientation));
+ input_report_abs(input, ABS_MT_PRESSURE, le16_to_int(f->pressure));
+ input_report_abs(input, ABS_MT_POSITION_X, pos->x);
+ input_report_abs(input, ABS_MT_POSITION_Y, pos->y);
+}
+
+static int magicmouse_raw_event_mtp(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;
+ struct tp_header *tp_hdr;
+ struct tp_finger *f;
+ int i, n;
+ u32 npoints;
+ const size_t hdr_sz = sizeof(struct tp_header);
+ const size_t touch_sz = sizeof(struct tp_finger);
+ u8 map_contacs[MAX_CONTACTS];
+
+ // hid_warn(hdev, "%s\n", __func__);
+ // print_hex_dump_debug("appleft ev: ", DUMP_PREFIX_OFFSET, 16, 1, data,
+ // size, false);
+
+ /* Expect 46 bytes of prefix, and N * 30 bytes of touch data. */
+ if (size < hdr_sz || ((size - hdr_sz) % touch_sz) != 0)
+ return 0;
+
+ tp_hdr = (struct tp_header *)data;
+
+ npoints = (size - hdr_sz) / touch_sz;
+ if (npoints < tp_hdr->num_fingers || npoints > MAX_CONTACTS) {
+ hid_warn(hdev,
+ "unexpected number of touches (%u) for "
+ "report\n",
+ npoints);
+ return 0;
+ }
+
+ n = 0;
+ for (i = 0; i < tp_hdr->num_fingers; i++) {
+ f = (struct tp_finger *)(data + hdr_sz + i * touch_sz);
+ if (le16_to_int(f->touch_major) == 0)
+ continue;
+
+ hid_dbg(hdev, "ev x:%04x y:%04x\n", le16_to_int(f->abs_x),
+ le16_to_int(f->abs_y));
+ msc->pos[n].x = le16_to_int(f->abs_x);
+ msc->pos[n].y = -le16_to_int(f->abs_y);
+ map_contacs[n] = i;
+ n++;
+ }
+
+ input_mt_assign_slots(input, msc->tracking_ids, msc->pos, n, 0);
+
+ for (i = 0; i < n; i++) {
+ int idx = map_contacs[i];
+ f = (struct tp_finger *)(data + hdr_sz + idx * touch_sz);
+ report_finger_data(input, msc->tracking_ids[i], &msc->pos[i], f);
+ }
+
+ input_mt_sync_frame(input);
+ input_report_key(input, BTN_MOUSE, tp_hdr->buttons & 1);
+
+ input_sync(input);
+ return 1;
+}
+
+static int magicmouse_raw_event_spi(struct hid_device *hdev,
+ struct hid_report *report, u8 *data, int size)
+{
+ const size_t hdr_sz = sizeof(struct tp_mouse_report);
+
+ if (size < hdr_sz)
+ return 0;
+
+ if (data[0] != TRACKPAD2_USB_REPORT_ID)
+ return 0;
+
+ return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz);
+}
+
static int magicmouse_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
@@ -519,7 +719,17 @@ static int magicmouse_event(struct hid_device *hdev, struct hid_field *field,
return 0;
}
-static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev)
+
+static int magicmouse_setup_input(struct input_dev *input,
+ struct hid_device *hdev)
+{
+ struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+
+ return msc->input_ops.setup_input(input, hdev);
+}
+
+static int magicmouse_setup_input_usb(struct input_dev *input,
+ struct hid_device *hdev)
{
int error;
int mt_flags = 0;
@@ -592,7 +802,7 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
__set_bit(EV_ABS, input->evbit);
- error = input_mt_init_slots(input, 16, mt_flags);
+ error = input_mt_init_slots(input, MAX_CONTACTS, mt_flags);
if (error)
return error;
input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2,
@@ -671,6 +881,79 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
return 0;
}
+static int magicmouse_setup_input_spi(struct input_dev *input,
+ struct hid_device *hdev)
+{
+ int error;
+ int mt_flags = 0;
+
+ __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+ __clear_bit(BTN_0, input->keybit);
+ __clear_bit(BTN_RIGHT, input->keybit);
+ __clear_bit(BTN_MIDDLE, input->keybit);
+ __clear_bit(EV_REL, input->evbit);
+ __clear_bit(REL_X, input->relbit);
+ __clear_bit(REL_Y, input->relbit);
+
+ mt_flags = INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED | INPUT_MT_TRACK;
+
+ /* finger touch area */
+ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 5000, 0, 0);
+ input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 5000, 0, 0);
+
+ /* finger approach area */
+ input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 5000, 0, 0);
+ input_set_abs_params(input, ABS_MT_WIDTH_MINOR, 0, 5000, 0, 0);
+
+ /* 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.
+ */
+
+ input_set_abs_params(input, ABS_MT_PRESSURE, 0, 6000, 0, 0);
+
+ /*
+ * This makes libinput recognize this as a PressurePad and
+ * stop trying to use pressure for touch size. Pressure unit
+ * seems to be ~grams on these touchpads.
+ */
+ input_abs_set_res(input, ABS_MT_PRESSURE, 1);
+
+ /* finger orientation */
+ input_set_abs_params(input, ABS_MT_ORIENTATION, -J314_TP_MAX_FINGER_ORIENTATION,
+ J314_TP_MAX_FINGER_ORIENTATION, 0, 0);
+
+ /* finger position */
+ input_set_abs_params(input, ABS_MT_POSITION_X, J314_TP_MIN_X, J314_TP_MAX_X,
+ 0, 0);
+ /* Y axis is inverted */
+ input_set_abs_params(input, ABS_MT_POSITION_Y, -J314_TP_MAX_Y, -J314_TP_MIN_Y,
+ 0, 0);
+
+ /* X/Y resolution */
+ input_abs_set_res(input, ABS_MT_POSITION_X, J314_TP_RES_X);
+ input_abs_set_res(input, ABS_MT_POSITION_Y, J314_TP_RES_Y);
+
+ input_set_events_per_packet(input, 60);
+
+ /* touchpad button */
+ input_set_capability(input, EV_KEY, BTN_MOUSE);
+
+ /*
+ * hid-input may mark device as using autorepeat, but the trackpad does
+ * not actually want it.
+ */
+ __clear_bit(EV_REP, input->evbit);
+
+ error = input_mt_init_slots(input, MAX_CONTACTS, mt_flags);
+ if (error)
+ return error;
+
+ return 0;
+}
+
static int magicmouse_input_mapping(struct hid_device *hdev,
struct hid_input *hi, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max)
@@ -726,6 +1009,9 @@ static int magicmouse_enable_multitouch(struct hid_device *hdev)
feature_size = sizeof(feature_mt_trackpad2_usb);
feature = feature_mt_trackpad2_usb;
}
+ } else if (hdev->vendor == SPI_VENDOR_ID_APPLE) {
+ feature_size = sizeof(feature_mt_trackpad2_usb);
+ feature = feature_mt_trackpad2_usb;
} else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
feature_size = sizeof(feature_mt_mouse2);
feature = feature_mt_mouse2;
@@ -800,12 +1086,30 @@ static int magicmouse_probe(struct hid_device *hdev,
struct hid_report *report;
int ret;
+ if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && id->vendor == SPI_VENDOR_ID_APPLE &&
+ hdev->type != HID_TYPE_SPI_MOUSE)
+ return -ENODEV;
+
msc = devm_kzalloc(&hdev->dev, sizeof(*msc), GFP_KERNEL);
if (msc == NULL) {
hid_err(hdev, "can't alloc magicmouse descriptor\n");
return -ENOMEM;
}
+ // internal trackpad use a data format use input ops to avoid
+ // conflicts with the report ID.
+ if (id->bus == BUS_HOST) {
+ msc->input_ops.raw_event = magicmouse_raw_event_mtp;
+ msc->input_ops.setup_input = magicmouse_setup_input_spi;
+ } else if (id->bus == BUS_SPI) {
+ msc->input_ops.raw_event = magicmouse_raw_event_spi;
+ msc->input_ops.setup_input = magicmouse_setup_input_spi;
+
+ } else {
+ msc->input_ops.raw_event = magicmouse_raw_event_usb;
+ msc->input_ops.setup_input = magicmouse_setup_input_usb;
+ }
+
msc->scroll_accel = SCROLL_ACCEL_DEFAULT;
msc->hdev = hdev;
INIT_DEFERRABLE_WORK(&msc->work, magicmouse_enable_mt_work);
@@ -854,6 +1158,10 @@ static int magicmouse_probe(struct hid_device *hdev,
else /* USB_VENDOR_ID_APPLE */
report = hid_register_report(hdev, HID_INPUT_REPORT,
TRACKPAD2_USB_REPORT_ID, 0);
+ } else if (id->bus == BUS_SPI) {
+ report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_REPORT_ID, 0);
+ } else if (id->bus == BUS_HOST) {
+ report = hid_register_report(hdev, HID_INPUT_REPORT, MTP_REPORT_ID, 0);
} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
report = hid_register_report(hdev, HID_INPUT_REPORT,
TRACKPAD_REPORT_ID, 0);
@@ -868,6 +1176,10 @@ static int magicmouse_probe(struct hid_device *hdev,
}
report->size = 6;
+ /* MTP devices do not need the MT enable, this is handled by the MTP driver */
+ if (id->bus == BUS_HOST)
+ return 0;
+
/*
* Some devices repond with 'invalid report id' when feature
* report switching it into multitouch mode is sent to it.
@@ -948,6 +1260,10 @@ static const struct hid_device_id magic_mice[] = {
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
+ { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID),
+ .driver_data = 0 },
+ { HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE,
+ HID_ANY_ID), .driver_data = 0 },
{ }
};
MODULE_DEVICE_TABLE(hid, magic_mice);