| /****************************************************************************** |
| * |
| * Copyright 2011, Cypress Semiconductor Corporation. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * 3. Neither the name of the Cypress Semiconductor Corporation, nor the names |
| * of its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY Cypress Semiconductor Corporation ''AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Cypress Semiconductor Corporation |
| * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| * The views and conclusions contained in the software and documentation are |
| * those of the authors and should not be interpreted as representing official |
| * policies, either expressed or implied, of Cypress Semiconductor Corporation. |
| * |
| ******************************************************************************* |
| * |
| * Authors: |
| * Dudley Du <dudl@cypress.com> |
| * Usage: |
| * Used to update Cypress Trackpad device firmware image from .iic or |
| * .bin files. |
| * e.g., update Cypress Trackpad device with new "new_image.iic" file: |
| * $ sudo ./cyapa_fw_update new_image.iic -f |
| * Version: |
| * 1.0.0 2011/07/26 Initial release of cyapa_fw_update utility tool. |
| */ |
| |
| #include <ctype.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <getopt.h> |
| #include <libgen.h> |
| #include <linux/limits.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include "cyapa.h" |
| |
| #ifdef DBG |
| #define prt_info(fmt, ...) printf(fmt, ##__VA_ARGS__) |
| #define prt_warn(fmt, ...) printf("WARNING: " fmt, ##__VA_ARGS__) |
| #define prt_err(fmt, ...) printf("ERROR: " fmt, ##__VA_ARGS__) |
| #define prt_dbg(fmt, ...) printf("DEBUG: %d: " fmt, __LINE__, ##__VA_ARGS__) |
| #define DBG_DUMP_DATA_BLOCK 1 |
| #else |
| #define prt_info(fmt, ...) printf(fmt, ##__VA_ARGS__) |
| #define prt_warn(fmt, ...) printf("WARNING: " fmt, ##__VA_ARGS__) |
| #define prt_err(fmt, ...) printf("ERROR: " fmt, ##__VA_ARGS__) |
| #define prt_dbg(fmt, ...) |
| #define DBG_DUMP_DATA_BLOCK 0 |
| #endif |
| |
| #define CYAPA_FW_UPDATE_VER "1.0.0" |
| |
| #define DEFAULT_PROGRAM_NAME "cyapa_fw_update" |
| #define DEFAULT_FW_BAK_FOLDER "/tmp/cypress" |
| #define DEFAULT_FW_BAK_IMAGE_NAME "/tmp/cypress/cyapa_bak_firmware.bin" |
| |
| #define FILE_TYPE_IIC 0 |
| #define FILE_TYPE_BIN 1 |
| |
| #define CYAPA_FW_OFFSET_START 0x0780 |
| #define CYAPA_FW_CHECKSUM_END 0x07FF |
| #define CYAPA_FW_OFFSET_END 0x7FFF |
| #define CYAPA_FW_SIZE (CYAPA_FW_OFFSET_END - \ |
| CYAPA_FW_OFFSET_START + 1) |
| #define CYAPA_BAK_READ_BLOCK_LEN 16 |
| #define CYAPA_FW_BLOCK_LEN 64 |
| #define CYAPA_FW_BLOCK_COUNT (CYAPA_FW_SIZE / CYAPA_FW_BLOCK_LEN) |
| #define CYAPA_FW_START_BLOCK (CYAPA_FW_OFFSET_START / CYAPA_FW_BLOCK_LEN) |
| |
| #define CYAPA_IIC_CMD_OP_NONE 0 |
| #define CYAPA_IIC_CMD_OP_READ 1 |
| #define CYAPA_IIC_CMD_OP_WRITE 2 |
| #define CYAPA_IIC_CMD_OP_DELAY 3 |
| #define CYAPA_IIC_CMD_OP_tries_WRITE 4 |
| |
| /* |
| * CYAPA firmware starts at an absolute offset. |
| * We remove this offset when converting between firmware address and binary |
| * file locations. |
| */ |
| #define CYAPA_FW_TO_BIN(x) ((x) - CYAPA_FW_OFFSET_START) |
| #define CYAPA_BIN_TO_FW(x) ((x) + CYAPA_FW_OFFSET_START) |
| |
| #define CYAPA_IDAC_0_START 0x7900 |
| #define CYAPA_IDAC_0_END 0x797F |
| #define CYAPA_IDAC_0_SIZE (CYAPA_IDAC_0_END - CYAPA_IDAC_0_START + 1) |
| #define CYAPA_IDAC_0_DEF 0x08 |
| |
| #define CYAPA_IDAC_1_START 0x7A00 |
| #define CYAPA_IDAC_1_END 0x7A7F |
| #define CYAPA_IDAC_1_SIZE (CYAPA_IDAC_1_END - CYAPA_IDAC_1_START + 1) |
| #define CYAPA_IDAC_1_DEF 0x08 |
| |
| #define CYAPA_IDAC_2_START 0x7B00 |
| #define CYAPA_IDAC_2_END 0x7BFF |
| #define CYAPA_IDAC_2_SIZE (CYAPA_IDAC_2_END - CYAPA_IDAC_2_START + 1) |
| #define CYAPA_IDAC_2_DEF 0x90 |
| |
| |
| struct args { |
| /* the value should be FILE_TYPE_IIC or FILE_TYPE_BIN. */ |
| int file_type; |
| bool backup_fw; |
| bool force; |
| bool convert; |
| |
| const char *new_fw_image; |
| const char *bak_fw_image; |
| |
| int fd_dev; |
| int fd_new_fw; |
| int fd_bak_fw; |
| }; |
| |
| /* |
| * command format: |
| * write data: |
| * byte 0 : operation. CYAPA_IIC_CMD_OP_WRITE |
| * byte 1 : length of behind real command data, from byte 2 to byte n.q |
| * byte 2 - byte n : real command as show in .iic file. |
| * byte 2 : I2C address, 0x67 for trackpad. |
| * byte 3 : I2C registers offset where byte 4 -byte n should be written. |
| * usually 0x00 for trackpad. |
| * byte 4 - byte n : command data to be written to trackpad. |
| * byte 4 : index, 0x00, 0x10, 0x20, 0x30, 0x40. |
| * |
| * read status: |
| * byte 0 : operation. CYAPA_IIC_CMD_OP_READ |
| * byte 1 : read length. |
| * |
| * delay: |
| * byte 0 : operation. CYAPA_IIC_CMD_OP_DELAY |
| * byte 1 - byte 4 : delay milliseconds (int). |
| */ |
| struct cmds_update_block { |
| int valid_cmds; |
| unsigned char cmds[8][24]; |
| }; |
| |
| |
| /* Global buffer for storing firmware image */ |
| unsigned char fw_buf[CYAPA_FW_SIZE]; |
| |
| |
| #if DBG_DUMP_DATA_BLOCK |
| void cyapa_dump_data_block(const char *str, unsigned char *buf, |
| unsigned int offset, int length) |
| { |
| char strbuf[2048]; |
| unsigned int rest_len = sizeof(strbuf); |
| char *p = strbuf; |
| int i, len; |
| |
| memset(strbuf, 0, sizeof(strbuf)); |
| len = snprintf(p, rest_len, "%s: offset 0x%04X, %d bytes:", |
| str, offset, length); |
| rest_len -= len; |
| p += len; |
| for (i = 0; i < length && rest_len; i++, p += len, rest_len -= len) { |
| len = snprintf(p, rest_len, " %02X", |
| *(unsigned char *)(buf + i)); |
| } |
| |
| prt_info("%s\n", strbuf); |
| } |
| |
| void cyapa_dump_delay_time(const char *str, int delay) |
| { |
| prt_info("%s: delay [%d] ms\n", str, delay); |
| } |
| #else |
| void cyapa_dump_data_block(const char *str, unsigned char *buf, |
| unsigned int offset, int length) {} |
| void cyapa_dump_delay_time(const char *str, int delay) {} |
| #endif |
| |
| void show_usage(char *name) |
| { |
| printf("Usage: %s <new-firmware-image> [options]\n", |
| name ? name : DEFAULT_PROGRAM_NAME); |
| printf("Options:\n"); |
| printf("\t -b, --backup\n"); |
| printf("\t Backup current firmware before updating" |
| " to new firmware.\n"); |
| printf("\t If -o option is not set, the default back up" |
| " path is \"/tmp/cypress\".\n"); |
| printf("\t If -o option is set, use the path specified in" |
| "-o <path>.\n"); |
| printf("\t -c, --convert <iic-path>\n"); |
| printf("\t Convert new-firmware-image (.iic) to a .bin file.\n"); |
| printf("\t Does not actually write firmware to the device.\n"); |
| printf("\t Use --output to specify the destination path.\n"); |
| printf("\t -o, --output <path>\n"); |
| printf("\t Specifies full path of the output file for where to\n"); |
| printf("\t back up a copy of the current trackpad firmware.\n"); |
| printf("\t Only valid when option -b is set and\n"); |
| printf("\t filename must have a \'bin\' extension.\n"); |
| printf("\t By default, without -o option, the back up path is:\n"); |
| printf("\t %s\n", DEFAULT_FW_BAK_IMAGE_NAME); |
| printf("\t If the requested update fails, this backed up copy\n"); |
| printf("\t will be rewritten to the trackpad.\n"); |
| printf("\t -f, --force\n"); |
| printf("\t Force new firmware to be updated to trackpad device\n"); |
| printf("\t and suppress any prompt information.\n"); |
| printf("\t -v, --version\n"); |
| printf("\t Print version information and exit.\n"); |
| printf("\t -h, --help\n"); |
| printf("\t Show this help information.\n"); |
| printf("NOTE:\n"); |
| printf(" This program must be executed as root\n"); |
| printf(" cyapa_fw_update program release version: %s\n\n", |
| CYAPA_FW_UPDATE_VER); |
| } |
| |
| void show_version_info(char *name) |
| { |
| printf("Cypress utility: %s %s\n\n", name, CYAPA_FW_UPDATE_VER); |
| printf("Copyright (C) 2011 Cypress Semiconductor Corporation.\n"); |
| printf("License BSD 2-Clause License or later.\n"); |
| printf("This is free software: you are free to change and" |
| " redistribute it.\n"); |
| printf("There is NO WARRANTY, to the extent permitted by law.\n\n"); |
| } |
| |
| static struct option options[] = { |
| {"backup", no_argument, NULL, 'b'}, |
| {"convert", no_argument, NULL, 'c'}, |
| {"force", no_argument, NULL, 'f'}, |
| {"help", no_argument, NULL, 'h'}, |
| {"output", required_argument, NULL, 'o'}, |
| {"version", no_argument, NULL, 'v'}, |
| {0, 0, 0, 0} |
| }; |
| |
| |
| bool ends_with(const char* name, const char* ext) |
| { |
| size_t nlen = strlen(name); |
| size_t elen = strlen(ext); |
| |
| return (nlen >= elen && !strcmp(&name[nlen-elen], ext)); |
| } |
| |
| |
| /** |
| * return value: |
| * 0 - parse and get valid input parameters. |
| * 1 - show program usage information. |
| * 2 - show program version information. |
| * < 0 - failed to parse and check input parameters. |
| */ |
| int check_input_args(int argc, char **argv, struct args *args) |
| { |
| int c; |
| |
| while (1) { |
| int index = 0; |
| |
| c = getopt_long(argc, argv, "bcfho:v", options, &index); |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| case 'b': |
| args->backup_fw = true; |
| break; |
| case 'c': |
| args->convert = true; |
| break; |
| case 'f': |
| args->force = true; |
| break; |
| case 'o': |
| args->bak_fw_image = optarg; |
| break; |
| case 'v': |
| return 2; |
| default: |
| return 1; |
| } |
| } |
| |
| /* The last option should be the path of a new firmware image */ |
| if (optind < argc) |
| args->new_fw_image = argv[optind++]; |
| |
| if (args->new_fw_image) { |
| if (ends_with(args->new_fw_image, ".iic")) |
| args->file_type = FILE_TYPE_IIC; |
| else if (ends_with(args->new_fw_image, ".bin")) |
| args->file_type = FILE_TYPE_BIN; |
| else |
| return 1; |
| } |
| |
| if (args->backup_fw && !args->bak_fw_image) { |
| int fd; |
| |
| args->bak_fw_image = DEFAULT_FW_BAK_IMAGE_NAME; |
| mkdir(DEFAULT_FW_BAK_FOLDER, 0777); |
| remove(args->bak_fw_image); |
| fd = creat(args->bak_fw_image, O_CREAT | |
| O_TRUNC | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); |
| if (fd < 0) |
| return 1; |
| close(fd); |
| } |
| |
| if (args->new_fw_image) |
| prt_info("Update new firmware image from: %s\n", |
| args->new_fw_image); |
| if (args->backup_fw && args->backup_fw) |
| prt_info("Backup firmware image from trackpad device to: %s\n", |
| args->bak_fw_image); |
| |
| return 0; |
| } |
| |
| #define MAX_PROGRESS_LENGTH 110 |
| #define DEFAULT_TERM_WIDTH 80 |
| int get_terminal_width(void) |
| { |
| int ret; |
| struct winsize sz; |
| |
| ret = ioctl(0, TIOCGWINSZ, &sz); |
| if (ret < 0) |
| return DEFAULT_TERM_WIDTH; |
| else |
| return (sz.ws_col > MAX_PROGRESS_LENGTH) ? |
| MAX_PROGRESS_LENGTH : |
| ((sz.ws_col < 20) ? DEFAULT_TERM_WIDTH : sz.ws_col); |
| } |
| |
| /** |
| * Output progress format: |
| * |----------------------------- | ddd% | |
| */ |
| #define SHOW_PROGRESS_INIT 0 |
| #define SHOW_PROGRESS_CONT 1 |
| #define SHOW_PROGRESS_EXIT 2 |
| void show_progress(int percent, int state, char *str) |
| { |
| int i; |
| int term_width; |
| int progress_count; |
| char buf[MAX_PROGRESS_LENGTH]; |
| static int last_str_len; |
| |
| #if DBG_DUMP_DATA_BLOCK |
| /* when enable dumping data, disable show progress. */ |
| return; |
| #endif |
| |
| if (state == SHOW_PROGRESS_INIT) { |
| prt_info("\r"); |
| if (str != NULL) |
| prt_info("%s", str); |
| last_str_len = 0; |
| |
| return; |
| } else if (state == SHOW_PROGRESS_EXIT) { |
| prt_info("\n"); |
| if (str != NULL) |
| prt_info("%s", str); |
| last_str_len = 0; |
| |
| return; |
| } |
| |
| if (state != SHOW_PROGRESS_CONT) |
| return; |
| |
| fflush(stdout); |
| for (i = 0; i < last_str_len; i++) |
| prt_info("\b"); |
| |
| percent = (percent < 0) ? 0 : ((percent > 100) ? 100 : percent); |
| term_width = get_terminal_width(); |
| progress_count = percent * (term_width - 10) / 100; |
| memset(buf, ' ', term_width); |
| buf[0] = '|'; |
| memset(&buf[1], '-', progress_count); |
| buf[term_width - 9] = '|'; |
| sprintf(&buf[term_width - 7], "%3d", percent); |
| buf[term_width - 4] = '%'; |
| buf[term_width - 2] = '|'; |
| buf[term_width - 1] = '\0'; |
| |
| prt_info("%s", buf); |
| last_str_len = strlen(buf); |
| fflush(stdout); |
| } |
| |
| /* |
| * sleep "msec" milliseconds. |
| */ |
| void msleep(unsigned int msec) |
| { |
| struct timespec ts; |
| int rc; |
| |
| ts.tv_sec = msec / 1000; |
| ts.tv_nsec = (msec - (ts.tv_sec * 1000)) * 1000000; |
| do { |
| rc = nanosleep(&ts, &ts); |
| } while (rc == -1 && errno == EINTR); |
| } |
| |
| #define TIMER_START 0 |
| #define TIMER_STOP 1 |
| void calculate_duration_time(int start_stop, const char *str) |
| { |
| static struct timeval last; |
| struct timeval tv; |
| struct timeval duration; |
| |
| gettimeofday(&tv, NULL); |
| |
| if (start_stop == TIMER_START) { |
| last = tv; |
| return; |
| } |
| |
| if (start_stop != TIMER_STOP) |
| return; |
| |
| timersub(&tv, &last, &duration); |
| prt_info("%s: %ld.%06ld seconds\n", str ?: "Duration time", |
| duration.tv_sec, duration.tv_usec); |
| last = tv; |
| } |
| |
| unsigned char cyapa_calculate_checksum(unsigned char *buf, int count) |
| { |
| int i; |
| unsigned char checksum = 0; |
| |
| for (i = 0; i < count; i++) |
| checksum += buf[i]; |
| |
| return checksum; |
| } |
| |
| int cyapa_read_reg(int fd, unsigned int offset, int bytes, unsigned char *buf) |
| { |
| int ret; |
| int tries = 3; |
| |
| do { |
| ret = (int)lseek(fd, (off_t)offset, SEEK_SET); |
| if (ret < 0) |
| continue; |
| |
| ret = (int)read(fd, buf, (ssize_t)bytes); |
| if (ret == bytes) |
| break; |
| } while (tries--); |
| |
| if (tries < 0) |
| return -1; |
| |
| return ret; |
| } |
| |
| int cyapa_write_reg(int fd, unsigned char *buf, unsigned int offset, int bytes) |
| { |
| int ret; |
| int tries = 3; |
| |
| do { |
| ret = (int)lseek(fd, (off_t)offset, SEEK_SET); |
| if (ret < 0) |
| continue; |
| |
| ret = (int)write(fd, buf, (ssize_t)bytes); |
| if (ret == bytes) |
| break; |
| } while (tries--); |
| |
| if (tries < 0) |
| return -1; |
| |
| return ret; |
| } |
| |
| int open_trackpad_dev(struct args *args) |
| { |
| char dev_name[64]; |
| |
| sprintf(dev_name, "/dev/%s", CYAPA_MISC_NAME); |
| args->fd_dev = open(dev_name, O_RDWR); |
| if (args->fd_dev < 0) { |
| prt_err("Cannot open device %s:\n", dev_name); |
| perror(NULL); |
| return -EFAULT; |
| } |
| |
| if (lseek(args->fd_dev, 0, SEEK_SET) < 0) { |
| prt_err("Cannot access device %s\n", dev_name); |
| perror(NULL); |
| close(args->fd_dev); |
| return -errno; |
| } |
| return 0; |
| } |
| |
| int open_new_fw_image(struct args *args) |
| { |
| args->fd_new_fw = open(args->new_fw_image, O_RDONLY); |
| if (args->fd_new_fw < 0) { |
| prt_err("Cannot open firmware image %s\n", args->new_fw_image); |
| perror(NULL); |
| return -EFAULT; |
| } |
| return 0; |
| } |
| |
| int open_bak_fw_image(struct args *args) |
| { |
| args->fd_bak_fw = open(args->bak_fw_image, |
| O_WRONLY | O_CREAT | O_TRUNC, |
| S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| if (args->fd_bak_fw < 0) { |
| prt_err("Cannot open backup file %s\n", args->bak_fw_image); |
| perror(NULL); |
| return -EFAULT; |
| } |
| return 0; |
| } |
| |
| int cyapa_fw_image_check(struct args *args) |
| { |
| int ret; |
| int tries = 3; |
| int ret_bytes; |
| unsigned int offset = 0; |
| unsigned char buf[64]; |
| int fd = args->fd_new_fw; |
| const char iic_identify[] = "00 00 FF 38 00 01 02 03 04 05 06 07"; |
| |
| ret_bytes = 0; |
| while ((ret_bytes != 64) && (tries-- > 0)) { |
| ret = (int)lseek(fd, (off_t)offset, SEEK_SET); |
| if (ret != (int)offset) |
| continue; |
| |
| memset(buf, 0, sizeof(buf)); |
| ret_bytes = (int)read(fd, buf, 64); |
| } |
| if (tries < 0) |
| return -1; |
| |
| if (args->file_type == FILE_TYPE_BIN) { |
| if (buf[0x28] != 0xC0 || buf[0x29] != 0xC1 || buf[0x2A] != 0xC2) |
| return -2; |
| } else { |
| if (strncmp(iic_identify, (const char *)&buf[5], |
| strlen(iic_identify))) |
| return -3; |
| } |
| |
| /* reset file cursor to the start of the image file. */ |
| tries = 3; |
| do { |
| if (lseek(args->fd_new_fw, (off_t)offset, SEEK_SET) < 0) |
| continue; |
| else |
| break; |
| } while (tries--); |
| if (tries < 0) |
| return -4; |
| |
| return 0; |
| } |
| |
| int cyapa_get_trackpad_run_mode(int fd, |
| struct cyapa_trackpad_run_mode *run_mode) |
| { |
| struct cyapa_misc_ioctl_data ioctl_data; |
| |
| memset(&ioctl_data, 0, sizeof(struct cyapa_misc_ioctl_data)); |
| ioctl_data.buf = (__u8 *)run_mode; |
| ioctl_data.len = sizeof(struct cyapa_trackpad_run_mode); |
| |
| if (ioctl(fd, CYAPA_GET_TRACKPAD_RUN_MODE, &ioctl_data) < 0) { |
| prt_dbg("Failed to get trackpad run mode state, %d\n", -errno); |
| return -errno; |
| } |
| |
| prt_dbg("GET: run_mode = %d, bootloader_state = %d\n", |
| run_mode->run_mode, run_mode->bootloader_state); |
| |
| return 0; |
| } |
| |
| int cyapa_set_trackpad_run_mode(int fd, |
| struct cyapa_trackpad_run_mode *run_mode) |
| { |
| struct cyapa_misc_ioctl_data ioctl_data; |
| |
| memset(&ioctl_data, 0, sizeof(struct cyapa_misc_ioctl_data)); |
| ioctl_data.buf = (__u8 *)run_mode; |
| ioctl_data.len = sizeof(struct cyapa_trackpad_run_mode); |
| |
| if (ioctl(fd, CYAYA_SEND_MODE_SWITCH_CMD, &ioctl_data) < 0) { |
| prt_dbg("failed to set trackpad device run mode state, %d\n", |
| --errno); |
| return -errno; |
| } |
| |
| prt_dbg("SET: run_mode = %d, bootloader_state = %d\n", |
| run_mode->run_mode, run_mode->bootloader_state); |
| |
| return 0; |
| } |
| |
| void cyapa_recovery_bootload_header(struct args *args) |
| { |
| unsigned char cmd[] = {0x00, 0xFF, 0x3C, |
| 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, |
| 0x07, 0x80}; |
| |
| cyapa_write_reg(args->fd_dev, cmd, 0, sizeof(cmd)); |
| msleep(10); |
| } |
| |
| int cyapa_set_bootloader_idle_mode(struct args *args) |
| { |
| int tries = 3; |
| int fd = args->fd_dev; |
| struct cyapa_trackpad_run_mode run_mode; |
| |
| while (tries-- > 0) { |
| memset(&run_mode, 0, sizeof(struct cyapa_trackpad_run_mode)); |
| if (cyapa_get_trackpad_run_mode(fd, &run_mode) < 0) |
| continue; |
| |
| if ((run_mode.bootloader_state == CYAPA_BOOTLOADER_IDLE_STATE) |
| && (run_mode.run_mode == CYAPA_BOOTLOADER_MODE)) |
| break; /* already in correct state. */ |
| |
| if (run_mode.run_mode == CYAPA_OPERATIONAL_MODE) { |
| run_mode.rev_cmd = CYAPA_CMD_APP_TO_IDLE; |
| if (cyapa_set_trackpad_run_mode(fd, &run_mode) < 0) |
| continue; |
| |
| msleep(300); |
| } else if ((run_mode.run_mode == CYAPA_BOOTLOADER_MODE) |
| && (run_mode.bootloader_state == |
| CYAPA_BOOTLOADER_ACTIVE_STATE)) { |
| run_mode.rev_cmd = CYAPA_CMD_ACTIVE_TO_IDLE; |
| if (cyapa_set_trackpad_run_mode(fd, &run_mode) < 0) |
| continue; |
| |
| msleep(300); |
| } else { |
| /* |
| * unknown trackpad states. |
| * try to recovery bootloader header registers. |
| */ |
| cyapa_recovery_bootload_header(args); |
| continue; |
| } |
| } |
| |
| if (tries < 0) |
| return -1; |
| |
| return 0; |
| } |
| |
| int cyapa_set_bootloader_active_mode(struct args *args) |
| { |
| int tries = 3; |
| int fd = args->fd_dev; |
| struct cyapa_trackpad_run_mode run_mode; |
| |
| while (tries-- > 0) { |
| memset(&run_mode, 0, sizeof(struct cyapa_trackpad_run_mode)); |
| if (cyapa_get_trackpad_run_mode(fd, &run_mode) < 0) |
| continue; |
| |
| if ((run_mode.bootloader_state == CYAPA_BOOTLOADER_ACTIVE_STATE) |
| && (run_mode.run_mode == CYAPA_BOOTLOADER_MODE)) |
| break; /* already in correct state. */ |
| |
| if (run_mode.run_mode == CYAPA_OPERATIONAL_MODE) { |
| if (cyapa_set_bootloader_idle_mode(args) < 0) |
| continue; |
| |
| } else if ((run_mode.run_mode == CYAPA_BOOTLOADER_MODE) |
| && (run_mode.bootloader_state == |
| CYAPA_BOOTLOADER_IDLE_STATE)) { |
| run_mode.rev_cmd = CYAPA_CMD_IDLE_TO_ACTIVE; |
| if (cyapa_set_trackpad_run_mode(fd, &run_mode) < 0) |
| continue; |
| |
| msleep(12000); |
| break; |
| } else { |
| /* |
| * unknown trackpad states. |
| * try to recovery bootloader header registers. |
| */ |
| cyapa_recovery_bootload_header(args); |
| continue; |
| } |
| } |
| |
| if (tries < 0) |
| return -1; |
| |
| return 0; |
| } |
| |
| int cyapa_set_app_operational_mode(struct args *args) |
| { |
| int tries = 3; |
| int fd = args->fd_dev; |
| struct cyapa_trackpad_run_mode run_mode; |
| |
| |
| while (tries-- > 0) { |
| memset(&run_mode, 0, sizeof(struct cyapa_trackpad_run_mode)); |
| if (cyapa_get_trackpad_run_mode(fd, &run_mode) < 0) |
| continue; |
| |
| if (run_mode.run_mode == CYAPA_OPERATIONAL_MODE) |
| break; /* already in correct state. */ |
| |
| if ((run_mode.run_mode == CYAPA_BOOTLOADER_MODE) |
| && (run_mode.bootloader_state == |
| CYAPA_BOOTLOADER_IDLE_STATE)) { |
| run_mode.rev_cmd = CYAPA_CMD_IDLE_TO_APP; |
| if (cyapa_set_trackpad_run_mode(fd, &run_mode) < 0) |
| continue; |
| |
| msleep(300); |
| } else if ((run_mode.run_mode == CYAPA_BOOTLOADER_MODE) |
| && (run_mode.bootloader_state == |
| CYAPA_BOOTLOADER_ACTIVE_STATE)) { |
| run_mode.rev_cmd = CYAPA_CMD_ACTIVE_TO_IDLE; |
| if (cyapa_set_trackpad_run_mode(fd, &run_mode) < 0) |
| continue; |
| |
| msleep(300); |
| } else { |
| /* |
| * unknown trackpad states. |
| * try to recovery bootloader header registers. |
| */ |
| cyapa_recovery_bootload_header(args); |
| continue; |
| } |
| } |
| |
| if (tries < 0) |
| return -1; |
| |
| return 0; |
| } |
| |
| int cyapa_get_firmware_version(struct args *args, |
| struct cyapa_firmware_ver *fw_version) |
| { |
| int ret = 0; |
| int tries = 3; |
| int fd = args->fd_dev; |
| struct cyapa_misc_ioctl_data ioctl_data; |
| struct cyapa_trackpad_run_mode run_mode; |
| unsigned char buf[32]; |
| |
| do { |
| memset(&run_mode, 0, sizeof(struct cyapa_trackpad_run_mode)); |
| ret = cyapa_get_trackpad_run_mode(fd, &run_mode); |
| if (ret == 0) |
| break; |
| |
| cyapa_recovery_bootload_header(args); |
| } while (tries--); |
| |
| if (ret < 0) |
| goto error; |
| |
| if (run_mode.run_mode == CYAPA_OPERATIONAL_MODE) { |
| memset(&ioctl_data, 0, sizeof(struct cyapa_misc_ioctl_data)); |
| ioctl_data.buf = (__u8 *)fw_version; |
| ioctl_data.len = sizeof(struct cyapa_firmware_ver); |
| |
| if (ioctl(args->fd_dev, |
| CYAPA_GET_FIRMWARE_VER, &ioctl_data) < 0) |
| goto error; |
| |
| return 0; |
| } |
| |
| /* |
| * firmware working in bootload mode. |
| * try to get firmware version from bootload header. |
| */ |
| memset(buf, 0, sizeof(buf)); |
| ret = cyapa_read_reg(fd, 0, 16, buf); |
| if ((ret < 0) || ((buf[0x01] & 0x10) != 0x10)) |
| goto error; |
| |
| /* have valid bootload head and firmware version. */ |
| if ((buf[0x0D] == 0xC0) && (buf[0x0E] == 0xC1) && (buf[0x0F] == 0xC2)) { |
| fw_version->major_ver = buf[0x0B]; |
| fw_version->minor_ver = buf[0x0C]; |
| } else |
| goto error; |
| |
| return 0; |
| |
| error: |
| fw_version->major_ver = 0; |
| fw_version->minor_ver = 0; |
| prt_warn("Unknown trackpad device firmware version.\n"); |
| |
| return ret; |
| } |
| |
| int cyapa_get_protocol_version(struct args *args) |
| { |
| struct cyapa_misc_ioctl_data ioctl_data; |
| struct cyapa_protocol_ver protocol_gen; |
| |
| memset(&ioctl_data, 0, sizeof(struct cyapa_misc_ioctl_data)); |
| ioctl_data.buf = (__u8 *)&protocol_gen; |
| ioctl_data.len = sizeof(struct cyapa_protocol_ver); |
| |
| if (ioctl(args->fd_dev, CYAPA_GET_PROTOCOL_VER, &ioctl_data) < 0) |
| return 0; |
| |
| return (int)protocol_gen.protocol_gen; |
| } |
| |
| /* |
| * Read a block of firmware bytes from trackpad and store them in the supplied |
| * buffer. |
| */ |
| int cyapa_read_fw_block(int fd, unsigned short offset, unsigned char *buf, |
| int len) |
| { |
| int tries = 3; |
| unsigned char cmd_str[] = {0x00, 0xFF, 0x3C, |
| 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; |
| unsigned char read_cmd[sizeof(cmd_str) + 2]; |
| unsigned char read_buf[len]; |
| unsigned char verify_buf[len]; |
| |
| if (len > 16) { |
| prt_err("Can't read more then 16 bytes firmware each time.\n"); |
| return -1; |
| } |
| |
| memcpy(read_cmd, cmd_str, sizeof(cmd_str)); |
| /* big endian for the value of offset when sending command. */ |
| read_cmd[sizeof(cmd_str)] = offset >> 8; |
| read_cmd[sizeof(cmd_str) + 1] = offset; |
| |
| cyapa_dump_data_block("TP: read cmd", read_cmd, 0, sizeof(read_cmd)); |
| |
| /* |
| * since trackpad doesn't protect i2c data with a CRC, |
| * verify data by reading it twice. |
| */ |
| while (tries-- > 0) { |
| cyapa_write_reg(fd, read_cmd, 0, sizeof(read_cmd)); |
| msleep(1); |
| cyapa_read_reg(fd, 16, len, read_buf); |
| |
| cyapa_write_reg(fd, read_cmd, 0, sizeof(read_cmd)); |
| msleep(1); |
| cyapa_read_reg(fd, 16, len, verify_buf); |
| |
| if (!memcmp(read_buf, verify_buf, len)) |
| break; |
| } |
| |
| if (tries < 0) |
| return -2; |
| |
| memcpy(buf, read_buf, len); |
| |
| cyapa_dump_data_block("TP: read data", buf, offset, len); |
| return len; |
| } |
| |
| /* |
| * Some values read from the trackpad are volatile and must be restored to |
| * defaults before being used to write back as firmware. |
| * |
| * Assumes: sizeof buffer >= CYAPA_FW_SIZE |
| */ |
| void cyapa_fixup_fw_buffer(unsigned char* buf) |
| { |
| /* |
| * Set IDAC regions in the image to a default pattern. |
| * The values read from the device are dynamically generated at runtime |
| * during calibration, and should be ignored. Setting these regions |
| * to the pattern will ensure the resulting fw backup image has the |
| * correct checksum. |
| */ |
| memset(&buf[CYAPA_FW_TO_BIN(CYAPA_IDAC_0_START)], CYAPA_IDAC_0_DEF, |
| CYAPA_IDAC_0_SIZE); |
| memset(&buf[CYAPA_FW_TO_BIN(CYAPA_IDAC_1_START)], CYAPA_IDAC_1_DEF, |
| CYAPA_IDAC_1_SIZE); |
| memset(&buf[CYAPA_FW_TO_BIN(CYAPA_IDAC_2_START)], CYAPA_IDAC_2_DEF, |
| CYAPA_IDAC_2_SIZE); |
| } |
| |
| int cyapa_backup_fw_from_trackpad(struct args *args) |
| { |
| int i; |
| int ret; |
| int offset; |
| int len; |
| int total; |
| |
| ret = cyapa_set_bootloader_idle_mode(args); |
| if (ret < 0) { |
| prt_err("Failed to reset trackpad device, unable switch to " |
| "firmware bootloader idle state, %d.\n", |
| ret); |
| return -1; |
| } |
| |
| calculate_duration_time(TIMER_START, NULL); |
| show_progress(0, SHOW_PROGRESS_INIT, |
| "Backup firmware from trackpad device in progress:\n"); |
| show_progress(0, SHOW_PROGRESS_CONT, NULL); |
| |
| total = CYAPA_FW_SIZE; |
| offset = CYAPA_FW_OFFSET_START; |
| len = CYAPA_BAK_READ_BLOCK_LEN; |
| for (i = 0; i < total; i += len) { |
| ret = cyapa_read_fw_block(args->fd_dev, offset + i, |
| &fw_buf[i], len); |
| if (ret < 0) { |
| prt_err("Failed to read firmware image, %d.\n", ret); |
| return -2; |
| } |
| |
| show_progress(100 * (i+1) / total, SHOW_PROGRESS_CONT, NULL); |
| } |
| |
| cyapa_fixup_fw_buffer(fw_buf); |
| |
| /* store read data to output file. */ |
| ret = write(args->fd_bak_fw, fw_buf, total); |
| if (ret != total) { |
| prt_err("Failed to write backup firmware image.\n"); |
| perror(NULL); |
| return -3; |
| } |
| |
| show_progress(0, SHOW_PROGRESS_EXIT, |
| "Backup firmware from trackpad device done.\n"); |
| calculate_duration_time(TIMER_STOP, |
| "Backup firmware form trackpad device duration time"); |
| |
| return total; |
| } |
| |
| /* |
| * routines for updating trackpad firmware from .iic firmware image file. |
| */ |
| int string_to_hex(const char *str) |
| { |
| int i, j; |
| int len = strlen(str); |
| char c = 0; |
| int d = 0; |
| int hex = 0; |
| |
| for (j = 0, i = len - 1; i >= 0; j++, i--) { |
| c = tolower(*(str + i)); |
| if ((c >= '0') && (c <= '9')) |
| d = c - '0'; |
| else |
| d = c - 'a' + 10; |
| hex += (d << 4 * j); |
| } |
| |
| return hex; |
| } |
| |
| int cyapa_iic_cmd_to_data(char *iic_buf, int *cmd_op, |
| unsigned char *outbuf, int *cmd_data_len) |
| { |
| int i = 0; |
| int j = 0; |
| char *p = NULL; |
| char *tmpp = NULL; |
| char hexstr[4]; |
| char buf[128]; |
| |
| memset(buf, 0, sizeof(buf)); |
| strcpy(buf, iic_buf); |
| p = buf; |
| |
| /* skip white-space if exists for the head of the string. */ |
| while (*p == ' ') |
| p++; |
| |
| /* delay specific time command. */ |
| if (*p == '[' && !strncmp((p + 1), "delay=", 6)) { |
| *cmd_op = CYAPA_IIC_CMD_OP_DELAY; |
| |
| tmpp = strrchr(p, ']'); |
| *tmpp = '\0'; |
| p = strchr(p, '=') + 1; |
| *(int *)outbuf = atoi(p); |
| *cmd_data_len = sizeof(int); |
| |
| return 0; |
| } |
| |
| if (*p == 'r') |
| *cmd_op = CYAPA_IIC_CMD_OP_READ; |
| else if (*p == 'w') |
| *cmd_op = CYAPA_IIC_CMD_OP_WRITE; |
| else { |
| /* |
| * this iis command line doesn't contain valid command data. |
| * skip this command line. |
| */ |
| *cmd_op = CYAPA_IIC_CMD_OP_NONE; |
| *cmd_data_len = 0; |
| return 0; |
| } |
| /* skip white-space. */ |
| while (*(++p) == ' ') |
| ; |
| |
| /* parse command data. */ |
| while (*p != 'p') { |
| if (*p == ' ') { |
| p++; |
| continue; |
| } |
| |
| i = 0; |
| memset(hexstr, 0, sizeof(hexstr)); |
| while (*p != ' ') |
| hexstr[i++] = *(p++); |
| if (!strcmp(hexstr, "x")) |
| j++; |
| else |
| outbuf[j++] = string_to_hex(hexstr); |
| } |
| *cmd_data_len = j; |
| |
| return 0; |
| } |
| |
| int cyapa_send_update_cmds(struct args *args, |
| struct cmds_update_block *iic_cmds) |
| { |
| int i; |
| int ret = 0; |
| int tries = 3; |
| int fd = args->fd_dev; |
| int cmd_op; |
| unsigned int offset; |
| int cmd_len; |
| int delay = 100; |
| unsigned char *cmd_buf = NULL; |
| unsigned char status[32]; |
| |
| if (iic_cmds->valid_cmds <= 0) |
| return 0; |
| |
| do { |
| for (i = 0; i < iic_cmds->valid_cmds; i++) { |
| /* byte 0: cmd_op, read/write/delay*/ |
| cmd_op = iic_cmds->cmds[i][0]; |
| |
| if (cmd_op == CYAPA_IIC_CMD_OP_WRITE) { |
| /* |
| * command format for read/write: |
| * byte 0: cmd_op, read/write; |
| * byte 1: total cmd len; |
| * byte 2: i2c device addr; |
| * byte 3: i2c register map offset; |
| * byte 4 - ... : cmd data. |
| */ |
| cmd_len = iic_cmds->cmds[i][1] - 4; |
| offset = iic_cmds->cmds[i][3]; |
| cmd_buf = &iic_cmds->cmds[i][4]; |
| cyapa_dump_data_block("CYAPA_IIC_CMD_OP_WRITE", |
| cmd_buf, offset, cmd_len); |
| ret = cyapa_write_reg(fd, cmd_buf, |
| offset, cmd_len); |
| if (ret < 0) |
| break; |
| } else if (cmd_op == CYAPA_IIC_CMD_OP_DELAY) { |
| /* |
| * byte 0: cmd_op, delay; |
| * byte 1: total cmd len; |
| * byte 2 - byte 5: time in milliseconds, int. |
| */ |
| delay = *(int *)&iic_cmds->cmds[i][2]; |
| cyapa_dump_delay_time("CYAPA_IIC_CMD_OP_DELAY", |
| delay); |
| msleep(delay); |
| } else if (cmd_op == CYAPA_IIC_CMD_OP_READ) { |
| /* |
| * byte 0: cmd_op, read; |
| * byte 1: total cmd len; |
| * byte 2: i2c device addr. |
| * byte 3 - ... : total bytes number indicates |
| * how many bytes should be read. |
| */ |
| memset(status, 0, sizeof(status)); |
| cmd_len = iic_cmds->cmds[i][1] - 3; |
| ret = cyapa_read_reg(fd, 0, cmd_len, status); |
| cyapa_dump_data_block("CYAPA_IIC_CMD_OP_READ", |
| status, 0, cmd_len); |
| if ((ret < 0) || (status[2] != 0x20)) |
| break; /* tries to write this block. */ |
| } |
| } |
| |
| if (i == iic_cmds->valid_cmds) |
| break; |
| } while (tries--); |
| |
| if (tries < 0) |
| return -1; |
| |
| return 0; |
| } |
| |
| int is_bootloader_terminal_cmd(struct cmds_update_block *iic_cmds) |
| { |
| int i; |
| unsigned char terminal_bootloader_cmd[] = {0x00, 0xFF, 0x3B, |
| 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; |
| |
| if (iic_cmds->valid_cmds != 1) |
| return 0; |
| |
| for (i = 0; i < sizeof(terminal_bootloader_cmd); i++) { |
| if (iic_cmds->cmds[0][i+4] != terminal_bootloader_cmd[i]) |
| break; |
| } |
| |
| return (i == sizeof(terminal_bootloader_cmd)) ? 1 : 0; |
| } |
| |
| #define IIC_CHECKSUM_START_BLOCK 0x001E |
| #define IIC_CHECKSUM_END_BLOCK 0x001F |
| #define IIC_APP_START_BLOCK 0x0020 |
| #define IIC_APP_END_BLOCK 0x01FF |
| int cyapa_update_fw_image_from_iic(struct args *args) |
| { |
| int ret; |
| int block_index; |
| int total_blocks; |
| int fd = args->fd_new_fw; |
| char iic_buf[128]; |
| int cmd_op; |
| unsigned char cmd_buf[CYAPA_FW_BLOCK_LEN]; |
| int cmd_data_len; |
| FILE *fp = fdopen(fd, "r"); |
| char *retp = NULL; |
| struct cmds_update_block iic_cmds; |
| |
| if (fp == NULL) { |
| fp = fopen(args->new_fw_image, "r"); |
| if (fp == NULL) { |
| prt_err("Failed to open new firmware image file, %d\n", |
| -errno); |
| ret = -errno; |
| goto error; |
| } |
| } |
| |
| calculate_duration_time(TIMER_START, NULL); |
| total_blocks = (IIC_APP_END_BLOCK - IIC_APP_START_BLOCK + 1) + |
| (IIC_CHECKSUM_END_BLOCK - IIC_CHECKSUM_START_BLOCK + 1); |
| show_progress(0, SHOW_PROGRESS_INIT, |
| "Update trackpad firmware from .iic file in progress:\n"); |
| show_progress(0, SHOW_PROGRESS_CONT, NULL); |
| |
| /* updating firmware. */ |
| memset(&iic_cmds, 0, sizeof(struct cmds_update_block)); |
| while (1) { |
| /* read a command line from .iic file. */ |
| memset(iic_buf, 0, sizeof(iic_buf)); |
| retp = fgets(iic_buf, sizeof(iic_buf), fp); |
| /* TODO(djkurtz): Why forcing errno to 0?!? */ |
| errno = 0; |
| if (retp == NULL) { |
| if (errno == 0) { |
| show_progress(0, SHOW_PROGRESS_EXIT, |
| "Update firmware from .iic" |
| " file done.\n"); |
| calculate_duration_time(TIMER_STOP, |
| "Update firmware from .iic file" |
| " duration time"); |
| ret = 0; |
| break; |
| } else { |
| prt_err("Failed to read next command line" |
| " from .iic file, %d.\n", -errno); |
| ret = -errno; |
| goto error; |
| } |
| } |
| |
| /* convert .iic command line to i2c data. */ |
| memset(cmd_buf, 0, sizeof(cmd_buf)); |
| cyapa_iic_cmd_to_data(iic_buf, &cmd_op, cmd_buf, &cmd_data_len); |
| |
| if (cmd_op == CYAPA_IIC_CMD_OP_NONE) |
| continue; |
| |
| iic_cmds.cmds[iic_cmds.valid_cmds][0] = (unsigned char)cmd_op; |
| iic_cmds.cmds[iic_cmds.valid_cmds][1] = |
| (unsigned char)(cmd_data_len + 2); |
| memcpy(&iic_cmds.cmds[iic_cmds.valid_cmds][2], |
| cmd_buf, (cmd_data_len + 2)); |
| iic_cmds.valid_cmds++; |
| |
| /* update new firmware block data to trackpad device. */ |
| if ((cmd_op == CYAPA_IIC_CMD_OP_READ) |
| || (is_bootloader_terminal_cmd(&iic_cmds))) { |
| ret = cyapa_send_update_cmds(args, &iic_cmds); |
| if (ret < 0) { |
| prt_err("Failed to write new firmware's .iic" |
| " block data, %d.\n", ret); |
| goto error; |
| } |
| |
| /* show new firmware update progress. */ |
| if (iic_cmds.valid_cmds > 5) { |
| block_index = iic_cmds.cmds[0][15] << 8 | |
| iic_cmds.cmds[0][16]; |
| if (block_index == IIC_CHECKSUM_START_BLOCK) |
| block_index = IIC_APP_END_BLOCK + 1; |
| else if (block_index == IIC_CHECKSUM_END_BLOCK) |
| block_index = IIC_APP_END_BLOCK + 2; |
| |
| show_progress(((block_index - |
| IIC_APP_START_BLOCK + 1) * 100 / |
| total_blocks), |
| SHOW_PROGRESS_CONT, |
| NULL); |
| } |
| |
| memset(&iic_cmds, 0, sizeof(struct cmds_update_block)); |
| } |
| } |
| |
| error: |
| fclose(fp); |
| |
| return ret; |
| } |
| |
| /* |
| * routines for updating trackpad firmware from .bin firmware image file. |
| */ |
| int cyapa_read_bin_fw_image(struct args *args, |
| unsigned short offset, unsigned char *buf, int len) |
| { |
| int ret = 0; |
| int read_bytes = 0; |
| int fd = args->fd_new_fw; |
| unsigned short read_offset; |
| int tries = 3; |
| |
| if (args->file_type != FILE_TYPE_BIN) |
| return -1; |
| |
| read_offset = offset - CYAPA_FW_OFFSET_START; |
| while ((read_bytes != len) && (tries-- > 0)) { |
| ret = (int)lseek(fd, (off_t)read_offset, SEEK_SET); |
| if (ret != (int)read_offset) |
| continue; |
| |
| read_bytes = (int)read(fd, buf, (ssize_t)len); |
| } |
| |
| if (tries < 0) |
| return -2; |
| |
| return 0; |
| } |
| |
| int cyapa_write_fw_image_block(struct args *args, |
| unsigned short offset, unsigned char *buf, int len) |
| { |
| int ret = 0; |
| int tries = 3; |
| int fd = args->fd_dev; |
| unsigned char status[3]; |
| unsigned char cmd_buf[32]; |
| unsigned char block_offset; |
| unsigned char block_buf[78]; |
| unsigned char *p; |
| int left_len; |
| int cmd_len; |
| unsigned short block_index; |
| |
| memset(block_buf, 0, sizeof(block_buf)); |
| /* set write command and security key bytes. */ |
| block_buf[0] = 0xFF; |
| block_buf[1] = 0x39; |
| block_buf[2] = 0x00; |
| block_buf[3] = 0x01; |
| block_buf[4] = 0x02; |
| block_buf[5] = 0x03; |
| block_buf[6] = 0x04; |
| block_buf[7] = 0x05; |
| block_buf[8] = 0x06; |
| block_buf[9] = 0x07; |
| /* block index is sent big endian. */ |
| block_index = (unsigned short)(offset / CYAPA_FW_BLOCK_LEN); |
| block_buf[10] = block_index >> 8; |
| block_buf[11] = block_index; |
| memcpy(&block_buf[12], buf, len); |
| /* checksum for block data and whole command. */ |
| block_buf[76] = cyapa_calculate_checksum(&block_buf[12], len); |
| block_buf[77] = cyapa_calculate_checksum(&block_buf[0], 77); |
| |
| do { |
| p = block_buf; |
| left_len = sizeof(block_buf); |
| block_offset = 0; |
| while (left_len > 0) { |
| cmd_len = (left_len >= 16) ? 16 : left_len; |
| |
| memset(cmd_buf, 0, sizeof(cmd_buf)); |
| cmd_buf[0] = block_offset; |
| memcpy(&cmd_buf[1], p, cmd_len); |
| |
| p += cmd_len; |
| block_offset += cmd_len; |
| left_len -= cmd_len; |
| |
| /* write block data to trackpad device. */ |
| cyapa_dump_data_block("CYAPA_WRITE_BLOCK_DATA", |
| cmd_buf, offset, (cmd_len + 1)); |
| if (cyapa_write_reg(fd, cmd_buf, 0, (cmd_len + 1)) < 0) |
| break; |
| } |
| |
| /* wait write command finished by trackpad device. */ |
| msleep(100); |
| cyapa_dump_delay_time("CYAPA_WRITE_BLOCK_DELAY", 100); |
| |
| if (left_len > 0) /* write failed, tries again. */ |
| continue; |
| |
| /* reset device pointer to offset 0. */ |
| memset(cmd_buf, 0, sizeof(cmd_buf)); |
| if (cyapa_write_reg(fd, cmd_buf, 0, 0) < 0) |
| prt_warn("Failed to reset trackpad device pointer.\n"); |
| |
| /* check block write command result status. */ |
| memset(status, 0, sizeof(status)); |
| ret = cyapa_read_reg(fd, 0, 3, status); |
| cyapa_dump_data_block("CYAPA_WRITE_STATUS_STATUS", |
| status, 0, 3); |
| if ((ret < 0) || (status[2] != 0x20)) |
| continue; |
| |
| /* block data written successfully. */ |
| break; |
| } while (tries--); |
| |
| if (tries < 0) |
| return -1; |
| |
| return 0; |
| } |
| |
| int cyapa_update_part_fw_image(struct args *args, |
| unsigned short start, unsigned end, int *blocks_written) |
| { |
| int blocks; |
| int ret = 0; |
| int total_blocks; |
| unsigned short offset; |
| unsigned char buf[CYAPA_FW_BLOCK_LEN]; |
| |
| total_blocks = (CYAPA_FW_OFFSET_END - CYAPA_FW_OFFSET_START + 1) / |
| CYAPA_FW_BLOCK_LEN; |
| blocks = 0; |
| offset = start + blocks * CYAPA_FW_BLOCK_LEN; |
| while ((offset + CYAPA_FW_BLOCK_LEN - 1) <= end) { |
| /* read data block from firmware image file. */ |
| memset(buf, 0, sizeof(buf)); |
| ret = cyapa_read_bin_fw_image(args, offset, buf, |
| CYAPA_FW_BLOCK_LEN); |
| if (ret < 0) { |
| prt_err("Failed to read %d bytes block data at 0x%04x" |
| " from .bin firmware image file, %d\n", |
| CYAPA_FW_BLOCK_LEN, offset, ret); |
| return ret; |
| } |
| |
| /* write firmware data block to trackpad device. */ |
| ret = cyapa_write_fw_image_block(args, offset, |
| buf, CYAPA_FW_BLOCK_LEN); |
| if (ret < 0) { |
| prt_err("Failed to write an image block data" |
| " to trackpad device, %d\n", ret); |
| return ret; |
| } |
| |
| /* update pointers. */ |
| blocks++; |
| offset = start + blocks * CYAPA_FW_BLOCK_LEN; |
| |
| ++(*blocks_written); |
| show_progress(((*blocks_written) * 100 / total_blocks), |
| SHOW_PROGRESS_CONT, NULL); |
| } |
| |
| return blocks; |
| } |
| |
| int cyapa_update_fw_image_from_bin(struct args *args) |
| { |
| int ret; |
| int blocks_written = 0; |
| |
| calculate_duration_time(TIMER_START, NULL); |
| show_progress(0, SHOW_PROGRESS_INIT, |
| "Update trackpad firmware from .bin file in progress:\n"); |
| show_progress(0, SHOW_PROGRESS_CONT, NULL); |
| |
| /* switch firmware working state to bootloader active state. */ |
| ret = cyapa_set_bootloader_active_mode(args); |
| if (ret < 0) { |
| prt_err("Failed switching firmware working state" |
| " to bootloader active state.\n"); |
| goto error; |
| } |
| |
| /* |
| * update firmware image to trackpad device. |
| * by default, should burn checksum blocks in the last step. |
| */ |
| ret = cyapa_update_part_fw_image(args, (CYAPA_FW_CHECKSUM_END + 1), |
| CYAPA_FW_OFFSET_END, &blocks_written); |
| if (ret < 0) { |
| prt_err("Failed to write firmware image blocks to" |
| " trackpad device.\n"); |
| goto error; |
| } |
| |
| ret = cyapa_update_part_fw_image(args, CYAPA_FW_OFFSET_START, |
| CYAPA_FW_CHECKSUM_END, &blocks_written); |
| if (ret < 0) { |
| prt_err("Failed to write checksum data blocks" |
| " to trackpad device.\n"); |
| goto error; |
| } |
| |
| show_progress(0, SHOW_PROGRESS_EXIT, |
| "Update firmware from .bin image file done.\n"); |
| calculate_duration_time(TIMER_STOP, |
| "Update firmware from .bin image file duration time"); |
| error: |
| /* set firmware back to bootloader idle mode working state. */ |
| if (cyapa_set_bootloader_idle_mode(args) < 0) { |
| prt_warn("Failed to switch firmware working state" |
| " to bootloader idle state.\n"); |
| } |
| |
| return ret; |
| } |
| |
| int cyapa_update_firmware(struct args *args) |
| { |
| int ret; |
| |
| if (args->file_type == FILE_TYPE_IIC) |
| ret = cyapa_update_fw_image_from_iic(args); |
| else |
| ret = cyapa_update_fw_image_from_bin(args); |
| |
| return ret; |
| } |
| |
| int check_run_as_root(char *name) |
| { |
| const char *program; |
| |
| if (getuid() == 0) |
| return 0; |
| |
| program = basename(name) ?: DEFAULT_PROGRAM_NAME; |
| |
| prt_warn("\"%s\" must be run as root.\n", program); |
| prt_warn("e.g.:\n"); |
| prt_warn(" sudo %s new-firmware [options]\n", program); |
| prt_warn("Please run \"%s -h\" for more information.\n", program); |
| |
| return -1; |
| } |
| |
| static bool prompt(const char *intro, const char *Q, const char *Y, |
| const char *N) |
| { |
| int c; |
| prt_warn("%s", intro); |
| do { |
| prt_info("%s", Q); |
| c = getchar(); |
| if (toupper(c) == 'Y') { |
| prt_info("%s", Y); |
| break; |
| } else { |
| prt_info("%s", N); |
| return false; |
| } |
| } while (c != EOF); |
| return true; |
| } |
| |
| static bool warn_backup_fail(void) |
| { |
| return prompt("Failed to open firmware backup file.\n", |
| "Continue update without backup? <Y/N>: ", |
| "Continuing firmware update...\n", |
| "Canceling firmware update.\n"); |
| } |
| |
| static size_t line_to_buf(char *line, unsigned char *buf, size_t len) |
| { |
| size_t cnt = 0; |
| unsigned char b; |
| char *next; |
| |
| do { |
| errno = 0; |
| b = strtoul(line, &next, 16); |
| if (line == next) |
| break; |
| line = next; |
| buf[cnt++] = b; |
| } while (cnt < len); |
| |
| return cnt; |
| } |
| |
| const unsigned char wr_cmd[] = { 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, |
| 0x05, 0x06, 0x07 }; |
| |
| /* |
| * Process a 'w' line from an iic file. |
| * |
| * 5 'w' lines, when taken together, form a 78-byte write fw block command, |
| * with a 64-byte payload. |
| */ |
| static void proc_iic_w_line(const unsigned char *buf, size_t len) |
| { |
| static unsigned char cmd[14 + CYAPA_FW_BLOCK_LEN] = { 0 }; |
| size_t offset; |
| size_t cmd_len; |
| ssize_t block; |
| unsigned char csum; |
| |
| offset = buf[0]; |
| cmd_len = offset + len - 1; |
| if (cmd_len > sizeof(cmd) || len == 0) |
| return; |
| |
| memcpy(&cmd[offset], &buf[1], len - 1); |
| |
| /* Start processing only if command is a full 78 bytes. */ |
| if (cmd_len != sizeof(cmd)) |
| return; |
| |
| if (memcmp(cmd, wr_cmd, sizeof(wr_cmd))) |
| return; |
| |
| /* Penultimate byte is crc of payload */ |
| csum = cyapa_calculate_checksum(&cmd[12], CYAPA_FW_BLOCK_LEN); |
| if (csum != cmd[sizeof(cmd)-2]) |
| return; |
| /* Last byte is crc of entire command */ |
| csum = cyapa_calculate_checksum(cmd, sizeof(cmd)-1); |
| if (csum != cmd[sizeof(cmd)-1]) |
| return; |
| |
| block = ((cmd[10] << 8) | cmd[11]) - CYAPA_FW_START_BLOCK; |
| if (block >= 0 && block < CYAPA_FW_BLOCK_COUNT) |
| memcpy(&fw_buf[block * CYAPA_FW_BLOCK_LEN], &cmd[12], |
| CYAPA_FW_BLOCK_LEN); |
| } |
| |
| /* |
| * An iic file consists of a sequence of commands, each on a single text line. |
| * The commands are 'r', 'w', or '[delay=X]'. |
| * |
| * Each write command has the following format: |
| * w <addr> <line_offset> |
| */ |
| static int convert_iic(struct args *args) |
| { |
| int ret; |
| FILE *f_iic; |
| FILE *f_bin; |
| char line[256]; |
| unsigned char buf[20]; |
| size_t n; |
| |
| if (!args->new_fw_image || !args->bak_fw_image) |
| return -EINVAL; |
| f_iic = fopen(args->new_fw_image, "r"); |
| if (f_iic == NULL) { |
| perror(NULL); |
| return -EIO; |
| } |
| |
| while (!feof(f_iic)) { |
| char *r = fgets(line, sizeof(line), f_iic); |
| if (r == NULL) |
| break; |
| |
| if (ferror(f_iic)) { |
| perror(NULL); |
| ret = -1; |
| goto close_iic; |
| } |
| |
| /* Ignore lines that aren't part of a write command */ |
| if (strncmp(line, "w 67 00 ", 8)) |
| continue; |
| |
| /* Extract remaining hex-encoded bytes on line into buf */ |
| n = line_to_buf(&line[8], buf, sizeof(buf)); |
| |
| /* Ignore "w 67 00 p" */ |
| if (n == 0) |
| continue; |
| |
| proc_iic_w_line(buf, n); |
| } |
| |
| f_bin = fopen(args->bak_fw_image, "wb"); |
| if (f_bin == NULL) { |
| perror(NULL); |
| ret = -EIO; |
| goto close_iic; |
| } |
| |
| printf("Writing %d bytes\n", CYAPA_FW_SIZE); |
| ret = fwrite(fw_buf, 1, CYAPA_FW_SIZE, f_bin); |
| if (ret != CYAPA_FW_SIZE) { |
| perror(NULL); |
| ret = -EIO; |
| goto close_bin; |
| } |
| |
| ret = 0; |
| close_bin: |
| fclose(f_bin); |
| close_iic: |
| fclose(f_iic); |
| return ret; |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int err_code = 0; |
| int fd_temp = 0; |
| struct args args = { 0 }; |
| struct cyapa_firmware_ver fw_version; |
| int old_protocol = -1; |
| int new_protocol = -1; |
| |
| /* only root priority user can execute this program. */ |
| if (check_run_as_root(argv[0]) < 0) |
| exit(-1); |
| |
| /* parse input parameters. */ |
| err_code = check_input_args(argc, argv, &args); |
| if (err_code == 2) { |
| show_version_info(basename(argv[0])); |
| exit(0); |
| } else if (err_code == 1) { |
| show_usage(basename(argv[0])); |
| exit(0); |
| } |
| |
| if (args.convert) |
| return convert_iic(&args); |
| |
| /* open device and firmware image files. */ |
| if (open_trackpad_dev(&args)) { |
| prt_err("unable to open trackpad device.\n"); |
| exit(-3); |
| } |
| |
| if (args.new_fw_image && open_new_fw_image(&args)) { |
| prt_err("unable to open new firmware file.\n"); |
| exit(-4); |
| } |
| |
| if (args.backup_fw && open_bak_fw_image(&args) && !warn_backup_fail()) { |
| prt_err("unable to open backup file.\n"); |
| exit(-5); |
| } |
| |
| if (args.new_fw_image && cyapa_fw_image_check(&args) < 0) { |
| prt_err("New firmware image file \"%s\" is invalid.\n", |
| args.new_fw_image); |
| prt_info("Please specify a valid new trackpad" |
| " firmware image file.\n"); |
| exit(-6); |
| } |
| |
| /* show firmware version before firmware updated. */ |
| memset(&fw_version, 0, sizeof(struct cyapa_firmware_ver)); |
| if (cyapa_get_firmware_version(&args, &fw_version) < 0) { |
| err_code = -6; |
| goto error; |
| } |
| old_protocol = cyapa_get_protocol_version(&args); |
| prt_info("Firmware version before updated is: <%02d.%02d>," |
| " protocol version is: GEN%d.\n\n", |
| fw_version.major_ver, fw_version.minor_ver, old_protocol); |
| |
| /* backup firmware image for trackpad device. */ |
| if (args.backup_fw) { |
| if (cyapa_backup_fw_from_trackpad(&args) < 0) { |
| if (warn_backup_fail()) |
| goto error; |
| } else { |
| prt_info("Backup firmware image from trackpad device" |
| " to \"%s\" successfully.\n\n", |
| args.bak_fw_image); |
| } |
| } |
| |
| /* If a new firmware file was provided, do update . */ |
| if (args.new_fw_image) { |
| /* reset firmware working state to bootloader idle state. */ |
| if (cyapa_set_bootloader_idle_mode(&args) < 0) { |
| prt_err("Failed to reset and switch firmware working" |
| " state to bootloader idle state.\n"); |
| err_code = -8; |
| goto error; |
| } |
| |
| if (cyapa_update_firmware(&args) < 0) { |
| prt_err("Update new firmware image %s failed.\n", |
| args.new_fw_image); |
| err_code = -9; |
| |
| if (args.backup_fw) { |
| prt_info("Tries to recovery trackpad device to" |
| " previously backed up firmware image.\n"); |
| fd_temp = args.fd_new_fw; |
| args.fd_new_fw = args.fd_bak_fw; |
| args.file_type = FILE_TYPE_BIN; |
| if (cyapa_update_firmware(&args) < 0) { |
| prt_err("Trackpad firmware recovery failed.\n"); |
| err_code = -5; |
| } else { |
| err_code = 0; |
| prt_info("Successfully restored backed up" |
| " trackpad firmware\n"); |
| } |
| args.fd_new_fw = fd_temp; |
| } |
| |
| if (err_code < 0) |
| goto error; |
| } |
| prt_info("Successfully updated trackpad to new firmware image %s\n\n", |
| args.new_fw_image); |
| } |
| |
| error: |
| /* reset trackpad device to operational mode. */ |
| if (cyapa_set_app_operational_mode(&args) < 0) { |
| prt_info("Failed to reset trackpad device to" |
| " operational mode.\n"); |
| prt_info("Need to reboot system to recover trackpad device.\n"); |
| } |
| |
| /* show firmware version after firmware updated. */ |
| new_protocol = cyapa_get_protocol_version(&args); |
| memset(&fw_version, 0, sizeof(struct cyapa_firmware_ver)); |
| if ((cyapa_get_firmware_version(&args, &fw_version) >= 0) |
| && (err_code == 0)) |
| prt_info("Firmware version after update is: v%02d.%02d," |
| " new protocol is: GEN%d\n", |
| fw_version.major_ver, |
| fw_version.minor_ver, |
| new_protocol); |
| |
| if (args.fd_dev > 0) |
| close(args.fd_dev); |
| if (args.fd_new_fw > 0) |
| close(args.fd_new_fw); |
| if (args.fd_bak_fw > 0) |
| close(args.fd_bak_fw); |
| |
| return err_code; |
| } |