| /* Copyright 2016 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "byteorder.h" |
| #include "common.h" |
| #include "console.h" |
| #include "gpio.h" |
| #include "hwtimer.h" |
| #include "hooks.h" |
| #include "i2c.h" |
| #include "sha256.h" |
| #include "shared_mem.h" |
| #include "task.h" |
| #include "tablet_mode.h" |
| #include "timer.h" |
| #include "touchpad.h" |
| #include "update_fw.h" |
| #include "util.h" |
| #include "usb_api.h" |
| #include "usb_hid_touchpad.h" |
| #include "watchdog.h" |
| |
| /* Console output macros */ |
| #define CPUTS(outstr) cputs(CC_TOUCHPAD, outstr) |
| #define CPRINTF(format, args...) cprintf(CC_TOUCHPAD, format, ## args) |
| #define CPRINTS(format, args...) cprints(CC_TOUCHPAD, format, ## args) |
| |
| #define TASK_EVENT_POWER TASK_EVENT_CUSTOM_BIT(0) |
| |
| /******************************************************************************/ |
| /* How to talk to the controller */ |
| /******************************************************************************/ |
| |
| #define ELAN_VENDOR_ID 0x04f3 |
| |
| #define ETP_I2C_RESET 0x0100 |
| #define ETP_I2C_WAKE_UP 0x0800 |
| #define ETP_I2C_SLEEP 0x0801 |
| |
| #define ETP_I2C_STAND_CMD 0x0005 |
| #define ETP_I2C_UNIQUEID_CMD 0x0101 |
| #define ETP_I2C_FW_VERSION_CMD 0x0102 |
| #define ETP_I2C_OSM_VERSION_CMD 0x0103 |
| #define ETP_I2C_XY_TRACENUM_CMD 0x0105 |
| #define ETP_I2C_MAX_X_AXIS_CMD 0x0106 |
| #define ETP_I2C_MAX_Y_AXIS_CMD 0x0107 |
| #define ETP_I2C_RESOLUTION_CMD 0x0108 |
| #define ETP_I2C_IAP_VERSION_CMD 0x0110 |
| #define ETP_I2C_PRESSURE_CMD 0x010A |
| #define ETP_I2C_SET_CMD 0x0300 |
| #define ETP_I2C_IAP_TYPE_CMD 0x0304 |
| #define ETP_I2C_POWER_CMD 0x0307 |
| #define ETP_I2C_FW_CHECKSUM_CMD 0x030F |
| |
| #define ETP_ENABLE_ABS 0x0001 |
| |
| #define ETP_DISABLE_POWER 0x0001 |
| |
| #define ETP_I2C_REPORT_LEN 34 |
| |
| #define ETP_MAX_FINGERS 5 |
| #define ETP_FINGER_DATA_LEN 5 |
| |
| #define ETP_PRESSURE_OFFSET 25 |
| #define ETP_FWIDTH_REDUCE 90 |
| |
| #define ETP_REPORT_ID 0x5D |
| #define ETP_REPORT_ID_OFFSET 2 |
| #define ETP_TOUCH_INFO_OFFSET 3 |
| #define ETP_FINGER_DATA_OFFSET 4 |
| #define ETP_HOVER_INFO_OFFSET 30 |
| #define ETP_MAX_REPORT_LEN 34 |
| |
| #define ETP_IAP_START_ADDR 0x0083 |
| |
| #define ETP_I2C_IAP_RESET_CMD 0x0314 |
| #define ETP_I2C_IAP_RESET 0xF0F0 |
| #define ETP_I2C_IAP_CTRL_CMD 0x0310 |
| #define ETP_I2C_MAIN_MODE_ON BIT(9) |
| #define ETP_I2C_IAP_CMD 0x0311 |
| #define ETP_I2C_IAP_PASSWORD 0x1EA5 |
| |
| #define ETP_I2C_IAP_REG_L 0x01 |
| #define ETP_I2C_IAP_REG_H 0x06 |
| |
| #define ETP_FW_IAP_PAGE_ERR BIT(5) |
| #define ETP_FW_IAP_INTF_ERR BIT(4) |
| |
| #ifdef CONFIG_USB_UPDATE |
| /* The actual FW_SIZE depends on IC. */ |
| #define FW_SIZE CONFIG_TOUCHPAD_VIRTUAL_SIZE |
| #endif |
| |
| struct { |
| /* Max X/Y position */ |
| uint16_t max_x; |
| uint16_t max_y; |
| /* Scaling factor for finger width/height */ |
| uint16_t width_x; |
| uint16_t width_y; |
| /* Pressure adjustment */ |
| uint8_t pressure_adj; |
| uint16_t ic_type; |
| uint16_t page_count; |
| uint16_t page_size; |
| uint16_t iap_version; |
| } elan_tp_params; |
| |
| /* |
| * Report a more reasonable pressure value, so that no adjustment is necessary |
| * on Chrome OS side. 3216/1024 ~= 3.1416. |
| */ |
| const int pressure_mult = 3216; |
| const int pressure_div = 1024; |
| |
| static int elan_tp_read_cmd(uint16_t reg, uint16_t *val) |
| { |
| uint8_t buf[2]; |
| |
| buf[0] = reg; |
| buf[1] = reg >> 8; |
| |
| return i2c_xfer(CONFIG_TOUCHPAD_I2C_PORT, |
| CONFIG_TOUCHPAD_I2C_ADDR_FLAGS, |
| buf, sizeof(buf), (uint8_t *)val, sizeof(*val)); |
| } |
| |
| static int elan_tp_write_cmd(uint16_t reg, uint16_t val) |
| { |
| uint8_t buf[4]; |
| |
| buf[0] = reg; |
| buf[1] = reg >> 8; |
| buf[2] = val; |
| buf[3] = val >> 8; |
| |
| return i2c_xfer(CONFIG_TOUCHPAD_I2C_PORT, |
| CONFIG_TOUCHPAD_I2C_ADDR_FLAGS, |
| buf, sizeof(buf), NULL, 0); |
| } |
| |
| /* Power is on by default. */ |
| static int elan_tp_power = 1; |
| |
| static int elan_tp_set_power(int enable) |
| { |
| int rv; |
| uint16_t val; |
| |
| if ((enable && elan_tp_power) || (!enable && !elan_tp_power)) |
| return EC_SUCCESS; |
| |
| CPRINTS("elan TP power %s", enable ? "on" : "off"); |
| |
| rv = elan_tp_read_cmd(ETP_I2C_POWER_CMD, &val); |
| if (rv) |
| goto out; |
| |
| if (enable) |
| val &= ~ETP_DISABLE_POWER; |
| else |
| val |= ETP_DISABLE_POWER; |
| |
| rv = elan_tp_write_cmd(ETP_I2C_POWER_CMD, val); |
| |
| elan_tp_power = enable; |
| out: |
| return rv; |
| } |
| |
| static int finger_status[ETP_MAX_FINGERS] = {0}; |
| |
| /* |
| * Timestamp of last interrupt (32 bits are enough as we divide the value by 100 |
| * and then put it in a 16-bit field). |
| */ |
| static uint32_t irq_ts; |
| |
| /* |
| * Read touchpad report. |
| * Returns 0 on success, positive (EC_RES_*) value on I2C error, and a negative |
| * value if the I2C transaction is successful but the data is invalid (fairly |
| * common). |
| */ |
| static int elan_tp_read_report(void) |
| { |
| int rv; |
| uint8_t tp_buf[ETP_I2C_REPORT_LEN]; |
| int i, ri; |
| uint8_t touch_info; |
| uint8_t hover_info; |
| uint8_t *finger = tp_buf+ETP_FINGER_DATA_OFFSET; |
| struct usb_hid_touchpad_report report; |
| uint16_t timestamp; |
| |
| /* Compute and save timestamp early in case another interrupt comes. */ |
| timestamp = irq_ts / USB_HID_TOUCHPAD_TIMESTAMP_UNIT; |
| |
| rv = i2c_xfer(CONFIG_TOUCHPAD_I2C_PORT, |
| CONFIG_TOUCHPAD_I2C_ADDR_FLAGS, |
| NULL, 0, tp_buf, ETP_I2C_REPORT_LEN); |
| |
| if (rv) { |
| CPRINTS("read report error (%d)", rv); |
| return rv; |
| } |
| |
| if (tp_buf[ETP_REPORT_ID_OFFSET] != ETP_REPORT_ID) { |
| CPRINTS("Invalid report id (%x)", tp_buf[ETP_REPORT_ID_OFFSET]); |
| return -1; |
| } |
| |
| memset(&report, 0, sizeof(report)); |
| report.id = 0x01; |
| ri = 0; /* Next finger index in HID report */ |
| |
| touch_info = tp_buf[ETP_TOUCH_INFO_OFFSET]; |
| hover_info = tp_buf[ETP_HOVER_INFO_OFFSET]; |
| |
| for (i = 0; i < ETP_MAX_FINGERS; i++) { |
| int valid = touch_info & (1 << (3+i)); |
| |
| if (valid) { |
| int width = finger[3] & 0x0f; |
| int height = (finger[3] & 0xf0) >> 4; |
| int pressure = finger[4] + elan_tp_params.pressure_adj; |
| pressure = DIV_ROUND_NEAREST(pressure * pressure_mult, |
| pressure_div); |
| |
| width = MIN(4095, width * elan_tp_params.width_x); |
| height = MIN(4095, height * elan_tp_params.width_y); |
| pressure = MIN(1023, pressure); |
| |
| report.finger[ri].confidence = 1; |
| report.finger[ri].tip = 1; |
| report.finger[ri].inrange = 1; |
| report.finger[ri].id = i; |
| report.finger[ri].width = width; |
| report.finger[ri].height = height; |
| report.finger[ri].x = |
| ((finger[0] & 0xf0) << 4) | finger[1]; |
| report.finger[ri].y = |
| elan_tp_params.max_y - |
| (((finger[0] & 0x0f) << 8) | finger[2]); |
| report.finger[ri].pressure = pressure; |
| finger += ETP_FINGER_DATA_LEN; |
| ri++; |
| finger_status[i] = 1; |
| } else if (finger_status[i]) { |
| report.finger[ri].id = i; |
| /* When a finger is leaving, it's not a plam */ |
| report.finger[ri].confidence = 1; |
| ri++; |
| finger_status[i] = 0; |
| } |
| } |
| |
| report.count = ri; |
| report.timestamp = timestamp; |
| |
| if (touch_info & 0x01) { |
| /* Do not report zero-finger click events */ |
| if (report.count > 0) |
| report.button = 1; |
| } |
| |
| if (hover_info & 0x40) { |
| /* TODO(b/35582031): Report hover event */ |
| CPRINTF("[TP] hover!\n"); |
| } |
| |
| set_touchpad_report(&report); |
| |
| return 0; |
| } |
| |
| static int elan_get_fwinfo(void) |
| { |
| uint16_t ic_type = elan_tp_params.ic_type; |
| uint16_t iap_version = elan_tp_params.iap_version; |
| |
| switch (ic_type) { |
| case 0x09: |
| elan_tp_params.page_count = 768; |
| break; |
| case 0x0D: |
| elan_tp_params.page_count = 896; |
| break; |
| case 0x00: |
| case 0x10: |
| case 0x14: |
| elan_tp_params.page_count = 1024; |
| break; |
| default: |
| CPRINTS("unknown ic_type: %d", ic_type); |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| if (ic_type == 0x14 && iap_version >= 2) { |
| elan_tp_params.page_count /= 8; |
| elan_tp_params.page_size = 512; |
| } else if (ic_type >= 0x0D && iap_version >= 1) { |
| elan_tp_params.page_count /= 2; |
| elan_tp_params.page_size = 128; |
| } else { |
| elan_tp_params.page_size = 64; |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| /* Initialize the controller ICs after reset */ |
| static void elan_tp_init(void) |
| { |
| int rv; |
| uint8_t val[2]; |
| int dpi_x, dpi_y; |
| |
| CPRINTS("%s", __func__); |
| |
| elan_tp_write_cmd(ETP_I2C_STAND_CMD, ETP_I2C_RESET); |
| msleep(100); |
| rv = i2c_xfer(CONFIG_TOUCHPAD_I2C_PORT, |
| CONFIG_TOUCHPAD_I2C_ADDR_FLAGS, |
| NULL, 0, val, sizeof(val)); |
| |
| CPRINTS("reset rv %d buf=%04x", rv, *((uint16_t *)val)); |
| if (rv) |
| goto out; |
| |
| /* Read IC type, IAP version */ |
| rv = elan_tp_read_cmd(ETP_I2C_OSM_VERSION_CMD, &elan_tp_params.ic_type); |
| CPRINTS("%s: ic_type:%04X.", __func__, elan_tp_params.ic_type); |
| elan_tp_params.ic_type >>= 8; |
| if (rv) |
| goto out; |
| |
| rv = elan_tp_read_cmd(ETP_I2C_IAP_VERSION_CMD, |
| &elan_tp_params.iap_version); |
| CPRINTS("%s: iap_version:%04X.", __func__, elan_tp_params.iap_version); |
| elan_tp_params.iap_version >>= 8; |
| if (rv) |
| goto out; |
| |
| rv = elan_get_fwinfo(); |
| if (rv) |
| goto out; |
| |
| /* Read min/max */ |
| rv = elan_tp_read_cmd(ETP_I2C_MAX_X_AXIS_CMD, &elan_tp_params.max_x); |
| if (rv) |
| goto out; |
| rv = elan_tp_read_cmd(ETP_I2C_MAX_Y_AXIS_CMD, &elan_tp_params.max_y); |
| if (rv) |
| goto out; |
| |
| /* Read min/max */ |
| rv = elan_tp_read_cmd(ETP_I2C_XY_TRACENUM_CMD, (uint16_t *)val); |
| if (rv) |
| goto out; |
| if (val[0] == 0 || val[1] == 0) { |
| CPRINTS("Invalid XY_TRACENUM"); |
| goto out; |
| } |
| |
| /* ETP_FWIDTH_REDUCE reduces the apparent width to avoid treating large |
| * finger as palm. Multiply value by 2 as HID multitouch divides it. |
| */ |
| elan_tp_params.width_x = |
| 2 * ((elan_tp_params.max_x / val[0]) - ETP_FWIDTH_REDUCE); |
| elan_tp_params.width_y = |
| 2 * ((elan_tp_params.max_y / val[1]) - ETP_FWIDTH_REDUCE); |
| |
| rv = elan_tp_read_cmd(ETP_I2C_PRESSURE_CMD, (uint16_t *)val); |
| if (rv) |
| goto out; |
| elan_tp_params.pressure_adj = (val[0] & 0x10) ? 0 : ETP_PRESSURE_OFFSET; |
| |
| rv = elan_tp_read_cmd(ETP_I2C_RESOLUTION_CMD, (uint16_t *)val); |
| if (rv) |
| goto out; |
| |
| dpi_x = 10*val[0] + 790; |
| dpi_y = 10*val[1] + 790; |
| |
| CPRINTS("max=%d/%d width=%d/%d adj=%d dpi=%d/%d", |
| elan_tp_params.max_x, elan_tp_params.max_y, |
| elan_tp_params.width_x, elan_tp_params.width_y, |
| elan_tp_params.pressure_adj, dpi_x, dpi_y); |
| |
| #ifdef CONFIG_USB_HID_TOUCHPAD |
| /* |
| * Validity check dimensions provided at build time. |
| * - dpi == logical dimension / physical dimension (inches) |
| * (254 tenths of mm per inch) |
| */ |
| if (elan_tp_params.max_x != CONFIG_USB_HID_TOUCHPAD_LOGICAL_MAX_X || |
| elan_tp_params.max_y != CONFIG_USB_HID_TOUCHPAD_LOGICAL_MAX_Y || |
| dpi_x != 254*CONFIG_USB_HID_TOUCHPAD_LOGICAL_MAX_X / |
| CONFIG_USB_HID_TOUCHPAD_PHYSICAL_MAX_X || |
| dpi_y != 254*CONFIG_USB_HID_TOUCHPAD_LOGICAL_MAX_Y / |
| CONFIG_USB_HID_TOUCHPAD_PHYSICAL_MAX_Y) { |
| CPRINTS("*** TP mismatch!"); |
| } |
| #endif |
| |
| /* Switch to absolute mode */ |
| rv = elan_tp_write_cmd(ETP_I2C_SET_CMD, ETP_ENABLE_ABS); |
| if (rv) |
| goto out; |
| |
| /* Sleep control off */ |
| rv = elan_tp_write_cmd(ETP_I2C_STAND_CMD, ETP_I2C_WAKE_UP); |
| |
| /* Enable interrupt to fetch reports */ |
| gpio_enable_interrupt(GPIO_TOUCHPAD_INT); |
| |
| out: |
| CPRINTS("%s:%d", __func__, rv); |
| |
| return; |
| } |
| DECLARE_DEFERRED(elan_tp_init); |
| |
| #ifdef CONFIG_USB_UPDATE |
| int touchpad_get_info(struct touchpad_info *tp) |
| { |
| int rv; |
| uint16_t val; |
| |
| tp->status = EC_RES_SUCCESS; |
| tp->vendor = ELAN_VENDOR_ID; |
| |
| /* Get unique ID, FW, SM version. */ |
| rv = elan_tp_read_cmd(ETP_I2C_UNIQUEID_CMD, &val); |
| if (rv) |
| return -1; |
| tp->elan.id = val; |
| |
| rv = elan_tp_read_cmd(ETP_I2C_FW_VERSION_CMD, &val); |
| if (rv) |
| return -1; |
| tp->elan.fw_version = val & 0xff; |
| |
| rv = elan_tp_read_cmd(ETP_I2C_FW_CHECKSUM_CMD, &val); |
| if (rv) |
| return -1; |
| tp->elan.fw_checksum = val; |
| |
| return sizeof(*tp); |
| } |
| |
| static int elan_in_main_mode(void) |
| { |
| uint16_t val; |
| |
| elan_tp_read_cmd(ETP_I2C_IAP_CTRL_CMD, &val); |
| return val & ETP_I2C_MAIN_MODE_ON; |
| } |
| |
| static int elan_read_write_iap_type(void) |
| { |
| for (int retry = 0; retry < 3; ++retry) { |
| uint16_t val; |
| |
| if (elan_tp_write_cmd(ETP_I2C_IAP_TYPE_CMD, |
| elan_tp_params.page_size / 2)) |
| return EC_ERROR_UNKNOWN; |
| |
| if (elan_tp_read_cmd(ETP_I2C_IAP_TYPE_CMD, &val)) |
| return EC_ERROR_UNKNOWN; |
| |
| if (val == elan_tp_params.page_size / 2) |
| return EC_SUCCESS; |
| |
| } |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| static int elan_prepare_for_update(void) |
| { |
| uint16_t rx_buf; |
| int initial_mode; |
| |
| initial_mode = elan_in_main_mode(); |
| if (!initial_mode) { |
| CPRINTS("%s: In IAP mode, reset IC.", __func__); |
| elan_tp_write_cmd(ETP_I2C_IAP_RESET_CMD, ETP_I2C_IAP_RESET); |
| msleep(30); |
| } |
| /* Send the passphrase */ |
| elan_tp_write_cmd(ETP_I2C_IAP_CMD, ETP_I2C_IAP_PASSWORD); |
| msleep(initial_mode ? 100 : 30); |
| |
| /* We should be in the IAP mode now */ |
| if (elan_in_main_mode()) { |
| CPRINTS("%s: Failure to enter IAP mode.", __func__); |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| if (elan_tp_params.ic_type >= 0x0D && elan_tp_params.iap_version >= 1) { |
| if (elan_read_write_iap_type()) |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| /* Send the passphrase again */ |
| elan_tp_write_cmd(ETP_I2C_IAP_CMD, ETP_I2C_IAP_PASSWORD); |
| msleep(30); |
| |
| /* Verify the password */ |
| if (elan_tp_read_cmd(ETP_I2C_IAP_CMD, &rx_buf)) { |
| CPRINTS("%s: Cannot read IAP password.", __func__); |
| return EC_ERROR_UNKNOWN; |
| } |
| if (rx_buf != ETP_I2C_IAP_PASSWORD) { |
| CPRINTS("%s: Got an unexpected IAP password %0x4x.", __func__, |
| rx_buf); |
| return EC_ERROR_UNKNOWN; |
| } |
| return EC_SUCCESS; |
| } |
| |
| static int touchpad_update_page(const uint8_t *data) |
| { |
| const uint8_t cmd[2] = {ETP_I2C_IAP_REG_L, ETP_I2C_IAP_REG_H}; |
| uint16_t checksum = 0; |
| uint16_t rx_buf; |
| int i, rv; |
| |
| for (i = 0; i < elan_tp_params.page_size; i += 2) |
| checksum += ((uint16_t)(data[i + 1]) << 8) | (data[i]); |
| checksum = htole16(checksum); |
| |
| i2c_lock(CONFIG_TOUCHPAD_I2C_PORT, 1); |
| |
| rv = i2c_xfer_unlocked(CONFIG_TOUCHPAD_I2C_PORT, |
| CONFIG_TOUCHPAD_I2C_ADDR_FLAGS, |
| cmd, sizeof(cmd), NULL, 0, I2C_XFER_START); |
| if (rv) |
| goto fail; |
| rv = i2c_xfer_unlocked(CONFIG_TOUCHPAD_I2C_PORT, |
| CONFIG_TOUCHPAD_I2C_ADDR_FLAGS, |
| data, elan_tp_params.page_size, NULL, 0, 0); |
| if (rv) |
| goto fail; |
| rv = i2c_xfer_unlocked(CONFIG_TOUCHPAD_I2C_PORT, |
| CONFIG_TOUCHPAD_I2C_ADDR_FLAGS, |
| (uint8_t *)&checksum, sizeof(checksum), NULL, 0, |
| I2C_XFER_STOP); |
| if (rv) |
| goto fail; |
| |
| fail: |
| i2c_lock(CONFIG_TOUCHPAD_I2C_PORT, 0); |
| if (rv) |
| return rv; |
| msleep(elan_tp_params.page_size >= 512 ? 50 : 35); |
| |
| rv = elan_tp_read_cmd(ETP_I2C_IAP_CTRL_CMD, &rx_buf); |
| |
| if (rv || (rx_buf & (ETP_FW_IAP_PAGE_ERR | ETP_FW_IAP_INTF_ERR))) { |
| CPRINTS("%s: IAP reports failed write : %x.", |
| __func__, rx_buf); |
| return EC_ERROR_UNKNOWN; |
| } |
| return 0; |
| } |
| |
| int touchpad_update_write(int offset, int size, const uint8_t *data) |
| { |
| static int iap_addr = -1; |
| int addr, rv; |
| |
| CPRINTS("%s %08x %d", __func__, offset, size); |
| |
| if (offset == 0) { |
| /* Verify the IC type is aligned with defined firmware size */ |
| if (elan_tp_params.page_size * elan_tp_params.page_count |
| != FW_SIZE) { |
| CPRINTS("%s: IC(%d*%d) size and FW_SIZE(%d) mismatch", |
| __func__, elan_tp_params.page_count, |
| elan_tp_params.page_size, FW_SIZE); |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| gpio_disable_interrupt(GPIO_TOUCHPAD_INT); |
| CPRINTS("%s: prepare fw update.", __func__); |
| rv = elan_prepare_for_update(); |
| if (rv) |
| return rv; |
| iap_addr = 0; |
| } |
| |
| if (offset <= (ETP_IAP_START_ADDR * 2) && |
| (ETP_IAP_START_ADDR * 2) < (offset + size)) { |
| iap_addr = ((data[ETP_IAP_START_ADDR * 2 - offset + 1] << 8) | |
| data[ETP_IAP_START_ADDR * 2 - offset]) << 1; |
| CPRINTS("%s: payload starts from 0x%x.", __func__, iap_addr); |
| } |
| |
| /* Data that comes in must align with page_size */ |
| if (offset % elan_tp_params.page_size) |
| return EC_ERROR_INVAL; |
| |
| for (addr = offset; addr < (offset + size); |
| addr += elan_tp_params.page_size) { |
| if (iap_addr > addr) /* Skip chunk */ |
| continue; |
| rv = touchpad_update_page(data + addr - offset); |
| if (rv) |
| return rv; |
| CPRINTF("/p%d", addr / elan_tp_params.page_size); |
| watchdog_reload(); |
| } |
| CPRINTF("\n"); |
| |
| if (offset + size == FW_SIZE) { |
| CPRINTS("%s: End update, wait for reset.", __func__); |
| hook_call_deferred(&elan_tp_init_data, 600 * MSEC); |
| } |
| return EC_SUCCESS; |
| } |
| |
| /* Debugging mode. */ |
| |
| /* Allowed debug commands. We only store a hash of the allowed commands. */ |
| #define TOUCHPAD_ELAN_DEBUG_CMD_LENGTH 50 |
| #define TOUCHPAD_ELAN_DEBUG_NUM_CMD 2 |
| |
| static const uint8_t |
| allowed_command_hashes[TOUCHPAD_ELAN_DEBUG_NUM_CMD][SHA256_DIGEST_SIZE] = { |
| { |
| 0x0a, 0xf6, 0x37, 0x03, 0x93, 0xb2, 0xde, 0x8c, |
| 0x56, 0x7b, 0x86, 0xba, 0xa6, 0x79, 0xe3, 0xa3, |
| 0x8b, 0xc7, 0x15, 0xf2, 0x53, 0xcf, 0x71, 0x8b, |
| 0x3d, 0xe4, 0x81, 0xf9, 0xd9, 0xa8, 0x78, 0x48 |
| }, |
| { |
| 0xac, 0xe5, 0xbf, 0x17, 0x1f, 0xde, 0xce, 0x76, |
| 0x0c, 0x0e, 0xf8, 0xa2, 0xe9, 0x67, 0x2d, 0xc9, |
| 0x1b, 0xd4, 0xba, 0x34, 0x51, 0xca, 0xf6, 0x6d, |
| 0x7b, 0xb2, 0x1f, 0x14, 0x82, 0x1c, 0x0b, 0x74 |
| }, |
| }; |
| |
| /* Debugging commands need to allocate a <=1k buffer. */ |
| SHARED_MEM_CHECK_SIZE(1024); |
| |
| int touchpad_debug(const uint8_t *param, unsigned int param_size, |
| uint8_t **data, unsigned int *data_size) |
| { |
| static uint8_t *buffer; |
| static unsigned int buffer_size; |
| unsigned int offset; |
| |
| /* Offset parameter is 1 byte. */ |
| if (param_size < 1) |
| return EC_RES_INVALID_PARAM; |
| |
| /* |
| * Debug command, compute SHA-256, check that it matches allowed hashes, |
| * and execute I2C command. |
| * |
| * param[0] must be 0xff |
| * param[1] is the offset of the command in the data |
| * param[2] is the command length |
| * param[3-4] is the read-back length (MSB first), can be 0 |
| * param[5-49] is verified using SHA-256 hash. |
| */ |
| if (param[0] == 0xff && param_size == TOUCHPAD_ELAN_DEBUG_CMD_LENGTH) { |
| struct sha256_ctx ctx; |
| uint8_t *command_hash; |
| unsigned int offset = param[1]; |
| unsigned int write_length = param[2]; |
| unsigned int read_length = |
| ((unsigned int)param[3] << 8) | param[4]; |
| int i; |
| int match; |
| int rv; |
| |
| if (offset < 5 || write_length == 0 || |
| (offset + write_length) >= TOUCHPAD_ELAN_DEBUG_CMD_LENGTH) |
| return EC_RES_INVALID_PARAM; |
| |
| SHA256_init(&ctx); |
| SHA256_update(&ctx, param+5, TOUCHPAD_ELAN_DEBUG_CMD_LENGTH-5); |
| command_hash = SHA256_final(&ctx); |
| |
| match = 0; |
| for (i = 0; i < TOUCHPAD_ELAN_DEBUG_NUM_CMD; i++) { |
| if (!memcmp(command_hash, allowed_command_hashes[i], |
| sizeof(allowed_command_hashes[i]))) { |
| match = 1; |
| break; |
| } |
| } |
| |
| if (!match) |
| return EC_RES_INVALID_PARAM; |
| |
| if (buffer) { |
| shared_mem_release(buffer); |
| buffer = NULL; |
| } |
| |
| buffer_size = read_length; |
| |
| if (read_length > 0) { |
| if (shared_mem_acquire(buffer_size, |
| (char **)&buffer) != EC_SUCCESS) { |
| buffer = NULL; |
| buffer_size = 0; |
| return EC_RES_BUSY; |
| } |
| |
| memset(buffer, 0, buffer_size); |
| } |
| |
| rv = i2c_xfer(CONFIG_TOUCHPAD_I2C_PORT, |
| CONFIG_TOUCHPAD_I2C_ADDR_FLAGS, |
| ¶m[offset], write_length, |
| buffer, read_length); |
| |
| if (rv) |
| return EC_RES_BUS_ERROR; |
| |
| return EC_RES_SUCCESS; |
| } |
| |
| /* |
| * Data request: Retrieve previously read data from buffer, in blocks of |
| * 64 bytes. |
| */ |
| offset = param[0] * 64; |
| |
| if (!buffer) |
| return EC_RES_UNAVAILABLE; |
| |
| if (offset >= buffer_size) { |
| shared_mem_release(buffer); |
| buffer = NULL; |
| *data = NULL; |
| *data_size = 0; |
| return EC_RES_OVERFLOW; |
| } |
| |
| *data = buffer + offset; |
| *data_size = MIN(64, buffer_size - offset); |
| |
| return EC_RES_SUCCESS; |
| } |
| #endif |
| |
| /* |
| * Try to read touchpad report up to 3 times, reset the touchpad if we still |
| * fail. |
| */ |
| void elan_tp_read_report_retry(void) |
| { |
| int ret; |
| int retry = 3; |
| |
| while (retry--) { |
| ret = elan_tp_read_report(); |
| |
| if (ret <= 0) |
| return; |
| |
| /* Try again */ |
| msleep(1); |
| } |
| |
| /* Failed to read data, reset the touchpad. */ |
| CPRINTF("Resetting TP.\n"); |
| board_touchpad_reset(); |
| elan_tp_init(); |
| } |
| |
| void touchpad_interrupt(enum gpio_signal signal) |
| { |
| irq_ts = __hw_clock_source_read(); |
| |
| task_wake(TASK_ID_TOUCHPAD); |
| } |
| |
| /* Make a decision on touchpad power, based on USB and tablet mode status. */ |
| static void touchpad_power_control(void) |
| { |
| static int enabled = 1; |
| int enable = 1; |
| |
| #ifdef CONFIG_USB_SUSPEND |
| enable = enable && |
| (!usb_is_suspended() || usb_is_remote_wakeup_enabled()); |
| #endif |
| |
| #ifdef CONFIG_TABLET_MODE |
| enable = enable && !tablet_get_mode(); |
| #endif |
| |
| if (enabled == enable) |
| return; |
| |
| elan_tp_set_power(enable); |
| |
| enabled = enable; |
| } |
| |
| void touchpad_task(void *u) |
| { |
| uint32_t event; |
| |
| elan_tp_init(); |
| touchpad_power_control(); |
| |
| while (1) { |
| event = task_wait_event(-1); |
| |
| if (event & TASK_EVENT_WAKE) |
| elan_tp_read_report_retry(); |
| |
| if (event & TASK_EVENT_POWER) |
| touchpad_power_control(); |
| } |
| } |
| |
| /* |
| * When USB PM status changes, or tablet mode changes, call in the main task to |
| * decide whether to turn touchpad on or off. |
| */ |
| #if defined(CONFIG_USB_SUSPEND) || defined(CONFIG_TABLET_MODE) |
| static void touchpad_power_change(void) |
| { |
| task_set_event(TASK_ID_TOUCHPAD, TASK_EVENT_POWER); |
| } |
| #endif |
| #ifdef CONFIG_USB_SUSPEND |
| DECLARE_HOOK(HOOK_USB_PM_CHANGE, touchpad_power_change, HOOK_PRIO_DEFAULT); |
| #endif |
| #ifdef CONFIG_TABLET_MODE |
| DECLARE_HOOK(HOOK_TABLET_MODE_CHANGE, touchpad_power_change, HOOK_PRIO_DEFAULT); |
| #endif |