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