Files
OpenCellular/driver/touchpad_elan.c
Chun-Ta Lin e18d9dd530 hammer: Support different IC types in touchpad FW update
Some Elan touchpad ICs have a different firmware sizes (48, 56, or 64
KB). We use CONFIG_TOUCHPAD_VIRTUAL_SIZE, set in the board file, to
determine the appropriate size, and, at runtime, we sanity check the
firmware size according to the IC type reported by the touchpad.

BRANCH=none
BUG=b:65188846
TEST=Manually modify the CONFIG_TOUCHPAD_VIRTUAL_SIZE in hammer,
     executed and verified both (1) "EC_ERROR_UNKNOWN" returned
     (2) ic_type shows 0x09 on EC console
TEST=Successfully flashing 48k firmware using CL:658920 on hammer and
     56k firmware on staff. With success here, we specifically test
     with different firmware version and make sure it reflected in
     hammerd's touchpad info.

Change-Id: Ib30917d8376d4a2e8b6137daabad2341ac48d1f8
Signed-off-by: Chun-Ta Lin <itspeter@google.com>
Reviewed-on: https://chromium-review.googlesource.com/664937
Commit-Ready: Chun-ta Lin <itspeter@chromium.org>
Tested-by: Chun-ta Lin <itspeter@chromium.org>
Reviewed-by: Nicolas Boichat <drinkcat@chromium.org>
2017-09-14 22:25:02 -07:00

527 lines
13 KiB
C

/* 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 "common.h"
#include "console.h"
#include "touchpad_elan.h"
#include "gpio.h"
#include "hwtimer.h"
#include "hooks.h"
#include "i2c.h"
#include "task.h"
#include "timer.h"
#include "update_fw.h"
#include "util.h"
#include "usb_hid_touchpad.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)
/******************************************************************************/
/* 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_PRESSURE_CMD 0x010A
#define ETP_I2C_SET_CMD 0x0300
#define ETP_I2C_FW_CHECKSUM_CMD 0x030F
#define ETP_ENABLE_ABS 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 (1 << 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 (1 << 5)
#define ETP_FW_IAP_INTF_ERR (1 << 4)
#ifdef CONFIG_USB_UPDATE
/* The actual FW_SIZE depends on IC. */
#define FW_SIZE CONFIG_TOUCHPAD_VIRTUAL_SIZE
#define FW_PAGE_SIZE 64
#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;
} 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];
int rv;
buf[0] = reg;
buf[1] = reg >> 8;
i2c_lock(CONFIG_TOUCHPAD_I2C_PORT, 1);
rv = i2c_xfer(CONFIG_TOUCHPAD_I2C_PORT, CONFIG_TOUCHPAD_I2C_ADDR,
buf, sizeof(buf), (uint8_t *)val, sizeof(*val),
I2C_XFER_SINGLE);
i2c_lock(CONFIG_TOUCHPAD_I2C_PORT, 0);
return rv;
}
static int elan_tp_write_cmd(uint16_t reg, uint16_t val)
{
uint8_t buf[4];
int rv;
buf[0] = reg;
buf[1] = reg >> 8;
buf[2] = val;
buf[3] = val >> 8;
i2c_lock(CONFIG_TOUCHPAD_I2C_PORT, 1);
rv = i2c_xfer(CONFIG_TOUCHPAD_I2C_PORT, CONFIG_TOUCHPAD_I2C_ADDR,
buf, sizeof(buf), NULL, 0, I2C_XFER_SINGLE);
i2c_lock(CONFIG_TOUCHPAD_I2C_PORT, 0);
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;
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;
i2c_lock(CONFIG_TOUCHPAD_I2C_PORT, 1);
rv = i2c_xfer(CONFIG_TOUCHPAD_I2C_PORT, CONFIG_TOUCHPAD_I2C_ADDR,
NULL, 0, tp_buf, ETP_I2C_REPORT_LEN, I2C_XFER_SINGLE);
i2c_lock(CONFIG_TOUCHPAD_I2C_PORT, 0);
if (rv) {
CPRINTS("read report error");
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] & 0xf0) >> 4;
int height = finger[3] & 0x0f;
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].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;
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;
}
/* 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);
i2c_lock(CONFIG_TOUCHPAD_I2C_PORT, 1);
rv = i2c_xfer(CONFIG_TOUCHPAD_I2C_PORT, CONFIG_TOUCHPAD_I2C_ADDR,
NULL, 0, val, sizeof(val), I2C_XFER_SINGLE);
i2c_lock(CONFIG_TOUCHPAD_I2C_PORT, 0);
CPRINTS("reset rv %d buf=%04x", rv, *((uint16_t *)val));
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
/*
* Sanity 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_get_ic_page_count(void)
{
uint16_t ic_type;
elan_tp_read_cmd(ETP_I2C_OSM_VERSION_CMD, &ic_type);
CPRINTS("%s: ic_type:%04X.", __func__, ic_type);
switch (ic_type >> 8) {
case 0x09:
return 768;
case 0x0D:
return 896;
case 0x00:
return 1024;
}
return -1;
}
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;
}
/* 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)
{
uint8_t page_store[FW_PAGE_SIZE + 4];
uint16_t checksum = 0;
uint16_t rx_buf;
int i, rv;
for (i = 0; i < FW_PAGE_SIZE; i += 2)
checksum += ((uint16_t)(data[i + 1]) << 8) | (data[i]);
page_store[0] = ETP_I2C_IAP_REG_L;
page_store[1] = ETP_I2C_IAP_REG_H;
memcpy(page_store + 2, data, FW_PAGE_SIZE);
page_store[FW_PAGE_SIZE + 2 + 0] = checksum & 0xff;
page_store[FW_PAGE_SIZE + 2 + 1] = (checksum >> 8) & 0xff;
i2c_lock(CONFIG_TOUCHPAD_I2C_PORT, 1);
rv = i2c_xfer(CONFIG_TOUCHPAD_I2C_PORT, CONFIG_TOUCHPAD_I2C_ADDR,
page_store, sizeof(page_store), NULL, 0,
I2C_XFER_SINGLE);
i2c_lock(CONFIG_TOUCHPAD_I2C_PORT, 0);
if (rv)
return rv;
msleep(20);
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, page_count;
CPRINTS("%s %08x %d", __func__, offset, size);
if (offset == 0) {
/* Verify the IC type is aligned with defined firmware size */
page_count = elan_get_ic_page_count();
if (FW_PAGE_SIZE * page_count != FW_SIZE) {
CPRINTS("%s: IC(%d*%d) size and FW_SIZE(%d) mismatch",
__func__, page_count, FW_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 FW_PAGE_SIZE */
if (offset % FW_PAGE_SIZE)
return EC_ERROR_INVAL;
for (addr = (offset / FW_PAGE_SIZE) * FW_PAGE_SIZE;
addr < (offset + size); addr += FW_PAGE_SIZE) {
if (iap_addr > addr) /* Skip chunk */
continue;
rv = touchpad_update_page(data + addr - offset);
if (rv)
return rv;
CPRINTS("%s: page %d updated.", __func__, addr / FW_PAGE_SIZE);
}
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;
}
#endif
void elan_tp_interrupt(enum gpio_signal signal)
{
irq_ts = __hw_clock_source_read();
task_wake(TASK_ID_TOUCHPAD);
}
void elan_tp_task(void *u)
{
elan_tp_init();
while (1) {
task_wait_event(-1);
elan_tp_read_report();
}
}