/* * Copyright 2013-2025, Haiku, Inc. * Distributed under the terms of the MIT License. * * Hardware specs taken from the linux and BSDs drivers, thanks a lot! * * References: * - https://cgit.freebsd.org/src/tree/sys/dev/atkbdc/psm.c?h=releng/14.3 * - https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/dev/pckbc/?only_with_tag=OPENBSD_7_8_BASE * - https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/drivers/input/mouse/elantech.c?h=v6.17 * - https://www.kernel.org/doc/html/v4.16/input/devices/elantech.html * * Authors: * Jérôme Duval * Samuel Rodríguez Pérez */ #include "ps2_elantech.h" #include #include #include #include #include "ps2_service.h" //#define TRACE_PS2_ELANTECH #ifdef TRACE_PS2_ELANTECH # define TRACE(x...) dprintf(x) #else # define TRACE(x...) #endif const char* kElantechPath[4] = { "input/touchpad/ps2/elantech_0", "input/touchpad/ps2/elantech_1", "input/touchpad/ps2/elantech_2", "input/touchpad/ps2/elantech_3" }; #define ELANTECH_CMD_GET_ID 0x00 #define ELANTECH_CMD_GET_VERSION 0x01 #define ELANTECH_CMD_GET_CAPABILITIES 0x02 #define ELANTECH_CMD_GET_SAMPLE 0x03 #define ELANTECH_CMD_GET_RESOLUTION 0x04 #define ELANTECH_CMD_GET_ICBODY 0x05 #define ELANTECH_CMD_REGISTER_READ 0x10 #define ELANTECH_CMD_REGISTER_WRITE 0x11 #define ELANTECH_CMD_REGISTER_READWRITE 0x00 #define ELANTECH_CMD_PS2_CUSTOM_CMD 0xf8 // touchpad proportions #define EDGE_MOTION_WIDTH 55 #define MIN_PRESSURE 0 #define REAL_MAX_PRESSURE 50 #define MAX_PRESSURE 255 #define DEFAULT_PRESSURE 30 #define DEFAULT_FINGER_WIDTH 4 #define ELANTECH_HISTORY_SIZE 256 #define STATUS_PACKET 0x0 #define HEAD_PACKET 0x1 #define MOTION_PACKET 0x2 #define ELANTECH_XMIN_V2 0 #define ELANTECH_XMAX_V2 1152 #define ELANTECH_YMIN_V2 0 #define ELANTECH_YMAX_V2 768 #define ELANTECH_MAX_FINGERS 5 #define ELANTECH_CAP_HAS_ROCKER 4 #define ELANTECH_CAP_TRACKPOINT 0x80 // Error code used by MouseDevice::_ControlThread() in MouseInputDevice.cpp to reuse previous // event, basically ignoring the packet. #define IGNORE_EVENT B_BAD_DATA static touchpad_specs gHardwareSpecs; static status_t elantech_process_packet_v1(elantech_cookie *cookie, touchpad_movement *_event, uint8 packet[PS2_PACKET_ELANTECH]); static status_t elantech_process_packet_v2(elantech_cookie *cookie, touchpad_movement *_event, uint8 packet[PS2_PACKET_ELANTECH]); static status_t elantech_process_packet_v3(elantech_cookie *cookie, touchpad_movement *_event, uint8 packet[PS2_PACKET_ELANTECH]); static status_t elantech_process_packet_v4(elantech_cookie *cookie, touchpad_movement *_event, uint8 packet[PS2_PACKET_ELANTECH]); static status_t elantech_process_packet_trackpoint(elantech_cookie *cookie, mouse_movement *_movement, uint8 packet[PS2_PACKET_ELANTECH]); static inline void finger_width_hack(touchpad_movement *event) { // TODO: Improve fingerWidth computation by removing hack and updating other drivers. // FingerWidth values as per Synaptic driver "Finger width value". // This is currently used that way by movement_maker.cpp so keep this hack as of now. // Comments from FreeBSD psm.c driver for Synaptic protocol below: /* * Pressure value. * Interpretation: * z = 0 No finger contact * z = 10 Finger hovering near the pad * z = 30 Very light finger contact * z = 80 Normal finger contact * z = 110 Very heavy finger contact * z = 200 Finger lying flat on pad surface * z = 255 Maximum reportable Z */ /* * Finger width value * Interpretation: * w = 0 Two finger on the pad (capMultiFinger needed) * w = 1 Three or more fingers (capMultiFinger needed) * w = 2 Pen (instead of finger) (capPen needed) * w = 3 Reserved (passthrough?) * w = 4-7 Finger of normal width (capPalmDetect needed) * w = 8-14 Very wide finger or palm (capPalmDetect needed) * w = 15 Maximum reportable width (capPalmDetect needed) */ if (event->fingerWidth < 4) event->fingerWidth = 4; if (count_set_bits(event->fingers) == 2) event->fingerWidth = 0; else if (count_set_bits(event->fingers) > 2) event->fingerWidth = 1; } /* Common legend * L: Left mouse button pressed * R: Right mouse button pressed * N: number of fingers on touchpad * X: absolute x value (horizontal) * Y: absolute y value (vertical) * W; width of the finger touch * P: pressure */ static status_t get_elantech_movement(elantech_cookie *cookie, touchpad_read *_read) { touchpad_movement event = {}; mouse_movement movement {}; uint8 packet[PS2_PACKET_ELANTECH]; status_t status = acquire_sem_etc(cookie->sem, 1, B_CAN_INTERRUPT | B_RELATIVE_TIMEOUT, _read->timeout); if (status < B_OK) { TRACE("ELANTECH: acquire_sem_etc err or timeout.\n"); return status; } if (!cookie->dev->active) { TRACE("ELANTECH: read_event: Error device no longer active\n"); return B_ERROR; } if (packet_buffer_read(cookie->ring_buffer, packet, cookie->dev->packet_size) != cookie->dev->packet_size) { TRACE("ELANTECH: error copying buffer\n"); return B_ERROR; } if ((cookie->capabilities[0] & 0x80) != 0 && (packet[3] & 0x0f) == 0x06) { status = elantech_process_packet_trackpoint(cookie, &movement, packet); if (status == B_OK) { _read->event = MS_READ; _read->u.mouse = movement; } } else { switch (cookie->version) { case 1: status = elantech_process_packet_v1(cookie, &event, packet); break; case 2: status = elantech_process_packet_v2(cookie, &event, packet); break; case 3: status = elantech_process_packet_v3(cookie, &event, packet); break; case 4: status = elantech_process_packet_v4(cookie, &event, packet); break; default: TRACE("ELANTECH: Unknown version %d.\n", cookie->version); return B_ERROR; } if (status == B_OK) { _read->event = MS_READ_TOUCHPAD; // TODO: remove this hack finger_width_hack(&event); _read->u.touchpad = event; } } return status; } static status_t elantech_process_packet_v1(elantech_cookie *cookie, touchpad_movement *_event, uint8 packet[PS2_PACKET_ELANTECH_V1]) { /* HW V1. FW V1.x .*/ /* 7 6 5 4 3 2 1 0 (LSB) * ------------------------------------------- * ipacket[0]: RD RU p1 p2 1 p3 R L * ipacket[1]: f 0 3f 2f X9 X8 X9 X8 * ipacket[2]: X7 X6 X5 X4 X3 X2 X1 X0 * ipacket[3]: Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 * ------------------------------------------- * RD: Rocker switch pressed Down. * RU: Rocker switch pressed Up. * pn: Odd parity bit for byte n. * f: 1 when finger touch. * 2f: 1 when 2 finger touch. * 3f: 1 when 3 finger touch. */ /* HW V1. FW V2.x .*/ /* 7 6 5 4 3 2 1 0 (LSB) * ------------------------------------------- * ipacket[0]: N1 N0 p2 p1 1 p3 R L * ipacket[1]: . . . . X9 X8 X9 X8 * ipacket[2]: X7 X6 X5 X4 X3 X2 X1 X0 * ipacket[3]: Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 * ------------------------------------------- * pn: Odd parity bit for byte n. */ const bool kOddParityMap[256] = { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; uint8 p1, p2, p3; if (cookie->fwVersion < 0x020000) { p1 = (packet[0] & 0x20) >> 5; p2 = (packet[0] & 0x10) >> 4; } else { p1 = (packet[0] & 0x10) >> 4; p2 = (packet[0] & 0x20) >> 5; } p3 = (packet[0] & 0x04) >> 2; if (kOddParityMap[packet[1]] != p1 || kOddParityMap[packet[2]] != p2 || kOddParityMap[packet[3]] != p3) return IGNORE_EVENT; uint32 x, y; uint8 n, z; uint8 buttons; buttons = packet[0] & 3; if (cookie->capabilities[0] & ELANTECH_CAP_HAS_ROCKER) { if (packet[0] & 0x40) /* up */ buttons |= 1 << 4; if (packet[0] & 0x80) /* down */ buttons |= 1 << 5; } if (cookie->fwVersion < 0x20000 || cookie->fwVersion == 0x20600) n = ((packet[1] & 0x80) >> 7) + ((packet[1] & 0x30) >> 4); else n = (packet[0] & 0xc0) >> 6; /* * Firmwares 0x20022 and 0x20600 have a bug, position data in the * first two reports for single-touch contacts may be corrupt. */ if (cookie->fwVersion == 0x20022 || cookie->fwVersion == 0x20600) { if (n == 1) { if (cookie->quirkInitialPacketForV1 < 2) { cookie->quirkInitialPacketForV1++; return IGNORE_EVENT; } } else if (cookie->quirkInitialPacketForV1) { cookie->quirkInitialPacketForV1 = 0; } } /* Hardware version 1 doesn't report pressure. */ if (n) { x = ((packet[1] & 0x0c) << 6) | packet[2]; y = ((packet[1] & 0x03) << 8) | packet[3]; z = DEFAULT_PRESSURE; } else { x = y = z = 0; } touchpad_movement event = { .buttons = buttons, .xPosition = x, .yPosition = y, .zPressure = z, //.nFingers = n, .fingers = (uint8)((1 << n) - 1), .fingerWidth = DEFAULT_FINGER_WIDTH, }; *_event = event; return B_OK; } static status_t elantech_process_packet_v2(elantech_cookie *cookie, touchpad_movement *_event, uint8 packet[PS2_PACKET_ELANTECH]) { const uint8 debounce_pkt[] = { 0x84, 0xff, 0xff, 0x02, 0xff, 0xff }; uint32 x, y; uint8 n, w, z; uint8 buttons; /* * The hardware sends this packet when in debounce state. * The packet should be ignored. */ if (!memcmp(packet, debounce_pkt, sizeof(debounce_pkt))) return IGNORE_EVENT; // Constant bit checks based on FreeBSD psm.c and Linux drivers. bool valid = false; if (cookie->fwVersion >= 0x020800) if ((packet[0] & 0x0c) == 0x04 && (packet[3] & 0x0f) == 0x02) valid = true; if (!valid && ((packet[0] & 0xc0) == 0x80)) if ((packet[0] & 0x0c) == 0x0c && (packet[3] & 0x0e) == 0x08) valid = true; if (!valid && ((packet[0] & 0x3c) == 0x3c && (packet[1] & 0xf0) == 0x00 && (packet[3] & 0x3e) == 0x38 && (packet[4] & 0xf0) == 0x00)) valid = true; if (!valid) return IGNORE_EVENT; buttons = packet[0] & 3; n = (packet[0] & 0xc0) >> 6; if (n == 1 || n == 3) { /* HW V2. One/Three finger touch */ /* 7 6 5 4 3 2 1 0 (LSB) * ------------------------------------------- * ipacket[0]: N1 N0 W3 W2 . . R L * ipacket[1]: P7 P6 P5 P4 X11 X10 X9 X8 * ipacket[2]: X7 X6 X5 X4 X3 X2 X1 X0 * ipacket[3]: N4 VF W1 W0 . . . B2 * ipacket[4]: P3 P1 P2 P0 Y11 Y10 Y9 Y8 * ipacket[5]: Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 * ------------------------------------------- * N4: set if more than 3 fingers (only in 3 fingers mode) * VF: a kind of flag? (only on EF123, 0 when finger * is over one of the buttons, 1 otherwise) * B2: (on EF113 only, 0 otherwise), one button pressed * P & W is not reported on EF113 touchpads */ if (n == 3 && (packet[3] & 0x80)) n = 4; #if ELANTECH_EF113_MORE_THAN_TWO_BUTTONS // According to Linux and BSDs documentation, EF113 does not support multiple buttons // to be clicked at the same time but it's unclear to me that this is the case for left, // right and middle button as the implementation of their drivers differs here. // // For whatever reason neither Linux or any of the BSDs implement the documented // logic perhaps to be consistent with "HW V2. Two finger touch" packet handling // which only has 2 bits to provide button information. // So those drivers implementations threats L and R flags as independent left and right // buttons that can be pressed at the same time. That clashes with middle button mapping. if (cookie->fwVersion == 0x20800) { const int8 buttonsMap[] { 0x00, // 0 = none 0x01, // 1 = Left 0x02, // 2 = Right #if ELANTECH_EF113_MIDDLE_BUTTON_IS_LEFT_AND_RIGTH 0x04, // 3 = Middle or (Left and Right) // - Reports middle button // - Unable to report left and right buttons pressed at the same time #else 0x03, // 3 = Middle or (Left and Right) // - Reports left and right buttons pressed at the same time // - No middle button supported #endif 0x08, // 4 = Forward 0x10, // 5 = Back 0x20, // 6 = Another one 0x40, // 7 = Another one }; buttons = buttonsMap[((packet[3] & 1) << 3 ) + (packet[0] & 3)]; } #endif x = ((packet[1] & 0x0f) << 8) | packet[2]; y = ((packet[4] & 0x0f) << 8) | packet[5]; if (cookie->fwVersion >= 0x20800) { z = ((packet[1] & 0xf0) | (packet[4] & 0xf0) >> 4); w = ((packet[0] & 0x30) >> 2) | ((packet[3] & 0x30) >> 4); } else { z = DEFAULT_PRESSURE; w = DEFAULT_FINGER_WIDTH; } } else if (n == 2) { /* HW V2. Two finger touch */ /* 7 6 5 4 3 2 1 0 (LSB) * ------------------------------------------- * ipacket[0]: N1 N0 AY8 AX8 . . R L * ipacket[1]: AX7 AX6 AX5 AX4 AX3 AX2 AX1 AX0 * ipacket[2]: AY7 AY6 AY5 AY4 AY3 AY2 AY1 AY0 * ipacket[3]: . . BY8 BX8 . . . . * ipacket[4]: BX7 BX6 BX5 BX4 BX3 BX2 BX1 BX0 * ipacket[5]: BY7 BY6 BY5 BY4 BY3 BY2 BY1 BY0 * ------------------------------------------- * AX: lower-left finger absolute x value * AY: lower-left finger absolute y value * BX: upper-right finger absolute x value * BY: upper-right finger absolute y value */ #if ELANTECH_EF113_MORE_THAN_TWO_BUTTONS && ELANTECH_EF113_MIDDLE_BUTTON_IS_LEFT_AND_RIGTH if ((buttons & 3) == 3) buttons = 4; #endif x = (((packet[0] & 0x10) << 4) | packet[1]) << 2; y = (((packet[0] & 0x20) << 3) | packet[2]) << 2; z = DEFAULT_PRESSURE; w = DEFAULT_FINGER_WIDTH; } else { x = y = z = 0; w = DEFAULT_FINGER_WIDTH; } touchpad_movement event = { .buttons = buttons, .xPosition = x, .yPosition = y, .zPressure = z, //.nFingers = n, .fingers = (uint8)((1 << n) - 1), .fingerWidth = w, }; *_event = event; return B_OK; } static status_t elantech_process_packet_v3(elantech_cookie *cookie, touchpad_movement *_event, uint8 packet[PS2_PACKET_ELANTECH]) { /* 7 6 5 4 3 2 1 0 (LSB) * ------------------------------------------- * ipacket[0]: N1 N0 W3 W2 0 1 R L * ipacket[1]: P7 P6 P5 P4 X11 X10 X9 X8 * ipacket[2]: X7 X6 X5 X4 X3 X2 X1 X0 * ipacket[3]: 0 0 W1 W0 0 0 1 0 * ipacket[4]: P3 P1 P2 P0 Y11 Y10 Y9 Y8 * ipacket[5]: Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 * ------------------------------------------- */ // Validation based on FreeBSD psm.c driver. bool valid; valid = false; if (cookie->crcEnabled) { if ((packet[3] & 0x09) == 0x08 || (packet[3] & 0xcf) == 0x02) valid = true; } else if (((packet[0] & 0x0c) == 0x04 && (packet[3] & 0xcf) == 0x02) || ((packet[0] & 0x0c) == 0x0c && (packet[3] & 0xce) == 0x0c)) valid = true; if (!valid) return IGNORE_EVENT; const uint8 debounce_pkt[] = { 0xc4, 0xff, 0xff, 0x02, 0xff, 0xff }; uint32 x, y; uint8 n, w, z; uint8 buttons; buttons = packet[0] & 3; x = ((packet[1] & 0x0f) << 8 | packet[2]); y = ((packet[4] & 0x0f) << 8 | packet[5]); z = 0; n = (packet[0] & 0xc0) >> 6; if (n == 2) { /* * Two-finger touch causes two packets -- a head packet * and a tail packet. We report a single event and ignore * the tail packet. */ if (cookie->crcEnabled) { if ((packet[3] & 0x09) != 0x08) return IGNORE_EVENT; } else { /* The hardware sends this packet when in debounce state. * The packet should be ignored. */ if (!memcmp(packet, debounce_pkt, sizeof(debounce_pkt))) return IGNORE_EVENT; if ((packet[0] & 0x0c) != 0x04 && (packet[3] & 0xcf) != 0x02) { /* not the head packet -- ignore */ return IGNORE_EVENT; } } } /* Prevent jumping cursor if pad isn't touched or reports garbage. */ if (n == 0 || ((x == 0 || y == 0 || x == gHardwareSpecs.areaEndX || y == gHardwareSpecs.areaEndY) && (x != cookie->x || y != cookie->y))) { x = cookie->x; y = cookie->y; } if (cookie->fwVersion >= 0x020800) z = (packet[1] & 0xf0) | ((packet[4] & 0xf0) >> 4); else if (n) z = DEFAULT_PRESSURE; w = ((packet[0] & 0x30) >> 2) | ((packet[3] & 0x30) >> 4); cookie->x = x; cookie->y = y; touchpad_movement event = { .buttons = buttons, .xPosition = x, .yPosition = y, .zPressure = z, //.nFingers = n, .fingers = (uint8)((1 << n) - 1), .fingerWidth = w, }; *_event = event; return B_OK; } static status_t elantech_process_packet_v4(elantech_cookie *cookie, touchpad_movement *_event, uint8 packet[PS2_PACKET_ELANTECH]) { touchpad_movement event = {}; int invalidAt = 0; if (cookie->crcEnabled) { if ((packet[3] & 0x08) != 0x00) invalidAt = 1; } else if (cookie->icVersion == 7 && cookie->samples[1] == 0x2A) { if ((packet[3] & 0x1c) != 0x10) invalidAt = 2; } else { if (!((packet[0] & 0x08) == 0x00 && (packet[3] & 0x1c) == 0x10)) invalidAt = 3; } if (invalidAt != 0) { TRACE("ELANTECH: Failed v4 sanity check at %d.\n", invalidAt); return IGNORE_EVENT; } uint8 packet_type = packet[3] & 3; TRACE("ELANTECH: packet type %d\n", packet_type); TRACE("ELANTECH: packet content 0x%02x%02x%02x%02x%02x%02x\n", packet[0], packet[1], packet[2], packet[3], packet[4], packet[5]); switch (packet_type) { case STATUS_PACKET: /* 7 6 5 4 3 2 1 0 (LSB) * ------------------------------------------- * ipacket[0]: . . . . 0 1 R L * ipacket[1]: . . . F4 F3 F2 F1 F0 * ipacket[2]: . . . . . . . . * ipacket[3]: . . . 1 0 0 0 0 * ipacket[4]: PL . . . . . . . * ipacket[5]: . . . . . . . . * ------------------------------------------- * Fn: finger n is on touchpad * PL: palm * HV ver4 sends a status packet to indicate that the numbers * or identities of the fingers has been changed */ event.buttons = (packet[0] & 0x3); #ifdef ELANTECH_ENABLE_HARDWARE_PALM_DETECTION cookie->palm = (packet[4] & 0x80) != 0; if (cookie->palm) { TRACE("ELANTECH: Hardware palm detected (HEAD)\n"); return IGNORE_EVENT; } #endif // Event fingers contains a bitmap of fingers. event.fingers = packet[1] & 0x1f; TRACE("ELANTECH: Fingers bitmap %" B_PRId32 ", raw %x (STATUS)\n", event.fingers, packet[1]); // This is required for the mouse to not disapear after first iteration. event.xPosition = 0; event.yPosition = 0; // Pressure is not provided on this packet, so make it up with sensible values if (event.fingers == 0) event.zPressure = 0; else event.zPressure = DEFAULT_PRESSURE; TRACE("ELANTECH: Pos: %" B_PRId32 ":%" B_PRId32 " (STATUS)\n", cookie->x, cookie->y); break; case HEAD_PACKET: /* 7 6 5 4 3 2 1 0 (LSB) * ------------------------------------------- * ipacket[0]: W3 W2 W1 W0 0 1 R L * ipacket[1]: P7 P6 P5 P4 X11 X10 X9 X8 * ipacket[2]: X7 X6 X5 X4 X3 X2 X1 X0 * ipacket[3]: ID2 ID1 ID0 1 0 0 0 1 * ipacket[4]: P3 P1 P2 P0 Y11 Y10 Y9 Y8 * ipacket[5]: Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 * ------------------------------------------- * ID: finger id * HW ver 4 sends head packets in two cases: * 1. One finger touch and movement. * 2. Next after status packet to tell new finger positions. */ event.buttons = (packet[0] & 0x3); TRACE("ELANTECH: Finger id %d, raw %x (HEAD)\n", (packet[3] & 0xe0) >>5, packet[3]); int id; id = ((packet[3] & 0xe0) >> 5) - 1; if (id < 0 || id >= ELANTECH_MAX_FINGERS) { TRACE("ELANTECH: Not right fingers (HEAD)"); return IGNORE_EVENT; } #ifdef ELANTECH_ENABLE_HARDWARE_PALM_DETECTION if (cookie->palm) { TRACE("ELANTECH: Hardware palm detected (HEAD)\n"); return IGNORE_EVENT; } #endif // Head packet processes 1 finger only. Question is if the id is different than // the one provided by STATUS packet, is this a new finger or a replacement of // the previous ones? // As per testing let's assume a new finger is added. // That's in sync with the logic on BSDs and Linux drivers providing MT events. //event.fingers = cookie->fingers; event.fingers = cookie->fingers | (1 << id); //event.fingers = (1 << id); // only process first finger if (id != 0) { TRACE("ELANTECH: RET Only process first finger. (HEAD)\n"); return IGNORE_EVENT; } event.zPressure = (packet[1] & 0xf0) | ((packet[4] & 0xf0) >> 4); cookie->previousZ = event.zPressure; event.xPosition = ((packet[1] & 0xf) << 8) | packet[2]; event.yPosition = ((packet[4] & 0xf) << 8) | packet[5]; TRACE("ELANTECH: dx: %d dy: %d (HEAD)\n", (int)event.xPosition - (int)cookie->x, (int)event.yPosition - (int)cookie->y); cookie->x = event.xPosition; cookie->y = event.yPosition; TRACE("ELANTECH: Pos: %" B_PRId32 ":%" B_PRId32 " (HEAD)\n", cookie->x, cookie->y); TRACE("ELANTECH: buttons 0x%x x %" B_PRIu32 " y %" B_PRIu32 " z %d (HEAD)\n", event.buttons, event.xPosition, event.yPosition, event.zPressure); break; case MOTION_PACKET: /* 7 6 5 4 3 2 1 0 (LSB) * ------------------------------------------- * ipacket[0]: ID2 ID1 ID0 OF 0 1 R L * ipacket[1]: DX7 DX6 DX5 DX4 DX3 DX2 DX1 DX0 * ipacket[2]: DY7 DY6 DY5 DY4 DY3 DY2 DY1 DY0 * ipacket[3]: ID2 ID1 ID0 1 0 0 1 0 * ipacket[4]: DX7 DX6 DX5 DX4 DX3 DX2 DX1 DX0 * ipacket[5]: DY7 DY6 DY5 DY4 DY3 DY2 DY1 DY0 * ------------------------------------------- * OF: delta overflows (> 127 or < -128), in this case * firmware sends us (delta x / 5) and (delta y / 5) * ID: finger id * DX: delta x (two's complement) * XY: delta y (two's complement) * byte 0 ~ 2 for one finger * byte 3 ~ 5 for another finger */ event.buttons = (packet[0] & 0x3); // Pressure is not provided on this packet, so make it up with sensible values event.zPressure = DEFAULT_PRESSURE; TRACE("ELANTECH: Finger %d, raw %x (MOTION id)\n", (packet[0] & 0xe0) >>5, packet[0]); TRACE("ELANTECH: Finger %d, raw %x (MOTION sid)\n", (packet[3] & 0xe0) >>5, packet[3]); id = ((packet[0] & 0xe0) >> 5) - 1; int sid; sid = ((packet[3] & 0xe0) >> 5) - 1; // Motion packet processes 2 fingers only. Question is if the id is different than // the one provided by STATUS packet, are the fingers of these packet new fingers // or a replacement of the previous ones? // As per testing let's assume a new fingers are added. // That's in sync with the logic BSDs and Linux drivers providing MT events. //event.fingers = cookie->fingers; event.fingers = cookie->fingers | (1 << id) | (1 << sid); //event.fingers = (1 << id) | (1 << sid); if ((id < 0 || id >= ELANTECH_MAX_FINGERS) && (sid < 0 || sid >= ELANTECH_MAX_FINGERS)) { TRACE("ELANTECH: Not right fingers (MOTION)"); return IGNORE_EVENT; } #ifdef ELANTECH_ENABLE_HARDWARE_PALM_DETECTION if (cookie->palm) { TRACE("ELANTECH: Hardware palm detected (MOTION)\n"); return IGNORE_EVENT; } #endif int deltaX; int deltaY; deltaX = 0; deltaY = 0; int weight; weight = (packet[0] & 0x10) ? 5 : 1; // Only take care for finger id 0 for now. // TODO: Change this when support for Multi-finger is available on Haiku if (id == 0 || sid ==0) { if (id < 0 || id >= ELANTECH_MAX_FINGERS) event.fingers |= 1 << id; if (sid < 0 || sid >= ELANTECH_MAX_FINGERS) event.fingers |= 1 << sid; if (id == 0) { deltaX += weight * (int8)packet[1]; deltaY += weight * (int8)packet[2]; } else if (sid == 0) { deltaX += weight * (int8)packet[4]; deltaY += weight * (int8)packet[5]; } TRACE("ELANTECH: dx: %d dy: %d (MOTION)\n", deltaX, deltaY); } else { TRACE("ELANTECH: Ignore invalid or non 0 finger ids (MOTION)\n"); return IGNORE_EVENT; } // TODO: Avoid this conversion from rel to abs coordinates when Haiku supports both // type of coordinate systems for touchpads. Do the conversion as part of the driver // and pretend the device always provide absolute coordinates for the time being. // BEGIN: Conversion and tracking of abs coordinates: // Avoid underoverflow if (deltaX < 0 && (int)cookie->x < abs(deltaX)) deltaX = -cookie->x; if (deltaY < 0 && (int)cookie->y < abs(deltaY)) deltaY = -cookie->y; event.xPosition = cookie->x + deltaX; event.yPosition = cookie->y + deltaY; // Adjust to area boundaries if (event.xPosition < gHardwareSpecs.areaStartX) event.xPosition = gHardwareSpecs.areaStartX; if (event.xPosition > gHardwareSpecs.areaEndX) event.xPosition = gHardwareSpecs.areaEndX; if (event.yPosition < gHardwareSpecs.areaStartY) event.yPosition = gHardwareSpecs.areaStartY; if (event.yPosition > gHardwareSpecs.areaEndY) event.yPosition = gHardwareSpecs.areaEndY; cookie->x = event.xPosition; cookie->y = event.yPosition; // END: Conversion and tracking of abs coordinates TRACE("ELANTECH: Pos: %" B_PRId32 ":%" B_PRId32 " (MOTION)\n", cookie->x, cookie->y); break; default: dprintf("ELANTECH: unknown packet type %d\n", packet_type); return IGNORE_EVENT; } cookie->fingers = event.fingers; TRACE("ELANTECH: buttons %d\n", event.buttons); TRACE("ELANTECH: zPressure %d\n", event.zPressure); event.fingerWidth = DEFAULT_FINGER_WIDTH; *_event = event; return B_OK; } static status_t elantech_process_packet_trackpoint(elantech_cookie *cookie, mouse_movement *_movement, uint8 packet[PS2_PACKET_ELANTECH]) { /* 7 6 5 4 3 2 1 0 (LSB) * ------------------------------------------- * ipacket[0]: 0 0 SY SX 0 M R L * ipacket[1]: ~SX 0 0 0 0 0 0 0 * ipacket[2]: ~SY 0 0 0 0 0 0 0 * ipacket[3]: 0 0 ~SY ~SX 0 1 1 0 * ipacket[4]: X7 X6 X5 X4 X3 X2 X1 X0 * ipacket[5]: Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 * ------------------------------------------- * X and Y are written in two's complement spread * over 9 bits with SX/SY the relative top bit and * X7..X0 and Y7..Y0 the lower bits. */ int32 x, y; uint8 buttons; if (!(!(packet[0] & 0xC8) && !(packet[1] & 0x7F) && !(packet[2] & 0x7F) && !(packet[3] & 0xC9) && !(packet[0] & 0x10) != !(packet[1] & 0x80) && !(packet[0] & 0x10) != !(packet[3] & 0x10) && !(packet[0] & 0x20) != !(packet[2] & 0x80) && !(packet[0] & 0x20) != !(packet[3] & 0x20))) return IGNORE_EVENT; /* * This firmware misreport coordinates for trackpoint * occasionally. Discard packets outside of [-127, 127] range * to prevent cursor jumps. */ if (packet[4] == 0x80 || packet[5] == 0x80 || packet[1] >> 7 == packet[4] >> 7 || packet[2] >> 7 == packet[5] >> 7) return IGNORE_EVENT; x = packet[4] - 0x100 + (packet[1] << 1); y = packet[5] - 0x100 + (packet[2] << 1); buttons = packet[0] & 7; mouse_movement movement = { .buttons = buttons, .xdelta = x, .ydelta = y, .timestamp = system_time(), .wheel_ydelta = 0, .wheel_xdelta = 0, }; *_movement = movement; return B_OK; } static status_t synaptics_dev_send_command(ps2_dev* dev, uint8 cmd, uint8 *in, int in_count) { if (ps2_dev_sliced_command(dev, cmd) != B_OK || ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, in, in_count) != B_OK) { TRACE("ELANTECH: synaptics_dev_send_command failed\n"); return B_ERROR; } return B_OK; } static status_t elantech_dev_send_command(ps2_dev* dev, uint8 cmd, uint8 *in, int in_count) { if (ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK || ps2_dev_command(dev, cmd) != B_OK || ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, in, in_count) != B_OK) { TRACE("ELANTECH: elantech_dev_send_command failed\n"); return B_ERROR; } return B_OK; } // #pragma mark - exported functions status_t probe_elantech(ps2_dev* dev) { uint8 val[3]; TRACE("ELANTECH: probe\n"); ps2_dev_command(dev, PS2_CMD_MOUSE_RESET_DIS); if (ps2_dev_command(dev, PS2_CMD_DISABLE) != B_OK || ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11) != B_OK || ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11) != B_OK || ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11) != B_OK) { TRACE("ELANTECH: not found (1)\n"); return B_ERROR; } if (ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, val, 3) != B_OK) { TRACE("ELANTECH: not found (2)\n"); return B_ERROR; } if (val[0] != 0x3c || val[1] != 0x3 || (val[2] != 0xc8 && val[2] != 0x0)) { TRACE("ELANTECH: not found (3)\n"); return B_ERROR; } val[0] = 0; if (synaptics_dev_send_command(dev, ELANTECH_CMD_GET_VERSION, val, 3) != B_OK) { TRACE("ELANTECH: not found (4)\n"); return B_ERROR; } uint32 fwVersion = ((val[0] << 16) | (val[1] << 8) | val[2]); bool v1 = fwVersion < 0x020030 || fwVersion == 0x020600; bool v2 = (val[0] & 0xf) == 4 || ((val[0] & 0xf) == 2 && !v1); bool v3 = (val[0] & 0xf) == 5; bool v4 = (val[0] & 0xf) >= 6 && (val[0] & 0xf) <= 15; if (val[0] == 0x0 || val[2] == 10 || val[2] == 20 || val[2] == 40 || val[2] == 60 || val[2] == 80 || val[2] == 100 || val[2] == 200) { TRACE("ELANTECH: not found (5)\n"); return B_ERROR; } // TODO: Update supported after testing all versions. // Only hardware version 4 devices are known to be supported as of now. // v2 is known to be broken, see #19874. (void)v2; bool supported = v4 || v3 || v1; if (supported && v3) { uint8 samples[3]; if (elantech_dev_send_command(dev, ELANTECH_CMD_GET_SAMPLE, samples, 3) != B_OK) { TRACE("ELANTECH: failed to query sample data\n"); return B_ERROR; } if (samples[1] == 0x74) { TRACE("ELANTECH: Absolute mode broken, forcing standard PS/2 protocol\n"); supported = false; } } if (supported) { INFO("Elantech version %02X%02X%02X detected.\n", val[0], val[1], val[2]); } else { INFO("Elantech version %02X%02X%02X, under developement! Using fallback.\n", val[0], val[1], val[2]); } dev->name = kElantechPath[dev->idx]; dev->packet_size = v1 ? PS2_PACKET_ELANTECH_V1 : PS2_PACKET_ELANTECH; return supported ? B_OK : B_ERROR; } // #pragma mark - Setup functions static status_t elantech_write_reg(elantech_cookie* cookie, uint8 reg, uint8 value) { if (reg < 0x7 || reg > 0x26) return B_BAD_VALUE; if (reg > 0x11 && reg < 0x20) return B_BAD_VALUE; ps2_dev* dev = cookie->dev; switch (cookie->version) { case 1: if (ps2_dev_sliced_command(dev, ELANTECH_CMD_REGISTER_WRITE) != B_OK || ps2_dev_sliced_command(dev, reg) != B_OK || ps2_dev_sliced_command(dev, value) != B_OK || ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11)) return B_ERROR; break; case 2: if (ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK || ps2_dev_command(dev, ELANTECH_CMD_REGISTER_WRITE) != B_OK || ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK || ps2_dev_command(dev, reg) != B_OK || ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK || ps2_dev_command(dev, value) != B_OK || ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11) != B_OK) return B_ERROR; break; case 3: if (ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK || ps2_dev_command(dev, ELANTECH_CMD_REGISTER_READWRITE) != B_OK || ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK || ps2_dev_command(dev, reg) != B_OK || ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK || ps2_dev_command(dev, value) != B_OK || ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11) != B_OK) return B_ERROR; break; case 4: if (ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK || ps2_dev_command(dev, ELANTECH_CMD_REGISTER_READWRITE) != B_OK || ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK || ps2_dev_command(dev, reg) != B_OK || ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK || ps2_dev_command(dev, ELANTECH_CMD_REGISTER_READWRITE) != B_OK || ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK || ps2_dev_command(dev, value) != B_OK || ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11) != B_OK) return B_ERROR; break; default: TRACE("ELANTECH: read_write_reg: unknown version\n"); return B_ERROR; } return B_OK; } static status_t elantech_read_reg(elantech_cookie* cookie, uint8 reg, uint8 *value) { if (reg < 0x7 || reg > 0x26) return B_BAD_VALUE; if (reg > 0x11 && reg < 0x20) return B_BAD_VALUE; ps2_dev* dev = cookie->dev; uint8 val[3]; switch (cookie->version) { case 1: if (ps2_dev_sliced_command(dev, ELANTECH_CMD_REGISTER_READ) != B_OK || ps2_dev_sliced_command(dev, reg) != B_OK || ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, val, 3) != B_OK) return B_ERROR; break; case 2: if (ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK || ps2_dev_command(dev, ELANTECH_CMD_REGISTER_READ) != B_OK || ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK || ps2_dev_command(dev, reg) != B_OK || ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, val, 3) != B_OK) return B_ERROR; break; case 3: case 4: if (ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK || ps2_dev_command(dev, ELANTECH_CMD_REGISTER_READWRITE) != B_OK || ps2_dev_command(dev, ELANTECH_CMD_PS2_CUSTOM_CMD) != B_OK || ps2_dev_command(dev, reg) != B_OK || ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, val, 3) != B_OK) return B_ERROR; break; default: TRACE("ELANTECH: read_write_reg: unknown version\n"); return B_ERROR; } if (cookie->version == 4) *value = val[1]; else *value = val[0]; return B_OK; } static status_t get_resolution_v4(elantech_cookie* cookie, uint32* x, uint32* y) { uint8 val[3]; if (elantech_dev_send_command(cookie->dev, ELANTECH_CMD_GET_RESOLUTION, val, 3) != B_OK) return B_ERROR; *x = ((val[1] & 0xf) * 10 + 790) * 10 / 254; *y = (((val[1] & 0xf) >> 4) * 10 + 790) * 10 / 254; return B_OK; } static status_t get_range(elantech_cookie* cookie, uint32* x_min, uint32* y_min, uint32* x_max, uint32* y_max, uint32 *width) { uint8 val[3]; switch (cookie->version) { case 1: *x_min = 32; *y_min = 32; *x_max = 544; *y_max = 344; *width = 0; break; case 2: if (cookie->fwVersion == 0x020800 || cookie->fwVersion == 0x020b00 || cookie->fwVersion == 0x020030) { *x_min = ELANTECH_XMIN_V2; *y_min = ELANTECH_YMIN_V2; *x_max = ELANTECH_XMAX_V2; *y_max = ELANTECH_YMAX_V2; } else { int i; int fixed_dpi; i = (cookie->fwVersion > 0x020800 && cookie->fwVersion < 0x020900) ? 1 : 2; if ((cookie->send_command)(cookie->dev, ELANTECH_CMD_GET_ID, val, 3) != B_OK) return B_ERROR; fixed_dpi = val[1] & 0x10; if (((cookie->fwVersion >> 16) == 0x14) && fixed_dpi) { if ((cookie->send_command)(cookie->dev, ELANTECH_CMD_GET_SAMPLE, val, 3) != B_OK) return -EINVAL; *x_max = (cookie->capabilities[1] - i) * val[1] / 2; *y_max = (cookie->capabilities[2] - i) * val[2] / 2; } else if (cookie->fwVersion == 0x040216) { *x_max = 819; *y_max = 405; } else if (cookie->fwVersion == 0x040219 || cookie->fwVersion == 0x040215) { *x_max = 900; *y_max = 500; } else { *x_max = (cookie->capabilities[1] - i) * 64; *y_max = (cookie->capabilities[2] - i) * 64; } } break; case 3: if ((cookie->send_command)(cookie->dev, ELANTECH_CMD_GET_ID, val, 3) != B_OK) { return B_ERROR; } *x_min = 0; *y_min = 0; *x_max = ((val[0] & 0xf) << 8) | val[1]; *y_max = ((val[0] & 0xf0) << 4) | val[2]; *width = 0; break; case 4: if ((cookie->send_command)(cookie->dev, ELANTECH_CMD_GET_ID, val, 3) != B_OK) { return B_ERROR; } *x_min = 0; *y_min = 0; *x_max = ((val[0] & 0xf) << 8) | val[1]; *y_max = ((val[0] & 0xf0) << 4) | val[2]; if (cookie->capabilities[1] < 2 || cookie->capabilities[1] > *x_max) return B_ERROR; *width = *x_max / (cookie->capabilities[1] - 1); break; } return B_OK; } static status_t enable_absolute_mode(elantech_cookie* cookie) { status_t status = B_OK; switch (cookie->version) { case 1: status = elantech_write_reg(cookie, 0x10, 0x16); if (status == B_OK) status = elantech_write_reg(cookie, 0x11, 0x8f); break; case 2: status = elantech_write_reg(cookie, 0x10, 0x54); if (status == B_OK) status = elantech_write_reg(cookie, 0x11, 0x88); if (status == B_OK) status = elantech_write_reg(cookie, 0x12, 0x60); break; case 3: status = elantech_write_reg(cookie, 0x10, 0xb); break; case 4: status = elantech_write_reg(cookie, 0x7, 0x1); break; } if (status == B_OK && cookie->version < 4) { uint8 val; for (uint8 retry = 0; retry < 5; retry++) { status = elantech_read_reg(cookie, 0x10, &val); if (status != B_OK) break; snooze(2000); } } return status; } // #pragma mark - Device functions status_t elantech_open(const char *name, uint32 flags, void **_cookie) { TRACE("ELANTECH: open %s\n", name); ps2_dev* dev; int i; for (dev = NULL, i = 0; i < PS2_DEVICE_COUNT; i++) { if (0 == strcmp(ps2_device[i].name, name)) { dev = &ps2_device[i]; break; } } if (dev == NULL) { TRACE("ps2: dev = NULL\n"); return B_ERROR; } if (atomic_or(&dev->flags, PS2_FLAG_OPEN) & PS2_FLAG_OPEN) return B_BUSY; uint32 x_min = 0, x_max = 0, y_min = 0, y_max = 0, width = 0; elantech_cookie* cookie = (elantech_cookie*)malloc( sizeof(elantech_cookie)); if (cookie == NULL) goto err1; memset(cookie, 0, sizeof(*cookie)); cookie->previousZ = 0; *_cookie = cookie; cookie->dev = dev; dev->cookie = cookie; dev->disconnect = &elantech_disconnect; dev->handle_int = &elantech_handle_int; dev->packet_size = PS2_PACKET_ELANTECH; cookie->ring_buffer = create_packet_buffer( ELANTECH_HISTORY_SIZE * dev->packet_size); if (cookie->ring_buffer == NULL) { TRACE("ELANTECH: can't allocate mouse actions buffer\n"); goto err2; } // create the mouse semaphore, used for synchronization between // the interrupt handler and the read operation cookie->sem = create_sem(0, "ps2_elantech_sem"); if (cookie->sem < 0) { TRACE("ELANTECH: failed creating semaphore!\n"); goto err3; } uint8 val[3]; if (synaptics_dev_send_command(dev, ELANTECH_CMD_GET_VERSION, val, 3) != B_OK) { TRACE("ELANTECH: get version failed!\n"); goto err4; } cookie->fwVersion = (val[0] << 16) | (val[1] << 8) | val[2]; if (cookie->fwVersion < 0x020030 || cookie->fwVersion == 0x020600) cookie->version = 1; else { switch (val[0] & 0xf) { case 2: case 4: cookie->version = 2; break; case 5: cookie->version = 3; break; case 6 ... 15: cookie->version = 4; break; default: TRACE("ELANTECH: unknown version!\n"); goto err4; } } INFO("ELANTECH: version 0x%" B_PRIu32 " (0x%" B_PRIu32 ")\n", cookie->version, cookie->fwVersion); cookie->icVersion = (cookie->fwVersion & 0x0f0000) >> 16; if (cookie->version >= 3) cookie->send_command = &elantech_dev_send_command; else cookie->send_command = &synaptics_dev_send_command; cookie->crcEnabled = (cookie->fwVersion & 0x4000) == 0x4000; if ((cookie->send_command)(cookie->dev, ELANTECH_CMD_GET_CAPABILITIES, cookie->capabilities, 3) != B_OK) { TRACE("ELANTECH: get capabilities failed!\n"); return B_ERROR; } if (cookie->version != 1) { if (cookie->send_command(cookie->dev, ELANTECH_CMD_GET_SAMPLE, cookie->samples, 3) != B_OK) { TRACE("ELANTECH: failed to query sample data\n"); return B_ERROR; } if (cookie->samples[1] == 0x74 && cookie->version == 3) { TRACE("ELANTECH: absolute mode broken, forcing standard PS/2 protocol\n"); return B_ERROR; } } if (enable_absolute_mode(cookie) != B_OK) { TRACE("ELANTECH: failed enabling absolute mode!\n"); goto err4; } TRACE("ELANTECH: enabled absolute mode!\n"); if (get_range(cookie, &x_min, &y_min, &x_max, &y_max, &width) != B_OK) { TRACE("ELANTECH: get range failed!\n"); goto err4; } TRACE("ELANTECH: range x %" B_PRIu32 "-%" B_PRIu32 " y %" B_PRIu32 "-%" B_PRIu32 " (%" B_PRIu32 ")\n", x_min, x_max, y_min, y_max, width); uint32 x_res, y_res; x_res = 31; y_res = 31; if (cookie->version == 4) { if (get_resolution_v4(cookie, &x_res, &y_res) != B_OK) { TRACE("ELANTECH: get resolution failed!\n"); } } TRACE("ELANTECH: resolution x %" B_PRIu32 " y %" B_PRIu32 " (dpi)\n", x_res, y_res); gHardwareSpecs.edgeMotionWidth = EDGE_MOTION_WIDTH; gHardwareSpecs.areaStartX = x_min; gHardwareSpecs.areaEndX = x_max; gHardwareSpecs.areaStartY = y_min; gHardwareSpecs.areaEndY = y_max; gHardwareSpecs.minPressure = MIN_PRESSURE; gHardwareSpecs.realMaxPressure = REAL_MAX_PRESSURE; gHardwareSpecs.maxPressure = MAX_PRESSURE; if (ps2_dev_command(dev, PS2_CMD_ENABLE, NULL, 0, NULL, 0) != B_OK) goto err4; atomic_or(&dev->flags, PS2_FLAG_ENABLED); TRACE("ELANTECH: open %s success\n", name); return B_OK; err4: delete_sem(cookie->sem); err3: delete_packet_buffer(cookie->ring_buffer); err2: free(cookie); err1: atomic_and(&dev->flags, ~PS2_FLAG_OPEN); TRACE("ELANTECH: open %s failed\n", name); return B_ERROR; } status_t elantech_close(void *_cookie) { elantech_cookie *cookie = (elantech_cookie*)_cookie; ps2_dev_command_timeout(cookie->dev, PS2_CMD_DISABLE, NULL, 0, NULL, 0, 150000); delete_packet_buffer(cookie->ring_buffer); delete_sem(cookie->sem); atomic_and(&cookie->dev->flags, ~PS2_FLAG_OPEN); atomic_and(&cookie->dev->flags, ~PS2_FLAG_ENABLED); // Reset the touchpad so it generate standard ps2 packets instead of // extended ones. If not, BeOS is confused with such packets when rebooting // without a complete shutdown. status_t status = ps2_reset_mouse(cookie->dev); if (status != B_OK) { INFO("ps2_elantech: reset failed\n"); return B_ERROR; } TRACE("ELANTECH: close %s done\n", cookie->dev->name); return B_OK; } status_t elantech_freecookie(void *_cookie) { free(_cookie); return B_OK; } status_t elantech_ioctl(void *_cookie, uint32 op, void *buffer, size_t length) { elantech_cookie *cookie = (elantech_cookie*)_cookie; touchpad_read read; status_t status; switch (op) { case MS_IS_TOUCHPAD: TRACE("ELANTECH: MS_IS_TOUCHPAD\n"); if (buffer == NULL) return B_OK; return user_memcpy(buffer, &gHardwareSpecs, sizeof(gHardwareSpecs)); case MS_READ_TOUCHPAD: TRACE("ELANTECH: MS_READ_TOUCHPAD get event\n"); if (user_memcpy(&read.timeout, &(((touchpad_read*)buffer)->timeout), sizeof(bigtime_t)) != B_OK) return B_BAD_ADDRESS; if ((status = get_elantech_movement(cookie, &read)) != B_OK) { TRACE("ELANTECH: ioctl error with status: %s\n", strerror(status)); return status; } #ifdef TRACE_PS2_ELANTECH if (read.event = MS_READ_TOUCHPAD) { TRACE("ELANTECH: ioctl touchpad fingers: 0x%x\n", (int)read.u.touchpad.fingers); TRACE("ELANTECH: ioctl touchpad buttons: %d\n", (int)read.u.touchpad.buttons); } else if (read.event = MS_READ) { TRACE("ELANTECH: ioctl mouse buttons: %d\n", (int)read.u.mouse.buttons); } #endif return user_memcpy(buffer, &read, sizeof(read)); default: INFO("ELANTECH: unknown opcode: 0x%" B_PRIx32 "\n", op); return B_BAD_VALUE; } } static status_t elantech_read(void* cookie, off_t pos, void* buffer, size_t* _length) { *_length = 0; return B_NOT_ALLOWED; } static status_t elantech_write(void* cookie, off_t pos, const void* buffer, size_t* _length) { *_length = 0; return B_NOT_ALLOWED; } int32 elantech_handle_int(ps2_dev* dev) { elantech_cookie* cookie = (elantech_cookie*)dev->cookie; uint8 val; val = cookie->dev->history[0].data; cookie->buffer[cookie->packet_index] = val; cookie->packet_index++; if (cookie->packet_index < PS2_PACKET_ELANTECH) return B_HANDLED_INTERRUPT; cookie->packet_index = 0; if (packet_buffer_write(cookie->ring_buffer, cookie->buffer, cookie->dev->packet_size) != cookie->dev->packet_size) { // buffer is full, drop new data return B_HANDLED_INTERRUPT; } release_sem_etc(cookie->sem, 1, B_DO_NOT_RESCHEDULE); return B_INVOKE_SCHEDULER; } void elantech_disconnect(ps2_dev *dev) { elantech_cookie *cookie = (elantech_cookie*)dev->cookie; // the mouse device might not be opened at this point INFO("ELANTECH: elantech_disconnect %s\n", dev->name); if ((dev->flags & PS2_FLAG_OPEN) != 0) release_sem(cookie->sem); } device_hooks gElantechDeviceHooks = { elantech_open, elantech_close, elantech_freecookie, elantech_ioctl, elantech_read, elantech_write, };