18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 1998-2005 Vojtech Pavlik 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci/* 78c2ecf20Sopenharmony_ci * Logitech ADI joystick family driver for Linux 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci/* 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/delay.h> 148c2ecf20Sopenharmony_ci#include <linux/kernel.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/string.h> 178c2ecf20Sopenharmony_ci#include <linux/slab.h> 188c2ecf20Sopenharmony_ci#include <linux/input.h> 198c2ecf20Sopenharmony_ci#include <linux/gameport.h> 208c2ecf20Sopenharmony_ci#include <linux/jiffies.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#define DRIVER_DESC "Logitech ADI joystick family 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 * Times, array sizes, flags, ids. 308c2ecf20Sopenharmony_ci */ 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define ADI_MAX_START 200 /* Trigger to packet timeout [200us] */ 338c2ecf20Sopenharmony_ci#define ADI_MAX_STROBE 40 /* Single bit timeout [40us] */ 348c2ecf20Sopenharmony_ci#define ADI_INIT_DELAY 10 /* Delay after init packet [10ms] */ 358c2ecf20Sopenharmony_ci#define ADI_DATA_DELAY 4 /* Delay after data packet [4ms] */ 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci#define ADI_MAX_LENGTH 256 388c2ecf20Sopenharmony_ci#define ADI_MIN_LENGTH 8 398c2ecf20Sopenharmony_ci#define ADI_MIN_LEN_LENGTH 10 408c2ecf20Sopenharmony_ci#define ADI_MIN_ID_LENGTH 66 418c2ecf20Sopenharmony_ci#define ADI_MAX_NAME_LENGTH 64 428c2ecf20Sopenharmony_ci#define ADI_MAX_CNAME_LENGTH 16 438c2ecf20Sopenharmony_ci#define ADI_MAX_PHYS_LENGTH 64 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci#define ADI_FLAG_HAT 0x04 468c2ecf20Sopenharmony_ci#define ADI_FLAG_10BIT 0x08 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci#define ADI_ID_TPD 0x01 498c2ecf20Sopenharmony_ci#define ADI_ID_WGP 0x06 508c2ecf20Sopenharmony_ci#define ADI_ID_WGPE 0x08 518c2ecf20Sopenharmony_ci#define ADI_ID_MAX 0x0a 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci/* 548c2ecf20Sopenharmony_ci * Names, buttons, axes ... 558c2ecf20Sopenharmony_ci */ 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic char *adi_names[] = { "WingMan Extreme Digital", "ThunderPad Digital", "SideCar", "CyberMan 2", 588c2ecf20Sopenharmony_ci "WingMan Interceptor", "WingMan Formula", "WingMan GamePad", 598c2ecf20Sopenharmony_ci "WingMan Extreme Digital 3D", "WingMan GamePad Extreme", 608c2ecf20Sopenharmony_ci "WingMan GamePad USB", "Unknown Device %#x" }; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistatic char adi_wmgpe_abs[] = { ABS_X, ABS_Y, ABS_HAT0X, ABS_HAT0Y }; 638c2ecf20Sopenharmony_cistatic char adi_wmi_abs[] = { ABS_X, ABS_Y, ABS_THROTTLE, ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y, ABS_HAT2X, ABS_HAT2Y }; 648c2ecf20Sopenharmony_cistatic char adi_wmed3d_abs[] = { ABS_X, ABS_Y, ABS_THROTTLE, ABS_RZ, ABS_HAT0X, ABS_HAT0Y }; 658c2ecf20Sopenharmony_cistatic char adi_cm2_abs[] = { ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ }; 668c2ecf20Sopenharmony_cistatic char adi_wmf_abs[] = { ABS_WHEEL, ABS_GAS, ABS_BRAKE, ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y, ABS_HAT2X, ABS_HAT2Y }; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic short adi_wmgpe_key[] = { BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_START, BTN_MODE, BTN_SELECT }; 698c2ecf20Sopenharmony_cistatic short adi_wmi_key[] = { BTN_TRIGGER, BTN_TOP, BTN_THUMB, BTN_TOP2, BTN_BASE, BTN_BASE2, BTN_BASE3, BTN_BASE4, BTN_EXTRA }; 708c2ecf20Sopenharmony_cistatic short adi_wmed3d_key[] = { BTN_TRIGGER, BTN_THUMB, BTN_THUMB2, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2 }; 718c2ecf20Sopenharmony_cistatic short adi_cm2_key[] = { BTN_1, BTN_2, BTN_3, BTN_4, BTN_5, BTN_6, BTN_7, BTN_8 }; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic char* adi_abs[] = { adi_wmi_abs, adi_wmgpe_abs, adi_wmf_abs, adi_cm2_abs, adi_wmi_abs, adi_wmf_abs, 748c2ecf20Sopenharmony_ci adi_wmgpe_abs, adi_wmed3d_abs, adi_wmgpe_abs, adi_wmgpe_abs, adi_wmi_abs }; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic short* adi_key[] = { adi_wmi_key, adi_wmgpe_key, adi_cm2_key, adi_cm2_key, adi_wmi_key, adi_cm2_key, 778c2ecf20Sopenharmony_ci adi_wmgpe_key, adi_wmed3d_key, adi_wmgpe_key, adi_wmgpe_key, adi_wmi_key }; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci/* 808c2ecf20Sopenharmony_ci * Hat to axis conversion arrays. 818c2ecf20Sopenharmony_ci */ 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_cistatic struct { 848c2ecf20Sopenharmony_ci int x; 858c2ecf20Sopenharmony_ci int y; 868c2ecf20Sopenharmony_ci} adi_hat_to_axis[] = {{ 0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci/* 898c2ecf20Sopenharmony_ci * Per-port information. 908c2ecf20Sopenharmony_ci */ 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistruct adi { 938c2ecf20Sopenharmony_ci struct input_dev *dev; 948c2ecf20Sopenharmony_ci int length; 958c2ecf20Sopenharmony_ci int ret; 968c2ecf20Sopenharmony_ci int idx; 978c2ecf20Sopenharmony_ci unsigned char id; 988c2ecf20Sopenharmony_ci char buttons; 998c2ecf20Sopenharmony_ci char axes10; 1008c2ecf20Sopenharmony_ci char axes8; 1018c2ecf20Sopenharmony_ci signed char pad; 1028c2ecf20Sopenharmony_ci char hats; 1038c2ecf20Sopenharmony_ci char *abs; 1048c2ecf20Sopenharmony_ci short *key; 1058c2ecf20Sopenharmony_ci char name[ADI_MAX_NAME_LENGTH]; 1068c2ecf20Sopenharmony_ci char cname[ADI_MAX_CNAME_LENGTH]; 1078c2ecf20Sopenharmony_ci char phys[ADI_MAX_PHYS_LENGTH]; 1088c2ecf20Sopenharmony_ci unsigned char data[ADI_MAX_LENGTH]; 1098c2ecf20Sopenharmony_ci}; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_cistruct adi_port { 1128c2ecf20Sopenharmony_ci struct gameport *gameport; 1138c2ecf20Sopenharmony_ci struct adi adi[2]; 1148c2ecf20Sopenharmony_ci int bad; 1158c2ecf20Sopenharmony_ci int reads; 1168c2ecf20Sopenharmony_ci}; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci/* 1198c2ecf20Sopenharmony_ci * adi_read_packet() reads a Logitech ADI packet. 1208c2ecf20Sopenharmony_ci */ 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_cistatic void adi_read_packet(struct adi_port *port) 1238c2ecf20Sopenharmony_ci{ 1248c2ecf20Sopenharmony_ci struct adi *adi = port->adi; 1258c2ecf20Sopenharmony_ci struct gameport *gameport = port->gameport; 1268c2ecf20Sopenharmony_ci unsigned char u, v, w, x, z; 1278c2ecf20Sopenharmony_ci int t[2], s[2], i; 1288c2ecf20Sopenharmony_ci unsigned long flags; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci for (i = 0; i < 2; i++) { 1318c2ecf20Sopenharmony_ci adi[i].ret = -1; 1328c2ecf20Sopenharmony_ci t[i] = gameport_time(gameport, ADI_MAX_START); 1338c2ecf20Sopenharmony_ci s[i] = 0; 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci local_irq_save(flags); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci gameport_trigger(gameport); 1398c2ecf20Sopenharmony_ci v = z = gameport_read(gameport); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci do { 1428c2ecf20Sopenharmony_ci u = v; 1438c2ecf20Sopenharmony_ci w = u ^ (v = x = gameport_read(gameport)); 1448c2ecf20Sopenharmony_ci for (i = 0; i < 2; i++, w >>= 2, x >>= 2) { 1458c2ecf20Sopenharmony_ci t[i]--; 1468c2ecf20Sopenharmony_ci if ((w & 0x30) && s[i]) { 1478c2ecf20Sopenharmony_ci if ((w & 0x30) < 0x30 && adi[i].ret < ADI_MAX_LENGTH && t[i] > 0) { 1488c2ecf20Sopenharmony_ci adi[i].data[++adi[i].ret] = w; 1498c2ecf20Sopenharmony_ci t[i] = gameport_time(gameport, ADI_MAX_STROBE); 1508c2ecf20Sopenharmony_ci } else t[i] = 0; 1518c2ecf20Sopenharmony_ci } else if (!(x & 0x30)) s[i] = 1; 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci } while (t[0] > 0 || t[1] > 0); 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci local_irq_restore(flags); 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci return; 1588c2ecf20Sopenharmony_ci} 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci/* 1618c2ecf20Sopenharmony_ci * adi_move_bits() detects a possible 2-stream mode, and moves 1628c2ecf20Sopenharmony_ci * the bits accordingly. 1638c2ecf20Sopenharmony_ci */ 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_cistatic void adi_move_bits(struct adi_port *port, int length) 1668c2ecf20Sopenharmony_ci{ 1678c2ecf20Sopenharmony_ci int i; 1688c2ecf20Sopenharmony_ci struct adi *adi = port->adi; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci adi[0].idx = adi[1].idx = 0; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci if (adi[0].ret <= 0 || adi[1].ret <= 0) return; 1738c2ecf20Sopenharmony_ci if (adi[0].data[0] & 0x20 || ~adi[1].data[0] & 0x20) return; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci for (i = 1; i <= adi[1].ret; i++) 1768c2ecf20Sopenharmony_ci adi[0].data[((length - 1) >> 1) + i + 1] = adi[1].data[i]; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci adi[0].ret += adi[1].ret; 1798c2ecf20Sopenharmony_ci adi[1].ret = -1; 1808c2ecf20Sopenharmony_ci} 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci/* 1838c2ecf20Sopenharmony_ci * adi_get_bits() gathers bits from the data packet. 1848c2ecf20Sopenharmony_ci */ 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_cistatic inline int adi_get_bits(struct adi *adi, int count) 1878c2ecf20Sopenharmony_ci{ 1888c2ecf20Sopenharmony_ci int bits = 0; 1898c2ecf20Sopenharmony_ci int i; 1908c2ecf20Sopenharmony_ci if ((adi->idx += count) > adi->ret) return 0; 1918c2ecf20Sopenharmony_ci for (i = 0; i < count; i++) 1928c2ecf20Sopenharmony_ci bits |= ((adi->data[adi->idx - i] >> 5) & 1) << i; 1938c2ecf20Sopenharmony_ci return bits; 1948c2ecf20Sopenharmony_ci} 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci/* 1978c2ecf20Sopenharmony_ci * adi_decode() decodes Logitech joystick data into input events. 1988c2ecf20Sopenharmony_ci */ 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_cistatic int adi_decode(struct adi *adi) 2018c2ecf20Sopenharmony_ci{ 2028c2ecf20Sopenharmony_ci struct input_dev *dev = adi->dev; 2038c2ecf20Sopenharmony_ci char *abs = adi->abs; 2048c2ecf20Sopenharmony_ci short *key = adi->key; 2058c2ecf20Sopenharmony_ci int i, t; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci if (adi->ret < adi->length || adi->id != (adi_get_bits(adi, 4) | (adi_get_bits(adi, 4) << 4))) 2088c2ecf20Sopenharmony_ci return -1; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci for (i = 0; i < adi->axes10; i++) 2118c2ecf20Sopenharmony_ci input_report_abs(dev, *abs++, adi_get_bits(adi, 10)); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci for (i = 0; i < adi->axes8; i++) 2148c2ecf20Sopenharmony_ci input_report_abs(dev, *abs++, adi_get_bits(adi, 8)); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci for (i = 0; i < adi->buttons && i < 63; i++) { 2178c2ecf20Sopenharmony_ci if (i == adi->pad) { 2188c2ecf20Sopenharmony_ci t = adi_get_bits(adi, 4); 2198c2ecf20Sopenharmony_ci input_report_abs(dev, *abs++, ((t >> 2) & 1) - ( t & 1)); 2208c2ecf20Sopenharmony_ci input_report_abs(dev, *abs++, ((t >> 1) & 1) - ((t >> 3) & 1)); 2218c2ecf20Sopenharmony_ci } 2228c2ecf20Sopenharmony_ci input_report_key(dev, *key++, adi_get_bits(adi, 1)); 2238c2ecf20Sopenharmony_ci } 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci for (i = 0; i < adi->hats; i++) { 2268c2ecf20Sopenharmony_ci if ((t = adi_get_bits(adi, 4)) > 8) t = 0; 2278c2ecf20Sopenharmony_ci input_report_abs(dev, *abs++, adi_hat_to_axis[t].x); 2288c2ecf20Sopenharmony_ci input_report_abs(dev, *abs++, adi_hat_to_axis[t].y); 2298c2ecf20Sopenharmony_ci } 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci for (i = 63; i < adi->buttons; i++) 2328c2ecf20Sopenharmony_ci input_report_key(dev, *key++, adi_get_bits(adi, 1)); 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci input_sync(dev); 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci return 0; 2378c2ecf20Sopenharmony_ci} 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci/* 2408c2ecf20Sopenharmony_ci * adi_read() reads the data packet and decodes it. 2418c2ecf20Sopenharmony_ci */ 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_cistatic int adi_read(struct adi_port *port) 2448c2ecf20Sopenharmony_ci{ 2458c2ecf20Sopenharmony_ci int i; 2468c2ecf20Sopenharmony_ci int result = 0; 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci adi_read_packet(port); 2498c2ecf20Sopenharmony_ci adi_move_bits(port, port->adi[0].length); 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci for (i = 0; i < 2; i++) 2528c2ecf20Sopenharmony_ci if (port->adi[i].length) 2538c2ecf20Sopenharmony_ci result |= adi_decode(port->adi + i); 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci return result; 2568c2ecf20Sopenharmony_ci} 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci/* 2598c2ecf20Sopenharmony_ci * adi_poll() repeatedly polls the Logitech joysticks. 2608c2ecf20Sopenharmony_ci */ 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_cistatic void adi_poll(struct gameport *gameport) 2638c2ecf20Sopenharmony_ci{ 2648c2ecf20Sopenharmony_ci struct adi_port *port = gameport_get_drvdata(gameport); 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci port->bad -= adi_read(port); 2678c2ecf20Sopenharmony_ci port->reads++; 2688c2ecf20Sopenharmony_ci} 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci/* 2718c2ecf20Sopenharmony_ci * adi_open() is a callback from the input open routine. 2728c2ecf20Sopenharmony_ci */ 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_cistatic int adi_open(struct input_dev *dev) 2758c2ecf20Sopenharmony_ci{ 2768c2ecf20Sopenharmony_ci struct adi_port *port = input_get_drvdata(dev); 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci gameport_start_polling(port->gameport); 2798c2ecf20Sopenharmony_ci return 0; 2808c2ecf20Sopenharmony_ci} 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci/* 2838c2ecf20Sopenharmony_ci * adi_close() is a callback from the input close routine. 2848c2ecf20Sopenharmony_ci */ 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_cistatic void adi_close(struct input_dev *dev) 2878c2ecf20Sopenharmony_ci{ 2888c2ecf20Sopenharmony_ci struct adi_port *port = input_get_drvdata(dev); 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci gameport_stop_polling(port->gameport); 2918c2ecf20Sopenharmony_ci} 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci/* 2948c2ecf20Sopenharmony_ci * adi_init_digital() sends a trigger & delay sequence 2958c2ecf20Sopenharmony_ci * to reset and initialize a Logitech joystick into digital mode. 2968c2ecf20Sopenharmony_ci */ 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_cistatic void adi_init_digital(struct gameport *gameport) 2998c2ecf20Sopenharmony_ci{ 3008c2ecf20Sopenharmony_ci static const int seq[] = { 4, -2, -3, 10, -6, -11, -7, -9, 11, 0 }; 3018c2ecf20Sopenharmony_ci int i; 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci for (i = 0; seq[i]; i++) { 3048c2ecf20Sopenharmony_ci gameport_trigger(gameport); 3058c2ecf20Sopenharmony_ci if (seq[i] > 0) 3068c2ecf20Sopenharmony_ci msleep(seq[i]); 3078c2ecf20Sopenharmony_ci if (seq[i] < 0) { 3088c2ecf20Sopenharmony_ci mdelay(-seq[i]); 3098c2ecf20Sopenharmony_ci udelay(-seq[i]*14); /* It looks like mdelay() is off by approx 1.4% */ 3108c2ecf20Sopenharmony_ci } 3118c2ecf20Sopenharmony_ci } 3128c2ecf20Sopenharmony_ci} 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_cistatic void adi_id_decode(struct adi *adi, struct adi_port *port) 3158c2ecf20Sopenharmony_ci{ 3168c2ecf20Sopenharmony_ci int i, t; 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci if (adi->ret < ADI_MIN_ID_LENGTH) /* Minimum ID packet length */ 3198c2ecf20Sopenharmony_ci return; 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci if (adi->ret < (t = adi_get_bits(adi, 10))) { 3228c2ecf20Sopenharmony_ci printk(KERN_WARNING "adi: Short ID packet: reported: %d != read: %d\n", t, adi->ret); 3238c2ecf20Sopenharmony_ci return; 3248c2ecf20Sopenharmony_ci } 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci adi->id = adi_get_bits(adi, 4) | (adi_get_bits(adi, 4) << 4); 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci if ((t = adi_get_bits(adi, 4)) & ADI_FLAG_HAT) adi->hats++; 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci adi->length = adi_get_bits(adi, 10); 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_ci if (adi->length >= ADI_MAX_LENGTH || adi->length < ADI_MIN_LENGTH) { 3338c2ecf20Sopenharmony_ci printk(KERN_WARNING "adi: Bad data packet length (%d).\n", adi->length); 3348c2ecf20Sopenharmony_ci adi->length = 0; 3358c2ecf20Sopenharmony_ci return; 3368c2ecf20Sopenharmony_ci } 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_ci adi->axes8 = adi_get_bits(adi, 4); 3398c2ecf20Sopenharmony_ci adi->buttons = adi_get_bits(adi, 6); 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci if (adi_get_bits(adi, 6) != 8 && adi->hats) { 3428c2ecf20Sopenharmony_ci printk(KERN_WARNING "adi: Other than 8-dir POVs not supported yet.\n"); 3438c2ecf20Sopenharmony_ci adi->length = 0; 3448c2ecf20Sopenharmony_ci return; 3458c2ecf20Sopenharmony_ci } 3468c2ecf20Sopenharmony_ci 3478c2ecf20Sopenharmony_ci adi->buttons += adi_get_bits(adi, 6); 3488c2ecf20Sopenharmony_ci adi->hats += adi_get_bits(adi, 4); 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci i = adi_get_bits(adi, 4); 3518c2ecf20Sopenharmony_ci 3528c2ecf20Sopenharmony_ci if (t & ADI_FLAG_10BIT) { 3538c2ecf20Sopenharmony_ci adi->axes10 = adi->axes8 - i; 3548c2ecf20Sopenharmony_ci adi->axes8 = i; 3558c2ecf20Sopenharmony_ci } 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_ci t = adi_get_bits(adi, 4); 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci for (i = 0; i < t; i++) 3608c2ecf20Sopenharmony_ci adi->cname[i] = adi_get_bits(adi, 8); 3618c2ecf20Sopenharmony_ci adi->cname[i] = 0; 3628c2ecf20Sopenharmony_ci 3638c2ecf20Sopenharmony_ci t = 8 + adi->buttons + adi->axes10 * 10 + adi->axes8 * 8 + adi->hats * 4; 3648c2ecf20Sopenharmony_ci if (adi->length != t && adi->length != t + (t & 1)) { 3658c2ecf20Sopenharmony_ci printk(KERN_WARNING "adi: Expected length %d != data length %d\n", t, adi->length); 3668c2ecf20Sopenharmony_ci adi->length = 0; 3678c2ecf20Sopenharmony_ci return; 3688c2ecf20Sopenharmony_ci } 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci switch (adi->id) { 3718c2ecf20Sopenharmony_ci case ADI_ID_TPD: 3728c2ecf20Sopenharmony_ci adi->pad = 4; 3738c2ecf20Sopenharmony_ci adi->buttons -= 4; 3748c2ecf20Sopenharmony_ci break; 3758c2ecf20Sopenharmony_ci case ADI_ID_WGP: 3768c2ecf20Sopenharmony_ci adi->pad = 0; 3778c2ecf20Sopenharmony_ci adi->buttons -= 4; 3788c2ecf20Sopenharmony_ci break; 3798c2ecf20Sopenharmony_ci default: 3808c2ecf20Sopenharmony_ci adi->pad = -1; 3818c2ecf20Sopenharmony_ci break; 3828c2ecf20Sopenharmony_ci } 3838c2ecf20Sopenharmony_ci} 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_cistatic int adi_init_input(struct adi *adi, struct adi_port *port, int half) 3868c2ecf20Sopenharmony_ci{ 3878c2ecf20Sopenharmony_ci struct input_dev *input_dev; 3888c2ecf20Sopenharmony_ci char buf[ADI_MAX_NAME_LENGTH]; 3898c2ecf20Sopenharmony_ci int i, t; 3908c2ecf20Sopenharmony_ci 3918c2ecf20Sopenharmony_ci adi->dev = input_dev = input_allocate_device(); 3928c2ecf20Sopenharmony_ci if (!input_dev) 3938c2ecf20Sopenharmony_ci return -ENOMEM; 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_ci t = adi->id < ADI_ID_MAX ? adi->id : ADI_ID_MAX; 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_ci snprintf(buf, ADI_MAX_PHYS_LENGTH, adi_names[t], adi->id); 3988c2ecf20Sopenharmony_ci snprintf(adi->name, ADI_MAX_NAME_LENGTH, "Logitech %s [%s]", buf, adi->cname); 3998c2ecf20Sopenharmony_ci snprintf(adi->phys, ADI_MAX_PHYS_LENGTH, "%s/input%d", port->gameport->phys, half); 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ci adi->abs = adi_abs[t]; 4028c2ecf20Sopenharmony_ci adi->key = adi_key[t]; 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_ci input_dev->name = adi->name; 4058c2ecf20Sopenharmony_ci input_dev->phys = adi->phys; 4068c2ecf20Sopenharmony_ci input_dev->id.bustype = BUS_GAMEPORT; 4078c2ecf20Sopenharmony_ci input_dev->id.vendor = GAMEPORT_ID_VENDOR_LOGITECH; 4088c2ecf20Sopenharmony_ci input_dev->id.product = adi->id; 4098c2ecf20Sopenharmony_ci input_dev->id.version = 0x0100; 4108c2ecf20Sopenharmony_ci input_dev->dev.parent = &port->gameport->dev; 4118c2ecf20Sopenharmony_ci 4128c2ecf20Sopenharmony_ci input_set_drvdata(input_dev, port); 4138c2ecf20Sopenharmony_ci 4148c2ecf20Sopenharmony_ci input_dev->open = adi_open; 4158c2ecf20Sopenharmony_ci input_dev->close = adi_close; 4168c2ecf20Sopenharmony_ci 4178c2ecf20Sopenharmony_ci input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); 4188c2ecf20Sopenharmony_ci 4198c2ecf20Sopenharmony_ci for (i = 0; i < adi->axes10 + adi->axes8 + (adi->hats + (adi->pad != -1)) * 2; i++) 4208c2ecf20Sopenharmony_ci set_bit(adi->abs[i], input_dev->absbit); 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_ci for (i = 0; i < adi->buttons; i++) 4238c2ecf20Sopenharmony_ci set_bit(adi->key[i], input_dev->keybit); 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_ci return 0; 4268c2ecf20Sopenharmony_ci} 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_cistatic void adi_init_center(struct adi *adi) 4298c2ecf20Sopenharmony_ci{ 4308c2ecf20Sopenharmony_ci int i, t, x; 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci if (!adi->length) 4338c2ecf20Sopenharmony_ci return; 4348c2ecf20Sopenharmony_ci 4358c2ecf20Sopenharmony_ci for (i = 0; i < adi->axes10 + adi->axes8 + (adi->hats + (adi->pad != -1)) * 2; i++) { 4368c2ecf20Sopenharmony_ci 4378c2ecf20Sopenharmony_ci t = adi->abs[i]; 4388c2ecf20Sopenharmony_ci x = input_abs_get_val(adi->dev, t); 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_ci if (t == ABS_THROTTLE || t == ABS_RUDDER || adi->id == ADI_ID_WGPE) 4418c2ecf20Sopenharmony_ci x = i < adi->axes10 ? 512 : 128; 4428c2ecf20Sopenharmony_ci 4438c2ecf20Sopenharmony_ci if (i < adi->axes10) 4448c2ecf20Sopenharmony_ci input_set_abs_params(adi->dev, t, 64, x * 2 - 64, 2, 16); 4458c2ecf20Sopenharmony_ci else if (i < adi->axes10 + adi->axes8) 4468c2ecf20Sopenharmony_ci input_set_abs_params(adi->dev, t, 48, x * 2 - 48, 1, 16); 4478c2ecf20Sopenharmony_ci else 4488c2ecf20Sopenharmony_ci input_set_abs_params(adi->dev, t, -1, 1, 0, 0); 4498c2ecf20Sopenharmony_ci } 4508c2ecf20Sopenharmony_ci} 4518c2ecf20Sopenharmony_ci 4528c2ecf20Sopenharmony_ci/* 4538c2ecf20Sopenharmony_ci * adi_connect() probes for Logitech ADI joysticks. 4548c2ecf20Sopenharmony_ci */ 4558c2ecf20Sopenharmony_ci 4568c2ecf20Sopenharmony_cistatic int adi_connect(struct gameport *gameport, struct gameport_driver *drv) 4578c2ecf20Sopenharmony_ci{ 4588c2ecf20Sopenharmony_ci struct adi_port *port; 4598c2ecf20Sopenharmony_ci int i; 4608c2ecf20Sopenharmony_ci int err; 4618c2ecf20Sopenharmony_ci 4628c2ecf20Sopenharmony_ci port = kzalloc(sizeof(struct adi_port), GFP_KERNEL); 4638c2ecf20Sopenharmony_ci if (!port) 4648c2ecf20Sopenharmony_ci return -ENOMEM; 4658c2ecf20Sopenharmony_ci 4668c2ecf20Sopenharmony_ci port->gameport = gameport; 4678c2ecf20Sopenharmony_ci 4688c2ecf20Sopenharmony_ci gameport_set_drvdata(gameport, port); 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_ci err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); 4718c2ecf20Sopenharmony_ci if (err) 4728c2ecf20Sopenharmony_ci goto fail1; 4738c2ecf20Sopenharmony_ci 4748c2ecf20Sopenharmony_ci adi_init_digital(gameport); 4758c2ecf20Sopenharmony_ci adi_read_packet(port); 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_ci if (port->adi[0].ret >= ADI_MIN_LEN_LENGTH) 4788c2ecf20Sopenharmony_ci adi_move_bits(port, adi_get_bits(port->adi, 10)); 4798c2ecf20Sopenharmony_ci 4808c2ecf20Sopenharmony_ci for (i = 0; i < 2; i++) { 4818c2ecf20Sopenharmony_ci adi_id_decode(port->adi + i, port); 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci if (!port->adi[i].length) 4848c2ecf20Sopenharmony_ci continue; 4858c2ecf20Sopenharmony_ci 4868c2ecf20Sopenharmony_ci err = adi_init_input(port->adi + i, port, i); 4878c2ecf20Sopenharmony_ci if (err) 4888c2ecf20Sopenharmony_ci goto fail2; 4898c2ecf20Sopenharmony_ci } 4908c2ecf20Sopenharmony_ci 4918c2ecf20Sopenharmony_ci if (!port->adi[0].length && !port->adi[1].length) { 4928c2ecf20Sopenharmony_ci err = -ENODEV; 4938c2ecf20Sopenharmony_ci goto fail2; 4948c2ecf20Sopenharmony_ci } 4958c2ecf20Sopenharmony_ci 4968c2ecf20Sopenharmony_ci gameport_set_poll_handler(gameport, adi_poll); 4978c2ecf20Sopenharmony_ci gameport_set_poll_interval(gameport, 20); 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_ci msleep(ADI_INIT_DELAY); 5008c2ecf20Sopenharmony_ci if (adi_read(port)) { 5018c2ecf20Sopenharmony_ci msleep(ADI_DATA_DELAY); 5028c2ecf20Sopenharmony_ci adi_read(port); 5038c2ecf20Sopenharmony_ci } 5048c2ecf20Sopenharmony_ci 5058c2ecf20Sopenharmony_ci for (i = 0; i < 2; i++) 5068c2ecf20Sopenharmony_ci if (port->adi[i].length > 0) { 5078c2ecf20Sopenharmony_ci adi_init_center(port->adi + i); 5088c2ecf20Sopenharmony_ci err = input_register_device(port->adi[i].dev); 5098c2ecf20Sopenharmony_ci if (err) 5108c2ecf20Sopenharmony_ci goto fail3; 5118c2ecf20Sopenharmony_ci } 5128c2ecf20Sopenharmony_ci 5138c2ecf20Sopenharmony_ci return 0; 5148c2ecf20Sopenharmony_ci 5158c2ecf20Sopenharmony_ci fail3: while (--i >= 0) { 5168c2ecf20Sopenharmony_ci if (port->adi[i].length > 0) { 5178c2ecf20Sopenharmony_ci input_unregister_device(port->adi[i].dev); 5188c2ecf20Sopenharmony_ci port->adi[i].dev = NULL; 5198c2ecf20Sopenharmony_ci } 5208c2ecf20Sopenharmony_ci } 5218c2ecf20Sopenharmony_ci fail2: for (i = 0; i < 2; i++) 5228c2ecf20Sopenharmony_ci input_free_device(port->adi[i].dev); 5238c2ecf20Sopenharmony_ci gameport_close(gameport); 5248c2ecf20Sopenharmony_ci fail1: gameport_set_drvdata(gameport, NULL); 5258c2ecf20Sopenharmony_ci kfree(port); 5268c2ecf20Sopenharmony_ci return err; 5278c2ecf20Sopenharmony_ci} 5288c2ecf20Sopenharmony_ci 5298c2ecf20Sopenharmony_cistatic void adi_disconnect(struct gameport *gameport) 5308c2ecf20Sopenharmony_ci{ 5318c2ecf20Sopenharmony_ci int i; 5328c2ecf20Sopenharmony_ci struct adi_port *port = gameport_get_drvdata(gameport); 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci for (i = 0; i < 2; i++) 5358c2ecf20Sopenharmony_ci if (port->adi[i].length > 0) 5368c2ecf20Sopenharmony_ci input_unregister_device(port->adi[i].dev); 5378c2ecf20Sopenharmony_ci gameport_close(gameport); 5388c2ecf20Sopenharmony_ci gameport_set_drvdata(gameport, NULL); 5398c2ecf20Sopenharmony_ci kfree(port); 5408c2ecf20Sopenharmony_ci} 5418c2ecf20Sopenharmony_ci 5428c2ecf20Sopenharmony_cistatic struct gameport_driver adi_drv = { 5438c2ecf20Sopenharmony_ci .driver = { 5448c2ecf20Sopenharmony_ci .name = "adi", 5458c2ecf20Sopenharmony_ci }, 5468c2ecf20Sopenharmony_ci .description = DRIVER_DESC, 5478c2ecf20Sopenharmony_ci .connect = adi_connect, 5488c2ecf20Sopenharmony_ci .disconnect = adi_disconnect, 5498c2ecf20Sopenharmony_ci}; 5508c2ecf20Sopenharmony_ci 5518c2ecf20Sopenharmony_cimodule_gameport_driver(adi_drv); 552