Module Name:    src
Committed By:   bouyer
Date:           Mon Apr  6 17:45:31 UTC 2015

Modified Files:
        src/sys/dev/bluetooth: btmagic.c

Log Message:
Add support for Apple Magic Trackpad.
3 button emulation by detecting in which area of the bottom of
the device the trackpad's button is pressed.
Pointer move support with 1 finger touch, X/Y scroll with 2-finger touch.
TODO:
- detect tap to emulate button press and drag/n/drop.
- Detect and support zoom, if wsmouse allows to report this


To generate a diff of this commit:
cvs rdiff -u -r1.11 -r1.12 src/sys/dev/bluetooth/btmagic.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/dev/bluetooth/btmagic.c
diff -u src/sys/dev/bluetooth/btmagic.c:1.11 src/sys/dev/bluetooth/btmagic.c:1.12
--- src/sys/dev/bluetooth/btmagic.c:1.11	Tue Aug  5 07:55:31 2014
+++ src/sys/dev/bluetooth/btmagic.c	Mon Apr  6 17:45:31 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: btmagic.c,v 1.11 2014/08/05 07:55:31 rtr Exp $	*/
+/*	$NetBSD: btmagic.c,v 1.12 2015/04/06 17:45:31 bouyer Exp $	*/
 
 /*-
  * Copyright (c) 2010 The NetBSD Foundation, Inc.
@@ -85,7 +85,7 @@
  *****************************************************************************/
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: btmagic.c,v 1.11 2014/08/05 07:55:31 rtr Exp $");
+__KERNEL_RCSID(0, "$NetBSD: btmagic.c,v 1.12 2015/04/06 17:45:31 bouyer Exp $");
 
 #include <sys/param.h>
 #include <sys/conf.h>
@@ -163,11 +163,13 @@ struct btmagic_softc {
 	int			sc_rw;
 
 	/* previous touches */
-	uint32_t		sc_smask;	/* scrolling */
-	int			sc_az[16];
-	int			sc_aw[16];
+	uint32_t		sc_smask;	/* active(s) IDs */
+	int			sc_nfingers;	/* number of active IDs */
+	int			sc_ax[16];
+	int			sc_ay[16];
 
 	/* previous mouse buttons */
+	int			sc_mb_id; /* which ID selects the button */
 	uint32_t		sc_mb;
 };
 
@@ -216,7 +218,16 @@ static void  btmagic_complete(void *, in
 static void  btmagic_linkmode(void *, int);
 static void  btmagic_input(void *, struct mbuf *);
 static void  btmagic_input_basic(struct btmagic_softc *, uint8_t *, size_t);
-static void  btmagic_input_magic(struct btmagic_softc *, uint8_t *, size_t);
+static void  btmagic_input_magicm(struct btmagic_softc *, uint8_t *, size_t);
+static void  btmagic_input_magict(struct btmagic_softc *, uint8_t *, size_t);
+
+/* report types (data[1]) */
+#define BASIC_REPORT_ID		0x10
+#define TRACKPAD_REPORT_ID	0x28
+#define MOUSE_REPORT_ID		0x29
+#define BATT_STAT_REPORT_ID	0x30
+#define BATT_STRENGHT_REPORT_ID	0x47
+#define SURFACE_REPORT_ID	0x61
 
 static const struct btproto btmagic_ctl_proto = {
 	btmagic_connecting,
@@ -259,7 +270,8 @@ btmagic_match(device_t self, cfdata_t cf
 	if (prop_dictionary_get_uint16(aux, BTDEVvendor, &v)
 	    && prop_dictionary_get_uint16(aux, BTDEVproduct, &p)
 	    && v == USB_VENDOR_APPLE
-	    && p == USB_PRODUCT_APPLE_MAGICMOUSE)
+	    && (p == USB_PRODUCT_APPLE_MAGICMOUSE ||
+		p == USB_PRODUCT_APPLE_MAGICTRACKPAD))
 		return 2;	/* trump bthidev(4) */
 
 	return 0;
@@ -1047,15 +1059,18 @@ btmagic_input(void *arg, struct mbuf *m)
 			break;
 
 		switch (data[1]) {
-		case 0x10: /* Basic mouse (input) */
+		case BASIC_REPORT_ID: /* Basic mouse (input) */
 			btmagic_input_basic(sc, data + 2, len - 2);
 			break;
 
-		case 0x29: /* Magic touch (input) */
-			btmagic_input_magic(sc, data + 2, len - 2);
+		case TRACKPAD_REPORT_ID: /* Magic trackpad (input) */
+			btmagic_input_magict(sc, data + 2, len - 2);
+			break;
+		case MOUSE_REPORT_ID: /* Magic touch (input) */
+			btmagic_input_magicm(sc, data + 2, len - 2);
 			break;
 
-		case 0x30: /* Battery status (input) */
+		case BATT_STAT_REPORT_ID: /* Battery status (input) */
 			if (len != 3)
 				break;
 
@@ -1068,7 +1083,7 @@ btmagic_input(void *arg, struct mbuf *m)
 			}
 			break;
 
-		case 0x47: /* Battery strength (feature) */
+		case BATT_STRENGHT_REPORT_ID: /* Battery strength (feature) */
 			if (len != 3)
 				break;
 
@@ -1076,7 +1091,7 @@ btmagic_input(void *arg, struct mbuf *m)
 			    data[2]);
 			break;
 
-		case 0x61: /* Surface detection (input) */
+		case SURFACE_REPORT_ID: /* Surface detection (input) */
 			if (len != 3)
 				break;
 
@@ -1246,7 +1261,7 @@ static const struct {
 #define BTMAGIC_PHASE_CANCEL	0x0
 
 static void
-btmagic_input_magic(struct btmagic_softc *sc, uint8_t *data, size_t len)
+btmagic_input_magicm(struct btmagic_softc *sc, uint8_t *data, size_t len)
 {
 	uint32_t mb;
 	int dx, dy, dz, dw;
@@ -1290,10 +1305,12 @@ btmagic_input_magic(struct btmagic_softc
 
 		switch (hid_get_udata(data, &touch.phase)) {
 		case BTMAGIC_PHASE_CONT:
+#define sc_az sc_ay
+#define sc_aw sc_ax
 			tz = az - sc->sc_az[id];
 			tw = aw - sc->sc_aw[id];
 
-			if (ISSET(sc->sc_smask, id)) {
+			if (ISSET(sc->sc_smask, __BIT(id))) {
 				/* scrolling finger */
 				dz += btmagic_scale(tz, &sc->sc_rz,
 				    sc->sc_resolution / sc->sc_scale);
@@ -1307,7 +1324,7 @@ btmagic_input_magic(struct btmagic_softc
 					sc->sc_rw = 0;
 				}
 
-				SET(sc->sc_smask, id);
+				SET(sc->sc_smask, __BIT(id));
 			} else {
 				/* not scrolling finger */
 				az = sc->sc_az[id];
@@ -1321,12 +1338,14 @@ btmagic_input_magic(struct btmagic_softc
 			break;
 
 		default:
-			CLR(sc->sc_smask, id);
+			CLR(sc->sc_smask, __BIT(id));
 			break;
 		}
 
 		sc->sc_az[id] = az;
 		sc->sc_aw[id] = aw;
+#undef sc_az
+#undef sc_aw
 	}
 
 	/*
@@ -1355,3 +1374,204 @@ btmagic_input_magic(struct btmagic_softc
 		splx(s);
 	}
 }
+
+/*
+ * the Magic touch trackpad report (0x28), according to the Linux driver
+ * written by Michael Poole and Chase Douglas, is variable length starting
+ * with the fixed 24-bit header
+ *
+ *	button 1	1-bit
+ *      unknown		5-bits
+ *	timestamp	18-bits
+ *
+ * followed by (up to 5?) touch reports of 72-bits each
+ *
+ *	abs X		13-bits (signed)
+ *	abs Y		13-bits (signed)
+ * 	unknown		6-bits
+ *	axis major	8-bits
+ *	axis minor	8-bits
+ *	pressure	6-bits
+ *	id		4-bits
+ *	angle		6-bits	(from E(0)->N(32)->W(64))
+ *	unknown		4-bits
+ *	phase		4-bits
+ */
+
+static const struct {
+	struct hid_location button;
+	struct hid_location timestamp;
+} magict = {
+	.button = { .pos =  0, .size = 1 },
+	.timestamp = { .pos = 6, .size = 18 },
+};
+
+static const struct {
+	struct hid_location aX;
+	struct hid_location aY;
+	struct hid_location major;
+	struct hid_location minor;
+	struct hid_location pressure;
+	struct hid_location id;
+	struct hid_location angle;
+	struct hid_location unknown;
+	struct hid_location phase;
+} toucht = {
+	.aX = { .pos = 0, .size = 13 },
+	.aY = { .pos = 13, .size = 13 },
+	.major = { .pos = 32, .size = 8 },
+	.minor = { .pos = 40, .size = 8 },
+	.pressure = { .pos = 48, .size = 6 },
+	.id = { .pos = 54, .size = 4 },
+	.angle = { .pos = 58, .size = 6 },
+	.unknown = { .pos = 64, .size = 4 },
+	.phase = { .pos = 68, .size = 4 },
+};
+
+/*
+ * as for btmagic_input_magicm, 
+ * the phase of the touch starts at 0x01 as the finger is first detected
+ * approaching the mouse, increasing to 0x04 while the finger is touching,
+ * then increases towards 0x07 as the finger is lifted, and we get 0x00
+ * when the touch is cancelled. The values below seem to be produced for
+ * every touch, the others less consistently depending on how fast the
+ * approach or departure is.
+ *
+ * In fact we ignore touches unless they are in the steady 0x04 phase.
+ */
+
+/* min and max values reported */
+#define MAGICT_X_MIN	(-2910)
+#define MAGICT_X_MAX	(3170)
+#define MAGICT_Y_MIN	(-2565)
+#define MAGICT_Y_MAX	(2455)
+
+/*
+ * area for detecting the buttons: divide in 3 areas on X, 
+ * below -1900 on y
+ */
+#define MAGICT_B_YMAX	(-1900)
+#define MAGICT_B_XSIZE	((MAGICT_X_MAX - MAGICT_X_MIN) / 3)
+#define MAGICT_B_X1MAX	(MAGICT_X_MIN + MAGICT_B_XSIZE)
+#define MAGICT_B_X2MAX	(MAGICT_X_MIN + MAGICT_B_XSIZE * 2)
+
+static void
+btmagic_input_magict(struct btmagic_softc *sc, uint8_t *data, size_t len)
+{
+	bool bpress;
+	uint32_t mb;
+	int id, ax, ay, tx, ty;
+	int dx, dy, dz, dw;
+	int s;
+
+	if (((len - 3) % 9) != 0)
+		return;
+
+	bpress = 0;
+	if (hid_get_udata(data, &magict.button))
+		bpress = 1;
+
+	dx = dy = dz = dw = 0;
+	mb = 0;
+
+	len = (len - 3) / 9;
+	for (data += 3; len-- > 0; data += 9) {
+		id = hid_get_udata(data, &toucht.id);
+		ax = hid_get_data(data, &toucht.aX);
+		ay = hid_get_data(data, &toucht.aY);
+
+		DPRINTF(sc,
+		    "btmagic_input_magicm: id %d ax %d ay %d phase %ld %s\n",
+		    id, ax, ay, hid_get_udata(data, &toucht.phase),
+		    bpress ? "button pressed" : "");
+
+		/*
+		 * a single touch is interpreted as a mouse move.
+		 * If a button is pressed, the touch in the button area
+		 * defined above defines the button; a second touch is
+		 * interpreted as a mouse move.
+		 */
+
+		switch (hid_get_udata(data, &toucht.phase)) {
+		case BTMAGIC_PHASE_CONT:
+			if (bpress) {
+				if (sc->sc_mb == 0 && ay < MAGICT_B_YMAX) {
+					/*
+					 * we have a new button press,
+					 * and this id tells which one
+					 */
+					if (ax < MAGICT_B_X1MAX)
+						mb = __BIT(0);
+					else if (ax > MAGICT_B_X2MAX)
+						mb = __BIT(2);
+					else
+						mb = __BIT(1);
+					sc->sc_mb_id = id;
+				} else {
+					/* keep previous state */
+					mb = sc->sc_mb;
+				}
+			} else {
+				/* no button pressed */
+				mb = 0;
+				sc->sc_mb_id = -1;
+			}
+			if (id == sc->sc_mb_id) {
+				/*
+				 * this id selects the button
+				 * ignore for move/scroll
+				 */
+				 continue;
+			}
+					
+			tx = ax - sc->sc_ax[id];
+			ty = ay - sc->sc_ay[id];
+
+			if (ISSET(sc->sc_smask, __BIT(id))) {
+				if (sc->sc_nfingers == 1 || mb != 0) {
+					/* single finger moving */
+					dx += btmagic_scale(tx, &sc->sc_rx,
+					    sc->sc_resolution);
+					dy += btmagic_scale(ty, &sc->sc_ry,
+					    sc->sc_resolution);
+				} else {
+					/* scrolling fingers */
+					dz += btmagic_scale(ty, &sc->sc_rz,
+					    sc->sc_resolution / sc->sc_scale);
+					dw += btmagic_scale(tx, &sc->sc_rw,
+					    sc->sc_resolution / sc->sc_scale);
+				}
+			} else if (ay > MAGICT_B_YMAX) { /* new finger */
+				sc->sc_rx = 0;
+				sc->sc_ry = 0;
+				sc->sc_rz = 0;
+				sc->sc_rw = 0;
+
+				KASSERT(!ISSET(sc->sc_smask, __BIT(id)));
+				SET(sc->sc_smask, __BIT(id));
+				sc->sc_nfingers++;
+			}
+
+			break;
+		default:
+			if (ISSET(sc->sc_smask, __BIT(id))) {
+				CLR(sc->sc_smask, __BIT(id));
+				sc->sc_nfingers--;
+				KASSERT(sc->sc_nfingers >= 0);
+			}
+			break;
+		}
+
+		sc->sc_ax[id] = ax;
+		sc->sc_ay[id] = ay;
+	}
+
+	if (dx != 0 || dy != 0 || dz != 0 || dw != 0 || mb != sc->sc_mb) {
+		sc->sc_mb = mb;
+
+		s = spltty();
+		wsmouse_input(sc->sc_wsmouse, mb,
+		    dx, dy, -dz, dw, WSMOUSE_INPUT_DELTA);
+		splx(s);
+	}
+}

Reply via email to