File: /usr/src/linux/drivers/char/joystick/ns558.c

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     
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     
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     
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     
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