mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-12-29 18:11:05 +00:00
This adds functions required for the master in EC-EC communication, requesting base battery static and dynamic information, and charger control. BRANCH=none BUG=b:65526215 TEST=Flash lux and wand, EC-EC communication works. Change-Id: I7a46ee3f5848d22c2c9bea7870cbd7e74141cf3d Signed-off-by: Nicolas Boichat <drinkcat@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/839201
355 lines
10 KiB
C
355 lines
10 KiB
C
/* 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 "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:65697620): Move these to some the second position of some battery
|
|
* array, depending on a config option.
|
|
*/
|
|
struct ec_response_battery_static_info base_battery_static;
|
|
struct ec_response_battery_dynamic_info base_battery_dynamic;
|
|
|
|
/*
|
|
* 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(&base_battery_dynamic, &data.resp.info,
|
|
sizeof(base_battery_dynamic));
|
|
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(&base_battery_static, &data.resp.info,
|
|
sizeof(base_battery_static));
|
|
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);
|
|
}
|
|
#endif /* CONFIG_EC_EC_COMM_BATTERY */
|