1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (c) 1999-2001 Vojtech Pavlik |
4 | */ |
5 | |
6 | /* |
7 | * Creative Labs Blaster GamePad Cobra driver for Linux |
8 | */ |
9 | |
10 | #include <linux/kernel.h> |
11 | #include <linux/module.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/gameport.h> |
14 | #include <linux/input.h> |
15 | #include <linux/jiffies.h> |
16 | |
17 | #define DRIVER_DESC "Creative Labs Blaster GamePad Cobra driver" |
18 | |
19 | MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>" ); |
20 | MODULE_DESCRIPTION(DRIVER_DESC); |
21 | MODULE_LICENSE("GPL" ); |
22 | |
23 | #define COBRA_MAX_STROBE 45 /* 45 us max wait for first strobe */ |
24 | #define COBRA_LENGTH 36 |
25 | |
26 | static int cobra_btn[] = { BTN_START, BTN_SELECT, BTN_TL, BTN_TR, BTN_X, BTN_Y, BTN_Z, BTN_A, BTN_B, BTN_C, BTN_TL2, BTN_TR2, 0 }; |
27 | |
28 | struct cobra { |
29 | struct gameport *gameport; |
30 | struct input_dev *dev[2]; |
31 | int reads; |
32 | int bads; |
33 | unsigned char exists; |
34 | char phys[2][32]; |
35 | }; |
36 | |
37 | static unsigned char cobra_read_packet(struct gameport *gameport, unsigned int *data) |
38 | { |
39 | unsigned long flags; |
40 | unsigned char u, v, w; |
41 | __u64 buf[2]; |
42 | int r[2], t[2]; |
43 | int i, j, ret; |
44 | |
45 | int strobe = gameport_time(gameport, COBRA_MAX_STROBE); |
46 | |
47 | for (i = 0; i < 2; i++) { |
48 | r[i] = buf[i] = 0; |
49 | t[i] = COBRA_MAX_STROBE; |
50 | } |
51 | |
52 | local_irq_save(flags); |
53 | |
54 | u = gameport_read(gameport); |
55 | |
56 | do { |
57 | t[0]--; t[1]--; |
58 | v = gameport_read(gameport); |
59 | for (i = 0, w = u ^ v; i < 2 && w; i++, w >>= 2) |
60 | if (w & 0x30) { |
61 | if ((w & 0x30) < 0x30 && r[i] < COBRA_LENGTH && t[i] > 0) { |
62 | buf[i] |= (__u64)((w >> 5) & 1) << r[i]++; |
63 | t[i] = strobe; |
64 | u = v; |
65 | } else t[i] = 0; |
66 | } |
67 | } while (t[0] > 0 || t[1] > 0); |
68 | |
69 | local_irq_restore(flags); |
70 | |
71 | ret = 0; |
72 | |
73 | for (i = 0; i < 2; i++) { |
74 | |
75 | if (r[i] != COBRA_LENGTH) continue; |
76 | |
77 | for (j = 0; j < COBRA_LENGTH && (buf[i] & 0x04104107f) ^ 0x041041040; j++) |
78 | buf[i] = (buf[i] >> 1) | ((__u64)(buf[i] & 1) << (COBRA_LENGTH - 1)); |
79 | |
80 | if (j < COBRA_LENGTH) ret |= (1 << i); |
81 | |
82 | data[i] = ((buf[i] >> 7) & 0x000001f) | ((buf[i] >> 8) & 0x00003e0) |
83 | | ((buf[i] >> 9) & 0x0007c00) | ((buf[i] >> 10) & 0x00f8000) |
84 | | ((buf[i] >> 11) & 0x1f00000); |
85 | |
86 | } |
87 | |
88 | return ret; |
89 | } |
90 | |
91 | static void cobra_poll(struct gameport *gameport) |
92 | { |
93 | struct cobra *cobra = gameport_get_drvdata(gameport); |
94 | struct input_dev *dev; |
95 | unsigned int data[2]; |
96 | int i, j, r; |
97 | |
98 | cobra->reads++; |
99 | |
100 | if ((r = cobra_read_packet(gameport, data)) != cobra->exists) { |
101 | cobra->bads++; |
102 | return; |
103 | } |
104 | |
105 | for (i = 0; i < 2; i++) |
106 | if (cobra->exists & r & (1 << i)) { |
107 | |
108 | dev = cobra->dev[i]; |
109 | |
110 | input_report_abs(dev, ABS_X, value: ((data[i] >> 4) & 1) - ((data[i] >> 3) & 1)); |
111 | input_report_abs(dev, ABS_Y, value: ((data[i] >> 2) & 1) - ((data[i] >> 1) & 1)); |
112 | |
113 | for (j = 0; cobra_btn[j]; j++) |
114 | input_report_key(dev, code: cobra_btn[j], value: data[i] & (0x20 << j)); |
115 | |
116 | input_sync(dev); |
117 | |
118 | } |
119 | } |
120 | |
121 | static int cobra_open(struct input_dev *dev) |
122 | { |
123 | struct cobra *cobra = input_get_drvdata(dev); |
124 | |
125 | gameport_start_polling(gameport: cobra->gameport); |
126 | return 0; |
127 | } |
128 | |
129 | static void cobra_close(struct input_dev *dev) |
130 | { |
131 | struct cobra *cobra = input_get_drvdata(dev); |
132 | |
133 | gameport_stop_polling(gameport: cobra->gameport); |
134 | } |
135 | |
136 | static int cobra_connect(struct gameport *gameport, struct gameport_driver *drv) |
137 | { |
138 | struct cobra *cobra; |
139 | struct input_dev *input_dev; |
140 | unsigned int data[2]; |
141 | int i, j; |
142 | int err; |
143 | |
144 | cobra = kzalloc(size: sizeof(struct cobra), GFP_KERNEL); |
145 | if (!cobra) |
146 | return -ENOMEM; |
147 | |
148 | cobra->gameport = gameport; |
149 | |
150 | gameport_set_drvdata(gameport, data: cobra); |
151 | |
152 | err = gameport_open(gameport, drv, GAMEPORT_MODE_RAW); |
153 | if (err) |
154 | goto fail1; |
155 | |
156 | cobra->exists = cobra_read_packet(gameport, data); |
157 | |
158 | for (i = 0; i < 2; i++) |
159 | if ((cobra->exists >> i) & data[i] & 1) { |
160 | printk(KERN_WARNING "cobra.c: Device %d on %s has the Ext bit set. ID is: %d" |
161 | " Contact vojtech@ucw.cz\n" , i, gameport->phys, (data[i] >> 2) & 7); |
162 | cobra->exists &= ~(1 << i); |
163 | } |
164 | |
165 | if (!cobra->exists) { |
166 | err = -ENODEV; |
167 | goto fail2; |
168 | } |
169 | |
170 | gameport_set_poll_handler(gameport, handler: cobra_poll); |
171 | gameport_set_poll_interval(gameport, msecs: 20); |
172 | |
173 | for (i = 0; i < 2; i++) { |
174 | if (~(cobra->exists >> i) & 1) |
175 | continue; |
176 | |
177 | cobra->dev[i] = input_dev = input_allocate_device(); |
178 | if (!input_dev) { |
179 | err = -ENOMEM; |
180 | goto fail3; |
181 | } |
182 | |
183 | snprintf(buf: cobra->phys[i], size: sizeof(cobra->phys[i]), |
184 | fmt: "%s/input%d" , gameport->phys, i); |
185 | |
186 | input_dev->name = "Creative Labs Blaster GamePad Cobra" ; |
187 | input_dev->phys = cobra->phys[i]; |
188 | input_dev->id.bustype = BUS_GAMEPORT; |
189 | input_dev->id.vendor = GAMEPORT_ID_VENDOR_CREATIVE; |
190 | input_dev->id.product = 0x0008; |
191 | input_dev->id.version = 0x0100; |
192 | input_dev->dev.parent = &gameport->dev; |
193 | |
194 | input_set_drvdata(dev: input_dev, data: cobra); |
195 | |
196 | input_dev->open = cobra_open; |
197 | input_dev->close = cobra_close; |
198 | |
199 | input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); |
200 | input_set_abs_params(dev: input_dev, ABS_X, min: -1, max: 1, fuzz: 0, flat: 0); |
201 | input_set_abs_params(dev: input_dev, ABS_Y, min: -1, max: 1, fuzz: 0, flat: 0); |
202 | for (j = 0; cobra_btn[j]; j++) |
203 | set_bit(nr: cobra_btn[j], addr: input_dev->keybit); |
204 | |
205 | err = input_register_device(cobra->dev[i]); |
206 | if (err) |
207 | goto fail4; |
208 | } |
209 | |
210 | return 0; |
211 | |
212 | fail4: input_free_device(dev: cobra->dev[i]); |
213 | fail3: while (--i >= 0) |
214 | if (cobra->dev[i]) |
215 | input_unregister_device(cobra->dev[i]); |
216 | fail2: gameport_close(gameport); |
217 | fail1: gameport_set_drvdata(gameport, NULL); |
218 | kfree(objp: cobra); |
219 | return err; |
220 | } |
221 | |
222 | static void cobra_disconnect(struct gameport *gameport) |
223 | { |
224 | struct cobra *cobra = gameport_get_drvdata(gameport); |
225 | int i; |
226 | |
227 | for (i = 0; i < 2; i++) |
228 | if ((cobra->exists >> i) & 1) |
229 | input_unregister_device(cobra->dev[i]); |
230 | gameport_close(gameport); |
231 | gameport_set_drvdata(gameport, NULL); |
232 | kfree(objp: cobra); |
233 | } |
234 | |
235 | static struct gameport_driver cobra_drv = { |
236 | .driver = { |
237 | .name = "cobra" , |
238 | }, |
239 | .description = DRIVER_DESC, |
240 | .connect = cobra_connect, |
241 | .disconnect = cobra_disconnect, |
242 | }; |
243 | |
244 | module_gameport_driver(cobra_drv); |
245 | |