18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 1999-2001 Vojtech Pavlik 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci/* 78c2ecf20Sopenharmony_ci * Logitech WingMan Warrior joystick driver for Linux 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci/* 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/kernel.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/slab.h> 168c2ecf20Sopenharmony_ci#include <linux/input.h> 178c2ecf20Sopenharmony_ci#include <linux/serio.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#define DRIVER_DESC "Logitech WingMan Warrior joystick driver" 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); 228c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC); 238c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci/* 268c2ecf20Sopenharmony_ci * Constants. 278c2ecf20Sopenharmony_ci */ 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#define WARRIOR_MAX_LENGTH 16 308c2ecf20Sopenharmony_cistatic char warrior_lengths[] = { 0, 4, 12, 3, 4, 4, 0, 0 }; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci/* 338c2ecf20Sopenharmony_ci * Per-Warrior data. 348c2ecf20Sopenharmony_ci */ 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistruct warrior { 378c2ecf20Sopenharmony_ci struct input_dev *dev; 388c2ecf20Sopenharmony_ci int idx, len; 398c2ecf20Sopenharmony_ci unsigned char data[WARRIOR_MAX_LENGTH]; 408c2ecf20Sopenharmony_ci char phys[32]; 418c2ecf20Sopenharmony_ci}; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci/* 448c2ecf20Sopenharmony_ci * warrior_process_packet() decodes packets the driver receives from the 458c2ecf20Sopenharmony_ci * Warrior. It updates the data accordingly. 468c2ecf20Sopenharmony_ci */ 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic void warrior_process_packet(struct warrior *warrior) 498c2ecf20Sopenharmony_ci{ 508c2ecf20Sopenharmony_ci struct input_dev *dev = warrior->dev; 518c2ecf20Sopenharmony_ci unsigned char *data = warrior->data; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci if (!warrior->idx) return; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci switch ((data[0] >> 4) & 7) { 568c2ecf20Sopenharmony_ci case 1: /* Button data */ 578c2ecf20Sopenharmony_ci input_report_key(dev, BTN_TRIGGER, data[3] & 1); 588c2ecf20Sopenharmony_ci input_report_key(dev, BTN_THUMB, (data[3] >> 1) & 1); 598c2ecf20Sopenharmony_ci input_report_key(dev, BTN_TOP, (data[3] >> 2) & 1); 608c2ecf20Sopenharmony_ci input_report_key(dev, BTN_TOP2, (data[3] >> 3) & 1); 618c2ecf20Sopenharmony_ci break; 628c2ecf20Sopenharmony_ci case 3: /* XY-axis info->data */ 638c2ecf20Sopenharmony_ci input_report_abs(dev, ABS_X, ((data[0] & 8) << 5) - (data[2] | ((data[0] & 4) << 5))); 648c2ecf20Sopenharmony_ci input_report_abs(dev, ABS_Y, (data[1] | ((data[0] & 1) << 7)) - ((data[0] & 2) << 7)); 658c2ecf20Sopenharmony_ci break; 668c2ecf20Sopenharmony_ci case 5: /* Throttle, spinner, hat info->data */ 678c2ecf20Sopenharmony_ci input_report_abs(dev, ABS_THROTTLE, (data[1] | ((data[0] & 1) << 7)) - ((data[0] & 2) << 7)); 688c2ecf20Sopenharmony_ci input_report_abs(dev, ABS_HAT0X, (data[3] & 2 ? 1 : 0) - (data[3] & 1 ? 1 : 0)); 698c2ecf20Sopenharmony_ci input_report_abs(dev, ABS_HAT0Y, (data[3] & 8 ? 1 : 0) - (data[3] & 4 ? 1 : 0)); 708c2ecf20Sopenharmony_ci input_report_rel(dev, REL_DIAL, (data[2] | ((data[0] & 4) << 5)) - ((data[0] & 8) << 5)); 718c2ecf20Sopenharmony_ci break; 728c2ecf20Sopenharmony_ci } 738c2ecf20Sopenharmony_ci input_sync(dev); 748c2ecf20Sopenharmony_ci} 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci/* 778c2ecf20Sopenharmony_ci * warrior_interrupt() is called by the low level driver when characters 788c2ecf20Sopenharmony_ci * are ready for us. We then buffer them for further processing, or call the 798c2ecf20Sopenharmony_ci * packet processing routine. 808c2ecf20Sopenharmony_ci */ 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic irqreturn_t warrior_interrupt(struct serio *serio, 838c2ecf20Sopenharmony_ci unsigned char data, unsigned int flags) 848c2ecf20Sopenharmony_ci{ 858c2ecf20Sopenharmony_ci struct warrior *warrior = serio_get_drvdata(serio); 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci if (data & 0x80) { 888c2ecf20Sopenharmony_ci if (warrior->idx) warrior_process_packet(warrior); 898c2ecf20Sopenharmony_ci warrior->idx = 0; 908c2ecf20Sopenharmony_ci warrior->len = warrior_lengths[(data >> 4) & 7]; 918c2ecf20Sopenharmony_ci } 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci if (warrior->idx < warrior->len) 948c2ecf20Sopenharmony_ci warrior->data[warrior->idx++] = data; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci if (warrior->idx == warrior->len) { 978c2ecf20Sopenharmony_ci if (warrior->idx) warrior_process_packet(warrior); 988c2ecf20Sopenharmony_ci warrior->idx = 0; 998c2ecf20Sopenharmony_ci warrior->len = 0; 1008c2ecf20Sopenharmony_ci } 1018c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci/* 1058c2ecf20Sopenharmony_ci * warrior_disconnect() is the opposite of warrior_connect() 1068c2ecf20Sopenharmony_ci */ 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic void warrior_disconnect(struct serio *serio) 1098c2ecf20Sopenharmony_ci{ 1108c2ecf20Sopenharmony_ci struct warrior *warrior = serio_get_drvdata(serio); 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci serio_close(serio); 1138c2ecf20Sopenharmony_ci serio_set_drvdata(serio, NULL); 1148c2ecf20Sopenharmony_ci input_unregister_device(warrior->dev); 1158c2ecf20Sopenharmony_ci kfree(warrior); 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci/* 1198c2ecf20Sopenharmony_ci * warrior_connect() is the routine that is called when someone adds a 1208c2ecf20Sopenharmony_ci * new serio device. It looks for the Warrior, and if found, registers 1218c2ecf20Sopenharmony_ci * it as an input device. 1228c2ecf20Sopenharmony_ci */ 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_cistatic int warrior_connect(struct serio *serio, struct serio_driver *drv) 1258c2ecf20Sopenharmony_ci{ 1268c2ecf20Sopenharmony_ci struct warrior *warrior; 1278c2ecf20Sopenharmony_ci struct input_dev *input_dev; 1288c2ecf20Sopenharmony_ci int err = -ENOMEM; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci warrior = kzalloc(sizeof(struct warrior), GFP_KERNEL); 1318c2ecf20Sopenharmony_ci input_dev = input_allocate_device(); 1328c2ecf20Sopenharmony_ci if (!warrior || !input_dev) 1338c2ecf20Sopenharmony_ci goto fail1; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci warrior->dev = input_dev; 1368c2ecf20Sopenharmony_ci snprintf(warrior->phys, sizeof(warrior->phys), "%s/input0", serio->phys); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci input_dev->name = "Logitech WingMan Warrior"; 1398c2ecf20Sopenharmony_ci input_dev->phys = warrior->phys; 1408c2ecf20Sopenharmony_ci input_dev->id.bustype = BUS_RS232; 1418c2ecf20Sopenharmony_ci input_dev->id.vendor = SERIO_WARRIOR; 1428c2ecf20Sopenharmony_ci input_dev->id.product = 0x0001; 1438c2ecf20Sopenharmony_ci input_dev->id.version = 0x0100; 1448c2ecf20Sopenharmony_ci input_dev->dev.parent = &serio->dev; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) | 1478c2ecf20Sopenharmony_ci BIT_MASK(EV_ABS); 1488c2ecf20Sopenharmony_ci input_dev->keybit[BIT_WORD(BTN_TRIGGER)] = BIT_MASK(BTN_TRIGGER) | 1498c2ecf20Sopenharmony_ci BIT_MASK(BTN_THUMB) | BIT_MASK(BTN_TOP) | BIT_MASK(BTN_TOP2); 1508c2ecf20Sopenharmony_ci input_dev->relbit[0] = BIT_MASK(REL_DIAL); 1518c2ecf20Sopenharmony_ci input_set_abs_params(input_dev, ABS_X, -64, 64, 0, 8); 1528c2ecf20Sopenharmony_ci input_set_abs_params(input_dev, ABS_Y, -64, 64, 0, 8); 1538c2ecf20Sopenharmony_ci input_set_abs_params(input_dev, ABS_THROTTLE, -112, 112, 0, 0); 1548c2ecf20Sopenharmony_ci input_set_abs_params(input_dev, ABS_HAT0X, -1, 1, 0, 0); 1558c2ecf20Sopenharmony_ci input_set_abs_params(input_dev, ABS_HAT0Y, -1, 1, 0, 0); 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci serio_set_drvdata(serio, warrior); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci err = serio_open(serio, drv); 1608c2ecf20Sopenharmony_ci if (err) 1618c2ecf20Sopenharmony_ci goto fail2; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci err = input_register_device(warrior->dev); 1648c2ecf20Sopenharmony_ci if (err) 1658c2ecf20Sopenharmony_ci goto fail3; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci return 0; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci fail3: serio_close(serio); 1708c2ecf20Sopenharmony_ci fail2: serio_set_drvdata(serio, NULL); 1718c2ecf20Sopenharmony_ci fail1: input_free_device(input_dev); 1728c2ecf20Sopenharmony_ci kfree(warrior); 1738c2ecf20Sopenharmony_ci return err; 1748c2ecf20Sopenharmony_ci} 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci/* 1778c2ecf20Sopenharmony_ci * The serio driver structure. 1788c2ecf20Sopenharmony_ci */ 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_cistatic const struct serio_device_id warrior_serio_ids[] = { 1818c2ecf20Sopenharmony_ci { 1828c2ecf20Sopenharmony_ci .type = SERIO_RS232, 1838c2ecf20Sopenharmony_ci .proto = SERIO_WARRIOR, 1848c2ecf20Sopenharmony_ci .id = SERIO_ANY, 1858c2ecf20Sopenharmony_ci .extra = SERIO_ANY, 1868c2ecf20Sopenharmony_ci }, 1878c2ecf20Sopenharmony_ci { 0 } 1888c2ecf20Sopenharmony_ci}; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(serio, warrior_serio_ids); 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_cistatic struct serio_driver warrior_drv = { 1938c2ecf20Sopenharmony_ci .driver = { 1948c2ecf20Sopenharmony_ci .name = "warrior", 1958c2ecf20Sopenharmony_ci }, 1968c2ecf20Sopenharmony_ci .description = DRIVER_DESC, 1978c2ecf20Sopenharmony_ci .id_table = warrior_serio_ids, 1988c2ecf20Sopenharmony_ci .interrupt = warrior_interrupt, 1998c2ecf20Sopenharmony_ci .connect = warrior_connect, 2008c2ecf20Sopenharmony_ci .disconnect = warrior_disconnect, 2018c2ecf20Sopenharmony_ci}; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_cimodule_serio_driver(warrior_drv); 204