Files
OpenCellular/common/ec_ec_comm_slave.c
Nicolas Boichat 6bbb5adab1 charge_state_v2: Add charge_set_output_current_limit function
This function sets up and enables "OTG" mode on the charger chip
(i.e. use the charger to provide power from the battery).

It also records the output current in curr.output_current, to
make sure that the charger loop is aware that current is provided
externally.

We also add a CONFIG_CHARGER_OTG to remove these functions on
boards that do not require it.

BRANCH=none
BUG=b:65697962
TEST=On wand, when discharging, battery status is updated every
     5 seconds (and not every 60 seconds).

Change-Id: Ibf93933436f3eb24552a8e1eb9d97522fca2ce79
Signed-off-by: Nicolas Boichat <drinkcat@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/842743
Reviewed-by: Randall Spangler <rspangler@chromium.org>
2018-01-04 21:52:01 -08:00

305 lines
8.0 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, task and functions for slave.
*/
#include "common.h"
#include "battery.h"
#include "charge_state_v2.h"
#include "console.h"
#include "crc8.h"
#include "ec_commands.h"
#include "ec_ec_comm_slave.h"
#include "extpower.h"
#include "hwtimer.h"
#include "hooks.h"
#include "queue.h"
#include "queue_policies.h"
#include "task.h"
#include "util.h"
#define CPRINTS(format, args...) cprints(CC_USBCHARGE, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_USBCHARGE, format, ## args)
/* Print extra debugging information */
#undef EXTRA_DEBUG
/*
* TODO(b:65697620): Move these to some other C file, depending on a config
* option.
*/
struct ec_response_battery_static_info base_battery_static;
struct ec_response_battery_dynamic_info base_battery_dynamic;
/* Set if the master allows the slave to charge the battery. */
static int charging_allowed;
/*
* Our command parameter buffer must be big enough to fit any command
* parameter, and crc byte.
*/
#define LARGEST_PARAMS_SIZE 8
BUILD_ASSERT(LARGEST_PARAMS_SIZE >=
sizeof(struct ec_params_battery_static_info));
BUILD_ASSERT(LARGEST_PARAMS_SIZE >=
sizeof(struct ec_params_battery_dynamic_info));
BUILD_ASSERT(LARGEST_PARAMS_SIZE >=
sizeof(struct ec_params_charger_control));
#define COMMAND_BUFFER_PARAMS_SIZE (LARGEST_PARAMS_SIZE + 1)
/*
* Maximum time needed to read a full command, commands are at most 17 bytes, so
* should not take more than 2ms to be sent at 115200 bps.
*/
#define COMMAND_TIMEOUT_US (5 * MSEC)
void ec_ec_comm_slave_written(struct consumer const *consumer, size_t count)
{
task_wake(TASK_ID_ECCOMM);
}
/*
* Discard all data from the input queue.
*
* Note that we always sleep for 1ms after clearing the queue, to make sure
* that we give enough time for the next byte to arrive.
*/
static void discard_queue(void)
{
do {
queue_advance_head(&ec_ec_comm_slave_input,
queue_count(&ec_ec_comm_slave_input));
usleep(1 * MSEC);
} while (queue_count(&ec_ec_comm_slave_input) > 0);
}
/* Write response to master. */
static void write_response(uint16_t res, int seq, const void *data, int len)
{
struct ec_host_response4 header;
uint8_t crc;
header.fields0 =
4 | /* version */
EC_PACKET4_0_IS_RESPONSE_MASK | /* is_response */
(seq << EC_PACKET4_0_SEQ_NUM_SHIFT); /* seq_num */
/* Set data_crc_present if there is data */
header.fields1 = (len > 0) ? EC_PACKET4_1_DATA_CRC_PRESENT_MASK : 0;
header.result = res;
header.data_len = len;
header.reserved = 0;
header.header_crc =
crc8((uint8_t *)&header, sizeof(header)-1);
QUEUE_ADD_UNITS(&ec_ec_comm_slave_output,
(uint8_t *)&header, sizeof(header));
if (len > 0) {
QUEUE_ADD_UNITS(&ec_ec_comm_slave_output, data, len);
crc = crc8(data, len);
QUEUE_ADD_UNITS(&ec_ec_comm_slave_output, &crc, sizeof(crc));
}
}
/*
* Read len bytes into buffer. Waiting up to COMMAND_TIMEOUT_US after start.
*
* Returns EC_SUCCESS or EC_ERROR_TIMEOUT.
*/
static int read_data(void *buffer, size_t len, uint32_t start)
{
uint32_t delta;
while (queue_count(&ec_ec_comm_slave_input) < len) {
delta = __hw_clock_source_read() - start;
if (delta >= COMMAND_TIMEOUT_US)
return EC_ERROR_TIMEOUT;
/* Every incoming byte wakes the task. */
task_wait_event(COMMAND_TIMEOUT_US - delta);
}
/* Fetch header */
QUEUE_REMOVE_UNITS(&ec_ec_comm_slave_input, buffer, len);
return EC_SUCCESS;
}
#ifdef CONFIG_EC_EC_COMM_BATTERY
static void handle_cmd_charger_control(
const struct ec_params_charger_control *params,
int data_len, int seq)
{
int ret = EC_RES_SUCCESS;
int prev_charging_allowed = charging_allowed;
if (data_len != sizeof(*params)) {
ret = EC_RES_INVALID_COMMAND;
goto out;
}
if (params->max_current >= 0) {
charge_set_output_current_limit(0, 0);
charge_set_input_current_limit(
MIN(MAX_CURRENT_MA, params->max_current), 0);
charging_allowed = params->allow_charging;
} else {
if (-params->max_current > MAX_OTG_CURRENT_MA ||
params->otg_voltage > MAX_OTG_VOLTAGE_MV) {
ret = EC_RES_INVALID_PARAM;
goto out;
}
/* Reset input current to minimum. */
charge_set_input_current_limit(CONFIG_CHARGER_INPUT_CURRENT, 0);
/* Setup and enable "OTG". */
charge_set_output_current_limit(-params->max_current,
params->otg_voltage);
charging_allowed = 0;
}
if (prev_charging_allowed != charging_allowed)
hook_notify(HOOK_AC_CHANGE);
out:
write_response(ret, seq, NULL, 0);
}
/*
* On dual-battery slave, we use the charging allowed signal from master to
* indicate whether external power is present.
*
* In most cases, this actually matches the external power status of the master
* (slave battery charging when AC is connected, or discharging when slave
* battery still has enough capacity), with one exception: when we do master to
* slave battery charging (in this case the "external" power is the master).
*/
int extpower_is_present(void)
{
return charging_allowed;
}
#endif
void ec_ec_comm_slave_task(void *u)
{
struct ec_host_request4 header;
/*
* If CONFIG_HOSTCMD_ALIGNED is set, it is important that params is
* aligned on a 32-bit boundary.
*/
uint8_t __aligned(4) params[COMMAND_BUFFER_PARAMS_SIZE];
unsigned int len, seq, hascrc, cmdver;
uint32_t start;
while (1) {
task_wait_event(-1);
if (queue_count(&ec_ec_comm_slave_input) == 0)
continue;
/* We got some data, start timeout counter. */
start = __hw_clock_source_read();
/* Wait for whole header to be available and read it. */
if (read_data(&header, sizeof(header), start)) {
CPRINTS("%s timeout (header)", __func__);
goto discard;
}
#ifdef EXTRA_DEBUG
CPRINTS("%s f0=%02x f1=%02x cmd=%02x, length=%d", __func__,
header.fields0, header.fields1,
header.command, header.data_len);
#endif
/* Ignore response (we wrote that ourselves) */
if (header.fields0 & EC_PACKET4_0_IS_RESPONSE_MASK)
goto discard;
/* Validate version and crc. */
if ((header.fields0 & EC_PACKET4_0_STRUCT_VERSION_MASK) != 4 ||
header.header_crc !=
crc8((uint8_t *)&header, sizeof(header)-1)) {
CPRINTS("%s header/crc error", __func__);
goto discard;
}
len = header.data_len;
hascrc = header.fields1 & EC_PACKET4_1_DATA_CRC_PRESENT_MASK;
if (hascrc)
len += 1;
/*
* Ignore commands that are too long to fit in our buffer.
*/
if (len > sizeof(params)) {
CPRINTS("%s len error (%d)", __func__, len);
/* Discard the data first, then write error back. */
discard_queue();
write_response(EC_RES_OVERFLOW, seq, NULL, 0);
goto discard;
}
seq = (header.fields0 & EC_PACKET4_0_SEQ_NUM_MASK) >>
EC_PACKET4_0_SEQ_NUM_SHIFT;
cmdver = header.fields1 & EC_PACKET4_1_COMMAND_VERSION_MASK;
/* Wait for the rest of the data to be available and read it. */
if (read_data(params, len, start)) {
CPRINTS("%s timeout (data)", __func__);
goto discard;
}
/* Check data CRC */
if (hascrc && params[len-1] != crc8(params, len-1)) {
CPRINTS("%s data crc error", __func__);
write_response(EC_RES_INVALID_CHECKSUM, seq, NULL, 0);
goto discard;
}
/* For now, all commands have version 0. */
if (cmdver != 0) {
CPRINTS("%s bad command version", __func__);
write_response(EC_RES_INVALID_VERSION, seq, NULL, 0);
continue;
}
switch (header.command) {
#ifdef CONFIG_EC_EC_COMM_BATTERY
case EC_CMD_BATTERY_GET_STATIC:
/* Note that we ignore the battery index parameter. */
write_response(EC_RES_SUCCESS, seq,
&base_battery_static,
sizeof(base_battery_static));
break;
case EC_CMD_BATTERY_GET_DYNAMIC:
/* Note that we ignore the battery index parameter. */
write_response(EC_RES_SUCCESS, seq,
&base_battery_dynamic,
sizeof(base_battery_dynamic));
break;
case EC_CMD_CHARGER_CONTROL: {
handle_cmd_charger_control((void *)params,
header.data_len, seq);
break;
}
#endif
default:
write_response(EC_RES_INVALID_COMMAND, seq,
NULL, 0);
}
continue;
discard:
/*
* Some error occurred: discard all data in the queue.
*/
discard_queue();
}
}