| // 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 (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); |