mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
345 lines
7.8 KiB
345 lines
7.8 KiB
// 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 <[email protected]>"); |
|
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 (IS_ERR(priv->reg_base)) { |
|
err = PTR_ERR(priv->reg_base); |
|
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);
|
|
|