mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-08 16:41:55 +00:00
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>
305 lines
8.0 KiB
C
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();
|
|
}
|
|
}
|