lkml.org 
[lkml]   [2011]   [May]   [16]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
Subject[PATCH] input: Add Nintendo extension controller driver
From
Date
This driver adds support for Nintendo Wiimote extension controllers
directly attached to an i2c bus, such as when using an adaptor like
the Wiichuck.

Signed-off-by: Grant Likely <grant.likely@secretlab.ca>
---
drivers/input/joystick/Kconfig | 13 +
drivers/input/joystick/Makefile | 1
drivers/input/joystick/wiichuck.c | 441 +++++++++++++++++++++++++++++++++++++
3 files changed, 455 insertions(+), 0 deletions(-)
create mode 100644 drivers/input/joystick/wiichuck.c

diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
index 56eb471..5b7fba7 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -193,6 +193,19 @@ config JOYSTICK_TWIDJOY
To compile this driver as a module, choose M here: the
module will be called twidjoy.

+config JOYSTICK_WIICHUCK
+ tristate "Nintendo Wiimote Extension connector on i2c bus"
+ depends on I2C
+ select INPUT_POLLDEV
+ help
+ Say Y here if you have a Nintendo Wiimote extension connector
+ attached directly to an i2c bus, like the Sparcfun Wiichuck adapter
+ board. This driver supports both the Nunchuck and the Classic
+ Controller extensions.
+
+ To compile this driver as a module, choose M here: the
+ modules will be called wiichuck.
+
config JOYSTICK_ZHENHUA
tristate "5-byte Zhenhua RC transmitter"
select SERIO
diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
index 92dc0de..78466d6 100644
--- a/drivers/input/joystick/Makefile
+++ b/drivers/input/joystick/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_JOYSTICK_TMDC) += tmdc.o
obj-$(CONFIG_JOYSTICK_TURBOGRAFX) += turbografx.o
obj-$(CONFIG_JOYSTICK_TWIDJOY) += twidjoy.o
obj-$(CONFIG_JOYSTICK_WARRIOR) += warrior.o
+obj-$(CONFIG_JOYSTICK_WIICHUCK) += wiichuck.o
obj-$(CONFIG_JOYSTICK_XPAD) += xpad.o
obj-$(CONFIG_JOYSTICK_ZHENHUA) += zhenhua.o
obj-$(CONFIG_JOYSTICK_WALKERA0701) += walkera0701.o
diff --git a/drivers/input/joystick/wiichuck.c b/drivers/input/joystick/wiichuck.c
new file mode 100644
index 0000000..85a194f
--- /dev/null
+++ b/drivers/input/joystick/wiichuck.c
@@ -0,0 +1,441 @@
+/*
+ * i2c Wiichuck driver (Nintendo Wiimote accessory connector)
+ *
+ * This driver supports Nintendo Wiimote accessories like the Nunchuck and
+ * the Classic Controller connected to an i2c bus.
+ *
+ * Copyright (c) 2011 Secret Lab Technologies Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This driver uses the polled input device abstraction to implement an
+ * input driver for Nintendo expansion devices wired up to an i2c bus.
+ *
+ * A state machine implements the protocol handling. It starts in the
+ * DISCONNECTED state initially and polls every second waiting for a
+ * device to get attached, and reading the device id when one does.
+ * If the device is recognized, then the polling period is bumped up
+ * to 50ms, and the state machine enters into a loop alternating
+ * between writing the data address (which triggers data capture) and
+ * reading the data block out of the device. If at any time the
+ * device is disconnected, then it goes back to DISCONNECTED state and
+ * drops the polling frequency back down to 1 second.
+ *
+ * A callback is implemented for each supported devices, currently the
+ * Nunchuck and the Classic Controller. Wii Motion Plus has yet to be
+ * added.
+ */
+
+#include <linux/cache.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/input-polldev.h>
+#include <linux/mod_devicetable.h>
+#include <linux/slab.h>
+
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_DESCRIPTION("Wiichuck (i2c Nintendo Wiimote accessory) driver");
+MODULE_LICENSE("GPL");
+
+#define WIICHUCK_POLL_PERIOD (1000) /* 1 second */
+#ifdef DEBUG
+#define WIICHUCK_CAPTURE_PERIOD (500) /* 1/2 second for debug */
+#else
+#define WIICHUCK_CAPTURE_PERIOD (100) /* 100 milliseconds */
+#endif /* DEBUG */
+
+enum wiichuck_state {
+ WIICHUCK_STATE_DISCONNECTED = 0,
+ WIICHUCK_STATE_DATA,
+};
+
+struct wiichuck_device {
+ struct input_polled_dev *poll_dev;
+ struct i2c_client *i2c_client;
+ int (*process)(struct wiichuck_device *wiichuck);
+ enum wiichuck_state state;
+
+ /*
+ * DMA buffer, with padding to give it its own cache line so that
+ * the DMA streaming works on non-coherent architectures.
+ * Question: Is this the proper pattern, and is this really necessary?
+ */
+ uint8_t pad1[L1_CACHE_BYTES];
+ uint8_t buf[6];
+ uint8_t pad2[L1_CACHE_BYTES];
+};
+
+static int wiichuck_transfer(struct wiichuck_device *wiichuck,
+ int len, bool read)
+{
+ struct i2c_client *i2c = wiichuck->i2c_client;
+ struct i2c_msg msg = {
+ .addr = i2c->addr, .len = len, .buf = wiichuck->buf };
+
+ if (read)
+ msg.flags = I2C_M_RD;
+ return i2c_transfer(i2c->adapter, &msg, 1);
+}
+
+static int wiichuck_write(struct wiichuck_device *wiichuck,
+ uint8_t addr, uint8_t val)
+{
+ wiichuck->buf[0] = addr;
+ wiichuck->buf[1] = val;
+ return wiichuck_transfer(wiichuck, 2, false);
+}
+
+static inline int wiichuck_setaddr(struct wiichuck_device *wiichuck,
+ uint8_t addr)
+{
+ wiichuck->buf[0] = addr;
+ return wiichuck_transfer(wiichuck, 1, false);
+}
+
+static inline int wiichuck_read(struct wiichuck_device *wiichuck, int len)
+{
+ return wiichuck_transfer(wiichuck, len, true);
+}
+
+static inline bool WIIBTN(int val, int bit)
+{
+ return (val & BIT(bit)) == 0;
+}
+
+static int wiichuck_process_unknown(struct wiichuck_device *wiichuck)
+{
+ return 0;
+}
+
+static int wiichuck_process_nunchuck(struct wiichuck_device *wiichuck)
+{
+ struct input_dev *input_dev = wiichuck->poll_dev->input;
+ uint8_t *b = wiichuck->buf;
+ int ax, ay, az, rc;
+
+ rc = wiichuck_read(wiichuck, 6);
+ if (rc < 0)
+ return rc;
+
+ ax = (b[2] << 2) | ((b[5] >> 2) & 0x3);
+ ay = (b[3] << 2) | ((b[5] >> 4) & 0x3);
+ az = (b[4] << 2) | ((b[5] >> 6) & 0x3);
+
+ input_report_abs(input_dev, ABS_X, b[0]);
+ input_report_abs(input_dev, ABS_Y, b[1]);
+ input_report_abs(input_dev, ABS_RX, ax);
+ input_report_abs(input_dev, ABS_RY, ax);
+ input_report_abs(input_dev, ABS_RZ, ay);
+ input_report_key(input_dev, BTN_C, WIIBTN(b[5], 1));
+ input_report_key(input_dev, BTN_Z, WIIBTN(b[5], 0));
+ input_sync(input_dev);
+
+ dev_dbg(&wiichuck->i2c_client->dev,
+ "nunchuck: j=%.3i,%.3i a=%.3x,%.3x,%.3x c,z=%i,%i\n",
+ b[0],b[1], ax,ay,az, WIIBTN(b[5], 1), WIIBTN(b[5], 0));
+ return 0;
+};
+
+static int wiichuck_process_classic(struct wiichuck_device *wiichuck)
+{
+ struct input_dev *input_dev = wiichuck->poll_dev->input;
+ uint8_t *b = wiichuck->buf;
+ int lx, ly, lt, rx, ry, rt, rc;
+
+ rc = wiichuck_read(wiichuck, 6);
+ if (rc < 0)
+ return rc;
+
+ /* Analog measurements; some values are split between registers */
+ rx = ((b[0] >> 3) & 0x18) | ((b[1] >> 5) & 6) | ((b[2] >> 7) & 1);
+ ry = b[2] & 0x1f;
+ rt = b[3] & 0x1f;
+ lx = b[0] & 0x3f;
+ ly = b[1] & 0x3f;
+ lt = ((b[2] >> 2) & 0x18) | ((b[3] >> 5) & 7);
+
+ input_report_abs(input_dev, ABS_HAT0X, lx); /* left joystick */
+ input_report_abs(input_dev, ABS_HAT0Y, ly);
+ input_report_abs(input_dev, ABS_BRAKE, lt); /* left trigger */
+ input_report_abs(input_dev, ABS_HAT1X, rx); /* right joystick */
+ input_report_abs(input_dev, ABS_HAT1Y, ry);
+ input_report_abs(input_dev, ABS_GAS, rt); /* right trigger */
+
+ /* D-pad */
+ input_report_abs(input_dev, ABS_HAT2X, WIIBTN(b[4],7) - WIIBTN(b[5],1));
+ input_report_abs(input_dev, ABS_HAT2Y, WIIBTN(b[4],6) - WIIBTN(b[5],0));
+
+ /* Buttons */
+ input_report_key(input_dev, BTN_TL, WIIBTN(b[4], 5)); /* left trigger */
+ input_report_key(input_dev, BTN_SELECT, WIIBTN(b[4], 4)); /* minus */
+ input_report_key(input_dev, BTN_MODE, WIIBTN(b[4], 3)); /* home */
+ input_report_key(input_dev, BTN_START, WIIBTN(b[4], 2)); /* plus */
+ input_report_key(input_dev, BTN_TR, WIIBTN(b[4], 1)); /* right trigger */
+
+ input_report_key(input_dev, BTN_TL2, WIIBTN(b[5], 7)); /* left z */
+ input_report_key(input_dev, BTN_B, WIIBTN(b[5], 6));
+ input_report_key(input_dev, BTN_Y, WIIBTN(b[5], 5));
+ input_report_key(input_dev, BTN_A, WIIBTN(b[5], 4));
+ input_report_key(input_dev, BTN_X, WIIBTN(b[5], 3));
+ input_report_key(input_dev, BTN_TR2, WIIBTN(b[5], 2)); /* right z */
+ input_sync(input_dev);
+
+ dev_dbg(&wiichuck->i2c_client->dev,
+ "classic: r=%.3i,%.3i,%.3i l=%.3x,%.3x,%.3x b=%.2x,%.2x\n",
+ lx,ly,lt, rx,ry,rt, b[4],b[5]);
+ return 0;
+};
+
+/**
+ * wiichuck_poll() - Protocol state machine implementation
+ *
+ * A state machine is used here to keep the protocol processing
+ * reentrant. Any of the i2c transactions will indeed sleep because
+ * i2c transactions are slow, but there is a mandatory delay between
+ * triggering the data capture (set address) and reading the data
+ * back. Using a state machine means the poll function can return and
+ * free up the worker thread while waiting for the data.
+ */
+static void wiichuck_poll(struct input_polled_dev *poll_dev)
+{
+ struct wiichuck_device *wiichuck = poll_dev->private;
+ int id;
+
+ switch (wiichuck->state) {
+ case WIICHUCK_STATE_DISCONNECTED:
+ /* Disable encryption */
+ if (wiichuck_write(wiichuck, 0xf0, 0x55) < 0)
+ return;
+ if (wiichuck_write(wiichuck, 0xfb, 0x00) < 0)
+ return;
+
+ /* Read device id */
+ if (wiichuck_setaddr(wiichuck, 0xfe) < 0)
+ return;
+ if (wiichuck_read(wiichuck, 2) < 0)
+ return;
+ id = (wiichuck->buf[0] << 8) | wiichuck->buf[1];
+
+ switch (id) {
+ case 0x0000: /* Nunchuck */
+ wiichuck->process = wiichuck_process_nunchuck;
+ dev_info(&wiichuck->i2c_client->dev,
+ "Connected Nunchuck\n");
+ break;
+
+ case 0x0101: /* Classic Controller */
+ wiichuck->process = wiichuck_process_classic;
+ dev_info(&wiichuck->i2c_client->dev,
+ "Connected Classic Controller\n");
+
+ default: /* No connection or unsupported device */
+ wiichuck->process = wiichuck_process_unknown;
+ dev_dbg(&wiichuck->i2c_client->dev,
+ "Connected unknown id: %x\n", id);
+ return;
+ }
+
+ /* Setup the first data transfer */
+ if (wiichuck_setaddr(wiichuck, 0) < 0)
+ return;
+
+ wiichuck->state = WIICHUCK_STATE_DATA;
+ poll_dev->poll_interval = WIICHUCK_CAPTURE_PERIOD;
+ break;
+
+ case WIICHUCK_STATE_DATA:
+ /* Read the input report */
+ if (wiichuck->process(wiichuck) < 0)
+ goto disconnect;
+
+ /* Setup the next transfer */
+ if (wiichuck_setaddr(wiichuck, 0) < 0)
+ goto disconnect;
+ break;
+
+ default:
+ goto disconnect;
+ }
+ return;
+
+ disconnect:
+ dev_info(&wiichuck->i2c_client->dev, "disconnected\n");
+ wiichuck->state = WIICHUCK_STATE_DISCONNECTED;
+ poll_dev->poll_interval = WIICHUCK_POLL_PERIOD;
+}
+
+/**
+ * wiichuck_open() - Set up initial state machine state
+ *
+ * Called exactly once the first time the device is opened by
+ * userspace. Will not be called again unless all users close it
+ * before reopening. This simply clears the state to disconnected and
+ * sets the poll rate back to the slow speed
+ */
+static void wiichuck_open(struct input_polled_dev *poll_dev)
+{
+ struct wiichuck_device *wiichuck = poll_dev->private;
+
+ wiichuck->process = wiichuck_process_unknown;
+ wiichuck->state = WIICHUCK_STATE_DISCONNECTED;
+ wiichuck->poll_dev->poll_interval = WIICHUCK_POLL_PERIOD;
+}
+
+static int __devinit wiichuck_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct wiichuck_device *wiichuck;
+ struct input_polled_dev *poll_dev;
+ struct input_dev *input_dev;
+ int rc;
+
+ wiichuck = kzalloc(sizeof(*wiichuck), GFP_KERNEL);
+ if (!wiichuck)
+ return -ENOMEM;
+
+ poll_dev = input_allocate_polled_device();
+ if (!poll_dev) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+
+ wiichuck->i2c_client = client;
+ wiichuck->poll_dev = poll_dev;
+
+ poll_dev->private = wiichuck;
+ poll_dev->poll = wiichuck_poll;
+ poll_dev->open = wiichuck_open;
+
+ input_dev = poll_dev->input;
+ input_dev->name = "Wiichuck expansion connector";
+ input_dev->id.bustype = BUS_I2C;
+ input_dev->dev.parent = &client->dev;
+
+ /* Declare the events generated by this driver */
+ __set_bit(EV_ABS, input_dev->evbit);
+ __set_bit(EV_KEY, input_dev->evbit);
+
+ /* Nunchuck ranges */
+ __set_bit(ABS_X, input_dev->absbit); /* joystick */
+ __set_bit(ABS_Y, input_dev->absbit);
+ input_set_abs_params(input_dev, ABS_X, 30, 220, 4, 8);
+ input_set_abs_params(input_dev, ABS_Y, 40, 200, 4, 8);
+
+ __set_bit(ABS_RX, input_dev->absbit); /* accelerometer */
+ __set_bit(ABS_RY, input_dev->absbit);
+ __set_bit(ABS_RZ, input_dev->absbit);
+ input_set_abs_params(input_dev, ABS_RX, 0, 0x3ff, 4, 8);
+ input_set_abs_params(input_dev, ABS_RY, 0, 0x3ff, 4, 8);
+ input_set_abs_params(input_dev, ABS_RZ, 0, 0x3ff, 4, 8);
+
+ /* Nunchuck buttons */
+ __set_bit(BTN_C, input_dev->keybit);
+ __set_bit(BTN_Z, input_dev->keybit);
+
+ /* Classic ranges */
+ __set_bit(ABS_HAT0X, input_dev->absbit); /* Left Joystick */
+ __set_bit(ABS_HAT0Y, input_dev->absbit);
+ input_set_abs_params(input_dev, ABS_HAT0X, 0, 63, 2, 4);
+ input_set_abs_params(input_dev, ABS_HAT0Y, 0, 63, 2, 4);
+
+ __set_bit(ABS_HAT1X, input_dev->absbit); /* Right Joystick */
+ __set_bit(ABS_HAT1Y, input_dev->absbit);
+ input_set_abs_params(input_dev, ABS_HAT1X, 0, 31, 1, 2);
+ input_set_abs_params(input_dev, ABS_HAT1Y, 0, 31, 1, 2);
+
+ __set_bit(ABS_HAT2X, input_dev->absbit); /* D-pad */
+ __set_bit(ABS_HAT2Y, input_dev->absbit);
+ input_set_abs_params(input_dev, ABS_HAT2X, -1, 1, 0, 0);
+ input_set_abs_params(input_dev, ABS_HAT2Y, -1, 1, 0, 0);
+
+ __set_bit(ABS_BRAKE, input_dev->absbit); /* Left trigger */
+ input_set_abs_params(input_dev, ABS_BRAKE, 0, 31, 1, 2);
+ __set_bit(ABS_GAS, input_dev->absbit);
+ input_set_abs_params(input_dev, ABS_GAS, 0, 31, 1, 2);
+
+ /* Classic buttons */
+ __set_bit(BTN_TL, input_dev->keybit);
+ __set_bit(BTN_SELECT, input_dev->keybit);
+ __set_bit(BTN_MODE, input_dev->keybit);
+ __set_bit(BTN_START, input_dev->keybit);
+ __set_bit(BTN_TR, input_dev->keybit);
+ __set_bit(BTN_TL2, input_dev->keybit);
+ __set_bit(BTN_B, input_dev->keybit);
+ __set_bit(BTN_Y, input_dev->keybit);
+ __set_bit(BTN_A, input_dev->keybit);
+ __set_bit(BTN_X, input_dev->keybit);
+ __set_bit(BTN_TR2, input_dev->keybit);
+
+ i2c_set_clientdata(client, wiichuck);
+
+ /* Register the device; it is 'live' after this point */
+ rc = input_register_polled_device(wiichuck->poll_dev);
+ if (rc) {
+ dev_err(&client->dev, "Failed to register input device\n");
+ goto err_register;
+ }
+
+ return 0;
+
+ err_register:
+ i2c_set_clientdata(client, NULL);
+ input_free_polled_device(poll_dev);
+ err_alloc:
+ kfree(wiichuck);
+
+ return rc;
+}
+
+static int __devexit wiichuck_remove(struct i2c_client *client)
+{
+ struct wiichuck_device *wiichuck = i2c_get_clientdata(client);
+
+ i2c_set_clientdata(client, NULL);
+ input_unregister_polled_device(wiichuck->poll_dev);
+ input_free_polled_device(wiichuck->poll_dev);
+ kfree(wiichuck);
+
+ return 0;
+}
+
+static const struct i2c_device_id wiichuck_id[] = {
+ { "wiichuck", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wiichuck_id);
+
+#ifdef CONFIG_OF
+static struct of_device_id wiichuck_match_table[] __devinitdata = {
+ { .compatible = "nintendo,wiimote-extension", },
+ { }
+};
+#else
+#define wiichuck_match_table NULL
+#endif
+
+static struct i2c_driver wiichuck_driver = {
+ .driver = {
+ .name = "wiichuck",
+ .owner = THIS_MODULE,
+ .of_match_table = wiichuck_match_table,
+ },
+ .probe = wiichuck_probe,
+ .remove = __devexit_p(wiichuck_remove),
+ .id_table = wiichuck_id,
+};
+
+static int __init wiichuck_init(void)
+{
+ return i2c_add_driver(&wiichuck_driver);
+}
+module_init(wiichuck_init);
+
+static void __exit wiichuck_exit(void)
+{
+ i2c_del_driver(&wiichuck_driver);
+}
+module_exit(wiichuck_exit);


\
 
 \ /
  Last update: 2011-05-16 23:49    [W:0.083 / U:1.000 seconds]
©2003-2018 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site