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 * Creative Labs Blaster GamePad Cobra 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/gameport.h> 178c2ecf20Sopenharmony_ci#include <linux/input.h> 188c2ecf20Sopenharmony_ci#include <linux/jiffies.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#define DRIVER_DESC "Creative Labs Blaster GamePad Cobra driver" 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); 238c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC); 248c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#define COBRA_MAX_STROBE 45 /* 45 us max wait for first strobe */ 278c2ecf20Sopenharmony_ci#define COBRA_LENGTH 36 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_cistatic int cobra_btn[] = { BTN_START, BTN_SELECT, BTN_TL, BTN_TR, BTN_X, BTN_Y, BTN_Z, BTN_A, BTN_B, BTN_C, BTN_TL2, BTN_TR2, 0 }; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistruct cobra { 328c2ecf20Sopenharmony_ci struct gameport *gameport; 338c2ecf20Sopenharmony_ci struct input_dev *dev[2]; 348c2ecf20Sopenharmony_ci int reads; 358c2ecf20Sopenharmony_ci int bads; 368c2ecf20Sopenharmony_ci unsigned char exists; 378c2ecf20Sopenharmony_ci char phys[2][32]; 388c2ecf20Sopenharmony_ci}; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistatic unsigned char cobra_read_packet(struct gameport *gameport, unsigned int *data) 418c2ecf20Sopenharmony_ci{ 428c2ecf20Sopenharmony_ci unsigned long flags; 438c2ecf20Sopenharmony_ci unsigned char u, v, w; 448c2ecf20Sopenharmony_ci __u64 buf[2]; 458c2ecf20Sopenharmony_ci int r[2], t[2]; 468c2ecf20Sopenharmony_ci int i, j, ret; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci int strobe = gameport_time(gameport, COBRA_MAX_STROBE); 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci for (i = 0; i < 2; i++) { 518c2ecf20Sopenharmony_ci r[i] = buf[i] = 0; 528c2ecf20Sopenharmony_ci t[i] = COBRA_MAX_STROBE; 538c2ecf20Sopenharmony_ci } 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci local_irq_save(flags); 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci u = gameport_read(gameport); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci do { 608c2ecf20Sopenharmony_ci t[0]--; t[1]--; 618c2ecf20Sopenharmony_ci v = gameport_read(gameport); 628c2ecf20Sopenharmony_ci for (i = 0, w = u ^ v; i < 2 && w; i++, w >>= 2) 638c2ecf20Sopenharmony_ci if (w & 0x30) { 648c2ecf20Sopenharmony_ci if ((w & 0x30) < 0x30 && r[i] < COBRA_LENGTH && t[i] > 0) { 658c2ecf20Sopenharmony_ci buf[i] |= (__u64)((w >> 5) & 1) << r[i]++; 668c2ecf20Sopenharmony_ci t[i] = strobe; 678c2ecf20Sopenharmony_ci u = v; 688c2ecf20Sopenharmony_ci } else t[i] = 0; 698c2ecf20Sopenharmony_ci } 708c2ecf20Sopenharmony_ci } while (t[0] > 0 || t[1] > 0); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci local_irq_restore(flags); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci ret = 0; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci for (i = 0; i < 2; i++) { 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci if (r[i] != COBRA_LENGTH) continue; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci for (j = 0; j < COBRA_LENGTH && (buf[i] & 0x04104107f) ^ 0x041041040; j++) 818c2ecf20Sopenharmony_ci buf[i] = (buf[i] >> 1) | ((__u64)(buf[i] & 1) << (COBRA_LENGTH - 1)); 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci if (j < COBRA_LENGTH) ret |= (1 << i); 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci data[i] = ((buf[i] >> 7) & 0x000001f) | ((buf[i] >> 8) & 0x00003e0) 868c2ecf20Sopenharmony_ci | ((buf[i] >> 9) & 0x0007c00) | ((buf[i] >> 10) & 0x00f8000) 878c2ecf20Sopenharmony_ci | ((buf[i] >> 11) & 0x1f00000); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci } 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci return ret; 928c2ecf20Sopenharmony_ci} 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_cistatic void cobra_poll(struct gameport *gameport) 958c2ecf20Sopenharmony_ci{ 968c2ecf20Sopenharmony_ci struct cobra *cobra = gameport_get_drvdata(gameport); 978c2ecf20Sopenharmony_ci struct input_dev *dev; 988c2ecf20Sopenharmony_ci unsigned int data[2]; 998c2ecf20Sopenharmony_ci int i, j, r; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci cobra->reads++; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci if ((r = cobra_read_packet(gameport, data)) != cobra->exists) { 1048c2ecf20Sopenharmony_ci cobra->bads++; 1058c2ecf20Sopenharmony_ci return; 1068c2ecf20Sopenharmony_ci } 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci for (i = 0; i < 2; i++) 1098c2ecf20Sopenharmony_ci if (cobra->exists & r & (1 << i)) { 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci dev = cobra->dev[i]; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci input_report_abs(dev, ABS_X, ((data[i] >> 4) & 1) - ((data[i] >> 3) & 1)); 1148c2ecf20Sopenharmony_ci input_report_abs(dev, ABS_Y, ((data[i] >> 2) & 1) - ((data[i] >> 1) & 1)); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci for (j = 0; cobra_btn[j]; j++) 1178c2ecf20Sopenharmony_ci input_report_key(dev, cobra_btn[j], data[i] & (0x20 << j)); 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci input_sync(dev); 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci } 1228c2ecf20Sopenharmony_ci} 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_cistatic int cobra_open(struct input_dev *dev) 1258c2ecf20Sopenharmony_ci{ 1268c2ecf20Sopenharmony_ci struct cobra *cobra = input_get_drvdata(dev); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci gameport_start_polling(cobra->gameport); 1298c2ecf20Sopenharmony_ci return 0; 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic void cobra_close(struct input_dev *dev) 1338c2ecf20Sopenharmony_ci{ 1348c2ecf20Sopenharmony_ci struct cobra *cobra = input_get_drvdata(dev); 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci gameport_stop_polling(cobra->gameport); 1378c2ecf20Sopenharmony_ci} 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_cistatic int cobra_connect(struct gameport *gameport, struct gameport_driver *drv) 1408c2ecf20Sopenharmony_ci{ 1418c2ecf20Sopenharmony_ci struct cobra *cobra; 1428c2ecf20Sopenharmony_ci struct input_dev *input_dev; 1438c2ecf20Sopenharmony_ci unsigned int data[2]; 1448c2ecf20Sopenharmony_ci int i, j; 1458c2ecf20Sopenharmony_ci int err; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci cobra = kzalloc(sizeof(struct cobra), GFP_KERNEL); 1488c2ecf20Sopenharmony_ci if (!cobra) 1498c2ecf20Sopenharmony_ci return -ENOMEM; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci cobra->gameport = gameport; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci gameport_set_drvdata(gameport, cobra); 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); 1568c2ecf20Sopenharmony_ci if (err) 1578c2ecf20Sopenharmony_ci goto fail1; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci cobra->exists = cobra_read_packet(gameport, data); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci for (i = 0; i < 2; i++) 1628c2ecf20Sopenharmony_ci if ((cobra->exists >> i) & data[i] & 1) { 1638c2ecf20Sopenharmony_ci printk(KERN_WARNING "cobra.c: Device %d on %s has the Ext bit set. ID is: %d" 1648c2ecf20Sopenharmony_ci " Contact vojtech@ucw.cz\n", i, gameport->phys, (data[i] >> 2) & 7); 1658c2ecf20Sopenharmony_ci cobra->exists &= ~(1 << i); 1668c2ecf20Sopenharmony_ci } 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci if (!cobra->exists) { 1698c2ecf20Sopenharmony_ci err = -ENODEV; 1708c2ecf20Sopenharmony_ci goto fail2; 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci gameport_set_poll_handler(gameport, cobra_poll); 1748c2ecf20Sopenharmony_ci gameport_set_poll_interval(gameport, 20); 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci for (i = 0; i < 2; i++) { 1778c2ecf20Sopenharmony_ci if (~(cobra->exists >> i) & 1) 1788c2ecf20Sopenharmony_ci continue; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci cobra->dev[i] = input_dev = input_allocate_device(); 1818c2ecf20Sopenharmony_ci if (!input_dev) { 1828c2ecf20Sopenharmony_ci err = -ENOMEM; 1838c2ecf20Sopenharmony_ci goto fail3; 1848c2ecf20Sopenharmony_ci } 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci snprintf(cobra->phys[i], sizeof(cobra->phys[i]), 1878c2ecf20Sopenharmony_ci "%s/input%d", gameport->phys, i); 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci input_dev->name = "Creative Labs Blaster GamePad Cobra"; 1908c2ecf20Sopenharmony_ci input_dev->phys = cobra->phys[i]; 1918c2ecf20Sopenharmony_ci input_dev->id.bustype = BUS_GAMEPORT; 1928c2ecf20Sopenharmony_ci input_dev->id.vendor = GAMEPORT_ID_VENDOR_CREATIVE; 1938c2ecf20Sopenharmony_ci input_dev->id.product = 0x0008; 1948c2ecf20Sopenharmony_ci input_dev->id.version = 0x0100; 1958c2ecf20Sopenharmony_ci input_dev->dev.parent = &gameport->dev; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci input_set_drvdata(input_dev, cobra); 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci input_dev->open = cobra_open; 2008c2ecf20Sopenharmony_ci input_dev->close = cobra_close; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); 2038c2ecf20Sopenharmony_ci input_set_abs_params(input_dev, ABS_X, -1, 1, 0, 0); 2048c2ecf20Sopenharmony_ci input_set_abs_params(input_dev, ABS_Y, -1, 1, 0, 0); 2058c2ecf20Sopenharmony_ci for (j = 0; cobra_btn[j]; j++) 2068c2ecf20Sopenharmony_ci set_bit(cobra_btn[j], input_dev->keybit); 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci err = input_register_device(cobra->dev[i]); 2098c2ecf20Sopenharmony_ci if (err) 2108c2ecf20Sopenharmony_ci goto fail4; 2118c2ecf20Sopenharmony_ci } 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci return 0; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci fail4: input_free_device(cobra->dev[i]); 2168c2ecf20Sopenharmony_ci fail3: while (--i >= 0) 2178c2ecf20Sopenharmony_ci if (cobra->dev[i]) 2188c2ecf20Sopenharmony_ci input_unregister_device(cobra->dev[i]); 2198c2ecf20Sopenharmony_ci fail2: gameport_close(gameport); 2208c2ecf20Sopenharmony_ci fail1: gameport_set_drvdata(gameport, NULL); 2218c2ecf20Sopenharmony_ci kfree(cobra); 2228c2ecf20Sopenharmony_ci return err; 2238c2ecf20Sopenharmony_ci} 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_cistatic void cobra_disconnect(struct gameport *gameport) 2268c2ecf20Sopenharmony_ci{ 2278c2ecf20Sopenharmony_ci struct cobra *cobra = gameport_get_drvdata(gameport); 2288c2ecf20Sopenharmony_ci int i; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci for (i = 0; i < 2; i++) 2318c2ecf20Sopenharmony_ci if ((cobra->exists >> i) & 1) 2328c2ecf20Sopenharmony_ci input_unregister_device(cobra->dev[i]); 2338c2ecf20Sopenharmony_ci gameport_close(gameport); 2348c2ecf20Sopenharmony_ci gameport_set_drvdata(gameport, NULL); 2358c2ecf20Sopenharmony_ci kfree(cobra); 2368c2ecf20Sopenharmony_ci} 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_cistatic struct gameport_driver cobra_drv = { 2398c2ecf20Sopenharmony_ci .driver = { 2408c2ecf20Sopenharmony_ci .name = "cobra", 2418c2ecf20Sopenharmony_ci }, 2428c2ecf20Sopenharmony_ci .description = DRIVER_DESC, 2438c2ecf20Sopenharmony_ci .connect = cobra_connect, 2448c2ecf20Sopenharmony_ci .disconnect = cobra_disconnect, 2458c2ecf20Sopenharmony_ci}; 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_cimodule_gameport_driver(cobra_drv); 248