linux/drivers/input/gameport/ns558.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 *  Copyright (c) 1999-2001 Vojtech Pavlik
   4 *  Copyright (c) 1999 Brian Gerst
   5 */
   6
   7/*
   8 * NS558 based standard IBM game port driver for Linux
   9 */
  10
  11/*
  12 */
  13
  14#include <asm/io.h>
  15
  16#include <linux/module.h>
  17#include <linux/ioport.h>
  18#include <linux/init.h>
  19#include <linux/delay.h>
  20#include <linux/gameport.h>
  21#include <linux/slab.h>
  22#include <linux/pnp.h>
  23
  24MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
  25MODULE_DESCRIPTION("Classic gameport (ISA/PnP) driver");
  26MODULE_LICENSE("GPL");
  27
  28static int ns558_isa_portlist[] = { 0x201, 0x200, 0x202, 0x203, 0x204, 0x205, 0x207, 0x209,
  29                                    0x20b, 0x20c, 0x20e, 0x20f, 0x211, 0x219, 0x101, 0 };
  30
  31struct ns558 {
  32        int type;
  33        int io;
  34        int size;
  35        struct pnp_dev *dev;
  36        struct gameport *gameport;
  37        struct list_head node;
  38};
  39
  40static LIST_HEAD(ns558_list);
  41
  42/*
  43 * ns558_isa_probe() tries to find an isa gameport at the
  44 * specified address, and also checks for mirrors.
  45 * A joystick must be attached for this to work.
  46 */
  47
  48static int ns558_isa_probe(int io)
  49{
  50        int i, j, b;
  51        unsigned char c, u, v;
  52        struct ns558 *ns558;
  53        struct gameport *port;
  54
  55/*
  56 * No one should be using this address.
  57 */
  58
  59        if (!request_region(io, 1, "ns558-isa"))
  60                return -EBUSY;
  61
  62/*
  63 * We must not be able to write arbitrary values to the port.
  64 * The lower two axis bits must be 1 after a write.
  65 */
  66
  67        c = inb(io);
  68        outb(~c & ~3, io);
  69        if (~(u = v = inb(io)) & 3) {
  70                outb(c, io);
  71                release_region(io, 1);
  72                return -ENODEV;
  73        }
  74/*
  75 * After a trigger, there must be at least some bits changing.
  76 */
  77
  78        for (i = 0; i < 1000; i++) v &= inb(io);
  79
  80        if (u == v) {
  81                outb(c, io);
  82                release_region(io, 1);
  83                return -ENODEV;
  84        }
  85        msleep(3);
  86/*
  87 * After some time (4ms) the axes shouldn't change anymore.
  88 */
  89
  90        u = inb(io);
  91        for (i = 0; i < 1000; i++)
  92                if ((u ^ inb(io)) & 0xf) {
  93                        outb(c, io);
  94                        release_region(io, 1);
  95                        return -ENODEV;
  96                }
  97/*
  98 * And now find the number of mirrors of the port.
  99 */
 100
 101        for (i = 1; i < 5; i++) {
 102
 103                release_region(io & (-1 << (i - 1)), (1 << (i - 1)));
 104
 105                if (!request_region(io & (-1 << i), (1 << i), "ns558-isa"))
 106                        break;                          /* Don't disturb anyone */
 107
 108                outb(0xff, io & (-1 << i));
 109                for (j = b = 0; j < 1000; j++)
 110                        if (inb(io & (-1 << i)) != inb((io & (-1 << i)) + (1 << i) - 1)) b++;
 111                msleep(3);
 112
 113                if (b > 300) {                          /* We allow 30% difference */
 114                        release_region(io & (-1 << i), (1 << i));
 115                        break;
 116                }
 117        }
 118
 119        i--;
 120
 121        if (i != 4) {
 122                if (!request_region(io & (-1 << i), (1 << i), "ns558-isa"))
 123                        return -EBUSY;
 124        }
 125
 126        ns558 = kzalloc(sizeof(struct ns558), GFP_KERNEL);
 127        port = gameport_allocate_port();
 128        if (!ns558 || !port) {
 129                printk(KERN_ERR "ns558: Memory allocation failed.\n");
 130                release_region(io & (-1 << i), (1 << i));
 131                kfree(ns558);
 132                gameport_free_port(port);
 133                return -ENOMEM;
 134        }
 135
 136        ns558->io = io;
 137        ns558->size = 1 << i;
 138        ns558->gameport = port;
 139
 140        port->io = io;
 141        gameport_set_name(port, "NS558 ISA Gameport");
 142        gameport_set_phys(port, "isa%04x/gameport0", io & (-1 << i));
 143
 144        gameport_register_port(port);
 145
 146        list_add(&ns558->node, &ns558_list);
 147
 148        return 0;
 149}
 150
 151#ifdef CONFIG_PNP
 152
 153static const struct pnp_device_id pnp_devids[] = {
 154        { .id = "@P@0001", .driver_data = 0 }, /* ALS 100 */
 155        { .id = "@P@0020", .driver_data = 0 }, /* ALS 200 */
 156        { .id = "@P@1001", .driver_data = 0 }, /* ALS 100+ */
 157        { .id = "@P@2001", .driver_data = 0 }, /* ALS 120 */
 158        { .id = "ASB16fd", .driver_data = 0 }, /* AdLib NSC16 */
 159        { .id = "AZT3001", .driver_data = 0 }, /* AZT1008 */
 160        { .id = "CDC0001", .driver_data = 0 }, /* Opl3-SAx */
 161        { .id = "CSC0001", .driver_data = 0 }, /* CS4232 */
 162        { .id = "CSC000f", .driver_data = 0 }, /* CS4236 */
 163        { .id = "CSC0101", .driver_data = 0 }, /* CS4327 */
 164        { .id = "CTL7001", .driver_data = 0 }, /* SB16 */
 165        { .id = "CTL7002", .driver_data = 0 }, /* AWE64 */
 166        { .id = "CTL7005", .driver_data = 0 }, /* Vibra16 */
 167        { .id = "ENS2020", .driver_data = 0 }, /* SoundscapeVIVO */
 168        { .id = "ESS0001", .driver_data = 0 }, /* ES1869 */
 169        { .id = "ESS0005", .driver_data = 0 }, /* ES1878 */
 170        { .id = "ESS6880", .driver_data = 0 }, /* ES688 */
 171        { .id = "IBM0012", .driver_data = 0 }, /* CS4232 */
 172        { .id = "OPT0001", .driver_data = 0 }, /* OPTi Audio16 */
 173        { .id = "YMH0006", .driver_data = 0 }, /* Opl3-SA */
 174        { .id = "YMH0022", .driver_data = 0 }, /* Opl3-SAx */
 175        { .id = "PNPb02f", .driver_data = 0 }, /* Generic */
 176        { .id = "", },
 177};
 178
 179MODULE_DEVICE_TABLE(pnp, pnp_devids);
 180
 181static int ns558_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *did)
 182{
 183        int ioport, iolen;
 184        struct ns558 *ns558;
 185        struct gameport *port;
 186
 187        if (!pnp_port_valid(dev, 0)) {
 188                printk(KERN_WARNING "ns558: No i/o ports on a gameport? Weird\n");
 189                return -ENODEV;
 190        }
 191
 192        ioport = pnp_port_start(dev, 0);
 193        iolen = pnp_port_len(dev, 0);
 194
 195        if (!request_region(ioport, iolen, "ns558-pnp"))
 196                return -EBUSY;
 197
 198        ns558 = kzalloc(sizeof(struct ns558), GFP_KERNEL);
 199        port = gameport_allocate_port();
 200        if (!ns558 || !port) {
 201                printk(KERN_ERR "ns558: Memory allocation failed\n");
 202                kfree(ns558);
 203                gameport_free_port(port);
 204                return -ENOMEM;
 205        }
 206
 207        ns558->io = ioport;
 208        ns558->size = iolen;
 209        ns558->dev = dev;
 210        ns558->gameport = port;
 211
 212        gameport_set_name(port, "NS558 PnP Gameport");
 213        gameport_set_phys(port, "pnp%s/gameport0", dev_name(&dev->dev));
 214        port->dev.parent = &dev->dev;
 215        port->io = ioport;
 216
 217        gameport_register_port(port);
 218
 219        list_add_tail(&ns558->node, &ns558_list);
 220        return 0;
 221}
 222
 223static struct pnp_driver ns558_pnp_driver = {
 224        .name           = "ns558",
 225        .id_table       = pnp_devids,
 226        .probe          = ns558_pnp_probe,
 227};
 228
 229#else
 230
 231static struct pnp_driver ns558_pnp_driver;
 232
 233#endif
 234
 235static int __init ns558_init(void)
 236{
 237        int i = 0;
 238        int error;
 239
 240        error = pnp_register_driver(&ns558_pnp_driver);
 241        if (error && error != -ENODEV)  /* should be ENOSYS really */
 242                return error;
 243
 244/*
 245 * Probe ISA ports after PnP, so that PnP ports that are already
 246 * enabled get detected as PnP. This may be suboptimal in multi-device
 247 * configurations, but saves hassle with simple setups.
 248 */
 249
 250        while (ns558_isa_portlist[i])
 251                ns558_isa_probe(ns558_isa_portlist[i++]);
 252
 253        return list_empty(&ns558_list) && error ? -ENODEV : 0;
 254}
 255
 256static void __exit ns558_exit(void)
 257{
 258        struct ns558 *ns558, *safe;
 259
 260        list_for_each_entry_safe(ns558, safe, &ns558_list, node) {
 261                gameport_unregister_port(ns558->gameport);
 262                release_region(ns558->io & ~(ns558->size - 1), ns558->size);
 263                kfree(ns558);
 264        }
 265
 266        pnp_unregister_driver(&ns558_pnp_driver);
 267}
 268
 269module_init(ns558_init);
 270module_exit(ns558_exit);
 271