blob: 339f0f04907b2e2671cb085753bdd61b72eec8af [file] [log] [blame]
/* 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 "system.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
/* 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;
}
static void handle_cmd_reboot_ec(
const struct ec_params_reboot_ec *params,
int data_len, int seq)
{
int ret = EC_RES_SUCCESS;
if (data_len != sizeof(*params)) {
ret = EC_RES_INVALID_COMMAND;
goto out;
}
/* Only handle hibernate */
if (params->cmd != EC_REBOOT_HIBERNATE) {
ret = EC_RES_INVALID_PARAM;
goto out;
}
CPRINTS("Hibernating...");
system_hibernate(0, 0);
/* We should not be able to write back the response. */
out:
write_response(ret, seq, NULL, 0);
}
#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 = 0, 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,
&battery_static[BATT_IDX_MAIN],
sizeof(battery_static[BATT_IDX_MAIN]));
break;
case EC_CMD_BATTERY_GET_DYNAMIC:
/* Note that we ignore the battery index parameter. */
write_response(EC_RES_SUCCESS, seq,
&battery_dynamic[BATT_IDX_MAIN],
sizeof(battery_dynamic[BATT_IDX_MAIN]));
break;
case EC_CMD_CHARGER_CONTROL:
handle_cmd_charger_control((void *)params,
header.data_len, seq);
break;
#endif
case EC_CMD_REBOOT_EC:
handle_cmd_reboot_ec((void *)params,
header.data_len, seq);
break;
default:
write_response(EC_RES_INVALID_COMMAND, seq,
NULL, 0);
}
continue;
discard:
/*
* Some error occurred: discard all data in the queue.
*/
discard_queue();
}
}