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