Created
December 27, 2025 15:49
-
-
Save Herrie82/1e4300e63e2852e4c43a3a68510cfca3 to your computer and use it in GitHub Desktop.
Touchscreen for Tenderloin on Mainline
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Cleaned-Up ts-srv Driver Analysis | |
| ## Overview | |
| This analysis covers the cleaned-up HP TouchPad touchscreen driver (`ts-srv`) found in `/home/herrie/webos/touchpad-kernel/touchscreen/`. This version has been adapted from the original Android driver to work on modern Linux systems. | |
| **Source Origin:** Evervolv Android ROM for HP TouchPad | |
| **Modifications:** Android NDK dependencies removed, paths updated for Linux | |
| **Status:** Ready for testing on mainline Linux | |
| ## File Structure | |
| ### Core Files | |
| | File | Size | Purpose | | |
| |------|------|---------| | |
| | `ts_srv.c` | 45KB | Main touchscreen driver daemon | | |
| | `digitizer.c` | 5.9KB | Power management and I²C initialization | | |
| | `config.h` | 3.9KB | Tunable parameters and thresholds | | |
| | `hsuart.h` | 5.7KB | High-speed UART API definitions (Palm) | | |
| | `digitizer.h` | 1.1KB | Power management interface | | |
| | `log.h` | 163B | Android logging stub (no-ops) | | |
| | `ts_srv_set.c` | 4.3KB | Settings management utility | | |
| | `Android.mk` | 1.4KB | Android build file (reference only) | | |
| ## Key Modifications from Android Version | |
| ### 1. Logging System | |
| **Before (Android):** | |
| ```c | |
| #include <cutils/log.h> | |
| ALOGD("Debug message"); | |
| ALOGI("Info message"); | |
| ALOGE("Error message"); | |
| ``` | |
| **After (Linux):** | |
| ```c | |
| #include "log.h" // Stub header | |
| // All logging is no-ops | |
| #define ALOGD(...) | |
| #define ALOGI(...) | |
| #define ALOGE(...) | |
| ``` | |
| **Impact:** Silent operation by default. Can easily replace with `syslog()` for production. | |
| ### 2. File Paths | |
| **Socket Path:** | |
| - Android: `/dev/socket/tsdriver` | |
| - Linux: `/run/tsdriver.sock` | |
| **Settings File:** | |
| - Android: `/data/tssettings` | |
| - Linux: `/etc/tssettings` | |
| **UART Device:** | |
| - Remains: `/dev/ctp_uart` (needs device tree to create this device node) | |
| **uinput Device:** | |
| - Standard: `/dev/uinput` (same on both) | |
| ### 3. I²C Device | |
| **Critical Discovery:** | |
| ```c | |
| i2c_fd = open("/dev/i2c-5", O_RDWR); | |
| ``` | |
| **NOT `/dev/i2c-10`!** The cleaned code uses **I²C bus 5**. | |
| **Analysis:** | |
| - Original webOS kernel may have numbered buses differently | |
| - GSBI10 in device tree is I²C bus 10 in mainline kernel | |
| - **Need to verify actual I²C bus numbering on hardware** | |
| - May need to change to `/dev/i2c-10` for mainline kernel | |
| ### 4. Wake GPIO Path | |
| **Wake Pin Control:** | |
| ```c | |
| wake_fd = open("/sys/user_hw/pins/ctp/wake/level", O_WRONLY); | |
| ``` | |
| **Issue:** This is a **Palm/webOS specific sysfs path** that doesn't exist in mainline. | |
| **Mainline Alternative:** | |
| ```c | |
| // Use cy8ctma395 driver sysfs or control wake via I²C | |
| // The wake pin is GPIO 123, may need to export it manually | |
| wake_fd = open("/sys/class/gpio/gpio123/value", O_WRONLY); | |
| ``` | |
| ## UART Protocol Details | |
| ### Packet Format | |
| **Scan Line Data Packet (0xFF 0x43):** | |
| ```c | |
| if (cline[0] == 0xff && cline[1] == 0x43 && cidx == 44) | |
| ``` | |
| **Structure:** | |
| ``` | |
| Byte 0: 0xFF // Magic header | |
| Byte 1: 0x43 // Packet type: Scan data | |
| Byte 2: Row index & flags // bits 0-4: row (0-29) | |
| // bit 7: clear matrix flag | |
| Bytes 3-42: Matrix data (40 bytes, one per column) | |
| ``` | |
| **Total:** 44 bytes (including 1 byte consumed before reaching this check) | |
| **Scan Complete Packet (0xFF 0x47):** | |
| ```c | |
| if (cline[0] == 0xff && cline[1] == 0x47 && cidx > 4 && | |
| cidx == (cline[2]+4)) | |
| ``` | |
| **Structure:** | |
| ``` | |
| Byte 0: 0xFF // Magic header | |
| Byte 1: 0x47 // Packet type: Scan complete | |
| Byte 2: Data length (N) // Additional data bytes | |
| Bytes 3-N: Additional data // Meaning unknown | |
| ``` | |
| **Total:** Variable (4 + N bytes) | |
| ### Matrix Organization | |
| **30×40 Sensor Grid:** | |
| ```c | |
| #define X_AXIS_POINTS 30 // Columns (i-axis, 0-29) | |
| #define Y_AXIS_POINTS 40 // Rows (j-axis, 0-39) | |
| unsigned char matrix[X_AXIS_POINTS][Y_AXIS_POINTS]; | |
| ``` | |
| **Data Storage:** | |
| - Each 0xFF 0x43 packet contains **one row** (40 values) | |
| - Row index encoded in `cline[2] & 0x1F` (bits 0-4) | |
| - If bit 7 set (`cline[2] & 0x80`), clear entire matrix first | |
| - Takes **30 packets** to fill complete matrix | |
| - Final 0xFF 0x47 packet signals scan complete | |
| **Example Packet Processing:** | |
| ```c | |
| if (cline[1] == 0x43) { | |
| // Clear matrix if this is first row | |
| if (cline[2] & 0x80) { | |
| memset(matrix, 0, sizeof(matrix)); | |
| } | |
| // Write row data | |
| int row = cline[2] & 0x1F; // Extract row index (0-29) | |
| for (i = 0; i < Y_AXIS_POINTS; i++) | |
| matrix[row][i] = cline[i+3]; // Copy 40 bytes | |
| } | |
| ``` | |
| ### UART Configuration | |
| **Device Initialization:** | |
| ```c | |
| *uart_fd = open("/dev/ctp_uart", O_RDONLY|O_NONBLOCK); | |
| struct hsuart_mode uart_mode; | |
| ioctl(*uart_fd, HSUART_IOCTL_GET_UARTMODE, &uart_mode); | |
| uart_mode.speed = 0x3D0900; // 4 Mbps | |
| ioctl(*uart_fd, HSUART_IOCTL_SET_UARTMODE, &uart_mode); | |
| ioctl(*uart_fd, HSUART_IOCTL_FLUSH, 0x9); // Flush RX+TX | |
| ``` | |
| **Read Loop:** | |
| ```c | |
| #define RECV_BUF_SIZE 1540 | |
| unsigned char recv_buf[RECV_BUF_SIZE]; | |
| nbytes = read(uart_fd, recv_buf, RECV_BUF_SIZE); | |
| snarf2(recv_buf, nbytes); // Parse packets | |
| ``` | |
| ## I²C Initialization Sequence | |
| ### Power-On Sequence | |
| ```c | |
| void digitizer_power(int enable) { | |
| if (enable) { | |
| // 1. Assert reset | |
| write(xres_fd, "1", 1); | |
| // 2. Power on VDD | |
| write(vdd_fd, "1", 1); | |
| usleep(50000); // 50ms voltage stabilization | |
| // 3. Assert wake | |
| write(wake_fd, "1", 1); | |
| // 4. Release reset | |
| write(xres_fd, "0", 1); | |
| usleep(50000); | |
| // 5. Release wake | |
| write(wake_fd, "0", 1); | |
| usleep(50000); | |
| // 6. I²C configuration sequence (7 commands) | |
| i2c_msg.addr = 0x67; | |
| // Command 1: Enter config mode | |
| i2c_buf[0] = 0x08; i2c_buf[1] = 0x00; | |
| ioctl(i2c_fd, I2C_RDWR, &i2c_ioctl_data); | |
| // Command 2: Scan configuration (6 bytes) | |
| i2c_buf[0] = 0x31; i2c_buf[1] = 0x01; i2c_buf[2] = 0x08; | |
| i2c_buf[3] = 0x0C; i2c_buf[4] = 0x0D; i2c_buf[5] = 0x0A; | |
| ioctl(i2c_fd, I2C_RDWR, &i2c_ioctl_data); | |
| // Commands 3-6: Configuration parameters | |
| i2c_buf[0] = 0x30; i2c_buf[1] = 0x0F; | |
| ioctl(i2c_fd, I2C_RDWR, &i2c_ioctl_data); | |
| i2c_buf[0] = 0x40; i2c_buf[1] = 0x02; | |
| ioctl(i2c_fd, I2C_RDWR, &i2c_ioctl_data); | |
| i2c_buf[0] = 0x41; i2c_buf[1] = 0x10; | |
| ioctl(i2c_fd, I2C_RDWR, &i2c_ioctl_data); | |
| i2c_buf[0] = 0x0A; i2c_buf[1] = 0x04; | |
| ioctl(i2c_fd, I2C_RDWR, &i2c_ioctl_data); | |
| // Command 7: Start UART streaming | |
| i2c_buf[0] = 0x08; i2c_buf[1] = 0x03; | |
| ioctl(i2c_fd, I2C_RDWR, &i2c_ioctl_data); | |
| // 7. Re-assert wake (keep active during operation) | |
| write(wake_fd, "1", 1); | |
| } | |
| } | |
| ``` | |
| **Retry Logic:** | |
| - If first I²C command fails, retry up to `MAX_DIGITIZER_RETRY` (3) times | |
| - Power cycle between retries: VDD off, 10ms delay, try again | |
| ### Power-Off Sequence | |
| ```c | |
| // 1. Power off VDD | |
| write(vdd_fd, "0", 1); | |
| // 2. Reset to clear data stream (4G TouchPad quirk) | |
| write(xres_fd, "1", 1); | |
| usleep(10000); | |
| write(xres_fd, "0", 1); | |
| usleep(80000); // Wait for liftoff timeout | |
| ``` | |
| ## Touch Detection Algorithm | |
| ### Coordinate Transformation | |
| **Matrix to Screen Conversion:** | |
| ```c | |
| #define X_RESOLUTION 1024 | |
| #define Y_RESOLUTION 768 | |
| #define X_LOCATION_VALUE ((float)X_RESOLUTION / (float)(Y_AXIS_MINUS1)) // 1024/39 | |
| #define Y_LOCATION_VALUE ((float)Y_RESOLUTION / (float)(X_AXIS_MINUS1)) // 768/29 | |
| // Conversion (axes swapped and inverted) | |
| tp[tpoint][tpc].x = X_RESOLUTION_MINUS1 - tp[tpoint][tpc].j * X_LOCATION_VALUE; | |
| tp[tpoint][tpc].y = Y_RESOLUTION_MINUS1 - tp[tpoint][tpc].i * Y_LOCATION_VALUE; | |
| ``` | |
| **Coordinate Mapping:** | |
| - Matrix J-axis (0-29) → Screen X-axis (1023-0) **INVERTED** | |
| - Matrix I-axis (0-39) → Screen Y-axis (767-0) **INVERTED** | |
| ### Threshold Configuration | |
| **Finger Mode (Default):** | |
| ```c | |
| #define TOUCH_INITIAL_THRESHOLD 32 // Immediate report | |
| #define TOUCH_CONTINUE_THRESHOLD 26 // Continue tracking | |
| #define TOUCH_DELAY_THRESHOLD 28 // Delayed report | |
| #define TOUCH_DELAY 5 // Frames to wait | |
| #define LARGE_AREA_UNPRESS 22 // Large touch end | |
| #define LARGE_AREA_FRINGE 5 // Touch edge | |
| ``` | |
| **Stylus Mode:** | |
| ```c | |
| #define TOUCH_INITIAL_THRESHOLD_S 32 | |
| #define TOUCH_CONTINUE_THRESHOLD_S 16 // Lower threshold | |
| #define TOUCH_DELAY_THRESHOLD_S 24 | |
| #define TOUCH_DELAY_S 2 | |
| ``` | |
| **Settable via socket or `/etc/tssettings` file.** | |
| ### Touch Blob Detection | |
| **Weighted Centroid Calculation:** | |
| ```c | |
| // 1. Scan matrix for values above threshold | |
| for (i = 0; i < X_AXIS_POINTS; i++) { | |
| for (j = 0; j < Y_AXIS_POINTS; j++) { | |
| if (matrix[i][j] > touch_continue_thresh && !invalid_matrix[i][j]) { | |
| // 2. Recursively find connected area | |
| determine_area_loc(&isum, &jsum, &tweight, i, j, | |
| &mini, &maxi, &minj, &maxj, | |
| touch_id, &highest_val); | |
| // 3. Calculate weighted centroid | |
| powered = pow(matrix[i][j], 1.5); // 1.5-power scaling | |
| tweight += powered; | |
| isum += powered * i; | |
| jsum += powered * j; | |
| // 4. Centroid coordinates | |
| avgi = isum / (float)tweight; | |
| avgj = jsum / (float)tweight; | |
| } | |
| } | |
| } | |
| ``` | |
| **Touch Area Size:** | |
| ```c | |
| #define PIXELS_PER_POINT 25 // Roughly 1024/40 or 768/30 | |
| int maxi = maxi - mini; // Height in matrix units | |
| int maxj = maxj - minj; // Width in matrix units | |
| touch_major = MAX(maxi, maxj) * PIXELS_PER_POINT; | |
| ``` | |
| ### Touch Tracking | |
| **Nearest Neighbor Matching:** | |
| ```c | |
| // Match current touch to previous touch by distance | |
| for (k = 0; k < previoustpc; k++) { | |
| int dx = tp[tpoint][i].unfiltered_x - tp[prevtpoint][k].x; | |
| int dy = tp[tpoint][i].unfiltered_y - tp[prevtpoint][k].y; | |
| int distance_sq = dx*dx + dy*dy; | |
| if (distance_sq < min_distance_sq) { | |
| min_distance_sq = distance_sq; | |
| closest_prev_touch = k; | |
| } | |
| } | |
| // Inherit tracking ID from closest match | |
| if (closest_prev_touch >= 0) | |
| tp[tpoint][i].tracking_id = tp[prevtpoint][closest_prev_touch].tracking_id; | |
| ``` | |
| **Jump Detection:** | |
| ```c | |
| #define MAX_DELTA 130 // pixels | |
| #define MIN_PREV_DELTA 40 // pixels | |
| #define MAX_DELTA_ANGLE 0.25 // radians | |
| // If jump exceeds threshold, check for consistent motion | |
| if (distance_sq > MAX_DELTA_SQ) { | |
| // Check previous touch movement | |
| float prev_direction = tp[prevtpoint][k].direction; | |
| float prev_distance = tp[prevtpoint][k].distance; | |
| // Allow jump if previous motion was similar | |
| if (prev_distance >= MIN_PREV_DELTA && | |
| abs(direction - prev_direction) < MAX_DELTA_ANGLE) { | |
| // Accept as swipe continuation | |
| } else { | |
| // Reject as separate touch (lift + new touch) | |
| tp[tpoint][i].tracking_id = new_tracking_id++; | |
| } | |
| } | |
| ``` | |
| ### Debouncing Filters | |
| **Initial Touch Debounce:** | |
| ```c | |
| #define DEBOUNCE_RADIUS 10 // pixels (actually a square) | |
| // First touch in new location | |
| if (new_touch) { | |
| initialx = tp[tpoint][i].x; | |
| initialy = tp[tpoint][i].y; | |
| } | |
| // Keep initial position if within radius | |
| if (abs(tp[tpoint][i].x - initialx) < DEBOUNCE_RADIUS && | |
| abs(tp[tpoint][i].y - initialy) < DEBOUNCE_RADIUS) { | |
| tp[tpoint][i].x = initialx; | |
| tp[tpoint][i].y = initialy; | |
| } | |
| ``` | |
| **Hover Debounce (After Swipe):** | |
| ```c | |
| #define HOVER_DEBOUNCE_RADIUS 2 // pixels | |
| #define HOVER_DEBOUNCE_DELAY 30 // frames | |
| // Track consistency over multiple frames | |
| if (abs(tp[tpoint][i].x - tp[tpoint][i].hover_x) < HOVER_DEBOUNCE_RADIUS && | |
| abs(tp[tpoint][i].y - tp[tpoint][i].hover_y) < HOVER_DEBOUNCE_RADIUS) { | |
| tp[tpoint][i].hover_delay++; | |
| } else { | |
| // Position changed, reset tracking | |
| tp[tpoint][i].hover_x = tp[tpoint][i].x; | |
| tp[tpoint][i].hover_y = tp[tpoint][i].y; | |
| tp[tpoint][i].hover_delay = 0; | |
| } | |
| // Start debouncing after stable period | |
| if (tp[tpoint][i].hover_delay >= HOVER_DEBOUNCE_DELAY) { | |
| // Apply debounce filter | |
| } | |
| ``` | |
| ## Linux Input Integration | |
| ### uinput Device Setup | |
| ```c | |
| void open_uinput(void) { | |
| struct uinput_user_dev device; | |
| memset(&device, 0, sizeof device); | |
| uinput_fd = open("/dev/uinput", O_WRONLY); | |
| strcpy(device.name, "HPTouchpad"); | |
| // Enable event types | |
| ioctl(uinput_fd, UI_SET_EVBIT, EV_SYN); | |
| ioctl(uinput_fd, UI_SET_EVBIT, EV_ABS); | |
| ioctl(uinput_fd, UI_SET_EVBIT, EV_KEY); | |
| // Multi-touch protocol A (not B!) | |
| ioctl(uinput_fd, UI_SET_ABSBIT, ABS_MT_POSITION_X); | |
| ioctl(uinput_fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y); | |
| ioctl(uinput_fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR); | |
| ioctl(uinput_fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID); | |
| ioctl(uinput_fd, UI_SET_ABSBIT, ABS_MT_PRESSURE); | |
| // Set axis ranges | |
| device.absmin[ABS_MT_POSITION_X] = 0; | |
| device.absmax[ABS_MT_POSITION_X] = X_RESOLUTION - 1; | |
| device.absmin[ABS_MT_POSITION_Y] = 0; | |
| device.absmax[ABS_MT_POSITION_Y] = Y_RESOLUTION - 1; | |
| device.absmin[ABS_MT_TRACKING_ID] = 0; | |
| device.absmax[ABS_MT_TRACKING_ID] = TRACKING_ID_MAX; | |
| device.absmin[ABS_MT_PRESSURE] = 0; | |
| device.absmax[ABS_MT_PRESSURE] = 255; | |
| write(uinput_fd, &device, sizeof(device)); | |
| ioctl(uinput_fd, UI_DEV_CREATE); | |
| } | |
| ``` | |
| **Note:** Protocol B support exists (`USE_B_PROTOCOL`) but is **disabled** by default due to kernel issues on TouchPad. | |
| ### Event Reporting (Protocol A) | |
| ```c | |
| // For each active touch | |
| send_uevent(uinput_fd, EV_ABS, ABS_MT_TRACKING_ID, tracking_id); | |
| send_uevent(uinput_fd, EV_ABS, ABS_MT_TOUCH_MAJOR, touch_major); | |
| send_uevent(uinput_fd, EV_ABS, ABS_MT_POSITION_X, x); | |
| send_uevent(uinput_fd, EV_ABS, ABS_MT_POSITION_Y, y); | |
| send_uevent(uinput_fd, EV_ABS, ABS_MT_PRESSURE, pressure); | |
| send_uevent(uinput_fd, EV_SYN, SYN_MT_REPORT, 0); // End of touch | |
| // After all touches | |
| send_uevent(uinput_fd, EV_SYN, SYN_REPORT, 0); // Frame sync | |
| ``` | |
| ### Lift-Off Handling | |
| ```c | |
| #define LIFTOFF_TIMEOUT 25000 // microseconds | |
| // Timeout mechanism | |
| select(uart_fd + 1, &fdset, NULL, NULL, &timeout); | |
| if (select_ret == 0) { | |
| // No data for 25ms = liftoff | |
| if (need_liftoff) { | |
| liftoff(); // Send all tracking IDs as -1 | |
| clear_arrays(); | |
| } | |
| } | |
| ``` | |
| ## Runtime Control | |
| ### Socket Interface | |
| **Location:** `/run/tsdriver.sock` | |
| **Type:** UNIX domain socket, SOCK_STREAM | |
| **Permissions:** 0666 (world readable/writable) | |
| **Commands:** | |
| | Byte | Command | Description | | |
| |------|---------|-------------| | |
| | 'P' | Power off | Turn off touchscreen | | |
| | 'Q' | Power on | Turn on touchscreen | | |
| | 'F' | Finger mode | Set to finger thresholds | | |
| | 'S' | Stylus mode | Set to stylus thresholds | | |
| | 'M' | Get mode | Return current mode (0=finger, 1=stylus) | | |
| **Example Usage:** | |
| ```bash | |
| # Turn off touchscreen | |
| echo -n "P" | nc -U /run/tsdriver.sock | |
| # Turn on touchscreen | |
| echo -n "Q" | nc -U /run/tsdriver.sock | |
| # Switch to stylus mode | |
| echo -n "S" | nc -U /run/tsdriver.sock | |
| # Get current mode | |
| echo -n "M" | nc -U /run/tsdriver.sock | |
| ``` | |
| ### Settings File | |
| **Location:** `/etc/tssettings` | |
| **Format:** Single byte (0 or 1) | |
| ```bash | |
| # Finger mode | |
| echo -n "0" > /etc/tssettings | |
| # Stylus mode | |
| echo -n "1" > /etc/tssettings | |
| ``` | |
| **Auto-loaded on startup.** | |
| ## Performance Optimizations | |
| ### Real-Time Scheduling | |
| ```c | |
| struct sched_param sparam = { .sched_priority = 99 }; | |
| if (sched_setscheduler(0, SCHED_FIFO, &sparam)) | |
| perror("Cannot set RT priority, ignoring: "); | |
| ``` | |
| **Priority 99:** Linux maximum RT priority | |
| **SCHED_FIFO:** Never preempted until voluntarily yields CPU | |
| **Goal:** Zero latency in touch processing | |
| ### Efficient Event Loop | |
| ```c | |
| while (1) { | |
| FD_ZERO(&fdset); | |
| FD_SET(uart_fd, &fdset); | |
| FD_SET(socket_fd, &fdset); | |
| // Wait for data with timeout | |
| select(max_fd + 1, &fdset, NULL, NULL, &timeout); | |
| if (FD_ISSET(uart_fd, &fdset)) { | |
| // Process touch data | |
| } | |
| if (FD_ISSET(socket_fd, &fdset)) { | |
| // Process commands | |
| } | |
| } | |
| ``` | |
| **Advantages:** | |
| - Sleep until data arrives (low CPU) | |
| - Automatic liftoff detection via timeout | |
| - Multiplexes UART and socket | |
| ## Issues and Required Fixes for Mainline Linux | |
| ### 1. I²C Bus Number (CRITICAL) | |
| **Current Code:** | |
| ```c | |
| i2c_fd = open("/dev/i2c-5", O_RDWR); | |
| ``` | |
| **Likely Needed:** | |
| ```c | |
| i2c_fd = open("/dev/i2c-10", O_RDWR); // GSBI10 = bus 10 | |
| ``` | |
| **Verification:** Check `/sys/bus/i2c/devices/` after booting mainline kernel. | |
| ### 2. Wake GPIO Path (CRITICAL) | |
| **Current Code:** | |
| ```c | |
| wake_fd = open("/sys/user_hw/pins/ctp/wake/level", O_WRONLY); | |
| ``` | |
| **Fix Option A - Export GPIO:** | |
| ```bash | |
| echo 123 > /sys/class/gpio/export | |
| echo out > /sys/class/gpio/gpio123/direction | |
| ``` | |
| ```c | |
| wake_fd = open("/sys/class/gpio/gpio123/value", O_WRONLY); | |
| ``` | |
| **Fix Option B - Remove Wake Control:** | |
| The wake pin (GPIO 123) is also used as the IRQ pin. The cy8ctma395 driver may handle it automatically. Try commenting out wake control entirely. | |
| ### 3. UART Device Node | |
| **Current:** `/dev/ctp_uart` | |
| **Expected:** `/dev/ttyMSM2` (after setting `USE_I2C_TOUCHSCREEN_DRIVER=0`) | |
| **Fix:** Create udev rule or symlink: | |
| ```bash | |
| # Udev rule | |
| KERNEL=="ttyMSM2", SYMLINK+="ctp_uart" | |
| ``` | |
| Or modify code: | |
| ```c | |
| *uart_fd = open("/dev/ttyMSM2", O_RDONLY|O_NONBLOCK); | |
| ``` | |
| ### 4. HSUART ioctls | |
| **Current:** Uses Palm HSUART custom ioctls (`HSUART_IOCTL_*`) | |
| **Mainline Alternative:** Standard termios | |
| ```c | |
| #include <termios.h> | |
| int fd = open("/dev/ttyMSM2", O_RDONLY | O_NOCTTY); | |
| struct termios tty; | |
| tcgetattr(fd, &tty); | |
| cfsetispeed(&tty, B4000000); // May need custom baud rate ioctl | |
| cfmakeraw(&tty); | |
| tcsetattr(fd, TCSANOW, &tty); | |
| ``` | |
| **Issue:** Standard termios may not support 4 Mbps. May need MSM UART driver custom ioctl. | |
| ### 5. Logging | |
| **Current:** All logging disabled (no-ops) | |
| **Recommendation:** Add syslog support | |
| ```c | |
| #include <syslog.h> | |
| #define ALOGD(...) syslog(LOG_DEBUG, __VA_ARGS__) | |
| #define ALOGI(...) syslog(LOG_INFO, __VA_ARGS__) | |
| #define ALOGE(...) syslog(LOG_ERR, __VA_ARGS__) | |
| // In main() | |
| openlog("ts-srv", LOG_PID | LOG_CONS, LOG_DAEMON); | |
| ``` | |
| ### 6. Systemd Integration | |
| Create `/etc/systemd/system/ts-srv.service`: | |
| ```ini | |
| [Unit] | |
| Description=HP TouchPad Touchscreen Driver | |
| After=sysinit.target | |
| [Service] | |
| Type=simple | |
| ExecStart=/usr/sbin/ts-srv | |
| Restart=always | |
| RestartSec=1 | |
| [Install] | |
| WantedBy=multi-user.target | |
| ``` | |
| ## Build Instructions for Modern Linux | |
| ### Dependencies | |
| ```bash | |
| # Debian/Ubuntu | |
| sudo apt-get install build-essential libc6-dev linux-headers-$(uname -r) | |
| # Fedora | |
| sudo dnf install gcc glibc-devel kernel-devel | |
| ``` | |
| ### Compilation | |
| ```bash | |
| cd /home/herrie/webos/touchpad-kernel/touchscreen | |
| gcc -o ts-srv ts_srv.c digitizer.c -lm -O2 -Wall | |
| gcc -o ts-srv-set ts_srv_set.c -O2 -Wall | |
| ``` | |
| **Note:** Requires math library (`-lm`) for `pow()` and `sqrt()`. | |
| ### Installation | |
| ```bash | |
| sudo install -m 755 ts-srv /usr/sbin/ts-srv | |
| sudo install -m 755 ts-srv-set /usr/sbin/ts-srv-set | |
| # Create socket directory | |
| sudo mkdir -p /run | |
| # Set default mode (finger) | |
| echo -n "0" | sudo tee /etc/tssettings | |
| # Install systemd service | |
| sudo cp ts-srv.service /etc/systemd/system/ | |
| sudo systemctl daemon-reload | |
| sudo systemctl enable ts-srv | |
| ``` | |
| ## Testing Procedure | |
| ### 1. Verify Device Tree Configuration | |
| ```bash | |
| # Check UART mode is enabled | |
| grep "USE_I2C_TOUCHSCREEN_DRIVER" /path/to/device-tree-source.dts | |
| # Should be 0, not 1 | |
| # Verify GSBI10 mode | |
| grep -A5 "gsbi10:" /path/to/device-tree-source.dts | |
| # Should have qcom,mode = <GSBI_PROT_I2C_UART> | |
| ``` | |
| ### 2. Check UART Device | |
| ```bash | |
| ls -l /dev/ttyMSM2 | |
| # Should exist if device tree is correct | |
| # Or create symlink | |
| sudo ln -s /dev/ttyMSM2 /dev/ctp_uart | |
| ``` | |
| ### 3. Verify I²C Bus | |
| ```bash | |
| ls /dev/i2c-* | |
| # Should see /dev/i2c-10 (or /dev/i2c-5) | |
| # Test I²C communication | |
| sudo i2cdetect -y 10 | |
| # Should see device at 0x67 | |
| ``` | |
| ### 4. Export Wake GPIO (if needed) | |
| ```bash | |
| echo 123 > /sys/class/gpio/export | |
| echo out > /sys/class/gpio/gpio123/direction | |
| echo 1 > /sys/class/gpio/gpio123/value | |
| ``` | |
| ### 5. Run Driver | |
| ```bash | |
| # Run in foreground for debugging | |
| sudo /usr/sbin/ts-srv | |
| # Check for errors | |
| dmesg | tail -50 | |
| # In another terminal, test touch events | |
| sudo evtest | |
| # Select HPTouchpad device | |
| # Touch screen and verify events appear | |
| ``` | |
| ### 6. Control via Socket | |
| ```bash | |
| # Test socket commands | |
| echo -n "P" | nc -U /run/tsdriver.sock # Power off | |
| sleep 1 | |
| echo -n "Q" | nc -U /run/tsdriver.sock # Power on | |
| ``` | |
| ## Summary of Cleaning Changes | |
| | Aspect | Android Version | Cleaned Linux Version | | |
| |--------|----------------|----------------------| | |
| | Logging | `<cutils/log.h>` | Stub no-ops in `log.h` | | |
| | Socket path | `/dev/socket/tsdriver` | `/run/tsdriver.sock` | | |
| | Settings file | `/data/tssettings` | `/etc/tssettings` | | |
| | I²C bus | Unknown/varies | `/dev/i2c-5` (may need →10) | | |
| | Wake GPIO | webOS sysfs | `/sys/user_hw/pins/...` (needs fix) | | |
| | UART device | `/dev/ctp_uart` | Same (needs symlink) | | |
| | Build system | Android NDK | Standard GCC | | |
| ## Conclusion | |
| The cleaned-up `ts-srv` driver is **90% ready** for mainline Linux use. The remaining issues are: | |
| **Required Fixes:** | |
| 1. ✓ Change I²C bus from 5 to 10 (or verify actual number) | |
| 2. ✓ Fix wake GPIO path (export GPIO 123 or remove) | |
| 3. ✓ Create `/dev/ctp_uart` symlink to `/dev/ttyMSM2` | |
| 4. ? Replace HSUART ioctls with termios (if MSM UART doesn't support them) | |
| **Optional Improvements:** | |
| - Add syslog logging | |
| - Create systemd service | |
| - Add command-line options | |
| - Better error handling | |
| **Estimated effort:** 1-2 days for fixes, testing, and integration. | |
| The code quality is excellent, the algorithm is well-documented, and the touch processing logic is production-ready. This is a solid foundation for getting the HP TouchPad touchscreen working on mainline Linux. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment