xref: /haiku/src/add-ons/kernel/bus_managers/ps2/ps2_alps.cpp (revision 02354704729d38c3b078c696adc1bbbd33cbcf72)
1 /*
2  * Copyright 2011, Haiku, Inc.
3  * Distributed under the terms of the MIT License.
4  *
5  * The alps_model_info struct and all the hardware specs are taken from the
6  * linux driver, thanks a lot!
7  *
8  * Authors:
9  *		Clemens Zeidler (haiku@Clemens-Zeidler.de)
10  */
11 
12 
13 #include "ps2_alps.h"
14 
15 #include <stdlib.h>
16 #include <string.h>
17 
18 #include "ps2_service.h"
19 
20 
21 //#define TRACE_PS2_ALPS
22 #ifdef TRACE_PS2_APLS
23 #	define TRACE(x...) dprintf(x)
24 #else
25 #	define TRACE(x...)
26 #endif
27 
28 
29 static int32 generate_event(timer* timer);
30 
31 
32 const bigtime_t kEventInterval = 1000 * 50;
33 
34 
35 class EventProducer {
36 public:
37 	EventProducer()
38 	{
39 		fFired = false;
40 	}
41 
42 	status_t
43 	FireEvent(alps_cookie* cookie, uint8* package)
44 	{
45 		fCookie = cookie;
46 		memcpy(fLastPackage, package, sizeof(uint8) * PS2_PACKET_ALPS);
47 
48 		status_t status = add_timer(&fEventTimer, &generate_event,
49 			kEventInterval, B_ONE_SHOT_RELATIVE_TIMER);
50 		if (status == B_OK)
51 			fFired  = true;
52 		return status;
53 	}
54 
55 	bool
56 	CancelEvent()
57 	{
58 		if (!fFired)
59 			return false;
60 		fFired = false;
61 		return cancel_timer(&fEventTimer);
62 	}
63 
64 	void
65 	InjectEvent()
66 	{
67 		if (packet_buffer_write(fCookie->ring_buffer, fLastPackage,
68 			PS2_PACKET_ALPS) != PS2_PACKET_ALPS) {
69 			// buffer is full, drop new data
70 			return;
71 		}
72 		release_sem_etc(fCookie->sem, 1, B_DO_NOT_RESCHEDULE);
73 	}
74 
75 private:
76 	bool				fFired;
77 	uint8				fLastPackage[PS2_PACKET_ALPS];
78 	timer				fEventTimer;
79 	alps_cookie*		fCookie;
80 };
81 
82 
83 static EventProducer gEventProducer;
84 
85 
86 static int32
87 generate_event(timer* timer)
88 {
89 	gEventProducer.InjectEvent();
90 	return B_HANDLED_INTERRUPT;
91 }
92 
93 
94 const char* kALPSPath[4] = {
95 	"input/touchpad/ps2/alps_0",
96 	"input/touchpad/ps2/alps_1",
97 	"input/touchpad/ps2/alps_2",
98 	"input/touchpad/ps2/alps_3"
99 };
100 
101 
102 typedef struct alps_model_info {
103 	uint8		id[3];
104 	uint8		firstByte;
105 	uint8		maskFirstByte;
106 	uint8		flags;
107 } alps_model_info;
108 
109 
110 #define ALPS_OLDPROTO           0x01	// old style input
111 #define ALPS_DUALPOINT          0x02	// touchpad has trackstick
112 #define ALPS_PASS               0x04    // device has a pass-through port
113 
114 #define ALPS_WHEEL              0x08	// hardware wheel present
115 #define ALPS_FW_BK_1            0x10	// front & back buttons present
116 #define ALPS_FW_BK_2            0x20	// front & back buttons present
117 #define ALPS_FOUR_BUTTONS       0x40	// 4 direction button present
118 #define ALPS_PS2_INTERLEAVED    0x80	// 3-byte PS/2 packet interleaved with
119 										// 6-byte ALPS packet
120 
121 static const struct alps_model_info gALPSModelInfos[] = {
122 	{{0x32, 0x02, 0x14}, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT},
123 		// Toshiba Salellite Pro M10
124 //	{{0x33, 0x02, 0x0a}, 0x88, 0xf8, ALPS_OLDPROTO},
125 		// UMAX-530T
126 	{{0x53, 0x02, 0x0a}, 0xf8, 0xf8, 0},
127 	{{0x53, 0x02, 0x14}, 0xf8, 0xf8, 0},
128 	{{0x60, 0x03, 0xc8}, 0xf8, 0xf8, 0},
129 		// HP ze1115
130 	{{0x63, 0x02, 0x0a}, 0xf8, 0xf8, 0},
131 	{{0x63, 0x02, 0x14}, 0xf8, 0xf8, 0},
132 	{{0x63, 0x02, 0x28}, 0xf8, 0xf8, ALPS_FW_BK_2},
133 		// Fujitsu Siemens S6010
134 //	{{0x63, 0x02, 0x3c}, 0x8f, 0x8f, ALPS_WHEEL},
135 		// Toshiba Satellite S2400-103
136 	{{0x63, 0x02, 0x50}, 0xef, 0xef, ALPS_FW_BK_1},
137 		// NEC Versa L320
138 	{{0x63, 0x02, 0x64}, 0xf8, 0xf8, 0},
139 	{{0x63, 0x03, 0xc8}, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT},
140 		// Dell Latitude D800
141 	{{0x73, 0x00, 0x0a}, 0xf8, 0xf8, ALPS_DUALPOINT},
142 		// ThinkPad R61 8918-5QG, x301
143 	{{0x73, 0x02, 0x0a}, 0xf8, 0xf8, 0},
144 	{{0x73, 0x02, 0x14}, 0xf8, 0xf8, ALPS_FW_BK_2},
145 		// Ahtec Laptop
146 	{{0x20, 0x02, 0x0e}, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT},
147 		// XXX
148 	{{0x22, 0x02, 0x0a}, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT},
149 	{{0x22, 0x02, 0x14}, 0xff, 0xff, ALPS_PASS | ALPS_DUALPOINT},
150 		// Dell Latitude D600
151 //	{{0x62, 0x02, 0x14}, 0xcf, 0xcf,  ALPS_PASS | ALPS_DUALPOINT
152 //		| ALPS_PS2_INTERLEAVED},
153 		// Dell Latitude E5500, E6400, E6500, Precision M4400
154 	{{0x73, 0x02, 0x50}, 0xcf, 0xcf, ALPS_FOUR_BUTTONS},
155 		// Dell Vostro 1400
156 //	{{0x52, 0x01, 0x14}, 0xff, 0xff, ALPS_PASS | ALPS_DUALPOINT
157 //		| ALPS_PS2_INTERLEAVED},
158 		// Toshiba Tecra A11-11L
159 	{{0, 0, 0}, 0, 0, 0}
160 };
161 
162 
163 static alps_model_info* sFoundModel = NULL;
164 
165 
166 // touchpad proportions
167 #define EDGE_MOTION_WIDTH	55
168 // increase the touchpad size a little bit
169 #define AREA_START_X		40
170 #define AREA_END_X			987
171 #define AREA_START_Y		40
172 #define AREA_END_Y			734
173 
174 #define MIN_PRESSURE		15
175 #define REAL_MAX_PRESSURE	70
176 #define MAX_PRESSURE		115
177 
178 
179 #define ALPS_HISTORY_SIZE	256
180 
181 
182 static hardware_specs gHardwareSpecs;
183 
184 
185 /* Data taken from linux driver:
186 ALPS absolute Mode - new format
187 byte 0:  1    ?    ?    ?    1    ?    ?    ?
188 byte 1:  0   x6   x5   x4   x3   x2   x1   x0
189 byte 2:  0  x10   x9   x8   x7    ?  fin  ges
190 byte 3:  0   y9   y8   y7    1    M    R    L
191 byte 4:  0   y6   y5   y4   y3   y2   y1   y0
192 byte 5:  0   z6   z5   z4   z3   z2   z1   z0
193 */
194 static status_t
195 get_alps_movment(alps_cookie *cookie, mouse_movement *movement)
196 {
197 	status_t status;
198 	touch_event event;
199 	uint8 event_buffer[PS2_PACKET_ALPS];
200 
201 	status = acquire_sem_etc(cookie->sem, 1, B_CAN_INTERRUPT, 0);
202 	if (status < B_OK)
203 		return status;
204 
205 	if (!cookie->dev->active) {
206 		TRACE("ALPS: read_event: Error device no longer active\n");
207 		return B_ERROR;
208 	}
209 
210 	if (packet_buffer_read(cookie->ring_buffer, event_buffer,
211 			cookie->dev->packet_size) != cookie->dev->packet_size) {
212 		TRACE("ALPS: error copying buffer\n");
213 		return B_ERROR;
214 	}
215 
216 	event.buttons = event_buffer[3] & 7;
217 	event.zPressure = event_buffer[5];
218 
219 	// finger on touchpad
220 	if (event_buffer[2] & 0x2) {
221 		// finger with normal width
222 		event.wValue = 4;
223 	} else {
224 		event.wValue = 3;
225 	}
226 
227 	// tab gesture
228 	if (event_buffer[2] & 0x1) {
229 		event.zPressure = 60;
230 		event.wValue = 4;
231 	}
232 	// if hardware tab gesture is off a z pressure of 16 is reported
233 	if (cookie->previousZ == 0 && event.wValue == 4 && event.zPressure == 16)
234 		event.zPressure = 60;
235 
236 	cookie->previousZ = event.zPressure;
237 
238 	event.xPosition = event_buffer[1] | ((event_buffer[2] & 0x78) << 4);
239 	event.yPosition = event_buffer[4] | ((event_buffer[3] & 0x70) << 3);
240 
241 	// check for trackpoint even (z pressure 127)
242 	if (sFoundModel->flags & ALPS_DUALPOINT && event.zPressure == 127) {
243 		movement->xdelta = event.xPosition > 383 ? event.xPosition - 768
244 			: event.xPosition;
245 		movement->ydelta = event.yPosition > 255
246 			? event.yPosition - 512 : event.yPosition;
247 		movement->wheel_xdelta = 0;
248 		movement->wheel_ydelta = 0;
249 		movement->buttons = event.buttons;
250 		movement->timestamp = system_time();
251 		cookie->movementMaker.UpdateButtons(movement);
252 	} else {
253 		event.yPosition = AREA_END_Y - (event.yPosition - AREA_START_Y);
254 		status = cookie->movementMaker.EventToMovement(&event, movement);
255 	}
256 
257 	if (cookie->movementMaker.WasEdgeMotion()
258 		|| cookie->movementMaker.TapDragStarted()) {
259 		gEventProducer.FireEvent(cookie, event_buffer);
260 	}
261 
262 	return status;
263 }
264 
265 
266 static void
267 default_settings(touchpad_settings *set)
268 {
269 	memcpy(set, &kDefaultTouchpadSettings, sizeof(touchpad_settings));
270 }
271 
272 
273 status_t
274 probe_alps(ps2_dev* dev)
275 {
276 	int i;
277 	uint8 val[3];
278 	TRACE("ALPS: probe\n");
279 
280 	val[0] = 0;
281 	if (ps2_dev_command(dev, PS2_CMD_MOUSE_SET_RES, val, 1, NULL, 0) != B_OK
282 		|| ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11, NULL, 0, NULL, 0)
283 			!= B_OK
284 		|| ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11, NULL, 0, NULL, 0)
285 			!= B_OK
286 		|| ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11, NULL, 0, NULL, 0)
287 			!= B_OK)
288 		return B_ERROR;
289 
290 	if (ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, val, 3)
291 		!= B_OK)
292 		return B_ERROR;
293 
294 	if (val[0] != 0 || val[1] != 0 || (val[2] != 10 && val[2] != 100))
295 		return B_ERROR;
296 
297 	val[0] = 0;
298 	if (ps2_dev_command(dev, PS2_CMD_MOUSE_SET_RES, val, 1, NULL, 0) != B_OK
299 		|| ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE21, NULL, 0, NULL, 0)
300 			!= B_OK
301 		|| ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE21, NULL, 0, NULL, 0)
302 			!= B_OK
303 		|| ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE21, NULL, 0, NULL, 0)
304 			!= B_OK)
305 		return B_ERROR;
306 
307 	if (ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, val, 3)
308 		!= B_OK)
309 		return B_ERROR;
310 
311 	for (i = 0; ; i++) {
312 		const alps_model_info* info = &gALPSModelInfos[i];
313 		if (info->id[0] == 0) {
314 			INFO("ALPS not supported: %2.2x %2.2x %2.2x\n", val[0], val[1],
315 				val[2]);
316 			return B_ERROR;
317 		}
318 
319 		if (info->id[0] == val[0] && info->id[1] == val[1]
320 			&& info->id[2] == val[2]) {
321 			sFoundModel = (alps_model_info*)info;
322 			INFO("ALPS found: %2.2x %2.2x %2.2x\n", val[0], val[1], val[2]);
323 			break;
324 		}
325 	}
326 
327 	dev->name = kALPSPath[dev->idx];
328 	dev->packet_size = PS2_PACKET_ALPS;
329 
330 	return B_OK;
331 }
332 
333 
334 status_t
335 switch_hardware_tab(ps2_dev* dev, bool on)
336 {
337 	uint8 val[3];
338 	uint8 arg = 0x00;
339 	uint8 command = PS2_CMD_MOUSE_SET_RES;
340 	if (on) {
341 		arg = 0x0A;
342 		command = PS2_CMD_SET_TYPEMATIC;
343 	}
344 	if (ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, val, 3) != B_OK
345 		|| ps2_dev_command(dev, PS2_CMD_DISABLE, NULL, 0, NULL, 0) != B_OK
346 		|| ps2_dev_command(dev, PS2_CMD_DISABLE, NULL, 0, NULL, 0) != B_OK
347 		|| ps2_dev_command(dev, command, &arg, 1, NULL, 0) != B_OK)
348 		return B_ERROR;
349 
350 	return B_OK;
351 }
352 
353 
354 status_t
355 enable_passthrough(ps2_dev* dev, bool on)
356 {
357 	uint8 command = PS2_CMD_MOUSE_SET_SCALE11;
358 	if (on)
359 		command = PS2_CMD_MOUSE_SET_SCALE21;
360 
361 	if (ps2_dev_command(dev, command, NULL, 0, NULL, 0) != B_OK
362 		|| ps2_dev_command(dev, command, NULL, 0, NULL, 0) != B_OK
363 		|| ps2_dev_command(dev, command, NULL, 0, NULL, 0) != B_OK
364 		|| ps2_dev_command(dev, PS2_CMD_DISABLE, NULL, 0, NULL, 0) != B_OK)
365 		return B_ERROR;
366 
367 	return B_OK;
368 }
369 
370 
371 status_t
372 alps_open(const char *name, uint32 flags, void **_cookie)
373 {
374 	ps2_dev* dev;
375 	int i;
376 	for (dev = NULL, i = 0; i < PS2_DEVICE_COUNT; i++) {
377 		if (0 == strcmp(ps2_device[i].name, name)) {
378 			dev = &ps2_device[i];
379 			break;
380 		}
381 	}
382 
383 	if (dev == NULL) {
384 		TRACE("ps2: dev = NULL\n");
385 		return B_ERROR;
386 	}
387 
388 	if (atomic_or(&dev->flags, PS2_FLAG_OPEN) & PS2_FLAG_OPEN)
389 		return B_BUSY;
390 
391 	alps_cookie* cookie = (alps_cookie*)malloc(sizeof(alps_cookie));
392 	if (cookie == NULL)
393 		goto err1;
394 	memset(cookie, 0, sizeof(*cookie));
395 
396 	cookie->movementMaker.Init();
397 	cookie->previousZ = 0;
398 	*_cookie = cookie;
399 
400 	cookie->dev = dev;
401 	dev->cookie = cookie;
402 	dev->disconnect = &alps_disconnect;
403 	dev->handle_int = &alps_handle_int;
404 
405 	default_settings(&cookie->settings);
406 
407 	gHardwareSpecs.edgeMotionWidth = EDGE_MOTION_WIDTH;
408 
409 	gHardwareSpecs.areaStartX = AREA_START_X;
410 	gHardwareSpecs.areaEndX = AREA_END_X;
411 	gHardwareSpecs.areaStartY = AREA_START_Y;
412 	gHardwareSpecs.areaEndY = AREA_END_Y;
413 
414 	gHardwareSpecs.minPressure = MIN_PRESSURE;
415 	gHardwareSpecs.realMaxPressure = REAL_MAX_PRESSURE;
416 	gHardwareSpecs.maxPressure = MAX_PRESSURE;
417 
418 	cookie->movementMaker.SetSettings(&cookie->settings);
419 	cookie->movementMaker.SetSpecs(&gHardwareSpecs);
420 
421 	dev->packet_size = PS2_PACKET_ALPS;
422 
423 	cookie->ring_buffer = create_packet_buffer(
424 		ALPS_HISTORY_SIZE * dev->packet_size);
425 	if (cookie->ring_buffer == NULL) {
426 		TRACE("ALPS: can't allocate mouse actions buffer\n");
427 		goto err2;
428 	}
429 	// create the mouse semaphore, used for synchronization between
430 	// the interrupt handler and the read operation
431 	cookie->sem = create_sem(0, "ps2_alps_sem");
432 	if (cookie->sem < 0) {
433 		TRACE("ALPS: failed creating semaphore!\n");
434 		goto err3;
435 	}
436 
437 	if ((sFoundModel->flags & ALPS_PASS) != 0
438 		&& enable_passthrough(dev, true) != B_OK)
439 		goto err4;
440 
441 	// switch tap mode off
442 	if (switch_hardware_tab(dev, false) != B_OK)
443 		goto err4;
444 
445 	// init the alps device to absolut mode
446 	if (ps2_dev_command(dev, PS2_CMD_DISABLE, NULL, 0, NULL, 0) != B_OK
447 		|| ps2_dev_command(dev, PS2_CMD_DISABLE, NULL, 0, NULL, 0) != B_OK
448 		|| ps2_dev_command(dev, PS2_CMD_DISABLE, NULL, 0, NULL, 0) != B_OK
449 		|| ps2_dev_command(dev, PS2_CMD_DISABLE, NULL, 0, NULL, 0) != B_OK
450 		|| ps2_dev_command(dev, PS2_CMD_ENABLE, NULL, 0, NULL, 0) != B_OK)
451 		goto err4;
452 
453 	if ((sFoundModel->flags & ALPS_PASS) != 0
454 		&& enable_passthrough(dev, false) != B_OK)
455 		goto err4;
456 
457 	if (ps2_dev_command(dev, PS2_CMD_MOUSE_SET_STREAM, NULL, 0, NULL, 0) != B_OK)
458 		goto err4;
459 
460 	if (ps2_dev_command(dev, PS2_CMD_ENABLE, NULL, 0, NULL, 0) != B_OK)
461 		goto err4;
462 
463 	atomic_or(&dev->flags, PS2_FLAG_ENABLED);
464 
465 	TRACE("ALPS: open %s success\n", name);
466 	return B_OK;
467 
468 err4:
469 	delete_sem(cookie->sem);
470 err3:
471 	delete_packet_buffer(cookie->ring_buffer);
472 err2:
473 	free(cookie);
474 err1:
475 	atomic_and(&dev->flags, ~PS2_FLAG_OPEN);
476 
477 	TRACE("ALPS: open %s failed\n", name);
478 	return B_ERROR;
479 }
480 
481 
482 status_t
483 alps_close(void *_cookie)
484 {
485 	gEventProducer.CancelEvent();
486 
487 	alps_cookie *cookie = (alps_cookie*)_cookie;
488 
489 	ps2_dev_command_timeout(cookie->dev, PS2_CMD_DISABLE, NULL, 0, NULL, 0,
490 		150000);
491 
492 	delete_packet_buffer(cookie->ring_buffer);
493 	delete_sem(cookie->sem);
494 
495 	atomic_and(&cookie->dev->flags, ~PS2_FLAG_OPEN);
496 	atomic_and(&cookie->dev->flags, ~PS2_FLAG_ENABLED);
497 
498 	// Reset the touchpad so it generate standard ps2 packets instead of
499 	// extended ones. If not, BeOS is confused with such packets when rebooting
500 	// without a complete shutdown.
501 	status_t status = ps2_reset_mouse(cookie->dev);
502 	if (status != B_OK) {
503 		INFO("ps2: reset failed\n");
504 		return B_ERROR;
505 	}
506 
507 	TRACE("ALPS: close %s done\n", cookie->dev->name);
508 	return B_OK;
509 }
510 
511 
512 status_t
513 alps_freecookie(void *_cookie)
514 {
515 	free(_cookie);
516 	return B_OK;
517 }
518 
519 
520 status_t
521 alps_ioctl(void *_cookie, uint32 op, void *buffer, size_t length)
522 {
523 	alps_cookie *cookie = (alps_cookie*)_cookie;
524 	mouse_movement movement;
525 	status_t status;
526 
527 	switch (op) {
528 		case MS_READ:
529 			TRACE("ALPS: MS_READ get event\n");
530 			if ((status = get_alps_movment(cookie, &movement)) != B_OK)
531 				return status;
532 			return user_memcpy(buffer, &movement, sizeof(movement));
533 
534 		case MS_IS_TOUCHPAD:
535 			TRACE("ALPS: MS_IS_TOUCHPAD\n");
536 			return B_OK;
537 
538 		case MS_SET_TOUCHPAD_SETTINGS:
539 			TRACE("ALPS: MS_SET_TOUCHPAD_SETTINGS");
540 			user_memcpy(&cookie->settings, buffer, sizeof(touchpad_settings));
541 			return B_OK;
542 
543 		case MS_SET_CLICKSPEED:
544 			TRACE("ALPS: ioctl MS_SETCLICK (set click speed)\n");
545 			return user_memcpy(&cookie->movementMaker.click_speed, buffer,
546 				sizeof(bigtime_t));
547 
548 		default:
549 			TRACE("ALPS: unknown opcode: %" B_PRIu32 "\n", op);
550 			return B_BAD_VALUE;
551 	}
552 }
553 
554 
555 static status_t
556 alps_read(void* cookie, off_t pos, void* buffer, size_t* _length)
557 {
558 	*_length = 0;
559 	return B_NOT_ALLOWED;
560 }
561 
562 
563 static status_t
564 alps_write(void* cookie, off_t pos, const void* buffer, size_t* _length)
565 {
566 	*_length = 0;
567 	return B_NOT_ALLOWED;
568 }
569 
570 
571 int32
572 alps_handle_int(ps2_dev* dev)
573 {
574 	alps_cookie* cookie = (alps_cookie*)dev->cookie;
575 
576 	// we got a real event cancel the fake event
577 	gEventProducer.CancelEvent();
578 
579 	uint8 val;
580 	val = cookie->dev->history[0].data;
581 	if (cookie->packet_index == 0
582 		&& (val & sFoundModel->maskFirstByte) != sFoundModel->firstByte) {
583 		INFO("ALPS: bad header, trying resync\n");
584 		cookie->packet_index = 0;
585 		return B_UNHANDLED_INTERRUPT;
586 	}
587 
588 	// data packages starting with a 0
589 	if (cookie->packet_index > 1 && (val & 0x80)) {
590 		INFO("ALPS: bad package data, trying resync\n");
591 		cookie->packet_index = 0;
592 		return B_UNHANDLED_INTERRUPT;
593 	}
594 
595  	cookie->buffer[cookie->packet_index] = val;
596 
597 	cookie->packet_index++;
598 	if (cookie->packet_index >= 6) {
599 		cookie->packet_index = 0;
600 
601 		if (packet_buffer_write(cookie->ring_buffer,
602 				cookie->buffer, cookie->dev->packet_size)
603 			!= cookie->dev->packet_size) {
604 			// buffer is full, drop new data
605 			return B_HANDLED_INTERRUPT;
606 		}
607 		release_sem_etc(cookie->sem, 1, B_DO_NOT_RESCHEDULE);
608 
609 		return B_INVOKE_SCHEDULER;
610 	}
611 
612 	return B_HANDLED_INTERRUPT;
613 }
614 
615 
616 void
617 alps_disconnect(ps2_dev *dev)
618 {
619 	alps_cookie *cookie = (alps_cookie*)dev->cookie;
620 	// the mouse device might not be opened at this point
621 	INFO("ALPS: alps_disconnect %s\n", dev->name);
622 	if ((dev->flags & PS2_FLAG_OPEN) != 0)
623 		release_sem(cookie->sem);
624 }
625 
626 
627 device_hooks gALPSDeviceHooks = {
628 	alps_open,
629 	alps_close,
630 	alps_freecookie,
631 	alps_ioctl,
632 	alps_read,
633 	alps_write,
634 };
635