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 */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci/* 108c2ecf20Sopenharmony_ci * SpaceTec SpaceOrb 360 and Avenger 6dof controller driver for Linux 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci/* 148c2ecf20Sopenharmony_ci */ 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include <linux/kernel.h> 178c2ecf20Sopenharmony_ci#include <linux/slab.h> 188c2ecf20Sopenharmony_ci#include <linux/module.h> 198c2ecf20Sopenharmony_ci#include <linux/input.h> 208c2ecf20Sopenharmony_ci#include <linux/serio.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#define DRIVER_DESC "SpaceTec SpaceOrb 360 and Avenger 6dof controller driver" 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); 258c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC); 268c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci/* 298c2ecf20Sopenharmony_ci * Constants. 308c2ecf20Sopenharmony_ci */ 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define SPACEORB_MAX_LENGTH 64 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistatic int spaceorb_buttons[] = { BTN_TL, BTN_TR, BTN_Y, BTN_X, BTN_B, BTN_A }; 358c2ecf20Sopenharmony_cistatic int spaceorb_axes[] = { ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ }; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci/* 388c2ecf20Sopenharmony_ci * Per-Orb data. 398c2ecf20Sopenharmony_ci */ 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistruct spaceorb { 428c2ecf20Sopenharmony_ci struct input_dev *dev; 438c2ecf20Sopenharmony_ci int idx; 448c2ecf20Sopenharmony_ci unsigned char data[SPACEORB_MAX_LENGTH]; 458c2ecf20Sopenharmony_ci char phys[32]; 468c2ecf20Sopenharmony_ci}; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic unsigned char spaceorb_xor[] = "SpaceWare"; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cistatic unsigned char *spaceorb_errors[] = { "EEPROM storing 0 failed", "Receive queue overflow", "Transmit queue timeout", 518c2ecf20Sopenharmony_ci "Bad packet", "Power brown-out", "EEPROM checksum error", "Hardware fault" }; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci/* 548c2ecf20Sopenharmony_ci * spaceorb_process_packet() decodes packets the driver receives from the 558c2ecf20Sopenharmony_ci * SpaceOrb. 568c2ecf20Sopenharmony_ci */ 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistatic void spaceorb_process_packet(struct spaceorb *spaceorb) 598c2ecf20Sopenharmony_ci{ 608c2ecf20Sopenharmony_ci struct input_dev *dev = spaceorb->dev; 618c2ecf20Sopenharmony_ci unsigned char *data = spaceorb->data; 628c2ecf20Sopenharmony_ci unsigned char c = 0; 638c2ecf20Sopenharmony_ci int axes[6]; 648c2ecf20Sopenharmony_ci int i; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci if (spaceorb->idx < 2) return; 678c2ecf20Sopenharmony_ci for (i = 0; i < spaceorb->idx; i++) c ^= data[i]; 688c2ecf20Sopenharmony_ci if (c) return; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci switch (data[0]) { 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci case 'R': /* Reset packet */ 738c2ecf20Sopenharmony_ci spaceorb->data[spaceorb->idx - 1] = 0; 748c2ecf20Sopenharmony_ci for (i = 1; i < spaceorb->idx && spaceorb->data[i] == ' '; i++); 758c2ecf20Sopenharmony_ci printk(KERN_INFO "input: %s [%s] is %s\n", 768c2ecf20Sopenharmony_ci dev->name, spaceorb->data + i, spaceorb->phys); 778c2ecf20Sopenharmony_ci break; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci case 'D': /* Ball + button data */ 808c2ecf20Sopenharmony_ci if (spaceorb->idx != 12) return; 818c2ecf20Sopenharmony_ci for (i = 0; i < 9; i++) spaceorb->data[i+2] ^= spaceorb_xor[i]; 828c2ecf20Sopenharmony_ci axes[0] = ( data[2] << 3) | (data[ 3] >> 4); 838c2ecf20Sopenharmony_ci axes[1] = ((data[3] & 0x0f) << 6) | (data[ 4] >> 1); 848c2ecf20Sopenharmony_ci axes[2] = ((data[4] & 0x01) << 9) | (data[ 5] << 2) | (data[4] >> 5); 858c2ecf20Sopenharmony_ci axes[3] = ((data[6] & 0x1f) << 5) | (data[ 7] >> 2); 868c2ecf20Sopenharmony_ci axes[4] = ((data[7] & 0x03) << 8) | (data[ 8] << 1) | (data[7] >> 6); 878c2ecf20Sopenharmony_ci axes[5] = ((data[9] & 0x3f) << 4) | (data[10] >> 3); 888c2ecf20Sopenharmony_ci for (i = 0; i < 6; i++) 898c2ecf20Sopenharmony_ci input_report_abs(dev, spaceorb_axes[i], axes[i] - ((axes[i] & 0x200) ? 1024 : 0)); 908c2ecf20Sopenharmony_ci for (i = 0; i < 6; i++) 918c2ecf20Sopenharmony_ci input_report_key(dev, spaceorb_buttons[i], (data[1] >> i) & 1); 928c2ecf20Sopenharmony_ci break; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci case 'K': /* Button data */ 958c2ecf20Sopenharmony_ci if (spaceorb->idx != 5) return; 968c2ecf20Sopenharmony_ci for (i = 0; i < 6; i++) 978c2ecf20Sopenharmony_ci input_report_key(dev, spaceorb_buttons[i], (data[2] >> i) & 1); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci break; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci case 'E': /* Error packet */ 1028c2ecf20Sopenharmony_ci if (spaceorb->idx != 4) return; 1038c2ecf20Sopenharmony_ci printk(KERN_ERR "spaceorb: Device error. [ "); 1048c2ecf20Sopenharmony_ci for (i = 0; i < 7; i++) if (data[1] & (1 << i)) printk("%s ", spaceorb_errors[i]); 1058c2ecf20Sopenharmony_ci printk("]\n"); 1068c2ecf20Sopenharmony_ci break; 1078c2ecf20Sopenharmony_ci } 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci input_sync(dev); 1108c2ecf20Sopenharmony_ci} 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_cistatic irqreturn_t spaceorb_interrupt(struct serio *serio, 1138c2ecf20Sopenharmony_ci unsigned char data, unsigned int flags) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci struct spaceorb* spaceorb = serio_get_drvdata(serio); 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci if (~data & 0x80) { 1188c2ecf20Sopenharmony_ci if (spaceorb->idx) spaceorb_process_packet(spaceorb); 1198c2ecf20Sopenharmony_ci spaceorb->idx = 0; 1208c2ecf20Sopenharmony_ci } 1218c2ecf20Sopenharmony_ci if (spaceorb->idx < SPACEORB_MAX_LENGTH) 1228c2ecf20Sopenharmony_ci spaceorb->data[spaceorb->idx++] = data & 0x7f; 1238c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci/* 1278c2ecf20Sopenharmony_ci * spaceorb_disconnect() is the opposite of spaceorb_connect() 1288c2ecf20Sopenharmony_ci */ 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_cistatic void spaceorb_disconnect(struct serio *serio) 1318c2ecf20Sopenharmony_ci{ 1328c2ecf20Sopenharmony_ci struct spaceorb* spaceorb = serio_get_drvdata(serio); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci serio_close(serio); 1358c2ecf20Sopenharmony_ci serio_set_drvdata(serio, NULL); 1368c2ecf20Sopenharmony_ci input_unregister_device(spaceorb->dev); 1378c2ecf20Sopenharmony_ci kfree(spaceorb); 1388c2ecf20Sopenharmony_ci} 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci/* 1418c2ecf20Sopenharmony_ci * spaceorb_connect() is the routine that is called when someone adds a 1428c2ecf20Sopenharmony_ci * new serio device that supports SpaceOrb/Avenger protocol and registers 1438c2ecf20Sopenharmony_ci * it as an input device. 1448c2ecf20Sopenharmony_ci */ 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_cistatic int spaceorb_connect(struct serio *serio, struct serio_driver *drv) 1478c2ecf20Sopenharmony_ci{ 1488c2ecf20Sopenharmony_ci struct spaceorb *spaceorb; 1498c2ecf20Sopenharmony_ci struct input_dev *input_dev; 1508c2ecf20Sopenharmony_ci int err = -ENOMEM; 1518c2ecf20Sopenharmony_ci int i; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci spaceorb = kzalloc(sizeof(struct spaceorb), GFP_KERNEL); 1548c2ecf20Sopenharmony_ci input_dev = input_allocate_device(); 1558c2ecf20Sopenharmony_ci if (!spaceorb || !input_dev) 1568c2ecf20Sopenharmony_ci goto fail1; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci spaceorb->dev = input_dev; 1598c2ecf20Sopenharmony_ci snprintf(spaceorb->phys, sizeof(spaceorb->phys), "%s/input0", serio->phys); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci input_dev->name = "SpaceTec SpaceOrb 360 / Avenger"; 1628c2ecf20Sopenharmony_ci input_dev->phys = spaceorb->phys; 1638c2ecf20Sopenharmony_ci input_dev->id.bustype = BUS_RS232; 1648c2ecf20Sopenharmony_ci input_dev->id.vendor = SERIO_SPACEORB; 1658c2ecf20Sopenharmony_ci input_dev->id.product = 0x0001; 1668c2ecf20Sopenharmony_ci input_dev->id.version = 0x0100; 1678c2ecf20Sopenharmony_ci input_dev->dev.parent = &serio->dev; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci for (i = 0; i < 6; i++) 1728c2ecf20Sopenharmony_ci set_bit(spaceorb_buttons[i], input_dev->keybit); 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci for (i = 0; i < 6; i++) 1758c2ecf20Sopenharmony_ci input_set_abs_params(input_dev, spaceorb_axes[i], -508, 508, 0, 0); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci serio_set_drvdata(serio, spaceorb); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci err = serio_open(serio, drv); 1808c2ecf20Sopenharmony_ci if (err) 1818c2ecf20Sopenharmony_ci goto fail2; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci err = input_register_device(spaceorb->dev); 1848c2ecf20Sopenharmony_ci if (err) 1858c2ecf20Sopenharmony_ci goto fail3; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci return 0; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci fail3: serio_close(serio); 1908c2ecf20Sopenharmony_ci fail2: serio_set_drvdata(serio, NULL); 1918c2ecf20Sopenharmony_ci fail1: input_free_device(input_dev); 1928c2ecf20Sopenharmony_ci kfree(spaceorb); 1938c2ecf20Sopenharmony_ci return err; 1948c2ecf20Sopenharmony_ci} 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci/* 1978c2ecf20Sopenharmony_ci * The serio driver structure. 1988c2ecf20Sopenharmony_ci */ 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_cistatic const struct serio_device_id spaceorb_serio_ids[] = { 2018c2ecf20Sopenharmony_ci { 2028c2ecf20Sopenharmony_ci .type = SERIO_RS232, 2038c2ecf20Sopenharmony_ci .proto = SERIO_SPACEORB, 2048c2ecf20Sopenharmony_ci .id = SERIO_ANY, 2058c2ecf20Sopenharmony_ci .extra = SERIO_ANY, 2068c2ecf20Sopenharmony_ci }, 2078c2ecf20Sopenharmony_ci { 0 } 2088c2ecf20Sopenharmony_ci}; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(serio, spaceorb_serio_ids); 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_cistatic struct serio_driver spaceorb_drv = { 2138c2ecf20Sopenharmony_ci .driver = { 2148c2ecf20Sopenharmony_ci .name = "spaceorb", 2158c2ecf20Sopenharmony_ci }, 2168c2ecf20Sopenharmony_ci .description = DRIVER_DESC, 2178c2ecf20Sopenharmony_ci .id_table = spaceorb_serio_ids, 2188c2ecf20Sopenharmony_ci .interrupt = spaceorb_interrupt, 2198c2ecf20Sopenharmony_ci .connect = spaceorb_connect, 2208c2ecf20Sopenharmony_ci .disconnect = spaceorb_disconnect, 2218c2ecf20Sopenharmony_ci}; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_cimodule_serio_driver(spaceorb_drv); 224