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