18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 1998-2001 Vojtech Pavlik 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Based on the work of: 68c2ecf20Sopenharmony_ci * Steffen Schwenke 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci/* 108c2ecf20Sopenharmony_ci * TurboGraFX parallel port interface driver for Linux. 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci/* 148c2ecf20Sopenharmony_ci */ 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include <linux/kernel.h> 178c2ecf20Sopenharmony_ci#include <linux/parport.h> 188c2ecf20Sopenharmony_ci#include <linux/input.h> 198c2ecf20Sopenharmony_ci#include <linux/module.h> 208c2ecf20Sopenharmony_ci#include <linux/init.h> 218c2ecf20Sopenharmony_ci#include <linux/mutex.h> 228c2ecf20Sopenharmony_ci#include <linux/slab.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); 258c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TurboGraFX parallel port interface driver"); 268c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#define TGFX_MAX_PORTS 3 298c2ecf20Sopenharmony_ci#define TGFX_MAX_DEVICES 7 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistruct tgfx_config { 328c2ecf20Sopenharmony_ci int args[TGFX_MAX_DEVICES + 1]; 338c2ecf20Sopenharmony_ci unsigned int nargs; 348c2ecf20Sopenharmony_ci}; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistatic struct tgfx_config tgfx_cfg[TGFX_MAX_PORTS]; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cimodule_param_array_named(map, tgfx_cfg[0].args, int, &tgfx_cfg[0].nargs, 0); 398c2ecf20Sopenharmony_ciMODULE_PARM_DESC(map, "Describes first set of devices (<parport#>,<js1>,<js2>,..<js7>"); 408c2ecf20Sopenharmony_cimodule_param_array_named(map2, tgfx_cfg[1].args, int, &tgfx_cfg[1].nargs, 0); 418c2ecf20Sopenharmony_ciMODULE_PARM_DESC(map2, "Describes second set of devices"); 428c2ecf20Sopenharmony_cimodule_param_array_named(map3, tgfx_cfg[2].args, int, &tgfx_cfg[2].nargs, 0); 438c2ecf20Sopenharmony_ciMODULE_PARM_DESC(map3, "Describes third set of devices"); 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci#define TGFX_REFRESH_TIME HZ/100 /* 10 ms */ 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci#define TGFX_TRIGGER 0x08 488c2ecf20Sopenharmony_ci#define TGFX_UP 0x10 498c2ecf20Sopenharmony_ci#define TGFX_DOWN 0x20 508c2ecf20Sopenharmony_ci#define TGFX_LEFT 0x40 518c2ecf20Sopenharmony_ci#define TGFX_RIGHT 0x80 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci#define TGFX_THUMB 0x02 548c2ecf20Sopenharmony_ci#define TGFX_THUMB2 0x04 558c2ecf20Sopenharmony_ci#define TGFX_TOP 0x01 568c2ecf20Sopenharmony_ci#define TGFX_TOP2 0x08 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistatic int tgfx_buttons[] = { BTN_TRIGGER, BTN_THUMB, BTN_THUMB2, BTN_TOP, BTN_TOP2 }; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic struct tgfx { 618c2ecf20Sopenharmony_ci struct pardevice *pd; 628c2ecf20Sopenharmony_ci struct timer_list timer; 638c2ecf20Sopenharmony_ci struct input_dev *dev[TGFX_MAX_DEVICES]; 648c2ecf20Sopenharmony_ci char name[TGFX_MAX_DEVICES][64]; 658c2ecf20Sopenharmony_ci char phys[TGFX_MAX_DEVICES][32]; 668c2ecf20Sopenharmony_ci int sticks; 678c2ecf20Sopenharmony_ci int used; 688c2ecf20Sopenharmony_ci int parportno; 698c2ecf20Sopenharmony_ci struct mutex sem; 708c2ecf20Sopenharmony_ci} *tgfx_base[TGFX_MAX_PORTS]; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci/* 738c2ecf20Sopenharmony_ci * tgfx_timer() reads and analyzes TurboGraFX joystick data. 748c2ecf20Sopenharmony_ci */ 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic void tgfx_timer(struct timer_list *t) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci struct tgfx *tgfx = from_timer(tgfx, t, timer); 798c2ecf20Sopenharmony_ci struct input_dev *dev; 808c2ecf20Sopenharmony_ci int data1, data2, i; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci for (i = 0; i < 7; i++) 838c2ecf20Sopenharmony_ci if (tgfx->sticks & (1 << i)) { 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci dev = tgfx->dev[i]; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci parport_write_data(tgfx->pd->port, ~(1 << i)); 888c2ecf20Sopenharmony_ci data1 = parport_read_status(tgfx->pd->port) ^ 0x7f; 898c2ecf20Sopenharmony_ci data2 = parport_read_control(tgfx->pd->port) ^ 0x04; /* CAVEAT parport */ 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci input_report_abs(dev, ABS_X, !!(data1 & TGFX_RIGHT) - !!(data1 & TGFX_LEFT)); 928c2ecf20Sopenharmony_ci input_report_abs(dev, ABS_Y, !!(data1 & TGFX_DOWN ) - !!(data1 & TGFX_UP )); 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci input_report_key(dev, BTN_TRIGGER, (data1 & TGFX_TRIGGER)); 958c2ecf20Sopenharmony_ci input_report_key(dev, BTN_THUMB, (data2 & TGFX_THUMB )); 968c2ecf20Sopenharmony_ci input_report_key(dev, BTN_THUMB2, (data2 & TGFX_THUMB2 )); 978c2ecf20Sopenharmony_ci input_report_key(dev, BTN_TOP, (data2 & TGFX_TOP )); 988c2ecf20Sopenharmony_ci input_report_key(dev, BTN_TOP2, (data2 & TGFX_TOP2 )); 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci input_sync(dev); 1018c2ecf20Sopenharmony_ci } 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci mod_timer(&tgfx->timer, jiffies + TGFX_REFRESH_TIME); 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic int tgfx_open(struct input_dev *dev) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci struct tgfx *tgfx = input_get_drvdata(dev); 1098c2ecf20Sopenharmony_ci int err; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci err = mutex_lock_interruptible(&tgfx->sem); 1128c2ecf20Sopenharmony_ci if (err) 1138c2ecf20Sopenharmony_ci return err; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci if (!tgfx->used++) { 1168c2ecf20Sopenharmony_ci parport_claim(tgfx->pd); 1178c2ecf20Sopenharmony_ci parport_write_control(tgfx->pd->port, 0x04); 1188c2ecf20Sopenharmony_ci mod_timer(&tgfx->timer, jiffies + TGFX_REFRESH_TIME); 1198c2ecf20Sopenharmony_ci } 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci mutex_unlock(&tgfx->sem); 1228c2ecf20Sopenharmony_ci return 0; 1238c2ecf20Sopenharmony_ci} 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_cistatic void tgfx_close(struct input_dev *dev) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci struct tgfx *tgfx = input_get_drvdata(dev); 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci mutex_lock(&tgfx->sem); 1308c2ecf20Sopenharmony_ci if (!--tgfx->used) { 1318c2ecf20Sopenharmony_ci del_timer_sync(&tgfx->timer); 1328c2ecf20Sopenharmony_ci parport_write_control(tgfx->pd->port, 0x00); 1338c2ecf20Sopenharmony_ci parport_release(tgfx->pd); 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci mutex_unlock(&tgfx->sem); 1368c2ecf20Sopenharmony_ci} 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci/* 1418c2ecf20Sopenharmony_ci * tgfx_probe() probes for tg gamepads. 1428c2ecf20Sopenharmony_ci */ 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_cistatic void tgfx_attach(struct parport *pp) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci struct tgfx *tgfx; 1478c2ecf20Sopenharmony_ci struct input_dev *input_dev; 1488c2ecf20Sopenharmony_ci struct pardevice *pd; 1498c2ecf20Sopenharmony_ci int i, j, port_idx; 1508c2ecf20Sopenharmony_ci int *n_buttons, n_devs; 1518c2ecf20Sopenharmony_ci struct pardev_cb tgfx_parport_cb; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci for (port_idx = 0; port_idx < TGFX_MAX_PORTS; port_idx++) { 1548c2ecf20Sopenharmony_ci if (tgfx_cfg[port_idx].nargs == 0 || 1558c2ecf20Sopenharmony_ci tgfx_cfg[port_idx].args[0] < 0) 1568c2ecf20Sopenharmony_ci continue; 1578c2ecf20Sopenharmony_ci if (tgfx_cfg[port_idx].args[0] == pp->number) 1588c2ecf20Sopenharmony_ci break; 1598c2ecf20Sopenharmony_ci } 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci if (port_idx == TGFX_MAX_PORTS) { 1628c2ecf20Sopenharmony_ci pr_debug("Not using parport%d.\n", pp->number); 1638c2ecf20Sopenharmony_ci return; 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci n_buttons = tgfx_cfg[port_idx].args + 1; 1668c2ecf20Sopenharmony_ci n_devs = tgfx_cfg[port_idx].nargs - 1; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci memset(&tgfx_parport_cb, 0, sizeof(tgfx_parport_cb)); 1698c2ecf20Sopenharmony_ci tgfx_parport_cb.flags = PARPORT_FLAG_EXCL; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci pd = parport_register_dev_model(pp, "turbografx", &tgfx_parport_cb, 1728c2ecf20Sopenharmony_ci port_idx); 1738c2ecf20Sopenharmony_ci if (!pd) { 1748c2ecf20Sopenharmony_ci pr_err("parport busy already - lp.o loaded?\n"); 1758c2ecf20Sopenharmony_ci return; 1768c2ecf20Sopenharmony_ci } 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci tgfx = kzalloc(sizeof(struct tgfx), GFP_KERNEL); 1798c2ecf20Sopenharmony_ci if (!tgfx) { 1808c2ecf20Sopenharmony_ci printk(KERN_ERR "turbografx.c: Not enough memory\n"); 1818c2ecf20Sopenharmony_ci goto err_unreg_pardev; 1828c2ecf20Sopenharmony_ci } 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci mutex_init(&tgfx->sem); 1858c2ecf20Sopenharmony_ci tgfx->pd = pd; 1868c2ecf20Sopenharmony_ci tgfx->parportno = pp->number; 1878c2ecf20Sopenharmony_ci timer_setup(&tgfx->timer, tgfx_timer, 0); 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci for (i = 0; i < n_devs; i++) { 1908c2ecf20Sopenharmony_ci if (n_buttons[i] < 1) 1918c2ecf20Sopenharmony_ci continue; 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci if (n_buttons[i] > ARRAY_SIZE(tgfx_buttons)) { 1948c2ecf20Sopenharmony_ci printk(KERN_ERR "turbografx.c: Invalid number of buttons %d\n", n_buttons[i]); 1958c2ecf20Sopenharmony_ci goto err_unreg_devs; 1968c2ecf20Sopenharmony_ci } 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci tgfx->dev[i] = input_dev = input_allocate_device(); 1998c2ecf20Sopenharmony_ci if (!input_dev) { 2008c2ecf20Sopenharmony_ci printk(KERN_ERR "turbografx.c: Not enough memory for input device\n"); 2018c2ecf20Sopenharmony_ci goto err_unreg_devs; 2028c2ecf20Sopenharmony_ci } 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci tgfx->sticks |= (1 << i); 2058c2ecf20Sopenharmony_ci snprintf(tgfx->name[i], sizeof(tgfx->name[i]), 2068c2ecf20Sopenharmony_ci "TurboGraFX %d-button Multisystem joystick", n_buttons[i]); 2078c2ecf20Sopenharmony_ci snprintf(tgfx->phys[i], sizeof(tgfx->phys[i]), 2088c2ecf20Sopenharmony_ci "%s/input%d", tgfx->pd->port->name, i); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci input_dev->name = tgfx->name[i]; 2118c2ecf20Sopenharmony_ci input_dev->phys = tgfx->phys[i]; 2128c2ecf20Sopenharmony_ci input_dev->id.bustype = BUS_PARPORT; 2138c2ecf20Sopenharmony_ci input_dev->id.vendor = 0x0003; 2148c2ecf20Sopenharmony_ci input_dev->id.product = n_buttons[i]; 2158c2ecf20Sopenharmony_ci input_dev->id.version = 0x0100; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci input_set_drvdata(input_dev, tgfx); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci input_dev->open = tgfx_open; 2208c2ecf20Sopenharmony_ci input_dev->close = tgfx_close; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); 2238c2ecf20Sopenharmony_ci input_set_abs_params(input_dev, ABS_X, -1, 1, 0, 0); 2248c2ecf20Sopenharmony_ci input_set_abs_params(input_dev, ABS_Y, -1, 1, 0, 0); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci for (j = 0; j < n_buttons[i]; j++) 2278c2ecf20Sopenharmony_ci set_bit(tgfx_buttons[j], input_dev->keybit); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci if (input_register_device(tgfx->dev[i])) 2308c2ecf20Sopenharmony_ci goto err_free_dev; 2318c2ecf20Sopenharmony_ci } 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci if (!tgfx->sticks) { 2348c2ecf20Sopenharmony_ci printk(KERN_ERR "turbografx.c: No valid devices specified\n"); 2358c2ecf20Sopenharmony_ci goto err_free_tgfx; 2368c2ecf20Sopenharmony_ci } 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci tgfx_base[port_idx] = tgfx; 2398c2ecf20Sopenharmony_ci return; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci err_free_dev: 2428c2ecf20Sopenharmony_ci input_free_device(tgfx->dev[i]); 2438c2ecf20Sopenharmony_ci err_unreg_devs: 2448c2ecf20Sopenharmony_ci while (--i >= 0) 2458c2ecf20Sopenharmony_ci if (tgfx->dev[i]) 2468c2ecf20Sopenharmony_ci input_unregister_device(tgfx->dev[i]); 2478c2ecf20Sopenharmony_ci err_free_tgfx: 2488c2ecf20Sopenharmony_ci kfree(tgfx); 2498c2ecf20Sopenharmony_ci err_unreg_pardev: 2508c2ecf20Sopenharmony_ci parport_unregister_device(pd); 2518c2ecf20Sopenharmony_ci} 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_cistatic void tgfx_detach(struct parport *port) 2548c2ecf20Sopenharmony_ci{ 2558c2ecf20Sopenharmony_ci int i; 2568c2ecf20Sopenharmony_ci struct tgfx *tgfx; 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci for (i = 0; i < TGFX_MAX_PORTS; i++) { 2598c2ecf20Sopenharmony_ci if (tgfx_base[i] && tgfx_base[i]->parportno == port->number) 2608c2ecf20Sopenharmony_ci break; 2618c2ecf20Sopenharmony_ci } 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci if (i == TGFX_MAX_PORTS) 2648c2ecf20Sopenharmony_ci return; 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci tgfx = tgfx_base[i]; 2678c2ecf20Sopenharmony_ci tgfx_base[i] = NULL; 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci for (i = 0; i < TGFX_MAX_DEVICES; i++) 2708c2ecf20Sopenharmony_ci if (tgfx->dev[i]) 2718c2ecf20Sopenharmony_ci input_unregister_device(tgfx->dev[i]); 2728c2ecf20Sopenharmony_ci parport_unregister_device(tgfx->pd); 2738c2ecf20Sopenharmony_ci kfree(tgfx); 2748c2ecf20Sopenharmony_ci} 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_cistatic struct parport_driver tgfx_parport_driver = { 2778c2ecf20Sopenharmony_ci .name = "turbografx", 2788c2ecf20Sopenharmony_ci .match_port = tgfx_attach, 2798c2ecf20Sopenharmony_ci .detach = tgfx_detach, 2808c2ecf20Sopenharmony_ci .devmodel = true, 2818c2ecf20Sopenharmony_ci}; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_cistatic int __init tgfx_init(void) 2848c2ecf20Sopenharmony_ci{ 2858c2ecf20Sopenharmony_ci int i; 2868c2ecf20Sopenharmony_ci int have_dev = 0; 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci for (i = 0; i < TGFX_MAX_PORTS; i++) { 2898c2ecf20Sopenharmony_ci if (tgfx_cfg[i].nargs == 0 || tgfx_cfg[i].args[0] < 0) 2908c2ecf20Sopenharmony_ci continue; 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci if (tgfx_cfg[i].nargs < 2) { 2938c2ecf20Sopenharmony_ci printk(KERN_ERR "turbografx.c: at least one joystick must be specified\n"); 2948c2ecf20Sopenharmony_ci return -EINVAL; 2958c2ecf20Sopenharmony_ci } 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci have_dev = 1; 2988c2ecf20Sopenharmony_ci } 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci if (!have_dev) 3018c2ecf20Sopenharmony_ci return -ENODEV; 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci return parport_register_driver(&tgfx_parport_driver); 3048c2ecf20Sopenharmony_ci} 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_cistatic void __exit tgfx_exit(void) 3078c2ecf20Sopenharmony_ci{ 3088c2ecf20Sopenharmony_ci parport_unregister_driver(&tgfx_parport_driver); 3098c2ecf20Sopenharmony_ci} 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_cimodule_init(tgfx_init); 3128c2ecf20Sopenharmony_cimodule_exit(tgfx_exit); 313