13bdffa8fSLauri Kasanen // SPDX-License-Identifier: GPL-2.0
23bdffa8fSLauri Kasanen /*
33bdffa8fSLauri Kasanen * Support for the four N64 controllers.
43bdffa8fSLauri Kasanen *
53bdffa8fSLauri Kasanen * Copyright (c) 2021 Lauri Kasanen
63bdffa8fSLauri Kasanen */
73bdffa8fSLauri Kasanen
83bdffa8fSLauri Kasanen #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
93bdffa8fSLauri Kasanen
103bdffa8fSLauri Kasanen #include <linux/errno.h>
113bdffa8fSLauri Kasanen #include <linux/init.h>
123bdffa8fSLauri Kasanen #include <linux/input.h>
133bdffa8fSLauri Kasanen #include <linux/limits.h>
143bdffa8fSLauri Kasanen #include <linux/kernel.h>
153bdffa8fSLauri Kasanen #include <linux/module.h>
163bdffa8fSLauri Kasanen #include <linux/mutex.h>
173bdffa8fSLauri Kasanen #include <linux/platform_device.h>
183bdffa8fSLauri Kasanen #include <linux/slab.h>
193bdffa8fSLauri Kasanen #include <linux/timer.h>
203bdffa8fSLauri Kasanen
213bdffa8fSLauri Kasanen MODULE_AUTHOR("Lauri Kasanen <cand@gmx.com>");
223bdffa8fSLauri Kasanen MODULE_DESCRIPTION("Driver for N64 controllers");
233bdffa8fSLauri Kasanen MODULE_LICENSE("GPL");
243bdffa8fSLauri Kasanen
253bdffa8fSLauri Kasanen #define PIF_RAM 0x1fc007c0
263bdffa8fSLauri Kasanen
273bdffa8fSLauri Kasanen #define SI_DRAM_REG 0
283bdffa8fSLauri Kasanen #define SI_READ_REG 1
293bdffa8fSLauri Kasanen #define SI_WRITE_REG 4
303bdffa8fSLauri Kasanen #define SI_STATUS_REG 6
313bdffa8fSLauri Kasanen
323bdffa8fSLauri Kasanen #define SI_STATUS_DMA_BUSY BIT(0)
333bdffa8fSLauri Kasanen #define SI_STATUS_IO_BUSY BIT(1)
343bdffa8fSLauri Kasanen
353bdffa8fSLauri Kasanen #define N64_CONTROLLER_ID 0x0500
363bdffa8fSLauri Kasanen
373bdffa8fSLauri Kasanen #define MAX_CONTROLLERS 4
383bdffa8fSLauri Kasanen
393bdffa8fSLauri Kasanen static const char *n64joy_phys[MAX_CONTROLLERS] = {
403bdffa8fSLauri Kasanen "n64joy/port0",
413bdffa8fSLauri Kasanen "n64joy/port1",
423bdffa8fSLauri Kasanen "n64joy/port2",
433bdffa8fSLauri Kasanen "n64joy/port3",
443bdffa8fSLauri Kasanen };
453bdffa8fSLauri Kasanen
463bdffa8fSLauri Kasanen struct n64joy_priv {
473bdffa8fSLauri Kasanen u64 si_buf[8] ____cacheline_aligned;
483bdffa8fSLauri Kasanen struct timer_list timer;
493bdffa8fSLauri Kasanen struct mutex n64joy_mutex;
503bdffa8fSLauri Kasanen struct input_dev *n64joy_dev[MAX_CONTROLLERS];
513bdffa8fSLauri Kasanen u32 __iomem *reg_base;
523bdffa8fSLauri Kasanen u8 n64joy_opened;
533bdffa8fSLauri Kasanen };
543bdffa8fSLauri Kasanen
553bdffa8fSLauri Kasanen struct joydata {
563bdffa8fSLauri Kasanen unsigned int: 16; /* unused */
573bdffa8fSLauri Kasanen unsigned int err: 2;
583bdffa8fSLauri Kasanen unsigned int: 14; /* unused */
593bdffa8fSLauri Kasanen
603bdffa8fSLauri Kasanen union {
613bdffa8fSLauri Kasanen u32 data;
623bdffa8fSLauri Kasanen
633bdffa8fSLauri Kasanen struct {
643bdffa8fSLauri Kasanen unsigned int a: 1;
653bdffa8fSLauri Kasanen unsigned int b: 1;
663bdffa8fSLauri Kasanen unsigned int z: 1;
673bdffa8fSLauri Kasanen unsigned int start: 1;
683bdffa8fSLauri Kasanen unsigned int up: 1;
693bdffa8fSLauri Kasanen unsigned int down: 1;
703bdffa8fSLauri Kasanen unsigned int left: 1;
713bdffa8fSLauri Kasanen unsigned int right: 1;
723bdffa8fSLauri Kasanen unsigned int: 2; /* unused */
733bdffa8fSLauri Kasanen unsigned int l: 1;
743bdffa8fSLauri Kasanen unsigned int r: 1;
753bdffa8fSLauri Kasanen unsigned int c_up: 1;
763bdffa8fSLauri Kasanen unsigned int c_down: 1;
773bdffa8fSLauri Kasanen unsigned int c_left: 1;
783bdffa8fSLauri Kasanen unsigned int c_right: 1;
793bdffa8fSLauri Kasanen signed int x: 8;
803bdffa8fSLauri Kasanen signed int y: 8;
813bdffa8fSLauri Kasanen };
823bdffa8fSLauri Kasanen };
833bdffa8fSLauri Kasanen };
843bdffa8fSLauri Kasanen
n64joy_write_reg(u32 __iomem * reg_base,const u8 reg,const u32 value)853bdffa8fSLauri Kasanen static void n64joy_write_reg(u32 __iomem *reg_base, const u8 reg, const u32 value)
863bdffa8fSLauri Kasanen {
873bdffa8fSLauri Kasanen writel(value, reg_base + reg);
883bdffa8fSLauri Kasanen }
893bdffa8fSLauri Kasanen
n64joy_read_reg(u32 __iomem * reg_base,const u8 reg)903bdffa8fSLauri Kasanen static u32 n64joy_read_reg(u32 __iomem *reg_base, const u8 reg)
913bdffa8fSLauri Kasanen {
923bdffa8fSLauri Kasanen return readl(reg_base + reg);
933bdffa8fSLauri Kasanen }
943bdffa8fSLauri Kasanen
n64joy_wait_si_dma(u32 __iomem * reg_base)953bdffa8fSLauri Kasanen static void n64joy_wait_si_dma(u32 __iomem *reg_base)
963bdffa8fSLauri Kasanen {
973bdffa8fSLauri Kasanen while (n64joy_read_reg(reg_base, SI_STATUS_REG) &
983bdffa8fSLauri Kasanen (SI_STATUS_DMA_BUSY | SI_STATUS_IO_BUSY))
993bdffa8fSLauri Kasanen cpu_relax();
1003bdffa8fSLauri Kasanen }
1013bdffa8fSLauri Kasanen
n64joy_exec_pif(struct n64joy_priv * priv,const u64 in[8])1023bdffa8fSLauri Kasanen static void n64joy_exec_pif(struct n64joy_priv *priv, const u64 in[8])
1033bdffa8fSLauri Kasanen {
1043bdffa8fSLauri Kasanen unsigned long flags;
1053bdffa8fSLauri Kasanen
1063bdffa8fSLauri Kasanen dma_cache_wback_inv((unsigned long) in, 8 * 8);
1073bdffa8fSLauri Kasanen dma_cache_inv((unsigned long) priv->si_buf, 8 * 8);
1083bdffa8fSLauri Kasanen
1093bdffa8fSLauri Kasanen local_irq_save(flags);
1103bdffa8fSLauri Kasanen
1113bdffa8fSLauri Kasanen n64joy_wait_si_dma(priv->reg_base);
1123bdffa8fSLauri Kasanen
1133bdffa8fSLauri Kasanen barrier();
1143bdffa8fSLauri Kasanen n64joy_write_reg(priv->reg_base, SI_DRAM_REG, virt_to_phys(in));
1153bdffa8fSLauri Kasanen barrier();
1163bdffa8fSLauri Kasanen n64joy_write_reg(priv->reg_base, SI_WRITE_REG, PIF_RAM);
1173bdffa8fSLauri Kasanen barrier();
1183bdffa8fSLauri Kasanen
1193bdffa8fSLauri Kasanen n64joy_wait_si_dma(priv->reg_base);
1203bdffa8fSLauri Kasanen
1213bdffa8fSLauri Kasanen barrier();
1223bdffa8fSLauri Kasanen n64joy_write_reg(priv->reg_base, SI_DRAM_REG, virt_to_phys(priv->si_buf));
1233bdffa8fSLauri Kasanen barrier();
1243bdffa8fSLauri Kasanen n64joy_write_reg(priv->reg_base, SI_READ_REG, PIF_RAM);
1253bdffa8fSLauri Kasanen barrier();
1263bdffa8fSLauri Kasanen
1273bdffa8fSLauri Kasanen n64joy_wait_si_dma(priv->reg_base);
1283bdffa8fSLauri Kasanen
1293bdffa8fSLauri Kasanen local_irq_restore(flags);
1303bdffa8fSLauri Kasanen }
1313bdffa8fSLauri Kasanen
1323bdffa8fSLauri Kasanen static const u64 polldata[] ____cacheline_aligned = {
1333bdffa8fSLauri Kasanen 0xff010401ffffffff,
1343bdffa8fSLauri Kasanen 0xff010401ffffffff,
1353bdffa8fSLauri Kasanen 0xff010401ffffffff,
1363bdffa8fSLauri Kasanen 0xff010401ffffffff,
1373bdffa8fSLauri Kasanen 0xfe00000000000000,
1383bdffa8fSLauri Kasanen 0,
1393bdffa8fSLauri Kasanen 0,
1403bdffa8fSLauri Kasanen 1
1413bdffa8fSLauri Kasanen };
1423bdffa8fSLauri Kasanen
n64joy_poll(struct timer_list * t)1433bdffa8fSLauri Kasanen static void n64joy_poll(struct timer_list *t)
1443bdffa8fSLauri Kasanen {
1453bdffa8fSLauri Kasanen const struct joydata *data;
1463bdffa8fSLauri Kasanen struct n64joy_priv *priv = container_of(t, struct n64joy_priv, timer);
1473bdffa8fSLauri Kasanen struct input_dev *dev;
1483bdffa8fSLauri Kasanen u32 i;
1493bdffa8fSLauri Kasanen
1503bdffa8fSLauri Kasanen n64joy_exec_pif(priv, polldata);
1513bdffa8fSLauri Kasanen
1523bdffa8fSLauri Kasanen data = (struct joydata *) priv->si_buf;
1533bdffa8fSLauri Kasanen
1543bdffa8fSLauri Kasanen for (i = 0; i < MAX_CONTROLLERS; i++) {
1553bdffa8fSLauri Kasanen if (!priv->n64joy_dev[i])
1563bdffa8fSLauri Kasanen continue;
1573bdffa8fSLauri Kasanen
1583bdffa8fSLauri Kasanen dev = priv->n64joy_dev[i];
1593bdffa8fSLauri Kasanen
1603bdffa8fSLauri Kasanen /* d-pad */
1613bdffa8fSLauri Kasanen input_report_key(dev, BTN_DPAD_UP, data[i].up);
1623bdffa8fSLauri Kasanen input_report_key(dev, BTN_DPAD_DOWN, data[i].down);
1633bdffa8fSLauri Kasanen input_report_key(dev, BTN_DPAD_LEFT, data[i].left);
1643bdffa8fSLauri Kasanen input_report_key(dev, BTN_DPAD_RIGHT, data[i].right);
1653bdffa8fSLauri Kasanen
1663bdffa8fSLauri Kasanen /* c buttons */
1673bdffa8fSLauri Kasanen input_report_key(dev, BTN_FORWARD, data[i].c_up);
1683bdffa8fSLauri Kasanen input_report_key(dev, BTN_BACK, data[i].c_down);
1693bdffa8fSLauri Kasanen input_report_key(dev, BTN_LEFT, data[i].c_left);
1703bdffa8fSLauri Kasanen input_report_key(dev, BTN_RIGHT, data[i].c_right);
1713bdffa8fSLauri Kasanen
1723bdffa8fSLauri Kasanen /* matching buttons */
1733bdffa8fSLauri Kasanen input_report_key(dev, BTN_START, data[i].start);
1743bdffa8fSLauri Kasanen input_report_key(dev, BTN_Z, data[i].z);
1753bdffa8fSLauri Kasanen
1763bdffa8fSLauri Kasanen /* remaining ones: a, b, l, r */
1773bdffa8fSLauri Kasanen input_report_key(dev, BTN_0, data[i].a);
1783bdffa8fSLauri Kasanen input_report_key(dev, BTN_1, data[i].b);
1793bdffa8fSLauri Kasanen input_report_key(dev, BTN_2, data[i].l);
1803bdffa8fSLauri Kasanen input_report_key(dev, BTN_3, data[i].r);
1813bdffa8fSLauri Kasanen
1823bdffa8fSLauri Kasanen input_report_abs(dev, ABS_X, data[i].x);
1833bdffa8fSLauri Kasanen input_report_abs(dev, ABS_Y, data[i].y);
1843bdffa8fSLauri Kasanen
1853bdffa8fSLauri Kasanen input_sync(dev);
1863bdffa8fSLauri Kasanen }
1873bdffa8fSLauri Kasanen
1883bdffa8fSLauri Kasanen mod_timer(&priv->timer, jiffies + msecs_to_jiffies(16));
1893bdffa8fSLauri Kasanen }
1903bdffa8fSLauri Kasanen
n64joy_open(struct input_dev * dev)1913bdffa8fSLauri Kasanen static int n64joy_open(struct input_dev *dev)
1923bdffa8fSLauri Kasanen {
1933bdffa8fSLauri Kasanen struct n64joy_priv *priv = input_get_drvdata(dev);
1943bdffa8fSLauri Kasanen int err;
1953bdffa8fSLauri Kasanen
1963bdffa8fSLauri Kasanen err = mutex_lock_interruptible(&priv->n64joy_mutex);
1973bdffa8fSLauri Kasanen if (err)
1983bdffa8fSLauri Kasanen return err;
1993bdffa8fSLauri Kasanen
2003bdffa8fSLauri Kasanen if (!priv->n64joy_opened) {
2013bdffa8fSLauri Kasanen /*
2023bdffa8fSLauri Kasanen * We could use the vblank irq, but it's not important if
2033bdffa8fSLauri Kasanen * the poll point slightly changes.
2043bdffa8fSLauri Kasanen */
2053bdffa8fSLauri Kasanen timer_setup(&priv->timer, n64joy_poll, 0);
2063bdffa8fSLauri Kasanen mod_timer(&priv->timer, jiffies + msecs_to_jiffies(16));
2073bdffa8fSLauri Kasanen }
2083bdffa8fSLauri Kasanen
2093bdffa8fSLauri Kasanen priv->n64joy_opened++;
2103bdffa8fSLauri Kasanen
2113bdffa8fSLauri Kasanen mutex_unlock(&priv->n64joy_mutex);
2123bdffa8fSLauri Kasanen return err;
2133bdffa8fSLauri Kasanen }
2143bdffa8fSLauri Kasanen
n64joy_close(struct input_dev * dev)2153bdffa8fSLauri Kasanen static void n64joy_close(struct input_dev *dev)
2163bdffa8fSLauri Kasanen {
2173bdffa8fSLauri Kasanen struct n64joy_priv *priv = input_get_drvdata(dev);
2183bdffa8fSLauri Kasanen
2193bdffa8fSLauri Kasanen mutex_lock(&priv->n64joy_mutex);
2203bdffa8fSLauri Kasanen if (!--priv->n64joy_opened)
2213bdffa8fSLauri Kasanen del_timer_sync(&priv->timer);
2223bdffa8fSLauri Kasanen mutex_unlock(&priv->n64joy_mutex);
2233bdffa8fSLauri Kasanen }
2243bdffa8fSLauri Kasanen
2253bdffa8fSLauri Kasanen static const u64 __initconst scandata[] ____cacheline_aligned = {
2263bdffa8fSLauri Kasanen 0xff010300ffffffff,
2273bdffa8fSLauri Kasanen 0xff010300ffffffff,
2283bdffa8fSLauri Kasanen 0xff010300ffffffff,
2293bdffa8fSLauri Kasanen 0xff010300ffffffff,
2303bdffa8fSLauri Kasanen 0xfe00000000000000,
2313bdffa8fSLauri Kasanen 0,
2323bdffa8fSLauri Kasanen 0,
2333bdffa8fSLauri Kasanen 1
2343bdffa8fSLauri Kasanen };
2353bdffa8fSLauri Kasanen
2363bdffa8fSLauri Kasanen /*
2373bdffa8fSLauri Kasanen * The target device is embedded and RAM-constrained. We save RAM
2383bdffa8fSLauri Kasanen * by initializing in __init code that gets dropped late in boot.
2393bdffa8fSLauri Kasanen * For the same reason there is no module or unloading support.
2403bdffa8fSLauri Kasanen */
n64joy_probe(struct platform_device * pdev)2413bdffa8fSLauri Kasanen static int __init n64joy_probe(struct platform_device *pdev)
2423bdffa8fSLauri Kasanen {
2433bdffa8fSLauri Kasanen const struct joydata *data;
2443bdffa8fSLauri Kasanen struct n64joy_priv *priv;
2453bdffa8fSLauri Kasanen struct input_dev *dev;
2463bdffa8fSLauri Kasanen int err = 0;
2473bdffa8fSLauri Kasanen u32 i, j, found = 0;
2483bdffa8fSLauri Kasanen
2493bdffa8fSLauri Kasanen priv = kzalloc(sizeof(struct n64joy_priv), GFP_KERNEL);
2503bdffa8fSLauri Kasanen if (!priv)
2513bdffa8fSLauri Kasanen return -ENOMEM;
2523bdffa8fSLauri Kasanen mutex_init(&priv->n64joy_mutex);
2533bdffa8fSLauri Kasanen
2543bdffa8fSLauri Kasanen priv->reg_base = devm_platform_ioremap_resource(pdev, 0);
255*2d8aaa17SWei Yongjun if (IS_ERR(priv->reg_base)) {
256*2d8aaa17SWei Yongjun err = PTR_ERR(priv->reg_base);
2573bdffa8fSLauri Kasanen goto fail;
2583bdffa8fSLauri Kasanen }
2593bdffa8fSLauri Kasanen
2603bdffa8fSLauri Kasanen /* The controllers are not hotpluggable, so we can scan in init */
2613bdffa8fSLauri Kasanen n64joy_exec_pif(priv, scandata);
2623bdffa8fSLauri Kasanen
2633bdffa8fSLauri Kasanen data = (struct joydata *) priv->si_buf;
2643bdffa8fSLauri Kasanen
2653bdffa8fSLauri Kasanen for (i = 0; i < MAX_CONTROLLERS; i++) {
2663bdffa8fSLauri Kasanen if (!data[i].err && data[i].data >> 16 == N64_CONTROLLER_ID) {
2673bdffa8fSLauri Kasanen found++;
2683bdffa8fSLauri Kasanen
2693bdffa8fSLauri Kasanen dev = priv->n64joy_dev[i] = input_allocate_device();
2703bdffa8fSLauri Kasanen if (!priv->n64joy_dev[i]) {
2713bdffa8fSLauri Kasanen err = -ENOMEM;
2723bdffa8fSLauri Kasanen goto fail;
2733bdffa8fSLauri Kasanen }
2743bdffa8fSLauri Kasanen
2753bdffa8fSLauri Kasanen input_set_drvdata(dev, priv);
2763bdffa8fSLauri Kasanen
2773bdffa8fSLauri Kasanen dev->name = "N64 controller";
2783bdffa8fSLauri Kasanen dev->phys = n64joy_phys[i];
2793bdffa8fSLauri Kasanen dev->id.bustype = BUS_HOST;
2803bdffa8fSLauri Kasanen dev->id.vendor = 0;
2813bdffa8fSLauri Kasanen dev->id.product = data[i].data >> 16;
2823bdffa8fSLauri Kasanen dev->id.version = 0;
2833bdffa8fSLauri Kasanen dev->dev.parent = &pdev->dev;
2843bdffa8fSLauri Kasanen
2853bdffa8fSLauri Kasanen dev->open = n64joy_open;
2863bdffa8fSLauri Kasanen dev->close = n64joy_close;
2873bdffa8fSLauri Kasanen
2883bdffa8fSLauri Kasanen /* d-pad */
2893bdffa8fSLauri Kasanen input_set_capability(dev, EV_KEY, BTN_DPAD_UP);
2903bdffa8fSLauri Kasanen input_set_capability(dev, EV_KEY, BTN_DPAD_DOWN);
2913bdffa8fSLauri Kasanen input_set_capability(dev, EV_KEY, BTN_DPAD_LEFT);
2923bdffa8fSLauri Kasanen input_set_capability(dev, EV_KEY, BTN_DPAD_RIGHT);
2933bdffa8fSLauri Kasanen /* c buttons */
2943bdffa8fSLauri Kasanen input_set_capability(dev, EV_KEY, BTN_LEFT);
2953bdffa8fSLauri Kasanen input_set_capability(dev, EV_KEY, BTN_RIGHT);
2963bdffa8fSLauri Kasanen input_set_capability(dev, EV_KEY, BTN_FORWARD);
2973bdffa8fSLauri Kasanen input_set_capability(dev, EV_KEY, BTN_BACK);
2983bdffa8fSLauri Kasanen /* matching buttons */
2993bdffa8fSLauri Kasanen input_set_capability(dev, EV_KEY, BTN_START);
3003bdffa8fSLauri Kasanen input_set_capability(dev, EV_KEY, BTN_Z);
3013bdffa8fSLauri Kasanen /* remaining ones: a, b, l, r */
3023bdffa8fSLauri Kasanen input_set_capability(dev, EV_KEY, BTN_0);
3033bdffa8fSLauri Kasanen input_set_capability(dev, EV_KEY, BTN_1);
3043bdffa8fSLauri Kasanen input_set_capability(dev, EV_KEY, BTN_2);
3053bdffa8fSLauri Kasanen input_set_capability(dev, EV_KEY, BTN_3);
3063bdffa8fSLauri Kasanen
3073bdffa8fSLauri Kasanen for (j = 0; j < 2; j++)
3083bdffa8fSLauri Kasanen input_set_abs_params(dev, ABS_X + j,
3093bdffa8fSLauri Kasanen S8_MIN, S8_MAX, 0, 0);
3103bdffa8fSLauri Kasanen
3113bdffa8fSLauri Kasanen err = input_register_device(dev);
3123bdffa8fSLauri Kasanen if (err) {
3133bdffa8fSLauri Kasanen input_free_device(dev);
3143bdffa8fSLauri Kasanen goto fail;
3153bdffa8fSLauri Kasanen }
3163bdffa8fSLauri Kasanen }
3173bdffa8fSLauri Kasanen }
3183bdffa8fSLauri Kasanen
3193bdffa8fSLauri Kasanen pr_info("%u controller(s) connected\n", found);
3203bdffa8fSLauri Kasanen
3213bdffa8fSLauri Kasanen if (!found)
3223bdffa8fSLauri Kasanen return -ENODEV;
3233bdffa8fSLauri Kasanen
3243bdffa8fSLauri Kasanen return 0;
3253bdffa8fSLauri Kasanen fail:
3263bdffa8fSLauri Kasanen for (i = 0; i < MAX_CONTROLLERS; i++) {
3273bdffa8fSLauri Kasanen if (!priv->n64joy_dev[i])
3283bdffa8fSLauri Kasanen continue;
3293bdffa8fSLauri Kasanen input_unregister_device(priv->n64joy_dev[i]);
3303bdffa8fSLauri Kasanen }
3313bdffa8fSLauri Kasanen return err;
3323bdffa8fSLauri Kasanen }
3333bdffa8fSLauri Kasanen
3343bdffa8fSLauri Kasanen static struct platform_driver n64joy_driver = {
3353bdffa8fSLauri Kasanen .driver = {
3363bdffa8fSLauri Kasanen .name = "n64joy",
3373bdffa8fSLauri Kasanen },
3383bdffa8fSLauri Kasanen };
3393bdffa8fSLauri Kasanen
n64joy_init(void)3403bdffa8fSLauri Kasanen static int __init n64joy_init(void)
3413bdffa8fSLauri Kasanen {
3423bdffa8fSLauri Kasanen return platform_driver_probe(&n64joy_driver, n64joy_probe);
3433bdffa8fSLauri Kasanen }
3443bdffa8fSLauri Kasanen
3453bdffa8fSLauri Kasanen module_init(n64joy_init);
346