Input: Add N64 controller driver
authorLauri Kasanen <cand@gmx.com>
Mon, 25 Jan 2021 04:53:53 +0000 (20:53 -0800)
committerDmitry Torokhov <dmitry.torokhov@gmail.com>
Mon, 25 Jan 2021 05:16:21 +0000 (21:16 -0800)
This adds support for the four built-in controller
ports on the Nintendo 64 console. The N64 controller
includes an analog stick, a d-pad, and several buttons.

No module support as the target has only 8mb ram.

Signed-off-by: Lauri Kasanen <cand@gmx.com>
Link: https://lore.kernel.org/r/20210115133408.0acd70163b582b77ad0a029b@gmx.com
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
drivers/input/joystick/Kconfig
drivers/input/joystick/Makefile
drivers/input/joystick/n64joy.c [new file with mode: 0644]

index b080f0cfb068f0ad3912a72048cd84836ab76182..5e38899058c1d7743d394ba3b7b5d67877eeab04 100644 (file)
@@ -382,4 +382,11 @@ 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
+         Say Y here if you want enable support for the four
+         built-in controller ports on the Nintendo 64 console.
+
 endif
index 58232b3057d308fe02269f8b3b3da70aafb08880..31d720c9e49362182732824b0d2908c681366471 100644 (file)
@@ -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 (file)
index 0000000..8bcc529
--- /dev/null
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Support for the four N64 controllers.
+ *
+ * Copyright (c) 2021 Lauri Kasanen
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#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/platform_device.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+
+MODULE_AUTHOR("Lauri Kasanen <cand@gmx.com>");
+MODULE_DESCRIPTION("Driver for N64 controllers");
+MODULE_LICENSE("GPL");
+
+#define PIF_RAM 0x1fc007c0
+
+#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  BIT(0)
+#define SI_STATUS_IO_BUSY   BIT(1)
+
+#define N64_CONTROLLER_ID 0x0500
+
+#define MAX_CONTROLLERS 4
+
+static const char *n64joy_phys[MAX_CONTROLLERS] = {
+       "n64joy/port0",
+       "n64joy/port1",
+       "n64joy/port2",
+       "n64joy/port3",
+};
+
+struct n64joy_priv {
+       u64 si_buf[8] ____cacheline_aligned;
+       struct timer_list timer;
+       struct mutex n64joy_mutex;
+       struct input_dev *n64joy_dev[MAX_CONTROLLERS];
+       u32 __iomem *reg_base;
+       u8 n64joy_opened;
+};
+
+struct joydata {
+       unsigned int: 16; /* unused */
+       unsigned int err: 2;
+       unsigned int: 14; /* unused */
+
+       union {
+               u32 data;
+
+               struct {
+                       unsigned int a: 1;
+                       unsigned int b: 1;
+                       unsigned int z: 1;
+                       unsigned int start: 1;
+                       unsigned int up: 1;
+                       unsigned int down: 1;
+                       unsigned int left: 1;
+                       unsigned int right: 1;
+                       unsigned int: 2; /* unused */
+                       unsigned int l: 1;
+                       unsigned int r: 1;
+                       unsigned int c_up: 1;
+                       unsigned int c_down: 1;
+                       unsigned int c_left: 1;
+                       unsigned int c_right: 1;
+                       signed int x: 8;
+                       signed int y: 8;
+               };
+       };
+};
+
+static void n64joy_write_reg(u32 __iomem *reg_base, const u8 reg, const u32 value)
+{
+       writel(value, reg_base + reg);
+}
+
+static u32 n64joy_read_reg(u32 __iomem *reg_base, const u8 reg)
+{
+       return readl(reg_base + reg);
+}
+
+static void n64joy_wait_si_dma(u32 __iomem *reg_base)
+{
+       while (n64joy_read_reg(reg_base, SI_STATUS_REG) &
+              (SI_STATUS_DMA_BUSY | SI_STATUS_IO_BUSY))
+               cpu_relax();
+}
+
+static void n64joy_exec_pif(struct n64joy_priv *priv, const u64 in[8])
+{
+       unsigned long flags;
+
+       dma_cache_wback_inv((unsigned long) in, 8 * 8);
+       dma_cache_inv((unsigned long) priv->si_buf, 8 * 8);
+
+       local_irq_save(flags);
+
+       n64joy_wait_si_dma(priv->reg_base);
+
+       barrier();
+       n64joy_write_reg(priv->reg_base, SI_DRAM_REG, virt_to_phys(in));
+       barrier();
+       n64joy_write_reg(priv->reg_base, SI_WRITE_REG, PIF_RAM);
+       barrier();
+
+       n64joy_wait_si_dma(priv->reg_base);
+
+       barrier();
+       n64joy_write_reg(priv->reg_base, SI_DRAM_REG, virt_to_phys(priv->si_buf));
+       barrier();
+       n64joy_write_reg(priv->reg_base, SI_READ_REG, PIF_RAM);
+       barrier();
+
+       n64joy_wait_si_dma(priv->reg_base);
+
+       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;
+       struct n64joy_priv *priv = container_of(t, struct n64joy_priv, timer);
+       struct input_dev *dev;
+       u32 i;
+
+       n64joy_exec_pif(priv, polldata);
+
+       data = (struct joydata *) priv->si_buf;
+
+       for (i = 0; i < MAX_CONTROLLERS; i++) {
+               if (!priv->n64joy_dev[i])
+                       continue;
+
+               dev = priv->n64joy_dev[i];
+
+               /* d-pad */
+               input_report_key(dev, BTN_DPAD_UP, data[i].up);
+               input_report_key(dev, BTN_DPAD_DOWN, data[i].down);
+               input_report_key(dev, BTN_DPAD_LEFT, data[i].left);
+               input_report_key(dev, BTN_DPAD_RIGHT, data[i].right);
+
+               /* c buttons */
+               input_report_key(dev, BTN_FORWARD, data[i].c_up);
+               input_report_key(dev, BTN_BACK, data[i].c_down);
+               input_report_key(dev, BTN_LEFT, data[i].c_left);
+               input_report_key(dev, BTN_RIGHT, data[i].c_right);
+
+               /* matching buttons */
+               input_report_key(dev, BTN_START, data[i].start);
+               input_report_key(dev, BTN_Z, data[i].z);
+
+               /* remaining ones: a, b, l, r */
+               input_report_key(dev, BTN_0, data[i].a);
+               input_report_key(dev, BTN_1, data[i].b);
+               input_report_key(dev, BTN_2, data[i].l);
+               input_report_key(dev, BTN_3, data[i].r);
+
+               input_report_abs(dev, ABS_X, data[i].x);
+               input_report_abs(dev, ABS_Y, data[i].y);
+
+               input_sync(dev);
+       }
+
+       mod_timer(&priv->timer, jiffies + msecs_to_jiffies(16));
+}
+
+static int n64joy_open(struct input_dev *dev)
+{
+       struct n64joy_priv *priv = input_get_drvdata(dev);
+       int err;
+
+       err = mutex_lock_interruptible(&priv->n64joy_mutex);
+       if (err)
+               return err;
+
+       if (!priv->n64joy_opened) {
+               /*
+                * We could use the vblank irq, but it's not important if
+                * the poll point slightly changes.
+                */
+               timer_setup(&priv->timer, n64joy_poll, 0);
+               mod_timer(&priv->timer, jiffies + msecs_to_jiffies(16));
+       }
+
+       priv->n64joy_opened++;
+
+       mutex_unlock(&priv->n64joy_mutex);
+       return err;
+}
+
+static void n64joy_close(struct input_dev *dev)
+{
+       struct n64joy_priv *priv = input_get_drvdata(dev);
+
+       mutex_lock(&priv->n64joy_mutex);
+       if (!--priv->n64joy_opened)
+               del_timer_sync(&priv->timer);
+       mutex_unlock(&priv->n64joy_mutex);
+}
+
+static const u64 __initconst scandata[] ____cacheline_aligned = {
+       0xff010300ffffffff,
+       0xff010300ffffffff,
+       0xff010300ffffffff,
+       0xff010300ffffffff,
+       0xfe00000000000000,
+       0,
+       0,
+       1
+};
+
+/*
+ * The target device is embedded and RAM-constrained. We save RAM
+ * by initializing in __init code that gets dropped late in boot.
+ * For the same reason there is no module or unloading support.
+ */
+static int __init n64joy_probe(struct platform_device *pdev)
+{
+       const struct joydata *data;
+       struct n64joy_priv *priv;
+       struct input_dev *dev;
+       int err = 0;
+       u32 i, j, found = 0;
+
+       priv = kzalloc(sizeof(struct n64joy_priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+       mutex_init(&priv->n64joy_mutex);
+
+       priv->reg_base = devm_platform_ioremap_resource(pdev, 0);
+       if (!priv->reg_base) {
+               err = -EINVAL;
+               goto fail;
+       }
+
+       /* The controllers are not hotpluggable, so we can scan in init */
+       n64joy_exec_pif(priv, scandata);
+
+       data = (struct joydata *) priv->si_buf;
+
+       for (i = 0; i < MAX_CONTROLLERS; i++) {
+               if (!data[i].err && data[i].data >> 16 == N64_CONTROLLER_ID) {
+                       found++;
+
+                       dev = priv->n64joy_dev[i] = input_allocate_device();
+                       if (!priv->n64joy_dev[i]) {
+                               err = -ENOMEM;
+                               goto fail;
+                       }
+
+                       input_set_drvdata(dev, priv);
+
+                       dev->name = "N64 controller";
+                       dev->phys = n64joy_phys[i];
+                       dev->id.bustype = BUS_HOST;
+                       dev->id.vendor = 0;
+                       dev->id.product = data[i].data >> 16;
+                       dev->id.version = 0;
+                       dev->dev.parent = &pdev->dev;
+
+                       dev->open = n64joy_open;
+                       dev->close = n64joy_close;
+
+                       /* d-pad */
+                       input_set_capability(dev, EV_KEY, BTN_DPAD_UP);
+                       input_set_capability(dev, EV_KEY, BTN_DPAD_DOWN);
+                       input_set_capability(dev, EV_KEY, BTN_DPAD_LEFT);
+                       input_set_capability(dev, EV_KEY, BTN_DPAD_RIGHT);
+                       /* c buttons */
+                       input_set_capability(dev, EV_KEY, BTN_LEFT);
+                       input_set_capability(dev, EV_KEY, BTN_RIGHT);
+                       input_set_capability(dev, EV_KEY, BTN_FORWARD);
+                       input_set_capability(dev, EV_KEY, BTN_BACK);
+                       /* matching buttons */
+                       input_set_capability(dev, EV_KEY, BTN_START);
+                       input_set_capability(dev, EV_KEY, BTN_Z);
+                       /* remaining ones: a, b, l, r */
+                       input_set_capability(dev, EV_KEY, BTN_0);
+                       input_set_capability(dev, EV_KEY, BTN_1);
+                       input_set_capability(dev, EV_KEY, BTN_2);
+                       input_set_capability(dev, EV_KEY, BTN_3);
+
+                       for (j = 0; j < 2; j++)
+                               input_set_abs_params(dev, ABS_X + j,
+                                                    S8_MIN, S8_MAX, 0, 0);
+
+                       err = input_register_device(dev);
+                       if (err) {
+                               input_free_device(dev);
+                               goto fail;
+                       }
+               }
+       }
+
+       pr_info("%u controller(s) connected\n", found);
+
+       if (!found)
+               return -ENODEV;
+
+       return 0;
+fail:
+       for (i = 0; i < MAX_CONTROLLERS; i++) {
+               if (!priv->n64joy_dev[i])
+                       continue;
+               input_unregister_device(priv->n64joy_dev[i]);
+       }
+       return err;
+}
+
+static struct platform_driver n64joy_driver = {
+       .driver = {
+               .name = "n64joy",
+       },
+};
+
+static int __init n64joy_init(void)
+{
+       return platform_driver_probe(&n64joy_driver, n64joy_probe);
+}
+
+module_init(n64joy_init);