1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (c) 1998-2001 Vojtech Pavlik |
4 | */ |
5 | |
6 | /* |
7 | * PDPI Lightning 4 gamecard driver for Linux. |
8 | */ |
9 | |
10 | #include <asm/io.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/errno.h> |
13 | #include <linux/ioport.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/module.h> |
16 | #include <linux/init.h> |
17 | #include <linux/gameport.h> |
18 | |
19 | #define L4_PORT 0x201 |
20 | #define L4_SELECT_ANALOG 0xa4 |
21 | #define L4_SELECT_DIGITAL 0xa5 |
22 | #define L4_SELECT_SECONDARY 0xa6 |
23 | #define L4_CMD_ID 0x80 |
24 | #define L4_CMD_GETCAL 0x92 |
25 | #define L4_CMD_SETCAL 0x93 |
26 | #define L4_ID 0x04 |
27 | #define L4_BUSY 0x01 |
28 | #define L4_TIMEOUT 80 /* 80 us */ |
29 | |
30 | MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>" ); |
31 | MODULE_DESCRIPTION("PDPI Lightning 4 gamecard driver" ); |
32 | MODULE_LICENSE("GPL" ); |
33 | |
34 | struct l4 { |
35 | struct gameport *gameport; |
36 | unsigned char port; |
37 | }; |
38 | |
39 | static struct l4 l4_ports[8]; |
40 | |
41 | /* |
42 | * l4_wait_ready() waits for the L4 to become ready. |
43 | */ |
44 | |
45 | static int l4_wait_ready(void) |
46 | { |
47 | unsigned int t = L4_TIMEOUT; |
48 | |
49 | while ((inb(L4_PORT) & L4_BUSY) && t > 0) t--; |
50 | return -(t <= 0); |
51 | } |
52 | |
53 | /* |
54 | * l4_cooked_read() reads data from the Lightning 4. |
55 | */ |
56 | |
57 | static int l4_cooked_read(struct gameport *gameport, int *axes, int *buttons) |
58 | { |
59 | struct l4 *l4 = gameport->port_data; |
60 | unsigned char status; |
61 | int i, result = -1; |
62 | |
63 | outb(L4_SELECT_ANALOG, L4_PORT); |
64 | outb(L4_SELECT_DIGITAL + (l4->port >> 2), L4_PORT); |
65 | |
66 | if (inb(L4_PORT) & L4_BUSY) goto fail; |
67 | outb(value: l4->port & 3, L4_PORT); |
68 | |
69 | if (l4_wait_ready()) goto fail; |
70 | status = inb(L4_PORT); |
71 | |
72 | for (i = 0; i < 4; i++) |
73 | if (status & (1 << i)) { |
74 | if (l4_wait_ready()) goto fail; |
75 | axes[i] = inb(L4_PORT); |
76 | if (axes[i] > 252) axes[i] = -1; |
77 | } |
78 | |
79 | if (status & 0x10) { |
80 | if (l4_wait_ready()) goto fail; |
81 | *buttons = inb(L4_PORT) & 0x0f; |
82 | } |
83 | |
84 | result = 0; |
85 | |
86 | fail: outb(L4_SELECT_ANALOG, L4_PORT); |
87 | return result; |
88 | } |
89 | |
90 | static int l4_open(struct gameport *gameport, int mode) |
91 | { |
92 | struct l4 *l4 = gameport->port_data; |
93 | |
94 | if (l4->port != 0 && mode != GAMEPORT_MODE_COOKED) |
95 | return -1; |
96 | outb(L4_SELECT_ANALOG, L4_PORT); |
97 | return 0; |
98 | } |
99 | |
100 | /* |
101 | * l4_getcal() reads the L4 with calibration values. |
102 | */ |
103 | |
104 | static int l4_getcal(int port, int *cal) |
105 | { |
106 | int i, result = -1; |
107 | |
108 | outb(L4_SELECT_ANALOG, L4_PORT); |
109 | outb(L4_SELECT_DIGITAL + (port >> 2), L4_PORT); |
110 | if (inb(L4_PORT) & L4_BUSY) |
111 | goto out; |
112 | |
113 | outb(L4_CMD_GETCAL, L4_PORT); |
114 | if (l4_wait_ready()) |
115 | goto out; |
116 | |
117 | if (inb(L4_PORT) != L4_SELECT_DIGITAL + (port >> 2)) |
118 | goto out; |
119 | |
120 | if (l4_wait_ready()) |
121 | goto out; |
122 | outb(value: port & 3, L4_PORT); |
123 | |
124 | for (i = 0; i < 4; i++) { |
125 | if (l4_wait_ready()) |
126 | goto out; |
127 | cal[i] = inb(L4_PORT); |
128 | } |
129 | |
130 | result = 0; |
131 | |
132 | out: outb(L4_SELECT_ANALOG, L4_PORT); |
133 | return result; |
134 | } |
135 | |
136 | /* |
137 | * l4_setcal() programs the L4 with calibration values. |
138 | */ |
139 | |
140 | static int l4_setcal(int port, int *cal) |
141 | { |
142 | int i, result = -1; |
143 | |
144 | outb(L4_SELECT_ANALOG, L4_PORT); |
145 | outb(L4_SELECT_DIGITAL + (port >> 2), L4_PORT); |
146 | if (inb(L4_PORT) & L4_BUSY) |
147 | goto out; |
148 | |
149 | outb(L4_CMD_SETCAL, L4_PORT); |
150 | if (l4_wait_ready()) |
151 | goto out; |
152 | |
153 | if (inb(L4_PORT) != L4_SELECT_DIGITAL + (port >> 2)) |
154 | goto out; |
155 | |
156 | if (l4_wait_ready()) |
157 | goto out; |
158 | outb(value: port & 3, L4_PORT); |
159 | |
160 | for (i = 0; i < 4; i++) { |
161 | if (l4_wait_ready()) |
162 | goto out; |
163 | outb(value: cal[i], L4_PORT); |
164 | } |
165 | |
166 | result = 0; |
167 | |
168 | out: outb(L4_SELECT_ANALOG, L4_PORT); |
169 | return result; |
170 | } |
171 | |
172 | /* |
173 | * l4_calibrate() calibrates the L4 for the attached device, so |
174 | * that the device's resistance fits into the L4's 8-bit range. |
175 | */ |
176 | |
177 | static int l4_calibrate(struct gameport *gameport, int *axes, int *max) |
178 | { |
179 | int i, t; |
180 | int cal[4]; |
181 | struct l4 *l4 = gameport->port_data; |
182 | |
183 | if (l4_getcal(port: l4->port, cal)) |
184 | return -1; |
185 | |
186 | for (i = 0; i < 4; i++) { |
187 | t = (max[i] * cal[i]) / 200; |
188 | t = (t < 1) ? 1 : ((t > 255) ? 255 : t); |
189 | axes[i] = (axes[i] < 0) ? -1 : (axes[i] * cal[i]) / t; |
190 | axes[i] = (axes[i] > 252) ? 252 : axes[i]; |
191 | cal[i] = t; |
192 | } |
193 | |
194 | if (l4_setcal(port: l4->port, cal)) |
195 | return -1; |
196 | |
197 | return 0; |
198 | } |
199 | |
200 | static int __init l4_create_ports(int card_no) |
201 | { |
202 | struct l4 *l4; |
203 | struct gameport *port; |
204 | int i, idx; |
205 | |
206 | for (i = 0; i < 4; i++) { |
207 | |
208 | idx = card_no * 4 + i; |
209 | l4 = &l4_ports[idx]; |
210 | |
211 | if (!(l4->gameport = port = gameport_allocate_port())) { |
212 | printk(KERN_ERR "lightning: Memory allocation failed\n" ); |
213 | while (--i >= 0) { |
214 | gameport_free_port(gameport: l4->gameport); |
215 | l4->gameport = NULL; |
216 | } |
217 | return -ENOMEM; |
218 | } |
219 | l4->port = idx; |
220 | |
221 | port->port_data = l4; |
222 | port->open = l4_open; |
223 | port->cooked_read = l4_cooked_read; |
224 | port->calibrate = l4_calibrate; |
225 | |
226 | gameport_set_name(gameport: port, name: "PDPI Lightning 4" ); |
227 | gameport_set_phys(gameport: port, fmt: "isa%04x/gameport%d" , L4_PORT, idx); |
228 | |
229 | if (idx == 0) |
230 | port->io = L4_PORT; |
231 | } |
232 | |
233 | return 0; |
234 | } |
235 | |
236 | static int __init l4_add_card(int card_no) |
237 | { |
238 | int cal[4] = { 255, 255, 255, 255 }; |
239 | int i, rev, result; |
240 | struct l4 *l4; |
241 | |
242 | outb(L4_SELECT_ANALOG, L4_PORT); |
243 | outb(L4_SELECT_DIGITAL + card_no, L4_PORT); |
244 | |
245 | if (inb(L4_PORT) & L4_BUSY) |
246 | return -1; |
247 | outb(L4_CMD_ID, L4_PORT); |
248 | |
249 | if (l4_wait_ready()) |
250 | return -1; |
251 | |
252 | if (inb(L4_PORT) != L4_SELECT_DIGITAL + card_no) |
253 | return -1; |
254 | |
255 | if (l4_wait_ready()) |
256 | return -1; |
257 | if (inb(L4_PORT) != L4_ID) |
258 | return -1; |
259 | |
260 | if (l4_wait_ready()) |
261 | return -1; |
262 | rev = inb(L4_PORT); |
263 | |
264 | if (!rev) |
265 | return -1; |
266 | |
267 | result = l4_create_ports(card_no); |
268 | if (result) |
269 | return result; |
270 | |
271 | printk(KERN_INFO "gameport: PDPI Lightning 4 %s card v%d.%d at %#x\n" , |
272 | card_no ? "secondary" : "primary" , rev >> 4, rev, L4_PORT); |
273 | |
274 | for (i = 0; i < 4; i++) { |
275 | l4 = &l4_ports[card_no * 4 + i]; |
276 | |
277 | if (rev > 0x28) /* on 2.9+ the setcal command works correctly */ |
278 | l4_setcal(port: l4->port, cal); |
279 | gameport_register_port(l4->gameport); |
280 | } |
281 | |
282 | return 0; |
283 | } |
284 | |
285 | static int __init l4_init(void) |
286 | { |
287 | int i, cards = 0; |
288 | |
289 | if (!request_region(L4_PORT, 1, "lightning" )) |
290 | return -EBUSY; |
291 | |
292 | for (i = 0; i < 2; i++) |
293 | if (l4_add_card(card_no: i) == 0) |
294 | cards++; |
295 | |
296 | outb(L4_SELECT_ANALOG, L4_PORT); |
297 | |
298 | if (!cards) { |
299 | release_region(L4_PORT, 1); |
300 | return -ENODEV; |
301 | } |
302 | |
303 | return 0; |
304 | } |
305 | |
306 | static void __exit l4_exit(void) |
307 | { |
308 | int i; |
309 | int cal[4] = { 59, 59, 59, 59 }; |
310 | |
311 | for (i = 0; i < 8; i++) |
312 | if (l4_ports[i].gameport) { |
313 | l4_setcal(port: l4_ports[i].port, cal); |
314 | gameport_unregister_port(gameport: l4_ports[i].gameport); |
315 | } |
316 | |
317 | outb(L4_SELECT_ANALOG, L4_PORT); |
318 | release_region(L4_PORT, 1); |
319 | } |
320 | |
321 | module_init(l4_init); |
322 | module_exit(l4_exit); |
323 | |