| /* |
| * This file is part of the flashrom project. |
| * |
| * Copyright (C) 2012 Google Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 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. |
| * |
| * Neither the name of Google or the names of contributors or |
| * licensors may be used to endorse or promote products derived from this |
| * software without specific prior written permission. |
| * |
| * This software is provided "AS IS," without a warranty of any kind. |
| * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, |
| * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A |
| * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. |
| * GOOGLE INC AND ITS LICENSORS SHALL NOT BE LIABLE |
| * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING |
| * OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL |
| * GOOGLE OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, |
| * OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR |
| * PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF |
| * LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, |
| * EVEN IF GOOGLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. |
| */ |
| |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "file.h" |
| #include "flash.h" |
| #if USE_CROS_EC_LOCK == 1 |
| #include "cros_ec_lock.h" |
| #endif |
| #include "cros_ec_commands.h" |
| #include "programmer.h" |
| #include "cros_ec.h" |
| |
| #define SYSFS_I2C_DEV_ROOT "/sys/bus/i2c/devices" |
| #define CROS_EC_I2C_DEVICE_NAME1 "cros-ec-i2c" /* upstream name */ |
| #define CROS_EC_I2C_DEVICE_NAME2 "chromeos-ec-i2c" /* 3.4 name */ |
| #define CROS_EC_I2C_ADDRESS 0x1e |
| |
| /* v2 protocol bytes |
| * OUT: (version, command, size, ... request ..., checksum) */ |
| #define CROS_EC_PROTO_BYTES_V2_OUT 4 |
| /* IN: (command, size, ... response ..., checkcum) */ |
| #define CROS_EC_PROTO_BYTES_V2_IN 3 |
| |
| /* |
| * Flash erase on a 1024 byte chunk takes about 22.05ms on STM32-based CROS_EC. |
| * We'll leave about a half millisecond extra for other overhead to avoid |
| * polling the status too aggressively. |
| * |
| * TODO: determine better delay value or mechanism for all chips and commands. |
| */ |
| #define STM32_ERASE_DELAY 22500 /* 22.5ms */ |
| #define CROS_EC_COMMAND_RETRIES 5 |
| #define CROS_EC_LOCK_TIMEOUT_SECS 30 /* 30 secs */ |
| |
| |
| static int ec_timeout_usec = 1000000; |
| |
| static unsigned int bus; |
| |
| static int cros_ec_i2c_shutdown(void *data) |
| { |
| #if USE_CROS_EC_LOCK == 1 |
| release_cros_ec_lock(); |
| #endif |
| |
| return linux_i2c_shutdown(data); |
| } |
| |
| void delay_for_command(int command) |
| { |
| switch (command) { |
| case EC_CMD_FLASH_ERASE: |
| programmer_delay(STM32_ERASE_DELAY); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* returns 0 if command is successful, <0 to indicate timeout or error */ |
| static int command_response(int command, int version, uint8_t response_code) |
| { |
| uint8_t *status_cmd; |
| struct ec_response_get_comms_status status; |
| int i, status_cmd_len, ret = -EC_RES_TIMEOUT; |
| int csum; |
| |
| if (response_code != EC_RES_IN_PROGRESS) |
| return -response_code; |
| |
| status_cmd_len = CROS_EC_PROTO_BYTES_V2_OUT; |
| status_cmd = malloc(status_cmd_len); |
| status_cmd[0] = EC_CMD_VERSION0 + version; |
| status_cmd[1] = EC_CMD_GET_COMMS_STATUS; |
| status_cmd[2] = 0; |
| |
| csum = status_cmd[0] + status_cmd[1] + status_cmd[2]; |
| status_cmd[3] = csum & 0xff; |
| |
| for (i = 1; i <= CROS_EC_COMMAND_RETRIES; i++) { |
| /* |
| * The first retry might work practically immediately, so |
| * skip the delay for the first retry. |
| */ |
| if (i != 1) |
| delay_for_command(command); |
| |
| msg_pspew("retry %d / %d\n", i, CROS_EC_COMMAND_RETRIES); |
| ret = linux_i2c_xfer(bus, CROS_EC_I2C_ADDRESS, |
| &status, sizeof(status), |
| status_cmd, status_cmd_len); |
| |
| if (ret) { |
| msg_perr("%s(): linux_i2c_xfer() failed: %d\n", |
| __func__, ret); |
| ret = -EC_RES_ERROR; |
| break; |
| } |
| |
| if (!(status.flags & EC_COMMS_STATUS_PROCESSING)) { |
| ret = -EC_RES_SUCCESS; |
| break; |
| } |
| } |
| |
| free(status_cmd); |
| return ret; |
| } |
| |
| /* |
| * cros_ec_command_i2c - (protocol v2) Issue command to CROS_EC over I2C |
| * |
| * @command: command code |
| * @outdata: data to send to EC |
| * @outsize: number of bytes in outbound payload |
| * @indata: (unallocated) buffer to store data received from EC |
| * @insize: number of bytes in inbound payload |
| * |
| * Protocol-related details will be handled by this function. The outdata |
| * and indata buffers will contain payload data (if any); command and response |
| * codes as well as checksum data are handled transparently by this function. |
| * |
| * Returns >=0 for success, or negative if other error. |
| */ |
| static int cros_ec_command_i2c(int command, int version, |
| const void *outdata, int outsize, |
| void *indata, int insize) { |
| int ret = -1; |
| uint8_t *req_buf = NULL, *resp_buf = NULL; |
| int req_len = 0, resp_len = 0; |
| int i, csum; |
| |
| if (version > 1) { |
| msg_perr("%s() version >1 not supported yet.\n", __func__); |
| return -EC_RES_INVALID_VERSION; |
| } |
| |
| req_len = outsize + CROS_EC_PROTO_BYTES_V2_OUT; |
| req_buf = calloc(1, req_len); |
| if (!req_buf) |
| goto done; |
| |
| req_buf[0] = version + EC_CMD_VERSION0; |
| req_buf[1] = command; |
| req_buf[2] = outsize; |
| csum = req_buf[0] + req_buf[1] + req_buf[2]; |
| /* copy message payload and compute checksum */ |
| memcpy(&req_buf[3], outdata, outsize); |
| for (i = 0; i < outsize; i++) { |
| csum += *((uint8_t *)outdata + i); |
| } |
| req_buf[req_len - 1] = csum & 0xff; |
| |
| msg_pspew("%s: req_buf: ", __func__); |
| for (i = 0; i < req_len; i++) |
| msg_pspew("%02x ", req_buf[i]); |
| msg_pspew("\n"); |
| |
| resp_len = insize + CROS_EC_PROTO_BYTES_V2_IN; |
| resp_buf = calloc(1, resp_len); |
| if (!resp_buf) |
| goto done; |
| |
| ret = linux_i2c_xfer(bus, CROS_EC_I2C_ADDRESS, |
| resp_buf, resp_len, req_buf, req_len); |
| if (ret) { |
| msg_perr("%s(): linux_i2c_xfer() failed: %d\n", __func__, ret); |
| ret = -EC_RES_ERROR; |
| goto done; |
| } |
| |
| msg_pspew("%s: resp_buf: ", __func__); |
| for (i = 0; i < resp_len; i++) |
| msg_pspew("%02x ", resp_buf[i]); |
| msg_pspew("\n"); |
| |
| ret = command_response(command, version, resp_buf[0]); |
| if (ret) { |
| msg_pdbg("command 0x%02x returned an error %d\n", command, ret); |
| goto done; |
| } |
| |
| resp_len = resp_buf[1]; |
| if (resp_len > insize) { |
| msg_perr("%s(): responsed size is too large %d > %d\n", |
| __func__, resp_len, insize); |
| ret = -EC_RES_ERROR; |
| goto done; |
| } |
| |
| if (insize) { |
| /* copy response packet payload and compute checksum */ |
| csum = resp_buf[0] + resp_buf[1]; |
| for (i = 0; i < resp_len; i++) |
| csum += resp_buf[i + 2]; |
| csum &= 0xff; |
| |
| if (csum != resp_buf[resp_len + 2]) { |
| msg_pdbg("bad checksum (got 0x%02x from EC, calculated " |
| "0x%02x\n", resp_buf[resp_len + 2], csum); |
| ret = -EC_RES_INVALID_CHECKSUM; |
| goto done; |
| } |
| |
| memcpy(indata, &resp_buf[2], resp_len); |
| } |
| ret = resp_len; |
| done: |
| if (resp_buf) |
| free(resp_buf); |
| if (req_buf) |
| free(req_buf); |
| return ret; |
| } |
| |
| static struct cros_ec_priv cros_ec_i2c_priv = { |
| .detected = 0, |
| .ec_command = cros_ec_command_i2c, |
| }; |
| |
| static struct opaque_programmer opaque_programmer_cros_ec_i2c = { |
| /* These should be EC_PROTO2_MAX_PARAM_SIZE but for now we |
| * use values from earlier on to be safe. */ |
| .max_data_read = 128, |
| .max_data_write = EC_FLASH_WRITE_VER0_SIZE, |
| |
| .probe = cros_ec_probe_size, |
| .read = cros_ec_read, |
| .write = cros_ec_write, |
| .erase = cros_ec_block_erase, |
| }; |
| |
| int cros_ec_probe_i2c(const char *name) |
| { |
| const char *path, *s; |
| int ret = 1; |
| int old_timeout = ec_timeout_usec; |
| |
| if (alias && alias->type != ALIAS_EC) |
| return 1; |
| |
| if (cros_ec_parse_param(&cros_ec_i2c_priv)) |
| return 1; |
| |
| if (cros_ec_i2c_priv.dev && strcmp(cros_ec_i2c_priv.dev, "ec")) { |
| msg_pdbg("cros_ec_i2c only supports \"ec\" type devices.\n"); |
| return 1; |
| } |
| |
| #if USE_CROS_EC_LOCK == 1 |
| if (acquire_cros_ec_lock(CROS_EC_LOCK_TIMEOUT_SECS) < 0) { |
| msg_gerr("Could not acquire CROS_EC lock.\n"); |
| return 1; |
| } |
| #endif |
| |
| msg_pdbg("%s: probing for CROS_EC on I2C...\n", __func__); |
| |
| /* |
| * We look for the device using two possible names (since the EC landed |
| * upstream with a different name than the ChromeOS 3.4 kernel). |
| */ |
| path = scanft(SYSFS_I2C_DEV_ROOT, "name", CROS_EC_I2C_DEVICE_NAME1, 1); |
| if (!path) { |
| path = scanft(SYSFS_I2C_DEV_ROOT, "name", CROS_EC_I2C_DEVICE_NAME2, |
| 1); |
| if (!path) { |
| msg_pdbg("CROS_EC I2C device not found\n"); |
| goto cros_ec_probe_i2c_done; |
| } |
| } |
| |
| /* |
| * i2c-* may show up more than once in the path (especially in the |
| * case of the MFD with passthru I2C adapter), so use whichever |
| * instance shows up last. |
| */ |
| for (s = path + strlen(path) - 4; s > path; s--) { |
| if (!strncmp(s, "i2c-", 4)) |
| break; |
| } |
| |
| if ((s == path) || (sscanf(s, "i2c-%u", &bus) != 1)) { |
| msg_perr("Unable to parse I2C bus number\n"); |
| goto cros_ec_probe_i2c_done; |
| } |
| |
| msg_pdbg("Opening CROS_EC (bus %u, addr 0x%02x)\n", bus, CROS_EC_I2C_ADDRESS); |
| if (linux_i2c_open(bus, CROS_EC_I2C_ADDRESS, 1)) |
| goto cros_ec_probe_i2c_done; |
| |
| /* reduce timeout period temporarily in case EC is not present */ |
| ec_timeout_usec = 25000; |
| if (cros_ec_test(&cros_ec_i2c_priv)) { |
| linux_i2c_close(); |
| goto cros_ec_probe_i2c_done; |
| } |
| |
| if (register_shutdown(cros_ec_i2c_shutdown, NULL)) |
| goto cros_ec_probe_i2c_done; |
| |
| cros_ec_set_max_size(&cros_ec_i2c_priv, &opaque_programmer_cros_ec_i2c); |
| |
| msg_pdbg("CROS_EC detected on I2C bus\n"); |
| register_opaque_programmer(&opaque_programmer_cros_ec_i2c); |
| cros_ec_i2c_priv.detected = 1; |
| cros_ec_priv = &cros_ec_i2c_priv; |
| ret = 0; |
| |
| cros_ec_probe_i2c_done: |
| ec_timeout_usec = old_timeout; |
| free((void*)path); |
| return ret; |
| } |