346 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			346 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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(*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);
 |