162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Support for the four N64 controllers. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2021 Lauri Kasanen 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/errno.h> 1162306a36Sopenharmony_ci#include <linux/init.h> 1262306a36Sopenharmony_ci#include <linux/input.h> 1362306a36Sopenharmony_ci#include <linux/limits.h> 1462306a36Sopenharmony_ci#include <linux/kernel.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/mutex.h> 1762306a36Sopenharmony_ci#include <linux/platform_device.h> 1862306a36Sopenharmony_ci#include <linux/slab.h> 1962306a36Sopenharmony_ci#include <linux/timer.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ciMODULE_AUTHOR("Lauri Kasanen <cand@gmx.com>"); 2262306a36Sopenharmony_ciMODULE_DESCRIPTION("Driver for N64 controllers"); 2362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define PIF_RAM 0x1fc007c0 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define SI_DRAM_REG 0 2862306a36Sopenharmony_ci#define SI_READ_REG 1 2962306a36Sopenharmony_ci#define SI_WRITE_REG 4 3062306a36Sopenharmony_ci#define SI_STATUS_REG 6 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#define SI_STATUS_DMA_BUSY BIT(0) 3362306a36Sopenharmony_ci#define SI_STATUS_IO_BUSY BIT(1) 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#define N64_CONTROLLER_ID 0x0500 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci#define MAX_CONTROLLERS 4 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic const char *n64joy_phys[MAX_CONTROLLERS] = { 4062306a36Sopenharmony_ci "n64joy/port0", 4162306a36Sopenharmony_ci "n64joy/port1", 4262306a36Sopenharmony_ci "n64joy/port2", 4362306a36Sopenharmony_ci "n64joy/port3", 4462306a36Sopenharmony_ci}; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistruct n64joy_priv { 4762306a36Sopenharmony_ci u64 si_buf[8] ____cacheline_aligned; 4862306a36Sopenharmony_ci struct timer_list timer; 4962306a36Sopenharmony_ci struct mutex n64joy_mutex; 5062306a36Sopenharmony_ci struct input_dev *n64joy_dev[MAX_CONTROLLERS]; 5162306a36Sopenharmony_ci u32 __iomem *reg_base; 5262306a36Sopenharmony_ci u8 n64joy_opened; 5362306a36Sopenharmony_ci}; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistruct joydata { 5662306a36Sopenharmony_ci unsigned int: 16; /* unused */ 5762306a36Sopenharmony_ci unsigned int err: 2; 5862306a36Sopenharmony_ci unsigned int: 14; /* unused */ 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci union { 6162306a36Sopenharmony_ci u32 data; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci struct { 6462306a36Sopenharmony_ci unsigned int a: 1; 6562306a36Sopenharmony_ci unsigned int b: 1; 6662306a36Sopenharmony_ci unsigned int z: 1; 6762306a36Sopenharmony_ci unsigned int start: 1; 6862306a36Sopenharmony_ci unsigned int up: 1; 6962306a36Sopenharmony_ci unsigned int down: 1; 7062306a36Sopenharmony_ci unsigned int left: 1; 7162306a36Sopenharmony_ci unsigned int right: 1; 7262306a36Sopenharmony_ci unsigned int: 2; /* unused */ 7362306a36Sopenharmony_ci unsigned int l: 1; 7462306a36Sopenharmony_ci unsigned int r: 1; 7562306a36Sopenharmony_ci unsigned int c_up: 1; 7662306a36Sopenharmony_ci unsigned int c_down: 1; 7762306a36Sopenharmony_ci unsigned int c_left: 1; 7862306a36Sopenharmony_ci unsigned int c_right: 1; 7962306a36Sopenharmony_ci signed int x: 8; 8062306a36Sopenharmony_ci signed int y: 8; 8162306a36Sopenharmony_ci }; 8262306a36Sopenharmony_ci }; 8362306a36Sopenharmony_ci}; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic void n64joy_write_reg(u32 __iomem *reg_base, const u8 reg, const u32 value) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci writel(value, reg_base + reg); 8862306a36Sopenharmony_ci} 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cistatic u32 n64joy_read_reg(u32 __iomem *reg_base, const u8 reg) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci return readl(reg_base + reg); 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic void n64joy_wait_si_dma(u32 __iomem *reg_base) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci while (n64joy_read_reg(reg_base, SI_STATUS_REG) & 9862306a36Sopenharmony_ci (SI_STATUS_DMA_BUSY | SI_STATUS_IO_BUSY)) 9962306a36Sopenharmony_ci cpu_relax(); 10062306a36Sopenharmony_ci} 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_cistatic void n64joy_exec_pif(struct n64joy_priv *priv, const u64 in[8]) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci unsigned long flags; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci dma_cache_wback_inv((unsigned long) in, 8 * 8); 10762306a36Sopenharmony_ci dma_cache_inv((unsigned long) priv->si_buf, 8 * 8); 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci local_irq_save(flags); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci n64joy_wait_si_dma(priv->reg_base); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci barrier(); 11462306a36Sopenharmony_ci n64joy_write_reg(priv->reg_base, SI_DRAM_REG, virt_to_phys(in)); 11562306a36Sopenharmony_ci barrier(); 11662306a36Sopenharmony_ci n64joy_write_reg(priv->reg_base, SI_WRITE_REG, PIF_RAM); 11762306a36Sopenharmony_ci barrier(); 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci n64joy_wait_si_dma(priv->reg_base); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci barrier(); 12262306a36Sopenharmony_ci n64joy_write_reg(priv->reg_base, SI_DRAM_REG, virt_to_phys(priv->si_buf)); 12362306a36Sopenharmony_ci barrier(); 12462306a36Sopenharmony_ci n64joy_write_reg(priv->reg_base, SI_READ_REG, PIF_RAM); 12562306a36Sopenharmony_ci barrier(); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci n64joy_wait_si_dma(priv->reg_base); 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci local_irq_restore(flags); 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic const u64 polldata[] ____cacheline_aligned = { 13362306a36Sopenharmony_ci 0xff010401ffffffff, 13462306a36Sopenharmony_ci 0xff010401ffffffff, 13562306a36Sopenharmony_ci 0xff010401ffffffff, 13662306a36Sopenharmony_ci 0xff010401ffffffff, 13762306a36Sopenharmony_ci 0xfe00000000000000, 13862306a36Sopenharmony_ci 0, 13962306a36Sopenharmony_ci 0, 14062306a36Sopenharmony_ci 1 14162306a36Sopenharmony_ci}; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic void n64joy_poll(struct timer_list *t) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci const struct joydata *data; 14662306a36Sopenharmony_ci struct n64joy_priv *priv = container_of(t, struct n64joy_priv, timer); 14762306a36Sopenharmony_ci struct input_dev *dev; 14862306a36Sopenharmony_ci u32 i; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci n64joy_exec_pif(priv, polldata); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci data = (struct joydata *) priv->si_buf; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci for (i = 0; i < MAX_CONTROLLERS; i++) { 15562306a36Sopenharmony_ci if (!priv->n64joy_dev[i]) 15662306a36Sopenharmony_ci continue; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci dev = priv->n64joy_dev[i]; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci /* d-pad */ 16162306a36Sopenharmony_ci input_report_key(dev, BTN_DPAD_UP, data[i].up); 16262306a36Sopenharmony_ci input_report_key(dev, BTN_DPAD_DOWN, data[i].down); 16362306a36Sopenharmony_ci input_report_key(dev, BTN_DPAD_LEFT, data[i].left); 16462306a36Sopenharmony_ci input_report_key(dev, BTN_DPAD_RIGHT, data[i].right); 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci /* c buttons */ 16762306a36Sopenharmony_ci input_report_key(dev, BTN_FORWARD, data[i].c_up); 16862306a36Sopenharmony_ci input_report_key(dev, BTN_BACK, data[i].c_down); 16962306a36Sopenharmony_ci input_report_key(dev, BTN_LEFT, data[i].c_left); 17062306a36Sopenharmony_ci input_report_key(dev, BTN_RIGHT, data[i].c_right); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci /* matching buttons */ 17362306a36Sopenharmony_ci input_report_key(dev, BTN_START, data[i].start); 17462306a36Sopenharmony_ci input_report_key(dev, BTN_Z, data[i].z); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci /* remaining ones: a, b, l, r */ 17762306a36Sopenharmony_ci input_report_key(dev, BTN_0, data[i].a); 17862306a36Sopenharmony_ci input_report_key(dev, BTN_1, data[i].b); 17962306a36Sopenharmony_ci input_report_key(dev, BTN_2, data[i].l); 18062306a36Sopenharmony_ci input_report_key(dev, BTN_3, data[i].r); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci input_report_abs(dev, ABS_X, data[i].x); 18362306a36Sopenharmony_ci input_report_abs(dev, ABS_Y, data[i].y); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci input_sync(dev); 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci mod_timer(&priv->timer, jiffies + msecs_to_jiffies(16)); 18962306a36Sopenharmony_ci} 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cistatic int n64joy_open(struct input_dev *dev) 19262306a36Sopenharmony_ci{ 19362306a36Sopenharmony_ci struct n64joy_priv *priv = input_get_drvdata(dev); 19462306a36Sopenharmony_ci int err; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci err = mutex_lock_interruptible(&priv->n64joy_mutex); 19762306a36Sopenharmony_ci if (err) 19862306a36Sopenharmony_ci return err; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci if (!priv->n64joy_opened) { 20162306a36Sopenharmony_ci /* 20262306a36Sopenharmony_ci * We could use the vblank irq, but it's not important if 20362306a36Sopenharmony_ci * the poll point slightly changes. 20462306a36Sopenharmony_ci */ 20562306a36Sopenharmony_ci timer_setup(&priv->timer, n64joy_poll, 0); 20662306a36Sopenharmony_ci mod_timer(&priv->timer, jiffies + msecs_to_jiffies(16)); 20762306a36Sopenharmony_ci } 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci priv->n64joy_opened++; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci mutex_unlock(&priv->n64joy_mutex); 21262306a36Sopenharmony_ci return err; 21362306a36Sopenharmony_ci} 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_cistatic void n64joy_close(struct input_dev *dev) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci struct n64joy_priv *priv = input_get_drvdata(dev); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci mutex_lock(&priv->n64joy_mutex); 22062306a36Sopenharmony_ci if (!--priv->n64joy_opened) 22162306a36Sopenharmony_ci del_timer_sync(&priv->timer); 22262306a36Sopenharmony_ci mutex_unlock(&priv->n64joy_mutex); 22362306a36Sopenharmony_ci} 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_cistatic const u64 __initconst scandata[] ____cacheline_aligned = { 22662306a36Sopenharmony_ci 0xff010300ffffffff, 22762306a36Sopenharmony_ci 0xff010300ffffffff, 22862306a36Sopenharmony_ci 0xff010300ffffffff, 22962306a36Sopenharmony_ci 0xff010300ffffffff, 23062306a36Sopenharmony_ci 0xfe00000000000000, 23162306a36Sopenharmony_ci 0, 23262306a36Sopenharmony_ci 0, 23362306a36Sopenharmony_ci 1 23462306a36Sopenharmony_ci}; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci/* 23762306a36Sopenharmony_ci * The target device is embedded and RAM-constrained. We save RAM 23862306a36Sopenharmony_ci * by initializing in __init code that gets dropped late in boot. 23962306a36Sopenharmony_ci * For the same reason there is no module or unloading support. 24062306a36Sopenharmony_ci */ 24162306a36Sopenharmony_cistatic int __init n64joy_probe(struct platform_device *pdev) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci const struct joydata *data; 24462306a36Sopenharmony_ci struct n64joy_priv *priv; 24562306a36Sopenharmony_ci struct input_dev *dev; 24662306a36Sopenharmony_ci int err = 0; 24762306a36Sopenharmony_ci u32 i, j, found = 0; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci priv = kzalloc(sizeof(struct n64joy_priv), GFP_KERNEL); 25062306a36Sopenharmony_ci if (!priv) 25162306a36Sopenharmony_ci return -ENOMEM; 25262306a36Sopenharmony_ci mutex_init(&priv->n64joy_mutex); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci priv->reg_base = devm_platform_ioremap_resource(pdev, 0); 25562306a36Sopenharmony_ci if (IS_ERR(priv->reg_base)) { 25662306a36Sopenharmony_ci err = PTR_ERR(priv->reg_base); 25762306a36Sopenharmony_ci goto fail; 25862306a36Sopenharmony_ci } 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci /* The controllers are not hotpluggable, so we can scan in init */ 26162306a36Sopenharmony_ci n64joy_exec_pif(priv, scandata); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci data = (struct joydata *) priv->si_buf; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci for (i = 0; i < MAX_CONTROLLERS; i++) { 26662306a36Sopenharmony_ci if (!data[i].err && data[i].data >> 16 == N64_CONTROLLER_ID) { 26762306a36Sopenharmony_ci found++; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci dev = priv->n64joy_dev[i] = input_allocate_device(); 27062306a36Sopenharmony_ci if (!priv->n64joy_dev[i]) { 27162306a36Sopenharmony_ci err = -ENOMEM; 27262306a36Sopenharmony_ci goto fail; 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci input_set_drvdata(dev, priv); 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci dev->name = "N64 controller"; 27862306a36Sopenharmony_ci dev->phys = n64joy_phys[i]; 27962306a36Sopenharmony_ci dev->id.bustype = BUS_HOST; 28062306a36Sopenharmony_ci dev->id.vendor = 0; 28162306a36Sopenharmony_ci dev->id.product = data[i].data >> 16; 28262306a36Sopenharmony_ci dev->id.version = 0; 28362306a36Sopenharmony_ci dev->dev.parent = &pdev->dev; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci dev->open = n64joy_open; 28662306a36Sopenharmony_ci dev->close = n64joy_close; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci /* d-pad */ 28962306a36Sopenharmony_ci input_set_capability(dev, EV_KEY, BTN_DPAD_UP); 29062306a36Sopenharmony_ci input_set_capability(dev, EV_KEY, BTN_DPAD_DOWN); 29162306a36Sopenharmony_ci input_set_capability(dev, EV_KEY, BTN_DPAD_LEFT); 29262306a36Sopenharmony_ci input_set_capability(dev, EV_KEY, BTN_DPAD_RIGHT); 29362306a36Sopenharmony_ci /* c buttons */ 29462306a36Sopenharmony_ci input_set_capability(dev, EV_KEY, BTN_LEFT); 29562306a36Sopenharmony_ci input_set_capability(dev, EV_KEY, BTN_RIGHT); 29662306a36Sopenharmony_ci input_set_capability(dev, EV_KEY, BTN_FORWARD); 29762306a36Sopenharmony_ci input_set_capability(dev, EV_KEY, BTN_BACK); 29862306a36Sopenharmony_ci /* matching buttons */ 29962306a36Sopenharmony_ci input_set_capability(dev, EV_KEY, BTN_START); 30062306a36Sopenharmony_ci input_set_capability(dev, EV_KEY, BTN_Z); 30162306a36Sopenharmony_ci /* remaining ones: a, b, l, r */ 30262306a36Sopenharmony_ci input_set_capability(dev, EV_KEY, BTN_0); 30362306a36Sopenharmony_ci input_set_capability(dev, EV_KEY, BTN_1); 30462306a36Sopenharmony_ci input_set_capability(dev, EV_KEY, BTN_2); 30562306a36Sopenharmony_ci input_set_capability(dev, EV_KEY, BTN_3); 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci for (j = 0; j < 2; j++) 30862306a36Sopenharmony_ci input_set_abs_params(dev, ABS_X + j, 30962306a36Sopenharmony_ci S8_MIN, S8_MAX, 0, 0); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci err = input_register_device(dev); 31262306a36Sopenharmony_ci if (err) { 31362306a36Sopenharmony_ci input_free_device(dev); 31462306a36Sopenharmony_ci goto fail; 31562306a36Sopenharmony_ci } 31662306a36Sopenharmony_ci } 31762306a36Sopenharmony_ci } 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci pr_info("%u controller(s) connected\n", found); 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci if (!found) 32262306a36Sopenharmony_ci return -ENODEV; 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci return 0; 32562306a36Sopenharmony_cifail: 32662306a36Sopenharmony_ci for (i = 0; i < MAX_CONTROLLERS; i++) { 32762306a36Sopenharmony_ci if (!priv->n64joy_dev[i]) 32862306a36Sopenharmony_ci continue; 32962306a36Sopenharmony_ci input_unregister_device(priv->n64joy_dev[i]); 33062306a36Sopenharmony_ci } 33162306a36Sopenharmony_ci return err; 33262306a36Sopenharmony_ci} 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_cistatic struct platform_driver n64joy_driver = { 33562306a36Sopenharmony_ci .driver = { 33662306a36Sopenharmony_ci .name = "n64joy", 33762306a36Sopenharmony_ci }, 33862306a36Sopenharmony_ci}; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_cistatic int __init n64joy_init(void) 34162306a36Sopenharmony_ci{ 34262306a36Sopenharmony_ci return platform_driver_probe(&n64joy_driver, n64joy_probe); 34362306a36Sopenharmony_ci} 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_cimodule_init(n64joy_init); 346