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