Commit | Line | Data |
---|---|---|
1da177e4 | 1 | /* |
1da177e4 LT |
2 | * Copyright (c) 1999-2001 Vojtech Pavlik |
3 | * | |
4 | * Based on the work of: | |
ab0c3443 | 5 | * David Thompson |
1da177e4 LT |
6 | */ |
7 | ||
8 | /* | |
9 | * SpaceTec SpaceOrb 360 and Avenger 6dof controller driver for Linux | |
10 | */ | |
11 | ||
12 | /* | |
13 | * This program is free software; you can redistribute it and/or modify | |
14 | * it under the terms of the GNU General Public License as published by | |
15 | * the Free Software Foundation; either version 2 of the License, or | |
16 | * (at your option) any later version. | |
17 | * | |
18 | * This program is distributed in the hope that it will be useful, | |
19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
21 | * GNU General Public License for more details. | |
22 | * | |
23 | * You should have received a copy of the GNU General Public License | |
24 | * along with this program; if not, write to the Free Software | |
25 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
26 | * | |
27 | * Should you need to contact me, the author, you can do so either by | |
28 | * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: | |
29 | * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic | |
30 | */ | |
31 | ||
32 | #include <linux/kernel.h> | |
33 | #include <linux/slab.h> | |
34 | #include <linux/module.h> | |
35 | #include <linux/init.h> | |
36 | #include <linux/input.h> | |
37 | #include <linux/serio.h> | |
38 | ||
39 | #define DRIVER_DESC "SpaceTec SpaceOrb 360 and Avenger 6dof controller driver" | |
40 | ||
41 | MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); | |
42 | MODULE_DESCRIPTION(DRIVER_DESC); | |
43 | MODULE_LICENSE("GPL"); | |
44 | ||
45 | /* | |
46 | * Constants. | |
47 | */ | |
48 | ||
49 | #define SPACEORB_MAX_LENGTH 64 | |
50 | ||
51 | static int spaceorb_buttons[] = { BTN_TL, BTN_TR, BTN_Y, BTN_X, BTN_B, BTN_A }; | |
52 | static int spaceorb_axes[] = { ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ }; | |
1da177e4 LT |
53 | |
54 | /* | |
55 | * Per-Orb data. | |
56 | */ | |
57 | ||
58 | struct spaceorb { | |
17dd3f0f | 59 | struct input_dev *dev; |
1da177e4 LT |
60 | int idx; |
61 | unsigned char data[SPACEORB_MAX_LENGTH]; | |
62 | char phys[32]; | |
63 | }; | |
64 | ||
65 | static unsigned char spaceorb_xor[] = "SpaceWare"; | |
66 | ||
67 | static unsigned char *spaceorb_errors[] = { "EEPROM storing 0 failed", "Receive queue overflow", "Transmit queue timeout", | |
68 | "Bad packet", "Power brown-out", "EEPROM checksum error", "Hardware fault" }; | |
69 | ||
70 | /* | |
71 | * spaceorb_process_packet() decodes packets the driver receives from the | |
72 | * SpaceOrb. | |
73 | */ | |
74 | ||
7d12e780 | 75 | static void spaceorb_process_packet(struct spaceorb *spaceorb) |
1da177e4 | 76 | { |
17dd3f0f | 77 | struct input_dev *dev = spaceorb->dev; |
1da177e4 LT |
78 | unsigned char *data = spaceorb->data; |
79 | unsigned char c = 0; | |
80 | int axes[6]; | |
81 | int i; | |
82 | ||
83 | if (spaceorb->idx < 2) return; | |
84 | for (i = 0; i < spaceorb->idx; i++) c ^= data[i]; | |
85 | if (c) return; | |
86 | ||
1da177e4 LT |
87 | switch (data[0]) { |
88 | ||
89 | case 'R': /* Reset packet */ | |
90 | spaceorb->data[spaceorb->idx - 1] = 0; | |
91 | for (i = 1; i < spaceorb->idx && spaceorb->data[i] == ' '; i++); | |
17dd3f0f DT |
92 | printk(KERN_INFO "input: %s [%s] is %s\n", |
93 | dev->name, spaceorb->data + i, spaceorb->phys); | |
1da177e4 LT |
94 | break; |
95 | ||
96 | case 'D': /* Ball + button data */ | |
97 | if (spaceorb->idx != 12) return; | |
98 | for (i = 0; i < 9; i++) spaceorb->data[i+2] ^= spaceorb_xor[i]; | |
99 | axes[0] = ( data[2] << 3) | (data[ 3] >> 4); | |
100 | axes[1] = ((data[3] & 0x0f) << 6) | (data[ 4] >> 1); | |
101 | axes[2] = ((data[4] & 0x01) << 9) | (data[ 5] << 2) | (data[4] >> 5); | |
102 | axes[3] = ((data[6] & 0x1f) << 5) | (data[ 7] >> 2); | |
103 | axes[4] = ((data[7] & 0x03) << 8) | (data[ 8] << 1) | (data[7] >> 6); | |
104 | axes[5] = ((data[9] & 0x3f) << 4) | (data[10] >> 3); | |
105 | for (i = 0; i < 6; i++) | |
106 | input_report_abs(dev, spaceorb_axes[i], axes[i] - ((axes[i] & 0x200) ? 1024 : 0)); | |
107 | for (i = 0; i < 6; i++) | |
108 | input_report_key(dev, spaceorb_buttons[i], (data[1] >> i) & 1); | |
109 | break; | |
110 | ||
111 | case 'K': /* Button data */ | |
112 | if (spaceorb->idx != 5) return; | |
6c207e76 | 113 | for (i = 0; i < 6; i++) |
1da177e4 LT |
114 | input_report_key(dev, spaceorb_buttons[i], (data[2] >> i) & 1); |
115 | ||
116 | break; | |
117 | ||
118 | case 'E': /* Error packet */ | |
119 | if (spaceorb->idx != 4) return; | |
17dd3f0f | 120 | printk(KERN_ERR "spaceorb: Device error. [ "); |
1da177e4 LT |
121 | for (i = 0; i < 7; i++) if (data[1] & (1 << i)) printk("%s ", spaceorb_errors[i]); |
122 | printk("]\n"); | |
123 | break; | |
124 | } | |
125 | ||
126 | input_sync(dev); | |
127 | } | |
128 | ||
129 | static irqreturn_t spaceorb_interrupt(struct serio *serio, | |
7d12e780 | 130 | unsigned char data, unsigned int flags) |
1da177e4 LT |
131 | { |
132 | struct spaceorb* spaceorb = serio_get_drvdata(serio); | |
133 | ||
134 | if (~data & 0x80) { | |
7d12e780 | 135 | if (spaceorb->idx) spaceorb_process_packet(spaceorb); |
1da177e4 LT |
136 | spaceorb->idx = 0; |
137 | } | |
138 | if (spaceorb->idx < SPACEORB_MAX_LENGTH) | |
139 | spaceorb->data[spaceorb->idx++] = data & 0x7f; | |
140 | return IRQ_HANDLED; | |
141 | } | |
142 | ||
143 | /* | |
144 | * spaceorb_disconnect() is the opposite of spaceorb_connect() | |
145 | */ | |
146 | ||
147 | static void spaceorb_disconnect(struct serio *serio) | |
148 | { | |
149 | struct spaceorb* spaceorb = serio_get_drvdata(serio); | |
150 | ||
1da177e4 LT |
151 | serio_close(serio); |
152 | serio_set_drvdata(serio, NULL); | |
17dd3f0f | 153 | input_unregister_device(spaceorb->dev); |
1da177e4 LT |
154 | kfree(spaceorb); |
155 | } | |
156 | ||
157 | /* | |
158 | * spaceorb_connect() is the routine that is called when someone adds a | |
159 | * new serio device that supports SpaceOrb/Avenger protocol and registers | |
160 | * it as an input device. | |
161 | */ | |
162 | ||
163 | static int spaceorb_connect(struct serio *serio, struct serio_driver *drv) | |
164 | { | |
165 | struct spaceorb *spaceorb; | |
17dd3f0f DT |
166 | struct input_dev *input_dev; |
167 | int err = -ENOMEM; | |
168 | int i; | |
1da177e4 | 169 | |
17dd3f0f DT |
170 | spaceorb = kzalloc(sizeof(struct spaceorb), GFP_KERNEL); |
171 | input_dev = input_allocate_device(); | |
172 | if (!spaceorb || !input_dev) | |
127278ce | 173 | goto fail1; |
1da177e4 | 174 | |
17dd3f0f | 175 | spaceorb->dev = input_dev; |
10ca4c0a | 176 | snprintf(spaceorb->phys, sizeof(spaceorb->phys), "%s/input0", serio->phys); |
1da177e4 | 177 | |
17dd3f0f DT |
178 | input_dev->name = "SpaceTec SpaceOrb 360 / Avenger"; |
179 | input_dev->phys = spaceorb->phys; | |
180 | input_dev->id.bustype = BUS_RS232; | |
181 | input_dev->id.vendor = SERIO_SPACEORB; | |
182 | input_dev->id.product = 0x0001; | |
183 | input_dev->id.version = 0x0100; | |
935e658e | 184 | input_dev->dev.parent = &serio->dev; |
1da177e4 | 185 | |
7b19ada2 | 186 | input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); |
1da177e4 | 187 | |
17dd3f0f DT |
188 | for (i = 0; i < 6; i++) |
189 | set_bit(spaceorb_buttons[i], input_dev->keybit); | |
1da177e4 | 190 | |
17dd3f0f DT |
191 | for (i = 0; i < 6; i++) |
192 | input_set_abs_params(input_dev, spaceorb_axes[i], -508, 508, 0, 0); | |
1da177e4 LT |
193 | |
194 | serio_set_drvdata(serio, spaceorb); | |
195 | ||
196 | err = serio_open(serio, drv); | |
17dd3f0f | 197 | if (err) |
127278ce DT |
198 | goto fail2; |
199 | ||
200 | err = input_register_device(spaceorb->dev); | |
201 | if (err) | |
202 | goto fail3; | |
1da177e4 LT |
203 | |
204 | return 0; | |
17dd3f0f | 205 | |
127278ce DT |
206 | fail3: serio_close(serio); |
207 | fail2: serio_set_drvdata(serio, NULL); | |
208 | fail1: input_free_device(input_dev); | |
17dd3f0f DT |
209 | kfree(spaceorb); |
210 | return err; | |
1da177e4 LT |
211 | } |
212 | ||
213 | /* | |
214 | * The serio driver structure. | |
215 | */ | |
216 | ||
217 | static struct serio_device_id spaceorb_serio_ids[] = { | |
218 | { | |
219 | .type = SERIO_RS232, | |
220 | .proto = SERIO_SPACEORB, | |
221 | .id = SERIO_ANY, | |
222 | .extra = SERIO_ANY, | |
223 | }, | |
224 | { 0 } | |
225 | }; | |
226 | ||
227 | MODULE_DEVICE_TABLE(serio, spaceorb_serio_ids); | |
228 | ||
229 | static struct serio_driver spaceorb_drv = { | |
230 | .driver = { | |
231 | .name = "spaceorb", | |
232 | }, | |
233 | .description = DRIVER_DESC, | |
234 | .id_table = spaceorb_serio_ids, | |
235 | .interrupt = spaceorb_interrupt, | |
236 | .connect = spaceorb_connect, | |
237 | .disconnect = spaceorb_disconnect, | |
238 | }; | |
239 | ||
240 | /* | |
241 | * The functions for inserting/removing us as a module. | |
242 | */ | |
243 | ||
244 | static int __init spaceorb_init(void) | |
245 | { | |
153a9df0 | 246 | return serio_register_driver(&spaceorb_drv); |
1da177e4 LT |
247 | } |
248 | ||
249 | static void __exit spaceorb_exit(void) | |
250 | { | |
251 | serio_unregister_driver(&spaceorb_drv); | |
252 | } | |
253 | ||
254 | module_init(spaceorb_init); | |
255 | module_exit(spaceorb_exit); |