All of lore.kernel.org
 help / color / mirror / Atom feed
From: Grant Likely <grant.likely@secretlab.ca>
To: Fabien Marteau <fabien.marteau@armadeus.com>,
	Peter Korsgaard <jacmet@sunsite.dk>,
	Dmitry Torokhov <dmitry.torokhov@gmail.com>,
	linux-kernel@vger.kernel.org, linux-input@vger.kernel.org
Subject: [PATCH] input: Add Nintendo extension controller driver
Date: Fri, 27 May 2011 02:14:59 -0600	[thread overview]
Message-ID: <20110527081251.3992.27574.stgit@ponder> (raw)

This driver adds support for Nintendo Wiimote extension controllers
directly attached to an i2c bus, such as when using an adaptor like
the Wiichuck.

v3: - Fixed and tested classic controller connection
    - Fixed comment block
v2: - Fixed DMA buffers on stack problem
    - added classic controller support

Signed-off-by: Grant Likely <grant.likely@secretlab.ca>
---
I've actually got my hands on a classic controller now, so this driver
has been tested with both nunchuck and classic controller.  I leave adding
support for the motionplus to someone who actually has the hardware.  :)

This driver should be ready for merging now.

g.

 drivers/input/joystick/Kconfig    |   13 +
 drivers/input/joystick/Makefile   |    1 
 drivers/input/joystick/wiichuck.c |  435 +++++++++++++++++++++++++++++++++++++
 3 files changed, 449 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..ab26c34b
--- /dev/null
+++ b/drivers/input/joystick/wiichuck.c
@@ -0,0 +1,435 @@
+/*
+ * 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
+ * to 100ms, and the state machine enters into a loop reading the data
+ * capture reports out of the controller.  If at any time the device
+ * is disconnected, then it goes back to DISCONNECTED state and bumps
+ * the polling frequency back 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;
+
+	/* transfer buffer, aligned to cache line so it can be used with DMA */
+	uint8_t buf[6] ____cacheline_aligned;
+};
+
+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");
+				break;
+
+			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);


             reply	other threads:[~2011-05-27  8:15 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-05-27  8:14 Grant Likely [this message]
2011-08-01 16:04 ` [PATCH] input: Add Nintendo extension controller driver Grant Likely
2011-08-01 16:04   ` Grant Likely
2011-08-01 16:38   ` Dmitry Torokhov
2011-08-01 16:44     ` Grant Likely
  -- strict thread matches above, loose matches on Subject: below --
2011-05-16 21:46 Grant Likely
2011-05-16 21:51 ` Ben Dooks
2011-05-16 22:43   ` Dmitry Torokhov
2011-05-16 23:04     ` Grant Likely
2011-05-16 23:04       ` Grant Likely
2011-05-16 21:56 ` Grant Likely
2011-05-16 21:56   ` Grant Likely
2011-05-23 20:50 ` Peter Korsgaard
2011-05-27  8:01   ` Grant Likely

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20110527081251.3992.27574.stgit@ponder \
    --to=grant.likely@secretlab.ca \
    --cc=dmitry.torokhov@gmail.com \
    --cc=fabien.marteau@armadeus.com \
    --cc=jacmet@sunsite.dk \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.