/*
* 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]