/* 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 /* 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, &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 default: write_response(EC_RES_INVALID_COMMAND, seq, NULL, 0); } continue; discard: /* * Some error occurred: discard all data in the queue. */ discard_queue(); } }