1 /*
2  * $Id: ns558.c,v 1.29 2001/04/24 07:48:56 vojtech Exp $
3  *
4  *  Copyright (c) 1999-2001 Vojtech Pavlik
5  *  Copyright (c) 1999 Brian Gerst
6  *
7  *  Sponsored by SuSE
8  */
9 
10 /*
11  * NS558 based standard IBM game port driver for Linux
12  */
13 
14 /*
15  * This program is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 2 of the License, or
18  * (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with this program; if not, write to the Free Software
27  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28  *
29  * Should you need to contact me, the author, you can do so either by
30  * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
31  * Vojtech Pavlik, Ucitelska 1576, Prague 8, 182 00 Czech Republic
32  */
33 
34 #include <asm/io.h>
35 
36 #include <linux/module.h>
37 #include <linux/ioport.h>
38 #include <linux/config.h>
39 #include <linux/init.h>
40 #include <linux/gameport.h>
41 #include <linux/slab.h>
42 #include <linux/isapnp.h>
43 
44 MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
45 MODULE_LICENSE("GPL");
46 
47 #define NS558_ISA	1
48 #define NS558_PNP	2
49 
50 static int ns558_isa_portlist[] = { 0x200, 0x201, 0x202, 0x203, 0x204, 0x205, 0x207, 0x209,
51 				    0x20b, 0x20c, 0x20e, 0x20f, 0x211, 0x219, 0x101, 0 };
52 
53 struct ns558 {
54 	int type;
55 	int size;
56 	struct pci_dev *dev;
57 	struct ns558 *next;
58 	struct gameport gameport;
59 };
60 
61 static struct ns558 *ns558;
62 
63 /*
64  * ns558_isa_probe() tries to find an isa gameport at the
65  * specified address, and also checks for mirrors.
66  * A joystick must be attached for this to work.
67  */
68 
ns558_isa_probe(int io,struct ns558 * next)69 static struct ns558* ns558_isa_probe(int io, struct ns558 *next)
70 {
71 	int i, j, b;
72 	unsigned char c, u, v;
73 	struct ns558 *port;
74 
75 /*
76  * No one should be using this address.
77  */
78 
79 	if (check_region(io, 1))
80 		return next;
81 
82 /*
83  * We must not be able to write arbitrary values to the port.
84  * The lower two axis bits must be 1 after a write.
85  */
86 
87 	c = inb(io);
88 	outb(~c & ~3, io);
89 	if (~(u = v = inb(io)) & 3) {
90 		outb(c, io);
91 		return next;
92 	}
93 /*
94  * After a trigger, there must be at least some bits changing.
95  */
96 
97 	for (i = 0; i < 1000; i++) v &= inb(io);
98 
99 	if (u == v) {
100 		outb(c, io);
101 		return next;
102 	}
103 	wait_ms(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 			return next;
113 		}
114 /*
115  * And now find the number of mirrors of the port.
116  */
117 
118 	for (i = 1; i < 5; i++) {
119 
120 		if (check_region(io & (-1 << i), (1 << i)))	/* Don't disturb anyone */
121 			break;
122 
123 		outb(0xff, io & (-1 << i));
124 		for (j = b = 0; j < 1000; j++)
125 			if (inb(io & (-1 << i)) != inb((io & (-1 << i)) + (1 << i) - 1)) b++;
126 		wait_ms(3);
127 
128 		if (b > 300)					/* We allow 30% difference */
129 			break;
130 	}
131 
132 	i--;
133 
134 	if (!(port = kmalloc(sizeof(struct ns558), GFP_KERNEL))) {
135 		printk(KERN_ERR "ns558: Memory allocation failed.\n");
136 		return next;
137 	}
138        	memset(port, 0, sizeof(struct ns558));
139 
140 	port->next = next;
141 	port->type = NS558_ISA;
142 	port->size = (1 << i);
143 	port->gameport.io = io & (-1 << i);
144 
145 	request_region(port->gameport.io, (1 << i), "ns558-isa");
146 
147 	gameport_register_port(&port->gameport);
148 
149 	printk(KERN_INFO "gameport%d: NS558 ISA at %#x", port->gameport.number, port->gameport.io);
150 	if (port->size > 1) printk(" size %d", port->size);
151 	printk(" speed %d kHz\n", port->gameport.speed);
152 
153 	return port;
154 }
155 
156 #if defined(CONFIG_ISAPNP) || (defined(CONFIG_ISAPNP_MODULE) && defined(MODULE))
157 #define NSS558_ISAPNP
158 #endif
159 
160 #ifdef NSS558_ISAPNP
161 
162 static struct isapnp_device_id pnp_devids[] = {
163 	{ ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('@','P','@'), ISAPNP_DEVICE(0x0001), 0 },
164 	{ ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('@','P','@'), ISAPNP_DEVICE(0x2001), 0 },
165 	{ ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('C','T','L'), ISAPNP_DEVICE(0x7001), 0 },
166 	{ ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('C','T','L'), ISAPNP_DEVICE(0x7002), 0 },
167 	{ ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('C','S','C'), ISAPNP_DEVICE(0x0010), 0 },
168 	{ ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('C','S','C'), ISAPNP_DEVICE(0x0110), 0 },
169 	{ ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('C','S','C'), ISAPNP_DEVICE(0x0b35), 0 },
170 	{ ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('C','S','C'), ISAPNP_DEVICE(0x0010), 0 },
171 	{ ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('C','S','C'), ISAPNP_DEVICE(0x0110), 0 },
172 	{ ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('P','N','P'), ISAPNP_DEVICE(0xb02f), 0 },
173 	{ 0, },
174 };
175 
176 MODULE_DEVICE_TABLE(isapnp, pnp_devids);
177 
ns558_pnp_probe(struct pci_dev * dev,struct ns558 * next)178 static struct ns558* ns558_pnp_probe(struct pci_dev *dev, struct ns558 *next)
179 {
180 	int ioport, iolen;
181 	struct ns558 *port;
182 
183 	if (dev->prepare && dev->prepare(dev) < 0)
184 		return next;
185 
186 	if (!(dev->resource[0].flags & IORESOURCE_IO)) {
187 		printk(KERN_WARNING "ns558: No i/o ports on a gameport? Weird\n");
188 		return next;
189 	}
190 
191 	if (dev->activate && dev->activate(dev) < 0) {
192 		printk(KERN_ERR "ns558: PnP resource allocation failed\n");
193 		return next;
194 	}
195 
196 	ioport = pci_resource_start(dev, 0);
197 	iolen = pci_resource_len(dev, 0);
198 
199 	if (!request_region(ioport, iolen, "ns558-pnp"))
200 		goto deactivate;
201 
202 	if (!(port = kmalloc(sizeof(struct ns558), GFP_KERNEL))) {
203 		printk(KERN_ERR "ns558: Memory allocation failed.\n");
204 		goto deactivate;
205 	}
206 	memset(port, 0, sizeof(struct ns558));
207 
208 	port->next = next;
209 	port->type = NS558_PNP;
210 	port->gameport.io = ioport;
211 	port->size = iolen;
212 	port->dev = dev;
213 
214 	gameport_register_port(&port->gameport);
215 
216 	printk(KERN_INFO "gameport%d: NS558 PnP at %#x", port->gameport.number, port->gameport.io);
217 	if (iolen > 1) printk(" size %d", iolen);
218 	printk(" speed %d kHz\n", port->gameport.speed);
219 
220 	return port;
221 
222 deactivate:
223 	if (dev->deactivate)
224 		dev->deactivate(dev);
225 	return next;
226 }
227 #endif
228 
ns558_init(void)229 int __init ns558_init(void)
230 {
231 	int i = 0;
232 #ifdef NSS558_ISAPNP
233 	struct isapnp_device_id *devid;
234 	struct pci_dev *dev = NULL;
235 #endif
236 
237 /*
238  * Probe for ISA ports.
239  */
240 
241 	while (ns558_isa_portlist[i])
242 		ns558 = ns558_isa_probe(ns558_isa_portlist[i++], ns558);
243 
244 /*
245  * Probe for PnP ports.
246  */
247 
248 #ifdef NSS558_ISAPNP
249 	for (devid = pnp_devids; devid->vendor; devid++) {
250 		while ((dev = isapnp_find_dev(NULL, devid->vendor, devid->function, dev))) {
251 			ns558 = ns558_pnp_probe(dev, ns558);
252 		}
253 	}
254 #endif
255 
256 	return ns558 ? 0 : -ENODEV;
257 }
258 
ns558_exit(void)259 void __exit ns558_exit(void)
260 {
261 	struct ns558 *next, *port = ns558;
262 
263 	while (port) {
264 		gameport_unregister_port(&port->gameport);
265 		switch (port->type) {
266 
267 #ifdef NSS558_ISAPNP
268 			case NS558_PNP:
269 				if (port->dev->deactivate)
270 					port->dev->deactivate(port->dev);
271 				/* fall through */
272 #endif
273 
274 			case NS558_ISA:
275 				release_region(port->gameport.io, port->size);
276 				break;
277 
278 			default:
279 				break;
280 		}
281 
282 		next = port->next;
283 		kfree(port);
284 		port = next;
285 	}
286 }
287 
288 module_init(ns558_init);
289 module_exit(ns558_exit);
290