xref: /haiku/src/add-ons/kernel/bus_managers/ps2/ps2_elantech.cpp (revision 1a3518cf757c2da8006753f83962da5935bbc82b)
1 /*
2  * Copyright 2013, Haiku, Inc.
3  * Distributed under the terms of the MIT License.
4  *
5  * Hardware specs taken from the linux driver, thanks a lot!
6  * Based on ps2_alps.c
7  *
8  * Authors:
9  *		Jérôme Duval <korli@users.berlios.de>
10  */
11 
12 
13 #include "ps2_elantech.h"
14 
15 #include <stdlib.h>
16 #include <string.h>
17 
18 #include "ps2_service.h"
19 
20 
21 static int32 generate_event(timer* timer);
22 
23 
24 const bigtime_t kEventInterval = 1000 * 50;
25 
26 
27 class EventProducer {
28 public:
29 	EventProducer()
30 	{
31 		fFired = false;
32 	}
33 
34 	status_t
35 	FireEvent(elantech_cookie* cookie, uint8* package)
36 	{
37 		fCookie = cookie;
38 		memcpy(fLastPackage, package, sizeof(uint8) * PS2_PACKET_ELANTECH);
39 
40 		status_t status = add_timer(&fEventTimer, &generate_event,
41 			kEventInterval, B_ONE_SHOT_RELATIVE_TIMER);
42 		if (status == B_OK)
43 			fFired  = true;
44 		return status;
45 	}
46 
47 	bool
48 	CancelEvent()
49 	{
50 		if (!fFired)
51 			return false;
52 		fFired = false;
53 		return cancel_timer(&fEventTimer);
54 	}
55 
56 	void
57 	InjectEvent()
58 	{
59 		if (packet_buffer_write(fCookie->ring_buffer, fLastPackage,
60 			PS2_PACKET_ELANTECH) != PS2_PACKET_ELANTECH) {
61 			// buffer is full, drop new data
62 			return;
63 		}
64 		release_sem_etc(fCookie->sem, 1, B_DO_NOT_RESCHEDULE);
65 	}
66 
67 private:
68 	bool				fFired;
69 	uint8				fLastPackage[PS2_PACKET_ELANTECH];
70 	timer				fEventTimer;
71 	elantech_cookie*		fCookie;
72 };
73 
74 
75 static EventProducer gEventProducer;
76 
77 
78 static int32
79 generate_event(timer* timer)
80 {
81 	gEventProducer.InjectEvent();
82 	return B_HANDLED_INTERRUPT;
83 }
84 
85 
86 const char* kElantechPath[4] = {
87 	"input/touchpad/ps2/elantech_0",
88 	"input/touchpad/ps2/elantech_1",
89 	"input/touchpad/ps2/elantech_2",
90 	"input/touchpad/ps2/elantech_3"
91 };
92 
93 
94 #define ELANTECH_CMD_GET_ID				0x00
95 #define ELANTECH_CMD_GET_VERSION		0x01
96 #define ELANTECH_CMD_GET_CAPABILITIES	0x02
97 #define ELANTECH_CMD_GET_SAMPLE			0x03
98 #define ELANTECH_CMD_GET_RESOLUTION		0x04
99 
100 #define ELANTECH_CMD_REGISTER_READ		0x10
101 #define ELANTECH_CMD_REGISTER_WRITE		0x11
102 #define ELANTECH_CMD_REGISTER_READWRITE	0x00
103 #define ELANTECH_CMD_PS2_CUSTOM_CMD		0xf8
104 
105 
106 // touchpad proportions
107 #define EDGE_MOTION_WIDTH	55
108 
109 #define MIN_PRESSURE		0
110 #define REAL_MAX_PRESSURE	50
111 #define MAX_PRESSURE		255
112 
113 #define ELANTECH_HISTORY_SIZE	256
114 
115 #define STATUS_PACKET	0x0
116 #define HEAD_PACKET		0x1
117 #define MOTION_PACKET	0x2
118 
119 static hardware_specs gHardwareSpecs;
120 
121 
122 static status_t
123 get_elantech_movement(elantech_cookie *cookie, mouse_movement *movement)
124 {
125 	touch_event event;
126 	uint8 packet[PS2_PACKET_ELANTECH];
127 
128 	status_t status = acquire_sem_etc(cookie->sem, 1, B_CAN_INTERRUPT, 0);
129 	if (status < B_OK)
130 		return status;
131 
132 	if (!cookie->dev->active) {
133 		TRACE("ELANTECH: read_event: Error device no longer active\n");
134 		return B_ERROR;
135 	}
136 
137 	if (packet_buffer_read(cookie->ring_buffer, packet,
138 			cookie->dev->packet_size) != cookie->dev->packet_size) {
139 		TRACE("ELANTECH: error copying buffer\n");
140 		return B_ERROR;
141 	}
142 
143 	if (cookie->crcEnabled && (packet[3] & 0x08) != 0) {
144 		TRACE("ELANTECH: bad crc buffer\n");
145 		return B_ERROR;
146 	} else if (!cookie->crcEnabled && ((packet[0] & 0x0c) != 0x04
147 		|| (packet[3] & 0x1c) != 0x10)) {
148 		TRACE("ELANTECH: bad crc buffer\n");
149 		return B_ERROR;
150 	}
151 		// https://www.kernel.org/doc/html/v4.16/input/devices/elantech.html
152 	uint8 packet_type = packet[3] & 3;
153 	TRACE("ELANTECH: packet type %d\n", packet_type);
154 	TRACE("ELANTECH: packet content 0x%02x%02x%02x%02x%02x%02x\n",
155 		packet[0], packet[1], packet[2], packet[3],
156 		packet[4], packet[5]);
157 	switch (packet_type) {
158 		case STATUS_PACKET:
159 			//fingers, no palm
160 			cookie->fingers = (packet[4] & 0x80) == 0 ? packet[1] & 0x1f: 0;
161 			dprintf("ELANTECH: Fingers %" B_PRId32 ", raw %x (STATUS)\n",
162 				cookie->fingers, packet[1]);
163 			break;
164 		case HEAD_PACKET:
165 			dprintf("ELANTECH: Fingers %d, raw %x (HEAD)\n", (packet[3] & 0xe0) >>5, packet[3]);
166 			// only process first finger
167 			if ((packet[3] & 0xe0) != 0x20)
168 				return B_OK;
169 
170 			event.zPressure = (packet[1] & 0xf0) | ((packet[4] & 0xf0) >> 4);
171 
172 			cookie->previousZ = event.zPressure;
173 
174 			cookie->x = event.xPosition = ((packet[1] & 0xf) << 8) | packet[2];
175 			cookie->y = event.yPosition = ((packet[4] & 0xf) << 8) | packet[5];
176 			dprintf("ELANTECH: Pos: %" B_PRId32 ":%" B_PRId32 "\n (HEAD)",
177 				cookie->x, cookie->y);
178 			TRACE("ELANTECH: buttons 0x%x x %" B_PRIu32 " y %" B_PRIu32
179 				" z %d\n", event.buttons, event.xPosition, event.yPosition,
180 				event.zPressure);
181 			break;
182 		case MOTION_PACKET:
183 			dprintf("ELANTECH: Fingers %d, raw %x (MOTION)\n", (packet[3] & 0xe0) >>5, packet[3]);			//Most likely palm
184 			if (cookie->fingers == 0) return B_OK;
185 			//handle overflow and delta values
186 			if ((packet[0] & 0x10) != 0) {
187 				event.xPosition = cookie->x += 5 * (int8)packet[1];
188 				event.yPosition = cookie->y += 5 * (int8)packet[2];
189 			} else {
190 				event.xPosition = cookie->x += (int8)packet[1];
191 				event.yPosition = cookie->y += (int8)packet[2];
192 			}
193 			dprintf("ELANTECH: Pos: %" B_PRId32 ":%" B_PRId32 " (Motion)\n",
194 				cookie->x, cookie->y);
195 
196 			break;
197 		default:
198 			dprintf("ELANTECH: unknown packet type %d\n", packet_type);
199 			return B_ERROR;
200 	}
201 
202 	event.buttons = 0;
203 	event.wValue = cookie->fingers == 1 ? 4 :0;
204 	status = cookie->movementMaker.EventToMovement(&event, movement);
205 
206 	if (cookie->movementMaker.WasEdgeMotion()
207 		|| cookie->movementMaker.TapDragStarted()) {
208 		gEventProducer.FireEvent(cookie, packet);
209 	}
210 
211 	return status;
212 }
213 
214 
215 static void
216 default_settings(touchpad_settings *set)
217 {
218 	memcpy(set, &kDefaultTouchpadSettings, sizeof(touchpad_settings));
219 }
220 
221 
222 static status_t
223 synaptics_dev_send_command(ps2_dev* dev, uint8 cmd, uint8 *in, int in_count)
224 {
225 	if (ps2_dev_sliced_command(dev, cmd) != B_OK
226 		|| ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, in, in_count)
227 		!= B_OK) {
228 		TRACE("ELANTECH: synaptics_dev_send_command failed\n");
229 		return B_ERROR;
230 	}
231 	return B_OK;
232 }
233 
234 
235 static status_t
236 elantech_dev_send_command(ps2_dev* dev, uint8 cmd, uint8 *in, int in_count)
237 {
238 	if (ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK
239 		|| ps2_dev_command(dev, cmd) != B_OK
240 		|| ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, in, in_count)
241 		!= B_OK) {
242 		TRACE("ELANTECH: elantech_dev_send_command failed\n");
243 		return B_ERROR;
244 	}
245 	return B_OK;
246 }
247 
248 
249 status_t
250 probe_elantech(ps2_dev* dev)
251 {
252 	uint8 val[3];
253 	TRACE("ELANTECH: probe\n");
254 
255 	ps2_dev_command(dev, PS2_CMD_MOUSE_RESET_DIS);
256 
257 	if (ps2_dev_command(dev, PS2_CMD_DISABLE) != B_OK
258 		|| ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11) != B_OK
259 		|| ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11) != B_OK
260 		|| ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11) != B_OK) {
261 		TRACE("ELANTECH: not found (1)\n");
262 		return B_ERROR;
263 	}
264 
265 	if (ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, val, 3)
266 		!= B_OK) {
267 		TRACE("ELANTECH: not found (2)\n");
268 		return B_ERROR;
269 	}
270 
271 	if (val[0] != 0x3c || val[1] != 0x3 || (val[2] != 0xc8 && val[2] != 0x0)) {
272 		TRACE("ELANTECH: not found (3)\n");
273 		return B_ERROR;
274 	}
275 
276 	val[0] = 0;
277 	if (synaptics_dev_send_command(dev, ELANTECH_CMD_GET_VERSION, val, 3)
278 		!= B_OK) {
279 		TRACE("ELANTECH: not found (4)\n");
280 		return B_ERROR;
281 	}
282 
283 	if (val[0] == 0x0 || val[2] == 10 || val[2] == 20 || val[2] == 40
284 		|| val[2] == 60 || val[2] == 80 || val[2] == 100 || val[2] == 200) {
285 		TRACE("ELANTECH: not found (5)\n");
286 		return B_ERROR;
287 	}
288 
289 	INFO("Elantech version %02X%02X%02X, under developement! Using fallback.\n",
290 		val[0], val[1], val[2]);
291 
292 	dev->name = kElantechPath[dev->idx];
293 	dev->packet_size = PS2_PACKET_ELANTECH;
294 
295 	return B_ERROR;
296 }
297 
298 
299 static status_t
300 elantech_write_reg(elantech_cookie* cookie, uint8 reg, uint8 value)
301 {
302 	if (reg < 0x7 || reg > 0x26)
303 		return B_BAD_VALUE;
304 	if (reg > 0x11 && reg < 0x20)
305 		return B_BAD_VALUE;
306 
307 	ps2_dev* dev = cookie->dev;
308 	switch (cookie->version) {
309 		case 1:
310 			// TODO
311 			return B_ERROR;
312 			break;
313 		case 2:
314 			if (ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK
315 				|| ps2_dev_command(dev, ELANTECH_CMD_REGISTER_WRITE) != B_OK
316 				|| ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK
317 				|| ps2_dev_command(dev, reg) != B_OK
318 				|| ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK
319 				|| ps2_dev_command(dev, value) != B_OK
320 				|| ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11) != B_OK)
321 				return B_ERROR;
322 			break;
323 		case 3:
324 			if (ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK
325 				|| ps2_dev_command(dev, ELANTECH_CMD_REGISTER_READWRITE) != B_OK
326 				|| ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK
327 				|| ps2_dev_command(dev, reg) != B_OK
328 				|| ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK
329 				|| ps2_dev_command(dev, value) != B_OK
330 				|| ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11) != B_OK)
331 				return B_ERROR;
332 			break;
333 		case 4:
334 			if (ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK
335 				|| ps2_dev_command(dev, ELANTECH_CMD_REGISTER_READWRITE) != B_OK
336 				|| ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK
337 				|| ps2_dev_command(dev, reg) != B_OK
338 				|| ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK
339 				|| ps2_dev_command(dev, ELANTECH_CMD_REGISTER_READWRITE) != B_OK
340 				|| ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK
341 				|| ps2_dev_command(dev, value) != B_OK
342 				|| ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11) != B_OK)
343 				return B_ERROR;
344 			break;
345 		default:
346 			TRACE("ELANTECH: read_write_reg: unknown version\n");
347 			return B_ERROR;
348 	}
349 	return B_OK;
350 }
351 
352 
353 static status_t
354 elantech_read_reg(elantech_cookie* cookie, uint8 reg, uint8 *value)
355 {
356 	if (reg < 0x7 || reg > 0x26)
357 		return B_BAD_VALUE;
358 	if (reg > 0x11 && reg < 0x20)
359 		return B_BAD_VALUE;
360 
361 	ps2_dev* dev = cookie->dev;
362 	uint8 val[3];
363 	switch (cookie->version) {
364 		case 1:
365 			// TODO
366 			return B_ERROR;
367 			break;
368 		case 2:
369 			if (ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK
370 				|| ps2_dev_command(dev, ELANTECH_CMD_REGISTER_READ) != B_OK
371 				|| ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK
372 				|| ps2_dev_command(dev, reg) != B_OK
373 				|| ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, val,
374 					3) != B_OK)
375 				return B_ERROR;
376 			break;
377 		case 3:
378 		case 4:
379 			if (ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK
380 				|| ps2_dev_command(dev, ELANTECH_CMD_REGISTER_READWRITE)
381 					!= B_OK
382 				|| ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK
383 				|| ps2_dev_command(dev, reg) != B_OK
384 				|| ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, val,
385 					3) != B_OK)
386 				return B_ERROR;
387 			break;
388 		default:
389 			TRACE("ELANTECH: read_write_reg: unknown version\n");
390 			return B_ERROR;
391 	}
392 	if (cookie->version == 4)
393 		*value = val[1];
394 	else
395 		*value = val[0];
396 
397 	return B_OK;
398 }
399 
400 
401 static status_t
402 get_resolution_v4(elantech_cookie* cookie, uint32* x, uint32* y)
403 {
404 	uint8 val[3];
405 	if (elantech_dev_send_command(cookie->dev, ELANTECH_CMD_GET_RESOLUTION,
406 		val, 3) != B_OK)
407 		return B_ERROR;
408 	*x = (val[1] & 0xf) * 10 + 790;
409 	*y = ((val[1] & 0xf) >> 4) * 10 + 790;
410 	return B_OK;
411 }
412 
413 
414 static status_t
415 get_range(elantech_cookie* cookie, uint32* x_min, uint32* y_min, uint32* x_max,
416 	uint32* y_max, uint32 *width)
417 {
418 	uint8 val[3];
419 	switch (cookie->version) {
420 		case 1:
421 			*x_min = 32;
422 			*y_min = 32;
423 			*x_max = 544;
424 			*y_max = 344;
425 			*width = 0;
426 			break;
427 		case 2:
428 			// TODO
429 			break;
430 		case 3:
431 			if ((cookie->send_command)(cookie->dev, ELANTECH_CMD_GET_ID, val, 3)
432 				!= B_OK) {
433 				return B_ERROR;
434 			}
435 			*x_min = 0;
436 			*y_min = 0;
437 			*x_max = ((val[0] & 0xf) << 8) | val[1];
438 			*y_max = ((val[0] & 0xf0) << 4) | val[2];
439 			*width = 0;
440 			break;
441 		case 4:
442 			if ((cookie->send_command)(cookie->dev, ELANTECH_CMD_GET_ID, val, 3)
443 				!= B_OK) {
444 				return B_ERROR;
445 			}
446 			*x_min = 0;
447 			*y_min = 0;
448 			*x_max = ((val[0] & 0xf) << 8) | val[1];
449 			*y_max = ((val[0] & 0xf0) << 4) | val[2];
450 			if (cookie->capabilities[1] < 2 || cookie->capabilities[1] > *x_max)
451 				return B_ERROR;
452 			*width = *x_max / (cookie->capabilities[1] - 1);
453 			break;
454 	}
455 	return B_OK;
456 }
457 
458 
459 static status_t
460 enable_absolute_mode(elantech_cookie* cookie)
461 {
462 	status_t status = B_OK;
463 	switch (cookie->version) {
464 		case 1:
465 			status = elantech_write_reg(cookie, 0x10, 0x16);
466 			if (status == B_OK)
467 				status = elantech_write_reg(cookie, 0x11, 0x8f);
468 			break;
469 		case 2:
470 			status = elantech_write_reg(cookie, 0x10, 0x54);
471 			if (status == B_OK)
472 				status = elantech_write_reg(cookie, 0x11, 0x88);
473 			if (status == B_OK)
474 				status = elantech_write_reg(cookie, 0x12, 0x60);
475 			break;
476 		case 3:
477 			status = elantech_write_reg(cookie, 0x10, 0xb);
478 			break;
479 		case 4:
480 			status = elantech_write_reg(cookie, 0x7, 0x1);
481 			break;
482 
483 	}
484 
485 	if (cookie->version < 4) {
486 		uint8 val;
487 
488 		for (uint8 retry = 0; retry < 5; retry++) {
489 			status = elantech_read_reg(cookie, 0x10, &val);
490 			if (status != B_OK)
491 				break;
492 			snooze(100);
493 		}
494 	}
495 
496 	return status;
497 }
498 
499 
500 status_t
501 elantech_open(const char *name, uint32 flags, void **_cookie)
502 {
503 	TRACE("ELANTECH: open %s\n", name);
504 	ps2_dev* dev;
505 	int i;
506 	for (dev = NULL, i = 0; i < PS2_DEVICE_COUNT; i++) {
507 		if (0 == strcmp(ps2_device[i].name, name)) {
508 			dev = &ps2_device[i];
509 			break;
510 		}
511 	}
512 
513 	if (dev == NULL) {
514 		TRACE("ps2: dev = NULL\n");
515 		return B_ERROR;
516 	}
517 
518 	if (atomic_or(&dev->flags, PS2_FLAG_OPEN) & PS2_FLAG_OPEN)
519 		return B_BUSY;
520 
521 	uint32 x_min = 0, x_max = 0, y_min = 0, y_max = 0, width = 0;
522 
523 	elantech_cookie* cookie = (elantech_cookie*)malloc(
524 		sizeof(elantech_cookie));
525 	if (cookie == NULL)
526 		goto err1;
527 	memset(cookie, 0, sizeof(*cookie));
528 
529 	cookie->movementMaker.Init();
530 	cookie->previousZ = 0;
531 	*_cookie = cookie;
532 
533 	cookie->dev = dev;
534 	dev->cookie = cookie;
535 	dev->disconnect = &elantech_disconnect;
536 	dev->handle_int = &elantech_handle_int;
537 
538 	default_settings(&cookie->settings);
539 
540 	dev->packet_size = PS2_PACKET_ELANTECH;
541 
542 	cookie->ring_buffer = create_packet_buffer(
543 		ELANTECH_HISTORY_SIZE * dev->packet_size);
544 	if (cookie->ring_buffer == NULL) {
545 		TRACE("ELANTECH: can't allocate mouse actions buffer\n");
546 		goto err2;
547 	}
548 	// create the mouse semaphore, used for synchronization between
549 	// the interrupt handler and the read operation
550 	cookie->sem = create_sem(0, "ps2_elantech_sem");
551 	if (cookie->sem < 0) {
552 		TRACE("ELANTECH: failed creating semaphore!\n");
553 		goto err3;
554 	}
555 
556 	uint8 val[3];
557 	if (synaptics_dev_send_command(dev, ELANTECH_CMD_GET_VERSION, val, 3)
558 		!= B_OK) {
559 		TRACE("ELANTECH: get version failed!\n");
560 		goto err4;
561 	}
562 	cookie->fwVersion = (val[0] << 16) | (val[1] << 8) | val[2];
563 	if (cookie->fwVersion < 0x020030 || cookie->fwVersion == 0x020600)
564 		cookie->version = 1;
565 	else {
566 		switch (val[0] & 0xf) {
567 			case 2:
568 			case 4:
569 				cookie->version = 2;
570 				break;
571 			case 5:
572 				cookie->version = 3;
573 				break;
574 			case 6:
575 			case 7:
576 				cookie->version = 4;
577 				break;
578 			default:
579 				TRACE("ELANTECH: unknown version!\n");
580 				goto err4;
581 		}
582 	}
583 	INFO("ELANTECH: version 0x%" B_PRIu32 " (0x%" B_PRIu32 ")\n",
584 		cookie->version, cookie->fwVersion);
585 
586 	if (cookie->version >= 3)
587 		cookie->send_command = &elantech_dev_send_command;
588 	else
589 		cookie->send_command = &synaptics_dev_send_command;
590 	cookie->crcEnabled = (cookie->fwVersion & 0x4000) == 0x4000;
591 
592 	if ((cookie->send_command)(cookie->dev, ELANTECH_CMD_GET_CAPABILITIES,
593 		cookie->capabilities, 3) != B_OK) {
594 		TRACE("ELANTECH: get capabilities failed!\n");
595 		return B_ERROR;
596 	}
597 
598 	if (enable_absolute_mode(cookie) != B_OK) {
599 		TRACE("ELANTECH: failed enabling absolute mode!\n");
600 		goto err4;
601 	}
602 	TRACE("ELANTECH: enabled absolute mode!\n");
603 
604 	if (get_range(cookie, &x_min, &y_min, &x_max, &y_max, &width) != B_OK) {
605 		TRACE("ELANTECH: get range failed!\n");
606 		goto err4;
607 	}
608 
609 	TRACE("ELANTECH: range x %" B_PRIu32 "-%" B_PRIu32 " y %" B_PRIu32
610 		"-%" B_PRIu32 " (%" B_PRIu32 ")\n", x_min, x_max, y_min, y_max, width);
611 
612 	uint32 x_res, y_res;
613 	if (get_resolution_v4(cookie, &x_res, &y_res) != B_OK) {
614 		TRACE("ELANTECH: get resolution failed!\n");
615 		goto err4;
616 	}
617 
618 	TRACE("ELANTECH: resolution x %" B_PRIu32 " y %" B_PRIu32 " (dpi)\n",
619 		x_res, y_res);
620 
621 	gHardwareSpecs.edgeMotionWidth = EDGE_MOTION_WIDTH;
622 
623 	gHardwareSpecs.areaStartX = x_min;
624 	gHardwareSpecs.areaEndX = x_max;
625 	gHardwareSpecs.areaStartY = y_min;
626 	gHardwareSpecs.areaEndY = y_max;
627 
628 	gHardwareSpecs.minPressure = MIN_PRESSURE;
629 	gHardwareSpecs.realMaxPressure = REAL_MAX_PRESSURE;
630 	gHardwareSpecs.maxPressure = MAX_PRESSURE;
631 
632 	cookie->movementMaker.SetSettings(&cookie->settings);
633 	cookie->movementMaker.SetSpecs(&gHardwareSpecs);
634 
635 	if (ps2_dev_command(dev, PS2_CMD_ENABLE, NULL, 0, NULL, 0) != B_OK)
636 		goto err4;
637 
638 	atomic_or(&dev->flags, PS2_FLAG_ENABLED);
639 
640 	TRACE("ELANTECH: open %s success\n", name);
641 	return B_OK;
642 
643 err4:
644 	delete_sem(cookie->sem);
645 err3:
646 	delete_packet_buffer(cookie->ring_buffer);
647 err2:
648 	free(cookie);
649 err1:
650 	atomic_and(&dev->flags, ~PS2_FLAG_OPEN);
651 
652 	TRACE("ELANTECH: open %s failed\n", name);
653 	return B_ERROR;
654 }
655 
656 
657 status_t
658 elantech_close(void *_cookie)
659 {
660 	gEventProducer.CancelEvent();
661 
662 	elantech_cookie *cookie = (elantech_cookie*)_cookie;
663 
664 	ps2_dev_command_timeout(cookie->dev, PS2_CMD_DISABLE, NULL, 0, NULL, 0,
665 		150000);
666 
667 	delete_packet_buffer(cookie->ring_buffer);
668 	delete_sem(cookie->sem);
669 
670 	atomic_and(&cookie->dev->flags, ~PS2_FLAG_OPEN);
671 	atomic_and(&cookie->dev->flags, ~PS2_FLAG_ENABLED);
672 
673 	// Reset the touchpad so it generate standard ps2 packets instead of
674 	// extended ones. If not, BeOS is confused with such packets when rebooting
675 	// without a complete shutdown.
676 	status_t status = ps2_reset_mouse(cookie->dev);
677 	if (status != B_OK) {
678 		INFO("ps2: reset failed\n");
679 		return B_ERROR;
680 	}
681 
682 	TRACE("ELANTECH: close %s done\n", cookie->dev->name);
683 	return B_OK;
684 }
685 
686 
687 status_t
688 elantech_freecookie(void *_cookie)
689 {
690 	free(_cookie);
691 	return B_OK;
692 }
693 
694 
695 status_t
696 elantech_ioctl(void *_cookie, uint32 op, void *buffer, size_t length)
697 {
698 	elantech_cookie *cookie = (elantech_cookie*)_cookie;
699 	mouse_movement movement;
700 	status_t status;
701 
702 	switch (op) {
703 		case MS_READ:
704 			TRACE("ELANTECH: MS_READ get event\n");
705 			if ((status = get_elantech_movement(cookie, &movement)) != B_OK)
706 				return status;
707 			return user_memcpy(buffer, &movement, sizeof(movement));
708 
709 		case MS_IS_TOUCHPAD:
710 			TRACE("ELANTECH: MS_IS_TOUCHPAD\n");
711 			return B_OK;
712 
713 		case MS_SET_TOUCHPAD_SETTINGS:
714 			TRACE("ELANTECH: MS_SET_TOUCHPAD_SETTINGS\n");
715 			user_memcpy(&cookie->settings, buffer, sizeof(touchpad_settings));
716 			return B_OK;
717 
718 		case MS_SET_CLICKSPEED:
719 			TRACE("ELANTECH: ioctl MS_SETCLICK (set click speed)\n");
720 			return user_memcpy(&cookie->movementMaker.click_speed, buffer,
721 				sizeof(bigtime_t));
722 
723 		default:
724 			INFO("ELANTECH: unknown opcode: 0x%" B_PRIx32 "\n", op);
725 			return B_BAD_VALUE;
726 	}
727 }
728 
729 
730 static status_t
731 elantech_read(void* cookie, off_t pos, void* buffer, size_t* _length)
732 {
733 	*_length = 0;
734 	return B_NOT_ALLOWED;
735 }
736 
737 
738 static status_t
739 elantech_write(void* cookie, off_t pos, const void* buffer, size_t* _length)
740 {
741 	*_length = 0;
742 	return B_NOT_ALLOWED;
743 }
744 
745 
746 int32
747 elantech_handle_int(ps2_dev* dev)
748 {
749 	elantech_cookie* cookie = (elantech_cookie*)dev->cookie;
750 
751 	// we got a real event cancel the fake event
752 	gEventProducer.CancelEvent();
753 
754 	uint8 val;
755 	val = cookie->dev->history[0].data;
756  	cookie->buffer[cookie->packet_index] = val;
757 	cookie->packet_index++;
758 
759 	if (cookie->packet_index < PS2_PACKET_ELANTECH)
760 		return B_HANDLED_INTERRUPT;
761 
762 	cookie->packet_index = 0;
763 	if (packet_buffer_write(cookie->ring_buffer,
764 				cookie->buffer, cookie->dev->packet_size)
765 			!= cookie->dev->packet_size) {
766 			// buffer is full, drop new data
767 			return B_HANDLED_INTERRUPT;
768 		}
769 	release_sem_etc(cookie->sem, 1, B_DO_NOT_RESCHEDULE);
770 	return B_INVOKE_SCHEDULER;
771 }
772 
773 
774 void
775 elantech_disconnect(ps2_dev *dev)
776 {
777 	elantech_cookie *cookie = (elantech_cookie*)dev->cookie;
778 	// the mouse device might not be opened at this point
779 	INFO("ELANTECH: elantech_disconnect %s\n", dev->name);
780 	if ((dev->flags & PS2_FLAG_OPEN) != 0)
781 		release_sem(cookie->sem);
782 }
783 
784 
785 device_hooks gElantechDeviceHooks = {
786 	elantech_open,
787 	elantech_close,
788 	elantech_freecookie,
789 	elantech_ioctl,
790 	elantech_read,
791 	elantech_write,
792 };
793