// 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 #include #include #include #include #include #include #include #include #include MODULE_AUTHOR("Lauri Kasanen "); 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); scoped_guard(mutex_intr, &priv->n64joy_mutex) { 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++; return 0; } return -EINTR; } static void n64joy_close(struct input_dev *dev) { struct n64joy_priv *priv = input_get_drvdata(dev); guard(mutex)(&priv->n64joy_mutex); if (!--priv->n64joy_opened) del_timer_sync(&priv->timer); } 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(*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);