18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 1999-2001 Vojtech Pavlik 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Based on the work of: 68c2ecf20Sopenharmony_ci * David Thompson 78c2ecf20Sopenharmony_ci * Joseph Krahn 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci/* 118c2ecf20Sopenharmony_ci * SpaceTec SpaceBall 2003/3003/4000 FLX driver for Linux 128c2ecf20Sopenharmony_ci */ 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci/* 158c2ecf20Sopenharmony_ci */ 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#include <linux/kernel.h> 188c2ecf20Sopenharmony_ci#include <linux/slab.h> 198c2ecf20Sopenharmony_ci#include <linux/module.h> 208c2ecf20Sopenharmony_ci#include <linux/input.h> 218c2ecf20Sopenharmony_ci#include <linux/serio.h> 228c2ecf20Sopenharmony_ci#include <asm/unaligned.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#define DRIVER_DESC "SpaceTec SpaceBall 2003/3003/4000 FLX driver" 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); 278c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC); 288c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/* 318c2ecf20Sopenharmony_ci * Constants. 328c2ecf20Sopenharmony_ci */ 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci#define SPACEBALL_MAX_LENGTH 128 358c2ecf20Sopenharmony_ci#define SPACEBALL_MAX_ID 9 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci#define SPACEBALL_1003 1 388c2ecf20Sopenharmony_ci#define SPACEBALL_2003B 3 398c2ecf20Sopenharmony_ci#define SPACEBALL_2003C 4 408c2ecf20Sopenharmony_ci#define SPACEBALL_3003C 7 418c2ecf20Sopenharmony_ci#define SPACEBALL_4000FLX 8 428c2ecf20Sopenharmony_ci#define SPACEBALL_4000FLX_L 9 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistatic int spaceball_axes[] = { ABS_X, ABS_Z, ABS_Y, ABS_RX, ABS_RZ, ABS_RY }; 458c2ecf20Sopenharmony_cistatic char *spaceball_names[] = { 468c2ecf20Sopenharmony_ci "?", "SpaceTec SpaceBall 1003", "SpaceTec SpaceBall 2003", "SpaceTec SpaceBall 2003B", 478c2ecf20Sopenharmony_ci "SpaceTec SpaceBall 2003C", "SpaceTec SpaceBall 3003", "SpaceTec SpaceBall SpaceController", 488c2ecf20Sopenharmony_ci "SpaceTec SpaceBall 3003C", "SpaceTec SpaceBall 4000FLX", "SpaceTec SpaceBall 4000FLX Lefty" }; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci/* 518c2ecf20Sopenharmony_ci * Per-Ball data. 528c2ecf20Sopenharmony_ci */ 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cistruct spaceball { 558c2ecf20Sopenharmony_ci struct input_dev *dev; 568c2ecf20Sopenharmony_ci int idx; 578c2ecf20Sopenharmony_ci int escape; 588c2ecf20Sopenharmony_ci unsigned char data[SPACEBALL_MAX_LENGTH]; 598c2ecf20Sopenharmony_ci char phys[32]; 608c2ecf20Sopenharmony_ci}; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci/* 638c2ecf20Sopenharmony_ci * spaceball_process_packet() decodes packets the driver receives from the 648c2ecf20Sopenharmony_ci * SpaceBall. 658c2ecf20Sopenharmony_ci */ 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cistatic void spaceball_process_packet(struct spaceball* spaceball) 688c2ecf20Sopenharmony_ci{ 698c2ecf20Sopenharmony_ci struct input_dev *dev = spaceball->dev; 708c2ecf20Sopenharmony_ci unsigned char *data = spaceball->data; 718c2ecf20Sopenharmony_ci int i; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci if (spaceball->idx < 2) return; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci switch (spaceball->data[0]) { 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci case 'D': /* Ball data */ 788c2ecf20Sopenharmony_ci if (spaceball->idx != 15) return; 798c2ecf20Sopenharmony_ci /* 808c2ecf20Sopenharmony_ci * Skip first three bytes; read six axes worth of data. 818c2ecf20Sopenharmony_ci * Axis values are signed 16-bit big-endian. 828c2ecf20Sopenharmony_ci */ 838c2ecf20Sopenharmony_ci data += 3; 848c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(spaceball_axes); i++) { 858c2ecf20Sopenharmony_ci input_report_abs(dev, spaceball_axes[i], 868c2ecf20Sopenharmony_ci (__s16)get_unaligned_be16(&data[i * 2])); 878c2ecf20Sopenharmony_ci } 888c2ecf20Sopenharmony_ci break; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci case 'K': /* Button data */ 918c2ecf20Sopenharmony_ci if (spaceball->idx != 3) return; 928c2ecf20Sopenharmony_ci input_report_key(dev, BTN_1, (data[2] & 0x01) || (data[2] & 0x20)); 938c2ecf20Sopenharmony_ci input_report_key(dev, BTN_2, data[2] & 0x02); 948c2ecf20Sopenharmony_ci input_report_key(dev, BTN_3, data[2] & 0x04); 958c2ecf20Sopenharmony_ci input_report_key(dev, BTN_4, data[2] & 0x08); 968c2ecf20Sopenharmony_ci input_report_key(dev, BTN_5, data[1] & 0x01); 978c2ecf20Sopenharmony_ci input_report_key(dev, BTN_6, data[1] & 0x02); 988c2ecf20Sopenharmony_ci input_report_key(dev, BTN_7, data[1] & 0x04); 998c2ecf20Sopenharmony_ci input_report_key(dev, BTN_8, data[1] & 0x10); 1008c2ecf20Sopenharmony_ci break; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci case '.': /* Advanced button data */ 1038c2ecf20Sopenharmony_ci if (spaceball->idx != 3) return; 1048c2ecf20Sopenharmony_ci input_report_key(dev, BTN_1, data[2] & 0x01); 1058c2ecf20Sopenharmony_ci input_report_key(dev, BTN_2, data[2] & 0x02); 1068c2ecf20Sopenharmony_ci input_report_key(dev, BTN_3, data[2] & 0x04); 1078c2ecf20Sopenharmony_ci input_report_key(dev, BTN_4, data[2] & 0x08); 1088c2ecf20Sopenharmony_ci input_report_key(dev, BTN_5, data[2] & 0x10); 1098c2ecf20Sopenharmony_ci input_report_key(dev, BTN_6, data[2] & 0x20); 1108c2ecf20Sopenharmony_ci input_report_key(dev, BTN_7, data[2] & 0x80); 1118c2ecf20Sopenharmony_ci input_report_key(dev, BTN_8, data[1] & 0x01); 1128c2ecf20Sopenharmony_ci input_report_key(dev, BTN_9, data[1] & 0x02); 1138c2ecf20Sopenharmony_ci input_report_key(dev, BTN_A, data[1] & 0x04); 1148c2ecf20Sopenharmony_ci input_report_key(dev, BTN_B, data[1] & 0x08); 1158c2ecf20Sopenharmony_ci input_report_key(dev, BTN_C, data[1] & 0x10); 1168c2ecf20Sopenharmony_ci input_report_key(dev, BTN_MODE, data[1] & 0x20); 1178c2ecf20Sopenharmony_ci break; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci case 'E': /* Device error */ 1208c2ecf20Sopenharmony_ci spaceball->data[spaceball->idx - 1] = 0; 1218c2ecf20Sopenharmony_ci printk(KERN_ERR "spaceball: Device error. [%s]\n", spaceball->data + 1); 1228c2ecf20Sopenharmony_ci break; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci case '?': /* Bad command packet */ 1258c2ecf20Sopenharmony_ci spaceball->data[spaceball->idx - 1] = 0; 1268c2ecf20Sopenharmony_ci printk(KERN_ERR "spaceball: Bad command. [%s]\n", spaceball->data + 1); 1278c2ecf20Sopenharmony_ci break; 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci input_sync(dev); 1318c2ecf20Sopenharmony_ci} 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci/* 1348c2ecf20Sopenharmony_ci * Spaceball 4000 FLX packets all start with a one letter packet-type decriptor, 1358c2ecf20Sopenharmony_ci * and end in 0x0d. It uses '^' as an escape for CR, XOFF and XON characters which 1368c2ecf20Sopenharmony_ci * can occur in the axis values. 1378c2ecf20Sopenharmony_ci */ 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_cistatic irqreturn_t spaceball_interrupt(struct serio *serio, 1408c2ecf20Sopenharmony_ci unsigned char data, unsigned int flags) 1418c2ecf20Sopenharmony_ci{ 1428c2ecf20Sopenharmony_ci struct spaceball *spaceball = serio_get_drvdata(serio); 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci switch (data) { 1458c2ecf20Sopenharmony_ci case 0xd: 1468c2ecf20Sopenharmony_ci spaceball_process_packet(spaceball); 1478c2ecf20Sopenharmony_ci spaceball->idx = 0; 1488c2ecf20Sopenharmony_ci spaceball->escape = 0; 1498c2ecf20Sopenharmony_ci break; 1508c2ecf20Sopenharmony_ci case '^': 1518c2ecf20Sopenharmony_ci if (!spaceball->escape) { 1528c2ecf20Sopenharmony_ci spaceball->escape = 1; 1538c2ecf20Sopenharmony_ci break; 1548c2ecf20Sopenharmony_ci } 1558c2ecf20Sopenharmony_ci spaceball->escape = 0; 1568c2ecf20Sopenharmony_ci fallthrough; 1578c2ecf20Sopenharmony_ci case 'M': 1588c2ecf20Sopenharmony_ci case 'Q': 1598c2ecf20Sopenharmony_ci case 'S': 1608c2ecf20Sopenharmony_ci if (spaceball->escape) { 1618c2ecf20Sopenharmony_ci spaceball->escape = 0; 1628c2ecf20Sopenharmony_ci data &= 0x1f; 1638c2ecf20Sopenharmony_ci } 1648c2ecf20Sopenharmony_ci fallthrough; 1658c2ecf20Sopenharmony_ci default: 1668c2ecf20Sopenharmony_ci if (spaceball->escape) 1678c2ecf20Sopenharmony_ci spaceball->escape = 0; 1688c2ecf20Sopenharmony_ci if (spaceball->idx < SPACEBALL_MAX_LENGTH) 1698c2ecf20Sopenharmony_ci spaceball->data[spaceball->idx++] = data; 1708c2ecf20Sopenharmony_ci break; 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1738c2ecf20Sopenharmony_ci} 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci/* 1768c2ecf20Sopenharmony_ci * spaceball_disconnect() is the opposite of spaceball_connect() 1778c2ecf20Sopenharmony_ci */ 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_cistatic void spaceball_disconnect(struct serio *serio) 1808c2ecf20Sopenharmony_ci{ 1818c2ecf20Sopenharmony_ci struct spaceball* spaceball = serio_get_drvdata(serio); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci serio_close(serio); 1848c2ecf20Sopenharmony_ci serio_set_drvdata(serio, NULL); 1858c2ecf20Sopenharmony_ci input_unregister_device(spaceball->dev); 1868c2ecf20Sopenharmony_ci kfree(spaceball); 1878c2ecf20Sopenharmony_ci} 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci/* 1908c2ecf20Sopenharmony_ci * spaceball_connect() is the routine that is called when someone adds a 1918c2ecf20Sopenharmony_ci * new serio device that supports Spaceball protocol and registers it as 1928c2ecf20Sopenharmony_ci * an input device. 1938c2ecf20Sopenharmony_ci */ 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic int spaceball_connect(struct serio *serio, struct serio_driver *drv) 1968c2ecf20Sopenharmony_ci{ 1978c2ecf20Sopenharmony_ci struct spaceball *spaceball; 1988c2ecf20Sopenharmony_ci struct input_dev *input_dev; 1998c2ecf20Sopenharmony_ci int err = -ENOMEM; 2008c2ecf20Sopenharmony_ci int i, id; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci if ((id = serio->id.id) > SPACEBALL_MAX_ID) 2038c2ecf20Sopenharmony_ci return -ENODEV; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci spaceball = kmalloc(sizeof(struct spaceball), GFP_KERNEL); 2068c2ecf20Sopenharmony_ci input_dev = input_allocate_device(); 2078c2ecf20Sopenharmony_ci if (!spaceball || !input_dev) 2088c2ecf20Sopenharmony_ci goto fail1; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci spaceball->dev = input_dev; 2118c2ecf20Sopenharmony_ci snprintf(spaceball->phys, sizeof(spaceball->phys), "%s/input0", serio->phys); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci input_dev->name = spaceball_names[id]; 2148c2ecf20Sopenharmony_ci input_dev->phys = spaceball->phys; 2158c2ecf20Sopenharmony_ci input_dev->id.bustype = BUS_RS232; 2168c2ecf20Sopenharmony_ci input_dev->id.vendor = SERIO_SPACEBALL; 2178c2ecf20Sopenharmony_ci input_dev->id.product = id; 2188c2ecf20Sopenharmony_ci input_dev->id.version = 0x0100; 2198c2ecf20Sopenharmony_ci input_dev->dev.parent = &serio->dev; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci switch (id) { 2248c2ecf20Sopenharmony_ci case SPACEBALL_4000FLX: 2258c2ecf20Sopenharmony_ci case SPACEBALL_4000FLX_L: 2268c2ecf20Sopenharmony_ci input_dev->keybit[BIT_WORD(BTN_0)] |= BIT_MASK(BTN_9); 2278c2ecf20Sopenharmony_ci input_dev->keybit[BIT_WORD(BTN_A)] |= BIT_MASK(BTN_A) | 2288c2ecf20Sopenharmony_ci BIT_MASK(BTN_B) | BIT_MASK(BTN_C) | 2298c2ecf20Sopenharmony_ci BIT_MASK(BTN_MODE); 2308c2ecf20Sopenharmony_ci fallthrough; 2318c2ecf20Sopenharmony_ci default: 2328c2ecf20Sopenharmony_ci input_dev->keybit[BIT_WORD(BTN_0)] |= BIT_MASK(BTN_2) | 2338c2ecf20Sopenharmony_ci BIT_MASK(BTN_3) | BIT_MASK(BTN_4) | 2348c2ecf20Sopenharmony_ci BIT_MASK(BTN_5) | BIT_MASK(BTN_6) | 2358c2ecf20Sopenharmony_ci BIT_MASK(BTN_7) | BIT_MASK(BTN_8); 2368c2ecf20Sopenharmony_ci fallthrough; 2378c2ecf20Sopenharmony_ci case SPACEBALL_3003C: 2388c2ecf20Sopenharmony_ci input_dev->keybit[BIT_WORD(BTN_0)] |= BIT_MASK(BTN_1) | 2398c2ecf20Sopenharmony_ci BIT_MASK(BTN_8); 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci for (i = 0; i < 3; i++) { 2438c2ecf20Sopenharmony_ci input_set_abs_params(input_dev, ABS_X + i, -8000, 8000, 8, 40); 2448c2ecf20Sopenharmony_ci input_set_abs_params(input_dev, ABS_RX + i, -1600, 1600, 2, 8); 2458c2ecf20Sopenharmony_ci } 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci serio_set_drvdata(serio, spaceball); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci err = serio_open(serio, drv); 2508c2ecf20Sopenharmony_ci if (err) 2518c2ecf20Sopenharmony_ci goto fail2; 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci err = input_register_device(spaceball->dev); 2548c2ecf20Sopenharmony_ci if (err) 2558c2ecf20Sopenharmony_ci goto fail3; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci return 0; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci fail3: serio_close(serio); 2608c2ecf20Sopenharmony_ci fail2: serio_set_drvdata(serio, NULL); 2618c2ecf20Sopenharmony_ci fail1: input_free_device(input_dev); 2628c2ecf20Sopenharmony_ci kfree(spaceball); 2638c2ecf20Sopenharmony_ci return err; 2648c2ecf20Sopenharmony_ci} 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci/* 2678c2ecf20Sopenharmony_ci * The serio driver structure. 2688c2ecf20Sopenharmony_ci */ 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_cistatic const struct serio_device_id spaceball_serio_ids[] = { 2718c2ecf20Sopenharmony_ci { 2728c2ecf20Sopenharmony_ci .type = SERIO_RS232, 2738c2ecf20Sopenharmony_ci .proto = SERIO_SPACEBALL, 2748c2ecf20Sopenharmony_ci .id = SERIO_ANY, 2758c2ecf20Sopenharmony_ci .extra = SERIO_ANY, 2768c2ecf20Sopenharmony_ci }, 2778c2ecf20Sopenharmony_ci { 0 } 2788c2ecf20Sopenharmony_ci}; 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(serio, spaceball_serio_ids); 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_cistatic struct serio_driver spaceball_drv = { 2838c2ecf20Sopenharmony_ci .driver = { 2848c2ecf20Sopenharmony_ci .name = "spaceball", 2858c2ecf20Sopenharmony_ci }, 2868c2ecf20Sopenharmony_ci .description = DRIVER_DESC, 2878c2ecf20Sopenharmony_ci .id_table = spaceball_serio_ids, 2888c2ecf20Sopenharmony_ci .interrupt = spaceball_interrupt, 2898c2ecf20Sopenharmony_ci .connect = spaceball_connect, 2908c2ecf20Sopenharmony_ci .disconnect = spaceball_disconnect, 2918c2ecf20Sopenharmony_ci}; 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_cimodule_serio_driver(spaceball_drv); 294