From 4c78b73b8a5411d19320e31510654e42815178db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Duval?= Date: Sun, 22 Sep 2013 23:08:24 +0200 Subject: [PATCH] ps2: added initial support for elantech pads. * also added a license to ps2_alps.h * added ps2_dev_sliced_command() * moved some ps2 commands definitions to ps2_defs.h --- src/add-ons/kernel/bus_managers/ps2/Jamfile | 1 + .../kernel/bus_managers/ps2/ps2_alps.cpp | 50 +- .../kernel/bus_managers/ps2/ps2_alps.h | 7 + .../kernel/bus_managers/ps2/ps2_common.cpp | 7 +- .../kernel/bus_managers/ps2/ps2_common.h | 1 + .../kernel/bus_managers/ps2/ps2_defs.h | 8 + .../kernel/bus_managers/ps2/ps2_dev.cpp | 31 +- src/add-ons/kernel/bus_managers/ps2/ps2_dev.h | 6 +- .../kernel/bus_managers/ps2/ps2_elantech.cpp | 801 ++++++++++++++++++ .../kernel/bus_managers/ps2/ps2_elantech.h | 59 ++ 10 files changed, 934 insertions(+), 37 deletions(-) create mode 100644 src/add-ons/kernel/bus_managers/ps2/ps2_elantech.cpp create mode 100644 src/add-ons/kernel/bus_managers/ps2/ps2_elantech.h diff --git a/src/add-ons/kernel/bus_managers/ps2/Jamfile b/src/add-ons/kernel/bus_managers/ps2/Jamfile index d2abde2182..c5d6817669 100644 --- a/src/add-ons/kernel/bus_managers/ps2/Jamfile +++ b/src/add-ons/kernel/bus_managers/ps2/Jamfile @@ -8,6 +8,7 @@ KernelAddon ps2 : ps2_alps.cpp ps2_common.cpp ps2_dev.cpp + ps2_elantech.cpp ps2_keyboard.cpp ps2_module.cpp ps2_standard_mouse.cpp diff --git a/src/add-ons/kernel/bus_managers/ps2/ps2_alps.cpp b/src/add-ons/kernel/bus_managers/ps2/ps2_alps.cpp index 70625df8dd..31affc9e58 100644 --- a/src/add-ons/kernel/bus_managers/ps2/ps2_alps.cpp +++ b/src/add-ons/kernel/bus_managers/ps2/ps2_alps.cpp @@ -67,8 +67,8 @@ public: private: bool fFired; uint8 fLastPackage[PS2_PACKET_ALPS]; - timer fEventTimer; - alps_cookie* fCookie; + timer fEventTimer; + alps_cookie* fCookie; }; @@ -112,7 +112,7 @@ typedef struct alps_model_info { static const struct alps_model_info gALPSModelInfos[] = { {{0x32, 0x02, 0x14}, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT}, - // Toshiba Salellite Pro M10 + // Toshiba Salellite Pro M10 // {{0x33, 0x02, 0x0a}, 0x88, 0xf8, ALPS_OLDPROTO}, // UMAX-530T {{0x53, 0x02, 0x0a}, 0xf8, 0xf8, 0}, @@ -155,15 +155,6 @@ static const struct alps_model_info gALPSModelInfos[] = { static alps_model_info* sFoundModel = NULL; -#define PS2_MOUSE_CMD_SET_SCALE11 0xe6 -#define PS2_MOUSE_CMD_SET_SCALE21 0xe7 -#define PS2_MOUSE_CMD_SET_RES 0xe8 -#define PS2_MOUSE_CMD_GET_INFO 0xe9 -#define PS2_MOUSE_CMD_SET_STREAM 0xea -#define PS2_MOUSE_CMD_SET_POLL 0xf0 -#define PS2_MOUSE_CMD_SET_RATE 0xf3 - - // touchpad proportions #define EDGE_MOTION_WIDTH 55 // increase the touchpad size a little bit @@ -279,16 +270,16 @@ probe_alps(ps2_dev* dev) TRACE("ALPS: probe\n"); val[0] = 0; - if (ps2_dev_command(dev, PS2_MOUSE_CMD_SET_RES, val, 1, NULL, 0) != B_OK - || ps2_dev_command(dev, PS2_MOUSE_CMD_SET_SCALE11, NULL, 0, NULL, 0) + if (ps2_dev_command(dev, PS2_CMD_MOUSE_SET_RES, val, 1, NULL, 0) != B_OK + || ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11, NULL, 0, NULL, 0) != B_OK - || ps2_dev_command(dev, PS2_MOUSE_CMD_SET_SCALE11, NULL, 0, NULL, 0) + || ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11, NULL, 0, NULL, 0) != B_OK - || ps2_dev_command(dev, PS2_MOUSE_CMD_SET_SCALE11, NULL, 0, NULL, 0) + || ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11, NULL, 0, NULL, 0) != B_OK) return B_ERROR; - if (ps2_dev_command(dev, PS2_MOUSE_CMD_GET_INFO, NULL, 0, val, 3) + if (ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, val, 3) != B_OK) return B_ERROR; @@ -296,16 +287,16 @@ probe_alps(ps2_dev* dev) return B_ERROR; val[0] = 0; - if (ps2_dev_command(dev, PS2_MOUSE_CMD_SET_RES, val, 1, NULL, 0) != B_OK - || ps2_dev_command(dev, PS2_MOUSE_CMD_SET_SCALE21, NULL, 0, NULL, 0) + if (ps2_dev_command(dev, PS2_CMD_MOUSE_SET_RES, val, 1, NULL, 0) != B_OK + || ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE21, NULL, 0, NULL, 0) != B_OK - || ps2_dev_command(dev, PS2_MOUSE_CMD_SET_SCALE21, NULL, 0, NULL, 0) + || ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE21, NULL, 0, NULL, 0) != B_OK - || ps2_dev_command(dev, PS2_MOUSE_CMD_SET_SCALE21, NULL, 0, NULL, 0) + || ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE21, NULL, 0, NULL, 0) != B_OK) return B_ERROR; - if (ps2_dev_command(dev, PS2_MOUSE_CMD_GET_INFO, NULL, 0, val, 3) + if (ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, val, 3) != B_OK) return B_ERROR; @@ -337,12 +328,12 @@ switch_hardware_tab(ps2_dev* dev, bool on) { uint8 val[3]; uint8 arg = 0x00; - uint8 command = PS2_MOUSE_CMD_SET_RES; + uint8 command = PS2_CMD_MOUSE_SET_RES; if (on) { arg = 0x0A; - command = PS2_MOUSE_CMD_SET_RATE; + command = PS2_CMD_KEYBOARD_SET_TYPEMATIC; } - if (ps2_dev_command(dev, PS2_MOUSE_CMD_GET_INFO, NULL, 0, val, 3) != B_OK + if (ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, val, 3) != B_OK || ps2_dev_command(dev, PS2_CMD_DISABLE, NULL, 0, NULL, 0) != B_OK || ps2_dev_command(dev, PS2_CMD_DISABLE, NULL, 0, NULL, 0) != B_OK || ps2_dev_command(dev, command, &arg, 1, NULL, 0) != B_OK) @@ -355,9 +346,9 @@ switch_hardware_tab(ps2_dev* dev, bool on) status_t enable_passthrough(ps2_dev* dev, bool on) { - uint8 command = PS2_MOUSE_CMD_SET_SCALE11; + uint8 command = PS2_CMD_MOUSE_SET_SCALE11; if (on) - command = PS2_MOUSE_CMD_SET_SCALE21; + command = PS2_CMD_MOUSE_SET_SCALE21; if (ps2_dev_command(dev, command, NULL, 0, NULL, 0) != B_OK || ps2_dev_command(dev, command, NULL, 0, NULL, 0) != B_OK @@ -438,7 +429,7 @@ alps_open(const char *name, uint32 flags, void **_cookie) if ((sFoundModel->flags & ALPS_PASS) != 0 && enable_passthrough(dev, true) != B_OK) goto err4; - + // switch tap mode off if (switch_hardware_tab(dev, false) != B_OK) goto err4; @@ -455,7 +446,7 @@ alps_open(const char *name, uint32 flags, void **_cookie) && enable_passthrough(dev, false) != B_OK) goto err4; - if (ps2_dev_command(dev, PS2_MOUSE_CMD_SET_STREAM, NULL, 0, NULL, 0) != B_OK) + if (ps2_dev_command(dev, PS2_CMD_MOUSE_SET_STREAM, NULL, 0, NULL, 0) != B_OK) goto err4; if (ps2_dev_command(dev, PS2_CMD_ENABLE, NULL, 0, NULL, 0) != B_OK) @@ -633,4 +624,3 @@ device_hooks gALPSDeviceHooks = { alps_read, alps_write, }; - diff --git a/src/add-ons/kernel/bus_managers/ps2/ps2_alps.h b/src/add-ons/kernel/bus_managers/ps2/ps2_alps.h index e9dea0683d..4e25dc6485 100644 --- a/src/add-ons/kernel/bus_managers/ps2/ps2_alps.h +++ b/src/add-ons/kernel/bus_managers/ps2/ps2_alps.h @@ -1,3 +1,10 @@ +/* + * Copyright 2011, Haiku, Inc. + * Distributed under the terms of the MIT License. + * + * Authors: + * Clemens Zeidler (haiku@Clemens-Zeidler.de) + */ #ifndef ALPS_H #define ALPS_H diff --git a/src/add-ons/kernel/bus_managers/ps2/ps2_common.cpp b/src/add-ons/kernel/bus_managers/ps2/ps2_common.cpp index 37f902e74d..d0560f272b 100644 --- a/src/add-ons/kernel/bus_managers/ps2/ps2_common.cpp +++ b/src/add-ons/kernel/bus_managers/ps2/ps2_common.cpp @@ -369,7 +369,7 @@ ps2_init(void) //ps2_selftest(); // Setup the command byte with disabled keyboard and AUX interrupts - // to prevent interrupts storm on some KBCs during active multiplexing + // to prevent interrupts storm on some KBCs during active multiplexing // activation procedure. Fixes #7635. status = ps2_setup_command_byte(false); if (status) { @@ -391,8 +391,9 @@ ps2_init(void) } if (gActiveMultiplexingEnabled) { - if (ps2_dev_command_timeout(&ps2_device[PS2_DEVICE_MOUSE], 0xe6, - NULL, 0, NULL, 0, 100000) == B_TIMED_OUT) { + if (ps2_dev_command_timeout(&ps2_device[PS2_DEVICE_MOUSE], + PS2_CMD_MOUSE_SET_SCALE11, NULL, 0, NULL, 0, 100000) + == B_TIMED_OUT) { INFO("ps2: accessing multiplexed mouse port 0 timed out, ignoring it!\n"); } else { ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_MOUSE]); diff --git a/src/add-ons/kernel/bus_managers/ps2/ps2_common.h b/src/add-ons/kernel/bus_managers/ps2/ps2_common.h index fc302faead..c32c7349f0 100644 --- a/src/add-ons/kernel/bus_managers/ps2/ps2_common.h +++ b/src/add-ons/kernel/bus_managers/ps2/ps2_common.h @@ -28,6 +28,7 @@ #else # define INFO(x...) #endif +#define ERROR(x...) dprintf(x) //#define TRACE_PS2 #ifdef TRACE_PS2 diff --git a/src/add-ons/kernel/bus_managers/ps2/ps2_defs.h b/src/add-ons/kernel/bus_managers/ps2/ps2_defs.h index b9f237cc1c..25fbf1af28 100644 --- a/src/add-ons/kernel/bus_managers/ps2/ps2_defs.h +++ b/src/add-ons/kernel/bus_managers/ps2/ps2_defs.h @@ -48,7 +48,13 @@ #define PS2_BITS_TRANSLATE_SCANCODES 0x40 // data words +#define PS2_CMD_MOUSE_SET_SCALE11 0xe6 +#define PS2_CMD_MOUSE_SET_SCALE21 0xe7 +#define PS2_CMD_MOUSE_SET_RES 0xe8 +#define PS2_CMD_MOUSE_GET_INFO 0xe9 +#define PS2_CMD_MOUSE_SET_STREAM 0xea #define PS2_CMD_KEYBOARD_SET_LEDS 0xed +#define PS2_CMD_MOUSE_SET_POLL 0xf0 #define PS2_CMD_KEYBOARD_SET_TYPEMATIC 0xf3 #define PS2_CMD_ECHO 0xee #define PS2_CMD_TEST_PASSED 0xaa @@ -56,6 +62,7 @@ #define PS2_CMD_SET_SAMPLE_RATE 0xf3 #define PS2_CMD_ENABLE 0xf4 #define PS2_CMD_DISABLE 0xf5 +#define PS2_CMD_MOUSE_RESET_DIS 0xf6 #define PS2_CMD_RESET 0xff #define PS2_CMD_RESEND 0xfe @@ -79,6 +86,7 @@ #define PS2_PACKET_INTELLIMOUSE 4 #define PS2_PACKET_SYNAPTICS 6 #define PS2_PACKET_ALPS 6 +#define PS2_PACKET_ELANTECH 6 // version 1 is only 4 #define PS2_MAX_PACKET_SIZE 6 // Should be equal to the biggest packet size diff --git a/src/add-ons/kernel/bus_managers/ps2/ps2_dev.cpp b/src/add-ons/kernel/bus_managers/ps2/ps2_dev.cpp index 0cb799a5a4..4f7137b8e4 100644 --- a/src/add-ons/kernel/bus_managers/ps2/ps2_dev.cpp +++ b/src/add-ons/kernel/bus_managers/ps2/ps2_dev.cpp @@ -14,6 +14,7 @@ #include "ps2_service.h" #include "ps2_alps.h" +#include "ps2_elantech.h" #include "ps2_standard_mouse.h" #include "ps2_synaptics.h" #include "ps2_trackpoint.h" @@ -88,6 +89,12 @@ ps2_dev_detect_pointing(ps2_dev *dev, device_hooks **hooks) goto dev_found; } + status = probe_elantech(dev); + if (status == B_OK) { + *hooks = &gElantechDeviceHooks; + goto dev_found; + } + // reset the mouse for the case that the previous probes leaf the mouse in // a undefined state status = ps2_reset_mouse(dev); @@ -190,7 +197,7 @@ ps2_dev_publish(ps2_dev *dev) if (atomic_get(&dev->flags) & PS2_FLAG_KEYB) { status = devfs_publish_device(dev->name, &gKeyboardDeviceHooks); } else { - // Check if this is the "pass-through" device and wait until + // Check if this is the "pass-through" device and wait until // the parent_dev goes to enabled state. It is required to prevent // from messing up the Synaptics command sequences in synaptics_open. if (dev->parent_dev) { @@ -204,7 +211,7 @@ ps2_dev_publish(ps2_dev *dev) snooze(timeout / 20); } TRACE("ps2: publishing %s: parent %s is %s; wait time %" B_PRId64 - "\n", dev->name, dev->parent_dev->name, + "\n", dev->name, dev->parent_dev->name, status == B_OK ? "enabled" : "busy", system_time() - start); } @@ -490,3 +497,23 @@ ps2_dev_command_timeout(ps2_dev *dev, uint8 cmd, const uint8 *out, return dev->command(dev, cmd, out, out_count, in, in_count, timeout); } + +status_t +ps2_dev_sliced_command(ps2_dev *dev, uint8 cmd) +{ + if (ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11) != B_OK) + return B_ERROR; + uint8 val = (cmd >> 6) & 3; + if (ps2_dev_command(dev, PS2_CMD_MOUSE_SET_RES, &val, 1) != B_OK) + return B_ERROR; + val = (cmd >> 4) & 3; + if (ps2_dev_command(dev, PS2_CMD_MOUSE_SET_RES, &val, 1) != B_OK) + return B_ERROR; + val = (cmd >> 2) & 3; + if (ps2_dev_command(dev, PS2_CMD_MOUSE_SET_RES, &val, 1) != B_OK) + return B_ERROR; + val = cmd & 3; + if (ps2_dev_command(dev, PS2_CMD_MOUSE_SET_RES, &val, 1) != B_OK) + return B_ERROR; + return B_OK; +} diff --git a/src/add-ons/kernel/bus_managers/ps2/ps2_dev.h b/src/add-ons/kernel/bus_managers/ps2/ps2_dev.h index c8c283609f..229dbe0c0f 100644 --- a/src/add-ons/kernel/bus_managers/ps2/ps2_dev.h +++ b/src/add-ons/kernel/bus_managers/ps2/ps2_dev.h @@ -76,10 +76,12 @@ void ps2_dev_exit(void); status_t standard_command_timeout(ps2_dev *dev, uint8 cmd, const uint8 *out, int out_count, uint8 *in, int in_count, bigtime_t timeout); -status_t ps2_dev_command(ps2_dev *dev, uint8 cmd, const uint8 *out, - int out_count, uint8 *in, int in_count); +status_t ps2_dev_command(ps2_dev *dev, uint8 cmd, const uint8 *out = NULL, + int out_count = 0, uint8 *in = NULL, int in_count = 0); status_t ps2_dev_command_timeout(ps2_dev *dev, uint8 cmd, const uint8 *out, int out_count, uint8 *in, int in_count, bigtime_t timeout); +status_t ps2_dev_sliced_command(ps2_dev *dev, uint8 cmd); + status_t ps2_reset_mouse(ps2_dev *dev); diff --git a/src/add-ons/kernel/bus_managers/ps2/ps2_elantech.cpp b/src/add-ons/kernel/bus_managers/ps2/ps2_elantech.cpp new file mode 100644 index 0000000000..355bd4f03b --- /dev/null +++ b/src/add-ons/kernel/bus_managers/ps2/ps2_elantech.cpp @@ -0,0 +1,801 @@ +/* + * Copyright 2013, Haiku, Inc. + * Distributed under the terms of the MIT License. + * + * Hardware specs taken from the linux driver, thanks a lot! + * Based on ps2_alps.c + * + * Authors: + * Jérôme Duval + */ + + +#include "ps2_elantech.h" + +#include +#include + +#include "ps2_service.h" + + +static int32 generate_event(timer* timer); + + +const bigtime_t kEventInterval = 1000 * 50; + + +class EventProducer { +public: + EventProducer() + { + fFired = false; + } + + status_t + FireEvent(elantech_cookie* cookie, uint8* package) + { + fCookie = cookie; + memcpy(fLastPackage, package, sizeof(uint8) * PS2_PACKET_ELANTECH); + + status_t status = add_timer(&fEventTimer, &generate_event, + kEventInterval, B_ONE_SHOT_RELATIVE_TIMER); + if (status == B_OK) + fFired = true; + return status; + } + + bool + CancelEvent() + { + if (!fFired) + return false; + fFired = false; + return cancel_timer(&fEventTimer); + } + + void + InjectEvent() + { + if (packet_buffer_write(fCookie->ring_buffer, fLastPackage, + PS2_PACKET_ELANTECH) != PS2_PACKET_ELANTECH) { + // buffer is full, drop new data + return; + } + release_sem_etc(fCookie->sem, 1, B_DO_NOT_RESCHEDULE); + } + +private: + bool fFired; + uint8 fLastPackage[PS2_PACKET_ELANTECH]; + timer fEventTimer; + elantech_cookie* fCookie; +}; + + +static EventProducer gEventProducer; + + +static int32 +generate_event(timer* timer) +{ + gEventProducer.InjectEvent(); + return 0; +} + + +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_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 ELANTECH_HISTORY_SIZE 256 + + +static hardware_specs gHardwareSpecs; + + +static status_t +get_elantech_movement(elantech_cookie *cookie, mouse_movement *movement) +{ + touch_event event; + uint8 event_buffer[PS2_PACKET_ELANTECH]; + + status_t status = acquire_sem_etc(cookie->sem, 1, B_CAN_INTERRUPT, 0); + if (status < B_OK) + 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, event_buffer, + cookie->dev->packet_size) != cookie->dev->packet_size) { + TRACE("ELANTECH: error copying buffer\n"); + return B_ERROR; + } + + if (cookie->crcEnabled && (event_buffer[3] & 0x08) != 0) { + TRACE("ELANTECH: bad crc buffer\n"); + return B_ERROR; + } else if (!cookie->crcEnabled && ((event_buffer[0] & 0x0c) != 0x04 + || (event_buffer[3] & 0x1c) != 0x10)) { + TRACE("ELANTECH: bad crc buffer\n"); + return B_ERROR; + } + uint8 type = event_buffer[3] & 3; + TRACE("ELANTECH: packet type %d\n", type); + TRACE("ELANTECH: packet content 0x%02x%02x%02x%02x%02x%02x\n", + event_buffer[0], event_buffer[1], event_buffer[2], event_buffer[3], + event_buffer[4], event_buffer[5]); + switch (type) { + case 0: + // fingers + cookie->fingers = event_buffer[1] & 0x1f; + break; + case 1: + if ((((event_buffer[3] & 0xe0) >> 5) - 1) != 0) { + // only process first finger + return B_OK; + } + event.zPressure = (event_buffer[1] & 0xf0) + | ((event_buffer[4] & 0xf0) >> 4); + + cookie->previousZ = event.zPressure; + + event.xPosition = ((event_buffer[1] & 0xf) << 8) | event_buffer[2]; + event.yPosition = (((event_buffer[4] & 0xf) << 8) + | event_buffer[5]); + TRACE("ELANTECH: buttons 0x%x x %ld y %ld z %d\n", + event.buttons, event.xPosition, event.yPosition, + event.zPressure); + break; + case 2: + TRACE("ELANTECH: packet type motion\n"); + // TODO + return B_OK; + default: + TRACE("ELANTECH: unknown packet type %d\n", type); + return B_ERROR; + } + + event.buttons = 0; + // finger on touchpad + if ((cookie->fingers & 1) != 0) { + // finger with normal width + event.wValue = 4; + } else { + event.wValue = 3; + } + status = cookie->movementMaker.EventToMovement(&event, movement); + + if (cookie->movementMaker.WasEdgeMotion() + || cookie->movementMaker.TapDragStarted()) { + gEventProducer.FireEvent(cookie, event_buffer); + } + + return status; +} + + +static void +default_settings(touchpad_settings *set) +{ + memcpy(set, &kDefaultTouchpadSettings, sizeof(touchpad_settings)); +} + + +static status_t +synaptics_dev_send_command(ps2_dev* dev, uint8 cmd, uint8 *in, int in_count) +{ + uint8 val; + 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) +{ + uint8 val; + 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; +} + + +status_t +probe_elantech(ps2_dev* dev) +{ + int i; + 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; + } + + 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; + } + + INFO("Elantech found\n"); + + dev->name = kElantechPath[dev->idx]; + dev->packet_size = PS2_PACKET_ELANTECH; + + return B_OK; +} + + +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: + // TODO + 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: + // TODO + 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 +switch_hardware_tab(ps2_dev* dev, bool on) +{ + uint8 val[3]; + uint8 arg = 0x00; + uint8 command = PS2_CMD_MOUSE_SET_RES; + if (on) { + arg = 0x0A; + command = PS2_CMD_SET_SAMPLE_RATE; + } + if (ps2_dev_command(dev, PS2_CMD_MOUSE_GET_INFO, NULL, 0, val, 3) != B_OK + || ps2_dev_command(dev, PS2_CMD_DISABLE, NULL, 0, NULL, 0) != B_OK + || ps2_dev_command(dev, PS2_CMD_DISABLE, NULL, 0, NULL, 0) != B_OK + || ps2_dev_command(dev, command, &arg, 1, NULL, 0) != B_OK) + return B_ERROR; + + 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; + *y = ((val[1] & 0xf) >> 4) * 10 + 790; + 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) +{ + status_t status = B_OK; + 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: + // TODO + 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 (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(100); + } + } + + return status; +} + + +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; + + elantech_cookie* cookie = (elantech_cookie*)malloc( + sizeof(elantech_cookie)); + if (cookie == NULL) + goto err1; + memset(cookie, 0, sizeof(*cookie)); + + cookie->movementMaker.Init(); + cookie->previousZ = 0; + *_cookie = cookie; + + cookie->dev = dev; + dev->cookie = cookie; + dev->disconnect = &elantech_disconnect; + dev->handle_int = &elantech_handle_int; + + default_settings(&cookie->settings); + + 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; + case 6: + case 7: + cookie->version = 4; + break; + default: + TRACE("ELANTECH: unknown version!\n"); + goto err4; + } + } + TRACE("ELANTECH: version 0x%lx (0x%lx)\n", cookie->version, + cookie->fwVersion); + + 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 (enable_absolute_mode(cookie) != B_OK) { + TRACE("ELANTECH: failed enabling absolute mode!\n"); + goto err4; + } + TRACE("ELANTECH: enabled absolute mode!\n"); + + uint32 x_min, x_max, y_min, y_max, width; + 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 %ld-%ld y %ld-%ld (%ld)\n", x_min, x_max, + y_min, y_max, width); + + uint32 x_res, y_res; + if (get_resolution_v4(cookie, &x_res, &y_res) != B_OK) { + TRACE("ELANTECH: get resolution failed!\n"); + goto err4; + } + + TRACE("ELANTECH: resolution x %ld y %ld (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; + + cookie->movementMaker.SetSettings(&cookie->settings); + cookie->movementMaker.SetSpecs(&gHardwareSpecs); + + 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) +{ + gEventProducer.CancelEvent(); + + 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: 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; + mouse_movement movement; + status_t status; + + switch (op) { + case MS_READ: + TRACE("ELANTECH: MS_READ get event\n"); + if ((status = get_elantech_movement(cookie, &movement)) != B_OK) + return status; + return user_memcpy(buffer, &movement, sizeof(movement)); + + case MS_IS_TOUCHPAD: + TRACE("ELANTECH: MS_IS_TOUCHPAD\n"); + return B_OK; + + case MS_SET_TOUCHPAD_SETTINGS: + TRACE("ELANTECH: MS_SET_TOUCHPAD_SETTINGS"); + user_memcpy(&cookie->settings, buffer, sizeof(touchpad_settings)); + return B_OK; + + case MS_SET_CLICKSPEED: + TRACE("ELANTECH: ioctl MS_SETCLICK (set click speed)\n"); + return user_memcpy(&cookie->movementMaker.click_speed, buffer, + sizeof(bigtime_t)); + + default: + TRACE("ELANTECH: unknown opcode: %ld\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; + + // we got a real event cancel the fake event + gEventProducer.CancelEvent(); + + 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, +}; diff --git a/src/add-ons/kernel/bus_managers/ps2/ps2_elantech.h b/src/add-ons/kernel/bus_managers/ps2/ps2_elantech.h new file mode 100644 index 0000000000..b68a7bb369 --- /dev/null +++ b/src/add-ons/kernel/bus_managers/ps2/ps2_elantech.h @@ -0,0 +1,59 @@ +/* + * Copyright 2013, Haiku, Inc. + * Distributed under the terms of the MIT License. + * + * The elantech_model_info struct and all the hardware specs are taken from the + * linux driver, thanks a lot! + * + * Authors: + * Jérôme Duval + */ +#ifndef _PS2_ELANTECH_H +#define _PS2_ELANTECH_H + + +#include + +#include + +#include "movement_maker.h" +#include "packet_buffer.h" +#include "ps2_dev.h" + + +typedef struct { + ps2_dev* dev; + + sem_id sem; + struct packet_buffer* ring_buffer; + size_t packet_index; + uint8 buffer[PS2_PACKET_ELANTECH]; + uint8 mode; + + uint8 previousZ; + TouchpadMovement movementMaker; + + touchpad_settings settings; + uint32 version; + uint32 fwVersion; + uint8 capabilities[3]; + bool crcEnabled; + uint32 fingers; + status_t (*send_command)(ps2_dev* dev, uint8 cmd, uint8 *in, int in_count); +} elantech_cookie; + + +status_t probe_elantech(ps2_dev *dev); + +status_t elantech_open(const char *name, uint32 flags, void **_cookie); +status_t elantech_close(void *_cookie); +status_t elantech_freecookie(void *_cookie); +status_t elantech_ioctl(void *_cookie, uint32 op, void *buffer, size_t length); + +int32 elantech_handle_int(ps2_dev *dev); +void elantech_disconnect(ps2_dev *dev); + +extern device_hooks gElantechDeviceHooks; + + +#endif /* _PS2_ELANTECH_H */