| /* Copyright 2017 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. |
| * |
| * EC-EC communication, functions and definitions for master. |
| */ |
| |
| #include "battery.h" |
| #include "common.h" |
| #include "console.h" |
| #include "crc8.h" |
| #include "ec_commands.h" |
| #include "ec_ec_comm_master.h" |
| #include "timer.h" |
| #include "uart.h" |
| #include "util.h" |
| |
| /* Console output macros */ |
| #define CPRINTF(format, args...) cprintf(CC_CHARGER, format, ## args) |
| |
| /* |
| * TODO(b:65697962): The packed structures below do not play well if we force EC |
| * host commands structures to be aligned on 32-bit boundary. There are ways to |
| * fix that, possibly requiring copying data around, or modifying |
| * uart_alt_pad_write_read API to write the actual slave response to a separate |
| * buffer. |
| */ |
| #ifdef CONFIG_HOSTCMD_ALIGNED |
| #error "Cannot define CONFIG_HOSTCMD_ALIGNED with EC-EC communication master." |
| #endif |
| |
| #define EC_EC_HOSTCMD_VERSION 4 |
| |
| /* Print extra debugging information */ |
| #undef EXTRA_DEBUG |
| |
| /* |
| * During early debugging, we would like to check that the error rate does |
| * grow out of control. |
| */ |
| #define DEBUG_EC_COMM_STATS |
| #ifdef DEBUG_EC_COMM_STATS |
| struct { |
| int total; |
| int errtimeout; |
| int errbusy; |
| int errunknown; |
| int errdatacrc; |
| int errcrc; |
| int errinval; |
| } comm_stats; |
| |
| #define INCR_COMM_STATS(var) (comm_stats.var++) |
| #else |
| #define INCR_COMM_STATS(var) |
| #endif |
| |
| /** |
| * Write a command on the EC-EC communication UART channel. |
| * |
| * @param command One of EC_CMD_*. |
| * @param data Packed structure with this layout: |
| * struct { |
| * struct { |
| * struct ec_host_request4 head; |
| * struct ec_params_* param; |
| * uint8_t crc8; |
| * } req; |
| * struct { |
| * struct ec_host_response4 head; |
| * struct ec_response_* info; |
| * uint8_t crc8; |
| * } resp; |
| * } __packed data; |
| * |
| * Where req is the request to be transmitted (head and crc8 are computed by |
| * this function), and resp is the response to be received (head integrity and |
| * crc8 are verified by this function). |
| * |
| * This format is required as the EC-EC UART is half-duplex, and all the |
| * transmitted data is received back, i.e. the master writes req, then reads |
| * req, followed by resp. |
| * |
| * When a command does not take parameters, param/crc8 must be omitted in |
| * tx structure. The same applies to rx structure if the response does not |
| * include a payload: info/crc8 must be omitted. |
| * |
| * @param req_len size of req.param (0 if no parameter is passed). |
| * @param resp_len size of resp.info (0 if no information is returned). |
| * @param timeout_us timeout in microseconds for the transaction to complete. |
| * |
| * @return |
| * - EC_SUCCESS on success. |
| * - EC_ERROR_TIMEOUT when remote end times out replying. |
| * - EC_ERROR_BUSY when UART is busy and cannot transmit currently. |
| * - EC_ERROR_CRC when the header or data CRC is invalid. |
| * - EC_ERROR_INVAL when the received header is invalid. |
| * - EC_ERROR_UNKNOWN on other error. |
| */ |
| static int write_command(uint16_t command, |
| uint8_t *data, int req_len, int resp_len, |
| int timeout_us) |
| { |
| /* Sequence number. */ |
| static uint8_t cur_seq; |
| int ret; |
| int hascrc, response_seq; |
| |
| struct ec_host_request4 *request_header = (void *)data; |
| /* Request (TX) length is header + (data + crc8), response follows. */ |
| int tx_length = |
| sizeof(*request_header) + ((req_len > 0) ? (req_len + 1) : 0); |
| |
| struct ec_host_response4 *response_header = |
| (void *)&data[tx_length]; |
| /* RX length is TX length + response from slave. */ |
| int rx_length = tx_length + |
| sizeof(*request_header) + ((resp_len > 0) ? (resp_len + 1) : 0); |
| |
| /* |
| * Make sure there is a gap between each command, so that the slave |
| * can recover its state machine after each command. |
| * |
| * TODO(b:65697962): We can be much smarter than this, and record the |
| * last transaction time instead of just sleeping blindly. |
| */ |
| usleep(10*MSEC); |
| |
| #ifdef DEBUG_EC_COMM_STATS |
| if ((comm_stats.total % 128) == 0) { |
| CPRINTF("UART %d (T%dB%d,U%dC%dD%dI%d)\n", comm_stats.total, |
| comm_stats.errtimeout, comm_stats.errbusy, |
| comm_stats.errunknown, comm_stats.errcrc, |
| comm_stats.errdatacrc, comm_stats.errinval); |
| } |
| #endif |
| |
| cur_seq = (cur_seq + 1) & |
| (EC_PACKET4_0_SEQ_NUM_MASK >> EC_PACKET4_0_SEQ_NUM_SHIFT); |
| |
| memset(request_header, 0, sizeof(*request_header)); |
| /* fields0: leave seq_dup and is_response as 0. */ |
| request_header->fields0 = |
| EC_EC_HOSTCMD_VERSION | /* version */ |
| (cur_seq << EC_PACKET4_0_SEQ_NUM_SHIFT); /* seq_num */ |
| /* fields1: leave command_version as 0. */ |
| if (req_len > 0) |
| request_header->fields1 |= EC_PACKET4_1_DATA_CRC_PRESENT_MASK; |
| request_header->command = command; |
| request_header->data_len = req_len; |
| request_header->header_crc = |
| crc8((uint8_t *)request_header, sizeof(*request_header)-1); |
| if (req_len > 0) |
| data[sizeof(*request_header) + req_len] = |
| crc8(&data[sizeof(*request_header)], req_len); |
| |
| ret = uart_alt_pad_write_read((void *)data, tx_length, |
| (void *)data, rx_length, timeout_us); |
| |
| INCR_COMM_STATS(total); |
| |
| #ifdef EXTRA_DEBUG |
| CPRINTF("EC-EC ret=%d/%d\n", ret, rx_length); |
| #endif |
| |
| if (ret != rx_length) { |
| if (ret == -EC_ERROR_TIMEOUT) { |
| INCR_COMM_STATS(errtimeout); |
| return EC_ERROR_TIMEOUT; |
| } |
| |
| if (ret == -EC_ERROR_BUSY) { |
| INCR_COMM_STATS(errbusy); |
| return EC_ERROR_BUSY; |
| } |
| |
| INCR_COMM_STATS(errunknown); |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| if (response_header->header_crc != |
| crc8((uint8_t *)response_header, |
| sizeof(*response_header)-1)) { |
| INCR_COMM_STATS(errcrc); |
| return EC_ERROR_CRC; |
| } |
| |
| hascrc = response_header->fields1 & EC_PACKET4_1_DATA_CRC_PRESENT_MASK; |
| response_seq = (response_header->fields0 & EC_PACKET4_0_SEQ_NUM_MASK) >> |
| EC_PACKET4_0_SEQ_NUM_SHIFT; |
| |
| /* |
| * Validate received header. |
| * Note that we _require_ data crc to be present if there is data to be |
| * read back, else we would not know how many bytes to read exactly. |
| */ |
| if ((response_header->fields0 & EC_PACKET4_0_STRUCT_VERSION_MASK) |
| != EC_EC_HOSTCMD_VERSION || |
| !(response_header->fields0 & |
| EC_PACKET4_0_IS_RESPONSE_MASK) || |
| response_seq != cur_seq || |
| (response_header->data_len > 0 && !hascrc) || |
| response_header->data_len != resp_len) { |
| INCR_COMM_STATS(errinval); |
| return EC_ERROR_INVAL; |
| } |
| |
| /* Check data CRC. */ |
| if (hascrc && data[rx_length - 1] != |
| crc8(&data[tx_length + sizeof(*request_header)], |
| resp_len)) { |
| INCR_COMM_STATS(errdatacrc); |
| return EC_ERROR_CRC; |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| /** |
| * handle error from write_command |
| * |
| * @param ret is return value from write_command |
| * @param request_result is data.resp.head.result (response result value) |
| * |
| * @return EC_RES_ERROR if ret is not EC_SUCCESS, else request_result. |
| */ |
| static int handle_error(const char *func, int ret, int request_result) |
| { |
| if (ret != EC_SUCCESS) { |
| /* Do not print busy errors as they just spam the console. */ |
| if (ret != EC_ERROR_BUSY) |
| CPRINTF("%s: tx error %d\n", func, ret); |
| return EC_RES_ERROR; |
| } |
| |
| if (request_result != EC_RES_SUCCESS) |
| CPRINTF("%s: cmd error %d\n", func, ret); |
| |
| return request_result; |
| } |
| |
| #ifdef CONFIG_EC_EC_COMM_BATTERY |
| int ec_ec_master_base_get_dynamic_info(void) |
| { |
| int ret; |
| struct { |
| struct { |
| struct ec_host_request4 head; |
| struct ec_params_battery_dynamic_info param; |
| uint8_t crc8; |
| } req; |
| struct { |
| struct ec_host_response4 head; |
| struct ec_response_battery_dynamic_info info; |
| uint8_t crc8; |
| } resp; |
| } __packed data; |
| |
| data.req.param.index = 0; |
| |
| ret = write_command(EC_CMD_BATTERY_GET_DYNAMIC, |
| (void *)&data, sizeof(data.req.param), |
| sizeof(data.resp.info), 15 * MSEC); |
| ret = handle_error(__func__, ret, data.resp.head.result); |
| if (ret != EC_RES_SUCCESS) |
| return ret; |
| |
| #ifdef EXTRA_DEBUG |
| CPRINTF("V: %d mV\n", data.resp.info.actual_voltage); |
| CPRINTF("I: %d mA\n", data.resp.info.actual_current); |
| CPRINTF("Remaining: %d mAh\n", data.resp.info.remaining_capacity); |
| CPRINTF("Cap-full: %d mAh\n", data.resp.info.full_capacity); |
| CPRINTF("Flags: %04x\n", data.resp.info.flags); |
| CPRINTF("V-desired: %d mV\n", data.resp.info.desired_voltage); |
| CPRINTF("I-desired: %d mA\n", data.resp.info.desired_current); |
| #endif |
| |
| memcpy(&battery_dynamic[BATT_IDX_BASE], &data.resp.info, |
| sizeof(battery_dynamic[BATT_IDX_BASE])); |
| return EC_RES_SUCCESS; |
| } |
| |
| int ec_ec_master_base_get_static_info(void) |
| { |
| int ret; |
| struct { |
| struct { |
| struct ec_host_request4 head; |
| struct ec_params_battery_static_info param; |
| uint8_t crc8; |
| } req; |
| struct { |
| struct ec_host_response4 head; |
| struct ec_response_battery_static_info info; |
| uint8_t crc8; |
| } resp; |
| } __packed data; |
| |
| data.req.param.index = 0; |
| |
| ret = write_command(EC_CMD_BATTERY_GET_STATIC, |
| (void *)&data, sizeof(data.req.param), |
| sizeof(data.resp.info), 15 * MSEC); |
| ret = handle_error(__func__, ret, data.resp.head.result); |
| if (ret != EC_RES_SUCCESS) |
| return ret; |
| |
| #ifdef EXTRA_DEBUG |
| CPRINTF("Cap-design: %d mAh\n", data.resp.info.design_capacity); |
| CPRINTF("V-design: %d mV\n", data.resp.info.design_voltage); |
| CPRINTF("Manuf: %s\n", data.resp.info.manufacturer); |
| CPRINTF("Model: %s\n", data.resp.info.model); |
| CPRINTF("Serial: %s\n", data.resp.info.serial); |
| CPRINTF("Type: %s\n", data.resp.info.type); |
| CPRINTF("C-count: %d\n", data.resp.info.cycle_count); |
| #endif |
| |
| memcpy(&battery_static[BATT_IDX_BASE], &data.resp.info, |
| sizeof(battery_static[BATT_IDX_BASE])); |
| return EC_RES_SUCCESS; |
| } |
| |
| int ec_ec_master_base_charge_control(int max_current, |
| int otg_voltage, |
| int allow_charging) |
| { |
| int ret; |
| struct { |
| struct { |
| struct ec_host_request4 head; |
| struct ec_params_charger_control ctrl; |
| uint8_t crc8; |
| } req; |
| struct { |
| struct ec_host_response4 head; |
| } resp; |
| } __packed data; |
| |
| data.req.ctrl.allow_charging = allow_charging; |
| data.req.ctrl.max_current = max_current; |
| data.req.ctrl.otg_voltage = otg_voltage; |
| |
| ret = write_command(EC_CMD_CHARGER_CONTROL, |
| (void *)&data, sizeof(data.req.ctrl), 0, 30 * MSEC); |
| |
| return handle_error(__func__, ret, data.resp.head.result); |
| } |
| |
| int ec_ec_master_hibernate(void) |
| { |
| int ret; |
| struct { |
| struct { |
| struct ec_host_request4 head; |
| struct ec_params_reboot_ec param; |
| } req; |
| struct { |
| struct ec_host_response4 head; |
| } resp; |
| } __packed data; |
| |
| data.req.param.cmd = EC_REBOOT_HIBERNATE; |
| data.req.param.flags = 0; |
| |
| ret = write_command(EC_CMD_REBOOT_EC, |
| (void *)&data, sizeof(data.req.param), 0, 30 * MSEC); |
| |
| return handle_error(__func__, ret, data.resp.head.result); |
| } |
| #endif /* CONFIG_EC_EC_COMM_BATTERY */ |