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