| /* 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(); |
| } |
| } |