summary refs log tree commit diff
path: root/drivers/hid
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/hid-wiimote-core.c669
-rw-r--r--drivers/hid/hid-wiimote-modules.c16
-rw-r--r--drivers/hid/hid-wiimote.h49
3 files changed, 709 insertions, 25 deletions
diff --git a/drivers/hid/hid-wiimote-core.c b/drivers/hid/hid-wiimote-core.c
index 1ea70c8a824d..836611e3e37e 100644
--- a/drivers/hid/hid-wiimote-core.c
+++ b/drivers/hid/hid-wiimote-core.c
@@ -179,17 +179,38 @@ void wiiproto_req_leds(struct wiimote_data *wdata, int leds)
  * Check what peripherals of the wiimote are currently
  * active and select a proper DRM that supports all of
  * the requested data inputs.
+ *
+ * Not all combinations are actually supported. The following
+ * combinations work only with limitations:
+ *  - IR cam in extended or full mode disables any data transmission
+ *    of extension controllers. There is no DRM mode that supports
+ *    extension bytes plus extended/full IR.
+ *  - IR cam with accelerometer and extension *_EXT8 is not supported.
+ *    However, all extensions that need *_EXT8 are devices that don't
+ *    support IR cameras. Hence, this shouldn't happen under normal
+ *    operation.
+ *  - *_EXT16 is only supported in combination with buttons and
+ *    accelerometer. No IR or similar can be active simultaneously. As
+ *    above, all modules that require it are mutually exclusive with
+ *    IR/etc. so this doesn't matter.
  */
 static __u8 select_drm(struct wiimote_data *wdata)
 {
 	__u8 ir = wdata->state.flags & WIIPROTO_FLAGS_IR;
-	bool ext = wiiext_active(wdata);
+	bool ext;
+
+	ext = (wdata->state.flags & WIIPROTO_FLAG_EXT_USED) ||
+	      (wdata->state.flags & WIIPROTO_FLAG_MP_USED);
 
 	if (ir == WIIPROTO_FLAG_IR_BASIC) {
-		if (wdata->state.flags & WIIPROTO_FLAG_ACCEL)
-			return WIIPROTO_REQ_DRM_KAIE;
-		else
+		if (wdata->state.flags & WIIPROTO_FLAG_ACCEL) {
+			if (ext)
+				return WIIPROTO_REQ_DRM_KAIE;
+			else
+				return WIIPROTO_REQ_DRM_KAI;
+		} else {
 			return WIIPROTO_REQ_DRM_KIE;
+		}
 	} else if (ir == WIIPROTO_FLAG_IR_EXT) {
 		return WIIPROTO_REQ_DRM_KAI;
 	} else if (ir == WIIPROTO_FLAG_IR_FULL) {
@@ -202,7 +223,7 @@ static __u8 select_drm(struct wiimote_data *wdata)
 				return WIIPROTO_REQ_DRM_KA;
 		} else {
 			if (ext)
-				return WIIPROTO_REQ_DRM_KE;
+				return WIIPROTO_REQ_DRM_KEE;
 			else
 				return WIIPROTO_REQ_DRM_K;
 		}
@@ -399,9 +420,8 @@ static int wiimote_cmd_init_ext(struct wiimote_data *wdata)
 }
 
 /* requires the cmd-mutex to be held */
-static __u8 wiimote_cmd_read_ext(struct wiimote_data *wdata)
+static __u8 wiimote_cmd_read_ext(struct wiimote_data *wdata, __u8 *rmem)
 {
-	__u8 rmem[6];
 	int ret;
 
 	/* read extension ID */
@@ -409,6 +429,9 @@ static __u8 wiimote_cmd_read_ext(struct wiimote_data *wdata)
 	if (ret != 6)
 		return WIIMOTE_EXT_NONE;
 
+	hid_dbg(wdata->hdev, "extension ID: %02x:%02x %02x:%02x %02x:%02x\n",
+		rmem[0], rmem[1], rmem[2], rmem[3], rmem[4], rmem[5]);
+
 	if (rmem[0] == 0xff && rmem[1] == 0xff && rmem[2] == 0xff &&
 	    rmem[3] == 0xff && rmem[4] == 0xff && rmem[5] == 0xff)
 		return WIIMOTE_EXT_NONE;
@@ -416,6 +439,92 @@ static __u8 wiimote_cmd_read_ext(struct wiimote_data *wdata)
 	return WIIMOTE_EXT_UNKNOWN;
 }
 
+/* requires the cmd-mutex to be held */
+static int wiimote_cmd_init_mp(struct wiimote_data *wdata)
+{
+	__u8 wmem;
+	int ret;
+
+	/* initialize MP */
+	wmem = 0x55;
+	ret = wiimote_cmd_write(wdata, 0xa600f0, &wmem, sizeof(wmem));
+	if (ret)
+		return ret;
+
+	/* disable default encryption */
+	wmem = 0x0;
+	ret = wiimote_cmd_write(wdata, 0xa600fb, &wmem, sizeof(wmem));
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/* requires the cmd-mutex to be held */
+static bool wiimote_cmd_map_mp(struct wiimote_data *wdata, __u8 exttype)
+{
+	__u8 wmem;
+
+	/* map MP with correct pass-through mode */
+	switch (exttype) {
+	default:
+		wmem = 0x04;
+		break;
+	}
+
+	return wiimote_cmd_write(wdata, 0xa600fe, &wmem, sizeof(wmem));
+}
+
+/* requires the cmd-mutex to be held */
+static bool wiimote_cmd_read_mp(struct wiimote_data *wdata, __u8 *rmem)
+{
+	int ret;
+
+	/* read motion plus ID */
+	ret = wiimote_cmd_read(wdata, 0xa600fa, rmem, 6);
+	if (ret != 6)
+		return false;
+
+	hid_dbg(wdata->hdev, "motion plus ID: %02x:%02x %02x:%02x %02x:%02x\n",
+		rmem[0], rmem[1], rmem[2], rmem[3], rmem[4], rmem[5]);
+
+	if (rmem[5] == 0x05)
+		return true;
+
+	hid_info(wdata->hdev, "unknown motion plus ID: %02x:%02x %02x:%02x %02x:%02x\n",
+		 rmem[0], rmem[1], rmem[2], rmem[3], rmem[4], rmem[5]);
+
+	return false;
+}
+
+/* requires the cmd-mutex to be held */
+static __u8 wiimote_cmd_read_mp_mapped(struct wiimote_data *wdata)
+{
+	int ret;
+	__u8 rmem[6];
+
+	/* read motion plus ID */
+	ret = wiimote_cmd_read(wdata, 0xa400fa, rmem, 6);
+	if (ret != 6)
+		return WIIMOTE_MP_NONE;
+
+	hid_dbg(wdata->hdev, "mapped motion plus ID: %02x:%02x %02x:%02x %02x:%02x\n",
+		rmem[0], rmem[1], rmem[2], rmem[3], rmem[4], rmem[5]);
+
+	if (rmem[0] == 0xff && rmem[1] == 0xff && rmem[2] == 0xff &&
+	    rmem[3] == 0xff && rmem[4] == 0xff && rmem[5] == 0xff)
+		return WIIMOTE_MP_NONE;
+
+	if (rmem[4] == 0x04 && rmem[5] == 0x05)
+		return WIIMOTE_MP_SINGLE;
+	else if (rmem[4] == 0x05 && rmem[5] == 0x05)
+		return WIIMOTE_MP_PASSTHROUGH_NUNCHUK;
+	else if (rmem[4] == 0x07 && rmem[5] == 0x05)
+		return WIIMOTE_MP_PASSTHROUGH_CLASSIC;
+
+	return WIIMOTE_MP_UNKNOWN;
+}
+
 /* device module handling */
 
 static const __u8 * const wiimote_devtype_mods[WIIMOTE_DEV_NUM] = {
@@ -561,6 +670,81 @@ static void wiimote_modules_unload(struct wiimote_data *wdata)
 	}
 }
 
+/* device extension handling */
+
+static void wiimote_ext_load(struct wiimote_data *wdata, unsigned int ext)
+{
+	unsigned long flags;
+	const struct wiimod_ops *ops;
+	int ret;
+
+	ops = wiimod_ext_table[ext];
+
+	if (ops->probe) {
+		ret = ops->probe(ops, wdata);
+		if (ret)
+			ext = WIIMOTE_EXT_UNKNOWN;
+	}
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.exttype = ext;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static void wiimote_ext_unload(struct wiimote_data *wdata)
+{
+	unsigned long flags;
+	const struct wiimod_ops *ops;
+
+	ops = wiimod_ext_table[wdata->state.exttype];
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.exttype = WIIMOTE_EXT_UNKNOWN;
+	wdata->state.flags &= ~WIIPROTO_FLAG_EXT_USED;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	if (ops->remove)
+		ops->remove(ops, wdata);
+}
+
+static void wiimote_mp_load(struct wiimote_data *wdata)
+{
+	unsigned long flags;
+	const struct wiimod_ops *ops;
+	int ret;
+	__u8 mode = 2;
+
+	ops = &wiimod_mp;
+	if (ops->probe) {
+		ret = ops->probe(ops, wdata);
+		if (ret)
+			mode = 1;
+	}
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.mp = mode;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static void wiimote_mp_unload(struct wiimote_data *wdata)
+{
+	unsigned long flags;
+	const struct wiimod_ops *ops;
+
+	if (wdata->state.mp < 2)
+		return;
+
+	ops = &wiimod_mp;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.mp = 0;
+	wdata->state.flags &= ~WIIPROTO_FLAG_MP_USED;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	if (ops->remove)
+		ops->remove(ops, wdata);
+}
+
 /* device (re-)initialization and detection */
 
 static const char *wiimote_devtype_names[WIIMOTE_DEV_NUM] = {
@@ -617,7 +801,7 @@ done:
 
 static void wiimote_init_detect(struct wiimote_data *wdata)
 {
-	__u8 exttype = WIIMOTE_EXT_NONE;
+	__u8 exttype = WIIMOTE_EXT_NONE, extdata[6];
 	bool ext;
 	int ret;
 
@@ -641,11 +825,301 @@ static void wiimote_init_detect(struct wiimote_data *wdata)
 		goto out_release;
 
 	wiimote_cmd_init_ext(wdata);
-	exttype = wiimote_cmd_read_ext(wdata);
+	exttype = wiimote_cmd_read_ext(wdata, extdata);
 
 out_release:
 	wiimote_cmd_release(wdata);
 	wiimote_init_set_type(wdata, exttype);
+	/* schedule MP timer */
+	mod_timer(&wdata->timer, jiffies + HZ * 4);
+}
+
+/*
+ * MP hotplug events are not generated by the wiimote. Therefore, we need
+ * polling to detect it. We use a 4s interval for polling MP registers. This
+ * seems reasonable considering applications can trigger it manually via
+ * sysfs requests.
+ */
+static void wiimote_init_poll_mp(struct wiimote_data *wdata)
+{
+	bool mp;
+	__u8 mpdata[6];
+
+	wiimote_cmd_acquire_noint(wdata);
+	wiimote_cmd_init_mp(wdata);
+	mp = wiimote_cmd_read_mp(wdata, mpdata);
+	wiimote_cmd_release(wdata);
+
+	/* load/unload MP module if it changed */
+	if (mp) {
+		if (!wdata->state.mp) {
+			hid_info(wdata->hdev, "detected extension: Nintendo Wii Motion Plus\n");
+			wiimote_mp_load(wdata);
+		}
+	} else if (wdata->state.mp) {
+		wiimote_mp_unload(wdata);
+	}
+
+	mod_timer(&wdata->timer, jiffies + HZ * 4);
+}
+
+/*
+ * Check whether the wiimote is in the expected state. The extension registers
+ * may change during hotplug and initialization so we might get hotplug events
+ * that we caused by remapping some memory.
+ * We use some heuristics here to check known states. If the wiimote is in the
+ * expected state, we can ignore the hotplug event.
+ *
+ * Returns "true" if the device is in expected state, "false" if we should
+ * redo hotplug handling and extension initialization.
+ */
+static bool wiimote_init_check(struct wiimote_data *wdata)
+{
+	__u32 flags;
+	__u8 type, data[6];
+	bool ret, poll_mp;
+
+	spin_lock_irq(&wdata->state.lock);
+	flags = wdata->state.flags;
+	spin_unlock_irq(&wdata->state.lock);
+
+	wiimote_cmd_acquire_noint(wdata);
+
+	/* If MP is used and active, but the extension is not, we expect:
+	 *   read_mp_mapped() == WIIMOTE_MP_SINGLE
+	 *   state.flags == !EXT_ACTIVE && !MP_PLUGGED && MP_ACTIVE
+	 * We do not check EXT_PLUGGED because it might change during
+	 * initialization of MP without extensions.
+	 *  - If MP is unplugged/replugged, read_mp_mapped() fails
+	 *  - If EXT is plugged, MP_PLUGGED will get set */
+	if (wdata->state.exttype == WIIMOTE_EXT_NONE &&
+	    wdata->state.mp > 0 && (flags & WIIPROTO_FLAG_MP_USED)) {
+		type = wiimote_cmd_read_mp_mapped(wdata);
+		ret = type == WIIMOTE_MP_SINGLE;
+
+		spin_lock_irq(&wdata->state.lock);
+		ret = ret && !(wdata->state.flags & WIIPROTO_FLAG_EXT_ACTIVE);
+		ret = ret && !(wdata->state.flags & WIIPROTO_FLAG_MP_PLUGGED);
+		ret = ret && (wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE);
+		spin_unlock_irq(&wdata->state.lock);
+
+		if (!ret)
+			hid_dbg(wdata->hdev, "state left: !EXT && MP\n");
+
+		/* while MP is mapped, we get EXT_PLUGGED events */
+		poll_mp = false;
+
+		goto out_release;
+	}
+
+	/* If MP is unused, but the extension port is used, we expect:
+	 *   read_ext == state.exttype
+	 *   state.flags == !MP_ACTIVE && EXT_ACTIVE
+	 * - If MP is plugged/unplugged, our timer detects it
+	 * - If EXT is unplugged/replugged, EXT_ACTIVE will become unset */
+	if (!(flags & WIIPROTO_FLAG_MP_USED) &&
+	    wdata->state.exttype != WIIMOTE_EXT_NONE) {
+		type = wiimote_cmd_read_ext(wdata, data);
+		ret = type == wdata->state.exttype;
+
+		spin_lock_irq(&wdata->state.lock);
+		ret = ret && !(wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE);
+		ret = ret && (wdata->state.flags & WIIPROTO_FLAG_EXT_ACTIVE);
+		spin_unlock_irq(&wdata->state.lock);
+
+		if (!ret)
+			hid_dbg(wdata->hdev, "state left: EXT && !MP\n");
+
+		/* poll MP for hotplug events */
+		poll_mp = true;
+
+		goto out_release;
+	}
+
+	/* If neither MP nor an extension are used, we expect:
+	 *   read_ext() == WIIMOTE_EXT_NONE
+	 *   state.flags == !MP_ACTIVE && !EXT_ACTIVE && !EXT_PLUGGED
+	 * No need to perform any action in this case as everything is
+	 * disabled already.
+	 * - If MP is plugged/unplugged, our timer detects it
+	 * - If EXT is plugged, EXT_PLUGGED will be set */
+	if (!(flags & WIIPROTO_FLAG_MP_USED) &&
+	    wdata->state.exttype == WIIMOTE_EXT_NONE) {
+		type = wiimote_cmd_read_ext(wdata, data);
+		ret = type == wdata->state.exttype;
+
+		spin_lock_irq(&wdata->state.lock);
+		ret = ret && !(wdata->state.flags & WIIPROTO_FLAG_EXT_ACTIVE);
+		ret = ret && !(wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE);
+		ret = ret && !(wdata->state.flags & WIIPROTO_FLAG_EXT_PLUGGED);
+		spin_unlock_irq(&wdata->state.lock);
+
+		if (!ret)
+			hid_dbg(wdata->hdev, "state left: !EXT && !MP\n");
+
+		/* poll MP for hotplug events */
+		poll_mp = true;
+
+		goto out_release;
+	}
+
+	/* The trickiest part is if both EXT and MP are active. We cannot read
+	 * the EXT ID, anymore, because MP is mapped over it. However, we use
+	 * a handy trick here:
+	 *   - EXT_ACTIVE is unset whenever !MP_PLUGGED is sent
+	 * MP_PLUGGED might be re-sent again before we are scheduled, but
+	 * EXT_ACTIVE will stay unset.
+	 * So it is enough to check for mp_mapped() and MP_ACTIVE and
+	 * EXT_ACTIVE. EXT_PLUGGED is a sanity check. */
+	if (wdata->state.exttype != WIIMOTE_EXT_NONE &&
+	    wdata->state.mp > 0 && (flags & WIIPROTO_FLAG_MP_USED)) {
+		type = wiimote_cmd_read_mp_mapped(wdata);
+		ret = type != WIIMOTE_MP_NONE;
+		ret = ret && type != WIIMOTE_MP_UNKNOWN;
+		ret = ret && type != WIIMOTE_MP_SINGLE;
+
+		spin_lock_irq(&wdata->state.lock);
+		ret = ret && (wdata->state.flags & WIIPROTO_FLAG_EXT_PLUGGED);
+		ret = ret && (wdata->state.flags & WIIPROTO_FLAG_EXT_ACTIVE);
+		ret = ret && (wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE);
+		spin_unlock_irq(&wdata->state.lock);
+
+		if (!ret)
+			hid_dbg(wdata->hdev, "state left: EXT && MP\n");
+
+		/* while MP is mapped, we get EXT_PLUGGED events */
+		poll_mp = false;
+
+		goto out_release;
+	}
+
+	/* unknown state */
+	ret = false;
+
+out_release:
+	wiimote_cmd_release(wdata);
+
+	/* only poll for MP if requested and if state didn't change */
+	if (ret && poll_mp)
+		wiimote_init_poll_mp(wdata);
+
+	return ret;
+}
+
+static const char *wiimote_exttype_names[WIIMOTE_EXT_NUM] = {
+	[WIIMOTE_EXT_NONE] = "None",
+	[WIIMOTE_EXT_UNKNOWN] = "Unknown",
+};
+
+/*
+ * Handle hotplug events
+ * If we receive an hotplug event and the device-check failed, we deinitialize
+ * the extension ports, re-read all extension IDs and set the device into
+ * the desired state. This involves mapping MP into the main extension
+ * registers, setting up extension passthrough modes and initializing the
+ * requested extensions.
+ */
+static void wiimote_init_hotplug(struct wiimote_data *wdata)
+{
+	__u8 exttype, extdata[6], mpdata[6];
+	__u32 flags;
+	bool mp;
+
+	hid_dbg(wdata->hdev, "detect extensions..\n");
+
+	wiimote_cmd_acquire_noint(wdata);
+
+	spin_lock_irq(&wdata->state.lock);
+
+	/* get state snapshot that we will then work on */
+	flags = wdata->state.flags;
+
+	/* disable event forwarding temporarily */
+	wdata->state.flags &= ~WIIPROTO_FLAG_EXT_ACTIVE;
+	wdata->state.flags &= ~WIIPROTO_FLAG_MP_ACTIVE;
+
+	spin_unlock_irq(&wdata->state.lock);
+
+	/* init extension and MP (deactivates current extension or MP) */
+	wiimote_cmd_init_ext(wdata);
+	wiimote_cmd_init_mp(wdata);
+	mp = wiimote_cmd_read_mp(wdata, mpdata);
+	exttype = wiimote_cmd_read_ext(wdata, extdata);
+
+	wiimote_cmd_release(wdata);
+
+	/* load/unload extension module if it changed */
+	if (exttype != wdata->state.exttype) {
+		/* unload previous extension */
+		wiimote_ext_unload(wdata);
+
+		if (exttype == WIIMOTE_EXT_UNKNOWN) {
+			hid_info(wdata->hdev, "cannot detect extension; %02x:%02x %02x:%02x %02x:%02x\n",
+				 extdata[0], extdata[1], extdata[2],
+				 extdata[3], extdata[4], extdata[5]);
+		} else if (exttype == WIIMOTE_EXT_NONE) {
+			spin_lock_irq(&wdata->state.lock);
+			wdata->state.exttype = WIIMOTE_EXT_NONE;
+			spin_unlock_irq(&wdata->state.lock);
+		} else {
+			hid_info(wdata->hdev, "detected extension: %s\n",
+				 wiimote_exttype_names[exttype]);
+			/* try loading new extension */
+			wiimote_ext_load(wdata, exttype);
+		}
+	}
+
+	/* load/unload MP module if it changed */
+	if (mp) {
+		if (!wdata->state.mp) {
+			hid_info(wdata->hdev, "detected extension: Nintendo Wii Motion Plus\n");
+			wiimote_mp_load(wdata);
+		}
+	} else if (wdata->state.mp) {
+		wiimote_mp_unload(wdata);
+	}
+
+	/* if MP is not used, do not map or activate it */
+	if (!(flags & WIIPROTO_FLAG_MP_USED))
+		mp = false;
+
+	/* map MP into main extension registers if used */
+	if (mp) {
+		wiimote_cmd_acquire_noint(wdata);
+		wiimote_cmd_map_mp(wdata, exttype);
+		wiimote_cmd_release(wdata);
+
+		/* delete MP hotplug timer */
+		del_timer_sync(&wdata->timer);
+	} else {
+		/* reschedule MP hotplug timer */
+		mod_timer(&wdata->timer, jiffies + HZ * 4);
+	}
+
+	spin_lock_irq(&wdata->state.lock);
+
+	/* enable data forwarding again and set expected hotplug state */
+	if (mp) {
+		wdata->state.flags |= WIIPROTO_FLAG_MP_ACTIVE;
+		if (wdata->state.exttype == WIIMOTE_EXT_NONE) {
+			wdata->state.flags &= ~WIIPROTO_FLAG_EXT_PLUGGED;
+			wdata->state.flags &= ~WIIPROTO_FLAG_MP_PLUGGED;
+		} else {
+			wdata->state.flags &= ~WIIPROTO_FLAG_EXT_PLUGGED;
+			wdata->state.flags |= WIIPROTO_FLAG_MP_PLUGGED;
+			wdata->state.flags |= WIIPROTO_FLAG_EXT_ACTIVE;
+		}
+	} else if (wdata->state.exttype != WIIMOTE_EXT_NONE) {
+		wdata->state.flags |= WIIPROTO_FLAG_EXT_ACTIVE;
+	}
+
+	/* request status report for hotplug state updates */
+	wiiproto_req_status(wdata);
+
+	spin_unlock_irq(&wdata->state.lock);
+
+	hid_dbg(wdata->hdev, "detected extensions: MP: %d EXT: %d\n",
+		wdata->state.mp, wdata->state.exttype);
 }
 
 static void wiimote_init_worker(struct work_struct *work)
@@ -655,6 +1129,30 @@ static void wiimote_init_worker(struct work_struct *work)
 
 	if (wdata->state.devtype == WIIMOTE_DEV_PENDING)
 		wiimote_init_detect(wdata);
+	if (!wiimote_init_check(wdata))
+		wiimote_init_hotplug(wdata);
+}
+
+void __wiimote_schedule(struct wiimote_data *wdata)
+{
+	if (!(wdata->state.flags & WIIPROTO_FLAG_EXITING))
+		schedule_work(&wdata->init_worker);
+}
+
+static void wiimote_schedule(struct wiimote_data *wdata)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	__wiimote_schedule(wdata);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static void wiimote_init_timeout(unsigned long arg)
+{
+	struct wiimote_data *wdata = (void*)arg;
+
+	wiimote_schedule(wdata);
 }
 
 /* protocol handlers */
@@ -664,6 +1162,12 @@ static void handler_keys(struct wiimote_data *wdata, const __u8 *payload)
 	const __u8 *iter, *mods;
 	const struct wiimod_ops *ops;
 
+	ops = wiimod_ext_table[wdata->state.exttype];
+	if (ops->in_keys) {
+		ops->in_keys(wdata, payload);
+		return;
+	}
+
 	mods = wiimote_devtype_mods[wdata->state.devtype];
 	for (iter = mods; *iter != WIIMOD_NULL; ++iter) {
 		ops = wiimod_table[*iter];
@@ -679,6 +1183,12 @@ static void handler_accel(struct wiimote_data *wdata, const __u8 *payload)
 	const __u8 *iter, *mods;
 	const struct wiimod_ops *ops;
 
+	ops = wiimod_ext_table[wdata->state.exttype];
+	if (ops->in_accel) {
+		ops->in_accel(wdata, payload);
+		return;
+	}
+
 	mods = wiimote_devtype_mods[wdata->state.devtype];
 	for (iter = mods; *iter != WIIMOD_NULL; ++iter) {
 		ops = wiimod_table[*iter];
@@ -689,6 +1199,93 @@ static void handler_accel(struct wiimote_data *wdata, const __u8 *payload)
 	}
 }
 
+static bool valid_ext_handler(const struct wiimod_ops *ops, size_t len)
+{
+	if (!ops->in_ext)
+		return false;
+	if ((ops->flags & WIIMOD_FLAG_EXT8) && len < 8)
+		return false;
+	if ((ops->flags & WIIMOD_FLAG_EXT16) && len < 16)
+		return false;
+
+	return true;
+}
+
+static void handler_ext(struct wiimote_data *wdata, const __u8 *payload,
+			size_t len)
+{
+	const __u8 *iter, *mods;
+	const struct wiimod_ops *ops;
+	bool is_mp;
+
+	if (len < 6)
+		return;
+
+	/* if MP is active, track MP slot hotplugging */
+	if (wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE) {
+		/* this bit is set for invalid events (eg. during hotplug) */
+		if (payload[5] & 0x01)
+			return;
+
+		if (payload[4] & 0x01) {
+			if (!(wdata->state.flags & WIIPROTO_FLAG_MP_PLUGGED)) {
+				hid_dbg(wdata->hdev, "MP hotplug: 1\n");
+				wdata->state.flags |= WIIPROTO_FLAG_MP_PLUGGED;
+				__wiimote_schedule(wdata);
+			}
+		} else {
+			if (wdata->state.flags & WIIPROTO_FLAG_MP_PLUGGED) {
+				hid_dbg(wdata->hdev, "MP hotplug: 0\n");
+				wdata->state.flags &= ~WIIPROTO_FLAG_MP_PLUGGED;
+				wdata->state.flags &= ~WIIPROTO_FLAG_EXT_ACTIVE;
+				__wiimote_schedule(wdata);
+			}
+		}
+
+		/* detect MP data that is sent interleaved with EXT data */
+		is_mp = payload[5] & 0x02;
+	} else {
+		is_mp = false;
+	}
+
+	/* ignore EXT events if no extension is active */
+	if (!(wdata->state.flags & WIIPROTO_FLAG_EXT_ACTIVE) && !is_mp)
+		return;
+
+	/* try forwarding to extension handler, first */
+	ops = wiimod_ext_table[wdata->state.exttype];
+	if (is_mp && ops->in_mp) {
+		ops->in_mp(wdata, payload);
+		return;
+	} else if (!is_mp && valid_ext_handler(ops, len)) {
+		ops->in_ext(wdata, payload);
+		return;
+	}
+
+	/* try forwarding to MP handler */
+	ops = &wiimod_mp;
+	if (is_mp && ops->in_mp) {
+		ops->in_mp(wdata, payload);
+		return;
+	} else if (!is_mp && valid_ext_handler(ops, len)) {
+		ops->in_ext(wdata, payload);
+		return;
+	}
+
+	/* try forwarding to loaded modules */
+	mods = wiimote_devtype_mods[wdata->state.devtype];
+	for (iter = mods; *iter != WIIMOD_NULL; ++iter) {
+		ops = wiimod_table[*iter];
+		if (is_mp && ops->in_mp) {
+			ops->in_mp(wdata, payload);
+			return;
+		} else if (!is_mp && valid_ext_handler(ops, len)) {
+			ops->in_ext(wdata, payload);
+			return;
+		}
+	}
+}
+
 #define ir_to_input0(wdata, ir, packed) handler_ir((wdata), (ir), (packed), 0)
 #define ir_to_input1(wdata, ir, packed) handler_ir((wdata), (ir), (packed), 1)
 #define ir_to_input2(wdata, ir, packed) handler_ir((wdata), (ir), (packed), 2)
@@ -700,6 +1297,12 @@ static void handler_ir(struct wiimote_data *wdata, const __u8 *payload,
 	const __u8 *iter, *mods;
 	const struct wiimod_ops *ops;
 
+	ops = wiimod_ext_table[wdata->state.exttype];
+	if (ops->in_ir) {
+		ops->in_ir(wdata, payload, packed, id);
+		return;
+	}
+
 	mods = wiimote_devtype_mods[wdata->state.devtype];
 	for (iter = mods; *iter != WIIMOD_NULL; ++iter) {
 		ops = wiimod_table[*iter];
@@ -727,11 +1330,20 @@ static void handler_status(struct wiimote_data *wdata, const __u8 *payload)
 
 	/* update extension status */
 	if (payload[2] & 0x02) {
-		wdata->state.flags |= WIIPROTO_FLAG_EXT_PLUGGED;
-		wiiext_event(wdata, true);
+		if (!(wdata->state.flags & WIIPROTO_FLAG_EXT_PLUGGED)) {
+			hid_dbg(wdata->hdev, "EXT hotplug: 1\n");
+			wdata->state.flags |= WIIPROTO_FLAG_EXT_PLUGGED;
+			__wiimote_schedule(wdata);
+		}
 	} else {
-		wdata->state.flags &= ~WIIPROTO_FLAG_EXT_PLUGGED;
-		wiiext_event(wdata, false);
+		if (wdata->state.flags & WIIPROTO_FLAG_EXT_PLUGGED) {
+			hid_dbg(wdata->hdev, "EXT hotplug: 0\n");
+			wdata->state.flags &= ~WIIPROTO_FLAG_EXT_PLUGGED;
+			wdata->state.flags &= ~WIIPROTO_FLAG_MP_PLUGGED;
+			wdata->state.flags &= ~WIIPROTO_FLAG_EXT_ACTIVE;
+			wdata->state.flags &= ~WIIPROTO_FLAG_MP_ACTIVE;
+			__wiimote_schedule(wdata);
+		}
 	}
 
 	wdata->state.cmd_battery = payload[5];
@@ -791,7 +1403,7 @@ static void handler_drm_KA(struct wiimote_data *wdata, const __u8 *payload)
 static void handler_drm_KE(struct wiimote_data *wdata, const __u8 *payload)
 {
 	handler_keys(wdata, payload);
-	wiiext_handle(wdata, &payload[2]);
+	handler_ext(wdata, &payload[2], 8);
 }
 
 static void handler_drm_KAI(struct wiimote_data *wdata, const __u8 *payload)
@@ -807,7 +1419,7 @@ static void handler_drm_KAI(struct wiimote_data *wdata, const __u8 *payload)
 static void handler_drm_KEE(struct wiimote_data *wdata, const __u8 *payload)
 {
 	handler_keys(wdata, payload);
-	wiiext_handle(wdata, &payload[2]);
+	handler_ext(wdata, &payload[2], 19);
 }
 
 static void handler_drm_KIE(struct wiimote_data *wdata, const __u8 *payload)
@@ -817,14 +1429,14 @@ static void handler_drm_KIE(struct wiimote_data *wdata, const __u8 *payload)
 	ir_to_input1(wdata, &payload[4], true);
 	ir_to_input2(wdata, &payload[7], false);
 	ir_to_input3(wdata, &payload[9], true);
-	wiiext_handle(wdata, &payload[12]);
+	handler_ext(wdata, &payload[12], 9);
 }
 
 static void handler_drm_KAE(struct wiimote_data *wdata, const __u8 *payload)
 {
 	handler_keys(wdata, payload);
 	handler_accel(wdata, payload);
-	wiiext_handle(wdata, &payload[5]);
+	handler_ext(wdata, &payload[5], 16);
 }
 
 static void handler_drm_KAIE(struct wiimote_data *wdata, const __u8 *payload)
@@ -835,12 +1447,12 @@ static void handler_drm_KAIE(struct wiimote_data *wdata, const __u8 *payload)
 	ir_to_input1(wdata, &payload[7], true);
 	ir_to_input2(wdata, &payload[10], false);
 	ir_to_input3(wdata, &payload[12], true);
-	wiiext_handle(wdata, &payload[15]);
+	handler_ext(wdata, &payload[15], 6);
 }
 
 static void handler_drm_E(struct wiimote_data *wdata, const __u8 *payload)
 {
-	wiiext_handle(wdata, payload);
+	handler_ext(wdata, payload, 21);
 }
 
 static void handler_drm_SKAI1(struct wiimote_data *wdata, const __u8 *payload)
@@ -960,16 +1572,27 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev)
 	wdata->state.cmd_battery = 0xff;
 
 	INIT_WORK(&wdata->init_worker, wiimote_init_worker);
+	setup_timer(&wdata->timer, wiimote_init_timeout, (long)wdata);
 
 	return wdata;
 }
 
 static void wiimote_destroy(struct wiimote_data *wdata)
 {
+	unsigned long flags;
+
 	wiidebug_deinit(wdata);
-	wiiext_deinit(wdata);
+
+	/* prevent init_worker from being scheduled again */
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags |= WIIPROTO_FLAG_EXITING;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
 
 	cancel_work_sync(&wdata->init_worker);
+	del_timer_sync(&wdata->timer);
+
+	wiimote_mp_unload(wdata);
+	wiimote_ext_unload(wdata);
 	wiimote_modules_unload(wdata);
 	cancel_work_sync(&wdata->queue.worker);
 	hid_hw_close(wdata->hdev);
@@ -1010,10 +1633,6 @@ static int wiimote_hid_probe(struct hid_device *hdev,
 		goto err_stop;
 	}
 
-	ret = wiiext_init(wdata);
-	if (ret)
-		goto err_free;
-
 	ret = wiidebug_init(wdata);
 	if (ret)
 		goto err_free;
@@ -1021,7 +1640,7 @@ static int wiimote_hid_probe(struct hid_device *hdev,
 	hid_info(hdev, "New device registered\n");
 
 	/* schedule device detection */
-	schedule_work(&wdata->init_worker);
+	wiimote_schedule(wdata);
 
 	return 0;
 
diff --git a/drivers/hid/hid-wiimote-modules.c b/drivers/hid/hid-wiimote-modules.c
index 5cc593a9c5a6..d3eef771b00b 100644
--- a/drivers/hid/hid-wiimote-modules.c
+++ b/drivers/hid/hid-wiimote-modules.c
@@ -788,8 +788,19 @@ static const struct wiimod_ops wiimod_ir = {
 	.in_ir = wiimod_ir_in_ir,
 };
 
+/*
+ * Motion Plus
+ */
+
+const struct wiimod_ops wiimod_mp = {
+	.flags = 0,
+	.arg = 0,
+};
+
 /* module table */
 
+static const struct wiimod_ops wiimod_dummy;
+
 const struct wiimod_ops *wiimod_table[WIIMOD_NUM] = {
 	[WIIMOD_KEYS] = &wiimod_keys,
 	[WIIMOD_RUMBLE] = &wiimod_rumble,
@@ -801,3 +812,8 @@ const struct wiimod_ops *wiimod_table[WIIMOD_NUM] = {
 	[WIIMOD_ACCEL] = &wiimod_accel,
 	[WIIMOD_IR] = &wiimod_ir,
 };
+
+const struct wiimod_ops *wiimod_ext_table[WIIMOTE_EXT_NUM] = {
+	[WIIMOTE_EXT_NONE] = &wiimod_dummy,
+	[WIIMOTE_EXT_UNKNOWN] = &wiimod_dummy,
+};
diff --git a/drivers/hid/hid-wiimote.h b/drivers/hid/hid-wiimote.h
index 3a2d3a1d3d63..0afc9f9a9bd6 100644
--- a/drivers/hid/hid-wiimote.h
+++ b/drivers/hid/hid-wiimote.h
@@ -22,6 +22,7 @@
 #include <linux/mutex.h>
 #include <linux/power_supply.h>
 #include <linux/spinlock.h>
+#include <linux/timer.h>
 
 #define WIIMOTE_NAME "Nintendo Wii Remote"
 #define WIIMOTE_BUFSIZE 32
@@ -36,6 +37,12 @@
 #define WIIPROTO_FLAG_IR_EXT		0x80
 #define WIIPROTO_FLAG_IR_FULL		0xc0 /* IR_BASIC | IR_EXT */
 #define WIIPROTO_FLAG_EXT_PLUGGED	0x0100
+#define WIIPROTO_FLAG_EXT_USED		0x0200
+#define WIIPROTO_FLAG_EXT_ACTIVE	0x0400
+#define WIIPROTO_FLAG_MP_PLUGGED	0x0800
+#define WIIPROTO_FLAG_MP_USED		0x1000
+#define WIIPROTO_FLAG_MP_ACTIVE		0x2000
+#define WIIPROTO_FLAG_EXITING		0x4000
 
 #define WIIPROTO_FLAGS_LEDS (WIIPROTO_FLAG_LED1 | WIIPROTO_FLAG_LED2 | \
 					WIIPROTO_FLAG_LED3 | WIIPROTO_FLAG_LED4)
@@ -75,6 +82,14 @@ enum wiimote_exttype {
 	WIIMOTE_EXT_NUM,
 };
 
+enum wiimote_mptype {
+	WIIMOTE_MP_NONE,
+	WIIMOTE_MP_UNKNOWN,
+	WIIMOTE_MP_SINGLE,
+	WIIMOTE_MP_PASSTHROUGH_NUNCHUK,
+	WIIMOTE_MP_PASSTHROUGH_CLASSIC,
+};
+
 struct wiimote_buf {
 	__u8 data[HID_MAX_BUFFER_SIZE];
 	size_t size;
@@ -94,6 +109,8 @@ struct wiimote_state {
 	__u8 accel_split[2];
 	__u8 drm;
 	__u8 devtype;
+	__u8 exttype;
+	__u8 mp;
 
 	/* synchronous cmd requests */
 	struct mutex sync;
@@ -115,6 +132,7 @@ struct wiimote_data {
 	struct input_dev *accel;
 	struct input_dev *ir;
 	struct power_supply battery;
+	struct timer_list timer;
 	struct wiimote_ext *ext;
 	struct wiimote_debug *debug;
 
@@ -140,6 +158,8 @@ enum wiimod_module {
 };
 
 #define WIIMOD_FLAG_INPUT		0x0001
+#define WIIMOD_FLAG_EXT8		0x0002
+#define WIIMOD_FLAG_EXT16		0x0004
 
 struct wiimod_ops {
 	__u16 flags;
@@ -153,9 +173,13 @@ struct wiimod_ops {
 	void (*in_accel) (struct wiimote_data *wdata, const __u8 *accel);
 	void (*in_ir) (struct wiimote_data *wdata, const __u8 *ir, bool packed,
 		       unsigned int id);
+	void (*in_mp) (struct wiimote_data *wdata, const __u8 *mp);
+	void (*in_ext) (struct wiimote_data *wdata, const __u8 *ext);
 };
 
 extern const struct wiimod_ops *wiimod_table[WIIMOD_NUM];
+extern const struct wiimod_ops *wiimod_ext_table[WIIMOTE_EXT_NUM];
+extern const struct wiimod_ops wiimod_mp;
 
 /* wiimote requests */
 
@@ -172,23 +196,48 @@ enum wiiproto_reqs {
 	WIIPROTO_REQ_STATUS = 0x20,
 	WIIPROTO_REQ_DATA = 0x21,
 	WIIPROTO_REQ_RETURN = 0x22,
+
+	/* DRM_K: BB*2 */
 	WIIPROTO_REQ_DRM_K = 0x30,
+
+	/* DRM_KA: BB*2 AA*3 */
 	WIIPROTO_REQ_DRM_KA = 0x31,
+
+	/* DRM_KE: BB*2 EE*8 */
 	WIIPROTO_REQ_DRM_KE = 0x32,
+
+	/* DRM_KAI: BB*2 AA*3 II*12 */
 	WIIPROTO_REQ_DRM_KAI = 0x33,
+
+	/* DRM_KEE: BB*2 EE*19 */
 	WIIPROTO_REQ_DRM_KEE = 0x34,
+
+	/* DRM_KAE: BB*2 AA*3 EE*16 */
 	WIIPROTO_REQ_DRM_KAE = 0x35,
+
+	/* DRM_KIE: BB*2 II*10 EE*9 */
 	WIIPROTO_REQ_DRM_KIE = 0x36,
+
+	/* DRM_KAIE: BB*2 AA*3 II*10 EE*6 */
 	WIIPROTO_REQ_DRM_KAIE = 0x37,
+
+	/* DRM_E: EE*21 */
 	WIIPROTO_REQ_DRM_E = 0x3d,
+
+	/* DRM_SKAI1: BB*2 AA*1 II*18 */
 	WIIPROTO_REQ_DRM_SKAI1 = 0x3e,
+
+	/* DRM_SKAI2: BB*2 AA*1 II*18 */
 	WIIPROTO_REQ_DRM_SKAI2 = 0x3f,
+
 	WIIPROTO_REQ_MAX
 };
 
 #define dev_to_wii(pdev) hid_get_drvdata(container_of(pdev, struct hid_device, \
 									dev))
 
+void __wiimote_schedule(struct wiimote_data *wdata);
+
 extern void wiiproto_req_drm(struct wiimote_data *wdata, __u8 drm);
 extern void wiiproto_req_rumble(struct wiimote_data *wdata, __u8 rumble);
 extern void wiiproto_req_leds(struct wiimote_data *wdata, int leds);