mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 10:49:33 +00:00
Input: Add N64 controller driver
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>
This commit is contained in:
parent
ce996aa30e
commit
3bdffa8ffb
3 changed files with 353 additions and 1 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
345
drivers/input/joystick/n64joy.c
Normal file
345
drivers/input/joystick/n64joy.c
Normal file
|
@ -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);
|
Loading…
Reference in a new issue