162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Copyright (c) 1998-2001 Vojtech Pavlik
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Based on the work of:
662306a36Sopenharmony_ci *	Steffen Schwenke
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci/*
1062306a36Sopenharmony_ci * TurboGraFX parallel port interface driver for Linux.
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <linux/kernel.h>
1462306a36Sopenharmony_ci#include <linux/parport.h>
1562306a36Sopenharmony_ci#include <linux/input.h>
1662306a36Sopenharmony_ci#include <linux/module.h>
1762306a36Sopenharmony_ci#include <linux/init.h>
1862306a36Sopenharmony_ci#include <linux/mutex.h>
1962306a36Sopenharmony_ci#include <linux/slab.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ciMODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
2262306a36Sopenharmony_ciMODULE_DESCRIPTION("TurboGraFX parallel port interface driver");
2362306a36Sopenharmony_ciMODULE_LICENSE("GPL");
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define TGFX_MAX_PORTS		3
2662306a36Sopenharmony_ci#define TGFX_MAX_DEVICES	7
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistruct tgfx_config {
2962306a36Sopenharmony_ci	int args[TGFX_MAX_DEVICES + 1];
3062306a36Sopenharmony_ci	unsigned int nargs;
3162306a36Sopenharmony_ci};
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic struct tgfx_config tgfx_cfg[TGFX_MAX_PORTS];
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cimodule_param_array_named(map, tgfx_cfg[0].args, int, &tgfx_cfg[0].nargs, 0);
3662306a36Sopenharmony_ciMODULE_PARM_DESC(map, "Describes first set of devices (<parport#>,<js1>,<js2>,..<js7>");
3762306a36Sopenharmony_cimodule_param_array_named(map2, tgfx_cfg[1].args, int, &tgfx_cfg[1].nargs, 0);
3862306a36Sopenharmony_ciMODULE_PARM_DESC(map2, "Describes second set of devices");
3962306a36Sopenharmony_cimodule_param_array_named(map3, tgfx_cfg[2].args, int, &tgfx_cfg[2].nargs, 0);
4062306a36Sopenharmony_ciMODULE_PARM_DESC(map3, "Describes third set of devices");
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci#define TGFX_REFRESH_TIME	HZ/100	/* 10 ms */
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci#define TGFX_TRIGGER		0x08
4562306a36Sopenharmony_ci#define TGFX_UP			0x10
4662306a36Sopenharmony_ci#define TGFX_DOWN		0x20
4762306a36Sopenharmony_ci#define TGFX_LEFT		0x40
4862306a36Sopenharmony_ci#define TGFX_RIGHT		0x80
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci#define TGFX_THUMB		0x02
5162306a36Sopenharmony_ci#define TGFX_THUMB2		0x04
5262306a36Sopenharmony_ci#define TGFX_TOP		0x01
5362306a36Sopenharmony_ci#define TGFX_TOP2		0x08
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic int tgfx_buttons[] = { BTN_TRIGGER, BTN_THUMB, BTN_THUMB2, BTN_TOP, BTN_TOP2 };
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic struct tgfx {
5862306a36Sopenharmony_ci	struct pardevice *pd;
5962306a36Sopenharmony_ci	struct timer_list timer;
6062306a36Sopenharmony_ci	struct input_dev *dev[TGFX_MAX_DEVICES];
6162306a36Sopenharmony_ci	char name[TGFX_MAX_DEVICES][64];
6262306a36Sopenharmony_ci	char phys[TGFX_MAX_DEVICES][32];
6362306a36Sopenharmony_ci	int sticks;
6462306a36Sopenharmony_ci	int used;
6562306a36Sopenharmony_ci	int parportno;
6662306a36Sopenharmony_ci	struct mutex sem;
6762306a36Sopenharmony_ci} *tgfx_base[TGFX_MAX_PORTS];
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci/*
7062306a36Sopenharmony_ci * tgfx_timer() reads and analyzes TurboGraFX joystick data.
7162306a36Sopenharmony_ci */
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic void tgfx_timer(struct timer_list *t)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	struct tgfx *tgfx = from_timer(tgfx, t, timer);
7662306a36Sopenharmony_ci	struct input_dev *dev;
7762306a36Sopenharmony_ci	int data1, data2, i;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	for (i = 0; i < 7; i++)
8062306a36Sopenharmony_ci		if (tgfx->sticks & (1 << i)) {
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci			dev = tgfx->dev[i];
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci			parport_write_data(tgfx->pd->port, ~(1 << i));
8562306a36Sopenharmony_ci			data1 = parport_read_status(tgfx->pd->port) ^ 0x7f;
8662306a36Sopenharmony_ci			data2 = parport_read_control(tgfx->pd->port) ^ 0x04;	/* CAVEAT parport */
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci			input_report_abs(dev, ABS_X, !!(data1 & TGFX_RIGHT) - !!(data1 & TGFX_LEFT));
8962306a36Sopenharmony_ci			input_report_abs(dev, ABS_Y, !!(data1 & TGFX_DOWN ) - !!(data1 & TGFX_UP  ));
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci			input_report_key(dev, BTN_TRIGGER, (data1 & TGFX_TRIGGER));
9262306a36Sopenharmony_ci			input_report_key(dev, BTN_THUMB,   (data2 & TGFX_THUMB  ));
9362306a36Sopenharmony_ci			input_report_key(dev, BTN_THUMB2,  (data2 & TGFX_THUMB2 ));
9462306a36Sopenharmony_ci			input_report_key(dev, BTN_TOP,     (data2 & TGFX_TOP    ));
9562306a36Sopenharmony_ci			input_report_key(dev, BTN_TOP2,    (data2 & TGFX_TOP2   ));
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci			input_sync(dev);
9862306a36Sopenharmony_ci		}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	mod_timer(&tgfx->timer, jiffies + TGFX_REFRESH_TIME);
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistatic int tgfx_open(struct input_dev *dev)
10462306a36Sopenharmony_ci{
10562306a36Sopenharmony_ci	struct tgfx *tgfx = input_get_drvdata(dev);
10662306a36Sopenharmony_ci	int err;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	err = mutex_lock_interruptible(&tgfx->sem);
10962306a36Sopenharmony_ci	if (err)
11062306a36Sopenharmony_ci		return err;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	if (!tgfx->used++) {
11362306a36Sopenharmony_ci		parport_claim(tgfx->pd);
11462306a36Sopenharmony_ci		parport_write_control(tgfx->pd->port, 0x04);
11562306a36Sopenharmony_ci		mod_timer(&tgfx->timer, jiffies + TGFX_REFRESH_TIME);
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	mutex_unlock(&tgfx->sem);
11962306a36Sopenharmony_ci	return 0;
12062306a36Sopenharmony_ci}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic void tgfx_close(struct input_dev *dev)
12362306a36Sopenharmony_ci{
12462306a36Sopenharmony_ci	struct tgfx *tgfx = input_get_drvdata(dev);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	mutex_lock(&tgfx->sem);
12762306a36Sopenharmony_ci	if (!--tgfx->used) {
12862306a36Sopenharmony_ci		del_timer_sync(&tgfx->timer);
12962306a36Sopenharmony_ci		parport_write_control(tgfx->pd->port, 0x00);
13062306a36Sopenharmony_ci		parport_release(tgfx->pd);
13162306a36Sopenharmony_ci	}
13262306a36Sopenharmony_ci	mutex_unlock(&tgfx->sem);
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci/*
13862306a36Sopenharmony_ci * tgfx_probe() probes for tg gamepads.
13962306a36Sopenharmony_ci */
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_cistatic void tgfx_attach(struct parport *pp)
14262306a36Sopenharmony_ci{
14362306a36Sopenharmony_ci	struct tgfx *tgfx;
14462306a36Sopenharmony_ci	struct input_dev *input_dev;
14562306a36Sopenharmony_ci	struct pardevice *pd;
14662306a36Sopenharmony_ci	int i, j, port_idx;
14762306a36Sopenharmony_ci	int *n_buttons, n_devs;
14862306a36Sopenharmony_ci	struct pardev_cb tgfx_parport_cb;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	for (port_idx = 0; port_idx < TGFX_MAX_PORTS; port_idx++) {
15162306a36Sopenharmony_ci		if (tgfx_cfg[port_idx].nargs == 0 ||
15262306a36Sopenharmony_ci		    tgfx_cfg[port_idx].args[0] < 0)
15362306a36Sopenharmony_ci			continue;
15462306a36Sopenharmony_ci		if (tgfx_cfg[port_idx].args[0] == pp->number)
15562306a36Sopenharmony_ci			break;
15662306a36Sopenharmony_ci	}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	if (port_idx == TGFX_MAX_PORTS) {
15962306a36Sopenharmony_ci		pr_debug("Not using parport%d.\n", pp->number);
16062306a36Sopenharmony_ci		return;
16162306a36Sopenharmony_ci	}
16262306a36Sopenharmony_ci	n_buttons = tgfx_cfg[port_idx].args + 1;
16362306a36Sopenharmony_ci	n_devs = tgfx_cfg[port_idx].nargs - 1;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	memset(&tgfx_parport_cb, 0, sizeof(tgfx_parport_cb));
16662306a36Sopenharmony_ci	tgfx_parport_cb.flags = PARPORT_FLAG_EXCL;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	pd = parport_register_dev_model(pp, "turbografx", &tgfx_parport_cb,
16962306a36Sopenharmony_ci					port_idx);
17062306a36Sopenharmony_ci	if (!pd) {
17162306a36Sopenharmony_ci		pr_err("parport busy already - lp.o loaded?\n");
17262306a36Sopenharmony_ci		return;
17362306a36Sopenharmony_ci	}
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	tgfx = kzalloc(sizeof(struct tgfx), GFP_KERNEL);
17662306a36Sopenharmony_ci	if (!tgfx) {
17762306a36Sopenharmony_ci		printk(KERN_ERR "turbografx.c: Not enough memory\n");
17862306a36Sopenharmony_ci		goto err_unreg_pardev;
17962306a36Sopenharmony_ci	}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	mutex_init(&tgfx->sem);
18262306a36Sopenharmony_ci	tgfx->pd = pd;
18362306a36Sopenharmony_ci	tgfx->parportno = pp->number;
18462306a36Sopenharmony_ci	timer_setup(&tgfx->timer, tgfx_timer, 0);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	for (i = 0; i < n_devs; i++) {
18762306a36Sopenharmony_ci		if (n_buttons[i] < 1)
18862306a36Sopenharmony_ci			continue;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci		if (n_buttons[i] > ARRAY_SIZE(tgfx_buttons)) {
19162306a36Sopenharmony_ci			printk(KERN_ERR "turbografx.c: Invalid number of buttons %d\n", n_buttons[i]);
19262306a36Sopenharmony_ci			goto err_unreg_devs;
19362306a36Sopenharmony_ci		}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci		tgfx->dev[i] = input_dev = input_allocate_device();
19662306a36Sopenharmony_ci		if (!input_dev) {
19762306a36Sopenharmony_ci			printk(KERN_ERR "turbografx.c: Not enough memory for input device\n");
19862306a36Sopenharmony_ci			goto err_unreg_devs;
19962306a36Sopenharmony_ci		}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci		tgfx->sticks |= (1 << i);
20262306a36Sopenharmony_ci		snprintf(tgfx->name[i], sizeof(tgfx->name[i]),
20362306a36Sopenharmony_ci			 "TurboGraFX %d-button Multisystem joystick", n_buttons[i]);
20462306a36Sopenharmony_ci		snprintf(tgfx->phys[i], sizeof(tgfx->phys[i]),
20562306a36Sopenharmony_ci			 "%s/input%d", tgfx->pd->port->name, i);
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci		input_dev->name = tgfx->name[i];
20862306a36Sopenharmony_ci		input_dev->phys = tgfx->phys[i];
20962306a36Sopenharmony_ci		input_dev->id.bustype = BUS_PARPORT;
21062306a36Sopenharmony_ci		input_dev->id.vendor = 0x0003;
21162306a36Sopenharmony_ci		input_dev->id.product = n_buttons[i];
21262306a36Sopenharmony_ci		input_dev->id.version = 0x0100;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci		input_set_drvdata(input_dev, tgfx);
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci		input_dev->open = tgfx_open;
21762306a36Sopenharmony_ci		input_dev->close = tgfx_close;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci		input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
22062306a36Sopenharmony_ci		input_set_abs_params(input_dev, ABS_X, -1, 1, 0, 0);
22162306a36Sopenharmony_ci		input_set_abs_params(input_dev, ABS_Y, -1, 1, 0, 0);
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci		for (j = 0; j < n_buttons[i]; j++)
22462306a36Sopenharmony_ci			set_bit(tgfx_buttons[j], input_dev->keybit);
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci		if (input_register_device(tgfx->dev[i]))
22762306a36Sopenharmony_ci			goto err_free_dev;
22862306a36Sopenharmony_ci	}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci        if (!tgfx->sticks) {
23162306a36Sopenharmony_ci		printk(KERN_ERR "turbografx.c: No valid devices specified\n");
23262306a36Sopenharmony_ci		goto err_free_tgfx;
23362306a36Sopenharmony_ci        }
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	tgfx_base[port_idx] = tgfx;
23662306a36Sopenharmony_ci	return;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci err_free_dev:
23962306a36Sopenharmony_ci	input_free_device(tgfx->dev[i]);
24062306a36Sopenharmony_ci err_unreg_devs:
24162306a36Sopenharmony_ci	while (--i >= 0)
24262306a36Sopenharmony_ci		if (tgfx->dev[i])
24362306a36Sopenharmony_ci			input_unregister_device(tgfx->dev[i]);
24462306a36Sopenharmony_ci err_free_tgfx:
24562306a36Sopenharmony_ci	kfree(tgfx);
24662306a36Sopenharmony_ci err_unreg_pardev:
24762306a36Sopenharmony_ci	parport_unregister_device(pd);
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic void tgfx_detach(struct parport *port)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	int i;
25362306a36Sopenharmony_ci	struct tgfx *tgfx;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	for (i = 0; i < TGFX_MAX_PORTS; i++) {
25662306a36Sopenharmony_ci		if (tgfx_base[i] && tgfx_base[i]->parportno == port->number)
25762306a36Sopenharmony_ci			break;
25862306a36Sopenharmony_ci	}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	if (i == TGFX_MAX_PORTS)
26162306a36Sopenharmony_ci		return;
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	tgfx = tgfx_base[i];
26462306a36Sopenharmony_ci	tgfx_base[i] = NULL;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	for (i = 0; i < TGFX_MAX_DEVICES; i++)
26762306a36Sopenharmony_ci		if (tgfx->dev[i])
26862306a36Sopenharmony_ci			input_unregister_device(tgfx->dev[i]);
26962306a36Sopenharmony_ci	parport_unregister_device(tgfx->pd);
27062306a36Sopenharmony_ci	kfree(tgfx);
27162306a36Sopenharmony_ci}
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_cistatic struct parport_driver tgfx_parport_driver = {
27462306a36Sopenharmony_ci	.name = "turbografx",
27562306a36Sopenharmony_ci	.match_port = tgfx_attach,
27662306a36Sopenharmony_ci	.detach = tgfx_detach,
27762306a36Sopenharmony_ci	.devmodel = true,
27862306a36Sopenharmony_ci};
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cistatic int __init tgfx_init(void)
28162306a36Sopenharmony_ci{
28262306a36Sopenharmony_ci	int i;
28362306a36Sopenharmony_ci	int have_dev = 0;
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	for (i = 0; i < TGFX_MAX_PORTS; i++) {
28662306a36Sopenharmony_ci		if (tgfx_cfg[i].nargs == 0 || tgfx_cfg[i].args[0] < 0)
28762306a36Sopenharmony_ci			continue;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci		if (tgfx_cfg[i].nargs < 2) {
29062306a36Sopenharmony_ci			printk(KERN_ERR "turbografx.c: at least one joystick must be specified\n");
29162306a36Sopenharmony_ci			return -EINVAL;
29262306a36Sopenharmony_ci		}
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci		have_dev = 1;
29562306a36Sopenharmony_ci	}
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	if (!have_dev)
29862306a36Sopenharmony_ci		return -ENODEV;
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	return parport_register_driver(&tgfx_parport_driver);
30162306a36Sopenharmony_ci}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_cistatic void __exit tgfx_exit(void)
30462306a36Sopenharmony_ci{
30562306a36Sopenharmony_ci	parport_unregister_driver(&tgfx_parport_driver);
30662306a36Sopenharmony_ci}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_cimodule_init(tgfx_init);
30962306a36Sopenharmony_cimodule_exit(tgfx_exit);
310