diff mbox series

[4/6] input: Add N64 controller driver

Message ID 20210104154811.ced3a894cddc4d309827307c@gmx.com (mailing list archive)
State New, archived
Headers show
Series None | expand

Commit Message

Lauri Kasanen Jan. 4, 2021, 1:48 p.m. UTC
Signed-off-by: Lauri Kasanen <cand@gmx.com>
---
 drivers/input/joystick/Kconfig  |   6 +
 drivers/input/joystick/Makefile |   2 +-
 drivers/input/joystick/n64joy.c | 300 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 307 insertions(+), 1 deletion(-)
 create mode 100644 drivers/input/joystick/n64joy.c

input folks: rest of the series is on linux-mips. Being mips-specific,
not sure which tree this should go to.

--
2.6.2

Comments

Dmitry Torokhov Jan. 5, 2021, 10:15 p.m. UTC | #1
Hi Lauri,

On Mon, Jan 04, 2021 at 03:48:11PM +0200, Lauri Kasanen wrote:
> Signed-off-by: Lauri Kasanen <cand@gmx.com>
> ---
>  drivers/input/joystick/Kconfig  |   6 +
>  drivers/input/joystick/Makefile |   2 +-
>  drivers/input/joystick/n64joy.c | 300 ++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 307 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/input/joystick/n64joy.c
> 
> input folks: rest of the series is on linux-mips. Being mips-specific,
> not sure which tree this should go to.
> 
> diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
> index b080f0c..e1a8128 100644
> --- a/drivers/input/joystick/Kconfig
> +++ b/drivers/input/joystick/Kconfig
> @@ -382,4 +382,10 @@ config JOYSTICK_FSIA6B
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called fsia6b.
> 
> +config JOYSTICK_N64
> +	bool "N64 controller"
> +	depends on MACH_NINTENDO64
> +	help
> +	  Support for the four N64 controllers.
> +

"To compile this driver as a module..."

>  endif
> diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
> index 58232b3..31d720c 100644
> --- a/drivers/input/joystick/Makefile
> +++ b/drivers/input/joystick/Makefile
> @@ -24,6 +24,7 @@ obj-$(CONFIG_JOYSTICK_INTERACT)		+= interact.o
>  obj-$(CONFIG_JOYSTICK_JOYDUMP)		+= joydump.o
>  obj-$(CONFIG_JOYSTICK_MAGELLAN)		+= magellan.o
>  obj-$(CONFIG_JOYSTICK_MAPLE)		+= maplecontrol.o
> +obj-$(CONFIG_JOYSTICK_N64)		+= n64joy.o
>  obj-$(CONFIG_JOYSTICK_PSXPAD_SPI)	+= psxpad-spi.o
>  obj-$(CONFIG_JOYSTICK_PXRC)		+= pxrc.o
>  obj-$(CONFIG_JOYSTICK_SIDEWINDER)	+= sidewinder.o
> @@ -37,4 +38,3 @@ obj-$(CONFIG_JOYSTICK_WARRIOR)		+= warrior.o
>  obj-$(CONFIG_JOYSTICK_WALKERA0701)	+= walkera0701.o
>  obj-$(CONFIG_JOYSTICK_XPAD)		+= xpad.o
>  obj-$(CONFIG_JOYSTICK_ZHENHUA)		+= zhenhua.o
> -
> diff --git a/drivers/input/joystick/n64joy.c b/drivers/input/joystick/n64joy.c
> new file mode 100644
> index 0000000..477a4f7
> --- /dev/null
> +++ b/drivers/input/joystick/n64joy.c
> @@ -0,0 +1,300 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Support for the four N64 controllers.
> + *
> + * Copyright (c) 2020 Lauri Kasanen
> + */
> +
> +#include <linux/errno.h>
> +#include <linux/init.h>
> +#include <linux/input.h>
> +#include <linux/limits.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/timer.h>
> +
> +#include <asm/addrspace.h>
> +#include <asm/io.h>
> +
> +MODULE_AUTHOR("Lauri Kasanen <cand@gmx.com>");
> +MODULE_DESCRIPTION("Driver for N64 controllers");
> +MODULE_LICENSE("GPL");
> +
> +#define PIF_RAM 0x1fc007c0
> +#define REG_BASE ((u32 *) TO_UNCAC(0x4800000))
> +
> +#define SI_DRAM_REG 0
> +#define SI_READ_REG 1
> +#define SI_WRITE_REG 4
> +#define SI_STATUS_REG 6
> +
> +#define SI_STATUS_DMA_BUSY  (1 << 0)

BIT(0)

> +#define SI_STATUS_IO_BUSY   (1 << 1)

BIT(1)

> +
> +#define N64_CONTROLLER_ID 0x0500
> +
> +static struct input_dev *n64joy_dev[4];

Use a #define for 4 please.

> +static const char *n64joy_phys[4] = {
> +	"n64joy/port0",
> +	"n64joy/port1",
> +	"n64joy/port2",
> +	"n64joy/port3",
> +};
> +
> +static u8 n64joy_opened;
> +static DEFINE_MUTEX(n64joy_mutex);
> +static struct timer_list timer;
> +
> +static u64 si_buf[8] ____cacheline_aligned;

Even though this is a singleton device, I would prefer if we defined a
device structure and put these data items there, instead of having
module-globals.

> +
> +struct joydata {
> +	unsigned: 16; // unused
> +	unsigned err: 2;
> +	unsigned: 14; // unused
> +
> +	union {
> +		u32 data;
> +
> +		struct {
> +			unsigned a: 1;
> +			unsigned b: 1;
> +			unsigned z: 1;
> +			unsigned start: 1;
> +			unsigned up: 1;
> +			unsigned down: 1;
> +			unsigned left: 1;
> +			unsigned right: 1;
> +			unsigned: 2; // unused
> +			unsigned l: 1;
> +			unsigned r: 1;
> +			unsigned c_up: 1;
> +			unsigned c_down: 1;
> +			unsigned c_left: 1;
> +			unsigned c_right: 1;
> +			signed x: 8;
> +			signed y: 8;
> +		};
> +	};
> +};
> +
> +static void n64joy_write_reg(const u8 reg, const u32 value)
> +{
> +	__raw_writel(value, REG_BASE + reg);
> +}
> +
> +static u32 n64joy_read_reg(const u8 reg)
> +{
> +	return __raw_readl(REG_BASE + reg);
> +}
> +
> +static void n64joy_wait_si_dma(void)
> +{
> +	while (n64joy_read_reg(SI_STATUS_REG) & (SI_STATUS_DMA_BUSY | SI_STATUS_IO_BUSY))
> +		;

This will give a warning if compiled with -Wempty-body (W=1). Can you
either add braces, or maybe we should have somethng like cpu_relax()
here?

> +}
> +
> +static void n64joy_exec_pif(const u64 in[8])
> +{
> +	unsigned long flags;
> +
> +	dma_cache_wback_inv((unsigned long) in, 8 * 8);
> +	dma_cache_inv((unsigned long) si_buf, 8 * 8);
> +
> +	local_irq_save(flags);
> +
> +	n64joy_wait_si_dma();
> +
> +	barrier();
> +	n64joy_write_reg(SI_DRAM_REG, virt_to_phys(in));
> +	barrier();
> +	n64joy_write_reg(SI_WRITE_REG, PIF_RAM);
> +	barrier();
> +
> +	n64joy_wait_si_dma();
> +
> +	barrier();
> +	n64joy_write_reg(SI_DRAM_REG, virt_to_phys(si_buf));
> +	barrier();
> +	n64joy_write_reg(SI_READ_REG, PIF_RAM);
> +	barrier();
> +
> +	n64joy_wait_si_dma();
> +
> +	local_irq_restore(flags);
> +}
> +
> +static const u64 polldata[] ____cacheline_aligned = {
> +	0xff010401ffffffff,
> +	0xff010401ffffffff,
> +	0xff010401ffffffff,
> +	0xff010401ffffffff,
> +	0xfe00000000000000,
> +	0,
> +	0,
> +	1
> +};
> +
> +static void n64joy_poll(struct timer_list *t)
> +{
> +	const struct joydata *data;
> +	u32 i;
> +
> +	n64joy_exec_pif(polldata);
> +
> +	data = (struct joydata *) si_buf;
> +
> +	for (i = 0; i < 4; i++) {
> +		if (!n64joy_dev[i])
> +			continue;
> +
> +		// d-pad
> +		input_report_key(n64joy_dev[i], BTN_DPAD_UP, data[i].up);
> +		input_report_key(n64joy_dev[i], BTN_DPAD_DOWN, data[i].down);
> +		input_report_key(n64joy_dev[i], BTN_DPAD_LEFT, data[i].left);
> +		input_report_key(n64joy_dev[i], BTN_DPAD_RIGHT, data[i].right);
> +
> +		// c buttons
> +		input_report_key(n64joy_dev[i], BTN_FORWARD, data[i].c_up);
> +		input_report_key(n64joy_dev[i], BTN_BACK, data[i].c_down);
> +		input_report_key(n64joy_dev[i], BTN_LEFT, data[i].c_left);
> +		input_report_key(n64joy_dev[i], BTN_RIGHT, data[i].c_right);
> +
> +		// matching buttons
> +		input_report_key(n64joy_dev[i], BTN_START, data[i].start);
> +		input_report_key(n64joy_dev[i], BTN_Z, data[i].z);
> +
> +		// remaining ones: a, b, l, r
> +		input_report_key(n64joy_dev[i], BTN_0, data[i].a);
> +		input_report_key(n64joy_dev[i], BTN_1, data[i].b);
> +		input_report_key(n64joy_dev[i], BTN_2, data[i].l);
> +		input_report_key(n64joy_dev[i], BTN_3, data[i].r);
> +
> +		input_report_abs(n64joy_dev[i], ABS_X, data[i].x);
> +		input_report_abs(n64joy_dev[i], ABS_Y, data[i].y);
> +
> +		input_sync(n64joy_dev[i]);
> +	}
> +
> +	mod_timer(&timer, jiffies + msecs_to_jiffies(16));
> +}
> +
> +static int n64joy_open(struct input_dev *dev)
> +{
> +	int err;
> +
> +	err = mutex_lock_interruptible(&n64joy_mutex);
> +	if (err)
> +		return err;
> +
> +	if (!n64joy_opened) {
> +		// Could use the vblank irq, but it's not important if the poll
> +		// point slightly changes.
> +		timer_setup(&timer, n64joy_poll, 0);
> +		mod_timer(&timer, jiffies + msecs_to_jiffies(16));
> +	}
> +
> +	n64joy_opened++;
> +
> +	mutex_unlock(&n64joy_mutex);
> +	return err;
> +}
> +
> +static void n64joy_close(struct input_dev *dev)
> +{
> +	mutex_lock(&n64joy_mutex);
> +	if (!--n64joy_opened)
> +		del_timer_sync(&timer);
> +	mutex_unlock(&n64joy_mutex);
> +}
> +
> +static const u64 __initconst scandata[] ____cacheline_aligned = {
> +	0xff010300ffffffff,
> +	0xff010300ffffffff,
> +	0xff010300ffffffff,
> +	0xff010300ffffffff,
> +	0xfe00000000000000,
> +	0,
> +	0,
> +	1
> +};
> +
> +static int __init n64joy_init(void)
> +{
> +	const struct joydata *data;
> +	int err = 0;
> +	u32 i, j, found = 0;
> +
> +	// The controllers are not hotpluggable, so we can scan in init
> +	n64joy_exec_pif(scandata);
> +
> +	data = (struct joydata *) si_buf;
> +
> +	memset(n64joy_dev, 0, 4 * sizeof(void *));
> +
> +	for (i = 0; i < 4; i++) {
> +		if (!data[i].err && data[i].data >> 16 == N64_CONTROLLER_ID) {
> +			found++;
> +
> +			n64joy_dev[i] = input_allocate_device();
> +			if (!n64joy_dev[i]) {
> +				err = -ENOMEM;
> +				goto fail;
> +			}
> +
> +			n64joy_dev[i]->name = "N64 controller";
> +			n64joy_dev[i]->phys = n64joy_phys[i];
> +			n64joy_dev[i]->id.bustype = BUS_HOST;
> +			n64joy_dev[i]->id.vendor = 0;
> +			n64joy_dev[i]->id.product = data[i].data >> 16;
> +			n64joy_dev[i]->id.version = 0;
> +
> +			n64joy_dev[i]->open = n64joy_open;
> +			n64joy_dev[i]->close = n64joy_close;
> +
> +			n64joy_dev[i]->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);

If you switch keys set up to input_set_capability() you would not need
this.

> +			n64joy_dev[i]->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y);

This is not needed as you are using input_set_abs_params().

> +
> +			// d-pad
> +			n64joy_dev[i]->keybit[BIT_WORD(BTN_DPAD_UP)] = BIT_MASK(BTN_DPAD_UP) |
> +				BIT_MASK(BTN_DPAD_DOWN) | BIT_MASK(BTN_DPAD_LEFT) |
> +				BIT_MASK(BTN_DPAD_RIGHT);
> +			// c buttons
> +			n64joy_dev[i]->keybit[BIT_WORD(BTN_LEFT)] |= BIT_MASK(BTN_LEFT) |
> +				BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_FORWARD) | BIT_MASK(BTN_BACK);
> +			// matching buttons
> +			n64joy_dev[i]->keybit[BIT_WORD(BTN_GAMEPAD)] |= BIT_MASK(BTN_START) |
> +				BIT_MASK(BTN_Z);
> +			// remaining ones: a, b, l, r
> +			n64joy_dev[i]->keybit[BIT_WORD(BTN_0)] |= BIT_MASK(BTN_0) |
> +				BIT_MASK(BTN_1) | BIT_MASK(BTN_2) | BIT_MASK(BTN_3);
> +
> +			for (j = 0; j < 2; j++) {
> +				input_set_abs_params(n64joy_dev[i], ABS_X + j,
> +						     S8_MIN, S8_MAX, 0, 0);
> +			}
> +
> +			err = input_register_device(n64joy_dev[i]);
> +			if (err) {
> +				input_free_device(n64joy_dev[i]);
> +				goto fail;
> +			}
> +		}
> +	}
> +
> +	pr_info("n64joy: %u controller(s) connected\n", found);

Please define

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

and you will not need to specify prefixes on individual messages.

> +
> +	if (!found)
> +		return -ENODEV;
> +
> +	return 0;
> +fail:
> +	for (i = 0; i < 4; i++) {
> +		if (!n64joy_dev[i])
> +			continue;
> +		input_unregister_device(n64joy_dev[i]);
> +	}
> +	return err;
> +}
> +
> +module_init(n64joy_init);

No option to unload the driver?

Thanks.
Lauri Kasanen Jan. 6, 2021, 7:26 a.m. UTC | #2
On Tue, 5 Jan 2021 14:15:25 -0800
Dmitry Torokhov <dmitry.torokhov@gmail.com> wrote:

> > +config JOYSTICK_N64
> > +	bool "N64 controller"
> > +	depends on MACH_NINTENDO64
> > +	help
> > +	  Support for the four N64 controllers.
> > +
>
> "To compile this driver as a module..."
...
> No option to unload the driver?

Yes, no option to build as a module or to unload, on purpose. The target
system has 8mb RAM, and this is the only input system. It saves RAM,
and makes no sense to unload your only input possibility.

The driver is specific to the N64. It can't be used on other systems,
mips or otherwise.

- Lauri
diff mbox series

Patch

diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
index b080f0c..e1a8128 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -382,4 +382,10 @@  config JOYSTICK_FSIA6B
 	  To compile this driver as a module, choose M here: the
 	  module will be called fsia6b.

+config JOYSTICK_N64
+	bool "N64 controller"
+	depends on MACH_NINTENDO64
+	help
+	  Support for the four N64 controllers.
+
 endif
diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
index 58232b3..31d720c 100644
--- a/drivers/input/joystick/Makefile
+++ b/drivers/input/joystick/Makefile
@@ -24,6 +24,7 @@  obj-$(CONFIG_JOYSTICK_INTERACT)		+= interact.o
 obj-$(CONFIG_JOYSTICK_JOYDUMP)		+= joydump.o
 obj-$(CONFIG_JOYSTICK_MAGELLAN)		+= magellan.o
 obj-$(CONFIG_JOYSTICK_MAPLE)		+= maplecontrol.o
+obj-$(CONFIG_JOYSTICK_N64)		+= n64joy.o
 obj-$(CONFIG_JOYSTICK_PSXPAD_SPI)	+= psxpad-spi.o
 obj-$(CONFIG_JOYSTICK_PXRC)		+= pxrc.o
 obj-$(CONFIG_JOYSTICK_SIDEWINDER)	+= sidewinder.o
@@ -37,4 +38,3 @@  obj-$(CONFIG_JOYSTICK_WARRIOR)		+= warrior.o
 obj-$(CONFIG_JOYSTICK_WALKERA0701)	+= walkera0701.o
 obj-$(CONFIG_JOYSTICK_XPAD)		+= xpad.o
 obj-$(CONFIG_JOYSTICK_ZHENHUA)		+= zhenhua.o
-
diff --git a/drivers/input/joystick/n64joy.c b/drivers/input/joystick/n64joy.c
new file mode 100644
index 0000000..477a4f7
--- /dev/null
+++ b/drivers/input/joystick/n64joy.c
@@ -0,0 +1,300 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Support for the four N64 controllers.
+ *
+ * Copyright (c) 2020 Lauri Kasanen
+ */
+
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/limits.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/timer.h>
+
+#include <asm/addrspace.h>
+#include <asm/io.h>
+
+MODULE_AUTHOR("Lauri Kasanen <cand@gmx.com>");
+MODULE_DESCRIPTION("Driver for N64 controllers");
+MODULE_LICENSE("GPL");
+
+#define PIF_RAM 0x1fc007c0
+#define REG_BASE ((u32 *) TO_UNCAC(0x4800000))
+
+#define SI_DRAM_REG 0
+#define SI_READ_REG 1
+#define SI_WRITE_REG 4
+#define SI_STATUS_REG 6
+
+#define SI_STATUS_DMA_BUSY  (1 << 0)
+#define SI_STATUS_IO_BUSY   (1 << 1)
+
+#define N64_CONTROLLER_ID 0x0500
+
+static struct input_dev *n64joy_dev[4];
+static const char *n64joy_phys[4] = {
+	"n64joy/port0",
+	"n64joy/port1",
+	"n64joy/port2",
+	"n64joy/port3",
+};
+
+static u8 n64joy_opened;
+static DEFINE_MUTEX(n64joy_mutex);
+static struct timer_list timer;
+
+static u64 si_buf[8] ____cacheline_aligned;
+
+struct joydata {
+	unsigned: 16; // unused
+	unsigned err: 2;
+	unsigned: 14; // unused
+
+	union {
+		u32 data;
+
+		struct {
+			unsigned a: 1;
+			unsigned b: 1;
+			unsigned z: 1;
+			unsigned start: 1;
+			unsigned up: 1;
+			unsigned down: 1;
+			unsigned left: 1;
+			unsigned right: 1;
+			unsigned: 2; // unused
+			unsigned l: 1;
+			unsigned r: 1;
+			unsigned c_up: 1;
+			unsigned c_down: 1;
+			unsigned c_left: 1;
+			unsigned c_right: 1;
+			signed x: 8;
+			signed y: 8;
+		};
+	};
+};
+
+static void n64joy_write_reg(const u8 reg, const u32 value)
+{
+	__raw_writel(value, REG_BASE + reg);
+}
+
+static u32 n64joy_read_reg(const u8 reg)
+{
+	return __raw_readl(REG_BASE + reg);
+}
+
+static void n64joy_wait_si_dma(void)
+{
+	while (n64joy_read_reg(SI_STATUS_REG) & (SI_STATUS_DMA_BUSY | SI_STATUS_IO_BUSY))
+		;
+}
+
+static void n64joy_exec_pif(const u64 in[8])
+{
+	unsigned long flags;
+
+	dma_cache_wback_inv((unsigned long) in, 8 * 8);
+	dma_cache_inv((unsigned long) si_buf, 8 * 8);
+
+	local_irq_save(flags);
+
+	n64joy_wait_si_dma();
+
+	barrier();
+	n64joy_write_reg(SI_DRAM_REG, virt_to_phys(in));
+	barrier();
+	n64joy_write_reg(SI_WRITE_REG, PIF_RAM);
+	barrier();
+
+	n64joy_wait_si_dma();
+
+	barrier();
+	n64joy_write_reg(SI_DRAM_REG, virt_to_phys(si_buf));
+	barrier();
+	n64joy_write_reg(SI_READ_REG, PIF_RAM);
+	barrier();
+
+	n64joy_wait_si_dma();
+
+	local_irq_restore(flags);
+}
+
+static const u64 polldata[] ____cacheline_aligned = {
+	0xff010401ffffffff,
+	0xff010401ffffffff,
+	0xff010401ffffffff,
+	0xff010401ffffffff,
+	0xfe00000000000000,
+	0,
+	0,
+	1
+};
+
+static void n64joy_poll(struct timer_list *t)
+{
+	const struct joydata *data;
+	u32 i;
+
+	n64joy_exec_pif(polldata);
+
+	data = (struct joydata *) si_buf;
+
+	for (i = 0; i < 4; i++) {
+		if (!n64joy_dev[i])
+			continue;
+
+		// d-pad
+		input_report_key(n64joy_dev[i], BTN_DPAD_UP, data[i].up);
+		input_report_key(n64joy_dev[i], BTN_DPAD_DOWN, data[i].down);
+		input_report_key(n64joy_dev[i], BTN_DPAD_LEFT, data[i].left);
+		input_report_key(n64joy_dev[i], BTN_DPAD_RIGHT, data[i].right);
+
+		// c buttons
+		input_report_key(n64joy_dev[i], BTN_FORWARD, data[i].c_up);
+		input_report_key(n64joy_dev[i], BTN_BACK, data[i].c_down);
+		input_report_key(n64joy_dev[i], BTN_LEFT, data[i].c_left);
+		input_report_key(n64joy_dev[i], BTN_RIGHT, data[i].c_right);
+
+		// matching buttons
+		input_report_key(n64joy_dev[i], BTN_START, data[i].start);
+		input_report_key(n64joy_dev[i], BTN_Z, data[i].z);
+
+		// remaining ones: a, b, l, r
+		input_report_key(n64joy_dev[i], BTN_0, data[i].a);
+		input_report_key(n64joy_dev[i], BTN_1, data[i].b);
+		input_report_key(n64joy_dev[i], BTN_2, data[i].l);
+		input_report_key(n64joy_dev[i], BTN_3, data[i].r);
+
+		input_report_abs(n64joy_dev[i], ABS_X, data[i].x);
+		input_report_abs(n64joy_dev[i], ABS_Y, data[i].y);
+
+		input_sync(n64joy_dev[i]);
+	}
+
+	mod_timer(&timer, jiffies + msecs_to_jiffies(16));
+}
+
+static int n64joy_open(struct input_dev *dev)
+{
+	int err;
+
+	err = mutex_lock_interruptible(&n64joy_mutex);
+	if (err)
+		return err;
+
+	if (!n64joy_opened) {
+		// Could use the vblank irq, but it's not important if the poll
+		// point slightly changes.
+		timer_setup(&timer, n64joy_poll, 0);
+		mod_timer(&timer, jiffies + msecs_to_jiffies(16));
+	}
+
+	n64joy_opened++;
+
+	mutex_unlock(&n64joy_mutex);
+	return err;
+}
+
+static void n64joy_close(struct input_dev *dev)
+{
+	mutex_lock(&n64joy_mutex);
+	if (!--n64joy_opened)
+		del_timer_sync(&timer);
+	mutex_unlock(&n64joy_mutex);
+}
+
+static const u64 __initconst scandata[] ____cacheline_aligned = {
+	0xff010300ffffffff,
+	0xff010300ffffffff,
+	0xff010300ffffffff,
+	0xff010300ffffffff,
+	0xfe00000000000000,
+	0,
+	0,
+	1
+};
+
+static int __init n64joy_init(void)
+{
+	const struct joydata *data;
+	int err = 0;
+	u32 i, j, found = 0;
+
+	// The controllers are not hotpluggable, so we can scan in init
+	n64joy_exec_pif(scandata);
+
+	data = (struct joydata *) si_buf;
+
+	memset(n64joy_dev, 0, 4 * sizeof(void *));
+
+	for (i = 0; i < 4; i++) {
+		if (!data[i].err && data[i].data >> 16 == N64_CONTROLLER_ID) {
+			found++;
+
+			n64joy_dev[i] = input_allocate_device();
+			if (!n64joy_dev[i]) {
+				err = -ENOMEM;
+				goto fail;
+			}
+
+			n64joy_dev[i]->name = "N64 controller";
+			n64joy_dev[i]->phys = n64joy_phys[i];
+			n64joy_dev[i]->id.bustype = BUS_HOST;
+			n64joy_dev[i]->id.vendor = 0;
+			n64joy_dev[i]->id.product = data[i].data >> 16;
+			n64joy_dev[i]->id.version = 0;
+
+			n64joy_dev[i]->open = n64joy_open;
+			n64joy_dev[i]->close = n64joy_close;
+
+			n64joy_dev[i]->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+			n64joy_dev[i]->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y);
+
+			// d-pad
+			n64joy_dev[i]->keybit[BIT_WORD(BTN_DPAD_UP)] = BIT_MASK(BTN_DPAD_UP) |
+				BIT_MASK(BTN_DPAD_DOWN) | BIT_MASK(BTN_DPAD_LEFT) |
+				BIT_MASK(BTN_DPAD_RIGHT);
+			// c buttons
+			n64joy_dev[i]->keybit[BIT_WORD(BTN_LEFT)] |= BIT_MASK(BTN_LEFT) |
+				BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_FORWARD) | BIT_MASK(BTN_BACK);
+			// matching buttons
+			n64joy_dev[i]->keybit[BIT_WORD(BTN_GAMEPAD)] |= BIT_MASK(BTN_START) |
+				BIT_MASK(BTN_Z);
+			// remaining ones: a, b, l, r
+			n64joy_dev[i]->keybit[BIT_WORD(BTN_0)] |= BIT_MASK(BTN_0) |
+				BIT_MASK(BTN_1) | BIT_MASK(BTN_2) | BIT_MASK(BTN_3);
+
+			for (j = 0; j < 2; j++) {
+				input_set_abs_params(n64joy_dev[i], ABS_X + j,
+						     S8_MIN, S8_MAX, 0, 0);
+			}
+
+			err = input_register_device(n64joy_dev[i]);
+			if (err) {
+				input_free_device(n64joy_dev[i]);
+				goto fail;
+			}
+		}
+	}
+
+	pr_info("n64joy: %u controller(s) connected\n", found);
+
+	if (!found)
+		return -ENODEV;
+
+	return 0;
+fail:
+	for (i = 0; i < 4; i++) {
+		if (!n64joy_dev[i])
+			continue;
+		input_unregister_device(n64joy_dev[i]);
+	}
+	return err;
+}
+
+module_init(n64joy_init);