diff options
Diffstat (limited to 'drivers/hid/hid-3m-pct.c')
-rw-r--r-- | drivers/hid/hid-3m-pct.c | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/drivers/hid/hid-3m-pct.c b/drivers/hid/hid-3m-pct.c new file mode 100644 index 000000000000..2370aefc86b2 --- /dev/null +++ b/drivers/hid/hid-3m-pct.c | |||
@@ -0,0 +1,290 @@ | |||
1 | /* | ||
2 | * HID driver for 3M PCT multitouch panels | ||
3 | * | ||
4 | * Copyright (c) 2009 Stephane Chatty <chatty@enac.fr> | ||
5 | * | ||
6 | */ | ||
7 | |||
8 | /* | ||
9 | * This program is free software; you can redistribute it and/or modify it | ||
10 | * under the terms of the GNU General Public License as published by the Free | ||
11 | * Software Foundation; either version 2 of the License, or (at your option) | ||
12 | * any later version. | ||
13 | */ | ||
14 | |||
15 | #include <linux/device.h> | ||
16 | #include <linux/hid.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/usb.h> | ||
19 | |||
20 | MODULE_AUTHOR("Stephane Chatty <chatty@enac.fr>"); | ||
21 | MODULE_DESCRIPTION("3M PCT multitouch panels"); | ||
22 | MODULE_LICENSE("GPL"); | ||
23 | |||
24 | #include "hid-ids.h" | ||
25 | |||
26 | struct mmm_finger { | ||
27 | __s32 x, y; | ||
28 | __u8 rank; | ||
29 | bool touch, valid; | ||
30 | }; | ||
31 | |||
32 | struct mmm_data { | ||
33 | struct mmm_finger f[10]; | ||
34 | __u8 curid, num; | ||
35 | bool touch, valid; | ||
36 | }; | ||
37 | |||
38 | static int mmm_input_mapping(struct hid_device *hdev, struct hid_input *hi, | ||
39 | struct hid_field *field, struct hid_usage *usage, | ||
40 | unsigned long **bit, int *max) | ||
41 | { | ||
42 | switch (usage->hid & HID_USAGE_PAGE) { | ||
43 | |||
44 | case HID_UP_BUTTON: | ||
45 | return -1; | ||
46 | |||
47 | case HID_UP_GENDESK: | ||
48 | switch (usage->hid) { | ||
49 | case HID_GD_X: | ||
50 | hid_map_usage(hi, usage, bit, max, | ||
51 | EV_ABS, ABS_MT_POSITION_X); | ||
52 | /* touchscreen emulation */ | ||
53 | input_set_abs_params(hi->input, ABS_X, | ||
54 | field->logical_minimum, | ||
55 | field->logical_maximum, 0, 0); | ||
56 | return 1; | ||
57 | case HID_GD_Y: | ||
58 | hid_map_usage(hi, usage, bit, max, | ||
59 | EV_ABS, ABS_MT_POSITION_Y); | ||
60 | /* touchscreen emulation */ | ||
61 | input_set_abs_params(hi->input, ABS_Y, | ||
62 | field->logical_minimum, | ||
63 | field->logical_maximum, 0, 0); | ||
64 | return 1; | ||
65 | } | ||
66 | return 0; | ||
67 | |||
68 | case HID_UP_DIGITIZER: | ||
69 | switch (usage->hid) { | ||
70 | /* we do not want to map these: no input-oriented meaning */ | ||
71 | case 0x14: | ||
72 | case 0x23: | ||
73 | case HID_DG_INPUTMODE: | ||
74 | case HID_DG_DEVICEINDEX: | ||
75 | case HID_DG_CONTACTCOUNT: | ||
76 | case HID_DG_CONTACTMAX: | ||
77 | case HID_DG_INRANGE: | ||
78 | case HID_DG_CONFIDENCE: | ||
79 | return -1; | ||
80 | case HID_DG_TIPSWITCH: | ||
81 | /* touchscreen emulation */ | ||
82 | hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_TOUCH); | ||
83 | return 1; | ||
84 | case HID_DG_CONTACTID: | ||
85 | hid_map_usage(hi, usage, bit, max, | ||
86 | EV_ABS, ABS_MT_TRACKING_ID); | ||
87 | return 1; | ||
88 | } | ||
89 | /* let hid-input decide for the others */ | ||
90 | return 0; | ||
91 | |||
92 | case 0xff000000: | ||
93 | /* we do not want to map these: no input-oriented meaning */ | ||
94 | return -1; | ||
95 | } | ||
96 | |||
97 | return 0; | ||
98 | } | ||
99 | |||
100 | static int mmm_input_mapped(struct hid_device *hdev, struct hid_input *hi, | ||
101 | struct hid_field *field, struct hid_usage *usage, | ||
102 | unsigned long **bit, int *max) | ||
103 | { | ||
104 | if (usage->type == EV_KEY || usage->type == EV_ABS) | ||
105 | clear_bit(usage->code, *bit); | ||
106 | |||
107 | return 0; | ||
108 | } | ||
109 | |||
110 | /* | ||
111 | * this function is called when a whole packet has been received and processed, | ||
112 | * so that it can decide what to send to the input layer. | ||
113 | */ | ||
114 | static void mmm_filter_event(struct mmm_data *md, struct input_dev *input) | ||
115 | { | ||
116 | struct mmm_finger *oldest = 0; | ||
117 | bool pressed = false, released = false; | ||
118 | int i; | ||
119 | |||
120 | /* | ||
121 | * we need to iterate on all fingers to decide if we have a press | ||
122 | * or a release event in our touchscreen emulation. | ||
123 | */ | ||
124 | for (i = 0; i < 10; ++i) { | ||
125 | struct mmm_finger *f = &md->f[i]; | ||
126 | if (!f->valid) { | ||
127 | /* this finger is just placeholder data, ignore */ | ||
128 | } else if (f->touch) { | ||
129 | /* this finger is on the screen */ | ||
130 | input_event(input, EV_ABS, ABS_MT_TRACKING_ID, i); | ||
131 | input_event(input, EV_ABS, ABS_MT_POSITION_X, f->x); | ||
132 | input_event(input, EV_ABS, ABS_MT_POSITION_Y, f->y); | ||
133 | input_mt_sync(input); | ||
134 | /* | ||
135 | * touchscreen emulation: maintain the age rank | ||
136 | * of this finger, decide if we have a press | ||
137 | */ | ||
138 | if (f->rank == 0) { | ||
139 | f->rank = ++(md->num); | ||
140 | if (f->rank == 1) | ||
141 | pressed = true; | ||
142 | } | ||
143 | if (f->rank == 1) | ||
144 | oldest = f; | ||
145 | } else { | ||
146 | /* this finger took off the screen */ | ||
147 | /* touchscreen emulation: maintain age rank of others */ | ||
148 | int j; | ||
149 | |||
150 | for (j = 0; j < 10; ++j) { | ||
151 | struct mmm_finger *g = &md->f[j]; | ||
152 | if (g->rank > f->rank) { | ||
153 | g->rank--; | ||
154 | if (g->rank == 1) | ||
155 | oldest = g; | ||
156 | } | ||
157 | } | ||
158 | f->rank = 0; | ||
159 | --(md->num); | ||
160 | if (md->num == 0) | ||
161 | released = true; | ||
162 | } | ||
163 | f->valid = 0; | ||
164 | } | ||
165 | |||
166 | /* touchscreen emulation */ | ||
167 | if (oldest) { | ||
168 | if (pressed) | ||
169 | input_event(input, EV_KEY, BTN_TOUCH, 1); | ||
170 | input_event(input, EV_ABS, ABS_X, oldest->x); | ||
171 | input_event(input, EV_ABS, ABS_Y, oldest->y); | ||
172 | } else if (released) { | ||
173 | input_event(input, EV_KEY, BTN_TOUCH, 0); | ||
174 | } | ||
175 | } | ||
176 | |||
177 | /* | ||
178 | * this function is called upon all reports | ||
179 | * so that we can accumulate contact point information, | ||
180 | * and call input_mt_sync after each point. | ||
181 | */ | ||
182 | static int mmm_event(struct hid_device *hid, struct hid_field *field, | ||
183 | struct hid_usage *usage, __s32 value) | ||
184 | { | ||
185 | struct mmm_data *md = hid_get_drvdata(hid); | ||
186 | /* | ||
187 | * strangely, this function can be called before | ||
188 | * field->hidinput is initialized! | ||
189 | */ | ||
190 | if (hid->claimed & HID_CLAIMED_INPUT) { | ||
191 | struct input_dev *input = field->hidinput->input; | ||
192 | switch (usage->hid) { | ||
193 | case HID_DG_TIPSWITCH: | ||
194 | md->touch = value; | ||
195 | break; | ||
196 | case HID_DG_CONFIDENCE: | ||
197 | md->valid = value; | ||
198 | break; | ||
199 | case HID_DG_CONTACTID: | ||
200 | if (md->valid) { | ||
201 | md->curid = value; | ||
202 | md->f[value].touch = md->touch; | ||
203 | md->f[value].valid = 1; | ||
204 | } | ||
205 | break; | ||
206 | case HID_GD_X: | ||
207 | if (md->valid) | ||
208 | md->f[md->curid].x = value; | ||
209 | break; | ||
210 | case HID_GD_Y: | ||
211 | if (md->valid) | ||
212 | md->f[md->curid].y = value; | ||
213 | break; | ||
214 | case HID_DG_CONTACTCOUNT: | ||
215 | mmm_filter_event(md, input); | ||
216 | break; | ||
217 | } | ||
218 | } | ||
219 | |||
220 | /* we have handled the hidinput part, now remains hiddev */ | ||
221 | if (hid->claimed & HID_CLAIMED_HIDDEV && hid->hiddev_hid_event) | ||
222 | hid->hiddev_hid_event(hid, field, usage, value); | ||
223 | |||
224 | return 1; | ||
225 | } | ||
226 | |||
227 | static int mmm_probe(struct hid_device *hdev, const struct hid_device_id *id) | ||
228 | { | ||
229 | int ret; | ||
230 | struct mmm_data *md; | ||
231 | |||
232 | md = kzalloc(sizeof(struct mmm_data), GFP_KERNEL); | ||
233 | if (!md) { | ||
234 | dev_err(&hdev->dev, "cannot allocate 3M data\n"); | ||
235 | return -ENOMEM; | ||
236 | } | ||
237 | hid_set_drvdata(hdev, md); | ||
238 | |||
239 | ret = hid_parse(hdev); | ||
240 | if (!ret) | ||
241 | ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); | ||
242 | |||
243 | if (ret) | ||
244 | kfree(md); | ||
245 | return ret; | ||
246 | } | ||
247 | |||
248 | static void mmm_remove(struct hid_device *hdev) | ||
249 | { | ||
250 | hid_hw_stop(hdev); | ||
251 | kfree(hid_get_drvdata(hdev)); | ||
252 | hid_set_drvdata(hdev, NULL); | ||
253 | } | ||
254 | |||
255 | static const struct hid_device_id mmm_devices[] = { | ||
256 | { HID_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M1968) }, | ||
257 | { } | ||
258 | }; | ||
259 | MODULE_DEVICE_TABLE(hid, mmm_devices); | ||
260 | |||
261 | static const struct hid_usage_id mmm_grabbed_usages[] = { | ||
262 | { HID_ANY_ID, HID_ANY_ID, HID_ANY_ID }, | ||
263 | { HID_ANY_ID - 1, HID_ANY_ID - 1, HID_ANY_ID - 1} | ||
264 | }; | ||
265 | |||
266 | static struct hid_driver mmm_driver = { | ||
267 | .name = "3m-pct", | ||
268 | .id_table = mmm_devices, | ||
269 | .probe = mmm_probe, | ||
270 | .remove = mmm_remove, | ||
271 | .input_mapping = mmm_input_mapping, | ||
272 | .input_mapped = mmm_input_mapped, | ||
273 | .usage_table = mmm_grabbed_usages, | ||
274 | .event = mmm_event, | ||
275 | }; | ||
276 | |||
277 | static int __init mmm_init(void) | ||
278 | { | ||
279 | return hid_register_driver(&mmm_driver); | ||
280 | } | ||
281 | |||
282 | static void __exit mmm_exit(void) | ||
283 | { | ||
284 | hid_unregister_driver(&mmm_driver); | ||
285 | } | ||
286 | |||
287 | module_init(mmm_init); | ||
288 | module_exit(mmm_exit); | ||
289 | MODULE_LICENSE("GPL"); | ||
290 | |||