aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/hid/hid-wiimote-ext.c
blob: 38ae87772e96d18144a5b2e256192fd67b4bde55 (plain) (tree)




















                                                                             

                                   



                           
                        

                      
                          





                                                                  
                                                                       

  


                     














                         





                                          














                                              

  
                            


                                                





                                                                             



                                                          
                                                        




                                                               



                           


                                          
















                                                                           



                                             
                    
                                     


                                
                                                        




















                                                                             

                                                            

         



















                                                                          


                                        




                                                                            


















                                                                                   



                                                          
                                                        


                                                               



                                                                                






                                           




                                                            
                                    


















                                                                                



                                               





















                                                                               

                                                                         
























































                                                                               



                                                                          











































































                                                                                



                                                                         

















































































































                                                                               

 

                                                                               

                          





































                                                                            








                                                                   

                                                                   



                             







                                                        













                                                                   

                                                           


         
























                                                                             




                                                                      









                                                          















































                                                         




                                                   
                   







                                                















                                                      






                                                              





                                               

                                                                     





                                                                   






                                                                  





















                                                         







                                                                           





                                                   

                                                                         
                             
 




                                                          
 




                                               

                   






















                                                                            
                                                                   

                                               
 


                                       
/*
 * HID driver for Nintendo Wiimote extension devices
 * Copyright (c) 2011 David Herrmann
 */

/*
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 */

#include <linux/atomic.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include "hid-wiimote.h"

struct wiimote_ext {
	struct wiimote_data *wdata;
	struct work_struct worker;
	struct input_dev *input;
	struct input_dev *mp_input;

	atomic_t opened;
	atomic_t mp_opened;
	bool plugged;
	bool mp_plugged;
	bool motionp;
	__u8 ext_type;
	__u16 calib[4][3];
};

enum wiiext_type {
	WIIEXT_NONE,		/* placeholder */
	WIIEXT_CLASSIC,		/* Nintendo classic controller */
	WIIEXT_NUNCHUCK,	/* Nintendo nunchuck controller */
	WIIEXT_BALANCE_BOARD,	/* Nintendo balance board controller */
};

enum wiiext_keys {
	WIIEXT_KEY_C,
	WIIEXT_KEY_Z,
	WIIEXT_KEY_A,
	WIIEXT_KEY_B,
	WIIEXT_KEY_X,
	WIIEXT_KEY_Y,
	WIIEXT_KEY_ZL,
	WIIEXT_KEY_ZR,
	WIIEXT_KEY_PLUS,
	WIIEXT_KEY_MINUS,
	WIIEXT_KEY_HOME,
	WIIEXT_KEY_LEFT,
	WIIEXT_KEY_RIGHT,
	WIIEXT_KEY_UP,
	WIIEXT_KEY_DOWN,
	WIIEXT_KEY_LT,
	WIIEXT_KEY_RT,
	WIIEXT_KEY_COUNT
};

static __u16 wiiext_keymap[] = {
	BTN_C,		/* WIIEXT_KEY_C */
	BTN_Z,		/* WIIEXT_KEY_Z */
	BTN_A,		/* WIIEXT_KEY_A */
	BTN_B,		/* WIIEXT_KEY_B */
	BTN_X,		/* WIIEXT_KEY_X */
	BTN_Y,		/* WIIEXT_KEY_Y */
	BTN_TL2,	/* WIIEXT_KEY_ZL */
	BTN_TR2,	/* WIIEXT_KEY_ZR */
	KEY_NEXT,	/* WIIEXT_KEY_PLUS */
	KEY_PREVIOUS,	/* WIIEXT_KEY_MINUS */
	BTN_MODE,	/* WIIEXT_KEY_HOME */
	KEY_LEFT,	/* WIIEXT_KEY_LEFT */
	KEY_RIGHT,	/* WIIEXT_KEY_RIGHT */
	KEY_UP,		/* WIIEXT_KEY_UP */
	KEY_DOWN,	/* WIIEXT_KEY_DOWN */
	BTN_TL,		/* WIIEXT_KEY_LT */
	BTN_TR,		/* WIIEXT_KEY_RT */
};

/* disable all extensions */
static void ext_disable(struct wiimote_ext *ext)
{
	unsigned long flags;
	__u8 wmem = 0x55;

	if (!wiimote_cmd_acquire(ext->wdata)) {
		wiimote_cmd_write(ext->wdata, 0xa400f0, &wmem, sizeof(wmem));
		wiimote_cmd_release(ext->wdata);
	}

	spin_lock_irqsave(&ext->wdata->state.lock, flags);
	ext->motionp = false;
	ext->ext_type = WIIEXT_NONE;
	wiiproto_req_drm(ext->wdata, WIIPROTO_REQ_NULL);
	spin_unlock_irqrestore(&ext->wdata->state.lock, flags);
}

static bool motionp_read(struct wiimote_ext *ext)
{
	__u8 rmem[2], wmem;
	ssize_t ret;
	bool avail = false;

	if (!atomic_read(&ext->mp_opened))
		return false;

	if (wiimote_cmd_acquire(ext->wdata))
		return false;

	/* initialize motion plus */
	wmem = 0x55;
	ret = wiimote_cmd_write(ext->wdata, 0xa600f0, &wmem, sizeof(wmem));
	if (ret)
		goto error;

	/* read motion plus ID */
	ret = wiimote_cmd_read(ext->wdata, 0xa600fe, rmem, 2);
	if (ret == 2 || rmem[1] == 0x5)
		avail = true;

error:
	wiimote_cmd_release(ext->wdata);
	return avail;
}

static __u8 ext_read(struct wiimote_ext *ext)
{
	ssize_t ret;
	__u8 buf[24], i, j, offs = 0;
	__u8 rmem[2], wmem;
	__u8 type = WIIEXT_NONE;

	if (!ext->plugged || !atomic_read(&ext->opened))
		return WIIEXT_NONE;

	if (wiimote_cmd_acquire(ext->wdata))
		return WIIEXT_NONE;

	/* initialize extension */
	wmem = 0x55;
	ret = wiimote_cmd_write(ext->wdata, 0xa400f0, &wmem, sizeof(wmem));
	if (!ret) {
		/* disable encryption */
		wmem = 0x0;
		wiimote_cmd_write(ext->wdata, 0xa400fb, &wmem, sizeof(wmem));
	}

	/* read extension ID */
	ret = wiimote_cmd_read(ext->wdata, 0xa400fe, rmem, 2);
	if (ret == 2) {
		if (rmem[0] == 0 && rmem[1] == 0)
			type = WIIEXT_NUNCHUCK;
		else if (rmem[0] == 0x01 && rmem[1] == 0x01)
			type = WIIEXT_CLASSIC;
		else if (rmem[0] == 0x04 && rmem[1] == 0x02)
			type = WIIEXT_BALANCE_BOARD;
	}

	/* get balance board calibration data */
	if (type == WIIEXT_BALANCE_BOARD) {
		ret = wiimote_cmd_read(ext->wdata, 0xa40024, buf, 12);
		ret += wiimote_cmd_read(ext->wdata, 0xa40024 + 12,
					buf + 12, 12);

		if (ret != 24) {
			type = WIIEXT_NONE;
		} else {
			for (i = 0; i < 3; i++) {
				for (j = 0; j < 4; j++) {
					ext->calib[j][i] = buf[offs];
					ext->calib[j][i] <<= 8;
					ext->calib[j][i] |= buf[offs + 1];
					offs += 2;
				}
			}
		}
	}

	wiimote_cmd_release(ext->wdata);

	return type;
}

static void ext_enable(struct wiimote_ext *ext, bool motionp, __u8 ext_type)
{
	unsigned long flags;
	__u8 wmem;
	int ret;

	if (motionp) {
		if (wiimote_cmd_acquire(ext->wdata))
			return;

		if (ext_type == WIIEXT_CLASSIC)
			wmem = 0x07;
		else if (ext_type == WIIEXT_NUNCHUCK)
			wmem = 0x05;
		else
			wmem = 0x04;

		ret = wiimote_cmd_write(ext->wdata, 0xa600fe, &wmem, sizeof(wmem));
		wiimote_cmd_release(ext->wdata);
		if (ret)
			return;
	}

	spin_lock_irqsave(&ext->wdata->state.lock, flags);
	ext->motionp = motionp;
	ext->ext_type = ext_type;
	wiiproto_req_drm(ext->wdata, WIIPROTO_REQ_NULL);
	spin_unlock_irqrestore(&ext->wdata->state.lock, flags);
}

static void wiiext_worker(struct work_struct *work)
{
	struct wiimote_ext *ext = container_of(work, struct wiimote_ext,
									worker);
	bool motionp;
	__u8 ext_type;

	ext_disable(ext);
	motionp = motionp_read(ext);
	ext_type = ext_read(ext);
	ext_enable(ext, motionp, ext_type);
}

/* schedule work only once, otherwise mark for reschedule */
static void wiiext_schedule(struct wiimote_ext *ext)
{
	schedule_work(&ext->worker);
}

/*
 * Reacts on extension port events
 * Whenever the driver gets an event from the wiimote that an extension has been
 * plugged or unplugged, this funtion shall be called. It checks what extensions
 * are connected and initializes and activates them.
 * This can be called in atomic context. The initialization is done in a
 * separate worker thread. The state.lock spinlock must be held by the caller.
 */
void wiiext_event(struct wiimote_data *wdata, bool plugged)
{
	if (!wdata->ext)
		return;

	if (wdata->ext->plugged == plugged)
		return;

	wdata->ext->plugged = plugged;

	if (!plugged)
		wdata->ext->mp_plugged = false;

	/*
	 * We need to call wiiext_schedule(wdata->ext) here, however, the
	 * extension initialization logic is not fully understood and so
	 * automatic initialization is not supported, yet.
	 */
}

/*
 * Returns true if the current DRM mode should contain extension data and false
 * if there is no interest in extension data.
 * All supported extensions send 6 byte extension data so any DRM that contains
 * extension bytes is fine.
 * The caller must hold the state.lock spinlock.
 */
bool wiiext_active(struct wiimote_data *wdata)
{
	if (!wdata->ext)
		return false;

	return wdata->ext->motionp || wdata->ext->ext_type;
}

static void handler_motionp(struct wiimote_ext *ext, const __u8 *payload)
{
	__s32 x, y, z;
	bool plugged;

	/*        |   8    7    6    5    4    3 |  2  |  1  |
	 *   -----+------------------------------+-----+-----+
	 *    1   |               Yaw Speed <7:0>            |
	 *    2   |              Roll Speed <7:0>            |
	 *    3   |             Pitch Speed <7:0>            |
	 *   -----+------------------------------+-----+-----+
	 *    4   |       Yaw Speed <13:8>       | Yaw |Pitch|
	 *   -----+------------------------------+-----+-----+
	 *    5   |      Roll Speed <13:8>       |Roll | Ext |
	 *   -----+------------------------------+-----+-----+
	 *    6   |     Pitch Speed <13:8>       |  1  |  0  |
	 *   -----+------------------------------+-----+-----+
	 * The single bits Yaw, Roll, Pitch in the lower right corner specify
	 * whether the wiimote is rotating fast (0) or slow (1). Speed for slow
	 * roation is 440 deg/s and for fast rotation 2000 deg/s. To get a
	 * linear scale we multiply by 2000/440 = ~4.5454 which is 18 for fast
	 * and 9 for slow.
	 * If the wiimote is not rotating the sensor reports 2^13 = 8192.
	 * Ext specifies whether an extension is connected to the motionp.
	 */

	x = payload[0];
	y = payload[1];
	z = payload[2];

	x |= (((__u16)payload[3]) << 6) & 0xff00;
	y |= (((__u16)payload[4]) << 6) & 0xff00;
	z |= (((__u16)payload[5]) << 6) & 0xff00;

	x -= 8192;
	y -= 8192;
	z -= 8192;

	if (!(payload[3] & 0x02))
		x *= 18;
	else
		x *= 9;
	if (!(payload[4] & 0x02))
		y *= 18;
	else
		y *= 9;
	if (!(payload[3] & 0x01))
		z *= 18;
	else
		z *= 9;

	input_report_abs(ext->mp_input, ABS_RX, x);
	input_report_abs(ext->mp_input, ABS_RY, y);
	input_report_abs(ext->mp_input, ABS_RZ, z);
	input_sync(ext->mp_input);

	plugged = payload[5] & 0x01;
	if (plugged != ext->mp_plugged)
		ext->mp_plugged = plugged;
}

static void handler_nunchuck(struct wiimote_ext *ext, const __u8 *payload)
{
	__s16 x, y, z, bx, by;

	/*   Byte |   8    7 |  6    5 |  4    3 |  2 |  1  |
	 *   -----+----------+---------+---------+----+-----+
	 *    1   |              Button X <7:0>             |
	 *    2   |              Button Y <7:0>             |
	 *   -----+----------+---------+---------+----+-----+
	 *    3   |               Speed X <9:2>             |
	 *    4   |               Speed Y <9:2>             |
	 *    5   |               Speed Z <9:2>             |
	 *   -----+----------+---------+---------+----+-----+
	 *    6   | Z <1:0>  | Y <1:0> | X <1:0> | BC | BZ  |
	 *   -----+----------+---------+---------+----+-----+
	 * Button X/Y is the analog stick. Speed X, Y and Z are the
	 * accelerometer data in the same format as the wiimote's accelerometer.
	 * The 6th byte contains the LSBs of the accelerometer data.
	 * BC and BZ are the C and Z buttons: 0 means pressed
	 *
	 * If reported interleaved with motionp, then the layout changes. The
	 * 5th and 6th byte changes to:
	 *   -----+-----------------------------------+-----+
	 *    5   |            Speed Z <9:3>          | EXT |
	 *   -----+--------+-----+-----+----+----+----+-----+
	 *    6   |Z <2:1> |Y <1>|X <1>| BC | BZ | 0  |  0  |
	 *   -----+--------+-----+-----+----+----+----+-----+
	 * All three accelerometer values lose their LSB. The other data is
	 * still available but slightly moved.
	 *
	 * Center data for button values is 128. Center value for accelerometer
	 * values it 512 / 0x200
	 */

	bx = payload[0];
	by = payload[1];
	bx -= 128;
	by -= 128;

	x = payload[2] << 2;
	y = payload[3] << 2;
	z = payload[4] << 2;

	if (ext->motionp) {
		x |= (payload[5] >> 3) & 0x02;
		y |= (payload[5] >> 4) & 0x02;
		z &= ~0x4;
		z |= (payload[5] >> 5) & 0x06;
	} else {
		x |= (payload[5] >> 2) & 0x03;
		y |= (payload[5] >> 4) & 0x03;
		z |= (payload[5] >> 6) & 0x03;
	}

	x -= 0x200;
	y -= 0x200;
	z -= 0x200;

	input_report_abs(ext->input, ABS_HAT0X, bx);
	input_report_abs(ext->input, ABS_HAT0Y, by);

	input_report_abs(ext->input, ABS_RX, x);
	input_report_abs(ext->input, ABS_RY, y);
	input_report_abs(ext->input, ABS_RZ, z);

	if (ext->motionp) {
		input_report_key(ext->input,
			wiiext_keymap[WIIEXT_KEY_Z], !!(payload[5] & 0x04));
		input_report_key(ext->input,
			wiiext_keymap[WIIEXT_KEY_C], !!(payload[5] & 0x08));
	} else {
		input_report_key(ext->input,
			wiiext_keymap[WIIEXT_KEY_Z], !!(payload[5] & 0x01));
		input_report_key(ext->input,
			wiiext_keymap[WIIEXT_KEY_C], !!(payload[5] & 0x02));
	}

	input_sync(ext->input);
}

static void handler_classic(struct wiimote_ext *ext, const __u8 *payload)
{
	__s8 rx, ry, lx, ly, lt, rt;

	/*   Byte |  8  |  7  |  6  |  5  |  4  |  3  |  2  |  1  |
	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
	 *    1   | RX <5:4>  |              LX <5:0>             |
	 *    2   | RX <3:2>  |              LY <5:0>             |
	 *   -----+-----+-----+-----+-----------------------------+
	 *    3   |RX<1>| LT <5:4>  |         RY <5:1>            |
	 *   -----+-----+-----------+-----------------------------+
	 *    4   |     LT <3:1>    |         RT <5:1>            |
	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
	 *    5   | BDR | BDD | BLT | B-  | BH  | B+  | BRT |  1  |
	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
	 *    6   | BZL | BB  | BY  | BA  | BX  | BZR | BDL | BDU |
	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
	 * All buttons are 0 if pressed
	 * RX and RY are right analog stick
	 * LX and LY are left analog stick
	 * LT is left trigger, RT is right trigger
	 * BLT is 0 if left trigger is fully pressed
	 * BRT is 0 if right trigger is fully pressed
	 * BDR, BDD, BDL, BDU form the D-Pad with right, down, left, up buttons
	 * BZL is left Z button and BZR is right Z button
	 * B-, BH, B+ are +, HOME and - buttons
	 * BB, BY, BA, BX are A, B, X, Y buttons
	 * LSB of RX, RY, LT, and RT are not transmitted and always 0.
	 *
	 * With motionp enabled it changes slightly to this:
	 *   Byte |  8  |  7  |  6  |  5  |  4  |  3  |  2  |  1  |
	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
	 *    1   | RX <4:3>  |          LX <5:1>           | BDU |
	 *    2   | RX <2:1>  |          LY <5:1>           | BDL |
	 *   -----+-----+-----+-----+-----------------------+-----+
	 *    3   |RX<0>| LT <4:3>  |         RY <4:0>            |
	 *   -----+-----+-----------+-----------------------------+
	 *    4   |     LT <2:0>    |         RT <4:0>            |
	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
	 *    5   | BDR | BDD | BLT | B-  | BH  | B+  | BRT | EXT |
	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
	 *    6   | BZL | BB  | BY  | BA  | BX  | BZR |  0  |  0  |
	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
	 * Only the LSBs of LX and LY are lost. BDU and BDL are moved, the rest
	 * is the same as before.
	 */

	if (ext->motionp) {
		lx = payload[0] & 0x3e;
		ly = payload[0] & 0x3e;
	} else {
		lx = payload[0] & 0x3f;
		ly = payload[0] & 0x3f;
	}

	rx = (payload[0] >> 3) & 0x14;
	rx |= (payload[1] >> 5) & 0x06;
	rx |= (payload[2] >> 7) & 0x01;
	ry = payload[2] & 0x1f;

	rt = payload[3] & 0x1f;
	lt = (payload[2] >> 2) & 0x18;
	lt |= (payload[3] >> 5) & 0x07;

	rx <<= 1;
	ry <<= 1;
	rt <<= 1;
	lt <<= 1;

	input_report_abs(ext->input, ABS_HAT1X, lx - 0x20);
	input_report_abs(ext->input, ABS_HAT1Y, ly - 0x20);
	input_report_abs(ext->input, ABS_HAT2X, rx - 0x20);
	input_report_abs(ext->input, ABS_HAT2Y, ry - 0x20);
	input_report_abs(ext->input, ABS_HAT3X, rt - 0x20);
	input_report_abs(ext->input, ABS_HAT3Y, lt - 0x20);

	input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_RIGHT],
							!!(payload[4] & 0x80));
	input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_DOWN],
							!!(payload[4] & 0x40));
	input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_LT],
							!!(payload[4] & 0x20));
	input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_MINUS],
							!!(payload[4] & 0x10));
	input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_HOME],
							!!(payload[4] & 0x08));
	input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_PLUS],
							!!(payload[4] & 0x04));
	input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_RT],
							!!(payload[4] & 0x02));
	input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_ZL],
							!!(payload[5] & 0x80));
	input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_B],
							!!(payload[5] & 0x40));
	input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_Y],
							!!(payload[5] & 0x20));
	input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_A],
							!!(payload[5] & 0x10));
	input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_X],
							!!(payload[5] & 0x08));
	input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_ZR],
							!!(payload[5] & 0x04));

	if (ext->motionp) {
		input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_UP],
							!!(payload[0] & 0x01));
		input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_LEFT],
							!!(payload[1] & 0x01));
	} else {
		input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_UP],
							!!(payload[5] & 0x01));
		input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_LEFT],
							!!(payload[5] & 0x02));
	}

	input_sync(ext->input);
}

static void handler_balance_board(struct wiimote_ext *ext, const __u8 *payload)
{
	__s32 val[4], tmp;
	unsigned int i;

	/*   Byte |  8  7  6  5  4  3  2  1  |
	 *   -----+--------------------------+
	 *    1   |    Top Right <15:8>      |
	 *    2   |    Top Right  <7:0>      |
	 *   -----+--------------------------+
	 *    3   | Bottom Right <15:8>      |
	 *    4   | Bottom Right  <7:0>      |
	 *   -----+--------------------------+
	 *    5   |     Top Left <15:8>      |
	 *    6   |     Top Left  <7:0>      |
	 *   -----+--------------------------+
	 *    7   |  Bottom Left <15:8>      |
	 *    8   |  Bottom Left  <7:0>      |
	 *   -----+--------------------------+
	 *
	 * These values represent the weight-measurements of the Wii-balance
	 * board with 16bit precision.
	 *
	 * The balance-board is never reported interleaved with motionp.
	 */

	val[0] = payload[0];
	val[0] <<= 8;
	val[0] |= payload[1];

	val[1] = payload[2];
	val[1] <<= 8;
	val[1] |= payload[3];

	val[2] = payload[4];
	val[2] <<= 8;
	val[2] |= payload[5];

	val[3] = payload[6];
	val[3] <<= 8;
	val[3] |= payload[7];

	/* apply calibration data */
	for (i = 0; i < 4; i++) {
		if (val[i] < ext->calib[i][1]) {
			tmp = val[i] - ext->calib[i][0];
			tmp *= 1700;
			tmp /= ext->calib[i][1] - ext->calib[i][0];
		} else {
			tmp = val[i] - ext->calib[i][1];
			tmp *= 1700;
			tmp /= ext->calib[i][2] - ext->calib[i][1];
			tmp += 1700;
		}
		val[i] = tmp;
	}

	input_report_abs(ext->input, ABS_HAT0X, val[0]);
	input_report_abs(ext->input, ABS_HAT0Y, val[1]);
	input_report_abs(ext->input, ABS_HAT1X, val[2]);
	input_report_abs(ext->input, ABS_HAT1Y, val[3]);

	input_sync(ext->input);
}

/* call this with state.lock spinlock held */
void wiiext_handle(struct wiimote_data *wdata, const __u8 *payload)
{
	struct wiimote_ext *ext = wdata->ext;

	if (!ext)
		return;

	if (ext->motionp && (payload[5] & 0x02)) {
		handler_motionp(ext, payload);
	} else if (ext->ext_type == WIIEXT_NUNCHUCK) {
		handler_nunchuck(ext, payload);
	} else if (ext->ext_type == WIIEXT_CLASSIC) {
		handler_classic(ext, payload);
	} else if (ext->ext_type == WIIEXT_BALANCE_BOARD) {
		handler_balance_board(ext, payload);
	}
}

static ssize_t wiiext_show(struct device *dev, struct device_attribute *attr,
								char *buf)
{
	struct wiimote_data *wdata = dev_to_wii(dev);
	__u8 type = WIIEXT_NONE;
	bool motionp = false;
	unsigned long flags;

	spin_lock_irqsave(&wdata->state.lock, flags);
	if (wdata->ext) {
		motionp = wdata->ext->motionp;
		type = wdata->ext->ext_type;
	}
	spin_unlock_irqrestore(&wdata->state.lock, flags);

	if (type == WIIEXT_NUNCHUCK) {
		if (motionp)
			return sprintf(buf, "motionp+nunchuck\n");
		else
			return sprintf(buf, "nunchuck\n");
	} else if (type == WIIEXT_CLASSIC) {
		if (motionp)
			return sprintf(buf, "motionp+classic\n");
		else
			return sprintf(buf, "classic\n");
	} else if (type == WIIEXT_BALANCE_BOARD) {
		if (motionp)
			return sprintf(buf, "motionp+balanceboard\n");
		else
			return sprintf(buf, "balanceboard\n");
	} else {
		if (motionp)
			return sprintf(buf, "motionp\n");
		else
			return sprintf(buf, "none\n");
	}
}

static DEVICE_ATTR(extension, S_IRUGO, wiiext_show, NULL);

static int wiiext_input_open(struct input_dev *dev)
{
	struct wiimote_ext *ext = input_get_drvdata(dev);
	int ret;

	ret = hid_hw_open(ext->wdata->hdev);
	if (ret)
		return ret;

	atomic_inc(&ext->opened);
	wiiext_schedule(ext);

	return 0;
}

static void wiiext_input_close(struct input_dev *dev)
{
	struct wiimote_ext *ext = input_get_drvdata(dev);

	atomic_dec(&ext->opened);
	wiiext_schedule(ext);
	hid_hw_close(ext->wdata->hdev);
}

static int wiiext_mp_open(struct input_dev *dev)
{
	struct wiimote_ext *ext = input_get_drvdata(dev);
	int ret;

	ret = hid_hw_open(ext->wdata->hdev);
	if (ret)
		return ret;

	atomic_inc(&ext->mp_opened);
	wiiext_schedule(ext);

	return 0;
}

static void wiiext_mp_close(struct input_dev *dev)
{
	struct wiimote_ext *ext = input_get_drvdata(dev);

	atomic_dec(&ext->mp_opened);
	wiiext_schedule(ext);
	hid_hw_close(ext->wdata->hdev);
}

/* Initializes the extension driver of a wiimote */
int wiiext_init(struct wiimote_data *wdata)
{
	struct wiimote_ext *ext;
	unsigned long flags;
	int ret, i;

	ext = kzalloc(sizeof(*ext), GFP_KERNEL);
	if (!ext)
		return -ENOMEM;

	ext->wdata = wdata;
	INIT_WORK(&ext->worker, wiiext_worker);

	ext->input = input_allocate_device();
	if (!ext->input) {
		ret = -ENOMEM;
		goto err_input;
	}

	input_set_drvdata(ext->input, ext);
	ext->input->open = wiiext_input_open;
	ext->input->close = wiiext_input_close;
	ext->input->dev.parent = &wdata->hdev->dev;
	ext->input->id.bustype = wdata->hdev->bus;
	ext->input->id.vendor = wdata->hdev->vendor;
	ext->input->id.product = wdata->hdev->product;
	ext->input->id.version = wdata->hdev->version;
	ext->input->name = WIIMOTE_NAME " Extension";

	set_bit(EV_KEY, ext->input->evbit);
	for (i = 0; i < WIIEXT_KEY_COUNT; ++i)
		set_bit(wiiext_keymap[i], ext->input->keybit);

	set_bit(EV_ABS, ext->input->evbit);
	set_bit(ABS_HAT0X, ext->input->absbit);
	set_bit(ABS_HAT0Y, ext->input->absbit);
	set_bit(ABS_HAT1X, ext->input->absbit);
	set_bit(ABS_HAT1Y, ext->input->absbit);
	set_bit(ABS_HAT2X, ext->input->absbit);
	set_bit(ABS_HAT2Y, ext->input->absbit);
	set_bit(ABS_HAT3X, ext->input->absbit);
	set_bit(ABS_HAT3Y, ext->input->absbit);
	input_set_abs_params(ext->input, ABS_HAT0X, -120, 120, 2, 4);
	input_set_abs_params(ext->input, ABS_HAT0Y, -120, 120, 2, 4);
	input_set_abs_params(ext->input, ABS_HAT1X, -30, 30, 1, 1);
	input_set_abs_params(ext->input, ABS_HAT1Y, -30, 30, 1, 1);
	input_set_abs_params(ext->input, ABS_HAT2X, -30, 30, 1, 1);
	input_set_abs_params(ext->input, ABS_HAT2Y, -30, 30, 1, 1);
	input_set_abs_params(ext->input, ABS_HAT3X, -30, 30, 1, 1);
	input_set_abs_params(ext->input, ABS_HAT3Y, -30, 30, 1, 1);
	set_bit(ABS_RX, ext->input->absbit);
	set_bit(ABS_RY, ext->input->absbit);
	set_bit(ABS_RZ, ext->input->absbit);
	input_set_abs_params(ext->input, ABS_RX, -500, 500, 2, 4);
	input_set_abs_params(ext->input, ABS_RY, -500, 500, 2, 4);
	input_set_abs_params(ext->input, ABS_RZ, -500, 500, 2, 4);

	ret = input_register_device(ext->input);
	if (ret) {
		input_free_device(ext->input);
		goto err_input;
	}

	ext->mp_input = input_allocate_device();
	if (!ext->mp_input) {
		ret = -ENOMEM;
		goto err_mp;
	}

	input_set_drvdata(ext->mp_input, ext);
	ext->mp_input->open = wiiext_mp_open;
	ext->mp_input->close = wiiext_mp_close;
	ext->mp_input->dev.parent = &wdata->hdev->dev;
	ext->mp_input->id.bustype = wdata->hdev->bus;
	ext->mp_input->id.vendor = wdata->hdev->vendor;
	ext->mp_input->id.product = wdata->hdev->product;
	ext->mp_input->id.version = wdata->hdev->version;
	ext->mp_input->name = WIIMOTE_NAME " Motion+";

	set_bit(EV_ABS, ext->mp_input->evbit);
	set_bit(ABS_RX, ext->mp_input->absbit);
	set_bit(ABS_RY, ext->mp_input->absbit);
	set_bit(ABS_RZ, ext->mp_input->absbit);
	input_set_abs_params(ext->mp_input, ABS_RX, -160000, 160000, 4, 8);
	input_set_abs_params(ext->mp_input, ABS_RY, -160000, 160000, 4, 8);
	input_set_abs_params(ext->mp_input, ABS_RZ, -160000, 160000, 4, 8);

	ret = input_register_device(ext->mp_input);
	if (ret) {
		input_free_device(ext->mp_input);
		goto err_mp;
	}

	ret = device_create_file(&wdata->hdev->dev, &dev_attr_extension);
	if (ret)
		goto err_dev;

	spin_lock_irqsave(&wdata->state.lock, flags);
	wdata->ext = ext;
	spin_unlock_irqrestore(&wdata->state.lock, flags);

	return 0;

err_dev:
	input_unregister_device(ext->mp_input);
err_mp:
	input_unregister_device(ext->input);
err_input:
	kfree(ext);
	return ret;
}

/* Deinitializes the extension driver of a wiimote */
void wiiext_deinit(struct wiimote_data *wdata)
{
	struct wiimote_ext *ext = wdata->ext;
	unsigned long flags;

	if (!ext)
		return;

	/*
	 * We first unset wdata->ext to avoid further input from the wiimote
	 * core. The worker thread does not access this pointer so it is not
	 * affected by this.
	 * We kill the worker after this so it does not get respawned during
	 * deinitialization.
	 */

	spin_lock_irqsave(&wdata->state.lock, flags);
	wdata->ext = NULL;
	spin_unlock_irqrestore(&wdata->state.lock, flags);

	device_remove_file(&wdata->hdev->dev, &dev_attr_extension);
	input_unregister_device(ext->mp_input);
	input_unregister_device(ext->input);

	cancel_work_sync(&ext->worker);
	kfree(ext);
}